WAV文件数据解析(4.10更新一点小代码)

作为一个初级的算法和声纹的工程师,写个blog记录一下自己近期所接触的知识点,作为日后参考和复习用

首先都是文本,后期有空再加图片和改格式,很多内容都是通过被人的blog参考和整理得来。

日后慢慢的更新声纹识别SRE的算法以及机器学习的算法和数据分析的实践


更新:最近项目测了个模型,刚好在读取文件中尝试写了一下关于读取WAV文件里面纯音频的C++代码,很少的行数,但是逻辑应该蛮清楚的。wav文件的解析只是帮助了解文件的格式,但是用代码读它才是实际应用的干货

----------------------------------华丽的分割线------------------------------------------------------------------------------------------

wav是微软开发的一种音频文件格式.

它符合它符合RIFF(Resource Interchange File Format)文件规范,用于保存Windows平台的音频信息资源,
被Windows平台及其应用程序所广泛支持。

其基本构成为以“块”(chunk)组成的单元,RIFF 文件以一串数据块(data chunks)组成的文件头为开始,"标
准型"wav文件往往只是 RIFF 文件中一个单独的"WAVE"大块,包含两个子块:描述数据格式信息的fmt块和包含实际的示例数据的数据块

标准化的wav文件都是44.1k的采样率,采用16位的数字表示。
但是我们的成了里面采样率是16k的,采用16为的数字表示。
wav文件分为两个部分,第一个部分是wav头文件,第二个部分是PCM编码的音频数据部分。

od -x raw_mfcc_train.2.ark | head 以16进制的形式查看二进制文件的头几行
或者你可以
用vim name.wav 打开命名微name的音频文件后,输入 :%!xxd 将当前文本转换微16进制格式,(不推荐)

会生成一个这样的格式,取前48字节得:

00000000: 5249 4646 4417 0200 5741 5645 666d 7420  RIFFD...WAVEfmt
00000010: 1000 0000 0100 0100 803e 0000 007d 0000  .........>...}..
00000020: 0200 1000 6461 7461 2017 0200 3a00 2c00  ....data ...:.,.

(注意:对于16bit的wav格式语音来说,文件是由44个字节Bytes组成的,语音是读取short的格式,两个byte一起读
比如0001读取后 01是高位,00是低位,进行高低位互换,就是倒着看 0001)

他是两个字节两个字节一起读的,例如5249有两个字节 52 和 49,每一行总共16个字节.

下面的四个字节,两个字节是按顺序截取。

RIFF chunck:(当文件的类型是WAVE时,需要fmt数据快和data数据块)
1.四个字节:把文件标记为RIFF文件,也就是52 49 46 46 = RIFF格式,每个字符用一个字节表示.
对照这ASCII表可以得到:R=52,I=49,F=46,F=46。 52 的16进制等于82的10进制,在ASCII中为R,同理得I,F,F

http://ascii.911cha.com/ 贴个ASCII对照表方便懒人检查

2.四个字节: 44 17 02 00 描述了从下个地址到文件尾的总字节数,计算的时候得倒着看
00021744变10进制=4*16^0+4*16^1+7*16^2+1*16^3+2*16^4 = 137028字节。4个字节整数表示总体文件大小,以字节为单位(32 位整数)在创建之后即填写。这里得到的大小是需要减去前8个bytes的。

3.四个字节:文件类型标记为为WAVE, *.wav格式,57 41 56 45,这四个字节对应ASCII的表也是代表WAVE


fmt sub-chunck:(描述的是在数据块中,声音信息的格式)
4.四个字节:波形格式标志(fmt), 最后一位是空格,描述数据格式信息。66 6d 74 20就是ASCII “fmt”的每个字符16进制表达式 最后一个字节代表了空格 66=f, 6d=m, 74=t, 20=空格


5.四个字节:fmt chunck的大小 10 00 00 00,就是fmt数据的大小,有多少个字节,在这里倒过来看是00000010,也就是16个字节,代表这PCM,从后面的0100 到最后的1000总共有16个字节

6.两个字节:0100倒过来看是0001,10进制值为1时,代表PCM类型的音频数据
PCM简单介绍:
数字信号是对连续变化的模拟信号进行抽样、量化和编码产生的,称为PCM(Pulse-code modulation),即脉冲编码调制。PCM实际上就是讲这个波形图通过按一定的时间间隔,收集起来。音频波形图

7.两个字节:通道数,1为单声道,2为双声道。这里是0100倒过来看是0001,值为1,就是单声道

8.四个字节:采样率,这里是80 3e 00 00 倒过来高低位互换,00003e80变成二进制是8*16+14*16*16+3*16*16*16=16000=16K

9.四个字节:每秒的数据字节数00 7d 00 00,倒过来看 00007d00变成二进制=32000,所以速度是32kByte/s
每秒传输32K字节的数据

