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

    • SDK 简介与核心特性
    • 芯片平台与硬件资料
    • SDK 版本与发布信息
  • 快速开始

    • 环境搭建与工具链
    • 编译工程
    • 烧录与量产工具
  • 工程结构与构建系统

    • 工程目录布局
    • 构建与链接配置
  • 应用层开发

    • mbox_flash 应用框架
    • 板级支持包 (BSP)
    • 公共应用模块
    • UI 显示子系统
  • 蓝牙子系统

    • BLE 控制器、链路层与 HCI 传输
    • GATT 服务框架
    • BLE 应用示例:遥控器 / Dongle / 对讲机
    • 经典蓝牙支持
  • 音频子系统

    • 音频编解码器
    • 音频设备接口 (DAC / ADC / APA)
    • 音效处理与 EQ
    • 播放、录音与 MIO 工作流
  • 设备与文件系统

    • 存储设备驱动 (NorFlash / SDMMC / USB)
    • 文件系统 (FAT / nor_fs / SYDF)
    • 设备管理框架 (dev_mg)
  • 系统服务与电源管理

    • 消息机制 (msg / hot_msg)
    • 配置与参数存储 (app_config / VM)
    • 电源管理 (SOFT OFF / POWER DOWN)
  • 固件升级

    • 升级框架总览 (code_v1 / code_v2)
    • 双 Bank 升级机制
    • 升级通道:UART / 测试盒 / BLE OTA / USB / SD
  • 补丁包与版本维护

    • 版本升级补丁链 (v1.1.0 → v1.4.0)
    • 问题修复补丁
    • 固件裁剪与资源优化
  • 开发工具与支持

    • 辅助工具与脚本
    • 文档、配置说明与常见问题

音频编解码器

本文档介绍 AW30N BLE SDK 中的音频编解码器(Audio Codec)子系统:它通过 audio_codec_format.c 提供的格式-解码器索引映射表,将上层音频格式枚举(AUDIO_FORMAT)转换为底层解码器实例索引(INDEX_*),并支持 UMP3、OPUS、IMA、SBC、SPEEX、JLA_LW 六种编解码格式。

Purpose and Scope

本页聚焦于 SDK 中音频编解码格式的注册与选择机制,覆盖:

  • AUDIO_FORMAT 枚举的语义与取值(audio_codec_format.h)
  • 编译期编解码器列表 audio_codec_list 的构建规则(由 DECODER_*_EN 宏裁剪)
  • select_codec() 格式→索引映射算法及其调用约定(返回 -1 哨兵值表示未匹配)
  • 各编解码器的启用配置宏与 JLA_LW 预编译库的集成方式

以下主题不在本页范围:具体解码器算法实现(UMP3/SBC/OPUS 等解码内核)、音频流播放链路(stream/task 调度)、蓝牙 A2DP 音频通道。解码器运行时索引表 decoder_msg_tab.h 仅作为本页的上下文引用,其完整机制属于"解码器管理"相关页面。

Overview

在 AW30N 这种资源受限的 BLE SoC 上,音频解码能力通过编译期裁剪 + 运行期线性查找的方式组织。整个子系统的核心思路是:

  1. 系统定义一套统一的音频格式枚举 AUDIO_FORMAT,作为上层(播放任务、蓝牙协议栈、文件解析器)与解码器之间的语义契约——上层只需表达"这是一段 OPUS 数据",而不关心具体由哪个解码器实例处理。
  2. 底层解码器子系统通过数值索引(INDEX_UMP3、INDEX_OPUS 等)在 decoder_msg_tab.h 注册表中挑选解码器。
  3. select_codec() 是二者之间的桥接函数:给定 AUDIO_FORMAT,在线性表中找到对应的 INDEX_*,找不到时返回 -1。

这个设计的关键取舍在于:

  • 编译期裁剪:audio_codec_list 的每个条目都用 #if DECODER_XXX_EN 包裹,未启用的编解码器根本不会进入 ROM 中的表,既节省存储,又避免运行期访问未实现的解码器。
  • 简单优于复杂:表项最多 6 个,线性查找的 O(n) 开销可以忽略,换来的是零依赖、无状态、可重入的极简实现,非常适合 RTOS 多任务环境。

Architecture

