From dc197ae711f0f1ac7593f66f20135e2ec3aefd30 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:06:55 +0100 Subject: [PATCH] Send silence when no audio is coming through from Shairport Sync. This eliminates (?) xrun errors. Clean up a few imprefections. --- audio_pw.c | 121 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 50 deletions(-) diff --git a/audio_pw.c b/audio_pw.c index 663dba12..a053edc7 100644 --- a/audio_pw.c +++ b/audio_pw.c @@ -26,7 +26,7 @@ // This uses ideas from the tone generator sample code at: // https://github.com/PipeWire/pipewire/blob/master/src/examples/audio-src.c -// Thanks to the Wim Taymans. +// Thanks to Wim Taymans. #include "audio.h" #include "common.h" @@ -39,7 +39,7 @@ #include #include -// note -- these are hacked and hardwired into this code. +// note -- these are hardwired into this code. #define DEFAULT_FORMAT SPA_AUDIO_FORMAT_S16_LE #define DEFAULT_RATE 44100 #define DEFAULT_CHANNELS 2 @@ -52,8 +52,7 @@ static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; static char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq; static size_t audio_size = buffer_allocation; static size_t audio_occupancy; - -uint64_t starting_time; +static int enable_fill; struct timing_data { int pw_time_is_valid; // set when the pw_time has been set @@ -72,10 +71,9 @@ struct data { }; // the pipewire global data structure -struct data data = { - 0, -}; +struct data data = {NULL, NULL}; +/* static void on_state_changed(__attribute__((unused)) void *userdata, enum pw_stream_state old, enum pw_stream_state state, __attribute__((unused)) const char *error) { @@ -83,6 +81,7 @@ static void on_state_changed(__attribute__((unused)) void *userdata, enum pw_str debug(3, "pw: stream state changed %s -> %s", pw_stream_state_as_string(old), pw_stream_state_as_string(state)); } +*/ static void on_process(void *userdata) { @@ -91,52 +90,63 @@ static void on_process(void *userdata) { pthread_mutex_lock(&buffer_mutex); - if (audio_occupancy > 0) { + if ((audio_occupancy > 0) || (enable_fill)) { // get a buffer to see how big it can be struct pw_buffer *b = pw_stream_dequeue_buffer(data->stream); if (b == NULL) { pw_log_warn("out of buffers: %m"); - return; + die("PipeWire failue -- out of buffers!"); } struct spa_buffer *buf = b->buffer; uint8_t *dest = buf->datas[0].data; - if (dest == NULL) // the first data block does not contain a data pointer - return; - - int stride = sizeof(int16_t) * DEFAULT_CHANNELS; - int max_possible_frames = SPA_MIN(b->requested, buf->datas[0].maxsize / stride); - - size_t bytes_we_can_transfer = max_possible_frames * stride; - - if (bytes_we_can_transfer > audio_occupancy) - bytes_we_can_transfer = audio_occupancy; - - n_frames = bytes_we_can_transfer / stride; - - size_t bytes_to_end_of_buffer = (size_t)(audio_umb - audio_toq); // must be zero or positive - - if (bytes_we_can_transfer <= bytes_to_end_of_buffer) { - // the bytes are all in a row in the audio buffer - memcpy(dest, audio_toq, bytes_we_can_transfer); - audio_toq += bytes_we_can_transfer; - } else { - // the bytes are in two places in the audio buffer - size_t first_portion_to_write = audio_umb - audio_toq; - if (first_portion_to_write != 0) - memcpy(dest, audio_toq, first_portion_to_write); - uint8_t *new_dest = dest + first_portion_to_write; - memcpy(new_dest, audio_lmb, bytes_we_can_transfer - first_portion_to_write); - audio_toq = audio_lmb + bytes_we_can_transfer - first_portion_to_write; - } - - buf->datas[0].chunk->offset = 0; - buf->datas[0].chunk->stride = stride; - buf->datas[0].chunk->size = n_frames * stride; - pw_stream_queue_buffer(data->stream, b); - debug(3, "Queueing %d frames for output.", n_frames); - - audio_occupancy -= bytes_we_can_transfer; + if (dest != NULL) { + int stride = sizeof(int16_t) * DEFAULT_CHANNELS; + + // note: the requested field is the number of frames, not bytes, requested + int max_possible_frames = SPA_MIN(b->requested, buf->datas[0].maxsize / stride); + + size_t bytes_we_can_transfer = max_possible_frames * stride; + + if (audio_occupancy > 0) { + // if (enable_fill == 1)) { + // debug(1, "got audio -- disable_fill"); + // } + enable_fill = 0; + + if (bytes_we_can_transfer > audio_occupancy) + bytes_we_can_transfer = audio_occupancy; + + n_frames = bytes_we_can_transfer / stride; + + size_t bytes_to_end_of_buffer = (size_t)(audio_umb - audio_toq); // must be zero or positive + if (bytes_we_can_transfer <= bytes_to_end_of_buffer) { + // the bytes are all in a row in the audio buffer + memcpy(dest, audio_toq, bytes_we_can_transfer); + audio_toq += bytes_we_can_transfer; + } else { + // the bytes are in two places in the audio buffer + size_t first_portion_to_write = audio_umb - audio_toq; + if (first_portion_to_write != 0) + memcpy(dest, audio_toq, first_portion_to_write); + uint8_t *new_dest = dest + first_portion_to_write; + memcpy(new_dest, audio_lmb, bytes_we_can_transfer - first_portion_to_write); + audio_toq = audio_lmb + bytes_we_can_transfer - first_portion_to_write; + } + audio_occupancy -= bytes_we_can_transfer; + + } else { + debug(3, "send silence"); + // this should really be dithered silence + memset(dest, 0, bytes_we_can_transfer); + n_frames = max_possible_frames; + } + buf->datas[0].chunk->offset = 0; + buf->datas[0].chunk->stride = stride; + buf->datas[0].chunk->size = n_frames * stride; + pw_stream_queue_buffer(data->stream, b); + debug(3, "Queueing %d frames for output.", n_frames); + } // (else the first data block does not contain a data pointer) } pthread_mutex_unlock(&buffer_mutex); @@ -150,8 +160,9 @@ static void on_process(void *userdata) { __sync_synchronize(); } -static const struct pw_stream_events stream_events = { - PW_VERSION_STREAM_EVENTS, .process = on_process, .state_changed = on_state_changed}; +static const struct pw_stream_events stream_events = {PW_VERSION_STREAM_EVENTS, + .process = on_process}; +// PW_VERSION_STREAM_EVENTS, .process = on_process, .state_changed = on_state_changed}; static void deinit(void) { pw_thread_loop_stop(data.loop); @@ -173,7 +184,6 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * config.audio_backend_latency_offset = 0; // get settings from settings file - // do the "general" audio options. Note, these options are in the "general" stanza! parse_general_audio_options(); @@ -188,10 +198,12 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * // allocate space for the audio buffer audio_lmb = malloc(audio_size); if (audio_lmb == NULL) - die("Can't allocate %d bytes for pulseaudio buffer.", audio_size); + die("Can't allocate %d bytes for PipeWire buffer.", audio_size); audio_toq = audio_eoq = audio_lmb; audio_umb = audio_lmb + audio_size; audio_occupancy = 0; + // debug(1, "init enable_fill"); + enable_fill = 1; const struct spa_pod *params[1]; uint8_t buffer[1024]; @@ -233,7 +245,8 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * } static void start(__attribute__((unused)) int sample_rate, - __attribute__((unused)) int sample_format) {} + __attribute__((unused)) int sample_format) { +} static int play(__attribute__((unused)) void *buf, int samples, __attribute__((unused)) int sample_type, __attribute__((unused)) uint32_t timestamp, @@ -322,6 +335,10 @@ static void flush(void) { audio_toq = audio_eoq = audio_lmb; audio_umb = audio_lmb + audio_size; audio_occupancy = 0; + // if (enable_fill == 0) { + // debug(1, "flush enable_fill"); + // } + enable_fill = 1; pthread_mutex_unlock(&buffer_mutex); } @@ -330,6 +347,10 @@ static void stop(void) { audio_toq = audio_eoq = audio_lmb; audio_umb = audio_lmb + audio_size; audio_occupancy = 0; + // if (enable_fill == 0) { + // debug(1, "stop enable_fill"); + // } + enable_fill = 1; pthread_mutex_unlock(&buffer_mutex); } -- 2.39.5