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 sepBtaAvCoSep sources[BTAV_A2DP_CODEC_INDEX_MAX];  // 支持的source sepuint8_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 sepconst BtaAvCoSep* p_source;             // 当前选择的source sepuint8_t codec_config[AVDT_CODEC_SIZE];  // 当前的codec 配置bool acceptor;                          // true表示连接接受方,反之表示发起方private:tBTA_AV_HNDL bta_av_handle_;   // BTA AV handle to useA2dpCodecs* 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 peerBtaAvCoPeer* 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 codecif (A2DP_IsPeerSinkCodecValid(p_codec_info)) {// If there is room for a new oneif (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 codecfor (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中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/621218.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis分布式锁--java实现

文章目录 Redis分布式锁方案&#xff1a;SETNX EXPIRE基本原理比较好的实现会产生四个问题 几种解决原子性的方案方案&#xff1a;SETNX value值是&#xff08;系统时间过期时间&#xff09;方案&#xff1a;使用Lua脚本(包含SETNX EXPIRE两条指令)方案&#xff1a;SET的扩展…

低代码开发平台

低代码开发平台&#xff08;LCDP&#xff09;本身也是一种软件&#xff0c;它为开发者提供了一个创建应用软件的开发环境。看到“开发环境”几个字是不是很亲切&#xff1f;对于程序员而言&#xff0c;低代码开发平台的性质与IDEA、VS等代码IDE&#xff08;集成开发环境&#x…

蓝桥杯练习题(九)

&#x1f4d1;前言 本文主要是【算法】——蓝桥杯练习题&#xff08;九&#xff09;的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 …

Tomcat解压打包文件和并部署

一、文件压缩和上传解压 1.本地打包好dist.tar.gz文件 2.通过xftp拖拽上传到知道文件夹下,或者通过命令: cp dist.tar.gz /path/to/destination/folder注:将dist.tar.gz复制到 /path/to/destination/folder文件夹下,该文件夹只是举个例子怎么复制和解压! 3.进入/path/…

51-13 多模态论文串讲—BEiT v3 论文精读

BEiT-3的核心思想是将图像建模为一种语言&#xff0c;这样我们就可以对图像、文本以及图像-文本对进行统一的mask modeling。Multi-way transformer模型可以有效地完成不同的视觉和视觉语言任务&#xff0c;使其成为通用建模的一个有效选择。 同时&#xff0c;本文也对多模态大…

K8s-Pod资源(一)Pod介绍、创建Pod、Pod简单资源配额

Pod概述 Kubernetes Pod | Kubernetes Pod是Kubernetes中的最小调度单元&#xff0c;k8s都是以pod的方式运行服务的 一个pod可以指定镜像&#xff0c;封装一个或多个容器 pod需要调度到工作节点运行&#xff0c;节点的选择由scheduler调度器实现 pod定义时&#xff0c;会…

Android Studio 如何设置中文

Android Studio 是一个为 Adndroid 平台开发程序的集成开发环境&#xff08;IDE&#xff09;。 如何安装中文插件 在 Jetbrains 家族的插件市场上&#xff0c;是能够搜到语言包插件的&#xff0c;正常情况下安装之后只需要重启即可享受中文界面&#xff0c;可AndroidStudio 中…

视频剪辑软件Camtasia2024最新版本快捷键大全

Camtasia Studio是一款专门录制屏幕动作的工具&#xff0c;它能在任何颜色模式下轻松地记录 屏幕动作&#xff0c;包括影像、音效、鼠标移动轨迹、解说声音等等。 今天来给大家介绍一下Camtasia快捷键的相关内容&#xff0c;Camtasia也是一个十分好用的电脑屏幕录制与视频剪辑…

Linux系统——远程访问及控制

目录 一、OpenSSH服务器 1.SSH&#xff08;Secure Shell&#xff09;协议 2.OpenSSH 2.SSH原理 2.1公钥传输原理 2.2加密原理 &#xff08;1&#xff09;对称加密 &#xff08;2&#xff09;非对称加密 2.3远程登录 2.3.1延伸 2.3.2登录用户 3.SSH格式及选项 3.1延…

Pod调度

