audio: Query minimum buffer size before opening streams

The proper way to obtain the minimum buffer size when
opening a stream is to retrieve it from the patch. Thus,
a patch must be established prior to opening a stream.
This step was often skipped by VTS tests, they were
providing a fixed stream buffer size which might not
work for all HAL module implementations.

Created a helper class `StreamFixture` which handles
all necessary steps for opening a stream. Overhauled
tests to use this class.

Also, remove special treatment of remote submix devices
by ModuleConfig.

Bug: 300735639
Test: atest VtsHalAudioCoreTargetTest
Change-Id: Ic51d603d2bb8ff0fd62434bd16fc02c51326fc42
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
index 8f19547..a633d83 100644
--- a/audio/aidl/vts/ModuleConfig.cpp
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -67,20 +67,7 @@
     return {};
 }
 
-std::vector<aidl::android::media::audio::common::AudioPort>
-ModuleConfig::getAudioPortsForDeviceTypes(const std::vector<AudioDeviceType>& deviceTypes,
-                                          const std::string& connection) {
-    return getAudioPortsForDeviceTypes(mPorts, deviceTypes, connection);
-}
-
 // static
-std::vector<aidl::android::media::audio::common::AudioPort> ModuleConfig::getBuiltInMicPorts(
-        const std::vector<aidl::android::media::audio::common::AudioPort>& ports) {
-    return getAudioPortsForDeviceTypes(
-            ports, std::vector<AudioDeviceType>{AudioDeviceType::IN_MICROPHONE,
-                                                AudioDeviceType::IN_MICROPHONE_BACK});
-}
-
 std::vector<aidl::android::media::audio::common::AudioPort>
 ModuleConfig::getAudioPortsForDeviceTypes(
         const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
@@ -100,6 +87,14 @@
     return result;
 }
 
+// static
+std::vector<aidl::android::media::audio::common::AudioPort> ModuleConfig::getBuiltInMicPorts(
+        const std::vector<aidl::android::media::audio::common::AudioPort>& ports) {
+    return getAudioPortsForDeviceTypes(
+            ports, std::vector<AudioDeviceType>{AudioDeviceType::IN_MICROPHONE,
+                                                AudioDeviceType::IN_MICROPHONE_BACK});
+}
+
 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; });
@@ -119,10 +114,7 @@
             } else {
                 mAttachedSinkDevicePorts.insert(port.id);
             }
-        } else if (devicePort.device.type.connection != AudioDeviceDescription::CONNECTION_VIRTUAL
-                   // The "virtual" connection is used for remote submix which is a dynamic
-                   // device but it can be connected and used w/o external hardware.
-                   && port.profiles.empty()) {
+        } else {
             mExternalDevicePorts.insert(port.id);
         }
     }
@@ -141,6 +133,12 @@
     return result;
 }
 
+std::vector<aidl::android::media::audio::common::AudioPort>
+ModuleConfig::getAudioPortsForDeviceTypes(const std::vector<AudioDeviceType>& deviceTypes,
+                                          const std::string& connection) const {
+    return getAudioPortsForDeviceTypes(mPorts, deviceTypes, connection);
+}
+
 std::vector<AudioPort> ModuleConfig::getConnectedExternalDevicePorts() const {
     std::vector<AudioPort> result;
     std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
@@ -229,6 +227,16 @@
     });
 }
 
+std::vector<AudioPort> ModuleConfig::getRemoteSubmixPorts(bool isInput, bool singlePort) const {
+    AudioDeviceType deviceType = isInput ? AudioDeviceType::IN_SUBMIX : AudioDeviceType::OUT_SUBMIX;
+    auto ports = getAudioPortsForDeviceTypes(std::vector<AudioDeviceType>{deviceType},
+                                             AudioDeviceDescription::CONNECTION_VIRTUAL);
+    if (singlePort) {
+        if (!ports.empty()) ports.resize(1);
+    }
+    return ports;
+}
+
 std::vector<AudioPort> ModuleConfig::getConnectedDevicesPortsForMixPort(
         bool isInput, const AudioPortConfig& mixPortConfig) const {
     const auto mixPortIt = findById<AudioPort>(mPorts, mixPortConfig.portId);
@@ -281,19 +289,29 @@
     return {};
 }
 
-std::vector<AudioPort> ModuleConfig::getRoutableMixPortsForDevicePort(const AudioPort& port) const {
-    std::set<int32_t> 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);
-        }
-    }
+std::vector<AudioPort> ModuleConfig::getRoutableDevicePortsForMixPort(const AudioPort& port,
+                                                                      bool connectedOnly) const {
+    std::set<int32_t> portIds = findRoutablePortIds(port.id);
     const bool isInput = port.flags.getTag() == AudioIoFlags::input;
-    return findMixPorts(isInput, false /*connectedOnly*/, false /*singlePort*/,
+    std::set<int32_t> devicePortIds;
+    if (connectedOnly) {
+        devicePortIds = isInput ? getConnectedSourceDevicePorts() : getConnectedSinkDevicePorts();
+    } else {
+        devicePortIds = portIds;
+    }
+    std::vector<AudioPort> result;
+    std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
+        return port.ext.getTag() == AudioPortExt::Tag::device && portIds.count(port.id) > 0 &&
+               devicePortIds.count(port.id) > 0;
+    });
+    return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getRoutableMixPortsForDevicePort(const AudioPort& port,
+                                                                      bool connectedOnly) const {
+    std::set<int32_t> portIds = findRoutablePortIds(port.id);
+    const bool isInput = port.flags.getTag() == AudioIoFlags::input;
+    return findMixPorts(isInput, connectedOnly, false /*singlePort*/,
                         [&portIds](const AudioPort& p) { return portIds.count(p.id) > 0; });
 }
 
@@ -470,6 +488,20 @@
     return result;
 }
 
+std::set<int32_t> ModuleConfig::findRoutablePortIds(int32_t portId) const {
+    std::set<int32_t> portIds;
+    for (const auto& route : mRoutes) {
+        if (portId == route.sinkPortId) {
+            portIds.insert(route.sourcePortIds.begin(), route.sourcePortIds.end());
+        } else if (auto it = std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(),
+                                       portId);
+                   it != route.sourcePortIds.end()) {
+            portIds.insert(route.sinkPortId);
+        }
+    }
+    return portIds;
+}
+
 std::vector<AudioPortConfig> ModuleConfig::generateAudioMixPortConfigs(
         const std::vector<AudioPort>& ports, bool isInput, bool singleProfile) const {
     std::vector<AudioPortConfig> result;
diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h
index b89adc0..4a87f8c 100644
--- a/audio/aidl/vts/ModuleConfig.h
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -38,9 +38,6 @@
     generateOffloadInfoIfNeeded(
             const aidl::android::media::audio::common::AudioPortConfig& portConfig);
 
-    std::vector<aidl::android::media::audio::common::AudioPort> getAudioPortsForDeviceTypes(
-            const std::vector<aidl::android::media::audio::common::AudioDeviceType>& deviceTypes,
-            const std::string& connection = "");
     static std::vector<aidl::android::media::audio::common::AudioPort> getAudioPortsForDeviceTypes(
             const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
             const std::vector<aidl::android::media::audio::common::AudioDeviceType>& deviceTypes,
@@ -53,6 +50,9 @@
     std::string getError() const { return mStatus.getMessage(); }
 
     std::vector<aidl::android::media::audio::common::AudioPort> getAttachedDevicePorts() const;
+    std::vector<aidl::android::media::audio::common::AudioPort> getAudioPortsForDeviceTypes(
+            const std::vector<aidl::android::media::audio::common::AudioDeviceType>& deviceTypes,
+            const std::string& connection = "") const;
     std::vector<aidl::android::media::audio::common::AudioPort> getConnectedExternalDevicePorts()
             const;
     std::set<int32_t> getConnectedSinkDevicePorts() const;
@@ -85,6 +85,8 @@
     std::vector<aidl::android::media::audio::common::AudioPort> getMmapInMixPorts(
             bool connectedOnly /*Permanently attached and connected external devices*/,
             bool singlePort) const;
+    std::vector<aidl::android::media::audio::common::AudioPort> getRemoteSubmixPorts(
+            bool isInput, bool singlePort) const;
 
     std::vector<aidl::android::media::audio::common::AudioPort> getConnectedDevicesPortsForMixPort(
             bool isInput, const aidl::android::media::audio::common::AudioPort& mixPort) const {
@@ -103,8 +105,12 @@
     std::optional<aidl::android::media::audio::common::AudioPort>
     getSourceMixPortForConnectedDevice() const;
 
+    std::vector<aidl::android::media::audio::common::AudioPort> getRoutableDevicePortsForMixPort(
+            const aidl::android::media::audio::common::AudioPort& port,
+            bool connectedOnly /*Permanently attached and connected external devices*/) const;
     std::vector<aidl::android::media::audio::common::AudioPort> getRoutableMixPortsForDevicePort(
-            const aidl::android::media::audio::common::AudioPort& port) const;
+            const aidl::android::media::audio::common::AudioPort& port,
+            bool connectedOnly /*Permanently attached and connected external devices*/) const;
 
     std::optional<SrcSinkPair> getNonRoutableSrcSinkPair(bool isInput) const;
     std::optional<SrcSinkPair> getRoutableSrcSinkPair(bool isInput) const;
@@ -176,6 +182,7 @@
             bool isInput, bool connectedOnly, bool singlePort,
             const std::function<bool(const aidl::android::media::audio::common::AudioPort&)>& pred)
             const;
+    std::set<int32_t> findRoutablePortIds(int32_t portId) const;
     std::vector<aidl::android::media::audio::common::AudioPortConfig> generateAudioMixPortConfigs(
             const std::vector<aidl::android::media::audio::common::AudioPort>& ports, bool isInput,
             bool singleProfile) const;
diff --git a/audio/aidl/vts/TestUtils.h b/audio/aidl/vts/TestUtils.h
index 191e980..e9f3251 100644
--- a/audio/aidl/vts/TestUtils.h
+++ b/audio/aidl/vts/TestUtils.h
@@ -18,7 +18,6 @@
 
 #include <algorithm>
 #include <initializer_list>
-#include <iostream>
 
 #include <android/binder_auto_utils.h>
 #include <gtest/gtest.h>
@@ -93,4 +92,4 @@
         if ((flags).hwAcceleratorMode == Flags::HardwareAccelerator::TUNNEL || (flags).bypass) { \
             GTEST_SKIP() << "Skip data path for offload";                                        \
         }                                                                                        \
-    })
\ No newline at end of file
+    })
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index d0fc4a4..536bc26 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -113,10 +113,23 @@
 using ndk::ScopedAStatus;
 
 template <typename T>
