From b855cec6d45ac9c59bd827268f34f0af644f6f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 5 Sep 2026 18:59:30 -0600 Subject: [PATCH] sceMp3: stop decoding at endPos instead of running off the buffer A game can notify more data than the file actually had - audio/mp3/stream asks for 3360 bytes and notifies all of them even when the read came up short - so the tail of the buffer holds stale bytes from the previous half. We happily decoded those, six frames past the end of the stream, because the end flag only suppressed the zero fill and never stopped the decoder. Check it before decoding too. The post-decode check stays where it was: the hardware rewinds in the same call that decodes the last frame, so the sum reads back as zero right after it, which is what audio/mp3/getsumdecoded records. Moving the whole thing up front breaks that test. Fixes audio/mp3/stream, added to tests_good - it walks 27 refills end to end, so it also covers the half-buffer handout. Co-Authored-By: Claude Opus 5 --- Core/HW/SimpleAudioDec.cpp | 13 +++++++++++-- test.py | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Core/HW/SimpleAudioDec.cpp b/Core/HW/SimpleAudioDec.cpp index 8d3517f022..7efa57f194 100644 --- a/Core/HW/SimpleAudioDec.cpp +++ b/Core/HW/SimpleAudioDec.cpp @@ -503,8 +503,15 @@ u32 AuCtx::AuDecode(u32 pcmAddr) { if (pcmAddr) Memory::WriteOrException_U32(outptr, pcmAddr); + // The stream is over once the decoder has consumed up to endPos, whatever is still sitting in + // the buffer. A game can hand us more than the file actually had - audio/mp3/stream notifies + // the full size it asked for even when the read came up short - and the hardware won't decode + // that tail, it just reports the end. A stream that still has loops left was already rewound + // by the block below, so this only stops us for good. + bool end = (int64_t)readPos - AuBufAvailable >= (int64_t)endPos; + // Decode a single frame in sourcebuff and output into PCMBuf. - if (!sourcebuff.empty()) { + if (!end && !sourcebuff.empty()) { // FFmpeg doesn't seem to search for a sync for us, so let's do that. int nextSync = 0; if (decoder->GetAudioType() == PSP_CODEC_MP3) { @@ -533,7 +540,9 @@ u32 AuCtx::AuDecode(u32 pcmAddr) { } } - bool end = readPos - AuBufAvailable >= (int64_t)endPos; + // Check again now that the decode has consumed more. The hardware rewinds in the same call that + // decodes the last frame, so the sum reads back as zero right after it (audio/mp3/getsumdecoded). + end = (int64_t)readPos - AuBufAvailable >= (int64_t)endPos; if (end && LoopNum != 0) { // When looping, start the sum back off at zero and reset readPos to the start. SumDecodedSamples = 0; diff --git a/test.py b/test.py index d330ce2135..e86427c0c7 100755 --- a/test.py +++ b/test.py @@ -112,6 +112,7 @@ tests_good = [ "audio/mp3/release", "audio/mp3/reserve", "audio/mp3/setloopnum", + "audio/mp3/stream", "audio/output2/changelength", "audio/output2/reserve", "audio/output2/threads",