测试一下 ADPCM 解码播放 ...... 矜辰所致
前言
之前我们我们已经掌握了一种低成本的 CH585 音频播放方案,我们也测试过了 SBC 解码播放 以及 原始的 PCM 播放,作为嵌入式中另外一种常用的语音编解码的 ADPCM,博主早就有计划也要试一下。
所以本文我们主要就来测试一下 ADPCM 解码以及播放 。
相关博文:
.
我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!
目录
- 前言
- [一、 基础说明](#一、 基础说明)
-
- [1.1 IMA ADPCM](#1.1 IMA ADPCM)
- [1.2 测试环境](#1.2 测试环境)
- [1.3 ADPCM 解码源码](#1.3 ADPCM 解码源码)
- [二、 应用层处理](#二、 应用层处理)
-
- [2.1 与 SBC 解码的区别](#2.1 与 SBC 解码的区别)
- [2.2 ADPCM 文件处理方式](#2.2 ADPCM 文件处理方式)
- [2.2 APP 代码修改](#2.2 APP 代码修改)
- 三、音频文件生成
-
- [3.1 音频文件转成 wav](#3.1 音频文件转成 wav)
- [3.2 wav 转成 adpcm](#3.2 wav 转成 adpcm)
- [3.3 adpcm 转成 Hex](#3.3 adpcm 转成 Hex)
- [3.4 指令流程小结](#3.4 指令流程小结)
- 结语
一、 基础说明
1.1 IMA ADPCM
ADPCM (自适应差分脉冲编码调制)是一种音频有损压缩算法。
ADPCM 也分很多种: IMA ADPCM(adpcm_ima_wav) 、G.726(电信 ADPCM)、MS ADPCM(微软 ADPCM)等。
而本文的我们测试的对象是 IMA ADPCM ,也是嵌入式行业存 Flash 提示音、报警语音 的统一标准 :
- 4bit / 采样,1 字节 = 2 个采样;低 4bit 在前、高 4bit 在后;步长表 89 级,输入输出 16bit 线性 PCM;
- 无专利、开源代码遍地;FFmpeg 原生编码;
- 封装:带 44 字节 WAV 头,去掉头部就是纯裸流,可直接烧录单片机;
本文测试的为存裸流的 ADPCM ,不带有任何包头,块头。
1.2 测试环境
使用的测试代码还是之前 SBC 解码播放的那套架构,播放的形式与之前一样, 硬件连接双 PWM 接 H 桥播放。
我们需要把之前的 SBC 解码的库换成 ADPCM 的解码库。
1.3 ADPCM 解码源码
ADPCM 的解码是开源的,这里咱也不墨迹,直接放一下本次测试使用的源码。
adpcm.c
c
#include "adpcm.h"
/* IMA ADPCM Step Size Table */
static const int16_t ima_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
};
/* IMA ADPCM Index Table
* 索引调整表
*/
static const int8_t ima_index_table[16] = {
-1, -1, -1, -1, 2, 4, 6, 8,
-1, -1, -1, -1, 2, 4, 6, 8
};
// 初始化
void ADPCM_DecodeInit(ima_adpcm_state_t *state)
{
state->predictor = 0;
state->stepIndex = 0;
}
/* Decode one 4-bit nibble → 16-bit PCM sample (supplier algorithm)
* 解码一个 nibble
*/
static int16_t ima_adpcm_decode_nibble(uint8_t nibble, ima_adpcm_state_t *st)
{
int step = ima_step_table[st->stepIndex];
int diff = step >> 3;
if(nibble & 1) diff += step >> 2; /* bit0: step/4 */
if(nibble & 2) diff += step >> 1; /* bit1: step/2 */
if(nibble & 4) diff += step; /* bit2: step */
if(nibble & 8) diff = -diff; /* bit3: sign */
st->predictor += diff;
if(st->predictor > 32767) st->predictor = 32767;
if(st->predictor < -32768) st->predictor = -32768;
st->stepIndex += ima_index_table[nibble & 0x0F];
if(st->stepIndex < 0) st->stepIndex = 0;
if(st->stepIndex > 88) st->stepIndex = 88;
return (int16_t)st->predictor;
}
/* Decode 120-byte ADPCM block → 240 PCM samples
* Low nibble first per byte (matches supplier)
* 解码一段 ADPCM 数据
*/
void ima_adpcm_decode(const uint8_t *adpcmData, uint32_t adpcmLen,
int16_t *pcmData, ima_adpcm_state_t *state)
{
uint16_t n = 0;
for(uint16_t i = 0; i < adpcmLen; i++)
{
uint8_t b = adpcmData[i];
pcmData[n++] = ima_adpcm_decode_nibble(b & 0x0F, state); /* low nibble first */
pcmData[n++] = ima_adpcm_decode_nibble((b >> 4) & 0x0F, state); /* high nibble second */
}
}
adpcm.h
c
#ifndef ADPCM_DECODER_H
#define ADPCM_DECODER_H
#include <stdint.h>
typedef struct {
int32_t predictor;
int32_t stepIndex;
} ima_adpcm_state_t;
void ADPCM_DecodeInit(ima_adpcm_state_t *state);
void ima_adpcm_decode(const uint8_t *adpcmData, uint32_t adpcmLen,
int16_t *pcmData, ima_adpcm_state_t *state);
#ifdef __cplusplus
}
#endif
#endif
这个应该也是标准的 ADPCM 的解码源码(我反正是查了好些不同的代码示例,基本都一样)可以直接复制过去用。
二、 应用层处理
接下来就是修改一下我们的应用层面了。
2.1 与 SBC 解码的区别
SBC 压缩方式有帧结构可以计算出长度,而 ADPCM 没有,所以我们单从应用层去读取 ADPCM 的原始数据,我们是无法判断 ADPCM 是否结束了的。
我们测试示例的处理方式是在 Flash 里面分配了多段区域,每段区域用来存放不同的音频,Flash 分配的大小要根据自己的音频大小来划分的,分配的 Flash 大小肯定不能小于需要存放的音频大小的:
c
typedef struct {
uint32_t addr;
uint32_t length;
} audio_record_t;
audio_record_t music_record[] = {
{ 0x000000, 51200 },
{ 0x00c800, 102400 },//0x14800
{ 0x025800, 153600 }, //offset=0x2D800
};
一般来说,我们在分配 Flash 的时候会多预留一点余量。但是如果是 ADPCM 压缩的音频文件,因为我们程序只知道 Flash 分配的大小,却无法知道音频文件的大小,当分配的 Flash 空间比音频文件大的时候, 后面不需要的 Flash 的数据都会被当成音频数据播放,就会产生不需要的杂音。
所以我们需要保证 ADPCM 音频文件的大小与 分配的 Flash 大小正好一致,才能避免出现杂音。但是这样无法保证对齐,而且每次音频改变,都得改一下代码,在实际应用上,这样的处理方式明显繁琐且不常规,我们需要更加通用一点的方式。
2.2 ADPCM 文件处理方式
再次声明一下,本文测试针对纯裸流的 ADPCM ,不带有任何包头,块头。
为了解决上面的问题,我们可以进行一些处理,博主了解到的方式有两种:
- 在生成 ADPCM 音频文件的时候,给 ADPCM 文件补全到自己分配的音频 FLASH 大小 (更加推荐的方式是不改变原始 ADPCM ,转成 Hex 的时候补齐到分配大小)。
- 在生成 ADPCM 音频文件的时候,给 ADPCM 音频文件头部追加长度。
两种方式各有优缺点,这个看应用需求,博主本文测试使用第一种方式,具体的生成补全方式下文会有说明,我们先来看一下应用层的处理代码(上面两种不同的处理方式,应用层代码也略有不同,一个是要先获取追加的长度数据,再根据长度处理音频数据,一个只处理 ADPCM 音频数据)。
2.2 APP 代码修改
博主根据 ADPCM 解码播放修改了一下应用层,最关键的修改就是在 audio_handler 函数种的数据处理部分,其他的一些关键点请查看之前文章 《CH585 SBC 音频解码播放测试说明》 的内容。
这里就直接放一下修改后的测试代码(这面的解析是基于上面 ADPCM 音频文件第一种处理方式,把整段分配的 Flash 区域都当成音频数据来处理的,这就要求我们生成音频文件的时候要特殊处理):
c
#include "CH58x_common.h"
#include <stdbool.h>
#include "ch5xx_audio_dma.h"
#include "adpcm.h"
#define ADPCM_FLASH_ADDR 0x32000
typedef struct {
uint32_t addr;
uint32_t length;
} audio_record_t;
// 分配空间大小,ADPCM 数据必须填满!
audio_record_t music_record[] = {
{ 0x000000, 0x0A000 }, //40751
// { 0x3C000, 37199 },
// { 0x003D00, 8192 },
};
#define MUSIC_COUNT (sizeof(music_record) / sizeof(music_record[0]))
volatile uint32_t read_index;
volatile uint8_t play_on;
volatile uint8_t read_data_sta;
ima_adpcm_state_t adpcm_state;
uint8_t adpcm_buf[64];
int16_t pcm_buf[128];
typedef struct {
uint32_t start_addr;
uint32_t length;
} sample_t;
sample_t sample;
void DebugInit(void)
{
GPIOA_SetBits(GPIO_Pin_9);
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
UART1_DefInit();
UART1_BaudRateCfg(460800);
}
__HIGH_CODE
static void audio_handler(ch5xx_audio_event_t * p_event)
{
switch(p_event->type) {
case CH5XX_PWM_DMA_GET: {
uint32_t *p_data = p_event->p_data;
uint32_t *p_data2 = p_event->p_data2;
uint32_t length = p_event->len;
switch(read_data_sta) {
case 0: {
uint32_t filled = 0;
while(filled < length) {
if(read_index >= sample.length) {
read_data_sta = 1;
break;
}
uint32_t remain = sample.length - read_index;
uint32_t need_samples = length - filled;
uint32_t need_bytes = (need_samples + 1) / 2;
uint32_t copy_bytes = (remain < need_bytes) ? remain : need_bytes;
uint32_t decode_samples = copy_bytes * 2;
if(decode_samples > need_samples) {
decode_samples = need_samples;
}
__wrap_memcpy(adpcm_buf, (uint8_t *)(sample.start_addr + read_index), copy_bytes);
ima_adpcm_decode(adpcm_buf, copy_bytes, &pcm_buf[filled], &adpcm_state);
for(uint32_t i = 0; i < decode_samples; i++) {
int16_t pcm_sample = pcm_buf[filled + i];
if(pcm_sample > 0) {
*p_data = pcm_sample / 8;
*p_data2 = 0;
} else {
*p_data2 = (0 - pcm_sample) / 8;
*p_data = 0;
}
p_data++;
p_data2++;
}
filled += decode_samples;
read_index += copy_bytes;
}
for(; filled < length; filled++) {
*p_data++ = 0;
*p_data2++ = 0;
}
if(read_data_sta == 1) {
PRINT("ADPCM end\r\n");
ch5xx_audio_stop();
}
break;
}
case 1:
ch5xx_audio_stop();
break;
default:
break;
}
break;
}
case CH5XX_PWM_ALL_END:
PRINT("ALL END\r\n");
play_on = 0;
read_data_sta = 0;
break;
default:
break;
}
}
__HIGH_CODE
void main_loop(void)
{
uint8_t index = 0;
while(1) {
read_index = 0;
play_on = 1;
read_data_sta = 0;
ADPCM_DecodeInit(&adpcm_state);
sample.length = music_record[index].length;
sample.start_addr = ADPCM_FLASH_ADDR + music_record[index].addr;
PRINT("Play:%d addr:%08x len:%u\r\n", index, sample.start_addr, sample.length);
ch5xx_audio_start();
while(play_on);
ch5xx_audio_stop();
index++;
if(index >= MUSIC_COUNT) {
index = 0;
}
DelayMs(1000);
}
}
int main(void)
{
SetSysClock(CLK_SOURCE_HSE_PLL_78MHz);
DebugInit();
PRINT("\r\nADPCM Player\r\n");
sample.start_addr = ADPCM_FLASH_ADDR;
sample.length = 0xFFFFFFFF;
ch5xx_audio_init(CH5XX_AUDIO_SAMPLE_RATE_16K, audio_handler);
main_loop();
}
三、音频文件生成
我们知道 ffmpeg 可以把音频文件生成 SBC 和 PCM 文件:
bash
SBC 文件生成
ffmpeg -i input.mp3 -ar 16000 -ac 1 -f sbc voice.sbc
PCM 文件生成
ffmpeg -i 004.mp3 -ar 16000 -ac 1 -f s16le 004p.pcm
在最开始测试的时候博主以为也是和之前测试一样, ffmpeg 一条命令就生成,然后直接使用了 :
bash
ffmpeg -i 004.mp3 -ar 16000 -ac 1 -c:a adpcm_ima_wav -f data -map 0:a 004adp.adpcm
也确实生成了 adpcm, 但是上面这条指令生成的 adpcm 是分块编码的,每块开头 4 字节状态头 。
每块开头有 4 字节头,记录该块开始时的 predictor 和 stepIndex。这样的好处是可以从任意块开始解码,支持随机跳转。
我们本文修改的代码是针对 纯裸流 IMA ADPCM ,绝大多数消费类 MCU 设备也是用的纯裸流的结构。所以我们还得想个办法,生成纯裸流的 ADPCM 。
上面这种方式生成的 ADPCM 用本文的代码直接也能播放出来,但是因为没有处理块头,都当成音频数据解析,所以能明显听出来杂音。
我自己把流程记录下来(这里只是博主测试成功的方式,并不一定是最优解,仅供参考)。
3.1 音频文件转成 wav
先通过 ffmpeg 工具把音乐文件转成 wav 文件:
bash
ffmpeg -i 004.mp3 -ar 16000 -ac 1 004_16k.wav
3.2 wav 转成 adpcm
然后再通过 Python (Python 安装之前的博文有,网上也很多,自行查找)脚本压缩成我们需要的 adpcm 音频文件(和上面解码源码相对应的压缩流程),新建一个 .py 文件 ,内容如下:
python
# adpcm_encode.py - 纯标准库
import wave
import struct
import sys
class IMAADPCMEncoder:
def __init__(self):
self.predictor = 0
self.step_index = 0
self.step_table = [
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
]
self.index_table = [
-1, -1, -1, -1, 2, 4, 6, 8,
-1, -1, -1, -1, 2, 4, 6, 8
]
def encode_nibble(self, sample):
diff = sample - self.predictor
step = self.step_table[self.step_index]
nibble = 0
if diff < 0:
nibble = 8
diff = -diff
if diff >= step:
nibble |= 4
diff -= step
if diff >= step >> 1:
nibble |= 2
diff -= step >> 1
if diff >= step >> 2:
nibble |= 1
# 更新 predictor(和解码端一致)
diff = step >> 3
if nibble & 4: diff += step
if nibble & 2: diff += step >> 1
if nibble & 1: diff += step >> 2
if nibble & 8: diff = -diff
self.predictor += diff
if self.predictor > 32767:
self.predictor = 32767
elif self.predictor < -32768:
self.predictor = -32768
self.step_index += self.index_table[nibble & 0x0F]
if self.step_index < 0:
self.step_index = 0
elif self.step_index > 88:
self.step_index = 88
return nibble
def encode_file(input_wav, output_adpcm):
with wave.open(input_wav, 'rb') as wf:
nchannels = wf.getnchannels()
sampwidth = wf.getsampwidth()
framerate = wf.getframerate()
nframes = wf.getnframes()
# 读取所有 PCM 数据
raw_data = wf.readframes(nframes)
# 解析 16bit 采样
# struct.unpack 解析所有采样
fmt = '<' + 'h' * (len(raw_data) // 2)
pcm_samples = list(struct.unpack(fmt, raw_data))
# 立体声转单声道
if nchannels == 2:
mono_samples = []
for i in range(0, len(pcm_samples), 2):
mono_samples.append((pcm_samples[i] + pcm_samples[i+1]) // 2)
pcm_samples = mono_samples
# 重采样到 16KHz(简单线性)
if framerate != 16000:
old_len = len(pcm_samples)
new_len = int(old_len * 16000 / framerate)
resampled = []
for i in range(new_len):
idx = i * (old_len - 1) / (new_len - 1) if new_len > 1 else 0
idx_low = int(idx)
idx_high = min(idx_low + 1, old_len - 1)
frac = idx - idx_low
val = int(pcm_samples[idx_low] * (1 - frac) + pcm_samples[idx_high] * frac)
resampled.append(val)
pcm_samples = resampled
# IMA ADPCM 编码
enc = IMAADPCMEncoder()
adpcm_bytes = bytearray()
for i in range(0, len(pcm_samples), 2):
s0 = pcm_samples[i]
s1 = pcm_samples[i + 1] if i + 1 < len(pcm_samples) else 0
n0 = enc.encode_nibble(s0)
n1 = enc.encode_nibble(s1)
adpcm_bytes.append((n1 << 4) | n0)
with open(output_adpcm, 'wb') as f:
f.write(adpcm_bytes)
print(f"PCM samples: {len(pcm_samples)}")
print(f"PCM bytes: {len(pcm_samples) * 2}")
print(f"ADPCM bytes: {len(adpcm_bytes)}")
print(f"Ratio: {len(pcm_samples) * 2 / len(adpcm_bytes):.2f}:1")
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python adpcm_encode.py input.wav output.adpcm")
sys.exit(1)
encode_file(sys.argv[1], sys.argv[2])
然后使用此 python 脚本把我们生成的 wav 文件生成 adpcm 文件:
bash
python adpcm_encode.py 004w_16k.wav 004adp.adpcm
效果如下:
C:\Users\OWNER\Desktop\Demo抽烟报警>python adpcm_encode.py 004w_16k.wav 004adp.adpcm
PCM samples: 81502
PCM bytes: 163004
ADPCM bytes: 40751
Ratio: 4.00:1
转化完成,脚本文件还给我们输出了文件大小ADPCM bytes: 40751
这里得到这个文件,实际上我们都可以先测试一下了,我们直接在程序中定义:
c
// 分配空间大小,ADPCM 数据必须填满!
audio_record_t music_record[] = {
{ 0x000000, 40751 },
};
然后根据之前文章的学习把刚才生成的 004adp.adpcm 转成 hex :
bash
srec_cat.exe 004adp.adpcm -binary -offset 0x32000 -o 004adp.hex -intel
或者
bin2hex.exe --offset=0x32000 004adp.adpcm 004adpcm.hex
先烧录测试一下看看能否播放:

OK! 测试过,解码播放没问题。
3.3 adpcm 转成 Hex
上面生成了 ADPCM 我们只能播放一段,我们要多放几段,那占用空间就不能像一个那样随意了,至少要保证对齐。
所以我们会多分配一点 flash 空间,为了避免杂音,我们需要补齐分配的大小,这里我们补齐就直接在生成 Hex 的时候进行补齐 0 ,这样可以不破坏音频源文件。
根据我们的 app 中分配的 Flash 区域进行 Hex 生成(第三段故意放长测试填充效果,不填充直接放会有刺耳的声音):
c
define ADPCM_FLASH_ADDR 0x32000
audio_record_t music_record[] = {
{ 0x000000, 51200 }, //ADPCM_FLASH_ADDR 0x32000
{ 0x00C800, 51200 }, //0x3E800
{ 0x019000, 102400 }, //0x4B000
};
这里直接上一下指令:
bash
srec_cat 004adp.adpcm -binary -offset 0x32000 -fill 0x00 0x32000 0x3E800 -o 004adp_h.hex -intel
srec_cat 005adp.adpcm -binary -offset 0x3E800 -fill 0x00 0x3E800 0x4B000 -o 005adp_h.hex -intel
srec_cat 006adp.adpcm -binary -offset 0x4B000 -fill 0x00 0x4B000 0x64000 -o 006adp_h.hex -intel
使用 srec_info xxx.hex -intel 可以查看 hex 文件位置:
bash
C:\Users\OWNER\Desktop\Demo抽烟报警>srec_info 004adp_h.hex -intel
Format: Intel Hexadecimal (MCS-86)
Data: 032000 - 03E7FF
C:\Users\OWNER\Desktop\Demo抽烟报警>srec_info 005adp_h.hex -intel
Format: Intel Hexadecimal (MCS-86)
Data: 03E800 - 04AFFF
C:\Users\OWNER\Desktop\Demo抽烟报警>srec_info 006adp_h.hex -intel
Format: Intel Hexadecimal (MCS-86)
Data: 04B000 - 063FFF
本来以为这样就好了,但是我烧录的时候一直提示 HEX 文件重叠,这个地方我查了一会儿,也使用了上面的指令查大小,按理来说确实不会重叠。
后来我直接把上面转换填充结尾地址减少了一个地址,使用了下面的指令就成功了:
bash
srec_cat 004adp.adpcm -binary -offset 0x32000 -fill 0x00 0x32000 0x3E799 -o 004adp_h.hex -intel
srec_cat 005adp.adpcm -binary -offset 0x3E800 -fill 0x00 0x3E800 0x04AFFF -o 005adp_h.hex -intel
最后就是下载测试播放就好了:

疑问补充说明
上面说到,开始以正常地址设置,我在 ISP 工具里面怎么都显示重合 :
bash
0x32000 0x3E800 -o 004adp_h.hex -intel
0x3E800 0x4B000 -o 005adp_h.hex -intel
0x4B000 0x64000 -o 006adp_h.hex -intel

但是上文也说了,使用工具读出来确实是不重合的,上文使用了减小一个地址的方式处理,我尝试了把 这个 3 个 Hex 合并,也是可以解决上面问题
bash
srec_cat 004adp_h.hex -intel 005adp_h.hex -intel 006adp_h.hex -intel -o all.hex -intel -address-length=4
-address-length=4 参数:
CH585 Flash 地址超过 64K(0x10000),普通 2 字节 Intel HEX 只能表达 0~0xFFFF,大于 0x10000 的高位地址会解析错乱;
该参数输出32 位 4 字节扩展地址 Hex,烧录器能正确识别 0x32000、0x64000 这类高位地址。
烧录的时候选择合并后的 Hex 文件即可:

3.4 指令流程小结
bash
步骤 1:
ffmpeg -i 004.mp3 -ar 16000 -ac 1 004_16k.wav
步骤 2:
python adpcm_encode.py 004w_16k.wav 004adp.adpcm
...
...
步骤 3:
srec_cat 004adp.adpcm -binary -offset 0x32000 -fill 0x00 0x32000 0x3E800 -o 004adp_h.hex -intel
srec_cat 005adp.adpcm -binary -offset 0x3E800 -fill 0x00 0x3E800 0x4B000 -o 005adp_h.hex -intel
srec_cat 006adp.adpcm -binary -offset 0x4B000 -fill 0x00 0x4B000 0x64000 -o 006adp_h.hex -intel
步骤 4:合并(可选的,但是个人建议)
srec_cat 004adp_h.hex -intel 005adp_h.hex -intel 006adp_h.hex -intel -o all.hex -intel -address-length=4
结语
本文在之前播放示例的基础上,添加了 ADPCM 解码, 也成功的测试了解码播放效果。
那之前测试过 SBC 解码播放,原始 PCM 播放,加上 ADPCM 解码播放,基本上能够满足嵌入式一般应用的需求了,当然还有些嵌入式设备使用的 是 Speex 编码,这个后期有实际应用再来说明把(主要是博主的应用不需要 = =!可没时间搞了(●'◡'●) )。
好了,本文就到这里。谢谢大家!