-auto findById(std::vector<T>& v, int32_t id) {
+std::set<int32_t> extractIds(const std::vector<T>& v) {
+    std::set<int32_t> ids;
+    std::transform(v.begin(), v.end(), std::inserter(ids, ids.begin()),
+                   [](const auto& entity) { return entity.id; });
+    return ids;
+}
+
+template <typename T>
+auto findById(const std::vector<T>& v, int32_t id) {
     return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; });
 }
 
+template <typename T>
+auto findAny(const std::vector<T>& v, const std::set<int32_t>& ids) {
+    return std::find_if(v.begin(), v.end(), [&](const auto& e) { return ids.count(e.id) > 0; });
+}
+
 template <typename C>
 std::vector<int32_t> GetNonExistentIds(const C& allIds) {
     if (allIds.empty()) {
@@ -155,8 +168,10 @@
     static int nextId = 0;
     using Tag = AudioDeviceAddress::Tag;
     const auto& deviceDescription = port.ext.get<AudioPortExt::Tag::device>().device.type;
-    AudioDeviceAddress address;
-    if (kPointToPointConnections.count(deviceDescription.connection) == 0) {
+    AudioDeviceAddress address = port.ext.get<AudioPortExt::Tag::device>().device.address;
+    // If the address is already set, do not re-generate.
+    if (address == AudioDeviceAddress() &&
+        kPointToPointConnections.count(deviceDescription.connection) == 0) {
         switch (suggestDeviceAddressTag(deviceDescription)) {
             case Tag::id:
                 address = AudioDeviceAddress::make<Tag::id>(std::to_string(++nextId));
@@ -417,12 +432,14 @@
 // Can be used as a base for any test here, does not depend on the fixture GTest parameters.
 class AudioCoreModuleBase {
   public:
-    // Default buffer sizes are used mostly for negative tests.
-    static constexpr int kDefaultBufferSizeFrames = 256;
+    // Fixed buffer size are used for negative tests only. For any tests involving stream
+    // opening that must success, the minimum buffer size must be obtained from a patch.
+    // This is implemented by the 'StreamFixture' utility class.
+    static constexpr int kNegativeTestBufferSizeFrames = 256;
     static constexpr int kDefaultLargeBufferSizeFrames = 48000;
 
-    void SetUpImpl(const std::string& moduleName) {
-        ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName));
+    void SetUpImpl(const std::string& moduleName, bool setUpDebug = true) {
+        ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName, setUpDebug));
         ASSERT_IS_OK(module->getAudioPorts(&initialPorts));
         ASSERT_IS_OK(module->getAudioRoutes(&initialRoutes));
     }
@@ -439,21 +456,26 @@
                 << "The list of audio routes was not restored to the initial state";
     }
 
-    void ConnectToService(const std::string& moduleName) {
+    void ConnectToService(const std::string& moduleName, bool setUpDebug) {
         ASSERT_EQ(module, nullptr);
         ASSERT_EQ(debug, nullptr);
         module = IModule::fromBinder(binderUtil.connectToService(moduleName));
         ASSERT_NE(module, nullptr);
-        ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+        if (setUpDebug) {
+            ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+        }
     }
 
     void RestartService() {
         ASSERT_NE(module, nullptr);
         moduleConfig.reset();
+        const bool setUpDebug = !!debug;
         debug.reset();
         module = IModule::fromBinder(binderUtil.restartService());
         ASSERT_NE(module, nullptr);
-        ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+        if (setUpDebug) {
+            ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+        }
     }
 
     void SetUpDebug() {
@@ -493,9 +515,7 @@
                          const std::string& errorMessage) {
         std::vector<Entity> entities;
         { ASSERT_IS_OK((module.get()->*getter)(&entities)); }
-        std::transform(entities.begin(), entities.end(),
-                       std::inserter(*entityIds, entityIds->begin()),
-                       [](const auto& entity) { return entity.id; });
+        *entityIds = extractIds<Entity>(entities);
         EXPECT_EQ(entities.size(), entityIds->size()) << errorMessage;
     }
 
@@ -1144,8 +1164,7 @@
     }
     ScopedAStatus SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig,
                                 long bufferSizeFrames);
-    void SetUp(IModule* module, long bufferSizeFrames) {
-        ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
+    void SetUpStream(IModule* module, long bufferSizeFrames) {
         ASSERT_IS_OK(SetUpNoChecks(module, bufferSizeFrames)) << "port config id " << getPortId();
         ASSERT_NE(nullptr, mStream) << "port config id " << getPortId();
         EXPECT_GE(mDescriptor.bufferSizeFrames, bufferSizeFrames)
@@ -1153,6 +1172,10 @@
         mContext.emplace(mDescriptor);
         ASSERT_NO_FATAL_FAILURE(mContext.value().checkIsValid());
     }
+    void SetUp(IModule* module, long bufferSizeFrames) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module, bufferSizeFrames));
+    }
     Stream* get() const { return mStream.get(); }
     const StreamContext* getContext() const { return mContext ? &(mContext.value()) : nullptr; }
     StreamEventReceiver* getEventReceiver() { return mStreamCallback->getEventReceiver(); }
@@ -1292,6 +1315,9 @@
     }
     int32_t getId() const { return mPatch.id; }
     const AudioPatch& get() const { return mPatch; }
