chiark / gitweb /
uaudio: more sophisticated choice of default playback API
[disorder] / lib / uaudio-command.c
index 6b7ef66309b108653c53303894b70d10c773b62f..f93454a10210dddcf4bfbb7ca83595aff77f2ca5 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 /** @file lib/uaudio-command.c
- * @brief Support for commmand backend */
+ * @brief Support for commmand backend
+ *
+ * We use the code in @ref lib/uaudio-schedule.c to ensure that we write at
+ * approximately the 'real' rate.  For disorder-playrtp this isn't very useful
+ * (thought it might reduce the size of various buffers downstream of us) but
+ * when run from the speaker it means that pausing stands a chance of working.
+ */
 #include "common.h"
 
 #include <errno.h>
@@ -28,6 +34,7 @@
 #include "mem.h"
 #include "wstat.h"
 #include "uaudio.h"
+#include "configuration.h"
 
 /** @brief Pipe to subprocess */
 static int command_fd;
@@ -35,8 +42,12 @@ static int command_fd;
 /** @brief Child process ID */
 static pid_t command_pid;
 
+/** @brief Whether to suspend on pause */
+static int command_suspend_on_pause;
+
 static const char *const command_options[] = {
   "command",
+  "pause-mode",
   NULL
 };
 
@@ -50,10 +61,10 @@ static void command_wait(void) {
   while((rc = waitpid(command_pid, &w, 0) < 0 && errno == EINTR))
     ;
   if(rc < 0)
-    fatal(errno, "waitpid");
+    disorder_fatal(errno, "waitpid");
   if(w) {
     ws = wstat(w);
-    error(0, "command subprocess %s", ws);
+    disorder_error(0, "command subprocess %s", ws);
     xfree(ws);
   }
 }
@@ -63,8 +74,8 @@ static void command_open(void) {
   int pfd[2];
   const char *command;
 
-  if(!(command = uaudio_get("command")))
-    fatal(0, "'command' not set");
+  if(!(command = uaudio_get("command", NULL)))
+    disorder_fatal(0, "'command' not set");
   xpipe(pfd);
   command_pid = xfork();
   if(!command_pid) {
@@ -77,14 +88,19 @@ static void command_open(void) {
      * format.  The original intended model is that you adapt DisOrder to the
      * command you run but it'd be nice to support the opposite. */
     execl("/bin/sh", "sh", "-c", command, (char *)0);
-    fatal(errno, "error executing /bin/sh");
+    disorder_fatal(errno, "error executing /bin/sh");
   }
   close(pfd[0]);
   command_fd = pfd[1];
 }
 
 /** @brief Send audio data to subprocess */
-static size_t command_play(void *buffer, size_t nsamples) {
+static size_t command_play(void *buffer, size_t nsamples, unsigned flags) {
+  uaudio_schedule_sync();
+  /* If we're pausing and want that to be represented by stopping writing, we
+   * just pretend */
+  if((flags & UAUDIO_PAUSED) && command_suspend_on_pause)
+    return nsamples;
   const size_t bytes = nsamples * uaudio_sample_size;
   int written = write(command_fd, buffer, bytes);
   if(written < 0) {
@@ -92,25 +108,41 @@ static size_t command_play(void *buffer, size_t nsamples) {
     case EINTR:
       return 0;                        /* will retry */
     case EPIPE:
-      error(0, "audio command subprocess terminated");
+      disorder_error(0, "audio command subprocess terminated");
       command_wait();
       command_open();
       return 0;                        /* will retry */
     default:
-      fatal(errno, "error writing to audio command subprocess");
+      disorder_fatal(errno, "error writing to audio command subprocess");
     }
   }
-  return written / uaudio_sample_size;
+  /* TODO what if we write a partial sample? Actually reasonably unlikely but
+   * not impossible.  Maybe someone who actually uses this backend should sort
+   * it out. */
+  const size_t written_samples = written / uaudio_sample_size;
+  uaudio_schedule_sent(written_samples);
+  return written_samples;
 }
 
 static void command_start(uaudio_callback *callback,
-                      void *userdata) {
+                          void *userdata) {
+  const char *pausemode = uaudio_get("pause-mode", "silence");
+  unsigned flags = 0;
+
+  if(!strcmp(pausemode, "silence"))
+    command_suspend_on_pause = 0;
+  else if(!strcmp(pausemode, "suspend"))
+    command_suspend_on_pause = 1;
+  else
+    disorder_fatal(0, "unknown pause mode '%s'", pausemode);
   command_open();
+  uaudio_schedule_init();
   uaudio_thread_start(callback,
                       userdata,
                       command_play,
                       uaudio_channels,
-                     4096 / uaudio_sample_size);
+                     4096 / uaudio_sample_size,
+                      flags);
 }
 
 static void command_stop(void) {
@@ -118,12 +150,9 @@ static void command_stop(void) {
   command_wait();
 }
 
-static void command_activate(void) {
-  uaudio_thread_activate();
-}
-
-static void command_deactivate(void) {
-  uaudio_thread_deactivate();
+static void command_configure(void) {
+  uaudio_set("command", config->speaker_command);
+  uaudio_set("pause-mode", config->pause_mode);
 }
 
 const struct uaudio uaudio_command = {
@@ -131,8 +160,10 @@ const struct uaudio uaudio_command = {
   .options = command_options,
   .start = command_start,
   .stop = command_stop,
-  .activate = command_activate,
-  .deactivate = command_deactivate
+  .activate = uaudio_thread_activate,
+  .deactivate = uaudio_thread_deactivate,
+  .configure = command_configure,
+  .flags = UAUDIO_API_CLIENT | UAUDIO_API_SERVER,
 };
 
 /*