diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ff7dbd09a..42d44d87f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,6 +171,14 @@ else() message("Xrandr NOT found") endif(XRANDR_FOUND) +pkg_search_module(AVCODEC "libavcodec libswscale") +if(AVCODEC_FOUND) + message("avcodec found, enabling MPG frame dumps") + add_definitions(-DHAVE_AVCODEC) +else() + message("avcodec not found, disabling MPG frame dumps") +endif() + include(CheckCXXSourceRuns) set(CMAKE_REQUIRED_LIBRARIES portaudio) CHECK_CXX_SOURCE_RUNS( diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index f06fc2c978..f5a3ff2e2d 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -34,7 +34,15 @@ set(SRCS Src/BPMemory.cpp Src/XFStructs.cpp Src/OpenCL/OCLTextureDecoder.cpp) +if(AVCODEC_FOUND OR WIN32) + set(SRCS ${SRCS} Src/AVIDump.cpp) +endif() + add_library(videocommon STATIC ${SRCS}) if(UNIX) + if(AVCODEC_FOUND) + target_link_libraries(videocommon avcodec swscale) + add_definitions(-D__STDC_CONSTANT_MACROS) + endif() add_definitions(-fPIC) endif(UNIX) diff --git a/Source/Core/VideoCommon/Src/AVIDump.cpp b/Source/Core/VideoCommon/Src/AVIDump.cpp index 72df3456ab..3c5eb4d057 100644 --- a/Source/Core/VideoCommon/Src/AVIDump.cpp +++ b/Source/Core/VideoCommon/Src/AVIDump.cpp @@ -16,6 +16,9 @@ // http://code.google.com/p/dolphin-emu/ #include "AVIDump.h" + +#ifdef _WIN32 + #include "tchar.h" #include @@ -27,20 +30,20 @@ #include "CommonPaths.h" #include "Log.h" -static HWND m_emuWnd; -static int m_width; -static int m_height; -static LONG m_byteBuffer; -static LONG m_frameCount; -static LONG m_totalBytes; -static PAVIFILE m_file; -static int m_fileCount; -static PAVISTREAM m_stream; -static PAVISTREAM m_streamCompressed; -static AVISTREAMINFO m_header; -static AVICOMPRESSOPTIONS m_options; -static AVICOMPRESSOPTIONS *m_arrayOptions[1]; -static BITMAPINFOHEADER m_bitmap; +HWND m_emuWnd; +LONG m_byteBuffer; +LONG m_frameCount; +LONG m_totalBytes; +PAVIFILE m_file; +int m_width; +int m_height; +int m_fileCount; +PAVISTREAM m_stream; +PAVISTREAM m_streamCompressed; +AVISTREAMINFO m_header; +AVICOMPRESSOPTIONS m_options; +AVICOMPRESSOPTIONS *m_arrayOptions[1]; +BITMAPINFOHEADER m_bitmap; bool AVIDump::Start(HWND hWnd, int w, int h) { @@ -192,3 +195,158 @@ bool AVIDump::SetVideoFormat() return SUCCEEDED(AVIFileCreateStream(m_file, &m_stream, &m_header)); } + +#else + +#include "FileUtil.h" +#include "StringUtil.h" +#include "Log.h" + +extern "C" { +#include +#include +} + +FILE* f_pFrameDump = NULL; +AVCodec *s_Codec = NULL; +AVCodecContext *s_CodecContext = NULL; +AVFrame *s_BGRFrame = NULL, *s_YUVFrame = NULL; +uint8_t *s_YUVBuffer = NULL; +uint8_t *s_OutBuffer = NULL; +struct SwsContext *s_SwsContext = NULL; +int s_width; +int s_height; +int s_size; + +static void InitAVCodec() +{ + static bool first_run = true; + if (first_run) + { + avcodec_init(); + avcodec_register_all(); + first_run = false; + } +} + +bool AVIDump::Start(int w, int h) +{ + s_width = w; + s_height = h; + + InitAVCodec(); + return CreateFile(); +} + +bool AVIDump::CreateFile() +{ + s_Codec = avcodec_find_encoder(CODEC_ID_MPEG1VIDEO); + if (!s_Codec) + return false; + + s_CodecContext = avcodec_alloc_context(); + + s_CodecContext->bit_rate = 400000; + s_CodecContext->width = s_width; + s_CodecContext->height = s_height; + s_CodecContext->time_base = (AVRational){1, 60}; + s_CodecContext->gop_size = 10; + s_CodecContext->max_b_frames = 1; + s_CodecContext->pix_fmt = PIX_FMT_YUV420P; + + NOTICE_LOG(VIDEO, "Opening MPG file framedump.mpg for dumping"); + if ((avcodec_open(s_CodecContext, s_Codec) < 0) || + !(f_pFrameDump = fopen(StringFromFormat("%sframedump.mpg", + File::GetUserPath(D_DUMPFRAMES_IDX)).c_str(), "wb")) || + !(s_SwsContext = sws_getContext(s_width, s_height, PIX_FMT_BGR24, s_width, s_height, + PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL))) + { + WARN_LOG(VIDEO, "Unable to initialize mpg dump file"); + CloseFile(); + return false; + } + + s_BGRFrame = avcodec_alloc_frame(); + s_YUVFrame = avcodec_alloc_frame(); + + s_size = avpicture_get_size(PIX_FMT_YUV420P, s_width, s_height); + + s_YUVBuffer = new uint8_t[s_size]; + avpicture_fill((AVPicture *)s_YUVFrame, s_YUVBuffer, PIX_FMT_YUV420P, s_width, s_height); + + s_OutBuffer = new uint8_t[s_size]; + + return true; +} + +void AVIDump::AddFrame(uint8_t *data) +{ + avpicture_fill((AVPicture *)s_BGRFrame, data, PIX_FMT_BGR24, s_width, s_height); + + // Convert image from BGR24 to YUV420P + sws_scale(s_SwsContext, s_BGRFrame->data, s_BGRFrame->linesize, 0, + s_height, s_YUVFrame->data, s_YUVFrame->linesize); + + // Encode and write the image + int s_iOutSize = avcodec_encode_video(s_CodecContext, s_OutBuffer, s_size, s_YUVFrame); + fwrite(s_OutBuffer, 1, s_iOutSize, f_pFrameDump); + fflush(f_pFrameDump); + + // Encode and write delayed frames + do + { + s_iOutSize = avcodec_encode_video(s_CodecContext, s_OutBuffer, s_size, NULL); + fwrite(s_OutBuffer, 1, s_iOutSize, f_pFrameDump); + fflush(f_pFrameDump); + } while (s_iOutSize); +} + +void AVIDump::Stop() +{ + // Write MPG footer + s_OutBuffer[0] = 0x00; + s_OutBuffer[1] = 0x00; + s_OutBuffer[2] = 0x01; + s_OutBuffer[3] = 0xb7; + fwrite(s_OutBuffer, 1, 4, f_pFrameDump); + fflush(f_pFrameDump); + + CloseFile(); + NOTICE_LOG(VIDEO, "Stopping frame dump"); +} + +void AVIDump::CloseFile() +{ + if (f_pFrameDump) + fclose(f_pFrameDump); + f_pFrameDump = NULL; + + if (s_YUVBuffer) + delete[] s_YUVBuffer; + s_YUVBuffer = NULL; + + if (s_OutBuffer) + delete[] s_OutBuffer; + s_OutBuffer = NULL; + + if (s_CodecContext) + { + avcodec_close(s_CodecContext); + av_free(s_CodecContext); + } + s_CodecContext = NULL; + + if (s_BGRFrame) + av_free(s_BGRFrame); + s_BGRFrame = NULL; + + if (s_YUVFrame) + av_free(s_YUVFrame); + s_YUVFrame = NULL; + + if (s_SwsContext) + sws_freeContext(s_SwsContext); + s_SwsContext = NULL; +} + +#endif diff --git a/Source/Core/VideoCommon/Src/AVIDump.h b/Source/Core/VideoCommon/Src/AVIDump.h index 26107d0c0b..ae5f37579a 100644 --- a/Source/Core/VideoCommon/Src/AVIDump.h +++ b/Source/Core/VideoCommon/Src/AVIDump.h @@ -18,20 +18,30 @@ #ifndef _AVIDUMP_H #define _AVIDUMP_H +#ifdef _WIN32 #include +#else +#include +#endif class AVIDump { -private: - static bool CreateFile(); - static void CloseFile(); - static void SetBitmapFormat(); - static bool SetCompressionOptions(); - static bool SetVideoFormat(); -public: - static bool Start(HWND hWnd, int w, int h); - static void Stop(); - static void AddFrame(char *data); + private: + static bool CreateFile(); + static void CloseFile(); + static void SetBitmapFormat(); + static bool SetCompressionOptions(); + static bool SetVideoFormat(); + + public: +#ifdef _WIN32 + static bool Start(HWND hWnd, int w, int h); + static void AddFrame(char *data); +#else + static bool Start(int w, int h); + static void AddFrame(uint8_t *data); +#endif + static void Stop(); }; #endif // _AVIDUMP_H diff --git a/Source/Plugins/Plugin_VideoOGL/Src/Render.cpp b/Source/Plugins/Plugin_VideoOGL/Src/Render.cpp index aa443ae4e8..bc9352fe79 100644 --- a/Source/Plugins/Plugin_VideoOGL/Src/Render.cpp +++ b/Source/Plugins/Plugin_VideoOGL/Src/Render.cpp @@ -62,6 +62,8 @@ #include "main.h" // Local #ifdef _WIN32 #include "OS/Win32.h" +#endif +#if defined _WIN32 || defined HAVE_AVCODEC #include "AVIDump.h" #endif @@ -82,7 +84,7 @@ CGprofile g_cgfProf; RasterFont* s_pfont = NULL; static bool s_bLastFrameDumped = false; -#ifdef _WIN32 +#if defined _WIN32 || defined HAVE_AVCODEC static bool s_bAVIDumping = false; #else static FILE* f_pFrameDump; @@ -529,7 +531,7 @@ void Renderer::Shutdown() g_framebufferManager.Shutdown(); -#ifdef _WIN32 +#if defined _WIN32 || defined HAVE_AVCODEC if(s_bAVIDumping) AVIDump::Stop(); #else @@ -1325,38 +1327,53 @@ void Renderer::Swap(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight,cons } // Frame dumps are handled a little differently in Windows -#ifdef _WIN32 +#if defined _WIN32 || defined HAVE_AVCODEC if (g_ActiveConfig.bDumpFrames) { s_criticalScreenshot.Enter(); int w = dst_rect.GetWidth(); int h = dst_rect.GetHeight(); - u8 *data = (u8 *) malloc(3 * w * h); + uint8_t *data = new uint8_t[3 * w * h]; glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(dst_rect.left, dst_rect.bottom, w, h, GL_BGR, GL_UNSIGNED_BYTE, data); if (GL_REPORT_ERROR() == GL_NO_ERROR && w > 0 && h > 0) { if (!s_bLastFrameDumped) { - s_bAVIDumping = AVIDump::Start(EmuWindow::GetParentWnd(), w, h); + #ifdef _WIN32 + s_bAVIDumping = AVIDump::Start(EmuWindow::GetParentWnd(), w, h); + #else + s_bAVIDumping = AVIDump::Start(w, h); + #endif if (!s_bAVIDumping) OSD::AddMessage("AVIDump Start failed", 2000); else { OSD::AddMessage(StringFromFormat( + #ifdef _WIN32 "Dumping Frames to \"%sframedump0.avi\" (%dx%d RGB24)", + #else + "Dumping Frames to \"%sframedump.mpg\" (%dx%d RGB24)", + #endif File::GetUserPath(D_DUMPFRAMES_IDX), w, h).c_str(), 2000); } } if (s_bAVIDumping) - AVIDump::AddFrame((char *) data); + { + #ifdef _WIN32 + AVIDump::AddFrame((char *) data); + #else + FlipImageData(data, w, h); + AVIDump::AddFrame(data); + #endif + } s_bLastFrameDumped = true; } else NOTICE_LOG(VIDEO, "Error reading framebuffer"); - free(data); + delete[] data; s_criticalScreenshot.Leave(); } else @@ -1365,7 +1382,7 @@ void Renderer::Swap(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight,cons { AVIDump::Stop(); s_bAVIDumping = false; - OSD::AddMessage("Stop dumping frames to AVI", 2000); + OSD::AddMessage("Stop dumping frames", 2000); } s_bLastFrameDumped = false; } @@ -1376,7 +1393,7 @@ void Renderer::Swap(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight,cons char movie_file_name[255]; int w = dst_rect.GetWidth(); int h = dst_rect.GetHeight(); - u8 *data = (u8 *) malloc(3 * w * h); + u8 *data = new u8[3 * w * h]; glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(dst_rect.left, dst_rect.bottom, w, h, GL_BGR, GL_UNSIGNED_BYTE, data); if (GL_REPORT_ERROR() == GL_NO_ERROR) @@ -1403,7 +1420,7 @@ void Renderer::Swap(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight,cons s_bLastFrameDumped = true; } - free(data); + delete[] data; s_criticalScreenshot.Leave(); } else @@ -1763,7 +1780,7 @@ bool Renderer::SaveRenderTarget(const char *filename, TargetRectangle back_rc) { u32 W = back_rc.GetWidth(); u32 H = back_rc.GetHeight(); - u8 *data = (u8 *)malloc(3 * W * H); + u8 *data = new u8[3 * W * H]; glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(back_rc.left, back_rc.bottom, W, H, GL_RGB, GL_UNSIGNED_BYTE, data); @@ -1803,7 +1820,7 @@ bool Renderer::SaveRenderTarget(const char *filename, TargetRectangle back_rc) #else bool result = SaveTGA(filename, W, H, data); - free(data); + delete[] data; #endif return result;