From 65a3869be28f646521a7a13f63d0950d9230116f Mon Sep 17 00:00:00 2001 From: capmare Date: Thu, 15 Oct 2026 15:41:02 +0200 Subject: [PATCH] feat(libSceAudioOut): implement sceAudioOut2MasteringGetState --- core/libs/SceTypes.hpp | 31 ++++++++++++++++ .../libSceAudioOut/src/AudioOut2Mastering.cpp | 37 +++++++++++++++++-- .../libSceAudioOut/tests/AudioOut2Latency.cpp | 33 +++++++++++++++++ docs/dev/debt/libSceAudioOut.md | 3 +- 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 1e46d81df251..4993ce42076f 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -552,6 +552,37 @@ struct AudioOut2MasteringStatesHeader { std::uint32_t states_id; }; +struct AudioOut2MasteringStatesDescriptor { + std::uint32_t id; + std::uint32_t size; +}; + +constexpr std::uint32_t AudioOut2MasteringMaxSystemChannels = 8; + +template +struct AudioOut2MasteringCompressorStates { + AudioOut2MasteringStatesDescriptor descriptor; + std::uint32_t reserved[2]; + float input_rms[Bands][AudioOut2MasteringMaxSystemChannels]; + float compression_coeff[Bands][AudioOut2MasteringMaxSystemChannels]; +}; + +struct AudioOut2MasteringLimiterStates { + AudioOut2MasteringStatesDescriptor descriptor; + std::uint32_t reserved[2]; + float input_peak[AudioOut2MasteringMaxSystemChannels]; + float output_peak[AudioOut2MasteringMaxSystemChannels]; + float gain_peak[AudioOut2MasteringMaxSystemChannels]; +}; + +template +struct AudioOut2MasteringStates { + AudioOut2MasteringStatesHeader states_header; + std::uint32_t reserved[3]; + AudioOut2MasteringCompressorStates compressor_states; + AudioOut2MasteringLimiterStates limiter_states; +}; + struct AudioOutOutputParam { int handle; const void* ptr; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp index 0d437bbb8184..f2372b37fcbf 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp @@ -2,6 +2,29 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int ErrorMasteringInvalidApiParam = static_cast(0x80268201); +constexpr int ErrorMasteringInvalidStatesId = static_cast(0x80268204); +constexpr std::uint32_t StatesIdDefault = 1; +constexpr std::uint32_t StatesIdV2 = 2; +constexpr std::uint32_t CompressorBandsDefault = 3; +constexpr std::uint32_t CompressorBandsV2 = 4; +constexpr std::uint32_t CompressorStructIdDefault = 0x01010001; +constexpr std::uint32_t CompressorStructIdV2 = 0x01020001; +constexpr std::uint32_t LimiterStructId = 0x01010003; + +template +void ClearStates(AudioOut2MasteringStatesHeader* header, std::uint32_t statesId, std::uint32_t compressorStructId) { + auto* states = reinterpret_cast*>(header); + *states = {}; + states->states_header.states_id = statesId; + states->compressor_states.descriptor = {compressorStructId, sizeof(states->compressor_states)}; + states->limiter_states.descriptor = {LimiterStructId, sizeof(states->limiter_states)}; +} + +} + extern "C" { int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags) { @@ -14,11 +37,19 @@ int APS5_VABI sceAudioOut2MasteringTerm(void) { } int APS5_VABI sceAudioOut2MasteringGetState(AudioOut2MasteringStatesHeader* state, uint32_t output, AudioOut2UserHandle user) { - (void)state; (void)output; (void)user; - NotImplemented_nid_no_patch(__func__); - return 0; + if (state == nullptr) return ErrorMasteringInvalidApiParam; + switch (state->states_id) { + case StatesIdDefault: + ClearStates(state, StatesIdDefault, CompressorStructIdDefault); + return 0; + case StatesIdV2: + ClearStates(state, StatesIdV2, CompressorStructIdV2); + return 0; + default: + return ErrorMasteringInvalidStatesId; + } } int APS5_VABI sceAudioOut2MasteringSetParam(const AudioOut2MasteringParamsHeader* param, uint32_t output, uint32_t flags) { diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp index b91b5d29dfdf..c9594c3e977c 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp @@ -1,8 +1,10 @@ +#include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include +#include #include extern "C" { @@ -11,6 +13,7 @@ int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t); int APS5_VABI sceAudioOut2MasteringInit(std::uint32_t); int APS5_VABI sceAudioOut2MasteringTerm(); int APS5_VABI sceAudioOut2MasteringSetParam(const void*, std::uint32_t, std::uint32_t); +int APS5_VABI sceAudioOut2MasteringGetState(void*, std::uint32_t, std::uint64_t); int APS5_VABI sceAudioOut2EnableChat(); int APS5_VABI sceAudioOut2DisableChat(); } @@ -62,6 +65,35 @@ void TestMasteringSetParam() { Require(ThrowsRuntimeError([] { sceAudioOut2MasteringSetParam(nullptr, 0, 0); }), "null mastering parameters must throw"); } +static_assert(sizeof(AudioOut2MasteringCompressorStates<3>) == 208); +static_assert(sizeof(AudioOut2MasteringCompressorStates<4>) == 272); +static_assert(sizeof(AudioOut2MasteringLimiterStates) == 112); +static_assert(sizeof(AudioOut2MasteringStates<3>) == 336); +static_assert(sizeof(AudioOut2MasteringStates<4>) == 400); + +template +void RequireClearedStates(std::uint32_t statesId, std::uint32_t compressorId) { + AudioOut2MasteringStates states; + std::memset(&states, 0xAB, sizeof(states)); + states.states_header.states_id = statesId; + Require(sceAudioOut2MasteringGetState(&states, 0, 0) == 0, "known states ids must be accepted"); + Require(states.states_header.states_id == statesId, "the states id must be kept"); + Require(states.compressor_states.descriptor.id == compressorId, "the compressor descriptor id must be set"); + Require(states.compressor_states.descriptor.size == sizeof(states.compressor_states), "the compressor descriptor size must be set"); + Require(states.limiter_states.descriptor.id == 0x01010003u, "the limiter descriptor id must be set"); + Require(states.limiter_states.descriptor.size == sizeof(states.limiter_states), "the limiter descriptor size must be set"); + Require(states.compressor_states.input_rms[Bands - 1][7] == 0.0f, "the compressor levels must be zero"); + Require(states.limiter_states.gain_peak[7] == 0.0f, "the limiter peaks must be zero"); +} + +void TestMasteringGetState() { + RequireClearedStates<3>(1, 0x01010001u); + RequireClearedStates<4>(2, 0x01020001u); + Require(sceAudioOut2MasteringGetState(nullptr, 0, 0) == static_cast(0x80268201), "null states must be refused"); + AudioOut2MasteringStatesHeader unknown{3}; + Require(sceAudioOut2MasteringGetState(&unknown, 0, 0) == static_cast(0x80268204), "an unknown states id must be refused"); +} + void TestEnableChat() { Require(sceAudioOut2EnableChat() == 0, "enabling chat must be accepted"); Require(sceAudioOut2EnableChat() == 0, "enabling chat again must be accepted"); @@ -78,6 +110,7 @@ int main() { TestMasteringInit(); TestMasteringTerm(); TestMasteringSetParam(); + TestMasteringGetState(); TestEnableChat(); return 0; } diff --git a/docs/dev/debt/libSceAudioOut.md b/docs/dev/debt/libSceAudioOut.md index 75a1c104697e..9c186316f185 100644 --- a/docs/dev/debt/libSceAudioOut.md +++ b/docs/dev/debt/libSceAudioOut.md @@ -18,11 +18,12 @@ - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - the stereo fold is assumed, not the console's downmix: C at -3 dB into both sides, Ls/Rs, Lb/Rb and Ltf/Rtf at -3 dB and Ltb/Rtb at -6 dB into their own side, LFE dropped - [sceAudioOut2PortSetAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - each PCM buffer set (attribute 0) is taken as one grain: buffers set between two pushes are queued and played one per grain, at most 16 queued. A grain with no new buffer repeats the last one only when the port has always been given that same buffer (rewritten in place); a port whose buffers move on plays silence instead. Inferred from PPSA12544, whose object port (type 6) is given one grain of a 64-grain ring per set, often two or three between pushes and then none for a few; the console behaviour for several sets per push and for a push without a new buffer is not documented - [sceAudioOutDeviceIdOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - imported by PPSA01317, which fails to load without it; declared without parameters, signature unknown. It opens a port on a given audio device; which device ids exist and how they map to host outputs is unknown, so it throws +- [sceAudioOut2MasteringGetState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - states id 1 fills the default layout (three compressor bands) and id 2 the V2 layout (four bands); any other id returns 0x80268204 and a null pointer 0x80268201. The layouts, descriptor ids (0x01010001, 0x01020001, 0x01010003), sizes and error codes follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libAudio2.cpp), another emulator, and no console or title has confirmed them. There is no mastering stage, so every level, peak and compression coefficient is reported as zero; whether the console reports a compression coefficient of 1.0 for an idle band is unknown. `output` and `user` are not validated, and the call is accepted without `sceAudioOut2MasteringInit`; the console's behaviour for either is unknown. - [sceAudioOut2PortGetState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) and [sceAudioOut2GetSpeakerInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp) (libSceAudioOut) - the port state reports the channel count of the port's data format, output main, volume 127 and a zero reroute counter and flags; the speaker info reports a stereo front pair at -30 and +30 degrees. Taking the channel count from the data format follows [SharpEmu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs), another emulator, and no console measurement exists for either call: the output of a pad port, the volume after `sceAudioOut2PortSetAttributes` and the reroute counter on a device change are unverified, as are the speaker types other than stereo. ASTRO BOT and ASTRO's PLAYROOM reportedly stop on an assertion that depends on these values (#4317), but which field matters has not been isolated ### Functional - [sceAudioOut2EnableChat](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) takes no arguments and returns 0, as declared by [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs#L460-L464) ("Allows chat audio alongside the application's own"). The host mixes no chat audio and AudioOut2 has no chat port, so the call only records that chat audio is allowed. Whether the console fails it without a chat session, when repeated or before `sceAudioOut2Initialize` is unknown. [sceAudioOut2DisableChat](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) is the counterpart, imported by PPSA18216: it takes no arguments, returns 0 and records that chat audio is no longer allowed; its signature is not confirmed by a console measurement, and whether the console fails it without a chat session or when repeated is unknown. -- [sceAudioOut2Set3DLatency](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) and [sceAudioOut2MasteringInit, sceAudioOut2MasteringTerm and sceAudioOut2MasteringSetParam](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) have no effect: there is no 3D rendering or mastering stage. +- [sceAudioOut2Set3DLatency](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) and [sceAudioOut2MasteringInit, sceAudioOut2MasteringTerm, sceAudioOut2MasteringGetState and sceAudioOut2MasteringSetParam](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) have no effect: there is no 3D rendering or mastering stage. - [AudioOut2 contexts](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp) (libSceAudioOut) with an SDL device and no `sceAudioOut2ContextAdvance` calls read a grain's port buffers at the next push, at least 100 us after the grain was pushed, and play the mix behind a 40 ms cushion that the queue level leaves out. When the hardware reads the buffers is not known. The timing follows a title whose mixer fills a buffer after pushing it, reuses it two pushes later, and may still be writing it when the next push follows within microseconds. The last grain pushed before a title stops pushing is never read or played. - [sceAudioOutSysGetHdmiMonitorInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) reports an HDMI sink without a monitor name or audio descriptors: it zeroes the 0x180 bytes and returns 0, since audio reaches the host through SDL as PCM and there is no EDID to describe. The arguments `(type, info, size)`, the required size 0x180 and the fields titles read (monitor name at +0x0B, 13 bytes; descriptor count as a 32-bit value at +0x94; 8-byte descriptors `[coding type, channels, rate mask, 0, extra, 0, 0, 0]` from +0x98) come from [PS5-Audio-Passthrough-Research](https://github.com/sainsaji/PS5-Audio-Passthrough-Research/blob/c3be9c2afcb8f7838784ead0fe1c12a2ceea965a/docs/FINDINGS.md) (firmware 12.70, one monitor). What a console without an HDMI audio sink returns, an error or no descriptors, is unknown. The other bytes, among them the header at +0x90 (`01 09 4f 00` on that monitor), have no known meaning and are written as 0. No LPCM descriptor is reported for the host device, as the rate mask and extra byte of one are not known. A null buffer returns `0x80260004` like the other AudioOut calls; the console's code is not measured. Monitor type 0x10, which the console also accepts, other types and sizes other than 0x180 throw: their results and error codes are unknown.