ESP-IDF 蓝牙经典模式 AVRCP 协议详解:API 体系、实现机制与三个实战示例
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
导读
AVRCP(Audio/Video Remote Control Profile,音频/视频远程控制协议)是蓝牙经典模式(BR/EDR)下用于远程控制音频/视频设备的标准协议,它让用户可以通过控制端(CT)远程管理播放(播放、暂停、上一曲/下一曲)、调节音量,并获取媒体元数据。本文以 ESP-IDF 官方文档 Bluetooth® AVRCP API 为骨架,结合 Bluedroid 协议栈源码(esp_avrc_api.h 与 esp_avrc_api.c)以及仓库内三个官方示例,系统讲解 AVRCP 的 CT/TG 双角色 API 全貌、事件回调机制、元数据与绝对音量控制、Cover Art 封面图传输,以及完整的工程实践流程。读完本文,你将能够在 ESP32 / ESP32-S31 上独立开发基于 AVRCP 的蓝牙音频控制应用。
一、AVRCP 协议概述:它解决什么问题
根据官方文档的 Overview 部分,AVRCP 使设备能够通过蓝牙远程控制音频/视频设备,允许用户:
- 管理播放状态:播放、暂停、上一曲/下一曲;
- 调节音量(包括绝对音量 Absolute Volume);
- 获取媒体元数据(歌曲标题、艺术家、专辑、时长等)。
在 ESP-IDF 的 Bluedroid 协议栈实现中,AVRCP 与 A2DP(高级音频分发协议)是强耦合的两个协议:AVRC 无法独立工作,必须与 A2DP 配合使用。从源码注释可以看到明确的初始化约束:
"AVRC cannot work independently, AVRC should be used along with A2DP and AVRC should be initialized before A2DP."
这意味着在应用初始化阶段,必须先初始化 AVRCP 再初始化 A2DP(见 esp_avrc_api.h 中esp_avrc_ct_init()/esp_avrc_tg_init()的说明);去初始化时则顺序相反,必须先反初始化 A2DP 再反初始化 AVRCP。Kconfig 中的BT_AVRCP_ENABLED配置项也印证了这一点:
"AVRCP and A2DP are coupled in Bluedroid, AVRCP still controlled by A2DP option, this is a dummy option currently"
(见 Kconfig.in)
1.1 AVRCP 的双角色模型
AVRCP 协议定义了两种角色,ESP-IDF 的 API 分别对应两套独立的接口:
| 角色 | 全称 | ESP-IDF API 前缀 | 典型设备 |
|---|---|---|---|
| CT(Controller) | 控制端 | esp_avrc_ct_* | 耳机、音箱按键、车机,向 TG 发送控制命令 |
| TG(Target) | 目标端 | esp_avrc_tg_* | 手机、播放器,执行控制并上报状态 |
在一个典型的车载蓝牙场景中:车机是 CT,手机是 TG;车机上的音量按键、播放/暂停按键通过 AVRCP 发送 Passthrough 命令和 SetAbsoluteVolume 命令,手机则通过 RegisterNotification 通知车机当前播放状态、音量变化等。而在"手机控制音箱"的常见蓝牙音箱场景中,手机是 CT,音箱是 TG,此时音箱需要实现esp_avrc_tg_*接口来接收手机发送的控制命令。
1.2 AVRCP 的功能特性位
esp_avrc_features_t枚举定义了 AVRCP 的功能特性位(esp_avrc_api.h):
| 特性宏 | 值 | 含义 |
|---|---|---|
ESP_AVRC_FEAT_RCTG | 0x0001 | 支持远程控制目标角色 |
ESP_AVRC_FEAT_RCCT | 0x0002 | 支持远程控制控制端角色 |
ESP_AVRC_FEAT_VENDOR | 0x0008 | 支持厂商私有命令 |
ESP_AVRC_FEAT_BROWSE | 0x0010 | 使用浏览通道(Browsing Channel) |
ESP_AVRC_FEAT_META_DATA | 0x0040 | 支持元数据传输命令/响应 |
ESP_AVRC_FEAT_ADV_CTRL | 0x0200 | 支持高级控制命令/响应 |
此外,SDP 记录中还会通过esp_avrc_feature_flag_t上报对端设备的能力标志,包括通用分类(Category 1~4)、浏览能力,以及 CT 侧的 Cover Art 能力(ESP_AVRC_FEAT_FLAG_COVER_ART_GET_IMAGE_PROP、ESP_AVRC_FEAT_FLAG_COVER_ART_GET_IMAGE、ESP_AVRC_FEAT_FLAG_COVER_ART_GET_LINKED_THUMBNAIL)和 TG 侧的 Cover Art 能力(ESP_AVRC_FEAT_FLAG_TG_COVER_ART)。应用层通过ESP_AVRC_CT_REMOTE_FEATURES_EVT/ESP_AVRC_TG_REMOTE_FEATURES_EVT事件回调即可获得对端设备的特性掩码(feat_mask)与特性标志(tg_feat_flag/ct_feat_flag),从而决定后续发起哪些 AVRCP 交互。
二、API 体系全景:头文件中的类型定义与核心常量
ESP-IDF 的 AVRCP API 全部集中在 esp_avrc_api.h 中(C 实现见 esp_avrc_api.c),底层由 stack/avrc 目录下的协议栈代码支撑(报文构建avrc_bld_ct.c/avrc_bld_tg.c、报文解析avrc_pars_ct.c/avrc_pars_tg.c、SDP 发现avrc_sdp.c等)。本节梳理开发中最常用的类型与常量。
2.1 关键常量
#define ESP_AVRC_TRANS_LABEL_MAX 15 /*!< 最大事务标签值 */ #define ESP_AVRC_CA_IMAGE_HANDLE_LEN 7 /*!< 封面图句柄固定长度 7 字节(Basic Image Profile 规定) */ #define ESP_AVRC_CA_MTU_MIN 255 /*!< Cover Art OBEX 连接允许的最小 MTU */ #define ESP_AVRC_CA_MTU_MAX 1691 /*!< Cover Art OBEX 连接允许的最大 MTU */其中事务标签(Transaction Label,范围 0~15)是 AVRCP 中区分并发命令的重要字段,源码中几乎所有esp_avrc_ct_*_cmd()系列 API 都要求传入tl参数,且连续的命令应使用不同的标签值,便于将响应事件与请求一一对应。
2.2 Passthrough 命令码
esp_avrc_pt_cmd_t定义了完整的 Passthrough 按键命令码(esp_avrc_api.h),覆盖了媒体控制、导航菜单、数字键盘与功能键:
- 媒体控制:
ESP_AVRC_PT_CMD_PLAY(0x44)、ESP_AVRC_PT_CMD_STOP(0x45)、ESP_AVRC_PT_CMD_PAUSE(0x46)、ESP_AVRC_PT_CMD_REWIND(0x48)、ESP_AVRC_PT_CMD_FAST_FORWARD(0x49)、ESP_AVRC_PT_CMD_FORWARD(0x4B)、ESP_AVRC_PT_CMD_BACKWARD(0x4C)、ESP_AVRC_PT_CMD_RECORD(0x47)、ESP_AVRC_PT_CMD_EJECT(0x4A); - 音量控制:
ESP_AVRC_PT_CMD_VOL_UP(0x41)、ESP_AVRC_PT_CMD_VOL_DOWN(0x42)、ESP_AVRC_PT_CMD_MUTE(0x43); - 菜单导航:
ESP_AVRC_PT_CMD_SELECT(0x00)、UP/DOWN/LEFT/RIGHT及四个斜向组合、ROOT_MENU(0x09)、SETUP_MENU(0x0A)、CONT_MENU(0x0B)、FAV_MENU(0x0C)、EXIT(0x0D); - 数字键盘:
ESP_AVRC_PT_CMD_0~ESP_AVRC_PT_CMD_9(0x20~0x29)、DOT、ENTER、CLEAR; - 频道与显示:
CHAN_UP/CHAN_DOWN/PREV_CHAN、SOUND_SEL、INPUT_SEL、DISP_INFO、HELP、PAGE_UP/PAGE_DOWN; - 其他:
POWER、ANGLE、SUBPICT、功能键F1~F5、厂商自定义ESP_AVRC_PT_CMD_VENDOR(0x7E)。
按键状态由esp_avrc_pt_cmd_state_t表示:ESP_AVRC_PT_CMD_STATE_PRESSED(0,按下) 与ESP_AVRC_PT_CMD_STATE_RELEASED(1,释放)。一次完整的按键操作通常需要先后发送 PRESSED 与 RELEASED 两个命令。
2.3 媒体元数据属性掩码
esp_avrc_md_attr_mask_t定义了可通过esp_avrc_ct_send_metadata_cmd()请求的元数据属性(esp_avrc_api.h):
| 属性掩码 | 值 | 含义 |
|---|---|---|
ESP_AVRC_MD_ATTR_TITLE | 0x1 | 当前曲目标题 |
ESP_AVRC_MD_ATTR_ARTIST | 0x2 | 曲目艺术家 |
ESP_AVRC_MD_ATTR_ALBUM | 0x4 | 专辑名称 |
ESP_AVRC_MD_ATTR_TRACK_NUM | 0x8 | 曲目在专辑中的位置 |
ESP_AVRC_MD_ATTR_NUM_TRACKS | 0x10 | 专辑曲目总数 |
ESP_AVRC_MD_ATTR_GENRE | 0x20 | 曲目流派 |
ESP_AVRC_MD_ATTR_PLAYING_TIME | 0x40 | 专辑总播放时长(毫秒) |
ESP_AVRC_MD_ATTR_COVER_ART | 0x80 | 封面图句柄 |
注意esp_avrc_ct_send_metadata_cmd()的第二个参数是uint8_t attr_mask,可像ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST这样用按位或组合多个属性一次请求。
2.4 注册通知事件 ID
esp_avrc_rn_event_ids_t定义了 AVRCP 的事件通知类型(esp_avrc_api.h),CT 可调用esp_avrc_ct_send_register_notification_cmd()向 TG 注册:
| 事件 ID | 值 | 含义 |
|---|---|---|
ESP_AVRC_RN_PLAY_STATUS_CHANGE | 0x01 | 播放状态改变(如从播放变为暂停) |
ESP_AVRC_RN_TRACK_CHANGE | 0x02 | 载入新曲目 |
ESP_AVRC_RN_TRACK_REACHED_END | 0x03 | 当前曲目播放到末尾 |
ESP_AVRC_RN_TRACK_REACHED_START | 0x04 | 当前曲目播放到开头 |
ESP_AVRC_RN_PLAY_POS_CHANGED | 0x05 | 播放位置改变 |
ESP_AVRC_RN_BATTERY_STATUS_CHANGE | 0x06 | 电池状态改变 |
ESP_AVRC_RN_SYSTEM_STATUS_CHANGE | 0x07 | 系统状态改变 |
ESP_AVRC_RN_APP_SETTING_CHANGE | 0x08 | 应用设置改变 |
ESP_AVRC_RN_NOW_PLAYING_CHANGE | 0x09 | 当前播放内容改变 |
ESP_AVRC_RN_AVAILABLE_PLAYERS_CHANGE | 0x0a | 可用播放器改变 |
ESP_AVRC_RN_ADDRESSED_PLAYER_CHANGE | 0x0b | 寻址播放器改变 |
ESP_AVRC_RN_UIDS_CHANGE | 0x0c | UID 改变 |
ESP_AVRC_RN_VOLUME_CHANGE | 0x0d | TG 本地音量改变 |
其中ESP_AVRC_RN_PLAY_POS_CHANGED注册时需要传入播放间隔参数(毫秒),其他事件该参数会被忽略。通知参数由联合体esp_avrc_rn_param_t承载(音量 0~127、播放状态、8 字节元素 ID、播放位置毫秒值、电池状态)。
2.5 播放器设置(Player Application Settings)
esp_avrc_ps_attr_ids_t定义了可远程调整的播放器应用属性(esp_avrc_api.h):均衡器(ESP_AVRC_PS_EQUALIZER)、重复模式(ESP_AVRC_PS_REPEAT_MODE)、随机模式(ESP_AVRC_PS_SHUFFLE_MODE)、扫描模式(ESP_AVRC_PS_SCAN_MODE)。每个属性对应一组取值枚举:
- 均衡器:
ESP_AVRC_PS_EQUALIZER_OFF(0x1) /ESP_AVRC_PS_EQUALIZER_ON(0x2); - 重复模式:
ESP_AVRC_PS_REPEAT_OFF(0x1) /ESP_AVRC_PS_REPEAT_SINGLE(0x2 单曲) /ESP_AVRC_PS_REPEAT_GROUP(0x3 分组); - 随机模式:
ESP_AVRC_PS_SHUFFLE_OFF(0x1) /ESP_AVRC_PS_SHUFFLE_ALL(0x2 全部) /ESP_AVRC_PS_SHUFFLE_GROUP(0x3 分组); - 扫描模式:
ESP_AVRC_PS_SCAN_OFF(0x1) /ESP_AVRC_PS_SCAN_ALL(0x2 全部) /ESP_AVRC_PS_SCAN_GROUP(0x3 分组)。
2.6 其他常用枚举
- AVCTP 响应码
esp_avrc_rsp_t:NOT_IMPL(8)、ACCEPT(9)、REJECT(10)、IN_TRANS(11)、IMPL_STBL(12)、CHANGED(13)、INTERIM(15); - 播放状态
esp_avrc_playback_stat_t:STOPPED(0)、PLAYING(1)、PAUSED(2)、FWD_SEEK(3)、REV_SEEK(4)、ERROR(0xFF); - 电池状态
esp_avrc_batt_stat_t:NORMAL(0)、WARNING(1)、CRITICAL(2)、EXTERNAL(3 外接电源)、FULL_CHARGE(4 已充满); - 通知响应类型
esp_avrc_rn_rsp_t:ESP_AVRC_RN_RSP_INTERIM(13,注册命令的初始响应,应在收到命令后 T_mtp(1000ms) 内发送) 与ESP_AVRC_RN_RSP_CHANGED(15,事件变化后的最终响应); - AVRCP 初始化状态
esp_avrc_init_state_t:INIT_SUCCESS、INIT_ALREADY、INIT_FAIL、DEINIT_SUCCESS、DEINIT_ALREADY、DEINIT_FAIL。
三、AVRCP Controller(CT)角色 API 详解
CT 侧 API 用于让设备充当控制端,向对端(TG)发送命令并接收响应。所有 CT API 都应遵循"先注册回调、再初始化"的顺序,且需在esp_bluedroid_enable()成功之后调用。
3.1 生命周期管理
| 函数 | 说明 |
|---|---|
esp_avrc_ct_register_callback(esp_avrc_ct_cb_t callback) | 注册 CT 回调函数,蓝牙栈未使能时返回ESP_ERR_INVALID_STATE |
esp_avrc_ct_init(void) | 初始化 AVRCP CT 模块。注意必须在 A2DP 之前初始化;完成后上报ESP_AVRC_CT_PROF_STATE_EVT(参数为ESP_AVRC_INIT_SUCCESS) |
esp_avrc_ct_deinit(void) | 反初始化 AVRCP CT 模块,须在 A2DP 之后反初始化;完成后上报ESP_AVRC_CT_PROF_STATE_EVT(参数为ESP_AVRC_DEINIT_SUCCESS) |
3.2 命令发送 API
以下命令类 API 均要求AVRCP 连接建立之后(即收到ESP_AVRC_CT_CONNECTION_STATE_EVT且connected == true)才能调用:
| 函数 | 功能 | 关键参数 |
|---|---|---|
esp_avrc_ct_send_passthrough_cmd(tl, key_code, key_state) | 发送按键透传命令 | key_code取esp_avrc_pt_cmd_t;key_state取 PRESSED/RELEASED |
esp_avrc_ct_send_metadata_cmd(tl, attr_mask) | 请求媒体元数据 | attr_mask按位组合esp_avrc_md_attr_mask_t |
esp_avrc_ct_send_get_play_status_cmd(tl) | 获取播放状态 | 响应中带回总时长、当前位置与播放状态 |
esp_avrc_ct_send_register_notification_cmd(tl, event_id, event_parameter) | 注册事件通知 | event_parameter仅对PLAY_POS_CHANGED有意义(播放间隔毫秒) |
esp_avrc_ct_send_get_rn_capabilities_cmd(tl) | 查询对端支持的通知事件能力 | 响应经ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT返回 |
esp_avrc_ct_send_set_player_value_cmd(tl, attr_id, value_id) | 设置播放器应用属性 | 如均衡器开关、重复/随机模式 |
esp_avrc_ct_send_set_absolute_volume_cmd(tl, volume) | 设置绝对音量 | volume取值 0~0x7F,对应 0%~100% |
3.3 CT 回调事件
esp_avrc_ct_cb_event_t定义了 CT 侧的全部回调事件(esp_avrc_api.h),事件参数通过联合体esp_avrc_ct_cb_param_t传递:
| 事件 | 参数结构 | 说明 |
|---|---|---|
ESP_AVRC_CT_CONNECTION_STATE_EVT | conn_stat | 连接状态变化(connected+ 远端地址remote_bda) |
ESP_AVRC_CT_PASSTHROUGH_RSP_EVT | psth_rsp | 透传命令响应(事务标签、按键码、按键状态、响应码) |
ESP_AVRC_CT_METADATA_RSP_EVT | meta_rsp | 元数据响应(属性 ID + 属性文本 + 长度) |
ESP_AVRC_CT_PLAY_STATUS_RSP_EVT | play_status_rsp | 播放状态响应(总时长、当前位置、播放状态) |
ESP_AVRC_CT_CHANGE_NOTIFY_EVT | change_ntf | 收到对端事件通知(事件 ID + 通知参数) |
ESP_AVRC_CT_REMOTE_FEATURES_EVT | rmt_feats | 对端(作为 TG)的特性掩码与特性标志(来自 SDP 发现) |
ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT | get_rn_caps_rsp | 对端支持的通知事件能力位掩码 |
ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT | set_volume_rsp | 绝对音量设置结果(实际生效的音量 0~0x7F) |
ESP_AVRC_CT_COVER_ART_STATE_EVT | cover_art_state | Cover Art 客户端连接状态变化(连接/断开 + 原因) |
ESP_AVRC_CT_COVER_ART_DATA_EVT | cover_art_data | Cover Art 数据事件(status、final是否最后一段、data_len、p_data) |
ESP_AVRC_CT_PROF_STATE_EVT | avrc_ct_init_stat | AVRCP CT 初始化/反初始化完成 |
需要注意:cover_art_data中的p_data指针指向栈内缓冲区,必须在回调返回前拷贝到自己的缓冲区,否则数据可能被后续事件覆盖。
四、AVRCP Target(TG)角色 API 详解
TG 侧 API 让设备充当被控制的目标端,接收对端(CT)发来的命令并响应。典型场景是蓝牙音箱/耳机:手机作为 CT 控制音箱的播放与音量。
4.1 生命周期管理
esp_avrc_tg_register_callback()、esp_avrc_tg_init()、esp_avrc_tg_deinit()与 CT 侧对应 API 的使用约束完全一致(须在esp_bluedroid_enable()之后、且在 A2DP 之前初始化),初始化/反初始化完成分别上报ESP_AVRC_TG_PROF_STATE_EVT。
4.2 Passthrough 命令过滤
TG 角色可以控制自己接收哪些 Passthrough 命令,避免对无关按键(如数字键盘、菜单导航)做无意义响应:
esp_avrc_tg_get_psth_cmd_filter(filter, cmd_set):获取当前过滤配置。ESP_AVRC_PSTH_FILTER_ALLOWED_CMD返回协议栈允许的全部命令集(固定不变);ESP_AVRC_PSTH_FILTER_SUPPORTED_CMD返回当前配置下实际支持的子集;esp_avrc_tg_set_psth_cmd_filter(filter, cmd_set):设置支持的命令集。命令集中置 1 的命令会产生ESP_AVRC_TG_PASSTHROUGH_CMD_EVT回调并被协议栈自动接受;未置 1 的命令会被回复NOT IMPLEMENTED(8) 响应码。支持的命令集必须是允许命令集的子集,且ESP_AVRC_PSTH_FILTER_ALLOWED_CMD类型不可用于本函数;esp_avrc_psth_bit_mask_operation(op, psth, cmd):对esp_avrc_psth_bit_mask_t(uint16_t bits[8])执行置位/清位/测试操作(ESP_AVRC_BIT_MASK_OP_SET/CLEAR/TEST)。
4.3 通知事件能力配置与响应
esp_avrc_tg_get_rn_evt_cap(cap, evt_set):获取本地 TG 的通知事件能力。ESP_AVRC_RN_CAP_ALLOWED_EVT返回当前实现可支持的全部事件;ESP_AVRC_RN_CAP_SUPPORTED_EVT返回当前配置支持的事件子集;esp_avrc_tg_set_rn_evt_cap(evt_set):设置本地 TG 支持的通知事件集合(须为允许集合的子集);esp_avrc_rn_evt_bit_mask_operation(op, events, event_id):操作esp_avrc_rn_evt_cap_mask_t(uint16_t bits)位掩码;esp_avrc_tg_send_rn_rsp(event_id, rsp, param):向远端 CT 发送 RegisterNotification 响应。rsp取ESP_AVRC_RN_RSP_INTERIM(收到注册命令后 T_mtp 时限内先发 INTERIM)或ESP_AVRC_RN_RSP_CHANGED(事件发生时发送最终响应),param携带具体通知参数(音量、播放状态等)。
4.4 TG 回调事件
esp_avrc_tg_cb_event_t定义了 TG 侧回调事件(esp_avrc_api.h):
| 事件 | 参数结构 | 说明 |
|---|---|---|
ESP_AVRC_TG_CONNECTION_STATE_EVT | conn_stat | 连接状态变化 |
ESP_AVRC_TG_REMOTE_FEATURES_EVT | rmt_feats | 对端(作为 CT)的特性掩码与特性标志(SDP 发现) |
ESP_AVRC_TG_PASSTHROUGH_CMD_EVT | psth_cmd | 收到远端透传命令(按键码 + 按键状态) |
ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT | set_abs_vol | 收到远端绝对音量设置命令(0~127) |
ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT | reg_ntf | 收到远端注册通知命令(事件 ID + 事件参数) |
ESP_AVRC_TG_SET_PLAYER_APP_VALUE_EVT | set_app_value | 收到设置播放器应用属性命令(属性个数 + 属性 ID/值数组) |
ESP_AVRC_TG_PROF_STATE_EVT | avrc_tg_init_stat | AVRCP TG 初始化/反初始化完成 |
4.5 查询 AVRCP 状态
esp_avrc_get_profile_status(esp_avrc_profile_status_t *profile_status)返回 AVRCP 的整体运行状态,包含三个字段:avrc_ct_inited(CT 是否已初始化)、avrc_tg_inited(TG 是否已初始化)、ct_cover_art_conn_num(Cover Art 客户端连接数),便于应用在运行时诊断 AVRCP 状态。
五、Cover Art(封面图)功能:从 OBEX 连接到图像显示
Cover Art 是 AVRCP 1.6 引入的扩展功能,允许 CT 通过 OBEX 连接从 TG 获取当前曲目的封面图。ESP-IDF 的 Cover Art 实现分为客户端(CT)侧 API 与服务端能力标志。
5.1 功能开关
Cover Art 功能由 Kconfig 选项控制(Kconfig.in):
Component config --> Bluetooth --> Bluedroid Options --> Classic Bluetooth --> AVRCP Features --> AVRCP CT Cover Art即配置BT_AVRCP_CT_COVER_ART_ENABLED。该选项使能 AVRCP CT Cover Art 功能,且启用后 AVRCP 版本将被设定为 1.6,否则保持在 1.5。该功能依赖 AVRCP 元数据能力(Cover Art 通过ESP_AVRC_MD_ATTR_COVER_ART元数据属性携带 7 字节图像句柄)。
5.2 CT 侧 Cover Art API
| 函数 | 功能 | 说明 |
|---|---|---|
esp_avrc_ct_cover_art_connect(mtu) | 建立用于 Cover Art 的 OBEX 连接 | mtu须在ESP_AVRC_CA_MTU_MIN(255) ~ESP_AVRC_CA_MTU_MAX(1691) 之间,非法值会被重置为最大值;MTU 同时限定了cover_art_data事件中单次数据的最大长度。完成后触发ESP_AVRC_CT_COVER_ART_STATE_EVT;对端不支持时返回ESP_ERR_NOT_SUPPORTED |
esp_avrc_ct_cover_art_disconnect() | 释放 Cover Art OBEX 连接 | 完成后触发ESP_AVRC_CT_COVER_ART_STATE_EVT |
esp_avrc_ct_cover_art_get_image_properties(image_handle) | 获取图像属性 | image_handle长度为ESP_AVRC_CA_IMAGE_HANDLE_LEN(7) 字节,函数返回后即可释放 |
esp_avrc_ct_cover_art_get_image(image_handle, image_descriptor, image_descriptor_len) | 获取图像数据 | image_descriptor会被协议栈内部缓存,API 返回后可释放 |
esp_avrc_ct_cover_art_get_linked_thumbnail(image_handle) | 获取关联缩略图 | 同get_image,获取的是缩略图版本 |
5.3 Cover Art 数据接收流程
调用get_image后,数据通过ESP_AVRC_CT_COVER_ART_DATA_EVT分片到达:
status:为ESP_BT_STATUS_SUCCESS时p_data才有效;final:为true表示这是整个对象的最后一段数据;data_len与p_data:本段数据长度与指针(需在回调内拷贝)。
应用层需要累积所有分片直到final == true,再交给 JPEG/图像解码器处理。
六、官方示例解读:三个 AVRCP 实战项目
官方文档列举了三个与 AVRCP 相关的经典蓝牙示例,均位于 examples/bluetooth/bluedroid/classic_bt 目录,支持目标为 ESP32 与 ESP32-S31。
6.1 avrcp_absolute_volume:绝对音量控制
路径:avrcp_absolute_volume
该示例实现 AVRCP 的绝对音量控制能力。工程通过分层组件组合构建(依赖 bt_app_core_utils、bredr_app_common_utils、a2dp_sink 系列组件以及 avrcp_common_utils 和 avrcp_abs_vol_utils),整体为"AVRCP 绝对值音量工具 → AVRCP 通用工具 → A2DP 工具 → 应用基础工具"的依赖金字塔。
核心初始化流程见 main/main.c 的bt_av_hdl_stack_evt()(蓝牙栈启动后执行):
- 设置设备名称、注册设备与 GAP 回调;
- 先注册并初始化 AVRCP:
esp_avrc_ct_register_callback()→esp_avrc_ct_init(),随后esp_avrc_tg_register_callback()→esp_avrc_tg_init(); - 通过位掩码操作设置 TG 支持的通知事件:只使能
ESP_AVRC_RN_VOLUME_CHANGE,然后调用esp_avrc_tg_set_rn_evt_cap(); - 再初始化 A2DP sink:
esp_a2d_register_callback()→esp_a2d_sink_init(),符合"AVRC 必须先于 A2DP 初始化"的约束; - 设置可被发现、可连接模式,等待手机连接。
从示例输出可以看到完整交互:RC_VC_SRV日志表明本地(TG 侧)音量被连续设置并上报,RC_TG: AVRC register event notification: 13, param: 0x0表明手机(CT 侧)向音箱注册了ESP_AVRC_RN_VOLUME_CHANGE(0x0d=13) 通知:
I (56320) RC_VC_SRV: Volume is set locally to: 3% I (57160) RC_TG: AVRC register event notification: 13, param: 0x0 I (66320) RC_VC_SRV: Volume is set locally to: 7% ...构建与烧录方式为标准 ESP-IDF 流程:idf.py menuconfig配置 →idf.py -p PORT flash monitor烧录并监控串口(按Ctrl-]退出串口监视器)。
6.2 avrcp_ct_metadata:媒体元数据获取
路径:avrcp_ct_metadata
该示例演示设备作为 CT 获取对端播放器的媒体元数据。组件依赖与 6.1 类似,只是将 avrcp_abs_vol_utils 换成 avrcp_metadata_utils。示例输出展示了收到ESP_AVRC_CT_CHANGE_NOTIFY_EVT(曲目变化事件,事件 ID 为 2 即ESP_AVRC_RN_TRACK_CHANGE)后触发元数据请求,进而收到各属性 ID 的元数据响应:
I (81700) RC_CT: AVRC event notification: 2 I (81740) RC_CT: AVRC metadata rsp: attribute id 0x1, // TITLE I (81740) RC_CT: AVRC metadata rsp: attribute id 0x2, // ARTIST I (81740) RC_CT: AVRC metadata rsp: attribute id 0x4, // ALBUM I (81750) RC_CT: AVRC metadata rsp: attribute id 0x20, // GENRE这些属性 ID 与 esp_avrc_api.h 中esp_avrc_md_attr_mask_t的定义一一对应(0x1=标题、0x2=艺术家、0x4=专辑、0x20=流派),实现时在ESP_AVRC_CT_METADATA_RSP_EVT回调中根据meta_rsp.attr_id区分属性并保存attr_text。工程内还带有 pytest_classic_bt_metadata_test.py 自动化测试脚本。
6.3 avrcp_ct_cover_art:封面图获取与显示
路径:avrcp_ct_cover_art
该示例演示设备作为 CT 获取并显示封面图,是三个示例中最完整的端到端应用。相比前两个示例,它额外依赖 avrcp_cover_art_utils,并需要一块 SPI 接口 LCD 屏显示封面:
- 硬件连接:开发板通过 3V3/GND 供电,DATA0→MOSI、PCLK→SCK、CS、D/C、RST、BK_LIGHT 等引脚连接 LCD(GPIO 可在 avrcp_cover_art_service.c 中通过
EXAMPLE_PIN_NUM_*宏修改;背光使能电平因 LCD 模块而异,可通过EXAMPLE_LCD_BK_LIGHT_ON_LEVEL宏调整); - 功能开关:AVRCP CT Cover Art 默认使能,可在 menuconfig 的
Component config --> Bluetooth --> Bluedroid Options --> Classic Bluetooth --> AVRCP Features --> AVRCP CT Cover Art中关闭; - 内存配置(已在 sdkconfig.defaults 中预设):
CONFIG_SPIRAM=y:启用外部 PSRAM,为 A2DP 音频流缓冲与封面图解码显示提供额外内存,避免同时处理音视频数据时耗尽内部 RAM;CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y:选用 single app large 分区表,为包含 A2DP、AVRCP、图像解码库与 LCD 驱动的大型应用二进制提供更大分区。
示例输出展示了完整的封面图获取链路——先是元数据响应携带ESP_AVRC_MD_ATTR_COVER_ART(0x80) 属性(即 7 字节图像句柄),随后 Cover Art 客户端分片接收图像数据,最后完成 JPEG 解码:
I (31579) RC_CT: AVRC metadata rsp: attribute id 0x80, 1000526 I (32039) RC_CA_SRV: Cover Art Client final data event, image size: 12315 bytes I (32119) RC_CA_SRV: JPEG image decoded! Size of the decoded image is: 200px x 200px.工程内同样提供 pytest_classic_bt_cover_art_test.py 自动化测试脚本。
七、协议栈实现纵深:AVRCP 在 Bluedroid 中的层次结构
从源码结构看,ESP-IDF 的 AVRCP 实现遵循 Bluedroid 经典的分层架构,相关代码主要分布在components/bt/host/bluedroid下:
| 层次 | 路径 | 职责 |
|---|---|---|
| 应用 API 层 | api/esp_avrc_api.c 与 api/include/api/esp_avrc_api.h | 向应用暴露esp_avrc_*接口,做参数校验与状态检查 |
| 桥接/回调层(BTC) | btc/profile/std/avrc/btc_avrc.c 与 btc/profile/std/avrc/bta_avrc_co.c | 在应用回调与 BTA 层之间桥接,处理 Cover Art 等回调数据(bta_avrc_co.c即 Call-Out 文件) |
| 协议栈层(BTA/Stack) | bta/av 与 stack/avrc | 实现 AVRCP 报文构建/解析、SDP 发现、命令状态机 |
其中 stack/avrc 内的文件分工明确:avrc_bld_ct.c/avrc_bld_tg.c负责构建 CT/TG 方向的 PDU 报文,avrc_pars_ct.c/avrc_pars_tg.c负责解析对端 PDU,avrc_sdp.c负责 SDP 服务发现(即ESP_AVRC_*_REMOTE_FEATURES_EVT中特性掩码的来源),avrc_api.c与avrc_utils.c提供内部 API 与工具函数。头文件 stack/avrc/include/avrc_int.h 与 stack/include/stack/avrc_defs.h 定义了协议内部的常量与结构。
可以推断,应用调用esp_avrc_ct_send_passthrough_cmd()等命令 API 的典型调用链为:应用 →esp_avrc_api.c(参数校验)→btc_avrc.c(桥接到 BTA)→bta_av状态机 →stack/avrc/avrc_bld_ct.c(构建 AVRCP 报文)→ 通过 AVCTP 传输通道发送到对端;对端响应则沿反向路径经解析、桥接最终以回调事件形式送达应用。
八、开发要点与常见问题
8.1 初始化顺序是硬约束
AVRCP 与 A2DP 耦合,必须严格遵守:
esp_bluedroid_enable() └─ esp_avrc_ct_register_callback() / esp_avrc_tg_register_callback() └─ esp_avrc_ct_init() / esp_avrc_tg_init() // 先 AVRCP └─ esp_a2d_register_callback() / esp_a2d_sink_init() // 后 A2DP反初始化顺序相反:先 A2DP,再 AVRCP。所有 API 在蓝牙栈未使能时返回ESP_ERR_INVALID_STATE。
8.2 事件驱动的编程模型
AVRCP 是异步事件驱动的:命令 API 只负责发送,结果一律通过注册的回调事件返回。因此应用应集中处理esp_avrc_ct_cb_param_t/esp_avrc_tg_cb_param_t联合体中的各类参数,并以ESP_AVRC_CT_CONNECTION_STATE_EVT(connected == true)作为可以开始发送命令的信号。
8.3 事务标签与并发命令
tl(0~15)用于匹配请求与响应,连续命令必须使用不同标签。若多个 AVRCP 操作并发,需要自行维护标签分配。
8.4 Cover Art 数据的生命周期
ESP_AVRC_CT_COVER_ART_DATA_EVT中的p_data只在回调期间有效,必须立即拷贝;图像数据以分片形式到达,须累积至final == true。另外 Cover Art 的 OBEX 连接 MTU 应设置在 255~1691 之间,MTU 越大单次数据事件承载的数据越多。
8.5 内存规划
同时跑 A2DP 音频流和 Cover Art 图像解码/显示时内存压力较大,参考 avrcp_ct_cover_art 的 sdkconfig.defaults,建议开启 PSRAM(CONFIG_SPIRAM=y)并选用大分区表(CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y)。
总结
AVRCP 是蓝牙经典模式音频生态中不可或缺的远程控制协议。ESP-IDF 通过 esp_avrc_api.h 提供了完整的 CT/TG 双角色 API,覆盖 Passthrough 按键透传、媒体元数据、播放状态查询、事件通知注册、播放器应用设置、绝对音量控制以及 AVRCP 1.6 的 Cover Art 封面图传输等全部核心能力。结合仓库内 avrcp_absolute_volume、avrcp_ct_metadata、avrcp_ct_cover_art 三个官方示例,开发者可以快速搭建从简单按键控制到封面图显示的各种蓝牙音频控制应用。开发时牢记"AVRC 与 A2DP 耦合、先 AVRCP 后 A2DP 初始化、事件驱动、事务标签区分并发"这几个核心要点,即可少走弯路。
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考