/* * Copyright 2024 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. */ #include #include #include #include #include #include // Test returning DataCallbackResult::Stop from a callback. using namespace oboe; // Test whether there is a deadlock when stopping streams. // See Issue #2059 class TestReturnStopDeadlock : public ::testing::Test { public: void start(bool useOpenSL); void stop(); int32_t getCycleCount() { return mCycleCount.load(); } protected: void TearDown() override; private: void cycleRapidly(bool useOpenSL); class MyDataCallback : public oboe::AudioStreamDataCallback { public: MyDataCallback() {} oboe::DataCallbackResult onAudioReady( oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) override; std::atomic returnStop = false; std::atomic callbackCount{0}; }; std::shared_ptr mStream; std::shared_ptr mDataCallback; std::atomic mCycleCount{0}; std::atomic mThreadEnabled{false}; std::thread mCycleThread; static constexpr int kChannelCount = 1; static constexpr int kMaxSleepMicros = 25000; }; // start a thread to cycle through stream tests void TestReturnStopDeadlock::start(bool useOpenSL) { mThreadEnabled = true; mCycleCount = 0; mCycleThread = std::thread([this, useOpenSL]() { cycleRapidly(useOpenSL); }); } void TestReturnStopDeadlock::stop() { mThreadEnabled = false; // Terminate the thread with a timeout. const int timeout = 1; auto future = std::async(std::launch::async, &std::thread::join, &mCycleThread); ASSERT_NE(future.wait_for(std::chrono::seconds(timeout)), std::future_status::timeout) << " join() timed out! cycles = " << getCycleCount(); } void TestReturnStopDeadlock::TearDown() { if (mStream) { mStream->close(); } } void TestReturnStopDeadlock::cycleRapidly(bool useOpenSL) { while(mThreadEnabled) { mCycleCount++; mDataCallback = std::make_shared(); AudioStreamBuilder builder; oboe::Result result = builder.setFormat(oboe::AudioFormat::Float) ->setAudioApi(useOpenSL ? oboe::AudioApi::OpenSLES : oboe::AudioApi::AAudio) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->setChannelCount(kChannelCount) ->setDataCallback(mDataCallback) ->setUsage(oboe::Usage::Notification) ->openStream(mStream); ASSERT_EQ(result, oboe::Result::OK); mStream->setDelayBeforeCloseMillis(0); result = mStream->requestStart(); ASSERT_EQ(result, oboe::Result::OK); // Sleep for some random time. int countdown = 100; while ((mDataCallback->callbackCount < 4) && (--countdown > 0)) { int32_t durationMicros = (int32_t)(drand48() * kMaxSleepMicros); usleep(durationMicros); } mDataCallback->returnStop = true; result = mStream->close(); ASSERT_EQ(result, oboe::Result::OK); mStream = nullptr; ASSERT_GT(mDataCallback->callbackCount, 1) << " cycleCount = " << mCycleCount; } } // Callback that returns Continue or Stop DataCallbackResult TestReturnStopDeadlock::MyDataCallback::onAudioReady( AudioStream *audioStream, void *audioData, int32_t numFrames) { float *floatData = (float *) audioData; const int numSamples = numFrames * kChannelCount; callbackCount++; // Fill buffer with white noise. for (int i = 0; i < numSamples; i++) { floatData[i] = ((float) drand48() - 0.5f) * 2 * 0.1f; } usleep(500); // half a millisecond if (returnStop) { usleep(20 * 1000); return DataCallbackResult::Stop; } else { return DataCallbackResult::Continue; } } TEST_F(TestReturnStopDeadlock, RapidCycleAAudio){ start(false); usleep(3000 * 1000); stop(); } TEST_F(TestReturnStopDeadlock, RapidCycleOpenSL){ start(true); usleep(3000 * 1000); stop(); }