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