杰理 SDK 文档中心
首页
首页
  • 概述

    • SDK 概览与产品定位
    • 支持芯片平台与蓝牙认证
    • SDK 架构与目录分层
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建系统
    • 板级工程与配置
    • 烧录与固件升级工具
  • 应用工程

    • 应用选择与工程总览
    • SPP + BLE 数传应用框架
    • 透传与 AT 指令示例
    • BLE 广播/中心与定位示例
    • 2.4G 私有协议与 Dongle 示例
    • 云平台接入示例
    • HID 人机交互应用框架
    • HID 示例工程(键盘/鼠标/遥控器/手柄)
    • Bluetooth Mesh 应用框架
    • Mesh 模型与 Mesh DFU 固件升级
    • Mesh 音频编解码演示
  • 芯片平台与硬件抽象

    • 芯片平台总览与差异
    • 音频编解码与时钟管理
    • 外设驱动接口(ADC/IIC/SPI/PWM/LED/充电)
    • 芯片配置工具与下载支持
  • 蓝牙协议栈

    • 蓝牙控制器层(btctrler)
    • 蓝牙协议栈与 Profile(btstack)
    • 蓝牙模块选择与配置
  • 媒体与音频框架

    • 音频流框架
    • 音频编解码与 A2DP 媒体
    • 音频效果处理(EQ/频谱/变调/环绕/超低音)
    • 本地 TWS 与音频同步
  • 系统服务与运行时

    • 实时操作系统与任务调度
    • 消息事件机制
    • 电源管理与低功耗
    • 存储与配置系统
    • 设备驱动框架(USB/RTC)
  • 应用公共组件

    • 音频应用组件
    • 设备外设抽象(按键/触摸/传感器/存储)
    • 蓝牙公共模块与消息联动
    • 调试与配置组件
    • 杰理关键词唤醒(jl_kws)
  • 第三方协议与云平台接入

    • 杰理 RCSP 私有协议
    • 低功耗蓝牙 Mesh 方案(llsync_mesh)
    • Sig Mesh 方案
    • 涂鸦协议接入
    • 腾讯连连接入
    • 华为 HiLink 接入
  • 固件升级与维护

    • OTA 升级机制
    • 升级补丁与版本维护
    • 升级工具链(BLE OTA / USB Dongle OTA)
  • 文档与开发资源

    • 数据手册与架构文档
    • 协议与云平台开发文档
    • 常见问题与技术支持

音频编解码与 A2DP 媒体

本文档介绍 fw-AC63_BT_SDK 中 A2DP(Advanced Audio Distribution Profile,高级音频分发规范)媒体链路的完整实现:从手机端通过 AVDTP 信令协商 SBC 编解码能力,到 A2DP 媒体通道接收音频数据包,再到 audio_dec.c 中的解码器(a2dp_dec_hdl)完成 SBC 解码、采样率检测、丢包隐藏(PLC)、TWS 时钟同步并最终混音输出的全过程。

Purpose and Scope

本页覆盖"音频编解码与 A2DP 媒体"这一能力域的以下内容:

  • A2DP 协议栈分层与 AVDTP 信令能力协商(SBC codec capability)
  • 媒体数据通道 API(a2dp_media_codec.h 声明的媒体包缓冲与取包接口)
  • A2DP 解码器实现(cpu/br30/audio_dec.c 中的 a2dp_dec_hdl 及其状态机)
  • 采样率检测(a2dp_sample_detect.h)、丢包隐藏(PLC)、TWS 编解码同步机制
  • A2DP 模式下的系统时钟档位配置(audio_codec_clock.c)

以下相关主题属于兄弟页面,不在本页展开:通话音频(HFP/mSBC/CVSD)、AVRCP 控制命令、TWS 无线对箱整体架构、EQ/音效处理细节。需要时请参阅对应目录页面。

概述

A2DP 是经典蓝牙(BR/EDR)上承载高质量单向立体声音乐流的规范。AC63 系列芯片的 SDK 采用 BTstack 协议栈 + 自研媒体管线的组合:

  1. 信令面(AVDTP):手机与耳机通过 AVDTP 发现、协商并配置流端点(SEID),SBC 是 A2DP 强制支持的编解码器。协商结果通过 btstack_event.h 中的 AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CAPABILITY 事件上报。
  2. 数据面(Media Transport):音频数据以 RTP 封装(payload 为 SBC 帧)经 ACL 通道到达;SDK 侧由 a2dp_media_codec 模块缓冲为包队列,供解码线程拉取。
  3. 解码与输出:audio_dec.c 实现解码器 a2dp_dec_hdl,它从媒体队列取包、剥离 RTP/帧头、执行 SBC 解码(硬件/软件解码器)、逐帧做采样率检测,并在丢包时通过 PLC(Packet Loss Concealment)填补,最终将 PCM 送入混音器(audio_mixer_ch)输出到 DAC。

