杰理 SDK 文档中心
首页
首页
  • 项目概览与快速开始

    • 项目概述与芯片支持
    • 环境搭建与工具链
    • 工程与构建系统
    • 烧录与升级工具
    • 文档与硬件资料
  • 系统架构与芯片平台

    • 芯片平台与启动流程
    • 预编译库与头文件体系
    • 消息、定时器与中断服务
    • 通用外设驱动
  • 存储与文件系统

    • 文件系统实现
    • 存储设备驱动
    • VM 参数存储系统
  • 音频处理

    • 音频解码器
    • 音频编码器
    • MIDI 合成与播放
    • 音效、变速变调与降噪
  • 语音玩具应用

    • 应用框架与状态机
    • 音乐播放与外部音源
    • MIDI 乐器模式
    • 录音应用
    • 待机、电源管理与 USB 从机
  • 小音箱应用

    • 应用框架与模式管理
    • 播放源:音乐、FM、录音与 LineIn
  • 应用层与示例工程

    • 通用 MCU 应用
  • 固件更新与补丁

    • 固件升级机制
    • AD14N 主动降噪补丁

音频解码器

本页介绍 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"整条链路的中间枢纽。其设计目标可以概括为三点:

  1. 统一入口:上层播放逻辑只面对 decoder_fun / decoder_io 两个函数,不必关心当前文件是何种格式。格式识别通过 dec_ctl 位掩码(BIT(dec_i))逐项探测 decoder_tab 中的解码器 open 函数完成。
  2. 资源互斥:SDK 通过编译期生成的 decoder_mutual 缓冲占用表,在启动新解码器时检查各解码器工作缓冲是否重叠,重叠即强制停止冲突解码器,避免同一块 RAM 被两个解码器同时使用。
  3. 链路可插拔:解码器输出后可以按宏开关(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

处理逻辑分为三支:

  1. MIDI 特殊分支:MAD_ERROR_PLAY_END 直接回调弱函数 midi_error_play_end_cb(应用可覆盖),用于 MIDI 曲目错误播放结束的定制处理;
  2. 事件上报:非 0 返回码按 ret & 0x0f 索引 obj->event_tab 投递事件(MAD_ERROR_F1X_START_ADDR 归一到 MAD_ERROR_PLAY_END 事件槽),上层消息循环据此驱动"播放结束/下一首"等业务;
  3. 错误标志: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_CHANNELint1大于 1 时启用 D_TYPE_F1A_2 第二 F1A 通道,decoder_tab 增加 F1A2 条目
DECODER_UMP3_ENbool0编译 UMP3 解码器,决定 dp_buff.ump3[20] 与 UMP3_LST/API/MUT_TAB 条目
DECODER_MP3_ST_ENbool0编译标准 MP3 解码器,决定 dp_buff.mp3[0x10] 与 MP3_ST_* 条目
DECODER_F1A_ENbool0编译 F1A 解码器,决定 dp_buff.f1a[60] 与 F1A*_* 条目
DECODER_WAV_ENbool0编译 WAV 解码器,决定 dp_buff.wav[12] 与 WAV_* 条目
DEC_FUNCTION_FF_FRbitBIT(0)是否支持快进快退功能标志
AUDIO_SPEED_ENbool0编译变速变调模块,dec_ctl & BIT_SPEED 时串联 speed_api
HAS_HW_SRC_EN / HAS_SW_SRC_ENbool0编译硬件/软件 SRC;启用且 sr != SR_DEFAULT 时走 link_src_sound 链路
HAS_MIO_ENbool0编译 MIO 混音/叠加功能,decoder_io 中通过 vfs_openbyfile(...,"mio") 打开伴音
IRQ_DECODER_IPint平台定义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_ENDirq_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]" 过滤。

扩展点

  1. 新增格式解码器:实现 audio_decoder_ops 全部回调、导出 get_xxx_ops(),并在工程配置中定义对应 DECODER_XXX_EN 宏以展开 XXX_LST / XXX_API / XXX_MUT_TAB 三个登记宏——框架循环、互斥表与断点 union 布局全部自动适配;
  2. 覆盖弱回调:重定义 midi_error_play_end_cb 处理 MIDI 错误播放结束的定制业务;
  3. 音效链扩展:sound_out_obj 的 effect 字段支持串联自定义效果节点(speed_api 即通过该机制挂接),可参考 B_DEC_EFFECT 标志的用法接入新算法;
  4. 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)。
Next
音频编码器