From 8d5f56fd054c0b0b7fe22cf37c777fac14ee3813 Mon Sep 17 00:00:00 2001 From: oxmc7769 Date: Sat, 4 Jul 2026 00:16:17 -0700 Subject: [PATCH] Sync to upstream android-17.0 (AOSP17) Pull in raspberry-vanilla's AOSP17 device tree changes for rpi4 while keeping all PawletOS customizations (A/B partition layout, sepolicy, zram, TWRP recovery scaffolding, ALSA HDMI pre-warm, product/overlay structure) intact on top. Upstream changes absorbed: - AOSP17 audio HAL refactor (StreamAlsa split into StreamAlsaBase/ StreamAlsaMonoPipe, Utils.cpp -> UtilsAlsa.cpp, new Bluetooth/stub helper classes, libeffects AIDL rebuild via *_ndk_shared defaults) - API level 36 -> 37, FCM matrix level 202504 -> 202604 - QuickSettings tile default split (SystemUIRpiOverlay, ported into pawlet_rpi_common where this overlay now lives) - vendor_tracing_descriptors added to enforce-product-packages-exist - misc AOSP17 logging header switch (android-base/logging.h -> Log.h) Co-Authored-By: Claude Sonnet 5 --- README.md | 2 +- audio/AidlConversionXsdc.cpp | 2 +- audio/Android.bp | 57 +- audio/AudioPolicyConfigXmlConverter.cpp | 2 +- audio/Bluetooth.cpp | 15 +- audio/CapEngineConfigXmlConverter.cpp | 4 +- audio/Config.cpp | 2 +- audio/Configuration.cpp | 40 +- audio/EffectConfig.cpp | 1 + audio/EffectImpl.cpp | 12 +- audio/EngineConfigXmlConverter.cpp | 13 +- audio/Module.cpp | 76 +- audio/ModulePrimary.cpp | 57 +- audio/SoundDose.cpp | 2 +- audio/Stream.cpp | 188 ++++- audio/Telephony.cpp | 2 +- audio/XsdcConversion.cpp | 37 +- audio/alsa/Mixer.cpp | 2 +- audio/alsa/ModuleAlsa.cpp | 14 +- audio/alsa/StreamAlsa.cpp | 274 +------ audio/alsa/StreamAlsaBase.cpp | 136 ++++ audio/alsa/StreamAlsaMonoPipe.cpp | 236 ++++++ audio/alsa/{Utils.cpp => UtilsAlsa.cpp} | 45 +- audio/alsa/{Utils.h => UtilsAlsa.h} | 4 +- audio/android.hardware.audio.service.rpi.xml | 14 +- audio/bluetooth/DevicePortProxy.cpp | 692 ++++++++++++------ audio/bluetooth/ModuleBluetooth.cpp | 306 +------- audio/bluetooth/ModuleBluetoothBase.cpp | 414 +++++++++++ audio/bluetooth/StreamBluetooth.cpp | 204 +++++- audio/config/audioPolicy/api/current.txt | 52 +- audio/config/audioPolicy/api/removed.txt | 3 +- .../audio_policy_configuration.xsd | 37 +- .../audioPolicy/capengine/api/current.txt | 3 +- .../audioPolicy/capengine/api/removed.txt | 3 +- .../config/audioPolicy/engine/api/current.txt | 11 +- .../config/audioPolicy/engine/api/removed.txt | 3 +- .../audio_policy_engine_configuration.xsd | 3 + audio/deprecated/StreamSwitcher.cpp | 14 +- audio/deprecated/StreamSwitcher.h | 1 + audio/include/core-impl/Bluetooth.h | 2 +- audio/include/core-impl/BluetoothAudioPort.h | 184 +++++ audio/include/core-impl/Configuration.h | 2 + audio/include/core-impl/DevicePortProxy.h | 179 ++--- audio/include/core-impl/Module.h | 9 +- audio/include/core-impl/ModuleAlsa.h | 3 + audio/include/core-impl/ModuleBluetooth.h | 74 +- audio/include/core-impl/ModuleBluetoothBase.h | 111 +++ audio/include/core-impl/ModulePrimary.h | 7 +- audio/include/core-impl/Stream.h | 32 +- audio/include/core-impl/StreamAlsa.h | 58 +- audio/include/core-impl/StreamAlsaBase.h | 73 ++ audio/include/core-impl/StreamAlsaMonoPipe.h | 62 ++ audio/include/core-impl/StreamBluetooth.h | 61 +- audio/include/core-impl/StreamMmapStub.h | 5 +- audio/include/core-impl/StreamOffloadStub.h | 93 ++- audio/include/core-impl/StreamPrimary.h | 4 +- audio/include/core-impl/StreamRemoteSubmix.h | 53 +- audio/include/core-impl/StreamStub.h | 14 +- audio/include/effect-impl/EffectImpl.h | 7 +- audio/libeffects/bundle/Android.bp | 8 +- audio/libeffects/downmix/Android.bp | 10 +- audio/libeffects/dynamicsproc/Android.bp | 8 +- .../aidl/DynamicsProcessingContext.cpp | 7 +- audio/libeffects/loudness/Android.bp | 10 +- .../dsp/core/dynamic_range_compression.cpp | 2 + audio/libeffects/reverb/Android.bp | 8 +- audio/libeffects/visualizer/Android.bp | 10 +- audio/main.cpp | 2 +- audio/primary/StreamPrimary.cpp | 27 +- audio/r_submix/ModuleRemoteSubmix.cpp | 6 +- audio/r_submix/StreamRemoteSubmix.cpp | 369 +++++----- audio/r_submix/SubmixRoute.cpp | 20 +- audio/r_submix/SubmixRoute.h | 56 +- audio/stub/ApeHeader.cpp | 2 +- audio/stub/DriverStubImpl.cpp | 2 +- audio/stub/ModuleStub.cpp | 2 +- audio/stub/MpegAudioHeader.cpp | 245 +++++++ audio/stub/MpegAudioHeader.h | 54 ++ audio/stub/StreamMmapStub.cpp | 48 +- audio/stub/StreamOffloadStub.cpp | 379 ++++++++-- audio/stub/StreamStub.cpp | 17 +- audio/usb/ModuleUsb.cpp | 4 +- audio/usb/StreamUsb.cpp | 2 +- audio/usb/UsbAlsaMixerControl.cpp | 2 +- device.mk | 2 +- framework_compatibility_matrix.xml | 2 +- health/Android.bp | 2 +- health/HealthImpl.cpp | 3 + .../android.hardware.health-service.rpi.xml | 2 +- keylayout/Generic-tv.kl | 94 ++- keylayout/Generic.kl | 3 + manifest.xml | 2 +- mkimg.sh | 2 +- pawlet_rpi4.mk | 2 +- pawlet_rpi4_car.mk | 2 +- pawlet_rpi4_tv.mk | 2 +- 96 files changed, 3900 insertions(+), 1534 deletions(-) create mode 100644 audio/alsa/StreamAlsaBase.cpp create mode 100644 audio/alsa/StreamAlsaMonoPipe.cpp rename audio/alsa/{Utils.cpp => UtilsAlsa.cpp} (90%) rename audio/alsa/{Utils.h => UtilsAlsa.h} (95%) create mode 100644 audio/bluetooth/ModuleBluetoothBase.cpp create mode 100644 audio/include/core-impl/BluetoothAudioPort.h create mode 100644 audio/include/core-impl/ModuleBluetoothBase.h create mode 100644 audio/include/core-impl/StreamAlsaBase.h create mode 100644 audio/include/core-impl/StreamAlsaMonoPipe.h create mode 100644 audio/stub/MpegAudioHeader.cpp create mode 100644 audio/stub/MpegAudioHeader.h diff --git a/README.md b/README.md index 96811bb..5ac8a73 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -Raspberry Vanilla AOSP 16 device configuration for Raspberry Pi 4. +Raspberry Vanilla AOSP 17 device configuration for Raspberry Pi 4. diff --git a/audio/AidlConversionXsdc.cpp b/audio/AidlConversionXsdc.cpp index c404d67..149f6d3 100644 --- a/audio/AidlConversionXsdc.cpp +++ b/audio/AidlConversionXsdc.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "AHAL_AidlXsdc" -#include +#include #include #include #include diff --git a/audio/Android.bp b/audio/Android.bp index 29df469..edeab42 100644 --- a/audio/Android.bp +++ b/audio/Android.bp @@ -7,13 +7,9 @@ cc_defaults { name: "aidlaudioservice_rpi_defaults", vendor: true, shared_libs: [ - "android.hardware.audio.core-V3-ndk", - "android.hardware.audio.core.sounddose-V3-ndk", - "android.hardware.bluetooth.audio-V5-ndk", "android.hardware.bluetooth.audio-impl", "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", - "android.media.audio.common.types-V4-ndk", "libalsautilsv2-rpi", "libasound", "libaudio_aidl_conversion_common_ndk", @@ -43,6 +39,15 @@ cc_defaults { "-Werror", "-Wextra", "-Wthread-safety", + ] + select(release_flag("RELEASE_HARDWARE_AUDIO_USE_CAP_AIDL"), { + true: [], + default: ["-DDISABLE_CAP_AIDL"], + }), + defaults: [ + "latest_android_hardware_audio_core_ndk_shared", + "latest_android_hardware_audio_core_sounddose_ndk_shared", + "latest_android_hardware_bluetooth_audio_ndk_shared", + "latest_android_media_audio_common_types_ndk_shared", ], } @@ -67,9 +72,12 @@ cc_library { "alsa/Mixer.cpp", "alsa/ModuleAlsa.cpp", "alsa/StreamAlsa.cpp", - "alsa/Utils.cpp", + "alsa/StreamAlsaBase.cpp", + "alsa/StreamAlsaMonoPipe.cpp", + "alsa/UtilsAlsa.cpp", "bluetooth/DevicePortProxy.cpp", "bluetooth/ModuleBluetooth.cpp", + "bluetooth/ModuleBluetoothBase.cpp", "bluetooth/StreamBluetooth.cpp", "deprecated/StreamSwitcher.cpp", "primary/PrimaryMixer.cpp", @@ -80,6 +88,7 @@ cc_library { "stub/ApeHeader.cpp", "stub/DriverStubImpl.cpp", "stub/ModuleStub.cpp", + "stub/MpegAudioHeader.cpp", "stub/StreamMmapStub.cpp", "stub/StreamOffloadStub.cpp", "stub/StreamStub.cpp", @@ -128,13 +137,38 @@ cc_library { ], } -filegroup { - name: "effectCommonFileRpi", +cc_library_static { + name: "libaudioeffectaidlcommon-rpi", srcs: [ "EffectContext.cpp", "EffectImpl.cpp", "EffectThread.cpp", ], + defaults: [ + "latest_android_media_audio_common_types_ndk_shared", + "latest_android_hardware_audio_effect_ndk_shared", + ], + shared_libs: [ + "libaudioaidlcommon", + "libaudioutils", + "libfmq", + ], + static_libs: [ + "libaudioaidlranges", + ], + header_libs: [ + "libaudioaidl_headers", + ], + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-Wthread-safety", + "-DBACKEND_NDK", + ], + export_include_dirs: ["include"], + host_supported: true, + vendor: true, } cc_binary { @@ -152,8 +186,6 @@ cc_defaults { name: "aidlaudioeffectservice_rpi_defaults", vendor: true, shared_libs: [ - "android.hardware.audio.effect-V3-ndk", - "android.media.audio.common.types-V4-ndk", "libapexsupport", "libaudio_aidl_conversion_common_ndk", "libaudioutils", @@ -172,6 +204,10 @@ cc_defaults { "-Werror", "-Wextra", ], + defaults: [ + "latest_android_hardware_audio_effect_ndk_shared", + "latest_android_media_audio_common_types_ndk_shared", + ], } cc_binary { @@ -186,6 +222,9 @@ cc_binary { "libeffectconfig-rpi", "libfmq", ], + static_libs: [ + "libaudioeffectaidlcommon-rpi", + ], header_libs: [ "libsystem_headers", ], diff --git a/audio/AudioPolicyConfigXmlConverter.cpp b/audio/AudioPolicyConfigXmlConverter.cpp index 2f1282a..5b7a7b1 100644 --- a/audio/AudioPolicyConfigXmlConverter.cpp +++ b/audio/AudioPolicyConfigXmlConverter.cpp @@ -22,7 +22,7 @@ #include #define LOG_TAG "AHAL_ApmXmlConverter" -#include +#include #include #include diff --git a/audio/Bluetooth.cpp b/audio/Bluetooth.cpp index 072b89f..681683d 100644 --- a/audio/Bluetooth.cpp +++ b/audio/Bluetooth.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "AHAL_Bluetooth" -#include +#include #include "core-impl/Bluetooth.h" @@ -31,30 +31,37 @@ Bluetooth::Bluetooth() { mScoConfig.isNrecEnabled = Boolean{false}; mScoConfig.mode = ScoConfig::Mode::SCO; mHfpConfig.isEnabled = Boolean{false}; - mHfpConfig.sampleRate = Int{8000}; + mHfpConfig.sampleRate = Int{32000}; mHfpConfig.volume = Float{HfpConfig::VOLUME_MAX}; } ndk::ScopedAStatus Bluetooth::setScoConfig(const ScoConfig& in_config, ScoConfig* _aidl_return) { + bool updated = false; if (in_config.isEnabled.has_value()) { mScoConfig.isEnabled = in_config.isEnabled; + updated = true; } if (in_config.isNrecEnabled.has_value()) { mScoConfig.isNrecEnabled = in_config.isNrecEnabled; + updated = true; } if (in_config.mode != ScoConfig::Mode::UNSPECIFIED) { mScoConfig.mode = in_config.mode; + updated = true; } if (in_config.debugName.has_value()) { mScoConfig.debugName = in_config.debugName; + updated = true; } *_aidl_return = mScoConfig; LOG(DEBUG) << __func__ << ": received " << in_config.toString() << ", returning " << _aidl_return->toString(); + if (updated && mHandler) return mHandler(); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Bluetooth::setHfpConfig(const HfpConfig& in_config, HfpConfig* _aidl_return) { + bool updated = false; if (in_config.sampleRate.has_value() && in_config.sampleRate.value().value <= 0) { LOG(ERROR) << __func__ << ": invalid sample rate: " << in_config.sampleRate.value().value; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); @@ -67,16 +74,20 @@ ndk::ScopedAStatus Bluetooth::setHfpConfig(const HfpConfig& in_config, HfpConfig if (in_config.isEnabled.has_value()) { mHfpConfig.isEnabled = in_config.isEnabled; + updated = true; } if (in_config.sampleRate.has_value()) { mHfpConfig.sampleRate = in_config.sampleRate; + updated = true; } if (in_config.volume.has_value()) { mHfpConfig.volume = in_config.volume; + updated = true; } *_aidl_return = mHfpConfig; LOG(DEBUG) << __func__ << ": received " << in_config.toString() << ", returning " << _aidl_return->toString(); + if (updated && mHandler) return mHandler(); return ndk::ScopedAStatus::ok(); } diff --git a/audio/CapEngineConfigXmlConverter.cpp b/audio/CapEngineConfigXmlConverter.cpp index 14d27f2..9f12db6 100644 --- a/audio/CapEngineConfigXmlConverter.cpp +++ b/audio/CapEngineConfigXmlConverter.cpp @@ -16,8 +16,8 @@ #define LOG_TAG "AHAL_Config" +#include #include -#include #include #include #include @@ -56,7 +56,6 @@ static constexpr const char* gLegacyStrategyPrefix = "STRATEGY_"; static constexpr const char* gLegacyOutputDevicePrefix = "AUDIO_DEVICE_OUT_"; static constexpr const char* gLegacyInputDevicePrefix = "AUDIO_DEVICE_IN_"; static constexpr const char* gLegacyStreamPrefix = "AUDIO_STREAM_"; -static constexpr const char* gLegacySourcePrefix = "AUDIO_SOURCE_"; std::optional>>& CapEngineConfigXmlConverter::getAidlCapEngineConfig() { @@ -121,7 +120,6 @@ ConversionResult convertCriterionRuleToAidl( ConversionResult convertRule(const eng_xsd::CompoundRuleType& xsdcCompoundRule) { AudioHalCapRule rule{}; - bool isPreviousCompoundRule = true; if (xsdcCompoundRule.getType() == eng_xsd::TypeEnum::Any) { rule.compoundRule = AudioHalCapRule::CompoundRule::ANY; } else if (xsdcCompoundRule.getType() == eng_xsd::TypeEnum::All) { diff --git a/audio/Config.cpp b/audio/Config.cpp index 308200a..da3c521 100644 --- a/audio/Config.cpp +++ b/audio/Config.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "AHAL_Config" -#include +#include #include diff --git a/audio/Configuration.cpp b/audio/Configuration.cpp index 0ff8eb4..30c528c 100644 --- a/audio/Configuration.cpp +++ b/audio/Configuration.cpp @@ -264,7 +264,7 @@ std::unique_ptr getPrimaryConfiguration() { c.ports.push_back(primaryInMix); AudioPort telephonyTxOutMix = - createPort(c.nextPortId++, "telephony_tx", 0, false, createPortMixExt(1, 1)); + createPort(c.nextPortId++, kPortNameTelephonyTx, 0, false, createPortMixExt(1, 1)); telephonyTxOutMix.profiles.insert(telephonyTxOutMix.profiles.begin(), standardPcmAudioProfiles.begin(), standardPcmAudioProfiles.end()); @@ -604,24 +604,36 @@ std::unique_ptr getStubConfiguration() { // - profile PCM 16-bit; STEREO; 44100, 48000, 88200, 96000 // * "BT Hearing Aid Out", OUT_HEARING_AID, CONNECTION_WIRELESS // - no profiles specified +// * "BLE Headset Out", OUT_HEADSET, CONNECTION_BT_LE +// - no profiles specified +// * "BLE Headset In", IN_HEADSET, CONNECTION_BT_LE +// - 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 +// * "le audio output", 1 max open, 1 max active stream +// - profile PCM 16-bit; STEREO; 44100, 48000 +// * "le audio input", 1 max open, 1 max active stream +// - profile PCM 16-bit; STEREO; 44100, 48000 // // Routes: // "a2dp output" -> "BT A2DP Out" // "a2dp output" -> "BT A2DP Headphones" // "a2dp output" -> "BT A2DP Speaker" // "hearing aid output" -> "BT Hearing Aid Out" +// "le audio output" -> "BLE Headset Out" +// "BLE Headset In" -> "le audio input" // // Profiles for device port connected state (when simulating connections): // * "BT A2DP Out", "BT A2DP Headphones", "BT A2DP Speaker": // - profile PCM 16-bit; STEREO; 44100, 48000, 88200, 96000 // * "BT Hearing Aid Out": // - profile PCM 16-bit; STEREO; 16000, 24000 +// * "BLE Headset Out", "BLE Headset In" +// - profile PCM 16-bit; STEREO; 44100, 48000 // std::unique_ptr getBluetoothConfiguration() { static const Configuration configuration = []() { @@ -630,6 +642,8 @@ std::unique_ptr getBluetoothConfiguration() { {44100, 48000, 88200, 96000})}; const std::vector hearingAidAudioProfiles = {createProfile( PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000, 24000})}; + const std::vector leAudioProfiles = {createProfile( + PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {44100, 48000})}; Configuration c; // Device ports @@ -669,6 +683,20 @@ std::unique_ptr getBluetoothConfiguration() { c.ports.push_back(btOutHearingAid); c.connectedProfiles[btOutHearingAid.id] = hearingAidAudioProfiles; + AudioPort btLeOutHeadset = + createPort(c.nextPortId++, "BLE Headset Out", 0, false, + createDeviceExt(AudioDeviceType::OUT_HEADSET, 0, + AudioDeviceDescription::CONNECTION_BT_LE)); + c.ports.push_back(btLeOutHeadset); + c.connectedProfiles[btLeOutHeadset.id] = leAudioProfiles; + + AudioPort btLeInHeadset = + createPort(c.nextPortId++, "BLE Headset In", 0, true, + createDeviceExt(AudioDeviceType::IN_HEADSET, 0, + AudioDeviceDescription::CONNECTION_BT_LE)); + c.ports.push_back(btLeInHeadset); + c.connectedProfiles[btLeInHeadset.id] = leAudioProfiles; + // Mix ports AudioPort btOutMix = createPort(c.nextPortId++, "a2dp output", 0, false, createPortMixExt(1, 1)); @@ -679,10 +707,20 @@ std::unique_ptr getBluetoothConfiguration() { btHearingOutMix.profiles = hearingAidAudioProfiles; c.ports.push_back(btHearingOutMix); + AudioPort btLeOutMix = + createPort(c.nextPortId++, "le audio output", 0, false, createPortMixExt(1, 1)); + c.ports.push_back(btLeOutMix); + + AudioPort btLeInMix = + createPort(c.nextPortId++, "le audio input", 0, true, createPortMixExt(1, 1)); + c.ports.push_back(btLeInMix); + c.routes.push_back(createRoute({btOutMix}, btOutDevice)); c.routes.push_back(createRoute({btOutMix}, btOutHeadphone)); c.routes.push_back(createRoute({btOutMix}, btOutSpeaker)); c.routes.push_back(createRoute({btHearingOutMix}, btOutHearingAid)); + c.routes.push_back(createRoute({btLeOutMix}, btLeOutHeadset)); + c.routes.push_back(createRoute({btLeInHeadset}, btLeInMix)); return c; }(); diff --git a/audio/EffectConfig.cpp b/audio/EffectConfig.cpp index fa12056..0d883ac 100644 --- a/audio/EffectConfig.cpp +++ b/audio/EffectConfig.cpp @@ -311,6 +311,7 @@ bool EffectConfig::findUuid(const std::pair V("downmix", Downmix) \ V("dynamics_processing", DynamicsProcessing) \ V("equalizer", Equalizer) \ + V("eraser", Eraser) \ V("extensioneffect", Extension) \ V("haptic_generator", HapticGenerator) \ V("loudness_enhancer", LoudnessEnhancer) \ diff --git a/audio/EffectImpl.cpp b/audio/EffectImpl.cpp index 97f7286..9db323b 100644 --- a/audio/EffectImpl.cpp +++ b/audio/EffectImpl.cpp @@ -300,7 +300,7 @@ ndk::ScopedAStatus EffectImpl::command(CommandId command) { "CommandIdNotSupported"); } LOG(VERBOSE) << getEffectNameWithVersion() << __func__ - << " transfer to state: " << toString(mState); + << " transfer to state: " << toString(mState) << " with " << toString(command); return ndk::ScopedAStatus::ok(); } @@ -392,6 +392,7 @@ void EffectImpl::process() { auto outputMQ = mImplContext->getOutputDataFmq(); auto buffer = mImplContext->getWorkBuffer(); if (!inputMQ || !outputMQ) { + LOG(WARNING) << __func__ << " skip processing with empty FMQs"; return; } @@ -403,6 +404,8 @@ void EffectImpl::process() { IEffect::Status status = effectProcessImpl(buffer, buffer, processSamples); outputMQ->write(buffer, status.fmqProduced); statusMQ->writeBlocking(&status, 1); + } else { + drainingComplete_l(); } } } @@ -415,4 +418,11 @@ IEffect::Status EffectImpl::effectProcessImpl(float* in, float* out, int samples return {STATUS_OK, samples, samples}; } +void EffectImpl::drainingComplete_l() { + if (mState != State::DRAINING) return; + + finishDraining(); + mState = State::IDLE; +} + } // namespace aidl::android::hardware::audio::effect diff --git a/audio/EngineConfigXmlConverter.cpp b/audio/EngineConfigXmlConverter.cpp index 78deb64..e8b71e7 100644 --- a/audio/EngineConfigXmlConverter.cpp +++ b/audio/EngineConfigXmlConverter.cpp @@ -21,10 +21,10 @@ #include #define LOG_TAG "AHAL_Config" +#include #include #include #include -#include #include "core-impl/CapEngineConfigXmlConverter.h" #include "core-impl/EngineConfigXmlConverter.h" @@ -127,9 +127,6 @@ ConversionResult EngineConfigXmlConverter::convertAudioAttribut ConversionResult EngineConfigXmlConverter::convertAttributesGroupToAidl( const eng_xsd::AttributesGroup& xsdcAttributesGroup) { AudioHalAttributesGroup aidlAttributesGroup; - static const int kStreamTypeEnumOffset = - static_cast(eng_xsd::Stream::AUDIO_STREAM_VOICE_CALL) - - static_cast(AudioStreamType::VOICE_CALL); aidlAttributesGroup.streamType = xsdcAttributesGroup.hasStreamType() ? VALUE_OR_FATAL(convertAudioStreamTypeToAidl( xsdcAttributesGroup.getStreamType())) @@ -173,7 +170,9 @@ ConversionResult EngineConfigXmlConverter::convertProdu VALUE_OR_FATAL(convertProductStrategyNameToAidl(xsdcProductStrategy.getName())); } aidlProductStrategy.name = xsdcProductStrategy.getName(); - + if (xsdcProductStrategy.hasZoneId()) { + aidlProductStrategy.zoneId = xsdcProductStrategy.getZoneId(); + } if (xsdcProductStrategy.hasAttributesGroup()) { aidlProductStrategy.attributesGroups = VALUE_OR_FATAL( (convertCollectionToAidl( @@ -254,6 +253,9 @@ void EngineConfigXmlConverter::init() { } if (getXsdcConfig()->hasCriteria() && getXsdcConfig()->hasCriterion_types()) { AudioHalEngineConfig::CapSpecificConfig capSpecificConfig; + // In hybrid mode, we use legacy XML file, that would not be compatible with AIDL + // (e.g. some device like ambient or incommunication deprecated...) +#ifndef DISABLE_CAP_AIDL capSpecificConfig.criteriaV2 = std::make_optional<>(VALUE_OR_FATAL((convertCapCriteriaCollectionToAidl( getXsdcConfig()->getCriteria(), getXsdcConfig()->getCriterion_types())))); @@ -262,6 +264,7 @@ void EngineConfigXmlConverter::init() { if (capEngConfigConverter.getStatus() == ::android::OK) { capSpecificConfig.domains = std::move(capEngConfigConverter.getAidlCapEngineConfig()); } +#endif mAidlEngineConfig.capSpecificConfig = capSpecificConfig; } } diff --git a/audio/Module.cpp b/audio/Module.cpp index aa624ff..6f37f0b 100644 --- a/audio/Module.cpp +++ b/audio/Module.cpp @@ -18,9 +18,9 @@ #include #define LOG_TAG "AHAL_Module" +#include #include #include -#include #include #include @@ -61,6 +61,7 @@ using aidl::android::media::audio::common::AudioPortConfig; using aidl::android::media::audio::common::AudioPortExt; using aidl::android::media::audio::common::AudioProfile; using aidl::android::media::audio::common::Boolean; +using aidl::android::media::audio::common::FlushFromFrameSupport; using aidl::android::media::audio::common::Int; using aidl::android::media::audio::common::MicrophoneInfo; using aidl::android::media::audio::common::PcmType; @@ -185,9 +186,10 @@ ndk::ScopedAStatus Module::createStreamContext( // Since this is a private method, it is assumed that // validity of the portConfigId has already been checked. int32_t minimumStreamBufferSizeFrames = 0; - if (!calculateBufferSizeFrames( - portConfigIt->format.value(), nominalLatencyMs, - portConfigIt->sampleRate.value().value, &minimumStreamBufferSizeFrames).isOk()) { + if (!calculateBufferSizeFrames(portConfigIt->format.value(), portConfigIt->flags.value(), + nominalLatencyMs, portConfigIt->sampleRate.value().value, + &minimumStreamBufferSizeFrames) + .isOk()) { return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } if (in_bufferSizeFrames < minimumStreamBufferSizeFrames) { @@ -392,8 +394,9 @@ int32_t Module::getNominalLatencyMs(const AudioPortConfig&) { } ndk::ScopedAStatus Module::calculateBufferSizeFrames( - const ::aidl::android::media::audio::common::AudioFormatDescription &format, - int32_t latencyMs, int32_t sampleRateHz, int32_t *bufferSizeFrames) { + const ::aidl::android::media::audio::common::AudioFormatDescription& format, + const AudioIoFlags& /*flags*/, int32_t latencyMs, int32_t sampleRateHz, + int32_t* bufferSizeFrames) { if (format.type == AudioFormatType::PCM) { *bufferSizeFrames = calculateBufferSizeFramesForPcm(latencyMs, sampleRateHz); return ndk::ScopedAStatus::ok(); @@ -623,11 +626,21 @@ ndk::ScopedAStatus Module::updateStreamsConnectedState(const AudioPatch& oldPatc } binder_status_t Module::dump(int fd, const char** args, uint32_t numArgs) { + if (::aidl::android::hardware::audio::common::hasArgument( + args, numArgs, + ::aidl::android::hardware::audio::common::kDumpFromAudioServerArgument)) { + std::ostringstream s; + s << mType; + // Not needed in the case of a dedicated module dump. + dprintf(fd, "\n[Module %s] ", s.str().c_str()); + } + dprintf(fd, "Stream dumps:\n"); for (const auto& portConfig : getConfig().portConfigs) { if (portConfig.ext.getTag() == AudioPortExt::Tag::mix) { getStreams().dump(portConfig.id, fd, args, numArgs); } } + dprintf(fd, "\n"); return STATUS_OK; } @@ -972,10 +985,24 @@ ndk::ScopedAStatus Module::getAudioRoutesForAudioPort(int32_t in_portId, return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus Module::validateMetadataAttributeTags(const std::vector& tags) { + for (auto& tag : tags) { + if (!common::isVendorExtension(tag)) { + LOG(ERROR) << __func__ << ": " << mType << ": metadata attribute tag " << tag + << " is invalid."; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + return ndk::ScopedAStatus::ok(); +} + ndk::ScopedAStatus Module::openInputStream(const OpenInputStreamArguments& in_args, OpenInputStreamReturn* _aidl_return) { LOG(DEBUG) << __func__ << ": " << mType << ": port config id " << in_args.portConfigId << ", buffer size " << in_args.bufferSizeFrames << " frames"; + for (const auto& track : in_args.sinkMetadata.tracks) { + RETURN_STATUS_IF_ERROR(validateMetadataAttributeTags(track.tags)); + } AudioPort* port = nullptr; RETURN_STATUS_IF_ERROR(findPortIdForNewStream(in_args.portConfigId, &port)); if (port->flags.getTag() != AudioIoFlags::Tag::input) { @@ -1008,6 +1035,16 @@ ndk::ScopedAStatus Module::openOutputStream(const OpenOutputStreamArguments& in_ LOG(DEBUG) << __func__ << ": " << mType << ": port config id " << in_args.portConfigId << ", has offload info? " << (in_args.offloadInfo.has_value()) << ", buffer size " << in_args.bufferSizeFrames << " frames"; + for (const auto& track : in_args.sourceMetadata.tracks) { + RETURN_STATUS_IF_ERROR(validateMetadataAttributeTags(track.tags)); + if (const auto& codecMime = track.codecProvenance; + codecMime.has_value() && !codecMime->empty()) { + if (!aidl::android::hardware::audio::common::isAudioMimeType(*codecMime)) { + LOG(ERROR) << __func__ << ": invalid audio MIME type: \"" << *codecMime << "\""; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + } AudioPort* port = nullptr; RETURN_STATUS_IF_ERROR(findPortIdForNewStream(in_args.portConfigId, &port)); if (port->flags.getTag() != AudioIoFlags::Tag::output) { @@ -1153,8 +1190,9 @@ ndk::ScopedAStatus Module::setAudioPatch(const AudioPatch& in_requested, AudioPa auto maxSampleRateIt = std::max_element(sampleRates.begin(), sampleRates.end()); const int32_t latencyMs = getNominalLatencyMs(*(maxSampleRateIt->second)); if (!calculateBufferSizeFrames( - maxSampleRateIt->second->format.value(), latencyMs, maxSampleRateIt->first, - &_aidl_return->minimumStreamBufferSizeFrames).isOk()) { + maxSampleRateIt->second->format.value(), maxSampleRateIt->second->flags.value(), + latencyMs, maxSampleRateIt->first, &_aidl_return->minimumStreamBufferSizeFrames) + .isOk()) { if (patchesBackup.has_value()) { mPatches = std::move(*patchesBackup); } @@ -1227,6 +1265,15 @@ ndk::ScopedAStatus Module::setAudioPortConfigImpl( << ": requested port config points to non-existent portId " << portId; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } + + if (portIt->ext.getTag() == AudioPortExt::Tag::device && + !portIt->ext.get().device.type.connection.empty() && + mConnectedDevicePorts.find(portId) == mConnectedDevicePorts.end()) { + LOG(ERROR) << __func__ << ": " << mType << ": requested portId " << portId + << " is not connected to an external device (is it a template port?)."; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (existing != configs.end()) { *out_suggested = *existing; } else { @@ -1362,11 +1409,14 @@ ndk::ScopedAStatus Module::setAudioPortConfigImpl( } bool Module::setAudioPortConfigGain(const AudioPort& port, const AudioGainConfig& gainRequested) { - auto& ports = getConfig().ports; if (gainRequested.index < 0 || gainRequested.index >= (int)port.gains.size()) { LOG(ERROR) << __func__ << ": gains for port " << port.id << " is undefined"; return false; } + if (gainRequested.values.empty()) { + LOG(ERROR) << __func__ << ": received empty gain values"; + return false; + } int stepValue = port.gains[gainRequested.index].stepValue; if (stepValue == 0) { LOG(ERROR) << __func__ << ": port gain step value is 0"; @@ -1427,6 +1477,8 @@ ndk::ScopedAStatus Module::resetAudioPatch(int32_t in_patchId) { ndk::ScopedAStatus Module::resetAudioPortConfig(int32_t in_portConfigId) { auto& configs = getConfig().portConfigs; + LOG(DEBUG) << __func__ << ": " << mType << ": in_portConfigId " << in_portConfigId + << " configs size: " << configs.size(); auto configIt = findById(configs, in_portConfigId); if (configIt != configs.end()) { if (mStreams.count(in_portConfigId) != 0) { @@ -1838,4 +1890,10 @@ ndk::ScopedAStatus Module::bluetoothParametersUpdated() { return mStreams.bluetoothParametersUpdated(); } +ndk::ScopedAStatus Module::getFlushFromFrameSupport(const AudioPortConfig& in_config __unused, + FlushFromFrameSupport* _aidl_return __unused) { + LOG(VERBOSE) << __func__ << ": " << mType; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + } // namespace aidl::android::hardware::audio::core diff --git a/audio/ModulePrimary.cpp b/audio/ModulePrimary.cpp index 6cb9251..9e4e9e7 100644 --- a/audio/ModulePrimary.cpp +++ b/audio/ModulePrimary.cpp @@ -17,13 +17,15 @@ #include #define LOG_TAG "AHAL_ModulePrimary" +#include #include -#include +#include "core-impl/Configuration.h" #include "core-impl/ModulePrimary.h" #include "core-impl/StreamMmapStub.h" #include "core-impl/StreamOffloadStub.h" #include "core-impl/StreamPrimary.h" +#include "core-impl/StreamStub.h" #include "core-impl/Telephony.h" using aidl::android::hardware::audio::common::areAllBitPositionFlagsSet; @@ -38,6 +40,7 @@ using aidl::android::media::audio::common::AudioOutputFlags; using aidl::android::media::audio::common::AudioPort; using aidl::android::media::audio::common::AudioPortConfig; using aidl::android::media::audio::common::AudioPortExt; +using aidl::android::media::audio::common::FlushFromFrameSupport; using aidl::android::media::audio::common::MicrophoneInfo; namespace aidl::android::hardware::audio::core { @@ -54,13 +57,16 @@ ndk::ScopedAStatus ModulePrimary::getTelephony(std::shared_ptr* _aid ndk::ScopedAStatus ModulePrimary::calculateBufferSizeFrames( const ::aidl::android::media::audio::common::AudioFormatDescription& format, - int32_t latencyMs, int32_t sampleRateHz, int32_t* bufferSizeFrames) { - if (format.type != ::aidl::android::media::audio::common::AudioFormatType::PCM && - StreamOffloadStub::getSupportedEncodings().count(format.encoding)) { + const ::aidl::android::media::audio::common::AudioIoFlags& flags, int32_t latencyMs, + int32_t sampleRateHz, int32_t* bufferSizeFrames) { + if ((format.type != ::aidl::android::media::audio::common::AudioFormatType::PCM && + StreamOffloadStub::getSupportedEncodings().count(format.encoding)) || + common::isPcmOffload(format, flags)) { *bufferSizeFrames = sampleRateHz / 2; // 1/2 of a second. return ndk::ScopedAStatus::ok(); } - return Module::calculateBufferSizeFrames(format, latencyMs, sampleRateHz, bufferSizeFrames); + return Module::calculateBufferSizeFrames(format, flags, latencyMs, sampleRateHz, + bufferSizeFrames); } ndk::ScopedAStatus ModulePrimary::createInputStream(StreamContext&& context, @@ -89,9 +95,32 @@ ndk::ScopedAStatus ModulePrimary::createOutputStream( // "Stub" is used because there is no actual decoder. The stream just // extracts the clip duration from the media file header and simulates // playback over time. + if (context.getSampleRate() <= 0) { + LOG(ERROR) << __func__ + << ": Invalid sample rate for offload stream: " << context.getSampleRate(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + return createStreamInstance(result, std::move(context), sourceMetadata, offloadInfo); } + + const auto& activeConfigs = getConfig().portConfigs; + int32_t handle = context.getMixPortHandle(); + auto it = std::find_if(activeConfigs.begin(), activeConfigs.end(), [&](const auto& config) { + return config.ext.getTag() == AudioPortExt::Tag::mix && + config.ext.template get().handle == handle; + }); + if (it != activeConfigs.end()) { + int32_t portId = it->portId; + auto& ports = getConfig().ports; + auto foundPort = findById(ports, portId); + if (foundPort != ports.end() && foundPort->name == internal::kPortNameTelephonyTx) { + return createStreamInstance(result, std::move(context), + sourceMetadata, offloadInfo); + } + } + return createStreamInstance(result, std::move(context), sourceMetadata, offloadInfo); } @@ -114,14 +143,14 @@ ndk::ScopedAStatus ModulePrimary::createMmapBuffer(const AudioPortConfig& portCo } desc->sharedMemory.fd = ndk::ScopedFileDescriptor(fd); desc->sharedMemory.size = bufferSizeBytes; - desc->burstSizeFrames = bufferSizeFrames / 2; - desc->flags = 0; + desc->burstSizeFrames = bufferSizeFrames / 4; + desc->flags = 1 << MmapBufferDescriptor::FLAG_INDEX_APPLICATION_SHAREABLE; LOG(DEBUG) << __func__ << ": " << desc->toString(); return ndk::ScopedAStatus::ok(); } int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig& portConfig) { - static constexpr int32_t kLowLatencyMs = 5; + static constexpr int32_t kLowLatencyMs = 10; // 85 ms is chosen considering 4096 frames @ 48 kHz. This is the value which allows // the virtual Android device implementation to pass CTS. Hardware implementations // should have significantly lower latency. @@ -129,4 +158,16 @@ int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig& portConfig) { return hasMmapFlag(portConfig.flags.value()) ? kLowLatencyMs : kStandardLatencyMs; } +ndk::ScopedAStatus ModulePrimary::getFlushFromFrameSupport(const AudioPortConfig& in_config, + FlushFromFrameSupport* _aidl_return) { + LOG(DEBUG) << __func__ << ": config=" << in_config.toString(); + if (!in_config.flags.has_value() || !in_config.format.has_value()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + *_aidl_return = common::isPcmOffload(in_config.format.value(), in_config.flags.value()) + ? FlushFromFrameSupport::SUPPORTED + : FlushFromFrameSupport::UNSUPPORTED; + return ndk::ScopedAStatus::ok(); +} + } // namespace aidl::android::hardware::audio::core diff --git a/audio/SoundDose.cpp b/audio/SoundDose.cpp index 6c3a067..90ec5ae 100644 --- a/audio/SoundDose.cpp +++ b/audio/SoundDose.cpp @@ -18,8 +18,8 @@ #include "core-impl/SoundDose.h" +#include #include -#include #include #include diff --git a/audio/Stream.cpp b/audio/Stream.cpp index 873fc48..2670c40 100644 --- a/audio/Stream.cpp +++ b/audio/Stream.cpp @@ -15,11 +15,12 @@ */ #include +#include #define ATRACE_TAG ATRACE_TAG_AUDIO #define LOG_TAG "AHAL_Stream" +#include #include -#include #include #include #include @@ -36,12 +37,14 @@ using aidl::android::hardware::audio::common::SinkMetadata; using aidl::android::hardware::audio::common::SourceMetadata; using aidl::android::media::audio::common::AudioDevice; using aidl::android::media::audio::common::AudioDualMonoMode; +using aidl::android::media::audio::common::AudioFormatType; using aidl::android::media::audio::common::AudioInputFlags; using aidl::android::media::audio::common::AudioIoFlags; using aidl::android::media::audio::common::AudioLatencyMode; using aidl::android::media::audio::common::AudioOffloadInfo; using aidl::android::media::audio::common::AudioOutputFlags; using aidl::android::media::audio::common::AudioPlaybackRate; +using aidl::android::media::audio::common::FlushFromFrameAccuracy; using aidl::android::media::audio::common::MicrophoneDynamicInfo; using aidl::android::media::audio::common::MicrophoneInfo; @@ -196,6 +199,27 @@ void StreamWorkerCommonLogic::populateReplyWrongState( reply->status = STATUS_INVALID_OPERATION; } +void StreamWorkerCommonLogic::populateReplyUnsupportedCommand( + StreamDescriptor::Reply* reply, const StreamDescriptor::Command& command) const { + LOG(WARNING) << "command '" << toString(command.getTag()) << "' is not supported by the stream"; + reply->status = STATUS_INVALID_OPERATION; +} + +void StreamWorkerCommonLogic::switchFromTransientState(StreamDescriptor::State state) { + if (mTransientStateDelayMs.count() != 0) { + if (auto stateDurationMs = std::chrono::duration_cast( + std::chrono::steady_clock::now() - mTransientStateStart); + stateDurationMs < mTransientStateDelayMs) { + const auto delayMs = mTransientStateDelayMs - stateDurationMs; + LOG(DEBUG) << __func__ << ": inducing transient state delay when switching from " + << toString(mState) << " to " << toString(state) << " of " << delayMs + << "ms"; + std::this_thread::sleep_for(delayMs); + } + } + mState = state; +} + const std::string StreamInWorkerLogic::kThreadName = "reader"; StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() { @@ -346,6 +370,10 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() { populateReplyWrongState(&reply, command); } break; + case Tag::flushFromFrame: + LOG(ERROR) << __func__ << ": flushFromFrame is not supported for input stream"; + populateReplyUnsupportedCommand(&reply, command); + break; } reply.state = mState; LOG(severity) << __func__ << ": writing reply " << reply.toString(); @@ -416,14 +444,31 @@ bool StreamInWorkerLogic::readMmap(StreamDescriptor::Reply* reply) { const std::string StreamOutWorkerLogic::kThreadName = "writer"; +using StreamOutWorkerLogicCall = + ::android::hardware::audio::common::PostponedMethodCall; + void StreamOutWorkerLogic::onBufferStateChange(size_t bufferFramesLeft) { + // It is assumed that all 'on...StateChange' callbacks originate from the same thread, + // thus it is impossible to get one callback while processing another. + if (!mCallTasks.tryObtainOrPush(StreamOutWorkerLogicCall::create( + this, &StreamOutWorkerLogic::onBufferStateChangeImpl, (size_t)bufferFramesLeft))) { + // Call queued, will be executed after 'cycle' ends processing command. + return; + } + onBufferStateChangeImpl(bufferFramesLeft); + if (!mCallTasks.release()) { + LOG(FATAL) << __func__ << ": call tasks queue release failed"; + } +} + +void StreamOutWorkerLogic::onBufferStateChangeImpl(size_t bufferFramesLeft) { const StreamDescriptor::State state = mState; const DrainState drainState = mDrainState; LOG(DEBUG) << __func__ << ": state: " << toString(state) << ", drainState: " << drainState << ", bufferFramesLeft: " << bufferFramesLeft; if (state == StreamDescriptor::State::TRANSFERRING || drainState == DrainState::EN_SENT) { if (state == StreamDescriptor::State::TRANSFERRING) { - mState = StreamDescriptor::State::ACTIVE; + switchFromTransientState(StreamDescriptor::State::ACTIVE); } std::shared_ptr asyncCallback = mContext->getAsyncCallback(); if (asyncCallback != nullptr) { @@ -437,18 +482,34 @@ void StreamOutWorkerLogic::onBufferStateChange(size_t bufferFramesLeft) { } void StreamOutWorkerLogic::onClipStateChange(size_t clipFramesLeft, bool hasNextClip) { + if (!mCallTasks.tryObtainOrPush( + StreamOutWorkerLogicCall::create(this, &StreamOutWorkerLogic::onClipStateChangeImpl, + (size_t)clipFramesLeft, (bool)hasNextClip))) { + // Call queued, will be executed after 'cycle' ends processing command. + return; + } + onClipStateChangeImpl(clipFramesLeft, hasNextClip); + if (!mCallTasks.release()) { + LOG(FATAL) << __func__ << ": call tasks queue release failed"; + } +} + +void StreamOutWorkerLogic::onClipStateChangeImpl(size_t clipFramesLeft, bool hasNextClip) { const DrainState drainState = mDrainState; std::shared_ptr asyncCallback = mContext->getAsyncCallback(); LOG(DEBUG) << __func__ << ": drainState: " << drainState << "; clipFramesLeft " << clipFramesLeft << "; hasNextClip? " << hasNextClip << "; asyncCallback? " << (asyncCallback != nullptr); if (drainState != DrainState::NONE && clipFramesLeft == 0) { - mState = - hasNextClip ? StreamDescriptor::State::TRANSFERRING : StreamDescriptor::State::IDLE; + if (hasNextClip) { + switchToTransientState(StreamDescriptor::State::TRANSFERRING); + } else { + switchFromTransientState(StreamDescriptor::State::IDLE); + } mDrainState = DrainState::NONE; if ((drainState == DrainState::ALL || drainState == DrainState::EN_SENT) && asyncCallback != nullptr) { - LOG(DEBUG) << __func__ << ": sending onDrainReady"; + LOG(DEBUG) << __func__ << ": sending onDrainReady (end of clip)"; // For EN_SENT, this is the second onDrainReady which notifies about clip transition. ndk::ScopedAStatus status = asyncCallback->onDrainReady(); if (!status.isOk()) { @@ -459,7 +520,7 @@ void StreamOutWorkerLogic::onClipStateChange(size_t clipFramesLeft, bool hasNext // The stream state does not change, it is still draining. mDrainState = DrainState::EN_SENT; if (asyncCallback != nullptr) { - LOG(DEBUG) << __func__ << ": sending onDrainReady"; + LOG(DEBUG) << __func__ << ": sending onDrainReady (ready for next clip data)"; ndk::ScopedAStatus status = asyncCallback->onDrainReady(); if (!status.isOk()) { LOG(ERROR) << __func__ << ": error from onDrainReady: " << status; @@ -469,7 +530,7 @@ void StreamOutWorkerLogic::onClipStateChange(size_t clipFramesLeft, bool hasNext } StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() { - // Non-blocking mode is handled within 'onClipStateChange' + // Non-blocking mode is handled within 'on{Buffer|Clip}StateChange' if (std::shared_ptr asyncCallback = mContext->getAsyncCallback(); mState == StreamDescriptor::State::DRAINING && asyncCallback == nullptr) { if (auto stateDurationMs = std::chrono::duration_cast( @@ -499,7 +560,14 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() { << kThreadName; StreamDescriptor::Reply reply{}; reply.status = STATUS_BAD_VALUE; + ::android::hardware::audio::common::PostponedMethodsCaller caller( + mCallTasks); using Tag = StreamDescriptor::Command::Tag; + // Do not need to obtain the call tasks queue for commands that do not change the state. + if (command.getTag() != Tag::halReservedExit && command.getTag() != Tag::getStatus) { + // Waits until any ongoing execution of 'on...StateChange' call finishes. + caller.obtainQueue(); + } switch (command.getTag()) { case Tag::halReservedExit: { const int32_t cookie = command.get(); @@ -572,7 +640,7 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() { if (asyncCallback == nullptr || mState != StreamDescriptor::State::DRAIN_PAUSED) { mState = StreamDescriptor::State::PAUSED; - } else { + } else if (mDrainState != DrainState::EN_SENT) { mState = StreamDescriptor::State::TRANSFER_PAUSED; } } else if (mState == StreamDescriptor::State::IDLE || @@ -680,6 +748,56 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() { populateReplyWrongState(&reply, command); } break; + case Tag::flushFromFrame: { + if (mContext->isOffload() && mContext->getFormat().type == AudioFormatType::PCM) { + bool validState = false; + switch (mState) { + case StreamDescriptor::State::ACTIVE: + case StreamDescriptor::State::TRANSFERRING: + case StreamDescriptor::State::DRAINING: + case StreamDescriptor::State::TRANSFER_PAUSED: + case StreamDescriptor::State::PAUSED: + case StreamDescriptor::State::DRAIN_PAUSED: + validState = true; + break; + default: + LOG(ERROR) + << __func__ + << ": flushFromFrame, invalid state: " << toString(mState.load()); + populateReplyWrongState(&reply, command); + } + if (validState) { + const int32_t flushFromFrameRequest = command.get(); + const FlushFromFrameAccuracy accuracy = + (FlushFromFrameAccuracy)((flushFromFrameRequest >> + StreamDescriptor:: + FLUSH_FROM_FRAME_POSITION_BITS) & + 0x0f); + if (accuracy == FlushFromFrameAccuracy::BEST_EFFORT || + accuracy == FlushFromFrameAccuracy::EXACT) { + static const int32_t kPositionMask = + (1 << StreamDescriptor::FLUSH_FROM_FRAME_POSITION_BITS) - 1; + int position = flushFromFrameRequest & kPositionMask; + const ::android::status_t status = mDriver->flushFromFrame( + accuracy, position, &reply.flushFromPosition); + populateReply(&reply, mIsConnected); + if (status == ::android::BAD_VALUE) { + LOG(INFO) << __func__ << ": flushFromFrame(" << position + << "), flushFromPosition=" << reply.flushFromPosition; + reply.status = STATUS_BAD_VALUE; + } else if (status != ::android::OK) { + LOG(ERROR) << __func__ << ": flushFromFrame failed: " << status; + reply.status = STATUS_INVALID_OPERATION; + } + } else { + LOG(ERROR) << __func__ << ": invalid accuracy: " << toString(accuracy); + reply.status = STATUS_BAD_VALUE; + } + } + } else { + populateReplyUnsupportedCommand(&reply, command); + } + } break; } reply.state = mState; LOG(severity) << __func__ << ": writing reply " << reply.toString(); @@ -705,8 +823,8 @@ bool StreamOutWorkerLogic::write(size_t clientSize, StreamDescriptor::Reply* rep // Amount of data that the HAL module is going to actually use. size_t byteCount = std::min({clientSize, readByteCount, mDataBufferSize}); if (byteCount >= frameSize && mContext->getForceTransientBurst()) { - // In order to prevent the state machine from going to ACTIVE state, - // simulate partial write. + // In order to prevent the state machine from going to ACTIVE state for full write + // (sync) transfers, simulate a partial write. byteCount -= frameSize; } size_t actualFrameCount = 0; @@ -839,6 +957,12 @@ ndk::ScopedAStatus StreamCommonImpl::removeEffect( return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } +ndk::ScopedAStatus StreamCommonImpl::createMmapBuffer(MmapBufferDescriptor* _aidl_return) { + LOG(DEBUG) << __func__; + (void)_aidl_return; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + ndk::ScopedAStatus StreamCommonImpl::close() { LOG(DEBUG) << __func__; if (!isClosed()) { @@ -927,12 +1051,38 @@ void StreamCommonImpl::stopWorker() { mWorkerStopIssued = true; } +ndk::ScopedAStatus validateMetadataAttributeTags(const std::vector& tags) { + for (auto& tag : tags) { + if (!common::isVendorExtension(tag)) { + LOG(ERROR) << __func__ << ": metadata attribute tag " << tag << " is invalid."; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + return ndk::ScopedAStatus::ok(); +} + ndk::ScopedAStatus StreamCommonImpl::updateMetadataCommon(const Metadata& metadata) { LOG(DEBUG) << __func__; if (!isClosed()) { if (metadata.index() != mMetadata.index()) { LOG(FATAL) << __func__ << ": changing metadata variant is not allowed"; } + ndk::ScopedAStatus status = std::visit( + [&](const auto& data) -> ndk::ScopedAStatus { + for (const auto& track : data.tracks) { + ndk::ScopedAStatus status = validateMetadataAttributeTags(track.tags); + if (!status.isOk()) { + return status; + } + } + return ndk::ScopedAStatus::ok(); + }, + metadata); + + if (!status.isOk()) { + return status; + } + mMetadata = metadata; return ndk::ScopedAStatus::ok(); } @@ -1066,6 +1216,11 @@ void StreamOut::defaultOnClose() { mContextInstance.reset(); } +ndk::ScopedAStatus StreamOut::updateMetadata(const SourceMetadata& in_sourceMetadata) { + RETURN_STATUS_IF_ERROR(validateMetadata(in_sourceMetadata)); + return updateMetadataCommon(in_sourceMetadata); +} + ndk::ScopedAStatus StreamOut::updateOffloadMetadata( const AudioOffloadMetadata& in_offloadMetadata) { LOG(DEBUG) << __func__; @@ -1162,6 +1317,19 @@ ndk::ScopedAStatus StreamOut::selectPresentation(int32_t in_presentationId, int3 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } +ndk::ScopedAStatus StreamOut::validateMetadata(const SourceMetadata& sourceMetadata) { + for (const auto& track : sourceMetadata.tracks) { + if (const auto& codecMime = track.codecProvenance; + codecMime.has_value() && !codecMime->empty()) { + if (!aidl::android::hardware::audio::common::isAudioMimeType(*codecMime)) { + LOG(ERROR) << __func__ << ": invalid audio MIME type: \"" << *codecMime << "\""; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + } + return ndk::ScopedAStatus::ok(); +} + StreamOutHwVolumeHelper::StreamOutHwVolumeHelper(const StreamContext* context) : mChannelCount(getChannelCount(context->getChannelLayout())) {} diff --git a/audio/Telephony.cpp b/audio/Telephony.cpp index d9da39f..f13b326 100644 --- a/audio/Telephony.cpp +++ b/audio/Telephony.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "AHAL_Telephony" -#include +#include #include #include diff --git a/audio/XsdcConversion.cpp b/audio/XsdcConversion.cpp index 5845903..fbda7aa 100644 --- a/audio/XsdcConversion.cpp +++ b/audio/XsdcConversion.cpp @@ -19,12 +19,13 @@ #include #define LOG_TAG "AHAL_Config" -#include +#include #include #include #include #include +#include #include #include #include @@ -59,6 +60,7 @@ using aidl::android::media::audio::common::AudioPortConfig; using aidl::android::media::audio::common::AudioPortDeviceExt; using aidl::android::media::audio::common::AudioPortExt; using aidl::android::media::audio::common::AudioPortMixExt; +using aidl::android::media::audio::common::AudioProductStrategyType; using aidl::android::media::audio::common::AudioProfile; using aidl::android::media::audio::common::AudioSource; using aidl::android::media::audio::common::AudioStreamType; @@ -284,7 +286,14 @@ ConversionResult convertGainModeToAidl(const std::vector(legacyGainMode); } } - return gainModeMask; + ConversionResult result = legacy2aidl_audio_gain_mode_t_int32_t_mask( + static_cast(gainModeMask)); + if (!result.ok()) { + LOG(ERROR) << __func__ << " Review Audio Policy config, " << gainModeMask + << " has invalid gain mode(s)."; + return unexpected(BAD_VALUE); + } + return result; } ConversionResult convertChannelMaskToAidl( @@ -817,21 +826,21 @@ ConversionResult convertCurvePointToAidl( return aidlCurvePoint; } -/** - * The hard coded id must be in sync with policy.h definition of legacy strategy ids. - */ std::unordered_map getLegacyProductStrategyMap() { #define STRATEGY_ENTRY(name, id) {"STRATEGY_" #name, static_cast(id)} - return {STRATEGY_ENTRY(MEDIA, 5), - STRATEGY_ENTRY(PHONE, 0), - STRATEGY_ENTRY(SONIFICATION, 1), - STRATEGY_ENTRY(SONIFICATION_RESPECTFUL, 4), - STRATEGY_ENTRY(DTMF, 6), - STRATEGY_ENTRY(ENFORCED_AUDIBLE, 2), - STRATEGY_ENTRY(CALL_ASSISTANT, 7), - STRATEGY_ENTRY(TRANSMITTED_THROUGH_SPEAKER,8), - STRATEGY_ENTRY(ACCESSIBILITY, 3)}; + return {STRATEGY_ENTRY(MEDIA, AudioProductStrategyType::MEDIA), + STRATEGY_ENTRY(PHONE, AudioProductStrategyType::PHONE), + STRATEGY_ENTRY(SONIFICATION, AudioProductStrategyType::SONIFICATION), + STRATEGY_ENTRY(SONIFICATION_RESPECTFUL, + AudioProductStrategyType::SONIFICATION_RESPECTFUL), + STRATEGY_ENTRY(DTMF, AudioProductStrategyType::DTMF), + STRATEGY_ENTRY(ENFORCED_AUDIBLE, AudioProductStrategyType::ENFORCED_AUDIBLE), + STRATEGY_ENTRY(CALL_ASSISTANT, AudioProductStrategyType::CALL_ASSISTANT), + STRATEGY_ENTRY(TRANSMITTED_THROUGH_SPEAKER, + AudioProductStrategyType::TRANSMITTED_THROUGH_SPEAKER), + STRATEGY_ENTRY(ACCESSIBILITY, AudioProductStrategyType::ACCESSIBILITY), + STRATEGY_ENTRY(ASSISTANT, AudioProductStrategyType::ASSISTANT)}; #undef STRATEGY_ENTRY } diff --git a/audio/alsa/Mixer.cpp b/audio/alsa/Mixer.cpp index e72502b..cd9287d 100644 --- a/audio/alsa/Mixer.cpp +++ b/audio/alsa/Mixer.cpp @@ -18,7 +18,7 @@ #include #define LOG_TAG "AHAL_AlsaMixer" -#include +#include #include #include diff --git a/audio/alsa/ModuleAlsa.cpp b/audio/alsa/ModuleAlsa.cpp index 9a2cce7..4c1e1a4 100644 --- a/audio/alsa/ModuleAlsa.cpp +++ b/audio/alsa/ModuleAlsa.cpp @@ -18,9 +18,9 @@ #include -#include +#include -#include "Utils.h" +#include "UtilsAlsa.h" #include "core-impl/ModuleAlsa.h" extern "C" { @@ -45,7 +45,8 @@ ndk::ScopedAStatus ModuleAlsa::populateConnectedDevicePort(AudioPort* audioPort, } alsa_device_profile* profile = proxy.getProfile(); - std::vector channels = alsa::getChannelMasksFromProfile(profile); + std::vector channels = + alsa::getChannelMasksFromProfile(profile, isDevicePortSupportAmbisonics(*audioPort)); std::vector sampleRates = alsa::getSampleRatesFromProfile(profile); for (size_t i = 0; i < std::min(MAX_PROFILE_FORMATS, AUDIO_PORT_MAX_AUDIO_PROFILES) && @@ -65,4 +66,11 @@ ndk::ScopedAStatus ModuleAlsa::populateConnectedDevicePort(AudioPort* audioPort, return ndk::ScopedAStatus::ok(); } +bool ModuleAlsa::isDevicePortSupportAmbisonics(const AudioPort&) { + // The default implementation always returns 'false'. Vendor implementations + // may supply logic which is based on the knowledge of SoC capabilities, + // or based on USB device ID. + return false; +} + } // namespace aidl::android::hardware::audio::core diff --git a/audio/alsa/StreamAlsa.cpp b/audio/alsa/StreamAlsa.cpp index 7a44cc7..4f66ecb 100644 --- a/audio/alsa/StreamAlsa.cpp +++ b/audio/alsa/StreamAlsa.cpp @@ -14,88 +14,25 @@ * limitations under the License. */ -#include #include #define LOG_TAG "AHAL_StreamAlsa" -#include - -#include -#include -#include -#include +#include +#include "UtilsAlsa.h" #include "core-impl/StreamAlsa.h" -using aidl::android::hardware::audio::common::getChannelCount; - namespace aidl::android::hardware::audio::core { StreamAlsa::StreamAlsa(StreamContext* context, const Metadata& metadata, int readWriteRetries) - : StreamCommonImpl(context, metadata), - mBufferSizeFrames(getContext().getBufferSizeInFrames()), - mFrameSizeBytes(getContext().getFrameSize()), - mSampleRate(getContext().getSampleRate()), - mIsInput(isInput(metadata)), - mConfig(alsa::getPcmConfig(getContext(), mIsInput)), - mReadWriteRetries(readWriteRetries) {} + : StreamAlsaBase(context, metadata, readWriteRetries) {} StreamAlsa::~StreamAlsa() { cleanupWorker(); } -::android::NBAIO_Format StreamAlsa::getPipeFormat() const { - const audio_format_t audioFormat = VALUE_OR_FATAL( - aidl2legacy_AudioFormatDescription_audio_format_t(getContext().getFormat())); - const int channelCount = getChannelCount(getContext().getChannelLayout()); - return ::android::Format_from_SR_C(getContext().getSampleRate(), channelCount, audioFormat); -} - -::android::sp<::android::MonoPipe> StreamAlsa::makeSink(bool writeCanBlock) { - const ::android::NBAIO_Format format = getPipeFormat(); - auto sink = ::android::sp<::android::MonoPipe>::make(mBufferSizeFrames, format, writeCanBlock); - const ::android::NBAIO_Format offers[1] = {format}; - size_t numCounterOffers = 0; - ssize_t index = sink->negotiate(offers, 1, nullptr, numCounterOffers); - LOG_IF(FATAL, index != 0) << __func__ << ": Negotiation for the sink failed, index = " << index; - return sink; -} - -::android::sp<::android::MonoPipeReader> StreamAlsa::makeSource(::android::MonoPipe* pipe) { - const ::android::NBAIO_Format format = getPipeFormat(); - const ::android::NBAIO_Format offers[1] = {format}; - auto source = ::android::sp<::android::MonoPipeReader>::make(pipe); - size_t numCounterOffers = 0; - ssize_t index = source->negotiate(offers, 1, nullptr, numCounterOffers); - LOG_IF(FATAL, index != 0) << __func__ - << ": Negotiation for the source failed, index = " << index; - return source; -} - -::android::status_t StreamAlsa::init(DriverCallbackInterface* /*callback*/) { - return mConfig.has_value() ? ::android::OK : ::android::NO_INIT; -} - -::android::status_t StreamAlsa::drain(StreamDescriptor::DrainMode) { - if (!mIsInput) { - static constexpr float kMicrosPerSecond = MICROS_PER_SECOND; - const size_t delayUs = static_cast( - std::roundf(mBufferSizeFrames * kMicrosPerSecond / mSampleRate)); - usleep(delayUs); - } - return ::android::OK; -} - -::android::status_t StreamAlsa::flush() { - return ::android::OK; -} - -::android::status_t StreamAlsa::pause() { - return ::android::OK; -} - ::android::status_t StreamAlsa::standby() { - teardownIo(); + mAlsaDeviceProxies.clear(); return ::android::OK; } @@ -104,54 +41,7 @@ StreamAlsa::~StreamAlsa() { // This is a resume after a pause. return ::android::OK; } - decltype(mAlsaDeviceProxies) alsaDeviceProxies; - decltype(mSources) sources; - decltype(mSinks) sinks; - for (const auto& device : getDeviceProfiles()) { - if ((device.direction == PCM_OUT && mIsInput) || - (device.direction == PCM_IN && !mIsInput)) { - continue; - } - alsa::DeviceProxy proxy; - if (device.isExternal) { - // Always ask alsa configure as required since the configuration should be supported - // by the connected device. That is guaranteed by `setAudioPortConfig` and - // `setAudioPatch`. - proxy = alsa::openProxyForExternalDevice( - device, const_cast(&mConfig.value()), - true /*require_exact_match*/); - } else { - proxy = alsa::openProxyForAttachedDevice( - device, const_cast(&mConfig.value()), mBufferSizeFrames); - } - if (proxy.get() == nullptr) { - return ::android::NO_INIT; - } - alsaDeviceProxies.push_back(std::move(proxy)); - auto sink = makeSink(mIsInput); // Do not block the writer when it is on our thread. - if (sink != nullptr) { - sinks.push_back(sink); - } else { - return ::android::NO_INIT; - } - if (auto source = makeSource(sink.get()); source != nullptr) { - sources.push_back(source); - } else { - return ::android::NO_INIT; - } - } - if (alsaDeviceProxies.empty()) { - return ::android::NO_INIT; - } - mAlsaDeviceProxies = std::move(alsaDeviceProxies); - mSources = std::move(sources); - mSinks = std::move(sinks); - mIoThreadIsRunning = true; - for (size_t i = 0; i < mAlsaDeviceProxies.size(); ++i) { - mIoThreads.emplace_back(mIsInput ? &StreamAlsa::inputIoThread : &StreamAlsa::outputIoThread, - this, i); - } - return ::android::OK; + return openDeviceProxies(&mAlsaDeviceProxies); } ::android::status_t StreamAlsa::transfer(void* buffer, size_t frameCount, size_t* actualFrameCount, @@ -163,28 +53,20 @@ StreamAlsa::~StreamAlsa() { const size_t bytesToTransfer = frameCount * mFrameSizeBytes; unsigned maxLatency = 0; if (mIsInput) { - const size_t i = 0; // For the input case, only support a single device. - LOG(VERBOSE) << __func__ << ": reading from sink " << i; - ssize_t framesRead = mSources[i]->read(buffer, frameCount); - LOG_IF(FATAL, framesRead < 0) << "Error reading from the pipe: " << framesRead; - if (ssize_t framesMissing = static_cast(frameCount) - framesRead; - framesMissing > 0) { - LOG(WARNING) << __func__ << ": incomplete data received, inserting " << framesMissing - << " frames of silence"; - memset(static_cast(buffer) + framesRead * mFrameSizeBytes, 0, - framesMissing * mFrameSizeBytes); + if (int ret = proxy_read_with_retries(mAlsaDeviceProxies[0].get(), buffer, bytesToTransfer, + mReadWriteRetries); + ret != 0) { + LOG(WARNING) << __func__ << ": read failed: " << ret; + return ::android::INVALID_OPERATION; } - maxLatency = proxy_get_latency(mAlsaDeviceProxies[i].get()); + maxLatency = proxy_get_latency(mAlsaDeviceProxies[0].get()); } else { alsa::applyGain(buffer, mGain, bytesToTransfer, mConfig.value().format, mConfig->channels); for (size_t i = 0; i < mAlsaDeviceProxies.size(); ++i) { - LOG(VERBOSE) << __func__ << ": writing into sink " << i; - ssize_t framesWritten = mSinks[i]->write(buffer, frameCount); - LOG_IF(FATAL, framesWritten < 0) << "Error writing into the pipe: " << framesWritten; - if (ssize_t framesLost = static_cast(frameCount) - framesWritten; - framesLost > 0) { - LOG(WARNING) << __func__ << ": sink " << i << " incomplete data sent, dropping " - << framesLost << " frames"; + if (int ret = proxy_write_with_retries(mAlsaDeviceProxies[i].get(), buffer, + bytesToTransfer, mReadWriteRetries); + ret != 0) { + LOG(WARNING) << __func__ << ": write failed for device " << i << ": " << ret; } maxLatency = std::max(maxLatency, proxy_get_latency(mAlsaDeviceProxies[i].get())); } @@ -195,132 +77,8 @@ StreamAlsa::~StreamAlsa() { return ::android::OK; } -::android::status_t StreamAlsa::refinePosition(StreamDescriptor::Position* position) { - if (mAlsaDeviceProxies.empty()) { - LOG(WARNING) << __func__ << ": no opened devices"; - return ::android::NO_INIT; - } - // Since the proxy can only count transferred frames since its creation, - // we override its counter value with ours and let it to correct for buffered frames. - alsa::resetTransferredFrames(mAlsaDeviceProxies[0], position->frames); - if (mIsInput) { - if (int ret = proxy_get_capture_position(mAlsaDeviceProxies[0].get(), &position->frames, - &position->timeNs); - ret != 0) { - LOG(WARNING) << __func__ << ": failed to retrieve capture position: " << ret; - return ::android::INVALID_OPERATION; - } - } else { - uint64_t hwFrames; - struct timespec timestamp; - if (int ret = proxy_get_presentation_position(mAlsaDeviceProxies[0].get(), &hwFrames, - ×tamp); - ret == 0) { - if (hwFrames > std::numeric_limits::max()) { - hwFrames -= std::numeric_limits::max(); - } - position->frames = static_cast(hwFrames); - position->timeNs = audio_utils_ns_from_timespec(×tamp); - } else { - LOG(WARNING) << __func__ << ": failed to retrieve presentation position: " << ret; - return ::android::INVALID_OPERATION; - } - } - return ::android::OK; -} - void StreamAlsa::shutdown() { - teardownIo(); -} - -ndk::ScopedAStatus StreamAlsa::setGain(float gain) { - mGain = gain; - return ndk::ScopedAStatus::ok(); -} - -void StreamAlsa::inputIoThread(size_t idx) { -#if defined(__ANDROID__) - setWorkerThreadPriority(pthread_gettid_np(pthread_self())); - const std::string threadName = (std::string("in_") + std::to_string(idx)).substr(0, 15); - pthread_setname_np(pthread_self(), threadName.c_str()); -#endif - const size_t bufferSize = mBufferSizeFrames * mFrameSizeBytes; - std::vector buffer(bufferSize); - while (mIoThreadIsRunning) { - if (int ret = proxy_read_with_retries(mAlsaDeviceProxies[idx].get(), &buffer[0], bufferSize, - mReadWriteRetries); - ret == 0) { - size_t bufferFramesWritten = 0; - while (bufferFramesWritten < mBufferSizeFrames) { - if (!mIoThreadIsRunning) return; - ssize_t framesWrittenOrError = - mSinks[idx]->write(&buffer[0], mBufferSizeFrames - bufferFramesWritten); - if (framesWrittenOrError >= 0) { - bufferFramesWritten += framesWrittenOrError; - } else { - LOG(WARNING) << __func__ << "[" << idx - << "]: Error while writing into the pipe: " - << framesWrittenOrError; - } - } - } else { - // Errors when the stream is being stopped are expected. - LOG_IF(WARNING, mIoThreadIsRunning) - << __func__ << "[" << idx << "]: Error reading from ALSA: " << ret; - } - } -} - -void StreamAlsa::outputIoThread(size_t idx) { -#if defined(__ANDROID__) - setWorkerThreadPriority(pthread_gettid_np(pthread_self())); - const std::string threadName = (std::string("out_") + std::to_string(idx)).substr(0, 15); - pthread_setname_np(pthread_self(), threadName.c_str()); -#endif - const size_t bufferSize = mBufferSizeFrames * mFrameSizeBytes; - std::vector buffer(bufferSize); - while (mIoThreadIsRunning) { - ssize_t framesReadOrError = mSources[idx]->read(&buffer[0], mBufferSizeFrames); - if (framesReadOrError > 0) { - int ret = proxy_write_with_retries(mAlsaDeviceProxies[idx].get(), &buffer[0], - framesReadOrError * mFrameSizeBytes, - mReadWriteRetries); - // Errors when the stream is being stopped are expected. - LOG_IF(WARNING, ret != 0 && mIoThreadIsRunning) - << __func__ << "[" << idx << "]: Error writing into ALSA: " << ret; - } else if (framesReadOrError == 0) { - // MonoPipeReader does not have a blocking read, while use of std::condition_variable - // requires use of a mutex. For now, just do a 1ms sleep. Consider using a different - // pipe / ring buffer mechanism. - if (mIoThreadIsRunning) usleep(1000); - } else { - LOG(WARNING) << __func__ << "[" << idx - << "]: Error while reading from the pipe: " << framesReadOrError; - } - } -} - -void StreamAlsa::teardownIo() { - mIoThreadIsRunning = false; - if (mIsInput) { - LOG(DEBUG) << __func__ << ": shutting down pipes"; - for (auto& sink : mSinks) { - sink->shutdown(true); - } - } - LOG(DEBUG) << __func__ << ": stopping PCM streams"; - for (const auto& proxy : mAlsaDeviceProxies) { - proxy_stop(proxy.get()); - } - LOG(DEBUG) << __func__ << ": joining threads"; - for (auto& thread : mIoThreads) { - if (thread.joinable()) thread.join(); - } - mIoThreads.clear(); - LOG(DEBUG) << __func__ << ": closing PCM devices"; mAlsaDeviceProxies.clear(); - mSources.clear(); - mSinks.clear(); } -} // namespace aidl::android::hardware::audio::core +} // namespace aidl::android::hardware::audio::core \ No newline at end of file diff --git a/audio/alsa/StreamAlsaBase.cpp b/audio/alsa/StreamAlsaBase.cpp new file mode 100644 index 0000000..9316517 --- /dev/null +++ b/audio/alsa/StreamAlsaBase.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2026 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 + +#define LOG_TAG "AHAL_StreamAlsaBase" +#include + +#include +#include +#include + +#include "UtilsAlsa.h" +#include "core-impl/StreamAlsaBase.h" + +namespace aidl::android::hardware::audio::core { + +StreamAlsaBase::StreamAlsaBase(StreamContext* context, const Metadata& metadata, + int readWriteRetries) + : StreamCommonImpl(context, metadata), + mBufferSizeFrames(getContext().getBufferSizeInFrames()), + mFrameSizeBytes(getContext().getFrameSize()), + mSampleRate(getContext().getSampleRate()), + mIsInput(isInput(metadata)), + mConfig(alsa::getPcmConfig(getContext(), mIsInput)), + mReadWriteRetries(readWriteRetries) {} + +StreamAlsaBase::~StreamAlsaBase() { + cleanupWorker(); +} + +::android::status_t StreamAlsaBase::init(DriverCallbackInterface* /*callback*/) { + return mConfig.has_value() ? ::android::OK : ::android::NO_INIT; +} + +::android::status_t StreamAlsaBase::drain(StreamDescriptor::DrainMode) { + if (!mIsInput) { + static constexpr float kMicrosPerSecond = MICROS_PER_SECOND; + const size_t delayUs = static_cast( + std::roundf(mBufferSizeFrames * kMicrosPerSecond / mSampleRate)); + usleep(delayUs); + } + return ::android::OK; +} + +::android::status_t StreamAlsaBase::flush() { + return ::android::OK; +} + +::android::status_t StreamAlsaBase::pause() { + return ::android::OK; +} + +::android::status_t StreamAlsaBase::refinePosition(StreamDescriptor::Position* position) { + if (mAlsaDeviceProxies.empty()) { + LOG(WARNING) << __func__ << ": no opened devices"; + return ::android::NO_INIT; + } + // Since the proxy can only count transferred frames since its creation, + // we override its counter value with ours and let it to correct for buffered frames. + alsa::resetTransferredFrames(mAlsaDeviceProxies[0], position->frames); + if (mIsInput) { + if (int ret = proxy_get_capture_position(mAlsaDeviceProxies[0].get(), &position->frames, + &position->timeNs); + ret != 0) { + LOG(WARNING) << __func__ << ": failed to retrieve capture position: " << ret; + return ::android::INVALID_OPERATION; + } + } else { + uint64_t hwFrames; + struct timespec timestamp; + if (int ret = proxy_get_presentation_position(mAlsaDeviceProxies[0].get(), &hwFrames, + ×tamp); + ret == 0) { + if (hwFrames > std::numeric_limits::max()) { + hwFrames -= std::numeric_limits::max(); + } + position->frames = static_cast(hwFrames); + position->timeNs = audio_utils_ns_from_timespec(×tamp); + } else { + LOG(WARNING) << __func__ << ": failed to retrieve presentation position: " << ret; + return ::android::INVALID_OPERATION; + } + } + return ::android::OK; +} + +ndk::ScopedAStatus StreamAlsaBase::setGain(float gain) { + mGain = gain; + return ndk::ScopedAStatus::ok(); +} + +::android::status_t StreamAlsaBase::openDeviceProxies(std::vector* proxies) { + for (const auto& device : getDeviceProfiles()) { + if ((device.direction == PCM_OUT && mIsInput) || + (device.direction == PCM_IN && !mIsInput)) { + continue; + } + alsa::DeviceProxy proxy; + if (device.isExternal) { + // Always ask alsa configure as required since the configuration should be supported + // by the connected device. That is guaranteed by `setAudioPortConfig` and + // `setAudioPatch`. + proxy = alsa::openProxyForExternalDevice( + device, const_cast(&mConfig.value()), + true /*require_exact_match*/); + } else { + proxy = alsa::openProxyForAttachedDevice( + device, const_cast(&mConfig.value()), mBufferSizeFrames); + } + if (proxy.get() == nullptr) { + return ::android::NO_INIT; + } + proxies->push_back(std::move(proxy)); + } + if (proxies->empty()) { + return ::android::NO_INIT; + } + return ::android::OK; +} + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/alsa/StreamAlsaMonoPipe.cpp b/audio/alsa/StreamAlsaMonoPipe.cpp new file mode 100644 index 0000000..3738f30 --- /dev/null +++ b/audio/alsa/StreamAlsaMonoPipe.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2026 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_StreamAlsaMonoPipe" +#include + +#include + +#include "core-impl/StreamAlsaMonoPipe.h" + +using aidl::android::hardware::audio::common::getChannelCount; + +namespace aidl::android::hardware::audio::core { + +StreamAlsaMonoPipe::StreamAlsaMonoPipe(StreamContext* context, const Metadata& metadata, + int readWriteRetries) + : StreamAlsaBase(context, metadata, readWriteRetries) {} + +StreamAlsaMonoPipe::~StreamAlsaMonoPipe() { + cleanupWorker(); +} + +::android::NBAIO_Format StreamAlsaMonoPipe::getPipeFormat() const { + const audio_format_t audioFormat = VALUE_OR_FATAL( + aidl2legacy_AudioFormatDescription_audio_format_t(getContext().getFormat())); + const int channelCount = getChannelCount(getContext().getChannelLayout()); + return ::android::Format_from_SR_C(getContext().getSampleRate(), channelCount, audioFormat); +} + +::android::sp<::android::MonoPipe> StreamAlsaMonoPipe::makeSink(bool writeCanBlock) { + const ::android::NBAIO_Format format = getPipeFormat(); + auto sink = ::android::sp<::android::MonoPipe>::make(mBufferSizeFrames, format, writeCanBlock); + const ::android::NBAIO_Format offers[1] = {format}; + size_t numCounterOffers = 0; + ssize_t index = sink->negotiate(offers, 1, nullptr, numCounterOffers); + LOG_IF(FATAL, index != 0) << __func__ << ": Negotiation for the sink failed, index = " << index; + return sink; +} + +::android::sp<::android::MonoPipeReader> StreamAlsaMonoPipe::makeSource(::android::MonoPipe* pipe) { + const ::android::NBAIO_Format format = getPipeFormat(); + const ::android::NBAIO_Format offers[1] = {format}; + auto source = ::android::sp<::android::MonoPipeReader>::make(pipe); + size_t numCounterOffers = 0; + ssize_t index = source->negotiate(offers, 1, nullptr, numCounterOffers); + LOG_IF(FATAL, index != 0) << __func__ + << ": Negotiation for the source failed, index = " << index; + return source; +} + +::android::status_t StreamAlsaMonoPipe::standby() { + teardownIo(); + return ::android::OK; +} + +::android::status_t StreamAlsaMonoPipe::start() { + if (!mAlsaDeviceProxies.empty()) { + // This is a resume after a pause. + return ::android::OK; + } + decltype(mAlsaDeviceProxies) alsaDeviceProxies; + decltype(mSources) sources; + decltype(mSinks) sinks; + if (::android::status_t status = openDeviceProxies(&alsaDeviceProxies); + status != ::android::OK) { + return status; + } + for (size_t i = 0; i < alsaDeviceProxies.size(); ++i) { + auto sink = makeSink(mIsInput); // Do not block the writer when it is on our thread. + if (sink != nullptr) { + sinks.push_back(sink); + } else { + return ::android::NO_INIT; + } + if (auto source = makeSource(sink.get()); source != nullptr) { + sources.push_back(source); + } else { + return ::android::NO_INIT; + } + } + mAlsaDeviceProxies = std::move(alsaDeviceProxies); + mSources = std::move(sources); + mSinks = std::move(sinks); + mIoThreadIsRunning = true; + for (size_t i = 0; i < mAlsaDeviceProxies.size(); ++i) { + mIoThreads.emplace_back( + mIsInput ? &StreamAlsaMonoPipe::inputIoThread : &StreamAlsaMonoPipe::outputIoThread, + this, i); + } + return ::android::OK; +} + +::android::status_t StreamAlsaMonoPipe::transfer(void* buffer, size_t frameCount, + size_t* actualFrameCount, int32_t* latencyMs) { + if (mAlsaDeviceProxies.empty()) { + LOG(FATAL) << __func__ << ": no opened devices"; + return ::android::NO_INIT; + } + const size_t bytesToTransfer = frameCount * mFrameSizeBytes; + unsigned maxLatency = 0; + if (mIsInput) { + const size_t i = 0; // For the input case, only support a single device. + LOG(VERBOSE) << __func__ << ": reading from sink " << i; + ssize_t framesRead = mSources[i]->read(buffer, frameCount); + LOG_IF(FATAL, framesRead < 0) << "Error reading from the pipe: " << framesRead; + if (ssize_t framesMissing = static_cast(frameCount) - framesRead; + framesMissing > 0) { + LOG(WARNING) << __func__ << ": incomplete data received, inserting " << framesMissing + << " frames of silence"; + memset(static_cast(buffer) + framesRead * mFrameSizeBytes, 0, + framesMissing * mFrameSizeBytes); + } + maxLatency = proxy_get_latency(mAlsaDeviceProxies[i].get()); + } else { + alsa::applyGain(buffer, mGain, bytesToTransfer, mConfig.value().format, mConfig->channels); + for (size_t i = 0; i < mAlsaDeviceProxies.size(); ++i) { + LOG(VERBOSE) << __func__ << ": writing into sink " << i; + ssize_t framesWritten = mSinks[i]->write(buffer, frameCount); + LOG_IF(FATAL, framesWritten < 0) << "Error writing into the pipe: " << framesWritten; + if (ssize_t framesLost = static_cast(frameCount) - framesWritten; + framesLost > 0) { + LOG(WARNING) << __func__ << ": sink " << i << " incomplete data sent, dropping " + << framesLost << " frames"; + } + maxLatency = std::max(maxLatency, proxy_get_latency(mAlsaDeviceProxies[i].get())); + } + } + *actualFrameCount = frameCount; + maxLatency = std::min(maxLatency, static_cast(std::numeric_limits::max())); + *latencyMs = maxLatency; + return ::android::OK; +} + +void StreamAlsaMonoPipe::shutdown() { + teardownIo(); +} + +void StreamAlsaMonoPipe::inputIoThread(size_t idx) { +#if defined(__ANDROID__) + setWorkerThreadPriority(pthread_gettid_np(pthread_self())); + const std::string threadName = (std::string("in_") + std::to_string(idx)).substr(0, 15); + pthread_setname_np(pthread_self(), threadName.c_str()); +#endif + const size_t bufferSize = mBufferSizeFrames * mFrameSizeBytes; + std::vector buffer(bufferSize); + while (mIoThreadIsRunning) { + if (int ret = proxy_read_with_retries(mAlsaDeviceProxies[idx].get(), &buffer[0], bufferSize, + mReadWriteRetries); + ret == 0) { + size_t bufferFramesWritten = 0; + while (bufferFramesWritten < mBufferSizeFrames) { + if (!mIoThreadIsRunning) return; + ssize_t framesWrittenOrError = + mSinks[idx]->write(&buffer[0], mBufferSizeFrames - bufferFramesWritten); + if (framesWrittenOrError >= 0) { + bufferFramesWritten += framesWrittenOrError; + } else { + LOG(WARNING) << __func__ << "[" << idx + << "]: Error while writing into the pipe: " + << framesWrittenOrError; + } + } + } else { + // Errors when the stream is being stopped are expected. + LOG_IF(WARNING, mIoThreadIsRunning) + << __func__ << "[" << idx << "]: Error reading from ALSA: " << ret; + } + } +} + +void StreamAlsaMonoPipe::outputIoThread(size_t idx) { +#if defined(__ANDROID__) + setWorkerThreadPriority(pthread_gettid_np(pthread_self())); + const std::string threadName = (std::string("out_") + std::to_string(idx)).substr(0, 15); + pthread_setname_np(pthread_self(), threadName.c_str()); +#endif + const size_t bufferSize = mBufferSizeFrames * mFrameSizeBytes; + std::vector buffer(bufferSize); + while (mIoThreadIsRunning) { + ssize_t framesReadOrError = mSources[idx]->read(&buffer[0], mBufferSizeFrames); + if (framesReadOrError > 0) { + int ret = proxy_write_with_retries(mAlsaDeviceProxies[idx].get(), &buffer[0], + framesReadOrError * mFrameSizeBytes, + mReadWriteRetries); + // Errors when the stream is being stopped are expected. + LOG_IF(WARNING, ret != 0 && mIoThreadIsRunning) + << __func__ << "[" << idx << "]: Error writing into ALSA: " << ret; + } else if (framesReadOrError == 0) { + // MonoPipeReader does not have a blocking read, while use of std::condition_variable + // requires use of a mutex. For now, just do a 1ms sleep. Consider using a different + // pipe / ring buffer mechanism. + if (mIoThreadIsRunning) usleep(1000); + } else { + LOG(WARNING) << __func__ << "[" << idx + << "]: Error while reading from the pipe: " << framesReadOrError; + } + } +} + +void StreamAlsaMonoPipe::teardownIo() { + mIoThreadIsRunning = false; + if (mIsInput) { + LOG(DEBUG) << __func__ << ": shutting down pipes"; + for (auto& sink : mSinks) { + sink->shutdown(true); + } + } + LOG(DEBUG) << __func__ << ": stopping PCM streams"; + for (const auto& proxy : mAlsaDeviceProxies) { + proxy_stop(proxy.get()); + } + LOG(DEBUG) << __func__ << ": joining threads"; + for (auto& thread : mIoThreads) { + if (thread.joinable()) thread.join(); + } + mIoThreads.clear(); + LOG(DEBUG) << __func__ << ": closing PCM devices"; + mAlsaDeviceProxies.clear(); + mSources.clear(); + mSinks.clear(); +} + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/alsa/Utils.cpp b/audio/alsa/UtilsAlsa.cpp similarity index 90% rename from audio/alsa/Utils.cpp rename to audio/alsa/UtilsAlsa.cpp index 77e4f65..6332fbf 100644 --- a/audio/alsa/Utils.cpp +++ b/audio/alsa/UtilsAlsa.cpp @@ -18,14 +18,14 @@ #include #define LOG_TAG "AHAL_AlsaUtils" +#include #include #include #include -#include #include #include -#include "Utils.h" +#include "UtilsAlsa.h" #include "core-impl/utils.h" using aidl::android::hardware::audio::common::getChannelCount; @@ -127,6 +127,28 @@ const AudioChannelCountToMaskMap& getSupportedChannelIndexLayoutMap() { } #undef DEFINE_CHANNEL_INDEX_MASK +#define DEFINE_CHANNEL_ACN_FS_MASK(n) \ + common::makeAcnAudioChannelLayout( \ + AudioChannelLayout::Ambisonics::SourceLayout::FULL_SPHERE, \ + AudioChannelLayout::Ambisonics::FULL_SPHERE_CHANNEL_COUNT_ORDER_##n) +#define DEFINE_CHANNEL_ACN_HZ_MASK(n) \ + common::makeAcnAudioChannelLayout( \ + AudioChannelLayout::Ambisonics::SourceLayout::HORIZONTAL, \ + AudioChannelLayout::Ambisonics::HORIZONTAL_CHANNEL_COUNT_ORDER_##n) + +const AudioChannelCountToMaskMap& getSupportedChannelAcnLayoutMap() { + static const std::set supportedAcnChannelLayouts = { + DEFINE_CHANNEL_ACN_FS_MASK(0), DEFINE_CHANNEL_ACN_FS_MASK(1), + DEFINE_CHANNEL_ACN_HZ_MASK(1), DEFINE_CHANNEL_ACN_HZ_MASK(2), + DEFINE_CHANNEL_ACN_HZ_MASK(3), + }; + static const AudioChannelCountToMaskMap acnLayouts = + make_ChannelCountToMaskMap(supportedAcnChannelLayouts); + return acnLayouts; +} + +#undef DEFINE_CHANNEL_ACN_HZ_MASK +#undef DEFINE_CHANNEL_ACN_FS_MASK AudioFormatDescription make_AudioFormatDescription(AudioFormatType type) { AudioFormatDescription result; @@ -269,6 +291,11 @@ AudioChannelLayout getChannelIndexMaskFromChannelCount(unsigned int channelCount getInvalidChannelLayout()); } +AudioChannelLayout getChannelAcnMaskFromChannelCount(unsigned int channelCount) { + return findValueOrDefault(getSupportedChannelAcnLayoutMap(), channelCount, + getInvalidChannelLayout()); +} + unsigned int getChannelCountFromChannelMask(const AudioChannelLayout& channelMask, bool isInput) { switch (channelMask.getTag()) { case AudioChannelLayout::Tag::layoutMask: { @@ -281,6 +308,11 @@ unsigned int getChannelCountFromChannelMask(const AudioChannelLayout& channelMas static_cast(getChannelCount(channelMask)), 0u /*defaultValue*/); } + case AudioChannelLayout::Tag::acnMask: { + return findKeyOrDefault(getSupportedChannelAcnLayoutMap(), + static_cast(getChannelCount(channelMask)), + 0u /*defaultValue*/); + } case AudioChannelLayout::Tag::none: case AudioChannelLayout::Tag::invalid: case AudioChannelLayout::Tag::voiceMask: @@ -289,7 +321,8 @@ unsigned int getChannelCountFromChannelMask(const AudioChannelLayout& channelMas } } -std::vector getChannelMasksFromProfile(const alsa_device_profile* profile) { +std::vector getChannelMasksFromProfile(const alsa_device_profile* profile, + bool addAcnMasks) { const bool isInput = profile->direction == PCM_IN; std::vector channels; for (size_t i = 0; i < AUDIO_PORT_MAX_CHANNEL_MASKS && profile->channel_counts[i] != 0; ++i) { @@ -302,6 +335,12 @@ std::vector getChannelMasksFromProfile(const alsa_device_pro if (indexMask.getTag() == AudioChannelLayout::Tag::indexMask) { channels.push_back(indexMask); } + if (addAcnMasks) { + auto acnMask = alsa::getChannelAcnMaskFromChannelCount(profile->channel_counts[i]); + if (acnMask.getTag() == AudioChannelLayout::Tag::acnMask) { + channels.push_back(acnMask); + } + } } return channels; } diff --git a/audio/alsa/Utils.h b/audio/alsa/UtilsAlsa.h similarity index 95% rename from audio/alsa/Utils.h rename to audio/alsa/UtilsAlsa.h index 53dcfd0..feac07f 100644 --- a/audio/alsa/Utils.h +++ b/audio/alsa/UtilsAlsa.h @@ -65,10 +65,12 @@ void applyGain(void* buffer, float gain, size_t bytesToTransfer, enum pcm_format unsigned int channelCount, int isInput); ::aidl::android::media::audio::common::AudioChannelLayout getChannelIndexMaskFromChannelCount( unsigned int channelCount); +::aidl::android::media::audio::common::AudioChannelLayout getChannelAcnMaskFromChannelCount( + unsigned int channelCount); unsigned int getChannelCountFromChannelMask( const ::aidl::android::media::audio::common::AudioChannelLayout& channelMask, bool isInput); std::vector<::aidl::android::media::audio::common::AudioChannelLayout> getChannelMasksFromProfile( - const alsa_device_profile* profile); + const alsa_device_profile* profile, bool addAcnMasks); std::optional getDeviceProfile( const ::aidl::android::media::audio::common::AudioDevice& audioDevice, bool isInput); std::optional getDeviceProfile( diff --git a/audio/android.hardware.audio.service.rpi.xml b/audio/android.hardware.audio.service.rpi.xml index 909f859..001a547 100644 --- a/audio/android.hardware.audio.service.rpi.xml +++ b/audio/android.hardware.audio.service.rpi.xml @@ -1,39 +1,39 @@ android.hardware.audio.core - 3 + 4 IModule/default android.hardware.audio.core - 3 + 4 IModule/r_submix android.hardware.audio.core - 3 + 4 IModule/bluetooth android.hardware.audio.core - 3 + 4 IConfig/default android.hardware.audio.core - 3 + 4 IModule/usb android.hardware.audio.effect - 3 + 4 IFactory/default diff --git a/audio/bluetooth/DevicePortProxy.cpp b/audio/bluetooth/DevicePortProxy.cpp index d772c20..88e94ec 100644 --- a/audio/bluetooth/DevicePortProxy.cpp +++ b/audio/bluetooth/DevicePortProxy.cpp @@ -16,7 +16,7 @@ #define LOG_TAG "AHAL_BluetoothAudioPort" -#include +#include #include #include #include @@ -30,6 +30,7 @@ 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::LatencyMode; using aidl::android::hardware::bluetooth::audio::PcmConfiguration; using aidl::android::hardware::bluetooth::audio::PortStatusCallbacks; using aidl::android::hardware::bluetooth::audio::PresentationPosition; @@ -40,36 +41,110 @@ using android::base::StringPrintf; namespace android::bluetooth::audio::aidl { +class BluetoothSession { + public: + explicit BluetoothSession(SessionType session_type) : mSessionType(session_type) {} + + uint16_t registerControlResultCback(const PortStatusCallbacks& cbacks) { + return BluetoothAudioSessionControl::RegisterControlResultCback(mSessionType, cbacks); + } + + void unregisterControlResultCback(uint16_t cookie) { + BluetoothAudioSessionControl::UnregisterControlResultCback(mSessionType, cookie); + } + + bool isSessionReady(bool is_primary_hal = true) const { + return BluetoothAudioSessionControl::IsSessionReady(mSessionType, is_primary_hal); + } + + const AudioConfiguration getAudioConfig() const { + return BluetoothAudioSessionControl::GetAudioConfig(mSessionType); + } + + std::vector getSupportedLatencyModes() const { + return BluetoothAudioSessionControl::GetSupportedLatencyModes(mSessionType); + } + + void setLatencyMode(LatencyMode latency_mode) const { + BluetoothAudioSessionControl::SetLatencyMode(mSessionType, latency_mode); + } + + bool startStream(bool low_latency) const { + return BluetoothAudioSessionControl::StartStream(mSessionType, low_latency); + } + + bool suspendStream() const { return BluetoothAudioSessionControl::SuspendStream(mSessionType); } + + void stopStream() const { BluetoothAudioSessionControl::StopStream(mSessionType); } + + size_t outWritePcmData(const void* buffer, size_t bytes) const { + return BluetoothAudioSessionControl::OutWritePcmData(mSessionType, buffer, bytes); + } + + size_t inReadPcmData(void* buffer, size_t bytes) const { + return BluetoothAudioSessionControl::InReadPcmData(mSessionType, buffer, bytes); + } + + bool getPresentationPosition(PresentationPosition& presentation_position) const { + return BluetoothAudioSessionControl::GetPresentationPosition(mSessionType, + presentation_position); + } + + bool updateSourceMetadata(const SourceMetadata& source_metadata) const { + return BluetoothAudioSessionControl::UpdateSourceMetadata(mSessionType, source_metadata); + } + + bool updateSinkMetadata(const SinkMetadata& sink_metadata) const { + return BluetoothAudioSessionControl::UpdateSinkMetadata(mSessionType, sink_metadata); + } + + SessionType getSessionType() const { return mSessionType; } + + std::string toString() const { + return ::aidl::android::hardware::bluetooth::audio::toString(mSessionType); + } + + bool isA2dp() const { + return mSessionType == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || + mSessionType == SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH; + } + + bool isHfp() const { + switch (mSessionType) { + case SessionType::HFP_SOFTWARE_ENCODING_DATAPATH: + case SessionType::HFP_SOFTWARE_DECODING_DATAPATH: + case SessionType::HFP_HARDWARE_OFFLOAD_DATAPATH: + return true; + default: + return false; + } + } + + bool 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; + } + + private: + const SessionType mSessionType; +}; + namespace { // The maximum time to wait in std::condition_variable::wait_for() constexpr unsigned int kMaxWaitingTimeMs = 4500; +constexpr unsigned int kScoMixPortFixedSampleRate = 32000; } // 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 << android::base::StringPrintf("%#hhx", state); - } -} - -BluetoothAudioPortAidl::BluetoothAudioPortAidl() +BluetoothAudioPortAidl::BluetoothAudioPortAidl(std::optional supportsLowLatency) : mCookie(::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined), mState(BluetoothStreamState::DISABLED), - mSessionType(SessionType::UNKNOWN) {} + mSupportsLowLatency(supportsLowLatency) {} BluetoothAudioPortAidl::~BluetoothAudioPortAidl() { unregisterPort(); @@ -81,7 +156,7 @@ bool BluetoothAudioPortAidl::registerPort(const AudioDeviceDescription& descript return false; } - if (!initSessionType(description)) return false; + if (!initSession(description)) return false; auto control_result_cb = [port = this](uint16_t cookie, bool start_resp, const BluetoothAudioStatus& status) { @@ -91,12 +166,16 @@ bool BluetoothAudioPortAidl::registerPort(const AudioDeviceDescription& descript auto session_changed_cb = [port = this](uint16_t cookie) { port->sessionChangedHandler(cookie); }; - // TODO: Add audio_config_changed_cb + auto low_latency_allowed_cb = [port = this](uint16_t cookie, bool allowed) { + port->lowLatencyAllowedHandler(cookie, allowed); + }; + PortStatusCallbacks cbacks = { .control_result_cb_ = control_result_cb, .session_changed_cb_ = session_changed_cb, + .low_latency_mode_allowed_cb_ = low_latency_allowed_cb, }; - mCookie = BluetoothAudioSessionControl::RegisterControlResultCback(mSessionType, cbacks); + mCookie = getSession()->registerControlResultCback(cbacks); auto isOk = (mCookie != ::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined); if (isOk) { std::lock_guard guard(mCvMutex); @@ -106,7 +185,9 @@ bool BluetoothAudioPortAidl::registerPort(const AudioDeviceDescription& descript return isOk; } -bool BluetoothAudioPortAidl::initSessionType(const AudioDeviceDescription& description) { +bool BluetoothAudioPortAidl::initSession(const AudioDeviceDescription& description) { + SessionType sessionType = SessionType::UNKNOWN; + SessionType fallbackSessionType = SessionType::UNKNOWN; if (description.connection == AudioDeviceDescription::CONNECTION_BT_A2DP && (description.type == AudioDeviceType::OUT_DEVICE || description.type == AudioDeviceType::OUT_HEADPHONE || @@ -114,52 +195,82 @@ bool BluetoothAudioPortAidl::initSessionType(const AudioDeviceDescription& descr LOG(VERBOSE) << __func__ << ": device=AUDIO_DEVICE_OUT_BLUETOOTH_A2DP (HEADPHONES/SPEAKER) (" << description.toString() << ")"; - mSessionType = SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH; + sessionType = SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH; + fallbackSessionType = SessionType::A2DP_HARDWARE_OFFLOAD_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; + sessionType = 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; + sessionType = SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH; + fallbackSessionType = SessionType::LE_AUDIO_HARDWARE_OFFLOAD_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; + sessionType = SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH; + fallbackSessionType = SessionType::LE_AUDIO_HARDWARE_OFFLOAD_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; + sessionType = 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; + sessionType = SessionType::LE_AUDIO_BROADCAST_SOFTWARE_ENCODING_DATAPATH; + } else if (description.connection == AudioDeviceDescription::CONNECTION_BT_SCO && + description.type == AudioDeviceType::IN_HEADSET) { + LOG(VERBOSE) << __func__ << ": device=AUDIO_DEVICE_IN_SCO_HEADSET (VOICE) (" + << description.toString() << ")"; + sessionType = SessionType::HFP_SOFTWARE_DECODING_DATAPATH; + } else if (description.connection == AudioDeviceDescription::CONNECTION_BT_SCO && + (description.type == AudioDeviceType::OUT_DEVICE || + description.type == AudioDeviceType::OUT_HEADSET || + description.type == AudioDeviceType::OUT_CARKIT)) { + LOG(VERBOSE) << __func__ << ": device=AUDIO_DEVICE_OUT_SCO (HEADSET/OUT_CARKIT) (" + << description.toString() << ")"; + sessionType = SessionType::HFP_SOFTWARE_ENCODING_DATAPATH; } else { LOG(ERROR) << __func__ << ": unknown device=" << description.toString(); return false; } - if (!BluetoothAudioSessionControl::IsSessionReady(mSessionType)) { + if (!BluetoothAudioSessionControl::IsSessionReady(sessionType)) { + if (fallbackSessionType != SessionType::UNKNOWN) { + LOG(WARNING) << __func__ + << ": Retry fallback session_type=" << toString(fallbackSessionType) + << " for session_type=" << toString(sessionType); + if (BluetoothAudioSessionControl::IsSessionReady(fallbackSessionType, false)) { + mSession = std::make_unique(fallbackSessionType); + return true; + } else { + LOG(ERROR) << __func__ + << ": fallback session_type=" << toString(fallbackSessionType) + << " is not ready"; + } + } LOG(ERROR) << __func__ << ": device=" << description.toString() - << ", session_type=" << toString(mSessionType) << " is not ready"; + << ", session_type=" << toString(sessionType) << " is not ready"; return false; } + mSession = std::make_unique(sessionType); return true; } void BluetoothAudioPortAidl::unregisterPort() { if (!inUse()) { - LOG(WARNING) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(WARNING) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; return; } - BluetoothAudioSessionControl::UnregisterControlResultCback(mSessionType, mCookie); + getSession()->unregisterControlResultCback(mCookie); mCookie = ::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined; + mSession.reset(); LOG(VERBOSE) << __func__ << debugMessage() << " port unregistered"; } @@ -171,65 +282,77 @@ void BluetoothAudioPortAidl::controlResultHandler(uint16_t cookie, return; } if (mCookie != cookie) { - LOG(ERROR) << "control_result_cb: proxy of device port (cookie=" - << StringPrintf("%#hx", cookie) << ") is corrupted"; + LOG(ERROR) << "control_result_cb: proxy of device port is corrupted " + << "cookie=" << StringPrintf("%#hx", cookie) << ", expected " + << StringPrintf("%#hx", mCookie); return; } BluetoothStreamState previous_state = mState; - LOG(INFO) << "control_result_cb:" << debugMessage() << ", previous_state=" << previous_state - << ", status=" << toString(status); - + ::android::base::LogSeverity severity = ::android::base::FATAL; 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; + severity = ::android::base::INFO; } 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); + severity = ::android::base::WARNING; } break; case BluetoothStreamState::STARTING: if (status == BluetoothAudioStatus::SUCCESS) { mState = BluetoothStreamState::STARTED; + severity = ::android::base::INFO; } 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; + severity = ::android::base::WARNING; } break; case BluetoothStreamState::SUSPENDING: if (status == BluetoothAudioStatus::SUCCESS) { mState = BluetoothStreamState::STANDBY; + severity = ::android::base::INFO; } else { - // It will be failed if the headset is disconnecting, and set to disable + // Will fail if the headset is disconnecting, so 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; + severity = ::android::base::WARNING; } 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; + severity = ::android::base::ERROR; + } + if (previous_state != mState) { + LOG(severity) << "control_result_cb" << debugMessage() << ", status=" << toString(status) + << ", " << previous_state << " -> " << mState; + } else { + LOG(severity) << "control_result_cb" << debugMessage() << ", status=" << toString(status) + << ", " << previous_state; + } + if (severity != ::android::base::ERROR) { + mInternalCv.notify_all(); + } +} + +void BluetoothAudioPortAidl::lowLatencyAllowedHandler(uint16_t cookie, bool allowed) { + if (mCookie != cookie) { + LOG(ERROR) << "low_latency_allowed_cb: proxy of device port (cookie=" + << StringPrintf("%#hx", cookie) << ") is corrupted"; + return; + } + LOG(INFO) << "low_latency_allowed_cb:" << debugMessage() << ", allowed=" << allowed; + std::vector latency_modes; + if (!getRecommendedLatencyModes(&latency_modes)) return; + std::shared_ptr callbacks; + { + std::lock_guard guard(mCvMutex); + callbacks = mCallbacks; + } + if (callbacks) { + callbacks->onRecommendedLatencyModeChanged(latency_modes); } - mInternalCv.notify_all(); } void BluetoothAudioPortAidl::sessionChangedHandler(uint16_t cookie) { @@ -239,14 +362,15 @@ void BluetoothAudioPortAidl::sessionChangedHandler(uint16_t cookie) { return; } if (mCookie != cookie) { - LOG(ERROR) << "session_changed_cb: proxy of device port (cookie=" - << StringPrintf("%#hx", cookie) << ") is corrupted"; + LOG(ERROR) << "session_changed_cb: proxy of device port is corrupted " + << "cookie=" << StringPrintf("%#hx", cookie) << ", expected " + << StringPrintf("%#hx", mCookie); return; } BluetoothStreamState previous_state = mState; - LOG(VERBOSE) << "session_changed_cb:" << debugMessage() - << ", previous_state=" << previous_state; mState = BluetoothStreamState::DISABLED; + LOG(DEBUG) << "session_changed_cb" << debugMessage() << ", " << previous_state << " -> " + << mState; mInternalCv.notify_all(); } @@ -254,16 +378,19 @@ bool BluetoothAudioPortAidl::inUse() const { return (mCookie != ::aidl::android::hardware::bluetooth::audio::kObserversCookieUndefined); } +BluetoothSession* BluetoothAudioPortAidl::getSession() const { + return mSession.get(); +} + bool BluetoothAudioPortAidl::getPreferredDataIntervalUs(size_t& interval_us) const { if (!inUse()) { - LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(ERROR) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; return false; } - const AudioConfiguration& hal_audio_cfg = - BluetoothAudioSessionControl::GetAudioConfig(mSessionType); + const AudioConfiguration& hal_audio_cfg = getSession()->getAudioConfig(); if (hal_audio_cfg.getTag() != AudioConfiguration::pcmConfig) { - LOG(ERROR) << __func__ << ": unsupported audio cfg tag"; + LOG(ERROR) << __func__ << debugMessage() << ": unsupported audio cfg tag"; return false; } @@ -271,24 +398,106 @@ bool BluetoothAudioPortAidl::getPreferredDataIntervalUs(size_t& interval_us) con return true; } +bool BluetoothAudioPortAidl::getRecommendedLatencyModes(std::vector* latency_modes, + std::optional* supports_low_latency) { + if (!inUse()) { + LOG(ERROR) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; + return false; + } + *latency_modes = getSession()->getSupportedLatencyModes(); + LOG(INFO) << __func__ << debugMessage() << ": " + << ::android::internal::ToString(*latency_modes); + *supports_low_latency = std::find(latency_modes->begin(), latency_modes->end(), + LatencyMode::LOW_LATENCY) != latency_modes->end(); + return true; +} + +bool BluetoothAudioPortAidl::getRecommendedLatencyModes(std::vector* latency_modes) { + std::optional supportsLowLatency; + if (getRecommendedLatencyModes(latency_modes, &supportsLowLatency)) { + std::lock_guard guard(mCvMutex); + mSupportsLowLatency = supportsLowLatency; + return true; + } + return false; +} + bool BluetoothAudioPortAidl::loadAudioConfig(PcmConfiguration& audio_cfg) { if (!inUse()) { - LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(ERROR) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; return false; } - const AudioConfiguration& hal_audio_cfg = - BluetoothAudioSessionControl::GetAudioConfig(mSessionType); + const AudioConfiguration& hal_audio_cfg = getSession()->getAudioConfig(); if (hal_audio_cfg.getTag() != AudioConfiguration::pcmConfig) { - LOG(ERROR) << __func__ << ": unsupported audio cfg tag"; + LOG(ERROR) << __func__ << debugMessage() + << ": unsupported audio cfg tag: " << toString(hal_audio_cfg.getTag()); return false; } audio_cfg = hal_audio_cfg.get(); - LOG(VERBOSE) << __func__ << debugMessage() << ", state*=" << getState() << ", PcmConfig=[" + LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << getState() << ", PcmConfig=[" << audio_cfg.toString() << "]"; if (audio_cfg.channelMode == ChannelMode::UNKNOWN) { + LOG(ERROR) << __func__ << debugMessage() + << ": unsupported channel mode: " << toString(audio_cfg.channelMode); return false; } + + if (mSession->isHfp()) { + if (audio_cfg.sampleRateHz == kScoMixPortFixedSampleRate) { + mResampler = {nullptr, nullptr}; + mResampleRatio = 0; + } else { + uint32_t resampleTargetRate = audio_cfg.sampleRateHz; + switch (audio_cfg.sampleRateHz) { + case 8000: + case 16000: + case 32000: + break; + default: + LOG(FATAL) << __func__ << debugMessage() + << ": Resample target rate must be a divisor of " + << kScoMixPortFixedSampleRate << ", but is " + << audio_cfg.sampleRateHz; + } + mResampleRatio = kScoMixPortFixedSampleRate / resampleTargetRate; + audio_cfg.sampleRateHz = kScoMixPortFixedSampleRate; + + int in_rate, out_rate; + switch (mSession->getSessionType()) { + case SessionType::HFP_SOFTWARE_ENCODING_DATAPATH: + in_rate = kScoMixPortFixedSampleRate; + out_rate = resampleTargetRate; + break; + case SessionType::HFP_SOFTWARE_DECODING_DATAPATH: + in_rate = resampleTargetRate; + out_rate = kScoMixPortFixedSampleRate; + break; + default: + LOG(ERROR) << __func__ + << ": cannot create resampler for unsupported session type " + << toString(mSession->getSessionType()); + return false; + } + struct resampler_itfe* resampler = nullptr; + + if (int rc = create_resampler(/* in_sample_rate= */ in_rate, + /* outSampleRate= */ out_rate, + /* channelCount= */ 1, + /* quality= */ RESAMPLER_QUALITY_VOIP, + /* provider= */ nullptr, + /* resampler= */ &resampler); + rc != 0) { + LOG(ERROR) << __func__ << ": failed to create resampler, rc=" << rc; + return false; + } + mResampler = {resampler, release_resampler}; + + LOG(DEBUG) << __func__ << ": created resampler with in_rate=" << in_rate + << ", out_rate=" << out_rate << ", for session type " + << toString(mSession->getSessionType()); + } + } return true; } @@ -298,221 +507,279 @@ bool BluetoothAudioPortAidlOut::loadAudioConfig(PcmConfiguration& audio_cfg) { if (audio_cfg.channelMode == ChannelMode::MONO && audio_cfg.bitsPerSample == 16) { mIsStereoToMono = true; audio_cfg.channelMode = ChannelMode::STEREO; - LOG(INFO) << __func__ << ": force channels = to be AUDIO_CHANNEL_OUT_STEREO"; + LOG(INFO) << __func__ << debugMessage() + << ": force channels = to be AUDIO_CHANNEL_OUT_STEREO"; + } else { + mIsStereoToMono = false; } return true; } bool BluetoothAudioPortAidl::standby() { if (!inUse()) { - LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(ERROR) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; return false; } std::lock_guard guard(mCvMutex); - LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << getState() << " request"; + BluetoothStreamState previous_state = mState; + LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << mState << " request"; if (mState == BluetoothStreamState::DISABLED) { mState = BluetoothStreamState::STANDBY; - LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << getState() << " done"; + LOG(INFO) << __func__ << debugMessage() << ", " << previous_state << " -> " << mState; return true; } return false; } -bool BluetoothAudioPortAidl::condWaitState(BluetoothStreamState state) { +bool BluetoothAudioPortAidl::condWaitState(std::unique_lock* lock) { 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; + const auto state = mState; + if (state == BluetoothStreamState::STARTING || state == BluetoothStreamState::SUSPENDING) { + LOG(DEBUG) << __func__ << debugMessage() << " waiting to change from " << state; + mInternalCv.wait_for(*lock, waitTime, [this, state] { + // No aliasing support in thread safety analysis, specify lock name explicitly. + base::ScopedLockAssertion lock_assertion(mCvMutex); + return mState != state; + }); + const bool expected = + mState == (state == BluetoothStreamState::STARTING ? BluetoothStreamState::STARTED + : BluetoothStreamState::STANDBY); + LOG(expected ? INFO : WARNING) + << __func__ << debugMessage() << ", " << state << " -> " << mState; + return expected; } + LOG(ERROR) << __func__ << debugMessage() << " called to wait when in " << state; return false; } bool BluetoothAudioPortAidl::start() { if (!inUse()) { - LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(ERROR) << __func__ << debugMessage() << ": 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); + LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << mState + << ", mono=" << (mIsStereoToMono ? "true" : "false") << " request"; + if (mState == BluetoothStreamState::STARTED) { + return true; // nop, return + } else if (mState == BluetoothStreamState::DISABLED) { + return false; // avoid logspam when called from `transfer` + } else if (mState == BluetoothStreamState::SUSPENDING || + mState == BluetoothStreamState::STARTING) { + /* If port is in transient state, give some time to respond */ + if (!condWaitState(&lock)) { + LOG(ERROR) << __func__ << debugMessage() << ", state=" << mState << " failure"; + return false; + } + } if (mState == BluetoothStreamState::STARTED) { retval = true; } else if (mState == BluetoothStreamState::STANDBY) { + if (!mSupportsLowLatency.has_value()) { + std::vector latency_modes; + getRecommendedLatencyModes(&latency_modes, &mSupportsLowLatency); + } + const bool low_latency = mSupportsLowLatency.value_or(false); mState = BluetoothStreamState::STARTING; lock.unlock(); - if (BluetoothAudioSessionControl::StartStream(mSessionType)) { - retval = condWaitState(BluetoothStreamState::STARTING); + const bool startSuccess = mSession->startStream(low_latency); + lock.lock(); + if (startSuccess && mState == BluetoothStreamState::STARTING) { + retval = condWaitState(&lock); + } else if (startSuccess && mState == BluetoothStreamState::STARTED) { + retval = true; } else { - LOG(ERROR) << __func__ << debugMessage() << ", state=" << getState() - << " Hal fails"; + // !startSuccess => no session instance + const BluetoothStreamState newState = startSuccess ? BluetoothStreamState::STANDBY + : BluetoothStreamState::DISABLED; + LOG(ERROR) << __func__ << debugMessage() << ", startSuccess=" << startSuccess + << ", state=" << mState << " -> " << newState; + mState = newState; } } + if (retval) { + LOG(INFO) << __func__ << debugMessage() << ", state=" << mState + << ", mono=" << (mIsStereoToMono ? "true" : "false") << " done"; + // Reload audio config. + PcmConfiguration pcm_config; + loadAudioConfig(pcm_config); + } else { + LOG(ERROR) << __func__ << debugMessage() << ", state=" << mState << " failure"; + } } - - 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"; + LOG(ERROR) << __func__ << debugMessage() << ": 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); + LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << mState << " request"; + 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 */ + if (!condWaitState(&lock)) { + LOG(ERROR) << __func__ << debugMessage() << ", state=" << mState << " failure"; + return false; + } + } if (mState == BluetoothStreamState::STANDBY) { retval = true; } else if (mState == BluetoothStreamState::STARTED) { mState = BluetoothStreamState::SUSPENDING; lock.unlock(); - if (BluetoothAudioSessionControl::SuspendStream(mSessionType)) { - retval = condWaitState(BluetoothStreamState::SUSPENDING); + const bool suspendSuccess = mSession->suspendStream(); + lock.lock(); + if (suspendSuccess && mState == BluetoothStreamState::SUSPENDING) { + retval = condWaitState(&lock); + } else if (suspendSuccess && mState == BluetoothStreamState::STANDBY) { + retval = true; } else { - LOG(ERROR) << __func__ << debugMessage() << ", state=" << getState() - << " failure to suspend stream"; + LOG(ERROR) << __func__ << debugMessage() << ", suspendSuccess=" << suspendSuccess + << ", state=" << mState << " -> DISABLED"; + mState = BluetoothStreamState::DISABLED; } } + if (retval) { + LOG(INFO) << __func__ << debugMessage() << ", state=" << mState << " done"; + } else { + LOG(ERROR) << __func__ << debugMessage() << ", state=" << mState << " failure"; + } } - - 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"; + LOG(ERROR) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; return; } - std::lock_guard guard(mCvMutex); - LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << getState() << " request"; + std::unique_lock lock(mCvMutex); + base::ScopedLockAssertion lock_assertion(mCvMutex); + BluetoothStreamState previous_state = mState; + LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << mState << " request"; if (mState != BluetoothStreamState::DISABLED) { - BluetoothAudioSessionControl::StopStream(mSessionType); + lock.unlock(); + mSession->stopStream(); + lock.lock(); mState = BluetoothStreamState::DISABLED; + LOG(INFO) << __func__ << debugMessage() << ", " << previous_state << " -> " << mState; } - 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"; + LOG(ERROR) << __func__ << debugMessage() << ": bad input arg"; return 0; } if (!inUse()) { - LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(ERROR) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; return 0; } - if (!mIsStereoToMono) { - return BluetoothAudioSessionControl::OutWritePcmData(mSessionType, buffer, bytes); + size_t multiplier = 1; + std::vector src(static_cast(buffer), + static_cast(buffer) + bytes / 2); + + if (mIsStereoToMono) { + // WAR to mix the stereo into Mono (16 bits per sample) + std::vector dst(src.size() / 2); + if (dst.empty()) { + LOG(ERROR) << __func__ << debugMessage() << ": No frame to write"; + return 0; + } + downmix_to_mono_i16_from_stereo_i16(dst.data(), src.data(), dst.size()); + src = std::move(dst); + + multiplier *= 2; } - // 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. - auto totalWrite = BluetoothAudioSessionControl::OutWritePcmData(mSessionType, dst.get(), - write_frames * 2); - return totalWrite * 2; + if (mResampler) { + // WAR to resample audio data from BT HAL to expected rate in mix port + size_t in_frames = src.size(); + if (in_frames == 0) { + LOG(ERROR) << __func__ << debugMessage() << ": No frame to write"; + return 0; + } + size_t out_frames = in_frames / mResampleRatio; + std::vector dst(out_frames); + int rc = mResampler->resample_from_input(mResampler.get(), src.data(), &in_frames, + dst.data(), &out_frames); + if (rc) { + LOG(ERROR) << __func__ << ": failed to resample, rc=" << rc; + } + dst.resize(out_frames); + src = std::move(dst); + + multiplier *= mResampleRatio; + } + + auto totalWrite = getSession()->outWritePcmData(src.data(), src.size() * 2); + return totalWrite * multiplier; +} + +bool BluetoothAudioPortAidlOut::setLatencyMode( + ::aidl::android::hardware::bluetooth::audio::LatencyMode latency_mode) { + if (!inUse()) { + LOG(ERROR) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; + return false; + } + LOG(INFO) << __func__ << debugMessage() << ": " << toString(latency_mode); + getSession()->setLatencyMode(latency_mode); + return true; } size_t BluetoothAudioPortAidlIn::readData(void* buffer, size_t bytes) const { if (!buffer) { - LOG(ERROR) << __func__ << ": bad input arg"; + LOG(ERROR) << __func__ << debugMessage() << ": bad input arg"; return 0; } if (!inUse()) { - LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(ERROR) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; return 0; } - return BluetoothAudioSessionControl::InReadPcmData(mSessionType, buffer, bytes); + if (!mResampler) { + return getSession()->inReadPcmData(buffer, bytes); + } else { + std::vector input(bytes / mResampleRatio / 2); + auto totalRead = getSession()->inReadPcmData(input.data(), input.size() * 2); + + size_t in_frames = totalRead / 2; + if (in_frames == 0) return 0; + + size_t out_frames = in_frames * mResampleRatio; + + int rc = mResampler->resample_from_input(mResampler.get(), input.data(), &in_frames, + static_cast(buffer), &out_frames); + if (rc) { + LOG(ERROR) << __func__ << ": failed to resample, rc=" << rc; + } + return out_frames * 2; + } } bool BluetoothAudioPortAidl::getPresentationPosition( PresentationPosition& presentation_position) const { if (!inUse()) { - LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(ERROR) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; return false; } - bool retval = BluetoothAudioSessionControl::GetPresentationPosition(mSessionType, - presentation_position); + bool retval = getSession()->getPresentationPosition(presentation_position); LOG(VERBOSE) << __func__ << debugMessage() << ", state=" << getState() << presentation_position.toString(); @@ -521,58 +788,67 @@ bool BluetoothAudioPortAidl::getPresentationPosition( bool BluetoothAudioPortAidl::updateSourceMetadata(const SourceMetadata& source_metadata) const { if (!inUse()) { - LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(ERROR) << __func__ << debugMessage() << ": 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); + return getSession()->updateSourceMetadata(source_metadata); } bool BluetoothAudioPortAidl::updateSinkMetadata(const SinkMetadata& sink_metadata) const { if (!inUse()) { - LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(ERROR) << __func__ << debugMessage() << ": 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); + return getSession()->updateSinkMetadata(sink_metadata); } BluetoothStreamState BluetoothAudioPortAidl::getState() const { + std::lock_guard guard(mCvMutex); return mState; } bool BluetoothAudioPortAidl::setState(BluetoothStreamState state) { if (!inUse()) { - LOG(ERROR) << __func__ << ": BluetoothAudioPortAidl is not in use"; + LOG(ERROR) << __func__ << debugMessage() << ": BluetoothAudioPortAidl is not in use"; return false; } std::lock_guard guard(mCvMutex); - LOG(DEBUG) << __func__ << ": BluetoothAudioPortAidl old state = " << mState - << " new state = " << state; + LOG(INFO) << __func__ << debugMessage() << ": " << mState << " -> " << state; mState = state; return true; } +void BluetoothAudioPortAidl::setCallbacks( + const std::shared_ptr& callbacks) { + std::lock_guard l(mCvMutex); + mCallbacks = callbacks; +} + bool BluetoothAudioPortAidl::isA2dp() const { - return mSessionType == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || - mSessionType == SessionType::A2DP_HARDWARE_OFFLOAD_ENCODING_DATAPATH; + return mSession ? mSession->isA2dp() : false; +} + +bool BluetoothAudioPortAidl::isHfp() const { + return mSession ? mSession->isHfp() : false; } 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; + return mSession ? mSession->isLeAudio() : false; } std::string BluetoothAudioPortAidl::debugMessage() const { - return StringPrintf(": session_type=%s, cookie=%#hx", toString(mSessionType).c_str(), mCookie); + const auto session_type_str = getSessionNameForDebug(); + return StringPrintf(": session_type=%s, cookie=%#hx", session_type_str.c_str(), mCookie); +} + +std::string BluetoothAudioPortAidl::getSessionNameForDebug() const { + return mSession ? mSession->toString() : toString(SessionType::UNKNOWN); } } // namespace android::bluetooth::audio::aidl diff --git a/audio/bluetooth/ModuleBluetooth.cpp b/audio/bluetooth/ModuleBluetooth.cpp index 8c381cd..698f89a 100644 --- a/audio/bluetooth/ModuleBluetooth.cpp +++ b/audio/bluetooth/ModuleBluetooth.cpp @@ -16,32 +16,13 @@ #define LOG_TAG "AHAL_ModuleBluetooth" -#include +#include #include "BluetoothAudioSession.h" +#include "core-impl/DevicePortProxy.h" #include "core-impl/ModuleBluetooth.h" -#include "core-impl/StreamBluetooth.h" -using aidl::android::hardware::audio::common::SinkMetadata; -using aidl::android::hardware::audio::common::SourceMetadata; -using aidl::android::hardware::bluetooth::audio::ChannelMode; -using aidl::android::hardware::bluetooth::audio::PcmConfiguration; -using aidl::android::media::audio::common::AudioChannelLayout; -using aidl::android::media::audio::common::AudioConfigBase; -using aidl::android::media::audio::common::AudioDeviceDescription; -using aidl::android::media::audio::common::AudioDeviceType; -using aidl::android::media::audio::common::AudioFormatDescription; -using aidl::android::media::audio::common::AudioFormatType; -using aidl::android::media::audio::common::AudioIoFlags; -using aidl::android::media::audio::common::AudioOffloadInfo; -using aidl::android::media::audio::common::AudioPort; -using aidl::android::media::audio::common::AudioPortConfig; -using aidl::android::media::audio::common::AudioPortExt; -using aidl::android::media::audio::common::AudioProfile; -using aidl::android::media::audio::common::Int; -using aidl::android::media::audio::common::MicrophoneInfo; -using aidl::android::media::audio::common::PcmType; -using android::bluetooth::audio::aidl::BluetoothAudioPortAidl; +using android::bluetooth::audio::aidl::BluetoothAudioPort; using android::bluetooth::audio::aidl::BluetoothAudioPortAidlIn; using android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut; @@ -51,292 +32,25 @@ extern "C" binder_status_t createIBluetoothAudioProviderFactory(); namespace aidl::android::hardware::audio::core { -namespace { - -PcmType pcmTypeFromBitsPerSample(int8_t bitsPerSample) { - if (bitsPerSample == 8) - return PcmType::UINT_8_BIT; - else if (bitsPerSample == 16) - return PcmType::INT_16_BIT; - else if (bitsPerSample == 24) - return PcmType::INT_24_BIT; - else if (bitsPerSample == 32) - return PcmType::INT_32_BIT; - ALOGE("Unsupported bitsPerSample: %d", bitsPerSample); - return PcmType::DEFAULT; -} - -AudioChannelLayout channelLayoutFromChannelMode(ChannelMode mode) { - if (mode == ChannelMode::MONO) { - return AudioChannelLayout::make( - AudioChannelLayout::LAYOUT_MONO); - } else if (mode == ChannelMode::STEREO || mode == ChannelMode::DUALMONO) { - return AudioChannelLayout::make( - AudioChannelLayout::LAYOUT_STEREO); - } - ALOGE("Unsupported channel mode: %s", toString(mode).c_str()); - return AudioChannelLayout{}; -} - -} // namespace - ModuleBluetooth::ModuleBluetooth(std::unique_ptr&& config) - : Module(Type::BLUETOOTH, std::move(config)) { + : ModuleBluetoothBase(std::move(config)) { // TODO(b/312265159) bluetooth audio should be in its own process // Remove this and the shared_libs when that happens binder_status_t status = createIBluetoothAudioProviderFactory(); if (status != STATUS_OK) { - LOG(ERROR) << "Failed to create bluetooth audio provider factory. Status: " + LOG(ERROR) << __func__ << ": Failed to create bluetooth audio provider factory. Status: " << ::android::statusToString(status); } -} - -ndk::ScopedAStatus ModuleBluetooth::getBluetoothA2dp( - std::shared_ptr* _aidl_return) { - *_aidl_return = getBtA2dp().getInstance(); - LOG(DEBUG) << __func__ << ": returning instance of IBluetoothA2dp: " << _aidl_return->get(); - return ndk::ScopedAStatus::ok(); -} - -ndk::ScopedAStatus ModuleBluetooth::getBluetoothLe(std::shared_ptr* _aidl_return) { - *_aidl_return = getBtLe().getInstance(); - LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _aidl_return->get(); - return ndk::ScopedAStatus::ok(); -} - -ChildInterface& ModuleBluetooth::getBtA2dp() { - if (!mBluetoothA2dp) { - auto handle = ndk::SharedRefBase::make(); - handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this)); - mBluetoothA2dp = handle; - } - return mBluetoothA2dp; -} - -ChildInterface& ModuleBluetooth::getBtLe() { - if (!mBluetoothLe) { - auto handle = ndk::SharedRefBase::make(); - handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this)); - mBluetoothLe = handle; - } - return mBluetoothLe; -} - -ModuleBluetooth::BtProfileHandles ModuleBluetooth::getBtProfileManagerHandles() { - return std::make_tuple(std::weak_ptr(), getBtA2dp().getPtr(), getBtLe().getPtr()); -} - -ndk::ScopedAStatus ModuleBluetooth::getMicMute(bool* _aidl_return __unused) { - LOG(DEBUG) << __func__ << ": is not supported"; - return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); -} - -ndk::ScopedAStatus ModuleBluetooth::setMicMute(bool in_mute __unused) { - LOG(DEBUG) << __func__ << ": is not supported"; - return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); -} - -ndk::ScopedAStatus ModuleBluetooth::setAudioPortConfig(const AudioPortConfig& in_requested, - AudioPortConfig* out_suggested, - bool* _aidl_return) { - auto fillConfig = [this](const AudioPort& port, AudioPortConfig* config) { - if (port.ext.getTag() == AudioPortExt::device) { - CachedProxy proxy; - auto status = findOrCreateProxy(port, proxy); - if (status.isOk()) { - const auto& pcmConfig = proxy.pcmConfig; - LOG(DEBUG) << "setAudioPortConfig: suggesting port config from " - << pcmConfig.toString(); - const auto pcmType = pcmTypeFromBitsPerSample(pcmConfig.bitsPerSample); - const auto channelMask = channelLayoutFromChannelMode(pcmConfig.channelMode); - if (pcmType != PcmType::DEFAULT && channelMask != AudioChannelLayout{}) { - config->format = - AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType}; - config->channelMask = channelMask; - config->sampleRate = Int{.value = pcmConfig.sampleRateHz}; - config->flags = port.flags; - config->ext = port.ext; - return true; - } - } - } - return generateDefaultPortConfig(port, config); - }; - return Module::setAudioPortConfigImpl(in_requested, fillConfig, out_suggested, _aidl_return); -} - -ndk::ScopedAStatus ModuleBluetooth::checkAudioPatchEndpointsMatch( - const std::vector& sources, const std::vector& sinks) { - // Both sources and sinks must be non-empty, this is guaranteed by 'setAudioPatch'. - const bool isInput = sources[0]->ext.getTag() == AudioPortExt::device; - const int32_t devicePortId = isInput ? sources[0]->portId : sinks[0]->portId; - const auto proxyIt = mProxies.find(devicePortId); - if (proxyIt == mProxies.end()) return ndk::ScopedAStatus::ok(); - const auto& pcmConfig = proxyIt->second.pcmConfig; - const AudioPortConfig* mixPortConfig = isInput ? sinks[0] : sources[0]; - if (!StreamBluetooth::checkConfigParams( - pcmConfig, AudioConfigBase{.sampleRate = mixPortConfig->sampleRate->value, - .channelMask = *(mixPortConfig->channelMask), - .format = *(mixPortConfig->format)})) { - return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); - } - if (int32_t handle = mixPortConfig->ext.get().handle; handle > 0) { - mConnections.insert(std::pair(handle, devicePortId)); - } - return ndk::ScopedAStatus::ok(); -} - -void ModuleBluetooth::onExternalDeviceConnectionChanged(const AudioPort& audioPort, - bool connected) { - if (!connected) mProxies.erase(audioPort.id); -} - -ndk::ScopedAStatus ModuleBluetooth::createInputStream( - StreamContext&& context, const SinkMetadata& sinkMetadata, - const std::vector& microphones, std::shared_ptr* result) { - CachedProxy proxy; - RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy)); - return createStreamInstance(result, std::move(context), sinkMetadata, - microphones, getBtProfileManagerHandles(), - proxy.ptr, proxy.pcmConfig); -} - -ndk::ScopedAStatus ModuleBluetooth::createOutputStream( - StreamContext&& context, const SourceMetadata& sourceMetadata, - const std::optional& offloadInfo, std::shared_ptr* result) { - CachedProxy proxy; - RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy)); - return createStreamInstance(result, std::move(context), sourceMetadata, - offloadInfo, getBtProfileManagerHandles(), - proxy.ptr, proxy.pcmConfig); -} - -ndk::ScopedAStatus ModuleBluetooth::populateConnectedDevicePort(AudioPort* audioPort, - int32_t nextPortId) { - if (audioPort->ext.getTag() != AudioPortExt::device) { - LOG(ERROR) << __func__ << ": not a device port: " << audioPort->toString(); - return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); - } if (!::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession::IsAidlAvailable()) { LOG(ERROR) << __func__ << ": IBluetoothAudioProviderFactory AIDL service not available"; - return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } - const auto& devicePort = audioPort->ext.get(); - const auto& description = devicePort.device.type; - // This method must return an error when the device can not be connected. - // Since A2DP/LE status events are sent asynchronously, it is more reliable - // to attempt connecting to the BT stack rather than judge by the A2DP/LE status. - if (description.connection != AudioDeviceDescription::CONNECTION_BT_A2DP && - description.connection != AudioDeviceDescription::CONNECTION_BT_LE && - !(description.connection == AudioDeviceDescription::CONNECTION_WIRELESS && - description.type == AudioDeviceType::OUT_HEARING_AID)) { - LOG(ERROR) << __func__ << ": unsupported device type: " << audioPort->toString(); - return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); - } - CachedProxy proxy; - RETURN_STATUS_IF_ERROR(createProxy(*audioPort, nextPortId, proxy)); - // If the device is actually connected, it is configured by the BT stack. - // Provide the current configuration instead of all possible profiles. - const auto& pcmConfig = proxy.pcmConfig; - audioPort->profiles.clear(); - audioPort->profiles.push_back( - AudioProfile{.format = AudioFormatDescription{.type = AudioFormatType::PCM, - .pcm = pcmTypeFromBitsPerSample( - pcmConfig.bitsPerSample)}, - .channelMasks = std::vector( - {channelLayoutFromChannelMode(pcmConfig.channelMode)}), - .sampleRates = std::vector({pcmConfig.sampleRateHz})}); - LOG(DEBUG) << __func__ << ": " << audioPort->toString(); - return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus ModuleBluetooth::onMasterMuteChanged(bool) { - LOG(DEBUG) << __func__ << ": is not supported"; - return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); -} - -ndk::ScopedAStatus ModuleBluetooth::onMasterVolumeChanged(float) { - LOG(DEBUG) << __func__ << ": is not supported"; - return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); -} - -int32_t ModuleBluetooth::getNominalLatencyMs(const AudioPortConfig& portConfig) { - const auto connectionsIt = mConnections.find(portConfig.ext.get().handle); - if (connectionsIt != mConnections.end()) { - const auto proxyIt = mProxies.find(connectionsIt->second); - if (proxyIt != mProxies.end()) { - auto proxy = proxyIt->second.ptr; - size_t dataIntervalUs = 0; - if (!proxy->getPreferredDataIntervalUs(dataIntervalUs)) { - LOG(WARNING) << __func__ << ": could not fetch preferred data interval"; - } - const bool isInput = portConfig.flags->getTag() == AudioIoFlags::input; - return isInput ? StreamInBluetooth::getNominalLatencyMs(dataIntervalUs) - : StreamOutBluetooth::getNominalLatencyMs(dataIntervalUs); - } - } - LOG(ERROR) << __func__ << ": no connection or proxy found for " << portConfig.toString(); - return Module::getNominalLatencyMs(portConfig); -} - -ndk::ScopedAStatus ModuleBluetooth::createProxy(const AudioPort& audioPort, int32_t instancePortId, - CachedProxy& proxy) { - const bool isInput = audioPort.flags.getTag() == AudioIoFlags::input; - proxy.ptr = isInput ? std::shared_ptr( - std::make_shared()) - : std::shared_ptr( - std::make_shared()); - const auto& devicePort = audioPort.ext.get(); - const auto device = devicePort.device.type; - bool registrationSuccess = false; - for (int i = 0; i < kCreateProxyRetries && !registrationSuccess; ++i) { - registrationSuccess = proxy.ptr->registerPort(device); - usleep(kCreateProxyRetrySleepMs * 1000); - } - if (!registrationSuccess) { - LOG(ERROR) << __func__ << ": failed to register BT port for " << device.toString(); - return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); - } - if (!proxy.ptr->loadAudioConfig(proxy.pcmConfig)) { - LOG(ERROR) << __func__ << ": state=" << proxy.ptr->getState() - << ", failed to load audio config"; - return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); - } - mProxies.insert(std::pair(instancePortId, proxy)); - return ndk::ScopedAStatus::ok(); -} - -ndk::ScopedAStatus ModuleBluetooth::fetchAndCheckProxy(const StreamContext& context, - CachedProxy& proxy) { - const auto connectionsIt = mConnections.find(context.getMixPortHandle()); - if (connectionsIt != mConnections.end()) { - const auto proxyIt = mProxies.find(connectionsIt->second); - if (proxyIt != mProxies.end()) { - proxy = proxyIt->second; - mProxies.erase(proxyIt); - } - mConnections.erase(connectionsIt); - } - if (proxy.ptr != nullptr) { - if (!StreamBluetooth::checkConfigParams( - proxy.pcmConfig, AudioConfigBase{.sampleRate = context.getSampleRate(), - .channelMask = context.getChannelLayout(), - .format = context.getFormat()})) { - return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); - } - } - // Not having a proxy is OK, it may happen in VTS tests when streams are opened on unconnected - // mix ports. - return ndk::ScopedAStatus::ok(); -} - -ndk::ScopedAStatus ModuleBluetooth::findOrCreateProxy(const AudioPort& audioPort, - CachedProxy& proxy) { - if (auto proxyIt = mProxies.find(audioPort.id); proxyIt != mProxies.end()) { - proxy = proxyIt->second; - return ndk::ScopedAStatus::ok(); - } - return createProxy(audioPort, audioPort.id, proxy); +std::shared_ptr ModuleBluetooth::createProxyInstance(bool isInput) { + return isInput ? std::shared_ptr( + std::make_shared()) + : std::shared_ptr( + std::make_shared()); } } // namespace aidl::android::hardware::audio::core diff --git a/audio/bluetooth/ModuleBluetoothBase.cpp b/audio/bluetooth/ModuleBluetoothBase.cpp new file mode 100644 index 0000000..fc72320 --- /dev/null +++ b/audio/bluetooth/ModuleBluetoothBase.cpp @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AHAL_ModuleBluetooth" + +#include + +#include "core-impl/ModuleBluetoothBase.h" +#include "core-impl/StreamBluetooth.h" + +using aidl::android::hardware::audio::common::SinkMetadata; +using aidl::android::hardware::audio::common::SourceMetadata; +using aidl::android::hardware::bluetooth::audio::ChannelMode; +using aidl::android::hardware::bluetooth::audio::PcmConfiguration; +using aidl::android::media::audio::common::AudioChannelLayout; +using aidl::android::media::audio::common::AudioConfigBase; +using aidl::android::media::audio::common::AudioDeviceDescription; +using aidl::android::media::audio::common::AudioDeviceType; +using aidl::android::media::audio::common::AudioFormatDescription; +using aidl::android::media::audio::common::AudioFormatType; +using aidl::android::media::audio::common::AudioIoFlags; +using aidl::android::media::audio::common::AudioOffloadInfo; +using aidl::android::media::audio::common::AudioPort; +using aidl::android::media::audio::common::AudioPortConfig; +using aidl::android::media::audio::common::AudioPortExt; +using aidl::android::media::audio::common::AudioProfile; +using aidl::android::media::audio::common::Int; +using aidl::android::media::audio::common::MicrophoneInfo; +using aidl::android::media::audio::common::PcmType; + +namespace aidl::android::hardware::audio::core { + +namespace { + +PcmType pcmTypeFromBitsPerSample(int8_t bitsPerSample) { + if (bitsPerSample == 8) + return PcmType::UINT_8_BIT; + else if (bitsPerSample == 16) + return PcmType::INT_16_BIT; + else if (bitsPerSample == 24) + return PcmType::INT_24_BIT; + else if (bitsPerSample == 32) + return PcmType::INT_32_BIT; + ALOGE("Unsupported bitsPerSample: %d", bitsPerSample); + return PcmType::DEFAULT; +} + +AudioChannelLayout channelLayoutFromChannelMode(ChannelMode mode) { + if (mode == ChannelMode::MONO) { + return AudioChannelLayout::make( + AudioChannelLayout::LAYOUT_MONO); + } else if (mode == ChannelMode::STEREO || mode == ChannelMode::DUALMONO) { + return AudioChannelLayout::make( + AudioChannelLayout::LAYOUT_STEREO); + } + ALOGE("Unsupported channel mode: %s", toString(mode).c_str()); + return AudioChannelLayout{}; +} + +} // namespace + +ModuleBluetoothBase::ModuleBluetoothBase(std::unique_ptr&& config) + : Module(Type::BLUETOOTH, std::move(config)) {} + +ndk::ScopedAStatus ModuleBluetoothBase::getBluetoothA2dp( + std::shared_ptr* _aidl_return) { + *_aidl_return = getBtA2dp().getInstance(); + LOG(DEBUG) << __func__ << ": returning instance of IBluetoothA2dp: " << _aidl_return->get(); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus ModuleBluetoothBase::getBluetoothLe( + std::shared_ptr* _aidl_return) { + *_aidl_return = getBtLe().getInstance(); + LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _aidl_return->get(); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus ModuleBluetoothBase::getBluetooth(std::shared_ptr* _aidl_return) { + // Only provided when Bluetooth port is present. + const auto& ports = getConfig().ports; + bool hasSco = false; + for (const auto& port : ports) { + if (port.ext.getTag() == AudioPortExt::device && + port.ext.get().device.type.connection == + AudioDeviceDescription::CONNECTION_BT_SCO) { + hasSco = true; + break; + } + } + if (hasSco) { + *_aidl_return = getBt().getInstance(); + } else { + *_aidl_return = nullptr; + } + LOG(DEBUG) << __func__ << ": returning instance of IBluetooth: " << _aidl_return->get(); + return ndk::ScopedAStatus::ok(); +} + +ChildInterface& ModuleBluetoothBase::getBtA2dp() { + if (!mBluetoothA2dp) { + auto handle = ndk::SharedRefBase::make(); + handle->registerHandler(std::bind(&ModuleBluetoothBase::bluetoothParametersUpdated, this)); + mBluetoothA2dp = handle; + } + return mBluetoothA2dp; +} + +ChildInterface& ModuleBluetoothBase::getBtLe() { + if (!mBluetoothLe) { + auto handle = ndk::SharedRefBase::make(); + handle->registerHandler(std::bind(&ModuleBluetoothBase::bluetoothParametersUpdated, this)); + mBluetoothLe = handle; + } + return mBluetoothLe; +} + +ChildInterface& ModuleBluetoothBase::getBt() { + if (!mBluetooth) { + auto handle = ndk::SharedRefBase::make(); + handle->registerHandler(std::bind(&ModuleBluetoothBase::bluetoothParametersUpdated, this)); + mBluetooth = handle; + } + return mBluetooth; +} + +ModuleBluetoothBase::BtProfileHandles ModuleBluetoothBase::getBtProfileManagerHandles() { + return std::make_tuple(getBt().getPtr(), getBtA2dp().getPtr(), getBtLe().getPtr()); +} + +ndk::ScopedAStatus ModuleBluetoothBase::getMicMute(bool* _aidl_return __unused) { + LOG(DEBUG) << __func__ << ": is not supported"; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + +ndk::ScopedAStatus ModuleBluetoothBase::setMicMute(bool in_mute __unused) { + LOG(DEBUG) << __func__ << ": is not supported"; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + +ndk::ScopedAStatus ModuleBluetoothBase::setAudioPortConfig(const AudioPortConfig& in_requested, + AudioPortConfig* out_suggested, + bool* _aidl_return) { + // Since 'fillConfig' is only called when a new port config is created, + // handle the case of updating an existing port config separately. + int32_t portId = 0; + if (in_requested.id != 0) { + auto& configs = getConfig().portConfigs; + if (auto existing = findById(configs, in_requested.id); + existing != configs.end()) { + auto& ports = getConfig().ports; + auto portIt = findById(ports, existing->portId); + if (portIt != ports.end() && portIt->ext.getTag() == AudioPortExt::Tag::device) { + portId = portIt->id; + } + } + } + + auto fillConfig = [this](const AudioPort& port, AudioPortConfig* config) { + if (port.ext.getTag() == AudioPortExt::device) { + CachedProxy proxy; + auto status = findOrCreateProxy(port, proxy); + if (status.isOk()) { + const auto& pcmConfig = proxy.pcmConfig; + LOG(DEBUG) << "setAudioPortConfig: suggesting port config from " + << pcmConfig.toString(); + const auto pcmType = pcmTypeFromBitsPerSample(pcmConfig.bitsPerSample); + const auto channelMask = channelLayoutFromChannelMode(pcmConfig.channelMode); + if (pcmType != PcmType::DEFAULT && channelMask != AudioChannelLayout{}) { + config->format = + AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType}; + config->channelMask = channelMask; + config->sampleRate = Int{.value = pcmConfig.sampleRateHz}; + config->flags = port.flags; + config->ext = port.ext; + return true; + } + } + } + return generateDefaultPortConfig(port, config); + }; + RETURN_STATUS_IF_ERROR( + Module::setAudioPortConfigImpl(in_requested, fillConfig, out_suggested, _aidl_return)); + if (portId != 0) { + auto& ports = getConfig().ports; + auto portIt = findById(ports, portId); + if (portIt != ports.end() && portIt->ext.getTag() == AudioPortExt::Tag::device) { + CachedProxy proxy; + // Ignore error similar to 'fillConfig', this can happen in VTS tests when + // device connections are simulated. + findOrCreateProxy(*portIt, proxy); + } + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus ModuleBluetoothBase::supportsVariableLatency(bool* _aidl_return) { + LOG(DEBUG) << __func__ << ": " << getType(); + *_aidl_return = true; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus ModuleBluetoothBase::checkAudioPatchEndpointsMatch( + const std::vector& sources, const std::vector& sinks) { + // Both sources and sinks must be non-empty, this is guaranteed by 'setAudioPatch'. + const bool isInput = sources[0]->ext.getTag() == AudioPortExt::device; + const int32_t devicePortId = isInput ? sources[0]->portId : sinks[0]->portId; + const auto proxyIt = mProxies.find(devicePortId); + if (proxyIt == mProxies.end()) return ndk::ScopedAStatus::ok(); + const auto& pcmConfig = proxyIt->second.pcmConfig; + const AudioPortConfig* mixPortConfig = isInput ? sinks[0] : sources[0]; + if (!StreamBluetooth::checkConfigParams( + pcmConfig, AudioConfigBase{.sampleRate = mixPortConfig->sampleRate->value, + .channelMask = *(mixPortConfig->channelMask), + .format = *(mixPortConfig->format)})) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + if (int32_t handle = mixPortConfig->ext.get().handle; handle > 0) { + mConnections.insert(std::pair(handle, devicePortId)); + } + return ndk::ScopedAStatus::ok(); +} + +void ModuleBluetoothBase::onExternalDeviceConnectionChanged(const AudioPort& audioPort, + bool connected) { + if (!connected) mProxies.erase(audioPort.id); +} + +ndk::ScopedAStatus ModuleBluetoothBase::createInputStream( + StreamContext&& context, const SinkMetadata& sinkMetadata, + const std::vector& microphones, std::shared_ptr* result) { + CachedProxy proxy; + RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy)); + return createStreamInstance(result, std::move(context), sinkMetadata, + microphones, getBtProfileManagerHandles(), + proxy.ptr, proxy.pcmConfig); +} + +ndk::ScopedAStatus ModuleBluetoothBase::createOutputStream( + StreamContext&& context, const SourceMetadata& sourceMetadata, + const std::optional& offloadInfo, std::shared_ptr* result) { + CachedProxy proxy; + RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy)); + return createStreamInstance(result, std::move(context), sourceMetadata, + offloadInfo, getBtProfileManagerHandles(), + proxy.ptr, proxy.pcmConfig); +} + +ndk::ScopedAStatus ModuleBluetoothBase::populateConnectedDevicePort(AudioPort* audioPort, + int32_t nextPortId) { + if (audioPort->ext.getTag() != AudioPortExt::device) { + LOG(ERROR) << __func__ << ": not a device port: " << audioPort->toString(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + const auto& devicePort = audioPort->ext.get(); + const auto& description = devicePort.device.type; + // This method must return an error when the device can not be connected. + // Since A2DP/LE status events are sent asynchronously, it is more reliable + // to attempt connecting to the BT stack rather than judge by the A2DP/LE status. + if (description.connection != AudioDeviceDescription::CONNECTION_BT_A2DP && + description.connection != AudioDeviceDescription::CONNECTION_BT_LE && + description.connection != AudioDeviceDescription::CONNECTION_BT_SCO && + !(description.connection == AudioDeviceDescription::CONNECTION_WIRELESS && + description.type == AudioDeviceType::OUT_HEARING_AID)) { + LOG(ERROR) << __func__ << ": unsupported device type: " << audioPort->toString(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + CachedProxy proxy; + RETURN_STATUS_IF_ERROR(createProxy(*audioPort, nextPortId, proxy)); + // If the device is actually connected, it is configured by the BT stack. + // Provide the current configuration instead of all possible profiles. + const auto& pcmConfig = proxy.pcmConfig; + audioPort->profiles.assign( + {AudioProfile{.format = AudioFormatDescription{.type = AudioFormatType::PCM, + .pcm = pcmTypeFromBitsPerSample( + pcmConfig.bitsPerSample)}, + .channelMasks = std::vector( + {channelLayoutFromChannelMode(pcmConfig.channelMode)}), + .sampleRates = std::vector({pcmConfig.sampleRateHz})}}); + LOG(DEBUG) << __func__ << ": " << audioPort->toString(); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus ModuleBluetoothBase::onMasterMuteChanged(bool) { + LOG(DEBUG) << __func__ << ": is not supported"; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + +ndk::ScopedAStatus ModuleBluetoothBase::onMasterVolumeChanged(float) { + LOG(DEBUG) << __func__ << ": is not supported"; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + +int32_t ModuleBluetoothBase::getNominalLatencyMs(const AudioPortConfig& portConfig) { + const auto connectionsIt = mConnections.find(portConfig.ext.get().handle); + if (connectionsIt != mConnections.end()) { + const auto proxyIt = mProxies.find(connectionsIt->second); + if (proxyIt != mProxies.end()) { + auto proxy = proxyIt->second.ptr; + size_t dataIntervalUs = 0; + if (!proxy->getPreferredDataIntervalUs(dataIntervalUs)) { + LOG(WARNING) << __func__ << ": could not fetch preferred data interval"; + } + const bool isInput = portConfig.flags->getTag() == AudioIoFlags::input; + return isInput ? StreamInBluetooth::getNominalLatencyMs(dataIntervalUs) + : StreamOutBluetooth::getNominalLatencyMs(dataIntervalUs); + } + } + LOG(ERROR) << __func__ << ": no connection or proxy found for " << portConfig.toString(); + return Module::getNominalLatencyMs(portConfig); +} + +binder_status_t ModuleBluetoothBase::dump(int fd, const char** args, uint32_t numArgs) { + if (!::aidl::android::hardware::audio::common::hasArgument( + args, numArgs, + ::aidl::android::hardware::audio::common::kDumpFromAudioServerArgument)) { + // Streams are dumped as part of audio flinger threads dump, + // no need for a separate dump since the module itself does not + // have anything interesting. + Module::dump(fd, args, numArgs); + } + return ::android::OK; +} + +ndk::ScopedAStatus ModuleBluetoothBase::createProxy(const AudioPort& audioPort, + int32_t instancePortId, CachedProxy& proxy) { + proxy.ptr = createProxyInstance(audioPort.flags.getTag() == AudioIoFlags::input); + const auto& devicePort = audioPort.ext.get(); + const auto device = devicePort.device.type; + bool registrationSuccess = false; + for (int i = 0; i < kCreateProxyRetries && !registrationSuccess; ++i) { + registrationSuccess = proxy.ptr->registerPort(device); + usleep(kCreateProxyRetrySleepMs * 1000); + } + if (!registrationSuccess) { + LOG(ERROR) << __func__ << ": failed to register BT port for " << device.toString(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + if (!proxy.ptr->loadAudioConfig(proxy.pcmConfig)) { + LOG(ERROR) << __func__ << ": state=" << proxy.ptr->getState() + << ", failed to load audio config"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + mProxies.insert(std::pair(instancePortId, proxy)); + mProxiesWeak[device] = proxy.ptr; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus ModuleBluetoothBase::fetchAndCheckProxy(const StreamContext& context, + CachedProxy& proxy) { + const auto connectionsIt = mConnections.find(context.getMixPortHandle()); + if (connectionsIt != mConnections.end()) { + const auto proxyIt = mProxies.find(connectionsIt->second); + if (proxyIt != mProxies.end()) { + proxy = proxyIt->second; + mProxies.erase(proxyIt); + } + mConnections.erase(connectionsIt); + } + if (proxy.ptr != nullptr) { + if (!StreamBluetooth::checkConfigParams( + proxy.pcmConfig, AudioConfigBase{.sampleRate = context.getSampleRate(), + .channelMask = context.getChannelLayout(), + .format = context.getFormat()})) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } + // Not having a proxy is OK, it may happen in VTS tests when streams are opened on unconnected + // mix ports. + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus ModuleBluetoothBase::findOrCreateProxy(const AudioPort& audioPort, + CachedProxy& proxy) { + if (auto proxyIt = mProxies.find(audioPort.id); proxyIt != mProxies.end()) { + proxy = proxyIt->second; + return ndk::ScopedAStatus::ok(); + } + if (audioPort.ext.getTag() == AudioPortExt::Tag::device) { + const auto& description = audioPort.ext.get().device.type; + auto weakIt = mProxiesWeak.find(description); + if (weakIt != mProxiesWeak.end()) { + if (auto existingProxy = weakIt->second.lock()) { + proxy.ptr = existingProxy; + if (proxy.ptr->loadAudioConfig(proxy.pcmConfig)) { + mProxies.insert(std::pair(audioPort.id, proxy)); + LOG(DEBUG) << __func__ << ": Reused existing proxy for " + << description.toString(); + return ndk::ScopedAStatus::ok(); + } else { + LOG(ERROR) << __func__ << ": Failed to load config from reused proxy"; + } + } + // If expired or failed to load config, remove from map and create new + mProxiesWeak.erase(weakIt); + } + } + return createProxy(audioPort, audioPort.id, proxy); +} + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/bluetooth/StreamBluetooth.cpp b/audio/bluetooth/StreamBluetooth.cpp index 77ce121..782c51c 100644 --- a/audio/bluetooth/StreamBluetooth.cpp +++ b/audio/bluetooth/StreamBluetooth.cpp @@ -14,11 +14,12 @@ * limitations under the License. */ +#include #include #define LOG_TAG "AHAL_StreamBluetooth" +#include #include -#include #include #include "core-impl/StreamBluetooth.h" @@ -27,6 +28,7 @@ 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::LatencyMode; using aidl::android::hardware::bluetooth::audio::PcmConfiguration; using aidl::android::hardware::bluetooth::audio::PresentationPosition; using aidl::android::media::audio::common::AudioChannelLayout; @@ -35,12 +37,11 @@ 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::AudioLatencyMode; 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::BluetoothAudioPort; using android::bluetooth::audio::aidl::BluetoothStreamState; namespace aidl::android::hardware::audio::core { @@ -50,21 +51,78 @@ constexpr int kBluetoothDefaultOutputBufferMs = 10; // constexpr int kBluetoothSpatializerOutputBufferMs = 10; constexpr int kBluetoothDefaultRemoteDelayMs = 200; +namespace { + +LatencyMode audio2bt_LatencyMode(AudioLatencyMode mode) { + switch (mode) { + case AudioLatencyMode::FREE: + return LatencyMode::FREE; + case AudioLatencyMode::LOW: + return LatencyMode::LOW_LATENCY; + case AudioLatencyMode::DYNAMIC_SPATIAL_AUDIO_SOFTWARE: + return LatencyMode::DYNAMIC_SPATIAL_AUDIO_SOFTWARE; + case AudioLatencyMode::DYNAMIC_SPATIAL_AUDIO_HARDWARE: + return LatencyMode::DYNAMIC_SPATIAL_AUDIO_HARDWARE; + } + return LatencyMode::UNKNOWN; +} + +std::optional bt2audio_LatencyMode(LatencyMode mode) { + switch (mode) { + case LatencyMode::UNKNOWN: + return std::nullopt; + case LatencyMode::FREE: + return AudioLatencyMode::FREE; + case LatencyMode::LOW_LATENCY: + return AudioLatencyMode::LOW; + case LatencyMode::DYNAMIC_SPATIAL_AUDIO_SOFTWARE: + return AudioLatencyMode::DYNAMIC_SPATIAL_AUDIO_SOFTWARE; + case LatencyMode::DYNAMIC_SPATIAL_AUDIO_HARDWARE: + return AudioLatencyMode::DYNAMIC_SPATIAL_AUDIO_HARDWARE; + } + return std::nullopt; +} + +std::vector bt2audio_LatencyModes(const std::vector& modes) { + std::vector result; + for (const auto m : modes) { + if (const auto alm = bt2audio_LatencyMode(m); alm.has_value()) { + result.push_back(*alm); + } + } + return result; +} + +} // namespace + +void PortCallbacksHandler::onRecommendedLatencyModeChanged(const std::vector& modes) { + mStreamCallback->onRecommendedLatencyModeChanged(bt2audio_LatencyModes(modes)); +} + StreamBluetooth::StreamBluetooth(StreamContext* context, const Metadata& metadata, ModuleBluetooth::BtProfileHandles&& btHandles, - const std::shared_ptr& btDeviceProxy, + const std::shared_ptr& btDeviceProxy, const PcmConfiguration& pcmConfig) : StreamCommonImpl(context, metadata), mFrameSizeBytes(getContext().getFrameSize()), mIsInput(isInput(metadata)), mBluetoothA2dp(std::move(std::get(btHandles))), mBluetoothLe(std::move(std::get(btHandles))), + mBluetoothHfp(std::move(std::get(btHandles))), mPreferredDataIntervalUs(pcmConfig.dataIntervalUs != 0 ? pcmConfig.dataIntervalUs : (mIsInput ? kBluetoothDefaultInputBufferMs : kBluetoothDefaultOutputBufferMs) * 1000), - mBtDeviceProxy(btDeviceProxy) {} + mCallbacksHandler(new PortCallbacksHandler(getContext().getOutEventCallback())), + mBtDeviceProxy(btDeviceProxy) { + if (mBtDeviceProxy != nullptr) { + mSessionTypeName = mBtDeviceProxy->getSessionNameForDebug(); + if (mCallbacksHandler->hasCallback()) { + mBtDeviceProxy->setCallbacks(mCallbacksHandler); + } + } +} StreamBluetooth::~StreamBluetooth() { cleanupWorker(); @@ -97,15 +155,16 @@ StreamBluetooth::~StreamBluetooth() { ::android::status_t StreamBluetooth::transfer(void* buffer, size_t frameCount, size_t* actualFrameCount, int32_t* latencyMs) { std::lock_guard guard(mLock); - *actualFrameCount = 0; - *latencyMs = StreamDescriptor::LATENCY_UNKNOWN; - if (mBtDeviceProxy == nullptr || mBtDeviceProxy->getState() == BluetoothStreamState::DISABLED) { - // The BT session is turned down, silently ignore write. + if (!mEnabled) { + *actualFrameCount = frameCount; + *latencyMs = kBluetoothDefaultRemoteDelayMs; + usleep((float)(frameCount * 1000000) / (float)getContext().getSampleRate()); return ::android::OK; } - if (!mBtDeviceProxy->start()) { - LOG(WARNING) << __func__ << ": state= " << mBtDeviceProxy->getState() - << " failed to start, will retry"; + *actualFrameCount = 0; + *latencyMs = StreamDescriptor::LATENCY_UNKNOWN; + if (mBtDeviceProxy == nullptr || !mBtDeviceProxy->start()) { + // The BT session is turned down. return ::android::OK; } *latencyMs = 0; @@ -113,17 +172,24 @@ StreamBluetooth::~StreamBluetooth() { const size_t bytesTransferred = mIsInput ? mBtDeviceProxy->readData(buffer, bytesToTransfer) : mBtDeviceProxy->writeData(buffer, bytesToTransfer); *actualFrameCount = bytesTransferred / mFrameSizeBytes; - PresentationPosition presentation_position; - if (!mBtDeviceProxy->getPresentationPosition(presentation_position)) { - presentation_position.remoteDeviceAudioDelayNanos = - kBluetoothDefaultRemoteDelayMs * NANOS_PER_MILLISECOND; - LOG(WARNING) << __func__ << ": getPresentationPosition failed, latency info is unavailable"; + + const int64_t decimPresentationPosition = + getContext().getFrameCount() / getContext().getSampleRate(); + if (decimPresentationPosition != mDecimPresentationPosition) { + if (!mBtDeviceProxy->getPresentationPosition(mActualPresentationPosition)) { + mActualPresentationPosition.remoteDeviceAudioDelayNanos = + kBluetoothDefaultRemoteDelayMs * NANOS_PER_MILLISECOND; + LOG(WARNING) << __func__ + << ": getPresentationPosition failed, latency info is unavailable"; + } + mDecimPresentationPosition = decimPresentationPosition; } // TODO(b/317117580): incorporate logic from // packages/modules/Bluetooth/system/audio_bluetooth_hw/stream_apis.cc // out_calculate_feeding_delay_ms / in_calculate_starving_delay_ms - *latencyMs = std::max(*latencyMs, (int32_t)(presentation_position.remoteDeviceAudioDelayNanos / - NANOS_PER_MILLISECOND)); + *latencyMs = + std::max(*latencyMs, (int32_t)(mActualPresentationPosition.remoteDeviceAudioDelayNanos / + NANOS_PER_MILLISECOND)); return ::android::OK; } @@ -151,8 +217,8 @@ bool StreamBluetooth::checkConfigParams(const PcmConfiguration& pcmConfig, const int8_t bitsPerSample = aidl::android::hardware::audio::common::getPcmSampleSizeInBytes(config.format.pcm) * 8; if (bitsPerSample != pcmConfig.bitsPerSample) { - LOG(ERROR) << __func__ << ": bits per sample mismatch, stream value=" << bitsPerSample - << ", BT HAL value=" << pcmConfig.bitsPerSample; + LOG(ERROR) << __func__ << ": bits per sample mismatch, stream value=" << (int)bitsPerSample + << ", BT HAL value=" << (int)pcmConfig.bitsPerSample; return false; } return true; @@ -161,9 +227,15 @@ bool StreamBluetooth::checkConfigParams(const PcmConfiguration& pcmConfig, ndk::ScopedAStatus StreamBluetooth::prepareToClose() { std::lock_guard guard(mLock); if (mBtDeviceProxy != nullptr) { - if (mBtDeviceProxy->getState() != BluetoothStreamState::DISABLED) { - mBtDeviceProxy->stop(); - } + mBtDeviceProxy->stop(); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StreamBluetooth::setConnectedDevices(const std::vector& devices) { + RETURN_STATUS_IF_ERROR(StreamCommonImpl::setConnectedDevices(devices)); + if (devices.empty()) { + return ndk::ScopedAStatus::fromStatus(standby()); } return ndk::ScopedAStatus::ok(); } @@ -176,6 +248,10 @@ ndk::ScopedAStatus StreamBluetooth::prepareToClose() { ::android::status_t StreamBluetooth::start() { std::lock_guard guard(mLock); + if (!mEnabled) { + return ::android::OK; + } + mDecimPresentationPosition = -1; if (mBtDeviceProxy != nullptr) mBtDeviceProxy->start(); return ::android::OK; } @@ -207,8 +283,7 @@ ndk::ScopedAStatus StreamBluetooth::bluetoothParametersUpdated() { if (mIsInput) { return ndk::ScopedAStatus::ok(); } - auto applyParam = [](const std::shared_ptr& proxy, - bool isEnabled) -> bool { + auto applyParam = [](const std::shared_ptr& proxy, bool isEnabled) -> bool { if (!isEnabled) { if (proxy->suspend()) return proxy->setState(BluetoothStreamState::DISABLED); return false; @@ -221,17 +296,67 @@ ndk::ScopedAStatus StreamBluetooth::bluetoothParametersUpdated() { bool hasLeParam, enableLe; auto btLe = mBluetoothLe.lock(); hasLeParam = btLe != nullptr && btLe->isEnabled(&enableLe).isOk(); + bool hasScoParam; + Bluetooth::ScoConfig scoConfig; + auto btHfp = mBluetoothHfp.lock(); + hasScoParam = btHfp != nullptr && btHfp->setScoConfig({}, &scoConfig).isOk(); std::lock_guard guard(mLock); if (mBtDeviceProxy != nullptr) { if ((hasA2dpParam && mBtDeviceProxy->isA2dp() && !applyParam(mBtDeviceProxy, enableA2dp)) || - (hasLeParam && mBtDeviceProxy->isLeAudio() && !applyParam(mBtDeviceProxy, enableLe))) { + (hasLeParam && mBtDeviceProxy->isLeAudio() && !applyParam(mBtDeviceProxy, enableLe)) || + (hasScoParam && mBtDeviceProxy->isHfp() && + !applyParam(mBtDeviceProxy, scoConfig.isEnabled.value().value))) { LOG(DEBUG) << __func__ << ": applyParam failed"; return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } + mEnabled = (mBtDeviceProxy->isA2dp() && enableA2dp) || + (mBtDeviceProxy->isLeAudio() && enableLe) || + (mBtDeviceProxy->isHfp() && scoConfig.isEnabled.value().value); + LOG(INFO) << __func__ << ": mEnabled: " << mEnabled; } return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus StreamBluetooth::getRecommendedLatencyModes( + std::vector* _aidl_return) { + LOG(DEBUG) << __func__; + std::vector modes; + std::lock_guard guard(mLock); + if (!mBtDeviceProxy || !mBtDeviceProxy->getRecommendedLatencyModes(&modes)) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + *_aidl_return = bt2audio_LatencyModes(modes); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StreamBluetooth::setLatencyMode(AudioLatencyMode in_mode) { + LOG(DEBUG) << __func__ << ": " << toString(in_mode); + std::lock_guard guard(mLock); + if (mBtDeviceProxy != nullptr) { + return mBtDeviceProxy->setLatencyMode(audio2bt_LatencyMode(in_mode)) + ? ndk::ScopedAStatus::ok() + : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); +} + +void StreamBluetooth::dump(int fd, const char** args, uint32_t numArgs) { + const int indent = 4; + if (::aidl::android::hardware::audio::common::hasArgument( + args, numArgs, + ::aidl::android::hardware::audio::common::kDumpFromAudioServerArgument)) { + // Just provide the frames count. + dprintf(fd, "%*sFrames transferred: %" PRId64 "\n", indent, "", + getContext().getFrameCount()); + return; + } + dprintf(fd, "%*sI/O handle %d:\n", indent, "", getContext().getMixPortHandle()); + dprintf(fd, "%*sFrames transferred: %" PRId64 "\n", indent + 2, "", + getContext().getFrameCount()); + dprintf(fd, "%*sSession type: %s\n", indent + 2, "", mSessionTypeName.c_str()); + dprintf(fd, "%*sSample rate: %d\n", indent + 2, "", getContext().getSampleRate()); +} + // static int32_t StreamInBluetooth::getNominalLatencyMs(size_t dataIntervalUs) { if (dataIntervalUs == 0) dataIntervalUs = kBluetoothDefaultInputBufferMs * 1000LL; @@ -241,7 +366,7 @@ int32_t StreamInBluetooth::getNominalLatencyMs(size_t dataIntervalUs) { StreamInBluetooth::StreamInBluetooth(StreamContext&& context, const SinkMetadata& sinkMetadata, const std::vector& microphones, ModuleBluetooth::BtProfileHandles&& btProfileHandles, - const std::shared_ptr& btDeviceProxy, + const std::shared_ptr& btDeviceProxy, const PcmConfiguration& pcmConfig) : StreamIn(std::move(context), microphones), StreamBluetooth(&mContextInstance, sinkMetadata, std::move(btProfileHandles), btDeviceProxy, @@ -253,6 +378,11 @@ ndk::ScopedAStatus StreamInBluetooth::getActiveMicrophones( return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } +binder_status_t StreamInBluetooth::dump(int fd, const char** args, uint32_t numArgs) { + StreamBluetooth::dump(fd, args, numArgs); + return ::android::OK; +} + // static int32_t StreamOutBluetooth::getNominalLatencyMs(size_t dataIntervalUs) { if (dataIntervalUs == 0) dataIntervalUs = kBluetoothDefaultOutputBufferMs * 1000LL; @@ -263,10 +393,24 @@ StreamOutBluetooth::StreamOutBluetooth(StreamContext&& context, const SourceMetadata& sourceMetadata, const std::optional& offloadInfo, ModuleBluetooth::BtProfileHandles&& btProfileHandles, - const std::shared_ptr& btDeviceProxy, + const std::shared_ptr& btDeviceProxy, const PcmConfiguration& pcmConfig) : StreamOut(std::move(context), offloadInfo), StreamBluetooth(&mContextInstance, sourceMetadata, std::move(btProfileHandles), btDeviceProxy, pcmConfig) {} +ndk::ScopedAStatus StreamOutBluetooth::getRecommendedLatencyModes( + std::vector* _aidl_return) { + return StreamBluetooth::getRecommendedLatencyModes(_aidl_return); +} + +ndk::ScopedAStatus StreamOutBluetooth::setLatencyMode(AudioLatencyMode in_mode) { + return StreamBluetooth::setLatencyMode(in_mode); +} + +binder_status_t StreamOutBluetooth::dump(int fd, const char** args, uint32_t numArgs) { + StreamBluetooth::dump(fd, args, numArgs); + return ::android::OK; +} + } // namespace aidl::android::hardware::audio::core diff --git a/audio/config/audioPolicy/api/current.txt b/audio/config/audioPolicy/api/current.txt index c675820..a68b012 100644 --- a/audio/config/audioPolicy/api/current.txt +++ b/audio/config/audioPolicy/api/current.txt @@ -1,13 +1,44 @@ -// Signature format: 2.0 +// Signature format: 6.0 +// - style=java package android.audio.policy.configuration { public class AttachedDevices { ctor public AttachedDevices(); - method @Nullable public java.util.List getItem(); + method @Nullable public java.util.List getItem(); } public enum AudioChannelMask { method @NonNull public String getRawName(); + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_0; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_0_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_1; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_10; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_10_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_11; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_11_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_12; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_12_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_13; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_13_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_14; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_14_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_1_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_2; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_2_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_3; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_3_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_4; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_4_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_5; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_5_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_6; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_6_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_7; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_7_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_8; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_8_HRZ; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_9; + enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_9_HRZ; enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_INDEX_MASK_1; enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_INDEX_MASK_10; enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_INDEX_MASK_11; @@ -91,6 +122,8 @@ package android.audio.policy.configuration { enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_AUX_DIGITAL; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BACK_MIC; + enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BLE_CENTRAL; + enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BLE_CENTRAL_BROADCAST; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BLE_HEADSET; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BLUETOOTH_A2DP; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BLUETOOTH_BLE; @@ -124,7 +157,9 @@ package android.audio.policy.configuration { enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_AUX_DIGITAL; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_AUX_LINE; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLE_BROADCAST; + enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLE_CENTRAL; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLE_HEADSET; + enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLE_HEARING_AID; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLE_SPEAKER; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLUETOOTH_A2DP; enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES; @@ -200,6 +235,7 @@ package android.audio.policy.configuration { enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APE; enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX; enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX_ADAPTIVE; + enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX_ADAPTIVE_PLUS; enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX_ADAPTIVE_QLEA; enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX_ADAPTIVE_R4; enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX_HD; @@ -373,7 +409,7 @@ package android.audio.policy.configuration { public static class DevicePorts.DevicePort { ctor public DevicePorts.DevicePort(); method @Nullable public String getAddress(); - method @Nullable public java.util.List getEncodedFormats(); + method @Nullable public java.util.List getEncodedFormats(); method @Nullable public android.audio.policy.configuration.Gains getGains(); method @Nullable public java.util.List getProfile(); method @Nullable public android.audio.policy.configuration.Role getRole(); @@ -381,7 +417,7 @@ package android.audio.policy.configuration { method @Nullable public String getType(); method @Nullable public boolean get_default(); method public void setAddress(@Nullable String); - method public void setEncodedFormats(@Nullable java.util.List); + method public void setEncodedFormats(@Nullable java.util.List); method public void setGains(@Nullable android.audio.policy.configuration.Gains); method public void setRole(@Nullable android.audio.policy.configuration.Role); method public void setTagName(@Nullable String); @@ -504,7 +540,7 @@ package android.audio.policy.configuration { public class Reference { ctor public Reference(); method @Nullable public String getName(); - method @Nullable public java.util.List getPoint(); + method @Nullable public java.util.List getPoint(); method public void setName(@Nullable String); } @@ -537,9 +573,9 @@ package android.audio.policy.configuration { public static class SurroundFormats.Format { ctor public SurroundFormats.Format(); method @Nullable public String getName(); - method @Nullable public java.util.List getSubformats(); + method @Nullable public java.util.List getSubformats(); method public void setName(@Nullable String); - method public void setSubformats(@Nullable java.util.List); + method public void setSubformats(@Nullable java.util.List); } public class SurroundSound { @@ -557,7 +593,7 @@ package android.audio.policy.configuration { public class Volume { ctor public Volume(); method @Nullable public android.audio.policy.configuration.DeviceCategory getDeviceCategory(); - method @Nullable public java.util.List getPoint(); + method @Nullable public java.util.List getPoint(); method @Nullable public String getRef(); method @Nullable public android.audio.policy.configuration.AudioStreamType getStream(); method public void setDeviceCategory(@Nullable android.audio.policy.configuration.DeviceCategory); diff --git a/audio/config/audioPolicy/api/removed.txt b/audio/config/audioPolicy/api/removed.txt index d802177..c079792 100644 --- a/audio/config/audioPolicy/api/removed.txt +++ b/audio/config/audioPolicy/api/removed.txt @@ -1 +1,2 @@ -// Signature format: 2.0 +// Signature format: 6.0 +// - style=java diff --git a/audio/config/audioPolicy/audio_policy_configuration.xsd b/audio/config/audioPolicy/audio_policy_configuration.xsd index 94856a5..c15ccf6 100644 --- a/audio/config/audioPolicy/audio_policy_configuration.xsd +++ b/audio/config/audioPolicy/audio_policy_configuration.xsd @@ -271,8 +271,10 @@ + + @@ -306,6 +308,8 @@ + + @@ -429,6 +433,7 @@ + @@ -524,7 +529,37 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/audio/config/audioPolicy/capengine/api/current.txt b/audio/config/audioPolicy/capengine/api/current.txt index 481abbf..273a09e 100644 --- a/audio/config/audioPolicy/capengine/api/current.txt +++ b/audio/config/audioPolicy/capengine/api/current.txt @@ -1,4 +1,5 @@ -// Signature format: 2.0 +// Signature format: 6.0 +// - style=java package android.audio.policy.capengine.configuration { public class BitParameterBlockType { diff --git a/audio/config/audioPolicy/capengine/api/removed.txt b/audio/config/audioPolicy/capengine/api/removed.txt index d802177..c079792 100644 --- a/audio/config/audioPolicy/capengine/api/removed.txt +++ b/audio/config/audioPolicy/capengine/api/removed.txt @@ -1 +1,2 @@ -// Signature format: 2.0 +// Signature format: 6.0 +// - style=java diff --git a/audio/config/audioPolicy/engine/api/current.txt b/audio/config/audioPolicy/engine/api/current.txt index 41cfb44..73b2173 100644 --- a/audio/config/audioPolicy/engine/api/current.txt +++ b/audio/config/audioPolicy/engine/api/current.txt @@ -1,4 +1,5 @@ -// Signature format: 2.0 +// Signature format: 6.0 +// - style=java package android.audio.policy.engine.configuration { public class AttributesGroup { @@ -203,8 +204,10 @@ package android.audio.policy.engine.configuration { method @Nullable public java.util.List getAttributesGroup(); method @Nullable public int getId(); method @Nullable public String getName(); + method @Nullable public int getZoneId(); method public void setId(@Nullable int); method public void setName(@Nullable String); + method public void setZoneId(@Nullable int); } public enum SourceEnumType { @@ -265,6 +268,8 @@ package android.audio.policy.engine.configuration { enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST; enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_NOTIFICATION_EVENT; enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE; + enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_NOTIFICATION_VIBRATION; + enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_RINGTONE_VIBRATION; enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_SAFETY; enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_SPEAKER_CLEANUP; enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_UNKNOWN; @@ -301,7 +306,7 @@ package android.audio.policy.engine.configuration { public class Volume { ctor public Volume(); method @Nullable public android.audio.policy.engine.configuration.DeviceCategory getDeviceCategory(); - method @Nullable public java.util.List getPoint(); + method @Nullable public java.util.List getPoint(); method @Nullable public String getRef(); method public void setDeviceCategory(@Nullable android.audio.policy.engine.configuration.DeviceCategory); method public void setRef(@Nullable String); @@ -326,7 +331,7 @@ package android.audio.policy.engine.configuration { public class VolumeRef { ctor public VolumeRef(); method @Nullable public String getName(); - method @Nullable public java.util.List getPoint(); + method @Nullable public java.util.List getPoint(); method public void setName(@Nullable String); } diff --git a/audio/config/audioPolicy/engine/api/removed.txt b/audio/config/audioPolicy/engine/api/removed.txt index d802177..c079792 100644 --- a/audio/config/audioPolicy/engine/api/removed.txt +++ b/audio/config/audioPolicy/engine/api/removed.txt @@ -1 +1,2 @@ -// Signature format: 2.0 +// Signature format: 6.0 +// - style=java diff --git a/audio/config/audioPolicy/engine/audio_policy_engine_configuration.xsd b/audio/config/audioPolicy/engine/audio_policy_engine_configuration.xsd index 02250c7..301c457 100644 --- a/audio/config/audioPolicy/engine/audio_policy_engine_configuration.xsd +++ b/audio/config/audioPolicy/engine/audio_policy_engine_configuration.xsd @@ -107,6 +107,7 @@ + @@ -377,6 +378,8 @@ + + diff --git a/audio/deprecated/StreamSwitcher.cpp b/audio/deprecated/StreamSwitcher.cpp index f1e0f52..ca8be53 100644 --- a/audio/deprecated/StreamSwitcher.cpp +++ b/audio/deprecated/StreamSwitcher.cpp @@ -18,8 +18,8 @@ #define LOG_TAG "AHAL_StreamSwitcher" +#include #include -#include #include #include "core-impl/StreamStub.h" @@ -135,6 +135,18 @@ ndk::ScopedAStatus StreamSwitcher::removeEffect(const std::shared_ptr& return !mIsStubStream ? mStream->removeEffect(in_effect) : ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus StreamSwitcher::createMmapBuffer(MmapBufferDescriptor* _aidl_return) { + if (mStream == nullptr) { + LOG(ERROR) << __func__ << ": stream was closed"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + if (mIsStubStream) { + LOG(ERROR) << __func__ << ": the stream is not connected"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + return mStream->createMmapBuffer(_aidl_return); +} + ndk::ScopedAStatus StreamSwitcher::getStreamCommonCommon( std::shared_ptr* _aidl_return) { if (!mCommon) { diff --git a/audio/deprecated/StreamSwitcher.h b/audio/deprecated/StreamSwitcher.h index 56fdd23..30c98a2 100644 --- a/audio/deprecated/StreamSwitcher.h +++ b/audio/deprecated/StreamSwitcher.h @@ -124,6 +124,7 @@ class StreamSwitcher : virtual public StreamCommonInterface { ndk::ScopedAStatus removeEffect( const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_effect) override; + ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* _aidl_return) override; ndk::ScopedAStatus getStreamCommonCommon(std::shared_ptr* _aidl_return) override; ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override; diff --git a/audio/include/core-impl/Bluetooth.h b/audio/include/core-impl/Bluetooth.h index 002cb19..0476c09 100644 --- a/audio/include/core-impl/Bluetooth.h +++ b/audio/include/core-impl/Bluetooth.h @@ -31,7 +31,7 @@ class ParamChangeHandler { std::function mHandler = nullptr; }; -class Bluetooth : public BnBluetooth { +class Bluetooth : public BnBluetooth, public ParamChangeHandler { public: Bluetooth(); diff --git a/audio/include/core-impl/BluetoothAudioPort.h b/audio/include/core-impl/BluetoothAudioPort.h new file mode 100644 index 0000000..26378c5 --- /dev/null +++ b/audio/include/core-impl/BluetoothAudioPort.h @@ -0,0 +1,184 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +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, +}; + +inline 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); + } +} + +class BluetoothAudioPortCallbacks { + public: + virtual ~BluetoothAudioPortCallbacks() = default; + virtual void onRecommendedLatencyModeChanged( + const std::vector<::aidl::android::hardware::bluetooth::audio::LatencyMode>&) = 0; +}; + +/** + * 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: + 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&) = 0; + + /** + * When the Audio framework / HAL wants to change the stream state, it invokes + * these 4 functions to control the Bluetooth stack (Audio Control Path). + * Note: standby(), start() and suspend() will return true when there are no errors. + + * Called by Audio framework / HAL to change the state to stand by. When A2DP/LE profile is + * disabled, the port is first set to STANDBY by calling suspend and then mState is set to + * DISABLED. To reset the state back to STANDBY this method is called. + */ + virtual bool standby() = 0; + + /** + * Called by Audio framework / HAL to start the stream. Starts the BT session stream with + * low latency when it is supported. + */ + 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 = 0; + + /** + * 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 = 0; + + /** + * 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 isHfp() 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; } + + virtual bool setLatencyMode(::aidl::android::hardware::bluetooth::audio::LatencyMode) { + return false; + } + + virtual bool getRecommendedLatencyModes( + std::vector<::aidl::android::hardware::bluetooth::audio::LatencyMode>*) { + return false; + } + + virtual void setCallbacks(const std::shared_ptr&) = 0; + + virtual std::string getSessionNameForDebug() const = 0; +}; + +} // namespace android::bluetooth::audio::aidl diff --git a/audio/include/core-impl/Configuration.h b/audio/include/core-impl/Configuration.h index a56c8c9..d84cd9a 100644 --- a/audio/include/core-impl/Configuration.h +++ b/audio/include/core-impl/Configuration.h @@ -26,4 +26,6 @@ std::unique_ptr getConfiguration(Module::Type moduleType) std::vector getStandard16And24BitPcmAudioProfiles(); +const std::string kPortNameTelephonyTx = "telephony_tx"; + } // namespace aidl::android::hardware::audio::core::internal diff --git a/audio/include/core-impl/DevicePortProxy.h b/audio/include/core-impl/DevicePortProxy.h index ccb23bb..b43e304 100644 --- a/audio/include/core-impl/DevicePortProxy.h +++ b/audio/include/core-impl/DevicePortProxy.h @@ -20,150 +20,35 @@ #include #include +#include -#include -#include #include -#include -#include #include #include +#include "BluetoothAudioPort.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&) = 0; - - /** - * When the Audio framework / HAL wants to change the stream state, it invokes - * these 4 functions to control the Bluetooth stack (Audio Control Path). - * Note: standby(), start() and suspend() will return true when there are no errors. - - * Called by Audio framework / HAL to change the state to stand by. When A2DP/LE profile is - * disabled, the port is first set to STANDBY by calling suspend and then mState is set to - * DISABLED. To reset the state back to STANDBY this method is called. - */ - virtual bool standby() = 0; - - /** - * 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 BluetoothSession; class BluetoothAudioPortAidl : public BluetoothAudioPort { public: - BluetoothAudioPortAidl(); + explicit BluetoothAudioPortAidl(std::optional supportsLowLatency); virtual ~BluetoothAudioPortAidl(); bool registerPort(const ::aidl::android::media::audio::common::AudioDeviceDescription& - description) override; + description) override EXCLUDES(mCvMutex); void unregisterPort() override; bool loadAudioConfig( ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& audio_cfg) override; - bool standby() override; - bool start() override; - bool suspend() override; - void stop() override; + bool standby() override EXCLUDES(mCvMutex); + bool start() override EXCLUDES(mCvMutex); + bool suspend() override EXCLUDES(mCvMutex); + void stop() override EXCLUDES(mCvMutex); bool getPresentationPosition(::aidl::android::hardware::bluetooth::audio::PresentationPosition& presentation_position) const override; @@ -176,26 +61,41 @@ class BluetoothAudioPortAidl : public BluetoothAudioPort { /** * 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; + BluetoothStreamState getState() const override EXCLUDES(mCvMutex); - bool setState(BluetoothStreamState state) override; + bool setState(BluetoothStreamState state) override EXCLUDES(mCvMutex); bool isA2dp() const override; + bool isHfp() const override; + bool isLeAudio() const override; bool getPreferredDataIntervalUs(size_t& interval_us) const override; + bool getRecommendedLatencyModes( + std::vector<::aidl::android::hardware::bluetooth::audio::LatencyMode>* latencyModes) + override EXCLUDES(mCvMutex); + + void setCallbacks(const std::shared_ptr& callbacks) override + EXCLUDES(mCvMutex); + + std::string getSessionNameForDebug() 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; + uint32_t mResampleRatio = 0; + using Resampler = std::unique_ptr; + Resampler mResampler = {nullptr, nullptr}; + std::shared_ptr mCallbacks GUARDED_BY(mCvMutex); + std::optional mSupportsLowLatency GUARDED_BY(mCvMutex); bool inUse() const; + BluetoothSession* getSession() const EXCLUDES(mCvMutex); std::string debugMessage() const; @@ -205,31 +105,44 @@ class BluetoothAudioPortAidl : public BluetoothAudioPort { // conditional variable serves this purpose. mutable std::mutex mCvMutex; std::condition_variable mInternalCv GUARDED_BY(mCvMutex); + // do not call into BluetoothSession directly, use getSession() to ensure that 'mCvMutex' + // is not taken, to avoid deadlocks with callbacks. + std::unique_ptr mSession; + bool getRecommendedLatencyModes( + std::vector<::aidl::android::hardware::bluetooth::audio::LatencyMode>* latency_modes, + std::optional* supports_low_latency); // Check and initialize session type for |devices| If failed, this // BluetoothAudioPortAidl is not initialized and must be deleted. - bool initSessionType( + bool initSession( const ::aidl::android::media::audio::common::AudioDeviceDescription& description); - bool condWaitState(BluetoothStreamState state); + bool condWaitState(std::unique_lock* lock) REQUIRES(mCvMutex); void controlResultHandler( uint16_t cookie, - const ::aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus& status); - void sessionChangedHandler(uint16_t cookie); + const ::aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus& status) + EXCLUDES(mCvMutex); + void lowLatencyAllowedHandler(uint16_t cookie, bool allowed) EXCLUDES(mCvMutex); + void sessionChangedHandler(uint16_t cookie) EXCLUDES(mCvMutex); }; class BluetoothAudioPortAidlOut : public BluetoothAudioPortAidl { public: + BluetoothAudioPortAidlOut() : BluetoothAudioPortAidl(std::nullopt /*supportsLowLatency*/) {} bool loadAudioConfig( ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& audio_cfg) override; // The audio data path to the Bluetooth stack (Software encoding) size_t writeData(const void* buffer, size_t bytes) const override; + + bool setLatencyMode( + ::aidl::android::hardware::bluetooth::audio::LatencyMode latency_mode) override; }; class BluetoothAudioPortAidlIn : public BluetoothAudioPortAidl { public: + BluetoothAudioPortAidlIn() : BluetoothAudioPortAidl(false /*supportsLowLatency*/) {} // The audio data path from the Bluetooth stack (Software decoded) size_t readData(void* buffer, size_t bytes) const override; }; diff --git a/audio/include/core-impl/Module.h b/audio/include/core-impl/Module.h index 379264d..485cba5 100644 --- a/audio/include/core-impl/Module.h +++ b/audio/include/core-impl/Module.h @@ -142,6 +142,9 @@ class Module : public BnModule { ndk::ScopedAStatus supportsVariableLatency(bool* _aidl_return) override; ndk::ScopedAStatus getAAudioMixerBurstCount(int32_t* _aidl_return) override; ndk::ScopedAStatus getAAudioHardwareBurstMinUsec(int32_t* _aidl_return) override; + ndk::ScopedAStatus getFlushFromFrameSupport( + const ::aidl::android::media::audio::common::AudioPortConfig& in_config, + ::aidl::android::media::audio::common::FlushFromFrameSupport* _aidl_return) override; // The maximum stream buffer size is 1 GiB = 2 ** 30 bytes; static constexpr int32_t kMaximumStreamBufferSizeBytes = 1 << 30; @@ -209,8 +212,9 @@ class Module : public BnModule { virtual int32_t getNominalLatencyMs( const ::aidl::android::media::audio::common::AudioPortConfig& portConfig); virtual ndk::ScopedAStatus calculateBufferSizeFrames( - const ::aidl::android::media::audio::common::AudioFormatDescription &format, - int32_t latencyMs, int32_t sampleRateHz, int32_t *bufferSizeFrames); + const ::aidl::android::media::audio::common::AudioFormatDescription& format, + const ::aidl::android::media::audio::common::AudioIoFlags& flags, int32_t latencyMs, + int32_t sampleRateHz, int32_t* bufferSizeFrames); virtual ndk::ScopedAStatus createMmapBuffer( const ::aidl::android::media::audio::common::AudioPortConfig& portConfig, int32_t bufferSizeFrames, int32_t frameSizeBytes, MmapBufferDescriptor* desc); @@ -274,6 +278,7 @@ class Module : public BnModule { bool setAudioPortConfigGain( const ::aidl::android::media::audio::common::AudioPort& port, const ::aidl::android::media::audio::common::AudioGainConfig& gainRequested); + ndk::ScopedAStatus validateMetadataAttributeTags(const std::vector& tags); }; std::ostream& operator<<(std::ostream& os, Module::Type t); diff --git a/audio/include/core-impl/ModuleAlsa.h b/audio/include/core-impl/ModuleAlsa.h index 3392b41..e0dae7a 100644 --- a/audio/include/core-impl/ModuleAlsa.h +++ b/audio/include/core-impl/ModuleAlsa.h @@ -35,6 +35,9 @@ class ModuleAlsa : public Module { ndk::ScopedAStatus populateConnectedDevicePort( ::aidl::android::media::audio::common::AudioPort* audioPort, int32_t nextPortId) override; + + virtual bool isDevicePortSupportAmbisonics( + const ::aidl::android::media::audio::common::AudioPort& audioPort); }; } // namespace aidl::android::hardware::audio::core diff --git a/audio/include/core-impl/ModuleBluetooth.h b/audio/include/core-impl/ModuleBluetooth.h index 4e68d72..b4a6908 100644 --- a/audio/include/core-impl/ModuleBluetooth.h +++ b/audio/include/core-impl/ModuleBluetooth.h @@ -16,81 +16,17 @@ #pragma once -#include - -#include "core-impl/Bluetooth.h" -#include "core-impl/DevicePortProxy.h" -#include "core-impl/Module.h" +#include "core-impl/ModuleBluetoothBase.h" namespace aidl::android::hardware::audio::core { -class ModuleBluetooth final : public Module { +class ModuleBluetooth final : public ModuleBluetoothBase { public: - enum BtInterface : int { BTSCO, BTA2DP, BTLE }; - typedef std::tuple, std::weak_ptr, - std::weak_ptr> - BtProfileHandles; - ModuleBluetooth(std::unique_ptr&& config); - private: - struct CachedProxy { - std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl> ptr; - ::aidl::android::hardware::bluetooth::audio::PcmConfiguration pcmConfig; - }; - - ChildInterface& getBtA2dp(); - ChildInterface& getBtLe(); - BtProfileHandles getBtProfileManagerHandles(); - - ndk::ScopedAStatus getBluetoothA2dp(std::shared_ptr* _aidl_return) override; - ndk::ScopedAStatus getBluetoothLe(std::shared_ptr* _aidl_return) override; - ndk::ScopedAStatus getMicMute(bool* _aidl_return) override; - ndk::ScopedAStatus setMicMute(bool in_mute) override; - - ndk::ScopedAStatus setAudioPortConfig( - const ::aidl::android::media::audio::common::AudioPortConfig& in_requested, - ::aidl::android::media::audio::common::AudioPortConfig* out_suggested, - bool* _aidl_return) override; - - ndk::ScopedAStatus checkAudioPatchEndpointsMatch( - const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sources, - const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sinks) - override; - void onExternalDeviceConnectionChanged( - const ::aidl::android::media::audio::common::AudioPort& audioPort, bool connected); - 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* 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* result) override; - ndk::ScopedAStatus populateConnectedDevicePort( - ::aidl::android::media::audio::common::AudioPort* audioPort, - int32_t nextPortId) override; - ndk::ScopedAStatus onMasterMuteChanged(bool mute) override; - ndk::ScopedAStatus onMasterVolumeChanged(float volume) override; - int32_t getNominalLatencyMs( - const ::aidl::android::media::audio::common::AudioPortConfig& portConfig) override; - - ndk::ScopedAStatus createProxy( - const ::aidl::android::media::audio::common::AudioPort& audioPort, - int32_t instancePortId, CachedProxy& proxy); - ndk::ScopedAStatus fetchAndCheckProxy(const StreamContext& context, CachedProxy& proxy); - ndk::ScopedAStatus findOrCreateProxy( - const ::aidl::android::media::audio::common::AudioPort& audioPort, CachedProxy& proxy); - - static constexpr int kCreateProxyRetries = 5; - static constexpr int kCreateProxyRetrySleepMs = 75; - ChildInterface mBluetoothA2dp; - ChildInterface mBluetoothLe; - std::map mProxies; - std::map mConnections; + protected: + std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort> createProxyInstance( + bool isInput) override; }; } // namespace aidl::android::hardware::audio::core diff --git a/audio/include/core-impl/ModuleBluetoothBase.h b/audio/include/core-impl/ModuleBluetoothBase.h new file mode 100644 index 0000000..186063c --- /dev/null +++ b/audio/include/core-impl/ModuleBluetoothBase.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "core-impl/Bluetooth.h" +#include "core-impl/BluetoothAudioPort.h" +#include "core-impl/Module.h" + +namespace aidl::android::hardware::audio::core { + +class ModuleBluetoothBase : public Module { + public: + enum BtInterface : int { BTSCO, BTA2DP, BTLE }; + typedef std::tuple, std::weak_ptr, + std::weak_ptr> + BtProfileHandles; + + ModuleBluetoothBase(std::unique_ptr&& config); + + protected: + ndk::ScopedAStatus getBluetoothA2dp(std::shared_ptr* _aidl_return) override; + ndk::ScopedAStatus getBluetoothLe(std::shared_ptr* _aidl_return) override; + ndk::ScopedAStatus getBluetooth(std::shared_ptr* _aidl_return) override; + ndk::ScopedAStatus getMicMute(bool* _aidl_return) override; + ndk::ScopedAStatus setMicMute(bool in_mute) override; + + ndk::ScopedAStatus setAudioPortConfig( + const ::aidl::android::media::audio::common::AudioPortConfig& in_requested, + ::aidl::android::media::audio::common::AudioPortConfig* out_suggested, + bool* _aidl_return) override; + + ndk::ScopedAStatus supportsVariableLatency(bool* _aidl_return) override; + + ndk::ScopedAStatus checkAudioPatchEndpointsMatch( + const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sources, + const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sinks) + override; + void onExternalDeviceConnectionChanged( + const ::aidl::android::media::audio::common::AudioPort& audioPort, bool connected); + 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* 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* result) override; + ndk::ScopedAStatus populateConnectedDevicePort( + ::aidl::android::media::audio::common::AudioPort* audioPort, + int32_t nextPortId) override; + ndk::ScopedAStatus onMasterMuteChanged(bool mute) override; + ndk::ScopedAStatus onMasterVolumeChanged(float volume) override; + int32_t getNominalLatencyMs( + const ::aidl::android::media::audio::common::AudioPortConfig& portConfig) override; + + binder_status_t dump(int fd, const char** args, uint32_t numArgs) override; + + // Must be implemented by derived classes. + virtual std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort> + createProxyInstance(bool isInput) = 0; + + private: + struct CachedProxy { + std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort> ptr; + ::aidl::android::hardware::bluetooth::audio::PcmConfiguration pcmConfig; + }; + + ndk::ScopedAStatus createProxy( + const ::aidl::android::media::audio::common::AudioPort& audioPort, + int32_t instancePortId, CachedProxy& proxy); + ndk::ScopedAStatus fetchAndCheckProxy(const StreamContext& context, CachedProxy& proxy); + ndk::ScopedAStatus findOrCreateProxy( + const ::aidl::android::media::audio::common::AudioPort& audioPort, CachedProxy& proxy); + ChildInterface& getBtA2dp(); + ChildInterface& getBtLe(); + ChildInterface& getBt(); + BtProfileHandles getBtProfileManagerHandles(); + + static constexpr int kCreateProxyRetries = 5; + static constexpr int kCreateProxyRetrySleepMs = 75; + ChildInterface mBluetoothA2dp; + ChildInterface mBluetoothLe; + ChildInterface mBluetooth; + std::map mProxies; + std::map mConnections; + std::map<::aidl::android::media::audio::common::AudioDeviceDescription, + std::weak_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort>> + mProxiesWeak; +}; + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/include/core-impl/ModulePrimary.h b/audio/include/core-impl/ModulePrimary.h index c93deed..0d2bf67 100644 --- a/audio/include/core-impl/ModulePrimary.h +++ b/audio/include/core-impl/ModulePrimary.h @@ -25,12 +25,17 @@ class ModulePrimary final : public Module { ModulePrimary(std::unique_ptr&& config) : Module(Type::DEFAULT, std::move(config)) {} + ndk::ScopedAStatus getFlushFromFrameSupport( + const ::aidl::android::media::audio::common::AudioPortConfig& in_config, + ::aidl::android::media::audio::common::FlushFromFrameSupport* _aidl_return) override; + protected: ndk::ScopedAStatus getTelephony(std::shared_ptr* _aidl_return) override; ndk::ScopedAStatus calculateBufferSizeFrames( const ::aidl::android::media::audio::common::AudioFormatDescription& format, - int32_t latencyMs, int32_t sampleRateHz, int32_t* bufferSizeFrames) override; + const ::aidl::android::media::audio::common::AudioIoFlags& flags, int32_t latencyMs, + int32_t sampleRateHz, int32_t* bufferSizeFrames) override; ndk::ScopedAStatus createInputStream( StreamContext&& context, const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata, diff --git a/audio/include/core-impl/Stream.h b/audio/include/core-impl/Stream.h index bb790e9..f4aeaa4 100644 --- a/audio/include/core-impl/Stream.h +++ b/audio/include/core-impl/Stream.h @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -158,6 +160,9 @@ class StreamContext { return mFlags.getTag() == ::aidl::android::media::audio::common::AudioIoFlags::input; } bool isMmap() const { return ::aidl::android::hardware::audio::common::hasMmapFlag(mFlags); } + bool isOffload() const { + return ::aidl::android::hardware::audio::common::hasNonblockingOffloadFlag(mFlags); + } bool isValid() const; // 'reset' is called on a Binder thread when closing the stream. Does not use // locking because it only cleans MQ pointers which were also set on the Binder thread. @@ -226,6 +231,12 @@ struct DriverInterface { int32_t* /*latency*/) { return ::android::OK; } + // Implement 'flushFromFrame' for offload stream if it is supported. + virtual ::android::status_t flushFromFrame( + ::aidl::android::media::audio::common::FlushFromFrameAccuracy /*accuracy*/, + int32_t /*position*/, int32_t* /*flushFromPosition*/) { + return ::android::INVALID_OPERATION; + } virtual void shutdown() = 0; // This function is only called once. }; @@ -260,6 +271,9 @@ class StreamWorkerCommonLogic : public ::android::hardware::audio::common::Strea void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const; void populateReplyWrongState(StreamDescriptor::Reply* reply, const StreamDescriptor::Command& command) const; + void populateReplyUnsupportedCommand(StreamDescriptor::Reply* reply, + const StreamDescriptor::Command& command) const; + void switchFromTransientState(StreamDescriptor::State state); void switchToTransientState(StreamDescriptor::State state) { mState = state; mTransientStateStart = std::chrono::steady_clock::now(); @@ -349,6 +363,8 @@ class StreamOutWorkerLogic : public StreamWorkerCommonLogic { void onClipStateChange(size_t clipFramesLeft, bool hasNextClip) override; private: + void onBufferStateChangeImpl(size_t bufferFramesLeft); + void onClipStateChangeImpl(size_t clipFramesLeft, bool hasNextClip); bool write(size_t clientSize, StreamDescriptor::Reply* reply); bool writeMmap(StreamDescriptor::Reply* reply); @@ -356,6 +372,9 @@ class StreamOutWorkerLogic : public StreamWorkerCommonLogic { enum DrainState : int32_t { NONE, ALL, EN /*early notify*/, EN_SENT }; std::atomic mDrainState = DrainState::NONE; + ::android::hardware::audio::common::SynchronousTaskQueue< + ::android::hardware::audio::common::PostponedMethodCall> + mCallTasks; }; using StreamOutWorker = StreamWorkerImpl; @@ -386,6 +405,7 @@ struct StreamCommonInterface { virtual ndk::ScopedAStatus removeEffect( const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_effect) = 0; + virtual ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* _aidl_return) = 0; // Methods below are common for both 'IStreamIn' and 'IStreamOut'. Note that // 'updateMetadata' in them uses an individual structure which is wrapped here. // The 'Common' suffix is added to distinguish them from the methods from 'IStreamIn/Out'. @@ -456,6 +476,11 @@ class StreamCommonDelegator : public BnStreamCommon { return delegate != nullptr ? delegate->removeEffect(in_effect) : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } + ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* _aidl_return) override { + auto delegate = mDelegate.lock(); + return delegate != nullptr ? delegate->createMmapBuffer(_aidl_return) + : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } // It is possible that on the client side the proxy for IStreamCommon will outlive // the IStream* instance, and the server side IStream* instance will get destroyed // while this IStreamCommon instance is still alive. @@ -490,6 +515,7 @@ class StreamCommonImpl : virtual public StreamCommonInterface, virtual public Dr ndk::ScopedAStatus removeEffect( const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_effect) override; + ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* _aidl_return) override; ndk::ScopedAStatus getStreamCommonCommon(std::shared_ptr* _aidl_return) override; ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override; @@ -588,9 +614,7 @@ class StreamOut : virtual public StreamCommonInterface, public BnStreamOut { } ndk::ScopedAStatus updateMetadata( const ::aidl::android::hardware::audio::common::SourceMetadata& in_sourceMetadata) - override { - return updateMetadataCommon(in_sourceMetadata); - } + override; ndk::ScopedAStatus updateOffloadMetadata( const ::aidl::android::hardware::audio::common::AudioOffloadMetadata& in_offloadMetadata) override; @@ -619,6 +643,8 @@ class StreamOut : virtual public StreamCommonInterface, public BnStreamOut { StreamOut(StreamContext&& context, const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>& offloadInfo); + ndk::ScopedAStatus validateMetadata( + const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata); StreamContext mContextInstance; const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo> mOffloadInfo; diff --git a/audio/include/core-impl/StreamAlsa.h b/audio/include/core-impl/StreamAlsa.h index c0dcb63..eb9695e 100644 --- a/audio/include/core-impl/StreamAlsa.h +++ b/audio/include/core-impl/StreamAlsa.h @@ -16,70 +16,24 @@ #pragma once -#include -#include -#include -#include - -#include -#include - -#include "Stream.h" -#include "alsa/Utils.h" +#include "StreamAlsaBase.h" namespace aidl::android::hardware::audio::core { -// This class is intended to be used as a base class for implementations -// that use TinyAlsa. -// This class does not define a complete stream implementation, -// and should never be used on its own. Derived classes are expected to -// provide necessary overrides for all interface methods omitted here. -class StreamAlsa : public StreamCommonImpl { +// Direct ALSA I/O path (no MonoPipe, no background I/O thread). +// Reads/writes directly to/from ALSA device proxies in the transfer() call. +// Used by StreamUsb. +class StreamAlsa : public StreamAlsaBase { public: StreamAlsa(StreamContext* context, const Metadata& metadata, int readWriteRetries); ~StreamAlsa(); // Methods of 'DriverInterface'. - ::android::status_t init(DriverCallbackInterface* callback) 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; - ::android::status_t refinePosition(StreamDescriptor::Position* position) override; void shutdown() override; - ndk::ScopedAStatus setGain(float gain) override; - - protected: - // Called from 'start' to initialize 'mAlsaDeviceProxies', the vector must be non-empty. - virtual std::vector getDeviceProfiles() = 0; - - const size_t mBufferSizeFrames; - const size_t mFrameSizeBytes; - const int mSampleRate; - const bool mIsInput; - const std::optional mConfig; - const int mReadWriteRetries; - - private: - ::android::NBAIO_Format getPipeFormat() const; - ::android::sp<::android::MonoPipe> makeSink(bool writeCanBlock); - ::android::sp<::android::MonoPipeReader> makeSource(::android::MonoPipe* pipe); - void inputIoThread(size_t idx); - void outputIoThread(size_t idx); - void teardownIo(); - - std::atomic mGain = 1.0; - - // All fields below are only used on the worker thread. - std::vector mAlsaDeviceProxies; - // Only 'libnbaio_mono' is vendor-accessible, thus no access to the multi-reader Pipe. - std::vector<::android::sp<::android::MonoPipe>> mSinks; - std::vector<::android::sp<::android::MonoPipeReader>> mSources; - std::vector mIoThreads; - std::atomic mIoThreadIsRunning = false; // used by all threads }; -} // namespace aidl::android::hardware::audio::core +} // namespace aidl::android::hardware::audio::core \ No newline at end of file diff --git a/audio/include/core-impl/StreamAlsaBase.h b/audio/include/core-impl/StreamAlsaBase.h new file mode 100644 index 0000000..8d6a85b --- /dev/null +++ b/audio/include/core-impl/StreamAlsaBase.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2026 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 "Stream.h" +#include "alsa/UtilsAlsa.h" + +namespace aidl::android::hardware::audio::core { + +// Base class for implementations that use TinyAlsa. +// Contains code common to both the direct ALSA path (StreamAlsa) and the +// MonoPipe + I/O thread path (StreamAlsaMonoPipe). +// This class does not define a complete stream implementation and should never +// be used on its own. Derived classes must provide implementations for +// standby(), start(), transfer(), and shutdown(). +class StreamAlsaBase : public StreamCommonImpl { + public: + StreamAlsaBase(StreamContext* context, const Metadata& metadata, int readWriteRetries); + ~StreamAlsaBase(); + + // Methods of 'DriverInterface'. + ::android::status_t init(DriverCallbackInterface* callback) override; + ::android::status_t drain(StreamDescriptor::DrainMode) override; + ::android::status_t flush() override; + ::android::status_t pause() override; + ::android::status_t standby() override = 0; + ::android::status_t start() override = 0; + ::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount, + int32_t* latencyMs) override = 0; + ::android::status_t refinePosition(StreamDescriptor::Position* position) override; + void shutdown() override = 0; + ndk::ScopedAStatus setGain(float gain) override; + + protected: + // Called from 'start' to initialize 'mAlsaDeviceProxies', the vector must be non-empty. + virtual std::vector getDeviceProfiles() = 0; + + // Helper used by subclass start() implementations to open ALSA device proxies. + // Returns ::android::NO_INIT if no proxies could be opened, ::android::OK otherwise. + ::android::status_t openDeviceProxies(std::vector* proxies); + + const size_t mBufferSizeFrames; + const size_t mFrameSizeBytes; + const int mSampleRate; + const bool mIsInput; + const std::optional mConfig; + const int mReadWriteRetries; + + std::atomic mGain = 1.0; + + // Opened ALSA device proxies. Populated by start(), cleared by standby()/shutdown(). + std::vector mAlsaDeviceProxies; +}; + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/include/core-impl/StreamAlsaMonoPipe.h b/audio/include/core-impl/StreamAlsaMonoPipe.h new file mode 100644 index 0000000..98f4d3b --- /dev/null +++ b/audio/include/core-impl/StreamAlsaMonoPipe.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2026 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 "StreamAlsaBase.h" + +namespace aidl::android::hardware::audio::core { + +// Extends StreamAlsaBase with a MonoPipe + dedicated I/O thread per device. +// The worker thread communicates with ALSA via a pipe, allowing the main +// transfer() call to be decoupled from the ALSA read/write latency. +// Used by StreamPrimary (and any other implementation that needs the pipe path). +class StreamAlsaMonoPipe : public StreamAlsaBase { + public: + StreamAlsaMonoPipe(StreamContext* context, const Metadata& metadata, int readWriteRetries); + ~StreamAlsaMonoPipe(); + + // Methods of 'DriverInterface'. + ::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; + + private: + ::android::NBAIO_Format getPipeFormat() const; + ::android::sp<::android::MonoPipe> makeSink(bool writeCanBlock); + ::android::sp<::android::MonoPipeReader> makeSource(::android::MonoPipe* pipe); + void inputIoThread(size_t idx); + void outputIoThread(size_t idx); + void teardownIo(); + + // All fields below are only used on the worker thread. + // Only 'libnbaio_mono' is vendor-accessible, thus no access to the multi-reader Pipe. + std::vector<::android::sp<::android::MonoPipe>> mSinks; + std::vector<::android::sp<::android::MonoPipeReader>> mSources; + std::vector mIoThreads; + std::atomic mIoThreadIsRunning = false; // used by all threads +}; + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/include/core-impl/StreamBluetooth.h b/audio/include/core-impl/StreamBluetooth.h index 2bdd6b2..48f6f0a 100644 --- a/audio/include/core-impl/StreamBluetooth.h +++ b/audio/include/core-impl/StreamBluetooth.h @@ -29,18 +29,30 @@ namespace aidl::android::hardware::audio::core { +class PortCallbacksHandler : public ::android::bluetooth::audio::aidl::BluetoothAudioPortCallbacks { + public: + explicit PortCallbacksHandler(std::shared_ptr streamCallback) + : mStreamCallback(streamCallback) {} + bool hasCallback() const { return mStreamCallback != nullptr; } + void onRecommendedLatencyModeChanged( + const std::vector<::aidl::android::hardware::bluetooth::audio::LatencyMode>& modes) + override; + + private: + std::shared_ptr mStreamCallback; +}; + class StreamBluetooth : public StreamCommonImpl { public: static bool checkConfigParams( const ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& pcmConfig, const ::aidl::android::media::audio::common::AudioConfigBase& config); - StreamBluetooth( - StreamContext* context, const Metadata& metadata, - ModuleBluetooth::BtProfileHandles&& btHandles, - const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl>& - btDeviceProxy, - const ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& pcmConfig); + StreamBluetooth(StreamContext* context, const Metadata& metadata, + ModuleBluetooth::BtProfileHandles&& btHandles, + const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort>& + btDeviceProxy, + const ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& pcmConfig); ~StreamBluetooth(); // Methods of 'DriverInterface'. @@ -55,20 +67,37 @@ class StreamBluetooth : public StreamCommonImpl { void shutdown() override; // Overridden methods of 'StreamCommonImpl', called on a Binder thread. - ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override; - ndk::ScopedAStatus prepareToClose() override; ndk::ScopedAStatus bluetoothParametersUpdated() override; + ndk::ScopedAStatus prepareToClose() override; + ndk::ScopedAStatus setConnectedDevices( + const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) + override; + ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override; + + protected: + ndk::ScopedAStatus getRecommendedLatencyModes( + std::vector<::aidl::android::media::audio::common::AudioLatencyMode>* _aidl_return); + ndk::ScopedAStatus setLatencyMode( + ::aidl::android::media::audio::common::AudioLatencyMode in_mode); + + void dump(int fd, const char** args, uint32_t numArgs); private: const size_t mFrameSizeBytes; const bool mIsInput; const std::weak_ptr mBluetoothA2dp; const std::weak_ptr mBluetoothLe; + const std::weak_ptr mBluetoothHfp; const size_t mPreferredDataIntervalUs; + std::shared_ptr mCallbacksHandler; + std::string mSessionTypeName; mutable std::mutex mLock; // The lock is also used to serialize calls to the proxy. - std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl> mBtDeviceProxy + std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort> mBtDeviceProxy GUARDED_BY(mLock); // proxy may be null if the stream is not connected to a device + bool mEnabled GUARDED_BY(mLock) = true; + int64_t mDecimPresentationPosition; + ::aidl::android::hardware::bluetooth::audio::PresentationPosition mActualPresentationPosition; }; class StreamInBluetooth final : public StreamIn, public StreamBluetooth { @@ -82,7 +111,7 @@ class StreamInBluetooth final : public StreamIn, public StreamBluetooth { const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata, const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones, ModuleBluetooth::BtProfileHandles&& btHandles, - const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl>& + const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort>& btDeviceProxy, const ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& pcmConfig); @@ -91,6 +120,8 @@ class StreamInBluetooth final : public StreamIn, public StreamBluetooth { ndk::ScopedAStatus getActiveMicrophones( std::vector<::aidl::android::media::audio::common::MicrophoneDynamicInfo>* _aidl_return) override; + + binder_status_t dump(int fd, const char** args, uint32_t numArgs) override; }; class StreamOutBluetooth final : public StreamOut, public StreamBluetooth { @@ -105,12 +136,20 @@ class StreamOutBluetooth final : public StreamOut, public StreamBluetooth { const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>& offloadInfo, ModuleBluetooth::BtProfileHandles&& btHandles, - const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl>& + const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort>& btDeviceProxy, const ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& pcmConfig); private: void onClose(StreamDescriptor::State) override { defaultOnClose(); } + + ndk::ScopedAStatus getRecommendedLatencyModes( + std::vector<::aidl::android::media::audio::common::AudioLatencyMode>* _aidl_return) + override; + ndk::ScopedAStatus setLatencyMode( + ::aidl::android::media::audio::common::AudioLatencyMode in_mode) override; + + binder_status_t dump(int fd, const char** args, uint32_t numArgs) override; }; } // namespace aidl::android::hardware::audio::core diff --git a/audio/include/core-impl/StreamMmapStub.h b/audio/include/core-impl/StreamMmapStub.h index 0332007..5178282 100644 --- a/audio/include/core-impl/StreamMmapStub.h +++ b/audio/include/core-impl/StreamMmapStub.h @@ -65,7 +65,9 @@ class DriverMmapStubImpl : public DriverStubImpl { explicit DriverMmapStubImpl(const StreamContext& context); ::android::status_t init(DriverCallbackInterface* callback) override; ::android::status_t drain(StreamDescriptor::DrainMode 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; @@ -97,10 +99,9 @@ class StreamMmapStub : public StreamCommonImpl, public DriverMmapStubImpl { std::vector* _aidl_return) override; ndk::ScopedAStatus setVendorParameters(const std::vector& in_parameters, bool in_async) override; + ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* _aidl_return) override; private: - ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* desc); - ndk::ScopedFileDescriptor mSharedMemoryFd; }; diff --git a/audio/include/core-impl/StreamOffloadStub.h b/audio/include/core-impl/StreamOffloadStub.h index 09b88aa..22e72b5 100644 --- a/audio/include/core-impl/StreamOffloadStub.h +++ b/audio/include/core-impl/StreamOffloadStub.h @@ -28,17 +28,72 @@ namespace aidl::android::hardware::audio::core { namespace offload { +class DspClipState { + public: + static constexpr int64_t kError = -1; + static constexpr size_t kClipCountLimit = 2; + + bool add(int64_t frames) { + if (mFrames.size() < kClipCountLimit) { + mFrames.push_back(frames); + return true; + } + return false; + } + int64_t currentFrames() const { return !empty() ? mFrames[0] : kError; } + bool empty() const { return mFrames.empty(); } + bool hasNext() const { return mFrames.size() > 1; } + void erase() { mFrames.clear(); } + void eraseAllNext() { + if (!empty()) mFrames.resize(1); + } + int64_t removeCurrent() { + if (empty()) return kError; + const int64_t result = mFrames[0]; + mFrames.erase(mFrames.begin()); + return result; + } + int64_t trimCurrentFrames(int64_t frames) { + if (!empty()) { + if (mFrames[0] > frames) mFrames[0] = frames; + return mFrames[0]; + } + return kError; + } + int64_t updateCurrentFrames(int64_t delta) { + return !empty() ? updateFrames(0, delta) : kError; + } + int64_t updateLastFrames(int64_t delta) { + return !empty() ? updateFrames(mFrames.size() - 1, delta) : kError; + } + + std::string log() const { return ::android::internal::ToString(mFrames); } + + private: + int64_t updateFrames(size_t index, int64_t delta) { + return delta >= 0 || mFrames[index] >= -delta ? mFrames[index] += delta : kError; + } + std::vector mFrames; +}; + struct DspSimulatorState { static constexpr int64_t kSkipBufferNotifyFrames = -1; - const std::string formatEncoding; + const ::aidl::android::media::audio::common::AudioFormatDescription format; const int sampleRate; const int64_t earlyNotifyFrames; + const int fallbackBitRatePerSecond; // Fallback from AudioOffloadInfo DriverCallbackInterface* callback = nullptr; // set before starting DSP worker std::mutex lock; - std::vector clipFramesLeft GUARDED_BY(lock); + DspClipState clips GUARDED_BY(lock); + int bitRatePerSecond GUARDED_BY(lock) = 0; int64_t bufferFramesLeft GUARDED_BY(lock) = 0; int64_t bufferNotifyFrames GUARDED_BY(lock) = kSkipBufferNotifyFrames; + StreamDescriptor::DrainMode draining GUARDED_BY(lock) = + StreamDescriptor::DrainMode::DRAIN_UNSPECIFIED; + int64_t mTotalFramesPlayed GUARDED_BY(lock) = 0; + int64_t mLastReportedFrames GUARDED_BY(lock) = 0; + bool isDrainCompleteClipStateChangeSent GUARDED_BY(lock) = false; }; class DspSimulatorLogic : public ::android::hardware::audio::common::StreamLogic { @@ -60,9 +115,23 @@ class DspSimulatorWorker } // namespace offload +struct MpegFrameState { + bool clipEnded = false; + size_t bytesPending = 0; + + // True if the frame contains actual audio samples. + // False if it is a metadata/ID3 tag. + bool isAudioFrame = false; + int frameSize = 0; + size_t totalFrameLengthBytes = 0; +}; + class DriverOffloadStubImpl : public DriverStubImpl { public: - explicit DriverOffloadStubImpl(const StreamContext& context); + explicit DriverOffloadStubImpl( + const StreamContext& context, + const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>& + offloadInfo); ::android::status_t init(DriverCallbackInterface* callback) override; ::android::status_t drain(StreamDescriptor::DrainMode drainMode) override; ::android::status_t flush() override; @@ -70,22 +139,38 @@ class DriverOffloadStubImpl : public DriverStubImpl { ::android::status_t start() override; ::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount, int32_t* latencyMs) override; + ::android::status_t flushFromFrame( + ::aidl::android::media::audio::common::FlushFromFrameAccuracy accuracy, + int32_t position, int32_t* flushFromPosition) override; void shutdown() override; + ::android::status_t refinePosition(StreamDescriptor::Position* position) override; + private: ::android::status_t startWorkerIfNeeded(); + ::android::status_t handleApeTransfer(void* buffer, size_t frameCount, + size_t* actualFrameCount); + ::android::status_t handleMpegTransfer(void* buffer, size_t frameCount, + size_t* actualFrameCount); + ::android::status_t handlePcmTransfer(void* buffer, size_t frameCount, + size_t* actualFrameCount); + ::android::status_t (DriverOffloadStubImpl::*mTransferHandler)(void*, size_t, size_t*); const int64_t mBufferNotifyFrames; + const int32_t mSafeMarginForFlushFromFrames; + const int32_t mSafeMarginForDrainMetadataFrames; offload::DspSimulatorState mState; offload::DspSimulatorWorker mDspWorker; bool mDspWorkerStarted = false; + MpegFrameState mMpegFrameState; }; class StreamOffloadStub : public StreamCommonImpl, public DriverOffloadStubImpl { public: static const std::set& getSupportedEncodings(); - StreamOffloadStub(StreamContext* context, const Metadata& metadata); + StreamOffloadStub(StreamContext* context, const Metadata& metadata, + const ::aidl::android::media::audio::common::AudioOffloadInfo& offloadInfo); ~StreamOffloadStub(); }; diff --git a/audio/include/core-impl/StreamPrimary.h b/audio/include/core-impl/StreamPrimary.h index a566d8c..aed7e25 100644 --- a/audio/include/core-impl/StreamPrimary.h +++ b/audio/include/core-impl/StreamPrimary.h @@ -23,12 +23,12 @@ #include #include "DriverStubImpl.h" -#include "StreamAlsa.h" +#include "StreamAlsaMonoPipe.h" #include "primary/PrimaryMixer.h" namespace aidl::android::hardware::audio::core { -class StreamPrimary : public StreamAlsa { +class StreamPrimary : public StreamAlsaMonoPipe { public: StreamPrimary(StreamContext* context, const Metadata& metadata); diff --git a/audio/include/core-impl/StreamRemoteSubmix.h b/audio/include/core-impl/StreamRemoteSubmix.h index 28a446a..dc91aad 100644 --- a/audio/include/core-impl/StreamRemoteSubmix.h +++ b/audio/include/core-impl/StreamRemoteSubmix.h @@ -16,19 +16,18 @@ #pragma once +#include +#include #include #include "core-impl/Stream.h" -#include "deprecated/StreamSwitcher.h" #include "r_submix/SubmixRoute.h" namespace aidl::android::hardware::audio::core { class StreamRemoteSubmix : public StreamCommonImpl { public: - StreamRemoteSubmix( - StreamContext* context, const Metadata& metadata, - const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress); + StreamRemoteSubmix(StreamContext* context, const Metadata& metadata); ~StreamRemoteSubmix(); // Methods of 'DriverInterface'. @@ -45,35 +44,39 @@ class StreamRemoteSubmix : public StreamCommonImpl { // Overridden methods of 'StreamCommonImpl', called on a Binder thread. ndk::ScopedAStatus prepareToClose() override; + ndk::ScopedAStatus setConnectedDevices(const ConnectedDevices& devices) override; private: - long getDelayInUsForFrameCount(size_t frameCount); - size_t getStreamPipeSizeInFrames(); - ::android::status_t outWrite(void* buffer, size_t frameCount, size_t* actualFrameCount); - ::android::status_t inRead(void* buffer, size_t frameCount, size_t* actualFrameCount); + long getDurationInUsForFrameCount(size_t frameCount) const; + std::shared_ptr prepareCurrentRoute( + const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress); + + size_t getStreamPipeSizeInFrames(const std::shared_ptr& currentRoute); + ::android::status_t outWrite(const std::shared_ptr& currentRoute, + void* buffer, size_t frameCount, size_t* actualFrameCount); + ::android::status_t inRead(const std::shared_ptr& currentRoute, + void* buffer, size_t frameCount, size_t* actualFrameCount); - const ::aidl::android::media::audio::common::AudioDeviceAddress mDeviceAddress; const bool mIsInput; - r_submix::AudioConfig mStreamConfig; - std::shared_ptr mCurrentRoute = nullptr; + const r_submix::AudioConfig mStreamConfig; + const int mReadAttemptSleepUs; // Limit for the number of error log entries to avoid spamming the logs. static constexpr int kMaxErrorLogs = 5; - // The duration of kMaxReadFailureAttempts * READ_ATTEMPT_SLEEP_MS must be strictly inferior - // to the duration of a record buffer at the current record sample rate (of the device, not of - // the recording itself). Here we have: 3 * 5ms = 15ms < 1024 frames * 1000 / 48000 = 21.333ms - static constexpr int kMaxReadFailureAttempts = 3; - // 5ms between two read attempts when pipe is empty - static constexpr int kReadAttemptSleepUs = 5000; + mutable std::mutex mLock; + std::shared_ptr mCurrentRoute GUARDED_BY(mLock); + + // Used by the worker thread only. int64_t mStartTimeNs = 0; long mFramesSinceStart = 0; int mReadErrorCount = 0; int mReadFailureCount = 0; int mWriteShutdownCount = 0; + bool mSkipNextTransfer = false; }; -class StreamInRemoteSubmix final : public StreamIn, public deprecated::StreamSwitcher { +class StreamInRemoteSubmix final : public StreamIn, public StreamRemoteSubmix { public: friend class ndk::SharedRefBase; StreamInRemoteSubmix( @@ -82,19 +85,13 @@ class StreamInRemoteSubmix final : public StreamIn, public deprecated::StreamSwi const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones); private: - DeviceSwitchBehavior switchCurrentStream( - const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) - override; - std::unique_ptr createNewStream( - const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices, - StreamContext* context, const Metadata& metadata) override; void onClose(StreamDescriptor::State) override { defaultOnClose(); } ndk::ScopedAStatus getActiveMicrophones( std::vector<::aidl::android::media::audio::common::MicrophoneDynamicInfo>* _aidl_return) override; }; -class StreamOutRemoteSubmix final : public StreamOut, public deprecated::StreamSwitcher { +class StreamOutRemoteSubmix final : public StreamOut, public StreamRemoteSubmix { public: friend class ndk::SharedRefBase; StreamOutRemoteSubmix( @@ -104,12 +101,6 @@ class StreamOutRemoteSubmix final : public StreamOut, public deprecated::StreamS offloadInfo); private: - DeviceSwitchBehavior switchCurrentStream( - const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) - override; - std::unique_ptr createNewStream( - const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices, - StreamContext* context, const Metadata& metadata) override; void onClose(StreamDescriptor::State) override { defaultOnClose(); } }; diff --git a/audio/include/core-impl/StreamStub.h b/audio/include/core-impl/StreamStub.h index cee44db..3a8877f 100644 --- a/audio/include/core-impl/StreamStub.h +++ b/audio/include/core-impl/StreamStub.h @@ -39,7 +39,7 @@ class StreamInStub final : public StreamIn, public StreamStub { void onClose(StreamDescriptor::State) override { defaultOnClose(); } }; -class StreamOutStub final : public StreamOut, public StreamStub { +class StreamOutStub : public StreamOut, public StreamStub { public: friend class ndk::SharedRefBase; StreamOutStub(StreamContext&& context, @@ -51,4 +51,16 @@ class StreamOutStub final : public StreamOut, public StreamStub { void onClose(StreamDescriptor::State) override { defaultOnClose(); } }; +class StreamOutTelephonyStub final : public StreamOutStub, public StreamOutHwVolumeHelper { + public: + StreamOutTelephonyStub( + StreamContext&& context, + const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata, + const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>& + offloadInfo); + + ndk::ScopedAStatus getHwVolume(std::vector* _aidl_return) override; + ndk::ScopedAStatus setHwVolume(const std::vector& in_channelVolumes) override; +}; + } // namespace aidl::android::hardware::audio::core diff --git a/audio/include/effect-impl/EffectImpl.h b/audio/include/effect-impl/EffectImpl.h index d3bb7f4..bfe5efe 100644 --- a/audio/include/effect-impl/EffectImpl.h +++ b/audio/include/effect-impl/EffectImpl.h @@ -88,6 +88,11 @@ class EffectImpl : public BnEffect, public EffectThread { */ void process() override; + /** + * Finish effect draining, and transit the state to State::IDLE. + */ + virtual void drainingComplete_l() EXCLUSIVE_LOCKS_REQUIRED(mImplMutex); + protected: // current Hal version int mVersion = 0; @@ -114,7 +119,7 @@ class EffectImpl : public BnEffect, public EffectThread { RetCode notifyEventFlag(uint32_t flag); std::string getEffectNameWithVersion() { - return getEffectName() + "V" + std::to_string(mVersion); + return getEffectName() + " (aidl_v" + std::to_string(mVersion) + ")"; } ::android::hardware::EventFlag* mEventFlag; diff --git a/audio/libeffects/bundle/Android.bp b/audio/libeffects/bundle/Android.bp index d8b0a55..4871a08 100644 --- a/audio/libeffects/bundle/Android.bp +++ b/audio/libeffects/bundle/Android.bp @@ -8,19 +8,17 @@ cc_library_shared { relative_install_path: "soundfx", vendor: true, srcs: [ - ":effectCommonFileRpi", "aidl/BundleContext.cpp", "aidl/EffectBundleAidl.cpp", ], local_include_dirs: ["aidl"], static_libs: [ + "libaudioeffectaidlcommon-rpi", "libmusicbundle", ], shared_libs: [ - "android.hardware.audio.effect-V3-ndk", "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", - "android.media.audio.common.types-V4-ndk", "libaudio_aidl_conversion_common_ndk", "libaudioaidlcommon", "libaudioutils", @@ -46,4 +44,8 @@ cc_library_shared { "-Wextra", "-Wthread-safety", ], + defaults: [ + "latest_android_hardware_audio_effect_ndk_shared", + "latest_android_media_audio_common_types_ndk_shared", + ], } diff --git a/audio/libeffects/downmix/Android.bp b/audio/libeffects/downmix/Android.bp index b1890bd..6ddaba5 100644 --- a/audio/libeffects/downmix/Android.bp +++ b/audio/libeffects/downmix/Android.bp @@ -8,15 +8,15 @@ cc_library_shared { relative_install_path: "soundfx", vendor: true, srcs: [ - ":effectCommonFileRpi", "aidl/DownmixContext.cpp", "aidl/EffectDownmix.cpp", ], + static_libs: [ + "libaudioeffectaidlcommon-rpi", + ], shared_libs: [ - "android.hardware.audio.effect-V3-ndk", "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", - "android.media.audio.common.types-V4-ndk", "libaudio_aidl_conversion_common_ndk", "libaudioaidlcommon", "libaudioutils", @@ -41,4 +41,8 @@ cc_library_shared { "-Wextra", "-Wthread-safety", ], + defaults: [ + "latest_android_hardware_audio_effect_ndk_shared", + "latest_android_media_audio_common_types_ndk_shared", + ], } diff --git a/audio/libeffects/dynamicsproc/Android.bp b/audio/libeffects/dynamicsproc/Android.bp index 5ea5767..89d1e41 100644 --- a/audio/libeffects/dynamicsproc/Android.bp +++ b/audio/libeffects/dynamicsproc/Android.bp @@ -8,7 +8,6 @@ cc_library_shared { relative_install_path: "soundfx", vendor: true, srcs: [ - ":effectCommonFileRpi", "aidl/DynamicsProcessing.cpp", "aidl/DynamicsProcessingContext.cpp", "dsp/DPBase.cpp", @@ -16,12 +15,11 @@ cc_library_shared { ], static_libs: [ "libaudioaidlranges", + "libaudioeffectaidlcommon-rpi", ], shared_libs: [ - "android.hardware.audio.effect-V3-ndk", "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", - "android.media.audio.common.types-V4-ndk", "libaudio_aidl_conversion_common_ndk", "libaudioaidlcommon", "libaudioutils", @@ -46,4 +44,8 @@ cc_library_shared { "-Wextra", "-Wthread-safety", ], + defaults: [ + "latest_android_hardware_audio_effect_ndk_shared", + "latest_android_media_audio_common_types_ndk_shared", + ], } diff --git a/audio/libeffects/dynamicsproc/aidl/DynamicsProcessingContext.cpp b/audio/libeffects/dynamicsproc/aidl/DynamicsProcessingContext.cpp index 81b52aa..00538b4 100644 --- a/audio/libeffects/dynamicsproc/aidl/DynamicsProcessingContext.cpp +++ b/audio/libeffects/dynamicsproc/aidl/DynamicsProcessingContext.cpp @@ -473,9 +473,8 @@ RetCode DynamicsProcessingContext::setDpChannelBand_l(const std::any& anyConfig, case StageType::POSTEQ: { dp_fx::DPEq* dp; const auto& config = std::any_cast(anyConfig); - RETURN_VALUE_IF( - nullptr == (dp = getEqWithType_l(type, config.channel)) || !dp->isEnabled(), - RetCode::ERROR_ILLEGAL_PARAMETER, "dpEqNotExist"); + RETURN_VALUE_IF(nullptr == (dp = getEqWithType_l(type, config.channel)), + RetCode::ERROR_ILLEGAL_PARAMETER, "dpEqNotExist"); dp_fx::DPEqBand band; band.init(config.enable, config.cutoffFrequencyHz, config.gainDb); dp->setBand(config.band, band); @@ -485,7 +484,7 @@ RetCode DynamicsProcessingContext::setDpChannelBand_l(const std::any& anyConfig, case StageType::MBC: { dp_fx::DPMbc* dp; const auto& config = std::any_cast(anyConfig); - RETURN_VALUE_IF(nullptr == (dp = getMbc_l(config.channel)) || !dp->isEnabled(), + RETURN_VALUE_IF(nullptr == (dp = getMbc_l(config.channel)), RetCode::ERROR_ILLEGAL_PARAMETER, "dpMbcNotExist"); dp_fx::DPMbcBand band; band.init(config.enable, config.cutoffFrequencyHz, config.attackTimeMs, diff --git a/audio/libeffects/loudness/Android.bp b/audio/libeffects/loudness/Android.bp index e387129..9878635 100644 --- a/audio/libeffects/loudness/Android.bp +++ b/audio/libeffects/loudness/Android.bp @@ -8,16 +8,16 @@ cc_library_shared { relative_install_path: "soundfx", vendor: true, srcs: [ - ":effectCommonFileRpi", "aidl/EffectLoudnessEnhancer.cpp", "aidl/LoudnessEnhancerContext.cpp", "dsp/core/dynamic_range_compression.cpp", ], + static_libs: [ + "libaudioeffectaidlcommon-rpi", + ], shared_libs: [ - "android.hardware.audio.effect-V3-ndk", "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", - "android.media.audio.common.types-V4-ndk", "libaudio_aidl_conversion_common_ndk", "libaudioaidlcommon", "libaudioutils", @@ -42,4 +42,8 @@ cc_library_shared { "-Wextra", "-Wthread-safety", ], + defaults: [ + "latest_android_hardware_audio_effect_ndk_shared", + "latest_android_media_audio_common_types_ndk_shared", + ], } diff --git a/audio/libeffects/loudness/dsp/core/dynamic_range_compression.cpp b/audio/libeffects/loudness/dsp/core/dynamic_range_compression.cpp index 3d71184..e3f02ba 100644 --- a/audio/libeffects/loudness/dsp/core/dynamic_range_compression.cpp +++ b/audio/libeffects/loudness/dsp/core/dynamic_range_compression.cpp @@ -19,6 +19,8 @@ #include "dsp/core/dynamic_range_compression.h" #include +#include + namespace le_fx { AdaptiveDynamicRangeCompression::AdaptiveDynamicRangeCompression() { diff --git a/audio/libeffects/reverb/Android.bp b/audio/libeffects/reverb/Android.bp index f2817e5..97fefc7 100644 --- a/audio/libeffects/reverb/Android.bp +++ b/audio/libeffects/reverb/Android.bp @@ -8,19 +8,17 @@ cc_library_shared { relative_install_path: "soundfx", vendor: true, srcs: [ - ":effectCommonFileRpi", "aidl/EffectReverb.cpp", "aidl/ReverbContext.cpp", ], local_include_dirs: ["aidl"], static_libs: [ + "libaudioeffectaidlcommon-rpi", "libreverb", ], shared_libs: [ - "android.hardware.audio.effect-V3-ndk", "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", - "android.media.audio.common.types-V4-ndk", "libaudio_aidl_conversion_common_ndk", "libaudioaidlcommon", "libaudioutils", @@ -45,4 +43,8 @@ cc_library_shared { "-Wextra", "-Wthread-safety", ], + defaults: [ + "latest_android_hardware_audio_effect_ndk_shared", + "latest_android_media_audio_common_types_ndk_shared", + ], } diff --git a/audio/libeffects/visualizer/Android.bp b/audio/libeffects/visualizer/Android.bp index 6024db5..eb55da2 100644 --- a/audio/libeffects/visualizer/Android.bp +++ b/audio/libeffects/visualizer/Android.bp @@ -8,15 +8,15 @@ cc_library_shared { relative_install_path: "soundfx", vendor: true, srcs: [ - ":effectCommonFileRpi", "aidl/Visualizer.cpp", "aidl/VisualizerContext.cpp", ], + static_libs: [ + "libaudioeffectaidlcommon-rpi", + ], shared_libs: [ - "android.hardware.audio.effect-V3-ndk", "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", - "android.media.audio.common.types-V4-ndk", "libaudio_aidl_conversion_common_ndk", "libaudioaidlcommon", "libaudioutils", @@ -43,4 +43,8 @@ cc_library_shared { "-Wextra", "-Wthread-safety", ], + defaults: [ + "latest_android_hardware_audio_effect_ndk_shared", + "latest_android_media_audio_common_types_ndk_shared", + ], } diff --git a/audio/main.cpp b/audio/main.cpp index 5af5297..0530940 100644 --- a/audio/main.cpp +++ b/audio/main.cpp @@ -20,7 +20,7 @@ #include #define LOG_TAG "AHAL_Main" -#include +#include #include #include #include diff --git a/audio/primary/StreamPrimary.cpp b/audio/primary/StreamPrimary.cpp index 74b4f0c..164abfb 100644 --- a/audio/primary/StreamPrimary.cpp +++ b/audio/primary/StreamPrimary.cpp @@ -19,8 +19,8 @@ #include +#include #include -#include #include #include #include @@ -44,7 +44,7 @@ using android::base::ReadFileToString; namespace aidl::android::hardware::audio::core { StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata) - : StreamAlsa(context, metadata, 3 /*readWriteRetries*/), + : StreamAlsaMonoPipe(context, metadata, 3 /*readWriteRetries*/), mIsAsynchronous(!!getContext().getAsyncCallback()), mStubDriver(getContext()) { context->startStreamDataProcessor(); @@ -52,25 +52,26 @@ StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata) ::android::status_t StreamPrimary::init(DriverCallbackInterface* callback) { RETURN_STATUS_IF_ERROR(mStubDriver.init(callback)); - return StreamAlsa::init(callback); + return StreamAlsaMonoPipe::init(callback); } ::android::status_t StreamPrimary::drain(StreamDescriptor::DrainMode mode) { - return isStubStreamOnWorker() ? mStubDriver.drain(mode) : StreamAlsa::drain(mode); + return isStubStreamOnWorker() ? mStubDriver.drain(mode) : StreamAlsaMonoPipe::drain(mode); } ::android::status_t StreamPrimary::flush() { - RETURN_STATUS_IF_ERROR(isStubStreamOnWorker() ? mStubDriver.flush() : StreamAlsa::flush()); + RETURN_STATUS_IF_ERROR(isStubStreamOnWorker() ? mStubDriver.flush() + : StreamAlsaMonoPipe::flush()); // TODO(b/372951987): consider if this needs to be done from 'StreamInWorkerLogic::cycle'. return mIsInput ? standby() : ::android::OK; } ::android::status_t StreamPrimary::pause() { - return isStubStreamOnWorker() ? mStubDriver.pause() : StreamAlsa::pause(); + return isStubStreamOnWorker() ? mStubDriver.pause() : StreamAlsaMonoPipe::pause(); } ::android::status_t StreamPrimary::standby() { - return isStubStreamOnWorker() ? mStubDriver.standby() : StreamAlsa::standby(); + return isStubStreamOnWorker() ? mStubDriver.standby() : StreamAlsaMonoPipe::standby(); } ::android::status_t StreamPrimary::start() { @@ -83,12 +84,12 @@ StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata) mCurrAlsaDeviceId = mAlsaDeviceId; } if (shutdownAlsaStream) { - StreamAlsa::shutdown(); // Close currently opened ALSA devices. + StreamAlsaMonoPipe::shutdown(); // Close currently opened ALSA devices. } if (isStub) { return mStubDriver.start(); } - RETURN_STATUS_IF_ERROR(StreamAlsa::start()); + RETURN_STATUS_IF_ERROR(StreamAlsaMonoPipe::start()); mStartTimeNs = ::android::uptimeNanos(); mFramesSinceStart = 0; mSkipNextTransfer = false; @@ -104,7 +105,7 @@ StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata) // and is not being able to achieve real-time behavior similar to ADSPs (b/302587331). if (!mSkipNextTransfer) { RETURN_STATUS_IF_ERROR( - StreamAlsa::transfer(buffer, frameCount, actualFrameCount, latencyMs)); + StreamAlsaMonoPipe::transfer(buffer, frameCount, actualFrameCount, latencyMs)); } else { LOG(DEBUG) << __func__ << ": skipping transfer (" << frameCount << " frames)"; *actualFrameCount = frameCount; @@ -137,13 +138,13 @@ StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata) const bool refinePosition = GetBoolProperty("persist.vendor.audio.refine_position", true); if (refinePosition) { - RETURN_STATUS_IF_ERROR(StreamAlsa::refinePosition(position)); + RETURN_STATUS_IF_ERROR(StreamAlsaBase::refinePosition(position)); } return ::android::OK; } void StreamPrimary::shutdown() { - StreamAlsa::shutdown(); + StreamAlsaMonoPipe::shutdown(); mStubDriver.shutdown(); } @@ -165,7 +166,7 @@ ndk::ScopedAStatus StreamPrimary::setConnectedDevices(const ConnectedDevices& de streamDataProcessor->setAudioDevice(devices[0]); } } - return StreamAlsa::setConnectedDevices(devices); + return StreamAlsaMonoPipe::setConnectedDevices(devices); } std::vector StreamPrimary::getDeviceProfiles() { diff --git a/audio/r_submix/ModuleRemoteSubmix.cpp b/audio/r_submix/ModuleRemoteSubmix.cpp index b44f37b..759c73d 100644 --- a/audio/r_submix/ModuleRemoteSubmix.cpp +++ b/audio/r_submix/ModuleRemoteSubmix.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include "SubmixRoute.h" @@ -28,6 +28,7 @@ using aidl::android::hardware::audio::common::SinkMetadata; using aidl::android::hardware::audio::common::SourceMetadata; +using aidl::android::media::audio::common::AudioChannelLayout; using aidl::android::media::audio::common::AudioDeviceAddress; using aidl::android::media::audio::common::AudioFormatType; using aidl::android::media::audio::common::AudioIoFlags; @@ -170,7 +171,8 @@ ndk::ScopedAStatus ModuleRemoteSubmix::onMasterVolumeChanged(float __unused) { int32_t ModuleRemoteSubmix::getNominalLatencyMs(const AudioPortConfig&) { // See the note on kDefaultPipePeriodCount. static constexpr int32_t kMaxLatencyMs = - (r_submix::kDefaultPipeSizeInFrames * 1000) / r_submix::kDefaultSampleRateHz; + (r_submix::kDefaultPipeSizeInFrames * MILLIS_PER_SECOND) / + r_submix::kDefaultSampleRateHz; static constexpr int32_t kMinLatencyMs = kMaxLatencyMs / r_submix::kDefaultPipePeriodCount; return kMinLatencyMs; } diff --git a/audio/r_submix/StreamRemoteSubmix.cpp b/audio/r_submix/StreamRemoteSubmix.cpp index cc3c644..b4bd8f2 100644 --- a/audio/r_submix/StreamRemoteSubmix.cpp +++ b/audio/r_submix/StreamRemoteSubmix.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "AHAL_StreamRemoteSubmix" -#include +#include #include #include #include @@ -26,53 +26,31 @@ using aidl::android::hardware::audio::common::SinkMetadata; using aidl::android::hardware::audio::common::SourceMetadata; using aidl::android::hardware::audio::core::r_submix::SubmixRoute; using aidl::android::media::audio::common::AudioDeviceAddress; +using aidl::android::media::audio::common::AudioDeviceType; using aidl::android::media::audio::common::AudioOffloadInfo; using aidl::android::media::audio::common::MicrophoneDynamicInfo; using aidl::android::media::audio::common::MicrophoneInfo; +using android::MonoPipe; +using android::MonoPipeReader; +using android::sp; namespace aidl::android::hardware::audio::core { -using deprecated::InnerStreamWrapper; -using deprecated::StreamCommonInterfaceEx; -using deprecated::StreamSwitcher; - -StreamRemoteSubmix::StreamRemoteSubmix(StreamContext* context, const Metadata& metadata, - const AudioDeviceAddress& deviceAddress) +StreamRemoteSubmix::StreamRemoteSubmix(StreamContext* context, const Metadata& metadata) : StreamCommonImpl(context, metadata), - mDeviceAddress(deviceAddress), - mIsInput(isInput(metadata)) { - mStreamConfig.frameSize = context->getFrameSize(); - mStreamConfig.format = context->getFormat(); - mStreamConfig.channelLayout = context->getChannelLayout(); - mStreamConfig.sampleRate = context->getSampleRate(); -} + mIsInput(isInput(metadata)), + mStreamConfig{.sampleRate = context->getSampleRate(), + .format = context->getFormat(), + .channelLayout = context->getChannelLayout(), + .frameSize = context->getFrameSize(), + .frameCount = context->getBufferSizeInFrames()}, + mReadAttemptSleepUs(getDurationInUsForFrameCount(r_submix::kReadAttemptSleepFrames)) {} StreamRemoteSubmix::~StreamRemoteSubmix() { cleanupWorker(); } ::android::status_t StreamRemoteSubmix::init(DriverCallbackInterface*) { - mCurrentRoute = SubmixRoute::findOrCreateRoute(mDeviceAddress, mStreamConfig); - if (mCurrentRoute == nullptr) { - return ::android::NO_INIT; - } - if (!mCurrentRoute->isStreamConfigValid(mIsInput, mStreamConfig)) { - LOG(ERROR) << __func__ << ": invalid stream config"; - return ::android::NO_INIT; - } - sp sink = mCurrentRoute->getSink(); - if (sink == nullptr) { - LOG(ERROR) << __func__ << ": nullptr sink when opening stream"; - return ::android::NO_INIT; - } - if ((!mIsInput || mCurrentRoute->isStreamInOpen()) && sink->isShutdown()) { - LOG(DEBUG) << __func__ << ": Shut down sink when opening stream"; - if (::android::OK != mCurrentRoute->resetPipe()) { - LOG(ERROR) << __func__ << ": reset pipe failed"; - return ::android::NO_INIT; - } - } - mCurrentRoute->openStream(mIsInput); return ::android::OK; } @@ -90,82 +68,111 @@ StreamRemoteSubmix::~StreamRemoteSubmix() { } ::android::status_t StreamRemoteSubmix::standby() { - mCurrentRoute->standby(mIsInput); + std::lock_guard guard(mLock); + if (mCurrentRoute) mCurrentRoute->standby(mIsInput); return ::android::OK; } ::android::status_t StreamRemoteSubmix::start() { - mCurrentRoute->exitStandby(mIsInput); + { + std::lock_guard guard(mLock); + if (mCurrentRoute) mCurrentRoute->exitStandby(mIsInput); + } mStartTimeNs = ::android::uptimeNanos(); mFramesSinceStart = 0; return ::android::OK; } -ndk::ScopedAStatus StreamRemoteSubmix::prepareToClose() { - if (!mIsInput) { - std::shared_ptr route = SubmixRoute::findRoute(mDeviceAddress); - if (route != nullptr) { - sp sink = route->getSink(); - if (sink == nullptr) { - ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); - } - LOG(DEBUG) << __func__ << ": shutting down MonoPipe sink"; - - sink->shutdown(true); - // The client already considers this stream as closed, release the output end. - route->closeStream(mIsInput); - } else { - LOG(DEBUG) << __func__ << ": stream already closed."; - ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); - } - } - return ndk::ScopedAStatus::ok(); -} - // Remove references to the specified input and output streams. When the device no longer // references input and output streams destroy the associated pipe. void StreamRemoteSubmix::shutdown() { - mCurrentRoute->closeStream(mIsInput); - // If all stream instances are closed, we can remove route information for this port. - if (!mCurrentRoute->hasAtleastOneStreamOpen()) { - mCurrentRoute->releasePipe(); - LOG(DEBUG) << __func__ << ": pipe destroyed"; - SubmixRoute::removeRoute(mDeviceAddress); + std::shared_ptr currentRoute; + { + std::lock_guard guard(mLock); + mCurrentRoute.swap(currentRoute); + } + if (!currentRoute) { + LOG(DEBUG) << __func__ << ": no current route"; + return; + } + currentRoute->closeStream(mIsInput); + // If all stream instances are closed, we can remove route information for this port. + if (!currentRoute->hasAtleastOneStreamOpen()) { + currentRoute->releasePipe(); + LOG(DEBUG) << __func__ << ": pipe " << currentRoute->getDeviceAddress().toString() + << " destroyed"; + currentRoute->remove(); + } else { + LOG(DEBUG) << __func__ << ": pipe " << currentRoute->getDeviceAddress().toString() + << " status: " << currentRoute->dump(); } - mCurrentRoute.reset(); } ::android::status_t StreamRemoteSubmix::transfer(void* buffer, size_t frameCount, size_t* actualFrameCount, int32_t* latencyMs) { - *latencyMs = getDelayInUsForFrameCount(getStreamPipeSizeInFrames()) / 1000; + std::shared_ptr currentRoute; + { + std::lock_guard guard(mLock); + currentRoute = mCurrentRoute; + } + *latencyMs = getDurationInUsForFrameCount(getStreamPipeSizeInFrames(currentRoute)) / 1000; LOG(VERBOSE) << __func__ << ": Latency " << *latencyMs << "ms"; - mCurrentRoute->exitStandby(mIsInput); - ::android::status_t status = mIsInput ? inRead(buffer, frameCount, actualFrameCount) - : outWrite(buffer, frameCount, actualFrameCount); - if ((status != ::android::OK && mIsInput) || - ((status != ::android::OK && status != ::android::DEAD_OBJECT) && !mIsInput)) { - return status; + ::android::status_t status = ::android::OK; + if (currentRoute) { + currentRoute->exitStandby(mIsInput); + if (!mSkipNextTransfer) { + status = mIsInput ? inRead(currentRoute, buffer, frameCount, actualFrameCount) + : outWrite(currentRoute, buffer, frameCount, actualFrameCount); + if ((status != ::android::OK && mIsInput) || + ((status != ::android::OK && status != ::android::DEAD_OBJECT) && !mIsInput)) { + return status; + } + } else { + LOG(VERBOSE) << __func__ << ": Skipping transfer"; + if (mIsInput) memset(buffer, 0, mStreamConfig.frameSize * frameCount); + *actualFrameCount = frameCount; + } + } else { + LOG(WARNING) << __func__ << ": no current route"; + if (mIsInput) { + memset(buffer, 0, mStreamConfig.frameSize * frameCount); + } + *actualFrameCount = frameCount; } mFramesSinceStart += *actualFrameCount; - if (!mIsInput && status != ::android::DEAD_OBJECT) return ::android::OK; - // Input streams always need to block, output streams need to block when there is no sink. - // When the sink exists, more sophisticated blocking algorithm is implemented by MonoPipe. + // If there is no route, always block, otherwise: + // - Input streams always need to block, output streams need to block when there is no sink. + // - When the sink exists, more sophisticated blocking algorithm is implemented by MonoPipe. + if (mSkipNextTransfer || (currentRoute && !mIsInput && status != ::android::DEAD_OBJECT)) { + mSkipNextTransfer = false; + return ::android::OK; + } const long bufferDurationUs = (*actualFrameCount) * MICROS_PER_SECOND / mContext.getSampleRate(); const auto totalDurationUs = (::android::uptimeNanos() - mStartTimeNs) / NANOS_PER_MICROSECOND; - const long totalOffsetUs = - mFramesSinceStart * MICROS_PER_SECOND / mContext.getSampleRate() - totalDurationUs; + const long totalOffsetUs = getDurationInUsForFrameCount(mFramesSinceStart) - totalDurationUs; LOG(VERBOSE) << __func__ << ": totalOffsetUs " << totalOffsetUs; if (totalOffsetUs > 0) { - const long sleepTimeUs = std::min(totalOffsetUs, bufferDurationUs); + const long sleepTimeUs = std::max(0L, std::min(totalOffsetUs, bufferDurationUs)); LOG(VERBOSE) << __func__ << ": sleeping for " << sleepTimeUs << " us"; usleep(sleepTimeUs); + } else if (totalOffsetUs <= -(bufferDurationUs / 2)) { + LOG(VERBOSE) << __func__ << ": skipping next transfer"; + mSkipNextTransfer = true; } return ::android::OK; } ::android::status_t StreamRemoteSubmix::refinePosition(StreamDescriptor::Position* position) { - sp source = mCurrentRoute->getSource(); + std::shared_ptr currentRoute; + { + std::lock_guard guard(mLock); + currentRoute = mCurrentRoute; + } + if (!currentRoute) { + return ::android::OK; + } + sp source = currentRoute->getSource(); if (source == nullptr) { return ::android::NO_INIT; } @@ -182,20 +189,23 @@ void StreamRemoteSubmix::shutdown() { return ::android::OK; } -long StreamRemoteSubmix::getDelayInUsForFrameCount(size_t frameCount) { +long StreamRemoteSubmix::getDurationInUsForFrameCount(size_t frameCount) const { return frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate; } // Calculate the maximum size of the pipe buffer in frames for the specified stream. -size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() { - auto pipeConfig = mCurrentRoute->getPipeConfig(); +size_t StreamRemoteSubmix::getStreamPipeSizeInFrames( + const std::shared_ptr& currentRoute) { + if (!currentRoute) return r_submix::kDefaultPipeSizeInFrames; + auto pipeConfig = currentRoute->getPipeConfig(); const size_t maxFrameSize = std::max(mStreamConfig.frameSize, pipeConfig.frameSize); return (pipeConfig.frameCount * pipeConfig.frameSize) / maxFrameSize; } -::android::status_t StreamRemoteSubmix::outWrite(void* buffer, size_t frameCount, - size_t* actualFrameCount) { - sp sink = mCurrentRoute->getSink(); +::android::status_t StreamRemoteSubmix::outWrite( + const std::shared_ptr& currentRoute, void* buffer, size_t frameCount, + size_t* actualFrameCount) { + sp sink = currentRoute->getSink(); if (sink != nullptr) { if (sink->isShutdown()) { sink.clear(); @@ -211,13 +221,13 @@ size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() { } mWriteShutdownCount = 0; - LOG(VERBOSE) << __func__ << ": " << mDeviceAddress.toString() << ", " << frameCount - << " frames"; + LOG(VERBOSE) << __func__ << ": " << currentRoute->getDeviceAddress().toString() << ", " + << frameCount << " frames"; - const bool shouldBlockWrite = mCurrentRoute->shouldBlockWrite(); + const bool shouldBlockWrite = currentRoute->shouldBlockWrite(); size_t availableToWrite = sink->availableToWrite(); // NOTE: sink has been checked above and sink and source life cycles are synchronized - sp source = mCurrentRoute->getSource(); + sp source = currentRoute->getSource(); // If the write to the sink should be blocked, flush enough frames from the pipe to make space // to write the most recent data. if (!shouldBlockWrite && availableToWrite < frameCount) { @@ -267,14 +277,22 @@ size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() { return ::android::OK; } -::android::status_t StreamRemoteSubmix::inRead(void* buffer, size_t frameCount, - size_t* actualFrameCount) { +::android::status_t StreamRemoteSubmix::inRead( + const std::shared_ptr& currentRoute, void* buffer, size_t frameCount, + size_t* actualFrameCount) { + // Try to wait as long as possible for the audio duration, but leave some time for the call to + // 'transfer' to complete. 'mReadAttemptSleepUs' is a good constant for this purpose because it + // is by definition "strictly inferior" to the typical buffer duration. + const long durationUs = + std::max(0L, getDurationInUsForFrameCount(frameCount) - mReadAttemptSleepUs * 2); + const int64_t deadlineTimeNs = ::android::uptimeNanos() + durationUs * NANOS_PER_MICROSECOND; + // in any case, it is emulated that data for the entire buffer was available memset(buffer, 0, mStreamConfig.frameSize * frameCount); *actualFrameCount = frameCount; // about to read from audio source - sp source = mCurrentRoute->getSource(); + sp source = currentRoute->getSource(); if (source == nullptr) { if (++mReadErrorCount < kMaxErrorLogs) { LOG(ERROR) << __func__ @@ -284,7 +302,7 @@ size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() { return ::android::OK; } // get and hold the sink because 'MonoPipeReader' does not hold a strong pointer to it. - sp sink = mCurrentRoute->getSink(); + sp sink = currentRoute->getSink(); if (sink == nullptr) { if (++mReadErrorCount < kMaxErrorLogs) { LOG(ERROR) << __func__ @@ -294,18 +312,13 @@ size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() { } mReadErrorCount = 0; - LOG(VERBOSE) << __func__ << ": " << mDeviceAddress.toString() << ", " << frameCount - << " frames"; + LOG(VERBOSE) << __func__ << ": " << currentRoute->getDeviceAddress().toString() << ", " + << frameCount << " frames"; + // read the data from the pipe char* buff = (char*)buffer; size_t actuallyRead = 0; long remainingFrames = frameCount; - // Try to wait as long as possible for the audio duration, but leave some time for the call to - // 'transfer' to complete. 'kReadAttemptSleepUs' is a good constant for this purpose because it - // is by definition "strictly inferior" to the typical buffer duration. - const long durationUs = - std::max(0L, getDelayInUsForFrameCount(frameCount) - kReadAttemptSleepUs); - const int64_t deadlineTimeNs = ::android::uptimeNanos() + durationUs * NANOS_PER_MICROSECOND; while (remainingFrames > 0) { ssize_t framesRead = source->read(buff, remainingFrames); LOG(VERBOSE) << __func__ << ": frames read " << framesRead; @@ -319,26 +332,112 @@ size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() { if (::android::uptimeNanos() >= deadlineTimeNs) break; if (framesRead <= 0) { LOG(VERBOSE) << __func__ << ": read returned " << framesRead - << ", read failure, sleeping for " << kReadAttemptSleepUs << " us"; - usleep(kReadAttemptSleepUs); + << ", read failure, sleeping for " << mReadAttemptSleepUs << " us"; + usleep(mReadAttemptSleepUs); } } if (actuallyRead < frameCount) { - if (++mReadFailureCount < kMaxReadFailureAttempts) { + if (++mReadFailureCount < r_submix::kMaxReadFailureAttempts) { LOG(WARNING) << __func__ << ": read " << actuallyRead << " vs. requested " << frameCount << " (not all errors will be logged)"; } } else { mReadFailureCount = 0; } - mCurrentRoute->updateReadCounterFrames(*actualFrameCount); + currentRoute->updateReadCounterFrames(*actualFrameCount); return ::android::OK; } +std::shared_ptr StreamRemoteSubmix::prepareCurrentRoute( + const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress) { + if (deviceAddress == AudioDeviceAddress{}) { + return nullptr; + } + auto currentRoute = SubmixRoute::findOrCreateRoute(deviceAddress, mStreamConfig); + if (currentRoute == nullptr) return nullptr; + if (!currentRoute->isStreamConfigValid(mIsInput, mStreamConfig)) { + LOG(ERROR) << __func__ << ": invalid stream config"; + return nullptr; + } + sp sink = currentRoute->getSink(); + if (sink == nullptr) { + LOG(ERROR) << __func__ << ": nullptr sink when opening stream"; + return nullptr; + } + if ((!mIsInput || currentRoute->isStreamInOpen()) && sink->isShutdown()) { + LOG(DEBUG) << __func__ << ": shut down sink when opening stream"; + if (::android::OK != currentRoute->resetPipe()) { + LOG(ERROR) << __func__ << ": reset pipe failed"; + return nullptr; + } + } + currentRoute->openStream(mIsInput); + return currentRoute; +} + +ndk::ScopedAStatus StreamRemoteSubmix::prepareToClose() { + std::shared_ptr currentRoute; + { + std::lock_guard guard(mLock); + currentRoute = mCurrentRoute; + } + if (currentRoute != nullptr) { + if (!mIsInput) { + // The client already considers this stream as closed, release the output end. + currentRoute->closeStream(mIsInput); + } + } else { + LOG(DEBUG) << __func__ << ": stream already closed"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StreamRemoteSubmix::setConnectedDevices(const ConnectedDevices& devices) { + LOG(DEBUG) << __func__ << ": ioHandle: " << mContext.getMixPortHandle() + << ", devices: " << ::android::internal::ToString(devices); + if (devices.size() > 1) { + LOG(ERROR) << __func__ << ": Only single device supported, got " << devices.size(); + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + AudioDeviceAddress newAddress; + if (!devices.empty()) { + if (auto deviceDesc = devices.front().type; + (mIsInput && deviceDesc.type != AudioDeviceType::IN_SUBMIX) || + (!mIsInput && deviceDesc.type != AudioDeviceType::OUT_SUBMIX)) { + LOG(ERROR) << __func__ << ": Device type " << toString(deviceDesc.type) + << " not supported"; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + newAddress = devices.front().address; + if (newAddress != AudioDeviceAddress{}) { + auto existingRoute = SubmixRoute::findRoute(newAddress); + if (existingRoute != nullptr) { + if (!existingRoute->isStreamConfigValid(mIsInput, mStreamConfig)) { + LOG(ERROR) << __func__ << ": invalid stream config"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + } + } + RETURN_STATUS_IF_ERROR(StreamCommonImpl::setConnectedDevices(devices)); + auto newCurrentRoute = prepareCurrentRoute(newAddress); + if (newCurrentRoute) { + std::lock_guard guard(mLock); + mCurrentRoute = newCurrentRoute; + LOG(DEBUG) << __func__ << ": connected to " << newAddress.toString(); + } else { + // Do not update `mCurrentRoute`, it will be cleaned up by the worker thread. + LOG(DEBUG) << __func__ << ": disconnected"; + } + return ndk::ScopedAStatus::ok(); +} + StreamInRemoteSubmix::StreamInRemoteSubmix(StreamContext&& context, const SinkMetadata& sinkMetadata, const std::vector& microphones) - : StreamIn(std::move(context), microphones), StreamSwitcher(&mContextInstance, sinkMetadata) {} + : StreamIn(std::move(context), microphones), + StreamRemoteSubmix(&mContextInstance, sinkMetadata) {} ndk::ScopedAStatus StreamInRemoteSubmix::getActiveMicrophones( std::vector* _aidl_return) { @@ -347,66 +446,10 @@ ndk::ScopedAStatus StreamInRemoteSubmix::getActiveMicrophones( return ndk::ScopedAStatus::ok(); } -StreamSwitcher::DeviceSwitchBehavior StreamInRemoteSubmix::switchCurrentStream( - const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) { - // This implementation effectively postpones stream creation until - // receiving the first call to 'setConnectedDevices' with a non-empty list. - if (isStubStream()) { - if (devices.size() == 1) { - auto deviceDesc = devices.front().type; - if (deviceDesc.type == - ::aidl::android::media::audio::common::AudioDeviceType::IN_SUBMIX) { - return DeviceSwitchBehavior::CREATE_NEW_STREAM; - } - LOG(ERROR) << __func__ << ": Device type " << toString(deviceDesc.type) - << " not supported"; - } else { - LOG(ERROR) << __func__ << ": Only single device supported."; - } - return DeviceSwitchBehavior::UNSUPPORTED_DEVICES; - } - return DeviceSwitchBehavior::USE_CURRENT_STREAM; -} - -std::unique_ptr StreamInRemoteSubmix::createNewStream( - const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices, - StreamContext* context, const Metadata& metadata) { - return std::unique_ptr( - new InnerStreamWrapper(context, metadata, devices.front().address)); -} - StreamOutRemoteSubmix::StreamOutRemoteSubmix(StreamContext&& context, const SourceMetadata& sourceMetadata, const std::optional& offloadInfo) : StreamOut(std::move(context), offloadInfo), - StreamSwitcher(&mContextInstance, sourceMetadata) {} - -StreamSwitcher::DeviceSwitchBehavior StreamOutRemoteSubmix::switchCurrentStream( - const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) { - // This implementation effectively postpones stream creation until - // receiving the first call to 'setConnectedDevices' with a non-empty list. - if (isStubStream()) { - if (devices.size() == 1) { - auto deviceDesc = devices.front().type; - if (deviceDesc.type == - ::aidl::android::media::audio::common::AudioDeviceType::OUT_SUBMIX) { - return DeviceSwitchBehavior::CREATE_NEW_STREAM; - } - LOG(ERROR) << __func__ << ": Device type " << toString(deviceDesc.type) - << " not supported"; - } else { - LOG(ERROR) << __func__ << ": Only single device supported."; - } - return DeviceSwitchBehavior::UNSUPPORTED_DEVICES; - } - return DeviceSwitchBehavior::USE_CURRENT_STREAM; -} - -std::unique_ptr StreamOutRemoteSubmix::createNewStream( - const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices, - StreamContext* context, const Metadata& metadata) { - return std::unique_ptr( - new InnerStreamWrapper(context, metadata, devices.front().address)); -} + StreamRemoteSubmix(&mContextInstance, sourceMetadata) {} } // namespace aidl::android::hardware::audio::core diff --git a/audio/r_submix/SubmixRoute.cpp b/audio/r_submix/SubmixRoute.cpp index 445b1d3..94773f5 100644 --- a/audio/r_submix/SubmixRoute.cpp +++ b/audio/r_submix/SubmixRoute.cpp @@ -17,7 +17,7 @@ #include #define LOG_TAG "AHAL_SubmixRoute" -#include +#include #include #include @@ -25,7 +25,11 @@ #include "SubmixRoute.h" using aidl::android::hardware::audio::common::getChannelCount; +using aidl::android::media::audio::common::AudioChannelLayout; using aidl::android::media::audio::common::AudioDeviceAddress; +using android::MonoPipe; +using android::MonoPipeReader; +using android::sp; namespace aidl::android::hardware::audio::core::r_submix { @@ -45,7 +49,7 @@ std::shared_ptr SubmixRoute::findOrCreateRoute(const AudioDeviceAdd if (routeItr != routes->end()) { return routeItr->second; } - auto route = std::make_shared(); + auto route = std::make_shared(deviceAddress); if (::android::OK != route->createPipe(pipeConfig)) { LOG(ERROR) << __func__ << ": create pipe failed"; return nullptr; @@ -64,11 +68,6 @@ std::shared_ptr SubmixRoute::findRoute(const AudioDeviceAddress& de return nullptr; } -// static -void SubmixRoute::removeRoute(const AudioDeviceAddress& deviceAddress) { - getRoutes()->erase(deviceAddress); -} - // static std::string SubmixRoute::dumpRoutes() { auto routes = getRoutes(true /*tryLock*/); @@ -176,6 +175,9 @@ void SubmixRoute::closeStream(bool isInput) { } } else { mStreamOutOpen = false; + if (mSink != nullptr) { + mSink->shutdown(true); + } } } @@ -244,6 +246,10 @@ AudioConfig SubmixRoute::releasePipe() { return mPipeConfig; } +void SubmixRoute::remove() { + getRoutes()->erase(mDeviceAddress); +} + ::android::status_t SubmixRoute::resetPipe() { return createPipe(releasePipe()); } diff --git a/audio/r_submix/SubmixRoute.h b/audio/r_submix/SubmixRoute.h index 5425f12..ea372c3 100644 --- a/audio/r_submix/SubmixRoute.h +++ b/audio/r_submix/SubmixRoute.h @@ -25,18 +25,11 @@ #include #include +#include #include #include #include -using aidl::android::media::audio::common::AudioChannelLayout; -using aidl::android::media::audio::common::AudioFormatDescription; -using aidl::android::media::audio::common::AudioFormatType; -using aidl::android::media::audio::common::PcmType; -using ::android::MonoPipe; -using ::android::MonoPipeReader; -using ::android::sp; - namespace aidl::android::hardware::audio::core::r_submix { static constexpr int kDefaultSampleRateHz = 48000; @@ -44,20 +37,34 @@ static constexpr int kDefaultSampleRateHz = 48000; // read from the sink. The maximum latency of the device is the size of the MonoPipe's buffer // the minimum latency is the MonoPipe buffer size divided by this value. static constexpr int kDefaultPipePeriodCount = 4; +static constexpr int kDefaultPipePeriodSizeFrames = 1024; // Size at the default sample rate // NOTE: This value will be rounded up to the nearest power of 2 by MonoPipe. -static constexpr int kDefaultPipeSizeInFrames = 1024 * kDefaultPipePeriodCount; +static constexpr int kDefaultPipeSizeInFrames = + kDefaultPipePeriodSizeFrames * kDefaultPipePeriodCount; +// The duration of kMaxReadFailureAttempts * kReadAttemptSleepUs must be strictly inferior to the +// duration of a record buffer (kDefaultPipePeriodSizeFrames) at the current record sample rate (of +// the device, not of the recording itself). Here we have: 3 * 240 = 720 < 1024 frames +static constexpr int kMaxReadFailureAttempts = 3; +static constexpr int kReadAttemptSleepFrames = 240; +static_assert(kMaxReadFailureAttempts * kReadAttemptSleepFrames * 7 < + kDefaultPipePeriodSizeFrames * 5, + "Duration of attempts * sleep must be significantly less than the period size"); // Configuration of the audio stream. struct AudioConfig { int sampleRate = kDefaultSampleRateHz; - AudioFormatDescription format = - AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = PcmType::INT_16_BIT}; - AudioChannelLayout channelLayout = - AudioChannelLayout::make( - AudioChannelLayout::LAYOUT_STEREO); - size_t frameSize; - size_t frameCount; + ::aidl::android::media::audio::common::AudioFormatDescription format = + ::aidl::android::media::audio::common::AudioFormatDescription{ + .type = ::aidl::android::media::audio::common::AudioFormatType::PCM, + .pcm = ::aidl::android::media::audio::common::PcmType::INT_16_BIT}; + ::aidl::android::media::audio::common::AudioChannelLayout channelLayout = + ::aidl::android::media::audio::common::AudioChannelLayout::make< + ::aidl::android::media::audio::common::AudioChannelLayout::Tag::layoutMask>( + ::aidl::android::media::audio::common::AudioChannelLayout::LAYOUT_STEREO); + size_t frameSize = + ::aidl::android::hardware::audio::common::getFrameSizeInBytes(format, channelLayout); + size_t frameCount = 0; }; class SubmixRoute { @@ -67,10 +74,10 @@ class SubmixRoute { const AudioConfig& pipeConfig); static std::shared_ptr findRoute( const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress); - static void removeRoute( - const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress); static std::string dumpRoutes(); + SubmixRoute(const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress) + : mDeviceAddress(deviceAddress) {} bool isStreamInOpen() { std::lock_guard guard(mLock); return mStreamInOpen; @@ -91,11 +98,11 @@ class SubmixRoute { std::lock_guard guard(mLock); return mReadCounterFrames; } - sp getSink() { + ::android::sp<::android::MonoPipe> getSink() { std::lock_guard guard(mLock); return mSink; } - sp getSource() { + ::android::sp<::android::MonoPipeReader> getSource() { std::lock_guard guard(mLock); return mSource; } @@ -104,6 +111,9 @@ class SubmixRoute { return mPipeConfig; } + ::aidl::android::media::audio::common::AudioDeviceAddress getDeviceAddress() const { + return mDeviceAddress; + } bool isStreamConfigValid(bool isInput, const AudioConfig& streamConfig); void closeStream(bool isInput); ::android::status_t createPipe(const AudioConfig& streamConfig); @@ -112,6 +122,7 @@ class SubmixRoute { int notifyReadError(); void openStream(bool isInput); AudioConfig releasePipe(); + void remove(); ::android::status_t resetPipe(); bool shouldBlockWrite(); void standby(bool isInput); @@ -138,6 +149,7 @@ class SubmixRoute { bool isStreamConfigCompatible(const AudioConfig& streamConfig); + const ::aidl::android::media::audio::common::AudioDeviceAddress mDeviceAddress; std::mutex mLock; AudioConfig mPipeConfig GUARDED_BY(mLock); bool mStreamInOpen GUARDED_BY(mLock) = false; @@ -157,8 +169,8 @@ class SubmixRoute { // A usecase example is one where the component capturing the audio is then sending it over // Wifi for presentation on a remote Wifi Display device (e.g. a dongle attached to a TV, or a // TV with Wifi Display capabilities), or to a wireless audio player. - sp mSink GUARDED_BY(mLock); - sp mSource GUARDED_BY(mLock); + ::android::sp<::android::MonoPipe> mSink GUARDED_BY(mLock); + ::android::sp<::android::MonoPipeReader> mSource GUARDED_BY(mLock); }; } // namespace aidl::android::hardware::audio::core::r_submix diff --git a/audio/stub/ApeHeader.cpp b/audio/stub/ApeHeader.cpp index 9112377..ee68f37 100644 --- a/audio/stub/ApeHeader.cpp +++ b/audio/stub/ApeHeader.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "AHAL_OffloadStream" -#include +#include #include "ApeHeader.h" diff --git a/audio/stub/DriverStubImpl.cpp b/audio/stub/DriverStubImpl.cpp index cb8ee70..74a4edc 100644 --- a/audio/stub/DriverStubImpl.cpp +++ b/audio/stub/DriverStubImpl.cpp @@ -18,7 +18,7 @@ #include #define LOG_TAG "AHAL_Stream" -#include +#include #include #include "core-impl/DriverStubImpl.h" diff --git a/audio/stub/ModuleStub.cpp b/audio/stub/ModuleStub.cpp index 9f6e0b4..242959f 100644 --- a/audio/stub/ModuleStub.cpp +++ b/audio/stub/ModuleStub.cpp @@ -17,8 +17,8 @@ #include #define LOG_TAG "AHAL_ModuleStub" +#include #include -#include #include "core-impl/Bluetooth.h" #include "core-impl/ModuleStub.h" diff --git a/audio/stub/MpegAudioHeader.cpp b/audio/stub/MpegAudioHeader.cpp new file mode 100644 index 0000000..365395f --- /dev/null +++ b/audio/stub/MpegAudioHeader.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MpegAudioHeader.h" +#define LOG_TAG "AHAL_OffloadStream" +#include +#include +#include + +namespace aidl::android::hardware::audio::core { + +enum class Version { MPEG_2_5 = 0, RESERVED = 1, MPEG_2 = 2, MPEG_1 = 3 }; + +enum class Layer { RESERVED = 0, LAYER_3 = 1, LAYER_2 = 2, LAYER_1 = 3 }; + +// Bitrates in kilo bits per second +const int kBitrateTable[16][5] = { + // V1,L1, V1,L2, V1,L3, V2,L1, V2,L2&L3 + {0, 0, 0, 0, 0}, // Invalid + {32, 32, 32, 32, 8}, {64, 48, 40, 48, 16}, {96, 56, 48, 56, 24}, + {128, 64, 56, 64, 32}, {160, 80, 64, 80, 40}, {192, 96, 80, 96, 48}, + {224, 112, 96, 112, 56}, {256, 128, 112, 128, 64}, {288, 160, 128, 144, 80}, + {320, 192, 160, 160, 96}, {352, 224, 192, 176, 112}, {384, 256, 224, 192, 128}, + {416, 320, 256, 224, 144}, {448, 384, 320, 256, 160}, {0, 0, 0, 0, 0} // Invalid +}; + +const int kSampleRateTable[4][3] = { + // MPEG1, MPEG2, MPEG2.5 + {44100, 22050, 11025}, + {48000, 24000, 12000}, + {32000, 16000, 8000}, + {0, 0, 0} // Invalid +}; + +constexpr int kLayer1SamplesPerMpegFrame = 384; +constexpr int kLayer2SamplesPerMpegFrame = 1152; +constexpr int kID3v2TagHeaderSize = 10; +constexpr int kID3v1TagSize = 128; +constexpr int kID3TagIdentifierSize = 3; +constexpr int kFrameHeaderSize = 4; + +constexpr uint8_t kSyncByte = 0xFF; +constexpr uint8_t kId3v2VersionByteMax = 0xFF; +constexpr uint8_t kId3v2SizeByteMax = 0x80; +constexpr uint8_t kSyncByteMask = 0xE0; + +constexpr int kPaddingBitShift = 1; +constexpr int kLayerDescShift = 1; +constexpr int kSampleRateIndexShift = 2; +constexpr int kVersionIdShift = 3; +constexpr int kBitrateIndexShift = 4; +constexpr int kChannelIdxShift = 6; + +constexpr uint8_t kPaddingBitMask = 0x01; +constexpr uint8_t kLayerDescMask = 0x03; +constexpr uint8_t kSampleRateIndexMask = 0x03; +constexpr uint8_t kVersionIdMask = 0x03; +constexpr uint8_t kBitrateIndexMask = 0x0F; +constexpr uint8_t kChannelIdxMask = 0x03; + +constexpr int kMonoChannelIdx = 3; + +bool findID3v1Tag(const uint8_t** currBuff, const uint8_t* endBuff, MpegFrame& frame) { + frame.bytesPending = 0; + if ((*currBuff) + kID3TagIdentifierSize > endBuff) { + frame.bytesPending = (*currBuff + kID3TagIdentifierSize) - endBuff; + return false; + } + // ID3v1 header must start with TAG + if ((*currBuff) + kID3TagIdentifierSize <= endBuff) { + if (std::memcmp(*currBuff, "TAG", kID3TagIdentifierSize) == 0) { + frame.isID3v1 = true; + if ((*currBuff) + kID3v1TagSize > endBuff) { + frame.bytesPending = (*currBuff + kID3v1TagSize) - endBuff; + } + *currBuff = std::min(*currBuff + kID3v1TagSize, endBuff); + return true; + } + } + return false; +} + +bool findID3v2Tag(const uint8_t** currBuff, const uint8_t* endBuff, MpegFrame& frame) { + frame.bytesPending = 0; + if ((*currBuff) + kID3v2TagHeaderSize > endBuff) { + frame.bytesPending = (*currBuff + kID3v2TagHeaderSize) - endBuff; + return false; + } + // An ID3v2 tag can be detected with the following pattern: + // I D 3 yy yy xx zz zz zz zz + // Where yy is less than $FF and zz is less than $80. + if (std::memcmp(*currBuff, "ID3", kID3TagIdentifierSize) == 0 && + (*currBuff)[3] < kId3v2VersionByteMax /*yy*/ && + (*currBuff)[4] < kId3v2VersionByteMax /*yy*/ && (*currBuff)[6] < kId3v2SizeByteMax /*zz*/ && + (*currBuff)[7] < kId3v2SizeByteMax /*zz*/ && (*currBuff)[8] < kId3v2SizeByteMax /*zz*/ && + (*currBuff)[9] < kId3v2SizeByteMax) /*zz*/ { + frame.isID3v2 = true; + size_t dataSize = ((*currBuff)[6] << 21) | ((*currBuff)[7] << 14) | ((*currBuff)[8] << 7) | + (*currBuff)[9]; + size_t totalSize = dataSize + kID3v2TagHeaderSize; + if (*currBuff + totalSize > endBuff) { + frame.bytesPending = (*currBuff + totalSize) - endBuff; + } + *currBuff = std::min(*currBuff + totalSize, endBuff); + return true; + } + return false; +} + +std::optional getBitrate(Version version, Layer layer, size_t bitrateIndex) { + if (bitrateIndex >= std::size(kBitrateTable)) { + LOG(ERROR) << "Invalid bitrate index: " << bitrateIndex; + return std::nullopt; + } + + int bitrateColumn = 0; + if (version == Version::MPEG_1) { + if (layer == Layer::LAYER_1) + bitrateColumn = 0; + else if (layer == Layer::LAYER_2) + bitrateColumn = 1; + else + bitrateColumn = 2; + } else { // MPEG_2 or MPEG_2_5 + if (layer == Layer::LAYER_1) + bitrateColumn = 3; + else + bitrateColumn = 4; + } + + const int bitrateInKbps = kBitrateTable[bitrateIndex][bitrateColumn]; + if (bitrateInKbps == 0) { + LOG(WARNING) << "Invalid frame: bitrate is 0"; + return std::nullopt; + } + + return bitrateInKbps; +} + +std::optional getSampleRate(Version version, size_t sampleRateIndex) { + if (sampleRateIndex >= std::size(kSampleRateTable)) { + LOG(ERROR) << "Invalid sample rate index: " << sampleRateIndex; + return std::nullopt; + } + + int versionColumn = 0; + if (version == Version::MPEG_1) + versionColumn = 0; + else if (version == Version::MPEG_2) + versionColumn = 1; + else + versionColumn = 2; // MPEG_2_5 + + const int sampleRate = kSampleRateTable[sampleRateIndex][versionColumn]; + if (sampleRate == 0) { + LOG(WARNING) << "Invalid frame: sampling frequency is 0"; + return std::nullopt; + } + + return sampleRate; +} + +std::optional findMpegFrame(const uint8_t** currBuff, const uint8_t* endBuff) { + MpegFrame frame; + bool id3TagFound = + findID3v2Tag(currBuff, endBuff, frame) || findID3v1Tag(currBuff, endBuff, frame); + if (id3TagFound && *currBuff == endBuff) { + return frame; + } + while ((*currBuff) + kFrameHeaderSize <= endBuff) { + // Sync word of 11 set bits + if ((*currBuff)[0] != kSyncByte || ((*currBuff)[1] & kSyncByteMask) != kSyncByteMask) { + (*currBuff)++; + continue; + } + const Version version = + static_cast((((*currBuff)[1] >> kVersionIdShift) & kVersionIdMask)); + const Layer layer = + static_cast(((*currBuff)[1] >> kLayerDescShift) & kLayerDescMask); + const size_t bitrateIndex = ((*currBuff)[2] >> kBitrateIndexShift) & kBitrateIndexMask; + const size_t sampleRateIndex = + ((*currBuff)[2] >> kSampleRateIndexShift) & kSampleRateIndexMask; + + if (version != Version::RESERVED && layer != Layer::RESERVED) { + // Get the bitrate and sample rate + auto bitrateOpt = getBitrate(version, layer, bitrateIndex); + auto sampleRateOpt = getSampleRate(version, sampleRateIndex); + + if (bitrateOpt && sampleRateOpt) { + const int bitrateInKbps = bitrateOpt.value(); + const int sampleRate = sampleRateOpt.value(); + const int paddingBit = ((*currBuff)[2] >> kPaddingBitShift) & kPaddingBitMask; + const int channelIdx = ((*currBuff)[3] >> kChannelIdxShift) & kChannelIdxMask; + frame.sampleRate = sampleRate; + frame.bitRate = bitrateInKbps * 1000; + + [[maybe_unused]] int channelCount = (channelIdx == kMonoChannelIdx) ? 1 : 2; + // Compute frameSize and frameLength + if (layer == Layer::LAYER_1) { + frame.frameSize = kLayer1SamplesPerMpegFrame; + // For Layer 1, frame length is calculated based on 4-byte "slots" + frame.frameLengthBytes = ((kLayer1SamplesPerMpegFrame / sizeof(uint32_t)) * + (bitrateInKbps * 1000 /* convert to bps */) / + (sampleRate * CHAR_BIT) + + paddingBit) * + sizeof(uint32_t); + } else { + frame.frameSize = kLayer2SamplesPerMpegFrame; + // For Layer 2 & 3, frame length is calculated directly in bytes. + frame.frameLengthBytes = (kLayer2SamplesPerMpegFrame * + (bitrateInKbps * 1000 /* convert to bps */) / + (sampleRate * CHAR_BIT)) + + paddingBit; + } + + if (*currBuff + frame.frameLengthBytes > endBuff) { + // This buffer only has partial frame data. + // The second part of this frame will be available in the next call. Consume + // all the bytes in this call. The findMpegFrame call for the next frame + // will consume the second half of the frame. + frame.bytesPending = (*currBuff + frame.frameLengthBytes) - endBuff; + } + *currBuff = std::min(*currBuff + frame.frameLengthBytes, endBuff); + return frame; + } + } + (*currBuff)++; + } + return std::nullopt; +} + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/stub/MpegAudioHeader.h b/audio/stub/MpegAudioHeader.h new file mode 100644 index 0000000..eeff5cb --- /dev/null +++ b/audio/stub/MpegAudioHeader.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +namespace aidl::android::hardware::audio::core { + +/* + * This parser captures these fields to determine properties such as + * the MPEG version, layer, bitrate, sample rate, frame length and frame size. + * + * Reference - https://www.datavoyage.com/mpgscript/mpeghdr.htm + * - https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.2.html + */ + +struct MpegFrame { + // The total length of the MPEG audio frame in bytes, including the header + int frameLengthBytes = 0; + // The number of audio samples in the frame + int frameSize = 0; + bool isID3v2 = false; + bool isID3v1 = false; + // Last few bytes of an ID3 tag or frame that couldn't be processed in a single chunk + size_t bytesPending = 0; + int sampleRate = 0; + int bitRate = 0; +}; + +inline std::ostream& operator<<(std::ostream& os, const MpegFrame& frame) { + os << "MpegFrame{len=" << frame.frameLengthBytes << ", size=" << frame.frameSize + << ", isID3v2=" << (frame.isID3v2 ? "true" : "false") + << ", isID3v1=" << (frame.isID3v1 ? "true" : "false") << ", pending=" << frame.bytesPending + << ", sampleRate=" << frame.sampleRate << "}"; + return os; +} + +std::optional findMpegFrame(const uint8_t** currBuff, const uint8_t* endBuff); + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/stub/StreamMmapStub.cpp b/audio/stub/StreamMmapStub.cpp index f48aea4..5c68535 100644 --- a/audio/stub/StreamMmapStub.cpp +++ b/audio/stub/StreamMmapStub.cpp @@ -18,7 +18,7 @@ #include #define LOG_TAG "AHAL_MmapStream" -#include +#include #include #include #include @@ -105,12 +105,28 @@ DriverMmapStubImpl::DriverMmapStubImpl(const StreamContext& context) return ::android::OK; } +::android::status_t DriverMmapStubImpl::flush() { + RETURN_STATUS_IF_ERROR(DriverStubImpl::flush()); + mDspWorker.pause(); + if (mIsInput) { + RETURN_STATUS_IF_ERROR(standby()); + } + return ::android::OK; +} + ::android::status_t DriverMmapStubImpl::pause() { RETURN_STATUS_IF_ERROR(DriverStubImpl::pause()); mDspWorker.pause(); return ::android::OK; } +::android::status_t DriverMmapStubImpl::standby() { + RETURN_STATUS_IF_ERROR(DriverStubImpl::standby()); + std::lock_guard l(mState.lock); + RETURN_STATUS_IF_ERROR(releaseSharedMemory()); + return ::android::OK; +} + ::android::status_t DriverMmapStubImpl::start() { RETURN_STATUS_IF_ERROR(DriverStubImpl::start()); RETURN_STATUS_IF_ERROR(startWorkerIfNeeded()); @@ -118,7 +134,7 @@ DriverMmapStubImpl::DriverMmapStubImpl(const StreamContext& context) return ::android::OK; } -::android::status_t DriverMmapStubImpl::transfer(void*, size_t, size_t*, int32_t*) { +::android::status_t DriverMmapStubImpl::transfer(void*, size_t frameCount, size_t*, int32_t*) { // Do not call into DriverStubImpl::transfer if (!mIsInitialized) { LOG(FATAL) << __func__ << ": must not happen for an uninitialized driver"; @@ -126,6 +142,10 @@ DriverMmapStubImpl::DriverMmapStubImpl(const StreamContext& context) if (mIsStandby) { LOG(FATAL) << __func__ << ": must not happen while in standby"; } + if (frameCount != 0) { + LOG(ERROR) << __func__ << ": burst value size must be 0 for MMAP"; + return ::android::BAD_VALUE; + } RETURN_STATUS_IF_ERROR(startWorkerIfNeeded()); mDspWorker.resume(); return ::android::OK; @@ -194,7 +214,7 @@ void DriverMmapStubImpl::shutdown() { std::lock_guard l(mState.lock); *position = mState.mmapPos; } - const size_t latencyFrames = mBufferSizeFrames / 2; + const size_t latencyFrames = mBufferSizeFrames / 4; if (position->frames != StreamDescriptor::Position::UNKNOWN) { position->frames += latencyFrames; } @@ -251,7 +271,17 @@ ndk::ScopedAStatus StreamMmapStub::setVendorParameters( return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus StreamMmapStub::createMmapBuffer(MmapBufferDescriptor* desc) { +ndk::ScopedAStatus StreamMmapStub::createMmapBuffer(MmapBufferDescriptor* _aidl_return) { + LOG(DEBUG) << __func__; + if (isClosed()) { + LOG(ERROR) << __func__ << ": stream was closed"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + if (getConnectedDevices().empty()) { + LOG(ERROR) << __func__ << ": stream is not connected"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + const size_t bufferSizeFrames = mContext.getBufferSizeInFrames(); const size_t bufferSizeBytes = static_cast(bufferSizeFrames) * mContext.getFrameSize(); const std::string regionName = @@ -266,11 +296,11 @@ ndk::ScopedAStatus StreamMmapStub::createMmapBuffer(MmapBufferDescriptor* desc) if (initSharedMemory(mSharedMemoryFd.get()) != ::android::OK) { return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } - desc->sharedMemory.fd = mSharedMemoryFd.dup(); - desc->sharedMemory.size = bufferSizeBytes; - desc->burstSizeFrames = bufferSizeFrames / 2; - desc->flags = 0; - LOG(DEBUG) << __func__ << ": " << desc->toString(); + _aidl_return->sharedMemory.fd = mSharedMemoryFd.dup(); + _aidl_return->sharedMemory.size = bufferSizeBytes; + _aidl_return->burstSizeFrames = bufferSizeFrames / 4; + _aidl_return->flags = 1 << MmapBufferDescriptor::FLAG_INDEX_APPLICATION_SHAREABLE; + LOG(DEBUG) << __func__ << ": " << _aidl_return->toString(); return ndk::ScopedAStatus::ok(); } diff --git a/audio/stub/StreamOffloadStub.cpp b/audio/stub/StreamOffloadStub.cpp index 5f5f741..4850d19 100644 --- a/audio/stub/StreamOffloadStub.cpp +++ b/audio/stub/StreamOffloadStub.cpp @@ -15,61 +15,101 @@ */ #define LOG_TAG "AHAL_OffloadStream" -#include +#include #include #include #include #include "ApeHeader.h" +#include "MpegAudioHeader.h" #include "core-impl/StreamOffloadStub.h" using aidl::android::hardware::audio::common::SourceMetadata; using aidl::android::media::audio::common::AudioDevice; +using aidl::android::media::audio::common::AudioFormatType; using aidl::android::media::audio::common::AudioOffloadInfo; +using aidl::android::media::audio::common::FlushFromFrameAccuracy; using aidl::android::media::audio::common::MicrophoneInfo; namespace aidl::android::hardware::audio::core { namespace offload { +static constexpr int32_t SAFE_FLUSH_FROM_MARGIN_IN_MS = 100; +static constexpr int32_t SAFE_DRAIN_METADATA_MARGIN_IN_MS = 10; + std::string DspSimulatorLogic::init() { return ""; } DspSimulatorLogic::Status DspSimulatorLogic::cycle() { + using DrainMode = StreamDescriptor::DrainMode; std::vector> clipNotifies; + DrainMode emptyDrainNotify = DrainMode::DRAIN_UNSPECIFIED; // Simulate playback. const int64_t timeBeginNs = ::android::uptimeNanos(); usleep(1000); const int64_t clipFramesPlayed = (::android::uptimeNanos() - timeBeginNs) * mSharedState.sampleRate / NANOS_PER_SECOND; - const int64_t bufferFramesConsumed = clipFramesPlayed / 2; // assume 1:2 compression ratio - int64_t bufferFramesLeft = 0, bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames; + + int64_t bufferFramesConsumed = 0, bufferFramesLeft = 0, + bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames; + bool isEmpty = false; { std::lock_guard l(mSharedState.lock); + int64_t bytesPerSecond = 0; + + if (mSharedState.format.type == AudioFormatType::PCM) { + bytesPerSecond = mSharedState.sampleRate; + } else { + bytesPerSecond = mSharedState.bitRatePerSecond / 8; + + if (bytesPerSecond <= 0) { + bytesPerSecond = mSharedState.fallbackBitRatePerSecond / 8; + } + if (bytesPerSecond <= 0) { + // Fallback to a 1:2 compression ratio + bytesPerSecond = mSharedState.sampleRate / 2LL; + } + } + + bufferFramesConsumed = clipFramesPlayed * bytesPerSecond / mSharedState.sampleRate; + + if (mSharedState.clips.empty()) { + isEmpty = true; + emptyDrainNotify = mSharedState.draining; + if (mSharedState.draining != DrainMode::DRAIN_EARLY_NOTIFY) { + mSharedState.draining = DrainMode::DRAIN_UNSPECIFIED; + } + } mSharedState.bufferFramesLeft = mSharedState.bufferFramesLeft > bufferFramesConsumed ? mSharedState.bufferFramesLeft - bufferFramesConsumed : 0; int64_t framesPlayed = clipFramesPlayed; - while (framesPlayed > 0 && !mSharedState.clipFramesLeft.empty()) { - LOG(VERBOSE) << __func__ << ": clips: " - << ::android::internal::ToString(mSharedState.clipFramesLeft); - const bool hasNextClip = mSharedState.clipFramesLeft.size() > 1; - if (mSharedState.clipFramesLeft[0] > framesPlayed) { - mSharedState.clipFramesLeft[0] -= framesPlayed; + while (framesPlayed > 0 && !mSharedState.clips.empty()) { + LOG(VERBOSE) << __func__ << ": " << mSharedState.clips.log(); + const bool hasNextClip = mSharedState.clips.hasNext(); + if (mSharedState.clips.currentFrames() > framesPlayed) { + mSharedState.clips.updateCurrentFrames(-framesPlayed); + mSharedState.mTotalFramesPlayed += framesPlayed; framesPlayed = 0; - if (mSharedState.clipFramesLeft[0] <= mSharedState.earlyNotifyFrames) { - clipNotifies.emplace_back(mSharedState.clipFramesLeft[0], hasNextClip); + if (auto clipFramesLeft = mSharedState.clips.currentFrames(); + mSharedState.draining == DrainMode::DRAIN_EARLY_NOTIFY && + clipFramesLeft <= mSharedState.earlyNotifyFrames) { + clipNotifies.emplace_back(clipFramesLeft, hasNextClip); } } else { - clipNotifies.emplace_back(0 /*clipFramesLeft*/, hasNextClip); - framesPlayed -= mSharedState.clipFramesLeft[0]; - mSharedState.clipFramesLeft.erase(mSharedState.clipFramesLeft.begin()); - if (!hasNextClip) { - // Since it's a simulation, the buffer consumption rate it not real, - // thus 'bufferFramesLeft' might still have something, need to erase it. - mSharedState.bufferFramesLeft = 0; + mSharedState.mTotalFramesPlayed += mSharedState.clips.currentFrames(); + if (mSharedState.format.type == AudioFormatType::PCM) { + // There is not enough data to be full played, set `framesPlayed` to 0 so that + // it can exit the while loop. + framesPlayed = 0; + mSharedState.clips.trimCurrentFrames(0); + clipNotifies.emplace_back(0 /*clipFramesLeft*/, false /*hasNextClip*/); + } else { + clipNotifies.emplace_back(0 /*clipFramesLeft*/, hasNextClip); + framesPlayed -= mSharedState.clips.removeCurrent(); } } } @@ -84,10 +124,31 @@ DspSimulatorLogic::Status DspSimulatorLogic::cycle() { LOG(DEBUG) << __func__ << ": sending onBufferStateChange: " << bufferFramesLeft; mSharedState.callback->onBufferStateChange(bufferFramesLeft); } - for (const auto& notify : clipNotifies) { - LOG(DEBUG) << __func__ << ": sending onClipStateChange: " << notify.first << ", " - << notify.second; - mSharedState.callback->onClipStateChange(notify.first, notify.second); + if (!clipNotifies.empty()) { + for (const auto& notify : clipNotifies) { + LOG(DEBUG) << __func__ << ": sending onClipStateChange: " << notify.first << ", " + << notify.second; + mSharedState.callback->onClipStateChange(notify.first, notify.second); + } + } else if (emptyDrainNotify != DrainMode::DRAIN_UNSPECIFIED) { + bool alreadySent; + { + std::lock_guard l(mSharedState.lock); + alreadySent = mSharedState.isDrainCompleteClipStateChangeSent; + if (!alreadySent) mSharedState.isDrainCompleteClipStateChangeSent = true; + } + if (!alreadySent) { + LOG(DEBUG) << __func__ << ": sending onClipStateChange with no clips for " + << toString(emptyDrainNotify); + // Regardless of the drain mode, the buffer is now empty, so we signal completion (0 + // frames). + mSharedState.callback->onClipStateChange(0 /*clipFramesLeft*/, false /*hasNextClip*/); + std::lock_guard l(mSharedState.lock); + mSharedState.draining = DrainMode::DRAIN_UNSPECIFIED; + } + } else if (isEmpty) { + // Sleep when idle to avoid busy-waiting and reduce CPU usage. + usleep(10000); } return Status::CONTINUE; } @@ -96,19 +157,37 @@ DspSimulatorLogic::Status DspSimulatorLogic::cycle() { using offload::DspSimulatorState; -DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context) +DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context, + const std::optional& offloadInfo) : DriverStubImpl(context, 0 /*asyncSleepTimeUs*/), mBufferNotifyFrames(static_cast(context.getBufferSizeInFrames()) / 2), - mState{context.getFormat().encoding, context.getSampleRate(), - 250 /*earlyNotifyMs*/ * context.getSampleRate() / MILLIS_PER_SECOND}, + mSafeMarginForFlushFromFrames(offload::SAFE_FLUSH_FROM_MARGIN_IN_MS * + context.getSampleRate() / MILLIS_PER_SECOND), + mSafeMarginForDrainMetadataFrames(offload::SAFE_DRAIN_METADATA_MARGIN_IN_MS * + context.getSampleRate() / MILLIS_PER_SECOND), + mState{context.getFormat(), context.getSampleRate(), + 250 /*earlyNotifyMs*/ * context.getSampleRate() / MILLIS_PER_SECOND, + offloadInfo.has_value() ? offloadInfo->bitRatePerSecond : 0}, mDspWorker(mState) { LOG_IF(FATAL, !mIsAsynchronous) << "The steam must be used in asynchronous mode"; + + if (mState.format.encoding == "audio/x-ape") { + mTransferHandler = &DriverOffloadStubImpl::handleApeTransfer; + } else if (mState.format.encoding == "audio/mpeg") { + mTransferHandler = &DriverOffloadStubImpl::handleMpegTransfer; + } else if (mState.format.type == AudioFormatType::PCM) { + mTransferHandler = &DriverOffloadStubImpl::handlePcmTransfer; + // For PCM offload, there is no way for the HAL to know where is the end of a clip. + // In that case, it assumes there is only one clip. + LOG_IF(FATAL, !mState.clips.add(0)) << "Cannot initialize for PCM offload"; + } } ::android::status_t DriverOffloadStubImpl::init(DriverCallbackInterface* callback) { RETURN_STATUS_IF_ERROR(DriverStubImpl::init(callback)); - if (!StreamOffloadStub::getSupportedEncodings().count(mState.formatEncoding)) { - LOG(ERROR) << __func__ << ": encoded format \"" << mState.formatEncoding + if (!StreamOffloadStub::getSupportedEncodings().count(mState.format.encoding) && + mState.format.type != AudioFormatType::PCM) { + LOG(ERROR) << __func__ << ": encoded format \"" << mState.format.toString() << "\" is not supported"; return ::android::NO_INIT; } @@ -119,15 +198,15 @@ DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context) ::android::status_t DriverOffloadStubImpl::drain(StreamDescriptor::DrainMode drainMode) { RETURN_STATUS_IF_ERROR(DriverStubImpl::drain(drainMode)); std::lock_guard l(mState.lock); - if (!mState.clipFramesLeft.empty()) { - // Cut playback of the current clip. - mState.clipFramesLeft[0] = std::min(mState.earlyNotifyFrames * 2, mState.clipFramesLeft[0]); + if (!mState.clips.empty()) { + mState.clips.trimCurrentFrames(mState.earlyNotifyFrames * 2); if (drainMode == StreamDescriptor::DrainMode::DRAIN_ALL) { - // Make sure there are no clips after the current one. - mState.clipFramesLeft.resize(1); + mState.clips.eraseAllNext(); } } mState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames; + mState.draining = drainMode; + mState.isDrainCompleteClipStateChangeSent = false; return ::android::OK; } @@ -136,9 +215,11 @@ DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context) mDspWorker.pause(); { std::lock_guard l(mState.lock); - mState.clipFramesLeft.clear(); + mState.clips.erase(); mState.bufferFramesLeft = 0; mState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames; + mState.draining = StreamDescriptor::DrainMode::DRAIN_UNSPECIFIED; + mState.isDrainCompleteClipStateChangeSent = false; } return ::android::OK; } @@ -156,15 +237,20 @@ DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context) ::android::status_t DriverOffloadStubImpl::start() { RETURN_STATUS_IF_ERROR(DriverStubImpl::start()); RETURN_STATUS_IF_ERROR(startWorkerIfNeeded()); - bool hasClips; // Can be start after paused draining. + bool shouldResume; { std::lock_guard l(mState.lock); - hasClips = !mState.clipFramesLeft.empty(); - LOG(DEBUG) << __func__ - << ": clipFramesLeft: " << ::android::internal::ToString(mState.clipFramesLeft); + bool hasClips = !mState.clips.empty(); + bool isDraining = mState.draining != StreamDescriptor::DrainMode::DRAIN_UNSPECIFIED; + // Allow notifications if resuming into a drain with an empty buffer. + if (isDraining && !hasClips) { + mState.isDrainCompleteClipStateChangeSent = false; + } + LOG(DEBUG) << __func__ << ": " << mState.clips.log(); mState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames; + shouldResume = hasClips || isDraining; } - if (hasClips) { + if (shouldResume) { mDspWorker.resume(); } return ::android::OK; @@ -175,6 +261,77 @@ DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context) RETURN_STATUS_IF_ERROR( DriverStubImpl::transfer(buffer, frameCount, actualFrameCount, latencyMs)); RETURN_STATUS_IF_ERROR(startWorkerIfNeeded()); + + if (mTransferHandler != nullptr) { + const ::android::status_t status = + (this->*mTransferHandler)(buffer, frameCount, actualFrameCount); + if (status != ::android::OK) { + return status; + } + } else { + LOG(FATAL) << __func__ << ": unsupported format for offload: " << mState.format.toString(); + *actualFrameCount = 0; + } + + { + std::lock_guard l(mState.lock); + mState.bufferFramesLeft += *actualFrameCount; + mState.bufferNotifyFrames = mBufferNotifyFrames; + } + mDspWorker.resume(); + return ::android::OK; +} + +::android::status_t DriverOffloadStubImpl::flushFromFrame(FlushFromFrameAccuracy accuracy, + int32_t position, + int32_t* flushFromPosition) { + LOG(DEBUG) << __func__ << ": accuracy=" << toString(accuracy) << ", position=" << position; + if ((accuracy != FlushFromFrameAccuracy::BEST_EFFORT && + accuracy != FlushFromFrameAccuracy::EXACT) || + position < 0 || flushFromPosition == nullptr) { + *flushFromPosition = position; + LOG(ERROR) << __func__ << ": invalid parameters, accuracy= " << toString(accuracy) + << ", position=" << position + << ", suggestedPosition is null:" << (flushFromPosition == nullptr); + return ::android::BAD_VALUE; + } + if (mState.format.type != AudioFormatType::PCM) { + // Currently only support flushFromFrame for PCM offload. + LOG(ERROR) << __func__ << ": invalid as format is " << mState.format.toString(); + return ::android::INVALID_OPERATION; + } + { + std::lock_guard l(mState.lock); + const int64_t requestedPosition = position + mState.mLastReportedFrames; + const int64_t safeFlushedPosition = + mState.mTotalFramesPlayed + + std::min((int64_t)mSafeMarginForFlushFromFrames, mState.clips.currentFrames()); + const int64_t totalWrittenFrames = mState.mTotalFramesPlayed + mState.clips.currentFrames(); + if (accuracy == FlushFromFrameAccuracy::BEST_EFFORT) { + if (requestedPosition < safeFlushedPosition) { + *flushFromPosition = safeFlushedPosition - mState.mLastReportedFrames; + mState.clips.trimCurrentFrames(safeFlushedPosition - mState.mTotalFramesPlayed); + } else if (requestedPosition > totalWrittenFrames) { + *flushFromPosition = totalWrittenFrames - mState.mLastReportedFrames; + } else { + *flushFromPosition = requestedPosition - mState.mLastReportedFrames; + mState.clips.trimCurrentFrames(requestedPosition - mState.mTotalFramesPlayed); + } + } else { // accuracy == FlushFromFrameAccuracy::EXACT + if (requestedPosition < safeFlushedPosition || requestedPosition > totalWrittenFrames) { + *flushFromPosition = safeFlushedPosition - mState.mLastReportedFrames; + return ::android::BAD_VALUE; + } else { + *flushFromPosition = requestedPosition - mState.mLastReportedFrames; + mState.clips.trimCurrentFrames(requestedPosition - mState.mTotalFramesPlayed); + } + } + } + return ::android::OK; +} + +::android::status_t DriverOffloadStubImpl::handleApeTransfer(void* buffer, size_t frameCount, + size_t* actualFrameCount) { // Scan the buffer for clip headers. *actualFrameCount = frameCount; while (buffer != nullptr && frameCount > 0) { @@ -195,21 +352,141 @@ DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context) << "sample rate: " << clipSampleRate; if (clipSampleRate == mState.sampleRate) { std::lock_guard l(mState.lock); - mState.clipFramesLeft.push_back(clipDurationFrames); + if (!mState.clips.add(clipDurationFrames)) { + LOG(ERROR) << __func__ + << ": not ready for the next clip, clips: " << mState.clips.log(); + return ::android::INVALID_OPERATION; + } } else { LOG(ERROR) << __func__ << ": clip sample rate " << clipSampleRate << " does not match stream sample rate " << mState.sampleRate; + return ::android::BAD_VALUE; } } else { frameCount = 0; } } - { - std::lock_guard l(mState.lock); - mState.bufferFramesLeft = *actualFrameCount; - mState.bufferNotifyFrames = mBufferNotifyFrames; + return ::android::OK; +} + +::android::status_t DriverOffloadStubImpl::handleMpegTransfer(void* buffer, size_t frameCount, + size_t* actualFrameCount) { + const size_t bufferSizeBytes = frameCount * mFrameSizeBytes; + const uint8_t* currentPtr = static_cast(buffer); + const uint8_t* const endPtr = currentPtr + bufferSizeBytes; + const uint8_t* const beginPtr = currentPtr; + + // Track total audio payload bytes to distinguish from metadata. + size_t totalAudioBytes = 0; + + if (mMpegFrameState.bytesPending > 0) { + size_t processBytes = + std::min(mMpegFrameState.bytesPending, static_cast(endPtr - currentPtr)); + currentPtr += processBytes; + mMpegFrameState.bytesPending -= processBytes; + if (mMpegFrameState.isAudioFrame) { + totalAudioBytes += processBytes; + // Incremental Timing: Update duration based on ratio of bytes processed to fix time + // jump. + if (mMpegFrameState.totalFrameLengthBytes > 0) { + std::lock_guard l(mState.lock); + const int64_t processedFrames = mMpegFrameState.frameSize * (int64_t)processBytes / + mMpegFrameState.totalFrameLengthBytes; + mState.clips.updateLastFrames(processedFrames); + LOG(DEBUG) << __func__ << ": added samples=" << processedFrames; + } + } } - mDspWorker.resume(); + while (currentPtr < endPtr) { + const uint8_t* const frameBeginning = currentPtr; + std::optional frameOpt = findMpegFrame(¤tPtr, endPtr); + if (!frameOpt.has_value()) { + // Could not find a header in the input buffer. + mMpegFrameState.bytesPending = 0; + break; + } + + const MpegFrame& frame = frameOpt.value(); + LOG(DEBUG) << __func__ << ": Found at offset " << frameBeginning - beginPtr << " " << frame; + mMpegFrameState.isAudioFrame = !(frame.isID3v2 || frame.isID3v1); + if (frame.isID3v1) { + mMpegFrameState.clipEnded = true; + } else { + if (frame.sampleRate != mState.sampleRate) { + LOG(ERROR) << __func__ << ": clip sample rate " << frame.sampleRate + << " does not match stream sample rate " << mState.sampleRate; + return ::android::BAD_VALUE; + } + + // Calculate physical bytes consumed in this iteration + size_t bytesInThisBuffer = currentPtr - frameBeginning; + if (mMpegFrameState.isAudioFrame) { + totalAudioBytes += bytesInThisBuffer; + } + + std::lock_guard l(mState.lock); + if (frame.bitRate > 0) mState.bitRatePerSecond = frame.bitRate; + if (frame.isID3v2 || mState.clips.empty() || mMpegFrameState.clipEnded) { + if (!mState.clips.add(0)) { + LOG(ERROR) << __func__ + << ": not ready for the next clip, clips: " << mState.clips.log(); + return ::android::INVALID_OPERATION; + } + mMpegFrameState.clipEnded = false; + } + // Proportional timing fix: satisfy PauseAsync by adding time for bytes delivered only. + if (mMpegFrameState.isAudioFrame && frame.frameLengthBytes > 0) { + const int64_t processedFrames = + frame.frameSize * (int64_t)bytesInThisBuffer / frame.frameLengthBytes; + mState.clips.updateLastFrames(processedFrames); + LOG(DEBUG) << __func__ << ": added samples=" << processedFrames; + } + } + if (currentPtr == endPtr) { + mMpegFrameState.bytesPending = frame.bytesPending; + mMpegFrameState.frameSize = frame.frameSize; + mMpegFrameState.totalFrameLengthBytes = frame.frameLengthBytes; + break; + } + } + *actualFrameCount = static_cast(currentPtr - beginPtr) / mFrameSizeBytes; + + // If totalAudioBytes > 0, we have mixed content, so we subtract metadata bytes. + // If totalAudioBytes == 0 (Header Only), we SKIP subtraction to keep buffer non-empty. + // The fallback bitrate in cycle() ensures it eventually drains. + if (*actualFrameCount > totalAudioBytes) { + size_t nonAudioBytes = *actualFrameCount - totalAudioBytes; + if (totalAudioBytes > 0) { + std::lock_guard l(mState.lock); + + // Calculate the anticipated buffer size once the caller adds the frames. + int64_t projectedBufferLevel = mState.bufferFramesLeft + totalAudioBytes; + + // Ensure the buffer level does not dip below mBufferNotifyFrames after subtraction. + // An instant drop triggers the worker thread to fire onTransferReady prematurely... + const int64_t safeThreshold = mBufferNotifyFrames + mSafeMarginForDrainMetadataFrames; + + if (projectedBufferLevel < safeThreshold) { + // Subtract only enough to perfectly land on the safeThreshold + int64_t allowedSubtract = + (mState.bufferFramesLeft + *actualFrameCount) - safeThreshold; + if (allowedSubtract > 0) { + mState.bufferFramesLeft -= allowedSubtract; + } + } else { + // Safe to subtract all nonAudioBytes without triggering the race condition + mState.bufferFramesLeft -= nonAudioBytes; + } + } + } + return ::android::OK; +} + +::android::status_t DriverOffloadStubImpl::handlePcmTransfer(void* /*buffer*/, size_t frameCount, + size_t* actualFrameCount) { + *actualFrameCount = frameCount; + std::lock_guard l(mState.lock); + mState.clips.updateLastFrames(frameCount); return ::android::OK; } @@ -230,16 +507,24 @@ void DriverOffloadStubImpl::shutdown() { return ::android::OK; } +::android::status_t DriverOffloadStubImpl::refinePosition(StreamDescriptor::Position* position) { + std::lock_guard l(mState.lock); + position->frames = mState.mTotalFramesPlayed; + return ::android::OK; +} + // static const std::set& StreamOffloadStub::getSupportedEncodings() { static const std::set kSupportedEncodings = { "audio/x-ape", + "audio/mpeg", }; return kSupportedEncodings; } -StreamOffloadStub::StreamOffloadStub(StreamContext* context, const Metadata& metadata) - : StreamCommonImpl(context, metadata), DriverOffloadStubImpl(getContext()) {} +StreamOffloadStub::StreamOffloadStub(StreamContext* context, const Metadata& metadata, + const AudioOffloadInfo& offloadInfo) + : StreamCommonImpl(context, metadata), DriverOffloadStubImpl(getContext(), offloadInfo) {} StreamOffloadStub::~StreamOffloadStub() { cleanupWorker(); @@ -249,6 +534,6 @@ StreamOutOffloadStub::StreamOutOffloadStub(StreamContext&& context, const SourceMetadata& sourceMetadata, const std::optional& offloadInfo) : StreamOut(std::move(context), offloadInfo), - StreamOffloadStub(&mContextInstance, sourceMetadata) {} + StreamOffloadStub(&mContextInstance, sourceMetadata, offloadInfo.value()) {} } // namespace aidl::android::hardware::audio::core diff --git a/audio/stub/StreamStub.cpp b/audio/stub/StreamStub.cpp index 2278880..208c55a 100644 --- a/audio/stub/StreamStub.cpp +++ b/audio/stub/StreamStub.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "AHAL_Stream" -#include +#include #include "core-impl/Module.h" #include "core-impl/StreamStub.h" @@ -43,4 +43,19 @@ StreamOutStub::StreamOutStub(StreamContext&& context, const SourceMetadata& sour const std::optional& offloadInfo) : StreamOut(std::move(context), offloadInfo), StreamStub(&mContextInstance, sourceMetadata) {} +StreamOutTelephonyStub::StreamOutTelephonyStub(StreamContext&& context, + const SourceMetadata& sourceMetadata, + const std::optional& offloadInfo) + : StreamOutStub(std::move(context), sourceMetadata, offloadInfo), + StreamOutHwVolumeHelper(&mContext) {} + +ndk::ScopedAStatus StreamOutTelephonyStub::getHwVolume(std::vector* _aidl_return) { + return getHwVolumeImpl(_aidl_return); +} + +ndk::ScopedAStatus StreamOutTelephonyStub::setHwVolume( + const std::vector& in_channelVolumes) { + return setHwVolumeImpl(in_channelVolumes); +} + } // namespace aidl::android::hardware::audio::core diff --git a/audio/usb/ModuleUsb.cpp b/audio/usb/ModuleUsb.cpp index 1d97bc4..97906a5 100644 --- a/audio/usb/ModuleUsb.cpp +++ b/audio/usb/ModuleUsb.cpp @@ -17,11 +17,11 @@ #include #define LOG_TAG "AHAL_ModuleUsb" +#include #include -#include #include "UsbAlsaMixerControl.h" -#include "alsa/Utils.h" +#include "alsa/UtilsAlsa.h" #include "core-impl/ModuleUsb.h" #include "core-impl/StreamUsb.h" diff --git a/audio/usb/StreamUsb.cpp b/audio/usb/StreamUsb.cpp index 9b10432..abfe011 100644 --- a/audio/usb/StreamUsb.cpp +++ b/audio/usb/StreamUsb.cpp @@ -17,7 +17,7 @@ #include #define LOG_TAG "AHAL_StreamUsb" -#include +#include #include #include "UsbAlsaMixerControl.h" diff --git a/audio/usb/UsbAlsaMixerControl.cpp b/audio/usb/UsbAlsaMixerControl.cpp index 0a49446..44f72b0 100644 --- a/audio/usb/UsbAlsaMixerControl.cpp +++ b/audio/usb/UsbAlsaMixerControl.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "AHAL_UsbAlsaMixerControl" -#include +#include #include diff --git a/device.mk b/device.mk index 5aaf7fa..35d24a3 100644 --- a/device.mk +++ b/device.mk @@ -26,7 +26,7 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/updatable_apex.mk) OVERRIDE_PRODUCT_COMPRESSED_APEX := false # API level -PRODUCT_SHIPPING_API_LEVEL := 36 +PRODUCT_SHIPPING_API_LEVEL := 37 # Audio PRODUCT_PACKAGES += \ diff --git a/framework_compatibility_matrix.xml b/framework_compatibility_matrix.xml index 5c0dda4..37be084 100644 --- a/framework_compatibility_matrix.xml +++ b/framework_compatibility_matrix.xml @@ -1,4 +1,4 @@ - + android.hardware.media.c2 1 diff --git a/health/Android.bp b/health/Android.bp index 2291c77..13d167f 100644 --- a/health/Android.bp +++ b/health/Android.bp @@ -17,7 +17,7 @@ cc_binary { "libhealthloop", ], shared_libs: [ - "android.hardware.health-V4-ndk", + "android.hardware.health-V5-ndk", "libbase", "libbinder_ndk", "libcutils", diff --git a/health/HealthImpl.cpp b/health/HealthImpl.cpp index 44c7077..0c22305 100644 --- a/health/HealthImpl.cpp +++ b/health/HealthImpl.cpp @@ -76,6 +76,9 @@ ndk::ScopedAStatus HealthImpl::getBatteryHealthData(BatteryHealthData* out) { out->batterySerialNumber = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; out->batteryPartStatus = BatteryPartStatus::ORIGINAL; + out->batteryManufacturer = "Raspberry Pi"; + out->batteryModelName = "Raspberry Pi fake battery"; + out->batteryVoltageMinDesignUv = 5000000; return ndk::ScopedAStatus::ok(); } diff --git a/health/android.hardware.health-service.rpi.xml b/health/android.hardware.health-service.rpi.xml index 8ddfbda..14828d4 100644 --- a/health/android.hardware.health-service.rpi.xml +++ b/health/android.hardware.health-service.rpi.xml @@ -1,7 +1,7 @@ android.hardware.health - 4 + 5 IHealth/default diff --git a/keylayout/Generic-tv.kl b/keylayout/Generic-tv.kl index 8394cd7..0ac0640 100644 --- a/keylayout/Generic-tv.kl +++ b/keylayout/Generic-tv.kl @@ -139,7 +139,7 @@ key 116 POWER key 117 NUMPAD_EQUALS # key 118 "KEY_KPPLUSMINUS" key 119 BREAK -# key 120 (undefined) +key 120 RECENT_APPS key 121 NUMPAD_COMMA key 122 KANA key 123 EISU @@ -171,7 +171,7 @@ key 143 WAKEUP # key 149 "KEY_PROG2" key 150 EXPLORER # key 151 "KEY_MSDOS" -key 152 POWER +key 152 LOCK # key 153 "KEY_DIRECTION" # key 154 "KEY_CYCLEWINDOWS" key 155 ENVELOPE @@ -200,20 +200,20 @@ key 177 PAGE_UP key 178 PAGE_DOWN key 179 NUMPAD_LEFT_PAREN key 180 NUMPAD_RIGHT_PAREN -# key 181 "KEY_NEW" +key 181 NEW # key 182 "KEY_REDO" -# key 183 F13 -# key 184 F14 -# key 185 F15 -# key 186 F16 -# key 187 F17 -# key 188 F18 -# key 189 F19 -# key 190 F20 -# key 191 F21 -# key 192 F22 -# key 193 F23 -# key 194 F24 +key 183 F13 +key 184 F14 +key 185 F15 +key 186 F16 +key 187 F17 +key 188 F18 +key 189 F19 +key 190 F20 +key 191 F21 +key 192 F22 +key 193 F23 +key 194 F24 # key 195 (undefined) # key 196 (undefined) # key 197 (undefined) @@ -223,13 +223,13 @@ key 200 MEDIA_PLAY key 201 MEDIA_PAUSE # key 202 "KEY_PROG3" # key 203 "KEY_PROG4" -key 204 NOTIFICATION +key 204 NOTIFICATION # key 205 "KEY_SUSPEND" -# key 206 "KEY_CLOSE" +key 206 CLOSE key 207 MEDIA_PLAY key 208 MEDIA_FAST_FORWARD # key 209 "KEY_BASSBOOST" -# key 210 "KEY_PRINT" +key 210 PRINT # key 211 "KEY_HP" key 212 CAMERA key 213 MUSIC @@ -246,6 +246,10 @@ key 217 ASSIST key 224 BRIGHTNESS_DOWN key 225 BRIGHTNESS_UP key 226 HEADSETHOOK +key 228 KEYBOARD_BACKLIGHT_TOGGLE +key 229 KEYBOARD_BACKLIGHT_DOWN +key 230 KEYBOARD_BACKLIGHT_UP +key 248 MUTE key 256 BUTTON_1 key 257 BUTTON_2 @@ -299,6 +303,11 @@ key 317 BUTTON_THUMBL key 318 BUTTON_THUMBR +key 329 STYLUS_BUTTON_TERTIARY +key 331 STYLUS_BUTTON_PRIMARY +key 332 STYLUS_BUTTON_SECONDARY + + # key 352 "KEY_OK" key 353 DPAD_CENTER # key 354 "KEY_GOTO" @@ -315,11 +324,11 @@ key 362 GUIDE # key 365 "KEY_EPG" key 366 DVR # key 367 "KEY_MHP" -# key 368 "KEY_LANGUAGE" +key 368 LANGUAGE_SWITCH # key 369 "KEY_TITLE" key 370 CAPTIONS # key 371 "KEY_ANGLE" -# key 372 "KEY_ZOOM" +key 372 FULLSCREEN # key 373 "KEY_MODE" # key 374 "KEY_KEYBOARD" # key 375 "KEY_SCREEN" @@ -363,6 +372,11 @@ key 405 LAST_CHANNEL # key 413 "KEY_DIGITS" # key 414 "KEY_TEEN" # key 415 "KEY_TWEN" +# key 418 "KEY_ZOOM_IN" +key 418 ZOOM_IN +# key 419 "KEY_ZOOM_OUT" +key 419 ZOOM_OUT +key 528 FOCUS key 429 CONTACTS @@ -408,6 +422,15 @@ key 522 STAR key 523 POUND key 580 APP_SWITCH key 582 VOICE_ASSIST +# Linux KEY_ASSISTANT +key 583 ASSIST +key 585 EMOJI_PICKER +key 586 DICTATE +key 591 DO_NOT_DISTURB +key 656 MACRO_1 +key 657 MACRO_2 +key 658 MACRO_3 +key 659 MACRO_4 # CEC key 352 ENTER @@ -416,10 +439,29 @@ key 141 POWER key 174 BACK # Keys defined by HID usages -key usage 0x0c0067 WINDOW -key usage 0x0c006F BRIGHTNESS_UP -key usage 0x0c0070 BRIGHTNESS_DOWN -key usage 0x0c0173 MEDIA_AUDIO_TRACK +key usage 0x0c0065 SCREENSHOT FALLBACK_USAGE_MAPPING +key usage 0x0c0067 WINDOW FALLBACK_USAGE_MAPPING +key usage 0x0c006F BRIGHTNESS_UP FALLBACK_USAGE_MAPPING +key usage 0x0c0070 BRIGHTNESS_DOWN FALLBACK_USAGE_MAPPING +key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP FALLBACK_USAGE_MAPPING +key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN FALLBACK_USAGE_MAPPING +key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING +key usage 0x0c00D8 DICTATE FALLBACK_USAGE_MAPPING +key usage 0x0c00D9 EMOJI_PICKER FALLBACK_USAGE_MAPPING +key usage 0x0c0173 MEDIA_AUDIO_TRACK FALLBACK_USAGE_MAPPING +key usage 0x0c019C PROFILE_SWITCH FALLBACK_USAGE_MAPPING +key usage 0x0c019F SETTINGS FALLBACK_USAGE_MAPPING +key usage 0x0c01A2 ALL_APPS FALLBACK_USAGE_MAPPING +key usage 0x0c0201 NEW FALLBACK_USAGE_MAPPING +key usage 0x0c0203 CLOSE FALLBACK_USAGE_MAPPING +key usage 0x0c0208 PRINT FALLBACK_USAGE_MAPPING +key usage 0x0c0227 REFRESH FALLBACK_USAGE_MAPPING +key usage 0x0c0232 FULLSCREEN FALLBACK_USAGE_MAPPING +key usage 0x0c029D LANGUAGE_SWITCH FALLBACK_USAGE_MAPPING +key usage 0x0c029F RECENT_APPS FALLBACK_USAGE_MAPPING +key usage 0x0c02A2 ALL_APPS FALLBACK_USAGE_MAPPING +key usage 0x0d0044 STYLUS_BUTTON_PRIMARY FALLBACK_USAGE_MAPPING +key usage 0x0d005a STYLUS_BUTTON_SECONDARY FALLBACK_USAGE_MAPPING # Joystick and game controller axes. # Axes that are not mapped will be assigned generic axis numbers by the input subsystem. @@ -432,8 +474,8 @@ axis 0x05 RZ axis 0x06 THROTTLE axis 0x07 RUDDER axis 0x08 WHEEL -axis 0x09 GAS -axis 0x0a BRAKE +axis 0x09 RTRIGGER +axis 0x0a LTRIGGER axis 0x10 HAT_X axis 0x11 HAT_Y diff --git a/keylayout/Generic.kl b/keylayout/Generic.kl index 697fd1f..4e80c92 100644 --- a/keylayout/Generic.kl +++ b/keylayout/Generic.kl @@ -426,6 +426,7 @@ key 582 VOICE_ASSIST key 583 ASSIST key 585 EMOJI_PICKER key 586 DICTATE +key 590 ACCESSIBILITY key 591 DO_NOT_DISTURB key 656 MACRO_1 key 657 MACRO_2 @@ -439,6 +440,7 @@ key 141 POWER key 174 BACK # Keys defined by HID usages +key usage 0x0100AA ACCESSIBILITY FALLBACK_USAGE_MAPPING key usage 0x0c0065 SCREENSHOT FALLBACK_USAGE_MAPPING key usage 0x0c0067 WINDOW FALLBACK_USAGE_MAPPING key usage 0x0c006F BRIGHTNESS_UP FALLBACK_USAGE_MAPPING @@ -452,6 +454,7 @@ key usage 0x0c0173 MEDIA_AUDIO_TRACK FALLBACK_USAGE_MAPPING key usage 0x0c019C PROFILE_SWITCH FALLBACK_USAGE_MAPPING key usage 0x0c019F SETTINGS FALLBACK_USAGE_MAPPING key usage 0x0c01A2 ALL_APPS FALLBACK_USAGE_MAPPING +key usage 0x0c01CD CONTEXTUAL_INSERT FALLBACK_USAGE_MAPPING key usage 0x0c0201 NEW FALLBACK_USAGE_MAPPING key usage 0x0c0203 CLOSE FALLBACK_USAGE_MAPPING key usage 0x0c0208 PRINT FALLBACK_USAGE_MAPPING diff --git a/manifest.xml b/manifest.xml index f849d9d..9a2997a 100644 --- a/manifest.xml +++ b/manifest.xml @@ -1 +1 @@ - + diff --git a/mkimg.sh b/mkimg.sh index daa25c7..cc41bf3 100755 --- a/mkimg.sh +++ b/mkimg.sh @@ -25,7 +25,7 @@ for PARTITION in "boot" "system" "system_ext" "vendor"; do fi done -VERSION=RaspberryVanillaAOSP16 +VERSION=RaspberryVanillaAOSP17 DATE=$(date +%Y%m%d) TARGET=$(echo ${TARGET_PRODUCT} | sed 's/^aosp_//') IMGNAME=${VERSION}-${DATE}-${TARGET}.img diff --git a/pawlet_rpi4.mk b/pawlet_rpi4.mk index 3e98055..3e229d7 100644 --- a/pawlet_rpi4.mk +++ b/pawlet_rpi4.mk @@ -16,7 +16,7 @@ PRODUCT_CHARACTERISTICS := nosdcard # Base config $(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk) -$(call enforce-product-packages-exist,com.android.ranging) +$(call enforce-product-packages-exist,com.android.ranging vendor_tracing_descriptors) # Keylayout PRODUCT_PACKAGES += \ diff --git a/pawlet_rpi4_car.mk b/pawlet_rpi4_car.mk index d579fd1..a4cc481 100644 --- a/pawlet_rpi4_car.mk +++ b/pawlet_rpi4_car.mk @@ -17,7 +17,7 @@ PRODUCT_CHARACTERISTICS := automotive,nosdcard # Base config $(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk) $(call inherit-product, packages/services/Car/car_product/build/car.mk) -$(call enforce-product-packages-exist,Bluetooth Keyguard Launcher2 OverviewApp RotaryIME RotaryPlayground com.android.ranging display_compat_config libnfc_ndef libvariablespeed pppd) +$(call enforce-product-packages-exist,Bluetooth Keyguard Launcher2 OverviewApp RotaryIME RotaryPlayground com.android.ranging libnfc_ndef libvariablespeed pppd vendor_tracing_descriptors) # android.car PRODUCT_PACKAGES += \ diff --git a/pawlet_rpi4_tv.mk b/pawlet_rpi4_tv.mk index dc6e3c3..fb82042 100644 --- a/pawlet_rpi4_tv.mk +++ b/pawlet_rpi4_tv.mk @@ -15,7 +15,7 @@ PRODUCT_CHARACTERISTICS := tv # Base config $(call inherit-product, device/google/atv/products/atv_base.mk) -$(call enforce-product-packages-exist,com.android.ranging) +$(call enforce-product-packages-exist,com.android.ranging vendor_tracing_descriptors) # Android TV PRODUCT_PACKAGES += \