flowchart TD
    subgraph sg_Caller["上层调用方"]
        App["解码/播放任务"]
    end

    subgraph sg_Map["格式映射层"]
        Select["select_codec(enc_type)"]
        List["audio_codec_list[][2]<br/>FORMAT/INDEX 对"]
        Enum["AUDIO_FORMAT 枚举<br/>FORMAT_UMP3 ... FORMAT_JLA_LW"]
    end

    subgraph sg_Cfg["编译期配置"]
        Macros["DECODER_*_EN 宏<br/>(UMP3/OPUS/IMA/SBC/SPEEX/JLA_LW)"]
    end

    subgraph sg_Dec["解码器实例层"]
        UMP3["INDEX_UMP3 解码器"]
        OPUS["INDEX_OPUS 解码器"]
        IMA["INDEX_IMA 解码器"]
        SBC["INDEX_SBC 解码器"]
        SPEEX["INDEX_SPEEX 解码器"]
        JLALW["INDEX_JLA_LW 解码器<br/>(lib_jla_lw_codec_pi32.a)"]
    end

    App -->|"enc_type: AUDIO_FORMAT"| Select
    Macros -->|"条件编译裁剪条目"| List
    Enum -->|"提供 FORMAT_* 常量"| List
    Select -->|"线性遍历比对"| List
    Select -->|"INDEX_xxx 或 -1"| App
    App -->|"按索引实例化解码器"| UMP3
    App -->|"按索引实例化解码器"| OPUS
    App -->|"按索引实例化解码器"| IMA
    App -->|"按索引实例化解码器"| SBC
    App -->|"按索引实例化解码器"| SPEEX
    App -->|"按索引实例化解码器"| JLALW

架构说明:

  • 格式映射层是本节点的核心:select_codec() 读取编译期生成的 audio_codec_list,将语义化的 AUDIO_FORMAT 翻译为解码器子系统使用的数字索引。
  • 编译期配置决定了映射表的内容:每个 DECODER_XXX_EN 宏控制对应条目是否被编译进表。这也意味着同一份固件只会包含已启用的编解码器,select_codec() 的返回值空间与固件实际能力严格一致。
  • 解码器实例层由索引驱动:JLA_LW 解码器以预编译静态库 lib_jla_lw_codec_pi32.a 提供,其控制接口见 jla_lw_codec_ctrl.h,这是 SDK 中唯一以二进制库形式交付的编解码器。

核心实现分析

1. 音频格式枚举(AUDIO_FORMAT)

格式枚举定义在头文件中,是上层与映射层之间的统一契约:

typedef enum {
    FORMAT_UMP3 = 0,
    FORMAT_A = 1,
    FORMAT_MP3_ST = 2,
    FORMAT_OPUS = 3,
    FORMAT_IMA = 4,
    FORMAT_SBC = 5,
    FORMAT_SPEEX = 6,
    FORMAT_JLA_LW = 7,
} AUDIO_FORMAT;

Source: audio_codec_format.h

枚举值采用显式赋值而非自动递增的默认写法(尽管此处恰好是 0..7 连续),这保证了格式编号是稳定的 ABI——即使未来在中间插入新格式,已有编号不会漂移,有利于与固件升级、外部数据流中的格式标识保持兼容。

值得注意:FORMAT_A(ADPCM 类)与 FORMAT_MP3_ST(流式 MP3)虽然占用了枚举号,但没有出现在 audio_codec_list 映射表中,说明这两类格式由解码器子系统中的独立路径处理,不走本映射表。

2. 编译期映射表(audio_codec_list)

映射表是一个 const u8 [][2] 二维数组,每行是一对 {格式号, 解码器索引}:

const u8 audio_codec_list[][2] = {
#if DECODER_UMP3_EN
    {FORMAT_UMP3,   INDEX_UMP3},
#endif
#if DECODER_OPUS_EN
    {FORMAT_OPUS,   INDEX_OPUS},
#endif
#if DECODER_IMA_EN
    {FORMAT_IMA,    INDEX_IMA},
#endif
#if DECODER_SBC_EN
    {FORMAT_SBC,    INDEX_SBC},
#endif
#if DECODER_SPEEX_EN
    {FORMAT_SPEEX,  INDEX_SPEEX},
#endif
#if DECODER_JLA_LW_EN
    {FORMAT_JLA_LW,  INDEX_JLA_LW},
#endif
};

Source: audio_codec_format.c

