解压缩单声道波数据并将其存储在阵列中

时间:2014-12-09 01:49:43

标签: python audio

我正在尝试使用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]

当我传入文件时,我得到以下输出:

NumChannels: 1
ChunkSize: 78476
BloackAlign: 0
Filename: foo.wav
ByteRate: 32000
BlockAlign: 2
AudioFormat: 1
SampleRate: 16000
BitsPerSample: 16
Format: WAVE
Subchunk1Size: 16

然后我尝试通过执行struct.unpack('<h', self.bufHeader[36:])[0]来获取数据,但这样做会返回一个简单的整数值24932。我不允许使用wave库或其他任何与波有关的事情,因为我必须将其适应其他类型的信号。如何存储和操作实际波形数据?

编辑:

while chunk_reader < stHeaderFields['ChunkSize']:
        data.append(struct.unpack('<H', bufHeader[chunk_reader:chunk_reader+stHeaderFields['BlockAlign']]))

1 个答案:

答案 0 :(得分:2)

好的,我会尝试写一个完整的演练。

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

WAV文件通常只有两个子元素('fmt'和'data'),但它也可能包含一些子元素('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个字节,并确保它是RIFFRIFX标记,否则它不是有效的RIFF文件。 RIFFRIFX之间的区别是前者使用little-endian编码(并且在任何地方都受支持),而后者使用big-endian(实际上没有人支持它)。为简单起见,我们假设我们只处理little-endian RIFF文件。

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

阅读fmt标题非常简单,您实际上是在代码中完成的。

数据样本通常表示为1,2,3或4个字节(同样,在文件字节序中)。最常见的格式是所谓的s16_le(您可能已经在某些音频处理实用程序中看到了这样的命名,如ffmpeg),这意味着样本以小端显示为带符号的16位整数。其他可能的格式是u8(8位样本是无符号数!),s24_les32_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)

现在我们知道文件格式信息及其示例数据,因此我们可以解析它。如上所述,样本可能有任何大小,但现在让我们假设我们只处理16位样本:

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.

现在您拥有rawData中的所有示例,您可以根据需要访问和修改它。使用Python的array()来有效地访问和修改数据可能很方便(但是在24位音频的情况下不会这样做,你需要编写自己的序列化和反序列化)。

完成数据处理(可能涉及升级或缩减每个样本的位数,更改通道数,声级操作等)之后,您只需编写一个新的RIFF标题。数据长度(通常可以使用简化的公式36 + len(rawData)计算),更改的fmt标头和data流。

希望这有帮助。