在數(shù)字音頻的浩瀚星圖中,ADPCM是?顆低調(diào)卻恒久的星,它誕生于1970年代貝爾實(shí)驗(yàn)室的走廊,見證了從電話交換機(jī)到 PlayStation 游戲機(jī)的滄桑變遷,如今依然在工業(yè)控制器的蜂鳴聲、監(jiān)控錄像的背景音、老舊 WAV文件的字節(jié)流中默默運(yùn)轉(zhuǎn);?FFmpeg,這位開源音視頻領(lǐng)域的"全能管家",正是與ADPCM對話的最佳橋梁。本篇將通過代碼和原理,完整拆解ADPCM的編解碼魔法。
理解 ADPCM:壓縮的哲學(xué)
1.1 為什么需要 ADPCM
1980年代,存儲介質(zhì)寸土寸金,一分鐘CD音質(zhì)的PCM音頻(44.1kHz/16bit/立體聲)需要約10MB空間,這在軟盤時(shí)代是天文數(shù)字;工程師們開始思考:能否只存儲"變化",而非"全部"?人類語音和自然聲音有一個(gè)顯著特征:相鄰采樣點(diǎn)高度相關(guān),前一個(gè)樣本是1000,下一個(gè)大概率在950~1050之間,?非跳到30000。ADPCM正是抓住了這個(gè)規(guī)律。
1.2 ADPCM 的核心思想
ADPCM(Adaptive Differential Pulse Code Modulation,自適應(yīng)差分脈沖編碼調(diào)制)的名字藏著三個(gè)關(guān)鍵詞:
| 關(guān)鍵詞 | 含義 |
| Differential(差分) | 不存原始值,只存與預(yù)測值的差 |
| Adaptive(自適應(yīng)) | 量化步長隨信號動態(tài)調(diào)整 |
| Pulse Code(脈沖編碼) | 最終輸出為離散數(shù)字碼 |
直觀理解:
原始PCM序列: 1000 1050 1080 1120 1100 1060 ... ↓ ↓ ↓ ↓ ↓ ↓ 預(yù)測值: (0) 1000 1050 1080 1120 1100 ... ↓ ↓ ↓ ↓ ↓ ↓ 差值: 1000 +50 +30 +40 -20 -40 ... ↓ ↓ ↓ ↓ ↓ ↓ 量化后(4bit): 15 6 4 5 13 12 ... 壓縮?: 16 bit → 4 bit = 4:1
1.3 ADPCM家族:30種變體的江湖
打開FFmpeg的 libavcodec/adpcm.c,會看到一個(gè)龐大的switch-case,這里住著30多位"兄弟姐妹":
ADPCM 家族譜系圖 ├── 微軟陣營 │ ├── ADPCM_MS ← Windows 系統(tǒng)音頻,7組自適應(yīng)系數(shù) │ └── ADPCM_IMA_WAV ← 更簡單,兼容性更好 │ ├── 蘋果陣營 │ ├── ADPCM_IMA_QT ← QuickTime 音頻 │ └── ADPCM_QT ← 古老的 QuickTime 格式 │ ├── 游戲機(jī)陣營 │ ├── ADPCM_XA ← PlayStation CD-ROM │ ├── ADPCM_PSX ← PlayStation 音效 │ ├── ADPCM_THP ← GameCube/Wii 視頻 │ ├── ADPCM_DTK ← GameCube 流媒體 │ └── ADPCM_ADX ← CRI Middleware,?量日系游戲 │ ├── 電信陣營 │ ├── ADPCM_G722 ← ITU-T 寬帶語音,7kHz │ ├── ADPCM_G726 ← ITU-T 窄帶語音,16/24/32/40kbps │ └── ADPCM_G726LE ←G.726小端變體 │ └── 其他 ├── ADPCM_YAMAHA ← 雅馬哈音源芯? ├── ADPCM_AICA ← 世嘉 Dreamcast └── ADPCM_CT ← Creative Labs 聲卡
好消息:雖然變體眾多,但核?算法只有兩大流派——IMA ADPCM 和 MS ADPCM,掌握這兩個(gè),其余觸類旁通。
深入原理:從數(shù)學(xué)到代碼
2.1 IMA ADPCM:簡潔之美
IMA(Interactive Multimedia Association)ADPCM是最廣泛使用的變體。
2.1.1 解碼算法可以濃縮為以下公式:
差值 = step × (nibble[2] × 1 + nibble[1] × 0.5 + nibble[0] × 0.25 + 0.125) 如果 nibble[3] == 1,差值取負(fù) 新樣本 = 舊樣本 + 差值 新步?索引 = 舊步?索引 + index_table[nibble]
2.1.2 步長表89 級的精妙設(shè)計(jì)
IMA ADPCM使用?張固定的 89級步長表,覆蓋從7到32767的動態(tài)范圍:
staticconstint16_tima_step_table[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
};
這張表的設(shè)計(jì)遵循近似指數(shù)增長,低索引對應(yīng)?步長(精細(xì)量化),高索引對應(yīng)大步長(粗糙量化),這使得ADPCM能夠自動適應(yīng):
安靜段落 → 小步長 → 高精度
劇烈變化 → 大步長 → 不溢出
2.1.3 索引調(diào)整表4 位的智慧
staticconstint8_tima_index_table[16] = {
-1,-1,-1,-1,// nibble 0-3:差值小,降低步長
2,4,6,8,// nibble 4-7:差值大,提高步長
-1,-1,-1,-1,// nibble 8-11:負(fù)向小差值
2,4,6,8// nibble 12-15:負(fù)向大差值
};
這張表決定了"自適應(yīng)"特性:
差值?。╪ibble 0-3, 8-11)→ 索引減小 → 下次用更小步長
差值大(nibble 4-7, 12-15)→ 索引增大 → 下次用更大步長
2.1.4 完整解碼實(shí)現(xiàn)
/** * 解碼單個(gè) IMA ADPCM 樣本 * @param nibble 4位編碼值 (0-15) * @param predictor 預(yù)測器狀態(tài)(輸?輸出) * @param step_idx 步長索引(輸?輸出) * @return 16位 PCM 樣本 */ staticinlineint16_tdecode_ima_sample(uint8_tnibble,int32_t*predictor,int32_t*step_idx){ intstep = ima_step_table[*step_idx]; // 核?公式:diff = step/8 + step/4*b2 + step/2*b1 + step*b0 // 使用位運(yùn)算優(yōu)化,避免浮點(diǎn) intdiff = step >>3;// step/8,基礎(chǔ)值 if(nibble &4) diff += step;// bit2: +step if(nibble &2) diff += step >>1;// bit1: +step/2 if(nibble &1) diff += step >>2;// bit0: +step/4 if(nibble &8) diff = -diff;// bit3: 符號位 // 更新預(yù)測器 *predictor += diff; // 鉗位到 16 位有符號范圍 if(*predictor >32767) *predictor =32767; if(*predictor -32768) *predictor =?-32768; // 更新步長索引 *step_idx += ima_index_table[nibble]; if?(*step_idx 0) *step_idx =?0; if?(*step_idx >88) *step_idx =88; return(int16_t)*predictor; }
2.2 MS ADPCM:微軟的增強(qiáng)版
2.2.1 Microsoft ADPCM比IMA更復(fù)雜,但理論上能獲得更好的音質(zhì)
| 特性 | IMA ADPCM | MS ADPCM |
| 預(yù)測器 | 1個(gè)歷史值 | 2個(gè)歷史值 |
| 系數(shù) | 固定 | 7組自適應(yīng)系數(shù) |
| WAV格式碼 | 0x0011 | 0x0002 |
| 塊頭大? | 4字節(jié)/聲道 | 7字節(jié)/聲道 |
2.2.2 MS ADPCM的預(yù)測公式
predictor= (sample1 * coef1 + sample2 * coef2) /256 output= predictor + (nibble * delta)
2.2.3 7組自適應(yīng)系數(shù)
staticconst int16_t ms_adapt_coef1[7] = {256,512,0,192,240,460,392};
staticconst int16_t ms_adapt_coef2[7] = {0,-256,0,64,0,-208,-232};
編碼器會為每個(gè)塊選擇最佳系數(shù)組,存儲在塊頭中。
2.2.4 Delta自適應(yīng)表
staticconstint16_tms_adapt_table[16] = {
230,230,230,230,307,409,512,614,
768,614,512,409,307,230,230,230
};
// 更新公式
new_delta = (delta * ms_adapt_table[nibble]) /256;
if(new_delta 16) new_delta =?16;?// 最小值保護(hù)
FFmpeg實(shí)戰(zhàn):完整解碼流程
3.1 解碼流程圖


3.2 代碼示例如下:
依賴的頭文件

WAV文件頭


循環(huán)解碼

3.3 編譯與運(yùn)行
# 編譯 gcc -o adpcm_decode adpcm_decode.c $(pkg-config --cflags --libs libavformat libavcodec libavutil) # 運(yùn)行 ./adpcm_decode input_adpcm.wav output_pcm.wav # 輸出示例: # ====== 文件信息 ====== # 格式: WAV / WAVE (Waveform Audio) # 時(shí)長: 5.23 秒 # 流數(shù)量: 1 # # ====== 音頻參數(shù) ====== # 編碼格式: adpcm_ima_wav (ID: 69638) # 采樣率: 44100 Hz # 聲道數(shù): 2 # 塊對齊: 2048 字節(jié) # ?特率: 352800 bps # 解碼器: ADPCM IMA WAV # # ====== 開始解碼 ====== # 已解碼 100 幀, 204800 樣本... # # ====== 解碼完成 ====== # 總幀數(shù): 113 # 總樣本: 230912 # 輸出??: 901.22 KB # 輸出?件: output_pcm.wav
深度避坑:那些"詭異問題"的根源
4.1 坑一:MS ADPCM和IMA ADPCM混淆
問題:解碼后聽到刺耳噪音或沉默。
原因:兩種格式雖然都叫 ADPCM,但塊結(jié)構(gòu)完全不同。
診斷方法:
# 使? ffprobe ffprobe-v quiet -select_streams a:0-show_entries stream=codec_name input.wav # 或用 hexdump 直接看 hexdump-C input.wav | head -2 # 查看偏移 20-21 字節(jié): # 0x0002 = MS ADPCM # 0x0011 = IMA ADPCM
解決方案:讓 FFmpeg 自動識別,不要手動指定 codec_id。
4.2 坑二:塊對齊(Block Align)錯(cuò)誤
問題:解碼正常但周期性出現(xiàn)噪音。
原因:ADPCM數(shù)據(jù)必須按塊讀取,每個(gè)塊以狀態(tài)信息開頭,如果在塊中間切斷,狀態(tài)會丟失。
診斷方法:
# 查看塊對齊值 ffprobe-v quiet -select_streams a:0-show_entries stream=block_align input.wav
正確做法:
// 確保每次讀取完整的塊
if(pkt->size % par->block_align !=0) {
fprintf(stderr,"警告: 數(shù)據(jù)包?? (%d) 不是塊對? (%d) 的整數(shù)倍
",
pkt->size, par->block_align);
}
4.3 坑三:立體聲聲道錯(cuò)亂
問題:左右聲道交換,或聲音"撕裂"。
原因:IMA 和 MS ADPCM 的?體聲交織方式不同:
IMAADPCM ?體聲交織: 塊內(nèi): [L頭][R頭] [L樣本組(8個(gè))] [R樣本組(8個(gè))] [L組] [R組] ... MS ADPCM ?體聲交織: 塊內(nèi): [L頭][R頭] [LR] [LR] [LR] ... (每個(gè)nibble交替)
解決方案:使用 FFmpeg 的自動處理,它會正確解交織。
4.4 坑四:文件被截?cái)?/p>
問題:解碼到末尾時(shí)崩潰或輸出噪音。
原因:某些錄音軟件在異常終止時(shí)未正確寫?文件尾。
診斷方法:
# 檢查?件完整性 ffprobe-verrorinput.wav # 如果有錯(cuò)誤會輸出
解決方案:
// 在解碼器中啟用錯(cuò)誤容忍 dec_ctx->err_recognition =AV_EF_CAREFUL;// 或 AV_EF_IGNORE_ERR // 或用FFmpeg命令?修復(fù) // ffmpeg -i broken.wav -c copy fixed.wav
4.5 坑五:采樣率/聲道數(shù)信息缺失
問題:FFmpeg 報(bào)告nvalid data found when processing input。
原因:某些非標(biāo)準(zhǔn)工具生成的 WAV文件fmt塊不完整。
解決方案:手動指定參數(shù):
// 強(qiáng)制指定采樣率和聲道數(shù) av_dict_set(&options,"sample_rate","44100",0); av_dict_set(&options,"channels","2",0); avformat_open_input(&fmt_ctx, input_path,NULL, &options);
性能優(yōu)化:讓古老格式飛起來
5.1 性能基準(zhǔn)
在典型的開發(fā)環(huán)境中(Intel i5/Apple M1 級別):
ADPCM解碼是計(jì)算輕量型任務(wù),瓶頸通常在I/O而非CPU。
| 配置 | 解碼速度 | 實(shí)時(shí)倍率 | CPU占用 |
| FFmpeg單線程 | ~180,000樣本/ms | ~4000x | ~3% |
| 純C手寫實(shí)現(xiàn) | ~220,000樣本/ms | ~5000x | ~2% |
5.2 優(yōu)化策略
策略一:增大讀取緩沖區(qū)。
// 默認(rèn)緩沖區(qū)可能較?,增大可減少系統(tǒng)調(diào)用 AVDictionary*options =NULL; av_dict_set(&options,"buffer_size","1048576",0);// 1MB avformat_open_input(&fmt_ctx, path,NULL, &options);
策略二:跳過不需要的幀。
// 如果只需要某個(gè)時(shí)間段 av_seek_frame(fmt_ctx, audio_stream_idx, target_pts,AVSEEK_FLAG_BACKWARD);
策略三:使用SIMD加速。
對于需要極致性能的場景,可以使用SSE/NEON指令集批量處理多個(gè)樣本:
// 偽代碼示意 #include// ?次處理 8 個(gè)樣本 __m256i step_vec = _mm256_set1_epi32(step); __m256i diff_vec = _mm256_srai_epi32(step_vec,3);// step >> 3 // ... 后續(xù) SIMD 運(yùn)算
調(diào)試技巧:當(dāng)聲音"沉默"時(shí)
6.1 啟用FFmpeg詳細(xì)日志
av_log_set_level(AV_LOG_VERBOSE); // 或只看特定級別 av_log_set_level(AV_LOG_WARNING);
6.2 命令行快速診斷
#查看完整流信息 ffprobe -v quiet -print_format json -show_format -show_streams input.wav #解碼第?秒并檢查 ffmpeg -i input.wav -t 1 -f s16le -acodec pcm_s16le - | hexdump -C |head #?成波形圖 ffmpeg -i input.wav -filter_complex"showwavespic=s=800x200"waveform.png #對?兩個(gè)?件的頻譜 ffmpeg -i original.wav -i decoded.wav -filter_complex "[0:a]showspectrumpic=s=800x400[s0];[1:a]showspectrumpic=s=800x400[s1];[s0] [s1]vstack" spectrum_compare.png
6.3 自檢清單
當(dāng)解碼出現(xiàn)問題時(shí),按順序檢查:
□ ?件是否完整?(?件大小是否合理) □ 格式是否正確識別?(ffprobe codec_name) □ 采樣率/聲道數(shù)是否正確? □ 塊對齊是否正確? □ 是否有 DRM 保護(hù)? □ 是否使?了非標(biāo)準(zhǔn)擴(kuò)展? □ 解碼器是否正確初始化? □ 輸出格式是否正確處理?(平面 vs 交織)
結(jié)語:技術(shù),是時(shí)間的譯者
每一段ADPCM音頻背后,可能是90年代游戲廳的喧囂、老式答錄機(jī)的留言、工廠車間的運(yùn)轉(zhuǎn)聲,用4位的密度,記錄著16位的時(shí)光。通過FFmpeg解碼,不只是波形數(shù)據(jù),更是一段段被壓縮卻未曾遺忘的記憶。ADPCM的偉大,在于它用極簡的算法,在那個(gè)存儲金貴的年代,讓聲音得以保存和傳遞。而今天仍在使用和研究它,這是對這份工程智慧的致敬。當(dāng)調(diào)用avcodec_receive_frame的那一刻,當(dāng)predictor加上diff的那一瞬,一段1980年代的聲音,正穿越40年的時(shí)光,完整地回到耳畔。
供稿:閆超美
責(zé)編:開發(fā)者與活動運(yùn)營組 李健
編審:品牌管理組 麗娜
審核:開源鴻蒙項(xiàng)目群工作委員會執(zhí)行總監(jiān) 陶銘
開源鴻蒙項(xiàng)目群工作委員會執(zhí)行秘書 曹云菲
-
數(shù)字音頻
+關(guān)注
關(guān)注
9文章
220瀏覽量
68126 -
解碼
+關(guān)注
關(guān)注
0文章
188瀏覽量
28545 -
ffmpeg
+關(guān)注
關(guān)注
0文章
51瀏覽量
7891
原文標(biāo)題:拆·應(yīng)用 | 第十期:基于FFmpeg解碼ADPCM音頻,用4位還原16位的魔法
文章出處:【微信號:gh_e4f28cfa3159,微信公眾號:OpenAtom OpenHarmony】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
Linux下基于ffmpeg音視頻解碼
Tiny4412移植ffmpeg實(shí)現(xiàn)視頻解碼
振南對ADPCM音頻編解碼原理的一些通俗闡述【附振南的ADPCM解碼原代碼】
振南真正實(shí)現(xiàn)ADPCM音頻解碼與播放【為了邊解碼邊播放的流暢度,振南提出了“追隨策略”!】
【NXP LPC54110試用體驗(yàn)】ADPCM音頻壓縮
ADPCM語音編解碼VLSI芯片的設(shè)計(jì)方法
FFmpeg硬解碼
如何使用L9320實(shí)現(xiàn)ADPCM語音編解碼
在QT上構(gòu)建ffmpeg環(huán)境實(shí)現(xiàn)音頻的解碼
FFmpeg 6.0 發(fā)布
瑞芯微RK3588平臺FFmpeg硬件編解碼移植及性能測試實(shí)戰(zhàn)攻略
瑞芯微RK3562平臺FFmpeg硬件編解碼移植及性能測試實(shí)戰(zhàn)攻略
如何基于FFmpeg解碼ADPCM音頻
評論