设计意图:

  • 使用 u8 存储格式号与索引,充分利用 8 位 MCU 架构(pi32)的存储效率;当前枚举最大值为 7,索引值也在 u8 范围内,无溢出风险。
  • #if DECODER_XXX_EN 条件编译实现按需裁剪:关闭某个解码器时,不仅解码器代码不链接,映射表条目也随之消失,select_codec() 对已禁用格式的查询将得到"未匹配",从而杜绝了"格式映射到了不存在的解码器"这类运行时错误。
  • 表按宏声明顺序排列(UMP3→OPUS→IMA→SBC→SPEEX→JLA_LW),与枚举编号顺序一致,便于阅读维护;线性查找的命中顺序因此也与格式号升序一致。

3. 格式选择算法(select_codec)

u32 select_codec(AUDIO_FORMAT enc_type)
{
    u32 res = -1;
    for (int i = 0; i < ARRAY_SIZE(audio_codec_list); i++) {
        if (enc_type == audio_codec_list[i][0]) {
            res = audio_codec_list[i][1];
            break;
        }
    }
    return res;
}

Source: audio_codec_format.c

算法要点:

  • 用 ARRAY_SIZE(audio_codec_list) 动态计算表长,因此新增/删除编解码器条目时无需同步修改循环边界——这是防止"表与循环不同步"错误的经典防御性写法。
  • 初始值 res = -1:由于 res 是 u32,-1 实际为 0xFFFFFFFF,作为哨兵值表示"格式未匹配"。调用方必须显式检查该值(if (idx == (u32)-1) 或 if (idx > N))再使用索引。
  • 找到即 break,避免无谓的后续遍历;未找到时返回哨兵,且不会触发任何日志或断言(此文件仅有 LOG_TAG "[codec]" 的定义,未在函数内打日志),因此对未支持格式的查询是静默失败的——调用方负责错误提示。

核心控制流

sequenceDiagram
    participant App as 解码/播放任务
    participant Sel as select_codec()
    participant Tbl as audio_codec_list
    participant Dec as 解码器实例

    App->>Sel: enc_type = FORMAT_OPUS
    activate Sel
    Sel->>Tbl: 遍历比对 enc_type 与各行 FORMAT_*
    Tbl-->>Sel: 第 2 行匹配 {FORMAT_OPUS, INDEX_OPUS}
    Sel-->>App: 返回 INDEX_OPUS (u32)
    deactivate Sel
    App->>App: 校验返回值 != (u32)-1
    App->>Dec: 按 INDEX_OPUS 实例化/选择解码器
    Dec-->>App: 解码数据输出

关键路径说明:

  1. 上层任务持有格式信息(例如从文件头或蓝牙配置解析出的 AUDIO_FORMAT),调用 select_codec() 获取解码器索引。
  2. 函数对编译期生成的表做一次线性扫描;匹配成功即返回对应 INDEX_*,失败返回 (u32)-1。
  3. 调用方检查哨兵值:合法则用索引驱动解码器实例化;非法则按业务逻辑处理(如回退到默认格式或上报错误)。
  4. 整个调用链无全局可变状态、无锁、无堆分配,可在任意任务上下文(中断回调之外)安全调用。

匹配失败分支

flowchart LR
    A["enc_type = FORMAT_A<br/>或 FORMAT_MP3_ST<br/>或未启用格式"] --> B["select_codec 遍历表"]
    B --> C{"存在匹配条目?"}
    C -->|"否"| D["返回 (u32)-1 哨兵"]
    C -->|"是"| E["返回对应 INDEX_*"]
    D --> F["调用方错误处理<br/>(回退/上报)"]

匹配失败有且仅有一种返回值表达方式:0xFFFFFFFF。这意味着调用方不能用 == 0 判断失败(因为 FORMAT_UMP3 = 0 且其索引也可能为 0),必须显式比较哨兵值,这是本 API 最重要的调用约定。

使用示例

基本用法:格式→索引映射查询

以下代码展示了上层如何将音频格式枚举转换为解码器索引,并处理未匹配的情况:

AUDIO_FORMAT fmt = FORMAT_OPUS;   /* 例如从蓝牙或文件解析得到 */
u32 codec_idx = select_codec(fmt);

if (codec_idx == (u32)-1) {
    /* 该格式未编译进固件,或不在映射表中 */
    /* 业务层处理:回退到默认解码器或上报不支持 */
} else {
    /* 用 codec_idx 从 decoder_msg_tab.h 注册表中选择解码器实例 */
}

