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

这篇具有很好参考价值的文章主要介绍了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的选择流程如下:
Android蓝牙协议栈fluoride(十一) - 音乐播放(4),android蓝牙协议栈 fluoride,android,android bt,android 蓝牙协议栈,蓝牙协议栈,bt stack,fluoride,A2DP
执行完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取消音频通路选择等。运行过程中数据流向和函数触发关系如下:
Android蓝牙协议栈fluoride(十一) - 音乐播放(4),android蓝牙协议栈 fluoride,android,android bt,android 蓝牙协议栈,蓝牙协议栈,bt stack,fluoride,A2DP
开始播放时,启动定时器,定时器定时触发(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),停止播放时停止定时器和解码器并清除各个阶段的缓存,数据流向如下:
Android蓝牙协议栈fluoride(十一) - 音乐播放(4),android蓝牙协议栈 fluoride,android,android bt,android 蓝牙协议栈,蓝牙协议栈,bt stack,fluoride,A2DP
到此,btif层关于音频播放相关的内容就以介绍完毕,其中codec的实现在stack/a2dp中。文章来源地址https://www.toymoban.com/news/detail-805994.html

到了这里,关于Android蓝牙协议栈fluoride(十一) - 音乐播放(4)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android Studio 实现音乐播放器

    🍅 文章末尾有获取完整项目源码方式 🍅         Android初学者开发第一个完整的实例项目应该就属《音乐播放器》了,项目包含SQLlit数据库的使用、listview、Fragment、等。话不多说先上成品: Android Studio 音乐播放器 图片效果展示: 1.启动页效果 2.登录页效果 3.注册页效果

    2024年02月06日
    浏览(49)
  • 小项目开发——Android 音乐播放器

    ◼ 音乐播放器 . ◼ 要求 : Activity 编程、 ListView 编程、 SeekBar 编程、 ExoPlayer 编程( 播放 、 暂停 、 停止 、 上一首 、 下一首 ),音乐文件放在 assets/music 目录下,界面自拟. ◼ 期望最终效果: ◼ 分别对应 activity_music_list.xml 、 activity_my_music_player.xml 的视图. ◼ 点击列表任

    2024年01月21日
    浏览(45)
  • Android课程设计大作业-音乐播放器

    1)使用Service播放音乐 Android SDK提供了Service。Service有两种类型: 本地服务(Local Service):用于应用程序内部 远程服务(Remote Sercie):用于Android系统内部的应用程序之间前者用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如Activity所属线程,而是单

    2024年02月10日
    浏览(44)
  • Android程序设计之音乐播放器实现

    基于MediaPlayer技术实现在线音乐播放器,播放在线音乐,后端使用SpringBoot将音乐存放在Tomcat服务器。app通过网络请求获取音乐,从而实现在线音乐播放。该项目分为用户端和管理员端 一、核心技术Service组件介绍 Service它可以在后台执行长时间运行操作而没有用户界面的应用组

    2024年02月04日
    浏览(60)
  • Android手机开发课程设计之音乐播放器

    一、音乐播放器概述与分析 目前手机的音乐播放功能已经是大家比较关注的一个部分,不少在人在购买手机的时候都会关心手机的音乐播放的能力,这也足以看出目前大家对音乐播放功能的重视,所以一款性能良好的手机音乐播放器软件一定会受到欢迎。和传统的音乐播放器

    2024年02月05日
    浏览(55)
  • android Alarm闹钟发送广播播放音乐,吐血整理

    ========================================== 一共有3个类 MainActivity.java 主程序 AlarmReceiver.java 广播接收器 MusicService.java service播放音乐 MainActivity.java package com.yqy.yqy_alarm; import java.util.Calendar; import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.TimePickerDi

    2024年04月23日
    浏览(50)
  • Android开发教程:如何利用Service实现简单的音乐播放

    android音乐播放效果,简单的服务开启。 这里将用到android的四大组件之一:Service 注意:Service是自大组件之一,需要注册。 什么是服务? 1:“Service” 意思即“服务”的意思, 像 Windows 上面的服务一样,服务是在后台上运行,承担着静悄悄的不为人所注意的工作。 2:Serv

    2023年04月09日
    浏览(41)
  • Android Studio初学者实例:仿网易音乐播放器

    本期带来的是以Service为主要的知识点的网易音乐播放器 看一下效果图  首先项目准备: 在res下新建raw文件夹,并在文件夹中添加喜爱的mp3音乐  OK,第一步,先写一个背景文件,在res/drawable文件夹中新建xml文件: btn_bg_selector.xml  编写主界面代码activity_main.xml 编写MusicServic

    2024年02月05日
    浏览(52)
  • Android 9 蓝牙协议初始化

    先讲一下Application类的使用 要使用自定义的Application,首先就是要自己新建一个Application的子类,然后把它的名字写在manifest文件里面的application标签里的android:name属性就行,如我的Application子类名字是BaseApplication,则: 1. 初始化资源 由于Application类是在APP启动的时候就启动,

    2024年02月11日
    浏览(51)
  • Android Studio初学者实例:音乐播放器与Service学习

    本次一个案例实现的一个简单的音乐播放器 用到的知识点最主要的几点是:Service、handler(实现音乐播放的进度条更新与图片旋转)以及用于播放音频的MediaPlayer 看一下案例效果:  由于Service是Android的四大组件之一,Activity、Service等等一个重要知识点就是生命周期的问题,

    2024年02月03日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包