本文由三部分組成,第一部分背景介紹 —— 音頻類型及本文動機,第二部分類比matlab下wavread()函數的做用,第三部分則給出該函數的C++實現。html
1)全部wav音頻處理的基礎就是將wav格式的文件解析出來,解析成數組才能供咱們去作後續的處理(fft等等)。shell
2)在matlab中直接有一個很好用的函數wavread(' test.wav'),輸入是wav音頻,輸出是數組,如第二章所述。windows
3)通常的C++函數讀取出來的數據,格式如1.2節所述,然而不論是什麼格式,數據之間是可互相轉換的。數組
4) 我在解決問題的過程當中,沒有發現一篇詳細的參考文獻。數據結構
鑑於此,本文將介紹如何用C++徹底實現matlab的wavread函數,輸出數據格式如出一轍,在這個過程當中,你們也能夠領略文件中數據的本質,及相互間的轉換關係。函數
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字節 };
WAVE 文件做爲多媒體中使用的聲音波形文件格式之一,它是以RIFF(Resource Interchange File Format)格式爲標準的。每一個WAVE文件的頭四個字節即是「RIFF」。一樣的,WAVE 文件由文件頭和數據體兩大部分組成。其中文件頭又分爲 RIFF/WAV 文件標識段和聲音數據格式說明段兩部分。WAVE文件各部份內容及格式見後文。編碼
常見的聲音文件主要有兩種,分別對應於單聲道(11.025KHz 採樣率、8Bit 的採樣值)和雙聲道(44.1KHz 採樣率、16Bit 的採樣值)。採樣率是指:聲音信號在「模→數」轉換過程當中單位時間內採樣的次數。採樣值是指每一次採樣週期
內聲音模擬信號的積分值。spa
對於單聲道聲音文件,採樣數據爲八位的短整數(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 1e 3c 13 3c 14 16 18 34 23 3c 24 11 1a 0d
舉例分析數據:形如 'FFFF' 爲一個咱們須要的完整的數據。如上圖中 sample3:3c 和 13是兩個數組合在一塊兒是一個咱們須要的數, 3c 13,但右端爲大端,則應爲 3c 13,十六進制數3c按位轉換爲2進製爲0011 1100,同理13按位轉換爲2進製爲0001 0011,則連起來的16bits的二進制數爲0011 1100 0001 0011,那麼咱們能夠看到符號位爲0,即爲正數。
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)
上面介紹了這麼多,咱們來進入主題,怎麼用C++實現matlab中的wavread('testwav.wav')函數,且輸出一致。
在介紹以前,咱們須要瞭解這幾串數據之間的關係。本章節以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個數。
那麼C++實現,就是先讀取原始採樣數據,每次讀16字節,而後將16字節的16進制數字轉化成十進制數,再轉換成其補碼,並歸一化。轉換時注意大小端和符號問題。
具體的C++代碼,我已分享,讀者可移步查看:http://www.oschina.net/code/snippet_1768500_39013
參考文獻
1. http://www.cnblogs.com/liyiwen/archive/2010/04/19/1715715.html