ADPCM(自適應差分脈衝編碼調變)的原理和計算

2022-11-22 21:00:26

關於ADPCM

ADPCM(Adaptive Differential Pulse Code Modulation, 自適應差分脈衝編碼調變) 是一種音訊訊號數位化編碼技術, 聲頻壓縮標準G.722, G.723, G.726 中都會使用到 ADPCM

G.722 is an ITU-T standard 7 kHz wideband audio codec operating at 48, 56 and 64 kbit/s. It was approved by ITU-T in November 1988. Technology of the codec is based on sub-band ADPCM (SB-ADPCM). The corresponding narrow-band codec based on the same technology is G.726.[1]

G.723 is an ITU-T standard speech codec using extensions of G.721 providing voice quality covering 300 Hz to 3400 Hz using Adaptive Differential Pulse Code Modulation (ADPCM) to 24 and 40 kbit/s for digital circuit multiplication equipment (DCME) applications.

G.726 is an ITU-T ADPCM speech codec standard covering the transmission of voice at rates of 16, 24, 32, and 40 kbit/s. It was introduced to supersede both G.721, which covered ADPCM at 32 kbit/s, and G.723, which described ADPCM for 24 and 40 kbit/s. G.726 also introduced a new 16 kbit/s rate. The four bit rates associated with G.726 are often referred to by the bit size of a sample, which are 2, 3, 4, and 5-bits respectively. The corresponding wide-band codec based on the same technology is G.722.

要了解ADPCM, 需要先了解PCM和DPCM.

PCM, Pulse Code Modulation, 脈衝編碼調變

PCM 是聲音模擬訊號數位化的一種基礎技術, 就是把時間連續取值連續的模擬訊號變換成離散取值的數位訊號, 熟悉ADC(模擬數位轉換)的應該很好理解, 過程就是取樣, 量化和編碼.

1. 取樣

用固定的頻率, 對模擬訊號提取樣本值, 人耳能夠感覺到的最高頻率為20kHz, 根據奈奎斯特取樣定律, Nyquist rate, 只需要每秒進行40k次取樣, 就能覆蓋人耳的聽覺範圍, 也就是說取樣高於40k每秒對於普通人來說, 聽覺基本上沒有提升了.

In signal processing, the Nyquist rate, named after Harry Nyquist, is a value (in units of samples per second or hertz, Hz) equal to twice the highest frequency (bandwidth) of a given function or signal. When the function is digitized at a higher sample rate (see § Critical frequency), the resulting discrete-time sequence is said to be free of the distortion known as aliasing. Conversely, for a given sample-rate the corresponding Nyquist frequency in Hz is one-half the sample-rate. Note that the Nyquist rate is a property of a continuous-time signal, whereas Nyquist frequency is a property of a discrete-time system.

人類語音的頻率在300 - 3000 Hz之間, 成年男性的語音基頻在85-155Hz, 女性在165-255Hz, 同時會產生豐富的諧振頻率, 在通訊上, 需要保證 300 - 3400Hz 的頻率範圍才能滿足正常通話, 對於這個頻率範圍, 需要使用 8K每秒的取樣率. 通常電話的取樣率為8k和16kHz.

The voiced speech of a typical adult male will have a fundamental frequency from 85 to 155 Hz, and that of a typical adult female from 165 to 255 Hz.[3] Thus, the fundamental frequency of most speech falls below the bottom of the voice frequency band as defined. However, enough of the harmonic series will be present for the missing fundamental to create the impression of hearing the fundamental tone.

日常音訊訊號常見的取樣率為8K, 16k, 22.05k, 32k, 44.1k, 48k, 192k. 常見的無線電廣播最高取樣率為22.05K, CD最高取樣率為44.1k, DVD最高取樣率為48k, Hi-Res音訊取樣率可以高達192k.

2. 量化和編碼

量化和編碼就是把取樣得到的訊號幅度轉換成數位值(ADC), 再組織成固定尺寸的序列. PCM實際上就是一個大陣列, 陣列中每個值, 代表了當前時間點上的模擬量強度, 在播放時在對應的時間點上被轉換為模擬量輸出(DAC).

在量化的過程中會產生誤差, 一般而言, ADC的精度越高, 失真越小. 常見的量化位數為8bit, 16bit, 24bit. 模數轉換都會有誤差和底噪, 對於ADC而言, 除了精度, 還有轉換的實現方式, 電壓基準, 電磁環境等都會對轉換效果造成影響.

