diff --git a/man/ustreamer.1 b/man/ustreamer.1 index e08bd91..e270e04 100644 --- a/man/ustreamer.1 +++ b/man/ustreamer.1 @@ -112,6 +112,9 @@ 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. +.TP .BR \-\-device\-timeout\ \fIsec Timeout for device querying. Default: 1. .TP diff --git a/src/ustreamer/options.c b/src/ustreamer/options.c index 8a177ed..88c7c2c 100644 --- a/src/ustreamer/options.c +++ b/src/ustreamer/options.c @@ -89,7 +89,8 @@ enum _US_OPT_VALUES { // Longs only - _O_DEVICE_TIMEOUT = 10000, + _O_OPEN_ON_DEMAND = 10000, + _O_DEVICE_TIMEOUT, _O_DEVICE_ERROR_DELAY, _O_FORMAT_SWAP_RGB, _O_M2M_DEVICE, @@ -190,6 +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}, {"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}, @@ -453,6 +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_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); @@ -749,6 +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(" --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 43a6131..3c7fdbf 100644 --- a/src/ustreamer/stream.c +++ b/src/ustreamer/stream.c @@ -91,6 +91,7 @@ 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); @@ -206,7 +207,23 @@ void us_stream_loop(us_stream_s *stream) { US_LOG_INFO("Capturing ..."); 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_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) { + US_LOG_INFO("No stream or sink clients found in last %u seconds, closing the device ...", + US_ON_DEMAND_CLOSE_DELAY); + goto close; + } + } + } + us_capture_hwbuf_s *hw; switch (us_capture_hwbuf_grab(cap, &hw)) { case 0 ... INT_MAX: break; // Grabbed buffer number @@ -580,6 +597,23 @@ static int _stream_init_loop(us_stream_s *stream) { _stream_check_suicide(stream, _stream_has_any_clients_cached(stream)); + if (stream->open_on_demand && !_stream_has_any_clients_cached(stream)) { + 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; + } + stream->cap->dma_export = ( stream->enc->type == US_ENCODER_TYPE_M2M_VIDEO || stream->enc->type == US_ENCODER_TYPE_M2M_IMAGE @@ -659,22 +693,7 @@ static int _stream_init_loop(us_stream_s *stream) { } if (i % 10 == 0) { // Каждую секунду повторяем blank - 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 + _stream_publish_blank(stream, blank_reason); } usleep(100 * 1000); } @@ -720,6 +739,27 @@ 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 9020cf9..7112a23 100644 --- a/src/ustreamer/stream.h +++ b/src/ustreamer/stream.h @@ -44,6 +44,9 @@ #include "m2m.h" +#define US_ON_DEMAND_CLOSE_DELAY 3u + + typedef struct { # ifdef WITH_V4P atomic_bool drm_live; @@ -83,6 +86,7 @@ typedef struct { uint desired_fps; bool notify_parent; bool slowdown; + bool open_on_demand; uint error_delay; bool exit_on_device_error; uint exit_on_no_clients;