add option to close output device when idle
Adrian Smith
9 years ago
125 | 125 | |
126 | 126 | Minor changes |
127 | 127 | - fix crash which could occur when resampling |
128 | ||
129 | Version 1.8 | |
130 | =========== | |
131 | ||
132 | Features | |
133 | - support for closing output device when idle with -C option |
57 | 57 | " -a <f>\t\tSpecify sample format (16|24|32) of output file when using -o - to output samples to stdout (interleaved little endian only)\n" |
58 | 58 | " -b <stream>:<output>\tSpecify internal Stream and Output buffer sizes in Kbytes\n" |
59 | 59 | " -c <codec1>,<codec2>\tRestrict codecs to those specified, otherwise load all available codecs; known codecs: " CODECS "\n" |
60 | " -C <timeout>\t\tClose output device when idle after timeout seconds, default is to keep it open while player is 'on'\n" | |
60 | 61 | " -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output, level: info|debug|sdebug\n" |
61 | 62 | " -e <codec1>,<codec2>\tExplicitly exclude native support of one or more codecs; known codecs: " CODECS "\n" |
62 | 63 | " -f <logfile>\t\tWrite debug to logfile\n" |
186 | 187 | unsigned rate_delay = 0; |
187 | 188 | char *resample = NULL; |
188 | 189 | char *output_params = NULL; |
190 | unsigned idle = 0; | |
189 | 191 | #if LINUX || FREEBSD |
190 | 192 | bool daemonize = false; |
191 | 193 | char *pidfile = NULL; |
223 | 225 | |
224 | 226 | while (optind < argc && strlen(argv[optind]) >= 2 && argv[optind][0] == '-') { |
225 | 227 | char *opt = argv[optind] + 1; |
226 | if (strstr("oabcdefmMnNpPrs", opt) && optind < argc - 1) { | |
228 | if (strstr("oabcCdefmMnNpPrs", opt) && optind < argc - 1) { | |
227 | 229 | optarg = argv[optind + 1]; |
228 | 230 | optind += 2; |
229 | 231 | } else if (strstr("ltz?" |
262 | 264 | break; |
263 | 265 | case 'c': |
264 | 266 | include_codecs = optarg; |
267 | break; | |
268 | case 'C': | |
269 | if (atoi(optarg) > 0) { | |
270 | idle = atoi(optarg) * 1000; | |
271 | } | |
265 | 272 | break; |
266 | 273 | case 'e': |
267 | 274 | exclude_codecs = optarg; |
491 | 498 | output_init_stdout(log_output, output_buf_size, output_params, rates, rate_delay); |
492 | 499 | } else { |
493 | 500 | #if ALSA |
494 | output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority); | |
501 | output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority, idle); | |
495 | 502 | #endif |
496 | 503 | #if PORTAUDIO |
497 | output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay); | |
504 | output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle); | |
498 | 505 | #endif |
499 | 506 | } |
500 | 507 |
332 | 332 | } |
333 | 333 | } |
334 | 334 | |
335 | void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[]) { | |
335 | void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle) { | |
336 | 336 | unsigned i; |
337 | 337 | |
338 | 338 | loglevel = level; |
366 | 366 | output.device = device; |
367 | 367 | output.fade = FADE_INACTIVE; |
368 | 368 | output.error_opening = false; |
369 | output.idle_to = (u32_t) idle; | |
369 | 370 | |
370 | 371 | if (!rates[0]) { |
371 | 372 | if (!test_open(output.device, output.supported_rates)) { |
617 | 617 | static pthread_t thread; |
618 | 618 | |
619 | 619 | void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], |
620 | unsigned rate_delay, unsigned rt_priority) { | |
620 | unsigned rate_delay, unsigned rt_priority, unsigned idle) { | |
621 | 621 | |
622 | 622 | unsigned alsa_buffer = ALSA_BUFFER_TIME; |
623 | 623 | unsigned alsa_period = ALSA_PERIOD_COUNT; |
666 | 666 | |
667 | 667 | snd_lib_error_set_handler((snd_lib_error_handler_t)alsa_error_handler); |
668 | 668 | |
669 | output_init_common(level, device, output_buf_size, rates); | |
669 | output_init_common(level, device, output_buf_size, rates, idle); | |
670 | 670 | |
671 | 671 | #if LINUX |
672 | 672 | // RT linux - aim to avoid pagefaults by locking memory: |
376 | 376 | return ret; |
377 | 377 | } |
378 | 378 | |
379 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay) { | |
379 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, | |
380 | unsigned idle) { | |
380 | 381 | PaError err; |
381 | 382 | unsigned latency = 0; |
382 | 383 | int osx_playnice = -1; |
408 | 409 | exit(0); |
409 | 410 | } |
410 | 411 | |
411 | output_init_common(level, device, output_buf_size, rates); | |
412 | output_init_common(level, device, output_buf_size, rates, idle); | |
412 | 413 | |
413 | 414 | LOCK; |
414 | 415 |
149 | 149 | rates[0] = 44100; |
150 | 150 | } |
151 | 151 | |
152 | output_init_common(level, "-", output_buf_size, rates); | |
152 | output_init_common(level, "-", output_buf_size, rates, 0); | |
153 | 153 | |
154 | 154 | #if LINUX || OSX || FREEBSD |
155 | 155 | pthread_attr_t attr; |
256 | 256 | unsigned interval = unpackN(&strm->replay_gain); |
257 | 257 | LOCK_O; |
258 | 258 | output.pause_frames = interval * status.current_sample_rate / 1000; |
259 | output.state = interval ? OUTPUT_PAUSE_FRAMES : OUTPUT_STOPPED; | |
259 | if (interval) { | |
260 | output.state = OUTPUT_PAUSE_FRAMES; | |
261 | } else { | |
262 | output.state = OUTPUT_STOPPED; | |
263 | output.stop_time = gettime_ms(); | |
264 | } | |
260 | 265 | UNLOCK_O; |
261 | 266 | if (!interval) sendSTAT("STMp", 0); |
262 | 267 | LOG_DEBUG("pause interval: %u", interval); |
367 | 372 | if (!aude->enable_spdif && output.state != OUTPUT_OFF) { |
368 | 373 | output.state = OUTPUT_OFF; |
369 | 374 | } |
370 | if (aude->enable_spdif && output.state == OUTPUT_OFF) { | |
375 | if (aude->enable_spdif && output.state == OUTPUT_OFF && !output.idle_to) { | |
371 | 376 | output.state = OUTPUT_STOPPED; |
377 | output.stop_time = gettime_ms(); | |
372 | 378 | } |
373 | 379 | UNLOCK_O; |
374 | 380 | } |
560 | 566 | bool _sendSTMo = false; |
561 | 567 | bool _sendSTMn = false; |
562 | 568 | bool _stream_disconnect = false; |
569 | bool _start_output = false; | |
570 | decode_state _decode_state; | |
563 | 571 | disconnect_code disconnect_code; |
564 | 572 | static char header[MAX_HEADER]; |
565 | 573 | size_t header_len = 0; |
590 | 598 | stream.meta_send = false; |
591 | 599 | } |
592 | 600 | UNLOCK_S; |
601 | ||
602 | LOCK_D; | |
603 | if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) && !sentSTMl | |
604 | && decode.state == DECODE_READY) { | |
605 | if (autostart == 0) { | |
606 | decode.state = DECODE_RUNNING; | |
607 | _sendSTMl = true; | |
608 | sentSTMl = true; | |
609 | } else if (autostart == 1) { | |
610 | decode.state = DECODE_RUNNING; | |
611 | _start_output = true; | |
612 | } | |
613 | // autostart 2 and 3 require cont to be received first | |
614 | } | |
615 | if (decode.state == DECODE_COMPLETE || decode.state == DECODE_ERROR) { | |
616 | if (decode.state == DECODE_COMPLETE) _sendSTMd = true; | |
617 | if (decode.state == DECODE_ERROR) _sendSTMn = true; | |
618 | decode.state = DECODE_STOPPED; | |
619 | if (status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) { | |
620 | _stream_disconnect = true; | |
621 | } | |
622 | } | |
623 | _decode_state = decode.state; | |
624 | UNLOCK_D; | |
593 | 625 | |
594 | 626 | LOCK_O; |
595 | 627 | status.output_full = _buf_used(outputbuf); |
610 | 642 | output.pa_reopen = false; |
611 | 643 | } |
612 | 644 | #endif |
613 | if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT) { | |
645 | if (_start_output && (output.state == OUTPUT_STOPPED || OUTPUT_OFF)) { | |
646 | output.state = OUTPUT_BUFFER; | |
647 | } | |
648 | if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT && | |
649 | _decode_state == DECODE_STOPPED) { | |
614 | 650 | _sendSTMu = true; |
615 | 651 | sentSTMu = true; |
652 | LOG_DEBUG("output underrun"); | |
653 | output.state = OUTPUT_STOPPED; | |
654 | output.stop_time = now; | |
616 | 655 | } |
617 | 656 | if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) { |
618 | 657 | _sendSTMo = true; |
619 | 658 | sentSTMo = true; |
620 | 659 | } |
621 | UNLOCK_O; | |
622 | ||
623 | LOCK_D; | |
624 | if (decode.state == DECODE_RUNNING && now - status.last > 1000) { | |
660 | if (output.state == OUTPUT_STOPPED && output.idle_to && (now - output.stop_time > output.idle_to)) { | |
661 | output.state = OUTPUT_OFF; | |
662 | LOG_DEBUG("output timeout"); | |
663 | } | |
664 | if (output.state == OUTPUT_RUNNING && now - status.last > 1000) { | |
625 | 665 | _sendSTMt = true; |
626 | 666 | status.last = now; |
627 | 667 | } |
628 | if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) && !sentSTMl | |
629 | && decode.state == DECODE_READY) { | |
630 | if (autostart == 0) { | |
631 | decode.state = DECODE_RUNNING; | |
632 | _sendSTMl = true; | |
633 | sentSTMl = true; | |
634 | } else if (autostart == 1) { | |
635 | decode.state = DECODE_RUNNING; | |
636 | LOCK_O; | |
637 | if (output.state == OUTPUT_STOPPED) { | |
638 | output.state = OUTPUT_BUFFER; | |
639 | } | |
640 | UNLOCK_O; | |
641 | } | |
642 | // autostart 2 and 3 require cont to be received first | |
643 | } | |
644 | if (decode.state == DECODE_COMPLETE || decode.state == DECODE_ERROR) { | |
645 | if (decode.state == DECODE_COMPLETE) _sendSTMd = true; | |
646 | if (decode.state == DECODE_ERROR) _sendSTMn = true; | |
647 | decode.state = DECODE_STOPPED; | |
648 | if (status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) { | |
649 | _stream_disconnect = true; | |
650 | } | |
651 | } | |
652 | UNLOCK_D; | |
653 | ||
668 | UNLOCK_O; | |
669 | ||
654 | 670 | if (_stream_disconnect) stream_disconnect(); |
655 | 671 | |
656 | 672 | // send packets once locks released as packet sending can block |
19 | 19 | |
20 | 20 | // make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, DSD, LINKALL to influence build |
21 | 21 | |
22 | #define VERSION "v1.7.1" | |
22 | #define VERSION "v1.8-dev" | |
23 | 23 | |
24 | 24 | #if !defined(MODEL_NAME) |
25 | 25 | #define MODEL_NAME SqueezeLite |
561 | 561 | unsigned fade_secs; // set by slimproto |
562 | 562 | unsigned rate_delay; |
563 | 563 | bool delay_active; |
564 | u32_t stop_time; | |
565 | u32_t idle_to; | |
564 | 566 | #if DSD |
565 | 567 | bool next_dop; // set in decode thread |
566 | 568 | bool dop; |
569 | 571 | #endif |
570 | 572 | }; |
571 | 573 | |
572 | void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[]); | |
574 | void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle); | |
573 | 575 | void output_close_common(void); |
574 | 576 | void output_flush(void); |
575 | 577 | // _* called with mutex locked |
581 | 583 | void list_devices(void); |
582 | 584 | bool test_open(const char *device, unsigned rates[]); |
583 | 585 | void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], |
584 | unsigned rate_delay, unsigned rt_priority); | |
586 | unsigned rate_delay, unsigned rt_priority, unsigned idle); | |
585 | 587 | void output_close_alsa(void); |
586 | 588 | #endif |
587 | 589 | |
589 | 591 | #if PORTAUDIO |
590 | 592 | void list_devices(void); |
591 | 593 | bool test_open(const char *device, unsigned rates[]); |
592 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay); | |
594 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle); | |
593 | 595 | void output_close_pa(void); |
594 | 596 | void _pa_open(void); |
595 | 597 | #endif |