From 1fc6191c85d8ec8dc22c5e6c50cf4ee64dc8fd1b Mon Sep 17 00:00:00 2001 From: Chris Burgener Date: Sat, 27 Aug 2016 14:38:05 -0400 Subject: [PATCH 1/9] Add Frame Dumping for Windows --- Core/AVIDump.cpp | 352 ++++++++++++++++++++++++++++++++++++++ Core/AVIDump.h | 20 +++ Core/Config.cpp | 10 +- Core/Config.h | 16 +- Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/CoreTiming.cpp | 13 +- Core/CoreTiming.h | 1 + Core/SaveState.cpp | 1 + Core/System.cpp | 6 +- Core/System.h | 1 + UI/EmuScreen.cpp | 25 +++ UI/GameSettingsScreen.cpp | 18 +- 13 files changed, 446 insertions(+), 25 deletions(-) create mode 100644 Core/AVIDump.cpp create mode 100644 Core/AVIDump.h diff --git a/Core/AVIDump.cpp b/Core/AVIDump.cpp new file mode 100644 index 000000000000..7996fd5b889d --- /dev/null +++ b/Core/AVIDump.cpp @@ -0,0 +1,352 @@ +// Copyright 2009 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#if defined(__FreeBSD__) +#define __STDC_CONSTANT_MACROS 1 +#endif + +#include +#include + +extern "C" { +#include +#include +#include +#include +} + +#include "Common/FileUtil.h" +#include "Common/MsgHandler.h" +#include "Common/ColorConv.h" + +#include "Core/Config.h" +#include "Core/AVIDump.h" +#include "Core/System.h" +#include "Core/Screenshot.h" + +#include "GPU/Common/GPUDebugInterface.h" +#ifdef _WIN32 +#include "GPU/Directx9/GPU_DX9.h" +#endif +#include "GPU/GLES/GPU_GLES.h" + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 28, 1) +#define av_frame_alloc avcodec_alloc_frame +#define av_frame_free avcodec_free_frame +#endif + +static AVFormatContext* s_format_context = nullptr; +static AVStream* s_stream = nullptr; +static AVFrame* s_src_frame = nullptr; +static AVFrame* s_scaled_frame = nullptr; +static int s_bytes_per_pixel; +static SwsContext* s_sws_context = nullptr; +static int s_width; +static int s_height; +static bool s_start_dumping = false; +static int s_current_width; +static int s_current_height; +static int s_file_index = 0; + +static void InitAVCodec() +{ + static bool first_run = true; + if (first_run) + { + av_register_all(); + first_run = false; + } +} + +bool AVIDump::Start(int w, int h) +{ + s_width = w; + s_height = h; + s_current_width = w; + s_current_height = h; + + InitAVCodec(); + bool success = CreateAVI(); + if (!success) + CloseFile(); + return success; +} + +bool AVIDump::CreateAVI() +{ + AVCodec* codec = nullptr; + + s_format_context = avformat_alloc_context(); + std::stringstream s_file_index_str; + s_file_index_str << s_file_index; + snprintf(s_format_context->filename, sizeof(s_format_context->filename), "%s", (GetSysDirectory(DIRECTORY_VIDEO_DUMP) + "framedump" + s_file_index_str.str() + ".avi").c_str()); + // Make sure that the path exists + if (!File::Exists(GetSysDirectory(DIRECTORY_VIDEO_DUMP))) + File::CreateDir(GetSysDirectory(DIRECTORY_VIDEO_DUMP)); + + if (File::Exists(s_format_context->filename)) + File::Delete(s_format_context->filename); + + if (!(s_format_context->oformat = av_guess_format("avi", nullptr, nullptr)) || !(s_stream = avformat_new_stream(s_format_context, codec))) + { + return false; + } + + s_stream->codec->codec_id = g_Config.bUseFFV1 ? AV_CODEC_ID_FFV1 : s_format_context->oformat->video_codec; + if (!g_Config.bUseFFV1) + s_stream->codec->codec_tag = MKTAG('X', 'V', 'I', 'D'); // Force XVID FourCC for better compatibility + s_stream->codec->codec_type = AVMEDIA_TYPE_VIDEO; + s_stream->codec->bit_rate = 400000; + s_stream->codec->width = s_width; + s_stream->codec->height = s_height; + s_stream->codec->time_base.num = 1001; + s_stream->codec->time_base.den = 60000; + s_stream->codec->gop_size = 12; + s_stream->codec->pix_fmt = g_Config.bUseFFV1 ? AV_PIX_FMT_BGRA : AV_PIX_FMT_YUV420P; + + if (!(codec = avcodec_find_encoder(s_stream->codec->codec_id)) || (avcodec_open2(s_stream->codec, codec, nullptr) < 0)) + { + return false; + } + + s_src_frame = av_frame_alloc(); + s_scaled_frame = av_frame_alloc(); + + s_scaled_frame->format = s_stream->codec->pix_fmt; + s_scaled_frame->width = s_width; + s_scaled_frame->height = s_height; + +#if LIBAVCODEC_VERSION_MAJOR >= 55 + if (av_frame_get_buffer(s_scaled_frame, 1)) + return false; +#else + if (avcodec_default_get_buffer(s_stream->codec, s_scaled_frame)) + return false; +#endif + + NOTICE_LOG(G3D, "Opening file %s for dumping", s_format_context->filename); + if (avio_open(&s_format_context->pb, s_format_context->filename, AVIO_FLAG_WRITE) < 0 || avformat_write_header(s_format_context, nullptr)) + { + WARN_LOG(G3D, "Could not open %s", s_format_context->filename); + return false; + } + + return true; +} + +static void PreparePacket(AVPacket* pkt) +{ + av_init_packet(pkt); + pkt->data = nullptr; + pkt->size = 0; +} + +static const u8 *ConvertBufferTo888RGB(const GPUDebugBuffer &buf, u8 *&temp, u32 &w, u32 &h) { + // The temp buffer will be freed by the caller if set, and can be the return value. + temp = nullptr; + + w = std::min(w, buf.GetStride()); + h = std::min(h, buf.GetHeight()); + + const u8 *buffer = buf.GetData(); + if (buf.GetFlipped() && buf.GetFormat() == GPU_DBG_FORMAT_888_RGB) { + // Silly OpenGL reads upside down, we flip to another buffer for simplicity. + temp = new u8[3 * w * h]; + for (u32 y = 0; y < h; y++) { + memcpy(temp + y * w * 3, buffer + (buf.GetHeight() - y - 1) * buf.GetStride() * 3, w * 3); + } + buffer = temp; + } + else if (buf.GetFormat() != GPU_DBG_FORMAT_888_RGB) { + // Let's boil it down to how we need to interpret the bits. + int baseFmt = buf.GetFormat() & ~(GPU_DBG_FORMAT_REVERSE_FLAG | GPU_DBG_FORMAT_BRSWAP_FLAG); + bool rev = (buf.GetFormat() & GPU_DBG_FORMAT_REVERSE_FLAG) != 0; + bool brswap = (buf.GetFormat() & GPU_DBG_FORMAT_BRSWAP_FLAG) != 0; + bool flip = buf.GetFlipped(); + + temp = new u8[3 * w * h]; + + // This is pretty inefficient. + const u16 *buf16 = (const u16 *)buffer; + const u32 *buf32 = (const u32 *)buffer; + for (u32 y = 0; y < h; y++) { + for (u32 x = 0; x < w; x++) { + u8 *dst; + if (flip) { + dst = &temp[(h - y - 1) * w * 3 + x * 3]; + } + else { + dst = &temp[y * w * 3 + x * 3]; + } + + u8 &r = brswap ? dst[2] : dst[0]; + u8 &g = dst[1]; + u8 &b = brswap ? dst[0] : dst[2]; + + u32 src; + switch (baseFmt) { + case GPU_DBG_FORMAT_565: + src = buf16[y * buf.GetStride() + x]; + if (rev) { + src = bswap16(src); + } + r = Convert5To8((src >> 0) & 0x1F); + g = Convert6To8((src >> 5) & 0x3F); + b = Convert5To8((src >> 11) & 0x1F); + break; + case GPU_DBG_FORMAT_5551: + src = buf16[y * buf.GetStride() + x]; + if (rev) { + src = bswap16(src); + } + r = Convert5To8((src >> 0) & 0x1F); + g = Convert5To8((src >> 5) & 0x1F); + b = Convert5To8((src >> 10) & 0x1F); + break; + case GPU_DBG_FORMAT_4444: + src = buf16[y * buf.GetStride() + x]; + if (rev) { + src = bswap16(src); + } + r = Convert4To8((src >> 0) & 0xF); + g = Convert4To8((src >> 4) & 0xF); + b = Convert4To8((src >> 8) & 0xF); + break; + case GPU_DBG_FORMAT_8888: + src = buf32[y * buf.GetStride() + x]; + if (rev) { + src = bswap32(src); + } + r = (src >> 0) & 0xFF; + g = (src >> 8) & 0xFF; + b = (src >> 16) & 0xFF; + break; + default: + ERROR_LOG(COMMON, "Unsupported framebuffer format for screenshot: %d", buf.GetFormat()); + return nullptr; + } + } + } + buffer = temp; + } + + return buffer; +} + +void AVIDump::AddFrame() +{ + GPUDebugBuffer buf; + gpuDebug->GetCurrentFramebuffer(buf); + u32 w = buf.GetStride(); + u32 h = buf.GetHeight(); + CheckResolution(w, h); + u8 *flipbuffer = nullptr; + const u8 *buffer = ConvertBufferTo888RGB(buf, flipbuffer, w, h); + s_src_frame->data[0] = const_cast(buffer); + s_src_frame->linesize[0] = w * 3; + s_src_frame->format = AV_PIX_FMT_RGB24; + s_src_frame->width = s_width; + s_src_frame->height = s_height; + + // Convert image from BGR24 to desired pixel format, and scale to initial + // width and height + if ((s_sws_context = + sws_getCachedContext(s_sws_context, w, h, AV_PIX_FMT_RGB24, s_width, s_height, + s_stream->codec->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr))) + { + sws_scale(s_sws_context, s_src_frame->data, s_src_frame->linesize, 0, h, + s_scaled_frame->data, s_scaled_frame->linesize); + } + + s_scaled_frame->format = s_stream->codec->pix_fmt; + s_scaled_frame->width = s_width; + s_scaled_frame->height = s_height; + + // Encode and write the image. + AVPacket pkt; + PreparePacket(&pkt); + int got_packet; + int error = avcodec_encode_video2(s_stream->codec, &pkt, s_scaled_frame, &got_packet); + while (!error && got_packet) + { + // Write the compressed frame in the media file. + if (pkt.pts != (s64)AV_NOPTS_VALUE) + { + pkt.pts = av_rescale_q(pkt.pts, s_stream->codec->time_base, s_stream->time_base); + } + if (pkt.dts != (s64)AV_NOPTS_VALUE) + { + pkt.dts = av_rescale_q(pkt.dts, s_stream->codec->time_base, s_stream->time_base); + } +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 60, 100) + if (s_stream->codec->coded_frame->key_frame) + pkt.flags |= AV_PKT_FLAG_KEY; +#endif + pkt.stream_index = s_stream->index; + av_interleaved_write_frame(s_format_context, &pkt); + + // Handle delayed frames. + PreparePacket(&pkt); + error = avcodec_encode_video2(s_stream->codec, &pkt, nullptr, &got_packet); + } + if (error) + ERROR_LOG(G3D, "Error while encoding video: %d", error); +} + +void AVIDump::Stop() +{ + av_write_trailer(s_format_context); + CloseFile(); + s_file_index = 0; + NOTICE_LOG(G3D, "Stopping frame dump"); +} + +void AVIDump::CloseFile() +{ + if (s_stream) + { + if (s_stream->codec) + { +#if LIBAVCODEC_VERSION_MAJOR < 55 + avcodec_default_release_buffer(s_stream->codec, s_src_frame); +#endif + avcodec_close(s_stream->codec); + } + av_freep(&s_stream); + } + + av_frame_free(&s_src_frame); + av_frame_free(&s_scaled_frame); + + if (s_format_context) + { + if (s_format_context->pb) + avio_close(s_format_context->pb); + av_freep(&s_format_context); + } + + if (s_sws_context) + { + sws_freeContext(s_sws_context); + s_sws_context = nullptr; + } +} + +void AVIDump::CheckResolution(int width, int height) +{ + // We check here to see if the requested width and height have changed since the last frame which + // was dumped, then create a new file accordingly. However, is it possible for the height + // (possibly width as well, but no examples known) to have a value of zero. This can occur as the + // VI is able to be set to a zero value for height/width to disable output. If this is the case, + // simply keep the last known resolution of the video for the added frame. + if ((width != s_current_width || height != s_current_height) && (width > 0 && height > 0)) + { + int temp_file_index = s_file_index; + Stop(); + s_file_index = temp_file_index + 1; + Start(width, height); + s_current_width = width; + s_current_height = height; + } +} diff --git a/Core/AVIDump.h b/Core/AVIDump.h new file mode 100644 index 000000000000..5288d25cd099 --- /dev/null +++ b/Core/AVIDump.h @@ -0,0 +1,20 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "Common/CommonTypes.h" + +class AVIDump +{ +private: + static bool CreateAVI(); + static void CloseFile(); + static void CheckResolution(int width, int height); + +public: + static bool Start(int w, int h); + static void AddFrame(); + static void Stop(); +}; \ No newline at end of file diff --git a/Core/Config.cpp b/Core/Config.cpp index e38a04c2d9a6..670c82377c43 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -40,7 +40,7 @@ #include "HLE/sceUtility.h" #ifndef USING_QT_UI -extern const char *PPSSPP_GIT_VERSION; +extern const char *PPSSPP_GIT_VERSION; #endif // TODO: Find a better place for this. @@ -327,6 +327,8 @@ static ConfigSetting generalSettings[] = { ConfigSetting("CwCheatRefreshRate", &g_Config.iCwCheatRefreshRate, 77, true, true), ConfigSetting("ScreenshotsAsPNG", &g_Config.bScreenshotsAsPNG, false, true, true), + ConfigSetting("UseFFV1", &g_Config.bUseFFV1, false), + ConfigSetting("DumpFrames", &g_Config.bDumpFrames, false), ConfigSetting("StateSlot", &g_Config.iCurrentStateSlot, 0, true, true), ConfigSetting("RewindFlipFrequency", &g_Config.iRewindFlipFrequency, 0, true, true), @@ -926,7 +928,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { fcombo4X /= screen_width; fcombo4Y /= screen_height; } - + const char *gitVer = PPSSPP_GIT_VERSION; Version installed(gitVer); Version upgrade(upgradeVersion); @@ -955,7 +957,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { bSaveSettings = true; LoadStandardControllerIni(); - + //so this is all the way down here to overwrite the controller settings //sadly it won't benefit from all the "version conversion" going on up-above //but these configs shouldn't contain older versions anyhow @@ -987,7 +989,7 @@ void Config::Save() { g_Config.iCpuCore = CPU_CORE_JIT; } if (iniFilename_.size() && g_Config.bSaveSettings) { - + saveGameConfig(gameId_); CleanRecent(); diff --git a/Core/Config.h b/Core/Config.h index 119acebea97a..5f3c5dec2032 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -104,6 +104,8 @@ struct Config { // General int iNumWorkerThreads; bool bScreenshotsAsPNG; + bool bUseFFV1; + bool bDumpFrames; bool bEnableLogging; bool bDumpDecryptedEboot; bool bFullscreenOnDoubleclick; @@ -235,7 +237,7 @@ struct Config { //considers this orientation to be equal to no movement of the analog stick. float fTiltBaseX, fTiltBaseY; //whether the x axes and y axes should invert directions (left becomes right, top becomes bottom.) - bool bInvertTiltX, bInvertTiltY; + bool bInvertTiltX, bInvertTiltY; //the sensitivity of the tilt in the x direction int iTiltSensitivityX; //the sensitivity of the tilt in the Y direction @@ -330,7 +332,7 @@ struct Config { bool bShowComboKey2; bool bShowComboKey3; bool bShowComboKey4; - + // Combo_key mapping. These are bitfields. int iCombokey0; int iCombokey1; @@ -359,9 +361,9 @@ struct Config { // proper options when good enough. // PrescaleUV: // * Applies UV scale/offset when decoding verts. Get rid of some work in the vertex shader, - // saves a uniform upload and is a prerequisite for future optimized hybrid + // saves a uniform upload and is a prerequisite for future optimized hybrid // (SW skinning, HW transform) skinning. - // * Still has major problems so off by default - need to store tex scale/offset per DeferredDrawCall, + // * Still has major problems so off by default - need to store tex scale/offset per DeferredDrawCall, // which currently isn't done so if texscale/offset isn't static (like in Tekken 6) things go wrong. bool bPrescaleUV; bool bDisableAlphaTest; // Helps PowerVR immensely, breaks some graphics @@ -424,7 +426,7 @@ struct Config { bool bShowFrameProfiler; std::string currentDirectory; - std::string externalDirectory; + std::string externalDirectory; std::string memStickDirectory; std::string flash0Directory; std::string internalDataDirectory; @@ -438,7 +440,7 @@ struct Config { void Load(const char *iniFileName = nullptr, const char *controllerIniFilename = nullptr); void Save(); void RestoreDefaults(); - + //per game config managment, should maybe be in it's own class void changeGameSpecific(const std::string &gameId = ""); bool createGameConfig(const std::string &game_id); @@ -472,7 +474,7 @@ struct Config { protected: void LoadStandardControllerIni(); - + private: std::string gameId_; std::string iniFilename_; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 60d9c7c66c21..c2351f035b67 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -181,6 +181,7 @@ + @@ -519,6 +520,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 600070d1e8d4..a77ebd3d12fc 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -673,6 +673,9 @@ MIPS\IR + + Core + @@ -1236,6 +1239,9 @@ MIPS\IR + + Core + diff --git a/Core/CoreTiming.cpp b/Core/CoreTiming.cpp index 887e420dd146..951498ef61bd 100644 --- a/Core/CoreTiming.cpp +++ b/Core/CoreTiming.cpp @@ -240,6 +240,11 @@ u64 GetTicks() return (u64)globalTimer + slicelength - currentMIPS->downcount; } +u64 GetTicksPerSecond() +{ + return CPU_HZ; +} + u64 GetIdleTicks() { return (u64)idledCycles; @@ -308,7 +313,7 @@ void AddEventToQueue(Event* ne) // This must be run ONLY from within the cpu thread // cyclesIntoFuture may be VERY inaccurate if called from anything else -// than Advance +// than Advance void ScheduleEvent(s64 cyclesIntoFuture, int event_type, u64 userdata) { Event *ne = GetNewEvent(); @@ -418,7 +423,7 @@ void RegisterMHzChangeCallback(MHzChangeCallback callback) { mhzChangeCallbacks.push_back(callback); } -bool IsScheduled(int event_type) +bool IsScheduled(int event_type) { if (!first) return false; @@ -498,7 +503,7 @@ void RemoveThreadsafeEvent(int event_type) while (ptr) { if (ptr->type == event_type) - { + { prev->next = ptr->next; if (ptr == tsLast) tsLast = prev; @@ -526,7 +531,7 @@ void ProcessFifoWaitEvents() { if (first->time <= (s64)GetTicks()) { -// LOG(TIMER, "[Scheduler] %s (%lld, %lld) ", +// LOG(TIMER, "[Scheduler] %s (%lld, %lld) ", // first->name ? first->name : "?", (u64)GetTicks(), (u64)first->time); Event* evt = first; first = first->next; diff --git a/Core/CoreTiming.h b/Core/CoreTiming.h index ee7a26a714bb..82f28aa8f796 100644 --- a/Core/CoreTiming.h +++ b/Core/CoreTiming.h @@ -82,6 +82,7 @@ namespace CoreTiming u64 GetIdleTicks(); u64 GetGlobalTimeUs(); u64 GetGlobalTimeUsScaled(); + u64 GetTicksPerSecond(); // Returns the event_type identifier. int RegisterEvent(const char *name, TimedCallback callback); diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 74a698b58a23..f949426e24e9 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -25,6 +25,7 @@ #include "Common/FileUtil.h" #include "Common/ChunkFile.h" +#include "Core/AVIDump.h" #include "Core/SaveState.h" #include "Core/Config.h" #include "Core/Core.h" diff --git a/Core/System.cpp b/Core/System.cpp index 5a78541596d7..7f03be126369 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -182,7 +182,7 @@ void CPU_Shutdown(); void CPU_Init() { coreState = CORE_POWERUP; currentMIPS = &mipsr4k; - + g_symbolMap = new SymbolMap(); // Default memory settings @@ -386,7 +386,7 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { #else INFO_LOG(BOOT, "PPSSPP %s", PPSSPP_GIT_VERSION); #endif - + GraphicsContext *temp = coreParameter.graphicsContext; coreParameter = coreParam; if (coreParameter.graphicsContext == nullptr) { @@ -593,6 +593,8 @@ std::string GetSysDirectory(PSPDirectories directoryType) { return g_Config.appCacheDirectory; } return g_Config.memStickDirectory + "PSP/SYSTEM/CACHE/"; + case DIRECTORY_VIDEO_DUMP: + return g_Config.memStickDirectory + "PSP/FRAMEDUMP/"; // Just return the memory stick root if we run into some sort of problem. default: ERROR_LOG(FILESYS, "Unknown directory type."); diff --git a/Core/System.h b/Core/System.h index 7815b60accc1..2aef7c21e8a9 100644 --- a/Core/System.h +++ b/Core/System.h @@ -47,6 +47,7 @@ enum PSPDirectories { DIRECTORY_CACHE, DIRECTORY_TEXTURES, DIRECTORY_APP_CACHE, // Use the OS app cache if available + DIRECTORY_VIDEO_DUMP }; class GraphicsContext; diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 0122b945332d..e1d678cbb74a 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -32,6 +32,7 @@ #include "Common/KeyMap.h" +#include "Core/AVIDump.h" #include "Core/Config.h" #include "Core/CoreTiming.h" #include "Core/CoreParameter.h" @@ -70,8 +71,11 @@ #include "Windows/MainWindow.h" #endif +AVIDump avi; + static bool frameStep_; static int lastNumFlips; +static bool startDumping; static void __EmuScreenVblank() { @@ -81,6 +85,21 @@ static void __EmuScreenVblank() Core_EnableStepping(true); lastNumFlips = gpuStats.numFlips; } + + if (g_Config.bDumpFrames && !startDumping) + { + avi.Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight); + startDumping = true; + } + if (g_Config.bDumpFrames && startDumping) + { + avi.AddFrame(); + } + else if (!g_Config.bDumpFrames && startDumping) + { + avi.Stop(); + startDumping = false; + } } EmuScreen::EmuScreen(const std::string &filename) @@ -90,6 +109,7 @@ EmuScreen::EmuScreen(const std::string &filename) __DisplayListenVblank(__EmuScreenVblank); frameStep_ = false; lastNumFlips = gpuStats.numFlips; + startDumping = false; } void EmuScreen::bootGame(const std::string &filename) { @@ -227,6 +247,11 @@ EmuScreen::~EmuScreen() { // If we were invalid, it would already be shutdown. PSP_Shutdown(); } + if (g_Config.bDumpFrames && startDumping) + { + avi.Stop(); + startDumping = false; + } } void EmuScreen::dialogFinished(const Screen *dialog, DialogResult result) { diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index fea7d947efdf..0977717e5b54 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -273,7 +273,7 @@ void GameSettingsScreen::CreateViews() { static const char *quality[] = { "Low", "Medium", "High"}; PopupMultiChoice *beziersChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iSplineBezierQuality, gr->T("LowCurves", "Spline/Bezier curves quality"), quality, 0, ARRAY_SIZE(quality), gr->GetName(), screenManager())); beziersChoice->SetDisabledPtr(&g_Config.bSoftwareRendering); - + // In case we're going to add few other antialiasing option like MSAA in the future. // graphicsSettings->Add(new CheckBox(&g_Config.bFXAA, gr->T("FXAA"))); graphicsSettings->Add(new ItemHeader(gr->T("Texture Scaling"))); @@ -676,6 +676,8 @@ void GameSettingsScreen::CreateViews() { #if defined(_WIN32) || (defined(USING_QT_UI) && !defined(MOBILE_DEVICE)) // Screenshot functionality is not yet available on non-Windows/non-Qt systemSettings->Add(new CheckBox(&g_Config.bScreenshotsAsPNG, sy->T("Screenshots as PNG"))); + systemSettings->Add(new CheckBox(&g_Config.bDumpFrames, sy->T("Dump Frames"))); + systemSettings->Add(new CheckBox(&g_Config.bUseFFV1, sy->T("Use FFV1 for Frame Dumps"))); #endif systemSettings->Add(new CheckBox(&g_Config.bDayLightSavings, sy->T("Day Light Saving"))); static const char *dateFormat[] = { "YYYYMMDD", "MMDDYYYY", "DDMMYYYY"}; @@ -801,7 +803,7 @@ UI::EventReturn GameSettingsScreen::OnSavePathMydoc(UI::EventParams &e) { } UI::EventReturn GameSettingsScreen::OnSavePathOther(UI::EventParams &e) { - const std::string PPSSPPpath = File::GetExeDirectory(); + const std::string PPSSPPpath = File::GetExeDirectory(); if (otherinstalled_) { I18NCategory *di = GetI18NCategory("Dialog"); std::string folder = W32Util::BrowseForFolder(MainWindow::GetHWND(), di->T("Choose PPSSPP save folder")); @@ -983,8 +985,8 @@ UI::EventReturn GameSettingsScreen::OnChangeNickname(UI::EventParams &e) { return UI::EVENT_DONE; } -UI::EventReturn GameSettingsScreen::OnChangeproAdhocServerAddress(UI::EventParams &e) { -#if defined(_WIN32) || defined(USING_QT_UI) +UI::EventReturn GameSettingsScreen::OnChangeproAdhocServerAddress(UI::EventParams &e) { +#if defined(_WIN32) || defined(USING_QT_UI) if (!g_Config.bFullScreen) { const size_t name_len = 256; @@ -1002,7 +1004,7 @@ UI::EventReturn GameSettingsScreen::OnChangeproAdhocServerAddress(UI::EventParam #else screenManager()->push(new ProAdhocServerScreen); #endif - + return UI::EVENT_DONE; } @@ -1246,14 +1248,14 @@ UI::EventReturn DeveloperToolsScreen::OnJitAffectingSetting(UI::EventParams &e) } void ProAdhocServerScreen::CreateViews() { - using namespace UI; + using namespace UI; I18NCategory *sy = GetI18NCategory("System"); I18NCategory *di = GetI18NCategory("Dialog"); - + tempProAdhocServer = g_Config.proAdhocServer; root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); - + leftColumn->Add(new ItemHeader(sy->T("proAdhocServer Address:"))); addrView_ = new TextView(tempProAdhocServer, ALIGN_LEFT, false); leftColumn->Add(addrView_); From 3d8a49c846233d05c84383ebca76ee08216ec000 Mon Sep 17 00:00:00 2001 From: Chris Burgener Date: Thu, 1 Sep 2016 16:45:39 -0400 Subject: [PATCH 2/9] Add Movie Menu to Win32 --- Core/AVIDump.cpp | 8 ++++++++ Windows/MainWindowMenu.cpp | 20 ++++++++++++++++++-- Windows/ppsspp.rc | 14 ++++++++++---- Windows/resource.h | 2 ++ 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/Core/AVIDump.cpp b/Core/AVIDump.cpp index 7996fd5b889d..25ab77a940b1 100644 --- a/Core/AVIDump.cpp +++ b/Core/AVIDump.cpp @@ -140,6 +140,14 @@ static void PreparePacket(AVPacket* pkt) av_init_packet(pkt); pkt->data = nullptr; pkt->size = 0; + if (s_stream->codec->coded_frame->pts != AV_NOPTS_VALUE) + { + pkt->pts = av_rescale_q(s_stream->codec->coded_frame->pts, + s_stream->codec->time_base, s_stream->time_base); + } + if (s_stream->codec->coded_frame->key_frame) + pkt->flags |= AV_PKT_FLAG_KEY; + pkt->stream_index = s_stream->index; } static const u8 *ConvertBufferTo888RGB(const GPUDebugBuffer &buf, u8 *&temp, u32 &w, u32 &h) { diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index 3a6de2fc42b7..d528e82a62b0 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -74,8 +74,9 @@ namespace MainWindow { MENU_FILE = 0, MENU_EMULATION = 1, MENU_DEBUG = 2, - MENU_OPTIONS = 3, - MENU_HELP = 4, + MENU_MOVIE = 3, + MENU_OPTIONS = 4, + MENU_HELP = 5, // File submenus SUBMENU_FILE_SAVESTATE_SLOT = 6, @@ -217,6 +218,7 @@ namespace MainWindow { TranslateMenu(menu, "File", MENU_FILE); TranslateMenu(menu, "Emulation", MENU_EMULATION); TranslateMenu(menu, "Debugging", MENU_DEBUG); + TranslateMenu(menu, "Movie", MENU_MOVIE); TranslateMenu(menu, "Game Settings", MENU_OPTIONS); TranslateMenu(menu, "Help", MENU_HELP); @@ -271,6 +273,10 @@ namespace MainWindow { TranslateMenuItem(menu, ID_OPTIONS_CONTROLS); TranslateMenuItem(menu, ID_OPTIONS_DISPLAY_LAYOUT); + // Movie menu + TranslateMenuItem(menu, ID_MOVIE_DUMPFRAMES); + TranslateMenuItem(menu, ID_MOVIE_USEFFV1); + // Skip display multipliers x1-x10 TranslateMenuItem(menu, ID_OPTIONS_FULLSCREEN, L"\tAlt+Return, F11"); TranslateMenuItem(menu, ID_OPTIONS_VSYNC); @@ -928,6 +934,14 @@ namespace MainWindow { g_TakeScreenshot = true; break; + case ID_MOVIE_DUMPFRAMES: + g_Config.bDumpFrames = !g_Config.bDumpFrames; + break; + + case ID_MOVIE_USEFFV1: + g_Config.bUseFFV1 = !g_Config.bUseFFV1; + break; + default: { // Handle the dynamic shader switching here. @@ -966,6 +980,8 @@ namespace MainWindow { CHECKITEM(ID_TEXTURESCALING_DEPOSTERIZE, g_Config.bTexDeposterize); CHECKITEM(ID_EMULATION_CHEATS, g_Config.bEnableCheats); CHECKITEM(ID_OPTIONS_IGNOREWINKEY, g_Config.bIgnoreWindowsKey); + CHECKITEM(ID_MOVIE_DUMPFRAMES, g_Config.bDumpFrames); + CHECKITEM(ID_MOVIE_USEFFV1, g_Config.bUseFFV1); static const int displayrotationitems[] = { ID_EMULATION_ROTATION_H, diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 5135556f6fd1..91e8c85745eb 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -19,17 +19,17 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_DEFAULT // TEXTINCLUDE // -1 TEXTINCLUDE +1 TEXTINCLUDE BEGIN "resource.h\0" END -2 TEXTINCLUDE +2 TEXTINCLUDE BEGIN "\0" END -3 TEXTINCLUDE +3 TEXTINCLUDE BEGIN "\r\n" "\0" @@ -401,7 +401,7 @@ BEGIN MENUITEM "&5", ID_FILE_SAVESTATE_SLOT_5 END MENUITEM "Load State", ID_FILE_QUICKLOADSTATE - MENUITEM "Save State", ID_FILE_QUICKSAVESTATE + MENUITEM "Save State", ID_FILE_QUICKSAVESTATE MENUITEM "Load State File...", ID_FILE_LOADSTATEFILE MENUITEM "Save State File...", ID_FILE_SAVESTATEFILE MENUITEM SEPARATOR @@ -445,6 +445,12 @@ BEGIN MENUITEM "Memory View...", ID_DEBUG_MEMORYVIEW END + POPUP "Movie" + BEGIN + MENUITEM "Dump Frames", ID_MOVIE_DUMPFRAMES + MENUITEM "Lossless Codec (FFV1)", ID_MOVIE_USEFFV1 + END + POPUP "Options" BEGIN MENUITEM "Keep PPSSPP On Top", ID_OPTIONS_TOPMOST diff --git a/Windows/resource.h b/Windows/resource.h index 66e88a362068..4f9d9a069fcf 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -328,6 +328,8 @@ #define IDC_GEDBG_BREAKTARGET 40162 #define ID_GEDBG_COPYALL 40163 #define ID_GEDBG_WATCH 40164 +#define ID_MOVIE_DUMPFRAMES 40165 +#define ID_MOVIE_USEFFV1 40166 // Dummy option to let the buffered rendering hotkey cycle through all the options. #define ID_OPTIONS_BUFFEREDRENDERINGDUMMY 40500 From 7ca90872fb36f46b5d2c4e02b6b32dd32a0e6e6b Mon Sep 17 00:00:00 2001 From: Chris Burgener Date: Thu, 1 Sep 2016 21:59:12 -0400 Subject: [PATCH 3/9] Add simple OSD display --- UI/EmuScreen.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index e1d678cbb74a..0ea26937092c 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -89,6 +89,7 @@ static void __EmuScreenVblank() if (g_Config.bDumpFrames && !startDumping) { avi.Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight); + osm.Show("AVI Dump started.", 3.0f); startDumping = true; } if (g_Config.bDumpFrames && startDumping) @@ -98,6 +99,7 @@ static void __EmuScreenVblank() else if (!g_Config.bDumpFrames && startDumping) { avi.Stop(); + osm.Show("AVI Dump stopped.", 3.0f); startDumping = false; } } @@ -250,6 +252,7 @@ EmuScreen::~EmuScreen() { if (g_Config.bDumpFrames && startDumping) { avi.Stop(); + osm.Show("AVI Dump stopped.", 3.0f); startDumping = false; } } From 78d1af4b501215e85f232c4a3d562768320c7ef4 Mon Sep 17 00:00:00 2001 From: Chris Burgener Date: Thu, 1 Sep 2016 20:09:56 -0400 Subject: [PATCH 4/9] Fix builds for non-Win32 --- CMakeLists.txt | 6 ++++++ UI/EmuScreen.cpp | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 64424f455534..f29a9eba6fef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1190,6 +1190,12 @@ else() ext/disarm.cpp) endif() +if (NOT MOBILE_DEVICE) + set(CoreExtra ${CoreExtra} + Core/AVIDump.cpp + Core/AVIDump.h) +endif() + if(ARMV7) set(CORE_NEON Core/Util/AudioFormatNEON.cpp Core/Util/AudioFormatNEON.h) endif() diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 0ea26937092c..e2d860dc48c5 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -32,7 +32,9 @@ #include "Common/KeyMap.h" +#ifndef MOBILE_DEVICE #include "Core/AVIDump.h" +#endif #include "Core/Config.h" #include "Core/CoreTiming.h" #include "Core/CoreParameter.h" @@ -71,7 +73,9 @@ #include "Windows/MainWindow.h" #endif +#ifndef MOBILE_DEVICE AVIDump avi; +#endif static bool frameStep_; static int lastNumFlips; @@ -85,7 +89,7 @@ static void __EmuScreenVblank() Core_EnableStepping(true); lastNumFlips = gpuStats.numFlips; } - +#ifndef MOBILE_DEVICE if (g_Config.bDumpFrames && !startDumping) { avi.Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight); @@ -102,6 +106,7 @@ static void __EmuScreenVblank() osm.Show("AVI Dump stopped.", 3.0f); startDumping = false; } +#endif } EmuScreen::EmuScreen(const std::string &filename) @@ -249,12 +254,14 @@ EmuScreen::~EmuScreen() { // If we were invalid, it would already be shutdown. PSP_Shutdown(); } +#ifndef MOBILE_DEVICE if (g_Config.bDumpFrames && startDumping) { avi.Stop(); osm.Show("AVI Dump stopped.", 3.0f); startDumping = false; } +#endif } void EmuScreen::dialogFinished(const Screen *dialog, DialogResult result) { From 645cb4d69eb87744c45e1ceffea4a98296cfa7d6 Mon Sep 17 00:00:00 2001 From: Chris Burgener Date: Sat, 3 Sep 2016 10:06:40 -0400 Subject: [PATCH 5/9] Add Audio Dumping to File --- Core/Config.cpp | 1 + Core/Config.h | 1 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 2 + Core/HLE/__sceAudio.cpp | 56 ++++++++++++++++- Core/HLE/__sceAudio.h | 5 ++ Core/HW/StereoResampler.cpp | 4 +- Core/HW/StereoResampler.h | 1 + Core/WaveFile.cpp | 116 ++++++++++++++++++++++++++++++++++++ Core/WaveFile.h | 44 ++++++++++++++ UI/GameSettingsScreen.cpp | 1 + Windows/MainWindowMenu.cpp | 6 ++ Windows/ppsspp.rc | 5 +- Windows/resource.h | 1 + 14 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 Core/WaveFile.cpp create mode 100644 Core/WaveFile.h diff --git a/Core/Config.cpp b/Core/Config.cpp index 670c82377c43..f32aa786cfb0 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -329,6 +329,7 @@ static ConfigSetting generalSettings[] = { ConfigSetting("ScreenshotsAsPNG", &g_Config.bScreenshotsAsPNG, false, true, true), ConfigSetting("UseFFV1", &g_Config.bUseFFV1, false), ConfigSetting("DumpFrames", &g_Config.bDumpFrames, false), + ConfigSetting("DumpAudio", &g_Config.bDumpAudio, false), ConfigSetting("StateSlot", &g_Config.iCurrentStateSlot, 0, true, true), ConfigSetting("RewindFlipFrequency", &g_Config.iRewindFlipFrequency, 0, true, true), diff --git a/Core/Config.h b/Core/Config.h index 5f3c5dec2032..5e691dc68623 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -106,6 +106,7 @@ struct Config { bool bScreenshotsAsPNG; bool bUseFFV1; bool bDumpFrames; + bool bDumpAudio; bool bEnableLogging; bool bDumpDecryptedEboot; bool bFullscreenOnDoubleclick; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index c2351f035b67..ef80700b1b32 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -501,6 +501,7 @@ AnySuitable AnySuitable + @@ -731,6 +732,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index a77ebd3d12fc..09949201de09 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -676,6 +676,7 @@ Core + @@ -1242,6 +1243,7 @@ Core + diff --git a/Core/HLE/__sceAudio.cpp b/Core/HLE/__sceAudio.cpp index 02a0b604cec9..1ffc92800d9e 100644 --- a/Core/HLE/__sceAudio.cpp +++ b/Core/HLE/__sceAudio.cpp @@ -32,6 +32,7 @@ #include "Core/Host.h" #include "Core/MemMapHelpers.h" #include "Core/Reporting.h" +#include "Core/System.h" #include "Core/HLE/__sceAudio.h" #include "Core/HLE/sceAudio.h" #include "Core/HLE/sceKernel.h" @@ -54,7 +55,7 @@ enum latency { }; int eventAudioUpdate = -1; -int eventHostAudioUpdate = -1; +int eventHostAudioUpdate = -1; int mixFrequency = 44100; const int hwSampleRate = 44100; @@ -67,6 +68,9 @@ static int audioHostIntervalCycles; static s32 *mixBuffer; +WaveFileWriter g_wave_writer; +bool m_logAudio; + // High and low watermarks, basically. For perfect emulation, the correct values are 0 and 1, respectively. // TODO: Tweak. Hm, there aren't actually even used currently... static int chanQueueMaxSizeFactor; @@ -134,6 +138,13 @@ void __AudioInit() { resampler.Clear(); CoreTiming::RegisterMHzChangeCallback(&__AudioCPUMHzChange); + + if (g_Config.bDumpAudio) + { + std::string audio_file_name = GetSysDirectory(DIRECTORY_VIDEO_DUMP) + "audiodump.wav"; + File::CreateEmptyFile(audio_file_name); + __StartLogAudio(audio_file_name); + } } void __AudioDoState(PointerWrap &p) { @@ -177,6 +188,11 @@ void __AudioShutdown() { mixBuffer = 0; for (u32 i = 0; i < PSP_AUDIO_CHANNEL_MAX + 1; i++) chans[i].clear(); + + if (g_Config.bDumpAudio) + { + __StopLogAudio(); + } } u32 __AudioEnqueue(AudioChannel &chan, int chanNum, bool blocking) { @@ -364,6 +380,15 @@ void __AudioUpdate() { if (g_Config.bEnableSound) { resampler.PushSamples(mixBuffer, hwBlockSize); + if (m_logAudio) + { + s16 *clamped_data = new s16[hwBlockSize * 2]; + + for (int i = 0; i < hwBlockSize * 2; i++) { + clamped_data[i] = clamp_s16(mixBuffer[i]); + } + g_wave_writer.AddStereoSamples(clamped_data, hwBlockSize); + } } } @@ -385,3 +410,32 @@ void __PushExternalAudio(const s32 *audio, int numSamples) { resampler.Clear(); } } + +void __StartLogAudio(const std::string& filename) +{ + if (!m_logAudio) + { + m_logAudio = true; + g_wave_writer.Start(filename, 44100); + g_wave_writer.SetSkipSilence(false); + NOTICE_LOG(SCEAUDIO, "Starting Audio logging"); + } + else + { + WARN_LOG(SCEAUDIO, "Audio logging has already been started"); + } +} + +void __StopLogAudio() +{ + if (m_logAudio) + { + m_logAudio = false; + g_wave_writer.Stop(); + NOTICE_LOG(SCEAUDIO, "Stopping Audio logging"); + } + else + { + WARN_LOG(SCEAUDIO, "Audio logging has already been stopped"); + } +} diff --git a/Core/HLE/__sceAudio.h b/Core/HLE/__sceAudio.h index 83948414bd52..99dcabd130a5 100644 --- a/Core/HLE/__sceAudio.h +++ b/Core/HLE/__sceAudio.h @@ -18,6 +18,7 @@ #pragma once #include "sceAudio.h" +#include "Core/WaveFile.h" struct AudioDebugStats { int buffered; @@ -45,3 +46,7 @@ void __AudioWakeThreads(AudioChannel &chan, int result); int __AudioMix(short *outstereo, int numSamples, int sampleRate); const AudioDebugStats *__AudioGetDebugStats(); void __PushExternalAudio(const s32 *audio, int numSamples); // Should not be used in-game, only at the menu! + +// Audio Dumping stuff +void __StartLogAudio(const std::string& filename); +void __StopLogAudio(); diff --git a/Core/HW/StereoResampler.cpp b/Core/HW/StereoResampler.cpp index bdd616c7fbfe..2b01f84b743a 100644 --- a/Core/HW/StereoResampler.cpp +++ b/Core/HW/StereoResampler.cpp @@ -38,7 +38,7 @@ StereoResampler::StereoResampler() : m_dma_mixer(this, 44100) { - // Some Android devices are v-synced to non-60Hz framerates. We simply timestretch audio to fit. + // Some Android devices are v-synced to non-60Hz framerates. We simply timestretch audio to fit. // TODO: should only do this if auto frameskip is off? float refresh = System_GetPropertyInt(SYSPROP_DISPLAY_REFRESH_RATE) / 1000.0f; @@ -120,7 +120,7 @@ unsigned int StereoResampler::MixerFifo::Mix(short* samples, unsigned int numSam if (offset < -MAX_FREQ_SHIFT) offset = -MAX_FREQ_SHIFT; aid_sample_rate_ = m_input_sample_rate + offset; - + /* Hm? u32 framelimit = SConfig::GetInstance().m_Framelimit; if (consider_framelimit && framelimit > 1) { diff --git a/Core/HW/StereoResampler.h b/Core/HW/StereoResampler.h index 77b55c7e2843..b0f44de3137f 100644 --- a/Core/HW/StereoResampler.h +++ b/Core/HW/StereoResampler.h @@ -25,6 +25,7 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" +#include "Core/WaveFile.h" struct AudioDebugStats; diff --git a/Core/WaveFile.cpp b/Core/WaveFile.cpp new file mode 100644 index 000000000000..cf21740b9bfa --- /dev/null +++ b/Core/WaveFile.cpp @@ -0,0 +1,116 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "Core/WaveFile.h" +#include "Common/CommonTypes.h" +#include "Common/MsgHandler.h" +#include "Core/Config.h" + +constexpr size_t WaveFileWriter::BUFFER_SIZE; + +WaveFileWriter::WaveFileWriter() +{ +} + +WaveFileWriter::~WaveFileWriter() +{ + Stop(); +} + +bool WaveFileWriter::Start(const std::string& filename, unsigned int HLESampleRate) +{ + // Check if the file is already open + if (file) + { + PanicAlert("The file %s was already open, the file header will not be written.", + filename.c_str()); + return false; + } + + file.Open(filename, "wb"); + if (!file) + { + PanicAlert("The file %s could not be opened for writing. Please check if it's already opened " + "by another program.", + filename.c_str()); + return false; + } + + audio_size = 0; + + // ----------------- + // Write file header + // ----------------- + Write4("RIFF"); + Write(100 * 1000 * 1000); // write big value in case the file gets truncated + Write4("WAVE"); + Write4("fmt "); + + Write(16); // size of fmt block + Write(0x00020001); // two channels, uncompressed + + const u32 sample_rate = HLESampleRate; + Write(sample_rate); + Write(sample_rate * 2 * 2); // two channels, 16bit + + Write(0x00100004); + Write4("data"); + Write(100 * 1000 * 1000 - 32); + + // We are now at offset 44 + if (file.Tell() != 44) + PanicAlert("Wrong offset: %lld", (long long)file.Tell()); + + return true; +} + +void WaveFileWriter::Stop() +{ + // u32 file_size = (u32)ftello(file); + file.Seek(4, SEEK_SET); + Write(audio_size + 36); + + file.Seek(40, SEEK_SET); + Write(audio_size); + + file.Close(); +} + +void WaveFileWriter::Write(u32 value) +{ + file.WriteArray(&value, 1); +} + +void WaveFileWriter::Write4(const char* ptr) +{ + file.WriteBytes(ptr, 4); +} + +void WaveFileWriter::AddStereoSamples(const short* sample_data, u32 count) +{ + if (!file) + PanicAlert("WaveFileWriter - file not open."); + + if (count > BUFFER_SIZE * 2) + PanicAlert("WaveFileWriter - buffer too small (count = %u).", count); + + if (skip_silence) + { + bool all_zero = true; + + for (u32 i = 0; i < count * 2; i++) + { + if (sample_data[i]) + all_zero = false; + } + + if (all_zero) + return; + } + + file.WriteBytes(sample_data, count * 4); + audio_size += count * 4; +} diff --git a/Core/WaveFile.h b/Core/WaveFile.h new file mode 100644 index 000000000000..273cedd46048 --- /dev/null +++ b/Core/WaveFile.h @@ -0,0 +1,44 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +// --------------------------------------------------------------------------------- +// Class: WaveFileWriter +// Description: Simple utility class to make it easy to write long 16-bit stereo +// audio streams to disk. +// Use Start() to start recording to a file, and AddStereoSamples to add wave data. +// The float variant will convert from -1.0-1.0 range and clamp. +// Alternatively, AddSamplesBE for big endian wave data. +// If Stop is not called when it destructs, the destructor will call Stop(). +// --------------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include "Common/CommonTypes.h" +#include "Common/FileUtil.h" + +class WaveFileWriter +{ +public: + WaveFileWriter(); + ~WaveFileWriter(); + + bool Start(const std::string& filename, unsigned int HLESampleRate); + void Stop(); + + void SetSkipSilence(bool skip) { skip_silence = skip; } + void AddStereoSamples(const short* sample_data, u32 count); + u32 GetAudioSize() const { return audio_size; } +private: + static constexpr size_t BUFFER_SIZE = 32 * 1024; + + File::IOFile file; + bool skip_silence = false; + u32 audio_size = 0; + std::array conv_buffer{}; + void Write(u32 value); + void Write4(const char* ptr); +}; + diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 0977717e5b54..5f2ddeac0c50 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -678,6 +678,7 @@ void GameSettingsScreen::CreateViews() { systemSettings->Add(new CheckBox(&g_Config.bScreenshotsAsPNG, sy->T("Screenshots as PNG"))); systemSettings->Add(new CheckBox(&g_Config.bDumpFrames, sy->T("Dump Frames"))); systemSettings->Add(new CheckBox(&g_Config.bUseFFV1, sy->T("Use FFV1 for Frame Dumps"))); + systemSettings->Add(new CheckBox(&g_Config.bDumpAudio, sy->T("Dump Audio"))); #endif systemSettings->Add(new CheckBox(&g_Config.bDayLightSavings, sy->T("Day Light Saving"))); static const char *dateFormat[] = { "YYYYMMDD", "MMDDYYYY", "DDMMYYYY"}; diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index d528e82a62b0..ac500d5e6fbd 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -276,6 +276,7 @@ namespace MainWindow { // Movie menu TranslateMenuItem(menu, ID_MOVIE_DUMPFRAMES); TranslateMenuItem(menu, ID_MOVIE_USEFFV1); + TranslateMenuItem(menu, ID_MOVIE_DUMPAUDIO); // Skip display multipliers x1-x10 TranslateMenuItem(menu, ID_OPTIONS_FULLSCREEN, L"\tAlt+Return, F11"); @@ -942,6 +943,10 @@ namespace MainWindow { g_Config.bUseFFV1 = !g_Config.bUseFFV1; break; + case ID_MOVIE_DUMPAUDIO: + g_Config.bDumpAudio = !g_Config.bDumpAudio; + break; + default: { // Handle the dynamic shader switching here. @@ -982,6 +987,7 @@ namespace MainWindow { CHECKITEM(ID_OPTIONS_IGNOREWINKEY, g_Config.bIgnoreWindowsKey); CHECKITEM(ID_MOVIE_DUMPFRAMES, g_Config.bDumpFrames); CHECKITEM(ID_MOVIE_USEFFV1, g_Config.bUseFFV1); + CHECKITEM(ID_MOVIE_DUMPAUDIO, g_Config.bDumpAudio); static const int displayrotationitems[] = { ID_EMULATION_ROTATION_H, diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 91e8c85745eb..a4129d136a67 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -447,8 +447,9 @@ BEGIN POPUP "Movie" BEGIN - MENUITEM "Dump Frames", ID_MOVIE_DUMPFRAMES - MENUITEM "Lossless Codec (FFV1)", ID_MOVIE_USEFFV1 + MENUITEM "Dump Frames", ID_MOVIE_DUMPFRAMES + MENUITEM "Lossless Codec (FFV1)", ID_MOVIE_USEFFV1 + MENUITEM "Dump Audio", ID_MOVIE_DUMPAUDIO END POPUP "Options" diff --git a/Windows/resource.h b/Windows/resource.h index 4f9d9a069fcf..40e0cefa8331 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -330,6 +330,7 @@ #define ID_GEDBG_WATCH 40164 #define ID_MOVIE_DUMPFRAMES 40165 #define ID_MOVIE_USEFFV1 40166 +#define ID_MOVIE_DUMPAUDIO 40167 // Dummy option to let the buffered rendering hotkey cycle through all the options. #define ID_OPTIONS_BUFFEREDRENDERINGDUMMY 40500 From 9fb152ce017420dcacde3d7b724cb0af0f1dac1a Mon Sep 17 00:00:00 2001 From: Chris Burgener Date: Sat, 3 Sep 2016 14:28:51 -0400 Subject: [PATCH 6/9] Fix non-Windows builds --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f29a9eba6fef..bfd8eb421285 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1218,6 +1218,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/HDRemaster.cpp Core/HDRemaster.h Core/ThreadEventQueue.h + Core/WaveFile.cpp + Core/WaveFile.h Core/Debugger/Breakpoints.cpp Core/Debugger/Breakpoints.h Core/Debugger/DebugInterface.h From 7d089a0f76b96c4e7985594f30c056ae8187e4a3 Mon Sep 17 00:00:00 2001 From: Chris Burgener Date: Sat, 3 Sep 2016 18:26:01 -0400 Subject: [PATCH 7/9] Clean up various files --- CMakeLists.txt | 6 +- Core/AVIDump.cpp | 121 ++++---------------------------------- Core/CoreTiming.cpp | 5 -- Core/CoreTiming.h | 1 - Core/HLE/__sceAudio.cpp | 31 +++++++--- Core/HLE/__sceAudio.h | 1 - Core/HLE/sceAudio.cpp | 4 +- Core/HW/StereoResampler.h | 1 - Core/SaveState.cpp | 1 - Core/Screenshot.cpp | 2 +- Core/Screenshot.h | 4 ++ Core/System.cpp | 6 +- Core/System.h | 3 +- UI/GameSettingsScreen.cpp | 6 +- Windows/ppsspp.rc | 6 +- 15 files changed, 54 insertions(+), 144 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bfd8eb421285..12b7b7874c7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1193,7 +1193,9 @@ endif() if (NOT MOBILE_DEVICE) set(CoreExtra ${CoreExtra} Core/AVIDump.cpp - Core/AVIDump.h) + Core/AVIDump.h + Core/WaveFile.cpp + Core/WaveFile.h) endif() if(ARMV7) @@ -1218,8 +1220,6 @@ add_library(${CoreLibName} ${CoreLinkType} Core/HDRemaster.cpp Core/HDRemaster.h Core/ThreadEventQueue.h - Core/WaveFile.cpp - Core/WaveFile.h Core/Debugger/Breakpoints.cpp Core/Debugger/Breakpoints.h Core/Debugger/DebugInterface.h diff --git a/Core/AVIDump.cpp b/Core/AVIDump.cpp index 25ab77a940b1..16095915c6c5 100644 --- a/Core/AVIDump.cpp +++ b/Core/AVIDump.cpp @@ -26,10 +26,6 @@ extern "C" { #include "Core/Screenshot.h" #include "GPU/Common/GPUDebugInterface.h" -#ifdef _WIN32 -#include "GPU/Directx9/GPU_DX9.h" -#endif -#include "GPU/GLES/GPU_GLES.h" #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 28, 1) #define av_frame_alloc avcodec_alloc_frame @@ -48,6 +44,7 @@ static bool s_start_dumping = false; static int s_current_width; static int s_current_height; static int s_file_index = 0; +static GPUDebugBuffer buf; static void InitAVCodec() { @@ -80,10 +77,10 @@ bool AVIDump::CreateAVI() s_format_context = avformat_alloc_context(); std::stringstream s_file_index_str; s_file_index_str << s_file_index; - snprintf(s_format_context->filename, sizeof(s_format_context->filename), "%s", (GetSysDirectory(DIRECTORY_VIDEO_DUMP) + "framedump" + s_file_index_str.str() + ".avi").c_str()); + snprintf(s_format_context->filename, sizeof(s_format_context->filename), "%s", (GetSysDirectory(DIRECTORY_VIDEO) + "framedump" + s_file_index_str.str() + ".avi").c_str()); // Make sure that the path exists - if (!File::Exists(GetSysDirectory(DIRECTORY_VIDEO_DUMP))) - File::CreateDir(GetSysDirectory(DIRECTORY_VIDEO_DUMP)); + if (!File::Exists(GetSysDirectory(DIRECTORY_VIDEO))) + File::CreateDir(GetSysDirectory(DIRECTORY_VIDEO)); if (File::Exists(s_format_context->filename)) File::Delete(s_format_context->filename); @@ -150,101 +147,8 @@ static void PreparePacket(AVPacket* pkt) pkt->stream_index = s_stream->index; } -static const u8 *ConvertBufferTo888RGB(const GPUDebugBuffer &buf, u8 *&temp, u32 &w, u32 &h) { - // The temp buffer will be freed by the caller if set, and can be the return value. - temp = nullptr; - - w = std::min(w, buf.GetStride()); - h = std::min(h, buf.GetHeight()); - - const u8 *buffer = buf.GetData(); - if (buf.GetFlipped() && buf.GetFormat() == GPU_DBG_FORMAT_888_RGB) { - // Silly OpenGL reads upside down, we flip to another buffer for simplicity. - temp = new u8[3 * w * h]; - for (u32 y = 0; y < h; y++) { - memcpy(temp + y * w * 3, buffer + (buf.GetHeight() - y - 1) * buf.GetStride() * 3, w * 3); - } - buffer = temp; - } - else if (buf.GetFormat() != GPU_DBG_FORMAT_888_RGB) { - // Let's boil it down to how we need to interpret the bits. - int baseFmt = buf.GetFormat() & ~(GPU_DBG_FORMAT_REVERSE_FLAG | GPU_DBG_FORMAT_BRSWAP_FLAG); - bool rev = (buf.GetFormat() & GPU_DBG_FORMAT_REVERSE_FLAG) != 0; - bool brswap = (buf.GetFormat() & GPU_DBG_FORMAT_BRSWAP_FLAG) != 0; - bool flip = buf.GetFlipped(); - - temp = new u8[3 * w * h]; - - // This is pretty inefficient. - const u16 *buf16 = (const u16 *)buffer; - const u32 *buf32 = (const u32 *)buffer; - for (u32 y = 0; y < h; y++) { - for (u32 x = 0; x < w; x++) { - u8 *dst; - if (flip) { - dst = &temp[(h - y - 1) * w * 3 + x * 3]; - } - else { - dst = &temp[y * w * 3 + x * 3]; - } - - u8 &r = brswap ? dst[2] : dst[0]; - u8 &g = dst[1]; - u8 &b = brswap ? dst[0] : dst[2]; - - u32 src; - switch (baseFmt) { - case GPU_DBG_FORMAT_565: - src = buf16[y * buf.GetStride() + x]; - if (rev) { - src = bswap16(src); - } - r = Convert5To8((src >> 0) & 0x1F); - g = Convert6To8((src >> 5) & 0x3F); - b = Convert5To8((src >> 11) & 0x1F); - break; - case GPU_DBG_FORMAT_5551: - src = buf16[y * buf.GetStride() + x]; - if (rev) { - src = bswap16(src); - } - r = Convert5To8((src >> 0) & 0x1F); - g = Convert5To8((src >> 5) & 0x1F); - b = Convert5To8((src >> 10) & 0x1F); - break; - case GPU_DBG_FORMAT_4444: - src = buf16[y * buf.GetStride() + x]; - if (rev) { - src = bswap16(src); - } - r = Convert4To8((src >> 0) & 0xF); - g = Convert4To8((src >> 4) & 0xF); - b = Convert4To8((src >> 8) & 0xF); - break; - case GPU_DBG_FORMAT_8888: - src = buf32[y * buf.GetStride() + x]; - if (rev) { - src = bswap32(src); - } - r = (src >> 0) & 0xFF; - g = (src >> 8) & 0xFF; - b = (src >> 16) & 0xFF; - break; - default: - ERROR_LOG(COMMON, "Unsupported framebuffer format for screenshot: %d", buf.GetFormat()); - return nullptr; - } - } - } - buffer = temp; - } - - return buffer; -} - void AVIDump::AddFrame() { - GPUDebugBuffer buf; gpuDebug->GetCurrentFramebuffer(buf); u32 w = buf.GetStride(); u32 h = buf.GetHeight(); @@ -257,14 +161,10 @@ void AVIDump::AddFrame() s_src_frame->width = s_width; s_src_frame->height = s_height; - // Convert image from BGR24 to desired pixel format, and scale to initial - // width and height - if ((s_sws_context = - sws_getCachedContext(s_sws_context, w, h, AV_PIX_FMT_RGB24, s_width, s_height, - s_stream->codec->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr))) + // Convert image from BGR24 to desired pixel format, and scale to initial width and height + if ((s_sws_context = sws_getCachedContext(s_sws_context, w, h, AV_PIX_FMT_RGB24, s_width, s_height, s_stream->codec->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr))) { - sws_scale(s_sws_context, s_src_frame->data, s_src_frame->linesize, 0, h, - s_scaled_frame->data, s_scaled_frame->linesize); + sws_scale(s_sws_context, s_src_frame->data, s_src_frame->linesize, 0, h, s_scaled_frame->data, s_scaled_frame->linesize); } s_scaled_frame->format = s_stream->codec->pix_fmt; @@ -344,10 +244,9 @@ void AVIDump::CloseFile() void AVIDump::CheckResolution(int width, int height) { // We check here to see if the requested width and height have changed since the last frame which - // was dumped, then create a new file accordingly. However, is it possible for the height - // (possibly width as well, but no examples known) to have a value of zero. This can occur as the - // VI is able to be set to a zero value for height/width to disable output. If this is the case, - // simply keep the last known resolution of the video for the added frame. + // was dumped, then create a new file accordingly. However, is it possible for the width and height + // to have a value of zero. If this is the case, simply keep the last known resolution of the video + // for the added frame. if ((width != s_current_width || height != s_current_height) && (width > 0 && height > 0)) { int temp_file_index = s_file_index; diff --git a/Core/CoreTiming.cpp b/Core/CoreTiming.cpp index 951498ef61bd..7999cb18ad46 100644 --- a/Core/CoreTiming.cpp +++ b/Core/CoreTiming.cpp @@ -240,11 +240,6 @@ u64 GetTicks() return (u64)globalTimer + slicelength - currentMIPS->downcount; } -u64 GetTicksPerSecond() -{ - return CPU_HZ; -} - u64 GetIdleTicks() { return (u64)idledCycles; diff --git a/Core/CoreTiming.h b/Core/CoreTiming.h index 82f28aa8f796..ee7a26a714bb 100644 --- a/Core/CoreTiming.h +++ b/Core/CoreTiming.h @@ -82,7 +82,6 @@ namespace CoreTiming u64 GetIdleTicks(); u64 GetGlobalTimeUs(); u64 GetGlobalTimeUsScaled(); - u64 GetTicksPerSecond(); // Returns the event_type identifier. int RegisterEvent(const char *name, TimedCallback callback); diff --git a/Core/HLE/__sceAudio.cpp b/Core/HLE/__sceAudio.cpp index 1ffc92800d9e..84483a035015 100644 --- a/Core/HLE/__sceAudio.cpp +++ b/Core/HLE/__sceAudio.cpp @@ -33,6 +33,9 @@ #include "Core/MemMapHelpers.h" #include "Core/Reporting.h" #include "Core/System.h" +#ifndef MOBILE_DEVICE +#include "Core/WaveFile.h" +#endif #include "Core/HLE/__sceAudio.h" #include "Core/HLE/sceAudio.h" #include "Core/HLE/sceKernel.h" @@ -67,9 +70,11 @@ static int audioIntervalCycles; static int audioHostIntervalCycles; static s32 *mixBuffer; - +static s16 *clampedMixBuffer; +#ifndef MOBILE_DEVICE WaveFileWriter g_wave_writer; -bool m_logAudio; +static bool m_logAudio; +#endif // High and low watermarks, basically. For perfect emulation, the correct values are 0 and 1, respectively. // TODO: Tweak. Hm, there aren't actually even used currently... @@ -134,17 +139,21 @@ void __AudioInit() { chans[i].clear(); mixBuffer = new s32[hwBlockSize * 2]; + clampedMixBuffer = new s16[hwBlockSize * 2]; memset(mixBuffer, 0, hwBlockSize * 2 * sizeof(s32)); resampler.Clear(); CoreTiming::RegisterMHzChangeCallback(&__AudioCPUMHzChange); - +#ifndef MOBILE_DEVICE if (g_Config.bDumpAudio) { - std::string audio_file_name = GetSysDirectory(DIRECTORY_VIDEO_DUMP) + "audiodump.wav"; + std::string audio_file_name = GetSysDirectory(DIRECTORY_AUDIO) + "audiodump.wav"; + // Create the path just in case it doesn't exist + File::CreateDir(GetSysDirectory(DIRECTORY_AUDIO)); File::CreateEmptyFile(audio_file_name); __StartLogAudio(audio_file_name); } +#endif } void __AudioDoState(PointerWrap &p) { @@ -184,15 +193,18 @@ void __AudioDoState(PointerWrap &p) { void __AudioShutdown() { delete [] mixBuffer; + delete [] clampedMixBuffer; mixBuffer = 0; for (u32 i = 0; i < PSP_AUDIO_CHANNEL_MAX + 1; i++) chans[i].clear(); +#ifndef MOBILE_DEVICE if (g_Config.bDumpAudio) { __StopLogAudio(); } +#endif } u32 __AudioEnqueue(AudioChannel &chan, int chanNum, bool blocking) { @@ -380,15 +392,15 @@ void __AudioUpdate() { if (g_Config.bEnableSound) { resampler.PushSamples(mixBuffer, hwBlockSize); +#ifndef MOBILE_DEVICE if (m_logAudio) { - s16 *clamped_data = new s16[hwBlockSize * 2]; - for (int i = 0; i < hwBlockSize * 2; i++) { - clamped_data[i] = clamp_s16(mixBuffer[i]); + clampedMixBuffer[i] = clamp_s16(mixBuffer[i]); } - g_wave_writer.AddStereoSamples(clamped_data, hwBlockSize); + g_wave_writer.AddStereoSamples(clampedMixBuffer, hwBlockSize); } +#endif } } @@ -410,7 +422,7 @@ void __PushExternalAudio(const s32 *audio, int numSamples) { resampler.Clear(); } } - +#ifndef MOBILE_DEVICE void __StartLogAudio(const std::string& filename) { if (!m_logAudio) @@ -439,3 +451,4 @@ void __StopLogAudio() WARN_LOG(SCEAUDIO, "Audio logging has already been stopped"); } } +#endif diff --git a/Core/HLE/__sceAudio.h b/Core/HLE/__sceAudio.h index 99dcabd130a5..87365fb3612a 100644 --- a/Core/HLE/__sceAudio.h +++ b/Core/HLE/__sceAudio.h @@ -18,7 +18,6 @@ #pragma once #include "sceAudio.h" -#include "Core/WaveFile.h" struct AudioDebugStats { int buffered; diff --git a/Core/HLE/sceAudio.cpp b/Core/HLE/sceAudio.cpp index 93acd0a1f53b..cdbc811e4768 100644 --- a/Core/HLE/sceAudio.cpp +++ b/Core/HLE/sceAudio.cpp @@ -211,7 +211,7 @@ static u32 sceAudioChReserve(int chan, u32 sampleCount, u32 format) { ERROR_LOG(SCEAUDIO, "sceAudioChReserve - no channels remaining"); return SCE_ERROR_AUDIO_NO_CHANNELS_AVAILABLE; } - } + } if ((u32)chan >= PSP_AUDIO_CHANNEL_MAX) { ERROR_LOG(SCEAUDIO, "sceAudioChReserve(%08x, %08x, %08x) - bad channel", chan, sampleCount, format); return SCE_ERROR_AUDIO_INVALID_CHANNEL; @@ -457,7 +457,7 @@ static u32 sceAudioRoutingGetVolumeMode() { return defaultRoutingVolMode; } -const HLEFunction sceAudio[] = +const HLEFunction sceAudio[] = { // Newer simplified single channel audio output. Presumably for games that use Atrac3 // directly from Sas instead of playing it on a separate audio channel. diff --git a/Core/HW/StereoResampler.h b/Core/HW/StereoResampler.h index b0f44de3137f..77b55c7e2843 100644 --- a/Core/HW/StereoResampler.h +++ b/Core/HW/StereoResampler.h @@ -25,7 +25,6 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" -#include "Core/WaveFile.h" struct AudioDebugStats; diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index f949426e24e9..74a698b58a23 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -25,7 +25,6 @@ #include "Common/FileUtil.h" #include "Common/ChunkFile.h" -#include "Core/AVIDump.h" #include "Core/SaveState.h" #include "Core/Config.h" #include "Core/Core.h" diff --git a/Core/Screenshot.cpp b/Core/Screenshot.cpp index e2175d3bd4fc..35cd517c9f5a 100644 --- a/Core/Screenshot.cpp +++ b/Core/Screenshot.cpp @@ -125,7 +125,7 @@ static bool WriteScreenshotToPNG(png_imagep image, const char *filename, int con } #endif -static const u8 *ConvertBufferTo888RGB(const GPUDebugBuffer &buf, u8 *&temp, u32 &w, u32 &h) { +const u8 *ConvertBufferTo888RGB(const GPUDebugBuffer &buf, u8 *&temp, u32 &w, u32 &h) { // The temp buffer will be freed by the caller if set, and can be the return value. temp = nullptr; diff --git a/Core/Screenshot.h b/Core/Screenshot.h index 3cecc4547e4a..1f4bdd6d285b 100644 --- a/Core/Screenshot.h +++ b/Core/Screenshot.h @@ -17,6 +17,8 @@ #pragma once +class GPUDebugBuffer; + enum ScreenshotFormat { SCREENSHOT_PNG, SCREENSHOT_JPG, @@ -30,4 +32,6 @@ enum ScreenshotType { SCREENSHOT_RENDER, }; +const u8 * ConvertBufferTo888RGB(const GPUDebugBuffer & buf, u8 *& temp, u32 & w, u32 & h); + bool TakeGameScreenshot(const char *filename, ScreenshotFormat fmt, ScreenshotType type, int *width = nullptr, int *height = nullptr, int maxRes = -1); diff --git a/Core/System.cpp b/Core/System.cpp index 7f03be126369..03976499c9c6 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -593,8 +593,10 @@ std::string GetSysDirectory(PSPDirectories directoryType) { return g_Config.appCacheDirectory; } return g_Config.memStickDirectory + "PSP/SYSTEM/CACHE/"; - case DIRECTORY_VIDEO_DUMP: - return g_Config.memStickDirectory + "PSP/FRAMEDUMP/"; + case DIRECTORY_VIDEO: + return g_Config.memStickDirectory + "PSP/VIDEO/"; + case DIRECTORY_AUDIO: + return g_Config.memStickDirectory + "PSP/AUDIO/"; // Just return the memory stick root if we run into some sort of problem. default: ERROR_LOG(FILESYS, "Unknown directory type."); diff --git a/Core/System.h b/Core/System.h index 2aef7c21e8a9..b9c5feadf6c2 100644 --- a/Core/System.h +++ b/Core/System.h @@ -47,7 +47,8 @@ enum PSPDirectories { DIRECTORY_CACHE, DIRECTORY_TEXTURES, DIRECTORY_APP_CACHE, // Use the OS app cache if available - DIRECTORY_VIDEO_DUMP + DIRECTORY_VIDEO, + DIRECTORY_AUDIO }; class GraphicsContext; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 5f2ddeac0c50..ffa28b3be9c6 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -676,9 +676,9 @@ void GameSettingsScreen::CreateViews() { #if defined(_WIN32) || (defined(USING_QT_UI) && !defined(MOBILE_DEVICE)) // Screenshot functionality is not yet available on non-Windows/non-Qt systemSettings->Add(new CheckBox(&g_Config.bScreenshotsAsPNG, sy->T("Screenshots as PNG"))); - systemSettings->Add(new CheckBox(&g_Config.bDumpFrames, sy->T("Dump Frames"))); - systemSettings->Add(new CheckBox(&g_Config.bUseFFV1, sy->T("Use FFV1 for Frame Dumps"))); - systemSettings->Add(new CheckBox(&g_Config.bDumpAudio, sy->T("Dump Audio"))); + systemSettings->Add(new CheckBox(&g_Config.bDumpFrames, sy->T("Record Display"))); + systemSettings->Add(new CheckBox(&g_Config.bUseFFV1, sy->T("Use Lossless Video Codec (FFV1)"))); + systemSettings->Add(new CheckBox(&g_Config.bDumpAudio, sy->T("Export Audio"))); #endif systemSettings->Add(new CheckBox(&g_Config.bDayLightSavings, sy->T("Day Light Saving"))); static const char *dateFormat[] = { "YYYYMMDD", "MMDDYYYY", "DDMMYYYY"}; diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index a4129d136a67..5962e1611bec 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -447,9 +447,9 @@ BEGIN POPUP "Movie" BEGIN - MENUITEM "Dump Frames", ID_MOVIE_DUMPFRAMES - MENUITEM "Lossless Codec (FFV1)", ID_MOVIE_USEFFV1 - MENUITEM "Dump Audio", ID_MOVIE_DUMPAUDIO + MENUITEM "Record Display", ID_MOVIE_DUMPFRAMES + MENUITEM "Use Lossless Video Codec (FFV1)", ID_MOVIE_USEFFV1 + MENUITEM "Export Audio", ID_MOVIE_DUMPAUDIO END POPUP "Options" From 1e7eab9d267364e94e954ec644bc326434694ba4 Mon Sep 17 00:00:00 2001 From: Chris Burgener Date: Sun, 4 Sep 2016 12:12:28 -0400 Subject: [PATCH 8/9] Rename Movie menu to Record and move to File --- Windows/MainWindowMenu.cpp | 26 +++++++++++++------------- Windows/ppsspp.rc | 15 ++++++++------- Windows/resource.h | 6 +++--- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index ac500d5e6fbd..c9916aefef70 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -74,12 +74,12 @@ namespace MainWindow { MENU_FILE = 0, MENU_EMULATION = 1, MENU_DEBUG = 2, - MENU_MOVIE = 3, - MENU_OPTIONS = 4, - MENU_HELP = 5, + MENU_OPTIONS = 3, + MENU_HELP = 4, // File submenus SUBMENU_FILE_SAVESTATE_SLOT = 6, + SUBMENU_FILE_RECORD = 11, // Emulation submenus SUBMENU_DISPLAY_ROTATION = 4, @@ -218,7 +218,6 @@ namespace MainWindow { TranslateMenu(menu, "File", MENU_FILE); TranslateMenu(menu, "Emulation", MENU_EMULATION); TranslateMenu(menu, "Debugging", MENU_DEBUG); - TranslateMenu(menu, "Movie", MENU_MOVIE); TranslateMenu(menu, "Game Settings", MENU_OPTIONS); TranslateMenu(menu, "Help", MENU_HELP); @@ -234,6 +233,7 @@ namespace MainWindow { TranslateMenuItem(menu, ID_FILE_QUICKSAVESTATE, L"\tF2"); TranslateMenuItem(menu, ID_FILE_LOADSTATEFILE); TranslateMenuItem(menu, ID_FILE_SAVESTATEFILE); + TranslateSubMenu(menu, "Record", MENU_FILE, SUBMENU_FILE_RECORD); TranslateMenuItem(menu, ID_FILE_EXIT, L"\tAlt+F4"); // Emulation menu @@ -274,9 +274,9 @@ namespace MainWindow { TranslateMenuItem(menu, ID_OPTIONS_DISPLAY_LAYOUT); // Movie menu - TranslateMenuItem(menu, ID_MOVIE_DUMPFRAMES); - TranslateMenuItem(menu, ID_MOVIE_USEFFV1); - TranslateMenuItem(menu, ID_MOVIE_DUMPAUDIO); + TranslateMenuItem(menu, ID_FILE_DUMPFRAMES); + TranslateMenuItem(menu, ID_FILE_USEFFV1); + TranslateMenuItem(menu, ID_FILE_DUMPAUDIO); // Skip display multipliers x1-x10 TranslateMenuItem(menu, ID_OPTIONS_FULLSCREEN, L"\tAlt+Return, F11"); @@ -935,15 +935,15 @@ namespace MainWindow { g_TakeScreenshot = true; break; - case ID_MOVIE_DUMPFRAMES: + case ID_FILE_DUMPFRAMES: g_Config.bDumpFrames = !g_Config.bDumpFrames; break; - case ID_MOVIE_USEFFV1: + case ID_FILE_USEFFV1: g_Config.bUseFFV1 = !g_Config.bUseFFV1; break; - case ID_MOVIE_DUMPAUDIO: + case ID_FILE_DUMPAUDIO: g_Config.bDumpAudio = !g_Config.bDumpAudio; break; @@ -985,9 +985,9 @@ namespace MainWindow { CHECKITEM(ID_TEXTURESCALING_DEPOSTERIZE, g_Config.bTexDeposterize); CHECKITEM(ID_EMULATION_CHEATS, g_Config.bEnableCheats); CHECKITEM(ID_OPTIONS_IGNOREWINKEY, g_Config.bIgnoreWindowsKey); - CHECKITEM(ID_MOVIE_DUMPFRAMES, g_Config.bDumpFrames); - CHECKITEM(ID_MOVIE_USEFFV1, g_Config.bUseFFV1); - CHECKITEM(ID_MOVIE_DUMPAUDIO, g_Config.bDumpAudio); + CHECKITEM(ID_FILE_DUMPFRAMES, g_Config.bDumpFrames); + CHECKITEM(ID_FILE_USEFFV1, g_Config.bUseFFV1); + CHECKITEM(ID_FILE_DUMPAUDIO, g_Config.bDumpAudio); static const int displayrotationitems[] = { ID_EMULATION_ROTATION_H, diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 5962e1611bec..233dfd8105df 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -404,6 +404,14 @@ BEGIN MENUITEM "Save State", ID_FILE_QUICKSAVESTATE MENUITEM "Load State File...", ID_FILE_LOADSTATEFILE MENUITEM "Save State File...", ID_FILE_SAVESTATEFILE + + POPUP "Record" + BEGIN + MENUITEM "Record Display", ID_FILE_DUMPFRAMES + MENUITEM "Use Lossless Video Codec (FFV1)", ID_FILE_USEFFV1 + MENUITEM SEPARATOR + MENUITEM "Record Audio", ID_FILE_DUMPAUDIO + END MENUITEM SEPARATOR MENUITEM "Exit", ID_FILE_EXIT END @@ -445,13 +453,6 @@ BEGIN MENUITEM "Memory View...", ID_DEBUG_MEMORYVIEW END - POPUP "Movie" - BEGIN - MENUITEM "Record Display", ID_MOVIE_DUMPFRAMES - MENUITEM "Use Lossless Video Codec (FFV1)", ID_MOVIE_USEFFV1 - MENUITEM "Export Audio", ID_MOVIE_DUMPAUDIO - END - POPUP "Options" BEGIN MENUITEM "Keep PPSSPP On Top", ID_OPTIONS_TOPMOST diff --git a/Windows/resource.h b/Windows/resource.h index 40e0cefa8331..37e054bcaa2d 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -328,9 +328,9 @@ #define IDC_GEDBG_BREAKTARGET 40162 #define ID_GEDBG_COPYALL 40163 #define ID_GEDBG_WATCH 40164 -#define ID_MOVIE_DUMPFRAMES 40165 -#define ID_MOVIE_USEFFV1 40166 -#define ID_MOVIE_DUMPAUDIO 40167 +#define ID_FILE_DUMPFRAMES 40165 +#define ID_FILE_USEFFV1 40166 +#define ID_FILE_DUMPAUDIO 40167 // Dummy option to let the buffered rendering hotkey cycle through all the options. #define ID_OPTIONS_BUFFEREDRENDERINGDUMMY 40500 From b22f785769e4dba62909162a157dbe89eb16f162 Mon Sep 17 00:00:00 2001 From: Chris Burgener Date: Sun, 4 Sep 2016 12:52:14 -0400 Subject: [PATCH 9/9] Fix Symbian Builds --- Core/AVIDump.cpp | 2 ++ Core/AVIDump.h | 4 +++- Core/WaveFile.cpp | 3 ++- Core/WaveFile.h | 5 +++-- UI/GameSettingsScreen.cpp | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Core/AVIDump.cpp b/Core/AVIDump.cpp index 16095915c6c5..9e0680dc69f9 100644 --- a/Core/AVIDump.cpp +++ b/Core/AVIDump.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#ifndef MOBILE_DEVICE #if defined(__FreeBSD__) #define __STDC_CONSTANT_MACROS 1 #endif @@ -257,3 +258,4 @@ void AVIDump::CheckResolution(int width, int height) s_current_height = height; } } +#endif diff --git a/Core/AVIDump.h b/Core/AVIDump.h index 5288d25cd099..07147d578057 100644 --- a/Core/AVIDump.h +++ b/Core/AVIDump.h @@ -1,6 +1,7 @@ // Copyright 2008 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. +#ifndef MOBILE_DEVICE #pragma once @@ -17,4 +18,5 @@ class AVIDump static bool Start(int w, int h); static void AddFrame(); static void Stop(); -}; \ No newline at end of file +}; +#endif diff --git a/Core/WaveFile.cpp b/Core/WaveFile.cpp index cf21740b9bfa..76df969f61ee 100644 --- a/Core/WaveFile.cpp +++ b/Core/WaveFile.cpp @@ -1,7 +1,7 @@ // Copyright 2008 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. - +#ifndef MOBILE_DEVICE #include #include "Core/WaveFile.h" @@ -114,3 +114,4 @@ void WaveFileWriter::AddStereoSamples(const short* sample_data, u32 count) file.WriteBytes(sample_data, count * 4); audio_size += count * 4; } +#endif diff --git a/Core/WaveFile.h b/Core/WaveFile.h index 273cedd46048..39bcf483a475 100644 --- a/Core/WaveFile.h +++ b/Core/WaveFile.h @@ -7,12 +7,11 @@ // Description: Simple utility class to make it easy to write long 16-bit stereo // audio streams to disk. // Use Start() to start recording to a file, and AddStereoSamples to add wave data. -// The float variant will convert from -1.0-1.0 range and clamp. -// Alternatively, AddSamplesBE for big endian wave data. // If Stop is not called when it destructs, the destructor will call Stop(). // --------------------------------------------------------------------------------- #pragma once +#ifndef MOBILE_DEVICE #include #include @@ -42,3 +41,5 @@ class WaveFileWriter void Write4(const char* ptr); }; +#endif + diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index ffa28b3be9c6..a241fcb465b7 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -678,7 +678,7 @@ void GameSettingsScreen::CreateViews() { systemSettings->Add(new CheckBox(&g_Config.bScreenshotsAsPNG, sy->T("Screenshots as PNG"))); systemSettings->Add(new CheckBox(&g_Config.bDumpFrames, sy->T("Record Display"))); systemSettings->Add(new CheckBox(&g_Config.bUseFFV1, sy->T("Use Lossless Video Codec (FFV1)"))); - systemSettings->Add(new CheckBox(&g_Config.bDumpAudio, sy->T("Export Audio"))); + systemSettings->Add(new CheckBox(&g_Config.bDumpAudio, sy->T("Record Audio"))); #endif systemSettings->Add(new CheckBox(&g_Config.bDayLightSavings, sy->T("Day Light Saving"))); static const char *dateFormat[] = { "YYYYMMDD", "MMDDYYYY", "DDMMYYYY"};