From c645bbf04f3a2d814d029a3e6010b5622d0e9224 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Fri, 20 Jul 2018 03:15:16 +0100 Subject: [PATCH] lib/uaudio-pulseaudio.c: Rewrite using the asynchronous API. Organization: Straylight/Edgeware From: Mark Wooding It's really hard to do volume control using the `simple' synchronous API, because that involves `introspection' requests to the server, which aren't part of the API, and it involves knowing the stream index, which the API won't give us. The rewrite isn't especially painful. --- lib/uaudio-pulseaudio.c | 144 +++++++++++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 37 deletions(-) diff --git a/lib/uaudio-pulseaudio.c b/lib/uaudio-pulseaudio.c index f0124be..7908e6d 100644 --- a/lib/uaudio-pulseaudio.c +++ b/lib/uaudio-pulseaudio.c @@ -1,6 +1,6 @@ /* * This file is part of DisOrder. - * Copyright (C) 2013 Richard Kettlewell + * Copyright (C) 2013 Richard Kettlewell, 2018 Mark Wooding * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,8 +21,10 @@ #if HAVE_PULSEAUDIO -#include +#include #include +#include +#include #include "mem.h" #include "log.h" @@ -34,61 +36,129 @@ static const char *const pulseaudio_options[] = { NULL }; -static pa_simple *pulseaudio_simple_handle; +static pa_threaded_mainloop *loop; +static pa_context *ctx; +static pa_stream *str; + +#define PAERRSTR pa_strerror(pa_context_errno(ctx)) + +/** @brief Callback: wake up main loop when the context is ready. */ +static void cb_ctxstate(attribute((unused)) pa_context *xctx, + attribute((unused)) void *p) { + pa_context_state_t st = pa_context_get_state(ctx); + switch(st) { + case PA_CONTEXT_READY: + pa_threaded_mainloop_signal(loop, 0); + break; + default: + if(!PA_CONTEXT_IS_GOOD(st)) + disorder_fatal(0, "pulseaudio failed: %s", PAERRSTR); + } +} + +/** @brief Callback: wake up main loop when the stream is ready. */ +static void cb_strstate(attribute((unused)) pa_stream *xstr, + attribute((unused)) void *p) { + pa_stream_state_t st = pa_stream_get_state(str); + switch(st) { + case PA_STREAM_READY: + pa_threaded_mainloop_signal(loop, 0); + break; + default: + if(!PA_STREAM_IS_GOOD(st)) + disorder_fatal(0, "pulseaudio failed: %s", PAERRSTR); + } +} + +/** @brief Callback: wake up main loop when there's output buffer space. */ +static void cb_wakeup(attribute((unused)) pa_stream *xstr, + attribute((unused)) size_t sz, + attribute((unused)) void *p) + { pa_threaded_mainloop_signal(loop, 0); } /** @brief Open the PulseAudio sound device */ static void pulseaudio_open() { + /* Much of the following is cribbed from the PulseAudio `simple' source. */ + pa_sample_spec ss; - int error; + + /* Set up the sample format. */ ss.format = -1; switch(uaudio_bits) { - case 8: - if(!uaudio_signed) - ss.format = PA_SAMPLE_U8; - break; - case 16: - if(uaudio_signed) - ss.format = PA_SAMPLE_S16NE; - break; - case 32: - if(uaudio_signed) - ss.format = PA_SAMPLE_S32NE; - break; + case 8: if(!uaudio_signed) ss.format = PA_SAMPLE_U8; break; + case 16: if(uaudio_signed) ss.format = PA_SAMPLE_S16NE; break; + case 32: if(uaudio_signed) ss.format = PA_SAMPLE_S32NE; break; } if(ss.format == -1) disorder_fatal(0, "unsupported uaudio format (%d, %d)", uaudio_bits, uaudio_signed); ss.channels = uaudio_channels; ss.rate = uaudio_rate; - pulseaudio_simple_handle = pa_simple_new(NULL, - uaudio_get("application", "DisOrder"), - PA_STREAM_PLAYBACK, - NULL, - "DisOrder", - &ss, - NULL, - NULL, - &error); - if(!pulseaudio_simple_handle) - disorder_fatal(0, "pa_simple_new: %s", pa_strerror(error)); + + /* Create the random PulseAudio pieces. */ + loop = pa_threaded_mainloop_new(); + if(!loop) disorder_fatal(0, "failed to create pulseaudio main loop"); + pa_threaded_mainloop_lock(loop); + ctx = pa_context_new(pa_threaded_mainloop_get_api(loop), + uaudio_get("application", "DisOrder")); + if(!ctx) disorder_fatal(0, "failed to create pulseaudio context"); + pa_context_set_state_callback(ctx, cb_ctxstate, 0); + if(pa_context_connect(ctx, 0, 0, 0) < 0) + disorder_fatal(0, "failed to connect to pulseaudio server: %s", + PAERRSTR); + + /* Set the main loop going. */ + if(pa_threaded_mainloop_start(loop) < 0) + disorder_fatal(0, "failed to start pulseaudio main loop"); + while(pa_context_get_state(ctx) != PA_CONTEXT_READY) + pa_threaded_mainloop_wait(loop); + + /* Set up my stream. */ + str = pa_stream_new(ctx, "DisOrder", &ss, 0); + if(!str) + disorder_fatal(0, "failed to create pulseaudio stream: %s", PAERRSTR); + pa_stream_set_write_callback(str, cb_wakeup, 0); + if(pa_stream_connect_playback(str, 0, 0, + PA_STREAM_ADJUST_LATENCY, + 0, 0)) + disorder_fatal(0, "failed to connect pulseaudio stream for playback: %s", + PAERRSTR); + pa_stream_set_state_callback(str, cb_strstate, 0); + + /* Wait until the stream is ready. */ + while(pa_stream_get_state(str) != PA_STREAM_READY) + pa_threaded_mainloop_wait(loop); + + /* All done. */ + pa_threaded_mainloop_unlock(loop); } /** @brief Close the PulseAudio sound device */ static void pulseaudio_close(void) { - pa_simple_free(pulseaudio_simple_handle); - pulseaudio_simple_handle = NULL; + if(loop) pa_threaded_mainloop_stop(loop); + if(str) { pa_stream_unref(str); str = 0; } + if(ctx) { pa_context_disconnect(ctx); pa_context_unref(ctx); ctx = 0; } + if(loop) { pa_threaded_mainloop_free(loop); loop = 0; } } /** @brief Actually play sound via PulseAudio */ static size_t pulseaudio_play(void *buffer, size_t samples, - unsigned attribute((unused)) flags) { - int error; - int ret = pa_simple_write(pulseaudio_simple_handle, - buffer, - samples * uaudio_sample_size, - &error); - if(ret < 0) - disorder_fatal(0, "pa_simple_write: %s", pa_strerror(error)); + attribute((unused)) unsigned flags) { + unsigned char *p = buffer; + size_t n, sz = samples*uaudio_sample_size; + + pa_threaded_mainloop_lock(loop); + while(sz) { + + /* Wait until some output space becomes available. */ + while(!(n = pa_stream_writable_size(str))) + pa_threaded_mainloop_wait(loop); + if(n > sz) n = sz; + if(pa_stream_write(str, p, n, 0, 0, PA_SEEK_RELATIVE) < 0) + disorder_fatal(0, "failed to write pulseaudio data: %s", PAERRSTR); + p += n; sz -= n; + } + pa_threaded_mainloop_unlock(loop); return samples; } -- [mdw]