blob: 670c07794f29ac8b505b2684a946a53979a3fdbc [file] [log] [blame]
/*
* Copyright 2017 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 <atomic>
#include <tuple>
#include <aaudio/AAudio.h>
#include <android/log.h>
#include <gtest/gtest.h>
#include "test_aaudio.h"
#include "utils.h"
static int32_t measureLatency(AAudioStream *stream) {
int64_t presentationTime = 0;
int64_t presentationPosition = 0;
int64_t now = getNanoseconds();
int32_t sampleRate = AAudioStream_getSampleRate(stream);
int64_t framesWritten = AAudioStream_getFramesWritten(stream);
aaudio_result_t result = AAudioStream_getTimestamp(stream,
CLOCK_MONOTONIC,
&presentationPosition,
&presentationTime);
if (result < 0) {
return result;
}
// Calculate when the last frame written would be played.
int64_t deltaFrames = framesWritten - presentationPosition;
if (deltaFrames < 0) {
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Underrun detected: %lld frames",
(long long)(-deltaFrames));
return 0;
}
int64_t calculatedDeltaNanos = deltaFrames * NANOS_PER_SECOND / sampleRate;
int64_t calculatedTimeNanos = presentationTime + calculatedDeltaNanos;
int64_t latencyNanos = calculatedTimeNanos - now;
int32_t latencyMillis = (int32_t) ((latencyNanos + NANOS_PER_MILLISECOND - 1)
/ NANOS_PER_MILLISECOND);
return latencyMillis;
}
using CbTestParams = std::tuple<aaudio_sharing_mode_t, int32_t, aaudio_performance_mode_t>;
enum {
PARAM_SHARING_MODE = 0,
PARAM_FRAMES_PER_CB,
PARAM_PERF_MODE
};
static std::string getTestName(const ::testing::TestParamInfo<CbTestParams>& info) {
return std::string() + sharingModeToString(std::get<PARAM_SHARING_MODE>(info.param)) +
"__" + std::to_string(std::get<PARAM_FRAMES_PER_CB>(info.param)) +
"__" + performanceModeToString(std::get<PARAM_PERF_MODE>(info.param));
}
template<typename T>
class AAudioStreamCallbackTest : public ::testing::TestWithParam<CbTestParams> {
protected:
struct AAudioCallbackTestData {
int32_t expectedFramesPerCallback;
int32_t actualFramesPerCallback;
int32_t minLatency;
int32_t maxLatency;
std::atomic<aaudio_result_t> callbackError;
std::atomic<int32_t> callbackCount;
AAudioCallbackTestData() {
reset(0);
}
void reset(int32_t expectedFramesPerCb) {
expectedFramesPerCallback = expectedFramesPerCb;
actualFramesPerCallback = 0;
minLatency = INT32_MAX;
maxLatency = 0;
callbackError = AAUDIO_OK;
callbackCount = 0;
}
void updateFrameCount(int32_t numFrames) {
if (numFrames != expectedFramesPerCallback) {
// record unexpected framecounts
actualFramesPerCallback = numFrames;
} else if (actualFramesPerCallback == 0) {
// record at least one frame count
actualFramesPerCallback = numFrames;
}
}
void updateLatency(int32_t latency) {
if (latency <= 0) return;
minLatency = std::min(minLatency, latency);
maxLatency = std::max(maxLatency, latency);
}
};
static void MyErrorCallbackProc(AAudioStream *stream, void *userData, aaudio_result_t error);
AAudioStreamBuilder* builder() const { return mHelper->builder(); }
AAudioStream* stream() const { return mHelper->stream(); }
const StreamBuilderHelper::Parameters& actual() const { return mHelper->actual(); }
std::unique_ptr<T> mHelper;
bool mSetupSuccesful = false;
std::unique_ptr<AAudioCallbackTestData> mCbData;
};
template<typename T>
void AAudioStreamCallbackTest<T>::MyErrorCallbackProc(
AAudioStream* /*stream*/, void *userData, aaudio_result_t error) {
AAudioCallbackTestData *myData = static_cast<AAudioCallbackTestData*>(userData);
myData->callbackError = error;
}
class AAudioInputStreamCallbackTest : public AAudioStreamCallbackTest<InputStreamBuilderHelper> {
protected:
void SetUp() override;
static aaudio_data_callback_result_t MyDataCallbackProc(
AAudioStream *stream, void *userData, void *audioData, int32_t numFrames);
};
aaudio_data_callback_result_t AAudioInputStreamCallbackTest::MyDataCallbackProc(
AAudioStream* /*stream*/, void *userData, void* /*audioData*/, int32_t numFrames) {
AAudioCallbackTestData *myData = static_cast<AAudioCallbackTestData*>(userData);
myData->updateFrameCount(numFrames);
// No latency measurement as there is no API for querying capture position.
myData->callbackCount++;
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
void AAudioInputStreamCallbackTest::SetUp() {
mHelper.reset(new InputStreamBuilderHelper(
std::get<PARAM_SHARING_MODE>(GetParam()),
std::get<PARAM_PERF_MODE>(GetParam())));
mHelper->initBuilder();
int32_t framesPerDataCallback = std::get<PARAM_FRAMES_PER_CB>(GetParam());
mCbData.reset(new AAudioCallbackTestData());
AAudioStreamBuilder_setErrorCallback(builder(), &MyErrorCallbackProc, mCbData.get());
AAudioStreamBuilder_setDataCallback(builder(), &MyDataCallbackProc, mCbData.get());
if (framesPerDataCallback != AAUDIO_UNSPECIFIED) {
AAudioStreamBuilder_setFramesPerDataCallback(builder(), framesPerDataCallback);
}
mSetupSuccesful = false;
mHelper->createAndVerifyStream(&mSetupSuccesful);
}
// Test Reading from an AAudioStream using a Callback
TEST_P(AAudioInputStreamCallbackTest, testRecording) {
if (!mSetupSuccesful) return;
const int32_t framesPerDataCallback = std::get<PARAM_FRAMES_PER_CB>(GetParam());
const int32_t actualFramesPerDataCallback = AAudioStream_getFramesPerDataCallback(stream());
if (framesPerDataCallback != AAUDIO_UNSPECIFIED) {
ASSERT_EQ(framesPerDataCallback, actualFramesPerDataCallback);
}
mCbData->reset(actualFramesPerDataCallback);
mHelper->startStream();
// See b/62090113. For legacy path, the device is only known after
// the stream has been started.
EXPECT_NE(AAUDIO_UNSPECIFIED, AAudioStream_getDeviceId(stream()));
sleep(2); // let the stream run
ASSERT_EQ(AAUDIO_OK, mCbData->callbackError);
ASSERT_GT(mCbData->callbackCount, 10);
mHelper->stopStream();
int32_t oldCallbackCount = mCbData->callbackCount;
EXPECT_GT(oldCallbackCount, 10);
sleep(1);
EXPECT_EQ(oldCallbackCount, mCbData->callbackCount); // expect not advancing
if (framesPerDataCallback != AAUDIO_UNSPECIFIED) {
ASSERT_EQ(framesPerDataCallback, mCbData->actualFramesPerCallback);
}
ASSERT_EQ(AAUDIO_OK, mCbData->callbackError);
}
INSTANTIATE_TEST_CASE_P(SPM, AAudioInputStreamCallbackTest,
::testing::Values(
std::make_tuple(
AAUDIO_SHARING_MODE_SHARED,
AAUDIO_UNSPECIFIED,
AAUDIO_PERFORMANCE_MODE_NONE),
// cb buffer size: arbitrary prime number < 192
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 109, AAUDIO_PERFORMANCE_MODE_NONE),
// cb buffer size: arbitrary prime number > 192
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 223, AAUDIO_PERFORMANCE_MODE_NONE),
// Recording in POWER_SAVING mode isn't supported, b/62291775.
std::make_tuple(
AAUDIO_SHARING_MODE_SHARED,
AAUDIO_UNSPECIFIED,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY)),
&getTestName);
class AAudioOutputStreamCallbackTest : public AAudioStreamCallbackTest<OutputStreamBuilderHelper> {
protected:
void SetUp() override;
static aaudio_data_callback_result_t MyDataCallbackProc(
AAudioStream *stream, void *userData, void *audioData, int32_t numFrames);
};
// Callback function that fills the audio output buffer.
aaudio_data_callback_result_t AAudioOutputStreamCallbackTest::MyDataCallbackProc(
AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) {
int32_t channelCount = AAudioStream_getChannelCount(stream);
int32_t numSamples = channelCount * numFrames;
if (AAudioStream_getFormat(stream) == AAUDIO_FORMAT_PCM_I16) {
int16_t *shortData = (int16_t *) audioData;
for (int i = 0; i < numSamples; i++) *shortData++ = 0;
} else if (AAudioStream_getFormat(stream) == AAUDIO_FORMAT_PCM_FLOAT) {
float *floatData = (float *) audioData;
for (int i = 0; i < numSamples; i++) *floatData++ = 0.0f;
}
AAudioCallbackTestData *myData = static_cast<AAudioCallbackTestData*>(userData);
myData->updateFrameCount(numFrames);
myData->updateLatency(measureLatency(stream));
myData->callbackCount++;
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
void AAudioOutputStreamCallbackTest::SetUp() {
mHelper.reset(new OutputStreamBuilderHelper(
std::get<PARAM_SHARING_MODE>(GetParam()),
std::get<PARAM_PERF_MODE>(GetParam())));
mHelper->initBuilder();
int32_t framesPerDataCallback = std::get<PARAM_FRAMES_PER_CB>(GetParam());
mCbData.reset(new AAudioCallbackTestData());
AAudioStreamBuilder_setErrorCallback(builder(), &MyErrorCallbackProc, mCbData.get());
AAudioStreamBuilder_setDataCallback(builder(), &MyDataCallbackProc, mCbData.get());
if (framesPerDataCallback != AAUDIO_UNSPECIFIED) {
AAudioStreamBuilder_setFramesPerDataCallback(builder(), framesPerDataCallback);
}
mSetupSuccesful = false;
mHelper->createAndVerifyStream(&mSetupSuccesful);
}
// Test Writing to an AAudioStream using a Callback
TEST_P(AAudioOutputStreamCallbackTest, testPlayback) {
if (!mSetupSuccesful) return;
const int32_t framesPerDataCallback = std::get<PARAM_FRAMES_PER_CB>(GetParam());
const int32_t actualFramesPerDataCallback = AAudioStream_getFramesPerDataCallback(stream());
if (framesPerDataCallback != AAUDIO_UNSPECIFIED) {
ASSERT_EQ(framesPerDataCallback, actualFramesPerDataCallback);
}
// Start/stop 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 loopIndex = 0; loopIndex < 2; loopIndex++) {
mCbData->reset(actualFramesPerDataCallback);
mHelper->startStream();
// See b/62090113. For legacy path, the device is only known after
// the stream has been started.
EXPECT_NE(AAUDIO_UNSPECIFIED, AAudioStream_getDeviceId(stream()));
sleep(2); // let the stream run
ASSERT_EQ(AAUDIO_OK, mCbData->callbackError);
ASSERT_GT(mCbData->callbackCount, 10);
// For more coverage, alternate pausing and stopping.
if ((loopIndex & 1) == 0) {
mHelper->pauseStream();
} else {
mHelper->stopStream();
}
int32_t oldCallbackCount = mCbData->callbackCount;
EXPECT_GT(oldCallbackCount, 10);
sleep(1);
EXPECT_EQ(oldCallbackCount, mCbData->callbackCount); // expect not advancing
if (framesPerDataCallback != AAUDIO_UNSPECIFIED) {
ASSERT_EQ(framesPerDataCallback, mCbData->actualFramesPerCallback);
}
EXPECT_GE(mCbData->minLatency, 1); // Absurdly low
EXPECT_LE(mCbData->maxLatency, 300); // Absurdly high, should be < 30
// Note that on some devices it's 200-something
//printf("latency: %d, %d\n", mCbData->minLatency, mCbData->maxLatency);
}
ASSERT_EQ(AAUDIO_OK, mCbData->callbackError);
}
INSTANTIATE_TEST_CASE_P(SPM, AAudioOutputStreamCallbackTest,
::testing::Values(
std::make_tuple(
AAUDIO_SHARING_MODE_SHARED,
AAUDIO_UNSPECIFIED,
AAUDIO_PERFORMANCE_MODE_NONE),
// cb buffer size: arbitrary prime number < 192
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 109, AAUDIO_PERFORMANCE_MODE_NONE),
// cb buffer size: arbitrary prime number > 192
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 223, AAUDIO_PERFORMANCE_MODE_NONE),
std::make_tuple(
AAUDIO_SHARING_MODE_SHARED,
AAUDIO_UNSPECIFIED,
AAUDIO_PERFORMANCE_MODE_POWER_SAVING),
std::make_tuple(
AAUDIO_SHARING_MODE_SHARED,
AAUDIO_UNSPECIFIED,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY)),
&getTestName);