1. 程式人生 > >ADPCM檔案解碼詳解

ADPCM檔案解碼詳解

一、概述:
  本文敘述瞭如何通過IMA-ADPCM壓縮和解壓縮演算法來完成從IMA-ADPCM檔案轉換為PCM檔案的過程。主要包括的內容有:PCM和IMA-ADPCM WAVE檔案內部結構的介紹,IMA-ADPCM壓縮與解壓縮演算法,以及如何生成特有的音訊壓縮格式檔案等三方面的內容。


二、WAVE檔案的認識
  WAVE檔案是計算機領域最常用的數字化聲音檔案格式之一,它是微軟專門為Windows系統定義的波形檔案格式(Waveform Audio),由於其副檔名為"*.wav"。
wave檔案有很多不同的壓縮格式,而且現在一些程式生成的wave檔案都或多或少地含有一些錯誤。這些錯誤的產生不是因為單個數據壓縮和解壓縮演算法的問題,而是因為在壓縮和解壓縮後沒有正確地組織好檔案的內部結構。所以,正確而詳細地瞭解各種WAVE檔案的內部結構是成功完成壓縮和解壓縮的基礎,也是生成特有音訊壓縮格式檔案的前提。
最基本的WAVE檔案是PCM(脈衝編碼調製)格式的,這種檔案直接儲存取樣的聲音資料沒有經過任何的壓縮,是音效卡直接支援的資料格式,要讓音效卡正確播放其它被壓縮的聲音資料,就應該先把壓縮的資料解壓縮成PCM格式,然後再讓音效卡來播放。

1.Wave檔案的內部結構
WAVE檔案是以RIFF(Resource Interchange File Format,"資源互動檔案格式")格式來組織內部結構的。RIFF檔案結構可以看作是樹狀結構,其基本構成是稱為"塊"(Chunk)的單元,最頂端是一個“RIFF”塊,下面的每個塊有“型別塊標識(可選)”、“標誌符”、“資料大小”及“資料”等項所組成,塊的結構如表1所示:

基本chunk的內部結構表

上面說到的“型別塊標識”只在部分chunk中用到,如“WAVE”chunk中,這時表示下面巢狀有別的chunk,當使用了“型別塊標識”時,該chunk就沒有別的項(如塊標誌符,資料大小等),它只作為檔案讀取時的一個標識。先找到這個“型別塊標識”,再以它為起來讀取它下面巢狀的其它chunk。
每個檔案最前端寫入的是RIFF塊,每個檔案只有一個RIFF塊。從表2中可以看出它的結構:

PCM WAVE檔案的內部結構示意表

PCM格式的檔案會至少多加入一個“fact”塊,它用來記錄資料解壓縮後的大小。(注意是資料而不是檔案)這個“fact”塊一般加在“data”塊的前面。

2.WAVEFORMAT結構的認識

PCM和非PCM的主要區別是聲音資料的組織不同,這些區別可以通過兩者的WAVEFORMAT結構來區分。下面以PCMIMA-ADPCM來進行對比:

WAVE的基本結構WAVEFORMATEX結構定義如下:

typedef struct

{

WORD wFormatag;  //編碼格式,包括WAVE_FORMAT_PCM//WAVEFORMAT_ADPCM

WORD  nChannls;       //聲道數,單聲道為

1,雙聲道為2;

DWORD nSamplesPerSec;//取樣頻率;

DWORD nAvgBytesperSec//每秒的資料量;

WORD  nBlockAlign;//塊對齊;

WORD  wBitsPerSample;//WAVE檔案的取樣大小;

WORD  sbSize;        //PCM中忽略此值

}WAVEFORMATEX

PCM的結構就是基本結構;

IMAADPCMWAVEFORMAT結構定義如下:

Typedef struct

{

WAVEFORMATEX wfmt;

WORD nSamplesPerBlock;

}IMAADPCMWAVEFORMAT;

