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
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值