feat(audioout): implement sceAudioOut2Set3DLatency and accept sceAudioOut2MasteringInit(0)

PPSA26344 calls sceAudioOut2Set3DLatency(0xFF, 2) right after
sceAudioOut2Initialize and fails its audio initialization on a negative
return; the export was missing, so the import did not resolve. Its
audio subsystem setup also calls sceAudioOut2MasteringInit(0), which
threw.

sceAudioOut2Set3DLatency accepts a latency of 2 for the system user
(0xFF) and throws for any other user or latency, as the unit and range
are unknown. sceAudioOut2MasteringInit accepts flags 0 and throws for
other flags. There is no 3D rendering or mastering stage, so neither
call changes the output; TechnicalDebt lists this under Functional and
the assumed signatures under Unknown function info.

The new audio_out2_latency test covers the accepted and thrown values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Celegans12 2026-10-05 09:31:24 +02:00
parent e248f404ef
commit 0404752111
5 changed files with 78 additions and 2 deletions

View File

@ -52,4 +52,14 @@ if(BUILD_TESTING)
set_tests_properties(audio_out_last_output_time PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAudioOut>")
endif()
add_test_executable(audio_out2_latency_tests tests/AudioOut2Latency.cpp)
target_include_directories(audio_out2_latency_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(audio_out2_latency_tests PRIVATE libSceAudioOut libc)
configure_windows_unwind(audio_out2_latency_tests)
add_test(NAME audio_out2_latency COMMAND audio_out2_latency_tests)
if(WIN32)
set_tests_properties(audio_out2_latency PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAudioOut>")
endif()
endif()

View File

@ -5,8 +5,7 @@
extern "C" {
int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags) {
(void)flags;
NotImplemented_nid_no_patch(__func__);
if (flags != 0) NotImplemented_nid_no_patch(__func__);
return 0;
}

View File

@ -1,12 +1,21 @@
#include <cstdint>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
static constexpr int USER_ID_SYSTEM = 0xFF;
static constexpr std::uint32_t SUPPORTED_3D_LATENCY = 2;
extern "C" {
int APS5_VABI sceAudioOut2Initialize(void) {
return 0;
}
int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t latency) {
if (userId != USER_ID_SYSTEM || latency != SUPPORTED_3D_LATENCY) NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2GetSystemState(AudioOut2SystemState* state) {
if (!state) return static_cast<int>(0x80260502);
state->loudness = 0.0f;

View File

@ -0,0 +1,55 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <stdexcept>
extern "C" {
int APS5_VABI sceAudioOut2Initialize();
int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t);
int APS5_VABI sceAudioOut2MasteringInit(std::uint32_t);
}
static void Require(bool value, const char* message) {
if (value) return;
std::fprintf(stderr, "%s\n", message);
std::abort();
}
template<typename TFunction>
static bool ThrowsRuntimeError(TFunction function) {
try {
function();
} catch (const std::runtime_error&) {
return true;
}
return false;
}
namespace {
constexpr int systemUser = 0xFF;
constexpr int user = 0x10000000;
void TestSet3DLatency() {
Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "latency 2 for the system user must be accepted");
Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "latency 2 must be accepted again");
Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(systemUser, 1); }), "latency 1 must throw");
Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(systemUser, 3); }), "latency 3 must throw");
Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(user, 2); }), "a user other than the system user must throw");
}
void TestMasteringInit() {
Require(sceAudioOut2MasteringInit(0) == 0, "flags 0 must be accepted");
Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInit(1); }), "flags 1 must throw");
}
}
int main() {
Require(sceAudioOut2Initialize() == 0, "initialization must succeed");
TestSet3DLatency();
TestMasteringInit();
return 0;
}

View File

@ -49,6 +49,8 @@ Throughout the project, every function at every stage either **does exactly what
- [sceSslClose](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature
- [sceSslGetSerialNumber](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature
- [X+4jdIS75P0](../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown name, signature
- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - assumed to take `(user_id, latency)`: KytyPS5 reads `(user_id, output, latency_us)`, but the only known call sets just edi and esi; only `(0xFF, 2)` is accepted
- [sceAudioOut2MasteringInit](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - flag values unknown; only 0 is accepted
- [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - effect of a negative mix level unknown; only 0 to 32768 (0 dB) is accepted
- [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
@ -96,6 +98,7 @@ Throughout the project, every function at every stage either **does exactly what
- `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows.
- `--to-intel` replaces the approximate VEX.128 register forms of VRSQRTPS and VRCPPS with [correctly rounded](../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). The 256-bit, scalar, legacy and memory forms keep the native instruction, as does a 4-byte site with no movable instruction after it for the jump (5 sites in PPSA21564).
- [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character.
- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) and [sceAudioOut2MasteringInit](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) have no effect: there is no 3D rendering or mastering stage.
- [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture.
- [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero): reading it from the frame's Xing/Info, VBRI or Fraunhofer data is not implemented.
- The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the hardware with the IEEE mode off and f32 denormals flushed, the LLVM graphics default; the shader's MODE register is not read, and the hardware ignores the modifier in the other modes. On f16 results it is ignored, as on the hardware while f16 denormals are enabled; with them flushed the hardware applies it.