Loading media/libaaudio/include/aaudio/AAudioTesting.h +0 −6 Original line number Diff line number Diff line Loading @@ -49,12 +49,6 @@ enum { }; typedef int32_t aaudio_policy_t; // Internal error codes. Only used by the framework. enum { AAUDIO_INTERNAL_ERROR_BASE = -1000, AAUDIO_ERROR_STANDBY, }; /** * Control whether AAudioStreamBuilder_openStream() will use the new MMAP data path * or the older "Legacy" data path. Loading media/libaaudio/src/core/AudioGlobal.h +8 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,14 @@ namespace aaudio { // Internal error codes. Only used by the framework. enum { AAUDIO_INTERNAL_ERROR_BASE = -1000, AAUDIO_ERROR_STANDBY, AAUDIO_ERROR_ALREADY_CLOSED, }; aaudio_policy_t AudioGlobal_getMMapPolicy(); aaudio_result_t AudioGlobal_setMMapPolicy(aaudio_policy_t policy); Loading media/libaaudio/tests/Android.bp +27 −0 Original line number Diff line number Diff line Loading @@ -248,3 +248,30 @@ cc_binary { srcs: ["test_idle_disconnected_shared_stream.cpp"], shared_libs: ["libaaudio"], } cc_test { name: "test_multiple_close_simultaneously", defaults: [ "latest_android_media_audio_common_types_cpp_shared", "libaaudio_tests_defaults", ], srcs: ["test_multiple_close_simultaneously.cpp"], shared_libs: [ "aaudio-aidl-cpp", "framework-permission-aidl-cpp", "libaaudio", "libbinder", "liblog", "libutils", ], // This test will run 1 minute to ensure there is no crash happen. // In that case, set the timeout as 2 minutes to allow the test to complete. test_options: { test_runner_options: [ { name: "native-test-timeout", value: "2m", } ], }, } media/libaaudio/tests/test_multiple_close_simultaneously.cpp 0 → 100644 +153 −0 Original line number Diff line number Diff line /* * Copyright (C) 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. */ #define LOG_TAG "test_multiple_close_simultaneously" #include <chrono> #include <condition_variable> #include <shared_mutex> #include <string> #include <thread> #include <gtest/gtest.h> #include <binder/IBinder.h> #include <binder/IServiceManager.h> #include <utils/Log.h> #include <aaudio/AAudio.h> #include <aaudio/IAAudioService.h> #include <aaudio/StreamRequest.h> #include <aaudio/StreamParameters.h> using namespace android; using namespace aaudio; #define AAUDIO_SERVICE_NAME "media.aaudio" static constexpr int THREAD_NUM = 2; static constexpr auto TEST_DURATION = std::chrono::minutes(1); static std::string sError; static bool sTestPassed = true; struct Signal { std::atomic_int value{0}; std::shared_mutex lock; std::condition_variable_any cv; }; class AAudioServiceDeathRecipient : public IBinder::DeathRecipient { public: void binderDied(const wp<IBinder>& who __unused) override { sError = "AAudioService is dead"; ALOGE("%s", sError.c_str()); sTestPassed = false; } }; sp<IAAudioService> getAAudioService(const sp<IBinder::DeathRecipient>& recipient) { auto sm = defaultServiceManager(); if (sm == nullptr) { sError = "Cannot get service manager"; ALOGE("%s", sError.c_str()); return nullptr; } sp<IBinder> binder = sm->waitForService(String16(AAUDIO_SERVICE_NAME)); if (binder == nullptr) { sError = "Cannot get aaudio service"; ALOGE("%s", sError.c_str()); return nullptr; } if (binder->linkToDeath(recipient) != NO_ERROR) { sError = "Cannot link to binder death"; ALOGE("%s", sError.c_str()); return nullptr; } return interface_cast<IAAudioService>(binder); } void openAndMultipleClose(const sp<IAAudioService>& aaudioService) { auto start = std::chrono::system_clock::now(); bool hasFailedOpening = false; while (sTestPassed && std::chrono::system_clock::now() - start < TEST_DURATION) { StreamRequest inRequest; StreamParameters outParams; int32_t handle = 0; inRequest.attributionSource.uid = getuid(); inRequest.attributionSource.pid = getpid(); inRequest.attributionSource.token = sp<BBinder>::make(); auto status = aaudioService->openStream(inRequest, &outParams, &handle); if (!status.isOk()) { sError = "Cannot open stream, it can be caused by service death"; ALOGE("%s", sError.c_str()); sTestPassed = false; break; } if (handle <= 0) { sError = "Cannot get stream handle after open, returned handle" + std::to_string(handle); ALOGE("%s", sError.c_str()); sTestPassed = false; break; } hasFailedOpening = false; Signal isReady; Signal startWork; Signal isCompleted; std::unique_lock readyLock(isReady.lock); std::unique_lock completedLock(isCompleted.lock); for (int i = 0; i < THREAD_NUM; ++i) { std::thread closeStream([aaudioService, handle, &isReady, &startWork, &isCompleted] { isReady.value++; isReady.cv.notify_one(); { std::shared_lock<std::shared_mutex> _l(startWork.lock); startWork.cv.wait(_l, [&startWork] { return startWork.value.load() == 1; }); } int32_t result; aaudioService->closeStream(handle, &result); isCompleted.value++; isCompleted.cv.notify_one(); }); closeStream.detach(); } isReady.cv.wait(readyLock, [&isReady] { return isReady.value == THREAD_NUM; }); { std::unique_lock startWorkLock(startWork.lock); startWork.value.store(1); } startWork.cv.notify_all(); isCompleted.cv.wait_for(completedLock, std::chrono::milliseconds(1000), [&isCompleted] { return isCompleted.value == THREAD_NUM; }); if (isCompleted.value != THREAD_NUM) { sError = "Close is not completed within 1 second"; ALOGE("%s", sError.c_str()); sTestPassed = false; break; } } } TEST(test_multiple_close_simultaneously, open_multiple_close) { const auto recipient = sp<AAudioServiceDeathRecipient>::make(); auto aaudioService = getAAudioService(recipient); ASSERT_NE(nullptr, aaudioService) << sError; openAndMultipleClose(aaudioService); ASSERT_TRUE(sTestPassed) << sError; } services/oboeservice/AAudioServiceStreamBase.cpp +18 −13 Original line number Diff line number Diff line Loading @@ -75,11 +75,7 @@ AAudioServiceStreamBase::~AAudioServiceStreamBase() { this, getState()); // Stop the command thread before destroying. if (mThreadEnabled) { mThreadEnabled = false; mCommandQueue.stopWaiting(); mCommandThread.stop(); } stopCommandThread(); } std::string AAudioServiceStreamBase::dumpHeader() { Loading Loading @@ -194,26 +190,27 @@ aaudio_result_t AAudioServiceStreamBase::open(const aaudio::AAudioStreamRequest error: closeAndClear(); mThreadEnabled = false; mCommandQueue.stopWaiting(); mCommandThread.stop(); stopCommandThread(); return result; } aaudio_result_t AAudioServiceStreamBase::close() { aaudio_result_t result = sendCommand(CLOSE, nullptr, true /*waitForReply*/, TIMEOUT_NANOS); if (result == AAUDIO_ERROR_ALREADY_CLOSED) { // AAUDIO_ERROR_ALREADY_CLOSED is not a really error but just indicate the stream has // already been closed. In that case, there is no need to close the stream once more. ALOGD("The stream(%d) is already closed", mHandle); return AAUDIO_OK; } // Stop the command thread as the stream is closed. mThreadEnabled = false; mCommandQueue.stopWaiting(); mCommandThread.stop(); stopCommandThread(); return result; } aaudio_result_t AAudioServiceStreamBase::close_l() { if (getState() == AAUDIO_STREAM_STATE_CLOSED) { return AAUDIO_OK; return AAUDIO_ERROR_ALREADY_CLOSED; } // This will stop the stream, just in case it was not already stopped. Loading Loading @@ -766,3 +763,11 @@ aaudio_result_t AAudioServiceStreamBase::closeAndClear() { .record(); return result; } void AAudioServiceStreamBase::stopCommandThread() { bool threadEnabled = true; if (mThreadEnabled.compare_exchange_strong(threadEnabled, false)) { mCommandQueue.stopWaiting(); mCommandThread.stop(); } } Loading
media/libaaudio/include/aaudio/AAudioTesting.h +0 −6 Original line number Diff line number Diff line Loading @@ -49,12 +49,6 @@ enum { }; typedef int32_t aaudio_policy_t; // Internal error codes. Only used by the framework. enum { AAUDIO_INTERNAL_ERROR_BASE = -1000, AAUDIO_ERROR_STANDBY, }; /** * Control whether AAudioStreamBuilder_openStream() will use the new MMAP data path * or the older "Legacy" data path. Loading
media/libaaudio/src/core/AudioGlobal.h +8 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,14 @@ namespace aaudio { // Internal error codes. Only used by the framework. enum { AAUDIO_INTERNAL_ERROR_BASE = -1000, AAUDIO_ERROR_STANDBY, AAUDIO_ERROR_ALREADY_CLOSED, }; aaudio_policy_t AudioGlobal_getMMapPolicy(); aaudio_result_t AudioGlobal_setMMapPolicy(aaudio_policy_t policy); Loading
media/libaaudio/tests/Android.bp +27 −0 Original line number Diff line number Diff line Loading @@ -248,3 +248,30 @@ cc_binary { srcs: ["test_idle_disconnected_shared_stream.cpp"], shared_libs: ["libaaudio"], } cc_test { name: "test_multiple_close_simultaneously", defaults: [ "latest_android_media_audio_common_types_cpp_shared", "libaaudio_tests_defaults", ], srcs: ["test_multiple_close_simultaneously.cpp"], shared_libs: [ "aaudio-aidl-cpp", "framework-permission-aidl-cpp", "libaaudio", "libbinder", "liblog", "libutils", ], // This test will run 1 minute to ensure there is no crash happen. // In that case, set the timeout as 2 minutes to allow the test to complete. test_options: { test_runner_options: [ { name: "native-test-timeout", value: "2m", } ], }, }
media/libaaudio/tests/test_multiple_close_simultaneously.cpp 0 → 100644 +153 −0 Original line number Diff line number Diff line /* * Copyright (C) 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. */ #define LOG_TAG "test_multiple_close_simultaneously" #include <chrono> #include <condition_variable> #include <shared_mutex> #include <string> #include <thread> #include <gtest/gtest.h> #include <binder/IBinder.h> #include <binder/IServiceManager.h> #include <utils/Log.h> #include <aaudio/AAudio.h> #include <aaudio/IAAudioService.h> #include <aaudio/StreamRequest.h> #include <aaudio/StreamParameters.h> using namespace android; using namespace aaudio; #define AAUDIO_SERVICE_NAME "media.aaudio" static constexpr int THREAD_NUM = 2; static constexpr auto TEST_DURATION = std::chrono::minutes(1); static std::string sError; static bool sTestPassed = true; struct Signal { std::atomic_int value{0}; std::shared_mutex lock; std::condition_variable_any cv; }; class AAudioServiceDeathRecipient : public IBinder::DeathRecipient { public: void binderDied(const wp<IBinder>& who __unused) override { sError = "AAudioService is dead"; ALOGE("%s", sError.c_str()); sTestPassed = false; } }; sp<IAAudioService> getAAudioService(const sp<IBinder::DeathRecipient>& recipient) { auto sm = defaultServiceManager(); if (sm == nullptr) { sError = "Cannot get service manager"; ALOGE("%s", sError.c_str()); return nullptr; } sp<IBinder> binder = sm->waitForService(String16(AAUDIO_SERVICE_NAME)); if (binder == nullptr) { sError = "Cannot get aaudio service"; ALOGE("%s", sError.c_str()); return nullptr; } if (binder->linkToDeath(recipient) != NO_ERROR) { sError = "Cannot link to binder death"; ALOGE("%s", sError.c_str()); return nullptr; } return interface_cast<IAAudioService>(binder); } void openAndMultipleClose(const sp<IAAudioService>& aaudioService) { auto start = std::chrono::system_clock::now(); bool hasFailedOpening = false; while (sTestPassed && std::chrono::system_clock::now() - start < TEST_DURATION) { StreamRequest inRequest; StreamParameters outParams; int32_t handle = 0; inRequest.attributionSource.uid = getuid(); inRequest.attributionSource.pid = getpid(); inRequest.attributionSource.token = sp<BBinder>::make(); auto status = aaudioService->openStream(inRequest, &outParams, &handle); if (!status.isOk()) { sError = "Cannot open stream, it can be caused by service death"; ALOGE("%s", sError.c_str()); sTestPassed = false; break; } if (handle <= 0) { sError = "Cannot get stream handle after open, returned handle" + std::to_string(handle); ALOGE("%s", sError.c_str()); sTestPassed = false; break; } hasFailedOpening = false; Signal isReady; Signal startWork; Signal isCompleted; std::unique_lock readyLock(isReady.lock); std::unique_lock completedLock(isCompleted.lock); for (int i = 0; i < THREAD_NUM; ++i) { std::thread closeStream([aaudioService, handle, &isReady, &startWork, &isCompleted] { isReady.value++; isReady.cv.notify_one(); { std::shared_lock<std::shared_mutex> _l(startWork.lock); startWork.cv.wait(_l, [&startWork] { return startWork.value.load() == 1; }); } int32_t result; aaudioService->closeStream(handle, &result); isCompleted.value++; isCompleted.cv.notify_one(); }); closeStream.detach(); } isReady.cv.wait(readyLock, [&isReady] { return isReady.value == THREAD_NUM; }); { std::unique_lock startWorkLock(startWork.lock); startWork.value.store(1); } startWork.cv.notify_all(); isCompleted.cv.wait_for(completedLock, std::chrono::milliseconds(1000), [&isCompleted] { return isCompleted.value == THREAD_NUM; }); if (isCompleted.value != THREAD_NUM) { sError = "Close is not completed within 1 second"; ALOGE("%s", sError.c_str()); sTestPassed = false; break; } } } TEST(test_multiple_close_simultaneously, open_multiple_close) { const auto recipient = sp<AAudioServiceDeathRecipient>::make(); auto aaudioService = getAAudioService(recipient); ASSERT_NE(nullptr, aaudioService) << sError; openAndMultipleClose(aaudioService); ASSERT_TRUE(sTestPassed) << sError; }
services/oboeservice/AAudioServiceStreamBase.cpp +18 −13 Original line number Diff line number Diff line Loading @@ -75,11 +75,7 @@ AAudioServiceStreamBase::~AAudioServiceStreamBase() { this, getState()); // Stop the command thread before destroying. if (mThreadEnabled) { mThreadEnabled = false; mCommandQueue.stopWaiting(); mCommandThread.stop(); } stopCommandThread(); } std::string AAudioServiceStreamBase::dumpHeader() { Loading Loading @@ -194,26 +190,27 @@ aaudio_result_t AAudioServiceStreamBase::open(const aaudio::AAudioStreamRequest error: closeAndClear(); mThreadEnabled = false; mCommandQueue.stopWaiting(); mCommandThread.stop(); stopCommandThread(); return result; } aaudio_result_t AAudioServiceStreamBase::close() { aaudio_result_t result = sendCommand(CLOSE, nullptr, true /*waitForReply*/, TIMEOUT_NANOS); if (result == AAUDIO_ERROR_ALREADY_CLOSED) { // AAUDIO_ERROR_ALREADY_CLOSED is not a really error but just indicate the stream has // already been closed. In that case, there is no need to close the stream once more. ALOGD("The stream(%d) is already closed", mHandle); return AAUDIO_OK; } // Stop the command thread as the stream is closed. mThreadEnabled = false; mCommandQueue.stopWaiting(); mCommandThread.stop(); stopCommandThread(); return result; } aaudio_result_t AAudioServiceStreamBase::close_l() { if (getState() == AAUDIO_STREAM_STATE_CLOSED) { return AAUDIO_OK; return AAUDIO_ERROR_ALREADY_CLOSED; } // This will stop the stream, just in case it was not already stopped. Loading Loading @@ -766,3 +763,11 @@ aaudio_result_t AAudioServiceStreamBase::closeAndClear() { .record(); return result; } void AAudioServiceStreamBase::stopCommandThread() { bool threadEnabled = true; if (mThreadEnabled.compare_exchange_strong(threadEnabled, false)) { mCommandQueue.stopWaiting(); mCommandThread.stop(); } }