音频解码器
本页介绍 Jieli AD1NN GP-MCU SDK 中的音频解码器子系统:它通过统一的 decoder_api 接口调度 F1A、UMP3、A、MIDI、MIDI_CTRL、WAV、MP3_ST 等多种格式解码器,并串联变速变调、采样率转换与 DAC 输出链路,向上层播放业务提供文件解码、断点续播、快进快退、循环播放等能力。
Purpose and Scope
本文档覆盖解码器子系统的完整实现机制:
- 统一解码框架
decoder_api(decoder_api.h/decoder_api.c)的设计与调度逻辑; - 各格式解码器所遵循的操作接口
audio_decoder_ops(if_decoder_ctrl.h); - 解码启动(
decoder_io)、互斥调度(decoder_mutex)、初始化(decoder_init)与中断处理(decoder_soft0_isr/irq_decoder_ret)的真实控制流; - 断点续播(
get_dp/check_dp/clear_dp)、快进快退(decoder_ff/decoder_fr)、循环播放等能力; - 相关的配置宏、失败模式与并发约束。
本页不展开以下属于兄弟页面的话题:具体格式解码器内部算法(UMP3/MIDI/WAV 各自的编解码细节)、DAC 输出硬件细节、EQ/音效算法本身、文件系统(VFS)的读写实现。这些能力在对应目录页中另行说明。
Overview
在 AD1NN 系列 MCU 的音频方案中,解码器子系统是"文件 → 音频流 → DAC"整条链路的中间枢纽。其设计目标可以概括为三点:
- 统一入口:上层播放逻辑只面对
decoder_fun/decoder_io两个函数,不必关心当前文件是何种格式。格式识别通过dec_ctl位掩码(BIT(dec_i))逐项探测decoder_tab中的解码器 open 函数完成。 - 资源互斥:SDK 通过编译期生成的
decoder_mutual缓冲占用表,在启动新解码器时检查各解码器工作缓冲是否重叠,重叠即强制停止冲突解码器,避免同一块 RAM 被两个解码器同时使用。 - 链路可插拔:解码器输出后可以按宏开关(
AUDIO_SPEED_EN、HAS_HW_SRC_EN/HAS_SW_SRC_EN)串联变速变调(speed_api)和采样率转换(SRC),最终送入 DAC;无 SRC 时直接将 DAC 采样率配置为与文件采样率一致。
解码器类型在 DECOER_TYPE 枚举中定义:D_TYPE_F1A_1(F1A 通道 1)、D_TYPE_F1A_2(可选第二通道)、D_TYPE_UMP3(杰理私有 UMP3)、D_TYPE_A(杰理 A 格式)、D_TYPE_MIDI、D_TYPE_MIDI_CTRL、D_TYPE_WAV、D_TYPE_MP3_ST(标准 MP3)。每种类型对应一个全局解码句柄(dec_ump3_hld、dec_f1a_hld[] 等),这些句柄被集中登记在 dec_hld_tab 表中,由 decoder_init 统一清零初始化。
Architecture
下图展示了解码器子系统在 SDK 中的层次结构与数据流向:
flowchart TD
subgraph sg_App["应用层"]
App["App / 业务模块"]
Player["播放控制<br/>decoder_fun / decoder_io"]
end
subgraph sg_Decoder["解码管理框架 decoder_api"]
API["decoder_api.c"]
INIT["decoder_init 句柄清零 + SWI0 中断注册"]
MUTEX["decoder_mutex 缓冲互斥调度"]
IRQ["decoder_soft0_isr 解码软中断"]
end
subgraph sg_Codec["格式解码器层 (audio_decoder_ops)"]
F1A["F1A 解码器 (dec_f1a_hld)"]
UMP3["UMP3 解码器 (dec_ump3_hld)"]
A["A 解码器 (dec_a_hld)"]
MIDI["MIDI 解码器 (dec_midi_hld)"]
WAV["WAV 解码器 (dec_wav_hld)"]
MP3ST["MP3_ST 解码器 (dec_mp3_st_hld)"]
end
subgraph sg_AudioPath["音频输出链路"]
SPEED["speed_api 变速变调"]
SRC["SRC 采样率转换"]
DAC["DAC 输出"]
end
App --> Player
Player --> API
API --> INIT
API --> MUTEX
API --> IRQ
API --> F1A
API --> UMP3
API --> A
API --> MIDI
API --> WAV
API --> MP3ST
F1A --> SPEED
UMP3 --> SPEED
A --> SPEED
MIDI --> SPEED
WAV --> SPEED
SPEED --> SRC
SRC --> DAC
图中各层职责如下:
- 应用层:播放业务通过
decoder_fun(事件驱动)或decoder_io(同步启动)发起解码;快进快退、暂停、停止通过decoder_ff/decoder_fr/decoder_pause/decoder_stop等接口控制。 - 解码管理框架(
decoder_api):持有三张关键表——解码句柄表dec_hld_tab、解码器 open 函数表decoder_tab、互斥缓冲表decoder_mutual。decoder_init在系统启动时清零句柄并注册 SWI0 软中断,解码过程中解码器通过kick_decoder()触发该软中断驱动解码推进。 - 格式解码器层:每种格式实现
audio_decoder_ops操作集(open/format_check/run/get_dec_inf/get_playtime/get_bp_inf/need_dcbuf_size/need_bpbuf_size/dec_confing),框架通过decoder_tab[dec_i]间接调用。decoder_tab与dec_hld_tab按下标一一对应(F1A1、F1A2、UMP3、A、MIDI、MIDI_CTRL、WAV、MP3_ST)。 - 音频输出链路:解码产生的 PCM 经可选
speed_api变速变调、SRC 采样率转换后进入 DAC;若未编译 SRC 模块,则调用dac_sr_api(p_dec->sr)将 DAC 采样率配置为与文件一致。
关键的设计权衡:将互斥检查放在框架层而非各解码器内部,使得新增一种格式解码器时只需要在三个表中登记条目并提供 audio_decoder_ops,无需改动调度逻辑——这是典型的"表驱动 + 操作集接口"插件化设计,与嵌入式系统 ROM 化、可裁剪的需求相匹配。
核心数据结构
解码器操作集 audio_decoder_ops
所有格式解码器通过 if_decoder_ctrl.h 中定义的操作集接口接入框架。这是整个解码器子系统最重要的抽象:
typedef struct __audio_decoder_ops {
char *name; ///< 解码器名称
u32(*open)(void *work_buf, const struct if_decoder_io *decoder_io, u8 *bk_point_ptr); ///<打开解码器
u32(*format_check)(void *work_buf); ///<格式检查
u32(*run)(void *work_buf, u32 type); ///<主循环
dec_inf_t *(*get_dec_inf)(void *work_buf); ///<获取解码信息
u32(*get_playtime)(void *work_buf); ///<获取播放时间
u32(*get_bp_inf)(void *work_buf); ///<获取断点信息
u32(*need_dcbuf_size)(); ///<获取解码需要的buffer
u32(*need_bpbuf_size)(); ///<获取保存断点信息需要的buffer
u32(*dec_confing)(void *work_buf, u32 cmd, void *parm);
} audio_decoder_ops, decoder_ops_t;
extern audio_decoder_ops *get_ump3_ops();
extern audio_decoder_ops *get_f1a_ops();
extern audio_decoder_ops *get_ima_ops();
extern audio_decoder_ops *get_mp3_ops();
extern audio_decoder_ops *get_wav_ops();
Source: if_decoder_ctrl.h
设计意图:open 承担"打开 + 格式检查"双重职责,返回 0 表示成功;run 是解码主循环,由软中断驱动;dec_confing 接收 SET_DECODE_MODE、SET_FILE_TOTAL_LEN、SET_FF_FR_STEP_CMD 等命令字,是框架向解码器下发控制参数的统一通道。need_dcbuf_size 与 need_bpbuf_size 让框架能够预知每种解码器的缓冲需求,这是 decoder_mutex 做缓冲区重叠检查的数据基础。
解码信息与 I/O 接口
typedef struct if_decoder_io {
void *priv;
int(*input)(void *priv, u32 addr, void *buf, int len);
int(*output)(void *priv, void *data, int len);
} IF_DECODER_IO;
typedef struct decoder_inf {
u16 sr; ///< sample rate
u16 br; ///< bit rate
u32 nch; ///<声道
u32 total_time; ///<总时间
} dec_inf_t;
typedef struct _dec_buff {
u32 start;
u32 end;
} dec_buf;
Source: if_decoder_ctrl.h
if_decoder_io 是解码器读取文件、输出 PCM 的抽象通道,priv 通常指向 VFS 文件句柄;dec_inf_t 保存采样率、码率、声道数与总时长,供上层显示与 SRC 链路使用;dec_buf 表示一段内存区间 [start, end),decoder_mutex 正是用"两区间是否相交"来判断解码器是否共用工作缓冲。
断点缓冲 dp_buff
typedef struct _dp_buff {
u32 findex;
u16 crc;
u16 len;
union {
u8 buff[1];
#if defined(DECODER_UMP3_EN) && (DECODER_UMP3_EN)
u8 ump3[20];
#endif
#if defined(DECODER_MP3_ST_EN) && (DECODER_MP3_ST_EN)
u8 mp3[0x10];
#endif
#if defined(DECODER_F1A_EN) && (DECODER_F1A_EN)
u8 f1a[60];
#endif
#if defined(DECODER_WAV_EN) && (DECODER_WAV_EN)
u8 wav[12];
#endif
};
} dp_buff;
Source: decoder_api.h
dp_buff 用于保存解码断点(循环点),findex 为文件索引,crc 用于校验断点有效性,len 为有效数据长度,union 按编译开关为不同格式提供最大断点存储空间。断点存取由 get_dp / check_dp / clear_dp / clear_dp_buff 完成。
解码类型与播放控制常量
typedef enum {
D_TYPE_F1A_1 = 0,
#if defined(MAX_F1A_CHANNEL) && (MAX_F1A_CHANNEL > 1)
D_TYPE_F1A_2 = 1,
#endif
D_TYPE_UMP3 = 2,
D_TYPE_A,
D_TYPE_MIDI,
D_TYPE_MIDI_CTRL,
D_TYPE_WAV,
D_TYPE_MP3_ST,
} DECOER_TYPE ;
#define DEC_FUNCTION_FF_FR BIT(0) // 快进快退功能
typedef enum {//停止解码时,是否需要将DAC中剩余的样点消耗完
NO_WAIT = 0,
NEED_WAIT = 1,
} DEC_STOP_WAIT;
Source: decoder_api.h
DECOER_TYPE 枚举值与 decoder_tab / dec_hld_tab 的下标顺序严格对应,dec_ctl 中的 BIT(dec_i) 位用于指定允许探测的解码器集合。DEC_STOP_WAIT 控制停止解码时是否等待 DAC 中残留样点播完,这是防止"停止瞬间爆音/截断"的音频体验设计。
初始化与生命周期
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
初始化分三步:首先遍历 dec_hld_tab 将每个全局解码句柄清零(防止上次运行残留状态);然后通过 decoder_channel_set(dc) 通知底层解码通道数量;最后把 decoder_soft0_isr 注册到 SWI0 软中断(IRQ_SOFT0_IDX,优先级 IRQ_DECODER_IP)。此后解码器在数据就绪时通过 kick_decoder()(bit_set_swi0)触发该软中断,在中断上下文中推进解码主循环——这是"解码在中断中跑、控制在外层跑"的实时音频架构。
三张核心表的登记关系
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
F1A1_LST / F1A1_API / F1A1_MUT_TAB 等宏由各格式的编译开关定义(如 DECODER_F1A_EN、DECODER_UMP3_EN),在工程裁剪时自动增删条目,因此 decoder_tab 与 dec_hld_tab 的实际长度随配置变化。dec_hld_tab 被显式放在 .audio_d.text.cache.L2 段,表明解码句柄表是音频实时路径上的热数据,需要放入 L2 缓存以降低访问延迟。
解码器互斥调度 decoder_mutex
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;
}
/* log_info("decoder mutex : %d %d\n", index, i); */
decoder_stop_now((void *)dec_hld_tab[i]);
}
}
Source: decoder_api.c
设计意图:嵌入式方案中多个解码器可能静态共享同一块工作 RAM(decoder_mutual 表编译期确定各解码器的缓冲区间)。当启动解码器 index 时,框架调用其互斥函数拿到自身区间 [cw.start, cw.end),再遍历其余解码器区间 [cl.start, cl.end);若两区间相交(!(cl.start >= cw.end || cw.start >= cl.end)),说明它们会踩踏同一块内存,立即对已有解码器执行 decoder_stop_now 强制停止。这是一种轻量级、零锁的静态互斥方案——互斥关系在编译期已知,运行期只需区间比较,非常适合无 RTOS 或中断上下文的 MCU 环境。
解码启动核心流程 decoder_io
decoder_io 是解码器子系统的核心入口,负责格式探测、互斥调度、解码对象创建、音效链路搭建与启动。其签名与实现要点如下:
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));
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;
}
//启动解码时,将其他与之互斥的解码停止
decoder_mutex(dec_i);
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
启动成功后的链路搭建(源码节选):
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_curr_sound = p_next_sound;
p_curr_sound->p_obuf = cbuff_o;
p_next_sound = 0;
}
}
#endif
//硬件src
p_curr_sound->enable = 0;
#if (defined(HAS_HW_SRC_EN) || defined(HAS_SW_SRC_EN))
if (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
...
clear_dp(dbuff);
if (0 != 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);
}
dac_fade_in_api();
return p_dec;
}
Source: decoder_api.c
完整启动时序
sequenceDiagram
participant App as 应用/播放器
participant DA as decoder_api
participant FS as VFS 文件系统
participant CO as 具体解码器 (ops)
participant SM as 音效/SRC 链路
participant DAC as DAC
App->>DA: decoder_io(pfile, dec_ctl, dbuff, loop)
DA->>FS: vfs_file_name 获取文件名
DA->>DA: check_ext_api 识别 .mio 扩展名
loop 遍历 decoder_tab
DA->>DA: 检查 dec_ctl & BIT(dec_i)
DA->>DA: decoder_mutex 停止缓冲重叠的解码器
DA->>FS: vfs_seek 到文件头
DA->>CO: 调用 decoder_tab[i] open 函数
CO-->>DA: res==0 返回 dec_obj,否则尝试下一种格式
end
DA->>FS: vfs_get_attrs 获取文件长度
DA->>DA: decoder_set_file_size 设置解码文件长度
DA->>SM: 按需链接 speed_api 变速变调 (B_DEC_EFFECT)
DA->>SM: 按需链接 SRC 采样率转换 (link_src_sound)
alt 无 SRC 编译
DA->>DAC: dac_sr_api 配置 DAC 采样率
end
DA->>DA: loop!=0 时保存断点 get_dp/check_dp
DA->>DA: enable |= B_DEC_ENABLE|B_DEC_KICK|B_DEC_FIRST
DA->>DA: kick_decoder 触发 SWI0 启动解码
DA->>DAC: dac_fade_in_api 淡入防爆音
DA-->>App: 返回 dec_obj*(失败返回 NULL)
关键设计点:
- 格式探测即打开:
decoder_tab[i]的 open 函数内部先做format_check,失败返回非 0,框架随即vfs_seek回文件头尝试下一种格式;由于dec_ctl位掩码可裁剪候选集,能显著减少探测开销。 - 互斥前置:每次尝试打开前都调用
decoder_mutex(dec_i),保证同一时刻只有一组不冲突的解码缓冲在运行。 - 采样率链路:当
HAS_HW_SRC_EN/HAS_SW_SRC_EN编译且文件采样率p_dec->sr != SR_DEFAULT时,通过link_src_sound把 SRC 效果节点挂到sound链上,DAC 固定工作在SR_DEFAULT;否则直接把 DAC 采样率设为文件采样率——两种策略都保证 DAC 侧采样率确定,避免时钟失配。 - 启动时序:
B_DEC_ENABLE | B_DEC_KICK | B_DEC_FIRST三个标志位一次性置位后调用kick_decoder(),确保解码器在第一个软中断即开始工作;dac_fade_in_api()做淡入,避免启动瞬间的"咔哒"爆音。
解码事件与返回码处理 irq_decoder_ret
解码器在运行过程中通过返回码向框架汇报状态,irq_decoder_ret 负责将这些返回码转换为上层事件:
__attribute__((weak))
void midi_error_play_end_cb(dec_obj *obj, u32 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_FILE_END:
case MAD_ERROR_SYNC_LIMIT:
case MAD_ERROR_F1X_START_ADDR:
obj->sound.enable |= B_DEC_ERR;
...
Source: decoder_api.c
处理逻辑分为三支:
- MIDI 特殊分支:
MAD_ERROR_PLAY_END直接回调弱函数midi_error_play_end_cb(应用可覆盖),用于 MIDI 曲目错误播放结束的定制处理; - 事件上报:非 0 返回码按
ret & 0x0f索引obj->event_tab投递事件(MAD_ERROR_F1X_START_ADDR归一到MAD_ERROR_PLAY_END事件槽),上层消息循环据此驱动"播放结束/下一首"等业务; - 错误标志:
MAD_ERROR_FILE_END(文件播完)、MAD_ERROR_SYNC_LIMIT(同步超限)、MAD_ERROR_F1X_START_ADDR(F1A 起始地址异常)三类错误置位B_DEC_ERR,供解码状态机查询。
midi_error_play_end_cb 使用 __attribute__((weak)) 定义,体现了"框架提供默认空实现、应用按需覆盖"的扩展模式。
断点续播与循环播放
断点机制服务于两个场景:循环播放(decoder_io 的 loop 参数)与关机续播。相关接口:
get_dp(dec_obj *obj, dp_buff *dbuff):从解码器提取当前断点写入dp_buff;check_dp(dp_buff *dbuff):校验断点有效性(含 crc 校验),返回断点指针;clear_dp(dp_buff *dbuff)/clear_dp_buff(void *buff):清除断点数据。
在 decoder_io 中,当 loop != 0 时框架把解码器句柄的 loop 字段设为循环次数,并尝试 get_dp 保存起始断点到 p_dec->p_dp_buf;解码器播放到文件尾时根据断点跳回起点继续解码,从而在解码器内部完成无缝循环,无需上层干预。断点数据通过 dp_buff 的 union 按格式分配空间(F1A 60 字节、UMP3 20 字节、WAV 12 字节、MP3_ST 16 字节),由 DECODER_xxx_EN 编译开关决定实际布局。
播放控制接口与命令字
控制接口(decoder_api.h)
u32 if_decoder_is_run(dec_obj *obj);
u32 if_decoder_pause(dec_obj *obj);
void decoder_pause(dec_obj *obj);
void decoder_stop(dec_obj *obj, DEC_STOP_WAIT wait);
void decoder_stop_now(dec_obj *obj);
int decoder_fun(void *pfile, u32 dec_ctl, s32 *dec_index);
dec_obj *decoder_io(void *pfile, u32 dec_ctl, dp_buff *dbuff, u8 loop);
void decoder_ff(dec_obj *obj, u8 step); // 快进。step单位-秒
void decoder_fr(dec_obj *obj, u8 step); // 快退。step单位-秒
void decoder_set_file_size(dec_obj *obj, u32 size); // 设置解码文件长度
void decoder_soft_hook(void);
Source: decoder_api.h
decoder_fun:事件驱动型启动入口,返回解码索引并配合消息循环使用;decoder_io:同步型启动入口,直接返回dec_obj*;decoder_stop:带DEC_STOP_WAIT语义的停止——NO_WAIT立即停止,NEED_WAIT等待 DAC 中残留样点播完,避免截断爆音;decoder_stop_now:强制立即停止,供decoder_mutex等内部互斥场景使用;decoder_ff/decoder_fr:快进/快退,step单位为秒;decoder_soft_hook:软中断钩子,供主循环或定时器周期调用以推进解码。
解码器配置命令字(if_decoder_ctrl.h)
#define SET_DECODE_MODE 0x80
#define SET_FILE_TOTAL_LEN 0x84
#define SET_FF_FR_STEP_CMD 0x94
enum {
SET_BREAKPOINT_A = 0x08,
SET_BREAKPOINT_B,
SET_RECOVER_MODE
};
#define CMD_SET_CONTINUE_BK 0x90
#define CMD_SET_PLAY_FILE 0x91
#define CMD_SET_FADEOUT 0x93
//play control
#define PLAY_FILE 0x80000000
#define PLAY_CONTINUE 0x80000001
#define PLAY_NEXT 0x80000002
Source: if_decoder_ctrl.h
这些命令字通过 audio_decoder_ops.dec_confing(work_buf, cmd, parm) 下发给解码器,配套参数结构包括 AUDIO_DECODE_PARA(mode)、PARM_DECODE_STEPV(ff_fr_step)、AUDIO_FLEN_PARA(flen)、AUDIO_FADE_PARA(mode)以及 EX_PlayFile_STRUCT(set_play_file 回调)。播放模式常量 PLAY_MOD_NORMAL(0x00)、PLAY_MOD_FF(0x01)、PLAY_MOD_FB(0x02)与重复模式 REAPT_MOD_STARTA / STARTB / STARTN / FREPT 共同刻画单曲/区间/列表的播放语义。SET_BREAKPOINT_A/B 支持设置 A-B 两个断点区间,配合 CMD_SET_CONTINUE_BK 实现断点续播控制。
配置选项
下表汇总了解码器子系统相关的编译期配置宏(定义于工程配置/decoder_api.h 条件编译中):
| 宏 | 类型 | 默认行为 | 说明 |
|---|---|---|---|
MAX_F1A_CHANNEL | int | 1 | 大于 1 时启用 D_TYPE_F1A_2 第二 F1A 通道,decoder_tab 增加 F1A2 条目 |
DECODER_UMP3_EN | bool | 0 | 编译 UMP3 解码器,决定 dp_buff.ump3[20] 与 UMP3_LST/API/MUT_TAB 条目 |
DECODER_MP3_ST_EN | bool | 0 | 编译标准 MP3 解码器,决定 dp_buff.mp3[0x10] 与 MP3_ST_* 条目 |
DECODER_F1A_EN | bool | 0 | 编译 F1A 解码器,决定 dp_buff.f1a[60] 与 F1A*_* 条目 |
DECODER_WAV_EN | bool | 0 | 编译 WAV 解码器,决定 dp_buff.wav[12] 与 WAV_* 条目 |
DEC_FUNCTION_FF_FR | bit | BIT(0) | 是否支持快进快退功能标志 |
AUDIO_SPEED_EN | bool | 0 | 编译变速变调模块,dec_ctl & BIT_SPEED 时串联 speed_api |
HAS_HW_SRC_EN / HAS_SW_SRC_EN | bool | 0 | 编译硬件/软件 SRC;启用且 sr != SR_DEFAULT 时走 link_src_sound 链路 |
HAS_MIO_EN | bool | 0 | 编译 MIO 混音/叠加功能,decoder_io 中通过 vfs_openbyfile(...,"mio") 打开伴音 |
IRQ_DECODER_IP | int | 平台定义 | SWI0 软中断优先级,decoder_init 注册 decoder_soft0_isr 时使用 |
decoder_tab、dec_hld_tab、decoder_mutual 三表的条目由上述 *_EN 宏展开的 *_LST / *_API / *_MUT_TAB 宏自动生成,因此裁剪某个格式后,框架循环的探测范围与互斥表同步收缩,无需修改框架代码。
使用示例
示例 1:同步启动解码并返回解码对象
上层播放器打开文件后,将文件句柄、解码控制位(BIT(D_TYPE_UMP3) 等格式掩码)与断点缓冲传入 decoder_io,得到解码对象后即可执行快进快退等控制:
// decoder_io 签名:按 dec_ctl 位掩码探测格式,返回解码对象或 NULL
dec_obj *decoder_io(void *pfile, u32 dec_ctl, dp_buff *dbuff, u8 loop);
// 快进/快退,step 单位为秒
void decoder_ff(dec_obj *obj, u8 step);
void decoder_fr(dec_obj *obj, u8 step);
// 停止解码;NEED_WAIT 表示等待 DAC 残留样点播完
void decoder_stop(dec_obj *obj, DEC_STOP_WAIT wait);
Source: decoder_api.h
示例 2:格式探测与互斥调度的调用约定
decoder_io 内部的循环体现了框架对格式解码器的调用约定——open 函数返回 0 表示该格式匹配成功,非 0 则回退文件指针尝试下一种格式;每次尝试前先执行互斥调度:
for (dec_i = 0; dec_i < (sizeof(decoder_tab) / 4); dec_i++) {
if (0 == (dec_ctl & BIT(dec_i))) {
continue;
}
//启动解码时,将其他与之互斥的解码停止
decoder_mutex(dec_i);
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
示例 3:新增一种格式解码器所需实现的操作集
任何新格式解码器只需实现 audio_decoder_ops 并导出 get_xxx_ops(),即可被框架的 decoder_tab 机制接纳:
typedef struct __audio_decoder_ops {
char *name;
u32(*open)(void *work_buf, const struct if_decoder_io *decoder_io, u8 *bk_point_ptr);
u32(*format_check)(void *work_buf);
u32(*run)(void *work_buf, u32 type);
dec_inf_t *(*get_dec_inf)(void *work_buf);
u32(*get_playtime)(void *work_buf);
u32(*get_bp_inf)(void *work_buf);
u32(*need_dcbuf_size)();
u32(*need_bpbuf_size)();
u32(*dec_confing)(void *work_buf, u32 cmd, void *parm);
} audio_decoder_ops, decoder_ops_t;
Source: if_decoder_ctrl.h
API 参考
void decoder_init(void)
清理解码句柄表、设置解码通道数并注册 SWI0 软中断。系统启动阶段调用一次。
dec_obj *decoder_io(void *pfile, u32 dec_ctl, dp_buff *dbuff, u8 loop)
同步启动解码的主入口。
参数:
pfile(void*):VFS 文件句柄;dec_ctl(u32):允许探测的解码器位掩码(BIT(D_TYPE_*)),可叠加BIT_SPEED等能力位;dbuff(dp_buff*):断点缓冲,用于循环/续播场景;无断点需求可传空并经check_dp校验;loop(u8):非 0 表示循环播放,同时保存起始断点。
返回: 解码对象 dec_obj*;格式不匹配或 .mio 文件(由 check_ext_api 提前拦截)返回 NULL。
int decoder_fun(void *pfile, u32 dec_ctl, s32 *dec_index)
事件驱动型解码入口,返回解码索引,配合消息循环使用。
void decoder_stop(dec_obj *obj, DEC_STOP_WAIT wait)
停止解码。wait 为 NO_WAIT 时立即停止;NEED_WAIT 时等待 DAC 中残留样点消耗完。
void decoder_stop_now(dec_obj *obj)
强制立即停止解码,供 decoder_mutex 互斥调度内部调用。
u32 if_decoder_is_run(dec_obj *obj) / u32 if_decoder_pause(dec_obj *obj) / void decoder_pause(dec_obj *obj)
查询解码器是否运行 / 暂停。if_ 前缀为状态查询,decoder_pause 执行暂停。
void decoder_ff(dec_obj *obj, u8 step) / void decoder_fr(dec_obj *obj, u8 step)
快进/快退,step 单位为秒。是否可用受 DEC_FUNCTION_FF_FR(BIT(0))控制。
void decoder_set_file_size(dec_obj *obj, u32 size)
设置解码文件长度,供解码器计算播放进度与文件尾判断(decoder_io 中由 vfs_get_attrs 获取后调用)。
u32 decoder_set_sr(dec_obj *d_obj)
根据解码对象采样率配置采样率链路,返回处理结果。
断点函数
bool get_dp(dec_obj *obj, dp_buff *dbuff):提取当前断点;void *check_dp(dp_buff *dbuff):校验断点有效性并返回断点指针;void clear_dp(dp_buff *dbuff)/void clear_dp_buff(void *buff):清除断点数据。
中断与钩子
void decoder_soft0_isr(void):SWI0 软中断处理函数,由decoder_init注册到IRQ_SOFT0_IDX;void decoder_soft_hook(void):软中断钩子,供外部周期调用以推进解码;void irq_decoder_ret(dec_obj *obj, u32 ret):解码返回码处理,向event_tab投递事件;void midi_error_play_end_cb(dec_obj *obj, u32 ret):弱函数,MIDI 错误播放结束回调,应用可覆盖。
Throws / 错误码: 解码器 open 失败返回非 0(框架以 E_DECODER 作为初始错误值),运行期通过 MAD_ERROR_FILE_END、MAD_ERROR_SYNC_LIMIT、MAD_ERROR_F1X_START_ADDR、MAD_ERROR_PLAY_END 等返回码上报,经 irq_decoder_ret 转为事件与 B_DEC_ERR 标志。
失败模式、边界情况与并发
失败模式
| 场景 | 触发条件 | 处理方式 |
|---|---|---|
| 格式不识别 | 所有候选解码器 open 均失败 | decoder_io 返回 NULL,打印 decode err : 0x%x,关闭已打开的 MIO 文件 |
| 文件播完 | 解码器返回 MAD_ERROR_FILE_END | irq_decoder_ret 置 B_DEC_ERR 并向 event_tab 投递事件,上层切下一首 |
| 同步超限 | 流损坏导致 MAD_ERROR_SYNC_LIMIT | 按错误处理,置 B_DEC_ERR 并上报事件 |
| F1A 起始地址异常 | MAD_ERROR_F1X_START_ADDR | 归一到 MAD_ERROR_PLAY_END 事件槽上报 |
| MIDI 曲目异常结束 | MAD_ERROR_PLAY_END | 调用弱回调 midi_error_play_end_cb(默认空实现) |
| MIO 伴音打开失败 | vfs_openbyfile 失败 | mio_res != 0,跳过 MIO 链接,不影响主解码 |
边界情况
.mio文件拦截:decoder_io先vfs_file_name取文件名,check_ext_api(..., ".mio", 4)命中时直接返回NULL,避免把 MIO 伴音文件当作主音频解码;- 断点校验:
check_dp内含 crc 校验,损坏的断点会被拒绝,decoder_io转而从文件头开始解码; - 无 SRC 场景:未编译
HAS_HW_SRC_EN/HAS_SW_SRC_EN时调用dac_sr_api(p_dec->sr)动态重配 DAC 采样率,文件切换时存在采样率切换开销; decoder_mutex越界保护:index >= max_loop时直接返回,防止非法索引越界访问decoder_mutual。
并发模型
解码子系统是"中断驱动 + 主循环控制"的并发模型:
- 解码推进发生在 SWI0 软中断(
decoder_soft0_isr,优先级IRQ_DECODER_IP)上下文中,kick_decoder()(bit_set_swi0)触发; - 上层控制(启动/停止/快进快退)运行在普通线程/主循环中,与软中断通过
dec_obj的enable标志位(B_DEC_ENABLE、B_DEC_KICK、B_DEC_FIRST、B_DEC_ERR、B_DEC_EFFECT)交互; - 多解码器之间的并发冲突不是用锁解决,而是用
decoder_mutex的静态缓冲区间重叠检测在启动前"先停后启",属于嵌入式场景典型的无锁互斥策略; decoder_stop_now可在中断上下文安全调用,因此decoder_mutex能在启动路径上强制回收被占用的解码缓冲。
性能与运维注意事项
- 热数据缓存:
dec_hld_tab显式放置于.audio_d.text.cache.L2段,确保解码高频访问的句柄表命中 L2 缓存,降低中断路径延迟; - 格式探测开销:
decoder_io按dec_ctl位掩码逐格式尝试,业务方应精确置位候选格式位以缩短启动探测时间; - 采样率切换:无 SRC 编译时切换不同采样率文件会触发
dac_sr_api重配,频繁切换建议启用 SRC 链路让 DAC 固定在SR_DEFAULT; - 停止策略:
decoder_stop的NEED_WAIT模式牺牲少量响应时间换取无爆音停止,适合音乐播放;即时响应场景(如闹钟打断)应使用NO_WAIT; - 日志:框架使用
log_info("decode succ/err")与decoder ret : 0x%x输出关键路径日志,排障时可通过LOG_TAG "[normal]"过滤。
扩展点
- 新增格式解码器:实现
audio_decoder_ops全部回调、导出get_xxx_ops(),并在工程配置中定义对应DECODER_XXX_EN宏以展开XXX_LST/XXX_API/XXX_MUT_TAB三个登记宏——框架循环、互斥表与断点 union 布局全部自动适配; - 覆盖弱回调:重定义
midi_error_play_end_cb处理 MIDI 错误播放结束的定制业务; - 音效链扩展:
sound_out_obj的effect字段支持串联自定义效果节点(speed_api即通过该机制挂接),可参考B_DEC_EFFECT标志的用法接入新算法; - MIO 伴音:
HAS_MIO_EN编译时可通过d_mio_open+mio_a_hook_init挂接混音伴音通道。
测试
仓库中解码器框架以静态库形式发布(decoder_mge_lib.a),核心算法(UMP3/MIDI/WAV 编解码)不开放源码;框架层 decoder_api.c 提供 decoder_test_fun(void) 测试入口用于驱动解码验证。实际行为验证依赖 decoder_io 返回码、irq_decoder_ret 事件日志与 decode succ / decode err 打印,可在板级集成测试中按"启动 → 播放 → 快进 → 停止"顺序回归验证。
Related Links
- 解码器操作集与命令字定义:if_decoder_ctrl.h
- 解码框架头文件:decoder_api.h
- 解码框架实现:decoder_api.c
- 解码消息表:decoder_msg_tab.c 与 decoder_msg_tab.h
- 解码器管理:decoder_mge.h
- 解码点表:decoder_point.c
- 相关主题:DAC 输出与采样率配置(
dac_api.h/dac_sr_api)、音效与变速变调(sound_effect_api.h/speed_api)、文件系统(vfs.h)。