1 /*
2  * sndio output driver. This file is part of Shairport Sync.
3  * Copyright (c) 2013 Dimitri Sokolyuk <demon@dim13.org>
4  * Copyright (c) 2017 Tobias Kortkamp <t@tobik.me>
5  *
6  * Modifications for audio synchronisation
7  * and related work, copyright (c) Mike Brady 2014 -- 2017
8  * All rights reserved.
9  *
10  * Permission to use, copy, modify, and distribute this software for any
11  * purpose with or without fee is hereby granted, provided that the above
12  * copyright notice and this permission notice appear in all copies.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21  */
22 
23 #include "audio.h"
24 #include "common.h"
25 #include <pthread.h>
26 #include <sndio.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 static void help(void);
32 static int init(int, char **);
33 static void onmove_cb(void *, int);
34 static void deinit(void);
35 static void start(int, int);
36 static int play(void *, int);
37 static void stop(void);
38 static void onmove_cb(void *, int);
39 static int delay(long *);
40 static void flush(void);
41 
42 audio_output audio_sndio = {.name = "sndio",
43                             .help = &help,
44                             .init = &init,
45                             .deinit = &deinit,
46                             .prepare = NULL,
47                             .start = &start,
48                             .stop = &stop,
49                             .is_running = NULL,
50                             .flush = &flush,
51                             .delay = &delay,
52                             .play = &play,
53                             .volume = NULL,
54                             .parameters = NULL,
55                             .mute = NULL};
56 
57 static pthread_mutex_t sndio_mutex = PTHREAD_MUTEX_INITIALIZER;
58 static struct sio_hdl *hdl;
59 static int framesize;
60 static size_t played;
61 static size_t written;
62 int64_t time_of_last_onmove_cb;
63 int at_least_one_onmove_cb_seen;
64 struct sio_par par;
65 
66 struct sndio_formats {
67   const char *name;
68   sps_format_t fmt;
69   unsigned int rate;
70   unsigned int bits;
71   unsigned int bps;
72   unsigned int sig;
73   unsigned int le;
74 };
75 
76 static struct sndio_formats formats[] = {{"S8", SPS_FORMAT_S8, 44100, 8, 1, 1, SIO_LE_NATIVE},
77                                          {"U8", SPS_FORMAT_U8, 44100, 8, 1, 0, SIO_LE_NATIVE},
78                                          {"S16", SPS_FORMAT_S16, 44100, 16, 2, 1, SIO_LE_NATIVE},
79                                          {"AUTOMATIC", SPS_FORMAT_S16, 44100, 16, 2, 1,
80                                           SIO_LE_NATIVE}, // TODO: make this really automatic?
81                                          {"S24", SPS_FORMAT_S24, 44100, 24, 4, 1, SIO_LE_NATIVE},
82                                          {"S24_3LE", SPS_FORMAT_S24_3LE, 44100, 24, 3, 1, 1},
83                                          {"S24_3BE", SPS_FORMAT_S24_3BE, 44100, 24, 3, 1, 0},
84                                          {"S32", SPS_FORMAT_S32, 44100, 24, 4, 1, SIO_LE_NATIVE}};
85 
help()86 static void help() { printf("    -d output-device    set the output device [default*|...]\n"); }
87 
init(int argc,char ** argv)88 static int init(int argc, char **argv) {
89   int found, opt, round, rate, bufsz;
90   unsigned int i;
91   const char *devname, *tmp;
92 
93   // set up default values first
94 
95   sio_initpar(&par);
96   par.rate = 44100;
97   par.pchan = 2;
98   par.bits = 16;
99   par.bps = SIO_BPS(par.bits);
100   par.le = 1;
101   par.sig = 1;
102   devname = SIO_DEVANY;
103 
104   config.audio_backend_buffer_desired_length = 1.0;
105   config.audio_backend_buffer_interpolation_threshold_in_seconds =
106       0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation
107             // instead.
108   config.audio_backend_latency_offset = 0;
109 
110   // get settings from settings file
111 
112   // do the "general" audio  options. Note, these options are in the "general" stanza!
113   parse_general_audio_options();
114 
115   // get the specific settings
116 
117   if (config.cfg != NULL) {
118     if (!config_lookup_string(config.cfg, "sndio.device", &devname))
119       devname = SIO_DEVANY;
120     if (config_lookup_int(config.cfg, "sndio.rate", &rate)) {
121       if (rate % 44100 == 0 && rate >= 44100 && rate <= 352800) {
122         par.rate = rate;
123       } else {
124         die("sndio: output rate must be a multiple of 44100 and 44100 <= rate <= "
125             "352800");
126       }
127     }
128     if (config_lookup_int(config.cfg, "sndio.bufsz", &bufsz)) {
129       if (bufsz > 0) {
130         par.appbufsz = bufsz;
131       } else {
132         die("sndio: bufsz must be > 0");
133       }
134     }
135     if (config_lookup_int(config.cfg, "sndio.round", &round)) {
136       if (round > 0) {
137         par.round = round;
138       } else {
139         die("sndio: round must be > 0");
140       }
141     }
142     if (config_lookup_string(config.cfg, "sndio.format", &tmp)) {
143       for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
144         if (strcasecmp(formats[i].name, tmp) == 0) {
145           config.output_format = formats[i].fmt;
146           found = 1;
147           break;
148         }
149       }
150       if (!found)
151         die("Invalid output format \"%s\". Should be one of: S8, U8, S16, S24, "
152             "S24_3LE, S24_3BE, S32, Automatic",
153             tmp);
154     }
155   }
156   optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
157   argv--;     // so we shift the arguments to satisfy getopt()
158   argc++;
159   while ((opt = getopt(argc, argv, "d:")) > 0) {
160     switch (opt) {
161     case 'd':
162       devname = optarg;
163       break;
164     default:
165       help();
166       die("Invalid audio option -%c specified", opt);
167     }
168   }
169   if (optind < argc)
170     die("Invalid audio argument: %s", argv[optind]);
171   pthread_mutex_lock(&sndio_mutex);
172   debug(1, "Output device name is \"%s\".", devname);
173   hdl = sio_open(devname, SIO_PLAY, 0);
174   if (!hdl)
175     die("sndio: cannot open audio device");
176 
177   written = played = 0;
178   time_of_last_onmove_cb = 0;
179   at_least_one_onmove_cb_seen = 0;
180 
181   for (i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
182     if (formats[i].fmt == config.output_format) {
183       par.bits = formats[i].bits;
184       par.bps = formats[i].bps;
185       par.sig = formats[i].sig;
186       par.le = formats[i].le;
187       break;
188     }
189   }
190 
191   if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par))
192     die("sndio: failed to set audio parameters");
193   for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
194     if (formats[i].bits == par.bits && formats[i].bps == par.bps && formats[i].sig == par.sig &&
195         formats[i].le == par.le && formats[i].rate == par.rate) {
196       config.output_format = formats[i].fmt;
197       found = 1;
198       break;
199     }
200   }
201   if (!found)
202     die("sndio: could not set output device to the required format and rate.");
203 
204   framesize = par.bps * par.pchan;
205   config.output_rate = par.rate;
206   config.audio_backend_buffer_desired_length = 1.0 * par.bufsz / par.rate;
207   config.audio_backend_latency_offset = 0;
208 
209   sio_onmove(hdl, onmove_cb, NULL);
210 
211   pthread_mutex_unlock(&sndio_mutex);
212   return 0;
213 }
214 
deinit()215 static void deinit() {
216   pthread_mutex_lock(&sndio_mutex);
217   sio_close(hdl);
218   pthread_mutex_unlock(&sndio_mutex);
219 }
220 
start(int sample_rate,int sample_format)221 static void start(__attribute__((unused)) int sample_rate,
222                   __attribute__((unused)) int sample_format) {
223   pthread_mutex_lock(&sndio_mutex);
224   if (!sio_start(hdl))
225     die("sndio: unable to start");
226   written = played = 0;
227   time_of_last_onmove_cb = 0;
228   at_least_one_onmove_cb_seen = 0;
229   pthread_mutex_unlock(&sndio_mutex);
230 }
231 
play(void * buf,int frames)232 static int play(void *buf, int frames) {
233   if (frames > 0) {
234     pthread_mutex_lock(&sndio_mutex);
235     written += sio_write(hdl, buf, frames * framesize);
236     pthread_mutex_unlock(&sndio_mutex);
237   }
238   return 0;
239 }
240 
stop()241 static void stop() {
242   pthread_mutex_lock(&sndio_mutex);
243   if (!sio_stop(hdl))
244     die("sndio: unable to stop");
245   written = played = 0;
246   pthread_mutex_unlock(&sndio_mutex);
247 }
248 
onmove_cb(void * arg,int delta)249 static void onmove_cb(__attribute__((unused)) void *arg, int delta) {
250   time_of_last_onmove_cb = get_absolute_time_in_ns();
251   at_least_one_onmove_cb_seen = 1;
252   played += delta;
253 }
254 
delay(long * _delay)255 static int delay(long *_delay) {
256   pthread_mutex_lock(&sndio_mutex);
257   size_t estimated_extra_frames_output = 0;
258   if (at_least_one_onmove_cb_seen) { // when output starts, the onmove_cb callback will be made
259     // calculate the difference in time between now and when the last callback occurred,
260     // and use it to estimate the frames that would have been output
261     uint64_t time_difference = get_absolute_time_in_ns() - time_of_last_onmove_cb;
262     uint64_t frame_difference = (time_difference * par.rate) / 1000000000;
263     estimated_extra_frames_output = frame_difference;
264     // sanity check -- total estimate can not exceed frames written.
265     if ((estimated_extra_frames_output + played) > written / framesize) {
266       // debug(1,"play estimate fails sanity check, possibly due to running on a VM");
267       estimated_extra_frames_output = 0; // can't make any sensible guess
268     }
269     // debug(1,"Frames played to last cb: %d, estimated to current time:
270     // %d.",played,estimated_extra_frames_output);
271   }
272   *_delay = (written / framesize) - (played + estimated_extra_frames_output);
273   pthread_mutex_unlock(&sndio_mutex);
274   return 0;
275 }
276 
flush()277 static void flush() {
278   pthread_mutex_lock(&sndio_mutex);
279   if (!sio_stop(hdl) || !sio_start(hdl))
280     die("sndio: unable to flush");
281   written = played = 0;
282   pthread_mutex_unlock(&sndio_mutex);
283 }
284