| /* |
| * Copyright 2016 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 "AAudioTest" |
| |
| #include <memory> |
| #include <tuple> |
| |
| #include <unistd.h> |
| |
| #include <aaudio/AAudio.h> |
| #include <android/log.h> |
| #include <gtest/gtest.h> |
| |
| #include "test_aaudio.h" |
| #include "utils.h" |
| |
| using StreamTestParams = std::tuple<aaudio_sharing_mode_t, aaudio_performance_mode_t>; |
| enum { |
| PARAM_SHARING_MODE = 0, |
| PARAM_PERF_MODE |
| }; |
| |
| static std::string getTestName(const ::testing::TestParamInfo<StreamTestParams>& info) { |
| return std::string() + sharingModeToString(std::get<PARAM_SHARING_MODE>(info.param)) + |
| "__" + performanceModeToString(std::get<PARAM_PERF_MODE>(info.param)); |
| } |
| |
| template<typename T> |
| class AAudioStreamTest : public ::testing::TestWithParam<StreamTestParams> { |
| protected: |
| AAudioStreamBuilder* builder() const { return mHelper->builder(); } |
| AAudioStream* stream() const { return mHelper->stream(); } |
| const StreamBuilderHelper::Parameters& actual() const { return mHelper->actual(); } |
| int32_t framesPerBurst() const { return mHelper->framesPerBurst(); } |
| |
| std::unique_ptr<T> mHelper; |
| bool mSetupSuccesful = false; |
| std::unique_ptr<int16_t[]> mData; |
| }; |
| |
| |
| class AAudioInputStreamTest : public AAudioStreamTest<InputStreamBuilderHelper> { |
| protected: |
| void SetUp() override; |
| |
| int32_t mFramesPerRead; |
| }; |
| |
| void AAudioInputStreamTest::SetUp() { |
| mSetupSuccesful = false; |
| if (!deviceSupportsFeature(FEATURE_RECORDING)) return; |
| mHelper.reset(new InputStreamBuilderHelper( |
| std::get<PARAM_SHARING_MODE>(GetParam()), |
| std::get<PARAM_PERF_MODE>(GetParam()))); |
| mHelper->initBuilder(); |
| mHelper->createAndVerifyStream(&mSetupSuccesful); |
| if (!mSetupSuccesful) return; |
| |
| mFramesPerRead = framesPerBurst(); |
| const int32_t framesPerMsec = actual().sampleRate / MILLIS_PER_SECOND; |
| // Some DMA might use very short bursts of 16 frames. We don't need to read such small |
| // buffers. But it helps to use a multiple of the burst size for predictable scheduling. |
| while (mFramesPerRead < framesPerMsec) { |
| mFramesPerRead *= 2; |
| } |
| mData.reset(new int16_t[mFramesPerRead * actual().channelCount]); |
| } |
| |
| TEST_P(AAudioInputStreamTest, testReading) { |
| if (!mSetupSuccesful) return; |
| |
| const int32_t framesToRecord = actual().sampleRate; // 1 second |
| EXPECT_EQ(0, AAudioStream_getFramesRead(stream())); |
| EXPECT_EQ(0, AAudioStream_getFramesWritten(stream())); |
| mHelper->startStream(); |
| // See b/62090113. For legacy path, the device is only known after |
| // the stream has been started. |
| ASSERT_NE(AAUDIO_UNSPECIFIED, AAudioStream_getDeviceId(stream())); |
| for (int32_t framesLeft = framesToRecord; framesLeft > 0; ) { |
| aaudio_result_t result = AAudioStream_read( |
| stream(), &mData[0], std::min(framesToRecord, mFramesPerRead), |
| DEFAULT_READ_TIMEOUT); |
| ASSERT_GT(result, 0); |
| framesLeft -= result; |
| } |
| mHelper->stopStream(); |
| EXPECT_GE(AAudioStream_getFramesRead(stream()), framesToRecord); |
| EXPECT_GE(AAudioStream_getFramesWritten(stream()), framesToRecord); |
| EXPECT_GE(AAudioStream_getXRunCount(stream()), 0); |
| } |
| |
| TEST_P(AAudioInputStreamTest, testStartReadStop) { |
| if (!mSetupSuccesful) return; |
| |
| // Use 1/8 second as start-stops take a lot more time than just recording. |
| const int32_t framesToRecord = actual().sampleRate / 8; |
| EXPECT_EQ(0, AAudioStream_getFramesRead(stream())); |
| EXPECT_EQ(0, AAudioStream_getFramesWritten(stream())); |
| for (int32_t framesLeft = framesToRecord; framesLeft > 0; ) { |
| mHelper->startStream(); |
| aaudio_result_t result = AAudioStream_read( |
| stream(), &mData[0], std::min(framesToRecord, mFramesPerRead), |
| DEFAULT_READ_TIMEOUT); |
| ASSERT_GT(result, 0); |
| framesLeft -= result; |
| mHelper->stopStream(); |
| } |
| EXPECT_GE(AAudioStream_getFramesRead(stream()), framesToRecord); |
| EXPECT_GE(AAudioStream_getFramesWritten(stream()), framesToRecord); |
| } |
| |
| TEST_P(AAudioInputStreamTest, testReadCounterFreezeAfterStop) { |
| if (!mSetupSuccesful) return; |
| |
| const int32_t framesToRecord = actual().sampleRate / 10; // 1/10 second |
| EXPECT_EQ(0, AAudioStream_getFramesRead(stream())); |
| EXPECT_EQ(0, AAudioStream_getFramesWritten(stream())); |
| mHelper->startStream(); |
| for (int32_t framesLeft = framesToRecord; framesLeft > 0; ) { |
| aaudio_result_t result = AAudioStream_read( |
| stream(), &mData[0], std::min(framesToRecord, mFramesPerRead), |
| DEFAULT_READ_TIMEOUT); |
| ASSERT_GT(result, 0); |
| framesLeft -= result; |
| } |
| mHelper->stopStream(); |
| const int32_t framesReadAtStop = AAudioStream_getFramesRead(stream()); |
| const int32_t framesWrittenAtStop = AAudioStream_getFramesWritten(stream()); |
| ASSERT_EQ(0, TEMP_FAILURE_RETRY(usleep(100 * MICROS_PER_MILLISECOND))); |
| EXPECT_EQ(framesReadAtStop, AAudioStream_getFramesRead(stream())); |
| EXPECT_EQ(framesWrittenAtStop, AAudioStream_getFramesWritten(stream())); |
| } |
| |
| TEST_P(AAudioInputStreamTest, testPauseAndFlushNotSupported) { |
| if (!mSetupSuccesful) return; |
| mHelper->startStream(); |
| EXPECT_EQ(AAUDIO_ERROR_UNIMPLEMENTED, AAudioStream_requestPause(stream())); |
| EXPECT_EQ(AAUDIO_ERROR_UNIMPLEMENTED, AAudioStream_requestFlush(stream())); |
| mHelper->stopStream(); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(SPM, AAudioInputStreamTest, |
| ::testing::Values( |
| std::make_tuple(AAUDIO_SHARING_MODE_SHARED, AAUDIO_PERFORMANCE_MODE_NONE), |
| // Recording in POWER_SAVING mode isn't supported, b/62291775. |
| std::make_tuple(AAUDIO_SHARING_MODE_SHARED, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY), |
| std::make_tuple(AAUDIO_SHARING_MODE_EXCLUSIVE, AAUDIO_PERFORMANCE_MODE_NONE), |
| std::make_tuple( |
| AAUDIO_SHARING_MODE_EXCLUSIVE, AAUDIO_PERFORMANCE_MODE_POWER_SAVING), |
| std::make_tuple( |
| AAUDIO_SHARING_MODE_EXCLUSIVE, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY)), |
| &getTestName); |
| |
| |
| class AAudioOutputStreamTest : public AAudioStreamTest<OutputStreamBuilderHelper> { |
| protected: |
| void SetUp() override; |
| }; |
| |
| void AAudioOutputStreamTest::SetUp() { |
| mSetupSuccesful = false; |
| if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return; |
| mHelper.reset(new OutputStreamBuilderHelper( |
| std::get<PARAM_SHARING_MODE>(GetParam()), |
| std::get<PARAM_PERF_MODE>(GetParam()))); |
| mHelper->initBuilder(); |
| |
| mHelper->createAndVerifyStream(&mSetupSuccesful); |
| if (!mSetupSuccesful) return; |
| |
| // Allocate a buffer for the audio data. |
| // TODO handle possibility of other data formats |
| size_t dataSizeSamples = framesPerBurst() * actual().channelCount; |
| mData.reset(new int16_t[dataSizeSamples]); |
| memset(&mData[0], 0, dataSizeSamples); |
| } |
| |
| TEST_P(AAudioOutputStreamTest, testWriting) { |
| if (!mSetupSuccesful) return; |
| |
| // Prime the buffer. |
| int32_t framesWritten = 0; |
| int64_t framesTotal = 0; |
| int64_t timeoutNanos = 0; |
| do { |
| framesWritten = AAudioStream_write( |
| stream(), &mData[0], framesPerBurst(), timeoutNanos); |
| // There should be some room for priming the buffer. |
| framesTotal += framesWritten; |
| ASSERT_GE(framesWritten, 0); |
| ASSERT_LE(framesWritten, framesPerBurst()); |
| } while (framesWritten > 0); |
| ASSERT_TRUE(framesTotal > 0); |
| |
| int writeLoops = 0; |
| int64_t aaudioFramesRead = 0; |
| int64_t aaudioFramesReadPrev = 0; |
| int64_t aaudioFramesReadFinal = 0; |
| int64_t aaudioFramesWritten = 0; |
| // Start/write/pause more than once to see if it fails after the first time. |
| // Write some data and measure the rate to see if the timing is OK. |
| for (int numLoops = 0; numLoops < 2; numLoops++) { |
| mHelper->startStream(); |
| // See b/62090113. For legacy path, the device is only known after |
| // the stream has been started. |
| ASSERT_NE(AAUDIO_UNSPECIFIED, AAudioStream_getDeviceId(stream())); |
| |
| // Write some data while we are running. Read counter should be advancing. |
| writeLoops = 1 * actual().sampleRate / framesPerBurst(); // 1 second |
| ASSERT_LT(2, writeLoops); // detect absurdly high framesPerBurst |
| timeoutNanos = 100 * (NANOS_PER_SECOND * framesPerBurst() / |
| actual().sampleRate); // N bursts |
| framesWritten = 1; |
| aaudioFramesRead = AAudioStream_getFramesRead(stream()); |
| aaudioFramesReadPrev = aaudioFramesRead; |
| int64_t beginTime = getNanoseconds(CLOCK_MONOTONIC); |
| do { |
| framesWritten = AAudioStream_write( |
| stream(), &mData[0], framesPerBurst(), timeoutNanos); |
| EXPECT_EQ(framesPerBurst(), framesWritten); |
| |
| framesTotal += framesWritten; |
| aaudioFramesWritten = AAudioStream_getFramesWritten(stream()); |
| EXPECT_EQ(framesTotal, aaudioFramesWritten); |
| |
| // Try to get a more accurate measure of the sample rate. |
| if (beginTime == 0) { |
| aaudioFramesRead = AAudioStream_getFramesRead(stream()); |
| if (aaudioFramesRead > aaudioFramesReadPrev) { // is read pointer advancing |
| beginTime = getNanoseconds(CLOCK_MONOTONIC); |
| aaudioFramesReadPrev = aaudioFramesRead; |
| } |
| } |
| } while (framesWritten > 0 && writeLoops-- > 0); |
| |
| aaudioFramesReadFinal = AAudioStream_getFramesRead(stream()); |
| ASSERT_GT(aaudioFramesReadFinal, 0); |
| EXPECT_GT(aaudioFramesReadFinal, aaudioFramesReadPrev); |
| |
| |
| // TODO why is AudioTrack path so inaccurate? |
| /* See b/38268547, there is no way to specify that MMAP mode needs to be used, |
| even EXCLUSIVE mode may fall back to legacy |
| const int64_t endTime = getNanoseconds(CLOCK_MONOTONIC); |
| const double rateTolerance = 200.0; // arbitrary tolerance for sample rate |
| if (std::get<PARAM_SHARING_MODE>(GetParam()) != AAUDIO_SHARING_MODE_SHARED) { |
| // Calculate approximate sample rate and compare with stream rate. |
| double seconds = (endTime - beginTime) / (double) NANOS_PER_SECOND; |
| double measuredRate = (aaudioFramesReadFinal - aaudioFramesReadPrev) / seconds; |
| ASSERT_NEAR(actual().sampleRate, measuredRate, rateTolerance); |
| } |
| */ |
| |
| mHelper->pauseStream(); |
| } |
| EXPECT_GE(AAudioStream_getXRunCount(stream()), 0); |
| |
| // Make sure the read counter is not advancing when we are paused. |
| aaudioFramesRead = AAudioStream_getFramesRead(stream()); |
| ASSERT_GE(aaudioFramesRead, aaudioFramesReadFinal); // monotonic increase |
| // Currently not possible to enforce for AAudio over AudioTrack (b/33354715). |
| // ASSERT_EQ(0, TEMP_FAILURE_RETRY(usleep(100 * MICROS_PER_MILLISECOND))); |
| // EXPECT_EQ(aaudioFramesRead, AAudioStream_getFramesRead(stream())); |
| |
| // ------------------- TEST FLUSH ----------------- |
| // Prime the buffer. |
| timeoutNanos = 0; |
| writeLoops = 1000; |
| do { |
| framesWritten = AAudioStream_write( |
| stream(), &mData[0], framesPerBurst(), timeoutNanos); |
| framesTotal += framesWritten; |
| } while (framesWritten > 0 && writeLoops-- > 0); |
| EXPECT_EQ(0, framesWritten); |
| |
| mHelper->flushStream(); |
| |
| // After a flush, the read counter should be caught up with the write counter. |
| aaudioFramesWritten = AAudioStream_getFramesWritten(stream()); |
| EXPECT_EQ(framesTotal, aaudioFramesWritten); |
| aaudioFramesRead = AAudioStream_getFramesRead(stream()); |
| EXPECT_EQ(aaudioFramesWritten, aaudioFramesRead); |
| |
| sleep(1); // FIXME - The write returns 0 if we remove this sleep! Why? |
| |
| // The buffer should be empty after a flush so we should be able to write. |
| framesWritten = AAudioStream_write(stream(), &mData[0], framesPerBurst(), timeoutNanos); |
| // There should be some room for priming the buffer. |
| ASSERT_GT(framesWritten, 0); |
| ASSERT_LE(framesWritten, framesPerBurst()); |
| } |
| |
| // Make sure the read and write frame counters do not diverge by more than the |
| // capacity of the buffer. |
| TEST_P(AAudioOutputStreamTest, testWriteStopWrite) { |
| if (!mSetupSuccesful) return; |
| |
| int32_t framesWritten = 0; |
| int64_t framesTotal = 0; |
| int64_t timeoutNanos = 0; |
| int32_t writeLoops = 0; |
| int64_t aaudioFramesRead = 0; |
| int64_t aaudioFramesWritten = 0; |
| int32_t frameCapacity = AAudioStream_getBufferCapacityInFrames(stream()); |
| |
| // Start/write/stop more than once to see if it fails after the first time. |
| for (int numLoops = 0; numLoops < 2; numLoops++) { |
| mHelper->startStream(); |
| |
| // Write some data while we are running. Read counter should be advancing. |
| writeLoops = 1 * actual().sampleRate / framesPerBurst(); // 1 second |
| ASSERT_LT(2, writeLoops); // detect absurdly high framesPerBurst |
| |
| // Calculate a reasonable timeout value. |
| const int32_t timeoutBursts = 20; |
| timeoutNanos = timeoutBursts * (NANOS_PER_SECOND * framesPerBurst() / |
| actual().sampleRate); |
| // Account for cold start latency. |
| timeoutNanos = std::max(timeoutNanos, 400 * NANOS_PER_MILLISECOND); |
| |
| do { |
| framesWritten = AAudioStream_write( |
| stream(), &mData[0], framesPerBurst(), timeoutNanos); |
| EXPECT_EQ(framesPerBurst(), framesWritten); |
| framesTotal += framesWritten; |
| |
| aaudioFramesWritten = AAudioStream_getFramesWritten(stream()); |
| EXPECT_EQ(framesTotal, aaudioFramesWritten); |
| aaudioFramesRead = AAudioStream_getFramesRead(stream()); |
| |
| // How many frames are sitting in the buffer? |
| int32_t writtenButNotRead = (int32_t)(aaudioFramesWritten - aaudioFramesRead); |
| ASSERT_LE(writtenButNotRead, frameCapacity); |
| // It is legal for writtenButNotRead to be negative because |
| // MMAP HW can underrun the FIFO. |
| } while (framesWritten > 0 && writeLoops-- > 0); |
| |
| mHelper->stopStream(); |
| } |
| } |
| |
| // Note that the test for EXCLUSIVE sharing mode may fail gracefully if |
| // this mode isn't supported by the platform. |
| INSTANTIATE_TEST_CASE_P(SPM, AAudioOutputStreamTest, |
| ::testing::Values( |
| std::make_tuple(AAUDIO_SHARING_MODE_SHARED, AAUDIO_PERFORMANCE_MODE_NONE), |
| std::make_tuple(AAUDIO_SHARING_MODE_SHARED, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY), |
| std::make_tuple(AAUDIO_SHARING_MODE_SHARED, AAUDIO_PERFORMANCE_MODE_POWER_SAVING), |
| |
| std::make_tuple(AAUDIO_SHARING_MODE_EXCLUSIVE, AAUDIO_PERFORMANCE_MODE_NONE), |
| std::make_tuple( |
| AAUDIO_SHARING_MODE_EXCLUSIVE, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY), |
| std::make_tuple( |
| AAUDIO_SHARING_MODE_EXCLUSIVE, AAUDIO_PERFORMANCE_MODE_POWER_SAVING)), |
| &getTestName); |