Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6f7a1f3e authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "APM: Create multiple MSD patches"

parents 790ce117 b7637e9d
Loading
Loading
Loading
Loading
+95 −48
Original line number Diff line number Diff line
@@ -1037,8 +1037,7 @@ status_t AudioPolicyManager::getOutputForAttrInt(
    *output = AUDIO_IO_HANDLE_NONE;
    if (!msdDevices.isEmpty()) {
        *output = getOutputForDevices(msdDevices, session, *stream, config, flags);
        sp<DeviceDescriptor> device = outputDevices.isEmpty() ? nullptr : outputDevices.itemAt(0);
        if (*output != AUDIO_IO_HANDLE_NONE && setMsdPatch(device) == NO_ERROR) {
        if (*output != AUDIO_IO_HANDLE_NONE && setMsdPatches(&outputDevices) == NO_ERROR) {
            ALOGV("%s() Using MSD devices %s instead of devices %s",
                  __func__, msdDevices.toString().c_str(), outputDevices.toString().c_str());
        } else {
@@ -1054,6 +1053,12 @@ status_t AudioPolicyManager::getOutputForAttrInt(
    }

    *selectedDeviceId = getFirstDeviceId(outputDevices);
    for (auto &outputDevice : outputDevices) {
        if (outputDevice->getId() == getConfig().getDefaultOutputDevice()->getId()) {
            *selectedDeviceId = outputDevice->getId();
            break;
        }
    }

    if (outputDevices.onlyContainsDevicesWithType(AUDIO_DEVICE_OUT_TELEPHONY_TX)) {
        *outputType = API_OUTPUT_TELEPHONY_TX;
@@ -1196,24 +1201,9 @@ status_t AudioPolicyManager::openDirectOutput(audio_stream_type_t stream,
    sp<SwAudioOutputDescriptor> outputDesc =
            new SwAudioOutputDescriptor(profile, mpClientInterface);

    String8 address = getFirstDeviceAddress(devices);

    // MSD patch may be using the only output stream that can service this request. Release
    // MSD patch to prioritize this request over any active output on MSD.
    AudioPatchCollection msdPatches = getMsdPatches();
    for (size_t i = 0; i < msdPatches.size(); i++) {
        const auto& patch = msdPatches[i];
        for (size_t j = 0; j < patch->mPatch.num_sinks; ++j) {
            const struct audio_port_config *sink = &patch->mPatch.sinks[j];
            if (sink->type == AUDIO_PORT_TYPE_DEVICE &&
                    devices.containsDeviceWithType(sink->ext.device.type) &&
                    (address.isEmpty() || strncmp(sink->ext.device.address, address.string(),
                            AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0)) {
                releaseAudioPatch(patch->getHandle(), mUidCached);
                break;
            }
        }
    }
    // An MSD patch may be using the only output stream that can service this request. Release
    // all MSD patches to prioritize this request over any active output on MSD.
    releaseMsdPatches(devices);

    status_t status = outputDesc->open(config, devices, stream, flags, output);

@@ -1386,7 +1376,8 @@ status_t AudioPolicyManager::getBestMsdAudioProfileFor(const sp<DeviceDescriptor
    }
    AudioProfileVector deviceProfiles;
    for (const auto &outProfile : outputProfiles) {
        if (hwAvSync == ((outProfile->getFlags() & AUDIO_OUTPUT_FLAG_HW_AV_SYNC) != 0)) {
        if (hwAvSync == ((outProfile->getFlags() & AUDIO_OUTPUT_FLAG_HW_AV_SYNC) != 0) &&
                outProfile->supportsDevice(outputDevice)) {
            appendAudioProfiles(deviceProfiles, outProfile->getAudioProfiles());
        }
    }
@@ -1454,40 +1445,85 @@ PatchBuilder AudioPolicyManager::buildMsdPatch(const sp<DeviceDescriptor> &outpu
    return patchBuilder;
}

status_t AudioPolicyManager::setMsdPatch(const sp<DeviceDescriptor> &outputDevice) {
    sp<DeviceDescriptor> device = outputDevice;
    if (device == nullptr) {
status_t AudioPolicyManager::setMsdPatches(const DeviceVector *outputDevices) {
    DeviceVector devices;
    if (outputDevices != nullptr && outputDevices->size() > 0) {
        devices.add(*outputDevices);
    } else {
        // Use media strategy for unspecified output device. This should only
        // occur on checkForDeviceAndOutputChanges(). Device connection events may
        // therefore invalidate explicit routing requests.
        DeviceVector devices = mEngine->getOutputDevicesForAttributes(
        devices = mEngine->getOutputDevicesForAttributes(
                    attributes_initializer(AUDIO_USAGE_MEDIA), nullptr, false /*fromCache*/);
        LOG_ALWAYS_FATAL_IF(devices.isEmpty(), "no outpudevice to set Msd Patch");
        device = devices.itemAt(0);
    }
    ALOGV("%s() for device %s", __func__, device->toString().c_str());
    PatchBuilder patchBuilder = buildMsdPatch(device);
    const struct audio_patch* patch = patchBuilder.patch();
    const AudioPatchCollection msdPatches = getMsdPatches();
    if (!msdPatches.isEmpty()) {
        LOG_ALWAYS_FATAL_IF(msdPatches.size() > 1,
                "The current MSD prototype only supports one output patch");
        sp<AudioPatch> currentPatch = msdPatches.valueAt(0);
        if (audio_patches_are_equal(&currentPatch->mPatch, patch)) {
        LOG_ALWAYS_FATAL_IF(devices.isEmpty(), "no output device to set MSD patch");
    }
    std::vector<PatchBuilder> patchesToCreate;
    for (auto i = 0u; i < devices.size(); ++i) {
        ALOGV("%s() for device %s", __func__, devices[i]->toString().c_str());
        patchesToCreate.push_back(buildMsdPatch(devices[i]));
    }
    // Retain only the MSD patches associated with outputDevices request.
    // Tear down the others, and create new ones as needed.
    AudioPatchCollection patchesToRemove = getMsdPatches();
    for (auto it = patchesToCreate.begin(); it != patchesToCreate.end(); ) {
        auto retainedPatch = false;
        for (auto i = 0u; i < patchesToRemove.size(); ++i) {
            if (audio_patches_are_equal(it->patch(), &patchesToRemove[i]->mPatch)) {
                patchesToRemove.removeItemsAt(i);
                retainedPatch = true;
                break;
            }
        }
        if (retainedPatch) {
            it = patchesToCreate.erase(it);
            continue;
        }
        ++it;
    }
    if (patchesToCreate.size() == 0 && patchesToRemove.size() == 0) {
        return NO_ERROR;
    }
    for (auto i = 0u; i < patchesToRemove.size(); ++i) {
        auto &currentPatch = patchesToRemove.valueAt(i);
        releaseAudioPatch(currentPatch->getHandle(), mUidCached);
    }
    status_t status = installPatch(__func__, -1 /*index*/, nullptr /*patchHandle*/,
            patch, 0 /*delayMs*/, mUidCached, nullptr /*patchDescPtr*/);
    ALOGE_IF(status != NO_ERROR, "%s() error %d creating MSD audio patch", __func__, status);
    ALOGI_IF(status == NO_ERROR, "%s() Patch created from MSD_IN to "
           "device:%s (format:%#x channels:%#x samplerate:%d)", __func__,
             device->toString().c_str(), patch->sources[0].format,
             patch->sources[0].channel_mask, patch->sources[0].sample_rate);
    status_t status = NO_ERROR;
    for (const auto &p : patchesToCreate) {
        auto currStatus = installPatch(__func__, -1 /*index*/, nullptr /*patchHandle*/,
                p.patch(), 0 /*delayMs*/, mUidCached, nullptr /*patchDescPtr*/);
        char message[256];
        snprintf(message, sizeof(message), "%s() %s: creating MSD patch from device:IN_BUS to "
            "device:%#x (format:%#x channels:%#x samplerate:%d)", __func__,
                currStatus == NO_ERROR ? "Success" : "Error",
                p.patch()->sinks[0].ext.device.type, p.patch()->sources[0].format,
                p.patch()->sources[0].channel_mask, p.patch()->sources[0].sample_rate);
        if (currStatus == NO_ERROR) {
            ALOGD("%s", message);
        } else {
            ALOGE("%s", message);
            if (status == NO_ERROR) {
                status = currStatus;
            }
        }
    }
    return status;
}

void AudioPolicyManager::releaseMsdPatches(const DeviceVector& devices) {
    AudioPatchCollection msdPatches = getMsdPatches();
    for (size_t i = 0; i < msdPatches.size(); i++) {
        const auto& patch = msdPatches[i];
        for (size_t j = 0; j < patch->mPatch.num_sinks; ++j) {
            const struct audio_port_config *sink = &patch->mPatch.sinks[j];
            if (sink->type == AUDIO_PORT_TYPE_DEVICE && devices.getDevice(sink->ext.device.type,
                    String8(sink->ext.device.address), AUDIO_FORMAT_DEFAULT) != nullptr) {
                releaseAudioPatch(patch->getHandle(), mUidCached);
                break;
            }
        }
    }
}

audio_io_handle_t AudioPolicyManager::selectOutput(const SortedVector<audio_io_handle_t>& outputs,
                                                       audio_output_flags_t flags,
                                                       audio_format_t format,
@@ -5309,8 +5345,13 @@ void AudioPolicyManager::closeOutput(audio_io_handle_t output)
            }
        }
        if (!directOutputOpen) {
            ALOGV("no direct outputs open, reset MSD patch");
            setMsdPatch();
            ALOGV("no direct outputs open, reset MSD patches");
            // TODO: The MSD patches to be established here may differ to current MSD patches due to
            // how output devices for patching are resolved. Avoid by caching and reusing the
            // arguments to mEngine->getOutputDevicesForAttributes() when resolving which output
            // devices to patch to. This may be complicated by the fact that devices may become
            // unavailable.
            setMsdPatches();
        }
    }
}
@@ -5377,7 +5418,13 @@ void AudioPolicyManager::checkForDeviceAndOutputChanges(std::function<bool()> on
    if (onOutputsChecked != nullptr && onOutputsChecked()) checkA2dpSuspend();
    updateDevicesAndOutputs();
    if (mHwModules.getModuleFromName(AUDIO_HARDWARE_MODULE_ID_MSD) != 0) {
        setMsdPatch();
        // TODO: The MSD patches to be established here may differ to current MSD patches due to how
        // output devices for patching are resolved. Nevertheless, AudioTracks affected by device
        // configuration changes will ultimately be rerouted correctly. We can still avoid
        // unnecessary rerouting by caching and reusing the arguments to
        // mEngine->getOutputDevicesForAttributes() when resolving which output devices to patch to.
        // This may be complicated by the fact that devices may become unavailable.
        setMsdPatches();
    }
}

+8 −8
Original line number Diff line number Diff line
@@ -844,13 +844,6 @@ protected:
        // end point.
        audio_port_handle_t mCallRxSourceClientPort = AUDIO_PORT_HANDLE_NONE;

private:
        void onNewAudioModulesAvailableInt(DeviceVector *newDevices);

        // Add or remove AC3 DTS encodings based on user preferences.
        void modifySurroundFormats(const sp<DeviceDescriptor>& devDesc, FormatVector *formatsPtr);
        void modifySurroundChannelMasks(ChannelMaskSet *channelMasksPtr);

        // Support for Multi-Stream Decoder (MSD) module
        sp<DeviceDescriptor> getMsdAudioInDevice() const;
        DeviceVector getMsdAudioOutDevices() const;
@@ -860,7 +853,14 @@ private:
                                           audio_port_config *sourceConfig,
                                           audio_port_config *sinkConfig) const;
        PatchBuilder buildMsdPatch(const sp<DeviceDescriptor> &outputDevice) const;
        status_t setMsdPatch(const sp<DeviceDescriptor> &outputDevice = nullptr);
        status_t setMsdPatches(const DeviceVector *outputDevices = nullptr);
        void releaseMsdPatches(const DeviceVector& devices);
private:
        void onNewAudioModulesAvailableInt(DeviceVector *newDevices);

        // Add or remove AC3 DTS encodings based on user preferences.
        void modifySurroundFormats(const sp<DeviceDescriptor>& devDesc, FormatVector *formatsPtr);
        void modifySurroundChannelMasks(ChannelMaskSet *channelMasksPtr);

        // If any, resolve any "dynamic" fields of an Audio Profiles collection
        void updateAudioProfiles(const sp<DeviceDescriptor>& devDesc, audio_io_handle_t ioHandle,
+2 −0
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ class AudioPolicyTestManager : public AudioPolicyManager {
    using AudioPolicyManager::getOutputs;
    using AudioPolicyManager::getAvailableOutputDevices;
    using AudioPolicyManager::getAvailableInputDevices;
    using AudioPolicyManager::releaseMsdPatches;
    using AudioPolicyManager::setMsdPatches;
    uint32_t getAudioPortGeneration() const { return mAudioPortGeneration; }
};

+82 −17
Original line number Diff line number Diff line
@@ -319,7 +319,17 @@ TEST_F(AudioPolicyManagerTest, CreateAudioPatchFromMix) {

// TODO: Add patch creation tests that involve already existing patch

class AudioPolicyManagerTestMsd : public AudioPolicyManagerTest {
enum
{
    MSD_AUDIO_PATCH_COUNT_NUM_AUDIO_PATCHES_INDEX = 0,
    MSD_AUDIO_PATCH_COUNT_NAME_INDEX = 1
};
using MsdAudioPatchCountSpecification = std::tuple<size_t, std::string>;

class AudioPolicyManagerTestMsd : public AudioPolicyManagerTest,
        public ::testing::WithParamInterface<MsdAudioPatchCountSpecification> {
  public:
    AudioPolicyManagerTestMsd();
  protected:
    void SetUpManagerConfig() override;
    void TearDown() override;
@@ -327,8 +337,26 @@ class AudioPolicyManagerTestMsd : public AudioPolicyManagerTest {
    sp<DeviceDescriptor> mMsdOutputDevice;
    sp<DeviceDescriptor> mMsdInputDevice;
    sp<DeviceDescriptor> mDefaultOutputDevice;

    const size_t mExpectedAudioPatchCount;
    sp<DeviceDescriptor> mSpdifDevice;
};

AudioPolicyManagerTestMsd::AudioPolicyManagerTestMsd()
    : mExpectedAudioPatchCount(std::get<MSD_AUDIO_PATCH_COUNT_NUM_AUDIO_PATCHES_INDEX>(
            GetParam())) {}

INSTANTIATE_TEST_CASE_P(
        MsdAudioPatchCount,
        AudioPolicyManagerTestMsd,
        ::testing::Values(
                MsdAudioPatchCountSpecification(1u, "single"),
                MsdAudioPatchCountSpecification(2u, "dual")
        ),
        [](const ::testing::TestParamInfo<MsdAudioPatchCountSpecification> &info) {
                return std::get<MSD_AUDIO_PATCH_COUNT_NAME_INDEX>(info.param); }
);

void AudioPolicyManagerTestMsd::SetUpManagerConfig() {
    // TODO: Consider using Serializer to load part of the config from a string.
    AudioPolicyManagerTest::SetUpManagerConfig();
@@ -348,6 +376,19 @@ void AudioPolicyManagerTestMsd::SetUpManagerConfig() {
    config.addDevice(mMsdOutputDevice);
    config.addDevice(mMsdInputDevice);

    if (mExpectedAudioPatchCount == 2) {
        // Add SPDIF device with PCM output profile as a second device for dual MSD audio patching.
        mSpdifDevice = new DeviceDescriptor(AUDIO_DEVICE_OUT_SPDIF);
        mSpdifDevice->addAudioProfile(pcmOutputProfile);
        config.addDevice(mSpdifDevice);

        sp<OutputProfile> spdifOutputProfile = new OutputProfile("spdif output");
        spdifOutputProfile->addAudioProfile(pcmOutputProfile);
        spdifOutputProfile->addSupportedDevice(mSpdifDevice);
        config.getHwModules().getModuleFromName(AUDIO_HARDWARE_MODULE_ID_PRIMARY)->
                addOutputProfile(spdifOutputProfile);
    }

    sp<HwModule> msdModule = new HwModule(AUDIO_HARDWARE_MODULE_ID_MSD, 2 /*halVersionMajor*/);
    HwModuleCollection modules = config.getHwModules();
    modules.add(msdModule);
@@ -383,64 +424,88 @@ void AudioPolicyManagerTestMsd::SetUpManagerConfig() {
            addOutputProfile(primaryEncodedOutputProfile);

    mDefaultOutputDevice = config.getDefaultOutputDevice();
    if (mExpectedAudioPatchCount == 2) {
        mSpdifDevice->addAudioProfile(dtsOutputProfile);
        primaryEncodedOutputProfile->addSupportedDevice(mSpdifDevice);
    }
}

void AudioPolicyManagerTestMsd::TearDown() {
    mMsdOutputDevice.clear();
    mMsdInputDevice.clear();
    mDefaultOutputDevice.clear();
    mSpdifDevice.clear();
    AudioPolicyManagerTest::TearDown();
}

TEST_F(AudioPolicyManagerTestMsd, InitSuccess) {
TEST_P(AudioPolicyManagerTestMsd, InitSuccess) {
    ASSERT_TRUE(mMsdOutputDevice);
    ASSERT_TRUE(mMsdInputDevice);
    ASSERT_TRUE(mDefaultOutputDevice);
}

TEST_F(AudioPolicyManagerTestMsd, Dump) {
TEST_P(AudioPolicyManagerTestMsd, Dump) {
    dumpToLog();
}

TEST_F(AudioPolicyManagerTestMsd, PatchCreationOnSetForceUse) {
TEST_P(AudioPolicyManagerTestMsd, PatchCreationOnSetForceUse) {
    const PatchCountCheck patchCount = snapshotPatchCount();
    mManager->setForceUse(AUDIO_POLICY_FORCE_FOR_ENCODED_SURROUND,
            AUDIO_POLICY_FORCE_ENCODED_SURROUND_ALWAYS);
    ASSERT_EQ(1, patchCount.deltaFromSnapshot());
    ASSERT_EQ(mExpectedAudioPatchCount, patchCount.deltaFromSnapshot());
}

TEST_P(AudioPolicyManagerTestMsd, PatchCreationSetReleaseMsdPatches) {
    const PatchCountCheck patchCount = snapshotPatchCount();
    DeviceVector devices = mManager->getAvailableOutputDevices();
    // Remove MSD output device to avoid patching to itself
    devices.remove(mMsdOutputDevice);
    ASSERT_EQ(mExpectedAudioPatchCount, devices.size());
    mManager->setMsdPatches(&devices);
    ASSERT_EQ(mExpectedAudioPatchCount, patchCount.deltaFromSnapshot());
    // Dual patch: exercise creating one new audio patch and reusing another existing audio patch.
    DeviceVector singleDevice(devices[0]);
    mManager->releaseMsdPatches(singleDevice);
    ASSERT_EQ(mExpectedAudioPatchCount - 1, patchCount.deltaFromSnapshot());
    mManager->setMsdPatches(&devices);
    ASSERT_EQ(mExpectedAudioPatchCount, patchCount.deltaFromSnapshot());
    mManager->releaseMsdPatches(devices);
    ASSERT_EQ(0, patchCount.deltaFromSnapshot());
}

TEST_F(AudioPolicyManagerTestMsd, GetOutputForAttrEncodedRoutesToMsd) {
TEST_P(AudioPolicyManagerTestMsd, GetOutputForAttrEncodedRoutesToMsd) {
    const PatchCountCheck patchCount = snapshotPatchCount();
    audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
    getOutputForAttr(&selectedDeviceId,
            AUDIO_FORMAT_AC3, AUDIO_CHANNEL_OUT_5POINT1, 48000, AUDIO_OUTPUT_FLAG_DIRECT);
    ASSERT_EQ(selectedDeviceId, mDefaultOutputDevice->getId());
    ASSERT_EQ(1, patchCount.deltaFromSnapshot());
    ASSERT_EQ(mExpectedAudioPatchCount, patchCount.deltaFromSnapshot());
}

TEST_F(AudioPolicyManagerTestMsd, GetOutputForAttrPcmRoutesToMsd) {
TEST_P(AudioPolicyManagerTestMsd, GetOutputForAttrPcmRoutesToMsd) {
    const PatchCountCheck patchCount = snapshotPatchCount();
    audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
    getOutputForAttr(&selectedDeviceId,
            AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO, 48000);
    ASSERT_EQ(selectedDeviceId, mDefaultOutputDevice->getId());
    ASSERT_EQ(1, patchCount.deltaFromSnapshot());
    ASSERT_EQ(mExpectedAudioPatchCount, patchCount.deltaFromSnapshot());
}

TEST_F(AudioPolicyManagerTestMsd, GetOutputForAttrEncodedPlusPcmRoutesToMsd) {
TEST_P(AudioPolicyManagerTestMsd, GetOutputForAttrEncodedPlusPcmRoutesToMsd) {
    const PatchCountCheck patchCount = snapshotPatchCount();
    audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
    getOutputForAttr(&selectedDeviceId,
            AUDIO_FORMAT_AC3, AUDIO_CHANNEL_OUT_5POINT1, 48000, AUDIO_OUTPUT_FLAG_DIRECT);
    ASSERT_EQ(selectedDeviceId, mDefaultOutputDevice->getId());
    ASSERT_EQ(1, patchCount.deltaFromSnapshot());
    ASSERT_EQ(mExpectedAudioPatchCount, patchCount.deltaFromSnapshot());
    selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
    getOutputForAttr(&selectedDeviceId,
            AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO, 48000);
    ASSERT_EQ(selectedDeviceId, mDefaultOutputDevice->getId());
    ASSERT_EQ(1, patchCount.deltaFromSnapshot());
    ASSERT_EQ(mExpectedAudioPatchCount, patchCount.deltaFromSnapshot());
}

TEST_F(AudioPolicyManagerTestMsd, GetOutputForAttrUnsupportedFormatBypassesMsd) {
TEST_P(AudioPolicyManagerTestMsd, GetOutputForAttrUnsupportedFormatBypassesMsd) {
    const PatchCountCheck patchCount = snapshotPatchCount();
    audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
    getOutputForAttr(&selectedDeviceId,
@@ -449,7 +514,7 @@ TEST_F(AudioPolicyManagerTestMsd, GetOutputForAttrUnsupportedFormatBypassesMsd)
    ASSERT_EQ(0, patchCount.deltaFromSnapshot());
}

TEST_F(AudioPolicyManagerTestMsd, GetOutputForAttrFormatSwitching) {
TEST_P(AudioPolicyManagerTestMsd, GetOutputForAttrFormatSwitching) {
    // Switch between formats that are supported and not supported by MSD.
    {
        const PatchCountCheck patchCount = snapshotPatchCount();
@@ -459,9 +524,9 @@ TEST_F(AudioPolicyManagerTestMsd, GetOutputForAttrFormatSwitching) {
                AUDIO_FORMAT_AC3, AUDIO_CHANNEL_OUT_5POINT1, 48000, AUDIO_OUTPUT_FLAG_DIRECT,
                nullptr /*output*/, &portId);
        ASSERT_EQ(selectedDeviceId, mDefaultOutputDevice->getId());
        ASSERT_EQ(1, patchCount.deltaFromSnapshot());
        ASSERT_EQ(mExpectedAudioPatchCount, patchCount.deltaFromSnapshot());
        mManager->releaseOutput(portId);
        ASSERT_EQ(1, patchCount.deltaFromSnapshot());
        ASSERT_EQ(mExpectedAudioPatchCount, patchCount.deltaFromSnapshot());
    }
    {
        const PatchCountCheck patchCount = snapshotPatchCount();
@@ -471,7 +536,7 @@ TEST_F(AudioPolicyManagerTestMsd, GetOutputForAttrFormatSwitching) {
                AUDIO_FORMAT_DTS, AUDIO_CHANNEL_OUT_5POINT1, 48000, AUDIO_OUTPUT_FLAG_DIRECT,
                nullptr /*output*/, &portId);
        ASSERT_NE(selectedDeviceId, mMsdOutputDevice->getId());
        ASSERT_EQ(-1, patchCount.deltaFromSnapshot());
        ASSERT_EQ(-static_cast<int>(mExpectedAudioPatchCount), patchCount.deltaFromSnapshot());
        mManager->releaseOutput(portId);
        ASSERT_EQ(0, patchCount.deltaFromSnapshot());
    }