关键设计意图:媒体包缓冲与解码消费解耦。协议栈把收到的媒体包放入队列,解码器按自己的节奏取包,并通过序号(seqn)比较、剩余播放时间等指标做抗抖动与重同步,从而在蓝牙射频波动下保持音频连续。

架构

flowchart TD
    subgraph sg_Phone["对端设备(手机/发射端)"]
        SRC["A2DP Source"]
    end

    subgraph sg_BT["蓝牙协议栈(BTstack)"]
        AVDTP["AVDTP 信令<br/>SBC 能力协商"]
        MEDIA["a2dp_media_codec<br/>媒体包缓冲队列"]
    end

    subgraph sg_DEC["解码层(cpu/br30/audio_dec.c)"]
        DEC["a2dp_dec_hdl<br/>A2DP 解码器"]
        DET["a2dp_sample_detect<br/>采样率检测"]
        PLC["LFaudio_plc<br/>丢包隐藏"]
        SYNC["audio_syncts / bt_audio_timestamp<br/>TWS 同步"]
    end

    subgraph sg_OUT["输出链路"]
        MIX["audio_mixer_ch 混音"]
        DAC["DAC"]
    end

    subgraph sg_CLK["时钟管理"]
        CLK["audio_codec_clock<br/>A2DP_MODE 档位"]
    end

    SRC -->|"ACL 媒体数据 (RTP/SBC)"| MEDIA
    SRC -->|"AVDTP 信令"| AVDTP
    AVDTP -->|"配置 SBC 流端点"| MEDIA
    MEDIA -->|"a2dp_media_fetch_packet"| DEC
    DEC --> DET
    DEC --> PLC
    DEC --> SYNC
    DEC --> MIX
    MIX --> DAC
    CLK -->|"SYS_64M 等档位"| DEC

各组件职责

组件文件职责
AVDTP 信令include_lib/btstack/btstack_event.h上报 SBC 能力协商事件(cid、SEID、采样率位图)
媒体通道include_lib/btstack/a2dp_media_codec.h媒体包队列:取包、剩余缓冲/播放时间、按序号清包
A2DP 解码器cpu/br30/audio_dec.ca2dp_dec_hdl 状态机:取帧、解码、修复、输出
采样率检测include_lib/media/a2dp_sample_detect.h逐帧统计样本数,估算真实采样率,驱动时钟微调
丢包隐藏cpu/br30/audio_dec.c(A2DP_AUDIO_PLC_ENABLE,调用 media/tech_lib/LFaudio_plc_api.h)对丢失/损坏的帧做波形预测填补
TWS 同步media/audio_syncts.h、media/bt_audio_timestamp.h主/从箱播放时钟对齐(AUDIO_CODEC_SUPPORT_SYNC)
时钟管理cpu/br30/audio_codec_clock.c按功能组合选择 SYS_64M/SYS_96M 等时钟档位

这种分层把"协议栈收包"与"音频消费"隔离:即使射频突发丢包或解码瞬时繁忙,队列仍能提供缓冲,解码器通过序号与时间戳判断是否需要清空或等待,保证音画(声像)同步与播放连续性。

AVDTP 信令与 SBC 能力协商

A2DP 会话的建立走 AVDTP(Audio/Video Distribution Transport Protocol)信令。协议栈在协商阶段产生 SBC capability 相关事件,include_lib/btstack/btstack_event.h 提供了一组内联访问函数来读取事件字段,例如:

* @brief Get field avdtp_cid from event AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CAPABILITY
static inline uint16_t avdtp_subevent_signaling_media_codec_sbc_capability_get_avdtp_cid(const uint8_t *event)
...
* @brief Get field sampling_frequency_bitmap from event AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CAPABILITY

Source: btstack_event.h

该事件携带 avdtp_cid、local_seid、remote_seid、media_type 与 sampling_frequency_bitmap 等字段,用于确认对端支持的采样率与声道配置。SBC 作为 A2DP 的强制编解码器,协商结果决定后续媒体包的 payload 格式。

