diff --git a/main.c b/main.c index ff0de68..1602948 100644 --- a/main.c +++ b/main.c @@ -56,7 +56,7 @@ #if ALSA " -p \t\tSet real time priority of output thread (1-99)\n" #endif - " -r \t\tSpecify sample rates supported by device, enables output device to be off when squeezelite is started; rates = | - | ,,\n" + " -r [:]\tSample rates supported, allows output to be off when squeezelite is started; rates = |-|,,; delay = optional delay switching rates in ms\n" #if RESAMPLE " -R -u [params]\tResample, params = ::::::,\n" " \t\t\t recipe = (v|h|m|l|q)(L|I|M)(s) [E|X], E = exception - resample only if native rate not supported, X = async - resample to max rate for device, otherwise to max sync rate\n" @@ -159,6 +159,7 @@ unsigned stream_buf_size = STREAMBUF_SIZE; unsigned output_buf_size = 0; // set later unsigned rates[MAX_SUPPORTED_SAMPLERATES] = { 0 }; + unsigned rate_delay = 0; char *resample = NULL; char *output_params = NULL; #if LINUX @@ -260,40 +261,47 @@ } break; case 'r': - if (strstr(optarg,",")) { - // parse sample rates and sort them - char *r = next_param(optarg, ','); - unsigned tmp[MAX_SUPPORTED_SAMPLERATES] = { 0 }; - int i, j; - int last = 999999; - for (i = 0; r && i < MAX_SUPPORTED_SAMPLERATES; ++i) { - tmp[i] = atoi(r); - r = next_param(NULL, ','); - } - for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) { - int largest = 0; - for (j = 0; j < MAX_SUPPORTED_SAMPLERATES; ++j) { - if (tmp[j] > largest && tmp[j] < last) { - largest = tmp[j]; + { + char *rstr = next_param(optarg, ':'); + char *dstr = next_param(NULL, ':'); + if (rstr && strstr(rstr, ",")) { + // parse sample rates and sort them + char *r = next_param(rstr, ','); + unsigned tmp[MAX_SUPPORTED_SAMPLERATES] = { 0 }; + int i, j; + int last = 999999; + for (i = 0; r && i < MAX_SUPPORTED_SAMPLERATES; ++i) { + tmp[i] = atoi(r); + r = next_param(NULL, ','); + } + for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) { + int largest = 0; + for (j = 0; j < MAX_SUPPORTED_SAMPLERATES; ++j) { + if (tmp[j] > largest && tmp[j] < last) { + largest = tmp[j]; + } + } + rates[i] = last = largest; + } + } else if (rstr) { + // optstr is - or , extract rates from test rates within this range + unsigned ref[] TEST_RATES; + char *str1 = next_param(rstr, '-'); + char *str2 = next_param(NULL, '-'); + unsigned max = str2 ? atoi(str2) : (str1 ? atoi(str1) : ref[0]); + unsigned min = str1 && str2 ? atoi(str1) : 0; + unsigned tmp; + int i, j; + if (max < min) { tmp = max; max = min; min = tmp; } + rates[0] = max; + for (i = 0, j = 1; i < MAX_SUPPORTED_SAMPLERATES; ++i) { + if (ref[i] < rates[j-1] && ref[i] >= min) { + rates[j++] = ref[i]; } } - rates[i] = last = largest; } - } else { - // optstr is - or , extract rates from test rates within this range - unsigned ref[] TEST_RATES; - char *str1 = next_param(optarg, '-'); - char *str2 = next_param(NULL, '-'); - unsigned max = str2 ? atoi(str2) : (str1 ? atoi(str1) : ref[0]); - unsigned min = str1 && str2 ? atoi(str1) : 0; - unsigned tmp; - int i, j; - if (max < min) { tmp = max; max = min; min = tmp; } - rates[0] = max; - for (i = 0, j = 1; i < MAX_SUPPORTED_SAMPLERATES; ++i) { - if (ref[i] < rates[j-1] && ref[i] >= min) { - rates[j++] = ref[i]; - } + if (dstr) { + rate_delay = atoi(dstr); } } break; @@ -405,13 +413,13 @@ stream_init(log_stream, stream_buf_size); if (!strcmp(output_device, "-")) { - output_init_stdout(log_output, output_buf_size, output_params, rates); + output_init_stdout(log_output, output_buf_size, output_params, rates, rate_delay); } else { #if ALSA - output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rt_priority); + output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority); #endif #if PORTAUDIO - output_init_pa(log_output, output_device, output_buf_size, output_params, rates); + output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay); #endif } diff --git a/output.c b/output.c index 775e90e..79109c6 100644 --- a/output.c +++ b/output.c @@ -119,28 +119,35 @@ if (output.track_start && !silence) { if (output.track_start == outputbuf->readp) { + unsigned delay = 0; + if (output.current_sample_rate != output.next_sample_rate) { + delay = output.rate_delay; + } + IF_DSD( + if (output.dop != output.next_dop) { + delay = output.dop_delay; + } + ) frames -= size; - IF_DSD( - if (output.dop != output.next_dop) { - if (output.dop_delay) { - // add silence delay in two halves, before and after track start and pcm-dop change - if (!output.dop_delay_active) { - output.pause_frames = output.current_sample_rate * output.dop_delay / 2000; - output.dop_delay_active = true; // first delay - don't process track start - break; - } else { - output.pause_frames = output.next_sample_rate * output.dop_delay / 2000; - output.dop_delay_active = false; // second delay - process track start - } - output.state = OUTPUT_PAUSE_FRAMES; - } + // add silence delay in two halves, before and after track start and rate or pcm-dop change + if (delay) { + output.state = OUTPUT_PAUSE_FRAMES; + if (!output.delay_active) { + output.pause_frames = output.current_sample_rate * delay / 2000; + output.delay_active = true; // first delay - don't process track start + break; + } else { + output.pause_frames = output.next_sample_rate * delay / 2000; + output.delay_active = false; // second delay - process track start } - output.dop = output.next_dop; - ) + } LOG_INFO("track start sample rate: %u replay_gain: %u", output.next_sample_rate, output.next_replay_gain); output.frames_played = 0; output.track_started = true; output.current_sample_rate = output.next_sample_rate; + IF_DSD( + output.dop = output.next_dop; + ) if (!output.fade == FADE_ACTIVE || !output.fade_mode == FADE_CROSSFADE) { output.current_replay_gain = output.next_replay_gain; } @@ -413,7 +420,7 @@ if (output.error_opening) { output.current_sample_rate = output.default_sample_rate; } - IF_DSD( output.dop_delay_active = false; ) + IF_DSD( output.delay_active = false; ) } output.frames_played = 0; UNLOCK; diff --git a/output_alsa.c b/output_alsa.c index bdb26ff..9ccc81e 100644 --- a/output_alsa.c +++ b/output_alsa.c @@ -611,7 +611,8 @@ static pthread_t thread; -void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rt_priority) { +void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], + unsigned rate_delay, unsigned rt_priority) { unsigned alsa_buffer = ALSA_BUFFER_TIME; unsigned alsa_period = ALSA_PERIOD_COUNT; @@ -646,6 +647,7 @@ output.period = alsa_period; output.start_frames = 0; output.write_cb = &_write_frames; + output.rate_delay = rate_delay; if (alsa_sample_fmt) { if (!strcmp(alsa_sample_fmt, "32")) alsa.format = SND_PCM_FORMAT_S32_LE; diff --git a/output_pa.c b/output_pa.c index 46e1cc8..0d1bc29 100644 --- a/output_pa.c +++ b/output_pa.c @@ -369,7 +369,7 @@ return ret; } -void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[]) { +void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay) { PaError err; unsigned latency = 0; int osx_playnice = -1; @@ -391,6 +391,7 @@ output.format = 0; output.start_frames = 0; output.write_cb = &_write_frames; + output.rate_delay = rate_delay; pa.stream = NULL; LOG_INFO("requested latency: %u", output.latency); diff --git a/output_stdout.c b/output_stdout.c index e72a927..93b96c0 100644 --- a/output_stdout.c +++ b/output_stdout.c @@ -119,7 +119,7 @@ static thread_type thread; -void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[]) { +void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay) { loglevel = level; LOG_INFO("init output stdout"); @@ -136,6 +136,7 @@ output.format = S32_LE; output.start_frames = FRAME_BLOCK * 2; output.write_cb = &_stdout_write_frames; + output.rate_delay = rate_delay; if (params) { if (!strcmp(params, "32")) output.format = S32_LE; diff --git a/squeezelite.h b/squeezelite.h index ddffcbe..be31215 100644 --- a/squeezelite.h +++ b/squeezelite.h @@ -524,12 +524,13 @@ fade_dir fade_dir; fade_mode fade_mode; // set by slimproto unsigned fade_secs; // set by slimproto + unsigned rate_delay; + bool delay_active; #if DSD bool next_dop; // set in decode thread bool dop; bool has_dop; // set in dop_init - output device supports dop unsigned dop_delay; // set in dop_init - delay in ms switching to/from dop - bool dop_delay_active; #endif }; @@ -544,7 +545,8 @@ #if ALSA void list_devices(void); bool test_open(const char *device, unsigned rates[]); -void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rt_priority); +void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], + unsigned rate_delay, unsigned rt_priority); void output_close_alsa(void); #endif @@ -552,13 +554,13 @@ #if PORTAUDIO void list_devices(void); bool test_open(const char *device, unsigned rates[]); -void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[]); +void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay); void output_close_pa(void); void _pa_open(void); #endif // output_stdout.c -void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[]); +void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay); void output_close_stdout(void); // output_pack.c