]> git.ipfire.org Git - thirdparty/shairport-sync.git/commitdiff
Rewrite and unbreak sndio backend
authorTobias Kortkamp <git@tobik.me>
Wed, 12 Apr 2017 13:15:57 +0000 (15:15 +0200)
committerTobias Kortkamp <git@tobik.me>
Sat, 15 Apr 2017 00:28:53 +0000 (02:28 +0200)
audio_sndio.c
configure.ac
scripts/shairport-sync.conf

index e8f148076bc89b2f2541080a03af85f85470ed87..8aa1d8ab3a5e8bfc79f12ed14c950ee9339ba90e 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * sndio output driver. This file is part of Shairport.
  * Copyright (c) 2013 Dimitri Sokolyuk <demon@dim13.org>
+ * Copyright (c) 2017 Tobias Kortkamp <t@tobik.me>
  *
  * Modifications for audio synchronisation
  * and related work, copyright (c) Mike Brady 2014
  */
 
 #include "audio.h"
+#include "common.h"
+#include <pthread.h>
 #include <sndio.h>
 #include <stdio.h>
+#include <string.h>
 #include <unistd.h>
 
-static struct sio_hdl *sio;
-static struct sio_par par;
+static void help(void);
+static int init(int, char **);
+static void onmove_cb(void *, int);
+static void deinit(void);
+static void start(int, int);
+static void play(short[], int);
+static void stop(void);
+static void onmove_cb(void *, int);
+static int delay(long *);
+static void flush(void);
+
+audio_output audio_sndio = {.name = "sndio",
+                            .help = &help,
+                            .init = &init,
+                            .deinit = &deinit,
+                            .start = &start,
+                            .stop = &stop,
+                            .flush = &flush,
+                            .delay = &delay,
+                            .play = &play,
+                            .volume = NULL,
+                            .parameters = NULL,
+                            .mute = NULL};
+
+static pthread_mutex_t sndio_mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct sio_hdl *hdl;
+static int framesize;
+static size_t played;
+static size_t written;
+
+struct sndio_formats {
+  const char *name;
+  enum sps_format_t fmt;
+
+  unsigned int bits;
+  unsigned int bps;
+  unsigned int sig;
+  unsigned int le;
+};
+
+static struct sndio_formats formats[] = {
+    {"s8", SPS_FORMAT_S8, 8, 1, 1, SIO_LE_NATIVE},
+    {"u8", SPS_FORMAT_U8, 8, 1, 0, SIO_LE_NATIVE},
+    {"s16", SPS_FORMAT_S16, 16, 2, 1, SIO_LE_NATIVE},
+    {"s24", SPS_FORMAT_S24, 24, 4, 1, SIO_LE_NATIVE},
+    {"s24le3", SPS_FORMAT_S24_3LE, 24, 3, 1, 1},
+    {"s24be3", SPS_FORMAT_S24_3BE, 24, 3, 1, 0},
+    {"s32", SPS_FORMAT_S32, 24, 4, 1, SIO_LE_NATIVE}};
+
+static void help() {
+  printf("    -d output-device    set the output device [default*|...]\n");
+}
 
 static int init(int argc, char **argv) {
-  sio = sio_open(SIO_DEVANY, SIO_PLAY, 0);
-  if (!sio)
-    die("sndio: cannot connect to sound server");
+  struct sio_par par;
+  int i, found, opt, round, rate, bufsz;
+  const char *devname, *tmp;
 
   sio_initpar(&par);
-
-  par.bits = 16;
   par.rate = 44100;
   par.pchan = 2;
-  par.le = SIO_LE_NATIVE;
+  par.bits = 16;
+  par.bps = SIO_BPS(par.bits);
+  par.le = 1;
   par.sig = 1;
 
-  if (!sio_setpar(sio, &par))
+  if (!config_lookup_string(config.cfg, "sndio.device", &devname))
+    devname = SIO_DEVANY;
+  if (config_lookup_int(config.cfg, "sndio.rate", &rate)) {
+    if (rate % 44100 == 0 && rate >= 44100 && rate <= 352800) {
+      par.rate = rate;
+    } else {
+      die("sndio: output rate must be a multiple of 44100 and 44100 <= rate <= "
+          "352800");
+    }
+  }
+  if (config_lookup_int(config.cfg, "sndio.bufsz", &bufsz)) {
+    if (bufsz > 0) {
+      par.appbufsz = bufsz;
+    } else {
+      die("sndio: bufsz must be > 0");
+    }
+  }
+  if (config_lookup_int(config.cfg, "sndio.round", &round)) {
+    if (round > 0) {
+      par.round = round;
+    } else {
+      die("sndio: round must be > 0");
+    }
+  }
+  if (config_lookup_string(config.cfg, "sndio.format", &tmp)) {
+    for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
+      if (strcmp(formats[i].name, tmp) == 0) {
+        config.output_format = formats[i].fmt;
+        found = 1;
+        break;
+      }
+    }
+    if (!found)
+      die("Invalid output format \"%s\". Should be one of: s8, u8, s16, s24, "
+          "s24le3, s24be3, s32",
+          tmp);
+  }
+
+  optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
+  argv--;     // so we shift the arguments to satisfy getopt()
+  argc++;
+  while ((opt = getopt(argc, argv, "d:")) > 0) {
+    switch (opt) {
+    case 'd':
+      devname = optarg;
+      break;
+    default:
+      help();
+      die("Invalid audio option -%c specified", opt);
+    }
+  }
+  if (optind < argc)
+    die("Invalid audio argument: %s", argv[optind]);
+
+  pthread_mutex_lock(&sndio_mutex);
+  debug(1, "Output device name is \"%s\".", devname);
+  hdl = sio_open(devname, SIO_PLAY, 0);
+  if (!hdl)
+    die("sndio: cannot open audio device");
+
+  written = played = 0;
+
+  for (i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
+    if (formats[i].fmt == config.output_format) {
+      par.bits = formats[i].bits;
+      par.bps = formats[i].bps;
+      par.sig = formats[i].sig;
+      par.le = formats[i].le;
+      break;
+    }
+  }
+
+  if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par))
     die("sndio: failed to set audio parameters");
