From ad7be62321f5f0df6ccc20b63b9315f6b9251aa1 Mon Sep 17 00:00:00 2001 From: Dipshet <264011288+Dipshet@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:26:02 +0200 Subject: [PATCH] apu: decode XMA frames whose 15-bit header straddles a packet boundary The frame walk treated fewer than 15 remaining bits as end-of-chain, but a frame starting there is real: its header continues in the next packet, as the encoder's own seek ledger confirms. Each one was silently dropped, so a premix's parallel streams drifted apart in 512-sample steps and doubled on the fold. Cvar audio_xma_header_straddle_fix, on. --- .../src/native/audio/audio_system.cpp | 14 +++++++++ .../src/native/audio/xma/context.cpp | 30 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/thirdparty/rexglue-sdk/src/native/audio/audio_system.cpp b/thirdparty/rexglue-sdk/src/native/audio/audio_system.cpp index df138624..1b230216 100644 --- a/thirdparty/rexglue-sdk/src/native/audio/audio_system.cpp +++ b/thirdparty/rexglue-sdk/src/native/audio/audio_system.cpp @@ -70,6 +70,20 @@ REXCVAR_DEFINE_BOOL(audio_xma_loop_guard, true, "Audio", "machinery on every input buffer swap, truncating/" "skipping subframes of the stream (confirmed firing on " "AC6 cutscene streams via the deep-trace counter)."); +REXCVAR_DEFINE_BOOL(audio_xma_header_straddle_fix, true, "Audio", + "Decode XMA frames whose 15-bit frame header straddles a " + "2 KiB packet boundary. The frame walk treated " + "fewer-than-15 remaining bits as end-of-chain, but the " + "straddled frame is real: the encoder's own seek ledger " + "confirms it on every occurrence (its size field, read " + "across the boundary, equals the next packet's " + "first-frame offset minus the packet header). Without " + "this, one whole 512-sample frame is silently dropped per " + "occurrence, so the parallel streams of a multi-stream " + "voice drift apart in 512-sample steps and the premix " + "combs on the stereo fold - the long-reported doubled/" + "robotic cutscene dialogue. Off restores the legacy " + "walk for A/B."); REXCVAR_DEFINE_BOOL(audio_xma_preserve_timeline, true, "Audio", "When the XMA decoder backend rejects a damaged frame, emit that " "frame's worth of silence instead of dropping it. The input read " diff --git a/thirdparty/rexglue-sdk/src/native/audio/xma/context.cpp b/thirdparty/rexglue-sdk/src/native/audio/xma/context.cpp index cb39582a..c9fc0a38 100644 --- a/thirdparty/rexglue-sdk/src/native/audio/xma/context.cpp +++ b/thirdparty/rexglue-sdk/src/native/audio/xma/context.cpp @@ -19,6 +19,7 @@ REXCVAR_DECLARE(bool, audio_deep_trace); REXCVAR_DECLARE(bool, audio_xma_loop_guard); REXCVAR_DECLARE(bool, audio_xma_preserve_timeline); +REXCVAR_DECLARE(bool, audio_xma_header_straddle_fix); namespace rex::audio { @@ -517,6 +518,35 @@ kPacketInfo XmaContext::GetPacketInfo(uint8_t* packet, uint32_t frame_offset) { while (true) { if (stream.BitsRemaining() < kBitsPerFrameHeader) { + // A frame whose 15-bit header STRADDLES the packet boundary is real. + // The chain only reaches this point when the previous frame's + // continuation bit promised another frame (or the packet's declared + // first-frame offset points here), and the encoder's own seek ledger + // confirms it: the straddled size field, read across the boundary, + // equals the next packet's first_frame_offset minus the 32-bit packet + // header on every occurrence in AC6's demopacks. Treating <15 + // remaining bits as end-of-chain mislabelled the previous frame "last + // in packet", so Decode()'s advance jumped to the next packet's + // first_frame_offset - exactly PAST the straddled frame's continuation + // - and one whole 512-sample frame vanished silently (~1 per stream + // per 14 s in AC6 cutscene premixes, at stream-specific positions). + // The streams of a multi-stream voice drifted apart in 512-sample + // steps and the coherent premix combed on the stereo fold: the + // long-reported robotic cutscene echo. + // + // Counting the frame is the whole fix: current_frame_size_ = 0 routes + // Decode() into its existing cross-packet path, whose combined + // two-payload bitstream reads the straddled header and copies the full + // frame. (BitsRemaining() == 0 exactly - a header starting precisely + // at the payload junction - keeps the old behaviour; no occurrence + // exists in the packs and the read offset cannot represent it.) + if (REXCVAR_GET(audio_xma_header_straddle_fix) && stream.BitsRemaining() > 0) { + if (stream.offset_bits() == frame_offset) { + packet_info.current_frame_ = packet_info.frame_count_; + packet_info.current_frame_size_ = 0; + } + packet_info.frame_count_++; + } break; }