From 5c646d334a419c1c2b9b126621d30b63436e556f Mon Sep 17 00:00:00 2001 From: degasus Date: Fri, 31 Jan 2014 22:00:33 +0100 Subject: [PATCH] Pulseaudio: rewrite the pa backend with the async api The default async api allow us to set some latency options. The old one (simple API) was the lazy way to go for usual audio where latency doesn't matter. This also streams audio, so it should be a bit faster then the old one. --- CMakeLists.txt | 2 +- Source/Core/AudioCommon/PulseAudioStream.cpp | 144 ++++++++++++++----- Source/Core/AudioCommon/PulseAudioStream.h | 28 ++-- 3 files changed, 130 insertions(+), 44 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dade7a6358..6b5d555ffa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -363,7 +363,7 @@ if(NOT ANDROID) message("bluez NOT found, disabling bluetooth support") endif(BLUEZ_FOUND) - check_lib(PULSEAUDIO libpulse-simple QUIET) + check_lib(PULSEAUDIO libpulse QUIET) if(PULSEAUDIO_FOUND) add_definitions(-DHAVE_PULSEAUDIO=1) message("PulseAudio found, enabling PulseAudio sound backend") diff --git a/Source/Core/AudioCommon/PulseAudioStream.cpp b/Source/Core/AudioCommon/PulseAudioStream.cpp index 13307a8fe0..685d5365a8 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 && 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(); + 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); - int error; - pa = pa_simple_new(nullptr, "dolphin-emu", PA_STREAM_PLAYBACK, - nullptr, "audio", &ss, nullptr, nullptr, &error); - - if (!pa) + // 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..d3087df263 100644 --- a/Source/Core/AudioCommon/PulseAudioStream.h +++ b/Source/Core/AudioCommon/PulseAudioStream.h @@ -6,8 +6,7 @@ #define _PULSE_AUDIO_STREAM_H #if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO -#include -#include +#include #endif #include "Common.h" @@ -15,8 +14,6 @@ #include "Thread.h" -#include - class PulseAudio : public SoundStream { #if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO @@ -32,18 +29,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 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); - pa_simple* pa; + std::thread m_thread; + bool m_run_thread; + + 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) {}