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>
6 * Modifications for audio synchronisation
7 * and related work, copyright (c) Mike Brady 2014 -- 2022
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.
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.
31 static pthread_mutex_t sndio_mutex
= PTHREAD_MUTEX_INITIALIZER
;
32 static struct sio_hdl
*hdl
;
35 static size_t written
;
36 uint64_t time_of_last_onmove_cb
;
37 int at_least_one_onmove_cb_seen
;
41 struct sndio_formats
{
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
}};
61 static void help() { printf(" -d output-device set the output device [default*|...]\n"); }
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;
69 static int init(int argc
, char **argv
) {
70 int found
, opt
, round
, rate
, bufsz
;
72 const char *devname
, *tmp
;
74 // set up default values first
80 par
.bps
= SIO_BPS(par
.bits
);
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
89 config
.audio_backend_latency_offset
= 0;
91 // get settings from settings file
93 // do the "general" audio options. Note, these options are in the "general" stanza!
94 parse_general_audio_options();
96 // get the specific settings
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) {
105 die("sndio: output rate must be a multiple of 44100 and 44100 <= rate <= "
109 if (config_lookup_int(config
.cfg
, "sndio.bufsz", &bufsz
)) {
111 par
.appbufsz
= bufsz
;
113 die("sndio: bufsz must be > 0");
116 if (config_lookup_int(config
.cfg
, "sndio.round", &round
)) {
120 die("sndio: round must be > 0");
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
;
132 die("Invalid output format \"%s\". Should be one of: S8, U8, S16, S24, "
133 "S24_3LE, S24_3BE, S32, Automatic",
137 optind
= 1; // optind=0 is equivalent to optind=1 plus special behaviour
138 argv
--; // so we shift the arguments to satisfy getopt()
140 while ((opt
= getopt(argc
, argv
, "d:")) > 0) {
147 die("Invalid audio option -%c specified", opt
);
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
);
158 hdl
= sio_open(devname
, SIO_PLAY
, 0);
160 die("sndio: cannot open audio device");
162 written
= played
= 0;
163 time_of_last_onmove_cb
= 0;
164 at_least_one_onmove_cb_seen
= 0;
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
;
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
;
187 die("sndio: could not set output device to the required format and rate.");
189 framesize
= par
.bps
* par
.pchan
;
190 config
.output_rate
= par
.rate
;
192 die("sndio: par.rate set to zero.");
195 config
.audio_backend_buffer_desired_length
= 1.0 * par
.bufsz
/ par
.rate
;
196 config
.audio_backend_latency_offset
= 0;
198 sio_onmove(hdl
, onmove_cb
, NULL
);
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.");
208 static void deinit() {
209 // pthread_mutex_lock(&sndio_mutex);
210 pthread_cleanup_debug_mutex_lock(&sndio_mutex
, 1000, 1);
212 // pthread_mutex_unlock(&sndio_mutex);
213 pthread_cleanup_pop(1); // unlock the mutex
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
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
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
) {
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
246 pthread_cleanup_debug_mutex_lock(&sndio_mutex
, 1000, 1);
249 die("sndio: unable to stop");
250 written
= played
= 0;
251 pthread_cleanup_pop(1); // unlock the mutex
254 int get_delay(long *delay
) {
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
268 // debug(1,"Frames played to last cb: %d, estimated to current time:
269 // %d.",played,estimated_extra_frames_output);
272 *delay
= (written
/ framesize
) - (played
+ estimated_extra_frames_output
);
276 static int delay(long *delay
) {
278 pthread_cleanup_debug_mutex_lock(&sndio_mutex
, 1000, 1);
279 result
= get_delay(delay
);
280 pthread_cleanup_pop(1); // unlock the mutex
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
294 audio_output audio_sndio
= {.name
= "sndio",