调用约定(依据 audio_codec_format.c 与 audio_codec_format.h):

  • 入参 enc_type 必须是 AUDIO_FORMAT 枚举值(0~7),传入越界值不会触发断言,只会返回哨兵。
  • 返回值 u32:匹配时是解码器索引(INDEX_*,定义于 decoder_msg_tab.h);未匹配时为 (u32)-1。

扩展用法:新增一种编解码器

要在系统中接入新的解码器 X(假设其格式号为 FORMAT_X、索引为 INDEX_X、启用宏为 DECODER_X_EN),需要:

  1. 在 audio_codec_format.h 的 AUDIO_FORMAT 枚举中追加 FORMAT_X。
  2. 在 audio_codec_format.c 的 audio_codec_list 中追加条件编译条目:
#if DECODER_X_EN
    {FORMAT_X,  INDEX_X},
#endif

Source: audio_codec_format.c

该示例的扩展方式由现有代码结构直接支持:ARRAY_SIZE 动态计算表长,select_codec() 无需任何改动即可覆盖新条目——这正是把"格式注册"集中在一张编译期表里的核心价值。

配置选项

映射表的编译期裁剪由以下宏控制,定义于 SDK 的配置头文件(与 DECODER_*_EN 同族):

配置宏控制内容默认状态说明
DECODER_UMP3_EN是否编译 {FORMAT_UMP3, INDEX_UMP3} 条目依项目配置UMP3(杰理微控制器专用 MP3 变体)解码
DECODER_OPUS_EN是否编译 {FORMAT_OPUS, INDEX_OPUS} 条目依项目配置OPUS 语音/音频解码
DECODER_IMA_EN是否编译 {FORMAT_IMA, INDEX_IMA} 条目依项目配置IMA ADPCM 解码
DECODER_SBC_EN是否编译 {FORMAT_SBC, INDEX_SBC} 条目依项目配置SBC(蓝牙 A2DP 常用)解码
DECODER_SPEEX_EN是否编译 {FORMAT_SPEEX, INDEX_SPEEX} 条目依项目配置Speex 语音解码
DECODER_JLA_LW_EN是否编译 {FORMAT_JLA_LW, INDEX_JLA_LW} 条目依项目配置JLA_LW 轻量语音编解码

说明:以上宏的默认开启状态取决于具体工程配置文件(如 app_config.h 等),本页源码中仅体现其作为条件编译开关的用法。宏关闭时对应条目被预处理剔除,select_codec() 对相应格式返回 (u32)-1。

API 参考

u32 select_codec(AUDIO_FORMAT enc_type)

将语义化的音频格式枚举映射为解码器子系统使用的索引。

声明位置: audio_codec_format.h,实现于 audio_codec_format.c

参数:

  • enc_type(AUDIO_FORMAT):待查询的音频格式,取值范围 FORMAT_UMP3(0) ~ FORMAT_JLA_LW(7)。

返回值:

  • 匹配成功:对应的解码器索引(u32,值为 INDEX_UMP3/INDEX_OPUS/INDEX_IMA/INDEX_SBC/INDEX_SPEEX/INDEX_JLA_LW 之一)。
  • 匹配失败:(u32)-1,即 0xFFFFFFFF。典型场景:格式未启用(宏关闭)、格式属于枚举但无映射条目(FORMAT_A、FORMAT_MP3_ST)、或入参越界。

Throws:

  • 无。该函数不抛异常、不触发断言、不记录日志,失败仅通过哨兵返回值表达。

线程/重入性:

  • 函数为纯函数:只读编译期常量表,不修改任何全局状态,无静态局部变量。可在多任务环境下安全并发调用,无需加锁。

辅助类型:AUDIO_FORMAT

定义于 audio_codec_format.h,见上文"音频格式枚举"小节。使用 typedef enum 形式,默认按 int 存储,便于在日志与协议中直接打印。

关联常量:INDEX_*

