Add some settings to the new PulseAudio backend. Limit the silent lead-in time to pipe and stdout backends. Not tested much.

This commit is contained in:
Mike Brady
2017-06-30 20:05:48 +01:00
parent c13ff31c82
commit 9ba3ed39a5
4 changed files with 161 additions and 66 deletions
+45 -2
View File
@@ -1,4 +1,4 @@
// Based on
// Based (distantly, with thanks) on
// http://stackoverflow.com/questions/29977651/how-can-the-pulseaudio-asynchronous-library-be-used-to-play-raw-pcm-data
#include "audio.h"
@@ -45,6 +45,46 @@ static int init(int argc, char **argv) {
// default values
config.audio_backend_buffer_desired_length = 0.35;
config.audio_backend_latency_offset = 0;
// get settings from settings file first, allow them to be overridden by
// command line options
if (config.cfg != NULL) {
const char *str;
double dvalue;
/* Get the desired buffer size setting. */
if (config_lookup_float(config.cfg, "pa.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
if ((dvalue < 0) || (dvalue > 1.5)) {
die("Invalid pa audio_backend_buffer_desired_length_in_seconds \"%f\". It "
"should be between 0 and "
"1.5, default is 0.35 seconds",
dvalue);
} else {
config.audio_backend_buffer_desired_length = dvalue;
}
}
/* Get the latency offset. */
if (config_lookup_float(config.cfg, "pa.audio_backend_latency_offset_in_seconds", &dvalue)) {
if ((dvalue < -1.0) || (dvalue > 1.5)) {
die("Invalid pa audio_backend_latency_offset_in_seconds \"%f\". It "
"should be between -1.0 and +1.5, default is 0 seconds",
dvalue);
} else {
config.audio_backend_latency_offset = dvalue;
}
}
/* Get the Application Name. */
if (config_lookup_string(config.cfg, "pa.application_name", &str)) {
config.pa_application_name = (char *)str;
}
}
// finish collecting settings
// allocate space for the audio buffer
audio_lmb = malloc(audio_size);
@@ -58,7 +98,10 @@ static int init(int argc, char **argv) {
mainloop = pa_threaded_mainloop_new();
assert(mainloop);
mainloop_api = pa_threaded_mainloop_get_api(mainloop);
context = pa_context_new(mainloop_api, "Shairport Sync");
if (config.pa_application_name)
context = pa_context_new(mainloop_api, config.pa_application_name);
else
context = pa_context_new(mainloop_api, "Shairport Sync");
assert(context);
// Set a callback so we can wait for the context to be ready
+5
View File
@@ -74,6 +74,10 @@ typedef struct {
char *password;
char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running
// on host %h"
#ifdef CONFIG_PA
char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in the Sound Preferences in most desktop Linuxes.
// Defaults to "Shairport Sync". Shairport Sync must be playing to see it.
#endif
#ifdef CONFIG_METADATA
int metadata_enabled;
char *metadata_pipename;
@@ -134,6 +138,7 @@ typedef struct {
// audio backend buffer -- the DAC buffer for ALSA
double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any
// fixed latency there might be in the audio path
double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play.
uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's
// native range.
enum sps_format_t output_format;
+109 -64
View File
@@ -874,80 +874,124 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC
if (local_time_now >= conn->first_packet_time_to_play) {
debug(1,"Gone past starting time");
have_sent_prefiller_silence = 1;
conn->ab_buffering = 0;
// we've gone past the time...
// debug(1,"Run past the exact start time by %llu frames, with time now of %llx, fpttp
// of %llx and dac_delay of %d and %d packets;
// flush.",(((tn-conn->first_packet_time_to_play)*config.output_rate)>>32)+dac_delay,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read,
// ab_write));
/*
if (config.output->flush)
config.output->flush();
ab_resync(conn);
conn->first_packet_timestamp = 0;
conn->first_packet_time_to_play = 0;
conn->time_since_play_started = 0;
*/
} else {
// conn->first_packet_time_to_play is definitely later than local_time_now
if ((config.output->delay) && (have_sent_prefiller_silence != 0)) {
int resp = config.output->delay(&dac_delay);
if (resp != 0) {
debug(1, "Error %d getting dac_delay in buffer_get_frame.", resp);
dac_delay = 0;
}
} else
dac_delay = 0;
int64_t gross_frame_gap =
((conn->first_packet_time_to_play - local_time_now) * config.output_rate) >> 32;
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
if (exact_frame_gap < 0) {
// we've gone past the time...
// debug(1,"Run a bit past the exact start time by %lld frames, with time now of
// %llx, fpttp of %llx and dac_delay of %d and %d packets;
// flush.",-exact_frame_gap,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read,
// ab_write));
if (config.output->flush)
config.output->flush();
ab_resync(conn);
conn->first_packet_timestamp = 0;
conn->first_packet_time_to_play = 0;
} else {
int64_t fs = filler_size;
if (fs > (max_dac_delay - dac_delay))
fs = max_dac_delay - dac_delay;
if (fs < 0) {
debug(2, "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",
max_dac_delay, dac_delay);
fs = 0;
}
if ((exact_frame_gap <= fs) ||
(exact_frame_gap <= conn->max_frames_per_packet * 2)) {
fs = exact_frame_gap;
// debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d,
// with %d packets, ab_read is %04x, ab_write is
// %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read,
// ab_write),ab_read,ab_write);
conn->ab_buffering = 0;
}
signed short *silence;
// if (fs==0)
// debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and
// dac_delay of %lld.",gross_frame_gap,dac_delay);
// the fs (number of frames of silence to play) can be zero in the DAC doesn't start
// ouotputting frames for a while -- it could get loaded up but not start responding
// for many milliseconds.
if (fs != 0) {
silence = malloc(conn->output_bytes_per_frame * fs);
if (silence == NULL)
debug(1, "Failed to allocate %d byte silence buffer.", fs);
else {
memset(silence, 0, conn->output_bytes_per_frame * fs);
// debug(1,"Frames to start: %llu, DAC delay %ld, buffer: %d
// packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, conn->ab_write,
// conn->ab_read));
config.output->play(silence, fs);
free(silence);
have_sent_prefiller_silence = 1;
// do some calculations
int64_t lead_time = conn->first_packet_time_to_play-local_time_now;
int64_t lead_in_time = (int64_t)(config.audio_backend_silent_lead_in_time*(int64_t)0x100000000);
//debug(1,"Lead in time is %llx.",lead_in_time);
// an audio_backend_silent_lead_in_time of less than zero means start filling ASAP
if ((lead_in_time<0) || (lead_time<=lead_in_time)) {
// debug(1,"Continuing...");
if (config.output->delay) {
// conn->first_packet_time_to_play is definitely later than local_time_now
if (have_sent_prefiller_silence != 0) {
int resp = config.output->delay(&dac_delay);
if (resp != 0) {
debug(1, "Error %d getting dac_delay in buffer_get_frame.", resp);
dac_delay = 0;
}
} else {
dac_delay = 0;
}
int64_t gross_frame_gap =
((conn->first_packet_time_to_play - local_time_now) * config.output_rate) >> 32;
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
if (exact_frame_gap < 0) {
// we've gone past the time...
// debug(1,"Run a bit past the exact start time by %lld frames, with time now of
// %llx, fpttp of %llx and dac_delay of %d and %d packets;
// flush.",-exact_frame_gap,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read,
// ab_write));
if (config.output->flush)
config.output->flush();
ab_resync(conn);
conn->first_packet_timestamp = 0;
conn->first_packet_time_to_play = 0;
} else {
int64_t fs = filler_size;
if (fs > (max_dac_delay - dac_delay))
fs = max_dac_delay - dac_delay;
if (fs < 0) {
debug(2, "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",
max_dac_delay, dac_delay);
fs = 0;
}
if ((exact_frame_gap <= fs) ||
(exact_frame_gap <= conn->max_frames_per_packet * 2)) {
fs = exact_frame_gap;
// debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d,
// with %d packets, ab_read is %04x, ab_write is
// %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read,
// ab_write),ab_read,ab_write);
conn->ab_buffering = 0;
}
signed short *silence;
// if (fs==0)
// debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and
// dac_delay of %lld.",gross_frame_gap,dac_delay);
// the fs (number of frames of silence to play) can be zero in the DAC doesn't start
// ouotputting frames for a while -- it could get loaded up but not start responding
// for many milliseconds.
if (fs != 0) {
silence = malloc(conn->output_bytes_per_frame * fs);
if (silence == NULL)
debug(1, "Failed to allocate %d byte silence buffer.", fs);
else {
memset(silence, 0, conn->output_bytes_per_frame * fs);
debug(1,"Frames to start: %llu, DAC delay %ld, buffer: %d packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, conn->ab_write, conn->ab_read));
config.output->play(silence, fs);
free(silence);
have_sent_prefiller_silence = 1;
}
}
}
} else {
//no delay function on back end -- just send the prefiller silence
debug(1,"Back end has no delay function.");
// send the appropriate prefiller here...
signed short *silence;
if (lead_time != 0) {
int64_t frame_gap = (lead_time * config.output_rate) >> 32;
debug(1,"%d frames needed.",frame_gap);
while (frame_gap>0) {
size_t fs = config.output_rate/10;
if (fs>frame_gap)
fs = frame_gap;
silence = malloc(conn->output_bytes_per_frame * fs);
if (silence == NULL)
debug(1, "Failed to allocate %d frame silence buffer.", fs);
else {
debug(1, "Outputting %d frames of silence.", fs);
memset(silence, 0, conn->output_bytes_per_frame * fs);
config.output->play(silence, fs);
free(silence);
}
frame_gap -= fs;
}
}
have_sent_prefiller_silence = 1;
conn->ab_buffering = 0;
}
}
}
@@ -1755,9 +1799,9 @@ static void *player_thread_func(void *arg) {
}
if (sync_error_out_of_bounds > 3) {
// debug(1, "New lost sync with source for %d consecutive packets -- flushing and "
// "resyncing. Error: %lld.",
// sync_error_out_of_bounds, sync_error);
// debug(1, "New lost sync with source for %d consecutive packets -- flushing and "
// "resyncing. Error: %lld.",
// sync_error_out_of_bounds, sync_error);
sync_error_out_of_bounds = 0;
size_t filler_length =
@@ -1919,7 +1963,8 @@ static void *player_thread_func(void *arg) {
sync_error_out_of_bounds++;
// debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld;
// timestamp: %llx, time now
// %llx",sync_error,previous_sync_error,current_delay,inframe->timestamp,local_time_now);
//
%llx",sync_error,previous_sync_error,current_delay,inframe->timestamp,local_time_now);
if (sync_error_out_of_bounds > 3) {
debug(1, "Lost sync with source for %d consecutive packets -- flushing and "
"resyncing. Error: %lld.",
+2
View File
@@ -372,6 +372,8 @@ int parse_options(int argc, char **argv) {
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
config.tolerance = 1.0 * fTolerance / 44100;
config.audio_backend_silent_lead_in_time = -1.0; // flag to indicate it has not been set
config.audio_backend_silent_lead_in_time = 0.0; // flag to indicate it has not been set
config_setting_t *setting;
const char *str = 0;