]> git.ipfire.org Git - thirdparty/shairport-sync.git/blob - audio_sndio.c
Update check_classic_mac_basic.yml
[thirdparty/shairport-sync.git] / audio_sndio.c
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 -- 2022
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 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;
36 uint64_t time_of_last_onmove_cb;
37 int at_least_one_onmove_cb_seen;
38
39 struct sio_par par;
40
41 struct sndio_formats {
42 const char *name;
43 sps_format_t fmt;
44 unsigned int rate;
45 unsigned int bits;
46 unsigned int bps;
47 unsigned int sig;
48 unsigned int le;
49 };
50
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},
54 {"AUTOMATIC", SPS_FORMAT_S16, 44100, 16, 2, 1,
55 SIO_LE_NATIVE}, // TODO: make this really automatic?
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},
59 {"S32", SPS_FORMAT_S32, 44100, 32, 4, 1, SIO_LE_NATIVE}};
60
61 static void help() { printf(" -d output-device set the output device [default*|...]\n"); }
62
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;
67 }
68
69 static int init(int argc, char **argv) {
70 int found, opt, round, rate, bufsz;
71 unsigned int i;
72 const char *devname, *tmp;
73
74 // set up default values first
75
76 sio_initpar(&par);
77 par.rate = 44100;
78 par.pchan = 2;
79 par.bits = 16;
80 par.bps = SIO_BPS(par.bits);
81 par.le = 1;
82 par.sig = 1;
83 devname = SIO_DEVANY;
84
85 config.audio_backend_buffer_desired_length = 1.0;
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.
89 config.audio_backend_latency_offset = 0;
90
91 // get settings from settings file
92
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
97
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) {
103 par.rate = rate;
104 } else {
105 die("sndio: output rate must be a multiple of 44100 and 44100 <= rate <= "
106 "352800");
107 }
108 }
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 }
115 }
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 }
122 }
123 if (config_lookup_string(config.cfg, "sndio.format", &tmp)) {
124 for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
125 if (strcasecmp(formats[i].name, tmp) == 0) {
126 config.output_format = formats[i].fmt;
127 found = 1;
128 break;
129 }
130 }
131 if (!found)
132 die("Invalid output format \"%s\". Should be one of: S8, U8, S16, S24, "
133 "S24_3LE, S24_3BE, S32, Automatic",
134 tmp);
135 }
136 }
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]);
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);
155 debug(1, "sndio: rate: %u.", par.rate);
156 debug(1, "sndio: bits: %u.", par.bits);
157
158 hdl = sio_open(devname, SIO_PLAY, 0);
159 if (!hdl)
160 die("sndio: cannot open audio device");
161
162 written = played = 0;
163 time_of_last_onmove_cb = 0;
164 at_least_one_onmove_cb_seen = 0;
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 }
175
176 if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par))
177 die("sndio: failed to set audio parameters");
178 for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
179 if (formats[i].bits == par.bits && formats[i].bps == par.bps && formats[i].sig == par.sig &&
180 formats[i].le == par.le && formats[i].rate == par.rate) {
181 config.output_format = formats[i].fmt;
182 found = 1;
183 break;
184 }
185 }
186 if (!found)
187 die("sndio: could not set output device to the required format and rate.");
188
189 framesize = par.bps * par.pchan;
190 config.output_rate = par.rate;
191 if (par.rate == 0) {
192 die("sndio: par.rate set to zero.");
193 }
194
195 config.audio_backend_buffer_desired_length = 1.0 * par.bufsz / par.rate;
196 config.audio_backend_latency_offset = 0;
197
198 sio_onmove(hdl, onmove_cb, NULL);
199
200 // pthread_mutex_unlock(&sndio_mutex);
201 pthread_cleanup_pop(1); // unlock the mutex
202 if (framesize == 0) {
203 die("sndio: framesize set to zero.");
204 }
205 return 0;
206 }
207
208 static void deinit() {
209 // pthread_mutex_lock(&sndio_mutex);
210 pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
211 sio_close(hdl);
212 // pthread_mutex_unlock(&sndio_mutex);
213 pthread_cleanup_pop(1); // unlock the mutex
214 }
215
216 static void start(__attribute__((unused)) int sample_rate,
217 __attribute__((unused)) int sample_format) {
218 // pthread_mutex_lock(&sndio_mutex);
219 pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
220 at_least_one_onmove_cb_seen = 0;
221 // any previously-reported frame count
222
223 if (!sio_start(hdl))
224 die("sndio: unable to start");
225 written = played = 0;
226 time_of_last_onmove_cb = 0;
227 at_least_one_onmove_cb_seen = 0;
228 // pthread_mutex_unlock(&sndio_mutex);
229 pthread_cleanup_pop(1); // unlock the mutex
230 }
231
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) {
235 if (frames > 0) {
236 // pthread_mutex_lock(&sndio_mutex);
237 pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
238 written += sio_write(hdl, buf, frames * framesize);
239 // pthread_mutex_unlock(&sndio_mutex);
240 pthread_cleanup_pop(1); // unlock the mutex
241 }
242 return 0;
243 }
244
245 static void stop() {
246 pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
247
248 if (!sio_stop(hdl))
249 die("sndio: unable to stop");
250 written = played = 0;
251 pthread_cleanup_pop(1); // unlock the mutex
252 }
253
254 int get_delay(long *delay) {
255 int response = 0;
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
258 // calculate the difference in time between now and when the last callback occurred,
259 // and use it to estimate the frames that would have been output
260 uint64_t time_difference = get_absolute_time_in_ns() - time_of_last_onmove_cb;
261 uint64_t frame_difference = (time_difference * par.rate) / 1000000000;
262 estimated_extra_frames_output = frame_difference;
263 // sanity check -- total estimate can not exceed frames written.
264 if ((estimated_extra_frames_output + played) > written / framesize) {
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
267 }
268 // debug(1,"Frames played to last cb: %d, estimated to current time:
269 // %d.",played,estimated_extra_frames_output);
270 }
271 if (delay != NULL)
272 *delay = (written / framesize) - (played + estimated_extra_frames_output);
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;
282 }
283
284 static void flush() {
285 // pthread_mutex_lock(&sndio_mutex);
286 pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
287 if (!sio_stop(hdl) || !sio_start(hdl))
288 die("sndio: unable to flush");
289 written = played = 0;
290 // pthread_mutex_unlock(&sndio_mutex);
291 pthread_cleanup_pop(1); // unlock the mutex
292 }
293
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};