10.两个字节:数据快的对齐数=每样值位数*通道/8,表示所有通道的一个样值所需的比特数。02 00 倒过来0002

11.两个字节:每个采样点的量化位数=每样值位数 10 00 倒过来为0010=16比特

Data sub-chunck:(这里主要描述的是音频数据的大小,后面就是音频数据了)

12:四个字节:data块标记码。 描述“data”这个字符的四个16进制的字节。转换成ascii码好理解
64 61 74 61 对照ascii表格得到  64=d 61=a 74=t 61=a

13:四个字节:有效语音数觉的大小:20 17 02 00 高低位互换倒过来看是00 02 17 20,然后变成10进制
0*16^0+2*16+7*16*16+1*16*16*16+2*16*16*16*16=136992字节

14:之后就是音频数据了


这是将语音音频文件用16进制的编译器打开后的最后一行
00021740: 2b00 2a00 2c00 2c00 2c00 2f00 0a         +.*.,.,.,./..
一共有00021740个地址快,所以文件大小=0*16^0+4*16^1+7*16^2+1*16^3+2*16^4=137024字节
然后最后一行少了3个字节,所以是137024-3=137021字节
硬盘是1024Bytes对齐,所以 有137024/1024=133.8,为了对齐,占用空间为134*1024=137216字节



更新:上面说了多知识,但到底得怎么用C++读这些个wav文件呢? C++有专门的函数来读取 文件头

直接复制代码了,后期还会贴上如何去遍历一个文件下不同说话人的音频的代码,然后两个代码组合来读取不同说话人的WAV文件。我现在用的方法很笨,不是很智能所以就不贴出来了。目前在研究map去做存储和查找,struct去把音频分类为训练和测试。

#include "wave_parser.h"  //首先需要头文件引用这个库
#include <string>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <vector>

 int SimpleReadWav(const char* fname, uint32_t headersize, int16_t **wav, uint32_t *len)

//上面这里的headersize可以忽略

{
    string path(fname);
    WaveParser parser(path); //读取了wav,这个类对于处理wave文件很便利,他还有很多member,详细的自己去看把

    if (!parser.parse())    {
        return 0;
    }
    headersize = (uint32_t)parser.getHeaderLen();    //这一句就是用来读取文件头的长度
    printf("HeaderLen=%d\n", headersize);
    FILE *fp = fopen(fname, "rb");
    if (fp == NULL)
    {
        printf("Open file failed: %s\n", fname);
        return 1;
    }
    fseek(fp, 0, SEEK_END);   //fseek() 这一句是把当前的指针指向文件内容的最后面

    *len = (ftell(fp) - headersize) / sizeof(int16_t);

//ftell()是当前为止即文件末到文件首的字节数,即文件大小,减去headersize,剩下的就是纯音频data的长度                                                                     

    if (*len <= 0)
    {
        printf("Read file failed: %s\n", fname);
        return 2;
    }
    *wav = new int16_t[*len];    //开一个数组存音频
    fseek(fp, headersize, SEEK_SET);

    fread(*wav, sizeof(int16_t), *len, fp);

//就是除开头文件的长度,从第头文件长度+1的位置开始读取data出入wav中,读到len即整个音频data的长度结束

    fclose(fp);
    return 0;
}

在kaldi的管道存储文件中 .scp

每一行 表示数据从去掉音频文件名的位置开始读取数据

比如

BAC009S0105W0469
有16个字节,那么他kaldi会从他第17个字节开始读取他需要的数据

BAC009S0105W0469 /home/yongyu/Gao/kaldi/egs/aishell/v1/mfcc/raw_mfcc_train.4.ark:17
BAC009S0105W0470 /home/yongyu/Gao/kaldi/egs/aishell/v1/mfcc/raw_mfcc_train.4.ark:12255
BAC009S0105W0471 /home/yongyu/Gao/kaldi/egs/aishell/v1/mfcc/raw_mfcc_train.4.ark:18973
BAC009S0105W0472 /home/yongyu/Gao/kaldi/egs/aishell/v1/mfcc/raw_mfcc_train.4.ark:29331
BAC009S0105W0473 /home/yongyu/Gao/kaldi/egs/aishell/v1/mfcc/raw_mfcc_train.4.ark:38589
BAC009S0105W0474 /home/yongyu/Gao/kaldi/egs/aishell/v1/mfcc/raw_mfcc_train.4.ark:48207
BAC009S0105W0475 /home/yongyu/Gao/kaldi/egs/aishell/v1/mfcc/raw_mfcc_train.4.ark:55905

References:

https://blog.csdn.net/llearner/article/details/69396283

http://soundfile.sapp.org/doc/WaveFormat/

https://blog.csdn.net/pi9nc/article/details/12570841






猜你喜欢

转载自blog.csdn.net/robingao1994/article/details/79757254