Oreo对A2DP codec的支持

Android Oreo增强了A2DP编码支持,包括SBC、AAC、APTX和LDAC。编码的选择取决于设备能力和配置文件的优先级。在Java层,Bluetooth.apk的A2dpStateMachine初始化编码支持。在C++层,BT stack初始化codec并根据双方设备的能力和优先级选择合适的编码。目前,Oreo默认支持SBC,新增编码主要用于source,如APTX和LDAC需要特定平台支持。

除了使用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的支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值