blob: 3703a399cb527560c450761f8ecc786268946f2b [file] [log] [blame]
/*
* 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);