+    int32_t getMinimumStreamBufferSizeFrames() const {
+        return mPatch.minimumStreamBufferSizeFrames;
+    }
     const AudioPortConfig& getSinkPortConfig() const { return mSinkPortConfig.get(); }
     const AudioPortConfig& getSrcPortConfig() const { return mSrcPortConfig.get(); }
     const AudioPortConfig& getPortConfig(bool getSink) const {
@@ -1504,8 +1530,8 @@
         EXPECT_EQ(portConnected.get(), connectedPort);
         const auto& portProfiles = connectedPort.profiles;
         if (portProfiles.empty()) {
-            const auto routableMixPorts =
-                    moduleConfig->getRoutableMixPortsForDevicePort(connectedPort);
+            const auto routableMixPorts = moduleConfig->getRoutableMixPortsForDevicePort(
+                    connectedPort, true /*connectedOnly*/);
             bool hasMixPortWithStaticProfile = false;
             for (const auto& mixPort : routableMixPorts) {
                 const auto& mixPortProfiles = mixPort.profiles;
@@ -1546,7 +1572,7 @@
         {
             aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments args;
             args.portConfigId = portConfigId;
-            args.bufferSizeFrames = kDefaultBufferSizeFrames;
+            args.bufferSizeFrames = kNegativeTestBufferSizeFrames;
             aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn ret;
             EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openInputStream(args, &ret))
                     << "port config ID " << portConfigId;
@@ -1555,7 +1581,7 @@
         {
             aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
             args.portConfigId = portConfigId;
-            args.bufferSizeFrames = kDefaultBufferSizeFrames;
+            args.bufferSizeFrames = kNegativeTestBufferSizeFrames;
             aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
             EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
                     << "port config ID " << portConfigId;
@@ -1718,6 +1744,10 @@
     doNotSimulateConnections.flags().simulateDeviceConnections = false;
     ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get()));
     for (const auto& port : ports) {
+        // Virtual devices may not require external hardware and thus can always be connected.
+        if (port.ext.get<AudioPortExt::device>().device.type.connection ==
+            AudioDeviceDescription::CONNECTION_VIRTUAL)
+            continue;
         AudioPort portWithData = GenerateUniqueDeviceAddress(port), connectedPort;
         ScopedAStatus status = module->connectExternalDevice(portWithData, &connectedPort);
         EXPECT_STATUS(EX_ILLEGAL_STATE, status) << "static port " << portWithData.toString();
@@ -2564,6 +2594,260 @@
     std::vector<std::string> mStatuses;
 };
 
+// A helper which sets up necessary HAL structures for a proper stream initialization.
+//
+// The full sequence of actions to set up a stream is as follows:
+//
+//  device port -> connect if necessary -> set up port config   | -> set up patch
+//  mix port -> set up port config, unless it has been provided |
+//
+//  then, from the patch, figure out the minimum HAL buffer size -> set up stream
+//
+// This sequence is reflected in the order of fields declaration.
+// Various tests need to be able to start and stop at various point in this sequence,
+// this is why there are methods that do just part of the work.
+//
+// Note: To maximize test coverage, this class relies on simulation of external device
+// connections by the HAL module.
+template <typename Stream>
+class StreamFixture {
+  public:
+    // Tests might need to override the direction.
+    StreamFixture(bool isInput = IOTraits<Stream>::is_input) : mIsInput(isInput) {}
+
+    void SetUpPortConfigAnyMixPort(IModule* module, ModuleConfig* moduleConfig,
+                                   bool connectedOnly) {
+        const auto mixPorts = moduleConfig->getMixPorts(mIsInput, connectedOnly);
+        mSkipTestReason = "No mix ports";
+        for (const auto& mixPort : mixPorts) {
+            mSkipTestReason = "";
+            ASSERT_NO_FATAL_FAILURE(SetUpPortConfigForMixPortOrConfig(module, moduleConfig, mixPort,
+                                                                      connectedOnly));
+            if (mSkipTestReason.empty()) break;
+        }
+    }
+
+    void SetUpPortConfigForMixPortOrConfig(
+            IModule* module, ModuleConfig* moduleConfig, const AudioPort& initialMixPort,
+            bool connectedOnly, const std::optional<AudioPortConfig>& mixPortConfig = {}) {
+        if (mixPortConfig.has_value() && !connectedOnly) {
+            // Connecting an external device may cause change in mix port profiles and the provided
+            // config may become invalid.
+            LOG(FATAL) << __func__ << ": when specifying a mix port config, it is not allowed "
+                       << "to change connected devices, thus `connectedOnly` must be `true`";
+        }
+        std::optional<AudioPort> connectedDevicePort;
+        ASSERT_NO_FATAL_FAILURE(SetUpDevicePortForMixPort(module, moduleConfig, initialMixPort,
+                                                          connectedOnly, &connectedDevicePort));
+        if (!mSkipTestReason.empty()) return;
+        if (mixPortConfig.has_value()) {
+            ASSERT_NO_FATAL_FAILURE(
+                    SetUpPortConfig(module, moduleConfig, *mixPortConfig, *connectedDevicePort));
+        } else {
+            // If an external device was connected, the profiles of the mix port might have changed.
+            AudioPort mixPort;
+            ASSERT_NO_FATAL_FAILURE(module->getAudioPort(initialMixPort.id, &mixPort));
+            ASSERT_NO_FATAL_FAILURE(
+                    SetUpPortConfig(module, moduleConfig, mixPort, *connectedDevicePort));
+        }
+    }
+
+    void SetUpPortConfig(IModule* module, ModuleConfig* moduleConfig, const AudioPort& mixPort,
+                         const AudioPort& devicePort) {
+        auto mixPortConfig = moduleConfig->getSingleConfigForMixPort(mIsInput, mixPort);
+        ASSERT_TRUE(mixPortConfig.has_value())
+                << "Unable to generate port config for mix port " << mixPort.toString();
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module, moduleConfig, *mixPortConfig, devicePort));
+    }
+    void SetUpPortConfig(IModule* module, ModuleConfig* moduleConfig,
+                         const AudioPortConfig& mixPortConfig, const AudioPort& devicePort) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPatch(module, moduleConfig, mixPortConfig, devicePort));
+        mStream = std::make_unique<WithStream<Stream>>(mMixPortConfig->get());
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpPortConfig(module));
+    }
+
+    ScopedAStatus SetUpStreamNoChecks(IModule* module) {
+        return mStream->SetUpNoChecks(module, getMinimumStreamBufferSizeFrames());
+    }
+    void SetUpStream(IModule* module) {
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpStream(module, getMinimumStreamBufferSizeFrames()));
+    }
+
+    void SetUpStreamForDevicePort(IModule* module, ModuleConfig* moduleConfig,
+                                  const AudioPort& devicePort, bool connectedOnly = false) {
+        ASSERT_NO_FATAL_FAILURE(
+                SetUpPortConfigForDevicePort(module, moduleConfig, devicePort, connectedOnly));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
+    void SetUpStreamForAnyMixPort(IModule* module, ModuleConfig* moduleConfig,
+                                  bool connectedOnly = false) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfigAnyMixPort(module, moduleConfig, connectedOnly));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
+    void SetUpStreamForMixPort(IModule* module, ModuleConfig* moduleConfig,
+                               const AudioPort& mixPort, bool connectedOnly = false) {
+        ASSERT_NO_FATAL_FAILURE(
+                SetUpPortConfigForMixPortOrConfig(module, moduleConfig, mixPort, connectedOnly));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
+    void SetUpStreamForPortsPair(IModule* module, ModuleConfig* moduleConfig,
+                                 const AudioPort& mixPort, const AudioPort& devicePort) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module, moduleConfig, mixPort, devicePort));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
+    void SetUpStreamForMixPortConfig(IModule* module, ModuleConfig* moduleConfig,
+                                     const AudioPortConfig& mixPortConfig) {
+        // Since mix port configs may change after connecting an external device,
+        // only connected device ports are considered.
+        constexpr bool connectedOnly = true;
+        const auto& ports = moduleConfig->getMixPorts(mIsInput, connectedOnly);
+        const auto mixPortIt = findById<AudioPort>(ports, mixPortConfig.portId);
+        ASSERT_NE(mixPortIt, ports.end()) << "Port id " << mixPortConfig.portId << " not found";
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfigForMixPortOrConfig(module, moduleConfig, *mixPortIt,
+                                                                  connectedOnly, mixPortConfig));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
+    void SetUpPatchForMixPortConfig(IModule* module, ModuleConfig* moduleConfig,
+                                    const AudioPortConfig& mixPortConfig) {
+        constexpr bool connectedOnly = true;
+        const auto& ports = moduleConfig->getMixPorts(mIsInput, connectedOnly);
+        const auto mixPortIt = findById<AudioPort>(ports, mixPortConfig.portId);
+        ASSERT_NE(mixPortIt, ports.end()) << "Port id " << mixPortConfig.portId << " not found";
+        std::optional<AudioPort> connectedDevicePort;
+        ASSERT_NO_FATAL_FAILURE(SetUpDevicePortForMixPort(module, moduleConfig, *mixPortIt,
+                                                          connectedOnly, &connectedDevicePort));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(
+                SetUpPatch(module, moduleConfig, mixPortConfig, *connectedDevicePort));
+    }
+
+    void ReconnectPatch(IModule* module) {
+        mPatch = std::make_unique<WithAudioPatch>(mIsInput, mMixPortConfig->get(),
+                                                  mDevicePortConfig->get());
+        ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(module));
+    }
+    void TeardownPatch() { mPatch.reset(); }
+    // Assuming that the patch is set up, while the stream isn't yet,
+    // tear the patch down and set up stream.
+    void TeardownPatchSetUpStream(IModule* module) {
+        const int32_t bufferSize = getMinimumStreamBufferSizeFrames();
+        ASSERT_NO_FATAL_FAILURE(TeardownPatch());
+        mStream = std::make_unique<WithStream<Stream>>(mMixPortConfig->get());
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpPortConfig(module));
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpStream(module, bufferSize));
+    }
+
+    const AudioDevice& getDevice() const { return mDevice; }
+    int32_t getMinimumStreamBufferSizeFrames() const {
+        return mPatch->getMinimumStreamBufferSizeFrames();
+    }
+    const AudioPatch& getPatch() const { return mPatch->get(); }
+    const AudioPortConfig& getPortConfig() const { return mMixPortConfig->get(); }
+    int32_t getPortId() const { return mMixPortConfig->getId(); }
+    Stream* getStream() const { return mStream->get(); }
+    const StreamContext* getStreamContext() const { return mStream->getContext(); }
+    StreamEventReceiver* getStreamEventReceiver() { return mStream->getEventReceiver(); }
+    std::shared_ptr<Stream> getStreamSharedPointer() const { return mStream->getSharedPointer(); }
+    const std::string& skipTestReason() const { return mSkipTestReason; }
+
+  private:
+    void SetUpDevicePort(IModule* module, ModuleConfig* moduleConfig,
+                         const std::set<int32_t>& devicePortIds, bool connectedOnly,
+                         std::optional<AudioPort>* connectedDevicePort) {
+        const auto attachedDevicePorts = moduleConfig->getAttachedDevicePorts();
+        if (auto it = findAny<AudioPort>(attachedDevicePorts, devicePortIds);
+            it != attachedDevicePorts.end()) {
+            *connectedDevicePort = *it;
+            LOG(DEBUG) << __func__ << ": found attached port " << it->toString();
+        }
+        const auto connectedDevicePorts = moduleConfig->getConnectedExternalDevicePorts();
+        if (auto it = findAny<AudioPort>(connectedDevicePorts, devicePortIds);
+            it != connectedDevicePorts.end()) {
+            *connectedDevicePort = *it;
+            LOG(DEBUG) << __func__ << ": found connected port " << it->toString();
+        }
+        if (!connectedOnly && !connectedDevicePort->has_value()) {
+            const auto externalDevicePorts = moduleConfig->getExternalDevicePorts();
+            if (auto it = findAny<AudioPort>(externalDevicePorts, devicePortIds);
+                it != externalDevicePorts.end()) {
+                AudioPort portWithData = GenerateUniqueDeviceAddress(*it);
+                mPortConnected = std::make_unique<WithDevicePortConnectedState>(portWithData);
+                ASSERT_NO_FATAL_FAILURE(mPortConnected->SetUp(module, moduleConfig));
+                *connectedDevicePort = mPortConnected->get();
+                LOG(DEBUG) << __func__ << ": connected port " << mPortConnected->get().toString();
+            }
+        }
+    }
+    void SetUpDevicePortForMixPort(IModule* module, ModuleConfig* moduleConfig,
+                                   const AudioPort& mixPort, bool connectedOnly,
+                                   std::optional<AudioPort>* connectedDevicePort) {
+        const auto devicePorts =
+                moduleConfig->getRoutableDevicePortsForMixPort(mixPort, connectedOnly);
+        if (devicePorts.empty()) {
+            mSkipTestReason = std::string("No routable device ports found for mix port id ")
+                                      .append(std::to_string(mixPort.id));
+            LOG(DEBUG) << __func__ << ": " << mSkipTestReason;
+            return;
+        };
+        ASSERT_NO_FATAL_FAILURE(SetUpDevicePort(module, moduleConfig,
+                                                extractIds<AudioPort>(devicePorts), connectedOnly,
+                                                connectedDevicePort));
+        if (!connectedDevicePort->has_value()) {
+            mSkipTestReason = std::string("Unable to find a device port pair for mix port id ")
+                                      .append(std::to_string(mixPort.id));
+            LOG(DEBUG) << __func__ << ": " << mSkipTestReason;
+            return;
+        }
+    }
+    void SetUpPortConfigForDevicePort(IModule* module, ModuleConfig* moduleConfig,
+                                      const AudioPort& devicePort, bool connectedOnly) {
+        std::optional<AudioPort> connectedDevicePort;
+        ASSERT_NO_FATAL_FAILURE(SetUpDevicePort(module, moduleConfig, {devicePort.id},
+                                                connectedOnly, &connectedDevicePort));
+        if (!connectedDevicePort.has_value()) {
+            mSkipTestReason = std::string("Device port id ")
+                                      .append(std::to_string(devicePort.id))
+                                      .append(" is not attached and can not be connected");
+            return;
+        }
+        const auto mixPorts = moduleConfig->getRoutableMixPortsForDevicePort(
+                *connectedDevicePort, true /*connectedOnly*/);
+        if (mixPorts.empty()) {
+            mSkipTestReason = std::string("No routable mix ports found for device port id ")
+                                      .append(std::to_string(devicePort.id));
+            return;
+        }
+        ASSERT_NO_FATAL_FAILURE(
+                SetUpPortConfig(module, moduleConfig, *mixPorts.begin(), *connectedDevicePort));
+    }
+    void SetUpPatch(IModule* module, ModuleConfig* moduleConfig,
+                    const AudioPortConfig& mixPortConfig, const AudioPort& devicePort) {
+        mMixPortConfig = std::make_unique<WithAudioPortConfig>(mixPortConfig);
+        ASSERT_NO_FATAL_FAILURE(mMixPortConfig->SetUp(module));
+        mDevicePortConfig = std::make_unique<WithAudioPortConfig>(
+                moduleConfig->getSingleConfigForDevicePort(devicePort));
+        ASSERT_NO_FATAL_FAILURE(mDevicePortConfig->SetUp(module));
+        mDevice = devicePort.ext.get<AudioPortExt::device>().device;
+        mPatch = std::make_unique<WithAudioPatch>(mIsInput, mMixPortConfig->get(),
+                                                  mDevicePortConfig->get());
+        ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(module));
+    }
+
+    const bool mIsInput;
+    std::string mSkipTestReason;
+    std::unique_ptr<WithDevicePortConnectedState> mPortConnected;
+    AudioDevice mDevice;
+    std::unique_ptr<WithAudioPortConfig> mMixPortConfig;
+    std::unique_ptr<WithAudioPortConfig> mDevicePortConfig;
+    std::unique_ptr<WithAudioPatch> mPatch;
+    std::unique_ptr<WithStream<Stream>> mStream;
+};
+
 template <typename Stream>
 class AudioStream : public AudioCoreModule {
   public:
@@ -2573,16 +2857,15 @@
     }
 
     void GetStreamCommon() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         std::shared_ptr<IStreamCommon> streamCommon1;
