mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-24 14:49:42 -06:00
Reformat all the things. Have fun with merge conflicts.
This commit is contained in:
@ -6,77 +6,77 @@
|
||||
|
||||
#include "AudioCommon/AOSoundStream.h"
|
||||
#include "AudioCommon/Mixer.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
#if defined(HAVE_AO) && HAVE_AO
|
||||
|
||||
void AOSound::SoundLoop()
|
||||
{
|
||||
Common::SetCurrentThreadName("Audio thread - ao");
|
||||
Common::SetCurrentThreadName("Audio thread - ao");
|
||||
|
||||
uint_32 numBytesToRender = 256;
|
||||
ao_initialize();
|
||||
default_driver = ao_default_driver_id();
|
||||
format.bits = 16;
|
||||
format.channels = 2;
|
||||
format.rate = m_mixer->GetSampleRate();
|
||||
format.byte_format = AO_FMT_LITTLE;
|
||||
uint_32 numBytesToRender = 256;
|
||||
ao_initialize();
|
||||
default_driver = ao_default_driver_id();
|
||||
format.bits = 16;
|
||||
format.channels = 2;
|
||||
format.rate = m_mixer->GetSampleRate();
|
||||
format.byte_format = AO_FMT_LITTLE;
|
||||
|
||||
device = ao_open_live(default_driver, &format, nullptr /* no options */);
|
||||
if (!device)
|
||||
{
|
||||
PanicAlertT("AudioCommon: Error opening AO device.\n");
|
||||
ao_shutdown();
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
device = ao_open_live(default_driver, &format, nullptr /* no options */);
|
||||
if (!device)
|
||||
{
|
||||
PanicAlertT("AudioCommon: Error opening AO device.\n");
|
||||
ao_shutdown();
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
buf_size = format.bits/8 * format.channels * format.rate;
|
||||
buf_size = format.bits / 8 * format.channels * format.rate;
|
||||
|
||||
while (m_run_thread.load())
|
||||
{
|
||||
m_mixer->Mix(realtimeBuffer, numBytesToRender >> 2);
|
||||
while (m_run_thread.load())
|
||||
{
|
||||
m_mixer->Mix(realtimeBuffer, numBytesToRender >> 2);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(soundCriticalSection);
|
||||
ao_play(device, (char*)realtimeBuffer, numBytesToRender);
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(soundCriticalSection);
|
||||
ao_play(device, (char*)realtimeBuffer, numBytesToRender);
|
||||
}
|
||||
|
||||
soundSyncEvent.Wait();
|
||||
}
|
||||
soundSyncEvent.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
bool AOSound::Start()
|
||||
{
|
||||
m_run_thread.store(true);
|
||||
memset(realtimeBuffer, 0, sizeof(realtimeBuffer));
|
||||
m_run_thread.store(true);
|
||||
memset(realtimeBuffer, 0, sizeof(realtimeBuffer));
|
||||
|
||||
thread = std::thread(&AOSound::SoundLoop, this);
|
||||
return true;
|
||||
thread = std::thread(&AOSound::SoundLoop, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AOSound::Update()
|
||||
{
|
||||
soundSyncEvent.Set();
|
||||
soundSyncEvent.Set();
|
||||
}
|
||||
|
||||
void AOSound::Stop()
|
||||
{
|
||||
m_run_thread.store(false);
|
||||
soundSyncEvent.Set();
|
||||
m_run_thread.store(false);
|
||||
soundSyncEvent.Set();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(soundCriticalSection);
|
||||
thread.join();
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(soundCriticalSection);
|
||||
thread.join();
|
||||
|
||||
if (device)
|
||||
ao_close(device);
|
||||
if (device)
|
||||
ao_close(device);
|
||||
|
||||
ao_shutdown();
|
||||
ao_shutdown();
|
||||
|
||||
device = nullptr;
|
||||
}
|
||||
device = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -19,29 +19,25 @@
|
||||
class AOSound final : public SoundStream
|
||||
{
|
||||
#if defined(HAVE_AO) && HAVE_AO
|
||||
std::thread thread;
|
||||
std::atomic<bool> m_run_thread;
|
||||
std::mutex soundCriticalSection;
|
||||
Common::Event soundSyncEvent;
|
||||
std::thread thread;
|
||||
std::atomic<bool> m_run_thread;
|
||||
std::mutex soundCriticalSection;
|
||||
Common::Event soundSyncEvent;
|
||||
|
||||
int buf_size;
|
||||
int buf_size;
|
||||
|
||||
ao_device *device;
|
||||
ao_sample_format format;
|
||||
int default_driver;
|
||||
ao_device* device;
|
||||
ao_sample_format format;
|
||||
int default_driver;
|
||||
|
||||
short realtimeBuffer[1024 * 1024];
|
||||
short realtimeBuffer[1024 * 1024];
|
||||
|
||||
public:
|
||||
bool Start() override;
|
||||
void SoundLoop() override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool Start() override;
|
||||
void SoundLoop() override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid() { return true; }
|
||||
#endif
|
||||
};
|
||||
|
@ -6,230 +6,229 @@
|
||||
|
||||
#include "AudioCommon/AlsaSoundStream.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Thread.h"
|
||||
|
||||
AlsaSound::AlsaSound()
|
||||
: m_thread_status(ALSAThreadStatus::STOPPED)
|
||||
, handle(nullptr)
|
||||
, frames_to_deliver(FRAME_COUNT_MIN)
|
||||
: m_thread_status(ALSAThreadStatus::STOPPED), handle(nullptr),
|
||||
frames_to_deliver(FRAME_COUNT_MIN)
|
||||
{
|
||||
}
|
||||
|
||||
bool AlsaSound::Start()
|
||||
{
|
||||
m_thread_status.store(ALSAThreadStatus::RUNNING);
|
||||
if (!AlsaInit())
|
||||
{
|
||||
m_thread_status.store(ALSAThreadStatus::STOPPED);
|
||||
return false;
|
||||
}
|
||||
m_thread_status.store(ALSAThreadStatus::RUNNING);
|
||||
if (!AlsaInit())
|
||||
{
|
||||
m_thread_status.store(ALSAThreadStatus::STOPPED);
|
||||
return false;
|
||||
}
|
||||
|
||||
thread = std::thread(&AlsaSound::SoundLoop, this);
|
||||
return true;
|
||||
thread = std::thread(&AlsaSound::SoundLoop, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AlsaSound::Stop()
|
||||
{
|
||||
m_thread_status.store(ALSAThreadStatus::STOPPING);
|
||||
m_thread_status.store(ALSAThreadStatus::STOPPING);
|
||||
|
||||
//Give the opportunity to the audio thread
|
||||
//to realize we are stopping the emulation
|
||||
cv.notify_one();
|
||||
thread.join();
|
||||
// Give the opportunity to the audio thread
|
||||
// to realize we are stopping the emulation
|
||||
cv.notify_one();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void AlsaSound::Update()
|
||||
{
|
||||
// don't need to do anything here.
|
||||
// don't need to do anything here.
|
||||
}
|
||||
|
||||
// Called on audio thread.
|
||||
void AlsaSound::SoundLoop()
|
||||
{
|
||||
Common::SetCurrentThreadName("Audio thread - alsa");
|
||||
while (m_thread_status.load() != ALSAThreadStatus::STOPPING)
|
||||
{
|
||||
while (m_thread_status.load() == ALSAThreadStatus::RUNNING)
|
||||
{
|
||||
m_mixer->Mix(mix_buffer, frames_to_deliver);
|
||||
int rc = snd_pcm_writei(handle, mix_buffer, frames_to_deliver);
|
||||
if (rc == -EPIPE)
|
||||
{
|
||||
// Underrun
|
||||
snd_pcm_prepare(handle);
|
||||
}
|
||||
else if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "writei fail: %s", snd_strerror(rc));
|
||||
}
|
||||
}
|
||||
if (m_thread_status.load() == ALSAThreadStatus::PAUSED)
|
||||
{
|
||||
snd_pcm_drop(handle); // Stop sound output
|
||||
Common::SetCurrentThreadName("Audio thread - alsa");
|
||||
while (m_thread_status.load() != ALSAThreadStatus::STOPPING)
|
||||
{
|
||||
while (m_thread_status.load() == ALSAThreadStatus::RUNNING)
|
||||
{
|
||||
m_mixer->Mix(mix_buffer, frames_to_deliver);
|
||||
int rc = snd_pcm_writei(handle, mix_buffer, frames_to_deliver);
|
||||
if (rc == -EPIPE)
|
||||
{
|
||||
// Underrun
|
||||
snd_pcm_prepare(handle);
|
||||
}
|
||||
else if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "writei fail: %s", snd_strerror(rc));
|
||||
}
|
||||
}
|
||||
if (m_thread_status.load() == ALSAThreadStatus::PAUSED)
|
||||
{
|
||||
snd_pcm_drop(handle); // Stop sound output
|
||||
|
||||
// Block until thread status changes.
|
||||
std::unique_lock<std::mutex> lock(cv_m);
|
||||
cv.wait(lock, [this]{ return m_thread_status.load() != ALSAThreadStatus::PAUSED; });
|
||||
// Block until thread status changes.
|
||||
std::unique_lock<std::mutex> lock(cv_m);
|
||||
cv.wait(lock, [this] { return m_thread_status.load() != ALSAThreadStatus::PAUSED; });
|
||||
|
||||
snd_pcm_prepare(handle); // resume sound output
|
||||
}
|
||||
}
|
||||
AlsaShutdown();
|
||||
m_thread_status.store(ALSAThreadStatus::STOPPED);
|
||||
snd_pcm_prepare(handle); // resume sound output
|
||||
}
|
||||
}
|
||||
AlsaShutdown();
|
||||
m_thread_status.store(ALSAThreadStatus::STOPPED);
|
||||
}
|
||||
|
||||
|
||||
void AlsaSound::Clear(bool muted)
|
||||
{
|
||||
m_muted = muted;
|
||||
m_thread_status.store(muted ? ALSAThreadStatus::PAUSED : ALSAThreadStatus::RUNNING);
|
||||
cv.notify_one(); // Notify thread that status has changed
|
||||
m_muted = muted;
|
||||
m_thread_status.store(muted ? ALSAThreadStatus::PAUSED : ALSAThreadStatus::RUNNING);
|
||||
cv.notify_one(); // Notify thread that status has changed
|
||||
}
|
||||
|
||||
bool AlsaSound::AlsaInit()
|
||||
{
|
||||
unsigned int sample_rate = m_mixer->GetSampleRate();
|
||||
int err;
|
||||
int dir;
|
||||
snd_pcm_sw_params_t *swparams;
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
snd_pcm_uframes_t buffer_size,buffer_size_max;
|
||||
unsigned int periods;
|
||||
unsigned int sample_rate = m_mixer->GetSampleRate();
|
||||
int err;
|
||||
int dir;
|
||||
snd_pcm_sw_params_t* swparams;
|
||||
snd_pcm_hw_params_t* hwparams;
|
||||
snd_pcm_uframes_t buffer_size, buffer_size_max;
|
||||
unsigned int periods;
|
||||
|
||||
err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Audio open error: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Audio open error: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
|
||||
err = snd_pcm_hw_params_any(handle, hwparams);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Broken configuration for this PCM: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_hw_params_any(handle, hwparams);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Broken configuration for this PCM: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Access type not available: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Access type not available: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16_LE);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Sample format not available: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16_LE);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Sample format not available: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
dir = 0;
|
||||
err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &sample_rate, &dir);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Rate not available: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
dir = 0;
|
||||
err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &sample_rate, &dir);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Rate not available: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_channels(handle, hwparams, CHANNEL_COUNT);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Channels count not available: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_hw_params_set_channels(handle, hwparams, CHANNEL_COUNT);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Channels count not available: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
periods = BUFFER_SIZE_MAX / FRAME_COUNT_MIN;
|
||||
err = snd_pcm_hw_params_set_periods_max(handle, hwparams, &periods, &dir);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Cannot set maximum periods per buffer: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
periods = BUFFER_SIZE_MAX / FRAME_COUNT_MIN;
|
||||
err = snd_pcm_hw_params_set_periods_max(handle, hwparams, &periods, &dir);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Cannot set maximum periods per buffer: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer_size_max = BUFFER_SIZE_MAX;
|
||||
err = snd_pcm_hw_params_set_buffer_size_max(handle, hwparams, &buffer_size_max);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Cannot set maximum buffer size: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
buffer_size_max = BUFFER_SIZE_MAX;
|
||||
err = snd_pcm_hw_params_set_buffer_size_max(handle, hwparams, &buffer_size_max);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Cannot set maximum buffer size: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params(handle, hwparams);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Unable to install hw params: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_hw_params(handle, hwparams);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Unable to install hw params: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Cannot get buffer size: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Cannot get buffer size: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_get_periods_max(hwparams, &periods, &dir);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Cannot get periods: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_hw_params_get_periods_max(hwparams, &periods, &dir);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Cannot get periods: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
//periods is the number of fragments alsa can wait for during one
|
||||
//buffer_size
|
||||
frames_to_deliver = buffer_size / periods;
|
||||
//limit the minimum size. pulseaudio advertises a minimum of 32 samples.
|
||||
if (frames_to_deliver < FRAME_COUNT_MIN)
|
||||
frames_to_deliver = FRAME_COUNT_MIN;
|
||||
//it is probably a bad idea to try to send more than one buffer of data
|
||||
if ((unsigned int)frames_to_deliver > buffer_size)
|
||||
frames_to_deliver = buffer_size;
|
||||
NOTICE_LOG(AUDIO, "ALSA gave us a %ld sample \"hardware\" buffer with %d periods. Will send %d samples per fragments.\n", buffer_size, periods, frames_to_deliver);
|
||||
// periods is the number of fragments alsa can wait for during one
|
||||
// buffer_size
|
||||
frames_to_deliver = buffer_size / periods;
|
||||
// limit the minimum size. pulseaudio advertises a minimum of 32 samples.
|
||||
if (frames_to_deliver < FRAME_COUNT_MIN)
|
||||
frames_to_deliver = FRAME_COUNT_MIN;
|
||||
// it is probably a bad idea to try to send more than one buffer of data
|
||||
if ((unsigned int)frames_to_deliver > buffer_size)
|
||||
frames_to_deliver = buffer_size;
|
||||
NOTICE_LOG(AUDIO, "ALSA gave us a %ld sample \"hardware\" buffer with %d periods. Will send %d "
|
||||
"samples per fragments.\n",
|
||||
buffer_size, periods, frames_to_deliver);
|
||||
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
|
||||
err = snd_pcm_sw_params_current(handle, swparams);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "cannot init sw params: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_sw_params_current(handle, swparams);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "cannot init sw params: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0U);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "cannot set start thresh: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0U);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "cannot set start thresh: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = snd_pcm_sw_params(handle, swparams);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "cannot set sw params: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
err = snd_pcm_sw_params(handle, swparams);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "cannot set sw params: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = snd_pcm_prepare(handle);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Unable to prepare: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
NOTICE_LOG(AUDIO, "ALSA successfully initialized.\n");
|
||||
return true;
|
||||
err = snd_pcm_prepare(handle);
|
||||
if (err < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Unable to prepare: %s\n", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
NOTICE_LOG(AUDIO, "ALSA successfully initialized.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void AlsaSound::AlsaShutdown()
|
||||
{
|
||||
if (handle != nullptr)
|
||||
{
|
||||
snd_pcm_drop(handle);
|
||||
snd_pcm_close(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
if (handle != nullptr)
|
||||
{
|
||||
snd_pcm_drop(handle);
|
||||
snd_pcm_close(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,47 +20,43 @@ class AlsaSound final : public SoundStream
|
||||
{
|
||||
#if defined(HAVE_ALSA) && HAVE_ALSA
|
||||
public:
|
||||
AlsaSound();
|
||||
AlsaSound();
|
||||
|
||||
bool Start() override;
|
||||
void SoundLoop() override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
void Clear(bool) override;
|
||||
|
||||
static bool isValid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool Start() override;
|
||||
void SoundLoop() override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
void Clear(bool) override;
|
||||
|
||||
static bool isValid() { return true; }
|
||||
private:
|
||||
// maximum number of frames the buffer can hold
|
||||
static constexpr size_t BUFFER_SIZE_MAX = 8192;
|
||||
// maximum number of frames the buffer can hold
|
||||
static constexpr size_t BUFFER_SIZE_MAX = 8192;
|
||||
|
||||
// minimum number of frames to deliver in one transfer
|
||||
static constexpr u32 FRAME_COUNT_MIN = 256;
|
||||
// minimum number of frames to deliver in one transfer
|
||||
static constexpr u32 FRAME_COUNT_MIN = 256;
|
||||
|
||||
// number of channels per frame
|
||||
static constexpr u32 CHANNEL_COUNT = 2;
|
||||
// number of channels per frame
|
||||
static constexpr u32 CHANNEL_COUNT = 2;
|
||||
|
||||
enum class ALSAThreadStatus
|
||||
{
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
STOPPING,
|
||||
STOPPED,
|
||||
};
|
||||
enum class ALSAThreadStatus
|
||||
{
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
STOPPING,
|
||||
STOPPED,
|
||||
};
|
||||
|
||||
bool AlsaInit();
|
||||
void AlsaShutdown();
|
||||
bool AlsaInit();
|
||||
void AlsaShutdown();
|
||||
|
||||
s16 mix_buffer[BUFFER_SIZE_MAX * CHANNEL_COUNT];
|
||||
std::thread thread;
|
||||
std::atomic<ALSAThreadStatus> m_thread_status;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
s16 mix_buffer[BUFFER_SIZE_MAX * CHANNEL_COUNT];
|
||||
std::thread thread;
|
||||
std::atomic<ALSAThreadStatus> m_thread_status;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
|
||||
snd_pcm_t *handle;
|
||||
unsigned int frames_to_deliver;
|
||||
snd_pcm_t* handle;
|
||||
unsigned int frames_to_deliver;
|
||||
#endif
|
||||
};
|
||||
|
@ -2,22 +2,21 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
#include "AudioCommon/AlsaSoundStream.h"
|
||||
#include "AudioCommon/AOSoundStream.h"
|
||||
#include "AudioCommon/AudioCommon.h"
|
||||
#include "AudioCommon/AOSoundStream.h"
|
||||
#include "AudioCommon/AlsaSoundStream.h"
|
||||
#include "AudioCommon/CoreAudioSoundStream.h"
|
||||
#include "AudioCommon/Mixer.h"
|
||||
#include "AudioCommon/NullSoundStream.h"
|
||||
#include "AudioCommon/OpenALStream.h"
|
||||
#include "AudioCommon/OpenSLESStream.h"
|
||||
#include "AudioCommon/PulseAudioStream.h"
|
||||
#include "AudioCommon/XAudio2_7Stream.h"
|
||||
#include "AudioCommon/XAudio2Stream.h"
|
||||
#include "AudioCommon/XAudio2_7Stream.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Movie.h"
|
||||
|
||||
@ -28,182 +27,182 @@ static bool s_audio_dump_start = false;
|
||||
|
||||
namespace AudioCommon
|
||||
{
|
||||
static const int AUDIO_VOLUME_MIN = 0;
|
||||
static const int AUDIO_VOLUME_MAX = 100;
|
||||
static const int AUDIO_VOLUME_MIN = 0;
|
||||
static const int AUDIO_VOLUME_MAX = 100;
|
||||
|
||||
SoundStream* InitSoundStream()
|
||||
{
|
||||
std::string backend = SConfig::GetInstance().sBackend;
|
||||
if (backend == BACKEND_OPENAL && OpenALStream::isValid())
|
||||
g_sound_stream = new OpenALStream();
|
||||
else if (backend == BACKEND_NULLSOUND && NullSound::isValid())
|
||||
g_sound_stream = new NullSound();
|
||||
else if (backend == BACKEND_XAUDIO2)
|
||||
{
|
||||
if (XAudio2::isValid())
|
||||
g_sound_stream = new XAudio2();
|
||||
else if (XAudio2_7::isValid())
|
||||
g_sound_stream = new XAudio2_7();
|
||||
}
|
||||
else if (backend == BACKEND_AOSOUND && AOSound::isValid())
|
||||
g_sound_stream = new AOSound();
|
||||
else if (backend == BACKEND_ALSA && AlsaSound::isValid())
|
||||
g_sound_stream = new AlsaSound();
|
||||
else if (backend == BACKEND_COREAUDIO && CoreAudioSound::isValid())
|
||||
g_sound_stream = new CoreAudioSound();
|
||||
else if (backend == BACKEND_PULSEAUDIO && PulseAudio::isValid())
|
||||
g_sound_stream = new PulseAudio();
|
||||
else if (backend == BACKEND_OPENSLES && OpenSLESStream::isValid())
|
||||
g_sound_stream = new OpenSLESStream();
|
||||
SoundStream* InitSoundStream()
|
||||
{
|
||||
std::string backend = SConfig::GetInstance().sBackend;
|
||||
if (backend == BACKEND_OPENAL && OpenALStream::isValid())
|
||||
g_sound_stream = new OpenALStream();
|
||||
else if (backend == BACKEND_NULLSOUND && NullSound::isValid())
|
||||
g_sound_stream = new NullSound();
|
||||
else if (backend == BACKEND_XAUDIO2)
|
||||
{
|
||||
if (XAudio2::isValid())
|
||||
g_sound_stream = new XAudio2();
|
||||
else if (XAudio2_7::isValid())
|
||||
g_sound_stream = new XAudio2_7();
|
||||
}
|
||||
else if (backend == BACKEND_AOSOUND && AOSound::isValid())
|
||||
g_sound_stream = new AOSound();
|
||||
else if (backend == BACKEND_ALSA && AlsaSound::isValid())
|
||||
g_sound_stream = new AlsaSound();
|
||||
else if (backend == BACKEND_COREAUDIO && CoreAudioSound::isValid())
|
||||
g_sound_stream = new CoreAudioSound();
|
||||
else if (backend == BACKEND_PULSEAUDIO && PulseAudio::isValid())
|
||||
g_sound_stream = new PulseAudio();
|
||||
else if (backend == BACKEND_OPENSLES && OpenSLESStream::isValid())
|
||||
g_sound_stream = new OpenSLESStream();
|
||||
|
||||
if (!g_sound_stream && NullSound::isValid())
|
||||
{
|
||||
WARN_LOG(AUDIO, "Could not initialize backend %s, using %s instead.",
|
||||
backend.c_str(), BACKEND_NULLSOUND);
|
||||
g_sound_stream = new NullSound();
|
||||
}
|
||||
if (!g_sound_stream && NullSound::isValid())
|
||||
{
|
||||
WARN_LOG(AUDIO, "Could not initialize backend %s, using %s instead.", backend.c_str(),
|
||||
BACKEND_NULLSOUND);
|
||||
g_sound_stream = new NullSound();
|
||||
}
|
||||
|
||||
if (g_sound_stream)
|
||||
{
|
||||
UpdateSoundStream();
|
||||
if (!g_sound_stream->Start())
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Could not start backend %s, using %s instead",
|
||||
backend.c_str(), BACKEND_NULLSOUND);
|
||||
delete g_sound_stream;
|
||||
g_sound_stream = new NullSound();
|
||||
g_sound_stream->Start();
|
||||
}
|
||||
if (g_sound_stream)
|
||||
{
|
||||
UpdateSoundStream();
|
||||
if (!g_sound_stream->Start())
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Could not start backend %s, using %s instead", backend.c_str(),
|
||||
BACKEND_NULLSOUND);
|
||||
delete g_sound_stream;
|
||||
g_sound_stream = new NullSound();
|
||||
g_sound_stream->Start();
|
||||
}
|
||||
|
||||
if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_start)
|
||||
StartAudioDump();
|
||||
if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_start)
|
||||
StartAudioDump();
|
||||
|
||||
return g_sound_stream;
|
||||
}
|
||||
return g_sound_stream;
|
||||
}
|
||||
|
||||
PanicAlertT("Sound backend %s is not valid.", backend.c_str());
|
||||
PanicAlertT("Sound backend %s is not valid.", backend.c_str());
|
||||
|
||||
delete g_sound_stream;
|
||||
g_sound_stream = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ShutdownSoundStream()
|
||||
{
|
||||
INFO_LOG(AUDIO, "Shutting down sound stream");
|
||||
|
||||
if (g_sound_stream)
|
||||
{
|
||||
g_sound_stream->Stop();
|
||||
if (SConfig::GetInstance().m_DumpAudio && s_audio_dump_start)
|
||||
StopAudioDump();
|
||||
delete g_sound_stream;
|
||||
g_sound_stream = nullptr;
|
||||
}
|
||||
|
||||
INFO_LOG(AUDIO, "Done shutting down sound stream");
|
||||
}
|
||||
|
||||
std::vector<std::string> GetSoundBackends()
|
||||
{
|
||||
std::vector<std::string> backends;
|
||||
|
||||
if (NullSound::isValid())
|
||||
backends.push_back(BACKEND_NULLSOUND);
|
||||
if (XAudio2_7::isValid() || XAudio2::isValid())
|
||||
backends.push_back(BACKEND_XAUDIO2);
|
||||
if (AOSound::isValid())
|
||||
backends.push_back(BACKEND_AOSOUND);
|
||||
if (AlsaSound::isValid())
|
||||
backends.push_back(BACKEND_ALSA);
|
||||
if (CoreAudioSound::isValid())
|
||||
backends.push_back(BACKEND_COREAUDIO);
|
||||
if (PulseAudio::isValid())
|
||||
backends.push_back(BACKEND_PULSEAUDIO);
|
||||
if (OpenALStream::isValid())
|
||||
backends.push_back(BACKEND_OPENAL);
|
||||
if (OpenSLESStream::isValid())
|
||||
backends.push_back(BACKEND_OPENSLES);
|
||||
return backends;
|
||||
}
|
||||
|
||||
void UpdateSoundStream()
|
||||
{
|
||||
if (g_sound_stream)
|
||||
{
|
||||
int volume = SConfig::GetInstance().m_IsMuted ? 0 : SConfig::GetInstance().m_Volume;
|
||||
g_sound_stream->SetVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearAudioBuffer(bool mute)
|
||||
{
|
||||
if (g_sound_stream)
|
||||
g_sound_stream->Clear(mute);
|
||||
}
|
||||
|
||||
void SendAIBuffer(short *samples, unsigned int num_samples)
|
||||
{
|
||||
if (!g_sound_stream)
|
||||
return;
|
||||
|
||||
if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_start)
|
||||
StartAudioDump();
|
||||
else if (!SConfig::GetInstance().m_DumpAudio && s_audio_dump_start)
|
||||
StopAudioDump();
|
||||
|
||||
CMixer* pMixer = g_sound_stream->GetMixer();
|
||||
|
||||
if (pMixer && samples)
|
||||
{
|
||||
pMixer->PushSamples(samples, num_samples);
|
||||
}
|
||||
|
||||
g_sound_stream->Update();
|
||||
}
|
||||
|
||||
void StartAudioDump()
|
||||
{
|
||||
std::string audio_file_name_dtk = File::GetUserPath(D_DUMPAUDIO_IDX) + "dtkdump.wav";
|
||||
std::string audio_file_name_dsp = File::GetUserPath(D_DUMPAUDIO_IDX) + "dspdump.wav";
|
||||
File::CreateFullPath(audio_file_name_dtk);
|
||||
File::CreateFullPath(audio_file_name_dsp);
|
||||
g_sound_stream->GetMixer()->StartLogDTKAudio(audio_file_name_dtk);
|
||||
g_sound_stream->GetMixer()->StartLogDSPAudio(audio_file_name_dsp);
|
||||
s_audio_dump_start = true;
|
||||
}
|
||||
|
||||
void StopAudioDump()
|
||||
{
|
||||
g_sound_stream->GetMixer()->StopLogDTKAudio();
|
||||
g_sound_stream->GetMixer()->StopLogDSPAudio();
|
||||
s_audio_dump_start = false;
|
||||
}
|
||||
|
||||
void IncreaseVolume(unsigned short offset)
|
||||
{
|
||||
SConfig::GetInstance().m_IsMuted = false;
|
||||
int& currentVolume = SConfig::GetInstance().m_Volume;
|
||||
currentVolume += offset;
|
||||
if (currentVolume > AUDIO_VOLUME_MAX)
|
||||
currentVolume = AUDIO_VOLUME_MAX;
|
||||
UpdateSoundStream();
|
||||
}
|
||||
|
||||
void DecreaseVolume(unsigned short offset)
|
||||
{
|
||||
SConfig::GetInstance().m_IsMuted = false;
|
||||
int& currentVolume = SConfig::GetInstance().m_Volume;
|
||||
currentVolume -= offset;
|
||||
if (currentVolume < AUDIO_VOLUME_MIN)
|
||||
currentVolume = AUDIO_VOLUME_MIN;
|
||||
UpdateSoundStream();
|
||||
}
|
||||
|
||||
void ToggleMuteVolume()
|
||||
{
|
||||
bool& isMuted = SConfig::GetInstance().m_IsMuted;
|
||||
isMuted = !isMuted;
|
||||
UpdateSoundStream();
|
||||
}
|
||||
delete g_sound_stream;
|
||||
g_sound_stream = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ShutdownSoundStream()
|
||||
{
|
||||
INFO_LOG(AUDIO, "Shutting down sound stream");
|
||||
|
||||
if (g_sound_stream)
|
||||
{
|
||||
g_sound_stream->Stop();
|
||||
if (SConfig::GetInstance().m_DumpAudio && s_audio_dump_start)
|
||||
StopAudioDump();
|
||||
delete g_sound_stream;
|
||||
g_sound_stream = nullptr;
|
||||
}
|
||||
|
||||
INFO_LOG(AUDIO, "Done shutting down sound stream");
|
||||
}
|
||||
|
||||
std::vector<std::string> GetSoundBackends()
|
||||
{
|
||||
std::vector<std::string> backends;
|
||||
|
||||
if (NullSound::isValid())
|
||||
backends.push_back(BACKEND_NULLSOUND);
|
||||
if (XAudio2_7::isValid() || XAudio2::isValid())
|
||||
backends.push_back(BACKEND_XAUDIO2);
|
||||
if (AOSound::isValid())
|
||||
backends.push_back(BACKEND_AOSOUND);
|
||||
if (AlsaSound::isValid())
|
||||
backends.push_back(BACKEND_ALSA);
|
||||
if (CoreAudioSound::isValid())
|
||||
backends.push_back(BACKEND_COREAUDIO);
|
||||
if (PulseAudio::isValid())
|
||||
backends.push_back(BACKEND_PULSEAUDIO);
|
||||
if (OpenALStream::isValid())
|
||||
backends.push_back(BACKEND_OPENAL);
|
||||
if (OpenSLESStream::isValid())
|
||||
backends.push_back(BACKEND_OPENSLES);
|
||||
return backends;
|
||||
}
|
||||
|
||||
void UpdateSoundStream()
|
||||
{
|
||||
if (g_sound_stream)
|
||||
{
|
||||
int volume = SConfig::GetInstance().m_IsMuted ? 0 : SConfig::GetInstance().m_Volume;
|
||||
g_sound_stream->SetVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearAudioBuffer(bool mute)
|
||||
{
|
||||
if (g_sound_stream)
|
||||
g_sound_stream->Clear(mute);
|
||||
}
|
||||
|
||||
void SendAIBuffer(short* samples, unsigned int num_samples)
|
||||
{
|
||||
if (!g_sound_stream)
|
||||
return;
|
||||
|
||||
if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_start)
|
||||
StartAudioDump();
|
||||
else if (!SConfig::GetInstance().m_DumpAudio && s_audio_dump_start)
|
||||
StopAudioDump();
|
||||
|
||||
CMixer* pMixer = g_sound_stream->GetMixer();
|
||||
|
||||
if (pMixer && samples)
|
||||
{
|
||||
pMixer->PushSamples(samples, num_samples);
|
||||
}
|
||||
|
||||
g_sound_stream->Update();
|
||||
}
|
||||
|
||||
void StartAudioDump()
|
||||
{
|
||||
std::string audio_file_name_dtk = File::GetUserPath(D_DUMPAUDIO_IDX) + "dtkdump.wav";
|
||||
std::string audio_file_name_dsp = File::GetUserPath(D_DUMPAUDIO_IDX) + "dspdump.wav";
|
||||
File::CreateFullPath(audio_file_name_dtk);
|
||||
File::CreateFullPath(audio_file_name_dsp);
|
||||
g_sound_stream->GetMixer()->StartLogDTKAudio(audio_file_name_dtk);
|
||||
g_sound_stream->GetMixer()->StartLogDSPAudio(audio_file_name_dsp);
|
||||
s_audio_dump_start = true;
|
||||
}
|
||||
|
||||
void StopAudioDump()
|
||||
{
|
||||
g_sound_stream->GetMixer()->StopLogDTKAudio();
|
||||
g_sound_stream->GetMixer()->StopLogDSPAudio();
|
||||
s_audio_dump_start = false;
|
||||
}
|
||||
|
||||
void IncreaseVolume(unsigned short offset)
|
||||
{
|
||||
SConfig::GetInstance().m_IsMuted = false;
|
||||
int& currentVolume = SConfig::GetInstance().m_Volume;
|
||||
currentVolume += offset;
|
||||
if (currentVolume > AUDIO_VOLUME_MAX)
|
||||
currentVolume = AUDIO_VOLUME_MAX;
|
||||
UpdateSoundStream();
|
||||
}
|
||||
|
||||
void DecreaseVolume(unsigned short offset)
|
||||
{
|
||||
SConfig::GetInstance().m_IsMuted = false;
|
||||
int& currentVolume = SConfig::GetInstance().m_Volume;
|
||||
currentVolume -= offset;
|
||||
if (currentVolume < AUDIO_VOLUME_MIN)
|
||||
currentVolume = AUDIO_VOLUME_MIN;
|
||||
UpdateSoundStream();
|
||||
}
|
||||
|
||||
void ToggleMuteVolume()
|
||||
{
|
||||
bool& isMuted = SConfig::GetInstance().m_IsMuted;
|
||||
isMuted = !isMuted;
|
||||
UpdateSoundStream();
|
||||
}
|
||||
}
|
||||
|
@ -7,22 +7,21 @@
|
||||
#include "AudioCommon/SoundStream.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
|
||||
class CMixer;
|
||||
|
||||
extern SoundStream *g_sound_stream;
|
||||
extern SoundStream* g_sound_stream;
|
||||
|
||||
namespace AudioCommon
|
||||
{
|
||||
SoundStream* InitSoundStream();
|
||||
void ShutdownSoundStream();
|
||||
std::vector<std::string> GetSoundBackends();
|
||||
void UpdateSoundStream();
|
||||
void ClearAudioBuffer(bool mute);
|
||||
void SendAIBuffer(short* samples, unsigned int num_samples);
|
||||
void StartAudioDump();
|
||||
void StopAudioDump();
|
||||
void IncreaseVolume(unsigned short offset);
|
||||
void DecreaseVolume(unsigned short offset);
|
||||
void ToggleMuteVolume();
|
||||
SoundStream* InitSoundStream();
|
||||
void ShutdownSoundStream();
|
||||
std::vector<std::string> GetSoundBackends();
|
||||
void UpdateSoundStream();
|
||||
void ClearAudioBuffer(bool mute);
|
||||
void SendAIBuffer(short* samples, unsigned int num_samples);
|
||||
void StartAudioDump();
|
||||
void StopAudioDump();
|
||||
void IncreaseVolume(unsigned short offset);
|
||||
void DecreaseVolume(unsigned short offset);
|
||||
void ToggleMuteVolume();
|
||||
}
|
||||
|
@ -7,105 +7,94 @@
|
||||
#include "AudioCommon/CoreAudioSoundStream.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
OSStatus CoreAudioSound::callback(void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames, AudioBufferList *ioData)
|
||||
OSStatus CoreAudioSound::callback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags,
|
||||
const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames, AudioBufferList* ioData)
|
||||
{
|
||||
for (UInt32 i = 0; i < ioData->mNumberBuffers; i++)
|
||||
((CoreAudioSound *)inRefCon)->m_mixer->
|
||||
Mix((short *)ioData->mBuffers[i].mData,
|
||||
ioData->mBuffers[i].mDataByteSize / 4);
|
||||
for (UInt32 i = 0; i < ioData->mNumberBuffers; i++)
|
||||
((CoreAudioSound*)inRefCon)
|
||||
->m_mixer->Mix((short*)ioData->mBuffers[i].mData, ioData->mBuffers[i].mDataByteSize / 4);
|
||||
|
||||
return noErr;
|
||||
return noErr;
|
||||
}
|
||||
|
||||
bool CoreAudioSound::Start()
|
||||
{
|
||||
OSStatus err;
|
||||
AURenderCallbackStruct callback_struct;
|
||||
AudioStreamBasicDescription format;
|
||||
AudioComponentDescription desc;
|
||||
AudioComponent component;
|
||||
OSStatus err;
|
||||
AURenderCallbackStruct callback_struct;
|
||||
AudioStreamBasicDescription format;
|
||||
AudioComponentDescription desc;
|
||||
AudioComponent component;
|
||||
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
component = AudioComponentFindNext(nullptr, &desc);
|
||||
if (component == nullptr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error finding audio component");
|
||||
return false;
|
||||
}
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
component = AudioComponentFindNext(nullptr, &desc);
|
||||
if (component == nullptr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error finding audio component");
|
||||
return false;
|
||||
}
|
||||
|
||||
err = AudioComponentInstanceNew(component, &audioUnit);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error opening audio component");
|
||||
return false;
|
||||
}
|
||||
err = AudioComponentInstanceNew(component, &audioUnit);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error opening audio component");
|
||||
return false;
|
||||
}
|
||||
|
||||
FillOutASBDForLPCM(format, m_mixer->GetSampleRate(),
|
||||
2, 16, 16, false, false, false);
|
||||
err = AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &format,
|
||||
sizeof(AudioStreamBasicDescription));
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error setting audio format");
|
||||
return false;
|
||||
}
|
||||
FillOutASBDForLPCM(format, m_mixer->GetSampleRate(), 2, 16, 16, false, false, false);
|
||||
err = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0,
|
||||
&format, sizeof(AudioStreamBasicDescription));
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error setting audio format");
|
||||
return false;
|
||||
}
|
||||
|
||||
callback_struct.inputProc = callback;
|
||||
callback_struct.inputProcRefCon = this;
|
||||
err = AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &callback_struct,
|
||||
sizeof callback_struct);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error setting audio callback");
|
||||
return false;
|
||||
}
|
||||
callback_struct.inputProc = callback;
|
||||
callback_struct.inputProcRefCon = this;
|
||||
err = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
|
||||
0, &callback_struct, sizeof callback_struct);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error setting audio callback");
|
||||
return false;
|
||||
}
|
||||
|
||||
err = AudioUnitSetParameter(audioUnit,
|
||||
kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Output, 0,
|
||||
m_volume / 100., 0);
|
||||
if (err != noErr)
|
||||
ERROR_LOG(AUDIO, "error setting volume");
|
||||
err = AudioUnitSetParameter(audioUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, 0,
|
||||
m_volume / 100., 0);
|
||||
if (err != noErr)
|
||||
ERROR_LOG(AUDIO, "error setting volume");
|
||||
|
||||
err = AudioUnitInitialize(audioUnit);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error initializing audiounit");
|
||||
return false;
|
||||
}
|
||||
err = AudioUnitInitialize(audioUnit);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error initializing audiounit");
|
||||
return false;
|
||||
}
|
||||
|
||||
err = AudioOutputUnitStart(audioUnit);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error starting audiounit");
|
||||
return false;
|
||||
}
|
||||
err = AudioOutputUnitStart(audioUnit);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "error starting audiounit");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CoreAudioSound::SetVolume(int volume)
|
||||
{
|
||||
OSStatus err;
|
||||
m_volume = volume;
|
||||
OSStatus err;
|
||||
m_volume = volume;
|
||||
|
||||
err = AudioUnitSetParameter(audioUnit,
|
||||
kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Output, 0,
|
||||
volume / 100., 0);
|
||||
if (err != noErr)
|
||||
ERROR_LOG(AUDIO, "error setting volume");
|
||||
err = AudioUnitSetParameter(audioUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, 0,
|
||||
volume / 100., 0);
|
||||
if (err != noErr)
|
||||
ERROR_LOG(AUDIO, "error setting volume");
|
||||
}
|
||||
|
||||
void CoreAudioSound::SoundLoop()
|
||||
@ -114,19 +103,19 @@ void CoreAudioSound::SoundLoop()
|
||||
|
||||
void CoreAudioSound::Stop()
|
||||
{
|
||||
OSStatus err;
|
||||
OSStatus err;
|
||||
|
||||
err = AudioOutputUnitStop(audioUnit);
|
||||
if (err != noErr)
|
||||
ERROR_LOG(AUDIO, "error stopping audiounit");
|
||||
err = AudioOutputUnitStop(audioUnit);
|
||||
if (err != noErr)
|
||||
ERROR_LOG(AUDIO, "error stopping audiounit");
|
||||
|
||||
err = AudioUnitUninitialize(audioUnit);
|
||||
if (err != noErr)
|
||||
ERROR_LOG(AUDIO, "error uninitializing audiounit");
|
||||
err = AudioUnitUninitialize(audioUnit);
|
||||
if (err != noErr)
|
||||
ERROR_LOG(AUDIO, "error uninitializing audiounit");
|
||||
|
||||
err = AudioComponentInstanceDispose(audioUnit);
|
||||
if (err != noErr)
|
||||
ERROR_LOG(AUDIO, "error closing audio component");
|
||||
err = AudioComponentInstanceDispose(audioUnit);
|
||||
if (err != noErr)
|
||||
ERROR_LOG(AUDIO, "error closing audio component");
|
||||
}
|
||||
|
||||
void CoreAudioSound::Update()
|
||||
|
@ -14,25 +14,19 @@ class CoreAudioSound final : public SoundStream
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
public:
|
||||
bool Start() override;
|
||||
void SetVolume(int volume) override;
|
||||
void SoundLoop() override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool Start() override;
|
||||
void SetVolume(int volume) override;
|
||||
void SoundLoop() override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid() { return true; }
|
||||
private:
|
||||
AudioUnit audioUnit;
|
||||
int m_volume;
|
||||
AudioUnit audioUnit;
|
||||
int m_volume;
|
||||
|
||||
static OSStatus callback(void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber, UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData);
|
||||
static OSStatus callback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags,
|
||||
const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames, AudioBufferList* ioData);
|
||||
#endif
|
||||
};
|
||||
|
@ -35,54 +35,56 @@ static float adapt_l_gain, adapt_r_gain, adapt_lpr_gain, adapt_lmr_gain;
|
||||
static std::vector<float> lf, rf, lr, rr, cf, cr;
|
||||
static float LFE_buf[256];
|
||||
static unsigned int lfe_pos;
|
||||
static float *filter_coefs_lfe;
|
||||
static float* filter_coefs_lfe;
|
||||
static unsigned int len125;
|
||||
|
||||
template<class T, class _ftype_t>
|
||||
static _ftype_t DotProduct(int count, const T *buf, const _ftype_t *coefficients)
|
||||
template <class T, class _ftype_t>
|
||||
static _ftype_t DotProduct(int count, const T* buf, const _ftype_t* coefficients)
|
||||
{
|
||||
int i;
|
||||
float sum0 = 0.0f, sum1 = 0.0f, sum2 = 0.0f, sum3 = 0.0f;
|
||||
int i;
|
||||
float sum0 = 0.0f, sum1 = 0.0f, sum2 = 0.0f, sum3 = 0.0f;
|
||||
|
||||
// Unrolled loop
|
||||
for (i = 0; (i + 3) < count; i += 4)
|
||||
{
|
||||
sum0 += buf[i + 0] * coefficients[i + 0];
|
||||
sum1 += buf[i + 1] * coefficients[i + 1];
|
||||
sum2 += buf[i + 2] * coefficients[i + 2];
|
||||
sum3 += buf[i + 3] * coefficients[i + 3];
|
||||
}
|
||||
// Unrolled loop
|
||||
for (i = 0; (i + 3) < count; i += 4)
|
||||
{
|
||||
sum0 += buf[i + 0] * coefficients[i + 0];
|
||||
sum1 += buf[i + 1] * coefficients[i + 1];
|
||||
sum2 += buf[i + 2] * coefficients[i + 2];
|
||||
sum3 += buf[i + 3] * coefficients[i + 3];
|
||||
}
|
||||
|
||||
// Epilogue of unrolled loop
|
||||
for (; i < count; i++)
|
||||
sum0 += buf[i] * coefficients[i];
|
||||
// Epilogue of unrolled loop
|
||||
for (; i < count; i++)
|
||||
sum0 += buf[i] * coefficients[i];
|
||||
|
||||
return sum0 + sum1 + sum2 + sum3;
|
||||
return sum0 + sum1 + sum2 + sum3;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static T FIRFilter(const T *buf, int pos, int len, int count, const float *coefficients)
|
||||
template <class T>
|
||||
static T FIRFilter(const T* buf, int pos, int len, int count, const float* coefficients)
|
||||
{
|
||||
int count1, count2;
|
||||
int count1, count2;
|
||||
|
||||
if (pos >= count)
|
||||
{
|
||||
pos -= count;
|
||||
count1 = count; count2 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
count2 = pos;
|
||||
count1 = count - pos;
|
||||
pos = len - count1;
|
||||
}
|
||||
if (pos >= count)
|
||||
{
|
||||
pos -= count;
|
||||
count1 = count;
|
||||
count2 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
count2 = pos;
|
||||
count1 = count - pos;
|
||||
pos = len - count1;
|
||||
}
|
||||
|
||||
// high part of window
|
||||
const T *ptr = &buf[pos];
|
||||
// high part of window
|
||||
const T* ptr = &buf[pos];
|
||||
|
||||
float r1 = DotProduct(count1, ptr, coefficients); coefficients += count1;
|
||||
float r2 = DotProduct(count2, buf, coefficients);
|
||||
return T(r1 + r2);
|
||||
float r1 = DotProduct(count1, ptr, coefficients);
|
||||
coefficients += count1;
|
||||
float r2 = DotProduct(count2, buf, coefficients);
|
||||
return T(r1 + r2);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -96,11 +98,11 @@ static T FIRFilter(const T *buf, int pos, int len, int count, const float *coeff
|
||||
*/
|
||||
static void Hamming(int n, float* w)
|
||||
{
|
||||
float k = float(2*M_PI/((float)(n - 1))); // 2*pi/(N-1)
|
||||
float k = float(2 * M_PI / ((float)(n - 1))); // 2*pi/(N-1)
|
||||
|
||||
// Calculate window coefficients
|
||||
for (int i = 0; i < n; i++)
|
||||
*w++ = float(0.54 - 0.46*cos(k*(float)i));
|
||||
// Calculate window coefficients
|
||||
for (int i = 0; i < n; i++)
|
||||
*w++ = float(0.54 - 0.46 * cos(k * (float)i));
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
@ -120,276 +122,270 @@ opt beta constant used only when designing using kaiser windows
|
||||
|
||||
returns 0 if OK, -1 if fail
|
||||
*/
|
||||
static float* DesignFIR(unsigned int *n, float* fc, float opt)
|
||||
static float* DesignFIR(unsigned int* n, float* fc, float opt)
|
||||
{
|
||||
unsigned int o = *n & 1; // Indicator for odd filter length
|
||||
unsigned int end = ((*n + 1) >> 1) - o; // Loop end
|
||||
unsigned int o = *n & 1; // Indicator for odd filter length
|
||||
unsigned int end = ((*n + 1) >> 1) - o; // Loop end
|
||||
|
||||
float k1 = 2 * float(M_PI); // 2*pi*fc1
|
||||
float k2 = 0.5f * (float)(1 - o); // Constant used if the filter has even length
|
||||
float g = 0.0f; // Gain
|
||||
float t1; // Temporary variables
|
||||
float fc1; // Cutoff frequencies
|
||||
float k1 = 2 * float(M_PI); // 2*pi*fc1
|
||||
float k2 = 0.5f * (float)(1 - o); // Constant used if the filter has even length
|
||||
float g = 0.0f; // Gain
|
||||
float t1; // Temporary variables
|
||||
float fc1; // Cutoff frequencies
|
||||
|
||||
// Sanity check
|
||||
if (*n == 0)
|
||||
return nullptr;
|
||||
// Sanity check
|
||||
if (*n == 0)
|
||||
return nullptr;
|
||||
|
||||
fc[0] = MathUtil::Clamp(fc[0], 0.001f, 1.0f);
|
||||
fc[0] = MathUtil::Clamp(fc[0], 0.001f, 1.0f);
|
||||
|
||||
float *w = (float*)calloc(sizeof(float), *n);
|
||||
float* w = (float*)calloc(sizeof(float), *n);
|
||||
|
||||
// Get window coefficients
|
||||
Hamming(*n, w);
|
||||
// Get window coefficients
|
||||
Hamming(*n, w);
|
||||
|
||||
fc1 = *fc;
|
||||
// Cutoff frequency must be < 0.5 where 0.5 <=> Fs/2
|
||||
fc1 = ((fc1 <= 1.0) && (fc1 > 0.0)) ? fc1 / 2 : 0.25f;
|
||||
k1 *= fc1;
|
||||
fc1 = *fc;
|
||||
// Cutoff frequency must be < 0.5 where 0.5 <=> Fs/2
|
||||
fc1 = ((fc1 <= 1.0) && (fc1 > 0.0)) ? fc1 / 2 : 0.25f;
|
||||
k1 *= fc1;
|
||||
|
||||
// Low pass filter
|
||||
// Low pass filter
|
||||
|
||||
// If the filter length is odd, there is one point which is exactly
|
||||
// in the middle. The value at this point is 2*fCutoff*sin(x)/x,
|
||||
// where x is zero. To make sure nothing strange happens, we set this
|
||||
// value separately.
|
||||
if (o)
|
||||
{
|
||||
w[end] = fc1 * w[end] * 2.0f;
|
||||
g = w[end];
|
||||
}
|
||||
// If the filter length is odd, there is one point which is exactly
|
||||
// in the middle. The value at this point is 2*fCutoff*sin(x)/x,
|
||||
// where x is zero. To make sure nothing strange happens, we set this
|
||||
// value separately.
|
||||
if (o)
|
||||
{
|
||||
w[end] = fc1 * w[end] * 2.0f;
|
||||
g = w[end];
|
||||
}
|
||||
|
||||
// Create filter
|
||||
for (u32 i = 0; i < end; i++)
|
||||
{
|
||||
t1 = (float)(i + 1) - k2;
|
||||
w[end - i - 1] = w[*n - end + i] = float(w[end - i - 1] * sin(k1 * t1)/(M_PI * t1)); // Sinc
|
||||
g += 2*w[end - i - 1]; // Total gain in filter
|
||||
}
|
||||
// Create filter
|
||||
for (u32 i = 0; i < end; i++)
|
||||
{
|
||||
t1 = (float)(i + 1) - k2;
|
||||
w[end - i - 1] = w[*n - end + i] = float(w[end - i - 1] * sin(k1 * t1) / (M_PI * t1)); // Sinc
|
||||
g += 2 * w[end - i - 1]; // Total gain in filter
|
||||
}
|
||||
|
||||
// Normalize gain
|
||||
g = 1 / g;
|
||||
for (u32 i = 0; i < *n; i++)
|
||||
w[i] *= g;
|
||||
|
||||
// Normalize gain
|
||||
g = 1/g;
|
||||
for (u32 i = 0; i < *n; i++)
|
||||
w[i] *= g;
|
||||
|
||||
return w;
|
||||
return w;
|
||||
}
|
||||
|
||||
static void OnSeek()
|
||||
{
|
||||
l_fwr = r_fwr = lpr_fwr = lmr_fwr = 0;
|
||||
std::fill(fwrbuf_l.begin(), fwrbuf_l.end(), 0.0f);
|
||||
std::fill(fwrbuf_r.begin(), fwrbuf_r.end(), 0.0f);
|
||||
adapt_l_gain = adapt_r_gain = adapt_lpr_gain = adapt_lmr_gain = 0;
|
||||
std::fill(lf.begin(), lf.end(), 0.0f);
|
||||
std::fill(rf.begin(), rf.end(), 0.0f);
|
||||
std::fill(lr.begin(), lr.end(), 0.0f);
|
||||
std::fill(rr.begin(), rr.end(), 0.0f);
|
||||
std::fill(cf.begin(), cf.end(), 0.0f);
|
||||
std::fill(cr.begin(), cr.end(), 0.0f);
|
||||
lfe_pos = 0;
|
||||
memset(LFE_buf, 0, sizeof(LFE_buf));
|
||||
l_fwr = r_fwr = lpr_fwr = lmr_fwr = 0;
|
||||
std::fill(fwrbuf_l.begin(), fwrbuf_l.end(), 0.0f);
|
||||
std::fill(fwrbuf_r.begin(), fwrbuf_r.end(), 0.0f);
|
||||
adapt_l_gain = adapt_r_gain = adapt_lpr_gain = adapt_lmr_gain = 0;
|
||||
std::fill(lf.begin(), lf.end(), 0.0f);
|
||||
std::fill(rf.begin(), rf.end(), 0.0f);
|
||||
std::fill(lr.begin(), lr.end(), 0.0f);
|
||||
std::fill(rr.begin(), rr.end(), 0.0f);
|
||||
std::fill(cf.begin(), cf.end(), 0.0f);
|
||||
std::fill(cr.begin(), cr.end(), 0.0f);
|
||||
lfe_pos = 0;
|
||||
memset(LFE_buf, 0, sizeof(LFE_buf));
|
||||
}
|
||||
|
||||
static void Done()
|
||||
{
|
||||
OnSeek();
|
||||
OnSeek();
|
||||
|
||||
if (filter_coefs_lfe)
|
||||
{
|
||||
free(filter_coefs_lfe);
|
||||
}
|
||||
if (filter_coefs_lfe)
|
||||
{
|
||||
free(filter_coefs_lfe);
|
||||
}
|
||||
|
||||
filter_coefs_lfe = nullptr;
|
||||
filter_coefs_lfe = nullptr;
|
||||
}
|
||||
|
||||
static float* CalculateCoefficients125HzLowpass(int rate)
|
||||
{
|
||||
len125 = 256;
|
||||
float f = 125.0f / (rate / 2);
|
||||
float *coeffs = DesignFIR(&len125, &f, 0);
|
||||
static const float M3_01DB = 0.7071067812f;
|
||||
for (unsigned int i = 0; i < len125; i++)
|
||||
{
|
||||
coeffs[i] *= M3_01DB;
|
||||
}
|
||||
return coeffs;
|
||||
len125 = 256;
|
||||
float f = 125.0f / (rate / 2);
|
||||
float* coeffs = DesignFIR(&len125, &f, 0);
|
||||
static const float M3_01DB = 0.7071067812f;
|
||||
for (unsigned int i = 0; i < len125; i++)
|
||||
{
|
||||
coeffs[i] *= M3_01DB;
|
||||
}
|
||||
return coeffs;
|
||||
}
|
||||
|
||||
static float PassiveLock(float x)
|
||||
{
|
||||
static const float MATAGCLOCK = 0.2f; /* AGC range (around 1) where the matrix behaves passively */
|
||||
const float x1 = x - 1;
|
||||
const float ax1s = fabs(x - 1) * (1.0f / MATAGCLOCK);
|
||||
return x1 - x1 / (1 + ax1s * ax1s) + 1;
|
||||
static const float MATAGCLOCK =
|
||||
0.2f; /* AGC range (around 1) where the matrix behaves passively */
|
||||
const float x1 = x - 1;
|
||||
const float ax1s = fabs(x - 1) * (1.0f / MATAGCLOCK);
|
||||
return x1 - x1 / (1 + ax1s * ax1s) + 1;
|
||||
}
|
||||
|
||||
static void MatrixDecode(const float *in, const int k, const int il,
|
||||
const int ir, bool decode_rear,
|
||||
const int _dlbuflen,
|
||||
float _l_fwr, float _r_fwr,
|
||||
float _lpr_fwr, float _lmr_fwr,
|
||||
float *_adapt_l_gain, float *_adapt_r_gain,
|
||||
float *_adapt_lpr_gain, float *_adapt_lmr_gain,
|
||||
float *_lf, float *_rf, float *_lr,
|
||||
float *_rr, float *_cf)
|
||||
static void MatrixDecode(const float* in, const int k, const int il, const int ir, bool decode_rear,
|
||||
const int _dlbuflen, float _l_fwr, float _r_fwr, float _lpr_fwr,
|
||||
float _lmr_fwr, float* _adapt_l_gain, float* _adapt_r_gain,
|
||||
float* _adapt_lpr_gain, float* _adapt_lmr_gain, float* _lf, float* _rf,
|
||||
float* _lr, float* _rr, float* _cf)
|
||||
{
|
||||
static const float M9_03DB = 0.3535533906f;
|
||||
static const float MATAGCTRIG = 8.0f; /* (Fuzzy) AGC trigger */
|
||||
static const float MATAGCDECAY = 1.0f; /* AGC baseline decay rate (1/samp.) */
|
||||
static const float MATCOMPGAIN = 0.37f; /* Cross talk compensation gain, 0.50 - 0.55 is full cancellation. */
|
||||
static const float M9_03DB = 0.3535533906f;
|
||||
static const float MATAGCTRIG = 8.0f; /* (Fuzzy) AGC trigger */
|
||||
static const float MATAGCDECAY = 1.0f; /* AGC baseline decay rate (1/samp.) */
|
||||
static const float MATCOMPGAIN =
|
||||
0.37f; /* Cross talk compensation gain, 0.50 - 0.55 is full cancellation. */
|
||||
|
||||
const int kr = (k + olddelay) % _dlbuflen;
|
||||
float l_gain = (_l_fwr + _r_fwr) / (1 + _l_fwr + _l_fwr);
|
||||
float r_gain = (_l_fwr + _r_fwr) / (1 + _r_fwr + _r_fwr);
|
||||
// The 2nd axis has strong gain fluctuations, and therefore require
|
||||
// limits. The factor corresponds to the 1 / amplification of (Lt
|
||||
// - Rt) when (Lt, Rt) is strongly correlated. (e.g. during
|
||||
// dialogues). It should be bigger than -12 dB to prevent
|
||||
// distortion.
|
||||
float lmr_lim_fwr = _lmr_fwr > M9_03DB * _lpr_fwr ? _lmr_fwr : M9_03DB * _lpr_fwr;
|
||||
float lpr_gain = (_lpr_fwr + lmr_lim_fwr) / (1 + _lpr_fwr + _lpr_fwr);
|
||||
float lmr_gain = (_lpr_fwr + lmr_lim_fwr) / (1 + lmr_lim_fwr + lmr_lim_fwr);
|
||||
float lmr_unlim_gain = (_lpr_fwr + _lmr_fwr) / (1 + _lmr_fwr + _lmr_fwr);
|
||||
float lpr, lmr;
|
||||
float l_agc, r_agc, lpr_agc, lmr_agc;
|
||||
float f, d_gain, c_gain, c_agc_cfk;
|
||||
const int kr = (k + olddelay) % _dlbuflen;
|
||||
float l_gain = (_l_fwr + _r_fwr) / (1 + _l_fwr + _l_fwr);
|
||||
float r_gain = (_l_fwr + _r_fwr) / (1 + _r_fwr + _r_fwr);
|
||||
// The 2nd axis has strong gain fluctuations, and therefore require
|
||||
// limits. The factor corresponds to the 1 / amplification of (Lt
|
||||
// - Rt) when (Lt, Rt) is strongly correlated. (e.g. during
|
||||
// dialogues). It should be bigger than -12 dB to prevent
|
||||
// distortion.
|
||||
float lmr_lim_fwr = _lmr_fwr > M9_03DB * _lpr_fwr ? _lmr_fwr : M9_03DB * _lpr_fwr;
|
||||
float lpr_gain = (_lpr_fwr + lmr_lim_fwr) / (1 + _lpr_fwr + _lpr_fwr);
|
||||
float lmr_gain = (_lpr_fwr + lmr_lim_fwr) / (1 + lmr_lim_fwr + lmr_lim_fwr);
|
||||
float lmr_unlim_gain = (_lpr_fwr + _lmr_fwr) / (1 + _lmr_fwr + _lmr_fwr);
|
||||
float lpr, lmr;
|
||||
float l_agc, r_agc, lpr_agc, lmr_agc;
|
||||
float f, d_gain, c_gain, c_agc_cfk;
|
||||
|
||||
/*** AXIS NO. 1: (Lt, Rt) -> (C, Ls, Rs) ***/
|
||||
/* AGC adaption */
|
||||
d_gain = (fabs(l_gain - *_adapt_l_gain) + fabs(r_gain - *_adapt_r_gain)) * 0.5f;
|
||||
f = d_gain * (1.0f / MATAGCTRIG);
|
||||
f = MATAGCDECAY - MATAGCDECAY / (1 + f * f);
|
||||
*_adapt_l_gain = (1 - f) * *_adapt_l_gain + f * l_gain;
|
||||
*_adapt_r_gain = (1 - f) * *_adapt_r_gain + f * r_gain;
|
||||
/* Matrix */
|
||||
l_agc = in[il] * PassiveLock(*_adapt_l_gain);
|
||||
r_agc = in[ir] * PassiveLock(*_adapt_r_gain);
|
||||
_cf[k] = (l_agc + r_agc) * (float)M_SQRT1_2;
|
||||
if (decode_rear)
|
||||
{
|
||||
_lr[kr] = _rr[kr] = (l_agc - r_agc) * (float)M_SQRT1_2;
|
||||
// Stereo rear channel is steered with the same AGC steering as
|
||||
// the decoding matrix. Note this requires a fast updating AGC
|
||||
// at the order of 20 ms (which is the case here).
|
||||
_lr[kr] *= (_l_fwr + _l_fwr) / (1 + _l_fwr + _r_fwr);
|
||||
_rr[kr] *= (_r_fwr + _r_fwr) / (1 + _l_fwr + _r_fwr);
|
||||
}
|
||||
/*** AXIS NO. 1: (Lt, Rt) -> (C, Ls, Rs) ***/
|
||||
/* AGC adaption */
|
||||
d_gain = (fabs(l_gain - *_adapt_l_gain) + fabs(r_gain - *_adapt_r_gain)) * 0.5f;
|
||||
f = d_gain * (1.0f / MATAGCTRIG);
|
||||
f = MATAGCDECAY - MATAGCDECAY / (1 + f * f);
|
||||
*_adapt_l_gain = (1 - f) * *_adapt_l_gain + f * l_gain;
|
||||
*_adapt_r_gain = (1 - f) * *_adapt_r_gain + f * r_gain;
|
||||
/* Matrix */
|
||||
l_agc = in[il] * PassiveLock(*_adapt_l_gain);
|
||||
r_agc = in[ir] * PassiveLock(*_adapt_r_gain);
|
||||
_cf[k] = (l_agc + r_agc) * (float)M_SQRT1_2;
|
||||
if (decode_rear)
|
||||
{
|
||||
_lr[kr] = _rr[kr] = (l_agc - r_agc) * (float)M_SQRT1_2;
|
||||
// Stereo rear channel is steered with the same AGC steering as
|
||||
// the decoding matrix. Note this requires a fast updating AGC
|
||||
// at the order of 20 ms (which is the case here).
|
||||
_lr[kr] *= (_l_fwr + _l_fwr) / (1 + _l_fwr + _r_fwr);
|
||||
_rr[kr] *= (_r_fwr + _r_fwr) / (1 + _l_fwr + _r_fwr);
|
||||
}
|
||||
|
||||
/*** AXIS NO. 2: (Lt + Rt, Lt - Rt) -> (L, R) ***/
|
||||
lpr = (in[il] + in[ir]) * (float)M_SQRT1_2;
|
||||
lmr = (in[il] - in[ir]) * (float)M_SQRT1_2;
|
||||
/* AGC adaption */
|
||||
d_gain = fabs(lmr_unlim_gain - *_adapt_lmr_gain);
|
||||
f = d_gain * (1.0f / MATAGCTRIG);
|
||||
f = MATAGCDECAY - MATAGCDECAY / (1 + f * f);
|
||||
*_adapt_lpr_gain = (1 - f) * *_adapt_lpr_gain + f * lpr_gain;
|
||||
*_adapt_lmr_gain = (1 - f) * *_adapt_lmr_gain + f * lmr_gain;
|
||||
/* Matrix */
|
||||
lpr_agc = lpr * PassiveLock(*_adapt_lpr_gain);
|
||||
lmr_agc = lmr * PassiveLock(*_adapt_lmr_gain);
|
||||
_lf[k] = (lpr_agc + lmr_agc) * (float)M_SQRT1_2;
|
||||
_rf[k] = (lpr_agc - lmr_agc) * (float)M_SQRT1_2;
|
||||
/*** AXIS NO. 2: (Lt + Rt, Lt - Rt) -> (L, R) ***/
|
||||
lpr = (in[il] + in[ir]) * (float)M_SQRT1_2;
|
||||
lmr = (in[il] - in[ir]) * (float)M_SQRT1_2;
|
||||
/* AGC adaption */
|
||||
d_gain = fabs(lmr_unlim_gain - *_adapt_lmr_gain);
|
||||
f = d_gain * (1.0f / MATAGCTRIG);
|
||||
f = MATAGCDECAY - MATAGCDECAY / (1 + f * f);
|
||||
*_adapt_lpr_gain = (1 - f) * *_adapt_lpr_gain + f * lpr_gain;
|
||||
*_adapt_lmr_gain = (1 - f) * *_adapt_lmr_gain + f * lmr_gain;
|
||||
/* Matrix */
|
||||
lpr_agc = lpr * PassiveLock(*_adapt_lpr_gain);
|
||||
lmr_agc = lmr * PassiveLock(*_adapt_lmr_gain);
|
||||
_lf[k] = (lpr_agc + lmr_agc) * (float)M_SQRT1_2;
|
||||
_rf[k] = (lpr_agc - lmr_agc) * (float)M_SQRT1_2;
|
||||
|
||||
/*** CENTER FRONT CANCELLATION ***/
|
||||
// A heuristic approach exploits that Lt + Rt gain contains the
|
||||
// information about Lt, Rt correlation. This effectively reshapes
|
||||
// the front and rear "cones" to concentrate Lt + Rt to C and
|
||||
// introduce Lt - Rt in L, R.
|
||||
/* 0.67677 is the empirical lower bound for lpr_gain. */
|
||||
c_gain = 8 * (*_adapt_lpr_gain - 0.67677f);
|
||||
c_gain = c_gain > 0 ? c_gain : 0;
|
||||
// c_gain should not be too high, not even reaching full
|
||||
// cancellation (~ 0.50 - 0.55 at current AGC implementation), or
|
||||
// the center will sound too narrow. */
|
||||
c_gain = MATCOMPGAIN / (1 + c_gain * c_gain);
|
||||
c_agc_cfk = c_gain * _cf[k];
|
||||
_lf[k] -= c_agc_cfk;
|
||||
_rf[k] -= c_agc_cfk;
|
||||
_cf[k] += c_agc_cfk + c_agc_cfk;
|
||||
/*** CENTER FRONT CANCELLATION ***/
|
||||
// A heuristic approach exploits that Lt + Rt gain contains the
|
||||
// information about Lt, Rt correlation. This effectively reshapes
|
||||
// the front and rear "cones" to concentrate Lt + Rt to C and
|
||||
// introduce Lt - Rt in L, R.
|
||||
/* 0.67677 is the empirical lower bound for lpr_gain. */
|
||||
c_gain = 8 * (*_adapt_lpr_gain - 0.67677f);
|
||||
c_gain = c_gain > 0 ? c_gain : 0;
|
||||
// c_gain should not be too high, not even reaching full
|
||||
// cancellation (~ 0.50 - 0.55 at current AGC implementation), or
|
||||
// the center will sound too narrow. */
|
||||
c_gain = MATCOMPGAIN / (1 + c_gain * c_gain);
|
||||
c_agc_cfk = c_gain * _cf[k];
|
||||
_lf[k] -= c_agc_cfk;
|
||||
_rf[k] -= c_agc_cfk;
|
||||
_cf[k] += c_agc_cfk + c_agc_cfk;
|
||||
}
|
||||
|
||||
void DPL2Decode(float *samples, int numsamples, float *out)
|
||||
void DPL2Decode(float* samples, int numsamples, float* out)
|
||||
{
|
||||
static const unsigned int FWRDURATION = 240; // FWR average duration (samples)
|
||||
static const int cfg_delay = 0;
|
||||
static const unsigned int fmt_freq = 48000;
|
||||
static const unsigned int fmt_nchannels = 2; // input channels
|
||||
static const unsigned int FWRDURATION = 240; // FWR average duration (samples)
|
||||
static const int cfg_delay = 0;
|
||||
static const unsigned int fmt_freq = 48000;
|
||||
static const unsigned int fmt_nchannels = 2; // input channels
|
||||
|
||||
int cur = 0;
|
||||
int cur = 0;
|
||||
|
||||
if (olddelay != cfg_delay || oldfreq != fmt_freq)
|
||||
{
|
||||
Done();
|
||||
olddelay = cfg_delay;
|
||||
oldfreq = fmt_freq;
|
||||
dlbuflen = std::max(FWRDURATION, (fmt_freq * cfg_delay / 1000)); //+(len7000-1);
|
||||
cyc_pos = dlbuflen - 1;
|
||||
fwrbuf_l.resize(dlbuflen);
|
||||
fwrbuf_r.resize(dlbuflen);
|
||||
lf.resize(dlbuflen);
|
||||
rf.resize(dlbuflen);
|
||||
lr.resize(dlbuflen);
|
||||
rr.resize(dlbuflen);
|
||||
cf.resize(dlbuflen);
|
||||
cr.resize(dlbuflen);
|
||||
filter_coefs_lfe = CalculateCoefficients125HzLowpass(fmt_freq);
|
||||
lfe_pos = 0;
|
||||
memset(LFE_buf, 0, sizeof(LFE_buf));
|
||||
}
|
||||
if (olddelay != cfg_delay || oldfreq != fmt_freq)
|
||||
{
|
||||
Done();
|
||||
olddelay = cfg_delay;
|
||||
oldfreq = fmt_freq;
|
||||
dlbuflen = std::max(FWRDURATION, (fmt_freq * cfg_delay / 1000)); //+(len7000-1);
|
||||
cyc_pos = dlbuflen - 1;
|
||||
fwrbuf_l.resize(dlbuflen);
|
||||
fwrbuf_r.resize(dlbuflen);
|
||||
lf.resize(dlbuflen);
|
||||
rf.resize(dlbuflen);
|
||||
lr.resize(dlbuflen);
|
||||
rr.resize(dlbuflen);
|
||||
cf.resize(dlbuflen);
|
||||
cr.resize(dlbuflen);
|
||||
filter_coefs_lfe = CalculateCoefficients125HzLowpass(fmt_freq);
|
||||
lfe_pos = 0;
|
||||
memset(LFE_buf, 0, sizeof(LFE_buf));
|
||||
}
|
||||
|
||||
float *in = samples; // Input audio data
|
||||
float *end = in + numsamples * fmt_nchannels; // Loop end
|
||||
float* in = samples; // Input audio data
|
||||
float* end = in + numsamples * fmt_nchannels; // Loop end
|
||||
|
||||
while (in < end)
|
||||
{
|
||||
const int k = cyc_pos;
|
||||
while (in < end)
|
||||
{
|
||||
const int k = cyc_pos;
|
||||
|
||||
const int fwr_pos = (k + FWRDURATION) % dlbuflen;
|
||||
/* Update the full wave rectified total amplitude */
|
||||
/* Input matrix decoder */
|
||||
l_fwr += fabs(in[0]) - fabs(fwrbuf_l[fwr_pos]);
|
||||
r_fwr += fabs(in[1]) - fabs(fwrbuf_r[fwr_pos]);
|
||||
lpr_fwr += fabs(in[0] + in[1]) - fabs(fwrbuf_l[fwr_pos] + fwrbuf_r[fwr_pos]);
|
||||
lmr_fwr += fabs(in[0] - in[1]) - fabs(fwrbuf_l[fwr_pos] - fwrbuf_r[fwr_pos]);
|
||||
const int fwr_pos = (k + FWRDURATION) % dlbuflen;
|
||||
/* Update the full wave rectified total amplitude */
|
||||
/* Input matrix decoder */
|
||||
l_fwr += fabs(in[0]) - fabs(fwrbuf_l[fwr_pos]);
|
||||
r_fwr += fabs(in[1]) - fabs(fwrbuf_r[fwr_pos]);
|
||||
lpr_fwr += fabs(in[0] + in[1]) - fabs(fwrbuf_l[fwr_pos] + fwrbuf_r[fwr_pos]);
|
||||
lmr_fwr += fabs(in[0] - in[1]) - fabs(fwrbuf_l[fwr_pos] - fwrbuf_r[fwr_pos]);
|
||||
|
||||
/* Matrix encoded 2 channel sources */
|
||||
fwrbuf_l[k] = in[0];
|
||||
fwrbuf_r[k] = in[1];
|
||||
MatrixDecode(in, k, 0, 1, true, dlbuflen,
|
||||
l_fwr, r_fwr,
|
||||
lpr_fwr, lmr_fwr,
|
||||
&adapt_l_gain, &adapt_r_gain,
|
||||
&adapt_lpr_gain, &adapt_lmr_gain,
|
||||
&lf[0], &rf[0], &lr[0], &rr[0], &cf[0]);
|
||||
/* Matrix encoded 2 channel sources */
|
||||
fwrbuf_l[k] = in[0];
|
||||
fwrbuf_r[k] = in[1];
|
||||
MatrixDecode(in, k, 0, 1, true, dlbuflen, l_fwr, r_fwr, lpr_fwr, lmr_fwr, &adapt_l_gain,
|
||||
&adapt_r_gain, &adapt_lpr_gain, &adapt_lmr_gain, &lf[0], &rf[0], &lr[0], &rr[0],
|
||||
&cf[0]);
|
||||
|
||||
out[cur + 0] = lf[k];
|
||||
out[cur + 1] = rf[k];
|
||||
out[cur + 2] = cf[k];
|
||||
LFE_buf[lfe_pos] = (lf[k] + rf[k] + 2.0f * cf[k] + lr[k] + rr[k]) / 2.0f;
|
||||
out[cur + 3] = FIRFilter(LFE_buf, lfe_pos, len125, len125, filter_coefs_lfe);
|
||||
lfe_pos++;
|
||||
if (lfe_pos == len125)
|
||||
{
|
||||
lfe_pos = 0;
|
||||
}
|
||||
out[cur + 4] = lr[k];
|
||||
out[cur + 5] = rr[k];
|
||||
// Next sample...
|
||||
in += 2;
|
||||
cur += 6;
|
||||
cyc_pos--;
|
||||
if (cyc_pos < 0)
|
||||
{
|
||||
cyc_pos += dlbuflen;
|
||||
}
|
||||
}
|
||||
out[cur + 0] = lf[k];
|
||||
out[cur + 1] = rf[k];
|
||||
out[cur + 2] = cf[k];
|
||||
LFE_buf[lfe_pos] = (lf[k] + rf[k] + 2.0f * cf[k] + lr[k] + rr[k]) / 2.0f;
|
||||
out[cur + 3] = FIRFilter(LFE_buf, lfe_pos, len125, len125, filter_coefs_lfe);
|
||||
lfe_pos++;
|
||||
if (lfe_pos == len125)
|
||||
{
|
||||
lfe_pos = 0;
|
||||
}
|
||||
out[cur + 4] = lr[k];
|
||||
out[cur + 5] = rr[k];
|
||||
// Next sample...
|
||||
in += 2;
|
||||
cur += 6;
|
||||
cyc_pos--;
|
||||
if (cyc_pos < 0)
|
||||
{
|
||||
cyc_pos += dlbuflen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DPL2Reset()
|
||||
{
|
||||
olddelay = -1;
|
||||
oldfreq = 0;
|
||||
filter_coefs_lfe = nullptr;
|
||||
olddelay = -1;
|
||||
oldfreq = 0;
|
||||
filter_coefs_lfe = nullptr;
|
||||
}
|
||||
|
@ -4,5 +4,5 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
void DPL2Decode(float *samples, int numsamples, float *out);
|
||||
void DPL2Decode(float* samples, int numsamples, float* out);
|
||||
void DPL2Reset();
|
||||
|
@ -8,18 +8,17 @@
|
||||
#include "AudioCommon/Mixer.h"
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#if _M_SSE >= 0x301 && !(defined __GNUC__ && !defined __SSSE3__)
|
||||
#include <tmmintrin.h>
|
||||
#endif
|
||||
|
||||
CMixer::CMixer(unsigned int BackendSampleRate)
|
||||
: m_sampleRate(BackendSampleRate)
|
||||
CMixer::CMixer(unsigned int BackendSampleRate) : m_sampleRate(BackendSampleRate)
|
||||
{
|
||||
INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized");
|
||||
INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized");
|
||||
}
|
||||
|
||||
CMixer::~CMixer()
|
||||
@ -27,249 +26,253 @@ CMixer::~CMixer()
|
||||
}
|
||||
|
||||
// Executed from sound stream thread
|
||||
unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples, bool consider_framelimit)
|
||||
unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples,
|
||||
bool consider_framelimit)
|
||||
{
|
||||
unsigned int currentSample = 0;
|
||||
unsigned int currentSample = 0;
|
||||
|
||||
// Cache access in non-volatile variable
|
||||
// This is the only function changing the read value, so it's safe to
|
||||
// cache it locally although it's written here.
|
||||
// The writing pointer will be modified outside, but it will only increase,
|
||||
// so we will just ignore new written data while interpolating.
|
||||
// Without this cache, the compiler wouldn't be allowed to optimize the
|
||||
// interpolation loop.
|
||||
u32 indexR = m_indexR.load();
|
||||
u32 indexW = m_indexW.load();
|
||||
// Cache access in non-volatile variable
|
||||
// This is the only function changing the read value, so it's safe to
|
||||
// cache it locally although it's written here.
|
||||
// The writing pointer will be modified outside, but it will only increase,
|
||||
// so we will just ignore new written data while interpolating.
|
||||
// Without this cache, the compiler wouldn't be allowed to optimize the
|
||||
// interpolation loop.
|
||||
u32 indexR = m_indexR.load();
|
||||
u32 indexW = m_indexW.load();
|
||||
|
||||
u32 low_waterwark = m_input_sample_rate * SConfig::GetInstance().iTimingVariance / 1000;
|
||||
low_waterwark = std::min(low_waterwark, MAX_SAMPLES / 2);
|
||||
u32 low_waterwark = m_input_sample_rate * SConfig::GetInstance().iTimingVariance / 1000;
|
||||
low_waterwark = std::min(low_waterwark, MAX_SAMPLES / 2);
|
||||
|
||||
float numLeft = (float)(((indexW - indexR) & INDEX_MASK) / 2);
|
||||
m_numLeftI = (numLeft + m_numLeftI*(CONTROL_AVG-1)) / CONTROL_AVG;
|
||||
float offset = (m_numLeftI - low_waterwark) * CONTROL_FACTOR;
|
||||
if (offset > MAX_FREQ_SHIFT) offset = MAX_FREQ_SHIFT;
|
||||
if (offset < -MAX_FREQ_SHIFT) offset = -MAX_FREQ_SHIFT;
|
||||
float numLeft = (float)(((indexW - indexR) & INDEX_MASK) / 2);
|
||||
m_numLeftI = (numLeft + m_numLeftI * (CONTROL_AVG - 1)) / CONTROL_AVG;
|
||||
float offset = (m_numLeftI - low_waterwark) * CONTROL_FACTOR;
|
||||
if (offset > MAX_FREQ_SHIFT)
|
||||
offset = MAX_FREQ_SHIFT;
|
||||
if (offset < -MAX_FREQ_SHIFT)
|
||||
offset = -MAX_FREQ_SHIFT;
|
||||
|
||||
//render numleft sample pairs to samples[]
|
||||
//advance indexR with sample position
|
||||
//remember fractional offset
|
||||
// render numleft sample pairs to samples[]
|
||||
// advance indexR with sample position
|
||||
// remember fractional offset
|
||||
|
||||
float emulationspeed = SConfig::GetInstance().m_EmulationSpeed;
|
||||
float aid_sample_rate = m_input_sample_rate + offset;
|
||||
if (consider_framelimit && emulationspeed > 0.0f)
|
||||
{
|
||||
aid_sample_rate = aid_sample_rate * emulationspeed;
|
||||
}
|
||||
float emulationspeed = SConfig::GetInstance().m_EmulationSpeed;
|
||||
float aid_sample_rate = m_input_sample_rate + offset;
|
||||
if (consider_framelimit && emulationspeed > 0.0f)
|
||||
{
|
||||
aid_sample_rate = aid_sample_rate * emulationspeed;
|
||||
}
|
||||
|
||||
const u32 ratio = (u32)(65536.0f * aid_sample_rate / (float)m_mixer->m_sampleRate);
|
||||
const u32 ratio = (u32)(65536.0f * aid_sample_rate / (float)m_mixer->m_sampleRate);
|
||||
|
||||
s32 lvolume = m_LVolume.load();
|
||||
s32 rvolume = m_RVolume.load();
|
||||
s32 lvolume = m_LVolume.load();
|
||||
s32 rvolume = m_RVolume.load();
|
||||
|
||||
// TODO: consider a higher-quality resampling algorithm.
|
||||
for (; currentSample < numSamples * 2 && ((indexW-indexR) & INDEX_MASK) > 2; currentSample += 2)
|
||||
{
|
||||
u32 indexR2 = indexR + 2; //next sample
|
||||
// TODO: consider a higher-quality resampling algorithm.
|
||||
for (; currentSample < numSamples * 2 && ((indexW - indexR) & INDEX_MASK) > 2; currentSample += 2)
|
||||
{
|
||||
u32 indexR2 = indexR + 2; // next sample
|
||||
|
||||
s16 l1 = Common::swap16(m_buffer[indexR & INDEX_MASK]); //current
|
||||
s16 l2 = Common::swap16(m_buffer[indexR2 & INDEX_MASK]); //next
|
||||
int sampleL = ((l1 << 16) + (l2 - l1) * (u16)m_frac) >> 16;
|
||||
sampleL = (sampleL * lvolume) >> 8;
|
||||
sampleL += samples[currentSample + 1];
|
||||
samples[currentSample + 1] = MathUtil::Clamp(sampleL, -32767, 32767);
|
||||
s16 l1 = Common::swap16(m_buffer[indexR & INDEX_MASK]); // current
|
||||
s16 l2 = Common::swap16(m_buffer[indexR2 & INDEX_MASK]); // next
|
||||
int sampleL = ((l1 << 16) + (l2 - l1) * (u16)m_frac) >> 16;
|
||||
sampleL = (sampleL * lvolume) >> 8;
|
||||
sampleL += samples[currentSample + 1];
|
||||
samples[currentSample + 1] = MathUtil::Clamp(sampleL, -32767, 32767);
|
||||
|
||||
s16 r1 = Common::swap16(m_buffer[(indexR + 1) & INDEX_MASK]); //current
|
||||
s16 r2 = Common::swap16(m_buffer[(indexR2 + 1) & INDEX_MASK]); //next
|
||||
int sampleR = ((r1 << 16) + (r2 - r1) * (u16)m_frac) >> 16;
|
||||
sampleR = (sampleR * rvolume) >> 8;
|
||||
sampleR += samples[currentSample];
|
||||
samples[currentSample] = MathUtil::Clamp(sampleR, -32767, 32767);
|
||||
s16 r1 = Common::swap16(m_buffer[(indexR + 1) & INDEX_MASK]); // current
|
||||
s16 r2 = Common::swap16(m_buffer[(indexR2 + 1) & INDEX_MASK]); // next
|
||||
int sampleR = ((r1 << 16) + (r2 - r1) * (u16)m_frac) >> 16;
|
||||
sampleR = (sampleR * rvolume) >> 8;
|
||||
sampleR += samples[currentSample];
|
||||
samples[currentSample] = MathUtil::Clamp(sampleR, -32767, 32767);
|
||||
|
||||
m_frac += ratio;
|
||||
indexR += 2 * (u16)(m_frac >> 16);
|
||||
m_frac &= 0xffff;
|
||||
}
|
||||
m_frac += ratio;
|
||||
indexR += 2 * (u16)(m_frac >> 16);
|
||||
m_frac &= 0xffff;
|
||||
}
|
||||
|
||||
// Padding
|
||||
short s[2];
|
||||
s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]);
|
||||
s[1] = Common::swap16(m_buffer[(indexR - 2) & INDEX_MASK]);
|
||||
s[0] = (s[0] * rvolume) >> 8;
|
||||
s[1] = (s[1] * lvolume) >> 8;
|
||||
for (; currentSample < numSamples * 2; currentSample += 2)
|
||||
{
|
||||
int sampleR = MathUtil::Clamp(s[0] + samples[currentSample + 0], -32767, 32767);
|
||||
int sampleL = MathUtil::Clamp(s[1] + samples[currentSample + 1], -32767, 32767);
|
||||
// Padding
|
||||
short s[2];
|
||||
s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]);
|
||||
s[1] = Common::swap16(m_buffer[(indexR - 2) & INDEX_MASK]);
|
||||
s[0] = (s[0] * rvolume) >> 8;
|
||||
s[1] = (s[1] * lvolume) >> 8;
|
||||
for (; currentSample < numSamples * 2; currentSample += 2)
|
||||
{
|
||||
int sampleR = MathUtil::Clamp(s[0] + samples[currentSample + 0], -32767, 32767);
|
||||
int sampleL = MathUtil::Clamp(s[1] + samples[currentSample + 1], -32767, 32767);
|
||||
|
||||
samples[currentSample + 0] = sampleR;
|
||||
samples[currentSample + 1] = sampleL;
|
||||
}
|
||||
samples[currentSample + 0] = sampleR;
|
||||
samples[currentSample + 1] = sampleL;
|
||||
}
|
||||
|
||||
// Flush cached variable
|
||||
m_indexR.store(indexR);
|
||||
// Flush cached variable
|
||||
m_indexR.store(indexR);
|
||||
|
||||
return numSamples;
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
unsigned int CMixer::Mix(short* samples, unsigned int num_samples, bool consider_framelimit)
|
||||
{
|
||||
if (!samples)
|
||||
return 0;
|
||||
if (!samples)
|
||||
return 0;
|
||||
|
||||
memset(samples, 0, num_samples * 2 * sizeof(short));
|
||||
memset(samples, 0, num_samples * 2 * sizeof(short));
|
||||
|
||||
m_dma_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
m_streaming_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
m_wiimote_speaker_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
return num_samples;
|
||||
m_dma_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
m_streaming_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
m_wiimote_speaker_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
return num_samples;
|
||||
}
|
||||
|
||||
void CMixer::MixerFifo::PushSamples(const short *samples, unsigned int num_samples)
|
||||
void CMixer::MixerFifo::PushSamples(const short* samples, unsigned int num_samples)
|
||||
{
|
||||
// Cache access in non-volatile variable
|
||||
// indexR isn't allowed to cache in the audio throttling loop as it
|
||||
// needs to get updates to not deadlock.
|
||||
u32 indexW = m_indexW.load();
|
||||
// Cache access in non-volatile variable
|
||||
// indexR isn't allowed to cache in the audio throttling loop as it
|
||||
// needs to get updates to not deadlock.
|
||||
u32 indexW = m_indexW.load();
|
||||
|
||||
// Check if we have enough free space
|
||||
// indexW == m_indexR results in empty buffer, so indexR must always be smaller than indexW
|
||||
if (num_samples * 2 + ((indexW - m_indexR.load()) & INDEX_MASK) >= MAX_SAMPLES * 2)
|
||||
return;
|
||||
// Check if we have enough free space
|
||||
// indexW == m_indexR results in empty buffer, so indexR must always be smaller than indexW
|
||||
if (num_samples * 2 + ((indexW - m_indexR.load()) & INDEX_MASK) >= MAX_SAMPLES * 2)
|
||||
return;
|
||||
|
||||
// AyuanX: Actual re-sampling work has been moved to sound thread
|
||||
// to alleviate the workload on main thread
|
||||
// and we simply store raw data here to make fast mem copy
|
||||
int over_bytes = num_samples * 4 - (MAX_SAMPLES * 2 - (indexW & INDEX_MASK)) * sizeof(short);
|
||||
if (over_bytes > 0)
|
||||
{
|
||||
memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * 4 - over_bytes);
|
||||
memcpy(&m_buffer[0], samples + (num_samples * 4 - over_bytes) / sizeof(short), over_bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * 4);
|
||||
}
|
||||
// AyuanX: Actual re-sampling work has been moved to sound thread
|
||||
// to alleviate the workload on main thread
|
||||
// and we simply store raw data here to make fast mem copy
|
||||
int over_bytes = num_samples * 4 - (MAX_SAMPLES * 2 - (indexW & INDEX_MASK)) * sizeof(short);
|
||||
if (over_bytes > 0)
|
||||
{
|
||||
memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * 4 - over_bytes);
|
||||
memcpy(&m_buffer[0], samples + (num_samples * 4 - over_bytes) / sizeof(short), over_bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * 4);
|
||||
}
|
||||
|
||||
m_indexW.fetch_add(num_samples * 2);
|
||||
m_indexW.fetch_add(num_samples * 2);
|
||||
}
|
||||
|
||||
void CMixer::PushSamples(const short *samples, unsigned int num_samples)
|
||||
void CMixer::PushSamples(const short* samples, unsigned int num_samples)
|
||||
{
|
||||
m_dma_mixer.PushSamples(samples, num_samples);
|
||||
if (m_log_dsp_audio)
|
||||
m_wave_writer_dsp.AddStereoSamplesBE(samples, num_samples);
|
||||
m_dma_mixer.PushSamples(samples, num_samples);
|
||||
if (m_log_dsp_audio)
|
||||
m_wave_writer_dsp.AddStereoSamplesBE(samples, num_samples);
|
||||
}
|
||||
|
||||
void CMixer::PushStreamingSamples(const short *samples, unsigned int num_samples)
|
||||
void CMixer::PushStreamingSamples(const short* samples, unsigned int num_samples)
|
||||
{
|
||||
m_streaming_mixer.PushSamples(samples, num_samples);
|
||||
if (m_log_dtk_audio)
|
||||
m_wave_writer_dtk.AddStereoSamplesBE(samples, num_samples);
|
||||
m_streaming_mixer.PushSamples(samples, num_samples);
|
||||
if (m_log_dtk_audio)
|
||||
m_wave_writer_dtk.AddStereoSamplesBE(samples, num_samples);
|
||||
}
|
||||
|
||||
void CMixer::PushWiimoteSpeakerSamples(const short *samples, unsigned int num_samples, unsigned int sample_rate)
|
||||
void CMixer::PushWiimoteSpeakerSamples(const short* samples, unsigned int num_samples,
|
||||
unsigned int sample_rate)
|
||||
{
|
||||
short samples_stereo[MAX_SAMPLES * 2];
|
||||
short samples_stereo[MAX_SAMPLES * 2];
|
||||
|
||||
if (num_samples < MAX_SAMPLES)
|
||||
{
|
||||
m_wiimote_speaker_mixer.SetInputSampleRate(sample_rate);
|
||||
if (num_samples < MAX_SAMPLES)
|
||||
{
|
||||
m_wiimote_speaker_mixer.SetInputSampleRate(sample_rate);
|
||||
|
||||
for (unsigned int i = 0; i < num_samples; ++i)
|
||||
{
|
||||
samples_stereo[i * 2] = Common::swap16(samples[i]);
|
||||
samples_stereo[i * 2 + 1] = Common::swap16(samples[i]);
|
||||
}
|
||||
for (unsigned int i = 0; i < num_samples; ++i)
|
||||
{
|
||||
samples_stereo[i * 2] = Common::swap16(samples[i]);
|
||||
samples_stereo[i * 2 + 1] = Common::swap16(samples[i]);
|
||||
}
|
||||
|
||||
m_wiimote_speaker_mixer.PushSamples(samples_stereo, num_samples);
|
||||
}
|
||||
m_wiimote_speaker_mixer.PushSamples(samples_stereo, num_samples);
|
||||
}
|
||||
}
|
||||
|
||||
void CMixer::SetDMAInputSampleRate(unsigned int rate)
|
||||
{
|
||||
m_dma_mixer.SetInputSampleRate(rate);
|
||||
m_dma_mixer.SetInputSampleRate(rate);
|
||||
}
|
||||
|
||||
void CMixer::SetStreamInputSampleRate(unsigned int rate)
|
||||
{
|
||||
m_streaming_mixer.SetInputSampleRate(rate);
|
||||
m_streaming_mixer.SetInputSampleRate(rate);
|
||||
}
|
||||
|
||||
void CMixer::SetStreamingVolume(unsigned int lvolume, unsigned int rvolume)
|
||||
{
|
||||
m_streaming_mixer.SetVolume(lvolume, rvolume);
|
||||
m_streaming_mixer.SetVolume(lvolume, rvolume);
|
||||
}
|
||||
|
||||
void CMixer::SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume)
|
||||
{
|
||||
m_wiimote_speaker_mixer.SetVolume(lvolume, rvolume);
|
||||
m_wiimote_speaker_mixer.SetVolume(lvolume, rvolume);
|
||||
}
|
||||
|
||||
void CMixer::StartLogDTKAudio(const std::string& filename)
|
||||
{
|
||||
if (!m_log_dtk_audio)
|
||||
{
|
||||
m_log_dtk_audio = true;
|
||||
m_wave_writer_dtk.Start(filename, 48000);
|
||||
m_wave_writer_dtk.SetSkipSilence(false);
|
||||
NOTICE_LOG(AUDIO, "Starting DTK Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "DTK Audio logging has already been started");
|
||||
}
|
||||
if (!m_log_dtk_audio)
|
||||
{
|
||||
m_log_dtk_audio = true;
|
||||
m_wave_writer_dtk.Start(filename, 48000);
|
||||
m_wave_writer_dtk.SetSkipSilence(false);
|
||||
NOTICE_LOG(AUDIO, "Starting DTK Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "DTK Audio logging has already been started");
|
||||
}
|
||||
}
|
||||
|
||||
void CMixer::StopLogDTKAudio()
|
||||
{
|
||||
if (m_log_dtk_audio)
|
||||
{
|
||||
m_log_dtk_audio = false;
|
||||
m_wave_writer_dtk.Stop();
|
||||
NOTICE_LOG(AUDIO, "Stopping DTK Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "DTK Audio logging has already been stopped");
|
||||
}
|
||||
if (m_log_dtk_audio)
|
||||
{
|
||||
m_log_dtk_audio = false;
|
||||
m_wave_writer_dtk.Stop();
|
||||
NOTICE_LOG(AUDIO, "Stopping DTK Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "DTK Audio logging has already been stopped");
|
||||
}
|
||||
}
|
||||
|
||||
void CMixer::StartLogDSPAudio(const std::string& filename)
|
||||
{
|
||||
if (!m_log_dsp_audio)
|
||||
{
|
||||
m_log_dsp_audio = true;
|
||||
m_wave_writer_dsp.Start(filename, 32000);
|
||||
m_wave_writer_dsp.SetSkipSilence(false);
|
||||
NOTICE_LOG(AUDIO, "Starting DSP Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "DSP Audio logging has already been started");
|
||||
}
|
||||
if (!m_log_dsp_audio)
|
||||
{
|
||||
m_log_dsp_audio = true;
|
||||
m_wave_writer_dsp.Start(filename, 32000);
|
||||
m_wave_writer_dsp.SetSkipSilence(false);
|
||||
NOTICE_LOG(AUDIO, "Starting DSP Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "DSP Audio logging has already been started");
|
||||
}
|
||||
}
|
||||
|
||||
void CMixer::StopLogDSPAudio()
|
||||
{
|
||||
if (m_log_dsp_audio)
|
||||
{
|
||||
m_log_dsp_audio = false;
|
||||
m_wave_writer_dsp.Stop();
|
||||
NOTICE_LOG(AUDIO, "Stopping DSP Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "DSP Audio logging has already been stopped");
|
||||
}
|
||||
if (m_log_dsp_audio)
|
||||
{
|
||||
m_log_dsp_audio = false;
|
||||
m_wave_writer_dsp.Stop();
|
||||
NOTICE_LOG(AUDIO, "Stopping DSP Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "DSP Audio logging has already been stopped");
|
||||
}
|
||||
}
|
||||
|
||||
void CMixer::MixerFifo::SetInputSampleRate(unsigned int rate)
|
||||
{
|
||||
m_input_sample_rate = rate;
|
||||
m_input_sample_rate = rate;
|
||||
}
|
||||
|
||||
void CMixer::MixerFifo::SetVolume(unsigned int lvolume, unsigned int rvolume)
|
||||
{
|
||||
m_LVolume.store(lvolume + (lvolume >> 7));
|
||||
m_RVolume.store(rvolume + (rvolume >> 7));
|
||||
m_LVolume.store(lvolume + (lvolume >> 7));
|
||||
m_RVolume.store(rvolume + (rvolume >> 7));
|
||||
}
|
||||
|
@ -13,74 +13,73 @@
|
||||
class CMixer final
|
||||
{
|
||||
public:
|
||||
explicit CMixer(unsigned int BackendSampleRate);
|
||||
~CMixer();
|
||||
explicit CMixer(unsigned int BackendSampleRate);
|
||||
~CMixer();
|
||||
|
||||
// Called from audio threads
|
||||
unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true);
|
||||
// Called from audio threads
|
||||
unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true);
|
||||
|
||||
// Called from main thread
|
||||
void PushSamples(const short* samples, unsigned int num_samples);
|
||||
void PushStreamingSamples(const short* samples, unsigned int num_samples);
|
||||
void PushWiimoteSpeakerSamples(const short* samples, unsigned int num_samples, unsigned int sample_rate);
|
||||
unsigned int GetSampleRate() const { return m_sampleRate; }
|
||||
// Called from main thread
|
||||
void PushSamples(const short* samples, unsigned int num_samples);
|
||||
void PushStreamingSamples(const short* samples, unsigned int num_samples);
|
||||
void PushWiimoteSpeakerSamples(const short* samples, unsigned int num_samples,
|
||||
unsigned int sample_rate);
|
||||
unsigned int GetSampleRate() const { return m_sampleRate; }
|
||||
void SetDMAInputSampleRate(unsigned int rate);
|
||||
void SetStreamInputSampleRate(unsigned int rate);
|
||||
void SetStreamingVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
void SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
|
||||
void SetDMAInputSampleRate(unsigned int rate);
|
||||
void SetStreamInputSampleRate(unsigned int rate);
|
||||
void SetStreamingVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
void SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
void StartLogDTKAudio(const std::string& filename);
|
||||
void StopLogDTKAudio();
|
||||
|
||||
void StartLogDTKAudio(const std::string& filename);
|
||||
void StopLogDTKAudio();
|
||||
|
||||
void StartLogDSPAudio(const std::string& filename);
|
||||
void StopLogDSPAudio();
|
||||
|
||||
float GetCurrentSpeed() const { return m_speed.load(); }
|
||||
void UpdateSpeed(float val) { m_speed.store(val); }
|
||||
void StartLogDSPAudio(const std::string& filename);
|
||||
void StopLogDSPAudio();
|
||||
|
||||
float GetCurrentSpeed() const { return m_speed.load(); }
|
||||
void UpdateSpeed(float val) { m_speed.store(val); }
|
||||
private:
|
||||
static constexpr u32 MAX_SAMPLES = 1024 * 4; // 128 ms
|
||||
static constexpr u32 INDEX_MASK = MAX_SAMPLES * 2 - 1;
|
||||
static constexpr int MAX_FREQ_SHIFT = 200; // Per 32000 Hz
|
||||
static constexpr float CONTROL_FACTOR = 0.2f;
|
||||
static constexpr u32 CONTROL_AVG = 32; // In freq_shift per FIFO size offset
|
||||
static constexpr u32 MAX_SAMPLES = 1024 * 4; // 128 ms
|
||||
static constexpr u32 INDEX_MASK = MAX_SAMPLES * 2 - 1;
|
||||
static constexpr int MAX_FREQ_SHIFT = 200; // Per 32000 Hz
|
||||
static constexpr float CONTROL_FACTOR = 0.2f;
|
||||
static constexpr u32 CONTROL_AVG = 32; // In freq_shift per FIFO size offset
|
||||
|
||||
class MixerFifo final
|
||||
{
|
||||
public:
|
||||
MixerFifo(CMixer* mixer, unsigned sample_rate)
|
||||
: m_mixer(mixer)
|
||||
, m_input_sample_rate(sample_rate)
|
||||
{
|
||||
}
|
||||
void PushSamples(const short* samples, unsigned int num_samples);
|
||||
unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true);
|
||||
void SetInputSampleRate(unsigned int rate);
|
||||
void SetVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
private:
|
||||
CMixer* m_mixer;
|
||||
unsigned m_input_sample_rate;
|
||||
std::array<short, MAX_SAMPLES * 2> m_buffer{};
|
||||
std::atomic<u32> m_indexW{0};
|
||||
std::atomic<u32> m_indexR{0};
|
||||
// Volume ranges from 0-256
|
||||
std::atomic<s32> m_LVolume{256};
|
||||
std::atomic<s32> m_RVolume{256};
|
||||
float m_numLeftI = 0.0f;
|
||||
u32 m_frac = 0;
|
||||
};
|
||||
MixerFifo m_dma_mixer{this, 32000};
|
||||
MixerFifo m_streaming_mixer{this, 48000};
|
||||
MixerFifo m_wiimote_speaker_mixer{this, 3000};
|
||||
unsigned int m_sampleRate;
|
||||
class MixerFifo final
|
||||
{
|
||||
public:
|
||||
MixerFifo(CMixer* mixer, unsigned sample_rate)
|
||||
: m_mixer(mixer), m_input_sample_rate(sample_rate)
|
||||
{
|
||||
}
|
||||
void PushSamples(const short* samples, unsigned int num_samples);
|
||||
unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true);
|
||||
void SetInputSampleRate(unsigned int rate);
|
||||
void SetVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
|
||||
WaveFileWriter m_wave_writer_dtk;
|
||||
WaveFileWriter m_wave_writer_dsp;
|
||||
private:
|
||||
CMixer* m_mixer;
|
||||
unsigned m_input_sample_rate;
|
||||
std::array<short, MAX_SAMPLES * 2> m_buffer{};
|
||||
std::atomic<u32> m_indexW{0};
|
||||
std::atomic<u32> m_indexR{0};
|
||||
// Volume ranges from 0-256
|
||||
std::atomic<s32> m_LVolume{256};
|
||||
std::atomic<s32> m_RVolume{256};
|
||||
float m_numLeftI = 0.0f;
|
||||
u32 m_frac = 0;
|
||||
};
|
||||
MixerFifo m_dma_mixer{this, 32000};
|
||||
MixerFifo m_streaming_mixer{this, 48000};
|
||||
MixerFifo m_wiimote_speaker_mixer{this, 3000};
|
||||
unsigned int m_sampleRate;
|
||||
|
||||
bool m_log_dtk_audio = false;
|
||||
bool m_log_dsp_audio = false;
|
||||
WaveFileWriter m_wave_writer_dtk;
|
||||
WaveFileWriter m_wave_writer_dsp;
|
||||
|
||||
// Current rate of emulation (1.0 = 100% speed)
|
||||
std::atomic<float> m_speed{0.0f};
|
||||
bool m_log_dtk_audio = false;
|
||||
bool m_log_dsp_audio = false;
|
||||
|
||||
// Current rate of emulation (1.0 = 100% speed)
|
||||
std::atomic<float> m_speed{0.0f};
|
||||
};
|
||||
|
@ -13,7 +13,7 @@ void NullSound::SoundLoop()
|
||||
|
||||
bool NullSound::Start()
|
||||
{
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void NullSound::SetVolume(int volume)
|
||||
@ -22,19 +22,22 @@ void NullSound::SetVolume(int volume)
|
||||
|
||||
void NullSound::Update()
|
||||
{
|
||||
// num_samples_to_render in this update - depends on SystemTimers::AUDIO_DMA_PERIOD.
|
||||
constexpr u32 stereo_16_bit_size = 4;
|
||||
constexpr u32 dma_length = 32;
|
||||
const u64 audio_dma_period = SystemTimers::GetTicksPerSecond() / (AudioInterface::GetAIDSampleRate() * stereo_16_bit_size / dma_length);
|
||||
const u64 ais_samples_per_second = 48000 * stereo_16_bit_size;
|
||||
const u64 num_samples_to_render = (audio_dma_period * ais_samples_per_second) / SystemTimers::GetTicksPerSecond();
|
||||
// num_samples_to_render in this update - depends on SystemTimers::AUDIO_DMA_PERIOD.
|
||||
constexpr u32 stereo_16_bit_size = 4;
|
||||
constexpr u32 dma_length = 32;
|
||||
const u64 audio_dma_period =
|
||||
SystemTimers::GetTicksPerSecond() /
|
||||
(AudioInterface::GetAIDSampleRate() * stereo_16_bit_size / dma_length);
|
||||
const u64 ais_samples_per_second = 48000 * stereo_16_bit_size;
|
||||
const u64 num_samples_to_render =
|
||||
(audio_dma_period * ais_samples_per_second) / SystemTimers::GetTicksPerSecond();
|
||||
|
||||
m_mixer->Mix(m_realtime_buffer.data(), (unsigned int)num_samples_to_render);
|
||||
m_mixer->Mix(m_realtime_buffer.data(), (unsigned int)num_samples_to_render);
|
||||
}
|
||||
|
||||
void NullSound::Clear(bool mute)
|
||||
{
|
||||
m_muted = mute;
|
||||
m_muted = mute;
|
||||
}
|
||||
|
||||
void NullSound::Stop()
|
||||
|
@ -10,18 +10,17 @@
|
||||
class NullSound final : public SoundStream
|
||||
{
|
||||
public:
|
||||
bool Start() override;
|
||||
void SoundLoop() override;
|
||||
void SetVolume(int volume) override;
|
||||
void Stop() override;
|
||||
void Clear(bool mute) override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid() { return true; }
|
||||
bool Start() override;
|
||||
void SoundLoop() override;
|
||||
void SetVolume(int volume) override;
|
||||
void Stop() override;
|
||||
void Clear(bool mute) override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid() { return true; }
|
||||
private:
|
||||
static constexpr size_t BUFFER_SIZE = 48000 * 4 / 32;
|
||||
static constexpr size_t BUFFER_SIZE = 48000 * 4 / 32;
|
||||
|
||||
// Playback position
|
||||
std::array<short, BUFFER_SIZE / sizeof(short)> m_realtime_buffer;
|
||||
// Playback position
|
||||
std::array<short, BUFFER_SIZE / sizeof(short)> m_realtime_buffer;
|
||||
};
|
||||
|
@ -5,11 +5,11 @@
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
#include "AudioCommon/aldlist.h"
|
||||
#include "AudioCommon/DPL2Decoder.h"
|
||||
#include "AudioCommon/OpenALStream.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "AudioCommon/aldlist.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#if defined HAVE_OPENAL && HAVE_OPENAL
|
||||
@ -25,351 +25,363 @@ static soundtouch::SoundTouch soundTouch;
|
||||
//
|
||||
bool OpenALStream::Start()
|
||||
{
|
||||
m_run_thread.store(true);
|
||||
bool bReturn = false;
|
||||
m_run_thread.store(true);
|
||||
bool bReturn = false;
|
||||
|
||||
ALDeviceList pDeviceList;
|
||||
if (pDeviceList.GetNumDevices())
|
||||
{
|
||||
char *defDevName = pDeviceList.GetDeviceName(pDeviceList.GetDefaultDevice());
|
||||
ALDeviceList pDeviceList;
|
||||
if (pDeviceList.GetNumDevices())
|
||||
{
|
||||
char* defDevName = pDeviceList.GetDeviceName(pDeviceList.GetDefaultDevice());
|
||||
|
||||
WARN_LOG(AUDIO, "Found OpenAL device %s", defDevName);
|
||||
WARN_LOG(AUDIO, "Found OpenAL device %s", defDevName);
|
||||
|
||||
ALCdevice *pDevice = alcOpenDevice(defDevName);
|
||||
if (pDevice)
|
||||
{
|
||||
ALCcontext *pContext = alcCreateContext(pDevice, nullptr);
|
||||
if (pContext)
|
||||
{
|
||||
// Used to determine an appropriate period size (2x period = total buffer size)
|
||||
//ALCint refresh;
|
||||
//alcGetIntegerv(pDevice, ALC_REFRESH, 1, &refresh);
|
||||
//period_size_in_millisec = 1000 / refresh;
|
||||
ALCdevice* pDevice = alcOpenDevice(defDevName);
|
||||
if (pDevice)
|
||||
{
|
||||
ALCcontext* pContext = alcCreateContext(pDevice, nullptr);
|
||||
if (pContext)
|
||||
{
|
||||
// Used to determine an appropriate period size (2x period = total buffer size)
|
||||
// ALCint refresh;
|
||||
// alcGetIntegerv(pDevice, ALC_REFRESH, 1, &refresh);
|
||||
// period_size_in_millisec = 1000 / refresh;
|
||||
|
||||
alcMakeContextCurrent(pContext);
|
||||
thread = std::thread(&OpenALStream::SoundLoop, this);
|
||||
bReturn = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
alcCloseDevice(pDevice);
|
||||
PanicAlertT("OpenAL: can't create context for device %s", defDevName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertT("OpenAL: can't open device %s", defDevName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertT("OpenAL: can't find sound devices");
|
||||
}
|
||||
alcMakeContextCurrent(pContext);
|
||||
thread = std::thread(&OpenALStream::SoundLoop, this);
|
||||
bReturn = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
alcCloseDevice(pDevice);
|
||||
PanicAlertT("OpenAL: can't create context for device %s", defDevName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertT("OpenAL: can't open device %s", defDevName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertT("OpenAL: can't find sound devices");
|
||||
}
|
||||
|
||||
// Initialize DPL2 parameters
|
||||
DPL2Reset();
|
||||
// Initialize DPL2 parameters
|
||||
DPL2Reset();
|
||||
|
||||
soundTouch.clear();
|
||||
return bReturn;
|
||||
soundTouch.clear();
|
||||
return bReturn;
|
||||
}
|
||||
|
||||
void OpenALStream::Stop()
|
||||
{
|
||||
m_run_thread.store(false);
|
||||
// kick the thread if it's waiting
|
||||
soundSyncEvent.Set();
|
||||
m_run_thread.store(false);
|
||||
// kick the thread if it's waiting
|
||||
soundSyncEvent.Set();
|
||||
|
||||
soundTouch.clear();
|
||||
soundTouch.clear();
|
||||
|
||||
thread.join();
|
||||
thread.join();
|
||||
|
||||
alSourceStop(uiSource);
|
||||
alSourcei(uiSource, AL_BUFFER, 0);
|
||||
alSourceStop(uiSource);
|
||||
alSourcei(uiSource, AL_BUFFER, 0);
|
||||
|
||||
// Clean up buffers and sources
|
||||
alDeleteSources(1, &uiSource);
|
||||
uiSource = 0;
|
||||
alDeleteBuffers(numBuffers, uiBuffers);
|
||||
// Clean up buffers and sources
|
||||
alDeleteSources(1, &uiSource);
|
||||
uiSource = 0;
|
||||
alDeleteBuffers(numBuffers, uiBuffers);
|
||||
|
||||
ALCcontext *pContext = alcGetCurrentContext();
|
||||
ALCdevice *pDevice = alcGetContextsDevice(pContext);
|
||||
ALCcontext* pContext = alcGetCurrentContext();
|
||||
ALCdevice* pDevice = alcGetContextsDevice(pContext);
|
||||
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(pContext);
|
||||
alcCloseDevice(pDevice);
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(pContext);
|
||||
alcCloseDevice(pDevice);
|
||||
}
|
||||
|
||||
void OpenALStream::SetVolume(int volume)
|
||||
{
|
||||
fVolume = (float)volume / 100.0f;
|
||||
fVolume = (float)volume / 100.0f;
|
||||
|
||||
if (uiSource)
|
||||
alSourcef(uiSource, AL_GAIN, fVolume);
|
||||
if (uiSource)
|
||||
alSourcef(uiSource, AL_GAIN, fVolume);
|
||||
}
|
||||
|
||||
void OpenALStream::Update()
|
||||
{
|
||||
soundSyncEvent.Set();
|
||||
soundSyncEvent.Set();
|
||||
}
|
||||
|
||||
void OpenALStream::Clear(bool mute)
|
||||
{
|
||||
m_muted = mute;
|
||||
m_muted = mute;
|
||||
|
||||
if (m_muted)
|
||||
{
|
||||
soundTouch.clear();
|
||||
alSourceStop(uiSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
alSourcePlay(uiSource);
|
||||
}
|
||||
if (m_muted)
|
||||
{
|
||||
soundTouch.clear();
|
||||
alSourceStop(uiSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
alSourcePlay(uiSource);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenALStream::SoundLoop()
|
||||
{
|
||||
Common::SetCurrentThreadName("Audio thread - openal");
|
||||
Common::SetCurrentThreadName("Audio thread - openal");
|
||||
|
||||
bool surround_capable = SConfig::GetInstance().bDPL2Decoder;
|
||||
bool surround_capable = SConfig::GetInstance().bDPL2Decoder;
|
||||
#if defined(__APPLE__)
|
||||
bool float32_capable = false;
|
||||
const ALenum AL_FORMAT_STEREO_FLOAT32 = 0;
|
||||
// OS X does not have the alext AL_FORMAT_51CHN32 yet.
|
||||
surround_capable = false;
|
||||
const ALenum AL_FORMAT_51CHN32 = 0;
|
||||
const ALenum AL_FORMAT_51CHN16 = 0;
|
||||
bool float32_capable = false;
|
||||
const ALenum AL_FORMAT_STEREO_FLOAT32 = 0;
|
||||
// OS X does not have the alext AL_FORMAT_51CHN32 yet.
|
||||
surround_capable = false;
|
||||
const ALenum AL_FORMAT_51CHN32 = 0;
|
||||
const ALenum AL_FORMAT_51CHN16 = 0;
|
||||
#else
|
||||
bool float32_capable = true;
|
||||
bool float32_capable = true;
|
||||
#endif
|
||||
|
||||
u32 ulFrequency = m_mixer->GetSampleRate();
|
||||
numBuffers = SConfig::GetInstance().iLatency + 2; // OpenAL requires a minimum of two buffers
|
||||
u32 ulFrequency = m_mixer->GetSampleRate();
|
||||
numBuffers = SConfig::GetInstance().iLatency + 2; // OpenAL requires a minimum of two buffers
|
||||
|
||||
memset(uiBuffers, 0, numBuffers * sizeof(ALuint));
|
||||
uiSource = 0;
|
||||
memset(uiBuffers, 0, numBuffers * sizeof(ALuint));
|
||||
uiSource = 0;
|
||||
|
||||
// Checks if a X-Fi is being used. If it is, disable FLOAT32 support as this sound card has no support for it even though it reports it does.
|
||||
if (strstr(alGetString(AL_RENDERER), "X-Fi"))
|
||||
float32_capable = false;
|
||||
// Checks if a X-Fi is being used. If it is, disable FLOAT32 support as this sound card has no
|
||||
// support for it even though it reports it does.
|
||||
if (strstr(alGetString(AL_RENDERER), "X-Fi"))
|
||||
float32_capable = false;
|
||||
|
||||
// Generate some AL Buffers for streaming
|
||||
alGenBuffers(numBuffers, (ALuint *)uiBuffers);
|
||||
// Generate a Source to playback the Buffers
|
||||
alGenSources(1, &uiSource);
|
||||
// Generate some AL Buffers for streaming
|
||||
alGenBuffers(numBuffers, (ALuint*)uiBuffers);
|
||||
// Generate a Source to playback the Buffers
|
||||
alGenSources(1, &uiSource);
|
||||
|
||||
// Short Silence
|
||||
if (float32_capable)
|
||||
memset(sampleBuffer, 0, OAL_MAX_SAMPLES * numBuffers * FRAME_SURROUND_FLOAT);
|
||||
else
|
||||
memset(sampleBuffer, 0, OAL_MAX_SAMPLES * numBuffers * FRAME_SURROUND_SHORT);
|
||||
// Short Silence
|
||||
if (float32_capable)
|
||||
memset(sampleBuffer, 0, OAL_MAX_SAMPLES * numBuffers * FRAME_SURROUND_FLOAT);
|
||||
else
|
||||
memset(sampleBuffer, 0, OAL_MAX_SAMPLES * numBuffers * FRAME_SURROUND_SHORT);
|
||||
|
||||
memset(realtimeBuffer, 0, OAL_MAX_SAMPLES * FRAME_STEREO_SHORT);
|
||||
memset(realtimeBuffer, 0, OAL_MAX_SAMPLES * FRAME_STEREO_SHORT);
|
||||
|
||||
for (int i = 0; i < numBuffers; i++)
|
||||
{
|
||||
if (surround_capable)
|
||||
{
|
||||
if (float32_capable)
|
||||
alBufferData(uiBuffers[i], AL_FORMAT_51CHN32, sampleBuffer, 4 * FRAME_SURROUND_FLOAT, ulFrequency);
|
||||
else
|
||||
alBufferData(uiBuffers[i], AL_FORMAT_51CHN16, sampleBuffer, 4 * FRAME_SURROUND_SHORT, ulFrequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
alBufferData(uiBuffers[i], AL_FORMAT_STEREO16, realtimeBuffer, 4 * FRAME_STEREO_SHORT, ulFrequency);
|
||||
}
|
||||
}
|
||||
alSourceQueueBuffers(uiSource, numBuffers, uiBuffers);
|
||||
alSourcePlay(uiSource);
|
||||
for (int i = 0; i < numBuffers; i++)
|
||||
{
|
||||
if (surround_capable)
|
||||
{
|
||||
if (float32_capable)
|
||||
alBufferData(uiBuffers[i], AL_FORMAT_51CHN32, sampleBuffer, 4 * FRAME_SURROUND_FLOAT,
|
||||
ulFrequency);
|
||||
else
|
||||
alBufferData(uiBuffers[i], AL_FORMAT_51CHN16, sampleBuffer, 4 * FRAME_SURROUND_SHORT,
|
||||
ulFrequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
alBufferData(uiBuffers[i], AL_FORMAT_STEREO16, realtimeBuffer, 4 * FRAME_STEREO_SHORT,
|
||||
ulFrequency);
|
||||
}
|
||||
}
|
||||
alSourceQueueBuffers(uiSource, numBuffers, uiBuffers);
|
||||
alSourcePlay(uiSource);
|
||||
|
||||
// Set the default sound volume as saved in the config file.
|
||||
alSourcef(uiSource, AL_GAIN, fVolume);
|
||||
// Set the default sound volume as saved in the config file.
|
||||
alSourcef(uiSource, AL_GAIN, fVolume);
|
||||
|
||||
// TODO: Error handling
|
||||
//ALenum err = alGetError();
|
||||
// TODO: Error handling
|
||||
// ALenum err = alGetError();
|
||||
|
||||
ALint iBuffersFilled = 0;
|
||||
ALint iBuffersProcessed = 0;
|
||||
ALint iState = 0;
|
||||
ALuint uiBufferTemp[OAL_MAX_BUFFERS] = { 0 };
|
||||
ALint iBuffersFilled = 0;
|
||||
ALint iBuffersProcessed = 0;
|
||||
ALint iState = 0;
|
||||
ALuint uiBufferTemp[OAL_MAX_BUFFERS] = {0};
|
||||
|
||||
soundTouch.setChannels(2);
|
||||
soundTouch.setSampleRate(ulFrequency);
|
||||
soundTouch.setTempo(1.0);
|
||||
soundTouch.setSetting(SETTING_USE_QUICKSEEK, 0);
|
||||
soundTouch.setSetting(SETTING_USE_AA_FILTER, 0);
|
||||
soundTouch.setSetting(SETTING_SEQUENCE_MS, 1);
|
||||
soundTouch.setSetting(SETTING_SEEKWINDOW_MS, 28);
|
||||
soundTouch.setSetting(SETTING_OVERLAP_MS, 12);
|
||||
soundTouch.setChannels(2);
|
||||
soundTouch.setSampleRate(ulFrequency);
|
||||
soundTouch.setTempo(1.0);
|
||||
soundTouch.setSetting(SETTING_USE_QUICKSEEK, 0);
|
||||
soundTouch.setSetting(SETTING_USE_AA_FILTER, 0);
|
||||
soundTouch.setSetting(SETTING_SEQUENCE_MS, 1);
|
||||
soundTouch.setSetting(SETTING_SEEKWINDOW_MS, 28);
|
||||
soundTouch.setSetting(SETTING_OVERLAP_MS, 12);
|
||||
|
||||
while (m_run_thread.load())
|
||||
{
|
||||
// num_samples_to_render in this update - depends on SystemTimers::AUDIO_DMA_PERIOD.
|
||||
const u32 stereo_16_bit_size = 4;
|
||||
const u32 dma_length = 32;
|
||||
const u64 ais_samples_per_second = 48000 * stereo_16_bit_size;
|
||||
u64 audio_dma_period = SystemTimers::GetTicksPerSecond() / (AudioInterface::GetAIDSampleRate() * stereo_16_bit_size / dma_length);
|
||||
u64 num_samples_to_render = (audio_dma_period * ais_samples_per_second) / SystemTimers::GetTicksPerSecond();
|
||||
while (m_run_thread.load())
|
||||
{
|
||||
// num_samples_to_render in this update - depends on SystemTimers::AUDIO_DMA_PERIOD.
|
||||
const u32 stereo_16_bit_size = 4;
|
||||
const u32 dma_length = 32;
|
||||
const u64 ais_samples_per_second = 48000 * stereo_16_bit_size;
|
||||
u64 audio_dma_period = SystemTimers::GetTicksPerSecond() /
|
||||
(AudioInterface::GetAIDSampleRate() * stereo_16_bit_size / dma_length);
|
||||
u64 num_samples_to_render =
|
||||
(audio_dma_period * ais_samples_per_second) / SystemTimers::GetTicksPerSecond();
|
||||
|
||||
unsigned int numSamples = (unsigned int)num_samples_to_render;
|
||||
unsigned int minSamples = surround_capable ? 240 : 0; // DPL2 accepts 240 samples minimum (FWRDURATION)
|
||||
unsigned int numSamples = (unsigned int)num_samples_to_render;
|
||||
unsigned int minSamples =
|
||||
surround_capable ? 240 : 0; // DPL2 accepts 240 samples minimum (FWRDURATION)
|
||||
|
||||
numSamples = (numSamples > OAL_MAX_SAMPLES) ? OAL_MAX_SAMPLES : numSamples;
|
||||
numSamples = m_mixer->Mix(realtimeBuffer, numSamples, false);
|
||||
numSamples = (numSamples > OAL_MAX_SAMPLES) ? OAL_MAX_SAMPLES : numSamples;
|
||||
numSamples = m_mixer->Mix(realtimeBuffer, numSamples, false);
|
||||
|
||||
// Convert the samples from short to float
|
||||
float dest[OAL_MAX_SAMPLES * STEREO_CHANNELS];
|
||||
for (u32 i = 0; i < numSamples * STEREO_CHANNELS; ++i)
|
||||
dest[i] = (float)realtimeBuffer[i] / (1 << 15);
|
||||
// Convert the samples from short to float
|
||||
float dest[OAL_MAX_SAMPLES * STEREO_CHANNELS];
|
||||
for (u32 i = 0; i < numSamples * STEREO_CHANNELS; ++i)
|
||||
dest[i] = (float)realtimeBuffer[i] / (1 << 15);
|
||||
|
||||
soundTouch.putSamples(dest, numSamples);
|
||||
soundTouch.putSamples(dest, numSamples);
|
||||
|
||||
if (iBuffersProcessed == iBuffersFilled)
|
||||
{
|
||||
alGetSourcei(uiSource, AL_BUFFERS_PROCESSED, &iBuffersProcessed);
|
||||
iBuffersFilled = 0;
|
||||
}
|
||||
if (iBuffersProcessed == iBuffersFilled)
|
||||
{
|
||||
alGetSourcei(uiSource, AL_BUFFERS_PROCESSED, &iBuffersProcessed);
|
||||
iBuffersFilled = 0;
|
||||
}
|
||||
|
||||
if (iBuffersProcessed)
|
||||
{
|
||||
double rate = (double)m_mixer->GetCurrentSpeed();
|
||||
if (rate <= 0)
|
||||
{
|
||||
Core::RequestRefreshInfo();
|
||||
rate = (double)m_mixer->GetCurrentSpeed();
|
||||
}
|
||||
if (iBuffersProcessed)
|
||||
{
|
||||
double rate = (double)m_mixer->GetCurrentSpeed();
|
||||
if (rate <= 0)
|
||||
{
|
||||
Core::RequestRefreshInfo();
|
||||
rate = (double)m_mixer->GetCurrentSpeed();
|
||||
}
|
||||
|
||||
// Place a lower limit of 10% speed. When a game boots up, there will be
|
||||
// many silence samples. These do not need to be timestretched.
|
||||
if (rate > 0.10)
|
||||
{
|
||||
soundTouch.setTempo(rate);
|
||||
if (rate > 10)
|
||||
{
|
||||
soundTouch.clear();
|
||||
}
|
||||
}
|
||||
// Place a lower limit of 10% speed. When a game boots up, there will be
|
||||
// many silence samples. These do not need to be timestretched.
|
||||
if (rate > 0.10)
|
||||
{
|
||||
soundTouch.setTempo(rate);
|
||||
if (rate > 10)
|
||||
{
|
||||
soundTouch.clear();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int nSamples = soundTouch.receiveSamples(sampleBuffer, OAL_MAX_SAMPLES * numBuffers);
|
||||
unsigned int nSamples = soundTouch.receiveSamples(sampleBuffer, OAL_MAX_SAMPLES * numBuffers);
|
||||
|
||||
if (nSamples <= minSamples)
|
||||
continue;
|
||||
if (nSamples <= minSamples)
|
||||
continue;
|
||||
|
||||
// Remove the Buffer from the Queue. (uiBuffer contains the Buffer ID for the unqueued Buffer)
|
||||
if (iBuffersFilled == 0)
|
||||
{
|
||||
alSourceUnqueueBuffers(uiSource, iBuffersProcessed, uiBufferTemp);
|
||||
ALenum err = alGetError();
|
||||
if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error unqueuing buffers: %08x", err);
|
||||
}
|
||||
}
|
||||
// Remove the Buffer from the Queue. (uiBuffer contains the Buffer ID for the unqueued
|
||||
// Buffer)
|
||||
if (iBuffersFilled == 0)
|
||||
{
|
||||
alSourceUnqueueBuffers(uiSource, iBuffersProcessed, uiBufferTemp);
|
||||
ALenum err = alGetError();
|
||||
if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error unqueuing buffers: %08x", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (surround_capable)
|
||||
{
|
||||
float dpl2[OAL_MAX_SAMPLES * OAL_MAX_BUFFERS * SURROUND_CHANNELS];
|
||||
DPL2Decode(sampleBuffer, nSamples, dpl2);
|
||||
if (surround_capable)
|
||||
{
|
||||
float dpl2[OAL_MAX_SAMPLES * OAL_MAX_BUFFERS * SURROUND_CHANNELS];
|
||||
DPL2Decode(sampleBuffer, nSamples, dpl2);
|
||||
|
||||
// zero-out the subwoofer channel - DPL2Decode generates a pretty
|
||||
// good 5.0 but not a good 5.1 output. Sadly there is not a 5.0
|
||||
// AL_FORMAT_50CHN32 to make this super-explicit.
|
||||
// DPL2Decode output: LEFTFRONT, RIGHTFRONT, CENTREFRONT, (sub), LEFTREAR, RIGHTREAR
|
||||
for (u32 i = 0; i < nSamples; ++i)
|
||||
{
|
||||
dpl2[i*SURROUND_CHANNELS + 3 /*sub/lfe*/] = 0.0f;
|
||||
}
|
||||
// zero-out the subwoofer channel - DPL2Decode generates a pretty
|
||||
// good 5.0 but not a good 5.1 output. Sadly there is not a 5.0
|
||||
// AL_FORMAT_50CHN32 to make this super-explicit.
|
||||
// DPL2Decode output: LEFTFRONT, RIGHTFRONT, CENTREFRONT, (sub), LEFTREAR, RIGHTREAR
|
||||
for (u32 i = 0; i < nSamples; ++i)
|
||||
{
|
||||
dpl2[i * SURROUND_CHANNELS + 3 /*sub/lfe*/] = 0.0f;
|
||||
}
|
||||
|
||||
if (float32_capable)
|
||||
{
|
||||
alBufferData(uiBufferTemp[iBuffersFilled], AL_FORMAT_51CHN32, dpl2, nSamples * FRAME_SURROUND_FLOAT, ulFrequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
short surround_short[OAL_MAX_SAMPLES * SURROUND_CHANNELS * OAL_MAX_BUFFERS];
|
||||
for (u32 i = 0; i < nSamples * SURROUND_CHANNELS; ++i)
|
||||
surround_short[i] = (short)((float)dpl2[i] * (1 << 15));
|
||||
if (float32_capable)
|
||||
{
|
||||
alBufferData(uiBufferTemp[iBuffersFilled], AL_FORMAT_51CHN32, dpl2,
|
||||
nSamples * FRAME_SURROUND_FLOAT, ulFrequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
short surround_short[OAL_MAX_SAMPLES * SURROUND_CHANNELS * OAL_MAX_BUFFERS];
|
||||
for (u32 i = 0; i < nSamples * SURROUND_CHANNELS; ++i)
|
||||
surround_short[i] = (short)((float)dpl2[i] * (1 << 15));
|
||||
|
||||
alBufferData(uiBufferTemp[iBuffersFilled], AL_FORMAT_51CHN16, surround_short, nSamples * FRAME_SURROUND_SHORT, ulFrequency);
|
||||
}
|
||||
alBufferData(uiBufferTemp[iBuffersFilled], AL_FORMAT_51CHN16, surround_short,
|
||||
nSamples * FRAME_SURROUND_SHORT, ulFrequency);
|
||||
}
|
||||
|
||||
ALenum err = alGetError();
|
||||
if (err == AL_INVALID_ENUM)
|
||||
{
|
||||
// 5.1 is not supported by the host, fallback to stereo
|
||||
WARN_LOG(AUDIO, "Unable to set 5.1 surround mode. Updating OpenAL Soft might fix this issue.");
|
||||
surround_capable = false;
|
||||
}
|
||||
else if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error occurred while buffering data: %08x", err);
|
||||
}
|
||||
}
|
||||
ALenum err = alGetError();
|
||||
if (err == AL_INVALID_ENUM)
|
||||
{
|
||||
// 5.1 is not supported by the host, fallback to stereo
|
||||
WARN_LOG(AUDIO,
|
||||
"Unable to set 5.1 surround mode. Updating OpenAL Soft might fix this issue.");
|
||||
surround_capable = false;
|
||||
}
|
||||
else if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error occurred while buffering data: %08x", err);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (float32_capable)
|
||||
{
|
||||
alBufferData(uiBufferTemp[iBuffersFilled], AL_FORMAT_STEREO_FLOAT32, sampleBuffer, nSamples * FRAME_STEREO_FLOAT, ulFrequency);
|
||||
ALenum err = alGetError();
|
||||
if (err == AL_INVALID_ENUM)
|
||||
{
|
||||
float32_capable = false;
|
||||
}
|
||||
else if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error occurred while buffering float32 data: %08x", err);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (float32_capable)
|
||||
{
|
||||
alBufferData(uiBufferTemp[iBuffersFilled], AL_FORMAT_STEREO_FLOAT32, sampleBuffer,
|
||||
nSamples * FRAME_STEREO_FLOAT, ulFrequency);
|
||||
ALenum err = alGetError();
|
||||
if (err == AL_INVALID_ENUM)
|
||||
{
|
||||
float32_capable = false;
|
||||
}
|
||||
else if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error occurred while buffering float32 data: %08x", err);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Convert the samples from float to short
|
||||
short stereo[OAL_MAX_SAMPLES * STEREO_CHANNELS * OAL_MAX_BUFFERS];
|
||||
for (u32 i = 0; i < nSamples * STEREO_CHANNELS; ++i)
|
||||
stereo[i] = (short)((float)sampleBuffer[i] * (1 << 15));
|
||||
else
|
||||
{
|
||||
// Convert the samples from float to short
|
||||
short stereo[OAL_MAX_SAMPLES * STEREO_CHANNELS * OAL_MAX_BUFFERS];
|
||||
for (u32 i = 0; i < nSamples * STEREO_CHANNELS; ++i)
|
||||
stereo[i] = (short)((float)sampleBuffer[i] * (1 << 15));
|
||||
|
||||
alBufferData(uiBufferTemp[iBuffersFilled], AL_FORMAT_STEREO16, stereo, nSamples * FRAME_STEREO_SHORT, ulFrequency);
|
||||
}
|
||||
}
|
||||
alBufferData(uiBufferTemp[iBuffersFilled], AL_FORMAT_STEREO16, stereo,
|
||||
nSamples * FRAME_STEREO_SHORT, ulFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
alSourceQueueBuffers(uiSource, 1, &uiBufferTemp[iBuffersFilled]);
|
||||
ALenum err = alGetError();
|
||||
if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error queuing buffers: %08x", err);
|
||||
}
|
||||
iBuffersFilled++;
|
||||
alSourceQueueBuffers(uiSource, 1, &uiBufferTemp[iBuffersFilled]);
|
||||
ALenum err = alGetError();
|
||||
if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error queuing buffers: %08x", err);
|
||||
}
|
||||
iBuffersFilled++;
|
||||
|
||||
if (iBuffersFilled == numBuffers)
|
||||
{
|
||||
alSourcePlay(uiSource);
|
||||
err = alGetError();
|
||||
if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error occurred during playback: %08x", err);
|
||||
}
|
||||
}
|
||||
if (iBuffersFilled == numBuffers)
|
||||
{
|
||||
alSourcePlay(uiSource);
|
||||
err = alGetError();
|
||||
if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error occurred during playback: %08x", err);
|
||||
}
|
||||
}
|
||||
|
||||
alGetSourcei(uiSource, AL_SOURCE_STATE, &iState);
|
||||
if (iState != AL_PLAYING)
|
||||
{
|
||||
// Buffer underrun occurred, resume playback
|
||||
alSourcePlay(uiSource);
|
||||
err = alGetError();
|
||||
if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error occurred resuming playback: %08x", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
soundSyncEvent.Wait();
|
||||
}
|
||||
}
|
||||
alGetSourcei(uiSource, AL_SOURCE_STATE, &iState);
|
||||
if (iState != AL_PLAYING)
|
||||
{
|
||||
// Buffer underrun occurred, resume playback
|
||||
alSourcePlay(uiSource);
|
||||
err = alGetError();
|
||||
if (err != 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error occurred resuming playback: %08x", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
soundSyncEvent.Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //HAVE_OPENAL
|
||||
|
||||
#endif // HAVE_OPENAL
|
||||
|
@ -32,56 +32,52 @@
|
||||
#define BOOL SoundTouch_BOOL
|
||||
#endif
|
||||
|
||||
#include <soundtouch/SoundTouch.h>
|
||||
#include <soundtouch/STTypes.h>
|
||||
#include <soundtouch/SoundTouch.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#undef BOOL
|
||||
#endif
|
||||
|
||||
// 16 bit Stereo
|
||||
#define SFX_MAX_SOURCE 1
|
||||
#define OAL_MAX_BUFFERS 32
|
||||
#define OAL_MAX_SAMPLES 256
|
||||
#define STEREO_CHANNELS 2
|
||||
#define SURROUND_CHANNELS 6 // number of channels in surround mode
|
||||
#define SIZE_SHORT 2
|
||||
#define SIZE_FLOAT 4 // size of a float in bytes
|
||||
#define FRAME_STEREO_SHORT STEREO_CHANNELS * SIZE_SHORT
|
||||
#define FRAME_STEREO_FLOAT STEREO_CHANNELS * SIZE_FLOAT
|
||||
#define FRAME_SURROUND_FLOAT SURROUND_CHANNELS * SIZE_FLOAT
|
||||
#define FRAME_SURROUND_SHORT SURROUND_CHANNELS * SIZE_SHORT
|
||||
#define SFX_MAX_SOURCE 1
|
||||
#define OAL_MAX_BUFFERS 32
|
||||
#define OAL_MAX_SAMPLES 256
|
||||
#define STEREO_CHANNELS 2
|
||||
#define SURROUND_CHANNELS 6 // number of channels in surround mode
|
||||
#define SIZE_SHORT 2
|
||||
#define SIZE_FLOAT 4 // size of a float in bytes
|
||||
#define FRAME_STEREO_SHORT STEREO_CHANNELS* SIZE_SHORT
|
||||
#define FRAME_STEREO_FLOAT STEREO_CHANNELS* SIZE_FLOAT
|
||||
#define FRAME_SURROUND_FLOAT SURROUND_CHANNELS* SIZE_FLOAT
|
||||
#define FRAME_SURROUND_SHORT SURROUND_CHANNELS* SIZE_SHORT
|
||||
#endif
|
||||
|
||||
class OpenALStream final : public SoundStream
|
||||
{
|
||||
#if defined HAVE_OPENAL && HAVE_OPENAL
|
||||
public:
|
||||
OpenALStream() : uiSource(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool Start() override;
|
||||
void SoundLoop() override;
|
||||
void SetVolume(int volume) override;
|
||||
void Stop() override;
|
||||
void Clear(bool mute) override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid() { return true; }
|
||||
OpenALStream() : uiSource(0) {}
|
||||
bool Start() override;
|
||||
void SoundLoop() override;
|
||||
void SetVolume(int volume) override;
|
||||
void Stop() override;
|
||||
void Clear(bool mute) override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid() { return true; }
|
||||
private:
|
||||
std::thread thread;
|
||||
std::atomic<bool> m_run_thread;
|
||||
std::thread thread;
|
||||
std::atomic<bool> m_run_thread;
|
||||
|
||||
Common::Event soundSyncEvent;
|
||||
Common::Event soundSyncEvent;
|
||||
|
||||
short realtimeBuffer[OAL_MAX_SAMPLES * STEREO_CHANNELS];
|
||||
soundtouch::SAMPLETYPE sampleBuffer[OAL_MAX_SAMPLES * SURROUND_CHANNELS * OAL_MAX_BUFFERS];
|
||||
ALuint uiBuffers[OAL_MAX_BUFFERS];
|
||||
ALuint uiSource;
|
||||
ALfloat fVolume;
|
||||
short realtimeBuffer[OAL_MAX_SAMPLES * STEREO_CHANNELS];
|
||||
soundtouch::SAMPLETYPE sampleBuffer[OAL_MAX_SAMPLES * SURROUND_CHANNELS * OAL_MAX_BUFFERS];
|
||||
ALuint uiBuffers[OAL_MAX_BUFFERS];
|
||||
ALuint uiSource;
|
||||
ALfloat fVolume;
|
||||
|
||||
u8 numBuffers;
|
||||
#endif // HAVE_OPENAL
|
||||
u8 numBuffers;
|
||||
#endif // HAVE_OPENAL
|
||||
};
|
||||
|
@ -24,7 +24,7 @@ static SLPlayItf bqPlayerPlay;
|
||||
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
|
||||
static SLMuteSoloItf bqPlayerMuteSolo;
|
||||
static SLVolumeItf bqPlayerVolume;
|
||||
static CMixer *g_mixer;
|
||||
static CMixer* g_mixer;
|
||||
#define BUFFER_SIZE 512
|
||||
#define BUFFER_SIZE_IN_SAMPLES (BUFFER_SIZE / 2)
|
||||
|
||||
@ -32,106 +32,107 @@ static CMixer *g_mixer;
|
||||
static short buffer[2][BUFFER_SIZE];
|
||||
static int curBuffer = 0;
|
||||
|
||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
||||
{
|
||||
assert(bq == bqPlayerBufferQueue);
|
||||
assert(nullptr == context);
|
||||
assert(bq == bqPlayerBufferQueue);
|
||||
assert(nullptr == context);
|
||||
|
||||
// Render to the fresh buffer
|
||||
g_mixer->Mix(reinterpret_cast<short *>(buffer[curBuffer]), BUFFER_SIZE_IN_SAMPLES);
|
||||
SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeof(buffer[0]));
|
||||
curBuffer ^= 1; // Switch buffer
|
||||
// Render to the fresh buffer
|
||||
g_mixer->Mix(reinterpret_cast<short*>(buffer[curBuffer]), BUFFER_SIZE_IN_SAMPLES);
|
||||
SLresult result =
|
||||
(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeof(buffer[0]));
|
||||
curBuffer ^= 1; // Switch buffer
|
||||
|
||||
// Comment from sample code:
|
||||
// the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
|
||||
// which for this code example would indicate a programming error
|
||||
_assert_msg_(AUDIO, SL_RESULT_SUCCESS == result, "Couldn't enqueue audio stream.");
|
||||
// Comment from sample code:
|
||||
// the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
|
||||
// which for this code example would indicate a programming error
|
||||
_assert_msg_(AUDIO, SL_RESULT_SUCCESS == result, "Couldn't enqueue audio stream.");
|
||||
}
|
||||
|
||||
bool OpenSLESStream::Start()
|
||||
{
|
||||
SLresult result;
|
||||
// create engine
|
||||
result = slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
SLresult result;
|
||||
// create engine
|
||||
result = slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
||||
SLDataFormat_PCM format_pcm = {
|
||||
SL_DATAFORMAT_PCM,
|
||||
2,
|
||||
m_mixer->GetSampleRate() * 1000,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
||||
SL_BYTEORDER_LITTLEENDIAN
|
||||
};
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
||||
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,
|
||||
2,
|
||||
m_mixer->GetSampleRate() * 1000,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
||||
SL_BYTEORDER_LITTLEENDIAN};
|
||||
|
||||
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
|
||||
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
|
||||
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
|
||||
SLDataSink audioSnk = {&loc_outmix, nullptr};
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
|
||||
SLDataSink audioSnk = {&loc_outmix, nullptr};
|
||||
|
||||
// create audio player
|
||||
const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
|
||||
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
||||
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
// create audio player
|
||||
const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
|
||||
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
||||
result =
|
||||
(*engineEngine)
|
||||
->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
|
||||
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
|
||||
&bqPlayerBufferQueue);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, nullptr);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result =
|
||||
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, nullptr);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
|
||||
// Render and enqueue a first buffer.
|
||||
curBuffer ^= 1;
|
||||
g_mixer = m_mixer.get();
|
||||
// Render and enqueue a first buffer.
|
||||
curBuffer ^= 1;
|
||||
g_mixer = m_mixer.get();
|
||||
|
||||
result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[0], sizeof(buffer[0]));
|
||||
if (SL_RESULT_SUCCESS != result)
|
||||
return false;
|
||||
result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[0], sizeof(buffer[0]));
|
||||
if (SL_RESULT_SUCCESS != result)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenSLESStream::Stop()
|
||||
{
|
||||
if (bqPlayerObject != nullptr)
|
||||
{
|
||||
(*bqPlayerObject)->Destroy(bqPlayerObject);
|
||||
bqPlayerObject = nullptr;
|
||||
bqPlayerPlay = nullptr;
|
||||
bqPlayerBufferQueue = nullptr;
|
||||
bqPlayerMuteSolo = nullptr;
|
||||
bqPlayerVolume = nullptr;
|
||||
}
|
||||
if (bqPlayerObject != nullptr)
|
||||
{
|
||||
(*bqPlayerObject)->Destroy(bqPlayerObject);
|
||||
bqPlayerObject = nullptr;
|
||||
bqPlayerPlay = nullptr;
|
||||
bqPlayerBufferQueue = nullptr;
|
||||
bqPlayerMuteSolo = nullptr;
|
||||
bqPlayerVolume = nullptr;
|
||||
}
|
||||
|
||||
if (outputMixObject != nullptr)
|
||||
{
|
||||
(*outputMixObject)->Destroy(outputMixObject);
|
||||
outputMixObject = nullptr;
|
||||
}
|
||||
if (outputMixObject != nullptr)
|
||||
{
|
||||
(*outputMixObject)->Destroy(outputMixObject);
|
||||
outputMixObject = nullptr;
|
||||
}
|
||||
|
||||
if (engineObject != nullptr)
|
||||
{
|
||||
(*engineObject)->Destroy(engineObject);
|
||||
engineObject = nullptr;
|
||||
engineEngine = nullptr;
|
||||
}
|
||||
if (engineObject != nullptr)
|
||||
{
|
||||
(*engineObject)->Destroy(engineObject);
|
||||
engineObject = nullptr;
|
||||
engineEngine = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -13,12 +13,11 @@ class OpenSLESStream final : public SoundStream
|
||||
{
|
||||
#ifdef ANDROID
|
||||
public:
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
static bool isValid() { return true; }
|
||||
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
static bool isValid() { return true; }
|
||||
private:
|
||||
std::thread thread;
|
||||
Common::Event soundSyncEvent;
|
||||
#endif // HAVE_OPENSL
|
||||
std::thread thread;
|
||||
Common::Event soundSyncEvent;
|
||||
#endif // HAVE_OPENSL
|
||||
};
|
||||
|
@ -7,245 +7,247 @@
|
||||
#include "AudioCommon/DPL2Decoder.h"
|
||||
#include "AudioCommon/PulseAudioStream.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const size_t BUFFER_SAMPLES = 512; // ~10 ms - needs to be at least 240 for surround
|
||||
const size_t BUFFER_SAMPLES = 512; // ~10 ms - needs to be at least 240 for surround
|
||||
}
|
||||
|
||||
PulseAudio::PulseAudio()
|
||||
: m_thread()
|
||||
, m_run_thread()
|
||||
PulseAudio::PulseAudio() : m_thread(), m_run_thread()
|
||||
{
|
||||
}
|
||||
|
||||
bool PulseAudio::Start()
|
||||
{
|
||||
m_stereo = !SConfig::GetInstance().bDPL2Decoder;
|
||||
m_channels = m_stereo ? 2 : 5; // will tell PA we use a Stereo or 5.0 channel setup
|
||||
m_stereo = !SConfig::GetInstance().bDPL2Decoder;
|
||||
m_channels = m_stereo ? 2 : 5; // will tell PA we use a Stereo or 5.0 channel setup
|
||||
|
||||
NOTICE_LOG(AUDIO, "PulseAudio backend using %d channels", m_channels);
|
||||
NOTICE_LOG(AUDIO, "PulseAudio backend using %d channels", m_channels);
|
||||
|
||||
m_run_thread = true;
|
||||
m_thread = std::thread(&PulseAudio::SoundLoop, this);
|
||||
m_run_thread = true;
|
||||
m_thread = std::thread(&PulseAudio::SoundLoop, this);
|
||||
|
||||
// Initialize DPL2 parameters
|
||||
DPL2Reset();
|
||||
// Initialize DPL2 parameters
|
||||
DPL2Reset();
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PulseAudio::Stop()
|
||||
{
|
||||
m_run_thread = false;
|
||||
m_thread.join();
|
||||
m_run_thread = false;
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
void PulseAudio::Update()
|
||||
{
|
||||
// don't need to do anything here.
|
||||
// don't need to do anything here.
|
||||
}
|
||||
|
||||
// Called on audio thread.
|
||||
void PulseAudio::SoundLoop()
|
||||
{
|
||||
Common::SetCurrentThreadName("Audio thread - pulse");
|
||||
Common::SetCurrentThreadName("Audio thread - pulse");
|
||||
|
||||
if (PulseInit())
|
||||
{
|
||||
while (m_run_thread.load() && m_pa_connected == 1 && m_pa_error >= 0)
|
||||
m_pa_error = pa_mainloop_iterate(m_pa_ml, 1, nullptr);
|
||||
if (PulseInit())
|
||||
{
|
||||
while (m_run_thread.load() && m_pa_connected == 1 && m_pa_error >= 0)
|
||||
m_pa_error = pa_mainloop_iterate(m_pa_ml, 1, nullptr);
|
||||
|
||||
if (m_pa_error < 0)
|
||||
ERROR_LOG(AUDIO, "PulseAudio error: %s", pa_strerror(m_pa_error));
|
||||
if (m_pa_error < 0)
|
||||
ERROR_LOG(AUDIO, "PulseAudio error: %s", pa_strerror(m_pa_error));
|
||||
|
||||
PulseShutdown();
|
||||
}
|
||||
PulseShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
bool PulseAudio::PulseInit()
|
||||
{
|
||||
m_pa_error = 0;
|
||||
m_pa_connected = 0;
|
||||
m_pa_error = 0;
|
||||
m_pa_connected = 0;
|
||||
|
||||
// create pulseaudio main loop and context
|
||||
// also register the async state callback which is called when the connection to the pa server has changed
|
||||
m_pa_ml = pa_mainloop_new();
|
||||
m_pa_mlapi = pa_mainloop_get_api(m_pa_ml);
|
||||
m_pa_ctx = pa_context_new(m_pa_mlapi, "dolphin-emu");
|
||||
m_pa_error = pa_context_connect(m_pa_ctx, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
|
||||
pa_context_set_state_callback(m_pa_ctx, StateCallback, this);
|
||||
// create pulseaudio main loop and context
|
||||
// also register the async state callback which is called when the connection to the pa server has
|
||||
// changed
|
||||
m_pa_ml = pa_mainloop_new();
|
||||
m_pa_mlapi = pa_mainloop_get_api(m_pa_ml);
|
||||
m_pa_ctx = pa_context_new(m_pa_mlapi, "dolphin-emu");
|
||||
m_pa_error = pa_context_connect(m_pa_ctx, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
|
||||
pa_context_set_state_callback(m_pa_ctx, StateCallback, this);
|
||||
|
||||
// wait until we're connected to the pulseaudio server
|
||||
while (m_pa_connected == 0 && m_pa_error >= 0)
|
||||
m_pa_error = pa_mainloop_iterate(m_pa_ml, 1, nullptr);
|
||||
// wait until we're connected to the pulseaudio server
|
||||
while (m_pa_connected == 0 && m_pa_error >= 0)
|
||||
m_pa_error = pa_mainloop_iterate(m_pa_ml, 1, nullptr);
|
||||
|
||||
if (m_pa_connected == 2 || m_pa_error < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "PulseAudio failed to initialize: %s", pa_strerror(m_pa_error));
|
||||
return false;
|
||||
}
|
||||
if (m_pa_connected == 2 || m_pa_error < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "PulseAudio failed to initialize: %s", pa_strerror(m_pa_error));
|
||||
return false;
|
||||
}
|
||||
|
||||
// create a new audio stream with our sample format
|
||||
// also connect the callbacks for this stream
|
||||
pa_sample_spec ss;
|
||||
pa_channel_map channel_map;
|
||||
pa_channel_map* channel_map_p = nullptr; // auto channel map
|
||||
if (m_stereo)
|
||||
{
|
||||
ss.format = PA_SAMPLE_S16LE;
|
||||
m_bytespersample = sizeof(s16);
|
||||
}
|
||||
else
|
||||
{
|
||||
// surround is remixed in floats, use a float PA buffer to save another conversion
|
||||
ss.format = PA_SAMPLE_FLOAT32NE;
|
||||
m_bytespersample = sizeof(float);
|
||||
// create a new audio stream with our sample format
|
||||
// also connect the callbacks for this stream
|
||||
pa_sample_spec ss;
|
||||
pa_channel_map channel_map;
|
||||
pa_channel_map* channel_map_p = nullptr; // auto channel map
|
||||
if (m_stereo)
|
||||
{
|
||||
ss.format = PA_SAMPLE_S16LE;
|
||||
m_bytespersample = sizeof(s16);
|
||||
}
|
||||
else
|
||||
{
|
||||
// surround is remixed in floats, use a float PA buffer to save another conversion
|
||||
ss.format = PA_SAMPLE_FLOAT32NE;
|
||||
m_bytespersample = sizeof(float);
|
||||
|
||||
channel_map_p = &channel_map; // explicit channel map:
|
||||
channel_map.channels = 5;
|
||||
channel_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
|
||||
channel_map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
|
||||
channel_map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;
|
||||
channel_map.map[3] = PA_CHANNEL_POSITION_REAR_LEFT;
|
||||
channel_map.map[4] = PA_CHANNEL_POSITION_REAR_RIGHT;
|
||||
}
|
||||
ss.channels = m_channels;
|
||||
ss.rate = m_mixer->GetSampleRate();
|
||||
assert(pa_sample_spec_valid(&ss));
|
||||
m_pa_s = pa_stream_new(m_pa_ctx, "Playback", &ss, channel_map_p);
|
||||
pa_stream_set_write_callback(m_pa_s, WriteCallback, this);
|
||||
pa_stream_set_underflow_callback(m_pa_s, UnderflowCallback, this);
|
||||
channel_map_p = &channel_map; // explicit channel map:
|
||||
channel_map.channels = 5;
|
||||
channel_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
|
||||
channel_map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
|
||||
channel_map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;
|
||||
channel_map.map[3] = PA_CHANNEL_POSITION_REAR_LEFT;
|
||||
channel_map.map[4] = PA_CHANNEL_POSITION_REAR_RIGHT;
|
||||
}
|
||||
ss.channels = m_channels;
|
||||
ss.rate = m_mixer->GetSampleRate();
|
||||
assert(pa_sample_spec_valid(&ss));
|
||||
m_pa_s = pa_stream_new(m_pa_ctx, "Playback", &ss, channel_map_p);
|
||||
pa_stream_set_write_callback(m_pa_s, WriteCallback, this);
|
||||
pa_stream_set_underflow_callback(m_pa_s, UnderflowCallback, this);
|
||||
|
||||
// connect this audio stream to the default audio playback
|
||||
// limit buffersize to reduce latency
|
||||
m_pa_ba.fragsize = -1;
|
||||
m_pa_ba.maxlength = -1; // max buffer, so also max latency
|
||||
m_pa_ba.minreq = -1; // don't read every byte, try to group them _a bit_
|
||||
m_pa_ba.prebuf = -1; // start as early as possible
|
||||
m_pa_ba.tlength = BUFFER_SAMPLES * m_channels * m_bytespersample; // designed latency, only change this flag for low latency output
|
||||
pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE);
|
||||
m_pa_error = pa_stream_connect_playback(m_pa_s, nullptr, &m_pa_ba, flags, nullptr, nullptr);
|
||||
if (m_pa_error < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "PulseAudio failed to initialize: %s", pa_strerror(m_pa_error));
|
||||
return false;
|
||||
}
|
||||
// connect this audio stream to the default audio playback
|
||||
// limit buffersize to reduce latency
|
||||
m_pa_ba.fragsize = -1;
|
||||
m_pa_ba.maxlength = -1; // max buffer, so also max latency
|
||||
m_pa_ba.minreq = -1; // don't read every byte, try to group them _a bit_
|
||||
m_pa_ba.prebuf = -1; // start as early as possible
|
||||
m_pa_ba.tlength =
|
||||
BUFFER_SAMPLES * m_channels *
|
||||
m_bytespersample; // designed latency, only change this flag for low latency output
|
||||
pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY |
|
||||
PA_STREAM_AUTO_TIMING_UPDATE);
|
||||
m_pa_error = pa_stream_connect_playback(m_pa_s, nullptr, &m_pa_ba, flags, nullptr, nullptr);
|
||||
if (m_pa_error < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "PulseAudio failed to initialize: %s", pa_strerror(m_pa_error));
|
||||
return false;
|
||||
}
|
||||
|
||||
INFO_LOG(AUDIO, "Pulse successfully initialized");
|
||||
return true;
|
||||
INFO_LOG(AUDIO, "Pulse successfully initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
void PulseAudio::PulseShutdown()
|
||||
{
|
||||
pa_context_disconnect(m_pa_ctx);
|
||||
pa_context_unref(m_pa_ctx);
|
||||
pa_mainloop_free(m_pa_ml);
|
||||
pa_context_disconnect(m_pa_ctx);
|
||||
pa_context_unref(m_pa_ctx);
|
||||
pa_mainloop_free(m_pa_ml);
|
||||
}
|
||||
|
||||
void PulseAudio::StateCallback(pa_context* c)
|
||||
{
|
||||
pa_context_state_t state = pa_context_get_state(c);
|
||||
switch (state)
|
||||
{
|
||||
case PA_CONTEXT_FAILED:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
m_pa_connected = 2;
|
||||
break;
|
||||
case PA_CONTEXT_READY:
|
||||
m_pa_connected = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
pa_context_state_t state = pa_context_get_state(c);
|
||||
switch (state)
|
||||
{
|
||||
case PA_CONTEXT_FAILED:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
m_pa_connected = 2;
|
||||
break;
|
||||
case PA_CONTEXT_READY:
|
||||
m_pa_connected = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// on underflow, increase pulseaudio latency in ~10ms steps
|
||||
void PulseAudio::UnderflowCallback(pa_stream* s)
|
||||
{
|
||||
m_pa_ba.tlength += BUFFER_SAMPLES * m_channels * m_bytespersample;
|
||||
pa_operation* op = pa_stream_set_buffer_attr(s, &m_pa_ba, nullptr, nullptr);
|
||||
pa_operation_unref(op);
|
||||
m_pa_ba.tlength += BUFFER_SAMPLES * m_channels * m_bytespersample;
|
||||
pa_operation* op = pa_stream_set_buffer_attr(s, &m_pa_ba, nullptr, nullptr);
|
||||
pa_operation_unref(op);
|
||||
|
||||
WARN_LOG(AUDIO, "pulseaudio underflow, new latency: %d bytes", m_pa_ba.tlength);
|
||||
WARN_LOG(AUDIO, "pulseaudio underflow, new latency: %d bytes", m_pa_ba.tlength);
|
||||
}
|
||||
|
||||
void PulseAudio::WriteCallback(pa_stream* s, size_t length)
|
||||
{
|
||||
int bytes_per_frame = m_channels * m_bytespersample;
|
||||
int frames = (length / bytes_per_frame);
|
||||
size_t trunc_length = frames * bytes_per_frame;
|
||||
int bytes_per_frame = m_channels * m_bytespersample;
|
||||
int frames = (length / bytes_per_frame);
|
||||
size_t trunc_length = frames * bytes_per_frame;
|
||||
|
||||
// fetch dst buffer directly from pulseaudio, so no memcpy is needed
|
||||
void* buffer;
|
||||
m_pa_error = pa_stream_begin_write(s, &buffer, &trunc_length);
|
||||
// fetch dst buffer directly from pulseaudio, so no memcpy is needed
|
||||
void* buffer;
|
||||
m_pa_error = pa_stream_begin_write(s, &buffer, &trunc_length);
|
||||
|
||||
if (!buffer || m_pa_error < 0)
|
||||
return; // error will be printed from main loop
|
||||
if (!buffer || m_pa_error < 0)
|
||||
return; // error will be printed from main loop
|
||||
|
||||
if (m_stereo)
|
||||
{
|
||||
// use the raw s16 stereo mix
|
||||
m_mixer->Mix((s16*) buffer, frames);
|
||||
}
|
||||
else
|
||||
{
|
||||
// get a floating point mix
|
||||
s16 s16buffer_stereo[frames * 2];
|
||||
m_mixer->Mix(s16buffer_stereo, frames); // implicitly mixes to 16-bit stereo
|
||||
if (m_stereo)
|
||||
{
|
||||
// use the raw s16 stereo mix
|
||||
m_mixer->Mix((s16*)buffer, frames);
|
||||
}
|
||||
else
|
||||
{
|
||||
// get a floating point mix
|
||||
s16 s16buffer_stereo[frames * 2];
|
||||
m_mixer->Mix(s16buffer_stereo, frames); // implicitly mixes to 16-bit stereo
|
||||
|
||||
float floatbuffer_stereo[frames * 2];
|
||||
// s16 to float
|
||||
for (int i=0; i < frames * 2; ++i)
|
||||
{
|
||||
floatbuffer_stereo[i] = s16buffer_stereo[i] / float(1 << 15);
|
||||
}
|
||||
float floatbuffer_stereo[frames * 2];
|
||||
// s16 to float
|
||||
for (int i = 0; i < frames * 2; ++i)
|
||||
{
|
||||
floatbuffer_stereo[i] = s16buffer_stereo[i] / float(1 << 15);
|
||||
}
|
||||
|
||||
if (m_channels == 5) // Extract dpl2/5.0 Surround
|
||||
{
|
||||
float floatbuffer_6chan[frames * 6];
|
||||
// DPL2Decode output: LEFTFRONT, RIGHTFRONT, CENTREFRONT, (sub), LEFTREAR, RIGHTREAR
|
||||
DPL2Decode(floatbuffer_stereo, frames, floatbuffer_6chan);
|
||||
if (m_channels == 5) // Extract dpl2/5.0 Surround
|
||||
{
|
||||
float floatbuffer_6chan[frames * 6];
|
||||
// DPL2Decode output: LEFTFRONT, RIGHTFRONT, CENTREFRONT, (sub), LEFTREAR, RIGHTREAR
|
||||
DPL2Decode(floatbuffer_stereo, frames, floatbuffer_6chan);
|
||||
|
||||
// Discard the subwoofer channel - DPL2Decode generates a pretty
|
||||
// good 5.0 but not a good 5.1 output.
|
||||
const int dpl2_to_5chan[] = {0,1,2,4,5};
|
||||
for (int i=0; i < frames; ++i)
|
||||
{
|
||||
for (int j=0; j < m_channels; ++j)
|
||||
{
|
||||
((float*)buffer)[m_channels * i + j] = floatbuffer_6chan[6 * i + dpl2_to_5chan[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Unsupported number of PA channels requested: %d", (int)m_channels);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Discard the subwoofer channel - DPL2Decode generates a pretty
|
||||
// good 5.0 but not a good 5.1 output.
|
||||
const int dpl2_to_5chan[] = {0, 1, 2, 4, 5};
|
||||
for (int i = 0; i < frames; ++i)
|
||||
{
|
||||
for (int j = 0; j < m_channels; ++j)
|
||||
{
|
||||
((float*)buffer)[m_channels * i + j] = floatbuffer_6chan[6 * i + dpl2_to_5chan[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Unsupported number of PA channels requested: %d", (int)m_channels);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_pa_error = pa_stream_write(s, buffer, trunc_length, nullptr, 0, PA_SEEK_RELATIVE);
|
||||
m_pa_error = pa_stream_write(s, buffer, trunc_length, nullptr, 0, PA_SEEK_RELATIVE);
|
||||
}
|
||||
|
||||
// Callbacks that forward to internal methods (required because PulseAudio is a C API).
|
||||
|
||||
void PulseAudio::StateCallback(pa_context* c, void* userdata)
|
||||
{
|
||||
PulseAudio* p = (PulseAudio*) userdata;
|
||||
p->StateCallback(c);
|
||||
PulseAudio* p = (PulseAudio*)userdata;
|
||||
p->StateCallback(c);
|
||||
}
|
||||
|
||||
void PulseAudio::UnderflowCallback(pa_stream* s, void* userdata)
|
||||
{
|
||||
PulseAudio* p = (PulseAudio*) userdata;
|
||||
p->UnderflowCallback(s);
|
||||
PulseAudio* p = (PulseAudio*)userdata;
|
||||
p->UnderflowCallback(s);
|
||||
}
|
||||
|
||||
void PulseAudio::WriteCallback(pa_stream* s, size_t length, void* userdata)
|
||||
{
|
||||
PulseAudio* p = (PulseAudio*) userdata;
|
||||
p->WriteCallback(s, length);
|
||||
PulseAudio* p = (PulseAudio*)userdata;
|
||||
p->WriteCallback(s, length);
|
||||
}
|
||||
|
@ -18,42 +18,41 @@ class PulseAudio final : public SoundStream
|
||||
{
|
||||
#if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO
|
||||
public:
|
||||
PulseAudio();
|
||||
PulseAudio();
|
||||
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid() { return true; }
|
||||
|
||||
void StateCallback(pa_context *c);
|
||||
void WriteCallback(pa_stream *s, size_t length);
|
||||
void UnderflowCallback(pa_stream *s);
|
||||
static bool isValid() { return true; }
|
||||
void StateCallback(pa_context* c);
|
||||
void WriteCallback(pa_stream* s, size_t length);
|
||||
void UnderflowCallback(pa_stream* s);
|
||||
|
||||
private:
|
||||
void SoundLoop() override;
|
||||
void SoundLoop() override;
|
||||
|
||||
bool PulseInit();
|
||||
void PulseShutdown();
|
||||
bool PulseInit();
|
||||
void PulseShutdown();
|
||||
|
||||
// wrapper callback functions, last parameter _must_ be PulseAudio*
|
||||
static void StateCallback(pa_context *c, void *userdata);
|
||||
static void WriteCallback(pa_stream *s, size_t length, void *userdata);
|
||||
static void UnderflowCallback(pa_stream *s, void *userdata);
|
||||
// wrapper callback functions, last parameter _must_ be PulseAudio*
|
||||
static void StateCallback(pa_context* c, void* userdata);
|
||||
static void WriteCallback(pa_stream* s, size_t length, void* userdata);
|
||||
static void UnderflowCallback(pa_stream* s, void* userdata);
|
||||
|
||||
std::thread m_thread;
|
||||
std::atomic<bool> m_run_thread;
|
||||
std::thread m_thread;
|
||||
std::atomic<bool> m_run_thread;
|
||||
|
||||
bool m_stereo; // stereo, else surround
|
||||
int m_bytespersample;
|
||||
int m_channels;
|
||||
bool m_stereo; // stereo, else surround
|
||||
int m_bytespersample;
|
||||
int m_channels;
|
||||
|
||||
int m_pa_error;
|
||||
int m_pa_connected;
|
||||
pa_mainloop *m_pa_ml;
|
||||
pa_mainloop_api *m_pa_mlapi;
|
||||
pa_context *m_pa_ctx;
|
||||
pa_stream *m_pa_s;
|
||||
pa_buffer_attr m_pa_ba;
|
||||
int m_pa_error;
|
||||
int m_pa_connected;
|
||||
pa_mainloop* m_pa_ml;
|
||||
pa_mainloop_api* m_pa_mlapi;
|
||||
pa_context* m_pa_ctx;
|
||||
pa_stream* m_pa_s;
|
||||
pa_buffer_attr m_pa_ba;
|
||||
#endif
|
||||
};
|
||||
|
@ -14,51 +14,49 @@
|
||||
class SoundStream
|
||||
{
|
||||
protected:
|
||||
std::unique_ptr<CMixer> m_mixer;
|
||||
bool m_logAudio;
|
||||
WaveFileWriter g_wave_writer;
|
||||
bool m_muted;
|
||||
std::unique_ptr<CMixer> m_mixer;
|
||||
bool m_logAudio;
|
||||
WaveFileWriter g_wave_writer;
|
||||
bool m_muted;
|
||||
|
||||
public:
|
||||
SoundStream() : m_mixer(new CMixer(48000)), m_logAudio(false), m_muted(false) {}
|
||||
virtual ~SoundStream() { }
|
||||
SoundStream() : m_mixer(new CMixer(48000)), m_logAudio(false), m_muted(false) {}
|
||||
virtual ~SoundStream() {}
|
||||
static bool isValid() { return false; }
|
||||
CMixer* GetMixer() const { return m_mixer.get(); }
|
||||
virtual bool Start() { return false; }
|
||||
virtual void SetVolume(int) {}
|
||||
virtual void SoundLoop() {}
|
||||
virtual void Stop() {}
|
||||
virtual void Update() {}
|
||||
virtual void Clear(bool mute) { m_muted = mute; }
|
||||
bool IsMuted() const { return m_muted; }
|
||||
void StartLogAudio(const std::string& filename)
|
||||
{
|
||||
if (!m_logAudio)
|
||||
{
|
||||
m_logAudio = true;
|
||||
g_wave_writer.Start(filename, m_mixer->GetSampleRate());
|
||||
g_wave_writer.SetSkipSilence(false);
|
||||
NOTICE_LOG(AUDIO, "Starting Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "Audio logging already started");
|
||||
}
|
||||
}
|
||||
|
||||
static bool isValid() { return false; }
|
||||
CMixer* GetMixer() const { return m_mixer.get(); }
|
||||
virtual bool Start() { return false; }
|
||||
virtual void SetVolume(int) {}
|
||||
virtual void SoundLoop() {}
|
||||
virtual void Stop() {}
|
||||
virtual void Update() {}
|
||||
virtual void Clear(bool mute) { m_muted = mute; }
|
||||
bool IsMuted() const { return m_muted; }
|
||||
|
||||
void StartLogAudio(const std::string& filename)
|
||||
{
|
||||
if (!m_logAudio)
|
||||
{
|
||||
m_logAudio = true;
|
||||
g_wave_writer.Start(filename, m_mixer->GetSampleRate());
|
||||
g_wave_writer.SetSkipSilence(false);
|
||||
NOTICE_LOG(AUDIO, "Starting Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "Audio logging already started");
|
||||
}
|
||||
}
|
||||
|
||||
void StopLogAudio()
|
||||
{
|
||||
if (m_logAudio)
|
||||
{
|
||||
m_logAudio = false;
|
||||
g_wave_writer.Stop();
|
||||
NOTICE_LOG(AUDIO, "Stopping Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "Audio logging already stopped");
|
||||
}
|
||||
}
|
||||
void StopLogAudio()
|
||||
{
|
||||
if (m_logAudio)
|
||||
{
|
||||
m_logAudio = false;
|
||||
g_wave_writer.Stop();
|
||||
NOTICE_LOG(AUDIO, "Stopping Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(AUDIO, "Audio logging already stopped");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -17,127 +17,130 @@ WaveFileWriter::WaveFileWriter()
|
||||
|
||||
WaveFileWriter::~WaveFileWriter()
|
||||
{
|
||||
Stop();
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool WaveFileWriter::Start(const std::string& filename, unsigned int HLESampleRate)
|
||||
{
|
||||
// Check if the file is already open
|
||||
if (file)
|
||||
{
|
||||
PanicAlertT("The file %s was already open, the file header will not be written.", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
// Check if the file is already open
|
||||
if (file)
|
||||
{
|
||||
PanicAlertT("The file %s was already open, the file header will not be written.",
|
||||
filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
file.Open(filename, "wb");
|
||||
if (!file)
|
||||
{
|
||||
PanicAlertT("The file %s could not be opened for writing. Please check if it's already opened by another program.", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
file.Open(filename, "wb");
|
||||
if (!file)
|
||||
{
|
||||
PanicAlertT("The file %s could not be opened for writing. Please check if it's already opened "
|
||||
"by another program.",
|
||||
filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
audio_size = 0;
|
||||
audio_size = 0;
|
||||
|
||||
// -----------------
|
||||
// Write file header
|
||||
// -----------------
|
||||
Write4("RIFF");
|
||||
Write(100 * 1000 * 1000); // write big value in case the file gets truncated
|
||||
Write4("WAVE");
|
||||
Write4("fmt ");
|
||||
// -----------------
|
||||
// Write file header
|
||||
// -----------------
|
||||
Write4("RIFF");
|
||||
Write(100 * 1000 * 1000); // write big value in case the file gets truncated
|
||||
Write4("WAVE");
|
||||
Write4("fmt ");
|
||||
|
||||
Write(16); // size of fmt block
|
||||
Write(0x00020001); //two channels, uncompressed
|
||||
Write(16); // size of fmt block
|
||||
Write(0x00020001); // two channels, uncompressed
|
||||
|
||||
const u32 sample_rate = HLESampleRate;
|
||||
Write(sample_rate);
|
||||
Write(sample_rate * 2 * 2); //two channels, 16bit
|
||||
const u32 sample_rate = HLESampleRate;
|
||||
Write(sample_rate);
|
||||
Write(sample_rate * 2 * 2); // two channels, 16bit
|
||||
|
||||
Write(0x00100004);
|
||||
Write4("data");
|
||||
Write(100 * 1000 * 1000 - 32);
|
||||
Write(0x00100004);
|
||||
Write4("data");
|
||||
Write(100 * 1000 * 1000 - 32);
|
||||
|
||||
// We are now at offset 44
|
||||
if (file.Tell() != 44)
|
||||
PanicAlert("Wrong offset: %lld", (long long)file.Tell());
|
||||
// We are now at offset 44
|
||||
if (file.Tell() != 44)
|
||||
PanicAlert("Wrong offset: %lld", (long long)file.Tell());
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WaveFileWriter::Stop()
|
||||
{
|
||||
// u32 file_size = (u32)ftello(file);
|
||||
file.Seek(4, SEEK_SET);
|
||||
Write(audio_size + 36);
|
||||
// u32 file_size = (u32)ftello(file);
|
||||
file.Seek(4, SEEK_SET);
|
||||
Write(audio_size + 36);
|
||||
|
||||
file.Seek(40, SEEK_SET);
|
||||
Write(audio_size);
|
||||
file.Seek(40, SEEK_SET);
|
||||
Write(audio_size);
|
||||
|
||||
file.Close();
|
||||
file.Close();
|
||||
}
|
||||
|
||||
void WaveFileWriter::Write(u32 value)
|
||||
{
|
||||
file.WriteArray(&value, 1);
|
||||
file.WriteArray(&value, 1);
|
||||
}
|
||||
|
||||
void WaveFileWriter::Write4(const char *ptr)
|
||||
void WaveFileWriter::Write4(const char* ptr)
|
||||
{
|
||||
file.WriteBytes(ptr, 4);
|
||||
file.WriteBytes(ptr, 4);
|
||||
}
|
||||
|
||||
void WaveFileWriter::AddStereoSamples(const short *sample_data, u32 count)
|
||||
void WaveFileWriter::AddStereoSamples(const short* sample_data, u32 count)
|
||||
{
|
||||
if (!file)
|
||||
PanicAlertT("WaveFileWriter - file not open.");
|
||||
if (!file)
|
||||
PanicAlertT("WaveFileWriter - file not open.");
|
||||
|
||||
if (skip_silence)
|
||||
{
|
||||
bool all_zero = true;
|
||||
if (skip_silence)
|
||||
{
|
||||
bool all_zero = true;
|
||||
|
||||
for (u32 i = 0; i < count * 2; i++)
|
||||
{
|
||||
if (sample_data[i])
|
||||
all_zero = false;
|
||||
}
|
||||
for (u32 i = 0; i < count * 2; i++)
|
||||
{
|
||||
if (sample_data[i])
|
||||
all_zero = false;
|
||||
}
|
||||
|
||||
if (all_zero)
|
||||
return;
|
||||
}
|
||||
if (all_zero)
|
||||
return;
|
||||
}
|
||||
|
||||
file.WriteBytes(sample_data, count * 4);
|
||||
audio_size += count * 4;
|
||||
file.WriteBytes(sample_data, count * 4);
|
||||
audio_size += count * 4;
|
||||
}
|
||||
|
||||
void WaveFileWriter::AddStereoSamplesBE(const short *sample_data, u32 count)
|
||||
void WaveFileWriter::AddStereoSamplesBE(const short* sample_data, u32 count)
|
||||
{
|
||||
if (!file)
|
||||
PanicAlertT("WaveFileWriter - file not open.");
|
||||
if (!file)
|
||||
PanicAlertT("WaveFileWriter - file not open.");
|
||||
|
||||
if (count > BUFFER_SIZE * 2)
|
||||
PanicAlert("WaveFileWriter - buffer too small (count = %u).", count);
|
||||
if (count > BUFFER_SIZE * 2)
|
||||
PanicAlert("WaveFileWriter - buffer too small (count = %u).", count);
|
||||
|
||||
if (skip_silence)
|
||||
{
|
||||
bool all_zero = true;
|
||||
if (skip_silence)
|
||||
{
|
||||
bool all_zero = true;
|
||||
|
||||
for (u32 i = 0; i < count * 2; i++)
|
||||
{
|
||||
if (sample_data[i])
|
||||
all_zero = false;
|
||||
}
|
||||
for (u32 i = 0; i < count * 2; i++)
|
||||
{
|
||||
if (sample_data[i])
|
||||
all_zero = false;
|
||||
}
|
||||
|
||||
if (all_zero)
|
||||
return;
|
||||
}
|
||||
if (all_zero)
|
||||
return;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
//Flip the audio channels from RL to LR
|
||||
conv_buffer[2 * i] = Common::swap16((u16)sample_data[2 * i + 1]);
|
||||
conv_buffer[2 * i + 1] = Common::swap16((u16)sample_data[2 * i]);
|
||||
}
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
// Flip the audio channels from RL to LR
|
||||
conv_buffer[2 * i] = Common::swap16((u16)sample_data[2 * i + 1]);
|
||||
conv_buffer[2 * i + 1] = Common::swap16((u16)sample_data[2 * i]);
|
||||
}
|
||||
|
||||
file.WriteBytes(conv_buffer.data(), count * 4);
|
||||
audio_size += count * 4;
|
||||
file.WriteBytes(conv_buffer.data(), count * 4);
|
||||
audio_size += count * 4;
|
||||
}
|
||||
|
@ -23,25 +23,23 @@
|
||||
class WaveFileWriter : NonCopyable
|
||||
{
|
||||
public:
|
||||
WaveFileWriter();
|
||||
~WaveFileWriter();
|
||||
WaveFileWriter();
|
||||
~WaveFileWriter();
|
||||
|
||||
bool Start(const std::string& filename, unsigned int HLESampleRate);
|
||||
void Stop();
|
||||
|
||||
void SetSkipSilence(bool skip) { skip_silence = skip; }
|
||||
|
||||
void AddStereoSamples(const short *sample_data, u32 count);
|
||||
void AddStereoSamplesBE(const short *sample_data, u32 count); // big endian
|
||||
u32 GetAudioSize() const { return audio_size; }
|
||||
bool Start(const std::string& filename, unsigned int HLESampleRate);
|
||||
void Stop();
|
||||
|
||||
void SetSkipSilence(bool skip) { skip_silence = skip; }
|
||||
void AddStereoSamples(const short* sample_data, u32 count);
|
||||
void AddStereoSamplesBE(const short* sample_data, u32 count); // big endian
|
||||
u32 GetAudioSize() const { return audio_size; }
|
||||
private:
|
||||
static constexpr size_t BUFFER_SIZE = 32 * 1024;
|
||||
static constexpr size_t BUFFER_SIZE = 32 * 1024;
|
||||
|
||||
File::IOFile file;
|
||||
bool skip_silence = false;
|
||||
u32 audio_size = 0;
|
||||
std::array<short, BUFFER_SIZE> conv_buffer{};
|
||||
void Write(u32 value);
|
||||
void Write4(const char* ptr);
|
||||
File::IOFile file;
|
||||
bool skip_silence = false;
|
||||
u32 audio_size = 0;
|
||||
std::array<short, BUFFER_SIZE> conv_buffer{};
|
||||
void Write(u32 value);
|
||||
void Write4(const char* ptr);
|
||||
};
|
||||
|
@ -2,12 +2,12 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "AudioCommon/XAudio2Stream.h"
|
||||
#include <xaudio2.h>
|
||||
#include "AudioCommon/AudioCommon.h"
|
||||
#include "AudioCommon/XAudio2Stream.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
#ifndef XAUDIO2_DLL
|
||||
#error You are building this module against the wrong version of DirectX. You probably need to remove DXSDK_DIR from your include path.
|
||||
@ -16,29 +16,28 @@
|
||||
struct StreamingVoiceContext : public IXAudio2VoiceCallback
|
||||
{
|
||||
private:
|
||||
CMixer* const m_mixer;
|
||||
Common::Event& m_sound_sync_event;
|
||||
IXAudio2SourceVoice* m_source_voice;
|
||||
std::unique_ptr<BYTE[]> xaudio_buffer;
|
||||
CMixer* const m_mixer;
|
||||
Common::Event& m_sound_sync_event;
|
||||
IXAudio2SourceVoice* m_source_voice;
|
||||
std::unique_ptr<BYTE[]> xaudio_buffer;
|
||||
|
||||
void SubmitBuffer(PBYTE buf_data);
|
||||
void SubmitBuffer(PBYTE buf_data);
|
||||
|
||||
public:
|
||||
StreamingVoiceContext(IXAudio2 *pXAudio2, CMixer *pMixer, Common::Event& pSyncEvent);
|
||||
StreamingVoiceContext(IXAudio2* pXAudio2, CMixer* pMixer, Common::Event& pSyncEvent);
|
||||
|
||||
~StreamingVoiceContext();
|
||||
~StreamingVoiceContext();
|
||||
|
||||
void StreamingVoiceContext::Stop();
|
||||
void StreamingVoiceContext::Play();
|
||||
void StreamingVoiceContext::Stop();
|
||||
void StreamingVoiceContext::Play();
|
||||
|
||||
STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd) () {}
|
||||
STDMETHOD_(void, OnBufferStart) (void*) {}
|
||||
STDMETHOD_(void, OnLoopEnd) (void*) {}
|
||||
STDMETHOD_(void, OnStreamEnd) () {}
|
||||
|
||||
STDMETHOD_(void, OnBufferEnd) (void* context);
|
||||
STDMETHOD_(void, OnVoiceError)(THIS_ void* pBufferContext, HRESULT Error) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart)(UINT32) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd)() {}
|
||||
STDMETHOD_(void, OnBufferStart)(void*) {}
|
||||
STDMETHOD_(void, OnLoopEnd)(void*) {}
|
||||
STDMETHOD_(void, OnStreamEnd)() {}
|
||||
STDMETHOD_(void, OnBufferEnd)(void* context);
|
||||
};
|
||||
|
||||
const int NUM_BUFFERS = 3;
|
||||
@ -50,199 +49,199 @@ const int BUFFER_SIZE_BYTES = BUFFER_SIZE * sizeof(s16);
|
||||
|
||||
void StreamingVoiceContext::SubmitBuffer(PBYTE buf_data)
|
||||
{
|
||||
XAUDIO2_BUFFER buf = {};
|
||||
buf.AudioBytes = BUFFER_SIZE_BYTES;
|
||||
buf.pContext = buf_data;
|
||||
buf.pAudioData = buf_data;
|
||||
XAUDIO2_BUFFER buf = {};
|
||||
buf.AudioBytes = BUFFER_SIZE_BYTES;
|
||||
buf.pContext = buf_data;
|
||||
buf.pAudioData = buf_data;
|
||||
|
||||
m_source_voice->SubmitSourceBuffer(&buf);
|
||||
m_source_voice->SubmitSourceBuffer(&buf);
|
||||
}
|
||||
|
||||
StreamingVoiceContext::StreamingVoiceContext(IXAudio2 *pXAudio2, CMixer *pMixer, Common::Event& pSyncEvent)
|
||||
: m_mixer(pMixer)
|
||||
, m_sound_sync_event(pSyncEvent)
|
||||
, xaudio_buffer(new BYTE[NUM_BUFFERS * BUFFER_SIZE_BYTES]())
|
||||
StreamingVoiceContext::StreamingVoiceContext(IXAudio2* pXAudio2, CMixer* pMixer,
|
||||
Common::Event& pSyncEvent)
|
||||
: m_mixer(pMixer), m_sound_sync_event(pSyncEvent),
|
||||
xaudio_buffer(new BYTE[NUM_BUFFERS * BUFFER_SIZE_BYTES]())
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE wfx = {};
|
||||
WAVEFORMATEXTENSIBLE wfx = {};
|
||||
|
||||
wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
wfx.Format.nSamplesPerSec = m_mixer->GetSampleRate();
|
||||
wfx.Format.nChannels = 2;
|
||||
wfx.Format.wBitsPerSample = 16;
|
||||
wfx.Format.nBlockAlign = wfx.Format.nChannels*wfx.Format.wBitsPerSample / 8;
|
||||
wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
|
||||
wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
wfx.Samples.wValidBitsPerSample = 16;
|
||||
wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
||||
wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
wfx.Format.nSamplesPerSec = m_mixer->GetSampleRate();
|
||||
wfx.Format.nChannels = 2;
|
||||
wfx.Format.wBitsPerSample = 16;
|
||||
wfx.Format.nBlockAlign = wfx.Format.nChannels * wfx.Format.wBitsPerSample / 8;
|
||||
wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
|
||||
wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
wfx.Samples.wValidBitsPerSample = 16;
|
||||
wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
||||
wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
|
||||
// create source voice
|
||||
HRESULT hr;
|
||||
if (FAILED(hr = pXAudio2->CreateSourceVoice(&m_source_voice, &wfx.Format, XAUDIO2_VOICE_NOSRC, 1.0f, this)))
|
||||
{
|
||||
PanicAlert("XAudio2 CreateSourceVoice failed: %#X", hr);
|
||||
return;
|
||||
}
|
||||
// create source voice
|
||||
HRESULT hr;
|
||||
if (FAILED(hr = pXAudio2->CreateSourceVoice(&m_source_voice, &wfx.Format, XAUDIO2_VOICE_NOSRC,
|
||||
1.0f, this)))
|
||||
{
|
||||
PanicAlert("XAudio2 CreateSourceVoice failed: %#X", hr);
|
||||
return;
|
||||
}
|
||||
|
||||
m_source_voice->Start();
|
||||
m_source_voice->Start();
|
||||
|
||||
// start buffers with silence
|
||||
for (int i = 0; i != NUM_BUFFERS; ++i)
|
||||
SubmitBuffer(xaudio_buffer.get() + (i * BUFFER_SIZE_BYTES));
|
||||
// start buffers with silence
|
||||
for (int i = 0; i != NUM_BUFFERS; ++i)
|
||||
SubmitBuffer(xaudio_buffer.get() + (i * BUFFER_SIZE_BYTES));
|
||||
}
|
||||
|
||||
StreamingVoiceContext::~StreamingVoiceContext()
|
||||
{
|
||||
if (m_source_voice)
|
||||
{
|
||||
m_source_voice->Stop();
|
||||
m_source_voice->DestroyVoice();
|
||||
}
|
||||
if (m_source_voice)
|
||||
{
|
||||
m_source_voice->Stop();
|
||||
m_source_voice->DestroyVoice();
|
||||
}
|
||||
}
|
||||
|
||||
void StreamingVoiceContext::Stop()
|
||||
{
|
||||
if (m_source_voice)
|
||||
m_source_voice->Stop();
|
||||
if (m_source_voice)
|
||||
m_source_voice->Stop();
|
||||
}
|
||||
|
||||
void StreamingVoiceContext::Play()
|
||||
{
|
||||
if (m_source_voice)
|
||||
m_source_voice->Start();
|
||||
if (m_source_voice)
|
||||
m_source_voice->Start();
|
||||
}
|
||||
|
||||
void StreamingVoiceContext::OnBufferEnd(void* context)
|
||||
{
|
||||
// buffer end callback; gets SAMPLES_PER_BUFFER samples for a new buffer
|
||||
// buffer end callback; gets SAMPLES_PER_BUFFER samples for a new buffer
|
||||
|
||||
if (!m_source_voice || !context)
|
||||
return;
|
||||
if (!m_source_voice || !context)
|
||||
return;
|
||||
|
||||
//m_sound_sync_event->Wait(); // sync
|
||||
//m_sound_sync_event->Spin(); // or tight sync
|
||||
// m_sound_sync_event->Wait(); // sync
|
||||
// m_sound_sync_event->Spin(); // or tight sync
|
||||
|
||||
m_mixer->Mix(static_cast<short*>(context), SAMPLES_PER_BUFFER);
|
||||
SubmitBuffer(static_cast<BYTE*>(context));
|
||||
m_mixer->Mix(static_cast<short*>(context), SAMPLES_PER_BUFFER);
|
||||
SubmitBuffer(static_cast<BYTE*>(context));
|
||||
}
|
||||
|
||||
HMODULE XAudio2::m_xaudio2_dll = nullptr;
|
||||
typedef decltype(&XAudio2Create) XAudio2Create_t;
|
||||
void *XAudio2::PXAudio2Create = nullptr;
|
||||
void* XAudio2::PXAudio2Create = nullptr;
|
||||
|
||||
bool XAudio2::InitLibrary()
|
||||
{
|
||||
if (m_xaudio2_dll)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (m_xaudio2_dll)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
m_xaudio2_dll = ::LoadLibrary(XAUDIO2_DLL);
|
||||
if (!m_xaudio2_dll)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_xaudio2_dll = ::LoadLibrary(XAUDIO2_DLL);
|
||||
if (!m_xaudio2_dll)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!PXAudio2Create)
|
||||
{
|
||||
PXAudio2Create = (XAudio2Create_t)::GetProcAddress(m_xaudio2_dll, "XAudio2Create");
|
||||
if (!PXAudio2Create)
|
||||
{
|
||||
::FreeLibrary(m_xaudio2_dll);
|
||||
m_xaudio2_dll = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!PXAudio2Create)
|
||||
{
|
||||
PXAudio2Create = (XAudio2Create_t)::GetProcAddress(m_xaudio2_dll, "XAudio2Create");
|
||||
if (!PXAudio2Create)
|
||||
{
|
||||
::FreeLibrary(m_xaudio2_dll);
|
||||
m_xaudio2_dll = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
XAudio2::XAudio2()
|
||||
: m_mastering_voice(nullptr)
|
||||
, m_volume(1.0f)
|
||||
, m_cleanup_com(SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
|
||||
: m_mastering_voice(nullptr), m_volume(1.0f),
|
||||
m_cleanup_com(SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
|
||||
{
|
||||
}
|
||||
|
||||
XAudio2::~XAudio2()
|
||||
{
|
||||
Stop();
|
||||
if (m_cleanup_com)
|
||||
CoUninitialize();
|
||||
Stop();
|
||||
if (m_cleanup_com)
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
bool XAudio2::Start()
|
||||
{
|
||||
HRESULT hr;
|
||||
HRESULT hr;
|
||||
|
||||
// callback doesn't seem to run on a specific CPU anyways
|
||||
IXAudio2* xaudptr;
|
||||
if (FAILED(hr = ((XAudio2Create_t)PXAudio2Create)(&xaudptr, 0, XAUDIO2_DEFAULT_PROCESSOR)))
|
||||
{
|
||||
PanicAlert("XAudio2 init failed: %#X", hr);
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
m_xaudio2 = std::unique_ptr<IXAudio2, Releaser>(xaudptr);
|
||||
// callback doesn't seem to run on a specific CPU anyways
|
||||
IXAudio2* xaudptr;
|
||||
if (FAILED(hr = ((XAudio2Create_t)PXAudio2Create)(&xaudptr, 0, XAUDIO2_DEFAULT_PROCESSOR)))
|
||||
{
|
||||
PanicAlert("XAudio2 init failed: %#X", hr);
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
m_xaudio2 = std::unique_ptr<IXAudio2, Releaser>(xaudptr);
|
||||
|
||||
// XAudio2 master voice
|
||||
// XAUDIO2_DEFAULT_CHANNELS instead of 2 for expansion?
|
||||
if (FAILED(hr = m_xaudio2->CreateMasteringVoice(&m_mastering_voice, 2, m_mixer->GetSampleRate())))
|
||||
{
|
||||
PanicAlert("XAudio2 master voice creation failed: %#X", hr);
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
// XAudio2 master voice
|
||||
// XAUDIO2_DEFAULT_CHANNELS instead of 2 for expansion?
|
||||
if (FAILED(hr = m_xaudio2->CreateMasteringVoice(&m_mastering_voice, 2, m_mixer->GetSampleRate())))
|
||||
{
|
||||
PanicAlert("XAudio2 master voice creation failed: %#X", hr);
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Volume
|
||||
m_mastering_voice->SetVolume(m_volume);
|
||||
// Volume
|
||||
m_mastering_voice->SetVolume(m_volume);
|
||||
|
||||
m_voice_context = std::unique_ptr<StreamingVoiceContext>
|
||||
(new StreamingVoiceContext(m_xaudio2.get(), m_mixer.get(), m_sound_sync_event));
|
||||
m_voice_context = std::unique_ptr<StreamingVoiceContext>(
|
||||
new StreamingVoiceContext(m_xaudio2.get(), m_mixer.get(), m_sound_sync_event));
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void XAudio2::SetVolume(int volume)
|
||||
{
|
||||
//linear 1- .01
|
||||
m_volume = (float)volume / 100.f;
|
||||
// linear 1- .01
|
||||
m_volume = (float)volume / 100.f;
|
||||
|
||||
if (m_mastering_voice)
|
||||
m_mastering_voice->SetVolume(m_volume);
|
||||
if (m_mastering_voice)
|
||||
m_mastering_voice->SetVolume(m_volume);
|
||||
}
|
||||
|
||||
void XAudio2::Clear(bool mute)
|
||||
{
|
||||
m_muted = mute;
|
||||
m_muted = mute;
|
||||
|
||||
if (m_voice_context)
|
||||
{
|
||||
if (m_muted)
|
||||
m_voice_context->Stop();
|
||||
else
|
||||
m_voice_context->Play();
|
||||
}
|
||||
if (m_voice_context)
|
||||
{
|
||||
if (m_muted)
|
||||
m_voice_context->Stop();
|
||||
else
|
||||
m_voice_context->Play();
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2::Stop()
|
||||
{
|
||||
//m_sound_sync_event.Set();
|
||||
// m_sound_sync_event.Set();
|
||||
|
||||
m_voice_context.reset();
|
||||
m_voice_context.reset();
|
||||
|
||||
if (m_mastering_voice)
|
||||
{
|
||||
m_mastering_voice->DestroyVoice();
|
||||
m_mastering_voice = nullptr;
|
||||
}
|
||||
if (m_mastering_voice)
|
||||
{
|
||||
m_mastering_voice->DestroyVoice();
|
||||
m_mastering_voice = nullptr;
|
||||
}
|
||||
|
||||
m_xaudio2.reset(); // release interface
|
||||
m_xaudio2.reset(); // release interface
|
||||
|
||||
if (m_xaudio2_dll)
|
||||
{
|
||||
::FreeLibrary(m_xaudio2_dll);
|
||||
m_xaudio2_dll = nullptr;
|
||||
PXAudio2Create = nullptr;
|
||||
}
|
||||
if (m_xaudio2_dll)
|
||||
{
|
||||
::FreeLibrary(m_xaudio2_dll);
|
||||
m_xaudio2_dll = nullptr;
|
||||
PXAudio2Create = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -26,40 +26,40 @@ class XAudio2 final : public SoundStream
|
||||
#ifdef _WIN32
|
||||
|
||||
private:
|
||||
class Releaser
|
||||
{
|
||||
public:
|
||||
template <typename R>
|
||||
void operator()(R* ptr)
|
||||
{
|
||||
ptr->Release();
|
||||
}
|
||||
};
|
||||
class Releaser
|
||||
{
|
||||
public:
|
||||
template <typename R>
|
||||
void operator()(R* ptr)
|
||||
{
|
||||
ptr->Release();
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<IXAudio2, Releaser> m_xaudio2;
|
||||
std::unique_ptr<StreamingVoiceContext> m_voice_context;
|
||||
IXAudio2MasteringVoice *m_mastering_voice;
|
||||
std::unique_ptr<IXAudio2, Releaser> m_xaudio2;
|
||||
std::unique_ptr<StreamingVoiceContext> m_voice_context;
|
||||
IXAudio2MasteringVoice* m_mastering_voice;
|
||||
|
||||
Common::Event m_sound_sync_event;
|
||||
float m_volume;
|
||||
Common::Event m_sound_sync_event;
|
||||
float m_volume;
|
||||
|
||||
const bool m_cleanup_com;
|
||||
const bool m_cleanup_com;
|
||||
|
||||
static HMODULE m_xaudio2_dll;
|
||||
static void *PXAudio2Create;
|
||||
static HMODULE m_xaudio2_dll;
|
||||
static void* PXAudio2Create;
|
||||
|
||||
static bool InitLibrary();
|
||||
static bool InitLibrary();
|
||||
|
||||
public:
|
||||
XAudio2();
|
||||
virtual ~XAudio2();
|
||||
XAudio2();
|
||||
virtual ~XAudio2();
|
||||
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
|
||||
void Clear(bool mute) override;
|
||||
void SetVolume(int volume) override;
|
||||
void Clear(bool mute) override;
|
||||
void SetVolume(int volume) override;
|
||||
|
||||
static bool isValid() { return InitLibrary(); }
|
||||
static bool isValid() { return InitLibrary(); }
|
||||
#endif
|
||||
};
|
||||
|
@ -10,35 +10,34 @@
|
||||
#include "AudioCommon/AudioCommon.h"
|
||||
#include "AudioCommon/XAudio2_7Stream.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
struct StreamingVoiceContext2_7 : public IXAudio2VoiceCallback
|
||||
{
|
||||
private:
|
||||
CMixer* const m_mixer;
|
||||
Common::Event& m_sound_sync_event;
|
||||
IXAudio2SourceVoice* m_source_voice;
|
||||
std::unique_ptr<BYTE[]> xaudio_buffer;
|
||||
CMixer* const m_mixer;
|
||||
Common::Event& m_sound_sync_event;
|
||||
IXAudio2SourceVoice* m_source_voice;
|
||||
std::unique_ptr<BYTE[]> xaudio_buffer;
|
||||
|
||||
void SubmitBuffer(PBYTE buf_data);
|
||||
void SubmitBuffer(PBYTE buf_data);
|
||||
|
||||
public:
|
||||
StreamingVoiceContext2_7(IXAudio2 *pXAudio2, CMixer *pMixer, Common::Event& pSyncEvent);
|
||||
StreamingVoiceContext2_7(IXAudio2* pXAudio2, CMixer* pMixer, Common::Event& pSyncEvent);
|
||||
|
||||
~StreamingVoiceContext2_7();
|
||||
~StreamingVoiceContext2_7();
|
||||
|
||||
void StreamingVoiceContext2_7::Stop();
|
||||
void StreamingVoiceContext2_7::Play();
|
||||
void StreamingVoiceContext2_7::Stop();
|
||||
void StreamingVoiceContext2_7::Play();
|
||||
|
||||
STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd) () {}
|
||||
STDMETHOD_(void, OnBufferStart) (void*) {}
|
||||
STDMETHOD_(void, OnLoopEnd) (void*) {}
|
||||
STDMETHOD_(void, OnStreamEnd) () {}
|
||||
|
||||
STDMETHOD_(void, OnBufferEnd) (void* context);
|
||||
STDMETHOD_(void, OnVoiceError)(THIS_ void* pBufferContext, HRESULT Error) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart)(UINT32) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd)() {}
|
||||
STDMETHOD_(void, OnBufferStart)(void*) {}
|
||||
STDMETHOD_(void, OnLoopEnd)(void*) {}
|
||||
STDMETHOD_(void, OnStreamEnd)() {}
|
||||
STDMETHOD_(void, OnBufferEnd)(void* context);
|
||||
};
|
||||
|
||||
const int NUM_BUFFERS = 3;
|
||||
@ -50,186 +49,186 @@ const int BUFFER_SIZE_BYTES = BUFFER_SIZE * sizeof(s16);
|
||||
|
||||
void StreamingVoiceContext2_7::SubmitBuffer(PBYTE buf_data)
|
||||
{
|
||||
XAUDIO2_BUFFER buf = {};
|
||||
buf.AudioBytes = BUFFER_SIZE_BYTES;
|
||||
buf.pContext = buf_data;
|
||||
buf.pAudioData = buf_data;
|
||||
XAUDIO2_BUFFER buf = {};
|
||||
buf.AudioBytes = BUFFER_SIZE_BYTES;
|
||||
buf.pContext = buf_data;
|
||||
buf.pAudioData = buf_data;
|
||||
|
||||
m_source_voice->SubmitSourceBuffer(&buf);
|
||||
m_source_voice->SubmitSourceBuffer(&buf);
|
||||
}
|
||||
|
||||
StreamingVoiceContext2_7::StreamingVoiceContext2_7(IXAudio2 *pXAudio2, CMixer *pMixer, Common::Event& pSyncEvent)
|
||||
: m_mixer(pMixer)
|
||||
, m_sound_sync_event(pSyncEvent)
|
||||
, xaudio_buffer(new BYTE[NUM_BUFFERS * BUFFER_SIZE_BYTES]())
|
||||
StreamingVoiceContext2_7::StreamingVoiceContext2_7(IXAudio2* pXAudio2, CMixer* pMixer,
|
||||
Common::Event& pSyncEvent)
|
||||
: m_mixer(pMixer), m_sound_sync_event(pSyncEvent),
|
||||
xaudio_buffer(new BYTE[NUM_BUFFERS * BUFFER_SIZE_BYTES]())
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE wfx = {};
|
||||
WAVEFORMATEXTENSIBLE wfx = {};
|
||||
|
||||
wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
wfx.Format.nSamplesPerSec = m_mixer->GetSampleRate();
|
||||
wfx.Format.nChannels = 2;
|
||||
wfx.Format.wBitsPerSample = 16;
|
||||
wfx.Format.nBlockAlign = wfx.Format.nChannels*wfx.Format.wBitsPerSample / 8;
|
||||
wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
|
||||
wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
wfx.Samples.wValidBitsPerSample = 16;
|
||||
wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
||||
wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
wfx.Format.nSamplesPerSec = m_mixer->GetSampleRate();
|
||||
wfx.Format.nChannels = 2;
|
||||
wfx.Format.wBitsPerSample = 16;
|
||||
wfx.Format.nBlockAlign = wfx.Format.nChannels * wfx.Format.wBitsPerSample / 8;
|
||||
wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
|
||||
wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
wfx.Samples.wValidBitsPerSample = 16;
|
||||
wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
||||
wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
|
||||
// create source voice
|
||||
HRESULT hr;
|
||||
if (FAILED(hr = pXAudio2->CreateSourceVoice(&m_source_voice, &wfx.Format, XAUDIO2_VOICE_NOSRC, 1.0f, this)))
|
||||
{
|
||||
PanicAlert("XAudio2_7 CreateSourceVoice failed: %#X", hr);
|
||||
return;
|
||||
}
|
||||
// create source voice
|
||||
HRESULT hr;
|
||||
if (FAILED(hr = pXAudio2->CreateSourceVoice(&m_source_voice, &wfx.Format, XAUDIO2_VOICE_NOSRC,
|
||||
1.0f, this)))
|
||||
{
|
||||
PanicAlert("XAudio2_7 CreateSourceVoice failed: %#X", hr);
|
||||
return;
|
||||
}
|
||||
|
||||
m_source_voice->Start();
|
||||
m_source_voice->Start();
|
||||
|
||||
// start buffers with silence
|
||||
for (int i = 0; i != NUM_BUFFERS; ++i)
|
||||
SubmitBuffer(xaudio_buffer.get() + (i * BUFFER_SIZE_BYTES));
|
||||
// start buffers with silence
|
||||
for (int i = 0; i != NUM_BUFFERS; ++i)
|
||||
SubmitBuffer(xaudio_buffer.get() + (i * BUFFER_SIZE_BYTES));
|
||||
}
|
||||
|
||||
StreamingVoiceContext2_7::~StreamingVoiceContext2_7()
|
||||
{
|
||||
if (m_source_voice)
|
||||
{
|
||||
m_source_voice->Stop();
|
||||
m_source_voice->DestroyVoice();
|
||||
}
|
||||
if (m_source_voice)
|
||||
{
|
||||
m_source_voice->Stop();
|
||||
m_source_voice->DestroyVoice();
|
||||
}
|
||||
}
|
||||
|
||||
void StreamingVoiceContext2_7::Stop()
|
||||
{
|
||||
if (m_source_voice)
|
||||
m_source_voice->Stop();
|
||||
if (m_source_voice)
|
||||
m_source_voice->Stop();
|
||||
}
|
||||
|
||||
void StreamingVoiceContext2_7::Play()
|
||||
{
|
||||
if (m_source_voice)
|
||||
m_source_voice->Start();
|
||||
if (m_source_voice)
|
||||
m_source_voice->Start();
|
||||
}
|
||||
|
||||
void StreamingVoiceContext2_7::OnBufferEnd(void* context)
|
||||
{
|
||||
// buffer end callback; gets SAMPLES_PER_BUFFER samples for a new buffer
|
||||
// buffer end callback; gets SAMPLES_PER_BUFFER samples for a new buffer
|
||||
|
||||
if (!m_source_voice || !context)
|
||||
return;
|
||||
if (!m_source_voice || !context)
|
||||
return;
|
||||
|
||||
//m_sound_sync_event->Wait(); // sync
|
||||
//m_sound_sync_event->Spin(); // or tight sync
|
||||
// m_sound_sync_event->Wait(); // sync
|
||||
// m_sound_sync_event->Spin(); // or tight sync
|
||||
|
||||
m_mixer->Mix(static_cast<short*>(context), SAMPLES_PER_BUFFER);
|
||||
SubmitBuffer(static_cast<BYTE*>(context));
|
||||
m_mixer->Mix(static_cast<short*>(context), SAMPLES_PER_BUFFER);
|
||||
SubmitBuffer(static_cast<BYTE*>(context));
|
||||
}
|
||||
|
||||
HMODULE XAudio2_7::m_xaudio2_dll = nullptr;
|
||||
|
||||
void XAudio2_7::ReleaseIXAudio2(IXAudio2* ptr)
|
||||
{
|
||||
ptr->Release();
|
||||
ptr->Release();
|
||||
}
|
||||
|
||||
bool XAudio2_7::InitLibrary()
|
||||
{
|
||||
if (m_xaudio2_dll)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (m_xaudio2_dll)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
m_xaudio2_dll = ::LoadLibrary(TEXT("xaudio2_7.dll"));
|
||||
m_xaudio2_dll = ::LoadLibrary(TEXT("xaudio2_7.dll"));
|
||||
|
||||
return m_xaudio2_dll != nullptr;
|
||||
return m_xaudio2_dll != nullptr;
|
||||
}
|
||||
|
||||
XAudio2_7::XAudio2_7()
|
||||
: m_mastering_voice(nullptr)
|
||||
, m_volume(1.0f)
|
||||
, m_cleanup_com(SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
|
||||
: m_mastering_voice(nullptr), m_volume(1.0f),
|
||||
m_cleanup_com(SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
|
||||
{
|
||||
}
|
||||
|
||||
XAudio2_7::~XAudio2_7()
|
||||
{
|
||||
Stop();
|
||||
if (m_cleanup_com)
|
||||
CoUninitialize();
|
||||
Stop();
|
||||
if (m_cleanup_com)
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
bool XAudio2_7::Start()
|
||||
{
|
||||
HRESULT hr;
|
||||
HRESULT hr;
|
||||
|
||||
// callback doesn't seem to run on a specific CPU anyways
|
||||
IXAudio2* xaudptr;
|
||||
if (FAILED(hr = XAudio2Create(&xaudptr, 0, XAUDIO2_DEFAULT_PROCESSOR)))
|
||||
{
|
||||
PanicAlert("XAudio2_7 init failed: %#X", hr);
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
m_xaudio2 = std::unique_ptr<IXAudio2, Releaser>(xaudptr);
|
||||
// callback doesn't seem to run on a specific CPU anyways
|
||||
IXAudio2* xaudptr;
|
||||
if (FAILED(hr = XAudio2Create(&xaudptr, 0, XAUDIO2_DEFAULT_PROCESSOR)))
|
||||
{
|
||||
PanicAlert("XAudio2_7 init failed: %#X", hr);
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
m_xaudio2 = std::unique_ptr<IXAudio2, Releaser>(xaudptr);
|
||||
|
||||
// XAudio2 master voice
|
||||
// XAUDIO2_DEFAULT_CHANNELS instead of 2 for expansion?
|
||||
if (FAILED(hr = m_xaudio2->CreateMasteringVoice(&m_mastering_voice, 2, m_mixer->GetSampleRate())))
|
||||
{
|
||||
PanicAlert("XAudio2_7 master voice creation failed: %#X", hr);
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
// XAudio2 master voice
|
||||
// XAUDIO2_DEFAULT_CHANNELS instead of 2 for expansion?
|
||||
if (FAILED(hr = m_xaudio2->CreateMasteringVoice(&m_mastering_voice, 2, m_mixer->GetSampleRate())))
|
||||
{
|
||||
PanicAlert("XAudio2_7 master voice creation failed: %#X", hr);
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Volume
|
||||
m_mastering_voice->SetVolume(m_volume);
|
||||
// Volume
|
||||
m_mastering_voice->SetVolume(m_volume);
|
||||
|
||||
m_voice_context = std::unique_ptr<StreamingVoiceContext2_7>
|
||||
(new StreamingVoiceContext2_7(m_xaudio2.get(), m_mixer.get(), m_sound_sync_event));
|
||||
m_voice_context = std::unique_ptr<StreamingVoiceContext2_7>(
|
||||
new StreamingVoiceContext2_7(m_xaudio2.get(), m_mixer.get(), m_sound_sync_event));
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void XAudio2_7::SetVolume(int volume)
|
||||
{
|
||||
//linear 1- .01
|
||||
m_volume = (float)volume / 100.f;
|
||||
// linear 1- .01
|
||||
m_volume = (float)volume / 100.f;
|
||||
|
||||
if (m_mastering_voice)
|
||||
m_mastering_voice->SetVolume(m_volume);
|
||||
if (m_mastering_voice)
|
||||
m_mastering_voice->SetVolume(m_volume);
|
||||
}
|
||||
|
||||
void XAudio2_7::Clear(bool mute)
|
||||
{
|
||||
m_muted = mute;
|
||||
m_muted = mute;
|
||||
|
||||
if (m_voice_context)
|
||||
{
|
||||
if (m_muted)
|
||||
m_voice_context->Stop();
|
||||
else
|
||||
m_voice_context->Play();
|
||||
}
|
||||
if (m_voice_context)
|
||||
{
|
||||
if (m_muted)
|
||||
m_voice_context->Stop();
|
||||
else
|
||||
m_voice_context->Play();
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2_7::Stop()
|
||||
{
|
||||
//m_sound_sync_event.Set();
|
||||
// m_sound_sync_event.Set();
|
||||
|
||||
m_voice_context.reset();
|
||||
m_voice_context.reset();
|
||||
|
||||
if (m_mastering_voice)
|
||||
{
|
||||
m_mastering_voice->DestroyVoice();
|
||||
m_mastering_voice = nullptr;
|
||||
}
|
||||
if (m_mastering_voice)
|
||||
{
|
||||
m_mastering_voice->DestroyVoice();
|
||||
m_mastering_voice = nullptr;
|
||||
}
|
||||
|
||||
m_xaudio2.reset(); // release interface
|
||||
m_xaudio2.reset(); // release interface
|
||||
|
||||
if (m_xaudio2_dll)
|
||||
{
|
||||
::FreeLibrary(m_xaudio2_dll);
|
||||
m_xaudio2_dll = nullptr;
|
||||
}
|
||||
if (m_xaudio2_dll)
|
||||
{
|
||||
::FreeLibrary(m_xaudio2_dll);
|
||||
m_xaudio2_dll = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -32,41 +32,41 @@ class XAudio2_7 final : public SoundStream
|
||||
#ifdef _WIN32
|
||||
|
||||
private:
|
||||
static void ReleaseIXAudio2(IXAudio2 *ptr);
|
||||
static void ReleaseIXAudio2(IXAudio2* ptr);
|
||||
|
||||
class Releaser
|
||||
{
|
||||
public:
|
||||
template <typename R>
|
||||
void operator()(R *ptr)
|
||||
{
|
||||
ReleaseIXAudio2(ptr);
|
||||
}
|
||||
};
|
||||
class Releaser
|
||||
{
|
||||
public:
|
||||
template <typename R>
|
||||
void operator()(R* ptr)
|
||||
{
|
||||
ReleaseIXAudio2(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<IXAudio2, Releaser> m_xaudio2;
|
||||
std::unique_ptr<StreamingVoiceContext2_7> m_voice_context;
|
||||
IXAudio2MasteringVoice *m_mastering_voice;
|
||||
std::unique_ptr<IXAudio2, Releaser> m_xaudio2;
|
||||
std::unique_ptr<StreamingVoiceContext2_7> m_voice_context;
|
||||
IXAudio2MasteringVoice* m_mastering_voice;
|
||||
|
||||
Common::Event m_sound_sync_event;
|
||||
float m_volume;
|
||||
Common::Event m_sound_sync_event;
|
||||
float m_volume;
|
||||
|
||||
const bool m_cleanup_com;
|
||||
const bool m_cleanup_com;
|
||||
|
||||
static HMODULE m_xaudio2_dll;
|
||||
static HMODULE m_xaudio2_dll;
|
||||
|
||||
static bool InitLibrary();
|
||||
static bool InitLibrary();
|
||||
|
||||
public:
|
||||
XAudio2_7();
|
||||
virtual ~XAudio2_7();
|
||||
XAudio2_7();
|
||||
virtual ~XAudio2_7();
|
||||
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
|
||||
void Clear(bool mute) override;
|
||||
void SetVolume(int volume) override;
|
||||
void Clear(bool mute) override;
|
||||
void SetVolume(int volume) override;
|
||||
|
||||
static bool isValid() { return InitLibrary(); }
|
||||
static bool isValid() { return InitLibrary(); }
|
||||
#endif
|
||||
};
|
||||
|
@ -6,23 +6,35 @@
|
||||
* Copyright (c) 2006, Creative Labs Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
* provided
|
||||
* that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this list of conditions and
|
||||
* * Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and
|
||||
* the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
|
||||
* and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Creative Labs Inc. nor the names of its contributors may be used to endorse or
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
* conditions
|
||||
* and the following disclaimer in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Creative Labs Inc. nor the names of its contributors may be used to
|
||||
* endorse or
|
||||
* promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
* IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED
|
||||
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
@ -42,101 +54,104 @@
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Init call
|
||||
*/
|
||||
ALDeviceList::ALDeviceList()
|
||||
{
|
||||
ALDEVICEINFO ALDeviceInfo;
|
||||
ALDEVICEINFO ALDeviceInfo;
|
||||
|
||||
// DeviceInfo vector stores, for each enumerated device, it's device name, selection status, spec version #, and extension support
|
||||
vDeviceInfo.clear();
|
||||
vDeviceInfo.reserve(10);
|
||||
// DeviceInfo vector stores, for each enumerated device, it's device name, selection status, spec
|
||||
// version #, and extension support
|
||||
vDeviceInfo.clear();
|
||||
vDeviceInfo.reserve(10);
|
||||
|
||||
defaultDeviceIndex = 0;
|
||||
defaultDeviceIndex = 0;
|
||||
|
||||
// grab function pointers for 1.0-API functions, and if successful proceed to enumerate all devices
|
||||
//if (LoadOAL10Library(nullptr, &ALFunction) == TRUE) {
|
||||
if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"))
|
||||
{
|
||||
const char *devices = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
|
||||
const char *defaultDeviceName = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
|
||||
// go through device list (each device terminated with a single nullptr, list terminated with double nullptr)
|
||||
for (s32 index = 0; devices != nullptr && strlen(devices) > 0; index++, devices += strlen(devices) + 1)
|
||||
{
|
||||
if (strcmp(defaultDeviceName, devices) == 0)
|
||||
{
|
||||
defaultDeviceIndex = index;
|
||||
}
|
||||
ALCdevice *device = alcOpenDevice(devices);
|
||||
if (device)
|
||||
{
|
||||
ALCcontext *context = alcCreateContext(device, nullptr);
|
||||
if (context)
|
||||
{
|
||||
alcMakeContextCurrent(context);
|
||||
// if new actual device name isn't already in the list, then add it...
|
||||
const char *actualDeviceName = alcGetString(device, ALC_DEVICE_SPECIFIER);
|
||||
bool bNewName = true;
|
||||
for (s32 i = 0; i < GetNumDevices(); i++)
|
||||
{
|
||||
if (strcmp(GetDeviceName(i), actualDeviceName) == 0)
|
||||
{
|
||||
bNewName = false;
|
||||
}
|
||||
}
|
||||
if ((bNewName) && (actualDeviceName != nullptr) && (strlen(actualDeviceName) > 0))
|
||||
{
|
||||
ALDeviceInfo.bSelected = true;
|
||||
ALDeviceInfo.strDeviceName = actualDeviceName;
|
||||
alcGetIntegerv(device, ALC_MAJOR_VERSION, sizeof(s32), &ALDeviceInfo.iMajorVersion);
|
||||
alcGetIntegerv(device, ALC_MINOR_VERSION, sizeof(s32), &ALDeviceInfo.iMinorVersion);
|
||||
// grab function pointers for 1.0-API functions, and if successful proceed to enumerate all
|
||||
// devices
|
||||
// if (LoadOAL10Library(nullptr, &ALFunction) == TRUE) {
|
||||
if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"))
|
||||
{
|
||||
const char* devices = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
|
||||
const char* defaultDeviceName = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
|
||||
// go through device list (each device terminated with a single nullptr, list terminated with
|
||||
// double nullptr)
|
||||
for (s32 index = 0; devices != nullptr && strlen(devices) > 0;
|
||||
index++, devices += strlen(devices) + 1)
|
||||
{
|
||||
if (strcmp(defaultDeviceName, devices) == 0)
|
||||
{
|
||||
defaultDeviceIndex = index;
|
||||
}
|
||||
ALCdevice* device = alcOpenDevice(devices);
|
||||
if (device)
|
||||
{
|
||||
ALCcontext* context = alcCreateContext(device, nullptr);
|
||||
if (context)
|
||||
{
|
||||
alcMakeContextCurrent(context);
|
||||
// if new actual device name isn't already in the list, then add it...
|
||||
const char* actualDeviceName = alcGetString(device, ALC_DEVICE_SPECIFIER);
|
||||
bool bNewName = true;
|
||||
for (s32 i = 0; i < GetNumDevices(); i++)
|
||||
{
|
||||
if (strcmp(GetDeviceName(i), actualDeviceName) == 0)
|
||||
{
|
||||
bNewName = false;
|
||||
}
|
||||
}
|
||||
if ((bNewName) && (actualDeviceName != nullptr) && (strlen(actualDeviceName) > 0))
|
||||
{
|
||||
ALDeviceInfo.bSelected = true;
|
||||
ALDeviceInfo.strDeviceName = actualDeviceName;
|
||||
alcGetIntegerv(device, ALC_MAJOR_VERSION, sizeof(s32), &ALDeviceInfo.iMajorVersion);
|
||||
alcGetIntegerv(device, ALC_MINOR_VERSION, sizeof(s32), &ALDeviceInfo.iMinorVersion);
|
||||
|
||||
ALDeviceInfo.pvstrExtensions = new std::vector<std::string>;
|
||||
ALDeviceInfo.pvstrExtensions = new std::vector<std::string>;
|
||||
|
||||
// Check for ALC Extensions
|
||||
if (alcIsExtensionPresent(device, "ALC_EXT_CAPTURE") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("ALC_EXT_CAPTURE");
|
||||
if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("ALC_EXT_EFX");
|
||||
// Check for ALC Extensions
|
||||
if (alcIsExtensionPresent(device, "ALC_EXT_CAPTURE") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("ALC_EXT_CAPTURE");
|
||||
if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("ALC_EXT_EFX");
|
||||
|
||||
// Check for AL Extensions
|
||||
if (alIsExtensionPresent("AL_EXT_OFFSET") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("AL_EXT_OFFSET");
|
||||
// Check for AL Extensions
|
||||
if (alIsExtensionPresent("AL_EXT_OFFSET") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("AL_EXT_OFFSET");
|
||||
|
||||
if (alIsExtensionPresent("AL_EXT_LINEAR_DISTANCE") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("AL_EXT_LINEAR_DISTANCE");
|
||||
if (alIsExtensionPresent("AL_EXT_EXPONENT_DISTANCE") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("AL_EXT_EXPONENT_DISTANCE");
|
||||
if (alIsExtensionPresent("AL_EXT_LINEAR_DISTANCE") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("AL_EXT_LINEAR_DISTANCE");
|
||||
if (alIsExtensionPresent("AL_EXT_EXPONENT_DISTANCE") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("AL_EXT_EXPONENT_DISTANCE");
|
||||
|
||||
if (alIsExtensionPresent("EAX2.0") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("EAX2.0");
|
||||
if (alIsExtensionPresent("EAX3.0") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("EAX3.0");
|
||||
if (alIsExtensionPresent("EAX4.0") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("EAX4.0");
|
||||
if (alIsExtensionPresent("EAX5.0") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("EAX5.0");
|
||||
if (alIsExtensionPresent("EAX2.0") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("EAX2.0");
|
||||
if (alIsExtensionPresent("EAX3.0") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("EAX3.0");
|
||||
if (alIsExtensionPresent("EAX4.0") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("EAX4.0");
|
||||
if (alIsExtensionPresent("EAX5.0") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("EAX5.0");
|
||||
|
||||
if (alIsExtensionPresent("EAX-RAM") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("EAX-RAM");
|
||||
if (alIsExtensionPresent("EAX-RAM") == AL_TRUE)
|
||||
ALDeviceInfo.pvstrExtensions->push_back("EAX-RAM");
|
||||
|
||||
// Get Source Count
|
||||
ALDeviceInfo.uiSourceCount = GetMaxNumSources();
|
||||
// Get Source Count
|
||||
ALDeviceInfo.uiSourceCount = GetMaxNumSources();
|
||||
|
||||
vDeviceInfo.push_back(ALDeviceInfo);
|
||||
}
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(context);
|
||||
}
|
||||
alcCloseDevice(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
//}
|
||||
vDeviceInfo.push_back(ALDeviceInfo);
|
||||
}
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(context);
|
||||
}
|
||||
alcCloseDevice(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
ResetFilters();
|
||||
ResetFilters();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -144,16 +159,16 @@ ALDeviceList::ALDeviceList()
|
||||
*/
|
||||
ALDeviceList::~ALDeviceList()
|
||||
{
|
||||
for (auto& di : vDeviceInfo)
|
||||
{
|
||||
if (di.pvstrExtensions)
|
||||
{
|
||||
di.pvstrExtensions->clear();
|
||||
delete di.pvstrExtensions;
|
||||
}
|
||||
}
|
||||
for (auto& di : vDeviceInfo)
|
||||
{
|
||||
if (di.pvstrExtensions)
|
||||
{
|
||||
di.pvstrExtensions->clear();
|
||||
delete di.pvstrExtensions;
|
||||
}
|
||||
}
|
||||
|
||||
vDeviceInfo.clear();
|
||||
vDeviceInfo.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -161,32 +176,33 @@ ALDeviceList::~ALDeviceList()
|
||||
*/
|
||||
s32 ALDeviceList::GetNumDevices()
|
||||
{
|
||||
return (s32)vDeviceInfo.size();
|
||||
return (s32)vDeviceInfo.size();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the device name at an index in the complete device list
|
||||
*/
|
||||
char * ALDeviceList::GetDeviceName(s32 index)
|
||||
char* ALDeviceList::GetDeviceName(s32 index)
|
||||
{
|
||||
if (index < GetNumDevices())
|
||||
return (char *)vDeviceInfo[index].strDeviceName.c_str();
|
||||
else
|
||||
return nullptr;
|
||||
if (index < GetNumDevices())
|
||||
return (char*)vDeviceInfo[index].strDeviceName.c_str();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the major and minor version numbers for a device at a specified index in the complete list
|
||||
* Returns the major and minor version numbers for a device at a specified index in the complete
|
||||
* list
|
||||
*/
|
||||
void ALDeviceList::GetDeviceVersion(s32 index, s32 *major, s32 *minor)
|
||||
void ALDeviceList::GetDeviceVersion(s32 index, s32* major, s32* minor)
|
||||
{
|
||||
if (index < GetNumDevices())
|
||||
{
|
||||
if (major)
|
||||
*major = vDeviceInfo[index].iMajorVersion;
|
||||
if (minor)
|
||||
*minor = vDeviceInfo[index].iMinorVersion;
|
||||
}
|
||||
if (index < GetNumDevices())
|
||||
{
|
||||
if (major)
|
||||
*major = vDeviceInfo[index].iMajorVersion;
|
||||
if (minor)
|
||||
*minor = vDeviceInfo[index].iMinorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -194,32 +210,32 @@ void ALDeviceList::GetDeviceVersion(s32 index, s32 *major, s32 *minor)
|
||||
*/
|
||||
u32 ALDeviceList::GetMaxNumSources(s32 index)
|
||||
{
|
||||
if (index < GetNumDevices())
|
||||
return vDeviceInfo[index].uiSourceCount;
|
||||
else
|
||||
return 0;
|
||||
if (index < GetNumDevices())
|
||||
return vDeviceInfo[index].uiSourceCount;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the extension is supported on the given device
|
||||
*/
|
||||
bool ALDeviceList::IsExtensionSupported(s32 index, char *szExtName)
|
||||
bool ALDeviceList::IsExtensionSupported(s32 index, char* szExtName)
|
||||
{
|
||||
bool bReturn = false;
|
||||
bool bReturn = false;
|
||||
|
||||
if (index < GetNumDevices())
|
||||
{
|
||||
for (auto& ext : *vDeviceInfo[index].pvstrExtensions)
|
||||
{
|
||||
if (!strcasecmp(ext.c_str(), szExtName))
|
||||
{
|
||||
bReturn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index < GetNumDevices())
|
||||
{
|
||||
for (auto& ext : *vDeviceInfo[index].pvstrExtensions)
|
||||
{
|
||||
if (!strcasecmp(ext.c_str(), szExtName))
|
||||
{
|
||||
bReturn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bReturn;
|
||||
return bReturn;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -227,7 +243,7 @@ bool ALDeviceList::IsExtensionSupported(s32 index, char *szExtName)
|
||||
*/
|
||||
s32 ALDeviceList::GetDefaultDevice()
|
||||
{
|
||||
return defaultDeviceIndex;
|
||||
return defaultDeviceIndex;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -235,15 +251,15 @@ s32 ALDeviceList::GetDefaultDevice()
|
||||
*/
|
||||
void ALDeviceList::FilterDevicesMinVer(s32 major, s32 minor)
|
||||
{
|
||||
s32 dMajor = 0, dMinor = 0;
|
||||
for (u32 i = 0; i < vDeviceInfo.size(); i++)
|
||||
{
|
||||
GetDeviceVersion(i, &dMajor, &dMinor);
|
||||
if ((dMajor < major) || ((dMajor == major) && (dMinor < minor)))
|
||||
{
|
||||
vDeviceInfo[i].bSelected = false;
|
||||
}
|
||||
}
|
||||
s32 dMajor = 0, dMinor = 0;
|
||||
for (u32 i = 0; i < vDeviceInfo.size(); i++)
|
||||
{
|
||||
GetDeviceVersion(i, &dMajor, &dMinor);
|
||||
if ((dMajor < major) || ((dMajor == major) && (dMinor < minor)))
|
||||
{
|
||||
vDeviceInfo[i].bSelected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -251,39 +267,39 @@ void ALDeviceList::FilterDevicesMinVer(s32 major, s32 minor)
|
||||
*/
|
||||
void ALDeviceList::FilterDevicesMaxVer(s32 major, s32 minor)
|
||||
{
|
||||
s32 dMajor = 0, dMinor = 0;
|
||||
for (u32 i = 0; i < vDeviceInfo.size(); i++)
|
||||
{
|
||||
GetDeviceVersion(i, &dMajor, &dMinor);
|
||||
if ((dMajor > major) || ((dMajor == major) && (dMinor > minor)))
|
||||
{
|
||||
vDeviceInfo[i].bSelected = false;
|
||||
}
|
||||
}
|
||||
s32 dMajor = 0, dMinor = 0;
|
||||
for (u32 i = 0; i < vDeviceInfo.size(); i++)
|
||||
{
|
||||
GetDeviceVersion(i, &dMajor, &dMinor);
|
||||
if ((dMajor > major) || ((dMajor == major) && (dMinor > minor)))
|
||||
{
|
||||
vDeviceInfo[i].bSelected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Deselects device which don't support the given extension name
|
||||
*/
|
||||
void ALDeviceList::FilterDevicesExtension(char *szExtName)
|
||||
void ALDeviceList::FilterDevicesExtension(char* szExtName)
|
||||
{
|
||||
bool bFound;
|
||||
bool bFound;
|
||||
|
||||
for (auto& di : vDeviceInfo)
|
||||
{
|
||||
bFound = false;
|
||||
for (auto& ext : *di.pvstrExtensions)
|
||||
{
|
||||
if (!strcasecmp(ext.c_str(), szExtName))
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto& di : vDeviceInfo)
|
||||
{
|
||||
bFound = false;
|
||||
for (auto& ext : *di.pvstrExtensions)
|
||||
{
|
||||
if (!strcasecmp(ext.c_str(), szExtName))
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bFound)
|
||||
di.bSelected = false;
|
||||
}
|
||||
if (!bFound)
|
||||
di.bSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -291,12 +307,12 @@ void ALDeviceList::FilterDevicesExtension(char *szExtName)
|
||||
*/
|
||||
void ALDeviceList::ResetFilters()
|
||||
{
|
||||
for (s32 i = 0; i < GetNumDevices(); i++)
|
||||
{
|
||||
vDeviceInfo[i].bSelected = true;
|
||||
}
|
||||
for (s32 i = 0; i < GetNumDevices(); i++)
|
||||
{
|
||||
vDeviceInfo[i].bSelected = true;
|
||||
}
|
||||
|
||||
filterIndex = 0;
|
||||
filterIndex = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -304,18 +320,18 @@ void ALDeviceList::ResetFilters()
|
||||
*/
|
||||
s32 ALDeviceList::GetFirstFilteredDevice()
|
||||
{
|
||||
s32 i;
|
||||
s32 i;
|
||||
|
||||
for (i = 0; i < GetNumDevices(); i++)
|
||||
{
|
||||
if (vDeviceInfo[i].bSelected == true)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < GetNumDevices(); i++)
|
||||
{
|
||||
if (vDeviceInfo[i].bSelected == true)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
filterIndex = i + 1;
|
||||
return i;
|
||||
filterIndex = i + 1;
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -323,18 +339,18 @@ s32 ALDeviceList::GetFirstFilteredDevice()
|
||||
*/
|
||||
s32 ALDeviceList::GetNextFilteredDevice()
|
||||
{
|
||||
s32 i;
|
||||
s32 i;
|
||||
|
||||
for (i = filterIndex; i < GetNumDevices(); i++)
|
||||
{
|
||||
if (vDeviceInfo[i].bSelected == true)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = filterIndex; i < GetNumDevices(); i++)
|
||||
{
|
||||
if (vDeviceInfo[i].bSelected == true)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
filterIndex = i + 1;
|
||||
return i;
|
||||
filterIndex = i + 1;
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -342,29 +358,29 @@ s32 ALDeviceList::GetNextFilteredDevice()
|
||||
*/
|
||||
u32 ALDeviceList::GetMaxNumSources()
|
||||
{
|
||||
ALuint uiSources[256];
|
||||
u32 iSourceCount = 0;
|
||||
ALuint uiSources[256];
|
||||
u32 iSourceCount = 0;
|
||||
|
||||
// Clear AL Error Code
|
||||
alGetError();
|
||||
// Clear AL Error Code
|
||||
alGetError();
|
||||
|
||||
// Generate up to 256 Sources, checking for any errors
|
||||
for (iSourceCount = 0; iSourceCount < 256; iSourceCount++)
|
||||
{
|
||||
alGenSources(1, &uiSources[iSourceCount]);
|
||||
if (alGetError() != AL_NO_ERROR)
|
||||
break;
|
||||
}
|
||||
// Generate up to 256 Sources, checking for any errors
|
||||
for (iSourceCount = 0; iSourceCount < 256; iSourceCount++)
|
||||
{
|
||||
alGenSources(1, &uiSources[iSourceCount]);
|
||||
if (alGetError() != AL_NO_ERROR)
|
||||
break;
|
||||
}
|
||||
|
||||
// Release the Sources
|
||||
alDeleteSources(iSourceCount, uiSources);
|
||||
if (alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
for (auto& uiSource : uiSources)
|
||||
{
|
||||
alDeleteSources(1, &uiSource);
|
||||
}
|
||||
}
|
||||
// Release the Sources
|
||||
alDeleteSources(iSourceCount, uiSources);
|
||||
if (alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
for (auto& uiSource : uiSources)
|
||||
{
|
||||
alDeleteSources(1, &uiSource);
|
||||
}
|
||||
}
|
||||
|
||||
return iSourceCount;
|
||||
return iSourceCount;
|
||||
}
|
||||
|
@ -10,43 +10,43 @@
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(disable: 4786) //disable warning "identifier was truncated to
|
||||
//'255' characters in the browser information"
|
||||
#pragma warning(disable : 4786) // disable warning "identifier was truncated to
|
||||
//'255' characters in the browser information"
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
std::string strDeviceName;
|
||||
s32 iMajorVersion;
|
||||
s32 iMinorVersion;
|
||||
u32 uiSourceCount;
|
||||
std::vector<std::string>* pvstrExtensions;
|
||||
bool bSelected;
|
||||
std::string strDeviceName;
|
||||
s32 iMajorVersion;
|
||||
s32 iMinorVersion;
|
||||
u32 uiSourceCount;
|
||||
std::vector<std::string>* pvstrExtensions;
|
||||
bool bSelected;
|
||||
} ALDEVICEINFO, *LPALDEVICEINFO;
|
||||
|
||||
class ALDeviceList
|
||||
{
|
||||
private:
|
||||
std::vector<ALDEVICEINFO> vDeviceInfo;
|
||||
s32 defaultDeviceIndex;
|
||||
s32 filterIndex;
|
||||
std::vector<ALDEVICEINFO> vDeviceInfo;
|
||||
s32 defaultDeviceIndex;
|
||||
s32 filterIndex;
|
||||
|
||||
public:
|
||||
ALDeviceList();
|
||||
~ALDeviceList();
|
||||
s32 GetNumDevices();
|
||||
char* GetDeviceName(s32 index);
|
||||
void GetDeviceVersion(s32 index, s32* major, s32* minor);
|
||||
u32 GetMaxNumSources(s32 index);
|
||||
bool IsExtensionSupported(s32 index, char* szExtName);
|
||||
s32 GetDefaultDevice();
|
||||
void FilterDevicesMinVer(s32 major, s32 minor);
|
||||
void FilterDevicesMaxVer(s32 major, s32 minor);
|
||||
void FilterDevicesExtension(char* szExtName);
|
||||
void ResetFilters();
|
||||
s32 GetFirstFilteredDevice();
|
||||
s32 GetNextFilteredDevice();
|
||||
ALDeviceList();
|
||||
~ALDeviceList();
|
||||
s32 GetNumDevices();
|
||||
char* GetDeviceName(s32 index);
|
||||
void GetDeviceVersion(s32 index, s32* major, s32* minor);
|
||||
u32 GetMaxNumSources(s32 index);
|
||||
bool IsExtensionSupported(s32 index, char* szExtName);
|
||||
s32 GetDefaultDevice();
|
||||
void FilterDevicesMinVer(s32 major, s32 minor);
|
||||
void FilterDevicesMaxVer(s32 major, s32 minor);
|
||||
void FilterDevicesExtension(char* szExtName);
|
||||
void ResetFilters();
|
||||
s32 GetFirstFilteredDevice();
|
||||
s32 GetNextFilteredDevice();
|
||||
|
||||
private:
|
||||
u32 GetMaxNumSources();
|
||||
u32 GetMaxNumSources();
|
||||
};
|
||||
|
Reference in New Issue
Block a user