Android蓝牙协议栈fluoride(十一) - 音乐播放(4)

上一篇介绍了蓝牙音频的播放通路和编解码器,接下来介绍Source和Sink如何选择编解码器以及编解码流程。

编解码器选择

连接蓝牙后想要播放音乐,需要协商使用哪种编码器,还需要协商编码器使用什么配置,前面介绍了如何协商编码器的配置,这里将介绍如何选择编码器,从spec可以知道,SBC codec是a2dp必须支持的codec,因此Source和Sink至少有一个codec是相同的,当然也可能有多个相同的codec。因此在A2DP中将codec抽象成SEP,一个codec对应一个SEP,在fluoride中SEP通过BtaAvCoSep描述,设备通过BtaAvCoPeer描述,核心成员如下:

class BtaAvCoSep {
 public:
  uint8_t sep_info_idx;                    // 对端设备SEP在本地的index(bta中)
  uint8_t seid;                            // 对端SEP的index(对端设备中)
  uint8_t codec_caps[AVDT_CODEC_SIZE];     // 对端SEP index对应codec的能力
};

class BtaAvCoPeer {
 public:
  RawAddress addr;                                // 对端设备地址
  BtaAvCoSep sinks[BTAV_A2DP_CODEC_INDEX_MAX];    // 支持的sink sep
  BtaAvCoSep sources[BTAV_A2DP_CODEC_INDEX_MAX];  // 支持的source sep
  uint8_t num_sinks;                      // 对端sink的数量
  uint8_t num_sources;                    // 对端source的数量
  uint8_t num_rx_sinks;                   // 收到对端sink的数量
  uint8_t num_rx_sources;                 // 收到对端source的数量
  uint8_t num_sup_sinks;                  // 收到且支持的对端sink的数量
  uint8_t num_sup_sources;                // 收到且支持的对端source的数量
  const BtaAvCoSep* p_sink;               // 当前选择的的sink sep
  const BtaAvCoSep* p_source;             // 当前选择的source sep
  uint8_t codec_config[AVDT_CODEC_SIZE];  // 当前的codec 配置
  bool acceptor;                          // true表示连接接受方,反之表示发起方

 private:
  tBTA_AV_HNDL bta_av_handle_;   // BTA AV handle to use
  A2dpCodecs* codecs_;           // 本地支持的codec
};
  1. 获取SEP数量
    发起连接的设备通过AVDTP协议发现对端设备支持的SEP数量,发现的结果通过bta_av_co_audio_disc_res回调通知给btif,结果中包含对端设备地址、sink SEP数量、source SEP数量、总的SEP数量,根据设备bta生成的bta_av_handle找到对应的peer,并将这些信息存放在peer中。
void BtaAvCo::ProcessDiscoveryResult(tBTA_AV_HNDL bta_av_handle,
                                     const RawAddress& peer_address,
                                     uint8_t num_seps, uint8_t num_sinks,
                                     uint8_t num_sources, uint16_t uuid_local) {
  // Find the peer
  BtaAvCoPeer* p_peer = FindPeerAndUpdate(bta_av_handle, peer_address);
  ...
  /* Copy the discovery results */
  p_peer->addr = peer_address;
  p_peer->num_sinks = num_sinks;
  p_peer->num_sources = num_sources;
  p_peer->num_seps = num_seps;
  ...
}
  1. 获取SEP配置信息
    通过AVDTP协议获取每个SEP的能力(codec类型、codec 能力),获取到的结果通过bta_av_co_audio_getconfig回调通知给btif,结果包括SEP的codec信息、对端sep在本地的index、对端sep index等,btif会将这些信息保存在peer中(ProcessSinkGetConfigProcessSourceGetConfig),当对端所有sep的信息都获取完成后进行codec的选择。
