杰理 SDK 文档中心
首页
首页
  • 项目概览

    • AD16N 系列芯片与 SDK 能力总览
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建指南
    • 烧录与固件升级
  • SDK 工程架构

    • SDK 目录结构与模块分层
    • 构建系统与批处理工具
    • BSP 板级支持包
  • mbox_flash 小音箱应用

    • 应用初始化与启动流程
    • 应用配置系统
    • 按键、UI 与用户交互
  • 音频子系统

    • 音频解码框架与调度
    • 音频格式解码器实现
    • MIDI 合成与播放
    • 音频编码与录音
    • EQ/DRC 与音效处理
    • DAC/ADC 音频接口与采样
  • 存储与文件系统

    • 媒体 IO 抽象层 MIO
    • 存储设备驱动
    • 文件系统支持
  • 平台系统库

    • 系统基础服务
    • CPU 平台与运行库
    • 固件升级与更新机制
    • 蓝牙与扩展连接接口
  • 电源与低功耗管理

    • 电源管理与低功耗设计
    • 锂电池充电管理
  • 硬件与文档参考

    • SDK 文档中心与版本发布记录
    • 芯片数据手册与硬件设计参考

音频解码框架与调度

音频解码框架与调度是 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 开销下,用一套统一接口调度多个异构解码器。为此框架采用了典型的"表驱动 + 消息分发"架构:

  1. 表驱动注册:每种解码器通过 decoder_msg_tab.h 中的宏(如 UMP3_LST、UMP3_API)把自己的句柄对象地址(_hld)和解码 API 函数指针(_api)登记到框架的两张全局表 dec_hld_tab[] 与 decoder_tab[] 中。框架不关心解码器内部实现,只按索引访问表项。
  2. 索引位掩码调度:INDEX_XXX 枚举为每种解码器分配固定索引,BIT_XXX 位掩码用于在"同时支持多种格式"的场景下快速判断某解码器是否启用。索引 12/13/14 被预留给 SPEED(变速)、EQ(均衡器)、SRC_FORCE(重采样强制),说明框架把音频后处理也纳入了同一调度体系。
  3. 条件编译裁剪:所有解码器都以 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 场景下为第二个通道预留的类型位:

源码:decoder_api.h#L13-L29

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 中的索引枚举则是调度用的"槽位",全部用条件编译包裹,保证未启用的解码器不占用索引:

源码:decoder_msg_tab.h#L8-L52

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 指针、互斥缓冲表:

源码:decoder_msg_tab.h#L58-L73

#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 缓存段以加速访问:

源码:decoder_api.c#L94-L110

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 函数指针:

源码:decoder_api.c#L113-L120

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

源码声明:decoder_api.h#L98-L115

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)协议。控制字采用高位标志 + 低位枚举的编码方式:

源码:if_decoder_ctrl.h#L12-L47

#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 特色——用联合体复用同一块内存保存不同格式的断点数据:

源码:decoder_api.h#L32-L55

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)),实现复读循环完成时的应用层通知:

源码:if_decoder_ctrl.h#L64-L68

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,避免空转:

源码:if_decoder_ctrl.h#L94-L98

#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: 停止完成 + 断点数据

关键时序说明:

  1. 启动阶段:decoder_fun 解析 dec_ctl 控制字后,通过索引查两张调度表得到解码器句柄与 API 入口,随即调用解码 API 完成格式探测与初始化——启动即完成"解码器选择"。
  2. 运行阶段:解码在循环中由数据驱动。mp_input 把媒体数据喂给解码库,解码库产出 PCM 后经 mp_output 输出。decoder_soft0_isr(软中断)与 irq_decoder_ret(中断返回)参与数据搬运的节拍控制;decoder_soft_hook 提供软钩子让上层介入每一轮解码。kick_decoder_api(p_stream_in, psound) 用于在流模式下"踢"一下解码器,推动其继续取数。
  3. 停止阶段: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整数≥1F1A 通道数;>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 为例):

  1. 提供解码库接口:在解码库中实现 foo_decode_api(解码入口)与 foo_buff_api(缓冲区管理),并定义全局句柄 dec_foo_hld(dec_obj 结构)。
  2. 新增开关宏:在配置头中加入 #define DECODER_FOO_EN 1。
  3. 条件包含:在 decoder_api.c 顶部加入 #if DECODER_FOO_EN #include "foo_api.h" #endif。
  4. 登记宏:在 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。
  5. 挂入调度表:将 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 层)
Next
音频格式解码器实现