fix(libSceAudioOut): allow state queries during output waits

This commit is contained in:
authurtabo-byte 2026-10-09 02:28:30 -07:00
parent f8c2f07246
commit ce66d2688e
2 changed files with 55 additions and 25 deletions

View File

@ -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<std::mutex>& lock, std::uint64_t us) {
struct timespec req{};
req.tv_sec = static_cast<time_t>(us / 1000000ULL);
req.tv_nsec = static_cast<long>((us % 1000000ULL) * 1000ULL);
lock.unlock();
nanosleep(&req, nullptr);
lock.lock();
}
static void queueAudio(Port& port, const void* data, std::unique_lock<std::mutex>& 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<long>(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<long>(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<time_t>(us / 1000000ULL);
req.tv_nsec = static_cast<long>((us % 1000000ULL) * 1000ULL);
nanosleep(&req, nullptr);
}
static void paceVirtualPort(Port& port, bool hasData) {
static void paceVirtualPort(Port& port, bool hasData, std::unique_lock<std::mutex>& lock) {
const std::uint64_t now = sceKernelGetProcessTime();
const auto queued = static_cast<std::int64_t>(port.virtualQueueEnd - now);
if (queued <= 0) port.virtualQueueEnd = now;
if (!hasData) {
if (queued > 0) sleepUs(static_cast<std::uint64_t>(queued));
if (queued > 0) sleepUs(lock, static_cast<std::uint64_t>(queued));
return;
}
if (queued > static_cast<std::int64_t>(TARGET_LATENCY_US)) sleepUs(static_cast<std::uint64_t>(queued) - TARGET_LATENCY_US);
if (queued > static_cast<std::int64_t>(TARGET_LATENCY_US)) sleepUs(lock, static_cast<std::uint64_t>(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<std::mutex> outputLock(g_outputMutex);
std::lock_guard<std::mutex> 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<std::mutex> lock(g_mutex);
std::lock_guard<std::mutex> outputLock(g_outputMutex);
std::unique_lock<std::mutex> 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<int>(port->samplesNum);
@ -392,7 +391,8 @@ int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num)
return -2144993276;
}
std::lock_guard<std::mutex> lock(g_mutex);
std::lock_guard<std::mutex> outputLock(g_outputMutex);
std::unique_lock<std::mutex> 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);
}
}

View File

@ -1,6 +1,7 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
@ -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<std::int16_t> 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<int>(frequency), "output must accept the block");
const auto initialTime = LastOutputTime(handle);
std::atomic<bool> started = false;
std::thread output([&] {
AudioOutOutputParam param{handle, drain ? nullptr : block.data()};
started = true;
const int result = batch ? sceAudioOutOutputs(&param, 1) : sceAudioOutOutput(handle, param.ptr);
Require(result == static_cast<int>(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;
}