mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-07-11 01:25:07 +02:00
Fix the gemini-written video player to actually work
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -932,6 +932,7 @@
|
||||
<InlineFunctionExpansion Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">AnySuitable</InlineFunctionExpansion>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Util\RecentFiles.cpp" />
|
||||
<ClCompile Include="Util\VideoPlayer.cpp" />
|
||||
<ClCompile Include="WaveFile.cpp" />
|
||||
<ClCompile Include="WebServer.cpp" />
|
||||
<ClCompile Include="MIPS\x86\X64IRRegCache.cpp" />
|
||||
@@ -1266,6 +1267,7 @@
|
||||
<ClInclude Include="Util\PPGeDraw.h" />
|
||||
<ClInclude Include="..\ext\xxhash.h" />
|
||||
<ClInclude Include="Util\RecentFiles.h" />
|
||||
<ClInclude Include="Util\VideoPlayer.h" />
|
||||
<ClInclude Include="WaveFile.h" />
|
||||
<ClInclude Include="WebServer.h" />
|
||||
<ClInclude Include="MIPS\x86\X64IRRegCache.h" />
|
||||
|
||||
@@ -1387,6 +1387,9 @@
|
||||
<ClCompile Include="SaveStateRewind.cpp">
|
||||
<Filter>Core</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Util\VideoPlayer.cpp">
|
||||
<Filter>Util</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ELF\ElfReader.h">
|
||||
@@ -2244,6 +2247,9 @@
|
||||
<ClInclude Include="SaveStateRewind.h">
|
||||
<Filter>Core</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Util\VideoPlayer.h">
|
||||
<Filter>Util</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE.TXT" />
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
|
||||
#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<MemBuffer*>(opaque);
|
||||
int remaining = static_cast<int>(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<MemBuffer*>(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<int64_t>(mb->size)) return -1;
|
||||
mb->pos = static_cast<size_t>(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<MemBuffer*>(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<uint8_t*>(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<double>(ps->frame->pkt_pts) * av_q2d(st->time_base);
|
||||
} else {
|
||||
// Fallback (or actually, more reliable solution): Use frame count * duration
|
||||
pts = static_cast<double>(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
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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);
|
||||
@@ -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<std::mutex> 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);
|
||||
}
|
||||
|
||||
@@ -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<bool> sndDataLoaded{};
|
||||
|
||||
+110
-3
@@ -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<u8> 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<std::mutex> 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<GameDBInfo> dbInfos;
|
||||
const bool inGameDB = g_gameDB.GetGameInfos(info_->id_version, &dbInfos);
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
<ClCompile Include="Theme.cpp" />
|
||||
<ClCompile Include="UIAtlas.cpp" />
|
||||
<ClCompile Include="UploadScreen.cpp" />
|
||||
<ClCompile Include="VideoPlayer.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AudioCommon.h" />
|
||||
@@ -134,7 +133,6 @@
|
||||
<ClInclude Include="Theme.h" />
|
||||
<ClInclude Include="UIAtlas.h" />
|
||||
<ClInclude Include="UploadScreen.h" />
|
||||
<ClInclude Include="VideoPlayer.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Core\Core.vcxproj">
|
||||
|
||||
@@ -144,7 +144,6 @@
|
||||
<ClCompile Include="Background.cpp">
|
||||
<Filter>Screens</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VideoPlayer.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="GameInfoCache.h" />
|
||||
@@ -286,7 +285,6 @@
|
||||
<ClInclude Include="Background.h">
|
||||
<Filter>Screens</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VideoPlayer.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Screens">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
#pragma once
|
||||
@@ -318,6 +318,7 @@
|
||||
<ClInclude Include="..\..\Core\Util\PathUtil.h" />
|
||||
<ClInclude Include="..\..\Core\Util\PortManager.h" />
|
||||
<ClInclude Include="..\..\Core\Util\RecentFiles.h" />
|
||||
<ClInclude Include="..\..\Core\Util\VideoPlayer.h" />
|
||||
<ClInclude Include="..\..\Core\WebServer.h" />
|
||||
<ClInclude Include="..\..\Core\Util\AudioFormat.h" />
|
||||
<ClInclude Include="..\..\Core\Util\BlockAllocator.h" />
|
||||
@@ -624,6 +625,7 @@
|
||||
<ClCompile Include="..\..\Core\Util\PathUtil.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\PortManager.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\RecentFiles.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\VideoPlayer.cpp" />
|
||||
<ClCompile Include="..\..\Core\WebServer.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\AudioFormat.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\BlockAllocator.cpp" />
|
||||
|
||||
@@ -410,6 +410,7 @@
|
||||
<ClCompile Include="pch.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\PathUtil.cpp" />
|
||||
<ClCompile Include="..\..\Core\SaveStateRewind.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\VideoPlayer.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\Core\AVIDump.h" />
|
||||
@@ -682,6 +683,7 @@
|
||||
<ClInclude Include="targetver.h" />
|
||||
<ClInclude Include="..\..\Core\Util\PathUtil.h" />
|
||||
<ClInclude Include="..\..\Core\SaveStateRewind.h" />
|
||||
<ClInclude Include="..\..\Core\Util\VideoPlayer.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\ext\gason\LICENSE" />
|
||||
|
||||
@@ -277,4 +277,4 @@
|
||||
<UniqueIdentifier>{38b8413d-438d-4197-9db8-7bd123fc7431}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user