mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-22 20:36:22 +02:00
sceVideocodec is the interface the Media Engine really exposes: the caller owns the buffers and hands over one access unit at a time. MediaEngine can't serve that shape - 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, separate path rather than a refactor of code every game that plays video depends on. Merging the two is worth doing once sceVideocodec has earned its keep. Nothing calls it yet; sceVideocodec is the next piece. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
188 lines
4.9 KiB
C++
188 lines
4.9 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/.
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#include "Common/Log.h"
|
|
#include "Core/HW/AvcDecoder.h"
|
|
|
|
#ifdef USE_FFMPEG
|
|
extern "C" {
|
|
#include "libavcodec/avcodec.h"
|
|
#include "libavutil/imgutils.h"
|
|
}
|
|
#include "Core/FFMPEGCompat.h"
|
|
#endif // USE_FFMPEG
|
|
|
|
AvcDecoder::AvcDecoder() {
|
|
#ifdef USE_FFMPEG
|
|
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
|
if (!codec) {
|
|
ERROR_LOG(Log::ME, "AvcDecoder: no H.264 decoder in this ffmpeg build");
|
|
return;
|
|
}
|
|
codecCtx_ = avcodec_alloc_context3(codec);
|
|
if (!codecCtx_) {
|
|
ERROR_LOG(Log::ME, "AvcDecoder: couldn't allocate a codec context");
|
|
return;
|
|
}
|
|
// The PSP hands us whole access units, so no parser is needed and no truncation is expected.
|
|
codecCtx_->flags = 0;
|
|
codecCtx_->flags2 = 0;
|
|
if (avcodec_open2(codecCtx_, codec, nullptr) < 0) {
|
|
ERROR_LOG(Log::ME, "AvcDecoder: couldn't open the H.264 decoder");
|
|
avcodec_free_context(&codecCtx_);
|
|
codecCtx_ = nullptr;
|
|
return;
|
|
}
|
|
frame_ = av_frame_alloc();
|
|
if (!frame_) {
|
|
avcodec_free_context(&codecCtx_);
|
|
codecCtx_ = nullptr;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
AvcDecoder::~AvcDecoder() {
|
|
#ifdef USE_FFMPEG
|
|
if (frame_) {
|
|
av_frame_free(&frame_);
|
|
}
|
|
if (codecCtx_) {
|
|
avcodec_free_context(&codecCtx_);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool AvcDecoder::IsAvailable() {
|
|
#ifdef USE_FFMPEG
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void AvcDecoder::Flush() {
|
|
haveFrame_ = false;
|
|
#ifdef USE_FFMPEG
|
|
if (codecCtx_) {
|
|
avcodec_flush_buffers(codecCtx_);
|
|
}
|
|
if (frame_) {
|
|
av_frame_unref(frame_);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool AvcDecoder::Decode(const u8 *data, int size) {
|
|
haveFrame_ = false;
|
|
#ifdef USE_FFMPEG
|
|
if (!codecCtx_ || !frame_ || !data || size <= 0) {
|
|
return false;
|
|
}
|
|
|
|
// ffmpeg reads a little past the end of a packet, so it gets a padded copy rather than the
|
|
// caller's buffer. A plain stack AVPacket, like the rest of the emulator uses - the heap
|
|
// packet API isn't in the ffmpeg headers every target builds against.
|
|
packetBuf_.resize((size_t)size + AV_INPUT_BUFFER_PADDING_SIZE);
|
|
memcpy(packetBuf_.data(), data, size);
|
|
memset(packetBuf_.data() + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
|
|
|
AVPacket packet;
|
|
av_init_packet(&packet);
|
|
packet.data = packetBuf_.data();
|
|
packet.size = size;
|
|
|
|
av_frame_unref(frame_);
|
|
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
int err = avcodec_send_packet(codecCtx_, &packet);
|
|
if (err == AVERROR(EAGAIN)) {
|
|
// The decoder has frames waiting and won't take more input until one is taken out. It did
|
|
// not consume the packet, so drain a frame and hand it the same packet again rather than
|
|
// dropping the access unit.
|
|
err = avcodec_receive_frame(codecCtx_, frame_);
|
|
if (err < 0) {
|
|
WARN_LOG(Log::ME, "AvcDecoder: send_packet wanted a frame out but none came (%d)", err);
|
|
return false;
|
|
}
|
|
const int resend = avcodec_send_packet(codecCtx_, &packet);
|
|
if (resend < 0) {
|
|
WARN_LOG(Log::ME, "AvcDecoder: send_packet failed on retry (%d)", resend);
|
|
return false;
|
|
}
|
|
// A frame is already in hand from the drain above - fall through to the format check.
|
|
} else {
|
|
if (err < 0) {
|
|
WARN_LOG(Log::ME, "AvcDecoder: send_packet failed (%d)", err);
|
|
return false;
|
|
}
|
|
|
|
err = avcodec_receive_frame(codecCtx_, frame_);
|
|
if (err == AVERROR(EAGAIN)) {
|
|
// Normal: H.264 reorders, so the decoder wants more input before it gives a frame back.
|
|
return false;
|
|
}
|
|
if (err < 0) {
|
|
WARN_LOG(Log::ME, "AvcDecoder: receive_frame failed (%d)", err);
|
|
return false;
|
|
}
|
|
}
|
|
#else
|
|
int gotFrame = 0;
|
|
int err = avcodec_decode_video2(codecCtx_, frame_, &gotFrame, &packet);
|
|
if (err < 0) {
|
|
WARN_LOG(Log::ME, "AvcDecoder: decode_video2 failed (%d)", err);
|
|
return false;
|
|
}
|
|
if (!gotFrame) {
|
|
// Normal: H.264 reorders, so the decoder wants more input before it gives a frame back.
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
width_ = frame_->width;
|
|
height_ = frame_->height;
|
|
haveFrame_ = width_ > 0 && height_ > 0;
|
|
return haveFrame_;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
const u8 *AvcDecoder::Plane(int index) const {
|
|
#ifdef USE_FFMPEG
|
|
if (!haveFrame_ || !frame_ || index < 0 || index >= 3) {
|
|
return nullptr;
|
|
}
|
|
return frame_->data[index];
|
|
#else
|
|
return nullptr;
|
|
#endif
|
|
}
|
|
|
|
int AvcDecoder::Stride(int index) const {
|
|
#ifdef USE_FFMPEG
|
|
if (!haveFrame_ || !frame_ || index < 0 || index >= 3) {
|
|
return 0;
|
|
}
|
|
return frame_->linesize[index];
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|