WAV系列之二:ADPCM編解碼原理及代碼實現

參考自:《adpcm編解碼原理及其代碼實現》
    《ADPCM編碼與解碼學習筆記》
    《音頻編碼:ADPCM》

1、PCM

PCM (Pulse Code Modulation),脈衝編碼調製。

PCM是把聲音從模擬信號轉化爲數字信號的技術,把一個時間連續取值連續的模擬信號變換成時間離散取值離散的數字信號,模擬信號轉化爲數字信號需要三個步驟:採樣、量化、編碼。

1.1、採樣

採樣用一個固定的頻率對模擬信號進行提取樣值。

常用採樣率爲8KHz,16kHz,22.05kHz,32kHz,44.1kHz,48kHz,192kHz。

人耳能夠感覺到的最高頻率爲20kHz,要滿足人耳的聽覺要求,根據奈奎斯特採樣定律則,需要每秒進行40k次採樣,即40kHz。

8Khz的採樣率就可以達到人的對話程度,通常電話的採樣率爲8kHz/16kHz。

常見的無線電廣播採樣率爲22.05KHz,CD採樣率爲44.1kHz,DVD採樣率爲48kHz,Hi-Res音頻採樣率爲192kHz

1.2、量化編碼

量化編碼就是把採樣得到的聲音信號幅度轉換成數字值。這個過程會產生失真,量化的精度越高失真越小。常見的量化位數爲8bit,16bit,24bit。
clipboard
PCM約定俗成爲無損編碼,因爲PCM代表了數字音頻中最佳的保真水準,並不意味着PCM就能夠確保信號絕對保真,PCM也只能做到最大程度的無限接近。

2、DPCM

DPCM(Differential Pulse Code Modulation),差分脈衝編碼調。

PCM是不壓縮的,通常數據量比較大,存儲和通訊都必需付出比較大的代價,早期的通訊是不能傳輸那麼大的數據量的,所以就要想辦法把數據壓縮一下,以減少帶寬和存儲的壓力。

假設我們以8kHz的採樣率,16bit量化編碼,則1秒的數據量爲8000 * 16 = 128000 bit 。一般音頻信息都是比較連續的,不會突然很高或者突然很低,兩點之間差值不會太大,所以這個差值只需要很少的幾個位(比如4bit)即可表示。這樣,我們只需要知道前一個點的值,又知道它與下一個點的差值,就可以計算得到下一個點了。這個差值就是所謂的Differential ,將PCM數據轉成DPCM數據,數據量會小很多,如上面所說的用4bit的表示差值,則1秒的(8kHz採樣率16bit量化編碼) PCM數據轉成DPCM則只需要大約32000bit , 壓縮比大約4:1。

3、ADPCM

ADPCM (Adaptive Differential Pulse Code Modulation)、自適應差分脈衝編碼調。

音頻信號雖然是比較連續性的,有些差值比較小,有些差值比較大,如果差值比較大有可能用4bit表示不了,如果增大表示差值的位數(例如8bit\16bit)是可以解決這個問題,但就導致數據量變大,沒起到壓縮的目的,而且這種差值比較大的只是少數,大部分還是差值比較小的。

爲了解決這個問題,前輩們就想出了 ADPCM,定義一個因子,用差值除以因子的值來表示兩點之差,如果兩點之間差值比較大,則因子也比較大。通過因子引入,可以使得DPCM編碼自動適應差值比較大的數據。

ADPCM算法並沒用固定標準,最經典的就是IMA ADPCM

4、IMA-ADPCM 的編解碼原理

ADPCM(Adaptive Differential Pulse Code Modulation 差分脈衝編碼調製)主要是針對連續的波形數據的, 保存的是相臨波形的變化情況, 以達到描述整個波形的目的。本文的以IMA的ADPCM編碼標準爲例進行描述,IMA-ADPCM 是Intel公司首先開發的是一種主要針對16bit採樣波形數據的有損壓縮算法,壓縮比爲 4:1,它與通常的DVI-ADPCM是同一算法。 (對8bit數據壓縮時是3.2:1,也有非標準的IMA-ADPCM壓縮算法,可以達到5:1甚至更高的壓縮比) 4:1的壓縮是目前使用最多的壓縮方式。結尾附adpcm編解碼的源代碼adpcm.h與adpcm.c。

ADPCM編碼本質是一種預測編碼,那麼它是怎麼樣進行預測的呢?預測編碼利用相鄰的音頻數據在時間上的相關性,相鄰採樣點的音頻數據具有相似的特點。因此,經過壓縮後的數據並不是音頻數據本身,而是該數據的預測值與實際值之差。偏差需要量化器進行量化,假如我們對於16bit的音頻數據採用16bit的量化,那麼偏差與實際的數據值佔據的位數一樣則無法達到壓縮數據的目的,如果採用4bit的量化位數,其最大的量化步數只能是16,顯然是不能滿足使用要求,因此ADPCM應運而生,ADPCM是一種採用變步長的量化器的預測編碼算法,它的本質是根據預測值與實際的偏差範圍,在量化表格中選擇出合適的量化值,使預測變化的幅度保持在4bit的範圍內。ADPCM的核心公式如下,其中 delta 代表爲量化後的值,step 爲量化步長,vpdiff 代表經過量化後有效的偏差值,vpdiff 加上本次的預測值做爲下一次的運算的預測值:
在這裏插入圖片描述
在這裏插入圖片描述
整個ADPCM的編碼過程分三步進行:

第一步爲計算出當前實際值與預測值的偏差diffval 代表了當前數據的實際值,valpred 爲當前數的預測值。delta 爲量化後的帶符號的有效數據爲4bit的數據,其最高位代表的數據的方向,bit3爲1代表負數,代表-7~7的整型數據。
在這裏插入圖片描述
diff 小於0, delta bit3被置1。

第二步通過index(首次編碼index爲0)求出step,通過diff和step求出delta。

第三步爲對 diff 進行量化,簡易實現不考慮計算效率的情況下完全可以直接參考上面的公式,因爲是在計算機平臺進行了除法運算與小數運算,該作者很巧妙的把這些運算使用與或非來實現了,提高了運算的效率,有興趣的讀者可以看看代碼,學習一下這種思路。我們細看一下公式,
在這裏插入圖片描述
可以發現公式可以拆分爲兩部分實現,小數部分的量化被轉換爲了固定的step/8,因此節約了計算的成本。vpdiff 就是對應這部分的值。

 vpdiff = (step >> 3);

4.1、adpcm編碼原理

在這裏插入圖片描述
編碼步驟:

  1. 求出輸入的pcm數據與預測的pcm數據(第一次爲上一個pcm數據)的差值diff;
  2. 通過差分量化器算出delta(通過index(首次編碼index爲0)求出step,通過diff和step求出delta)。delta即爲編碼後的數據;
  3. 通過逆量化器求出vpdiff(通過求出的delta和step算出vpdiff);
  4. 求出新的預測valpred,即上次預測的valpred+vpdiff;
  5. 通過預測器(歸一化),求出當前輸入pcm input的預測pcm值,爲下一次計算用;
  6. 量化階調整(通過delta查表及index,計算出新的index值)。爲下次計算用;

4.2、adpcm解碼原理

在這裏插入圖片描述
解碼步驟(其實解碼原理就是編碼的第三到六步):

  1. 通過逆量化器求出vpdiff(通過存儲的delta和index,求出step,算出vpdiff);
  2. 求出新的預測valpred,即上次預測的valpred+vpdiff;
  3. 通過預測器(歸一化),求出當前輸入pcm input的預測pcm值,爲下一次計算用。預測的pcm值即爲解碼後的數據;
  4. 量化階調整(通過delta查表及index,計算出新的index值)。爲下次計算用;

註釋說明:

  1. 通過編碼和解碼的原理我們可以看出其實第一次編碼的時候已經進行了解碼,即預測的pcm。
  2. 因爲編碼再解碼後輸出的數據已經被量化了。根據計算公式delta = diff*4/step; vpdiff = (delta+0.5)*step/4;考慮到都是整數運算,可以推導出:pcm數據經過編碼再解碼生成的預測pcm數據,如果預測pcm數據再次編碼所得的數據與第一次編碼所得的數據是相同的。故pcm數據經過一次編碼有損後,不論後面經過幾次解碼再編碼都是數據一樣,音質不會再次損失。即相對於第一次編碼後,以後數據不論多少次編解碼,屬於無損輸出。

4.3、源代碼

adpcm.h

#ifndef ADPCM_H
#define ADPCM_H

struct adpcm_state
{
    int valprev;
    int index;
};

extern void adpcm_coder(short *indata, signed char *outdata, int len, struct adpcm_state *state);
extern void adpcm_decoder(signed char *indata, short *outdata, int len, struct adpcm_state *state);

#endif /*ADPCM_H*/

adpcm.c

/***********************************************************
Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The
Netherlands.

                        All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in 
supporting documentation, and that the names of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior permission.

STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

******************************************************************/

