21 KiB
libSceAjm.native technical debt
Unknown function info
- sceAjmBatchGetLog, sceAjmInstanceAvailable (libSceAjm.native) - names and NIDs are present in the PS5 3.20 export inventory, but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim.
- sceAjmInstanceExtend, sceAjmInstanceSwitch (libSceAjm.native) - their four-argument signatures come only from SharpProspero, whose comments do not describe them; shadPS4 declares both without parameters. Neither is measured on a console; these throwing declarations only resolve imports.
- sceAjmInstanceCreate (libSceAjm.native) - a full 14-bit instance slot namespace returns
0x80930007(OUT_OF_RESOURCES), as shadPS4's InstanceCreate does for its own instance pool (PS4). The 16384 slots per codec prefix are this runtime's existing ID layout, not a measured console capacity; the PS5 capacity and exhaustion result are unverified. - ATRAC9 vibration decoding (libSceAjm.native): channel configurations 6 (mono) and 7 (dual mono) use the pinned vgmstream decoder, including its 12/24/48 kHz profile limits. Synthetic fixtures cover interleaving, reset after malformed input, partial superframes and PCM16/PCM32/float output; console PCM rounding and physical haptic output are unverified. Ordinary channel configurations and discrete multichannel streams still use LibAtrac9. Decoder creation and decoding run under the existing AJM instance lock because the dependency rebuilds shared Huffman tables during initialization.
- sceAjmBatchJobGetInfo (libSceAjm.native) - writes the result and format sideband of a run job without buffers with the
SIDEBAND_FORMATflag (0x400000000000), as KytyPS5 does, not confirmed on a PS5 title; the ATRAC9 format's bitrate is the superframe's bits per second (superframe bytes * 8 * sample rate / samples per superframe), as shadPS4 derives it and as a PS4 reported it (168000 for 448-byte superframes at 48 kHz), where KytyPS5 reports 0; 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 (libSceAjm.native) - signature, result layout and rate tables taken from the shadPS4 reimplementation of the PS4 library, not confirmed on a PS5 title: Layer I/II headers are accepted when no original-file-length (
parse_ofl) lookup is requested, which is supported only for Layer III; MPEG-2.5 stops at 64 kbps. Theparse_ofllayout (Xing/Info + LAME tag after side information, with the standard fixed Xing/Info offset retained for encoders such as LAME, VBRI at byte 36 or after side information, Fraunhofer0xB4block 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 (libSceAjm.native) - the
RUN_GET_CODEC_INFOflag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the shadPS4 and RPCSX reimplementations, not confirmed on a PS5 title (the one call in PPSA04354 passes a 24-byte result buffer, the 8-byte result plus the 16-byte ATRAC9 codec info written here; the call does not show the codec or the fields); 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 (libSceAjm.native, codec 24) - the initialize parameter layout and little-endian
u16packet byte count come from The Smurfs Dreams (2, 48000, 0) and Lost Soul Aside (4, 48000, 1). The third word is interpreted as the mapping family, as in KytyPS5's initialize structure. Family 1 uses the conventional stream counts, coupled counts and channel map from libopus 1.5.2's surround layout, passed to FFmpeg through an RFC 7845 section 5.1 header with zero pre-skip and gain; gapless skipping remains controlled by AJM sideband parameters. FFmpeg returns PCM in its native speaker order. The implicit stream layout, PCM channel order and invalid family-0 channel count result are not verified on a console. NHL 27 (PPSA34063) passes1, 48000, 255. Family 255 with one channel is decoded as one mono stream, the same as family 0; this is assumed, since the stream count, coupled stream count and channel mapping that a family-255 Opus header carries (RFC 7845 section 5.1.1) are not passed. Only 48000 Hz, mapping families 0 and 1, and family 255 with one channel are implemented; other rates and families, and family 255 with more channels, throw - AJM codec 23 (libSceAjm.native) - taken as interleaved
s16PCM that only passes through the resampler, from Broken Sword - Shadow of the Templars: Reforged (PPSA21480); no source names a codec 23 (shadPS4's PS4 header ends its codec list withMax = 23) and the nameCODEC_PCMis ours. The title decodes its Ogg Opus and Vorbis audio itself and, for every block, initializes the instance (flags 0x1002) with0, 2, 44100, sets the ratio 0.91875 (44100/48000) and decodes 235 stereo frames into a 256-frame output. The 12 initialize bytes are taken as input format (0 iss16, other values throw), channel count and sample rate; the rate is only reported in the format sideband; other sizes throw; zero or more than 8 channels or a zero rate return the invalid-parameter result as for Opus, not verified. A run consumes its whole input and emits every output frame whose position (output index × ratio) is below the input frame count, with the 4-point Catmull-Rom of the other codecs' resampler and the first and last input frames repeated past the edges, so each run restarts at position 0;totalDecodedSamplescounts input frames;s32and float outputs are accepted although onlys16was seen. Throwing because the console's behaviour is unknown: a run after a resampled run without an initialize or context clear in between (whether the console carries the resampler position across runs), a ratio change per sample, gapless counts, an empty input or one that is not a whole number of frames, an output that cannot hold the resampled input, run flags other thanSIDEBAND_STREAMand initialization throughsceAjmBatchJobControl - sceAjmBatchJobSetResampleParametersEx (libSceAjm.native) - the
(info, instance, float ratio_start, float ratio_change_per_sample, flags, result)signature is inferred from the name and fromsceAjmBatchJobSetResampleParameters, and PPSA02225's one call agrees with that layout (rdi,esi,xmm0andxmm1as two single floats,edx,rcx) without showingflagsor the float roles. With a zero change per sample it sets the ratio assceAjmBatchJobSetResampleParametersdoes (Watch Dogs: Legion, PPSA01500, calls it so). How a non-zero change ramps the ratio is not confirmed on a console (no source: KytyPS5 ignores it, prosper stubs it, the PS5 firmware does not export the batch builders to a payload): it is assumed to be added to the ratio after each emitted output frame, clamped at zero, and kept across batches. RIDE 4 (PPSA01599) passes one call with a change of 4.6e-7, so the exact rule matters little there. - sceAjmBatchJobControl (libSceAjm.native) - RESET (bit 13), INITIALIZE (bit 14) and the sideband input order (gapless decode, then initialize parameters) come from shadPS4 (PS4); the one call in PPSA25936 passes the flags 0x4000 (bit 14 only), an 8-byte input and an 8-byte output, which agrees with INITIALIZE alone and the 8-byte parameters, without showing the codec or the meaning of the words; other flags throw
- sceAjmBatchJobControl (libSceAjm.native) - RESET is assumed to clear the context as sceAjmBatchJobClearContext does
- sceAjmBatchJobControl (libSceAjm.native) - initialize parameters are 8 bytes for ATRAC9 and M4AAC, 12 for Opus and none for MP3, as one title passes them; other codecs and sizes, and an output other than the 8-byte result, throw
- sceAjmBatchJobControl (libSceAjm.native) - SIDEBAND_GAPLESS_DECODE with RESET overwrites both counts as in RPCSX; shadPS4 ignores a zero count when the other is nonzero. Without RESET they differ, so it throws
- sceAjmBatchJobSetResampleParameters, sceAjmBatchJobGetResampleInfo (libSceAjm) - the ratio is taken as decoded samples per output sample and num_samples as the decoded samples the resampler holds, both inferred from FMOD (Hades II); the console's interpolation is unknown, a 4-point Catmull-Rom is used. The flags argument is ignored, as in shadPS4 and Kyty
- sceAjmBatchJobControlBufferRa, sceAjmBatchJobRunBufferRa, sceAjmBatchJobRunSplitBufferRa, sceAjmBatchStartBuffer (libSceAjm, PS4) - the batch layout (chunk kinds, 20-bit payload, flags bits 32-51 as the flags chunk's payload, the builders' chunk order) follows shadPS4 and was confirmed byte for byte for 64-byte control and 80-byte run jobs by a PS4 trace of Knowledge is Power: Decades (CUSA12435, Wwise, 32 545 batches dumped by a GoldHEN plugin). The console's builders write only bits 0-3 (kind), bit 4 (a buffer chunk's output direction) and bits 6-25 (payload) of the header word and leave bits 5, 26, 27 and 31 as the previous batch left them, so the kind is read as 4 bits plus the direction bit, not as 6 bits
- sceAjmBatchStartBuffer (libSceAjm, PS4) - the statistics instance 0x80000 and the inline chunk come from shadPS4 only: the traced title builds every batch with the builders and a return address, and no title was seen writing an inline chunk. Flags are kept as passed. Unknown chunk kinds, control flags other than RESET, INITIALIZE and SIDEBAND_GAPLESS_DECODE, control jobs with run buffers and run jobs with a control input throw
- sceAjmBatchStartBuffer refusals (libSceAjm, PS4) - probe batches through the console's builders on a PS4 showed that a run buffer chunk of size zero is dropped whatever its address (the job runs as without it: 0x10 and nothing consumed for no room, 0 for no input), and that a null address with a nonzero size refuses the batch before any of its jobs runs, with 0x80930011 returned and the error record holding 0x80930016, the job chunk's address, the chunk's offset in the job and the job's return address. Control buffer chunks follow the same rule here, not measured. A size not a multiple of 8, a chunk that overruns its job or the batch, or a job without flags are refused the same way with 0x80930011 in the record; the console's report for those was not measured
- sceAjmBatchStartBuffer null and empty batches (libSceAjm, PS4) - a null batch address returns 0x80930005 whatever the size, as the PS5 builders' sceAjmBatchInitialize does for a null buffer, and a batch of zero bytes at a non-null address is accepted, runs nothing and completes at once (a null address with size 0 is refused like any null address); neither was measured on a console (the probe that passed a null batch froze the console before a result was written, so it is not retried on hardware)
- sceAjmStrError (libSceAjm.native) - throws as unimplemented. The console's texts were read by calling its own export in PPSA01317 on a PS5 (firmware 13.40, reported on #3205):
okfor 0, 24 lowercase texts without a full stop for 0x80930002 to 0x80930019 (context does not existtospecified memory area is not registered), and(unknown)for every other value including 0x80930000, 0x80930001 and 0x8093001a; none is implemented here. 14 titles and modules import it. Two were disassembled for this entry: the cri_ware_unity plugin of PPSA25936 calls it once (0x120929) after a negative Ajm result and passes the returned string with the code to a logging or formatting function; PPSA07426 calls it after a negativesceAjmBatchWaitresult (0xe863f0) and, after a success, for two job codes (0xe862f9 and 0xe86308) when the first is nonzero, and formats the strings into a text buffer. What those functions do with the strings was not followed, and the other importers were not read. - ATRAC9 run results (libSceAjm.native) - probe runs of a title's ATRAC9 stream through the console's builders on a PS4 showed the run result naming the side that ran out:
PARTIAL_INPUT(0x8) when the input ran out with room left, on a superframe boundary or not;NOT_ENOUGH_ROOM(0x10) when the room ran out with input left, after any number of frames, and with nothing consumed for a zero room; both (0x18) when the input ended just as the output filled, which is every Wwise run job. ATRAC9 run jobs withRUN_MULTIPLE_FRAMESfollow those rules here, vibration streams included, which were not traced; resampled runs and the other codecs keep the earlier rule (a trailing partial frame is partial input, a run that produced nothing is out of room) - ATRAC9 run without RUN_MULTIPLE_FRAMES (libSceAjm.native) - the same PS4 probe decoded exactly one frame and reported 0 for such a run, and a job read from a PS4 batch buffer (sceAjmBatchStartBuffer) does the same here. A job from the PS5 builders still decodes its whole input, as FMOD in PPSA29141 needs (it puts two run jobs in one batch with the second's input right after the whole of the first's); whether a PS5 stops after one frame is not measured. MP3 and Opus run jobs stop after one frame on both paths. The console also rejected the tests' synthetic silent superframe after three frames (
CODEC_ERROR | FATAL, internal result -577) where LibAtrac9 decodes four, so that vector proves job plumbing, not decoding; on the title's stream LibAtrac9 matched the console's PCM within 4 LSB, 83.5% of the samples exactly
Functional
-
sceAjmDecWVorbisCreateHeaderPacket (libSceAjmWVorbis, implemented in libSceAjm.native) - refuses an output smaller than the whole packet (56 bytes plus the setup packet) with
0x8A6E0003. The PS5 13.60 bit writer only checks that each write starts inside the buffer, so the console can accept a buffer a few bytes short and write past it -
AJM batch contexts (libSceAjm.native): start, wait and cancel reject context IDs not allocated by initialization; batches belong to their creating context, as in shadPS4. This is not verified on PS5. Finalization still does not invalidate contexts or release their instances and outstanding batches; jobs execute synchronously and cancellation does not interrupt them.
-
sceAjmBatchStart: console results for malformed batch descriptors and corrupted jobs are unverified. A null batch buffer or an offset beyond its capacity returns the existing invalid-parameter error; a truncated or inconsistent private packed job throws before its payload is read. The packed representation belongs to this implementation.
-
ATRAC9 gapless segments (libSceAjm.native) - a run job stops after the gapless total of output samples counted from the start of the segment, leaving the rest of its input unconsumed, and the next run starts the next segment with the decoder as it is and without a second skip. FMOD in PPSA29141 sets a total shorter than a sound's frames and reads the frames after it on later runs; with the output held at the total its stream reader waited for them forever and the last buffer repeated. The console's handling of the frames after the total is not documented; a reset (
SetGaplessDecodewith the reset flag,ClearContext, initialization) starts a new segment. -
AJM M4AAC decoder (libSceAjm.native, codec 2) - Ghost of Yotei (PPSA26344) initializes it through
sceAjmBatchJobControl. The initialize parameters (u32config type, 1 ADTS or 2 raw, thenu32sampling frequency index), instance flags (channels in bits 3-6,ENABLE_SBR_DECODEbit 32,ENABLE_NONDELAY_OUTPUTbit 33, surround interleave order bits 34-35),{heaac, reserved}codec info and decoder error result (CODEC_ERROR | FATAL) follow shadPS4 (PS4, fdk-aac) and are unverified on PS5; decoding uses FFmpeg's AAC decoder. WithoutENABLE_NONDELAY_OUTPUT, the first two frames after initialization or reset are consumed without output. Raw input takes its channel configuration from the instance (0 means stereo) and is decoded as one access unit per run job. The format reports the last frame's bitrate. Invalid config type or raw sampling frequency index above 11 returns invalid-parameter result 4. The implementation throws for ADTS without a frame header, profiles other than AAC LC, multiple raw data blocks per ADTS frame, unsupported channel configurations, more channels than the instance supports, HE-AAC withoutENABLE_SBR_DECODE, raw input withRUN_MULTIPLE_FRAMES, output room smaller than the previous frame length, and unknown codec flags. -
AJM, APR and NGS2 state is allocated with
newand never freed on purpose: title threads still call into these libraries whilestd::exitruns static destructors, so ordinary statics would be destroyed under them. Keep them leaked;guest_ajm_teardowncovers the AJM case on Unix. -
AJM Opus mapping family 255 with several channels (libSceAjm.native): the initialization words carry no stream count or channel mapping, so the decoder opens on the first packet. It counts the self-delimited streams of that multistream packet (RFC 6716 appendix B) and takes each stream's stereo flag from its TOC byte as coupled, then decodes with an identity channel mapping. A packet whose streams do not add up to the channel count throws. The layout is inferred, not read from the console; unverified on hardware.