blob: 6dbe1656c4d79fdfc9fd758902f2467410fa51d2 [file] [log] [blame]
/*
* Copyright (C) 2021 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 an error 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 <aaudio/AAudio.h>
// Sleep long enough that the foreground has a chance to call close.
static constexpr int kCallbackSleepMillis = 1000;
static constexpr int kPollSleepMillis = 100;
static int sErrorCount = 0;
#define MY_ASSERT_TRUE(statement) \
if (!(statement)) { \
printf("ERROR line:%d - " #statement "\n", __LINE__); \
sErrorCount++; \
return false; \
}
#define MY_ASSERT_EQ(aa,bb) MY_ASSERT_TRUE(((aa) == (bb)))
#define MY_ASSERT_NE(aa,bb) MY_ASSERT_TRUE(((aa) != (bb)))
class AudioEngine {
public:
// Check for a crash or late callback if we close without stopping.
bool checkCloseJoins(aaudio_direction_t direction,
aaudio_performance_mode_t perfMode,
bool callStopFromCallback) {
mCallStopFromCallback = callStopFromCallback;
if (!startStreamForStall(direction, perfMode)) return false;
printf("--------------------------------------------------------\n");
printf("%s() - direction = %d, perfMode = %d, callStop = %d\n",
__func__, direction, perfMode, callStopFromCallback);
// When the callback starts it will go to sleep.
if (!waitForCallbackToStart()) return false;
printf("call AAudioStream_close()\n");
MY_ASSERT_TRUE(!mCallbackFinished); // Still sleeping?
aaudio_result_t result = AAudioStream_close(mStream); // May hang here!
if (mCallbackStarted) {
MY_ASSERT_TRUE(mCallbackFinished);
}
MY_ASSERT_EQ(AAUDIO_OK, result);
printf("AAudioStream_close() returned %d\n", result);
MY_ASSERT_EQ(AAUDIO_ERROR_DISCONNECTED, mError.load());
if (mCallStopFromCallback) {
// Did calling stop() from callback fail? It should have.
MY_ASSERT_NE(AAUDIO_OK, mStopResult.load());
}
return true;
}
private:
bool 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);
MY_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);
MY_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);
MY_ASSERT_EQ(AAUDIO_OK, result);
return true;
}
bool waitForCallbackToStart() {
// Wait for callback to say it has been called.
int countDown = 10 * 1000 / kPollSleepMillis;
while (!mCallbackStarted && countDown > 0) {
if ((countDown % 5) == 0) {
printf("===== Please PLUG or UNPLUG headphones! ======= %d\n", countDown);
}
usleep(kPollSleepMillis * 1000);
countDown--;
}
MY_ASSERT_TRUE(countDown > 0);
MY_ASSERT_TRUE(mCallbackStarted);
return true;
}
// 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 */
) {
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
static void s_myErrorCallbackProc(
AAudioStream * stream,
void *userData,
aaudio_result_t error) {
AudioEngine *engine = (AudioEngine *)userData;
engine->mError = error;
engine->mCallbackStarted = true;
usleep(kCallbackSleepMillis * 1000);
// it is illegal to call stop() from the callback. It should
// return an error and not hang.
if (engine->mCallStopFromCallback) {
engine->mStopResult = AAudioStream_requestStop(stream);
}
engine->mCallbackFinished = true;
}
AAudioStream* mStream = nullptr;
std::atomic<aaudio_result_t> mError{AAUDIO_OK}; // written by error callback
std::atomic<bool> mCallStopFromCallback{false};
std::atomic<bool> mCallbackStarted{false}; // written by error callback
std::atomic<bool> mCallbackFinished{false}; // written by error callback
std::atomic<aaudio_result_t> mStopResult{AAUDIO_OK};
};
int main(int, char **) {
// Parameters to test.
static aaudio_direction_t directions[] = {AAUDIO_DIRECTION_OUTPUT,
AAUDIO_DIRECTION_INPUT};
static aaudio_performance_mode_t perfModes[] =
{AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_PERFORMANCE_MODE_NONE};
static bool callStops[] = { false, true };
// 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);
printf("Test Disconnect Race V1.0\n");
printf("\n");
for (auto callStop : callStops) {
for (auto direction : directions) {
for (auto perfMode : perfModes) {
AudioEngine engine;
engine.checkCloseJoins(direction, perfMode, callStop);
}
}
}
printf("Error Count = %d, %s\n", sErrorCount,
((sErrorCount == 0) ? "PASS" : "FAIL"));
}