1. 程式人生 > >WAV檔案資料解析(4.10更新一點小程式碼)

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