Various fixes

In addition, linux builds (and ALSA/PA) now work again
This commit is contained in:
Rui Pinheiro 2018-12-21 02:13:22 +00:00 committed by kd-11
parent 4f39457858
commit 67f9397746
18 changed files with 708 additions and 686 deletions

View File

@ -118,7 +118,7 @@ void OpenALBackend::Close()
} }
} }
bool OpenALBackend::AddData(const void* src, u32 size) bool OpenALBackend::AddData(const void* src, u32 num_samples)
{ {
AUDIT(alIsSource(m_source)); AUDIT(alIsSource(m_source));
@ -133,7 +133,7 @@ bool OpenALBackend::AddData(const void* src, u32 size)
} }
// Copy data to the next available buffer // Copy data to the next available buffer
alBufferData(m_buffers[m_next_buffer], m_format, src, size * m_sample_size, m_sampling_rate); alBufferData(m_buffers[m_next_buffer], m_format, src, num_samples * m_sample_size, m_sampling_rate);
checkForAlError("AddData->alBufferData"); checkForAlError("AddData->alBufferData");
// Enqueue buffer // Enqueue buffer
@ -186,7 +186,7 @@ u64 OpenALBackend::GetNumEnqueuedSamples()
ALint num_queued; ALint num_queued;
alGetSourcei(m_source, AL_BUFFERS_QUEUED, &num_queued); alGetSourcei(m_source, AL_BUFFERS_QUEUED, &num_queued);
checkForAlError("GetNumEnqueuedSamples->alGetSourcei(AL_BUFFERS_QUEUED)"); checkForAlError("GetNumEnqueuedSamples->alGetSourcei(AL_BUFFERS_QUEUED)");
AUDIT(num_queued <= m_num_buffers - m_num_unqueued); AUDIT(static_cast<u32>(num_queued) <= m_num_buffers - m_num_unqueued);
// Get sample position // Get sample position
ALint sample_pos; ALint sample_pos;

View File

@ -26,7 +26,7 @@ public:
virtual const char* GetName() const override { return "OpenAL"; }; virtual const char* GetName() const override { return "OpenAL"; };
static const u32 capabilities = NON_BLOCKING | IS_PLAYING | GET_NUM_ENQUEUED_SAMPLES | SET_FREQUENCY_RATIO; static const u32 capabilities = PLAY_PAUSE_FLUSH | IS_PLAYING | GET_NUM_ENQUEUED_SAMPLES | SET_FREQUENCY_RATIO;
virtual u32 GetCapabilities() const override { return capabilities; }; virtual u32 GetCapabilities() const override { return capabilities; };
virtual void Open(u32 num_buffers) override; virtual void Open(u32 num_buffers) override;

View File

@ -1,15 +1,10 @@
#include "stdafx.h" #include "stdafx.h"
#include "Emu/System.h" #include "Emu/System.h"
#include "ALSAThread.h" #include "ALSABackend.h"
#ifdef HAVE_ALSA #ifdef HAVE_ALSA
#include <alsa/asoundlib.h>
static thread_local snd_pcm_t* s_tls_handle{nullptr};
static thread_local snd_pcm_hw_params_t* s_tls_hw_params{nullptr};
static thread_local snd_pcm_sw_params_t* s_tls_sw_params{nullptr};
static void error(int err, const char* reason) static void error(int err, const char* reason)
{ {
@ -27,7 +22,19 @@ static bool check(int err, const char* reason)
return true; return true;
} }
ALSAThread::ALSAThread() ALSABackend::ALSABackend()
{
}
ALSABackend::~ALSABackend()
{
if (s_tls_sw_params || s_tls_hw_params || s_tls_handle)
{
Close();
}
}
void ALSABackend::Open(u32 num_buffers)
{ {
if (!check(snd_pcm_open(&s_tls_handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK), "snd_pcm_open")) if (!check(snd_pcm_open(&s_tls_handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK), "snd_pcm_open"))
return; return;
@ -44,11 +51,11 @@ ALSAThread::ALSAThread()
if (!check(snd_pcm_hw_params_set_format(s_tls_handle, s_tls_hw_params, g_cfg.audio.convert_to_u16 ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_FLOAT_LE), "snd_pcm_hw_params_set_format")) if (!check(snd_pcm_hw_params_set_format(s_tls_handle, s_tls_hw_params, g_cfg.audio.convert_to_u16 ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_FLOAT_LE), "snd_pcm_hw_params_set_format"))
return; return;
uint rate = 48000; uint rate = get_sampling_rate();
if (!check(snd_pcm_hw_params_set_rate_near(s_tls_handle, s_tls_hw_params, &rate, nullptr), "snd_pcm_hw_params_set_rate_near")) if (!check(snd_pcm_hw_params_set_rate_near(s_tls_handle, s_tls_hw_params, &rate, nullptr), "snd_pcm_hw_params_set_rate_near"))
return; return;
if (!check(snd_pcm_hw_params_set_channels(s_tls_handle, s_tls_hw_params, g_cfg.audio.downmix_to_2ch ? 2 : 8), "snd_pcm_hw_params_set_channels")) if (!check(snd_pcm_hw_params_set_channels(s_tls_handle, s_tls_hw_params, get_channels()), "snd_pcm_hw_params_set_channels"))
return; return;
//uint period = 5333; //uint period = 5333;
@ -58,8 +65,8 @@ ALSAThread::ALSAThread()
//if (!check(snd_pcm_hw_params_set_periods(s_tls_handle, s_tls_hw_params, 4, 0), "snd_pcm_hw_params_set_periods")) //if (!check(snd_pcm_hw_params_set_periods(s_tls_handle, s_tls_hw_params, 4, 0), "snd_pcm_hw_params_set_periods"))
// return; // return;
snd_pcm_uframes_t bufsize_frames = g_cfg.audio.frames * 256; snd_pcm_uframes_t bufsize_frames = num_buffers * AUDIO_BUFFER_SAMPLES;
snd_pcm_uframes_t period_frames = 256; snd_pcm_uframes_t period_frames = AUDIO_BUFFER_SAMPLES;
if (!check(snd_pcm_hw_params_set_buffer_size_near(s_tls_handle, s_tls_hw_params, &bufsize_frames), "snd_pcm_hw_params_set_buffer_size_near")) if (!check(snd_pcm_hw_params_set_buffer_size_near(s_tls_handle, s_tls_hw_params, &bufsize_frames), "snd_pcm_hw_params_set_buffer_size_near"))
return; return;
@ -84,7 +91,7 @@ ALSAThread::ALSAThread()
period_frames *= g_cfg.audio.startt; period_frames *= g_cfg.audio.startt;
if (!check(snd_pcm_sw_params_set_start_threshold(s_tls_handle, s_tls_sw_params, period_frames), "snd_pcm_sw_params_set_start_threshold")) if (!check(snd_pcm_sw_params_set_start_threshold(s_tls_handle, s_tls_sw_params, period_frames + 1), "snd_pcm_sw_params_set_start_threshold"))
return; return;
if (!check(snd_pcm_sw_params_set_stop_threshold(s_tls_handle, s_tls_sw_params, bufsize_frames), "snd_pcm_sw_params_set_stop_threshold")) if (!check(snd_pcm_sw_params_set_stop_threshold(s_tls_handle, s_tls_sw_params, bufsize_frames), "snd_pcm_sw_params_set_stop_threshold"))
@ -99,52 +106,37 @@ ALSAThread::ALSAThread()
LOG_NOTICE(GENERAL, "ALSA: bufsize_frames=%u, period_frames=%u", bufsize_frames, period_frames); LOG_NOTICE(GENERAL, "ALSA: bufsize_frames=%u, period_frames=%u", bufsize_frames, period_frames);
} }
ALSAThread::~ALSAThread() void ALSABackend::Close()
{ {
if (s_tls_sw_params) if (s_tls_sw_params)
{ {
snd_pcm_sw_params_free(s_tls_sw_params); snd_pcm_sw_params_free(s_tls_sw_params);
s_tls_sw_params = nullptr;
} }
if (s_tls_hw_params) if (s_tls_hw_params)
{ {
snd_pcm_hw_params_free(s_tls_hw_params); snd_pcm_hw_params_free(s_tls_hw_params);
s_tls_hw_params = nullptr;
} }
if (s_tls_handle) if (s_tls_handle)
{ {
snd_pcm_close(s_tls_handle); snd_pcm_close(s_tls_handle);
s_tls_handle = nullptr;
} }
} }
void ALSAThread::Play() bool ALSABackend::AddData(const void* src, u32 num_samples)
{ {
} u32 num_frames = num_samples / get_channels();
void ALSAThread::Close() int res = snd_pcm_writei(s_tls_handle, src, num_frames);
{
}
void ALSAThread::Stop()
{
}
void ALSAThread::Open(const void* src, int size)
{
AddData(src, size);
}
void ALSAThread::AddData(const void* src, int size)
{
size /= g_cfg.audio.convert_to_u16 ? 2 : 4;
size /= g_cfg.audio.downmix_to_2ch ? 2 : 8;
int res = snd_pcm_writei(s_tls_handle, src, size);
if (res == -EAGAIN) if (res == -EAGAIN)
{ {
LOG_WARNING(GENERAL, "ALSA: EAGAIN"); LOG_WARNING(GENERAL, "ALSA: EAGAIN");
return; return false;
} }
if (res < 0) if (res < 0)
@ -154,16 +146,19 @@ void ALSAThread::AddData(const void* src, int size)
if (res < 0) if (res < 0)
{ {
LOG_WARNING(GENERAL, "ALSA: failed to recover (%d)", res); LOG_WARNING(GENERAL, "ALSA: failed to recover (%d)", res);
return; return false;
} }
res = snd_pcm_writei(s_tls_handle, src, size); res = snd_pcm_writei(s_tls_handle, src, num_frames);
} }
if (res != size) if (res != num_frames)
{ {
LOG_WARNING(GENERAL, "ALSA: error (%d)", res); LOG_WARNING(GENERAL, "ALSA: error (%d)", res);
return false;
} }
return true;
} }
#endif #endif

