/* * Copyright (C) 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. */ // Test various AAudio features including AAudioStream_setBufferSizeInFrames(). #include <condition_variable> #include <mutex> #include <stdio.h> #include <android-base/macros.h> #include <aaudio/AAudio.h> #include <gtest/gtest.h> #include <unistd.h> // Callback function that does nothing. aaudio_data_callback_result_t NoopDataCallbackProc( AAudioStream *stream, void *userData, void *audioData, int32_t numFrames ) { (void) stream; (void) userData; (void) audioData; (void) numFrames; return AAUDIO_CALLBACK_RESULT_CONTINUE; } // Test AAudioStream_setBufferSizeInFrames() constexpr int64_t NANOS_PER_MILLISECOND = 1000 * 1000; enum FunctionToCall { CALL_START, CALL_STOP, CALL_PAUSE, CALL_FLUSH }; void checkStateTransition(aaudio_performance_mode_t perfMode, aaudio_stream_state_t originalState, FunctionToCall functionToCall, aaudio_result_t expectedResult, aaudio_stream_state_t expectedState) { AAudioStreamBuilder *aaudioBuilder = nullptr; AAudioStream *aaudioStream = nullptr; // Use an AAudioStreamBuilder to contain requested parameters. ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); // Request stream properties. AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr); AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode); // Create an AAudioStream using the Builder. ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream)); // Verify Open State aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN; EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_UNKNOWN, &state, 1000 * NANOS_PER_MILLISECOND)); EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state); // Put stream into desired state. aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_UNINITIALIZED; if (originalState != AAUDIO_STREAM_STATE_OPEN) { ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream)); if (originalState != AAUDIO_STREAM_STATE_STARTING) { ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_STARTING, &state, 1000 * NANOS_PER_MILLISECOND)); ASSERT_EQ(AAUDIO_STREAM_STATE_STARTED, state); if (originalState == AAUDIO_STREAM_STATE_STOPPING) { ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream)); } else if (originalState == AAUDIO_STREAM_STATE_STOPPED) { ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream)); inputState = AAUDIO_STREAM_STATE_STOPPING; } else if (originalState == AAUDIO_STREAM_STATE_PAUSING) { ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream)); } else if (originalState == AAUDIO_STREAM_STATE_PAUSED) { ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream)); inputState = AAUDIO_STREAM_STATE_PAUSING; } } } // Wait until past transitional state. if (inputState != AAUDIO_STREAM_STATE_UNINITIALIZED) { ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream, inputState, &state, 1000 * NANOS_PER_MILLISECOND)); ASSERT_EQ(originalState, state); } aaudio_stream_state_t transitionalState = originalState; switch(functionToCall) { case FunctionToCall::CALL_START: EXPECT_EQ(expectedResult, AAudioStream_requestStart(aaudioStream)); transitionalState = AAUDIO_STREAM_STATE_STARTING; break; case FunctionToCall::CALL_STOP: EXPECT_EQ(expectedResult, AAudioStream_requestStop(aaudioStream)); transitionalState = AAUDIO_STREAM_STATE_STOPPING; break; case FunctionToCall::CALL_PAUSE: EXPECT_EQ(expectedResult, AAudioStream_requestPause(aaudioStream)); transitionalState = AAUDIO_STREAM_STATE_PAUSING; break; case FunctionToCall::CALL_FLUSH: EXPECT_EQ(expectedResult, AAudioStream_requestFlush(aaudioStream)); transitionalState = AAUDIO_STREAM_STATE_FLUSHING; break; } EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream, transitionalState, &state, 1000 * NANOS_PER_MILLISECOND)); // We should not change state when a function fails. if (expectedResult != AAUDIO_OK) { ASSERT_EQ(originalState, expectedState); } EXPECT_EQ(expectedState, state); if (state != expectedState) { printf("ERROR - expected %s, actual = %s\n", AAudio_convertStreamStateToText(expectedState), AAudio_convertStreamStateToText(state)); fflush(stdout); } AAudioStream_close(aaudioStream); AAudioStreamBuilder_delete(aaudioBuilder); } // TODO Use parameterized tests instead of these individual specific tests. // OPEN ================================================================= TEST(test_various, aaudio_state_lowlat_open_start) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_OPEN, FunctionToCall::CALL_START, AAUDIO_OK, AAUDIO_STREAM_STATE_STARTED); } TEST(test_various, aaudio_state_none_open_start) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_OPEN, FunctionToCall::CALL_START, AAUDIO_OK, AAUDIO_STREAM_STATE_STARTED); } TEST(test_various, aaudio_state_lowlat_open_stop) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_OPEN, FunctionToCall::CALL_STOP, AAUDIO_OK, AAUDIO_STREAM_STATE_STOPPED); } TEST(test_various, aaudio_state_none_open_stop) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_OPEN, FunctionToCall::CALL_STOP, AAUDIO_OK, AAUDIO_STREAM_STATE_STOPPED); } TEST(test_various, aaudio_state_lowlat_open_pause) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_OPEN, FunctionToCall::CALL_PAUSE, AAUDIO_OK, AAUDIO_STREAM_STATE_PAUSED); } TEST(test_various, aaudio_state_none_open_pause) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_OPEN, FunctionToCall::CALL_PAUSE, AAUDIO_OK, AAUDIO_STREAM_STATE_PAUSED); } TEST(test_various, aaudio_state_lowlat_open_flush) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_OPEN, FunctionToCall::CALL_FLUSH, AAUDIO_OK, AAUDIO_STREAM_STATE_FLUSHED); } TEST(test_various, aaudio_state_none_open_flush) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_OPEN, FunctionToCall::CALL_FLUSH, AAUDIO_OK, AAUDIO_STREAM_STATE_FLUSHED); } // STARTED ================================================================= TEST(test_various, aaudio_state_lowlat_started_start) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_STARTED, FunctionToCall::CALL_START, AAUDIO_ERROR_INVALID_STATE, AAUDIO_STREAM_STATE_STARTED); } TEST(test_various, aaudio_state_none_started_start) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_STARTED, FunctionToCall::CALL_START, AAUDIO_ERROR_INVALID_STATE, AAUDIO_STREAM_STATE_STARTED); } TEST(test_various, aaudio_state_lowlat_started_stop) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_STARTED, FunctionToCall::CALL_STOP, AAUDIO_OK, AAUDIO_STREAM_STATE_STOPPED); } TEST(test_various, aaudio_state_none_started_stop) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_STARTED, FunctionToCall::CALL_STOP, AAUDIO_OK, AAUDIO_STREAM_STATE_STOPPED); } TEST(test_various, aaudio_state_lowlat_started_pause) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_STARTED, FunctionToCall::CALL_PAUSE, AAUDIO_OK, AAUDIO_STREAM_STATE_PAUSED); } TEST(test_various, aaudio_state_none_started_pause) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_STARTED, FunctionToCall::CALL_PAUSE, AAUDIO_OK, AAUDIO_STREAM_STATE_PAUSED); } TEST(test_various, aaudio_state_lowlat_started_flush) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_STARTED, FunctionToCall::CALL_FLUSH, AAUDIO_ERROR_INVALID_STATE, AAUDIO_STREAM_STATE_STARTED); } TEST(test_various, aaudio_state_none_started_flush) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_STARTED, FunctionToCall::CALL_FLUSH, AAUDIO_ERROR_INVALID_STATE, AAUDIO_STREAM_STATE_STARTED); } // STOPPED ================================================================= TEST(test_various, aaudio_state_lowlat_stopped_start) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_STOPPED, FunctionToCall::CALL_START, AAUDIO_OK, AAUDIO_STREAM_STATE_STARTED); } TEST(test_various, aaudio_state_none_stopped_start) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_STOPPED, FunctionToCall::CALL_START, AAUDIO_OK, AAUDIO_STREAM_STATE_STARTED); } TEST(test_various, aaudio_state_lowlat_stopped_stop) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_STOPPED, FunctionToCall::CALL_STOP, AAUDIO_OK, AAUDIO_STREAM_STATE_STOPPED); } TEST(test_various, aaudio_state_none_stopped_stop) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_STOPPED, FunctionToCall::CALL_STOP, AAUDIO_OK, AAUDIO_STREAM_STATE_STOPPED); } TEST(test_various, aaudio_state_lowlat_stopped_pause) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_STOPPED, FunctionToCall::CALL_PAUSE, AAUDIO_OK, AAUDIO_STREAM_STATE_PAUSED); } TEST(test_various, aaudio_state_none_stopped_pause) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_STOPPED, FunctionToCall::CALL_PAUSE, AAUDIO_OK, AAUDIO_STREAM_STATE_PAUSED); } TEST(test_various, aaudio_state_lowlat_stopped_flush) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_STOPPED, FunctionToCall::CALL_FLUSH, AAUDIO_OK, AAUDIO_STREAM_STATE_FLUSHED); } TEST(test_various, aaudio_state_none_stopped_flush) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_STOPPED, FunctionToCall::CALL_FLUSH, AAUDIO_OK, AAUDIO_STREAM_STATE_FLUSHED); } // PAUSED ================================================================= TEST(test_various, aaudio_state_lowlat_paused_start) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_PAUSED, FunctionToCall::CALL_START, AAUDIO_OK, AAUDIO_STREAM_STATE_STARTED); } TEST(test_various, aaudio_state_none_paused_start) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_PAUSED, FunctionToCall::CALL_START, AAUDIO_OK, AAUDIO_STREAM_STATE_STARTED); } TEST(test_various, aaudio_state_lowlat_paused_stop) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_PAUSED, FunctionToCall::CALL_STOP, AAUDIO_OK, AAUDIO_STREAM_STATE_STOPPED); } TEST(test_various, aaudio_state_none_paused_stop) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_PAUSED, FunctionToCall::CALL_STOP, AAUDIO_OK, AAUDIO_STREAM_STATE_STOPPED); } TEST(test_various, aaudio_state_lowlat_paused_pause) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_PAUSED, FunctionToCall::CALL_PAUSE, AAUDIO_OK, AAUDIO_STREAM_STATE_PAUSED); } TEST(test_various, aaudio_state_none_paused_pause) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_PAUSED, FunctionToCall::CALL_PAUSE, AAUDIO_OK, AAUDIO_STREAM_STATE_PAUSED); } TEST(test_various, aaudio_state_lowlat_paused_flush) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_STREAM_STATE_PAUSED, FunctionToCall::CALL_FLUSH, AAUDIO_OK, AAUDIO_STREAM_STATE_FLUSHED); } TEST(test_various, aaudio_state_none_paused_flush) { checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_STREAM_STATE_PAUSED, FunctionToCall::CALL_FLUSH, AAUDIO_OK, AAUDIO_STREAM_STATE_FLUSHED); } // ========================================================================== TEST(test_various, aaudio_set_buffer_size) { int32_t bufferCapacity; int32_t framesPerBurst = 0; int32_t actualSize = 0; AAudioStreamBuilder *aaudioBuilder = nullptr; AAudioStream *aaudioStream = nullptr; // Use an AAudioStreamBuilder to contain requested parameters. ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); // Request stream properties. AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr); AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); // Create an AAudioStream using the Builder. EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream)); // This is the number of frames that are read in one chunk by a DMA controller // or a DSP or a mixer. framesPerBurst = AAudioStream_getFramesPerBurst(aaudioStream); bufferCapacity = AAudioStream_getBufferCapacityInFrames(aaudioStream); printf(" bufferCapacity = %d, remainder = %d\n", bufferCapacity, bufferCapacity % framesPerBurst); actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 0); EXPECT_GT(actualSize, 0); EXPECT_LE(actualSize, bufferCapacity); actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 2 * framesPerBurst); EXPECT_GT(actualSize, framesPerBurst); EXPECT_LE(actualSize, bufferCapacity); actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, bufferCapacity - 1); EXPECT_GT(actualSize, framesPerBurst); EXPECT_LE(actualSize, bufferCapacity); actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, bufferCapacity); EXPECT_GT(actualSize, framesPerBurst); EXPECT_LE(actualSize, bufferCapacity); actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, bufferCapacity + 1); EXPECT_GT(actualSize, framesPerBurst); EXPECT_LE(actualSize, bufferCapacity); actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 1234567); EXPECT_GT(actualSize, framesPerBurst); EXPECT_LE(actualSize, bufferCapacity); actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, INT32_MAX); EXPECT_GT(actualSize, framesPerBurst); EXPECT_LE(actualSize, bufferCapacity); actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, INT32_MIN); EXPECT_GT(actualSize, 0); EXPECT_LE(actualSize, bufferCapacity); AAudioStream_close(aaudioStream); AAudioStreamBuilder_delete(aaudioBuilder); } // ************************************************************ // Test to make sure that AAUDIO_CALLBACK_RESULT_STOP works. // Callback function that counts calls. aaudio_data_callback_result_t CallbackOnceProc( AAudioStream *stream, void *userData, void *audioData, int32_t numFrames ) { (void) stream; (void) audioData; (void) numFrames; std::atomic<int32_t> *callbackCountPtr = (std::atomic<int32_t> *)userData; (*callbackCountPtr)++; return AAUDIO_CALLBACK_RESULT_STOP; } void checkCallbackOnce(aaudio_performance_mode_t perfMode) { std::atomic<int32_t> callbackCount{0}; AAudioStreamBuilder *aaudioBuilder = nullptr; AAudioStream *aaudioStream = nullptr; // Use an AAudioStreamBuilder to contain requested parameters. ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); // Request stream properties. AAudioStreamBuilder_setDataCallback(aaudioBuilder, CallbackOnceProc, &callbackCount); AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode); // Create an AAudioStream using the Builder. ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream)); AAudioStreamBuilder_delete(aaudioBuilder); ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream)); sleep(1); // Give callback a chance to run many times. EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream)); EXPECT_EQ(1, callbackCount.load()); // should stop after first call EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream)); } TEST(test_various, aaudio_callback_once_none) { checkCallbackOnce(AAUDIO_PERFORMANCE_MODE_NONE); } TEST(test_various, aaudio_callback_once_lowlat) { checkCallbackOnce(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); } // ************************************************************ struct WakeUpCallbackData { void wakeOther() { // signal waiting test to wake up { std::lock_guard <std::mutex> lock(mutex); finished = true; } conditionVariable.notify_one(); } void waitForFinished() { std::unique_lock <std::mutex> aLock(mutex); conditionVariable.wait(aLock, [=] { return finished; }); } // For signalling foreground test when callback finished std::mutex mutex; std::condition_variable conditionVariable; bool finished = false; }; // Test to make sure we cannot call recursively into the system from a callback. struct DangerousData : public WakeUpCallbackData { aaudio_result_t resultStart = AAUDIO_OK; aaudio_result_t resultStop = AAUDIO_OK; aaudio_result_t resultPause = AAUDIO_OK; aaudio_result_t resultFlush = AAUDIO_OK; aaudio_result_t resultClose = AAUDIO_OK; }; // Callback function that tries to call back into the stream. aaudio_data_callback_result_t DangerousDataCallbackProc( AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) { (void) audioData; (void) numFrames; DangerousData *data = (DangerousData *)userData; data->resultStart = AAudioStream_requestStart(stream); data->resultStop = AAudioStream_requestStop(stream); data->resultPause = AAudioStream_requestPause(stream); data->resultFlush = AAudioStream_requestFlush(stream); data->resultClose = AAudioStream_close(stream); data->wakeOther(); return AAUDIO_CALLBACK_RESULT_STOP; } //int main() { // To fix Android Studio formatting when editing. void checkDangerousCallback(aaudio_performance_mode_t perfMode) { DangerousData dangerousData; AAudioStreamBuilder *aaudioBuilder = nullptr; AAudioStream *aaudioStream = nullptr; // Use an AAudioStreamBuilder to contain requested parameters. ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); // Request stream properties. AAudioStreamBuilder_setDataCallback(aaudioBuilder, DangerousDataCallbackProc, &dangerousData); AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode); // Create an AAudioStream using the Builder. ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream)); AAudioStreamBuilder_delete(aaudioBuilder); ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream)); dangerousData.waitForFinished(); EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream)); EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultStart); EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultStop); EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultPause); EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultFlush); EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultClose); EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream)); } //int main() { // To fix Android Studio formatting when editing. TEST(test_various, aaudio_callback_blockers_none) { checkDangerousCallback(AAUDIO_PERFORMANCE_MODE_NONE); } TEST(test_various, aaudio_callback_blockers_lowlat) { checkDangerousCallback(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); }