-        EXPECT_IS_OK(stream.get()->getStreamCommon(&streamCommon1));
+        EXPECT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon1));
         std::shared_ptr<IStreamCommon> streamCommon2;
-        EXPECT_IS_OK(stream.get()->getStreamCommon(&streamCommon2));
+        EXPECT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon2));
         ASSERT_NE(nullptr, streamCommon1);
         ASSERT_NE(nullptr, streamCommon2);
         EXPECT_EQ(streamCommon1->asBinder(), streamCommon2->asBinder())
@@ -2590,31 +2873,31 @@
     }
 
     void CloseTwice() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
-        }
         std::shared_ptr<Stream> heldStream;
         {
-            WithStream<Stream> stream(portConfig.value());
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
-            heldStream = stream.getSharedPointer();
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(
+                    stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+            if (auto reason = stream.skipTestReason(); !reason.empty()) {
+                GTEST_SKIP() << reason;
+            }
+            heldStream = stream.getStreamSharedPointer();
         }
         EXPECT_STATUS(EX_ILLEGAL_STATE, WithStream<Stream>::callClose(heldStream))
                 << "when closing the stream twice";
     }
 
     void PrepareToCloseTwice() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
-        }
         std::shared_ptr<IStreamCommon> heldStreamCommon;
         {
-            WithStream<Stream> stream(portConfig.value());
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(
+                    stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+            if (auto reason = stream.skipTestReason(); !reason.empty()) {
+                GTEST_SKIP() << reason;
+            }
             std::shared_ptr<IStreamCommon> streamCommon;
-            ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+            ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
             heldStreamCommon = streamCommon;
             EXPECT_IS_OK(streamCommon->prepareToClose());
             EXPECT_IS_OK(streamCommon->prepareToClose())
@@ -2627,9 +2910,13 @@
     void OpenAllConfigs() {
         const auto allPortConfigs =
                 moduleConfig->getPortConfigsForMixPorts(IOTraits<Stream>::is_input);
+        if (allPortConfigs.empty()) {
+            GTEST_SKIP() << "No mix ports for attached devices";
+        }
         for (const auto& portConfig : allPortConfigs) {
-            WithStream<Stream> stream(portConfig);
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPortConfig(
+                    module.get(), moduleConfig.get(), portConfig));
         }
     }
 
@@ -2649,22 +2936,21 @@
 
     void OpenInvalidDirection() {
         // Important! The direction of the port config must be reversed.
-        const auto portConfig =
-                moduleConfig->getSingleConfigForMixPort(!IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream(!IOTraits<Stream>::is_input);
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfigAnyMixPort(module.get(), moduleConfig.get(),
+                                                                 false /*connectedOnly*/));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
-        EXPECT_STATUS(EX_ILLEGAL_ARGUMENT,
-                      stream.SetUpNoChecks(module.get(), kDefaultBufferSizeFrames))
+        EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.SetUpStreamNoChecks(module.get()))
                 << "port config ID " << stream.getPortId();
-        EXPECT_EQ(nullptr, stream.get());
+        EXPECT_EQ(nullptr, stream.getStream());
     }
 
     void OpenOverMaxCount() {
+        constexpr bool connectedOnly = true;
         constexpr bool isInput = IOTraits<Stream>::is_input;
-        auto ports = moduleConfig->getMixPorts(isInput, true /*connectedOnly*/);
+        auto ports = moduleConfig->getMixPorts(isInput, connectedOnly);
         bool hasSingleRun = false;
         for (const auto& port : ports) {
             const size_t maxStreamCount = port.ext.get<AudioPortExt::Tag::mix>().maxOpenStreamCount;
@@ -2677,16 +2963,16 @@
                 continue;
             }
             hasSingleRun = true;
-            std::optional<WithStream<Stream>> streamWraps[maxStreamCount + 1];
+            StreamFixture<Stream> streams[maxStreamCount + 1];
             for (size_t i = 0; i <= maxStreamCount; ++i) {
-                streamWraps[i].emplace(portConfigs[i]);
-                WithStream<Stream>& stream = streamWraps[i].value();
+                ASSERT_NO_FATAL_FAILURE(streams[i].SetUpPortConfigForMixPortOrConfig(
+                        module.get(), moduleConfig.get(), port, connectedOnly, portConfigs[i]));
+                ASSERT_EQ("", streams[i].skipTestReason());
+                auto& stream = streams[i];
                 if (i < maxStreamCount) {
-                    ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+                    ASSERT_NO_FATAL_FAILURE(stream.SetUpStream(module.get()));
                 } else {
-                    ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
-                    EXPECT_STATUS(EX_ILLEGAL_STATE,
-                                  stream.SetUpNoChecks(module.get(), kDefaultBufferSizeFrames))
+                    EXPECT_STATUS(EX_ILLEGAL_STATE, stream.SetUpStreamNoChecks(module.get()))
                             << "port config ID " << stream.getPortId() << ", maxOpenStreamCount is "
                             << maxStreamCount;
                 }
@@ -2706,12 +2992,11 @@
     }
 
     void ResetPortConfigWithOpenStream() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         EXPECT_STATUS(EX_ILLEGAL_STATE, module->resetAudioPortConfig(stream.getPortId()))
                 << "port config ID " << stream.getPortId();
     }
@@ -2725,14 +3010,13 @@
     }
 
     void UpdateHwAvSyncId() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         std::shared_ptr<IStreamCommon> streamCommon;
-        ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+        ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
         ASSERT_NE(nullptr, streamCommon);
         const auto kStatuses = {EX_NONE, EX_ILLEGAL_ARGUMENT, EX_ILLEGAL_STATE};
         for (const auto id : {-100, -1, 0, 1, 100}) {
@@ -2745,14 +3029,13 @@
     }
 
     void GetVendorParameters() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         std::shared_ptr<IStreamCommon> streamCommon;
