首頁 > 評測 > 【嵌入式音頻】第五章|差分編碼,ADPCM與G726

【嵌入式音頻】第五章|差分編碼,ADPCM與G726

差分編碼   ADPCM   G726   嵌入式   音頻   
  • 作者:zhanzr
  • 來源:21ic
  • [導(dǎo)讀]
  • “EveryBoard Can Sing” 21ic打算攜手資(tu)深(ding)直男癌晚期工程師zhanzr21,來給大家講一講嵌入式系統(tǒng)與音頻處理的故事。 關(guān)于zhanzr21: 曾經(jīng)混跡于兩岸三地,摸爬滾打在前端后端,搞過學(xué)術(shù)上過班。現(xiàn)在創(chuàng)業(yè)中,歡迎各種撩

前言

本章介紹另外一個廣泛使用的ITU音頻算法標準G726.與同G711一樣, G726也是由數(shù)字電話中的應(yīng)用而發(fā)展而來的語音壓縮算法.數(shù)字電話應(yīng)用中,G726經(jīng)常與G711同時使用以提高帶寬利用率.

6t992m5wk76kn1.jpg

點擊鏈接加入群【嵌入式音頻信號處理】:https://jq.qq.com/?_wv=1027&k=45wk8Ks

嵌入式音頻專用資料代碼分享:https://pan.baidu.com/s/1dFh5pWd

本期活動地址:pls wt

幾個容易混淆的專業(yè)名詞

DM(Delta Modulation,也寫作Δ-modulation): 講的是一種調(diào)制編碼思路,即增量調(diào)制.比如如下數(shù)據(jù):

[1, 2, 3, 100, 0, 2000, 0, 60000]

如果以16 bit的PCM編碼傳輸,需要8x2=16個Bytes.如果以DM方式傳輸,最低只需要1個byte: 二進制(11110101).即僅僅傳輸每個sample與上次的變化趨勢.

DPCM(Differential pulse-code modulation):與DM基本同義,但是強調(diào)DM在音頻,信號處理方面的應(yīng)用.DPCM在音頻編碼上的應(yīng)用是1950年由Bell實驗室的C. Chapin Cutler發(fā)明的,當(dāng)然這個專利也早就過期了.

Cutler_DPCM_patent.png

圖 當(dāng)年的專利申請書中的DPCM算法說明

LDM(Linear Delta-Modulation): 講的是最簡單的DM,也就是上述DM例子中的種類.可以說是DM的子集,如果僅僅提到DM,一般指的是LDM.

Delta1.svg.png

圖 清晰一點的DM編碼與解碼算法說明

CVSD/CVSDM(Continuously variable slope delta modulation):是LDM的一種發(fā)展,LDM的增量描述的步長固定,CVSD的步長可變.CVSD在軍用通信中應(yīng)用較為廣泛,因為它能提供可靠的通信質(zhì)量,但是對計算要求不那么高,很適合在惡劣的環(huán)境提供穩(wěn)定的輸出.

ADPCM(Adaptive differential pulse-code modulation): 是CVSD進一步發(fā)展,自適應(yīng)地計算步長.是本文的主角算法.

IMA(The Interactive Multimedia Association): 一個音頻處理的行業(yè)組織,1998年就停止活動了.IMA的最為人稱道的成就就是優(yōu)化與規(guī)范了ADPCM算法的實現(xiàn)標準.目前江湖上的ADPCM也被稱為IMA ADPCM,但是事實上除了IMA ADPCM,也沒有其他的廣泛實現(xiàn)的ADPCM規(guī)范.IMA的規(guī)范對于ITU的標準的最大改進就是將對數(shù)與浮點運算都優(yōu)化成了查表與定點版本.這樣一來對硬件的要求就大大降低了.