在默认情况下&#xff0c;一个Pod在哪个Node节点上运行&#xff0c;是由Scheduler组件采用相应的算法计算出来的&#xff0c;这个过程是不受人工控制的。但是在实际使用中&#xff0c;这并不满足的需求&#xff0c;因为很多情况下&#xff0c;我们想控制某些Pod到达某些节点上&…

高斯Hack算法

背景 刷leetcode时&#xff0c;碰到一题需要求解n个bit中选择m个bit的所有组合集&#xff0c;我只想到了递归求解&#xff0c;没啥问题&#xff0c;但是在官方题解中看到了牛逼的方法(Gospers Hack)&#xff0c;故记录一下。 4bit中2个1的情况 0011b0101b0110b1001b1010b1100b…

基于协同过滤的零食推荐系统

介绍 协同过滤算法&#xff1a;协同过滤是推荐系统中比较常用的算法之一&#xff0c;其核心思想是寻找用户之间的相似性&#xff0c;通过发现用户之间的共性&#xff0c;向用户推荐可能喜欢的商品或服务。Python Django Vue&#xff1a;Django 是一个基于 Python 的 Web 开发框…

模型的权值平均的原理和Pytorch的实现

一、前言 模型权值平均是一种用于改善深度神经网络泛化性能的技术。通过对训练过程中不同时间步的模型权值进行平均&#xff0c;可以得到更宽的极值点&#xff08;optima&#xff09;并提高模型的泛化能力。 在PyTorch中&#xff0c;官方提供了实现模型权值平均的方法。 这里…

PYTHON通过跳板机巡检CENTOS的简单实现

实现的细节和引用的文件和以前博客记录的基本一致 https://shaka.blog.csdn.net/article/details/106927633 差别在于,这次是通过跳板机登陆获取的主机信息,只记录差异的部份 1.需要在跳板机相应的路径放置PYTHON的脚本resc.py resc.py这个脚本中有引用的文件(pm.sh,diskpn…

查询速度提升15倍!银联商务基于 Apache Doris 的数据平台升级实践

本文导读&#xff1a; 在长期服务广大规模商户的过程中&#xff0c;银联商务已沉淀了庞大、真实、优质的数据资产数据&#xff0c;这些数据不仅是银联商务开启新增长曲线的基础&#xff0c;更是进一步服务好商户的关键支撑。为更好提供数据服务&#xff0c;银联商务实现了从 H…

EI级 | Matlab实现VMD-TCN-LSTM变分模态分解结合时间卷积长短期记忆神经网络多变量光伏功率时间序列预测

EI级 | Matlab实现VMD-TCN-LSTM变分模态分解结合时间卷积长短期记忆神经网络多变量光伏功率时间序列预测 目录 EI级 | Matlab实现VMD-TCN-LSTM变分模态分解结合时间卷积长短期记忆神经网络多变量光伏功率时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.【E…

Springboot3新特性:开发第一个 GraalVM 本机应用程序(完整教程)

在讲述之前&#xff0c;各位先自行在网上下载并安装Visual Studio 2022&#xff0c;安装的时候别忘了勾选msvc 概述&#xff1a;GraalVM 本机应用程序&#xff08;Native Image&#xff09;是使用 GraalVM 的一个特性&#xff0c;允许将 Java 应用程序编译成本机二进制文件&am…

AI-数学-高中-5.求函数解析式(4种方法)

原作者视频&#xff1a;函数】3函数解析式求法&#xff08;易&#xff09;_哔哩哔哩_bilibili 1.已知函数类型-待定系数法&#xff1a;先用待定系数法把一次或二次函数一般表达式写出来&#xff1b;再用“要变一起变”左右两边同时替换&#xff0c;计算出一般表达式的常数&…

SpringIOC之support模块GenericGroovyApplicationContext

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

【c++】利用嵌套map创建多层树结构

通常树的深度都大于1&#xff0c;即树有多层&#xff0c;而树结构又可以用c的map容器来实现&#xff0c;所以&#xff0c;本文给出了一种多层树结构的实现思路&#xff0c;同时也给出了相应的c代码。 整体思路概述 首先定义一个节点类Node类&#xff0c;要包括children&#x…