-        ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+        ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
         ASSERT_NE(nullptr, streamCommon);
 
         bool isGetterSupported = false;
@@ -2766,14 +3049,13 @@
     }
 
     void SetVendorParameters() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         std::shared_ptr<IStreamCommon> streamCommon;
-        ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+        ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
         ASSERT_NE(nullptr, streamCommon);
 
         bool isSupported = false;
@@ -2784,32 +3066,37 @@
     }
 
     void HwGainHwVolume() {
-        const auto ports =
-                moduleConfig->getMixPorts(IOTraits<Stream>::is_input, true /*connectedOnly*/);
+        // Since device connection emulation does not cover complete functionality,
+        // only use this test with connected devices.
+        constexpr bool connectedOnly = true;
+        const auto ports = moduleConfig->getMixPorts(IOTraits<Stream>::is_input, connectedOnly);
         if (ports.empty()) {
             GTEST_SKIP() << "No mix ports";
         }
         bool atLeastOneSupports = false;
         for (const auto& port : ports) {
-            const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
-            if (!portConfig.has_value()) continue;
-            WithStream<Stream> stream(portConfig.value());
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+            SCOPED_TRACE(port.toString());
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(),
+                                                                 port, connectedOnly));
+            if (!stream.skipTestReason().empty()) continue;
+            const auto portConfig = stream.getPortConfig();
+            SCOPED_TRACE(portConfig.toString());
             std::vector<std::vector<float>> validValues, invalidValues;
             bool isSupported = false;
             if constexpr (IOTraits<Stream>::is_input) {
-                GenerateTestArrays<float>(getChannelCount(portConfig.value().channelMask.value()),
+                GenerateTestArrays<float>(getChannelCount(portConfig.channelMask.value()),
                                           IStreamIn::HW_GAIN_MIN, IStreamIn::HW_GAIN_MAX,
                                           &validValues, &invalidValues);
                 EXPECT_NO_FATAL_FAILURE(TestAccessors<std::vector<float>>(
-                        stream.get(), &IStreamIn::getHwGain, &IStreamIn::setHwGain, validValues,
-                        invalidValues, &isSupported));
+                        stream.getStream(), &IStreamIn::getHwGain, &IStreamIn::setHwGain,
+                        validValues, invalidValues, &isSupported));
             } else {
-                GenerateTestArrays<float>(getChannelCount(portConfig.value().channelMask.value()),
+                GenerateTestArrays<float>(getChannelCount(portConfig.channelMask.value()),
                                           IStreamOut::HW_VOLUME_MIN, IStreamOut::HW_VOLUME_MAX,
                                           &validValues, &invalidValues);
                 EXPECT_NO_FATAL_FAILURE(TestAccessors<std::vector<float>>(
-                        stream.get(), &IStreamOut::getHwVolume, &IStreamOut::setHwVolume,
+                        stream.getStream(), &IStreamOut::getHwVolume, &IStreamOut::setHwVolume,
                         validValues, invalidValues, &isSupported));
             }
             if (isSupported) atLeastOneSupports = true;
@@ -2823,19 +3110,22 @@
     // currently we can only pass a nullptr, and the HAL module must either reject
     // it as an invalid argument, or say that offloaded effects are not supported.
     void AddRemoveEffectInvalidArguments() {
-        const auto ports =
-                moduleConfig->getMixPorts(IOTraits<Stream>::is_input, true /*connectedOnly*/);
+        constexpr bool connectedOnly = true;
+        const auto ports = moduleConfig->getMixPorts(IOTraits<Stream>::is_input, connectedOnly);
         if (ports.empty()) {
             GTEST_SKIP() << "No mix ports";
         }
         bool atLeastOneSupports = false;
         for (const auto& port : ports) {
-            const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
-            if (!portConfig.has_value()) continue;
-            WithStream<Stream> stream(portConfig.value());
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+            SCOPED_TRACE(port.toString());
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(),
+                                                                 port, connectedOnly));
+            if (!stream.skipTestReason().empty()) continue;
+            const auto portConfig = stream.getPortConfig();
+            SCOPED_TRACE(portConfig.toString());
             std::shared_ptr<IStreamCommon> streamCommon;
-            ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+            ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
             ASSERT_NE(nullptr, streamCommon);
             ndk::ScopedAStatus addEffectStatus = streamCommon->addEffect(nullptr);
             ndk::ScopedAStatus removeEffectStatus = streamCommon->removeEffect(nullptr);
@@ -2855,11 +3145,14 @@
     }
 
     void OpenTwiceSamePortConfigImpl(const AudioPortConfig& portConfig) {
-        WithStream<Stream> stream1(portConfig);
-        ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get(), kDefaultBufferSizeFrames));
+        StreamFixture<Stream> stream1;
+        ASSERT_NO_FATAL_FAILURE(
+                stream1.SetUpStreamForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
+        ASSERT_EQ("", stream1.skipTestReason());
         WithStream<Stream> stream2;
-        EXPECT_STATUS(EX_ILLEGAL_STATE, stream2.SetUpNoChecks(module.get(), stream1.getPortConfig(),
-                                                              kDefaultBufferSizeFrames))
+        EXPECT_STATUS(EX_ILLEGAL_STATE,
+                      stream2.SetUpNoChecks(module.get(), stream1.getPortConfig(),
+                                            stream1.getMinimumStreamBufferSizeFrames()))
                 << "when opening a stream twice for the same port config ID "
                 << stream1.getPortId();
     }
@@ -2894,11 +3187,13 @@
         for (const auto& seq : sequences) {
             SCOPED_TRACE(std::string("Sequence ").append(seq.first));
             LOG(DEBUG) << __func__ << ": Sequence " << seq.first;
-            WithStream<Stream> stream(portConfig);
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPortConfig(
+                    module.get(), moduleConfig.get(), portConfig));
+            ASSERT_EQ("", stream.skipTestReason());
             StreamLogicDriverInvalidCommand driver(seq.second);
-            typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
-                                                     stream.getEventReceiver());
+            typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+                                                     stream.getStreamEventReceiver());
             LOG(DEBUG) << __func__ << ": starting worker...";
             ASSERT_TRUE(worker.start());
             LOG(DEBUG) << __func__ << ": joining worker...";
@@ -2951,63 +3246,59 @@
     if (ports.empty()) {
         GTEST_SKIP() << "No input mix ports for attached devices";
     }
+    bool atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port";
-        WithStream<IStreamIn> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
-        {
-            // The port of the stream is not connected, thus the list of active mics must be empty.
-            std::vector<MicrophoneDynamicInfo> activeMics;
-            EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics));
-            EXPECT_TRUE(activeMics.empty()) << "a stream on an unconnected port returns a "
-                                               "non-empty list of active microphones";
+        auto micDevicePorts = ModuleConfig::getBuiltInMicPorts(
+                moduleConfig->getConnectedSourceDevicesPortsForMixPort(port));
+        if (micDevicePorts.empty()) continue;
+        atLeastOnePort = true;
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamIn> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForPortsPair(module.get(), moduleConfig.get(),
+                                                               port, micDevicePorts[0]));
+        if (!stream.skipTestReason().empty()) continue;
+        std::vector<MicrophoneDynamicInfo> activeMics;
+        EXPECT_IS_OK(stream.getStream()->getActiveMicrophones(&activeMics));
+        EXPECT_FALSE(activeMics.empty());
+        for (const auto& mic : activeMics) {
+            EXPECT_NE(micInfos.end(),
+                      std::find_if(micInfos.begin(), micInfos.end(),
+                                   [&](const auto& micInfo) { return micInfo.id == mic.id; }))
+                    << "active microphone \"" << mic.id << "\" is not listed in "
+                    << "microphone infos returned by the module: "
+                    << ::android::internal::ToString(micInfos);
+            EXPECT_NE(0UL, mic.channelMapping.size())
+                    << "No channels specified for the microphone \"" << mic.id << "\"";
         }
