Rewrite and unbreak sndio backend

This commit is contained in:
Tobias Kortkamp
2017-04-15 02:28:53 +02:00
parent e3dab142cf
commit da12c44978
3 changed files with 224 additions and 53 deletions
+212 -51
View 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
@@ -20,58 +21,23 @@
*/
#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 int init(int argc, char **argv) {
sio = sio_open(SIO_DEVANY, SIO_PLAY, 0);
if (!sio)
die("sndio: cannot connect to sound server");
sio_initpar(&par);
par.bits = 16;
par.rate = 44100;
par.pchan = 2;
par.le = SIO_LE_NATIVE;
par.sig = 1;
if (!sio_setpar(sio, &par))
die("sndio: failed to set audio parameters");
if (!sio_getpar(sio, &par))
die("sndio: failed to get audio parameters");
config.audio_backend_buffer_desired_length = 44100; // one second.
config.audio_backend_latency_offset = 0;
return 0;
}
static void deinit(void) { sio_close(sio); }
static void start(int sample_rate) {
sio_start(sio);
}
static void play(short buf[], int samples) {
sio_write(sio, (char *)buf, samples * par.bps * par.pchan);
}
static void stop(void) { sio_stop(sio); }
static void help(void) {
printf(" There are no options for sndio audio.\n");
printf(" Use AUDIODEVICE environment variable.\n");
}
static void volume(double vol) {
unsigned int v = vol * SIO_MAXVOL;
sio_setvol(sio, v);
}
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,
@@ -79,9 +45,204 @@ audio_output audio_sndio = {.name = "sndio",
.deinit = &deinit,
.start = &start,
.stop = &stop,
.flush = NULL,
.delay = NULL,
.flush = &flush,
.delay = &delay,
.play = &play,
.volume = &volume,
.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) {
struct sio_par par;
int i, found, opt, round, rate, bufsz;
const char *devname, *tmp;
sio_initpar(&par);
par.rate = 44100;
par.pchan = 2;
par.bits = 16;
par.bps = SIO_BPS(par.bits);
par.le = 1;
par.sig = 1;
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");
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");
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() {
pthread_mutex_lock(&sndio_mutex);
sio_close(hdl);
pthread_mutex_unlock(&sndio_mutex);
}
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 frames) {
if (frames > 0) {
pthread_mutex_lock(&sndio_mutex);
written += sio_write(hdl, buf, frames * framesize);
pthread_mutex_unlock(&sndio_mutex);
}
}
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 onmove_cb(void *arg, int delta) {
pthread_mutex_lock(&sndio_mutex);
played += delta;
pthread_mutex_unlock(&sndio_mutex);
}
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;
}
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);
}