]>
Commit | Line | Data |
---|---|---|
629e202f | 1 | /* |
c677ad48 | 2 | * sndio output driver. This file is part of Shairport Sync. |
629e202f | 3 | * Copyright (c) 2013 Dimitri Sokolyuk <demon@dim13.org> |
da12c449 | 4 | * Copyright (c) 2017 Tobias Kortkamp <t@tobik.me> |
629e202f | 5 | * |
107df039 | 6 | * Modifications for audio synchronisation |
55d5d496 | 7 | * and related work, copyright (c) Mike Brady 2014 -- 2022 |
107df039 MB |
8 | * All rights reserved. |
9 | * | |
629e202f DS |
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 | ||
064bd293 | 23 | #include "audio.h" |
da12c449 TK |
24 | #include "common.h" |
25 | #include <pthread.h> | |
064bd293 | 26 | #include <sndio.h> |
629e202f | 27 | #include <stdio.h> |
da12c449 | 28 | #include <string.h> |
629e202f | 29 | #include <unistd.h> |
629e202f | 30 | |
da12c449 TK |
31 | static pthread_mutex_t sndio_mutex = PTHREAD_MUTEX_INITIALIZER; |
32 | static struct sio_hdl *hdl; | |
33 | static int framesize; | |
34 | static size_t played; | |
35 | static size_t written; | |
55d5d496 | 36 | uint64_t time_of_last_onmove_cb; |
4d169d7b | 37 | int at_least_one_onmove_cb_seen; |
55d5d496 | 38 | |
b7864b4e | 39 | struct sio_par par; |
da12c449 TK |
40 | |
41 | struct sndio_formats { | |
42 | const char *name; | |
85ba78b6 | 43 | sps_format_t fmt; |
dc393b98 | 44 | unsigned int rate; |
da12c449 TK |
45 | unsigned int bits; |
46 | unsigned int bps; | |
47 | unsigned int sig; | |
48 | unsigned int le; | |
49 | }; | |
50 | ||
03eb21a9 MB |
51 | static struct sndio_formats formats[] = {{"S8", SPS_FORMAT_S8, 44100, 8, 1, 1, SIO_LE_NATIVE}, |
52 | {"U8", SPS_FORMAT_U8, 44100, 8, 1, 0, SIO_LE_NATIVE}, | |
53 | {"S16", SPS_FORMAT_S16, 44100, 16, 2, 1, SIO_LE_NATIVE}, | |
c8b0be30 MB |
54 | {"AUTOMATIC", SPS_FORMAT_S16, 44100, 16, 2, 1, |
55 | SIO_LE_NATIVE}, // TODO: make this really automatic? | |
03eb21a9 MB |
56 | {"S24", SPS_FORMAT_S24, 44100, 24, 4, 1, SIO_LE_NATIVE}, |
57 | {"S24_3LE", SPS_FORMAT_S24_3LE, 44100, 24, 3, 1, 1}, | |
58 | {"S24_3BE", SPS_FORMAT_S24_3BE, 44100, 24, 3, 1, 0}, | |
55d5d496 | 59 | {"S32", SPS_FORMAT_S32, 44100, 32, 4, 1, SIO_LE_NATIVE}}; |
da12c449 | 60 | |
e513e533 | 61 | static void help() { printf(" -d output-device set the output device [default*|...]\n"); } |
629e202f | 62 | |
fd880056 MB |
63 | void onmove_cb(__attribute__((unused)) void *arg, int delta) { |
64 | time_of_last_onmove_cb = get_absolute_time_in_ns(); | |
65 | at_least_one_onmove_cb_seen = 1; | |
66 | played += delta; | |
55d5d496 MB |
67 | } |
68 | ||
54250f75 | 69 | static int init(int argc, char **argv) { |
0cd8a2ce MB |
70 | int found, opt, round, rate, bufsz; |
71 | unsigned int i; | |
da12c449 | 72 | const char *devname, *tmp; |
629e202f | 73 | |
b7864b4e | 74 | // set up default values first |
629e202f | 75 | |
87a0475c | 76 | sio_initpar(&par); |
87a0475c MB |
77 | par.rate = 44100; |
78 | par.pchan = 2; | |
da12c449 TK |
79 | par.bits = 16; |
80 | par.bps = SIO_BPS(par.bits); | |
81 | par.le = 1; | |
87a0475c | 82 | par.sig = 1; |
67aed3a3 | 83 | devname = SIO_DEVANY; |
e513e533 | 84 | |
b7864b4e | 85 | config.audio_backend_buffer_desired_length = 1.0; |
80f15e1f MB |
86 | config.audio_backend_buffer_interpolation_threshold_in_seconds = |
87 | 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation | |
88 | // instead. | |
b7864b4e | 89 | config.audio_backend_latency_offset = 0; |
629e202f | 90 | |
e513e533 MB |
91 | // get settings from settings file |
92 | ||
b7864b4e MB |
93 | // do the "general" audio options. Note, these options are in the "general" stanza! |
94 | parse_general_audio_options(); | |
95 | ||
96 | // get the specific settings | |
e513e533 | 97 | |
67aed3a3 MB |
98 | if (config.cfg != NULL) { |
99 | if (!config_lookup_string(config.cfg, "sndio.device", &devname)) | |
100 | devname = SIO_DEVANY; | |
101 | if (config_lookup_int(config.cfg, "sndio.rate", &rate)) { | |
102 | if (rate % 44100 == 0 && rate >= 44100 && rate <= 352800) { | |
fd880056 | 103 | par.rate = rate; |
67aed3a3 MB |
104 | } else { |
105 | die("sndio: output rate must be a multiple of 44100 and 44100 <= rate <= " | |
106 | "352800"); | |
107 | } | |
da12c449 | 108 | } |
67aed3a3 MB |
109 | if (config_lookup_int(config.cfg, "sndio.bufsz", &bufsz)) { |
110 | if (bufsz > 0) { | |
111 | par.appbufsz = bufsz; | |
112 | } else { | |
113 | die("sndio: bufsz must be > 0"); | |
114 | } | |
da12c449 | 115 | } |
67aed3a3 MB |
116 | if (config_lookup_int(config.cfg, "sndio.round", &round)) { |
117 | if (round > 0) { | |
118 | par.round = round; | |
119 | } else { | |
120 | die("sndio: round must be > 0"); | |
121 | } | |
da12c449 | 122 | } |
67aed3a3 MB |
123 | if (config_lookup_string(config.cfg, "sndio.format", &tmp)) { |
124 | for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { | |
3a180706 | 125 | if (strcasecmp(formats[i].name, tmp) == 0) { |
67aed3a3 MB |
126 | config.output_format = formats[i].fmt; |
127 | found = 1; | |
128 | break; | |
129 | } | |
da12c449 | 130 | } |
67aed3a3 | 131 | if (!found) |
3a180706 | 132 | die("Invalid output format \"%s\". Should be one of: S8, U8, S16, S24, " |
20967812 | 133 | "S24_3LE, S24_3BE, S32, Automatic", |
67aed3a3 | 134 | tmp); |
da12c449 | 135 | } |
da12c449 | 136 | } |
da12c449 TK |
137 | optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour |
138 | argv--; // so we shift the arguments to satisfy getopt() | |
139 | argc++; | |
140 | while ((opt = getopt(argc, argv, "d:")) > 0) { | |
141 | switch (opt) { | |
142 | case 'd': | |
143 | devname = optarg; | |
144 | break; | |
145 | default: | |
146 | help(); | |
147 | die("Invalid audio option -%c specified", opt); | |
148 | } | |
149 | } | |
150 | if (optind < argc) | |
151 | die("Invalid audio argument: %s", argv[optind]); | |
55d5d496 MB |
152 | pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1); |
153 | // pthread_mutex_lock(&sndio_mutex); | |
154 | debug(1, "sndio: output device name is \"%s\".", devname); | |
fd880056 MB |
155 | debug(1, "sndio: rate: %u.", par.rate); |
156 | debug(1, "sndio: bits: %u.", par.bits); | |
55d5d496 | 157 | |
da12c449 TK |
158 | hdl = sio_open(devname, SIO_PLAY, 0); |
159 | if (!hdl) | |
160 | die("sndio: cannot open audio device"); | |
161 | ||
162 | written = played = 0; | |
fd880056 | 163 | time_of_last_onmove_cb = 0; |
4d169d7b | 164 | at_least_one_onmove_cb_seen = 0; |
da12c449 TK |
165 | |
166 | for (i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { | |
167 | if (formats[i].fmt == config.output_format) { | |
168 | par.bits = formats[i].bits; | |
169 | par.bps = formats[i].bps; | |
170 | par.sig = formats[i].sig; | |
171 | par.le = formats[i].le; | |
172 | break; | |
173 | } | |
174 | } | |
c8b0be30 | 175 | |
da12c449 | 176 | if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) |
87a0475c | 177 | die("sndio: failed to set audio parameters"); |
da12c449 | 178 | for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { |
e513e533 | 179 | if (formats[i].bits == par.bits && formats[i].bps == par.bps && formats[i].sig == par.sig && |
03eb21a9 | 180 | formats[i].le == par.le && formats[i].rate == par.rate) { |
da12c449 TK |
181 | config.output_format = formats[i].fmt; |
182 | found = 1; | |
183 | break; | |
184 | } | |
185 | } | |
186 | if (!found) | |
1b47023c | 187 | die("sndio: could not set output device to the required format and rate."); |
87a0475c | 188 | |
da12c449 TK |
189 | framesize = par.bps * par.pchan; |
190 | config.output_rate = par.rate; | |
14bfba27 MB |
191 | if (par.rate == 0) { |
192 | die("sndio: par.rate set to zero."); | |
193 | } | |
194 | ||
da12c449 | 195 | config.audio_backend_buffer_desired_length = 1.0 * par.bufsz / par.rate; |
54250f75 | 196 | config.audio_backend_latency_offset = 0; |
629e202f | 197 | |
da12c449 TK |
198 | sio_onmove(hdl, onmove_cb, NULL); |
199 | ||
55d5d496 MB |
200 | // pthread_mutex_unlock(&sndio_mutex); |
201 | pthread_cleanup_pop(1); // unlock the mutex | |
14bfba27 MB |
202 | if (framesize == 0) { |
203 | die("sndio: framesize set to zero."); | |
204 | } | |
87a0475c | 205 | return 0; |
629e202f DS |
206 | } |
207 | ||
da12c449 | 208 | static void deinit() { |
55d5d496 MB |
209 | // pthread_mutex_lock(&sndio_mutex); |
210 | pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1); | |
da12c449 | 211 | sio_close(hdl); |
55d5d496 MB |
212 | // pthread_mutex_unlock(&sndio_mutex); |
213 | pthread_cleanup_pop(1); // unlock the mutex | |
da12c449 | 214 | } |
629e202f | 215 | |
0cd8a2ce MB |
216 | static void start(__attribute__((unused)) int sample_rate, |
217 | __attribute__((unused)) int sample_format) { | |
55d5d496 MB |
218 | // pthread_mutex_lock(&sndio_mutex); |
219 | pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1); | |
55d5d496 MB |
220 | at_least_one_onmove_cb_seen = 0; |
221 | // any previously-reported frame count | |
222 | ||
da12c449 TK |
223 | if (!sio_start(hdl)) |
224 | die("sndio: unable to start"); | |
225 | written = played = 0; | |
4d169d7b MB |
226 | time_of_last_onmove_cb = 0; |
227 | at_least_one_onmove_cb_seen = 0; | |
55d5d496 MB |
228 | // pthread_mutex_unlock(&sndio_mutex); |
229 | pthread_cleanup_pop(1); // unlock the mutex | |
629e202f DS |
230 | } |
231 | ||
fd880056 MB |
232 | static int play(void *buf, int frames, __attribute__((unused)) int sample_type, |
233 | __attribute__((unused)) uint32_t timestamp, | |
234 | __attribute__((unused)) uint64_t playtime) { | |
da12c449 | 235 | if (frames > 0) { |
55d5d496 MB |
236 | // pthread_mutex_lock(&sndio_mutex); |
237 | pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1); | |
da12c449 | 238 | written += sio_write(hdl, buf, frames * framesize); |
55d5d496 MB |
239 | // pthread_mutex_unlock(&sndio_mutex); |
240 | pthread_cleanup_pop(1); // unlock the mutex | |
da12c449 | 241 | } |
ea20840d | 242 | return 0; |
629e202f DS |
243 | } |
244 | ||
da12c449 | 245 | static void stop() { |
55d5d496 | 246 | pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1); |
57af5f3c | 247 | |
da12c449 TK |
248 | if (!sio_stop(hdl)) |
249 | die("sndio: unable to stop"); | |
250 | written = played = 0; | |
55d5d496 | 251 | pthread_cleanup_pop(1); // unlock the mutex |
da12c449 | 252 | } |
629e202f | 253 | |
55d5d496 MB |
254 | int get_delay(long *delay) { |
255 | int response = 0; | |
4d169d7b MB |
256 | size_t estimated_extra_frames_output = 0; |
257 | if (at_least_one_onmove_cb_seen) { // when output starts, the onmove_cb callback will be made | |
b9cf91b2 | 258 | // calculate the difference in time between now and when the last callback occurred, |
4d169d7b | 259 | // and use it to estimate the frames that would have been output |
fd880056 | 260 | uint64_t time_difference = get_absolute_time_in_ns() - time_of_last_onmove_cb; |
c2655b03 | 261 | uint64_t frame_difference = (time_difference * par.rate) / 1000000000; |
54d761ff | 262 | estimated_extra_frames_output = frame_difference; |
c2655b03 | 263 | // sanity check -- total estimate can not exceed frames written. |
54d761ff | 264 | if ((estimated_extra_frames_output + played) > written / framesize) { |
c2655b03 MB |
265 | // debug(1,"play estimate fails sanity check, possibly due to running on a VM"); |
266 | estimated_extra_frames_output = 0; // can't make any sensible guess | |
54d761ff | 267 | } |
e513e533 MB |
268 | // debug(1,"Frames played to last cb: %d, estimated to current time: |
269 | // %d.",played,estimated_extra_frames_output); | |
4d169d7b | 270 | } |
432b56c6 | 271 | if (delay != NULL) |
55d5d496 | 272 | *delay = (written / framesize) - (played + estimated_extra_frames_output); |
55d5d496 MB |
273 | return response; |
274 | } | |
275 | ||
276 | static int delay(long *delay) { | |
277 | int result = 0; | |
278 | pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1); | |
279 | result = get_delay(delay); | |
280 | pthread_cleanup_pop(1); // unlock the mutex | |
281 | return result; | |
629e202f DS |
282 | } |
283 | ||
da12c449 | 284 | static void flush() { |
55d5d496 MB |
285 | // pthread_mutex_lock(&sndio_mutex); |
286 | pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1); | |
da12c449 TK |
287 | if (!sio_stop(hdl) || !sio_start(hdl)) |
288 | die("sndio: unable to flush"); | |
289 | written = played = 0; | |
55d5d496 MB |
290 | // pthread_mutex_unlock(&sndio_mutex); |
291 | pthread_cleanup_pop(1); // unlock the mutex | |
292 | } | |
293 | ||
fd880056 MB |
294 | audio_output audio_sndio = {.name = "sndio", |
295 | .help = &help, | |
296 | .init = &init, | |
297 | .deinit = &deinit, | |
298 | .prepare = NULL, | |
299 | .start = &start, | |
300 | .stop = &stop, | |
301 | .is_running = NULL, | |
302 | .flush = &flush, | |
303 | .delay = &delay, | |
304 | .stats = NULL, | |
305 | .play = &play, | |
306 | .volume = NULL, | |
307 | .parameters = NULL, | |
308 | .mute = NULL}; |