From: Tobias Kortkamp Date: Wed, 12 Apr 2017 13:15:57 +0000 (+0200) Subject: Rewrite and unbreak sndio backend X-Git-Tag: 3.1.d6~21^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=da12c4497881fabcfe72ecdb032d98bc6162d2e9;p=thirdparty%2Fshairport-sync.git Rewrite and unbreak sndio backend --- diff --git a/audio_sndio.c b/audio_sndio.c index e8f14807..8aa1d8ab 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -1,6 +1,7 @@ /* * sndio output driver. This file is part of Shairport. * Copyright (c) 2013 Dimitri Sokolyuk + * Copyright (c) 2017 Tobias Kortkamp * * Modifications for audio synchronisation * and related work, copyright (c) Mike Brady 2014 @@ -20,68 +21,228 @@ */ #include "audio.h" +#include "common.h" +#include #include #include +#include #include -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); +} diff --git a/configure.ac b/configure.ac index a900949b..816858b6 100644 --- a/configure.ac +++ b/configure.ac @@ -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!))], ) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 0ebe7021..9af4460f 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -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 = ; // set the period size near to this value +// bufsz = ; // set the buffer size near to this value +}; + // Static latency settings are deprecated and the settings have been removed.