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

Commit b2fc7ce4 authored by Ram Mohan's avatar Ram Mohan Committed by Automerger Merge Worker
Browse files

Add audio routing tests am: 236a7625

parents b18af434 236a7625
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -170,6 +170,18 @@ cc_test {
    ],
}

cc_test {
    name: "audiorouting_tests",
    defaults: ["libaudioclient_gtests_defaults"],
    srcs: [
        "audiorouting_tests.cpp",
        "audio_test_utils.cpp",
    ],
    shared_libs: [
        "libxml2",
    ],
}

cc_test {
    name: "audioclient_serialization_tests",
    defaults: ["libaudioclient_gtests_defaults"],
+243 −0
Original line number Diff line number Diff line
@@ -548,3 +548,246 @@ status_t AudioCapture::audioProcess() {
        }
    }
}

status_t listAudioPorts(std::vector<audio_port_v7>& portsVec) {
    int attempts = 5;
    status_t status;
    unsigned int generation1, generation;
    unsigned int numPorts = 0;
    do {
        if (attempts-- < 0) {
            status = TIMED_OUT;
            break;
        }
        status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE, AUDIO_PORT_TYPE_NONE, &numPorts,
                                             nullptr, &generation1);
        if (status != NO_ERROR) {
            ALOGE("AudioSystem::listAudioPorts returned error %d", status);
            break;
        }
        portsVec.resize(numPorts);
        status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE, AUDIO_PORT_TYPE_NONE, &numPorts,
                                             portsVec.data(), &generation);
    } while (generation1 != generation && status == NO_ERROR);
    if (status != NO_ERROR) {
        numPorts = 0;
        portsVec.clear();
    }
    return status;
}

status_t getPortById(const audio_port_handle_t portId, audio_port_v7& port) {
    std::vector<struct audio_port_v7> ports;
    status_t status = listAudioPorts(ports);
    if (status != OK) return status;
    for (auto i = 0; i < ports.size(); i++) {
        if (ports[i].id == portId) {
            port = ports[i];
            return OK;
        }
    }
    return BAD_VALUE;
}

status_t getPortByAttributes(audio_port_role_t role, audio_port_type_t type,
                             audio_devices_t deviceType, audio_port_v7& port) {
    std::vector<struct audio_port_v7> ports;
    status_t status = listAudioPorts(ports);
    if (status != OK) return status;
    for (auto i = 0; i < ports.size(); i++) {
        if (ports[i].role == role && ports[i].type == type &&
            ports[i].ext.device.type == deviceType) {
            port = ports[i];
            return OK;
        }
    }
    return BAD_VALUE;
}

status_t listAudioPatches(std::vector<struct audio_patch>& patchesVec) {
    int attempts = 5;
    status_t status;
    unsigned int generation1, generation;
    unsigned int numPatches = 0;
    do {
        if (attempts-- < 0) {
            status = TIMED_OUT;
            break;
        }
        status = AudioSystem::listAudioPatches(&numPatches, nullptr, &generation1);
        if (status != NO_ERROR) {
            ALOGE("AudioSystem::listAudioPatches returned error %d", status);
            break;
        }
        patchesVec.resize(numPatches);
        status = AudioSystem::listAudioPatches(&numPatches, patchesVec.data(), &generation);
    } while (generation1 != generation && status == NO_ERROR);
    if (status != NO_ERROR) {
        numPatches = 0;
        patchesVec.clear();
    }
    return status;
}

status_t getPatchForOutputMix(audio_io_handle_t audioIo, audio_patch& patch) {
    std::vector<struct audio_patch> patches;
    status_t status = listAudioPatches(patches);
    if (status != OK) return status;

    for (auto i = 0; i < patches.size(); i++) {
        for (auto j = 0; j < patches[i].num_sources; j++) {
            if (patches[i].sources[j].type == AUDIO_PORT_TYPE_MIX &&
                patches[i].sources[j].ext.mix.handle == audioIo) {
                patch = patches[i];
                return OK;
            }
        }
    }
    return BAD_VALUE;
}

