音频格式解码器实现
音频格式解码器子系统是 AD16N MCU SDK 中负责将压缩/编码音频(F1A、MP3、WAV、AAC、OPUS、SBC、Speex 等)解码为 PCM 数据的核心软件层,涵盖从文件 I/O、格式检查、解码主循环、断点记忆到播放控制(快进快退、AB 重复、循环播放)的完整链路。
Purpose and Scope
本页面系统性地介绍 SDK 中音频格式解码器的整体架构与实现机制,包括:
- 解码器类型体系(
DECOER_TYPE)与支持的音频格式 - 解码器抽象接口层
audio_decoder_ops(open / run / format_check / get_playtime 等) - 解码器与文件系统的 I/O 桥接接口
if_decoder_io - 解码对象
dec_obj与流式解码数据源dec_data_stream - 播放控制命令(PLAY_FILE / PLAY_CONTINUE / PLAY_NEXT、快进快退、AB 重复、循环播放)
- 断点(bookpoint)记忆机制与
dp_buff结构 - 解码错误码体系(
MAD_INFO)与边界行为
本页边界:解码器框架本身(API 层与接口层)是本页主题;而各具体编解码算法(如 UMP3、F1A、AAC 的具体解码运算)以静态库 lib_wav_decoder.a、lib_a_decode.a、lib_f1a_decode.a、decoder_mge_lib.a 形式提供,算法内部实现不在本页展开。应用层如何发起解码(如 simple_decode 播放流程)、音频输出(DAC)与音效处理属于音频子系统其他页面(见"Related Links")。
Overview
在 AD16N 这种资源受限的 MCU 平台上,音频解码必须兼顾低内存占用、低 CPU 占用和可裁剪性。为此 SDK 采用了两层设计:
- 解码算法库(静态库层):每个格式的解码算法(UMP3、F1A、AAC、WAV、OPUS、IMA、Speex、SBC 等)被封装为符合统一接口
audio_decoder_ops的操作集,通过get_xxx_ops()工厂函数对外暴露。算法具体实现以.a静态库形式链接进固件,用户可根据产品需求裁剪(通过DECODER_XXX_EN宏开关)。 - 解码器管理框架(应用层):
decoder_api.c/decoder_msg_tab.c/decoder_point.c等文件实现了解码对象的创建、I/O 桥接、状态机控制、断点保存、错误上报等与具体格式无关的通用逻辑。
这种"框架 + 插件"的架构使得新增一种音频格式时,只需实现一个 audio_decoder_ops 操作集并注册到解码器表 decoder_tab[],无需改动上层播放控制逻辑。
flowchart TD
subgraph sg_App["应用层 (App)"]
Player["播放控制 (如 simple_decode)"]
MsgTab["decoder_msg_tab.c<br/>解码事件表"]
end
subgraph sg_Api["解码器API层 (decoder_api.c)"]
DecoderIO["decoder_io / decoder_list<br/>创建解码对象"]
DecoderCtl["decoder_pause / decoder_stop<br/>decoder_ff / decoder_fr"]
DpMgr["get_dp / check_dp / clear_dp<br/>断点管理"]
end
subgraph sg_Framework["解码器框架层"]
DecObj["dec_obj<br/>解码对象"]
Ops["audio_decoder_ops<br/>解码器操作集"]
IoBridge["if_decoder_io<br/>I/O桥接"]
Stream["dec_data_stream<br/>流式数据源"]
end
subgraph sg_Codec["编解码算法库 (静态库)"]
Wav["WAV decoder ops"]
Mp3["UMP3 / MP3-ST ops"]
F1a["F1A ops"]
Aac["AAC (D_TYPE_A) ops"]
Opus["OPUS / SBC / Speex / IMA ops"]
end
subgraph sg_Storage["存储与输出"]
FS["文件系统 / 存储介质"]
SoundMge["sound_mge 音频输出"]
end
Player --> DecoderIO
Player --> DecoderCtl
Player --> DpMgr
DecoderIO --> DecObj
DecoderCtl --> DecObj
DecObj --> Ops
DecObj --> IoBridge
IoBridge --> FS
Ops --> Wav
Ops --> Mp3
Ops --> F1a
Ops --> Aac
Ops --> Opus
DecObj --> Stream
DecObj --> SoundMge
MsgTab --> Player
Architecture
解码器类型体系(DECOER_TYPE)
解码器框架通过统一的类型枚举 DECOER_TYPE 标识每种音频格式。该枚举定义在 decoder_api.h:
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 ;
Source: decoder_api.h
从枚举定义可以看出几个设计要点:
- F1A 是杰理自研的压缩格式(
D_TYPE_F1A_1/D_TYPE_F1A_2),且当MAX_F1A_CHANNEL > 1(双声道配置)时才会编译出 F1A_2 类型——这是典型的"按宏裁剪"做法,MAX_F1A_CHANNEL由构建配置决定。 - 类型序号从 0 开始,
D_TYPE_MP3_ST显式指定为 7,说明早期版本该枚举存在固定的 ABI 依赖(消息表、断点数据与类型号关联),后续新增类型(OPUS=8、IMA、SPEEX、SBC、JLA_LW)向后追加,保证已有固件升级兼容性。 D_TYPE_MIDI_CTRL与D_TYPE_MIDI并列,说明 MIDI 的"控制数据输出"(例如歌词/灯光同步)被建模为独立解码类型。
解码器操作集(audio_decoder_ops)
所有格式解码器必须实现同一套操作接口 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_rdbuf_size)(); ///<获取读数buf的读文件缓存buf的大小
u32(*need_bpbuf_size)() ; ///<获取保存断点信息需要的buffer
void (*set_step)(void *work_buf, u32 step); ///<设置快进快进步长。
void (*set_err_info)(void *work_buf, u32 cmd, u8 *ptr, u32 size); ///<设置解码的错误条件
u32(*dec_confing)(void *work_buf, u32 cmd, void *parm);
} audio_decoder_ops, decoder_ops_t;
Source: if_decoder_ctrl.h
该接口的设计意图:
- 工作缓冲区由框架分配、解码器按需声明大小:
need_dcbuf_size/need_rdbuf_size/need_bpbuf_size三个函数让框架在解码前查询所需内存(解码缓冲、读文件缓存、断点缓冲),再统一从内存池分配,避免每个解码器自行管理内存导致碎片化。这是 MCU 场景下"一次性静态分配"策略的体现。 run是解码主循环:每次被调用消费一定量数据并产出 PCM,返回u32状态(如MAD_INFO中的错误码)。type参数区分普通解码与快进快退等模式。format_check支持预检查:在正式open之前用少量头部数据验证文件格式,配合decoder_msg_tab.c中的格式判定逻辑完成文件类型自动识别。dec_confing是命令通道:cmd对应播放控制命令(如SET_DECODE_MODE、SET_DEST_PLAYPOS、CMD_SET_SAMPLE),parm携带参数,是框架向解码器下发控制指令的统一入口。
注册入口通过工厂函数暴露:
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();
extern audio_decoder_ops *get_opusdec_ops();
Source: if_decoder_ctrl.h
各 get_xxx_ops() 返回的操作集指针最终被聚合到 decoder_tab[] 解码器表中(声明于 decoder_api.h),框架按 DECOER_TYPE 索引即可取得对应解码器的操作集。
解码器 I/O 桥接(if_decoder_io)
解码器本身不直接访问文件系统,而是通过 if_decoder_io 这一 I/O 抽象与外部数据源交互,定义在 if_decoder_ctrl.h:
struct if_decoder_io {
void *priv ;
int (*input)(void *priv, u32 addr, void *buf, int len, u8 type);
int (*check_buf)(void *priv, u32 addr, void *buf);
u32(*output)(void *priv, void *data, int len);
u32(*get_lslen)(void *priv);
u32(*store_rev_data)(void *priv, u32 addr, int len);
};
Source: if_decoder_ctrl.h
input:从addr(文件偏移)读取len字节到buf。type取值 0 表示同步读(数据就绪后函数才返回),1 表示异步读(立即返回,数据通过中断/回调就绪)——异步读让解码器可以在等待存储介质 DMA 期间让出 CPU,配合decoder_soft_hook()软中断机制实现协作式调度。output:解码后的 PCM 数据输出通道,通常对接sound_mge(声音管理)而非直接写 DAC。check_buf:预取缓冲检查,用于优化读取(避免重复搬运)。get_lslen:获取"剩余长度"(last stream length),流式解码时用于判断数据是否耗尽。store_rev_data:反向写入(如断点恢复时的数据回填)。
解码对象与流数据源
解码器运行时状态被封装在 dec_obj 中,定义在 decoder_mge.h:
typedef struct _dec_obj {
void *p_file; // 文件句柄/数据源
void *dec_ops; // audio_decoder_ops 操作集
void *p_dbuf; // 解码数据缓冲
void *p_dp_buf; // 断点缓冲
void *p_kick; // kick 回调
u8 *event_tab; // 事件表
u32 sr; // 采样率
u16 br; // 码率
sound_out_obj sound; // 声音输出对象
void *src_effect; // 音源效果
void *eq_effect; // EQ 效果
u32(*eq)(u8 eq_mode);
u8 loop; // 循环播放标志
u8 type; // DECOER_TYPE
u8 function; // 解码器支持的功能(如 DEC_FUNCTION_FF_FR)
char ff_fr_step; // 快进快退步长,正数-快进,负数-快退,单位-秒
} dec_obj;
Source: decoder_mge.h
dec_obj 把"解码器无关的通用状态"(文件、缓冲、输出、循环、功能标志)集中管理,而格式相关的解码上下文则留在各解码器自己的工作缓冲区中,通过 dec_ops 间接访问。function 字段配合 DEC_FUNCTION_FF_FR(快进快退)与 DEC_FORMAT_CHECK_FIX(MP3 格式检查)位标志,让框架在播放前就知道该解码器支持哪些增强功能,定义见 decoder_api.h。
对于流式(网络/实时)音频,框架额外提供 dec_data_stream:
typedef struct _dec_data_stream {
void *strm_source; // 流数据源
struct if_decoder_io *io; // I/O 桥接
void (*reset_stream)(void *); // 流重置
u32(*goon_callback)(void *); // 继续回调(告知本次可取的字节数)
u32 sr;
u16 br;
u16 strm_ctl; // B_DEC_NO_CHECK / B_DEC_IS_STRM
} dec_data_stream;
Source: decoder_mge.h
strm_ctl 的两个标志位 B_DEC_NO_CHECK(无需格式检查)与 B_DEC_IS_STRM(数据源为流)定义于 decoder_mge.h,用于跳过文件格式探测流程,直接进入流式解码。
断点(书签)机制
断点功能让设备在断电/暂停后能从上次位置继续播放。dp_buff 是断点数据的统一载体,定义在 decoder_api.h:
typedef struct _dp_buff {
union {
u32 findex; // 文件索引
u32 sclust; // 存储簇号(FAT 文件系统)
};
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;
Source: decoder_api.h
设计意图:
findex/sclust联合体记录"在哪个文件、哪个存储位置";crc与len保证断点数据在 Flash 中损坏时能识别并丢弃(见check_dp);buff联合体按格式编译期裁出不同大小的私有断点区(F1A 需要 60 字节、MP3 16 字节、WAV 12 字节),由DECODER_XXX_EN宏控制——每种格式把"恢复到该帧所需的最小上下文"存进自己的断点区。- 配套 API:
get_dp(dec_obj*, dp_buff*)获取当前断点、check_dp(dp_buff*)校验断点有效性、clear_dp(dp_buff*)清除断点、clear_dp_buff(void*)清零断点缓冲,声明见 decoder_api.h。
播放控制命令与状态机
控制命令字
解码器框架通过一组命令字与解码器交互,定义在 if_decoder_ctrl.h:
#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
Source: if_decoder_ctrl.h
SET_DECODE_MODE:切换解码模式(如从断点续播PLAY_CONTINUE到正常播放)。CMD_SET_CONTINUE_BK:设置"继续播放"断点信息。CMD_SET_PLAY_FILE:指定播放文件(配合EX_PlayFile_STRUCT中的set_play_file回调,由外部提供文件起止位置)。CMD_SET_SAMPLE:设置采样率。CMD_SET_FADEOUT:淡出控制,参数为AUDIO_FADE_PARA.mode(1 表示淡出到 0),用于停止播放时防爆音。
播放模式与播放动作定义:
#define PLAY_MOD_NORMAL 0x00
#define PLAY_MOD_FF 0x01
#define PLAY_MOD_FB 0x02
#define PLAY_FILE 0x80000000
#define PLAY_CONTINUE 0x80000001
#define PLAY_NEXT 0x80000002
Source: if_decoder_ctrl.h
PLAY_MOD_NORMAL / PLAY_MOD_FF / PLAY_MOD_FB 是解码模式(正常/快进/快退),与 dec_obj.ff_fr_step 的符号配合:正数快进、负数快退。PLAY_FILE、PLAY_CONTINUE、PLAY_NEXT 是高层播放动作,应用层通过 decoder_io() 的 dec_ctl 参数传入,决定解码器打开时是新建播放、断点续播还是播下一首。快进快退时 set_step 与 dec_confing(SET_DECODE_MODE, ...) 协同工作。
AB 重复与循环播放
AB 重复支持三个断点模式(AB_REPEAT_MODE_BP_A / BP_B / CUR,见 if_decoder_ctrl.h),并通过回调 audio_ab_repeat_mode_param.callback(priv, mode) 通知应用层 A/B 点已设置。AB 重复的配置事件号为 AB_REPEAT_CB_ABCONIFG 11。
循环播放由 repeat_mode_flag 结构描述(if_decoder_ctrl.h):
#define REPEAT_PLAY_ALWAYS 0x90
typedef struct _repeat_mode_flag {
int flag; //1代表循环模式开,0代表循环模式关
int headcut_frame; //循环模式的时候跳掉文件前面的n帧: wav解码器不会用这个参数的;
int tailcut_frame; //循环模式的时候跳掉文件末尾的n帧: wav解码器不会用这个参数的;
int (*repeat_callback)(void *priv); //每次播完一遍就回调。如果不需要callback,设成0.
void *callback_priv;
FIXPHASE_obj *fix_obj;
} repeat_mode_flag;
Source: if_decoder_ctrl.h
设计要点:headcut_frame/tailcut_frame 允许循环时裁剪首尾若干帧,消除循环接缝处的爆音;FIXPHASE_obj(short fifo_buf[18+12][32][2])为特定解码器提供相位修复缓冲,保证循环点 PCM 相位连续;repeat_callback 让应用在每播完一遍时得到通知(例如更新计数或切换音效)。WAV 解码器不使用 headcut/tailcut 参数,因为 WAV 是线性 PCM,帧边界即采样边界。
跳转与播放位置
#define SET_DEST_PLAYPOS 0x93
#define GET_PLAYPOS 0x94
typedef struct _PARM_PLAYPOS_ {
u32 time_v;
} PARM_PLAYPOS;
typedef struct _PARM_DESTTIME_ {
u32 start_time; //要跳转过去播放的起始时间:ms
u32 dest_time; //要跳转过去播放的目标时间: ms
u32(*callbackfun)(void *priv);
void *priv;
} PARM_DESTTIME;
Source: if_decoder_ctrl.h
SET_DEST_PLAYPOS 将解码器跳转到指定时间点(毫秒),dest_time 为目标时间,start_time 为跳转起点(用于部分格式必须从关键帧开始解析的场景);GET_PLAYPOS 通过 PARM_PLAYPOS.time_v 返回当前播放时间,供 UI 进度条使用。
核心控制流
解码器 API 总览
decoder_api.h 暴露给上层播放控制的核心函数(decoder_api.h):
decoder_io(void *pfile, u32 dec_ctl, dp_buff *dbuff, u8 loop):以文件方式创建/打开解码对象,dec_ctl取PLAY_FILE/PLAY_CONTINUE/PLAY_NEXT,dbuff为断点输入,loop使能循环。decoder_list(dec_data_stream *p_strm, u32 dec_ctl, dp_buff *dbuff, u8 loop, u32 output_sr):以流方式创建解码对象(对应B_DEC_IS_STRM),额外指定输出采样率output_sr。decoder_pause(dec_obj *obj)/decoder_stop(dec_obj *obj, IS_WAIT dec_stop_wait, void *p_dp):暂停/停止;decoder_stop_phy增加 fade 与注销回调能力。decoder_ff(dec_obj *obj, u8 step)/decoder_fr(dec_obj *obj, u8 step):快进/快退,step 单位秒。decoder_time(dec_obj *p_dec):获取当前播放时间;decoder_status(dec_obj *obj):查询状态。decoder_set_file_size(dec_obj *obj, u32 size):设置解码文件长度。if_decoder_is_run(dec_obj *obj):查询解码器是否在运行。kick_decoder_api(void *p_stream_in, void *psound):外部数据就绪后"踢"一下解码器继续推进。
解码主流程时序
sequenceDiagram
participant App as 应用层播放控制
participant Api as decoder_api.c
participant Ops as audio_decoder_ops
participant Io as if_decoder_io
participant FS as 文件系统/存储
participant SM as sound_mge 输出
App->>Api: decoder_io(pfile, PLAY_FILE, dbuff, loop)
Api->>Api: 按扩展名/格式检查识别 DECOER_TYPE
Api->>Ops: format_check(work_buf)
Api->>Ops: open(work_buf, io, bk_point_ptr)
Api->>Ops: need_dcbuf_size / need_rdbuf_size / need_bpbuf_size
Api->>Api: 分配解码/读文件/断点缓冲
Api-->>App: 返回 dec_obj*
loop 解码循环(软中断/任务驱动)
App->>Api: decoder_soft_hook() / kick_decoder_api()
Api->>Ops: run(work_buf, PLAY_MOD_NORMAL)
Ops->>Io: input(priv, addr, buf, len, type)
Io->>FS: 读取压缩数据
FS-->>Io: 数据就绪
Io-->>Ops: 返回读取长度
Ops->>Ops: 解码一帧 -> PCM
Ops->>Io: output(priv, pcm, len)
Io->>SM: 输出 PCM
Ops-->>Api: 返回状态(MAD_INFO / 继续)
end
App->>Api: decoder_ff(obj, 30) 快进30秒
Api->>Ops: set_step(work_buf, 30) + dec_confing(SET_DECODE_MODE, FF)
Ops->>Io: input(priv, new_addr, ...)
App->>Api: decoder_pause(obj) / decoder_stop(obj, WAIT, p_dp)
Api->>Ops: get_bp_inf(work_buf) -> 写回 dp_buff
Api-->>App: 保存断点完成
说明:
decoder_soft_hook()是协作式调度钩子(decoder_soft0_isr软中断),使解码器在异步 I/O 等待期间让出 CPU,适配 MCU 单核协作式任务模型。
解码器消息表与事件上报
decoder_msg_tab.c 维护解码器事件表(dec_obj.event_tab),解码器通过 post_event(int event) 上报事件(如文件结束、快进到头、出错),由应用层消息循环分发。错误码枚举 MAD_INFO 定义于 decoder_mge.h:
typedef enum {
MAD_ERROR_FILE_END = 0x40, // 文件播放结束
MAD_ERROR_FILESYSTEM_ERR = 0x41, // 文件系统错误 (NO USED)
MAD_ERROR_DISK_ERR = 0x42, // 磁盘错误 (NO USED)
MAD_ERROR_SYNC_LIMIT = 0x43, // 文件错误(找不到同步字)
MAD_ERROR_FF_FR_FILE_END = 0x44, // 快进结束
MAD_ERROR_FF_FR_END = 0x45, // (NO USED)
MAD_ERROR_FF_FR_FILE_START = 0x46, // 快退到头
MAD_ERROR_LIMIT = 0x47, // (NO USED)
MAD_ERROR_NODATA = 0x48, // WAV格式文件结束
MAD_ERROR_PLAY_END = 0x50, // MIDI CTRL DATA OUTPUT END
MAD_ERROR_F1X_START_ADDR = 0x51, // F1X起始位置错误
MAD_ERROR_STREAM_NODATA = 0x60, // 本轮run读不到数据
MAD_CANNOT_SYNC_TSLOOP = 0x61, // 本轮没有找到同步字
MAD_THISREAD_LT_NEEDSZ = 0x62, // 本次读数据没读够
MAD_FRAMELEN_GT_BUFFSZ = 0x63, // 压缩帧长超出范围
} MAD_INFO ;
Source: decoder_mge.h
错误码按语义分三组:文件级结束/错误(0x40–0x51,如文件播完、快进到头、F1X 起始地址错)、流式解码瞬态错误(0x60–0x63,如本轮无数据、未找到同步字、读不够、帧超长)。瞬态错误通常在下一轮 run 自动恢复,应用层主要关注文件级错误来驱动"切歌"逻辑。
用法示例
以下示例均提取自 SDK 实际源码(头文件接口声明与框架代码),展示解码器框架的典型使用方式。
文件方式创建解码器并控制播放
上层播放控制通过 decoder_io() 创建解码对象,再以 decoder_ff/decoder_fr/decoder_pause/decoder_stop 进行控制:
// 打开文件进行解码。dec_ctl: PLAY_FILE / PLAY_CONTINUE / PLAY_NEXT
// dbuff: 断点数据(续播时传入),loop: 是否循环播放
dec_obj *decoder_io(void *pfile, u32 dec_ctl, dp_buff *dbuff, u8 loop);
// 快进/快退,step 单位-秒
bool decoder_ff(dec_obj *obj, u8 step);
bool decoder_fr(dec_obj *obj, u8 step);
// 暂停 / 停止(dec_stop_wait: 是否等待解码器线程退出;p_dp: 输出断点)
bool decoder_pause(dec_obj *obj);
bool decoder_stop(dec_obj *obj, IS_WAIT dec_stop_wait, void *p_dp);
Source: decoder_api.h
流式解码创建
对网络/实时流,改用 decoder_list() 并传入 dec_data_stream,同时可指定输出采样率完成重采样对接:
dec_obj *decoder_list(dec_data_stream *p_strm, u32 dec_ctl, dp_buff *dbuff, u8 loop, u32 output_sr);
// 数据源侧需实现 if_decoder_io 桥接:
struct if_decoder_io {
void *priv ;
int (*input)(void *priv, u32 addr, void *buf, int len, u8 type); // type: 0同步读 1异步读
int (*check_buf)(void *priv, u32 addr, void *buf);
u32(*output)(void *priv, void *data, int len);
u32(*get_lslen)(void *priv);
u32(*store_rev_data)(void *priv, u32 addr, int len);
};
Sources:
断点(续播)管理
播放前用 check_dp 校验断点有效性,停止时用 get_dp 取回断点保存到非易失存储:
// 获取解码器当前断点
bool get_dp(dec_obj *obj, dp_buff *dbuff);
// 校验断点是否有效(返回解码器对象或 NULL)
void *check_dp(dp_buff *dbuff);
// 清除断点
void clear_dp(dp_buff *dbuff);
void clear_dp_buff(void *buff);
// dp_buff 载体:findex/sclust 记录文件位置,crc 校验,len 长度,
// buff 联合体按 DECODER_UMP3_EN / DECODER_MP3_ST_EN / DECODER_F1A_EN / DECODER_WAV_EN
// 编译期裁剪出各格式私有断点区
typedef struct _dp_buff {
union { u32 findex; u32 sclust; };
u16 crc;
u16 len;
union { u8 buff[1]; /* 各格式私有断点区 */ };
} dp_buff;
Sources:
跳转播放位置
应用层通过 dec_confing 命令通道下发时间跳转(毫秒级):
typedef struct _PARM_DESTTIME_ {
u32 start_time; //要跳转过去播放的起始时间:ms
u32 dest_time; //要跳转过去播放的目标时间: ms
u32(*callbackfun)(void *priv);
void *priv;
} PARM_DESTTIME;
#define SET_DEST_PLAYPOS 0x93
#define GET_PLAYPOS 0x94
Source: if_decoder_ctrl.h
配置选项
| 宏/配置项 | 类型 | 默认/取值 | 说明 |
|---|---|---|---|
MAX_F1A_CHANNEL | int | 构建配置 | F1A 声道数;>1 时编译 D_TYPE_F1A_2 类型 |
DECODER_UMP3_EN | bool | 构建配置 | 使能 UMP3 解码器,同时决定 dp_buff.ump3[20] 断点区 |
DECODER_MP3_ST_EN | bool | 构建配置 | 使能标准 MP3 解码器,决定 dp_buff.mp3[0x10] 断点区 |
DECODER_F1A_EN | bool | 构建配置 | 使能 F1A 解码器,决定 dp_buff.f1a[60] 断点区 |
DECODER_WAV_EN | bool | 构建配置 | 使能 WAV 解码器,决定 dp_buff.wav[12] 断点区 |
DEC_FUNCTION_FF_FR | bit | BIT(0) | 解码器快进快退功能标志 |
DEC_FORMAT_CHECK_FIX | bit | BIT(1) | MP3 格式检查修复标志 |
AUDIO_BK_EN | 宏 | 定义 | 使能音频断点(书签)功能 |
PLAY_MOD_NORMAL/FF/FB | 枚举 | 0/1/2 | 解码模式:正常 / 快进 / 快退 |
B_DEC_NO_CHECK / B_DEC_IS_STRM | bit | BIT(0) / BIT(1) | 流数据源控制:跳过格式检查 / 标记为流 |
REPEAT_PLAY_ALWAYS | 宏 | 0x90 | 循环播放命令值 |
配置要点:
- 解码器类型枚举(
DECOER_TYPE)与断点联合体(dp_buff)都随上述宏裁剪,改动裁剪宏必须保持解码器表decoder_tab[]与DECOER_TYPE序号一致,否则格式识别会错位。 D_TYPE_MP3_ST = 7显式定值,说明该枚举已形成 ABI 约束,新增类型只能向后追加。- 错误码
MAD_INFO同样具有 ABI 意义:0x40段的文件级错误会被消息表翻译为"切歌"事件,不可随意改动数值。
API Reference
decoder_io(void *pfile, u32 dec_ctl, dp_buff *dbuff, u8 loop): dec_obj *
以文件方式打开解码器。
参数:
pfile(void *): 文件句柄/数据源指针dec_ctl(u32): 播放动作,PLAY_FILE(0x80000000) /PLAY_CONTINUE(0x80000001) /PLAY_NEXT(0x80000002)dbuff(dp_buff *): 断点数据;PLAY_CONTINUE时传入上次保存的断点loop(u8): 1 循环播放,0 单次
返回: 解码对象 dec_obj *;失败返回 NULL。
decoder_list(dec_data_stream *p_strm, u32 dec_ctl, dp_buff *dbuff, u8 loop, u32 output_sr): dec_obj *
以流方式打开解码器(网络/实时流),output_sr 指定输出采样率。
参数:
p_strm(dec_data_stream *): 流数据源,含if_decoder_io桥接与goon_callback- 其余参数同
decoder_io
返回: 解码对象 dec_obj *。
decoder_stop(dec_obj *obj, IS_WAIT dec_stop_wait, void *p_dp): bool
停止解码并(可选)输出断点。
参数:
obj(dec_obj *): 解码对象dec_stop_wait(IS_WAIT): 是否等待解码任务完全退出p_dp(void *): 断点输出缓冲(可 NULL)
返回: bool 停止是否成功。
decoder_ff(dec_obj *obj, u8 step) / decoder_fr(dec_obj *obj, u8 step): bool
快进/快退。
参数:
obj(dec_obj *): 解码对象step(u8): 步长,单位秒
返回: bool 操作是否成功;快进到文件尾/快退到头时返回失败并上报 MAD_ERROR_FF_FR_FILE_END / MAD_ERROR_FF_FR_FILE_START。
decoder_time(dec_obj *p_dec): int / decoder_status(dec_obj *obj): u32
获取当前播放时间(毫秒)与解码器状态。
返回: decoder_time 返回播放毫秒数;decoder_status 返回状态字。
get_dp(dec_obj *obj, dp_buff *dbuff): bool / check_dp(dp_buff *dbuff): void *
断点读写。get_dp 将当前断点写入 dbuff;check_dp 校验断点(crc/len)并返回解码器对象指针,无效返回 NULL。
kick_decoder_api(void *p_stream_in, void *psound): void
外部数据就绪后通知解码器继续推进(配合 decoder_soft_hook 软中断模型)。
失败模式、边界情况与并发
文件级错误与"切歌"驱动
解码器通过 MAD_INFO 错误码区分"正常结束"与"异常错误":
MAD_ERROR_FILE_END(0x40):文件播放结束,应用层应驱动切歌。MAD_ERROR_FF_FR_FILE_END(0x44) /MAD_ERROR_FF_FR_FILE_START(0x46):快进到文件尾 / 快退到文件头,此时decoder_ff/decoder_fr返回失败,播放位置停在边界。MAD_ERROR_SYNC_LIMIT(0x43):文件内长时间找不到同步字,说明文件损坏或格式误判,应用层应终止该文件并上报用户。MAD_ERROR_F1X_START_ADDR(0x51):F1X 格式起始地址非法,属于文件头损坏。
流式解码瞬态错误
0x60–0x63 段是流式解码特有的瞬态错误,不属于致命错误:
MAD_ERROR_STREAM_NODATA(0x60):本轮run读不到数据(网络流缓冲为空)。配合CMD_SET_GOON_CALLBACK(GoOn_DEC_CallBack,见 if_decoder_ctrl.h)——回调告知解码库本次 input 能取多少字节,数据过少则跳过本轮 run,避免空转浪费 CPU。MAD_CANNOT_SYNC_TSLOOP(0x61):本轮未找到同步字,解码器会继续读取更多数据重试,直到MAD_ERROR_SYNC_LIMIT触发。MAD_THISREAD_LT_NEEDSZ(0x62):异步读模式下本次数据不足一帧,等待下一次 kick。MAD_FRAMELEN_GT_BUFFSZ(0x63):压缩帧长超出解码缓冲(need_dcbuf_size分配不足),属于配置/资源错误,需增大解码缓冲。
断点数据可靠性
断点写入 Flash 的场景下,dp_buff 依赖 crc 与 len 字段校验完整性。check_dp 对无效断点返回 NULL,播放逻辑必须处理"断点校验失败 → 从头播放"的降级路径;clear_dp/clear_dp_buff 用于在"不再支持续播"时显式清除,防止陈旧断点被误用。F1A 断点区 60 字节 > MP3 的 16 字节,反映了不同格式恢复解码所需上下文的差异——设计断点存储介质时应按最大格式预留空间。
并发与中断模型
AD16N 是单核 MCU,解码框架采用软中断 + 协作式调度模型:
decoder_soft0_isr/decoder_soft_hook():异步 I/O 数据就绪后触发软中断,解码流程从中断点继续。kick_decoder_api/kick_decoder:外部(任务/中断)通知解码器"数据可推进",解码器在下一个调度点运行run()。decoder_stop_phy(..., bool fade, bool(*unregist_func)(void *)):停止时支持淡出(fade=true时AUDIO_FADE_PARA.mode=1淡出到 0)防爆音,并允许传入注销回调,在音频路径切换(如蓝牙来电抢占)时安全回收资源。
并发注意点:decoder_stop 的 IS_WAIT 参数要求调用方在需要严格同步的场合等待解码任务退出后再释放缓冲;播放控制与解码中断共享 dec_obj,修改 ff_fr_step、loop 等字段应在解码暂停或临界区内进行,避免指令级撕裂。
格式识别失败
format_check 失败时框架会依据 decoder_msg_tab.c 中的消息表与 decoder_tab[] 尝试下一种格式;check_ext_api(char *fname, char const *ext, u32 len) 提供扩展名快速匹配路径(声明见 decoder_api.h)。对无法识别格式的文件,最终应上报错误事件并跳过,避免卡死播放队列。
性能与运维考虑
- 内存按需声明:
need_dcbuf_size/need_rdbuf_size/need_bpbuf_size三函数让框架在open后统一分配,整个生命周期内缓冲固定,无运行时 malloc/释放,避免堆碎片。裁剪宏直接影响断点缓冲大小与类型表规模。 - 异步读 + 软中断:
if_decoder_io.input的type=1异步模式使解码器在存储介质 DMA 期间让出 CPU,显著降低 CPU 峰值占用;代价是实现复杂度上升(需要goon_callback与 kick 机制配合)。 - 快进快退优化:
DEC_FUNCTION_FF_FR标志位允许框架按解码器能力选择快进实现;FAST_FREQ_restrict / FAST_FILTER_restrict / FAST_CHANNEL_restrict(见 if_decoder_ctrl.h)定义快速解码时可牺牲的质量维度(采样率/滤波/声道),用于高倍速快进时降低计算量。
扩展点
- 新增音频格式:实现
audio_decoder_ops全套函数(open/format_check/run/get_dec_inf/get_playtime/get_bp_inf/need_xxx_size/set_step/set_err_info/dec_confing),提供get_xxx_ops()工厂函数,注册进decoder_tab[],并在DECOER_TYPE尾部追加类型号。框架层无需改动。 - 自定义 I/O 源:实现
if_decoder_io(input/output/check_buf/get_lslen/store_rev_data)即可接入任意数据源(文件系统、SPI Flash、网络流)。 - 断点格式扩展:在
dp_buff联合体增加#if DECODER_XXX_EN u8 xxx[N]; #endif分支,N 为该格式恢复解码所需上下文大小。 - 播放策略:通过
repeat_mode_flag.repeat_callback、PARM_DESTTIME.callbackfun、GoOn_DEC_CallBack等回调注入业务逻辑,无需修改解码框架。 - 解码器表
dec_hld_tab[]/decoder_tab[]:导出符号(见 decoder_api.h),支持外部遍历与调试。
测试与验证
仓库中解码器算法以静态库(lib_wav_decoder.a、lib_a_decode.a、lib_f1a_decode.a、decoder_mge_lib.a,见 sdk/apps/include_lib/liba/)形式提供,算法级单元测试不在公开源码中;框架层验证主要依赖:
decoder_test_fun():框架自带的解码器功能自测入口(decoder_api.h)。mp_read_2_dac():解码数据直通 DAC 的验证路径(decoder_api.h)。- 应用层
simple_decode.c(sdk/apps/app/src/mbox_flash/simple_decode/)提供最小解码播放用例,可验证"打开 → run → 输出 → 停止"完整链路。
Related Links
- 音频子系统总览 — 音频子系统页面入口,涵盖 DAC 输出与声音管理(
sound_mge) - 解码器应用流程(simple_decode) — 应用层如何发起解码与按键控制(
simple_decode.c/simple_decode_key.c) - 断点续播机制 —
dp_buff与断电续播的完整设计(若目录中存在) - 关键源文件:
- decoder_api.h — 解码器 API 与类型定义
- if_decoder_ctrl.h — 解码器抽象接口与命令定义
- decoder_mge.h — 解码对象、错误码与流数据源定义
- decoder_api.c — 解码器 API 框架实现
- decoder_msg_tab.c — 解码事件/消息表
- decoder_point.c — 断点相关实现