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

    • AD23N SDK 概述与芯片平台
    • 工程结构与模块划分
  • 快速开始

    • 开发环境搭建与工具链
    • 编译构建指南
    • 烧录与固件升级工具
  • 应用框架与产品工作流

    • 应用入口与模式调度
    • 音乐播放应用
    • MIDI 解码与键盘演奏
    • 录音应用
    • LINEIN 与扩音应用
    • USB 从设备应用
    • 待机、软关机与空闲检测
    • 公共 UI 与 LED 显示
  • 音频子系统

    • 音频解码器框架
    • 音频编码器框架
    • 音效算法库
    • 音频管理与输出通路
  • 存储与文件系统

    • 文件系统层
    • NOR Flash 与虚拟机存储
    • 设备与设备管理
  • 系统服务与运行时

    • 消息机制与事件分发
    • 按键扫描与输入处理
    • 电源管理与低功耗控制
    • 定时器与系统任务
  • 外设驱动与平台

    • CPU 平台与启动流程
    • USB 协议栈与主机/设备驱动
    • SPI 与通用外设接口
  • 固件升级与构建工具

    • 固件升级机制
    • 编译后处理与镜像打包
    • 构建系统与命令行工具

音频解码器框架

音频解码器框架(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_ENbool 宏关闭使能杰理私有 F1A 解码器;MAX_F1A_CHANNEL > 1 时额外注册 F1A2 通道(INDEX_F1A2、f1a_decode_api_2)
MAX_F1A_CHANNELint1F1A 通道数,决定 dec_f1a_hld[] 句柄数组长度与第二个 F1A 索引是否编译
DECODER_UMP3_ENbool 宏关闭使能 UMP3(压缩 MP3)解码器,注册 ump3_decode_api、ump3_buff_api
DECODER_A_ENbool 宏关闭使能 A 格式解码器,注册 a_decode_api、a_buff_api
DECODER_MIDI_ENbool 宏关闭使能 MIDI 解码器,注册 midi_decode_api、midi_buff_api
DECODER_MIDI_KEYBOARD_ENbool 宏关闭使能 MIDI 键盘控制解码器(INDEX_MIDI_CTRL),注册 midi_ctrl_decode_api
DECODER_WAV_ENbool 宏关闭使能 WAV 解码器,注册 wav_decode_api、wav_buff_api
DECODER_MP3_ST_ENbool 宏关闭使能标准 MP3 解码器(INDEX_MP3_ST),注册 mp3_st_decode_api
AUDIO_SPEED_ENbool 宏关闭使能变速变调功能;dec_ctl & BIT_SPEED 时经 speed_api() 链接效果器
HAS_HW_SRC_EN / HAS_SW_SRC_ENbool 宏关闭使能硬件/软件采样率转换;解码采样率 ≠ SR_DEFAULT 时链接 SRC
HAS_MIO_ENbool 宏关闭使能 MIO 语音叠加;.mio 文件被 check_ext_api() 过滤,成功时打开 MIO 通道
SR_DEFAULTu32—系统默认采样率,用于判断是否需要 SRC 或直接配置 DAC 采样率
DEC_FUNCTION_FF_FR位定义BIT(0)解码器能力位:是否支持快进快退(decoder_ff/fr 先校验该位)
DEC_STOP_WAIT枚举NO_WAIT=0decoder_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 通道并启动软中断解码。

  • 参数:pfile VFS 文件句柄;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

  1. 新增解码格式(如 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 枚举中登记类型。框架的探测循环、断点机制、停止回收均自动适配,无需改动框架代码。
  2. 自定义停止回收:解码器可设置 obj->decoder_res_release 回调,decoder_stop_phy() 在回收末尾调用并置 NULL,用于释放解码器私有资源(如 MIDI 音色库句柄)。

  3. 播放结束回调:覆盖 midi_error_play_end_cb()(weak 符号)可接管 MAD_ERROR_PLAY_END 的结束处理。

  4. MIO 叠加:使能 HAS_MIO_EN 后,decoder_io() 会自动为播放通道挂载 MIO 解码(d_mio_open(&first_sound->mio, mio_pfile, mio_a_hook_init)),可在不打断主解码的前提下叠加语音提示。

  5. 效果器链: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 — 解码控制接口定义
Next
音频编码器框架