]> git.ipfire.org Git - thirdparty/shairport-sync.git/blame - audio_pw.c
Update RELEASENOTES-DEVELOPMENT.md
[thirdparty/shairport-sync.git] / audio_pw.c
CommitLineData
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 43#define DEFAULT_FORMAT SPA_AUDIO_FORMAT_S16_LE
3763b321
MB
44#define DEFAULT_BYTES_PER_SAMPLE 2
45
afaf94b7
MB
46#define DEFAULT_RATE 44100
47#define DEFAULT_CHANNELS 2
3763b321 48#define DEFAULT_BUFFER_SIZE_IN_SECONDS 4
ec232363 49
afaf94b7 50// Four seconds buffer -- should be plenty
3763b321 51#define buffer_allocation DEFAULT_RATE * DEFAULT_BUFFER_SIZE_IN_SECONDS * DEFAULT_BYTES_PER_SAMPLE * DEFAULT_CHANNELS
ec232363 52
27e4ba09 53static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
ec232363 54
27e4ba09
MB
55static char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq;
56static size_t audio_size = buffer_allocation;
57static size_t audio_occupancy;
dc197ae7 58static int enable_fill;
ec232363 59
27e4ba09 60struct timing_data {
bde2bcc8 61 int pw_time_is_valid; // set when the pw_time has been set
27e4ba09 62 struct pw_time time_info; // information about the last time a process callback occurred
bde2bcc8 63 size_t frames; // the number of frames sent at that time
27e4ba09
MB
64};
65
66// to avoid using a mutex, write the same data twice and check they are the same
67// to ensure they are consistent. Make sure the first is written strictly before the second
68// using __sync_synchronize();
69struct timing_data timing_data_1, timing_data_2;
70
afaf94b7
MB
71struct data {
72 struct pw_thread_loop *loop;
73 struct pw_stream *stream;
ec232363
LR
74};
75
afaf94b7 76// the pipewire global data structure
dc197ae7 77struct data data = {NULL, NULL};
ec232363 78
dc197ae7 79/*
bde2bcc8
MB
80static void on_state_changed(__attribute__((unused)) void *userdata, enum pw_stream_state old,
81 enum pw_stream_state state,
82 __attribute__((unused)) const char *error) {
83 // struct pw_data *pw = userdata;
84 debug(3, "pw: stream state changed %s -> %s", pw_stream_state_as_string(old),
85 pw_stream_state_as_string(state));
86}
dc197ae7 87*/
bde2bcc8
MB
88
89static void on_process(void *userdata) {
90
91 struct data *data = userdata;
92 int n_frames = 0;
93
27e4ba09 94 pthread_mutex_lock(&buffer_mutex);
bde2bcc8 95
dc197ae7 96 if ((audio_occupancy > 0) || (enable_fill)) {
bde2bcc8
MB
97
98 // get a buffer to see how big it can be
99 struct pw_buffer *b = pw_stream_dequeue_buffer(data->stream);
100 if (b == NULL) {
101 pw_log_warn("out of buffers: %m");
dc197ae7 102 die("PipeWire failue -- out of buffers!");
bde2bcc8
MB
103 }
104 struct spa_buffer *buf = b->buffer;
105 uint8_t *dest = buf->datas[0].data;
dc197ae7 106 if (dest != NULL) {
3763b321 107 int stride = DEFAULT_BYTES_PER_SAMPLE * DEFAULT_CHANNELS;
dc197ae7
MB
108
109 // note: the requested field is the number of frames, not bytes, requested
110 int max_possible_frames = SPA_MIN(b->requested, buf->datas[0].maxsize / stride);
111
112 size_t bytes_we_can_transfer = max_possible_frames * stride;
113
114 if (audio_occupancy > 0) {
115 // if (enable_fill == 1)) {
116 // debug(1, "got audio -- disable_fill");
117 // }
118 enable_fill = 0;
119
120 if (bytes_we_can_transfer > audio_occupancy)
121 bytes_we_can_transfer = audio_occupancy;
122
123 n_frames = bytes_we_can_transfer / stride;
124
125 size_t bytes_to_end_of_buffer = (size_t)(audio_umb - audio_toq); // must be zero or positive
126 if (bytes_we_can_transfer <= bytes_to_end_of_buffer) {
127 // the bytes are all in a row in the audio buffer
128 memcpy(dest, audio_toq, bytes_we_can_transfer);
129 audio_toq += bytes_we_can_transfer;
130 } else {
131 // the bytes are in two places in the audio buffer
132 size_t first_portion_to_write = audio_umb - audio_toq;
133 if (first_portion_to_write != 0)
134 memcpy(dest, audio_toq, first_portion_to_write);
135 uint8_t *new_dest = dest + first_portion_to_write;
136 memcpy(new_dest, audio_lmb, bytes_we_can_transfer - first_portion_to_write);
137 audio_toq = audio_lmb + bytes_we_can_transfer - first_portion_to_write;
138 }
139 audio_occupancy -= bytes_we_can_transfer;
140
141 } else {
142 debug(3, "send silence");
143 // this should really be dithered silence
144 memset(dest, 0, bytes_we_can_transfer);
145 n_frames = max_possible_frames;
146 }
147 buf->datas[0].chunk->offset = 0;
148 buf->datas[0].chunk->stride = stride;
149 buf->datas[0].chunk->size = n_frames * stride;
150 pw_stream_queue_buffer(data->stream, b);
151 debug(3, "Queueing %d frames for output.", n_frames);
152 } // (else the first data block does not contain a data pointer)
bde2bcc8
MB
153 }
154 pthread_mutex_unlock(&buffer_mutex);
ec232363 155
bde2bcc8
MB
156 timing_data_1.frames = n_frames;
157 if (pw_stream_get_time_n(data->stream, &timing_data_1.time_info, sizeof(struct timing_data)) == 0)
27e4ba09
MB
158 timing_data_1.pw_time_is_valid = 1;
159 else
160 timing_data_1.pw_time_is_valid = 0;
161 __sync_synchronize();
162 memcpy((char *)&timing_data_2, (char *)&timing_data_1, sizeof(struct timing_data));
163 __sync_synchronize();
ec232363
LR
164}
165
dc197ae7
MB
166static const struct pw_stream_events stream_events = {PW_VERSION_STREAM_EVENTS,
167 .process = on_process};
168// PW_VERSION_STREAM_EVENTS, .process = on_process, .state_changed = on_state_changed};
bde2bcc8
MB
169
170static void deinit(void) {
171 pw_thread_loop_stop(data.loop);
172 pw_stream_destroy(data.stream);
173 pw_thread_loop_destroy(data.loop);
174 pw_deinit();
175 free(audio_lmb); // deallocate that buffer
3763b321
MB
176 if (config.pw_application_name)
177 free(config.pw_application_name);
178 if (config.pw_node_name)
179 free(config.pw_node_name);
180 config.pw_application_name = config.pw_node_name = NULL;
181
182
bde2bcc8 183}
ec232363 184
c0a3dacf 185static int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
ec232363 186 // set up default values first
bde2bcc8
MB
187 memset(&timing_data_1, 0, sizeof(struct timing_data));
188 memset(&timing_data_2, 0, sizeof(struct timing_data));
ec232363 189 config.audio_backend_buffer_desired_length = 0.35;
afaf94b7
MB
190 config.audio_backend_buffer_interpolation_threshold_in_seconds =
191 0.02; // below this, soxr interpolation will not occur -- it'll be basic interpolation
192 // instead.
ec232363 193
afaf94b7 194 config.audio_backend_latency_offset = 0;
ec232363 195
afaf94b7 196 // get settings from settings file
afaf94b7
MB
197 // do the "general" audio options. Note, these options are in the "general" stanza!
198 parse_general_audio_options();
ec232363 199
3763b321 200
27e4ba09
MB
201 // now any PipeWire-specific options
202 if (config.cfg != NULL) {
203 const char *str;
3763b321
MB
204
205 /* Get the Application Name. */
206 if (config_lookup_string(config.cfg, "pw.application_name", &str)) {
207 config.pw_application_name = (char *)str;
208 }
209
288e5eb6 210 /* Get the PipeWire node name. */
3763b321
MB
211 if (config_lookup_string(config.cfg, "pa.node_name", &str)) {
212 config.pw_node_name = (char *)str;
213 }
27e4ba09 214 }
3763b321 215
afaf94b7 216 // finished collecting settings
ec232363 217
afaf94b7
MB
218 // allocate space for the audio buffer
219 audio_lmb = malloc(audio_size);
220 if (audio_lmb == NULL)
dc197ae7 221 die("Can't allocate %d bytes for PipeWire buffer.", audio_size);
afaf94b7
MB
222 audio_toq = audio_eoq = audio_lmb;
223 audio_umb = audio_lmb + audio_size;
224 audio_occupancy = 0;
dc197ae7
MB
225 // debug(1, "init enable_fill");
226 enable_fill = 1;
ec232363 227
afaf94b7
MB
228 const struct spa_pod *params[1];
229 uint8_t buffer[1024];
230 struct pw_properties *props;
231 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
232
233 int largc = 0;
234 pw_init(&largc, NULL);
235
bde2bcc8
MB
236 /* make a threaded loop. */
237 data.loop = pw_thread_loop_new("shairport-sync", NULL);
afaf94b7
MB
238
239 pw_thread_loop_lock(data.loop);
27e4ba09 240
afaf94b7 241 pw_thread_loop_start(data.loop);
3763b321
MB
242
243 char* appname = config.pw_application_name;
244 if (appname == NULL)
245 appname = "Shairport Sync";
246
247 char* nodename = config.pw_node_name;
248 if (nodename == NULL)
288e5eb6 249 nodename = "Shairport Sync";
3763b321
MB
250
251
afaf94b7 252
afaf94b7 253 props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback",
3763b321
MB
254 PW_KEY_MEDIA_ROLE, "Music", PW_KEY_APP_NAME, appname,
255 PW_KEY_NODE_NAME, nodename, NULL);
afdf0d86 256
3763b321 257 data.stream = pw_stream_new_simple(pw_thread_loop_get_loop(data.loop), config.appName, props,
afaf94b7
MB
258 &stream_events, &data);
259
288e5eb6
MB
260 // Make one parameter with the supported formats. The SPA_PARAM_EnumFormat
261 // id means that this is a format enumeration (of 1 value).
afaf94b7 262 params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
3763b321 263 &SPA_AUDIO_INFO_RAW_INIT(.format = DEFAULT_FORMAT,
afaf94b7
MB
264 .channels = DEFAULT_CHANNELS,
265 .rate = DEFAULT_RATE));
266
288e5eb6
MB
267 // Now connect this stream. We ask that our process function is
268 // called in a realtime thread.
afaf94b7
MB
269 pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY,
270 PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS |
271 PW_STREAM_FLAG_RT_PROCESS,
272 params, 1);
273
274 pw_thread_loop_unlock(data.loop);
ec232363
LR
275 return 0;
276}
277
27e4ba09 278static void start(__attribute__((unused)) int sample_rate,
dc197ae7
MB
279 __attribute__((unused)) int sample_format) {
280}
27e4ba09
MB
281
282static int play(__attribute__((unused)) void *buf, int samples,
283 __attribute__((unused)) int sample_type, __attribute__((unused)) uint32_t timestamp,
bde2bcc8 284 __attribute__((unused)) uint64_t playtime) {
afaf94b7 285 // copy the samples into the queue
bde2bcc8 286 debug(3, "play %u samples; %u bytes already in the buffer.", samples, audio_occupancy);
288e5eb6 287 size_t bytes_to_transfer = samples * DEFAULT_CHANNELS * DEFAULT_BYTES_PER_SAMPLE;
bde2bcc8
MB
288 pthread_mutex_lock(&buffer_mutex);
289 size_t bytes_available = audio_size - audio_occupancy;
290 if (bytes_available < bytes_to_transfer)
291 bytes_to_transfer = bytes_available;
292 if (bytes_to_transfer > 0) {
293 size_t space_to_end_of_buffer = audio_umb - audio_eoq;
294 if (space_to_end_of_buffer >= bytes_to_transfer) {
295 memcpy(audio_eoq, buf, bytes_to_transfer);
296 audio_eoq += bytes_to_transfer;
297 } else {
298 memcpy(audio_eoq, buf, space_to_end_of_buffer);
299 buf += space_to_end_of_buffer;
300 memcpy(audio_lmb, buf, bytes_to_transfer - space_to_end_of_buffer);
301 audio_eoq = audio_lmb + bytes_to_transfer - space_to_end_of_buffer;
302 }
afaf94b7 303 audio_occupancy += bytes_to_transfer;
ec232363 304 }
bde2bcc8 305 pthread_mutex_unlock(&buffer_mutex);
afaf94b7 306 return 0;
ec232363
LR
307}
308
27e4ba09 309int delay(long *the_delay) {
bde2bcc8
MB
310 long result = 0;
311 int reply = 0;
27e4ba09
MB
312 // find out what's already in the PipeWire system and when
313 struct timing_data timing_data;
314 int loop_count = 1;
315 do {
316 memcpy(&timing_data, (char *)&timing_data_1, sizeof(struct timing_data));
317 __sync_synchronize();
318 if (memcmp(&timing_data, (char *)&timing_data_2, sizeof(struct timing_data)) != 0) {
bde2bcc8
MB
319 usleep(2); // microseconds
320 loop_count++;
321 __sync_synchronize();
27e4ba09 322 }
bde2bcc8
MB
323 } while ((memcmp(&timing_data, (char *)&timing_data_2, sizeof(struct timing_data)) != 0) &&
324 (loop_count < 10));
27e4ba09
MB
325 long total_delay_now_frames_long = 0;
326 if ((loop_count < 10) && (timing_data.pw_time_is_valid != 0)) {
bde2bcc8 327 struct timespec time_now;
27e4ba09 328 clock_gettime(CLOCK_MONOTONIC, &time_now);
bde2bcc8
MB
329 int64_t interval_from_process_time_to_now =
330 SPA_TIMESPEC_TO_NSEC(&time_now) - timing_data.time_info.now;
27e4ba09
MB
331 int64_t delay_in_ns = timing_data.time_info.delay + timing_data.time_info.buffered;
332 delay_in_ns = delay_in_ns * 1000000000;
333 delay_in_ns = delay_in_ns * timing_data.time_info.rate.num;
334 delay_in_ns = delay_in_ns / timing_data.time_info.rate.denom;
bde2bcc8 335
27e4ba09 336 int64_t total_delay_now_ns = delay_in_ns - interval_from_process_time_to_now;
3763b321 337 int64_t total_delay_now_frames = (total_delay_now_ns * DEFAULT_RATE) / 1000000000 + timing_data.frames;
27e4ba09 338 total_delay_now_frames_long = total_delay_now_frames;
bde2bcc8 339 debug(3, "total delay in frames: %ld.", total_delay_now_frames_long);
27e4ba09 340
bde2bcc8
MB
341 if (timing_data.time_info.queued != 0) {
342 debug(1, "buffers queued: %d", timing_data.time_info.queued);
343 }
344 /*
345 debug(3,
346 "interval_from_process_time_to_now: %" PRId64 " ns, "
347 "delay_in_ns: %" PRId64 ", queued: %" PRId64 ", buffered: %" PRId64 ".",
348 // delay_timing_data.time_info.rate.num, delay_timing_data.time_info.rate.denom,
349 interval_from_process_time_to_now, delay_in_ns,
350 timing_data.time_info.queued, timing_data.time_info.buffered);
351 */
27e4ba09 352
afaf94b7 353 } else {
288e5eb6
MB
354 warn("Shairport Sync's PipeWire backend can not get timing information from the PipeWire "
355 "system. Is PipeWire running?");
ec232363 356 }
27e4ba09 357
27e4ba09 358 pthread_mutex_lock(&buffer_mutex);
288e5eb6 359 result = total_delay_now_frames_long + audio_occupancy / (DEFAULT_BYTES_PER_SAMPLE * DEFAULT_CHANNELS);
27e4ba09 360 pthread_mutex_unlock(&buffer_mutex);
afaf94b7
MB
361 *the_delay = result;
362 return reply;
ec232363
LR
363}
364
27e4ba09 365static void flush(void) {
bde2bcc8 366 pthread_mutex_lock(&buffer_mutex);
afaf94b7
MB
367 audio_toq = audio_eoq = audio_lmb;
368 audio_umb = audio_lmb + audio_size;
369 audio_occupancy = 0;
dc197ae7
MB
370 // if (enable_fill == 0) {
371 // debug(1, "flush enable_fill");
372 // }
373 enable_fill = 1;
27e4ba09 374 pthread_mutex_unlock(&buffer_mutex);
ec232363
LR
375}
376
afaf94b7 377static void stop(void) {
bde2bcc8 378 pthread_mutex_lock(&buffer_mutex);
afaf94b7
MB
379 audio_toq = audio_eoq = audio_lmb;
380 audio_umb = audio_lmb + audio_size;
381 audio_occupancy = 0;
dc197ae7
MB
382 // if (enable_fill == 0) {
383 // debug(1, "stop enable_fill");
384 // }
385 enable_fill = 1;
27e4ba09 386 pthread_mutex_unlock(&buffer_mutex);
ec232363
LR
387}
388
c0a3dacf
MB
389audio_output audio_pw = {.name = "pw",
390 .help = NULL,
391 .init = &init,
392 .deinit = &deinit,
393 .prepare = NULL,
27e4ba09 394 .start = &start,
c0a3dacf
MB
395 .stop = &stop,
396 .is_running = NULL,
397 .flush = &flush,
27e4ba09 398 .delay = &delay,
cd9da86f 399 .stats = NULL,
c0a3dacf
MB
400 .play = &play,
401 .volume = NULL,
402 .parameters = NULL,
afaf94b7 403 .mute = NULL};