确定 wav 文件的位深度

Posted

技术标签:

【中文标题】确定 wav 文件的位深度【英文标题】:Determining Bit-Depth of a wav file 【发布时间】:2017-09-13 17:59:47 【问题描述】:

我正在寻找一种快速、最好是标准库机制来,例如“16 位”或“24 位”。

我正在使用对 Sox 的子进程调用来获取过多的音频元数据,但子进程调用非常慢,我目前只能从 Sox 可靠地获取的唯一信息是位深度。

内置的 wave 模块没有“getbitdepth()”之类的功能,并且与 24 位 wav 文件不兼容 - 我可以使用“try except”来使用 wave 模块访问文件元数据(如果可以的话,手动记录它是 16 位)然后打开,除了调用 sox (其中 sox 将执行分析以准确记录其位深度)。我担心的是,这种方法感觉像是猜测工作。如果读取一个 8 位文件怎么办?如果不是,我会手动分配 16 位。

SciPy.io.wavefile 也与 24 位音频不兼容,因此会产生类似的问题。

这个tutorial 非常有趣,甚至包括一些非常低级(至少对于 Python 来说是低级)脚本示例,用于从 wav 文件头中提取信息 - 不幸的是,这些脚本不适用于 16 位音频。

有什么方法可以简单地(并且不调用 sox)确定我正在检查的 wav 文件的位深度是多少?

我使用的波头解析器脚本如下:

import struct
import os

def print_wave_header(f):
    '''
    Function takes an audio file path as a parameter and 
    returns a dictionary of metadata parsed from the header
    '''
    r =  #the results of the header parse
    r['path'] = f
    fin = open(f,"rb") # Read wav file, "r flag" - read, "b flag" - binary 
    ChunkID=fin.read(4) # First four bytes are ChunkID which must be "RIFF" in ASCII
    r["ChunkID"]=ChunkID
    ChunkSizeString=fin.read(4) # Total Size of File in Bytes - 8 Bytes
    ChunkSize=struct.unpack('I',ChunkSizeString) # 'I' Format is to to treat the 4 bytes as unsigned 32-bit inter
    TotalSize=ChunkSize[0]+8 # The subscript is used because struct unpack returns everything as tuple
    r["TotalSize"]=TotalSize
    DataSize=TotalSize-44 # This is the number of bytes of data
    r["DataSize"]=DataSize
    Format=fin.read(4) # "WAVE" in ASCII
    r["Format"]=Format
    SubChunk1ID=fin.read(4) # "fmt " in ASCII
    r["SubChunk1ID"]=SubChunk1ID
    SubChunk1SizeString=fin.read(4) # Should be 16 (PCM, Pulse Code Modulation)
    SubChunk1Size=struct.unpack("I",SubChunk1SizeString) # 'I' format to treat as unsigned 32-bit integer
    r["SubChunk1Size"]=SubChunk1Size
    AudioFormatString=fin.read(2) # Should be 1 (PCM)
    AudioFormat=struct.unpack("H",AudioFormatString) ## 'H' format to treat as unsigned 16-bit integer
    r["AudioFormat"]=AudioFormat[0]
    NumChannelsString=fin.read(2) # Should be 1 for mono, 2 for stereo
    NumChannels=struct.unpack("H",NumChannelsString) # 'H' unsigned 16-bit integer
    r["NumChannels"]=NumChannels[0]
    SampleRateString=fin.read(4) # Should be 44100 (CD sampling rate)
    SampleRate=struct.unpack("I",SampleRateString)
    r["SampleRate"]=SampleRate[0]
    ByteRateString=fin.read(4) # 44100*NumChan*2 (88200 - Mono, 176400 - Stereo)
    ByteRate=struct.unpack("I",ByteRateString) # 'I' unsigned 32 bit integer
    r["ByteRate"]=ByteRate[0]
    BlockAlignString=fin.read(2) # NumChan*2 (2 - Mono, 4 - Stereo)
    BlockAlign=struct.unpack("H",BlockAlignString) # 'H' unsigned 16-bit integer
    r["BlockAlign"]=BlockAlign[0]
    BitsPerSampleString=fin.read(2) # 16 (CD has 16-bits per sample for each channel)
    BitsPerSample=struct.unpack("H",BitsPerSampleString) # 'H' unsigned 16-bit integer
    r["BitsPerSample"]=BitsPerSample[0]
    SubChunk2ID=fin.read(4) # "data" in ASCII
    r["SubChunk2ID"]=SubChunk2ID
    SubChunk2SizeString=fin.read(4) # Number of Data Bytes, Same as DataSize
    SubChunk2Size=struct.unpack("I",SubChunk2SizeString)
    r["SubChunk2Size"]=SubChunk2Size[0]
    S1String=fin.read(2) # Read first data, number between -32768 and 32767
    S1=struct.unpack("h",S1String)
    r["S1"]=S1[0]
    S2String=fin.read(2) # Read second data, number between -32768 and 32767
    S2=struct.unpack("h",S2String)
    r["S2"]=S2[0]
    S3String=fin.read(2) # Read second data, number between -32768 and 32767
    S3=struct.unpack("h",S3String)
    r["S3"]=S3[0]
    S4String=fin.read(2) # Read second data, number between -32768 and 32767
    S4=struct.unpack("h",S4String)
    r["S4"]=S4[0]
    S5String=fin.read(2) # Read second data, number between -32768 and 32767
    S5=struct.unpack("h",S5String)
    r["S5"]=S5[0]
    fin.close()
    return r