status_t getPatchForInputMix(audio_io_handle_t audioIo, audio_patch& patch) {
    std::vector<struct audio_patch> patches;
    status_t status = listAudioPatches(patches);
    if (status != OK) return status;

    for (auto i = 0; i < patches.size(); i++) {
        for (auto j = 0; j < patches[i].num_sinks; j++) {
            if (patches[i].sinks[j].type == AUDIO_PORT_TYPE_MIX &&
                patches[i].sinks[j].ext.mix.handle == audioIo) {
                patch = patches[i];
                return OK;
            }
        }
    }
    return BAD_VALUE;
}

bool patchContainsOutputDevice(audio_port_handle_t deviceId, audio_patch patch) {
    for (auto j = 0; j < patch.num_sinks; j++) {
        if (patch.sinks[j].type == AUDIO_PORT_TYPE_DEVICE && patch.sinks[j].id == deviceId) {
            return true;
        }
    }
    return false;
}

bool patchContainsInputDevice(audio_port_handle_t deviceId, audio_patch patch) {
    for (auto j = 0; j < patch.num_sources; j++) {
        if (patch.sources[j].type == AUDIO_PORT_TYPE_DEVICE && patch.sources[j].id == deviceId) {
            return true;
        }
    }
    return false;
}

bool checkPatchPlayback(audio_io_handle_t audioIo, audio_port_handle_t deviceId) {
    struct audio_patch patch;
    if (getPatchForOutputMix(audioIo, patch) == OK) {
        return patchContainsOutputDevice(deviceId, patch);
    }
    return false;
}

bool checkPatchCapture(audio_io_handle_t audioIo, audio_port_handle_t deviceId) {
    struct audio_patch patch;
    if (getPatchForInputMix(audioIo, patch) == OK) {
        return patchContainsInputDevice(deviceId, patch);
    }
    return false;
}

std::string dumpPortConfig(const audio_port_config& port) {
    std::ostringstream result;
    std::string deviceInfo;
    if (port.type == AUDIO_PORT_TYPE_DEVICE) {
        if (port.ext.device.type & AUDIO_DEVICE_BIT_IN) {
            InputDeviceConverter::maskToString(port.ext.device.type, deviceInfo);
        } else {
            OutputDeviceConverter::maskToString(port.ext.device.type, deviceInfo);
        }
        deviceInfo += std::string(", address = ") + port.ext.device.address;
    }
    result << "audio_port_handle_t = " << port.id << ", "
           << "Role = " << (port.role == AUDIO_PORT_ROLE_SOURCE ? "source" : "sink") << ", "
           << "Type = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix") << ", "
           << "deviceInfo = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? deviceInfo : "") << ", "
           << "config_mask = 0x" << std::hex << port.config_mask << std::dec << ", ";
    if (port.config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE) {
        result << "sample rate = " << port.sample_rate << ", ";
    }
    if (port.config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK) {
        result << "channel mask = " << port.channel_mask << ", ";
    }
    if (port.config_mask & AUDIO_PORT_CONFIG_FORMAT) {
        result << "format = " << port.format << ", ";
    }
    result << "input flags = " << port.flags.input << ", ";
    result << "output flags = " << port.flags.output << ", ";
    result << "mix io handle = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? 0 : port.ext.mix.handle)
           << "\n";
    return result.str();
}

std::string dumpPatch(const audio_patch& patch) {
    std::ostringstream result;
    result << "----------------- Dumping Patch ------------ \n";
    result << "Patch Handle: " << patch.id << ", sources: " << patch.num_sources
           << ", sink: " << patch.num_sinks << "\n";
    audio_port_v7 port;
    for (uint32_t i = 0; i < patch.num_sources; i++) {
        result << "----------------- Dumping Source Port Config @ index " << i
               << " ------------ \n";
        result << dumpPortConfig(patch.sources[i]);
        result << "----------------- Dumping Source Port for id " << patch.sources[i].id
               << " ------------ \n";
        getPortById(patch.sources[i].id, port);
        result << dumpPort(port);
    }
    for (uint32_t i = 0; i < patch.num_sinks; i++) {
        result << "----------------- Dumping Sink Port Config @ index " << i << " ------------ \n";
        result << dumpPortConfig(patch.sinks[i]);
        result << "----------------- Dumping Sink Port for id " << patch.sinks[i].id
               << " ------------ \n";
        getPortById(patch.sinks[i].id, port);
        result << dumpPort(port);
    }
    return result.str();
}