G726:ITU的ADPCM的標準,主要從數(shù)學(xué)角度對算法上進行定義.G726整合了G721與G723兩個舊標準的內(nèi)容.G721覆蓋的內(nèi)容為32kbps的ADPCM版本.G723覆蓋的是24kbps與40kbps的ADPCM版本.G726在整合G721與G723的基礎(chǔ)上又增加了一個16kbps的版本.其中以32kbps的版本使用的最廣泛,本文也主要介紹這個版本.看完G711文章的讀者應(yīng)該有映像,G711的兩種版本都是將13bit/14bit采樣的音頻壓縮到8bit.因為數(shù)字電話一般都是8KHz的采樣率,所以G711的帶寬需求為8K*8bit=64kbps.而G726的16kbps,24kbps,32kbps與40kbps分別將8bit的采樣(一般就是G711的輸出,也可以直接是16bit的原始sample)再壓縮成2bit,3bit,4bit與5bit.為了不繞口,本文若不另加說明后面講的都是32kbps也就是4bit一個sample的版本.

G727跟G726內(nèi)容基本一致,只是講的ADPCM在另外一種環(huán)境下的應(yīng)用.為了行文方便,下文如果不另外說明,G726與ADPCM可互相替換.

算法數(shù)學(xué)公式與實驗

為了更好說明ADPCM,也順便提一下LDM與CVSD.因為這三種算法是逐步發(fā)展的概念,且都有很廣泛的應(yīng)用.只是在實際應(yīng)用中,尤其是民用的音頻應(yīng)用領(lǐng)域,ADPCM是最具知名度的.

LDM

LDM的原理很容易理解,即是把原來每個sample(比如G711的輸出:8bit,或者直接是16bit的PCM數(shù)據(jù))以一個bit的增量來表示.比如考慮到以下的簡單遞增8bit PCM序列:

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}

如果直接傳輸或者存儲該序列,則需要Lengh*sample_depth的數(shù)據(jù)量,這個特例中:

DataEntropy = 128 * 8bit = 128bit

也就是要傳輸128bit.但是仔細觀察了這個序列就可以得知,每個序列之間的變化只有一個LSB.所以可以將初始值設(shè)定為滿幅度的1/2,之后每次傳輸sample之間的增量:1.這樣立即得到了8:1的壓縮比.

只需傳輸以下數(shù)據(jù)序列(以byte流表示,第一個sample為初始值):

{ 00, 00, 00, 00, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF}

在解碼的時候,遇到bit=0則減,遇到bit=1則增加即可基本完全還原原始數(shù)據(jù).

simple_series_ascend_codec_ldm.png

圖 遞增序列的LDM解碼波形與原始波形對比(紅為解碼結(jié)果,藍色為原始信號)

上面的例子是完全均等遞增的例子,那么其他數(shù)據(jù)怎么辦.考慮這樣一個方波序列:

{127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

還是以上面的算法進行編碼,結(jié)果為這樣:

{ FF, FF, 00, 00, FF, FF, 00, 00, FF, FF, 00, 00, FF, FF, 00, 00}

進行解碼之后,與原始波形相比是這樣:

square_original_decode.png

圖 方波經(jīng)過LDM編解碼之后與原始波形對比(藍色為原始波形,紅色為解碼出來的波形)

不難看出來,LDM對快速變化的信號響應(yīng)不夠快.這是因為LDM使用1bit表示每兩個sample之間的變化,故此變化的步長也就固定了.步長若設(shè)定的太大則會失調(diào)震蕩,若設(shè)定的過小則無法響應(yīng)快速變化.所幸的是,音頻信號尤其是語音信號,變化幅度不可能有上述的方波那么夸張.

再來考慮一個正弦序列的實驗結(jié)果:

simple_sine_codec_ldm.png

圖 1/4幅度,400Hz的正弦波經(jīng)過LDM編解碼之后與原始波形對比(藍色為原始波形,紅色為解碼出來的波形)

再來考慮一個直流序列的實驗結(jié)果:

simple_dc_codec_ldm.png

圖 直流序列經(jīng)過LDM編解碼之后與原始波形對比(藍色為原始波形,紅色為解碼出來的波形)

結(jié)論是:LDM因為只有一個變化步長,所以能夠較好的編碼變化緩慢的信號,而對變化較大的信號則失真嚴重.

附上測試代碼:(網(wǎng)盤內(nèi)可直接打包下載)

# LDM 8bit Encode/Decode Test

# Author: zhanzr21 @ 21ic BBS

#

import math

import sys

list_8bit_pcm = []

STEP_SIZE = 1

TEST_LENGTH = 128

POS_AMP = 127

NEG_AMP = 0

FSample = 8000

TEST_SIGNAL_FREQ = 400

SAMPLE_N_PERIOD = (FSample//TEST_SIGNAL_FREQ)

INIT_VAL = int((POS_AMP/2))

GAIN=0.25

for i in range(TEST_LENGTH):

#Square

list_8bit_pcm.append((POS_AMP if (0==(i//16)%2) else NEG_AMP))

#Simple Ascend

#           list_8bit_pcm.append(i)

#Sine

# tmpSample = (POS_AMP/2) + (POS_AMP/2)*math.sin(i*(2*math.pi)/SAMPLE_N_PERIOD)

# tmpSample = int(tmpSample * GAIN)

# list_8bit_pcm.append(tmpSample)

#DC

# tmpSample = 30

# list_8bit_pcm.append(tmpSample)

print()

print("The Original signal")

for v in list_8bit_pcm:

print( v, end=', ')

print()

print("Now Encode")

#test encode

byte_encode = 0

byte_reach = INIT_VAL

list_encode = []

for i in range(0,len(list_8bit_pcm)):

if(list_8bit_pcm[i]>byte_reach):

byte_encode = byte_encode | (1<<(i%8))

byte_reach = byte_reach + STEP_SIZE

else:

byte_reach = byte_reach - STEP_SIZE

#Update the reference

#byte_reach = list_8bit_pcm[i]

if(0==(i+1)%8):

#output

print('%02X' % (byte_encode), end=', ')

list_encode.append(byte_encode)

byte_encode = 0

print()

print("Now Decode")

#test decode

byte_encode = 0

byte_reach = INIT_VAL

list_decode = []

for i in range(0,len(list_encode)):

for j in range(8):

if(0!=(list_encode[i]&(1<

byte_reach = byte_reach + 1

list_decode.append(byte_reach)

else:

byte_reach = byte_reach - 1

list_decode.append(byte_reach)

#output

print('%u' % (byte_reach), end=', ')

CVSD

如上所述,此種算法是針對LDM的固定步長的改良.即遇到連續(xù)的1或者0則相應(yīng)的修改步長.一般情況下連續(xù)的門限為4或者3.還是針對上面的幾種波形做實驗.

square_codec_cvsd.png

圖 方波經(jīng)過CVSD編解碼之后與原始波形對比(藍色為原始波形,紅色為解碼出來的波形)

ascend_codec_cvsd.png

圖 遞增序列經(jīng)過CVSD編解碼之后與原始波形對比(藍色為原始波形,紅色為解碼出來的波形)

sine_codec_cvsd.png

圖 正弦序列經(jīng)過CVSD編解碼之后與原始波形對比(藍色為原始波形,紅色為解碼出來的波形)

simple_dc_codec_ldm.png

圖 直流序列經(jīng)過CVSD編解碼之后與原始波形對比(藍色為原始波形,紅色為解碼出來的波形)

不難看出來,CVSD與LDM相比起來對原始信號的還原度有所提高.當(dāng)然CVSD的調(diào)整門限,調(diào)整步長的速度都是可以設(shè)置的變量.因為CVSD并非本文主題,為了避免將話題扯的太遠,這里不深入展開.代碼在下面,感興趣的同學(xué)可以自己做做實驗.

CVSD測試代碼:

# CVSD 8bit Encode/Decode Test

# Author: zhanzr21 @ 21ic BBS

#

import math

import sys

TEST_LENGTH = 128

POS_AMP = 127

NEG_AMP = 0

FSample = 8000

TEST_SIGNAL_FREQ = 400

INIT_STEP_SIZE = 1

STEP_OF_STEP = 5

THRESOLD = 4

MAX_STEP_SIZE = int((POS_AMP/8))

MIN_STEP_SIZE = INIT_STEP_SIZE

SAMPLE_N_PERIOD = (FSample//TEST_SIGNAL_FREQ)

INIT_VAL = int((POS_AMP/2))

GAIN=0.25

list_8bit_pcm = []

for i in range(TEST_LENGTH):

#Square

# list_8bit_pcm.append((POS_AMP if (0==(i//16)%2) else NEG_AMP))

#Simple Ascend

# list_8bit_pcm.append(i)

#Sine

# tmpSample = (POS_AMP/2) + (POS_AMP/2)*math.sin(i*(2*math.pi)/SAMPLE_N_PERIOD)

# tmpSample = int(tmpSample * GAIN)

# list_8bit_pcm.append(tmpSample)

#DC

tmpSample = 30

list_8bit_pcm.append(tmpSample)

print()

print("The Original signal")

for v in list_8bit_pcm:

print( v, end=', ')

print()

print("Now Encode")

#test encode

STEP_SIZE = INIT_STEP_SIZE

byte_encode = 0

byte_reach = INIT_VAL

cont_1 = 0

cont_0 = 0

list_encode = []

for i in range(0,len(list_8bit_pcm)):

if(list_8bit_pcm[i]>byte_reach):

cont_1 = cont_1+1

cont_0 = 0

if(cont_1==THRESOLD):

#Increase the step and limit the step size

STEP_SIZE = STEP_SIZE+STEP_OF_STEP

STEP_SIZE = MAX_STEP_SIZE if(STEP_SIZE>MAX_STEP_SIZE) else STEP_SIZE

cont_1 = 0

byte_encode = byte_encode | (1<<(i%8))

byte_reach = byte_reach + STEP_SIZE

else:

cont_0 = cont_0+1

cont_1 = 0

if(cont_0==THRESOLD):

#Decrease the step and limit the step size

STEP_SIZE = STEP_SIZE-STEP_OF_STEP

STEP_SIZE = MIN_STEP_SIZE if(STEP_SIZE

cont_1 = 0

byte_reach = byte_reach - STEP_SIZE

if(0==(i+1)%8):

#output

print('%02X' % (byte_encode), end=', ')

list_encode.append(byte_encode)

byte_encode = 0

print()

print("Now Decode")

#test decode

byte_encode = 0

byte_reach = INIT_VAL

list_decode = []

STEP_SIZE = INIT_STEP_SIZE

cont_1 = 0

cont_0 = 0

for i in range(0,len(list_encode)):

for j in range(8):

mark = (0!=(list_encode[i]&(1<

if(mark):

cont_1 = cont_1+1

cont_0 = 0

if(cont_1==THRESOLD):

#Increase the step and limit the step size

STEP_SIZE = STEP_SIZE+STEP_OF_STEP

STEP_SIZE = MAX_STEP_SIZE if(STEP_SIZE>MAX_STEP_SIZE) else STEP_SIZE

cont_1 = 0

byte_reach = byte_reach + STEP_SIZE

else:

cont_0 = cont_0+1

cont_1 = 0

if(cont_0==THRESOLD):

#Decrease the step and limit the step size

STEP_SIZE = STEP_SIZE-STEP_OF_STEP

STEP_SIZE = MIN_STEP_SIZE if(STEP_SIZE

cont_1 = 0

byte_reach = byte_reach - STEP_SIZE

list_decode.append(byte_reach)

#output

print('%u' % (byte_reach), end=', ')

ADPCM

千呼萬喚始出來,我有點擔(dān)心沒有耐心的讀者已經(jīng)散了.不過ADPCM算法為上述LDM與CVSD的自然發(fā)展,這么寫我覺得更易理解.LDM使用固定步長,CVSD線性地調(diào)整步長,而ADPCM則是自適應(yīng)地調(diào)整步長.作為代價,G726所規(guī)定的最低速率是16kbps也就是2bit一個sample,對于8bit輸入是4:1的壓縮比,相對于LDM與CVSD的8:1已經(jīng)低了不少.而最常用的32kbps的版本則是4bit對應(yīng)一個sample,對于8bit的PCM來講是2:1的壓縮比.雖然減少了壓縮比,但是ADPCM的效果對于LDM與CVSD有很大提高.

稍稍介紹一下子ADPCM的算法框架.先看編碼:

encode_adpcm_block_algorithm.png

圖 ADPCM編碼框圖

其中Si為輸入.預(yù)測的結(jié)果Sp與量化步長序號q保存為靜態(tài)變量,因為下次的編碼要用到.初始化的時候,Sp與q設(shè)定為0.t為最終的返回值(對于我們的32Kbps的版本,即4bit).輸入一個采樣Si后,計算預(yù)測結(jié)果與實際輸入的差值:d.對這個差值進行自適應(yīng)的量化得到返回值t.編碼器與解碼器都有內(nèi)部狀態(tài)變量.編碼器中其實也包含一個解碼器,這是為了節(jié)省需要傳輸?shù)臓顟B(tài)變量.上圖中用虛線包起來的就是內(nèi)嵌的解碼器部分.此解碼器使用返回值t已更新反向量化器,并得到一個逆量化差值:dq.dq會被加到預(yù)測值Sp上去以備下次迭代之用.

再看解碼:

decode_adpcm_block_algorithm.png

圖 ADPCM解碼框圖

解碼的輸入為1個4bit的nibble(對于32kbps的版本來講)t,Sp與q同樣為靜態(tài)變量,每次迭代都會改變.初始的預(yù)測結(jié)果與步長指針都為0.dq增加到預(yù)測結(jié)果Sp上去,并輸出Sr.Sr也被更新為下一次迭代所需的Sp.

先看一個500個sample的編解碼效果:

adpcm_codec_sample_effect.png

圖 ADPCM效果圖(藍色為原始數(shù)據(jù),紅色為編碼解碼還原數(shù)據(jù),除了剛開始有點誤差,后面看不出明顯的誤差)

以下是ADPCM的測試代碼,這里讀入一段16bit的單通道音頻數(shù)據(jù),并進編碼解碼實驗.出于篇幅的關(guān)系,這里不做生成信號的實驗,但是感興趣的同學(xué)可以修改原始數(shù)據(jù)的那部分進行研究.ADPCM可以輸入8bit也可輸入16bit,考慮到16bit的音頻數(shù)據(jù)更加方便獲取與使用,這里使用16bit作為編碼的輸入與解碼的輸出.

# ADPCM 16bit Encode/Decode Test

# This is for algorithm test and study only, use Python audioop library if you

# need standard and tested quality

#

# Author: zhanzr21 @ 21ic BBS

#

import math

import sys

#Quantizer step size lookup table

StepSizeTable = [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]

#Table of index changes

IndexTable = [-1,-1,-1,-1,2,4,6,8,-1,-1,-1,-1,2,4,6,8]

#ADPCM_Encode.

#sample: a 16-bit PCM sample

#retval : a 4-bit ADPCM sample

index = 0

predsample = 0

def ADPCM_Encode(sample):

global index

global predsample

code=0

tmpstep=0

diff=0

diffq=0

step=0

step = StepSizeTable[index]

#compute diff and record sign and absolut value

diff = sample-predsample

if (diff < 0):

code=8

diff = -diff

#quantize the diff into ADPCM code

#inverse quantize the code into a predicted diff

tmpstep = step

diffq = (step >> 3)

if (diff >= tmpstep):

code = code | 0x04

diff = diff - tmpstep

diffq = diffq + step

tmpstep = tmpstep >> 1

if (diff >= tmpstep):

code = code | 0x02

diff = diff - tmpstep

diffq = diffq + (step >> 1)

tmpstep = tmpstep >> 1

if (diff >= tmpstep):

code = code | 0x01

diffq = diffq + (step >> 2)

#fixed predictor to get new predicted sample

if (code & 8):

predsample = predsample - diffq

else:

predsample = predsample + diffq

#check for overflow

if (predsample > 32767):

predsample = 32767

elif (predsample < -32768):

predsample = -32768

#find new stepsize index

index = index + IndexTable[code]

#check for overflow

if (index <0):

index = 0

elif (index > 88):

index = 88

#return new ADPCM code

return (code & 0x0f)

#ADPCM_Decode.

#code: a byte containing a 4-bit ADPCM sample.

#retval : 16-bit ADPCM sample

de_index = 0

de_predsample = 0

def ADPCM_Decode(code):

global de_index

global de_predsample

step=0

diffq=0

step = StepSizeTable[de_index]

#inverse code into diff

diffq = step>> 3

if (code&4):

diffq += step

if (code&2):

diffq += step>>1

if (code&1):

diffq += step>>2

# add diff to predicted sample

if (code&8):

de_predsample -= diffq

else:

de_predsample += diffq

# check for overflow

if (de_predsample > 32767):

de_predsample = 32767

elif (de_predsample < -32768):

de_predsample = -32768

# find new quantizer step size

de_index += IndexTable[code]

# check for overflow

if (de_index < 0):

de_index = 0

if (de_index > 88):

de_index = 88

# save predict sample and de_index for next iteration

# return new decoded sample

return (de_predsample)

list_16bit_pcm = bytes()

#Read From Sample File

fin = open('little_16bit8k.bin', mode='rb')

list_16bit_pcm=fin.read()

fin.close()

print()

print("ADPCM encode/decode Demo")

for i in range(len(list_16bit_pcm)//4):

#first sample

testSampleU16_0 = list_16bit_pcm[i*4] + list_16bit_pcm[1 + i*4] * 256

#unsigned to signed

if(testSampleU16_0>32767):

test_sample_0 = testSampleU16_0 - 65536

else:

test_sample_0 = testSampleU16_0

#second sample

testSampleU16_1 = list_16bit_pcm[2+i*4] + list_16bit_pcm[3 + i*4] * 256

#unsigned to signed

if(testSampleU16_1>32767):

test_sample_1 = testSampleU16_1 - 65536

else:

test_sample_1 = testSampleU16_1

# print( test_sample_0)

# print( test_sample_1)

#

#Now Encode

tmpU8_0 = ADPCM_Encode(test_sample_0)

tmpU8_1 = ADPCM_Encode(test_sample_1)

#print('%02X' % (tmpU8_1 | (tmpU8_0<<4)), end=' ')

#Now Decode

tmpDeS16_0 = ADPCM_Decode(tmpU8_0)

tmpDeS16_1 = ADPCM_Decode(tmpU8_1)

#print(tmpDeS16_0)

#print(tmpDeS16_1)

實踐制作ADPCM音頻數(shù)據(jù)

之前的文章都是提到使用Audacity來制作音頻數(shù)據(jù).但這集有點不同,因為Audacity本身支持的ADPCM版本為VOX ADPCM版本,與我們這里介紹的IMA ADPCM算法有點小小不同.

vox_ima_adpcm_diff.png

圖 上下兩曲線分別是Audacity分別VOX算法解碼VOX編碼與IMA編碼的ADPCM的結(jié)果,總體包絡(luò)一至,但幅度與過零點有些差別

當(dāng)然直接使用也沒有很大問題.導(dǎo)入導(dǎo)出時注意選擇格式:

audacity_import_format.png

圖 導(dǎo)入導(dǎo)出選擇格式,至于采樣率只是為了試聽與數(shù)據(jù)處理,不影響文件內(nèi)容

但為了更加精確評估研究,作者準備了一個工具腳本,使用Python的標準庫audioop來制作ADPCM編碼數(shù)據(jù).audioop使用的IMA ADPCM版本算法與我們實驗所用算法是一樣的.

將原始音頻制作成ADPCM的數(shù)據(jù)文件:

# ADPCM 16bit Encode Tool

# Using standlibary audioop to compress a 16bit PCM data file into 4bit ADPCM data file

#

# Author: zhanzr21 @ 21ic BBS

#

import audioop

INPUT_FILE = 'radio_dump_8k16bit_30s.bin'

ENCODE_FILE = 'audioop.adpcm'

#Read From data File

fin = open(INPUT_FILE, mode='rb')

raw_pcm=fin.read()

fin.close()

encode = audioop.lin2adpcm(raw_pcm, 2, None)

#Write to encode data file

fencode = open(ENCODE_FILE, mode='wb')

fencode.write(encode[0])

fencode.close()

如需改變文件,可直接修改代碼中定義文件名的位置.為了簡潔沒有做帶參數(shù)執(zhí)行的處理.

將ADPCM數(shù)據(jù)文件解碼成原始16bit的PCM音頻:

# ADPCM 16bit Decode Tool

# Using standlibary audioop to decode ADPCM data file into raw 16bit PCM data file

#

# Author: zhanzr21 @ 21ic BBS

#

import audioop

INPUT_FILE = 'audioop.adpcm'

DECODE_FILE = 'decode.bin'

#Read From data File

fin = open(INPUT_FILE, mode='rb')

raw_adpcm=fin.read()

fin.close()

decode = audioop.adpcm2lin(raw_adpcm, 2, None)

#Write to Decode data file

fdecode = open(DECODE_FILE, mode='wb')

fdecode.write(decode[0])

fdecode.close()

嵌入式系統(tǒng)播放實驗(STM32F769-Disco開發(fā)板為例)

這節(jié)實驗與上節(jié)基本相同,因為兩種算法相當(dāng)類似,都是固定壓縮比.所以流程也類似,只是我為了把實驗硬件搞得多樣化一點,這次用了STM32F769-Disco開發(fā)板子做實驗.實驗用的22050采樣率,16bit,單通道的音頻,這次使用久石讓的天空之城作為演示音頻.

首先把已有音頻轉(zhuǎn)成你想要的格式的raw數(shù)據(jù),再用上述所說的python腳本工具轉(zhuǎn)成ADPCM數(shù)據(jù),最后用STLink/或者JLink燒入芯片.這些步驟以前都講過多次,如有不明白可以翻翻以前的文章或者論壇發(fā)貼,群里直接提問.

adpcm_software_process.png

圖 軟件流程

因為跟之前的播放差不多,所以這里也不詳解展開,如有疑問直接看共享文件夾中的代碼,或者論壇群內(nèi)提問都可以.

f769_playback_copy.jpg

圖 F769播放ADPCM

總結(jié)

這節(jié)從基礎(chǔ)原理,編碼思想上對ADPCM進行了比較深入的分析.可能有讀者會覺得有點冗長,但是因為從LDM,CVSD著手,所里講解還是循序漸進的.這種思想不僅用在音頻編解碼,在其他很多領(lǐng)域都有應(yīng)用,所以大家學(xué)一下子是有用處的.多謝閱讀,如有任何建議與意見可以通過論壇/交流群聯(lián)系作者本人或者編輯.論壇活動見!

  • 本文系21ic原創(chuàng),未經(jīng)許可禁止轉(zhuǎn)載!

網(wǎng)友評論