From 5e2542420f2e871f8f528210e7dfeb8c7fe1df87 Mon Sep 17 00:00:00 2001 From: Maxim Devaev Date: Wed, 4 Nov 2026 18:34:36 +0200 Subject: [PATCH] add timeout argument for --open-on-demand --- man/ustreamer.1 | 4 +- src/ustreamer/options.c | 12 +++--- src/ustreamer/stream.c | 81 ++++++++++++++++++----------------------- src/ustreamer/stream.h | 5 +-- 4 files changed, 44 insertions(+), 58 deletions(-) diff --git a/man/ustreamer.1 b/man/ustreamer.1 index e270e04..97d0e29 100644 --- a/man/ustreamer.1 +++ b/man/ustreamer.1 @@ -112,8 +112,8 @@ It doesn't do anything. Still here for compatibility. .BR \-l ", " \-\-slowdown Slowdown capturing to 1 FPS or less when no stream or sink clients are connected. Useful to reduce CPU consumption. Default: disabled. .TP -.BR \-\-open\-on\-demand -Don't open the capture device until at least one stream or sink client is connected, and close it again 3 seconds after the last one disconnects. Clients see a blank screen while the device is closed. Default: disabled. +.BR \-\-open\-on\-demand \fIsec +Don't open the capture device until at least one stream or sink client is connected, and close it again after a specified timeout since the last one disconnects. Clients see a blank screen while the device is closed. Default: disabled. .TP .BR \-\-device\-timeout\ \fIsec Timeout for device querying. Default: 1. diff --git a/src/ustreamer/options.c b/src/ustreamer/options.c index 88c7c2c..8fe87e0 100644 --- a/src/ustreamer/options.c +++ b/src/ustreamer/options.c @@ -191,7 +191,7 @@ static const struct option _LONG_OPTS[] = { {"blank", required_argument, NULL, _O_BLANK}, {"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK}, {"slowdown", no_argument, NULL, _O_SLOWDOWN}, - {"open-on-demand", no_argument, NULL, _O_OPEN_ON_DEMAND}, + {"open-on-demand", required_argument, NULL, _O_OPEN_ON_DEMAND}, {"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT}, {"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY}, {"m2m-device", required_argument, NULL, _O_M2M_DEVICE}, @@ -455,7 +455,7 @@ int us_options_parse( case _O_BLANK: break; // Deprecated case _O_LAST_AS_BLANK: break; // Deprecated case _O_SLOWDOWN: OPT_SET(stream->slowdown, true); - case _O_OPEN_ON_DEMAND: OPT_SET(stream->open_on_demand, true); + case _O_OPEN_ON_DEMAND: OPT_NUMBER("--open-on-demand", stream->open_on_demand, 1, 60, 0); case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", cap->timeout, 1, 60, 0); case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", stream->error_delay, 1, 60, 0); case _O_M2M_DEVICE: OPT_SET(enc->m2m_path, optarg); @@ -752,10 +752,10 @@ static void _help( SAY(" -K|--last-as-blank ──────────── It doesn't do anything. Still here for compatibility.\n"); SAY(" -l|--slowdown ─────────────────────── Slowdown capturing to 1 FPS or less when no stream or sink clients"); SAY(" are connected. Useful to reduce CPU consumption. Default: disabled.\n"); - SAY(" --open-on-demand ──────────────────── Don't open the capture device until at least one stream"); - SAY(" or sink client is connected, and close it again %u seconds", US_ON_DEMAND_CLOSE_DELAY); - SAY(" after the last one disconnects. Clients see a blank"); - SAY(" screen while the device is closed. Default: disabled.\n"); + SAY(" --open-on-demand ────────────── Don't open the capture device until at least one stream"); + SAY(" or sink client is connected, and close it again"); + SAY(" after a specified timeout since the last one disconnects."); + SAY(" Clients see a blank screen while the device is closed. Default: disabled.\n"); SAY(" --device-timeout ────────────── Timeout for device querying. Default: %u.\n", cap->timeout); SAY(" --device-error-delay ────────── Delay before trying to connect to the device again"); SAY(" after an error (timeout for example). Default: %u.\n", stream->error_delay); diff --git a/src/ustreamer/stream.c b/src/ustreamer/stream.c index 3c7fdbf..aa43faf 100644 --- a/src/ustreamer/stream.c +++ b/src/ustreamer/stream.c @@ -91,7 +91,6 @@ static void _stream_update_captured_fpsi(us_stream_s *stream, const us_frame_s * #ifdef WITH_V4P static void _stream_drm_ensure_no_signal(us_stream_s *stream); #endif -static void _stream_publish_blank(us_stream_s *stream, const char *reason); static void _stream_expose_jpeg(us_stream_s *stream, const us_frame_s *frame); static void _stream_expose_raw(us_stream_s *stream, const us_frame_s *frame); static void _stream_encode_expose_h264(us_stream_s *stream, const us_frame_s *frame, bool force_key); @@ -209,16 +208,16 @@ void us_stream_loop(us_stream_s *stream) { int has_any_clients_prev = -1; ldf no_clients_since_ts = 0; while (!atomic_load(&run->stop) && !atomic_load(&threads_stop)) { - if (stream->open_on_demand) { + if (stream->open_on_demand > 0) { if (_stream_has_any_clients_cached(stream)) { no_clients_since_ts = 0; } else { const ldf now_ts = us_get_now_monotonic(); if (no_clients_since_ts == 0) { no_clients_since_ts = now_ts; - } else if (no_clients_since_ts + US_ON_DEMAND_CLOSE_DELAY < now_ts) { + } else if (no_clients_since_ts + stream->open_on_demand < now_ts) { US_LOG_INFO("No stream or sink clients found in last %u seconds, closing the device ...", - US_ON_DEMAND_CLOSE_DELAY); + stream->open_on_demand); goto close; } } @@ -595,23 +594,17 @@ static int _stream_init_loop(us_stream_s *stream) { UPDATE_SINK(stream->h264_sink); # undef UPDATE_SINK - _stream_check_suicide(stream, _stream_has_any_clients_cached(stream)); + const bool has_any_clients = _stream_has_any_clients_cached(stream); - if (stream->open_on_demand && !_stream_has_any_clients_cached(stream)) { + _stream_check_suicide(stream, has_any_clients); + + if (stream->open_on_demand > 0 && !has_any_clients) { US_ONCE({ US_LOG_INFO("Waiting for the stream or sink clients to open the device ..."); }); - _stream_publish_blank(stream, ( - "< CAMERA IS IDLE >\n \n" - " The device will \n \n" - " be opened on the \n \n" - " first client " - )); - for (uint i = 0; i < 10; ++i) { - if (atomic_load(&run->stop) || _stream_has_any_clients_cached(stream)) { - break; - } - usleep(100 * 1000); - } - continue; + blank_reason = ( + "< WAITING FOR CLIENTS >\n \n" + " The camera is idle " + ); + goto silent_error; } stream->cap->dma_export = ( @@ -632,7 +625,7 @@ static int _stream_init_loop(us_stream_s *stream) { " - Bad config \n \n" " - Malfunction " ); - goto silent_error; + goto silent_capture_error; case US_ERROR_NO_CABLE: blank_reason = ( "< NO VIDEO SOURCE >\n \n" @@ -640,7 +633,7 @@ static int _stream_init_loop(us_stream_s *stream) { " - Source is off \n \n" " - Cable problems " ); - goto silent_error; + goto silent_capture_error; case US_ERROR_NO_SIGNAL: blank_reason = ( "< NO SIGNAL DETECTED >\n \n" @@ -648,7 +641,7 @@ static int _stream_init_loop(us_stream_s *stream) { " - Video suspended \n \n" " - Cable problems " ); - goto silent_error; + goto silent_capture_error; case US_ERROR_NO_SYNC: blank_reason = ( "< NO SYNC WITH SIGNAL >\n \n" @@ -656,7 +649,7 @@ static int _stream_init_loop(us_stream_s *stream) { " - Source is crazy \n \n" " - Cable problems " ); - goto silent_error; + goto silent_capture_error; case US_ERROR_NO_LANES: blank_reason = ( "< UNSUPPORTED SIGNAL TIMINGS >\n \n" @@ -672,10 +665,12 @@ static int _stream_init_loop(us_stream_s *stream) { us_encoder_open(stream->enc, stream->cap); return 0; - silent_error: + silent_capture_error: if (!stream->exit_on_device_error) { US_ONCE({ US_LOG_INFO("Waiting for the capture device ..."); }); } + + silent_error: goto offline_and_retry; verbose_error: @@ -693,7 +688,22 @@ static int _stream_init_loop(us_stream_s *stream) { } if (i % 10 == 0) { // Каждую секунду повторяем blank - _stream_publish_blank(stream, blank_reason); + uint width = stream->cap->run->width; + uint height = stream->cap->run->height; + if (width == 0 || height == 0) { + width = stream->cap->width; + height = stream->cap->height; + } + us_blank_draw(run->blank, blank_reason, width, height); + + _stream_update_captured_fpsi(stream, run->blank->raw, false); + _stream_expose_jpeg(stream, run->blank->jpeg); + _stream_expose_raw(stream, run->blank->raw); + _stream_encode_expose_h264(stream, run->blank->raw, true); + +# ifdef WITH_V4P + _stream_drm_ensure_no_signal(stream); +# endif } usleep(100 * 1000); } @@ -739,27 +749,6 @@ close: } #endif -static void _stream_publish_blank(us_stream_s *stream, const char *reason) { - us_stream_runtime_s *const run = stream->run; - - uint width = stream->cap->run->width; - uint height = stream->cap->run->height; - if (width == 0 || height == 0) { - width = stream->cap->width; - height = stream->cap->height; - } - us_blank_draw(run->blank, reason, width, height); - - _stream_update_captured_fpsi(stream, run->blank->raw, false); - _stream_expose_jpeg(stream, run->blank->jpeg); - _stream_expose_raw(stream, run->blank->raw); - _stream_encode_expose_h264(stream, run->blank->raw, true); - -# ifdef WITH_V4P - _stream_drm_ensure_no_signal(stream); -# endif -} - static void _stream_expose_jpeg(us_stream_s *stream, const us_frame_s *frame) { us_stream_runtime_s *const run = stream->run; diff --git a/src/ustreamer/stream.h b/src/ustreamer/stream.h index 7112a23..3203640 100644 --- a/src/ustreamer/stream.h +++ b/src/ustreamer/stream.h @@ -44,9 +44,6 @@ #include "m2m.h" -#define US_ON_DEMAND_CLOSE_DELAY 3u - - typedef struct { # ifdef WITH_V4P atomic_bool drm_live; @@ -86,7 +83,7 @@ typedef struct { uint desired_fps; bool notify_parent; bool slowdown; - bool open_on_demand; + uint open_on_demand; uint error_delay; bool exit_on_device_error; uint exit_on_no_clients;