std::string dumpPort(const audio_port_v7& port) {
    std::ostringstream result;
    std::string deviceInfo;
    if (port.type == AUDIO_PORT_TYPE_DEVICE) {
        if (port.ext.device.type & AUDIO_DEVICE_BIT_IN) {
            InputDeviceConverter::maskToString(port.ext.device.type, deviceInfo);
        } else {
            OutputDeviceConverter::maskToString(port.ext.device.type, deviceInfo);
        }
        deviceInfo += std::string(", address = ") + port.ext.device.address;
    }
    result << "audio_port_handle_t = " << port.id << ", "
           << "Role = " << (port.role == AUDIO_PORT_ROLE_SOURCE ? "source" : "sink") << ", "
           << "Type = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix") << ", "
           << "deviceInfo = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? deviceInfo : "") << ", "
           << "Name = " << port.name << ", "
           << "num profiles = " << port.num_audio_profiles << ", "
           << "mix io handle = " << (port.type == AUDIO_PORT_TYPE_DEVICE ? 0 : port.ext.mix.handle)
           << ", ";
    for (int i = 0; i < port.num_audio_profiles; i++) {
        result << "AudioProfile = " << i << " {";
        result << "format = " << port.audio_profiles[i].format << ", ";
        result << "samplerates = ";
        for (int j = 0; j < port.audio_profiles[i].num_sample_rates; j++) {
            result << port.audio_profiles[i].sample_rates[j] << ", ";
        }
        result << "channelmasks = ";
        for (int j = 0; j < port.audio_profiles[i].num_channel_masks; j++) {
            result << "0x" << std::hex << port.audio_profiles[i].channel_masks[j] << std::dec
                   << ", ";
        }
        result << "} ";
    }
    result << dumpPortConfig(port.active_config);
    return result.str();
}
+13 −0
Original line number Diff line number Diff line
@@ -37,6 +37,19 @@
using namespace android;

void CreateRandomFile(int& fd);
status_t listAudioPorts(std::vector<audio_port_v7>& portsVec);
status_t listAudioPatches(std::vector<struct audio_patch>& patchesVec);
status_t getPortByAttributes(audio_port_role_t role, audio_port_type_t type,
                             audio_devices_t deviceType, audio_port_v7& port);
status_t getPatchForOutputMix(audio_io_handle_t audioIo, audio_patch& patch);
status_t getPatchForInputMix(audio_io_handle_t audioIo, audio_patch& patch);
bool patchContainsOutputDevice(audio_port_handle_t deviceId, audio_patch patch);
bool patchContainsInputDevice(audio_port_handle_t deviceId, audio_patch patch);
bool checkPatchPlayback(audio_io_handle_t audioIo, audio_port_handle_t deviceId);
bool checkPatchCapture(audio_io_handle_t audioIo, audio_port_handle_t deviceId);
std::string dumpPort(const audio_port_v7& port);
std::string dumpPortConfig(const audio_port_config& port);
std::string dumpPatch(const audio_patch& patch);