tA2DP_STATUS BtaAvCo::ProcessSourceGetConfig(
    tBTA_AV_HNDL bta_av_handle, const RawAddress& peer_address,
    uint8_t* p_codec_info, uint8_t* p_sep_info_idx, uint8_t seid,
    uint8_t* p_num_protect, uint8_t* p_protect_info) {
  ...
  p_peer->num_rx_sinks++;
  // Check the peer's Sink codec
  if (A2DP_IsPeerSinkCodecValid(p_codec_info)) {
    // If there is room for a new one
    if (p_peer->num_sup_sinks < BTA_AV_CO_NUM_ELEMENTS(p_peer->sinks)) {
      BtaAvCoSep* p_sink = &p_peer->sinks[p_peer->num_sup_sinks++];
	  // 保存sep信息
      memcpy(p_sink->codec_caps, p_codec_info, AVDT_CODEC_SIZE);
      p_sink->sep_info_idx = *p_sep_info_idx;
      p_sink->seid = seid;
      p_sink->num_protect = *p_num_protect;
      memcpy(p_sink->protect_info, p_protect_info, AVDT_CP_INFO_LEN);
    }
    ...
  }
  // 判断所有SEP信息是否获取完
  if ((p_peer->num_rx_sinks != p_peer->num_sinks) &&
      (p_peer->num_sup_sinks != BTA_AV_CO_NUM_ELEMENTS(p_peer->sinks))) {
    return A2DP_FAIL;
  }
 // Select the Source codec
 ...
}

  1. 选择codec
    以对端设备是Sink为例,选择codec的具体实现为SelectSourceCodec(Source为SelectSinkCodec,逻辑相同,后续不再赘述),按优先级排序从高到低获取每个codec并判断是否在对端的SEP中,若在则结合对端SEP的codec信息codec_caps调用A2dpCodecs::setCodecConfig(参考上一篇文章)判断该codec配置是否双方都支持,如果支持则codec选择完成,同时调用ReportSourceCodecState将配置信息上报到JNI,如果不支持继续判断下一个codec。除了连接时会选择codec,当用户修改编码器配置时也会触发codec选择,如果选择的codec有变化或配置有变化还会触发AVDTP reconfig动作,其调用路径如下:
btav_source_interface_t::codec_config()
 -> codec_config_src() 
  -> BtifAvSource::UpdateCodecConfig()
   -> btif_a2dp_source_encoder_user_config_update_req()
    -> bta_av_co_set_codec_user_config()
     -> btif_a2dp_source_encoder_user_config_update_event()
      -> bta_av_co_set_codec_user_config()
       -> BtaAvCo::SetCodecUserConfig()
        -> A2dpCodecs::setCodecUserConfig()
        -> SelectSourceCodec()

SelectSourceCodec代码实现如下:

const BtaAvCoSep* BtaAvCo::SelectSourceCodec(BtaAvCoPeer* p_peer) {
  const BtaAvCoSep* p_sink = nullptr;
  ...
  // Select the codec
  for (const auto& iter : p_peer->GetCodecs()->orderedSourceCodecs()) {
    p_sink = AttemptSourceCodecSelection(*iter, p_peer);
    if (p_sink != nullptr)
      break;
  }
  return p_sink;
}
const BtaAvCoSep* BtaAvCo::AttemptSourceCodecSelection(
    const A2dpCodecConfig& codec_config, BtaAvCoPeer* p_peer) {
  ...
  if (!p_peer->GetCodecs()->setCodecConfig(
          p_sink->codec_caps, true /* is_capability */, new_codec_config,
          true /* select_current_codec */)) {
    APPL_TRACE_DEBUG("%s: cannot set source codec %s", __func__,
                     codec_config.name().c_str());
    return nullptr;
  }
  ...
  return p_sink;
}

codec的选择流程如下:
在这里插入图片描述
执行完codec选择之后,AVDTP的音频流传输通路就建立完成了,此时会回调bta_av_co_audio_open进入opened状态p_peer->opened = true。进入opened状态后就可以进行音频播放了,在btif层音频播放主要是音频编码、解码。

音频编码(Source)

BtifAvSource::Init中调用btif_a2dp_source_init进行Source编码相关的初始化,主要是开启一个编码线程btif_a2dp_source_thread,然后在A2DP连接成功后会设置action设备(调用BtifAvSource::SetActivePeer)在这个函数中会进行Audio相关的设置(调用btif_a2dp_source_restart_session, 即编码前音箱相关的设置如音频参数、音频通路选择等,编码器获取与初始化等),当有音频需要播放时调用btif_a2dp_source_start_audio_req 启动编码器开始编码音频数据并将编码后的数据放入队列总,bta根据需要中队列中获取数据包然后通过AVDTP发送到对端设备,不要播放音频时调用btif_a2dp_source_stop_audio_req停止编码,连接断开后调用btif_a2dp_source_end_session取消音频通路选择等。运行过程中数据流向和函数触发关系如下:
在这里插入图片描述
开始播放时,启动定时器,定时器定时触发(send_frames)编码器编码数据,编码器从音频模块中读取(btif_a2dp_source_read_callback)PCM数据,编码完成后将编码后的数据缓存(btif_a2dp_source_enqueue_callback)在队列中(tx_audio_queue),同时BTA在从队列中获取(btif_a2dp_source_audio_readbuf)编码后的数据包进行发送,结束播放时,停止定时器,并清除各个阶段缓存的音频数据。

音频解码(Sink)

