【AVRCP】规范精讲[26]: 旧遥控器如何控制新设备?播放命令跨版本兼容全流程解析

在蓝牙音频的世界里,版本不兼容是开发者最头疼的问题之一。你可能遇到过这样的情况:用一个老款蓝牙耳机控制新手机播放音乐,有时候能正常工作,有时候却毫无反应。这背后其实是AVRCP协议不同版本之间的交互逻辑在起作用。本文就来深入拆解一个最常见也最容易被忽视的场景:传统控制器(Legacy CT)如何与1.4版本目标设备(v1.4 TG)完成播放命令的交互。


目录

一、为什么这个场景如此重要

二、播放命令交互的完整流程

[2.1 命令发送阶段](#2.1 命令发送阶段)

[2.2 命令响应阶段](#2.2 命令响应阶段)

[2.3 状态通知阶段](#2.3 状态通知阶段)

三、关键技术细节解析

[3.1 版本协商过程](#3.1 版本协商过程)

[3.2 命令的幂等性问题](#3.2 命令的幂等性问题)

[3.3 超时处理](#3.3 超时处理)

四、Android系统中的代码实现

五、常见问题与调试技巧

[5.1 播放命令没有响应](#5.1 播放命令没有响应)

[5.2 播放状态不同步](#5.2 播放状态不同步)

[5.3 连续按两次播放按钮导致暂停](#5.3 连续按两次播放按钮导致暂停)

六、测验


一、为什么这个场景如此重要

蓝牙技术发展了二十多年,市场上同时存在着从AVRCP 1.0到1.6的各种设备。一个优秀的蓝牙产品必须能够与尽可能多的旧设备兼容,否则就会失去大量用户。

Legacy CT指的是支持AVRCP 1.0、1.3或更早版本的控制器设备,比如老款蓝牙耳机、车载蓝牙系统、遥控器等。这些设备只实现了最基本的控制功能,不支持1.4版本新增的浏览通道和绝对音量调节。

v1.4 TG则是支持AVRCP 1.4版本的目标设备,比如现代智能手机、平板电脑、智能音箱等。这些设备不仅支持所有传统命令,还实现了1.4版本的新特性。

当这两种设备连接时,v1.4 TG必须能够正确理解并响应Legacy CT发送的所有传统命令,同时不能期望Legacy CT能够理解1.4版本的新特性。这就像一个现代人要和一个只会说古文的人交流,必须用对方能听懂的语言说话。

二、播放命令交互的完整流程

播放命令是AVRCP中最基本也是最常用的命令。整个交互过程可以分为三个阶段:命令发送、命令响应和状态通知。

2.1 命令发送阶段

当用户按下Legacy CT上的播放按钮时,CT会通过AVCTP控制通道向TG发送一个AV/C Pass Through命令。这个命令的格式如下:

cpp 复制代码
AV/C Command Frame:
  CType: 0x00 (CONTROL)
  Subunit_type: 0x09 (Panel)
  Subunit_id: 0x00
  Opcode: 0x7C (Pass Through)
  Operation_id: 0x44 (PLAY)
  State: 0x00 (PRESSED)

这里有几个关键点需要注意:

  • CType字段设置为CONTROL,表示这是一个控制命令

  • Subunit_type设置为Panel,表示这是面板按钮操作

  • Opcode设置为Pass Through,表示这是一个透传命令

  • Operation_id设置为PLAY,表示具体的操作是播放

  • State字段设置为PRESSED,表示按钮被按下

紧接着,CT会发送第二个相同的命令,但State字段设置为RELEASED,表示按钮被释放。这两个命令必须成对出现,否则TG可能不会执行任何操作。

2.2 命令响应阶段

当v1.4 TG收到第一个PLAY PRESSED命令时,它会立即检查自己的当前状态。如果当前处于暂停或停止状态,TG会开始播放音乐;如果已经在播放状态,TG可能会忽略这个命令或者执行其他操作,具体取决于设备的实现。

然后,TG会向CT发送一个AV/C响应帧:

cpp 复制代码
AV/C Response Frame:
  CType: 0x09 (ACCEPTED)
  Subunit_type: 0x09 (Panel)
  Subunit_id: 0x00
  Opcode: 0x7C (Pass Through)
  Operation_id: 0x44 (PLAY)
  State: 0x00 (PRESSED)

CType字段设置为ACCEPTED,表示TG已经接受并执行了这个命令。如果TG无法执行这个命令,比如当前没有可播放的媒体,它会返回REJECTED响应。

当TG收到第二个PLAY RELEASED命令时,它会再次发送一个ACCEPTED响应。至此,整个播放命令的请求-响应过程就完成了。

2.3 状态通知阶段

这是最容易被误解的部分。很多人以为播放命令执行完成后,交互就结束了。但实际上,v1.4 TG还会主动向CT发送一个播放状态变化的通知。

这个通知使用的是AVRCP 1.3版本引入的RegisterNotification机制。当TG的播放状态从暂停变为播放时,它会向所有已经注册了播放状态变化通知的CT发送一个事件:

cpp 复制代码
AV/C Notification Frame:
  CType: 0x0F (NOTIFICATION)
  Subunit_type: 0x09 (Panel)
  Subunit_id: 0x00
  Opcode: 0x00 (Vendor Dependent)
  Company ID: 0x001958 (Bluetooth SIG)
  PDU ID: 0x51 (RegisterNotification)
  Event ID: 0x01 (Playback Status Changed)
  Playback Status: 0x01 (PLAYING)

这里有一个非常重要的细节:Legacy CT可能没有注册任何通知,甚至可能不理解这个通知帧。那么v1.4 TG应该怎么做呢?

规范中明确规定:TG必须能够处理CT不响应通知的情况。如果CT没有注册播放状态变化通知,TG可以选择不发送这个通知;如果发送了通知但没有收到CT的响应,TG不能因此而断开连接或停止工作。

这就是为什么有些老款蓝牙耳机在播放音乐时,耳机上的指示灯不会变化,因为它们不支持播放状态通知。

三、关键技术细节解析

3.1 版本协商过程

在AVRCP连接建立之初,CT和TG会通过SDP服务发现对方支持的版本。Legacy CT会在SDP记录中声明自己支持的最高版本,比如1.3。v1.4 TG会看到这个版本号,并在后续的交互中只使用CT支持的特性。

具体来说,v1.4 TG不会尝试建立浏览通道,因为Legacy CT不支持;也不会发送绝对音量调节命令,因为Legacy CT可能无法理解。

3.2 命令的幂等性问题

播放命令是幂等的吗?规范中没有明确规定。不同的设备可能有不同的实现:

  • 有些设备在播放状态下收到播放命令会忽略

  • 有些设备会重新开始播放当前歌曲

  • 有些设备会切换到下一首歌曲

这就是为什么有时候你按一下播放按钮,却跳到了下一首歌曲的原因。为了保证兼容性,建议在实现时采用第一种行为:在播放状态下收到播放命令时忽略。

3.3 超时处理

当CT发送一个命令后,它会等待TG的响应。如果在规定的时间内没有收到响应,CT会认为命令失败。规范中规定的超时时间是1000毫秒。

v1.4 TG必须在收到命令后的1000毫秒内发送响应。如果需要执行耗时操作,比如加载媒体文件,TG应该先发送一个INTERIM响应,然后在操作完成后再发送最终的ACCEPTED或REJECTED响应。

四、Android系统中的代码实现

下面我们来看一下在Android系统中,v1.4 TG是如何处理Legacy CT发送的播放命令的。

cpp 复制代码
// packages/modules/Bluetooth/system/stack/avrcp/avrcp_api.cc
void AvrcpApi::HandlePassThroughCommand(uint8_t operation_id, uint8_t state) {
  ALOGD("HandlePassThroughCommand: operation_id=0x%02X, state=0x%02X",
        operation_id, state);

  if (operation_id == AVRCP_PASS_THROUGH_OP_PLAY) {
    if (state == AVRCP_PASS_THROUGH_STATE_PRESSED) {
      // 处理播放按钮按下事件
      media_session_->GetTransportControls()->Play();
      
      // 发送ACCEPTED响应
      SendPassThroughResponse(AVRC_CTYPE_ACCEPTED, operation_id, state);
      
      // 通知所有注册的CT播放状态已经变化
      NotifyPlaybackStatusChanged(AVRC_PLAY_STATUS_PLAYING);
    } else if (state == AVRCP_PASS_THROUGH_STATE_RELEASED) {
      // 处理播放按钮释放事件
      // 通常不需要做任何操作,只需要发送响应
      SendPassThroughResponse(AVRC_CTYPE_ACCEPTED, operation_id, state);
    }
  }
  // 处理其他操作...
}

void AvrcpApi::NotifyPlaybackStatusChanged(uint8_t playback_status) {
  ALOGD("NotifyPlaybackStatusChanged: playback_status=0x%02X",
        playback_status);

  // 遍历所有已连接的CT
  for (auto& ct : connected_cts_) {
    // 检查CT是否注册了播放状态变化通知
    if (ct->IsNotificationRegistered(AVRC_EVT_PLAY_STATUS_CHANGED)) {
      // 发送通知
      SendRegisterNotificationResponse(
          ct->GetAddress(),
          AVRC_CTYPE_NOTIFICATION,
          AVRC_EVT_PLAY_STATUS_CHANGED,
          &playback_status,
          sizeof(playback_status));
    }
  }
}

从这段代码可以看出:

  1. Android系统会分别处理按钮按下和释放事件

  2. 在处理按下事件时,会调用媒体会话的Play方法开始播放

  3. 立即发送ACCEPTED响应

  4. 只向已经注册了通知的CT发送播放状态变化通知

五、常见问题与调试技巧

5.1 播放命令没有响应

如果Legacy CT发送了播放命令但v1.4 TG没有响应,可能的原因有:

  • CT发送的命令格式不正确

  • TG当前没有可播放的媒体

  • TG的媒体会话没有被激活

  • 蓝牙连接不稳定

调试方法:使用HCI日志工具抓取蓝牙数据包,检查CT是否发送了正确的PLAY PRESSED和PLAY RELEASED命令,以及TG是否发送了ACCEPTED响应。

5.2 播放状态不同步

有时候会出现CT显示的播放状态和TG实际的播放状态不一致的情况。这通常是因为CT没有注册播放状态变化通知,或者TG没有正确发送通知。

调试方法:检查CT是否在连接建立后发送了RegisterNotification命令,以及TG是否在播放状态变化时发送了通知。

5.3 连续按两次播放按钮导致暂停

有些设备在播放状态下收到播放命令会切换到暂停状态。这是因为这些设备将播放命令实现为切换命令,而不是单纯的播放命令。

解决方法:在TG端实现时,确保在播放状态下收到播放命令时忽略,而不是切换到暂停状态。

六、测验

题目:当Legacy CT(AVRCP 1.3)与v1.4 TG连接时,TG是否会建立浏览通道?为什么?(某蓝牙芯片公司2025年校招面试题)

答案

不会建立浏览通道。因为浏览通道是AVRCP 1.4版本新增的特性,Legacy CT不支持。在SDP服务发现阶段,TG会发现CT支持的最高版本是1.3,因此不会尝试建立浏览通道。如果TG强行建立浏览通道,CT会拒绝连接,导致整个AVRCP连接失败。

题目:AVRCP播放命令为什么需要发送PRESSED和RELEASED两个状态?只发送一个PRESSED状态可以吗?(某手机厂商2024年社招面试题)

答案

不可以只发送一个PRESSED状态。规范中明确规定,所有Pass Through命令都必须包含PRESSED和RELEASED两个状态。这是为了模拟真实的按钮操作,区分短按和长按。有些设备会根据按钮按下的时间长短执行不同的操作,比如短按播放,长按快进。如果只发送PRESSED状态,TG可能会认为按钮一直被按住,从而执行长按操作,或者干脆忽略这个命令。

题目:v1.4 TG在执行完播放命令后,是否必须向Legacy CT发送播放状态变化通知?为什么?

答案

不是必须的。只有当Legacy CT已经注册了播放状态变化通知时,TG才需要发送通知。如果CT没有注册通知,TG可以选择不发送。即使发送了通知,如果CT没有响应,TG也不能因此而断开连接或停止工作。这是为了保证向下兼容性,因为很多Legacy CT不支持RegisterNotification机制。


相关推荐
zlinear数据采集卡1 天前
状态指示灯电路深度解析:从板卡的“眼睛”到ZLinear采集卡的硬核人机交互实战
人机交互
声光界2 天前
《声音与音乐中的情感理解及人机交互设计》
人工智能·人机交互·声学
byte轻骑兵2 天前
【AVRCP】规范精讲[25]: 大数据包拆分传输的完整流程与实战
智能手机·音视频·avrcp·音视频控制·车机蓝牙
BSD_HY3 天前
薄膜开关的材料选型与可靠性验证:从PET与PC的对比说起
人机交互·制造·薄膜开关·深圳工厂
byte轻骑兵3 天前
【LE Audio】CAP精讲[14]: BR/EDR传输连接实战,老设备兼容的核心流程解析
网络·音视频·le audio·音视频控制·车机蓝牙
BSD_HY4 天前
国产工业硬件的“突围”:从薄膜开关看国产化替代背后的“隐形挑战”
人机交互·制造·薄膜开关·深圳工厂
byte轻骑兵4 天前
【AVRCP】规范精讲[23]: 字符集切换全流程与两种典型场景解析
网络·人机交互·媒体·avrcp·媒体控制·车机蓝牙
搞科研的小刘选手5 天前
【重庆大学主办】第三届智能感知与模式识别国际学术会议(IPPR 2026)
物联网·机器学习·计算机视觉·机器人·人机交互·感知·传感
某林2126 天前
ROS2 机器人底盘调试避坑指南:从 `/odom` 丢失到彻底跑通的硬核排障实录
stm32·机器人·人机交互