mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-01-31 12:31:45 +01: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));
|
||||
|
||||
@ -133,7 +133,7 @@ bool OpenALBackend::AddData(const void* src, u32 size)
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
||||
// Enqueue buffer
|
||||
@ -186,7 +186,7 @@ u64 OpenALBackend::GetNumEnqueuedSamples()
|
||||
ALint num_queued;
|
||||
alGetSourcei(m_source, AL_BUFFERS_QUEUED, &num_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
|
||||
ALint sample_pos;
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
|
||||
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 void Open(u32 num_buffers) override;
|
||||
|
@ -1,15 +1,10 @@
|
||||
#include "stdafx.h"
|
||||
#include "Emu/System.h"
|
||||
|
||||
#include "ALSAThread.h"
|
||||
#include "ALSABackend.h"
|
||||
|
||||
#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)
|
||||
{
|
||||
@ -27,7 +22,19 @@ static bool check(int err, const char* reason)
|
||||
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"))
|
||||
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"))
|
||||
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"))
|
||||
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;
|
||||
|
||||
//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"))
|
||||
// return;
|
||||
|
||||
snd_pcm_uframes_t bufsize_frames = g_cfg.audio.frames * 256;
|
||||
snd_pcm_uframes_t period_frames = 256;
|
||||
snd_pcm_uframes_t bufsize_frames = num_buffers * AUDIO_BUFFER_SAMPLES;
|
||||
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"))
|
||||
return;
|
||||
@ -84,7 +91,7 @@ ALSAThread::ALSAThread()
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ALSAThread::~ALSAThread()
|
||||
void ALSABackend::Close()
|
||||
{
|
||||
if (s_tls_sw_params)
|
||||
{
|
||||
snd_pcm_sw_params_free(s_tls_sw_params);
|
||||
s_tls_sw_params = nullptr;
|
||||
}
|
||||
|
||||
if (s_tls_hw_params)
|
||||
{
|
||||
snd_pcm_hw_params_free(s_tls_hw_params);
|
||||
s_tls_hw_params = nullptr;
|
||||
}
|
||||
|
||||
if (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()
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
int res = snd_pcm_writei(s_tls_handle, src, num_frames);
|
||||
|
||||
if (res == -EAGAIN)
|
||||
{
|
||||
LOG_WARNING(GENERAL, "ALSA: EAGAIN");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (res < 0)
|
||||
@ -154,16 +146,19 @@ void ALSAThread::AddData(const void* src, int size)
|
||||
if (res < 0)
|
||||
{
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#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:
|
||||
enum Capabilities : u32
|
||||
{
|
||||
NON_BLOCKING = 0x1,
|
||||
IS_PLAYING = 0x2,
|
||||
GET_NUM_ENQUEUED_SAMPLES = 0x4,
|
||||
SET_FREQUENCY_RATIO = 0x8,
|
||||
PLAY_PAUSE_FLUSH = 0x1, // AddData implements Play, Pause, Flush
|
||||
IS_PLAYING = 0x2, // Implements IsPlaying method
|
||||
GET_NUM_ENQUEUED_SAMPLES = 0x4, // Supports GetNumEnqueuedSamples method
|
||||
SET_FREQUENCY_RATIO = 0x8, // Implements SetFrequencyRatio method
|
||||
};
|
||||
|
||||
virtual ~AudioBackend() = default;
|
||||
|
||||
// Callbacks
|
||||
/*
|
||||
* Pure virtual methods
|
||||
*/
|
||||
virtual const char* GetName() const = 0;
|
||||
virtual u32 GetCapabilities() const = 0;
|
||||
|
||||
virtual void Open(u32 num_buffers) = 0;
|
||||
virtual void Close() = 0;
|
||||
|
||||
virtual void Play() = 0;
|
||||
virtual void Pause() = 0;
|
||||
virtual bool AddData(const void* src, u32 num_samples) = 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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
fmt::throw_exception("GetNumEnqueuedSamples() not implemented");
|
||||
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
|
||||
{
|
||||
fmt::throw_exception("SetFrequencyRatio() not implemented");
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
/*
|
||||
* Helper methods
|
||||
*/
|
||||
static u32 get_sampling_rate()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
@ -86,9 +121,9 @@ public:
|
||||
u32 count = 0;
|
||||
u32 capabilities = GetCapabilities();
|
||||
|
||||
if (capabilities & NON_BLOCKING)
|
||||
if (capabilities & PLAY_PAUSE_FLUSH)
|
||||
{
|
||||
fmt::append(out, "NON_BLOCKING");
|
||||
fmt::append(out, "PLAY_PAUSE_FLUSH");
|
||||
count++;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ public:
|
||||
|
||||
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 void Open(u32) override {};
|
||||
|
@ -1,25 +1,21 @@
|
||||
#include "Emu/System.h"
|
||||
#include "PulseThread.h"
|
||||
#include "PulseBackend.h"
|
||||
|
||||
#ifdef HAVE_PULSE
|
||||
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
|
||||
PulseThread::PulseThread()
|
||||
PulseBackend::PulseBackend()
|
||||
{
|
||||
}
|
||||
|
||||
PulseThread::~PulseThread()
|
||||
PulseBackend::~PulseBackend()
|
||||
{
|
||||
this->Close();
|
||||
}
|
||||
|
||||
void PulseThread::Play()
|
||||
{
|
||||
}
|
||||
|
||||
void PulseThread::Close()
|
||||
void PulseBackend::Close()
|
||||
{
|
||||
if(this->connection) {
|
||||
pa_simple_free(this->connection);
|
||||
@ -27,19 +23,15 @@ void PulseThread::Close()
|
||||
}
|
||||
}
|
||||
|
||||
void PulseThread::Stop()
|
||||
{
|
||||
}
|
||||
|
||||
void PulseThread::Open(const void* src, int size)
|
||||
void PulseBackend::Open(u32 /* num_buffers */)
|
||||
{
|
||||
pa_sample_spec ss;
|
||||
ss.format = g_cfg.audio.convert_to_u16 ? PA_SAMPLE_S16LE : PA_SAMPLE_FLOAT32LE;
|
||||
ss.rate = 48000;
|
||||
ss.format = (get_sample_size() == 2) ? PA_SAMPLE_S16LE : PA_SAMPLE_FLOAT32LE;
|
||||
ss.rate = get_sampling_rate();
|
||||
|
||||
pa_channel_map channel_map;
|
||||
|
||||
if (g_cfg.audio.downmix_to_2ch)
|
||||
if (get_channels() == 2)
|
||||
{
|
||||
channel_map.channels = 2;
|
||||
channel_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
|
||||
@ -64,18 +56,19 @@ void PulseThread::Open(const void* src, int size)
|
||||
if(!this->connection) {
|
||||
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) {
|
||||
int err;
|
||||
if(pa_simple_write(this->connection, src, size, &err) < 0) {
|
||||
fprintf(stderr, "PulseAudio: Failed to write audio stream: %s\n", pa_strerror(err));
|
||||
}
|
||||
AUDIT(this->connection);
|
||||
|
||||
int 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
|
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 "3rdparty/XAudio2_7/XAudio2.h"
|
||||
|
||||
static thread_local HMODULE s_tls_xaudio2_lib{};
|
||||
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)
|
||||
class XAudio27Library : public XAudio2Backend::XAudio2Library
|
||||
{
|
||||
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;
|
||||
|
||||
hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (FAILED(hr))
|
||||
public:
|
||||
XAudio27Library(void* lib2_7)
|
||||
: s_tls_xaudio2_lib(static_cast<HMODULE>(lib2_7))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Backend : CoInitializeEx() failed(0x%08x)", (u32)hr);
|
||||
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 = 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);
|
||||
if (FAILED(hr))
|
||||
~XAudio27Library()
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Backend : XAudio2Create() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
if (s_tls_source_voice != nullptr)
|
||||
{
|
||||
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);
|
||||
if (FAILED(hr))
|
||||
virtual void play() override
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Backend : CreateMasteringVoice() failed(0x%08x)", (u32)hr);
|
||||
s_tls_xaudio2_instance->Release();
|
||||
Emu.Pause();
|
||||
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_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)
|
||||
{
|
||||
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;
|
||||
return new XAudio27Library(lib2_7);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -7,201 +7,209 @@
|
||||
#include "XAudio2Backend.h"
|
||||
#include "3rdparty/minidx12/Include/xaudio2.h"
|
||||
|
||||
static thread_local HMODULE s_tls_xaudio2_lib{};
|
||||
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)
|
||||
class XAudio28Library : public XAudio2Backend::XAudio2Library
|
||||
{
|
||||
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");
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (FAILED(hr))
|
||||
public:
|
||||
XAudio28Library(void* lib2_8)
|
||||
: s_tls_xaudio2_lib(static_cast<HMODULE>(lib2_8))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Backend : CoInitializeEx() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
const auto create = (XAudio2Create)GetProcAddress(s_tls_xaudio2_lib, "XAudio2Create");
|
||||
|
||||
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);
|
||||
if (FAILED(hr))
|
||||
~XAudio28Library()
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Backend : XAudio2Create() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
if (s_tls_source_voice != nullptr)
|
||||
{
|
||||
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);
|
||||
if (FAILED(hr))
|
||||
virtual void play() override
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Backend : CreateMasteringVoice() failed(0x%08x)", (u32)hr);
|
||||
s_tls_xaudio2_instance->Release();
|
||||
Emu.Pause();
|
||||
}
|
||||
}
|
||||
AUDIT(s_tls_source_voice != nullptr);
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
return new XAudio28Library(lib2_8);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -8,113 +8,87 @@
|
||||
|
||||
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()
|
||||
{
|
||||
m_funcs.destroy();
|
||||
}
|
||||
|
||||
void XAudio2Backend::Play()
|
||||
{
|
||||
m_funcs.play();
|
||||
lib->play();
|
||||
}
|
||||
|
||||
void XAudio2Backend::Close()
|
||||
{
|
||||
m_funcs.stop();
|
||||
m_funcs.flush();
|
||||
lib->stop();
|
||||
lib->flush();
|
||||
}
|
||||
|
||||
void XAudio2Backend::Pause()
|
||||
{
|
||||
m_funcs.stop();
|
||||
lib->stop();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
m_funcs.flush();
|
||||
lib->flush();
|
||||
}
|
||||
|
||||
u64 XAudio2Backend::GetNumEnqueuedSamples()
|
||||
{
|
||||
return m_funcs.enqueued_samples();
|
||||
return lib->enqueued_samples();
|
||||
}
|
||||
|
||||
f32 XAudio2Backend::SetFrequencyRatio(f32 new_ratio)
|
||||
{
|
||||
return m_funcs.set_freq_ratio(new_ratio);
|
||||
return lib->set_freq_ratio(new_ratio);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -4,44 +4,28 @@
|
||||
|
||||
#include "Emu/Audio/AudioBackend.h"
|
||||
|
||||
|
||||
class XAudio2Backend : public AudioBackend
|
||||
{
|
||||
struct vtable
|
||||
public:
|
||||
class XAudio2Library
|
||||
{
|
||||
void(*destroy)();
|
||||
void(*play)();
|
||||
void(*flush)();
|
||||
void(*stop)();
|
||||
void(*open)();
|
||||
bool(*is_playing)();
|
||||
bool(*add)(const void*, u32);
|
||||
u64(*enqueued_samples)();
|
||||
f32(*set_freq_ratio)(f32);
|
||||
public:
|
||||
virtual void play() = 0;
|
||||
virtual void flush() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void open() = 0;
|
||||
virtual bool is_playing() = 0;
|
||||
virtual bool add(const void*, u32) = 0;
|
||||
virtual u64 enqueued_samples() = 0;
|
||||
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*);
|
||||
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);
|
||||
std::unique_ptr<XAudio2Library> lib = nullptr;
|
||||
|
||||
public:
|
||||
XAudio2Backend();
|
||||
@ -49,7 +33,7 @@ public:
|
||||
|
||||
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 void Open(u32 /* num_buffers */) override;
|
||||
@ -59,7 +43,7 @@ public:
|
||||
virtual void Pause() 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 u64 GetNumEnqueuedSamples() override;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "Emu/Cell/lv2/sys_event.h"
|
||||
#include "cellAudio.h"
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
|
||||
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)
|
||||
: cfg(_cfg)
|
||||
, backend(Emu.GetCallbacks().get_audio())
|
||||
, buf_sz(AUDIO_BUFFER_SAMPLES * cfg.audio_channels)
|
||||
, backend(_cfg.backend)
|
||||
, buf_sz(AUDIO_BUFFER_SAMPLES * _cfg.audio_channels)
|
||||
, emu_paused(Emu.IsPaused())
|
||||
{
|
||||
// Initialize buffers
|
||||
@ -64,13 +80,13 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg)
|
||||
{
|
||||
std::string 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 = true;
|
||||
|
||||
ASSERT(!backend_is_playing());
|
||||
ASSERT(!get_backend_playing());
|
||||
}
|
||||
|
||||
audio_ringbuffer::~audio_ringbuffer()
|
||||
@ -80,7 +96,7 @@ audio_ringbuffer::~audio_ringbuffer()
|
||||
return;
|
||||
}
|
||||
|
||||
if (backend_is_playing())
|
||||
if (get_backend_playing() && has_capability(AudioBackend::PLAY_PAUSE_FLUSH))
|
||||
{
|
||||
backend->Pause();
|
||||
}
|
||||
@ -98,7 +114,7 @@ f32 audio_ringbuffer::set_frequency_ratio(f32 new_ratio)
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -119,11 +135,11 @@ void audio_ringbuffer::enqueue(const float* in_buffer)
|
||||
// Dump audio if enabled
|
||||
if (m_dump)
|
||||
{
|
||||
m_dump->WriteData(buf, buf_sz);
|
||||
m_dump->WriteData(buf, cfg.audio_buffer_size);
|
||||
}
|
||||
|
||||
// Enqueue audio
|
||||
bool success = backend->AddData(buf, buf_sz);
|
||||
bool success = backend->AddData(buf, AUDIO_BUFFER_SAMPLES * cfg.audio_channels);
|
||||
if (!success)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
enqueued_samples += AUDIO_BUFFER_SAMPLES;
|
||||
if (has_capability(AudioBackend::PLAY_PAUSE_FLUSH))
|
||||
{
|
||||
enqueued_samples += AUDIO_BUFFER_SAMPLES;
|
||||
|
||||
// Start playing audio
|
||||
play();
|
||||
// Start playing audio
|
||||
play();
|
||||
}
|
||||
}
|
||||
|
||||
void audio_ringbuffer::enqueue_silence(u32 buf_count)
|
||||
{
|
||||
AUDIT(has_capability(AudioBackend::PLAY_PAUSE_FLUSH));
|
||||
|
||||
for (u32 i = 0; i < buf_count; i++)
|
||||
{
|
||||
enqueue(silence_buffer);
|
||||
@ -147,8 +168,16 @@ void audio_ringbuffer::enqueue_silence(u32 buf_count)
|
||||
|
||||
void audio_ringbuffer::play()
|
||||
{
|
||||
if (has_capability(AudioBackend::IS_PLAYING) && playing)
|
||||
if (!has_capability(AudioBackend::PLAY_PAUSE_FLUSH))
|
||||
{
|
||||
playing = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (has_capability(AudioBackend::IS_PLAYING) && playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (frequency_ratio != 1.0f)
|
||||
{
|
||||
@ -165,7 +194,13 @@ void audio_ringbuffer::play()
|
||||
|
||||
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();
|
||||
playing = false;
|
||||
@ -186,7 +221,7 @@ u64 audio_ringbuffer::update()
|
||||
if (Emu.IsPaused())
|
||||
{
|
||||
// Emulator paused
|
||||
if (playing)
|
||||
if (has_capability(AudioBackend::PLAY_PAUSE_FLUSH) && playing)
|
||||
{
|
||||
backend->Pause();
|
||||
}
|
||||
@ -200,7 +235,7 @@ u64 audio_ringbuffer::update()
|
||||
}
|
||||
|
||||
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
|
||||
if (cfg.buffering_enabled && (playing || new_playing))
|
||||
@ -234,7 +269,7 @@ u64 audio_ringbuffer::update()
|
||||
|
||||
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)
|
||||
{
|
||||
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();
|
||||
playing = false;
|
||||
@ -261,11 +296,6 @@ u64 audio_ringbuffer::update()
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
constexpr bool audio_port::is_tag(float val)
|
||||
{
|
||||
return val == 0.0f && std::signbit(val);
|
||||
}
|
||||
|
||||
void audio_port::tag(s32 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++)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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_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;
|
||||
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++)
|
||||
{
|
||||
// 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;
|
||||
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)
|
||||
{
|
||||
// Memset previous buffer to 0
|
||||
// Memset previous buffer to 0 and tag
|
||||
for (auto& port : ports)
|
||||
{
|
||||
if (port.state != audio_port_state::started) continue;
|
||||
@ -383,7 +398,6 @@ void cell_audio_thread::reset_ports(s32 offset)
|
||||
|
||||
if (cfg.buffering_enabled)
|
||||
{
|
||||
//port.reset_tag_backups(offset);
|
||||
port.tag(offset);
|
||||
}
|
||||
}
|
||||
@ -414,7 +428,7 @@ void cell_audio_thread::advance(u64 timestamp, bool reset)
|
||||
if (cfg.buffering_enabled)
|
||||
{
|
||||
// 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;
|
||||
//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
|
||||
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
|
||||
m_counter = 0;
|
||||
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)
|
||||
|
||||
const u64 time_since_last_period = timestamp - m_last_period_end;
|
||||
const bool playing = !ringbuffer->is_playing();
|
||||
|
||||
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);
|
||||
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)
|
||||
@ -548,13 +544,12 @@ void cell_audio_thread::operator()()
|
||||
|
||||
// update frequency ratio if necessary
|
||||
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);
|
||||
}
|
||||
else if (frequency_ratio != 1.0f)
|
||||
{
|
||||
new_ratio = ringbuffer->set_frequency_ratio(1.0f);
|
||||
new_ratio = ringbuffer->set_frequency_ratio(std::min(desired_duration_rate, 1.0f));
|
||||
}
|
||||
|
||||
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 (cfg.buffering_enabled)
|
||||
{
|
||||
port.apply_tag_backups(offset);
|
||||
}
|
||||
|
||||
auto buf = port.get_vm_ptr(offset);
|
||||
static const float k = 1.0f;
|
||||
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)
|
||||
{
|
||||
// TODO ruipin: Revisit this
|
||||
const auto scale = _mm_set1_ps(0x8000);
|
||||
_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)),
|
||||
|
@ -90,7 +90,7 @@ enum : u32
|
||||
AUDIO_BLOCK_SIZE_2CH = 2 * 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_DELTA_2CH = PORT_BUFFER_TAG_LAST_2CH / (PORT_BUFFER_TAG_COUNT - 1),
|
||||
@ -163,37 +163,59 @@ struct audio_port
|
||||
|
||||
// Tags
|
||||
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 apply_tag_backups(s32 offset = 0);
|
||||
};
|
||||
|
||||
struct cell_audio_config
|
||||
{
|
||||
const s64 period_comparison_margin = 100; // When comparing the current period time with the desired period, if it is below this number of usecs we do not wait any longer
|
||||
const std::shared_ptr<AudioBackend> backend = Emu.GetCallbacks().get_audio();
|
||||
|
||||
const u32 audio_channels = AudioBackend::get_channels();
|
||||
const u32 audio_sampling_rate = AudioBackend::get_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_size = audio_buffer_length * sizeof(f32);
|
||||
const bool buffering_enabled = g_cfg.audio.enable_buffering && (desired_buffer_duration >= audio_block_period);
|
||||
const u32 audio_buffer_size = audio_buffer_length * AudioBackend::get_sample_size();
|
||||
|
||||
/*
|
||||
* 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 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 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_frequency_scale_factor = 1.0f / time_stretching_threshold;
|
||||
const f32 time_stretching_step = 0.1f;
|
||||
|
||||
/*
|
||||
* Constructor
|
||||
*/
|
||||
cell_audio_config();
|
||||
};
|
||||
|
||||
class audio_ringbuffer
|
||||
@ -224,9 +246,9 @@ private:
|
||||
|
||||
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:
|
||||
@ -280,7 +302,7 @@ public:
|
||||
return frequency_ratio;
|
||||
}
|
||||
|
||||
u32 has_capability(AudioBackend::Capabilities cap) const
|
||||
u32 has_capability(u32 cap) const
|
||||
{
|
||||
return backend->has_capability(cap);
|
||||
}
|
||||
@ -307,7 +329,7 @@ class cell_audio_thread
|
||||
|
||||
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:
|
||||
@ -349,7 +371,7 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool has_capability(AudioBackend::Capabilities cap) const
|
||||
bool has_capability(u32 cap) const
|
||||
{
|
||||
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 convert_to_u16{this, "Convert to 16 bit"};
|
||||
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::_bool enable_buffering{this, "Enable Buffering", true};
|
||||
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::_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};
|
||||
|
||||
} audio{this};
|
||||
|
@ -48,10 +48,10 @@
|
||||
#include "Emu/Audio/XAudio2/XAudio2Backend.h"
|
||||
#endif
|
||||
#ifdef HAVE_ALSA
|
||||
#include "Emu/Audio/ALSA/ALSAThread.h"
|
||||
#include "Emu/Audio/ALSA/ALSABackend.h"
|
||||
#endif
|
||||
#ifdef HAVE_PULSE
|
||||
#include "Emu/Audio/Pulse/PulseThread.h"
|
||||
#include "Emu/Audio/Pulse/PulseBackend.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -263,10 +263,10 @@ void rpcs3_app::InitializeCallbacks()
|
||||
case audio_renderer::xaudio: return std::make_shared<XAudio2Backend>();
|
||||
#endif
|
||||
#ifdef HAVE_ALSA
|
||||
case audio_renderer::alsa: return std::make_shared<ALSAThread>();
|
||||
case audio_renderer::alsa: return std::make_shared<ALSABackend>();
|
||||
#endif
|
||||
#ifdef HAVE_PULSE
|
||||
case audio_renderer::pulse: return std::make_shared<PulseThread>();
|
||||
case audio_renderer::pulse: return std::make_shared<PulseBackend>();
|
||||
#endif
|
||||
|
||||
case audio_renderer::openal: return std::make_shared<OpenALBackend>();
|
||||
|
Loading…
x
Reference in New Issue
Block a user