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