-        if (auto micDevicePorts = ModuleConfig::getBuiltInMicPorts(
-                    moduleConfig->getConnectedSourceDevicesPortsForMixPort(port));
-            !micDevicePorts.empty()) {
-            auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(micDevicePorts[0]);
-            WithAudioPatch patch(true /*isInput*/, stream.getPortConfig(), devicePortConfig);
-            ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
-            std::vector<MicrophoneDynamicInfo> activeMics;
-            EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics));
-            EXPECT_FALSE(activeMics.empty());
-            for (const auto& mic : activeMics) {
-                EXPECT_NE(micInfos.end(),
-                          std::find_if(micInfos.begin(), micInfos.end(),
-                                       [&](const auto& micInfo) { return micInfo.id == mic.id; }))
-                        << "active microphone \"" << mic.id << "\" is not listed in "
-                        << "microphone infos returned by the module: "
-                        << ::android::internal::ToString(micInfos);
-                EXPECT_NE(0UL, mic.channelMapping.size())
-                        << "No channels specified for the microphone \"" << mic.id << "\"";
-            }
-        }
-        {
-            // Now the port of the stream is not connected again, re-check that there are no
-            // active microphones.
-            std::vector<MicrophoneDynamicInfo> activeMics;
-            EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics));
-            EXPECT_TRUE(activeMics.empty()) << "a stream on an unconnected port returns a "
-                                               "non-empty list of active microphones";
-        }
+        stream.TeardownPatch();
+        // Now the port of the stream is not connected, check that there are no active microphones.
+        std::vector<MicrophoneDynamicInfo> emptyMics;
+        EXPECT_IS_OK(stream.getStream()->getActiveMicrophones(&emptyMics));
+        EXPECT_TRUE(emptyMics.empty()) << "a stream on an unconnected port returns a "
+                                          "non-empty list of active microphones";
+    }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No input mix ports could be routed to built-in microphone devices";
     }
 }
 
 TEST_P(AudioStreamIn, MicrophoneDirection) {
     using MD = IStreamIn::MicrophoneDirection;
-    const auto ports = moduleConfig->getInputMixPorts(true /*connectedOnly*/);
+    constexpr bool connectedOnly = true;
+    const auto ports = moduleConfig->getInputMixPorts(connectedOnly);
     if (ports.empty()) {
         GTEST_SKIP() << "No input mix ports for attached devices";
     }
-    bool isSupported = false;
+    bool isSupported = false, atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port";
-        WithStream<IStreamIn> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamIn> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+                                                             connectedOnly));
+        if (!stream.skipTestReason().empty()) continue;
+        atLeastOnePort = true;
         EXPECT_NO_FATAL_FAILURE(
-                TestAccessors<MD>(stream.get(), &IStreamIn::getMicrophoneDirection,
+                TestAccessors<MD>(stream.getStream(), &IStreamIn::getMicrophoneDirection,
                                   &IStreamIn::setMicrophoneDirection,
                                   std::vector<MD>(enum_range<MD>().begin(), enum_range<MD>().end()),
                                   {}, &isSupported));
@@ -3016,21 +3307,27 @@
     if (!isSupported) {
         GTEST_SKIP() << "Microphone direction is not supported";
     }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No input mix ports could be routed to built-in microphone devices";
+    }
 }
 
 TEST_P(AudioStreamIn, MicrophoneFieldDimension) {
-    const auto ports = moduleConfig->getInputMixPorts(true /*connectedOnly*/);
+    constexpr bool connectedOnly = true;
+    const auto ports = moduleConfig->getInputMixPorts(connectedOnly);
     if (ports.empty()) {
         GTEST_SKIP() << "No input mix ports for attached devices";
     }
-    bool isSupported = false;
+    bool isSupported = false, atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port";
-        WithStream<IStreamIn> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamIn> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+                                                             connectedOnly));
+        if (!stream.skipTestReason().empty()) continue;
+        atLeastOnePort = true;
         EXPECT_NO_FATAL_FAILURE(TestAccessors<float>(
-                stream.get(), &IStreamIn::getMicrophoneFieldDimension,
+                stream.getStream(), &IStreamIn::getMicrophoneFieldDimension,
                 &IStreamIn::setMicrophoneFieldDimension,
                 {IStreamIn::MIC_FIELD_DIMENSION_WIDE_ANGLE,
                  IStreamIn::MIC_FIELD_DIMENSION_WIDE_ANGLE / 2.0f,
@@ -3047,6 +3344,9 @@
     if (!isSupported) {
         GTEST_SKIP() << "Microphone direction is not supported";
     }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No input mix ports could be routed to built-in microphone devices";
+    }
 }
 
 TEST_P(AudioStreamOut, OpenTwicePrimary) {
@@ -3061,65 +3361,79 @@
 }
 
 TEST_P(AudioStreamOut, RequireOffloadInfo) {
+    constexpr bool connectedOnly = true;
     const auto offloadMixPorts =
-            moduleConfig->getOffloadMixPorts(true /*connectedOnly*/, true /*singlePort*/);
+            moduleConfig->getOffloadMixPorts(connectedOnly, true /*singlePort*/);
     if (offloadMixPorts.empty()) {
         GTEST_SKIP()
                 << "No mix port for compressed offload that could be routed to attached devices";
     }
-    const auto config = moduleConfig->getSingleConfigForMixPort(false, *offloadMixPorts.begin());
-    ASSERT_TRUE(config.has_value()) << "No profiles specified for the compressed offload mix port";
-    WithAudioPortConfig portConfig(config.value());
-    ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
+    StreamFixture<IStreamOut> stream;
+    ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfigForMixPortOrConfig(
+            module.get(), moduleConfig.get(), *offloadMixPorts.begin(), connectedOnly));
+    if (auto reason = stream.skipTestReason(); !reason.empty()) {
+        GTEST_SKIP() << reason;
+    }
+    const auto portConfig = stream.getPortConfig();
     StreamDescriptor descriptor;
-    std::shared_ptr<IStreamOut> ignored;
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
-    args.portConfigId = portConfig.getId();
-    args.sourceMetadata = GenerateSourceMetadata(portConfig.get());
+    args.portConfigId = portConfig.id;
+    args.sourceMetadata = GenerateSourceMetadata(portConfig);
     args.bufferSizeFrames = kDefaultLargeBufferSizeFrames;
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
     EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
             << "when no offload info is provided for a compressed offload mix port";
+    if (ret.stream != nullptr) {
+        (void)WithStream<IStreamOut>::callClose(ret.stream);
+    }
 }
 
 TEST_P(AudioStreamOut, RequireAsyncCallback) {
+    constexpr bool connectedOnly = true;
     const auto nonBlockingMixPorts =
-            moduleConfig->getNonBlockingMixPorts(true /*connectedOnly*/, true /*singlePort*/);
+            moduleConfig->getNonBlockingMixPorts(connectedOnly, true /*singlePort*/);
     if (nonBlockingMixPorts.empty()) {
         GTEST_SKIP()
                 << "No mix port for non-blocking output that could be routed to attached devices";
     }
-    const auto config =
-            moduleConfig->getSingleConfigForMixPort(false, *nonBlockingMixPorts.begin());
-    ASSERT_TRUE(config.has_value()) << "No profiles specified for the non-blocking mix port";
-    WithAudioPortConfig portConfig(config.value());
-    ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
+    StreamFixture<IStreamOut> stream;
+    ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfigForMixPortOrConfig(
+            module.get(), moduleConfig.get(), *nonBlockingMixPorts.begin(), connectedOnly));
+    if (auto reason = stream.skipTestReason(); !reason.empty()) {
+        GTEST_SKIP() << reason;
+    }
+    const auto portConfig = stream.getPortConfig();
     StreamDescriptor descriptor;
-    std::shared_ptr<IStreamOut> ignored;
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
-    args.portConfigId = portConfig.getId();
-    args.sourceMetadata = GenerateSourceMetadata(portConfig.get());
-    args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig.get());
-    args.bufferSizeFrames = kDefaultBufferSizeFrames;
+    args.portConfigId = portConfig.id;
+    args.sourceMetadata = GenerateSourceMetadata(portConfig);
+    args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig);
+    args.bufferSizeFrames = stream.getPatch().minimumStreamBufferSizeFrames;
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
     EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
             << "when no async callback is provided for a non-blocking mix port";
+    if (ret.stream != nullptr) {
+        (void)WithStream<IStreamOut>::callClose(ret.stream);
+    }
 }
 
 TEST_P(AudioStreamOut, AudioDescriptionMixLevel) {
-    const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
+    constexpr bool connectedOnly = true;
+    const auto ports = moduleConfig->getOutputMixPorts(connectedOnly);
     if (ports.empty()) {
-        GTEST_SKIP() << "No output mix ports";
+        GTEST_SKIP() << "No output mix ports for attached devices";
     }
-    bool atLeastOneSupports = false;
+    bool atLeastOneSupports = false, atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for output mix port";
-        WithStream<IStreamOut> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamOut> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+                                                             connectedOnly));
+        if (!stream.skipTestReason().empty()) continue;
+        atLeastOnePort = true;
         bool isSupported = false;
         EXPECT_NO_FATAL_FAILURE(
-                TestAccessors<float>(stream.get(), &IStreamOut::getAudioDescriptionMixLevel,
+                TestAccessors<float>(stream.getStream(), &IStreamOut::getAudioDescriptionMixLevel,
                                      &IStreamOut::setAudioDescriptionMixLevel,
                                      {IStreamOut::AUDIO_DESCRIPTION_MIX_LEVEL_MAX,
                                       IStreamOut::AUDIO_DESCRIPTION_MIX_LEVEL_MAX - 1, 0,
@@ -3129,48 +3443,60 @@
                                      &isSupported));
         if (isSupported) atLeastOneSupports = true;
     }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No output mix ports could be routed to devices";
+    }
     if (!atLeastOneSupports) {
         GTEST_SKIP() << "Audio description mix level is not supported";
     }
 }
 
 TEST_P(AudioStreamOut, DualMonoMode) {
-    const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
+    constexpr bool connectedOnly = true;
+    const auto ports = moduleConfig->getOutputMixPorts(connectedOnly);
     if (ports.empty()) {
-        GTEST_SKIP() << "No output mix ports";
+        GTEST_SKIP() << "No output mix ports for attached devices";
     }
-    bool atLeastOneSupports = false;
+    bool atLeastOneSupports = false, atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for output mix port";
-        WithStream<IStreamOut> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamOut> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+                                                             connectedOnly));
+        if (!stream.skipTestReason().empty()) continue;
+        atLeastOnePort = true;
         bool isSupported = false;
         EXPECT_NO_FATAL_FAILURE(TestAccessors<AudioDualMonoMode>(
-                stream.get(), &IStreamOut::getDualMonoMode, &IStreamOut::setDualMonoMode,
+                stream.getStream(), &IStreamOut::getDualMonoMode, &IStreamOut::setDualMonoMode,
                 std::vector<AudioDualMonoMode>(enum_range<AudioDualMonoMode>().begin(),
                                                enum_range<AudioDualMonoMode>().end()),
                 {}, &isSupported));
         if (isSupported) atLeastOneSupports = true;
     }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No output mix ports could be routed to devices";
