From ce66d2688e2c42b8a3c705f2be8c2de6291f971c Mon Sep 17 00:00:00 2001 From: authurtabo-byte <298079739+authurtabo-byte@users.noreply.github.com> Date: Fri, 9 Oct 2026 02:28:30 -0700 Subject: [PATCH] fix(libSceAudioOut): allow state queries during output waits --- core/libs/prx/libSceAudioOut/src/AudioOut.cpp | 50 +++++++++---------- .../tests/AudioOutLastOutputTime.cpp | 30 +++++++++++ 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index a6697a1a0..cf035c56f 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -95,6 +95,7 @@ struct Port { }; static std::mutex g_mutex; +static std::mutex g_outputMutex; static Port g_ports[PORTS_MAX]; static bool g_sdlInitialized = false; @@ -190,7 +191,16 @@ static const void* prepareBuffer(const Port& port, const void* data, std::vector return buf.data(); } -static void queueAudio(Port& port, const void* data) { +static void sleepUs(std::unique_lock& lock, std::uint64_t us) { + struct timespec req{}; + req.tv_sec = static_cast(us / 1000000ULL); + req.tv_nsec = static_cast((us % 1000000ULL) * 1000ULL); + lock.unlock(); + nanosleep(&req, nullptr); + lock.lock(); +} + +static void queueAudio(Port& port, const void* data, std::unique_lock& lock) { if (data == nullptr) { const std::uint64_t waitStart = sceKernelGetProcessTime(); while (SDL_GetQueuedAudioSize(port.device) > 0) { @@ -198,10 +208,7 @@ static void queueAudio(Port& port, const void* data) { SDL_ClearQueuedAudio(port.device); break; } - struct timespec req{}; - req.tv_sec = 0; - req.tv_nsec = static_cast(DRAIN_SLEEP_US * 1000ULL); - nanosleep(&req, nullptr); + sleepUs(lock, DRAIN_SLEEP_US); } return; } @@ -251,10 +258,7 @@ static void queueAudio(Port& port, const void* data) { SDL_ClearQueuedAudio(port.device); break; } - struct timespec req{}; - req.tv_sec = 0; - req.tv_nsec = static_cast(DRAIN_SLEEP_US * 1000ULL); - nanosleep(&req, nullptr); + sleepUs(lock, DRAIN_SLEEP_US); } if (SDL_QueueAudio(port.device, queueData, queueSize) < 0) { @@ -263,22 +267,15 @@ static void queueAudio(Port& port, const void* data) { // APS5_LOG_OUT("device=%u type=%d bytes=%u queued=%u", port.device, port.type, queueSize, SDL_GetQueuedAudioSize(port.device)); } -static void sleepUs(std::uint64_t us) { - struct timespec req{}; - req.tv_sec = static_cast(us / 1000000ULL); - req.tv_nsec = static_cast((us % 1000000ULL) * 1000ULL); - nanosleep(&req, nullptr); -} - -static void paceVirtualPort(Port& port, bool hasData) { +static void paceVirtualPort(Port& port, bool hasData, std::unique_lock& lock) { const std::uint64_t now = sceKernelGetProcessTime(); const auto queued = static_cast(port.virtualQueueEnd - now); if (queued <= 0) port.virtualQueueEnd = now; if (!hasData) { - if (queued > 0) sleepUs(static_cast(queued)); + if (queued > 0) sleepUs(lock, static_cast(queued)); return; } - if (queued > static_cast(TARGET_LATENCY_US)) sleepUs(static_cast(queued) - TARGET_LATENCY_US); + if (queued > static_cast(TARGET_LATENCY_US)) sleepUs(lock, static_cast(queued) - TARGET_LATENCY_US); port.virtualQueueEnd += (1000000ULL * port.samplesNum) / port.freq; } @@ -361,6 +358,7 @@ int APS5_VABI sceAudioOutOpen(int userId, int type, int index, std::uint32_t len } int APS5_VABI sceAudioOutClose(int handle) { + std::lock_guard outputLock(g_outputMutex); std::lock_guard lock(g_mutex); Port* port = getPort(handle); if (port == nullptr) { @@ -372,16 +370,17 @@ int APS5_VABI sceAudioOutClose(int handle) { } int APS5_VABI sceAudioOutOutput(int handle, const void* ptr) { - std::lock_guard lock(g_mutex); + std::lock_guard outputLock(g_outputMutex); + std::unique_lock lock(g_mutex); Port* port = getPort(handle); if (port == nullptr) { return -2144993277; } if (port->device == 0) { - paceVirtualPort(*port, ptr != nullptr); + paceVirtualPort(*port, ptr != nullptr, lock); } else { - queueAudio(*port, ptr); + queueAudio(*port, ptr, lock); } if (ptr != nullptr) port->lastDataOutputTime = sceKernelGetProcessTime(); return static_cast(port->samplesNum); @@ -392,7 +391,8 @@ int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num) return -2144993276; } - std::lock_guard lock(g_mutex); + std::lock_guard outputLock(g_outputMutex); + std::unique_lock lock(g_mutex); for (std::uint32_t i = 0; i < num; i++) { if (getPort(param[i].handle) == nullptr) { @@ -404,9 +404,9 @@ int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num) for (std::uint32_t i = 0; i < num; i++) { Port& port = *getPort(param[i].handle); if (port.device == 0) { - paceVirtualPort(port, param[i].ptr != nullptr); + paceVirtualPort(port, param[i].ptr != nullptr, lock); } else { - queueAudio(port, param[i].ptr); + queueAudio(port, param[i].ptr, lock); } } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp index c9463eccc..c06cc14d1 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp @@ -1,6 +1,7 @@ #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -13,6 +14,7 @@ int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std:: int APS5_VABI sceAudioOutClose(int); int APS5_VABI sceAudioOutOutput(int, const void*); int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t); +int APS5_VABI sceAudioOutGetPortState(int, AudioOutPortState*); int APS5_VABI sceAudioOutGetLastOutputTime(int, std::uint64_t*); std::uint64_t APS5_VABI sceKernelGetProcessTime(); } @@ -154,6 +156,30 @@ void TestErrors() { Require(destination == untouched, "a rejected call must not write the destination"); } +void TestQueriesDuringOutput(bool batch, bool drain) { + const std::vector block(frequency, 256); + const int handle = sceAudioOutOpen(user, portTypeVibration, 0, frequency, frequency, formatS16Mono); + Require(handle > 0, "port must open"); + Require(sceAudioOutOutput(handle, block.data()) == static_cast(frequency), "output must accept the block"); + const auto initialTime = LastOutputTime(handle); + std::atomic started = false; + std::thread output([&] { + AudioOutOutputParam param{handle, drain ? nullptr : block.data()}; + started = true; + const int result = batch ? sceAudioOutOutputs(¶m, 1) : sceAudioOutOutput(handle, param.ptr); + Require(result == static_cast(frequency), "pending output must complete before close"); + }); + while (!started) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + const auto before = std::chrono::steady_clock::now(); + AudioOutPortState state{}; + Require(sceAudioOutGetPortState(handle, &state) == 0, "pending output must allow port state queries"); + Require(LastOutputTime(handle) == initialTime, "pending output must not change its timestamp yet"); + Require(std::chrono::steady_clock::now() - before < std::chrono::milliseconds(300), "audio queries must not wait for output pacing or draining"); + Require(sceAudioOutClose(handle) == 0, "close must safely wait for pending output"); + output.join(); +} + } int main() { @@ -170,5 +196,9 @@ int main() { TestReopenedPort(); TestErrors(); TestPortWithoutDeviceKeepsRealTime(); + TestQueriesDuringOutput(false, false); + TestQueriesDuringOutput(false, true); + TestQueriesDuringOutput(true, false); + TestQueriesDuringOutput(true, true); return 0; }