mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-06-05 22:55:04 -06:00
Various fixes
In addition, linux builds (and ALSA/PA) now work again
This commit is contained in:
parent
4f39457858
commit
67f9397746
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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
|
||||||
30
rpcs3/Emu/Audio/ALSA/ALSABackend.h
Normal file
30
rpcs3/Emu/Audio/ALSA/ALSABackend.h
Normal 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
|
||||||
@ -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
|
|
||||||
@ -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++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {};
|
||||||
|
|||||||
@ -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
|
||||||
27
rpcs3/Emu/Audio/Pulse/PulseBackend.h
Normal file
27
rpcs3/Emu/Audio/Pulse/PulseBackend.h
Normal 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
|
||||||
@ -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
|
|
||||||
@ -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 = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
|
if (FAILED(hr))
|
||||||
|
{
|
||||||
|
LOG_ERROR(GENERAL, "XAudio2Backend : CoInitializeEx() failed(0x%08x)", (u32)hr);
|
||||||
|
Emu.Pause();
|
||||||
|
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 = XAudio2Create(&s_tls_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
|
~XAudio27Library()
|
||||||
if (FAILED(hr))
|
|
||||||
{
|
{
|
||||||
LOG_ERROR(GENERAL, "XAudio2Backend : XAudio2Create() failed(0x%08x)", (u32)hr);
|
if (s_tls_source_voice != nullptr)
|
||||||
Emu.Pause();
|
{
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = s_tls_xaudio2_instance->CreateMasteringVoice(&s_tls_master_voice, g_cfg.audio.downmix_to_2ch ? 2 : 8, 48000);
|
virtual void play() override
|
||||||
if (FAILED(hr))
|
|
||||||
{
|
{
|
||||||
LOG_ERROR(GENERAL, "XAudio2Backend : CreateMasteringVoice() failed(0x%08x)", (u32)hr);
|
HRESULT hr = s_tls_source_voice->Start();
|
||||||
s_tls_xaudio2_instance->Release();
|
if (FAILED(hr))
|
||||||
Emu.Pause();
|
{
|
||||||
|
LOG_ERROR(GENERAL, "XAudio2Backend : Start() failed(0x%08x)", (u32)hr);
|
||||||
|
Emu.Pause();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void XAudio2Backend::xa27_destroy()
|
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
|
||||||
|
|||||||
@ -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;
|
HRESULT hr = S_OK;
|
||||||
|
|
||||||
|
hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
|
if (FAILED(hr))
|
||||||
|
{
|
||||||
|
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 = create(&s_tls_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
|
~XAudio28Library()
|
||||||
if (FAILED(hr))
|
|
||||||
{
|
{
|
||||||
LOG_ERROR(GENERAL, "XAudio2Backend : XAudio2Create() failed(0x%08x)", (u32)hr);
|
if (s_tls_source_voice != nullptr)
|
||||||
Emu.Pause();
|
{
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = s_tls_xaudio2_instance->CreateMasteringVoice(&s_tls_master_voice, g_cfg.audio.downmix_to_2ch ? 2 : 8, 48000);
|
virtual void play() override
|
||||||
if (FAILED(hr))
|
|
||||||
{
|
{
|
||||||
LOG_ERROR(GENERAL, "XAudio2Backend : CreateMasteringVoice() failed(0x%08x)", (u32)hr);
|
AUDIT(s_tls_source_voice != nullptr);
|
||||||
s_tls_xaudio2_instance->Release();
|
|
||||||
Emu.Pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void XAudio2Backend::xa28_destroy()
|
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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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)),
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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};
|
||||||
|
|||||||
@ -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>();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user