2022年 11月 4日

python解析wav语音文件

说明

本文主要使用python编程,实现解析wav语音文件,得到.wav语音文件的声道数,量化位数,采样频率,采样点数。编写python程序使用pycharm。
下面简单介绍一下wav文件结构。

一 解析.wav文件原理

WAVE文件本质上就是一种RIFF格式,它可以抽象成一颗树(数据结构的一种)来看。

在这里插入图片描述
​ 如图所示,从上到下分别对应着二进制数据在文件中相对于起始位置的偏移量。每一个格子对应一个字段,field size表示每个字段所占据的大小,根据这个大小以及当前的偏移量,可以计算出下一个字段的起始地址(偏移量)。

​ 根据RIFF的规范,整个WAV文件的顶级chunk就是最顶上的ChunkID为RIFF的这个chunk,这也可以解释为什么wav文件开头都是RIFF几个字母。而接下来的ChunkSize则表示这个chunk下的那些子chunk的大小,如果按照“树结构”来理解,那么每一个子chunk(Subchunk)则为树的树枝。而Format则为这个chunk的实际数据。

​ 一个chunk结构包括三个部分,第一个部分标识符用于说明这个chunk是存什么内容的,第二个部分则是说明这个chunk的内容到底有多大,用于让程序知道如果要找到下一个chunk该把地址偏移多少去读取,而第三个部分则是实际内容。

​ 上面说的是顶级chunk,接下来是子chunk。第一个子chunk的Subchunk1ID在WAV文件中恒定为fmt,表示该subchunk的内容为该WAV音频文件的一些元数据,也就是该WAV音频的一些格式信息。比如说AudioFormat这个字段一般为1,表示这个WAV音频为PCM编码。NumChannels则是该WAV音频文件的声道数量。SampleRate则为采样率,ByteRate则为采样率。BlockAlign则是每个block的平均大小,它等于NumChannels * BitsPerSample/8。BitsPerSample则为每秒采样比特,有的地方称它为量化精度或者PCM位宽。

​ 另一个子chunk也就是Subchunk2ID是在WAV文件中恒定为data,也就是这个WAV音频文件的实际音频数据,也就是音频的采样数据。但是我们的音频如果是双声道,那么实际上某一个采样时刻采样的数据是由左声道和右声道共同组成的。而这个共同组成的采样我们把他成为block。前面有讲到BlockAlign = NumChannels * BitsPerSample / 8,计算机中是以8个二进制数表示一个字节,所以要除以8来求出字节数。

​ 至于音频的持续长度,我们可以通过Subchunk2Size除以ByteRate,也就是实际音频data的chunk总长度除以每秒字节数得到持续多少秒。

详细的wav文件结构参考:wav文件格式分析与详解

二 python编程实现解析.wav文件

采用命令行传参的形式,将输入的语音文件和输出的解析数据保存的文件均从命令行输入。

程序

#解析.wav文件,得到采样频率,声道数,量化位数,采样点数
import numpy as np
import sys
import wave   #语音文件处理包
import getopt

def main(argv):  #定义一个函数
    try:  #首先执行try后的程序,如果输入格式不对,则执行except getopt.GetoptError:后的程序
        opts, args = getopt.getopt(argv[1:], "i:o:h", ["input", "output","help"])  #命令行输入参数
    except getopt.GetoptError:
        print('输入参数错误,输入格式为:python wavinfo.py -i voice.wav -o text1.txt,\n其中wavinfo.py为程序文件名称,voice.wav为语音文件,text1.txt为.wav文件的数据保存到的文件')
        sys.exit()

    for opt, arg in opts:
        if opt in ("-h", "--help"):   #打印帮助
            print('读取语音文件声道数,采样频率,采样深度,采样点数')
            print('输入格式为:')
            print('python wavinfo.py -i voice.wav -o text1.txt')
            print('其中wavinfo.py为程序文件名称,voice.wav为语音文件,text1.txt为.wav文件的数据保存到的文件' )
            sys.exit()
        elif opt in ("-i", "--input"):
            input = arg
            f = wave.open(input, "rb")
            # 读取格式信息
            # 一次性返回所有的WAV文件的格式信息,它返回的是一个组元(tuple):声道数, 量化位数(byte单位), 采样频率, 采样点数, 压缩类型, 压缩类型的描述。wave模块只支持非压缩的数据,因此可以忽略最后两个信息
            params = f.getparams()
            nchannels, sampwidth, framerate, nframes = params[:4]
            print("声道数=", nchannels, "\n量化位数=", sampwidth, "\n采样频率=", framerate, "\n采样点数=", nframes)
        elif opt in ("-o", "--output"):
            output = arg
            params = f.getparams()
            # file = open('results_storage.txt', 'a')
            file = open(output, 'w+')
            bins = ['声道数', '量化位数(byte单位)', '采样频率', '采样点数']
            # i=0
            # 保存到本地txt文件
            params = params[:4]
            for i in range(4):
                # s = str(bins[i]).replace('[',").replace('[',")+'\t'+str(data[i]).replace('[',").replace('[',")#去除[],这两行按数据不同,可以选择
                s = str(bins[i]).replace('[', ").replace('[',") + '=' + str(params[i]).replace('[', ").replace('[',")
                s = s.replace("'", ").replace(',',") + '\n'  # 去除单引号,逗号,每行末尾追加换行符
                file.write(s)
            file.close()
            f.close()


if __name__ == '__main__':
    main(sys.argv)  # 调用函数
    
#python wavinfo.py -i voice.wav -o text1.txt
#python wavinfo.py -h
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

结果

可以先查看帮助文档:
在pycharm的terminal命令行运行程序

python wavinfo.py -h
  • 1

运行结果:
在这里插入图片描述
接着在pycharm的terminal命令行运行程序:

python wavinfo.py -i voice.wav -o text1.txt
  • 1

结果
在这里插入图片描述
数据同时保存在txt文件中,保存文件结果:
在这里插入图片描述