控制命令面(avctp_user)

include_lib/btstack/avctp_user.h 还暴露了 A2DP 媒体相关的用户控制命令与辅助接口:

USER_CTRL_TWS_SYNC_SBC_CDM,
USER_CTRL_TWS_RESTART_SBC_CDM,

/*能量检测之后有一些判断流程要走,非蓝牙开发不用*/
extern int sbc_energy_check(u8 *packet, u16 size);
/*配置通话使用16k的msbc还是8k的cvsd*/
extern void __set_support_msbc_flag(bool flag);

Source: avctp_user.h(另见 L549、L594-L595)

  • USER_CTRL_TWS_SYNC_SBC_CDM / USER_CTRL_TWS_RESTART_SBC_CDM:TWS 场景下主从箱同步/重启 SBC 解码的命令字。
  • sbc_energy_check():对 SBC 包做能量检测(用于防伪/事件识别等判断流程)。
  • __set_support_msbc_flag():切换通话编码(16k mSBC 或 8k CVSD),属于 HFP 侧能力,此处仅说明其在 avctp_user 层与编解码配置共存。

媒体数据通道(a2dp_media_codec)

媒体包到达后由协议栈写入缓冲队列,解码侧通过 include_lib/btstack/a2dp_media_codec.h 声明的接口访问。该头文件还定义了蓝牙序号比较宏:

#define seqn_after(a, b)        ((s16)((s16)(b) - (s16)(a)) < 0)
#define seqn_before(a, b)       seqn_after(b, a)

extern int a2dp_media_get_packet(u8 **frame);
extern int a2dp_media_try_get_packet(u8 **frame);
extern int a2dp_media_get_remain_buffer_size();
extern int a2dp_media_get_remain_play_time(u8 include_tws);
extern int a2dp_media_get_total_data_len();
extern int a2dp_media_get_packet_num();
extern int a2dp_media_clear_packet_before_seqn(u16 seqn_number);
extern void *a2dp_media_fetch_packet(int *len, void *prev_packet);
extern void *a2dp_media_fetch_packet_and_wait(int *len, void *prev_packet, int msec);
extern void a2dp_media_free_packet(void *_packet);
extern int a2dp_media_channel_exist(void);
extern int a2dp_media_is_clearing_frame(void);
extern int a2dp_media_get_codec_type();

Source: a2dp_media_codec.h

设计意图:

  • seqn_after/before 使用 s16 有符号回绕比较,正确处理 16 位 RTP 序号翻转(sequence wrap-around)问题,这是音频连续性判断的基础。
  • fetch_packet/fetch_packet_and_wait 返回解码器可消费的帧指针(区别于 get_packet 的原生包),并支持传入 prev_packet 以迭代遍历队列;and_wait 变体在队列空时最多阻塞 msec 毫秒。
  • clear_packet_before_seqn(seqn) 用于在重同步时将指定序号之前的包全部丢弃,配合 is_clearing_frame 查询当前是否处于清帧状态。
  • get_remain_play_time(include_tws) 返回剩余可播时间(可计入 TWS 对端缓存),是上抛到应用层做缓冲水位提示的指标。

A2DP 解码器实现(a2dp_dec_hdl)

cpu/br30/audio_dec.c 是 A2DP 媒体消费的核心文件。文件顶部通过宏开关配置特性:

#define AUDIO_CODEC_SUPPORT_SYNC	1
#define A2DP_AUDIO_PLC_ENABLE       1

#if A2DP_AUDIO_PLC_ENABLE
#include "media/tech_lib/LFaudio_plc_api.h"
#endif

#define A2DP_RX_AND_AUDIO_DELAY     1

Source: audio_dec.c

  • AUDIO_CODEC_SUPPORT_SYNC:使能编解码时钟同步(TWS 主从对齐、timestamp 驱动)。
  • A2DP_AUDIO_PLC_ENABLE:使能丢包隐藏,调用杰理自研 LFaudio_plc_api.h 的 PLC 算法。
  • A2DP_RX_AND_AUDIO_DELAY:使能"接收即延迟"策略,即收到数据后引入固定音频延迟,为抗抖动与 PLC 留出处理窗口。

解码器句柄 struct a2dp_dec_hdl 是理解整个状态机的钥匙:

