blob: 7db3688e1118f31545e99baa55ce97b43d948e06 [file] [log] [blame]
/*
* Copyright (C) 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.
*/
// Unit tests for AAudio 'C' API.
#include <stdlib.h>
#include <math.h>
#include <gtest/gtest.h>
#include <aaudio/AAudioDefinitions.h>
#include <aaudio/AAudio.h>
#include "AAudioUtilities.h"
#define DEFAULT_STATE_TIMEOUT (500 * AAUDIO_NANOS_PER_MILLISECOND)
// Test AAudioStreamBuilder
TEST(test_aaudio_api, aaudio_stream_builder) {
const aaudio_sample_rate_t requestedSampleRate1 = 48000;
const aaudio_sample_rate_t requestedSampleRate2 = 44100;
const int32_t requestedSamplesPerFrame = 2;
const aaudio_audio_format_t requestedDataFormat = AAUDIO_FORMAT_PCM16;
aaudio_sample_rate_t sampleRate = 0;
int32_t samplesPerFrame = 0;
aaudio_audio_format_t actualDataFormat;
AAudioStreamBuilder aaudioBuilder1;
AAudioStreamBuilder aaudioBuilder2;
aaudio_result_t result = AAUDIO_OK;
// Use an AAudioStreamBuilder to define the stream.
result = AAudio_createStreamBuilder(&aaudioBuilder1);
ASSERT_EQ(AAUDIO_OK, result);
// Request stream properties.
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSampleRate(aaudioBuilder1, requestedSampleRate1));
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSamplesPerFrame(aaudioBuilder1, requestedSamplesPerFrame));
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setFormat(aaudioBuilder1, requestedDataFormat));
// Check to make sure builder saved the properties.
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder1, &sampleRate));
EXPECT_EQ(requestedSampleRate1, sampleRate);
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSamplesPerFrame(aaudioBuilder1, &samplesPerFrame));
EXPECT_EQ(requestedSamplesPerFrame, samplesPerFrame);
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getFormat(aaudioBuilder1, &actualDataFormat));
EXPECT_EQ(requestedDataFormat, actualDataFormat);
result = AAudioStreamBuilder_getSampleRate(0x0BADCAFE, &sampleRate); // ridiculous token
EXPECT_EQ(AAUDIO_ERROR_INVALID_HANDLE, result);
// Create a second builder and make sure they do not collide.
ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder2));
ASSERT_NE(aaudioBuilder1, aaudioBuilder2);
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSampleRate(aaudioBuilder2, requestedSampleRate2));
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder1, &sampleRate));
EXPECT_EQ(requestedSampleRate1, sampleRate);
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder2, &sampleRate));
EXPECT_EQ(requestedSampleRate2, sampleRate);
// Delete the builder.
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder1));
// Now it should no longer be valid.
// Note that test assumes we are using the HandleTracker. If we use plain pointers
// then it will be difficult to detect this kind of error.
result = AAudioStreamBuilder_getSampleRate(aaudioBuilder1, &sampleRate); // stale token
EXPECT_EQ(AAUDIO_ERROR_INVALID_HANDLE, result);
// Second builder should still be valid.
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder2, &sampleRate));
EXPECT_EQ(requestedSampleRate2, sampleRate);
// Delete the second builder.
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder2));
// Now it should no longer be valid. Assumes HandlerTracker used.
EXPECT_EQ(AAUDIO_ERROR_INVALID_HANDLE, AAudioStreamBuilder_getSampleRate(aaudioBuilder2, &sampleRate));
}
// Test creating a default stream with everything unspecified.
TEST(test_aaudio_api, aaudio_stream_unspecified) {
AAudioStreamBuilder aaudioBuilder;
AAudioStream aaudioStream;
aaudio_result_t result = AAUDIO_OK;
// Use an AAudioStreamBuilder to define the stream.
result = AAudio_createStreamBuilder(&aaudioBuilder);
ASSERT_EQ(AAUDIO_OK, result);
// Create an AAudioStream using the Builder.
ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
// Cleanup
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
}
// Test Writing to an AAudioStream
void runtest_aaudio_stream(aaudio_sharing_mode_t requestedSharingMode) {
const aaudio_sample_rate_t requestedSampleRate = 48000;
const aaudio_sample_rate_t requestedSamplesPerFrame = 2;
const aaudio_audio_format_t requestedDataFormat = AAUDIO_FORMAT_PCM16;
aaudio_sample_rate_t actualSampleRate = -1;
int32_t actualSamplesPerFrame = -1;
aaudio_audio_format_t actualDataFormat = AAUDIO_FORMAT_INVALID;
aaudio_sharing_mode_t actualSharingMode;
aaudio_size_frames_t framesPerBurst = -1;
int writeLoops = 0;
aaudio_size_frames_t framesWritten = 0;
aaudio_size_frames_t framesPrimed = 0;
aaudio_position_frames_t framesTotal = 0;
aaudio_position_frames_t aaudioFramesRead = 0;
aaudio_position_frames_t aaudioFramesRead1 = 0;
aaudio_position_frames_t aaudioFramesRead2 = 0;
aaudio_position_frames_t aaudioFramesWritten = 0;
aaudio_nanoseconds_t timeoutNanos;
aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
AAudioStreamBuilder aaudioBuilder;
AAudioStream aaudioStream;
aaudio_result_t result = AAUDIO_OK;
// Use an AAudioStreamBuilder to define the stream.
result = AAudio_createStreamBuilder(&aaudioBuilder);
ASSERT_EQ(AAUDIO_OK, result);
// Request stream properties.
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSampleRate(aaudioBuilder, requestedSampleRate));
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSamplesPerFrame(aaudioBuilder, requestedSamplesPerFrame));
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setFormat(aaudioBuilder, requestedDataFormat));
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSharingMode(aaudioBuilder, requestedSharingMode));
// Create an AAudioStream using the Builder.
ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
EXPECT_EQ(AAUDIO_OK, AAudioStream_getState(aaudioStream, &state));
EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
// Check to see what kind of stream we actually got.
EXPECT_EQ(AAUDIO_OK, AAudioStream_getSampleRate(aaudioStream, &actualSampleRate));
ASSERT_TRUE(actualSampleRate >= 44100 && actualSampleRate <= 96000); // TODO what is range?
EXPECT_EQ(AAUDIO_OK, AAudioStream_getSamplesPerFrame(aaudioStream, &actualSamplesPerFrame));
ASSERT_TRUE(actualSamplesPerFrame >= 1 && actualSamplesPerFrame <= 16); // TODO what is max?
EXPECT_EQ(AAUDIO_OK, AAudioStream_getSharingMode(aaudioStream, &actualSharingMode));
ASSERT_TRUE(actualSharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE
|| actualSharingMode == AAUDIO_SHARING_MODE_LEGACY);
EXPECT_EQ(AAUDIO_OK, AAudioStream_getFormat(aaudioStream, &actualDataFormat));
EXPECT_NE(AAUDIO_FORMAT_INVALID, actualDataFormat);
EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesPerBurst(aaudioStream, &framesPerBurst));
ASSERT_TRUE(framesPerBurst >= 16 && framesPerBurst <= 1024); // TODO what is min/max?
// Allocate a buffer for the audio data.
// TODO handle possibility of other data formats
ASSERT_TRUE(actualDataFormat == AAUDIO_FORMAT_PCM16);
size_t dataSizeSamples = framesPerBurst * actualSamplesPerFrame;
int16_t *data = new int16_t[dataSizeSamples];
ASSERT_TRUE(nullptr != data);
memset(data, 0, sizeof(int16_t) * dataSizeSamples);
// Prime the buffer.
timeoutNanos = 0;
do {
framesWritten = AAudioStream_write(aaudioStream, data, 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);
// 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++) {
// Start and wait for server to respond.
ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
AAUDIO_STREAM_STATE_STARTING,
&state,
DEFAULT_STATE_TIMEOUT));
EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
// Write some data while we are running. Read counter should be advancing.
writeLoops = 1 * actualSampleRate / framesPerBurst; // 1 second
ASSERT_LT(2, writeLoops); // detect absurdly high framesPerBurst
timeoutNanos = 10 * AAUDIO_NANOS_PER_SECOND * framesPerBurst / actualSampleRate; // bursts
framesWritten = 1;
ASSERT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
aaudioFramesRead1 = aaudioFramesRead;
aaudio_nanoseconds_t beginTime = AAudio_getNanoseconds(AAUDIO_CLOCK_MONOTONIC);
do {
framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
ASSERT_GE(framesWritten, 0);
ASSERT_LE(framesWritten, framesPerBurst);
framesTotal += framesWritten;
EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesWritten(aaudioStream, &aaudioFramesWritten));
EXPECT_EQ(framesTotal, aaudioFramesWritten);
// Try to get a more accurate measure of the sample rate.
if (beginTime == 0) {
EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
if (aaudioFramesRead > aaudioFramesRead1) { // is read pointer advancing
beginTime = AAudio_getNanoseconds(AAUDIO_CLOCK_MONOTONIC);
aaudioFramesRead1 = aaudioFramesRead;
}
}
} while (framesWritten > 0 && writeLoops-- > 0);
EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead2));
aaudio_nanoseconds_t endTime = AAudio_getNanoseconds(AAUDIO_CLOCK_MONOTONIC);
ASSERT_GT(aaudioFramesRead2, 0);
ASSERT_GT(aaudioFramesRead2, aaudioFramesRead1);
ASSERT_LE(aaudioFramesRead2, aaudioFramesWritten);
// TODO why is legacy so inaccurate?
const double rateTolerance = 200.0; // arbitrary tolerance for sample rate
if (requestedSharingMode != AAUDIO_SHARING_MODE_LEGACY) {
// Calculate approximate sample rate and compare with stream rate.
double seconds = (endTime - beginTime) / (double) AAUDIO_NANOS_PER_SECOND;
double measuredRate = (aaudioFramesRead2 - aaudioFramesRead1) / seconds;
ASSERT_NEAR(actualSampleRate, measuredRate, rateTolerance);
}
// Request async pause and wait for server to say that it has completed the pause.
ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
AAUDIO_STREAM_STATE_PAUSING,
&state,
DEFAULT_STATE_TIMEOUT));
EXPECT_EQ(AAUDIO_STREAM_STATE_PAUSED, state);
}
// Make sure the read counter is not advancing when we are paused.
ASSERT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
ASSERT_GE(aaudioFramesRead, aaudioFramesRead2); // monotonic increase
// Use this to sleep by waiting for something that won't happen.
AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_PAUSED, &state, timeoutNanos);
ASSERT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead2));
EXPECT_EQ(aaudioFramesRead, aaudioFramesRead2);
// ------------------- TEST FLUSH -----------------
// Prime the buffer.
timeoutNanos = 0;
writeLoops = 100;
do {
framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
framesTotal += framesWritten;
} while (framesWritten > 0 && writeLoops-- > 0);
EXPECT_EQ(0, framesWritten);
// Flush and wait for server to respond.
ASSERT_EQ(AAUDIO_OK, AAudioStream_requestFlush(aaudioStream));
EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
AAUDIO_STREAM_STATE_FLUSHING,
&state,
DEFAULT_STATE_TIMEOUT));
EXPECT_EQ(AAUDIO_STREAM_STATE_FLUSHED, state);
// After a flush, the read counter should be caught up with the write counter.
EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesWritten(aaudioStream, &aaudioFramesWritten));
EXPECT_EQ(framesTotal, aaudioFramesWritten);
EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
EXPECT_EQ(aaudioFramesRead, aaudioFramesWritten);
// The buffer should be empty after a flush so we should be able to write.
framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
// There should be some room for priming the buffer.
ASSERT_TRUE(framesWritten > 0 && framesWritten <= framesPerBurst);
EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
}
// Test Writing to an AAudioStream using LEGACY sharing mode.
TEST(test_aaudio_api, aaudio_stream_legacy) {
runtest_aaudio_stream(AAUDIO_SHARING_MODE_LEGACY);
}
// Test Writing to an AAudioStream using EXCLUSIVE sharing mode.
TEST(test_aaudio_api, aaudio_stream_exclusive) {
runtest_aaudio_stream(AAUDIO_SHARING_MODE_EXCLUSIVE);
}
#define AAUDIO_THREAD_ANSWER 1826375
#define AAUDIO_THREAD_DURATION_MSEC 500
static void *TestAAudioStreamThreadProc(void *arg) {
AAudioStream aaudioStream = (AAudioStream) reinterpret_cast<size_t>(arg);
aaudio_stream_state_t state;
// Use this to sleep by waiting for something that won't happen.
EXPECT_EQ(AAUDIO_OK, AAudioStream_getState(aaudioStream, &state));
AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_PAUSED, &state,
AAUDIO_THREAD_DURATION_MSEC * AAUDIO_NANOS_PER_MILLISECOND);
return reinterpret_cast<void *>(AAUDIO_THREAD_ANSWER);
}
// Test creating a stream related thread.
TEST(test_aaudio_api, aaudio_stream_thread_basic) {
AAudioStreamBuilder aaudioBuilder;
AAudioStream aaudioStream;
aaudio_result_t result = AAUDIO_OK;
void *threadResult;
// Use an AAudioStreamBuilder to define the stream.
result = AAudio_createStreamBuilder(&aaudioBuilder);
ASSERT_EQ(AAUDIO_OK, result);
// Create an AAudioStream using the Builder.
ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
// Start a thread.
ASSERT_EQ(AAUDIO_OK, AAudioStream_createThread(aaudioStream,
10 * AAUDIO_NANOS_PER_MILLISECOND,
TestAAudioStreamThreadProc,
reinterpret_cast<void *>(aaudioStream)));
// Thread already started.
ASSERT_NE(AAUDIO_OK, AAudioStream_createThread(aaudioStream, // should fail!
10 * AAUDIO_NANOS_PER_MILLISECOND,
TestAAudioStreamThreadProc,
reinterpret_cast<void *>(aaudioStream)));
// Wait for the thread to finish.
ASSERT_EQ(AAUDIO_OK, AAudioStream_joinThread(aaudioStream,
&threadResult, 2 * AAUDIO_THREAD_DURATION_MSEC * AAUDIO_NANOS_PER_MILLISECOND));
// The thread returns a special answer.
ASSERT_EQ(AAUDIO_THREAD_ANSWER, (int)reinterpret_cast<size_t>(threadResult));
// Thread should already be joined.
ASSERT_NE(AAUDIO_OK, AAudioStream_joinThread(aaudioStream, // should fail!
&threadResult, 2 * AAUDIO_THREAD_DURATION_MSEC * AAUDIO_NANOS_PER_MILLISECOND));
// Cleanup
EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
}