【问题标题】:Read WAV files in python在 python 中读取 WAV 文件
【发布时间】:2016-06-01 19:51:05
【问题描述】:
import wave,struct
f = wave.open('bird.wav', 'r')

for i in range(5,10):
    frame = f.readframes(i)
    print frame
struct.unpack('<H',frame)

我使用上面的代码从 python 中的立体声 wav 文件中提取字节。但是,我得到的不是字节,而是一些乱码。使用struct.unpack() 函数我得到以下错误

"解包需要长度为 2 的字符串参数"

我在代码中做了哪些更改以打印 1 和 0 中的这些字节?我想稍后修改用于隐写术的音频帧的 LSB。

【问题讨论】:

  • 乱码可能是字节,它们只是以这种方式打印。是什么让你认为他们不是?你试过print(type(frame))吗?
  • @rofls - 类型是 str。如何以 1 和 0 打印这些字节?
  • 嗯,很奇怪。那么,你最终想用 .wav 文件做什么呢?只打印 1 和 0?无论如何,Python 并不会真正将二进制或机器数据打印为 1 和 0……您可以使用 bin 将数字转换为二进制字符串,但这实际上并没有多大帮助。如果数据是字节,在 Python2 中,字符串看起来像 b'some string'。您可能见过以 u 开头的字符串,u'some string',即 Unicode。
  • @rofls - 我想稍后修改音频帧的 LSB 以进行隐写术。此外,使用 bin 会出现以下错误“str' object cannot be Explained as an index”
  • 对不起,我不建议使用 bin :) 我只是说它很可能是二进制数据,这就是为什么它看起来像胡言乱语。你试过在谷歌上搜索“lsb audio steganography python”吗?有一些现有的包,虽然看起来它们是面向图像的......

标签: python wav unpack steganography


【解决方案1】:

我不确定您为什么要以二进制形式打印这些字节,但这样做很容易。

您需要将字节转换为整数,然后使用str.format 方法对其进行格式化,旧的% 样式格式不做位。

进行这种转换的简单方法是使用ord 函数,但对于大量字节,最好通过创建bytearray 一次性转换它们。

#Some bytes, using hexadecimal escape codes
s = '\x01\x07\x0f\x35\xad\xff'
print ' '.join(['{0:08b}'.format(ord(c)) for c in s])

b = bytearray(s)
print ' '.join(['{0:08b}'.format(u) for u in b])

输出

00000001 00000111 00001111 00110101 10101101 11111111
00000001 00000111 00001111 00110101 10101101 11111111

一般来说,十六进制比二进制更方便阅读。

from binascii import hexlify

print hexlify(s)
print ' '.join(['%02X' % u for u in b])
print ' '.join(['%02X' % ord(c) for c in s])
print ' '.join(['{0:02X}'.format(ord(c)) for c in s])    

输出

01070f35adff
01 07 0F 35 AD FF
01 07 0F 35 AD FF
01 07 0F 35 AD FF

我刚刚看到您对隐写术的评论。旋转字节的最方便的方法是使用bytearray。您可以使用str 函数轻松地将bytearray 转换回字节字符串。

print hexlify(str(b))        

输出

01070f35adff

官方 Python 文档中描述了字符串格式化选项。对于旧的% 样式格式,请参阅5.6.2. String Formatting Operations。对于现代str.format 选项,请参阅7.1.3. Format String Syntax7.1.3.1. Format Specification Mini-Language

{0:08b} 中,冒号前的0 是字段位置(在最新版本的Python 中可以省略)。它表示我们希望将此格式化代码应用于.format 的第一个参数,即索引为零的参数。例如,

'{0} {2} {1}'.format('one', 'two', 'three') 

打印

one three two

b 表示我们要将数字打印为二进制。 08 表示我们希望输出为 8 个字符宽,对于小于 8 位的二进制数使用零填充。

%02X中,大写的X表示我们要打印一个十六进制的数字,对于大于9的数字使用大写字母A-F,我们可以使用小写x得到小写字母。 02 表示我们希望输出为 2 个字符宽,对于小于 2 个十六进制数字的十六进制数字使用零填充。

【讨论】:

  • 能否再解释一下格式化选项,例如{0:08b}%02X?那些在做一些特别的事情吗?
【解决方案2】:

如果要修改字节的 lsb,则将值表示为二进制字符串是没有意义的。实际上,你会做一些事情(在伪代码中):

byte = '\x6h'
binary = convert_to_bits(byte)    # some way of getting 1s and 0s in a string
binary = binary[:7] + my_bit_string
byte = convert_to_byte(binary)

有更直接和有效的方法来修改位值,那就是bitwise operators。例如,假设我们想将 01001001(十进制 73)更改为 01001000。我们想创建一个位掩码 11111110,十进制中的值是 254,AND 是我们的值。

>>> value = 73 & 254
>>> value
72
>>> '{0:08b}'.format(value)
'01001000'

当您将位嵌入到一个字节时,lsb 可能会改变,也可能不会。有很多方法可以解决,但最直接的方法是将 lsb 清零,然后用 OR 覆盖它(如果您还想嵌入多个位,则非常通用)。

byte = (byte & 254) | my_bit

您也可以使用 right shift 将 lsb 归零,然后使用 left shift,但这需要 2 次操作而不是 1 次。

byte = ((byte >> 1) << 1) | my_bit

或者您可以检查 lsb 和您的位是否不同,然后使用 XOR 翻转它。但是,这种方法使用分支,效率最低。

if (byte & 1) != my_bit:
    byte = byte ^ 1
# no need to do anything if they are the same

因此,您需要做的就是将字节转换为整数数组。您可以使用[ord(byte) for byte in frame],但有更有效的内置方法。与bytearray()bytes()

>>> frame = '\x0f\x02\x0e\x02\xf7\x00\xf7\x00T\xffT\xff'
>>> frame_bytes = bytearray(frame)
>>> frame_bytes[0]
15
>>> frame_bytes[0] = 14       # modify
>>> bytes(frame_bytes)        # convert back to bytes
'\x0e\x02\x0e\x02\xf7\x00\xf7\x00T\xffT\xff'

使用array.array()(对于数十万字节,这似乎有点慢):

>>> import array
>>> frame = '\x0f\x02\x0e\x02\xf7\x00\xf7\x00T\xffT\xff'
>>> frame_bytes = array.array('B', frame)
>>> frame_bytes[0]
15
>>> frame_bytes[0] = 14       # modify
>>> frame_bytes.tostring()    # convert back to bytes; in Python 3 use `tobytes()`
'\x0e\x02\x0e\x02\xf7\x00\xf7\x00T\xffT\xff'

嵌入和提取示例。

frame = '\x0f\x02\x0e\x02\xf7\xf7T\xffT\xff'
bits = [0, 0, 1, 1, 0]

# Embedding
frame_bytes = bytearray(frame)
for i, bit in enumerate(bits):
    frame_bytes[i] = (frame_bytes[i] & 254) | bit
frame_modified = bytes(frame_bytes)

# Extraction
frame_bytes = bytearray(frame_modified)
extracted = [frame_bytes[i] & 1 for i in range(5)]
assert bits == extracted

如果您的秘密是一个字符串或一系列字节,很容易convert them to a list of 1s and 0s

最后,请确保您没有修改任何标题数据,因为这可能会使文件无法读取。

【讨论】:

    猜你喜欢
    • 2011-01-04
    • 2022-01-23
    • 2021-01-30
    • 1970-01-01
    • 2019-06-15
    • 1970-01-01
    • 2013-05-30
    • 2011-04-07
    • 2011-07-09
    相关资源
    最近更新 更多