mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 21:37:44 +00:00
When ModuleBluetooth::createProxy is invoked on BT device connection, the BT stack may not be fully ready yet, and port registration can fail. This is an intermittent state and registration should succeed after retrying. Bug: 320838889 Test: atest pts-bot:A2DP/SRC/REL/BV-02-I Change-Id: I0c7cf7c1c6a8ee03ef55b004f89139e3b56ee9cd
355 lines
17 KiB
C++
355 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2023 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 <android-base/logging.h>
|
|
|
|
#include "BluetoothAudioSession.h"
|
|
#include "core-impl/ModuleBluetooth.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;
|
|
using android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
|
|
using android::bluetooth::audio::aidl::BluetoothAudioPortAidlIn;
|
|
using android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
|
|
|
|
// TODO(b/312265159) bluetooth audio should be in its own process
|
|
// Remove this and the shared_libs when that happens
|
|
extern "C" binder_status_t createIBluetoothAudioProviderFactory();
|
|
|
|
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::layoutMask>(
|
|
AudioChannelLayout::LAYOUT_MONO);
|
|
} else if (mode == ChannelMode::STEREO || mode == ChannelMode::DUALMONO) {
|
|
return AudioChannelLayout::make<AudioChannelLayout::layoutMask>(
|
|
AudioChannelLayout::LAYOUT_STEREO);
|
|
}
|
|
ALOGE("Unsupported channel mode: %s", toString(mode).c_str());
|
|
return AudioChannelLayout{};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ModuleBluetooth::ModuleBluetooth(std::unique_ptr<Module::Configuration>&& config)
|
|
: Module(Type::BLUETOOTH, std::move(config)) {
|
|
// TODO(b/312265159) bluetooth audio should be in its own process
|
|
// Remove this and the shared_libs when that happens
|
|
binder_status_t status = createIBluetoothAudioProviderFactory();
|
|
if (status != STATUS_OK) {
|
|
LOG(ERROR) << "Failed to create bluetooth audio provider factory. Status: "
|
|
<< ::android::statusToString(status);
|
|
}
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::getBluetoothA2dp(
|
|
std::shared_ptr<IBluetoothA2dp>* _aidl_return) {
|
|
*_aidl_return = getBtA2dp().getInstance();
|
|
LOG(DEBUG) << __func__ << ": returning instance of IBluetoothA2dp: " << _aidl_return->get();
|
|
return ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::getBluetoothLe(std::shared_ptr<IBluetoothLe>* _aidl_return) {
|
|
*_aidl_return = getBtLe().getInstance();
|
|
LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _aidl_return->get();
|
|
return ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
ChildInterface<BluetoothA2dp>& ModuleBluetooth::getBtA2dp() {
|
|
if (!mBluetoothA2dp) {
|
|
auto handle = ndk::SharedRefBase::make<BluetoothA2dp>();
|
|
handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this));
|
|
mBluetoothA2dp = handle;
|
|
}
|
|
return mBluetoothA2dp;
|
|
}
|
|
|
|
ChildInterface<BluetoothLe>& ModuleBluetooth::getBtLe() {
|
|
if (!mBluetoothLe) {
|
|
auto handle = ndk::SharedRefBase::make<BluetoothLe>();
|
|
handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this));
|
|
mBluetoothLe = handle;
|
|
}
|
|
return mBluetoothLe;
|
|
}
|
|
|
|
ModuleBluetooth::BtProfileHandles ModuleBluetooth::getBtProfileManagerHandles() {
|
|
return std::make_tuple(std::weak_ptr<IBluetooth>(), getBtA2dp().getPtr(), getBtLe().getPtr());
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::getMicMute(bool* _aidl_return __unused) {
|
|
LOG(DEBUG) << __func__ << ": is not supported";
|
|
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::setMicMute(bool in_mute __unused) {
|
|
LOG(DEBUG) << __func__ << ": is not supported";
|
|
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::setAudioPortConfig(const AudioPortConfig& in_requested,
|
|
AudioPortConfig* out_suggested,
|
|
bool* _aidl_return) {
|
|
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 Module::setAudioPortConfigImpl(in_requested, fillConfig, out_suggested, _aidl_return);
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::checkAudioPatchEndpointsMatch(
|
|
const std::vector<AudioPortConfig*>& sources, const std::vector<AudioPortConfig*>& 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<AudioPortExt::mix>().handle; handle > 0) {
|
|
mConnections.insert(std::pair(handle, devicePortId));
|
|
}
|
|
return ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
void ModuleBluetooth::onExternalDeviceConnectionChanged(const AudioPort& audioPort,
|
|
bool connected) {
|
|
if (!connected) mProxies.erase(audioPort.id);
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::createInputStream(
|
|
StreamContext&& context, const SinkMetadata& sinkMetadata,
|
|
const std::vector<MicrophoneInfo>& microphones, std::shared_ptr<StreamIn>* result) {
|
|
CachedProxy proxy;
|
|
RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy));
|
|
return createStreamInstance<StreamInBluetooth>(result, std::move(context), sinkMetadata,
|
|
microphones, getBtProfileManagerHandles(),
|
|
proxy.ptr, proxy.pcmConfig);
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::createOutputStream(
|
|
StreamContext&& context, const SourceMetadata& sourceMetadata,
|
|
const std::optional<AudioOffloadInfo>& offloadInfo, std::shared_ptr<StreamOut>* result) {
|
|
CachedProxy proxy;
|
|
RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy));
|
|
return createStreamInstance<StreamOutBluetooth>(result, std::move(context), sourceMetadata,
|
|
offloadInfo, getBtProfileManagerHandles(),
|
|
proxy.ptr, proxy.pcmConfig);
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::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);
|
|
}
|
|
if (!::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession::IsAidlAvailable()) {
|
|
LOG(ERROR) << __func__ << ": IBluetoothAudioProviderFactory AIDL service not available";
|
|
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
|
|
}
|
|
const auto& devicePort = audioPort->ext.get<AudioPortExt::device>();
|
|
const auto& description = devicePort.device.type;
|
|
// This method must return an error when the device can not be connected.
|
|
if (description.connection == AudioDeviceDescription::CONNECTION_BT_A2DP) {
|
|
bool isA2dpEnabled = false;
|
|
if (!!mBluetoothA2dp) {
|
|
RETURN_STATUS_IF_ERROR((*mBluetoothA2dp).isEnabled(&isA2dpEnabled));
|
|
}
|
|
LOG(DEBUG) << __func__ << ": isA2dpEnabled: " << isA2dpEnabled;
|
|
if (!isA2dpEnabled) return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
|
|
} else if (description.connection == AudioDeviceDescription::CONNECTION_BT_LE) {
|
|
bool isLeEnabled = false;
|
|
if (!!mBluetoothLe) {
|
|
RETURN_STATUS_IF_ERROR((*mBluetoothLe).isEnabled(&isLeEnabled));
|
|
}
|
|
LOG(DEBUG) << __func__ << ": isLeEnabled: " << isLeEnabled;
|
|
if (!isLeEnabled) return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
|
|
} else if (description.connection == AudioDeviceDescription::CONNECTION_WIRELESS &&
|
|
description.type == AudioDeviceType::OUT_HEARING_AID) {
|
|
// Hearing aids can use a number of profiles, no single switch exists.
|
|
} else {
|
|
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));
|
|
// Since the device is already connected and configured by the BT stack, provide
|
|
// the current configuration instead of all possible profiles.
|
|
const auto& pcmConfig = proxy.pcmConfig;
|
|
audioPort->profiles.clear();
|
|
audioPort->profiles.push_back(
|
|
AudioProfile{.format = AudioFormatDescription{.type = AudioFormatType::PCM,
|
|
.pcm = pcmTypeFromBitsPerSample(
|
|
pcmConfig.bitsPerSample)},
|
|
.channelMasks = std::vector<AudioChannelLayout>(
|
|
{channelLayoutFromChannelMode(pcmConfig.channelMode)}),
|
|
.sampleRates = std::vector<int>({pcmConfig.sampleRateHz})});
|
|
LOG(DEBUG) << __func__ << ": " << audioPort->toString();
|
|
return ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::onMasterMuteChanged(bool) {
|
|
LOG(DEBUG) << __func__ << ": is not supported";
|
|
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::onMasterVolumeChanged(float) {
|
|
LOG(DEBUG) << __func__ << ": is not supported";
|
|
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
|
|
}
|
|
|
|
int32_t ModuleBluetooth::getNominalLatencyMs(const AudioPortConfig& portConfig) {
|
|
const auto connectionsIt = mConnections.find(portConfig.ext.get<AudioPortExt::mix>().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);
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::createProxy(const AudioPort& audioPort, int32_t instancePortId,
|
|
CachedProxy& proxy) {
|
|
const bool isInput = audioPort.flags.getTag() == AudioIoFlags::input;
|
|
proxy.ptr = isInput ? std::shared_ptr<BluetoothAudioPortAidl>(
|
|
std::make_shared<BluetoothAudioPortAidlIn>())
|
|
: std::shared_ptr<BluetoothAudioPortAidl>(
|
|
std::make_shared<BluetoothAudioPortAidlOut>());
|
|
const auto& devicePort = audioPort.ext.get<AudioPortExt::device>();
|
|
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));
|
|
return ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
ndk::ScopedAStatus ModuleBluetooth::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 ModuleBluetooth::findOrCreateProxy(const AudioPort& audioPort,
|
|
CachedProxy& proxy) {
|
|
if (auto proxyIt = mProxies.find(audioPort.id); proxyIt != mProxies.end()) {
|
|
proxy = proxyIt->second;
|
|
return ndk::ScopedAStatus::ok();
|
|
}
|
|
return createProxy(audioPort, audioPort.id, proxy);
|
|
}
|
|
|
|
} // namespace aidl::android::hardware::audio::core
|