View File

@ -0,0 +1,30 @@
#pragma once
#ifdef HAVE_ALSA
#include "Emu/Audio/AudioBackend.h"
#include <alsa/asoundlib.h>
class ALSABackend : public AudioBackend
{
snd_pcm_t* s_tls_handle{nullptr};
snd_pcm_hw_params_t* s_tls_hw_params{nullptr};
snd_pcm_sw_params_t* s_tls_sw_params{nullptr};
public:
ALSABackend();
virtual ~ALSABackend() override;
virtual const char* GetName() const override { return "ALSA"; };
static const u32 capabilities = 0;
virtual u32 GetCapabilities() const override { return capabilities; };
virtual void Open(u32) override;
virtual void Close() override;
virtual bool AddData(const void* src, u32 num_samples) override;
};
#endif

View File

@ -1,20 +0,0 @@
#pragma once
#ifdef HAVE_ALSA
#include "Emu/Audio/AudioThread.h"
class ALSAThread : public AudioBackend
{
public:
ALSAThread();
virtual ~ALSAThread() override;
virtual void Play() override;
virtual void Open(const void* src, int size) override;
virtual void Close() override;
virtual void Stop() override;
virtual void AddData(const void* src, int size) override;
};
#endif

View File

@ -15,45 +15,80 @@ class AudioBackend
public: public:
enum Capabilities : u32 enum Capabilities : u32
{ {
NON_BLOCKING = 0x1, PLAY_PAUSE_FLUSH = 0x1, // AddData implements Play, Pause, Flush
IS_PLAYING = 0x2, IS_PLAYING = 0x2, // Implements IsPlaying method
GET_NUM_ENQUEUED_SAMPLES = 0x4, GET_NUM_ENQUEUED_SAMPLES = 0x4, // Supports GetNumEnqueuedSamples method
SET_FREQUENCY_RATIO = 0x8, SET_FREQUENCY_RATIO = 0x8, // Implements SetFrequencyRatio method
}; };
virtual ~AudioBackend() = default; virtual ~AudioBackend() = default;
// Callbacks /*
* Pure virtual methods
*/
virtual const char* GetName() const = 0; virtual const char* GetName() const = 0;
virtual u32 GetCapabilities() const = 0; virtual u32 GetCapabilities() const = 0;
virtual void Open(u32 num_buffers) = 0; virtual void Open(u32 num_buffers) = 0;
virtual void Close() = 0; virtual void Close() = 0;
virtual void Play() = 0; virtual bool AddData(const void* src, u32 num_samples) = 0;
virtual void Pause() = 0;
/*
* Virtual methods - should be implemented depending on backend capabilities
*/
// Start playing enqueued data
// Should be implemented if capabilities & PLAY_PAUSE_FLUSH
virtual void Play()
{
fmt::throw_exception("Play() not implemented");
}
// Pause playing enqueued data
// Should be implemented if capabilities & PLAY_PAUSE_FLUSH
virtual void Pause()
{
fmt::throw_exception("Pause() not implemented");
}
// Pause audio, and unqueue all currently queued buffers
// Should be implemented if capabilities & PLAY_PAUSE_FLUSH
virtual void Flush()
{
fmt::throw_exception("Flush() not implemented");
}
// Returns true if audio is currently being played, false otherwise
// Should be implemented if capabilities & IS_PLAYING
virtual bool IsPlaying() virtual bool IsPlaying()
{ {
fmt::throw_exception("IsPlaying() not implemented"); fmt::throw_exception("IsPlaying() not implemented");
}; }
virtual bool AddData(const void* src, u32 size) = 0;
virtual void Flush() = 0;
// Returns the number of currently enqueued samples
// Should be implemented if capabilities & GET_NUM_ENQUEUED_SAMPLES
virtual u64 GetNumEnqueuedSamples() virtual u64 GetNumEnqueuedSamples()
{ {
fmt::throw_exception("GetNumEnqueuedSamples() not implemented"); fmt::throw_exception("GetNumEnqueuedSamples() not implemented");
return 0; return 0;
} }
// Sets a new frequency ratio. Backend is allowed to modify the ratio value, e.g. clamping it to the allowed range
// Returns the new frequency ratio set
// Should be implemented if capabilities & SET_FREQUENCY_RATIO
virtual f32 SetFrequencyRatio(f32 /* new_ratio */) // returns the new ratio virtual f32 SetFrequencyRatio(f32 /* new_ratio */) // returns the new ratio
{ {
fmt::throw_exception("SetFrequencyRatio() not implemented"); fmt::throw_exception("SetFrequencyRatio() not implemented");
return 1.0f; return 1.0f;
} }
// Helper methods
/*
* Helper methods
*/
static u32 get_sampling_rate() static u32 get_sampling_rate()
{ {
const u32 sampling_period_multiplier_u32 = g_cfg.audio.sampling_period_multiplier; const u32 sampling_period_multiplier_u32 = g_cfg.audio.sampling_period_multiplier;
@ -76,9 +111,9 @@ public:
return g_cfg.audio.downmix_to_2ch ? 2 : 8; return g_cfg.audio.downmix_to_2ch ? 2 : 8;
} }
bool has_capability(Capabilities cap) const bool has_capability(u32 cap) const
{ {
return (cap & GetCapabilities()) != 0; return (cap & GetCapabilities()) == cap;
} }
void dump_capabilities(std::string& out) const void dump_capabilities(std::string& out) const
@ -86,9 +121,9 @@ public:
u32 count = 0; u32 count = 0;
u32 capabilities = GetCapabilities(); u32 capabilities = GetCapabilities();
if (capabilities & NON_BLOCKING) if (capabilities & PLAY_PAUSE_FLUSH)
{ {
fmt::append(out, "NON_BLOCKING"); fmt::append(out, "PLAY_PAUSE_FLUSH");
count++; count++;
} }

View File

@ -10,7 +10,7 @@ public:
virtual const char* GetName() const override { return "Null"; } virtual const char* GetName() const override { return "Null"; }
static const u32 capabilities = NON_BLOCKING; static const u32 capabilities = PLAY_PAUSE_FLUSH;
virtual u32 GetCapabilities() const override { return capabilities; }; virtual u32 GetCapabilities() const override { return capabilities; };
virtual void Open(u32) override {}; virtual void Open(u32) override {};

View File

@ -1,25 +1,21 @@
#include "Emu/System.h" #include "Emu/System.h"
#include "PulseThread.h" #include "PulseBackend.h"
#ifdef HAVE_PULSE #ifdef HAVE_PULSE
#include <pulse/simple.h> #include <pulse/simple.h>
#include <pulse/error.h> #include <pulse/error.h>
PulseThread::PulseThread() PulseBackend::PulseBackend()
{ {
} }
PulseThread::~PulseThread() PulseBackend::~PulseBackend()
{ {
this->Close(); this->Close();
} }
void PulseThread::Play() void PulseBackend::Close()
{
}
void PulseThread::Close()
{ {
if(this->connection) { if(this->connection) {
pa_simple_free(this->connection); pa_simple_free(this->connection);
@ -27,19 +23,15 @@ void PulseThread::Close()
} }
} }
void PulseThread::Stop() void PulseBackend::Open(u32 /* num_buffers */)
{
}
void PulseThread::Open(const void* src, int size)
{ {
pa_sample_spec ss; pa_sample_spec ss;
ss.format = g_cfg.audio.convert_to_u16 ? PA_SAMPLE_S16LE : PA_SAMPLE_FLOAT32LE; ss.format = (get_sample_size() == 2) ? PA_SAMPLE_S16LE : PA_SAMPLE_FLOAT32LE;
ss.rate = 48000; ss.rate = get_sampling_rate();
pa_channel_map channel_map; pa_channel_map channel_map;
if (g_cfg.audio.downmix_to_2ch) if (get_channels() == 2)
{ {
channel_map.channels = 2; channel_map.channels = 2;
channel_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; channel_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
@ -64,18 +56,19 @@ void PulseThread::Open(const void* src, int size)
if(!this->connection) { if(!this->connection) {
fprintf(stderr, "PulseAudio: Failed to initialize audio: %s\n", pa_strerror(err)); fprintf(stderr, "PulseAudio: Failed to initialize audio: %s\n", pa_strerror(err));
} }
this->AddData(src, size);
} }
void PulseThread::AddData(const void* src, int size) bool PulseBackend::AddData(const void* src, u32 num_samples)
{ {
if(this->connection) { AUDIT(this->connection);
int err;
if(pa_simple_write(this->connection, src, size, &err) < 0) { int err;
fprintf(stderr, "PulseAudio: Failed to write audio stream: %s\n", pa_strerror(err)); if(pa_simple_write(this->connection, src, num_samples * get_sample_size(), &err) < 0) {
} fprintf(stderr, "PulseAudio: Failed to write audio stream: %s\n", pa_strerror(err));
return false;
} }
return true;
} }
#endif #endif

View File

@ -0,0 +1,27 @@
#pragma once
#ifdef HAVE_PULSE
#include <pulse/simple.h>
#include "Emu/Audio/AudioBackend.h"
class PulseBackend : public AudioBackend
{
public:
PulseBackend();
virtual ~PulseBackend() override;
virtual const char* GetName() const override { return "Pulse"; };
static const u32 capabilities = 0;
virtual u32 GetCapabilities() const override { return capabilities; };
virtual void Open(u32) override;
virtual void Close() override;
virtual bool AddData(const void* src, u32 num_samples) override;
private:
pa_simple *connection = nullptr;
};
#endif

View File

@ -1,23 +0,0 @@
#pragma once
#ifdef HAVE_PULSE
#include <pulse/simple.h>
#include "Emu/Audio/AudioThread.h"
class PulseThread : public AudioBackend
{
public:
PulseThread();
virtual ~PulseThread() override;
virtual void Play() override;
virtual void Open(const void* src, int size) override;
virtual void Close() override;
virtual void Stop() override;
virtual void AddData(const void* src, int size) override;
private:
pa_simple *connection = nullptr;
};
#endif

View File

@ -7,189 +7,197 @@
#include "XAudio2Backend.h" #include "XAudio2Backend.h"
#include "3rdparty/XAudio2_7/XAudio2.h" #include "3rdparty/XAudio2_7/XAudio2.h"
static thread_local HMODULE s_tls_xaudio2_lib{}; class XAudio27Library : public XAudio2Backend::XAudio2Library
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 XAudio2Backend::xa27_init(void* lib2_7)
{ {
s_tls_xaudio2_lib = (HMODULE)lib2_7; const HMODULE s_tls_xaudio2_lib;
IXAudio2* s_tls_xaudio2_instance{};
IXAudio2MasteringVoice* s_tls_master_voice{};
IXAudio2SourceVoice* s_tls_source_voice{};
HRESULT hr = S_OK; public:
XAudio27Library(void* lib2_7)
hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); : s_tls_xaudio2_lib(static_cast<HMODULE>(lib2_7))
if (FAILED(hr))
{ {
LOG_ERROR(GENERAL, "XAudio2Backend : CoInitializeEx() failed(0x%08x)", (u32)hr); HRESULT hr = S_OK;
Emu.Pause();
return;
}
hr = XAudio2Create(&s_tls_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR); hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(hr)) if (FAILED(hr))
{ {
LOG_ERROR(GENERAL, "XAudio2Backend : XAudio2Create() failed(0x%08x)", (u32)hr); LOG_ERROR(GENERAL, "XAudio2Backend : CoInitializeEx() failed(0x%08x)", (u32)hr);
Emu.Pause(); Emu.Pause();
return; return;
}
hr = XAudio2Create(&s_tls_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : XAudio2Create() failed(0x%08x)", (u32)hr);
Emu.Pause();
return;
}
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, "XAudio2Backend : CreateMasteringVoice() failed(0x%08x)", (u32)hr);
s_tls_xaudio2_instance->Release();
Emu.Pause();
}
} }
hr = s_tls_xaudio2_instance->CreateMasteringVoice(&s_tls_master_voice, g_cfg.audio.downmix_to_2ch ? 2 : 8, 48000); ~XAudio27Library()
if (FAILED(hr))
{ {
LOG_ERROR(GENERAL, "XAudio2Backend : CreateMasteringVoice() failed(0x%08x)", (u32)hr); if (s_tls_source_voice != nullptr)
s_tls_xaudio2_instance->Release(); {
Emu.Pause(); s_tls_source_voice->Stop();
} s_tls_source_voice->DestroyVoice();
} }
void XAudio2Backend::xa27_destroy() if (s_tls_master_voice != nullptr)
{
s_tls_master_voice->DestroyVoice();
}
if (s_tls_xaudio2_instance != nullptr)
{
s_tls_xaudio2_instance->StopEngine();
s_tls_xaudio2_instance->Release();
}
CoUninitialize();
FreeLibrary(s_tls_xaudio2_lib);
}
virtual void play() override
{
HRESULT hr = s_tls_source_voice->Start();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : Start() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
virtual void flush() override
{
HRESULT hr = s_tls_source_voice->FlushSourceBuffers();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : FlushSourceBuffers() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
virtual void stop() override
{
HRESULT hr = s_tls_source_voice->Stop();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : Stop() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
virtual bool is_playing() override
{
XAUDIO2_VOICE_STATE state;
s_tls_source_voice->GetState(&state);
return state.BuffersQueued > 0 || state.pCurrentBufferContext != nullptr;
}
virtual void open() override
{
HRESULT hr;
const u32 sample_size = AudioBackend::get_sample_size();
const u32 channels = AudioBackend::get_channels();
const u32 sampling_rate = AudioBackend::get_sampling_rate();
WAVEFORMATEX waveformatex;
waveformatex.wFormatTag = g_cfg.audio.convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;
waveformatex.nChannels = channels;
waveformatex.nSamplesPerSec = sampling_rate;
waveformatex.nAvgBytesPerSec = static_cast<DWORD>(sampling_rate * channels * sample_size);
waveformatex.nBlockAlign = channels * sample_size;
waveformatex.wBitsPerSample = sample_size * 8;
waveformatex.cbSize = 0;
hr = s_tls_xaudio2_instance->CreateSourceVoice(&s_tls_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : CreateSourceVoice() failed(0x%08x)", (u32)hr);
Emu.Pause();
return;
}
s_tls_source_voice->SetVolume(channels == 2 ? 1.0f : 4.0f);
}
virtual bool add(const void* src, u32 num_samples) override
{
XAUDIO2_VOICE_STATE state;
s_tls_source_voice->GetState(&state);
// 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, "XAudio2Backend : too many buffers enqueued (%d, pos=%u)", state.BuffersQueued, state.SamplesPlayed);
return false;
}
XAUDIO2_BUFFER buffer;
buffer.AudioBytes = num_samples * AudioBackend::get_sample_size();
buffer.Flags = 0;
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;
buffer.LoopCount = 0;
buffer.LoopLength = 0;
buffer.pAudioData = (const BYTE*)src;
buffer.pContext = 0;
buffer.PlayBegin = 0;
buffer.PlayLength = AUDIO_BUFFER_SAMPLES;
HRESULT hr = s_tls_source_voice->SubmitSourceBuffer(&buffer);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : AddData() failed(0x%08x)", (u32)hr);
Emu.Pause();
return false;
}
return true;
}
virtual u64 enqueued_samples() override
{
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);
}
virtual f32 set_freq_ratio(f32 new_ratio) override
{
new_ratio = std::clamp(new_ratio, XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_DEFAULT_FREQ_RATIO);
HRESULT hr = s_tls_source_voice->SetFrequencyRatio(new_ratio);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : SetFrequencyRatio() failed(0x%08x)", (u32)hr);
Emu.Pause();
return 1.0f;
}
return new_ratio;
}
};
XAudio2Backend::XAudio2Library* XAudio2Backend::xa27_init(void* lib2_7)
{ {
if (s_tls_source_voice != nullptr) return new XAudio27Library(lib2_7);
{
s_tls_source_voice->Stop();
s_tls_source_voice->DestroyVoice();
}
if (s_tls_master_voice != nullptr)
{
s_tls_master_voice->DestroyVoice();
}
if (s_tls_xaudio2_instance != nullptr)
{
s_tls_xaudio2_instance->StopEngine();
s_tls_xaudio2_instance->Release();
}
CoUninitialize();
FreeLibrary(s_tls_xaudio2_lib);
}
void XAudio2Backend::xa27_play()
{
HRESULT hr = s_tls_source_voice->Start();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : Start() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
void XAudio2Backend::xa27_flush()
{
HRESULT hr = s_tls_source_voice->FlushSourceBuffers();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : FlushSourceBuffers() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
void XAudio2Backend::xa27_stop()
{
HRESULT hr = s_tls_source_voice->Stop();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : Stop() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
bool XAudio2Backend::xa27_is_playing()
{
XAUDIO2_VOICE_STATE state;
s_tls_source_voice->GetState(&state);
return state.BuffersQueued > 0 || state.pCurrentBufferContext != nullptr;
}
void XAudio2Backend::xa27_open()
{
HRESULT hr;
const u32 sample_size = get_sample_size();
const u32 channels = get_channels();
const u32 sampling_rate = get_sampling_rate();
WAVEFORMATEX waveformatex;
waveformatex.wFormatTag = g_cfg.audio.convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;
waveformatex.nChannels = channels;
waveformatex.nSamplesPerSec = sampling_rate;
waveformatex.nAvgBytesPerSec = static_cast<DWORD>(sampling_rate * channels * sample_size);
waveformatex.nBlockAlign = channels * sample_size;
waveformatex.wBitsPerSample = sample_size * 8;
waveformatex.cbSize = 0;
hr = s_tls_xaudio2_instance->CreateSourceVoice(&s_tls_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : CreateSourceVoice() failed(0x%08x)", (u32)hr);
Emu.Pause();
return;
}
s_tls_source_voice->SetVolume(channels == 2 ? 1.0f : 4.0f);
}
bool XAudio2Backend::xa27_add(const void* src, u32 size)
{
XAUDIO2_VOICE_STATE state;
s_tls_source_voice->GetState(&state);
// 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, "XAudio2Backend : too many buffers enqueued (%d, pos=%u)", state.BuffersQueued, state.SamplesPlayed);
return false;
}
XAUDIO2_BUFFER buffer;
buffer.AudioBytes = size * get_sample_size();
buffer.Flags = 0;
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;
buffer.LoopCount = 0;
buffer.LoopLength = 0;
buffer.pAudioData = (const BYTE*)src;
buffer.pContext = 0;
buffer.PlayBegin = 0;
buffer.PlayLength = AUDIO_BUFFER_SAMPLES;
HRESULT hr = s_tls_source_voice->SubmitSourceBuffer(&buffer);
if (FAILED(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);
}
f32 XAudio2Backend::xa27_set_freq_ratio(f32 new_ratio)
{
new_ratio = std::clamp(new_ratio, XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_DEFAULT_FREQ_RATIO);
HRESULT hr = s_tls_source_voice->SetFrequencyRatio(new_ratio);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : SetFrequencyRatio() failed(0x%08x)", (u32)hr);
Emu.Pause();
return 1.0f;
}
return new_ratio;
} }
#endif #endif

View File

@ -7,201 +7,209 @@
#include "XAudio2Backend.h" #include "XAudio2Backend.h"
#include "3rdparty/minidx12/Include/xaudio2.h" #include "3rdparty/minidx12/Include/xaudio2.h"
static thread_local HMODULE s_tls_xaudio2_lib{}; class XAudio28Library : public XAudio2Backend::XAudio2Library
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 XAudio2Backend::xa28_init(void* lib)
{ {
s_tls_xaudio2_lib = (HMODULE)lib; const HMODULE s_tls_xaudio2_lib;
IXAudio2* s_tls_xaudio2_instance{};
IXAudio2MasteringVoice* s_tls_master_voice{};
IXAudio2SourceVoice* s_tls_source_voice{};
const auto create = (XAudio2Create)GetProcAddress(s_tls_xaudio2_lib, "XAudio2Create"); public:
XAudio28Library(void* lib2_8)
HRESULT hr = S_OK; : s_tls_xaudio2_lib(static_cast<HMODULE>(lib2_8))
hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(hr))
{ {
LOG_ERROR(GENERAL, "XAudio2Backend : CoInitializeEx() failed(0x%08x)", (u32)hr); const auto create = (XAudio2Create)GetProcAddress(s_tls_xaudio2_lib, "XAudio2Create");
Emu.Pause();
return;
}
hr = create(&s_tls_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR); HRESULT hr = S_OK;
if (FAILED(hr))
{ hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
LOG_ERROR(GENERAL, "XAudio2Backend : XAudio2Create() failed(0x%08x)", (u32)hr); if (FAILED(hr))
Emu.Pause(); {
return; LOG_ERROR(GENERAL, "XAudio2Backend : CoInitializeEx() failed(0x%08x)", (u32)hr);
Emu.Pause();
return;
}
hr = create(&s_tls_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : XAudio2Create() failed(0x%08x)", (u32)hr);
Emu.Pause();
return;
}
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, "XAudio2Backend : CreateMasteringVoice() failed(0x%08x)", (u32)hr);
s_tls_xaudio2_instance->Release();
Emu.Pause();
}
} }
hr = s_tls_xaudio2_instance->CreateMasteringVoice(&s_tls_master_voice, g_cfg.audio.downmix_to_2ch ? 2 : 8, 48000); ~XAudio28Library()
if (FAILED(hr))
{ {
LOG_ERROR(GENERAL, "XAudio2Backend : CreateMasteringVoice() failed(0x%08x)", (u32)hr); if (s_tls_source_voice != nullptr)
s_tls_xaudio2_instance->Release(); {
Emu.Pause(); s_tls_source_voice->Stop();
} s_tls_source_voice->DestroyVoice();
} }
void XAudio2Backend::xa28_destroy() if (s_tls_master_voice != nullptr)
{
s_tls_master_voice->DestroyVoice();
}
if (s_tls_xaudio2_instance != nullptr)
{
s_tls_xaudio2_instance->StopEngine();
s_tls_xaudio2_instance->Release();
}
CoUninitialize();
FreeLibrary(s_tls_xaudio2_lib);
}
virtual void play() override
{
AUDIT(s_tls_source_voice != nullptr);
HRESULT hr = s_tls_source_voice->Start();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : Start() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
virtual void flush() override
{
AUDIT(s_tls_source_voice != nullptr);
HRESULT hr = s_tls_source_voice->FlushSourceBuffers();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : FlushSourceBuffers() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
virtual void stop() override
{
AUDIT(s_tls_source_voice != nullptr);
HRESULT hr = s_tls_source_voice->Stop();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : Stop() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
virtual bool is_playing() override
{
AUDIT(s_tls_source_voice != nullptr);
XAUDIO2_VOICE_STATE state;
s_tls_source_voice->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
return state.BuffersQueued > 0 || state.pCurrentBufferContext != nullptr;
}
virtual void open() override
{
HRESULT hr;
const u32 sample_size = AudioBackend::get_sample_size();
const u32 channels = AudioBackend::get_channels();
const u32 sampling_rate = AudioBackend::get_sampling_rate();
WAVEFORMATEX waveformatex;
waveformatex.wFormatTag = g_cfg.audio.convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;
waveformatex.nChannels = channels;
waveformatex.nSamplesPerSec = sampling_rate;
waveformatex.nAvgBytesPerSec = static_cast<DWORD>(sampling_rate * channels * sample_size);
waveformatex.nBlockAlign = channels * sample_size;
waveformatex.wBitsPerSample = sample_size * 8;
waveformatex.cbSize = 0;
hr = s_tls_xaudio2_instance->CreateSourceVoice(&s_tls_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : CreateSourceVoice() failed(0x%08x)", (u32)hr);
Emu.Pause();
return;
}
AUDIT(s_tls_source_voice != nullptr);
s_tls_source_voice->SetVolume(channels == 2 ? 1.0f : 4.0f);
}
virtual bool add(const void* src, u32 num_samples) override
{
AUDIT(s_tls_source_voice != nullptr);
XAUDIO2_VOICE_STATE state;
s_tls_source_voice->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
if (state.BuffersQueued >= MAX_AUDIO_BUFFERS)
{
LOG_WARNING(GENERAL, "XAudio2Backend : too many buffers enqueued (%d)", state.BuffersQueued);
return false;
}
XAUDIO2_BUFFER buffer;
buffer.AudioBytes = num_samples * AudioBackend::get_sample_size();
buffer.Flags = 0;
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;
buffer.LoopCount = 0;
buffer.LoopLength = 0;
buffer.pAudioData = (const BYTE*)src;
buffer.pContext = 0;
buffer.PlayBegin = 0;
buffer.PlayLength = AUDIO_BUFFER_SAMPLES;
HRESULT hr = s_tls_source_voice->SubmitSourceBuffer(&buffer);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : AddData() failed(0x%08x)", (u32)hr);
Emu.Pause();
return false;
}
return true;
}
virtual u64 enqueued_samples() override
{
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);
}
virtual f32 set_freq_ratio(f32 new_ratio) override
{
new_ratio = std::clamp(new_ratio, XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_DEFAULT_FREQ_RATIO);
HRESULT hr = s_tls_source_voice->SetFrequencyRatio(new_ratio);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : SetFrequencyRatio() failed(0x%08x)", (u32)hr);
Emu.Pause();
return 1.0f;
}
return new_ratio;
}
};
XAudio2Backend::XAudio2Library* XAudio2Backend::xa28_init(void* lib2_8)
{ {
if (s_tls_source_voice != nullptr) return new XAudio28Library(lib2_8);
{
s_tls_source_voice->Stop();
s_tls_source_voice->DestroyVoice();
}
if (s_tls_master_voice != nullptr)
{
s_tls_master_voice->DestroyVoice();
}
if (s_tls_xaudio2_instance != nullptr)
{
s_tls_xaudio2_instance->StopEngine();
s_tls_xaudio2_instance->Release();
}
CoUninitialize();
FreeLibrary(s_tls_xaudio2_lib);
}
void XAudio2Backend::xa28_play()
{
AUDIT(s_tls_source_voice != nullptr);
HRESULT hr = s_tls_source_voice->Start();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : Start() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
void XAudio2Backend::xa28_flush()
{
AUDIT(s_tls_source_voice != nullptr);
HRESULT hr = s_tls_source_voice->FlushSourceBuffers();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : FlushSourceBuffers() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
void XAudio2Backend::xa28_stop()
{
AUDIT(s_tls_source_voice != nullptr);
HRESULT hr = s_tls_source_voice->Stop();
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : Stop() failed(0x%08x)", (u32)hr);
Emu.Pause();
}
}
bool XAudio2Backend::xa28_is_playing()
{
AUDIT(s_tls_source_voice != nullptr);
XAUDIO2_VOICE_STATE state;
s_tls_source_voice->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
return state.BuffersQueued > 0 || state.pCurrentBufferContext != nullptr;
}
void XAudio2Backend::xa28_open()
{
HRESULT hr;
const u32 sample_size = get_sample_size();
const u32 channels = get_channels();
const u32 sampling_rate = get_sampling_rate();
WAVEFORMATEX waveformatex;
waveformatex.wFormatTag = g_cfg.audio.convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;
waveformatex.nChannels = channels;
waveformatex.nSamplesPerSec = sampling_rate;
waveformatex.nAvgBytesPerSec = static_cast<DWORD>(sampling_rate * channels * sample_size);
waveformatex.nBlockAlign = channels * sample_size;
waveformatex.wBitsPerSample = sample_size * 8;
waveformatex.cbSize = 0;
hr = s_tls_xaudio2_instance->CreateSourceVoice(&s_tls_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : CreateSourceVoice() failed(0x%08x)", (u32)hr);
Emu.Pause();
return;
}
AUDIT(s_tls_source_voice != nullptr);
s_tls_source_voice->SetVolume(channels == 2 ? 1.0f : 4.0f);
}
bool XAudio2Backend::xa28_add(const void* src, u32 size)
{
AUDIT(s_tls_source_voice != nullptr);
XAUDIO2_VOICE_STATE state;
s_tls_source_voice->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
if (state.BuffersQueued >= MAX_AUDIO_BUFFERS)
{
LOG_WARNING(GENERAL, "XAudio2Backend : too many buffers enqueued (%d)", state.BuffersQueued);
return false;
}
XAUDIO2_BUFFER buffer;
buffer.AudioBytes = size * get_sample_size();
buffer.Flags = 0;
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;
buffer.LoopCount = 0;
buffer.LoopLength = 0;
buffer.pAudioData = (const BYTE*)src;
buffer.pContext = 0;
buffer.PlayBegin = 0;
buffer.PlayLength = AUDIO_BUFFER_SAMPLES;
HRESULT hr = s_tls_source_voice->SubmitSourceBuffer(&buffer);
if (FAILED(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);
}
f32 XAudio2Backend::xa28_set_freq_ratio(f32 new_ratio)
{
new_ratio = std::clamp(new_ratio, XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_DEFAULT_FREQ_RATIO);
HRESULT hr = s_tls_source_voice->SetFrequencyRatio(new_ratio);
if (FAILED(hr))
{
LOG_ERROR(GENERAL, "XAudio2Backend : SetFrequencyRatio() failed(0x%08x)", (u32)hr);
Emu.Pause();
return 1.0f;
}
return new_ratio;
} }
#endif #endif

View File

@ -8,113 +8,87 @@
XAudio2Backend::XAudio2Backend() XAudio2Backend::XAudio2Backend()
{ {
if (auto lib2_9 = LoadLibraryExW(L"XAudio2_9.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))
{
// xa28* implementation is fully compatible with library 2.9
xa28_init(lib2_9);
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;
m_funcs.set_freq_ratio = &xa28_set_freq_ratio;
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;
m_funcs.set_freq_ratio = &xa28_set_freq_ratio;
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);
m_funcs.destroy = &xa27_destroy;
m_funcs.play = &xa27_play;
m_funcs.flush = &xa27_flush;
m_funcs.stop = &xa27_stop;
m_funcs.open = &xa27_open;
m_funcs.is_playing = &xa27_is_playing;
m_funcs.add = &xa27_add;
m_funcs.enqueued_samples = &xa27_enqueued_samples;
m_funcs.set_freq_ratio = &xa27_set_freq_ratio;
LOG_SUCCESS(GENERAL, "XAudio 2.7 initialized");
return;
}
fmt::throw_exception("No supported XAudio2 library found");
} }
XAudio2Backend::~XAudio2Backend() XAudio2Backend::~XAudio2Backend()
{ {
m_funcs.destroy();
} }
void XAudio2Backend::Play() void XAudio2Backend::Play()
{ {
m_funcs.play(); lib->play();
} }
void XAudio2Backend::Close() void XAudio2Backend::Close()
{ {
m_funcs.stop(); lib->stop();
m_funcs.flush(); lib->flush();
} }
void XAudio2Backend::Pause() void XAudio2Backend::Pause()
{ {
m_funcs.stop(); lib->stop();
} }
void XAudio2Backend::Open(u32 /* num_buffers */) void XAudio2Backend::Open(u32 /* num_buffers */)
{ {
m_funcs.open(); if (lib.get() == nullptr)
{
void* hmodule;
if (hmodule = LoadLibraryExW(L"XAudio2_9.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))
{
// XAudio 2.9 uses the same code as XAudio 2.8
lib.reset(xa28_init(hmodule));
LOG_SUCCESS(GENERAL, "XAudio 2.9 initialized");
}
else if (hmodule = LoadLibraryExW(L"XAudio2_8.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))
{
// XAudio 2.9 uses the same code as XAudio 2.8
lib.reset(xa28_init(hmodule));
LOG_SUCCESS(GENERAL, "XAudio 2.8 initialized");
}
else if (hmodule = LoadLibraryExW(L"XAudio2_7.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))
{
// XAudio 2.9 uses the same code as XAudio 2.8
lib.reset(xa27_init(hmodule));
LOG_SUCCESS(GENERAL, "XAudio 2.7 initialized");
}
else
{
fmt::throw_exception("No supported XAudio2 library found");
}
}
lib->open();
} }
bool XAudio2Backend::IsPlaying() bool XAudio2Backend::IsPlaying()
{ {
return m_funcs.is_playing(); return lib->is_playing();
} }
bool XAudio2Backend::AddData(const void* src, u32 size) bool XAudio2Backend::AddData(const void* src, u32 num_samples)
{ {
return m_funcs.add(src, size); return lib->add(src, num_samples);
} }
void XAudio2Backend::Flush() void XAudio2Backend::Flush()
{ {
m_funcs.flush(); lib->flush();
} }
u64 XAudio2Backend::GetNumEnqueuedSamples() u64 XAudio2Backend::GetNumEnqueuedSamples()
{ {
return m_funcs.enqueued_samples(); return lib->enqueued_samples();
} }
f32 XAudio2Backend::SetFrequencyRatio(f32 new_ratio) f32 XAudio2Backend::SetFrequencyRatio(f32 new_ratio)
{ {
return m_funcs.set_freq_ratio(new_ratio); return lib->set_freq_ratio(new_ratio);
} }
#endif #endif

View File

@ -4,44 +4,28 @@
#include "Emu/Audio/AudioBackend.h" #include "Emu/Audio/AudioBackend.h"
class XAudio2Backend : public AudioBackend class XAudio2Backend : public AudioBackend
{ {
struct vtable public:
class XAudio2Library
{ {
void(*destroy)(); public:
void(*play)(); virtual void play() = 0;
void(*flush)(); virtual void flush() = 0;
void(*stop)(); virtual void stop() = 0;
void(*open)(); virtual void open() = 0;
bool(*is_playing)(); virtual bool is_playing() = 0;
bool(*add)(const void*, u32); virtual bool add(const void*, u32) = 0;
u64(*enqueued_samples)(); virtual u64 enqueued_samples() = 0;
f32(*set_freq_ratio)(f32); virtual f32 set_freq_ratio(f32) = 0;
}; };
vtable m_funcs; private:
static XAudio2Library* xa27_init(void*);
static XAudio2Library* xa28_init(void*);
static void xa27_init(void*); std::unique_ptr<XAudio2Library> lib = nullptr;
static void xa27_destroy();
static void xa27_play();
static void xa27_flush();
static void xa27_stop();
static void xa27_open();
static bool xa27_is_playing();
static bool xa27_add(const void*, u32);
static u64 xa27_enqueued_samples();
static f32 xa27_set_freq_ratio(f32);
static void xa28_init(void*);
static void xa28_destroy();
static void xa28_play();
static void xa28_flush();
static void xa28_stop();
static void xa28_open();
static bool xa28_is_playing();
static bool xa28_add(const void*, u32);
static u64 xa28_enqueued_samples();
static f32 xa28_set_freq_ratio(f32);
public: public:
XAudio2Backend(); XAudio2Backend();
@ -49,7 +33,7 @@ public:
virtual const char* GetName() const override { return "XAudio2"; }; virtual const char* GetName() const override { return "XAudio2"; };
static const u32 capabilities = NON_BLOCKING | IS_PLAYING | GET_NUM_ENQUEUED_SAMPLES | SET_FREQUENCY_RATIO; static const u32 capabilities = PLAY_PAUSE_FLUSH | IS_PLAYING | GET_NUM_ENQUEUED_SAMPLES | SET_FREQUENCY_RATIO;
virtual u32 GetCapabilities() const override { return capabilities; }; virtual u32 GetCapabilities() const override { return capabilities; };
virtual void Open(u32 /* num_buffers */) override; virtual void Open(u32 /* num_buffers */) override;
@ -59,7 +43,7 @@ public:
virtual void Pause() override; virtual void Pause() override;
virtual bool IsPlaying() override; virtual bool IsPlaying() override;
virtual bool AddData(const void* src, u32 size) override; virtual bool AddData(const void* src, u32 num_samples) override;
virtual void Flush() override; virtual void Flush() override;
virtual u64 GetNumEnqueuedSamples() override; virtual u64 GetNumEnqueuedSamples() override;

View File

@ -6,6 +6,7 @@
#include "Emu/Cell/lv2/sys_event.h" #include "Emu/Cell/lv2/sys_event.h"
#include "cellAudio.h" #include "cellAudio.h"
#include <atomic> #include <atomic>
#include <cmath>
LOG_CHANNEL(cellAudio); LOG_CHANNEL(cellAudio);
@ -37,10 +38,25 @@ void fmt_class_string<CellAudioError>::format(std::string& out, u64 arg)
}); });
} }
cell_audio_config::cell_audio_config()
{
// Warn if audio backend does not support all requested features
if (raw_buffering_enabled && !buffering_enabled)
{
cellAudio.error("Audio backend %s does not support buffering, this option will be ignored.", backend->GetName());
}
if (raw_time_stretching_enabled && !time_stretching_enabled)
{
cellAudio.error("Audio backend %s does not support time stretching, this option will be ignored.", backend->GetName());
}
}
audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg) audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg)
: cfg(_cfg) : cfg(_cfg)
, backend(Emu.GetCallbacks().get_audio()) , backend(_cfg.backend)
, buf_sz(AUDIO_BUFFER_SAMPLES * cfg.audio_channels) , buf_sz(AUDIO_BUFFER_SAMPLES * _cfg.audio_channels)
, emu_paused(Emu.IsPaused()) , emu_paused(Emu.IsPaused())
{ {
// Initialize buffers // Initialize buffers
@ -64,13 +80,13 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg)
{ {
std::string str; std::string str;
backend->dump_capabilities(str); backend->dump_capabilities(str);
cellAudio.error("cellAudio initializing. Backend: %s, Capabilities: %s", backend->GetName(), str.c_str()); cellAudio.notice("cellAudio initializing. Backend: %s, Capabilities: %s", backend->GetName(), str.c_str());
} }
backend->Open(cfg.num_allocated_buffers); backend->Open(cfg.num_allocated_buffers);
backend_open = true; backend_open = true;
ASSERT(!backend_is_playing()); ASSERT(!get_backend_playing());
} }
audio_ringbuffer::~audio_ringbuffer() audio_ringbuffer::~audio_ringbuffer()
@ -80,7 +96,7 @@ audio_ringbuffer::~audio_ringbuffer()
return; return;
} }
if (backend_is_playing()) if (get_backend_playing() && has_capability(AudioBackend::PLAY_PAUSE_FLUSH))
{ {
backend->Pause(); backend->Pause();
} }
@ -98,7 +114,7 @@ f32 audio_ringbuffer::set_frequency_ratio(f32 new_ratio)
else else
{ {
frequency_ratio = backend->SetFrequencyRatio(new_ratio); frequency_ratio = backend->SetFrequencyRatio(new_ratio);
//cellAudio.error("set_frequency_ratio(%1.2f) -> %1.2f", new_ratio, frequency_ratio); //cellAudio.trace("set_frequency_ratio(%1.2f) -> %1.2f", new_ratio, frequency_ratio);
} }
return frequency_ratio; return frequency_ratio;
} }
@ -119,11 +135,11 @@ void audio_ringbuffer::enqueue(const float* in_buffer)
// Dump audio if enabled // Dump audio if enabled
if (m_dump) if (m_dump)
{ {
m_dump->WriteData(buf, buf_sz); m_dump->WriteData(buf, cfg.audio_buffer_size);
} }
// Enqueue audio // Enqueue audio
bool success = backend->AddData(buf, buf_sz); bool success = backend->AddData(buf, AUDIO_BUFFER_SAMPLES * cfg.audio_channels);
if (!success) if (!success)
{ {
cellAudio.error("Could not enqueue buffer onto audio backend. Attempting to recover..."); cellAudio.error("Could not enqueue buffer onto audio backend. Attempting to recover...");
@ -131,14 +147,19 @@ void audio_ringbuffer::enqueue(const float* in_buffer)
return; return;
} }
enqueued_samples += AUDIO_BUFFER_SAMPLES; if (has_capability(AudioBackend::PLAY_PAUSE_FLUSH))
{
enqueued_samples += AUDIO_BUFFER_SAMPLES;
// Start playing audio // Start playing audio
play(); play();
}
} }
void audio_ringbuffer::enqueue_silence(u32 buf_count) void audio_ringbuffer::enqueue_silence(u32 buf_count)
{ {
AUDIT(has_capability(AudioBackend::PLAY_PAUSE_FLUSH));
for (u32 i = 0; i < buf_count; i++) for (u32 i = 0; i < buf_count; i++)
{ {
enqueue(silence_buffer); enqueue(silence_buffer);
@ -147,8 +168,16 @@ void audio_ringbuffer::enqueue_silence(u32 buf_count)
void audio_ringbuffer::play() void audio_ringbuffer::play()
{ {
if (has_capability(AudioBackend::IS_PLAYING) && playing) if (!has_capability(AudioBackend::PLAY_PAUSE_FLUSH))
{
playing = true;
return; return;
}
if (has_capability(AudioBackend::IS_PLAYING) && playing)
{
return;
}
if (frequency_ratio != 1.0f) if (frequency_ratio != 1.0f)
{ {
@ -165,7 +194,13 @@ void audio_ringbuffer::play()
void audio_ringbuffer::flush() void audio_ringbuffer::flush()
{ {
cellAudio.trace("Flushing an estimated %llu enqueued samples", enqueued_samples); if (!has_capability(AudioBackend::PLAY_PAUSE_FLUSH))
{
playing = false;
return;
}
//cellAudio.trace("Flushing an estimated %llu enqueued samples", enqueued_samples);
backend->Pause(); backend->Pause();
playing = false; playing = false;
@ -186,7 +221,7 @@ u64 audio_ringbuffer::update()
if (Emu.IsPaused()) if (Emu.IsPaused())
{ {
// Emulator paused // Emulator paused
if (playing) if (has_capability(AudioBackend::PLAY_PAUSE_FLUSH) && playing)
{ {
backend->Pause(); backend->Pause();
} }
@ -200,7 +235,7 @@ u64 audio_ringbuffer::update()
} }
const u64 timestamp = get_timestamp(); const u64 timestamp = get_timestamp();
const bool new_playing = !emu_paused && backend_is_playing(); const bool new_playing = !emu_paused && get_backend_playing();
// Calculate how many audio samples have played since last time // Calculate how many audio samples have played since last time
if (cfg.buffering_enabled && (playing || new_playing)) if (cfg.buffering_enabled && (playing || new_playing))
@ -234,7 +269,7 @@ u64 audio_ringbuffer::update()
if (enqueued_samples == 0) if (enqueued_samples == 0)
{ {
cellAudio.warning("Audio buffer about to underrun!"); cellAudio.trace("Audio buffer about to underrun!");
} }
} }
} }
@ -245,7 +280,7 @@ u64 audio_ringbuffer::update()
{ {
if (!new_playing) if (!new_playing)
{ {
cellAudio.error("Audio backend stopped unexpectedly, likely due to a buffer underrun"); cellAudio.warning("Audio backend stopped unexpectedly, likely due to a buffer underrun");
flush(); flush();
playing = false; playing = false;
@ -261,11 +296,6 @@ u64 audio_ringbuffer::update()
return timestamp; return timestamp;
} }
constexpr bool audio_port::is_tag(float val)
{
return val == 0.0f && std::signbit(val);
}
void audio_port::tag(s32 offset) void audio_port::tag(s32 offset)
{ {
auto port_pos = position(offset); auto port_pos = position(offset);
@ -281,26 +311,12 @@ void audio_port::tag(s32 offset)
for (u32 tag_pos = tag_first_pos, tag_nr = 0; tag_nr < PORT_BUFFER_TAG_COUNT; tag_pos += tag_delta, tag_nr++) for (u32 tag_pos = tag_first_pos, tag_nr = 0; tag_nr < PORT_BUFFER_TAG_COUNT; tag_pos += tag_delta, tag_nr++)
{ {
port_buf[tag_pos] = tag; port_buf[tag_pos] = tag;
tag_backup[port_pos][tag_nr] = 0.0f; last_tag_value[tag_nr] = -0.0f;
} }
prev_touched_tag_nr = UINT32_MAX; prev_touched_tag_nr = UINT32_MAX;
} }
void audio_port::apply_tag_backups(s32 offset)
{
auto port_pos = position(offset);
auto port_buf = get_vm_ptr(offset);
const u32 tag_first_pos = num_channels == 2 ? PORT_BUFFER_TAG_FIRST_2CH : PORT_BUFFER_TAG_FIRST_8CH;
const u32 tag_delta = num_channels == 2 ? PORT_BUFFER_TAG_DELTA_2CH : PORT_BUFFER_TAG_DELTA_8CH;
for (u32 tag_pos = tag_first_pos, tag_nr = 0; tag_nr < PORT_BUFFER_TAG_COUNT; tag_pos += tag_delta, tag_nr++)
{
port_buf[tag_pos] += tag_backup[port_pos][tag_nr];
}
}
std::tuple<u32, u32, u32, u32> cell_audio_thread::count_port_buffer_tags() std::tuple<u32, u32, u32, u32> cell_audio_thread::count_port_buffer_tags()
{ {
AUDIT(cfg.buffering_enabled); AUDIT(cfg.buffering_enabled);
@ -322,18 +338,17 @@ std::tuple<u32, u32, u32, u32> cell_audio_thread::count_port_buffer_tags()
const u32 tag_first_pos = port.num_channels == 2 ? PORT_BUFFER_TAG_FIRST_2CH : PORT_BUFFER_TAG_FIRST_8CH; const u32 tag_first_pos = port.num_channels == 2 ? PORT_BUFFER_TAG_FIRST_2CH : PORT_BUFFER_TAG_FIRST_8CH;
const u32 tag_delta = port.num_channels == 2 ? PORT_BUFFER_TAG_DELTA_2CH : PORT_BUFFER_TAG_DELTA_8CH; const u32 tag_delta = port.num_channels == 2 ? PORT_BUFFER_TAG_DELTA_2CH : PORT_BUFFER_TAG_DELTA_8CH;
const f32 tag = -0.0f;
u32 last_touched_tag_nr = port.prev_touched_tag_nr; u32 last_touched_tag_nr = port.prev_touched_tag_nr;
bool retouched = false; bool retouched = false;
for (u32 tag_pos = tag_first_pos, tag_nr = 0; tag_nr < PORT_BUFFER_TAG_COUNT; tag_pos += tag_delta, tag_nr++) for (u32 tag_pos = tag_first_pos, tag_nr = 0; tag_nr < PORT_BUFFER_TAG_COUNT; tag_pos += tag_delta, tag_nr++)
{ {
// grab current value and re-tag atomically // grab current value and re-tag atomically
f32 val = atomic_storage<to_be_t<float>, 4>::exchange(port_buf[tag_pos], tag); const f32 val = port_buf[tag_pos];
f32& last_val = port.last_tag_value[tag_nr];
if (!audio_port::is_tag(val)) if (val != last_val || (last_val == -0.0f && std::signbit(last_val) && !std::signbit(val)))
{ {
port.tag_backup[port_pos][tag_nr] += val; last_val = val;
retouched |= tag_nr < port.prev_touched_tag_nr && port.prev_touched_tag_nr != UINT32_MAX; retouched |= tag_nr < port.prev_touched_tag_nr && port.prev_touched_tag_nr != UINT32_MAX;
last_touched_tag_nr = tag_nr; last_touched_tag_nr = tag_nr;
@ -374,7 +389,7 @@ std::tuple<u32, u32, u32, u32> cell_audio_thread::count_port_buffer_tags()
void cell_audio_thread::reset_ports(s32 offset) void cell_audio_thread::reset_ports(s32 offset)
{ {
// Memset previous buffer to 0 // Memset previous buffer to 0 and tag
for (auto& port : ports) for (auto& port : ports)
{ {
if (port.state != audio_port_state::started) continue; if (port.state != audio_port_state::started) continue;
@ -383,7 +398,6 @@ void cell_audio_thread::reset_ports(s32 offset)
if (cfg.buffering_enabled) if (cfg.buffering_enabled)
{ {
//port.reset_tag_backups(offset);
port.tag(offset); port.tag(offset);
} }
} }
@ -414,7 +428,7 @@ void cell_audio_thread::advance(u64 timestamp, bool reset)
if (cfg.buffering_enabled) if (cfg.buffering_enabled)
{ {
// Calculate rolling average of enqueued playtime // Calculate rolling average of enqueued playtime
const u64 enqueued_playtime = ringbuffer->get_enqueued_playtime(); const u64 enqueued_playtime = ringbuffer->get_enqueued_playtime(/* raw */ true);
m_average_playtime = cfg.period_average_alpha * enqueued_playtime + (1.0f - cfg.period_average_alpha) * m_average_playtime; m_average_playtime = cfg.period_average_alpha * enqueued_playtime + (1.0f - cfg.period_average_alpha) * m_average_playtime;
//cellAudio.error("m_average_playtime=%4.2f, enqueued_playtime=%u", m_average_playtime, enqueued_playtime); //cellAudio.error("m_average_playtime=%4.2f, enqueued_playtime=%u", m_average_playtime, enqueued_playtime);
} }
@ -450,23 +464,6 @@ void cell_audio_thread::operator()()
// Allocate ringbuffer // Allocate ringbuffer
ringbuffer.reset(new audio_ringbuffer(cfg)); ringbuffer.reset(new audio_ringbuffer(cfg));
// Check backend capabilities
if (cfg.buffering_enabled)
{
if (!has_capability(AudioBackend::NON_BLOCKING) || !has_capability(AudioBackend::IS_PLAYING))
{
// We need a non-blocking backend to be able to do buffering correctly
// We also need to be able to query the current playing state
fmt::throw_exception("Audio backend %s does not support buffering.", ringbuffer->get_backend_name());
}
if (cfg.time_stretching_enabled && !has_capability(AudioBackend::SET_FREQUENCY_RATIO))
{
// We need to be able to set a dynamic frequency ratio to be able to do time stretching
fmt::throw_exception("Audio backend %s does not support time stretching", ringbuffer->get_backend_name());
}
}
// Initialize loop variables // Initialize loop variables
m_counter = 0; m_counter = 0;
m_start_time = ringbuffer->get_timestamp(); m_start_time = ringbuffer->get_timestamp();
@ -490,7 +487,6 @@ void cell_audio_thread::operator()()
// TODO: send beforemix event (in ~2,6 ms before mixing) // TODO: send beforemix event (in ~2,6 ms before mixing)
const u64 time_since_last_period = timestamp - m_last_period_end; const u64 time_since_last_period = timestamp - m_last_period_end;
const bool playing = !ringbuffer->is_playing();
if (!cfg.buffering_enabled) if (!cfg.buffering_enabled)
{ {
@ -533,7 +529,7 @@ void cell_audio_thread::operator()()
f32 desired_duration_adjusted = cfg.desired_buffer_duration + (cfg.audio_block_period / 2.0f); f32 desired_duration_adjusted = cfg.desired_buffer_duration + (cfg.audio_block_period / 2.0f);
if (average_playtime_ratio < 1.0f) if (average_playtime_ratio < 1.0f)
{ {
desired_duration_adjusted /= average_playtime_ratio; desired_duration_adjusted /= std::max(average_playtime_ratio, 0.25f);
} }
if (cfg.time_stretching_enabled) if (cfg.time_stretching_enabled)
@ -548,13 +544,12 @@ void cell_audio_thread::operator()()
// update frequency ratio if necessary // update frequency ratio if necessary
f32 new_ratio = frequency_ratio; f32 new_ratio = frequency_ratio;
if (desired_duration_rate < cfg.time_stretching_threshold) if (
(desired_duration_rate < cfg.time_stretching_threshold && desired_duration_rate < frequency_ratio - cfg.time_stretching_step) || // Reduce frequency ratio below threshold in 0.1f steps
(desired_duration_rate > frequency_ratio + cfg.time_stretching_step) // Increase frequency ratio in 0.1f steps
)
{ {
new_ratio = ringbuffer->set_frequency_ratio(desired_duration_rate * cfg.time_stretching_frequency_scale_factor); new_ratio = ringbuffer->set_frequency_ratio(std::min(desired_duration_rate, 1.0f));
}
else if (frequency_ratio != 1.0f)
{
new_ratio = ringbuffer->set_frequency_ratio(1.0f);
} }
if (new_ratio != frequency_ratio) if (new_ratio != frequency_ratio)
@ -714,11 +709,6 @@ void cell_audio_thread::mix(float *out_buffer, s32 offset)
{ {
if (port.state != audio_port_state::started) continue; if (port.state != audio_port_state::started) continue;
if (cfg.buffering_enabled)
{
port.apply_tag_backups(offset);
}
auto buf = port.get_vm_ptr(offset); auto buf = port.get_vm_ptr(offset);
static const float k = 1.0f; static const float k = 1.0f;
float& m = port.level; float& m = port.level;
@ -876,7 +866,6 @@ void cell_audio_thread::mix(float *out_buffer, s32 offset)
for (size_t i = 0; i < out_buffer_sz; i += 8) for (size_t i = 0; i < out_buffer_sz; i += 8)
{ {
// TODO ruipin: Revisit this
const auto scale = _mm_set1_ps(0x8000); const auto scale = _mm_set1_ps(0x8000);
_mm_store_ps(out_buffer + i / 2, _mm_castsi128_ps(_mm_packs_epi32( _mm_store_ps(out_buffer + i / 2, _mm_castsi128_ps(_mm_packs_epi32(
_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer + i), scale)), _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer + i), scale)),

View File

@ -90,7 +90,7 @@ enum : u32
AUDIO_BLOCK_SIZE_2CH = 2 * AUDIO_BUFFER_SAMPLES, AUDIO_BLOCK_SIZE_2CH = 2 * AUDIO_BUFFER_SAMPLES,
AUDIO_BLOCK_SIZE_8CH = 8 * AUDIO_BUFFER_SAMPLES, AUDIO_BLOCK_SIZE_8CH = 8 * AUDIO_BUFFER_SAMPLES,
PORT_BUFFER_TAG_COUNT = 8, PORT_BUFFER_TAG_COUNT = 6,
PORT_BUFFER_TAG_LAST_2CH = AUDIO_BLOCK_SIZE_2CH - 1, PORT_BUFFER_TAG_LAST_2CH = AUDIO_BLOCK_SIZE_2CH - 1,
PORT_BUFFER_TAG_DELTA_2CH = PORT_BUFFER_TAG_LAST_2CH / (PORT_BUFFER_TAG_COUNT - 1), PORT_BUFFER_TAG_DELTA_2CH = PORT_BUFFER_TAG_LAST_2CH / (PORT_BUFFER_TAG_COUNT - 1),
@ -163,37 +163,59 @@ struct audio_port
// Tags // Tags
u32 prev_touched_tag_nr; u32 prev_touched_tag_nr;
f32 tag_backup[AUDIO_MAX_BLOCK_COUNT][PORT_BUFFER_TAG_COUNT] = { 0 }; f32 last_tag_value[PORT_BUFFER_TAG_COUNT] = { 0 };
constexpr static bool is_tag(float val);
void tag(s32 offset = 0); void tag(s32 offset = 0);
void apply_tag_backups(s32 offset = 0);
}; };
struct cell_audio_config 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 std::shared_ptr<AudioBackend> backend = Emu.GetCallbacks().get_audio();
const u32 audio_channels = AudioBackend::get_channels(); const u32 audio_channels = AudioBackend::get_channels();
const u32 audio_sampling_rate = AudioBackend::get_sampling_rate(); const u32 audio_sampling_rate = AudioBackend::get_sampling_rate();
const u32 audio_block_period = AUDIO_BUFFER_SAMPLES * 1000000 / audio_sampling_rate; const u32 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_length = AUDIO_BUFFER_SAMPLES * audio_channels;
const u32 audio_buffer_size = audio_buffer_length * sizeof(f32); const u32 audio_buffer_size = audio_buffer_length * AudioBackend::get_sample_size();
const bool buffering_enabled = g_cfg.audio.enable_buffering && (desired_buffer_duration >= audio_block_period);
/*
* Buffering
*/
const u64 desired_buffer_duration = g_cfg.audio.enable_buffering ? g_cfg.audio.desired_buffer_duration : 0;
private:
const bool raw_buffering_enabled = g_cfg.audio.enable_buffering && (desired_buffer_duration >= audio_block_period);
public:
// We need a non-blocking backend (implementing play/pause/flush) to be able to do buffering correctly
// We also need to be able to query the current playing state
const bool buffering_enabled = raw_buffering_enabled && backend->has_capability(AudioBackend::PLAY_PAUSE_FLUSH | AudioBackend::IS_PLAYING);
const u64 minimum_block_period = audio_block_period / 2; // the block period will not be dynamically lowered below this value (usecs) 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 = (6 * audio_block_period) / 5; // the block period will not be dynamically increased above this value (usecs) const u64 maximum_block_period = (6 * audio_block_period) / 5; // 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 desired_full_buffers = buffering_enabled ? static_cast<u32>(desired_buffer_duration / audio_block_period) + 1 : 2;
const u32 num_allocated_buffers = desired_full_buffers + EXTRA_AUDIO_BUFFERS; // number of ringbuffer buffers const u32 num_allocated_buffers = desired_full_buffers + EXTRA_AUDIO_BUFFERS; // number of ringbuffer buffers
const f32 period_average_alpha = 0.02f; // alpha factor for the m_average_period rolling average const f32 period_average_alpha = 0.02f; // alpha factor for the m_average_period rolling average
const bool time_stretching_enabled = buffering_enabled && g_cfg.audio.enable_time_stretching && (g_cfg.audio.time_stretching_threshold > 0); const s64 period_comparison_margin = 250; // when comparing the current period time with the desired period, if it is below this number of usecs we do not wait any longer
/*
* Time Stretching
*/
private:
const bool raw_time_stretching_enabled = buffering_enabled && g_cfg.audio.enable_time_stretching && (g_cfg.audio.time_stretching_threshold > 0);
public:
// We need to be able to set a dynamic frequency ratio to be able to do time stretching
const bool time_stretching_enabled = raw_time_stretching_enabled && backend->has_capability(AudioBackend::SET_FREQUENCY_RATIO);
const f32 time_stretching_threshold = g_cfg.audio.time_stretching_threshold / 100.0f; // we only apply time stretching below this buffer fill rate (adjusted for average period) const f32 time_stretching_threshold = g_cfg.audio.time_stretching_threshold / 100.0f; // we only apply time stretching below this buffer fill rate (adjusted for average period)
const f32 time_stretching_frequency_scale_factor = 1.0f / time_stretching_threshold; const f32 time_stretching_step = 0.1f;
/*
* Constructor
*/
cell_audio_config();
}; };
class audio_ringbuffer class audio_ringbuffer
@ -224,9 +246,9 @@ private:
u32 cur_pos = 0; u32 cur_pos = 0;
bool backend_is_playing() const bool get_backend_playing() const
{ {
return has_capability(AudioBackend::IS_PLAYING) ? backend->IsPlaying() : playing; return has_capability(AudioBackend::PLAY_PAUSE_FLUSH | AudioBackend::IS_PLAYING) ? backend->IsPlaying() : playing;
} }
public: public:
@ -280,7 +302,7 @@ public:
return frequency_ratio; return frequency_ratio;
} }
u32 has_capability(AudioBackend::Capabilities cap) const u32 has_capability(u32 cap) const
{ {
return backend->has_capability(cap); return backend->has_capability(cap);
} }
@ -307,7 +329,7 @@ class cell_audio_thread
constexpr static u64 get_thread_wait_delay(u64 time_left) constexpr static u64 get_thread_wait_delay(u64 time_left)
{ {
return (time_left > 1000) ? time_left - 750 : 100; return (time_left > 350) ? time_left - 250 : 100;
} }
public: public:
@ -349,7 +371,7 @@ public:
return nullptr; return nullptr;
} }
bool has_capability(AudioBackend::Capabilities cap) const bool has_capability(u32 cap) const
{ {
return ringbuffer->has_capability(cap); return ringbuffer->has_capability(cap);
} }

View File

@ -526,12 +526,12 @@ struct cfg_root : cfg::node
cfg::_bool dump_to_file{this, "Dump to file"}; cfg::_bool dump_to_file{this, "Dump to file"};
cfg::_bool convert_to_u16{this, "Convert to 16 bit"}; cfg::_bool convert_to_u16{this, "Convert to 16 bit"};
cfg::_bool downmix_to_2ch{this, "Downmix to Stereo", true}; cfg::_bool downmix_to_2ch{this, "Downmix to Stereo", true};
cfg::_int<1, 128> startt{this, "Start Threshold", 1}; cfg::_int<1, 128> startt{this, "Start Threshold", 1}; // TODO: used only by ALSA, should probably be removed once ALSA is upgraded
cfg::_int<0, 200> volume{this, "Master Volume", 100}; cfg::_int<0, 200> volume{this, "Master Volume", 100};
cfg::_bool enable_buffering{this, "Enable Buffering", true}; cfg::_bool enable_buffering{this, "Enable Buffering", true};
cfg::_int <0, 250'000> desired_buffer_duration{this, "Desired Audio Buffer Duration", 100'000}; cfg::_int <0, 250'000> desired_buffer_duration{this, "Desired Audio Buffer Duration", 100'000};
cfg::_int<1, 1000> sampling_period_multiplier{this, "Sampling Period Multiplier", 100}; cfg::_int<1, 1000> sampling_period_multiplier{this, "Sampling Period Multiplier", 100};
cfg::_bool enable_time_stretching{this, "Enable Time Stretching", true}; cfg::_bool enable_time_stretching{this, "Enable Time Stretching", false};
cfg::_int<0, 100> time_stretching_threshold{this, "Time Stretching Threshold", 75}; cfg::_int<0, 100> time_stretching_threshold{this, "Time Stretching Threshold", 75};
} audio{this}; } audio{this};

View File

@ -48,10 +48,10 @@
#include "Emu/Audio/XAudio2/XAudio2Backend.h" #include "Emu/Audio/XAudio2/XAudio2Backend.h"
#endif #endif
#ifdef HAVE_ALSA #ifdef HAVE_ALSA
#include "Emu/Audio/ALSA/ALSAThread.h" #include "Emu/Audio/ALSA/ALSABackend.h"
#endif #endif
#ifdef HAVE_PULSE #ifdef HAVE_PULSE
#include "Emu/Audio/Pulse/PulseThread.h" #include "Emu/Audio/Pulse/PulseBackend.h"
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
@ -263,10 +263,10 @@ void rpcs3_app::InitializeCallbacks()
case audio_renderer::xaudio: return std::make_shared<XAudio2Backend>(); case audio_renderer::xaudio: return std::make_shared<XAudio2Backend>();
#endif #endif
#ifdef HAVE_ALSA #ifdef HAVE_ALSA
case audio_renderer::alsa: return std::make_shared<ALSAThread>(); case audio_renderer::alsa: return std::make_shared<ALSABackend>();
#endif #endif
#ifdef HAVE_PULSE #ifdef HAVE_PULSE
case audio_renderer::pulse: return std::make_shared<PulseThread>(); case audio_renderer::pulse: return std::make_shared<PulseBackend>();
#endif #endif
case audio_renderer::openal: return std::make_shared<OpenALBackend>(); case audio_renderer::openal: return std::make_shared<OpenALBackend>();