1. 程式人生 > >淺談adpcm編解碼原理及其程式碼實現

淺談adpcm編解碼原理及其程式碼實現

本文講解 0x0011 /* Intel’s DVI ADPCM */的編碼解碼原理。本文原創,轉發請標註littlezls原創。

1. 原始碼

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; }

2. adpcm編解碼原理

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值)。為下次計算用;

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資料經過一次編碼有損後,不論後面經過幾次解碼再編碼都是資料一樣,音質不會再次損失。即相對於第一次編碼後,以後資料不論多少次編解碼,屬於無損輸出。

3. ADPCM資料存放形式

本部分為adpcm資料存放說明,屬於細節部分,很多程式碼解碼出來有噪音就是因為本部分細節不對,所以需要仔細閱讀。

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;

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高四位...

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 byte17
data0L data1L data2L data3L data0R data1R data2R data3R data4L data5L data6L

4. 編解碼程式碼實現