/* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "AHAL_ModuleBluetooth" #include #include "core-impl/ModuleBluetoothBase.h" #include "core-impl/StreamBluetooth.h" using aidl::android::hardware::audio::common::SinkMetadata; using aidl::android::hardware::audio::common::SourceMetadata; using aidl::android::hardware::bluetooth::audio::ChannelMode; using aidl::android::hardware::bluetooth::audio::PcmConfiguration; using aidl::android::media::audio::common::AudioChannelLayout; using aidl::android::media::audio::common::AudioConfigBase; using aidl::android::media::audio::common::AudioDeviceDescription; using aidl::android::media::audio::common::AudioDeviceType; using aidl::android::media::audio::common::AudioFormatDescription; using aidl::android::media::audio::common::AudioFormatType; using aidl::android::media::audio::common::AudioIoFlags; using aidl::android::media::audio::common::AudioOffloadInfo; using aidl::android::media::audio::common::AudioPort; using aidl::android::media::audio::common::AudioPortConfig; using aidl::android::media::audio::common::AudioPortExt; using aidl::android::media::audio::common::AudioProfile; using aidl::android::media::audio::common::Int; using aidl::android::media::audio::common::MicrophoneInfo; using aidl::android::media::audio::common::PcmType; namespace aidl::android::hardware::audio::core { namespace { PcmType pcmTypeFromBitsPerSample(int8_t bitsPerSample) { if (bitsPerSample == 8) return PcmType::UINT_8_BIT; else if (bitsPerSample == 16) return PcmType::INT_16_BIT; else if (bitsPerSample == 24) return PcmType::INT_24_BIT; else if (bitsPerSample == 32) return PcmType::INT_32_BIT; ALOGE("Unsupported bitsPerSample: %d", bitsPerSample); return PcmType::DEFAULT; } AudioChannelLayout channelLayoutFromChannelMode(ChannelMode mode) { if (mode == ChannelMode::MONO) { return AudioChannelLayout::make( AudioChannelLayout::LAYOUT_MONO); } else if (mode == ChannelMode::STEREO || mode == ChannelMode::DUALMONO) { return AudioChannelLayout::make( AudioChannelLayout::LAYOUT_STEREO); } ALOGE("Unsupported channel mode: %s", toString(mode).c_str()); return AudioChannelLayout{}; } } // namespace ModuleBluetoothBase::ModuleBluetoothBase(std::unique_ptr&& config) : Module(Type::BLUETOOTH, std::move(config)) {} ndk::ScopedAStatus ModuleBluetoothBase::getBluetoothA2dp( std::shared_ptr* _aidl_return) { *_aidl_return = getBtA2dp().getInstance(); LOG(DEBUG) << __func__ << ": returning instance of IBluetoothA2dp: " << _aidl_return->get(); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus ModuleBluetoothBase::getBluetoothLe( std::shared_ptr* _aidl_return) { *_aidl_return = getBtLe().getInstance(); LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _aidl_return->get(); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus ModuleBluetoothBase::getBluetooth(std::shared_ptr* _aidl_return) { // Only provided when Bluetooth port is present. const auto& ports = getConfig().ports; bool hasSco = false; for (const auto& port : ports) { if (port.ext.getTag() == AudioPortExt::device && port.ext.get().device.type.connection == AudioDeviceDescription::CONNECTION_BT_SCO) { hasSco = true; break; } } if (hasSco) { *_aidl_return = getBt().getInstance(); } else { *_aidl_return = nullptr; } LOG(DEBUG) << __func__ << ": returning instance of IBluetooth: " << _aidl_return->get(); return ndk::ScopedAStatus::ok(); } ChildInterface& ModuleBluetoothBase::getBtA2dp() { if (!mBluetoothA2dp) { auto handle = ndk::SharedRefBase::make(); handle->registerHandler(std::bind(&ModuleBluetoothBase::bluetoothParametersUpdated, this)); mBluetoothA2dp = handle; } return mBluetoothA2dp; } ChildInterface& ModuleBluetoothBase::getBtLe() { if (!mBluetoothLe) { auto handle = ndk::SharedRefBase::make(); handle->registerHandler(std::bind(&ModuleBluetoothBase::bluetoothParametersUpdated, this)); mBluetoothLe = handle; } return mBluetoothLe; } ChildInterface& ModuleBluetoothBase::getBt() { if (!mBluetooth) { auto handle = ndk::SharedRefBase::make(); handle->registerHandler(std::bind(&ModuleBluetoothBase::bluetoothParametersUpdated, this)); mBluetooth = handle; } return mBluetooth; } ModuleBluetoothBase::BtProfileHandles ModuleBluetoothBase::getBtProfileManagerHandles() { return std::make_tuple(getBt().getPtr(), getBtA2dp().getPtr(), getBtLe().getPtr()); } ndk::ScopedAStatus ModuleBluetoothBase::getMicMute(bool* _aidl_return __unused) { LOG(DEBUG) << __func__ << ": is not supported"; return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } ndk::ScopedAStatus ModuleBluetoothBase::setMicMute(bool in_mute __unused) { LOG(DEBUG) << __func__ << ": is not supported"; return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } ndk::ScopedAStatus ModuleBluetoothBase::setAudioPortConfig(const AudioPortConfig& in_requested, AudioPortConfig* out_suggested, bool* _aidl_return) { // Since 'fillConfig' is only called when a new port config is created, // handle the case of updating an existing port config separately. int32_t portId = 0; if (in_requested.id != 0) { auto& configs = getConfig().portConfigs; if (auto existing = findById(configs, in_requested.id); existing != configs.end()) { auto& ports = getConfig().ports; auto portIt = findById(ports, existing->portId); if (portIt != ports.end() && portIt->ext.getTag() == AudioPortExt::Tag::device) { portId = portIt->id; } } } auto fillConfig = [this](const AudioPort& port, AudioPortConfig* config) { if (port.ext.getTag() == AudioPortExt::device) { CachedProxy proxy; auto status = findOrCreateProxy(port, proxy); if (status.isOk()) { const auto& pcmConfig = proxy.pcmConfig; LOG(DEBUG) << "setAudioPortConfig: suggesting port config from " << pcmConfig.toString(); const auto pcmType = pcmTypeFromBitsPerSample(pcmConfig.bitsPerSample); const auto channelMask = channelLayoutFromChannelMode(pcmConfig.channelMode); if (pcmType != PcmType::DEFAULT && channelMask != AudioChannelLayout{}) { config->format = AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType}; config->channelMask = channelMask; config->sampleRate = Int{.value = pcmConfig.sampleRateHz}; config->flags = port.flags; config->ext = port.ext; return true; } } } return generateDefaultPortConfig(port, config); }; RETURN_STATUS_IF_ERROR( Module::setAudioPortConfigImpl(in_requested, fillConfig, out_suggested, _aidl_return)); if (portId != 0) { auto& ports = getConfig().ports; auto portIt = findById(ports, portId); if (portIt != ports.end() && portIt->ext.getTag() == AudioPortExt::Tag::device) { CachedProxy proxy; // Ignore error similar to 'fillConfig', this can happen in VTS tests when // device connections are simulated. findOrCreateProxy(*portIt, proxy); } } return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus ModuleBluetoothBase::supportsVariableLatency(bool* _aidl_return) { LOG(DEBUG) << __func__ << ": " << getType(); *_aidl_return = true; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus ModuleBluetoothBase::checkAudioPatchEndpointsMatch( const std::vector& sources, const std::vector& sinks) { // Both sources and sinks must be non-empty, this is guaranteed by 'setAudioPatch'. const bool isInput = sources[0]->ext.getTag() == AudioPortExt::device; const int32_t devicePortId = isInput ? sources[0]->portId : sinks[0]->portId; const auto proxyIt = mProxies.find(devicePortId); if (proxyIt == mProxies.end()) return ndk::ScopedAStatus::ok(); const auto& pcmConfig = proxyIt->second.pcmConfig; const AudioPortConfig* mixPortConfig = isInput ? sinks[0] : sources[0]; if (!StreamBluetooth::checkConfigParams( pcmConfig, AudioConfigBase{.sampleRate = mixPortConfig->sampleRate->value, .channelMask = *(mixPortConfig->channelMask), .format = *(mixPortConfig->format)})) { return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } if (int32_t handle = mixPortConfig->ext.get().handle; handle > 0) { mConnections.insert(std::pair(handle, devicePortId)); } return ndk::ScopedAStatus::ok(); } void ModuleBluetoothBase::onExternalDeviceConnectionChanged(const AudioPort& audioPort, bool connected) { if (!connected) mProxies.erase(audioPort.id); } ndk::ScopedAStatus ModuleBluetoothBase::createInputStream( StreamContext&& context, const SinkMetadata& sinkMetadata, const std::vector& microphones, std::shared_ptr* result) { CachedProxy proxy; RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy)); return createStreamInstance(result, std::move(context), sinkMetadata, microphones, getBtProfileManagerHandles(), proxy.ptr, proxy.pcmConfig); } ndk::ScopedAStatus ModuleBluetoothBase::createOutputStream( StreamContext&& context, const SourceMetadata& sourceMetadata, const std::optional& offloadInfo, std::shared_ptr* result) { CachedProxy proxy; RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy)); return createStreamInstance(result, std::move(context), sourceMetadata, offloadInfo, getBtProfileManagerHandles(), proxy.ptr, proxy.pcmConfig); } ndk::ScopedAStatus ModuleBluetoothBase::populateConnectedDevicePort(AudioPort* audioPort, int32_t nextPortId) { if (audioPort->ext.getTag() != AudioPortExt::device) { LOG(ERROR) << __func__ << ": not a device port: " << audioPort->toString(); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } const auto& devicePort = audioPort->ext.get(); const auto& description = devicePort.device.type; // This method must return an error when the device can not be connected. // Since A2DP/LE status events are sent asynchronously, it is more reliable // to attempt connecting to the BT stack rather than judge by the A2DP/LE status. if (description.connection != AudioDeviceDescription::CONNECTION_BT_A2DP && description.connection != AudioDeviceDescription::CONNECTION_BT_LE && description.connection != AudioDeviceDescription::CONNECTION_BT_SCO && !(description.connection == AudioDeviceDescription::CONNECTION_WIRELESS && description.type == AudioDeviceType::OUT_HEARING_AID)) { LOG(ERROR) << __func__ << ": unsupported device type: " << audioPort->toString(); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } CachedProxy proxy; RETURN_STATUS_IF_ERROR(createProxy(*audioPort, nextPortId, proxy)); // If the device is actually connected, it is configured by the BT stack. // Provide the current configuration instead of all possible profiles. const auto& pcmConfig = proxy.pcmConfig; audioPort->profiles.assign( {AudioProfile{.format = AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmTypeFromBitsPerSample( pcmConfig.bitsPerSample)}, .channelMasks = std::vector( {channelLayoutFromChannelMode(pcmConfig.channelMode)}), .sampleRates = std::vector({pcmConfig.sampleRateHz})}}); LOG(DEBUG) << __func__ << ": " << audioPort->toString(); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus ModuleBluetoothBase::onMasterMuteChanged(bool) { LOG(DEBUG) << __func__ << ": is not supported"; return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } ndk::ScopedAStatus ModuleBluetoothBase::onMasterVolumeChanged(float) { LOG(DEBUG) << __func__ << ": is not supported"; return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } int32_t ModuleBluetoothBase::getNominalLatencyMs(const AudioPortConfig& portConfig) { const auto connectionsIt = mConnections.find(portConfig.ext.get().handle); if (connectionsIt != mConnections.end()) { const auto proxyIt = mProxies.find(connectionsIt->second); if (proxyIt != mProxies.end()) { auto proxy = proxyIt->second.ptr; size_t dataIntervalUs = 0; if (!proxy->getPreferredDataIntervalUs(dataIntervalUs)) { LOG(WARNING) << __func__ << ": could not fetch preferred data interval"; } const bool isInput = portConfig.flags->getTag() == AudioIoFlags::input; return isInput ? StreamInBluetooth::getNominalLatencyMs(dataIntervalUs) : StreamOutBluetooth::getNominalLatencyMs(dataIntervalUs); } } LOG(ERROR) << __func__ << ": no connection or proxy found for " << portConfig.toString(); return Module::getNominalLatencyMs(portConfig); } binder_status_t ModuleBluetoothBase::dump(int fd, const char** args, uint32_t numArgs) { if (!::aidl::android::hardware::audio::common::hasArgument( args, numArgs, ::aidl::android::hardware::audio::common::kDumpFromAudioServerArgument)) { // Streams are dumped as part of audio flinger threads dump, // no need for a separate dump since the module itself does not // have anything interesting. Module::dump(fd, args, numArgs); } return ::android::OK; } ndk::ScopedAStatus ModuleBluetoothBase::createProxy(const AudioPort& audioPort, int32_t instancePortId, CachedProxy& proxy) { proxy.ptr = createProxyInstance(audioPort.flags.getTag() == AudioIoFlags::input); const auto& devicePort = audioPort.ext.get(); const auto device = devicePort.device.type; bool registrationSuccess = false; for (int i = 0; i < kCreateProxyRetries && !registrationSuccess; ++i) { registrationSuccess = proxy.ptr->registerPort(device); usleep(kCreateProxyRetrySleepMs * 1000); } if (!registrationSuccess) { LOG(ERROR) << __func__ << ": failed to register BT port for " << device.toString(); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } if (!proxy.ptr->loadAudioConfig(proxy.pcmConfig)) { LOG(ERROR) << __func__ << ": state=" << proxy.ptr->getState() << ", failed to load audio config"; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } mProxies.insert(std::pair(instancePortId, proxy)); mProxiesWeak[device] = proxy.ptr; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus ModuleBluetoothBase::fetchAndCheckProxy(const StreamContext& context, CachedProxy& proxy) { const auto connectionsIt = mConnections.find(context.getMixPortHandle()); if (connectionsIt != mConnections.end()) { const auto proxyIt = mProxies.find(connectionsIt->second); if (proxyIt != mProxies.end()) { proxy = proxyIt->second; mProxies.erase(proxyIt); } mConnections.erase(connectionsIt); } if (proxy.ptr != nullptr) { if (!StreamBluetooth::checkConfigParams( proxy.pcmConfig, AudioConfigBase{.sampleRate = context.getSampleRate(), .channelMask = context.getChannelLayout(), .format = context.getFormat()})) { return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } } // Not having a proxy is OK, it may happen in VTS tests when streams are opened on unconnected // mix ports. return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus ModuleBluetoothBase::findOrCreateProxy(const AudioPort& audioPort, CachedProxy& proxy) { if (auto proxyIt = mProxies.find(audioPort.id); proxyIt != mProxies.end()) { proxy = proxyIt->second; return ndk::ScopedAStatus::ok(); } if (audioPort.ext.getTag() == AudioPortExt::Tag::device) { const auto& description = audioPort.ext.get().device.type; auto weakIt = mProxiesWeak.find(description); if (weakIt != mProxiesWeak.end()) { if (auto existingProxy = weakIt->second.lock()) { proxy.ptr = existingProxy; if (proxy.ptr->loadAudioConfig(proxy.pcmConfig)) { mProxies.insert(std::pair(audioPort.id, proxy)); LOG(DEBUG) << __func__ << ": Reused existing proxy for " << description.toString(); return ndk::ScopedAStatus::ok(); } else { LOG(ERROR) << __func__ << ": Failed to load config from reused proxy"; } } // If expired or failed to load config, remove from map and create new mProxiesWeak.erase(weakIt); } } return createProxy(audioPort, audioPort.id, proxy); } } // namespace aidl::android::hardware::audio::core