/* * 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 #include #include #include // 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 mError{AAUDIO_OK}; // written by error callback std::atomic mCallStopFromCallback{false}; std::atomic mCallbackStarted{false}; // written by error callback std::atomic mCallbackFinished{false}; // written by error callback std::atomic 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")); }