summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon/AVIDump.cpp
diff options
context:
space:
mode:
authorJules Blok <jules.blok@gmail.com>2015-06-10 00:00:12 +0200
committerJules Blok <jules.blok@gmail.com>2015-06-10 00:00:12 +0200
commit7dfced21a2e5aac7195e0e1ad76468e30306766b (patch)
treebd671b639d3d911460b767caabad8fc8a759a6bf /Source/Core/VideoCommon/AVIDump.cpp
parentc25be031fc1031d795157d8344b87b7841145197 (diff)
parent7b0a65e295c784f9d573f2ef52d4e2a7b9bb2889 (diff)
Merge branch 'master' into stable
Diffstat (limited to 'Source/Core/VideoCommon/AVIDump.cpp')
-rw-r--r--Source/Core/VideoCommon/AVIDump.cpp552
1 files changed, 552 insertions, 0 deletions
diff --git a/Source/Core/VideoCommon/AVIDump.cpp b/Source/Core/VideoCommon/AVIDump.cpp
new file mode 100644
index 0000000000..fe5b0050e7
--- /dev/null
+++ b/Source/Core/VideoCommon/AVIDump.cpp
@@ -0,0 +1,552 @@
+// 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 <string>
+
+#include "Common/CommonPaths.h"
+#include "Common/FileUtil.h"
+#include "Common/StringUtil.h"
+#include "Common/Logging/Log.h"
+
+#include "Core/CoreTiming.h"
+#include "Core/HW/SystemTimers.h"
+#include "Core/HW/VideoInterface.h" //for TargetRefreshRate
+#include "VideoCommon/AVIDump.h"
+#include "VideoCommon/VideoConfig.h"
+
+#ifdef _WIN32
+
+#include "tchar.h"
+
+#include <cstdio>
+#include <cstring>
+#include <vfw.h>
+#include <winerror.h>
+
+#include "Core/ConfigManager.h" // for PAL60
+#include "Core/CoreTiming.h"
+
+static HWND s_emu_wnd;
+static LONG s_byte_buffer;
+static LONG s_frame_count;
+static LONG s_total_bytes;
+static PAVIFILE s_file;
+static int s_width;
+static int s_height;
+static int s_file_count;
+static u64 s_last_frame;
+static PAVISTREAM s_stream;
+static PAVISTREAM s_stream_compressed;
+static int s_frame_rate;
+static AVISTREAMINFO s_header;
+static AVICOMPRESSOPTIONS s_options;
+static AVICOMPRESSOPTIONS* s_array_options[1];
+static BITMAPINFOHEADER s_bitmap;
+// the CFR dump design doesn't let you dump until you know the NEXT timecode.
+// so we have to save a frame and always be behind
+static void* s_stored_frame = nullptr;
+static u64 s_stored_frame_size = 0;
+static bool s_start_dumping = false;
+
+bool AVIDump::Start(HWND hWnd, int w, int h)
+{
+ s_emu_wnd = hWnd;
+ s_file_count = 0;
+
+ s_width = w;
+ s_height = h;
+
+ s_last_frame = CoreTiming::GetTicks();
+
+ if (SConfig::GetInstance().m_SYSCONF->GetData<u8>("IPL.E60"))
+ s_frame_rate = 60; // always 60, for either pal60 or ntsc
+ else
+ s_frame_rate = VideoInterface::TargetRefreshRate; // 50 or 60, depending on region
+
+ // clear CFR frame cache on start, not on file create (which is also segment switch)
+ SetBitmapFormat();
+ StoreFrame(nullptr);
+
+ return CreateFile();
+}
+
+bool AVIDump::CreateFile()
+{
+ s_total_bytes = 0;
+ s_frame_count = 0;
+
+ std::string movie_file_name = StringFromFormat("%sframedump%d.avi", File::GetUserPath(D_DUMPFRAMES_IDX).c_str(), s_file_count);
+
+ // Create path
+ File::CreateFullPath(movie_file_name);
+
+ // Ask to delete file
+ if (File::Exists(movie_file_name))
+ {
+ if (SConfig::GetInstance().m_DumpFramesSilent ||
+ AskYesNoT("Delete the existing file '%s'?", movie_file_name.c_str()))
+ {
+ File::Delete(movie_file_name);
+ }
+ }
+
+ AVIFileInit();
+ NOTICE_LOG(VIDEO, "Opening AVI file (%s) for dumping", movie_file_name.c_str());
+ // TODO: Make this work with AVIFileOpenW without it throwing REGDB_E_CLASSNOTREG
+ HRESULT hr = AVIFileOpenA(&s_file, movie_file_name.c_str(), OF_WRITE | OF_CREATE, nullptr);
+ if (FAILED(hr))
+ {
+ if (hr == AVIERR_BADFORMAT) NOTICE_LOG(VIDEO, "The file couldn't be read, indicating a corrupt file or an unrecognized format.");
+ if (hr == AVIERR_MEMORY) NOTICE_LOG(VIDEO, "The file could not be opened because of insufficient memory.");
+ if (hr == AVIERR_FILEREAD) NOTICE_LOG(VIDEO, "A disk error occurred while reading the file.");
+ if (hr == AVIERR_FILEOPEN) NOTICE_LOG(VIDEO, "A disk error occurred while opening the file.");
+ if (hr == REGDB_E_CLASSNOTREG) NOTICE_LOG(VIDEO, "AVI class not registered");
+ Stop();
+ return false;
+ }
+
+ SetBitmapFormat();
+ NOTICE_LOG(VIDEO, "Setting video format...");
+ if (!SetVideoFormat())
+ {
+ NOTICE_LOG(VIDEO, "Setting video format failed");
+ Stop();
+ return false;
+ }
+
+ if (!s_file_count)
+ {
+ if (!SetCompressionOptions())
+ {
+ NOTICE_LOG(VIDEO, "SetCompressionOptions failed");
+ Stop();
+ return false;
+ }
+ }
+
+ if (FAILED(AVIMakeCompressedStream(&s_stream_compressed, s_stream, &s_options, nullptr)))
+ {
+ NOTICE_LOG(VIDEO, "AVIMakeCompressedStream failed");
+ Stop();
+ return false;
+ }
+
+ if (FAILED(AVIStreamSetFormat(s_stream_compressed, 0, &s_bitmap, s_bitmap.biSize)))
+ {
+ NOTICE_LOG(VIDEO, "AVIStreamSetFormat failed");
+ Stop();
+ return false;
+ }
+
+ return true;
+}
+
+void AVIDump::CloseFile()
+{
+ if (s_stream_compressed)
+ {
+ AVIStreamClose(s_stream_compressed);
+ s_stream_compressed = nullptr;
+ }
+
+ if (s_stream)
+ {
+ AVIStreamClose(s_stream);
+ s_stream = nullptr;
+ }
+
+ if (s_file)
+ {
+ AVIFileRelease(s_file);
+ s_file = nullptr;
+ }
+
+ AVIFileExit();
+}
+
+void AVIDump::Stop()
+{
+ // store one copy of the last video frame, CFR case
+ if (s_stream_compressed)
+ AVIStreamWrite(s_stream_compressed, s_frame_count++, 1, GetFrame(), s_bitmap.biSizeImage, AVIIF_KEYFRAME, nullptr, &s_byte_buffer);
+ s_start_dumping = false;
+ CloseFile();
+ s_file_count = 0;
+ NOTICE_LOG(VIDEO, "Stop");
+}
+
+void AVIDump::StoreFrame(const void* data)
+{
+ if (s_bitmap.biSizeImage > s_stored_frame_size)
+ {
+ void* temp_stored_frame = realloc(s_stored_frame, s_bitmap.biSizeImage);
+ if (temp_stored_frame)
+ {
+ s_stored_frame = temp_stored_frame;
+ }
+ else
+ {
+ free(s_stored_frame);
+ PanicAlertT("Something has gone seriously wrong.\n"
+ "Stopping video recording.\n"
+ "Your video will likely be broken.");
+ Stop();
+ }
+ s_stored_frame_size = s_bitmap.biSizeImage;
+ memset(s_stored_frame, 0, s_bitmap.biSizeImage);
+ }
+ if (s_stored_frame)
+ {
+ if (data)
+ memcpy(s_stored_frame, data, s_bitmap.biSizeImage);
+ else // pitch black frame
+ memset(s_stored_frame, 0, s_bitmap.biSizeImage);
+ }
+}
+
+void* AVIDump::GetFrame()
+{
+ return s_stored_frame;
+}
+
+void AVIDump::AddFrame(const u8* data, int w, int h)
+{
+ static bool shown_error = false;
+ if ((w != s_bitmap.biWidth || h != s_bitmap.biHeight) && !shown_error)
+ {
+ PanicAlertT("You have resized the window while dumping frames.\n"
+ "Nothing can be done to handle this properly.\n"
+ "Your video will likely be broken.");
+ shown_error = true;
+
+ s_bitmap.biWidth = w;
+ s_bitmap.biHeight = h;
+ }
+ // no timecodes, instead dump each frame as many/few times as needed to keep sync
+ u64 one_cfr = SystemTimers::GetTicksPerSecond() / VideoInterface::TargetRefreshRate;
+ int nplay = 0;
+ s64 delta;
+ if (!s_start_dumping && s_last_frame <= SystemTimers::GetTicksPerSecond())
+ {
+ delta = CoreTiming::GetTicks();
+ s_start_dumping = true;
+ }
+ else
+ {
+ delta = CoreTiming::GetTicks() - s_last_frame;
+ }
+ bool b_frame_dumped = false;
+ // try really hard to place one copy of frame in stream (otherwise it's dropped)
+ if (delta > (s64)one_cfr * 1 / 10) // place if 1/10th of a frame space
+ {
+ delta -= one_cfr;
+ nplay++;
+ }
+ // try not nearly so hard to place additional copies of the frame
+ while (delta > (s64)one_cfr * 9 / 10) // place if 9/10th of a frame space
+ {
+ delta -= one_cfr;
+ nplay++;
+ }
+ while (nplay--)
+ {
+ if (!b_frame_dumped)
+ {
+ AVIStreamWrite(s_stream_compressed, s_frame_count++, 1, GetFrame(), s_bitmap.biSizeImage, AVIIF_KEYFRAME, nullptr, &s_byte_buffer);
+ b_frame_dumped = true;
+ }
+ else
+ {
+ AVIStreamWrite(s_stream, s_frame_count++, 1, nullptr, 0, 0, nullptr, nullptr);
+ }
+ s_total_bytes += s_byte_buffer;
+ // Close the recording if the file is larger than 2gb
+ // VfW can't properly save files over 2gb in size, but can keep writing to them up to 4gb.
+ if (s_total_bytes >= 2000000000)
+ {
+ CloseFile();
+ s_file_count++;
+ CreateFile();
+ }
+ }
+ StoreFrame(data);
+ s_last_frame = CoreTiming::GetTicks();
+}
+
+void AVIDump::SetBitmapFormat()
+{
+ memset(&s_bitmap, 0, sizeof(s_bitmap));
+ s_bitmap.biSize = 0x28;
+ s_bitmap.biPlanes = 1;
+ s_bitmap.biBitCount = 24;
+ s_bitmap.biWidth = s_width;
+ s_bitmap.biHeight = s_height;
+ s_bitmap.biSizeImage = 3 * s_width * s_height;
+}
+
+bool AVIDump::SetCompressionOptions()
+{
+ memset(&s_options, 0, sizeof(s_options));
+ s_array_options[0] = &s_options;
+
+ if (SConfig::GetInstance().m_DumpFramesSilent)
+ {
+ s_options.fccType = streamtypeVIDEO;
+ s_options.fccHandler = mmioFOURCC('D', 'I', 'B', ' '); // Uncompressed
+ return true;
+ }
+ else
+ {
+ return (AVISaveOptions(s_emu_wnd, 0, 1, &s_stream, s_array_options) != 0);
+ }
+}
+
+bool AVIDump::SetVideoFormat()
+{
+ memset(&s_header, 0, sizeof(s_header));
+ s_header.fccType = streamtypeVIDEO;
+ s_header.dwScale = 1;
+ s_header.dwRate = s_frame_rate;
+ s_header.dwSuggestedBufferSize = s_bitmap.biSizeImage;
+
+ return SUCCEEDED(AVIFileCreateStream(s_file, &s_stream, &s_header));
+}
+
+#else
+
+#include "Common/CommonPaths.h"
+#include "Common/FileUtil.h"
+#include "Common/StringUtil.h"
+#include "Common/Logging/Log.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <libavutil/mathematics.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 uint8_t* s_yuv_buffer = nullptr;
+static SwsContext* s_sws_context = nullptr;
+static int s_width;
+static int s_height;
+static int s_size;
+static u64 s_last_frame;
+static bool s_start_dumping = false;
+static u64 s_last_pts;
+
+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_last_frame = CoreTiming::GetTicks();
+ s_last_pts = 0;
+
+ InitAVCodec();
+ bool success = CreateFile();
+ if (!success)
+ CloseFile();
+ return success;
+}
+
+bool AVIDump::CreateFile()
+{
+ AVCodec* codec = nullptr;
+
+ s_format_context = avformat_alloc_context();
+ snprintf(s_format_context->filename, sizeof(s_format_context->filename), "%s",
+ (File::GetUserPath(D_DUMPFRAMES_IDX) + "framedump0.avi").c_str());
+ File::CreateFullPath(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;
+ 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 = (AVRational){1, static_cast<int>(VideoInterface::TargetRefreshRate)};
+ 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_size = avpicture_get_size(s_stream->codec->pix_fmt, s_width, s_height);
+
+ s_yuv_buffer = new uint8_t[s_size];
+ avpicture_fill((AVPicture*)s_scaled_frame, s_yuv_buffer, s_stream->codec->pix_fmt, s_width, s_height);
+
+ NOTICE_LOG(VIDEO, "Opening file %s for dumping", s_format_context->filename);
+ if (avio_open(&s_format_context->pb, s_format_context->filename, AVIO_FLAG_WRITE) < 0)
+ {
+ WARN_LOG(VIDEO, "Could not open %s", s_format_context->filename);
+ return false;
+ }
+
+ avformat_write_header(s_format_context, nullptr);
+
+ return true;
+}
+
+static void PreparePacket(AVPacket* pkt)
+{
+ av_init_packet(pkt);
+ pkt->data = nullptr;
+ pkt->size = 0;
+}
+
+void AVIDump::AddFrame(const u8* data, int width, int height)
+{
+ avpicture_fill((AVPicture*)s_src_frame, const_cast<u8*>(data), AV_PIX_FMT_BGR24, width, 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,
+ width, height, AV_PIX_FMT_BGR24,
+ 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,
+ height, 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 = 0;
+ int error = 0;
+ u64 delta;
+ s64 last_pts;
+ if (!s_start_dumping && s_last_frame <= SystemTimers::GetTicksPerSecond())
+ {
+ delta = CoreTiming::GetTicks();
+ last_pts = AV_NOPTS_VALUE;
+ s_start_dumping = true;
+ }
+ else
+ {
+ delta = CoreTiming::GetTicks() - s_last_frame;
+ last_pts = (s_last_pts * s_stream->codec->time_base.den) / SystemTimers::GetTicksPerSecond();
+ }
+ u64 pts_in_ticks = s_last_pts + delta;
+ s_scaled_frame->pts = (pts_in_ticks * s_stream->codec->time_base.den) / SystemTimers::GetTicksPerSecond();
+ if (s_scaled_frame->pts != last_pts)
+ {
+ s_last_frame = CoreTiming::GetTicks();
+ s_last_pts = pts_in_ticks;
+ 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 != AV_NOPTS_VALUE)
+ {
+ pkt.pts = av_rescale_q(pkt.pts,
+ s_stream->codec->time_base, s_stream->time_base);
+ }
+ if (pkt.dts != AV_NOPTS_VALUE)
+ {
+ pkt.dts = av_rescale_q(pkt.dts,
+ 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;
+ 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(VIDEO, "Error while encoding video: %d", error);
+}
+
+void AVIDump::Stop()
+{
+ av_write_trailer(s_format_context);
+ CloseFile();
+ NOTICE_LOG(VIDEO, "Stopping frame dump");
+}
+
+void AVIDump::CloseFile()
+{
+ if (s_stream)
+ {
+ if (s_stream->codec)
+ avcodec_close(s_stream->codec);
+ av_free(s_stream);
+ s_stream = nullptr;
+ }
+
+ if (s_yuv_buffer)
+ {
+ delete[] s_yuv_buffer;
+ s_yuv_buffer = nullptr;
+ }
+
+ 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_free(s_format_context);
+ s_format_context = nullptr;
+ }
+
+ if (s_sws_context)
+ {
+ sws_freeContext(s_sws_context);
+ s_sws_context = nullptr;
+ }
+}
+
+#endif
+
+void AVIDump::DoState()
+{
+ s_last_frame = CoreTiming::GetTicks();
+}