BtifAvSink::Init中调用btif_a2dp_sink_init进行Sink解码相关的初始化,包括开启解码线程、创建音频缓存的队列等,当需要播放Source端发送的音频时,首先会通过BTA_AvRegister注册的回调bta_av_sink_media_callback上报BTA_AV_SINK_MEDIA_CFG_EVT事件,收到该时间后调用btif_a2dp_sink_update_decoder初始化解码器和音频播放通路(BtifAvrcpAudioTrackCreate),当A2DP状态切换到Start状态后调用btif_a2dp_sink_on_start启动解码器,然后在收到bta_av_sink_media_callback回调的BTA_AV_SINK_MEDIA_DATA_EVT事件(收到音频数据)时将数据存放在队列中,同时启动定时器,定时器定时从队列中获取音频数据解码,解码成功后播放(BtifAvrcpAudioTrackWriteData),停止播放时停止定时器和解码器并清除各个阶段的缓存,数据流向如下:
在这里插入图片描述
到此,btif层关于音频播放相关的内容就以介绍完毕,其中codec的实现在stack/a2dp中。

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux 系统中有几种常见的蓝牙协议栈: 1. BlueZ:这是 Linux 系统中默认的蓝牙协议栈,它由 Linux 基金会开发并维护。BlueZ 提供了蓝牙的高级功能,并且可以让用户通过命令行或脚本来控制蓝牙设备。 2. Bluedroid:这是 Android 系统中的蓝牙协议栈,也可以在其他基于 Linux 的系统中使用。Bluedroid 提供了丰富的蓝牙功能,包括蓝牙协议栈、配对、连接、传输数据等。 3. BlueZ for Android:这是一个基于 BlueZ 的蓝牙协议栈,专门为 Android 系统开发。它兼容 BlueZ 命令行工具,可以让用户在 Android 系统中使用 BlueZ 命令行来控制蓝牙设备。 4. hciattach:这是 Linux 中用于驱动蓝牙硬件的工具,它可以将蓝牙硬件附加到蓝牙协议栈中,使蓝牙硬件可以与其他蓝牙设备通信。 总的来说,Linux 系统中的蓝牙协议栈主要有 BlueZ 和 Bluedroid 两种,其中 BlueZ 是 Linux 系统中的默认蓝牙协议栈,Bluedroid 则是 Android 系统中的蓝牙协议栈。 ### 回答2: Linux操作系统中有两种蓝牙协议栈可供选择。 1. BlueZ协议栈:BlueZ是Linux操作系统上最常用的蓝牙协议栈。它提供API和工具,用于在Linux设备上实现蓝牙功能。BlueZ支持几乎所有的蓝牙协议,包括传统的蓝牙核心规范(Bluetooth Core Specification)和最新的蓝牙低功耗(Bluetooth Low Energy)规范。BlueZ协议栈被广泛应用于各种Linux设备,包括智能手机、平板电脑、智能手表等。 2. Affix协议栈:Affix是一个开源的Linux蓝牙协议栈。它提供了蓝牙通信的基本功能,并可以在嵌入式系统上运行。Affix协议栈支持蓝牙核心规范,但对于蓝牙低功耗规范的支持相对有限。它适用于资源受限的嵌入式设备,如物联网设备、传感器等。 总结起来,Linux操作系统上有两种常用的蓝牙协议栈:BlueZ协议栈和Affix协议栈。其中,BlueZ协议栈是最常用和功能最全面的蓝牙协议栈,被广泛应用于各种Linux设备。而Affix协议栈则适用于资源受限的嵌入式设备。 ### 回答3: Linux操作系统支持多种蓝牙协议栈,以下是其中一些常见的蓝牙协议栈: 1. BlueZ:BlueZ是Linux操作系统上最为广泛使用的开源蓝牙协议栈。它提供了一套完整的蓝牙协议栈实现,包括蓝牙核心协议、蓝牙硬件驱动和一系列用户空间的工具和库。BlueZ支持众多的蓝牙协议和功能,如蓝牙文件传输、蓝牙耳机、蓝牙键盘等。 2. Affix:Affix是一种轻量级的蓝牙协议栈,旨在提供更小的内存占用和更低的功耗。它适用于嵌入式设备和资源受限的系统。Affix支持核心蓝牙协议,但不支持所有BlueZ的高级功能。 3. Bluedroid:Bluedroid最初是由Android平台使用的蓝牙协议栈,后来也被移植到了Linux操作系统中。Bluedroid基于BlueZ协议栈,但做了一些定制化的修改和优化,以适应移动设备的需求。 4. FluorideFluoride是谷歌开发的蓝牙协议栈,最初用于Android平台,后来也支持了Linux操作系统。Fluoride提供了一套用于开发蓝牙应用的API和工具,并且与BlueZ相比有一些特有的优化和功能。 这些蓝牙协议栈都有各自的特点和适用场景,开发者可以根据不同的需求选择合适的协议栈来进行蓝牙应用程序的开发和调试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值