From fc4125cdd15c6119d75f6667cd44dcbd4df4a8b3 Mon Sep 17 00:00:00 2001 From: Fog Date: Sat, 4 Oct 2014 03:28:01 -0400 Subject: [PATCH] Proper Audio/Video Dumping --- Source/Core/AudioCommon/AudioCommon.cpp | 14 +- Source/Core/AudioCommon/AudioCommon.vcxproj | 2 +- .../AudioCommon/AudioCommon.vcxproj.filters | 2 +- Source/Core/AudioCommon/Mixer.cpp | 6 +- Source/Core/AudioCommon/Mixer.h | 64 +++-- Source/Core/Core/State.cpp | 4 + Source/Core/VideoCommon/AVIDump.cpp | 238 +++++++++++++----- Source/Core/VideoCommon/AVIDump.h | 5 +- 8 files changed, 247 insertions(+), 88 deletions(-) diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index 88fc7b892f..8afbc1478e 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -68,9 +68,12 @@ namespace AudioCommon { if (SConfig::GetInstance().m_DumpAudio) { - std::string audio_file_name = File::GetUserPath(D_DUMPAUDIO_IDX) + "audiodump.wav"; - File::CreateFullPath(audio_file_name); - mixer->StartLogAudio(audio_file_name); + std::string audio_file_name_dtk = File::GetUserPath(D_DUMPAUDIO_IDX) + "dtkdump.wav"; + std::string audio_file_name_dsp = File::GetUserPath(D_DUMPAUDIO_IDX) + "dspdump.wav"; + File::CreateFullPath(audio_file_name_dtk); + File::CreateFullPath(audio_file_name_dsp); + mixer->StartLogDTKAudio(audio_file_name_dtk); + mixer->StartLogDSPAudio(audio_file_name_dsp); } return g_sound_stream; @@ -93,8 +96,11 @@ namespace AudioCommon { g_sound_stream->Stop(); if (SConfig::GetInstance().m_DumpAudio) - g_sound_stream->GetMixer()->StopLogAudio(); + { + g_sound_stream->GetMixer()->StopLogDTKAudio(); + g_sound_stream->GetMixer()->StopLogDSPAudio(); //g_sound_stream->StopLogAudio(); + } delete g_sound_stream; g_sound_stream = nullptr; } diff --git a/Source/Core/AudioCommon/AudioCommon.vcxproj b/Source/Core/AudioCommon/AudioCommon.vcxproj index c626ffd77d..13aa887e32 100644 --- a/Source/Core/AudioCommon/AudioCommon.vcxproj +++ b/Source/Core/AudioCommon/AudioCommon.vcxproj @@ -1,4 +1,4 @@ - + diff --git a/Source/Core/AudioCommon/AudioCommon.vcxproj.filters b/Source/Core/AudioCommon/AudioCommon.vcxproj.filters index 2832422073..fee0799dbb 100644 --- a/Source/Core/AudioCommon/AudioCommon.vcxproj.filters +++ b/Source/Core/AudioCommon/AudioCommon.vcxproj.filters @@ -1,4 +1,4 @@ - + diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index a7984579ca..60203b2f37 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -122,8 +122,6 @@ unsigned int CMixer::Mix(short* samples, unsigned int num_samples, bool consider m_dma_mixer.Mix(samples, num_samples, consider_framelimit); m_streaming_mixer.Mix(samples, num_samples, consider_framelimit); m_wiimote_speaker_mixer.Mix(samples, num_samples, consider_framelimit); - if (m_logAudio) - g_wave_writer.AddStereoSamples(samples, num_samples); return num_samples; } @@ -161,11 +159,15 @@ void CMixer::MixerFifo::PushSamples(const short *samples, unsigned int num_sampl void CMixer::PushSamples(const short *samples, unsigned int num_samples) { m_dma_mixer.PushSamples(samples, num_samples); + if (m_log_dsp_audio) + g_wave_writer_dsp.AddStereoSamplesBE(samples, num_samples); } void CMixer::PushStreamingSamples(const short *samples, unsigned int num_samples) { m_streaming_mixer.PushSamples(samples, num_samples); + if (m_log_dtk_audio) + g_wave_writer_dtk.AddStereoSamplesBE(samples, num_samples); } void CMixer::PushWiimoteSpeakerSamples(const short *samples, unsigned int num_samples, unsigned int sample_rate) diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index e3272af070..ba9809d5bc 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -26,7 +26,8 @@ public: , m_streaming_mixer(this, 48000) , m_wiimote_speaker_mixer(this, 3000) , m_sampleRate(BackendSampleRate) - , m_logAudio(0) + , m_log_dtk_audio(0) + , m_log_dsp_audio(0) , m_speed(0) { INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized"); @@ -48,32 +49,61 @@ public: void SetStreamingVolume(unsigned int lvolume, unsigned int rvolume); void SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume); - virtual void StartLogAudio(const std::string& filename) + virtual void StartLogDTKAudio(const std::string& filename) { - if (! m_logAudio) + if (!m_log_dtk_audio) { - m_logAudio = true; - g_wave_writer.Start(filename, GetSampleRate()); - g_wave_writer.SetSkipSilence(false); - NOTICE_LOG(DSPHLE, "Starting Audio logging"); + m_log_dtk_audio = true; + g_wave_writer_dtk.Start(filename, 48000); + g_wave_writer_dtk.SetSkipSilence(false); + NOTICE_LOG(DSPHLE, "Starting DTK Audio logging"); } else { - WARN_LOG(DSPHLE, "Audio logging has already been started"); + WARN_LOG(DSPHLE, "DTK Audio logging has already been started"); } } - virtual void StopLogAudio() + virtual void StopLogDTKAudio() { - if (m_logAudio) + if (m_log_dtk_audio) { - m_logAudio = false; - g_wave_writer.Stop(); - NOTICE_LOG(DSPHLE, "Stopping Audio logging"); + m_log_dtk_audio = false; + g_wave_writer_dtk.Stop(); + NOTICE_LOG(DSPHLE, "Stopping DTK Audio logging"); } else { - WARN_LOG(DSPHLE, "Audio logging has already been stopped"); + WARN_LOG(DSPHLE, "DTK Audio logging has already been stopped"); + } + } + + virtual void StartLogDSPAudio(const std::string& filename) + { + if (!m_log_dsp_audio) + { + m_log_dsp_audio = true; + g_wave_writer_dsp.Start(filename, 32000); + g_wave_writer_dsp.SetSkipSilence(false); + NOTICE_LOG(DSPHLE, "Starting DSP Audio logging"); + } + else + { + WARN_LOG(DSPHLE, "DSP Audio logging has already been started"); + } + } + + virtual void StopLogDSPAudio() + { + if (m_log_dsp_audio) + { + m_log_dsp_audio = false; + g_wave_writer_dsp.Stop(); + NOTICE_LOG(DSPHLE, "Stopping DSP Audio logging"); + } + else + { + WARN_LOG(DSPHLE, "DSP Audio logging has already been stopped"); } } @@ -118,9 +148,11 @@ protected: MixerFifo m_wiimote_speaker_mixer; unsigned int m_sampleRate; - WaveFileWriter g_wave_writer; + WaveFileWriter g_wave_writer_dtk; + WaveFileWriter g_wave_writer_dsp; - bool m_logAudio; + bool m_log_dtk_audio; + bool m_log_dsp_audio; std::mutex m_csMixing; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 8d05d01fbc..b1cb54afd0 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -25,6 +25,7 @@ #include "Core/HW/Wiimote.h" #include "Core/PowerPC/JitCommon/JitBase.h" +#include "VideoCommon/AVIDump.h" #include "VideoCommon/VideoBackendBase.h" namespace State @@ -116,6 +117,9 @@ static void DoState(PointerWrap &p) p.DoMarker("CoreTiming"); Movie::DoState(p); p.DoMarker("Movie"); +#if defined(HAVE_LIBAV) || defined (WIN32) + AVIDump::DoState(); +#endif } void LoadFromBuffer(std::vector& buffer) diff --git a/Source/Core/VideoCommon/AVIDump.cpp b/Source/Core/VideoCommon/AVIDump.cpp index 850868d54e..2bd655b9d0 100644 --- a/Source/Core/VideoCommon/AVIDump.cpp +++ b/Source/Core/VideoCommon/AVIDump.cpp @@ -26,38 +26,60 @@ #include #include -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; +#include "Core/ConfigManager.h" // for EuRGB60 +#include "Core/CoreTiming.h" +#include "Core/HW/SystemTimers.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; +bool b_start_dumping = false; bool AVIDump::Start(HWND hWnd, int w, int h) { - m_emuWnd = hWnd; - m_fileCount = 0; + s_emu_wnd = hWnd; + s_file_count = 0; - m_width = w; - m_height = h; + s_width = w; + s_height = h; + + s_last_frame = CoreTiming::GetTicks(); + + if (SConfig::GetInstance().m_SYSCONF->GetData("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() { - m_totalBytes = 0; - m_frameCount = 0; + s_total_bytes = 0; + s_frame_count = 0; - std::string movie_file_name = StringFromFormat("%sframedump%d.avi", File::GetUserPath(D_DUMPFRAMES_IDX).c_str(), m_fileCount); + 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); @@ -72,7 +94,7 @@ bool AVIDump::CreateFile() 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(&m_file, movie_file_name.c_str(), OF_WRITE | OF_CREATE, nullptr); + 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."); @@ -93,7 +115,7 @@ bool AVIDump::CreateFile() return false; } - if (!m_fileCount) + if (!s_file_count) { if (!SetCompressionOptions()) { @@ -103,14 +125,14 @@ bool AVIDump::CreateFile() } } - if (FAILED(AVIMakeCompressedStream(&m_streamCompressed, m_stream, &m_options, nullptr))) + if (FAILED(AVIMakeCompressedStream(&s_stream_compressed, s_stream, &s_options, nullptr))) { NOTICE_LOG(VIDEO, "AVIMakeCompressedStream failed"); Stop(); return false; } - if (FAILED(AVIStreamSetFormat(m_streamCompressed, 0, &m_bitmap, m_bitmap.biSize))) + if (FAILED(AVIStreamSetFormat(s_stream_compressed, 0, &s_bitmap, s_bitmap.biSize))) { NOTICE_LOG(VIDEO, "AVIStreamSetFormat failed"); Stop(); @@ -122,22 +144,22 @@ bool AVIDump::CreateFile() void AVIDump::CloseFile() { - if (m_streamCompressed) + if (s_stream_compressed) { - AVIStreamClose(m_streamCompressed); - m_streamCompressed = nullptr; + AVIStreamClose(s_stream_compressed); + s_stream_compressed = nullptr; } - if (m_stream) + if (s_stream) { - AVIStreamClose(m_stream); - m_stream = nullptr; + AVIStreamClose(s_stream); + s_stream = nullptr; } - if (m_file) + if (s_file) { - AVIFileRelease(m_file); - m_file = nullptr; + AVIFileRelease(s_file); + s_file = nullptr; } AVIFileExit(); @@ -145,69 +167,150 @@ void AVIDump::CloseFile() 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); + b_start_dumping = false; CloseFile(); - m_fileCount = 0; + 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); + PanicAlert("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 != m_bitmap.biWidth || h != m_bitmap.biHeight) && !shown_error) + if ((w != s_bitmap.biWidth || h != s_bitmap.biHeight) && !shown_error) { PanicAlert("You have resized the window while dumping frames.\n" "Nothing sane can be done to handle this.\n" "Your video will likely be broken."); shown_error = true; - m_bitmap.biWidth = w; - m_bitmap.biHeight = h; + s_bitmap.biWidth = w; + s_bitmap.biHeight = h; } - - AVIStreamWrite(m_streamCompressed, ++m_frameCount, 1, const_cast(data), m_bitmap.biSizeImage, AVIIF_KEYFRAME, nullptr, &m_byteBuffer); - m_totalBytes += m_byteBuffer; - // Close the recording if the file is more than 2gb - // VfW can't properly save files over 2gb in size, but can keep writing to them up to 4gb. - if (m_totalBytes >= 2000000000) + // 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 (!b_start_dumping && s_last_frame <= SystemTimers::GetTicksPerSecond()) { - CloseFile(); - m_fileCount++; - CreateFile(); + delta = CoreTiming::GetTicks(); + b_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 * 3 / 10) // place if 3/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 * 8 / 10) // place if 8/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(&m_bitmap, 0, sizeof(m_bitmap)); - m_bitmap.biSize = 0x28; - m_bitmap.biPlanes = 1; - m_bitmap.biBitCount = 24; - m_bitmap.biWidth = m_width; - m_bitmap.biHeight = m_height; - m_bitmap.biSizeImage = 3 * m_width * m_height; + 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(&m_options, 0, sizeof(m_options)); - m_arrayOptions[0] = &m_options; + memset(&s_options, 0, sizeof(s_options)); + s_array_options[0] = &s_options; - return (AVISaveOptions(m_emuWnd, 0, 1, &m_stream, m_arrayOptions) != 0); + return (AVISaveOptions(s_emu_wnd, 0, 1, &s_stream, s_array_options) != 0); } bool AVIDump::SetVideoFormat() { - memset(&m_header, 0, sizeof(m_header)); - m_header.fccType = streamtypeVIDEO; - m_header.dwScale = 1; - m_header.dwRate = VideoInterface::TargetRefreshRate; - m_header.dwSuggestedBufferSize = m_bitmap.biSizeImage; + 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(m_file, &m_stream, &m_header)); + 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 #include @@ -229,6 +332,8 @@ static SwsContext* s_sws_context = nullptr; static int s_width; static int s_height; static int s_size; +static u64 s_last_frame; +bool b_start_dumping = false; static void InitAVCodec() { @@ -245,6 +350,8 @@ bool AVIDump::Start(int w, int h) s_width = w; s_height = h; + s_last_frame = CoreTiming::GetTicks(); + InitAVCodec(); bool success = CreateFile(); if (!success) @@ -397,3 +504,8 @@ void AVIDump::CloseFile() } #endif + +void AVIDump::DoState() +{ + s_last_frame = CoreTiming::GetTicks(); +} diff --git a/Source/Core/VideoCommon/AVIDump.h b/Source/Core/VideoCommon/AVIDump.h index df17ae409f..be6d4eed95 100644 --- a/Source/Core/VideoCommon/AVIDump.h +++ b/Source/Core/VideoCommon/AVIDump.h @@ -21,6 +21,9 @@ private: static bool SetCompressionOptions(); static bool SetVideoFormat(); + static void StoreFrame(const void* data); + static void* GetFrame(); + public: #ifdef _WIN32 static bool Start(HWND hWnd, int w, int h); @@ -28,6 +31,6 @@ public: static bool Start(int w, int h); #endif static void AddFrame(const u8* data, int width, int height); - static void Stop(); + static void DoState(); };