diff options
| author | Pierre Bourdon <delroth@gmail.com> | 2014-02-07 11:51:25 +0100 |
|---|---|---|
| committer | Pierre Bourdon <delroth@gmail.com> | 2014-02-07 11:51:25 +0100 |
| commit | 4f97666bfddc8c080465c76c63bae9e3023ce2e6 (patch) | |
| tree | 781ab53211d589a94bc26da50d082ebe86f2a403 /Source | |
| parent | 70e2ed320de81a33b146469c87e9a1203c13c171 (diff) | |
| parent | ab124b96c4d952255986779b0dec303ef8921fd3 (diff) | |
Merge pull request #20 from degasus/pulseaudioRewrite
Pulseaudio: rewrite the pa backend with the async api
Diffstat (limited to 'Source')
| -rw-r--r-- | Source/Core/AudioCommon/PulseAudioStream.cpp | 146 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/PulseAudioStream.h | 30 |
2 files changed, 132 insertions, 44 deletions
diff --git a/Source/Core/AudioCommon/PulseAudioStream.cpp b/Source/Core/AudioCommon/PulseAudioStream.cpp index 13307a8fe0..fe68d0eb6c 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.cpp +++ b/Source/Core/AudioCommon/PulseAudioStream.cpp @@ -11,30 +11,28 @@ namespace { -const size_t BUFFER_SAMPLES = 512; +const size_t BUFFER_SAMPLES = 512; // ~10 ms const size_t CHANNEL_COUNT = 2; -const size_t BUFFER_SIZE = BUFFER_SAMPLES * CHANNEL_COUNT; +const size_t BUFFER_SIZE = BUFFER_SAMPLES * CHANNEL_COUNT * sizeof(s16); } PulseAudio::PulseAudio(CMixer *mixer) : SoundStream(mixer) - , mix_buffer(BUFFER_SIZE) - , thread() - , run_thread() - , pa() + , m_thread() + , m_run_thread() {} bool PulseAudio::Start() { - run_thread = true; - thread = std::thread(std::mem_fun(&PulseAudio::SoundLoop), this); + m_run_thread = true; + m_thread = std::thread(std::mem_fun(&PulseAudio::SoundLoop), this); return true; } void PulseAudio::Stop() { - run_thread = false; - thread.join(); + m_run_thread = false; + m_thread.join(); } void PulseAudio::Update() @@ -49,11 +47,11 @@ void PulseAudio::SoundLoop() if (PulseInit()) { - while (run_thread) - { - m_mixer->Mix(&mix_buffer[0], mix_buffer.size() / CHANNEL_COUNT); - Write(&mix_buffer[0], mix_buffer.size() * sizeof(s16)); - } + while (m_run_thread.load() && m_pa_connected == 1 && m_pa_error >= 0) + m_pa_error = pa_mainloop_iterate(m_pa_ml, 1, NULL); + + if(m_pa_error < 0) + ERROR_LOG(AUDIO, "PulseAudio error: %s", pa_strerror(m_pa_error)); PulseShutdown(); } @@ -61,39 +59,117 @@ void PulseAudio::SoundLoop() bool PulseAudio::PulseInit() { - pa_sample_spec ss = {}; + 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, NULL, PA_CONTEXT_NOFLAGS, NULL); + 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, NULL); + + 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; ss.format = PA_SAMPLE_S16LE; ss.channels = 2; ss.rate = m_mixer->GetSampleRate(); - - int error; - pa = pa_simple_new(nullptr, "dolphin-emu", PA_STREAM_PLAYBACK, - nullptr, "audio", &ss, nullptr, nullptr, &error); - - if (!pa) + m_pa_s = pa_stream_new(m_pa_ctx, "Playback", &ss, NULL); + 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_SIZE; // 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, NULL, &m_pa_ba, flags, NULL, NULL); + if (m_pa_error < 0) { - ERROR_LOG(AUDIO, "PulseAudio failed to initialize: %s", - pa_strerror(error)); + ERROR_LOG(AUDIO, "PulseAudio failed to initialize: %s", pa_strerror(m_pa_error)); return false; } - else - { - NOTICE_LOG(AUDIO, "Pulse successfully initialized."); - return true; - } + + INFO_LOG(AUDIO, "Pulse successfully initialized"); + return true; } void PulseAudio::PulseShutdown() { - pa_simple_free(pa); + pa_context_disconnect(m_pa_ctx); + pa_context_unref(m_pa_ctx); + pa_mainloop_free(m_pa_ml); } -void PulseAudio::Write(const void *data, size_t length) +void PulseAudio::StateCallback(pa_context* c) { - int error; - if (pa_simple_write(pa, data, length, &error) < 0) + pa_context_state_t state = pa_context_get_state(c); + switch (state) { - ERROR_LOG(AUDIO, "PulseAudio failed to write data: %s", - pa_strerror(error)); + 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_SIZE; + pa_stream_set_buffer_attr(s, &m_pa_ba, NULL, NULL); + + WARN_LOG(AUDIO, "pulseaudio underflow, new latency: %d bytes", m_pa_ba.tlength); +} + +void PulseAudio::WriteCallback(pa_stream* s, size_t length) +{ + // fetch dst buffer directly from pulseaudio, so no memcpy is needed + void* buffer; + m_pa_error = pa_stream_begin_write(s, &buffer, &length); + + if (!buffer || m_pa_error < 0) + return; // error will be printed from main loop + + m_mixer->Mix((s16*) buffer, length / sizeof(s16) / CHANNEL_COUNT); + m_pa_error = pa_stream_write(s, buffer, length, NULL, 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); +} + +void PulseAudio::UnderflowCallback(pa_stream* s, void* userdata) +{ + 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); +} diff --git a/Source/Core/AudioCommon/PulseAudioStream.h b/Source/Core/AudioCommon/PulseAudioStream.h index 8be8eae11d..3164a4ee94 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.h +++ b/Source/Core/AudioCommon/PulseAudioStream.h @@ -6,17 +6,16 @@ #define _PULSE_AUDIO_STREAM_H #if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO -#include <pulse/simple.h> -#include <pulse/error.h> +#include <pulse/pulseaudio.h> #endif +#include <atomic> + #include "Common.h" #include "SoundStream.h" #include "Thread.h" -#include <vector> - class PulseAudio : public SoundStream { #if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO @@ -32,18 +31,31 @@ public: virtual void Update(); + void StateCallback(pa_context *c); + void WriteCallback(pa_stream *s, size_t length); + void UnderflowCallback(pa_stream *s); + private: virtual void SoundLoop(); bool PulseInit(); void PulseShutdown(); - void Write(const void *data, size_t bytes); - std::vector<s16> mix_buffer; - std::thread thread; - volatile bool run_thread; + // 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; - pa_simple* pa; + 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; #else public: PulseAudio(CMixer *mixer) : SoundStream(mixer) {} |