IMA-ADPCMwfmt->cbsize不能忽略,一般取值為2,表示此型別的WAVEFORMAT比一般的WAVEFORMAT多出2個位元組。這兩個字元也就是nSamplesPerBlock

3.“fact”chunk的內部組織

在非PCM格式的檔案中,一般會在WAVEFORMAT結構後面加入一個“factchunk,結構如下:

typedef struct{

char[4];          //fact”字串

DWORD chunksize;

DWORD datafactsize;    //資料轉換為PCM格式後的大小。

}factchunk; 

datafactsize是這個chunk中最重要的資料,如果這是某種壓縮格式的聲音檔案,那麼從這裡就可以知道他解壓縮後的大小。對於解壓時的計算會有很大的好處!

4.“datachunk的內部組織

從“datachunk的第9個位元組開始,儲存的就是聲音資訊的資料了,(前八個位元組儲存的是標誌符“data”和後接資料大小size(DWORD)。這些資料可能是壓縮的,也可能是沒有壓縮的。

PCM中的聲音資料沒有被壓縮,如果是單聲道的檔案,取樣資料按時間的先後順序依次存入。(它的基本組織單位是BYTE(8bit)WORD(16bit))如果是雙聲道的檔案,取樣資料按時間先後順序交叉地存入。如圖所示:

IMA-ADPCM是壓縮格式,它是從PCM16位取樣壓縮成4位的。對於單聲道的IMA-ADPCM來說,它是將PCM的資料按時間次序依次壓縮並寫入檔案中的,每個byte中含兩個取樣,低四位對應第一個取樣,高四位對應第二個取樣。而對於雙聲道的IMA-ADPCM來說,它的儲存相對就麻煩一些了,它是將PCM的左聲道的前8個取樣依次壓縮並寫入到一個DWORD中,然後寫入“datachunk裡。緊接著是右聲道的前8個取樣。以此迴圈,當取樣數不足8時(到資料尾端),應該把多出來的取樣用0填充。其示意圖如下:

特別注意:

IMA-ADPCM中,“datachuck中的資料是以block形式來組織的,我把它叫做“段”,也就是說在進行壓縮時,並不是依次把所有的資料進行壓縮儲存,而是分段進行的,這樣有一個十分重要的好處:那就是在只需要檔案中的某一段資訊時,可以在解壓縮時可以只解所需資料所在的段就行了,沒有必要再從檔案開始起一個一個地解壓縮。這對於處理大檔案將有相當的優勢。同時,這樣也可以保證聲音效果。

Block一般是由block header (blockdata 兩者組成的。其中block header是一個結構,它在單聲道下的定義如下:

Typedef struct

{

short  sample0;    //block中第一個取樣值(未壓縮)

BYTE  index;     //上一個block最後一個index,第一個blockindex=0;

BYTE  reserved;   //尚未使用

}MonoBlockHeader;

有了blockheader的資訊後,就可以不需要知道這個block前面和後面的資料而輕鬆地解出本block中的壓縮資料。對於雙聲道,它的blockheader應該包含兩個MonoBlockHeader其定義如下:

typedaf struct

{

MonoBlockHeader leftbher;

MonoBlockHeader rightbher;

}StereoBlockHeader;

在解壓縮時,左右聲道是分開處理的,所以必須有兩個MonoBlockHeader;

注1:上述的index是解壓縮演算法中必須用到的一個引數。詳見後面。

注2: 關於block的大小,通常會有以下幾種情況:

對於單聲道,大小一般為512byte,顯然這裡面可以儲存的sample個數為(512-sizeof(MonoBlockHeader))/4 + 1 = 1017個<其中"+1"是第一個存在頭結構中的沒有壓縮的sample.

對於雙聲道,大小一般為1024byte,按上面的演算法可以得出,其中的sample個數也是1017個.

4.讀取WAVE檔案的方法.

在知道了WAVE檔案的內部資料組織後,可以直接通過FILE或HFILE來實現檔案的讀取。但由於WAVE檔案是以RIFF格式來組織的,所以用多媒體輸入輸出流來操作將更加方便,可以直接在檔案中查詢chunk並定位資料。

三、IMA-ADPCM 編碼和解碼演算法

IMA-ADPCM Intel公司首先開發的是一種主要針對16bit取樣波形資料的有失真壓縮演算法壓縮比為4:1.它與通常的DVI-ADPCM是同一演算法。(8bit資料壓縮時是3.2:1,也有非標準的IMA-ADPCM壓縮演算法,可以達到5:1甚至更高的壓縮比)4:1的壓縮是目前使用最多的壓縮方式。

  ADPCM(Adaptive Differential Pulse Code Modulation 差分脈衝編碼調製)主要是針對連續的波形資料的儲存的是相臨波形的變化情況以達到描述整個波形的目的。演算法中必須用到兩個一維陣列,setptab[] index_adjust[],附在下面的程式碼之後。

--------------------------------------------------------------------------------

IMA-ADPCM 壓縮過程

首先我們認為聲音訊號都是從零開始的,那麼需要初始化兩個變數

int index = 0,prev_sample = 0;

但在實際使用中,prev_sample的值是每個block中第一個取樣的值。(這點在後面的block中會詳細介紹)

假設已經寫好了兩個函式:

GetNextSamp() ——得到一個16bit 的取樣資料;

SaveComCode() ——儲存一個4bit 的壓縮樣品;

下面的迴圈將依次壓縮聲音資料流:

while (還有資料要處理) {

cur_sample = GetNextSamp();         // 得到PCM中的當前取樣資料

diff = cur_sample-prev_sample;      // 計算出和上一個的增量

if (diff<0)

{

diff=-diff;

fg=8; 

}  

else fg=0;                          // fg 儲存的是符號位

code = 4*diff / steptab[index]; 

if (code>7) code=7;                 // 根據steptab[] 得到一個0~7 的值,它描述了取樣振幅的變化量

index+=index_adjust[code];          // 根據聲音強度調整下次取steptab 的序號,便於下次得到更精確的變化量的描述

if (index<0) index=0;               // 調整index的值

else if (index>88) index=88;

prev_sample=cur_sample;

SaveComCode(code|fg);                 // 加上符號位儲存起來

}

--------------------------------------------------------------------------------

IMA-ADPCM 解壓縮過程

解壓縮實際是壓縮的一個逆過程,假設寫好了以下兩個函式:

GetNextCode() ——得到一個編碼(4bit

OutputSamp() ——將解碼出來的聲音訊號儲存起來(16bit

int index=0,cur_sample=0;

while (還有資料要處理) {

code=GetNextCode();           // 得到下一個壓縮樣品Code 4bit

if ((code & 8) != 0) fg=1 else fg=0;

code&=7;                      // code 分離為資料和符號

diff = (steptab[index]*code) /4 + steptab[index] / 8;   // 後面加的一項是為了減少誤差

if (fg==1) diff=-diff;

cur_sample+=diff;            // 計算出當前的波形資料

if (cur_sample>32767) OutputSamp(32767);

else if (cur_sample<-32768) OutputSamp(-32768);

else OutputSamp(cur_sample);

index+=index_adjust[code];

if (index<0) index=0;

if (index>88) index=88;

}

--------------------------------------------------------------------------------

附表

int index_adjust[8] = {-1,-1,-1,-1,2,4,6,8}; 

int steptab[89] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 }; 

四、設計自己特用的壓縮聲音檔案格式。

    有了上述幾節的知識,要基於IMA-ADPCM Encode/Dcode演算法來設計出特有的壓縮聲音檔案格式就不難了!只要 先設計好特有的檔案內部結構和特殊的資料組織結構,再以此為標準編寫壓縮和解壓縮程式就行了。由於我們已經搞清楚了PCM裡面的資料組織,所以我們還可以進行PCM檔案的擷取、連線、壓縮等更多功能。