]>
Commit | Line | Data |
---|---|---|
ec232363 | 1 | /* |
afaf94b7 | 2 | * Asynchronous PipeWire Backend. This file is part of Shairport Sync. |
bde2bcc8 | 3 | * Copyright (c) Mike Brady 2023 |
ec232363 LR |
4 | * All rights reserved. |
5 | * | |
6 | * Permission is hereby granted, free of charge, to any person | |
7 | * obtaining a copy of this software and associated documentation | |
8 | * files (the "Software"), to deal in the Software without | |
9 | * restriction, including without limitation the rights to use, | |
10 | * copy, modify, merge, publish, distribute, sublicense, and/or | |
11 | * sell copies of the Software, and to permit persons to whom the | |
12 | * Software is furnished to do so, subject to the following conditions: | |
13 | * | |
14 | * The above copyright notice and this permission notice shall be | |
15 | * included in all copies or substantial portions of the Software. | |
16 | * | |
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
19 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
21 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
22 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
24 | * OTHER DEALINGS IN THE SOFTWARE. | |
25 | */ | |
26 | ||
bde2bcc8 MB |
27 | // This uses ideas from the tone generator sample code at: |
28 | // https://github.com/PipeWire/pipewire/blob/master/src/examples/audio-src.c | |
dc197ae7 | 29 | // Thanks to Wim Taymans. |
bde2bcc8 | 30 | |
ec232363 LR |
31 | #include "audio.h" |
32 | #include "common.h" | |
afaf94b7 MB |
33 | #include <errno.h> |
34 | #include <pthread.h> | |
35 | #include <stdio.h> | |
36 | #include <string.h> | |
37 | #include <unistd.h> | |
ec232363 | 38 | |
afaf94b7 | 39 | #include <pipewire/pipewire.h> |
27e4ba09 | 40 | #include <spa/param/audio/format-utils.h> |
ec232363 | 41 | |
dc197ae7 | 42 | // note -- these are hardwired into this code. |
afaf94b7 MB |
43 | #define DEFAULT_FORMAT SPA_AUDIO_FORMAT_S16_LE |
44 | #define DEFAULT_RATE 44100 | |
45 | #define DEFAULT_CHANNELS 2 | |
ec232363 | 46 | |
afaf94b7 MB |
47 | // Four seconds buffer -- should be plenty |
48 | #define buffer_allocation 44100 * 4 * 2 * 2 | |
ec232363 | 49 | |
27e4ba09 | 50 | static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; |
ec232363 | 51 | |
27e4ba09 MB |
52 | static char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq; |
53 | static size_t audio_size = buffer_allocation; | |
54 | static size_t audio_occupancy; | |
dc197ae7 | 55 | static int enable_fill; |
ec232363 | 56 | |
27e4ba09 | 57 | struct timing_data { |
bde2bcc8 | 58 | int pw_time_is_valid; // set when the pw_time has been set |
27e4ba09 | 59 | struct pw_time time_info; // information about the last time a process callback occurred |
bde2bcc8 | 60 | size_t frames; // the number of frames sent at that time |
27e4ba09 MB |
61 | }; |
62 | ||
63 | // to avoid using a mutex, write the same data twice and check they are the same | |
64 | // to ensure they are consistent. Make sure the first is written strictly before the second | |
65 | // using __sync_synchronize(); | |
66 | struct timing_data timing_data_1, timing_data_2; | |
67 | ||
afaf94b7 MB |
68 | struct data { |
69 | struct pw_thread_loop *loop; | |
70 | struct pw_stream *stream; | |
ec232363 LR |
71 | }; |
72 | ||
afaf94b7 | 73 | // the pipewire global data structure |
dc197ae7 | 74 | struct data data = {NULL, NULL}; |
ec232363 | 75 | |
dc197ae7 | 76 | /* |
bde2bcc8 MB |
77 | static void on_state_changed(__attribute__((unused)) void *userdata, enum pw_stream_state old, |
78 | enum pw_stream_state state, | |
79 | __attribute__((unused)) const char *error) { | |
80 | // struct pw_data *pw = userdata; | |
81 | debug(3, "pw: stream state changed %s -> %s", pw_stream_state_as_string(old), | |
82 | pw_stream_state_as_string(state)); | |
83 | } | |
dc197ae7 | 84 | */ |
bde2bcc8 MB |
85 | |
86 | static void on_process(void *userdata) { | |
87 | ||
88 | struct data *data = userdata; | |
89 | int n_frames = 0; | |
90 | ||
27e4ba09 | 91 | pthread_mutex_lock(&buffer_mutex); |
bde2bcc8 | 92 | |
dc197ae7 | 93 | if ((audio_occupancy > 0) || (enable_fill)) { |
bde2bcc8 MB |
94 | |
95 | // get a buffer to see how big it can be | |
96 | struct pw_buffer *b = pw_stream_dequeue_buffer(data->stream); | |
97 | if (b == NULL) { | |
98 | pw_log_warn("out of buffers: %m"); | |
dc197ae7 | 99 | die("PipeWire failue -- out of buffers!"); |
bde2bcc8 MB |
100 | } |
101 | struct spa_buffer *buf = b->buffer; | |
102 | uint8_t *dest = buf->datas[0].data; | |
dc197ae7 MB |
103 | if (dest != NULL) { |
104 | int stride = sizeof(int16_t) * DEFAULT_CHANNELS; | |
105 | ||
106 | // note: the requested field is the number of frames, not bytes, requested | |
107 | int max_possible_frames = SPA_MIN(b->requested, buf->datas[0].maxsize / stride); | |
108 | ||
109 | size_t bytes_we_can_transfer = max_possible_frames * stride; | |
110 | ||
111 | if (audio_occupancy > 0) { | |
112 | // if (enable_fill == 1)) { | |
113 | // debug(1, "got audio -- disable_fill"); | |
114 | // } | |
115 | enable_fill = 0; | |
116 | ||
117 | if (bytes_we_can_transfer > audio_occupancy) | |
118 | bytes_we_can_transfer = audio_occupancy; | |
119 | ||
120 | n_frames = bytes_we_can_transfer / stride; | |
121 | ||
122 | size_t bytes_to_end_of_buffer = (size_t)(audio_umb - audio_toq); // must be zero or positive | |
123 | if (bytes_we_can_transfer <= bytes_to_end_of_buffer) { | |
124 | // the bytes are all in a row in the audio buffer | |
125 | memcpy(dest, audio_toq, bytes_we_can_transfer); | |
126 | audio_toq += bytes_we_can_transfer; | |
127 | } else { | |
128 | // the bytes are in two places in the audio buffer | |
129 | size_t first_portion_to_write = audio_umb - audio_toq; | |
130 | if (first_portion_to_write != 0) | |
131 | memcpy(dest, audio_toq, first_portion_to_write); | |
132 | uint8_t *new_dest = dest + first_portion_to_write; | |
133 | memcpy(new_dest, audio_lmb, bytes_we_can_transfer - first_portion_to_write); | |
134 | audio_toq = audio_lmb + bytes_we_can_transfer - first_portion_to_write; | |
135 | } | |
136 | audio_occupancy -= bytes_we_can_transfer; | |
137 | ||
138 | } else { | |
139 | debug(3, "send silence"); | |
140 | // this should really be dithered silence | |
141 | memset(dest, 0, bytes_we_can_transfer); | |
142 | n_frames = max_possible_frames; | |
143 | } | |
144 | buf->datas[0].chunk->offset = 0; | |
145 | buf->datas[0].chunk->stride = stride; | |
146 | buf->datas[0].chunk->size = n_frames * stride; | |
147 | pw_stream_queue_buffer(data->stream, b); | |
148 | debug(3, "Queueing %d frames for output.", n_frames); | |
149 | } // (else the first data block does not contain a data pointer) | |
bde2bcc8 MB |
150 | } |
151 | pthread_mutex_unlock(&buffer_mutex); | |
ec232363 | 152 | |
bde2bcc8 MB |
153 | timing_data_1.frames = n_frames; |
154 | if (pw_stream_get_time_n(data->stream, &timing_data_1.time_info, sizeof(struct timing_data)) == 0) | |
27e4ba09 MB |
155 | timing_data_1.pw_time_is_valid = 1; |
156 | else | |
157 | timing_data_1.pw_time_is_valid = 0; | |
158 | __sync_synchronize(); | |
159 | memcpy((char *)&timing_data_2, (char *)&timing_data_1, sizeof(struct timing_data)); | |
160 | __sync_synchronize(); | |
ec232363 LR |
161 | } |
162 | ||
dc197ae7 MB |
163 | static const struct pw_stream_events stream_events = {PW_VERSION_STREAM_EVENTS, |
164 | .process = on_process}; | |
165 | // PW_VERSION_STREAM_EVENTS, .process = on_process, .state_changed = on_state_changed}; | |
bde2bcc8 MB |
166 | |
167 | static void deinit(void) { | |
168 | pw_thread_loop_stop(data.loop); | |
169 | pw_stream_destroy(data.stream); | |
170 | pw_thread_loop_destroy(data.loop); | |
171 | pw_deinit(); | |
172 | free(audio_lmb); // deallocate that buffer | |
173 | } | |
ec232363 | 174 | |
c0a3dacf | 175 | static int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { |
ec232363 | 176 | // set up default values first |
bde2bcc8 MB |
177 | memset(&timing_data_1, 0, sizeof(struct timing_data)); |
178 | memset(&timing_data_2, 0, sizeof(struct timing_data)); | |
ec232363 | 179 | config.audio_backend_buffer_desired_length = 0.35; |
afaf94b7 MB |
180 | config.audio_backend_buffer_interpolation_threshold_in_seconds = |
181 | 0.02; // below this, soxr interpolation will not occur -- it'll be basic interpolation | |
182 | // instead. | |
ec232363 | 183 | |
afaf94b7 | 184 | config.audio_backend_latency_offset = 0; |
ec232363 | 185 | |
afaf94b7 | 186 | // get settings from settings file |
afaf94b7 MB |
187 | // do the "general" audio options. Note, these options are in the "general" stanza! |
188 | parse_general_audio_options(); | |
ec232363 | 189 | |
27e4ba09 MB |
190 | /* |
191 | // now any PipeWire-specific options | |
192 | if (config.cfg != NULL) { | |
193 | const char *str; | |
194 | } | |
195 | */ | |
afaf94b7 | 196 | // finished collecting settings |
ec232363 | 197 | |
afaf94b7 MB |
198 | // allocate space for the audio buffer |
199 | audio_lmb = malloc(audio_size); | |
200 | if (audio_lmb == NULL) | |
dc197ae7 | 201 | die("Can't allocate %d bytes for PipeWire buffer.", audio_size); |
afaf94b7 MB |
202 | audio_toq = audio_eoq = audio_lmb; |
203 | audio_umb = audio_lmb + audio_size; | |
204 | audio_occupancy = 0; | |
dc197ae7 MB |
205 | // debug(1, "init enable_fill"); |
206 | enable_fill = 1; | |
ec232363 | 207 | |
afaf94b7 MB |
208 | const struct spa_pod *params[1]; |
209 | uint8_t buffer[1024]; | |
210 | struct pw_properties *props; | |
211 | struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); | |
212 | ||
213 | int largc = 0; | |
214 | pw_init(&largc, NULL); | |
215 | ||
bde2bcc8 MB |
216 | /* make a threaded loop. */ |
217 | data.loop = pw_thread_loop_new("shairport-sync", NULL); | |
afaf94b7 MB |
218 | |
219 | pw_thread_loop_lock(data.loop); | |
27e4ba09 | 220 | |
afaf94b7 MB |
221 | pw_thread_loop_start(data.loop); |
222 | ||
afaf94b7 | 223 | props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", |
afdf0d86 MB |
224 | PW_KEY_MEDIA_ROLE, "Music", PW_KEY_APP_NAME, "Shairport Sync", NULL); |
225 | ||
bde2bcc8 | 226 | data.stream = pw_stream_new_simple(pw_thread_loop_get_loop(data.loop), "shairport-sync", props, |
afaf94b7 MB |
227 | &stream_events, &data); |
228 | ||
229 | /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat | |
230 | * id means that this is a format enumeration (of 1 value). */ | |
231 | params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, | |
232 | &SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_S16_LE, | |
233 | .channels = DEFAULT_CHANNELS, | |
234 | .rate = DEFAULT_RATE)); | |
235 | ||
236 | /* Now connect this stream. We ask that our process function is | |
237 | * called in a realtime thread. */ | |
238 | pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, | |
239 | PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | | |
240 | PW_STREAM_FLAG_RT_PROCESS, | |
241 | params, 1); | |
242 | ||
243 | pw_thread_loop_unlock(data.loop); | |
ec232363 LR |
244 | return 0; |
245 | } | |
246 | ||
27e4ba09 | 247 | static void start(__attribute__((unused)) int sample_rate, |
dc197ae7 MB |
248 | __attribute__((unused)) int sample_format) { |
249 | } | |
27e4ba09 MB |
250 | |
251 | static int play(__attribute__((unused)) void *buf, int samples, | |
252 | __attribute__((unused)) int sample_type, __attribute__((unused)) uint32_t timestamp, | |
bde2bcc8 | 253 | __attribute__((unused)) uint64_t playtime) { |
afaf94b7 | 254 | // copy the samples into the queue |
bde2bcc8 | 255 | debug(3, "play %u samples; %u bytes already in the buffer.", samples, audio_occupancy); |
afaf94b7 | 256 | size_t bytes_to_transfer = samples * 2 * 2; |
bde2bcc8 MB |
257 | pthread_mutex_lock(&buffer_mutex); |
258 | size_t bytes_available = audio_size - audio_occupancy; | |
259 | if (bytes_available < bytes_to_transfer) | |
260 | bytes_to_transfer = bytes_available; | |
261 | if (bytes_to_transfer > 0) { | |
262 | size_t space_to_end_of_buffer = audio_umb - audio_eoq; | |
263 | if (space_to_end_of_buffer >= bytes_to_transfer) { | |
264 | memcpy(audio_eoq, buf, bytes_to_transfer); | |
265 | audio_eoq += bytes_to_transfer; | |
266 | } else { | |
267 | memcpy(audio_eoq, buf, space_to_end_of_buffer); | |
268 | buf += space_to_end_of_buffer; | |
269 | memcpy(audio_lmb, buf, bytes_to_transfer - space_to_end_of_buffer); | |
270 | audio_eoq = audio_lmb + bytes_to_transfer - space_to_end_of_buffer; | |
271 | } | |
afaf94b7 | 272 | audio_occupancy += bytes_to_transfer; |
ec232363 | 273 | } |
bde2bcc8 | 274 | pthread_mutex_unlock(&buffer_mutex); |
afaf94b7 | 275 | return 0; |
ec232363 LR |
276 | } |
277 | ||
27e4ba09 | 278 | int delay(long *the_delay) { |
bde2bcc8 MB |
279 | long result = 0; |
280 | int reply = 0; | |
27e4ba09 MB |
281 | // find out what's already in the PipeWire system and when |
282 | struct timing_data timing_data; | |
283 | int loop_count = 1; | |
284 | do { | |
285 | memcpy(&timing_data, (char *)&timing_data_1, sizeof(struct timing_data)); | |
286 | __sync_synchronize(); | |
287 | if (memcmp(&timing_data, (char *)&timing_data_2, sizeof(struct timing_data)) != 0) { | |
bde2bcc8 MB |
288 | usleep(2); // microseconds |
289 | loop_count++; | |
290 | __sync_synchronize(); | |
27e4ba09 | 291 | } |
bde2bcc8 MB |
292 | } while ((memcmp(&timing_data, (char *)&timing_data_2, sizeof(struct timing_data)) != 0) && |
293 | (loop_count < 10)); | |
27e4ba09 MB |
294 | long total_delay_now_frames_long = 0; |
295 | if ((loop_count < 10) && (timing_data.pw_time_is_valid != 0)) { | |
bde2bcc8 | 296 | struct timespec time_now; |
27e4ba09 | 297 | clock_gettime(CLOCK_MONOTONIC, &time_now); |
bde2bcc8 MB |
298 | int64_t interval_from_process_time_to_now = |
299 | SPA_TIMESPEC_TO_NSEC(&time_now) - timing_data.time_info.now; | |
27e4ba09 MB |
300 | int64_t delay_in_ns = timing_data.time_info.delay + timing_data.time_info.buffered; |
301 | delay_in_ns = delay_in_ns * 1000000000; | |
302 | delay_in_ns = delay_in_ns * timing_data.time_info.rate.num; | |
303 | delay_in_ns = delay_in_ns / timing_data.time_info.rate.denom; | |
bde2bcc8 | 304 | |
27e4ba09 | 305 | int64_t total_delay_now_ns = delay_in_ns - interval_from_process_time_to_now; |
bde2bcc8 | 306 | int64_t total_delay_now_frames = (total_delay_now_ns * 44100) / 1000000000 + timing_data.frames; |
27e4ba09 | 307 | total_delay_now_frames_long = total_delay_now_frames; |
bde2bcc8 | 308 | debug(3, "total delay in frames: %ld.", total_delay_now_frames_long); |
27e4ba09 | 309 | |
bde2bcc8 MB |
310 | if (timing_data.time_info.queued != 0) { |
311 | debug(1, "buffers queued: %d", timing_data.time_info.queued); | |
312 | } | |
313 | /* | |
314 | debug(3, | |
315 | "interval_from_process_time_to_now: %" PRId64 " ns, " | |
316 | "delay_in_ns: %" PRId64 ", queued: %" PRId64 ", buffered: %" PRId64 ".", | |
317 | // delay_timing_data.time_info.rate.num, delay_timing_data.time_info.rate.denom, | |
318 | interval_from_process_time_to_now, delay_in_ns, | |
319 | timing_data.time_info.queued, timing_data.time_info.buffered); | |
320 | */ | |
27e4ba09 | 321 | |
afaf94b7 | 322 | } else { |
27e4ba09 | 323 | debug(1, "can't get time info."); |
ec232363 | 324 | } |
27e4ba09 | 325 | |
27e4ba09 MB |
326 | pthread_mutex_lock(&buffer_mutex); |
327 | result = total_delay_now_frames_long + audio_occupancy / (2 * 2); | |
328 | pthread_mutex_unlock(&buffer_mutex); | |
afaf94b7 MB |
329 | *the_delay = result; |
330 | return reply; | |
ec232363 LR |
331 | } |
332 | ||
27e4ba09 | 333 | static void flush(void) { |
bde2bcc8 | 334 | pthread_mutex_lock(&buffer_mutex); |
afaf94b7 MB |
335 | audio_toq = audio_eoq = audio_lmb; |
336 | audio_umb = audio_lmb + audio_size; | |
337 | audio_occupancy = 0; | |
dc197ae7 MB |
338 | // if (enable_fill == 0) { |
339 | // debug(1, "flush enable_fill"); | |
340 | // } | |
341 | enable_fill = 1; | |
27e4ba09 | 342 | pthread_mutex_unlock(&buffer_mutex); |
ec232363 LR |
343 | } |
344 | ||
afaf94b7 | 345 | static void stop(void) { |
bde2bcc8 | 346 | pthread_mutex_lock(&buffer_mutex); |
afaf94b7 MB |
347 | audio_toq = audio_eoq = audio_lmb; |
348 | audio_umb = audio_lmb + audio_size; | |
349 | audio_occupancy = 0; | |
dc197ae7 MB |
350 | // if (enable_fill == 0) { |
351 | // debug(1, "stop enable_fill"); | |
352 | // } | |
353 | enable_fill = 1; | |
27e4ba09 | 354 | pthread_mutex_unlock(&buffer_mutex); |
ec232363 LR |
355 | } |
356 | ||
c0a3dacf MB |
357 | audio_output audio_pw = {.name = "pw", |
358 | .help = NULL, | |
359 | .init = &init, | |
360 | .deinit = &deinit, | |
361 | .prepare = NULL, | |
27e4ba09 | 362 | .start = &start, |
c0a3dacf MB |
363 | .stop = &stop, |
364 | .is_running = NULL, | |
365 | .flush = &flush, | |
27e4ba09 | 366 | .delay = &delay, |
cd9da86f | 367 | .stats = NULL, |
c0a3dacf MB |
368 | .play = &play, |
369 | .volume = NULL, | |
370 | .parameters = NULL, | |
afaf94b7 | 371 | .mute = NULL}; |