diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9d22e2118c..a9b8b7f232 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1651,8 +1651,6 @@ list(APPEND NativeAppSource
UI/CustomButtonMappingScreen.cpp
UI/Theme.h
UI/Theme.cpp
- UI/VideoPlayer.h
- UI/VideoPlayer.cpp
UI/UIAtlas.h
UI/UIAtlas.cpp
UI/RetroAchievementScreens.cpp
diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp
index d558bc6b95..b04eddb62a 100644
--- a/Common/GPU/Vulkan/thin3d_vulkan.cpp
+++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp
@@ -802,6 +802,10 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanBarrierBatch *postBarriers, Vu
return false;
}
_dbg_assert_(pushBuffer);
+ _dbg_assert_(desc.tag != nullptr);
+ _dbg_assert_(desc.mipLevels > 0);
+ _dbg_assert_(desc.format != DataFormat::UNDEFINED);
+ // _dbg_assert_(desc.type == TextureType::LINEAR2D);
format_ = desc.format;
mipLevels_ = desc.mipLevels;
width_ = desc.width;
diff --git a/Common/Render/DrawBuffer.h b/Common/Render/DrawBuffer.h
index 1b1067a9de..7cb5028f6f 100644
--- a/Common/Render/DrawBuffer.h
+++ b/Common/Render/DrawBuffer.h
@@ -124,6 +124,7 @@ public:
}
// Results in 18 triangles. Kind of expensive for a button.
void DrawImage4Grid(ImageID atlas_image, float x1, float y1, float x2, float y2, Color color = COLOR(0xFFFFFF), float corner_scale = 1.0);
+
// This is only 6 triangles, much cheaper.
void DrawImage2GridH(ImageID atlas_image, float x1, float y1, float x2, Color color = COLOR(0xFFFFFF), float scale = 1.0);
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index 5a007cbdb5..9a5aef32ec 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -932,6 +932,7 @@
AnySuitable
+
@@ -1266,6 +1267,7 @@
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index b6dcbc79b5..2cd01b7853 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -1387,6 +1387,9 @@
Core
+
+ Util
+
@@ -2244,6 +2247,9 @@
Core
+
+ Util
+
diff --git a/Core/Util/VideoPlayer.cpp b/Core/Util/VideoPlayer.cpp
new file mode 100644
index 0000000000..87444ed89e
--- /dev/null
+++ b/Core/Util/VideoPlayer.cpp
@@ -0,0 +1,253 @@
+#include
+#include
+
+#include "VideoPlayer.h"
+
+#ifdef USE_FFMPEG
+
+extern "C" {
+#include "libavformat/avformat.h"
+#include "libavutil/samplefmt.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/version.h"
+#include "libswscale/swscale.h"
+#include "libavcodec/avcodec.h"
+#include "libavutil/time.h" // For av_gettime and av_usleep
+}
+
+#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(57, 33, 100)
+
+#define HAVE_VIDEO_PLAYER
+
+#endif
+
+#endif
+
+#ifdef HAVE_VIDEO_PLAYER
+
+#include "Common/Log.h"
+#include "Core/FFMPEGCompat.h"
+
+bool pmf_player_available() {
+ return true;
+}
+
+struct MemBuffer {
+ const uint8_t* ptr;
+ size_t size;
+ size_t pos;
+};
+
+struct PMFPlayer {
+ AVFormatContext* fmt_ctx;
+ AVCodecContext* video_ctx;
+ struct SwsContext* sws_ctx;
+ AVFrame* frame;
+ AVFrame* rgb_frame;
+ uint8_t* avio_ctx_buffer;
+ AVIOContext* avio_ctx;
+ MemBuffer* mem_stream;
+ int video_stream_idx;
+ int audio_stream_idx;
+ double last_pts;
+ int64_t frame_count; // Fallback for missing PTS
+};
+
+// --- Custom IO Callbacks ---
+
+static int read_packet(void* opaque, uint8_t* buf, int buf_size) {
+ MemBuffer* mb = static_cast(opaque);
+ int remaining = static_cast(mb->size - mb->pos);
+ if (remaining <= 0) return AVERROR_EOF;
+
+ int to_read = (buf_size < remaining) ? buf_size : remaining;
+ std::memcpy(buf, mb->ptr + mb->pos, to_read);
+ mb->pos += to_read;
+ return to_read;
+}
+
+static int64_t seek_packet(void* opaque, int64_t offset, int whence) {
+ MemBuffer* mb = static_cast(opaque);
+ if (whence == AVSEEK_SIZE) return mb->size;
+
+ int64_t new_pos = mb->pos;
+ if (whence == SEEK_SET) new_pos = offset;
+ else if (whence == SEEK_CUR) new_pos += offset;
+ else if (whence == SEEK_END) new_pos = mb->size + offset;
+
+ if (new_pos < 0 || new_pos > static_cast(mb->size)) return -1;
+ mb->pos = static_cast(new_pos);
+ return mb->pos;
+}
+
+// --- Core API ---
+
+PMFPlayer *pmf_create() {
+ return new PMFPlayer();
+}
+
+void pmf_destroy(PMFPlayer* ps) {
+ if (ps) {
+ pmf_deinit(ps);
+ delete ps;
+ }
+}
+
+int pmf_init(PMFPlayer* ps, const uint8_t* data, size_t size, int* out_w, int* out_h) {
+ av_register_all();
+ std::memset(ps, 0, sizeof(PMFPlayer));
+ ps->audio_stream_idx = -1;
+ ps->video_stream_idx = -1;
+
+ // Safety Check: Verify PSMF Magic (0x50 0x53 0x4D 0x46)
+ if (size < 24 || std::memcmp(data, "PSMF", 4) != 0) {
+ return -1;
+ }
+
+ const int PMF_HEADER_SIZE = 20;
+ ps->mem_stream = static_cast(av_malloc(sizeof(MemBuffer)));
+ ps->mem_stream->ptr = data + PMF_HEADER_SIZE;
+ ps->mem_stream->size = size - PMF_HEADER_SIZE;
+ ps->mem_stream->pos = 0;
+
+ const int io_buffer_size = 32768;
+ ps->avio_ctx_buffer = static_cast(av_malloc(io_buffer_size));
+ ps->avio_ctx = avio_alloc_context(ps->avio_ctx_buffer, io_buffer_size, 0,
+ ps->mem_stream, &read_packet, nullptr, &seek_packet);
+
+ ps->fmt_ctx = avformat_alloc_context();
+ ps->fmt_ctx->pb = ps->avio_ctx;
+
+ if (avformat_open_input(&ps->fmt_ctx, nullptr, nullptr, nullptr) < 0) return -1;
+ if (avformat_find_stream_info(ps->fmt_ctx, nullptr) < 0) return -1;
+
+ ps->video_stream_idx = -1;
+ for (unsigned int i = 0; i < ps->fmt_ctx->nb_streams; i++) {
+ if (ps->fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && ps->video_stream_idx == -1) {
+ ps->video_stream_idx = i;
+ }
+ if (ps->fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && ps->audio_stream_idx == -1) {
+ ps->audio_stream_idx = i;
+
+ INFO_LOG(Log::System, "Audio stream found in PMF: stream index %d", i);
+ }
+ }
+ if (ps->video_stream_idx == -1) {
+ WARN_LOG(Log::System, "Video stream not found in PMF");
+ return -1;
+ }
+
+ ps->video_ctx = ps->fmt_ctx->streams[ps->video_stream_idx]->codec;
+ AVCodec* codec = avcodec_find_decoder(ps->video_ctx->codec_id);
+ if (!codec || avcodec_open2(ps->video_ctx, codec, nullptr) < 0) return -1;
+
+ ps->frame = av_frame_alloc();
+ ps->rgb_frame = av_frame_alloc();
+ ps->last_pts = -1.0;
+
+ *out_w = ps->video_ctx->width;
+ *out_h = ps->video_ctx->height;
+
+ ps->sws_ctx = sws_getContext(ps->video_ctx->width, ps->video_ctx->height, ps->video_ctx->pix_fmt,
+ ps->video_ctx->width, ps->video_ctx->height, AV_PIX_FMT_RGBA,
+ SWS_BILINEAR, nullptr, nullptr, nullptr);
+ return 0;
+}
+
+bool pmf_update(PMFPlayer* ps, double current_time_seconds, uint8_t* user_rgba_buffer) {
+ bool bufferWritten = false;
+ AVPacket pkt;
+ int got_frame = 0;
+ AVStream* st = ps->fmt_ctx->streams[ps->video_stream_idx];
+
+ // Calculate duration for broken PTS files
+ double frame_rate = av_q2d(st->avg_frame_rate);
+ double frame_duration = (frame_rate > 0) ? (1.0 / frame_rate) : 0.033;
+
+ // Don't decode if the UI clock hasn't caught up to our last frame yet
+ if (ps->last_pts >= current_time_seconds && ps->last_pts >= 0) return false;
+
+ while (av_read_frame(ps->fmt_ctx, &pkt) >= 0) {
+ if (pkt.stream_index == ps->video_stream_idx) {
+ if (avcodec_decode_video2(ps->video_ctx, ps->frame, &got_frame, &pkt) >= 0 && got_frame) {
+ double pts = 0;
+ if (false && ps->frame->pkt_pts != AV_NOPTS_VALUE) {
+ pts = static_cast(ps->frame->pkt_pts) * av_q2d(st->time_base);
+ } else {
+ // Fallback (or actually, more reliable solution): Use frame count * duration
+ pts = static_cast(ps->frame_count) * frame_duration;
+ }
+ ps->last_pts = pts;
+ ps->frame_count++;
+
+ // If we've reached the point in the stream the UI requested
+ if (pts >= current_time_seconds) {
+ // Wrap the user's RGBA32 buffer for the scaler
+ uint8_t* dest_data[4] = {user_rgba_buffer, nullptr, nullptr, nullptr};
+ int dest_linesize[4] = {ps->video_ctx->width * 4, 0, 0, 0};
+
+ sws_scale(ps->sws_ctx, ps->frame->data, ps->frame->linesize, 0,
+ ps->video_ctx->height, dest_data, dest_linesize);
+
+ av_packet_unref(&pkt);
+ return true;
+ }
+ }
+ }
+ av_packet_unref(&pkt);
+ }
+
+ // EOF Reached: Seek to start and reset local counters
+ av_seek_frame(ps->fmt_ctx, ps->video_stream_idx, 0, AVSEEK_FLAG_BACKWARD);
+ avcodec_flush_buffers(ps->video_ctx);
+ ps->last_pts = -1.0;
+ return false;
+}
+
+void pmf_deinit(PMFPlayer* ps) {
+ if (!ps) return;
+
+ if (ps->video_ctx) {
+ avcodec_close(ps->video_ctx);
+ ps->video_ctx = nullptr;
+ }
+
+ if (ps->fmt_ctx) {
+ // avformat_close_input will free the AVIOContext and the
+ // avio_ctx_buffer because it owns them.
+ avformat_close_input(&ps->fmt_ctx);
+ ps->fmt_ctx = nullptr;
+
+ // Note: Do NOT free ps->avio_ctx_buffer or ps->avio_ctx here.
+ }
+
+ // We still own the MemBuffer struct (the opaque pointer),
+ // so we free that last.
+ if (ps->mem_stream) {
+ av_free(ps->mem_stream);
+ ps->mem_stream = nullptr;
+ }
+
+ if (ps->sws_ctx) {
+ sws_freeContext(ps->sws_ctx);
+ ps->sws_ctx = nullptr;
+ }
+
+ if (ps->frame) {
+ av_frame_free(&ps->frame);
+ ps->frame = nullptr;
+ }
+}
+
+#else
+
+bool pmf_player_available() { return false; }
+
+PMFPlayer *pmf_create() { return nullptr; }
+void pmf_destroy(PMFPlayer* ps) {}
+
+int pmf_init(PMFPlayer* ps, const uint8_t* data, size_t size, int* out_w, int* out_h) { return -1; }
+bool pmf_update(PMFPlayer* ps, double current_time_seconds, uint8_t* user_rgb_buffer) { return false; }
+void pmf_deinit(PMFPlayer* ps) {}
+
+#endif
diff --git a/Core/Util/VideoPlayer.h b/Core/Util/VideoPlayer.h
new file mode 100644
index 0000000000..5f0a28b894
--- /dev/null
+++ b/Core/Util/VideoPlayer.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include
+
+struct PMFPlayer;
+
+// Currently only supported with built-in FFMPEG.
+bool pmf_player_available();
+
+PMFPlayer *pmf_create();
+void pmf_destroy(PMFPlayer* ps);
+
+int pmf_init(PMFPlayer* ps, const uint8_t* data, size_t size, int* out_w, int* out_h);
+bool pmf_update(PMFPlayer* ps, double current_time_seconds, uint8_t* user_rgb_buffer);
+void pmf_deinit(PMFPlayer* ps);
diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp
index 6b55006961..09d2f8af62 100644
--- a/UI/GameInfoCache.cpp
+++ b/UI/GameInfoCache.cpp
@@ -636,6 +636,15 @@ public:
info_->icon.dataLoaded = true;
}
+ if (flags_ & GameInfoFlags::ICON1_PMF) {
+ if (pbp.GetSubFileSize(PBP_ICON1_PMF) > 0) {
+ std::string data;
+ pbp.GetSubFileAsString(PBP_ICON1_PMF, &data);
+ std::lock_guard lock(info_->lock);
+ info_->icon1pmf = std::move(data);
+ }
+ }
+
if (flags_ & GameInfoFlags::PIC0) {
if (pbp.GetSubFileSize(PBP_PIC0_PNG) > 0) {
std::string data;
@@ -769,6 +778,9 @@ handleELF:
ReadFileToString(&umd, "/PSP_GAME/ICON0.PNG", &info_->icon.data, &info_->lock);
info_->icon.dataLoaded = true;
}
+ if (flags_ & GameInfoFlags::ICON1_PMF) {
+ ReadFileToString(&umd, "/PSP_GAME/ICON1.PMF", &info_->icon1pmf, &info_->lock);
+ }
if (flags_ & GameInfoFlags::PIC0) {
ReadFileToString(&umd, "/PSP_GAME/PIC0.PNG", &info_->pic0.data, &info_->lock);
info_->pic0.dataLoaded = true;
@@ -832,6 +844,10 @@ handleELF:
info_->pic0.dataLoaded = ReadFileToString(&umd, join(gameRoot, "PIC0.PNG"), &info_->pic0.data, &info_->lock);
}
+ if (flags_ & GameInfoFlags::ICON1_PMF) {
+ ReadFileToString(&umd, join(gameRoot, "ICON1.PMF"), &info_->icon1pmf, &info_->lock);
+ }
+
if (flags_ & GameInfoFlags::PIC1) {
info_->pic1.dataLoaded = ReadFileToString(&umd, join(gameRoot, "PIC1.PNG"), &info_->pic1.data, &info_->lock);
}
diff --git a/UI/GameInfoCache.h b/UI/GameInfoCache.h
index 362ecfd1e8..23b9b2d8ec 100644
--- a/UI/GameInfoCache.h
+++ b/UI/GameInfoCache.h
@@ -49,6 +49,7 @@ enum class GameInfoFlags {
SIZE = 0x40,
UNCOMPRESSED_SIZE = 0x80,
SAVEDATA_SIZE = 0x100,
+ ICON1_PMF = 0x200,
};
ENUM_CLASS_BITOPS(GameInfoFlags);
@@ -157,6 +158,7 @@ public:
GameInfoTex icon;
GameInfoTex pic0;
GameInfoTex pic1;
+ std::string icon1pmf;
std::string sndFileData;
std::atomic sndDataLoaded{};
diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp
index f1e7cd1d27..e1e17c46bd 100644
--- a/UI/GameScreen.cpp
+++ b/UI/GameScreen.cpp
@@ -42,6 +42,7 @@
#include "Core/Util/GameDB.h"
#include "Core/Util/RecentFiles.h"
#include "Core/Util/PathUtil.h"
+#include "Core/Util/VideoPlayer.h"
#include "UI/OnScreenDisplay.h"
#include "UI/Background.h"
#include "UI/CwCheatScreen.h"
@@ -56,7 +57,95 @@
#include "UI/SavedataScreen.h"
#include "UI/MiscViews.h"
-constexpr GameInfoFlags g_desiredFlags = GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::PIC0 | GameInfoFlags::PIC1 | GameInfoFlags::UNCOMPRESSED_SIZE | GameInfoFlags::SIZE | GameInfoFlags::SAVEDATA_SIZE;
+constexpr GameInfoFlags g_desiredFlags = GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::PIC0 | GameInfoFlags::PIC1 | GameInfoFlags::ICON1_PMF | GameInfoFlags::UNCOMPRESSED_SIZE | GameInfoFlags::SIZE | GameInfoFlags::SAVEDATA_SIZE;
+
+class PMFView : public UI::InertView {
+public:
+ PMFView(std::string data, UI::LayoutParams *params) : UI::InertView(params), data_(data) {
+ startTime_ = Instant::Now();
+ player_ = pmf_create();
+ if (0 == pmf_init(player_, (const uint8_t *)data_.data(), data_.size(), &width_, &height_)) {
+
+ } else {
+ ERROR_LOG(Log::G3D, "Couldn't load pmf");
+ pmf_destroy(player_);
+ player_ = nullptr;
+ }
+ }
+
+ ~PMFView() {
+ if (curFrame_) {
+ curFrame_->Release();
+ curFrame_ = nullptr;
+ }
+
+ if (player_) {
+ pmf_destroy(player_);
+ player_ = nullptr;
+ }
+ }
+
+ void DeviceLost() override {
+ if (curFrame_) {
+ curFrame_->Release();
+ curFrame_ = nullptr;
+ }
+ }
+
+ void GetContentDimensions(const UIContext &dc, float &w, float &h) const override {
+ // Just use the video size.
+ w = width_;
+ h = height_;
+ }
+
+ void Draw(UIContext &dc) override {
+ if (!player_) {
+ return;
+ }
+
+ Draw::DrawContext *draw = dc.GetDrawContext();
+
+ std::vector frame(width_ * height_ * 4);
+ if (pmf_update(player_, startTime_.ElapsedSeconds(), frame.data())) {
+ if (curFrame_) {
+ curFrame_->Release();
+ curFrame_ = nullptr;
+ }
+
+ Draw::TextureDesc desc{};
+ desc.width = width_;
+ desc.height = height_;
+ desc.depth = 1;
+ desc.format = Draw::DataFormat::R8G8B8A8_UNORM;
+ desc.generateMips = false;
+ desc.tag = "pmf_frame";
+ desc.type = Draw::TextureType::LINEAR2D;
+ desc.mipLevels = 1;
+ desc.initData.push_back(frame.data());
+
+ curFrame_ = draw->CreateTexture(desc);
+ }
+
+ if (curFrame_) {
+ int dropsize = 5;
+ dc.Draw()->DrawImage4Grid(dc.GetTheme().dropShadow4Grid, bounds_.x - dropsize * 1.5f, bounds_.y - dropsize * 1.5f, bounds_.x2() + dropsize * 1.5f, bounds_.y2() + dropsize * 1.5f, 0xFF000000, 1.0f);
+ dc.Flush();
+ draw->BindTexture(0, curFrame_);
+ dc.Draw()->DrawTexRect(bounds_, 0, 0, 1.0f, 1.0f, 0xFFFFFFFF);
+ dc.Flush();
+ }
+
+ dc.RebindTexture();
+ }
+
+private:
+ std::string data_;
+ Draw::Texture *curFrame_ = nullptr;
+ PMFPlayer *player_ = nullptr;
+ Instant startTime_;
+ int width_ = 0;
+ int height_ = 0;
+};
GameScreen::GameScreen(const Path &gamePath, bool inGame) : UITwoPaneBaseDialogScreen(gamePath, TwoPaneFlags::SettingsToTheRight | TwoPaneFlags::CustomContextMenu), inGame_(inGame) {
g_BackgroundAudio.SetGame(gamePath);
@@ -92,8 +181,13 @@ void GameScreen::update() {
bool recreate = false;
if (knownFlags_ != hasFlags) {
+ bool justAudio = ((u32)knownFlags_ ^ (u32)hasFlags) == (u32)GameInfoFlags::SND;
+
knownFlags_ = hasFlags;
- recreate = true;
+ // don't need to recreate if the audio loaded.
+ if (!justAudio) {
+ recreate = true;
+ }
}
// Has the user requested a CRC32?
@@ -130,6 +224,7 @@ static bool FileTypeHasIcon(IdentifiedFileType fileType) {
case IdentifiedFileType::PSP_ISO_NP:
case IdentifiedFileType::PSP_ISO:
case IdentifiedFileType::PSP_UMD_VIDEO_ISO:
+ case IdentifiedFileType::PSP_DISC_DIRECTORY:
return true;
default:
return false;
@@ -191,7 +286,7 @@ void GameScreen::CreateContentViews(UI::ViewGroup *parent) {
mainGameInfo = new LinearLayout(ORIENT_VERTICAL);
leftColumn->Add(new Spacer(8.0f));
if (fileTypeHasIcon && !(info_->icon.dataLoaded && info_->icon.data.empty())) {
- leftColumn->Add(new GameImageView(gamePath_, GameInfoFlags::ICON, 2.0f, new LinearLayoutParams(UI::Margins(0))));
+ leftColumn->Add(new GameImageView(gamePath_, GameInfoFlags::ICON, 2.0f, new LinearLayoutParams(144 * 2, 80 * 2, UI::Margins(0))));
}
leftColumn->Add(mainGameInfo);
} else {
@@ -205,6 +300,18 @@ void GameScreen::CreateContentViews(UI::ViewGroup *parent) {
}
mainGameInfo->SetSpacing(3.0f);
+ if (pmf_player_available() && fileTypeHasIcon && (knownFlags_ & GameInfoFlags::ICON1_PMF) && !info_->icon1pmf.empty()) {
+ std::string pmfData;
+ {
+ std::lock_guard guard(info_->lock);
+ pmfData = info_->icon1pmf;
+ }
+
+ if (!pmfData.empty()) {
+ leftColumn->Add(new PMFView(pmfData, new LinearLayoutParams(144 * 2, 80 * 2, UI::Margins(0, 10))));
+ }
+ }
+
std::vector dbInfos;
const bool inGameDB = g_gameDB.GetGameInfos(info_->id_version, &dbInfos);
diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj
index 79a7ab8ae0..e199b21376 100644
--- a/UI/UI.vcxproj
+++ b/UI/UI.vcxproj
@@ -80,7 +80,6 @@
-
@@ -134,7 +133,6 @@
-
diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters
index 7d3a0b2814..fc871858a6 100644
--- a/UI/UI.vcxproj.filters
+++ b/UI/UI.vcxproj.filters
@@ -144,7 +144,6 @@
Screens
-
@@ -286,7 +285,6 @@
Screens
-
diff --git a/UI/VideoPlayer.cpp b/UI/VideoPlayer.cpp
deleted file mode 100644
index f467a2a2d5..0000000000
--- a/UI/VideoPlayer.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-
-extern "C" {
-#include "libavformat/avformat.h"
-#include "libavutil/samplefmt.h"
-#include "libavutil/imgutils.h"
-#include "libavutil/version.h"
-#include "libswscale/swscale.h"
-#include "libavcodec/avcodec.h"
-#include "libavutil/time.h" // For av_gettime and av_usleep
-
-}
-#include "Core/FFMPEGCompat.h"
-
-typedef struct {
- AVFormatContext *fmt_ctx;
- AVCodecContext *video_ctx;
- struct SwsContext *sws_ctx;
- AVFrame *frame;
- AVFrame *rgb_frame;
- uint8_t *rgb_buffer;
- int video_stream_idx;
- double last_pts; // Seconds
-} PMFPlayer;
-
-// User-defined callback for the UI
-void display_pixels(uint8_t *data, int linesize, int width, int height);
-
-int pmf_init(PMFPlayer *ps, const char *filename) {
- av_register_all();
- memset(ps, 0, sizeof(PMFPlayer));
-
- // Open file and skip Sony PMF header (typically 20 bytes)
- AVDictionary *options = NULL;
- av_dict_set(&options, "skip_initial_bytes", "20", 0);
-
- if (avformat_open_input(&ps->fmt_ctx, filename, NULL, &options) < 0) {
- return -1;
- }
- av_dict_free(&options);
-
- if (avformat_find_stream_info(ps->fmt_ctx, NULL) < 0) return -1;
-
- // Find video stream
- ps->video_stream_idx = -1;
- for (int i = 0; i < ps->fmt_ctx->nb_streams; i++) {
- if (ps->fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
- ps->video_stream_idx = i;
- break;
- }
- }
- if (ps->video_stream_idx == -1) return -1;
-
- // Setup Decoder
- ps->video_ctx = ps->fmt_ctx->streams[ps->video_stream_idx]->codec;
- AVCodec *codec = avcodec_find_decoder(ps->video_ctx->codec_id);
- if (!codec || avcodec_open2(ps->video_ctx, codec, NULL) < 0) return -1;
-
- // Allocate Frames
- ps->frame = av_frame_alloc();
- ps->rgb_frame = av_frame_alloc();
-
- int size = avpicture_get_size(AV_PIX_FMT_RGB24, ps->video_ctx->width, ps->video_ctx->height);
- ps->rgb_buffer = (uint8_t *)av_malloc(size);
- avpicture_fill((AVPicture *)ps->rgb_frame, ps->rgb_buffer, AV_PIX_FMT_RGB24,
- ps->video_ctx->width, ps->video_ctx->height);
-
- ps->sws_ctx = sws_getContext(ps->video_ctx->width, ps->video_ctx->height, ps->video_ctx->pix_fmt,
- ps->video_ctx->width, ps->video_ctx->height, AV_PIX_FMT_RGB24,
- SWS_BILINEAR, NULL, NULL, NULL);
- ps->last_pts = -1.0;
- return 0;
-}
-
-void pmf_update(PMFPlayer *ps, double current_time_seconds) {
- AVPacket pkt;
- int got_frame = 0;
- AVStream *st = ps->fmt_ctx->streams[ps->video_stream_idx];
-
- // If the UI clock is still behind our last decoded frame, do nothing
- if (current_time_seconds <= ps->last_pts && ps->last_pts >= 0) return;
-
- while (av_read_frame(ps->fmt_ctx, &pkt) >= 0) {
- if (pkt.stream_index == ps->video_stream_idx) {
- avcodec_decode_video2(ps->video_ctx, ps->frame, &got_frame, &pkt);
-
- if (got_frame) {
- double pts = (double)ps->frame->pkt_pts * av_q2d(st->time_base);
- ps->last_pts = pts;
-
- // Only scale and display if this frame is "due"
- if (pts >= current_time_seconds) {
- sws_scale(ps->sws_ctx, (const uint8_t *const *)ps->frame->data, ps->frame->linesize,
- 0, ps->video_ctx->height, ps->rgb_frame->data, ps->rgb_frame->linesize);
-
- display_pixels(ps->rgb_frame->data[0], ps->rgb_frame->linesize[0], ps->video_ctx->width, ps->video_ctx->height);
-
- av_packet_unref(&pkt);
- break; // We found the frame we needed
- }
- }
- }
- av_packet_unref(&pkt);
- }
-
- // Loop logic: if we hit the end of the file
- if (av_read_frame(ps->fmt_ctx, &pkt) < 0) {
- av_seek_frame(ps->fmt_ctx, ps->video_stream_idx, 0, AVSEEK_FLAG_BACKWARD);
- avcodec_flush_buffers(ps->video_ctx);
- ps->last_pts = -1.0;
- }
-}
-
-void pmf_deinit(PMFPlayer *ps) {
- if (ps->video_ctx) avcodec_close(ps->video_ctx);
- if (ps->fmt_ctx) avformat_close_input(&ps->fmt_ctx);
- if (ps->sws_ctx) sws_freeContext(ps->sws_ctx);
- if (ps->frame) av_frame_free(&ps->frame);
- if (ps->rgb_frame) av_frame_free(&ps->rgb_frame);
- if (ps->rgb_buffer) av_free(ps->rgb_buffer);
-}
diff --git a/UI/VideoPlayer.h b/UI/VideoPlayer.h
deleted file mode 100644
index 6f70f09bee..0000000000
--- a/UI/VideoPlayer.h
+++ /dev/null
@@ -1 +0,0 @@
-#pragma once
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj
index 8fc83213a0..dadb1715b2 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj
+++ b/UWP/CoreUWP/CoreUWP.vcxproj
@@ -318,6 +318,7 @@
+
@@ -624,6 +625,7 @@
+
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters
index f84d43d052..211b5a9410 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj.filters
+++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters
@@ -410,6 +410,7 @@
+
@@ -682,6 +683,7 @@
+
diff --git a/UWP/UI_UWP/UI_UWP.vcxproj.filters b/UWP/UI_UWP/UI_UWP.vcxproj.filters
index 7242f7a7e4..bc90bc3a1c 100644
--- a/UWP/UI_UWP/UI_UWP.vcxproj.filters
+++ b/UWP/UI_UWP/UI_UWP.vcxproj.filters
@@ -277,4 +277,4 @@
{38b8413d-438d-4197-9db8-7bd123fc7431}
-
+
\ No newline at end of file
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index da4af56101..eb3326d33b 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -769,6 +769,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/Util/BlockAllocator.cpp \
$(SRC)/Core/Util/PPGeDraw.cpp \
$(SRC)/Core/Util/RecentFiles.cpp \
+ $(SRC)/Core/Util/VideoPlayer.cpp \
$(SRC)/git-version.cpp
LOCAL_MODULE := ppsspp_core
@@ -947,7 +948,6 @@ LOCAL_SRC_FILES := \
$(SRC)/UI/NativeApp.cpp \
$(SRC)/UI/Theme.cpp \
$(SRC)/UI/UIAtlas.cpp \
- $(SRC)/UI/VideoPlayer.cpp \
$(SRC)/UI/CustomButtonMappingScreen.cpp \
$(SRC)/UI/RetroAchievementScreens.cpp