/*
** Intel/DVI ADPCM coder/decoder.
**
** The algorithm for this coder was taken from the IMA Compatability Project
** proceedings, Vol 2, Number 2; May 1992.
**
** Version 1.2, 18-Dec-92.
**
** Change log:
** - Fixed a stupid bug, where the delta was computed as
**   stepsize*code/4 in stead of stepsize*(code+0.5)/4.
** - There was an off-by-one error causing it to pick
**   an incorrect delta once in a blue moon.
** - The NODIVMUL define has been removed. Computations are now always done
**   using shifts, adds and subtracts. It turned out that, because the standard
**   is defined using shift/add/subtract, you needed bits of fixup code
**   (because the div/mul simulation using shift/add/sub made some rounding
**   errors that real div/mul don't make) and all together the resultant code
**   ran slower than just using the shifts all the time.
** - Changed some of the variable names to be more meaningful.
*/

#include "adpcm.h"
#include <stdio.h> /*DBG*/

#ifndef __STDC__
#define signed
#endif

/* Intel ADPCM step variation table */
static int indexTable[16] = {
    -1, -1, -1, -1, 2, 4, 6, 8,
    -1, -1, -1, -1, 2, 4, 6, 8,
};

static int stepsizeTable[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
};
    
void adpcm_coder(short *indata, signed char *outdata, int len, struct adpcm_state *state)
{
    short *inp;			/* Input buffer pointer */
    signed char *outp;		/* output buffer pointer */
    int val;			/* Current input sample value */
    int sign;			/* Current adpcm sign bit */
    int delta;			/* Current adpcm output value */
    int diff;			/* Difference between val and valprev */
    int step;			/* Stepsize */
    int valpred;		/* Predicted output value */
    int vpdiff;			/* Current change to valpred */
    int index;			/* Current step change index */
    int outputbuffer;		/* place to keep previous 4-bit value */
    int bufferstep;		/* toggle between outputbuffer/output */

    outp = (signed char *)outdata;
    inp = indata;

    valpred = state->valprev;
    index = state->index;
    step = stepsizeTable[index];
    
    bufferstep = 1;

    for ( ; len > 0 ; len-- ) {
	val = *inp++;

	/* Step 1 - compute difference with previous value */
	diff = val - valpred;
	sign = (diff < 0) ? 8 : 0;
	if ( sign ) diff = (-diff);

	/* Step 2 - Divide and clamp */
	/* Note:
	** This code *approximately* computes:
	**    delta = diff*4/step;
	**    vpdiff = (delta+0.5)*step/4;
	** but in shift step bits are dropped. The net result of this is
	** that even if you have fast mul/div hardware you cannot put it to
	** good use since the fixup would be too expensive.
	*/
	delta = 0;
	vpdiff = (step >> 3);
	
	if ( diff >= step ) {
	    delta = 4;
	    diff -= step;
	    vpdiff += step;
	}
	step >>= 1;
	if ( diff >= step  ) {
	    delta |= 2;
	    diff -= step;
	    vpdiff += step;
	}
	step >>= 1;
	if ( diff >= step ) {
	    delta |= 1;
	    vpdiff += step;
	}

	/* Step 3 - Update previous value */
	if ( sign )
	  valpred -= vpdiff;
	else
	  valpred += vpdiff;

	/* Step 4 - Clamp previous value to 16 bits */
	if ( valpred > 32767 )
	  valpred = 32767;
	else if ( valpred < -32768 )
	  valpred = -32768;

	/* Step 5 - Assemble value, update index and step values */
	delta |= sign;
	
	index += indexTable[delta];
	if ( index < 0 ) index = 0;
	if ( index > 88 ) index = 88;
	step = stepsizeTable[index];

	/* Step 6 - Output value 
	if ( bufferstep ) {
	    outputbuffer = (delta << 4) & 0xf0;
	} else {
	    *outp++ = (delta & 0x0f) | outputbuffer;
	}*/
    if ( bufferstep ) {
	    outputbuffer = delta & 0x0f;
	} else {
	    *outp++ = ((delta << 4) & 0xf0) | outputbuffer;
	}
	bufferstep = !bufferstep;
    }

    /* Output last step, if needed */
    if ( !bufferstep )
      *outp++ = outputbuffer;
    
    state->valprev = valpred;
    state->index = index;
}