-  if (!sio_getpar(sio, &par))
-    die("sndio: failed to get audio parameters");
+  for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
+    if (formats[i].bits == par.bits && formats[i].bps == par.bps &&
+        formats[i].sig == par.sig && formats[i].le == par.le) {
+      config.output_format = formats[i].fmt;
+      found = 1;
+      break;
+    }
+  }
+  if (!found)
+    die("sndio: failed to negotiate audio parameters");
 
-  config.audio_backend_buffer_desired_length = 44100; // one second.
+  framesize = par.bps * par.pchan;
+  config.output_rate = par.rate;
+  config.audio_backend_buffer_desired_length = 1.0 * par.bufsz / par.rate;
   config.audio_backend_latency_offset = 0;
 
+  sio_onmove(hdl, onmove_cb, NULL);
+
+  pthread_mutex_unlock(&sndio_mutex);
+
   return 0;
 }
 
-static void deinit(void) { sio_close(sio); }
+static void deinit() {
+  pthread_mutex_lock(&sndio_mutex);
+  sio_close(hdl);
+  pthread_mutex_unlock(&sndio_mutex);
+}
 
-static void start(int sample_rate) {
-  sio_start(sio);
+static void start(int sample_rate, int sample_format) {
+  pthread_mutex_lock(&sndio_mutex);
+  if (!sio_start(hdl))
+    die("sndio: unable to start");
+  written = played = 0;
+  pthread_mutex_unlock(&sndio_mutex);
 }
 
-static void play(short buf[], int samples) {
-  sio_write(sio, (char *)buf, samples * par.bps * par.pchan);
+static void play(short buf[], int frames) {
+  if (frames > 0) {
+    pthread_mutex_lock(&sndio_mutex);
+    written += sio_write(hdl, buf, frames * framesize);
+    pthread_mutex_unlock(&sndio_mutex);
+  }
 }
 
-static void stop(void) { sio_stop(sio); }
+static void stop() {
+  pthread_mutex_lock(&sndio_mutex);
+  if (!sio_stop(hdl))
+    die("sndio: unable to stop");
+  written = played = 0;
+  pthread_mutex_unlock(&sndio_mutex);
+}
 
-static void help(void) {
-  printf("    There are no options for sndio audio.\n");
-  printf("    Use AUDIODEVICE environment variable.\n");
+static void onmove_cb(void *arg, int delta) {
+  pthread_mutex_lock(&sndio_mutex);
+  played += delta;
+  pthread_mutex_unlock(&sndio_mutex);
 }
 
-static void volume(double vol) {
-  unsigned int v = vol * SIO_MAXVOL;
-  sio_setvol(sio, v);
+static int delay(long *_delay) {
+  pthread_mutex_lock(&sndio_mutex);
+  if (played > (written / framesize)) {
+    /* _delay is in frames */
+    *_delay = (written / framesize) - played;
+  } else {
+    *_delay = 0;
+  }
+  pthread_mutex_unlock(&sndio_mutex);
+  return 0;
 }
 
-audio_output audio_sndio = {.name = "sndio",
-                            .help = &help,
-                            .init = &init,
-                            .deinit = &deinit,
-                            .start = &start,
-                            .stop = &stop,
-                            .flush = NULL,
-                            .delay = NULL,
-                            .play = &play,
-                            .volume = &volume,
-                            .parameters = NULL,
-                            .mute = NULL};
+static void flush() {
+  pthread_mutex_lock(&sndio_mutex);
+  if (!sio_stop(hdl) || !sio_start(hdl))
+    die("sndio: unable to flush");
+  written = played = 0;
+  pthread_mutex_unlock(&sndio_mutex);
+}
index a900949be0cba226b2586adfc011dae875f2832b..816858b6e45d65d4de74a4f60109fe95ffe04147 100644 (file)
@@ -206,8 +206,8 @@ AC_ARG_WITH(alsa, [  --with-alsa = choose ALSA API support (GNU/Linux only)],
 AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"])
 
 # Look for SNDIO flag
-AC_ARG_WITH(sndio, [  --with-sndio = choose SNDIO API support (FreeBSD) -- probably broken], [
-  AC_MSG_RESULT(>>Including a SNDIO back end -- N.B. this is probably broken!)
+AC_ARG_WITH(sndio, [  --with-sndio = choose SNDIO API support], [
+  AC_MSG_RESULT(>>Including a SNDIO back end)
   HAS_SNDIO=1
   AC_DEFINE([CONFIG_SNDIO], 1, [Needed by the compiler.])
   AC_CHECK_LIB([sndio], [sio_open], , AC_MSG_ERROR(SNDIO support requires the sndio library!))], )
index 0ebe702122b4692cdaac7a03d65f55f3d28fac9a..9af4460f6f259c438576d349a2fd896eeedabbc1 100644 (file)
@@ -127,6 +127,16 @@ ao =
 //  audio_backend_buffer_desired_length = 1.0;  // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length.
 };
 
+// These are parameters for the "sndio" audio back end. All are optional.
+sndio =
+{
+//  device = "snd/0"; // the name of the output device
+//  rate = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability.
+//  format = "s16le"; // set output format. can be "u8", "s8", "s16le", "s24le", "s24le3", "s24be3", "s32"
+//  round = <number>; // set the period size near to this value
+//  bufsz = <number>; // set the buffer size near to this value
+};
+
 // Static latency settings are deprecated and the settings have been removed.