struct a2dp_dec_hdl {
    struct audio_decoder decoder;
    struct audio_res_wait wait;
    struct audio_mixer_ch mix_ch;
    enum audio_channel channel;
    u8 start;
    u8 ch;
    s16 header_len;
    u8 remain;

    u8 fetch_lock;
    u8 preempt;
    u8 stream_error;
    u8 new_frame;
    u8 repair;
    void *sample_detect;
    void *syncts;
    void *repair_pkt;
    s16 repair_pkt_len;
    u16 missed_num;
    u16 repair_frames;
    u16 overrun_seqn;
    u16 slience_frames;
#if AUDIO_CODEC_SUPPORT_SYNC
    ...
};

Source: audio_dec.c

字段语义(设计意图):

字段含义
decoder / mix_ch / channel标准音频解码器基类、混音通道与声道信息,接入统一音频框架
start / ch播放启动标志与声道数
header_len / remainRTP/帧头长度与剩余帧数据,用于跨包拼接 SBC 帧
fetch_lock / preempt取包锁与抢占标志,保护队列访问
stream_error / new_frame流错误标志与新帧到达标志
repair / repair_pkt / repair_pkt_lenPLC 修复包缓存,保存用于填补的参考帧
missed_num / repair_frames累计丢帧数与已修复帧数
overrun_seqn / slience_frames溢出时的参考序号与静音填充帧计数
sample_detecta2dp_sample_detect_open() 返回的采样率检测句柄
synctsTWS/播放时钟同步句柄(AUDIO_CODEC_SUPPORT_SYNC 时启用)

解码主循环的控制流

解码器按"取包 → 拆帧 → 检测采样率 → 解码 → 修复/重同步 → 混音输出"循环工作:

flowchart TD
    Start([解码线程启动]) --> Fetch{"a2dp_media_fetch_packet<br/>取到帧?"}
    Fetch -->|"否(超时)"| Check{"stream_error 或<br/>队列空?"}
    Check -->|"是"| Repair["PLC 修复 /<br/>slience 填充"]
    Check -->|"否"| Wait["等待新帧<br/>(fetch_lock)"]
    Wait --> Fetch
    Fetch -->|"是"| Seq{"序号连续?<br/>(seqn_after)"}
    Seq -->|"丢帧"| Repair
    Seq -->|"乱序/溢出"| Clear["clear_packet_before_seqn<br/>重同步"]
    Repair --> Decode["SBC 解码"]
    Clear --> Decode
    Decode --> Detect["a2dp_frame_sample_detect<br/>采样率检测"]
    Detect --> Sync["syncts 时钟对齐<br/>(TWS)"]
    Sync --> Mix["混音输出到 DAC"]
    Mix --> Fetch

核心思路:以序号驱动状态。正常时逐帧解码;检测到跳号(missed_num 累加)时用 PLC 填补;检测到序号倒退或溢出(overrun_seqn)时清空旧包并重同步,防止旧数据污染新流。

采样率检测(a2dp_sample_detect)

include_lib/media/a2dp_sample_detect.h 提供逐帧采样率检测接口,用于校准本地播放时钟:

void *a2dp_sample_detect_open(int sample_rate, u32 coding_type);
int a2dp_frame_sample_detect_start(void *hdl, u32 time);
int a2dp_frame_sample_detect(void *hdl, void *data, int len, u32 time);
void a2dp_sample_detect_close(void *hdl);

Source: a2dp_sample_detect.h

  • open:按期望采样率与编码类型(coding_type)创建检测句柄;hdl 保存在 a2dp_dec_hdl.sample_detect。
  • frame_sample_detect_start / frame_sample_detect:在每帧解码前后调用,传入解码数据与硬件时间戳 time,模块统计实际样本数与时间差,从而得出真实采样率漂移。
  • close:释放句柄。

该模块的输出喂给 audio_codec_clock 或播放时钟微调逻辑,以补偿 SBC 时钟与本地晶振之间的偏差——这是长时间播放不逐渐加速/减速的关键。

核心流程:从手机播放到耳机出声

