Skip to content

Commit

Permalink
Send silence when no audio is coming through from Shairport Sync. Thi…
Browse files Browse the repository at this point in the history
…s eliminates (?) xrun errors. Clean up a few imprefections.
  • Loading branch information
mikebrady committed Oct 2, 2023
1 parent 29a5589 commit dc197ae
Showing 1 changed file with 71 additions and 50 deletions.
121 changes: 71 additions & 50 deletions audio_pw.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -39,7 +39,7 @@
#include <pipewire/pipewire.h>
#include <spa/param/audio/format-utils.h>

// 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
Expand All @@ -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
Expand All @@ -72,17 +71,17 @@ 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) {
// struct pw_data *pw = userdata;
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) {

Expand All @@ -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);

Expand All @@ -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);
Expand All @@ -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();

Expand All @@ -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];
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand Down

0 comments on commit dc197ae

Please sign in to comment.