void adpcm_decoder(signed char *indata, short *outdata, int len, struct adpcm_state *state)
{
    signed char *inp;		/* Input buffer pointer */
    short *outp;		/* output buffer pointer */
    int sign;			/* Current adpcm sign bit */
    int delta;			/* Current adpcm output value */
    int step;			/* Stepsize */
    int valpred;		/* Predicted value */
    int vpdiff;			/* Current change to valpred */
    int index;			/* Current step change index */
    int inputbuffer;		/* place to keep next 4-bit value */
    int bufferstep;		/* toggle between inputbuffer/input */

    outp = outdata;
    inp = (signed char *)indata;

    valpred = state->valprev;
    index = state->index;
    step = stepsizeTable[index];

    bufferstep = 0;
    
    for ( ; len > 0 ; len-- ) {
	
	/* Step 1 - get the delta value */
	if ( !bufferstep ) {
	    inputbuffer = *inp++;
	    delta = inputbuffer & 0xf;
	} else {
	    delta = (inputbuffer >> 4) & 0xf;
	}
	bufferstep = !bufferstep;

	/* Step 2 - Find new index value (for later) */
	index += indexTable[delta];
	if ( index < 0 ) index = 0;
	if ( index > 88 ) index = 88;

	/* Step 3 - Separate sign and magnitude */
	sign = delta & 8;
	delta = delta & 7;

	/* Step 4 - Compute difference and new predicted value */
	/*
	** Computes 'vpdiff = (delta+0.5)*step/4', but see comment
	** in adpcm_coder.
	*/
	vpdiff = step >> 3;
	if ( delta & 4 ) vpdiff += step;
	if ( delta & 2 ) vpdiff += step>>1;
	if ( delta & 1 ) vpdiff += step>>2;

	if ( sign )
	  valpred -= vpdiff;
	else
	  valpred += vpdiff;

	/* Step 5 - clamp output value */
	if ( valpred > 32767 )
	  valpred = 32767;
	else if ( valpred < -32768 )
	  valpred = -32768;

	/* Step 6 - Update step value */
	step = stepsizeTable[index];

	/* Step 7 - Output value */
	*outp++ = valpred;
    }

    state->valprev = valpred;
    state->index = index;
}

5、ADPCM數據存放形式

本部分爲adpcm數據存放說明,屬於細節部分,很多代碼解碼出來有噪音就是因爲本部分細節不對,所以需要仔細閱讀。

5.1、adpcm 數據塊介紹

adpcm數據是一個block一個block存放的,block由block header (block頭) 和data 兩者組成的。其中block header是一個結構體,它在單聲道下的定義如下:

Typedef struct
{
	short  sample0;    //block中第一個採樣值(未壓縮)
	BYTE  index;     //上一個block最後一個index,第一個block的index=0;
	BYTE  reserved;   //尚未使用
}MonoBlockHeader;

對於雙聲道,它的blockheader應該包含兩個MonoBlockHeader其定義如下:

typedaf struct
{
	MonoBlockHeader leftbher;
	MonoBlockHeader rightbher;
}StereoBlockHeader;

在解壓縮時,左右聲道是分開處理的,所以必須有兩個MonoBlockHeader;
有了blockheader的信息後,就可以不需要知道這個block前面數據而輕鬆地解出本block中的壓縮數據。故adpcm解碼只與本block有關,與其他block無關,可以只單個解任何一個block數據。
block的大小是固定的,可以自定義,每個block含的採樣數nsamples計算如下:

//
#define BLKSIZE 1024
block = BLKSIZE * channels;
//block = BLKSIZE;//ffmpeg
nsamples = (block  - 4 * channels) * 8 / (4 * channels) + 1;

例如audition軟件就是採用上面的,單通路block爲1024bytes,2041個samples,雙通路block爲2048,也是含有2041個sample。
而ffmpeg採用block =1024bytes,即不論單雙通路都爲1024bytes,通過公式可以算出單雙通路的samples數分別爲2041和1017;

5.2、單通路pcm格式

byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7 byte 8 byte 9
sample0 sample1 sample2 sample3 sample4

單通路壓縮爲adpcm數據爲 4bytes block head + raw data:

byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7 byte 8 byte 9
sample0 index reserved data0 data1 data2 data3 data4 data5

其中sample1編碼後存data0低4位,sample2編碼後存data0高四位...

5.3、雙通路pcm格式

byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7 byte 8 byte 9
sampleL0 sampleR0 sampleL1 sampleR1 sampleL2

雙通路壓縮爲adpcm數據爲 4bytes block L head + 4bytes block R head + 4bytes raw L data + 4bytes raw R data…:

adpcm雙通路block head:

byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7
sample0L indexL reservedL sample0R indexR reservedR

接着雙通路raw壓縮數據4byte L, 4byte R …:

byte8 byte9 byte10 byte11 byte12 byte13 byte14 byte15 byte16 byte17 byte18
data0L data1L data2L data3L data0R data1R data2R data3R data4L data5L data6L

注意:需要特別留意雙聲道的處理和當數據不夠1 block時的處理方式。

6、參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章