From 975ea3ae8959b1d9502290ebd5c71fba68645c64 Mon Sep 17 00:00:00 2001 From: Mikhail Naganov Date: Fri, 17 Jun 2022 21:41:19 +0000 Subject: [PATCH] audio: Add support for compressed offload - Add compressed offload mix port into default implementation. - Require AudioOffloadInfo to be passed to IModule.openOutputStream for compressed offload port configs. - Update VTS to handle compressed offload. Bug: 205884982 Test: atest VtsHalAudioCoreTargetTest Change-Id: I118b2c04bff12b64a7cac4dc2c88217a6a270046 --- .../android/hardware/audio/core/IModule.aidl | 2 + audio/aidl/default/Android.bp | 2 + audio/aidl/default/Configuration.cpp | 49 ++++++-- audio/aidl/default/Module.cpp | 8 ++ audio/aidl/vts/ModuleConfig.cpp | 109 ++++++++---------- audio/aidl/vts/ModuleConfig.h | 38 +++--- audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp | 51 ++++++-- 7 files changed, 161 insertions(+), 98 deletions(-) diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl index f406cd8ee0..802cb2f151 100644 --- a/audio/aidl/android/hardware/audio/core/IModule.aidl +++ b/audio/aidl/android/hardware/audio/core/IModule.aidl @@ -282,6 +282,8 @@ interface IModule { * @throws EX_ILLEGAL_ARGUMENT In the following cases: * - If the port config can not be found by the ID. * - If the port config is not of an output mix port. + * - If the offload info is not provided for an offload + * port configuration. * @throws EX_ILLEGAL_STATE In the following cases: * - If the port config already has a stream opened on it. * - If the limit on the open stream count for the port has diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp index 4728a89cdd..ad1d9c761a 100644 --- a/audio/aidl/default/Android.bp +++ b/audio/aidl/default/Android.bp @@ -13,6 +13,7 @@ cc_library_static { shared_libs: [ "libbase", "libbinder_ndk", + "libstagefright_foundation", "android.media.audio.common.types-V1-ndk", "android.hardware.audio.core-V1-ndk", ], @@ -37,6 +38,7 @@ cc_binary { shared_libs: [ "libbase", "libbinder_ndk", + "libstagefright_foundation", "android.media.audio.common.types-V1-ndk", "android.hardware.audio.core-V1-ndk", ], diff --git a/audio/aidl/default/Configuration.cpp b/audio/aidl/default/Configuration.cpp index 19d0b3c93f..f5d679bd3e 100644 --- a/audio/aidl/default/Configuration.cpp +++ b/audio/aidl/default/Configuration.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "core-impl/Configuration.h" @@ -42,16 +43,30 @@ using aidl::android::media::audio::common::PcmType; namespace aidl::android::hardware::audio::core::internal { +static void fillProfile(AudioProfile* profile, const std::vector& channelLayouts, + const std::vector& sampleRates) { + for (auto layout : channelLayouts) { + profile->channelMasks.push_back( + AudioChannelLayout::make(layout)); + } + profile->sampleRates.insert(profile->sampleRates.end(), sampleRates.begin(), sampleRates.end()); +} + static AudioProfile createProfile(PcmType pcmType, const std::vector& channelLayouts, const std::vector& sampleRates) { AudioProfile profile; profile.format.type = AudioFormatType::PCM; profile.format.pcm = pcmType; - for (auto layout : channelLayouts) { - profile.channelMasks.push_back( - AudioChannelLayout::make(layout)); - } - profile.sampleRates.insert(profile.sampleRates.end(), sampleRates.begin(), sampleRates.end()); + fillProfile(&profile, channelLayouts, sampleRates); + return profile; +} + +static AudioProfile createProfile(const std::string& encodingType, + const std::vector& channelLayouts, + const std::vector& sampleRates) { + AudioProfile profile; + profile.format.encoding = encodingType; + fillProfile(&profile, channelLayouts, sampleRates); return profile; } @@ -125,6 +140,8 @@ static AudioRoute createRoute(const std::vector& sources, int32_t sink) // * "primary output", PRIMARY, 1 max open, 1 max active stream // - profile PCM 16-bit; MONO, STEREO; 44100, 48000 // - profile PCM 24-bit; MONO, STEREO; 44100, 48000 +// * "compressed offload", DIRECT|COMPRESS_OFFLOAD|NON_BLOCKING, 1 max open, 1 max active stream +// - profile MP3; MONO, STEREO; 44100, 48000 // * "loopback output", stream count unlimited // - profile PCM 24-bit; STEREO; 48000 // * "primary input", 2 max open, 2 max active streams @@ -136,8 +153,8 @@ static AudioRoute createRoute(const std::vector& sources, int32_t sink) // - profile PCM 24-bit; STEREO; 48000 // // Routes: -// "primary out" -> "Null" -// "primary out" -> "USB Out" +// "primary out", "compressed offload" -> "Null" +// "primary out", "compressed offload" -> "USB Out" // "loopback out" -> "Loopback Out" // "Zero", "USB In" -> "primary input" // "Loopback In" -> "loopback input" @@ -183,6 +200,18 @@ Configuration& getNullPrimaryConfiguration() { standardPcmAudioProfiles.end()); c.ports.push_back(primaryOutMix); + AudioPort compressedOffloadOutMix = + createPort(c.nextPortId++, "compressed offload", + 1 << static_cast(AudioOutputFlags::DIRECT) | + 1 << static_cast(AudioOutputFlags::COMPRESS_OFFLOAD) | + 1 << static_cast(AudioOutputFlags::NON_BLOCKING), + false, createPortMixExt(1, 1)); + compressedOffloadOutMix.profiles.push_back( + createProfile(::android::MEDIA_MIMETYPE_AUDIO_MPEG, + {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO}, + {44100, 48000})); + c.ports.push_back(compressedOffloadOutMix); + AudioPort loopOutDevice = createPort(c.nextPortId++, "Loopback Out", 0, false, createDeviceExt(AudioDeviceType::OUT_SUBMIX, 0)); loopOutDevice.profiles.push_back( @@ -244,8 +273,10 @@ Configuration& getNullPrimaryConfiguration() { c.ports.push_back(usbInDevice); c.connectedProfiles[usbInDevice.id] = standardPcmAudioProfiles; - c.routes.push_back(createRoute({primaryOutMix.id}, nullOutDevice.id)); - c.routes.push_back(createRoute({primaryOutMix.id}, usbOutDevice.id)); + c.routes.push_back( + createRoute({primaryOutMix.id, compressedOffloadOutMix.id}, nullOutDevice.id)); + c.routes.push_back( + createRoute({primaryOutMix.id, compressedOffloadOutMix.id}, usbOutDevice.id)); c.routes.push_back(createRoute({loopOutMix.id}, loopOutDevice.id)); c.routes.push_back(createRoute({zeroInDevice.id, usbInDevice.id}, primaryInMix.id)); c.routes.push_back(createRoute({loopInDevice.id}, loopInMix.id)); diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp index 1bf7321f15..5b4d48a837 100644 --- a/audio/aidl/default/Module.cpp +++ b/audio/aidl/default/Module.cpp @@ -405,6 +405,14 @@ ndk::ScopedAStatus Module::openOutputStream(int32_t in_portConfigId, << " does not correspond to an output mix port"; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } + if (portConfigIt->flags.has_value() && + ((portConfigIt->flags.value().get() & + 1 << static_cast(AudioOutputFlags::COMPRESS_OFFLOAD)) != 0) && + !in_offloadInfo.has_value()) { + LOG(ERROR) << __func__ << ": port config id " << in_portConfigId + << " has COMPRESS_OFFLOAD flag set, requires offload info"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } if (mStreams.count(in_portConfigId) != 0) { LOG(ERROR) << __func__ << ": port config id " << in_portConfigId << " already has a stream opened on it"; diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp index 0e76d9a647..969b0e9051 100644 --- a/audio/aidl/vts/ModuleConfig.cpp +++ b/audio/aidl/vts/ModuleConfig.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include @@ -22,19 +23,43 @@ #include "ModuleConfig.h" using namespace android; +using namespace std::chrono_literals; using aidl::android::hardware::audio::core::IModule; using aidl::android::media::audio::common::AudioChannelLayout; +using aidl::android::media::audio::common::AudioEncapsulationMode; 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::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::AudioProfile; +using aidl::android::media::audio::common::AudioUsage; using aidl::android::media::audio::common::Int; +// static +std::optional ModuleConfig::generateOffloadInfoIfNeeded( + const AudioPortConfig& portConfig) { + if (portConfig.flags.has_value() && + portConfig.flags.value().getTag() == AudioIoFlags::Tag::output && + (portConfig.flags.value().get() & + 1 << static_cast(AudioOutputFlags::COMPRESS_OFFLOAD)) != 0) { + AudioOffloadInfo offloadInfo; + offloadInfo.base.sampleRate = portConfig.sampleRate.value().value; + offloadInfo.base.channelMask = portConfig.channelMask.value(); + offloadInfo.base.format = portConfig.format.value(); + offloadInfo.bitRatePerSecond = 256; // Arbitrary value. + offloadInfo.durationUs = std::chrono::microseconds(1min).count(); // Arbitrary value. + offloadInfo.usage = AudioUsage::MEDIA; + offloadInfo.encapsulationMode = AudioEncapsulationMode::NONE; + return offloadInfo; + } + return {}; +} + template auto findById(const std::vector& v, int32_t id) { return std::find_if(v.begin(), v.end(), [&](const auto& p) { return p.id == id; }); @@ -264,10 +289,10 @@ std::string ModuleConfig::toString() const { return result; } -static std::vector combineAudioConfigs(const AudioPort& port, - const AudioProfile& profile) { - std::vector configs; - configs.reserve(profile.channelMasks.size() * profile.sampleRates.size()); +static size_t combineAudioConfigs(const AudioPort& port, const AudioProfile& profile, + std::vector* result) { + const size_t newConfigCount = profile.channelMasks.size() * profile.sampleRates.size(); + result->reserve(result->capacity() + newConfigCount); for (auto channelMask : profile.channelMasks) { for (auto sampleRate : profile.sampleRates) { AudioPortConfig config{}; @@ -277,66 +302,32 @@ static std::vector combineAudioConfigs(const AudioPort& port, config.sampleRate = sr; config.channelMask = channelMask; config.format = profile.format; + config.flags = port.flags; config.ext = port.ext; - configs.push_back(config); + result->push_back(std::move(config)); } } - return configs; + return newConfigCount; } -std::vector ModuleConfig::generateInputAudioMixPortConfigs( - const std::vector& ports, bool singleProfile) const { +static bool isDynamicProfile(const AudioProfile& profile) { + return (profile.format.type == AudioFormatType::DEFAULT && profile.format.encoding.empty()) || + profile.sampleRates.empty() || profile.channelMasks.empty(); +} + +std::vector ModuleConfig::generateAudioMixPortConfigs( + const std::vector& ports, bool isInput, bool singleProfile) const { std::vector result; for (const auto& mixPort : ports) { - if (getAttachedSourceDevicesPortsForMixPort(mixPort).empty()) { - continue; // no attached devices + if (getAttachedDevicesPortsForMixPort(isInput, mixPort).empty()) { + continue; } for (const auto& profile : mixPort.profiles) { - if (profile.format.type == AudioFormatType::DEFAULT || profile.sampleRates.empty() || - profile.channelMasks.empty()) { - continue; // dynamic profile - } - auto configs = combineAudioConfigs(mixPort, profile); - for (auto& config : configs) { - config.flags = mixPort.flags; - result.push_back(config); - if (singleProfile) return result; - } - } - } - return result; -} - -static std::tuple generateOutFlags(const AudioPort& mixPort) { - static const AudioIoFlags offloadFlags = AudioIoFlags::make( - (1 << static_cast(AudioOutputFlags::COMPRESS_OFFLOAD)) | - (1 << static_cast(AudioOutputFlags::DIRECT))); - const bool isOffload = (mixPort.flags.get() & - (1 << static_cast(AudioOutputFlags::COMPRESS_OFFLOAD))) != 0; - return {isOffload ? offloadFlags : mixPort.flags, isOffload}; -} - -std::vector ModuleConfig::generateOutputAudioMixPortConfigs( - const std::vector& ports, bool singleProfile) const { - std::vector result; - for (const auto& mixPort : ports) { - if (getAttachedSinkDevicesPortsForMixPort(mixPort).empty()) { - continue; // no attached devices - } - auto [flags, isOffload] = generateOutFlags(mixPort); - (void)isOffload; - for (const auto& profile : mixPort.profiles) { - if (profile.format.type == AudioFormatType::DEFAULT) continue; - auto configs = combineAudioConfigs(mixPort, profile); - for (auto& config : configs) { - // Some combinations of flags declared in the config file require special - // treatment. - // if (isOffload) { - // config.offloadInfo.info(generateOffloadInfo(config.base)); - // } - config.flags = flags; - result.push_back(config); - if (singleProfile) return result; + if (isDynamicProfile(profile)) continue; + combineAudioConfigs(mixPort, profile, &result); + if (singleProfile && !result.empty()) { + result.resize(1); + return result; } } } @@ -349,9 +340,11 @@ std::vector ModuleConfig::generateAudioDevicePortConfigs( for (const auto& devicePort : ports) { const size_t resultSizeBefore = result.size(); for (const auto& profile : devicePort.profiles) { - auto configs = combineAudioConfigs(devicePort, profile); - result.insert(result.end(), configs.begin(), configs.end()); - if (singleProfile && !result.empty()) return result; + combineAudioConfigs(devicePort, profile, &result); + if (singleProfile && !result.empty()) { + result.resize(1); + return result; + } } if (resultSizeBefore == result.size()) { std::copy_if(mInitialConfigs.begin(), mInitialConfigs.end(), std::back_inserter(result), diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h index 504c0fdf96..df13430955 100644 --- a/audio/aidl/vts/ModuleConfig.h +++ b/audio/aidl/vts/ModuleConfig.h @@ -23,6 +23,7 @@ #include #include +#include #include class ModuleConfig { @@ -32,6 +33,10 @@ class ModuleConfig { using SrcSinkGroup = std::pair>; + static std::optional + generateOffloadInfoIfNeeded( + const aidl::android::media::audio::common::AudioPortConfig& portConfig); + explicit ModuleConfig(aidl::android::hardware::audio::core::IModule* module); const ndk::ScopedAStatus& getStatus() const { return mStatus; } std::string getError() const { return mStatus.getMessage(); } @@ -68,42 +73,34 @@ class ModuleConfig { } std::vector getPortConfigsForMixPorts() const { - auto inputs = generateInputAudioMixPortConfigs(getInputMixPorts(), false); - auto outputs = generateOutputAudioMixPortConfigs(getOutputMixPorts(), false); + auto inputs = generateAudioMixPortConfigs(getInputMixPorts(), true, false); + auto outputs = generateAudioMixPortConfigs(getOutputMixPorts(), false, false); inputs.insert(inputs.end(), outputs.begin(), outputs.end()); return inputs; } std::vector getPortConfigsForMixPorts( bool isInput) const { - return isInput ? generateInputAudioMixPortConfigs(getInputMixPorts(), false) - : generateOutputAudioMixPortConfigs(getOutputMixPorts(), false); + return generateAudioMixPortConfigs(getMixPorts(isInput), isInput, false); } std::vector getPortConfigsForMixPorts( bool isInput, const aidl::android::media::audio::common::AudioPort& port) const { - return isInput ? generateInputAudioMixPortConfigs({port}, false) - : generateOutputAudioMixPortConfigs({port}, false); + return generateAudioMixPortConfigs({port}, isInput, false); } std::optional getSingleConfigForMixPort( bool isInput) const { - const auto config = isInput ? generateInputAudioMixPortConfigs(getInputMixPorts(), true) - : generateOutputAudioMixPortConfigs(getOutputMixPorts(), true); - // TODO: Avoid returning configs for offload since they require an extra - // argument to openOutputStream. + const auto config = generateAudioMixPortConfigs(getMixPorts(isInput), isInput, true); if (!config.empty()) { return *config.begin(); - } else { - return {}; } + return {}; } std::optional getSingleConfigForMixPort( bool isInput, const aidl::android::media::audio::common::AudioPort& port) const { - const auto config = isInput ? generateInputAudioMixPortConfigs({port}, true) - : generateOutputAudioMixPortConfigs({port}, true); + const auto config = generateAudioMixPortConfigs({port}, isInput, true); if (!config.empty()) { return *config.begin(); - } else { - return {}; } + return {}; } std::vector getPortConfigsForDevicePort( @@ -119,13 +116,8 @@ class ModuleConfig { std::string toString() const; private: - std::vector - generateInputAudioMixPortConfigs( - const std::vector& ports, - bool singleProfile) const; - std::vector - generateOutputAudioMixPortConfigs( - const std::vector& ports, + std::vector generateAudioMixPortConfigs( + const std::vector& ports, bool isInput, bool singleProfile) const; // Unlike MixPorts, the generator for DevicePorts always returns a non-empty diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp index 6c91d247e7..bb24365741 100644 --- a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp +++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp @@ -403,21 +403,23 @@ class WithStream { std::shared_ptr mStream; }; -template <> -ScopedAStatus WithStream::SetUpNoChecks(IModule* module, - const AudioPortConfig& portConfig) { +SinkMetadata GenerateSinkMetadata(const AudioPortConfig& portConfig) { RecordTrackMetadata trackMeta; trackMeta.source = AudioSource::MIC; trackMeta.gain = 1.0; trackMeta.channelMask = portConfig.channelMask.value(); SinkMetadata metadata; metadata.tracks.push_back(trackMeta); - return module->openInputStream(portConfig.id, metadata, &mStream); + return metadata; } template <> -ScopedAStatus WithStream::SetUpNoChecks(IModule* module, - const AudioPortConfig& portConfig) { +ScopedAStatus WithStream::SetUpNoChecks(IModule* module, + const AudioPortConfig& portConfig) { + return module->openInputStream(portConfig.id, GenerateSinkMetadata(portConfig), &mStream); +} + +SourceMetadata GenerateSourceMetadata(const AudioPortConfig& portConfig) { PlaybackTrackMetadata trackMeta; trackMeta.usage = AudioUsage::MEDIA; trackMeta.contentType = AudioContentType::MUSIC; @@ -425,7 +427,15 @@ ScopedAStatus WithStream::SetUpNoChecks(IModule* module, trackMeta.channelMask = portConfig.channelMask.value(); SourceMetadata metadata; metadata.tracks.push_back(trackMeta); - return module->openOutputStream(portConfig.id, metadata, {}, &mStream); + return metadata; +} + +template <> +ScopedAStatus WithStream::SetUpNoChecks(IModule* module, + const AudioPortConfig& portConfig) { + return module->openOutputStream(portConfig.id, GenerateSourceMetadata(portConfig), + ModuleConfig::generateOffloadInfoIfNeeded(portConfig), + &mStream); } class WithAudioPatch { @@ -1238,7 +1248,7 @@ TEST_P(AudioStreamOut, OpenTwicePrimary) { auto primaryPortIt = std::find_if(mixPorts.begin(), mixPorts.end(), [](const AudioPort& port) { constexpr int primaryOutputFlag = 1 << static_cast(AudioOutputFlags::PRIMARY); return port.flags.getTag() == AudioIoFlags::Tag::output && - ((port.flags.get() & primaryOutputFlag) != 0); + (port.flags.get() & primaryOutputFlag) != 0; }); if (primaryPortIt == mixPorts.end()) { GTEST_SKIP() << "No primary mix port"; @@ -1251,6 +1261,31 @@ TEST_P(AudioStreamOut, OpenTwicePrimary) { EXPECT_NO_FATAL_FAILURE(OpenTwiceSamePortConfigImpl(portConfig.value())); } +TEST_P(AudioStreamOut, RequireOffloadInfo) { + const auto mixPorts = moduleConfig->getMixPorts(false); + auto offloadPortIt = std::find_if(mixPorts.begin(), mixPorts.end(), [&](const AudioPort& port) { + constexpr int compressOffloadFlag = 1 + << static_cast(AudioOutputFlags::COMPRESS_OFFLOAD); + return port.flags.getTag() == AudioIoFlags::Tag::output && + (port.flags.get() & compressOffloadFlag) != 0 && + !moduleConfig->getAttachedSinkDevicesPortsForMixPort(port).empty(); + }); + if (offloadPortIt == mixPorts.end()) { + GTEST_SKIP() + << "No mix port for compressed offload that could be routed to attached devices"; + } + const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, *offloadPortIt); + ASSERT_TRUE(portConfig.has_value()) + << "No profiles specified for the compressed offload mix port"; + std::shared_ptr ignored; + ScopedAStatus status = module->openOutputStream(portConfig.value().id, + GenerateSourceMetadata(portConfig.value()), + {} /* offloadInfo */, &ignored); + EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()) + << status + << " returned when no offload info is provided for a compressed offload mix port"; +} + // Tests specific to audio patches. The fixure class is named 'AudioModulePatch' // to avoid clashing with 'AudioPatch' class. class AudioModulePatch : public AudioCoreModule {