DPCM, Differential Pulse Code Modulation, 差分脈衝編碼調變

PCM 儲存的是最原始的模數轉換結果, 是不壓縮的, 資料量比較大, 儲存和通訊都會佔用很大資源, 需要將資料壓縮以減少通訊頻寬和儲存的資源消耗.

將音訊PCM的陣列展開觀察可以看到, 資料值與相鄰的值通常是比較連續的, 不會突然很高或者突然很低, 兩點之間差值不會太大, 所以這個差值可以用很少的幾個位(比如4bit)表示. 這樣只需要知道起始點的值和每個點的差值, 就可以還原得到原來的序列. 記錄的差值序列就是DPCM資料, 這樣資料量會小很多.

以8k取樣率為例, 如果量化精度為16bit, 則1秒的資料量為8000 * 16 bit = 128kb, 如果用4bit的表示差值, 則1秒的 PCM 資料轉成 DPCM 只需要約 32kb.

ADPCM, Adaptive Differential Pulse Code Modulation, 自適應差分脈衝編碼調

DPCM存在一個問題, 音訊訊號雖然比較連續性, 但是存在差值較大的情況, 例如差值超過4bit表示的範圍(-15, 15) 就無法很好還原原來的PCM序列, 這時候如果增大差值寬度, 例如用6bit, 8bit表示, 可以減小這個問題, 但資料量也增大了.

ADPCM 的出發點就是解決 DPCM 的差值寬度問題, 通過定義一個差值表(例如IMA ADPCM 中使用 89個固定差值, 取值從7到32767), 將差值的範圍放寬到16bit, 此時差值在陣列中的編號只需要6bit就可以表示(0 - 88), 再進一步只記錄編號的變化值, 就將變化量壓縮到了4bit.

ADPCM演演算法是一個統稱, 有 YAMAHA, Microsoft, IMA 等標準, 下面以嵌入式開發中最常見的 IMA ADPCM 為例進行說明

IMA ADPCM 編碼

在瞭解編解碼演演算法前, 先了解 IMA ADPCM 的編碼格式.

16bit的 IMA ADPCM 編碼產生的資料為一個陣列, 陣列中每個數都是4個bit(值範圍為0x00到0x0F), 因為C語言程式設計中變數的最小單位是byte, 所以通常表示為 uint8_t 陣列, 陣列中每個元素儲存2個 ADPCM 編碼值, 或者對於32位元系統使用 uint32_t, 每個元素儲存8個 ADPCM 編碼值.

對於IMA ADPCM, 還需要了解兩個碼錶, 一個是差值步長碼錶, 一個是差值步長下標變化量碼錶

  • 差值步長碼錶: 下標從0到88, 共89個值, 從小到大, 非均勻分佈, 下標越大, 值之間的間隔越大, 這個碼錶的具體計算方式不清楚, 通過多次項擬合需要至少4次方到5次方才能擬合.
  • 差值步長下標變化量碼錶: 下標從-7到7, ADPCM 佇列中每個值可以通過這個直接查表得到下一個值的差值步長的下標變化量, 進而得到下一個值的差值步長. 值在 [-3, 3] 之間的, 變化都是-1, 也就是差值步長變小, 在[-4,-7]和[4,7]的, 變化是2,4,6,8, 可以看到對於-7和7, 差值步長會快速增大.

IMA ADPCM的編碼格式說明

知道了 ADPCM 編碼值的格式, 也知道了兩個碼錶, 就可以瞭解 ADPCM 編碼值中各個bit位的作用.

例如一個編碼值為0x05, 對應二進位制0101, 其中最高位為0, 代表變化為正, 輸出值是在前一個值上疊加; 低三位為5, 代表差值步長下標變化量為+4, 也就是差值步長變大了, 另外第三位的每一位分別代表對應實際差值的差值步長的倍數, 參與了差值的計算

bit位 含義
4 0 最高位代表了正負, 如果是0, 代表這個差值是正的, 1則表示差值是負的
3 1 1-3bit合起來代表了下一個值的差值步長下標變化量, 同時每個bit代表當前步長的一個係數, 這個bit表示1倍差值步長
2 0 這個bit表示0.5倍差值步長
1 1 這個bit表示0.25倍差值步長

