diff --git a/base.mk b/base.mk index af27de5..2293510 100644 --- a/base.mk +++ b/base.mk @@ -8,6 +8,15 @@ PRODUCT_COPY_FILES := \ frameworks/native/data/etc/android.hardware.bluetooth_le.xml:system/etc/permissions/android.hardware.bluetooth_le.xml \ frameworks/native/data/etc/android.hardware.usb.host.xml:system/etc/permissions/android.hardware.usb.host.xml \ +# Bluetooth Audio (System-side HAL, sysbta) +PRODUCT_PACKAGES += \ + audio.sysbta.default \ + android.hardware.bluetooth.audio-service-system + +PRODUCT_COPY_FILES += \ + device/phh/treble/bluetooth/audio/config/sysbta_audio_policy_configuration.xml:$(TARGET_COPY_OUT_SYSTEM)/etc/sysbta_audio_policy_configuration.xml \ + device/phh/treble/bluetooth/audio/config/sysbta_audio_policy_configuration_7_0.xml:$(TARGET_COPY_OUTY_SYSTEM)/etc/sysbta_audio_policy_configuration_7_0.xml + #Use a more decent APN config PRODUCT_COPY_FILES += \ device/sample/etc/apns-full-conf.xml:system/etc/apns-conf.xml diff --git a/bluetooth/audio/config/sysbta_audio_policy_configuration.xml b/bluetooth/audio/config/sysbta_audio_policy_configuration.xml new file mode 100644 index 0000000..79d1643 --- /dev/null +++ b/bluetooth/audio/config/sysbta_audio_policy_configuration.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bluetooth/audio/config/sysbta_audio_policy_configuration_7_0.xml b/bluetooth/audio/config/sysbta_audio_policy_configuration_7_0.xml new file mode 100644 index 0000000..47228b2 --- /dev/null +++ b/bluetooth/audio/config/sysbta_audio_policy_configuration_7_0.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bluetooth/audio/hal/A2dpOffloadAudioProvider.cpp b/bluetooth/audio/hal/A2dpOffloadAudioProvider.cpp new file mode 100644 index 0000000..2d0d8c9 --- /dev/null +++ b/bluetooth/audio/hal/A2dpOffloadAudioProvider.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 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 "BTAudioProviderA2dpHW" + +#include "A2dpOffloadAudioProvider.h" + +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +A2dpOffloadEncodingAudioProvider::A2dpOffloadEncodingAudioProvider() + : A2dpOffloadAudioProvider() { + session_type_ = SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH; +} + +A2dpOffloadDecodingAudioProvider::A2dpOffloadDecodingAudioProvider() + : A2dpOffloadAudioProvider() { + session_type_ = SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH; +} + +A2dpOffloadAudioProvider::A2dpOffloadAudioProvider() {} + +bool A2dpOffloadAudioProvider::isValid(const SessionType& session_type) { + return (session_type == session_type_); +} + +ndk::ScopedAStatus A2dpOffloadAudioProvider::startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, DataMQDesc* _aidl_return) { + if (audio_config.getTag() != AudioConfiguration::a2dpConfig) { + LOG(WARNING) << __func__ << " - Invalid Audio Configuration=" + << audio_config.toString(); + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (!BluetoothAudioCodecs::IsOffloadCodecConfigurationValid( + session_type_, audio_config.get())) { + LOG(WARNING) << __func__ << " - Invalid Audio Configuration=" + << audio_config.toString(); + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + return BluetoothAudioProvider::startSession( + host_if, audio_config, latency_modes, _aidl_return); +} + +ndk::ScopedAStatus A2dpOffloadAudioProvider::onSessionReady( + DataMQDesc* _aidl_return) { + *_aidl_return = DataMQDesc(); + BluetoothAudioSessionReport::OnSessionStarted( + session_type_, stack_iface_, nullptr, *audio_config_, latency_modes_); + return ndk::ScopedAStatus::ok(); +} + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/A2dpOffloadAudioProvider.h b/bluetooth/audio/hal/A2dpOffloadAudioProvider.h new file mode 100644 index 0000000..e6f188b --- /dev/null +++ b/bluetooth/audio/hal/A2dpOffloadAudioProvider.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include "BluetoothAudioProvider.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +class A2dpOffloadAudioProvider : public BluetoothAudioProvider { + public: + A2dpOffloadAudioProvider(); + + bool isValid(const SessionType& session_type) override; + + ndk::ScopedAStatus startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, + DataMQDesc* _aidl_return); + + private: + ndk::ScopedAStatus onSessionReady(DataMQDesc* _aidl_return) override; +}; + +class A2dpOffloadEncodingAudioProvider : public A2dpOffloadAudioProvider { + public: + A2dpOffloadEncodingAudioProvider(); +}; + +class A2dpOffloadDecodingAudioProvider : public A2dpOffloadAudioProvider { + public: + A2dpOffloadDecodingAudioProvider(); +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/A2dpSoftwareAudioProvider.cpp b/bluetooth/audio/hal/A2dpSoftwareAudioProvider.cpp new file mode 100644 index 0000000..bd2da95 --- /dev/null +++ b/bluetooth/audio/hal/A2dpSoftwareAudioProvider.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 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 "BTAudioProviderA2dpSW" + +#include "A2dpSoftwareAudioProvider.h" + +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +// Here the buffer size is based on SBC +static constexpr uint32_t kPcmFrameSize = 4; // 16 bits per sample / stereo +// SBC is 128, and here we choose the LCM of 16, 24, and 32 +static constexpr uint32_t kPcmFrameCount = 96; +static constexpr uint32_t kRtpFrameSize = kPcmFrameSize * kPcmFrameCount; +// The max counts by 1 tick (20ms) for SBC is about 7. Since using 96 for the +// PCM counts, here we just choose a greater number +static constexpr uint32_t kRtpFrameCount = 10; +static constexpr uint32_t kBufferSize = kRtpFrameSize * kRtpFrameCount; +static constexpr uint32_t kBufferCount = 2; // double buffer +static constexpr uint32_t kDataMqSize = kBufferSize * kBufferCount; + +A2dpSoftwareEncodingAudioProvider::A2dpSoftwareEncodingAudioProvider() + : A2dpSoftwareAudioProvider() { + session_type_ = SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH; +} + +A2dpSoftwareDecodingAudioProvider::A2dpSoftwareDecodingAudioProvider() + : A2dpSoftwareAudioProvider() { + session_type_ = SessionType::A2DP_SOFTWARE_DECODING_DATAPATH; +} + +A2dpSoftwareAudioProvider::A2dpSoftwareAudioProvider() + : BluetoothAudioProvider(), data_mq_(nullptr) { + LOG(INFO) << __func__ << " - size of audio buffer " << kDataMqSize + << " byte(s)"; + std::unique_ptr data_mq( + new DataMQ(kDataMqSize, /* EventFlag */ true)); + if (data_mq && data_mq->isValid()) { + data_mq_ = std::move(data_mq); + } else { + ALOGE_IF(!data_mq, "failed to allocate data MQ"); + ALOGE_IF(data_mq && !data_mq->isValid(), "data MQ is invalid"); + } +} + +bool A2dpSoftwareAudioProvider::isValid(const SessionType& sessionType) { + return (sessionType == session_type_ && data_mq_ && data_mq_->isValid()); +} + +ndk::ScopedAStatus A2dpSoftwareAudioProvider::startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, DataMQDesc* _aidl_return) { + if (audio_config.getTag() != AudioConfiguration::pcmConfig) { + LOG(WARNING) << __func__ << " - Invalid Audio Configuration=" + << audio_config.toString(); + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + const PcmConfiguration& pcm_config = + audio_config.get(); + if (!BluetoothAudioCodecs::IsSoftwarePcmConfigurationValid(pcm_config)) { + LOG(WARNING) << __func__ << " - Unsupported PCM Configuration=" + << pcm_config.toString(); + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + return BluetoothAudioProvider::startSession( + host_if, audio_config, latency_modes, _aidl_return); +} + +ndk::ScopedAStatus A2dpSoftwareAudioProvider::onSessionReady( + DataMQDesc* _aidl_return) { + if (data_mq_ == nullptr || !data_mq_->isValid()) { + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + *_aidl_return = data_mq_->dupeDesc(); + auto desc = data_mq_->dupeDesc(); + BluetoothAudioSessionReport::OnSessionStarted( + session_type_, stack_iface_, &desc, *audio_config_, latency_modes_); + return ndk::ScopedAStatus::ok(); +} + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/A2dpSoftwareAudioProvider.h b/bluetooth/audio/hal/A2dpSoftwareAudioProvider.h new file mode 100644 index 0000000..3ebecf2 --- /dev/null +++ b/bluetooth/audio/hal/A2dpSoftwareAudioProvider.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include "BluetoothAudioProvider.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +class A2dpSoftwareAudioProvider : public BluetoothAudioProvider { + public: + A2dpSoftwareAudioProvider(); + + bool isValid(const SessionType& sessionType) override; + + ndk::ScopedAStatus startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, + DataMQDesc* _aidl_return); + + private: + // audio data queue for software encoding + std::unique_ptr data_mq_; + + ndk::ScopedAStatus onSessionReady(DataMQDesc* _aidl_return) override; +}; + +class A2dpSoftwareEncodingAudioProvider : public A2dpSoftwareAudioProvider { + public: + A2dpSoftwareEncodingAudioProvider(); +}; + +class A2dpSoftwareDecodingAudioProvider : public A2dpSoftwareAudioProvider { + public: + A2dpSoftwareDecodingAudioProvider(); +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/Android.bp b/bluetooth/audio/hal/Android.bp new file mode 100644 index 0000000..e52a6b7 --- /dev/null +++ b/bluetooth/audio/hal/Android.bp @@ -0,0 +1,92 @@ +cc_library_shared { + name: "android.hardware.bluetooth.audio-system-impl", + vintf_fragments: ["bluetooth_audio.xml"], + srcs: [ + "BluetoothAudioProvider.cpp", + "BluetoothAudioProviderFactory.cpp", + "A2dpOffloadAudioProvider.cpp", + "A2dpSoftwareAudioProvider.cpp", + "HearingAidAudioProvider.cpp", + "LeAudioOffloadAudioProvider.cpp", + "LeAudioSoftwareAudioProvider.cpp", + "service.cpp", + ], + export_include_dirs: ["."], + header_libs: ["libhardware_headers"], + shared_libs: [ + "libbase", + "libbinder_ndk", + "libcutils", + "libfmq", + "liblog", + "android.hardware.bluetooth.audio-V2-ndk", + "libbluetooth_audio_session_aidl_system", + ], +} + +cc_binary { + name: "android.hardware.bluetooth.audio-service-system", + vintf_fragments: ["bluetooth_audio_system.xml"], + init_rc: ["android.hardware.bluetooth.audio-service-system.rc"], + relative_install_path: "hw", + srcs: [ + "BluetoothAudioProvider.cpp", + "BluetoothAudioProviderFactory.cpp", + "A2dpOffloadAudioProvider.cpp", + "A2dpSoftwareAudioProvider.cpp", + "HearingAidAudioProvider.cpp", + "LeAudioOffloadAudioProvider.cpp", + "LeAudioSoftwareAudioProvider.cpp", + "Device.cpp", + "DevicesFactory.cpp", + "ParametersUtil.cpp", + "PrimaryDevice.cpp", + "Stream.cpp", + "StreamIn.cpp", + "StreamOut.cpp", + "service_system.cpp", + ], + header_libs: [ + "libhardware_headers", + "android.hardware.audio.common.util@all-versions", + "libaudioutils_headers", + "libaudio_system_headers", + "libmedia_headers", + "libmediautils_headers", + ], + shared_libs: [ + "libbase", + "libbinder", + "libbinder_ndk", + "libcutils", + "libfmq", + "liblog", + "android.hardware.bluetooth.audio-V2-ndk", + "libbluetooth_audio_session_aidl_system", + "libfmq", + "libhardware", + "libhidlbase", + "liblog", + "libmedia_helper", + "libmediautils_vendor", + "libmemunreachable", + "libutils", + "android.hardware.audio.common-util", + "android.hardware.audio@6.0", +// "android.hardware.audio@7.1", + "android.hardware.audio@6.0-util", + "android.hardware.audio.common@6.0", +// "android.hardware.audio.common@6.0-enums", + "android.hardware.audio.common@6.0-util", + ], + static_libs: [ + "libaudiofoundation", + ], + cflags: [ + "-DMAJOR_VERSION=6", + "-DMINOR_VERSION=0", + "-DCOMMON_TYPES_MINOR_VERSION=0", + "-DCORE_TYPES_MINOR_VERSION=0", + "-include common/all-versions/VersionMacro.h", + ], +} diff --git a/bluetooth/audio/hal/BluetoothAudioProvider.cpp b/bluetooth/audio/hal/BluetoothAudioProvider.cpp new file mode 100644 index 0000000..2a88959 --- /dev/null +++ b/bluetooth/audio/hal/BluetoothAudioProvider.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2022 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 "BTAudioProviderStub" + +#include "BluetoothAudioProvider.h" + +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +BluetoothAudioProvider::BluetoothAudioProvider() { + death_recipient_ = ::ndk::ScopedAIBinder_DeathRecipient( + AIBinder_DeathRecipient_new(binderDiedCallbackAidl)); +} + +ndk::ScopedAStatus BluetoothAudioProvider::startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latencyModes, + DataMQDesc* _aidl_return) { + if (host_if == nullptr) { + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + latency_modes_ = latencyModes; + audio_config_ = std::make_unique(audio_config); + stack_iface_ = host_if; + is_binder_died = false; + + AIBinder_linkToDeath(stack_iface_->asBinder().get(), death_recipient_.get(), + this); + + onSessionReady(_aidl_return); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothAudioProvider::endSession() { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_); + + if (stack_iface_ != nullptr) { + BluetoothAudioSessionReport::OnSessionEnded(session_type_); + + if (!is_binder_died) { + AIBinder_unlinkToDeath(stack_iface_->asBinder().get(), + death_recipient_.get(), this); + } + } else { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + } + + stack_iface_ = nullptr; + audio_config_ = nullptr; + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothAudioProvider::streamStarted( + BluetoothAudioStatus status) { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << ", status=" << toString(status); + + if (stack_iface_ != nullptr) { + BluetoothAudioSessionReport::ReportControlStatus(session_type_, true, + status); + } else { + LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) + << ", status=" << toString(status) << " has NO session"; + } + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothAudioProvider::streamSuspended( + BluetoothAudioStatus status) { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << ", status=" << toString(status); + + if (stack_iface_ != nullptr) { + BluetoothAudioSessionReport::ReportControlStatus(session_type_, false, + status); + } else { + LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) + << ", status=" << toString(status) << " has NO session"; + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothAudioProvider::updateAudioConfiguration( + const AudioConfiguration& audio_config) { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_); + + if (stack_iface_ == nullptr || audio_config_ == nullptr) { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + if (audio_config.getTag() != audio_config_->getTag()) { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << " audio config type is not match"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + audio_config_ = std::make_unique(audio_config); + BluetoothAudioSessionReport::ReportAudioConfigChanged(session_type_, + *audio_config_); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothAudioProvider::setLowLatencyModeAllowed( + bool allowed) { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_); + + if (stack_iface_ == nullptr) { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + LOG(INFO) << __func__ << " - allowed " << allowed; + BluetoothAudioSessionReport::ReportLowLatencyModeAllowedChanged( + session_type_, allowed); + return ndk::ScopedAStatus::ok(); +} + +void BluetoothAudioProvider::binderDiedCallbackAidl(void* ptr) { + LOG(ERROR) << __func__ << " - BluetoothAudio Service died"; + auto provider = static_cast(ptr); + if (provider == nullptr) { + LOG(ERROR) << __func__ << ": Null AudioProvider HAL died"; + return; + } + provider->is_binder_died = true; + provider->endSession(); +} + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl \ No newline at end of file diff --git a/bluetooth/audio/hal/BluetoothAudioProvider.h b/bluetooth/audio/hal/BluetoothAudioProvider.h new file mode 100644 index 0000000..dbfff7d --- /dev/null +++ b/bluetooth/audio/hal/BluetoothAudioProvider.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 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. + */ +#pragma once + +#include +#include +#include +#include + +using ::aidl::android::hardware::common::fmq::MQDescriptor; +using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite; +using ::android::AidlMessageQueue; + +using MqDataType = int8_t; +using MqDataMode = SynchronizedReadWrite; +using DataMQ = AidlMessageQueue; +using DataMQDesc = MQDescriptor; + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +class BluetoothAudioProvider : public BnBluetoothAudioProvider { + public: + BluetoothAudioProvider(); + ndk::ScopedAStatus startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, + DataMQDesc* _aidl_return); + ndk::ScopedAStatus endSession(); + ndk::ScopedAStatus streamStarted(BluetoothAudioStatus status); + ndk::ScopedAStatus streamSuspended(BluetoothAudioStatus status); + ndk::ScopedAStatus updateAudioConfiguration( + const AudioConfiguration& audio_config); + ndk::ScopedAStatus setLowLatencyModeAllowed(bool allowed); + + virtual bool isValid(const SessionType& sessionType) = 0; + + protected: + virtual ndk::ScopedAStatus onSessionReady(DataMQDesc* _aidl_return) = 0; + static void binderDiedCallbackAidl(void* cookie_ptr); + + ::ndk::ScopedAIBinder_DeathRecipient death_recipient_; + + std::shared_ptr stack_iface_; + std::unique_ptr audio_config_ = nullptr; + SessionType session_type_; + std::vector latency_modes_; + bool is_binder_died = false; +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/BluetoothAudioProviderFactory.cpp b/bluetooth/audio/hal/BluetoothAudioProviderFactory.cpp new file mode 100644 index 0000000..91731d4 --- /dev/null +++ b/bluetooth/audio/hal/BluetoothAudioProviderFactory.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2022 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 "BTAudioProviderFactoryAIDL" + +#include "BluetoothAudioProviderFactory.h" + +#include +#include + +#include "A2dpOffloadAudioProvider.h" +#include "A2dpSoftwareAudioProvider.h" +#include "BluetoothAudioProvider.h" +#include "HearingAidAudioProvider.h" +#include "LeAudioOffloadAudioProvider.h" +#include "LeAudioSoftwareAudioProvider.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +BluetoothAudioProviderFactory::BluetoothAudioProviderFactory() {} + +ndk::ScopedAStatus BluetoothAudioProviderFactory::openProvider( + const SessionType session_type, + std::shared_ptr* _aidl_return) { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type); + std::shared_ptr provider = nullptr; + + switch (session_type) { + case SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH: + provider = ndk::SharedRefBase::make(); + break; + case SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH: + provider = ndk::SharedRefBase::make(); + break; + case SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH: + provider = ndk::SharedRefBase::make(); + break; + case SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH: + provider = ndk::SharedRefBase::make(); + break; + case SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH: + provider = ndk::SharedRefBase::make(); + break; + case SessionType::LE_AUDIO_SOFTWARE_DECODING_DATAPATH: + provider = ndk::SharedRefBase::make(); + break; + case SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH: + provider = ndk::SharedRefBase::make(); + break; + case SessionType::LE_AUDIO_BROADCAST_SOFTWARE_ENCODING_DATAPATH: + provider = + ndk::SharedRefBase::make(); + break; + case SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH: + provider = + ndk::SharedRefBase::make(); + break; + case SessionType::A2DP_SOFTWARE_DECODING_DATAPATH: + provider = ndk::SharedRefBase::make(); + break; + case SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH: + provider = ndk::SharedRefBase::make(); + break; + default: + provider = nullptr; + break; + } + + if (provider == nullptr || !provider->isValid(session_type)) { + provider = nullptr; + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + *_aidl_return = provider; + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothAudioProviderFactory::getProviderCapabilities( + const SessionType session_type, + std::vector* _aidl_return) { + if (session_type == SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type == SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH) { + auto codec_capabilities = + BluetoothAudioCodecs::GetA2dpOffloadCodecCapabilities(session_type); + _aidl_return->resize(codec_capabilities.size()); + for (int i = 0; i < codec_capabilities.size(); i++) { + _aidl_return->at(i).set( + codec_capabilities[i]); + } + } else if (session_type == + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type == + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH || + session_type == + SessionType:: + LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH) { + std::vector db_codec_capabilities = + BluetoothAudioCodecs::GetLeAudioOffloadCodecCapabilities(session_type); + if (db_codec_capabilities.size()) { + _aidl_return->resize(db_codec_capabilities.size()); + for (int i = 0; i < db_codec_capabilities.size(); ++i) { + _aidl_return->at(i).set( + db_codec_capabilities[i]); + } + } + } else if (session_type != SessionType::UNKNOWN) { + auto pcm_capabilities = BluetoothAudioCodecs::GetSoftwarePcmCapabilities(); + _aidl_return->resize(pcm_capabilities.size()); + for (int i = 0; i < pcm_capabilities.size(); i++) { + _aidl_return->at(i).set( + pcm_capabilities[i]); + } + } + + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type) + << " supports " << _aidl_return->size() << " codecs"; + return ndk::ScopedAStatus::ok(); +} + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl \ No newline at end of file diff --git a/bluetooth/audio/hal/BluetoothAudioProviderFactory.h b/bluetooth/audio/hal/BluetoothAudioProviderFactory.h new file mode 100644 index 0000000..b38cfd2 --- /dev/null +++ b/bluetooth/audio/hal/BluetoothAudioProviderFactory.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +class BluetoothAudioProviderFactory : public BnBluetoothAudioProviderFactory { + public: + BluetoothAudioProviderFactory(); + + ndk::ScopedAStatus openProvider( + const SessionType session_type, + std::shared_ptr* _aidl_return) override; + + ndk::ScopedAStatus getProviderCapabilities( + const SessionType session_type, + std::vector* _aidl_return) override; +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/Device.cpp b/bluetooth/audio/hal/Device.cpp new file mode 100644 index 0000000..85b2768 --- /dev/null +++ b/bluetooth/audio/hal/Device.cpp @@ -0,0 +1,628 @@ +/* + * Copyright (C) 2018 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 "DeviceHAL" + +#include "Device.h" +#include "common/all-versions/default/EffectMap.h" +#include "StreamIn.h" +#include "StreamOut.h" +#include "Util.h" + +//#define LOG_NDEBUG 0 + +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +using ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::implementation::HidlUtils; +namespace util { +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::util; +} + +Device::Device(audio_hw_device_t* device) : mIsClosed(false), mDevice(device) {} + +Device::~Device() { + (void)doClose(); + mDevice = nullptr; +} + +Result Device::analyzeStatus(const char* funcName, int status, + const std::vector& ignoreErrors) { + return util::analyzeStatus("Device", funcName, status, ignoreErrors); +} + +void Device::closeInputStream(audio_stream_in_t* stream) { + mDevice->close_input_stream(mDevice, stream); + LOG_ALWAYS_FATAL_IF(mOpenedStreamsCount == 0, "mOpenedStreamsCount is already 0"); + --mOpenedStreamsCount; +} + +void Device::closeOutputStream(audio_stream_out_t* stream) { + mDevice->close_output_stream(mDevice, stream); + LOG_ALWAYS_FATAL_IF(mOpenedStreamsCount == 0, "mOpenedStreamsCount is already 0"); + --mOpenedStreamsCount; +} + +char* Device::halGetParameters(const char* keys) { + return mDevice->get_parameters(mDevice, keys); +} + +int Device::halSetParameters(const char* keysAndValues) { + return mDevice->set_parameters(mDevice, keysAndValues); +} + +// Methods from ::android::hardware::audio::CPP_VERSION::IDevice follow. +Return Device::initCheck() { + return analyzeStatus("init_check", mDevice->init_check(mDevice)); +} + +Return Device::setMasterVolume(float volume) { + if (mDevice->set_master_volume == NULL) { + return Result::NOT_SUPPORTED; + } + if (!util::isGainNormalized(volume)) { + ALOGW("Can not set a master volume (%f) outside [0,1]", volume); + return Result::INVALID_ARGUMENTS; + } + return analyzeStatus("set_master_volume", mDevice->set_master_volume(mDevice, volume), + {ENOSYS} /*ignore*/); +} + +Return Device::getMasterVolume(getMasterVolume_cb _hidl_cb) { + Result retval(Result::NOT_SUPPORTED); + float volume = 0; + if (mDevice->get_master_volume != NULL) { + retval = analyzeStatus("get_master_volume", mDevice->get_master_volume(mDevice, &volume), + {ENOSYS} /*ignore*/); + } + _hidl_cb(retval, volume); + return Void(); +} + +Return Device::setMicMute(bool mute) { + return analyzeStatus("set_mic_mute", mDevice->set_mic_mute(mDevice, mute), {ENOSYS} /*ignore*/); +} + +Return Device::getMicMute(getMicMute_cb _hidl_cb) { + bool mute = false; + Result retval = analyzeStatus("get_mic_mute", mDevice->get_mic_mute(mDevice, &mute), + {ENOSYS} /*ignore*/); + _hidl_cb(retval, mute); + return Void(); +} + +Return Device::setMasterMute(bool mute) { + Result retval(Result::NOT_SUPPORTED); + if (mDevice->set_master_mute != NULL) { + retval = analyzeStatus("set_master_mute", mDevice->set_master_mute(mDevice, mute), + {ENOSYS} /*ignore*/); + } + return retval; +} + +Return Device::getMasterMute(getMasterMute_cb _hidl_cb) { + Result retval(Result::NOT_SUPPORTED); + bool mute = false; + if (mDevice->get_master_mute != NULL) { + retval = analyzeStatus("get_master_mute", mDevice->get_master_mute(mDevice, &mute), + {ENOSYS} /*ignore*/); + } + _hidl_cb(retval, mute); + return Void(); +} + +Return Device::getInputBufferSize(const AudioConfig& config, getInputBufferSize_cb _hidl_cb) { + audio_config_t halConfig; + Result retval(Result::INVALID_ARGUMENTS); + uint64_t bufferSize = 0; + if (HidlUtils::audioConfigToHal(config, &halConfig) == NO_ERROR) { + size_t halBufferSize = mDevice->get_input_buffer_size(mDevice, &halConfig); + if (halBufferSize != 0) { + retval = Result::OK; + bufferSize = halBufferSize; + } + } + _hidl_cb(retval, bufferSize); + return Void(); +} + +std::tuple> Device::openOutputStreamCore(int32_t ioHandle, + const DeviceAddress& device, + const AudioConfig& config, + const AudioOutputFlags& flags, + AudioConfig* suggestedConfig) { + audio_config_t halConfig; + if (HidlUtils::audioConfigToHal(config, &halConfig) != NO_ERROR) { + return {Result::INVALID_ARGUMENTS, nullptr}; + } + audio_stream_out_t* halStream; + audio_devices_t halDevice; + char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN]; + if (CoreUtils::deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) { + return {Result::INVALID_ARGUMENTS, nullptr}; + } + audio_output_flags_t halFlags; + if (CoreUtils::audioOutputFlagsToHal(flags, &halFlags) != NO_ERROR) { + return {Result::INVALID_ARGUMENTS, nullptr}; + } + ALOGV("open_output_stream handle: %d devices: %x flags: %#x " + "srate: %d format %#x channels %x address %s", + ioHandle, halDevice, halFlags, halConfig.sample_rate, halConfig.format, + halConfig.channel_mask, halDeviceAddress); + int status = mDevice->open_output_stream(mDevice, ioHandle, halDevice, halFlags, &halConfig, + &halStream, halDeviceAddress); + ALOGV("open_output_stream status %d stream %p", status, halStream); + sp streamOut; + if (status == OK) { + streamOut = new StreamOut(this, halStream); + ++mOpenedStreamsCount; + } + status_t convertStatus = + HidlUtils::audioConfigFromHal(halConfig, false /*isInput*/, suggestedConfig); + ALOGW_IF(convertStatus != OK, "%s: suggested config with incompatible fields", __func__); + return {analyzeStatus("open_output_stream", status, {EINVAL} /*ignore*/), streamOut}; +} + +std::tuple> Device::openInputStreamCore( + int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config, + const AudioInputFlags& flags, AudioSource source, AudioConfig* suggestedConfig) { + audio_config_t halConfig; + if (HidlUtils::audioConfigToHal(config, &halConfig) != NO_ERROR) { + return {Result::INVALID_ARGUMENTS, nullptr}; + } + audio_stream_in_t* halStream; + audio_devices_t halDevice; + char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN]; + if (CoreUtils::deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) { + return {Result::INVALID_ARGUMENTS, nullptr}; + } + audio_input_flags_t halFlags; + audio_source_t halSource; + if (CoreUtils::audioInputFlagsToHal(flags, &halFlags) != NO_ERROR || + HidlUtils::audioSourceToHal(source, &halSource) != NO_ERROR) { + return {Result::INVALID_ARGUMENTS, nullptr}; + } + ALOGV("open_input_stream handle: %d devices: %x flags: %#x " + "srate: %d format %#x channels %x address %s source %d", + ioHandle, halDevice, halFlags, halConfig.sample_rate, halConfig.format, + halConfig.channel_mask, halDeviceAddress, halSource); + int status = mDevice->open_input_stream(mDevice, ioHandle, halDevice, &halConfig, &halStream, + halFlags, halDeviceAddress, halSource); + ALOGV("open_input_stream status %d stream %p", status, halStream); + sp streamIn; + if (status == OK) { + streamIn = new StreamIn(this, halStream); + ++mOpenedStreamsCount; + } + status_t convertStatus = + HidlUtils::audioConfigFromHal(halConfig, true /*isInput*/, suggestedConfig); + ALOGW_IF(convertStatus != OK, "%s: suggested config with incompatible fields", __func__); + return {analyzeStatus("open_input_stream", status, {EINVAL} /*ignore*/), streamIn}; +} + +#if MAJOR_VERSION == 2 +Return Device::openOutputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, AudioOutputFlags flags, + openOutputStream_cb _hidl_cb) { + AudioConfig suggestedConfig; + auto [result, streamOut] = + openOutputStreamCore(ioHandle, device, config, flags, &suggestedConfig); + _hidl_cb(result, streamOut, suggestedConfig); + return Void(); +} + +Return Device::openInputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, AudioInputFlags flags, + AudioSource source, openInputStream_cb _hidl_cb) { + AudioConfig suggestedConfig; + auto [result, streamIn] = + openInputStreamCore(ioHandle, device, config, flags, source, &suggestedConfig); + _hidl_cb(result, streamIn, suggestedConfig); + return Void(); +} + +#elif MAJOR_VERSION >= 4 +std::tuple, AudioConfig> Device::openOutputStreamImpl( + int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config, + const SourceMetadata& sourceMetadata, +#if MAJOR_VERSION <= 6 + AudioOutputFlags flags) { + if (status_t status = CoreUtils::sourceMetadataToHal(sourceMetadata, nullptr); + status != NO_ERROR) { +#else + const AudioOutputFlags& flags) { + if (status_t status = CoreUtils::sourceMetadataToHalV7(sourceMetadata, + false /*ignoreNonVendorTags*/, nullptr); + status != NO_ERROR) { +#endif + return {analyzeStatus("sourceMetadataToHal", status), nullptr, {}}; + } + AudioConfig suggestedConfig; + auto [result, streamOut] = + openOutputStreamCore(ioHandle, device, config, flags, &suggestedConfig); + if (streamOut) { + streamOut->updateSourceMetadata(sourceMetadata); + } + return {result, streamOut, suggestedConfig}; +} + +Return Device::openOutputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, +#if MAJOR_VERSION <= 6 + AudioOutputFlags flags, +#else + const AudioOutputFlags& flags, +#endif + const SourceMetadata& sourceMetadata, + openOutputStream_cb _hidl_cb) { + auto [result, streamOut, suggestedConfig] = + openOutputStreamImpl(ioHandle, device, config, sourceMetadata, flags); + _hidl_cb(result, streamOut, suggestedConfig); + return Void(); +} + +std::tuple, AudioConfig> Device::openInputStreamImpl( + int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config, +#if MAJOR_VERSION <= 6 + AudioInputFlags flags, +#else + const AudioInputFlags& flags, +#endif + const SinkMetadata& sinkMetadata) { + if (sinkMetadata.tracks.size() == 0) { + // This should never happen, the framework must not create as stream + // if there is no client + ALOGE("openInputStream called without tracks connected"); + return {Result::INVALID_ARGUMENTS, nullptr, AudioConfig{}}; + } +#if MAJOR_VERSION <= 6 + if (status_t status = CoreUtils::sinkMetadataToHal(sinkMetadata, nullptr); status != NO_ERROR) { +#else + if (status_t status = CoreUtils::sinkMetadataToHalV7(sinkMetadata, + false /*ignoreNonVendorTags*/, nullptr); + status != NO_ERROR) { +#endif + return {analyzeStatus("sinkMetadataToHal", status), nullptr, AudioConfig{}}; + } + // Pick the first one as the main. + AudioSource source = sinkMetadata.tracks[0].source; + AudioConfig suggestedConfig; + auto [result, streamIn] = + openInputStreamCore(ioHandle, device, config, flags, source, &suggestedConfig); + if (streamIn) { + streamIn->updateSinkMetadata(sinkMetadata); + } + return {result, streamIn, suggestedConfig}; +} + +Return Device::openInputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, +#if MAJOR_VERSION <= 6 + AudioInputFlags flags, +#else + const AudioInputFlags& flags, +#endif + const SinkMetadata& sinkMetadata, + openInputStream_cb _hidl_cb) { + auto [result, streamIn, suggestedConfig] = + openInputStreamImpl(ioHandle, device, config, flags, sinkMetadata); + _hidl_cb(result, streamIn, suggestedConfig); + return Void(); +} +#endif /* MAJOR_VERSION */ + +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 +Return Device::openOutputStream_7_1(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, const AudioOutputFlags& flags, + const SourceMetadata& sourceMetadata, + openOutputStream_7_1_cb _hidl_cb) { + auto [result, streamOut, suggestedConfig] = + openOutputStreamImpl(ioHandle, device, config, sourceMetadata, flags); + _hidl_cb(result, streamOut, suggestedConfig); + return Void(); +} +#endif // V7.1 + +Return Device::supportsAudioPatches() { + return version() >= AUDIO_DEVICE_API_VERSION_3_0; +} + +Return Device::createAudioPatch(const hidl_vec& sources, + const hidl_vec& sinks, + createAudioPatch_cb _hidl_cb) { + auto [retval, patch] = createOrUpdateAudioPatch(AudioPatchHandle{}, sources, sinks); + _hidl_cb(retval, patch); + return Void(); +} + +std::tuple Device::createOrUpdateAudioPatch( + AudioPatchHandle patch, const hidl_vec& sources, + const hidl_vec& sinks) { + Result retval(Result::NOT_SUPPORTED); + if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { + audio_patch_handle_t halPatch = static_cast(patch); + std::unique_ptr halSources; + if (status_t status = HidlUtils::audioPortConfigsToHal(sources, &halSources); + status != NO_ERROR) { + return {analyzeStatus("audioPortConfigsToHal;sources", status), patch}; + } + std::unique_ptr halSinks; + if (status_t status = HidlUtils::audioPortConfigsToHal(sinks, &halSinks); + status != NO_ERROR) { + return {analyzeStatus("audioPortConfigsToHal;sinks", status), patch}; + } + retval = analyzeStatus("create_audio_patch", + mDevice->create_audio_patch(mDevice, sources.size(), &halSources[0], + sinks.size(), &halSinks[0], &halPatch)); + if (retval == Result::OK) { + patch = static_cast(halPatch); + } + } + return {retval, patch}; +} + +Return Device::releaseAudioPatch(int32_t patch) { + if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { + return analyzeStatus( + "release_audio_patch", + mDevice->release_audio_patch(mDevice, static_cast(patch))); + } + return Result::NOT_SUPPORTED; +} + +template +Return Device::getAudioPortImpl(const AudioPort& port, getAudioPort_cb _hidl_cb, + int (*halGetter)(audio_hw_device_t*, HalPort*), + const char* halGetterName) { + HalPort halPort; + if (status_t status = HidlUtils::audioPortToHal(port, &halPort); status != NO_ERROR) { + _hidl_cb(analyzeStatus("audioPortToHal", status), port); + return Void(); + } + Result retval = analyzeStatus(halGetterName, halGetter(mDevice, &halPort)); + AudioPort resultPort = port; + if (retval == Result::OK) { + if (status_t status = HidlUtils::audioPortFromHal(halPort, &resultPort); + status != NO_ERROR) { + _hidl_cb(analyzeStatus("audioPortFromHal", status), port); + return Void(); + } + } + _hidl_cb(retval, resultPort); + return Void(); +} + +#if MAJOR_VERSION <= 6 +Return Device::getAudioPort(const AudioPort& port, getAudioPort_cb _hidl_cb) { + return getAudioPortImpl(port, _hidl_cb, mDevice->get_audio_port, "get_audio_port"); +} +#else +Return Device::getAudioPort(const AudioPort& port, getAudioPort_cb _hidl_cb) { + if (version() >= AUDIO_DEVICE_API_VERSION_3_2) { + // get_audio_port_v7 is mandatory if legacy HAL support this API version. + return getAudioPortImpl(port, _hidl_cb, mDevice->get_audio_port_v7, "get_audio_port_v7"); + } else { + return getAudioPortImpl(port, _hidl_cb, mDevice->get_audio_port, "get_audio_port"); + } +} +#endif + +Return Device::setAudioPortConfig(const AudioPortConfig& config) { + if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { + struct audio_port_config halPortConfig; + if (status_t status = HidlUtils::audioPortConfigToHal(config, &halPortConfig); + status != NO_ERROR) { + return analyzeStatus("audioPortConfigToHal", status); + } + return analyzeStatus("set_audio_port_config", + mDevice->set_audio_port_config(mDevice, &halPortConfig)); + } + return Result::NOT_SUPPORTED; +} + +#if MAJOR_VERSION == 2 +Return Device::getHwAvSync() { + int halHwAvSync; + Result retval = getParam(AudioParameter::keyHwAvSync, &halHwAvSync); + return retval == Result::OK ? halHwAvSync : AUDIO_HW_SYNC_INVALID; +} +#elif MAJOR_VERSION >= 4 +Return Device::getHwAvSync(getHwAvSync_cb _hidl_cb) { + int halHwAvSync; + Result retval = getParam(AudioParameter::keyHwAvSync, &halHwAvSync); + _hidl_cb(retval, halHwAvSync); + return Void(); +} +#endif + +Return Device::setScreenState(bool turnedOn) { + return setParam(AudioParameter::keyScreenState, turnedOn); +} + +#if MAJOR_VERSION == 2 +Return Device::getParameters(const hidl_vec& keys, getParameters_cb _hidl_cb) { + getParametersImpl({}, keys, _hidl_cb); + return Void(); +} + +Return Device::setParameters(const hidl_vec& parameters) { + return setParametersImpl({} /* context */, parameters); +} +#elif MAJOR_VERSION >= 4 +Return Device::getParameters(const hidl_vec& context, + const hidl_vec& keys, getParameters_cb _hidl_cb) { + getParametersImpl(context, keys, _hidl_cb); + return Void(); +} +Return Device::setParameters(const hidl_vec& context, + const hidl_vec& parameters) { + return setParametersImpl(context, parameters); +} +#endif + +#if MAJOR_VERSION == 2 +Return Device::debugDump(const hidl_handle& fd) { + return debug(fd, {}); +} +#endif + +Return Device::debug(const hidl_handle& fd, const hidl_vec& options) { + if (fd.getNativeHandle() != nullptr && fd->numFds == 1) { + const int fd0 = fd->data[0]; + bool dumpMem = false; + bool unreachableMemory = false; + for (const auto& option : options) { + if (option == "-m") { + dumpMem = true; + } else if (option == "--unreachable") { + unreachableMemory = true; + } + } + + if (dumpMem) { + dprintf(fd0, "\nDumping memory:\n"); + std::string s = dumpMemoryAddresses(100 /* limit */); + write(fd0, s.c_str(), s.size()); + } + if (unreachableMemory) { + dprintf(fd0, "\nDumping unreachable memory:\n"); + // TODO - should limit be an argument parameter? + std::string s = GetUnreachableMemoryString(true /* contents */, 100 /* limit */); + write(fd0, s.c_str(), s.size()); + } + + analyzeStatus("dump", mDevice->dump(mDevice, fd0)); + } + return Void(); +} + +#if MAJOR_VERSION >= 4 +Return Device::getMicrophones(getMicrophones_cb _hidl_cb) { + Result retval = Result::NOT_SUPPORTED; + size_t actual_mics = AUDIO_MICROPHONE_MAX_COUNT; + audio_microphone_characteristic_t mic_array[AUDIO_MICROPHONE_MAX_COUNT]; + + hidl_vec microphones; + if (mDevice->get_microphones != NULL && + mDevice->get_microphones(mDevice, &mic_array[0], &actual_mics) == 0) { + microphones.resize(actual_mics); + for (size_t i = 0; i < actual_mics; ++i) { + (void)CoreUtils::microphoneInfoFromHal(mic_array[i], µphones[i]); + } + retval = Result::OK; + } + _hidl_cb(retval, microphones); + return Void(); +} + +Return Device::setConnectedState(const DeviceAddress& address, bool connected) { + auto key = connected ? AudioParameter::keyDeviceConnect : AudioParameter::keyDeviceDisconnect; + return setParam(key, address); +} +#endif + +Result Device::doClose() { + if (mIsClosed || mOpenedStreamsCount != 0) return Result::INVALID_STATE; + mIsClosed = true; + return analyzeStatus("close", audio_hw_device_close(mDevice)); +} + +#if MAJOR_VERSION >= 6 +Return Device::close() { + return doClose(); +} + +Return Device::addDeviceEffect(AudioPortHandle device, uint64_t effectId) { + if (version() < AUDIO_DEVICE_API_VERSION_3_1 || mDevice->add_device_effect == nullptr) { + return Result::NOT_SUPPORTED; + } + + effect_handle_t halEffect = EffectMap::getInstance().get(effectId); + if (halEffect != NULL) { + return analyzeStatus("add_device_effect", + mDevice->add_device_effect( + mDevice, static_cast(device), halEffect)); + } else { + ALOGW("%s Invalid effect ID passed from client: %" PRIu64 "", __func__, effectId); + return Result::INVALID_ARGUMENTS; + } +} + +Return Device::removeDeviceEffect(AudioPortHandle device, uint64_t effectId) { + if (version() < AUDIO_DEVICE_API_VERSION_3_1 || mDevice->remove_device_effect == nullptr) { + return Result::NOT_SUPPORTED; + } + + effect_handle_t halEffect = EffectMap::getInstance().get(effectId); + if (halEffect != NULL) { + return analyzeStatus("remove_device_effect", + mDevice->remove_device_effect( + mDevice, static_cast(device), halEffect)); + } else { + ALOGW("%s Invalid effect ID passed from client: %" PRIu64 "", __func__, effectId); + return Result::INVALID_ARGUMENTS; + } +} + +Return Device::updateAudioPatch(int32_t previousPatch, + const hidl_vec& sources, + const hidl_vec& sinks, + createAudioPatch_cb _hidl_cb) { + if (previousPatch != static_cast(AudioPatchHandle{})) { + auto [retval, patch] = createOrUpdateAudioPatch(previousPatch, sources, sinks); + _hidl_cb(retval, patch); + } else { + _hidl_cb(Result::INVALID_ARGUMENTS, previousPatch); + } + return Void(); +} + +#endif + +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 +Return Device::setConnectedState_7_1(const AudioPort& devicePort, bool connected) { + if (version() >= AUDIO_DEVICE_API_VERSION_3_2 && + mDevice->set_device_connected_state_v7 != nullptr) { + audio_port_v7 halPort; + if (status_t status = HidlUtils::audioPortToHal(devicePort, &halPort); status != NO_ERROR) { + return analyzeStatus("audioPortToHal", status); + } + return analyzeStatus("set_device_connected_state_v7", + mDevice->set_device_connected_state_v7(mDevice, &halPort, connected)); + } + return Result::NOT_SUPPORTED; +} +#endif + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android diff --git a/bluetooth/audio/hal/Device.h b/bluetooth/audio/hal/Device.h new file mode 100644 index 0000000..0696f97 --- /dev/null +++ b/bluetooth/audio/hal/Device.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_HARDWARE_AUDIO_DEVICE_H +#define ANDROID_HARDWARE_AUDIO_DEVICE_H + +#include PATH(android/hardware/audio/FILE_VERSION/IDevice.h) + +#include "ParametersUtil.h" + +#include + +#include +#include + +#include + +#include + +#include +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::CoreUtils; +using ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::ParametersUtil; +using namespace ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CPP_VERSION; +using AudioInputFlags = CoreUtils::AudioInputFlags; +using AudioOutputFlags = CoreUtils::AudioOutputFlags; + +struct Device : public IDevice, public ParametersUtil { + explicit Device(audio_hw_device_t* device); + + // Methods from ::android::hardware::audio::CPP_VERSION::IDevice follow. + Return initCheck() override; + Return setMasterVolume(float volume) override; + Return getMasterVolume(getMasterVolume_cb _hidl_cb) override; + Return setMicMute(bool mute) override; + Return getMicMute(getMicMute_cb _hidl_cb) override; + Return setMasterMute(bool mute) override; + Return getMasterMute(getMasterMute_cb _hidl_cb) override; + Return getInputBufferSize(const AudioConfig& config, + getInputBufferSize_cb _hidl_cb) override; + + std::tuple> openOutputStreamCore(int32_t ioHandle, + const DeviceAddress& device, + const AudioConfig& config, + const AudioOutputFlags& flags, + AudioConfig* suggestedConfig); + std::tuple> openInputStreamCore( + int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config, + const AudioInputFlags& flags, AudioSource source, AudioConfig* suggestedConfig); +#if MAJOR_VERSION >= 4 + std::tuple, AudioConfig> openOutputStreamImpl( + int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config, + const SourceMetadata& sourceMetadata, +#if MAJOR_VERSION <= 6 + AudioOutputFlags flags); +#else + const AudioOutputFlags& flags); +#endif + std::tuple, AudioConfig> openInputStreamImpl( + int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config, +#if MAJOR_VERSION <= 6 + AudioInputFlags flags, +#else + const AudioInputFlags& flags, +#endif + const SinkMetadata& sinkMetadata); +#endif // MAJOR_VERSION >= 4 + + Return openOutputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, +#if MAJOR_VERSION <= 6 + AudioOutputFlags flags, +#else + const AudioOutputFlags& flags, +#endif +#if MAJOR_VERSION >= 4 + const SourceMetadata& sourceMetadata, +#endif + openOutputStream_cb _hidl_cb) override; + Return openInputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, +#if MAJOR_VERSION <= 6 + AudioInputFlags flags, +#else + const AudioInputFlags& flags, +#endif +#if MAJOR_VERSION == 2 + AudioSource source, +#elif MAJOR_VERSION >= 4 + const SinkMetadata& sinkMetadata, +#endif + openInputStream_cb _hidl_cb) override; + +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 + Return openOutputStream_7_1(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, const AudioOutputFlags& flags, + const SourceMetadata& sourceMetadata, + openOutputStream_7_1_cb _hidl_cb) override; +#endif + + Return supportsAudioPatches() override; + Return createAudioPatch(const hidl_vec& sources, + const hidl_vec& sinks, + createAudioPatch_cb _hidl_cb) override; + Return releaseAudioPatch(int32_t patch) override; + Return getAudioPort(const AudioPort& port, getAudioPort_cb _hidl_cb) override; + Return setAudioPortConfig(const AudioPortConfig& config) override; + + Return setScreenState(bool turnedOn) override; + +#if MAJOR_VERSION == 2 + Return getHwAvSync() override; + Return getParameters(const hidl_vec& keys, + getParameters_cb _hidl_cb) override; + Return setParameters(const hidl_vec& parameters) override; + Return debugDump(const hidl_handle& fd) override; +#elif MAJOR_VERSION >= 4 + Return getHwAvSync(getHwAvSync_cb _hidl_cb) override; + Return getParameters(const hidl_vec& context, + const hidl_vec& keys, + getParameters_cb _hidl_cb) override; + Return setParameters(const hidl_vec& context, + const hidl_vec& parameters) override; + Return getMicrophones(getMicrophones_cb _hidl_cb) override; + Return setConnectedState(const DeviceAddress& address, bool connected) override; +#endif +#if MAJOR_VERSION >= 6 + Return close() override; + Return addDeviceEffect(AudioPortHandle device, uint64_t effectId) override; + Return removeDeviceEffect(AudioPortHandle device, uint64_t effectId) override; + Return updateAudioPatch(int32_t previousPatch, const hidl_vec& sources, + const hidl_vec& sinks, + createAudioPatch_cb _hidl_cb) override; +#endif +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 + Return setConnectedState_7_1(const AudioPort& devicePort, bool connected) override; +#endif + Return debug(const hidl_handle& fd, const hidl_vec& options) override; + + // Utility methods for extending interfaces. + Result analyzeStatus(const char* funcName, int status, + const std::vector& ignoreErrors = {}); + void closeInputStream(audio_stream_in_t* stream); + void closeOutputStream(audio_stream_out_t* stream); + audio_hw_device_t* device() const { return mDevice; } + + uint32_t version() const { return mDevice->common.version; } + + private: + bool mIsClosed; + audio_hw_device_t* mDevice; + int mOpenedStreamsCount = 0; + + virtual ~Device(); + + Result doClose(); + std::tuple createOrUpdateAudioPatch( + AudioPatchHandle patch, const hidl_vec& sources, + const hidl_vec& sinks); + template + Return getAudioPortImpl(const AudioPort& port, getAudioPort_cb _hidl_cb, + int (*halGetter)(audio_hw_device_t*, HalPort*), + const char* halGetterName); + + // Methods from ParametersUtil. + char* halGetParameters(const char* keys) override; + int halSetParameters(const char* keysAndValues) override; +}; + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUDIO_DEVICE_H diff --git a/bluetooth/audio/hal/DevicesFactory.cpp b/bluetooth/audio/hal/DevicesFactory.cpp new file mode 100644 index 0000000..cc42293 --- /dev/null +++ b/bluetooth/audio/hal/DevicesFactory.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2018 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 "DevicesFactoryHAL" + +#include "DevicesFactory.h" +#include "Device.h" +#include "PrimaryDevice.h" + +#include + +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +#if MAJOR_VERSION == 2 +Return DevicesFactory::openDevice(IDevicesFactory::Device device, openDevice_cb _hidl_cb) { + switch (device) { + case IDevicesFactory::Device::PRIMARY: + return openDevice(AUDIO_HARDWARE_MODULE_ID_PRIMARY, _hidl_cb); + case IDevicesFactory::Device::A2DP: + return openDevice(AUDIO_HARDWARE_MODULE_ID_A2DP, _hidl_cb); + case IDevicesFactory::Device::USB: + return openDevice(AUDIO_HARDWARE_MODULE_ID_USB, _hidl_cb); + case IDevicesFactory::Device::R_SUBMIX: + return openDevice(AUDIO_HARDWARE_MODULE_ID_REMOTE_SUBMIX, _hidl_cb); + case IDevicesFactory::Device::STUB: + return openDevice(AUDIO_HARDWARE_MODULE_ID_STUB, _hidl_cb); + } + _hidl_cb(Result::INVALID_ARGUMENTS, nullptr); + return Void(); +} + +Return DevicesFactory::openDevice(const char* moduleName, openDevice_cb _hidl_cb) { + return openDevice(moduleName, _hidl_cb); +} +#elif MAJOR_VERSION >= 4 +Return DevicesFactory::openDevice(const hidl_string& moduleName, openDevice_cb _hidl_cb) { + if (moduleName == AUDIO_HARDWARE_MODULE_ID_PRIMARY) { + return openDevice(moduleName.c_str(), _hidl_cb); + } + return openDevice(moduleName.c_str(), _hidl_cb); +} +Return DevicesFactory::openPrimaryDevice(openPrimaryDevice_cb _hidl_cb) { + return openDevice(AUDIO_HARDWARE_MODULE_ID_PRIMARY, _hidl_cb); +} +#endif + +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 +Return DevicesFactory::openDevice_7_1(const hidl_string& moduleName, + openDevice_7_1_cb _hidl_cb) { + if (moduleName == AUDIO_HARDWARE_MODULE_ID_PRIMARY) { + Result result; + sp primary; + auto ret = openDevice( + AUDIO_HARDWARE_MODULE_ID_PRIMARY, + [&result, &primary](Result r, const sp& p) { + result = r; + primary = p; + }); + if (ret.isOk() && result == Result::OK && primary != nullptr) { + auto getDeviceRet = primary->getDevice(); + if (getDeviceRet.isOk()) { + _hidl_cb(result, getDeviceRet); + } else { + _hidl_cb(Result::NOT_INITIALIZED, nullptr); + } + } else { + _hidl_cb(result, nullptr); + } + return Void(); + } + return openDevice(moduleName.c_str(), _hidl_cb); +} + +Return DevicesFactory::openPrimaryDevice_7_1(openPrimaryDevice_7_1_cb _hidl_cb) { + return openDevice(AUDIO_HARDWARE_MODULE_ID_PRIMARY, _hidl_cb); +} +#endif // V7.1 + +template +Return DevicesFactory::openDevice(const char* moduleName, Callback _hidl_cb) { + audio_hw_device_t* halDevice; + Result retval(Result::INVALID_ARGUMENTS); + sp result; + int halStatus = loadAudioInterface(moduleName, &halDevice); + if (halStatus == OK) { + result = new DeviceShim(halDevice); + retval = Result::OK; + } else if (halStatus == -EINVAL) { + retval = Result::NOT_INITIALIZED; + } + _hidl_cb(retval, result); + return Void(); +} + +// static +int DevicesFactory::loadAudioInterface(const char* if_name, audio_hw_device_t** dev) { + const hw_module_t* mod; + int rc; + + rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod); + if (rc) { + ALOGE("%s couldn't load audio hw module %s.%s (%s)", __func__, AUDIO_HARDWARE_MODULE_ID, + if_name, strerror(-rc)); + goto out; + } + rc = audio_hw_device_open(mod, dev); + if (rc) { + ALOGE("%s couldn't open audio hw device in %s.%s (%s)", __func__, AUDIO_HARDWARE_MODULE_ID, + if_name, strerror(-rc)); + goto out; + } + if ((*dev)->common.version < AUDIO_DEVICE_API_VERSION_MIN) { + ALOGE("%s wrong audio hw device version %04x", __func__, (*dev)->common.version); + rc = -EINVAL; + audio_hw_device_close(*dev); + goto out; + } + return OK; + +out: + *dev = NULL; + return rc; +} + +IDevicesFactory* HIDL_FETCH_IDevicesFactory(const char* name) { + return strcmp(name, "default") == 0 ? new DevicesFactory() : nullptr; +} + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android diff --git a/bluetooth/audio/hal/DevicesFactory.h b/bluetooth/audio/hal/DevicesFactory.h new file mode 100644 index 0000000..566bc8a --- /dev/null +++ b/bluetooth/audio/hal/DevicesFactory.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_HARDWARE_AUDIO_DEVICESFACTORY_H +#define ANDROID_HARDWARE_AUDIO_DEVICESFACTORY_H + +#include PATH(android/hardware/audio/FILE_VERSION/IDevicesFactory.h) + +#include + +#include + +#include +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using namespace ::android::hardware::audio::CPP_VERSION; + +struct DevicesFactory : public IDevicesFactory { +#if MAJOR_VERSION == 2 + Return openDevice(IDevicesFactory::Device device, openDevice_cb _hidl_cb) override; +#elif MAJOR_VERSION >= 4 + Return openDevice(const hidl_string& device, openDevice_cb _hidl_cb) override; + Return openPrimaryDevice(openPrimaryDevice_cb _hidl_cb) override; +#endif +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 + Return openDevice_7_1(const hidl_string& device, openDevice_7_1_cb _hidl_cb) override; + Return openPrimaryDevice_7_1(openPrimaryDevice_7_1_cb _hidl_cb) override; +#endif + + private: + template + Return openDevice(const char* moduleName, Callback _hidl_cb); +#if MAJOR_VERSION == 2 + Return openDevice(const char* moduleName, openDevice_cb _hidl_cb); +#endif + + static int loadAudioInterface(const char* if_name, audio_hw_device_t** dev); +}; + +extern "C" IDevicesFactory* HIDL_FETCH_IDevicesFactory(const char* name); + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUDIO_DEVICESFACTORY_H diff --git a/bluetooth/audio/hal/HearingAidAudioProvider.cpp b/bluetooth/audio/hal/HearingAidAudioProvider.cpp new file mode 100644 index 0000000..e8b01ac --- /dev/null +++ b/bluetooth/audio/hal/HearingAidAudioProvider.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 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 "BTAudioProviderHearingAid" + +#include "HearingAidAudioProvider.h" + +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +static constexpr uint32_t kPcmFrameSize = 4; // 16 bits per sample / stereo +static constexpr uint32_t kPcmFrameCount = 128; +static constexpr uint32_t kRtpFrameSize = kPcmFrameSize * kPcmFrameCount; +static constexpr uint32_t kRtpFrameCount = 7; // max counts by 1 tick (20ms) +static constexpr uint32_t kBufferSize = kRtpFrameSize * kRtpFrameCount; +static constexpr uint32_t kBufferCount = 1; // single buffer +static constexpr uint32_t kDataMqSize = kBufferSize * kBufferCount; + +HearingAidAudioProvider::HearingAidAudioProvider() + : BluetoothAudioProvider(), data_mq_(nullptr) { + LOG(INFO) << __func__ << " - size of audio buffer " << kDataMqSize + << " byte(s)"; + std::unique_ptr data_mq( + new DataMQ(kDataMqSize, /* EventFlag */ true)); + if (data_mq && data_mq->isValid()) { + data_mq_ = std::move(data_mq); + session_type_ = SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH; + } else { + ALOGE_IF(!data_mq, "failed to allocate data MQ"); + ALOGE_IF(data_mq && !data_mq->isValid(), "data MQ is invalid"); + } +} +bool HearingAidAudioProvider::isValid(const SessionType& sessionType) { + return (sessionType == session_type_ && data_mq_ && data_mq_->isValid()); +} + +ndk::ScopedAStatus HearingAidAudioProvider::startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, DataMQDesc* _aidl_return) { + if (audio_config.getTag() != AudioConfiguration::pcmConfig) { + LOG(WARNING) << __func__ << " - Invalid Audio Configuration=" + << audio_config.toString(); + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + const auto& pcm_config = audio_config.get(); + if (!BluetoothAudioCodecs::IsSoftwarePcmConfigurationValid(pcm_config)) { + LOG(WARNING) << __func__ << " - Unsupported PCM Configuration=" + << pcm_config.toString(); + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + return BluetoothAudioProvider::startSession( + host_if, audio_config, latency_modes, _aidl_return); +} + +ndk::ScopedAStatus HearingAidAudioProvider::onSessionReady( + DataMQDesc* _aidl_return) { + if (data_mq_ == nullptr || !data_mq_->isValid()) { + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + *_aidl_return = data_mq_->dupeDesc(); + auto desc = data_mq_->dupeDesc(); + BluetoothAudioSessionReport::OnSessionStarted( + session_type_, stack_iface_, &desc, *audio_config_, latency_modes_); + return ndk::ScopedAStatus::ok(); +} + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/HearingAidAudioProvider.h b/bluetooth/audio/hal/HearingAidAudioProvider.h new file mode 100644 index 0000000..a158c86 --- /dev/null +++ b/bluetooth/audio/hal/HearingAidAudioProvider.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include "BluetoothAudioProvider.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +class HearingAidAudioProvider : public BluetoothAudioProvider { + public: + HearingAidAudioProvider(); + + bool isValid(const SessionType& sessionType) override; + + ndk::ScopedAStatus startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, + DataMQDesc* _aidl_return); + + private: + // audio data queue for software encoding + std::unique_ptr data_mq_; + + ndk::ScopedAStatus onSessionReady(DataMQDesc* _aidl_return) override; +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/LeAudioOffloadAudioProvider.cpp b/bluetooth/audio/hal/LeAudioOffloadAudioProvider.cpp new file mode 100644 index 0000000..0e22e44 --- /dev/null +++ b/bluetooth/audio/hal/LeAudioOffloadAudioProvider.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 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 "BTAudioProviderLeAudioHW" + +#include "LeAudioOffloadAudioProvider.h" + +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +LeAudioOffloadOutputAudioProvider::LeAudioOffloadOutputAudioProvider() + : LeAudioOffloadAudioProvider() { + session_type_ = SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH; +} + +LeAudioOffloadInputAudioProvider::LeAudioOffloadInputAudioProvider() + : LeAudioOffloadAudioProvider() { + session_type_ = SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH; +} + +LeAudioOffloadBroadcastAudioProvider::LeAudioOffloadBroadcastAudioProvider() + : LeAudioOffloadAudioProvider() { + session_type_ = + SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH; +} + +LeAudioOffloadAudioProvider::LeAudioOffloadAudioProvider() + : BluetoothAudioProvider() {} + +bool LeAudioOffloadAudioProvider::isValid(const SessionType& sessionType) { + return (sessionType == session_type_); +} + +ndk::ScopedAStatus LeAudioOffloadAudioProvider::startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, DataMQDesc* _aidl_return) { + if (audio_config.getTag() != AudioConfiguration::leAudioConfig) { + LOG(WARNING) << __func__ << " - Invalid Audio Configuration=" + << audio_config.toString(); + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + const auto& le_audio_config = + audio_config.get(); + if (!BluetoothAudioCodecs::IsOffloadLeAudioConfigurationValid( + session_type_, le_audio_config)) { + LOG(WARNING) << __func__ << " - Unsupported LC3 Offloaded Configuration=" + << le_audio_config.toString(); + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + return BluetoothAudioProvider::startSession( + host_if, audio_config, latency_modes, _aidl_return); +} + +ndk::ScopedAStatus LeAudioOffloadAudioProvider::onSessionReady( + DataMQDesc* _aidl_return) { + BluetoothAudioSessionReport::OnSessionStarted( + session_type_, stack_iface_, nullptr, *audio_config_, latency_modes_); + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::ok(); +} + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/LeAudioOffloadAudioProvider.h b/bluetooth/audio/hal/LeAudioOffloadAudioProvider.h new file mode 100644 index 0000000..614c794 --- /dev/null +++ b/bluetooth/audio/hal/LeAudioOffloadAudioProvider.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include "BluetoothAudioProvider.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +class LeAudioOffloadAudioProvider : public BluetoothAudioProvider { + public: + LeAudioOffloadAudioProvider(); + + bool isValid(const SessionType& sessionType) override; + + ndk::ScopedAStatus startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, + DataMQDesc* _aidl_return); + + private: + ndk::ScopedAStatus onSessionReady(DataMQDesc* _aidl_return) override; +}; + +class LeAudioOffloadOutputAudioProvider : public LeAudioOffloadAudioProvider { + public: + LeAudioOffloadOutputAudioProvider(); +}; + +class LeAudioOffloadInputAudioProvider : public LeAudioOffloadAudioProvider { + public: + LeAudioOffloadInputAudioProvider(); +}; + +class LeAudioOffloadBroadcastAudioProvider + : public LeAudioOffloadAudioProvider { + public: + LeAudioOffloadBroadcastAudioProvider(); +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/LeAudioSoftwareAudioProvider.cpp b/bluetooth/audio/hal/LeAudioSoftwareAudioProvider.cpp new file mode 100644 index 0000000..c16ff54 --- /dev/null +++ b/bluetooth/audio/hal/LeAudioSoftwareAudioProvider.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 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 "BTAudioProviderLeAudioSW" + +#include "LeAudioSoftwareAudioProvider.h" + +#include +#include +#include + +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +static constexpr uint32_t kBufferOutCount = 2; // two frame buffer +static constexpr uint32_t kBufferInCount = 2; // two frame buffer + +inline uint32_t channel_mode_to_channel_count(ChannelMode channel_mode) { + switch (channel_mode) { + case ChannelMode::MONO: + return 1; + case ChannelMode::STEREO: + return 2; + default: + return 0; + } + return 0; +} + +LeAudioSoftwareOutputAudioProvider::LeAudioSoftwareOutputAudioProvider() + : LeAudioSoftwareAudioProvider() { + session_type_ = SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH; +} + +LeAudioSoftwareInputAudioProvider::LeAudioSoftwareInputAudioProvider() + : LeAudioSoftwareAudioProvider() { + session_type_ = SessionType::LE_AUDIO_SOFTWARE_DECODING_DATAPATH; +} + +LeAudioSoftwareBroadcastAudioProvider::LeAudioSoftwareBroadcastAudioProvider() + : LeAudioSoftwareAudioProvider() { + session_type_ = SessionType::LE_AUDIO_BROADCAST_SOFTWARE_ENCODING_DATAPATH; +} + +LeAudioSoftwareAudioProvider::LeAudioSoftwareAudioProvider() + : BluetoothAudioProvider(), data_mq_(nullptr) {} + +bool LeAudioSoftwareAudioProvider::isValid(const SessionType& sessionType) { + return (sessionType == session_type_); +} + +ndk::ScopedAStatus LeAudioSoftwareAudioProvider::startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, DataMQDesc* _aidl_return) { + if (audio_config.getTag() != AudioConfiguration::pcmConfig) { + LOG(WARNING) << __func__ << " - Invalid Audio Configuration=" + << audio_config.toString(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + const auto& pcm_config = audio_config.get(); + if (!BluetoothAudioCodecs::IsSoftwarePcmConfigurationValid(pcm_config)) { + LOG(WARNING) << __func__ << " - Unsupported PCM Configuration=" + << pcm_config.toString(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + uint32_t buffer_modifier = 0; + if (session_type_ == SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH || + session_type_ == + SessionType::LE_AUDIO_BROADCAST_SOFTWARE_ENCODING_DATAPATH) + buffer_modifier = kBufferOutCount; + else if (session_type_ == SessionType::LE_AUDIO_SOFTWARE_DECODING_DATAPATH) + buffer_modifier = kBufferInCount; + + // 24 bit audio stream is sent as unpacked + int bytes_per_sample = + (pcm_config.bitsPerSample == 24) ? 4 : (pcm_config.bitsPerSample / 8); + + uint32_t data_mq_size = + (ceil(pcm_config.sampleRateHz) / 1000) * + channel_mode_to_channel_count(pcm_config.channelMode) * bytes_per_sample * + (pcm_config.dataIntervalUs / 1000) * buffer_modifier; + if (data_mq_size <= 0) { + LOG(ERROR) << __func__ << "Unexpected audio buffer size: " << data_mq_size + << ", SampleRateHz: " << pcm_config.sampleRateHz + << ", ChannelMode: " << toString(pcm_config.channelMode) + << ", BitsPerSample: " + << static_cast(pcm_config.bitsPerSample) + << ", BytesPerSample: " << bytes_per_sample + << ", DataIntervalUs: " << pcm_config.dataIntervalUs + << ", SessionType: " << toString(session_type_); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + LOG(INFO) << __func__ << " - size of audio buffer " << data_mq_size + << " byte(s)"; + + std::unique_ptr temp_data_mq( + new DataMQ(data_mq_size, /* EventFlag */ true)); + if (temp_data_mq == nullptr || !temp_data_mq->isValid()) { + ALOGE_IF(!temp_data_mq, "failed to allocate data MQ"); + ALOGE_IF(temp_data_mq && !temp_data_mq->isValid(), "data MQ is invalid"); + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + data_mq_ = std::move(temp_data_mq); + + return BluetoothAudioProvider::startSession( + host_if, audio_config, latency_modes, _aidl_return); +} + +ndk::ScopedAStatus LeAudioSoftwareAudioProvider::onSessionReady( + DataMQDesc* _aidl_return) { + if (data_mq_ == nullptr || !data_mq_->isValid()) { + *_aidl_return = DataMQDesc(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + *_aidl_return = data_mq_->dupeDesc(); + auto desc = data_mq_->dupeDesc(); + BluetoothAudioSessionReport::OnSessionStarted( + session_type_, stack_iface_, &desc, *audio_config_, latency_modes_); + return ndk::ScopedAStatus::ok(); +} + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/hal/LeAudioSoftwareAudioProvider.h b/bluetooth/audio/hal/LeAudioSoftwareAudioProvider.h new file mode 100644 index 0000000..21243ff --- /dev/null +++ b/bluetooth/audio/hal/LeAudioSoftwareAudioProvider.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include "BluetoothAudioProvider.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +class LeAudioSoftwareAudioProvider : public BluetoothAudioProvider { + public: + LeAudioSoftwareAudioProvider(); + + bool isValid(const SessionType& sessionType) override; + + ndk::ScopedAStatus startSession( + const std::shared_ptr& host_if, + const AudioConfiguration& audio_config, + const std::vector& latency_modes, + DataMQDesc* _aidl_return); + + private: + // audio data queue for software encoding + std::unique_ptr data_mq_; + + ndk::ScopedAStatus onSessionReady(DataMQDesc* _aidl_return) override; +}; + +class LeAudioSoftwareOutputAudioProvider : public LeAudioSoftwareAudioProvider { + public: + LeAudioSoftwareOutputAudioProvider(); +}; + +class LeAudioSoftwareInputAudioProvider : public LeAudioSoftwareAudioProvider { + public: + LeAudioSoftwareInputAudioProvider(); +}; + +class LeAudioSoftwareBroadcastAudioProvider + : public LeAudioSoftwareAudioProvider { + public: + LeAudioSoftwareBroadcastAudioProvider(); +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl \ No newline at end of file diff --git a/bluetooth/audio/hal/ParametersUtil.cpp b/bluetooth/audio/hal/ParametersUtil.cpp new file mode 100644 index 0000000..a632070 --- /dev/null +++ b/bluetooth/audio/hal/ParametersUtil.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "ParametersUtil.h" +#include "Util.h" + +#include + +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CORE_TYPES_CPP_VERSION { +namespace implementation { + +/** Converts a status_t in Result according to the rules of AudioParameter::get* + * Note: Static method and not private method to avoid leaking status_t dependency + */ +static Result getHalStatusToResult(status_t status) { + switch (status) { + case OK: + return Result::OK; + case BAD_VALUE: // Nothing was returned, probably because the HAL does + // not handle it + return Result::NOT_SUPPORTED; + case INVALID_OPERATION: // Conversion from string to the requested type + // failed + return Result::INVALID_ARGUMENTS; + default: // Should not happen + ALOGW("Unexpected status returned by getParam: %u", status); + return Result::INVALID_ARGUMENTS; + } +} + +Result ParametersUtil::getParam(const char* name, bool* value) { + String8 halValue; + Result retval = getParam(name, &halValue); + *value = false; + if (retval == Result::OK) { + if (halValue.empty()) { + return Result::NOT_SUPPORTED; + } + *value = !(halValue == AudioParameter::valueOff); + } + return retval; +} + +Result ParametersUtil::getParam(const char* name, int* value) { + const String8 halName(name); + AudioParameter keys; + keys.addKey(halName); + std::unique_ptr params = getParams(keys); + return getHalStatusToResult(params->getInt(halName, *value)); +} + +Result ParametersUtil::getParam(const char* name, String8* value, AudioParameter context) { + const String8 halName(name); + context.addKey(halName); + std::unique_ptr params = getParams(context); + return getHalStatusToResult(params->get(halName, *value)); +} + +void ParametersUtil::getParametersImpl( + const hidl_vec& context, const hidl_vec& keys, + std::function& parameters)> cb) { + AudioParameter halKeys; + for (auto& pair : context) { + halKeys.add(String8(pair.key.c_str()), String8(pair.value.c_str())); + } + for (size_t i = 0; i < keys.size(); ++i) { + halKeys.addKey(String8(keys[i].c_str())); + } + std::unique_ptr halValues = getParams(halKeys); + Result retval = + (keys.size() == 0 || halValues->size() != 0) ? Result::OK : Result::NOT_SUPPORTED; + hidl_vec result; + result.resize(halValues->size()); + String8 halKey, halValue; + for (size_t i = 0; i < halValues->size(); ++i) { + status_t status = halValues->getAt(i, halKey, halValue); + if (status != OK) { + result.resize(0); + retval = getHalStatusToResult(status); + break; + } + result[i].key = halKey.string(); + result[i].value = halValue.string(); + } + cb(retval, result); +} + +std::unique_ptr ParametersUtil::getParams(const AudioParameter& keys) { + String8 paramsAndValues; + char* halValues = halGetParameters(keys.keysToString().string()); + if (halValues != NULL) { + paramsAndValues.setTo(halValues); + free(halValues); + } else { + paramsAndValues.clear(); + } + return std::unique_ptr(new AudioParameter(paramsAndValues)); +} + +Result ParametersUtil::setParam(const char* name, const char* value) { + AudioParameter param; + param.add(String8(name), String8(value)); + return setParams(param); +} + +Result ParametersUtil::setParam(const char* name, bool value) { + AudioParameter param; + param.add(String8(name), String8(value ? AudioParameter::valueOn : AudioParameter::valueOff)); + return setParams(param); +} + +Result ParametersUtil::setParam(const char* name, int value) { + AudioParameter param; + param.addInt(String8(name), value); + return setParams(param); +} + +Result ParametersUtil::setParam(const char* name, float value) { + AudioParameter param; + param.addFloat(String8(name), value); + return setParams(param); +} + +Result ParametersUtil::setParametersImpl(const hidl_vec& context, + const hidl_vec& parameters) { + AudioParameter params; + for (auto& pair : context) { + params.add(String8(pair.key.c_str()), String8(pair.value.c_str())); + } + for (size_t i = 0; i < parameters.size(); ++i) { + params.add(String8(parameters[i].key.c_str()), String8(parameters[i].value.c_str())); + } + return setParams(params); +} + +Result ParametersUtil::setParam(const char* name, const DeviceAddress& address) { + audio_devices_t halDeviceType; + char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN]; + if (CoreUtils::deviceAddressToHal(address, &halDeviceType, halDeviceAddress) != NO_ERROR) { + return Result::INVALID_ARGUMENTS; + } + AudioParameter params{String8(halDeviceAddress)}; + params.addInt(String8(name), halDeviceType); + return setParams(params); +} + +Result ParametersUtil::setParams(const AudioParameter& param) { + int halStatus = halSetParameters(param.toString().string()); + return util::analyzeStatus(halStatus); +} + +} // namespace implementation +} // namespace CORE_TYPES_CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android diff --git a/bluetooth/audio/hal/ParametersUtil.h b/bluetooth/audio/hal/ParametersUtil.h new file mode 100644 index 0000000..25c193a --- /dev/null +++ b/bluetooth/audio/hal/ParametersUtil.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_HARDWARE_AUDIO_PARAMETERS_UTIL_H_ +#define ANDROID_HARDWARE_AUDIO_PARAMETERS_UTIL_H_ + +// clang-format off +#include PATH(android/hardware/audio/common/COMMON_TYPES_FILE_VERSION/types.h) +#include PATH(android/hardware/audio/CORE_TYPES_FILE_VERSION/types.h) +// clang-format on + +#include +#include + +#include +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CORE_TYPES_CPP_VERSION { +namespace implementation { + +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using namespace ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION; + +class ParametersUtil { + public: + Result setParam(const char* name, const char* value); + Result getParam(const char* name, bool* value); + Result getParam(const char* name, int* value); + Result getParam(const char* name, String8* value, AudioParameter context = {}); + void getParametersImpl( + const hidl_vec& context, const hidl_vec& keys, + std::function& parameters)> cb); + std::unique_ptr getParams(const AudioParameter& keys); + Result setParam(const char* name, bool value); + Result setParam(const char* name, int value); + Result setParam(const char* name, float value); + Result setParametersImpl(const hidl_vec& context, + const hidl_vec& parameters); + Result setParams(const AudioParameter& param); + Result setParam(const char* name, const DeviceAddress& address); + + protected: + virtual ~ParametersUtil() {} + + virtual char* halGetParameters(const char* keys) = 0; + virtual int halSetParameters(const char* keysAndValues) = 0; +}; + +} // namespace implementation +} // namespace CORE_TYPES_CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUDIO_PARAMETERS_UTIL_H_ diff --git a/bluetooth/audio/hal/PrimaryDevice.cpp b/bluetooth/audio/hal/PrimaryDevice.cpp new file mode 100644 index 0000000..1bba007 --- /dev/null +++ b/bluetooth/audio/hal/PrimaryDevice.cpp @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2018 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 "PrimaryDeviceHAL" + +#include "PrimaryDevice.h" +#include "Util.h" + +#if MAJOR_VERSION >= 4 +#include +#endif + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +namespace util { +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::util; +} + +PrimaryDevice::PrimaryDevice(audio_hw_device_t* device) : mDevice(new Device(device)) {} + +PrimaryDevice::~PrimaryDevice() { + // Do not call mDevice->close here. If there are any unclosed streams, + // they only hold IDevice instance, not IPrimaryDevice, thus IPrimaryDevice + // "part" of a device can be destroyed before the streams. +} + +// Methods from ::android::hardware::audio::CPP_VERSION::IDevice follow. +Return PrimaryDevice::initCheck() { + return mDevice->initCheck(); +} + +Return PrimaryDevice::setMasterVolume(float volume) { + return mDevice->setMasterVolume(volume); +} + +Return PrimaryDevice::getMasterVolume(getMasterVolume_cb _hidl_cb) { + return mDevice->getMasterVolume(_hidl_cb); +} + +Return PrimaryDevice::setMicMute(bool mute) { + return mDevice->setMicMute(mute); +} + +Return PrimaryDevice::getMicMute(getMicMute_cb _hidl_cb) { + return mDevice->getMicMute(_hidl_cb); +} + +Return PrimaryDevice::setMasterMute(bool mute) { + return mDevice->setMasterMute(mute); +} + +Return PrimaryDevice::getMasterMute(getMasterMute_cb _hidl_cb) { + return mDevice->getMasterMute(_hidl_cb); +} + +Return PrimaryDevice::getInputBufferSize(const AudioConfig& config, + getInputBufferSize_cb _hidl_cb) { + return mDevice->getInputBufferSize(config, _hidl_cb); +} + +#if MAJOR_VERSION == 2 +Return PrimaryDevice::openOutputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, AudioOutputFlags flags, + openOutputStream_cb _hidl_cb) { + return mDevice->openOutputStream(ioHandle, device, config, flags, _hidl_cb); +} + +Return PrimaryDevice::openInputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, AudioInputFlags flags, + AudioSource source, openInputStream_cb _hidl_cb) { + return mDevice->openInputStream(ioHandle, device, config, flags, source, _hidl_cb); +} +#elif MAJOR_VERSION >= 4 +Return PrimaryDevice::openOutputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, +#if MAJOR_VERSION <= 6 + AudioOutputFlags flags, +#else + const AudioOutputFlags& flags, +#endif + const SourceMetadata& sourceMetadata, + openOutputStream_cb _hidl_cb) { + return mDevice->openOutputStream(ioHandle, device, config, flags, sourceMetadata, _hidl_cb); +} + +Return PrimaryDevice::openInputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, +#if MAJOR_VERSION <= 6 + AudioInputFlags flags, +#else + const AudioInputFlags& flags, +#endif + const SinkMetadata& sinkMetadata, + openInputStream_cb _hidl_cb) { + return mDevice->openInputStream(ioHandle, device, config, flags, sinkMetadata, _hidl_cb); +} +#endif + +Return PrimaryDevice::supportsAudioPatches() { + return mDevice->supportsAudioPatches(); +} + +Return PrimaryDevice::createAudioPatch(const hidl_vec& sources, + const hidl_vec& sinks, + createAudioPatch_cb _hidl_cb) { + return mDevice->createAudioPatch(sources, sinks, _hidl_cb); +} + +Return PrimaryDevice::releaseAudioPatch(int32_t patch) { + return mDevice->releaseAudioPatch(patch); +} + +Return PrimaryDevice::getAudioPort(const AudioPort& port, getAudioPort_cb _hidl_cb) { + return mDevice->getAudioPort(port, _hidl_cb); +} + +Return PrimaryDevice::setAudioPortConfig(const AudioPortConfig& config) { + return mDevice->setAudioPortConfig(config); +} + +Return PrimaryDevice::setScreenState(bool turnedOn) { + return mDevice->setScreenState(turnedOn); +} + +#if MAJOR_VERSION == 2 +Return PrimaryDevice::getHwAvSync() { + return mDevice->getHwAvSync(); +} + +Return PrimaryDevice::getParameters(const hidl_vec& keys, + getParameters_cb _hidl_cb) { + return mDevice->getParameters(keys, _hidl_cb); +} + +Return PrimaryDevice::setParameters(const hidl_vec& parameters) { + return mDevice->setParameters(parameters); +} + +Return PrimaryDevice::debugDump(const hidl_handle& fd) { + return mDevice->debugDump(fd); +} +#elif MAJOR_VERSION >= 4 +Return PrimaryDevice::getHwAvSync(getHwAvSync_cb _hidl_cb) { + return mDevice->getHwAvSync(_hidl_cb); +} +Return PrimaryDevice::getParameters(const hidl_vec& context, + const hidl_vec& keys, + getParameters_cb _hidl_cb) { + return mDevice->getParameters(context, keys, _hidl_cb); +} +Return PrimaryDevice::setParameters(const hidl_vec& context, + const hidl_vec& parameters) { + return mDevice->setParameters(context, parameters); +} +Return PrimaryDevice::getMicrophones(getMicrophones_cb _hidl_cb) { + return mDevice->getMicrophones(_hidl_cb); +} +Return PrimaryDevice::setConnectedState(const DeviceAddress& address, bool connected) { + return mDevice->setConnectedState(address, connected); +} +#endif +#if MAJOR_VERSION >= 6 +Return PrimaryDevice::close() { + return mDevice->close(); +} + +Return PrimaryDevice::addDeviceEffect(AudioPortHandle device, uint64_t effectId) { + return mDevice->addDeviceEffect(device, effectId); +} + +Return PrimaryDevice::removeDeviceEffect(AudioPortHandle device, uint64_t effectId) { + return mDevice->removeDeviceEffect(device, effectId); +} + +Return PrimaryDevice::updateAudioPatch(int32_t previousPatch, + const hidl_vec& sources, + const hidl_vec& sinks, + updateAudioPatch_cb _hidl_cb) { + return mDevice->updateAudioPatch(previousPatch, sources, sinks, _hidl_cb); +} +#endif + +// Methods from ::android::hardware::audio::CPP_VERSION::IPrimaryDevice follow. +Return PrimaryDevice::setVoiceVolume(float volume) { + if (!util::isGainNormalized(volume)) { + ALOGW("Can not set a voice volume (%f) outside [0,1]", volume); + return Result::INVALID_ARGUMENTS; + } + return mDevice->analyzeStatus("set_voice_volume", + mDevice->device()->set_voice_volume(mDevice->device(), volume)); +} + +Return PrimaryDevice::setMode(AudioMode mode) { + // INVALID, CURRENT, CNT, MAX are reserved for internal use. + // TODO: remove the values from the HIDL interface + switch (mode) { + case AudioMode::NORMAL: + case AudioMode::RINGTONE: + case AudioMode::IN_CALL: + case AudioMode::IN_COMMUNICATION: +#if MAJOR_VERSION >= 6 + case AudioMode::CALL_SCREEN: +#endif + break; // Valid values + default: + return Result::INVALID_ARGUMENTS; + }; + + return mDevice->analyzeStatus( + "set_mode", + mDevice->device()->set_mode(mDevice->device(), static_cast(mode))); +} + +Return PrimaryDevice::getBtScoNrecEnabled(getBtScoNrecEnabled_cb _hidl_cb) { + bool enabled; + Result retval = mDevice->getParam(AudioParameter::keyBtNrec, &enabled); + _hidl_cb(retval, enabled); + return Void(); +} + +Return PrimaryDevice::setBtScoNrecEnabled(bool enabled) { + return mDevice->setParam(AudioParameter::keyBtNrec, enabled); +} + +Return PrimaryDevice::getBtScoWidebandEnabled(getBtScoWidebandEnabled_cb _hidl_cb) { + bool enabled; + Result retval = mDevice->getParam(AUDIO_PARAMETER_KEY_BT_SCO_WB, &enabled); + _hidl_cb(retval, enabled); + return Void(); +} + +Return PrimaryDevice::setBtScoWidebandEnabled(bool enabled) { + return mDevice->setParam(AUDIO_PARAMETER_KEY_BT_SCO_WB, enabled); +} + +static const char* convertTtyModeFromHIDL(IPrimaryDevice::TtyMode mode) { + switch (mode) { + case IPrimaryDevice::TtyMode::OFF: + return AUDIO_PARAMETER_VALUE_TTY_OFF; + case IPrimaryDevice::TtyMode::VCO: + return AUDIO_PARAMETER_VALUE_TTY_VCO; + case IPrimaryDevice::TtyMode::HCO: + return AUDIO_PARAMETER_VALUE_TTY_HCO; + case IPrimaryDevice::TtyMode::FULL: + return AUDIO_PARAMETER_VALUE_TTY_FULL; + default: + return nullptr; + } +} +static IPrimaryDevice::TtyMode convertTtyModeToHIDL(const char* halMode) { + if (strcmp(halMode, AUDIO_PARAMETER_VALUE_TTY_OFF) == 0) + return IPrimaryDevice::TtyMode::OFF; + else if (strcmp(halMode, AUDIO_PARAMETER_VALUE_TTY_VCO) == 0) + return IPrimaryDevice::TtyMode::VCO; + else if (strcmp(halMode, AUDIO_PARAMETER_VALUE_TTY_HCO) == 0) + return IPrimaryDevice::TtyMode::HCO; + else if (strcmp(halMode, AUDIO_PARAMETER_VALUE_TTY_FULL) == 0) + return IPrimaryDevice::TtyMode::FULL; + return IPrimaryDevice::TtyMode(-1); +} + +Return PrimaryDevice::getTtyMode(getTtyMode_cb _hidl_cb) { + String8 halMode; + Result retval = mDevice->getParam(AUDIO_PARAMETER_KEY_TTY_MODE, &halMode); + if (retval != Result::OK) { + _hidl_cb(retval, TtyMode::OFF); + return Void(); + } + TtyMode mode = convertTtyModeToHIDL(halMode); + if (mode == TtyMode(-1)) { + ALOGE("HAL returned invalid TTY value: %s", halMode.c_str()); + _hidl_cb(Result::INVALID_STATE, TtyMode::OFF); + return Void(); + } + _hidl_cb(Result::OK, mode); + return Void(); +} + +Return PrimaryDevice::setTtyMode(IPrimaryDevice::TtyMode mode) { + const char* modeStr = convertTtyModeFromHIDL(mode); + if (modeStr == nullptr) { + ALOGW("Can not set an invalid TTY value: %d", mode); + return Result::INVALID_ARGUMENTS; + } + return mDevice->setParam(AUDIO_PARAMETER_KEY_TTY_MODE, modeStr); +} + +Return PrimaryDevice::getHacEnabled(getHacEnabled_cb _hidl_cb) { + bool enabled; + Result retval = mDevice->getParam(AUDIO_PARAMETER_KEY_HAC, &enabled); + _hidl_cb(retval, enabled); + return Void(); +} + +Return PrimaryDevice::setHacEnabled(bool enabled) { + return mDevice->setParam(AUDIO_PARAMETER_KEY_HAC, enabled); +} + +#if MAJOR_VERSION >= 4 +Return PrimaryDevice::setBtScoHeadsetDebugName(const hidl_string& name) { + return mDevice->setParam(AUDIO_PARAMETER_KEY_BT_SCO_HEADSET_NAME, name.c_str()); +} +Return PrimaryDevice::getBtHfpEnabled(getBtHfpEnabled_cb _hidl_cb) { + bool enabled; + Result retval = mDevice->getParam(AUDIO_PARAMETER_KEY_HFP_ENABLE, &enabled); + _hidl_cb(retval, enabled); + return Void(); +} +Return PrimaryDevice::setBtHfpEnabled(bool enabled) { + return mDevice->setParam(AUDIO_PARAMETER_KEY_HFP_ENABLE, enabled); +} +Return PrimaryDevice::setBtHfpSampleRate(uint32_t sampleRateHz) { + return mDevice->setParam(AUDIO_PARAMETER_KEY_HFP_SET_SAMPLING_RATE, int(sampleRateHz)); +} +Return PrimaryDevice::setBtHfpVolume(float volume) { + if (!util::isGainNormalized(volume)) { + ALOGW("Can not set BT HFP volume (%f) outside [0,1]", volume); + return Result::INVALID_ARGUMENTS; + } + // Map the normalized volume onto the range of [0, 15] + return mDevice->setParam(AUDIO_PARAMETER_KEY_HFP_VOLUME, + static_cast(std::round(volume * 15))); +} +Return PrimaryDevice::updateRotation(IPrimaryDevice::Rotation rotation) { + // legacy API expects the rotation in degree + return mDevice->setParam(AUDIO_PARAMETER_KEY_ROTATION, int(rotation) * 90); +} +#endif + +Return PrimaryDevice::debug(const hidl_handle& fd, const hidl_vec& options) { + return mDevice->debug(fd, options); +} + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android diff --git a/bluetooth/audio/hal/PrimaryDevice.h b/bluetooth/audio/hal/PrimaryDevice.h new file mode 100644 index 0000000..8b37e01 --- /dev/null +++ b/bluetooth/audio/hal/PrimaryDevice.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_HARDWARE_AUDIO_PRIMARYDEVICE_H +#define ANDROID_HARDWARE_AUDIO_PRIMARYDEVICE_H + +#include PATH(android/hardware/audio/FILE_VERSION/IPrimaryDevice.h) + +#include "Device.h" + +#include + +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using namespace ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CPP_VERSION; + +struct PrimaryDevice : public IPrimaryDevice { + explicit PrimaryDevice(audio_hw_device_t* device); + + // Methods from ::android::hardware::audio::CPP_VERSION::IDevice follow. + Return initCheck() override; + Return setMasterVolume(float volume) override; + Return getMasterVolume(getMasterVolume_cb _hidl_cb) override; + Return setMicMute(bool mute) override; + Return getMicMute(getMicMute_cb _hidl_cb) override; + Return setMasterMute(bool mute) override; + Return getMasterMute(getMasterMute_cb _hidl_cb) override; + Return getInputBufferSize(const AudioConfig& config, + getInputBufferSize_cb _hidl_cb) override; + + Return openOutputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, +#if MAJOR_VERSION <= 6 + AudioOutputFlags flags, +#else + const AudioOutputFlags& flags, +#endif +#if MAJOR_VERSION >= 4 + const SourceMetadata& sourceMetadata, +#endif + openOutputStream_cb _hidl_cb) override; + Return openInputStream(int32_t ioHandle, const DeviceAddress& device, + const AudioConfig& config, +#if MAJOR_VERSION <= 6 + AudioInputFlags flags, +#else + const AudioInputFlags& flags, +#endif +#if MAJOR_VERSION == 2 + AudioSource source, +#elif MAJOR_VERSION >= 4 + const SinkMetadata& sinkMetadata, +#endif + openInputStream_cb _hidl_cb) override; + + Return supportsAudioPatches() override; + Return createAudioPatch(const hidl_vec& sources, + const hidl_vec& sinks, + createAudioPatch_cb _hidl_cb) override; + Return releaseAudioPatch(int32_t patch) override; + Return getAudioPort(const AudioPort& port, getAudioPort_cb _hidl_cb) override; + Return setAudioPortConfig(const AudioPortConfig& config) override; + + Return setScreenState(bool turnedOn) override; + +#if MAJOR_VERSION == 2 + Return getHwAvSync() override; + Return getParameters(const hidl_vec& keys, + getParameters_cb _hidl_cb) override; + Return setParameters(const hidl_vec& parameters) override; + Return debugDump(const hidl_handle& fd) override; +#elif MAJOR_VERSION >= 4 + Return getHwAvSync(getHwAvSync_cb _hidl_cb) override; + Return getParameters(const hidl_vec& context, + const hidl_vec& keys, + getParameters_cb _hidl_cb) override; + Return setParameters(const hidl_vec& context, + const hidl_vec& parameters) override; + Return getMicrophones(getMicrophones_cb _hidl_cb) override; + Return setConnectedState(const DeviceAddress& address, bool connected) override; +#endif +#if MAJOR_VERSION >= 6 + Return close() override; + Return addDeviceEffect(AudioPortHandle device, uint64_t effectId) override; + Return removeDeviceEffect(AudioPortHandle device, uint64_t effectId) override; + Return updateAudioPatch(int32_t previousPatch, const hidl_vec& sources, + const hidl_vec& sinks, + updateAudioPatch_cb _hidl_cb) override; +#endif + + Return debug(const hidl_handle& fd, const hidl_vec& options) override; + + // Methods from ::android::hardware::audio::CPP_VERSION::IPrimaryDevice follow. + Return setVoiceVolume(float volume) override; + Return setMode(AudioMode mode) override; + Return getBtScoNrecEnabled(getBtScoNrecEnabled_cb _hidl_cb) override; + Return setBtScoNrecEnabled(bool enabled) override; + Return getBtScoWidebandEnabled(getBtScoWidebandEnabled_cb _hidl_cb) override; + Return setBtScoWidebandEnabled(bool enabled) override; + Return getTtyMode(getTtyMode_cb _hidl_cb) override; + Return setTtyMode(IPrimaryDevice::TtyMode mode) override; + Return getHacEnabled(getHacEnabled_cb _hidl_cb) override; + Return setHacEnabled(bool enabled) override; + +#if MAJOR_VERSION >= 4 + Return setBtScoHeadsetDebugName(const hidl_string& name) override; + Return getBtHfpEnabled(getBtHfpEnabled_cb _hidl_cb) override; + Return setBtHfpEnabled(bool enabled) override; + Return setBtHfpSampleRate(uint32_t sampleRateHz) override; + Return setBtHfpVolume(float volume) override; + Return updateRotation(IPrimaryDevice::Rotation rotation) override; +#endif +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 + Return> getDevice() override { return mDevice; } +#endif + private: + sp mDevice; + + virtual ~PrimaryDevice(); +}; + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUDIO_PRIMARYDEVICE_H diff --git a/bluetooth/audio/hal/Stream.cpp b/bluetooth/audio/hal/Stream.cpp new file mode 100644 index 0000000..1d3b89c --- /dev/null +++ b/bluetooth/audio/hal/Stream.cpp @@ -0,0 +1,457 @@ +/* + * Copyright (C) 2018 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 "StreamHAL" + +#include "Stream.h" +#include "common/all-versions/HidlSupport.h" +#include "common/all-versions/default/EffectMap.h" +#include "Util.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +using ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::implementation::HidlUtils; +using ::android::hardware::audio::common::utils::splitString; +using ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::CoreUtils; +namespace util { +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::util; +} + +Stream::Stream(bool isInput, audio_stream_t* stream) : mIsInput(isInput), mStream(stream) { + (void)mIsInput; // prevent 'unused field' warnings in pre-V7 versions. +} + +Stream::~Stream() { + mStream = nullptr; +} + +// static +Result Stream::analyzeStatus(const char* funcName, int status) { + return util::analyzeStatus("stream", funcName, status); +} + +// static +Result Stream::analyzeStatus(const char* funcName, int status, + const std::vector& ignoreErrors) { + return util::analyzeStatus("stream", funcName, status, ignoreErrors); +} + +char* Stream::halGetParameters(const char* keys) { + return mStream->get_parameters(mStream, keys); +} + +int Stream::halSetParameters(const char* keysAndValues) { + return mStream->set_parameters(mStream, keysAndValues); +} + +// Methods from ::android::hardware::audio::CPP_VERSION::IStream follow. +Return Stream::getFrameSize() { + // Needs to be implemented by interface subclasses. But can't be declared as pure virtual, + // since interface subclasses implementation do not inherit from this class. + LOG_ALWAYS_FATAL("Stream::getFrameSize is pure abstract"); + return uint64_t{}; +} + +Return Stream::getFrameCount() { + int halFrameCount; + Result retval = getParam(AudioParameter::keyFrameCount, &halFrameCount); + return retval == Result::OK ? halFrameCount : 0; +} + +Return Stream::getBufferSize() { + return mStream->get_buffer_size(mStream); +} + +#if MAJOR_VERSION <= 6 +Return Stream::getSampleRate() { + return mStream->get_sample_rate(mStream); +} + +#if MAJOR_VERSION == 2 +Return Stream::getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) { + return getSupportedSampleRates(getFormat(), _hidl_cb); +} +Return Stream::getSupportedChannelMasks(getSupportedChannelMasks_cb _hidl_cb) { + return getSupportedChannelMasks(getFormat(), _hidl_cb); +} +#endif + +Return Stream::getSupportedSampleRates(AudioFormat format, + getSupportedSampleRates_cb _hidl_cb) { + AudioParameter context; + context.addInt(String8(AUDIO_PARAMETER_STREAM_FORMAT), int(format)); + String8 halListValue; + Result result = + getParam(AudioParameter::keyStreamSupportedSamplingRates, &halListValue, context); + hidl_vec sampleRates; + SampleRateSet halSampleRates; + if (result == Result::OK) { + halSampleRates = + samplingRatesFromString(halListValue.string(), AudioParameter::valueListSeparator); + sampleRates = hidl_vec(halSampleRates.begin(), halSampleRates.end()); + // Legacy get_parameter does not return a status_t, thus can not advertise of failure. + // Note that this method must succeed (non empty list) if the format is supported. + if (sampleRates.size() == 0) { + result = Result::NOT_SUPPORTED; + } + } +#if MAJOR_VERSION == 2 + _hidl_cb(sampleRates); +#elif MAJOR_VERSION >= 4 + _hidl_cb(result, sampleRates); +#endif + return Void(); +} + +Return Stream::getSupportedChannelMasks(AudioFormat format, + getSupportedChannelMasks_cb _hidl_cb) { + AudioParameter context; + context.addInt(String8(AUDIO_PARAMETER_STREAM_FORMAT), int(format)); + String8 halListValue; + Result result = getParam(AudioParameter::keyStreamSupportedChannels, &halListValue, context); + hidl_vec channelMasks; + ChannelMaskSet halChannelMasks; + if (result == Result::OK) { + halChannelMasks = + channelMasksFromString(halListValue.string(), AudioParameter::valueListSeparator); + channelMasks.resize(halChannelMasks.size()); + size_t i = 0; + for (auto channelMask : halChannelMasks) { + channelMasks[i++] = AudioChannelBitfield(channelMask); + } + // Legacy get_parameter does not return a status_t, thus can not advertise of failure. + // Note that this method must succeed (non empty list) if the format is supported. + if (channelMasks.size() == 0) { + result = Result::NOT_SUPPORTED; + } + } +#if MAJOR_VERSION == 2 + _hidl_cb(channelMasks); +#elif MAJOR_VERSION >= 4 + _hidl_cb(result, channelMasks); +#endif + return Void(); +} + +Return Stream::setSampleRate(uint32_t sampleRateHz) { + return setParam(AudioParameter::keySamplingRate, static_cast(sampleRateHz)); +} + +Return Stream::getChannelMask() { + return AudioChannelBitfield(mStream->get_channels(mStream)); +} + +Return Stream::setChannelMask(AudioChannelBitfield mask) { + return setParam(AudioParameter::keyChannels, static_cast(mask)); +} + +Return Stream::getFormat() { + return AudioFormat(mStream->get_format(mStream)); +} + +Return Stream::getSupportedFormats(getSupportedFormats_cb _hidl_cb) { + String8 halListValue; + Result result = getParam(AudioParameter::keyStreamSupportedFormats, &halListValue); + hidl_vec formats; + FormatVector halFormats; + if (result == Result::OK) { + halFormats = formatsFromString(halListValue.string(), AudioParameter::valueListSeparator); + formats.resize(halFormats.size()); + for (size_t i = 0; i < halFormats.size(); ++i) { + formats[i] = AudioFormat(halFormats[i]); + } + // Legacy get_parameter does not return a status_t, thus can not advertise of failure. + // Note that the method must not return an empty list if this capability is supported. + if (formats.size() == 0) { + result = Result::NOT_SUPPORTED; + } + } +#if MAJOR_VERSION <= 5 + _hidl_cb(formats); +#elif MAJOR_VERSION >= 6 + _hidl_cb(result, formats); +#endif + return Void(); +} + +Return Stream::setFormat(AudioFormat format) { + return setParam(AudioParameter::keyFormat, static_cast(format)); +} + +Return Stream::getAudioProperties(getAudioProperties_cb _hidl_cb) { + uint32_t halSampleRate = mStream->get_sample_rate(mStream); + audio_channel_mask_t halMask = mStream->get_channels(mStream); + audio_format_t halFormat = mStream->get_format(mStream); + _hidl_cb(halSampleRate, AudioChannelBitfield(halMask), AudioFormat(halFormat)); + return Void(); +} + +#else // MAJOR_VERSION <= 6 + +Return Stream::getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) { + String8 halListValue; + Result result = getParam(AudioParameter::keyStreamSupportedFormats, &halListValue); + hidl_vec profiles; + if (result != Result::OK) { + _hidl_cb(result, profiles); + return Void(); + } + // Ensure that the separator is one character, despite that it's defined as a C string. + static_assert(sizeof(AUDIO_PARAMETER_VALUE_LIST_SEPARATOR) == 2); + std::vector halFormats = + splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]); + hidl_vec formats; + (void)HidlUtils::audioFormatsFromHal(halFormats, &formats); + std::vector tempProfiles; + for (const auto& format : formats) { + audio_format_t halFormat; + if (status_t status = HidlUtils::audioFormatToHal(format, &halFormat); status != NO_ERROR) { + continue; + } + AudioParameter context; + context.addInt(String8(AUDIO_PARAMETER_STREAM_FORMAT), int(halFormat)); + // Query supported sample rates for the format. + result = getParam(AudioParameter::keyStreamSupportedSamplingRates, &halListValue, context); + if (result != Result::OK) break; + std::vector halSampleRates = + splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]); + hidl_vec sampleRates; + sampleRates.resize(halSampleRates.size()); + for (size_t i = 0; i < sampleRates.size(); ++i) { + sampleRates[i] = std::stoi(halSampleRates[i]); + } + // Query supported channel masks for the format. + result = getParam(AudioParameter::keyStreamSupportedChannels, &halListValue, context); + if (result != Result::OK) break; + std::vector halChannelMasks = + splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]); + hidl_vec channelMasks; + (void)HidlUtils::audioChannelMasksFromHal(halChannelMasks, &channelMasks); + // Create a profile. + if (channelMasks.size() != 0 && sampleRates.size() != 0) { + tempProfiles.push_back({.format = format, + .sampleRates = std::move(sampleRates), + .channelMasks = std::move(channelMasks)}); + } + } + // Legacy get_parameter does not return a status_t, thus can not advertise of failure. + // Note that the method must not return an empty list if this capability is supported. + if (!tempProfiles.empty()) { + profiles = tempProfiles; + } else { + result = Result::NOT_SUPPORTED; + } + _hidl_cb(result, profiles); + return Void(); +} + +Return Stream::getAudioProperties(getAudioProperties_cb _hidl_cb) { + audio_config_base_t halConfigBase = {mStream->get_sample_rate(mStream), + mStream->get_channels(mStream), + mStream->get_format(mStream)}; + AudioConfigBase configBase = {}; + status_t status = HidlUtils::audioConfigBaseFromHal(halConfigBase, mIsInput, &configBase); + _hidl_cb(Stream::analyzeStatus("get_audio_properties", status), configBase); + return Void(); +} + +Return Stream::setAudioProperties(const AudioConfigBaseOptional& config) { + audio_config_base_t halConfigBase = AUDIO_CONFIG_BASE_INITIALIZER; + bool formatSpecified, sRateSpecified, channelMaskSpecified; + status_t status = HidlUtils::audioConfigBaseOptionalToHal( + config, &halConfigBase, &formatSpecified, &sRateSpecified, &channelMaskSpecified); + if (status != NO_ERROR) { + return Stream::analyzeStatus("set_audio_properties", status); + } + if (sRateSpecified) { + if (Result result = setParam(AudioParameter::keySamplingRate, + static_cast(halConfigBase.sample_rate)); + result != Result::OK) { + return result; + } + } + if (channelMaskSpecified) { + if (Result result = setParam(AudioParameter::keyChannels, + static_cast(halConfigBase.channel_mask)); + result != Result::OK) { + return result; + } + } + if (formatSpecified) { + if (Result result = + setParam(AudioParameter::keyFormat, static_cast(halConfigBase.format)); + result != Result::OK) { + return result; + } + } + return Result::OK; +} + +#endif // MAJOR_VERSION <= 6 + +Return Stream::addEffect(uint64_t effectId) { + effect_handle_t halEffect = EffectMap::getInstance().get(effectId); + if (halEffect != NULL) { + return analyzeStatus("add_audio_effect", mStream->add_audio_effect(mStream, halEffect)); + } else { + ALOGW("Invalid effect ID passed from client: %" PRIu64, effectId); + return Result::INVALID_ARGUMENTS; + } +} + +Return Stream::removeEffect(uint64_t effectId) { + effect_handle_t halEffect = EffectMap::getInstance().get(effectId); + if (halEffect != NULL) { + return analyzeStatus("remove_audio_effect", + mStream->remove_audio_effect(mStream, halEffect)); + } else { + ALOGW("Invalid effect ID passed from client: %" PRIu64, effectId); + return Result::INVALID_ARGUMENTS; + } +} + +Return Stream::standby() { + return analyzeStatus("standby", mStream->standby(mStream)); +} + +Return Stream::setHwAvSync(uint32_t hwAvSync) { + return setParam(AudioParameter::keyStreamHwAvSync, static_cast(hwAvSync)); +} + +#if MAJOR_VERSION == 2 +Return Stream::getDevice() { + int device = 0; + Result retval = getParam(AudioParameter::keyRouting, &device); + return retval == Result::OK ? static_cast(device) : AudioDevice::NONE; +} + +Return Stream::setDevice(const DeviceAddress& address) { + return setParam(AudioParameter::keyRouting, address); +} + +Return Stream::getParameters(const hidl_vec& keys, getParameters_cb _hidl_cb) { + getParametersImpl({} /* context */, keys, _hidl_cb); + return Void(); +} + +Return Stream::setParameters(const hidl_vec& parameters) { + return setParametersImpl({} /* context */, parameters); +} + +Return Stream::setConnectedState(const DeviceAddress& address, bool connected) { + return setParam( + connected ? AudioParameter::keyDeviceConnect : AudioParameter::keyDeviceDisconnect, + address); +} +#elif MAJOR_VERSION >= 4 +Return Stream::getDevices(getDevices_cb _hidl_cb) { + int halDevice = 0; + Result retval = getParam(AudioParameter::keyRouting, &halDevice); + hidl_vec devices; + if (retval == Result::OK) { + devices.resize(1); + retval = Stream::analyzeStatus( + "get_devices", + CoreUtils::deviceAddressFromHal(static_cast(halDevice), nullptr, + &devices[0])); + } + _hidl_cb(retval, devices); + return Void(); +} + +Return Stream::setDevices(const hidl_vec& devices) { + // FIXME: can the legacy API set multiple device with address ? + if (devices.size() > 1) { + return Result::NOT_SUPPORTED; + } + DeviceAddress address{}; + if (devices.size() == 1) { + address = devices[0]; + } + return setParam(AudioParameter::keyRouting, address); +} + +Return Stream::getParameters(const hidl_vec& context, + const hidl_vec& keys, getParameters_cb _hidl_cb) { + getParametersImpl(context, keys, _hidl_cb); + return Void(); +} + +Return Stream::setParameters(const hidl_vec& context, + const hidl_vec& parameters) { + return setParametersImpl(context, parameters); +} +#endif + +Return Stream::start() { + return Result::NOT_SUPPORTED; +} + +Return Stream::stop() { + return Result::NOT_SUPPORTED; +} + +Return Stream::createMmapBuffer(int32_t minSizeFrames __unused, + createMmapBuffer_cb _hidl_cb) { + Result retval(Result::NOT_SUPPORTED); + MmapBufferInfo info; + _hidl_cb(retval, info); + return Void(); +} + +Return Stream::getMmapPosition(getMmapPosition_cb _hidl_cb) { + Result retval(Result::NOT_SUPPORTED); + MmapPosition position; + _hidl_cb(retval, position); + return Void(); +} + +Return Stream::close() { + return Result::NOT_SUPPORTED; +} + +Return Stream::debug(const hidl_handle& fd, const hidl_vec& /* options */) { + if (fd.getNativeHandle() != nullptr && fd->numFds == 1) { + analyzeStatus("dump", mStream->dump(mStream, fd->data[0])); + } + return Void(); +} + +#if MAJOR_VERSION == 2 +Return Stream::debugDump(const hidl_handle& fd) { + return debug(fd, {} /* options */); +} +#endif + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android diff --git a/bluetooth/audio/hal/Stream.h b/bluetooth/audio/hal/Stream.h new file mode 100644 index 0000000..4e79884 --- /dev/null +++ b/bluetooth/audio/hal/Stream.h @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_HARDWARE_AUDIO_STREAM_H +#define ANDROID_HARDWARE_AUDIO_STREAM_H + +// clang-format off +#include PATH(android/hardware/audio/COMMON_TYPES_FILE_VERSION/IStream.h) +// clang-format on + +#include "ParametersUtil.h" + +#include + +#include +#include + +#include + +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::ParametersUtil; +#if MAJOR_VERSION <= 6 +using ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::implementation:: + AudioChannelBitfield; +#endif +using namespace ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CPP_VERSION; + +struct Stream : public IStream, public ParametersUtil { + Stream(bool isInput, audio_stream_t* stream); + + /** 1GiB is the maximum buffer size the HAL client is allowed to request. + * This value has been chosen to be under SIZE_MAX and still big enough + * for all audio use case. + * Keep private for 2.0, put in .hal in 2.1 + */ + static constexpr uint32_t MAX_BUFFER_SIZE = 2 << 30 /* == 1GiB */; + + // Methods from ::android::hardware::audio::CPP_VERSION::IStream follow. + Return getFrameSize() override; + Return getFrameCount() override; + Return getBufferSize() override; +#if MAJOR_VERSION <= 6 + Return getSampleRate() override; +#if MAJOR_VERSION == 2 + Return getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) override; + Return getSupportedChannelMasks(getSupportedChannelMasks_cb _hidl_cb) override; +#endif + Return getSupportedSampleRates(AudioFormat format, getSupportedSampleRates_cb _hidl_cb); + Return getSupportedChannelMasks(AudioFormat format, getSupportedChannelMasks_cb _hidl_cb); + Return setSampleRate(uint32_t sampleRateHz) override; + Return getChannelMask() override; + Return setChannelMask(AudioChannelBitfield mask) override; + Return getFormat() override; + Return getSupportedFormats(getSupportedFormats_cb _hidl_cb) override; + Return setFormat(AudioFormat format) override; +#else + Return getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override; + Return setAudioProperties(const AudioConfigBaseOptional& config) override; +#endif // MAJOR_VERSION <= 6 + Return getAudioProperties(getAudioProperties_cb _hidl_cb) override; + Return addEffect(uint64_t effectId) override; + Return removeEffect(uint64_t effectId) override; + Return standby() override; +#if MAJOR_VERSION == 2 + Return getDevice() override; + Return setDevice(const DeviceAddress& address) override; + Return getParameters(const hidl_vec& keys, + getParameters_cb _hidl_cb) override; + Return setParameters(const hidl_vec& parameters) override; + Return setConnectedState(const DeviceAddress& address, bool connected) override; +#elif MAJOR_VERSION >= 4 + Return getDevices(getDevices_cb _hidl_cb) override; + Return setDevices(const hidl_vec& devices) override; + Return getParameters(const hidl_vec& context, + const hidl_vec& keys, + getParameters_cb _hidl_cb) override; + Return setParameters(const hidl_vec& context, + const hidl_vec& parameters) override; +#endif + Return setHwAvSync(uint32_t hwAvSync) override; + Return start() override; + Return stop() override; + Return createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override; + Return getMmapPosition(getMmapPosition_cb _hidl_cb) override; + Return close() override; + + Return debug(const hidl_handle& fd, const hidl_vec& options) override; +#if MAJOR_VERSION == 2 + Return debugDump(const hidl_handle& fd) override; +#endif + + // Utility methods for extending interfaces. + static Result analyzeStatus(const char* funcName, int status); + static Result analyzeStatus(const char* funcName, int status, + const std::vector& ignoreErrors); + + private: + const bool mIsInput; + audio_stream_t* mStream; + + virtual ~Stream(); + + // Methods from ParametersUtil. + char* halGetParameters(const char* keys) override; + int halSetParameters(const char* keysAndValues) override; +}; + +template +struct StreamMmap : public RefBase { + explicit StreamMmap(T* stream) : mStream(stream) {} + + Return start(); + Return stop(); + Return createMmapBuffer(int32_t minSizeFrames, size_t frameSize, + IStream::createMmapBuffer_cb _hidl_cb); + Return getMmapPosition(IStream::getMmapPosition_cb _hidl_cb); + + private: + StreamMmap() {} + + T* mStream; +}; + +template +Return StreamMmap::start() { + if (mStream->start == NULL) return Result::NOT_SUPPORTED; + int result = mStream->start(mStream); + return Stream::analyzeStatus("start", result); +} + +template +Return StreamMmap::stop() { + if (mStream->stop == NULL) return Result::NOT_SUPPORTED; + int result = mStream->stop(mStream); + return Stream::analyzeStatus("stop", result); +} + +template +Return StreamMmap::createMmapBuffer(int32_t minSizeFrames, size_t frameSize, + IStream::createMmapBuffer_cb _hidl_cb) { + Result retval(Result::NOT_SUPPORTED); + MmapBufferInfo info; + native_handle_t* hidlHandle = nullptr; + + if (mStream->create_mmap_buffer != NULL) { + if (minSizeFrames <= 0) { + retval = Result::INVALID_ARGUMENTS; + goto exit; + } + struct audio_mmap_buffer_info halInfo; + retval = Stream::analyzeStatus( + "create_mmap_buffer", mStream->create_mmap_buffer(mStream, minSizeFrames, &halInfo)); + if (retval == Result::OK) { + hidlHandle = native_handle_create(1, 0); + hidlHandle->data[0] = halInfo.shared_memory_fd; + + // Negative buffer size frame is a legacy hack to indicate that the buffer + // is shareable to applications before the relevant flag was introduced + bool applicationShareable = + halInfo.flags & AUDIO_MMAP_APPLICATION_SHAREABLE || halInfo.buffer_size_frames < 0; + halInfo.buffer_size_frames = abs(halInfo.buffer_size_frames); + info.sharedMemory = // hidl_memory size must always be positive + hidl_memory("audio_buffer", hidlHandle, frameSize * halInfo.buffer_size_frames); +#if MAJOR_VERSION == 2 + if (applicationShareable) { + halInfo.buffer_size_frames *= -1; + } +#else + info.flags = + halInfo.flags | (applicationShareable ? MmapBufferFlag::APPLICATION_SHAREABLE + : MmapBufferFlag::NONE); +#endif + info.bufferSizeFrames = halInfo.buffer_size_frames; + info.burstSizeFrames = halInfo.burst_size_frames; + } + } +exit: + _hidl_cb(retval, info); + if (hidlHandle != nullptr) { + native_handle_delete(hidlHandle); + } + return Void(); +} + +template +Return StreamMmap::getMmapPosition(IStream::getMmapPosition_cb _hidl_cb) { + Result retval(Result::NOT_SUPPORTED); + MmapPosition position; + + if (mStream->get_mmap_position != NULL) { + struct audio_mmap_position halPosition; + retval = Stream::analyzeStatus("get_mmap_position", + mStream->get_mmap_position(mStream, &halPosition)); + if (retval == Result::OK) { + position.timeNanoseconds = halPosition.time_nanoseconds; + position.positionFrames = halPosition.position_frames; + } + } + _hidl_cb(retval, position); + return Void(); +} + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUDIO_STREAM_H diff --git a/bluetooth/audio/hal/StreamIn.cpp b/bluetooth/audio/hal/StreamIn.cpp new file mode 100644 index 0000000..c45fb0c --- /dev/null +++ b/bluetooth/audio/hal/StreamIn.cpp @@ -0,0 +1,607 @@ +/* + * Copyright (C) 2018 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 "StreamInHAL" + +#include "StreamIn.h" +#include "Util.h" +#include "common/all-versions/HidlSupport.h" + +//#define LOG_NDEBUG 0 +#define ATRACE_TAG ATRACE_TAG_AUDIO + +#include +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +using ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::implementation::HidlUtils; +using ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::CoreUtils; +namespace util { +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::util; +} + +namespace { + +class ReadThread : public Thread { + public: + // ReadThread's lifespan never exceeds StreamIn's lifespan. + ReadThread(std::atomic* stop, audio_stream_in_t* stream, StreamIn::CommandMQ* commandMQ, + StreamIn::DataMQ* dataMQ, StreamIn::StatusMQ* statusMQ, EventFlag* efGroup) + : Thread(false /*canCallJava*/), + mStop(stop), + mStream(stream), + mCommandMQ(commandMQ), + mDataMQ(dataMQ), + mStatusMQ(statusMQ), + mEfGroup(efGroup), + mBuffer(nullptr) {} + bool init() { + mBuffer.reset(new (std::nothrow) uint8_t[mDataMQ->getQuantumCount()]); + return mBuffer != nullptr; + } + virtual ~ReadThread() {} + + private: + std::atomic* mStop; + audio_stream_in_t* mStream; + StreamIn::CommandMQ* mCommandMQ; + StreamIn::DataMQ* mDataMQ; + StreamIn::StatusMQ* mStatusMQ; + EventFlag* mEfGroup; + std::unique_ptr mBuffer; + IStreamIn::ReadParameters mParameters; + IStreamIn::ReadStatus mStatus; + + bool threadLoop() override; + + void doGetCapturePosition(); + void doRead(); +}; + +void ReadThread::doRead() { + size_t availableToWrite = mDataMQ->availableToWrite(); + size_t requestedToRead = mParameters.params.read; + if (requestedToRead > availableToWrite) { + ALOGW( + "truncating read data from %d to %d due to insufficient data queue " + "space", + (int32_t)requestedToRead, (int32_t)availableToWrite); + requestedToRead = availableToWrite; + } + ssize_t readResult = mStream->read(mStream, &mBuffer[0], requestedToRead); + mStatus.retval = Result::OK; + if (readResult >= 0) { + mStatus.reply.read = readResult; + if (!mDataMQ->write(&mBuffer[0], readResult)) { + ALOGW("data message queue write failed"); + } + } else { + mStatus.retval = Stream::analyzeStatus("read", readResult); + } +} + +void ReadThread::doGetCapturePosition() { + mStatus.retval = StreamIn::getCapturePositionImpl( + mStream, &mStatus.reply.capturePosition.frames, &mStatus.reply.capturePosition.time); +} + +bool ReadThread::threadLoop() { + // This implementation doesn't return control back to the Thread until it + // decides to stop, + // as the Thread uses mutexes, and this can lead to priority inversion. + while (!std::atomic_load_explicit(mStop, std::memory_order_acquire)) { + uint32_t efState = 0; + mEfGroup->wait(static_cast(MessageQueueFlagBits::NOT_FULL), &efState); + if (!(efState & static_cast(MessageQueueFlagBits::NOT_FULL))) { + continue; // Nothing to do. + } + if (!mCommandMQ->read(&mParameters)) { + continue; // Nothing to do. + } + mStatus.replyTo = mParameters.command; + switch (mParameters.command) { + case IStreamIn::ReadCommand::READ: + doRead(); + break; + case IStreamIn::ReadCommand::GET_CAPTURE_POSITION: + doGetCapturePosition(); + break; + default: + ALOGE("Unknown read thread command code %d", mParameters.command); + mStatus.retval = Result::NOT_SUPPORTED; + break; + } + if (!mStatusMQ->write(&mStatus)) { + ALOGW("status message queue write failed"); + } + mEfGroup->wake(static_cast(MessageQueueFlagBits::NOT_EMPTY)); + } + + return false; +} + +} // namespace + +StreamIn::StreamIn(const sp& device, audio_stream_in_t* stream) + : mDevice(device), + mStream(stream), + mStreamCommon(new Stream(true /*isInput*/, &stream->common)), + mStreamMmap(new StreamMmap(stream)), + mEfGroup(nullptr), + mStopReadThread(false) {} + +StreamIn::~StreamIn() { + ATRACE_CALL(); + close(); + if (mReadThread.get()) { + ATRACE_NAME("mReadThread->join"); + status_t status = mReadThread->join(); + ALOGE_IF(status, "read thread exit error: %s", strerror(-status)); + } + if (mEfGroup) { + status_t status = EventFlag::deleteEventFlag(&mEfGroup); + ALOGE_IF(status, "read MQ event flag deletion error: %s", strerror(-status)); + } +#if MAJOR_VERSION <= 5 + mDevice->closeInputStream(mStream); +#endif + mStream = nullptr; +} + +// Methods from ::android::hardware::audio::CPP_VERSION::IStream follow. +Return StreamIn::getFrameSize() { + return audio_stream_in_frame_size(mStream); +} + +Return StreamIn::getFrameCount() { + return mStreamCommon->getFrameCount(); +} + +Return StreamIn::getBufferSize() { + return mStreamCommon->getBufferSize(); +} + +#if MAJOR_VERSION <= 6 +Return StreamIn::getSampleRate() { + return mStreamCommon->getSampleRate(); +} + +#if MAJOR_VERSION == 2 +Return StreamIn::getSupportedChannelMasks(getSupportedChannelMasks_cb _hidl_cb) { + return mStreamCommon->getSupportedChannelMasks(_hidl_cb); +} +Return StreamIn::getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) { + return mStreamCommon->getSupportedSampleRates(_hidl_cb); +} +#endif + +Return StreamIn::getSupportedChannelMasks(AudioFormat format, + getSupportedChannelMasks_cb _hidl_cb) { + return mStreamCommon->getSupportedChannelMasks(format, _hidl_cb); +} +Return StreamIn::getSupportedSampleRates(AudioFormat format, + getSupportedSampleRates_cb _hidl_cb) { + return mStreamCommon->getSupportedSampleRates(format, _hidl_cb); +} + +Return StreamIn::setSampleRate(uint32_t sampleRateHz) { + return mStreamCommon->setSampleRate(sampleRateHz); +} + +Return StreamIn::getChannelMask() { + return mStreamCommon->getChannelMask(); +} + +Return StreamIn::setChannelMask(AudioChannelBitfield mask) { + return mStreamCommon->setChannelMask(mask); +} + +Return StreamIn::getFormat() { + return mStreamCommon->getFormat(); +} + +Return StreamIn::getSupportedFormats(getSupportedFormats_cb _hidl_cb) { + return mStreamCommon->getSupportedFormats(_hidl_cb); +} + +Return StreamIn::setFormat(AudioFormat format) { + return mStreamCommon->setFormat(format); +} + +#else + +Return StreamIn::getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) { + return mStreamCommon->getSupportedProfiles(_hidl_cb); +} + +Return StreamIn::setAudioProperties(const AudioConfigBaseOptional& config) { + return mStreamCommon->setAudioProperties(config); +} + +#endif // MAJOR_VERSION <= 6 + +Return StreamIn::getAudioProperties(getAudioProperties_cb _hidl_cb) { + return mStreamCommon->getAudioProperties(_hidl_cb); +} + +Return StreamIn::addEffect(uint64_t effectId) { + return mStreamCommon->addEffect(effectId); +} + +Return StreamIn::removeEffect(uint64_t effectId) { + return mStreamCommon->removeEffect(effectId); +} + +Return StreamIn::standby() { + return mStreamCommon->standby(); +} + +Return StreamIn::setHwAvSync(uint32_t hwAvSync) { + return mStreamCommon->setHwAvSync(hwAvSync); +} + +#if MAJOR_VERSION == 2 +Return StreamIn::setConnectedState(const DeviceAddress& address, bool connected) { + return mStreamCommon->setConnectedState(address, connected); +} + +Return StreamIn::getDevice() { + return mStreamCommon->getDevice(); +} + +Return StreamIn::setDevice(const DeviceAddress& address) { + return mStreamCommon->setDevice(address); +} + +Return StreamIn::getParameters(const hidl_vec& keys, getParameters_cb _hidl_cb) { + return mStreamCommon->getParameters(keys, _hidl_cb); +} + +Return StreamIn::setParameters(const hidl_vec& parameters) { + return mStreamCommon->setParameters(parameters); +} + +Return StreamIn::debugDump(const hidl_handle& fd) { + return mStreamCommon->debugDump(fd); +} +#elif MAJOR_VERSION >= 4 +Return StreamIn::getDevices(getDevices_cb _hidl_cb) { + return mStreamCommon->getDevices(_hidl_cb); +} + +Return StreamIn::setDevices(const hidl_vec& devices) { + return mStreamCommon->setDevices(devices); +} +Return StreamIn::getParameters(const hidl_vec& context, + const hidl_vec& keys, getParameters_cb _hidl_cb) { + return mStreamCommon->getParameters(context, keys, _hidl_cb); +} + +Return StreamIn::setParameters(const hidl_vec& context, + const hidl_vec& parameters) { + return mStreamCommon->setParameters(context, parameters); +} +#endif + +Return StreamIn::start() { + return mStreamMmap->start(); +} + +Return StreamIn::stop() { + return mStreamMmap->stop(); +} + +Return StreamIn::createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) { + return mStreamMmap->createMmapBuffer(minSizeFrames, audio_stream_in_frame_size(mStream), + _hidl_cb); +} + +Return StreamIn::getMmapPosition(getMmapPosition_cb _hidl_cb) { + return mStreamMmap->getMmapPosition(_hidl_cb); +} + +Return StreamIn::close() { + if (mStopReadThread.load(std::memory_order_relaxed)) { // only this thread writes + return Result::INVALID_STATE; + } + mStopReadThread.store(true, std::memory_order_release); + if (mEfGroup) { + mEfGroup->wake(static_cast(MessageQueueFlagBits::NOT_FULL)); + } +#if MAJOR_VERSION >= 6 + mDevice->closeInputStream(mStream); +#endif + return Result::OK; +} + +// Methods from ::android::hardware::audio::CPP_VERSION::IStreamIn follow. +Return StreamIn::getAudioSource(getAudioSource_cb _hidl_cb) { + int halSource; + Result retval = mStreamCommon->getParam(AudioParameter::keyInputSource, &halSource); + AudioSource source = {}; + if (retval == Result::OK) { + retval = Stream::analyzeStatus( + "get_audio_source", + HidlUtils::audioSourceFromHal(static_cast(halSource), &source)); + } + _hidl_cb(retval, source); + return Void(); +} + +Return StreamIn::setGain(float gain) { + if (!util::isGainNormalized(gain)) { + ALOGW("Can not set a stream input gain (%f) outside [0,1]", gain); + return Result::INVALID_ARGUMENTS; + } + return Stream::analyzeStatus("set_gain", mStream->set_gain(mStream, gain)); +} + +Return StreamIn::prepareForReading(uint32_t frameSize, uint32_t framesCount, + prepareForReading_cb _hidl_cb) { + status_t status; +#if MAJOR_VERSION <= 6 + ThreadInfo threadInfo = {0, 0}; +#else + int32_t threadInfo = 0; +#endif + + // Wrap the _hidl_cb to return an error + auto sendError = [&threadInfo, &_hidl_cb](Result result) { + _hidl_cb(result, CommandMQ::Descriptor(), DataMQ::Descriptor(), StatusMQ::Descriptor(), + threadInfo); + }; + + // Create message queues. + if (mDataMQ) { + ALOGE("the client attempts to call prepareForReading twice"); + sendError(Result::INVALID_STATE); + return Void(); + } + std::unique_ptr tempCommandMQ(new CommandMQ(1)); + + // Check frameSize and framesCount + if (frameSize == 0 || framesCount == 0) { + ALOGE("Null frameSize (%u) or framesCount (%u)", frameSize, framesCount); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + + if (frameSize > Stream::MAX_BUFFER_SIZE / framesCount) { + ALOGE("Buffer too big: %u*%u bytes > MAX_BUFFER_SIZE (%u)", frameSize, framesCount, + Stream::MAX_BUFFER_SIZE); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + std::unique_ptr tempDataMQ(new DataMQ(frameSize * framesCount, true /* EventFlag */)); + + std::unique_ptr tempStatusMQ(new StatusMQ(1)); + if (!tempCommandMQ->isValid() || !tempDataMQ->isValid() || !tempStatusMQ->isValid()) { + ALOGE_IF(!tempCommandMQ->isValid(), "command MQ is invalid"); + ALOGE_IF(!tempDataMQ->isValid(), "data MQ is invalid"); + ALOGE_IF(!tempStatusMQ->isValid(), "status MQ is invalid"); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + EventFlag* tempRawEfGroup{}; + status = EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &tempRawEfGroup); + std::unique_ptr tempElfGroup( + tempRawEfGroup, [](auto* ef) { EventFlag::deleteEventFlag(&ef); }); + if (status != OK || !tempElfGroup) { + ALOGE("failed creating event flag for data MQ: %s", strerror(-status)); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + + // Create and launch the thread. + auto tempReadThread = + sp::make(&mStopReadThread, mStream, tempCommandMQ.get(), tempDataMQ.get(), + tempStatusMQ.get(), tempElfGroup.get()); + if (!tempReadThread->init()) { + ALOGW("failed to start reader thread: %s", strerror(-status)); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + status = tempReadThread->run("reader", PRIORITY_URGENT_AUDIO); + if (status != OK) { + ALOGW("failed to start reader thread: %s", strerror(-status)); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + + mCommandMQ = std::move(tempCommandMQ); + mDataMQ = std::move(tempDataMQ); + mStatusMQ = std::move(tempStatusMQ); + mReadThread = tempReadThread; + mEfGroup = tempElfGroup.release(); +#if MAJOR_VERSION <= 6 + threadInfo.pid = getpid(); + threadInfo.tid = mReadThread->getTid(); +#else + threadInfo = mReadThread->getTid(); +#endif + _hidl_cb(Result::OK, *mCommandMQ->getDesc(), *mDataMQ->getDesc(), *mStatusMQ->getDesc(), + threadInfo); + return Void(); +} + +Return StreamIn::getInputFramesLost() { + return mStream->get_input_frames_lost(mStream); +} + +// static +Result StreamIn::getCapturePositionImpl(audio_stream_in_t* stream, uint64_t* frames, + uint64_t* time) { + // HAL may have a stub function, always returning ENOSYS, don't + // spam the log in this case. + static const std::vector ignoredErrors{ENOSYS}; + Result retval(Result::NOT_SUPPORTED); + if (stream->get_capture_position == NULL) return retval; + int64_t halFrames, halTime; + retval = Stream::analyzeStatus("get_capture_position", + stream->get_capture_position(stream, &halFrames, &halTime), + ignoredErrors); + if (retval == Result::OK) { + *frames = halFrames; + *time = halTime; + } + return retval; +}; + +Return StreamIn::getCapturePosition(getCapturePosition_cb _hidl_cb) { + uint64_t frames = 0, time = 0; + Result retval = getCapturePositionImpl(mStream, &frames, &time); + _hidl_cb(retval, frames, time); + return Void(); +} + +Return StreamIn::debug(const hidl_handle& fd, const hidl_vec& options) { + return mStreamCommon->debug(fd, options); +} + +#if MAJOR_VERSION >= 4 +Result StreamIn::doUpdateSinkMetadata(const SinkMetadata& sinkMetadata) { + std::vector halTracks; +#if MAJOR_VERSION <= 6 + (void)CoreUtils::sinkMetadataToHal(sinkMetadata, &halTracks); +#else + // Validate whether a conversion to V7 is possible. This is needed + // to have a consistent behavior of the HAL regardless of the API + // version of the legacy HAL (and also to be consistent with openInputStream). + std::vector halTracksV7; + if (status_t status = CoreUtils::sinkMetadataToHalV7( + sinkMetadata, false /*ignoreNonVendorTags*/, &halTracksV7); + status == NO_ERROR) { + halTracks.reserve(halTracksV7.size()); + for (auto metadata_v7 : halTracksV7) { + halTracks.push_back(std::move(metadata_v7.base)); + } + } else { + return Stream::analyzeStatus("sinkMetadataToHal", status); + } +#endif // MAJOR_VERSION <= 6 + const sink_metadata_t halMetadata = { + .track_count = halTracks.size(), + .tracks = halTracks.data(), + }; + mStream->update_sink_metadata(mStream, &halMetadata); + return Result::OK; +} + +#if MAJOR_VERSION >= 7 +Result StreamIn::doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata) { + std::vector halTracks; + if (status_t status = CoreUtils::sinkMetadataToHalV7(sinkMetadata, + false /*ignoreNonVendorTags*/, &halTracks); + status != NO_ERROR) { + return Stream::analyzeStatus("sinkMetadataToHal", status); + } + const sink_metadata_v7_t halMetadata = { + .track_count = halTracks.size(), + .tracks = halTracks.data(), + }; + mStream->update_sink_metadata_v7(mStream, &halMetadata); + return Result::OK; +} +#endif // MAJOR_VERSION >= 7 + +#if MAJOR_VERSION <= 6 +Return StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) { + if (mStream->update_sink_metadata == nullptr) { + return Void(); // not supported by the HAL + } + (void)doUpdateSinkMetadata(sinkMetadata); + return Void(); +} +#elif MAJOR_VERSION >= 7 +Return StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) { + if (mDevice->version() < AUDIO_DEVICE_API_VERSION_3_2) { + if (mStream->update_sink_metadata == nullptr) { + return Result::NOT_SUPPORTED; + } + return doUpdateSinkMetadata(sinkMetadata); + } else { + if (mStream->update_sink_metadata_v7 == nullptr) { + return Result::NOT_SUPPORTED; + } + return doUpdateSinkMetadataV7(sinkMetadata); + } +} +#endif + +Return StreamIn::getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) { + Result retval = Result::NOT_SUPPORTED; + size_t actual_mics = AUDIO_MICROPHONE_MAX_COUNT; + audio_microphone_characteristic_t mic_array[AUDIO_MICROPHONE_MAX_COUNT]; + + hidl_vec microphones; + if (mStream->get_active_microphones != NULL && + mStream->get_active_microphones(mStream, &mic_array[0], &actual_mics) == 0) { + microphones.resize(actual_mics); + for (size_t i = 0; i < actual_mics; ++i) { + (void)CoreUtils::microphoneInfoFromHal(mic_array[i], µphones[i]); + } + retval = Result::OK; + } + + _hidl_cb(retval, microphones); + return Void(); +} +#endif + +#if MAJOR_VERSION >= 5 +Return StreamIn::setMicrophoneDirection(MicrophoneDirection direction) { + if (mStream->set_microphone_direction == nullptr) { + return Result::NOT_SUPPORTED; + } + if (!common::utils::isValidHidlEnum(direction)) { + ALOGE("%s: Invalid direction %d", __func__, direction); + return Result::INVALID_ARGUMENTS; + } + return Stream::analyzeStatus( + "set_microphone_direction", + mStream->set_microphone_direction( + mStream, static_cast(direction))); +} + +Return StreamIn::setMicrophoneFieldDimension(float zoom) { + if (mStream->set_microphone_field_dimension == nullptr) { + return Result::NOT_SUPPORTED; + } + if (std::isnan(zoom) || zoom < -1 || zoom > 1) { + ALOGE("%s: Invalid zoom %f", __func__, zoom); + return Result::INVALID_ARGUMENTS; + } + return Stream::analyzeStatus("set_microphone_field_dimension", + mStream->set_microphone_field_dimension(mStream, zoom)); +} + +#endif + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android diff --git a/bluetooth/audio/hal/StreamIn.h b/bluetooth/audio/hal/StreamIn.h new file mode 100644 index 0000000..4627eec --- /dev/null +++ b/bluetooth/audio/hal/StreamIn.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_HARDWARE_AUDIO_STREAMIN_H +#define ANDROID_HARDWARE_AUDIO_STREAMIN_H + +// clang-format off +#include PATH(android/hardware/audio/CORE_TYPES_FILE_VERSION/IStreamIn.h) +// clang-format on + +#include "Device.h" +#include "Stream.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using namespace ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CPP_VERSION; + +struct StreamIn : public IStreamIn { + typedef MessageQueue CommandMQ; + typedef MessageQueue DataMQ; + typedef MessageQueue StatusMQ; + + StreamIn(const sp& device, audio_stream_in_t* stream); + + // Methods from ::android::hardware::audio::CPP_VERSION::IStream follow. + Return getFrameSize() override; + Return getFrameCount() override; + Return getBufferSize() override; +#if MAJOR_VERSION <= 6 + Return getSampleRate() override; +#if MAJOR_VERSION == 2 + Return getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) override; + Return getSupportedChannelMasks(getSupportedChannelMasks_cb _hidl_cb) override; +#endif + Return getSupportedSampleRates(AudioFormat format, getSupportedSampleRates_cb _hidl_cb); + Return getSupportedChannelMasks(AudioFormat format, getSupportedChannelMasks_cb _hidl_cb); + Return setSampleRate(uint32_t sampleRateHz) override; + Return getChannelMask() override; + Return setChannelMask(AudioChannelBitfield mask) override; + Return getFormat() override; + Return getSupportedFormats(getSupportedFormats_cb _hidl_cb) override; + Return setFormat(AudioFormat format) override; +#else + Return getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override; + Return setAudioProperties(const AudioConfigBaseOptional& config) override; +#endif // MAJOR_VERSION <= 6 + Return getAudioProperties(getAudioProperties_cb _hidl_cb) override; + Return addEffect(uint64_t effectId) override; + Return removeEffect(uint64_t effectId) override; + Return standby() override; +#if MAJOR_VERSION == 2 + Return getDevice() override; + Return setDevice(const DeviceAddress& address) override; + Return getParameters(const hidl_vec& keys, + getParameters_cb _hidl_cb) override; + Return setParameters(const hidl_vec& parameters) override; + Return setConnectedState(const DeviceAddress& address, bool connected) override; +#elif MAJOR_VERSION >= 4 + Return getDevices(getDevices_cb _hidl_cb) override; + Return setDevices(const hidl_vec& devices) override; + Return getParameters(const hidl_vec& context, + const hidl_vec& keys, + getParameters_cb _hidl_cb) override; + Return setParameters(const hidl_vec& context, + const hidl_vec& parameters) override; +#endif + Return setHwAvSync(uint32_t hwAvSync) override; + Return close() override; + + Return debug(const hidl_handle& fd, const hidl_vec& options) override; +#if MAJOR_VERSION == 2 + Return debugDump(const hidl_handle& fd) override; +#endif + + // Methods from ::android::hardware::audio::CPP_VERSION::IStreamIn follow. + Return getAudioSource(getAudioSource_cb _hidl_cb) override; + Return setGain(float gain) override; + Return prepareForReading(uint32_t frameSize, uint32_t framesCount, + prepareForReading_cb _hidl_cb) override; + Return getInputFramesLost() override; + Return getCapturePosition(getCapturePosition_cb _hidl_cb) override; + Return start() override; + Return stop() override; + Return createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override; + Return getMmapPosition(getMmapPosition_cb _hidl_cb) override; +#if MAJOR_VERSION >= 4 +#if MAJOR_VERSION <= 6 + Return updateSinkMetadata(const SinkMetadata& sinkMetadata) override; +#else + Return updateSinkMetadata(const SinkMetadata& sinkMetadata) override; +#endif + Return getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) override; +#endif // MAJOR_VERSION >= 4 +#if MAJOR_VERSION >= 5 + Return setMicrophoneDirection(MicrophoneDirection direction) override; + Return setMicrophoneFieldDimension(float zoom) override; +#endif + static Result getCapturePositionImpl(audio_stream_in_t* stream, uint64_t* frames, + uint64_t* time); + + private: +#if MAJOR_VERSION >= 4 + Result doUpdateSinkMetadata(const SinkMetadata& sinkMetadata); +#if MAJOR_VERSION >= 7 + Result doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata); +#endif +#endif // MAJOR_VERSION >= 4 + + const sp mDevice; + audio_stream_in_t* mStream; + const sp mStreamCommon; + const sp> mStreamMmap; + std::unique_ptr mCommandMQ; + std::unique_ptr mDataMQ; + std::unique_ptr mStatusMQ; + EventFlag* mEfGroup; + std::atomic mStopReadThread; + sp mReadThread; + + virtual ~StreamIn(); +}; + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUDIO_STREAMIN_H diff --git a/bluetooth/audio/hal/StreamOut.cpp b/bluetooth/audio/hal/StreamOut.cpp new file mode 100644 index 0000000..be85a9f --- /dev/null +++ b/bluetooth/audio/hal/StreamOut.cpp @@ -0,0 +1,837 @@ +/* + * Copyright (C) 2018 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 "StreamOutHAL" + +#include "StreamOut.h" +#include "Util.h" + +//#define LOG_NDEBUG 0 +#define ATRACE_TAG ATRACE_TAG_AUDIO + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +using ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::implementation::HidlUtils; +using ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::CoreUtils; +namespace util { +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION::implementation::util; +} + +namespace { + +class WriteThread : public Thread { + public: + // WriteThread's lifespan never exceeds StreamOut's lifespan. + WriteThread(std::atomic* stop, audio_stream_out_t* stream, + StreamOut::CommandMQ* commandMQ, StreamOut::DataMQ* dataMQ, + StreamOut::StatusMQ* statusMQ, EventFlag* efGroup) + : Thread(false /*canCallJava*/), + mStop(stop), + mStream(stream), + mCommandMQ(commandMQ), + mDataMQ(dataMQ), + mStatusMQ(statusMQ), + mEfGroup(efGroup), + mBuffer(nullptr) {} + bool init() { + mBuffer.reset(new (std::nothrow) uint8_t[mDataMQ->getQuantumCount()]); + return mBuffer != nullptr; + } + virtual ~WriteThread() {} + + private: + std::atomic* mStop; + audio_stream_out_t* mStream; + StreamOut::CommandMQ* mCommandMQ; + StreamOut::DataMQ* mDataMQ; + StreamOut::StatusMQ* mStatusMQ; + EventFlag* mEfGroup; + std::unique_ptr mBuffer; + IStreamOut::WriteStatus mStatus; + + bool threadLoop() override; + + void doGetLatency(); + void doGetPresentationPosition(); + void doWrite(); +}; + +void WriteThread::doWrite() { + const size_t availToRead = mDataMQ->availableToRead(); + mStatus.retval = Result::OK; + mStatus.reply.written = 0; + if (mDataMQ->read(&mBuffer[0], availToRead)) { + ssize_t writeResult = mStream->write(mStream, &mBuffer[0], availToRead); + if (writeResult >= 0) { + mStatus.reply.written = writeResult; + } else { + mStatus.retval = Stream::analyzeStatus("write", writeResult); + } + } +} + +void WriteThread::doGetPresentationPosition() { + mStatus.retval = + StreamOut::getPresentationPositionImpl(mStream, &mStatus.reply.presentationPosition.frames, + &mStatus.reply.presentationPosition.timeStamp); +} + +void WriteThread::doGetLatency() { + mStatus.retval = Result::OK; + mStatus.reply.latencyMs = mStream->get_latency(mStream); +} + +bool WriteThread::threadLoop() { + // This implementation doesn't return control back to the Thread until it + // decides to stop, + // as the Thread uses mutexes, and this can lead to priority inversion. + while (!std::atomic_load_explicit(mStop, std::memory_order_acquire)) { + uint32_t efState = 0; + mEfGroup->wait(static_cast(MessageQueueFlagBits::NOT_EMPTY), &efState); + if (!(efState & static_cast(MessageQueueFlagBits::NOT_EMPTY))) { + continue; // Nothing to do. + } + if (!mCommandMQ->read(&mStatus.replyTo)) { + continue; // Nothing to do. + } + switch (mStatus.replyTo) { + case IStreamOut::WriteCommand::WRITE: + doWrite(); + break; + case IStreamOut::WriteCommand::GET_PRESENTATION_POSITION: + doGetPresentationPosition(); + break; + case IStreamOut::WriteCommand::GET_LATENCY: + doGetLatency(); + break; + default: + ALOGE("Unknown write thread command code %d", mStatus.replyTo); + mStatus.retval = Result::NOT_SUPPORTED; + break; + } + if (!mStatusMQ->write(&mStatus)) { + ALOGE("status message queue write failed"); + } + mEfGroup->wake(static_cast(MessageQueueFlagBits::NOT_FULL)); + } + + return false; +} + +} // namespace + +StreamOut::StreamOut(const sp& device, audio_stream_out_t* stream) + : mDevice(device), + mStream(stream), + mStreamCommon(new Stream(false /*isInput*/, &stream->common)), + mStreamMmap(new StreamMmap(stream)), + mEfGroup(nullptr), + mStopWriteThread(false) {} + +StreamOut::~StreamOut() { + ATRACE_CALL(); + (void)close(); + if (mWriteThread.get()) { + ATRACE_NAME("mWriteThread->join"); + status_t status = mWriteThread->join(); + ALOGE_IF(status, "write thread exit error: %s", strerror(-status)); + } + if (mEfGroup) { + status_t status = EventFlag::deleteEventFlag(&mEfGroup); + ALOGE_IF(status, "write MQ event flag deletion error: %s", strerror(-status)); + } + mCallback = nullptr; +#if MAJOR_VERSION <= 5 + mDevice->closeOutputStream(mStream); + // Closing the output stream in the HAL waits for the callback to finish, + // and joins the callback thread. Thus is it guaranteed that the callback + // thread will not be accessing our object anymore. +#endif + mStream = nullptr; +} + +// Methods from ::android::hardware::audio::CPP_VERSION::IStream follow. +Return StreamOut::getFrameSize() { + return audio_stream_out_frame_size(mStream); +} + +Return StreamOut::getFrameCount() { + return mStreamCommon->getFrameCount(); +} + +Return StreamOut::getBufferSize() { + return mStreamCommon->getBufferSize(); +} + +#if MAJOR_VERSION <= 6 +Return StreamOut::getSampleRate() { + return mStreamCommon->getSampleRate(); +} + +#if MAJOR_VERSION == 2 +Return StreamOut::getSupportedChannelMasks(getSupportedChannelMasks_cb _hidl_cb) { + return mStreamCommon->getSupportedChannelMasks(_hidl_cb); +} +Return StreamOut::getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) { + return mStreamCommon->getSupportedSampleRates(_hidl_cb); +} +#endif + +Return StreamOut::getSupportedChannelMasks(AudioFormat format, + getSupportedChannelMasks_cb _hidl_cb) { + return mStreamCommon->getSupportedChannelMasks(format, _hidl_cb); +} +Return StreamOut::getSupportedSampleRates(AudioFormat format, + getSupportedSampleRates_cb _hidl_cb) { + return mStreamCommon->getSupportedSampleRates(format, _hidl_cb); +} + +Return StreamOut::setSampleRate(uint32_t sampleRateHz) { + return mStreamCommon->setSampleRate(sampleRateHz); +} + +Return StreamOut::getChannelMask() { + return mStreamCommon->getChannelMask(); +} + +Return StreamOut::setChannelMask(AudioChannelBitfield mask) { + return mStreamCommon->setChannelMask(mask); +} + +Return StreamOut::getFormat() { + return mStreamCommon->getFormat(); +} + +Return StreamOut::getSupportedFormats(getSupportedFormats_cb _hidl_cb) { + return mStreamCommon->getSupportedFormats(_hidl_cb); +} + +Return StreamOut::setFormat(AudioFormat format) { + return mStreamCommon->setFormat(format); +} + +#else + +Return StreamOut::getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) { + return mStreamCommon->getSupportedProfiles(_hidl_cb); +} + +Return StreamOut::setAudioProperties(const AudioConfigBaseOptional& config) { + return mStreamCommon->setAudioProperties(config); +} + +#endif // MAJOR_VERSION <= 6 + +Return StreamOut::getAudioProperties(getAudioProperties_cb _hidl_cb) { + return mStreamCommon->getAudioProperties(_hidl_cb); +} + +Return StreamOut::addEffect(uint64_t effectId) { + return mStreamCommon->addEffect(effectId); +} + +Return StreamOut::removeEffect(uint64_t effectId) { + return mStreamCommon->removeEffect(effectId); +} + +Return StreamOut::standby() { + return mStreamCommon->standby(); +} + +Return StreamOut::setHwAvSync(uint32_t hwAvSync) { + return mStreamCommon->setHwAvSync(hwAvSync); +} + +#if MAJOR_VERSION == 2 +Return StreamOut::setConnectedState(const DeviceAddress& address, bool connected) { + return mStreamCommon->setConnectedState(address, connected); +} + +Return StreamOut::getDevice() { + return mStreamCommon->getDevice(); +} + +Return StreamOut::setDevice(const DeviceAddress& address) { + return mStreamCommon->setDevice(address); +} + +Return StreamOut::getParameters(const hidl_vec& keys, + getParameters_cb _hidl_cb) { + return mStreamCommon->getParameters(keys, _hidl_cb); +} + +Return StreamOut::setParameters(const hidl_vec& parameters) { + return mStreamCommon->setParameters(parameters); +} + +Return StreamOut::debugDump(const hidl_handle& fd) { + return mStreamCommon->debugDump(fd); +} +#elif MAJOR_VERSION >= 4 +Return StreamOut::getDevices(getDevices_cb _hidl_cb) { + return mStreamCommon->getDevices(_hidl_cb); +} + +Return StreamOut::setDevices(const hidl_vec& devices) { + return mStreamCommon->setDevices(devices); +} +Return StreamOut::getParameters(const hidl_vec& context, + const hidl_vec& keys, + getParameters_cb _hidl_cb) { + return mStreamCommon->getParameters(context, keys, _hidl_cb); +} + +Return StreamOut::setParameters(const hidl_vec& context, + const hidl_vec& parameters) { + return mStreamCommon->setParameters(context, parameters); +} +#endif + +Return StreamOut::close() { + if (mStopWriteThread.load(std::memory_order_relaxed)) { // only this thread writes + return Result::INVALID_STATE; + } + mStopWriteThread.store(true, std::memory_order_release); + if (mEfGroup) { + mEfGroup->wake(static_cast(MessageQueueFlagBits::NOT_EMPTY)); + } +#if MAJOR_VERSION >= 6 + mDevice->closeOutputStream(mStream); +#endif + return Result::OK; +} + +// Methods from ::android::hardware::audio::CPP_VERSION::IStreamOut follow. +Return StreamOut::getLatency() { + return mStream->get_latency(mStream); +} + +Return StreamOut::setVolume(float left, float right) { + if (mStream->set_volume == NULL) { + return Result::NOT_SUPPORTED; + } + if (!util::isGainNormalized(left)) { + ALOGW("Can not set a stream output volume {%f, %f} outside [0,1]", left, right); + return Result::INVALID_ARGUMENTS; + } + return Stream::analyzeStatus("set_volume", mStream->set_volume(mStream, left, right), + {ENOSYS} /*ignore*/); +} + +Return StreamOut::prepareForWriting(uint32_t frameSize, uint32_t framesCount, + prepareForWriting_cb _hidl_cb) { + status_t status; +#if MAJOR_VERSION <= 6 + ThreadInfo threadInfo = {0, 0}; +#else + int32_t threadInfo = 0; +#endif + + // Wrap the _hidl_cb to return an error + auto sendError = [&threadInfo, &_hidl_cb](Result result) { + _hidl_cb(result, CommandMQ::Descriptor(), DataMQ::Descriptor(), StatusMQ::Descriptor(), + threadInfo); + }; + + // Create message queues. + if (mDataMQ) { + ALOGE("the client attempts to call prepareForWriting twice"); + sendError(Result::INVALID_STATE); + return Void(); + } + std::unique_ptr tempCommandMQ(new CommandMQ(1)); + + // Check frameSize and framesCount + if (frameSize == 0 || framesCount == 0) { + ALOGE("Null frameSize (%u) or framesCount (%u)", frameSize, framesCount); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + if (frameSize > Stream::MAX_BUFFER_SIZE / framesCount) { + ALOGE("Buffer too big: %u*%u bytes > MAX_BUFFER_SIZE (%u)", frameSize, framesCount, + Stream::MAX_BUFFER_SIZE); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + std::unique_ptr tempDataMQ(new DataMQ(frameSize * framesCount, true /* EventFlag */)); + + std::unique_ptr tempStatusMQ(new StatusMQ(1)); + if (!tempCommandMQ->isValid() || !tempDataMQ->isValid() || !tempStatusMQ->isValid()) { + ALOGE_IF(!tempCommandMQ->isValid(), "command MQ is invalid"); + ALOGE_IF(!tempDataMQ->isValid(), "data MQ is invalid"); + ALOGE_IF(!tempStatusMQ->isValid(), "status MQ is invalid"); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + EventFlag* tempRawEfGroup{}; + status = EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &tempRawEfGroup); + std::unique_ptr tempElfGroup( + tempRawEfGroup, [](auto* ef) { EventFlag::deleteEventFlag(&ef); }); + if (status != OK || !tempElfGroup) { + ALOGE("failed creating event flag for data MQ: %s", strerror(-status)); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + + // Create and launch the thread. + auto tempWriteThread = + sp::make(&mStopWriteThread, mStream, tempCommandMQ.get(), tempDataMQ.get(), + tempStatusMQ.get(), tempElfGroup.get()); + if (!tempWriteThread->init()) { + ALOGW("failed to start writer thread: %s", strerror(-status)); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + status = tempWriteThread->run("writer", PRIORITY_URGENT_AUDIO); + if (status != OK) { + ALOGW("failed to start writer thread: %s", strerror(-status)); + sendError(Result::INVALID_ARGUMENTS); + return Void(); + } + + mCommandMQ = std::move(tempCommandMQ); + mDataMQ = std::move(tempDataMQ); + mStatusMQ = std::move(tempStatusMQ); + mWriteThread = tempWriteThread; + mEfGroup = tempElfGroup.release(); +#if MAJOR_VERSION <= 6 + threadInfo.pid = getpid(); + threadInfo.tid = mWriteThread->getTid(); +#else + threadInfo = mWriteThread->getTid(); +#endif + _hidl_cb(Result::OK, *mCommandMQ->getDesc(), *mDataMQ->getDesc(), *mStatusMQ->getDesc(), + threadInfo); + return Void(); +} + +Return StreamOut::getRenderPosition(getRenderPosition_cb _hidl_cb) { + uint32_t halDspFrames; + Result retval = Stream::analyzeStatus("get_render_position", + mStream->get_render_position(mStream, &halDspFrames), + {ENOSYS} /*ignore*/); + _hidl_cb(retval, halDspFrames); + return Void(); +} + +Return StreamOut::getNextWriteTimestamp(getNextWriteTimestamp_cb _hidl_cb) { + Result retval(Result::NOT_SUPPORTED); + int64_t timestampUs = 0; + if (mStream->get_next_write_timestamp != NULL) { + retval = Stream::analyzeStatus("get_next_write_timestamp", + mStream->get_next_write_timestamp(mStream, ×tampUs), + {ENOSYS} /*ignore*/); + } + _hidl_cb(retval, timestampUs); + return Void(); +} + +Return StreamOut::setCallback(const sp& callback) { + if (mStream->set_callback == NULL) return Result::NOT_SUPPORTED; + // Safe to pass 'this' because it is guaranteed that the callback thread + // is joined prior to exit from StreamOut's destructor. + int result = mStream->set_callback(mStream, StreamOut::asyncCallback, this); + if (result == 0) { + mCallback = callback; + } + return Stream::analyzeStatus("set_callback", result, {ENOSYS} /*ignore*/); +} + +Return StreamOut::clearCallback() { + if (mStream->set_callback == NULL) return Result::NOT_SUPPORTED; + mCallback = nullptr; + return Result::OK; +} + +// static +int StreamOut::asyncCallback(stream_callback_event_t event, void*, void* cookie) { + // It is guaranteed that the callback thread is joined prior + // to exiting from StreamOut's destructor. Must *not* use sp + // here because it can make this code the last owner of StreamOut, + // and an attempt to run the destructor on the callback thread + // will cause a deadlock in the legacy HAL code. + StreamOut* self = reinterpret_cast(cookie); + // It's correct to hold an sp<> to callback because the reference + // in the StreamOut instance can be cleared in the meantime. There is + // no difference on which thread to run IStreamOutCallback's destructor. + sp callback = self->mCallback.load(); + if (callback.get() == nullptr) return 0; + ALOGV("asyncCallback() event %d", event); + Return result; + switch (event) { + case STREAM_CBK_EVENT_WRITE_READY: + result = callback->onWriteReady(); + break; + case STREAM_CBK_EVENT_DRAIN_READY: + result = callback->onDrainReady(); + break; + case STREAM_CBK_EVENT_ERROR: + result = callback->onError(); + break; + default: + ALOGW("asyncCallback() unknown event %d", event); + break; + } + ALOGW_IF(!result.isOk(), "Client callback failed: %s", result.description().c_str()); + return 0; +} + +Return StreamOut::supportsPauseAndResume(supportsPauseAndResume_cb _hidl_cb) { + _hidl_cb(mStream->pause != NULL, mStream->resume != NULL); + return Void(); +} + +Return StreamOut::pause() { + return mStream->pause != NULL + ? Stream::analyzeStatus("pause", mStream->pause(mStream), {ENOSYS} /*ignore*/) + : Result::NOT_SUPPORTED; +} + +Return StreamOut::resume() { + return mStream->resume != NULL + ? Stream::analyzeStatus("resume", mStream->resume(mStream), {ENOSYS} /*ignore*/) + : Result::NOT_SUPPORTED; +} + +Return StreamOut::supportsDrain() { + return mStream->drain != NULL; +} + +Return StreamOut::drain(AudioDrain type) { + audio_drain_type_t halDrainType = + type == AudioDrain::EARLY_NOTIFY ? AUDIO_DRAIN_EARLY_NOTIFY : AUDIO_DRAIN_ALL; + return mStream->drain != NULL + ? Stream::analyzeStatus("drain", mStream->drain(mStream, halDrainType), + {ENOSYS} /*ignore*/) + : Result::NOT_SUPPORTED; +} + +Return StreamOut::flush() { + return mStream->flush != NULL + ? Stream::analyzeStatus("flush", mStream->flush(mStream), {ENOSYS} /*ignore*/) + : Result::NOT_SUPPORTED; +} + +// static +Result StreamOut::getPresentationPositionImpl(audio_stream_out_t* stream, uint64_t* frames, + TimeSpec* timeStamp) { + // Don't logspam on EINVAL--it's normal for get_presentation_position + // to return it sometimes. EAGAIN may be returned by A2DP audio HAL + // implementation. ENODATA can also be reported while the writer is + // continuously querying it, but the stream has been stopped. + static const std::vector ignoredErrors{EINVAL, EAGAIN, ENODATA, ENOSYS}; + Result retval(Result::NOT_SUPPORTED); + if (stream->get_presentation_position == NULL) return retval; + struct timespec halTimeStamp; + retval = Stream::analyzeStatus("get_presentation_position", + stream->get_presentation_position(stream, frames, &halTimeStamp), + ignoredErrors); + if (retval == Result::OK) { + timeStamp->tvSec = halTimeStamp.tv_sec; + timeStamp->tvNSec = halTimeStamp.tv_nsec; + } + return retval; +} + +Return StreamOut::getPresentationPosition(getPresentationPosition_cb _hidl_cb) { + uint64_t frames = 0; + TimeSpec timeStamp = {0, 0}; + Result retval = getPresentationPositionImpl(mStream, &frames, &timeStamp); + _hidl_cb(retval, frames, timeStamp); + return Void(); +} + +Return StreamOut::start() { + return mStreamMmap->start(); +} + +Return StreamOut::stop() { + return mStreamMmap->stop(); +} + +Return StreamOut::createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) { + return mStreamMmap->createMmapBuffer(minSizeFrames, audio_stream_out_frame_size(mStream), + _hidl_cb); +} + +Return StreamOut::getMmapPosition(getMmapPosition_cb _hidl_cb) { + return mStreamMmap->getMmapPosition(_hidl_cb); +} + +Return StreamOut::debug(const hidl_handle& fd, const hidl_vec& options) { + return mStreamCommon->debug(fd, options); +} + +#if MAJOR_VERSION >= 4 +Result StreamOut::doUpdateSourceMetadata(const SourceMetadata& sourceMetadata) { + std::vector halTracks; +#if MAJOR_VERSION <= 6 + (void)CoreUtils::sourceMetadataToHal(sourceMetadata, &halTracks); +#else + // Validate whether a conversion to V7 is possible. This is needed + // to have a consistent behavior of the HAL regardless of the API + // version of the legacy HAL (and also to be consistent with openOutputStream). + std::vector halTracksV7; + if (status_t status = CoreUtils::sourceMetadataToHalV7( + sourceMetadata, false /*ignoreNonVendorTags*/, &halTracksV7); + status == NO_ERROR) { + halTracks.reserve(halTracksV7.size()); + for (auto metadata_v7 : halTracksV7) { + halTracks.push_back(std::move(metadata_v7.base)); + } + } else { + return Stream::analyzeStatus("sourceMetadataToHal", status); + } +#endif // MAJOR_VERSION <= 6 + const source_metadata_t halMetadata = { + .track_count = halTracks.size(), + .tracks = halTracks.data(), + }; + mStream->update_source_metadata(mStream, &halMetadata); + return Result::OK; +} + +#if MAJOR_VERSION >= 7 +Result StreamOut::doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata) { + std::vector halTracks; + if (status_t status = CoreUtils::sourceMetadataToHalV7( + sourceMetadata, false /*ignoreNonVendorTags*/, &halTracks); + status != NO_ERROR) { + return Stream::analyzeStatus("sourceMetadataToHal", status); + } + const source_metadata_v7_t halMetadata = { + .track_count = halTracks.size(), + .tracks = halTracks.data(), + }; + mStream->update_source_metadata_v7(mStream, &halMetadata); + return Result::OK; +} +#endif // MAJOR_VERSION >= 7 + +#if MAJOR_VERSION <= 6 +Return StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) { + if (mStream->update_source_metadata == nullptr) { + return Void(); // not supported by the HAL + } + (void)doUpdateSourceMetadata(sourceMetadata); + return Void(); +} +#elif MAJOR_VERSION >= 7 +Return StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) { + if (mDevice->version() < AUDIO_DEVICE_API_VERSION_3_2) { + if (mStream->update_source_metadata == nullptr) { + return Result::NOT_SUPPORTED; + } + return doUpdateSourceMetadata(sourceMetadata); + } else { + if (mStream->update_source_metadata_v7 == nullptr) { + return Result::NOT_SUPPORTED; + } + return doUpdateSourceMetadataV7(sourceMetadata); + } +} +#endif + +Return StreamOut::selectPresentation(int32_t /*presentationId*/, int32_t /*programId*/) { + return Result::NOT_SUPPORTED; // TODO: propagate to legacy +} +#endif + +#if MAJOR_VERSION >= 6 +Return StreamOut::getDualMonoMode(getDualMonoMode_cb _hidl_cb) { + audio_dual_mono_mode_t mode = AUDIO_DUAL_MONO_MODE_OFF; + Result retval = mStream->get_dual_mono_mode != nullptr + ? Stream::analyzeStatus("get_dual_mono_mode", + mStream->get_dual_mono_mode(mStream, &mode)) + : Result::NOT_SUPPORTED; + _hidl_cb(retval, DualMonoMode(mode)); + return Void(); +} + +Return StreamOut::setDualMonoMode(DualMonoMode mode) { + return mStream->set_dual_mono_mode != nullptr + ? Stream::analyzeStatus( + "set_dual_mono_mode", + mStream->set_dual_mono_mode(mStream, + static_cast(mode))) + : Result::NOT_SUPPORTED; +} + +Return StreamOut::getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb) { + float leveldB = -std::numeric_limits::infinity(); + Result retval = mStream->get_audio_description_mix_level != nullptr + ? Stream::analyzeStatus( + "get_audio_description_mix_level", + mStream->get_audio_description_mix_level(mStream, &leveldB)) + : Result::NOT_SUPPORTED; + _hidl_cb(retval, leveldB); + return Void(); +} + +Return StreamOut::setAudioDescriptionMixLevel(float leveldB) { + return mStream->set_audio_description_mix_level != nullptr + ? Stream::analyzeStatus( + "set_audio_description_mix_level", + mStream->set_audio_description_mix_level(mStream, leveldB)) + : Result::NOT_SUPPORTED; +} + +Return StreamOut::getPlaybackRateParameters(getPlaybackRateParameters_cb _hidl_cb) { + audio_playback_rate_t rate = AUDIO_PLAYBACK_RATE_INITIALIZER; + Result retval = + mStream->get_playback_rate_parameters != nullptr + ? Stream::analyzeStatus("get_playback_rate_parameters", + mStream->get_playback_rate_parameters(mStream, &rate)) + : Result::NOT_SUPPORTED; + _hidl_cb(retval, + PlaybackRate{rate.mSpeed, rate.mPitch, static_cast(rate.mStretchMode), + static_cast(rate.mFallbackMode)}); + return Void(); +} + +Return StreamOut::setPlaybackRateParameters(const PlaybackRate& playbackRate) { + audio_playback_rate_t rate = { + playbackRate.speed, playbackRate.pitch, + static_cast(playbackRate.timestretchMode), + static_cast(playbackRate.fallbackMode)}; + return mStream->set_playback_rate_parameters != nullptr + ? Stream::analyzeStatus("set_playback_rate_parameters", + mStream->set_playback_rate_parameters(mStream, &rate)) + : Result::NOT_SUPPORTED; +} + +Return StreamOut::setEventCallback(const sp& callback) { + if (mStream->set_event_callback == nullptr) return Result::NOT_SUPPORTED; + int result = mStream->set_event_callback(mStream, StreamOut::asyncEventCallback, this); + if (result == 0) { + mEventCallback = callback; + } + return Stream::analyzeStatus("set_stream_out_callback", result, {ENOSYS} /*ignore*/); +} + +// static +int StreamOut::asyncEventCallback(stream_event_callback_type_t event, void* param, void* cookie) { + StreamOut* self = reinterpret_cast(cookie); + sp eventCallback = self->mEventCallback.load(); + if (eventCallback.get() == nullptr) return 0; + ALOGV("%s event %d", __func__, event); + Return result; + switch (event) { + case STREAM_EVENT_CBK_TYPE_CODEC_FORMAT_CHANGED: { + hidl_vec audioMetadata; + // void* param is the byte string buffer from byte_string_from_audio_metadata(). + // As the byte string buffer may have embedded zeroes, we cannot use strlen() + // but instead use audio_utils::metadata::dataByteStringLen(). + audioMetadata.setToExternal((uint8_t*)param, audio_utils::metadata::dataByteStringLen( + (const uint8_t*)param)); + result = eventCallback->onCodecFormatChanged(audioMetadata); + } break; + default: + ALOGW("%s unknown event %d", __func__, event); + break; + } + ALOGW_IF(!result.isOk(), "Client callback failed: %s", result.description().c_str()); + return 0; +} + +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 +Return StreamOut::setLatencyMode(LatencyMode mode) { + return mStream->set_latency_mode != nullptr + ? Stream::analyzeStatus( + "set_latency_mode", + mStream->set_latency_mode(mStream, + static_cast(mode))) + : Result::NOT_SUPPORTED; +}; + +Return StreamOut::getRecommendedLatencyModes(getRecommendedLatencyModes_cb _hidl_cb) { + Result retval = Result::NOT_SUPPORTED; + hidl_vec hidlModes; + size_t num_modes = AUDIO_LATENCY_MODE_CNT; + audio_latency_mode_t modes[AUDIO_LATENCY_MODE_CNT]; + + if (mStream->get_recommended_latency_modes != nullptr && + mStream->get_recommended_latency_modes(mStream, &modes[0], &num_modes) == 0) { + if (num_modes == 0 || num_modes > AUDIO_LATENCY_MODE_CNT) { + ALOGW("%s invalid number of modes returned: %zu", __func__, num_modes); + retval = Result::INVALID_STATE; + } else { + hidlModes.resize(num_modes); + for (size_t i = 0; i < num_modes; ++i) { + hidlModes[i] = static_cast(modes[i]); + } + retval = Result::OK; + } + } + _hidl_cb(retval, hidlModes); + return Void(); +}; + +// static +void StreamOut::latencyModeCallback(audio_latency_mode_t* modes, size_t num_modes, void* cookie) { + StreamOut* self = reinterpret_cast(cookie); + sp callback = self->mLatencyModeCallback.load(); + if (callback.get() == nullptr) return; + + ALOGV("%s", __func__); + + if (num_modes == 0 || num_modes > AUDIO_LATENCY_MODE_CNT) { + ALOGW("%s invalid number of modes returned: %zu", __func__, num_modes); + return; + } + + hidl_vec hidlModes(num_modes); + for (size_t i = 0; i < num_modes; ++i) { + hidlModes[i] = static_cast(modes[i]); + } + Return result = callback->onRecommendedLatencyModeChanged(hidlModes); + ALOGW_IF(!result.isOk(), "Client callback failed: %s", result.description().c_str()); +} + +Return StreamOut::setLatencyModeCallback( + const sp& callback) { + if (mStream->set_latency_mode_callback == nullptr) return Result::NOT_SUPPORTED; + int result = mStream->set_latency_mode_callback(mStream, StreamOut::latencyModeCallback, this); + if (result == 0) { + mLatencyModeCallback = callback; + } + return Stream::analyzeStatus("set_latency_mode_callback", result, {ENOSYS} /*ignore*/); +}; + +#endif + +#endif + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android diff --git a/bluetooth/audio/hal/StreamOut.h b/bluetooth/audio/hal/StreamOut.h new file mode 100644 index 0000000..ce5253f --- /dev/null +++ b/bluetooth/audio/hal/StreamOut.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_HARDWARE_AUDIO_STREAMOUT_H +#define ANDROID_HARDWARE_AUDIO_STREAMOUT_H + +#include PATH(android/hardware/audio/FILE_VERSION/IStreamOut.h) + +#include "Device.h" +#include "Stream.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CPP_VERSION { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using namespace ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CPP_VERSION; + +struct StreamOut : public IStreamOut { + typedef MessageQueue CommandMQ; + typedef MessageQueue DataMQ; + typedef MessageQueue StatusMQ; + + StreamOut(const sp& device, audio_stream_out_t* stream); + + // Methods from ::android::hardware::audio::CPP_VERSION::IStream follow. + Return getFrameSize() override; + Return getFrameCount() override; + Return getBufferSize() override; +#if MAJOR_VERSION <= 6 + Return getSampleRate() override; +#if MAJOR_VERSION == 2 + Return getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) override; + Return getSupportedChannelMasks(getSupportedChannelMasks_cb _hidl_cb) override; +#endif + Return getSupportedSampleRates(AudioFormat format, getSupportedSampleRates_cb _hidl_cb); + Return getSupportedChannelMasks(AudioFormat format, getSupportedChannelMasks_cb _hidl_cb); + Return setSampleRate(uint32_t sampleRateHz) override; + Return getChannelMask() override; + Return setChannelMask(AudioChannelBitfield mask) override; + Return getFormat() override; + Return getSupportedFormats(getSupportedFormats_cb _hidl_cb) override; + Return setFormat(AudioFormat format) override; +#else + Return getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override; + Return setAudioProperties(const AudioConfigBaseOptional& config) override; +#endif // MAJOR_VERSION <= 6 + Return getAudioProperties(getAudioProperties_cb _hidl_cb) override; + Return addEffect(uint64_t effectId) override; + Return removeEffect(uint64_t effectId) override; + Return standby() override; +#if MAJOR_VERSION == 2 + Return getDevice() override; + Return setDevice(const DeviceAddress& address) override; + Return getParameters(const hidl_vec& keys, + getParameters_cb _hidl_cb) override; + Return setParameters(const hidl_vec& parameters) override; + Return setConnectedState(const DeviceAddress& address, bool connected) override; +#elif MAJOR_VERSION >= 4 + Return getDevices(getDevices_cb _hidl_cb) override; + Return setDevices(const hidl_vec& devices) override; + Return getParameters(const hidl_vec& context, + const hidl_vec& keys, + getParameters_cb _hidl_cb) override; + Return setParameters(const hidl_vec& context, + const hidl_vec& parameters) override; +#endif + Return setHwAvSync(uint32_t hwAvSync) override; + Return close() override; + + Return debug(const hidl_handle& fd, const hidl_vec& options) override; +#if MAJOR_VERSION == 2 + Return debugDump(const hidl_handle& fd) override; +#endif + + // Methods from ::android::hardware::audio::CPP_VERSION::IStreamOut follow. + Return getLatency() override; + Return setVolume(float left, float right) override; + Return prepareForWriting(uint32_t frameSize, uint32_t framesCount, + prepareForWriting_cb _hidl_cb) override; + Return getRenderPosition(getRenderPosition_cb _hidl_cb) override; + Return getNextWriteTimestamp(getNextWriteTimestamp_cb _hidl_cb) override; + Return setCallback(const sp& callback) override; + Return clearCallback() override; + Return supportsPauseAndResume(supportsPauseAndResume_cb _hidl_cb) override; + Return pause() override; + Return resume() override; + Return supportsDrain() override; + Return drain(AudioDrain type) override; + Return flush() override; + Return getPresentationPosition(getPresentationPosition_cb _hidl_cb) override; + Return start() override; + Return stop() override; + Return createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override; + Return getMmapPosition(getMmapPosition_cb _hidl_cb) override; +#if MAJOR_VERSION >= 4 + Return selectPresentation(int32_t presentationId, int32_t programId) override; +#if MAJOR_VERSION <= 6 + Return updateSourceMetadata(const SourceMetadata& sourceMetadata) override; +#else + Return updateSourceMetadata(const SourceMetadata& sourceMetadata) override; +#endif +#endif // MAJOR_VERSION >= 4 +#if MAJOR_VERSION >= 6 + Return getDualMonoMode(getDualMonoMode_cb _hidl_cb) override; + Return setDualMonoMode(DualMonoMode mode) override; + Return getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb) override; + Return setAudioDescriptionMixLevel(float leveldB) override; + Return getPlaybackRateParameters(getPlaybackRateParameters_cb _hidl_cb) override; + Return setPlaybackRateParameters(const PlaybackRate& playbackRate) override; +#endif + + static Result getPresentationPositionImpl(audio_stream_out_t* stream, uint64_t* frames, + TimeSpec* timeStamp); + +#if MAJOR_VERSION >= 6 + Return setEventCallback(const sp& callback) override; +#endif + + private: +#if MAJOR_VERSION >= 4 + Result doUpdateSourceMetadata(const SourceMetadata& sourceMetadata); +#if MAJOR_VERSION >= 7 + Result doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata); +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 + Return setLatencyMode(LatencyMode mode) override; + Return getRecommendedLatencyModes(getRecommendedLatencyModes_cb _hidl_cb) override; + Return setLatencyModeCallback( + const sp& callback) override; +#endif +#endif +#endif // MAJOR_VERSION >= 4 + + const sp mDevice; + audio_stream_out_t* mStream; + const sp mStreamCommon; + const sp> mStreamMmap; + mediautils::atomic_sp mCallback; // for non-blocking write and drain +#if MAJOR_VERSION >= 6 + mediautils::atomic_sp mEventCallback; +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 + mediautils::atomic_sp mLatencyModeCallback; +#endif +#endif + std::unique_ptr mCommandMQ; + std::unique_ptr mDataMQ; + std::unique_ptr mStatusMQ; + EventFlag* mEfGroup; + std::atomic mStopWriteThread; + sp mWriteThread; + + virtual ~StreamOut(); + + static int asyncCallback(stream_callback_event_t event, void* param, void* cookie); + +#if MAJOR_VERSION >= 6 + static int asyncEventCallback(stream_event_callback_type_t event, void* param, void* cookie); +#if MAJOR_VERSION == 7 && MINOR_VERSION == 1 + static void latencyModeCallback(audio_latency_mode_t* modes, size_t num_modes, void* cookie); +#endif +#endif +}; + +} // namespace implementation +} // namespace CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUDIO_STREAMOUT_H diff --git a/bluetooth/audio/hal/Util.h b/bluetooth/audio/hal/Util.h new file mode 100644 index 0000000..abf5317 --- /dev/null +++ b/bluetooth/audio/hal/Util.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_HARDWARE_AUDIO_UTIL_H +#define ANDROID_HARDWARE_AUDIO_UTIL_H + +// clang-format off +#include PATH(android/hardware/audio/CORE_TYPES_FILE_VERSION/types.h) +// clang-format on + +#include +#include + +#include + +namespace android { +namespace hardware { +namespace audio { +namespace CORE_TYPES_CPP_VERSION { +namespace implementation { + +using namespace ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION; +using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION; + +namespace util { + +/** @return true if gain is between 0 and 1 included. */ +constexpr bool isGainNormalized(float gain) { + return gain >= 0.0 && gain <= 1.0; +} + +template +inline bool element_in(T e, const std::vector& v) { + return std::find(v.begin(), v.end(), e) != v.end(); +} + +static inline Result analyzeStatus(status_t status) { + switch (status) { + case 0: + return Result::OK; + case -EINVAL: + return Result::INVALID_ARGUMENTS; + case -ENODATA: + return Result::INVALID_STATE; + case -ENODEV: + return Result::NOT_INITIALIZED; + case -ENOSYS: + return Result::NOT_SUPPORTED; + default: + return Result::INVALID_STATE; + } +} + +static inline Result analyzeStatus(const char* className, const char* funcName, status_t status, + const std::vector& ignoreErrors = {}) { + if (status != 0 && !element_in(-status, ignoreErrors)) { + ALOGW("Error from HAL %s in function %s: %s", className, funcName, strerror(-status)); + } + return analyzeStatus(status); +} + +} // namespace util +} // namespace implementation +} // namespace CORE_TYPES_CPP_VERSION +} // namespace audio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUDIO_UTIL_H diff --git a/bluetooth/audio/hal/android.hardware.bluetooth.audio-service-system.rc b/bluetooth/audio/hal/android.hardware.bluetooth.audio-service-system.rc new file mode 100644 index 0000000..34be159 --- /dev/null +++ b/bluetooth/audio/hal/android.hardware.bluetooth.audio-service-system.rc @@ -0,0 +1,9 @@ +service system.bt-audio-hal /system/bin/hw/android.hardware.bluetooth.audio-service-system + class hal + user audioserver + # media gid needed for /dev/fm (radio) and for /data/misc/media (tee) + group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub + capabilities BLOCK_SUSPEND + ioprio rt 4 + task_profiles ProcessCapacityHigh HighPerformance + onrestart restart audioserver diff --git a/bluetooth/audio/hal/bluetooth_audio.xml b/bluetooth/audio/hal/bluetooth_audio.xml new file mode 100644 index 0000000..c4b1872 --- /dev/null +++ b/bluetooth/audio/hal/bluetooth_audio.xml @@ -0,0 +1,7 @@ + + + android.hardware.bluetooth.audio + 2 + IBluetoothAudioProviderFactory/default + + diff --git a/bluetooth/audio/hal/bluetooth_audio_system.xml b/bluetooth/audio/hal/bluetooth_audio_system.xml new file mode 100644 index 0000000..450127d --- /dev/null +++ b/bluetooth/audio/hal/bluetooth_audio_system.xml @@ -0,0 +1,12 @@ + + + android.hardware.bluetooth.audio + 2 + IBluetoothAudioProviderFactory/sysbta + + + android.hardware.audio + hwbinder + @6.0::IDevicesFactory/sysbta + + diff --git a/bluetooth/audio/hal/service.cpp b/bluetooth/audio/hal/service.cpp new file mode 100644 index 0000000..f8f9cde --- /dev/null +++ b/bluetooth/audio/hal/service.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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 "BtAudioAIDLService" + +#include +#include +#include + +#include "BluetoothAudioProviderFactory.h" + +using ::aidl::android::hardware::bluetooth::audio:: + BluetoothAudioProviderFactory; + +extern "C" __attribute__((visibility("default"))) binder_status_t +createIBluetoothAudioProviderFactory() { + auto factory = ::ndk::SharedRefBase::make(); + const std::string instance_name = + std::string() + BluetoothAudioProviderFactory::descriptor + "/default"; + binder_status_t aidl_status = AServiceManager_addService( + factory->asBinder().get(), instance_name.c_str()); + ALOGW_IF(aidl_status != STATUS_OK, "Could not register %s, status=%d", + instance_name.c_str(), aidl_status); + return aidl_status; +} \ No newline at end of file diff --git a/bluetooth/audio/hal/service_system.cpp b/bluetooth/audio/hal/service_system.cpp new file mode 100644 index 0000000..5b5ce11 --- /dev/null +++ b/bluetooth/audio/hal/service_system.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 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 "BtAudioAIDLServiceSystem" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include PATH(android/hardware/audio/FILE_VERSION/IDevicesFactory.h) + +#include + +#include "BluetoothAudioProviderFactory.h" +#include "DevicesFactory.h" + +//using namespace android::hardware; +using ::aidl::android::hardware::bluetooth::audio:: + BluetoothAudioProviderFactory; + +using ::android::hardware::audio::CPP_VERSION::implementation::DevicesFactory; +using namespace ::android::hardware::audio::CPP_VERSION; + +int main() { + signal(SIGPIPE, SIG_IGN); + + ::android::hardware::configureRpcThreadpool(16, true); + ::android::ProcessState::initWithDriver("/dev/binder"); + // start a threadpool for binder / hwbinder interactions + ::android::ProcessState::self()->startThreadPool(); + ::android::hardware::ProcessState::self()->startThreadPool(); + + auto factory = ::ndk::SharedRefBase::make(); + const std::string instance_name = + std::string() + BluetoothAudioProviderFactory::descriptor + "/sysbta"; + binder_status_t aidl_status = AServiceManager_addService( + factory->asBinder().get(), instance_name.c_str()); + ALOGW_IF(aidl_status != STATUS_OK, "Could not register %s, status=%d", + instance_name.c_str(), aidl_status); + + ::android::sp audio_factory = new DevicesFactory(); + ::android::status_t hidl_status = audio_factory->registerAsService("sysbta"); + ALOGW_IF(hidl_status != STATUS_OK, "Could not register sysbta, status=%d", hidl_status); + + ::android::hardware::joinRpcThreadpool(); +} diff --git a/bluetooth/audio/hw/Android.bp b/bluetooth/audio/hw/Android.bp new file mode 100644 index 0000000..b7f016d --- /dev/null +++ b/bluetooth/audio/hw/Android.bp @@ -0,0 +1,33 @@ +cc_library_shared { + name: "audio.sysbta.default", + relative_install_path: "hw", + srcs: [ + "audio_bluetooth_hw.cc", + "stream_apis.cc", + "device_port_proxy.cc", + "device_port_proxy_hidl.cc", + "utils.cc", + ], + header_libs: ["libhardware_headers"], + shared_libs: [ + "android.hardware.bluetooth.audio-V2-ndk", + "libbluetooth_audio_session_aidl_system", + "libaudioutils", + "libbase", + "libbinder_ndk", + "libcutils", + "libfmq", + "liblog", + "libutils", + // HIDL dependencies + "android.hardware.bluetooth.audio@2.0", + "android.hardware.bluetooth.audio@2.1", + "libbluetooth_audio_session_system", + "libhidlbase", + ], + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ], +} diff --git a/bluetooth/audio/hw/audio_bluetooth_hw.cc b/bluetooth/audio/hw/audio_bluetooth_hw.cc new file mode 100644 index 0000000..887c4e3 --- /dev/null +++ b/bluetooth/audio/hw/audio_bluetooth_hw.cc @@ -0,0 +1,161 @@ +/* + * Copyright 2019 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 "BTAudioHw" + +#include +#include +#include +#include +#include +#include +#include + +#include "stream_apis.h" +#include "utils.h" + +using ::android::bluetooth::audio::utils::GetAudioParamString; +using ::android::bluetooth::audio::utils::ParseAudioParams; + +static int adev_set_parameters(struct audio_hw_device* dev, + const char* kvpairs) { + LOG(VERBOSE) << __func__ << ": kevpairs=[" << kvpairs << "]"; + std::unordered_map params = + ParseAudioParams(kvpairs); + if (params.empty()) return 0; + + LOG(VERBOSE) << __func__ << ": ParamsMap=[" << GetAudioParamString(params) + << "]"; + if (params.find("A2dpSuspended") == params.end()) { + return -ENOSYS; + } + + auto* bluetooth_device = reinterpret_cast(dev); + std::lock_guard guard(bluetooth_device->mutex_); + for (auto sout : bluetooth_device->opened_stream_outs_) { + if (sout->stream_out_.common.set_parameters != nullptr) { + sout->stream_out_.common.set_parameters(&sout->stream_out_.common, + kvpairs); + } + } + return 0; +} + +static char* adev_get_parameters(const struct audio_hw_device* dev, + const char* keys) { + LOG(VERBOSE) << __func__ << ": keys=[" << keys << "]"; + return strdup(""); +} + +static int adev_init_check(const struct audio_hw_device* dev) { return 0; } + +static int adev_set_voice_volume(struct audio_hw_device* dev, float volume) { + LOG(VERBOSE) << __func__ << ": volume=" << volume; + return -ENOSYS; +} + +static int adev_set_master_volume(struct audio_hw_device* dev, float volume) { + LOG(VERBOSE) << __func__ << ": volume=" << volume; + return -ENOSYS; +} + +static int adev_get_master_volume(struct audio_hw_device* dev, float* volume) { + return -ENOSYS; +} + +static int adev_set_master_mute(struct audio_hw_device* dev, bool muted) { + LOG(VERBOSE) << __func__ << ": mute=" << muted; + return -ENOSYS; +} + +static int adev_get_master_mute(struct audio_hw_device* dev, bool* muted) { + return -ENOSYS; +} + +static int adev_set_mode(struct audio_hw_device* dev, audio_mode_t mode) { + LOG(VERBOSE) << __func__ << ": mode=" << mode; + return 0; +} + +static int adev_set_mic_mute(struct audio_hw_device* dev, bool state) { + LOG(VERBOSE) << __func__ << ": state=" << state; + return -ENOSYS; +} + +static int adev_get_mic_mute(const struct audio_hw_device* dev, bool* state) { + return -ENOSYS; +} + +static int adev_dump(const audio_hw_device_t* device, int fd) { return 0; } + +static int adev_close(hw_device_t* device) { + auto* bluetooth_device = reinterpret_cast(device); + delete bluetooth_device; + return 0; +} + +static int adev_open(const hw_module_t* module, const char* name, + hw_device_t** device) { + LOG(VERBOSE) << __func__ << ": name=[" << name << "]"; + if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) return -EINVAL; + + auto bluetooth_audio_device = new BluetoothAudioDevice{}; + struct audio_hw_device* adev = &bluetooth_audio_device->audio_device_; + if (!adev) return -ENOMEM; + + adev->common.tag = HARDWARE_DEVICE_TAG; + adev->common.version = AUDIO_DEVICE_API_VERSION_2_0; + adev->common.module = (struct hw_module_t*)module; + adev->common.close = adev_close; + + adev->init_check = adev_init_check; + adev->set_voice_volume = adev_set_voice_volume; + adev->set_master_volume = adev_set_master_volume; + adev->get_master_volume = adev_get_master_volume; + adev->set_mode = adev_set_mode; + adev->set_mic_mute = adev_set_mic_mute; + adev->get_mic_mute = adev_get_mic_mute; + adev->set_parameters = adev_set_parameters; + adev->get_parameters = adev_get_parameters; + adev->get_input_buffer_size = adev_get_input_buffer_size; + adev->open_output_stream = adev_open_output_stream; + adev->close_output_stream = adev_close_output_stream; + adev->open_input_stream = adev_open_input_stream; + adev->close_input_stream = adev_close_input_stream; + adev->dump = adev_dump; + adev->set_master_mute = adev_set_master_mute; + adev->get_master_mute = adev_get_master_mute; + + *device = &adev->common; + return 0; +} + +static struct hw_module_methods_t hal_module_methods = { + .open = adev_open, +}; + +struct audio_module HAL_MODULE_INFO_SYM = { + .common = + { + .tag = HARDWARE_MODULE_TAG, + .module_api_version = AUDIO_MODULE_API_VERSION_0_1, + .hal_api_version = HARDWARE_HAL_API_VERSION, + .id = AUDIO_HARDWARE_MODULE_ID, + .name = "Bluetooth Audio HW HAL", + .author = "The Android Open Source Project", + .methods = &hal_module_methods, + }, +}; diff --git a/bluetooth/audio/hw/device_port_proxy.cc b/bluetooth/audio/hw/device_port_proxy.cc new file mode 100644 index 0000000..8b49c4d --- /dev/null +++ b/bluetooth/audio/hw/device_port_proxy.cc @@ -0,0 +1,618 @@ +/* + * Copyright 2022 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 "BTAudioHalDeviceProxyAIDL" + +#include "device_port_proxy.h" + +#include +#include +#include +#include +#include +#include + +#include "BluetoothAudioSessionControl.h" +#include "stream_apis.h" +#include "utils.h" + +namespace android { +namespace bluetooth { +namespace audio { +namespace aidl { + +using ::aidl::android::hardware::bluetooth::audio::AudioConfiguration; +using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioSessionControl; +using ::aidl::android::hardware::bluetooth::audio::ChannelMode; +using ::aidl::android::hardware::bluetooth::audio::PcmConfiguration; +using ::aidl::android::hardware::bluetooth::audio::PortStatusCallbacks; +using ::aidl::android::hardware::bluetooth::audio::PresentationPosition; + +using ::android::base::StringPrintf; +using ControlResultCallback = std::function; +using SessionChangedCallback = std::function; + +namespace { + +audio_channel_mask_t OutputChannelModeToAudioFormat(ChannelMode channel_mode) { + switch (channel_mode) { + case ChannelMode::MONO: + return AUDIO_CHANNEL_OUT_MONO; + case ChannelMode::STEREO: + return AUDIO_CHANNEL_OUT_STEREO; + default: + return kBluetoothDefaultOutputChannelModeMask; + } +} + +audio_channel_mask_t InputChannelModeToAudioFormat(ChannelMode channel_mode) { + switch (channel_mode) { + case ChannelMode::MONO: + return AUDIO_CHANNEL_IN_MONO; + case ChannelMode::STEREO: + return AUDIO_CHANNEL_IN_STEREO; + default: + return kBluetoothDefaultInputChannelModeMask; + } +} + +audio_format_t BitsPerSampleToAudioFormat(uint8_t bits_per_sample, + const SessionType& session_type) { + switch (bits_per_sample) { + case 16: + return AUDIO_FORMAT_PCM_16_BIT; + case 24: + /* Now we use knowledge that Classic sessions used packed, and LE Audio + * LC3 encoder uses unpacked as input. This should be passed as parameter + * from BT stack through AIDL, but it would require new interface version, + * so sticking with this workaround for now. */ + if (session_type == + SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH) { + return AUDIO_FORMAT_PCM_24_BIT_PACKED; + } else { + return AUDIO_FORMAT_PCM_8_24_BIT; + } + case 32: + return AUDIO_FORMAT_PCM_32_BIT; + default: + return kBluetoothDefaultAudioFormatBitsPerSample; + } +} + +// The maximum time to wait in std::condition_variable::wait_for() +constexpr unsigned int kMaxWaitingTimeMs = 4500; + +} // namespace + +BluetoothAudioPortAidl::BluetoothAudioPortAidl() + : cookie_(::aidl::android::hardware::bluetooth::audio:: + kObserversCookieUndefined), + state_(BluetoothStreamState::DISABLED), + session_type_(SessionType::UNKNOWN) {} + +BluetoothAudioPortAidlOut::~BluetoothAudioPortAidlOut() { + if (in_use()) TearDown(); +} + +BluetoothAudioPortAidlIn::~BluetoothAudioPortAidlIn() { + if (in_use()) TearDown(); +} + +bool BluetoothAudioPortAidl::SetUp(audio_devices_t devices) { + if (!init_session_type(devices)) return false; + + state_ = BluetoothStreamState::STANDBY; + + auto control_result_cb = [port = this](uint16_t cookie, bool start_resp, + const BluetoothAudioStatus& status) { + if (!port->in_use()) { + LOG(ERROR) << "control_result_cb: BluetoothAudioPortAidl is not in use"; + return; + } + if (port->cookie_ != cookie) { + LOG(ERROR) << "control_result_cb: proxy of device port (cookie=" + << StringPrintf("%#hx", cookie) << ") is corrupted"; + return; + } + port->ControlResultHandler(status); + }; + auto session_changed_cb = [port = this](uint16_t cookie) { + if (!port->in_use()) { + LOG(ERROR) << "session_changed_cb: BluetoothAudioPortAidl is not in use"; + return; + } + if (port->cookie_ != cookie) { + LOG(ERROR) << "session_changed_cb: proxy of device port (cookie=" + << StringPrintf("%#hx", cookie) << ") is corrupted"; + return; + } + port->SessionChangedHandler(); + }; + // TODO: Add audio_config_changed_cb + PortStatusCallbacks cbacks = { + .control_result_cb_ = control_result_cb, + .session_changed_cb_ = session_changed_cb, + }; + cookie_ = BluetoothAudioSessionControl::RegisterControlResultCback( + session_type_, cbacks); + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_); + + return ( + cookie_ != + ::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined); +} + +bool BluetoothAudioPortAidl::init_session_type(audio_devices_t device) { + switch (device) { + case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP: + case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES: + case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER: + LOG(VERBOSE) + << __func__ + << ": device=AUDIO_DEVICE_OUT_BLUETOOTH_A2DP (HEADPHONES/SPEAKER) (" + << StringPrintf("%#x", device) << ")"; + session_type_ = SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH; + break; + case AUDIO_DEVICE_OUT_HEARING_AID: + LOG(VERBOSE) << __func__ + << ": device=AUDIO_DEVICE_OUT_HEARING_AID (MEDIA/VOICE) (" + << StringPrintf("%#x", device) << ")"; + session_type_ = SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH; + break; + case AUDIO_DEVICE_OUT_BLE_HEADSET: + LOG(VERBOSE) << __func__ + << ": device=AUDIO_DEVICE_OUT_BLE_HEADSET (MEDIA/VOICE) (" + << StringPrintf("%#x", device) << ")"; + session_type_ = SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH; + break; + case AUDIO_DEVICE_OUT_BLE_SPEAKER: + LOG(VERBOSE) << __func__ + << ": device=AUDIO_DEVICE_OUT_BLE_SPEAKER (MEDIA) (" + << StringPrintf("%#x", device) << ")"; + session_type_ = SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH; + break; + case AUDIO_DEVICE_IN_BLE_HEADSET: + LOG(VERBOSE) << __func__ + << ": device=AUDIO_DEVICE_IN_BLE_HEADSET (VOICE) (" + << StringPrintf("%#x", device) << ")"; + session_type_ = SessionType::LE_AUDIO_SOFTWARE_DECODING_DATAPATH; + break; + case AUDIO_DEVICE_OUT_BLE_BROADCAST: + LOG(VERBOSE) << __func__ + << ": device=AUDIO_DEVICE_OUT_BLE_BROADCAST (MEDIA) (" + << StringPrintf("%#x", device) << ")"; + session_type_ = + SessionType::LE_AUDIO_BROADCAST_SOFTWARE_ENCODING_DATAPATH; + break; + default: + LOG(ERROR) << __func__ + << ": unknown device=" << StringPrintf("%#x", device); + return false; + } + + if (!BluetoothAudioSessionControl::IsSessionReady(session_type_)) { + LOG(ERROR) << __func__ << ": device=" << StringPrintf("%#x", device) + << ", session_type=" << toString(session_type_) + << " is not ready"; + return false; + } + return true; +} + +void BluetoothAudioPortAidl::TearDown() { + if (!in_use()) { + LOG(ERROR) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << " unknown monitor"; + return; + } + + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_); + BluetoothAudioSessionControl::UnregisterControlResultCback(session_type_, + cookie_); + cookie_ = + ::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined; +} + +void BluetoothAudioPortAidl::ControlResultHandler( + const BluetoothAudioStatus& status) { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortAidlis not in use"; + return; + } + std::unique_lock port_lock(cv_mutex_); + BluetoothStreamState previous_state = state_; + LOG(INFO) << "control_result_cb: session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state + << ", status=" << toString(status); + + switch (previous_state) { + case BluetoothStreamState::STARTED: + /* Only Suspend signal can be send in STARTED state*/ + if (status == BluetoothAudioStatus::RECONFIGURATION || + status == BluetoothAudioStatus::SUCCESS) { + state_ = BluetoothStreamState::STANDBY; + } else { + // Set to standby since the stack may be busy switching between outputs + LOG(WARNING) << "control_result_cb: status=" << toString(status) + << " failure for session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state; + } + break; + case BluetoothStreamState::STARTING: + if (status == BluetoothAudioStatus::SUCCESS) { + state_ = BluetoothStreamState::STARTED; + } else { + // Set to standby since the stack may be busy switching between outputs + LOG(WARNING) << "control_result_cb: status=" << toString(status) + << " failure for session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state; + state_ = BluetoothStreamState::STANDBY; + } + break; + case BluetoothStreamState::SUSPENDING: + if (status == BluetoothAudioStatus::SUCCESS) { + state_ = BluetoothStreamState::STANDBY; + } else { + // It will be failed if the headset is disconnecting, and set to disable + // to wait for re-init again + LOG(WARNING) << "control_result_cb: status=" << toString(status) + << " failure for session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state; + state_ = BluetoothStreamState::DISABLED; + } + break; + default: + LOG(ERROR) << "control_result_cb: unexpected status=" << toString(status) + << " for session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state; + return; + } + port_lock.unlock(); + internal_cv_.notify_all(); +} + +void BluetoothAudioPortAidl::SessionChangedHandler() { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + return; + } + std::unique_lock port_lock(cv_mutex_); + BluetoothStreamState previous_state = state_; + LOG(INFO) << "session_changed_cb: session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state; + state_ = BluetoothStreamState::DISABLED; + port_lock.unlock(); + internal_cv_.notify_all(); +} + +bool BluetoothAudioPortAidl::in_use() const { + return ( + cookie_ != + ::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined); +} + +bool BluetoothAudioPortAidl::GetPreferredDataIntervalUs( + size_t* interval_us) const { + if (!in_use()) { + return false; + } + + const AudioConfiguration& hal_audio_cfg = + BluetoothAudioSessionControl::GetAudioConfig(session_type_); + if (hal_audio_cfg.getTag() != AudioConfiguration::pcmConfig) { + return false; + } + + const PcmConfiguration& pcm_cfg = + hal_audio_cfg.get(); + *interval_us = pcm_cfg.dataIntervalUs; + return true; +} + +bool BluetoothAudioPortAidlOut::LoadAudioConfig( + audio_config_t* audio_cfg) const { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortAidlOut is not in use"; + audio_cfg->sample_rate = kBluetoothDefaultSampleRate; + audio_cfg->channel_mask = kBluetoothDefaultOutputChannelModeMask; + audio_cfg->format = kBluetoothDefaultAudioFormatBitsPerSample; + return false; + } + + const AudioConfiguration& hal_audio_cfg = + BluetoothAudioSessionControl::GetAudioConfig(session_type_); + if (hal_audio_cfg.getTag() != AudioConfiguration::pcmConfig) { + audio_cfg->sample_rate = kBluetoothDefaultSampleRate; + audio_cfg->channel_mask = kBluetoothDefaultOutputChannelModeMask; + audio_cfg->format = kBluetoothDefaultAudioFormatBitsPerSample; + return false; + } + const PcmConfiguration& pcm_cfg = + hal_audio_cfg.get(); + LOG(VERBOSE) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << ", PcmConfig=[" << pcm_cfg.toString() + << "]"; + if (pcm_cfg.channelMode == ChannelMode::UNKNOWN) { + return false; + } + audio_cfg->sample_rate = pcm_cfg.sampleRateHz; + audio_cfg->channel_mask = + (is_stereo_to_mono_ + ? AUDIO_CHANNEL_OUT_STEREO + : OutputChannelModeToAudioFormat(pcm_cfg.channelMode)); + audio_cfg->format = + BitsPerSampleToAudioFormat(pcm_cfg.bitsPerSample, session_type_); + return true; +} + +bool BluetoothAudioPortAidlIn::LoadAudioConfig( + audio_config_t* audio_cfg) const { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortAidlIn is not in use"; + audio_cfg->sample_rate = kBluetoothDefaultSampleRate; + audio_cfg->channel_mask = kBluetoothDefaultInputChannelModeMask; + audio_cfg->format = kBluetoothDefaultAudioFormatBitsPerSample; + return false; + } + + const AudioConfiguration& hal_audio_cfg = + BluetoothAudioSessionControl::GetAudioConfig(session_type_); + if (hal_audio_cfg.getTag() != AudioConfiguration::pcmConfig) { + audio_cfg->sample_rate = kBluetoothDefaultSampleRate; + audio_cfg->channel_mask = kBluetoothDefaultInputChannelModeMask; + audio_cfg->format = kBluetoothDefaultAudioFormatBitsPerSample; + return false; + } + const PcmConfiguration& pcm_cfg = + hal_audio_cfg.get(); + LOG(VERBOSE) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << ", PcmConfig=[" << pcm_cfg.toString() + << "]"; + if (pcm_cfg.channelMode == ChannelMode::UNKNOWN) { + return false; + } + + audio_cfg->sample_rate = pcm_cfg.sampleRateHz; + audio_cfg->channel_mask = InputChannelModeToAudioFormat(pcm_cfg.channelMode); + audio_cfg->format = + BitsPerSampleToAudioFormat(pcm_cfg.bitsPerSample, session_type_); + return true; +} + +bool BluetoothAudioPortAidl::CondwaitState(BluetoothStreamState state) { + bool retval; + std::unique_lock port_lock(cv_mutex_); + switch (state) { + case BluetoothStreamState::STARTING: + LOG(VERBOSE) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << " waiting for STARTED"; + retval = internal_cv_.wait_for( + port_lock, std::chrono::milliseconds(kMaxWaitingTimeMs), + [this] { return this->state_ != BluetoothStreamState::STARTING; }); + retval = retval && state_ == BluetoothStreamState::STARTED; + break; + case BluetoothStreamState::SUSPENDING: + LOG(VERBOSE) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << " waiting for SUSPENDED"; + retval = internal_cv_.wait_for( + port_lock, std::chrono::milliseconds(kMaxWaitingTimeMs), + [this] { return this->state_ != BluetoothStreamState::SUSPENDING; }); + retval = retval && state_ == BluetoothStreamState::STANDBY; + break; + default: + LOG(WARNING) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << " waiting for KNOWN"; + return false; + } + + return retval; // false if any failure like timeout +} + +bool BluetoothAudioPortAidl::Start() { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + return false; + } + + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ + << ", mono=" << (is_stereo_to_mono_ ? "true" : "false") + << " request"; + bool retval = false; + if (state_ == BluetoothStreamState::STANDBY) { + state_ = BluetoothStreamState::STARTING; + if (BluetoothAudioSessionControl::StartStream(session_type_)) { + retval = CondwaitState(BluetoothStreamState::STARTING); + } else { + LOG(ERROR) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " Hal fails"; + } + } + + if (retval) { + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ + << ", mono=" << (is_stereo_to_mono_ ? "true" : "false") + << " done"; + } else { + LOG(ERROR) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " failure"; + } + + return retval; // false if any failure like timeout +} + +bool BluetoothAudioPortAidl::Suspend() { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + return false; + } + + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " request"; + bool retval = false; + if (state_ == BluetoothStreamState::STARTED) { + state_ = BluetoothStreamState::SUSPENDING; + if (BluetoothAudioSessionControl::SuspendStream(session_type_)) { + retval = CondwaitState(BluetoothStreamState::SUSPENDING); + } else { + LOG(ERROR) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " Hal fails"; + } + } + + if (retval) { + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " done"; + } else { + LOG(ERROR) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " failure"; + } + + return retval; // false if any failure like timeout +} + +void BluetoothAudioPortAidl::Stop() { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + return; + } + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " request"; + state_ = BluetoothStreamState::DISABLED; + BluetoothAudioSessionControl::StopStream(session_type_); + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " done"; +} + +size_t BluetoothAudioPortAidlOut::WriteData(const void* buffer, + size_t bytes) const { + if (!in_use()) return 0; + if (!is_stereo_to_mono_) { + return BluetoothAudioSessionControl::OutWritePcmData(session_type_, buffer, + bytes); + } + + // WAR to mix the stereo into Mono (16 bits per sample) + const size_t write_frames = bytes >> 2; + if (write_frames == 0) return 0; + auto src = static_cast(buffer); + std::unique_ptr dst{new int16_t[write_frames]}; + downmix_to_mono_i16_from_stereo_i16(dst.get(), src, write_frames); + // a frame is 16 bits, and the size of a mono frame is equal to half a stereo. + return BluetoothAudioSessionControl::OutWritePcmData(session_type_, dst.get(), + write_frames * 2) * + 2; +} + +size_t BluetoothAudioPortAidlIn::ReadData(void* buffer, size_t bytes) const { + if (!in_use()) return 0; + return BluetoothAudioSessionControl::InReadPcmData(session_type_, buffer, + bytes); +} + +bool BluetoothAudioPortAidl::GetPresentationPosition( + uint64_t* delay_ns, uint64_t* bytes, timespec* timestamp) const { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + return false; + } + PresentationPosition presentation_position; + bool retval = BluetoothAudioSessionControl::GetPresentationPosition( + session_type_, presentation_position); + *delay_ns = presentation_position.remoteDeviceAudioDelayNanos; + *bytes = presentation_position.transmittedOctets; + *timestamp = {.tv_sec = static_cast<__kernel_old_time_t>( + presentation_position.transmittedOctetsTimestamp.tvSec), + .tv_nsec = static_cast( + presentation_position.transmittedOctetsTimestamp.tvNSec)}; + LOG(VERBOSE) << __func__ + << ": session_type=" << StringPrintf("%#hhx", session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << ", delay=" << *delay_ns + << "ns, data=" << *bytes + << " bytes, timestamp=" << timestamp->tv_sec << "." + << StringPrintf("%09ld", timestamp->tv_nsec) << "s"; + + return retval; +} + +void BluetoothAudioPortAidl::UpdateSourceMetadata( + const source_metadata* source_metadata) const { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + return; + } + LOG(DEBUG) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << ", " << source_metadata->track_count + << " track(s)"; + if (source_metadata->track_count == 0) return; + BluetoothAudioSessionControl::UpdateSourceMetadata(session_type_, + *source_metadata); +} + +void BluetoothAudioPortAidl::UpdateSinkMetadata( + const sink_metadata* sink_metadata) const { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + return; + } + LOG(DEBUG) << __func__ << ": session_type=" << toString(session_type_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << ", " << sink_metadata->track_count + << " track(s)"; + if (sink_metadata->track_count == 0) return; + BluetoothAudioSessionControl::UpdateSinkMetadata(session_type_, + *sink_metadata); +} + +BluetoothStreamState BluetoothAudioPortAidl::GetState() const { return state_; } + +void BluetoothAudioPortAidl::SetState(BluetoothStreamState state) { + state_ = state; +} + +} // namespace aidl +} // namespace audio +} // namespace bluetooth +} // namespace android \ No newline at end of file diff --git a/bluetooth/audio/hw/device_port_proxy.h b/bluetooth/audio/hw/device_port_proxy.h new file mode 100644 index 0000000..dab4970 --- /dev/null +++ b/bluetooth/audio/hw/device_port_proxy.h @@ -0,0 +1,213 @@ +/* + * Copyright 2022 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. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +enum class BluetoothStreamState : uint8_t; + +namespace android { +namespace bluetooth { +namespace audio { + +/*** + * Proxy for Bluetooth Audio HW Module to communicate with Bluetooth Audio + * Session Control. All methods are not thread safe, so users must acquire a + * lock. Note: currently, in stream_apis.cc, if GetState() is only used for + * verbose logging, it is not locked, so the state may not be synchronized. + ***/ +class BluetoothAudioPort { + public: + BluetoothAudioPort(){}; + virtual ~BluetoothAudioPort() = default; + + /*** + * Fetch output control / data path of BluetoothAudioPort and setup + * callbacks into BluetoothAudioProvider. If SetUp() returns false, the audio + * HAL must delete this BluetoothAudioPort and return EINVAL to caller + ***/ + virtual bool SetUp(audio_devices_t) { return false; } + + /*** + * Unregister this BluetoothAudioPort from BluetoothAudioSessionControl. + * Audio HAL must delete this BluetoothAudioPort after calling this. + ***/ + virtual void TearDown() {} + + /*** + * When the Audio framework / HAL tries to query audio config about format, + * channel mask and sample rate, it uses this function to fetch from the + * Bluetooth stack + ***/ + virtual bool LoadAudioConfig(audio_config_t*) const { return false; }; + + /*** + * WAR to support Mono mode / 16 bits per sample + ***/ + virtual void ForcePcmStereoToMono(bool) {} + + /*** + * When the Audio framework / HAL wants to change the stream state, it invokes + * these 3 functions to control the Bluetooth stack (Audio Control Path). + * Note: Both Start() and Suspend() will return true when there are no errors. + * Called by Audio framework / HAL to start the stream + ***/ + virtual bool Start() { return false; } + + /*** + * Called by Audio framework / HAL to suspend the stream + ***/ + virtual bool Suspend() { return false; }; + + /*** + virtual bool Suspend() { return false; } + * Called by Audio framework / HAL to stop the stream + ***/ + virtual void Stop() {} + + /*** + * Called by the Audio framework / HAL to fetch information about audio frames + * presented to an external sink, or frames presented fror an internal sink + ***/ + virtual bool GetPresentationPosition(uint64_t*, uint64_t*, timespec*) const { + return false; + } + + /*** + * Called by the Audio framework / HAL when the metadata of the stream's + * source has been changed. + ***/ + virtual void UpdateSourceMetadata(const source_metadata*) const {}; + + /*** + * Return the current BluetoothStreamState + ***/ + virtual BluetoothStreamState GetState() const { + return static_cast(0); + } + + /*** + * Set the current BluetoothStreamState + ***/ + virtual void SetState(BluetoothStreamState state) {} + + virtual bool IsA2dp() const { return false; } + + virtual bool GetPreferredDataIntervalUs(size_t* interval_us) const { + return false; + }; + + virtual size_t WriteData(const void* buffer, size_t bytes) const { + return 0; + }; + virtual size_t ReadData(void* buffer, size_t bytes) const { return 0; }; +}; + +namespace aidl { + +using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus; +using ::aidl::android::hardware::bluetooth::audio::SessionType; + +class BluetoothAudioPortAidl : public BluetoothAudioPort { + public: + BluetoothAudioPortAidl(); + virtual ~BluetoothAudioPortAidl() = default; + + bool SetUp(audio_devices_t devices) override; + + void TearDown() override; + + void ForcePcmStereoToMono(bool force) override { is_stereo_to_mono_ = force; } + + bool Start() override; + bool Suspend() override; + void Stop() override; + + bool GetPresentationPosition(uint64_t* delay_ns, uint64_t* byte, + timespec* timestamp) const override; + + void UpdateSourceMetadata( + const source_metadata* source_metadata) const override; + + /*** + * Called by the Audio framework / HAL when the metadata of the stream's + * sink has been changed. + ***/ + virtual void UpdateSinkMetadata(const sink_metadata* sink_metadata) const; + + BluetoothStreamState GetState() const override; + + void SetState(BluetoothStreamState state) override; + + bool IsA2dp() const override { + return session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type_ == + SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH; + } + + bool GetPreferredDataIntervalUs(size_t* interval_us) const override; + + protected: + uint16_t cookie_; + BluetoothStreamState state_; + SessionType session_type_; + // WR to support Mono: True if fetching Stereo and mixing into Mono + bool is_stereo_to_mono_ = false; + virtual bool in_use() const; + + private: + mutable std::mutex cv_mutex_; + std::condition_variable internal_cv_; + + // Check and initialize session type for |devices| If failed, this + // BluetoothAudioPortAidl is not initialized and must be deleted. + bool init_session_type(audio_devices_t device); + + bool CondwaitState(BluetoothStreamState state); + + void ControlResultHandler(const BluetoothAudioStatus& status); + void SessionChangedHandler(); +}; + +class BluetoothAudioPortAidlOut : public BluetoothAudioPortAidl { + public: + ~BluetoothAudioPortAidlOut(); + + // The audio data path to the Bluetooth stack (Software encoding) + size_t WriteData(const void* buffer, size_t bytes) const override; + bool LoadAudioConfig(audio_config_t* audio_cfg) const override; +}; + +class BluetoothAudioPortAidlIn : public BluetoothAudioPortAidl { + public: + ~BluetoothAudioPortAidlIn(); + + // The audio data path from the Bluetooth stack (Software decoded) + size_t ReadData(void* buffer, size_t bytes) const override; + bool LoadAudioConfig(audio_config_t* audio_cfg) const override; +}; + +} // namespace aidl +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/hw/device_port_proxy_hidl.cc b/bluetooth/audio/hw/device_port_proxy_hidl.cc new file mode 100644 index 0000000..0f8aa3d --- /dev/null +++ b/bluetooth/audio/hw/device_port_proxy_hidl.cc @@ -0,0 +1,621 @@ +/* + * Copyright 2022 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 "BTAudioHalDeviceProxyHIDL" + +#include "device_port_proxy_hidl.h" + +#include +#include +#include +#include +#include +#include + +#include "BluetoothAudioSessionControl_2_1.h" +#include "stream_apis.h" +#include "utils.h" + +namespace android { +namespace bluetooth { +namespace audio { +namespace hidl { + +using ::android::base::StringPrintf; +using ::android::bluetooth::audio::BluetoothAudioSessionControl_2_1; +using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample; +using ::android::hardware::bluetooth::audio::V2_0::ChannelMode; +using ::android::hardware::bluetooth::audio::V2_0::PcmParameters; +using SampleRate = ::android::hardware::bluetooth::audio::V2_0::SampleRate; +using SampleRate_2_1 = ::android::hardware::bluetooth::audio::V2_1::SampleRate; +using BluetoothAudioStatusHidl = + ::android::hardware::bluetooth::audio::V2_0::Status; +using ControlResultCallback = std::function; +using SessionChangedCallback = std::function; + +namespace { + +unsigned int SampleRateToAudioFormat(SampleRate_2_1 sample_rate) { + switch (sample_rate) { + case SampleRate_2_1::RATE_8000: + return 8000; + case SampleRate_2_1::RATE_16000: + return 16000; + case SampleRate_2_1::RATE_24000: + return 24000; + case SampleRate_2_1::RATE_32000: + return 32000; + case SampleRate_2_1::RATE_44100: + return 44100; + case SampleRate_2_1::RATE_48000: + return 48000; + case SampleRate_2_1::RATE_88200: + return 88200; + case SampleRate_2_1::RATE_96000: + return 96000; + case SampleRate_2_1::RATE_176400: + return 176400; + case SampleRate_2_1::RATE_192000: + return 192000; + default: + return kBluetoothDefaultSampleRate; + } +} +audio_channel_mask_t OutputChannelModeToAudioFormat(ChannelMode channel_mode) { + switch (channel_mode) { + case ChannelMode::MONO: + return AUDIO_CHANNEL_OUT_MONO; + case ChannelMode::STEREO: + return AUDIO_CHANNEL_OUT_STEREO; + default: + return kBluetoothDefaultOutputChannelModeMask; + } +} + +audio_channel_mask_t InputChannelModeToAudioFormat(ChannelMode channel_mode) { + switch (channel_mode) { + case ChannelMode::MONO: + return AUDIO_CHANNEL_IN_MONO; + case ChannelMode::STEREO: + return AUDIO_CHANNEL_IN_STEREO; + default: + return kBluetoothDefaultInputChannelModeMask; + } +} + +audio_format_t BitsPerSampleToAudioFormat(BitsPerSample bits_per_sample) { + switch (bits_per_sample) { + case BitsPerSample::BITS_16: + return AUDIO_FORMAT_PCM_16_BIT; + case BitsPerSample::BITS_24: + return AUDIO_FORMAT_PCM_24_BIT_PACKED; + case BitsPerSample::BITS_32: + return AUDIO_FORMAT_PCM_32_BIT; + default: + return kBluetoothDefaultAudioFormatBitsPerSample; + } +} + +// The maximum time to wait in std::condition_variable::wait_for() +constexpr unsigned int kMaxWaitingTimeMs = 4500; + +} // namespace + +BluetoothAudioPortHidl::BluetoothAudioPortHidl() + : session_type_hidl_(SessionType_2_1::UNKNOWN), + cookie_(android::bluetooth::audio::kObserversCookieUndefined), + state_(BluetoothStreamState::DISABLED) {} + +BluetoothAudioPortHidlOut::~BluetoothAudioPortHidlOut() { + if (BluetoothAudioPortHidl::in_use()) BluetoothAudioPortHidl::TearDown(); +} + +BluetoothAudioPortHidlIn::~BluetoothAudioPortHidlIn() { + if (BluetoothAudioPortHidl::in_use()) BluetoothAudioPortHidl::TearDown(); +} + +bool BluetoothAudioPortHidl::SetUp(audio_devices_t devices) { + if (!init_session_type(devices)) return false; + + state_ = BluetoothStreamState::STANDBY; + + auto control_result_cb = [port = this]( + uint16_t cookie, bool start_resp, + const BluetoothAudioStatusHidl& status) { + if (!port->in_use()) { + LOG(ERROR) << "control_result_cb: BluetoothAudioPort is not in use"; + return; + } + if (port->cookie_ != cookie) { + LOG(ERROR) << "control_result_cb: proxy of device port (cookie=" + << StringPrintf("%#hx", cookie) << ") is corrupted"; + return; + } + port->ControlResultHandler(status); + }; + auto session_changed_cb = [port = this](uint16_t cookie) { + if (!port->in_use()) { + LOG(ERROR) << "session_changed_cb: BluetoothAudioPort is not in use"; + return; + } + if (port->cookie_ != cookie) { + LOG(ERROR) << "session_changed_cb: proxy of device port (cookie=" + << StringPrintf("%#hx", cookie) << ") is corrupted"; + return; + } + port->SessionChangedHandler(); + }; + ::android::bluetooth::audio::PortStatusCallbacks cbacks = { + .control_result_cb_ = control_result_cb, + .session_changed_cb_ = session_changed_cb}; + cookie_ = BluetoothAudioSessionControl_2_1::RegisterControlResultCback( + session_type_hidl_, cbacks); + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_); + + return (cookie_ != android::bluetooth::audio::kObserversCookieUndefined); +} + +bool BluetoothAudioPortHidl::init_session_type(audio_devices_t device) { + switch (device) { + case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP: + case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES: + case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER: + LOG(VERBOSE) + << __func__ + << ": device=AUDIO_DEVICE_OUT_BLUETOOTH_A2DP (HEADPHONES/SPEAKER) (" + << StringPrintf("%#x", device) << ")"; + session_type_hidl_ = SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH; + break; + case AUDIO_DEVICE_OUT_HEARING_AID: + LOG(VERBOSE) << __func__ + << ": device=AUDIO_DEVICE_OUT_HEARING_AID (MEDIA/VOICE) (" + << StringPrintf("%#x", device) << ")"; + session_type_hidl_ = + SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH; + break; + case AUDIO_DEVICE_OUT_BLE_HEADSET: + LOG(VERBOSE) << __func__ + << ": device=AUDIO_DEVICE_OUT_BLE_HEADSET (MEDIA/VOICE) (" + << StringPrintf("%#x", device) << ")"; + session_type_hidl_ = SessionType_2_1::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH; + break; + case AUDIO_DEVICE_OUT_BLE_SPEAKER: + LOG(VERBOSE) << __func__ + << ": device=AUDIO_DEVICE_OUT_BLE_SPEAKER (MEDIA) (" + << StringPrintf("%#x", device) << ")"; + session_type_hidl_ = SessionType_2_1::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH; + break; + case AUDIO_DEVICE_IN_BLE_HEADSET: + LOG(VERBOSE) << __func__ + << ": device=AUDIO_DEVICE_IN_BLE_HEADSET (VOICE) (" + << StringPrintf("%#x", device) << ")"; + session_type_hidl_ = SessionType_2_1::LE_AUDIO_SOFTWARE_DECODED_DATAPATH; + break; + default: + LOG(ERROR) << __func__ + << ": unknown device=" << StringPrintf("%#x", device); + return false; + } + + if (!BluetoothAudioSessionControl_2_1::IsSessionReady(session_type_hidl_)) { + LOG(ERROR) << __func__ << ": device=" << StringPrintf("%#x", device) + << ", session_type=" << toString(session_type_hidl_) + << " is not ready"; + return false; + } + return true; +} + +void BluetoothAudioPortHidl::TearDown() { + if (!in_use()) { + LOG(ERROR) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << " unknown monitor"; + return; + } + + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_); + BluetoothAudioSessionControl_2_1::UnregisterControlResultCback( + session_type_hidl_, cookie_); + cookie_ = android::bluetooth::audio::kObserversCookieUndefined; +} + +void BluetoothAudioPortHidl::ControlResultHandler( + const BluetoothAudioStatusHidl& status) { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortis not in use"; + return; + } + std::unique_lock port_lock(cv_mutex_); + BluetoothStreamState previous_state = state_; + LOG(INFO) << "control_result_cb: session_type=" + << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state + << ", status=" << toString(status); + + switch (previous_state) { + case BluetoothStreamState::STARTED: + /* Only Suspend signal can be send in STARTED state*/ + if (status == BluetoothAudioStatus::SUCCESS) { + state_ = BluetoothStreamState::STANDBY; + } else { + // Set to standby since the stack may be busy switching between outputs + LOG(WARNING) << "control_result_cb: status=" << toString(status) + << " failure for session_type=" + << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state; + } + break; + case BluetoothStreamState::STARTING: + if (status == BluetoothAudioStatusHidl::SUCCESS) { + state_ = BluetoothStreamState::STARTED; + } else { + // Set to standby since the stack may be busy switching between outputs + LOG(WARNING) << "control_result_cb: status=" << toString(status) + << " failure for session_type=" + << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state; + state_ = BluetoothStreamState::STANDBY; + } + break; + case BluetoothStreamState::SUSPENDING: + if (status == BluetoothAudioStatusHidl::SUCCESS) { + state_ = BluetoothStreamState::STANDBY; + } else { + // It will be failed if the headset is disconnecting, and set to disable + // to wait for re-init again + LOG(WARNING) << "control_result_cb: status=" << toString(status) + << " failure for session_type=" + << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state; + state_ = BluetoothStreamState::DISABLED; + } + break; + default: + LOG(ERROR) << "control_result_cb: unexpected status=" << toString(status) + << " for session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state; + return; + } + port_lock.unlock(); + internal_cv_.notify_all(); +} + +void BluetoothAudioPortHidl::SessionChangedHandler() { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPort is not in use"; + return; + } + std::unique_lock port_lock(cv_mutex_); + BluetoothStreamState previous_state = state_; + LOG(INFO) << "session_changed_cb: session_type=" + << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", previous_state=" << previous_state; + state_ = BluetoothStreamState::DISABLED; + port_lock.unlock(); + internal_cv_.notify_all(); +} + +bool BluetoothAudioPortHidl::in_use() const { + return (cookie_ != android::bluetooth::audio::kObserversCookieUndefined); +} + +bool BluetoothAudioPortHidl::GetPreferredDataIntervalUs( + size_t* interval_us) const { + if (!in_use()) { + return false; + } + + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + hal_audio_cfg = + BluetoothAudioSessionControl_2_1::GetAudioConfig(session_type_hidl_); + if (hal_audio_cfg.getDiscriminator() != + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration:: + hidl_discriminator::pcmConfig) { + return false; + } + + const ::android::hardware::bluetooth::audio::V2_1::PcmParameters& pcm_cfg = + hal_audio_cfg.pcmConfig(); + *interval_us = pcm_cfg.dataIntervalUs; + return true; +} + +bool BluetoothAudioPortHidl::CondwaitState(BluetoothStreamState state) { + bool retval; + std::unique_lock port_lock(cv_mutex_); + switch (state) { + case BluetoothStreamState::STARTING: + LOG(VERBOSE) << __func__ + << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << " waiting for STARTED"; + retval = internal_cv_.wait_for( + port_lock, std::chrono::milliseconds(kMaxWaitingTimeMs), + [this] { return this->state_ != BluetoothStreamState::STARTING; }); + retval = retval && state_ == BluetoothStreamState::STARTED; + break; + case BluetoothStreamState::SUSPENDING: + LOG(VERBOSE) << __func__ + << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << " waiting for SUSPENDED"; + retval = internal_cv_.wait_for( + port_lock, std::chrono::milliseconds(kMaxWaitingTimeMs), + [this] { return this->state_ != BluetoothStreamState::SUSPENDING; }); + retval = retval && state_ == BluetoothStreamState::STANDBY; + break; + default: + LOG(WARNING) << __func__ + << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << " waiting for KNOWN"; + return false; + } + + return retval; // false if any failure like timeout +} + +bool BluetoothAudioPortHidl::Start() { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPort is not in use"; + return false; + } + + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ + << ", mono=" << (is_stereo_to_mono_ ? "true" : "false") + << " request"; + bool retval = false; + if (state_ == BluetoothStreamState::STANDBY) { + state_ = BluetoothStreamState::STARTING; + if (BluetoothAudioSessionControl_2_1::StartStream(session_type_hidl_)) { + retval = CondwaitState(BluetoothStreamState::STARTING); + } else { + LOG(ERROR) << __func__ + << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " Hal fails"; + } + } + + if (retval) { + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ + << ", mono=" << (is_stereo_to_mono_ ? "true" : "false") + << " done"; + } else { + LOG(ERROR) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " failure"; + } + + return retval; // false if any failure like timeout +} + +bool BluetoothAudioPortHidl::Suspend() { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPort is not in use"; + return false; + } + + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " request"; + bool retval = false; + if (state_ == BluetoothStreamState::STARTED) { + state_ = BluetoothStreamState::SUSPENDING; + if (BluetoothAudioSessionControl_2_1::SuspendStream(session_type_hidl_)) { + retval = CondwaitState(BluetoothStreamState::SUSPENDING); + } else { + LOG(ERROR) << __func__ + << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " Hal fails"; + } + } + + if (retval) { + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " done"; + } else { + LOG(ERROR) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " failure"; + } + + return retval; // false if any failure like timeout +} + +void BluetoothAudioPortHidl::Stop() { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPort is not in use"; + return; + } + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " request"; + state_ = BluetoothStreamState::DISABLED; + BluetoothAudioSessionControl_2_1::StopStream(session_type_hidl_); + LOG(INFO) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << " done"; +} + +bool BluetoothAudioPortHidl::GetPresentationPosition( + uint64_t* delay_ns, uint64_t* bytes, timespec* timestamp) const { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPort is not in use"; + return false; + } + bool retval = BluetoothAudioSessionControl_2_1::GetPresentationPosition( + session_type_hidl_, delay_ns, bytes, timestamp); + LOG(VERBOSE) << __func__ + << ": session_type=" << StringPrintf("%#hhx", session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << ", delay=" << *delay_ns + << "ns, data=" << *bytes + << " bytes, timestamp=" << timestamp->tv_sec << "." + << StringPrintf("%09ld", timestamp->tv_nsec) << "s"; + + return retval; +} + +void BluetoothAudioPortHidl::UpdateSourceMetadata( + const source_metadata* source_metadata) const { + if (!in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPort is not in use"; + return; + } + LOG(DEBUG) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" << StringPrintf("%#hx", cookie_) + << ", state=" << state_ << ", " << source_metadata->track_count + << " track(s)"; + if (source_metadata->track_count == 0) return; + BluetoothAudioSessionControl_2_1::UpdateTracksMetadata(session_type_hidl_, + source_metadata); +} + +BluetoothStreamState BluetoothAudioPortHidl::GetState() const { return state_; } + +void BluetoothAudioPortHidl::SetState(BluetoothStreamState state) { + state_ = state; +} + +size_t BluetoothAudioPortHidlOut::WriteData(const void* buffer, + size_t bytes) const { + if (!BluetoothAudioPortHidl::in_use()) return 0; + if (!BluetoothAudioPortHidl::is_stereo_to_mono_) { + return BluetoothAudioSessionControl_2_1::OutWritePcmData(session_type_hidl_, + buffer, bytes); + } + + // WAR to mix the stereo into Mono (16 bits per sample) + const size_t write_frames = bytes >> 2; + if (write_frames == 0) return 0; + auto src = static_cast(buffer); + std::unique_ptr dst{new int16_t[write_frames]}; + downmix_to_mono_i16_from_stereo_i16(dst.get(), src, write_frames); + // a frame is 16 bits, and the size of a mono frame is equal to half a stereo. + return BluetoothAudioSessionControl_2_1::OutWritePcmData( + session_type_hidl_, dst.get(), write_frames * 2) * + 2; +} + +size_t BluetoothAudioPortHidlIn::ReadData(void* buffer, size_t bytes) const { + if (!BluetoothAudioPortHidl::in_use()) return 0; + return BluetoothAudioSessionControl_2_1::InReadPcmData(session_type_hidl_, + buffer, bytes); +} + +bool BluetoothAudioPortHidlIn::LoadAudioConfig( + audio_config_t* audio_cfg) const { + if (!BluetoothAudioPortHidl::in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortIn is not in use"; + audio_cfg->sample_rate = kBluetoothDefaultSampleRate; + audio_cfg->channel_mask = kBluetoothDefaultInputChannelModeMask; + audio_cfg->format = kBluetoothDefaultAudioFormatBitsPerSample; + return false; + } + + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + hal_audio_cfg = + BluetoothAudioSessionControl_2_1::GetAudioConfig(session_type_hidl_); + if (hal_audio_cfg.getDiscriminator() != + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration:: + hidl_discriminator::pcmConfig) { + audio_cfg->sample_rate = kBluetoothDefaultSampleRate; + audio_cfg->channel_mask = kBluetoothDefaultInputChannelModeMask; + audio_cfg->format = kBluetoothDefaultAudioFormatBitsPerSample; + return false; + } + const ::android::hardware::bluetooth::audio::V2_1::PcmParameters& pcm_cfg = + hal_audio_cfg.pcmConfig(); + LOG(VERBOSE) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" + << StringPrintf("%#hx", BluetoothAudioPortHidl::cookie_) + << ", state=" << BluetoothAudioPortHidl::state_ + << ", PcmConfig=[" << toString(pcm_cfg) << "]"; + if (pcm_cfg.sampleRate == SampleRate_2_1::RATE_UNKNOWN || + pcm_cfg.channelMode == ChannelMode::UNKNOWN || + pcm_cfg.bitsPerSample == BitsPerSample::BITS_UNKNOWN) { + return false; + } + + audio_cfg->sample_rate = SampleRateToAudioFormat(pcm_cfg.sampleRate); + audio_cfg->channel_mask = InputChannelModeToAudioFormat(pcm_cfg.channelMode); + audio_cfg->format = BitsPerSampleToAudioFormat(pcm_cfg.bitsPerSample); + return true; +} + +bool BluetoothAudioPortHidlOut::LoadAudioConfig( + audio_config_t* audio_cfg) const { + if (!BluetoothAudioPortHidl::in_use()) { + LOG(ERROR) << __func__ << ": BluetoothAudioPortOut is not in use"; + audio_cfg->sample_rate = kBluetoothDefaultSampleRate; + audio_cfg->channel_mask = kBluetoothDefaultOutputChannelModeMask; + audio_cfg->format = kBluetoothDefaultAudioFormatBitsPerSample; + return false; + } + + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + hal_audio_cfg = + BluetoothAudioSessionControl_2_1::GetAudioConfig(session_type_hidl_); + if (hal_audio_cfg.getDiscriminator() != + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration:: + hidl_discriminator::pcmConfig) { + audio_cfg->sample_rate = kBluetoothDefaultSampleRate; + audio_cfg->channel_mask = kBluetoothDefaultOutputChannelModeMask; + audio_cfg->format = kBluetoothDefaultAudioFormatBitsPerSample; + return false; + } + const ::android::hardware::bluetooth::audio::V2_1::PcmParameters& pcm_cfg = + hal_audio_cfg.pcmConfig(); + LOG(VERBOSE) << __func__ << ": session_type=" << toString(session_type_hidl_) + << ", cookie=" + << StringPrintf("%#hx", BluetoothAudioPortHidl::cookie_) + << ", state=" << BluetoothAudioPortHidl::state_ + << ", PcmConfig=[" << toString(pcm_cfg) << "]"; + if (pcm_cfg.sampleRate == SampleRate_2_1::RATE_UNKNOWN || + pcm_cfg.channelMode == ChannelMode::UNKNOWN || + pcm_cfg.bitsPerSample == BitsPerSample::BITS_UNKNOWN) { + return false; + } + audio_cfg->sample_rate = SampleRateToAudioFormat(pcm_cfg.sampleRate); + audio_cfg->channel_mask = + (BluetoothAudioPortHidl::is_stereo_to_mono_ + ? AUDIO_CHANNEL_OUT_STEREO + : OutputChannelModeToAudioFormat(pcm_cfg.channelMode)); + audio_cfg->format = BitsPerSampleToAudioFormat(pcm_cfg.bitsPerSample); + return true; +} + +} // namespace hidl +} // namespace audio +} // namespace bluetooth +} // namespace android \ No newline at end of file diff --git a/bluetooth/audio/hw/device_port_proxy_hidl.h b/bluetooth/audio/hw/device_port_proxy_hidl.h new file mode 100644 index 0000000..f37370d --- /dev/null +++ b/bluetooth/audio/hw/device_port_proxy_hidl.h @@ -0,0 +1,116 @@ +/* + * Copyright 2022 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. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +#include "device_port_proxy.h" + +enum class BluetoothStreamState : uint8_t; + +namespace android { +namespace bluetooth { +namespace audio { +namespace hidl { + +using SessionType_2_1 = + ::android::hardware::bluetooth::audio::V2_1::SessionType; + +class BluetoothAudioPortHidl : public BluetoothAudioPort { + public: + BluetoothAudioPortHidl(); + virtual ~BluetoothAudioPortHidl() = default; + + bool SetUp(audio_devices_t devices) override; + + void TearDown() override; + + void ForcePcmStereoToMono(bool force) override { is_stereo_to_mono_ = force; } + + bool Start() override; + + bool Suspend() override; + + void Stop() override; + + bool GetPresentationPosition(uint64_t* delay_ns, uint64_t* bytes, + timespec* timestamp) const override; + + void UpdateSourceMetadata( + const source_metadata* source_metadata) const override; + + BluetoothStreamState GetState() const override; + + void SetState(BluetoothStreamState state) override; + + bool IsA2dp() const override { + return session_type_hidl_ == + SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type_hidl_ == + SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH; + } + + bool GetPreferredDataIntervalUs(size_t* interval_us) const override; + + protected: + SessionType_2_1 session_type_hidl_; + uint16_t cookie_; + BluetoothStreamState state_; + // WR to support Mono: True if fetching Stereo and mixing into Mono + bool is_stereo_to_mono_ = false; + + bool in_use() const; + + private: + mutable std::mutex cv_mutex_; + std::condition_variable internal_cv_; + + bool init_session_type(audio_devices_t device); + + bool CondwaitState(BluetoothStreamState state); + + void ControlResultHandler( + const ::android::hardware::bluetooth::audio::V2_0::Status& status); + + void SessionChangedHandler(); +}; + +class BluetoothAudioPortHidlOut : public BluetoothAudioPortHidl { + public: + ~BluetoothAudioPortHidlOut(); + + size_t WriteData(const void* buffer, size_t bytes) const override; + bool LoadAudioConfig(audio_config_t* audio_cfg) const override; +}; + +class BluetoothAudioPortHidlIn : public BluetoothAudioPortHidl { + public: + ~BluetoothAudioPortHidlIn(); + + size_t ReadData(void* buffer, size_t bytes) const override; + bool LoadAudioConfig(audio_config_t* audio_cfg) const override; +}; + +} // namespace hidl +} // namespace audio +} // namespace bluetooth +} // namespace android \ No newline at end of file diff --git a/bluetooth/audio/hw/stream_apis.cc b/bluetooth/audio/hw/stream_apis.cc new file mode 100644 index 0000000..1f449fc --- /dev/null +++ b/bluetooth/audio/hw/stream_apis.cc @@ -0,0 +1,1308 @@ +/* + * Copyright 2019 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. + */ + +#include "device_port_proxy.h" +#define LOG_TAG "BTAudioHalStream" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "BluetoothAudioSession.h" +#include "stream_apis.h" +#include "utils.h" + +using ::android::base::StringPrintf; +using ::android::bluetooth::audio::utils::GetAudioParamString; +using ::android::bluetooth::audio::utils::ParseAudioParams; + +namespace { + +constexpr unsigned int kMinimumDelayMs = 50; +constexpr unsigned int kMaximumDelayMs = 1000; +constexpr int kExtraAudioSyncMs = 200; + +std::ostream& operator<<(std::ostream& os, const audio_config& config) { + return os << "audio_config[sample_rate=" << config.sample_rate + << ", channels=" << StringPrintf("%#x", config.channel_mask) + << ", format=" << config.format << "]"; +} + +void out_calculate_feeding_delay_ms(const BluetoothStreamOut* out, + uint32_t* latency_ms, + uint64_t* frames = nullptr, + struct timespec* timestamp = nullptr) { + if (latency_ms == nullptr && frames == nullptr && timestamp == nullptr) { + return; + } + + // delay_report is the audio delay from the remote headset receiving data to + // the headset playing sound in units of nanoseconds + uint64_t delay_report_ns = 0; + uint64_t delay_report_ms = 0; + // absorbed_bytes is the total number of bytes sent by the Bluetooth stack to + // a remote headset + uint64_t absorbed_bytes = 0; + // absorbed_timestamp is the ... + struct timespec absorbed_timestamp = {}; + bool timestamp_fetched = false; + + std::unique_lock lock(out->mutex_); + if (out->bluetooth_output_->GetPresentationPosition( + &delay_report_ns, &absorbed_bytes, &absorbed_timestamp)) { + delay_report_ms = delay_report_ns / 1000000; + // assume kMinimumDelayMs (50ms) < delay_report_ns < kMaximumDelayMs + // (1000ms), or it is invalid / ignored and use old delay calculated + // by ourselves. + if (delay_report_ms > kMinimumDelayMs && + delay_report_ms < kMaximumDelayMs) { + timestamp_fetched = true; + } else if (delay_report_ms >= kMaximumDelayMs) { + LOG(INFO) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", delay_report=" << delay_report_ns << "ns abnormal"; + } + } + if (!timestamp_fetched) { + // default to old delay if any failure is found when fetching from ports + // audio_a2dp_hw: + // frames_count = buffer_size / frame_size + // latency (sec.) = frames_count / samples_per_second (sample_rate) + // Sync from audio_a2dp_hw to add extra delay kExtraAudioSyncMs(+200ms) + delay_report_ms = + out->frames_count_ * 1000 / out->sample_rate_ + kExtraAudioSyncMs; + if (timestamp != nullptr) { + clock_gettime(CLOCK_MONOTONIC, &absorbed_timestamp); + } + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " uses the legacy delay " << delay_report_ms << " ms"; + } + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", delay=" << delay_report_ms << "ms, data=" << absorbed_bytes + << " bytes, timestamp=" << absorbed_timestamp.tv_sec << "." + << StringPrintf("%09ld", absorbed_timestamp.tv_nsec) << "s"; + + if (latency_ms != nullptr) { + *latency_ms = delay_report_ms; + } + if (frames != nullptr) { + const uint64_t latency_frames = delay_report_ms * out->sample_rate_ / 1000; + *frames = absorbed_bytes / audio_stream_out_frame_size(&out->stream_out_); + if (out->frames_presented_ < *frames) { + // Are we (the audio HAL) reset?! The stack counter is obsoleted. + *frames = out->frames_presented_; + } else if ((out->frames_presented_ - *frames) > latency_frames) { + // Is the Bluetooth output reset / restarted by AVDTP reconfig?! Its + // counter was reset but could not be used. + *frames = out->frames_presented_; + } + // suppose frames would be queued in the headset buffer for delay_report + // period, so those frames in buffers should not be included in the number + // of presented frames at the timestamp. + if (*frames > latency_frames) { + *frames -= latency_frames; + } else { + *frames = 0; + } + } + if (timestamp != nullptr) { + *timestamp = absorbed_timestamp; + } +} + +void in_calculate_starving_delay_ms(const BluetoothStreamIn* in, + int64_t* frames, int64_t* time) { + // delay_report is the audio delay from the remote headset receiving data to + // the headset playing sound in units of nanoseconds + uint64_t delay_report_ns = 0; + uint64_t delay_report_ms = 0; + // dispersed_bytes is the total number of bytes received by the Bluetooth + // stack from a remote headset + uint64_t dispersed_bytes = 0; + struct timespec dispersed_timestamp = {}; + + std::unique_lock lock(in->mutex_); + in->bluetooth_input_->GetPresentationPosition( + &delay_report_ns, &dispersed_bytes, &dispersed_timestamp); + delay_report_ms = delay_report_ns / 1000000; + + const uint64_t latency_frames = delay_report_ms * in->sample_rate_ / 1000; + *frames = dispersed_bytes / audio_stream_in_frame_size(&in->stream_in_); + + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", delay=" << delay_report_ms + << "ms, data=" << dispersed_bytes + << " bytes, timestamp=" << dispersed_timestamp.tv_sec << "." + << StringPrintf("%09ld", dispersed_timestamp.tv_nsec) << "s"; + + if (in->frames_presented_ < *frames) { + // Was audio HAL reset?! The stack counter is obsoleted. + *frames = in->frames_presented_; + } else if ((in->frames_presented_ - *frames) > latency_frames) { + // Is the Bluetooth input reset ?! Its counter was reset but could not be + // used. + *frames = in->frames_presented_; + } + // suppose frames would be queued in the headset buffer for delay_report + // period, so those frames in buffers should not be included in the number + // of presented frames at the timestamp. + if (*frames > latency_frames) { + *frames -= latency_frames; + } else { + *frames = 0; + } + + *time = (dispersed_timestamp.tv_sec * 1000000000LL + + dispersed_timestamp.tv_nsec) / + 1000; +} + +} // namespace + +std::ostream& operator<<(std::ostream& os, const BluetoothStreamState& state) { + switch (state) { + case BluetoothStreamState::DISABLED: + return os << "DISABLED"; + case BluetoothStreamState::STANDBY: + return os << "STANDBY"; + case BluetoothStreamState::STARTING: + return os << "STARTING"; + case BluetoothStreamState::STARTED: + return os << "STARTED"; + case BluetoothStreamState::SUSPENDING: + return os << "SUSPENDING"; + case BluetoothStreamState::UNKNOWN: + return os << "UNKNOWN"; + default: + return os << StringPrintf("%#hhx", state); + } +} + +static uint32_t out_get_sample_rate(const struct audio_stream* stream) { + const auto* out = reinterpret_cast(stream); + audio_config_t audio_cfg; + if (out->bluetooth_output_->LoadAudioConfig(&audio_cfg)) { + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " audio_cfg=" << audio_cfg; + return audio_cfg.sample_rate; + } else { + LOG(WARNING) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", sample_rate=" << out->sample_rate_ << " failed"; + return out->sample_rate_; + } +} + +static int out_set_sample_rate(struct audio_stream* stream, uint32_t rate) { + auto* out = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", sample_rate=" << out->sample_rate_; + return (rate == out->sample_rate_ ? 0 : -1); +} + +static size_t out_get_buffer_size(const struct audio_stream* stream) { + const auto* out = reinterpret_cast(stream); + size_t buffer_size = + out->frames_count_ * audio_stream_out_frame_size(&out->stream_out_); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", buffer_size=" << buffer_size; + return buffer_size; +} + +static audio_channel_mask_t out_get_channels( + const struct audio_stream* stream) { + const auto* out = reinterpret_cast(stream); + audio_config_t audio_cfg; + if (out->bluetooth_output_->LoadAudioConfig(&audio_cfg)) { + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " audio_cfg=" << audio_cfg; + return audio_cfg.channel_mask; + } else { + LOG(WARNING) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", channels=" << StringPrintf("%#x", out->channel_mask_) + << " failure"; + return out->channel_mask_; + } +} + +static audio_format_t out_get_format(const struct audio_stream* stream) { + const auto* out = reinterpret_cast(stream); + audio_config_t audio_cfg; + if (out->bluetooth_output_->LoadAudioConfig(&audio_cfg)) { + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " audio_cfg=" << audio_cfg; + return audio_cfg.format; + } else { + LOG(WARNING) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", format=" << out->format_ << " failure"; + return out->format_; + } +} + +static int out_set_format(struct audio_stream* stream, audio_format_t format) { + auto* out = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", format=" << out->format_; + return (format == out->format_ ? 0 : -1); +} + +static int out_standby(struct audio_stream* stream) { + auto* out = reinterpret_cast(stream); + std::unique_lock lock(out->mutex_); + int retval = 0; + + // out->last_write_time_us_ = 0; unnecessary as a stale write time has same + // effect + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " being standby (suspend)"; + if (out->bluetooth_output_->GetState() == BluetoothStreamState::STARTED) { + out->frames_rendered_ = 0; + retval = (out->bluetooth_output_->Suspend() ? 0 : -EIO); + } else if (out->bluetooth_output_->GetState() == + BluetoothStreamState::STARTING || + out->bluetooth_output_->GetState() == + BluetoothStreamState::SUSPENDING) { + LOG(WARNING) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " NOT ready to be standby"; + retval = -EBUSY; + } else { + LOG(DEBUG) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " standby already"; + } + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " standby (suspend) retval=" << retval; + + return retval; +} + +static int out_dump(const struct audio_stream* stream, int fd) { + const auto* out = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState(); + return 0; +} + +static int out_set_parameters(struct audio_stream* stream, + const char* kvpairs) { + auto* out = reinterpret_cast(stream); + std::unique_lock lock(out->mutex_); + int retval = 0; + + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", kvpairs=[" << kvpairs << "]"; + + std::unordered_map params = + ParseAudioParams(kvpairs); + if (params.empty()) return retval; + + LOG(VERBOSE) << __func__ << ": ParamsMap=[" << GetAudioParamString(params) + << "]"; + + audio_config_t audio_cfg; + if (params.find(AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES) != params.end() || + params.find(AUDIO_PARAMETER_STREAM_SUP_CHANNELS) != params.end() || + params.find(AUDIO_PARAMETER_STREAM_SUP_FORMATS) != params.end()) { + if (out->bluetooth_output_->LoadAudioConfig(&audio_cfg)) { + out->sample_rate_ = audio_cfg.sample_rate; + out->channel_mask_ = audio_cfg.channel_mask; + out->format_ = audio_cfg.format; + LOG(VERBOSE) << "state=" << out->bluetooth_output_->GetState() + << ", sample_rate=" << out->sample_rate_ + << ", channels=" << StringPrintf("%#x", out->channel_mask_) + << ", format=" << out->format_; + } else { + LOG(WARNING) << __func__ + << ": state=" << out->bluetooth_output_->GetState() + << " failed to get audio config"; + } + } + + if (params.find("routing") != params.end()) { + auto routing_param = params.find("routing"); + LOG(INFO) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", stream param '" << routing_param->first.c_str() << "=" + << routing_param->second.c_str() << "'"; + } + + if (params.find("A2dpSuspended") != params.end() && + out->bluetooth_output_->IsA2dp()) { + if (params["A2dpSuspended"] == "true") { + LOG(INFO) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " stream param stopped"; + out->frames_rendered_ = 0; + if (out->bluetooth_output_->GetState() == BluetoothStreamState::STARTED) { + out->bluetooth_output_->Suspend(); + out->bluetooth_output_->SetState(BluetoothStreamState::DISABLED); + } else if (out->bluetooth_output_->GetState() != + BluetoothStreamState::DISABLED) { + out->bluetooth_output_->Stop(); + } + } else { + LOG(INFO) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " stream param standby"; + if (out->bluetooth_output_->GetState() == + BluetoothStreamState::DISABLED) { + out->bluetooth_output_->SetState(BluetoothStreamState::STANDBY); + } + } + } + + if (params.find("closing") != params.end()) { + if (params["closing"] == "true") { + LOG(INFO) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " stream param closing, disallow any writes?"; + if (out->bluetooth_output_->GetState() != + BluetoothStreamState::DISABLED) { + out->frames_rendered_ = 0; + out->frames_presented_ = 0; + out->bluetooth_output_->Stop(); + } + } + } + + if (params.find("exiting") != params.end()) { + if (params["exiting"] == "1") { + LOG(INFO) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " stream param exiting"; + if (out->bluetooth_output_->GetState() != + BluetoothStreamState::DISABLED) { + out->frames_rendered_ = 0; + out->frames_presented_ = 0; + out->bluetooth_output_->Stop(); + } + } + } + + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", kvpairs=[" << kvpairs << "], retval=" << retval; + return retval; +} + +static char* out_get_parameters(const struct audio_stream* stream, + const char* keys) { + const auto* out = reinterpret_cast(stream); + std::unique_lock lock(out->mutex_); + + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", keys=[" << keys << "]"; + + std::unordered_map params = ParseAudioParams(keys); + if (params.empty()) return strdup(""); + + audio_config_t audio_cfg; + if (out->bluetooth_output_->LoadAudioConfig(&audio_cfg)) { + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " audio_cfg=" << audio_cfg; + } else { + LOG(ERROR) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " failed to get audio config"; + } + + std::unordered_map return_params; + if (params.find(AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES) != params.end()) { + std::string param; + if (audio_cfg.sample_rate == 16000) { + param = "16000"; + } + if (audio_cfg.sample_rate == 24000) { + param = "24000"; + } + if (audio_cfg.sample_rate == 44100) { + param = "44100"; + } + if (audio_cfg.sample_rate == 48000) { + param = "48000"; + } + if (audio_cfg.sample_rate == 88200) { + param = "88200"; + } + if (audio_cfg.sample_rate == 96000) { + param = "96000"; + } + if (audio_cfg.sample_rate == 176400) { + param = "176400"; + } + if (audio_cfg.sample_rate == 192000) { + param = "192000"; + } + return_params[AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES] = param; + } + + if (params.find(AUDIO_PARAMETER_STREAM_SUP_CHANNELS) != params.end()) { + std::string param; + if (audio_cfg.channel_mask == AUDIO_CHANNEL_OUT_MONO) { + param = "AUDIO_CHANNEL_OUT_MONO"; + } + if (audio_cfg.channel_mask == AUDIO_CHANNEL_OUT_STEREO) { + param = "AUDIO_CHANNEL_OUT_STEREO"; + } + return_params[AUDIO_PARAMETER_STREAM_SUP_CHANNELS] = param; + } + + if (params.find(AUDIO_PARAMETER_STREAM_SUP_FORMATS) != params.end()) { + std::string param; + if (audio_cfg.format == AUDIO_FORMAT_PCM_16_BIT) { + param = "AUDIO_FORMAT_PCM_16_BIT"; + } + if (audio_cfg.format == AUDIO_FORMAT_PCM_24_BIT_PACKED) { + param = "AUDIO_FORMAT_PCM_24_BIT_PACKED"; + } + if (audio_cfg.format == AUDIO_FORMAT_PCM_8_24_BIT) { + param = "AUDIO_FORMAT_PCM_8_24_BIT"; + } + if (audio_cfg.format == AUDIO_FORMAT_PCM_32_BIT) { + param = "AUDIO_FORMAT_PCM_32_BIT"; + } + return_params[AUDIO_PARAMETER_STREAM_SUP_FORMATS] = param; + } + + std::string result; + for (const auto& ptr : return_params) { + result += ptr.first + "=" + ptr.second + ";"; + } + + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", result=[" << result << "]"; + return strdup(result.c_str()); +} + +static uint32_t out_get_latency_ms(const struct audio_stream_out* stream) { + const auto* out = reinterpret_cast(stream); + uint32_t latency_ms = 0; + out_calculate_feeding_delay_ms(out, &latency_ms); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", latency=" << latency_ms << "ms"; + return latency_ms; +} + +static int out_set_volume(struct audio_stream_out* stream, float left, + float right) { + auto* out = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", Left=" << left << ", Right=" << right; + return -1; +} + +static ssize_t out_write(struct audio_stream_out* stream, const void* buffer, + size_t bytes) { + auto* out = reinterpret_cast(stream); + std::unique_lock lock(out->mutex_); + size_t totalWritten = 0; + + if (out->bluetooth_output_->GetState() != BluetoothStreamState::STARTED) { + LOG(INFO) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " first time bytes=" << bytes; + lock.unlock(); + if (stream->resume(stream)) { + LOG(ERROR) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " failed to resume"; + if (out->bluetooth_output_->GetState() == + BluetoothStreamState::DISABLED) { + // drop data for cases of A2dpSuspended=true / closing=true + totalWritten = bytes; + } + usleep(out->preferred_data_interval_us); + return totalWritten; + } + lock.lock(); + } + lock.unlock(); + totalWritten = out->bluetooth_output_->WriteData(buffer, bytes); + lock.lock(); + + struct timespec ts = {.tv_sec = 0, .tv_nsec = 0}; + clock_gettime(CLOCK_MONOTONIC, &ts); + if (totalWritten) { + const size_t frames = bytes / audio_stream_out_frame_size(stream); + out->frames_rendered_ += frames; + out->frames_presented_ += frames; + out->last_write_time_us_ = (ts.tv_sec * 1000000000LL + ts.tv_nsec) / 1000; + } else { + const int64_t now = (ts.tv_sec * 1000000000LL + ts.tv_nsec) / 1000; + const int64_t elapsed_time_since_last_write = + now - out->last_write_time_us_; + // frames_count = written_data / frame_size + // play_time (ms) = frames_count / (sample_rate (Sec.) / 1000000) + // sleep_time (ms) = play_time - elapsed_time + int64_t sleep_time = bytes * 1000000LL / + audio_stream_out_frame_size(stream) / + out_get_sample_rate(&stream->common) - + elapsed_time_since_last_write; + if (sleep_time > 0) { + LOG(VERBOSE) << __func__ << ": sleep " << (sleep_time / 1000) + << " ms when writting FMQ datapath"; + lock.unlock(); + usleep(sleep_time); + lock.lock(); + } else { + // we don't sleep when we exit standby (this is typical for a real alsa + // buffer). + sleep_time = 0; + } + out->last_write_time_us_ = now + sleep_time; + } + return totalWritten; +} + +static int out_get_render_position(const struct audio_stream_out* stream, + uint32_t* dsp_frames) { + if (dsp_frames == nullptr) return -EINVAL; + + const auto* out = reinterpret_cast(stream); + // frames = (latency (ms) / 1000) * samples_per_second (sample_rate) + const uint64_t latency_frames = + (uint64_t)out_get_latency_ms(stream) * out->sample_rate_ / 1000; + if (out->frames_rendered_ >= latency_frames) { + *dsp_frames = (uint32_t)(out->frames_rendered_ - latency_frames); + } else { + *dsp_frames = 0; + } + + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", dsp_frames=" << *dsp_frames; + return 0; +} + +static int out_add_audio_effect(const struct audio_stream* stream, + effect_handle_t effect) { + const auto* out = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", effect=" << effect; + return 0; +} + +static int out_remove_audio_effect(const struct audio_stream* stream, + effect_handle_t effect) { + const auto* out = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", effect=" << effect; + return 0; +} + +static int out_get_next_write_timestamp(const struct audio_stream_out* stream, + int64_t* timestamp) { + const auto* out = reinterpret_cast(stream); + *timestamp = 0; + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", timestamp=" << *timestamp; + return -EINVAL; +} + +static int out_pause(struct audio_stream_out* stream) { + auto* out = reinterpret_cast(stream); + std::unique_lock lock(out->mutex_); + int retval = 0; + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", pausing (suspend)"; + if (out->bluetooth_output_->GetState() == BluetoothStreamState::STARTED) { + out->frames_rendered_ = 0; + retval = (out->bluetooth_output_->Suspend() ? 0 : -EIO); + } else if (out->bluetooth_output_->GetState() == + BluetoothStreamState::STARTING || + out->bluetooth_output_->GetState() == + BluetoothStreamState::SUSPENDING) { + LOG(WARNING) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " NOT ready to pause?!"; + retval = -EBUSY; + } else { + LOG(DEBUG) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " paused already"; + } + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", pausing (suspend) retval=" << retval; + + return retval; +} + +static int out_resume(struct audio_stream_out* stream) { + auto* out = reinterpret_cast(stream); + std::unique_lock lock(out->mutex_); + int retval = 0; + + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", resuming (start)"; + if (out->bluetooth_output_->GetState() == BluetoothStreamState::STANDBY) { + retval = (out->bluetooth_output_->Start() ? 0 : -EIO); + } else if (out->bluetooth_output_->GetState() == + BluetoothStreamState::STARTING || + out->bluetooth_output_->GetState() == + BluetoothStreamState::SUSPENDING) { + LOG(WARNING) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " NOT ready to resume?!"; + retval = -EBUSY; + } else if (out->bluetooth_output_->GetState() == + BluetoothStreamState::DISABLED) { + LOG(WARNING) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " NOT allow to resume?!"; + retval = -EINVAL; + } else { + LOG(DEBUG) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " resumed already"; + } + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", resuming (start) retval=" << retval; + + return retval; +} + +static int out_get_presentation_position(const struct audio_stream_out* stream, + uint64_t* frames, + struct timespec* timestamp) { + if (frames == nullptr || timestamp == nullptr) { + return -EINVAL; + } + + const auto* out = reinterpret_cast(stream); + out_calculate_feeding_delay_ms(out, nullptr, frames, timestamp); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", frames=" << *frames << ", timestamp=" << timestamp->tv_sec + << "." << StringPrintf("%09ld", timestamp->tv_nsec) << "s"; + return 0; +} + +static void out_update_source_metadata( + struct audio_stream_out* stream, + const struct source_metadata* source_metadata) { + auto* out = reinterpret_cast(stream); + std::unique_lock lock(out->mutex_); + if (source_metadata == nullptr || source_metadata->track_count == 0) { + return; + } + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", " << source_metadata->track_count << " track(s)"; + out->bluetooth_output_->UpdateSourceMetadata(source_metadata); +} + +static size_t frame_count(size_t microseconds, uint32_t sample_rate) { + return (microseconds * sample_rate) / 1000000; +} + +int adev_open_output_stream(struct audio_hw_device* dev, + audio_io_handle_t handle, audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config* config, + struct audio_stream_out** stream_out, + const char* address __unused) { + *stream_out = nullptr; + auto out = std::make_unique(); + if (::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession:: + IsAidlAvailable()) { + out->bluetooth_output_ = std::make_unique< + ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut>(); + out->is_aidl = true; + } else { + out->bluetooth_output_ = std::make_unique< + ::android::bluetooth::audio::hidl::BluetoothAudioPortHidlOut>(); + out->is_aidl = false; + } + if (!out->bluetooth_output_->SetUp(devices)) { + out->bluetooth_output_ = nullptr; + LOG(ERROR) << __func__ << ": cannot init HAL"; + return -EINVAL; + } + LOG(VERBOSE) << __func__ << ": device=" << StringPrintf("%#x", devices); + + out->stream_out_.common.get_sample_rate = out_get_sample_rate; + out->stream_out_.common.set_sample_rate = out_set_sample_rate; + out->stream_out_.common.get_buffer_size = out_get_buffer_size; + out->stream_out_.common.get_channels = out_get_channels; + out->stream_out_.common.get_format = out_get_format; + out->stream_out_.common.set_format = out_set_format; + out->stream_out_.common.standby = out_standby; + out->stream_out_.common.dump = out_dump; + out->stream_out_.common.set_parameters = out_set_parameters; + out->stream_out_.common.get_parameters = out_get_parameters; + out->stream_out_.common.add_audio_effect = out_add_audio_effect; + out->stream_out_.common.remove_audio_effect = out_remove_audio_effect; + out->stream_out_.get_latency = out_get_latency_ms; + out->stream_out_.set_volume = out_set_volume; + out->stream_out_.write = out_write; + out->stream_out_.get_render_position = out_get_render_position; + out->stream_out_.get_next_write_timestamp = out_get_next_write_timestamp; + out->stream_out_.pause = out_pause; + out->stream_out_.resume = out_resume; + out->stream_out_.get_presentation_position = out_get_presentation_position; + out->stream_out_.update_source_metadata = out_update_source_metadata; + + if (!out->bluetooth_output_->LoadAudioConfig(config)) { + LOG(ERROR) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << " failed to get audio config"; + } + // WAR to support Mono / 16 bits per sample as the Bluetooth stack required + if (config->channel_mask == AUDIO_CHANNEL_OUT_MONO && + config->format == AUDIO_FORMAT_PCM_16_BIT) { + LOG(INFO) << __func__ + << ": force channels=" << StringPrintf("%#x", out->channel_mask_) + << " to be AUDIO_CHANNEL_OUT_STEREO"; + out->bluetooth_output_->ForcePcmStereoToMono(true); + config->channel_mask = AUDIO_CHANNEL_OUT_STEREO; + } + out->sample_rate_ = config->sample_rate; + out->channel_mask_ = config->channel_mask; + out->format_ = config->format; + // frame is number of samples per channel + + size_t preferred_data_interval_us = kBluetoothDefaultOutputBufferMs * 1000; + if (out->bluetooth_output_->GetPreferredDataIntervalUs( + &preferred_data_interval_us) && + preferred_data_interval_us != 0) { + out->preferred_data_interval_us = preferred_data_interval_us; + } else { + out->preferred_data_interval_us = kBluetoothDefaultOutputBufferMs * 1000; + } + + // Ensure minimum buffer duration for spatialized output + if ((flags == (AUDIO_OUTPUT_FLAG_FAST | AUDIO_OUTPUT_FLAG_DEEP_BUFFER) || + flags == AUDIO_OUTPUT_FLAG_SPATIALIZER) && + out->preferred_data_interval_us < + kBluetoothSpatializerOutputBufferMs * 1000) { + out->preferred_data_interval_us = + kBluetoothSpatializerOutputBufferMs * 1000; + LOG(INFO) << __func__ + << ": adjusting to minimum buffer duration for spatializer: " + << StringPrintf("%zu", out->preferred_data_interval_us); + } + + out->frames_count_ = + frame_count(out->preferred_data_interval_us, out->sample_rate_); + + out->frames_rendered_ = 0; + out->frames_presented_ = 0; + + BluetoothStreamOut* out_ptr = out.release(); + { + auto* bluetooth_device = reinterpret_cast(dev); + std::lock_guard guard(bluetooth_device->mutex_); + bluetooth_device->opened_stream_outs_.push_back(out_ptr); + } + + *stream_out = &out_ptr->stream_out_; + LOG(INFO) << __func__ << ": state=" << out_ptr->bluetooth_output_->GetState() + << ", sample_rate=" << out_ptr->sample_rate_ + << ", channels=" << StringPrintf("%#x", out_ptr->channel_mask_) + << ", format=" << out_ptr->format_ + << ", preferred_data_interval_us=" + << out_ptr->preferred_data_interval_us + << ", frames=" << out_ptr->frames_count_; + return 0; +} + +void adev_close_output_stream(struct audio_hw_device* dev, + struct audio_stream_out* stream) { + auto* out = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", stopping"; + { + auto* bluetooth_device = reinterpret_cast(dev); + std::lock_guard guard(bluetooth_device->mutex_); + bluetooth_device->opened_stream_outs_.remove(out); + } + if (out->bluetooth_output_->GetState() != BluetoothStreamState::DISABLED) { + out->frames_rendered_ = 0; + out->frames_presented_ = 0; + out->bluetooth_output_->Stop(); + } + out->bluetooth_output_->TearDown(); + LOG(VERBOSE) << __func__ << ": state=" << out->bluetooth_output_->GetState() + << ", stopped"; + delete out; +} + +size_t adev_get_input_buffer_size(const struct audio_hw_device* dev, + const struct audio_config* config) { + /* TODO: Adjust this value */ + LOG(VERBOSE) << __func__; + return 320; +} + +static uint32_t in_get_sample_rate(const struct audio_stream* stream) { + const auto* in = reinterpret_cast(stream); + + return in->sample_rate_; +} + +static int in_set_sample_rate(struct audio_stream* stream, uint32_t rate) { + const auto* in = reinterpret_cast(stream); + + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", sample_rate=" << in->sample_rate_; + return (rate == in->sample_rate_ ? 0 : -ENOSYS); +} + +static size_t in_get_buffer_size(const struct audio_stream* stream) { + const auto* in = reinterpret_cast(stream); + size_t buffer_size = + in->frames_count_ * audio_stream_in_frame_size(&in->stream_in_); + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", buffer_size=" << buffer_size; + return buffer_size; +} + +static audio_channel_mask_t in_get_channels(const struct audio_stream* stream) { + const auto* in = reinterpret_cast(stream); + audio_config_t audio_cfg; + if (in->bluetooth_input_->LoadAudioConfig(&audio_cfg)) { + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " audio_cfg=" << audio_cfg; + return audio_cfg.channel_mask; + } else { + LOG(WARNING) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", channels=" << StringPrintf("%#x", in->channel_mask_) + << " failure"; + return in->channel_mask_; + } +} + +static audio_format_t in_get_format(const struct audio_stream* stream) { + const auto* in = reinterpret_cast(stream); + audio_config_t audio_cfg; + if (in->bluetooth_input_->LoadAudioConfig(&audio_cfg)) { + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " audio_cfg=" << audio_cfg; + return audio_cfg.format; + } else { + LOG(WARNING) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", format=" << in->format_ << " failure"; + return in->format_; + } +} + +static int in_set_format(struct audio_stream* stream, audio_format_t format) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", format=" << in->format_; + return (format == in->format_ ? 0 : -ENOSYS); +} + +static bool in_state_transition_timeout(BluetoothStreamIn* in, + std::unique_lock& lock, + const BluetoothStreamState& state, + uint16_t timeout_ms) { + /* Don't loose suspend request, AF will not retry */ + while (in->bluetooth_input_->GetState() == state) { + lock.unlock(); + usleep(1000); + lock.lock(); + + /* Don't block AF forever */ + if (--timeout_ms <= 0) { + LOG(WARNING) << __func__ << ", can't suspend - stucked in: " + << static_cast(state) << " state"; + return false; + } + } + + return true; +} + +static int in_standby(struct audio_stream* stream) { + auto* in = reinterpret_cast(stream); + std::unique_lock lock(in->mutex_); + int retval = 0; + + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " being standby (suspend)"; + + /* Give some time to start up */ + if (!in_state_transition_timeout(in, lock, BluetoothStreamState::STARTING, + kBluetoothDefaultInputStateTimeoutMs)) { + LOG(ERROR) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " NOT ready to by standby"; + return retval; + } + + if (in->bluetooth_input_->GetState() == BluetoothStreamState::STARTED) { + retval = (in->bluetooth_input_->Suspend() ? 0 : -EIO); + } else if (in->bluetooth_input_->GetState() != + BluetoothStreamState::SUSPENDING) { + LOG(DEBUG) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " standby already"; + return retval; + } + + /* Give some time to suspend */ + if (!in_state_transition_timeout(in, lock, BluetoothStreamState::SUSPENDING, + kBluetoothDefaultInputStateTimeoutMs)) { + LOG(ERROR) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " NOT ready to by standby"; + return 0; + } + + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " standby (suspend) retval=" << retval; + + return retval; +} + +static int in_dump(const struct audio_stream* stream, int fd) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState(); + + return 0; +} + +static int in_set_parameters(struct audio_stream* stream, const char* kvpairs) { + auto* in = reinterpret_cast(stream); + std::unique_lock lock(in->mutex_); + int retval = 0; + + LOG(INFO) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState() + << ", kvpairs=[" << kvpairs << "]"; + + std::unordered_map params = + ParseAudioParams(kvpairs); + + if (params.empty()) return retval; + + LOG(INFO) << __func__ << ": ParamsMap=[" << GetAudioParamString(params) + << "]"; + + return retval; +} + +static char* in_get_parameters(const struct audio_stream* stream, + const char* keys) { + const auto* in = reinterpret_cast(stream); + std::unique_lock lock(in->mutex_); + + LOG(VERBOSE) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState() + << ", keys=[" << keys << "]"; + + std::unordered_map params = ParseAudioParams(keys); + if (params.empty()) return strdup(""); + + audio_config_t audio_cfg; + if (in->bluetooth_input_->LoadAudioConfig(&audio_cfg)) { + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " audio_cfg=" << audio_cfg; + } else { + LOG(ERROR) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " failed to get audio config"; + } + + std::unordered_map return_params; + + /* TODO: Implement parameter getter */ + + std::string result; + for (const auto& ptr : return_params) { + result += ptr.first + "=" + ptr.second + ";"; + } + + return strdup(result.c_str()); +} + +static int in_add_audio_effect(const struct audio_stream* stream, + effect_handle_t effect) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", effect=" << effect; + return 0; +} + +static int in_remove_audio_effect(const struct audio_stream* stream, + effect_handle_t effect) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", effect=" << effect; + return 0; +} + +static int in_set_gain(struct audio_stream_in* stream, float gain) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState(); + + return 0; +} + +static ssize_t in_read(struct audio_stream_in* stream, void* buffer, + size_t bytes) { + auto* in = reinterpret_cast(stream); + std::unique_lock lock(in->mutex_); + size_t totalRead = 0; + + /* Give some time to start up */ + if (!in_state_transition_timeout(in, lock, BluetoothStreamState::STARTING, + kBluetoothDefaultInputStateTimeoutMs)) + return -EBUSY; + + if (in->bluetooth_input_->GetState() != BluetoothStreamState::STARTED) { + LOG(INFO) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " first time bytes=" << bytes; + + int retval = 0; + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", starting"; + if (in->bluetooth_input_->GetState() == BluetoothStreamState::STANDBY) { + retval = (in->bluetooth_input_->Start() ? 0 : -EIO); + } else if (in->bluetooth_input_->GetState() == + BluetoothStreamState::SUSPENDING) { + LOG(WARNING) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " NOT ready to start?!"; + retval = -EBUSY; + } else if (in->bluetooth_input_->GetState() == + BluetoothStreamState::DISABLED) { + LOG(WARNING) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " NOT allow to start?!"; + retval = -EINVAL; + } else { + LOG(DEBUG) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " started already"; + } + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", starting (start) retval=" << retval; + + if (retval) { + LOG(ERROR) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " failed to start"; + return retval; + } + } + + lock.unlock(); + totalRead = in->bluetooth_input_->ReadData(buffer, bytes); + lock.lock(); + + struct timespec ts = {.tv_sec = 0, .tv_nsec = 0}; + clock_gettime(CLOCK_MONOTONIC, &ts); + in->last_read_time_us_ = (ts.tv_sec * 1000000000LL + ts.tv_nsec) / 1000; + + const size_t frames = totalRead / audio_stream_in_frame_size(stream); + in->frames_presented_ += frames; + + return totalRead; +} + +static uint32_t in_get_input_frames_lost(struct audio_stream_in* stream) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState(); + + return 0; +} + +static int in_get_capture_position(const struct audio_stream_in* stream, + int64_t* frames, int64_t* time) { + if (stream == NULL || frames == NULL || time == NULL) { + return -EINVAL; + } + const auto* in = reinterpret_cast(stream); + + if (in->bluetooth_input_->GetState() == BluetoothStreamState::STANDBY) { + LOG(WARNING) << __func__ << ": state= " << in->bluetooth_input_->GetState(); + return -ENOSYS; + } + + in_calculate_starving_delay_ms(in, frames, time); + + return 0; +} + +static int in_start(const struct audio_stream_in* stream) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState(); + + return 0; +} + +static int in_stop(const struct audio_stream_in* stream) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState(); + + return 0; +} + +static int in_create_mmap_buffer(const struct audio_stream_in* stream, + int32_t min_size_frames, + struct audio_mmap_buffer_info* info) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState(); + + return -ENOSYS; +} + +static int in_get_mmap_position(const struct audio_stream_in* stream, + struct audio_mmap_position* position) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState(); + + return -ENOSYS; +} + +static int in_get_active_microphones( + const struct audio_stream_in* stream, + struct audio_microphone_characteristic_t* mic_array, size_t* mic_count) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState(); + + return -ENOSYS; +} + +static int in_set_microphone_direction(const struct audio_stream_in* stream, + audio_microphone_direction_t direction) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState(); + + return -ENOSYS; +} + +static int in_set_microphone_field_dimension( + const struct audio_stream_in* stream, float zoom) { + const auto* in = reinterpret_cast(stream); + LOG(VERBOSE) << __func__ + << ": NOT HANDLED! state=" << in->bluetooth_input_->GetState(); + + return -ENOSYS; +} + +static void in_update_sink_metadata(struct audio_stream_in* stream, + const struct sink_metadata* sink_metadata) { + LOG(INFO) << __func__; + if (sink_metadata == nullptr || sink_metadata->track_count == 0) { + return; + } + + const auto* in = reinterpret_cast(stream); + LOG(INFO) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", " << sink_metadata->track_count << " track(s)"; + + if (!in->is_aidl) { + LOG(WARNING) << __func__ + << " is only supported in AIDL but using HIDL now!"; + return; + } + static_cast<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl*>( + in->bluetooth_input_.get()) + ->UpdateSinkMetadata(sink_metadata); +} + +int adev_open_input_stream(struct audio_hw_device* dev, + audio_io_handle_t handle, audio_devices_t devices, + struct audio_config* config, + struct audio_stream_in** stream_in, + audio_input_flags_t flags __unused, + const char* address __unused, + audio_source_t source __unused) { + *stream_in = nullptr; + auto in = std::make_unique(); + if (::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession:: + IsAidlAvailable()) { + in->bluetooth_input_ = std::make_unique< + ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlIn>(); + in->is_aidl = true; + } else { + in->bluetooth_input_ = std::make_unique< + ::android::bluetooth::audio::hidl::BluetoothAudioPortHidlIn>(); + in->is_aidl = false; + } + if (!in->bluetooth_input_->SetUp(devices)) { + in->bluetooth_input_ = nullptr; + LOG(ERROR) << __func__ << ": cannot init HAL"; + return -EINVAL; + } + + LOG(INFO) << __func__ << ": device=" << StringPrintf("%#x", devices); + + in->stream_in_.common.get_sample_rate = in_get_sample_rate; + in->stream_in_.common.set_sample_rate = in_set_sample_rate; + in->stream_in_.common.get_buffer_size = in_get_buffer_size; + in->stream_in_.common.get_channels = in_get_channels; + in->stream_in_.common.get_format = in_get_format; + in->stream_in_.common.set_format = in_set_format; + in->stream_in_.common.standby = in_standby; + in->stream_in_.common.dump = in_dump; + in->stream_in_.common.set_parameters = in_set_parameters; + in->stream_in_.common.get_parameters = in_get_parameters; + in->stream_in_.common.add_audio_effect = in_add_audio_effect; + in->stream_in_.common.remove_audio_effect = in_remove_audio_effect; + in->stream_in_.set_gain = in_set_gain; + in->stream_in_.read = in_read; + in->stream_in_.get_input_frames_lost = in_get_input_frames_lost; + in->stream_in_.get_capture_position = in_get_capture_position; + in->stream_in_.start = in_start; + in->stream_in_.stop = in_stop; + in->stream_in_.create_mmap_buffer = in_create_mmap_buffer; + in->stream_in_.get_mmap_position = in_get_mmap_position; + in->stream_in_.get_active_microphones = in_get_active_microphones; + in->stream_in_.set_microphone_direction = in_set_microphone_direction; + in->stream_in_.set_microphone_field_dimension = + in_set_microphone_field_dimension; + in->stream_in_.update_sink_metadata = in_update_sink_metadata; + + if (!in->bluetooth_input_->LoadAudioConfig(config)) { + LOG(ERROR) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << " failed to get audio config"; + return -EINVAL; + } + + in->sample_rate_ = config->sample_rate; + in->channel_mask_ = config->channel_mask; + in->format_ = config->format; + // frame is number of samples per channel + + size_t preferred_data_interval_us = kBluetoothDefaultInputBufferMs * 1000; + if (in->bluetooth_input_->GetPreferredDataIntervalUs( + &preferred_data_interval_us) && + preferred_data_interval_us != 0) { + in->preferred_data_interval_us = preferred_data_interval_us; + } else { + in->preferred_data_interval_us = kBluetoothDefaultInputBufferMs * 1000; + } + + in->frames_count_ = + frame_count(in->preferred_data_interval_us, in->sample_rate_); + in->frames_presented_ = 0; + + BluetoothStreamIn* in_ptr = in.release(); + *stream_in = &in_ptr->stream_in_; + LOG(INFO) << __func__ << ": state=" << in_ptr->bluetooth_input_->GetState() + << ", sample_rate=" << in_ptr->sample_rate_ + << ", channels=" << StringPrintf("%#x", in_ptr->channel_mask_) + << ", format=" << in_ptr->format_ << ", preferred_data_interval_us=" + << in_ptr->preferred_data_interval_us + << ", frames=" << in_ptr->frames_count_; + + return 0; +} + +void adev_close_input_stream(struct audio_hw_device* dev, + struct audio_stream_in* stream) { + auto* in = reinterpret_cast(stream); + + if (in->bluetooth_input_->GetState() != BluetoothStreamState::DISABLED) { + in->bluetooth_input_->Stop(); + } + + in->bluetooth_input_->TearDown(); + LOG(VERBOSE) << __func__ << ": state=" << in->bluetooth_input_->GetState() + << ", stopped"; + + delete in; +} diff --git a/bluetooth/audio/hw/stream_apis.h b/bluetooth/audio/hw/stream_apis.h new file mode 100644 index 0000000..36dd589 --- /dev/null +++ b/bluetooth/audio/hw/stream_apis.h @@ -0,0 +1,129 @@ +/* + * Copyright 2019 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. + */ + +#pragma once + +#include +#include + +#include + +#include "device_port_proxy.h" +#include "device_port_proxy_hidl.h" + +constexpr unsigned int kBluetoothDefaultSampleRate = 44100; +constexpr audio_format_t kBluetoothDefaultAudioFormatBitsPerSample = + AUDIO_FORMAT_PCM_16_BIT; + +constexpr unsigned int kBluetoothDefaultInputBufferMs = 20; +constexpr unsigned int kBluetoothDefaultInputStateTimeoutMs = 20; + +constexpr unsigned int kBluetoothDefaultOutputBufferMs = 10; +constexpr unsigned int kBluetoothSpatializerOutputBufferMs = 10; + +constexpr audio_channel_mask_t kBluetoothDefaultOutputChannelModeMask = + AUDIO_CHANNEL_OUT_STEREO; +constexpr audio_channel_mask_t kBluetoothDefaultInputChannelModeMask = + AUDIO_CHANNEL_IN_MONO; + +enum class BluetoothStreamState : uint8_t { + DISABLED = 0, // This stream is closing or set param "suspend=true" + STANDBY, + STARTING, + STARTED, + SUSPENDING, + UNKNOWN, +}; + +std::ostream& operator<<(std::ostream& os, const BluetoothStreamState& state); + +struct BluetoothStreamOut { + // Must be the first member so it can be cast from audio_stream + // or audio_stream_out pointer + audio_stream_out stream_out_{}; + std::unique_ptr<::android::bluetooth::audio::BluetoothAudioPort> + bluetooth_output_; + bool is_aidl; + int64_t last_write_time_us_; + // Audio PCM Configs + uint32_t sample_rate_; + audio_channel_mask_t channel_mask_; + audio_format_t format_; + size_t preferred_data_interval_us; + // frame is the number of samples per channel + // frames count per tick + size_t frames_count_; + // total frames written, reset on standby + uint64_t frames_rendered_; + // total frames written after opened, never reset + uint64_t frames_presented_; + mutable std::mutex mutex_; +}; + +struct BluetoothAudioDevice { + // Important: device must be first as an audio_hw_device* may be cast to + // BluetoothAudioDevice* when the type is implicitly known. + audio_hw_device audio_device_{}; + // protect against device->output and stream_out from being inconsistent + std::mutex mutex_; + std::list opened_stream_outs_ = + std::list(0); +}; + +struct BluetoothStreamIn { + // Must be the first member so it can be cast from audio_stream + // or audio_stream_in pointer + audio_stream_in stream_in_; + std::unique_ptr<::android::bluetooth::audio::BluetoothAudioPort> + bluetooth_input_; + bool is_aidl; + int64_t last_read_time_us_; + // Audio PCM Configs + uint32_t sample_rate_; + audio_channel_mask_t channel_mask_; + audio_format_t format_; + size_t preferred_data_interval_us; + // frame is the number of samples per channel + // frames count per tick + size_t frames_count_; + // total frames read after opened, never reset + uint64_t frames_presented_; + mutable std::mutex mutex_; +}; + +int adev_open_output_stream(struct audio_hw_device* dev, + audio_io_handle_t handle, audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config* config, + struct audio_stream_out** stream_out, + const char* address __unused); + +void adev_close_output_stream(struct audio_hw_device* dev, + struct audio_stream_out* stream); + +size_t adev_get_input_buffer_size(const struct audio_hw_device* dev, + const struct audio_config* config); + +int adev_open_input_stream(struct audio_hw_device* dev, + audio_io_handle_t handle, audio_devices_t devices, + struct audio_config* config, + struct audio_stream_in** stream_in, + audio_input_flags_t flags __unused, + const char* address __unused, + audio_source_t source __unused); + +void adev_close_input_stream(struct audio_hw_device* dev, + struct audio_stream_in* in); diff --git a/bluetooth/audio/hw/utils.cc b/bluetooth/audio/hw/utils.cc new file mode 100644 index 0000000..b3ac7a5 --- /dev/null +++ b/bluetooth/audio/hw/utils.cc @@ -0,0 +1,63 @@ +/* + * Copyright 2019 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 "BTAudioHalUtils" + +#include "utils.h" + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace bluetooth { +namespace audio { +namespace utils { + +std::unordered_map ParseAudioParams( + const std::string& params) { + std::vector segments = android::base::Split(params, ";"); + std::unordered_map params_map; + for (const auto& segment : segments) { + if (segment.length() == 0) { + continue; + } + std::vector kv = android::base::Split(segment, "="); + if (kv[0].empty()) { + LOG(WARNING) << __func__ << ": Invalid audio parameter " << segment; + continue; + } + params_map[kv[0]] = (kv.size() > 1 ? kv[1] : ""); + } + return params_map; +} + +std::string GetAudioParamString( + std::unordered_map& params_map) { + std::ostringstream sout; + for (const auto& ptr : params_map) { + sout << "key: '" << ptr.first << "' value: '" << ptr.second << "'\n"; + } + return sout.str(); +} + +} // namespace utils +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/hw/utils.h b/bluetooth/audio/hw/utils.h new file mode 100644 index 0000000..817a432 --- /dev/null +++ b/bluetooth/audio/hw/utils.h @@ -0,0 +1,48 @@ +/* + * Copyright 2019 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. + */ + +#pragma once + +#include +#include + +namespace android { +namespace bluetooth { +namespace audio { +namespace utils { + +// Creates a hash map based on the |params| string containing key and value +// pairs. Pairs are expected in the form "key=value" separated by the ';' +// character. Both ';' and '=' characters are invalid in keys or values. +// Examples: +// "key0" -> map: [key0]="" +// "key0=value0;key1=value1;" -> map: [key0]="value0" [key1]="value1" +// "key0=;key1=value1;" -> map: [key0]="" [key1]="value1" +// "=value0;key1=value1;" -> map: [key1]="value1" +std::unordered_map ParseAudioParams( + const std::string& params); + +// Dumps the contents of the hash_map to the log for debugging purposes. +// If |map| is not NULL, all entries of |map| will be dumped, otherwise +// nothing will be dumped. Note that this function does not take the ownership +// of the |map|. +std::string GetAudioParamString( + std::unordered_map& params_map); + +} // namespace utils +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/Android.bp b/bluetooth/audio/utils/Android.bp new file mode 100644 index 0000000..7b054e5 --- /dev/null +++ b/bluetooth/audio/utils/Android.bp @@ -0,0 +1,46 @@ +cc_library_shared { + name: "libbluetooth_audio_session_system", + defaults: ["hidl_defaults"], + srcs: [ + "session/BluetoothAudioSession.cpp", + "session/BluetoothAudioSession_2_1.cpp", + "session/BluetoothAudioSupportedCodecsDB.cpp", + "session/BluetoothAudioSupportedCodecsDB_2_1.cpp", + ], + export_include_dirs: ["session/"], + header_libs: ["libhardware_headers"], + shared_libs: [ + "android.hardware.audio.common@5.0", + "android.hardware.bluetooth.audio@2.0", + "android.hardware.bluetooth.audio@2.1", + "libbase", + "libcutils", + "libfmq", + "libhidlbase", + "liblog", + "libutils", + "libbluetooth_audio_session_aidl_system", + ], +} + +cc_library_shared { + name: "libbluetooth_audio_session_aidl_system", + srcs: [ + "aidl_session/BluetoothAudioCodecs.cpp", + "aidl_session/BluetoothAudioSession.cpp", + "aidl_session/HidlToAidlMiddleware.cpp", + ], + export_include_dirs: ["aidl_session/"], + header_libs: ["libhardware_headers"], + shared_libs: [ + "android.hardware.bluetooth.audio@2.0", + "android.hardware.bluetooth.audio@2.1", + "libbase", + "libcutils", + "libbinder_ndk", + "libfmq", + "liblog", + "android.hardware.bluetooth.audio-V2-ndk", + "libhidlbase", + ], +} diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioCodecs.cpp b/bluetooth/audio/utils/aidl_session/BluetoothAudioCodecs.cpp new file mode 100644 index 0000000..b858f50 --- /dev/null +++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioCodecs.cpp @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2022 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 "BTAudioCodecsAidl" + +#include "BluetoothAudioCodecs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +static const PcmCapabilities kDefaultSoftwarePcmCapabilities = { + .sampleRateHz = {16000, 24000, 32000, 44100, 48000, 88200, 96000}, + .channelMode = {ChannelMode::MONO, ChannelMode::STEREO}, + .bitsPerSample = {16, 24, 32}, + .dataIntervalUs = {}, +}; + +static const SbcCapabilities kDefaultOffloadSbcCapability = { + .sampleRateHz = {44100}, + .channelMode = {SbcChannelMode::MONO, SbcChannelMode::JOINT_STEREO}, + .blockLength = {4, 8, 12, 16}, + .numSubbands = {8}, + .allocMethod = {SbcAllocMethod::ALLOC_MD_L}, + .bitsPerSample = {16}, + .minBitpool = 2, + .maxBitpool = 53}; + +static const AacCapabilities kDefaultOffloadAacCapability = { + .objectType = {AacObjectType::MPEG2_LC}, + .sampleRateHz = {44100}, + .channelMode = {ChannelMode::STEREO}, + .variableBitRateSupported = true, + .bitsPerSample = {16}}; + +static const LdacCapabilities kDefaultOffloadLdacCapability = { + .sampleRateHz = {44100, 48000, 88200, 96000}, + .channelMode = {LdacChannelMode::DUAL, LdacChannelMode::STEREO}, + .qualityIndex = {LdacQualityIndex::HIGH}, + .bitsPerSample = {16, 24, 32}}; + +static const AptxCapabilities kDefaultOffloadAptxCapability = { + .sampleRateHz = {44100, 48000}, + .channelMode = {ChannelMode::STEREO}, + .bitsPerSample = {16}, +}; + +static const AptxCapabilities kDefaultOffloadAptxHdCapability = { + .sampleRateHz = {44100, 48000}, + .channelMode = {ChannelMode::STEREO}, + .bitsPerSample = {24}, +}; + +static const OpusCapabilities kDefaultOffloadOpusCapability = { + .samplingFrequencyHz = {48000}, + .frameDurationUs = {10000, 20000}, + .channelMode = {ChannelMode::MONO, ChannelMode::STEREO}, +}; + +const std::vector kDefaultOffloadA2dpCodecCapabilities = { + {.codecType = CodecType::SBC, .capabilities = {}}, + {.codecType = CodecType::AAC, .capabilities = {}}, + {.codecType = CodecType::LDAC, .capabilities = {}}, + {.codecType = CodecType::APTX, .capabilities = {}}, + {.codecType = CodecType::APTX_HD, .capabilities = {}}, + {.codecType = CodecType::OPUS, .capabilities = {}}}; + +std::vector kDefaultOffloadLeAudioCapabilities; + +static const UnicastCapability kInvalidUnicastCapability = { + .codecType = CodecType::UNKNOWN}; + +static const BroadcastCapability kInvalidBroadcastCapability = { + .codecType = CodecType::UNKNOWN}; + +// Default Supported Codecs +// LC3 16_1: sample rate: 16 kHz, frame duration: 7.5 ms, octets per frame: 30 +static const Lc3Capabilities kLc3Capability_16_1 = { + .samplingFrequencyHz = {16000}, + .frameDurationUs = {7500}, + .octetsPerFrame = {30}}; + +// Default Supported Codecs +// LC3 16_2: sample rate: 16 kHz, frame duration: 10 ms, octets per frame: 40 +static const Lc3Capabilities kLc3Capability_16_2 = { + .samplingFrequencyHz = {16000}, + .frameDurationUs = {10000}, + .octetsPerFrame = {40}}; + +// Default Supported Codecs +// LC3 24_2: sample rate: 24 kHz, frame duration: 10 ms, octets per frame: 60 +static const Lc3Capabilities kLc3Capability_24_2 = { + .samplingFrequencyHz = {24000}, + .frameDurationUs = {10000}, + .octetsPerFrame = {60}}; + +// Default Supported Codecs +// LC3 32_2: sample rate: 32 kHz, frame duration: 10 ms, octets per frame: 80 +static const Lc3Capabilities kLc3Capability_32_2 = { + .samplingFrequencyHz = {32000}, + .frameDurationUs = {10000}, + .octetsPerFrame = {80}}; + +// Default Supported Codecs +// LC3 48_4: sample rate: 48 kHz, frame duration: 10 ms, octets per frame: 120 +static const Lc3Capabilities kLc3Capability_48_4 = { + .samplingFrequencyHz = {48000}, + .frameDurationUs = {10000}, + .octetsPerFrame = {120}}; + +static const std::vector supportedLc3CapabilityList = { + kLc3Capability_48_4, kLc3Capability_32_2, kLc3Capability_24_2, + kLc3Capability_16_2, kLc3Capability_16_1}; + +static AudioLocation stereoAudio = static_cast( + static_cast(AudioLocation::FRONT_LEFT) | + static_cast(AudioLocation::FRONT_RIGHT)); +static AudioLocation monoAudio = AudioLocation::UNKNOWN; + +// Stores the supported setting of audio location, connected device, and the +// channel count for each device +std::vector> + supportedDeviceSetting = { + // Stereo, two connected device, one for L one for R + std::make_tuple(stereoAudio, 2, 1), + // Stereo, one connected device for both L and R + std::make_tuple(stereoAudio, 1, 2), + // Mono + std::make_tuple(monoAudio, 1, 1)}; + +template +bool BluetoothAudioCodecs::ContainedInVector( + const std::vector& vector, const typename identity::type& target) { + return std::find(vector.begin(), vector.end(), target) != vector.end(); +} + +bool BluetoothAudioCodecs::IsOffloadSbcConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getTag() != CodecConfiguration::CodecSpecific::sbcConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << codec_specific.toString(); + return false; + } + const SbcConfiguration sbc_data = + codec_specific.get(); + + if (ContainedInVector(kDefaultOffloadSbcCapability.sampleRateHz, + sbc_data.sampleRateHz) && + ContainedInVector(kDefaultOffloadSbcCapability.blockLength, + sbc_data.blockLength) && + ContainedInVector(kDefaultOffloadSbcCapability.numSubbands, + sbc_data.numSubbands) && + ContainedInVector(kDefaultOffloadSbcCapability.bitsPerSample, + sbc_data.bitsPerSample) && + ContainedInVector(kDefaultOffloadSbcCapability.channelMode, + sbc_data.channelMode) && + ContainedInVector(kDefaultOffloadSbcCapability.allocMethod, + sbc_data.allocMethod) && + sbc_data.minBitpool <= sbc_data.maxBitpool && + kDefaultOffloadSbcCapability.minBitpool <= sbc_data.minBitpool && + kDefaultOffloadSbcCapability.maxBitpool >= sbc_data.maxBitpool) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << codec_specific.toString(); + return false; +} + +bool BluetoothAudioCodecs::IsOffloadAacConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getTag() != CodecConfiguration::CodecSpecific::aacConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << codec_specific.toString(); + return false; + } + const AacConfiguration aac_data = + codec_specific.get(); + + if (ContainedInVector(kDefaultOffloadAacCapability.sampleRateHz, + aac_data.sampleRateHz) && + ContainedInVector(kDefaultOffloadAacCapability.bitsPerSample, + aac_data.bitsPerSample) && + ContainedInVector(kDefaultOffloadAacCapability.channelMode, + aac_data.channelMode) && + ContainedInVector(kDefaultOffloadAacCapability.objectType, + aac_data.objectType) && + (!aac_data.variableBitRateEnabled || + kDefaultOffloadAacCapability.variableBitRateSupported)) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << codec_specific.toString(); + return false; +} + +bool BluetoothAudioCodecs::IsOffloadLdacConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getTag() != + CodecConfiguration::CodecSpecific::ldacConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << codec_specific.toString(); + return false; + } + const LdacConfiguration ldac_data = + codec_specific.get(); + + if (ContainedInVector(kDefaultOffloadLdacCapability.sampleRateHz, + ldac_data.sampleRateHz) && + ContainedInVector(kDefaultOffloadLdacCapability.bitsPerSample, + ldac_data.bitsPerSample) && + ContainedInVector(kDefaultOffloadLdacCapability.channelMode, + ldac_data.channelMode) && + ContainedInVector(kDefaultOffloadLdacCapability.qualityIndex, + ldac_data.qualityIndex)) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << codec_specific.toString(); + return false; +} + +bool BluetoothAudioCodecs::IsOffloadAptxConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getTag() != + CodecConfiguration::CodecSpecific::aptxConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << codec_specific.toString(); + return false; + } + const AptxConfiguration aptx_data = + codec_specific.get(); + + if (ContainedInVector(kDefaultOffloadAptxCapability.sampleRateHz, + aptx_data.sampleRateHz) && + ContainedInVector(kDefaultOffloadAptxCapability.bitsPerSample, + aptx_data.bitsPerSample) && + ContainedInVector(kDefaultOffloadAptxCapability.channelMode, + aptx_data.channelMode)) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << codec_specific.toString(); + return false; +} + +bool BluetoothAudioCodecs::IsOffloadAptxHdConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getTag() != + CodecConfiguration::CodecSpecific::aptxConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << codec_specific.toString(); + return false; + } + const AptxConfiguration aptx_data = + codec_specific.get(); + + if (ContainedInVector(kDefaultOffloadAptxHdCapability.sampleRateHz, + aptx_data.sampleRateHz) && + ContainedInVector(kDefaultOffloadAptxHdCapability.bitsPerSample, + aptx_data.bitsPerSample) && + ContainedInVector(kDefaultOffloadAptxHdCapability.channelMode, + aptx_data.channelMode)) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << codec_specific.toString(); + return false; +} + +bool BluetoothAudioCodecs::IsOffloadOpusConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getTag() != + CodecConfiguration::CodecSpecific::opusConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << codec_specific.toString(); + return false; + } + std::optional opus_data = + codec_specific.get(); + + if (opus_data.has_value() && + ContainedInVector(kDefaultOffloadOpusCapability.samplingFrequencyHz, + opus_data->samplingFrequencyHz) && + ContainedInVector(kDefaultOffloadOpusCapability.frameDurationUs, + opus_data->frameDurationUs) && + ContainedInVector(kDefaultOffloadOpusCapability.channelMode, + opus_data->channelMode)) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << codec_specific.toString(); + return false; +} + +bool BluetoothAudioCodecs::IsOffloadLeAudioConfigurationValid( + const SessionType& session_type, const LeAudioConfiguration&) { + if (session_type != + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH && + session_type != + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH && + session_type != + SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH) { + return false; + } + return true; +} + +std::vector +BluetoothAudioCodecs::GetSoftwarePcmCapabilities() { + return {kDefaultSoftwarePcmCapabilities}; +} + +std::vector +BluetoothAudioCodecs::GetA2dpOffloadCodecCapabilities( + const SessionType& session_type) { + if (session_type != SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH && + session_type != SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH) { + return {}; + } + std::vector offload_a2dp_codec_capabilities = + kDefaultOffloadA2dpCodecCapabilities; + for (auto& codec_capability : offload_a2dp_codec_capabilities) { + switch (codec_capability.codecType) { + case CodecType::SBC: + codec_capability.capabilities + .set( + kDefaultOffloadSbcCapability); + break; + case CodecType::AAC: + codec_capability.capabilities + .set( + kDefaultOffloadAacCapability); + break; + case CodecType::LDAC: + codec_capability.capabilities + .set( + kDefaultOffloadLdacCapability); + break; + case CodecType::APTX: + codec_capability.capabilities + .set( + kDefaultOffloadAptxCapability); + break; + case CodecType::APTX_HD: + codec_capability.capabilities + .set( + kDefaultOffloadAptxHdCapability); + break; + case CodecType::OPUS: + codec_capability.capabilities + .set( + kDefaultOffloadOpusCapability); + break; + case CodecType::UNKNOWN: + case CodecType::VENDOR: + case CodecType::LC3: + case CodecType::APTX_ADAPTIVE: + break; + } + } + return offload_a2dp_codec_capabilities; +} + +bool BluetoothAudioCodecs::IsSoftwarePcmConfigurationValid( + const PcmConfiguration& pcm_config) { + if (ContainedInVector(kDefaultSoftwarePcmCapabilities.sampleRateHz, + pcm_config.sampleRateHz) && + ContainedInVector(kDefaultSoftwarePcmCapabilities.bitsPerSample, + pcm_config.bitsPerSample) && + ContainedInVector(kDefaultSoftwarePcmCapabilities.channelMode, + pcm_config.channelMode) + // data interval is not checked for now + // && pcm_config.dataIntervalUs != 0 + ) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << pcm_config.toString(); + return false; +} + +bool BluetoothAudioCodecs::IsOffloadCodecConfigurationValid( + const SessionType& session_type, const CodecConfiguration& codec_config) { + if (session_type != SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH && + session_type != SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH) { + LOG(ERROR) << __func__ + << ": Invalid SessionType=" << toString(session_type); + return false; + } + const CodecConfiguration::CodecSpecific& codec_specific = codec_config.config; + switch (codec_config.codecType) { + case CodecType::SBC: + if (IsOffloadSbcConfigurationValid(codec_specific)) { + return true; + } + break; + case CodecType::AAC: + if (IsOffloadAacConfigurationValid(codec_specific)) { + return true; + } + break; + case CodecType::LDAC: + if (IsOffloadLdacConfigurationValid(codec_specific)) { + return true; + } + break; + case CodecType::APTX: + if (IsOffloadAptxConfigurationValid(codec_specific)) { + return true; + } + break; + case CodecType::APTX_HD: + if (IsOffloadAptxHdConfigurationValid(codec_specific)) { + return true; + } + break; + case CodecType::OPUS: + if (IsOffloadOpusConfigurationValid(codec_specific)) { + return true; + } + break; + case CodecType::APTX_ADAPTIVE: + case CodecType::LC3: + case CodecType::UNKNOWN: + case CodecType::VENDOR: + break; + } + return false; +} + +UnicastCapability composeUnicastLc3Capability( + AudioLocation audioLocation, uint8_t deviceCnt, uint8_t channelCount, + const Lc3Capabilities& capability) { + return { + .codecType = CodecType::LC3, + .supportedChannel = audioLocation, + .deviceCount = deviceCnt, + .channelCountPerDevice = channelCount, + .leAudioCodecCapabilities = + UnicastCapability::LeAudioCodecCapabilities(capability), + }; +} + +std::vector +BluetoothAudioCodecs::GetLeAudioOffloadCodecCapabilities( + const SessionType& session_type) { + if (session_type != + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH && + session_type != + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH && + session_type != + SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH) { + return std::vector(0); + } + + if (kDefaultOffloadLeAudioCapabilities.empty()) { + for (auto [audioLocation, deviceCnt, channelCount] : + supportedDeviceSetting) { + for (auto capability : supportedLc3CapabilityList) { + UnicastCapability lc3Capability = composeUnicastLc3Capability( + audioLocation, deviceCnt, channelCount, capability); + UnicastCapability lc3MonoDecodeCapability = + composeUnicastLc3Capability(monoAudio, 1, 1, capability); + + // Adds the capability for encode only + kDefaultOffloadLeAudioCapabilities.push_back( + {.unicastEncodeCapability = lc3Capability, + .unicastDecodeCapability = kInvalidUnicastCapability, + .broadcastCapability = kInvalidBroadcastCapability}); + + // Adds the capability for decode only + kDefaultOffloadLeAudioCapabilities.push_back( + {.unicastEncodeCapability = kInvalidUnicastCapability, + .unicastDecodeCapability = lc3Capability, + .broadcastCapability = kInvalidBroadcastCapability}); + + // Adds the capability for the case that encode and decode exist at the + // same time + kDefaultOffloadLeAudioCapabilities.push_back( + {.unicastEncodeCapability = lc3Capability, + .unicastDecodeCapability = lc3MonoDecodeCapability, + .broadcastCapability = kInvalidBroadcastCapability}); + } + } + } + + return kDefaultOffloadLeAudioCapabilities; +} + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioCodecs.h b/bluetooth/audio/utils/aidl_session/BluetoothAudioCodecs.h new file mode 100644 index 0000000..ed0598b --- /dev/null +++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioCodecs.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +class BluetoothAudioCodecs { + public: + static std::vector GetSoftwarePcmCapabilities(); + static std::vector GetA2dpOffloadCodecCapabilities( + const SessionType& session_type); + + static bool IsSoftwarePcmConfigurationValid( + const PcmConfiguration& pcm_config); + static bool IsOffloadCodecConfigurationValid( + const SessionType& session_type, const CodecConfiguration& codec_config); + + static bool IsOffloadLeAudioConfigurationValid( + const SessionType& session_type, const LeAudioConfiguration&); + + static std::vector + GetLeAudioOffloadCodecCapabilities(const SessionType& session_type); + + private: + template + struct identity { + typedef T type; + }; + template + static bool ContainedInVector(const std::vector& vector, + const typename identity::type& target); + template + static bool ContainedInBitmask(const T& bitmask, const T& target); + static bool IsSingleBit(uint32_t bitmasks, uint32_t bitfield); + static bool IsOffloadSbcConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); + static bool IsOffloadAacConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); + static bool IsOffloadLdacConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); + static bool IsOffloadAptxConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); + static bool IsOffloadAptxHdConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); + static bool IsOffloadOpusConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp new file mode 100644 index 0000000..3214bf2 --- /dev/null +++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp @@ -0,0 +1,618 @@ +/* + * Copyright (C) 2022 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. + */ + +#include +#define LOG_TAG "BTAudioSessionAidl" + +#include +#include +#include + +#include "BluetoothAudioSession.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +static constexpr int kFmqSendTimeoutMs = 1000; // 1000 ms timeout for sending +static constexpr int kFmqReceiveTimeoutMs = + 1000; // 1000 ms timeout for receiving +static constexpr int kWritePollMs = 1; // polled non-blocking interval +static constexpr int kReadPollMs = 1; // polled non-blocking interval + +BluetoothAudioSession::BluetoothAudioSession(const SessionType& session_type) + : session_type_(session_type), stack_iface_(nullptr), data_mq_(nullptr) {} + +/*** + * + * Callback methods + * + ***/ + +void BluetoothAudioSession::OnSessionStarted( + const std::shared_ptr stack_iface, + const DataMQDesc* mq_desc, const AudioConfiguration& audio_config, + const std::vector& latency_modes) { + std::lock_guard guard(mutex_); + if (stack_iface == nullptr) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) + << ", IBluetoothAudioPort Invalid"; + } else if (!UpdateAudioConfig(audio_config)) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) + << ", AudioConfiguration=" << audio_config.toString() + << " Invalid"; + } else if (!UpdateDataPath(mq_desc)) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) + << " MqDescriptor Invalid"; + audio_config_ = nullptr; + } else { + stack_iface_ = stack_iface; + latency_modes_ = latency_modes; + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << ", AudioConfiguration=" << audio_config.toString(); + ReportSessionStatus(); + } +} + +void BluetoothAudioSession::OnSessionEnded() { + std::lock_guard guard(mutex_); + bool toggled = IsSessionReady(); + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_); + audio_config_ = nullptr; + stack_iface_ = nullptr; + UpdateDataPath(nullptr); + if (toggled) { + ReportSessionStatus(); + } +} + +/*** + * + * Util methods + * + ***/ + +const AudioConfiguration BluetoothAudioSession::GetAudioConfig() { + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + switch (session_type_) { + case SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH: + case SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH: + return AudioConfiguration(CodecConfiguration{}); + case SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH: + case SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH: + return AudioConfiguration(LeAudioConfiguration{}); + case SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH: + return AudioConfiguration(LeAudioBroadcastConfiguration{}); + default: + return AudioConfiguration(PcmConfiguration{}); + } + } + return *audio_config_; +} + +void BluetoothAudioSession::ReportAudioConfigChanged( + const AudioConfiguration& audio_config) { + if (session_type_ != + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH && + session_type_ != + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH) { + return; + } + std::lock_guard guard(mutex_); + audio_config_ = std::make_unique(audio_config); + if (observers_.empty()) { + LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO port state observer"; + return; + } + for (auto& observer : observers_) { + uint16_t cookie = observer.first; + std::shared_ptr cb = observer.second; + LOG(INFO) << __func__ << " for SessionType=" << toString(session_type_) + << ", bluetooth_audio=0x" + << ::android::base::StringPrintf("%04x", cookie); + if (cb->audio_configuration_changed_cb_ != nullptr) { + cb->audio_configuration_changed_cb_(cookie); + } + } +} + +bool BluetoothAudioSession::IsSessionReady() { + std::lock_guard guard(mutex_); + + bool is_mq_valid = + (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type_ == + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type_ == + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH || + session_type_ == + SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH || + (data_mq_ != nullptr && data_mq_->isValid())); + return stack_iface_ != nullptr && is_mq_valid && audio_config_ != nullptr; +} + +/*** + * + * Status callback methods + * + ***/ + +uint16_t BluetoothAudioSession::RegisterStatusCback( + const PortStatusCallbacks& callbacks) { + std::lock_guard guard(mutex_); + uint16_t cookie = ObserversCookieGetInitValue(session_type_); + uint16_t cookie_upper_bound = ObserversCookieGetUpperBound(session_type_); + + while (cookie < cookie_upper_bound) { + if (observers_.find(cookie) == observers_.end()) { + break; + } + ++cookie; + } + if (cookie >= cookie_upper_bound) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) + << " has " << observers_.size() + << " observers already (No Resource)"; + return kObserversCookieUndefined; + } + std::shared_ptr cb = + std::make_shared(); + *cb = callbacks; + observers_[cookie] = cb; + return cookie; +} + +void BluetoothAudioSession::UnregisterStatusCback(uint16_t cookie) { + std::lock_guard guard(mutex_); + if (observers_.erase(cookie) != 1) { + LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) + << " no such provider=0x" + << ::android::base::StringPrintf("%04x", cookie); + } +} + +/*** + * + * Stream methods + * + ***/ + +bool BluetoothAudioSession::StartStream(bool is_low_latency) { + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return false; + } + auto hal_retval = stack_iface_->startStream(is_low_latency); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + return false; + } + return true; +} + +bool BluetoothAudioSession::SuspendStream() { + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return false; + } + auto hal_retval = stack_iface_->suspendStream(); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + return false; + } + return true; +} + +void BluetoothAudioSession::StopStream() { + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + return; + } + auto hal_retval = stack_iface_->stopStream(); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + } +} + +/*** + * + * Private methods + * + ***/ + +bool BluetoothAudioSession::UpdateDataPath(const DataMQDesc* mq_desc) { + if (mq_desc == nullptr) { + // usecase of reset by nullptr + data_mq_ = nullptr; + return true; + } + std::unique_ptr temp_mq; + temp_mq.reset(new DataMQ(*mq_desc)); + if (!temp_mq || !temp_mq->isValid()) { + data_mq_ = nullptr; + return false; + } + data_mq_ = std::move(temp_mq); + return true; +} + +bool BluetoothAudioSession::UpdateAudioConfig( + const AudioConfiguration& audio_config) { + bool is_software_session = + (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type_ == SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH || + session_type_ == SessionType::LE_AUDIO_SOFTWARE_DECODING_DATAPATH || + session_type_ == SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH || + session_type_ == + SessionType::LE_AUDIO_BROADCAST_SOFTWARE_ENCODING_DATAPATH || + session_type_ == SessionType::A2DP_SOFTWARE_DECODING_DATAPATH); + bool is_offload_a2dp_session = + (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH); + bool is_offload_le_audio_session = + (session_type_ == + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type_ == + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH); + auto audio_config_tag = audio_config.getTag(); + bool is_software_audio_config = + (is_software_session && + audio_config_tag == AudioConfiguration::pcmConfig); + bool is_a2dp_offload_audio_config = + (is_offload_a2dp_session && + audio_config_tag == AudioConfiguration::a2dpConfig); + bool is_le_audio_offload_audio_config = + (is_offload_le_audio_session && + audio_config_tag == AudioConfiguration::leAudioConfig); + if (!is_software_audio_config && !is_a2dp_offload_audio_config && + !is_le_audio_offload_audio_config) { + return false; + } + audio_config_ = std::make_unique(audio_config); + return true; +} + +void BluetoothAudioSession::ReportSessionStatus() { + // This is locked already by OnSessionStarted / OnSessionEnded + if (observers_.empty()) { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO port state observer"; + return; + } + for (auto& observer : observers_) { + uint16_t cookie = observer.first; + std::shared_ptr callback = observer.second; + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << " notify to bluetooth_audio=0x" + << ::android::base::StringPrintf("%04x", cookie); + callback->session_changed_cb_(cookie); + } +} + +/*** + * + * PCM methods + * + ***/ + +size_t BluetoothAudioSession::OutWritePcmData(const void* buffer, + size_t bytes) { + if (buffer == nullptr || bytes <= 0) { + return 0; + } + size_t total_written = 0; + int timeout_ms = kFmqSendTimeoutMs; + do { + std::unique_lock lock(mutex_); + if (!IsSessionReady()) { + break; + } + size_t num_bytes_to_write = data_mq_->availableToWrite(); + if (num_bytes_to_write) { + if (num_bytes_to_write > (bytes - total_written)) { + num_bytes_to_write = bytes - total_written; + } + + if (!data_mq_->write( + static_cast(buffer) + total_written, + num_bytes_to_write)) { + LOG(ERROR) << "FMQ datapath writing " << total_written << "/" << bytes + << " failed"; + return total_written; + } + total_written += num_bytes_to_write; + } else if (timeout_ms >= kWritePollMs) { + lock.unlock(); + usleep(kWritePollMs * 1000); + timeout_ms -= kWritePollMs; + } else { + LOG(DEBUG) << "Data " << total_written << "/" << bytes << " overflow " + << (kFmqSendTimeoutMs - timeout_ms) << " ms"; + return total_written; + } + } while (total_written < bytes); + return total_written; +} + +size_t BluetoothAudioSession::InReadPcmData(void* buffer, size_t bytes) { + if (buffer == nullptr || bytes <= 0) { + return 0; + } + size_t total_read = 0; + int timeout_ms = kFmqReceiveTimeoutMs; + do { + std::unique_lock lock(mutex_); + if (!IsSessionReady()) { + break; + } + size_t num_bytes_to_read = data_mq_->availableToRead(); + if (num_bytes_to_read) { + if (num_bytes_to_read > (bytes - total_read)) { + num_bytes_to_read = bytes - total_read; + } + if (!data_mq_->read(static_cast(buffer) + total_read, + num_bytes_to_read)) { + LOG(ERROR) << "FMQ datapath reading " << total_read << "/" << bytes + << " failed"; + return total_read; + } + total_read += num_bytes_to_read; + } else if (timeout_ms >= kReadPollMs) { + lock.unlock(); + usleep(kReadPollMs * 1000); + timeout_ms -= kReadPollMs; + continue; + } else { + LOG(DEBUG) << "Data " << total_read << "/" << bytes << " overflow " + << (kFmqReceiveTimeoutMs - timeout_ms) << " ms"; + return total_read; + } + } while (total_read < bytes); + return total_read; +} + +/*** + * + * Other methods + * + ***/ + +void BluetoothAudioSession::ReportControlStatus(bool start_resp, + BluetoothAudioStatus status) { + std::lock_guard guard(mutex_); + if (observers_.empty()) { + LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO port state observer"; + return; + } + for (auto& observer : observers_) { + uint16_t cookie = observer.first; + std::shared_ptr callback = observer.second; + LOG(INFO) << __func__ << " - status=" << toString(status) + << " for SessionType=" << toString(session_type_) + << ", bluetooth_audio=0x" + << ::android::base::StringPrintf("%04x", cookie) + << (start_resp ? " started" : " suspended"); + callback->control_result_cb_(cookie, start_resp, status); + } +} + +void BluetoothAudioSession::ReportLowLatencyModeAllowedChanged(bool allowed) { + std::lock_guard guard(mutex_); + low_latency_allowed_ = allowed; + if (observers_.empty()) { + LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO port state observer"; + return; + } + for (auto& observer : observers_) { + uint16_t cookie = observer.first; + std::shared_ptr callback = observer.second; + LOG(INFO) << __func__ + << " - allowed=" << (allowed ? " allowed" : " disallowed"); + if (callback->low_latency_mode_allowed_cb_ != nullptr) { + callback->low_latency_mode_allowed_cb_(cookie, allowed); + } + } +} + +bool BluetoothAudioSession::GetPresentationPosition( + PresentationPosition& presentation_position) { + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return false; + } + bool retval = false; + + if (!stack_iface_->getPresentationPosition(&presentation_position).isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + return false; + } + return retval; +} + +void BluetoothAudioSession::UpdateSourceMetadata( + const struct source_metadata& source_metadata) { + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return; + } + + ssize_t track_count = source_metadata.track_count; + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) << "," + << track_count << " track(s)"; + if (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type_ == SessionType::A2DP_SOFTWARE_DECODING_DATAPATH || + session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH) { + return; + } + + SourceMetadata hal_source_metadata; + hal_source_metadata.tracks.resize(track_count); + for (int i = 0; i < track_count; i++) { + hal_source_metadata.tracks[i].usage = + static_cast( + source_metadata.tracks[i].usage); + hal_source_metadata.tracks[i].contentType = + static_cast( + source_metadata.tracks[i].content_type); + hal_source_metadata.tracks[i].gain = source_metadata.tracks[i].gain; + LOG(VERBOSE) << __func__ << " - SessionType=" << toString(session_type_) + << ", usage=" << toString(hal_source_metadata.tracks[i].usage) + << ", content=" + << toString(hal_source_metadata.tracks[i].contentType) + << ", gain=" << hal_source_metadata.tracks[i].gain; + } + + auto hal_retval = stack_iface_->updateSourceMetadata(hal_source_metadata); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + } +} + +void BluetoothAudioSession::UpdateSinkMetadata( + const struct sink_metadata& sink_metadata) { + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return; + } + + ssize_t track_count = sink_metadata.track_count; + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) << "," + << track_count << " track(s)"; + if (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type_ == SessionType::A2DP_SOFTWARE_DECODING_DATAPATH || + session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH) { + return; + } + + SinkMetadata hal_sink_metadata; + hal_sink_metadata.tracks.resize(track_count); + for (int i = 0; i < track_count; i++) { + hal_sink_metadata.tracks[i].source = + static_cast( + sink_metadata.tracks[i].source); + hal_sink_metadata.tracks[i].gain = sink_metadata.tracks[i].gain; + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << ", source=" << sink_metadata.tracks[i].source + << ", dest_device=" << sink_metadata.tracks[i].dest_device + << ", gain=" << sink_metadata.tracks[i].gain + << ", dest_device_address=" + << sink_metadata.tracks[i].dest_device_address; + } + + auto hal_retval = stack_iface_->updateSinkMetadata(hal_sink_metadata); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + } +} + +std::vector BluetoothAudioSession::GetSupportedLatencyModes() { + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return std::vector(); + } + if (low_latency_allowed_) return latency_modes_; + std::vector modes; + for (LatencyMode mode : latency_modes_) { + if (mode == LatencyMode::LOW_LATENCY) + // ignore those low latency mode if Bluetooth stack doesn't allow + continue; + modes.push_back(mode); + } + return modes; +} + +void BluetoothAudioSession::SetLatencyMode(const LatencyMode& latency_mode) { + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return; + } + + auto hal_retval = stack_iface_->setLatencyMode(latency_mode); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + } +} + +bool BluetoothAudioSession::IsAidlAvailable() { + if (is_aidl_checked) return is_aidl_available; + is_aidl_available = + (AServiceManager_checkService( + kDefaultAudioProviderFactoryInterface.c_str()) != nullptr); + is_aidl_checked = true; + return is_aidl_available; +} + +/*** + * + * BluetoothAudioSessionInstance + * + ***/ +std::mutex BluetoothAudioSessionInstance::mutex_; +std::unordered_map> + BluetoothAudioSessionInstance::sessions_map_; + +std::shared_ptr +BluetoothAudioSessionInstance::GetSessionInstance( + const SessionType& session_type) { + std::lock_guard guard(mutex_); + + if (!sessions_map_.empty()) { + auto entry = sessions_map_.find(session_type); + if (entry != sessions_map_.end()) { + return entry->second; + } + } + std::shared_ptr session_ptr = + std::make_shared(session_type); + sessions_map_[session_type] = session_ptr; + return session_ptr; +} + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h new file mode 100644 index 0000000..bdfaf36 --- /dev/null +++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +using ::aidl::android::hardware::common::fmq::MQDescriptor; +using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite; +using ::android::AidlMessageQueue; + +using ::aidl::android::hardware::audio::common::SinkMetadata; +using ::aidl::android::hardware::audio::common::SourceMetadata; + +using MQDataType = int8_t; +using MQDataMode = SynchronizedReadWrite; +using DataMQ = AidlMessageQueue; +using DataMQDesc = + ::aidl::android::hardware::common::fmq::MQDescriptor; + +static constexpr uint16_t kObserversCookieSize = 0x0010; // 0x0000 ~ 0x000f +static constexpr uint16_t kObserversCookieUndefined = + (static_cast(SessionType::UNKNOWN) << 8 & 0xff00); +inline SessionType ObserversCookieGetSessionType(uint16_t cookie) { + return static_cast(cookie >> 8 & 0x00ff); +} +inline uint16_t ObserversCookieGetInitValue(SessionType session_type) { + return (static_cast(session_type) << 8 & 0xff00); +} +inline uint16_t ObserversCookieGetUpperBound(SessionType session_type) { + return (static_cast(session_type) << 8 & 0xff00) + + kObserversCookieSize; +} + +/*** + * This presents the callbacks of started / suspended and session changed, + * and the bluetooth_audio module uses to receive the status notification + ***/ +struct PortStatusCallbacks { + /*** + * control_result_cb_ - when the Bluetooth stack reports results of + * streamStarted or streamSuspended, the BluetoothAudioProvider will invoke + * this callback to report to the bluetooth_audio module. + * @param: cookie - indicates which bluetooth_audio output should handle + * @param: start_resp - this report is for startStream or not + * @param: status - the result of startStream + ***/ + std::function + control_result_cb_; + /*** + * session_changed_cb_ - when the Bluetooth stack start / end session, the + * BluetoothAudioProvider will invoke this callback to notify to the + * bluetooth_audio module. + * @param: cookie - indicates which bluetooth_audio output should handle + ***/ + std::function session_changed_cb_; + /*** + * audio_configuration_changed_cb_ - when the Bluetooth stack change the audio + * configuration, the BluetoothAudioProvider will invoke this callback to + * notify to the bluetooth_audio module. + * @param: cookie - indicates which bluetooth_audio output should handle + ***/ + std::function audio_configuration_changed_cb_; + /*** + * low_latency_mode_allowed_cb_ - when the Bluetooth stack low latency mode + * allowed or disallowed, the BluetoothAudioProvider will invoke + * this callback to report to the bluetooth_audio module. + * @param: cookie - indicates which bluetooth_audio output should handle + * @param: allowed - indicates if low latency mode is allowed + ***/ + std::function + low_latency_mode_allowed_cb_; +}; + +class BluetoothAudioSession { + public: + BluetoothAudioSession(const SessionType& session_type); + + /*** + * The function helps to check if this session is ready or not + * @return: true if the Bluetooth stack has started the specified session + ***/ + bool IsSessionReady(); + + /*** + * The report function is used to report that the Bluetooth stack has started + * this session without any failure, and will invoke session_changed_cb_ to + * notify those registered bluetooth_audio outputs + ***/ + void OnSessionStarted(const std::shared_ptr stack_iface, + const DataMQDesc* mq_desc, + const AudioConfiguration& audio_config, + const std::vector& latency_modes); + + /*** + * The report function is used to report that the Bluetooth stack has ended + * the session, and will invoke session_changed_cb_ to notify registered + * bluetooth_audio outputs + ***/ + void OnSessionEnded(); + + /*** + * The report function is used to report that the Bluetooth stack has notified + * the result of startStream or suspendStream, and will invoke + * control_result_cb_ to notify registered bluetooth_audio outputs + ***/ + void ReportControlStatus(bool start_resp, BluetoothAudioStatus status); + + /*** + * The control function helps the bluetooth_audio module to register + * PortStatusCallbacks + * @return: cookie - the assigned number to this bluetooth_audio output + ***/ + uint16_t RegisterStatusCback(const PortStatusCallbacks& cbacks); + + /*** + * The control function helps the bluetooth_audio module to unregister + * PortStatusCallbacks + * @param: cookie - indicates which bluetooth_audio output is + ***/ + void UnregisterStatusCback(uint16_t cookie); + + /*** + * The control function is for the bluetooth_audio module to get the current + * AudioConfiguration + ***/ + const AudioConfiguration GetAudioConfig(); + + /*** + * The report function is used to report that the Bluetooth stack has notified + * the audio configuration changed, and will invoke + * audio_configuration_changed_cb_ to notify registered bluetooth_audio + * outputs + ***/ + void ReportAudioConfigChanged(const AudioConfiguration& audio_config); + + /*** + * The report function is used to report that the Bluetooth stack has notified + * the low latency mode allowed changed, and will invoke + * low_latency_mode_allowed_changed_cb to notify registered bluetooth_audio + * outputs + ***/ + void ReportLowLatencyModeAllowedChanged(bool allowed); + /*** + * Those control functions are for the bluetooth_audio module to start, + * suspend, stop stream, to check position, and to update metadata. + ***/ + bool StartStream(bool low_latency); + bool SuspendStream(); + void StopStream(); + bool GetPresentationPosition(PresentationPosition& presentation_position); + void UpdateSourceMetadata(const struct source_metadata& source_metadata); + void UpdateSinkMetadata(const struct sink_metadata& sink_metadata); + + std::vector GetSupportedLatencyModes(); + void SetLatencyMode(const LatencyMode& latency_mode); + + // The control function writes stream to FMQ + size_t OutWritePcmData(const void* buffer, size_t bytes); + // The control function read stream from FMQ + size_t InReadPcmData(void* buffer, size_t bytes); + + // Return if IBluetoothAudioProviderFactory implementation existed + static bool IsAidlAvailable(); + + private: + // using recursive_mutex to allow hwbinder to re-enter again. + std::recursive_mutex mutex_; + SessionType session_type_; + + // audio control path to use for both software and offloading + std::shared_ptr stack_iface_; + // audio data path (FMQ) for software encoding + std::unique_ptr data_mq_; + // audio data configuration for both software and offloading + std::unique_ptr audio_config_; + std::vector latency_modes_; + bool low_latency_allowed_ = true; + + // saving those registered bluetooth_audio's callbacks + std::unordered_map> + observers_; + + bool UpdateDataPath(const DataMQDesc* mq_desc); + bool UpdateAudioConfig(const AudioConfiguration& audio_config); + // invoking the registered session_changed_cb_ + void ReportSessionStatus(); + + static inline std::atomic is_aidl_checked = false; + static inline std::atomic is_aidl_available = false; + static inline const std::string kDefaultAudioProviderFactoryInterface = + std::string() + IBluetoothAudioProviderFactory::descriptor + "/sysbta"; +}; + +class BluetoothAudioSessionInstance { + public: + // The API is to fetch the specified session of A2DP / Hearing Aid + static std::shared_ptr GetSessionInstance( + const SessionType& session_type); + + private: + static std::mutex mutex_; + static std::unordered_map> + sessions_map_; +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h b/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h new file mode 100644 index 0000000..0782c82 --- /dev/null +++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h @@ -0,0 +1,209 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include "BluetoothAudioSession.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +class BluetoothAudioSessionControl { + public: + /*** + * The control API helps to check if session is ready or not + * @return: true if the Bluetooth stack has started th specified session + ***/ + static bool IsSessionReady(const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->IsSessionReady(); + } + + return false; + } + + /*** + * The control API helps the bluetooth_audio module to register + * PortStatusCallbacks + * @return: cookie - the assigned number to this bluetooth_audio output + ***/ + static uint16_t RegisterControlResultCback( + const SessionType& session_type, const PortStatusCallbacks& cbacks) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->RegisterStatusCback(cbacks); + } + return kObserversCookieUndefined; + } + + /*** + * The control API helps the bluetooth_audio module to unregister + * PortStatusCallbacks + * @param: cookie - indicates which bluetooth_audio output is + ***/ + static void UnregisterControlResultCback(const SessionType& session_type, + uint16_t cookie) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->UnregisterStatusCback(cookie); + } + } + + /*** + * The control API for the bluetooth_audio module to get current + * AudioConfiguration + ***/ + static const AudioConfiguration GetAudioConfig( + const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioConfig(); + } + switch (session_type) { + case SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH: + case SessionType::A2DP_HARDWARE_OFFLOAD_DECODING_DATAPATH: + return AudioConfiguration(CodecConfiguration{}); + case SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH: + case SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH: + return AudioConfiguration(LeAudioConfiguration{}); + case SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH: + return AudioConfiguration(LeAudioBroadcastConfiguration{}); + default: + return AudioConfiguration(PcmConfiguration{}); + } + } + + /*** + * Those control APIs for the bluetooth_audio module to start / suspend / + stop + * stream, to check position, and to update metadata. + ***/ + static bool StartStream(const SessionType& session_type, + bool low_latency = false) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->StartStream(low_latency); + } + return false; + } + + static bool SuspendStream(const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->SuspendStream(); + } + return false; + } + + static void StopStream(const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->StopStream(); + } + } + + static bool GetPresentationPosition( + const SessionType& session_type, + PresentationPosition& presentation_position) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetPresentationPosition(presentation_position); + } + return false; + } + + static void UpdateSourceMetadata( + const SessionType& session_type, + const struct source_metadata& source_metadata) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->UpdateSourceMetadata(source_metadata); + } + } + + static void UpdateSinkMetadata(const SessionType& session_type, + const struct sink_metadata& sink_metadata) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->UpdateSinkMetadata(sink_metadata); + } + } + + static std::vector GetSupportedLatencyModes( + const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetSupportedLatencyModes(); + } + return std::vector(); + } + + static void SetLatencyMode(const SessionType& session_type, + const LatencyMode& latency_mode) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->SetLatencyMode(latency_mode); + } + } + + /*** + * The control API writes stream to FMQ + ***/ + static size_t OutWritePcmData(const SessionType& session_type, + const void* buffer, size_t bytes) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->OutWritePcmData(buffer, bytes); + } + return 0; + } + + /*** + * The control API reads stream from FMQ + ***/ + static size_t InReadPcmData(const SessionType& session_type, void* buffer, + size_t bytes) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->InReadPcmData(buffer, bytes); + } + return 0; + } +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionReport.h b/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionReport.h new file mode 100644 index 0000000..0350259 --- /dev/null +++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionReport.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include "BluetoothAudioSession.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +class BluetoothAudioSessionReport { + public: + /*** + * The API reports the Bluetooth stack has started the session, and will + * inform registered bluetooth_audio outputs + ***/ + static void OnSessionStarted( + const SessionType& session_type, + const std::shared_ptr host_iface, + const DataMQDesc* data_mq, const AudioConfiguration& audio_config, + const std::vector& latency_modes) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->OnSessionStarted(host_iface, data_mq, audio_config, + latency_modes); + } + } + + /*** + * The API reports the Bluetooth stack has ended the session, and will + * inform registered bluetooth_audio outputs + ***/ + static void OnSessionEnded(const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->OnSessionEnded(); + } + } + + /*** + * The API reports the Bluetooth stack has replied the result of startStream + * or suspendStream, and will inform registered bluetooth_audio outputs + ***/ + static void ReportControlStatus(const SessionType& session_type, + const bool& start_resp, + BluetoothAudioStatus status) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->ReportControlStatus(start_resp, status); + } + } + /*** + * The API reports the Bluetooth stack has replied the changed of the audio + * configuration, and will inform registered bluetooth_audio outputs + ***/ + static void ReportAudioConfigChanged(const SessionType& session_type, + const AudioConfiguration& audio_config) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->ReportAudioConfigChanged(audio_config); + } + } + /*** + * The API reports the Bluetooth stack has replied the changed of the low + * latency audio allowed, and will inform registered bluetooth_audio outputs + ***/ + static void ReportLowLatencyModeAllowedChanged( + const SessionType& session_type, bool allowed) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->ReportLowLatencyModeAllowedChanged(allowed); + } + } +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/utils/aidl_session/HidlToAidlMiddleware.cpp b/bluetooth/audio/utils/aidl_session/HidlToAidlMiddleware.cpp new file mode 100644 index 0000000..a4664f1 --- /dev/null +++ b/bluetooth/audio/utils/aidl_session/HidlToAidlMiddleware.cpp @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2022 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 "BtAudioNakahara" + +#include +#include +#include +#include + +#include +#include + +#include "../aidl_session/BluetoothAudioSession.h" +#include "../aidl_session/BluetoothAudioSessionControl.h" +#include "HidlToAidlMiddleware_2_0.h" +#include "HidlToAidlMiddleware_2_1.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +using HidlStatus = ::android::hardware::bluetooth::audio::V2_0::Status; +using PcmConfig_2_0 = + ::android::hardware::bluetooth::audio::V2_0::PcmParameters; +using SampleRate_2_0 = ::android::hardware::bluetooth::audio::V2_0::SampleRate; +using ChannelMode_2_0 = + ::android::hardware::bluetooth::audio::V2_0::ChannelMode; +using BitsPerSample_2_0 = + ::android::hardware::bluetooth::audio::V2_0::BitsPerSample; +using CodecConfig_2_0 = + ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration; +using CodecType_2_0 = ::android::hardware::bluetooth::audio::V2_0::CodecType; +using SbcConfig_2_0 = + ::android::hardware::bluetooth::audio::V2_0::SbcParameters; +using AacConfig_2_0 = + ::android::hardware::bluetooth::audio::V2_0::AacParameters; +using LdacConfig_2_0 = + ::android::hardware::bluetooth::audio::V2_0::LdacParameters; +using AptxConfig_2_0 = + ::android::hardware::bluetooth::audio::V2_0::AptxParameters; +using SbcAllocMethod_2_0 = + ::android::hardware::bluetooth::audio::V2_0::SbcAllocMethod; +using SbcBlockLength_2_0 = + ::android::hardware::bluetooth::audio::V2_0::SbcBlockLength; +using SbcChannelMode_2_0 = + ::android::hardware::bluetooth::audio::V2_0::SbcChannelMode; +using SbcNumSubbands_2_0 = + ::android::hardware::bluetooth::audio::V2_0::SbcNumSubbands; +using AacObjectType_2_0 = + ::android::hardware::bluetooth::audio::V2_0::AacObjectType; +using AacVarBitRate_2_0 = + ::android::hardware::bluetooth::audio::V2_0::AacVariableBitRate; +using LdacChannelMode_2_0 = + ::android::hardware::bluetooth::audio::V2_0::LdacChannelMode; +using LdacQualityIndex_2_0 = + ::android::hardware::bluetooth::audio::V2_0::LdacQualityIndex; + +using PcmConfig_2_1 = + ::android::hardware::bluetooth::audio::V2_1::PcmParameters; +using SampleRate_2_1 = ::android::hardware::bluetooth::audio::V2_1::SampleRate; +using Lc3CodecConfig_2_1 = + ::android::hardware::bluetooth::audio::V2_1::Lc3CodecConfiguration; +using Lc3Config_2_1 = + ::android::hardware::bluetooth::audio::V2_1::Lc3Parameters; +using Lc3FrameDuration_2_1 = + ::android::hardware::bluetooth::audio::V2_1::Lc3FrameDuration; + +std::mutex legacy_callback_lock; +std::unordered_map< + SessionType, + std::unordered_map>> + legacy_callback_table; + +const static std::unordered_map + session_type_2_1_to_aidl_map{ + {SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH, + SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH}, + {SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH, + SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH}, + {SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH, + SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH}, + {SessionType_2_1::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH, + SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH}, + {SessionType_2_1::LE_AUDIO_SOFTWARE_DECODED_DATAPATH, + SessionType::LE_AUDIO_SOFTWARE_DECODING_DATAPATH}, + {SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH, + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH}, + {SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH, + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH}, + }; + +const static std::unordered_map + sample_rate_to_hidl_2_1_map{ + {44100, SampleRate_2_1::RATE_44100}, + {48000, SampleRate_2_1::RATE_48000}, + {88200, SampleRate_2_1::RATE_88200}, + {96000, SampleRate_2_1::RATE_96000}, + {176400, SampleRate_2_1::RATE_176400}, + {192000, SampleRate_2_1::RATE_192000}, + {16000, SampleRate_2_1::RATE_16000}, + {24000, SampleRate_2_1::RATE_24000}, + {8000, SampleRate_2_1::RATE_8000}, + {32000, SampleRate_2_1::RATE_32000}, + }; + +const static std::unordered_map + codec_type_to_hidl_2_0_map{ + {CodecType::UNKNOWN, CodecType_2_0::UNKNOWN}, + {CodecType::SBC, CodecType_2_0::SBC}, + {CodecType::AAC, CodecType_2_0::AAC}, + {CodecType::APTX, CodecType_2_0::APTX}, + {CodecType::APTX_HD, CodecType_2_0::APTX_HD}, + {CodecType::LDAC, CodecType_2_0::LDAC}, + {CodecType::LC3, CodecType_2_0::UNKNOWN}, + }; + +const static std::unordered_map + sbc_channel_mode_to_hidl_2_0_map{ + {SbcChannelMode::UNKNOWN, SbcChannelMode_2_0::UNKNOWN}, + {SbcChannelMode::JOINT_STEREO, SbcChannelMode_2_0::JOINT_STEREO}, + {SbcChannelMode::STEREO, SbcChannelMode_2_0::STEREO}, + {SbcChannelMode::DUAL, SbcChannelMode_2_0::DUAL}, + {SbcChannelMode::MONO, SbcChannelMode_2_0::MONO}, + }; + +const static std::unordered_map + sbc_block_length_to_hidl_map{ + {4, SbcBlockLength_2_0::BLOCKS_4}, + {8, SbcBlockLength_2_0::BLOCKS_8}, + {12, SbcBlockLength_2_0::BLOCKS_12}, + {16, SbcBlockLength_2_0::BLOCKS_16}, + }; + +const static std::unordered_map + sbc_subbands_to_hidl_map{ + {4, SbcNumSubbands_2_0::SUBBAND_4}, + {8, SbcNumSubbands_2_0::SUBBAND_8}, + }; + +const static std::unordered_map + sbc_alloc_method_to_hidl_map{ + {SbcAllocMethod::ALLOC_MD_S, SbcAllocMethod_2_0::ALLOC_MD_S}, + {SbcAllocMethod::ALLOC_MD_L, SbcAllocMethod_2_0::ALLOC_MD_L}, + }; + +const static std::unordered_map + aac_object_type_to_hidl_map{ + {AacObjectType::MPEG2_LC, AacObjectType_2_0::MPEG2_LC}, + {AacObjectType::MPEG4_LC, AacObjectType_2_0::MPEG4_LC}, + {AacObjectType::MPEG4_LTP, AacObjectType_2_0::MPEG4_LTP}, + {AacObjectType::MPEG4_SCALABLE, AacObjectType_2_0::MPEG4_SCALABLE}, + }; + +const static std::unordered_map + ldac_channel_mode_to_hidl_map{ + {LdacChannelMode::UNKNOWN, LdacChannelMode_2_0::UNKNOWN}, + {LdacChannelMode::STEREO, LdacChannelMode_2_0::STEREO}, + {LdacChannelMode::DUAL, LdacChannelMode_2_0::DUAL}, + {LdacChannelMode::MONO, LdacChannelMode_2_0::MONO}, + }; + +const static std::unordered_map + ldac_qindex_to_hidl_map{ + {LdacQualityIndex::HIGH, LdacQualityIndex_2_0::QUALITY_HIGH}, + {LdacQualityIndex::MID, LdacQualityIndex_2_0::QUALITY_MID}, + {LdacQualityIndex::LOW, LdacQualityIndex_2_0::QUALITY_LOW}, + {LdacQualityIndex::ABR, LdacQualityIndex_2_0::QUALITY_ABR}, + }; + +inline SessionType from_session_type_2_1( + const SessionType_2_1& session_type_hidl) { + auto it = session_type_2_1_to_aidl_map.find(session_type_hidl); + if (it != session_type_2_1_to_aidl_map.end()) return it->second; + return SessionType::UNKNOWN; +} + +inline SessionType from_session_type_2_0( + const SessionType_2_0& session_type_hidl) { + return from_session_type_2_1(static_cast(session_type_hidl)); +} + +inline HidlStatus to_hidl_status(const BluetoothAudioStatus& status) { + switch (status) { + case BluetoothAudioStatus::SUCCESS: + return HidlStatus::SUCCESS; + case BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION: + return HidlStatus::UNSUPPORTED_CODEC_CONFIGURATION; + default: + return HidlStatus::FAILURE; + } +} + +inline SampleRate_2_1 to_hidl_sample_rate_2_1(const int32_t sample_rate_hz) { + auto it = sample_rate_to_hidl_2_1_map.find(sample_rate_hz); + if (it != sample_rate_to_hidl_2_1_map.end()) return it->second; + return SampleRate_2_1::RATE_UNKNOWN; +} + +inline SampleRate_2_0 to_hidl_sample_rate_2_0(const int32_t sample_rate_hz) { + auto it = sample_rate_to_hidl_2_1_map.find(sample_rate_hz); + if (it != sample_rate_to_hidl_2_1_map.end()) + return static_cast(it->second); + return SampleRate_2_0::RATE_UNKNOWN; +} + +inline BitsPerSample_2_0 to_hidl_bits_per_sample(const int8_t bit_per_sample) { + switch (bit_per_sample) { + case 16: + return BitsPerSample_2_0::BITS_16; + case 24: + return BitsPerSample_2_0::BITS_24; + case 32: + return BitsPerSample_2_0::BITS_32; + default: + return BitsPerSample_2_0::BITS_UNKNOWN; + } +} + +inline ChannelMode_2_0 to_hidl_channel_mode(const ChannelMode channel_mode) { + switch (channel_mode) { + case ChannelMode::MONO: + return ChannelMode_2_0::MONO; + case ChannelMode::STEREO: + return ChannelMode_2_0::STEREO; + default: + return ChannelMode_2_0::UNKNOWN; + } +} + +inline PcmConfig_2_0 to_hidl_pcm_config_2_0( + const PcmConfiguration& pcm_config) { + PcmConfig_2_0 hidl_pcm_config; + hidl_pcm_config.sampleRate = to_hidl_sample_rate_2_0(pcm_config.sampleRateHz); + hidl_pcm_config.channelMode = to_hidl_channel_mode(pcm_config.channelMode); + hidl_pcm_config.bitsPerSample = + to_hidl_bits_per_sample(pcm_config.bitsPerSample); + return hidl_pcm_config; +} + +inline CodecType_2_0 to_hidl_codec_type_2_0(const CodecType codec_type) { + auto it = codec_type_to_hidl_2_0_map.find(codec_type); + if (it != codec_type_to_hidl_2_0_map.end()) return it->second; + return CodecType_2_0::UNKNOWN; +} + +inline SbcConfig_2_0 to_hidl_sbc_config(const SbcConfiguration sbc_config) { + SbcConfig_2_0 hidl_sbc_config; + hidl_sbc_config.minBitpool = sbc_config.minBitpool; + hidl_sbc_config.maxBitpool = sbc_config.maxBitpool; + hidl_sbc_config.sampleRate = to_hidl_sample_rate_2_0(sbc_config.sampleRateHz); + hidl_sbc_config.bitsPerSample = + to_hidl_bits_per_sample(sbc_config.bitsPerSample); + if (sbc_channel_mode_to_hidl_2_0_map.find(sbc_config.channelMode) != + sbc_channel_mode_to_hidl_2_0_map.end()) { + hidl_sbc_config.channelMode = + sbc_channel_mode_to_hidl_2_0_map.at(sbc_config.channelMode); + } + if (sbc_block_length_to_hidl_map.find(sbc_config.blockLength) != + sbc_block_length_to_hidl_map.end()) { + hidl_sbc_config.blockLength = + sbc_block_length_to_hidl_map.at(sbc_config.blockLength); + } + if (sbc_subbands_to_hidl_map.find(sbc_config.numSubbands) != + sbc_subbands_to_hidl_map.end()) { + hidl_sbc_config.numSubbands = + sbc_subbands_to_hidl_map.at(sbc_config.numSubbands); + } + if (sbc_alloc_method_to_hidl_map.find(sbc_config.allocMethod) != + sbc_alloc_method_to_hidl_map.end()) { + hidl_sbc_config.allocMethod = + sbc_alloc_method_to_hidl_map.at(sbc_config.allocMethod); + } + return hidl_sbc_config; +} + +inline AacConfig_2_0 to_hidl_aac_config(const AacConfiguration aac_config) { + AacConfig_2_0 hidl_aac_config; + hidl_aac_config.sampleRate = to_hidl_sample_rate_2_0(aac_config.sampleRateHz); + hidl_aac_config.bitsPerSample = + to_hidl_bits_per_sample(aac_config.bitsPerSample); + hidl_aac_config.channelMode = to_hidl_channel_mode(aac_config.channelMode); + if (aac_object_type_to_hidl_map.find(aac_config.objectType) != + aac_object_type_to_hidl_map.end()) { + hidl_aac_config.objectType = + aac_object_type_to_hidl_map.at(aac_config.objectType); + } + hidl_aac_config.variableBitRateEnabled = aac_config.variableBitRateEnabled + ? AacVarBitRate_2_0::ENABLED + : AacVarBitRate_2_0::DISABLED; + return hidl_aac_config; +} + +inline LdacConfig_2_0 to_hidl_ldac_config(const LdacConfiguration ldac_config) { + LdacConfig_2_0 hidl_ldac_config; + hidl_ldac_config.sampleRate = + to_hidl_sample_rate_2_0(ldac_config.sampleRateHz); + hidl_ldac_config.bitsPerSample = + to_hidl_bits_per_sample(ldac_config.bitsPerSample); + if (ldac_channel_mode_to_hidl_map.find(ldac_config.channelMode) != + ldac_channel_mode_to_hidl_map.end()) { + hidl_ldac_config.channelMode = + ldac_channel_mode_to_hidl_map.at(ldac_config.channelMode); + } + if (ldac_qindex_to_hidl_map.find(ldac_config.qualityIndex) != + ldac_qindex_to_hidl_map.end()) { + hidl_ldac_config.qualityIndex = + ldac_qindex_to_hidl_map.at(ldac_config.qualityIndex); + } + return hidl_ldac_config; +} + +inline AptxConfig_2_0 to_hidl_aptx_config(const AptxConfiguration aptx_config) { + AptxConfig_2_0 hidl_aptx_config; + hidl_aptx_config.sampleRate = + to_hidl_sample_rate_2_0(aptx_config.sampleRateHz); + hidl_aptx_config.bitsPerSample = + to_hidl_bits_per_sample(aptx_config.bitsPerSample); + hidl_aptx_config.channelMode = to_hidl_channel_mode(aptx_config.channelMode); + return hidl_aptx_config; +} + +inline CodecConfig_2_0 to_hidl_codec_config_2_0( + const CodecConfiguration& codec_config) { + CodecConfig_2_0 hidl_codec_config; + hidl_codec_config.codecType = to_hidl_codec_type_2_0(codec_config.codecType); + hidl_codec_config.encodedAudioBitrate = + static_cast(codec_config.encodedAudioBitrate); + hidl_codec_config.peerMtu = static_cast(codec_config.peerMtu); + hidl_codec_config.isScmstEnabled = codec_config.isScmstEnabled; + switch (codec_config.config.getTag()) { + case CodecConfiguration::CodecSpecific::sbcConfig: + hidl_codec_config.config.sbcConfig(to_hidl_sbc_config( + codec_config.config + .get())); + break; + case CodecConfiguration::CodecSpecific::aacConfig: + hidl_codec_config.config.aacConfig(to_hidl_aac_config( + codec_config.config + .get())); + break; + case CodecConfiguration::CodecSpecific::ldacConfig: + hidl_codec_config.config.ldacConfig(to_hidl_ldac_config( + codec_config.config + .get())); + break; + case CodecConfiguration::CodecSpecific::aptxConfig: + hidl_codec_config.config.aptxConfig(to_hidl_aptx_config( + codec_config.config + .get())); + break; + default: + break; + } + return hidl_codec_config; +} + +inline AudioConfig_2_0 to_hidl_audio_config_2_0( + const AudioConfiguration& audio_config) { + AudioConfig_2_0 hidl_audio_config; + if (audio_config.getTag() == AudioConfiguration::pcmConfig) { + hidl_audio_config.pcmConfig(to_hidl_pcm_config_2_0( + audio_config.get())); + } else if (audio_config.getTag() == AudioConfiguration::a2dpConfig) { + hidl_audio_config.codecConfig(to_hidl_codec_config_2_0( + audio_config.get())); + } + return hidl_audio_config; +} + +inline PcmConfig_2_1 to_hidl_pcm_config_2_1( + const PcmConfiguration& pcm_config) { + PcmConfig_2_1 hidl_pcm_config; + hidl_pcm_config.sampleRate = to_hidl_sample_rate_2_1(pcm_config.sampleRateHz); + hidl_pcm_config.channelMode = to_hidl_channel_mode(pcm_config.channelMode); + hidl_pcm_config.bitsPerSample = + to_hidl_bits_per_sample(pcm_config.bitsPerSample); + hidl_pcm_config.dataIntervalUs = + static_cast(pcm_config.dataIntervalUs); + return hidl_pcm_config; +} + +inline Lc3Config_2_1 to_hidl_lc3_config_2_1( + const Lc3Configuration& lc3_config) { + Lc3Config_2_1 hidl_lc3_config; + hidl_lc3_config.pcmBitDepth = to_hidl_bits_per_sample(lc3_config.pcmBitDepth); + hidl_lc3_config.samplingFrequency = + to_hidl_sample_rate_2_1(lc3_config.samplingFrequencyHz); + if (lc3_config.samplingFrequencyHz == 10000) + hidl_lc3_config.frameDuration = Lc3FrameDuration_2_1::DURATION_10000US; + else if (lc3_config.samplingFrequencyHz == 7500) + hidl_lc3_config.frameDuration = Lc3FrameDuration_2_1::DURATION_7500US; + hidl_lc3_config.octetsPerFrame = + static_cast(lc3_config.octetsPerFrame); + hidl_lc3_config.blocksPerSdu = static_cast(lc3_config.blocksPerSdu); + return hidl_lc3_config; +} + +inline Lc3CodecConfig_2_1 to_hidl_leaudio_config_2_1( + const LeAudioConfiguration& unicast_config) { + Lc3CodecConfig_2_1 hidl_lc3_codec_config = { + .audioChannelAllocation = 0, + }; + if (unicast_config.leAudioCodecConfig.getTag() == + LeAudioCodecConfiguration::lc3Config) { + LOG(FATAL) << __func__ << ": unexpected codec type(vendor?)"; + } + auto& le_codec_config = unicast_config.leAudioCodecConfig + .get(); + + hidl_lc3_codec_config.lc3Config = to_hidl_lc3_config_2_1(le_codec_config); + + for (const auto& map : unicast_config.streamMap) { + hidl_lc3_codec_config.audioChannelAllocation |= map.audioChannelAllocation; + } + return hidl_lc3_codec_config; +} + +inline Lc3CodecConfig_2_1 to_hidl_leaudio_broadcast_config_2_1( + const LeAudioBroadcastConfiguration& broadcast_config) { + Lc3CodecConfig_2_1 hidl_lc3_codec_config = { + .audioChannelAllocation = 0, + }; + // NOTE: Broadcast is not officially supported in HIDL + if (broadcast_config.streamMap.empty()) { + return hidl_lc3_codec_config; + } + if (broadcast_config.streamMap[0].leAudioCodecConfig.getTag() != + LeAudioCodecConfiguration::lc3Config) { + LOG(FATAL) << __func__ << ": unexpected codec type(vendor?)"; + } + auto& le_codec_config = + broadcast_config.streamMap[0] + .leAudioCodecConfig.get(); + hidl_lc3_codec_config.lc3Config = to_hidl_lc3_config_2_1(le_codec_config); + + for (const auto& map : broadcast_config.streamMap) { + hidl_lc3_codec_config.audioChannelAllocation |= map.audioChannelAllocation; + } + return hidl_lc3_codec_config; +} + +inline AudioConfig_2_1 to_hidl_audio_config_2_1( + const AudioConfiguration& audio_config) { + AudioConfig_2_1 hidl_audio_config; + switch (audio_config.getTag()) { + case AudioConfiguration::pcmConfig: + hidl_audio_config.pcmConfig(to_hidl_pcm_config_2_1( + audio_config.get())); + break; + case AudioConfiguration::a2dpConfig: + hidl_audio_config.codecConfig(to_hidl_codec_config_2_0( + audio_config.get())); + break; + case AudioConfiguration::leAudioConfig: + hidl_audio_config.leAudioCodecConfig(to_hidl_leaudio_config_2_1( + audio_config.get())); + break; + case AudioConfiguration::leAudioBroadcastConfig: + hidl_audio_config.leAudioCodecConfig(to_hidl_leaudio_broadcast_config_2_1( + audio_config.get())); + break; + } + return hidl_audio_config; +} + +/*** + * + * 2.0 + * + ***/ + +bool HidlToAidlMiddleware_2_0::IsSessionReady( + const SessionType_2_0& session_type) { + return BluetoothAudioSessionControl::IsSessionReady( + from_session_type_2_0(session_type)); +} + +uint16_t HidlToAidlMiddleware_2_0::RegisterControlResultCback( + const SessionType_2_0& session_type, + const PortStatusCallbacks_2_0& cbacks) { + LOG(INFO) << __func__ << ": " << toString(session_type); + auto aidl_session_type = from_session_type_2_0(session_type); + // Pass the exact reference to the lambda + auto& session_legacy_callback_table = + legacy_callback_table[aidl_session_type]; + PortStatusCallbacks aidl_callbacks{}; + if (cbacks.control_result_cb_) { + aidl_callbacks.control_result_cb_ = + [&session_legacy_callback_table](uint16_t cookie, bool start_resp, + const BluetoothAudioStatus& status) { + if (session_legacy_callback_table.find(cookie) == + session_legacy_callback_table.end()) { + LOG(ERROR) << __func__ << ": Unknown callback invoked!"; + return; + } + auto& cback = session_legacy_callback_table[cookie]; + cback->control_result_cb_(cookie, start_resp, to_hidl_status(status)); + }; + } + if (cbacks.session_changed_cb_) { + aidl_callbacks.session_changed_cb_ = + [&session_legacy_callback_table](uint16_t cookie) { + if (session_legacy_callback_table.find(cookie) == + session_legacy_callback_table.end()) { + LOG(ERROR) << __func__ << ": Unknown callback invoked!"; + return; + } + auto& cback = session_legacy_callback_table[cookie]; + cback->session_changed_cb_(cookie); + }; + }; + auto cookie = BluetoothAudioSessionControl::RegisterControlResultCback( + aidl_session_type, aidl_callbacks); + { + std::lock_guard guard(legacy_callback_lock); + session_legacy_callback_table[cookie] = + std::make_shared(cbacks); + } + return cookie; +} + +void HidlToAidlMiddleware_2_0::UnregisterControlResultCback( + const SessionType_2_0& session_type, uint16_t cookie) { + LOG(INFO) << __func__ << ": " << toString(session_type); + auto aidl_session_type = from_session_type_2_0(session_type); + BluetoothAudioSessionControl::UnregisterControlResultCback(aidl_session_type, + cookie); + auto& session_callback_table = legacy_callback_table[aidl_session_type]; + if (session_callback_table.find(cookie) != session_callback_table.end()) { + std::lock_guard guard(legacy_callback_lock); + session_callback_table.erase(cookie); + } +} + +const AudioConfig_2_0 HidlToAidlMiddleware_2_0::GetAudioConfig( + const SessionType_2_0& session_type) { + return to_hidl_audio_config_2_0(BluetoothAudioSessionControl::GetAudioConfig( + from_session_type_2_0(session_type))); +} + +bool HidlToAidlMiddleware_2_0::StartStream( + const SessionType_2_0& session_type) { + return BluetoothAudioSessionControl::StartStream( + from_session_type_2_0(session_type)); +} + +void HidlToAidlMiddleware_2_0::StopStream(const SessionType_2_0& session_type) { + return BluetoothAudioSessionControl::StopStream( + from_session_type_2_0(session_type)); +} + +bool HidlToAidlMiddleware_2_0::SuspendStream( + const SessionType_2_0& session_type) { + return BluetoothAudioSessionControl::SuspendStream( + from_session_type_2_0(session_type)); +} + +bool HidlToAidlMiddleware_2_0::GetPresentationPosition( + const SessionType_2_0& session_type, uint64_t* remote_delay_report_ns, + uint64_t* total_bytes_readed, timespec* data_position) { + PresentationPosition presentation_position; + auto ret_val = BluetoothAudioSessionControl::GetPresentationPosition( + from_session_type_2_0(session_type), presentation_position); + if (remote_delay_report_ns) + *remote_delay_report_ns = presentation_position.remoteDeviceAudioDelayNanos; + if (total_bytes_readed) + *total_bytes_readed = presentation_position.transmittedOctets; + if (data_position) + *data_position = { + .tv_sec = static_cast<__kernel_old_time_t>( + presentation_position.transmittedOctetsTimestamp.tvSec), + .tv_nsec = static_cast( + presentation_position.transmittedOctetsTimestamp.tvNSec)}; + return ret_val; +} + +void HidlToAidlMiddleware_2_0::UpdateTracksMetadata( + const SessionType_2_0& session_type, + const struct source_metadata* source_metadata) { + return BluetoothAudioSessionControl::UpdateSourceMetadata( + from_session_type_2_0(session_type), *source_metadata); +} + +size_t HidlToAidlMiddleware_2_0::OutWritePcmData( + const SessionType_2_0& session_type, const void* buffer, size_t bytes) { + return BluetoothAudioSessionControl::OutWritePcmData( + from_session_type_2_0(session_type), buffer, bytes); +} + +size_t HidlToAidlMiddleware_2_0::InReadPcmData( + const SessionType_2_0& session_type, void* buffer, size_t bytes) { + return BluetoothAudioSessionControl::InReadPcmData( + from_session_type_2_0(session_type), buffer, bytes); +} + +bool HidlToAidlMiddleware_2_0::IsAidlAvailable() { + return BluetoothAudioSession::IsAidlAvailable(); +} + +/*** + * + * 2.1 + * + ***/ + +const AudioConfig_2_1 HidlToAidlMiddleware_2_1::GetAudioConfig( + const SessionType_2_1& session_type) { + return to_hidl_audio_config_2_1(BluetoothAudioSessionControl::GetAudioConfig( + from_session_type_2_1(session_type))); +} + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/utils/aidl_session/HidlToAidlMiddleware_2_0.h b/bluetooth/audio/utils/aidl_session/HidlToAidlMiddleware_2_0.h new file mode 100644 index 0000000..b124d8f --- /dev/null +++ b/bluetooth/audio/utils/aidl_session/HidlToAidlMiddleware_2_0.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 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. + */ + +#include + +#include "../session/BluetoothAudioSession.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +using SessionType_2_0 = + ::android::hardware::bluetooth::audio::V2_0::SessionType; +using PortStatusCallbacks_2_0 = + ::android::bluetooth::audio::PortStatusCallbacks; +using AudioConfig_2_0 = + ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration; + +class HidlToAidlMiddleware_2_0 { + public: + static bool IsAidlAvailable(); + + static bool IsSessionReady(const SessionType_2_0& session_type); + + static uint16_t RegisterControlResultCback( + const SessionType_2_0& session_type, + const PortStatusCallbacks_2_0& cbacks); + + static void UnregisterControlResultCback(const SessionType_2_0& session_type, + uint16_t cookie); + + static const AudioConfig_2_0 GetAudioConfig( + const SessionType_2_0& session_type); + + static bool StartStream(const SessionType_2_0& session_type); + + static void StopStream(const SessionType_2_0& session_type); + + static bool SuspendStream(const SessionType_2_0& session_type); + + static bool GetPresentationPosition(const SessionType_2_0& session_type, + uint64_t* remote_delay_report_ns, + uint64_t* total_bytes_readed, + timespec* data_position); + + static void UpdateTracksMetadata( + const SessionType_2_0& session_type, + const struct source_metadata* source_metadata); + + static size_t OutWritePcmData(const SessionType_2_0& session_type, + const void* buffer, size_t bytes); + + static size_t InReadPcmData(const SessionType_2_0& session_type, void* buffer, + size_t bytes); +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/utils/aidl_session/HidlToAidlMiddleware_2_1.h b/bluetooth/audio/utils/aidl_session/HidlToAidlMiddleware_2_1.h new file mode 100644 index 0000000..82dce96 --- /dev/null +++ b/bluetooth/audio/utils/aidl_session/HidlToAidlMiddleware_2_1.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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. + */ + +#include + +#include "../session/BluetoothAudioSession.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace bluetooth { +namespace audio { + +using SessionType_2_1 = + ::android::hardware::bluetooth::audio::V2_1::SessionType; +using AudioConfig_2_1 = + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration; + +class HidlToAidlMiddleware_2_1 { + public: + static const AudioConfig_2_1 GetAudioConfig( + const SessionType_2_1& session_type); +}; + +} // namespace audio +} // namespace bluetooth +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession.cpp b/bluetooth/audio/utils/session/BluetoothAudioSession.cpp new file mode 100644 index 0000000..283952e --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSession.cpp @@ -0,0 +1,498 @@ +/* + * Copyright 2018 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 "BTAudioProviderSession" + +#include "BluetoothAudioSession.h" + +#include +#include + +#include "../aidl_session/HidlToAidlMiddleware_2_0.h" + +namespace android { +namespace bluetooth { +namespace audio { + +using ::aidl::android::hardware::bluetooth::audio::HidlToAidlMiddleware_2_0; +using ::android::hardware::audio::common::V5_0::AudioContentType; +using ::android::hardware::audio::common::V5_0::AudioUsage; +using ::android::hardware::audio::common::V5_0::PlaybackTrackMetadata; +using ::android::hardware::audio::common::V5_0::SourceMetadata; +using ::android::hardware::bluetooth::audio::V2_0::CodecType; +using ::android::hardware::bluetooth::audio::V2_0::TimeSpec; + +const CodecConfiguration BluetoothAudioSession::kInvalidCodecConfiguration = { + .codecType = CodecType::UNKNOWN, + .encodedAudioBitrate = 0x00000000, + .peerMtu = 0xffff, + .isScmstEnabled = false, + .config = {}}; +AudioConfiguration BluetoothAudioSession::invalidSoftwareAudioConfiguration = + {}; +AudioConfiguration BluetoothAudioSession::invalidOffloadAudioConfiguration = {}; + +static constexpr int kFmqSendTimeoutMs = 1000; // 1000 ms timeout for sending +static constexpr int kFmqReceiveTimeoutMs = + 1000; // 1000 ms timeout for receiving +static constexpr int kWritePollMs = 1; // polled non-blocking interval +static constexpr int kReadPollMs = 1; // polled non-blocking interval + +static inline timespec timespec_convert_from_hal(const TimeSpec& TS) { + return {.tv_sec = static_cast(TS.tvSec), + .tv_nsec = static_cast(TS.tvNSec)}; +} + +BluetoothAudioSession::BluetoothAudioSession(const SessionType& session_type) + : session_type_(session_type), stack_iface_(nullptr), mDataMQ(nullptr) { + invalidSoftwareAudioConfiguration.pcmConfig(kInvalidPcmParameters); + invalidOffloadAudioConfiguration.codecConfig(kInvalidCodecConfiguration); +} + +// The report function is used to report that the Bluetooth stack has started +// this session without any failure, and will invoke session_changed_cb_ to +// notify those registered bluetooth_audio outputs +void BluetoothAudioSession::OnSessionStarted( + const sp stack_iface, const DataMQ::Descriptor* dataMQ, + const AudioConfiguration& audio_config) { + std::lock_guard guard(mutex_); + if (stack_iface == nullptr) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) + << ", IBluetoothAudioPort Invalid"; + } else if (!UpdateAudioConfig(audio_config)) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) + << ", AudioConfiguration=" << toString(audio_config) + << " Invalid"; + } else if (!UpdateDataPath(dataMQ)) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) + << " DataMQ Invalid"; + audio_config_ = + (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH + ? kInvalidOffloadAudioConfiguration + : kInvalidSoftwareAudioConfiguration); + } else { + stack_iface_ = stack_iface; + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << ", AudioConfiguration=" << toString(audio_config); + ReportSessionStatus(); + } +} + +// The report function is used to report that the Bluetooth stack has ended the +// session, and will invoke session_changed_cb_ to notify registered +// bluetooth_audio outputs +void BluetoothAudioSession::OnSessionEnded() { + std::lock_guard guard(mutex_); + bool toggled = IsSessionReady(); + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_); + audio_config_ = (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH + ? kInvalidOffloadAudioConfiguration + : kInvalidSoftwareAudioConfiguration); + stack_iface_ = nullptr; + UpdateDataPath(nullptr); + if (toggled) { + ReportSessionStatus(); + } +} + +// invoking the registered session_changed_cb_ +void BluetoothAudioSession::ReportSessionStatus() { + // This is locked already by OnSessionStarted / OnSessionEnded + if (observers_.empty()) { + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO port state observer"; + return; + } + for (auto& observer : observers_) { + uint16_t cookie = observer.first; + std::shared_ptr cb = observer.second; + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) + << " notify to bluetooth_audio=0x" + << android::base::StringPrintf("%04x", cookie); + cb->session_changed_cb_(cookie); + } +} + +// The report function is used to report that the Bluetooth stack has notified +// the result of startStream or suspendStream, and will invoke +// control_result_cb_ to notify registered bluetooth_audio outputs +void BluetoothAudioSession::ReportControlStatus( + bool start_resp, const BluetoothAudioStatus& status) { + std::lock_guard guard(mutex_); + if (observers_.empty()) { + LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO port state observer"; + return; + } + for (auto& observer : observers_) { + uint16_t cookie = observer.first; + std::shared_ptr cb = observer.second; + LOG(INFO) << __func__ << " - status=" << toString(status) + << " for SessionType=" << toString(session_type_) + << ", bluetooth_audio=0x" + << android::base::StringPrintf("%04x", cookie) + << (start_resp ? " started" : " suspended"); + cb->control_result_cb_(cookie, start_resp, status); + } +} + +// The function helps to check if this session is ready or not +// @return: true if the Bluetooth stack has started the specified session +bool BluetoothAudioSession::IsSessionReady() { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_0::IsSessionReady(session_type_); + std::lock_guard guard(mutex_); + bool dataMQ_valid = + (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH || + (mDataMQ != nullptr && mDataMQ->isValid())); + return stack_iface_ != nullptr && dataMQ_valid; +} + +bool BluetoothAudioSession::UpdateDataPath(const DataMQ::Descriptor* dataMQ) { + if (dataMQ == nullptr) { + // usecase of reset by nullptr + mDataMQ = nullptr; + return true; + } + std::unique_ptr tempDataMQ; + tempDataMQ.reset(new DataMQ(*dataMQ)); + if (!tempDataMQ || !tempDataMQ->isValid()) { + mDataMQ = nullptr; + return false; + } + mDataMQ = std::move(tempDataMQ); + return true; +} + +bool BluetoothAudioSession::UpdateAudioConfig( + const AudioConfiguration& audio_config) { + bool is_software_session = + (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type_ == SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH); + bool is_offload_session = + (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH); + auto audio_config_discriminator = audio_config.getDiscriminator(); + bool is_software_audio_config = + (is_software_session && + audio_config_discriminator == + AudioConfiguration::hidl_discriminator::pcmConfig); + bool is_offload_audio_config = + (is_offload_session && + audio_config_discriminator == + AudioConfiguration::hidl_discriminator::codecConfig); + if (!is_software_audio_config && !is_offload_audio_config) { + return false; + } + audio_config_ = audio_config; + return true; +} + +// The control function helps the bluetooth_audio module to register +// PortStatusCallbacks +// @return: cookie - the assigned number to this bluetooth_audio output +uint16_t BluetoothAudioSession::RegisterStatusCback( + const PortStatusCallbacks& cbacks) { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_0::RegisterControlResultCback(session_type_, + cbacks); + std::lock_guard guard(mutex_); + uint16_t cookie = ObserversCookieGetInitValue(session_type_); + uint16_t cookie_upper_bound = ObserversCookieGetUpperBound(session_type_); + + while (cookie < cookie_upper_bound) { + if (observers_.find(cookie) == observers_.end()) { + break; + } + ++cookie; + } + if (cookie >= cookie_upper_bound) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) + << " has " << observers_.size() + << " observers already (No Resource)"; + return kObserversCookieUndefined; + } + std::shared_ptr cb = + std::make_shared(); + *cb = cbacks; + observers_[cookie] = cb; + return cookie; +} + +// The control function helps the bluetooth_audio module to unregister +// PortStatusCallbacks +// @param: cookie - indicates which bluetooth_audio output is +void BluetoothAudioSession::UnregisterStatusCback(uint16_t cookie) { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_0::UnregisterControlResultCback(session_type_, + cookie); + std::lock_guard guard(mutex_); + if (observers_.erase(cookie) != 1) { + LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) + << " no such provider=0x" + << android::base::StringPrintf("%04x", cookie); + } +} + +// The control function is for the bluetooth_audio module to get the current +// AudioConfiguration +const AudioConfiguration& BluetoothAudioSession::GetAudioConfig() { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return (audio_config_ = + HidlToAidlMiddleware_2_0::GetAudioConfig(session_type_)); + std::lock_guard guard(mutex_); + if (IsSessionReady()) { + return audio_config_; + } else if (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { + return kInvalidOffloadAudioConfiguration; + } else { + return kInvalidSoftwareAudioConfiguration; + } +} + +// Those control functions are for the bluetooth_audio module to start, suspend, +// stop stream, to check position, and to update metadata. +bool BluetoothAudioSession::StartStream() { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_0::StartStream(session_type_); + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return false; + } + auto hal_retval = stack_iface_->startStream(); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + return false; + } + return true; +} + +bool BluetoothAudioSession::SuspendStream() { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_0::SuspendStream(session_type_); + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return false; + } + auto hal_retval = stack_iface_->suspendStream(); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + return false; + } + return true; +} + +void BluetoothAudioSession::StopStream() { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_0::StopStream(session_type_); + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + return; + } + auto hal_retval = stack_iface_->stopStream(); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + } +} + +bool BluetoothAudioSession::GetPresentationPosition( + uint64_t* remote_delay_report_ns, uint64_t* total_bytes_readed, + timespec* data_position) { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_0::GetPresentationPosition( + session_type_, remote_delay_report_ns, total_bytes_readed, + data_position); + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return false; + } + bool retval = false; + auto hal_retval = stack_iface_->getPresentationPosition( + [&retval, &remote_delay_report_ns, &total_bytes_readed, &data_position]( + BluetoothAudioStatus status, + const uint64_t& remoteDeviceAudioDelayNanos, + uint64_t transmittedOctets, + const TimeSpec& transmittedOctetsTimeStamp) { + if (status == BluetoothAudioStatus::SUCCESS) { + if (remote_delay_report_ns) + *remote_delay_report_ns = remoteDeviceAudioDelayNanos; + if (total_bytes_readed) *total_bytes_readed = transmittedOctets; + if (data_position) + *data_position = + timespec_convert_from_hal(transmittedOctetsTimeStamp); + retval = true; + } + }); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + return false; + } + return retval; +} + +void BluetoothAudioSession::UpdateTracksMetadata( + const struct source_metadata* source_metadata) { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_0::UpdateTracksMetadata(session_type_, + source_metadata); + std::lock_guard guard(mutex_); + if (!IsSessionReady()) { + LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) + << " has NO session"; + return; + } + + ssize_t track_count = source_metadata->track_count; + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) << ", " + << track_count << " track(s)"; + if (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { + return; + } + + struct playback_track_metadata* track = source_metadata->tracks; + SourceMetadata sourceMetadata; + PlaybackTrackMetadata* halMetadata; + + sourceMetadata.tracks.resize(track_count); + halMetadata = sourceMetadata.tracks.data(); + while (track_count && track) { + halMetadata->usage = static_cast(track->usage); + halMetadata->contentType = + static_cast(track->content_type); + halMetadata->gain = track->gain; + LOG(VERBOSE) << __func__ << " - SessionType=" << toString(session_type_) + << ", usage=" << toString(halMetadata->usage) + << ", content=" << toString(halMetadata->contentType) + << ", gain=" << halMetadata->gain; + --track_count; + ++track; + ++halMetadata; + } + auto hal_retval = stack_iface_->updateMetadata(sourceMetadata); + if (!hal_retval.isOk()) { + LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" + << toString(session_type_) << " failed"; + } +} + +// The control function writes stream to FMQ +size_t BluetoothAudioSession::OutWritePcmData(const void* buffer, + size_t bytes) { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_0::OutWritePcmData(session_type_, buffer, + bytes); + if (buffer == nullptr || !bytes) return 0; + size_t totalWritten = 0; + int ms_timeout = kFmqSendTimeoutMs; + do { + std::unique_lock lock(mutex_); + if (!IsSessionReady()) break; + size_t availableToWrite = mDataMQ->availableToWrite(); + if (availableToWrite) { + if (availableToWrite > (bytes - totalWritten)) { + availableToWrite = bytes - totalWritten; + } + + if (!mDataMQ->write(static_cast(buffer) + totalWritten, + availableToWrite)) { + ALOGE("FMQ datapath writting %zu/%zu failed", totalWritten, bytes); + return totalWritten; + } + totalWritten += availableToWrite; + } else if (ms_timeout >= kWritePollMs) { + lock.unlock(); + usleep(kWritePollMs * 1000); + ms_timeout -= kWritePollMs; + } else { + ALOGD("data %zu/%zu overflow %d ms", totalWritten, bytes, + (kFmqSendTimeoutMs - ms_timeout)); + return totalWritten; + } + } while (totalWritten < bytes); + return totalWritten; +} + +// The control function reads stream from FMQ +size_t BluetoothAudioSession::InReadPcmData(void* buffer, size_t bytes) { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_0::InReadPcmData(session_type_, buffer, + bytes); + if (buffer == nullptr || !bytes) return 0; + size_t totalRead = 0; + int ms_timeout = kFmqReceiveTimeoutMs; + do { + std::unique_lock lock(mutex_); + if (!IsSessionReady()) break; + size_t availableToRead = mDataMQ->availableToRead(); + if (availableToRead) { + if (availableToRead > (bytes - totalRead)) { + availableToRead = bytes - totalRead; + } + if (!mDataMQ->read(static_cast(buffer) + totalRead, + availableToRead)) { + ALOGE("FMQ datapath reading %zu/%zu failed", totalRead, bytes); + return totalRead; + } + totalRead += availableToRead; + } else if (ms_timeout >= kReadPollMs) { + lock.unlock(); + usleep(kReadPollMs * 1000); + ms_timeout -= kReadPollMs; + continue; + } else { + ALOGD("in data %zu/%zu overflow %d ms", totalRead, bytes, + (kFmqReceiveTimeoutMs - ms_timeout)); + return totalRead; + } + } while (totalRead < bytes); + return totalRead; +} + +std::unique_ptr + BluetoothAudioSessionInstance::instance_ptr = + std::unique_ptr( + new BluetoothAudioSessionInstance()); + +// API to fetch the session of A2DP / Hearing Aid +std::shared_ptr +BluetoothAudioSessionInstance::GetSessionInstance( + const SessionType& session_type) { + std::lock_guard guard(instance_ptr->mutex_); + if (!instance_ptr->sessions_map_.empty()) { + auto entry = instance_ptr->sessions_map_.find(session_type); + if (entry != instance_ptr->sessions_map_.end()) { + return entry->second; + } + } + std::shared_ptr session_ptr = + std::make_shared(session_type); + instance_ptr->sessions_map_[session_type] = session_ptr; + return session_ptr; +} + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession.h b/bluetooth/audio/utils/session/BluetoothAudioSession.h new file mode 100644 index 0000000..3469cc0 --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSession.h @@ -0,0 +1,191 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace android { +namespace bluetooth { +namespace audio { + +using ::android::sp; +using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::MessageQueue; +using ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration; +using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample; +using ::android::hardware::bluetooth::audio::V2_0::ChannelMode; +using ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration; +using ::android::hardware::bluetooth::audio::V2_0::IBluetoothAudioPort; +using ::android::hardware::bluetooth::audio::V2_0::PcmParameters; +using ::android::hardware::bluetooth::audio::V2_0::SampleRate; +using ::android::hardware::bluetooth::audio::V2_0::SessionType; + +using BluetoothAudioStatus = + ::android::hardware::bluetooth::audio::V2_0::Status; + +using DataMQ = MessageQueue; + +static constexpr uint16_t kObserversCookieSize = 0x0010; // 0x0000 ~ 0x000f +constexpr uint16_t kObserversCookieUndefined = + (static_cast(SessionType::UNKNOWN) << 8 & 0xff00); +inline SessionType ObserversCookieGetSessionType(uint16_t cookie) { + return static_cast(cookie >> 8 & 0x00ff); +} +inline uint16_t ObserversCookieGetInitValue(SessionType session_type) { + return (static_cast(session_type) << 8 & 0xff00); +} +inline uint16_t ObserversCookieGetUpperBound(SessionType session_type) { + return (static_cast(session_type) << 8 & 0xff00) + + kObserversCookieSize; +} + +// This presents the callbacks of started / suspended and session changed, +// and the bluetooth_audio module uses to receive the status notification +struct PortStatusCallbacks { + // control_result_cb_ - when the Bluetooth stack reports results of + // streamStarted or streamSuspended, the BluetoothAudioProvider will invoke + // this callback to report to the bluetooth_audio module. + // @param: cookie - indicates which bluetooth_audio output should handle + // @param: start_resp - this report is for startStream or not + // @param: status - the result of startStream + std::function + control_result_cb_; + // session_changed_cb_ - when the Bluetooth stack start / end session, the + // BluetoothAudioProvider will invoke this callback to notify to the + // bluetooth_audio module. + // @param: cookie - indicates which bluetooth_audio output should handle + std::function session_changed_cb_; +}; + +class BluetoothAudioSession { + friend class BluetoothAudioSession_2_1; + friend class BluetoothAudioSession_2_2; + + private: + // using recursive_mutex to allow hwbinder to re-enter agian. + std::recursive_mutex mutex_; + SessionType session_type_; + + // audio control path to use for both software and offloading + sp stack_iface_; + // audio data path (FMQ) for software encoding + std::unique_ptr mDataMQ; + // audio data configuration for both software and offloading + AudioConfiguration audio_config_; + + static AudioConfiguration invalidSoftwareAudioConfiguration; + static AudioConfiguration invalidOffloadAudioConfiguration; + + // saving those registered bluetooth_audio's callbacks + std::unordered_map> + observers_; + + bool UpdateDataPath(const DataMQ::Descriptor* dataMQ); + bool UpdateAudioConfig(const AudioConfiguration& audio_config); + // invoking the registered session_changed_cb_ + void ReportSessionStatus(); + + public: + BluetoothAudioSession(const SessionType& session_type); + + // The function helps to check if this session is ready or not + // @return: true if the Bluetooth stack has started the specified session + bool IsSessionReady(); + + // The report function is used to report that the Bluetooth stack has started + // this session without any failure, and will invoke session_changed_cb_ to + // notify those registered bluetooth_audio outputs + void OnSessionStarted(const sp stack_iface, + const DataMQ::Descriptor* dataMQ, + const AudioConfiguration& audio_config); + + // The report function is used to report that the Bluetooth stack has ended + // the session, and will invoke session_changed_cb_ to notify registered + // bluetooth_audio outputs + void OnSessionEnded(); + + // The report function is used to report that the Bluetooth stack has notified + // the result of startStream or suspendStream, and will invoke + // control_result_cb_ to notify registered bluetooth_audio outputs + void ReportControlStatus(bool start_resp, const BluetoothAudioStatus& status); + + // The control function helps the bluetooth_audio module to register + // PortStatusCallbacks + // @return: cookie - the assigned number to this bluetooth_audio output + uint16_t RegisterStatusCback(const PortStatusCallbacks& cbacks); + + // The control function helps the bluetooth_audio module to unregister + // PortStatusCallbacks + // @param: cookie - indicates which bluetooth_audio output is + void UnregisterStatusCback(uint16_t cookie); + + // The control function is for the bluetooth_audio module to get the current + // AudioConfiguration + const AudioConfiguration& GetAudioConfig(); + + // Those control functions are for the bluetooth_audio module to start, + // suspend, stop stream, to check position, and to update metadata. + bool StartStream(); + bool SuspendStream(); + void StopStream(); + bool GetPresentationPosition(uint64_t* remote_delay_report_ns, + uint64_t* total_bytes_readed, + timespec* data_position); + void UpdateTracksMetadata(const struct source_metadata* source_metadata); + + // The control function writes stream to FMQ + size_t OutWritePcmData(const void* buffer, size_t bytes); + // The control function read stream from FMQ + size_t InReadPcmData(void* buffer, size_t bytes); + + static constexpr PcmParameters kInvalidPcmParameters = { + .sampleRate = SampleRate::RATE_UNKNOWN, + .channelMode = ChannelMode::UNKNOWN, + .bitsPerSample = BitsPerSample::BITS_UNKNOWN, + }; + // can't be constexpr because of non-literal type + static const CodecConfiguration kInvalidCodecConfiguration; + + static constexpr AudioConfiguration& kInvalidSoftwareAudioConfiguration = + invalidSoftwareAudioConfiguration; + static constexpr AudioConfiguration& kInvalidOffloadAudioConfiguration = + invalidOffloadAudioConfiguration; +}; + +class BluetoothAudioSessionInstance { + public: + // The API is to fetch the specified session of A2DP / Hearing Aid + static std::shared_ptr GetSessionInstance( + const SessionType& session_type); + + private: + static std::unique_ptr instance_ptr; + std::mutex mutex_; + std::unordered_map> + sessions_map_; +}; + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionControl.h b/bluetooth/audio/utils/session/BluetoothAudioSessionControl.h new file mode 100644 index 0000000..6707765 --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSessionControl.h @@ -0,0 +1,143 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include "BluetoothAudioSession.h" + +namespace android { +namespace bluetooth { +namespace audio { + +class BluetoothAudioSessionControl { + public: + // The control API helps to check if session is ready or not + // @return: true if the Bluetooth stack has started th specified session + static bool IsSessionReady(const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->IsSessionReady(); + } + return false; + } + + // The control API helps the bluetooth_audio module to register + // PortStatusCallbacks + // @return: cookie - the assigned number to this bluetooth_audio output + static uint16_t RegisterControlResultCback( + const SessionType& session_type, const PortStatusCallbacks& cbacks) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->RegisterStatusCback(cbacks); + } + return kObserversCookieUndefined; + } + + // The control API helps the bluetooth_audio module to unregister + // PortStatusCallbacks + // @param: cookie - indicates which bluetooth_audio output is + static void UnregisterControlResultCback(const SessionType& session_type, + uint16_t cookie) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->UnregisterStatusCback(cookie); + } + } + + // The control API for the bluetooth_audio module to get current + // AudioConfiguration + static const AudioConfiguration& GetAudioConfig( + const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioConfig(); + } else if (session_type == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { + return BluetoothAudioSession::kInvalidOffloadAudioConfiguration; + } else { + return BluetoothAudioSession::kInvalidSoftwareAudioConfiguration; + } + } + + // Those control APIs for the bluetooth_audio module to start / suspend / stop + // stream, to check position, and to update metadata. + static bool StartStream(const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->StartStream(); + } + return false; + } + + static bool SuspendStream(const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->SuspendStream(); + } + return false; + } + + static void StopStream(const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->StopStream(); + } + } + + static bool GetPresentationPosition(const SessionType& session_type, + uint64_t* remote_delay_report_ns, + uint64_t* total_bytes_readed, + timespec* data_position) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetPresentationPosition( + remote_delay_report_ns, total_bytes_readed, data_position); + } + return false; + } + + static void UpdateTracksMetadata( + const SessionType& session_type, + const struct source_metadata* source_metadata) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->UpdateTracksMetadata(source_metadata); + } + } + + // The control API writes stream to FMQ + static size_t OutWritePcmData(const SessionType& session_type, + const void* buffer, size_t bytes) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->OutWritePcmData(buffer, bytes); + } + return 0; + } +}; + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h new file mode 100644 index 0000000..4d7be21 --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h @@ -0,0 +1,160 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include "BluetoothAudioSession_2_1.h" + +namespace android { +namespace bluetooth { +namespace audio { + +class BluetoothAudioSessionControl_2_1 { + using SessionType_2_1 = + ::android::hardware::bluetooth::audio::V2_1::SessionType; + using AudioConfiguration_2_1 = + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration; + + public: + // The control API helps to check if session is ready or not + // @return: true if the Bluetooth stack has started th specified session + static bool IsSessionReady(const SessionType_2_1& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->IsSessionReady(); + } + return false; + } + + // The control API helps the bluetooth_audio module to register + // PortStatusCallbacks + // @return: cookie - the assigned number to this bluetooth_audio output + static uint16_t RegisterControlResultCback( + const SessionType_2_1& session_type, const PortStatusCallbacks& cbacks) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->RegisterStatusCback(cbacks); + } + return kObserversCookieUndefined; + } + + // The control API helps the bluetooth_audio module to unregister + // PortStatusCallbacks + // @param: cookie - indicates which bluetooth_audio output is + static void UnregisterControlResultCback(const SessionType_2_1& session_type, + uint16_t cookie) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->GetAudioSession()->UnregisterStatusCback(cookie); + } + } + + // The control API for the bluetooth_audio module to get current + // AudioConfiguration + static const AudioConfiguration_2_1 GetAudioConfig( + const SessionType_2_1& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioConfig(); + } else if (session_type == + SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH) { + return BluetoothAudioSession_2_1::kInvalidOffloadAudioConfiguration; + } else { + return BluetoothAudioSession_2_1::kInvalidSoftwareAudioConfiguration; + } + } + + // Those control APIs for the bluetooth_audio module to start / suspend / stop + // stream, to check position, and to update metadata. + static bool StartStream(const SessionType_2_1& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->StartStream(); + } + return false; + } + + static bool SuspendStream(const SessionType_2_1& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->SuspendStream(); + } + return false; + } + + static void StopStream(const SessionType_2_1& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->GetAudioSession()->StopStream(); + } + } + + static bool GetPresentationPosition(const SessionType_2_1& session_type, + uint64_t* remote_delay_report_ns, + uint64_t* total_bytes_readed, + timespec* data_position) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->GetPresentationPosition( + remote_delay_report_ns, total_bytes_readed, data_position); + } + return false; + } + + static void UpdateTracksMetadata( + const SessionType_2_1& session_type, + const struct source_metadata* source_metadata) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->GetAudioSession()->UpdateTracksMetadata(source_metadata); + } + } + + // The control API writes stream to FMQ + static size_t OutWritePcmData(const SessionType_2_1& session_type, + const void* buffer, size_t bytes) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->OutWritePcmData(buffer, bytes); + } + return 0; + } + + // The control API reads stream from FMQ + static size_t InReadPcmData(const SessionType_2_1& session_type, void* buffer, + size_t bytes) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + return session_ptr->GetAudioSession()->InReadPcmData(buffer, bytes); + } + return 0; + } +}; + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionReport.h b/bluetooth/audio/utils/session/BluetoothAudioSessionReport.h new file mode 100644 index 0000000..5a83ae2 --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSessionReport.h @@ -0,0 +1,63 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include "BluetoothAudioSession.h" + +namespace android { +namespace bluetooth { +namespace audio { + +class BluetoothAudioSessionReport { + public: + // The API reports the Bluetooth stack has started the session, and will + // inform registered bluetooth_audio outputs + static void OnSessionStarted(const SessionType& session_type, + const sp host_iface, + const DataMQ::Descriptor* dataMQ, + const AudioConfiguration& audio_config) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->OnSessionStarted(host_iface, dataMQ, audio_config); + } + } + // The API reports the Bluetooth stack has ended the session, and will + // inform registered bluetooth_audio outputs + static void OnSessionEnded(const SessionType& session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->OnSessionEnded(); + } + } + // The API reports the Bluetooth stack has replied the result of startStream + // or suspendStream, and will inform registered bluetooth_audio outputs + static void ReportControlStatus(const SessionType& session_type, + const bool& start_resp, + const BluetoothAudioStatus& status) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->ReportControlStatus(start_resp, status); + } + } +}; + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h new file mode 100644 index 0000000..ab30536 --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h @@ -0,0 +1,69 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include "BluetoothAudioSession_2_1.h" + +namespace android { +namespace bluetooth { +namespace audio { + +class BluetoothAudioSessionReport_2_1 { + public: + // The API reports the Bluetooth stack has started the session, and will + // inform registered bluetooth_audio outputs + static void OnSessionStarted( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type, + const sp host_iface, + const DataMQ::Descriptor* dataMQ, + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + audio_config) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->OnSessionStarted(host_iface, dataMQ, audio_config); + } + } + // The API reports the Bluetooth stack has ended the session, and will + // inform registered bluetooth_audio outputs + static void OnSessionEnded( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->GetAudioSession()->OnSessionEnded(); + } + } + // The API reports the Bluetooth stack has replied the result of startStream + // or suspendStream, and will inform registered bluetooth_audio outputs + static void ReportControlStatus( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type, + const bool& start_resp, const BluetoothAudioStatus& status) { + std::shared_ptr session_ptr = + BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type); + if (session_ptr != nullptr) { + session_ptr->GetAudioSession()->ReportControlStatus(start_resp, status); + } + } +}; + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp new file mode 100644 index 0000000..276a291 --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp @@ -0,0 +1,229 @@ +/* + * Copyright 2018 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 "BTAudioProviderSession_2_1" + +#include "BluetoothAudioSession_2_1.h" + +#include +#include + +#include "../aidl_session/HidlToAidlMiddleware_2_0.h" +#include "../aidl_session/HidlToAidlMiddleware_2_1.h" + +namespace android { +namespace bluetooth { +namespace audio { +using ::aidl::android::hardware::bluetooth::audio::HidlToAidlMiddleware_2_0; +using ::aidl::android::hardware::bluetooth::audio::HidlToAidlMiddleware_2_1; +using SessionType_2_1 = + ::android::hardware::bluetooth::audio::V2_1::SessionType; +using SessionType_2_0 = + ::android::hardware::bluetooth::audio::V2_0::SessionType; + +::android::hardware::bluetooth::audio::V2_1::AudioConfiguration + BluetoothAudioSession_2_1::invalidSoftwareAudioConfiguration = {}; +::android::hardware::bluetooth::audio::V2_1::AudioConfiguration + BluetoothAudioSession_2_1::invalidOffloadAudioConfiguration = {}; + +namespace { +bool is_2_0_session_type( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) { + if (session_type == SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH || + session_type == SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH) { + return true; + } else { + return false; + } +} + +bool is_unsupported_2_1_session_type( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) { + if (session_type == + SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH || + session_type == + SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH) { + return true; + } else { + return false; + } +} +} // namespace + +BluetoothAudioSession_2_1::BluetoothAudioSession_2_1( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) + : audio_session(BluetoothAudioSessionInstance::GetSessionInstance( + static_cast(session_type))) { + if (is_2_0_session_type(session_type) || + is_unsupported_2_1_session_type(session_type)) { + session_type_2_1_ = (SessionType_2_1::UNKNOWN); + } else { + session_type_2_1_ = (session_type); + } + raw_session_type_ = session_type; +} + +std::shared_ptr +BluetoothAudioSession_2_1::GetAudioSession() { + return audio_session; +} + +// The control function is for the bluetooth_audio module to get the current +// AudioConfiguration +const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration +BluetoothAudioSession_2_1::GetAudioConfig() { + if (HidlToAidlMiddleware_2_0::IsAidlAvailable()) + return HidlToAidlMiddleware_2_1::GetAudioConfig(raw_session_type_); + std::lock_guard guard(audio_session->mutex_); + if (audio_session->IsSessionReady()) { + // If session is unknown it means it should be 2.0 type + if (session_type_2_1_ != SessionType_2_1::UNKNOWN) + return audio_config_2_1_; + + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration toConf; + const AudioConfiguration fromConf = GetAudioSession()->GetAudioConfig(); + // pcmConfig only differs between 2.0 and 2.1 in AudioConfiguration + if (fromConf.getDiscriminator() == + AudioConfiguration::hidl_discriminator::codecConfig) { + toConf.codecConfig(fromConf.codecConfig()); + } else { + toConf.pcmConfig() = { + .sampleRate = static_cast< + ::android::hardware::bluetooth::audio::V2_1::SampleRate>( + fromConf.pcmConfig().sampleRate), + .channelMode = fromConf.pcmConfig().channelMode, + .bitsPerSample = fromConf.pcmConfig().bitsPerSample, + .dataIntervalUs = 0}; + } + return toConf; + } else if (session_type_2_1_ == + SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH) { + return kInvalidOffloadAudioConfiguration; + } else { + return kInvalidSoftwareAudioConfiguration; + } +} + +bool BluetoothAudioSession_2_1::UpdateAudioConfig( + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + audio_config) { + bool is_software_session = + (session_type_2_1_ == SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type_2_1_ == + SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH || + session_type_2_1_ == + SessionType_2_1::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH || + session_type_2_1_ == + SessionType_2_1::LE_AUDIO_SOFTWARE_DECODED_DATAPATH); + bool is_offload_a2dp_session = + (session_type_2_1_ == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH); + auto audio_config_discriminator = audio_config.getDiscriminator(); + bool is_software_audio_config = + (is_software_session && + audio_config_discriminator == + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration:: + hidl_discriminator::pcmConfig); + bool is_a2dp_offload_audio_config = + (is_offload_a2dp_session && + audio_config_discriminator == + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration:: + hidl_discriminator::codecConfig); + if (!is_software_audio_config && !is_a2dp_offload_audio_config) { + return false; + } + audio_config_2_1_ = audio_config; + return true; +} + +// The report function is used to report that the Bluetooth stack has started +// this session without any failure, and will invoke session_changed_cb_ to +// notify those registered bluetooth_audio outputs +void BluetoothAudioSession_2_1::OnSessionStarted( + const sp stack_iface, const DataMQ::Descriptor* dataMQ, + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + audio_config) { + if (session_type_2_1_ == SessionType_2_1::UNKNOWN) { + ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration config; + if (audio_config.getDiscriminator() == + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration:: + hidl_discriminator::codecConfig) { + config.codecConfig(audio_config.codecConfig()); + } else { + auto& tmpPcm = audio_config.pcmConfig(); + config.pcmConfig( + ::android::hardware::bluetooth::audio::V2_0::PcmParameters{ + .sampleRate = static_cast(tmpPcm.sampleRate), + .channelMode = tmpPcm.channelMode, + .bitsPerSample = tmpPcm.bitsPerSample + /*dataIntervalUs is not passed to 2.0 */ + }); + } + + audio_session->OnSessionStarted(stack_iface, dataMQ, config); + } else { + std::lock_guard guard(audio_session->mutex_); + if (stack_iface == nullptr) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_2_1_) + << ", IBluetoothAudioPort Invalid"; + } else if (!UpdateAudioConfig(audio_config)) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_2_1_) + << ", AudioConfiguration=" << toString(audio_config) + << " Invalid"; + } else if (!audio_session->UpdateDataPath(dataMQ)) { + LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_2_1_) + << " DataMQ Invalid"; + audio_config_2_1_ = + (session_type_2_1_ == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH + ? kInvalidOffloadAudioConfiguration + : kInvalidSoftwareAudioConfiguration); + } else { + audio_session->stack_iface_ = stack_iface; + LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_2_1_) + << ", AudioConfiguration=" << toString(audio_config); + audio_session->ReportSessionStatus(); + }; + } +} + +std::unique_ptr + BluetoothAudioSessionInstance_2_1::instance_ptr = + std::unique_ptr( + new BluetoothAudioSessionInstance_2_1()); + +// API to fetch the session of A2DP / Hearing Aid +std::shared_ptr +BluetoothAudioSessionInstance_2_1::GetSessionInstance( + const SessionType_2_1& session_type) { + std::lock_guard guard(instance_ptr->mutex_); + if (!instance_ptr->sessions_map_.empty()) { + auto entry = instance_ptr->sessions_map_.find(session_type); + if (entry != instance_ptr->sessions_map_.end()) { + return entry->second; + } + } + std::shared_ptr session_ptr = + std::make_shared(session_type); + instance_ptr->sessions_map_[session_type] = session_ptr; + return session_ptr; +} + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h new file mode 100644 index 0000000..e634064 --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h @@ -0,0 +1,95 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include +#include "BluetoothAudioSession.h" + +#include +#include + +namespace android { +namespace bluetooth { +namespace audio { + +class BluetoothAudioSession_2_1 { + private: + std::shared_ptr audio_session; + + ::android::hardware::bluetooth::audio::V2_1::SessionType session_type_2_1_; + ::android::hardware::bluetooth::audio::V2_1::SessionType raw_session_type_; + + // audio data configuration for both software and offloading + ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration + audio_config_2_1_; + + bool UpdateAudioConfig( + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + audio_config); + + static ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration + invalidSoftwareAudioConfiguration; + static ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration + invalidOffloadAudioConfiguration; + + public: + BluetoothAudioSession_2_1( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type); + + std::shared_ptr GetAudioSession(); + + // The report function is used to report that the Bluetooth stack has started + // this session without any failure, and will invoke session_changed_cb_ to + // notify those registered bluetooth_audio outputs + void OnSessionStarted( + const sp stack_iface, + const DataMQ::Descriptor* dataMQ, + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration& + audio_config); + + // The control function is for the bluetooth_audio module to get the current + // AudioConfiguration + const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration + GetAudioConfig(); + + static constexpr ::android::hardware::bluetooth::audio::V2_1:: + AudioConfiguration& kInvalidSoftwareAudioConfiguration = + invalidSoftwareAudioConfiguration; + static constexpr ::android::hardware::bluetooth::audio::V2_1:: + AudioConfiguration& kInvalidOffloadAudioConfiguration = + invalidOffloadAudioConfiguration; +}; + +class BluetoothAudioSessionInstance_2_1 { + public: + // The API is to fetch the specified session of A2DP / Hearing Aid + static std::shared_ptr GetSessionInstance( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type); + + private: + static std::unique_ptr instance_ptr; + std::mutex mutex_; + std::unordered_map<::android::hardware::bluetooth::audio::V2_1::SessionType, + std::shared_ptr> + sessions_map_; +}; + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.cpp b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.cpp new file mode 100644 index 0000000..c368197 --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.cpp @@ -0,0 +1,415 @@ +/* + * Copyright 2018 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 "BTAudioProviderSessionCodecsDB" + +#include "BluetoothAudioSupportedCodecsDB.h" + +#include + +namespace android { +namespace bluetooth { +namespace audio { + +using ::android::hardware::bluetooth::audio::V2_0::AacObjectType; +using ::android::hardware::bluetooth::audio::V2_0::AacParameters; +using ::android::hardware::bluetooth::audio::V2_0::AacVariableBitRate; +using ::android::hardware::bluetooth::audio::V2_0::AptxParameters; +using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample; +using ::android::hardware::bluetooth::audio::V2_0::ChannelMode; +using ::android::hardware::bluetooth::audio::V2_0::CodecType; +using ::android::hardware::bluetooth::audio::V2_0::LdacChannelMode; +using ::android::hardware::bluetooth::audio::V2_0::LdacParameters; +using ::android::hardware::bluetooth::audio::V2_0::LdacQualityIndex; +using ::android::hardware::bluetooth::audio::V2_0::SampleRate; +using ::android::hardware::bluetooth::audio::V2_0::SbcAllocMethod; +using ::android::hardware::bluetooth::audio::V2_0::SbcBlockLength; +using ::android::hardware::bluetooth::audio::V2_0::SbcChannelMode; +using ::android::hardware::bluetooth::audio::V2_0::SbcNumSubbands; +using ::android::hardware::bluetooth::audio::V2_0::SbcParameters; + +// Default Supported PCM Parameters +static const PcmParameters kDefaultSoftwarePcmCapabilities = { + .sampleRate = static_cast( + SampleRate::RATE_44100 | SampleRate::RATE_48000 | + SampleRate::RATE_88200 | SampleRate::RATE_96000 | + SampleRate::RATE_16000 | SampleRate::RATE_24000), + .channelMode = + static_cast(ChannelMode::MONO | ChannelMode::STEREO), + .bitsPerSample = static_cast(BitsPerSample::BITS_16 | + BitsPerSample::BITS_24 | + BitsPerSample::BITS_32)}; + +// Default Supported Codecs +// SBC: mSampleRate:(44100), mBitsPerSample:(16), mChannelMode:(MONO|STEREO) +// all blocks | subbands 8 | Loudness +static const SbcParameters kDefaultOffloadSbcCapability = { + .sampleRate = SampleRate::RATE_44100, + .channelMode = static_cast(SbcChannelMode::MONO | + SbcChannelMode::JOINT_STEREO), + .blockLength = static_cast( + SbcBlockLength::BLOCKS_4 | SbcBlockLength::BLOCKS_8 | + SbcBlockLength::BLOCKS_12 | SbcBlockLength::BLOCKS_16), + .numSubbands = SbcNumSubbands::SUBBAND_8, + .allocMethod = SbcAllocMethod::ALLOC_MD_L, + .bitsPerSample = BitsPerSample::BITS_16, + .minBitpool = 2, + .maxBitpool = 53}; + +// AAC: mSampleRate:(44100), mBitsPerSample:(16), mChannelMode:(STEREO) +static const AacParameters kDefaultOffloadAacCapability = { + .objectType = AacObjectType::MPEG2_LC, + .sampleRate = SampleRate::RATE_44100, + .channelMode = ChannelMode::STEREO, + .variableBitRateEnabled = AacVariableBitRate::ENABLED, + .bitsPerSample = BitsPerSample::BITS_16}; + +// LDAC: mSampleRate:(44100|48000|88200|96000), mBitsPerSample:(16|24|32), +// mChannelMode:(DUAL|STEREO) +static const LdacParameters kDefaultOffloadLdacCapability = { + .sampleRate = static_cast( + SampleRate::RATE_44100 | SampleRate::RATE_48000 | + SampleRate::RATE_88200 | SampleRate::RATE_96000), + .channelMode = static_cast(LdacChannelMode::DUAL | + LdacChannelMode::STEREO), + .qualityIndex = LdacQualityIndex::QUALITY_HIGH, + .bitsPerSample = static_cast(BitsPerSample::BITS_16 | + BitsPerSample::BITS_24 | + BitsPerSample::BITS_32)}; + +// aptX: mSampleRate:(44100|48000), mBitsPerSample:(16), mChannelMode:(STEREO) +static const AptxParameters kDefaultOffloadAptxCapability = { + .sampleRate = static_cast(SampleRate::RATE_44100 | + SampleRate::RATE_48000), + .channelMode = ChannelMode::STEREO, + .bitsPerSample = BitsPerSample::BITS_16, +}; + +// aptX HD: mSampleRate:(44100|48000), mBitsPerSample:(24), +// mChannelMode:(STEREO) +static const AptxParameters kDefaultOffloadAptxHdCapability = { + .sampleRate = static_cast(SampleRate::RATE_44100 | + SampleRate::RATE_48000), + .channelMode = ChannelMode::STEREO, + .bitsPerSample = BitsPerSample::BITS_24, +}; + +const std::vector kDefaultOffloadA2dpCodecCapabilities = { + {.codecType = CodecType::SBC, .capabilities = {}}, + {.codecType = CodecType::AAC, .capabilities = {}}, + {.codecType = CodecType::LDAC, .capabilities = {}}, + {.codecType = CodecType::APTX, .capabilities = {}}, + {.codecType = CodecType::APTX_HD, .capabilities = {}}}; + +static bool IsSingleBit(uint32_t bitmasks, uint32_t bitfield) { + bool single = false; + uint32_t test_bit = 0x00000001; + while (test_bit <= bitmasks && test_bit <= bitfield) { + if (bitfield & test_bit && bitmasks & test_bit) { + if (single) return false; + single = true; + } + if (test_bit == 0x80000000) break; + test_bit <<= 1; + } + return single; +} + +static bool IsOffloadSbcConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); +static bool IsOffloadAacConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); +static bool IsOffloadLdacConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); +static bool IsOffloadAptxConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); +static bool IsOffloadAptxHdConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific); + +static bool IsOffloadSbcConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getDiscriminator() != + CodecConfiguration::CodecSpecific::hidl_discriminator::sbcConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << toString(codec_specific); + return false; + } + const SbcParameters sbc_data = codec_specific.sbcConfig(); + if (!IsSingleBit(static_cast(sbc_data.sampleRate), 0xff) || + !IsSingleBit(static_cast(sbc_data.channelMode), 0x0f) || + !IsSingleBit(static_cast(sbc_data.blockLength), 0xf0) || + !IsSingleBit(static_cast(sbc_data.numSubbands), 0x0c) || + !IsSingleBit(static_cast(sbc_data.allocMethod), 0x03) || + !IsSingleBit(static_cast(sbc_data.bitsPerSample), 0x07) || + sbc_data.minBitpool > sbc_data.maxBitpool) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << toString(codec_specific); + return false; + } else if ((sbc_data.sampleRate & kDefaultOffloadSbcCapability.sampleRate) && + (sbc_data.channelMode & + kDefaultOffloadSbcCapability.channelMode) && + (sbc_data.blockLength & + kDefaultOffloadSbcCapability.blockLength) && + (sbc_data.numSubbands & + kDefaultOffloadSbcCapability.numSubbands) && + (sbc_data.allocMethod & + kDefaultOffloadSbcCapability.allocMethod) && + (sbc_data.bitsPerSample & + kDefaultOffloadSbcCapability.bitsPerSample) && + (kDefaultOffloadSbcCapability.minBitpool <= sbc_data.minBitpool && + sbc_data.maxBitpool <= kDefaultOffloadSbcCapability.maxBitpool)) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << toString(codec_specific); + return false; +} + +static bool IsOffloadAacConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getDiscriminator() != + CodecConfiguration::CodecSpecific::hidl_discriminator::aacConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << toString(codec_specific); + return false; + } + const AacParameters aac_data = codec_specific.aacConfig(); + if (!IsSingleBit(static_cast(aac_data.objectType), 0xf0) || + !IsSingleBit(static_cast(aac_data.sampleRate), 0xff) || + !IsSingleBit(static_cast(aac_data.channelMode), 0x03) || + !IsSingleBit(static_cast(aac_data.bitsPerSample), 0x07)) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << toString(codec_specific); + return false; + } else if ((aac_data.objectType & kDefaultOffloadAacCapability.objectType) && + (aac_data.sampleRate & kDefaultOffloadAacCapability.sampleRate) && + (aac_data.channelMode & + kDefaultOffloadAacCapability.channelMode) && + (aac_data.variableBitRateEnabled == AacVariableBitRate::DISABLED || + kDefaultOffloadAacCapability.variableBitRateEnabled == + AacVariableBitRate::ENABLED) && + (aac_data.bitsPerSample & + kDefaultOffloadAacCapability.bitsPerSample)) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << toString(codec_specific); + return false; +} + +static bool IsOffloadLdacConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getDiscriminator() != + CodecConfiguration::CodecSpecific::hidl_discriminator::ldacConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << toString(codec_specific); + return false; + } + const LdacParameters ldac_data = codec_specific.ldacConfig(); + if (!IsSingleBit(static_cast(ldac_data.sampleRate), 0xff) || + !IsSingleBit(static_cast(ldac_data.channelMode), 0x07) || + (ldac_data.qualityIndex > LdacQualityIndex::QUALITY_LOW && + ldac_data.qualityIndex != LdacQualityIndex::QUALITY_ABR) || + !IsSingleBit(static_cast(ldac_data.bitsPerSample), 0x07)) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << toString(codec_specific); + return false; + } else if ((ldac_data.sampleRate & + kDefaultOffloadLdacCapability.sampleRate) && + (ldac_data.channelMode & + kDefaultOffloadLdacCapability.channelMode) && + (ldac_data.bitsPerSample & + kDefaultOffloadLdacCapability.bitsPerSample)) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << toString(codec_specific); + return false; +} + +static bool IsOffloadAptxConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getDiscriminator() != + CodecConfiguration::CodecSpecific::hidl_discriminator::aptxConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << toString(codec_specific); + return false; + } + const AptxParameters aptx_data = codec_specific.aptxConfig(); + if (!IsSingleBit(static_cast(aptx_data.sampleRate), 0xff) || + !IsSingleBit(static_cast(aptx_data.channelMode), 0x03) || + !IsSingleBit(static_cast(aptx_data.bitsPerSample), 0x07)) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << toString(codec_specific); + return false; + } else if ((aptx_data.sampleRate & + kDefaultOffloadAptxCapability.sampleRate) && + (aptx_data.channelMode & + kDefaultOffloadAptxCapability.channelMode) && + (aptx_data.bitsPerSample & + kDefaultOffloadAptxCapability.bitsPerSample)) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << toString(codec_specific); + return false; +} + +static bool IsOffloadAptxHdConfigurationValid( + const CodecConfiguration::CodecSpecific& codec_specific) { + if (codec_specific.getDiscriminator() != + CodecConfiguration::CodecSpecific::hidl_discriminator::aptxConfig) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << toString(codec_specific); + return false; + } + const AptxParameters aptx_data = codec_specific.aptxConfig(); + if (!IsSingleBit(static_cast(aptx_data.sampleRate), 0xff) || + !IsSingleBit(static_cast(aptx_data.channelMode), 0x03) || + !IsSingleBit(static_cast(aptx_data.bitsPerSample), 0x07)) { + LOG(WARNING) << __func__ + << ": Invalid CodecSpecific=" << toString(codec_specific); + return false; + } else if ((aptx_data.sampleRate & + kDefaultOffloadAptxHdCapability.sampleRate) && + (aptx_data.channelMode & + kDefaultOffloadAptxHdCapability.channelMode) && + (aptx_data.bitsPerSample & + kDefaultOffloadAptxHdCapability.bitsPerSample)) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported CodecSpecific=" << toString(codec_specific); + return false; +} + +std::vector GetSoftwarePcmCapabilities() { + return std::vector(1, kDefaultSoftwarePcmCapabilities); +} + +std::vector GetOffloadCodecCapabilities( + const SessionType& session_type) { + if (session_type != SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { + return std::vector(0); + } + std::vector offload_a2dp_codec_capabilities = + kDefaultOffloadA2dpCodecCapabilities; + for (auto& codec_capability : offload_a2dp_codec_capabilities) { + switch (codec_capability.codecType) { + case CodecType::SBC: + codec_capability.capabilities.sbcCapabilities( + kDefaultOffloadSbcCapability); + break; + case CodecType::AAC: + codec_capability.capabilities.aacCapabilities( + kDefaultOffloadAacCapability); + break; + case CodecType::LDAC: + codec_capability.capabilities.ldacCapabilities( + kDefaultOffloadLdacCapability); + break; + case CodecType::APTX: + codec_capability.capabilities.aptxCapabilities( + kDefaultOffloadAptxCapability); + break; + case CodecType::APTX_HD: + codec_capability.capabilities.aptxCapabilities( + kDefaultOffloadAptxHdCapability); + break; + case CodecType::UNKNOWN: + codec_capability = {}; + break; + } + } + return offload_a2dp_codec_capabilities; +} + +bool IsSoftwarePcmConfigurationValid(const PcmParameters& pcm_config) { + if ((pcm_config.sampleRate != SampleRate::RATE_44100 && + pcm_config.sampleRate != SampleRate::RATE_48000 && + pcm_config.sampleRate != SampleRate::RATE_88200 && + pcm_config.sampleRate != SampleRate::RATE_96000 && + pcm_config.sampleRate != SampleRate::RATE_16000 && + pcm_config.sampleRate != SampleRate::RATE_24000) || + (pcm_config.bitsPerSample != BitsPerSample::BITS_16 && + pcm_config.bitsPerSample != BitsPerSample::BITS_24 && + pcm_config.bitsPerSample != BitsPerSample::BITS_32) || + (pcm_config.channelMode != ChannelMode::MONO && + pcm_config.channelMode != ChannelMode::STEREO)) { + LOG(WARNING) << __func__ + << ": Invalid PCM Configuration=" << toString(pcm_config); + return false; + } else if (pcm_config.sampleRate & + kDefaultSoftwarePcmCapabilities.sampleRate && + pcm_config.bitsPerSample & + kDefaultSoftwarePcmCapabilities.bitsPerSample && + pcm_config.channelMode & + kDefaultSoftwarePcmCapabilities.channelMode) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported PCM Configuration=" << toString(pcm_config); + return false; +} + +bool IsOffloadCodecConfigurationValid(const SessionType& session_type, + const CodecConfiguration& codec_config) { + if (session_type != SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { + LOG(ERROR) << __func__ + << ": Invalid SessionType=" << toString(session_type); + return false; + } else if (codec_config.encodedAudioBitrate < 0x00000001 || + 0x00ffffff < codec_config.encodedAudioBitrate) { + LOG(ERROR) << __func__ << ": Unsupported Codec Configuration=" + << toString(codec_config); + return false; + } + const CodecConfiguration::CodecSpecific& codec_specific = codec_config.config; + switch (codec_config.codecType) { + case CodecType::SBC: + if (IsOffloadSbcConfigurationValid(codec_specific)) { + return true; + } + return false; + case CodecType::AAC: + if (IsOffloadAacConfigurationValid(codec_specific)) { + return true; + } + return false; + case CodecType::LDAC: + if (IsOffloadLdacConfigurationValid(codec_specific)) { + return true; + } + return false; + case CodecType::APTX: + if (IsOffloadAptxConfigurationValid(codec_specific)) { + return true; + } + return false; + case CodecType::APTX_HD: + if (IsOffloadAptxHdConfigurationValid(codec_specific)) { + return true; + } + return false; + case CodecType::UNKNOWN: + return false; + } + return false; +} + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.h b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.h new file mode 100644 index 0000000..e71dc8a --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.h @@ -0,0 +1,40 @@ +/* + * Copyright 2018 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. + */ + +#pragma once + +#include + +namespace android { +namespace bluetooth { +namespace audio { + +using ::android::hardware::bluetooth::audio::V2_0::CodecCapabilities; +using ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration; +using ::android::hardware::bluetooth::audio::V2_0::PcmParameters; +using ::android::hardware::bluetooth::audio::V2_0::SessionType; + +std::vector GetSoftwarePcmCapabilities(); +std::vector GetOffloadCodecCapabilities( + const SessionType& session_type); + +bool IsSoftwarePcmConfigurationValid(const PcmParameters& pcm_config); +bool IsOffloadCodecConfigurationValid(const SessionType& session_type, + const CodecConfiguration& codec_config); + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp new file mode 100644 index 0000000..c90ce6d --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2018 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 "BTAudioProviderSessionCodecsDB_2_1" + +#include "BluetoothAudioSupportedCodecsDB_2_1.h" + +#include + +namespace android { +namespace bluetooth { +namespace audio { + +using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample; +using ::android::hardware::bluetooth::audio::V2_0::ChannelMode; + +using SampleRate_2_0 = ::android::hardware::bluetooth::audio::V2_0::SampleRate; +using SampleRate_2_1 = ::android::hardware::bluetooth::audio::V2_1::SampleRate; + +using SessionType_2_1 = + ::android::hardware::bluetooth::audio::V2_1::SessionType; +using SessionType_2_0 = + ::android::hardware::bluetooth::audio::V2_0::SessionType; + +namespace { +bool is_2_0_session_type( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) { + if (session_type == SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH || + session_type == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH || + session_type == SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH) { + return true; + } else { + return false; + } +} +} // namespace + +static const ::android::hardware::bluetooth::audio::V2_1::PcmParameters + kDefaultSoftwarePcmCapabilities_2_1 = { + .sampleRate = static_cast( + SampleRate_2_1::RATE_44100 | SampleRate_2_1::RATE_48000 | + SampleRate_2_1::RATE_88200 | SampleRate_2_1::RATE_96000 | + SampleRate_2_1::RATE_16000 | SampleRate_2_1::RATE_24000), + .channelMode = + static_cast(ChannelMode::MONO | ChannelMode::STEREO), + .bitsPerSample = static_cast(BitsPerSample::BITS_16 | + BitsPerSample::BITS_24 | + BitsPerSample::BITS_32)}; + +std::vector<::android::hardware::bluetooth::audio::V2_1::PcmParameters> +GetSoftwarePcmCapabilities_2_1() { + return std::vector< + ::android::hardware::bluetooth::audio::V2_1::PcmParameters>( + 1, kDefaultSoftwarePcmCapabilities_2_1); +} + +std::vector GetOffloadCodecCapabilities( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type) { + if (is_2_0_session_type(session_type)) { + return GetOffloadCodecCapabilities( + static_cast(session_type)); + } + return std::vector(0); +} + +bool IsSoftwarePcmConfigurationValid_2_1( + const ::android::hardware::bluetooth::audio::V2_1::PcmParameters& + pcm_config) { + if ((pcm_config.sampleRate != SampleRate_2_1::RATE_44100 && + pcm_config.sampleRate != SampleRate_2_1::RATE_48000 && + pcm_config.sampleRate != SampleRate_2_1::RATE_88200 && + pcm_config.sampleRate != SampleRate_2_1::RATE_96000 && + pcm_config.sampleRate != SampleRate_2_1::RATE_16000 && + pcm_config.sampleRate != SampleRate_2_1::RATE_24000) || + (pcm_config.bitsPerSample != BitsPerSample::BITS_16 && + pcm_config.bitsPerSample != BitsPerSample::BITS_24 && + pcm_config.bitsPerSample != BitsPerSample::BITS_32) || + (pcm_config.channelMode != ChannelMode::MONO && + pcm_config.channelMode != ChannelMode::STEREO)) { + LOG(WARNING) << __func__ + << ": Invalid PCM Configuration=" << toString(pcm_config); + return false; + } else if (pcm_config.sampleRate & + kDefaultSoftwarePcmCapabilities_2_1.sampleRate && + pcm_config.bitsPerSample & + kDefaultSoftwarePcmCapabilities_2_1.bitsPerSample && + pcm_config.channelMode & + kDefaultSoftwarePcmCapabilities_2_1.channelMode && + pcm_config.dataIntervalUs != 0) { + return true; + } + LOG(WARNING) << __func__ + << ": Unsupported PCM Configuration=" << toString(pcm_config); + return false; +} + +bool IsOffloadCodecConfigurationValid( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type, + const ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration& + codec_config) { + if (is_2_0_session_type(session_type)) { + return IsOffloadCodecConfigurationValid( + static_cast(session_type), codec_config); + } + + return false; +} + +bool IsOffloadLeAudioConfigurationValid( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type, + const ::android::hardware::bluetooth::audio::V2_1::Lc3CodecConfiguration&) { + + if (session_type != SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH && + session_type != SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH) { + return false; + } + + //TODO: perform checks on le_audio_codec_config once we know supported parameters + + return true; +} + +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h new file mode 100644 index 0000000..a52636c --- /dev/null +++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h @@ -0,0 +1,51 @@ +/* + * Copyright 2020 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. + */ + +#pragma once + +#include "BluetoothAudioSupportedCodecsDB.h" + +#include + +namespace android { +namespace bluetooth { +namespace audio { + +std::vector<::android::hardware::bluetooth::audio::V2_1::PcmParameters> +GetSoftwarePcmCapabilities_2_1(); +std::vector<::android::hardware::bluetooth::audio::V2_0::CodecCapabilities> +GetOffloadCodecCapabilities( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type); + +bool IsSoftwarePcmConfigurationValid_2_1( + const ::android::hardware::bluetooth::audio::V2_1::PcmParameters& + pcm_config); + +bool IsOffloadCodecConfigurationValid( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type, + const ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration& + codec_config); + +bool IsOffloadLeAudioConfigurationValid( + const ::android::hardware::bluetooth::audio::V2_1::SessionType& + session_type, + const ::android::hardware::bluetooth::audio::V2_1::Lc3CodecConfiguration& + le_audio_codec_config); +} // namespace audio +} // namespace bluetooth +} // namespace android diff --git a/bluetooth/bdroid_buildcfg.h b/bluetooth/include/bdroid_buildcfg.h similarity index 100% rename from bluetooth/bdroid_buildcfg.h rename to bluetooth/include/bdroid_buildcfg.h diff --git a/board-base.mk b/board-base.mk index 1ddacaf..2cb5dbc 100644 --- a/board-base.mk +++ b/board-base.mk @@ -1,5 +1,8 @@ TARGET_SYSTEM_PROP := device/phh/treble/system.prop $(TARGET_SYSTEM_PROP) -BOARD_BLUETOOTH_BDROID_BUILDCFG_INCLUDE_DIR := device/phh/treble/bluetooth + +# Bluetooth +BOARD_BLUETOOTH_BDROID_BUILDCFG_INCLUDE_DIR := device/phh/treble/bluetooth/include + TARGET_EXFAT_DRIVER := exfat DEVICE_FRAMEWORK_MANIFEST_FILE := device/phh/treble/framework_manifest.xml diff --git a/sepolicy/file_contexts b/sepolicy/file_contexts index 999ff97..09cde27 100644 --- a/sepolicy/file_contexts +++ b/sepolicy/file_contexts @@ -21,3 +21,4 @@ /efs u:object_r:efs_file:s0 /dev/smcinvoke u:object_r:smcinvoke_device:s0 +/system/bin/hw/android\.hardware\.bluetooth\.audio-service-system u:object_r:hal_audio_sysbta_exec:s0 diff --git a/sepolicy/hal_audio_sysbta.te b/sepolicy/hal_audio_sysbta.te new file mode 100644 index 0000000..d871ed3 --- /dev/null +++ b/sepolicy/hal_audio_sysbta.te @@ -0,0 +1,10 @@ +type hal_audio_sysbta, domain, coredomain; +hal_server_domain(hal_audio_sysbta, hal_audio) + +type hal_audio_sysbta_exec, exec_type, system_file_type, file_type; +init_daemon_domain(hal_audio_sysbta) + +hal_client_domain(hal_audio_sysbta, hal_allocator) + +# allow audioserver to call hal_audio dump with its own fd to retrieve status +allow hal_audio_sysbta audioserver:fifo_file write; diff --git a/sepolicy/service_contexts b/sepolicy/service_contexts index 7680633..73c6722 100644 --- a/sepolicy/service_contexts +++ b/sepolicy/service_contexts @@ -8,3 +8,6 @@ irit u:object_r:radio_service:s0 # MTK IMS mwis u:object_r:radio_service:s0 mtkIms u:object_r:radio_service:s0 + +# Audio AIDL interface +android.hardware.bluetooth.audio.IBluetoothAudioProviderFactory/sysbta u:object_r:hal_audio_service:s0 diff --git a/system.prop b/system.prop index 5152c5d..db7c86d 100644 --- a/system.prop +++ b/system.prop @@ -15,6 +15,8 @@ ro.boot.realme.lockstate=0 #Fixes fingerprint unlock delay persist.wm.enable_remote_keyguard_animation=0 +# Enable system-side generic bluetooth audio HAL +persist.bluetooth.system_audio_hal.enabled=1 # Set commonly-supported Bluetooth profiles to enabled # TODO: Enable BLE Audio profiles on newer vendors bluetooth.profile.asha.central.enabled?=true