阅读 python 中的 WAV 文件

Read WAV files in python

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() 函数我得到以下错误

"unpack requires a string argument of length 2"

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

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

您需要将字节转换为整数,然后使用 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. For the modern str.format options see 7.1.3. Format String Syntax and 7.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 个十六进制数字的十六进制数字使用零填充。

如果要修改字节的 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 次操作而不是一次。

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],但还有更有效的 built-in 方法。随着 bytearray() and 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

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