以上會產生 1 + 0 + 0.25 = 1.25倍的差值步長, 加上固定的1/8步長, 就是說這一步產生的輸出 = 前一步數值 + 當前差值步長 * 1.375, 這個值會作為下一步的數值, 同時下一步的差值步長下標+4, 也就是下一個值的計算中用到的差值步長增大了.

IMA ADPCM 的編解碼例子

輸入序列為

uint16_t nums[12] = {0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0050, 0x0050, 0x0040, 0x0400, 0x0400, 0x0400, 0x0400};

使用 IMA ADPCM 對上面的序列進行編碼, 每一步的記錄, 第一列為輸入, 最後一列為輸出

  • 初始: 0x0000,
  • 輸入: 0x0010, 步長下標0x00, 步長0x0007, 前取樣值0x0000, 輸出: 編碼0x7
  • 輸入: 0x0020, 步長下標0x08, 步長0x0010, 前取樣值0x000B, 輸出: 編碼0x5
  • 輸入: 0x0030, 步長下標0x0C, 步長0x0017, 前取樣值0x0021, 輸出: 編碼0x2
  • 輸入: 0x0040, 步長下標0x0B, 步長0x0015, 前取樣值0x002E, 輸出: 編碼0x3
  • 輸入: 0x0050, 步長下標0x0A, 步長0x0013, 前取樣值0x003F, 輸出: 編碼0x3
  • 輸入: 0x0050, 步長下標0x09, 步長0x0011, 前取樣值0x004E, 輸出: 編碼0x0
  • 輸入: 0x0050, 步長下標0x08, 步長0x0010, 前取樣值0x0050, 輸出: 編碼0x0
  • 輸入: 0x0040, 步長下標0x07, 步長0x000E, 前取樣值0x0052, 輸出: 編碼0xD
  • 輸入: 0x0400, 步長下標0x0B, 步長0x0015, 前取樣值0x0040, 輸出: 編碼0x7
  • 輸入: 0x0400, 步長下標0x13, 步長0x002D, 前取樣值0x0066, 輸出: 編碼0x7
  • 輸入: 0x0400, 步長下標0x1B, 步長0x0061, 前取樣值0x00B9, 輸出: 編碼0x7
  • 輸入: 0x0400, 步長下標0x23, 步長0x00D1, 前取樣值0x016E, 輸出: 編碼0x7

解碼輸出的序列為

# 解碼結果
0x000B, 0x0021, 0x002E, 0x003F, 0x004E, 0x0050, 0x0052, 0x0040, 0x0066, 0x00B9, 0x016E, 0x02F5
# 作為參照的原輸入序列
0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0050, 0x0050, 0x0040, 0x0400, 0x0400, 0x0400, 0x0400

通過觀察可以得到幾個規律

  1. 解碼還原的結果, 就是編碼過程中每一次產生的presample
  2. 每一次的code, 查表得到偏移量, 疊加在當前的index上, 產生下一個index, 而index又決定了step大小
  3. step的大小並不會直接影響下一個presample, 而是會跟code進行計算, 最多1.875個step, 最少0.125個step
    1. code 第四位(高) -- 決定正負
    2. code 第三位 -- 一個step
    3. code 第二位 -- 1/2個step
    4. code 第一位(低) -- 1/4個step
    5. 不管以上何值, 都會帶一個1/8 step 的變化
  4. 如果code大於4或小於-4, 說明差值大於一個步長, 步長需要增大, 這個規則體現在了差值步長下標變化量碼錶

IMA ADPCM 的程式碼分析

碼錶

/* 差值步長碼錶 */
const uint16_t 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};
/* 差值步長下標變化量碼錶 */
const int8_t IndexTable[16]={0xff,0xff,0xff,0xff,2,4,6,8,0xff,0xff,0xff,0xff,2,4,6,8};

編碼函數

熟悉前面的格式和編解碼邏輯, 下面的程式碼就比較好理解了. 函數輸入是一個16bit數位, 輸出一個4bit數位, 中間用兩個static變數, 用於儲存前一步確定的差值步長下標, 以及前一次的解碼值, 參與下一個值的編碼計算