sequenceDiagram
    participant P as 手机 (A2DP Source)
    participant S as BTstack AVDTP 信令
    participant M as a2dp_media_codec
    participant D as a2dp_dec_hdl
    participant T as sample_detect / syncts
    participant O as 混音 + DAC

    P->>S: 发现/能力交换(含 SBC capability)
    S-->>P: 本地 SEID + 采样率位图
    P->>S: 配置流端点 / 打开 / 启动流
    S-->>D: 通知解码器启动 (start)
    P->>M: RTP/SBC 媒体包(ACL)
    M->>D: a2dp_media_fetch_packet_and_wait()
    D->>D: 拆 RTP 头 (header_len),拼接 SBC 帧
    D->>T: a2dp_frame_sample_detect_start(time)
    D->>D: SBC 解码 → PCM
    D->>T: a2dp_frame_sample_detect(data,len,time)
    alt 序号跳变(丢包)
        D->>D: PLC 修复 / 静音填充
    end
    T->>O: 播放时钟校准(TWS 对齐)
    D->>O: PCM 混音输出
    O-->>D: 采样回调(能量检测等)

流程要点:

  1. 协商阶段:AVDTP 事件 AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CAPABILITY 上报对端 SBC 能力,本地按能力位图选择采样率/声道并配置 SEID。
  2. 流启动:start 置位后解码器进入取包循环;fetch_lock 保证同一时刻只有一个消费方在队列上操作。
  3. 帧消费:fetch_packet 返回的帧可能横跨多个 RTP 包(SBC 帧可小于包长),由 header_len/remain 完成跨包拼接。
  4. 逐帧检测:每帧解码前后调用 a2dp_frame_sample_detect*,统计样本与时间戳,修正采样率漂移。
  5. 异常路径:丢帧 → PLC 修复;序号倒退/溢出 → clear_packet_before_seqn 清旧帧重同步;长时间无数据 → 静音填充并上报 stream_error。
  6. 输出:PCM 进入 audio_mixer_ch 混音,与提示音/通话等其它音源混合后送 DAC。

A2DP 模式下的系统时钟配置

cpu/br30/audio_codec_clock.c 根据功能宏组合为 A2DP 播放选择 CPU/音频时钟档位:

