blob: 0541943b3775f553b43ec14b04665030efb70936 [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"
constexpr int kExtremelyHighCallbackPeriodMillis = 200;
constexpr int kMinCallbacksPerSecond = MILLIS_PER_SECOND / kExtremelyHighCallbackPeriodMillis;
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, int32_t, aaudio_format_t>;
enum {
PARAM_SHARING_MODE = 0,
PARAM_FRAMES_PER_CB,
PARAM_PERF_MODE,
PARAM_ALLOW_MMAP,
PARAM_AUDIO_FORMAT
};
enum {
MMAP_NOT_ALLOWED,
MMAP_ALLOWED,
};
static const char* allowMMapToString(int allow) {
switch (allow) {
case MMAP_NOT_ALLOWED: return "NOTMMAP";
case MMAP_ALLOWED:
default:
return "MMAPOK";
}
}
static const char* audioFormatToString(aaudio_format_t format) {
switch (format) {
case AAUDIO_FORMAT_UNSPECIFIED: return "UNSP";
case AAUDIO_FORMAT_PCM_I16: return "I16";
case AAUDIO_FORMAT_PCM_FLOAT: return "FLT";
default:
return "BAD";
}
}
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))
+ "__" + allowMMapToString(std::get<PARAM_ALLOW_MMAP>(info.param))
+ "__" + audioFormatToString(std::get<PARAM_AUDIO_FORMAT>(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;
std::atomic<bool> returnStop;
AAudioCallbackTestData() {
reset(0);
}
void reset(int32_t expectedFramesPerCb) {
expectedFramesPerCallback = expectedFramesPerCb;
actualFramesPerCallback = 0;
minLatency = INT32_MAX;
maxLatency = 0;
callbackError = AAUDIO_OK;
callbackCount = 0;
returnStop = false;
}
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);
}
};
void createAndVerifyHonoringMMap() {
aaudio_policy_t originalPolicy = AAUDIO_POLICY_AUTO;
// Turn off MMap if requested.
bool allowMMap = std::get<PARAM_ALLOW_MMAP>(GetParam()) == MMAP_ALLOWED;
if (AAudioExtensions::getInstance().isMMapSupported()) {
originalPolicy = AAudioExtensions::getInstance().getMMapPolicy();
AAudioExtensions::getInstance().setMMapEnabled(allowMMap);
}
mHelper->createAndVerifyStream(&mSetupSuccessful);
// Restore policy for next test.
if (AAudioExtensions::getInstance().isMMapSupported()) {
AAudioExtensions::getInstance().setMMapPolicy(originalPolicy);
}
// Make sure we do not get MMAP when we disable it.
if (mSetupSuccessful && !allowMMap) {
ASSERT_FALSE(AAudioExtensions::getInstance().isMMapUsed(mHelper->stream()));
}
}
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 mSetupSuccessful = 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 myData->returnStop ? AAUDIO_CALLBACK_RESULT_STOP : AAUDIO_CALLBACK_RESULT_CONTINUE;
}
void AAudioInputStreamCallbackTest::SetUp() {
mSetupSuccessful = false;
if (!deviceSupportsFeature(FEATURE_RECORDING)) return;
mHelper.reset(new InputStreamBuilderHelper(
std::get<PARAM_SHARING_MODE>(GetParam()),
std::get<PARAM_PERF_MODE>(GetParam()),
std::get<PARAM_AUDIO_FORMAT>(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);
}
createAndVerifyHonoringMMap();
}
// Test starting and stopping an INPUT AAudioStream that uses a Callback
TEST_P(AAudioInputStreamCallbackTest, testRecording) {
if (!mSetupSuccessful) return;
const int32_t framesPerDataCallback = std::get<PARAM_FRAMES_PER_CB>(GetParam());
const int32_t streamFramesPerDataCallback = AAudioStream_getFramesPerDataCallback(stream());
if (framesPerDataCallback != AAUDIO_UNSPECIFIED) {
ASSERT_EQ(framesPerDataCallback, streamFramesPerDataCallback);
}
constexpr int kSleepSeconds = 2;
constexpr int kMinExpectedCallbacks = kMinCallbacksPerSecond * kSleepSeconds;
// Try both methods of stopping a stream.
const int kNumMethods = 2;
// Start/stop more than once to see if it fails after the first time.
// Also check to make sure we do not get callbacks after the stop.
for (int loopIndex = 0; loopIndex < kNumMethods; loopIndex++) {
mCbData->reset(streamFramesPerDataCallback);
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(kSleepSeconds); // let the stream run
ASSERT_EQ(AAUDIO_OK, mCbData->callbackError);
ASSERT_GT(mCbData->callbackCount, kMinExpectedCallbacks);
switch (loopIndex % kNumMethods) {
case 0:
mCbData->returnStop = true; // callback return
mHelper->waitForState(AAUDIO_STREAM_STATE_STOPPED);
break;
case 1:
mHelper->stopStream();
break;
}
int32_t oldCallbackCount = mCbData->callbackCount;
EXPECT_GT(oldCallbackCount, kMinExpectedCallbacks);
sleep(1);
EXPECT_EQ(oldCallbackCount, mCbData->callbackCount); // expect not advancing
if (streamFramesPerDataCallback != AAUDIO_UNSPECIFIED) {
ASSERT_EQ(streamFramesPerDataCallback, 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,
MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
// cb buffer size: arbitrary prime number < 96
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 67,
AAUDIO_PERFORMANCE_MODE_NONE, MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 67,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
std::make_tuple(AAUDIO_SHARING_MODE_EXCLUSIVE, 67,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 67,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, MMAP_NOT_ALLOWED,
AAUDIO_FORMAT_PCM_I16),
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 67,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, MMAP_NOT_ALLOWED,
AAUDIO_FORMAT_PCM_FLOAT),
// cb buffer size: arbitrary prime number > 192
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 223,
AAUDIO_PERFORMANCE_MODE_NONE, MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
// Recording in POWER_SAVING mode isn't supported, b/62291775.
std::make_tuple(
AAUDIO_SHARING_MODE_SHARED,
AAUDIO_UNSPECIFIED,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
std::make_tuple(
AAUDIO_SHARING_MODE_EXCLUSIVE,
AAUDIO_UNSPECIFIED,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED)),
&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 myData->returnStop ? AAUDIO_CALLBACK_RESULT_STOP : AAUDIO_CALLBACK_RESULT_CONTINUE;
}
void AAudioOutputStreamCallbackTest::SetUp() {
mSetupSuccessful = false;
if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
mHelper.reset(new OutputStreamBuilderHelper(
std::get<PARAM_SHARING_MODE>(GetParam()),
std::get<PARAM_PERF_MODE>(GetParam()),
std::get<PARAM_AUDIO_FORMAT>(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);
}
createAndVerifyHonoringMMap();
}
// Test starting and stopping an OUTPUT AAudioStream that uses a Callback
TEST_P(AAudioOutputStreamCallbackTest, testPlayback) {
if (!mSetupSuccessful) return;
const int32_t framesPerDataCallback = std::get<PARAM_FRAMES_PER_CB>(GetParam());
const int32_t streamFramesPerDataCallback = AAudioStream_getFramesPerDataCallback(stream());
if (framesPerDataCallback != AAUDIO_UNSPECIFIED) {
ASSERT_EQ(framesPerDataCallback, streamFramesPerDataCallback);
}
constexpr int kSleepSeconds = 2;
constexpr int kMinExpectedCallbacks = kMinCallbacksPerSecond * kSleepSeconds;
// Try all 3 methods of stopping/pausing a stream.
constexpr int kNumMethods = 3;
// Start/stop more than once to see if it fails after the first time.
// Also check to make sure we do not get callbacks after the stop.
for (int loopIndex = 0; loopIndex < kNumMethods; loopIndex++) {
mCbData->reset(streamFramesPerDataCallback);
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(kSleepSeconds); // let the stream run
ASSERT_EQ(AAUDIO_OK, mCbData->callbackError);
ASSERT_GT(mCbData->callbackCount, kMinExpectedCallbacks);
switch (loopIndex % kNumMethods) {
case 0:
mCbData->returnStop = true; // callback return
mHelper->waitForState(AAUDIO_STREAM_STATE_STOPPED);
break;
case 1:
mHelper->pauseStream();
break;
case 2:
mHelper->stopStream();
break;
}
int32_t oldCallbackCount = mCbData->callbackCount;
EXPECT_GT(oldCallbackCount, kMinExpectedCallbacks);
sleep(1);
EXPECT_EQ(oldCallbackCount, mCbData->callbackCount); // expect not advancing
if (streamFramesPerDataCallback != AAUDIO_UNSPECIFIED) {
ASSERT_EQ(streamFramesPerDataCallback, mCbData->actualFramesPerCallback);
}
EXPECT_GE(mCbData->minLatency, 1); // Absurdly low
// We only issue a warning here because the CDD does not mandate a specific minimum latency
if (mCbData->maxLatency > 300) {
__android_log_print(ANDROID_LOG_WARN, LOG_TAG,
"Suspiciously high callback latency: %d", mCbData->maxLatency);
}
//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,
MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
// cb buffer size: arbitrary prime number < 96
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 67, AAUDIO_PERFORMANCE_MODE_NONE, MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 67, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
std::make_tuple(AAUDIO_SHARING_MODE_EXCLUSIVE, 67, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 67, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, MMAP_NOT_ALLOWED,
AAUDIO_FORMAT_PCM_I16),
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 67, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, MMAP_NOT_ALLOWED,
AAUDIO_FORMAT_PCM_FLOAT),
// cb buffer size: arbitrary prime number > 192
std::make_tuple(AAUDIO_SHARING_MODE_SHARED, 223, AAUDIO_PERFORMANCE_MODE_NONE, MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
std::make_tuple(
AAUDIO_SHARING_MODE_SHARED,
AAUDIO_UNSPECIFIED,
AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
std::make_tuple(
AAUDIO_SHARING_MODE_SHARED,
AAUDIO_UNSPECIFIED,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED),
std::make_tuple(
AAUDIO_SHARING_MODE_EXCLUSIVE,
AAUDIO_UNSPECIFIED,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
MMAP_ALLOWED,
AAUDIO_FORMAT_UNSPECIFIED)),
&getTestName);