diff --git a/rpcs3/Emu/Audio/AL/OpenALThread.h b/rpcs3/Emu/Audio/AL/OpenALThread.h index 86504d9e4a..b3868c2948 100644 --- a/rpcs3/Emu/Audio/AL/OpenALThread.h +++ b/rpcs3/Emu/Audio/AL/OpenALThread.h @@ -1,10 +1,10 @@ #pragma once -/*#include "Emu/Audio/AudioThread.h" +/*#include "Emu/Audio/AudioBackend.h" #include "3rdparty/OpenAL/include/alext.h" #include -class OpenALThread : public AudioThread +class OpenALThread : public AudioBackend { private: ALint m_format; diff --git a/rpcs3/Emu/Audio/ALSA/ALSAThread.h b/rpcs3/Emu/Audio/ALSA/ALSAThread.h index 6b729db2e5..14f4ce40d5 100644 --- a/rpcs3/Emu/Audio/ALSA/ALSAThread.h +++ b/rpcs3/Emu/Audio/ALSA/ALSAThread.h @@ -4,7 +4,7 @@ #include "Emu/Audio/AudioThread.h" -class ALSAThread : public AudioThread +class ALSAThread : public AudioBackend { public: ALSAThread(); diff --git a/rpcs3/Emu/Audio/AudioThread.h b/rpcs3/Emu/Audio/AudioBackend.h similarity index 69% rename from rpcs3/Emu/Audio/AudioThread.h rename to rpcs3/Emu/Audio/AudioBackend.h index 805ca86eba..ffca646b9a 100644 --- a/rpcs3/Emu/Audio/AudioThread.h +++ b/rpcs3/Emu/Audio/AudioBackend.h @@ -10,22 +10,42 @@ enum : u32 AUDIO_BUFFER_SAMPLES = 256 }; -class AudioThread +class AudioBackend { public: - virtual ~AudioThread() = default; + enum Capabilities : u32 + { + NON_BLOCKING = 0x1, + IS_PLAYING = 0x2, + GET_NUM_ENQUEUED_SAMPLES = 0x4, + }; + + virtual ~AudioBackend() = default; // Callbacks + virtual const char* GetName() const = 0; + virtual u32 GetCapabilities() const = 0; + virtual void Open() = 0; virtual void Close() = 0; virtual void Play() = 0; virtual void Pause() = 0; - virtual bool IsPlaying() = 0; + + virtual bool IsPlaying() + { + fmt::throw_exception("IsPlaying() not implemented"); + }; virtual bool AddData(const void* src, int size) = 0; virtual void Flush() = 0; + virtual u64 GetNumEnqueuedSamples() + { + fmt::throw_exception("GetNumEnqueuedSamples() not implemented"); + return 0; + } + // Helper methods static u32 get_sampling_rate() { diff --git a/rpcs3/Emu/Audio/Null/NullAudioBackend.h b/rpcs3/Emu/Audio/Null/NullAudioBackend.h new file mode 100644 index 0000000000..6ef96629ee --- /dev/null +++ b/rpcs3/Emu/Audio/Null/NullAudioBackend.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Emu/Audio/AudioBackend.h" + +class NullAudioBackend : public AudioBackend +{ +public: + NullAudioBackend() {} + virtual ~NullAudioBackend() {} + + virtual const char* GetName() const override { return "NullAudioBackend"; } + + static const u32 capabilities = NON_BLOCKING; + virtual u32 GetCapabilities() const override { return capabilities; }; + + virtual void Open() override {}; + virtual void Close() override {}; + + virtual void Play() override {}; + virtual void Pause() override {}; + + virtual bool AddData(const void* src, int size) override { return true; }; + virtual void Flush() override {}; +}; diff --git a/rpcs3/Emu/Audio/Null/NullAudioThread.h b/rpcs3/Emu/Audio/Null/NullAudioThread.h deleted file mode 100644 index a1ccf99d84..0000000000 --- a/rpcs3/Emu/Audio/Null/NullAudioThread.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "Emu/Audio/AudioThread.h" - -class NullAudioThread : public AudioThread -{ -public: - NullAudioThread() {} - virtual ~NullAudioThread() {} - - virtual void Open() {}; - virtual void Close() {}; - - virtual void Play() {}; - virtual void Pause() {}; - virtual bool IsPlaying() { return true; }; - - virtual bool AddData(const void* src, int size) { return true; }; - virtual void Flush() {}; -}; diff --git a/rpcs3/Emu/Audio/Pulse/PulseThread.h b/rpcs3/Emu/Audio/Pulse/PulseThread.h index 657722f622..94dda9e8e2 100644 --- a/rpcs3/Emu/Audio/Pulse/PulseThread.h +++ b/rpcs3/Emu/Audio/Pulse/PulseThread.h @@ -4,7 +4,7 @@ #include #include "Emu/Audio/AudioThread.h" -class PulseThread : public AudioThread +class PulseThread : public AudioBackend { public: PulseThread(); diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio27Thread.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio27Backend.cpp similarity index 69% rename from rpcs3/Emu/Audio/XAudio2/XAudio27Thread.cpp rename to rpcs3/Emu/Audio/XAudio2/XAudio27Backend.cpp index 62a20245ba..fe8d822c76 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio27Thread.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio27Backend.cpp @@ -4,7 +4,7 @@ #include "Utilities/StrFmt.h" #include "Emu/System.h" -#include "XAudio2Thread.h" +#include "XAudio2Backend.h" #include "3rdparty/XAudio2_7/XAudio2.h" static thread_local HMODULE s_tls_xaudio2_lib{}; @@ -12,7 +12,7 @@ static thread_local IXAudio2* s_tls_xaudio2_instance{}; static thread_local IXAudio2MasteringVoice* s_tls_master_voice{}; static thread_local IXAudio2SourceVoice* s_tls_source_voice{}; -void XAudio2Thread::xa27_init(void* lib2_7) +void XAudio2Backend::xa27_init(void* lib2_7) { s_tls_xaudio2_lib = (HMODULE)lib2_7; @@ -21,7 +21,7 @@ void XAudio2Thread::xa27_init(void* lib2_7) hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : CoInitializeEx() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : CoInitializeEx() failed(0x%08x)", (u32)hr); Emu.Pause(); return; } @@ -29,7 +29,7 @@ void XAudio2Thread::xa27_init(void* lib2_7) hr = XAudio2Create(&s_tls_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : XAudio2Create() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : XAudio2Create() failed(0x%08x)", (u32)hr); Emu.Pause(); return; } @@ -37,13 +37,13 @@ void XAudio2Thread::xa27_init(void* lib2_7) hr = s_tls_xaudio2_instance->CreateMasteringVoice(&s_tls_master_voice, g_cfg.audio.downmix_to_2ch ? 2 : 8, 48000); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : CreateMasteringVoice() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : CreateMasteringVoice() failed(0x%08x)", (u32)hr); s_tls_xaudio2_instance->Release(); Emu.Pause(); } } -void XAudio2Thread::xa27_destroy() +void XAudio2Backend::xa27_destroy() { if (s_tls_source_voice != nullptr) { @@ -67,37 +67,37 @@ void XAudio2Thread::xa27_destroy() FreeLibrary(s_tls_xaudio2_lib); } -void XAudio2Thread::xa27_play() +void XAudio2Backend::xa27_play() { HRESULT hr = s_tls_source_voice->Start(); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : Start() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : Start() failed(0x%08x)", (u32)hr); Emu.Pause(); } } -void XAudio2Thread::xa27_flush() +void XAudio2Backend::xa27_flush() { HRESULT hr = s_tls_source_voice->FlushSourceBuffers(); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : FlushSourceBuffers() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : FlushSourceBuffers() failed(0x%08x)", (u32)hr); Emu.Pause(); } } -void XAudio2Thread::xa27_stop() +void XAudio2Backend::xa27_stop() { HRESULT hr = s_tls_source_voice->Stop(); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : Stop() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : Stop() failed(0x%08x)", (u32)hr); Emu.Pause(); } } -bool XAudio2Thread::xa27_is_playing() +bool XAudio2Backend::xa27_is_playing() { XAUDIO2_VOICE_STATE state; s_tls_source_voice->GetState(&state); @@ -105,7 +105,7 @@ bool XAudio2Thread::xa27_is_playing() return state.BuffersQueued > 0 || state.pCurrentBufferContext != nullptr; } -void XAudio2Thread::xa27_open() +void XAudio2Backend::xa27_open() { HRESULT hr; @@ -125,7 +125,7 @@ void XAudio2Thread::xa27_open() hr = s_tls_xaudio2_instance->CreateSourceVoice(&s_tls_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : CreateSourceVoice() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : CreateSourceVoice() failed(0x%08x)", (u32)hr); Emu.Pause(); return; } @@ -133,7 +133,7 @@ void XAudio2Thread::xa27_open() s_tls_source_voice->SetVolume(channels == 2 ? 1.0f : 4.0f); } -bool XAudio2Thread::xa27_add(const void* src, int size) +bool XAudio2Backend::xa27_add(const void* src, int size) { XAUDIO2_VOICE_STATE state; s_tls_source_voice->GetState(&state); @@ -141,7 +141,7 @@ bool XAudio2Thread::xa27_add(const void* src, int size) // XAudio 2.7 bug workaround, when it says "SimpList: non-growable list ran out of room for new elements" and hits int 3 if (state.BuffersQueued >= MAX_AUDIO_BUFFERS) { - LOG_WARNING(GENERAL, "XAudio2Thread : too many buffers enqueued (%d, pos=%u)", state.BuffersQueued, state.SamplesPlayed); + LOG_WARNING(GENERAL, "XAudio2Backend : too many buffers enqueued (%d, pos=%u)", state.BuffersQueued, state.SamplesPlayed); return false; } @@ -160,11 +160,21 @@ bool XAudio2Thread::xa27_add(const void* src, int size) HRESULT hr = s_tls_source_voice->SubmitSourceBuffer(&buffer); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : AddData() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : AddData() failed(0x%08x)", (u32)hr); Emu.Pause(); + return false; } return true; } +u64 XAudio2Backend::xa27_enqueued_samples() +{ + XAUDIO2_VOICE_STATE state; + s_tls_source_voice->GetState(&state); + + // all buffers contain AUDIO_BUFFER_SAMPLES, so we can easily calculate how many samples there are remaining + return (AUDIO_BUFFER_SAMPLES - state.SamplesPlayed % AUDIO_BUFFER_SAMPLES) + (state.BuffersQueued * AUDIO_BUFFER_SAMPLES); +} + #endif diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio28Thread.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio28Backend.cpp similarity index 70% rename from rpcs3/Emu/Audio/XAudio2/XAudio28Thread.cpp rename to rpcs3/Emu/Audio/XAudio2/XAudio28Backend.cpp index d6953fc818..add61ee7a1 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio28Thread.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio28Backend.cpp @@ -4,7 +4,7 @@ #include "Utilities/StrFmt.h" #include "Emu/System.h" -#include "XAudio2Thread.h" +#include "XAudio2Backend.h" #include "3rdparty/minidx12/Include/xaudio2.h" static thread_local HMODULE s_tls_xaudio2_lib{}; @@ -12,7 +12,7 @@ static thread_local IXAudio2* s_tls_xaudio2_instance{}; static thread_local IXAudio2MasteringVoice* s_tls_master_voice{}; static thread_local IXAudio2SourceVoice* s_tls_source_voice{}; -void XAudio2Thread::xa28_init(void* lib) +void XAudio2Backend::xa28_init(void* lib) { s_tls_xaudio2_lib = (HMODULE)lib; @@ -23,7 +23,7 @@ void XAudio2Thread::xa28_init(void* lib) hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : CoInitializeEx() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : CoInitializeEx() failed(0x%08x)", (u32)hr); Emu.Pause(); return; } @@ -31,7 +31,7 @@ void XAudio2Thread::xa28_init(void* lib) hr = create(&s_tls_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : XAudio2Create() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : XAudio2Create() failed(0x%08x)", (u32)hr); Emu.Pause(); return; } @@ -39,13 +39,13 @@ void XAudio2Thread::xa28_init(void* lib) hr = s_tls_xaudio2_instance->CreateMasteringVoice(&s_tls_master_voice, g_cfg.audio.downmix_to_2ch ? 2 : 8, 48000); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : CreateMasteringVoice() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : CreateMasteringVoice() failed(0x%08x)", (u32)hr); s_tls_xaudio2_instance->Release(); Emu.Pause(); } } -void XAudio2Thread::xa28_destroy() +void XAudio2Backend::xa28_destroy() { if (s_tls_source_voice != nullptr) { @@ -69,43 +69,43 @@ void XAudio2Thread::xa28_destroy() FreeLibrary(s_tls_xaudio2_lib); } -void XAudio2Thread::xa28_play() +void XAudio2Backend::xa28_play() { AUDIT(s_tls_source_voice != nullptr); HRESULT hr = s_tls_source_voice->Start(); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : Start() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : Start() failed(0x%08x)", (u32)hr); Emu.Pause(); } } -void XAudio2Thread::xa28_flush() +void XAudio2Backend::xa28_flush() { AUDIT(s_tls_source_voice != nullptr); HRESULT hr = s_tls_source_voice->FlushSourceBuffers(); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : FlushSourceBuffers() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : FlushSourceBuffers() failed(0x%08x)", (u32)hr); Emu.Pause(); } } -void XAudio2Thread::xa28_stop() +void XAudio2Backend::xa28_stop() { AUDIT(s_tls_source_voice != nullptr); HRESULT hr = s_tls_source_voice->Stop(); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : Stop() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : Stop() failed(0x%08x)", (u32)hr); Emu.Pause(); } } -bool XAudio2Thread::xa28_is_playing() +bool XAudio2Backend::xa28_is_playing() { AUDIT(s_tls_source_voice != nullptr); @@ -115,7 +115,7 @@ bool XAudio2Thread::xa28_is_playing() return state.BuffersQueued > 0 || state.pCurrentBufferContext != nullptr; } -void XAudio2Thread::xa28_open() +void XAudio2Backend::xa28_open() { HRESULT hr; @@ -135,7 +135,7 @@ void XAudio2Thread::xa28_open() hr = s_tls_xaudio2_instance->CreateSourceVoice(&s_tls_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : CreateSourceVoice() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : CreateSourceVoice() failed(0x%08x)", (u32)hr); Emu.Pause(); return; } @@ -144,7 +144,7 @@ void XAudio2Thread::xa28_open() s_tls_source_voice->SetVolume(channels == 2 ? 1.0 : 4.0); } -bool XAudio2Thread::xa28_add(const void* src, int size) +bool XAudio2Backend::xa28_add(const void* src, int size) { AUDIT(s_tls_source_voice != nullptr); @@ -153,7 +153,7 @@ bool XAudio2Thread::xa28_add(const void* src, int size) if (state.BuffersQueued >= MAX_AUDIO_BUFFERS) { - LOG_WARNING(GENERAL, "XAudio2Thread : too many buffers enqueued (%d, pos=%u)", state.BuffersQueued, state.SamplesPlayed); + LOG_WARNING(GENERAL, "XAudio2Backend : too many buffers enqueued (%d, pos=%u)", state.BuffersQueued, state.SamplesPlayed); return false; } @@ -172,11 +172,21 @@ bool XAudio2Thread::xa28_add(const void* src, int size) HRESULT hr = s_tls_source_voice->SubmitSourceBuffer(&buffer); if (FAILED(hr)) { - LOG_ERROR(GENERAL, "XAudio2Thread : AddData() failed(0x%08x)", (u32)hr); + LOG_ERROR(GENERAL, "XAudio2Backend : AddData() failed(0x%08x)", (u32)hr); Emu.Pause(); + return false; } return true; } +u64 XAudio2Backend::xa28_enqueued_samples() +{ + XAUDIO2_VOICE_STATE state; + s_tls_source_voice->GetState(&state); + + // all buffers contain AUDIO_BUFFER_SAMPLES, so we can easily calculate how many samples there are remaining + return (AUDIO_BUFFER_SAMPLES - state.SamplesPlayed % AUDIO_BUFFER_SAMPLES) + (state.BuffersQueued * AUDIO_BUFFER_SAMPLES); +} + #endif diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp similarity index 75% rename from rpcs3/Emu/Audio/XAudio2/XAudio2Thread.cpp rename to rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp index 5bd13d4d3c..8af0304ac7 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp @@ -3,16 +3,11 @@ #include "Utilities/Log.h" #include "Utilities/StrFmt.h" -#include "XAudio2Thread.h" +#include "XAudio2Backend.h" #include -XAudio2Thread::XAudio2Thread() +XAudio2Backend::XAudio2Backend() { - if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) - { - LOG_ERROR(GENERAL, "XAudio: failed to increase thread priority"); - } - if (auto lib2_9 = LoadLibraryExW(L"XAudio2_9.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)) { // xa28* implementation is fully compatible with library 2.9 @@ -25,11 +20,29 @@ XAudio2Thread::XAudio2Thread() m_funcs.open = &xa28_open; m_funcs.is_playing = &xa28_is_playing; m_funcs.add = &xa28_add; + m_funcs.enqueued_samples = &xa28_enqueued_samples; LOG_SUCCESS(GENERAL, "XAudio 2.9 initialized"); return; } + if (auto lib2_8 = LoadLibraryExW(L"XAudio2_8.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)) + { + xa28_init(lib2_8); + + m_funcs.destroy = &xa28_destroy; + m_funcs.play = &xa28_play; + m_funcs.flush = &xa28_flush; + m_funcs.stop = &xa28_stop; + m_funcs.open = &xa28_open; + m_funcs.is_playing = &xa28_is_playing; + m_funcs.add = &xa28_add; + m_funcs.enqueued_samples = &xa28_enqueued_samples; + + LOG_SUCCESS(GENERAL, "XAudio 2.8 initialized"); + return; + } + if (auto lib2_7 = LoadLibraryExW(L"XAudio2_7.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)) { xa27_init(lib2_7); @@ -41,69 +54,59 @@ XAudio2Thread::XAudio2Thread() m_funcs.open = &xa27_open; m_funcs.is_playing = &xa27_is_playing; m_funcs.add = &xa27_add; + m_funcs.enqueued_samples = &xa27_enqueued_samples; LOG_SUCCESS(GENERAL, "XAudio 2.7 initialized"); return; } - - if (auto lib2_8 = LoadLibraryExW(L"XAudio2_8.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)) - { - xa28_init(lib2_8); - - m_funcs.destroy = &xa28_destroy; - m_funcs.play = &xa28_play; - m_funcs.flush = &xa28_flush; - m_funcs.stop = &xa28_stop; - m_funcs.open = &xa28_open; - m_funcs.is_playing = &xa28_is_playing; - m_funcs.add = &xa28_add; - - LOG_SUCCESS(GENERAL, "XAudio 2.8 initialized"); - return; - } fmt::throw_exception("No supported XAudio2 library found"); } -XAudio2Thread::~XAudio2Thread() +XAudio2Backend::~XAudio2Backend() { m_funcs.destroy(); } -void XAudio2Thread::Play() +void XAudio2Backend::Play() { m_funcs.play(); } -void XAudio2Thread::Close() +void XAudio2Backend::Close() { m_funcs.stop(); m_funcs.flush(); } -void XAudio2Thread::Pause() +void XAudio2Backend::Pause() { m_funcs.stop(); } -void XAudio2Thread::Open() +void XAudio2Backend::Open() { m_funcs.open(); } -bool XAudio2Thread::IsPlaying() +bool XAudio2Backend::IsPlaying() { return m_funcs.is_playing(); } -bool XAudio2Thread::AddData(const void* src, int size) +bool XAudio2Backend::AddData(const void* src, int size) { return m_funcs.add(src, size); } -void XAudio2Thread::Flush() +void XAudio2Backend::Flush() { m_funcs.flush(); } +u64 XAudio2Backend::GetNumEnqueuedSamples() +{ + return m_funcs.enqueued_samples(); +} + #endif diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.h b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h similarity index 64% rename from rpcs3/Emu/Audio/XAudio2/XAudio2Thread.h rename to rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h index 9a77d442f2..d79c6fbda6 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.h +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h @@ -2,9 +2,9 @@ #ifdef _WIN32 -#include "Emu/Audio/AudioThread.h" +#include "Emu/Audio/AudioBackend.h" -class XAudio2Thread : public AudioThread +class XAudio2Backend : public AudioBackend { struct vtable { @@ -15,6 +15,7 @@ class XAudio2Thread : public AudioThread void(*open)(); bool(*is_playing)(); bool(*add)(const void*, int); + u64(*enqueued_samples)(); }; vtable m_funcs; @@ -27,6 +28,7 @@ class XAudio2Thread : public AudioThread static void xa27_open(); static bool xa27_is_playing(); static bool xa27_add(const void*, int); + static u64 xa27_enqueued_samples(); static void xa28_init(void*); static void xa28_destroy(); @@ -36,10 +38,16 @@ class XAudio2Thread : public AudioThread static void xa28_open(); static bool xa28_is_playing(); static bool xa28_add(const void*, int); + static u64 xa28_enqueued_samples(); public: - XAudio2Thread(); - virtual ~XAudio2Thread() override; + XAudio2Backend(); + virtual ~XAudio2Backend() override; + + virtual const char* GetName() const override { return "XAudio2Backend"; }; + + static const u32 capabilities = NON_BLOCKING | IS_PLAYING | GET_NUM_ENQUEUED_SAMPLES; + virtual u32 GetCapabilities() const override { return capabilities; }; virtual void Open() override; virtual void Close() override; @@ -50,6 +58,8 @@ public: virtual bool AddData(const void* src, int size) override; virtual void Flush() override; + + virtual u64 GetNumEnqueuedSamples() override; }; #endif diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.cpp b/rpcs3/Emu/Cell/Modules/cellAudio.cpp index 21ad34278b..6db3b2a593 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAudio.cpp @@ -37,21 +37,19 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } -audio_ringbuffer::audio_ringbuffer(u32 num_buffers, u32 audio_sampling_rate, u32 channels) - : num_allocated_buffers(num_buffers) - , audio_sampling_rate(audio_sampling_rate) +audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg) + : cfg(_cfg) , backend(Emu.GetCallbacks().get_audio()) - , channels(channels) - , buf_sz(AUDIO_BUFFER_SAMPLES * channels) + , buf_sz(AUDIO_BUFFER_SAMPLES * cfg.audio_channels) , emu_paused(Emu.IsPaused()) { // Initialize buffers - if (num_allocated_buffers >= MAX_AUDIO_BUFFERS) + if (cfg.num_allocated_buffers >= MAX_AUDIO_BUFFERS) { fmt::throw_exception("MAX_AUDIO_BUFFERS is too small"); } - for (u32 i = 0; i < num_allocated_buffers; i++) + for (u32 i = 0; i < cfg.num_allocated_buffers; i++) { buffer[i].reset(new float[buf_sz]{}); } @@ -59,37 +57,53 @@ audio_ringbuffer::audio_ringbuffer(u32 num_buffers, u32 audio_sampling_rate, u32 // Init audio dumper if enabled if (g_cfg.audio.dump_to_file) { - m_dump.reset(new AudioDumper(channels)); + m_dump.reset(new AudioDumper(cfg.audio_channels)); + } + + // Sanity check configuration vs. capabilities + backend_capabilities = backend->GetCapabilities(); + if (cfg.buffering_enabled) + { + if (!(backend_capabilities & AudioBackend::NON_BLOCKING) || !(backend_capabilities & AudioBackend::IS_PLAYING)) + { + // We need a non-blocking backend to be able to do buffering correctly + fmt::throw_exception("Audio backend %s does not support buffering.", backend->GetName()); + } } // Initialize backend backend->Open(); backend_open = true; - ASSERT(!backend->IsPlaying()); + + ASSERT(!backend_is_playing()); } audio_ringbuffer::~audio_ringbuffer() { if (!backend_open) + { return; + } - if (backend->IsPlaying()) + if (backend_is_playing()) + { backend->Pause(); + } backend->Close(); } void audio_ringbuffer::enqueue(const float* in_buffer) { - AUDIT(next_buf < num_allocated_buffers); + AUDIT(cur_pos < cfg.num_allocated_buffers); // Prepare buffer const void* buf = in_buffer; if (buf == nullptr) { - buf = buffer[next_buf].get(); - next_buf = (next_buf + 1) % num_allocated_buffers; + buf = buffer[cur_pos].get(); + cur_pos = (cur_pos + 1) % cfg.num_allocated_buffers; } // Dump audio if enabled @@ -165,35 +179,42 @@ u64 audio_ringbuffer::update() } const u64 timestamp = get_timestamp(); - const bool new_playing = !emu_paused && backend->IsPlaying(); + const bool new_playing = !emu_paused && backend_is_playing(); // Calculate how many audio samples have played since last time - // TODO: Natively query backend for remaining samples - if (playing || new_playing) + if (cfg.buffering_enabled && (playing || new_playing)) { - const u64 play_delta = timestamp - (play_timestamp > update_timestamp ? play_timestamp : update_timestamp); - - // NOTE: Only works with a fixed sampling rate - const u64 delta_samples_tmp = (play_delta * audio_sampling_rate) + last_remainder; - last_remainder = delta_samples_tmp % 1'000'000; - const u64 delta_samples = delta_samples_tmp / 1'000'000; - - //cellAudio.error("play_delta=%llu delta_samples=%llu", play_delta, delta_samples); - if (delta_samples > 0) + if (backend_capabilities & AudioBackend::GET_NUM_ENQUEUED_SAMPLES) { + // Backend supports querying for the remaining playtime, so just ask it + enqueued_samples = backend->GetNumEnqueuedSamples(); + } + else + { + const u64 play_delta = timestamp - (play_timestamp > update_timestamp ? play_timestamp : update_timestamp); - if (enqueued_samples < delta_samples) - { - enqueued_samples = 0; - } - else - { - enqueued_samples -= delta_samples; - } + // NOTE: Only works with a fixed sampling rate + const u64 delta_samples_tmp = (play_delta * cfg.audio_sampling_rate) + last_remainder; + last_remainder = delta_samples_tmp % 1'000'000; + const u64 delta_samples = delta_samples_tmp / 1'000'000; - if (enqueued_samples == 0) + //cellAudio.error("play_delta=%llu delta_samples=%llu", play_delta, delta_samples); + if (delta_samples > 0) { - cellAudio.warning("Audio buffer about to underrun!"); + + if (enqueued_samples < delta_samples) + { + enqueued_samples = 0; + } + else + { + enqueued_samples -= delta_samples; + } + + if (enqueued_samples == 0) + { + cellAudio.warning("Audio buffer about to underrun!"); + } } } } @@ -261,7 +282,7 @@ void audio_port::apply_tag_backups(s32 offset) std::tuple cell_audio_thread::count_port_buffer_tags() { - AUDIT(buffering_enabled); + AUDIT(cfg.buffering_enabled); u32 active = 0; u32 in_progress = 0; @@ -339,7 +360,7 @@ void cell_audio_thread::reset_ports(s32 offset) memset(port.get_vm_ptr(offset), 0, port.block_size() * sizeof(float)); - if (buffering_enabled) + if (cfg.buffering_enabled) { //port.reset_tag_backups(offset); port.tag(offset); @@ -398,7 +419,7 @@ void cell_audio_thread::operator()() thread_ctrl::set_native_priority(1); // Allocate ringbuffer - ringbuffer.reset(new audio_ringbuffer(num_allocated_buffers, audio_sampling_rate, audio_channels)); + ringbuffer.reset(new audio_ringbuffer(cfg)); // Initialize loop variables m_counter = 0; @@ -425,12 +446,12 @@ void cell_audio_thread::operator()() const u64 time_since_last_period = timestamp - m_last_period_end; const bool playing = !ringbuffer->is_playing(); - if (!buffering_enabled) + if (!cfg.buffering_enabled) { - const u64 period_end = (m_counter * audio_block_period) + m_start_time; + const u64 period_end = (m_counter * cfg.audio_block_period) + m_start_time; const s64 time_left = period_end - timestamp; - if (time_left > period_comparison_margin) + if (time_left > cfg.period_comparison_margin) { thread_ctrl::wait_for(get_thread_wait_delay(time_left)); continue; @@ -438,8 +459,8 @@ void cell_audio_thread::operator()() } else { - const u64 enqueued_playtime = ringbuffer->get_enqueued_samples() * 1'000'000 / audio_sampling_rate; - const u64 enqueued_buffers = (enqueued_playtime) / audio_block_period; + const u64 enqueued_playtime = ringbuffer->get_enqueued_samples() * 1'000'000 / cfg.audio_sampling_rate; + const u64 enqueued_buffers = (enqueued_playtime) / cfg.audio_block_period; const bool playing = ringbuffer->is_playing(); @@ -455,32 +476,32 @@ void cell_audio_thread::operator()() if (!playing) { // When the buffer is empty, always use the correct block period - m_dynamic_period = audio_block_period; + m_dynamic_period = cfg.audio_block_period; } else { // 1.0 means exactly as desired // <1.0 means not as full as desired // >1.0 means more full than desired - const f32 desired_duration_rate = (enqueued_playtime) / static_cast(desired_buffer_duration); + const f32 desired_duration_rate = (enqueued_playtime) / static_cast(cfg.desired_buffer_duration); if (desired_duration_rate >= 1.0f) { // more full than desired const f32 multiplier = 1.0f / desired_duration_rate; - m_dynamic_period = maximum_block_period - static_cast((maximum_block_period - audio_block_period) * multiplier); + m_dynamic_period = cfg.maximum_block_period - static_cast((cfg.maximum_block_period - cfg.audio_block_period) * multiplier); } else { // not as full as desired const f32 multiplier = desired_duration_rate; - m_dynamic_period = minimum_block_period + static_cast((audio_block_period - minimum_block_period) * multiplier); + m_dynamic_period = cfg.minimum_block_period + static_cast((cfg.audio_block_period - cfg.minimum_block_period) * multiplier); } } } s64 time_left = m_dynamic_period - time_since_last_period; - if (time_left > period_comparison_margin) + if (time_left > cfg.period_comparison_margin) { thread_ctrl::wait_for(get_thread_wait_delay(time_left)); continue; @@ -549,33 +570,33 @@ void cell_audio_thread::operator()() { // We are not playing (likely buffer underrun) // align to 5.(3)ms on global clock - const s64 audio_period_alignment_delta = (timestamp - m_start_time) % audio_block_period; - if (audio_period_alignment_delta > period_comparison_margin) + const s64 audio_period_alignment_delta = (timestamp - m_start_time) % cfg.audio_block_period; + if (audio_period_alignment_delta > cfg.period_comparison_margin) { - thread_ctrl::wait_for(audio_period_alignment_delta - period_comparison_margin); + thread_ctrl::wait_for(audio_period_alignment_delta - cfg.period_comparison_margin); } // Flush, add silence, restart algorithm cellAudio.error("play/resume audio: received first audio buffer"); ringbuffer->flush(); - ringbuffer->enqueue_silence(desired_full_buffers); + ringbuffer->enqueue_silence(cfg.desired_full_buffers); finish_port_volume_stepping(); } } // Mix float *buf = ringbuffer->get_current_buffer(); - if (audio_channels == 2) + if (cfg.audio_channels == 2) { mix(buf); } - else if (audio_channels == 8) + else if (cfg.audio_channels == 8) { mix(buf); } else { - fmt::throw_exception("Unsupported number of audio channels: %u", audio_channels); + fmt::throw_exception("Unsupported number of audio channels: %u", cfg.audio_channels); } // Enqueue @@ -604,7 +625,7 @@ void cell_audio_thread::mix(float *out_buffer, s32 offset) { if (port.state != audio_port_state::started) continue; - if (buffering_enabled) + if (cfg.buffering_enabled) { port.apply_tag_backups(offset); } @@ -1094,7 +1115,7 @@ error_code cellAudioGetPortTimestamp(u32 portNum, u64 tag, vm::ptr stamp) } u64 delta_tag = port.global_counter - tag; - u64 delta_tag_stamp = delta_tag * g_audio->audio_block_period; + u64 delta_tag_stamp = delta_tag * g_audio->cfg.audio_block_period; *stamp = port.timestamp - delta_tag_stamp; return CELL_OK; diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.h b/rpcs3/Emu/Cell/Modules/cellAudio.h index 25f14eb982..1288509714 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.h +++ b/rpcs3/Emu/Cell/Modules/cellAudio.h @@ -2,7 +2,7 @@ #include "Utilities/Thread.h" #include "Emu/Memory/vm.h" -#include "Emu/Audio/AudioThread.h" +#include "Emu/Audio/AudioBackend.h" #include "Emu/Audio/AudioDumper.h" // Error codes @@ -170,15 +170,34 @@ struct audio_port void apply_tag_backups(s32 offset = 0); }; +struct cell_audio_config +{ + const s64 period_comparison_margin = 100; // When comparing the current period time with the desired period, if it is below this number of usecs we do not wait any longer + + const u32 audio_channels = AudioBackend::get_channels(); + const u32 audio_sampling_rate = AudioBackend::get_sampling_rate(); + const u64 audio_block_period = AUDIO_BUFFER_SAMPLES * 1000000 / audio_sampling_rate; + const u64 desired_buffer_duration = g_cfg.audio.enable_buffering ? g_cfg.audio.desired_buffer_duration : 0; + + const u32 audio_buffer_length = AUDIO_BUFFER_SAMPLES * audio_channels; + const u32 audio_buffer_size = audio_buffer_length * sizeof(f32); + const bool buffering_enabled = g_cfg.audio.enable_buffering && (desired_buffer_duration >= audio_block_period); + + const u64 minimum_block_period = audio_block_period / 2; // the block period will not be dynamically lowered below this value (usecs) + const u64 maximum_block_period = audio_block_period + (audio_block_period - minimum_block_period); // the block period will not be dynamically increased above this value (usecs) + + const u32 desired_full_buffers = buffering_enabled ? static_cast(desired_buffer_duration / audio_block_period) + 1 : 1; + const u32 num_allocated_buffers = desired_full_buffers + EXTRA_AUDIO_BUFFERS; // number of ringbuffer buffers +}; + class audio_ringbuffer { private: - const std::shared_ptr backend; + const std::shared_ptr backend; + + const cell_audio_config& cfg; - const u32 num_allocated_buffers; const u32 buf_sz; - const u32 audio_sampling_rate; - const u32 channels; std::unique_ptr m_dump; @@ -189,16 +208,23 @@ private: bool playing = false; bool emu_paused = false; + u32 backend_capabilities; + u64 update_timestamp = 0; u64 play_timestamp = 0; u64 last_remainder = 0; u64 enqueued_samples = 0; - u32 next_buf = 0; + u32 cur_pos = 0; + + bool backend_is_playing() const + { + return (backend_capabilities & AudioBackend::IS_PLAYING) ? backend->IsPlaying() : playing; + } public: - audio_ringbuffer(u32 num_buffers, u32 audio_sampling_rate, u32 channels); + audio_ringbuffer(cell_audio_config &cfg); ~audio_ringbuffer(); void play(); @@ -209,16 +235,11 @@ public: float* get_buffer(u32 num) const { - AUDIT(num < num_allocated_buffers); + AUDIT(num < cfg.num_allocated_buffers); AUDIT(buffer[num].get() != nullptr); return buffer[num].get(); } - u32 get_buf_sz() const - { - return buf_sz; - } - u64 get_timestamp() const { return get_system_time() - Emu.GetPauseTime(); @@ -226,11 +247,12 @@ public: float* get_current_buffer() const { - return get_buffer(next_buf); + return get_buffer(cur_pos); } u64 get_enqueued_samples() const { + AUDIT(cfg.buffering_enabled); return enqueued_samples; } @@ -238,6 +260,11 @@ public: { return playing; } + + u32 capabilities() const + { + return backend_capabilities; + } }; @@ -260,19 +287,7 @@ class cell_audio_thread } public: - const s64 period_comparison_margin = 100; // When comparing the current period time with the desired period, if it is below this number of usecs we do not wait any longer - - const u32 audio_channels = AudioThread::get_channels(); - const u32 audio_sampling_rate = AudioThread::get_sampling_rate(); - const u64 audio_block_period = AUDIO_BUFFER_SAMPLES * 1000000 / audio_sampling_rate; - const u64 desired_buffer_duration = g_cfg.audio.enable_buffering ? g_cfg.audio.desired_buffer_duration : 0; - const bool buffering_enabled = g_cfg.audio.enable_buffering && (desired_buffer_duration >= audio_block_period); - - const u64 minimum_block_period = audio_block_period / 2; // the block period will not be dynamically lowered below this value (usecs) - const u64 maximum_block_period = audio_block_period + (audio_block_period - minimum_block_period); // the block period will not be dynamically increased above this value (usecs) - - const u32 desired_full_buffers = buffering_enabled ? static_cast(desired_buffer_duration / audio_block_period) + 1 : 1; - const u32 num_allocated_buffers = desired_full_buffers + EXTRA_AUDIO_BUFFERS; // number of ringbuffer buffers + cell_audio_config cfg; std::vector keys; std::array ports; diff --git a/rpcs3/Emu/Cell/Modules/libmixer.cpp b/rpcs3/Emu/Cell/Modules/libmixer.cpp index f07053ffe5..61f8fa06c7 100644 --- a/rpcs3/Emu/Cell/Modules/libmixer.cpp +++ b/rpcs3/Emu/Cell/Modules/libmixer.cpp @@ -633,7 +633,7 @@ s32 cellSurMixerGetTimestamp(u64 tag, vm::ptr stamp) const auto g_audio = fxm::get(); - *stamp = g_audio->m_start_time + tag * AUDIO_BUFFER_SAMPLES * 1'000'000 / g_audio->audio_sampling_rate; + *stamp = g_audio->m_start_time + tag * AUDIO_BUFFER_SAMPLES * 1'000'000 / g_audio->cfg.audio_sampling_rate; return CELL_OK; } diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index b59767772d..c59e1c4602 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -196,7 +196,7 @@ struct EmuCallbacks std::function()> get_pad_handler; std::function()> get_gs_frame; std::function()> get_gs_render; - std::function()> get_audio; + std::function()> get_audio; std::function()> get_msg_dialog; std::function()> get_osk_dialog; std::function()> get_save_dialog; diff --git a/rpcs3/XAudio.vcxproj b/rpcs3/XAudio.vcxproj index 921afab18f..ea1ab488a1 100644 --- a/rpcs3/XAudio.vcxproj +++ b/rpcs3/XAudio.vcxproj @@ -72,12 +72,12 @@ - + - - - + + + diff --git a/rpcs3/XAudio.vcxproj.filters b/rpcs3/XAudio.vcxproj.filters index 84d38c3e6a..f882c3bc6c 100644 --- a/rpcs3/XAudio.vcxproj.filters +++ b/rpcs3/XAudio.vcxproj.filters @@ -7,18 +7,18 @@ - + Source Files - + Source Files - + Source Files - + Source Files diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index dd25ea47d1..5e58a1a0bd 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -406,8 +406,8 @@ - - + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 3a06f8ffb5..d64cefec91 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -925,9 +925,6 @@ Utilities - - Emu\Audio\Null - Utilities @@ -1456,8 +1453,11 @@ Emu\GPU\RSX\Common - + Emu\Audio + + Emu\Audio\Null + \ No newline at end of file diff --git a/rpcs3/rpcs3_app.cpp b/rpcs3/rpcs3_app.cpp index 6076bc089b..346e191803 100644 --- a/rpcs3/rpcs3_app.cpp +++ b/rpcs3/rpcs3_app.cpp @@ -36,7 +36,7 @@ #include "Emu/RSX/Null/NullGSRender.h" #include "Emu/RSX/GL/GLGSRender.h" -#include "Emu/Audio/Null/NullAudioThread.h" +#include "Emu/Audio/Null/NullAudioBackend.h" //#include "Emu/Audio/AL/OpenALThread.h" #ifdef _MSC_VER #include "Emu/RSX/D3D12/D3D12GSRender.h" @@ -45,7 +45,7 @@ #include "Emu/RSX/VK/VKGSRender.h" #endif #ifdef _WIN32 -#include "Emu/Audio/XAudio2/XAudio2Thread.h" +#include "Emu/Audio/XAudio2/XAudio2Backend.h" #endif #ifdef HAVE_ALSA #include "Emu/Audio/ALSA/ALSAThread.h" @@ -254,13 +254,13 @@ void rpcs3_app::InitializeCallbacks() } }; - callbacks.get_audio = []() -> std::shared_ptr + callbacks.get_audio = []() -> std::shared_ptr { switch (audio_renderer type = g_cfg.audio.renderer) { - case audio_renderer::null: return std::make_shared(); + case audio_renderer::null: return std::make_shared(); #ifdef _WIN32 - case audio_renderer::xaudio: return std::make_shared(); + case audio_renderer::xaudio: return std::make_shared(); #endif #ifdef HAVE_ALSA case audio_renderer::alsa: return std::make_shared(); diff --git a/rpcs3/rpcs3_app.h b/rpcs3/rpcs3_app.h index 70d4e17f79..7605404679 100644 --- a/rpcs3/rpcs3_app.h +++ b/rpcs3/rpcs3_app.h @@ -12,7 +12,7 @@ #include "Emu/Io/KeyboardHandler.h" #include "Emu/Io/PadHandler.h" #include "Emu/Io/MouseHandler.h" -#include "Emu/Audio/AudioThread.h" +#include "Emu/Audio/AudioBackend.h" #include "rpcs3qt/msg_dialog_frame.h" #include "rpcs3qt/osk_dialog_frame.h"