Proper Audio/Video Dumping

This commit is contained in:
Fog 2014-10-04 03:28:01 -04:00
parent 47bf698b70
commit fc4125cdd1
8 changed files with 247 additions and 88 deletions

View File

@ -68,9 +68,12 @@ namespace AudioCommon
{ {
if (SConfig::GetInstance().m_DumpAudio) if (SConfig::GetInstance().m_DumpAudio)
{ {
std::string audio_file_name = File::GetUserPath(D_DUMPAUDIO_IDX) + "audiodump.wav"; std::string audio_file_name_dtk = File::GetUserPath(D_DUMPAUDIO_IDX) + "dtkdump.wav";
File::CreateFullPath(audio_file_name); std::string audio_file_name_dsp = File::GetUserPath(D_DUMPAUDIO_IDX) + "dspdump.wav";
mixer->StartLogAudio(audio_file_name); 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; return g_sound_stream;
@ -93,8 +96,11 @@ namespace AudioCommon
{ {
g_sound_stream->Stop(); g_sound_stream->Stop();
if (SConfig::GetInstance().m_DumpAudio) if (SConfig::GetInstance().m_DumpAudio)
g_sound_stream->GetMixer()->StopLogAudio(); {
g_sound_stream->GetMixer()->StopLogDTKAudio();
g_sound_stream->GetMixer()->StopLogDSPAudio();
//g_sound_stream->StopLogAudio(); //g_sound_stream->StopLogAudio();
}
delete g_sound_stream; delete g_sound_stream;
g_sound_stream = nullptr; g_sound_stream = nullptr;
} }

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations"> <ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64"> <ProjectConfiguration Include="Debug|x64">

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<Filter Include="SoundStreams"> <Filter Include="SoundStreams">

View File

@ -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_dma_mixer.Mix(samples, num_samples, consider_framelimit);
m_streaming_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); m_wiimote_speaker_mixer.Mix(samples, num_samples, consider_framelimit);
if (m_logAudio)
g_wave_writer.AddStereoSamples(samples, num_samples);
return 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) void CMixer::PushSamples(const short *samples, unsigned int num_samples)
{ {
m_dma_mixer.PushSamples(samples, 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) void CMixer::PushStreamingSamples(const short *samples, unsigned int num_samples)
{ {
m_streaming_mixer.PushSamples(samples, 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) void CMixer::PushWiimoteSpeakerSamples(const short *samples, unsigned int num_samples, unsigned int sample_rate)

View File

@ -26,7 +26,8 @@ public:
, m_streaming_mixer(this, 48000) , m_streaming_mixer(this, 48000)
, m_wiimote_speaker_mixer(this, 3000) , m_wiimote_speaker_mixer(this, 3000)
, m_sampleRate(BackendSampleRate) , m_sampleRate(BackendSampleRate)
, m_logAudio(0) , m_log_dtk_audio(0)
, m_log_dsp_audio(0)
, m_speed(0) , m_speed(0)
{ {
INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized"); INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized");
@ -48,32 +49,61 @@ public:
void SetStreamingVolume(unsigned int lvolume, unsigned int rvolume); void SetStreamingVolume(unsigned int lvolume, unsigned int rvolume);
void SetWiimoteSpeakerVolume(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; m_log_dtk_audio = true;
g_wave_writer.Start(filename, GetSampleRate()); g_wave_writer_dtk.Start(filename, 48000);
g_wave_writer.SetSkipSilence(false); g_wave_writer_dtk.SetSkipSilence(false);
NOTICE_LOG(DSPHLE, "Starting Audio logging"); NOTICE_LOG(DSPHLE, "Starting DTK Audio logging");
} }
else 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; m_log_dtk_audio = false;
g_wave_writer.Stop(); g_wave_writer_dtk.Stop();
NOTICE_LOG(DSPHLE, "Stopping Audio logging"); NOTICE_LOG(DSPHLE, "Stopping DTK Audio logging");
} }
else 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; MixerFifo m_wiimote_speaker_mixer;
unsigned int m_sampleRate; 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; std::mutex m_csMixing;

View File

@ -25,6 +25,7 @@
#include "Core/HW/Wiimote.h" #include "Core/HW/Wiimote.h"
#include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/JitCommon/JitBase.h"
#include "VideoCommon/AVIDump.h"
#include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoBackendBase.h"
namespace State namespace State
@ -116,6 +117,9 @@ static void DoState(PointerWrap &p)
p.DoMarker("CoreTiming"); p.DoMarker("CoreTiming");
Movie::DoState(p); Movie::DoState(p);
p.DoMarker("Movie"); p.DoMarker("Movie");
#if defined(HAVE_LIBAV) || defined (WIN32)
AVIDump::DoState();
#endif
} }
void LoadFromBuffer(std::vector<u8>& buffer) void LoadFromBuffer(std::vector<u8>& buffer)

View File

@ -26,38 +26,60 @@
#include <vfw.h> #include <vfw.h>
#include <winerror.h> #include <winerror.h>
HWND m_emuWnd; #include "Core/ConfigManager.h" // for EuRGB60
LONG m_byteBuffer; #include "Core/CoreTiming.h"
LONG m_frameCount; #include "Core/HW/SystemTimers.h"
LONG m_totalBytes;
PAVIFILE m_file; static HWND s_emu_wnd;
int m_width; static LONG s_byte_buffer;
int m_height; static LONG s_frame_count;
int m_fileCount; static LONG s_total_bytes;
PAVISTREAM m_stream; static PAVIFILE s_file;
PAVISTREAM m_streamCompressed; static int s_width;
AVISTREAMINFO m_header; static int s_height;
AVICOMPRESSOPTIONS m_options; static int s_file_count;
AVICOMPRESSOPTIONS *m_arrayOptions[1]; static u64 s_last_frame;
BITMAPINFOHEADER m_bitmap; 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) bool AVIDump::Start(HWND hWnd, int w, int h)
{ {
m_emuWnd = hWnd; s_emu_wnd = hWnd;
m_fileCount = 0; s_file_count = 0;
m_width = w; s_width = w;
m_height = h; 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(); return CreateFile();
} }
bool AVIDump::CreateFile() bool AVIDump::CreateFile()
{ {
m_totalBytes = 0; s_total_bytes = 0;
m_frameCount = 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 // Create path
File::CreateFullPath(movie_file_name); File::CreateFullPath(movie_file_name);
@ -72,7 +94,7 @@ bool AVIDump::CreateFile()
AVIFileInit(); AVIFileInit();
NOTICE_LOG(VIDEO, "Opening AVI file (%s) for dumping", movie_file_name.c_str()); 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 // 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 (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_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; return false;
} }
if (!m_fileCount) if (!s_file_count)
{ {
if (!SetCompressionOptions()) 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"); NOTICE_LOG(VIDEO, "AVIMakeCompressedStream failed");
Stop(); Stop();
return false; 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"); NOTICE_LOG(VIDEO, "AVIStreamSetFormat failed");
Stop(); Stop();
@ -122,22 +144,22 @@ bool AVIDump::CreateFile()
void AVIDump::CloseFile() void AVIDump::CloseFile()
{ {
if (m_streamCompressed) if (s_stream_compressed)
{ {
AVIStreamClose(m_streamCompressed); AVIStreamClose(s_stream_compressed);
m_streamCompressed = nullptr; s_stream_compressed = nullptr;
} }
if (m_stream) if (s_stream)
{ {
AVIStreamClose(m_stream); AVIStreamClose(s_stream);
m_stream = nullptr; s_stream = nullptr;
} }
if (m_file) if (s_file)
{ {
AVIFileRelease(m_file); AVIFileRelease(s_file);
m_file = nullptr; s_file = nullptr;
} }
AVIFileExit(); AVIFileExit();
@ -145,69 +167,150 @@ void AVIDump::CloseFile()
void AVIDump::Stop() 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(); CloseFile();
m_fileCount = 0; s_file_count = 0;
NOTICE_LOG(VIDEO, "Stop"); 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) void AVIDump::AddFrame(const u8* data, int w, int h)
{ {
static bool shown_error = false; 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" PanicAlert("You have resized the window while dumping frames.\n"
"Nothing sane can be done to handle this.\n" "Nothing sane can be done to handle this.\n"
"Your video will likely be broken."); "Your video will likely be broken.");
shown_error = true; shown_error = true;
m_bitmap.biWidth = w; s_bitmap.biWidth = w;
m_bitmap.biHeight = h; s_bitmap.biHeight = h;
} }
// no timecodes, instead dump each frame as many/few times as needed to keep sync
AVIStreamWrite(m_streamCompressed, ++m_frameCount, 1, const_cast<u8*>(data), m_bitmap.biSizeImage, AVIIF_KEYFRAME, nullptr, &m_byteBuffer); u64 one_cfr = SystemTimers::GetTicksPerSecond() / VideoInterface::TargetRefreshRate;
m_totalBytes += m_byteBuffer; int nplay = 0;
// Close the recording if the file is more than 2gb s64 delta;
// VfW can't properly save files over 2gb in size, but can keep writing to them up to 4gb. if (!b_start_dumping && s_last_frame <= SystemTimers::GetTicksPerSecond())
if (m_totalBytes >= 2000000000)
{ {
CloseFile(); delta = CoreTiming::GetTicks();
m_fileCount++; b_start_dumping = true;
CreateFile();
} }
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() void AVIDump::SetBitmapFormat()
{ {
memset(&m_bitmap, 0, sizeof(m_bitmap)); memset(&s_bitmap, 0, sizeof(s_bitmap));
m_bitmap.biSize = 0x28; s_bitmap.biSize = 0x28;
m_bitmap.biPlanes = 1; s_bitmap.biPlanes = 1;
m_bitmap.biBitCount = 24; s_bitmap.biBitCount = 24;
m_bitmap.biWidth = m_width; s_bitmap.biWidth = s_width;
m_bitmap.biHeight = m_height; s_bitmap.biHeight = s_height;
m_bitmap.biSizeImage = 3 * m_width * m_height; s_bitmap.biSizeImage = 3 * s_width * s_height;
} }
bool AVIDump::SetCompressionOptions() bool AVIDump::SetCompressionOptions()
{ {
memset(&m_options, 0, sizeof(m_options)); memset(&s_options, 0, sizeof(s_options));
m_arrayOptions[0] = &m_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() bool AVIDump::SetVideoFormat()
{ {
memset(&m_header, 0, sizeof(m_header)); memset(&s_header, 0, sizeof(s_header));
m_header.fccType = streamtypeVIDEO; s_header.fccType = streamtypeVIDEO;
m_header.dwScale = 1; s_header.dwScale = 1;
m_header.dwRate = VideoInterface::TargetRefreshRate; s_header.dwRate = s_frame_rate;
m_header.dwSuggestedBufferSize = m_bitmap.biSizeImage; 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 #else
#include "Common/CommonPaths.h"
#include "Common/FileUtil.h"
#include "Common/StringUtil.h"
#include "Common/Logging/Log.h"
extern "C" { extern "C" {
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
@ -229,6 +332,8 @@ static SwsContext* s_sws_context = nullptr;
static int s_width; static int s_width;
static int s_height; static int s_height;
static int s_size; static int s_size;
static u64 s_last_frame;
bool b_start_dumping = false;
static void InitAVCodec() static void InitAVCodec()
{ {
@ -245,6 +350,8 @@ bool AVIDump::Start(int w, int h)
s_width = w; s_width = w;
s_height = h; s_height = h;
s_last_frame = CoreTiming::GetTicks();
InitAVCodec(); InitAVCodec();
bool success = CreateFile(); bool success = CreateFile();
if (!success) if (!success)
@ -397,3 +504,8 @@ void AVIDump::CloseFile()
} }
#endif #endif
void AVIDump::DoState()
{
s_last_frame = CoreTiming::GetTicks();
}

View File

@ -21,6 +21,9 @@ private:
static bool SetCompressionOptions(); static bool SetCompressionOptions();
static bool SetVideoFormat(); static bool SetVideoFormat();
static void StoreFrame(const void* data);
static void* GetFrame();
public: public:
#ifdef _WIN32 #ifdef _WIN32
static bool Start(HWND hWnd, int w, int h); static bool Start(HWND hWnd, int w, int h);
@ -28,6 +31,6 @@ public:
static bool Start(int w, int h); static bool Start(int w, int h);
#endif #endif
static void AddFrame(const u8* data, int width, int height); static void AddFrame(const u8* data, int width, int height);
static void Stop(); static void Stop();
static void DoState();
}; };