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
Merged-In: I118b2c04bff12b64a7cac4dc2c88217a6a270046
Change-Id: I118b2c04bff12b64a7cac4dc2c88217a6a270046
(cherry picked from commit 975ea3ae8959b1d9502290ebd5c71fba68645c64)
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
index 0e76d9a..969b0e9 100644
--- a/audio/aidl/vts/ModuleConfig.cpp
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <algorithm>
+#include <chrono>
 
 #include <aidl/android/media/audio/common/AudioIoFlags.h>
 #include <aidl/android/media/audio/common/AudioOutputFlags.h>
@@ -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<AudioOffloadInfo> ModuleConfig::generateOffloadInfoIfNeeded(
+        const AudioPortConfig& portConfig) {
+    if (portConfig.flags.has_value() &&
+        portConfig.flags.value().getTag() == AudioIoFlags::Tag::output &&
+        (portConfig.flags.value().get<AudioIoFlags::Tag::output>() &
+         1 << static_cast<int>(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 <typename T>
 auto findById(const std::vector<T>& v, int32_t id) {
     return std::find_if(v.begin(), v.end(), [&](const auto& p) { return p.id == id; });
@@ -264,10 +289,10 @@
     return result;
 }
 
-static std::vector<AudioPortConfig> combineAudioConfigs(const AudioPort& port,
-                                                        const AudioProfile& profile) {
-    std::vector<AudioPortConfig> configs;
-    configs.reserve(profile.channelMasks.size() * profile.sampleRates.size());
+static size_t combineAudioConfigs(const AudioPort& port, const AudioProfile& profile,
+                                  std::vector<AudioPortConfig>* 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 @@
             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<AudioPortConfig> ModuleConfig::generateInputAudioMixPortConfigs(
-        const std::vector<AudioPort>& 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<AudioPortConfig> ModuleConfig::generateAudioMixPortConfigs(
+        const std::vector<AudioPort>& ports, bool isInput, bool singleProfile) const {
     std::vector<AudioPortConfig> 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<AudioIoFlags, bool> generateOutFlags(const AudioPort& mixPort) {
-    static const AudioIoFlags offloadFlags = AudioIoFlags::make<AudioIoFlags::Tag::output>(
-            (1 << static_cast<int>(AudioOutputFlags::COMPRESS_OFFLOAD)) |
-            (1 << static_cast<int>(AudioOutputFlags::DIRECT)));
-    const bool isOffload = (mixPort.flags.get<AudioIoFlags::Tag::output>() &
-                            (1 << static_cast<int>(AudioOutputFlags::COMPRESS_OFFLOAD))) != 0;
-    return {isOffload ? offloadFlags : mixPort.flags, isOffload};
-}
-
-std::vector<AudioPortConfig> ModuleConfig::generateOutputAudioMixPortConfigs(
-        const std::vector<AudioPort>& ports, bool singleProfile) const {
-    std::vector<AudioPortConfig> 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 @@
     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 504c0fd..df13430 100644
--- a/audio/aidl/vts/ModuleConfig.h
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -23,6 +23,7 @@
 
 #include <aidl/android/hardware/audio/core/AudioRoute.h>
 #include <aidl/android/hardware/audio/core/IModule.h>
+#include <aidl/android/media/audio/common/AudioOffloadInfo.h>
 #include <aidl/android/media/audio/common/AudioPort.h>
 
 class ModuleConfig {
@@ -32,6 +33,10 @@
     using SrcSinkGroup =
             std::pair<aidl::android::hardware::audio::core::AudioRoute, std::vector<SrcSinkPair>>;
 
+    static std::optional<aidl::android::media::audio::common::AudioOffloadInfo>
+    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 @@
     }
     std::vector<aidl::android::media::audio::common::AudioPortConfig> 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<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
             bool isInput) const {
-        return isInput ? generateInputAudioMixPortConfigs(getInputMixPorts(), false)
-                       : generateOutputAudioMixPortConfigs(getOutputMixPorts(), false);
+        return generateAudioMixPortConfigs(getMixPorts(isInput), isInput, false);
     }
     std::vector<aidl::android::media::audio::common::AudioPortConfig> 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<aidl::android::media::audio::common::AudioPortConfig> 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<aidl::android::media::audio::common::AudioPortConfig> 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<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForDevicePort(
@@ -119,13 +116,8 @@
     std::string toString() const;
 
   private:
-    std::vector<aidl::android::media::audio::common::AudioPortConfig>
-    generateInputAudioMixPortConfigs(
-            const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
-            bool singleProfile) const;
-    std::vector<aidl::android::media::audio::common::AudioPortConfig>
-    generateOutputAudioMixPortConfigs(
-            const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
+    std::vector<aidl::android::media::audio::common::AudioPortConfig> generateAudioMixPortConfigs(
+            const std::vector<aidl::android::media::audio::common::AudioPort>& 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 6c91d24..bb24365 100644
--- a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
@@ -403,21 +403,23 @@
     std::shared_ptr<Stream> mStream;
 };
 
-template <>
-ScopedAStatus WithStream<IStreamIn>::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<IStreamOut>::SetUpNoChecks(IModule* module,
-                                                    const AudioPortConfig& portConfig) {
+ScopedAStatus WithStream<IStreamIn>::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 @@
     trackMeta.channelMask = portConfig.channelMask.value();
     SourceMetadata metadata;
     metadata.tracks.push_back(trackMeta);
-    return module->openOutputStream(portConfig.id, metadata, {}, &mStream);
+    return metadata;
+}
+
+template <>
+ScopedAStatus WithStream<IStreamOut>::SetUpNoChecks(IModule* module,
+                                                    const AudioPortConfig& portConfig) {
+    return module->openOutputStream(portConfig.id, GenerateSourceMetadata(portConfig),
+                                    ModuleConfig::generateOffloadInfoIfNeeded(portConfig),
+                                    &mStream);
 }
 
 class WithAudioPatch {
@@ -1238,7 +1248,7 @@
     auto primaryPortIt = std::find_if(mixPorts.begin(), mixPorts.end(), [](const AudioPort& port) {
         constexpr int primaryOutputFlag = 1 << static_cast<int>(AudioOutputFlags::PRIMARY);
         return port.flags.getTag() == AudioIoFlags::Tag::output &&
-               ((port.flags.get<AudioIoFlags::Tag::output>() & primaryOutputFlag) != 0);
+               (port.flags.get<AudioIoFlags::Tag::output>() & primaryOutputFlag) != 0;
     });
     if (primaryPortIt == mixPorts.end()) {
         GTEST_SKIP() << "No primary mix port";
@@ -1251,6 +1261,31 @@
     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<int>(AudioOutputFlags::COMPRESS_OFFLOAD);
+        return port.flags.getTag() == AudioIoFlags::Tag::output &&
+               (port.flags.get<AudioIoFlags::Tag::output>() & 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<IStreamOut> 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 {