Python 解包单通道波形数据并将其存储在阵列中

Python 解包单通道波形数据并将其存储在阵列中,python,audio,Python,Audio,我正在尝试使用struct.unpack从单通道WAVE文件解压数据。我希望将数据存储在一个数组中,并能够对其进行操作(比如通过添加给定方差的噪声)。我提取了标题数据并将其存储在字典中,如下所示: stHeaderFields['ChunkSize'] = struct.unpack('<L', bufHeader[4:8])[0] stHeaderFields['Format'] = bufHeader[8:12] stHeaderFields['Subchunk1Size'] = st

我正在尝试使用
struct.unpack
从单通道WAVE文件解压数据。我希望将数据存储在一个数组中,并能够对其进行操作(比如通过添加给定方差的噪声)。我提取了标题数据并将其存储在字典中,如下所示:

stHeaderFields['ChunkSize'] = struct.unpack('<L', bufHeader[4:8])[0]
stHeaderFields['Format'] = bufHeader[8:12]
stHeaderFields['Subchunk1Size'] = struct.unpack('<L', bufHeader[16:20])[0]
stHeaderFields['AudioFormat'] = struct.unpack('<H', bufHeader[20:22])[0]
stHeaderFields['NumChannels'] = struct.unpack('<H', bufHeader[22:24])[0]
stHeaderFields['SampleRate'] = struct.unpack('<L', bufHeader[24:28])[0]
stHeaderFields['ByteRate'] = struct.unpack('<L', bufHeader[28:32])[0]
stHeaderFields['BlockAlign'] = struct.unpack('<H', bufHeader[32:34])[0]
stHeaderFields['BitsPerSample'] = struct.unpack('<H', bufHeader[34:36])[0]