【问题讨论】:

每个 wav 文件在其头部(前 44 个字节)中都有 bit_depth ......每个 wav 库都必须解析头部......它很容易自己执行这个头部解析 使用我在示例中标记的教程,我已经能够解析标题,但位深度并不总是很清楚,例如ChunkID= b'RIFF' TotalSize= 602914 DataSize= 602870 Format= b'WAVE' SubChunk1ID= b'JUNK' SubChunk1Size= 92 AudioFormat= 0 NumChannels= 0 SampleRate= 0 ByteRate= 0 BlockAlign= 0 BitsPerSample= 0 SubChunk2ID= b'\ x00\x00\x00\x00' SubChunk2Size= 0 S1= 0 S2= 0 S3= 0 S4= 0 S5= 0 根据文件压缩,标题是否可读,但我希望无论文件如何都能够读取它格式/压缩,无需任何转换过程。 对于所有这些标头设置,看到 0 是一个危险信号 - 文件损坏或库错误......即使 wav 文件被压缩(我从未见过 wav 文件的压缩) 标头肯定不会被压缩...这是一个简洁的 wav 规范摘要soundfile.sapp.org/doc/WaveFormat ...如果您编写自己的标头解析器,请特别注意标头字段和数据部分的字节序...您可以编写您的在两页代码中拥有自己的 wav 解析器 谢谢 - 您发布的链接是一个很棒的信息来源。需要一些时间来消化它。当您说“文件已损坏或库错误”时,文件播放得很好,所以我不认为它已损坏。当你说它可能是错误的时候,你指的是哪个图书馆?我会将我正在使用的解析器添加到问题中。 一些 WAV 文件有一个 JUNK 块,显然是为了将 RIFF 块与某些边界对齐 (daubnet.com/en/file-format-riff)。这个 JUNK 块紧跟在 WAV 字节之后和 fmt 字节之前,所以如果您期望固定的字节偏移量可能会导致您看到的某些 0 值。 【参考方案1】:

与 Matthias 的答案基本相同,但使用可复制粘贴的代码。

要求

pip install soundfile

代码

import soundfile as sf

ob = sf.SoundFile('example.wav')
print('Sample rate: '.format(ob.samplerate))
print('Channels: '.format(ob.channels))
print('Subtype: '.format(ob.subtype))

说明

频道:通常为 2 个,表示您有一个左扬声器和一个右扬声器。 采样率:音频信号是模拟的,但我们希望以数字方式表示它们。这意味着我们希望在价值和时间上离散化它们。采样率给出了我们每秒获得值的次数。单位是赫兹。采样率需要至少是原始声音中最高频率的两倍,否则会出现混叠。 Human hearing range 从 ~20Hz 到 ~20kHz,因此您可以切断 20kHZ 以上的任何内容。这意味着超过 40kHz 的采样率没有多大意义。 位深:位深越高,可以捕捉到的动态范围越大。动态范围是乐器、部分或音乐的最安静和最大音量之间的差异。典型值似乎是 16 位或 24 位。 16 位的位深度的理论动态范围为 96 dB,而 24 位的动态范围为 144 dB (source)。 子类型PCM_16 表示 16 位深度,其中 PCM 代表 Pulse-Code Modulation

另类

如果你只找命令行工具,那我可以推荐MediaInfo:

$ mediainfo example.wav
General
Complete name                            : example.wav
Format                                   : Wave
File size                                : 83.2 MiB
Duration                                 : 8 min 14 s
Overall bit rate mode                    : Constant
Overall bit rate                         : 1 411 kb/s

Audio
Format                                   : PCM
Format settings                          : Little / Signed
Codec ID                                 : 1
Duration                                 : 8 min 14 s
Bit rate mode                            : Constant
Bit rate                                 : 1 411.2 kb/s
Channel(s)                               : 2 channels
Sampling rate                            : 44.1 kHz
Bit depth                                : 16 bits
Stream size                              : 83.2 MiB (100%)

【讨论】:

【参考方案2】:

我强烈推荐 soundfile 模块(但请注意,我非常有偏见,因为我写了大部分内容)。

在那里,您可以将文件作为soundfile.SoundFile 对象打开,该对象具有包含您要查找的信息的subtype 属性。

在您的情况下,这可能是 'PCM_16''PCM_24'

【讨论】:

我会试试这个。你知道soundfile对象是否可以异步实例化吗? 你能澄清一下你所说的“异步”是什么意思吗?如果你的意思是如果任何函数(我猜你在谈论构造函数)是await-able,那么不是。是否有任何支持该功能的声音文件模块? 您理解正确。我不知道有任何可用的音频模块等待 - 如果声音文件足够快,那么也许我可以尝试在没有异步的情况下工作。 从音频文件创建SoundFile 对象只需要读取几十个标头字节(在最简单的 WAV 情况下),所以它应该很快,我猜.如果对文件数据的访问速度很慢,您可以尝试使用某种预缓冲的类似文件的对象和awaitsoundfile 模块可以处理file-like objects。

以上是关于确定 wav 文件的位深度的主要内容,如果未能解决你的问题,请参考以下文章

使用 AudioQueue 读取音频缓冲区数据

我如何确定 wav 文件的持续时间

C代码创建多通道WAV音频文件

C代码创建多通道WAV音频文件

从 MemoryStream 打开音频 (wav) 文件以确定持续时间

24 位深 wav 文件生成器