上一篇介绍了蓝牙音频的播放通路和编解码器,接下来介绍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
};
- 获取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;
...
}
- 获取SEP配置信息
通过AVDTP协议获取每个SEP的能力(codec类型、codec 能力),获取到的结果通过bta_av_co_audio_getconfig
回调通知给btif,结果包括SEP的codec信息、对端sep在本地的index、对端sep index等,btif会将这些信息保存在peer中(ProcessSinkGetConfig
和ProcessSourceGetConfig
),当对端所有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
...
}
- 选择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
)编码后的数据包进行发送,结束播放时,停止定时器,并清除各个阶段缓存的音频数据。文章来源:https://www.toymoban.com/news/detail-805994.html
音频解码(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中。文章来源地址https://www.toymoban.com/news/detail-805994.html
到了这里,关于Android蓝牙协议栈fluoride(十一) - 音乐播放(4)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!