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