From c35572ec18dd068286a574fd17bf3ceee49e2e67 Mon Sep 17 00:00:00 2001 From: PrintOrb Date: Mon, 12 Oct 2026 00:17:17 -0500 Subject: [PATCH 1/2] fix(audioout): serialize shared SDL audio initialization --- core/libs/prx/libSceAudioOut/include/AudioOutSdl.hpp | 6 ++++++ core/libs/prx/libSceAudioOut/src/AudioOut.cpp | 9 ++++++++- core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp | 5 +++-- 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 core/libs/prx/libSceAudioOut/include/AudioOutSdl.hpp diff --git a/core/libs/prx/libSceAudioOut/include/AudioOutSdl.hpp b/core/libs/prx/libSceAudioOut/include/AudioOutSdl.hpp new file mode 100644 index 000000000..f0e8c87cc --- /dev/null +++ b/core/libs/prx/libSceAudioOut/include/AudioOutSdl.hpp @@ -0,0 +1,6 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAUDIOOUT_INCLUDE_AUDIOOUTSDL_HPP +#define CORE_LIBS_PRX_LIBSCEAUDIOOUT_INCLUDE_AUDIOOUTSDL_HPP + +int InitializeSdlAudio_nid_no_patch(); + +#endif diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index c8adb77b2..db1ecd88b 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -16,6 +16,7 @@ #include "SceTypes.hpp" #include "prx/libc/include/PreciseWait.hpp" #include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libSceAudioOut/include/AudioOutSdl.hpp" static constexpr int PORT_TYPE_MAIN = 0; static constexpr int PORT_TYPE_BGM = 1; @@ -103,11 +104,17 @@ static std::mutex g_outputMutex; static Port g_ports[PORTS_MAX]; static bool g_sdlInitialized = false; +int InitializeSdlAudio_nid_no_patch() { + static std::mutex initializationMutex; + std::lock_guard lock(initializationMutex); + return SDL_InitSubSystem(SDL_INIT_AUDIO); +} + static bool ensureSdlAudio() { if (g_sdlInitialized) { return true; } - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + if (InitializeSdlAudio_nid_no_patch() < 0) { return false; } g_sdlInitialized = true; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp index 383ad018f..3f8827744 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp @@ -14,6 +14,7 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/PreciseWait.hpp" #include "AudioOut2Internal.hpp" +#include "prx/libSceAudioOut/include/AudioOutSdl.hpp" // An AudioOut2 context is the hardware output queue: every push appends a grain (num_grains samples) // to a queue of queue_depth grains that plays in real time. Titles pace their mixer on that queue, so @@ -106,7 +107,7 @@ static std::uint32_t QueueLevel(AudioOut2Context& context, Clock::time_point now static void DeviceCallback(void* userdata, Uint8* stream, int length); static void OpenDevice(AudioOut2Context& context) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + if (InitializeSdlAudio_nid_no_patch() < 0) { AUDIOOUT2_TRACE("SDL audio init failed: %s\n", SDL_GetError()); return; } @@ -134,7 +135,7 @@ static void CloseDevice(SDL_AudioDeviceID device) { } static void OpenPadDevice(AudioOut2Context& context) { - if (SDL_WasInit(SDL_INIT_AUDIO) == 0 && SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) return; + if (SDL_WasInit(SDL_INIT_AUDIO) == 0 && InitializeSdlAudio_nid_no_patch() < 0) return; const int count = SDL_GetNumAudioDevices(0); for (int index = 0; index < count; index++) { const char* listed = SDL_GetAudioDeviceName(index, 0); From 5031432db0d39c8c95504218673166cf6a69ef1a Mon Sep 17 00:00:00 2001 From: PrintOrb Date: Mon, 12 Oct 2026 16:04:06 -0500 Subject: [PATCH 2/2] test(audioout): detect overlapping backend initialization --- core/libs/prx/libSceAudioOut/CMakeLists.txt | 12 ++++ core/libs/prx/libSceAudioOut/src/AudioOut.cpp | 6 -- .../prx/libSceAudioOut/src/AudioOutSdl.cpp | 10 +++ .../tests/AudioOutInitialization.cpp | 65 +++++++++++++++++++ 4 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libSceAudioOut/src/AudioOutSdl.cpp create mode 100644 core/libs/prx/libSceAudioOut/tests/AudioOutInitialization.cpp diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index bc11e8e5b..3ef08cfa1 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(libSceAudioOut SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOutSdl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut2Context.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut2PadMix.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut2Port.cpp @@ -17,6 +18,17 @@ target_link_libraries(libSceAudioOut PRIVATE libc libkernel SDL2-static) configure_windows_unwind(libSceAudioOut) if(BUILD_TESTING) + add_test_executable(audio_out_initialization_tests tests/AudioOutInitialization.cpp src/AudioOutSdl.cpp) + target_include_directories(audio_out_initialization_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) + target_link_libraries(audio_out_initialization_tests PRIVATE libc) + configure_windows_unwind(audio_out_initialization_tests) + add_test(NAME audio_out_initialization COMMAND audio_out_initialization_tests) + set_tests_properties(audio_out_initialization PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(audio_out_initialization PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(audio_out2_pad_mix_tests tests/AudioOut2PadMix.cpp src/AudioOut2PadMix.cpp) target_include_directories(audio_out2_pad_mix_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(audio_out2_pad_mix_tests PRIVATE libc) diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index db1ecd88b..dee5a712e 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -104,12 +104,6 @@ static std::mutex g_outputMutex; static Port g_ports[PORTS_MAX]; static bool g_sdlInitialized = false; -int InitializeSdlAudio_nid_no_patch() { - static std::mutex initializationMutex; - std::lock_guard lock(initializationMutex); - return SDL_InitSubSystem(SDL_INIT_AUDIO); -} - static bool ensureSdlAudio() { if (g_sdlInitialized) { return true; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOutSdl.cpp b/core/libs/prx/libSceAudioOut/src/AudioOutSdl.cpp new file mode 100644 index 000000000..45f41f5a4 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/src/AudioOutSdl.cpp @@ -0,0 +1,10 @@ +#include "prx/libSceAudioOut/include/AudioOutSdl.hpp" + +#include "SDL.h" +#include + +int InitializeSdlAudio_nid_no_patch() { + static std::mutex initializationMutex; + std::lock_guard lock(initializationMutex); + return SDL_InitSubSystem(SDL_INIT_AUDIO); +} diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutInitialization.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutInitialization.cpp new file mode 100644 index 000000000..8ae9cd327 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutInitialization.cpp @@ -0,0 +1,65 @@ +#define SDL_MAIN_HANDLED +#include "prx/libSceAudioOut/include/AudioOutSdl.hpp" +#include "SDL.h" + +#include +#include +#include +#include +#include +#include + +namespace { +std::mutex stateMutex; +std::condition_variable stateChanged; +int calls = 0; +int active = 0; +int peak = 0; +bool secondEntered = false; +bool secondReady = false; +bool validFlags = true; +} + +extern "C" int SDLCALL SDL_InitSubSystem(Uint32 flags) { + std::unique_lock lock(stateMutex); + validFlags = validFlags && flags == SDL_INIT_AUDIO; + const int call = ++calls; + ++active; + if (active > peak) peak = active; + stateChanged.notify_all(); + if (call == 1) { + stateChanged.wait(lock, [] { return secondReady; }); + stateChanged.wait_for(lock, std::chrono::milliseconds(500), [] { return secondEntered; }); + } else { + secondEntered = true; + stateChanged.notify_all(); + } + --active; + return call == 1 ? -1 : 0; +} + +int main() { + int firstResult = 99; + int secondResult = 99; + std::thread first([&] { firstResult = InitializeSdlAudio_nid_no_patch(); }); + { + std::unique_lock lock(stateMutex); + stateChanged.wait(lock, [] { return calls == 1; }); + } + std::thread second([&] { + { + std::lock_guard lock(stateMutex); + secondReady = true; + stateChanged.notify_all(); + } + secondResult = InitializeSdlAudio_nid_no_patch(); + }); + first.join(); + second.join(); + if (peak != 1 || calls != 2 || active != 0 || !validFlags || firstResult != -1 || secondResult != 0) { + std::fprintf(stderr, "audio initialization overlap or return-value mismatch: peak=%d calls=%d results=%d,%d\n", peak, calls, firstResult, secondResult); + return 1; + } + std::puts("Serialized audio initialization: PASS"); + return 0; +}