Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions core/libs/SceTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <std::uint32_t Bands>
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 <std::uint32_t Bands>
struct AudioOut2MasteringStates {
AudioOut2MasteringStatesHeader states_header;
std::uint32_t reserved[3];
AudioOut2MasteringCompressorStates<Bands> compressor_states;
AudioOut2MasteringLimiterStates limiter_states;
};

struct AudioOutOutputParam {
int handle;
const void* ptr;
Expand Down
37 changes: 34 additions & 3 deletions core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"

namespace {

constexpr int ErrorMasteringInvalidApiParam = static_cast<int>(0x80268201);
constexpr int ErrorMasteringInvalidStatesId = static_cast<int>(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 <std::uint32_t Bands>
void ClearStates(AudioOut2MasteringStatesHeader* header, std::uint32_t statesId, std::uint32_t compressorStructId) {
auto* states = reinterpret_cast<AudioOut2MasteringStates<Bands>*>(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) {
Expand All @@ -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<CompressorBandsDefault>(state, StatesIdDefault, CompressorStructIdDefault);
return 0;
case StatesIdV2:
ClearStates<CompressorBandsV2>(state, StatesIdV2, CompressorStructIdV2);
return 0;
default:
return ErrorMasteringInvalidStatesId;
}
}

int APS5_VABI sceAudioOut2MasteringSetParam(const AudioOut2MasteringParamsHeader* param, uint32_t output, uint32_t flags) {
Expand Down
33 changes: 33 additions & 0 deletions core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"

#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdexcept>

extern "C" {
Expand All @@ -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();
}
Expand Down Expand Up @@ -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 <std::uint32_t Bands>
void RequireClearedStates(std::uint32_t statesId, std::uint32_t compressorId) {
AudioOut2MasteringStates<Bands> 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<int>(0x80268201), "null states must be refused");
AudioOut2MasteringStatesHeader unknown{3};
Require(sceAudioOut2MasteringGetState(&unknown, 0, 0) == static_cast<int>(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");
Expand All @@ -78,6 +110,7 @@ int main() {
TestMasteringInit();
TestMasteringTerm();
TestMasteringSetParam();
TestMasteringGetState();
TestEnableChat();
return 0;
}
3 changes: 2 additions & 1 deletion docs/dev/debt/libSceAudioOut.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading