mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Add AvcDecoder, a plain access-unit-in, frame-out H.264 decoder
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) <[email protected]>
This commit is contained in:
1 parent
7c6f9d18b8
commit
3276cdf65f
9 files changed
+276
No files matched your search
@@ -597,6 +597,8 @@ add_library(Core STATIC
|
||||
HW/Display.h
|
||||
HW/GpioMMIO.cpp
|
||||
HW/GpioMMIO.h
|
||||
HW/AvcDecoder.cpp
|
||||
HW/AvcDecoder.h
|
||||
HW/MediaEngine.cpp
|
||||
HW/MediaEngine.h
|
||||
HW/MpegDemux.cpp
|
||||
|
||||
@@ -672,6 +672,7 @@
|
||||
<ClCompile Include="HLE\sceVaudio.cpp" />
|
||||
<ClCompile Include="HLE\sceVshBridge.cpp" />
|
||||
<ClCompile Include="HLE\__sceAudio.cpp" />
|
||||
<ClCompile Include="HW\AvcDecoder.cpp" />
|
||||
<ClCompile Include="HW\MediaEngine.cpp" />
|
||||
<ClCompile Include="HW\MemoryStick.cpp" />
|
||||
<ClCompile Include="HW\MpegDemux.cpp" />
|
||||
@@ -1189,6 +1190,7 @@
|
||||
<ClInclude Include="HLE\ThreadQueueList.h" />
|
||||
<ClInclude Include="HLE\__sceAudio.h" />
|
||||
<ClInclude Include="HW\BufferQueue.h" />
|
||||
<ClInclude Include="HW\AvcDecoder.h" />
|
||||
<ClInclude Include="HW\MediaEngine.h" />
|
||||
<ClInclude Include="HW\MpegDemux.h" />
|
||||
<ClInclude Include="HW\SasAudio.h" />
|
||||
|
||||
@@ -357,6 +357,9 @@
|
||||
<ClCompile Include="HLE\sceImpose.cpp">
|
||||
<Filter>HLE\Libraries</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HW\AvcDecoder.cpp">
|
||||
<Filter>HW</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HW\MediaEngine.cpp">
|
||||
<Filter>HW</Filter>
|
||||
</ClCompile>
|
||||
@@ -1692,6 +1695,9 @@
|
||||
<ClInclude Include="HLE\sceImpose.h">
|
||||
<Filter>HLE\Libraries</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HW\AvcDecoder.h">
|
||||
<Filter>HW</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HW\MediaEngine.h">
|
||||
<Filter>HW</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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 <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
struct AVCodecContext;
|
||||
struct AVFrame;
|
||||
|
||||
// A plain "hand it one access unit, get a frame back" H.264 decoder.
|
||||
//
|
||||
// This exists for sceVideocodec, which 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
|
||||
// - 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 deliberately 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 proven itself.
|
||||
//
|
||||
// Output is whatever pixel format the decoder produced, normally YUV420P; the caller converts.
|
||||
class AvcDecoder {
|
||||
public:
|
||||
AvcDecoder();
|
||||
~AvcDecoder();
|
||||
|
||||
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);
|
||||
|
||||
// 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:
|
||||
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;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
};
|
||||
@@ -258,6 +258,7 @@
|
||||
<ClInclude Include="..\..\Core\HW\Camera.h" />
|
||||
<ClInclude Include="..\..\Core\HW\Display.h" />
|
||||
<ClInclude Include="..\..\Core\HW\GpioMMIO.h" />
|
||||
<ClInclude Include="..\..\Core\HW\AvcDecoder.h" />
|
||||
<ClInclude Include="..\..\Core\HW\MediaEngine.h" />
|
||||
<ClInclude Include="..\..\Core\HW\MemoryStick.h" />
|
||||
<ClInclude Include="..\..\Core\HW\MpegDemux.h" />
|
||||
@@ -541,6 +542,7 @@
|
||||
<ClCompile Include="..\..\Core\HW\Camera.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\Display.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\GpioMMIO.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\AvcDecoder.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\MediaEngine.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\MemoryStick.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\MpegDemux.cpp" />
|
||||
|
||||
@@ -165,6 +165,7 @@
|
||||
<ClCompile Include="..\..\Core\HW\Camera.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\Display.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\GpioMMIO.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\AvcDecoder.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\MediaEngine.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\MemoryStick.cpp" />
|
||||
<ClCompile Include="..\..\Core\HW\MpegDemux.cpp" />
|
||||
@@ -524,6 +525,7 @@
|
||||
<ClInclude Include="..\..\Core\HLE\sceAudio.h" />
|
||||
<ClInclude Include="..\..\Core\HLE\sceAudiocodec.h" />
|
||||
<ClInclude Include="..\..\Core\HLE\sceMpegbase.h" />
|
||||
<ClInclude Include="..\..\Core\HW\AvcDecoder.h" />
|
||||
<ClInclude Include="..\..\Core\HLE\sceAudioRouting.h" />
|
||||
<ClInclude Include="..\..\Core\HLE\sceCcc.h" />
|
||||
<ClInclude Include="..\..\Core\HLE\sceChnnlsv.h" />
|
||||
|
||||
@@ -614,6 +614,7 @@ EXEC_AND_LIB_FILES := \
|
||||
$(SRC)/Core/HW/GpioMMIO.cpp \
|
||||
$(SRC)/Core/HW/MemoryStick.cpp \
|
||||
$(SRC)/Core/HW/MpegDemux.cpp.arm \
|
||||
$(SRC)/Core/HW/AvcDecoder.cpp.arm \
|
||||
$(SRC)/Core/HW/MediaEngine.cpp.arm \
|
||||
$(SRC)/Core/HW/SasAudio.cpp.arm \
|
||||
$(SRC)/Core/HW/SasReverb.cpp.arm \
|
||||
|
||||
@@ -848,6 +848,7 @@ SOURCES_CXX += \
|
||||
$(COREDIR)/HW/SimpleAudioDec.cpp \
|
||||
$(COREDIR)/HW/Atrac3Standalone.cpp \
|
||||
$(COREDIR)/HW/AsyncIOManager.cpp \
|
||||
$(COREDIR)/HW/AvcDecoder.cpp \
|
||||
$(COREDIR)/HW/MediaEngine.cpp \
|
||||
$(COREDIR)/HW/MpegDemux.cpp \
|
||||
$(COREDIR)/HW/MemoryStick.cpp \
|
||||
|
||||
Reference in new issue
Block a user