蓝牙开发那些事(9)——结合代码看a2dp协议

本文详细解析了蓝牙音频传输协议A2DP及控制协议AVRCP的实现原理,介绍了btstack实例中a2dp_sink_deom.c文件的处理流程,包括初始化过程、数据包分类处理、SEID分配等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一章讲了一下avdtp的连接过程,这一章我们看一下btstack的实例。

因为a2dp是一个音频传输的框架协议,具体的使用已经牵涉到应用层了,比如说我们的设备是个音箱设备还是个音源设备,我们目前是个音箱设备,所以可以看一下a2dp_sink_deom.c。

其中首先调用a2dp_and_avrcp_setup函数进行了一系列的初始化,从这个函数名就知道,初始化的内容包括了a2dp协议和avrcp协议,a2dp之前我们已经讲了其基础协议avdtp,avrcp的话呢是基于avctp协议的, AVCTP协议描述了蓝牙设备间Audio/Video的控制信号交换的格式和机制,它是一个总体的协议,具体的控制信息由其指定的协议(如AVRCP)实现,AVCTP本身只指定控制command和response的总体的格式,比如说我们在音箱端怎么暂停、播放、停止、上一首、下一首操作,就得依赖avrcp,再比如说音量同步功能,也是依赖avrcp。

1      l2cap_init();

2       // Initialize AVDTP Sink

3       a2dp_sink_init();

4       a2dp_sink_register_packet_handler(&a2dp_sink_packet_handler);

5       a2dp_sink_register_media_handler(&handle_l2cap_media_data_packet);

6

7       avdtp_stream_endpoint_t * local_stream_endpoint = a2dp_sink_create_stream_endpoint(AVDTP_AUDIO,

8           AVDTP_CODEC_SBC, media_sbc_codec_capabilities, sizeof(media_sbc_codec_capabilities),

9           media_sbc_codec_configuration, sizeof(media_sbc_codec_configuration));

10      if (!local_stream_endpoint){

11          printf("A2DP Sink: not enough memory to create local stream endpoint\n");

12          return 1;

13      }

14      a2dp_local_seid = avdtp_local_seid(local_stream_endpoint);

15

16      // Initialize AVRCP service.

17      avrcp_init();

18      avrcp_register_packet_handler(&avrcp_packet_handler);

……

基本上都是一些初始化的操作,第一行初始化l2cap, 第二行初始化a2dp sink, 其中给l2cap的数据注册了回调函数

l2cap_register_service(&avdtp_packet_handler, BLUETOOTH_PSM_AVDTP, 0xffff, gap_get_security_level());

我们之前说过,l2cap建立逻辑信道,区分上层协议的依据就是PSM,所以这里的PSM一定要填BLUETOOTH_PSM_AVDTP。

之后凡是avdtp相关的l2cap数据都会进入这个avdtp_packet_handler去处理。

