| /* | |
| * Copyright 2023 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. | |
| */ | |
| #include <thread> | |
| #include <gtest/gtest.h> | |
| #include <oboe/Oboe.h> | |
| using namespace oboe; | |
| static constexpr int kTimeToSleepMicros = 5 * 1000 * 1000; // 5 s | |
| using TestFullDuplexStreamParams = std::tuple<AudioApi, PerformanceMode, AudioApi, PerformanceMode>; | |
| class TestFullDuplexStream : public ::testing::Test, | |
| public ::testing::WithParamInterface<TestFullDuplexStreamParams>, | |
| public FullDuplexStream { | |
| public: | |
| DataCallbackResult onBothStreamsReady( | |
| const void *inputData, | |
| int numInputFrames, | |
| void *outputData, | |
| int numOutputFrames) override { | |
| mCallbackCount++; | |
| if (numInputFrames == numOutputFrames) { | |
| mGoodCallbackCount++; | |
| } | |
| return DataCallbackResult::Continue; | |
| } | |
| protected: | |
| void openStream(AudioApi inputAudioApi, PerformanceMode inputPerfMode, | |
| AudioApi outputAudioApi, PerformanceMode outputPerfMode) { | |
| mOutputBuilder.setDirection(Direction::Output); | |
| if (mOutputBuilder.isAAudioRecommended()) { | |
| mOutputBuilder.setAudioApi(outputAudioApi); | |
| } | |
| mOutputBuilder.setPerformanceMode(outputPerfMode); | |
| mOutputBuilder.setChannelCount(1); | |
| mOutputBuilder.setFormat(AudioFormat::Float); | |
| mOutputBuilder.setDataCallback(this); | |
| Result r = mOutputBuilder.openStream(mOutputStream); | |
| ASSERT_EQ(r, Result::OK) << "Failed to open output stream " << convertToText(r); | |
| mInputBuilder.setDirection(Direction::Input); | |
| if (mInputBuilder.isAAudioRecommended()) { | |
| mInputBuilder.setAudioApi(inputAudioApi); | |
| } | |
| mInputBuilder.setPerformanceMode(inputPerfMode); | |
| mInputBuilder.setChannelCount(1); | |
| mInputBuilder.setFormat(AudioFormat::Float); | |
| mInputBuilder.setBufferCapacityInFrames(mOutputStream->getBufferCapacityInFrames() * 2); | |
| mInputBuilder.setSampleRate(mOutputStream->getSampleRate()); | |
| r = mInputBuilder.openStream(mInputStream); | |
| ASSERT_EQ(r, Result::OK) << "Failed to open input stream " << convertToText(r); | |
| setSharedInputStream(mInputStream); | |
| setSharedOutputStream(mOutputStream); | |
| } | |
| void startStream() { | |
| Result r = start(); | |
| ASSERT_EQ(r, Result::OK) << "Failed to start streams " << convertToText(r); | |
| } | |
| void stopStream() { | |
| Result r = stop(); | |
| ASSERT_EQ(r, Result::OK) << "Failed to stop streams " << convertToText(r); | |
| } | |
| void closeStream() { | |
| Result r = mOutputStream->close(); | |
| ASSERT_EQ(r, Result::OK) << "Failed to close output stream " << convertToText(r); | |
| r = mInputStream->close(); | |
| ASSERT_EQ(r, Result::OK) << "Failed to close input stream " << convertToText(r); | |
| } | |
| void checkXRuns() { | |
| // Expect few xRuns with the use of full duplex stream | |
| EXPECT_LT(mInputStream->getXRunCount().value(), 10); | |
| EXPECT_LT(mOutputStream->getXRunCount().value(), 10); | |
| } | |
| void checkInputAndOutputBufferSizesMatch() { | |
| // Expect the large majority of callbacks to have the same sized input and output | |
| EXPECT_GE(mGoodCallbackCount, mCallbackCount * 4 / 5); | |
| } | |
| AudioStreamBuilder mInputBuilder; | |
| AudioStreamBuilder mOutputBuilder; | |
| std::shared_ptr<AudioStream> mInputStream; | |
| std::shared_ptr<AudioStream> mOutputStream; | |
| std::atomic<int32_t> mCallbackCount{0}; | |
| std::atomic<int32_t> mGoodCallbackCount{0}; | |
| }; | |
| TEST_P(TestFullDuplexStream, VerifyFullDuplexStream) { | |
| const AudioApi inputAudioApi = std::get<0>(GetParam()); | |
| const PerformanceMode inputPerformanceMode = std::get<1>(GetParam()); | |
| const AudioApi outputAudioApi = std::get<2>(GetParam()); | |
| const PerformanceMode outputPerformanceMode = std::get<3>(GetParam()); | |
| openStream(inputAudioApi, inputPerformanceMode, outputAudioApi, outputPerformanceMode); | |
| startStream(); | |
| usleep(kTimeToSleepMicros); | |
| checkXRuns(); | |
| checkInputAndOutputBufferSizesMatch(); | |
| stopStream(); | |
| closeStream(); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| TestFullDuplexStreamTest, | |
| TestFullDuplexStream, | |
| ::testing::Values( | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency, | |
| AudioApi::AAudio, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency, | |
| AudioApi::AAudio, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency, | |
| AudioApi::AAudio, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency, | |
| AudioApi::OpenSLES, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency, | |
| AudioApi::OpenSLES, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency, | |
| AudioApi::OpenSLES, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None, | |
| AudioApi::AAudio, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None, | |
| AudioApi::AAudio, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None, | |
| AudioApi::AAudio, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None, | |
| AudioApi::OpenSLES, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None, | |
| AudioApi::OpenSLES, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None, | |
| AudioApi::OpenSLES, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving, | |
| AudioApi::AAudio, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving, | |
| AudioApi::AAudio, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving, | |
| AudioApi::AAudio, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving, | |
| AudioApi::OpenSLES, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving, | |
| AudioApi::OpenSLES, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving, | |
| AudioApi::OpenSLES, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency, | |
| AudioApi::AAudio, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency, | |
| AudioApi::AAudio, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency, | |
| AudioApi::AAudio, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency, | |
| AudioApi::OpenSLES, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency, | |
| AudioApi::OpenSLES, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency, | |
| AudioApi::OpenSLES, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None, | |
| AudioApi::AAudio, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None, | |
| AudioApi::AAudio, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None, | |
| AudioApi::AAudio, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None, | |
| AudioApi::OpenSLES, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None, | |
| AudioApi::OpenSLES, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None, | |
| AudioApi::OpenSLES, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving, | |
| AudioApi::AAudio, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving, | |
| AudioApi::AAudio, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving, | |
| AudioApi::AAudio, PerformanceMode::PowerSaving}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving, | |
| AudioApi::OpenSLES, PerformanceMode::LowLatency}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving, | |
| AudioApi::OpenSLES, PerformanceMode::None}), | |
| TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving, | |
| AudioApi::OpenSLES, PerformanceMode::PowerSaving}) | |
| ) | |
| ); |