音频解码器框架
音频解码器框架(Audio Decoder Framework)是 AD23N 芯片 SDK 中负责统一管理多种音频解码器(F1A、UMP3、A、MIDI、WAV、MP3 标准解码等)的注册、探测、启动、控制与释放的公共子系统。它通过 decoder_io() 完成"文件 → 解码器识别 → 效果器链路 → DAC 输出"的完整播放流水线装配,并通过软件中断(IRQ_SOFT0)驱动解码过程的持续进行。
Purpose and Scope
本文档覆盖音频解码器框架的完整机制,包括:
- 解码器注册表(
decoder_msg_tab.h)与句柄/函数表(dec_hld_tab[]、decoder_tab[]、decoder_mutual[])的构建方式; - 系统初始化
decoder_init()与解码启动入口decoder_io()的完整控制流; - 播放控制接口(暂停、停止、快进、快退)、中断驱动机制与解码结束消息分发;
- 断点(断点续播)机制
dp_buff、互斥(mutex)检测、效果器(变速变调/采样率转换)链接; - 各编解码器的使能宏配置与扩展新解码器的方式。
各具体解码器的内部算法(如 UMP3 解码细节、F1A 编解码格式、MIDI 合成器等)不属于本文档范围,它们由各自的模块(ump3_api、f1a_api、midi_api、wav_api、a_api、mp3_standard_api)负责。DAC 输出与音频效果器(EQ/SRC/变速)的底层实现请参见对应的音频输出页面。
Overview
在 AD23N 的嵌入式音频方案中,同一套应用代码需要支持多种音频格式(杰理私有 F1A、压缩 MP3(UMP3)、A 格式、MIDI/MIDI 键盘、WAV、标准 MP3 等)。为了不让上层播放逻辑与具体解码格式耦合,SDK 抽象出解码器框架:
- 每种解码器以一个"探测 + 初始化"函数形式注册进
decoder_tab[]表; - 启动播放时,
decoder_io()按dec_ctl位掩码指定的顺序依次调用各解码器的探测函数,第一个成功识别文件格式并创建解码对象的解码器胜出; - 解码对象统一为
dec_obj,其内嵌sound_out_obj sound成员,通过sound.enable标志位(B_DEC_ENABLE、B_DEC_KICK、B_DEC_RUN_EN、B_DEC_PAUSE、B_DEC_ERR、B_DEC_OBUF_EN等)与 DAC 通道、中断服务协同工作; - 解码过程由软中断
IRQ_SOFT0周期性"踢"动(kick_decoder()→bit_set_swi(0)),解码器在中断上下文中向 DAC 环形缓冲填充 PCM 数据。
这一设计使得:上层只需"打开文件 → 调用 decoder_io() → 按需 decoder_pause/stop/ff/fr",即可完成任意支持格式的播放,新增格式只影响注册表与配置宏,不改变框架结构。
Architecture
flowchart TD
subgraph sg_App["应用层"]
App["播放应用 / 模块"]
end
subgraph sg_Decoder["解码器框架 decoder_api.c"]
DecoderIO["decoder_io() 启动装配"]
DecoderInit["decoder_init() 初始化"]
DecoderCtrl["decoder_pause / stop / ff / fr"]
IrqRet["irq_decoder_ret() 消息分发"]
Kick["kick_decoder() → IRQ_SOFT0"]
end
subgraph sg_Tab["注册表 decoder_msg_tab.h"]
HldTab["dec_hld_tab[] 解码句柄"]
ApiTab["decoder_tab[] 探测函数表"]
MutTab["decoder_mutual[] 缓冲区互斥表"]
end
subgraph sg_Codec["编解码器实例"]
F1A["F1A 解码器 (f1a_decode_api)"]
UMP3["UMP3 解码器 (ump3_decode_api)"]
A["A 解码器 (a_decode_api)"]
MIDI["MIDI 解码器 (midi_decode_api)"]
WAV["WAV 解码器 (wav_decode_api)"]
MP3ST["MP3 标准解码器"]
end
subgraph sg_Out["音频输出链路"]
Speed["speed_api 变速变调"]
SRC["src_api 采样率转换"]
DAC["DAC 输出通道"]
end
App -->|"decoder_io(pfile, dec_ctl, ...)"| DecoderIO
App -->|"暂停/停止/快进快退"| DecoderCtrl
DecoderInit -->|"memset + 安装软中断"| HldTab
DecoderIO -->|"按 BIT(dec_i) 探测"| ApiTab
ApiTab --> F1A
ApiTab --> UMP3
ApiTab --> A
ApiTab --> MIDI
ApiTab --> WAV
ApiTab --> MP3ST
DecoderIO -->|"B_DEC_ENABLE | B_DEC_KICK | B_DEC_FIRST"| Kick
Kick -->|"软中断 IRQ_SOFT0"| IrqRet
DecoderIO -->|"变速变调链接"| Speed
DecoderIO -->|"SRC 采样率转换链接"| SRC
DecoderIO -->|"注册输出通道"| DAC
DecoderCtrl -->|"操作 sound.enable 标志"| DAC
DecoderCtrl -->|"互斥检测 decoder_mutex()"| MutTab
架构说明:应用层只与框架的 decoder_io() 及控制接口交互;框架通过注册表(由 decoder_msg_tab.h 的宏在编译期拼接而成)间接调用各编解码器的探测函数;解码成功后框架将 PCM 数据经效果器链路(变速变调、采样率转换,编译期可选)送入 DAC;解码推进由软中断驱动,解码结果(错误码/结束事件)通过 irq_decoder_ret() 回投给上层事件表(obj->event_tab[])。
解码器注册表:编译期拼装的可插拔机制
框架的核心设计是"表格驱动 + 编译期裁剪"。decoder_msg_tab.h 用一组条件编译宏定义每个解码器的索引、位掩码、句柄、探测函数和缓冲区查询函数,未使能的解码器对应的宏全部展开为空:
#if defined(DECODER_UMP3_EN) && (DECODER_UMP3_EN)
#define UMP3_HLD (u32)&dec_ump3_hld
#define UMP3_LST UMP3_HLD,
#define UMP3_API (u32)ump3_decode_api,
#define UMP3_MUT_TAB (u32)ump3_buff_api,
#define BIT_UMP3 BIT(INDEX_UMP3)
#else
#define UMP3_HLD (u32)NULL
#define UMP3_LST
#define UMP3_API
#define UMP3_MUT_TAB
#define BIT_UMP3 0
#endif
Source: decoder_msg_tab.h
索引由枚举 INDEX_F1A1/INDEX_UMP3/INDEX_A/INDEX_MIDI/INDEX_MIDI_CTRL/INDEX_WAV/INDEX_MP3_ST 顺序分配(同样受宏控制),最后一个 INDEX_E_SPEED = 12 为变速变调效果器预留位(BIT_SPEED)。这样,dec_ctl 位掩码中的每一位都唯一对应一个解码器,框架可以在不知道具体格式的情况下按位探测。
decoder_api.c 中把上述宏拼接为三张表,分别存放解码对象句柄地址、探测函数指针、缓冲区查询函数指针:
AT(.audio_d.text.cache.L2)
u32 dec_hld_tab[] = {
F1A1_LST
F1A2_LST
UMP3_LST
A_LST
MIDI_LST
MIDI_CTRL_LST
WAV_LST
MP3_ST_LST
};
const u32 decoder_tab[] = {
F1A1_API
F1A2_API
UMP3_API
A_API
MIDI_API
MIDI_CTRL_API
WAV_API
MP3_ST_API
};
const u32 decoder_mutual[] = {
F1A1_MUT_TAB
F1A2_MUT_TAB
UMP3_MUT_TAB
A_MUT_TAB
MIDI_MUT_TAB
MIDI_CTRL_MUT_TAB
WAV_MUT_TAB
MP3_ST_MUT_TAB
};
Source: decoder_api.c
设计意图:把"表的内容"完全交给条件编译宏,decoder_tab[] 与 dec_hld_tab[] 天然对齐(同一编译配置下元素一一对应),框架遍历表即可完成探测与句柄管理,无需为每个格式写分支;AT(.audio_d.text.cache.L2) 与 SEC(.audio_d.text.cache.L2) 将热路径代码/数据放到 L2 缓存段,降低中断上下文取指延迟。
初始化:decoder_init()
void decoder_init(void)
{
u32 i;
dec_obj *obj;
u8 dc;
dc = sizeof(dec_hld_tab) / 4;
for (i = 0; i < dc; i++) {
obj = (void *)dec_hld_tab[i];
memset(obj, 0, sizeof(dec_obj));
}
decoder_channel_set(dc);
HWI_Install(IRQ_SOFT0_IDX, (u32)decoder_soft0_isr, IRQ_DECODER_IP) ;
}
Source: decoder_api.c
decoder_init() 做三件事:① 将所有解码句柄(dec_obj)清零,保证首次使用前处于干净状态;② 通过 decoder_channel_set(dc) 把解码器通道数(即表格长度)告知底层调度;③ 安装软中断 IRQ_SOFT0 的中断服务函数 decoder_soft0_isr,中断优先级为 IRQ_DECODER_IP——解码器的实际数据搬运、mp_read_2_dac() 等都在该中断上下文中被反复触发。
启动流程:decoder_io() 播放流水线装配
decoder_io() 是框架最核心的入口,它把"文件 → 解码器 → 效果器 → DAC"整条流水线装配起来。其流程如下:
sequenceDiagram
participant App as 应用层
participant IO as decoder_io()
participant Tab as decoder_tab[]
participant Codec as 编解码器
participant FS as 文件系统 (vfs)
participant DSP as 效果器链路 (Speed/SRC)
participant DAC as DAC 通道
App->>IO: decoder_io(pfile, dec_ctl, dbuff, loop)
IO->>FS: vfs_file_name() 获取文件名
IO->>IO: check_ext_api() 过滤 .mio 文件
loop 遍历 decoder_tab[dec_i]
IO->>IO: dec_ctl & BIT(dec_i) 未置位则跳过
IO->>FS: vfs_seek(pfile, 0, SEEK_SET)
IO->>Codec: fun(pfile, &p_dec, check_dp(dbuff))
Codec-->>IO: res==0 表示识别成功并创建 dec_obj
end
IO->>FS: vfs_get_attrs() 读取文件长度
IO->>IO: decoder_set_file_size() 下发 flen
IO->>DSP: 按需链接 speed_api (BIT_SPEED)
IO->>DSP: 按需链接 src_api (SR_DEFAULT 对比)
IO->>IO: loop 置位时保存断点 get_dp()
IO->>IO: enable |= B_DEC_ENABLE|B_DEC_KICK|B_DEC_FIRST
IO->>IO: kick_decoder() 启动软中断解码
IO->>DAC: dac_fade_in_api() 淡入
IO-->>App: 返回 dec_obj*
第一步:格式探测与解码对象创建
dec_obj *decoder_io(void *pfile, u32 dec_ctl, dp_buff *dbuff, u8 loop)
{
u32(*fun)(void *, void **, void *);
u32 res, dec_i, j;
int file_len = vfs_file_name(pfile, (void *)g_file_sname, sizeof(g_file_sname));
log_info("decoder io file name : %s", g_file_sname);
if (check_ext_api(g_file_sname, ".mio", 4)) {
return NULL;
}
...
res = E_DECODER;
for (dec_i = 0; dec_i < (sizeof(decoder_tab) / 4); dec_i++) {
if (0 == (dec_ctl & BIT(dec_i))) {
continue;
}
vfs_seek(pfile, 0, SEEK_SET);
fun = (void *)decoder_tab[dec_i];
p_dec = 0;
res = fun(pfile, (void **)(&p_dec), check_dp(dbuff));
if (0 == res) {
break;
}
}
...
}
Source: decoder_api.c
关键点:
- 每个探测函数签名统一为
u32 fun(void *pfile, void **pp_dec, void *dp):pfile是已打开的 VFS 文件句柄;pp_dec用于回传创建出的dec_obj *;第三个参数是断点缓冲(可能为 NULL)。返回 0 表示该解码器识别出文件格式; - 探测前先
vfs_seek(pfile, 0, SEEK_SET)回到文件头,保证每个探测函数都在同一位置读取文件头,不因前一个解码器读取而错位; dec_ctl的位掩码决定允许尝试哪些解码器;一旦某个探测函数返回 0,立即break,先注册者优先;- 探测失败时
res = E_DECODER,最终log_info("decode err : 0x%x", res)并关闭已打开的 MIO 文件。
第二步:参数下发与效果器链路装配
探测成功后,框架从 VFS 属性中取得文件大小并下发给解码器,随后按编译配置依次链接变速变调(AUDIO_SPEED_EN 且 dec_ctl & BIT_SPEED)与采样率转换(HAS_HW_SRC_EN/HAS_SW_SRC_EN 且解码采样率不等于 SR_DEFAULT):
if (0 == res) {
struct vfs_attr fattr = {0};
vfs_get_attrs(pfile, &fattr);
if (fattr.fsize) {
decoder_set_file_size(p_dec, fattr.fsize);
}
p_curr_sound = &p_dec->sound;
p_curr_sound->enable = 0;
sound_out_obj *first_sound = p_curr_sound;
void *cbuff_o = p_dec->sound.p_obuf;
#if defined(AUDIO_SPEED_EN) && (AUDIO_SPEED_EN)
if (dec_ctl & BIT_SPEED) {
p_curr_sound->effect = speed_api(cbuff_o, p_dec->sr, (void **) &p_next_sound);
if (NULL != p_curr_sound->effect) {
p_curr_sound->enable |= B_DEC_EFFECT;
p_dec->speed_effect = p_curr_sound->effect;
p_curr_sound = p_next_sound;
p_curr_sound->p_obuf = cbuff_o;
p_next_sound = 0;
}
}
#endif
p_curr_sound->enable = 0;
#if (defined(HAS_HW_SRC_EN) || defined(HAS_SW_SRC_EN))
if (SR_DEFAULT != p_dec->sr) {
log_info("need src %d %d\n", SR_DEFAULT, p_dec->sr);
p_curr_sound = link_src_sound(p_curr_sound, cbuff_o,
(void **) &p_dec->src_effect,
p_dec->sr, SR_DEFAULT,
(void *)GET_SRC_OPS());
} else {
void *src_tmp = src_hld_malloc((void *)GET_SRC_OPS(), NULL);
src_reless((void **)&src_tmp);
log_info("do't need src\n");
}
#else
void dac_sr_api(u32 sr);
dac_sr_api(p_dec->sr);
#endif
Source: decoder_api.c
设计意图:效果器以链式 sound_out_obj 方式串接——p_curr_sound 依次指向"解码器输出 → 变速效果 → SRC 效果",每个节点共享同一个输出环形缓冲 cbuff_o,最后统一注册到 DAC 通道。若目标采样率与 SR_DEFAULT 一致则不需要 SRC,但代码仍会做一次 src_hld_malloc/src_reless 的"热身"(分配并立即释放),用于预加载/初始化 SRC 资源池。
第三步:断点保存与启动
p_curr_sound->mio = p_dec->sound.mio;
clear_dp(dbuff);
if (0 != loop) { // (dec_ctl & BIT_LOOP)
p_dec->loop = loop;
if (true == get_dp(p_dec, dbuff)) {
p_dec->p_dp_buf = check_dp(dbuff);
}
}
p_dec->sound.enable |= B_DEC_ENABLE | B_DEC_KICK | B_DEC_FIRST;
kick_decoder();
log_info("decode succ \n");
} else {
log_info("decode err : 0x%x\n", res);
#if HAS_MIO_EN
if (0 == mio_res) {
vfs_file_close(&mio_pfile);
}
#endif
}
dac_fade_in_api();
return p_dec;
Source: decoder_api.c
- 断点(断点续播)数据通过
dp_buff(findex/sclust + crc + len + 各格式私有头)保存:get_dp()从解码器提取当前位置,check_dp()校验其有效性;loop非 0 时启用; B_DEC_ENABLE | B_DEC_KICK | B_DEC_FIRST三个标志分别表示"通道使能、需要立即踢一次解码、首次启动";随后kick_decoder()触发软中断,解码正式开跑;- 无论成功与否,最后都会调用
dac_fade_in_api()做 DAC 淡入,避免爆音。
播放控制:暂停 / 停止 / 快进快退
框架的控制接口统一通过操作 dec_obj->sound.enable 标志位实现,全部是"无锁的标志位 + 软中断踢动"模式:
| 接口 | 作用 | 实现要点 |
|---|---|---|
decoder_pause(obj) | 暂停/恢复 | 若不在运行直接返回;enable ^= B_DEC_PAUSE 翻转暂停位,恢复时补 B_DEC_KICK 并 kick_decoder() |
decoder_stop(obj, wait) | 停止(可等待 DAC 消耗完) | 转调 decoder_stop_phy() |
decoder_stop_now(obj) | 立即停止 | decoder_stop_phy(obj, NO_WAIT, 1) |
decoder_ff(obj, step) | 快进(步长秒) | 校验 DEC_FUNCTION_FF_FR 功能位,暂停态先恢复播放,obj->ff_fr_step = step |
decoder_fr(obj, step) | 快退(步长秒) | 同上,obj->ff_fr_step = 0 - step |
停止的底层实现 decoder_stop_phy() 展示了完整的资源回收顺序:
void decoder_stop_phy(dec_obj *obj, DEC_STOP_WAIT wait, bool fade)
{
if (NULL == obj) {
return;
}
obj->sound.enable &= ~B_DEC_RUN_EN;
dac_fade_out_api(200);
if (NO_WAIT != wait) {
log_info("decode stop wait!\n");
while (obj->sound.enable & B_DEC_OBUF_EN) {
if (false == dac_cbuff_active(&obj->sound)) {
break;
}
}
}
d_mio_close(&obj->sound.mio);
unregist_dac_channel(&obj->sound);
#if (defined(HAS_HW_SRC_EN) || defined(HAS_SW_SRC_EN))
if (NULL != obj->src_effect) {
src_reless(&obj->src_effect);
}
#endif
#if (defined(AUDIO_SPEED_EN) || defined(AUDIO_SPEED_EN))
if (NULL != obj->speed_effect) {
sp_release(&obj->speed_effect);
}
#endif
if (obj->decoder_res_release) {
obj->decoder_res_release(obj);
obj->decoder_res_release = NULL;
}
}
Source: decoder_api.c
回收顺序设计:先清 B_DEC_RUN_EN 停止解码器产生新数据 → DAC 淡出(200ms)→ 按需等待 DAC 环形缓冲消耗完(NEED_WAIT 模式下轮询 B_DEC_OBUF_EN 与 dac_cbuff_active(),带退出条件防止死等)→ 关闭 MIO → 注销 DAC 通道 → 释放 SRC/变速效果器 → 调用解码器自定义的 decoder_res_release 钩子释放其私有资源。DEC_STOP_WAIT 枚举(NO_WAIT/NEED_WAIT)正是"停止解码时,是否需要将 DAC 中剩余的样点消耗完"这一策略的显式表达。
中断驱动与解码结果分发
解码推进完全由软中断驱动。kick_decoder() 通过 bit_set_swi(0) 触发 IRQ_SOFT0,中断服务函数 decoder_soft0_isr(在 decoder_init() 中安装)驱动各解码通道向 DAC 填充数据。当解码器遇到文件结束或错误时,irq_decoder_ret() 负责把结果转成事件投递到上层:
void irq_decoder_ret(dec_obj *obj, u32 ret)
{
if (MAD_ERROR_PLAY_END == ret) {
midi_error_play_end_cb(obj, ret);
return;
}
if (0 != ret) {
log_info("decoder ret : 0x%x\n", ret);
if (MAD_ERROR_F1X_START_ADDR == ret) {
post_event(obj->event_tab[MAD_ERROR_PLAY_END & 0x0f]);
} else {
post_event(obj->event_tab[ret & 0x0f]);
}
}
switch (ret) {
case MAD_ERROR_NODATA:
case MAD_ERROR_FILE_END:
case MAD_ERROR_SYNC_LIMIT:
case MAD_ERROR_F1X_START_ADDR:
obj->sound.enable |= B_DEC_ERR;
log_info("file end\n");
break;
}
}
Source: decoder_api.c
要点:
- 错误码通过
ret & 0x0f索引obj->event_tab[]事件表,post_event()把事件投递到应用的消息队列——解码框架与上层 UI/播放逻辑通过事件表解耦; MAD_ERROR_PLAY_END特殊处理:调用midi_error_play_end_cb()回调(该函数声明为__attribute__((weak)),应用可覆盖),用于 MIDI 播放异常结束的兜底;MAD_ERROR_NODATA / FILE_END / SYNC_LIMIT / F1X_START_ADDR这些"正常结束类"错误会置B_DEC_ERR标志,表示本次播放已到文件尾,供 DAC 通道判断何时自然收尾。
Usage Examples
示例一:框架初始化(系统启动时调用一次)
void decoder_init(void)
{
u32 i;
dec_obj *obj;
u8 dc;
dc = sizeof(dec_hld_tab) / 4;
for (i = 0; i < dc; i++) {
obj = (void *)dec_hld_tab[i];
memset(obj, 0, sizeof(dec_obj));
}
decoder_channel_set(dc);
HWI_Install(IRQ_SOFT0_IDX, (u32)decoder_soft0_isr, IRQ_DECODER_IP) ;
}
Source: decoder_api.c
示例二:启动一次播放(应用层典型调用)
应用打开文件后,按需要组合 dec_ctl 位掩码(如 BIT_UMP3 | BIT_WAV | BIT_A 只尝试这几种格式),并可传入断点缓冲支持断点续播:
// decoder_api.h 中声明的入口(实际调用由应用完成)
dec_obj *decoder_io(void *pfile, u32 dec_ctl, dp_buff *dbuff, u8 loop);
void decoder_pause(dec_obj *obj);
void decoder_stop(dec_obj *obj, DEC_STOP_WAIT wait);
void decoder_ff(dec_obj *obj, u8 step); // 快进。step单位-秒
void decoder_fr(dec_obj *obj, u8 step); // 快退。step单位-秒
int decoder_time(dec_obj *p_dec);
bool get_dp(dec_obj *obj, dp_buff *dbuff); // 断点函数:读取断点
void *check_dp(dp_buff *dbuff); // 断点函数:校验断点
void clear_dp(dp_buff *dbuff); // 断点函数:清除断点
Source: decoder_api.h
示例三:探测函数表驱动的格式识别
每个解码器都向框架暴露 u32 fun(void *pfile, void **pp_dec, void *dp) 形式的探测函数,例如 UMP3 与 WAV 在 decoder_msg_tab.h 中注册为:
#define UMP3_API (u32)ump3_decode_api,
#define WAV_API (u32)wav_decode_api,
#define MIDI_API (u32)midi_decode_api,
#define F1A1_API (u32)f1a_decode_api_1,
#define MP3_ST_API (u32)mp3_st_decode_api,
Source: decoder_msg_tab.h
示例四:互斥检测(同时只能有一个解码器占用重叠缓冲)
框架提供 decoder_mutex() 用于检测两个解码器缓冲区是否重叠,重叠时强制停止后启动者,防止多个解码器共享同一块 RAM 导致数据互相覆盖:
void decoder_mutex(u32 index)
{
dec_buf cw;
dec_buf cl;
u32 max_loop = sizeof(decoder_mutual) / sizeof(decoder_mutual[0]);
if (index >= max_loop) {
return;
}
u32(*fun)(dec_buf * p) = (void *)decoder_mutual[index];
fun(&cw);
for (u32 i = 0; i < max_loop; i++) {
if (i == index) {
continue;
}
fun = (void *)decoder_mutual[i];
fun(&cl);
if ((cl.start >= cw.end) || (cw.start >= cl.end)) {
continue;
}
decoder_stop_now((void *)dec_hld_tab[i]);
}
}
Source: decoder_api.c
设计意图:嵌入式平台内存紧张,各解码器通常复用同一块解码缓冲池;*_buff_api() 返回各自的缓冲区 [start, end) 区间,decoder_mutex() 做区间重叠判断,重叠即冲突,直接 decoder_stop_now() 停掉旧解码器,保证新解码器独占缓冲。
Configuration Options
以下配置宏在编译期决定框架内实际包含哪些解码器与效果器,均在 decoder_msg_tab.h / decoder_api.c 中通过 #if defined(...) 使用:
| 配置宏 | 类型 | 默认(未定义时) | 说明 |
|---|---|---|---|
DECODER_F1A_EN | bool 宏 | 关闭 | 使能杰理私有 F1A 解码器;MAX_F1A_CHANNEL > 1 时额外注册 F1A2 通道(INDEX_F1A2、f1a_decode_api_2) |
MAX_F1A_CHANNEL | int | 1 | F1A 通道数,决定 dec_f1a_hld[] 句柄数组长度与第二个 F1A 索引是否编译 |
DECODER_UMP3_EN | bool 宏 | 关闭 | 使能 UMP3(压缩 MP3)解码器,注册 ump3_decode_api、ump3_buff_api |
DECODER_A_EN | bool 宏 | 关闭 | 使能 A 格式解码器,注册 a_decode_api、a_buff_api |
DECODER_MIDI_EN | bool 宏 | 关闭 | 使能 MIDI 解码器,注册 midi_decode_api、midi_buff_api |
DECODER_MIDI_KEYBOARD_EN | bool 宏 | 关闭 | 使能 MIDI 键盘控制解码器(INDEX_MIDI_CTRL),注册 midi_ctrl_decode_api |
DECODER_WAV_EN | bool 宏 | 关闭 | 使能 WAV 解码器,注册 wav_decode_api、wav_buff_api |
DECODER_MP3_ST_EN | bool 宏 | 关闭 | 使能标准 MP3 解码器(INDEX_MP3_ST),注册 mp3_st_decode_api |
AUDIO_SPEED_EN | bool 宏 | 关闭 | 使能变速变调功能;dec_ctl & BIT_SPEED 时经 speed_api() 链接效果器 |
HAS_HW_SRC_EN / HAS_SW_SRC_EN | bool 宏 | 关闭 | 使能硬件/软件采样率转换;解码采样率 ≠ SR_DEFAULT 时链接 SRC |
HAS_MIO_EN | bool 宏 | 关闭 | 使能 MIO 语音叠加;.mio 文件被 check_ext_api() 过滤,成功时打开 MIO 通道 |
SR_DEFAULT | u32 | — | 系统默认采样率,用于判断是否需要 SRC 或直接配置 DAC 采样率 |
DEC_FUNCTION_FF_FR | 位定义 | BIT(0) | 解码器能力位:是否支持快进快退(decoder_ff/fr 先校验该位) |
DEC_STOP_WAIT | 枚举 | NO_WAIT=0 | decoder_stop() 停止语义:是否等待 DAC 剩余样点消耗完(NEED_WAIT=1) |
未使能的解码器宏全部展开为空(NULL/0/空列表),因此 decoder_tab[] 中不会出现对应表项,dec_ctl 中对应位恒为 0,探测循环自然跳过——这就是编译期裁剪:配置决定体积与能力,代码路径完全统一。
API Reference
void decoder_init(void)
初始化框架:清零所有 dec_obj 句柄、设置解码器通道数、安装 IRQ_SOFT0 软中断服务(优先级 IRQ_DECODER_IP)。系统启动时调用一次。
dec_obj *decoder_io(void *pfile, u32 dec_ctl, dp_buff *dbuff, u8 loop)
核心启动入口:按 dec_ctl 位掩码顺序探测解码器,成功后装配效果器链路、注册 DAC 通道并启动软中断解码。
- 参数:
pfileVFS 文件句柄;dec_ctl允许的解码器位掩码(BIT_UMP3/BIT_WAV/…/BIT_SPEED);dbuff断点缓冲(可 NULL);loop非 0 启用断点续播。 - 返回:成功返回
dec_obj *;.mio文件或全部探测失败返回NULL(失败时res = E_DECODER并打印decode err)。
void decoder_pause(dec_obj *obj) / u32 if_decoder_is_run(dec_obj *obj) / u32 if_decoder_pause(dec_obj *obj)
暂停/恢复播放与状态查询。if_decoder_is_run 返回 sound.enable & B_DEC_RUN_EN;if_decoder_pause 返回 B_DEC_PAUSE 位,obj 为 NULL 时视为已暂停(返回 1)。
void decoder_stop(dec_obj *obj, DEC_STOP_WAIT wait) / void decoder_stop_now(dec_obj *obj)
停止解码。wait 取 NEED_WAIT 时阻塞等待 DAC 缓冲消耗完(带 dac_cbuff_active() 退出条件);NO_WAIT 立即停止。内部按序:清 B_DEC_RUN_EN → DAC 淡出 → 关闭 MIO → 注销 DAC 通道 → 释放 SRC/变速效果器 → 调用 decoder_res_release 钩子。
void decoder_ff(dec_obj *obj, u8 step) / void decoder_fr(dec_obj *obj, u8 step)
快进/快退,步长单位为秒。若解码器未运行或不支持 DEC_FUNCTION_FF_FR 则直接返回;暂停状态下先恢复播放,再设置 obj->ff_fr_step(快退为负值)。
void decoder_set_file_size(dec_obj *obj, u32 size)
下发文件总长度:通过 dec_ops->dec_confing(obj->p_dbuf, SET_FILE_TOTAL_LEN, &file_parm) 配置到解码器,用于进度计算与流式解码判断。
int decoder_time(dec_obj *p_dec)
返回解码进度(时间,单位取决于各解码器实现),供应用显示播放位置。
断点相关
bool get_dp(dec_obj *obj, dp_buff *dbuff):从解码器提取断点(文件偏移 + CRC + 各格式私有头)。void *check_dp(dp_buff *dbuff):校验断点有效性,有效返回内部缓冲指针,否则 NULL。void clear_dp(dp_buff *dbuff)/void clear_dp_buff(void *buff):清除断点缓冲内容。
数据通路辅助
int mp_input(void *priv, u32 addr, void *buf, int len, u8 type):从文件系统读入压缩数据。u32 mp_output(void *priv, void *data, int len)/int effect_output(void *priv, void *data, int len):解码 PCM 输出与效果器输出回调。u32 decoder_get_flen(void *priv):查询解码器已处理/剩余长度。u32 decoder_set_sr(dec_obj *d_obj):设置解码器采样率。bool dac_cbuff_active(void *sound_hld):查询 DAC 通道缓冲是否仍在活动(停止等待循环的退出条件)。u32 mp_read_2_dac(void):软中断上下文中把解码数据搬运到 DAC。void decoder_soft0_isr():IRQ_SOFT0中断服务函数。void decoder_channel_set(u8 dc):设置解码器通道数。void decoder_soft_hook(void):软中断钩子(默认调用d_mio_run(),供 MIO 处理)。void kick_decoder(void):bit_set_swi(0)触发软中断。bool check_ext_api(char *fname, char const *ext, u32 len):扩展名匹配检查。
回调/钩子
__attribute__((weak)) void midi_error_play_end_cb(dec_obj *obj, u32 ret):MAD_ERROR_PLAY_END时的弱回调,应用可覆盖。obj->decoder_res_release:解码器私有资源释放钩子,由decoder_stop_phy()在最后阶段调用并清空。
Failure Modes, Edge Cases & Concurrency
解码失败路径
- 全部探测失败:
decoder_io()中res保持E_DECODER,打印decode err : 0x%x;若曾打开 MIO 文件则vfs_file_close(&mio_pfile)关闭,返回NULL。上层应据此回退或提示"不支持的格式"。 .mio文件:check_ext_api(g_file_sname, ".mio", 4)命中即直接返回NULL——MIO 是语音叠加通道(经d_mio_open走mio_a_hook_init),不是普通解码目标,框架显式分流。- 文件头读取竞态:每个探测函数执行前都
vfs_seek(pfile, 0, SEEK_SET),保证探测起点一致;探测成功后文件位置也回到起点,避免"上一个解码器读走了文件头导致下一个识别失败"。
解码结束/错误事件
irq_decoder_ret() 将错误码映射为事件(obj->event_tab[ret & 0x0f])通过 post_event() 投递:
MAD_ERROR_PLAY_END:走弱回调midi_error_play_end_cb()(应用可覆盖),不投递普通事件;MAD_ERROR_F1X_START_ADDR:投递MAD_ERROR_PLAY_END事件(视为播放结束语义);MAD_ERROR_NODATA / FILE_END / SYNC_LIMIT / F1X_START_ADDR:置B_DEC_ERR标志,指示 DAC 通道自然收尾。
并发与中断安全
- 解码推进完全在
IRQ_SOFT0软中断上下文中完成(decoder_soft0_isr,优先级IRQ_DECODER_IP),由kick_decoder()(bit_set_swi(0))驱动;控制接口只改标志位 + 踢中断,不直接操作解码数据,因此对中断上下文安全; - 状态标志位(
B_DEC_RUN_EN、B_DEC_PAUSE、B_DEC_OBUF_EN、B_DEC_ERR、B_DEC_FIRST)全部位于sound.enable,是单字读写,中断与应用之间无需互斥锁; decoder_pause()的恢复分支必须补置B_DEC_KICK并kick_decoder(),否则暂停后解码器不会自动恢复推进;decoder_stop_phy()的NEED_WAIT等待循环以dac_cbuff_active()作为退出条件,防止 DAC 通道已注销/停摆时无限死等;decoder_mutex()提供缓冲区重叠互斥:内存紧张时多个解码器共享缓冲池,重叠检测通过各*_buff_api()返回的[start, end)区间完成,冲突即decoder_stop_now()强停旧解码器。注意当前decoder_io()中该调用被注释(/* decoder_mutex(dec_i); */),由上层按需启用。
边界情况
decoder_pause/decoder_stop/decoder_ff/fr均先判空(obj == NULL直接返回)或判运行态(if_decoder_is_run),避免对未启动解码器操作;if_decoder_pause(NULL)返回 1(视作已暂停),语义上"无对象即无播放";decoder_ff/fr要求obj->function & DEC_FUNCTION_FF_FR,不支持的格式静默忽略快进快退请求;- 断点续播依赖
dp_buff的 CRC 校验(check_dp),断点无效时静默回退为从头播放。
Performance & Operational Considerations
- 热路径放置 L2 缓存段:
dec_hld_tab[]使用AT(.audio_d.text.cache.L2),kick_decoder()使用SEC(.audio_d.text.cache.L2)——软中断每次触发都要访问这些表/函数,放 L2 可显著降低中断延迟与取指抖动; - 中断频率:解码推进粒度由 DAC 缓冲水位决定,
mp_read_2_dac()每次搬运固定长度的 PCM;缓冲设置(*_buff_api返回区间)直接决定中断频率与内存占用,是内存/功耗折中点; - 采样率策略:
SR_DEFAULT == p_dec->sr时不做 SRC(仅做一次分配/释放热身);有 SRC 时链入link_src_sound();无 SRC 能力时直接dac_sr_api(p_dec->sr)重配 DAC 采样率——后者会带来切换采样率时的短暂静音/爆音风险,故默认平台建议启用 SRC; - 停止等待:
NEED_WAIT会阻塞调用线程直至 DAC 缓冲排空(或dac_cbuff_active失效),用于无缝切换下一曲;对响应性要求高的场景应使用decoder_stop_now(); - 淡入淡出:启动统一
dac_fade_in_api()、停止统一dac_fade_out_api(200)(200ms),这是避免开关机/切歌爆音的全局策略,应用不应绕过。
Extension Points
新增解码格式(如 DTS/FLAC):
- 在解码器模块实现
u32 xxx_decode_api(void *pfile, void **pp_dec, void *dp)与u32 xxx_buff_api(dec_buf *p); - 在
decoder_msg_tab.h中新增INDEX_XXX枚举、XXX_LST/XXX_API/XXX_MUT_TAB宏与BIT_XXX位,并用DECODER_XXX_EN宏包住; - 声明
extern dec_obj dec_xxx_hld;并在decoder_api.h的DECOER_TYPE枚举中登记类型。框架的探测循环、断点机制、停止回收均自动适配,无需改动框架代码。
- 在解码器模块实现
自定义停止回收:解码器可设置
obj->decoder_res_release回调,decoder_stop_phy()在回收末尾调用并置 NULL,用于释放解码器私有资源(如 MIDI 音色库句柄)。播放结束回调:覆盖
midi_error_play_end_cb()(weak 符号)可接管MAD_ERROR_PLAY_END的结束处理。MIO 叠加:使能
HAS_MIO_EN后,decoder_io()会自动为播放通道挂载 MIO 解码(d_mio_open(&first_sound->mio, mio_pfile, mio_a_hook_init)),可在不打断主解码的前提下叠加语音提示。效果器链:
speed_api(变速变调)与src_api(采样率转换)以sound_out_obj链式节点接入,效果器的采样率参数来自p_dec->sr,扩展新效果器只需在decoder_io()的链路装配段仿照现有模式插入节点。
相关页面与源码
- 音频输出与 DAC 通道:
dac_api.h、dac.c(解码数据的最终消费方,dac_cbuff_active/dac_fade_*均属此域) - 效果器链路:
sound_effect_api.h、src_api、speed_api(采样率转换与变速变调实现) - 各解码器模块:
ump3_api、f1a_api、midi_api、midi_ctrl_api、wav_api、a_api、mp3_standard_api - 文件系统:
vfs.h(vfs_seek/vfs_get_attrs/vfs_file_name为解码器探测提供文件访问) - 事件机制:
msg.h/post_event()(解码结束事件投递目标)
关键源码文件:
- decoder_api.h — 框架对外 API、
dec_obj句柄声明、dp_buff断点结构 - decoder_msg_tab.h — 解码器索引/位掩码/注册宏(编译期裁剪核心)
- decoder_api.c — 框架实现:表拼接、
decoder_init、decoder_io、控制接口、中断分发 - decoder_mge.h — 解码管理相关定义
- if_decoder_ctrl.h — 解码控制接口定义