1. 程式人生 > >【wav音訊解析】之wavread函式的C++實現

【wav音訊解析】之wavread函式的C++實現

      本文由三部分組成,第一部分背景介紹 —— 音訊型別及本文動機,第二部分類比matlab下wavread()函式的作用,第三部分則給出該函式的C++實現。

一 背景介紹

1.1 本文動機

1)所有wav音訊處理的基礎就是將wav格式的檔案解析出來,解析成陣列才能供我們去做後續的處理(fft等等)。

2)在matlab中直接有一個很好用的函式wavread(' test.wav'),輸入是wav音訊,輸出是陣列,如第二章所述。

3)一般的C++函式讀取出來的資料,格式如1.2節所述,然而不管是什麼格式,資料之間是可互相轉換的。

4) 我在解決問題的過程中,沒有發現一篇詳細的參考文獻。

      鑑於此,本文將介紹如何用C++完全實現matlab的wavread函式,輸出資料格式一模一樣,在這個過程中,大家也可以領略檔案中資料的本質,及相互間的轉換關係。

1.2 音訊型別

RIFF全稱為資源互換檔案格式(ResourcesInterchange FileFormat),RIFF檔案是windows環境下大部分多媒體檔案遵循的一種檔案結構,RIFF檔案所包含的資料型別由該檔案的副檔名來標識,能以RIFF檔案儲存的資料包括:音訊視訊交錯格式資料(.AVI) 波形格式資料(.WAV) 點陣圖格式資料(.RDI) MIDI格式資料(.RMI)調色盤格式(.PAL)多媒體電影(.RMN)動畫游標(.ANI)其它RIFF檔案(.BND)。

Chunk是組成RIFF檔案的基本單元它的基本結構如下:

struct chunk{
  u32 id; //4個ASCII字元組成,用以識別塊中所包含的資料。如:'RIFF'
,'LIST','fmt','data','WAV','AVI'等   u32 size;    //塊大小,是儲存在data域中資料的長度,id與size域的大小則不包括在該值內   u8 dat[size];   //塊內容,資料以字(WORD)為單位排列,如果該資料結構長度是奇數,則最後添一個NULL位元組 };
1.3 wav音訊檔案

WAVE 檔案作為多媒體中使用的聲音波形檔案格式之一,它是以RIFF(Resource Interchange File Format)格式為標準的。每個WAVE檔案的頭四個位元組便是“RIFF”。同樣的,WAVE 檔案由檔案頭和資料體兩大部分組成。其中檔案頭又分為 RIFF/WAV 檔案標識段和聲音資料格式說明段兩部分。WAVE檔案各部分內容及格式見後文。

常見的聲音檔案主要有兩種,分別對應於單聲道(11.025KHz 取樣率、8Bit 的取樣值)和雙聲道(44.1KHz 取樣率、16Bit 的取樣值)。取樣率是指:聲音訊號在“模→數”轉換過程中單位時間內取樣的次數。取樣值是指每一次取樣週期      
內聲音模擬訊號的積分值。

對於單聲道聲音檔案,取樣資料為八位的短整數(short int 00H-FFH);而對於雙聲道立體聲聲音檔案,每次取樣資料為一個16位的整數(int),高八位和低八位分別代表左右兩個聲道。

WAVE 檔案資料塊包含以脈衝編碼調製(PCM)格式表示的樣本。WAVE 檔案是由樣本組織而成的。在單聲道 WAVE 檔案中,聲道0代表左聲道,聲道1代表右聲道。在多聲道WAVE檔案中,樣本是交替出現的。

WAVE 檔案除了前面一小段檔案頭對資料組織進行說明之外,Data 塊就是聲音的原始取樣資料,WAVE 檔案雖然可以壓縮,但一般都使用不壓縮的格式。44.1KHz 取樣率、16Bit的解析度、雙聲道,所以WAVE可以儲存音質要求非常高的聲音檔案,CD 採用的也是這種格式,聲音方面的專家或是音樂發燒友們應該非常熟悉。但這種檔案的體積也非常大,以 44.1KHz 16bit 雙聲道的資料為例,一分鐘的聲音資料量為:4100*2byte*2channel*60s/1024/1024=10.09M 。所以不合適在網上傳送。

下面我們具體地分析 WAVE 檔案的格式

endian

field name

Size

big ChunkID 4 檔案頭標識,一般就是" RIFF" 四個字母
little ChunkSize 4 整個資料檔案的大小,不包括上面ID和Size本身
big Format 4 一般就是" WAVE" 四個字母
big SubChunk1ID 4 格式說明塊,本欄位一般就是"fmt "
little SubChunk1Size 4 本資料塊的大小,不包括ID和Size欄位本身
little AudioFormat 2 音訊的格式說明
little NumChannels 2 聲道數
little SampleRate 4 取樣率
little ByteRate 4 位元率,每秒所需要的位元組數
little BlockAlign 2 資料塊對齊單元
little BitsPerSample 2 取樣時模數轉換的解析度
big SubChunk2ID 4 真正的聲音資料塊,本欄位一般是"data"
little SubChunk2Size 4 本資料塊的大小,不包括ID和Size欄位本身
little Data N 音訊的取樣資料

以下是對各個欄位的詳細解說:

ChunkID 4bytes ASCII 碼錶示的“RIFF”。(0x52494646)
ChunkSize 4bytes 36+SubChunk2Size,或是            
4 + ( 8 + SubChunk1Size ) + ( 8 + SubChunk2Size ),            
這是整個資料塊的大小(不包括ChunkID和ChunkSize的大小)
Format 4bytes ASCII 碼錶示的“WAVE”。(0x57415645)
SubChunk1ID 新的資料塊(格式資訊說明塊)            
ASCII 碼錶示的“fmt ”——最後是一個空格。(0x666d7420)
SubChunk1Size 4bytes 本塊資料的大小(對於PCM,值為16)。
AudioFormat 2bytes PCM = 1 (比如,線性取樣),如果是其它值的話,則可能是一些壓縮形式
NumChannels 2bytes 1 => 單聲道  |  2 => 雙聲道
SampleRate 4bytes 取樣率,如 8000,44100 等值
ByteRate 4bytes 等於: SampleRate * numChannels * BitsPerSample / 8
BlockAlign 2bytes 等於:NumChannels * BitsPerSample / 8
BitsPerSample 2bytes 取樣解析度,也就是每個樣本用幾位來表示,一般是 8bits 或是 16bits
SubChunk2ID 4bytes 新資料塊,真正的聲音資料            
ASCII 碼錶示的“data ”——最後是一個空格。(0x64617461)
SubChunk2Size 4bytes 資料大小,即,其後跟著的取樣資料的大小。
Data N bytes 真正的聲音資料

對於Data塊,根據聲道數和取樣率的不同情況,佈局如下(每列代表8bits):

1). 8 Bit 單聲道:

取樣1 取樣2
資料1 資料2

2). 8 Bit 雙聲道

取樣1 取樣2
聲道1資料1 聲道2資料1 聲道1資料2 聲道2資料2

3). 16 Bit 單聲道:

取樣1 取樣2
資料1低位元組 資料1高位元組 資料1低位元組 資料1高位元組

4). 16 Bit 雙聲道

取樣1
聲道1資料1低位元組 聲道1資料1高位元組 聲道2資料1低位元組 聲道2資料1高位元組
取樣2
聲道1資料2低位元組 聲道1資料2高位元組 聲道2資料2低位元組 聲道2資料2高位元組

 下面我們看一個具體的例子,wav音訊檔案如下:(十六進位制的形式)

52 49 46 46 24 08 00 00 57 41 56 45 
66 6d 74 20 10 00 00 00 01 00 02 00 
22 56 00 00 88 58 01 00 04 00 10 00 
64 61 74 61 00 08 00 00 00 00 00 00 
24 17 1313 314 16 18 34 23 324 11 10d

對應的分析如下圖所示:

untitled

     舉例分析資料:形如 'FFFF' 為一個我們需要的完整的資料。如上圖中 sample3:3c 和 13是兩個數組合在一起是一個我們需要的數, 3c 13,但右端為大端,則應為 3c 13,十六進位制數3c按位轉換為2進製為0011 1100,同理13按位轉換為2進製為0001 0011,則連起來的16bits的二進位制數為0011 1100 0001 0011,那麼我們可以看到符號位為0,即為正數。

二 matlab中的wavread( )函式

  1. wavread('testwav.wav' )

    讀者試試看輸出。例如,取我的一個聲音檔案'testwav.wav',輸出的最後10個數據為:

        -0.0001 -0.0001 -0.0002 -0.0003 -0.0002 -0.0002  -0.0002  -0.0003  -0.0002  -0.0002

   2.  wavread('testwav.wav','native')

        讀者可以試試看輸出。我的'testwav.wav' 輸出的最後10個數據為:

         -4  -2  -8  -9  -7  -8  -8  -11  -5  -7

1 和 2 的輸出資料之間的轉換公式為:-0.0002 = -7 / 32768 (其中32768 = 2 ^15,即2的15次冪。這是歸一化。因為編碼為16bits)

三 readwav的 C++實現

上面介紹了這麼多,我們來進入主題,怎麼用C++實現matlab中的wavread('testwav.wav')函式,且輸出一致。

3.1 編碼轉換規則

在介紹之前,我們需要了解這幾串資料之間的關係。本章節以test.wav檔案的資料為例來分析:

(1)該wave檔案的Data塊即原始取樣資料的最後20個數據是:

 fc ff fe ff f8 ff f7 ff f9 ff f8 ff f8 ff f5 ff fb ff f9 ff

(2)在matlab中解析得到的最後10個數據是:

-0.0001 -0.0001 -0.0002 -0.0003 -0.0002 -0.0002  -0.0002  -0.0003  -0.0002  -0.0002

     這兩組資料之間是原碼與補碼的關係,即(1)是原碼而(2)是補碼。

由資料(1)轉換為資料(2)的步驟是:先將(1)轉換為其補碼,再用補碼除以32768,則得到(2)。

原碼與補碼之間的轉換原則:

(2進位制形式的轉換):若原碼為正數,則補碼是其本身。若原碼為負數,則補碼為符號位不變,數值位按位取反,再加1。

(數值形式的轉換):若原碼為正數,則補碼是其本身。若原碼為負數,補碼 = 原碼 - 2^16。溫馨提示: 為了方便計算數值上有等價替換 2^16 = FFFF - 1。

為了更好的理解,舉例說明:

步驟一(每次讀16位元組):由於資料是從X0000到XFFFF的資料。以f9 ff為例,右端為大端,換言之,右端是高位,則應該是fff9。步驟二(轉換為補碼):按位轉換為二進位制形式為1111 1111 1111 1001(1位16進位制數值對應4位二進位制數值),該資料為原碼,轉換成帶符號的十進位制形式,先看符號位判斷其為負數,則補碼為FFF9 - FFFF -1 = -7。步驟三(歸一化):用補碼數值-7除以32768,取小數點後4位(四捨五入),則等於-0.0002,正確。

讀者可以試著用我的方法算一下(1)中的右起第3第4個數,是否對應等於(2)的右起第2個數。

3.2 C++實現

那麼C++實現,就是先讀取原始取樣資料,每次讀16位元組,然後將16位元組的16進位制數字轉化成十進位制數,再轉換成其補碼,並歸一化。轉換時注意大小端和符號問題。

參考文獻