From 84bcc049e695ea1fb60170f10a3a5753453cd61f Mon Sep 17 00:00:00 2001 From: Mikhail Naganov Date: Thu, 5 Oct 2023 17:36:57 -0700 Subject: [PATCH] audio: Allow "dynamic" profiles for device ports Some device ports are connected via ADSP which takes care of the actual audio configuration (format, channels, SR), for example the built-in speaker and mic ports, as well as some external devices like analog headsets. In the legacy implementation, such device ports did not have any profiles specified. Allow the same behavior in the AIDL implementation. To ensure correctness, device ports with no profiles must be routable to mix ports that have profiles specified. This requirement is fulfilled in legacy configs. Bug: 266124463 Test: atest VtsHalAudioCoreTargetTest Test: atest audiosystem_tests audiorouting_tests Test: atest CtsMediaAudioTestCases Change-Id: Iaccd1e8ef2a5af9a5f8bae453905d01c6b7fdc28 --- audio/aidl/default/Configuration.cpp | 106 ++++---- audio/aidl/default/Module.cpp | 257 ++++++++++++------ .../default/bluetooth/ModuleBluetooth.cpp | 59 +++- audio/aidl/default/include/core-impl/Module.h | 3 + .../include/core-impl/ModuleBluetooth.h | 4 +- .../default/r_submix/ModuleRemoteSubmix.cpp | 13 +- audio/aidl/vts/ModuleConfig.cpp | 16 ++ audio/aidl/vts/ModuleConfig.h | 3 + .../vts/VtsHalAudioCoreModuleTargetTest.cpp | 24 +- 9 files changed, 335 insertions(+), 150 deletions(-) diff --git a/audio/aidl/default/Configuration.cpp b/audio/aidl/default/Configuration.cpp index 635a25b2c6..3c3dadb112 100644 --- a/audio/aidl/default/Configuration.cpp +++ b/audio/aidl/default/Configuration.cpp @@ -105,15 +105,11 @@ static AudioPort createPort(int32_t id, const std::string& name, int32_t flags, return port; } -static AudioPortConfig createPortConfig(int32_t id, int32_t portId, PcmType pcmType, int32_t layout, - int32_t sampleRate, int32_t flags, bool isInput, - const AudioPortExt& ext) { +static AudioPortConfig createDynamicPortConfig(int32_t id, int32_t portId, int32_t flags, + bool isInput, const AudioPortExt& ext) { AudioPortConfig config; config.id = id; config.portId = portId; - config.sampleRate = Int{.value = sampleRate}; - config.channelMask = AudioChannelLayout::make(layout); - config.format = AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType}; config.gain = AudioGainConfig(); config.flags = isInput ? AudioIoFlags::make(flags) : AudioIoFlags::make(flags); @@ -121,6 +117,16 @@ static AudioPortConfig createPortConfig(int32_t id, int32_t portId, PcmType pcmT return config; } +static AudioPortConfig createPortConfig(int32_t id, int32_t portId, PcmType pcmType, int32_t layout, + int32_t sampleRate, int32_t flags, bool isInput, + const AudioPortExt& ext) { + AudioPortConfig config = createDynamicPortConfig(id, portId, flags, isInput, ext); + config.sampleRate = Int{.value = sampleRate}; + config.channelMask = AudioChannelLayout::make(layout); + config.format = AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType}; + return config; +} + static AudioRoute createRoute(const std::vector& sources, const AudioPort& sink) { AudioRoute route; route.sinkPortId = sink.id; @@ -147,8 +153,7 @@ static AudioRoute createRoute(const std::vector& sources, const Audio // * "primary output", PRIMARY, 1 max open, 1 max active stream // - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 // * "primary input", 1 max open, 1 max active stream -// - profile PCM 16-bit; MONO, STEREO; -// 8000, 11025, 16000, 32000, 44100, 48000 +// - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 // * "telephony_tx", 1 max open, 1 max active stream // - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 // * "telephony_rx", 1 max open, 1 max active stream @@ -164,11 +169,11 @@ static AudioRoute createRoute(const std::vector& sources, const Audio // "FM Tuner" -> "fm_tuner" // // Initial port configs: -// * "Speaker" device port: PCM 16-bit; STEREO; 48000 -// * "Built-In Mic" device port: PCM 16-bit; MONO; 48000 -// * "Telephony Tx" device port: PCM 16-bit; MONO; 48000 -// * "Telephony Rx" device port: PCM 16-bit; MONO; 48000 -// * "FM Tuner" device port: PCM 16-bit; STEREO; 48000 +// * "Speaker" device port: dynamic configuration +// * "Built-In Mic" device port: dynamic configuration +// * "Telephony Tx" device port: dynamic configuration +// * "Telephony Rx" device port: dynamic configuration +// * "FM Tuner" device port: dynamic configuration // std::unique_ptr getPrimaryConfiguration() { static const Configuration configuration = []() { @@ -186,9 +191,8 @@ std::unique_ptr getPrimaryConfiguration() { 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE)); c.ports.push_back(speakerOutDevice); c.initialConfigs.push_back( - createPortConfig(speakerOutDevice.id, speakerOutDevice.id, PcmType::INT_16_BIT, - AudioChannelLayout::LAYOUT_STEREO, 48000, 0, false, - createDeviceExt(AudioDeviceType::OUT_SPEAKER, 0))); + createDynamicPortConfig(speakerOutDevice.id, speakerOutDevice.id, 0, false, + createDeviceExt(AudioDeviceType::OUT_SPEAKER, 0))); AudioPort micInDevice = createPort(c.nextPortId++, "Built-In Mic", 0, true, @@ -196,35 +200,31 @@ std::unique_ptr getPrimaryConfiguration() { 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE)); c.ports.push_back(micInDevice); c.initialConfigs.push_back( - createPortConfig(micInDevice.id, micInDevice.id, PcmType::INT_16_BIT, - AudioChannelLayout::LAYOUT_MONO, 48000, 0, true, - createDeviceExt(AudioDeviceType::IN_MICROPHONE, 0))); + createDynamicPortConfig(micInDevice.id, micInDevice.id, 0, true, + createDeviceExt(AudioDeviceType::IN_MICROPHONE, 0))); AudioPort telephonyTxOutDevice = createPort(c.nextPortId++, "Telephony Tx", 0, false, createDeviceExt(AudioDeviceType::OUT_TELEPHONY_TX, 0)); c.ports.push_back(telephonyTxOutDevice); c.initialConfigs.push_back( - createPortConfig(telephonyTxOutDevice.id, telephonyTxOutDevice.id, - PcmType::INT_16_BIT, AudioChannelLayout::LAYOUT_MONO, 48000, 0, - false, createDeviceExt(AudioDeviceType::OUT_TELEPHONY_TX, 0))); + createDynamicPortConfig(telephonyTxOutDevice.id, telephonyTxOutDevice.id, 0, false, + createDeviceExt(AudioDeviceType::OUT_TELEPHONY_TX, 0))); AudioPort telephonyRxInDevice = createPort(c.nextPortId++, "Telephony Rx", 0, true, createDeviceExt(AudioDeviceType::IN_TELEPHONY_RX, 0)); c.ports.push_back(telephonyRxInDevice); c.initialConfigs.push_back( - createPortConfig(telephonyRxInDevice.id, telephonyRxInDevice.id, - PcmType::INT_16_BIT, AudioChannelLayout::LAYOUT_MONO, 48000, 0, - true, createDeviceExt(AudioDeviceType::IN_TELEPHONY_RX, 0))); + createDynamicPortConfig(telephonyRxInDevice.id, telephonyRxInDevice.id, 0, true, + createDeviceExt(AudioDeviceType::IN_TELEPHONY_RX, 0))); AudioPort fmTunerInDevice = createPort(c.nextPortId++, "FM Tuner", 0, true, createDeviceExt(AudioDeviceType::IN_FM_TUNER, 0)); c.ports.push_back(fmTunerInDevice); c.initialConfigs.push_back( - createPortConfig(fmTunerInDevice.id, fmTunerInDevice.id, PcmType::INT_16_BIT, - AudioChannelLayout::LAYOUT_STEREO, 48000, 0, true, - createDeviceExt(AudioDeviceType::IN_FM_TUNER, 0))); + createDynamicPortConfig(fmTunerInDevice.id, fmTunerInDevice.id, 0, true, + createDeviceExt(AudioDeviceType::IN_FM_TUNER, 0))); // Mix ports @@ -287,13 +287,15 @@ std::unique_ptr getPrimaryConfiguration() { // Note: When transitioning to loading of XML configs, either keep the configuration // of the remote submix sources from this static configuration, or update the XML -// config to match it. There are two reasons for that: -// 1. The canonical r_submix configuration only lists 'STEREO' and '48000', +// config to match it. There are several reasons for that: +// 1. The "Remote Submix In" device is listed in the XML config as "attached", +// however in the AIDL scheme its device type has a "virtual" connection. +// 2. The canonical r_submix configuration only lists 'STEREO' and '48000', // however the framework attempts to open streams for other sample rates // as well. The legacy r_submix implementation allowed that, but libaudiohal@aidl // will not find a mix port to use. Because of that, list all channel // masks and sample rates that the legacy implementation allowed. -// 2. The legacy implementation had a hard limit on the number of routes (10), +// 3. The legacy implementation had a hard limit on the number of routes (10), // and this is checked indirectly by AudioPlaybackCaptureTest#testPlaybackCaptureDoS // CTS test. Instead of hardcoding the number of routes, we can use // "maxOpen/ActiveStreamCount" to enforce a similar limit. However, the canonical @@ -331,15 +333,15 @@ std::unique_ptr getRSubmixConfiguration() { createPort(c.nextPortId++, "Remote Submix Out", 0, false, createDeviceExt(AudioDeviceType::OUT_SUBMIX, 0, AudioDeviceDescription::CONNECTION_VIRTUAL)); - rsubmixOutDevice.profiles = standardPcmAudioProfiles; c.ports.push_back(rsubmixOutDevice); + c.connectedProfiles[rsubmixOutDevice.id] = standardPcmAudioProfiles; AudioPort rsubmixInDevice = createPort(c.nextPortId++, "Remote Submix In", 0, true, createDeviceExt(AudioDeviceType::IN_SUBMIX, 0, AudioDeviceDescription::CONNECTION_VIRTUAL)); - rsubmixInDevice.profiles = standardPcmAudioProfiles; c.ports.push_back(rsubmixInDevice); + c.connectedProfiles[rsubmixInDevice.id] = standardPcmAudioProfiles; // Mix ports @@ -384,7 +386,7 @@ std::unique_ptr getRSubmixConfiguration() { // * "usb_device output" -> "USB Headset Out" // * "USB Device In", "USB Headset In" -> "usb_device input" // -// Profiles for device port connected state: +// Profiles for device port connected state (when simulating connections): // * "USB Device Out", "USB Headset Out": // - profile PCM 16-bit; MONO, STEREO, INDEX_MASK_1, INDEX_MASK_2; 44100, 48000 // - profile PCM 24-bit; MONO, STEREO, INDEX_MASK_1, INDEX_MASK_2; 44100, 48000 @@ -461,9 +463,9 @@ std::unique_ptr getUsbConfiguration() { // * "Test In", IN_AFE_PROXY // - no profiles specified // * "Wired Headset", OUT_HEADSET -// - profile PCM 24-bit; STEREO; 48000 +// - no profiles specified // * "Wired Headset Mic", IN_HEADSET -// - profile PCM 24-bit; MONO; 48000 +// - no profiles specified // // Mix ports: // * "test output", 1 max open, 1 max active stream @@ -486,6 +488,10 @@ std::unique_ptr getUsbConfiguration() { // * "Test Out" device port: PCM 24-bit; STEREO; 48000 // * "Test In" device port: PCM 24-bit; MONO; 48000 // +// Profiles for device port connected state (when simulating connections): +// * "Wired Headset": dynamic profiles +// * "Wired Headset Mic": dynamic profiles +// std::unique_ptr getStubConfiguration() { static const Configuration configuration = []() { Configuration c; @@ -504,8 +510,6 @@ std::unique_ptr getStubConfiguration() { createPort(c.nextPortId++, "Wired Headset", 0, false, createDeviceExt(AudioDeviceType::OUT_HEADSET, 0, AudioDeviceDescription::CONNECTION_ANALOG)); - headsetOutDevice.profiles.push_back( - createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); c.ports.push_back(headsetOutDevice); AudioPort testInDevice = createPort(c.nextPortId++, "Test In", 0, true, @@ -520,8 +524,6 @@ std::unique_ptr getStubConfiguration() { createPort(c.nextPortId++, "Wired Headset Mic", 0, true, createDeviceExt(AudioDeviceType::IN_HEADSET, 0, AudioDeviceDescription::CONNECTION_ANALOG)); - headsetInDevice.profiles.push_back( - createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_MONO}, {48000})); c.ports.push_back(headsetInDevice); // Mix ports @@ -553,24 +555,24 @@ std::unique_ptr getStubConfiguration() { {44100, 48000})); c.ports.push_back(compressedOffloadOutMix); - AudioPort testInMIx = + AudioPort testInMix = createPort(c.nextPortId++, "test input", 0, true, createPortMixExt(2, 2)); - testInMIx.profiles.push_back( + testInMix.profiles.push_back( createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO, AudioChannelLayout::LAYOUT_FRONT_BACK}, {8000, 11025, 16000, 22050, 32000, 44100, 48000})); - testInMIx.profiles.push_back( + testInMix.profiles.push_back( createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO, AudioChannelLayout::LAYOUT_FRONT_BACK}, {8000, 11025, 16000, 22050, 32000, 44100, 48000})); - c.ports.push_back(testInMIx); + c.ports.push_back(testInMix); c.routes.push_back( createRoute({testOutMix, testFastOutMix, compressedOffloadOutMix}, testOutDevice)); c.routes.push_back(createRoute({testOutMix}, headsetOutDevice)); - c.routes.push_back(createRoute({testInDevice, headsetInDevice}, testInMIx)); + c.routes.push_back(createRoute({testInDevice, headsetInDevice}, testInMix)); c.portConfigs.insert(c.portConfigs.end(), c.initialConfigs.begin(), c.initialConfigs.end()); @@ -603,11 +605,19 @@ std::unique_ptr getStubConfiguration() { // "a2dp output" -> "BT A2DP Speaker" // "hearing aid output" -> "BT Hearing Aid Out" // +// 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 +// std::unique_ptr getBluetoothConfiguration() { static const Configuration configuration = []() { const std::vector standardPcmAudioProfiles = { createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {44100, 48000, 88200, 96000})}; + const std::vector hearingAidAudioProfiles = {createProfile( + PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000, 24000})}; Configuration c; // Device ports @@ -645,8 +655,7 @@ std::unique_ptr getBluetoothConfiguration() { createDeviceExt(AudioDeviceType::OUT_HEARING_AID, 0, AudioDeviceDescription::CONNECTION_WIRELESS)); c.ports.push_back(btOutHearingAid); - c.connectedProfiles[btOutHearingAid.id] = std::vector( - {createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000})}); + c.connectedProfiles[btOutHearingAid.id] = hearingAidAudioProfiles; // Mix ports AudioPort btOutMix = @@ -655,8 +664,7 @@ std::unique_ptr getBluetoothConfiguration() { AudioPort btHearingOutMix = createPort(c.nextPortId++, "hearing aid output", 0, false, createPortMixExt(1, 1)); - btHearingOutMix.profiles.push_back(createProfile( - PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000, 24000})); + btHearingOutMix.profiles = hearingAidAudioProfiles; c.ports.push_back(btHearingOutMix); c.routes.push_back(createRoute({btOutMix}, btOutDevice)); diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp index 76132b38fe..d721b32d83 100644 --- a/audio/aidl/default/Module.cpp +++ b/audio/aidl/default/Module.cpp @@ -65,32 +65,56 @@ namespace aidl::android::hardware::audio::core { namespace { +inline bool hasDynamicChannelMasks(const std::vector& channelMasks) { + return channelMasks.empty() || + std::all_of(channelMasks.begin(), channelMasks.end(), + [](const auto& channelMask) { return channelMask == AudioChannelLayout{}; }); +} + +inline bool hasDynamicFormat(const AudioFormatDescription& format) { + return format == AudioFormatDescription{}; +} + +inline bool hasDynamicSampleRates(const std::vector& sampleRates) { + return sampleRates.empty() || + std::all_of(sampleRates.begin(), sampleRates.end(), + [](const auto& sampleRate) { return sampleRate == 0; }); +} + +inline bool isDynamicProfile(const AudioProfile& profile) { + return hasDynamicFormat(profile.format) || hasDynamicChannelMasks(profile.channelMasks) || + hasDynamicSampleRates(profile.sampleRates); +} + +bool hasDynamicProfilesOnly(const std::vector& profiles) { + if (profiles.empty()) return true; + return std::all_of(profiles.begin(), profiles.end(), isDynamicProfile); +} + +// Note: does not assign an ID to the config. bool generateDefaultPortConfig(const AudioPort& port, AudioPortConfig* config) { + const bool allowDynamicConfig = port.ext.getTag() == AudioPortExt::device; *config = {}; config->portId = port.id; - if (port.profiles.empty()) { - LOG(ERROR) << __func__ << ": port " << port.id << " has no profiles"; - return false; + for (const auto& profile : port.profiles) { + if (isDynamicProfile(profile)) continue; + config->format = profile.format; + config->channelMask = *profile.channelMasks.begin(); + config->sampleRate = Int{.value = *profile.sampleRates.begin()}; + config->flags = port.flags; + config->ext = port.ext; + return true; } - const auto& profile = port.profiles.begin(); - config->format = profile->format; - if (profile->channelMasks.empty()) { - LOG(ERROR) << __func__ << ": the first profile in port " << port.id - << " has no channel masks"; - return false; + if (allowDynamicConfig) { + config->format = AudioFormatDescription{}; + config->channelMask = AudioChannelLayout{}; + config->sampleRate = Int{.value = 0}; + config->flags = port.flags; + config->ext = port.ext; + return true; } - config->channelMask = *profile->channelMasks.begin(); - if (profile->sampleRates.empty()) { - LOG(ERROR) << __func__ << ": the first profile in port " << port.id - << " has no sample rates"; - return false; - } - Int sampleRate; - sampleRate.value = *profile->sampleRates.begin(); - config->sampleRate = sampleRate; - config->flags = port.flags; - config->ext = port.ext; - return true; + LOG(ERROR) << __func__ << ": port " << port.id << " only has dynamic profiles"; + return false; } bool findAudioProfile(const AudioPort& port, const AudioFormatDescription& format, @@ -314,6 +338,18 @@ std::unique_ptr Module::initializeConfig() { return config; } +std::vector Module::getAudioRoutesForAudioPortImpl(int32_t portId) { + std::vector result; + auto& routes = getConfig().routes; + for (auto& r : routes) { + const auto& srcs = r.sourcePortIds; + if (r.sinkPortId == portId || std::find(srcs.begin(), srcs.end(), portId) != srcs.end()) { + result.push_back(&r); + } + } + return result; +} + internal::Configuration& Module::getConfig() { if (!mConfig) { mConfig = std::move(initializeConfig()); @@ -321,6 +357,24 @@ internal::Configuration& Module::getConfig() { return *mConfig; } +std::set Module::getRoutableAudioPortIds(int32_t portId, + std::vector* routes) { + std::vector routesStorage; + if (routes == nullptr) { + routesStorage = getAudioRoutesForAudioPortImpl(portId); + routes = &routesStorage; + } + std::set result; + for (AudioRoute* r : *routes) { + if (r->sinkPortId == portId) { + result.insert(r->sourcePortIds.begin(), r->sourcePortIds.end()); + } else { + result.insert(r->sinkPortId); + } + } + return result; +} + void Module::registerPatch(const AudioPatch& patch) { auto& configs = getConfig().portConfigs; auto do_insert = [&](const std::vector& portConfigIds) { @@ -510,7 +564,31 @@ ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdA } } - if (connectedPort.profiles.empty()) { + // Two main cases are considered with regard to the profiles of the connected device port: + // + // 1. If the template device port has dynamic profiles, and at least one routable mix + // port also has dynamic profiles, it means that after connecting the device, the + // connected device port must have profiles populated with actual capabilities of + // the connected device, and dynamic of routable mix ports will be filled + // according to these capabilities. An example of this case is connection of an + // HDMI or USB device. For USB handled by ADSP, there can be mix ports with static + // profiles, and one dedicated mix port for "hi-fi" playback. The latter is left with + // dynamic profiles so that they can be populated with actual capabilities of + // the connected device. + // + // 2. If the template device port has dynamic profiles, while all routable mix ports + // have static profiles, it means that after connecting the device, the connected + // device port can be left with dynamic profiles, and profiles of mix ports are + // left untouched. An example of this case is connection of an analog wired + // headset, it should be treated in the same way as a speaker. + // + // Yet another possible case is when both the template device port and all routable + // mix ports have static profiles. This is allowed and handled correctly, however, it + // is not very practical, since these profiles are likely duplicates of each other. + + std::vector routesToMixPorts = getAudioRoutesForAudioPortImpl(templateId); + std::set routableMixPortIds = getRoutableAudioPortIds(templateId, &routesToMixPorts); + if (hasDynamicProfilesOnly(connectedPort.profiles)) { if (!mDebug.simulateDeviceConnections) { RETURN_STATUS_IF_ERROR(populateConnectedDevicePort(&connectedPort)); } else { @@ -520,23 +598,22 @@ ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdA connectedPort.profiles = connectedProfilesIt->second; } } - if (connectedPort.profiles.empty()) { - LOG(ERROR) << __func__ - << ": profiles of a connected port still empty after connecting external " - "device " - << connectedPort.toString(); - return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); - } - } - - for (auto profile : connectedPort.profiles) { - if (profile.channelMasks.empty()) { - LOG(ERROR) << __func__ << ": the profile " << profile.name << " has no channel masks"; - return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); - } - if (profile.sampleRates.empty()) { - LOG(ERROR) << __func__ << ": the profile " << profile.name << " has no sample rates"; - return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + if (hasDynamicProfilesOnly(connectedPort.profiles)) { + // Possible case 2. Check if all routable mix ports have static profiles. + if (auto dynamicMixPortIt = std::find_if(ports.begin(), ports.end(), + [&routableMixPortIds](const auto& p) { + return routableMixPortIds.count(p.id) > + 0 && + hasDynamicProfilesOnly(p.profiles); + }); + dynamicMixPortIt != ports.end()) { + LOG(ERROR) << __func__ + << ": connected port only has dynamic profiles after connecting " + << "external device " << connectedPort.toString() << ", and there exist " + << "a routable mix port with dynamic profiles: " + << dynamicMixPortIt->toString(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } } } @@ -548,44 +625,36 @@ ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdA ports.push_back(connectedPort); onExternalDeviceConnectionChanged(connectedPort, true /*connected*/); - std::vector routablePortIds; + // For routes where the template port is a source, add the connected port to sources, + // otherwise, create a new route by copying from the route for the template port. std::vector newRoutes; - auto& routes = getConfig().routes; - for (auto& r : routes) { - if (r.sinkPortId == templateId) { - AudioRoute newRoute; - newRoute.sourcePortIds = r.sourcePortIds; - newRoute.sinkPortId = connectedPort.id; - newRoute.isExclusive = r.isExclusive; - newRoutes.push_back(std::move(newRoute)); - routablePortIds.insert(routablePortIds.end(), r.sourcePortIds.begin(), - r.sourcePortIds.end()); + for (AudioRoute* r : routesToMixPorts) { + if (r->sinkPortId == templateId) { + newRoutes.push_back(AudioRoute{.sourcePortIds = r->sourcePortIds, + .sinkPortId = connectedPort.id, + .isExclusive = r->isExclusive}); } else { - auto& srcs = r.sourcePortIds; - if (std::find(srcs.begin(), srcs.end(), templateId) != srcs.end()) { - srcs.push_back(connectedPort.id); - routablePortIds.push_back(r.sinkPortId); - } + r->sourcePortIds.push_back(connectedPort.id); } } + auto& routes = getConfig().routes; routes.insert(routes.end(), newRoutes.begin(), newRoutes.end()); - // Note: this is a simplistic approach assuming that a mix port can only be populated - // from a single device port. Implementing support for stuffing dynamic profiles with a superset - // of all profiles from all routable dynamic device ports would be more involved. - for (const auto mixPortId : routablePortIds) { - auto portsIt = findById(ports, mixPortId); - if (portsIt != ports.end()) { - if (portsIt->profiles.empty()) { - portsIt->profiles = connectedPort.profiles; - connectedPortsIt->second.insert(portsIt->id); + if (!hasDynamicProfilesOnly(connectedPort.profiles) && !routableMixPortIds.empty()) { + // Note: this is a simplistic approach assuming that a mix port can only be populated + // from a single device port. Implementing support for stuffing dynamic profiles with + // a superset of all profiles from all routable dynamic device ports would be more involved. + for (auto& port : ports) { + if (routableMixPortIds.count(port.id) == 0) continue; + if (hasDynamicProfilesOnly(port.profiles)) { + port.profiles = connectedPort.profiles; + connectedPortsIt->second.insert(port.id); } else { - // Check if profiles are non empty because they were populated by - // a previous connection. Otherwise, it means that they are not empty because - // the mix port has static profiles. - for (const auto cp : mConnectedDevicePorts) { - if (cp.second.count(portsIt->id) > 0) { - connectedPortsIt->second.insert(portsIt->id); + // Check if profiles are not all dynamic because they were populated by + // a previous connection. Otherwise, it means that they are actually static. + for (const auto& cp : mConnectedDevicePorts) { + if (cp.second.count(port.id) > 0) { + connectedPortsIt->second.insert(port.id); break; } } @@ -705,13 +774,9 @@ ndk::ScopedAStatus Module::getAudioRoutesForAudioPort(int32_t in_portId, LOG(ERROR) << __func__ << ": port id " << in_portId << " not found"; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } - auto& routes = getConfig().routes; - std::copy_if(routes.begin(), routes.end(), std::back_inserter(*_aidl_return), - [&](const auto& r) { - const auto& srcs = r.sourcePortIds; - return r.sinkPortId == in_portId || - std::find(srcs.begin(), srcs.end(), in_portId) != srcs.end(); - }); + std::vector routes = getAudioRoutesForAudioPortImpl(in_portId); + std::transform(routes.begin(), routes.end(), std::back_inserter(*_aidl_return), + [](auto rptr) { return *rptr; }); return ndk::ScopedAStatus::ok(); } @@ -925,13 +990,14 @@ ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requeste const int portId = existing != configs.end() ? existing->portId : in_requested.portId; if (portId == 0) { - LOG(ERROR) << __func__ << ": input port config does not specify portId"; + LOG(ERROR) << __func__ << ": requested port config does not specify portId"; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } auto& ports = getConfig().ports; auto portIt = findById(ports, portId); if (portIt == ports.end()) { - LOG(ERROR) << __func__ << ": input port config points to non-existent portId " << portId; + LOG(ERROR) << __func__ << ": requested port config points to non-existent portId " + << portId; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } if (existing != configs.end()) { @@ -949,6 +1015,10 @@ ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requeste // or a new generated config. Now attempt to update it according to the specified // fields of 'in_requested'. + // Device ports with only dynamic profiles are used for devices that are connected via ADSP, + // which takes care of their actual configuration automatically. + const bool allowDynamicConfig = portIt->ext.getTag() == AudioPortExt::device && + hasDynamicProfilesOnly(portIt->profiles); bool requestedIsValid = true, requestedIsFullySpecified = true; AudioIoFlags portFlags = portIt->flags; @@ -966,17 +1036,19 @@ ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requeste AudioProfile portProfile; if (in_requested.format.has_value()) { const auto& format = in_requested.format.value(); - if (findAudioProfile(*portIt, format, &portProfile)) { + if ((format == AudioFormatDescription{} && allowDynamicConfig) || + findAudioProfile(*portIt, format, &portProfile)) { out_suggested->format = format; } else { LOG(WARNING) << __func__ << ": requested format " << format.toString() - << " is not found in port's " << portId << " profiles"; + << " is not found in the profiles of port " << portId; requestedIsValid = false; } } else { requestedIsFullySpecified = false; } - if (!findAudioProfile(*portIt, out_suggested->format.value(), &portProfile)) { + if (!(out_suggested->format.value() == AudioFormatDescription{} && allowDynamicConfig) && + !findAudioProfile(*portIt, out_suggested->format.value(), &portProfile)) { LOG(ERROR) << __func__ << ": port " << portId << " does not support format " << out_suggested->format.value().toString() << " anymore"; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); @@ -984,8 +1056,9 @@ ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requeste if (in_requested.channelMask.has_value()) { const auto& channelMask = in_requested.channelMask.value(); - if (find(portProfile.channelMasks.begin(), portProfile.channelMasks.end(), channelMask) != - portProfile.channelMasks.end()) { + if ((channelMask == AudioChannelLayout{} && allowDynamicConfig) || + find(portProfile.channelMasks.begin(), portProfile.channelMasks.end(), channelMask) != + portProfile.channelMasks.end()) { out_suggested->channelMask = channelMask; } else { LOG(WARNING) << __func__ << ": requested channel mask " << channelMask.toString() @@ -999,7 +1072,8 @@ ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requeste if (in_requested.sampleRate.has_value()) { const auto& sampleRate = in_requested.sampleRate.value(); - if (find(portProfile.sampleRates.begin(), portProfile.sampleRates.end(), + if ((sampleRate.value == 0 && allowDynamicConfig) || + find(portProfile.sampleRates.begin(), portProfile.sampleRates.end(), sampleRate.value) != portProfile.sampleRates.end()) { out_suggested->sampleRate = sampleRate; } else { @@ -1397,7 +1471,18 @@ bool Module::isMmapSupported() { return mIsMmapSupported.value(); } -ndk::ScopedAStatus Module::populateConnectedDevicePort(AudioPort* audioPort __unused) { +ndk::ScopedAStatus Module::populateConnectedDevicePort(AudioPort* audioPort) { + 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(); + if (!devicePort.device.type.connection.empty()) { + LOG(ERROR) << __func__ + << ": module implementation must override 'populateConnectedDevicePort' " + << "to handle connection of external devices."; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } LOG(VERBOSE) << __func__ << ": do nothing and return ok"; return ndk::ScopedAStatus::ok(); } diff --git a/audio/aidl/default/bluetooth/ModuleBluetooth.cpp b/audio/aidl/default/bluetooth/ModuleBluetooth.cpp index bfe7ca0384..3c33207d2c 100644 --- a/audio/aidl/default/bluetooth/ModuleBluetooth.cpp +++ b/audio/aidl/default/bluetooth/ModuleBluetooth.cpp @@ -18,15 +18,23 @@ #include +#include "BluetoothAudioSessionControl.h" #include "core-impl/ModuleBluetooth.h" #include "core-impl/StreamBluetooth.h" namespace aidl::android::hardware::audio::core { -using aidl::android::hardware::audio::common::SinkMetadata; -using aidl::android::hardware::audio::common::SourceMetadata; -using aidl::android::media::audio::common::AudioOffloadInfo; -using aidl::android::media::audio::common::MicrophoneInfo; +using ::aidl::android::hardware::audio::common::SinkMetadata; +using ::aidl::android::hardware::audio::common::SourceMetadata; +using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession; +using ::aidl::android::media::audio::common::AudioDeviceDescription; +using ::aidl::android::media::audio::common::AudioDeviceType; +using ::aidl::android::media::audio::common::AudioOffloadInfo; +using ::aidl::android::media::audio::common::AudioPort; +using ::aidl::android::media::audio::common::AudioPortExt; +using ::aidl::android::media::audio::common::MicrophoneInfo; +using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidl; +using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut; ndk::ScopedAStatus ModuleBluetooth::getBluetoothA2dp( std::shared_ptr* _aidl_return) { @@ -80,6 +88,49 @@ ndk::ScopedAStatus ModuleBluetooth::createOutputStream( offloadInfo, getBtProfileManagerHandles()); } +ndk::ScopedAStatus ModuleBluetooth::populateConnectedDevicePort(AudioPort* audioPort) { + 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; + // Since the configuration of the BT module is static, there is nothing to populate here. + // However, this method must return an error when the device can not be connected, + // this is determined by the status of BT profiles. + if (description.connection == AudioDeviceDescription::CONNECTION_BT_A2DP) { + bool isA2dpEnabled = false; + if (!!mBluetoothA2dp) { + RETURN_STATUS_IF_ERROR(mBluetoothA2dp.getInstance()->isEnabled(&isA2dpEnabled)); + } + return isA2dpEnabled ? ndk::ScopedAStatus::ok() + : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } else if (description.connection == AudioDeviceDescription::CONNECTION_BT_LE) { + bool isLeEnabled = false; + if (!!mBluetoothLe) { + RETURN_STATUS_IF_ERROR(mBluetoothLe.getInstance()->isEnabled(&isLeEnabled)); + } + return isLeEnabled ? ndk::ScopedAStatus::ok() + : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } else if (description.connection == AudioDeviceDescription::CONNECTION_WIRELESS && + description.type == AudioDeviceType::OUT_HEARING_AID) { + // Hearing aids can use a number of profiles, thus the only way to check + // connectivity is to try to talk to the BT HAL. + if (!BluetoothAudioSession::IsAidlAvailable()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + std::shared_ptr proxy = std::shared_ptr( + std::make_shared()); + if (proxy->registerPort(description)) { + proxy->unregisterPort(); + return ndk::ScopedAStatus::ok(); + } + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + LOG(ERROR) << __func__ << ": unsupported device type: " << audioPort->toString(); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); +} + ndk::ScopedAStatus ModuleBluetooth::onMasterMuteChanged(bool) { LOG(DEBUG) << __func__ << ": is not supported"; return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h index bfdab51454..da94815722 100644 --- a/audio/aidl/default/include/core-impl/Module.h +++ b/audio/aidl/default/include/core-impl/Module.h @@ -202,6 +202,7 @@ class Module : public BnModule { std::set findConnectedPortConfigIds(int32_t portConfigId); ndk::ScopedAStatus findPortIdForNewStream( int32_t in_portConfigId, ::aidl::android::media::audio::common::AudioPort** port); + std::vector getAudioRoutesForAudioPortImpl(int32_t portId); virtual BtProfileHandles getBtProfileManagerHandles(); internal::Configuration& getConfig(); const ConnectedDevicePorts& getConnectedDevicePorts() const { return mConnectedDevicePorts; } @@ -209,6 +210,8 @@ class Module : public BnModule { bool getMasterVolume() const { return mMasterVolume; } bool getMicMute() const { return mMicMute; } const Patches& getPatches() const { return mPatches; } + std::set getRoutableAudioPortIds(int32_t portId, + std::vector* routes = nullptr); const Streams& getStreams() const { return mStreams; } Type getType() const { return mType; } bool isMmapSupported(); diff --git a/audio/aidl/default/include/core-impl/ModuleBluetooth.h b/audio/aidl/default/include/core-impl/ModuleBluetooth.h index 68b4e6b058..526a809fdf 100644 --- a/audio/aidl/default/include/core-impl/ModuleBluetooth.h +++ b/audio/aidl/default/include/core-impl/ModuleBluetooth.h @@ -44,6 +44,8 @@ class ModuleBluetooth final : public Module { 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) override; ndk::ScopedAStatus onMasterMuteChanged(bool mute) override; ndk::ScopedAStatus onMasterVolumeChanged(float volume) override; @@ -51,4 +53,4 @@ class ModuleBluetooth final : public Module { ChildInterface mBluetoothLe; }; -} // namespace aidl::android::hardware::audio::core \ No newline at end of file +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp b/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp index adea877be6..f8c775f863 100644 --- a/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp +++ b/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp @@ -59,23 +59,22 @@ ndk::ScopedAStatus ModuleRemoteSubmix::createOutputStream( ndk::ScopedAStatus ModuleRemoteSubmix::populateConnectedDevicePort(AudioPort* audioPort) { // Find the corresponding mix port and copy its profiles. - std::vector routes; // At this moment, the port has the same ID as the template port, see connectExternalDevice. - RETURN_STATUS_IF_ERROR(getAudioRoutesForAudioPort(audioPort->id, &routes)); + std::vector routes = getAudioRoutesForAudioPortImpl(audioPort->id); if (routes.empty()) { LOG(ERROR) << __func__ << ": no routes found for the port " << audioPort->toString(); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } const auto& route = *routes.begin(); AudioPort mixPort; - if (route.sinkPortId == audioPort->id) { - if (route.sourcePortIds.empty()) { - LOG(ERROR) << __func__ << ": invalid route " << route.toString(); + if (route->sinkPortId == audioPort->id) { + if (route->sourcePortIds.empty()) { + LOG(ERROR) << __func__ << ": invalid route " << route->toString(); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } - RETURN_STATUS_IF_ERROR(getAudioPort(*route.sourcePortIds.begin(), &mixPort)); + RETURN_STATUS_IF_ERROR(getAudioPort(*route->sourcePortIds.begin(), &mixPort)); } else { - RETURN_STATUS_IF_ERROR(getAudioPort(route.sinkPortId, &mixPort)); + RETURN_STATUS_IF_ERROR(getAudioPort(route->sinkPortId, &mixPort)); } audioPort->profiles = mixPort.profiles; return ndk::ScopedAStatus::ok(); diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp index af3597d164..8f19547e45 100644 --- a/audio/aidl/vts/ModuleConfig.cpp +++ b/audio/aidl/vts/ModuleConfig.cpp @@ -281,6 +281,22 @@ std::optional ModuleConfig::getSourceMixPortForConnectedDevice() cons return {}; } +std::vector ModuleConfig::getRoutableMixPortsForDevicePort(const AudioPort& port) const { + std::set portIds; + for (const auto& route : mRoutes) { + if (port.id == route.sinkPortId) { + portIds.insert(route.sourcePortIds.begin(), route.sourcePortIds.end()); + } else if (auto it = std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(), + port.id); + it != route.sourcePortIds.end()) { + portIds.insert(route.sinkPortId); + } + } + const bool isInput = port.flags.getTag() == AudioIoFlags::input; + return findMixPorts(isInput, false /*connectedOnly*/, false /*singlePort*/, + [&portIds](const AudioPort& p) { return portIds.count(p.id) > 0; }); +} + std::optional ModuleConfig::getNonRoutableSrcSinkPair( bool isInput) const { const auto mixPorts = getMixPorts(isInput, false /*connectedOnly*/); diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h index 0cbf24ddfc..b89adc0dfd 100644 --- a/audio/aidl/vts/ModuleConfig.h +++ b/audio/aidl/vts/ModuleConfig.h @@ -103,6 +103,9 @@ class ModuleConfig { std::optional getSourceMixPortForConnectedDevice() const; + std::vector getRoutableMixPortsForDevicePort( + const aidl::android::media::audio::common::AudioPort& port) const; + std::optional getNonRoutableSrcSinkPair(bool isInput) const; std::optional getRoutableSrcSinkPair(bool isInput) const; std::vector getRoutableSrcSinkGroups(bool isInput) const; diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp index 04c827b226..ec084cf03b 100644 --- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp +++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp @@ -1501,8 +1501,25 @@ TEST_P(AudioCoreModule, GetAudioPortWithExternalDevices) { << "port ID " << connectedPortId; EXPECT_EQ(portConnected.get(), connectedPort); const auto& portProfiles = connectedPort.profiles; - EXPECT_NE(0UL, portProfiles.size()) - << "Connected port has no profiles: " << connectedPort.toString(); + if (portProfiles.empty()) { + const auto routableMixPorts = + moduleConfig->getRoutableMixPortsForDevicePort(connectedPort); + bool hasMixPortWithStaticProfile = false; + for (const auto& mixPort : routableMixPorts) { + const auto& mixPortProfiles = mixPort.profiles; + if (!mixPortProfiles.empty() && + !std::all_of(mixPortProfiles.begin(), mixPortProfiles.end(), + [](const auto& profile) { + return profile.format.type == AudioFormatType::DEFAULT; + })) { + hasMixPortWithStaticProfile = true; + break; + } + } + EXPECT_TRUE(hasMixPortWithStaticProfile) + << "Connected port has no profiles and no routable mix ports with profiles: " + << connectedPort.toString(); + } const auto dynamicProfileIt = std::find_if(portProfiles.begin(), portProfiles.end(), [](const auto& profile) { return profile.format.type == AudioFormatType::DEFAULT; @@ -1586,7 +1603,8 @@ TEST_P(AudioCoreModule, ResetAudioPortConfigToInitialValue) { EXPECT_NE(portConfigsAfter.end(), afterIt) << " port config ID " << c.id << " was removed by reset"; if (afterIt != portConfigsAfter.end()) { - EXPECT_EQ(c, *afterIt); + EXPECT_TRUE(c == *afterIt) + << "Expected: " << c.toString() << "; Actual: " << afterIt->toString(); } } }