然后,我尝试通过执行
struct.unpack(“好的,我将尝试编写一个完整的演练来获取数据

首先,将WAV(或者更可能是RIFF)文件视为线性结构是一个常见的错误。它实际上是一个树,每个元素都有一个4字节的标记、4字节长度的数据和/或子元素,以及其中的某种数据

WAV文件通常只有两个子元素(“fmt”和“data”),但也可能有元数据(“LIST”)和一些子元素(“INAM”、“IART”、“ICMT”等)另外,块没有实际的顺序要求,所以认为“数据”跟在“fmt”后面是不正确的,因为元数据可能会夹在中间

让我们看看RIFF文件:

'RIFF'
  |-- file type ('WAVE') 
  |-- 'fmt '
  |     |-- AudioFormat
  |     |-- NumChannels
  |     |-- ...
  |     L_ BitsPerSample
  |-- 'LIST' (optional)
  |     |-- ... (other tags)
  |     L_ ... (other tags)
  L_ 'data'
        |-- sample 1 for channel 1
        |-- ...
        |-- sample 1 for channel N
        |-- sample 2 for channel 1
        |-- ...
        |-- sample 2 for channel N
        L_ ...
那么,您应该如何读取WAV文件呢?首先,您需要从文件的开头读取4个字节,并确保它是
RIFF
RIFX
标记,否则它不是有效的RIFF文件。
RIFF
RIFX
之间的区别是前者使用小端编码(并且在任何地方都支持)后者使用big-endian(实际上没有人支持它),为了简单起见,让我们假设我们只处理小endian-RIFF文件

接下来,您将读取根元素长度(以文件结尾)和以下文件类型。如果文件类型不是
WAVE
,则它不是WAV文件,因此您可能会放弃进一步的处理。读取根元素后,您将开始读取所有子元素并处理感兴趣的子元素

读取
fmt
标题非常简单,实际上您已经在代码中完成了

数据样本通常表示为1、2、3或4个字节(同样,在文件结尾处)。最常见的格式是所谓的
s16_le
(您可能在一些音频处理实用程序(如ffmpeg)中见过这种命名),这意味着样本以小尾数形式表示为有符号16位整数。其他可能的格式为
u8
(8位样本为无符号数字!),
s24_le
s32_le
。数据样本是交错的,因此即使对于多声道音频,也很容易在流中找到任意位置。注意:这仅对未压缩的WAV文件有效,如AudioFormat==1所示。对于其他格式,数据样本可能有另一种布局

让我们来看看一个简单的WAV阅读器:

stHeaderFields = dict()
rawData = None

with open("file.wav", "rb") as f:
    riffTag = f.read(4)
    if riffTag != 'RIFF':
        print 'not a valid RIFF file'
        exit(1)

    riffLength = struct.unpack('<L', f.read(4))[0]
    riffType = f.read(4)
    if riffType != 'WAVE':
        print 'not a WAV file'
        exit(1)

    # now read children
    while f.tell() < 8 + riffLength:
        tag = f.read(4)
        length = struct.unpack('<L', f.read(4))[0]

        if tag == 'fmt ': # format element
            fmtData = f.read(length)
            fmt, numChannels, sampleRate, byteRate, blockAlign, bitsPerSample = struct.unpack('<HHLLHH', fmtData)
            stHeaderFields['AudioFormat'] = fmt
            stHeaderFields['NumChannels'] = numChannels
            stHeaderFields['SampleRate'] = sampleRate
            stHeaderFields['ByteRate'] = byteRate
            stHeaderFields['BlockAlign'] = blockAlign
            stHeaderFields['BitsPerSample'] = bitsPerSample

        elif tag == 'data': # data element
            rawData = f.read(length)

        else: # some other element, just skip it
            f.seek(length, 1)
sHeaderFields=dict()
原始数据=无
打开(“file.wav”、“rb”)作为f:
riffTag=f.read(4)
如果riffTag!=“RIFF”:
打印“不是有效的RIFF文件”
出口(1)

riffLength=struct.unpack('您读取了错误的值。
24932
实际上是
0x6164
,它是
ad
,它是
数据
签名的前半部分(反尾端)。数据块位于WAV头之后,具有4字节签名(
数据
)和4字节长度(以字节为单位)然后是实际样本的数据。因此,在读取样本之前,您需要验证数据块签名并读取数据长度。在您知道实际数据偏移量和长度后,以
BlockAlign
字节为单位读取数据,将每个数据块解压成一个元组(具有
NumChannels
元素,每个
BitsPerSample
位),然后做你需要的任何事情。我根据你所说的内容编辑了我的问题。它只是给了我一个包含许多值的数组
24932
。我读错了吗?你能对你的答案进行一点扩展吗?这是一个很大的帮助。一个问题是最后一行
samples=struct.unpack…
应该添加到示例中,这是Verwrite第一个索引。
samples
这里是循环中的一个局部变量,下面的注释解释了您可以对它执行任何操作。例如,您可以在循环之前声明一个列表:
stream=list()
,然后在循环中向其追加样本:
stream.append(samples)
(或
stream.append(samples[0])
如果您只需要一个频道)。啊,我明白了。再次感谢!我是否要使用
struct.pack()
保存我编辑的wave文件?
'RIFF'
  |-- file type ('WAVE') 
  |-- 'fmt '
  |     |-- AudioFormat
  |     |-- NumChannels
  |     |-- ...
  |     L_ BitsPerSample
  |-- 'LIST' (optional)
  |     |-- ... (other tags)
  |     L_ ... (other tags)
  L_ 'data'
        |-- sample 1 for channel 1
        |-- ...
        |-- sample 1 for channel N
        |-- sample 2 for channel 1
        |-- ...
        |-- sample 2 for channel N
        L_ ...
stHeaderFields = dict()
rawData = None

with open("file.wav", "rb") as f:
    riffTag = f.read(4)
    if riffTag != 'RIFF':
        print 'not a valid RIFF file'
        exit(1)

    riffLength = struct.unpack('<L', f.read(4))[0]
    riffType = f.read(4)
    if riffType != 'WAVE':
        print 'not a WAV file'
        exit(1)

    # now read children
    while f.tell() < 8 + riffLength:
        tag = f.read(4)
        length = struct.unpack('<L', f.read(4))[0]

        if tag == 'fmt ': # format element
            fmtData = f.read(length)
            fmt, numChannels, sampleRate, byteRate, blockAlign, bitsPerSample = struct.unpack('<HHLLHH', fmtData)
            stHeaderFields['AudioFormat'] = fmt
            stHeaderFields['NumChannels'] = numChannels
            stHeaderFields['SampleRate'] = sampleRate
            stHeaderFields['ByteRate'] = byteRate
            stHeaderFields['BlockAlign'] = blockAlign
            stHeaderFields['BitsPerSample'] = bitsPerSample

        elif tag == 'data': # data element
            rawData = f.read(length)

        else: # some other element, just skip it
            f.seek(length, 1)
blockAlign = stHeaderFields['BlockAlign']
numChannels = stHeaderFields['NumChannels']

# some sanity checks
assert(stHeaderFields['BitsPerSample'] == 16)
assert(numChannels * stHeaderFields['BitsPerSample'] == blockAlign * 8)

for offset in range(0, len(rawData), blockAlign):
    samples = struct.unpack('<' + 'h' * numChannels, rawData[offset:offset+blockAlign])

    # now samples contains a tuple with sample values for each channel
    # (in case of mono audio, you'll have a tuple with just one element).
    # you may store it in the array for future processing, 
    # change and immediately write to another stream, whatever.