+    }
     if (!atLeastOneSupports) {
         GTEST_SKIP() << "Audio dual mono mode is not supported";
     }
 }
 
 TEST_P(AudioStreamOut, LatencyMode) {
-    const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
+    constexpr bool connectedOnly = true;
+    const auto ports = moduleConfig->getOutputMixPorts(connectedOnly);
     if (ports.empty()) {
-        GTEST_SKIP() << "No output mix ports";
+        GTEST_SKIP() << "No output mix ports for attached devices";
     }
-    bool atLeastOneSupports = false;
+    bool atLeastOneSupports = false, atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for output mix port";
-        WithStream<IStreamOut> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamOut> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+                                                             connectedOnly));
+        if (!stream.skipTestReason().empty()) continue;
+        atLeastOnePort = true;
         std::vector<AudioLatencyMode> supportedModes;
-        ndk::ScopedAStatus status = stream.get()->getRecommendedLatencyModes(&supportedModes);
+        ndk::ScopedAStatus status = stream.getStream()->getRecommendedLatencyModes(&supportedModes);
         if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) continue;
         atLeastOneSupports = true;
         if (!status.isOk()) {
@@ -3182,7 +3508,7 @@
                                                     enum_range<AudioLatencyMode>().end());
         for (const auto mode : supportedModes) {
             unsupportedModes.erase(mode);
-            ndk::ScopedAStatus status = stream.get()->setLatencyMode(mode);
+            ndk::ScopedAStatus status = stream.getStream()->setLatencyMode(mode);
             if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
                 ADD_FAILURE() << "When latency modes are supported, both getRecommendedLatencyModes"
                               << " and setLatencyMode must be supported";
@@ -3190,12 +3516,15 @@
             EXPECT_IS_OK(status) << "Setting of supported latency mode must succeed";
         }
         for (const auto mode : unsupportedModes) {
-            EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.get()->setLatencyMode(mode));
+            EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.getStream()->setLatencyMode(mode));
         }
     }
     if (!atLeastOneSupports) {
         GTEST_SKIP() << "Audio latency modes are not supported";
     }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No output mix ports could be routed to devices";
+    }
 }
 
 TEST_P(AudioStreamOut, PlaybackRate) {
@@ -3497,29 +3826,22 @@
         }
     }
 
-    bool ValidateObservablePosition(const AudioPortConfig& devicePortConfig) {
-        return !isTelephonyDeviceType(
-                devicePortConfig.ext.get<AudioPortExt::Tag::device>().device.type.type);
+    bool ValidateObservablePosition(const AudioDevice& device) {
+        return !isTelephonyDeviceType(device.type.type);
     }
 
     // Set up a patch first, then open a stream.
     void RunStreamIoCommandsImplSeq1(const AudioPortConfig& portConfig,
                                      std::shared_ptr<StateSequence> commandsAndStates,
                                      bool validatePositionIncrease) {
-        auto devicePorts = moduleConfig->getConnectedDevicesPortsForMixPort(
-                IOTraits<Stream>::is_input, portConfig);
-        ASSERT_FALSE(devicePorts.empty());
-        auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]);
-        SCOPED_TRACE(devicePortConfig.toString());
-        WithAudioPatch patch(IOTraits<Stream>::is_input, portConfig, devicePortConfig);
-        ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
-
-        WithStream<Stream> stream(patch.getPortConfig(IOTraits<Stream>::is_input));
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(
+                stream.SetUpStreamForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
+        ASSERT_EQ("", stream.skipTestReason());
         StreamLogicDefaultDriver driver(commandsAndStates,
-                                        stream.getContext()->getFrameSizeBytes());
-        typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
-                                                 stream.getEventReceiver());
+                                        stream.getStreamContext()->getFrameSizeBytes());
+        typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+                                                 stream.getStreamEventReceiver());
 
         LOG(DEBUG) << __func__ << ": starting worker...";
         ASSERT_TRUE(worker.start());
@@ -3527,7 +3849,7 @@
         worker.join();
         EXPECT_FALSE(worker.hasError()) << worker.getError();
         EXPECT_EQ("", driver.getUnexpectedStateTransition());
-        if (ValidateObservablePosition(devicePortConfig)) {
+        if (ValidateObservablePosition(stream.getDevice())) {
             if (validatePositionIncrease) {
                 EXPECT_TRUE(driver.hasObservablePositionIncrease());
             }
@@ -3535,24 +3857,21 @@
         }
     }
 
-    // Open a stream, then set up a patch for it.
+    // Open a stream, then set up a patch for it. Since first it is needed to get
+    // the minimum buffer size, a preliminary patch is set up, then removed.
     void RunStreamIoCommandsImplSeq2(const AudioPortConfig& portConfig,
                                      std::shared_ptr<StateSequence> commandsAndStates,
                                      bool validatePositionIncrease) {
-        WithStream<Stream> stream(portConfig);
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(
+                stream.SetUpPatchForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
+        ASSERT_EQ("", stream.skipTestReason());
+        ASSERT_NO_FATAL_FAILURE(stream.TeardownPatchSetUpStream(module.get()));
         StreamLogicDefaultDriver driver(commandsAndStates,
-                                        stream.getContext()->getFrameSizeBytes());
-        typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
-                                                 stream.getEventReceiver());
-
-        auto devicePorts = moduleConfig->getConnectedDevicesPortsForMixPort(
-                IOTraits<Stream>::is_input, portConfig);
-        ASSERT_FALSE(devicePorts.empty());
-        auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]);
-        SCOPED_TRACE(devicePortConfig.toString());
-        WithAudioPatch patch(IOTraits<Stream>::is_input, stream.getPortConfig(), devicePortConfig);
-        ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+                                        stream.getStreamContext()->getFrameSizeBytes());
+        typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+                                                 stream.getStreamEventReceiver());
+        ASSERT_NO_FATAL_FAILURE(stream.ReconnectPatch(module.get()));
 
         LOG(DEBUG) << __func__ << ": starting worker...";
         ASSERT_TRUE(worker.start());
@@ -3560,7 +3879,7 @@
         worker.join();
         EXPECT_FALSE(worker.hasError()) << worker.getError();
         EXPECT_EQ("", driver.getUnexpectedStateTransition());
