From b0b7198b7a0ca2673deab11c4a87f64fae871e8b Mon Sep 17 00:00:00 2001 From: Benjamin Cortez Date: Wed, 7 Oct 2026 21:35:14 -0300 Subject: [PATCH 1/2] feat(libSceAudioOut): implement sceAudioOutGetSystemState --- core/libs/SceTypes.hpp | 7 ++++ core/libs/prx/libSceAudioOut/CMakeLists.txt | 10 +++++ core/libs/prx/libSceAudioOut/src/AudioOut.cpp | 8 ++++ .../tests/AudioOutSystemState.cpp | 39 +++++++++++++++++++ docs/dev/debt/libSceAudioOut.md | 1 + 5 files changed, 65 insertions(+) create mode 100644 core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 4a61c38ed718..f51297c09b18 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -655,6 +655,13 @@ struct AudioOutPortState { std::uint64_t activeState; }; +struct AudioOutSystemState { + float loudness; + std::uint8_t reserved8[4]; + std::uint64_t reserved[3]; +}; +static_assert(sizeof(AudioOutSystemState) == 32); + struct Audio3dOpenParameters { std::uint64_t size_this; std::uint32_t granularity; diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index ba5e91090b76..7c97d07a559d 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -245,4 +245,14 @@ if(BUILD_TESTING) set_tests_properties(audio_out2_shutdown audio_out2_waiting_push_shutdown audio_out2_advance_shutdown audio_out_shutdown PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(audio_out_system_state_tests tests/AudioOutSystemState.cpp) + target_include_directories(audio_out_system_state_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out_system_state_tests PRIVATE libSceAudioOut libc) + configure_windows_unwind(audio_out_system_state_tests) + add_test(NAME audio_out_system_state COMMAND audio_out_system_state_tests) + if(WIN32) + set_tests_properties(audio_out_system_state PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index 9faeaef95709..8f1573f9581d 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -714,4 +714,12 @@ int APS5_VABI sceAudioOutExOpen(int userId, std::uint32_t mode) { return allocatePort(PORT_TYPE_EX, highRate ? 1024 : 256, highRate ? 192000 : 48000, Format::S16Stereo); } +int APS5_VABI sceAudioOutGetSystemState(AudioOutSystemState* state) { + if (state == nullptr) { + return -2144993276; + } + state->loudness = 0.0f; + return 0; +} + } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp new file mode 100644 index 000000000000..4cf928d77027 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp @@ -0,0 +1,39 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include + +extern "C" { +int APS5_VABI sceAudioOutGetSystemState(AudioOutSystemState*); +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +namespace { + +constexpr int invalidPointer = static_cast(0x80260004); + +void TestGetSystemState() { + AudioOutSystemState state{}; + state.loudness = -1.0f; + Require(sceAudioOutGetSystemState(&state) == 0, "sceAudioOutGetSystemState must succeed with valid pointer"); + Require(state.loudness == 0.0f, "loudness must be reported as 0.0f"); +} + +void TestInvalidPointer() { + Require(sceAudioOutGetSystemState(nullptr) == invalidPointer, "null pointer must return SCE_AUDIO_OUT_ERROR_INVALID_POINTER"); +} + +} + +int main() { + TestGetSystemState(); + TestInvalidPointer(); + return 0; +} diff --git a/docs/dev/debt/libSceAudioOut.md b/docs/dev/debt/libSceAudioOut.md index 37e4a53c5ac4..83e83145fabf 100644 --- a/docs/dev/debt/libSceAudioOut.md +++ b/docs/dev/debt/libSceAudioOut.md @@ -18,6 +18,7 @@ - [sceAudioOut2GetSpeakerArrayCoefficients](../../../core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp) (libSceAudioOut) - validates the handle and the coefficient count like the ambisonics call, then writes the front pair at full gain and silence for every other speaker, ignoring the position, spread, height awareness and downmix spread radius: [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp) and [prosper](https://github.com/mattias800/prosper/blob/main/prosper/src/hle/audio/hle_audio.cpp) both return that pair. The console's VBAP gains over the array's directions are not measured - [sceAudioOutSetMixLevelPadSpk](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function - [sceAudioOutGetLastOutputTime](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device +- [sceAudioOutGetSystemState](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - the 32-byte layout (`float loudness`, 4 reserved bytes, 3 reserved 64-bit words) follows shadPS4's `OrbisAudioOutSystemState` and SharpProspero's `SceAudioOutSystemState` (`Size = 32`); it is not confirmed on a console. Loudness is reported as a constant 0.0f, although SharpProspero documents it in LKFS, so the value is unconfirmed. shadPS4 returns NOT_INIT before `sceAudioOutInit`; that check is not modelled. Imported by PPSA30528, which fails to load without it - [sceAudioOutSysOpen, sceAudioOutSysConfigureOutput, sceAudioOutSysClose](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - imported by PPSA99051 and PPSA99997, which fail to load without them; the signatures follow [EVO-PLAYER-PS5](https://github.com/sainsaji/EVO-PLAYER-PS5/blob/da0c0451333bab82e79fffd2de4718e917f9dcb0/projects/evoplayer/core/src/audio/evo_pt.cpp#L24-L29), whose audio code both titles share, and are not confirmed on a console. `sceAudioOutSysOpen(user, mode)` opens a port only for mode 5, the TrueHD MAT route: [the same project's carrier table](https://github.com/sainsaji/EVO-PLAYER-PS5/blob/da0c0451333bab82e79fffd2de4718e917f9dcb0/projects/evoplayer/core/src/audio/iec61937.cpp#L125-L140), read from sceAudioOutExOpen's own tables in firmware 12.70, describes it as a 768 kHz port of 1024 S16 8-channel frames whose 16-byte frames are consumed at 192 kHz; every other mode throws. Audio reaches the host through SDL as PCM and the host has no HDMI bitstream output, so like an Ex port a Sys port has no host device: `sceAudioOutOutput` paces it at its own rate and drops the MAT bursts. `sceAudioOutSysConfigureOutput(type, flags, mode, target, option)` accepts only the observed `(1, 0, 5, 1, 0)` HDMI MAT switch and changes nothing; other arguments throw. `sceAudioOutSysClose` closes a Sys port and throws for any other handle. The restore path EVO-PLAYER-PS5 takes after a Sys port calls `sceAudioOutExConfigureOutput(0, 0, 0xFF, 0xFF, 0)`, so no Sys reset combination is known - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - MAIN (type 0) format validation follows PS5 firmware 13.60 measurements: bits 8-11 accept 1, 2, 8 or 12, bits 0-6 accept 0 or 1, bit 7 requires 8 channels, and bits 12-31 do not affect validation. The sample types are still read as float and int16; bit 7's layout, the PCM effect of bits 12-31 and the port `flags` are unverified. Other port types retain unverified format validation, including 6 channels and rejection of bits 12-31 - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - 12 channels are assumed to be L R C LFE Ls Rs Lb Rb Ltf Rtf Ltb Rtb as in KytyPS5's 12-to-8 fold, and 6 and 8 channels to be its first 6 and 8; no title or SDK header confirms the order From de28243d263e0d00a36d231b41c18177b6e240e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Fri, 23 Oct 2026 20:18:56 +0200 Subject: [PATCH 2/2] fix(libSceAudioOut): clear sceAudioOutGetSystemState reserved fields Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libSceAudioOut/src/AudioOut.cpp | 1 + core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index 8f1573f9581d..e973018f3540 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -718,6 +718,7 @@ int APS5_VABI sceAudioOutGetSystemState(AudioOutSystemState* state) { if (state == nullptr) { return -2144993276; } + *state = {}; state->loudness = 0.0f; return 0; } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp index 4cf928d77027..614a2ba2ebee 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutSystemState.cpp @@ -22,8 +22,11 @@ constexpr int invalidPointer = static_cast(0x80260004); void TestGetSystemState() { AudioOutSystemState state{}; state.loudness = -1.0f; + state.reserved8[0] = 0xAA; + state.reserved[2] = ~0ull; Require(sceAudioOutGetSystemState(&state) == 0, "sceAudioOutGetSystemState must succeed with valid pointer"); Require(state.loudness == 0.0f, "loudness must be reported as 0.0f"); + Require(state.reserved8[0] == 0 && state.reserved[2] == 0, "reserved fields must be cleared"); } void TestInvalidPointer() {