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的扩展…

设计模式之多线程版本的if------Balking模式

系列文章目录 设计模式之避免共享的设计模式Immutability&#xff08;不变性&#xff09;模式 设计模式之并发特定场景下的设计模式 Two-phase Termination&#xff08;两阶段终止&#xff09;模式 设计模式之避免共享的设计模式Copy-on-Write模式 设计模式之避免共享的设计模…

watchdog,一个无敌的 Python 库

大家好&#xff0c;今天为大家分享一个无敌的 Python 库 - watchdog。 在软件开发和系统管理领域&#xff0c;经常需要监控文件和目录的变化&#xff0c;以便在文件被创建、修改或删除时触发相应的操作。Python Watchdog是一个强大的Python库&#xff0c;它提供了简单而灵活的…

低代码开发平台

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

蓝桥杯练习题(九)

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

抖店需要怎么开通?抖店入驻全流程,一看就会!

我是电商珠珠 抖店的热度很高&#xff0c;所以有很多新手想要入驻。 但是对于入驻的流程&#xff0c;部分新手还不太了解&#xff0c;今天我就来给大家详细的讲一下。 入驻准备 在入驻之前需要准备一张个体的营业执照&#xff0c;再准备好个人的身份证、银行卡、抖音号。 …

Tomcat解压打包文件和并部署

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

使用Python批量上传本地maven库到nexus

背景&#xff1a;外包类项目开发时是调用的公司maven仓库进行开发&#xff0c;交付后需要将maven仓库转移到客户环境。 原理&#xff1a;1、打开idea运行源代码&#xff0c;将maven包下载到本地仓库&#xff0c; 2、下载包所在目录中执行脚本将本地仓库的maven包上传到客户nex…

UE5 C++的TCP客户端示例

客户端.h 需要在Build.cs中加入模块:"Networking","Sockets","Json","JsonUtilities" // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include…

LeetCode第380场周赛个人题解

目录 100162.最大频率元素计数 原题链接 思路分析 AC代码 100165.找出数组中的美丽下标I 原题链接 思路分析 AC代码 100160. 价值和小于等于 K 的最大数字 原题链接 思路分析 位运算二分 AC代码 100207.找出数组中的美丽下标II 原题链接 思路分析 AC代码 10016…

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 中…

Python 列表、元组、字典区别

1.列表、元组和字典都是序列 2.列表字典可以修改和删除序列中的某个元素&#xff0c;而元组就是一个整体&#xff0c;不能修改和删除&#xff0c;一定要修改或删除的话&#xff0c;只能修改和删除整个元组。 3.既然元组不能删除和修改&#xff0c;有什么作用呢&#xff1f; 1…

视频剪辑软件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到达某些节点上&…

跟我学c++高级篇——基础类型和POD结构体反射

一、基础类型反射 在前面以枚举体和函数进行了反射的实践&#xff0c;在其中也提到过&#xff0c;其实更应该进行反射的是类&#xff08;结构体&#xff09;。毕竟现在以面向对象编程基本已经普及&#xff0c;类&#xff08;结构体&#xff09;的应用几乎已经是无法避免的。所…

高斯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 开发框…