Compare commits

..

18 Commits

Author SHA1 Message Date
Maxim Devaev
11fd828ab0 lint fix 2026-11-04 18:35:04 +02:00
Maxim Devaev
5e2542420f add timeout argument for --open-on-demand 2026-11-04 18:34:36 +02:00
Maxim Devaev
b8a224a5fa Merge pull request #343 from lagnat/open-on-demand
Add --open-on-demand: open the capture device only when someone is watching
2026-11-04 16:59:06 +02:00
Maxim Devaev
296639a2ac updated donate links 2026-10-10 07:50:30 +03:00
Stephen Green
7cde878f02 --open-on-demand: open the capture device only when someone is watching
With this option the device is not opened until at least one stream,
snapshot or sink client appears, and it's closed again 3 seconds after
the last one disconnects. While the device is closed, the clients get
the usual blank screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 12:22:54 -04:00
Maxim Devaev
0e64f1feda Bump version: v6.66 -> v6.67 2026-09-17 04:36:19 +03:00
Maxim Devaev
cfe83ae0bf governor scaling options 2026-09-17 03:39:16 +03:00
Maxim Devaev
864aac077e Bump version: v6.65 -> v6.66 2026-09-10 21:48:27 +03:00
Maxim Devaev
6952aff31d updated pkgbuild 2026-09-10 21:44:43 +03:00
Maxim Devaev
58e517b306 refactoring 2026-09-10 21:42:31 +03:00
sergey radionov
f6025267f6 janus, camera: aded non-IDR frames skip
after broken frame to avoid corrupted picture.
2026-09-10 21:41:44 +03:00
Maxim Devaev
3af6bfac0f typed list declaration 2026-08-30 00:16:47 +03:00
Maxim Devaev
db87e03ce7 Bump version: v6.64 -> v6.65 2026-08-23 12:58:19 +03:00
Maxim Devaev
27096fc20c janus: request camera key frame every 2 seconds 2026-08-23 12:56:12 +03:00
Maxim Devaev
84dc156b4a Bump version: v6.63 -> v6.64 2026-08-22 20:58:52 +03:00
Maxim Devaev
f627948a04 fixed conditions for sigabbrev_np 2026-08-22 20:56:14 +03:00
Maxim Devaev
72bfd68514 janus: no-camera-frames logging to prevent spam 2026-08-22 20:53:38 +03:00
sergey radionov
6273fc187a janus: license header updated 2026-08-22 20:53:12 +03:00
22 changed files with 162 additions and 49 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,4 +1,4 @@
# These are supported funding model platforms # These are supported funding model platforms
patreon: pikvm patreon: pikvm
custom: https://paypal.me/pikvm ko_fi: pikvm

View File

