From 8a343bec577ea60dba81330aab3b32a0acce01e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 19 Sep 2026 09:22:54 -0600 Subject: [PATCH] AvcDecoder: let ffmpeg say when a frame came out damaged Nothing on this path ever called InitFFmpeg, so ffmpeg's log callback was never installed and everything it had to say about the bitstream went nowhere. Our own return codes only report that a frame arrived, not what it was built from, so a video could fall apart on screen while the log showed two warnings in two thousand frames. Also report decode_error_flags and AV_FRAME_FLAG_CORRUPT per frame, which covers the other half: a frame that decodes "successfully" out of concealment. Coalesced into one line per run of damage, since a missing reference makes every frame damaged until the next keyframe. Not setting AV_EF_EXPLODE, which would make the decoder fail where it currently conceals - that is a behaviour change, and this is meant to be a reporting one. Co-Authored-By: Claude Opus 5 (1M context) --- Core/HW/AvcDecoder.cpp | 27 +++++++++++++++++++++++++++ Core/HW/AvcDecoder.h | 4 ++++ 2 files changed, 31 insertions(+) diff --git a/Core/HW/AvcDecoder.cpp b/Core/HW/AvcDecoder.cpp index 568adf750f..39f8a9168b 100644 --- a/Core/HW/AvcDecoder.cpp +++ b/Core/HW/AvcDecoder.cpp @@ -19,6 +19,7 @@ #include "Common/Log.h" #include "Core/HW/AvcDecoder.h" +#include "Core/HW/MediaEngine.h" #ifdef USE_FFMPEG extern "C" { @@ -40,6 +41,11 @@ AvcDecoder::AvcDecoder() { ERROR_LOG(Log::ME, "AvcDecoder: couldn't allocate a codec context"); return; } + // Route ffmpeg's own diagnostics into our log. It is the only thing that knows a frame came + // out of a damaged bitstream - our return codes say a frame arrived, not whether it was built + // out of concealment - and without this they go nowhere on this path. + InitFFmpeg(); + // The PSP hands us whole access units, so no parser is needed and no truncation is expected. codecCtx_->flags = 0; codecCtx_->flags2 = 0; @@ -155,6 +161,27 @@ bool AvcDecoder::Decode(const u8 *data, int size) { } #endif + // A frame can arrive with everything above returning success and still be built partly out of + // concealment - a reference the decoder never got, or a slice it had to guess at. That only + // shows up here, and it is the difference between "the video is decoding" and "the video is + // decoding what the game actually sent us". + if (frame_->decode_error_flags || (frame_->flags & AV_FRAME_FLAG_CORRUPT)) { + concealedFrames_++; + // One line per run of trouble rather than per frame: once a reference is missing, every + // frame until the next keyframe is damaged, and that would be hundreds of lines. + if (!wasConcealing_) { + WARN_LOG(Log::ME, "AvcDecoder: frame %d decoded from a damaged bitstream (flags %08x%s) - " + "the stream is missing data, not the decoder", frameCount_, frame_->decode_error_flags, + (frame_->flags & AV_FRAME_FLAG_CORRUPT) ? ", corrupt" : ""); + wasConcealing_ = true; + } + } else if (wasConcealing_) { + WARN_LOG(Log::ME, "AvcDecoder: clean again at frame %d, %d frames were concealed", + frameCount_, concealedFrames_); + wasConcealing_ = false; + } + frameCount_++; + // Everything downstream indexes the three planes as 8-bit 4:2:0, which is all the PSP's // encoder produced. Anything else would be read as if it were, so say so and drop the frame // rather than converting garbage. diff --git a/Core/HW/AvcDecoder.h b/Core/HW/AvcDecoder.h index 4b088a042e..9f662c5d55 100644 --- a/Core/HW/AvcDecoder.h +++ b/Core/HW/AvcDecoder.h @@ -67,6 +67,10 @@ private: // Kept between calls so the usual case doesn't allocate. std::vector packetBuf_; bool haveFrame_ = false; + // For reporting runs of concealed frames rather than one line each - see Decode. + bool wasConcealing_ = false; + int concealedFrames_ = 0; + int frameCount_ = 0; int width_ = 0; int height_ = 0; };