From: Mike Brady Date: Fri, 30 Jun 2017 19:05:48 +0000 (+0100) Subject: Add some settings to the new PulseAudio backend. Limit the silent lead-in time to... X-Git-Tag: 3.1~10^2~37 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9ba3ed39a5ae6198a99580df84eeb7c63e7d3f19;p=thirdparty%2Fshairport-sync.git Add some settings to the new PulseAudio backend. Limit the silent lead-in time to pipe and stdout backends. Not tested much. --- diff --git a/audio_pa.c b/audio_pa.c index 969886b1..7bb09686 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -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 diff --git a/common.h b/common.h index 2cfeaba7..9327cab3 100644 --- a/common.h +++ b/common.h @@ -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; diff --git a/player.c b/player.c index 3cd8088e..b176a719 100644 --- a/player.c +++ b/player.c @@ -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.", diff --git a/shairport.c b/shairport.c index 4c77b3b7..e07e8c2b 100644 --- a/shairport.c +++ b/shairport.c @@ -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;