1   void avdtp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){

2       bd_addr_t event_addr;

3       uint16_t psm;

4       uint16_t local_cid;

5       uint8_t  status;

6       uint16_t l2cap_mtu;

7

8       bool accept_streaming_connection;

9       bool outoing_signaling_active;

10      bool decline_connection;

11

12      avdtp_stream_endpoint_t * stream_endpoint = NULL;

13      avdtp_connection_t * connection = NULL;

14

15      switch (packet_type) {

16          case L2CAP_DATA_PACKET:

17              connection = avdtp_get_connection_for_l2cap_signaling_cid(channel);

18              if (connection){

19                  handle_l2cap_data_packet_for_signaling_connection(connection, packet, size);

20                  break;

21              }

22             

23              stream_endpoint = avdtp_get_stream_endpoint_for_l2cap_cid(channel);

24              if (!stream_endpoint){

25                  if (!connection) break;

26                  handle_l2cap_data_packet_for_signaling_connection(connection, packet, size);

27                  break;

28              }

29             

30              if (stream_endpoint->connection){

31                  if (channel == stream_endpoint->connection->l2cap_signaling_cid){

32                      handle_l2cap_data_packet_for_signaling_connection(stream_endpoint->connection, packet, size);

33                      break;

34                  }

35              }

36

37              if (channel == stream_endpoint->l2cap_media_cid){

38                  btstack_assert(avdtp_sink_handle_media_data);

39                  (*avdtp_sink_handle_media_data)(avdtp_local_seid(stream_endpoint), packet, size);

40                  break;

41              }

42

43              if (channel == stream_endpoint->l2cap_reporting_cid){

44                  log_info("L2CAP_DATA_PACKET for reporting: NOT IMPLEMENTED");

45              } else if (channel == stream_endpoint->l2cap_recovery_cid){

46                  log_info("L2CAP_DATA_PACKET for recovery: NOT IMPLEMENTED");

47              } else {

48                  log_error("avdtp packet handler L2CAP_DATA_PACKET: local cid 0x%02x not found", channel);

49              }

50              break;

51             

52          case HCI_EVENT_PACKET:

53              switch (hci_event_packet_get_type(packet)) {

54

55                  case L2CAP_EVENT_INCOMING_CONNECTION:

56                      l2cap_event_incoming_connection_get_address(packet, event_addr);

57                      local_cid = l2cap_event_incoming_connection_get_local_cid(packet);

58                     

59                      outoing_signaling_active = false;

60                      accept_streaming_connection = false;

61                     

62                      connection = avdtp_get_connection_for_bd_addr(event_addr);

……

有了上一章的讲述,这里面对各种数据包的分类就好理解了,handle_l2cap_data_packet_for_signaling_connection是处理signal channel的数据,第39行的(*avdtp_sink_handle_media_data)(avdtp_local_seid(stream_endpoint), packet, size)是处理stream channel的数据,第52行的HCI_EVENT_PACKET其实并不是真正的hci event,而是btstack虚拟出来的一类软件内部的hci event,看第55行就知道,这类hci event据事件type去执行不同操作,l2cap的signal channel去建立逻辑信道的连接的时候,也是有一套状态机的,其中有不同状态,这个L2CAP_EVENT_INCOMING_CONNECTION就是准备建立连接的状态。

我们再回到a2dp_and_avrcp_setup,

第4行a2dp_sink_register_packet_handler(&a2dp_sink_packet_handler);这里面除了有avdtp协议内部对于a2dp各子状态的处理之外,还注册了a2dp_sink_packet_handler这个应用层的回调函数,主要处理在a2dp连接各子状态的时候,对codec层需要做的一些处理,比如说sbc编码器的初始化,音频的具体处理,这些都依赖于上层的实现。

第5行a2dp_sink_register_media_handler(&handle_l2cap_media_data_packet);

这个handle_l2cap_media_data_packet是应用层对于stream包的处理函数,之前avdtp_packet_handler函数,在收到l2cap层的avdtp包的时候,会分析是signal channel的包还是stream channel的包,假如是stream channel的包的话,看一下39行,就是在执行这里所注册的回调函数了。一般来说,这里面应该做的工作是执行解码器解码的工作,解码完了打算干吗?送给播放器播放?具体的还是取决于应用层。

第7行

avdtp_stream_endpoint_t * local_stream_endpoint = a2dp_sink_create_stream_endpoint(AVDTP_AUDIO,

        AVDTP_CODEC_SBC, media_sbc_codec_capabilities, sizeof(media_sbc_codec_capabilities),

        media_sbc_codec_configuration, sizeof(media_sbc_codec_configuration));

创建一个sep,也可以看出a2dp中sep的确是一个虚拟的概念了,软件中想创建就可以创建的。

再深入看一下代码,看看给这个sep分配seid的时候:

static uint16_t avdtp_get_next_local_seid(void){

    if (stream_endpoints_id_counter == 0xffff) {

        stream_endpoints_id_counter = 1;

    } else {

        stream_endpoints_id_counter++;

    }

    return stream_endpoints_id_counter;

}

可以看到seid无非是做了一个加1 的动态分配的动作。

第14行

a2dp_local_seid = avdtp_local_seid(local_stream_endpoint);

给a2dp_local_seid这个重要变量赋值,因为avdtp的连接是基于seid的,所以以后连接过程都是需要频繁用到的,用一个全局变量保存并不为过。

再接下来avrcp的一系列的初始化。

整个a2dp_and_avrcp_setup函数执行完成后,上层的服务就设置好了,a2dp_sink_demo.c支持从上位机输入指令执行相应程序,比如说,输入‘b’,就会调用a2dp_sink_establish_stream执行a2dp的连接操作,接下来,输入b试试看,让状态机跑起来吧。

蓝牙核心规格 蓝牙设备的构成组件。 蓝牙核心规格定义各种技术构成组件,开发人员可以用它们来创建构成繁荣的蓝牙生态系统的互操作性设备。蓝牙规格由蓝牙技术联盟 (SIG) 负责监督,并由Bluetooth SIG 工作组 定期更新和补充,以满足不断发展的技术和市场要求。 蓝牙的两种类型 部署最为普遍的两种规格为蓝牙基础率/增强数据率 (BR/EDR)(采用版本为 2.0/2.1)和低耗能 (LE) 蓝牙(采用版本为 4.0/4.1/4.2)。每项部署都有不同的用例,同时采用不同的芯片以满足基本硬件要求。双模芯片也适用于包含两种用例的应用。 存在哪些差异? 蓝牙 BR/EDR—可建立相对较短距离的持续无线连接,因此非常适用于流式音频等应用 蓝牙 LE—可建立短时间的长距离无线电连接,非常适用于无需持续连接但依赖电池具有较长寿命的的物联网 (IoT) 应用 双模—双模芯片可支持需要连接 BR/EDR 设备(例如音频耳机)以及 LE 设备(例如穿戴设备或零售信标)的单一设备(例如智能手机或平板电脑) 核心系统结构 尽管每项部署都有具体要求(详见蓝牙规格),但蓝牙核心系统结构有许多统一要素。该系统包含射频收发器、基带和协议栈,支持设备连接和交换各类数据。 蓝牙设备交换根据蓝牙规格协议信号。核心系统协议包括射频 (RF) 协议、链路控制 (LC) 协议、链路管理器 (LM) 协议以及逻辑链路控制和适配协议 (L2CAP)蓝牙规格详细定义了这些协议。 最低的三个系统层—射频、链路控制和链路管理器协议—通常被归属于称为蓝牙控制器的子系统。这是一种采用可选标准接口—主机控制器接口 (HCI)—的通用部署,支持与蓝牙系统的其他设备(即蓝牙主机)进行双向通信。 主控制器可能是以下配置之一,具体取决于用例: BR/EDR 控制器,包括射频、基带、链路管理器和可选 HCI LE 控制器,包括 LE PHY、链路层和可选 HCI BR/EDR 组合控制器和 LE 控制器,组合控制器共享一个蓝牙设备地址 蓝牙规格通过定义等效层之间交换的协议信息来实现系统之间的互操作性。它还通过定义蓝牙控制器和蓝牙主机之间的公用接口来实现独立蓝牙子系统之间的互操作性。 蓝牙栈 物理 (PHY) 层 通过蓝牙通信信道控制 2.4Ghz 射频的传输/接收。BR/EDR 提供的信道较多但带宽较窄,而 LE 使用的信道较少但带宽较宽。 链路层 定义数据包结构/信道、发现/连接程序以及发送/接收数据。 直接测试模式 允许测试人员向 PHY 层发出指令以传输或接收给定数据包序列,通过 HCI 或 2 线 UART 接口提交命令。 主机控制器接口 (HCI) 蓝牙控制器子系统(底部三层)和蓝牙主机之间的可选标准接口。 逻辑链路控制和适配协议 (L2CAP) 层 基于数据包的协议,可将数据包传输至 HCI 或直接传输到无主机系统中的链路管理器。支持更高级别的协议多路复用、数据包分割和重组,以及将服务质量信息传输到更高层。 属性协议 (ATT) 在建立连接之后定义数据交换客户端/服务器协议。使用通用属性配置文件 (GATT) 将属性分类为有意义的服务。ATT 主要用于 LE 部署,偶尔也会用于 BR/EDR 部署。 安全管理器 定义管理蓝牙设备之间配对完整性、身份验证以及加密的协议和操作,提供安全功能工具箱,其他组件可利用该工具箱支持不同应用所需的各种安全级别。 通用属性配置文件 (GATT) 使用属性协议,GATT 对封装设备组件性能的服务进行分组,并描述基于 GATT 功能的用例、角色和一般性能。其服务框架定义服务规程和格式及其特性,其中包括发现、读取、写入、通知以及指示特性以及配置特性广播。GATT 仅用于蓝牙 LE 部署。 详细了解 GATT 信息。 通用访问配置文件(GAP) 可与蓝牙 LE 部署中的 GATT 配合使用,以定义与发现蓝牙设备和共享信息相关的规程和角色,以及连接蓝牙设备的链路管理内容
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值