class OnAudioDeviceUpdateNotifier : public AudioSystem::AudioDeviceCallback {
  public:
+252 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//#define LOG_NDEBUG 0

#include <cutils/properties.h>
#include <gtest/gtest.h>
#include <libxml/parser.h>
#include <libxml/xinclude.h>
#include <string.h>
#include <system/audio_config.h>

#include "audio_test_utils.h"

using namespace android;

template <class T>
constexpr void (*xmlDeleter)(T* t);
template <>
constexpr auto xmlDeleter<xmlDoc> = xmlFreeDoc;
template <>
constexpr auto xmlDeleter<xmlChar> = [](xmlChar* s) { xmlFree(s); };

/** @return a unique_ptr with the correct deleter for the libxml2 object. */
template <class T>
constexpr auto make_xmlUnique(T* t) {
    // Wrap deleter in lambda to enable empty base optimization
    auto deleter = [](T* t) { xmlDeleter<T>(t); };
    return std::unique_ptr<T, decltype(deleter)>{t, deleter};
}

std::string getXmlAttribute(const xmlNode* cur, const char* attribute) {
    auto charPtr = make_xmlUnique(xmlGetProp(cur, reinterpret_cast<const xmlChar*>(attribute)));
    if (charPtr == NULL) {
        return "";
    }
    std::string value(reinterpret_cast<const char*>(charPtr.get()));
    return value;
}

struct MixPort {
    std::string name;
    std::string role;
    std::string flags;
};

struct Route {
    std::string name;
    std::string sources;
    std::string sink;
};

status_t parse_audio_policy_configuration_xml(std::vector<std::string>& attachedDevices,
                                              std::vector<MixPort>& mixPorts,
                                              std::vector<Route>& routes) {
    std::string path = audio_find_readable_configuration_file("audio_policy_configuration.xml");
    if (path.length() == 0) return UNKNOWN_ERROR;
    auto doc = make_xmlUnique(xmlParseFile(path.c_str()));
    if (doc == nullptr) return UNKNOWN_ERROR;
    xmlNode* root = xmlDocGetRootElement(doc.get());
    if (root == nullptr) return UNKNOWN_ERROR;
    if (xmlXIncludeProcess(doc.get()) < 0) return UNKNOWN_ERROR;
    mixPorts.clear();
    if (!xmlStrcmp(root->name, reinterpret_cast<const xmlChar*>("audioPolicyConfiguration"))) {
        std::string raw{getXmlAttribute(root, "version")};
        for (auto* child = root->xmlChildrenNode; child != nullptr; child = child->next) {
            if (!xmlStrcmp(child->name, reinterpret_cast<const xmlChar*>("modules"))) {
                xmlNode* root = child;
                for (auto* child = root->xmlChildrenNode; child != nullptr; child = child->next) {
                    if (!xmlStrcmp(child->name, reinterpret_cast<const xmlChar*>("module"))) {
                        xmlNode* root = child;
                        for (auto* child = root->xmlChildrenNode; child != nullptr;
                             child = child->next) {
                            if (!xmlStrcmp(child->name,
                                           reinterpret_cast<const xmlChar*>("mixPorts"))) {
                                xmlNode* root = child;
                                for (auto* child = root->xmlChildrenNode; child != nullptr;
                                     child = child->next) {
                                    if (!xmlStrcmp(child->name,
                                                   reinterpret_cast<const xmlChar*>("mixPort"))) {
                                        MixPort mixPort;
                                        xmlNode* root = child;
                                        mixPort.name = getXmlAttribute(root, "name");
                                        mixPort.role = getXmlAttribute(root, "role");
                                        mixPort.flags = getXmlAttribute(root, "flags");
                                        if (mixPort.role == "source") mixPorts.push_back(mixPort);
                                    }
                                }
                            } else if (!xmlStrcmp(child->name, reinterpret_cast<const xmlChar*>(
                                                                       "attachedDevices"))) {
                                xmlNode* root = child;
                                for (auto* child = root->xmlChildrenNode; child != nullptr;
                                     child = child->next) {
                                    if (!xmlStrcmp(child->name,
                                                   reinterpret_cast<const xmlChar*>("item"))) {
                                        auto xmlValue = make_xmlUnique(xmlNodeListGetString(
                                                child->doc, child->xmlChildrenNode, 1));
                                        if (xmlValue == nullptr) {
                                            raw = "";
                                        } else {
                                            raw = reinterpret_cast<const char*>(xmlValue.get());
                                        }
                                        std::string& value = raw;
                                        attachedDevices.push_back(std::move(value));
                                    }
                                }
                            } else if (!xmlStrcmp(child->name,
                                                  reinterpret_cast<const xmlChar*>("routes"))) {
                                xmlNode* root = child;
                                for (auto* child = root->xmlChildrenNode; child != nullptr;
                                     child = child->next) {
                                    if (!xmlStrcmp(child->name,
                                                   reinterpret_cast<const xmlChar*>("route"))) {
                                        Route route;
                                        xmlNode* root = child;
                                        route.name = getXmlAttribute(root, "name");
                                        route.sources = getXmlAttribute(root, "sources");
                                        route.sink = getXmlAttribute(root, "sink");
                                        routes.push_back(route);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return OK;
}

// UNIT TEST
TEST(AudioTrackTest, TestPerformanceMode) {
    std::vector<std::string> attachedDevices;
    std::vector<MixPort> mixPorts;
    std::vector<Route> routes;
    EXPECT_EQ(OK, parse_audio_policy_configuration_xml(attachedDevices, mixPorts, routes));
    std::string output_flags_string[] = {"AUDIO_OUTPUT_FLAG_FAST", "AUDIO_OUTPUT_FLAG_DEEP_BUFFER"};
    audio_output_flags_t output_flags[] = {AUDIO_OUTPUT_FLAG_FAST, AUDIO_OUTPUT_FLAG_DEEP_BUFFER};
    audio_flags_mask_t flags[] = {AUDIO_FLAG_LOW_LATENCY, AUDIO_FLAG_DEEP_BUFFER};
    bool hasFlag = false;
    for (int i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
        hasFlag = false;
        for (int j = 0; j < mixPorts.size() && !hasFlag; j++) {
            MixPort port = mixPorts[j];
            if (port.role == "source" && port.flags.find(output_flags_string[i]) != -1) {
                for (int k = 0; k < routes.size() && !hasFlag; k++) {
                    if (routes[k].sources.find(port.name) != -1 &&
                        std::find(attachedDevices.begin(), attachedDevices.end(), routes[k].sink) !=
                                attachedDevices.end()) {
                        hasFlag = true;
                        std::cerr << "found port with flag " << output_flags_string[i] << "@ "
                                  << " port :: name : " << port.name << " role : " << port.role
                                  << " port :: flags : " << port.flags
                                  << " connected via route name : " << routes[k].name
                                  << " route sources : " << routes[k].sources
                                  << " route sink : " << routes[k].sink << std::endl;
                    }
                }
            }
        }
        if (!hasFlag) continue;
        audio_attributes_t attributes = AUDIO_ATTRIBUTES_INITIALIZER;
        attributes.usage = AUDIO_USAGE_MEDIA;
        attributes.content_type = AUDIO_CONTENT_TYPE_MUSIC;
        attributes.flags = flags[i];
        std::unique_ptr<AudioPlayback> ap(new AudioPlayback(
                0, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO, AUDIO_OUTPUT_FLAG_NONE,
                AUDIO_SESSION_NONE, AudioTrack::TRANSFER_OBTAIN, &attributes));
        ASSERT_NE(nullptr, ap);
        ASSERT_EQ(OK, ap->loadResource("/data/local/tmp/bbb_2ch_24kHz_s16le.raw"))
                << "Unable to open Resource";
        EXPECT_EQ(OK, ap->create()) << "track creation failed";
        sp<OnAudioDeviceUpdateNotifier> cb = new OnAudioDeviceUpdateNotifier();
        EXPECT_EQ(OK, ap->getAudioTrackHandle()->addAudioDeviceCallback(cb));
        EXPECT_EQ(OK, ap->start()) << "audio track start failed";
        EXPECT_EQ(OK, ap->onProcess());
        EXPECT_EQ(OK, cb->waitForAudioDeviceCb());
        EXPECT_TRUE(checkPatchPlayback(cb->mAudioIo, cb->mDeviceId));
        EXPECT_NE(0, ap->getAudioTrackHandle()->getFlags() & output_flags[i]);
        audio_patch patch;
        EXPECT_EQ(OK, getPatchForOutputMix(cb->mAudioIo, patch));
        for (auto j = 0; j < patch.num_sources; j++) {
            if (patch.sources[j].type == AUDIO_PORT_TYPE_MIX &&
                patch.sources[j].ext.mix.handle == cb->mAudioIo) {
                if ((patch.sources[j].flags.output & output_flags[i]) == 0) {
                    ADD_FAILURE() << "expected output flag " << output_flags[i] << " is absent";
                    std::cerr << dumpPortConfig(patch.sources[j]);
                }
            }
        }
        ap->stop();
        ap->getAudioTrackHandle()->removeAudioDeviceCallback(cb);
    }
}

TEST(AudioTrackTest, TestRemoteSubmix) {
    std::vector<std::string> attachedDevices;
    std::vector<MixPort> mixPorts;
    std::vector<Route> routes;
    EXPECT_EQ(OK, parse_audio_policy_configuration_xml(attachedDevices, mixPorts, routes));
    bool hasFlag = false;
    for (int j = 0; j < attachedDevices.size() && !hasFlag; j++) {
        if (attachedDevices[j].find("Remote Submix") != -1) hasFlag = true;
    }
    if (!hasFlag) GTEST_SKIP() << " Device does not have Remote Submix port.";
    sp<AudioCapture> capture = new AudioCapture(AUDIO_SOURCE_REMOTE_SUBMIX, 48000,
                                                AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_IN_STEREO);
    ASSERT_NE(nullptr, capture);
    ASSERT_EQ(OK, capture->create()) << "record creation failed";

    std::unique_ptr<AudioPlayback> playback = std::make_unique<AudioPlayback>(
            48000, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO, AUDIO_OUTPUT_FLAG_NONE,
            AUDIO_SESSION_NONE);
    ASSERT_NE(nullptr, playback);
    ASSERT_EQ(OK, playback->loadResource("/data/local/tmp/bbb_2ch_24kHz_s16le.raw"))
            << "Unable to open Resource";
    ASSERT_EQ(OK, playback->create()) << "track creation failed";

    audio_port_v7 port;
    status_t status = getPortByAttributes(AUDIO_PORT_ROLE_SOURCE, AUDIO_PORT_TYPE_DEVICE,
                                          AUDIO_DEVICE_IN_REMOTE_SUBMIX, port);
    EXPECT_EQ(OK, status) << "Could not find port";

    EXPECT_EQ(OK, capture->start()) << "start recording failed";
    EXPECT_EQ(port.id, capture->getAudioRecordHandle()->getRoutedDeviceId())
            << "Capture NOT routed on expected port";

    status = getPortByAttributes(AUDIO_PORT_ROLE_SINK, AUDIO_PORT_TYPE_DEVICE,
                                 AUDIO_DEVICE_OUT_REMOTE_SUBMIX, port);
    EXPECT_EQ(OK, status) << "Could not find port";

    EXPECT_EQ(OK, playback->start()) << "audio track start failed";
    EXPECT_EQ(OK, playback->onProcess());
    ASSERT_EQ(port.id, playback->getAudioTrackHandle()->getRoutedDeviceId())
            << "Playback NOT routed on expected port";
    capture->stop();
    playback->stop();
}
+1 −0
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ TEST(AudioTrackTest, TestAudioCbNotifier) {
    EXPECT_EQ(AUDIO_PORT_HANDLE_NONE, cbOld->mDeviceId);
    EXPECT_NE(AUDIO_IO_HANDLE_NONE, cb->mAudioIo);
    EXPECT_NE(AUDIO_PORT_HANDLE_NONE, cb->mDeviceId);
    EXPECT_TRUE(checkPatchPlayback(cb->mAudioIo, cb->mDeviceId));
    EXPECT_EQ(BAD_VALUE, ap->getAudioTrackHandle()->removeAudioDeviceCallback(nullptr));
    EXPECT_EQ(INVALID_OPERATION, ap->getAudioTrackHandle()->removeAudioDeviceCallback(cbOld));
    EXPECT_EQ(OK, ap->getAudioTrackHandle()->removeAudioDeviceCallback(cb));