Skip to content

feat(ajm): resample interleaved s16 PCM through codec 23 - #6841

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
Sevthered:feat/ajm-pcm-resample
Oct 19, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
Sevthered:feat/ajm-pcm-resample

Conversation

@Sevthered

Copy link
Copy Markdown
Contributor

What

Broken Sword - Shadow of the Templars: Reforged (PPSA21480) stops 11 to 15 s after start on current main (#6671), 2 of 2 runs. These are the APS5_TRACE_AJM=1 lines asked for in #6671:

[ajm] instance create: context 1, codec 23, flags 0x1002 -> instance 376833
[ajm] batch start: context 1, priority 40, 152 of 184 buffer bytes used
[ajm] instance 376833 initialize: 12 parameter bytes (00 00 00 00 02 00 00 00), sideband 8 bytes -> result 0x0, 0 channels, 0 Hz, superframe 0 bytes (0 frames of 0 samples)
[ajm] instance 376833 set resample ratio 0.918750 change 0.000000
terminate called after throwing an instance of 'std::runtime_error'
  what():  AJM: decoding codec 23 is not implemented

What the title does with codec 23, measured with a local probe (not part of this PR):

  • It decodes its audio itself: the .osa archives hold Ogg Opus streams, music.vt7a holds Ogg Vorbis, and libopus 1.5.2 is linked into eboot.bin.
  • For every block it submits one batch: sceAjmBatchJobInitialize with the 12 bytes 0, 2, 44100, sceAjmBatchJobSetResampleParameters with 0.91875 (44100/48000), then sceAjmBatchJobDecode with 940 input bytes and a 1024-byte output. 940 bytes are 235 interleaved stereo s16 frames and 1024 bytes are 256; 256 × 0.91875 = 235.2.
  • The input of consecutive blocks is continuous audio: over 10,434 non-silent blocks the step across a block boundary matches the step between samples inside a block (median 93 against 93, p99 949 against 934).

So the title uses codec 23 as PCM input that only goes through the resampler, and this PR handles it that way:

  • The initialize parameters are taken as input format, channel count and sample rate. Format 0 is interleaved s16; other formats throw.
  • A run consumes its whole input and emits every output frame whose position (output index × ratio) is below the input frame count. It uses the 4-point Catmull-Rom of the existing resampler, moved into a helper without change, with the edge frames repeated.
  • Cases the title does not show throw: a run after a resampled run without an initialize or context clear in between (whether the console carries the resampler position across runs is unknown), a ratio change per sample, gapless counts, an input that is empty or not a whole number of frames, an output too small for the resampled input, run flags other than SIDEBAND_STREAM, and initialization through sceAjmBatchJobControl.

Why every frame is emitted rather than going through the streaming resampler of the other codecs: that resampler holds two frames back for the next run. With it the title received 254 frames per block and then submitted a second job for exactly the missing 8 bytes, after a new initialize and with the same 940 input bytes. With every frame emitted it submits one job per block, 187.5 per second (48000 / 256), and the output is as smooth across block joins as inside a block (median step 88 against 85, p99 882 against 858).

The MP3, ATRAC9, M4AAC and Opus paths are unchanged. The debt entry lists every value taken from this title and every assumption. #6672 records codec 23 as not implemented in the same debt file; with this PR that entry would no longer apply. The name CODEC_PCM is mine, since no source names a codec 23 (shadPS4's PS4 header ends its codec list with Max = 23, as noted in #6671).

Tested

Windows 11 (build 26300), WinLibs MinGW-w64 GCC 15.2.0 r7; Ubuntu 24.04 in WSL, GCC 13.3. Both on main 82ddae7 plus this commit.

  • guest_ajm with the new TestPcmResample: the title's batch sequence twice on the same instance, every output frame of a ramp and of a non-linear channel compared with an independent Catmull-Rom, nothing written past frame 256 of a 300-frame output, the passthrough at ratio 1 over two runs, and the throwing cases. Passes on Windows and Linux. It fails with the output one frame short, without the continuity check, and when the run fills the whole output (each mutation tried and reverted).
  • guest_ajm_teardown (Linux): passes.
  • Full Windows suite: 1321 tests pass (four timeouts of the parallel run pass when rerun alone); the 14 skips are the ones tools/check_skips.py --job windows expects.
  • Broken Sword Reforged PPSA21480 01.110.461, my own copy from a repack folder, on Windows with an RTX 3080 Ti (NVIDIA 616.56), relinked with --windows --registry: with this PR it no longer stops (runs of 122 s and 92 s with the final build, no error), the codec 23 jobs come one per block at 187.5 per second, and the sound plays correctly. With APS5_TRACE_AJM=1 the sound stutters, because the per-job trace lines slow the title's audio thread; that is the trace, not the decoder.
  • Not tested: the title on Linux, other titles (none known to use codec 23).

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file (none added)
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source (none)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: PPSA21480 (issue AJM: decoding codec 23 is not implemented (Broken Sword - Shadow of the Templars: Reforged, PPSA21480) #6671; there is no Game bring-up discussion for it)
  • GPUs tested: n/a
  • AI-assisted: yes

Closes #6671

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 3205 core/libs/tests/GuestAjm.cpp
PR 6044 core/libs/tests/GuestAjm.cpp

@MarcZX10

Copy link
Copy Markdown
Contributor

The codec-23 trace and repeated-block regression give this implementation a strong evidence trail. One integration item remains from the overlap report: #6040 inserts tests at the same GuestAjm.cpp location, and #6672 adds the debt entry this PR supersedes. Please coordinate merge order with those authors and record Depends on: #N if one must land first; after the first merges, retain both test changes and make the debt entry match the landed implementation. The changes appear independent in behavior, but the shared hunks need an explicit resolution.

@MarcZX10

Copy link
Copy Markdown
Contributor

Follow-up from checking the live base: this PR still records base 82ddae75, while upstream main is bc3c0fc1. Its current checks are conventions and intake only; they do not validate the rebased implementation. Please rebase onto current main, preserve the #6040 test and #6672 debt changes when resolving those shared hunks, then rerun the focused AJM tests before merge.

@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed fix error Closes an issue (Fixes #N) labels Oct 17, 2026
@blawesom

Copy link
Copy Markdown
Contributor

Rebase handoff — rebased onto current main (authorship unchanged). Conflicts resolved: your codec-23 PCM test blocks sit alongside main's newer Opus gapless-reset test in GuestAjm.cpp, and your debt entry replaces the older "codec 23 is not implemented" one it supersedes.

Verified on Linux (CachyOS, kernel 7.2.9, GCC 16.2.1, Ninja, Release, BUILD_TESTING=ON, AMD RX 9070 XT / RADV):

To pick it up:

git fetch https://github.com/blawesom/AnyPS5 rebase/6841
git checkout <your branch>
git merge --ff-only FETCH_HEAD

Head is 417176200.

Broken Sword - Shadow of the Templars: Reforged (PPSA21480) decodes its
audio itself and uses AJM codec 23 only to resample interleaved s16 PCM
from 44.1 to 48 kHz: every block initializes the instance with
0, 2, 44100, sets the ratio 0.91875 and decodes 235 frames into a
256-frame output. The run threw "AJM: decoding codec 23 is not
implemented".

A codec 23 run now consumes its whole input and emits every output
frame whose position lies inside it, with the Catmull-Rom of the
existing resampler. Cases the title does not show throw, among them a
run after a resampled run without a new initialize.
@Sevthered
Sevthered force-pushed the feat/ajm-pcm-resample branch from 922e5b7 to 3f5f849 Compare October 17, 2026 14:48
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 17, 2026
@Sevthered

Copy link
Copy Markdown
Contributor Author

Thanks @blawesom, I took your rebase. I applied 417176200 onto today's main 82f0c9aec, which needed no further changes, and pushed it as 3f5f84996. The diff is the same as yours.

@MarcZX100 both shared hunks are resolved: the codec 23 tests sit after #6040's Opus gapless-reset test in GuestAjm.cpp, and the debt entry from #6672 is replaced by the one describing the implementation. Since both have merged, there is nothing left to list under Depends on.

Focused tests on 3f5f84996:

  • Windows 11, WinLibs GCC 15.2.0 r7: guest_ajm passes (guest_ajm_teardown is only registered on Linux).
  • Ubuntu 24.04 in WSL, GCC 13.3: guest_ajm and guest_ajm_teardown pass.
  • tools/check_conventions.py --base origin/main passes.

The AJM code itself is unchanged from the head I ran Broken Sword with, so I didn't rerun the title or the full suite on this head.

AI-assisted: yes (Claude Code)

@MarcZX10

Copy link
Copy Markdown
Contributor

One malformed-input edge case in RunPcm: SetResampleParameters stores the ratio without validation, and if it is NaN then both < frames comparisons here are false. The loop emits zero frames, the post-loop capacity check also passes, and the function reports the whole input consumed with a successful result. Could this reject a non-finite ratio (and cover it in TestPcmResample) so malformed state cannot silently drop a whole block of audio? I have not changed the PR branch.

@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 18, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
@adriadam10
adriadam10 merged commit 77b3edf into boykopovar:main Oct 19, 2026
8 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the diff against the trace in #6671: codec 23 is handled as interleaved s16 PCM through the existing Catmull-Rom resampler, and every case the title does not show throws (other input formats, ratio change, gapless, empty/partial input, small output, extra run flags, init through sceAjmBatchJobControl, second resampled run without init). Non-finite or non-positive ratios are already rejected in sceAjmBatchJobSetResampleParameters, so RunPcm cannot see a NaN. TestPcmResample pins the output against an independent Catmull-Rom and the debt entry replaces the old "not implemented" one; merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs fix error Closes an issue (Fixes #N) prx unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AJM: decoding codec 23 is not implemented (Broken Sword - Shadow of the Templars: Reforged, PPSA21480)

4 participants