uint8_t ADPCM_Encode(int32_t sample)
{
  // index 儲存的是上一次預測的差值步長下標, 通過查表可以得到步長
  static int16_t  index = 0;
  // predsample 儲存的是上一個解碼值, 解碼還原時產生的就是這個值
  static int32_t predsample = 0;
  // 當前輸入值, 編碼後的輸出, 4個bit
  uint8_t code=0;
  uint16_t tmpstep=0;
  int32_t diff=0;
  int32_t diffq=0;
  uint16_t step=0;
  
  // 先拿到差值步長
  step = StepSizeTable[index];

  // 看看當前輸入值, 跟上一個輸出值的差值
  diff = sample-predsample;
  // 如果是負的, 就給code 的高4位元置1, 表示差值是負數
  if (diff < 0)  
  {
    code=8;
    diff = -diff;
  }    
  
  tmpstep = step;

  // 以下根據差值, 計算步長的乘數係數(同時就會產生步長下標的偏移量)
  // 首先是固定的 1/8個step
  diffq = (step >> 3);

  // 下面就是按位元進行除法, 每一位的結果被依次賦值到code的3,2,1位, 同時presample的值也算出來了
  if (diff >= tmpstep)
  {
    code |= 0x04;
    diff -= tmpstep;
    diffq += step;
  }
  
  tmpstep = tmpstep >> 1;

  if (diff >= tmpstep)
  {
    code |= 0x02;
    diff -= tmpstep;
    diffq+=(step >> 1);
  }
  
  tmpstep = tmpstep >> 1;
  
  if (diff >= tmpstep)
  {
    code |=0x01;
    diffq+=(step >> 2);
  }
  // 到這一步, 如果code值大於等於4或小於等於-4, 就說明差值大於當前的步長(至少是1.125倍), 步長要增加, 否則步長要收縮

  // 以下都是避免值越界的一些計算
  if (code & 8)
  {
    predsample -= diffq;
  }
  else
  {
    predsample += diffq;
  }  

  if (predsample > 32767)
  {
    predsample = 32767;
  }
  else if (predsample < -32768)
  {
    predsample = -32768;
  }
  
  // 查表得到下一個數的差值步長下標
  index += IndexTable[code];
  // 避免越界
  if (index <0)
  {
    index = 0;
  }
  else if (index > 88)
  {
    index = 88;
  }
  
  // 避免越界
  return (code & 0x0f);
}

解碼函數

解碼就是將ADPCM陣列中的每個4bit數值, 還原回編碼過程中的每個presample值.

/**
  * @brief  ADPCM_Decode.
  * @param code: a byte containing a 4-bit ADPCM sample. 
  * @retval : 16-bit ADPCM sample
  */
int16_t ADPCM_Decode(uint8_t code)
{
  // 上一步預測的差值步長下標
  static int16_t  index = 0;
  // 上一步的解碼值
  static int32_t predsample = 0;
  uint16_t step=0;
  int32_t diffq=0;
  
  // 得到當前步長
  step = StepSizeTable[index];

  // 根據步長和4bit編碼值, 計算當前的實際差值

  // 先是1/8的固定差值
  diffq = step>> 3;

  // 第3位, 1倍步長
  if (code&4)
  {
    diffq += step;
  }
  // 第2位, 0.5倍步長
  if (code&2)
  {
    diffq += step>>1;
  }
  // 第1位, 0.25倍步長
  if (code&1)
  {
    diffq += step>>2;
  }

  // 根據正負符號, 加或者減, 算出解碼結果
  if (code&8)
  {
    predsample -= diffq;
  }
  else
  {
    predsample += diffq;
  }
  
  // 防止越界
  if (predsample > 32767)
  {
    predsample = 32767;
  }
  else if (predsample < -32768)
  {
    predsample = -32768;
  }

  // 查表, 計算得到下一個差值步長的下標
  index += IndexTable [code];
  // 防止越界
  if (index < 0)
  {
    index = 0;
  }
  if (index > 88)
  {
    index = 88;
  }
  
  // 返回解碼結果
  return ((int16_t)predsample);
}

總結

以上說明了ADPCM的原理, 以及通過對 IMA ADPCM 編碼程式碼的分析, 瞭解 ADPCM 的具體實現. ADPCM的優點是計算簡單, 對CPU和儲存資源的損耗很小, 以及可觀的壓縮比(接近4:1), 編解碼延時最短(相對其它技術), 缺點是有失真壓縮, 音質不算好, 壓縮率也不算高. 因為對硬體要求低, 適用於低成本低功耗的嵌入式硬體. 對於資源不受限, 有高音質需求, 或者有更高壓縮比需求的場景, 可以使用其它的編碼演演算法, 例如LPCM, AAC, MP3等.

參考