From 05de11ac1e988c3adc2c41e72701491c96e30646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Tue, 6 Oct 2026 18:01:00 +0200 Subject: [PATCH] feat(libSceAjm.native): implement sceAjmBatchJobGetInfo Co-Authored-By: Claude --- core/libs/prx/libSceAjm.native/Export.cpp | 8 ---- core/libs/prx/libSceAjm.native/src/Ajm.cpp | 4 ++ core/libs/tests/GuestAjm.cpp | 48 ++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAjm.native/Export.cpp b/core/libs/prx/libSceAjm.native/Export.cpp index 62c0bc44e..dee862dbb 100644 --- a/core/libs/prx/libSceAjm.native/Export.cpp +++ b/core/libs/prx/libSceAjm.native/Export.cpp @@ -50,14 +50,6 @@ int APS5_VABI sceAjmBatchJobEncode(AjmBatchInfo* info, uint32_t instance, const return 0; } -int APS5_VABI sceAjmBatchJobGetInfo(AjmBatchInfo* info, uint32_t instance, void* result) { - (void)info; - (void)instance; - (void)result; - AjmStub(__func__); - return 0; -} - int APS5_VABI sceAjmBatchJobGetResampleInfo(AjmBatchInfo* info, uint32_t instance, void* result) { (void)info; (void)instance; diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 9b5542631..af9cd4d4e 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -1055,6 +1055,10 @@ int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo* info, uint32_t instan return sceAjmBatchJobRunSplit(info, instance, SIDEBAND_GAPLESS_DECODE, nullptr, 0, nullptr, 0, result, sizeof(SidebandResult) + sizeof(SidebandGaplessDecode)); } +int APS5_VABI sceAjmBatchJobGetInfo(AjmBatchInfo* info, uint32_t instance, void* result) { + return sceAjmBatchJobRunSplit(info, instance, SIDEBAND_FORMAT, nullptr, 0, nullptr, 0, result, sizeof(SidebandResult) + sizeof(SidebandFormat)); +} + int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo* info, uint32_t instance, void* result, size_t result_size) { return sceAjmBatchJobRunSplit(info, instance, RUN_GET_CODEC_INFO, nullptr, 0, nullptr, 0, result, result_size); } diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index 0e9bb2483..434113f57 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -23,6 +23,7 @@ int APS5_VABI sceAjmBatchJobRun(AjmBatchInfo*, std::uint32_t, std::uint64_t, con int APS5_VABI sceAjmBatchJobSetGaplessDecode(AjmBatchInfo*, std::uint32_t, const void*, int, void*); int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo*, std::uint32_t, void*); int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo*, std::uint32_t, void*, std::size_t); +int APS5_VABI sceAjmBatchJobGetInfo(AjmBatchInfo*, std::uint32_t, void*); int APS5_VABI sceAjmBatchStart(std::uint32_t, const AjmBatchInfo*, int, AjmBatchError*, std::uint32_t*); int APS5_VABI sceAjmBatchWait(std::uint32_t, std::uint32_t, std::uint32_t, AjmBatchError*); int APS5_VABI sceAjmBatchCancel(std::uint32_t, std::uint32_t); @@ -427,6 +428,52 @@ void TestCodecInfo(std::uint32_t context) { Require(sceAjmInstanceDestroy(context, instance) == 0); } +struct FormatSideband { + std::int32_t result; + std::int32_t internalResult; + std::uint32_t numChannels; + std::uint32_t channelMask; + std::uint32_t sampleRate; + std::uint32_t sampleEncoding; + std::uint32_t bitrate; + std::uint32_t reserved; +}; + +void TestGetInfo(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + std::vector batch(4096); + AjmBatchInfo info{}; + FormatSideband early{-1, -1, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobGetInfo(&info, instance, &early) == 0); + Submit(context, info); + Require(early.result == 1 && early.numChannels == 0xaaaaaaaau && early.sampleRate == 0xaaaaaaaau); + + const std::uint8_t config[8] = {0xFE, 0x72, 0x1F, 0xF0}; + std::int32_t initResult[2] = {-1, -1}; + FormatSideband at9{-1, -1, 0, 0, 0, 0xaaaaaaaau, 0, 0xaaaaaaaau}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobInitialize(&info, instance, config, sizeof(config), initResult) == 0); + Require(sceAjmBatchJobGetInfo(&info, instance, &at9) == 0); + Submit(context, info); + Require(initResult[0] == 0); + Require(at9.result == 0 && at9.internalResult == 0 && at9.numChannels == 2 && at9.channelMask == 0x3 && at9.sampleRate == 48000 && at9.sampleEncoding == 0 && at9.reserved == 0); + Require(sceAjmInstanceDestroy(context, instance) == 0); + + Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); + std::vector pcm(1152); + DecodeSideband decoded{}; + FormatSideband mp3{-1, -1, 0, 0, 0, 0xaaaaaaaau, 0, 0xaaaaaaaau}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobDecodeSingle(&info, instance, MP3_MONO, sizeof(MP3_MONO), pcm.data(), pcm.size() * sizeof(std::int16_t), &decoded) == 0); + Require(sceAjmBatchJobGetInfo(&info, instance, &mp3) == 0); + Submit(context, info); + Require(decoded.result == 0 && decoded.inputConsumed == 96); + Require(mp3.result == 0 && mp3.internalResult == 0 && mp3.numChannels == 1 && mp3.channelMask == 0x4 && mp3.sampleRate == 48000 && mp3.sampleEncoding == 0 && mp3.bitrate == 32000 && mp3.reserved == 0); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + void TestBatchWaitRelease(std::uint32_t context) { constexpr int invalidBatch = static_cast(0x80930004); std::vector batch(64); @@ -487,6 +534,7 @@ int main() { TestDecodeSingle(context); TestGaplessDecode(context); TestCodecInfo(context); + TestGetInfo(context); TestBatchWaitRelease(context); TestBatchCancel(context); Require(sceAjmFinalize(context) == 0); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 645d976a4..a8af3b547 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -103,6 +103,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAvPlayerInit / sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap - [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info - [sceRazorCpuPushMarkerStatic, sceRazorCpuPopMarker](../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures from PPSA26344's calls; (name, color, flags) assumed for the push; both do nothing without a capture +- [sceAjmBatchJobGetInfo](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - writes the result and format sideband of a run job without buffers with the `SIDEBAND_FORMAT` flag (0x400000000000), as [KytyPS5](https://github.com/KytyPS5/KytyPS5) does, not confirmed on a PS5 title; the format reports bitrate 0 for ATRAC9 as in KytyPS5, while [shadPS4](https://github.com/shadps4-emu/shadPS4) derives it from the superframe size; before the instance is initialized it returns the not-initialized result (1) and no format, like the other run jobs here, while KytyPS5 returns result 0 with the format of the uninitialized decoder - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps; the original file length (`parse_ofl`) layout (Xing/Info + LAME tag, VBRI, Fraunhofer `0xB4` block with its CRC, encoder delay = samples per frame + LAME delay + 529) also comes from shadPS4 only, and the Fraunhofer block has no other public description - [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted