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