音频解码框架与调度
音频解码框架与调度是 AD16N 系列 MCU SDK 音频子系统的中枢,通过"句柄表 + API 函数表"的消息驱动机制,统一调度 F1A、UMP3、A、MIDI、WAV、MP3_ST、OPUS、IMA、SPEEX、SBC、JLA_LW 等多种音频解码器,并提供播放控制、断点续播、快进快退、A-B 复读等完整能力。
Purpose and Scope
本页面向有嵌入式音频开发经验的工程师,完整说明 AD16N 音频解码框架的架构分层、解码器注册与调度机制、播放控制协议、断点机制、输入输出数据通路与配置方式。
本页覆盖的内容:
- 解码器类型枚举、句柄表
dec_hld_tab与 API 表decoder_tab的构成与调度原理 decoder_msg_tab.h中基于条件编译的解码器登记宏体系- 播放控制命令协议(
if_decoder_ctrl.h):播放/继续/切歌、快进快退、A-B 复读、断点、跳转播放 - 断点缓冲
dp_buff与get_dp/check_dp/clear_dp生命周期 - 解码框架对外统一 API(
decoder_api.h)及内存段布局 - 相关配置宏(
DECODER_XXX_EN、MAX_F1A_CHANNEL等)与开关
不覆盖的内容(属于兄弟页面):
- 具体解码器的内部算法实现(如
ump3_api.h、wav_api.h等解码库细节) - DAC/音频输出链路(
audio_dac_api.h、audio_eq.h等,属于音频输出子系统) - 文件系统与媒体源读取(VFS 层)
Overview
在 AD16N 这种资源受限的 MCU 平台上,解码框架的设计目标是:在极小的 RAM/Flash 开销下,用一套统一接口调度多个异构解码器。为此框架采用了典型的"表驱动 + 消息分发"架构:
- 表驱动注册:每种解码器通过
decoder_msg_tab.h中的宏(如UMP3_LST、UMP3_API)把自己的句柄对象地址(_hld)和解码 API 函数指针(_api)登记到框架的两张全局表dec_hld_tab[]与decoder_tab[]中。框架不关心解码器内部实现,只按索引访问表项。 - 索引位掩码调度:
INDEX_XXX枚举为每种解码器分配固定索引,BIT_XXX位掩码用于在"同时支持多种格式"的场景下快速判断某解码器是否启用。索引 12/13/14 被预留给SPEED(变速)、EQ(均衡器)、SRC_FORCE(重采样强制),说明框架把音频后处理也纳入了同一调度体系。 - 条件编译裁剪:所有解码器都以
DECODER_XXX_EN宏控制是否编译进固件。禁用某解码器时,其_LST/_API/_MUT_TAB宏展开为空,表自动收缩,零运行时开销。
框架对外暴露的入口包括:decoder_fun()(按文件启动解码)、decoder_io()(带断点/循环的启动)、decoder_list()(带数据流/输出采样率的启动)、decoder_pause/stop/ff/fr(播放控制)、mp_input/mp_output(数据通路)以及断点系列函数。这些函数均实现于 sdk/apps/app/bsp/common/decoder/decoder_api.c,配套的调度点 decoder_point.c 与消息表实现 decoder_msg_tab.c 位于同一目录。
Architecture
flowchart TD
subgraph sg_App["应用层"]
App["应用模块 / 播放器逻辑"]
end
subgraph sg_API["解码框架 API 层 (decoder_api.c)"]
DInit["decoder_init / decoder_channel_set"]
DFun["decoder_fun / decoder_io / decoder_list"]
DCtrl["decoder_pause / stop / ff / fr / status"]
DDP["get_dp / check_dp / clear_dp (断点)"]
DIO["mp_input / mp_output (数据通路)"]
end
subgraph sg_Tables["调度表 (dec_hld_tab / decoder_tab)"]
TBL["句柄表 dec_hld_tab[]"]
TAPI["API 函数表 decoder_tab[]"]
TMUT["互斥/缓冲表 xxx_MUT_TAB"]
end
subgraph sg_Decoders["解码器实例 (decoder_msg_tab.h 登记)"]
D1["F1A / UMP3"]
D2["A / MIDI / MIDI_CTRL"]
D3["WAV / MP3_ST / OPUS"]
D4["IMA / SPEEX / SBC / JLA_LW"]
end
subgraph sg_HW["底层硬件/外设"]
DAC["音频 DAC (audio_dac)"]
SRAM["SRAM / 缓存段 L1"]
end
App -->|"dec_ctl 命令 + 文件"| DFun
App -->|"播放控制"| DCtrl
App -->|"断点读写"| DDP
DFun -->|"按 INDEX_XXX 查表"| TBL
TBL -->|"句柄地址"| D1
TAPI -->|"解码 API 指针"| D1
TMUT --> D1
D1 -->|"PCM 输出"| DIO
DIO --> DAC
DInit --> DAC
D1 -->|"运行于缓存段"| SRAM
架构说明:应用层通过 decoder_fun/decoder_io/decoder_list 等统一入口发起解码请求,框架根据文件格式/控制字(dec_ctl)在 dec_hld_tab 中定位解码器句柄,并通过 decoder_tab 中的函数指针调用对应解码器的解码 API。解码器产出的 PCM 数据经 mp_output 送往 DAC 链路(可经 audio_eq、src_api、pcm_eq 等后处理,见 decoder_api.c 的条件包含)。断点数据(dp_buff)独立于音频流,支持暂停/停止时保存、恢复时续播。整张调度表被放置于 AT(.docoder_mge.text.cache.L1) 缓存段以加速访问。
解码器注册机制:宏登记体系
类型枚举与索引分配
每种解码器在系统中拥有双重身份:一是运行时的类型枚举(DECOER_TYPE),二是调度索引(INDEX_XXX)。
decoder_api.h 定义了解码器类型枚举,其中 D_TYPE_F1A_1 固定为 0,D_TYPE_F1A_2 仅在 MAX_F1A_CHANNEL > 1 时存在——这是双通道 F1A 场景下为第二个通道预留的类型位:
typedef enum {
D_TYPE_F1A_1 = 0,
#if (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 = 7,
D_TYPE_OPUS = 8,
D_TYPE_IMA,
D_TYPE_SPEEX,
D_TYPE_SBC,
D_TYPE_JLA_LW,
} DECOER_TYPE ;
decoder_msg_tab.h 中的索引枚举则是调度用的"槽位",全部用条件编译包裹,保证未启用的解码器不占用索引:
enum {
#if DECODER_F1A_EN
INDEX_F1A1 = 0,
#if (MAX_F1A_CHANNEL > 1)
INDEX_F1A2,
#endif
#endif
#if DECODER_UMP3_EN
INDEX_UMP3,
#endif
#if DECODER_A_EN
INDEX_A,
#endif
#if DECODER_MIDI_EN
INDEX_MIDI,
#endif
#if DECODER_MIDI_KEYBOARD_EN
INDEX_MIDI_CTRL,
#endif
#if DECODER_WAV_EN
INDEX_WAV,
#endif
#if DECODER_MP3_ST_EN
INDEX_MP3_ST,
#endif
#if DECODER_OPUS_EN
INDEX_OPUS,
#endif
#if DECODER_IMA_EN
INDEX_IMA,
#endif
#if DECODER_SPEEX_EN
INDEX_SPEEX,
#endif
#if DECODER_SBC_EN
INDEX_SBC,
#endif
#if DECODER_JLA_LW_EN
INDEX_JLA_LW,
#endif
INDEX_E_SPEED = 12,
INDEX_E_EQ = 13,
INDEX_E_SRC_FORCE = 14,
};
注意索引 12/13/14 被固定预留给 SPEED(变速播放)、EQ(均衡器)、SRC_FORCE(强制重采样),对应位掩码 BIT_SPEED、BIT_EQ、BIT_SRC_FORCE。这表明框架将"音频后处理单元"与"解码器"统一视为可调度对象——这解释了 decoder_api.c 中为何会条件包含 song_speed_api.h、audio_eq.h、pcm_eq_api.h、src_api.h:它们与解码器共用同一张调度位图。
登记宏:_HLD / _LST / _API / _MUT_TAB
每种解码器在 decoder_msg_tab.h 中定义四个登记宏,分别对应句柄、列表项、API 指针、互斥缓冲表:
#if DECODER_MP3_ST_EN
#include "mp3_standard_api.h"
#define MP3_ST_HLD (u32)&dec_mp3_st_hld
#define MP3_ST_LST MP3_ST_HLD,
#define MP3_ST_API (u32)mp3_st_decode_api,
#define MP3_ST_MUT_TAB (u32)mp3_st_buff_api,
#define MP3_ST_PARM_SET (u32)NULL,
#define BIT_MP3_ST BIT(INDEX_MP3_ST)
#else
#define MP3_ST_HLD (u32)NULL
#define MP3_ST_LST
#define MP3_ST_API
#define MP3_ST_MUT_TAB
#define MP3_ST_PARM_SET
#define BIT_MP3_ST 0
#endif
该宏体系的设计意图:
- 启用时(
DECODER_MP3_ST_EN):MP3_ST_HLD指向解码器全局句柄dec_mp3_st_hld,MP3_ST_LST把该句柄带逗号地拼进dec_hld_tab[]初始化列表,MP3_ST_API把解码函数指针mp3_st_decode_api拼进decoder_tab[],MP3_ST_MUT_TAB登记解码器的缓冲区/互斥 API。 - 禁用时:全部宏展开为空(
MP3_ST_HLD为NULL但不用),BIT_MP3_ST为 0,调度位图对应位恒为 0,表项自动消失——固件体积与 RAM 开销随配置自动收缩,无需手工维护表长度。
两张核心调度表
decoder_api.c 通过宏展开生成两张全局表。句柄表 dec_hld_tab[] 是解码器对象地址的集合,被放置在 L1 缓存段以加速访问:
AT(.docoder_mge.text.cache.L1)
u32 dec_hld_tab[] = {
F1A1_LST
F1A2_LST
UMP3_LST
A_LST
MIDI_LST
MIDI_CTRL_LST
WAV_LST
MP3_ST_LST
OPUS_LST
IMA_LST
SPEEX_LST
SBC_LST
JLA_LW_LST
/* 0, */
};
API 函数表 decoder_tab[] 与句柄表一一对应,存放各解码器的解码 API 函数指针:
const u32 decoder_tab[] = {
F1A1_API
F1A2_API
UMP3_API
A_API
MIDI_API
MIDI_CTRL_API
WAV_API
...
};
调度原理:dec_hld_tab[INDEX_XXX] 给出解码器句柄(dec_obj 指针),decoder_tab[INDEX_XXX] 给出解码入口函数。框架通过"格式探测 → 索引定位 → 查表取函数指针"三步完成解码器选择,整个过程是纯数组访问,无分支判断,对 MCU 的指令缓存非常友好。句柄表声明为 u32 数组(非 dec_obj* 数组)则避免了不同解码器对象结构大小不一带来的对齐问题。
调度与控制流
统一入口:decoder_fun / decoder_io / decoder_list
框架为不同使用场景提供了三个启动入口(均在 decoder_api.h 中声明):
| 函数 | 适用场景 | 关键参数 |
|---|---|---|
decoder_fun(pfile, dec_ctl, &dec_index) | 按文件句柄启动,返回解码索引 | dec_ctl 控制字、dec_index 输出解码器索引 |
decoder_io(pfile, dec_ctl, dbuff, loop) | 支持断点(dp_buff*)与循环播放的启动 | dbuff 断点缓冲、loop 循环标志 |
decoder_list(p_strm, dec_ctl, dbuff, loop, output_sr) | 面向数据流(非文件)的启动,可指定输出采样率 | dec_data_stream* 流对象、output_sr |
u32 if_decoder_is_run(dec_obj *obj);
bool decoder_pause(dec_obj *obj);
bool decoder_stop(dec_obj *obj, IS_WAIT dec_stop_wait, void *p_dp);
bool decoder_stop_phy(dec_obj *obj, IS_WAIT DEC_STOP_WAIT, void *p_dp, bool fade, bool(*unregist_func)(void *));
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);
int decoder_time(dec_obj *p_dec);
u32 decoder_status(dec_obj *obj);
bool decoder_ff(dec_obj *obj, u8 step); // 快进。step单位-秒
bool decoder_fr(dec_obj *obj, u8 step); // 快退。step单位-秒
设计意图分析:
decoder_stop的第二个参数是IS_WAIT(是否等待停止完成),第三个参数p_dp用于回填断点——停止即断点采集点,这是断点续播的实现基础。decoder_stop_phy比decoder_stop多出fade(淡出)与unregist_func(注销回调)参数,用于需要 DAC 淡出或取消注册的精细控制场景(如切歌防爆音)。decoder_ff/decoder_fr的步进单位是秒,由解码器层负责换算为帧/字节偏移,上层无需关心具体格式的帧结构。
播放控制命令协议
if_decoder_ctrl.h 定义了播放器控制字(dec_ctl)协议。控制字采用高位标志 + 低位枚举的编码方式:
#define SET_DECODE_MODE 0x80
#define CMD_SET_CONTINUE_BK 0x90
#define CMD_SET_PLAY_FILE 0x91
#define CMD_SET_SAMPLE 0x92
#define CMD_SET_FADEOUT 0x93
#define PLAY_MOD_NORMAL 0x00
#define PLAY_MOD_FF 0x01
#define PLAY_MOD_FB 0x02
//play control
#define PLAY_FILE 0x80000000
#define PLAY_CONTINUE 0x80000001
#define PLAY_NEXT 0x80000002
#define REAPT_MOD_STARTA 0x01
#define REAPT_MOD_STARTB 0x02
#define REAPT_MOD_STARTN 0x03
#define REAPT_MOD_FREPT 0x04
关键设计:
PLAY_FILE / PLAY_CONTINUE / PLAY_NEXT为0x8000_0000起的高位编码,与低位命令(如0x80的SET_DECODE_MODE)天然区分,命令解析时可用(cmd & 0x80000000)快速分流。PLAY_MOD_FF/FB(快进/快退模式)与SET_DECODE_MODE组合使用,配合FAST_FREQ_restrict / FAST_FILTER_restrict / FAST_CHANNEL_restrict三个限制位(0x01/0x02/0x04),在快进快退时主动降频/降滤波/降通道以降低 CPU 占用——这是 MCU 平台典型的"解码质量-算力权衡"策略。
断点与特殊播放控制
断点机制由 dp_buff 承载,其设计极具 MCU 特色——用联合体复用同一块内存保存不同格式的断点数据:
typedef struct _dp_buff {
union {
u32 findex;
u32 sclust;
};
u16 crc;
u16 len;
union {
u8 buff[1];
#if DECODER_UMP3_EN
u8 ump3[20];
#endif
#if DECODER_MP3_ST_EN
u8 mp3[0x10];
#endif
#if DECODER_F1A_EN
u8 f1a[60];
#endif
#if DECODER_WAV_EN
u8 wav[12];
#endif
};
} dp_buff;
断点结构说明:
findex/sclust联合体:保存文件索引或 Flash 簇号,用于定位断点所在的媒体文件位置;crc+len:断点数据的完整性校验与长度,check_dp()据此判断断点是否有效;buff[1]与各格式定长数组(ump3[20]、mp3[0x10]、f1a[60]、wav[12])联合:不同解码器的断点载荷尺寸差异巨大(F1A 需要 60 字节、WAV 只需 12 字节),联合体让dp_buff只占用最大者的空间,同时允许按格式访问。buff[1]是"灵活数组成员"惯用法,调用方按len实际分配。
断点生命周期由四个函数管理(decoder_api.h#L71-L74):
bool get_dp(dec_obj *obj, dp_buff *dbuff):从运行中的解码器采集断点;void *check_dp(dp_buff *dbuff):校验断点有效性(crc),返回解码器句柄或 NULL;void clear_dp(dp_buff *dbuff)/void clear_dp_buff(void *buff):清除断点。
配合 if_decoder_ctrl.h 中的 SET_BREAKPOINT_A(0x08) / SET_BREAKPOINT_B(0x09) / SET_RECOVER_MODE(0x0A) 与 AB_REPEAT_MODE_BP_A/BP_B/CUR,可构造完整的 A-B 复读 能力:A 点断点 + B 点断点 + 恢复模式。audio_ab_repeat_mode_param 结构体携带回调(callback(priv, mode)),实现复读循环完成时的应用层通知:
typedef struct _audio_ab_repeat_mode_param {
u32 value;
int (*callback)(void *priv, int mode);
void *callback_priv;
} audio_ab_repeat_mode_param;
跳转播放通过 PARM_DESTTIME 实现:start_time(当前时间)→ dest_time(目标时间)的跳转,并支持完成回调 callbackfun(priv);SET_DEST_PLAYPOS(0x93) / GET_PLAYPOS(0x94) 提供按时间定位与查询。此外 CMD_SET_GOON_CALLBACK(0x95) + GoOn_DEC_CallBack 允许解码库在"本次 input 可取字节过少"时跳过本次 run,避免空转:
#define CMD_SET_GOON_CALLBACK 0x95
typedef struct _GoOn_DEC_CallBack_ {
void *priv;
int (*callback)(void *priv);//回调告知解码库本次input能取多少字节数据,数据过少跳过本次run
} GoOn_DEC_CallBack;
Core Flow:播放请求的完整生命周期
下图展示从应用层发起播放到 PCM 输出的完整时序(基于 decoder_fun/decoder_io 与 mp_input/mp_output 的真实调用链):
sequenceDiagram
participant App as 应用层(播放器)
participant API as 解码框架(decoder_api)
participant TBL as 调度表(dec_hld_tab/decoder_tab)
participant DEC as 具体解码器(f1a/ump3/wav...)
participant DAC as 音频输出链路(DAC)
App->>API: decoder_fun(pfile, dec_ctl, &index)
activate API
API->>API: 解析 dec_ctl (PLAY_FILE/PLAY_CONTINUE/PLAY_NEXT)
API->>TBL: 按 INDEX_XXX 查句柄与 API 指针
TBL-->>API: dec_obj* + decode_api
API->>DEC: 调用 decode_api 完成格式初始化
DEC-->>API: 解码器就绪
API-->>App: 返回解码索引 / dec_obj*
deactivate API
loop 解码运行循环
App->>API: mp_input(priv, addr, buf, len, type)
API->>DEC: 喂入压缩数据
DEC->>DEC: 解码一帧
DEC-->>API: mp_output(priv, data, len)
API->>DAC: 写 PCM (可经 eq/src/变速后处理)
end
App->>API: decoder_pause(obj) / decoder_stop(obj, wait, &dp)
API->>DEC: 暂停/停止解码
API->>API: get_dp(obj, &dp_buff) 采集断点
API-->>App: 停止完成 + 断点数据
关键时序说明:
- 启动阶段:
decoder_fun解析dec_ctl控制字后,通过索引查两张调度表得到解码器句柄与 API 入口,随即调用解码 API 完成格式探测与初始化——启动即完成"解码器选择"。 - 运行阶段:解码在循环中由数据驱动。
mp_input把媒体数据喂给解码库,解码库产出 PCM 后经mp_output输出。decoder_soft0_isr(软中断)与irq_decoder_ret(中断返回)参与数据搬运的节拍控制;decoder_soft_hook提供软钩子让上层介入每一轮解码。kick_decoder_api(p_stream_in, psound)用于在流模式下"踢"一下解码器,推动其继续取数。 - 停止阶段:
decoder_stop带IS_WAIT语义等待解码完全停止,同时get_dp从解码器采集断点写入dp_buff,为下次decoder_io(dbuff, loop)续播做准备。若配置了淡出(decoder_stop_phy的fade=true),停止路径还会经由audio_dac_fade平滑衰减,避免爆音。
Configuration Options
解码框架的配置全部通过编译期宏完成,宏定义在 config.h/app_config.h 等配置头中,由 decoder_api.c、decoder_msg_tab.h 在编译期消费。
| 配置宏 | 类型 | 默认/取值 | 作用 |
|---|---|---|---|
DECODER_F1A_EN | 布尔宏 | 0/1 | 启用 F1A 解码器(杰理私有语音格式) |
MAX_F1A_CHANNEL | 整数 | ≥1 | F1A 通道数;>1 时编译 D_TYPE_F1A_2/INDEX_F1A2 |
DECODER_UMP3_EN | 布尔宏 | 0/1 | 启用 UMP3(微型 MP3)解码器 |
DECODER_A_EN | 布尔宏 | 0/1 | 启用 A 格式解码器 |
DECODER_MIDI_EN | 布尔宏 | 0/1 | 启用 MIDI 解码器 |
DECODER_MIDI_KEYBOARD_EN | 布尔宏 | 0/1 | 启用 MIDI 键盘/控制器模式(INDEX_MIDI_CTRL) |
DECODER_WAV_EN | 布尔宏 | 0/1 | 启用 WAV 解码器 |
DECODER_MP3_ST_EN | 布尔宏 | 0/1 | 启用标准 MP3 解码器(mp3_standard_api) |
DECODER_OPUS_EN | 布尔宏 | 0/1 | 启用 OPUS 解码器 |
DECODER_IMA_EN | 布尔宏 | 0/1 | 启用 IMA-ADPCM 解码器 |
DECODER_SPEEX_EN | 布尔宏 | 0/1 | 启用 Speex 解码器 |
DECODER_SBC_EN | 布尔宏 | 0/1 | 启用 SBC 解码器(蓝牙音频场景) |
DECODER_JLA_LW_EN | 布尔宏 | 0/1 | 启用 JLA 轻量格式解码器 |
HAS_SONG_SPEED_EN | 布尔宏 | 0/1 | 编译变速播放支持(对应 INDEX_E_SPEED/BIT_SPEED) |
HAS_SRC_EN | 布尔宏 | 0/1 | 编译采样率转换支持(src_api,对应 BIT_SRC_FORCE) |
HAS_MIO_EN | 布尔宏 | 0/1 | 编译 MIO(多路 IO)支持 |
AUDIO_HW_EQ_EN / PCM_SW_EQ_EN | 布尔宏 | 0/1 | 硬件/软件均衡器(对应 INDEX_E_EQ/BIT_EQ) |
DEC_FUNCTION_FF_FR | 位宏 | BIT(0) | 快进快退功能开关(decoder_api.h 功能位) |
DEC_FORMAT_CHECK_FIX | 位宏 | BIT(1) | MP3 格式检查修复开关 |
设计意图:所有解码器开关集中在配置头,decoder_api.c 通过 #if DECODER_XXX_EN #include "xxx_api.h" #endif 做条件包含(见其 L22-L61),decoder_msg_tab.h 再做条件登记。这套"配置宏 → 条件包含 → 宏登记 → 表收缩"的流水线,使固件开发者只需改一个宏即可增减一种格式,无需触碰框架调度代码。
API Reference
解码生命周期
int decoder_fun(void *pfile, u32 dec_ctl, s32 *dec_index)
按文件启动解码。
- 参数:
pfile— 文件句柄(VFS);dec_ctl— 控制字(PLAY_FILE等);dec_index— 输出:选中的解码器索引。 - 返回:错误码(
errno-base.h),0 表示成功。
dec_obj *decoder_io(void *pfile, u32 dec_ctl, dp_buff *dbuff, u8 loop)
带断点/循环的文件解码启动。
- 参数:
dbuff— 断点缓冲(NULL 表示从头播);loop— 非 0 循环播放。 - 返回:解码器对象
dec_obj*,失败返回 NULL。
dec_obj *decoder_list(dec_data_stream *p_strm, u32 dec_ctl, dp_buff *dbuff, u8 loop, u32 output_sr)
面向数据流(非文件)的解码启动,可指定输出采样率。
- 参数:
p_strm— 数据流描述符;output_sr— 输出采样率(Hz)。 - 返回:
dec_obj*或 NULL。
播放控制
| 函数 | 说明 |
|---|---|
bool decoder_pause(dec_obj *obj) | 暂停解码 |
bool decoder_stop(dec_obj *obj, IS_WAIT wait, void *p_dp) | 停止解码;p_dp 回填断点 |
bool decoder_stop_phy(dec_obj *obj, IS_WAIT wait, void *p_dp, bool fade, bool(*unregist)(void*)) | 物理停止,支持淡出与注销回调 |
bool decoder_ff(dec_obj *obj, u8 step) / decoder_fr(obj, step) | 快进/快退,step 单位为秒 |
u32 decoder_status(dec_obj *obj) | 查询解码状态 |
int decoder_time(dec_obj *p_dec) | 获取当前播放时间 |
u32 if_decoder_is_run(dec_obj *obj) | 解码器是否在运行 |
void decoder_set_file_size(dec_obj *obj, u32 size) | 设置解码文件长度(进度计算用) |
void decoder_set_sr(dec_obj *d_obj) | 设置解码输出采样率 |
数据通路与断点
| 函数 | 说明 |
|---|---|
int mp_input(void *priv, u32 addr, void *buf, int len, u8 type) | 向解码器喂入压缩数据;type 区分数据来源 |
u32 mp_output(void *priv, void *data, int len) | 解码器产出 PCM;返回实际接收长度 |
bool get_dp(dec_obj *obj, dp_buff *dbuff) | 采集断点 |
void *check_dp(dp_buff *dbuff) | 校验并恢复断点,返回句柄 |
void clear_dp(dp_buff *dbuff) / clear_dp_buff(void *buff) | 清除断点 |
bool check_ext_api(char _xdata *fname, char const *ext, u32 len) | 校验文件名扩展名 |
中断与调度辅助
| 函数 | 说明 |
|---|---|
void decoder_init(void) | 框架初始化(解码器/DAC 相关注册) |
void decoder_soft0_isr(void) | 软中断 0 服务:解码数据搬运节拍 |
void irq_decoder_ret(dec_obj *obj, u32 ret) | 解码中断返回处理 |
void decoder_soft_hook(void) | 每轮解码软钩子(上层可覆写行为) |
void kick_decoder_api(void *p_stream_in, void *psound) | 流模式下推动解码继续 |
void decoder_channel_set(u8 dc) | 设置解码通道 |
u32 mp_read_2_dac(void) | 读解码数据直通 DAC(F1A 直通场景) |
void decoder_test_fun(void) | 框架自检函数 |
全部 API 声明见 decoder_api.h;实现位于 decoder_api.c。
Failure Modes、边界与并发
断点失效与恢复失败
dp_buff携带crc与len字段。断点数据在掉电、写 Flash 中断或长度超限时可能损坏,check_dp()通过 CRC 校验返回 NULL,上层应回退到从头播放,而不是尝试恢复。- 断点载荷随格式差异巨大(F1A 需 60 字节、WAV 仅 12 字节,见
dp_buff联合体)。若调用方按固定小缓冲存放断点,len溢出将直接破坏联合体相邻内存——实现上应按dp_buff.len动态分配,或为最重格式预留空间。
停止时序与竞态
decoder_stop的IS_WAIT参数决定停止是否同步等待。若在解码中断/软中断活跃期间非等待停止,解码器可能仍在向 DAC 输出数据,造成停止后残留音频或资源释放竞态。因此decoder_stop_phy额外提供fade(淡出)与unregist_func(注销回调),让上层在切歌/关机路径上先平滑衰减、再注销解码器。decoder_soft0_isr与解码主循环共享mp_input/mp_output缓冲区,属于典型的生产者-消费者并发。框架依赖软中断优先级与if_decoder_is_run状态位保证同一时刻只有一个角色写缓冲;应用层不应在中断上下文调用decoder_stop/pause等阻塞接口。
快进快退的算力限制
- 快进快退(
decoder_ff/fr)以秒为步进,解码器需快速跳帧/跳字节。在低主频档位,连续快进可能追不上实时输出。if_decoder_ctrl.h中的FAST_FREQ_restrict/FAST_FILTER_restrict/FAST_CHANNEL_restrict三个限制位正是为此设计:快进模式下允许框架主动降低采样率/关闭滤波/降通道,换取解码吞吐。上层在使能DEC_FUNCTION_FF_FR时应同步配置这些限制位。
数据不足与空转
CMD_SET_GOON_CALLBACK(0x95)+GoOn_DEC_CallBack解决"解码器想取数但源数据不足"的场景:回调告知本次input可取字节数,过少则跳过本次 run。若未注册该回调且源数据持续不足,解码循环可能空转耗电——流式播放(网络/蓝牙)场景必须注册。
多通道 F1A
MAX_F1A_CHANNEL > 1时会出现D_TYPE_F1A_2/INDEX_F1A2两个槽位。双通道同时解码意味着句柄表、中断节拍与 DAC 写通路都要处理两个实例,任何一处只按单通道编写(如decoder_channel_set只设一个通道)都会导致第二通道无声或数据错乱。启用前需核对decoder_channel_set与mp_output的通道参数化实现。
性能与运维考虑
- 缓存段优化:
dec_hld_tab[]显式放置于AT(.docoder_mge.text.cache.L1)段(decoder_api.c#L94),decoder_api.c全文通过#pragma code_seg(".decoder_api.text")等段属性(文件开头 L3-L7)把解码框架代码/数据/常量分别放入专用段。这些段声明了框架代码在 Cache 中的驻留策略——调度路径是热路径,应保证其不被其他模块的代码挤出 L1。 - 表驱动查表开销:解码器选择是 O(1) 数组访问,无分支;代价是索引与表项必须严格一一对应(
INDEX_XXX顺序 == 宏展开顺序)。新增解码器时若打乱decoder_msg_tab.h中宏的书写顺序,会导致索引错位、播放错格式——必须将新格式追加在末尾(或在INDEX_E_SPEED=12之前),这是该框架最重要的扩展纪律。 - 条件编译收缩:每个
DECODER_XXX_EN=0的解码器同时消除其 API 包含、句柄表项、缓冲表项与位掩码,固件体积随裁剪线性收缩。调试播放问题时,先用decoder_status确认目标格式的BIT_XXX已置位,避免在未编译的解码器上排查。
Extension Points:如何新增一种解码器
在框架中接入新解码格式需要完成 5 个登记步骤(以格式 FOO 为例):
- 提供解码库接口:在解码库中实现
foo_decode_api(解码入口)与foo_buff_api(缓冲区管理),并定义全局句柄dec_foo_hld(dec_obj结构)。 - 新增开关宏:在配置头中加入
#define DECODER_FOO_EN 1。 - 条件包含:在
decoder_api.c顶部加入#if DECODER_FOO_EN #include "foo_api.h" #endif。 - 登记宏:在
decoder_msg_tab.h中仿照MP3_ST_HLD/LST/API/MUT_TAB/PARM_SET/BIT_XXX定义FOO_*宏组,并在INDEX_枚举中追加INDEX_FOO(注意放在INDEX_E_SPEED = 12之前)、在DECOER_TYPE枚举中追加D_TYPE_FOO。 - 挂入调度表:将
FOO_LST、FOO_API、FOO_MUT_TAB分别追加到dec_hld_tab[]、decoder_tab[]及互斥缓冲表(decoder_api.c)的宏展开列表中。
完成上述步骤后,框架的 decoder_fun/decoder_io/decoder_list 即可自动调度新格式,断点机制(dp_buff 联合体需增加 foo[n] 载荷,若载荷大于现有最大者会放大整个结构,需评估 RAM 影响)、快进快退与状态查询全部复用现有通路。
相关源码与后续阅读
- 框架核心实现:decoder_api.c、decoder_msg_tab.c、decoder_point.c
- 对外接口头文件:decoder_api.h、decoder_mge.h、if_decoder_ctrl.h、decoder_msg_tab.h
- 解码器登记宏示例:
decoder_msg_tab.h中MP3_ST_*/UMP3_*宏组(上述链接 L58 起) - 相关兄弟主题(本页不展开):音频 DAC 输出链路(
audio_dac_api.h、audio_dac_fade.h)、音频后处理(audio_eq.h、src_api.h、song_speed_api.h)、媒体文件读取(VFS 层)