@@ -171,11 +171,17 @@ void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet) {
retry = true; retry = true;
// fall through // fall through
case US_RUR_DEPACKETIZATION_FAILED: case US_RUR_DEPACKETIZATION_FAILED:
client->last_key_req_ts = us_get_now_monotonic();
client->gw->send_pli(client->session); client->gw->send_pli(client->session);
break; break;
} }
} while (retry); } while (retry);
} }
const ldf now_ts = us_get_now_monotonic();
if (client->last_key_req_ts + 2 < now_ts) {
client->last_key_req_ts = now_ts;
client->gw->send_pli(client->session);
}
} else { } else {
if (header->type == US_RTP_OPUS_PAYLOAD && atomic_load(&client->transmit_aplay)) { if (header->type == US_RTP_OPUS_PAYLOAD && atomic_load(&client->transmit_aplay)) {
if (seq >= client->aplay_seq_next || (client->aplay_seq_next - seq) > 50) { if (seq >= client->aplay_seq_next || (client->aplay_seq_next - seq) > 50) {

View File

@@ -36,7 +36,7 @@
#include "rtpc.h" #include "rtpc.h"
typedef struct { typedef struct us_janus_client_sx {
janus_callbacks *gw; janus_callbacks *gw;
janus_plugin_session *session; janus_plugin_session *session;
@@ -64,7 +64,9 @@ typedef struct {
us_rtpc_s *rtpc; us_rtpc_s *rtpc;
us_ring_s *vplay_enc_ring; us_ring_s *vplay_enc_ring;
US_LIST_DECLARE; ldf last_key_req_ts;
US_LIST_DECLARE(struct us_janus_client_sx);
} us_janus_client_s; } us_janus_client_s;

View File

@@ -5,7 +5,8 @@
# This source file based on code of Janys-Gateway. # # This source file based on code of Janys-Gateway. #
# # # #
# Copyright (C) Lorenzo Miniero <lorenzo@meetecho.com> # # Copyright (C) Lorenzo Miniero <lorenzo@meetecho.com> #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> # # Copyright (C) 2026 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2026 Sergey Radionov <rsatom@gmail.com> #
# # # #
# This program is free software: you can redistribute it and/or modify # # This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by # # it under the terms of the GNU General Public License as published by #

View File

@@ -2,7 +2,8 @@
# # # #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. # # uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# # # #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> # # Copyright (C) 2026 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2026 Sergey Radionov <rsatom@gmail.com> #
# # # #
# This program is free software: you can redistribute it and/or modify # # This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by # # it under the terms of the GNU General Public License as published by #

View File

@@ -545,14 +545,14 @@ static void *_vplay_thread(void *arg) {
frame = _g_vplay_ring->items[in_ri]; frame = _g_vplay_ring->items[in_ri];
_g_vplay_ring->items[in_ri] = tmp_frame; _g_vplay_ring->items[in_ri] = tmp_frame;
us_ring_consumer_release(_g_vplay_ring, in_ri); us_ring_consumer_release(_g_vplay_ring, in_ri);
} else { /*} else { // FIXME: Leads to spam with "Streaming to the camera ..."
US_ONCE({ US_ONCE({
_LOCK_VPLAY; _LOCK_VPLAY;
if (_g_camera.active) { if (_g_camera.active) {
US_LOG_INFO("No frames have got from WebRTC"); US_LOG_INFO("No frames have got from WebRTC");
} }
_UNLOCK_VPLAY; _UNLOCK_VPLAY;
}); });*/
} }
} else { } else {
usleep(1000); // Don't iterate too frequently usleep(1000); // Don't iterate too frequently

View File

@@ -2,7 +2,8 @@
# # # #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. # # uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# # # #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> # # Copyright (C) 2026 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2026 Sergey Radionov <rsatom@gmail.com> #
# # # #
# This program is free software: you can redistribute it and/or modify # # This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by # # it under the terms of the GNU General Public License as published by #
@@ -42,7 +43,9 @@ enum {
}; };
enum { enum {
_NALU_SPS = 7 _NALU_IDR = 5,
_NALU_SPS = 7,
_NALU_PPS = 8,
}; };
@@ -91,6 +94,7 @@ us_rtpc_unwrap_result_e us_rtpc_unwrap(
if (rtpc->fu && (rtpc->fu_rts != rts || fragment_type != _FR_FU_A)) { if (rtpc->fu && (rtpc->fu_rts != rts || fragment_type != _FR_FU_A)) {
rtpc->fu = false; rtpc->fu = false;
if (!rtpc->fu_is_bad) { // Not reported yet if (!rtpc->fu_is_bad) { // Not reported yet
rtpc->skip_until_key = true;
return US_RUR_DEPACKETIZATION_FAILED_RETRY; return US_RUR_DEPACKETIZATION_FAILED_RETRY;
} }
} }
@@ -155,6 +159,7 @@ us_rtpc_unwrap_result_e us_rtpc_unwrap(
if (!first_fragment) { if (!first_fragment) {
// US_LOG_INFO("Ignoring FU without a first fragment: seq=%u, rts=%u", seq, rts); // US_LOG_INFO("Ignoring FU without a first fragment: seq=%u, rts=%u", seq, rts);
rtpc->fu_is_bad = true; rtpc->fu_is_bad = true;
rtpc->skip_until_key = true;
return US_RUR_DEPACKETIZATION_FAILED; return US_RUR_DEPACKETIZATION_FAILED;
} }
rtpc->fu_is_bad = false; rtpc->fu_is_bad = false;
@@ -180,6 +185,7 @@ us_rtpc_unwrap_result_e us_rtpc_unwrap(
} else { } else {
// US_LOG_ERROR("Something went wrong with FU processing: seq=%u, rts=%u", seq, rts); // US_LOG_ERROR("Something went wrong with FU processing: seq=%u, rts=%u", seq, rts);
rtpc->fu_is_bad = true; rtpc->fu_is_bad = true;
rtpc->skip_until_key = true;
return US_RUR_DEPACKETIZATION_FAILED; return US_RUR_DEPACKETIZATION_FAILED;
} }
@@ -281,6 +287,19 @@ static void _unwrapping_begin(us_rtpc_s *rtpc) {
static void _unwrapping_end(us_rtpc_s *rtpc) { static void _unwrapping_end(us_rtpc_s *rtpc) {
us_frame_s *const frame = rtpc->frame; us_frame_s *const frame = rtpc->frame;
US_A(frame->used > 4); // NAL unit start code
const u8 nalu_type = (frame->data[4] & 0x1F);
if (rtpc->skip_until_key && nalu_type != _NALU_SPS && nalu_type != _NALU_PPS) {
if (nalu_type != _NALU_IDR) {
frame->used = 0;
return;
} else {
rtpc->skip_until_key = false;
}
}
frame->format = V4L2_PIX_FMT_H264; frame->format = V4L2_PIX_FMT_H264;
frame->width = rtpc->frame_width; frame->width = rtpc->frame_width;
frame->height = rtpc->frame_height; frame->height = rtpc->frame_height;

View File

@@ -2,7 +2,8 @@
# # # #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. # # uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# # # #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> # # Copyright (C) 2026 Maxim Devaev <mdevaev@gmail.com> #
# Copyright (C) 2026 Sergey Radionov <rsatom@gmail.com> #
# # # #
# This program is free software: you can redistribute it and/or modify # # This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by # # it under the terms of the GNU General Public License as published by #
@@ -51,6 +52,8 @@ typedef struct { // *_rts == [R]TP [T]ime[S]tamp
u32 fu_rts; u32 fu_rts;
bool fu_is_bad; bool fu_is_bad;
bool skip_until_key;
us_frame_s *frame; us_frame_s *frame;
uint frame_width; uint frame_width;
uint frame_height; uint frame_height;

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer-dump. .\" Manpage for ustreamer-dump.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos .\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER-DUMP 1 "version 6.63" "January 2021" .TH USTREAMER-DUMP 1 "version 6.67" "January 2021"
.SH NAME .SH NAME
ustreamer-dump \- Dump uStreamer's memory sink to file ustreamer-dump \- Dump uStreamer's memory sink to file

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer. .\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos .\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 6.63" "November 2020" .TH USTREAMER 1 "version 6.67" "November 2020"
.SH NAME .SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network ustreamer \- stream MJPEG video from any V4L2 device to the network
@@ -112,6 +112,9 @@ It doesn't do anything. Still here for compatibility.
.BR \-l ", " \-\-slowdown .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. Slowdown capturing to 1 FPS or less when no stream or sink clients are connected. Useful to reduce CPU consumption. Default: disabled.
.TP .TP
.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 .BR \-\-device\-timeout\ \fIsec
Timeout for device querying. Default: 1. Timeout for device querying. Default: 1.
.TP .TP

View File

@@ -1,16 +1,15 @@
# Contributor: Maxim Devaev <mdevaev@gmail.com> # Maintainer: Maxim Devaev <mdevaev@gmail.com> [Software author]
# Author: Maxim Devaev <mdevaev@gmail.com>
pkgname=ustreamer pkgname=ustreamer
pkgver=6.63 pkgver=6.67
pkgrel=1 pkgrel=1
pkgdesc="Lightweight and fast MJPEG-HTTP streamer" pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
url="https://github.com/pikvm/ustreamer" url="https://github.com/pikvm/ustreamer"
license=(GPL) license=(GPL-3.0-or-later)
arch=(i686 x86_64 armv6h armv7h aarch64) arch=(i686 x86_64 armv6h armv7h aarch64)
depends=(libjpeg-turbo libevent libbsd libgpiod systemd) depends=(libjpeg-turbo libevent libbsd libgpiod systemd)
makedepends=(gcc make pkgconf systemd) makedepends=(git)
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}") source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP) md5sums=(SKIP)
@@ -19,7 +18,7 @@ _options="WITH_GPIO=1 WITH_SYSTEMD=1"
if [ -e /usr/bin/python3 ]; then if [ -e /usr/bin/python3 ]; then
_options="$_options WITH_PYTHON=1" _options="$_options WITH_PYTHON=1"
depends+=("python>=3.14" "python<3.15") depends+=("python>=3.14" "python<3.15")
makedepends+=(python-setuptools python-pip python-build python-wheel python-installer) makedepends+=(python-setuptools python-build python-wheel python-installer)
fi fi
if [ -e /usr/include/janus/plugins/plugin.h ];then if [ -e /usr/include/janus/plugins/plugin.h ];then
depends+=(janus-gateway alsa-lib opus) depends+=(janus-gateway alsa-lib opus)

View File

@@ -9,7 +9,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer PKG_NAME:=ustreamer
PKG_VERSION:=6.63 PKG_VERSION:=6.67
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com> PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

View File

@@ -34,7 +34,7 @@ def main() -> None:
flags = _find_flags() flags = _find_flags()
setup( setup(
name="ustreamer", name="ustreamer",
version="6.63", version="6.67",
description="uStreamer tools", description="uStreamer tools",
author="Maxim Devaev", author="Maxim Devaev",
author_email="mdevaev@gmail.com", author_email="mdevaev@gmail.com",

View File

@@ -26,7 +26,7 @@
#define US_VERSION_MAJOR 6 #define US_VERSION_MAJOR 6
#define US_VERSION_MINOR 63 #define US_VERSION_MINOR 67
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor #define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor) #define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)

View File

@@ -25,9 +25,9 @@
#include "tools.h" #include "tools.h"
#define US_LIST_DECLARE \ #define US_LIST_DECLARE(x_type) \
void *prev; \ x_type *prev; \
void *next; x_type *next;
#define US_LIST_ITERATE(x_first, x_item, ...) { \ #define US_LIST_ITERATE(x_first, x_item, ...) { \
for (__typeof__(x_first) x_item = x_first; x_item;) { \ for (__typeof__(x_first) x_item = x_first; x_item;) { \

View File

@@ -25,7 +25,7 @@
#include <string.h> #include <string.h>
#include <signal.h> #include <signal.h>
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32 #if defined(_GNU_SOURCE) && defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
# define HAS_SIGABBREV_NP # define HAS_SIGABBREV_NP
#endif #endif

View File

@@ -120,8 +120,8 @@ char *us_simplify_request_path(const char *str) {
#ifdef TEST_HTTP_PATH #ifdef TEST_HTTP_PATH
int test_simplify_request_path(const char *sample, const char *expected) { int test_simplify_request_path(const char *sample, const char *expected) {
char *result = us_simplify_request_path(sample); const char *result = us_simplify_request_path(sample);
int retval = -!!strcmp(result, expected); const int retval = -(!!strcmp(result, expected));
printf("Testing '%s' -> '%s' ... ", sample, expected); printf("Testing '%s' -> '%s' ... ", sample, expected);
if (retval == 0) { if (retval == 0) {

View File

@@ -36,7 +36,7 @@
#include "../stream.h" #include "../stream.h"
typedef struct { typedef struct us_stream_client_sx {
struct us_server_sx *server; struct us_server_sx *server;
struct evhttp_request *req; struct evhttp_request *req;
@@ -54,15 +54,15 @@ typedef struct {
us_fpsi_s *fpsi; us_fpsi_s *fpsi;
US_LIST_DECLARE; US_LIST_DECLARE(struct us_stream_client_sx);
} us_stream_client_s; } us_stream_client_s;
typedef struct { typedef struct us_snapshot_client_sx {
struct us_server_sx *server; struct us_server_sx *server;
struct evhttp_request *req; struct evhttp_request *req;
ldf req_ts; ldf req_ts;
US_LIST_DECLARE; US_LIST_DECLARE(struct us_snapshot_client_sx);
} us_snapshot_client_s; } us_snapshot_client_s;
typedef struct { typedef struct {

View File

@@ -89,7 +89,8 @@ enum _US_OPT_VALUES {
// Longs only // Longs only
_O_DEVICE_TIMEOUT = 10000, _O_OPEN_ON_DEMAND = 10000,
_O_DEVICE_TIMEOUT,
_O_DEVICE_ERROR_DELAY, _O_DEVICE_ERROR_DELAY,
_O_FORMAT_SWAP_RGB, _O_FORMAT_SWAP_RGB,
_O_M2M_DEVICE, _O_M2M_DEVICE,
@@ -156,6 +157,9 @@ enum _US_OPT_VALUES {
# endif # endif
_O_NOTIFY_PARENT, _O_NOTIFY_PARENT,
_O_CPU_SCALING_GOVERNOR_IDLE,
_O_CPU_SCALING_GOVERNOR_ACTIVE,
_O_LOG_LEVEL, _O_LOG_LEVEL,
_O_PERF, _O_PERF,
_O_VERBOSE, _O_VERBOSE,
@@ -187,6 +191,7 @@ static const struct option _LONG_OPTS[] = {
{"blank", required_argument, NULL, _O_BLANK}, {"blank", required_argument, NULL, _O_BLANK},
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK}, {"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
{"slowdown", no_argument, NULL, _O_SLOWDOWN}, {"slowdown", no_argument, NULL, _O_SLOWDOWN},
{"open-on-demand", required_argument, NULL, _O_OPEN_ON_DEMAND},
{"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT}, {"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT},
{"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY}, {"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY},
{"m2m-device", required_argument, NULL, _O_M2M_DEVICE}, {"m2m-device", required_argument, NULL, _O_M2M_DEVICE},
@@ -270,6 +275,9 @@ static const struct option _LONG_OPTS[] = {
# endif # endif
{"notify-parent", no_argument, NULL, _O_NOTIFY_PARENT}, {"notify-parent", no_argument, NULL, _O_NOTIFY_PARENT},
{"cpu-scaling-governor-idle", required_argument, NULL, _O_CPU_SCALING_GOVERNOR_IDLE},
{"cpu-scaling-governor-active", required_argument, NULL, _O_CPU_SCALING_GOVERNOR_ACTIVE},
{"log-level", required_argument, NULL, _O_LOG_LEVEL}, {"log-level", required_argument, NULL, _O_LOG_LEVEL},
{"perf", no_argument, NULL, _O_PERF}, {"perf", no_argument, NULL, _O_PERF},
{"verbose", no_argument, NULL, _O_VERBOSE}, {"verbose", no_argument, NULL, _O_VERBOSE},
@@ -447,6 +455,7 @@ int us_options_parse(
case _O_BLANK: break; // Deprecated case _O_BLANK: break; // Deprecated
case _O_LAST_AS_BLANK: break; // Deprecated case _O_LAST_AS_BLANK: break; // Deprecated
case _O_SLOWDOWN: OPT_SET(stream->slowdown, true); case _O_SLOWDOWN: OPT_SET(stream->slowdown, 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_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_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", stream->error_delay, 1, 60, 0);
case _O_M2M_DEVICE: OPT_SET(enc->m2m_path, optarg); case _O_M2M_DEVICE: OPT_SET(enc->m2m_path, optarg);
@@ -550,6 +559,9 @@ int us_options_parse(
# endif # endif
case _O_NOTIFY_PARENT: OPT_SET(stream->notify_parent, true); case _O_NOTIFY_PARENT: OPT_SET(stream->notify_parent, true);
case _O_CPU_SCALING_GOVERNOR_IDLE: OPT_SET(stream->governor_idle, optarg);
case _O_CPU_SCALING_GOVERNOR_ACTIVE: OPT_SET(stream->governor_active, optarg);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0); case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF); case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(us_g_log_level, US_LOG_LEVEL_VERBOSE); case _O_VERBOSE: OPT_SET(us_g_log_level, US_LOG_LEVEL_VERBOSE);
@@ -740,6 +752,10 @@ static void _help(
SAY(" -K|--last-as-blank <sec> ──────────── It doesn't do anything. Still here for compatibility.\n"); 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(" -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(" are connected. Useful to reduce CPU consumption. Default: disabled.\n");
SAY(" --open-on-demand <sec> ────────────── 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 <sec> ────────────── Timeout for device querying. Default: %u.\n", cap->timeout); 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(" --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); SAY(" after an error (timeout for example). Default: %u.\n", stream->error_delay);

View File

@@ -94,7 +94,8 @@ static void _stream_drm_ensure_no_signal(us_stream_s *stream);
static void _stream_expose_jpeg(us_stream_s *stream, const us_frame_s *frame); 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_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); static void _stream_encode_expose_h264(us_stream_s *stream, const us_frame_s *frame, bool force_key);
static void _stream_check_suicide(us_stream_s *stream); static void _stream_check_suicide(us_stream_s *stream, bool has_any_clients);
static void _stream_set_governor(us_stream_s *stream, bool active);
us_stream_s *us_stream_init(us_capture_s *cap, us_encoder_s *enc) { us_stream_s *us_stream_init(us_capture_s *cap, us_encoder_s *enc) {
@@ -204,8 +205,24 @@ void us_stream_loop(us_stream_s *stream) {
US_LOG_INFO("Capturing ..."); US_LOG_INFO("Capturing ...");
uint slowdown_count = 0; int has_any_clients_prev = -1;
ldf no_clients_since_ts = 0;
while (!atomic_load(&run->stop) && !atomic_load(&threads_stop)) { while (!atomic_load(&run->stop) && !atomic_load(&threads_stop)) {
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 + stream->open_on_demand < now_ts) {
US_LOG_INFO("No stream or sink clients found in last %u seconds, closing the device ...",
stream->open_on_demand);
goto close;
}
}
}
us_capture_hwbuf_s *hw; us_capture_hwbuf_s *hw;
switch (us_capture_hwbuf_grab(cap, &hw)) { switch (us_capture_hwbuf_grab(cap, &hw)) {
case 0 ... INT_MAX: break; // Grabbed buffer number case 0 ... INT_MAX: break; // Grabbed buffer number
@@ -233,17 +250,23 @@ void us_stream_loop(us_stream_s *stream) {
us_queue_put(releasers[hw->buf.index].q, hw, 0); // Plan to release us_queue_put(releasers[hw->buf.index].q, hw, 0); // Plan to release
// Мы не обновляем здесь состояние синков, потому что это происходит внутри обслуживающих их потоков // Мы не обновляем здесь состояние синков, потому что это происходит внутри обслуживающих их потоков
_stream_check_suicide(stream); const bool has_any_clients = _stream_has_any_clients_cached(stream);
if (stream->slowdown && !_stream_has_any_clients_cached(stream)) {
usleep(100 * 1000); if (has_any_clients_prev != has_any_clients) {
slowdown_count = (slowdown_count + 1) % 10; _stream_set_governor(stream, has_any_clients);
if (slowdown_count > 0) { has_any_clients_prev = has_any_clients;
continue;
} }
_stream_check_suicide(stream, has_any_clients);
if (stream->slowdown && !has_any_clients) {
usleep(100 * 1000);
} }
} }
close: close:
_stream_set_governor(stream, false);
atomic_store(&threads_stop, true); atomic_store(&threads_stop, true);
# define DELETE_WORKER(x_ctx) if (x_ctx != NULL) { \ # define DELETE_WORKER(x_ctx) if (x_ctx != NULL) { \
@@ -571,7 +594,18 @@ static int _stream_init_loop(us_stream_s *stream) {
UPDATE_SINK(stream->h264_sink); UPDATE_SINK(stream->h264_sink);
# undef UPDATE_SINK # undef UPDATE_SINK
_stream_check_suicide(stream); const bool has_any_clients = _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 ..."); });
blank_reason = (
"< WAITING FOR CLIENTS >\n \n"
" The camera is idle "
);
goto silent_error;
}
stream->cap->dma_export = ( stream->cap->dma_export = (
stream->enc->type == US_ENCODER_TYPE_M2M_VIDEO stream->enc->type == US_ENCODER_TYPE_M2M_VIDEO
@@ -591,7 +625,7 @@ static int _stream_init_loop(us_stream_s *stream) {
" - Bad config \n \n" " - Bad config \n \n"
" - Malfunction " " - Malfunction "
); );
goto silent_error; goto silent_capture_error;
case US_ERROR_NO_CABLE: case US_ERROR_NO_CABLE:
blank_reason = ( blank_reason = (
"< NO VIDEO SOURCE >\n \n" "< NO VIDEO SOURCE >\n \n"
@@ -599,7 +633,7 @@ static int _stream_init_loop(us_stream_s *stream) {
" - Source is off \n \n" " - Source is off \n \n"
" - Cable problems " " - Cable problems "
); );
goto silent_error; goto silent_capture_error;
case US_ERROR_NO_SIGNAL: case US_ERROR_NO_SIGNAL:
blank_reason = ( blank_reason = (
"< NO SIGNAL DETECTED >\n \n" "< NO SIGNAL DETECTED >\n \n"
@@ -607,7 +641,7 @@ static int _stream_init_loop(us_stream_s *stream) {
" - Video suspended \n \n" " - Video suspended \n \n"
" - Cable problems " " - Cable problems "
); );
goto silent_error; goto silent_capture_error;
case US_ERROR_NO_SYNC: case US_ERROR_NO_SYNC:
blank_reason = ( blank_reason = (
"< NO SYNC WITH SIGNAL >\n \n" "< NO SYNC WITH SIGNAL >\n \n"
@@ -615,7 +649,7 @@ static int _stream_init_loop(us_stream_s *stream) {
" - Source is crazy \n \n" " - Source is crazy \n \n"
" - Cable problems " " - Cable problems "
); );
goto silent_error; goto silent_capture_error;
case US_ERROR_NO_LANES: case US_ERROR_NO_LANES:
blank_reason = ( blank_reason = (
"< UNSUPPORTED SIGNAL TIMINGS >\n \n" "< UNSUPPORTED SIGNAL TIMINGS >\n \n"
@@ -631,10 +665,12 @@ static int _stream_init_loop(us_stream_s *stream) {
us_encoder_open(stream->enc, stream->cap); us_encoder_open(stream->enc, stream->cap);
return 0; return 0;
silent_error: silent_capture_error:
if (!stream->exit_on_device_error) { if (!stream->exit_on_device_error) {
US_ONCE({ US_LOG_INFO("Waiting for the capture device ..."); }); US_ONCE({ US_LOG_INFO("Waiting for the capture device ..."); });
} }
silent_error:
goto offline_and_retry; goto offline_and_retry;
verbose_error: verbose_error:
@@ -767,7 +803,7 @@ done:
us_fpsi_update(run->http->h264_fpsi, meta.online, &meta); us_fpsi_update(run->http->h264_fpsi, meta.online, &meta);
} }
static void _stream_check_suicide(us_stream_s *stream) { static void _stream_check_suicide(us_stream_s *stream, bool has_any_clients) {
if (stream->exit_on_no_clients == 0) { if (stream->exit_on_no_clients == 0) {
return; return;
} }
@@ -775,7 +811,7 @@ static void _stream_check_suicide(us_stream_s *stream) {
atomic_ullong *last_req_ts = &stream->run->http->last_req_ts; atomic_ullong *last_req_ts = &stream->run->http->last_req_ts;
const ldf now_ts = us_get_now_monotonic(); const ldf now_ts = us_get_now_monotonic();
if (_stream_has_any_clients_cached(stream)) { if (has_any_clients) {
atomic_store(last_req_ts, now_ts); atomic_store(last_req_ts, now_ts);
} else if (atomic_load(last_req_ts) + stream->exit_on_no_clients < now_ts) { } else if (atomic_load(last_req_ts) + stream->exit_on_no_clients < now_ts) {
US_LOG_INFO("No requests or HTTP/sink clients found in last %u seconds, exiting ...", US_LOG_INFO("No requests or HTTP/sink clients found in last %u seconds, exiting ...",
@@ -784,3 +820,26 @@ static void _stream_check_suicide(us_stream_s *stream) {
us_process_suicide(); us_process_suicide();
} }
} }
static void _stream_set_governor(us_stream_s *stream, bool active) {
if (!us_str_is_ok(stream->governor_idle) || !us_str_is_ok(stream->governor_active)) {
return;
}
const char *const path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor";
const int fd = open(path, O_RDWR);
if (fd < 0) {
US_LOG_PERROR("Can't open CPU governor file");
return;
}
const char *const gov = (active ? stream->governor_active : stream->governor_idle);
const sz len = strlen(gov);
if (write(fd, gov, len) != len) {
US_LOG_PERROR("Can't set CPU governor to %s", gov);
} else {
US_LOG_INFO("CPU governor changed to %s", gov);
}
close(fd);
}

View File

@@ -83,10 +83,14 @@ typedef struct {
uint desired_fps; uint desired_fps;
bool notify_parent; bool notify_parent;
bool slowdown; bool slowdown;
uint open_on_demand;
uint error_delay; uint error_delay;
bool exit_on_device_error; bool exit_on_device_error;
uint exit_on_no_clients; uint exit_on_no_clients;
char *governor_idle;
char *governor_active;
us_memsink_s *jpeg_sink; us_memsink_s *jpeg_sink;
us_memsink_s *raw_sink; us_memsink_s *raw_sink;

View File

@@ -47,7 +47,7 @@ typedef struct us_worker_sx {
struct us_workers_pool_sx *pool; struct us_workers_pool_sx *pool;
US_LIST_DECLARE; US_LIST_DECLARE(struct us_worker_sx);
} us_worker_s; } us_worker_s;
typedef void *(*us_workers_pool_job_init_f)(void *arg); typedef void *(*us_workers_pool_job_init_f)(void *arg);