/*
* 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
#if HAVE_PULSEAUDIO
-#include <pulse/simple.h>
+#include <pulse/context.h>
#include <pulse/error.h>
+#include <pulse/thread-mainloop.h>
+#include <pulse/stream.h>
#include "mem.h"
#include "log.h"
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;
}