| /* |
| * 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 |
| #define LOG_TAG "AudioRoutingTest" |
| |
| #include <string.h> |
| |
| #include <binder/ProcessState.h> |
| #include <cutils/properties.h> |
| #include <gtest/gtest.h> |
| |
| #include "audio_test_utils.h" |
| |
| using namespace android; |
| |
| // UNIT TEST |
| TEST(AudioTrackTest, TestPerformanceMode) { |
| std::vector<struct audio_port_v7> ports; |
| ASSERT_EQ(OK, listAudioPorts(ports)); |
| 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 (const auto& port : ports) { |
| if (port.role == AUDIO_PORT_ROLE_SOURCE && port.type == AUDIO_PORT_TYPE_MIX) { |
| if ((port.active_config.flags.output & output_flags[i]) != 0) { |
| hasFlag = true; |
| break; |
| } |
| } |
| } |
| 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]; |
| sp<AudioPlayback> ap = sp<AudioPlayback>::make(0 /* sampleRate */, 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"; |
| ASSERT_EQ(OK, ap->create()) << "track creation failed"; |
| sp<OnAudioDeviceUpdateNotifier> cb = sp<OnAudioDeviceUpdateNotifier>::make(); |
| 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)); |
| if (output_flags[i] != AUDIO_OUTPUT_FLAG_FAST) { |
| // A "normal" output can still have a FastMixer, depending on the buffer size. |
| // Thus, a fast track can be created on a mix port which does not have the FAST flag. |
| 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) { |
| SCOPED_TRACE(dumpPortConfig(patch.sources[j])); |
| EXPECT_NE(0, patch.sources[j].flags.output & output_flags[i]) |
| << "expected output flag " |
| << audio_output_flag_to_string(output_flags[i]) << " is absent"; |
| } |
| } |
| } |
| ap->stop(); |
| } |
| } |
| |
| TEST(AudioTrackTest, DefaultRoutingTest) { |
| audio_port_v7 port; |
| if (OK != getPortByAttributes(AUDIO_PORT_ROLE_SOURCE, AUDIO_PORT_TYPE_DEVICE, |
| AUDIO_DEVICE_IN_REMOTE_SUBMIX, "0", port)) { |
| GTEST_SKIP() << "remote submix in device not connected"; |
| } |
| |
| // create record instance |
| sp<AudioCapture> capture = sp<AudioCapture>::make( |
| 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"; |
| sp<OnAudioDeviceUpdateNotifier> cbCapture = sp<OnAudioDeviceUpdateNotifier>::make(); |
| EXPECT_EQ(OK, capture->getAudioRecordHandle()->addAudioDeviceCallback(cbCapture)); |
| |
| // create playback instance |
| sp<AudioPlayback> playback = sp<AudioPlayback>::make( |
| 48000 /* sampleRate */, 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"; |
| sp<OnAudioDeviceUpdateNotifier> cbPlayback = sp<OnAudioDeviceUpdateNotifier>::make(); |
| EXPECT_EQ(OK, playback->getAudioTrackHandle()->addAudioDeviceCallback(cbPlayback)); |
| |
| // capture should be routed to submix in port |
| EXPECT_EQ(OK, capture->start()) << "start recording failed"; |
| EXPECT_EQ(OK, cbCapture->waitForAudioDeviceCb()); |
| EXPECT_EQ(port.id, capture->getAudioRecordHandle()->getRoutedDeviceId()) |
| << "Capture NOT routed on expected port"; |
| |
| // capture start should create submix out port |
| status_t status = getPortByAttributes(AUDIO_PORT_ROLE_SINK, AUDIO_PORT_TYPE_DEVICE, |
| AUDIO_DEVICE_OUT_REMOTE_SUBMIX, "0", port); |
| EXPECT_EQ(OK, status) << "Could not find port"; |
| |
| // playback should be routed to submix out as long as capture is active |
| EXPECT_EQ(OK, playback->start()) << "audio track start failed"; |
| EXPECT_EQ(OK, cbPlayback->waitForAudioDeviceCb()); |
| EXPECT_EQ(port.id, playback->getAudioTrackHandle()->getRoutedDeviceId()) |
| << "Playback NOT routed on expected port"; |
| |
| capture->stop(); |
| playback->stop(); |
| } |
| |
| class AudioRoutingTest : public ::testing::Test { |
| public: |
| void SetUp() override { |
| audio_port_v7 port; |
| if (OK != getPortByAttributes(AUDIO_PORT_ROLE_SOURCE, AUDIO_PORT_TYPE_DEVICE, |
| AUDIO_DEVICE_IN_REMOTE_SUBMIX, "0", port)) { |
| GTEST_SKIP() << "remote submix in device not connected"; |
| } |
| uint32_t mixType = MIX_TYPE_PLAYERS; |
| uint32_t mixFlag = MIX_ROUTE_FLAG_LOOP_BACK; |
| audio_devices_t deviceType = AUDIO_DEVICE_OUT_REMOTE_SUBMIX; |
| AudioMixMatchCriterion criterion(AUDIO_USAGE_MEDIA, AUDIO_SOURCE_DEFAULT, |
| RULE_MATCH_ATTRIBUTE_USAGE); |
| std::vector<AudioMixMatchCriterion> criteria{criterion}; |
| audio_config_t config = AUDIO_CONFIG_INITIALIZER; |
| config.channel_mask = AUDIO_CHANNEL_OUT_STEREO; |
| config.format = AUDIO_FORMAT_PCM_16_BIT; |
| config.sample_rate = 48000; |
| AudioMix mix(criteria, mixType, config, mixFlag, String8{mAddress.c_str()}, 0); |
| mix.mDeviceType = deviceType; |
| mMixes.push(mix); |
| if (OK == AudioSystem::registerPolicyMixes(mMixes, true)) { |
| mPolicyMixRegistered = true; |
| } |
| ASSERT_TRUE(mPolicyMixRegistered) << "register policy mix failed"; |
| } |
| |
| void TearDown() override { |
| if (mPolicyMixRegistered) { |
| EXPECT_EQ(OK, AudioSystem::registerPolicyMixes(mMixes, false)); |
| } |
| } |
| |
| bool mPolicyMixRegistered{false}; |
| std::string mAddress{"mix_1"}; |
| Vector<AudioMix> mMixes; |
| }; |
| |
| TEST_F(AudioRoutingTest, ConcurrentDynamicRoutingTest) { |
| audio_port_v7 port, port_mix; |
| // expect legacy submix in port to be connected |
| status_t status = getPortByAttributes(AUDIO_PORT_ROLE_SOURCE, AUDIO_PORT_TYPE_DEVICE, |
| AUDIO_DEVICE_IN_REMOTE_SUBMIX, "0", port); |
| EXPECT_EQ(OK, status) << "Could not find port"; |
| |
| // as policy mix is registered, expect submix in port with mAddress to be connected |
| status = getPortByAttributes(AUDIO_PORT_ROLE_SOURCE, AUDIO_PORT_TYPE_DEVICE, |
| AUDIO_DEVICE_IN_REMOTE_SUBMIX, mAddress, port_mix); |
| EXPECT_EQ(OK, status) << "Could not find port"; |
| |
| // create playback instance |
| sp<AudioPlayback> playback = sp<AudioPlayback>::make( |
| 48000 /* sampleRate */, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_STEREO, |
| AUDIO_OUTPUT_FLAG_NONE, AUDIO_SESSION_NONE, AudioTrack::TRANSFER_OBTAIN); |
| 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"; |
| sp<OnAudioDeviceUpdateNotifier> cbPlayback = sp<OnAudioDeviceUpdateNotifier>::make(); |
| EXPECT_EQ(OK, playback->getAudioTrackHandle()->addAudioDeviceCallback(cbPlayback)); |
| |
| // create capture instances on different ports |
| sp<AudioCapture> captureA = sp<AudioCapture>::make( |
| AUDIO_SOURCE_REMOTE_SUBMIX, 48000, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_IN_STEREO); |
| ASSERT_NE(nullptr, captureA); |
| ASSERT_EQ(OK, captureA->create()) << "record creation failed"; |
| sp<OnAudioDeviceUpdateNotifier> cbCaptureA = sp<OnAudioDeviceUpdateNotifier>::make(); |
| EXPECT_EQ(OK, captureA->getAudioRecordHandle()->addAudioDeviceCallback(cbCaptureA)); |
| |
| audio_attributes_t attr = AUDIO_ATTRIBUTES_INITIALIZER; |
| attr.source = AUDIO_SOURCE_REMOTE_SUBMIX; |
| sprintf(attr.tags, "addr=%s", mAddress.c_str()); |
| sp<AudioCapture> captureB = sp<AudioCapture>::make( |
| AUDIO_SOURCE_REMOTE_SUBMIX, 48000, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_IN_STEREO, |
| AUDIO_INPUT_FLAG_NONE, AUDIO_SESSION_ALLOCATE, AudioRecord::TRANSFER_CALLBACK, &attr); |
| ASSERT_NE(nullptr, captureB); |
| ASSERT_EQ(OK, captureB->create()) << "record creation failed"; |
| sp<OnAudioDeviceUpdateNotifier> cbCaptureB = sp<OnAudioDeviceUpdateNotifier>::make(); |
| EXPECT_EQ(OK, captureB->getAudioRecordHandle()->addAudioDeviceCallback(cbCaptureB)); |
| |
| // launch |
| EXPECT_EQ(OK, captureA->start()) << "start recording failed"; |
| EXPECT_EQ(OK, cbCaptureA->waitForAudioDeviceCb()); |
| EXPECT_EQ(port.id, captureA->getAudioRecordHandle()->getRoutedDeviceId()) |
| << "Capture NOT routed on expected port"; |
| |
| EXPECT_EQ(OK, captureB->start()) << "start recording failed"; |
| EXPECT_EQ(OK, cbCaptureB->waitForAudioDeviceCb()); |
| EXPECT_EQ(port_mix.id, captureB->getAudioRecordHandle()->getRoutedDeviceId()) |
| << "Capture NOT routed on expected port"; |
| |
| // as record started, expect submix out ports to be connected |
| status = getPortByAttributes(AUDIO_PORT_ROLE_SINK, AUDIO_PORT_TYPE_DEVICE, |
| AUDIO_DEVICE_OUT_REMOTE_SUBMIX, "0", port); |
| EXPECT_EQ(OK, status) << "unexpected submix out port found"; |
| |
| status = getPortByAttributes(AUDIO_PORT_ROLE_SINK, AUDIO_PORT_TYPE_DEVICE, |
| AUDIO_DEVICE_OUT_REMOTE_SUBMIX, mAddress, port_mix); |
| EXPECT_EQ(OK, status) << "Could not find port"; |
| |
| // check if playback routed to desired port |
| EXPECT_EQ(OK, playback->start()); |
| EXPECT_EQ(OK, cbPlayback->waitForAudioDeviceCb()); |
| EXPECT_EQ(port_mix.id, playback->getAudioTrackHandle()->getRoutedDeviceId()) |
| << "Playback NOT routed on expected port"; |
| |
| captureB->stop(); |
| |
| // check if mAddress submix out is disconnected as capture session is stopped |
| status = getPortByAttributes(AUDIO_PORT_ROLE_SINK, AUDIO_PORT_TYPE_DEVICE, |
| AUDIO_DEVICE_OUT_REMOTE_SUBMIX, mAddress, port_mix); |
| EXPECT_NE(OK, status) << "unexpected submix in port found"; |
| |
| // check if legacy submix out is connected |
| status = getPortByAttributes(AUDIO_PORT_ROLE_SINK, AUDIO_PORT_TYPE_DEVICE, |
| AUDIO_DEVICE_OUT_REMOTE_SUBMIX, "0", port); |
| EXPECT_EQ(OK, status) << "port not found"; |
| |
| // unregister policy |
| EXPECT_EQ(OK, AudioSystem::registerPolicyMixes(mMixes, false)); |
| mPolicyMixRegistered = false; |
| |
| // as policy mix is unregistered, expect submix in port with mAddress to be disconnected |
| status = getPortByAttributes(AUDIO_PORT_ROLE_SOURCE, AUDIO_PORT_TYPE_DEVICE, |
| AUDIO_DEVICE_IN_REMOTE_SUBMIX, mAddress, port_mix); |
| EXPECT_NE(OK, status) << "unexpected submix in port found"; |
| |
| playback->onProcess(); |
| // as captureA is active, it should re route to legacy submix |
| EXPECT_EQ(OK, cbPlayback->waitForAudioDeviceCb(port.id)); |
| EXPECT_EQ(port.id, playback->getAudioTrackHandle()->getRoutedDeviceId()) |
| << "Playback NOT routed on expected port"; |
| |
| captureA->stop(); |
| playback->stop(); |
| } |
| |
| class TestExecutionTracer : public ::testing::EmptyTestEventListener { |
| public: |
| void OnTestStart(const ::testing::TestInfo& test_info) override { |
| TraceTestState("Started", test_info); |
| } |
| void OnTestEnd(const ::testing::TestInfo& test_info) override { |
| TraceTestState("Completed", test_info); |
| } |
| |
| private: |
| static void TraceTestState(const std::string& state, const ::testing::TestInfo& test_info) { |
| ALOGI("%s %s::%s", state.c_str(), test_info.test_suite_name(), test_info.name()); |
| } |
| }; |
| |
| int main(int argc, char** argv) { |
| android::ProcessState::self()->startThreadPool(); |
| ::testing::InitGoogleTest(&argc, argv); |
| ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer()); |
| return RUN_ALL_TESTS(); |
| } |