-        if (ValidateObservablePosition(devicePortConfig)) {
+        if (ValidateObservablePosition(stream.getDevice())) {
             if (validatePositionIncrease) {
                 EXPECT_TRUE(driver.hasObservablePositionIncrease());
             }
@@ -4254,59 +4573,41 @@
     explicit WithRemoteSubmix(AudioDeviceAddress address) : mAddress(address) {}
     WithRemoteSubmix(const WithRemoteSubmix&) = delete;
     WithRemoteSubmix& operator=(const WithRemoteSubmix&) = delete;
+
     static std::optional<AudioPort> getRemoteSubmixAudioPort(
             ModuleConfig* moduleConfig,
             const std::optional<AudioDeviceAddress>& address = std::nullopt) {
-        AudioDeviceType deviceType = IOTraits<Stream>::is_input ? AudioDeviceType::IN_SUBMIX
-                                                                : AudioDeviceType::OUT_SUBMIX;
-        auto ports = moduleConfig->getAudioPortsForDeviceTypes(
-                std::vector<AudioDeviceType>{deviceType},
-                AudioDeviceDescription::CONNECTION_VIRTUAL);
+        auto ports =
+                moduleConfig->getRemoteSubmixPorts(IOTraits<Stream>::is_input, true /*singlePort*/);
         if (ports.empty()) return {};
         AudioPort port = ports.front();
         if (address) {
             port.ext.template get<AudioPortExt::Tag::device>().device.address = address.value();
-        } else {
-            port = GenerateUniqueDeviceAddress(port);
         }
         return port;
     }
-    std::optional<AudioDeviceAddress> getAudioDeviceAddress() const { return mAddress; }
-    void SetUp(IModule* module, ModuleConfig* moduleConfig, const AudioPort& connectedPort) {
-        mModule = module;
-        mModuleConfig = moduleConfig;
 
-        ASSERT_NO_FATAL_FAILURE(SetupPatch(connectedPort));
-        if (!mSkipTest) {
-            // open stream
-            mStream = std::make_unique<WithStream<Stream>>(
-                    mPatch->getPortConfig(IOTraits<Stream>::is_input));
-            ASSERT_NO_FATAL_FAILURE(
-                    mStream->SetUp(mModule, AudioCoreModuleBase::kDefaultBufferSizeFrames));
-        }
-        mAddress = connectedPort.ext.template get<AudioPortExt::Tag::device>().device.address;
-    }
     void SetUp(IModule* module, ModuleConfig* moduleConfig) {
-        ASSERT_NO_FATAL_FAILURE(SetUpPortConnection(module, moduleConfig));
-        SetUp(module, moduleConfig, mConnectedPort->get());
+        auto devicePort = getRemoteSubmixAudioPort(moduleConfig, mAddress);
+        ASSERT_TRUE(devicePort.has_value()) << "Device port for remote submix device not found";
+        ASSERT_NO_FATAL_FAILURE(SetUp(module, moduleConfig, *devicePort));
     }
 
-    void sendBurstCommandsStartWorker() {
-        const StreamContext* context = mStream->getContext();
+    void SendBurstCommandsStartWorker() {
+        const StreamContext* context = mStream->getStreamContext();
         mWorkerDriver = std::make_unique<StreamLogicDefaultDriver>(makeBurstCommands(true),
                                                                    context->getFrameSizeBytes());
-        mWorker = std::make_unique<typename IOTraits<Stream>::Worker>(*context, mWorkerDriver.get(),
-                                                                      mStream->getEventReceiver());
-
+        mWorker = std::make_unique<typename IOTraits<Stream>::Worker>(
+                *context, mWorkerDriver.get(), mStream->getStreamEventReceiver());
         LOG(DEBUG) << __func__ << ": starting " << IOTraits<Stream>::directionStr << " worker...";
         ASSERT_TRUE(mWorker->start());
     }
 
-    void sendBurstCommandsJoinWorker() {
+    void SendBurstCommandsJoinWorker() {
         // Must call 'prepareToClose' before attempting to join because the stream may be
         // stuck due to absence of activity from the other side of the remote submix pipe.
         std::shared_ptr<IStreamCommon> common;
-        ASSERT_IS_OK(mStream->get()->getStreamCommon(&common));
+        ASSERT_IS_OK(mStream->getStream()->getStreamCommon(&common));
         ASSERT_IS_OK(common->prepareToClose());
         LOG(DEBUG) << __func__ << ": joining " << IOTraits<Stream>::directionStr << " worker...";
         mWorker->join();
@@ -4320,43 +4621,24 @@
         mWorkerDriver.reset();
     }
 
-    void sendBurstCommands() {
-        ASSERT_NO_FATAL_FAILURE(sendBurstCommandsStartWorker());
-        ASSERT_NO_FATAL_FAILURE(sendBurstCommandsJoinWorker());
+    void SendBurstCommands() {
+        ASSERT_NO_FATAL_FAILURE(SendBurstCommandsStartWorker());
+        ASSERT_NO_FATAL_FAILURE(SendBurstCommandsJoinWorker());
     }
 
-    bool skipTest() const { return mSkipTest; }
+    std::optional<AudioDeviceAddress> getAudioDeviceAddress() const { return mAddress; }
+    std::string skipTestReason() const { return mStream->skipTestReason(); }
 
   private:
-    /* Connect remote submix external device */
-    void SetUpPortConnection(IModule* module, ModuleConfig* moduleConfig) {
-        auto port = getRemoteSubmixAudioPort(moduleConfig, mAddress);
-        ASSERT_TRUE(port.has_value()) << "Device AudioPort for remote submix not found";
-        mConnectedPort = std::make_unique<WithDevicePortConnectedState>(port.value());
-        ASSERT_NO_FATAL_FAILURE(mConnectedPort->SetUp(module, moduleConfig));
-    }
-    /* Get mix port config for stream and setup patch for it. */
-    void SetupPatch(const AudioPort& connectedPort) {
-        const auto portConfig =
-                mModuleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            LOG(DEBUG) << __func__ << ": portConfig not found";
-            mSkipTest = true;
-            return;
-        }
-        auto devicePortConfig = mModuleConfig->getSingleConfigForDevicePort(connectedPort);
-        mPatch = std::make_unique<WithAudioPatch>(IOTraits<Stream>::is_input, portConfig.value(),
-                                                  devicePortConfig);
-        ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(mModule));
+    void SetUp(IModule* module, ModuleConfig* moduleConfig, const AudioPort& devicePort) {
+        mStream = std::make_unique<StreamFixture<Stream>>();
+        ASSERT_NO_FATAL_FAILURE(
+                mStream->SetUpStreamForDevicePort(module, moduleConfig, devicePort));
+        mAddress = mStream->getDevice().address;
     }
 
-    bool mSkipTest = false;
-    IModule* mModule = nullptr;
-    ModuleConfig* mModuleConfig = nullptr;
     std::optional<AudioDeviceAddress> mAddress;
-    std::unique_ptr<WithDevicePortConnectedState> mConnectedPort;
-    std::unique_ptr<WithAudioPatch> mPatch;
-    std::unique_ptr<WithStream<Stream>> mStream;
+    std::unique_ptr<StreamFixture<Stream>> mStream;
     std::unique_ptr<StreamLogicDefaultDriver> mWorkerDriver;
     std::unique_ptr<typename IOTraits<Stream>::Worker> mWorker;
 };
@@ -4364,98 +4646,82 @@
 class AudioModuleRemoteSubmix : public AudioCoreModule {
   public:
     void SetUp() override {
-        ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp());
+        // Turn off "debug" which enables connections simulation. Since devices of the remote
+        // submix module are virtual, there is no need for simulation.
+        ASSERT_NO_FATAL_FAILURE(SetUpImpl(GetParam(), false /*setUpDebug*/));
         ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
     }
-
-    void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownImpl()); }
 };
 
 TEST_P(AudioModuleRemoteSubmix, OutputDoesNotBlockWhenNoInput) {
     WithRemoteSubmix<IStreamOut> streamOut;
     ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    if (streamOut.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
-    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
+    // Note: here and in other tests any issue with connection attempts is considered as a problem.
+    ASSERT_EQ("", streamOut.skipTestReason());
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommands());
 }
 
 TEST_P(AudioModuleRemoteSubmix, OutputDoesNotBlockWhenInputStuck) {
     WithRemoteSubmix<IStreamOut> streamOut;
     ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    if (streamOut.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
+    ASSERT_EQ("", streamOut.skipTestReason());
     auto address = streamOut.getAudioDeviceAddress();
     ASSERT_TRUE(address.has_value());
 
     WithRemoteSubmix<IStreamIn> streamIn(address.value());
     ASSERT_NO_FATAL_FAILURE(streamIn.SetUp(module.get(), moduleConfig.get()));
-    if (streamIn.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
+    ASSERT_EQ("", streamIn.skipTestReason());
 
-    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommands());
 }
 
 TEST_P(AudioModuleRemoteSubmix, OutputAndInput) {
     WithRemoteSubmix<IStreamOut> streamOut;
     ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    if (streamOut.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
+    ASSERT_EQ("", streamOut.skipTestReason());
     auto address = streamOut.getAudioDeviceAddress();
     ASSERT_TRUE(address.has_value());
 
     WithRemoteSubmix<IStreamIn> streamIn(address.value());
     ASSERT_NO_FATAL_FAILURE(streamIn.SetUp(module.get(), moduleConfig.get()));
-    if (streamIn.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
+    ASSERT_EQ("", streamIn.skipTestReason());
 
     // Start writing into the output stream.
-    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommandsStartWorker());
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsStartWorker());
     // Simultaneously, read from the input stream.
-    ASSERT_NO_FATAL_FAILURE(streamIn.sendBurstCommands());
-    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommandsJoinWorker());
+    ASSERT_NO_FATAL_FAILURE(streamIn.SendBurstCommands());
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsJoinWorker());
 }
 
 TEST_P(AudioModuleRemoteSubmix, OpenInputMultipleTimes) {
     WithRemoteSubmix<IStreamOut> streamOut;
     ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    if (streamOut.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
+    ASSERT_EQ("", streamOut.skipTestReason());
     auto address = streamOut.getAudioDeviceAddress();
     ASSERT_TRUE(address.has_value());
 
-    // Connect remote submix input device port.
-    auto port = WithRemoteSubmix<IStreamIn>::getRemoteSubmixAudioPort(moduleConfig.get(),
-                                                                      address.value());
-    ASSERT_TRUE(port.has_value()) << "Device AudioPort for remote submix not found";
-    WithDevicePortConnectedState connectedInputPort(port.value());
-    ASSERT_NO_FATAL_FAILURE(connectedInputPort.SetUp(module.get(), moduleConfig.get()));
-
-    const int streamInCount = 3;
+    const size_t streamInCount = 3;
     std::vector<std::unique_ptr<WithRemoteSubmix<IStreamIn>>> streamIns(streamInCount);
-    for (int i = 0; i < streamInCount; i++) {
-        streamIns[i] = std::make_unique<WithRemoteSubmix<IStreamIn>>();
-        ASSERT_NO_FATAL_FAILURE(
-                streamIns[i]->SetUp(module.get(), moduleConfig.get(), connectedInputPort.get()));
-        if (streamIns[i]->skipTest()) {
-            GTEST_SKIP() << "No mix port for attached devices";
-        }
+    for (size_t i = 0; i < streamInCount; i++) {
+        streamIns[i] = std::make_unique<WithRemoteSubmix<IStreamIn>>(address.value());
+        ASSERT_NO_FATAL_FAILURE(streamIns[i]->SetUp(module.get(), moduleConfig.get()));
+        ASSERT_EQ("", streamIns[i]->skipTestReason());
     }
     // Start writing into the output stream.
-    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommandsStartWorker());
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsStartWorker());
     // Simultaneously, read from input streams.
-    for (int i = 0; i < streamInCount; i++) {
-        ASSERT_NO_FATAL_FAILURE(streamIns[i]->sendBurstCommandsStartWorker());
+    for (size_t i = 0; i < streamInCount; i++) {
+        ASSERT_NO_FATAL_FAILURE(streamIns[i]->SendBurstCommandsStartWorker());
     }
-    for (int i = 0; i < streamInCount; i++) {
-        ASSERT_NO_FATAL_FAILURE(streamIns[i]->sendBurstCommandsJoinWorker());
+    for (size_t i = 0; i < streamInCount; i++) {
+        ASSERT_NO_FATAL_FAILURE(streamIns[i]->SendBurstCommandsJoinWorker());
     }
-    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommandsJoinWorker());
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsJoinWorker());
+    // Clean up input streams in the reverse order because the device connection is owned
+    // by the first one.
+    for (size_t i = streamInCount; i != 0; --i) {
+        streamIns[i - 1].reset();
+    }
 }
 
 INSTANTIATE_TEST_SUITE_P(AudioModuleRemoteSubmixTest, AudioModuleRemoteSubmix,