Files
ppsspp/Core/HW/AvcDecoder.h
T
Henrik RydgårdandClaude Opus 5.5 8d2fbf7079 sceVideocodec: Hand pictures back when the PSP's decoder does
The PSP's AVC decoder follows the H.264 output process (Annex C),
measured in the new pspautotests video/mp4/avchold on streams that
differ only in their headers, and video/mpeg/decodeend. With HRD
parameters and picture timing SEI, a picture comes out dpb_output_delay
after its access unit; Sony's movies have a one frame delay. Without, it
is bumped out of a buffer three frames deep, whatever the stream
declares. sceMpegAvcDecodeStop hands back what's left, and streams with
more than three reference frames are refused with 0x80628002.

We handed each picture back as soon as it was decoded. MGS Peace
Walker's movie player only checks for the end of a movie while its
clock is short of the movie's length, so its last picture landed right
on that limit and the game hung on a black screen after the cutscene.

ffmpeg still decodes the pixels, in display order; each access unit is
tagged with its index so frames can be matched back, and ffmpeg's H.264
parser supplies the picture timing. A call can now hand back several
pictures, as consecutive 0x2C byte descriptors the way mpeg.prx's
sceMpegAvcDecodeStop reads them, each in a frame buffer slot of its own
(reallocated when a savestate brings a block too small for that).

Not followed: with B-frames and no picture timing, the PSP can complete
several pictures in one call; here they come out one a call. All of
the game movies checked have picture timing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 11:25:55 +02:00

127 lines
4.6 KiB
C++

// Copyright (c) 2026- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include <deque>
#include <map>
#include <vector>
#include "Common/CommonTypes.h"
struct AVCodecContext;
struct AVCodecParserContext;
struct AVFrame;
// One decoded picture: three 8-bit 4:2:0 planes (Y, Cb, Cr). Strides are in bytes and are not
// necessarily the width.
struct AvcPicture {
const u8 *planes[3]{};
int strides[3]{};
int width = 0;
int height = 0;
};
// An H.264 decoder for sceVideocodec, which is a low level interface to the PSP Media Engine.
// PPSSPP's MediaEngine can't serve that - it is built around sceMpeg's own model, with the
// PSMF demuxer, the ringbuffer and its own frame pacing wrapped around the decoder, so this
// is a second path in addition to that. Implementing the old path around this might happen later.
//
// There are two ways in. Decode is "hand it one access unit, get a frame back when ffmpeg has
// one". DecodePictures hands pictures back when the PSP's decoder would - see AvcDecoder.cpp.
class AvcDecoder {
public:
AvcDecoder();
~AvcDecoder();
// Not copyable.
AvcDecoder(const AvcDecoder &) = delete;
AvcDecoder &operator=(const AvcDecoder &) = delete;
// Decodes one access unit. Returns true when a frame came out - H.264 reorders, so a frame
// is not produced for every unit fed in, and that is not an error.
bool Decode(const u8 *data, int size);
// Decodes one access unit and returns how many pictures the PSP's decoder hands back for it,
// which Pictures() then holds - none, one, or several. -1 for a stream the PSP's decoder
// refuses.
int DecodePictures(const u8 *data, int size);
// The end of the stream: hands back everything the decoder still holds, as stopping the PSP's
// decoder does.
int StopPictures();
// The pictures of the last DecodePictures or StopPictures call, in output order. Valid until the
// next call.
const std::vector<AvcPicture> &Pictures() const { return pictures_; }
// Throws away any decoder state, for a seek or a new stream.
void Flush();
bool HasFrame() const { return haveFrame_; }
int Width() const { return width_; }
int Height() const { return height_; }
// Only meaningful while HasFrame(). Planes are Y, Cb, Cr; strides are in bytes and are not
// necessarily the width.
const u8 *Plane(int index) const;
int Stride(int index) const;
// True if we were built without ffmpeg, in which case nothing decodes.
static bool IsAvailable();
private:
// Feeds one packet to ffmpeg (null to drain at the end), and queues every frame that comes out
// under its access unit's index.
void FeedFfmpeg(const u8 *data, int size, int index);
// Checks a frame ffmpeg returned, and says whether it can be used.
bool AcceptFrame(AVFrame *frame);
// Hands back the frame at the front of the queue.
void Release();
void ClearPictures();
AVCodecContext *codecCtx_ = nullptr;
AVFrame *frame_ = nullptr;
// The access unit, copied so we can pad it - ffmpeg reads a little past the end of a packet.
// Kept between calls so the usual case doesn't allocate.
std::vector<u8> packetBuf_;
bool haveFrame_ = false;
// For reporting runs of concealed-error frames rather than one line each - see Decode.
bool wasConcealing_ = false;
int concealedFrames_ = 0;
int frameCount_ = 0;
int width_ = 0;
int height_ = 0;
// DecodePictures. The parser reads the picture timing SEI; it gets a context of its own so it
// can't change the decoder's.
AVCodecParserContext *parser_ = nullptr;
AVCodecContext *parserCtx_ = nullptr;
struct Decoded {
int index;
AVFrame *frame;
};
// What ffmpeg has decoded and not been handed back yet, in output order.
std::deque<Decoded> decoded_;
// When each access unit's picture is due, in clock ticks, for streams with picture timing.
std::map<int, s64> outputTicks_;
s64 bufferingTick_ = 0;
bool firstTiming_ = true;
int nextIndex_ = 0;
int released_ = 0;
std::vector<AVFrame *> outFrames_;
std::vector<AvcPicture> pictures_;
};