//A2DP_MODE
{
#if A2DP_AUDIO_PLC_ENABLE
    SYS_64M,
#elif (TCFG_BT_MUSIC_EQ_ENABLE && ((EQ_SECTION_MAX >= 10) || AUDIO_OUT_EFFECT_ENABLE))
    SYS_64M,
...

Source: audio_codec_clock.c(cpu/bd19/audio_codec_clock.c 结构相同)

设计意图:按需升频。纯 A2DP 播放且开启 PLC 时使用 SYS_64M 以给 PLC 算法留出算力;若叠加 EQ(段数 ≥ 10)或输出音效(AUDIO_OUT_EFFECT_ENABLE),同样需要更高档位。时钟档位的选择直接决定解码线程能否在帧间隔(约 23ms/帧 @ 44.1kHz)内完成"取包+解码+PLC+检测"全部工作,是音频不卡顿的底层保障。

用法示例

以下示例均直接取自仓库源码,展示各层接口的实际调用形态。

示例 1:媒体包消费(解码线程侧)

解码线程通过 a2dp_media_fetch_packet_and_wait 阻塞取帧,取不到时进入等待:

extern void *a2dp_media_fetch_packet(int *len, void *prev_packet);
extern void *a2dp_media_fetch_packet_and_wait(int *len, void *prev_packet, int msec);
extern void a2dp_media_free_packet(void *_packet);
extern int a2dp_media_channel_exist(void);
extern int a2dp_media_is_clearing_frame(void);
extern int a2dp_media_get_codec_type();

Source: a2dp_media_codec.h

调用方先检查 a2dp_media_channel_exist() 确认媒体通道存在,再取包;prev_packet 非空时表示迭代继续,解码完成后必须 a2dp_media_free_packet() 归还,否则队列会被耗尽。

示例 2:采样率检测句柄的生命周期

解码器打开时创建检测句柄,每帧检测,关闭时释放(接口由 a2dp_dec_hdl.sample_detect 持有):

void *a2dp_sample_detect_open(int sample_rate, u32 coding_type);
int a2dp_frame_sample_detect_start(void *hdl, u32 time);
int a2dp_frame_sample_detect(void *hdl, void *data, int len, u32 time);
void a2dp_sample_detect_close(void *hdl);

Source: a2dp_sample_detect.h

示例 3:序号比较宏(抗回绕)

RTP 序号为 16 位,比较时须按有符号回绕处理,这正是 seqn_after/before 宏的用途:

#define seqn_after(a, b)        ((s16)((s16)(b) - (s16)(a)) < 0)
#define seqn_before(a, b)       seqn_after(b, a)

Source: a2dp_media_codec.h

例如判断 seqn_b 是否早于 seqn_a(用于决定 a2dp_media_clear_packet_before_seqn(seqn) 是否触发)时,直接调用宏即可,无需手工处理 0xFFFF→0x0000 翻转。

示例 4:音量联动(应用层调用)

app_audio.c 在系统音量为数字音量类型时,将音量直接下发到 A2DP 数字音量模块:

#if (SYS_VOL_TYPE == VOL_TYPE_DIGITAL)
        extern void a2dp_digital_vol_set(u8 vol);
        a2dp_digital_vol_set(volume);
#endif

Source: app_audio.c(另见 L674-L675)

配置选项

以下宏/选项在 audio_dec.c、audio_codec_clock.c 及配置头文件中定义,控制 A2DP 媒体链路行为:

选项类型默认值说明
AUDIO_CODEC_SUPPORT_SYNC宏1使能编解码时钟同步(TWS timestamp 对齐)
A2DP_AUDIO_PLC_ENABLE宏1使能丢包隐藏(调用 LFaudio_plc_api.h),并影响时钟档位选择
A2DP_RX_AND_AUDIO_DELAY宏1使能接收延迟策略,为抗抖动/PLC 预留处理窗口
ESCO_DRC_EN宏0通话下行限幅器(依赖 TCFG_DRC_ENABLE 与 TCFG_PHONE_EQ_ENABLE),默认关闭
TCFG_BT_MUSIC_EQ_ENABLE配置依项目音乐 EQ 开关;与 EQ_SECTION_MAX >= 10 或 AUDIO_OUT_EFFECT_ENABLE 组合决定 A2DP 时钟档位
TCFG_USER_TWS_ENABLE配置依项目TWS 使能;开启时编译 bt_tws.h 并参与 a2dp_media_get_remain_play_time 统计
SYS_VOL_TYPE == VOL_TYPE_DIGITAL配置依项目数字音量类型;决定 a2dp_digital_vol_set 是否被调用

依据:audio_dec.c 顶部宏定义(L59-L67)、audio_codec_clock.c 档位分支(L36-L43)、app_audio.c 音量分支(L543-L545)。

API 参考

媒体通道(include_lib/btstack/a2dp_media_codec.h)

函数参数返回说明
a2dp_media_get_packetu8 **frameint取一个原生媒体包
a2dp_media_try_get_packetu8 **frameint非阻塞取包(队列空立即返回)
a2dp_media_get_remain_buffer_size—int队列剩余缓冲字节数
a2dp_media_get_remain_play_timeu8 include_twsint剩余可播时间(ms),可含 TWS 对端缓存
a2dp_media_get_total_data_len—int队列内数据总长度
a2dp_media_get_packet_num—int队列内包数
a2dp_media_clear_packet_before_seqnu16 seqn_numberint丢弃指定序号之前的所有包(重同步用)
a2dp_media_fetch_packetint *len, void *prev_packetvoid *取可解码帧;prev_packet 支持迭代遍历
a2dp_media_fetch_packet_and_waitint *len, void *prev_packet, int msecvoid *同上,队列空时最多等待 msec 毫秒
a2dp_media_free_packetvoid *_packetvoid归还取出的包
a2dp_media_channel_exist—int媒体通道是否已建立
a2dp_media_is_clearing_frame—int当前是否处于清帧状态
a2dp_media_get_codec_type—int当前协商的编解码类型

采样率检测(include_lib/media/a2dp_sample_detect.h)

函数参数返回说明
a2dp_sample_detect_openint sample_rate, u32 coding_typevoid *创建检测句柄
a2dp_frame_sample_detect_startvoid *hdl, u32 timeint帧解码前标记起始时间戳
a2dp_frame_sample_detectvoid *hdl, void *data, int len, u32 timeint帧解码后统计样本数/时间差
a2dp_sample_detect_closevoid *hdlvoid释放句柄

故障模式、边界情况与并发

丢包与序号跳变

蓝牙射频拥塞或对端调度抖动会导致 RTP 包丢失。解码器通过 seqn_after 检测跳号,missed_num 累加、repair_frames 计数;启用 PLC 时用 repair_pkt 中保存的参考帧做波形预测填补,未启用或修复失败则回退为静音填充(slience_frames)。边界:连续大量丢包时修复效果有限,应同时依赖上层缓冲水位(get_remain_play_time)触发清帧而非无限填补。

序号回绕(sequence wrap-around)

RTP 序号为 16 位,0xFFFF 后回到 0x0000。seqn_after/before 宏通过 s16 有符号差值比较,天然处理回绕;注意:宏有效前提是两次比较的序号跨度小于 32768,队列深度设计必须满足该约束。

流切换/旧数据污染

切换歌曲或对端重启流时,队列中残留旧帧。a2dp_media_clear_packet_before_seqn(seqn) 按新流的起始序号清空旧包,a2dp_media_is_clearing_frame() 供上层查询清帧状态;解码器侧 overrun_seqn 记录溢出参考序号,配合 stream_error 标志进入重同步路径。

并发访问

  • 媒体队列由协议栈(生产者)与解码线程(消费者)共享:取包侧用 fetch_lock 互斥,preempt 支持高优先级抢占(例如切歌立即放弃当前帧)。
  • sample_detect 与 syncts 句柄仅由解码线程触达,无需额外加锁。
  • TWS 场景下主从箱各自解码,a2dp_media_get_remain_play_time(include_tws=1) 需要统计对端缓存,由 TWS 链路同步播放时钟(USER_CTRL_TWS_SYNC_SBC_CDM / USER_CTRL_TWS_RESTART_SBC_CDM 控制启停)。

时钟漂移

SBC 源时钟与本地晶振存在 ppm 级偏差。a2dp_sample_detect 每帧统计"实际样本数 vs 时间戳差",输出漂移量用于微调播放时钟/audio_codec_clock 档位;长期不校准会导致缓冲逐渐耗尽或溢出,表现为周期性卡顿。

性能与运维要点

  • 时钟档位即算力预算:A2DP_MODE 下 A2DP_AUDIO_PLC_ENABLE、EQ 段数(≥10)、输出音效都会把档位抬到 SYS_64M 及以上(audio_codec_clock.c 分支)。改功能组合时须同步评估解码线程是否能在帧间隔内完成全部处理。
  • 缓冲水位是核心运维指标:get_remain_buffer_size / get_remain_play_time / get_packet_num 可用于日志或上抛 UI(如缓冲提示),用于排查"卡顿是收包不足还是解码超时"。
  • PLC 是 CPU 大头:LFaudio_plc 为自研算法,开启后功耗与算力上升;低功耗产品可权衡关闭(A2DP_AUDIO_PLC_ENABLE=0),此时时钟档位可下调。
  • 数字音量:SYS_VOL_TYPE == VOL_TYPE_DIGITAL 时音量经 a2dp_digital_vol_set 在解码链路内生效,避免额外 DSP 通路开销(app_audio.c 多处分发点)。

扩展点

  1. 编解码类型扩展:a2dp_media_get_codec_type() 暴露当前 codec 类型,SDK 的 audio_dec.c 按类型分发到对应解码器;新增 codec(如 AAC)时在此处增加分支即可接入现有取包/同步/输出框架。
  2. PLC 算法替换:A2DP_AUDIO_PLC_ENABLE 后调用 LFaudio_plc_api.h;若自研算法,只需保持 repair_pkt/repair_frames 语义并替换实现。
  3. 同步策略:AUDIO_CODEC_SUPPORT_SYNC 与 syncts(audio_syncts.h/bt_audio_timestamp.h)为 TWS/多设备对齐预留钩子;a2dp_media_get_remain_play_time(include_tws) 已是多端感知接口。
  4. 上层控制:avctp_user.h 的用户控制命令(USER_CTRL_*)与 sbc_energy_check 提供应用层介入点(如能量检测后的特殊处理流程,注释明确"非蓝牙开发不用")。

相关链接

  • a2dp_media_codec.h(媒体通道 API)
  • a2dp_sample_detect.h(采样率检测)
  • audio_dec.c(A2DP 解码器实现)
  • audio_codec_clock.c(A2DP_MODE 时钟配置)
  • avctp_user.h(AVDTP/用户控制命令)
  • btstack_event.h(AVDTP SBC 能力协商事件)
  • 兄弟页面:通话音频(HFP/mSBC/CVSD)、AVRCP 控制、TWS 对箱同步、EQ/音效处理
Prev
音频流框架
Next
音频效果处理(EQ/频谱/变调/环绕/超低音)