Implement Audio Backend Capabilities querying

Also renames "AudioThread" to "AudioBackend". The new name is more
descriptive of what the class really is responsible for, since the
backends are not responsible for managing the audio thread.

NOTE: Right now only XAudio2 is supported
This commit is contained in:
Rui Pinheiro 2018-12-16 17:40:50 +00:00 committed by kd-11
parent 2addbe6be2
commit 5159d3559e
20 changed files with 297 additions and 204 deletions

View File

@ -1,10 +1,10 @@
#pragma once
/*#include "Emu/Audio/AudioThread.h"
/*#include "Emu/Audio/AudioBackend.h"
#include "3rdparty/OpenAL/include/alext.h"
#include <memory>
class OpenALThread : public AudioThread
class OpenALThread : public AudioBackend
{
private:
ALint m_format;

View File

@ -4,7 +4,7 @@
#include "Emu/Audio/AudioThread.h"
class ALSAThread : public AudioThread
class ALSAThread : public AudioBackend
{
public:
ALSAThread();

View File

@ -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()
{

View File

@ -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 {};
};

View File

@ -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() {};
};

View File

@ -4,7 +4,7 @@
#include <pulse/simple.h>
#include "Emu/Audio/AudioThread.h"
class PulseThread : public AudioThread
class PulseThread : public AudioBackend
{
public:
PulseThread();

View File

@ -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

View File

@ -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

View File

@ -3,16 +3,11 @@
#include "Utilities/Log.h"
#include "Utilities/StrFmt.h"
#include "XAudio2Thread.h"
#include "XAudio2Backend.h"
#include <Windows.h>
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

View File

@ -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

View File

@ -37,21 +37,19 @@ void fmt_class_string<CellAudioError>::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<u32, u32, u32, u32> 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<f32>(desired_buffer_duration);
const f32 desired_duration_rate = (enqueued_playtime) / static_cast<f32>(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<u64>((maximum_block_period - audio_block_period) * multiplier);
m_dynamic_period = cfg.maximum_block_period - static_cast<u64>((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<u64>((audio_block_period - minimum_block_period) * multiplier);
m_dynamic_period = cfg.minimum_block_period + static_cast<u64>((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<true>(buf);
}
else if (audio_channels == 8)
else if (cfg.audio_channels == 8)
{
mix<false>(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<u64> 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;

View File

@ -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<u32>(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<AudioThread> backend;
const std::shared_ptr<AudioBackend> 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<AudioDumper> 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<u32>(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<u64> keys;
std::array<audio_port, AUDIO_PORT_COUNT> ports;

View File

@ -633,7 +633,7 @@ s32 cellSurMixerGetTimestamp(u64 tag, vm::ptr<u64> stamp)
const auto g_audio = fxm::get<cell_audio>();
*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;
}

View File

@ -196,7 +196,7 @@ struct EmuCallbacks
std::function<std::shared_ptr<class pad_thread>()> get_pad_handler;
std::function<std::unique_ptr<class GSFrameBase>()> get_gs_frame;
std::function<std::shared_ptr<class GSRender>()> get_gs_render;
std::function<std::shared_ptr<class AudioThread>()> get_audio;
std::function<std::shared_ptr<class AudioBackend>()> get_audio;
std::function<std::shared_ptr<class MsgDialogBase>()> get_msg_dialog;
std::function<std::shared_ptr<class OskDialogBase>()> get_osk_dialog;
std::function<std::unique_ptr<class SaveDialogBase>()> get_save_dialog;

View File

@ -72,12 +72,12 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\rpcs3\Emu\Audio\XAudio2\XAudio2Thread.h" />
<ClInclude Include="Emu\Audio\XAudio2\XAudio2Backend.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\rpcs3\Emu\Audio\XAudio2\XAudio2Thread.cpp" />
<ClCompile Include="Emu\Audio\XAudio2\XAudio27Thread.cpp" />
<ClCompile Include="Emu\Audio\XAudio2\XAudio28Thread.cpp" />
<ClCompile Include="Emu\Audio\XAudio2\XAudio2Backend.cpp" />
<ClCompile Include="Emu\Audio\XAudio2\XAudio27Backend.cpp" />
<ClCompile Include="Emu\Audio\XAudio2\XAudio28Backend.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -7,18 +7,18 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\rpcs3\Emu\Audio\XAudio2\XAudio2Thread.cpp">
<ClCompile Include="Emu\Audio\XAudio2\XAudio2Backend.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Emu\Audio\XAudio2\XAudio28Thread.cpp">
<ClCompile Include="Emu\Audio\XAudio2\XAudio27Backend.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Emu\Audio\XAudio2\XAudio27Thread.cpp">
<ClCompile Include="Emu\Audio\XAudio2\XAudio28Backend.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\rpcs3\Emu\Audio\XAudio2\XAudio2Thread.h">
<ClInclude Include="Emu\Audio\XAudio2\XAudio2Backend.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>

View File

@ -406,8 +406,8 @@
<ClInclude Include="Emu\CPU\CPUTranslator.h" />
<ClInclude Include="Emu\IPC.h" />
<ClInclude Include="Emu\Audio\AudioDumper.h" />
<ClInclude Include="Emu\Audio\AudioThread.h" />
<ClInclude Include="Emu\Audio\Null\NullAudioThread.h" />
<ClInclude Include="Emu\Audio\AudioBackend.h" />
<ClInclude Include="Emu\Audio\Null\NullAudioBackend.h" />
<ClInclude Include="Emu\Cell\Common.h" />
<ClInclude Include="Emu\Cell\ErrorCodes.h" />
<ClInclude Include="Emu\Cell\lv2\sys_cond.h" />

View File

@ -925,9 +925,6 @@
<ClInclude Include="..\Utilities\Thread.h">
<Filter>Utilities</Filter>
</ClInclude>
<ClInclude Include="Emu\Audio\Null\NullAudioThread.h">
<Filter>Emu\Audio\Null</Filter>
</ClInclude>
<ClInclude Include="..\Utilities\File.h">
<Filter>Utilities</Filter>
</ClInclude>
@ -1456,8 +1453,11 @@
<ClInclude Include="Emu\RSX\Common\texture_cache_predictor.h">
<Filter>Emu\GPU\RSX\Common</Filter>
</ClInclude>
<ClInclude Include="Emu\Audio\AudioThread.h">
<ClInclude Include="Emu\Audio\AudioBackend.h">
<Filter>Emu\Audio</Filter>
</ClInclude>
<ClInclude Include="Emu\Audio\Null\NullAudioBackend.h">
<Filter>Emu\Audio\Null</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -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<AudioThread>
callbacks.get_audio = []() -> std::shared_ptr<AudioBackend>
{
switch (audio_renderer type = g_cfg.audio.renderer)
{
case audio_renderer::null: return std::make_shared<NullAudioThread>();
case audio_renderer::null: return std::make_shared<NullAudioBackend>();
#ifdef _WIN32
case audio_renderer::xaudio: return std::make_shared<XAudio2Thread>();
case audio_renderer::xaudio: return std::make_shared<XAudio2Backend>();
#endif
#ifdef HAVE_ALSA
case audio_renderer::alsa: return std::make_shared<ALSAThread>();

View File

@ -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"