【AVRCP】规范精讲[18]: 从字节到交互,全流程拆解AVRCP命令与响应实战

在蓝牙音频开发的世界里,很多开发者都有过这样的经历:对着一堆十六进制的字节流发呆,不知道这些数字到底代表什么意思;或者调试了几天,发现只是因为某个命令的某个字节填错了,导致整个功能无法正常工作。AVRCP协议作为蓝牙音频控制的核心,其命令和响应的格式设计非常精巧,但也非常容易出错。


目录

一、AVRCP命令响应的基本框架

[1.1 AVCTP帧头格式](#1.1 AVCTP帧头格式)

[1.2 AVRCP命令体格式](#1.2 AVRCP命令体格式)

[1.3 AVRCP响应体格式](#1.3 AVRCP响应体格式)

[1.4 一个最简单的完整示例](#1.4 一个最简单的完整示例)

[二、直通命令(Pass Through Commands)详解与示例](#二、直通命令(Pass Through Commands)详解与示例)

[2.1 直通命令的基本格式](#2.1 直通命令的基本格式)

[2.2 常用直通命令示例](#2.2 常用直通命令示例)

[2.4 直通命令的常见问题](#2.4 直通命令的常见问题)

[三、通用命令(General Commands)详解与示例](#三、通用命令(General Commands)详解与示例)

[3.1 单元信息命令(Unit Info)](#3.1 单元信息命令(Unit Info))

[3.2 子单元信息命令(Subunit Info)](#3.2 子单元信息命令(Subunit Info))

[3.3 通用命令的实际应用](#3.3 通用命令的实际应用)

四、播放状态与控制命令详解与示例

[4.1 获取播放状态命令(Get Play Status)](#4.1 获取播放状态命令(Get Play Status))

[4.2 播放控制命令](#4.2 播放控制命令)

[4.3 跳转命令(Jump)](#4.3 跳转命令(Jump))

[4.4 播放控制命令的常见问题](#4.4 播放控制命令的常见问题)

五、媒体信息命令详解与示例

[5.1 获取媒体属性命令(Get Media Attributes)](#5.1 获取媒体属性命令(Get Media Attributes))

[5.2 媒体信息通知](#5.2 媒体信息通知)

[5.3 媒体信息命令的常见问题](#5.3 媒体信息命令的常见问题)

六、浏览命令详解与示例

[6.1 浏览命令的基本概念](#6.1 浏览命令的基本概念)

[6.2 获取文件夹内容命令(Get Folder Items)](#6.2 获取文件夹内容命令(Get Folder Items))

[6.3 播放项目命令(Play Item)](#6.3 播放项目命令(Play Item))

[6.4 浏览命令的实际应用](#6.4 浏览命令的实际应用)

七、绝对音量命令详解与示例

[7.1 绝对音量命令的基本原理](#7.1 绝对音量命令的基本原理)

[7.2 音量变化通知](#7.2 音量变化通知)

[7.3 绝对音量命令的优势](#7.3 绝对音量命令的优势)

[7.4 绝对音量命令的常见问题](#7.4 绝对音量命令的常见问题)

八、高级命令详解与示例

[8.1 重复模式命令(Repeat Mode)](#8.1 重复模式命令(Repeat Mode))

[8.2 均衡器命令(Equalizer)](#8.2 均衡器命令(Equalizer))

[8.3 现在播放列表命令(Now Playing List)](#8.3 现在播放列表命令(Now Playing List))

[8.4 高级命令的兼容性问题](#8.4 高级命令的兼容性问题)

九、错误响应与处理

[9.1 错误响应的基本格式](#9.1 错误响应的基本格式)

[9.2 常见错误代码详解](#9.2 常见错误代码详解)

[9.3 错误处理的最佳实践](#9.3 错误处理的最佳实践)

十、代码示例:Android中解析和构造AVRCP命令

[10.1 构造并发送播放命令](#10.1 构造并发送播放命令)

[10.2 解析AVRCP响应](#10.2 解析AVRCP响应)

[10.3 注册广播接收器](#10.3 注册广播接收器)

十一、测验


很多人都知道AVRCP有播放、暂停、上一曲、下一曲这些基本功能,但很少有人真正见过这些功能在底层是如何通过字节流来传输的。规范中提供了上百个完整的命令和响应示例,这些示例是我们理解AVRCP协议最宝贵的资源。本文我们就来逐字节拆解这些示例,从最基础的直通命令到最复杂的浏览命令,彻底搞懂AVRCP命令与响应的每一个细节。


一、AVRCP命令响应的基本框架

在深入具体的命令示例之前,我们首先需要建立一个清晰的AVRCP命令响应框架。所有的AVRCP命令和响应都遵循完全相同的基本格式,理解这个格式是读懂所有示例的基础。

1.1 AVCTP帧头格式

所有的AVRCP命令和响应都封装在AVCTP帧中进行传输。AVCTP帧头长度为3个字节,格式如下:

复制代码
字节1:事务标签(4位) + 数据包类型(2位) + 响应类型(2位)
字节2:PID高8位
字节3:PID低8位

规范中明确指出:

The AVCTP header consists of 3 octets. The first octet contains the Transaction Label, Packet Type, and Response Type fields. The second and third octets contain the Profile Identifier (PID).

我们来逐字段解释:

  • 事务标签(Transaction Label):4位无符号整数,用来标识同一个应用发送的不同命令。每个未完成的事务都必须有一个唯一的事务标签。

  • 数据包类型(Packet Type):2位无符号整数,用来标识数据包的类型。0表示命令帧,1表示响应帧,2表示错误帧,3保留。

  • 响应类型(Response Type):2位无符号整数,只有在响应帧中才有意义。0表示接受,1表示拒绝,2表示进行中,3表示已更改。

  • PID (Profile Identifier):16位无符号整数,AVRCP规范的PID固定为0x110E。

1.2 AVRCP命令体格式

AVCTP帧头之后是AVRCP命令体。AVRCP命令体的格式根据命令类型的不同而有所不同,但所有命令体都以一个操作码(Opcode)开头。

对于大多数命令,AVRCP命令体的格式如下:

复制代码
字节1:操作码(Opcode)
字节2:操作数长度(Operand Length)
字节3及以后:操作数(Operand)

操作码是一个8位无符号整数,用来唯一标识不同的AVRCP命令。例如,播放命令的操作码是0x44,暂停命令的操作码是0x45,停止命令的操作码是0x46。

操作数长度是一个8位无符号整数,用来指示后面操作数字段的长度,以字节为单位。如果操作数长度为0,表示该命令没有操作数。

操作数字段包含了命令的具体参数,长度可变,从0字节到255字节不等。

1.3 AVRCP响应体格式

AVRCP响应体的格式与命令体类似,也以一个操作码开头。响应体的操作码与对应的命令体的操作码完全相同。

对于大多数响应,AVRCP响应体的格式如下:

复制代码
字节1:操作码(Opcode)
字节2:操作数长度(Operand Length)
字节3及以后:操作数(Operand)

响应体的操作数字段包含了命令的执行结果。如果命令执行成功,操作数字段包含返回的数据;如果命令执行失败,操作数字段包含错误代码。

1.4 一个最简单的完整示例

为了让大家有一个直观的认识,我们来看一个最简单的完整AVRCP命令和响应示例------播放命令。

播放命令帧

复制代码
0x00 0x00 0x11 0x0E 0x44 0x00

我们来逐字节解析:

  • 0x00:事务标签0x0,数据包类型0x0(命令帧),响应类型0x0

  • 0x11 0x0E:PID 0x110E(AVRCP)

  • 0x44:操作码0x44(播放命令)

  • 0x00:操作数长度0x00(没有操作数)

播放响应帧

复制代码
0x00 0x01 0x11 0x0E 0x44 0x00

逐字节解析:

  • 0x00:事务标签0x0(与命令相同),数据包类型0x1(响应帧),响应类型0x0(接受)

  • 0x11 0x0E:PID 0x110E(AVRCP)

  • 0x44:操作码0x44(与命令相同)

  • 0x00:操作数长度0x00(没有操作数)

就是这短短的6个字节,实现了我们每天都在使用的播放功能。是不是很神奇?接下来我们就来逐一拆解各种类型的AVRCP命令和响应。

二、直通命令(Pass Through Commands)详解与示例

直通命令是AVRCP协议中最基础也是最常用的命令类型。直通命令用来模拟传统红外遥控器上的按键操作,每一个直通命令对应遥控器上的一个按键。

2.1 直通命令的基本格式

直通命令的操作码是0x7C。直通命令的操作数长度固定为2字节,操作数字段包含按键ID和按键状态。

直通命令的格式如下:

复制代码
字节1:操作码0x7C
字节2:操作数长度0x02
字节3:按键ID(Key ID)
字节4:按键状态(Key State)

按键ID是一个8位无符号整数,用来标识不同的按键。规范中定义了超过50个不同的按键ID,覆盖了从基本的播放控制到高级的菜单导航等各种功能。

按键状态是一个8位无符号整数,用来指示按键的状态。0x00表示按键按下,0x01表示按键释放。

规范中明确说明:

The Pass Through command is used to send user input events from the Controller to the Target. Each Pass Through command corresponds to a single key press or release event.

这里有一个非常重要的细节:每一个按键操作都需要发送两个直通命令,一个是按键按下命令,一个是按键释放命令。只发送按键按下命令而不发送按键释放命令,可能会导致目标设备认为按键一直被按住,从而产生意想不到的行为。

2.2 常用直通命令示例

下面我们来看一些最常用的直通命令示例。所有示例都假设事务标签为0x0。

2.2.1 播放按键

播放按键按下命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x44 0x00

播放按键释放命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x44 0x01

2.2.2 暂停按键

暂停按键按下命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x45 0x00

暂停按键释放命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x45 0x01

2.2.3 停止按键

停止按键按下命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x46 0x00

停止按键释放命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x46 0x01

2.2.4 上一曲按键

上一曲按键按下命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x4B 0x00

上一曲按键释放命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x4B 0x01

2.2.5 下一曲按键

下一曲按键按下命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x4C 0x00

下一曲按键释放命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x4C 0x01

2.2.6 音量加按键

音量加按键按下命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x41 0x00

音量加按键释放命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x41 0x01

2.2.7 音量减按键

音量减按键按下命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x42 0x00

音量减按键释放命令

复制代码
0x00 0x00 0x11 0x0E 0x7C 0x02 0x42 0x01

2.3 直通命令的响应

直通命令的响应非常简单。如果目标设备成功接收到并处理了直通命令,它会返回一个接受响应,操作数长度为0。

直通命令成功响应

复制代码
0x00 0x01 0x11 0x0E 0x7C 0x00

如果目标设备无法处理该直通命令,例如不支持该按键ID,它会返回一个拒绝响应,并在操作数字段中包含错误代码。

直通命令拒绝响应

复制代码
0x00 0x01 0x11 0x0E 0x7C 0x01 0x01

这里的0x01是错误代码,表示不支持的操作码。

2.4 直通命令的常见问题

在实际开发过程中,直通命令是最容易出现问题的地方。下面我们来看几个最常见的问题及其解决方案。

问题一:按键无响应

这是最常见的问题。按下耳机上的按键,手机没有任何反应。

可能原因

  1. 只发送了按键按下命令,没有发送按键释放命令

  2. 按键ID错误

  3. 事务标签重复

  4. 目标设备没有正确注册AVRCP服务

解决方案

  1. 确保每一个按键操作都发送按下和释放两个命令

  2. 检查按键ID是否符合规范

  3. 确保每个未完成的事务都有唯一的事务标签

  4. 检查目标设备的AVRCP服务是否正常运行

问题二:长按功能不正常

很多耳机支持长按功能,例如长按播放键挂断电话,长按音量键快进快退。但有时候长按功能会不正常。

可能原因

  1. 按键释放命令发送得太早

  2. 目标设备的长按检测时间设置不合理

  3. 没有正确处理按键重复事件

解决方案

  1. 确保在用户实际释放按键后再发送释放命令

  2. 调整目标设备的长按检测时间,通常为500-1000毫秒

  3. 对于需要支持长按重复的按键,在按键按住期间定期发送重复的按下命令

问题三:多个按键同时按下导致混乱

当用户同时按下多个按键时,可能会导致目标设备处理混乱。

可能原因

  1. 多个按键命令的事务标签相同

  2. 命令发送顺序混乱

  3. 目标设备不支持同时处理多个按键命令

解决方案

  1. 确保每个按键命令都有唯一的事务标签

  2. 按照按键按下的顺序发送命令

  3. 对于不支持同时处理多个按键的设备,避免同时发送多个按键命令

三、通用命令(General Commands)详解与示例

通用命令是AVRCP协议中用来获取设备基本信息的命令。这些命令通常在连接建立后立即发送,用来了解目标设备的能力和特性。

3.1 单元信息命令(Unit Info)

单元信息命令用来获取目标设备的基本信息,包括公司ID、单元类型和单元版本。

单元信息命令的操作码是0x30。单元信息命令没有操作数,操作数长度为0。

单元信息命令

复制代码
0x00 0x00 0x11 0x0E 0x30 0x00

单元信息响应的操作数长度为7字节,格式如下:

复制代码
字节1:操作码0x30
字节2:操作数长度0x07
字节3-5:公司ID(Company ID)
字节6:单元类型(Unit Type)
字节7:单元版本(Unit Version)

公司ID是一个24位无符号整数,由蓝牙SIG分配给各个公司。例如,苹果公司的ID是0x004C,三星公司的ID是0x0075,谷歌公司的ID是0x00E0。

单元类型是一个8位无符号整数,用来标识设备的类型。例如,音频设备的单元类型是0x0A,视频设备的单元类型是0x0B。

单元版本是一个8位无符号整数,用来标识设备的固件版本。

单元信息响应示例

复制代码
0x00 0x01 0x11 0x0E 0x30 0x07 0x00 0x4C 0x00 0x0A 0x01 0x00 0x00

逐字节解析:

  • 0x00 0x01 0x11 0x0E:AVCTP帧头,事务标签0x0,响应帧,接受,PID 0x110E

  • 0x30:操作码0x30(单元信息)

  • 0x07:操作数长度0x07

  • 0x00 0x4C 0x00:公司ID 0x004C00(苹果公司)

  • 0x0A:单元类型0x0A(音频设备)

  • 0x01 0x00 0x00:单元版本0x010000

3.2 子单元信息命令(Subunit Info)

子单元信息命令用来获取目标设备的子单元信息。一个AVRCP设备可以包含多个子单元,每个子单元负责不同的功能。

子单元信息命令的操作码是0x31。子单元信息命令的操作数长度为1字节,操作数字段包含子单元类型和子单元ID。

子单元信息命令格式

复制代码
字节1:操作码0x31
字节2:操作数长度0x01
字节3:子单元类型(5位) + 子单元ID(3位)

子单元类型是一个5位无符号整数,用来标识子单元的类型。例如,音频子单元的类型是0x04,视频子单元的类型是0x05。

子单元ID是一个3位无符号整数,用来标识同一类型的不同子单元。子单元ID从0开始编号。

如果子单元类型和子单元ID都设置为0x1F,表示请求获取所有子单元的信息。

获取所有子单元信息命令

复制代码
0x00 0x00 0x11 0x0E 0x31 0x01 0xFF

子单元信息响应的操作数长度可变,格式如下:

复制代码
字节1:操作码0x31
字节2:操作数长度
字节3及以后:子单元信息列表

每个子单元信息条目长度为1字节,格式与命令中的子单元类型和ID字段相同。

子单元信息响应示例

复制代码
0x00 0x01 0x11 0x0E 0x31 0x02 0x20 0x40

逐字节解析:

  • 0x00 0x01 0x11 0x0E:AVCTP帧头

  • 0x31:操作码0x31(子单元信息)

  • 0x02:操作数长度0x02

  • 0x20:子单元类型0x04(音频),子单元ID 0x00

  • 0x40:子单元类型0x08(面板),子单元ID 0x00

这个响应表示目标设备包含两个子单元:一个音频子单元和一个面板子单元。

3.3 通用命令的实际应用

通用命令虽然不直接控制媒体播放,但它们在实际应用中非常重要。控制器设备通常在连接建立后立即发送单元信息和子单元信息命令,以了解目标设备的能力。

例如,当手机连接到车载蓝牙系统时,手机会首先发送单元信息命令,获取车载系统的公司ID和版本信息。然后根据这些信息,决定启用哪些AVRCP功能。如果车载系统是苹果公司的CarPlay设备,手机会启用CarPlay特有的AVRCP扩展功能;如果车载系统是普通的蓝牙设备,手机只会启用标准的AVRCP功能。

四、播放状态与控制命令详解与示例

播放状态与控制命令是AVRCP协议的核心功能。这些命令用来获取当前播放状态,以及控制媒体的播放、暂停、停止、快进、快退等操作。

4.1 获取播放状态命令(Get Play Status)

获取播放状态命令用来获取目标设备当前的播放状态,包括播放状态、播放位置和播放速度。

获取播放状态命令的操作码是0x50。获取播放状态命令没有操作数,操作数长度为0。

获取播放状态命令

复制代码
0x00 0x00 0x11 0x0E 0x50 0x00

获取播放状态响应的操作数长度为9字节,格式如下:

复制代码
字节1:操作码0x50
字节2:操作数长度0x09
字节3:播放状态(Play Status)
字节4-7:歌曲长度(Song Length),单位毫秒
字节8-11:当前播放位置(Current Position),单位毫秒

播放状态是一个8位无符号整数,可能的取值如下:

  • 0x00:停止(Stopped)

  • 0x01:播放(Playing)

  • 0x02:暂停(Paused)

  • 0x03:快进(Fast Forward)

  • 0x04:快退(Rewind)

  • 0x05:错误(Error)

歌曲长度是一个32位无符号整数,表示当前歌曲的总长度,单位为毫秒。

当前播放位置是一个32位无符号整数,表示当前播放的位置,单位为毫秒。

获取播放状态响应示例

复制代码
0x00 0x01 0x11 0x0E 0x50 0x09 0x01 0x00 0x03 0x0D 0x40 0x00 0x00 0x1E 0x00

逐字节解析:

  • 0x00 0x01 0x11 0x0E:AVCTP帧头

  • 0x50:操作码0x50(获取播放状态)

  • 0x09:操作数长度0x09

  • 0x01:播放状态0x01(正在播放)

  • 0x00 0x03 0x0D 0x40:歌曲长度0x00030D40毫秒,即200000毫秒,也就是3分20秒

  • 0x00 0x00 0x1E 0x00:当前播放位置0x00001E00毫秒,即7680毫秒,也就是7.68秒

4.2 播放控制命令

播放控制命令用来控制媒体的播放操作。AVRCP协议定义了多种播放控制命令,包括播放、暂停、停止、快进、快退等。

4.2.1 播放命令(Play)

播放命令的操作码是0x44。播放命令没有操作数,操作数长度为0。

播放命令

复制代码
0x00 0x00 0x11 0x0E 0x44 0x00

播放命令响应

复制代码
0x00 0x01 0x11 0x0E 0x44 0x00

4.2.2 暂停命令(Pause)

暂停命令的操作码是0x45。暂停命令没有操作数,操作数长度为0。

暂停命令

复制代码
0x00 0x00 0x11 0x0E 0x45 0x00

暂停命令响应

复制代码
0x00 0x01 0x11 0x0E 0x45 0x00

4.2.3 停止命令(Stop)

停止命令的操作码是0x46。停止命令没有操作数,操作数长度为0。

停止命令

复制代码
0x00 0x00 0x11 0x0E 0x46 0x00

停止命令响应

复制代码
0x00 0x01 0x11 0x0E 0x46 0x00

4.2.4 快进命令(Fast Forward)

快进命令的操作码是0x47。快进命令没有操作数,操作数长度为0。

快进命令

复制代码
0x00 0x00 0x11 0x0E 0x47 0x00

快进命令响应

复制代码
0x00 0x01 0x11 0x0E 0x47 0x00

4.2.5 快退命令(Rewind)

快退命令的操作码是0x48。快退命令没有操作数,操作数长度为0。

快退命令

复制代码
0x00 0x00 0x11 0x0E 0x48 0x00

快退命令响应

复制代码
0x00 0x01 0x11 0x0E 0x48 0x00

4.3 跳转命令(Jump)

跳转命令用来跳转到指定的播放位置。跳转命令的操作码是0x51。跳转命令的操作数长度为4字节,操作数字段包含要跳转到的位置,单位为毫秒。

跳转命令格式

复制代码
字节1:操作码0x51
字节2:操作数长度0x04
字节3-6:跳转位置(Jump Position),单位毫秒

跳转到1分钟位置命令

复制代码
0x00 0x00 0x11 0x0E 0x51 0x04 0x00 0x00 0xEA 0x60

这里的0x0000EA60是60000毫秒,也就是1分钟。

跳转命令响应

复制代码
0x00 0x01 0x11 0x0E 0x51 0x00

4.4 播放控制命令的常见问题

播放控制命令在实际使用中也会遇到一些问题。下面我们来看几个最常见的问题及其解决方案。

问题一:播放状态不同步

控制器设备显示的播放状态与目标设备的实际播放状态不一致。

可能原因

  1. 控制器没有定期发送获取播放状态命令

  2. 目标设备没有在播放状态发生变化时主动通知控制器

  3. 网络延迟导致状态更新不及时

解决方案

  1. 控制器应该定期发送获取播放状态命令,通常每1-5秒发送一次

  2. 目标设备应该支持播放状态变化通知,在播放状态发生变化时主动向控制器发送通知

  3. 优化蓝牙连接,减少网络延迟

问题二:跳转命令不准确

发送跳转命令后,实际跳转到的位置与指定的位置不一致。

可能原因

  1. 目标设备不支持精确跳转

  2. 媒体文件的索引信息不完整

  3. 跳转位置超出了歌曲的长度范围

解决方案

  1. 检查目标设备是否支持精确跳转功能

  2. 确保媒体文件的索引信息完整

  3. 在发送跳转命令前,先获取歌曲长度,确保跳转位置在有效范围内

问题三:快进快退功能不正常

快进快退功能要么没有反应,要么速度太快或太慢。

可能原因

  1. 目标设备不支持快进快退功能

  2. 快进快退的速度设置不合理

  3. 没有正确处理快进快退的停止命令

解决方案

  1. 检查目标设备是否支持快进快退功能

  2. 调整快进快退的速度,通常为正常播放速度的2-16倍

  3. 在快进快退结束后,发送播放命令恢复正常播放

五、媒体信息命令详解与示例

媒体信息命令用来获取当前播放媒体的详细信息,包括歌曲名称、艺术家、专辑、时长等。这些信息可以显示在控制器设备的屏幕上,为用户提供更好的使用体验。

5.1 获取媒体属性命令(Get Media Attributes)

获取媒体属性命令用来获取当前播放媒体的属性信息。获取媒体属性命令的操作码是0x20。

获取媒体属性命令的操作数长度可变,格式如下:

复制代码
字节1:操作码0x20
字节2:操作数长度
字节3:属性数量(Number of Attributes)
字节4及以后:属性ID列表(Attribute ID List)

属性数量是一个8位无符号整数,表示要获取的属性的数量。

属性ID列表是一个属性ID的数组,每个属性ID是一个32位无符号整数。规范中定义了多种标准属性ID,包括:

  • 0x00000001:标题(Title)

  • 0x00000002:艺术家(Artist)

  • 0x00000003:专辑(Album)

  • 0x00000004:曲目号(Track Number)

  • 0x00000005:总曲目数(Total Number of Tracks)

  • 0x00000006:流派(Genre)

  • 0x00000007:播放时长(Playing Time)

如果属性数量设置为0,表示请求获取所有可用的媒体属性。

获取所有媒体属性命令

复制代码
0x00 0x00 0x11 0x0E 0x20 0x01 0x00

获取标题和艺术家命令

复制代码
0x00 0x00 0x11 0x0E 0x20 0x09 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x02

获取媒体属性响应的操作数长度可变,格式如下:

复制代码
字节1:操作码0x20
字节2:操作数长度
字节3:属性数量
字节4及以后:属性列表

每个属性条目格式如下:

复制代码
0x00 0x01 0x11 0x0E 0x20 0x37 0x02 0x00 0x00 0x00 0x01 0x00 0x0E 0xE5 0xA4 0xA9 0xE6 0x98 0x8E 0xE5 0xA4 0xA9 0xE7 0x9A 0x84 0xE4 0xB9 0x90 0xE5 0xA4 0xA9 0x00 0x00 0x00 0x02 0x00 0x06 0xE5 0xBC 0xA0 0xE5 0xA4 0x96 0xE5 0x8D 0x8E

获取媒体属性响应示例

复制代码
0x00 0x02 0x11 0x0E 0x21 0x37 0x02 0x00 0x00 0x00 0x01 0x00 0x0E 0xE5 0xA4 0xA9 0xE6 0x98 0x8E 0xE5 0xA4 0xA9 0xE7 0x9A 0x84 0xE4 0xB9 0x90 0xE5 0xA4 0xA9 0x00 0x00 0x00 0x02 0x00 0x06 0xE5 0xBC 0xA0 0xE5 0xA4 0x96 0xE5 0x8D 0x8E

逐字节解析:

  • 0x00 0x01 0x11 0x0E:AVCTP帧头

  • 0x20:操作码0x20(获取媒体属性)

  • 0x37:操作数长度0x37(55字节)

  • 0x02:属性数量0x02(2个属性)

  • 第一个属性:

    • 0x00 0x00 0x00 0x01:属性ID 0x00000001(标题)

    • 0x00 0x0E:属性值长度0x0E(14字节)

    • 0xE5 0xA4 0xA9 0xE6 0x98 0x8E 0xE5 0xA4 0xA9 0xE7 0x9A 0x84 0xE4 0xB9 0x90 0xE5 0xA4 0xA9:UTF-8编码的"夏天的风"

  • 第二个属性:

    • 0x00 0x00 0x00 0x02:属性ID 0x00000002(艺术家)

    • 0x00 0x06:属性值长度0x06(6字节)

    • 0xE5 0xBC 0xA0 0xE5 0xA4 0x96 0xE5 0x8D 0x8E:UTF-8编码的"周杰伦"

5.2 媒体信息通知

除了主动获取媒体属性外,目标设备还可以在媒体信息发生变化时主动向控制器发送通知。这种机制称为媒体信息通知。

媒体信息通知使用操作码0x21。媒体信息通知的格式与获取媒体属性响应的格式完全相同。

媒体信息通知示例

复制代码
0x00 0x02 0x11 0x0E 0x21 0x37 0x02 0x00 0x00 0x00 0x01 0x00 0x0E 0xE5 0xA4 0xA9 0xE6 0x98 0x8E 0xE5 0xA4 0xA9 0xE7 0x9A 0x84 0xE4 0xB9 0x90 0xE5 0xA4 0xA9 0x00 0x00 0x00 0x02 0x00 0x06 0xE5 0xBC 0xA0 0xE5 0xA4 0x96 0xE5 0x8D 0x8E

注意这里的AVCTP帧头中的数据包类型是0x02,表示这是一个通知帧,而不是响应帧。

媒体信息通知机制大大提高了效率。控制器不需要定期发送获取媒体属性命令,只需要在收到通知时更新显示即可。这不仅减少了蓝牙数据传输量,还降低了电量消耗。

5.3 媒体信息命令的常见问题

媒体信息命令在实际使用中也会遇到一些问题。下面我们来看几个最常见的问题及其解决方案。

问题一:媒体信息显示乱码

控制器设备上显示的歌曲名称、艺术家等信息是乱码。

可能原因

  1. 目标设备使用了错误的字符编码

  2. 控制器设备没有正确解析UTF-8编码

  3. 媒体文件的元数据编码不正确

解决方案

  1. 确保目标设备使用UTF-8编码传输媒体信息

  2. 确保控制器设备正确解析UTF-8编码

  3. 检查媒体文件的元数据,确保使用正确的编码

问题二:媒体信息不更新

当歌曲切换时,控制器设备上显示的媒体信息没有更新。

可能原因

  1. 目标设备不支持媒体信息通知

  2. 控制器没有注册接收媒体信息通知

  3. 目标设备在歌曲切换时没有发送通知

解决方案

  1. 检查目标设备是否支持媒体信息通知功能

  2. 确保控制器正确注册接收媒体信息通知

  3. 如果目标设备不支持通知,控制器需要定期发送获取媒体属性命令

问题三:部分媒体信息缺失

控制器设备上只能显示部分媒体信息,例如只能显示歌曲名称,不能显示艺术家和专辑。

可能原因

  1. 目标设备不支持这些媒体属性

  2. 媒体文件中没有包含这些元数据

  3. 获取媒体属性命令中没有请求这些属性

解决方案

  1. 检查目标设备支持的媒体属性列表

  2. 检查媒体文件的元数据,确保包含所需的信息

  3. 在获取媒体属性命令中明确请求所需的属性

六、浏览命令详解与示例

浏览命令是AVRCP 1.4及以上版本新增的功能,允许控制器设备浏览目标设备上的媒体内容,包括文件夹、播放列表、歌曲等。浏览功能大大扩展了AVRCP协议的能力,使用户可以直接在控制器设备上选择要播放的媒体内容。

6.1 浏览命令的基本概念

浏览命令基于一个虚拟文件系统模型。目标设备上的所有媒体内容都组织成一个层次化的文件夹结构。控制器可以通过浏览命令遍历这个文件夹结构,查看文件夹中的内容,并选择要播放的项目。

浏览命令使用一个单独的AVCTP连接进行传输,这个连接称为浏览连接。浏览连接的PID是0x110B,而不是普通AVRCP命令使用的0x110E。

规范中明确指出:

Browsing commands are transmitted over a separate AVCTP connection with PID 0x110B. This allows browsing operations to be performed without interfering with normal playback control operations.

使用单独的连接有很多好处:

  1. 浏览操作不会干扰正常的播放控制操作

  2. 浏览数据量通常比较大,使用单独的连接可以避免影响播放控制的实时性

  3. 可以同时进行浏览和播放控制操作

6.2 获取文件夹内容命令(Get Folder Items)

获取文件夹内容命令用来获取指定文件夹中的内容列表。获取文件夹内容命令的操作码是0x71。

获取文件夹内容命令的操作数格式如下:

复制代码
字节1:操作码0x71
字节2:操作数长度
字节3-4:范围起始索引(Start Index)
字节5-6:范围结束索引(End Index)
字节7-10:文件夹UID(Folder UID)

范围起始索引和范围结束索引是16位无符号整数,用来指定要获取的项目范围。例如,如果文件夹中有100个项目,我们可以通过设置起始索引为0,结束索引为9,获取前10个项目。

文件夹UID是一个32位无符号整数,用来唯一标识一个文件夹。根文件夹的UID是0x00000000。

获取根文件夹前10个项目命令

复制代码
0x00 0x00 0x11 0x0B 0x71 0x0A 0x00 0x00 0x00 0x09 0x00 0x00 0x00 0x00

获取文件夹内容响应的操作数格式如下:

复制代码
字节1:操作码0x71
字节2:操作数长度
字节3-4:项目总数(Total Number of Items)
字节5-6:返回项目数(Number of Items Returned)
字节7及以后:项目列表

每个项目条目格式如下:

复制代码
字节1-4:项目UID(Item UID)
字节5:项目类型(Item Type)
字节6-7:属性数量
字节8及以后:属性列表

项目类型是一个8位无符号整数,可能的取值如下:

  • 0x01:文件夹(Folder)

  • 0x02:歌曲(Song)

  • 0x03:播放列表(Playlist)

属性列表的格式与媒体属性命令中的属性列表格式相同。

获取文件夹内容响应示例

复制代码
0x00 0x01 0x11 0x0B 0x71 0x4A 0x00 0x02 0x00 0x02 0x00 0x00 0x00 0x01 0x01 0x01 0x00 0x00 0x00 0x01 0x00 0x0C 0xE6 0x88 0x91 0xE7 0x88 0xB1 0xE9 0x9F 0xB3 0xE4 0xB9 0x90 0x00 0x00 0x00 0x02 0x02 0x02 0x00 0x00 0x00 0x01 0x00 0x0E 0xE5 0xA4 0xA9 0xE6 0x98 0x8E 0xE5 0xA4 0xA9 0xE7 0x9A 0x84 0xE4 0xB9 0x90 0xE5 0xA4 0xA9 0x00 0x00 0x00 0x02 0x00 0x06 0xE5 0xBC 0xA0 0xE5 0xA4 0x96 0xE5 0x8D 0x8E

逐字节解析:

  • 0x00 0x01 0x11 0x0B:AVCTP帧头,PID 0x110B(浏览)

  • 0x71:操作码0x71(获取文件夹内容)

  • 0x4A:操作数长度0x4A(74字节)

  • 0x00 0x02:项目总数0x0002(2个项目)

  • 0x00 0x02:返回项目数0x0002(2个项目)

  • 第一个项目:

    • 0x00 0x00 0x00 0x01:项目UID 0x00000001

    • 0x01:项目类型0x01(文件夹)

    • 0x01:属性数量0x01(1个属性)

    • 0x00 0x00 0x00 0x01:属性ID 0x00000001(标题)

    • 0x00 0x0C:属性值长度0x0C(12字节)

    • 0xE6 0x88 0x91 0xE7 0x88 0xB1 0xE9 0x9F 0xB3 0xE4 0xB9 0x90:UTF-8编码的"我的音乐"

  • 第二个项目:

    • 0x00 0x00 0x00 0x02:项目UID 0x00000002

    • 0x02:项目类型0x02(歌曲)

    • 0x02:属性数量0x02(2个属性)

    • 第一个属性:

      • 0x00 0x00 0x00 0x01:属性ID 0x00000001(标题)

      • 0x00 0x0E:属性值长度0x0E(14字节)

      • 0xE5 0xA4 0xA9 0xE6 0x98 0x8E 0xE5 0xA4 0xA9 0xE7 0x9A 0x84 0xE4 0xB9 0x90 0xE5 0xA4 0xA9:UTF-8编码的"夏天的风"

    • 第二个属性:

      • 0x00 0x00 0x00 0x02:属性ID 0x00000002(艺术家)

      • 0x00 0x06:属性值长度0x06(6字节)

      • 0xE5 0xBC 0xA0 0xE5 0xA4 0x96 0xE5 0x8D 0x8E:UTF-8编码的"周杰伦"

6.3 播放项目命令(Play Item)

播放项目命令用来播放指定的项目。播放项目命令的操作码是0x74。

播放项目命令的操作数格式如下:

复制代码
字节1:操作码0x74
字节2:操作数长度
字节3-6:项目UID(Item UID)
字节7:播放范围(Play Scope)

项目UID是一个32位无符号整数,用来唯一标识要播放的项目。

播放范围是一个8位无符号整数,用来指定播放范围。可能的取值如下:

  • 0x00:只播放指定项目

  • 0x01:播放指定项目及其后面的所有项目

  • 0x02:播放整个文件夹

播放指定歌曲命令

复制代码
0x00 0x00 0x11 0x0B 0x74 0x05 0x00 0x00 0x00 0x02 0x00

播放项目命令响应

复制代码
0x00 0x01 0x11 0x0B 0x74 0x00

6.4 浏览命令的实际应用

浏览命令为用户提供了非常强大的功能。例如,当你的手机连接到车载蓝牙系统时,你可以直接在车载屏幕上浏览手机上的所有音乐文件夹和歌曲,选择你想要播放的歌曲,而不需要操作手机。这大大提高了驾驶安全性。

但浏览命令也有一些局限性。首先,浏览命令需要目标设备支持AVRCP 1.4及以上版本。其次,浏览数据量通常比较大,当文件夹中有很多项目时,浏览速度可能会比较慢。最后,不同设备对浏览命令的实现可能存在差异,导致兼容性问题。

七、绝对音量命令详解与示例

绝对音量命令是AVRCP 1.4及以上版本新增的功能,允许控制器设备精确控制目标设备的音量。在绝对音量功能出现之前,控制器设备只能通过音量加和音量减的直通命令来调整音量,无法知道目标设备的当前音量,也无法直接设置到指定的音量级别。

7.1 绝对音量命令的基本原理

绝对音量命令使用一个0-127的数值来表示音量级别。0表示静音,127表示最大音量。这个数值是线性的,也就是说,音量级别64对应的音量是最大音量的一半。

规范中明确说明:

The absolute volume is represented as an 8-bit unsigned integer ranging from 0 to 127. A value of 0 represents mute, and a value of 127 represents maximum volume. The volume scale is linear.

绝对音量命令使用操作码0x50。绝对音量命令的操作数长度为1字节,操作数字段包含要设置的音量级别。

设置绝对音量命令格式

复制代码
字节1:操作码0x50
字节2:操作数长度0x01
字节3:音量级别(Volume Level)

设置音量为50%命令

复制代码
0x00 0x00 0x11 0x0E 0x50 0x01 0x40

这里的0x40是64,对应最大音量的50%。

设置绝对音量命令响应

复制代码
0x00 0x01 0x11 0x0E 0x50 0x01 0x40

响应中的音量级别是目标设备实际设置的音量级别。如果目标设备不支持指定的音量级别,它会返回最接近的支持的音量级别。

7.2 音量变化通知

除了主动设置音量外,目标设备还可以在音量发生变化时主动向控制器发送通知。这种机制称为音量变化通知。

音量变化通知使用操作码0x51。音量变化通知的格式与设置绝对音量命令的格式完全相同。

音量变化通知示例

复制代码
0x00 0x02 0x11 0x0E 0x51 0x01 0x40

注意这里的AVCTP帧头中的数据包类型是0x02,表示这是一个通知帧。

音量变化通知机制确保了控制器设备和目标设备的音量始终保持同步。当用户在目标设备上调整音量时,控制器设备会立即收到通知,并更新显示的音量级别。

7.3 绝对音量命令的优势

与传统的音量加和音量减直通命令相比,绝对音量命令具有以下几个明显的优势:

  1. 精确控制:可以直接设置到任意音量级别,而不需要多次按音量加或减按钮

  2. 同步显示:控制器设备可以准确显示目标设备的当前音量级别

  3. 快速调整:可以快速从静音调整到最大音量,或者从最大音量调整到静音

  4. 双向同步:无论是在控制器设备上调整音量,还是在目标设备上调整音量,双方都会保持同步

7.4 绝对音量命令的常见问题

绝对音量命令在实际使用中也会遇到一些问题。下面我们来看几个最常见的问题及其解决方案。

问题一:音量不同步

控制器设备显示的音量与目标设备的实际音量不一致。

可能原因

  1. 目标设备不支持绝对音量功能

  2. 控制器没有注册接收音量变化通知

  3. 目标设备在音量变化时没有发送通知

解决方案

  1. 检查目标设备是否支持绝对音量功能

  2. 确保控制器正确注册接收音量变化通知

  3. 如果目标设备不支持绝对音量,只能使用传统的音量加和减命令

问题二:音量调整不精确

设置的音量级别与实际听到的音量不一致。

可能原因

  1. 目标设备的音量曲线不是线性的

  2. 目标设备只支持有限的音量级别

  3. 音频输出设备的特性导致音量感知不同

解决方案

  1. 调整目标设备的音量曲线,使其尽可能接近线性

  2. 在控制器端进行适当的补偿,以匹配目标设备的音量特性

  3. 向用户说明音量级别是相对的,可能会因设备不同而有所差异

问题三:绝对音量功能导致音量过大或过小

有些用户反映,开启绝对音量功能后,音量要么太大,要么太小。

可能原因

  1. 不同设备对绝对音量的实现存在差异

  2. 设备之间的音量校准不一致

  3. 音频源的音量本身过大或过小

解决方案

  1. 在控制器端提供音量校准功能,允许用户调整音量范围

  2. 提供一个开关,允许用户选择是否使用绝对音量功能

  3. 建议用户调整音频源的音量到合适的水平

八、高级命令详解与示例

除了上面介绍的基本命令外,AVRCP协议还定义了一些高级命令,用来实现更复杂的功能。下面我们来介绍几个最常用的高级命令。

8.1 重复模式命令(Repeat Mode)

重复模式命令用来设置播放的重复模式。重复模式命令的操作码是0x52。

重复模式命令的操作数长度为1字节,操作数字段包含重复模式。

重复模式命令格式

复制代码
字节1:操作码0x52
字节2:操作数长度0x01
字节3:重复模式(Repeat Mode)

重复模式是一个8位无符号整数,可能的取值如下:

  • 0x00:不重复(Off)

  • 0x01:单曲重复(Single Track Repeat)

  • 0x02:全部重复(All Tracks Repeat)

  • 0x03:随机播放(Shuffle)

设置全部重复命令

复制代码
0x00 0x00 0x11 0x0E 0x52 0x01 0x02

重复模式命令响应

复制代码
0x00 0x01 0x11 0x0E 0x52 0x01 0x02

8.2 均衡器命令(Equalizer)

均衡器命令用来设置目标设备的均衡器模式。均衡器命令的操作码是0x53。

均衡器命令的操作数长度为1字节,操作数字段包含均衡器模式。

均衡器命令格式

复制代码
字节1:操作码0x53
字节2:操作数长度0x01
字节3:均衡器模式(Equalizer Mode)

均衡器模式是一个8位无符号整数,可能的取值如下:

  • 0x00:关闭(Off)

  • 0x01:摇滚(Rock)

  • 0x02:流行(Pop)

  • 0x03:爵士(Jazz)

  • 0x04:古典(Classical)

  • 0x05:舞曲(Dance)

  • 0x06:重低音(Bass)

设置摇滚均衡器命令

复制代码
0x00 0x00 0x11 0x0E 0x53 0x01 0x01

均衡器命令响应

复制代码
0x00 0x01 0x11 0x0E 0x53 0x01 0x01

8.3 现在播放列表命令(Now Playing List)

现在播放列表命令用来获取当前播放列表的内容。现在播放列表命令的操作码是0x60。

现在播放列表命令的操作数格式与获取文件夹内容命令的格式类似:

复制代码
字节1:操作码0x60
字节2:操作数长度
字节3-4:范围起始索引
字节5-6:范围结束索引

获取现在播放列表前10个项目命令

复制代码
0x00 0x00 0x11 0x0E 0x60 0x04 0x00 0x00 0x00 0x09

现在播放列表响应的格式与获取文件夹内容响应的格式类似。

8.4 高级命令的兼容性问题

需要注意的是,并不是所有的AVRCP设备都支持这些高级命令。很多低端设备只支持最基本的播放控制命令,不支持重复模式、均衡器等高级功能。

在实际开发中,我们应该首先检查目标设备支持的功能列表,然后再决定是否使用这些高级命令。如果目标设备不支持某个高级命令,它会返回一个拒绝响应,我们应该优雅地处理这种情况,而不是崩溃或产生错误。

九、错误响应与处理

在实际使用中,并不是所有的命令都会成功执行。目标设备可能会因为各种原因无法执行命令,并返回一个错误响应。正确处理错误响应是编写健壮的AVRCP应用的关键。

9.1 错误响应的基本格式

错误响应的AVCTP帧头中的数据包类型是0x01(响应帧),响应类型是0x01(拒绝)。

错误响应的命令体格式如下:

复制代码
字节1:操作码(与命令相同)
字节2:操作数长度0x01
字节3:错误代码(Error Code)

错误代码是一个8位无符号整数,用来指示错误的原因。规范中定义了多种标准错误代码,包括:

  • 0x00:无错误(No Error)

  • 0x01:不支持的操作码(Unsupported Opcode)

  • 0x02:无效参数(Invalid Parameter)

  • 0x03:操作失败(Operation Failed)

  • 0x04:无效状态(Invalid State)

  • 0x05:资源不足(Insufficient Resources)

  • 0x06:不支持的参数(Unsupported Parameter)

  • 0x07:参数超出范围(Parameter Out of Range)

不支持的操作码错误响应示例

复制代码
0x00 0x01 0x11 0x0E 0x53 0x01 0x01

这个响应表示目标设备不支持操作码0x53(均衡器命令)。

9.2 常见错误代码详解

下面我们来详细解释几个最常见的错误代码及其可能的原因和解决方案。

(1) 0x01 不支持的操作码

这是最常见的错误代码。表示目标设备不支持发送的命令。

可能原因

  1. 目标设备的AVRCP版本较低,不支持该命令

  2. 目标设备没有实现该命令

  3. 命令的操作码错误

解决方案

  1. 检查目标设备的AVRCP版本

  2. 检查命令的操作码是否正确

  3. 如果目标设备不支持该命令,使用替代方案或者提示用户该功能不可用

(2) 0x02 无效参数

表示命令中的参数无效。

可能原因

  1. 参数格式错误

  2. 参数值超出了有效范围

  3. 缺少必要的参数

解决方案

  1. 检查命令的参数格式是否符合规范

  2. 确保参数值在有效范围内

  3. 确保所有必要的参数都已提供

(3) 0x03 操作失败

表示命令执行失败,但具体原因未知。

可能原因

  1. 目标设备内部错误

  2. 媒体文件损坏

  3. 存储设备不可用

解决方案

  1. 重试命令

  2. 检查媒体文件是否正常

  3. 检查存储设备是否可用

  4. 如果问题持续存在,提示用户设备可能有故障

(4) 0x04 无效状态

表示目标设备当前处于无法执行该命令的状态。

可能原因

  1. 没有媒体正在播放

  2. 媒体已经播放完毕

  3. 设备处于暂停状态,无法执行快进命令

解决方案

  1. 检查目标设备的当前状态

  2. 在执行命令前,确保设备处于合适的状态

  3. 向用户说明当前状态下无法执行该操作

9.3 错误处理的最佳实践

在处理AVRCP错误响应时,我们应该遵循以下最佳实践:

  1. 总是检查响应类型:不要假设所有命令都会成功执行,总是检查响应类型是否为接受。

  2. 优雅地处理不支持的命令:如果目标设备不支持某个命令,不要崩溃或产生错误,而是禁用该功能或者提示用户。

  3. 提供有意义的错误信息:向用户提供清晰、有意义的错误信息,帮助用户理解问题所在。

  4. 实现重试机制:对于临时的错误,实现重试机制,提高应用的健壮性。

  5. 记录错误日志:记录所有的错误响应,以便于调试和问题排查。

十、代码示例:Android中解析和构造AVRCP命令

在Android系统中,我们可以通过BluetoothAvrcpController和BluetoothAvrcpTarget类来发送和接收AVRCP命令。下面我们来看一段完整的代码示例,展示如何在Android中解析和构造AVRCP命令。

10.1 构造并发送播放命令

复制代码
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAvrcp;
import android.bluetooth.BluetoothAvrcpController;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;

public class AvrcpCommandSender {
    private static final String TAG = "AvrcpCommandSender";
    
    private BluetoothAvrcpController mAvrcpController;
    private final Context mContext;
    
    public AvrcpCommandSender(Context context) {
        mContext = context.getApplicationContext();
        initAvrcpController();
    }
    
    private void initAvrcpController() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null || !adapter.isEnabled()) {
            Log.e(TAG, "Bluetooth adapter is not available or disabled");
            return;
        }
        
        adapter.getProfileProxy(mContext, new BluetoothProfile.ServiceListener() {
            @Override
            public void onServiceConnected(int profile, BluetoothProfile proxy) {
                if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
                    mAvrcpController = (BluetoothAvrcpController) proxy;
                    Log.d(TAG, "AVRCP controller connected");
                }
            }
            
            @Override
            public void onServiceDisconnected(int profile) {
                if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
                    mAvrcpController = null;
                    Log.d(TAG, "AVRCP controller disconnected");
                }
            }
        }, BluetoothProfile.AVRCP_CONTROLLER);
    }
    
    public void sendPlayCommand() {
        if (mAvrcpController == null) {
            Log.e(TAG, "AVRCP controller is not available");
            return;
        }
        
        List<BluetoothDevice> connectedDevices = mAvrcpController.getConnectedDevices();
        if (connectedDevices.isEmpty()) {
            Log.e(TAG, "No connected AVRCP devices");
            return;
        }
        
        BluetoothDevice device = connectedDevices.get(0);
        
        // 发送播放按键按下命令
        mAvrcpController.sendPassThroughCommand(device, 
            BluetoothAvrcp.PASS_THRU_CMD_ID_PLAY, 
            BluetoothAvrcp.PASS_THRU_STATE_PRESSED);
        
        // 发送播放按键释放命令
        mAvrcpController.sendPassThroughCommand(device, 
            BluetoothAvrcp.PASS_THRU_CMD_ID_PLAY, 
            BluetoothAvrcp.PASS_THRU_STATE_RELEASED);
        
        Log.d(TAG, "Sent play command to " + device.getAddress());
    }
    
    public void sendGetPlayStatusCommand() {
        if (mAvrcpController == null) {
            Log.e(TAG, "AVRCP controller is not available");
            return;
        }
        
        List<BluetoothDevice> connectedDevices = mAvrcpController.getConnectedDevices();
        if (connectedDevices.isEmpty()) {
            Log.e(TAG, "No connected AVRCP devices");
            return;
        }
        
        BluetoothDevice device = connectedDevices.get(0);
        
        // 构造获取播放状态命令
        byte[] command = new byte[]{0x50, 0x00};
        
        // 发送命令
        mAvrcpController.sendVendorCommand(device, command);
        
        Log.d(TAG, "Sent get play status command to " + device.getAddress());
    }
    
    public void release() {
        if (mAvrcpController != null) {
            BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
                BluetoothProfile.AVRCP_CONTROLLER, mAvrcpController);
            mAvrcpController = null;
        }
    }
}

10.2 解析AVRCP响应

复制代码
import android.bluetooth.BluetoothAvrcp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class AvrcpResponseReceiver extends BroadcastReceiver {
    private static final String TAG = "AvrcpResponseReceiver";
    
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        
        if (BluetoothAvrcp.ACTION_PLAY_STATUS_CHANGED.equals(action)) {
            int playStatus = intent.getIntExtra(BluetoothAvrcp.EXTRA_PLAY_STATUS, 
                BluetoothAvrcp.PLAY_STATUS_STOPPED);
            
            long songLength = intent.getLongExtra(BluetoothAvrcp.EXTRA_SONG_LENGTH, 0);
            long currentPosition = intent.getLongExtra(BluetoothAvrcp.EXTRA_CURRENT_POSITION, 0);
            
            Log.d(TAG, "Play status changed: " + playStatus);
            Log.d(TAG, "Song length: " + songLength + " ms");
            Log.d(TAG, "Current position: " + currentPosition + " ms");
            
            // 更新UI显示
            updatePlayStatusUI(playStatus, songLength, currentPosition);
        } else if (BluetoothAvrcp.ACTION_META_DATA_CHANGED.equals(action)) {
            String title = intent.getStringExtra(BluetoothAvrcp.EXTRA_META_DATA_TITLE);
            String artist = intent.getStringExtra(BluetoothAvrcp.EXTRA_META_DATA_ARTIST);
            String album = intent.getStringExtra(BluetoothAvrcp.EXTRA_META_DATA_ALBUM);
            
            Log.d(TAG, "Meta data changed:");
            Log.d(TAG, "Title: " + title);
            Log.d(TAG, "Artist: " + artist);
            Log.d(TAG, "Album: " + album);
            
            // 更新UI显示
            updateMetaDataUI(title, artist, album);
        }
    }
    
    private void updatePlayStatusUI(int playStatus, long songLength, long currentPosition) {
        // 在这里更新播放状态UI
    }
    
    private void updateMetaDataUI(String title, String artist, String album) {
        // 在这里更新媒体信息UI
    }
}

10.3 注册广播接收器

复制代码
import android.content.IntentFilter;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private AvrcpResponseReceiver mReceiver;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 注册AVRCP响应广播接收器
        mReceiver = new AvrcpResponseReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothAvrcp.ACTION_PLAY_STATUS_CHANGED);
        filter.addAction(BluetoothAvrcp.ACTION_META_DATA_CHANGED);
        registerReceiver(mReceiver, filter);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        
        // 注销广播接收器
        if (mReceiver != null) {
            unregisterReceiver(mReceiver);
            mReceiver = null;
        }
    }
}

这段代码展示了如何在Android应用中发送AVRCP命令和接收AVRCP响应。在实际开发中,我们还需要处理更多的命令和响应,以及各种错误情况。

十一、测验

题目:请详细解释AVRCP直通命令的格式,并说明为什么每个按键操作都需要发送两个命令。(华为蓝牙协议工程师面试题)

答案

AVRCP直通命令的操作码是0x7C,操作数长度固定为2字节。操作数字段包含两个部分:按键ID和按键状态。按键ID是一个8位无符号整数,用来标识不同的按键;按键状态是一个8位无符号整数,0x00表示按键按下,0x01表示按键释放。

每个按键操作都需要发送两个命令的原因是:

  1. 模拟真实的按键操作:真实的按键操作包括按下和释放两个动作,直通命令需要准确模拟这个过程。

  2. 支持长按功能:通过测量按下命令和释放命令之间的时间间隔,目标设备可以检测长按操作。

  3. 支持按键重复:在按键按住期间,控制器可以定期发送重复的按下命令,实现按键重复功能。

  4. 避免误操作:如果只发送按下命令而不发送释放命令,目标设备可能会认为按键一直被按住,从而产生意想不到的行为。

题目:请解释AVRCP绝对音量命令的工作原理,并说明它与传统音量加/减命令相比有哪些优势。(小米蓝牙音频开发工程师面试题)

答案

AVRCP绝对音量命令使用一个0-127的8位无符号整数来表示音量级别。0表示静音,127表示最大音量,音量刻度是线性的。控制器可以直接发送这个数值给目标设备,目标设备将音量设置到对应的级别。目标设备也可以在音量发生变化时,主动向控制器发送音量变化通知,保持双方音量同步。

与传统的音量加/减命令相比,绝对音量命令具有以下优势:

  1. 精确控制:可以直接设置到任意音量级别,不需要多次按音量加/减按钮。

  2. 同步显示:控制器可以准确显示目标设备的当前音量级别。

  3. 快速调整:可以快速从静音调整到最大音量,或者从最大音量调整到静音。

  4. 双向同步:无论是在控制器还是目标设备上调整音量,双方都会保持同步。

  5. 减少数据传输:不需要多次发送音量加/减命令,减少了蓝牙数据传输量。

题目:请解释AVRCP浏览命令的工作原理,并说明为什么浏览命令需要使用单独的AVCTP连接。(字节跳动车载蓝牙系统开发工程师面试题)

答案

AVRCP浏览命令基于虚拟文件系统模型,将目标设备上的媒体内容组织成层次化的文件夹结构。控制器可以通过获取文件夹内容命令遍历这个结构,查看文件夹中的内容,并通过播放项目命令选择要播放的媒体内容。

浏览命令需要使用单独的AVCTP连接的原因是:

  1. 避免干扰:浏览操作不会干扰正常的播放控制操作,确保播放控制的实时性。

  2. 大数据量传输:浏览数据量通常比较大,使用单独的连接可以避免影响播放控制的响应速度。

  3. 并发操作:可以同时进行浏览和播放控制操作,提高用户体验。

  4. 独立的错误处理:浏览连接的错误不会影响播放控制连接的正常工作。

  5. 兼容性:对于不支持浏览功能的设备,可以只建立普通的AVRCP连接,而不需要建立浏览连接。


相关推荐
byte轻骑兵1 天前
【LE Audio】CAP精讲[10]: 多设备协同的通关秘籍——协调集全流程实战
音视频·蓝牙耳机·蓝牙音箱·le audio·低功耗音频
AI品信智慧数智人1 天前
企业级 AI 实时交互数智人全栈技术:重塑人机交互新范式✨
人工智能·人机交互·交互
搞科研的小刘选手2 天前
【人工智能专题研讨会】第五届人工智能与智能信息处理国际学术会议(AIIIP 2026)
人工智能·神经网络·机器学习·网络安全·数据挖掘·人机交互·信息处理
byte轻骑兵2 天前
【HID】规范精讲[18]: 蓝牙HID设备低延迟秘籍——从报告速率到全链路优化实战
人机交互·无人机·键盘·鼠标·hid
BU摆烂会噶3 天前
【LangGraph】House_Agent 实战(一):架构与环境配置
人工智能·vscode·python·架构·langchain·人机交互
BU摆烂会噶3 天前
【LangGraph】House_Agent 实战(五):持久化、流式输出与部署
人工智能·python·架构·langchain·人机交互
byte轻骑兵3 天前
【LE Audio】CAP精讲[9]:全流程操盘手,解锁CAP核心交互工序
人工智能·音视频·人机交互·le audio·音视频控制
cy_cy0023 天前
地砖感应屏在数字展厅的应用实践
大数据·科技·人机交互·交互·软件构建
byte轻骑兵4 天前
【HID】规范精讲[17]: 蓝牙HID设备功耗优化秘籍——从Sniff模式到断连重连的省电之道
人工智能·人机交互·蓝牙键盘·蓝牙鼠标·蓝牙hid