chiark / gitweb /
lib/uaudio-pulseaudio.c: Rewrite using the asynchronous API.
authorMark Wooding <mdw@distorted.org.uk>
Fri, 20 Jul 2018 02:15:16 +0000 (03:15 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 20 Jul 2018 11:30:27 +0000 (12:30 +0100)
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

index f0124be..7908e6d 100644 (file)
@@ -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
 
 #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"
@@ -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;
 }