Merge pull request #343 from lagnat/open-on-demand

Add --open-on-demand: open the capture device only when someone is watching
This commit is contained in:
Maxim Devaev
2026-11-04 16:59:06 +02:00
committed by GitHub
4 changed files with 71 additions and 17 deletions

View File

@@ -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

View File

@@ -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 <sec> ──────────── 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 <sec> ────────────── Timeout for device querying. Default: %u.\n", cap->timeout);
SAY(" --device-error-delay <sec> ────────── Delay before trying to connect to the device again");
SAY(" after an error (timeout for example). Default: %u.\n", stream->error_delay);

View File

@@ -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;

View File

@@ -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;