From 95f2277730407b045d73563fc7bc462ebd61441d Mon Sep 17 00:00:00 2001 From: Mikhail Naganov Date: Wed, 25 Oct 2023 08:44:47 -0700 Subject: [PATCH] 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 --- audio/aidl/vts/ModuleConfig.cpp | 90 +- audio/aidl/vts/ModuleConfig.h | 15 +- audio/aidl/vts/TestUtils.h | 3 +- .../vts/VtsHalAudioCoreModuleTargetTest.cpp | 954 +++++++++++------- 4 files changed, 683 insertions(+), 379 deletions(-) diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp index 8f19547e45..a633d837a5 100644 --- a/audio/aidl/vts/ModuleConfig.cpp +++ b/audio/aidl/vts/ModuleConfig.cpp @@ -67,20 +67,7 @@ std::optional ModuleConfig::generateOffloadInfoIfNeeded( return {}; } -std::vector -ModuleConfig::getAudioPortsForDeviceTypes(const std::vector& deviceTypes, - const std::string& connection) { - return getAudioPortsForDeviceTypes(mPorts, deviceTypes, connection); -} - // static -std::vector ModuleConfig::getBuiltInMicPorts( - const std::vector& ports) { - return getAudioPortsForDeviceTypes( - ports, std::vector{AudioDeviceType::IN_MICROPHONE, - AudioDeviceType::IN_MICROPHONE_BACK}); -} - std::vector ModuleConfig::getAudioPortsForDeviceTypes( const std::vector& ports, @@ -100,6 +87,14 @@ ModuleConfig::getAudioPortsForDeviceTypes( return result; } +// static +std::vector ModuleConfig::getBuiltInMicPorts( + const std::vector& ports) { + return getAudioPortsForDeviceTypes( + ports, std::vector{AudioDeviceType::IN_MICROPHONE, + AudioDeviceType::IN_MICROPHONE_BACK}); +} + template auto findById(const std::vector& v, int32_t id) { return std::find_if(v.begin(), v.end(), [&](const auto& p) { return p.id == id; }); @@ -119,10 +114,7 @@ ModuleConfig::ModuleConfig(IModule* module) { } 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 @@ std::vector ModuleConfig::getAttachedDevicePorts() const { return result; } +std::vector +ModuleConfig::getAudioPortsForDeviceTypes(const std::vector& deviceTypes, + const std::string& connection) const { + return getAudioPortsForDeviceTypes(mPorts, deviceTypes, connection); +} + std::vector ModuleConfig::getConnectedExternalDevicePorts() const { std::vector result; std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) { @@ -229,6 +227,16 @@ std::vector ModuleConfig::getMmapInMixPorts(bool connectedOnly, bool }); } +std::vector ModuleConfig::getRemoteSubmixPorts(bool isInput, bool singlePort) const { + AudioDeviceType deviceType = isInput ? AudioDeviceType::IN_SUBMIX : AudioDeviceType::OUT_SUBMIX; + auto ports = getAudioPortsForDeviceTypes(std::vector{deviceType}, + AudioDeviceDescription::CONNECTION_VIRTUAL); + if (singlePort) { + if (!ports.empty()) ports.resize(1); + } + return ports; +} + std::vector ModuleConfig::getConnectedDevicesPortsForMixPort( bool isInput, const AudioPortConfig& mixPortConfig) const { const auto mixPortIt = findById(mPorts, mixPortConfig.portId); @@ -281,19 +289,29 @@ std::optional ModuleConfig::getSourceMixPortForConnectedDevice() cons return {}; } -std::vector ModuleConfig::getRoutableMixPortsForDevicePort(const AudioPort& port) const { - std::set portIds; - for (const auto& route : mRoutes) { - if (port.id == route.sinkPortId) { - portIds.insert(route.sourcePortIds.begin(), route.sourcePortIds.end()); - } else if (auto it = std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(), - port.id); - it != route.sourcePortIds.end()) { - portIds.insert(route.sinkPortId); - } - } +std::vector ModuleConfig::getRoutableDevicePortsForMixPort(const AudioPort& port, + bool connectedOnly) const { + std::set portIds = findRoutablePortIds(port.id); const bool isInput = port.flags.getTag() == AudioIoFlags::input; - return findMixPorts(isInput, false /*connectedOnly*/, false /*singlePort*/, + std::set devicePortIds; + if (connectedOnly) { + devicePortIds = isInput ? getConnectedSourceDevicePorts() : getConnectedSinkDevicePorts(); + } else { + devicePortIds = portIds; + } + std::vector 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 ModuleConfig::getRoutableMixPortsForDevicePort(const AudioPort& port, + bool connectedOnly) const { + std::set 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 @@ std::vector ModuleConfig::findMixPorts( return result; } +std::set ModuleConfig::findRoutablePortIds(int32_t portId) const { + std::set 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 ModuleConfig::generateAudioMixPortConfigs( const std::vector& ports, bool isInput, bool singleProfile) const { std::vector result; diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h index b89adc0dfd..4a87f8cbd2 100644 --- a/audio/aidl/vts/ModuleConfig.h +++ b/audio/aidl/vts/ModuleConfig.h @@ -38,9 +38,6 @@ class ModuleConfig { generateOffloadInfoIfNeeded( const aidl::android::media::audio::common::AudioPortConfig& portConfig); - std::vector getAudioPortsForDeviceTypes( - const std::vector& deviceTypes, - const std::string& connection = ""); static std::vector getAudioPortsForDeviceTypes( const std::vector& ports, const std::vector& deviceTypes, @@ -53,6 +50,9 @@ class ModuleConfig { std::string getError() const { return mStatus.getMessage(); } std::vector getAttachedDevicePorts() const; + std::vector getAudioPortsForDeviceTypes( + const std::vector& deviceTypes, + const std::string& connection = "") const; std::vector getConnectedExternalDevicePorts() const; std::set getConnectedSinkDevicePorts() const; @@ -85,6 +85,8 @@ class ModuleConfig { std::vector getMmapInMixPorts( bool connectedOnly /*Permanently attached and connected external devices*/, bool singlePort) const; + std::vector getRemoteSubmixPorts( + bool isInput, bool singlePort) const; std::vector getConnectedDevicesPortsForMixPort( bool isInput, const aidl::android::media::audio::common::AudioPort& mixPort) const { @@ -103,8 +105,12 @@ class ModuleConfig { std::optional getSourceMixPortForConnectedDevice() const; + std::vector getRoutableDevicePortsForMixPort( + const aidl::android::media::audio::common::AudioPort& port, + bool connectedOnly /*Permanently attached and connected external devices*/) const; std::vector 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 getNonRoutableSrcSinkPair(bool isInput) const; std::optional getRoutableSrcSinkPair(bool isInput) const; @@ -176,6 +182,7 @@ class ModuleConfig { bool isInput, bool connectedOnly, bool singlePort, const std::function& pred) const; + std::set findRoutablePortIds(int32_t portId) const; std::vector generateAudioMixPortConfigs( const std::vector& ports, bool isInput, bool singleProfile) const; diff --git a/audio/aidl/vts/TestUtils.h b/audio/aidl/vts/TestUtils.h index 191e98002e..e9f3251c6b 100644 --- a/audio/aidl/vts/TestUtils.h +++ b/audio/aidl/vts/TestUtils.h @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -93,4 +92,4 @@ inline ::testing::AssertionResult assertResult(const char* exp_expr, const char* 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 d0fc4a498a..536bc26481 100644 --- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp +++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp @@ -113,10 +113,23 @@ using ndk::enum_range; using ndk::ScopedAStatus; template -auto findById(std::vector& v, int32_t id) { +std::set extractIds(const std::vector& v) { + std::set ids; + std::transform(v.begin(), v.end(), std::inserter(ids, ids.begin()), + [](const auto& entity) { return entity.id; }); + return ids; +} + +template +auto findById(const std::vector& v, int32_t id) { return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; }); } +template +auto findAny(const std::vector& v, const std::set& ids) { + return std::find_if(v.begin(), v.end(), [&](const auto& e) { return ids.count(e.id) > 0; }); +} + template std::vector GetNonExistentIds(const C& allIds) { if (allIds.empty()) { @@ -155,8 +168,10 @@ AudioPort GenerateUniqueDeviceAddress(const AudioPort& port) { static int nextId = 0; using Tag = AudioDeviceAddress::Tag; const auto& deviceDescription = port.ext.get().device.type; - AudioDeviceAddress address; - if (kPointToPointConnections.count(deviceDescription.connection) == 0) { + AudioDeviceAddress address = port.ext.get().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(std::to_string(++nextId)); @@ -417,12 +432,14 @@ void TestSetVendorParameters(Instance* inst, bool* isSupported) { // 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 @@ class AudioCoreModuleBase { << "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 @@ class AudioCoreModuleBase { const std::string& errorMessage) { std::vector 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(entities); EXPECT_EQ(entities.size(), entityIds->size()) << errorMessage; } @@ -1144,8 +1164,7 @@ class WithStream { } 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 @@ class WithStream { 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 @@ class WithAudioPatch { } 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 @@ TEST_P(AudioCoreModule, GetAudioPortWithExternalDevices) { 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 @@ TEST_P(AudioCoreModule, OpenStreamInvalidPortConfigId) { { 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 @@ TEST_P(AudioCoreModule, OpenStreamInvalidPortConfigId) { { 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 @@ TEST_P(AudioCoreModule, TryConnectMissingDevice) { 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().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 @@ class StreamLogicDriverInvalidCommand : public StreamLogicDriver { std::vector 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 +class StreamFixture { + public: + // Tests might need to override the direction. + StreamFixture(bool isInput = IOTraits::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& 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 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>(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(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(ports, mixPortConfig.portId); + ASSERT_NE(mixPortIt, ports.end()) << "Port id " << mixPortConfig.portId << " not found"; + std::optional 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(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>(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 getStreamSharedPointer() const { return mStream->getSharedPointer(); } + const std::string& skipTestReason() const { return mSkipTestReason; } + + private: + void SetUpDevicePort(IModule* module, ModuleConfig* moduleConfig, + const std::set& devicePortIds, bool connectedOnly, + std::optional* connectedDevicePort) { + const auto attachedDevicePorts = moduleConfig->getAttachedDevicePorts(); + if (auto it = findAny(attachedDevicePorts, devicePortIds); + it != attachedDevicePorts.end()) { + *connectedDevicePort = *it; + LOG(DEBUG) << __func__ << ": found attached port " << it->toString(); + } + const auto connectedDevicePorts = moduleConfig->getConnectedExternalDevicePorts(); + if (auto it = findAny(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(externalDevicePorts, devicePortIds); + it != externalDevicePorts.end()) { + AudioPort portWithData = GenerateUniqueDeviceAddress(*it); + mPortConnected = std::make_unique(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* 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(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 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(mixPortConfig); + ASSERT_NO_FATAL_FAILURE(mMixPortConfig->SetUp(module)); + mDevicePortConfig = std::make_unique( + moduleConfig->getSingleConfigForDevicePort(devicePort)); + ASSERT_NO_FATAL_FAILURE(mDevicePortConfig->SetUp(module)); + mDevice = devicePort.ext.get().device; + mPatch = std::make_unique(mIsInput, mMixPortConfig->get(), + mDevicePortConfig->get()); + ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(module)); + } + + const bool mIsInput; + std::string mSkipTestReason; + std::unique_ptr mPortConnected; + AudioDevice mDevice; + std::unique_ptr mMixPortConfig; + std::unique_ptr mDevicePortConfig; + std::unique_ptr mPatch; + std::unique_ptr> mStream; +}; + template class AudioStream : public AudioCoreModule { public: @@ -2573,16 +2857,15 @@ class AudioStream : public AudioCoreModule { } void GetStreamCommon() { - const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); - if (!portConfig.has_value()) { - GTEST_SKIP() << "No mix port for attached devices"; + StreamFixture stream; + ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get())); + if (auto reason = stream.skipTestReason(); !reason.empty()) { + GTEST_SKIP() << reason; } - WithStream stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); std::shared_ptr streamCommon1; - EXPECT_IS_OK(stream.get()->getStreamCommon(&streamCommon1)); + EXPECT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon1)); std::shared_ptr 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 @@ class AudioStream : public AudioCoreModule { } void CloseTwice() { - const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); - if (!portConfig.has_value()) { - GTEST_SKIP() << "No mix port for attached devices"; - } std::shared_ptr heldStream; { - WithStream stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); - heldStream = stream.getSharedPointer(); + StreamFixture 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::callClose(heldStream)) << "when closing the stream twice"; } void PrepareToCloseTwice() { - const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); - if (!portConfig.has_value()) { - GTEST_SKIP() << "No mix port for attached devices"; - } std::shared_ptr heldStreamCommon; { - WithStream stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + StreamFixture stream; + ASSERT_NO_FATAL_FAILURE( + stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get())); + if (auto reason = stream.skipTestReason(); !reason.empty()) { + GTEST_SKIP() << reason; + } std::shared_ptr 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 @@ class AudioStream : public AudioCoreModule { void OpenAllConfigs() { const auto allPortConfigs = moduleConfig->getPortConfigsForMixPorts(IOTraits::is_input); + if (allPortConfigs.empty()) { + GTEST_SKIP() << "No mix ports for attached devices"; + } for (const auto& portConfig : allPortConfigs) { - WithStream stream(portConfig); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + StreamFixture stream; + ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPortConfig( + module.get(), moduleConfig.get(), portConfig)); } } @@ -2649,22 +2936,21 @@ class AudioStream : public AudioCoreModule { void OpenInvalidDirection() { // Important! The direction of the port config must be reversed. - const auto portConfig = - moduleConfig->getSingleConfigForMixPort(!IOTraits::is_input); - if (!portConfig.has_value()) { - GTEST_SKIP() << "No mix port for attached devices"; + StreamFixture stream(!IOTraits::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(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::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().maxOpenStreamCount; @@ -2677,16 +2963,16 @@ class AudioStream : public AudioCoreModule { continue; } hasSingleRun = true; - std::optional> streamWraps[maxStreamCount + 1]; + StreamFixture streams[maxStreamCount + 1]; for (size_t i = 0; i <= maxStreamCount; ++i) { - streamWraps[i].emplace(portConfigs[i]); - WithStream& 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 @@ class AudioStream : public AudioCoreModule { } void ResetPortConfigWithOpenStream() { - const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); - if (!portConfig.has_value()) { - GTEST_SKIP() << "No mix port for attached devices"; + StreamFixture stream; + ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get())); + if (auto reason = stream.skipTestReason(); !reason.empty()) { + GTEST_SKIP() << reason; } - WithStream 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 @@ class AudioStream : public AudioCoreModule { } void UpdateHwAvSyncId() { - const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); - if (!portConfig.has_value()) { - GTEST_SKIP() << "No mix port for attached devices"; + StreamFixture stream; + ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get())); + if (auto reason = stream.skipTestReason(); !reason.empty()) { + GTEST_SKIP() << reason; } - WithStream stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); std::shared_ptr 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 @@ class AudioStream : public AudioCoreModule { } void GetVendorParameters() { - const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); - if (!portConfig.has_value()) { - GTEST_SKIP() << "No mix port for attached devices"; + StreamFixture stream; + ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get())); + if (auto reason = stream.skipTestReason(); !reason.empty()) { + GTEST_SKIP() << reason; } - WithStream stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); std::shared_ptr 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 @@ class AudioStream : public AudioCoreModule { } void SetVendorParameters() { - const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); - if (!portConfig.has_value()) { - GTEST_SKIP() << "No mix port for attached devices"; + StreamFixture stream; + ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get())); + if (auto reason = stream.skipTestReason(); !reason.empty()) { + GTEST_SKIP() << reason; } - WithStream stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); std::shared_ptr 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 @@ class AudioStream : public AudioCoreModule { } void HwGainHwVolume() { - const auto ports = - moduleConfig->getMixPorts(IOTraits::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::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(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + SCOPED_TRACE(port.toString()); + StreamFixture 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> validValues, invalidValues; bool isSupported = false; if constexpr (IOTraits::is_input) { - GenerateTestArrays(getChannelCount(portConfig.value().channelMask.value()), + GenerateTestArrays(getChannelCount(portConfig.channelMask.value()), IStreamIn::HW_GAIN_MIN, IStreamIn::HW_GAIN_MAX, &validValues, &invalidValues); EXPECT_NO_FATAL_FAILURE(TestAccessors>( - stream.get(), &IStreamIn::getHwGain, &IStreamIn::setHwGain, validValues, - invalidValues, &isSupported)); + stream.getStream(), &IStreamIn::getHwGain, &IStreamIn::setHwGain, + validValues, invalidValues, &isSupported)); } else { - GenerateTestArrays(getChannelCount(portConfig.value().channelMask.value()), + GenerateTestArrays(getChannelCount(portConfig.channelMask.value()), IStreamOut::HW_VOLUME_MIN, IStreamOut::HW_VOLUME_MAX, &validValues, &invalidValues); EXPECT_NO_FATAL_FAILURE(TestAccessors>( - stream.get(), &IStreamOut::getHwVolume, &IStreamOut::setHwVolume, + stream.getStream(), &IStreamOut::getHwVolume, &IStreamOut::setHwVolume, validValues, invalidValues, &isSupported)); } if (isSupported) atLeastOneSupports = true; @@ -2823,19 +3110,22 @@ class AudioStream : public AudioCoreModule { // 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::is_input, true /*connectedOnly*/); + constexpr bool connectedOnly = true; + const auto ports = moduleConfig->getMixPorts(IOTraits::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(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + SCOPED_TRACE(port.toString()); + StreamFixture 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 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 @@ class AudioStream : public AudioCoreModule { } void OpenTwiceSamePortConfigImpl(const AudioPortConfig& portConfig) { - WithStream stream1(portConfig); - ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get(), kDefaultBufferSizeFrames)); + StreamFixture stream1; + ASSERT_NO_FATAL_FAILURE( + stream1.SetUpStreamForMixPortConfig(module.get(), moduleConfig.get(), portConfig)); + ASSERT_EQ("", stream1.skipTestReason()); WithStream 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 @@ class AudioStream : public AudioCoreModule { for (const auto& seq : sequences) { SCOPED_TRACE(std::string("Sequence ").append(seq.first)); LOG(DEBUG) << __func__ << ": Sequence " << seq.first; - WithStream stream(portConfig); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + StreamFixture stream; + ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPortConfig( + module.get(), moduleConfig.get(), portConfig)); + ASSERT_EQ("", stream.skipTestReason()); StreamLogicDriverInvalidCommand driver(seq.second); - typename IOTraits::Worker worker(*stream.getContext(), &driver, - stream.getEventReceiver()); + typename IOTraits::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 @@ TEST_P(AudioStreamIn, ActiveMicrophones) { 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 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 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"; - } - 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 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 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 stream; + ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForPortsPair(module.get(), moduleConfig.get(), + port, micDevicePorts[0])); + if (!stream.skipTestReason().empty()) continue; + std::vector 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 << "\""; } + stream.TeardownPatch(); + // Now the port of the stream is not connected, check that there are no active microphones. + std::vector 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 stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + SCOPED_TRACE(port.toString()); + StreamFixture 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(stream.get(), &IStreamIn::getMicrophoneDirection, + TestAccessors(stream.getStream(), &IStreamIn::getMicrophoneDirection, &IStreamIn::setMicrophoneDirection, std::vector(enum_range().begin(), enum_range().end()), {}, &isSupported)); @@ -3016,21 +3307,27 @@ TEST_P(AudioStreamIn, MicrophoneDirection) { 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 stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + SCOPED_TRACE(port.toString()); + StreamFixture 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( - 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 @@ TEST_P(AudioStreamIn, MicrophoneFieldDimension) { 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, OpenTwicePrimary) { } 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 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 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::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 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 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::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 stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + SCOPED_TRACE(port.toString()); + StreamFixture 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(stream.get(), &IStreamOut::getAudioDescriptionMixLevel, + TestAccessors(stream.getStream(), &IStreamOut::getAudioDescriptionMixLevel, &IStreamOut::setAudioDescriptionMixLevel, {IStreamOut::AUDIO_DESCRIPTION_MIX_LEVEL_MAX, IStreamOut::AUDIO_DESCRIPTION_MIX_LEVEL_MAX - 1, 0, @@ -3129,48 +3443,60 @@ TEST_P(AudioStreamOut, AudioDescriptionMixLevel) { &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 stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + SCOPED_TRACE(port.toString()); + StreamFixture 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( - stream.get(), &IStreamOut::getDualMonoMode, &IStreamOut::setDualMonoMode, + stream.getStream(), &IStreamOut::getDualMonoMode, &IStreamOut::setDualMonoMode, std::vector(enum_range().begin(), enum_range().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 stream(portConfig.value()); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + SCOPED_TRACE(port.toString()); + StreamFixture stream; + ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port, + connectedOnly)); + if (!stream.skipTestReason().empty()) continue; + atLeastOnePort = true; std::vector 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 @@ TEST_P(AudioStreamOut, LatencyMode) { enum_range().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 @@ TEST_P(AudioStreamOut, LatencyMode) { 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 @@ class AudioStreamIo : public AudioCoreModuleBase, } } - bool ValidateObservablePosition(const AudioPortConfig& devicePortConfig) { - return !isTelephonyDeviceType( - devicePortConfig.ext.get().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 commandsAndStates, bool validatePositionIncrease) { - auto devicePorts = moduleConfig->getConnectedDevicesPortsForMixPort( - IOTraits::is_input, portConfig); - ASSERT_FALSE(devicePorts.empty()); - auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]); - SCOPED_TRACE(devicePortConfig.toString()); - WithAudioPatch patch(IOTraits::is_input, portConfig, devicePortConfig); - ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); - - WithStream stream(patch.getPortConfig(IOTraits::is_input)); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + StreamFixture stream; + ASSERT_NO_FATAL_FAILURE( + stream.SetUpStreamForMixPortConfig(module.get(), moduleConfig.get(), portConfig)); + ASSERT_EQ("", stream.skipTestReason()); StreamLogicDefaultDriver driver(commandsAndStates, - stream.getContext()->getFrameSizeBytes()); - typename IOTraits::Worker worker(*stream.getContext(), &driver, - stream.getEventReceiver()); + stream.getStreamContext()->getFrameSizeBytes()); + typename IOTraits::Worker worker(*stream.getStreamContext(), &driver, + stream.getStreamEventReceiver()); LOG(DEBUG) << __func__ << ": starting worker..."; ASSERT_TRUE(worker.start()); @@ -3527,7 +3849,7 @@ class AudioStreamIo : public AudioCoreModuleBase, 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 @@ class AudioStreamIo : public AudioCoreModuleBase, } } - // 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 commandsAndStates, bool validatePositionIncrease) { - WithStream stream(portConfig); - ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); + StreamFixture 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::Worker worker(*stream.getContext(), &driver, - stream.getEventReceiver()); - - auto devicePorts = moduleConfig->getConnectedDevicesPortsForMixPort( - IOTraits::is_input, portConfig); - ASSERT_FALSE(devicePorts.empty()); - auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]); - SCOPED_TRACE(devicePortConfig.toString()); - WithAudioPatch patch(IOTraits::is_input, stream.getPortConfig(), devicePortConfig); - ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); + stream.getStreamContext()->getFrameSizeBytes()); + typename IOTraits::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 @@ class AudioStreamIo : public AudioCoreModuleBase, 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 @@ class WithRemoteSubmix { explicit WithRemoteSubmix(AudioDeviceAddress address) : mAddress(address) {} WithRemoteSubmix(const WithRemoteSubmix&) = delete; WithRemoteSubmix& operator=(const WithRemoteSubmix&) = delete; + static std::optional getRemoteSubmixAudioPort( ModuleConfig* moduleConfig, const std::optional& address = std::nullopt) { - AudioDeviceType deviceType = IOTraits::is_input ? AudioDeviceType::IN_SUBMIX - : AudioDeviceType::OUT_SUBMIX; - auto ports = moduleConfig->getAudioPortsForDeviceTypes( - std::vector{deviceType}, - AudioDeviceDescription::CONNECTION_VIRTUAL); + auto ports = + moduleConfig->getRemoteSubmixPorts(IOTraits::is_input, true /*singlePort*/); if (ports.empty()) return {}; AudioPort port = ports.front(); if (address) { port.ext.template get().device.address = address.value(); - } else { - port = GenerateUniqueDeviceAddress(port); } return port; } - std::optional 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>( - mPatch->getPortConfig(IOTraits::is_input)); - ASSERT_NO_FATAL_FAILURE( - mStream->SetUp(mModule, AudioCoreModuleBase::kDefaultBufferSizeFrames)); - } - mAddress = connectedPort.ext.template get().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(makeBurstCommands(true), context->getFrameSizeBytes()); - mWorker = std::make_unique::Worker>(*context, mWorkerDriver.get(), - mStream->getEventReceiver()); - + mWorker = std::make_unique::Worker>( + *context, mWorkerDriver.get(), mStream->getStreamEventReceiver()); LOG(DEBUG) << __func__ << ": starting " << IOTraits::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 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::directionStr << " worker..."; mWorker->join(); @@ -4320,43 +4621,24 @@ class WithRemoteSubmix { 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 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(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::is_input); - if (!portConfig.has_value()) { - LOG(DEBUG) << __func__ << ": portConfig not found"; - mSkipTest = true; - return; - } - auto devicePortConfig = mModuleConfig->getSingleConfigForDevicePort(connectedPort); - mPatch = std::make_unique(IOTraits::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>(); + ASSERT_NO_FATAL_FAILURE( + mStream->SetUpStreamForDevicePort(module, moduleConfig, devicePort)); + mAddress = mStream->getDevice().address; } - bool mSkipTest = false; - IModule* mModule = nullptr; - ModuleConfig* mModuleConfig = nullptr; std::optional mAddress; - std::unique_ptr mConnectedPort; - std::unique_ptr mPatch; - std::unique_ptr> mStream; + std::unique_ptr> mStream; std::unique_ptr mWorkerDriver; std::unique_ptr::Worker> mWorker; }; @@ -4364,98 +4646,82 @@ class WithRemoteSubmix { 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 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 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 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 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 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 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::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>> streamIns(streamInCount); - for (int i = 0; i < streamInCount; i++) { - streamIns[i] = std::make_unique>(); - 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>(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()); + // 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(); } - ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommandsJoinWorker()); } INSTANTIATE_TEST_SUITE_P(AudioModuleRemoteSubmixTest, AudioModuleRemoteSubmix,