aosp aidl bluetooth audio hal implementation
Bug: 228804498
Test: atest VtsHalAudioCoreTargetTest
Change-Id: Id5fb60fe53ead9f5d2e6ecbb9988a23835cf2509
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 4e583a4..b6cfc13 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -82,6 +82,9 @@
"alsa/ModuleAlsa.cpp",
"alsa/StreamAlsa.cpp",
"alsa/Utils.cpp",
+ "bluetooth/DevicePortProxy.cpp",
+ "bluetooth/ModuleBluetooth.cpp",
+ "bluetooth/StreamBluetooth.cpp",
"r_submix/ModuleRemoteSubmix.cpp",
"r_submix/RemoteSubmixUtils.cpp",
"r_submix/SubmixRoute.cpp",
@@ -105,7 +108,9 @@
"audio_policy_engine_configuration_aidl_default",
],
shared_libs: [
+ "android.hardware.bluetooth.audio-V3-ndk",
"libaudio_aidl_conversion_common_ndk",
+ "libbluetooth_audio_session_aidl",
"libmedia_helper",
"libstagefright_foundation",
],
@@ -136,7 +141,9 @@
"libaudioserviceexampleimpl",
],
shared_libs: [
+ "android.hardware.bluetooth.audio-V3-ndk",
"libaudio_aidl_conversion_common_ndk",
+ "libbluetooth_audio_session_aidl",
"libmedia_helper",
"libstagefright_foundation",
],
diff --git a/audio/aidl/default/Configuration.cpp b/audio/aidl/default/Configuration.cpp
index a71c6ea..385ffbf 100644
--- a/audio/aidl/default/Configuration.cpp
+++ b/audio/aidl/default/Configuration.cpp
@@ -561,4 +561,86 @@
return std::make_unique<Configuration>(configuration);
}
+// Bluetooth configuration:
+//
+// Device ports:
+// * "BT A2DP Out", OUT_DEVICE, CONNECTION_BT_A2DP
+// - profile PCM 16-bit; STEREO; 44100, 48000, 88200, 96000
+// * "BT A2DP Headphones", OUT_HEADSET, CONNECTION_BT_A2DP
+// - profile PCM 16-bit; STEREO; 44100, 48000, 88200, 96000
+// * "BT A2DP Speaker", OUT_SPEAKER, CONNECTION_BT_A2DP
+// - profile PCM 16-bit; STEREO; 44100, 48000, 88200, 96000
+// * "BT Hearing Aid Out", OUT_HEARING_AID, CONNECTION_WIRELESS
+// - no profiles specified
+//
+// Mix ports:
+// * "a2dp output", 1 max open, 1 max active stream
+// - no profiles specified
+// * "hearing aid output", 1 max open, 1 max active stream
+// - profile PCM 16-bit; STEREO; 16000, 24000
+//
+// Routes:
+// "a2dp output" -> "BT A2DP Out"
+// "a2dp output" -> "BT A2DP Headphones"
+// "a2dp output" -> "BT A2DP Speaker"
+// "hearing aid output" -> "BT Hearing Aid Out"
+//
+std::unique_ptr<Configuration> getBluetoothConfiguration() {
+ static const Configuration configuration = []() {
+ const std::vector<AudioProfile> standardPcmAudioProfiles = {
+ createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO},
+ {44100, 48000, 88200, 96000})};
+ Configuration c;
+
+ // Device ports
+ AudioPort btOutDevice =
+ createPort(c.nextPortId++, "BT A2DP Out", 0, false,
+ createDeviceExt(AudioDeviceType::OUT_DEVICE, 0,
+ AudioDeviceDescription::CONNECTION_BT_A2DP));
+ c.ports.push_back(btOutDevice);
+ c.connectedProfiles[btOutDevice.id] = standardPcmAudioProfiles;
+
+ AudioPort btOutHeadphone =
+ createPort(c.nextPortId++, "BT A2DP Headphones", 0, false,
+ createDeviceExt(AudioDeviceType::OUT_HEADSET, 0,
+ AudioDeviceDescription::CONNECTION_BT_A2DP));
+ c.ports.push_back(btOutHeadphone);
+ c.connectedProfiles[btOutHeadphone.id] = standardPcmAudioProfiles;
+
+ AudioPort btOutSpeaker =
+ createPort(c.nextPortId++, "BT A2DP Speaker", 0, false,
+ createDeviceExt(AudioDeviceType::OUT_SPEAKER, 0,
+ AudioDeviceDescription::CONNECTION_BT_A2DP));
+ c.ports.push_back(btOutSpeaker);
+ c.connectedProfiles[btOutSpeaker.id] = standardPcmAudioProfiles;
+
+ AudioPort btOutHearingAid =
+ createPort(c.nextPortId++, "BT Hearing Aid Out", 0, false,
+ createDeviceExt(AudioDeviceType::OUT_HEARING_AID, 0,
+ AudioDeviceDescription::CONNECTION_WIRELESS));
+ c.ports.push_back(btOutHearingAid);
+ c.connectedProfiles[btOutHearingAid.id] = std::vector<AudioProfile>(
+ {createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000})});
+
+ // Mix ports
+ AudioPort btInMix =
+ createPort(c.nextPortId++, "a2dp output", 0, true, createPortMixExt(1, 1));
+ c.ports.push_back(btInMix);
+
+ AudioPort btHeadsetInMix =
+ createPort(c.nextPortId++, "hearing aid output", 0, true, createPortMixExt(1, 1));
+ btHeadsetInMix.profiles.push_back(createProfile(
+ PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000, 24000}));
+ c.ports.push_back(btHeadsetInMix);
+
+ c.routes.push_back(createRoute({btInMix}, btOutDevice));
+ c.routes.push_back(createRoute({btInMix}, btOutHeadphone));
+ c.routes.push_back(createRoute({btInMix}, btOutSpeaker));
+ c.routes.push_back(createRoute({btHeadsetInMix}, btOutHearingAid));
+
+ return c;
+ }();
+ return std::make_unique<Configuration>(configuration);
+}
+
} // namespace aidl::android::hardware::audio::core::internal
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index b59bd7c..5625a44 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -26,6 +26,7 @@
#include <error/expected_utils.h>
#include "core-impl/Module.h"
+#include "core-impl/ModuleBluetooth.h"
#include "core-impl/ModulePrimary.h"
#include "core-impl/ModuleRemoteSubmix.h"
#include "core-impl/ModuleStub.h"
@@ -117,6 +118,8 @@
return ndk::SharedRefBase::make<ModuleStub>();
case Type::USB:
return ndk::SharedRefBase::make<ModuleUsb>();
+ case Type::BLUETOOTH:
+ return ndk::SharedRefBase::make<ModuleBluetooth>();
}
}
@@ -134,6 +137,9 @@
case Module::Type::USB:
os << "usb";
break;
+ case Module::Type::BLUETOOTH:
+ os << "bluetooth";
+ break;
}
return os;
}
@@ -301,6 +307,9 @@
case Type::USB:
config = std::move(internal::getUsbConfiguration());
break;
+ case Type::BLUETOOTH:
+ config = std::move(internal::getBluetoothConfiguration());
+ break;
}
return config;
}
diff --git a/audio/aidl/default/android.hardware.audio.service-aidl.xml b/audio/aidl/default/android.hardware.audio.service-aidl.xml
index c9d6314..9db6061 100644
--- a/audio/aidl/default/android.hardware.audio.service-aidl.xml
+++ b/audio/aidl/default/android.hardware.audio.service-aidl.xml
@@ -22,6 +22,11 @@
<hal format="aidl">
<name>android.hardware.audio.core</name>
<version>1</version>
+ <fqname>IModule/bluetooth</fqname>
+ </hal>
+ <hal format="aidl">
+ <name>android.hardware.audio.core</name>
+ <version>1</version>
<fqname>IConfig/default</fqname>
</hal>
</manifest>
diff --git a/audio/aidl/default/bluetooth/DevicePortProxy.cpp b/audio/aidl/default/bluetooth/DevicePortProxy.cpp
new file mode 100644
index 0000000..aabf60f
--- /dev/null
+++ b/audio/aidl/default/bluetooth/DevicePortProxy.cpp
@@ -0,0 +1,562 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AHAL_BluetoothPortProxy"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <audio_utils/primitives.h>
+#include <inttypes.h>
+#include <log/log.h>
+
+#include "core-impl/DevicePortProxy.h"
+
+namespace android::bluetooth::audio::aidl {
+
+namespace {
+
+// The maximum time to wait in std::condition_variable::wait_for()
+constexpr unsigned int kMaxWaitingTimeMs = 4500;
+
+} // namespace
+
+using ::aidl::android::hardware::audio::common::SinkMetadata;
+using ::aidl::android::hardware::audio::common::SourceMetadata;
+using ::aidl::android::hardware::bluetooth::audio::AudioConfiguration;
+using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioSessionControl;
+using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus;
+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 ::aidl::android::hardware::bluetooth::audio::SessionType;
+using ::aidl::android::media::audio::common::AudioDeviceDescription;
+using ::aidl::android::media::audio::common::AudioDeviceType;
+using ::android::base::StringPrintf;
+
+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 << android::base::StringPrintf("%#hhx", state);
+ }
+}
+
+BluetoothAudioPortAidl::BluetoothAudioPortAidl()
+ : mCookie(::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined),
+ mState(BluetoothStreamState::DISABLED),
+ mSessionType(SessionType::UNKNOWN) {}
+
+BluetoothAudioPortAidl::~BluetoothAudioPortAidl() {
+ unregisterPort();
+}
+
+bool BluetoothAudioPortAidl::registerPort(const AudioDeviceDescription& description) {
+ if (inUse()) {
+ LOG(ERROR) << __func__ << debugMessage() << " already in use";
+ return false;
+ }
+
+ if (!initSessionType(description)) return false;
+
+ auto control_result_cb = [port = this](uint16_t cookie, bool start_resp,
+ const BluetoothAudioStatus& status) {
+ (void)start_resp;
+ port->controlResultHandler(cookie, status);
+ };
+ auto session_changed_cb = [port = this](uint16_t cookie) {
+ port->sessionChangedHandler(cookie);
+ };
+ // TODO: Add audio_config_changed_cb
+ PortStatusCallbacks cbacks = {
+ .control_result_cb_ = control_result_cb,
+ .session_changed_cb_ = session_changed_cb,
+ };
+ mCookie = BluetoothAudioSessionControl::RegisterControlResultCback(mSessionType, cbacks);
+ auto isOk = (mCookie != ::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined);
+ if (isOk) {
+ std::lock_guard guard(mCvMutex);
+ mState = BluetoothStreamState::STANDBY;
+ }
+ LOG(DEBUG) << __func__ << debugMessage();
+ return isOk;
+}
+
+bool BluetoothAudioPortAidl::initSessionType(const AudioDeviceDescription& description) {
+ if (description.connection == AudioDeviceDescription::CONNECTION_BT_A2DP &&
+ (description.type == AudioDeviceType::OUT_DEVICE ||
+ description.type == AudioDeviceType::OUT_HEADPHONE ||
+ description.type == AudioDeviceType::OUT_SPEAKER)) {
+ LOG(VERBOSE) << __func__
+ << ": device=AUDIO_DEVICE_OUT_BLUETOOTH_A2DP (HEADPHONES/SPEAKER) ("
+ << description.toString() << ")";
+ mSessionType = SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH;
+ } else if (description.connection == AudioDeviceDescription::CONNECTION_WIRELESS &&
+ description.type == AudioDeviceType::OUT_HEARING_AID) {
+ LOG(VERBOSE) << __func__ << ": device=AUDIO_DEVICE_OUT_HEARING_AID (MEDIA/VOICE) ("
+ << description.toString() << ")";
+ mSessionType = SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH;
+ } else if (description.connection == AudioDeviceDescription::CONNECTION_BT_LE &&
+ description.type == AudioDeviceType::OUT_HEADSET) {
+ LOG(VERBOSE) << __func__ << ": device=AUDIO_DEVICE_OUT_BLE_HEADSET (MEDIA/VOICE) ("
+ << description.toString() << ")";
+ mSessionType = SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH;
+ } else if (description.connection == AudioDeviceDescription::CONNECTION_BT_LE &&
+ description.type == AudioDeviceType::OUT_SPEAKER) {
+ LOG(VERBOSE) << __func__ << ": device=AUDIO_DEVICE_OUT_BLE_SPEAKER (MEDIA) ("
+ << description.toString() << ")";
+ mSessionType = SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH;
+ } else if (description.connection == AudioDeviceDescription::CONNECTION_BT_LE &&
+ description.type == AudioDeviceType::IN_HEADSET) {
+ LOG(VERBOSE) << __func__ << ": device=AUDIO_DEVICE_IN_BLE_HEADSET (VOICE) ("
+ << description.toString() << ")";
+ mSessionType = SessionType::LE_AUDIO_SOFTWARE_DECODING_DATAPATH;
+ } else if (description.connection == AudioDeviceDescription::CONNECTION_BT_LE &&
+ description.type == AudioDeviceType::OUT_BROADCAST) {
+ LOG(VERBOSE) << __func__ << ": device=AUDIO_DEVICE_OUT_BLE_BROADCAST (MEDIA) ("
+ << description.toString() << ")";
+ mSessionType = SessionType::LE_AUDIO_BROADCAST_SOFTWARE_ENCODING_DATAPATH;
+ } else {
+ LOG(ERROR) << __func__ << ": unknown device=" << description.toString();
+ return false;
+ }
+
+ if (!BluetoothAudioSessionControl::IsSessionReady(mSessionType)) {
+ LOG(ERROR) << __func__ << ": device=" << description.toString()
+ << ", session_type=" << toString(mSessionType) << " is not ready";
+ return false;
+ }
+ return true;
+}
+
+void BluetoothAudioPortAidl::unregisterPort() {
+ if (!inUse()) {
+ LOG(WARNING) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return;
+ }
+ BluetoothAudioSessionControl::UnregisterControlResultCback(mSessionType, mCookie);
+ mCookie = ::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined;
+ LOG(VERBOSE) << __func__ << debugMessage() << " port unregistered";
+}
+
+void BluetoothAudioPortAidl::controlResultHandler(uint16_t cookie,
+ const BluetoothAudioStatus& status) {
+ std::lock_guard guard(mCvMutex);
+ if (!inUse()) {
+ LOG(ERROR) << "control_result_cb: BluetoothAudioPortAidl is not in use";
+ return;
+ }
+ if (mCookie != cookie) {
+ LOG(ERROR) << "control_result_cb: proxy of device port (cookie="
+ << StringPrintf("%#hx", cookie) << ") is corrupted";
+ return;
+ }
+ BluetoothStreamState previous_state = mState;
+ LOG(INFO) << "control_result_cb:" << debugMessage() << ", 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) {
+ mState = BluetoothStreamState::STANDBY;
+ } else {
+ LOG(WARNING) << StringPrintf(
+ "control_result_cb: status=%s failure for session_type= %s, cookie=%#hx, "
+ "previous_state=%#hhx",
+ toString(status).c_str(), toString(mSessionType).c_str(), mCookie,
+ previous_state);
+ }
+ break;
+ case BluetoothStreamState::STARTING:
+ if (status == BluetoothAudioStatus::SUCCESS) {
+ mState = BluetoothStreamState::STARTED;
+ } else {
+ // Set to standby since the stack may be busy switching between outputs
+ LOG(WARNING) << StringPrintf(
+ "control_result_cb: status=%s failure for session_type= %s, cookie=%#hx, "
+ "previous_state=%#hhx",
+ toString(status).c_str(), toString(mSessionType).c_str(), mCookie,
+ previous_state);
+ mState = BluetoothStreamState::STANDBY;
+ }
+ break;
+ case BluetoothStreamState::SUSPENDING:
+ if (status == BluetoothAudioStatus::SUCCESS) {
+ mState = BluetoothStreamState::STANDBY;
+ } else {
+ // It will be failed if the headset is disconnecting, and set to disable
+ // to wait for re-init again
+ LOG(WARNING) << StringPrintf(
+ "control_result_cb: status=%s failure for session_type= %s, cookie=%#hx, "
+ "previous_state=%#hhx",
+ toString(status).c_str(), toString(mSessionType).c_str(), mCookie,
+ previous_state);
+ mState = BluetoothStreamState::DISABLED;
+ }
+ break;
+ default:
+ LOG(ERROR) << "control_result_cb: unexpected previous_state="
+ << StringPrintf(
+ "control_result_cb: status=%s failure for session_type= %s, "
+ "cookie=%#hx, previous_state=%#hhx",
+ toString(status).c_str(), toString(mSessionType).c_str(), mCookie,
+ previous_state);
+ return;
+ }
+ mInternalCv.notify_all();
+}
+
+void BluetoothAudioPortAidl::sessionChangedHandler(uint16_t cookie) {
+ std::lock_guard guard(mCvMutex);
+ if (!inUse()) {
+ LOG(ERROR) << "session_changed_cb: BluetoothAudioPortAidl is not in use";
+ return;
+ }
+ if (mCookie != cookie) {
+ LOG(ERROR) << "session_changed_cb: proxy of device port (cookie="
+ << StringPrintf("%#hx", cookie) << ") is corrupted";
+ return;
+ }
+ BluetoothStreamState previous_state = mState;
+ LOG(VERBOSE) << "session_changed_cb:" << debugMessage()
+ << ", previous_state=" << previous_state;
+ mState = BluetoothStreamState::DISABLED;
+ mInternalCv.notify_all();
+}
+
+bool BluetoothAudioPortAidl::inUse() const {
+ return (mCookie != ::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined);
+}
+
+bool BluetoothAudioPortAidl::getPreferredDataIntervalUs(size_t* interval_us) const {
+ if (!interval_us) {
+ LOG(ERROR) << __func__ << ": bad input arg";
+ return false;
+ }
+
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return false;
+ }
+
+ const AudioConfiguration& hal_audio_cfg =
+ BluetoothAudioSessionControl::GetAudioConfig(mSessionType);
+ if (hal_audio_cfg.getTag() != AudioConfiguration::pcmConfig) {
+ LOG(ERROR) << __func__ << ": unsupported audio cfg tag";
+ return false;
+ }
+
+ *interval_us = hal_audio_cfg.get<AudioConfiguration::pcmConfig>().dataIntervalUs;
+ return true;
+}
+
+bool BluetoothAudioPortAidl::loadAudioConfig(PcmConfiguration* audio_cfg) const {
+ if (!audio_cfg) {
+ LOG(ERROR) << __func__ << ": bad input arg";
+ return false;
+ }
+
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return false;
+ }
+
+ const AudioConfiguration& hal_audio_cfg =
+ BluetoothAudioSessionControl::GetAudioConfig(mSessionType);
+ if (hal_audio_cfg.getTag() != AudioConfiguration::pcmConfig) {
+ LOG(ERROR) << __func__ << ": unsupported audio cfg tag";
+ return false;
+ }
+ *audio_cfg = hal_audio_cfg.get<AudioConfiguration::pcmConfig>();
+ LOG(VERBOSE) << __func__ << debugMessage() << ", state*=" << getState() << ", PcmConfig=["
+ << audio_cfg->toString() << "]";
+ if (audio_cfg->channelMode == ChannelMode::UNKNOWN) {
+ return false;
+ }
+ return true;
+}
+
+bool BluetoothAudioPortAidl::condWaitState(BluetoothStreamState state) {
+ const auto waitTime = std::chrono::milliseconds(kMaxWaitingTimeMs);
+ std::unique_lock lock(mCvMutex);
+ base::ScopedLockAssertion lock_assertion(mCvMutex);
+ switch (state) {
+ case BluetoothStreamState::STARTING: {
+ LOG(VERBOSE) << __func__ << debugMessage() << " waiting for STARTED";
+ mInternalCv.wait_for(lock, waitTime, [this] {
+ base::ScopedLockAssertion lock_assertion(mCvMutex);
+ return mState != BluetoothStreamState::STARTING;
+ });
+ return mState == BluetoothStreamState::STARTED;
+ }
+ case BluetoothStreamState::SUSPENDING: {
+ LOG(VERBOSE) << __func__ << debugMessage() << " waiting for SUSPENDED";
+ mInternalCv.wait_for(lock, waitTime, [this] {
+ base::ScopedLockAssertion lock_assertion(mCvMutex);
+ return mState != BluetoothStreamState::SUSPENDING;
+ });
+ return mState == BluetoothStreamState::STANDBY;
+ }
+ default:
+ LOG(WARNING) << __func__ << debugMessage() << " waiting for KNOWN";
+ return false;
+ }
+ return false;
+}
+
+bool BluetoothAudioPortAidl::start() {
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return false;
+ }
+ LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << getState()
+ << ", mono=" << (mIsStereoToMono ? "true" : "false") << " request";
+
+ {
+ std::unique_lock lock(mCvMutex);
+ base::ScopedLockAssertion lock_assertion(mCvMutex);
+ if (mState == BluetoothStreamState::STARTED) {
+ return true; // nop, return
+ } else if (mState == BluetoothStreamState::SUSPENDING ||
+ mState == BluetoothStreamState::STARTING) {
+ /* If port is in transient state, give some time to respond */
+ auto state_ = mState;
+ lock.unlock();
+ if (!condWaitState(state_)) {
+ LOG(ERROR) << __func__ << debugMessage() << ", state=" << getState() << " failure";
+ return false;
+ }
+ }
+ }
+
+ bool retval = false;
+ {
+ std::unique_lock lock(mCvMutex);
+ base::ScopedLockAssertion lock_assertion(mCvMutex);
+ if (mState == BluetoothStreamState::STARTED) {
+ retval = true;
+ } else if (mState == BluetoothStreamState::STANDBY) {
+ mState = BluetoothStreamState::STARTING;
+ lock.unlock();
+ if (BluetoothAudioSessionControl::StartStream(mSessionType)) {
+ retval = condWaitState(BluetoothStreamState::STARTING);
+ } else {
+ LOG(ERROR) << __func__ << debugMessage() << ", state=" << getState()
+ << " Hal fails";
+ }
+ }
+ }
+
+ if (retval) {
+ LOG(INFO) << __func__ << debugMessage() << ", state=" << getState()
+ << ", mono=" << (mIsStereoToMono ? "true" : "false") << " done";
+ } else {
+ LOG(ERROR) << __func__ << debugMessage() << ", state=" << getState() << " failure";
+ }
+
+ return retval; // false if any failure like timeout
+}
+
+bool BluetoothAudioPortAidl::suspend() {
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return false;
+ }
+ LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << getState() << " request";
+
+ {
+ std::unique_lock lock(mCvMutex);
+ base::ScopedLockAssertion lock_assertion(mCvMutex);
+ if (mState == BluetoothStreamState::STANDBY) {
+ return true; // nop, return
+ } else if (mState == BluetoothStreamState::SUSPENDING ||
+ mState == BluetoothStreamState::STARTING) {
+ /* If port is in transient state, give some time to respond */
+ auto state_ = mState;
+ lock.unlock();
+ if (!condWaitState(state_)) {
+ LOG(ERROR) << __func__ << debugMessage() << ", state=" << getState() << " failure";
+ return false;
+ }
+ }
+ }
+
+ bool retval = false;
+ {
+ std::unique_lock lock(mCvMutex);
+ base::ScopedLockAssertion lock_assertion(mCvMutex);
+ if (mState == BluetoothStreamState::STANDBY) {
+ retval = true;
+ } else if (mState == BluetoothStreamState::STARTED) {
+ mState = BluetoothStreamState::SUSPENDING;
+ lock.unlock();
+ if (BluetoothAudioSessionControl::SuspendStream(mSessionType)) {
+ retval = condWaitState(BluetoothStreamState::SUSPENDING);
+ } else {
+ LOG(ERROR) << __func__ << debugMessage() << ", state=" << getState()
+ << " Hal fails";
+ }
+ }
+ }
+
+ if (retval) {
+ LOG(INFO) << __func__ << debugMessage() << ", state=" << getState() << " done";
+ } else {
+ LOG(ERROR) << __func__ << debugMessage() << ", state=" << getState() << " failure";
+ }
+
+ return retval; // false if any failure like timeout
+}
+
+void BluetoothAudioPortAidl::stop() {
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return;
+ }
+ std::lock_guard guard(mCvMutex);
+ LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << getState() << " request";
+ if (mState != BluetoothStreamState::DISABLED) {
+ BluetoothAudioSessionControl::StopStream(mSessionType);
+ mState = BluetoothStreamState::DISABLED;
+ }
+ LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << getState() << " done";
+}
+
+size_t BluetoothAudioPortAidlOut::writeData(const void* buffer, size_t bytes) const {
+ if (!buffer) {
+ LOG(ERROR) << __func__ << ": bad input arg";
+ return 0;
+ }
+
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return 0;
+ }
+
+ if (!mIsStereoToMono) {
+ return BluetoothAudioSessionControl::OutWritePcmData(mSessionType, 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<const int16_t*>(buffer);
+ std::unique_ptr<int16_t[]> 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.
+ auto totalWrite = BluetoothAudioSessionControl::OutWritePcmData(mSessionType, dst.get(),
+ write_frames * 2);
+ return totalWrite * 2;
+}
+
+size_t BluetoothAudioPortAidlIn::readData(void* buffer, size_t bytes) const {
+ if (!buffer) {
+ LOG(ERROR) << __func__ << ": bad input arg";
+ return 0;
+ }
+
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return 0;
+ }
+
+ return BluetoothAudioSessionControl::InReadPcmData(mSessionType, buffer, bytes);
+}
+
+bool BluetoothAudioPortAidl::getPresentationPosition(
+ PresentationPosition& presentation_position) const {
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return false;
+ }
+ bool retval = BluetoothAudioSessionControl::GetPresentationPosition(mSessionType,
+ presentation_position);
+ LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << getState()
+ << presentation_position.toString();
+
+ return retval;
+}
+
+bool BluetoothAudioPortAidl::updateSourceMetadata(const SourceMetadata& source_metadata) const {
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return false;
+ }
+ LOG(DEBUG) << __func__ << debugMessage() << ", state=" << getState() << ", "
+ << source_metadata.tracks.size() << " track(s)";
+ if (source_metadata.tracks.size() == 0) return true;
+ return BluetoothAudioSessionControl::UpdateSourceMetadata(mSessionType, source_metadata);
+}
+
+bool BluetoothAudioPortAidl::updateSinkMetadata(const SinkMetadata& sink_metadata) const {
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return false;
+ }
+ LOG(DEBUG) << __func__ << debugMessage() << ", state=" << getState() << ", "
+ << sink_metadata.tracks.size() << " track(s)";
+ if (sink_metadata.tracks.size() == 0) return true;
+ return BluetoothAudioSessionControl::UpdateSinkMetadata(mSessionType, sink_metadata);
+}
+
+BluetoothStreamState BluetoothAudioPortAidl::getState() const {
+ return mState;
+}
+
+bool BluetoothAudioPortAidl::setState(BluetoothStreamState state) {
+ if (!inUse()) {
+ LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use";
+ return false;
+ }
+ std::lock_guard guard(mCvMutex);
+ LOG(DEBUG) << __func__ << ": BluetoothAudioPortAidl old state = " << mState
+ << " new state = " << state;
+ mState = state;
+ return true;
+}
+
+bool BluetoothAudioPortAidl::isA2dp() const {
+ return mSessionType == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH ||
+ mSessionType == SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH;
+}
+
+bool BluetoothAudioPortAidl::isLeAudio() const {
+ return mSessionType == SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH ||
+ mSessionType == SessionType::LE_AUDIO_SOFTWARE_DECODING_DATAPATH ||
+ mSessionType == SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH ||
+ mSessionType == SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH ||
+ mSessionType == SessionType::LE_AUDIO_BROADCAST_SOFTWARE_ENCODING_DATAPATH ||
+ mSessionType == SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH;
+}
+
+std::string BluetoothAudioPortAidl::debugMessage() const {
+ return StringPrintf(": session_type=%s, cookie=%#hx", toString(mSessionType).c_str(), mCookie);
+}
+
+} // namespace android::bluetooth::audio::aidl
diff --git a/audio/aidl/default/bluetooth/ModuleBluetooth.cpp b/audio/aidl/default/bluetooth/ModuleBluetooth.cpp
new file mode 100644
index 0000000..bb11f0a
--- /dev/null
+++ b/audio/aidl/default/bluetooth/ModuleBluetooth.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AHAL_ModuleBluetooth"
+
+#include <android-base/logging.h>
+
+#include "core-impl/ModuleBluetooth.h"
+#include "core-impl/StreamBluetooth.h"
+
+namespace aidl::android::hardware::audio::core {
+
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::MicrophoneInfo;
+
+ndk::ScopedAStatus ModuleBluetooth::getMicMute(bool* _aidl_return __unused) {
+ LOG(DEBUG) << __func__ << ": is not supported";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus ModuleBluetooth::setMicMute(bool in_mute __unused) {
+ LOG(DEBUG) << __func__ << ": is not supported";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus ModuleBluetooth::createInputStream(
+ StreamContext&& context, const SinkMetadata& sinkMetadata,
+ const std::vector<MicrophoneInfo>& microphones, std::shared_ptr<StreamIn>* result) {
+ return createStreamInstance<StreamInBluetooth>(result, std::move(context), sinkMetadata,
+ microphones);
+}
+
+ndk::ScopedAStatus ModuleBluetooth::createOutputStream(
+ StreamContext&& context, const SourceMetadata& sourceMetadata,
+ const std::optional<AudioOffloadInfo>& offloadInfo, std::shared_ptr<StreamOut>* result) {
+ return createStreamInstance<StreamOutBluetooth>(result, std::move(context), sourceMetadata,
+ offloadInfo);
+}
+
+ndk::ScopedAStatus ModuleBluetooth::onMasterMuteChanged(bool) {
+ LOG(DEBUG) << __func__ << ": is not supported";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus ModuleBluetooth::onMasterVolumeChanged(float) {
+ LOG(DEBUG) << __func__ << ": is not supported";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/bluetooth/StreamBluetooth.cpp b/audio/aidl/default/bluetooth/StreamBluetooth.cpp
new file mode 100644
index 0000000..dfccb0e
--- /dev/null
+++ b/audio/aidl/default/bluetooth/StreamBluetooth.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AHAL_StreamBluetooth"
+
+#include <Utils.h>
+#include <android-base/logging.h>
+#include <audio_utils/clock.h>
+
+#include "BluetoothAudioSessionControl.h"
+#include "core-impl/StreamBluetooth.h"
+
+namespace aidl::android::hardware::audio::core {
+
+using ::aidl::android::hardware::audio::common::SinkMetadata;
+using ::aidl::android::hardware::audio::common::SourceMetadata;
+using ::aidl::android::hardware::audio::core::VendorParameter;
+using ::aidl::android::hardware::bluetooth::audio::ChannelMode;
+using ::aidl::android::hardware::bluetooth::audio::PcmConfiguration;
+using ::aidl::android::hardware::bluetooth::audio::PresentationPosition;
+using ::aidl::android::media::audio::common::AudioChannelLayout;
+using ::aidl::android::media::audio::common::AudioDevice;
+using ::aidl::android::media::audio::common::AudioDeviceAddress;
+using ::aidl::android::media::audio::common::AudioFormatDescription;
+using ::aidl::android::media::audio::common::AudioFormatType;
+using ::aidl::android::media::audio::common::AudioOffloadInfo;
+using ::aidl::android::media::audio::common::MicrophoneDynamicInfo;
+using ::aidl::android::media::audio::common::MicrophoneInfo;
+using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
+using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlIn;
+using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
+using ::android::bluetooth::audio::aidl::BluetoothStreamState;
+
+constexpr int kBluetoothDefaultInputBufferMs = 20;
+constexpr int kBluetoothDefaultOutputBufferMs = 10;
+// constexpr int kBluetoothSpatializerOutputBufferMs = 10;
+
+size_t getFrameCount(uint64_t durationUs, uint32_t sampleRate) {
+ return (durationUs * sampleRate) / 1000000;
+}
+
+// pcm configuration params are not really used by the module
+StreamBluetooth::StreamBluetooth(StreamContext* context, const Metadata& metadata)
+ : StreamCommonImpl(context, metadata),
+ mSampleRate(getContext().getSampleRate()),
+ mChannelLayout(getContext().getChannelLayout()),
+ mFormat(getContext().getFormat()),
+ mFrameSizeBytes(getContext().getFrameSize()),
+ mIsInput(isInput(metadata)) {
+ mPreferredDataIntervalUs =
+ mIsInput ? kBluetoothDefaultInputBufferMs : kBluetoothDefaultOutputBufferMs;
+ mPreferredFrameCount = getFrameCount(mPreferredDataIntervalUs, mSampleRate);
+ mIsInitialized = false;
+ mIsReadyToClose = false;
+}
+
+::android::status_t StreamBluetooth::init() {
+ return ::android::OK; // defering this till we get AudioDeviceDescription
+}
+
+const StreamCommonInterface::ConnectedDevices& StreamBluetooth::getConnectedDevices() const {
+ std::lock_guard guard(mLock);
+ return StreamCommonImpl::getConnectedDevices();
+}
+
+ndk::ScopedAStatus StreamBluetooth::setConnectedDevices(
+ const std::vector<AudioDevice>& connectedDevices) {
+ if (mIsInput && connectedDevices.size() > 1) {
+ LOG(ERROR) << __func__ << ": wrong device size(" << connectedDevices.size()
+ << ") for input stream";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ }
+ for (const auto& connectedDevice : connectedDevices) {
+ if (connectedDevice.address.getTag() != AudioDeviceAddress::mac) {
+ LOG(ERROR) << __func__ << ": bad device address" << connectedDevice.address.toString();
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ }
+ std::lock_guard guard(mLock);
+ RETURN_STATUS_IF_ERROR(StreamCommonImpl::setConnectedDevices(connectedDevices));
+ mIsInitialized = false; // updated connected device list, need initialization
+ return ndk::ScopedAStatus::ok();
+}
+
+::android::status_t StreamBluetooth::drain(StreamDescriptor::DrainMode) {
+ usleep(1000);
+ return ::android::OK;
+}
+
+::android::status_t StreamBluetooth::flush() {
+ usleep(1000);
+ return ::android::OK;
+}
+
+::android::status_t StreamBluetooth::pause() {
+ return standby();
+}
+
+::android::status_t StreamBluetooth::transfer(void* buffer, size_t frameCount,
+ size_t* actualFrameCount, int32_t* latencyMs) {
+ std::lock_guard guard(mLock);
+ if (!mIsInitialized || mIsReadyToClose) {
+ // 'setConnectedDevices' has been called or stream is ready to close, so no transfers
+ *actualFrameCount = 0;
+ *latencyMs = StreamDescriptor::LATENCY_UNKNOWN;
+ return ::android::OK;
+ }
+ *actualFrameCount = 0;
+ *latencyMs = 0;
+ for (auto proxy : mBtDeviceProxies) {
+ if (!proxy->start()) {
+ LOG(ERROR) << __func__ << ": state = " << proxy->getState() << " failed to start ";
+ return -EIO;
+ }
+ const size_t fc = std::min(frameCount, mPreferredFrameCount);
+ const size_t bytesToTransfer = fc * mFrameSizeBytes;
+ if (mIsInput) {
+ const size_t totalRead = proxy->readData(buffer, bytesToTransfer);
+ *actualFrameCount = std::max(*actualFrameCount, totalRead / mFrameSizeBytes);
+ } else {
+ const size_t totalWrite = proxy->writeData(buffer, bytesToTransfer);
+ *actualFrameCount = std::max(*actualFrameCount, totalWrite / mFrameSizeBytes);
+ }
+ PresentationPosition presentation_position;
+ if (!proxy->getPresentationPosition(presentation_position)) {
+ LOG(ERROR) << __func__ << ": getPresentationPosition returned error ";
+ return ::android::UNKNOWN_ERROR;
+ }
+ *latencyMs =
+ std::max(*latencyMs, (int32_t)(presentation_position.remoteDeviceAudioDelayNanos /
+ NANOS_PER_MILLISECOND));
+ }
+ return ::android::OK;
+}
+
+::android::status_t StreamBluetooth::initialize() {
+ if (!::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession::IsAidlAvailable()) {
+ LOG(ERROR) << __func__ << ": IBluetoothAudioProviderFactory service not available";
+ return ::android::UNKNOWN_ERROR;
+ }
+ if (StreamCommonImpl::getConnectedDevices().empty()) {
+ LOG(ERROR) << __func__ << ", has no connected devices";
+ return ::android::NO_INIT;
+ }
+ // unregister older proxies (if any)
+ for (auto proxy : mBtDeviceProxies) {
+ proxy->stop();
+ proxy->unregisterPort();
+ }
+ mBtDeviceProxies.clear();
+ for (auto it = StreamCommonImpl::getConnectedDevices().begin();
+ it != StreamCommonImpl::getConnectedDevices().end(); ++it) {
+ std::shared_ptr<BluetoothAudioPortAidl> proxy =
+ mIsInput ? std::shared_ptr<BluetoothAudioPortAidl>(
+ std::make_shared<BluetoothAudioPortAidlIn>())
+ : std::shared_ptr<BluetoothAudioPortAidl>(
+ std::make_shared<BluetoothAudioPortAidlOut>());
+ if (proxy->registerPort(it->type)) {
+ LOG(ERROR) << __func__ << ": cannot init HAL";
+ return ::android::UNKNOWN_ERROR;
+ }
+ PcmConfiguration config;
+ if (!proxy->loadAudioConfig(&config)) {
+ LOG(ERROR) << __func__ << ": state=" << proxy->getState()
+ << " failed to get audio config";
+ return ::android::UNKNOWN_ERROR;
+ }
+ // TODO: Ensure minimum duration for spatialized output?
+ // WAR to support Mono / 16 bits per sample as the Bluetooth stack required
+ if (!mIsInput && config.channelMode == ChannelMode::MONO && config.bitsPerSample == 16) {
+ proxy->forcePcmStereoToMono(true);
+ config.channelMode = ChannelMode::STEREO;
+ LOG(INFO) << __func__ << ": force channels = to be AUDIO_CHANNEL_OUT_STEREO";
+ }
+ if (!checkConfigParams(config)) {
+ LOG(ERROR) << __func__ << " checkConfigParams failed";
+ return ::android::UNKNOWN_ERROR;
+ }
+ mBtDeviceProxies.push_back(std::move(proxy));
+ }
+ mIsInitialized = true;
+ return ::android::OK;
+}
+
+bool StreamBluetooth::checkConfigParams(
+ ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& config) {
+ if ((int)mSampleRate != config.sampleRateHz) {
+ LOG(ERROR) << __func__ << ": Sample Rate mismatch, stream val = " << mSampleRate
+ << " hal val = " << config.sampleRateHz;
+ return false;
+ }
+ auto channelCount = aidl::android::hardware::audio::common::getChannelCount(mChannelLayout);
+ if ((config.channelMode == ChannelMode::MONO && channelCount != 1) ||
+ (config.channelMode == ChannelMode::STEREO && channelCount != 2)) {
+ LOG(ERROR) << __func__ << ": Channel count mismatch, stream val = " << channelCount
+ << " hal val = " << toString(config.channelMode);
+ return false;
+ }
+ if (mFormat.type != AudioFormatType::PCM) {
+ LOG(ERROR) << __func__ << ": unexpected format type "
+ << aidl::android::media::audio::common::toString(mFormat.type);
+ return false;
+ }
+ int8_t bps = aidl::android::hardware::audio::common::getPcmSampleSizeInBytes(mFormat.pcm) * 8;
+ if (bps != config.bitsPerSample) {
+ LOG(ERROR) << __func__ << ": bits per sample mismatch, stream val = " << bps
+ << " hal val = " << config.bitsPerSample;
+ return false;
+ }
+ if (config.dataIntervalUs > 0) {
+ mPreferredDataIntervalUs =
+ std::min((int32_t)mPreferredDataIntervalUs, config.dataIntervalUs);
+ mPreferredFrameCount = getFrameCount(mPreferredDataIntervalUs, mSampleRate);
+ }
+ return true;
+}
+
+ndk::ScopedAStatus StreamBluetooth::prepareToClose() {
+ std::lock_guard guard(mLock);
+ mIsReadyToClose = true;
+ return ndk::ScopedAStatus::ok();
+}
+
+::android::status_t StreamBluetooth::standby() {
+ std::lock_guard guard(mLock);
+ if (!mIsInitialized) {
+ if (auto status = initialize(); status != ::android::OK) return status;
+ }
+ for (auto proxy : mBtDeviceProxies) {
+ if (!proxy->suspend()) {
+ LOG(ERROR) << __func__ << ": state = " << proxy->getState() << " failed to stand by ";
+ return -EIO;
+ }
+ }
+ return ::android::OK;
+}
+
+::android::status_t StreamBluetooth::start() {
+ std::lock_guard guard(mLock);
+ if (!mIsInitialized) return initialize();
+ return ::android::OK;
+}
+
+void StreamBluetooth::shutdown() {
+ std::lock_guard guard(mLock);
+ for (auto proxy : mBtDeviceProxies) {
+ proxy->stop();
+ proxy->unregisterPort();
+ }
+ mBtDeviceProxies.clear();
+}
+
+ndk::ScopedAStatus StreamBluetooth::updateMetadataCommon(const Metadata& metadata) {
+ std::lock_guard guard(mLock);
+ if (!mIsInitialized) return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ bool isOk = true;
+ if (isInput(metadata)) {
+ isOk = mBtDeviceProxies[0]->updateSinkMetadata(std::get<SinkMetadata>(metadata));
+ } else {
+ for (auto proxy : mBtDeviceProxies) {
+ if (!proxy->updateSourceMetadata(std::get<SourceMetadata>(metadata))) isOk = false;
+ }
+ }
+ return isOk ? ndk::ScopedAStatus::ok()
+ : ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+StreamInBluetooth::StreamInBluetooth(StreamContext&& context, const SinkMetadata& sinkMetadata,
+ const std::vector<MicrophoneInfo>& microphones)
+ : StreamIn(std::move(context), microphones),
+ StreamBluetooth(&(StreamIn::mContext), sinkMetadata) {}
+
+ndk::ScopedAStatus StreamInBluetooth::getActiveMicrophones(
+ std::vector<MicrophoneDynamicInfo>* _aidl_return __unused) {
+ LOG(DEBUG) << __func__ << ": not supported";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+StreamOutBluetooth::StreamOutBluetooth(StreamContext&& context,
+ const SourceMetadata& sourceMetadata,
+ const std::optional<AudioOffloadInfo>& offloadInfo)
+ : StreamOut(std::move(context), offloadInfo),
+ StreamBluetooth(&(StreamOut::mContext), sourceMetadata) {}
+
+} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/Configuration.h b/audio/aidl/default/include/core-impl/Configuration.h
index 25bf7af..6277c38 100644
--- a/audio/aidl/default/include/core-impl/Configuration.h
+++ b/audio/aidl/default/include/core-impl/Configuration.h
@@ -47,5 +47,6 @@
std::unique_ptr<Configuration> getRSubmixConfiguration();
std::unique_ptr<Configuration> getStubConfiguration();
std::unique_ptr<Configuration> getUsbConfiguration();
+std::unique_ptr<Configuration> getBluetoothConfiguration();
} // namespace aidl::android::hardware::audio::core::internal
diff --git a/audio/aidl/default/include/core-impl/DevicePortProxy.h b/audio/aidl/default/include/core-impl/DevicePortProxy.h
new file mode 100644
index 0000000..72ffaad
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/DevicePortProxy.h
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <condition_variable>
+#include <mutex>
+
+#include <android-base/thread_annotations.h>
+
+#include <aidl/android/hardware/audio/common/SinkMetadata.h>
+#include <aidl/android/hardware/audio/common/SourceMetadata.h>
+#include <aidl/android/hardware/bluetooth/audio/BluetoothAudioStatus.h>
+#include <aidl/android/hardware/bluetooth/audio/PcmConfiguration.h>
+#include <aidl/android/hardware/bluetooth/audio/SessionType.h>
+#include <aidl/android/media/audio/common/AudioDeviceDescription.h>
+
+#include "BluetoothAudioSessionControl.h"
+
+namespace android::bluetooth::audio::aidl {
+
+enum class BluetoothStreamState : uint8_t {
+ DISABLED = 0, // This stream is closing or Bluetooth profiles (A2DP/LE) is disabled
+ STANDBY,
+ STARTING,
+ STARTED,
+ SUSPENDING,
+ UNKNOWN,
+};
+
+std::ostream& operator<<(std::ostream& os, const BluetoothStreamState& state);
+
+/**
+ * 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, getState() of DevicePortProxy is only used for
+ * verbose logging, it is not locked, so the state may not be synchronized.
+ */
+class BluetoothAudioPort {
+ public:
+ BluetoothAudioPort() = default;
+ virtual ~BluetoothAudioPort() = default;
+
+ /**
+ * Fetch output control / data path of BluetoothAudioPort and setup
+ * callbacks into BluetoothAudioProvider. If registerPort() returns false, the audio
+ * HAL must delete this BluetoothAudioPort and return EINVAL to caller
+ */
+ virtual bool registerPort(
+ const ::aidl::android::media::audio::common::AudioDeviceDescription&) = 0;
+
+ /**
+ * Unregister this BluetoothAudioPort from BluetoothAudioSessionControl.
+ * Audio HAL must delete this BluetoothAudioPort after calling this.
+ */
+ virtual void unregisterPort() = 0;
+
+ /**
+ * 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(
+ ::aidl::android::hardware::bluetooth::audio::PcmConfiguration*) const = 0;
+
+ /**
+ * WAR to support Mono mode / 16 bits per sample
+ */
+ virtual void forcePcmStereoToMono(bool) = 0;
+
+ /**
+ * 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() = 0;
+
+ /**
+ * Called by Audio framework / HAL to suspend the stream
+ */
+ virtual bool suspend() = 0;
+
+ /**
+ * Called by Audio framework / HAL to stop the stream
+ */
+ virtual void stop() = 0;
+
+ /**
+ * 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(
+ ::aidl::android::hardware::bluetooth::audio::PresentationPosition&) const = 0;
+
+ /**
+ * Called by the Audio framework / HAL when the metadata of the stream's
+ * source has been changed.
+ */
+ virtual bool updateSourceMetadata(
+ const ::aidl::android::hardware::audio::common::SourceMetadata&) const {
+ return false;
+ }
+
+ /**
+ * Called by the Audio framework / HAL when the metadata of the stream's
+ * sink has been changed.
+ */
+ virtual bool updateSinkMetadata(
+ const ::aidl::android::hardware::audio::common::SinkMetadata&) const {
+ return false;
+ }
+
+ /**
+ * Return the current BluetoothStreamState
+ */
+ virtual BluetoothStreamState getState() const = 0;
+
+ /**
+ * Set the current BluetoothStreamState
+ */
+ virtual bool setState(BluetoothStreamState) = 0;
+
+ virtual bool isA2dp() const = 0;
+
+ virtual bool isLeAudio() const = 0;
+
+ virtual bool getPreferredDataIntervalUs(size_t*) const = 0;
+
+ virtual size_t writeData(const void*, size_t) const { return 0; }
+
+ virtual size_t readData(void*, size_t) const { return 0; }
+};
+
+class BluetoothAudioPortAidl : public BluetoothAudioPort {
+ public:
+ BluetoothAudioPortAidl();
+ virtual ~BluetoothAudioPortAidl();
+
+ bool registerPort(const ::aidl::android::media::audio::common::AudioDeviceDescription&
+ description) override;
+
+ void unregisterPort() override;
+
+ bool loadAudioConfig(::aidl::android::hardware::bluetooth::audio::PcmConfiguration* audio_cfg)
+ const override;
+
+ void forcePcmStereoToMono(bool force) override { mIsStereoToMono = force; }
+
+ bool start() override;
+ bool suspend() override;
+ void stop() override;
+
+ bool getPresentationPosition(::aidl::android::hardware::bluetooth::audio::PresentationPosition&
+ presentation_position) const override;
+
+ bool updateSourceMetadata(const ::aidl::android::hardware::audio::common::SourceMetadata&
+ sourceMetadata) const override;
+
+ bool updateSinkMetadata(const ::aidl::android::hardware::audio::common::SinkMetadata&
+ sinkMetadata) const override;
+
+ /**
+ * Return the current BluetoothStreamState
+ * Note: This method is used for logging, does not lock, so value returned may not be latest
+ */
+ BluetoothStreamState getState() const override NO_THREAD_SAFETY_ANALYSIS;
+
+ bool setState(BluetoothStreamState state) override;
+
+ bool isA2dp() const override;
+
+ bool isLeAudio() const override;
+
+ bool getPreferredDataIntervalUs(size_t* interval_us) const override;
+
+ protected:
+ uint16_t mCookie;
+ BluetoothStreamState mState GUARDED_BY(mCvMutex);
+ ::aidl::android::hardware::bluetooth::audio::SessionType mSessionType;
+ // WR to support Mono: True if fetching Stereo and mixing into Mono
+ bool mIsStereoToMono = false;
+
+ bool inUse() const;
+
+ std::string debugMessage() const;
+
+ private:
+ // start()/suspend() report state change status via callback. Wait until kMaxWaitingTimeMs or a
+ // state change after a call to start()/suspend() and analyse the returned status. Below mutex,
+ // conditional variable serves this purpose.
+ mutable std::mutex mCvMutex;
+ std::condition_variable mInternalCv GUARDED_BY(mCvMutex);
+
+ // Check and initialize session type for |devices| If failed, this
+ // BluetoothAudioPortAidl is not initialized and must be deleted.
+ bool initSessionType(
+ const ::aidl::android::media::audio::common::AudioDeviceDescription& description);
+
+ bool condWaitState(BluetoothStreamState state);
+
+ void controlResultHandler(
+ uint16_t cookie,
+ const ::aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus& status);
+ void sessionChangedHandler(uint16_t cookie);
+};
+
+class BluetoothAudioPortAidlOut : public BluetoothAudioPortAidl {
+ public:
+ // The audio data path to the Bluetooth stack (Software encoding)
+ size_t writeData(const void* buffer, size_t bytes) const override;
+};
+
+class BluetoothAudioPortAidlIn : public BluetoothAudioPortAidl {
+ public:
+ // The audio data path from the Bluetooth stack (Software decoded)
+ size_t readData(void* buffer, size_t bytes) const override;
+};
+
+} // namespace android::bluetooth::audio::aidl
\ No newline at end of file
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 539221d..e52940e 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -33,7 +33,7 @@
public:
// This value is used for all AudioPatches and reported by all streams.
static constexpr int32_t kLatencyMs = 10;
- enum Type : int { DEFAULT, R_SUBMIX, STUB, USB };
+ enum Type : int { DEFAULT, R_SUBMIX, STUB, USB, BLUETOOTH };
static std::shared_ptr<Module> createInstance(Type type);
diff --git a/audio/aidl/default/include/core-impl/ModuleBluetooth.h b/audio/aidl/default/include/core-impl/ModuleBluetooth.h
new file mode 100644
index 0000000..e20f7a3
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/ModuleBluetooth.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "core-impl/Module.h"
+
+namespace aidl::android::hardware::audio::core {
+
+class ModuleBluetooth final : public Module {
+ public:
+ ModuleBluetooth() : Module(Type::BLUETOOTH) {}
+
+ private:
+ ndk::ScopedAStatus getMicMute(bool* _aidl_return) override;
+ ndk::ScopedAStatus setMicMute(bool in_mute) override;
+
+ ndk::ScopedAStatus createInputStream(
+ StreamContext&& context,
+ const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
+ const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones,
+ std::shared_ptr<StreamIn>* result) override;
+ ndk::ScopedAStatus createOutputStream(
+ StreamContext&& context,
+ const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
+ const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
+ offloadInfo,
+ std::shared_ptr<StreamOut>* result) override;
+ ndk::ScopedAStatus onMasterMuteChanged(bool mute) override;
+ ndk::ScopedAStatus onMasterVolumeChanged(float volume) override;
+};
+
+} // namespace aidl::android::hardware::audio::core
\ No newline at end of file
diff --git a/audio/aidl/default/include/core-impl/StreamBluetooth.h b/audio/aidl/default/include/core-impl/StreamBluetooth.h
new file mode 100644
index 0000000..b9c5ea9
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/StreamBluetooth.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <mutex>
+#include <vector>
+
+#include "core-impl/DevicePortProxy.h"
+#include "core-impl/Stream.h"
+
+namespace aidl::android::hardware::audio::core {
+
+class StreamBluetooth : public StreamCommonImpl {
+ public:
+ StreamBluetooth(StreamContext* context, const Metadata& metadata);
+
+ // Methods of 'DriverInterface'.
+ ::android::status_t init() override;
+ ::android::status_t drain(StreamDescriptor::DrainMode) override;
+ ::android::status_t flush() override;
+ ::android::status_t pause() override;
+ ::android::status_t standby() override;
+ ::android::status_t start() override;
+ ::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
+ int32_t* latencyMs) override;
+ void shutdown() override;
+
+ // Overridden methods of 'StreamCommonImpl', called on a Binder thread.
+ ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override;
+ ndk::ScopedAStatus prepareToClose() override;
+ const ConnectedDevices& getConnectedDevices() const override;
+ ndk::ScopedAStatus setConnectedDevices(const ConnectedDevices& devices) override;
+
+ private:
+ // Audio Pcm Config
+ const uint32_t mSampleRate;
+ const ::aidl::android::media::audio::common::AudioChannelLayout mChannelLayout;
+ const ::aidl::android::media::audio::common::AudioFormatDescription mFormat;
+ const size_t mFrameSizeBytes;
+ const bool mIsInput;
+
+ size_t mPreferredDataIntervalUs;
+ size_t mPreferredFrameCount;
+
+ mutable std::mutex mLock;
+ bool mIsInitialized GUARDED_BY(mLock);
+ bool mIsReadyToClose GUARDED_BY(mLock);
+ std::vector<std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl>>
+ mBtDeviceProxies GUARDED_BY(mLock);
+
+ ::android::status_t initialize() REQUIRES(mLock);
+ bool checkConfigParams(::aidl::android::hardware::bluetooth::audio::PcmConfiguration& config);
+};
+
+class StreamInBluetooth final : public StreamIn, public StreamBluetooth {
+ public:
+ friend class ndk::SharedRefBase;
+ StreamInBluetooth(
+ StreamContext&& context,
+ const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
+ const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones);
+
+ private:
+ void onClose(StreamDescriptor::State) override { defaultOnClose(); }
+ ndk::ScopedAStatus getActiveMicrophones(
+ std::vector<::aidl::android::media::audio::common::MicrophoneDynamicInfo>* _aidl_return)
+ override;
+};
+
+class StreamOutBluetooth final : public StreamOut, public StreamBluetooth {
+ public:
+ friend class ndk::SharedRefBase;
+ StreamOutBluetooth(
+ StreamContext&& context,
+ const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
+ const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
+ offloadInfo);
+
+ private:
+ void onClose(StreamDescriptor::State) override { defaultOnClose(); }
+};
+
+} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/main.cpp b/audio/aidl/default/main.cpp
index 93fb366..a0c0fab 100644
--- a/audio/aidl/default/main.cpp
+++ b/audio/aidl/default/main.cpp
@@ -65,7 +65,8 @@
return std::make_pair(module, moduleBinder);
};
auto modules = {createModule(Module::Type::DEFAULT), createModule(Module::Type::R_SUBMIX),
- createModule(Module::Type::USB), createModule(Module::Type::STUB)};
+ createModule(Module::Type::USB), createModule(Module::Type::STUB),
+ createModule(Module::Type::BLUETOOTH)};
(void)modules;
ABinderProcess_joinThreadPool();