210 lines
7.5 KiB
C++
210 lines
7.5 KiB
C++
/*
|
|
* Copyright (C) 2019 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 whether the callback is joined before the close finishes.
|
|
*
|
|
* Start a stream with a callback.
|
|
* The callback just sleeps for a long time.
|
|
* While the callback is sleeping, close() the stream from the main thread.
|
|
* Then check to make sure the callback was joined before the close() returns.
|
|
*
|
|
* This can hang if there are deadlocks. So make sure you get a PASSED result.
|
|
*/
|
|
|
|
#include <atomic>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <aaudio/AAudio.h>
|
|
|
|
// Sleep long enough that the foreground has a change to call close.
|
|
static constexpr int kCallbackSleepMicros = 600 * 1000;
|
|
|
|
class AudioEngine {
|
|
public:
|
|
|
|
// Check for a crash or late callback if we close without stopping.
|
|
void checkCloseJoins(aaudio_direction_t direction,
|
|
aaudio_performance_mode_t perfMode,
|
|
aaudio_data_callback_result_t callbackResult) {
|
|
|
|
// Make printf print immediately so that debug info is not stuck
|
|
// in a buffer if we hang or crash.
|
|
setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
|
|
|
|
mCallbackResult = callbackResult;
|
|
startStreamForStall(direction, perfMode);
|
|
// When the callback starts it will go to sleep.
|
|
waitForCallbackToStart();
|
|
|
|
printf("call AAudioStream_close()\n");
|
|
ASSERT_FALSE(mCallbackFinished); // Still sleeping?
|
|
aaudio_result_t result = AAudioStream_close(mStream); // May hang here!
|
|
ASSERT_TRUE(mCallbackFinished);
|
|
ASSERT_EQ(AAUDIO_OK, result);
|
|
printf("AAudioStream_close() returned %d\n", result);
|
|
|
|
ASSERT_EQ(AAUDIO_OK, mError.load());
|
|
// Did calling stop() from callback fail? It should have.
|
|
ASSERT_NE(AAUDIO_OK, mStopResult.load());
|
|
}
|
|
|
|
private:
|
|
void startStreamForStall(aaudio_direction_t direction,
|
|
aaudio_performance_mode_t perfMode) {
|
|
AAudioStreamBuilder* builder = nullptr;
|
|
aaudio_result_t result = AAUDIO_OK;
|
|
|
|
// Use an AAudioStreamBuilder to contain requested parameters.
|
|
result = AAudio_createStreamBuilder(&builder);
|
|
ASSERT_EQ(AAUDIO_OK, result);
|
|
|
|
// Request stream properties.
|
|
AAudioStreamBuilder_setDirection(builder, direction);
|
|
AAudioStreamBuilder_setPerformanceMode(builder, perfMode);
|
|
AAudioStreamBuilder_setDataCallback(builder, s_myDataCallbackProc, this);
|
|
AAudioStreamBuilder_setErrorCallback(builder, s_myErrorCallbackProc, this);
|
|
|
|
// Create an AAudioStream using the Builder.
|
|
result = AAudioStreamBuilder_openStream(builder, &mStream);
|
|
AAudioStreamBuilder_delete(builder);
|
|
ASSERT_EQ(AAUDIO_OK, result);
|
|
|
|
// Check to see what kind of stream we actually got.
|
|
int32_t deviceId = AAudioStream_getDeviceId(mStream);
|
|
aaudio_performance_mode_t
|
|
actualPerfMode = AAudioStream_getPerformanceMode(mStream);
|
|
printf("-------- opened: deviceId = %3d, perfMode = %d\n",
|
|
deviceId,
|
|
actualPerfMode);
|
|
|
|
// Start stream.
|
|
result = AAudioStream_requestStart(mStream);
|
|
ASSERT_EQ(AAUDIO_OK, result);
|
|
}
|
|
|
|
void waitForCallbackToStart() {
|
|
// Wait for callback to say it has been called.
|
|
int countDownMillis = 2000;
|
|
constexpr int countDownPeriodMillis = 50;
|
|
while (!mCallbackStarted && countDownMillis > 0) {
|
|
printf("Waiting for callback to start, %d\n", countDownMillis);
|
|
usleep(countDownPeriodMillis * 1000);
|
|
countDownMillis -= countDownPeriodMillis;
|
|
}
|
|
ASSERT_LT(0, countDownMillis);
|
|
ASSERT_TRUE(mCallbackStarted);
|
|
}
|
|
|
|
// Callback function that fills the audio output buffer.
|
|
static aaudio_data_callback_result_t s_myDataCallbackProc(
|
|
AAudioStream *stream,
|
|
void *userData,
|
|
void * /*audioData */,
|
|
int32_t /* numFrames */
|
|
) {
|
|
AudioEngine* engine = (AudioEngine*) userData;
|
|
engine->mCallbackStarted = true;
|
|
usleep(kCallbackSleepMicros);
|
|
// it is illegal to call stop() from the callback. It should
|
|
// return an error and not hang.
|
|
engine->mStopResult = AAudioStream_requestStop(stream);
|
|
engine->mCallbackFinished = true;
|
|
return engine->mCallbackResult;
|
|
}
|
|
|
|
static void s_myErrorCallbackProc(
|
|
AAudioStream * /* stream */,
|
|
void *userData,
|
|
aaudio_result_t error) {
|
|
AudioEngine *engine = (AudioEngine *)userData;
|
|
engine->mError = error;
|
|
}
|
|
|
|
AAudioStream* mStream = nullptr;
|
|
|
|
std::atomic<aaudio_result_t> mError{AAUDIO_OK}; // written by error callback
|
|
std::atomic<bool> mCallbackStarted{false}; // written by data callback
|
|
std::atomic<bool> mCallbackFinished{false}; // written by data callback
|
|
std::atomic<aaudio_data_callback_result_t> mCallbackResult{AAUDIO_CALLBACK_RESULT_CONTINUE};
|
|
std::atomic<aaudio_result_t> mStopResult{AAUDIO_OK};
|
|
};
|
|
|
|
/*********************************************************************/
|
|
// Tell the callback to return AAUDIO_CALLBACK_RESULT_CONTINUE.
|
|
|
|
TEST(test_close_timing, aaudio_close_joins_input_none) {
|
|
AudioEngine engine;
|
|
engine.checkCloseJoins(AAUDIO_DIRECTION_INPUT,
|
|
AAUDIO_PERFORMANCE_MODE_NONE,
|
|
AAUDIO_CALLBACK_RESULT_CONTINUE);
|
|
}
|
|
|
|
TEST(test_close_timing, aaudio_close_joins_output_none) {
|
|
AudioEngine engine;
|
|
engine.checkCloseJoins(AAUDIO_DIRECTION_OUTPUT,
|
|
AAUDIO_PERFORMANCE_MODE_NONE,
|
|
AAUDIO_CALLBACK_RESULT_CONTINUE);
|
|
}
|
|
|
|
TEST(test_close_timing, aaudio_close_joins_input_lowlat) {
|
|
AudioEngine engine;
|
|
engine.checkCloseJoins(AAUDIO_DIRECTION_INPUT,
|
|
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
|
|
AAUDIO_CALLBACK_RESULT_CONTINUE);
|
|
}
|
|
|
|
TEST(test_close_timing, aaudio_close_joins_output_lowlat) {
|
|
AudioEngine engine;
|
|
engine.checkCloseJoins(AAUDIO_DIRECTION_OUTPUT,
|
|
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
|
|
AAUDIO_CALLBACK_RESULT_CONTINUE);
|
|
}
|
|
|
|
/*********************************************************************/
|
|
// Tell the callback to return AAUDIO_CALLBACK_RESULT_STOP.
|
|
|
|
TEST(test_close_timing, aaudio_close_joins_input_lowlat_stop) {
|
|
AudioEngine engine;
|
|
engine.checkCloseJoins(AAUDIO_DIRECTION_INPUT,
|
|
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
|
|
AAUDIO_CALLBACK_RESULT_STOP);
|
|
}
|
|
|
|
TEST(test_close_timing, aaudio_close_joins_output_lowlat_stop) {
|
|
AudioEngine engine;
|
|
engine.checkCloseJoins(AAUDIO_DIRECTION_OUTPUT,
|
|
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
|
|
AAUDIO_CALLBACK_RESULT_STOP);
|
|
}
|
|
|
|
TEST(test_close_timing, aaudio_close_joins_output_none_stop) {
|
|
AudioEngine engine;
|
|
engine.checkCloseJoins(AAUDIO_DIRECTION_OUTPUT,
|
|
AAUDIO_PERFORMANCE_MODE_NONE,
|
|
AAUDIO_CALLBACK_RESULT_STOP);
|
|
}
|
|
|
|
TEST(test_close_timing, aaudio_close_joins_input_none_stop) {
|
|
AudioEngine engine;
|
|
engine.checkCloseJoins(AAUDIO_DIRECTION_INPUT,
|
|
AAUDIO_PERFORMANCE_MODE_NONE,
|
|
AAUDIO_CALLBACK_RESULT_STOP);
|
|
}
|