INDEX_UMP3、INDEX_OPUS 等索引常量定义于 decoder_msg_tab.h(由 audio_codec_format.c 第 2 行 #include "decoder_msg_tab.h" 引入),是解码器注册表(decoder msg tab)中的条目标号。本页不展开注册表机制,相关细节见"解码器管理"文档。

失败模式、边界情况与并发

失败模式

场景行为应对建议
格式未启用(对应 DECODER_XXX_EN 关闭)表中无条目,返回 (u32)-1调用方在固件能力协商时先查询,避免运行时才发现
查询 FORMAT_A 或 FORMAT_MP3_ST枚举存在但无映射条目,返回 (u32)-1这两类格式走独立解码路径,不应调用本 API
入参越界(如传入 8 以上数值)遍历不命中,返回 (u32)-1调用方保证入参来自受控枚举
用 == 0 判断失败错误用法:FORMAT_UMP3 = 0,INDEX_UMP3 可能也是 0,会导致误判必须与 (u32)-1 比较

边界情况

  • 空表情况:若所有 DECODER_*_EN 均关闭,audio_codec_list 长度为零,ARRAY_SIZE 为 0,循环体不执行,函数恒返回 (u32)-1。这是合法状态,固件退化为"无内置解码器"模式,代码不会崩溃。
  • 索引值为 0:INDEX_UMP3 的数值可能为 0(注册表首条目),因此返回值 0 是合法的成功结果,进一步印证了哨兵值设计(-1)的必要性——成功与失败不能靠"非零即失败"这类惯例区分。
  • u8 存储上限:映射表行元素为 u8,若未来解码器索引超过 255 需改表结构;当前 6 个条目远未触及上限。

并发与一致性

  • audio_codec_list 声明为 const,位于只读存储区,无运行期写入;select_codec() 无锁、无全局变量、无静态缓存,天然线程安全。
  • 在典型的双核/多任务 BLE 音频架构下(播放任务、协议栈任务可能同时查询),并发调用不会产生数据竞争或饥饿问题。
  • 由于表是编译期确定的,不存在"运行期动态增删格式"的一致性窗口——固件镜像中的映射集合与链接进镜像的解码器集合永远一致,这是本设计最大的正确性保证。

性能与运维考虑

  • 时间复杂度:select_codec() 最坏 O(n),n ≤ 6(启用全部解码器时),单次调用仅数次比较,在 MCU 上开销可忽略;位于冷路径(格式切换/会话建立时调用),不影响音频实时数据通路。
  • 存储开销:每个条目 2 字节(u8[2]),全部启用也仅 12 字节 ROM;配合条件编译,未启用的格式零成本。
  • 无运行时依赖:函数不依赖堆、文件系统或外设,可在早期初始化阶段调用。
  • 可观测性:映射层本身不打印日志(仅定义 LOG_TAG "[codec]"),排查"格式不支持"问题时,建议在调用方侧打印 enc_type 与返回值。

扩展点

  1. 新增解码格式:在 AUDIO_FORMAT 追加枚举值 + 在 audio_codec_list 追加 #if 条目(见"使用示例"节)。select_codec() 与 ARRAY_SIZE 自动适配,无需改动核心算法。
  2. 新增启用宏:每个编解码器独立宏控制,可在工程配置中按产品 SKU 差异化裁剪(如低端型号只开 SBC+IMA)。
  3. JLA_LW 二进制交付:JLA_LW 编解码器以预编译库 lib_jla_lw_codec_pi32.a 形式提供,控制接口声明于 jla_lw_codec_ctrl.h,适用于 pi32_lto 架构;其余解码器以源码/其他库形式提供。接入二进制库时需保证架构(pi32_lto)与链接选项匹配。
  4. 替换映射策略:若未来解码器数量增长导致线性查找成为瓶颈,可将表改为按格式号直接索引的数组(以 FORMAT_* 为下标),但当前规模下线性查找的简单性更利于维护。

测试情况

本页所涉源码文件(audio_codec_format.c / .h)在仓库中未发现独立单元测试文件;其正确性主要依赖编译期约束(条件编译与枚举强类型)与调用方集成测试(各解码器格式的播放/蓝牙用例)。select_codec() 的纯函数特性使其易于在主机侧做表格驱动测试:枚举所有 AUDIO_FORMAT 值,断言"已启用格式返回对应索引、未启用/未映射格式返回 (u32)-1"。

Related Links

  • audio_codec_format.c(映射表与选择算法实现)
  • audio_codec_format.h(AUDIO_FORMAT 枚举与 API 声明)
  • jla_lw_codec_ctrl.h(JLA_LW 编解码器控制接口)
  • 解码器注册表 decoder_msg_tab.h(INDEX_* 常量来源)——详见"解码器管理"相关页面
  • 蓝牙 A2DP/音频播放链路——详见对应音频子系统页面
Next
音频设备接口 (DAC / ADC / APA)