| /* |
| * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * * Neither the name of The Linux Foundation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #define LOG_TAG "PAL: Bluetooth" |
| #include "Bluetooth.h" |
| #include "ResourceManager.h" |
| #include "PayloadBuilder.h" |
| #include "Stream.h" |
| #include "Session.h" |
| #include "SessionAlsaUtils.h" |
| #include "Device.h" |
| #include "kvh2xml.h" |
| #include <dlfcn.h> |
| #include <unistd.h> |
| #include <cutils/properties.h> |
| #include <sstream> |
| #include <string> |
| #include <regex> |
| |
| #define PARAM_ID_RESET_PLACEHOLDER_MODULE 0x08001173 |
| #define BT_IPC_SOURCE_LIB "btaudio_offload_if.so" |
| #define BT_IPC_SINK_LIB "libbthost_if_sink.so" |
| #define MIXER_SET_FEEDBACK_CHANNEL "BT set feedback channel" |
| #define BT_SLIMBUS_CLK_STR "BT SLIMBUS CLK SRC" |
| |
| Bluetooth::Bluetooth(struct pal_device *device, std::shared_ptr<ResourceManager> Rm) |
| : Device(device, Rm), |
| codecFormat(CODEC_TYPE_INVALID), |
| codecInfo(NULL), |
| isAbrEnabled(false), |
| isConfigured(false), |
| isLC3MonoModeOn(false), |
| isTwsMonoModeOn(false), |
| isScramblingEnabled(false), |
| isDummySink(false), |
| isEncDecConfigured(false), |
| abrRefCnt(0), |
| totalActiveSessionRequests(0) |
| { |
| } |
| |
| Bluetooth::~Bluetooth() |
| { |
| } |
| |
| int Bluetooth::updateDeviceMetadata() |
| { |
| int ret = 0; |
| std::string backEndName; |
| std::vector <std::pair<int, int>> keyVector; |
| |
| ret = PayloadBuilder::getBtDeviceKV(deviceAttr.id, keyVector, codecFormat, |
| isAbrEnabled, false); |
| if (ret) |
| PAL_ERR(LOG_TAG, "No KVs found for device id %d codec format:0x%x", |
| deviceAttr.id, codecFormat); |
| |
| rm->getBackendName(deviceAttr.id, backEndName); |
| ret = SessionAlsaUtils::setDeviceMetadata(rm, backEndName, keyVector); |
| return ret; |
| } |
| |
| void Bluetooth::updateDeviceAttributes() |
| { |
| deviceAttr.config.sample_rate = codecConfig.sample_rate; |
| |
| switch (codecFormat) { |
| case CODEC_TYPE_AAC: |
| case CODEC_TYPE_SBC: |
| if (codecType == DEC && |
| (codecConfig.sample_rate == 44100 || |
| codecConfig.sample_rate == 48000)) |
| deviceAttr.config.sample_rate = codecConfig.sample_rate * 2; |
| break; |
| case CODEC_TYPE_LDAC: |
| case CODEC_TYPE_APTX_AD: |
| if (codecType == ENC && |
| (codecConfig.sample_rate == 44100 || |
| codecConfig.sample_rate == 48000)) |
| deviceAttr.config.sample_rate = codecConfig.sample_rate * 2; |
| break; |
| case CODEC_TYPE_APTX_AD_SPEECH: |
| deviceAttr.config.sample_rate = SAMPLINGRATE_96K; |
| deviceAttr.config.aud_fmt_id = PAL_AUDIO_FMT_DEFAULT_COMPRESSED; |
| break; |
| case CODEC_TYPE_LC3: |
| case CODEC_TYPE_APTX_AD_QLEA: |
| deviceAttr.config.sample_rate = SAMPLINGRATE_96K; |
| deviceAttr.config.aud_fmt_id = PAL_AUDIO_FMT_DEFAULT_COMPRESSED; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| bool Bluetooth::isPlaceholderEncoder() |
| { |
| switch (codecFormat) { |
| case CODEC_TYPE_LDAC: |
| case CODEC_TYPE_APTX_AD: |
| case CODEC_TYPE_APTX_AD_SPEECH: |
| case CODEC_TYPE_LC3: |
| case CODEC_TYPE_APTX_AD_QLEA: |
| return false; |
| case CODEC_TYPE_AAC: |
| return isAbrEnabled ? false : true; |
| default: |
| return true; |
| } |
| } |
| |
| int Bluetooth::getPluginPayload(void **libHandle, bt_codec_t **btCodec, |
| bt_enc_payload_t **out_buf, codec_type codecType) |
| { |
| std::string lib_path; |
| open_fn_t plugin_open_fn = NULL; |
| int status = 0; |
| bt_codec_t *codec = NULL; |
| void *handle = NULL; |
| |
| lib_path = rm->getBtCodecLib(codecFormat, (codecType == ENC ? "enc" : "dec")); |
| if (lib_path.empty()) { |
| PAL_ERR(LOG_TAG, "fail to get BT codec library"); |
| return -ENOSYS; |
| } |
| |
| handle = dlopen(lib_path.c_str(), RTLD_NOW); |
| if (handle == NULL) { |
| PAL_ERR(LOG_TAG, "failed to dlopen lib %s", lib_path.c_str()); |
| return -EINVAL; |
| } |
| |
| dlerror(); |
| plugin_open_fn = (open_fn_t)dlsym(handle, "plugin_open"); |
| if (!plugin_open_fn) { |
| PAL_ERR(LOG_TAG, "dlsym to open fn failed, err = '%s'", dlerror()); |
| status = -EINVAL; |
| goto error; |
| } |
| |
| status = plugin_open_fn(&codec, codecFormat, codecType); |
| if (status) { |
| PAL_ERR(LOG_TAG, "failed to open plugin %d", status); |
| goto error; |
| } |
| |
| status = codec->plugin_populate_payload(codec, codecInfo, (void **)out_buf); |
| if (status != 0) { |
| PAL_ERR(LOG_TAG, "fail to pack the encoder config %d", status); |
| goto error; |
| } |
| *btCodec = codec; |
| *libHandle = handle; |
| goto done; |
| |
| error: |
| if (codec) |
| codec->close_plugin(codec); |
| |
| if (handle) |
| dlclose(handle); |
| done: |
| return status; |
| } |
| |
| int Bluetooth::configureA2dpEncoderDecoder() |
| { |
| int status = 0, i; |
| Stream *stream = NULL; |
| Session *session = NULL; |
| std::vector<Stream*> activestreams; |
| bt_enc_payload_t *out_buf = NULL; |
| PayloadBuilder* builder = new PayloadBuilder(); |
| std::string backEndName; |
| uint8_t* paramData = NULL; |
| size_t paramSize = 0; |
| uint32_t codecTagId = 0; |
| uint32_t miid = 0, ratMiid = 0, copMiid = 0, cnvMiid = 0; |
| std::shared_ptr<Device> dev = nullptr; |
| uint32_t num_payloads = 0; |
| |
| isConfigured = false; |
| |
| PAL_DBG(LOG_TAG, "Enter"); |
| |
| rm->getBackendName(deviceAttr.id, backEndName); |
| |
| dev = Device::getInstance(&deviceAttr, rm); |
| status = rm->getActiveStream_l(activestreams, dev); |
| if ((0 != status) || (activestreams.size() == 0)) { |
| PAL_ERR(LOG_TAG, "no active stream available"); |
| status = -EINVAL; |
| goto error; |
| } |
| stream = static_cast<Stream *>(activestreams[0]); |
| stream->getAssociatedSession(&session); |
| PAL_INFO(LOG_TAG, "choose BT codec format %x", codecFormat); |
| |
| |
| /* Retrieve plugin library from resource manager. |
| * Map to interested symbols. |
| */ |
| status = getPluginPayload(&pluginHandler, &pluginCodec, &out_buf, codecType); |
| if (status) { |
| PAL_ERR(LOG_TAG, "failed to payload from plugin"); |
| goto error; |
| } |
| |
| codecConfig.sample_rate = out_buf->sample_rate; |
| codecConfig.bit_width = out_buf->bit_format; |
| codecConfig.ch_info.channels = out_buf->channel_count; |
| |
| isAbrEnabled = out_buf->is_abr_enabled; |
| isEncDecConfigured = (out_buf->is_enc_config_set && out_buf->is_dec_config_set); |
| |
| /* Reset device GKV for AAC ABR */ |
| if ((codecFormat == CODEC_TYPE_AAC) && isAbrEnabled) |
| updateDeviceMetadata(); |
| |
| /* Update Device sampleRate based on encoder config */ |
| updateDeviceAttributes(); |
| |
| codecTagId = (codecType == ENC ? BT_PLACEHOLDER_ENCODER : BT_PLACEHOLDER_DECODER); |
| status = session->getMIID(backEndName.c_str(), codecTagId, &miid); |
| if (status) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, status = %d", codecTagId, status); |
| goto error; |
| } |
| |
| if (isPlaceholderEncoder()) { |
| PAL_DBG(LOG_TAG, "Resetting placeholder module"); |
| builder->payloadCustomParam(¶mData, ¶mSize, NULL, 0, |
| miid, PARAM_ID_RESET_PLACEHOLDER_MODULE); |
| if (!paramData) { |
| PAL_ERR(LOG_TAG, "Failed to populateAPMHeader"); |
| status = -ENOMEM; |
| goto error; |
| } |
| dev->updateCustomPayload(paramData, paramSize); |
| free(paramData); |
| paramData = NULL; |
| paramSize = 0; |
| } |
| |
| /* BT Encoder & Decoder Module Configuration */ |
| num_payloads = out_buf->num_blks; |
| for (i = 0; i < num_payloads; i++) { |
| custom_block_t *blk = out_buf->blocks[i]; |
| builder->payloadCustomParam(¶mData, ¶mSize, |
| (uint32_t *)blk->payload, blk->payload_sz, miid, blk->param_id); |
| if (!paramData) { |
| PAL_ERR(LOG_TAG, "Failed to populateAPMHeader"); |
| status = -ENOMEM; |
| goto error; |
| } |
| dev->updateCustomPayload(paramData, paramSize); |
| free(paramData); |
| paramData = NULL; |
| paramSize = 0; |
| } |
| |
| /* --------------------------------------------------------------------------- |
| * | Encoder | PSPD MFC/RAT/PCM CNV | COP Packetizer/HW EP |
| * --------------------------------------------------------------------------- |
| * SBC | E_SR = SR of encoder | Same as encoder | SR:E_SR BW:16 CH:1 |
| * ------| |----------------------|---------------------- |
| * AAC | E_CH = CH of encoder | Same as encoder | SR:E_SR BW:16 CH:1 |
| * ------| |----------------------|---------------------- |
| * LDAC | E_BW = BW of encoder | Same as encoder | if E_SR = 44.1/48KHz |
| * | | | SR:E_SR*2 BW:16 CH:1 |
| * | | | else |
| * | | | SR:E_SR BW:16 CH:1 |
| * ------| |----------------------|---------------------- |
| * APTX | | Same as encoder | SR:E_SR BW:16 CH:1 |
| * ------| |----------------------|---------------------- |
| * APTX | | Same as encoder | SR:E_SR BW:16 CH:1 |
| * HD | | | |
| * ------| |----------------------|---------------------- |
| * APTX | | Same as encoder | if E_SR = 44.1/48KHz |
| * AD | | | SR:E_SR*2 BW:16 CH:1 |
| * | | | else |
| * | | | SR:E_SR BW:16 CH:1 |
| * ------|----------------------|----------------------|---------------------- |
| * LC3 | E_SR = SR of encoder | Same as encoder | SR:96KHz BW:16 CH:1 |
| * | E_CH = CH of encoder | | |
| * | E_BW = 24 | | |
| * --------------------------------------------------------------------------- |
| * APTX | E_SR = 32KHz | Same as encoder | SR:96KHz BW:16 CH:1 |
| * AD Speech | E_CH = 1 | | |
| * | E_BW = 16 | | |
| * --------------------------------------------------------------------------- |
| * LC3 | E_SR = SR of encoder | Same as encoder | SR:96KHz BW:16 CH:1 |
| * Voice | E_CH = CH of encoder | | |
| * | E_BW = 24 | | |
| * --------------------------------------------------------------------------- */ |
| if (codecFormat == CODEC_TYPE_APTX_AD_SPEECH) { |
| PAL_DBG(LOG_TAG, "Skip the rest of static configurations coming from ACDB"); |
| goto done; |
| } |
| |
| if (codecFormat == CODEC_TYPE_APTX_DUAL_MONO || |
| codecFormat == CODEC_TYPE_APTX_AD) { |
| builder->payloadTWSConfig(¶mData, ¶mSize, miid, |
| isTwsMonoModeOn, codecFormat); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| free(paramData); |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid TWS param size"); |
| goto error; |
| } |
| } |
| |
| if (codecFormat == CODEC_TYPE_LC3 || codecFormat == CODEC_TYPE_APTX_AD_QLEA) { |
| builder->payloadLC3Config(¶mData, ¶mSize, miid, |
| isLC3MonoModeOn); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| free(paramData); |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid LC3 param size"); |
| goto error; |
| } |
| |
| if (codecType == DEC) { |
| /* COP v2 DEPACKETIZER Module Configuration */ |
| status = session->getMIID(backEndName.c_str(), COP_DEPACKETIZER_V2, &copMiid); |
| if (status) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, status = %d", |
| COP_DEPACKETIZER_V2, status); |
| goto error; |
| } |
| |
| builder->payloadCopV2DepackConfig(¶mData, ¶mSize, |
| copMiid, codecInfo, false /* StreamMapOut */); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid COPv2 module param size"); |
| goto error; |
| } |
| |
| builder->payloadCopV2DepackConfig(¶mData, ¶mSize, |
| copMiid, codecInfo, true /* StreamMapIn */); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid COPv2 module param size"); |
| goto error; |
| } |
| |
| /* RAT Module Configuration */ |
| status = session->getMIID(backEndName.c_str(), RAT_RENDER, &ratMiid); |
| if (status) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, status = %d", RAT_RENDER, status); |
| goto error; |
| } else { |
| builder->payloadRATConfig(¶mData, ¶mSize, ratMiid, &codecConfig); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| free(paramData); |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid RAT module param size"); |
| goto error; |
| } |
| } |
| |
| /* PCM CNV Module Configuration */ |
| status = session->getMIID(backEndName.c_str(), BT_PCM_CONVERTER, &cnvMiid); |
| if (status) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, status = %d", |
| BT_PCM_CONVERTER, status); |
| goto error; |
| } |
| |
| builder->payloadPcmCnvConfig(¶mData, ¶mSize, cnvMiid, &codecConfig, false /* isRx */); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid PCM CNV module param size"); |
| goto error; |
| } |
| |
| goto done; |
| } |
| |
| /* COP v2 PACKETIZER Module Configuration */ |
| status = session->getMIID(backEndName.c_str(), COP_PACKETIZER_V2, &copMiid); |
| if (status) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, status = %d", |
| COP_PACKETIZER_V2, status); |
| goto error; |
| } |
| |
| // PARAM_ID_COP_V2_STREAM_INFO for COPv2 |
| builder->payloadCopV2PackConfig(¶mData, ¶mSize, copMiid, codecInfo); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid COPv2 module param size"); |
| goto error; |
| } |
| |
| // PARAM_ID_COP_PACKETIZER_OUTPUT_MEDIA_FORMAT for COPv2 |
| builder->payloadCopPackConfig(¶mData, ¶mSize, copMiid, &deviceAttr.config); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid COP module param size"); |
| goto error; |
| } |
| } else { /* codecFormat != CODEC_TYPE_LC3 */ |
| // Bypass COP V0 DEPACKETIZER Module Configuration for TX path |
| if (codecType == DEC) |
| goto done; |
| |
| /* COP v0 PACKETIZER Module Configuration */ |
| status = session->getMIID(backEndName.c_str(), COP_PACKETIZER_V0, &copMiid); |
| if (status) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, status = %d", |
| COP_PACKETIZER_V0, status); |
| goto error; |
| } |
| |
| // PARAM_ID_COP_PACKETIZER_OUTPUT_MEDIA_FORMAT for COPv0 |
| builder->payloadCopPackConfig(¶mData, ¶mSize, copMiid, &deviceAttr.config); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid COP module param size"); |
| goto error; |
| } |
| |
| if (isScramblingEnabled) { |
| builder->payloadScramblingConfig(¶mData, ¶mSize, copMiid, isScramblingEnabled); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid COP module param size"); |
| goto error; |
| } |
| } |
| } |
| |
| /* RAT Module Configuration */ |
| status = session->getMIID(backEndName.c_str(), RAT_RENDER, &ratMiid); |
| if (status) { |
| // Graphs with abr enabled will have RAT, Other graphs do not need a RAT. |
| // Hence not configuring RAT module can be ignored. |
| if (isAbrEnabled) { |
| PAL_ERR(LOG_TAG, "Failed to get tag for RAT_RENDER for isAbrEnabled %d," |
| "codecFormat = %x", isAbrEnabled, codecFormat); |
| goto error; |
| } else { |
| PAL_INFO(LOG_TAG, "Failed to get tag info %x, status = %d - can be ignored", |
| RAT_RENDER, status); |
| } |
| } else { |
| builder->payloadRATConfig(¶mData, ¶mSize, ratMiid, &codecConfig); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| free(paramData); |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid RAT module param size"); |
| goto error; |
| } |
| } |
| |
| /* PCM CNV Module Configuration */ |
| status = session->getMIID(backEndName.c_str(), BT_PCM_CONVERTER, &cnvMiid); |
| if (status) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, status = %d", |
| BT_PCM_CONVERTER, status); |
| goto error; |
| } |
| |
| builder->payloadPcmCnvConfig(¶mData, ¶mSize, cnvMiid, &codecConfig, true /* isRx */); |
| if (paramSize) { |
| dev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid PCM CNV module param size"); |
| goto error; |
| } |
| |
| done: |
| isConfigured = true; |
| |
| error: |
| if (builder) { |
| delete builder; |
| builder = NULL; |
| } |
| PAL_DBG(LOG_TAG, "Exit"); |
| return status; |
| } |
| |
| |
| int Bluetooth::configureNrecParameters(bool isNrecEnabled) |
| { |
| int status = 0, i; |
| Stream *stream = NULL; |
| Session *session = NULL; |
| std::vector<Stream*> activestreams; |
| PayloadBuilder* builder = new PayloadBuilder(); |
| std::string backEndName; |
| uint8_t* paramData = NULL; |
| size_t paramSize = 0; |
| uint32_t miid = 0; |
| std::shared_ptr<Device> dev = nullptr; |
| uint32_t num_payloads = 0; |
| |
| PAL_DBG(LOG_TAG, "Enter"); |
| if (!builder) { |
| PAL_ERR(LOG_TAG, "Failed to new PayloadBuilder()"); |
| status = -ENOMEM; |
| goto exit; |
| } |
| rm->getBackendName(deviceAttr.id, backEndName); |
| dev = Device::getInstance(&deviceAttr, rm); |
| if (dev == 0) { |
| PAL_ERR(LOG_TAG, "device_id[%d] Instance query failed", deviceAttr.id ); |
| status = -EINVAL; |
| goto exit; |
| } |
| |
| status = rm->getActiveStream_l(activestreams, dev); |
| if ((0 != status) || (activestreams.size() == 0)) { |
| PAL_ERR(LOG_TAG, "no active stream available"); |
| status = -EINVAL; |
| goto exit; |
| } |
| stream = static_cast<Stream *>(activestreams[0]); |
| stream->getAssociatedSession(&session); |
| |
| status = session->getMIID(backEndName.c_str(), TAG_ECNS, &miid); |
| if (!status) { |
| PAL_DBG(LOG_TAG, "Setting NREC Configuration"); |
| builder->payloadNRECConfig(¶mData, ¶mSize, |
| miid, isNrecEnabled); |
| if (!paramData) { |
| PAL_ERR(LOG_TAG, "Failed to payloadNRECConfig"); |
| status = -ENOMEM; |
| goto exit; |
| } |
| dev->updateCustomPayload(paramData, paramSize); |
| free(paramData); |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| PAL_ERR(LOG_TAG, "Failed to find ECNS module info %x, status = %d" |
| "cannot set NREC parameters", |
| TAG_ECNS, status); |
| } |
| exit: |
| if (builder) { |
| delete builder; |
| builder = NULL; |
| } |
| PAL_DBG(LOG_TAG, "Exit"); |
| return status; |
| } |
| |
| int Bluetooth::getCodecConfig(struct pal_media_config *config) |
| { |
| if (!config) { |
| PAL_ERR(LOG_TAG, "Invalid codec config"); |
| return -EINVAL; |
| } |
| |
| if (isConfigured) { |
| ar_mem_cpy(config, sizeof(struct pal_media_config), |
| &codecConfig, sizeof(struct pal_media_config)); |
| } |
| |
| return 0; |
| } |
| |
| void Bluetooth::startAbr() |
| { |
| int ret = 0, dir; |
| struct pal_device fbDevice; |
| struct pal_channel_info ch_info; |
| struct pal_stream_attributes sAttr; |
| std::string backEndName; |
| std::vector <std::pair<int, int>> keyVector; |
| struct pcm_config config; |
| struct mixer_ctl *connectCtrl = NULL; |
| struct mixer_ctl *disconnectCtrl = NULL; |
| struct mixer_ctl *btSetFeedbackChannelCtrl = NULL; |
| struct mixer *virtualMixerHandle = NULL; |
| struct mixer *hwMixerHandle = NULL; |
| std::ostringstream connectCtrlName; |
| std::ostringstream disconnectCtrlName; |
| unsigned int flags; |
| uint32_t codecTagId = 0, miid = 0; |
| void *pluginLibHandle = NULL; |
| bt_codec_t *codec = NULL; |
| bt_enc_payload_t *out_buf = NULL; |
| custom_block_t *blk = NULL; |
| uint8_t* paramData = NULL; |
| size_t paramSize = 0; |
| PayloadBuilder* builder = NULL; |
| bool isDeviceLocked = false; |
| audio_lc3_codec_cfg_t* bt_ble_codec = NULL; |
| |
| memset(&fbDevice, 0, sizeof(fbDevice)); |
| memset(&sAttr, 0, sizeof(sAttr)); |
| memset(&config, 0, sizeof(config)); |
| |
| mAbrMutex.lock(); |
| if (abrRefCnt > 0) { |
| abrRefCnt++; |
| mAbrMutex.unlock(); |
| return; |
| } |
| /* Configure device attributes */ |
| ch_info.channels = CHANNELS_1; |
| ch_info.ch_map[0] = PAL_CHMAP_CHANNEL_FL; |
| fbDevice.config.ch_info = ch_info; |
| fbDevice.config.bit_width = BITWIDTH_16; |
| fbDevice.config.aud_fmt_id = PAL_AUDIO_FMT_DEFAULT_COMPRESSED; |
| |
| if ((codecFormat == CODEC_TYPE_APTX_AD_SPEECH) || |
| (codecFormat == CODEC_TYPE_LC3) || |
| (codecFormat == CODEC_TYPE_APTX_AD_QLEA)) { |
| fbDevice.config.sample_rate = SAMPLINGRATE_96K; |
| } else { |
| fbDevice.config.sample_rate = SAMPLINGRATE_8K; |
| } |
| |
| if (codecType == DEC) { /* Usecase is TX, feedback device will be RX */ |
| if (deviceAttr.id == PAL_DEVICE_IN_BLUETOOTH_A2DP) { |
| fbDevice.id = PAL_DEVICE_OUT_BLUETOOTH_A2DP; |
| } else if (deviceAttr.id == PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET) { |
| fbDevice.id = PAL_DEVICE_OUT_BLUETOOTH_SCO; |
| } else if (deviceAttr.id == PAL_DEVICE_IN_BLUETOOTH_BLE) { |
| fbDevice.id = PAL_DEVICE_OUT_BLUETOOTH_BLE; |
| } |
| dir = RX_HOSTLESS; |
| flags = PCM_OUT; |
| } else { |
| if (deviceAttr.id == PAL_DEVICE_OUT_BLUETOOTH_A2DP) { |
| fbDevice.id = PAL_DEVICE_IN_BLUETOOTH_A2DP; |
| } else if (deviceAttr.id == PAL_DEVICE_OUT_BLUETOOTH_SCO) { |
| fbDevice.id = PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET; |
| } else if (deviceAttr.id == PAL_DEVICE_OUT_BLUETOOTH_BLE || |
| deviceAttr.id == PAL_DEVICE_OUT_BLUETOOTH_BLE_BROADCAST) { |
| fbDevice.id = PAL_DEVICE_IN_BLUETOOTH_BLE; |
| } |
| dir = TX_HOSTLESS; |
| flags = PCM_IN; |
| } |
| |
| ret = PayloadBuilder::getBtDeviceKV(fbDevice.id, keyVector, codecFormat, |
| true, true); |
| if (ret) |
| PAL_ERR(LOG_TAG, "No KVs found for device id %d codec format:0x%x", |
| fbDevice.id, codecFormat); |
| |
| /* Configure Device Metadata */ |
| rm->getBackendName(fbDevice.id, backEndName); |
| ret = SessionAlsaUtils::setDeviceMetadata(rm, backEndName, keyVector); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "setDeviceMetadata for feedback device failed"); |
| goto done; |
| } |
| ret = SessionAlsaUtils::setDeviceMediaConfig(rm, backEndName, &fbDevice); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "setDeviceMediaConfig for feedback device failed"); |
| goto done; |
| } |
| |
| /* Retrieve Hostless PCM device id */ |
| sAttr.type = PAL_STREAM_LOW_LATENCY; |
| sAttr.direction = PAL_AUDIO_INPUT_OUTPUT; |
| fbpcmDevIds = rm->allocateFrontEndIds(sAttr, dir); |
| if (fbpcmDevIds.size() == 0) { |
| PAL_ERR(LOG_TAG, "allocateFrontEndIds failed"); |
| ret = -ENOSYS; |
| goto done; |
| } |
| ret = rm->getVirtualAudioMixer(&virtualMixerHandle); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "get mixer handle failed %d", ret); |
| goto free_fe; |
| } |
| ret = rm->getHwAudioMixer(&hwMixerHandle); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "get hw mixer handle failed %d", ret); |
| goto free_fe; |
| } |
| |
| connectCtrlName << "PCM" << fbpcmDevIds.at(0) << " connect"; |
| connectCtrl = mixer_get_ctl_by_name(virtualMixerHandle, connectCtrlName.str().data()); |
| if (!connectCtrl) { |
| PAL_ERR(LOG_TAG, "invalid mixer control: %s", connectCtrlName.str().data()); |
| goto free_fe; |
| } |
| |
| ret = mixer_ctl_set_enum_by_string(connectCtrl, backEndName.c_str()); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "Mixer control %s set with %s failed: %d", |
| connectCtrlName.str().data(), backEndName.c_str(), ret); |
| goto free_fe; |
| } |
| |
| // Notify ABR usecase information to BT driver to distinguish |
| // between SCO and feedback usecase |
| btSetFeedbackChannelCtrl = mixer_get_ctl_by_name(hwMixerHandle, |
| MIXER_SET_FEEDBACK_CHANNEL); |
| if (!btSetFeedbackChannelCtrl) { |
| PAL_ERR(LOG_TAG, "ERROR %s mixer control not identified", |
| MIXER_SET_FEEDBACK_CHANNEL); |
| goto disconnect_fe; |
| } |
| |
| if (mixer_ctl_set_value(btSetFeedbackChannelCtrl, 0, 1) != 0) { |
| PAL_ERR(LOG_TAG, "Failed to set BT usecase"); |
| goto disconnect_fe; |
| } |
| |
| if (codecFormat == CODEC_TYPE_APTX_AD_SPEECH) { |
| builder = new PayloadBuilder(); |
| |
| fbDev = std::dynamic_pointer_cast<BtSco>(BtSco::getInstance(&fbDevice, rm)); |
| if (!fbDev) { |
| PAL_ERR(LOG_TAG, "failed to get BtSco singleton object."); |
| goto disconnect_fe; |
| } |
| |
| fbDev->lockDeviceMutex(); |
| isDeviceLocked = true; |
| |
| if (fbDev->isConfigured == true) { |
| PAL_INFO(LOG_TAG, "feedback path is already configured"); |
| goto start_pcm; |
| } |
| |
| codecTagId = (codecType == DEC ? BT_PLACEHOLDER_ENCODER : BT_PLACEHOLDER_DECODER); |
| ret = SessionAlsaUtils::getModuleInstanceId(virtualMixerHandle, |
| fbpcmDevIds.at(0), backEndName.c_str(), codecTagId, &miid); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "getMiid for feedback device failed"); |
| goto disconnect_fe; |
| } |
| |
| ret = getPluginPayload(&pluginLibHandle, &codec, &out_buf, (codecType == DEC ? ENC : DEC)); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "getPluginPayload failed"); |
| goto disconnect_fe; |
| } |
| |
| /* SWB Encoder/Decoder has only 1 param, read block 0 */ |
| if (out_buf->num_blks != 1) { |
| PAL_ERR(LOG_TAG, "incorrect block size %d", out_buf->num_blks); |
| goto disconnect_fe; |
| } |
| fbDev->codecConfig.sample_rate = out_buf->sample_rate; |
| fbDev->codecConfig.bit_width = out_buf->bit_format; |
| fbDev->codecConfig.ch_info.channels = out_buf->channel_count; |
| fbDev->isAbrEnabled = out_buf->is_abr_enabled; |
| |
| blk = out_buf->blocks[0]; |
| builder->payloadCustomParam(¶mData, ¶mSize, |
| (uint32_t *)blk->payload, blk->payload_sz, miid, blk->param_id); |
| |
| codec->close_plugin(codec); |
| dlclose(pluginLibHandle); |
| |
| if (!paramData) { |
| PAL_ERR(LOG_TAG, "Failed to populateAPMHeader"); |
| ret = -ENOMEM; |
| goto disconnect_fe; |
| } |
| |
| ret = SessionAlsaUtils::setDeviceCustomPayload(rm, backEndName, |
| paramData, paramSize); |
| free(paramData); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "Error: Dev setParam failed for %d", fbDevice.id); |
| goto disconnect_fe; |
| } |
| } else if ((codecFormat == CODEC_TYPE_LC3) || |
| (codecFormat == CODEC_TYPE_APTX_AD_QLEA)) { |
| builder = new PayloadBuilder(); |
| |
| if ((fbDevice.id == PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET) || |
| (fbDevice.id == PAL_DEVICE_OUT_BLUETOOTH_SCO)) { |
| fbDev = std::dynamic_pointer_cast<BtSco>(BtSco::getInstance(&fbDevice, rm)); |
| if (!fbDev) { |
| PAL_ERR(LOG_TAG, "failed to get BtSco singleton object."); |
| goto disconnect_fe; |
| } |
| } else if ((fbDevice.id == PAL_DEVICE_IN_BLUETOOTH_A2DP) || |
| (fbDevice.id == PAL_DEVICE_OUT_BLUETOOTH_BLE) || |
| (fbDevice.id == PAL_DEVICE_IN_BLUETOOTH_BLE)) { |
| fbDev = std::dynamic_pointer_cast<BtA2dp>(BtA2dp::getInstance(&fbDevice, rm)); |
| if (!fbDev) { |
| PAL_ERR(LOG_TAG, "failed to get BtA2dp singleton object."); |
| goto disconnect_fe; |
| } |
| } |
| |
| // don't configure any module explicitly within feedback path of a2dp capture usecase |
| if (!fbDev) { |
| PAL_DBG(LOG_TAG, "fbDev is null, skip configuring modules"); |
| goto start_pcm; |
| } |
| |
| fbDev->lockDeviceMutex(); |
| isDeviceLocked = true; |
| |
| if (fbDev->isConfigured == true) { |
| PAL_INFO(LOG_TAG, "feedback path is already configured"); |
| goto start_pcm; |
| } |
| |
| if ((fbDevice.id == PAL_DEVICE_IN_BLUETOOTH_A2DP) || |
| (fbDevice.id == PAL_DEVICE_IN_BLUETOOTH_BLE)) { |
| // set custom configuration for a2dp/ble feedback(tx) path. |
| /* configure COP v2 depacketizer */ |
| ret = SessionAlsaUtils::getModuleInstanceId(virtualMixerHandle, |
| fbpcmDevIds.at(0), backEndName.c_str(), COP_DEPACKETIZER_V2, &miid); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, ret = %d", |
| COP_DEPACKETIZER_V2, ret); |
| goto disconnect_fe; |
| } |
| |
| // intentionally configure depacketizer in the same manner as configuring packetizer |
| builder->payloadCopV2PackConfig(¶mData, ¶mSize, miid, codecInfo); |
| if (!paramData) { |
| PAL_ERR(LOG_TAG, "Invalid COPv2 module param size"); |
| ret = -EINVAL; |
| goto disconnect_fe; |
| } |
| |
| ret = SessionAlsaUtils::setDeviceCustomPayload(rm, backEndName, |
| paramData, paramSize); |
| delete [] paramData; |
| if (ret) { |
| PAL_ERR(LOG_TAG, "Error: Dev setParam failed for %d", fbDevice.id); |
| goto disconnect_fe; |
| } |
| } else if ((fbDevice.id == PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET) || |
| (fbDevice.id == PAL_DEVICE_OUT_BLUETOOTH_SCO) || |
| (fbDevice.id == PAL_DEVICE_OUT_BLUETOOTH_BLE)) { |
| // set custom configuration for sco/ble bidirectional feedback path. |
| /* LC3 Encoder & Decoder Module Configuration */ |
| codecTagId = (codecType == DEC ? BT_PLACEHOLDER_ENCODER : BT_PLACEHOLDER_DECODER); |
| ret = SessionAlsaUtils::getModuleInstanceId(virtualMixerHandle, |
| fbpcmDevIds.at(0), backEndName.c_str(), codecTagId, &miid); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "getMiid for feedback device failed"); |
| goto disconnect_fe; |
| } |
| |
| if (isEncDecConfigured) { |
| ret = getPluginPayload(&pluginLibHandle, &codec, &out_buf, |
| (codecType == DEC ? ENC : DEC)); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "getPluginPayload failed"); |
| goto disconnect_fe; |
| } |
| } else { |
| /* In case of BLE stereorecording/voice_call_decode_session, only decoder |
| * path configs are present so use the same config for RX feeedback path too |
| */ |
| bt_ble_codec = (audio_lc3_codec_cfg_t*)codecInfo; |
| |
| memcpy(&bt_ble_codec->enc_cfg.toAirConfig, &bt_ble_codec->dec_cfg.fromAirConfig, |
| sizeof(lc3_cfg_t)); |
| |
| ret = getPluginPayload(&pluginLibHandle, &codec, &out_buf, |
| (codecType == DEC ? ENC : DEC)); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "getPluginPayload failed"); |
| goto disconnect_fe; |
| } |
| } |
| |
| if (out_buf->num_blks != 1) { |
| PAL_ERR(LOG_TAG, "incorrect block size %d", out_buf->num_blks); |
| goto disconnect_fe; |
| } |
| fbDev->codecConfig.sample_rate = out_buf->sample_rate; |
| fbDev->codecConfig.bit_width = out_buf->bit_format; |
| fbDev->codecConfig.ch_info.channels = out_buf->channel_count; |
| fbDev->isAbrEnabled = out_buf->is_abr_enabled; |
| |
| blk = out_buf->blocks[0]; |
| builder->payloadCustomParam(¶mData, ¶mSize, |
| (uint32_t *)blk->payload, blk->payload_sz, miid, blk->param_id); |
| if (paramSize) { |
| fbDev->updateCustomPayload(paramData, paramSize); |
| free(paramData); |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| ret = -EINVAL; |
| PAL_ERR(LOG_TAG, "Failed to populateAPMHeader"); |
| goto disconnect_fe; |
| } |
| |
| codec->close_plugin(codec); |
| dlclose(pluginLibHandle); |
| |
| if (fbDevice.id == PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET) { |
| /* COP v2 DEPACKETIZER Module Configuration */ |
| ret = SessionAlsaUtils::getModuleInstanceId(virtualMixerHandle, |
| fbpcmDevIds.at(0), backEndName.c_str(), COP_DEPACKETIZER_V2, &miid); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, ret = %d", |
| COP_DEPACKETIZER_V2, ret); |
| goto disconnect_fe; |
| } |
| |
| builder->payloadCopV2DepackConfig(¶mData, ¶mSize, miid, codecInfo, false /* StreamMapOut */); |
| if (paramSize) { |
| fbDev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| ret = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid COPv2 module param size"); |
| goto disconnect_fe; |
| } |
| |
| builder->payloadCopV2DepackConfig(¶mData, ¶mSize, miid, codecInfo, true /* StreamMapIn */); |
| if (paramSize) { |
| fbDev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| ret = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid COPv2 module param size"); |
| goto disconnect_fe; |
| } |
| } else { /* fbDevice.id == PAL_DEVICE_OUT_BLUETOOTH_SCO || |
| * fbDevice.id == PAL_DEVICE_OUT_BLUETOOTH_BLE*/ |
| /* COP v2 PACKETIZER Module Configuration */ |
| ret = SessionAlsaUtils::getModuleInstanceId(virtualMixerHandle, |
| fbpcmDevIds.at(0), backEndName.c_str(), COP_PACKETIZER_V2, &miid); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, ret = %d", |
| COP_PACKETIZER_V2, ret); |
| goto disconnect_fe; |
| } |
| |
| // PARAM_ID_COP_V2_STREAM_INFO for COPv2 |
| builder->payloadCopV2PackConfig(¶mData, ¶mSize, miid, codecInfo); |
| if (paramSize) { |
| fbDev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| ret = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid COPv2 module param size"); |
| goto disconnect_fe; |
| } |
| |
| // PARAM_ID_COP_PACKETIZER_OUTPUT_MEDIA_FORMAT for COPv2 |
| builder->payloadCopPackConfig(¶mData, ¶mSize, miid, &fbDevice.config); |
| if (paramSize) { |
| fbDev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| ret = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid COP module param size"); |
| goto disconnect_fe; |
| } |
| } |
| |
| /* RAT Module Configuration */ |
| ret = SessionAlsaUtils::getModuleInstanceId(virtualMixerHandle, |
| fbpcmDevIds.at(0), backEndName.c_str(), RAT_RENDER, &miid); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, ret = %d", RAT_RENDER, ret); |
| goto disconnect_fe; |
| } else { |
| builder->payloadRATConfig(¶mData, ¶mSize, miid, &fbDev->codecConfig); |
| if (paramSize) { |
| fbDev->updateCustomPayload(paramData, paramSize); |
| free(paramData); |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| ret = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid RAT module param size"); |
| goto disconnect_fe; |
| } |
| } |
| |
| /* PCM CNV Module Configuration */ |
| ret = SessionAlsaUtils::getModuleInstanceId(virtualMixerHandle, |
| fbpcmDevIds.at(0), backEndName.c_str(), BT_PCM_CONVERTER, &miid); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "Failed to get tag info %x, ret = %d", BT_PCM_CONVERTER, ret); |
| goto disconnect_fe; |
| } else { |
| builder->payloadPcmCnvConfig(¶mData, ¶mSize, miid, &fbDev->codecConfig, |
| (codecType == DEC ? true : false) /* isRx */); |
| if (paramSize) { |
| fbDev->updateCustomPayload(paramData, paramSize); |
| delete [] paramData; |
| paramData = NULL; |
| paramSize = 0; |
| } else { |
| ret = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid PCM CNV module param size"); |
| goto disconnect_fe; |
| } |
| } |
| |
| if (fbDev->customPayloadSize) { |
| ret = SessionAlsaUtils::setDeviceCustomPayload(rm, backEndName, |
| fbDev->customPayload, fbDev->customPayloadSize); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "Error: Dev setParam failed for %d", fbDevice.id); |
| goto disconnect_fe; |
| } |
| |
| if (fbDev->customPayload) { |
| free(fbDev->customPayload); |
| fbDev->customPayload = NULL; |
| fbDev->customPayloadSize = 0; |
| } |
| } |
| } |
| } |
| |
| start_pcm: |
| config.rate = SAMPLINGRATE_8K; |
| config.format = PCM_FORMAT_S16_LE; |
| config.channels = CHANNELS_1; |
| config.period_size = 240; |
| config.period_count = 2; |
| config.start_threshold = 0; |
| config.stop_threshold = 0; |
| config.silence_threshold = 0; |
| fbPcm = pcm_open(rm->getVirtualSndCard(), fbpcmDevIds.at(0), flags, &config); |
| if (!fbPcm) { |
| PAL_ERR(LOG_TAG, "pcm open failed"); |
| goto disconnect_fe; |
| } |
| |
| if (!pcm_is_ready(fbPcm)) { |
| PAL_ERR(LOG_TAG, "pcm open not ready"); |
| goto err_pcm_open; |
| } |
| |
| ret = pcm_start(fbPcm); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "pcm_start rx failed %d", ret); |
| goto err_pcm_open; |
| } |
| |
| if (codecFormat == CODEC_TYPE_APTX_AD_SPEECH) { |
| fbDev->isConfigured = true; |
| fbDev->deviceStartStopCount++; |
| fbDev->deviceCount++; |
| PAL_DBG(LOG_TAG, " deviceCount %d deviceStartStopCount %d for device id %d", |
| fbDev->deviceCount, fbDev->deviceStartStopCount, fbDev->deviceAttr.id); |
| |
| } |
| if ((codecFormat == CODEC_TYPE_LC3) && |
| (fbDevice.id == PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET || |
| fbDevice.id == PAL_DEVICE_OUT_BLUETOOTH_SCO || |
| fbDevice.id == PAL_DEVICE_OUT_BLUETOOTH_BLE)) { |
| fbDev->isConfigured = true; |
| fbDev->deviceStartStopCount++; |
| fbDev->deviceCount++; |
| PAL_DBG(LOG_TAG, " deviceCount %d deviceStartStopCount %d for device id %d", |
| fbDev->deviceCount, fbDev->deviceStartStopCount, fbDev->deviceAttr.id); |
| } |
| |
| abrRefCnt++; |
| PAL_INFO(LOG_TAG, "Feedback Device started successfully"); |
| goto done; |
| err_pcm_open: |
| pcm_close(fbPcm); |
| fbPcm = NULL; |
| disconnect_fe: |
| disconnectCtrlName << "PCM" << fbpcmDevIds.at(0) << " disconnect"; |
| disconnectCtrl = mixer_get_ctl_by_name(virtualMixerHandle, disconnectCtrlName.str().data()); |
| if(disconnectCtrl != NULL){ |
| mixer_ctl_set_enum_by_string(disconnectCtrl, backEndName.c_str()); |
| } |
| free_fe: |
| rm->freeFrontEndIds(fbpcmDevIds, sAttr, dir); |
| fbpcmDevIds.clear(); |
| done: |
| if (isDeviceLocked) { |
| isDeviceLocked = false; |
| fbDev->unlockDeviceMutex(); |
| } |
| mAbrMutex.unlock(); |
| if (builder) { |
| delete builder; |
| builder = NULL; |
| } |
| return; |
| } |
| |
| void Bluetooth::stopAbr() |
| { |
| struct pal_stream_attributes sAttr; |
| struct mixer_ctl *btSetFeedbackChannelCtrl = NULL; |
| struct mixer *hwMixerHandle = NULL; |
| int dir, ret = 0; |
| bool isfbDeviceLocked = false; |
| |
| mAbrMutex.lock(); |
| if (!fbPcm) { |
| PAL_ERR(LOG_TAG, "fbPcm is null"); |
| mAbrMutex.unlock(); |
| return; |
| } |
| |
| if (abrRefCnt == 0) { |
| PAL_DBG(LOG_TAG, "skip as abrRefCnt is zero"); |
| mAbrMutex.unlock(); |
| return; |
| } |
| |
| if (--abrRefCnt > 0) { |
| PAL_DBG(LOG_TAG, "abrRefCnt is %d", abrRefCnt); |
| mAbrMutex.unlock(); |
| return; |
| } |
| |
| memset(&sAttr, 0, sizeof(sAttr)); |
| sAttr.type = PAL_STREAM_LOW_LATENCY; |
| sAttr.direction = PAL_AUDIO_INPUT_OUTPUT; |
| |
| if (fbDev) { |
| fbDev->lockDeviceMutex(); |
| isfbDeviceLocked = true; |
| } |
| pcm_stop(fbPcm); |
| pcm_close(fbPcm); |
| fbPcm = NULL; |
| |
| ret = rm->getHwAudioMixer(&hwMixerHandle); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "get mixer handle failed %d", ret); |
| goto free_fe; |
| } |
| // Reset BT driver mixer control for ABR usecase |
| btSetFeedbackChannelCtrl = mixer_get_ctl_by_name(hwMixerHandle, |
| MIXER_SET_FEEDBACK_CHANNEL); |
| if (!btSetFeedbackChannelCtrl) { |
| PAL_ERR(LOG_TAG, "%s mixer control not identified", |
| MIXER_SET_FEEDBACK_CHANNEL); |
| } else if (mixer_ctl_set_value(btSetFeedbackChannelCtrl, 0, 0) != 0) { |
| PAL_ERR(LOG_TAG, "Failed to reset BT usecase"); |
| } |
| |
| if ((codecFormat == CODEC_TYPE_APTX_AD_SPEECH) && fbDev) { |
| if ((fbDev->deviceStartStopCount > 0) && |
| (--fbDev->deviceStartStopCount == 0)) { |
| fbDev->isConfigured = false; |
| fbDev->isAbrEnabled = false; |
| } |
| if (fbDev->deviceCount > 0) |
| fbDev->deviceCount--; |
| PAL_DBG(LOG_TAG, " deviceCount %d deviceStartStopCount %d for device id %d", |
| fbDev->deviceCount, fbDev->deviceStartStopCount, fbDev->deviceAttr.id); |
| } |
| if ((codecFormat == CODEC_TYPE_LC3) && |
| (deviceAttr.id == PAL_DEVICE_OUT_BLUETOOTH_SCO || |
| deviceAttr.id == PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET || |
| deviceAttr.id == PAL_DEVICE_IN_BLUETOOTH_BLE) && |
| fbDev) { |
| if ((fbDev->deviceStartStopCount > 0) && |
| (--fbDev->deviceStartStopCount == 0)) { |
| fbDev->isConfigured = false; |
| fbDev->isAbrEnabled = false; |
| } |
| if (fbDev->deviceCount > 0) |
| fbDev->deviceCount--; |
| PAL_DBG(LOG_TAG, " deviceCount %d deviceStartStopCount %d for device id %d", |
| fbDev->deviceCount, fbDev->deviceStartStopCount, fbDev->deviceAttr.id); |
| } |
| |
| free_fe: |
| dir = ((codecType == DEC) ? RX_HOSTLESS : TX_HOSTLESS); |
| if (fbpcmDevIds.size()) { |
| rm->freeFrontEndIds(fbpcmDevIds, sAttr, dir); |
| fbpcmDevIds.clear(); |
| } |
| |
| /* Check for deviceStartStopCount, to avoid false reset of isAbrEnabled flag in |
| * case of BLE playback path stops during ongoing capture session |
| */ |
| if (deviceStartStopCount == 1) { |
| isAbrEnabled = false; |
| } |
| |
| if (isfbDeviceLocked) { |
| isfbDeviceLocked = false; |
| fbDev->unlockDeviceMutex(); |
| } |
| mAbrMutex.unlock(); |
| } |
| |
| int32_t Bluetooth::configureSlimbusClockSrc(void) |
| { |
| return configureDeviceClockSrc(BT_SLIMBUS_CLK_STR, |
| rm->getBtSlimClockSrc(codecFormat)); |
| } |
| |
| |
| /* Scope of BtA2dp class */ |
| // definition of static BtA2dp member variables |
| std::shared_ptr<Device> BtA2dp::objRx = nullptr; |
| std::shared_ptr<Device> BtA2dp::objTx = nullptr; |
| std::shared_ptr<Device> BtA2dp::objBleRx = nullptr; |
| std::shared_ptr<Device> BtA2dp::objBleTx = nullptr; |
| std::shared_ptr<Device> BtA2dp::objBleBroadcastRx = nullptr; |
| void *BtA2dp::bt_lib_source_handle = nullptr; |
| void *BtA2dp::bt_lib_sink_handle = nullptr; |
| bt_audio_pre_init_t BtA2dp::bt_audio_pre_init = nullptr; |
| audio_source_open_t BtA2dp::audio_source_open = nullptr; |
| audio_source_close_t BtA2dp::audio_source_close = nullptr; |
| audio_source_start_t BtA2dp::audio_source_start = nullptr; |
| audio_source_stop_t BtA2dp::audio_source_stop = nullptr; |
| audio_source_suspend_t BtA2dp::audio_source_suspend = nullptr; |
| audio_source_handoff_triggered_t BtA2dp::audio_source_handoff_triggered = nullptr; |
| clear_source_a2dpsuspend_flag_t BtA2dp::clear_source_a2dpsuspend_flag = nullptr; |
| audio_get_enc_config_t BtA2dp::audio_get_enc_config = nullptr; |
| audio_source_check_a2dp_ready_t BtA2dp::audio_source_check_a2dp_ready = nullptr; |
| audio_is_tws_mono_mode_enable_t BtA2dp::audio_is_tws_mono_mode_enable = nullptr; |
| audio_sink_get_a2dp_latency_t BtA2dp::audio_sink_get_a2dp_latency = nullptr; |
| audio_sink_start_t BtA2dp::audio_sink_start = nullptr; |
| audio_sink_stop_t BtA2dp::audio_sink_stop = nullptr; |
| audio_get_dec_config_t BtA2dp::audio_get_dec_config = nullptr; |
| audio_sink_session_setup_complete_t BtA2dp::audio_sink_session_setup_complete = nullptr; |
| audio_sink_check_a2dp_ready_t BtA2dp::audio_sink_check_a2dp_ready = nullptr; |
| audio_is_scrambling_enabled_t BtA2dp::audio_is_scrambling_enabled = nullptr; |
| audio_sink_suspend_t BtA2dp::audio_sink_suspend = nullptr; |
| audio_sink_open_t BtA2dp::audio_sink_open = nullptr; |
| audio_sink_close_t BtA2dp::audio_sink_close = nullptr; |
| |
| btoffload_update_metadata_api_t BtA2dp::btoffload_update_metadata_api = nullptr; |
| audio_source_open_api_t BtA2dp::audio_source_open_api = nullptr; |
| audio_source_close_api_t BtA2dp::audio_source_close_api = nullptr; |
| audio_source_start_api_t BtA2dp::audio_source_start_api = nullptr; |
| audio_source_stop_api_t BtA2dp::audio_source_stop_api = nullptr; |
| audio_source_suspend_api_t BtA2dp::audio_source_suspend_api = nullptr; |
| audio_get_enc_config_api_t BtA2dp::audio_get_enc_config_api = nullptr; |
| audio_source_check_a2dp_ready_api_t BtA2dp::audio_source_check_a2dp_ready_api = nullptr; |
| audio_sink_get_a2dp_latency_api_t BtA2dp::audio_sink_get_a2dp_latency_api = nullptr; |
| audio_sink_start_api_t BtA2dp::audio_sink_start_api = nullptr; |
| audio_sink_stop_api_t BtA2dp::audio_sink_stop_api = nullptr; |
| audio_sink_suspend_api_t BtA2dp::audio_sink_suspend_api = nullptr; |
| audio_sink_open_api_t BtA2dp::audio_sink_open_api = nullptr; |
| audio_sink_close_api_t BtA2dp::audio_sink_close_api = nullptr; |
| |
| BtA2dp::BtA2dp(struct pal_device *device, std::shared_ptr<ResourceManager> Rm) |
| : Bluetooth(device, Rm), |
| a2dpState(A2DP_STATE_DISCONNECTED) |
| { |
| a2dpRole = ((device->id == PAL_DEVICE_IN_BLUETOOTH_A2DP) || (device->id == PAL_DEVICE_IN_BLUETOOTH_BLE)) ? SINK : SOURCE; |
| codecType = ((device->id == PAL_DEVICE_IN_BLUETOOTH_A2DP) || (device->id == PAL_DEVICE_IN_BLUETOOTH_BLE)) ? DEC : ENC; |
| pluginHandler = NULL; |
| pluginCodec = NULL; |
| |
| param_bt_a2dp.reconfig = false; |
| param_bt_a2dp.a2dp_suspended = false; |
| param_bt_a2dp.a2dp_capture_suspended = false; |
| param_bt_a2dp.is_force_switch = false; |
| isA2dpOffloadSupported = |
| property_get_bool("ro.bluetooth.a2dp_offload.supported", false) && |
| !property_get_bool("persist.bluetooth.a2dp_offload.disabled", false); |
| |
| PAL_DBG(LOG_TAG, "A2DP offload supported = %d", |
| isA2dpOffloadSupported); |
| param_bt_a2dp.reconfig_supported = isA2dpOffloadSupported; |
| param_bt_a2dp.latency = 0; |
| |
| if (isA2dpOffloadSupported) { |
| init(); |
| } |
| } |
| |
| BtA2dp::~BtA2dp() |
| { |
| } |
| |
| tSESSION_TYPE BtA2dp::get_session_type() |
| { |
| tSESSION_TYPE session_type = A2DP_HARDWARE_OFFLOAD_DATAPATH; |
| if (a2dpRole == SOURCE) { |
| if (deviceAttr.id == PAL_DEVICE_OUT_BLUETOOTH_BLE) { |
| session_type = LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH; |
| } else if (deviceAttr.id == PAL_DEVICE_OUT_BLUETOOTH_BLE_BROADCAST) { |
| session_type = LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH; |
| } else { |
| session_type = A2DP_HARDWARE_OFFLOAD_DATAPATH; |
| } |
| } |
| else { |
| session_type = LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH; |
| } |
| |
| return session_type; |
| } |
| |
| void BtA2dp::open_a2dp_source() |
| { |
| int ret = 0; |
| |
| PAL_DBG(LOG_TAG, "Open A2DP source start"); |
| if (bt_lib_source_handle && (audio_source_open_api || |
| audio_source_open)) { |
| if (a2dpState == A2DP_STATE_DISCONNECTED) { |
| PAL_DBG(LOG_TAG, "calling BT stream open"); |
| /*To support backward compatibility check for BT IPC API's |
| * with session_type or w/o session_type*/ |
| if (audio_source_open_api) { |
| ret = audio_source_open_api(get_session_type()); |
| if (ret != 0) { |
| PAL_ERR(LOG_TAG, "Failed to open source stream for a2dp: status %d", ret); |
| return; |
| } |
| } else { |
| ret = audio_source_open(); |
| if (ret != 0) { |
| PAL_ERR(LOG_TAG, "Failed to open source stream for a2dp: status %d", ret); |
| return; |
| } |
| } |
| a2dpState = A2DP_STATE_CONNECTED; |
| } else { |
| PAL_DBG(LOG_TAG, "Called a2dp open with improper state %d", a2dpState); |
| } |
| } |
| } |
| |
| int BtA2dp::close_audio_source() |
| { |
| PAL_VERBOSE(LOG_TAG, "Enter"); |
| |
| if (!(bt_lib_source_handle && (audio_source_close_api || |
| audio_source_close))) { |
| PAL_ERR(LOG_TAG, "a2dp source handle is not identified, Ignoring close request"); |
| return -ENOSYS; |
| } |
| |
| if (a2dpState != A2DP_STATE_DISCONNECTED) { |
| PAL_DBG(LOG_TAG, "calling BT source stream close"); |
| mDeviceMutex.lock(); |
| if (audio_source_close_api) { |
| if (audio_source_close_api(get_session_type()) == false) |
| PAL_ERR(LOG_TAG, "failed close a2dp source control path from BT library"); |
| } else { |
| if (audio_source_close() == false) |
| PAL_ERR(LOG_TAG, "failed close a2dp source control path from BT library"); |
| } |
| mDeviceMutex.unlock(); |
| } |
| totalActiveSessionRequests = 0; |
| param_bt_a2dp.a2dp_suspended = false; |
| param_bt_a2dp.reconfig = false; |
| param_bt_a2dp.latency = 0; |
| a2dpState = A2DP_STATE_DISCONNECTED; |
| isConfigured = false; |
| |
| return 0; |
| } |
| |
| void BtA2dp::init_a2dp_source() |
| { |
| PAL_DBG(LOG_TAG, "init_a2dp_source START"); |
| if (bt_lib_source_handle == nullptr) { |
| PAL_DBG(LOG_TAG, "Requesting for BT lib handle"); |
| bt_lib_source_handle = dlopen(BT_IPC_SOURCE_LIB, RTLD_NOW); |
| if (bt_lib_source_handle == nullptr) { |
| PAL_ERR(LOG_TAG, "dlopen failed for %s", BT_IPC_SOURCE_LIB); |
| return; |
| } |
| } |
| bt_audio_pre_init = (bt_audio_pre_init_t) |
| dlsym(bt_lib_source_handle, "bt_audio_pre_init"); |
| audio_source_open_api = (audio_source_open_api_t) |
| dlsym(bt_lib_source_handle, "audio_stream_open_api"); |
| audio_source_start_api = (audio_source_start_api_t) |
| dlsym(bt_lib_source_handle, "audio_start_stream_api"); |
| audio_get_enc_config_api = (audio_get_enc_config_api_t) |
| dlsym(bt_lib_source_handle, "audio_get_codec_config_api"); |
| audio_source_suspend_api = (audio_source_suspend_api_t) |
| dlsym(bt_lib_source_handle, "audio_suspend_stream_api"); |
| audio_source_handoff_triggered = (audio_source_handoff_triggered_t) |
| dlsym(bt_lib_source_handle, "audio_handoff_triggered"); |
| clear_source_a2dpsuspend_flag = (clear_source_a2dpsuspend_flag_t) |
| dlsym(bt_lib_source_handle, "clear_a2dpsuspend_flag"); |
| audio_source_stop_api = (audio_source_stop_api_t) |
| dlsym(bt_lib_source_handle, "audio_stop_stream_api"); |
| audio_source_close_api = (audio_source_close_api_t) |
| dlsym(bt_lib_source_handle, "audio_stream_close_api"); |
| audio_source_check_a2dp_ready_api = (audio_source_check_a2dp_ready_api_t) |
| dlsym(bt_lib_source_handle, "audio_check_a2dp_ready_api"); |
| audio_sink_get_a2dp_latency_api = (audio_sink_get_a2dp_latency_api_t) |
| dlsym(bt_lib_source_handle, "audio_sink_get_a2dp_latency_api"); |
| audio_is_tws_mono_mode_enable = (audio_is_tws_mono_mode_enable_t) |
| dlsym(bt_lib_source_handle, "isTwsMonomodeEnable"); |
| audio_is_scrambling_enabled = (audio_is_scrambling_enabled_t) |
| dlsym(bt_lib_source_handle, "audio_is_scrambling_enabled"); |
| btoffload_update_metadata_api = (btoffload_update_metadata_api_t) |
| dlsym(bt_lib_source_handle, "update_metadata"); |
| |
| audio_source_open = (audio_source_open_t) |
| dlsym(bt_lib_source_handle, "audio_stream_open"); |
| audio_source_start = (audio_source_start_t) |
| dlsym(bt_lib_source_handle, "audio_start_stream"); |
| audio_get_enc_config = (audio_get_enc_config_t) |
| dlsym(bt_lib_source_handle, "audio_get_codec_config"); |
| audio_source_suspend = (audio_source_suspend_t) |
| dlsym(bt_lib_source_handle, "audio_suspend_stream"); |
| |
| audio_source_stop = (audio_source_stop_t) |
| dlsym(bt_lib_source_handle, "audio_stop_stream"); |
| audio_source_close = (audio_source_close_t) |
| dlsym(bt_lib_source_handle, "audio_stream_close"); |
| audio_source_check_a2dp_ready = (audio_source_check_a2dp_ready_t) |
| dlsym(bt_lib_source_handle, "audio_check_a2dp_ready"); |
| audio_sink_get_a2dp_latency = (audio_sink_get_a2dp_latency_t) |
| dlsym(bt_lib_source_handle, "audio_sink_get_a2dp_latency"); |
| |
| |
| if (bt_lib_source_handle && bt_audio_pre_init) { |
| PAL_DBG(LOG_TAG, "calling BT module preinit"); |
| bt_audio_pre_init(); |
| } |
| usleep(20 * 1000); //TODO: to add interval properly |
| open_a2dp_source(); |
| } |
| |
| void BtA2dp::init_a2dp_sink() |
| { |
| PAL_DBG(LOG_TAG, "Open A2DP input start"); |
| if (bt_lib_sink_handle == nullptr) { |
| PAL_DBG(LOG_TAG, "Requesting for BT lib handle"); |
| bt_lib_sink_handle = dlopen(BT_IPC_SINK_LIB, RTLD_NOW); |
| |
| if (bt_lib_sink_handle == nullptr) { |
| #ifndef LINUX_ENABLED |
| // On Mobile LE VoiceBackChannel implemented as A2DPSink Profile. |
| // However - All the BT-Host IPC calls are exposed via Source LIB itself. |
| PAL_DBG(LOG_TAG, "Requesting for BT lib source handle"); |
| bt_lib_sink_handle = dlopen(BT_IPC_SOURCE_LIB, RTLD_NOW); |
| if (bt_lib_sink_handle == nullptr) { |
| PAL_ERR(LOG_TAG, "DLOPEN failed"); |
| return; |
| } |
| isDummySink = true; |
| audio_get_enc_config_api = (audio_get_enc_config_api_t) |
| dlsym(bt_lib_sink_handle, "audio_get_codec_config_api"); |
| audio_sink_get_a2dp_latency_api = (audio_sink_get_a2dp_latency_api_t) |
| dlsym(bt_lib_sink_handle, "audio_sink_get_a2dp_latency_api"); |
| audio_sink_start_api = (audio_sink_start_api_t) |
| dlsym(bt_lib_sink_handle, "audio_start_stream_api"); |
| audio_sink_stop_api = (audio_sink_stop_api_t) |
| dlsym(bt_lib_sink_handle, "audio_stop_stream_api"); |
| audio_source_check_a2dp_ready_api = (audio_source_check_a2dp_ready_api_t) |
| dlsym(bt_lib_sink_handle, "audio_check_a2dp_ready_api"); |
| audio_sink_suspend_api = (audio_sink_suspend_api_t) |
| dlsym(bt_lib_sink_handle, "audio_suspend_stream_api"); |
| audio_sink_open_api = (audio_sink_open_api_t) |
| dlsym(bt_lib_sink_handle, "audio_stream_open_api"); |
| audio_sink_close_api = (audio_sink_close_api_t) |
| dlsym(bt_lib_sink_handle, "audio_stream_close_api"); |
| btoffload_update_metadata_api = (btoffload_update_metadata_api_t) |
| dlsym(bt_lib_sink_handle, "update_metadata"); |
| |
| audio_get_enc_config = (audio_get_enc_config_t) |
| dlsym(bt_lib_sink_handle, "audio_get_codec_config"); |
| audio_sink_get_a2dp_latency = (audio_sink_get_a2dp_latency_t) |
| dlsym(bt_lib_sink_handle, "audio_sink_get_a2dp_latency"); |
| audio_sink_start = (audio_sink_start_t) |
| dlsym(bt_lib_sink_handle, "audio_start_stream"); |
| audio_sink_stop = (audio_sink_stop_t) |
| dlsym(bt_lib_sink_handle, "audio_stop_stream"); |
| audio_source_check_a2dp_ready = (audio_source_check_a2dp_ready_t) |
| dlsym(bt_lib_sink_handle, "audio_check_a2dp_ready"); |
| audio_sink_suspend = (audio_sink_suspend_t) |
| dlsym(bt_lib_sink_handle, "audio_suspend_stream"); |
| audio_sink_open = (audio_sink_open_t) |
| dlsym(bt_lib_sink_handle, "audio_stream_open"); |
| audio_sink_close = (audio_sink_close_t) |
| dlsym(bt_lib_sink_handle, "audio_stream_close"); |
| |
| #ifdef A2DP_SINK_SUPPORTED |
| |
| open_a2dp_sink(); |
| #endif |
| |
| #else |
| // On Linux Builds - A2DP Sink Profile is supported via different lib |
| PAL_ERR(LOG_TAG, "DLOPEN failed for %s", BT_IPC_SINK_LIB); |
| #endif |
| } else { |
| audio_sink_start = (audio_sink_start_t) |
| dlsym(bt_lib_sink_handle, "audio_sink_start_capture"); |
| audio_get_dec_config = (audio_get_dec_config_t) |
| dlsym(bt_lib_sink_handle, "audio_get_decoder_config"); |
| audio_sink_stop = (audio_sink_stop_t) |
| dlsym(bt_lib_sink_handle, "audio_sink_stop_capture"); |
| audio_sink_check_a2dp_ready = (audio_sink_check_a2dp_ready_t) |
| dlsym(bt_lib_sink_handle, "audio_sink_check_a2dp_ready"); |
| audio_sink_session_setup_complete = (audio_sink_session_setup_complete_t) |
| dlsym(bt_lib_sink_handle, "audio_sink_session_setup_complete"); |
| } |
| } |
| } |
| |
| void BtA2dp::open_a2dp_sink() |
| { |
| int ret = 0; |
| |
| PAL_DBG(LOG_TAG, "Open A2DP sink start"); |
| if (bt_lib_sink_handle && (audio_sink_open_api || |
| audio_sink_open)) { |
| if (a2dpState == A2DP_STATE_DISCONNECTED) { |
| PAL_DBG(LOG_TAG, "calling BT stream open"); |
| if (audio_sink_open_api) { |
| ret = audio_sink_open_api(get_session_type()); |
| if (ret != 0) { |
| PAL_ERR(LOG_TAG, "Failed to open sink stream for a2dp: status %d", ret); |
| } |
| } else { |
| ret = audio_sink_open(); |
| if (ret != 0) { |
| PAL_ERR(LOG_TAG, "Failed to open sink stream for a2dp: status %d", ret); |
| } |
| } |
| a2dpState = A2DP_STATE_CONNECTED; |
| } |
| else { |
| PAL_DBG(LOG_TAG, "Called a2dp open with improper state %d", a2dpState); |
| } |
| } |
| } |
| |
| int BtA2dp::close_audio_sink() |
| { |
| PAL_VERBOSE(LOG_TAG, "Enter"); |
| |
| if (!(bt_lib_sink_handle && (audio_sink_close_api || |
| audio_sink_close))) { |
| PAL_ERR(LOG_TAG, "a2dp sink handle is not identified, Ignoring close request"); |
| return -ENOSYS; |
| } |
| |
| if (a2dpState != A2DP_STATE_DISCONNECTED) { |
| PAL_DBG(LOG_TAG, "calling BT sink stream close"); |
| mDeviceMutex.lock(); |
| if (audio_sink_close_api) { |
| if (audio_sink_close_api(get_session_type()) == false) |
| PAL_ERR(LOG_TAG, "failed close a2dp sink control path from BT library"); |
| } else { |
| if (audio_sink_close() == false) |
| PAL_ERR(LOG_TAG, "failed close a2dp sink control path from BT library"); |
| } |
| mDeviceMutex.unlock(); |
| } |
| totalActiveSessionRequests = 0; |
| param_bt_a2dp.a2dp_suspended = false; |
| param_bt_a2dp.a2dp_capture_suspended = false; |
| param_bt_a2dp.reconfig = false; |
| param_bt_a2dp.latency = 0; |
| a2dpState = A2DP_STATE_DISCONNECTED; |
| isConfigured = false; |
| |
| return 0; |
| } |
| |
| bool BtA2dp::a2dp_send_sink_setup_complete() |
| { |
| uint64_t system_latency = 0; |
| bool is_complete = false; |
| |
| /* TODO : Replace this with call to plugin */ |
| system_latency = 200; |
| |
| if (audio_sink_session_setup_complete(system_latency) == 0) { |
| is_complete = true; |
| } |
| return is_complete; |
| } |
| |
| void BtA2dp::init() |
| { |
| (a2dpRole == SOURCE) ? init_a2dp_source() : init_a2dp_sink(); |
| } |
| |
| int BtA2dp::start() |
| { |
| int status = 0; |
| mDeviceMutex.lock(); |
| |
| status = (a2dpRole == SOURCE) ? startPlayback() : startCapture(); |
| if (status) { |
| goto exit; |
| } |
| |
| if (totalActiveSessionRequests == 1) { |
| status = configureSlimbusClockSrc(); |
| if (status) { |
| goto exit; |
| } |
| } |
| |
| status = Device::start_l(); |
| |
| if (customPayload) { |
| free(customPayload); |
| customPayload = NULL; |
| customPayloadSize = 0; |
| } |
| |
| if (!status && isAbrEnabled) |
| startAbr(); |
| exit: |
| mDeviceMutex.unlock(); |
| return status; |
| } |
| |
| int BtA2dp::stop() |
| { |
| int status = 0; |
| |
| mDeviceMutex.lock(); |
| if (isAbrEnabled) |
| stopAbr(); |
| |
| Device::stop_l(); |
| |
| status = (a2dpRole == SOURCE) ? stopPlayback() : stopCapture(); |
| mDeviceMutex.unlock(); |
| |
| return status; |
| } |
| |
| int BtA2dp::startPlayback() |
| { |
| int ret = 0; |
| uint32_t slatency = 0; |
| uint8_t multi_cast = 0, num_dev = 1; |
| |
| PAL_DBG(LOG_TAG, "a2dp_start_playback start"); |
| |
| if (!(bt_lib_source_handle && (audio_source_start_api || |
| audio_source_start) && (audio_get_enc_config_api || |
| audio_get_enc_config))) { |
| PAL_ERR(LOG_TAG, "a2dp handle is not identified, Ignoring start playback request"); |
| return -ENOSYS; |
| } |
| |
| if (param_bt_a2dp.a2dp_suspended) { |
| // session will be restarted after suspend completion |
| PAL_INFO(LOG_TAG, "a2dp start requested during suspend state"); |
| return -ENOSYS; |
| } |
| |
| if (a2dpState != A2DP_STATE_STARTED && !totalActiveSessionRequests) { |
| codecFormat = CODEC_TYPE_INVALID; |
| |
| if (!isConfigured) |
| isAbrEnabled = false; |
| |
| PAL_DBG(LOG_TAG, "calling BT module stream start"); |
| /* This call indicates BT IPC lib to start playback */ |
| if (audio_source_start_api) { |
| ret = audio_source_start_api(get_session_type()); |
| } else { |
| ret = audio_source_start(); |
| } |
| if (ret != 0) { |
| PAL_ERR(LOG_TAG, "BT controller start failed"); |
| return ret; |
| } |
| PAL_ERR(LOG_TAG, "BT controller start return = %d", ret); |
| |
| PAL_DBG(LOG_TAG, "configure_a2dp_encoder_format start"); |
| if (audio_get_enc_config_api) { |
| codecInfo = audio_get_enc_config_api(get_session_type(), &multi_cast, &num_dev, (audio_format_t*)&codecFormat); |
| } |
| else { |
| codecInfo = audio_get_enc_config(&multi_cast, &num_dev, (audio_format_t*)&codecFormat); |
| } |
| |
| if (codecInfo == NULL || codecFormat == CODEC_TYPE_INVALID) { |
| PAL_ERR(LOG_TAG, "invalid encoder config"); |
| if (audio_source_stop_api) { |
| audio_source_stop_api(get_session_type()); |
| } else { |
| audio_source_stop(); |
| } |
| return -EINVAL; |
| } |
| |
| if (codecFormat == CODEC_TYPE_APTX_DUAL_MONO && audio_is_tws_mono_mode_enable) |
| isTwsMonoModeOn = audio_is_tws_mono_mode_enable(); |
| |
| if (audio_is_scrambling_enabled) |
| isScramblingEnabled = audio_is_scrambling_enabled(); |
| PAL_INFO(LOG_TAG, "isScramblingEnabled = %d", isScramblingEnabled); |
| |
| /* Update Device GKV based on Encoder type */ |
| updateDeviceMetadata(); |
| if (!isConfigured) { |
| ret = configureA2dpEncoderDecoder(); |
| if (ret) { |
| PAL_ERR(LOG_TAG, "unable to configure DSP encoder"); |
| if (audio_source_stop_api) { |
| audio_source_stop_api(get_session_type()); |
| } else { |
| audio_source_stop(); |
| } |
| return ret; |
| } |
| } |
| |
| /* Query and cache the a2dp latency */ |
| if (audio_sink_get_a2dp_latency_api && (a2dpState != A2DP_STATE_DISCONNECTED)) { |
| slatency = audio_sink_get_a2dp_latency_api(get_session_type()); |
| } else if (audio_sink_get_a2dp_latency && (a2dpState != A2DP_STATE_DISCONNECTED)) { |
| slatency = audio_sink_get_a2dp_latency(); |
| } |
| if (pluginCodec) { |
| param_bt_a2dp.latency = |
| pluginCodec->plugin_get_codec_latency(pluginCodec, slatency); |
| } else { |
| param_bt_a2dp.latency = 0; |
| } |
| |
| a2dpState = A2DP_STATE_STARTED; |
| } else { |
| /* Update Device GKV based on Already received encoder. */ |
| /* This is required for getting tagged module info in session class. */ |
| updateDeviceMetadata(); |
| } |
| |
| totalActiveSessionRequests++; |
| PAL_DBG(LOG_TAG, "start A2DP playback total active sessions :%d", |
| totalActiveSessionRequests); |
| return ret; |
| } |
| |
| int BtA2dp::stopPlayback() |
| { |
| int ret =0; |
| |
| PAL_VERBOSE(LOG_TAG, "a2dp_stop_playback start"); |
| if (!(bt_lib_source_handle && (audio_source_stop_api || |
| audio_source_stop))) { |
| PAL_ERR(LOG_TAG, "a2dp handle is not identified, Ignoring stop request"); |
| return -ENOSYS; |
| } |
| |
| if (totalActiveSessionRequests > 0) |
| totalActiveSessionRequests--; |
| else |
| PAL_ERR(LOG_TAG, "No active playback session requests on A2DP"); |
| |
| if (a2dpState == A2DP_STATE_STARTED && !totalActiveSessionRequests) { |
| PAL_VERBOSE(LOG_TAG, "calling BT module stream stop"); |
| if (audio_source_stop_api) { |
| ret = audio_source_stop_api(get_session_type()); |
| } else { |
| ret = audio_source_stop(); |
| } |
| |
| if (ret < 0) { |
| PAL_ERR(LOG_TAG, "stop stream to BT IPC lib failed"); |
| } else { |
| PAL_VERBOSE(LOG_TAG, "stop steam to BT IPC lib successful"); |
| } |
| |
| if (deviceStartStopCount == 0) { |
| isConfigured = false; |
| } |
| a2dpState = A2DP_STATE_STOPPED; |
| codecInfo = NULL; |
| |
| /* Reset isTwsMonoModeOn and isLC3MonoModeOn during stop */ |
| if (!param_bt_a2dp.a2dp_suspended) { |
| isTwsMonoModeOn = false; |
| isLC3MonoModeOn = false; |
| isScramblingEnabled = false; |
| } |
| |
| if (pluginCodec) { |
| pluginCodec->close_plugin(pluginCodec); |
| pluginCodec = NULL; |
| } |
| if (pluginHandler) { |
| dlclose(pluginHandler); |
| pluginHandler = NULL; |
| } |
| } |
| |
| PAL_DBG(LOG_TAG, "Stop A2DP playback, total active sessions :%d", |
| totalActiveSessionRequests); |
| return 0; |
| } |
| |
| bool BtA2dp::isDeviceReady() |
| { |
| bool ret = false; |
| |
| if (a2dpRole == SOURCE) { |
| if (param_bt_a2dp.a2dp_suspended) |
| return ret; |
| } else if (a2dpRole == SINK) { |
| if (param_bt_a2dp.a2dp_capture_suspended) |
| return ret; |
| } |
| |
| if ((a2dpState != A2DP_STATE_DISCONNECTED) && |
| (isA2dpOffloadSupported)) { |
| if ((a2dpRole == SOURCE) || isDummySink) { |
| if (audio_source_check_a2dp_ready_api) { |
| ret = audio_source_check_a2dp_ready_api(get_session_type()); |
| } else { |
| ret = audio_source_check_a2dp_ready(); |
| } |
| } else { |
| if (audio_sink_check_a2dp_ready) |
| ret = audio_sink_check_a2dp_ready(); |
| } |
| } |
| return ret; |
| } |
| |
| int BtA2dp::startCapture() |
| { |
| int ret = 0; |
| |
| PAL_DBG(LOG_TAG, "a2dp_start_capture start"); |
| |
| if (!isDummySink) { |
| if (!(bt_lib_sink_handle && (audio_sink_start_api || |
| audio_sink_start) && audio_get_dec_config)) { |
| PAL_ERR(LOG_TAG, "a2dp handle is not identified, Ignoring start capture request"); |
| return -ENOSYS; |
| } |
| |
| if (a2dpState != A2DP_STATE_STARTED && !totalActiveSessionRequests) { |
| codecFormat = CODEC_TYPE_INVALID; |
| PAL_DBG(LOG_TAG, "calling BT module stream start"); |
| /* This call indicates BT IPC lib to start capture */ |
| if (audio_sink_start_api) { |
| ret = audio_sink_start_api(get_session_type()); |
| } else { |
| ret = audio_sink_start(); |
| } |
| |
| PAL_ERR(LOG_TAG, "BT controller start capture return = %d",ret); |
| if (ret != 0 ) { |
| PAL_ERR(LOG_TAG, "BT controller start capture failed"); |
| return ret; |
| } |
| |
| codecInfo = audio_get_dec_config((audio_format_t *)&codecFormat); |
| if (codecInfo == NULL || codecFormat == CODEC_TYPE_INVALID) { |
| PAL_ERR(LOG_TAG, "invalid encoder config"); |
| return -EINVAL; |
| } |
| |
| /* Update Device GKV based on Decoder type */ |
| updateDeviceMetadata(); |
| |
| ret = configureA2dpEncoderDecoder(); |
| if (ret) { |
| PAL_DBG(LOG_TAG, "unable to configure DSP decoder"); |
| return ret; |
| } |
| } |
| } else { |
| uint8_t multi_cast = 0, num_dev = 1; |
| |
| if (!(bt_lib_sink_handle && (audio_sink_start_api || |
| audio_sink_start) && (audio_get_enc_config_api || |
| audio_get_enc_config))) { |
| PAL_ERR(LOG_TAG, "a2dp handle is not identified, Ignoring start capture request"); |
| return -ENOSYS; |
| } |
| |
| if (param_bt_a2dp.a2dp_capture_suspended) { |
| // session will be restarted after suspend completion |
| PAL_INFO(LOG_TAG, "a2dp start capture requested during suspend state"); |
| return -EINVAL; |
| } |
| |
| if (a2dpState != A2DP_STATE_STARTED && !totalActiveSessionRequests) { |
| codecFormat = CODEC_TYPE_INVALID; |
| PAL_DBG(LOG_TAG, "calling BT module stream start"); |
| /* This call indicates BT IPC lib to start */ |
| if (audio_sink_start_api) { |
| ret = audio_sink_start_api(get_session_type()); |
| } |
| else { |
| ret = audio_sink_start(); |
| } |
| |
| PAL_ERR(LOG_TAG, "BT controller start return = %d",ret); |
| if (ret != 0 ) { |
| PAL_ERR(LOG_TAG, "BT controller start failed"); |
| return ret; |
| } |
| |
| if (audio_get_enc_config_api) { |
| codecInfo = audio_get_enc_config_api(get_session_type(), &multi_cast, &num_dev, (audio_format_t*)&codecFormat); |
| } else { |
| codecInfo = audio_get_enc_config(&multi_cast, &num_dev, (audio_format_t*)&codecFormat); |
| } |
| |
| if (codecInfo == NULL || codecFormat == CODEC_TYPE_INVALID) { |
| PAL_ERR(LOG_TAG, "invalid codec config"); |
| return -EINVAL; |
| } |
| |
| /* Update Device GKV based on Decoder type */ |
| updateDeviceMetadata(); |
| |
| ret = configureA2dpEncoderDecoder(); |
| if (ret) { |
| PAL_DBG(LOG_TAG, "unable to configure DSP decoder"); |
| return ret; |
| } |
| } |
| } |
| |
| if (!isDummySink) { |
| if (!a2dp_send_sink_setup_complete()) { |
| PAL_DBG(LOG_TAG, "sink_setup_complete not successful"); |
| ret = -ETIMEDOUT; |
| } |
| } |
| |
| a2dpState = A2DP_STATE_STARTED; |
| totalActiveSessionRequests++; |
| |
| PAL_DBG(LOG_TAG, "start A2DP sink total active sessions :%d", |
| totalActiveSessionRequests); |
| return ret; |
| } |
| |
| int BtA2dp::stopCapture() |
| { |
| int ret =0; |
| |
| PAL_VERBOSE(LOG_TAG, "a2dp_stop_capture start"); |
| if (!(bt_lib_sink_handle && (audio_sink_stop_api || |
| audio_sink_stop))) { |
| PAL_ERR(LOG_TAG, "a2dp handle is not identified, Ignoring stop request"); |
| return -ENOSYS; |
| } |
| |
| if (totalActiveSessionRequests > 0) |
| totalActiveSessionRequests--; |
| |
| if (!totalActiveSessionRequests) { |
| PAL_VERBOSE(LOG_TAG, "calling BT module stream stop"); |
| isConfigured = false; |
| if (audio_sink_stop_api) { |
| ret = audio_sink_stop_api(get_session_type()); |
| } else { |
| ret = audio_sink_stop(); |
| } |
| |
| if (ret < 0) { |
| PAL_ERR(LOG_TAG, "stop stream to BT IPC lib failed"); |
| } else { |
| PAL_VERBOSE(LOG_TAG, "stop steam to BT IPC lib successful"); |
| } |
| |
| // It can be in A2DP_STATE_DISCONNECTED, if device disconnect happens prior to Stop. |
| if (a2dpState == A2DP_STATE_STARTED) |
| a2dpState = A2DP_STATE_STOPPED; |
| |
| if (pluginCodec) { |
| pluginCodec->close_plugin(pluginCodec); |
| pluginCodec = NULL; |
| } |
| if (pluginHandler) { |
| dlclose(pluginHandler); |
| pluginHandler = NULL; |
| } |
| } |
| PAL_DBG(LOG_TAG, "Stop A2DP capture, total active sessions :%d", |
| totalActiveSessionRequests); |
| return 0; |
| } |
| |
| int32_t BtA2dp::setDeviceParameter(uint32_t param_id, void *param) |
| { |
| int32_t status = 0; |
| pal_param_bta2dp_t* param_a2dp = (pal_param_bta2dp_t *)param; |
| |
| if (isA2dpOffloadSupported == false) { |
| PAL_VERBOSE(LOG_TAG, "no supported encoders identified,ignoring a2dp setparam"); |
| status = -EINVAL; |
| goto exit; |
| } |
| |
| switch(param_id) { |
| case PAL_PARAM_ID_DEVICE_CONNECTION: |
| { |
| pal_param_device_connection_t *device_connection = |
| (pal_param_device_connection_t *)param; |
| if (device_connection->connection_state == true) { |
| if (a2dpRole == SOURCE) |
| open_a2dp_source(); |
| |
| else { |
| #ifdef A2DP_SINK_SUPPORTED |
| |
| open_a2dp_sink(); |
| #else |
| a2dpState = A2DP_STATE_CONNECTED; |
| #endif |
| } |
| } else { |
| if (a2dpRole == SOURCE) { |
| status = close_audio_source(); |
| } else { |
| #ifdef A2DP_SINK_SUPPORTED |
| status = close_audio_sink(); |
| #else |
| totalActiveSessionRequests = 0; |
| param_bt_a2dp.a2dp_suspended = false; |
| param_bt_a2dp.a2dp_capture_suspended = false; |
| param_bt_a2dp.reconfig = false; |
| param_bt_a2dp.latency = 0; |
| a2dpState = A2DP_STATE_DISCONNECTED; |
| #endif |
| } |
| } |
| break; |
| } |
| case PAL_PARAM_ID_BT_A2DP_RECONFIG: |
| { |
| if (a2dpState != A2DP_STATE_DISCONNECTED) { |
| param_bt_a2dp.reconfig = param_a2dp->reconfig; |
| } |
| break; |
| } |
| case PAL_PARAM_ID_BT_A2DP_SUSPENDED: |
| { |
| if (bt_lib_source_handle == nullptr) |
| goto exit; |
| |
| if (param_bt_a2dp.a2dp_suspended == param_a2dp->a2dp_suspended) |
| goto exit; |
| |
| if (param_a2dp->a2dp_suspended == true) { |
| param_bt_a2dp.a2dp_suspended = true; |
| if (a2dpState == A2DP_STATE_DISCONNECTED) |
| goto exit; |
| |
| status = rm->a2dpSuspend(param_a2dp->dev_id); |
| if (audio_source_suspend_api) |
| audio_source_suspend_api(get_session_type()); |
| else |
| audio_source_suspend(); |
| } else { |
| if (clear_source_a2dpsuspend_flag) |
| clear_source_a2dpsuspend_flag(); |
| |
| param_bt_a2dp.a2dp_suspended = false; |
| |
| if (totalActiveSessionRequests > 0) { |
| if (audio_source_start_api) { |
| status = audio_source_start_api(get_session_type()); |
| } else { |
| status = audio_source_start(); |
| } |
| if (status) { |
| PAL_ERR(LOG_TAG, "BT controller start failed"); |
| goto exit; |
| } |
| } |
| status = rm->a2dpResume(param_a2dp->dev_id); |
| } |
| break; |
| } |
| case PAL_PARAM_ID_BT_A2DP_TWS_CONFIG: |
| { |
| isTwsMonoModeOn = param_a2dp->is_tws_mono_mode_on; |
| if (a2dpState == A2DP_STATE_STARTED) { |
| std::shared_ptr<Device> dev = nullptr; |
| Stream *stream = NULL; |
| Session *session = NULL; |
| std::vector<Stream*> activestreams; |
| pal_bt_tws_payload param_tws; |
| |
| dev = Device::getInstance(&deviceAttr, rm); |
| status = rm->getActiveStream_l(activestreams, dev); |
| if ((0 != status) || (activestreams.size() == 0)) { |
| PAL_ERR(LOG_TAG, "no active stream available"); |
| return -EINVAL; |
| } |
| stream = static_cast<Stream *>(activestreams[0]); |
| stream->getAssociatedSession(&session); |
| param_tws.isTwsMonoModeOn = isTwsMonoModeOn; |
| param_tws.codecFormat = (uint32_t)codecFormat; |
| session->setParameters(stream, BT_PLACEHOLDER_ENCODER, param_id, ¶m_tws); |
| } |
| break; |
| } |
| case PAL_PARAM_ID_BT_A2DP_LC3_CONFIG: |
| { |
| isLC3MonoModeOn = param_a2dp->is_lc3_mono_mode_on; |
| if (a2dpState == A2DP_STATE_STARTED) { |
| std::shared_ptr<Device> dev = nullptr; |
| Stream *stream = NULL; |
| Session *session = NULL; |
| std::vector<Stream*> activestreams; |
| pal_bt_lc3_payload param_lc3; |
| |
| dev = Device::getInstance(&deviceAttr, rm); |
| status = rm->getActiveStream_l(activestreams, dev); |
| if ((0 != status) || (activestreams.size() == 0)) { |
| PAL_ERR(LOG_TAG, "no active stream available"); |
| return -EINVAL; |
| } |
| stream = static_cast<Stream *>(activestreams[0]); |
| stream->getAssociatedSession(&session); |
| param_lc3.isLC3MonoModeOn = isLC3MonoModeOn; |
| session->setParameters(stream, BT_PLACEHOLDER_ENCODER, param_id, ¶m_lc3); |
| } |
| break; |
| } |
| case PAL_PARAM_ID_BT_A2DP_CAPTURE_SUSPENDED: |
| { |
| if (bt_lib_sink_handle == nullptr) |
| goto exit; |
| |
| if (param_bt_a2dp.a2dp_capture_suspended == param_a2dp->a2dp_capture_suspended) |
| goto exit; |
| |
| if (param_a2dp->a2dp_capture_suspended == true) { |
| param_bt_a2dp.a2dp_capture_suspended = true; |
| if (a2dpState == A2DP_STATE_DISCONNECTED) |
| goto exit; |
| |
| rm->a2dpCaptureSuspend(param_a2dp->dev_id); |
| if (audio_sink_suspend_api) |
| audio_sink_suspend_api(get_session_type()); |
| else |
| audio_sink_suspend(); |
| } else { |
| if (clear_source_a2dpsuspend_flag) |
| clear_source_a2dpsuspend_flag(); |
| |
| param_bt_a2dp.a2dp_capture_suspended = false; |
| |
| if (totalActiveSessionRequests > 0) { |
| if (audio_sink_start_api) { |
| status = audio_sink_start_api(get_session_type()); |
| } else { |
| status = audio_sink_start(); |
| } |
| |
| if (status) { |
| PAL_ERR(LOG_TAG, "BT controller start failed"); |
| goto exit; |
| } |
| } |
| rm->a2dpCaptureResume(param_a2dp->dev_id); |
| } |
| break; |
| } |
| case PAL_PARAM_ID_SET_SINK_METADATA: |
| if (deviceAttr.id == PAL_DEVICE_IN_BLUETOOTH_BLE) { |
| if (btoffload_update_metadata_api) { |
| PAL_INFO(LOG_TAG, "sending sink metadata to BT API"); |
| btoffload_update_metadata_api(get_session_type(), param); |
| } |
| } |
| break; |
| case PAL_PARAM_ID_SET_SOURCE_METADATA: |
| if (deviceAttr.id == PAL_DEVICE_OUT_BLUETOOTH_BLE) { |
| if (btoffload_update_metadata_api) { |
| PAL_INFO(LOG_TAG, "sending source metadata to BT API"); |
| btoffload_update_metadata_api(get_session_type(), param); |
| } |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| exit: |
| return status; |
| } |
| |
| int32_t BtA2dp::getDeviceParameter(uint32_t param_id, void **param) |
| { |
| switch (param_id) { |
| case PAL_PARAM_ID_BT_A2DP_RECONFIG: |
| case PAL_PARAM_ID_BT_A2DP_RECONFIG_SUPPORTED: |
| case PAL_PARAM_ID_BT_A2DP_SUSPENDED: |
| case PAL_PARAM_ID_BT_A2DP_CAPTURE_SUSPENDED: |
| case PAL_PARAM_ID_BT_A2DP_DECODER_LATENCY: |
| case PAL_PARAM_ID_BT_A2DP_ENCODER_LATENCY: |
| *param = ¶m_bt_a2dp; |
| break; |
| case PAL_PARAM_ID_BT_A2DP_FORCE_SWITCH: |
| { |
| if (param_bt_a2dp.reconfig || |
| ((a2dpState != A2DP_STATE_STARTED) && (param_bt_a2dp.a2dp_suspended == true))) { |
| param_bt_a2dp.is_force_switch = true; |
| PAL_DBG(LOG_TAG, "a2dp reconfig or a2dp suspended/a2dpState is not started"); |
| } else { |
| param_bt_a2dp.is_force_switch = false; |
| } |
| |
| *param = ¶m_bt_a2dp; |
| break; |
| } |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| std::shared_ptr<Device> BtA2dp::getObject(pal_device_id_t id) |
| { |
| if (id == PAL_DEVICE_OUT_BLUETOOTH_A2DP) { |
| return objRx; |
| } |
| else if (id == PAL_DEVICE_IN_BLUETOOTH_A2DP) { |
| return objTx; |
| } |
| else if (id == PAL_DEVICE_OUT_BLUETOOTH_BLE) { |
| return objBleRx; |
| } |
| else if (id == PAL_DEVICE_OUT_BLUETOOTH_BLE_BROADCAST) { |
| return objBleBroadcastRx; |
| } else { |
| return objBleTx; |
| } |
| } |
| |
| std::shared_ptr<Device> |
| BtA2dp::getInstance(struct pal_device *device, std::shared_ptr<ResourceManager> Rm) |
| { |
| if (device->id == PAL_DEVICE_OUT_BLUETOOTH_A2DP) { |
| if (!objRx) { |
| PAL_INFO(LOG_TAG, "creating instance for %d", device->id); |
| std::shared_ptr<Device> sp(new BtA2dp(device, Rm)); |
| objRx = sp; |
| } |
| return objRx; |
| } else if (device->id == PAL_DEVICE_IN_BLUETOOTH_A2DP) { |
| if (!objTx) { |
| PAL_INFO(LOG_TAG, "creating instance for %d", device->id); |
| std::shared_ptr<Device> sp(new BtA2dp(device, Rm)); |
| objTx = sp; |
| } |
| return objTx; |
| } else if (device->id == PAL_DEVICE_OUT_BLUETOOTH_BLE) { |
| if (!objBleRx) { |
| PAL_INFO(LOG_TAG, "creating instance for %d", device->id); |
| std::shared_ptr<Device> sp(new BtA2dp(device, Rm)); |
| objBleRx = sp; |
| } |
| return objBleRx; |
| } else if (device->id == PAL_DEVICE_OUT_BLUETOOTH_BLE_BROADCAST) { |
| if (!objBleBroadcastRx) { |
| PAL_INFO(LOG_TAG, "creating instance for %d", device->id); |
| std::shared_ptr<Device> sp(new BtA2dp(device, Rm)); |
| objBleBroadcastRx = sp; |
| } |
| return objBleBroadcastRx; |
| } else { |
| if (!objBleTx) { |
| PAL_INFO(LOG_TAG, "creating instance for %d", device->id); |
| std::shared_ptr<Device> sp(new BtA2dp(device, Rm)); |
| objBleTx = sp; |
| } |
| return objBleTx; |
| } |
| } |
| |
| /* Scope of BtScoRX/Tx class */ |
| // definition of static BtSco member variables |
| std::shared_ptr<Device> BtSco::objRx = nullptr; |
| std::shared_ptr<Device> BtSco::objTx = nullptr; |
| bool BtSco::isScoOn = false; |
| bool BtSco::isWbSpeechEnabled = false; |
| int BtSco::swbSpeechMode = SPEECH_MODE_INVALID; |
| bool BtSco::isSwbLc3Enabled = false; |
| audio_lc3_codec_cfg_t BtSco::lc3CodecInfo = {}; |
| bool BtSco::isNrecEnabled = false; |
| |
| BtSco::BtSco(struct pal_device *device, std::shared_ptr<ResourceManager> Rm) |
| : Bluetooth(device, Rm) |
| { |
| codecType = (device->id == PAL_DEVICE_OUT_BLUETOOTH_SCO) ? ENC : DEC; |
| pluginHandler = NULL; |
| pluginCodec = NULL; |
| } |
| |
| BtSco::~BtSco() |
| { |
| if (lc3CodecInfo.enc_cfg.streamMapOut != NULL) |
| delete [] lc3CodecInfo.enc_cfg.streamMapOut; |
| if (lc3CodecInfo.dec_cfg.streamMapIn != NULL) |
| delete [] lc3CodecInfo.dec_cfg.streamMapIn; |
| } |
| |
| bool BtSco::isDeviceReady() |
| { |
| return isScoOn; |
| } |
| |
| void BtSco::updateSampleRate(uint32_t *sampleRate) |
| { |
| if (isWbSpeechEnabled) |
| *sampleRate = SAMPLINGRATE_16K; |
| else if (swbSpeechMode != SPEECH_MODE_INVALID) |
| *sampleRate = SAMPLINGRATE_96K; |
| else if (isSwbLc3Enabled) |
| *sampleRate = SAMPLINGRATE_96K; |
| else |
| *sampleRate = SAMPLINGRATE_8K; |
| } |
| |
| int32_t BtSco::setDeviceParameter(uint32_t param_id, void *param) |
| { |
| pal_param_btsco_t* param_bt_sco = (pal_param_btsco_t *)param; |
| |
| switch (param_id) { |
| case PAL_PARAM_ID_BT_SCO: |
| isScoOn = param_bt_sco->bt_sco_on; |
| break; |
| case PAL_PARAM_ID_BT_SCO_WB: |
| isWbSpeechEnabled = param_bt_sco->bt_wb_speech_enabled; |
| PAL_DBG(LOG_TAG, "isWbSpeechEnabled = %d", isWbSpeechEnabled); |
| break; |
| case PAL_PARAM_ID_BT_SCO_SWB: |
| swbSpeechMode = param_bt_sco->bt_swb_speech_mode; |
| PAL_DBG(LOG_TAG, "swbSpeechMode = %d", swbSpeechMode); |
| break; |
| case PAL_PARAM_ID_BT_SCO_LC3: |
| isSwbLc3Enabled = param_bt_sco->bt_lc3_speech_enabled; |
| if (isSwbLc3Enabled) { |
| // parse sco lc3 parameters and pack into codec info |
| convertCodecInfo(lc3CodecInfo, param_bt_sco->lc3_cfg); |
| } |
| PAL_DBG(LOG_TAG, "isSwbLc3Enabled = %d", isSwbLc3Enabled); |
| break; |
| case PAL_PARAM_ID_BT_SCO_NREC: |
| isNrecEnabled = param_bt_sco->bt_sco_nrec; |
| PAL_DBG(LOG_TAG, "isNrecEnabled = %d", isNrecEnabled); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| void BtSco::convertCodecInfo(audio_lc3_codec_cfg_t &lc3CodecInfo, |
| btsco_lc3_cfg_t &lc3Cfg) |
| { |
| std::vector<lc3_stream_map_t> steamMapIn; |
| std::vector<lc3_stream_map_t> steamMapOut; |
| uint32_t audio_location = 0; |
| uint8_t stream_id = 0; |
| uint8_t direction = 0; |
| uint8_t value = 0; |
| int idx = 0; |
| std::string vendorStr(lc3Cfg.vendor); |
| std::string streamMapStr(lc3Cfg.streamMap); |
| std::regex vendorPattern("([0-9a-fA-F]{2})[,[:s:]]?"); |
| std::regex streamMapPattern("([0-9])[,[:s:]]+([0-9])[,[:s:]]+([MLR])"); |
| std::smatch match; |
| |
| // convert and fill in encoder cfg |
| lc3CodecInfo.enc_cfg.toAirConfig.sampling_freq = LC3_CSC[lc3Cfg.txconfig_index].sampling_freq; |
| lc3CodecInfo.enc_cfg.toAirConfig.max_octets_per_frame = LC3_CSC[lc3Cfg.txconfig_index].max_octets_per_frame; |
| lc3CodecInfo.enc_cfg.toAirConfig.frame_duration = LC3_CSC[lc3Cfg.txconfig_index].frame_duration; |
| lc3CodecInfo.enc_cfg.toAirConfig.bit_depth = LC3_CSC[lc3Cfg.txconfig_index].bit_depth; |
| if (lc3Cfg.fields_map & LC3_FRAME_DURATION_BIT) |
| lc3CodecInfo.enc_cfg.toAirConfig.frame_duration = lc3Cfg.frame_duration; |
| lc3CodecInfo.enc_cfg.toAirConfig.api_version = lc3Cfg.api_version; |
| lc3CodecInfo.enc_cfg.toAirConfig.num_blocks = lc3Cfg.num_blocks; |
| lc3CodecInfo.enc_cfg.toAirConfig.default_q_level = 0; |
| lc3CodecInfo.enc_cfg.toAirConfig.mode = 0x1; |
| lc3CodecInfo.is_enc_config_set = true; |
| |
| // convert and fill in decoder cfg |
| lc3CodecInfo.dec_cfg.fromAirConfig.sampling_freq = LC3_CSC[lc3Cfg.rxconfig_index].sampling_freq; |
| lc3CodecInfo.dec_cfg.fromAirConfig.max_octets_per_frame = LC3_CSC[lc3Cfg.rxconfig_index].max_octets_per_frame; |
| lc3CodecInfo.dec_cfg.fromAirConfig.frame_duration = LC3_CSC[lc3Cfg.rxconfig_index].frame_duration; |
| lc3CodecInfo.dec_cfg.fromAirConfig.bit_depth = LC3_CSC[lc3Cfg.rxconfig_index].bit_depth; |
| if (lc3Cfg.fields_map & LC3_FRAME_DURATION_BIT) |
| lc3CodecInfo.dec_cfg.fromAirConfig.frame_duration = lc3Cfg.frame_duration; |
| lc3CodecInfo.dec_cfg.fromAirConfig.api_version = lc3Cfg.api_version; |
| lc3CodecInfo.dec_cfg.fromAirConfig.num_blocks = lc3Cfg.num_blocks; |
| lc3CodecInfo.dec_cfg.fromAirConfig.default_q_level = 0; |
| lc3CodecInfo.dec_cfg.fromAirConfig.mode = 0x1; |
| lc3CodecInfo.is_dec_config_set = true; |
| |
| // parse vendor specific string |
| idx = 15; |
| while (std::regex_search(vendorStr, match, vendorPattern)) { |
| if (idx < 0) { |
| PAL_ERR(LOG_TAG, "wrong vendor info length, string %s", lc3Cfg.vendor); |
| break; |
| } |
| value = (uint8_t)strtol(match[1].str().c_str(), NULL, 16); |
| lc3CodecInfo.enc_cfg.toAirConfig.vendor_specific[idx] = value; |
| lc3CodecInfo.dec_cfg.fromAirConfig.vendor_specific[idx--] = value; |
| vendorStr = match.suffix().str(); |
| } |
| if (idx != -1) |
| PAL_ERR(LOG_TAG, "wrong vendor info length, string %s", lc3Cfg.vendor); |
| |
| // parse stream map string and append stream map structures |
| while (std::regex_search(streamMapStr, match, streamMapPattern)) { |
| stream_id = atoi(match[1].str().c_str()); |
| direction = atoi(match[2].str().c_str()); |
| if (!strcmp(match[3].str().c_str(), "M")) { |
| audio_location = 0; |
| } else if (!strcmp(match[3].str().c_str(), "L")) { |
| audio_location = 1; |
| } else if (!strcmp(match[3].str().c_str(), "R")) { |
| audio_location = 2; |
| } |
| |
| if ((stream_id > 1) || (direction > 1) || (audio_location > 2)) { |
| PAL_ERR(LOG_TAG, "invalid stream info (%d, %d, %d)", stream_id, direction, audio_location); |
| continue; |
| } |
| if (direction == TO_AIR) |
| steamMapOut.push_back({audio_location, stream_id, direction}); |
| else |
| steamMapIn.push_back({audio_location, stream_id, direction}); |
| |
| streamMapStr = match.suffix().str(); |
| } |
| |
| PAL_DBG(LOG_TAG, "stream map out size: %d, stream map in size: %d", steamMapOut.size(), steamMapIn.size()); |
| if ((steamMapOut.size() == 0) || (steamMapIn.size() == 0)) { |
| PAL_ERR(LOG_TAG, "invalid size steamMapOut.size %d, steamMapIn.size %d", |
| steamMapOut.size(), steamMapIn.size()); |
| return; |
| } |
| |
| idx = 0; |
| lc3CodecInfo.enc_cfg.stream_map_size = steamMapOut.size(); |
| if (lc3CodecInfo.enc_cfg.streamMapOut != NULL) |
| delete [] lc3CodecInfo.enc_cfg.streamMapOut; |
| lc3CodecInfo.enc_cfg.streamMapOut = new lc3_stream_map_t[steamMapOut.size()]; |
| for (auto &it : steamMapOut) { |
| lc3CodecInfo.enc_cfg.streamMapOut[idx].audio_location = it.audio_location; |
| lc3CodecInfo.enc_cfg.streamMapOut[idx].stream_id = it.stream_id; |
| lc3CodecInfo.enc_cfg.streamMapOut[idx++].direction = it.direction; |
| PAL_DBG(LOG_TAG, "streamMapOut: audio_location %d, stream_id %d, direction %d", |
| it.audio_location, it.stream_id, it.direction); |
| } |
| |
| idx = 0; |
| lc3CodecInfo.dec_cfg.stream_map_size = steamMapIn.size(); |
| if (lc3CodecInfo.dec_cfg.streamMapIn != NULL) |
| delete [] lc3CodecInfo.dec_cfg.streamMapIn; |
| lc3CodecInfo.dec_cfg.streamMapIn = new lc3_stream_map_t[steamMapIn.size()]; |
| for (auto &it : steamMapIn) { |
| lc3CodecInfo.dec_cfg.streamMapIn[idx].audio_location = it.audio_location; |
| lc3CodecInfo.dec_cfg.streamMapIn[idx].stream_id = it.stream_id; |
| lc3CodecInfo.dec_cfg.streamMapIn[idx++].direction = it.direction; |
| PAL_DBG(LOG_TAG, "steamMapIn: audio_location %d, stream_id %d, direction %d", |
| it.audio_location, it.stream_id, it.direction); |
| } |
| |
| if (lc3CodecInfo.dec_cfg.streamMapIn[0].audio_location == 0) |
| lc3CodecInfo.dec_cfg.decoder_output_channel = CH_MONO; |
| else |
| lc3CodecInfo.dec_cfg.decoder_output_channel = CH_STEREO; |
| } |
| |
| int BtSco::startSwb() |
| { |
| int ret = 0; |
| |
| if (!isConfigured) { |
| ret = configureA2dpEncoderDecoder(); |
| } else { |
| /* isAbrEnabled flag assignment will be skipped if |
| * path is already configured. |
| * Force isAbrEnabled flag for SWB use case. Ideally, |
| * this flag should be populated from plugin. |
| */ |
| if (codecFormat == CODEC_TYPE_APTX_AD_SPEECH) |
| isAbrEnabled = true; |
| } |
| |
| return ret; |
| } |
| |
| int BtSco::start() |
| { |
| int status = 0; |
| mDeviceMutex.lock(); |
| |
| if (customPayload) |
| free(customPayload); |
| |
| customPayload = NULL; |
| customPayloadSize = 0; |
| |
| if (swbSpeechMode != SPEECH_MODE_INVALID) { |
| codecFormat = CODEC_TYPE_APTX_AD_SPEECH; |
| codecInfo = (void *)&swbSpeechMode; |
| } else if (isSwbLc3Enabled) { |
| codecFormat = CODEC_TYPE_LC3; |
| codecInfo = (void *)&lc3CodecInfo; |
| } else |
| codecFormat = CODEC_TYPE_INVALID; |
| |
| updateDeviceMetadata(); |
| if ((codecFormat == CODEC_TYPE_APTX_AD_SPEECH) || |
| (codecFormat == CODEC_TYPE_LC3)) { |
| status = startSwb(); |
| if (status) |
| goto exit; |
| } else { |
| // For SCO NB and WB that don't have encoder and decoder in place, |
| // just override codec configurations with device attributions. |
| codecConfig.bit_width = deviceAttr.config.bit_width; |
| codecConfig.sample_rate = deviceAttr.config.sample_rate; |
| codecConfig.aud_fmt_id = PAL_AUDIO_FMT_DEFAULT_PCM; |
| codecConfig.ch_info.channels = deviceAttr.config.ch_info.channels; |
| isConfigured = true; |
| PAL_DBG(LOG_TAG, "SCO WB/NB codecConfig is same as deviceAttr bw = %d,sr = %d,ch = %d", |
| codecConfig.bit_width, codecConfig.sample_rate, codecConfig.ch_info.channels); |
| } |
| |
| // Configure NREC only on Tx path & First session request only. |
| if ((isConfigured == true) && |
| (deviceAttr.id == PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET)) { |
| if (deviceStartStopCount == 0) { |
| configureNrecParameters(isNrecEnabled); |
| } |
| } |
| |
| if (deviceStartStopCount == 0) { |
| status = configureSlimbusClockSrc(); |
| if (status) |
| goto exit; |
| } |
| |
| status = Device::start_l(); |
| if (!status && isAbrEnabled) |
| startAbr(); |
| |
| exit: |
| mDeviceMutex.unlock(); |
| return status; |
| } |
| |
| int BtSco::stop() |
| { |
| int status = 0; |
| mDeviceMutex.lock(); |
| |
| if (isAbrEnabled) |
| stopAbr(); |
| |
| if (pluginCodec) { |
| pluginCodec->close_plugin(pluginCodec); |
| pluginCodec = NULL; |
| } |
| if (pluginHandler) { |
| dlclose(pluginHandler); |
| pluginHandler = NULL; |
| } |
| |
| Device::stop_l(); |
| if (isAbrEnabled == false) |
| codecFormat = CODEC_TYPE_INVALID; |
| if (deviceStartStopCount == 0) |
| isConfigured = false; |
| |
| mDeviceMutex.unlock(); |
| return status; |
| } |
| |
| std::shared_ptr<Device> BtSco::getObject(pal_device_id_t id) |
| { |
| if (id == PAL_DEVICE_OUT_BLUETOOTH_SCO) |
| return objRx; |
| else |
| return objTx; |
| } |
| |
| std::shared_ptr<Device> BtSco::getInstance(struct pal_device *device, |
| std::shared_ptr<ResourceManager> Rm) |
| { |
| if (device->id == PAL_DEVICE_OUT_BLUETOOTH_SCO) { |
| if (!objRx) { |
| std::shared_ptr<Device> sp(new BtSco(device, Rm)); |
| objRx = sp; |
| } |
| return objRx; |
| } else { |
| if (!objTx) { |
| PAL_ERR(LOG_TAG, "creating instance for %d", device->id); |
| std::shared_ptr<Device> sp(new BtSco(device, Rm)); |
| objTx = sp; |
| } |
| return objTx; |
| } |
| } |
| /* BtSco class end */ |