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 621d200b3e..9c52d19da5 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(); } } }