add configurable silence delay on rate change in similar manner to dop delay
Adrian Smith
9 years ago
55 | 55 | #if ALSA |
56 | 56 | " -p <priority>\t\tSet real time priority of output thread (1-99)\n" |
57 | 57 | #endif |
58 | " -r <rates>\t\tSpecify sample rates supported by device, enables output device to be off when squeezelite is started; rates = <maxrate> | <minrate>-<maxrate> | <rate1>,<rate2>,<rate3>\n" | |
58 | " -r <rates>[:<delay>]\tSample rates supported, allows output to be off when squeezelite is started; rates = <maxrate>|<minrate>-<maxrate>|<rate1>,<rate2>,<rate3>; delay = optional delay switching rates in ms\n" | |
59 | 59 | #if RESAMPLE |
60 | 60 | " -R -u [params]\tResample, params = <recipe>:<flags>:<attenuation>:<precision>:<passband_end>:<stopband_start>:<phase_response>,\n" |
61 | 61 | " \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" |
158 | 158 | unsigned stream_buf_size = STREAMBUF_SIZE; |
159 | 159 | unsigned output_buf_size = 0; // set later |
160 | 160 | unsigned rates[MAX_SUPPORTED_SAMPLERATES] = { 0 }; |
161 | unsigned rate_delay = 0; | |
161 | 162 | char *resample = NULL; |
162 | 163 | char *output_params = NULL; |
163 | 164 | #if LINUX |
259 | 260 | } |
260 | 261 | break; |
261 | 262 | case 'r': |
262 | if (strstr(optarg,",")) { | |
263 | // parse sample rates and sort them | |
264 | char *r = next_param(optarg, ','); | |
265 | unsigned tmp[MAX_SUPPORTED_SAMPLERATES] = { 0 }; | |
266 | int i, j; | |
267 | int last = 999999; | |
268 | for (i = 0; r && i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
269 | tmp[i] = atoi(r); | |
270 | r = next_param(NULL, ','); | |
271 | } | |
272 | for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
273 | int largest = 0; | |
274 | for (j = 0; j < MAX_SUPPORTED_SAMPLERATES; ++j) { | |
275 | if (tmp[j] > largest && tmp[j] < last) { | |
276 | largest = tmp[j]; | |
263 | { | |
264 | char *rstr = next_param(optarg, ':'); | |
265 | char *dstr = next_param(NULL, ':'); | |
266 | if (rstr && strstr(rstr, ",")) { | |
267 | // parse sample rates and sort them | |
268 | char *r = next_param(rstr, ','); | |
269 | unsigned tmp[MAX_SUPPORTED_SAMPLERATES] = { 0 }; | |
270 | int i, j; | |
271 | int last = 999999; | |
272 | for (i = 0; r && i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
273 | tmp[i] = atoi(r); | |
274 | r = next_param(NULL, ','); | |
275 | } | |
276 | for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
277 | int largest = 0; | |
278 | for (j = 0; j < MAX_SUPPORTED_SAMPLERATES; ++j) { | |
279 | if (tmp[j] > largest && tmp[j] < last) { | |
280 | largest = tmp[j]; | |
281 | } | |
282 | } | |
283 | rates[i] = last = largest; | |
284 | } | |
285 | } else if (rstr) { | |
286 | // optstr is <min>-<max> or <max>, extract rates from test rates within this range | |
287 | unsigned ref[] TEST_RATES; | |
288 | char *str1 = next_param(rstr, '-'); | |
289 | char *str2 = next_param(NULL, '-'); | |
290 | unsigned max = str2 ? atoi(str2) : (str1 ? atoi(str1) : ref[0]); | |
291 | unsigned min = str1 && str2 ? atoi(str1) : 0; | |
292 | unsigned tmp; | |
293 | int i, j; | |
294 | if (max < min) { tmp = max; max = min; min = tmp; } | |
295 | rates[0] = max; | |
296 | for (i = 0, j = 1; i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
297 | if (ref[i] < rates[j-1] && ref[i] >= min) { | |
298 | rates[j++] = ref[i]; | |
277 | 299 | } |
278 | 300 | } |
279 | rates[i] = last = largest; | |
280 | 301 | } |
281 | } else { | |
282 | // optstr is <min>-<max> or <max>, extract rates from test rates within this range | |
283 | unsigned ref[] TEST_RATES; | |
284 | char *str1 = next_param(optarg, '-'); | |
285 | char *str2 = next_param(NULL, '-'); | |
286 | unsigned max = str2 ? atoi(str2) : (str1 ? atoi(str1) : ref[0]); | |
287 | unsigned min = str1 && str2 ? atoi(str1) : 0; | |
288 | unsigned tmp; | |
289 | int i, j; | |
290 | if (max < min) { tmp = max; max = min; min = tmp; } | |
291 | rates[0] = max; | |
292 | for (i = 0, j = 1; i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
293 | if (ref[i] < rates[j-1] && ref[i] >= min) { | |
294 | rates[j++] = ref[i]; | |
295 | } | |
302 | if (dstr) { | |
303 | rate_delay = atoi(dstr); | |
296 | 304 | } |
297 | 305 | } |
298 | 306 | break; |
404 | 412 | stream_init(log_stream, stream_buf_size); |
405 | 413 | |
406 | 414 | if (!strcmp(output_device, "-")) { |
407 | output_init_stdout(log_output, output_buf_size, output_params, rates); | |
415 | output_init_stdout(log_output, output_buf_size, output_params, rates, rate_delay); | |
408 | 416 | } else { |
409 | 417 | #if ALSA |
410 | output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rt_priority); | |
418 | output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority); | |
411 | 419 | #endif |
412 | 420 | #if PORTAUDIO |
413 | output_init_pa(log_output, output_device, output_buf_size, output_params, rates); | |
421 | output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay); | |
414 | 422 | #endif |
415 | 423 | } |
416 | 424 |
118 | 118 | |
119 | 119 | if (output.track_start && !silence) { |
120 | 120 | if (output.track_start == outputbuf->readp) { |
121 | unsigned delay = 0; | |
122 | if (output.current_sample_rate != output.next_sample_rate) { | |
123 | delay = output.rate_delay; | |
124 | } | |
125 | IF_DSD( | |
126 | if (output.dop != output.next_dop) { | |
127 | delay = output.dop_delay; | |
128 | } | |
129 | ) | |
121 | 130 | frames -= size; |
122 | IF_DSD( | |
123 | if (output.dop != output.next_dop) { | |
124 | if (output.dop_delay) { | |
125 | // add silence delay in two halves, before and after track start and pcm-dop change | |
126 | if (!output.dop_delay_active) { | |
127 | output.pause_frames = output.current_sample_rate * output.dop_delay / 2000; | |
128 | output.dop_delay_active = true; // first delay - don't process track start | |
129 | break; | |
130 | } else { | |
131 | output.pause_frames = output.next_sample_rate * output.dop_delay / 2000; | |
132 | output.dop_delay_active = false; // second delay - process track start | |
133 | } | |
134 | output.state = OUTPUT_PAUSE_FRAMES; | |
135 | } | |
131 | // add silence delay in two halves, before and after track start and rate or pcm-dop change | |
132 | if (delay) { | |
133 | output.state = OUTPUT_PAUSE_FRAMES; | |
134 | if (!output.delay_active) { | |
135 | output.pause_frames = output.current_sample_rate * delay / 2000; | |
136 | output.delay_active = true; // first delay - don't process track start | |
137 | break; | |
138 | } else { | |
139 | output.pause_frames = output.next_sample_rate * delay / 2000; | |
140 | output.delay_active = false; // second delay - process track start | |
136 | 141 | } |
137 | output.dop = output.next_dop; | |
138 | ) | |
142 | } | |
139 | 143 | LOG_INFO("track start sample rate: %u replay_gain: %u", output.next_sample_rate, output.next_replay_gain); |
140 | 144 | output.frames_played = 0; |
141 | 145 | output.track_started = true; |
142 | 146 | output.current_sample_rate = output.next_sample_rate; |
147 | IF_DSD( | |
148 | output.dop = output.next_dop; | |
149 | ) | |
143 | 150 | if (!output.fade == FADE_ACTIVE || !output.fade_mode == FADE_CROSSFADE) { |
144 | 151 | output.current_replay_gain = output.next_replay_gain; |
145 | 152 | } |
412 | 419 | if (output.error_opening) { |
413 | 420 | output.current_sample_rate = output.default_sample_rate; |
414 | 421 | } |
415 | IF_DSD( output.dop_delay_active = false; ) | |
422 | IF_DSD( output.delay_active = false; ) | |
416 | 423 | } |
417 | 424 | output.frames_played = 0; |
418 | 425 | UNLOCK; |
610 | 610 | |
611 | 611 | static pthread_t thread; |
612 | 612 | |
613 | void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rt_priority) { | |
613 | void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], | |
614 | unsigned rate_delay, unsigned rt_priority) { | |
614 | 615 | |
615 | 616 | unsigned alsa_buffer = ALSA_BUFFER_TIME; |
616 | 617 | unsigned alsa_period = ALSA_PERIOD_COUNT; |
645 | 646 | output.period = alsa_period; |
646 | 647 | output.start_frames = 0; |
647 | 648 | output.write_cb = &_write_frames; |
649 | output.rate_delay = rate_delay; | |
648 | 650 | |
649 | 651 | if (alsa_sample_fmt) { |
650 | 652 | if (!strcmp(alsa_sample_fmt, "32")) alsa.format = SND_PCM_FORMAT_S32_LE; |
368 | 368 | return ret; |
369 | 369 | } |
370 | 370 | |
371 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[]) { | |
371 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay) { | |
372 | 372 | PaError err; |
373 | 373 | unsigned latency = 0; |
374 | 374 | int osx_playnice = -1; |
390 | 390 | output.format = 0; |
391 | 391 | output.start_frames = 0; |
392 | 392 | output.write_cb = &_write_frames; |
393 | output.rate_delay = rate_delay; | |
393 | 394 | pa.stream = NULL; |
394 | 395 | |
395 | 396 | LOG_INFO("requested latency: %u", output.latency); |
118 | 118 | |
119 | 119 | static thread_type thread; |
120 | 120 | |
121 | void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[]) { | |
121 | void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay) { | |
122 | 122 | loglevel = level; |
123 | 123 | |
124 | 124 | LOG_INFO("init output stdout"); |
135 | 135 | output.format = S32_LE; |
136 | 136 | output.start_frames = FRAME_BLOCK * 2; |
137 | 137 | output.write_cb = &_stdout_write_frames; |
138 | output.rate_delay = rate_delay; | |
138 | 139 | |
139 | 140 | if (params) { |
140 | 141 | if (!strcmp(params, "32")) output.format = S32_LE; |
523 | 523 | fade_dir fade_dir; |
524 | 524 | fade_mode fade_mode; // set by slimproto |
525 | 525 | unsigned fade_secs; // set by slimproto |
526 | unsigned rate_delay; | |
527 | bool delay_active; | |
526 | 528 | #if DSD |
527 | 529 | bool next_dop; // set in decode thread |
528 | 530 | bool dop; |
529 | 531 | bool has_dop; // set in dop_init - output device supports dop |
530 | 532 | unsigned dop_delay; // set in dop_init - delay in ms switching to/from dop |
531 | bool dop_delay_active; | |
532 | 533 | #endif |
533 | 534 | }; |
534 | 535 | |
543 | 544 | #if ALSA |
544 | 545 | void list_devices(void); |
545 | 546 | bool test_open(const char *device, unsigned rates[]); |
546 | void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rt_priority); | |
547 | void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], | |
548 | unsigned rate_delay, unsigned rt_priority); | |
547 | 549 | void output_close_alsa(void); |
548 | 550 | #endif |
549 | 551 | |
551 | 553 | #if PORTAUDIO |
552 | 554 | void list_devices(void); |
553 | 555 | bool test_open(const char *device, unsigned rates[]); |
554 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[]); | |
556 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay); | |
555 | 557 | void output_close_pa(void); |
556 | 558 | void _pa_open(void); |
557 | 559 | #endif |
558 | 560 | |
559 | 561 | // output_stdout.c |
560 | void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[]); | |
562 | void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay); | |
561 | 563 | void output_close_stdout(void); |
562 | 564 | |
563 | 565 | // output_pack.c |