除了使用HIDL来区分蓝牙的公共stack和vendor lib,Oreo对蓝牙A2DP的支持也更加全面。除了A2DP profile强制支持的SBC,Oreo新增了对AAC、APTX、LDAC的编码定义。具体是否支持,还得看实现。这里简单说明Oreo是如何做到支持多种编码,并选择合适的编码方式的。
Java world的声明
codec的支持是A2DP profile的声明的。在Bluetooth.apk中,编码的支持包含在A2dpStateMachine当中。它在自己的构造函数中对编码的支持进行了初始化:
private A2dpStateMachine(A2dpService svc, Context context) {
//......
mCodecConfigPriorities = assignCodecConfigPriorities();
initNative(mCodecConfigPriorities);
//......
}
assignCodecConfigPriorities的作用是,首先加载各个codec的priority,再将每个支持的codec参数初始化。codec的priority保存在配置文件values/config.xml中。就目前的设置来看,它支持五种编码:
<!-- Configuring priorities of A2DP source codecs. Larger value means
higher priority. Value -1 means the codec is disabled.
Value 0 is reserved and should not be used here. Enabled codecs
should have priorities in the interval [1, 999999], and each priority
value should be unique. -->
<integer name="a2dp_source_codec_priority_sbc">1001</integer>
<integer name="a2dp_source_codec_priority_aac">2001</integer>
<integer name="a2dp_source_codec_priority_aptx">3001</integer>
<integer name="a2dp_source_codec_priority_aptx_hd">4001</integer>
<integer name="a2dp_source_codec_priority_ldac">5001</integer>
priority本身没有什么意义,它们的相对值决定应该选择使用哪种编码。在以上所有五种编码都已经实现了的情况下,Android会选择使用LDAC作为A2DP(source)的codec。这是它自己的偏好,还是说LDAC是更好的编码方式呢?除了priority,assignCodecConfigPriorities还会初始化各个codec的参数。由于实际使用的参数是有source和sink协商决定的,而encode和decode这种需要较高time efficiency的操作都是方法C++实现的native world中的,这里的初始化并没有什么意义。
BT stack的codec初始化
到了native world,BT stack会对所有codec进行初始化。BT stack在对a2dp初始化是就会完成这个动作,调用A2dpCodecConfig::createCodec创建A2dpCodecConfig对象。各个codec会有自己的codec config构造函数:
A2dpCodecConfig* A2dpCodecConfig::createCodec(
btav_a2dp_codec_index_t codec_index,
btav_a2dp_codec_priority_t codec_priority) {
LOG_DEBUG(LOG_TAG, "%s: codec %s", __func__, A2DP_CodecIndexStr(codec_index));
A2dpCodecConfig* codec_config = nullptr;
switch (codec_index) {
case BTAV_A2DP_CODEC_INDEX_SOURCE_SBC:
codec_config = new A2dpCodecConfigSbc(codec_priority);
break;
case BTAV_A2DP_CODEC_INDEX_SINK_SBC:
codec_config = new A2dpCodecConfigSbcSink(codec_priority);
break;
case BTAV_A2DP_CODEC_INDEX_SOURCE_AAC:
codec_config = new A2dpCodecConfigAac(codec_priority);
break;
case BTAV_A2DP_CODEC_INDEX_SOURCE_APTX:
codec_config = new A2dpCodecConfigAptx(codec_priority);
break;
case BTAV_A2DP_CODEC_INDEX_SOURCE_APTX_HD:
codec_config = new A2dpCodecConfigAptxHd(codec_priority);
break;
case BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC:
codec_config = new A2dpCodecConfigLdac(codec_priority);
break;
// Add a switch statement for each vendor-specific codec
case BTAV_A2DP_CODEC_INDEX_MAX:
break;
}
if (codec_config != nullptr) {
if (!codec_config->init()) {
delete codec_config;
codec_config = nullptr;
}
}
return codec_config;
}
首先可以注意到的是,对于sink而言,Oreo仍然只支持SBC编码;所有新增的codec都用于source。其次,每个codec有自己的codec_config构造,但即使创建成功,init失败了也会无法使用此codec。这里简单提一下BT stack对各个编码的支持。对于SBC和AAC,BT stack使用静态连接,因此无需再去单独加载它们的库文件。对于剩下的APTX、APTX_HD和LDAC,它们分别需要加载“libaptX_encoder.so”、“libaptXHD_encoder.so”和“libldacBT_enc.so”。对于前两者,它们目前属于qcom,因此你只能在使用qcom BT的平台上支持它们。而Oreo的代码中似乎有libladcBT_enc.so这个文件,因此理论上它也是可以默认支持的。
选择合适的codec
使用蓝牙音箱来播放音乐时,该如何选择“最适合”的音频编码呢?首先,选择的codec一定要是source和sink双方都支持的。这里的支持不仅仅是说,双方都支持某一个编码方式,它们对这个特定编码方式的支持能力也要是匹配的。举一反例来说,如果source支持仅支持bitpool为2~35的SBC编码,而sink支持40~53,这样SBC也是无法使用的。其次,BT stack要考虑priorities,在所有available的codec中选择priority最高的。具体的实现是在函数bta_av_co_audio_set_codec中:
//
// Select the current codec configuration based on peer codec support.
// Furthermore, the local state for the remaining non-selected codecs is
// updated to reflect whether the codec is selectable.
// Return a pointer to the corresponding |tBTA_AV_CO_SINK| sink entry
// on success, otherwise NULL.
//
static tBTA_AV_CO_SINK* bta_av_co_audio_set_codec(tBTA_AV_CO_PEER* p_peer) {
tBTA_AV_CO_SINK* p_sink = NULL;
// Update all selectable codecs.
// This is needed to update the selectable parameters for each codec.
// NOTE: The selectable codec info is used only for informational purpose.
for (const auto& iter : bta_av_co_cb.codecs->orderedSourceCodecs()) {
APPL_TRACE_DEBUG("%s: updating selectable codec %s", __func__,
iter->name().c_str());
bta_av_co_audio_update_selectable_codec(*iter, p_peer);
}
// Select the codec
for (const auto& iter : bta_av_co_cb.codecs->orderedSourceCodecs()) {
APPL_TRACE_DEBUG("%s: trying codec %s", __func__, iter->name().c_str());
p_sink = bta_av_co_audio_codec_selected(*iter, p_peer);
if (p_sink != NULL) {
APPL_TRACE_DEBUG("%s: selected codec %s", __func__, iter->name().c_str());
break;
}
APPL_TRACE_DEBUG("%s: cannot use codec %s", __func__, iter->name().c_str());
}
// NOTE: Unconditionally dispatch the event to make sure a callback with
// the most recent codec info is generated.
btif_dispatch_sm_event(BTIF_AV_SOURCE_CONFIG_UPDATED_EVT, NULL, 0);
return p_sink;
}
首先,基于双方的能力,在bta_av_co_audio_update_selectable_codec中更新每个codec的参数。bta_av_co_audio_codec_selected会决定最终的codec。显然,这里是按照priority由高至低一个一个去check是否支持。
More
如何添加vendor specific编码呢?Java和C++代码都需要修改,而stack中的代码似乎不少,需要按照模板去实现codec的相应方法。
Source的codec已经支持的不错了,不晓得今后会不会提高对sink的支持。