Compare commits

...

54 Commits
v6.31 ... v6.46

Author SHA1 Message Date
Maxim Devaev
efbb2aa7ba Bump version: 6.45 → 6.46 2026-01-21 08:26:04 +02:00
Maxim Devaev
5692d81e46 lint fix 2026-01-21 08:21:44 +02:00
Maxim Devaev
4cec824b13 fixed fps limit for h264 2026-01-21 07:57:44 +02:00
Maxim Devaev
ac1989451c added help for --h264-boost 2026-01-21 07:07:41 +02:00
Maxim Devaev
e39d27309a Merge branch 'h264-boost' 2026-01-21 07:04:06 +02:00
Maxim Devaev
b983b6c355 new fps limiter 2026-01-21 07:03:58 +02:00
Maxim Devaev
5204f00812 h264 boost mode 2026-01-21 03:16:20 +02:00
Maxim Devaev
9eb39bbfc3 grab_begin_ts and grab_end_ts 2026-01-21 00:07:40 +02:00
Maxim Devaev
6adbb93e57 fpsi: optional meta arg in us_fpsi_get() 2026-01-20 11:52:19 +02:00
Maxim Devaev
4bd1465a10 janus: apply zero_playout_delay 2026-01-20 11:49:46 +02:00
Maxim Devaev
cf7f8947ef always capture maximum possible fps 2026-01-20 05:16:02 +02:00
Maxim Devaev
ec2e6c313b removed old fps regulation for jpeg encoders 2026-01-20 02:48:39 +02:00
Maxim Devaev
de2cfa36e1 Bump version: 6.44 → 6.45 2026-01-16 23:31:31 +02:00
Maxim Devaev
6c1a8f75a1 bumped python 2026-01-16 23:29:54 +02:00
Maxim Devaev
26ee5143ee Bump version: 6.43 → 6.44 2026-01-04 16:43:12 +02:00
Maxim Devaev
e2890e5851 janus: removed sync between video and audio 2026-01-04 16:03:42 +02:00
Maxim Devaev
e2b01e4d79 Bump version: 6.42 → 6.43 2026-01-03 19:43:43 +02:00
Maxim Devaev
903bc45bee lint fixes 2026-01-03 19:21:10 +02:00
Maxim Devaev
b2b1989c5b reduced preallocated us_frame_s size 2026-01-03 18:54:56 +02:00
Maxim Devaev
36b539c275 Bump version: 6.41 → 6.42 2025-11-11 00:00:35 +02:00
Maxim Devaev
38c6917644 janus: pkg-config 2025-11-10 23:58:42 +02:00
Maxim Devaev
05a5d3fed4 Bump version: 6.40 → 6.41 2025-10-23 16:28:07 +03:00
Maxim Devaev
0e4bf31325 janus: non-tc358743 devices for acap suppurted
An alternative implementation of pikvm/ustreamer#304.
Thanks for the idea.
2025-10-23 00:50:19 +03:00
Maxim Devaev
9a5cce3b92 janus: deprecated aplay/check option 2025-10-22 21:35:56 +03:00
Maxim Devaev
c4ac67acba janus: plug audio devices dynamically 2025-10-22 19:35:35 +03:00
Maxim Devaev
472673ea90 Bump version: 6.39 → 6.40 2025-07-28 21:32:04 +03:00
Maxim Devaev
f7ebe31c71 refactoring 2025-07-28 21:29:27 +03:00
Maxim Devaev
3a831817f4 pikvm/pikvm#1558: Discard JPEGs with invalid headers 2025-07-28 21:26:08 +03:00
Maxim Devaev
913cdac7a6 Bump version: 6.38 → 6.39 2025-07-03 04:19:51 +03:00
Maxim Devaev
777697dc1e improved logging on --exit-on-device-error 2025-07-03 04:17:48 +03:00
Maxim Devaev
5f437b9a35 Bump version: 6.37 → 6.38 2025-07-03 03:51:05 +03:00
Maxim Devaev
b089f896da pikvm/pikvm#312: --exit-on-device-error 2025-07-03 03:49:02 +03:00
Maxim Devaev
0e521ad0c6 Bump version: 6.36 → 6.37 2025-05-27 19:42:34 +03:00
Maxim Devaev
620a0ec847 Fixed #290: improved blank diagnostics 2025-05-27 19:30:07 +03:00
Maxim Devaev
7a1d4816ed frametext: more improvements 2025-05-26 22:34:19 +03:00
Maxim Devaev
aec8431024 verbose on-screen error messages 2025-05-26 20:06:06 +03:00
Maxim Devaev
5b18e29555 frametext: improved proportions 2025-05-26 20:04:35 +03:00
Maxim Devaev
2717248581 Bump version: 6.35 → 6.36 2025-03-27 04:38:28 +02:00
Maxim Devaev
afd305e87d v4p: fix for some DOS device 2025-03-27 04:36:35 +02:00
Maxim Devaev
e3d8132237 fixed --format-swap-rgb 2025-03-27 04:33:17 +02:00
Maxim Devaev
1f32e875c3 openwrt: +libatomic 2025-03-09 06:45:37 +02:00
Maxim Devaev
2e88fb9294 Bump version: 6.34 → 6.35 2025-03-08 20:16:11 +02:00
Maxim Devaev
d68f8e6d86 added missing formats 2025-03-08 20:14:17 +02:00
gudvinr
b380beba6d Add GREY pixelformat (#171)
Fixes #170

Monochrome cameras send only Y component of YUV image
2025-03-08 20:01:49 +02:00
Maxim Devaev
3a06a484ce Bump version: 6.33 → 6.34 2025-03-05 17:34:18 +02:00
Maxim Devaev
0307d3bdb6 Issue #287: Don't add -latomic on FreeBSD 2025-03-05 17:32:01 +02:00
Maxim Devaev
f2dd9c3c5a pikvm/ustreamer#306: Added ifdef for linux 2025-02-28 22:24:31 +02:00
Maxim Devaev
4e3f873f0d Added pkg-config to README 2025-02-27 22:21:23 +02:00
Maxim Devaev
029440cf82 Bump version: 6.32 → 6.33 2025-02-24 18:47:04 +02:00
Maxim Devaev
df74f5cf18 janus: added default ICE url 2025-02-24 18:41:40 +02:00
Maxim Devaev
97494c3531 janus: replaces STUN variables to ICE_URL 2025-02-24 18:21:46 +02:00
Maxim Devaev
71544880d1 janus: changed env prefix 2025-02-24 17:16:24 +02:00
Maxim Devaev
83127e58ff Bump version: 6.31 → 6.32 2025-02-24 05:19:22 +02:00
Maxim Devaev
604a8f7cb4 janus: STUN env 2025-02-24 05:17:32 +02:00
47 changed files with 514 additions and 204 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 6.31
current_version = 6.46
parse = (?P<major>\d+)\.(?P<minor>\d+)
serialize =
{major}.{minor}

View File

@@ -11,7 +11,7 @@
|----------|---------------|-------------------|
| Multithreaded JPEG encoding | ✔ | ✘ |
| Hardware image encoding<br>on Raspberry Pi | ✔ | ✘ |
| Behavior when the device<br>is disconnected while streaming | ✔ Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ✘ Stops the streaming <sup>1</sup> |
| Behavior when the device<br>is disconnected while streaming | ✔ Shows a black screen<br>with ```NO LIVE VIDEO``` on it<br>until reconnected | ✘ Stops the streaming <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ✔ | ☹ Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save the traffic | ✔ <sup>2</sup> | ✘ |
| Streaming via UNIX domain socket | ✔ | ✘ |
@@ -44,7 +44,7 @@ You need to download the µStreamer onto your system and build it from the sourc
* FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
### Preconditions
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
You'll need ```make```, ```gcc```, ```pkg-config```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspberry OS Bullseye: `sudo apt install libevent-dev libjpeg62-turbo libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.

View File

@@ -10,14 +10,19 @@ LDFLAGS ?=
# =====
_PLUGIN = libjanus_ustreamer.so
_CFLAGS = -fPIC -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(shell $(PKG_CONFIG) --cflags glib-2.0) $(CFLAGS)
_LDFLAGS = -shared -lm -pthread -lrt -ljansson -lopus -lasound -lspeexdsp $(shell $(PKG_CONFIG) --libs glib-2.0) $(LDFLAGS)
_CFLAGS = -fPIC -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(shell $(PKG_CONFIG) --cflags janus-gateway) $(CFLAGS)
_LDFLAGS = -shared -lm -pthread -lrt -ljansson -lopus -lasound -lspeexdsp $(shell $(PKG_CONFIG) --libs janus-gateway) $(LDFLAGS)
_SRCS = $(shell ls src/uslibs/*.c src/*.c)
_BUILD = build
# =====
ifneq ($(shell sh -c 'uname 2>/dev/null || echo Unknown'),FreeBSD)
override _LDFLAGS += -latomic
endif
ifneq ($(MK_WITH_PTHREAD_NP),)
override _CFLAGS += -DWITH_PTHREAD_NP
endif

View File

@@ -47,19 +47,6 @@ static void *_pcm_thread(void *v_acap);
static void *_encoder_thread(void *v_acap);
bool us_acap_probe(const char *name) {
snd_pcm_t *dev;
int err;
US_JLOG_INFO("acap", "Probing PCM capture ...");
if ((err = snd_pcm_open(&dev, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
US_JLOG_PERROR_ALSA(err, "acap", "Can't probe PCM capture");
return false;
}
snd_pcm_close(dev);
US_JLOG_INFO("acap", "PCM capture is available");
return true;
}
us_acap_s *us_acap_init(const char *name, uint pcm_hz) {
us_acap_s *acap;
US_CALLOC(acap, 1);

View File

@@ -53,8 +53,6 @@ typedef struct {
} us_acap_s;
bool us_acap_probe(const char *name);
us_acap_s *us_acap_init(const char *name, uint pcm_hz);
void us_acap_destroy(us_acap_s *acap);

View File

@@ -23,10 +23,78 @@
#include "au.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include "uslibs/tools.h"
bool us_au_probe(const char *name) {
// This function is very limited. It takes something like:
// hw:0,0 or hw:tc358743,0 or plughw:UAC2Gadget,0
// parses card name (0, tc358743, UAC2Gadget) and checks
// the existence of it in /proc/asound/.
// It's enough for our case.
if (name == NULL) {
return false;
}
if (strchr(name, '/') || strchr(name, '.')) {
return false;
}
const char *begin = strchr(name, ':');
if (begin == NULL) {
return false;
}
begin += 1;
if (*begin == '\0') {
return false;
}
const char *end = strchr(begin, ',');
if (end == NULL) {
return false;
}
if (end - begin < 1) {
return false;
}
char *card = us_strdup(begin);
card[end - begin] = '\0';
bool numeric = true;
for (uz index = 0; card[index] != '\0'; ++index) {
if (!isdigit(card[index])) {
numeric = false;
break;
}
}
char *path;
if (numeric) {
US_ASPRINTF(path, "/proc/asound/card%s", card);
} else {
US_ASPRINTF(path, "/proc/asound/%s", card);
}
bool ok = false;
struct stat st;
if (lstat(path, &st) == 0) {
if (numeric && S_ISDIR(st.st_mode)) {
ok = true;
} else if (!numeric && S_ISLNK(st.st_mode)) {
ok = true;
}
}
free(path);
free(card);
return ok;
}
us_au_pcm_s *us_au_pcm_init(void) {
us_au_pcm_s *pcm;
US_CALLOC(pcm, 1);

View File

@@ -51,6 +51,7 @@ typedef struct {
u64 pts;
} us_au_encoded_s;
bool us_au_probe(const char *name);
us_au_pcm_s *us_au_pcm_init(void);
void us_au_pcm_destroy(us_au_pcm_s *pcm);

View File

@@ -193,16 +193,16 @@ static void *_video_or_acap_thread(void *v_client, bool video) {
};
janus_plugin_rtp_extensions_reset(&packet.extensions);
/*if (rtp->zero_playout_delay) {
if (rtp.zero_playout_delay) {
// https://github.com/pikvm/pikvm/issues/784
packet.extensions.min_delay = 0;
packet.extensions.max_delay = 0;
} else {
// Эти дефолты используются в Chrome/Safari/Firefox.
// Работает всё одинаково, потому что у них общая кодовая база WebRTC.
packet.extensions.min_delay = 0;
// 10s - Chromium/WebRTC default
// 3s - Firefox default
packet.extensions.max_delay = 300; // == 3s, i.e. 10ms granularity
}*/
packet.extensions.max_delay = 1000; // == 10s, i.e. 10ms granularity
}
if (rtp.video) {
uint video_orient = atomic_load(&client->video_orient);

View File

@@ -24,11 +24,11 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <janus/config.h>
#include <janus/plugins/plugin.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "const.h"
@@ -36,6 +36,7 @@
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
static uint _get_uint(janus_config *jcfg, const char *section, const char *option, uint def);
// static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
@@ -61,20 +62,13 @@ us_config_s *us_config_init(const char *config_dir_path) {
goto error;
}
if ((config->acap_dev_name = _get_value(jcfg, "acap", "device")) != NULL) {
if ((config->tc358743_dev_path = _get_value(jcfg, "acap", "tc358743")) == NULL) {
US_JLOG_INFO("config", "Missing config value: acap.tc358743");
config->acap_hz = _get_uint(jcfg, "acap", "sampling_rate", 0);
config->tc358743_dev_path = _get_value(jcfg, "acap", "tc358743");
if (config->acap_hz == 0 && config->tc358743_dev_path == NULL) {
US_JLOG_ERROR("config", "Either acap.sampling_rate or acap.tc358743 required");
goto error;
}
if ((config->aplay_dev_name = _get_value(jcfg, "aplay", "device")) != NULL) {
char *path = _get_value(jcfg, "aplay", "check");
if (path != NULL) {
if (access(path, F_OK) != 0) {
US_JLOG_INFO("config", "No check file found, aplay will be disabled");
US_DELETE(config->aplay_dev_name, free);
}
US_DELETE(path, free);
}
}
config->aplay_dev_name = _get_value(jcfg, "aplay", "device");
}
goto ok;
@@ -105,6 +99,20 @@ static char *_get_value(janus_config *jcfg, const char *section, const char *opt
return us_strdup(option_obj->value);
}
static uint _get_uint(janus_config *jcfg, const char *section, const char *option, uint def) {
char *const tmp = _get_value(jcfg, section, option);
uint value = def;
if (tmp != NULL) {
errno = 0;
value = (uint)strtoul(tmp, NULL, 10);
if (errno != 0) {
value = def;
}
free(tmp);
}
return value;
}
/*static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def) {
char *const tmp = _get_value(jcfg, section, option);
bool value = def;

View File

@@ -23,10 +23,14 @@
#pragma once
#include "uslibs/types.h"
typedef struct {
char *video_sink_name;
char *acap_dev_name;
uint acap_hz;
char *tc358743_dev_path;
char *aplay_dev_name;

View File

@@ -59,6 +59,9 @@
#include "memsinkfd.h"
#include "config.h"
static const char *const default_ice_url = "stun:stun.l.google.com:19302";
static us_config_s *_g_config = NULL;
static const useconds_t _g_watchers_polling = 100000;
@@ -66,7 +69,7 @@ static us_janus_client_s *_g_clients = NULL;
static janus_callbacks *_g_gw = NULL;
static us_ring_s *_g_video_ring = NULL;
static us_rtpv_s *_g_rtpv = NULL;
static us_rtpa_s *_g_rtpa = NULL; // Also indicates "audio capture is available"
static us_rtpa_s *_g_rtpa = NULL;
static pthread_t _g_video_rtp_tid;
static atomic_bool _g_video_rtp_tid_created = false;
@@ -211,7 +214,15 @@ static void *_video_sink_thread(void *arg) {
return NULL;
}
static int _check_tc358743_acap(uint *hz) {
static int _get_acap_hz(uint *hz) {
if (_g_config->acap_hz != 0) {
*hz = _g_config->acap_hz;
return 0;
}
if (_g_config->tc358743_dev_path == NULL) {
US_JLOG_ERROR("acap", "No configured sampling rate");
return -1;
}
int fd;
if ((fd = open(_g_config->tc358743_dev_path, O_RDWR)) < 0) {
US_JLOG_PERROR("acap", "Can't open TC358743 V4L2 device");
@@ -233,7 +244,6 @@ static void *_acap_thread(void *arg) {
atomic_store(&_g_acap_tid_created, true);
assert(_g_config->acap_dev_name != NULL);
assert(_g_config->tc358743_dev_path != NULL);
assert(_g_rtpa != NULL);
int once = 0;
@@ -247,7 +257,11 @@ static void *_acap_thread(void *arg) {
uint hz = 0;
us_acap_s *acap = NULL;
if (_check_tc358743_acap(&hz) < 0) {
if (!us_au_probe(_g_config->acap_dev_name)) {
US_ONCE({ US_JLOG_ERROR("acap", "No PCM capture device"); });
goto close_acap;
}
if (_get_acap_hz(&hz) < 0) {
goto close_acap;
}
if (hz == 0) {
@@ -262,7 +276,7 @@ static void *_acap_thread(void *arg) {
once = 0;
while (!_STOP && _HAS_WATCHERS && _HAS_LISTENERS) {
if (_check_tc358743_acap(&hz) < 0 || acap->pcm_hz != hz) {
if (_get_acap_hz(&hz) < 0 || acap->pcm_hz != hz) {
goto close_acap;
}
uz size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
@@ -336,6 +350,11 @@ static void *_aplay_thread(void *arg) {
}
if (dev == NULL) {
if (!us_au_probe(_g_config->aplay_dev_name)) {
US_ONCE({ US_JLOG_ERROR("aplay", "No PCM playback device"); });
goto close_aplay;
}
int err = snd_pcm_open(&dev, _g_config->aplay_dev_name, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) {
US_ONCE({ US_JLOG_PERROR_ALSA(err, "aplay", "Can't open PCM playback"); });
@@ -421,7 +440,7 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
US_RING_INIT_WITH_ITEMS(_g_video_ring, 64, us_frame_init);
_g_rtpv = us_rtpv_init(_relay_rtp_clients);
if (_g_config->acap_dev_name != NULL && us_acap_probe(_g_config->acap_dev_name)) {
if (_g_config->acap_dev_name != NULL) {
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
US_THREAD_CREATE(_g_acap_tid, _acap_thread, NULL);
if (_g_config->aplay_dev_name != NULL) {
@@ -599,13 +618,13 @@ static struct janus_plugin_result *_plugin_handle_message(
{
json_t *const obj = json_object_get(params, "audio");
if (obj != NULL && json_is_boolean(obj)) {
with_acap = (_g_rtpa != NULL && json_boolean_value(obj));
with_acap = (us_au_probe(_g_config->acap_dev_name) && json_boolean_value(obj));
}
}
{
json_t *const obj = json_object_get(params, "mic");
if (obj != NULL && json_is_boolean(obj)) {
with_aplay = (_g_config->aplay_dev_name != NULL && with_acap && json_boolean_value(obj));
with_aplay = (us_au_probe(_g_config->aplay_dev_name) && json_boolean_value(obj));
}
}
{
@@ -669,10 +688,13 @@ static struct janus_plugin_result *_plugin_handle_message(
}
} else if (!strcmp(request_str, "features")) {
const char *const ice_url = getenv("JANUS_USTREAMER_WEB_ICE_URL");
const bool acap_avail = us_au_probe(_g_config->acap_dev_name);
json_t *const features = json_pack(
"{sbsb}",
"audio", (_g_rtpa != NULL),
"mic", (_g_rtpa != NULL && _g_config->aplay_dev_name != NULL)
"{s:b, s:b, s:{s:s?}}",
"audio", acap_avail,
"mic", (acap_avail && us_au_probe(_g_config->aplay_dev_name)),
"ice", "url", (ice_url != NULL ? ice_url : default_ice_url)
);
PUSH_STATUS("features", features, NULL);
json_decref(features);

View File

@@ -54,6 +54,8 @@ char *us_rtpa_make_sdp(us_rtpa_s *rtpa, bool mic) {
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=mid:a" RN
"a=msid:audio a" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=%s" RN,
pl, pl,

View File

@@ -69,6 +69,8 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=mid:v" RN
"a=msid:video v" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
"a=extmap:2 urn:3gpp:video-orientation" RN

View File

@@ -1,5 +1,5 @@
[mypy]
python_version = 3.9
python_version = 3.14
ignore_missing_imports = true
disallow_untyped_defs = true
strict_optional = true

View File

@@ -3,7 +3,7 @@ envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
skipsdist = true
[testenv]
basepython = python3.13
basepython = python3.14
changedir = /src
[testenv:cppcheck]

View File

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

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 6.31" "November 2020"
.TH USTREAMER 1 "version 6.46" "November 2020"
.SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network
@@ -52,7 +52,7 @@ Initial image resolution. Default: 640x480.
.TP
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
Image format.
Available: YUYV, YVYU, UYVY, YUV420, YVU420, RGB565, RGB24, JPEG; default: YUYV.
Available: YUYV, YVYU, UYVY, YUV420, YVU420, RGB565, RGB24, GREY, MJPEG, JPEG; default: YUYV.
.TP
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
Force TV standard.
@@ -253,6 +253,9 @@ Interval between keyframes. Default: 30.
.TP
.BR \-\-h264\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
.TP
.BR \-\-h264\-boost\-device
Increase encoder performance on PiKVM V4. Default: disabled.
.SS "RAW sink options"
.TP
@@ -276,6 +279,9 @@ Timeout for lock. Default: 1.
.BR \-\-exit\-on\-parent\-death
Exit the program if the parent process is dead. Required \fBWITH_PDEATHSIG\fR feature. Default: disabled.
.TP
.BR \-\-exit\-on\-device\-error
Exit on any device error instead of polling until success. Default: disabled.
.TP
.BR \-\-exit\-on\-no\-clients \fIsec
Exit the program if there have been no stream or sink clients or any HTTP requests in the last N seconds. Default: 0 (disabled).
.TP

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=6.31
pkgver=6.46
pkgrel=1
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"
@@ -18,7 +18,7 @@ md5sums=(SKIP)
_options="WITH_GPIO=1 WITH_SYSTEMD=1"
if [ -e /usr/bin/python3 ]; then
_options="$_options WITH_PYTHON=1"
depends+=("python>=3.13" "python<3.14")
depends+=("python>=3.14" "python<3.15")
makedepends+=(python-setuptools python-pip python-build python-wheel)
fi
if [ -e /usr/include/janus/plugins/plugin.h ];then

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=6.31
PKG_VERSION:=6.46
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
@@ -25,7 +25,7 @@ define Package/ustreamer
SECTION:=multimedia
CATEGORY:=Multimedia
TITLE:=uStreamer
DEPENDS:=+libpthread +libjpeg +libv4l +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
DEPENDS:=+libatomic +libpthread +libjpeg +libv4l +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
URL:=https://github.com/pikvm/ustreamer
endef

View File

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

View File

@@ -241,7 +241,8 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
SET_NUMBER(online, Long, Bool);
SET_NUMBER(key, Long, Bool);
SET_NUMBER(gop, Long, Long);
SET_NUMBER(grab_ts, Double, Float);
SET_NUMBER(grab_begin_ts, Double, Float);
SET_NUMBER(grab_end_ts, Double, Float);
SET_NUMBER(encode_begin_ts, Double, Float);
SET_NUMBER(encode_end_ts, Double, Float);
SET_VALUE("data", PyBytes_FromStringAndSize((const char*)self->frame->data, self->frame->used));

View File

@@ -14,9 +14,9 @@ _V4P = ustreamer-v4p.bin
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
_USTR_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic -levent -levent_pthreads
_DUMP_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic
_V4P_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic
_USTR_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -levent -levent_pthreads
_DUMP_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt
_V4P_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt
_USTR_SRCS = $(shell ls \
libs/*.c \
@@ -46,6 +46,12 @@ _OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
# =====
ifneq ($(shell sh -c 'uname 2>/dev/null || echo Unknown'),FreeBSD)
override _USTR_LDFLAGS += -latomic
override _DUMP_LDFLAGS += -latomic
override _V4P_LDFLAGS += -latomic
endif
ifneq ($(MK_WITH_PYTHON),)
override _CFLAGS += -DMK_WITH_PYTHON
endif

View File

@@ -53,11 +53,13 @@ void us_output_file_write(void *v_output, const us_frame_s *frame) {
fprintf(output->fp,
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
" \"format\": %u, \"stride\": %u, \"online\": %u, \"key\": %u, \"gop\": %u,"
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
" \"grab_begin_ts\": %.3Lf, \"grab_end_ts\": %.3Lf,"
" \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
" \"data\": \"%s\"}\n",
frame->used, frame->width, frame->height,
frame->format, frame->stride, frame->online, frame->key, frame->gop,
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
frame->grab_begin_ts, frame->grab_end_ts,
frame->encode_begin_ts, frame->encode_end_ts,
output->base64_data);
} else {
fwrite(frame->data, 1, frame->used, output->fp);

View File

@@ -240,16 +240,22 @@ static int _dump_sink(
const long double now = us_get_now_monotonic();
char fourcc_str[8];
US_LOG_VERBOSE("Frame: %s - %ux%u -- online=%d, key=%d, kr=%d, gop=%u, latency=%.3Lf, backlog=%.3Lf, size=%zu",
US_LOG_VERBOSE("%s %.3Lf - %s %ux%u - gop=%u, key=%u, kr=%u - GRAB=%.3Lf ~~%.3Lf~~ ENC=%.3Lf ~~> LAT=%.3Lf - size=%zu",
(frame->online ? " ON" : "OFF"),
(last_ts ? now - last_ts : 0),
us_fourcc_to_string(frame->format, fourcc_str, 8),
frame->width, frame->height,
frame->online, frame->key, key_requested, frame->gop,
now - frame->grab_ts, (last_ts ? now - last_ts : 0),
frame->width,
frame->height,
frame->gop,
frame->key,
key_requested,
frame->grab_end_ts - frame->grab_begin_ts,
frame->encode_begin_ts - frame->grab_end_ts,
frame->encode_end_ts - frame->encode_begin_ts,
now - frame->grab_begin_ts,
frame->used);
last_ts = now;
US_LOG_DEBUG(" stride=%u, grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
frame->stride, frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
last_ts = now;
us_fpsi_update(fpsi, true, NULL);

View File

@@ -48,6 +48,7 @@
#include "threading.h"
#include "frame.h"
#include "xioctl.h"
#include "tc358743.h"
static const struct {
@@ -69,6 +70,7 @@ static const struct {
{"UYVY", V4L2_PIX_FMT_UYVY},
{"YUV420", V4L2_PIX_FMT_YUV420},
{"YVU420", V4L2_PIX_FMT_YVU420},
{"GREY", V4L2_PIX_FMT_GREY},
{"RGB565", V4L2_PIX_FMT_RGB565},
{"RGB24", V4L2_PIX_FMT_RGB24},
{"BGR24", V4L2_PIX_FMT_BGR24},
@@ -192,12 +194,18 @@ int us_capture_open(us_capture_s *cap) {
_LOG_DEBUG("Capture device fd=%d opened", run->fd);
if (cap->dv_timings && cap->persistent) {
struct v4l2_control ctl = {.id = V4L2_CID_DV_RX_POWER_PRESENT};
if (!us_xioctl(run->fd, VIDIOC_G_CTRL, &ctl)) {
if (!ctl.value) {
goto error_no_cable;
}
}
_LOG_DEBUG("Probing DV-timings or QuerySTD ...");
if (_capture_open_dv_timings(cap, false) < 0) {
US_ONCE_FOR(run->open_error_once, __LINE__, {
_LOG_ERROR("No signal from source");
});
goto error_no_signal;
switch (_capture_open_dv_timings(cap, false)) {
case 0: break;
case US_ERROR_NO_SIGNAL: goto error_no_signal;
case US_ERROR_NO_SYNC: goto error_no_sync;
default: goto error;
}
}
@@ -215,6 +223,15 @@ int us_capture_open(us_capture_s *cap) {
if (_capture_open_format(cap, true) < 0) {
goto error;
}
if (cap->dv_timings && cap->persistent) {
struct v4l2_control ctl = {.id = TC358743_CID_LANES_ENOUGH};
if (!us_xioctl(run->fd, VIDIOC_G_CTRL, &ctl)) {
if (!ctl.value) {
_LOG_ERROR("Not enough lanes, hardware can't handle this signal");
goto error_no_lanes;
}
}
}
_capture_open_hw_fps(cap);
_capture_open_jpeg_quality(cap);
if (_capture_open_io_method(cap) < 0) {
@@ -247,9 +264,23 @@ error_no_device:
us_capture_close(cap);
return US_ERROR_NO_DEVICE;
error_no_signal:
error_no_cable:
us_capture_close(cap);
return US_ERROR_NO_DATA;
return US_ERROR_NO_CABLE;
error_no_signal:
US_ONCE_FOR(run->open_error_once, __LINE__, { _LOG_ERROR("No signal from source"); });
us_capture_close(cap);
return US_ERROR_NO_SIGNAL;
error_no_sync:
US_ONCE_FOR(run->open_error_once, __LINE__, { _LOG_ERROR("No sync on signal"); });
us_capture_close(cap);
return US_ERROR_NO_SYNC;
error_no_lanes:
us_capture_close(cap);
return US_ERROR_NO_LANES;
error:
run->open_error_once = 0;
@@ -416,10 +447,16 @@ int us_capture_hwbuf_grab(us_capture_s *cap, us_capture_hwbuf_s **hw) {
(*hw)->raw.stride = run->stride;
(*hw)->raw.online = true;
_v4l2_buffer_copy(&buf, &(*hw)->buf);
(*hw)->raw.grab_ts = (ldf)((buf.timestamp.tv_sec * (u64)1000) + (buf.timestamp.tv_usec / 1000)) / 1000;
(*hw)->raw.grab_begin_ts = (ldf)((buf.timestamp.tv_sec * (u64)1000) + (buf.timestamp.tv_usec / 1000)) / 1000;
(*hw)->raw.grab_end_ts = us_get_now_monotonic();
_LOG_DEBUG("Grabbed HW buffer=%u: bytesused=%u, grab_begin_ts=%.3Lf, grab_end_ts=%.3Lf, latency=%.3Lf, skipped=%u",
buf.index, buf.bytesused,
(*hw)->raw.grab_begin_ts,
(*hw)->raw.grab_end_ts,
(*hw)->raw.grab_end_ts - (*hw)->raw.grab_begin_ts,
skipped);
_LOG_DEBUG("Grabbed HW buffer=%u: bytesused=%u, grab_ts=%.3Lf, latency=%.3Lf, skipped=%u",
buf.index, buf.bytesused, (*hw)->raw.grab_ts, us_get_now_monotonic() - (*hw)->raw.grab_ts, skipped);
return buf.index;
}
@@ -538,19 +575,28 @@ bool _capture_is_buffer_valid(const us_capture_s *cap, const struct v4l2_buffer
if (us_is_jpeg(cap->run->format)) {
if (buf->bytesused < 125) {
// https://stackoverflow.com/questions/2253404/what-is-the-smallest-valid-jpeg-file-size-in-bytes
_LOG_DEBUG("Discarding invalid frame, too small to be a valid JPEG: bytesused=%u", buf->bytesused);
_LOG_DEBUG("Discarding invalid frame, too small to be a valid JPEG: bytesused=%u",
buf->bytesused);
return false;
}
const u8 *const end_ptr = data + buf->bytesused;
const u8 *const eoi_ptr = end_ptr - 2;
const u16 eoi_marker = (((u16)(eoi_ptr[0]) << 8) | eoi_ptr[1]);
if (eoi_marker != 0xFFD9 && eoi_marker != 0xD900 && eoi_marker != 0x0000) {
const u16 begin_marker = (((u16)(data[0]) << 8) | data[1]);
if (begin_marker != 0xFFD8) {
_LOG_DEBUG("Discarding JPEG frame with invalid header: begin_marker=0x%04x, bytesused=%u",
begin_marker, buf->bytesused);
return false;
}
const u8 *const end_ptr = data + buf->bytesused - 2;
const u16 end_marker = (((u16)(end_ptr[0]) << 8) | end_ptr[1]);
if (end_marker != 0xFFD9 && end_marker != 0xD900 && end_marker != 0x0000) {
if (!cap->allow_truncated_frames) {
_LOG_DEBUG("Discarding truncated JPEG frame: eoi_marker=0x%04x, bytesused=%u", eoi_marker, buf->bytesused);
_LOG_DEBUG("Discarding truncated JPEG frame: end_marker=0x%04x, bytesused=%u",
end_marker, buf->bytesused);
return false;
}
_LOG_DEBUG("Got truncated JPEG frame: eoi_marker=0x%04x, bytesused=%u", eoi_marker, buf->bytesused);
_LOG_DEBUG("Got truncated JPEG frame: end_marker=0x%04x, bytesused=%u",
end_marker, buf->bytesused);
}
}
@@ -619,6 +665,10 @@ static int _capture_open_dv_timings(us_capture_s *cap, bool apply) {
// TC358743 errors here (see in the kernel: drivers/media/i2c/tc358743.c):
// - ENOLINK: No valid signal (SYS_STATUS & MASK_S_TMDS)
// - ENOLCK: No sync on signal (SYS_STATUS & MASK_S_SYNC)
switch (errno) {
case ENOLINK: return US_ERROR_NO_SIGNAL;
case ENOLCK: return US_ERROR_NO_SYNC;
}
dv_errno = errno;
goto querystd;
} else if (!apply) {
@@ -781,10 +831,8 @@ static int _capture_open_format(us_capture_s *cap, bool first) {
return 0;
}
static void _capture_open_hw_fps(us_capture_s *cap) {
us_capture_runtime_s *const run = cap->run;
run->hw_fps = 0;
static void _capture_open_hw_fps(us_capture_s *cap) { // cppcheck-suppress constParameterPointer
const us_capture_runtime_s *const run = cap->run;
struct v4l2_streamparm setfps = {.type = run->capture_type};
_LOG_DEBUG("Querying HW FPS ...");
@@ -807,7 +855,7 @@ static void _capture_open_hw_fps(us_capture_s *cap) {
US_MEMSET_ZERO(setfps);
setfps.type = run->capture_type;
SETFPS_TPF(numerator) = 1;
SETFPS_TPF(denominator) = (cap->desired_fps == 0 ? 255 : cap->desired_fps);
SETFPS_TPF(denominator) = -1; // Request maximum possible FPS
if (us_xioctl(run->fd, VIDIOC_S_PARM, &setfps) < 0) {
_LOG_PERROR("Can't set HW FPS");
@@ -824,12 +872,7 @@ static void _capture_open_hw_fps(us_capture_s *cap) {
return;
}
run->hw_fps = SETFPS_TPF(denominator);
if (cap->desired_fps != run->hw_fps) {
_LOG_INFO("Using HW FPS: %u -> %u (coerced)", cap->desired_fps, run->hw_fps);
} else {
_LOG_INFO("Using HW FPS: %u", run->hw_fps);
}
_LOG_INFO("Using HW FPS: %u/%u", SETFPS_TPF(numerator), SETFPS_TPF(denominator));
# undef SETFPS_TPF
}

View File

@@ -39,7 +39,7 @@
#define US_VIDEO_MAX_FPS ((uint)120)
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
#define US_FORMATS_STR "YUYV, YVYU, UYVY, YUV420, YVU420, RGB565, RGB24, BGR24, MJPEG, JPEG"
#define US_FORMATS_STR "YUYV, YVYU, UYVY, YUV420, YVU420, RGB565, RGB24, BGR24, GREY, MJPEG, JPEG"
#define US_IO_METHODS_STR "MMAP, USERPTR"
@@ -58,7 +58,6 @@ typedef struct {
uint format;
uint stride;
float hz;
uint hw_fps;
uint jpeg_quality;
uz raw_size;
uint n_bufs;
@@ -113,7 +112,6 @@ typedef struct {
uint n_bufs;
bool dma_export;
bool dma_required;
uint desired_fps;
uz min_frame_size;
bool allow_truncated_frames;
bool persistent;

View File

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

View File

@@ -28,7 +28,9 @@
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#ifdef __linux__
# include <sys/sysmacros.h>
#endif
#include <linux/videodev2.h>
@@ -376,7 +378,7 @@ int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *ca
DRAW_MSG("=== PiKVM ===\n \n< UNSUPPORTED CAPTURE FORMAT >");
break;
case US_DRM_STUB_NO_SIGNAL:
DRAW_MSG("=== PiKVM ===\n \n< NO SIGNAL >");
DRAW_MSG("=== PiKVM ===\n \n< NO LIVE VIDEO >");
break;
case US_DRM_STUB_BUSY:
DRAW_MSG("=== PiKVM ===\n \n< ONLINE IS ACTIVE >");
@@ -664,6 +666,15 @@ static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint
continue; // Discard interlaced
}
const float mode_hz = _get_refresh_rate(mode);
if (width == 640 && height == 416 && mode->hdisplay == 640 && mode->vdisplay == 480) {
// A special case for some ancient DOS device with VGA converter.
// @CapnKirk in Discord
if (hz > 0 && mode_hz < hz) {
best = mode;
best->vdisplay = 416;
break;
}
}
if (mode->hdisplay == width && mode->vdisplay == height) {
best = mode; // Any mode with exact resolution
if (hz > 0 && mode_hz == hz) {

View File

@@ -24,4 +24,8 @@
#define US_ERROR_COMMON -1
#define US_ERROR_NO_DEVICE -2
#define US_ERROR_NO_DATA -3
#define US_ERROR_NO_CABLE -3
#define US_ERROR_NO_SIGNAL -4
#define US_ERROR_NO_SYNC -5
#define US_ERROR_NO_LANES -6
#define US_ERROR_NO_DATA -7

View File

@@ -85,8 +85,6 @@ void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta) {
if (meta != NULL) {
assert(fpsi->with_meta);
} else {
assert(!fpsi->with_meta);
}
// Между чтением инфы и времени может быть гонка,
@@ -97,8 +95,7 @@ uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta) {
const ull state = atomic_load(&fpsi->state); // Потом инфа
uint current = state & 0xFFFF;
if (fpsi->with_meta) {
assert(meta != NULL);
if (fpsi->with_meta && meta != NULL) {
meta->width = (state >> 16) & 0xFFFF;
meta->height = (state >> 32) & 0xFFFF;
meta->online = (state >> 48) & 1;

View File

@@ -36,7 +36,7 @@
us_frame_s *us_frame_init(void) {
us_frame_s *frame;
US_CALLOC(frame, 1);
us_frame_realloc_data(frame, 512 * 1024);
us_frame_realloc_data(frame, 32 * 1024);
frame->dma_fd = -1;
return frame;
}
@@ -84,6 +84,7 @@ uint us_frame_get_padding(const us_frame_s *frame) {
switch (frame->format) {
case V4L2_PIX_FMT_YUV420:
case V4L2_PIX_FMT_YVU420:
case V4L2_PIX_FMT_GREY:
bytes_per_pixel = 1;
break;

View File

@@ -38,7 +38,8 @@
bool key; \
uint gop; \
\
ldf grab_ts; \
ldf grab_begin_ts; \
ldf grab_end_ts; \
ldf encode_begin_ts; \
ldf encode_end_ts;
@@ -62,7 +63,8 @@ typedef struct {
(x_dest)->key = (x_src)->key; \
(x_dest)->gop = (x_src)->gop; \
\
(x_dest)->grab_ts = (x_src)->grab_ts; \
(x_dest)->grab_begin_ts = (x_src)->grab_begin_ts; \
(x_dest)->grab_end_ts = (x_src)->grab_end_ts; \
(x_dest)->encode_begin_ts = (x_src)->encode_begin_ts; \
(x_dest)->encode_end_ts = (x_src)->encode_end_ts; \
}

View File

@@ -121,9 +121,16 @@ void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint he
if (block_width == 0 || block_height == 0) {
goto empty;
}
uint scale_x = frame->width / block_width / 2;
uint scale_y = frame->height / block_height / 3;
if (scale_x < scale_y / 1.5) {
// Ширина текста должна быть от 75%, до половины экрана, в зависимости от длины
const float div_x = US_MAX(US_MIN((100 / block_width * 2), 2.0), 1.5);
// Высоту тоже отрегулировать как-нибудь
const float div_y = US_MAX(US_MIN((70 / block_height * 2), 2.0), 1.5);
uint scale_x = frame->width / block_width / div_x;
uint scale_y = frame->height / block_height / div_y;
if (scale_x < scale_y / 1.5) { // Keep proportions
scale_y = scale_x * 1.5;
} else if (scale_y < scale_x * 1.5) {
scale_x = scale_y / 1.5;

View File

@@ -33,17 +33,6 @@
#include "xioctl.h"
#ifndef V4L2_CID_USER_TC358743_BASE
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
#endif
#ifndef TC358743_CID_AUDIO_PRESENT
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
#endif
#ifndef TC358743_CID_AUDIO_SAMPLING_RATE
# define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
#endif
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz) {
*audio_hz = 0;

View File

@@ -22,7 +22,26 @@
#pragma once
#include <linux/v4l2-controls.h>
#include "types.h"
#ifndef V4L2_CID_USER_TC358743_BASE
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
#endif
#ifndef TC358743_CID_AUDIO_SAMPLING_RATE
# define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
#endif
#ifndef TC358743_CID_AUDIO_PRESENT
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
#endif
#ifndef TC358743_CID_LANES_ENOUGH
# define TC358743_CID_LANES_ENOUGH (V4L2_CID_USER_TC358743_BASE + 2)
#endif
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz);

View File

@@ -36,7 +36,7 @@ us_blank_s *us_blank_init(void) {
blank->ft = us_frametext_init();
blank->raw = blank->ft->frame;
blank->jpeg = us_frame_init();
us_blank_draw(blank, "< NO SIGNAL >", 640, 480);
us_blank_draw(blank, "< NO LIVE VIDEO >", 640, 480);
return blank;
}

View File

@@ -131,7 +131,7 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
} else {
US_LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
type = US_ENCODER_TYPE_CPU;
quality = cap->jpeg_quality;
quality = cap->jpeg_quality; // cppcheck-suppress redundantAssignment
}
} else if (type == US_ENCODER_TYPE_M2M_VIDEO || type == US_ENCODER_TYPE_M2M_IMAGE) {
@@ -162,14 +162,8 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
run->quality = quality;
US_MUTEX_UNLOCK(run->mutex);
const ldf desired_interval = (
cap->desired_fps > 0 && (cap->desired_fps < cap->run->hw_fps || cap->run->hw_fps == 0)
? (ldf)1 / cap->desired_fps
: 0
);
enc->run->pool = us_workers_pool_init(
"JPEG", "jw", n_workers, desired_interval,
"JPEG", "jw", n_workers,
_worker_job_init, (void*)enc,
_worker_job_destroy,
_worker_run_job);

View File

@@ -39,6 +39,7 @@ static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_grey(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
#ifndef JCS_EXTENSIONS
@@ -75,6 +76,10 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, uint quali
case V4L2_PIX_FMT_YVU420:
jpeg.in_color_space = JCS_YCbCr;
break;
case V4L2_PIX_FMT_GREY:
jpeg.input_components = 1;
jpeg.in_color_space = JCS_GRAYSCALE;
break;
# ifdef JCS_EXTENSIONS
case V4L2_PIX_FMT_BGR24:
jpeg.in_color_space = JCS_EXT_BGR;
@@ -102,6 +107,10 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, uint quali
case V4L2_PIX_FMT_YVU420:
_jpeg_write_scanlines_yuv_planar(&jpeg, src);
break;
case V4L2_PIX_FMT_GREY:
_jpeg_write_scanlines_grey(&jpeg, src);
break;
case V4L2_PIX_FMT_RGB565:
_jpeg_write_scanlines_rgb565(&jpeg, src);
@@ -249,6 +258,30 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
free(line_buf);
}
static void _jpeg_write_scanlines_grey(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
u8 *line_buf;
US_CALLOC(line_buf, frame->width);
const uint padding = us_frame_get_padding(frame);
const u8 *data = frame->data;
while (jpeg->next_scanline < frame->height) {
u8 *ptr = line_buf;
for (uint x = 0; x < frame->width; ++x) {
ptr[0] = data[x];
ptr += 1;
}
data += frame->width + padding;
JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1);
}
free(line_buf);
}
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
u8 *line_buf;
US_CALLOC(line_buf, frame->width * 3);

View File

@@ -198,16 +198,8 @@ int us_server_listen(us_server_s *server) {
us_frame_copy(stream->run->blank->jpeg, ex->frame);
{
struct timeval interval = {0};
if (stream->cap->desired_fps > 0) {
interval.tv_usec = 1000000 / (stream->cap->desired_fps * 2);
} else {
interval.tv_usec = 16000; // ~60fps
}
assert((run->refresher = event_new(run->base, -1, EV_PERSIST, _http_refresher, server)) != NULL);
assert(!event_add(run->refresher, &interval));
}
assert((run->refresher = event_new(run->base, -1, 0, _http_refresher, server)) != NULL);
stream->run->http->jpeg_refresher = run->refresher;
evhttp_set_timeout(run->http, server->timeout);
@@ -519,7 +511,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
(server->fake_width ? server->fake_width : captured_meta.width),
(server->fake_height ? server->fake_height : captured_meta.height),
us_bool_to_string(captured_meta.online),
stream->cap->desired_fps,
stream->desired_fps,
captured_fps,
us_fpsi_get(ex->queued_fpsi, NULL),
run->stream_clients_count
@@ -730,7 +722,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"X-UStreamer-Width: %u" RN
"X-UStreamer-Height: %u" RN
"X-UStreamer-Client-FPS: %u" RN
"X-UStreamer-Grab-Time: %.06Lf" RN
"X-UStreamer-Grab-Begin-Time: %.06Lf" RN
"X-UStreamer-Grab-End-Time: %.06Lf" RN
"X-UStreamer-Encode-Begin-Time: %.06Lf" RN
"X-UStreamer-Encode-End-Time: %.06Lf" RN
"X-UStreamer-Expose-Begin-Time: %.06Lf" RN
@@ -744,14 +737,15 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
ex->frame->width,
ex->frame->height,
us_fpsi_get(client->fpsi, NULL),
ex->frame->grab_ts,
ex->frame->grab_begin_ts,
ex->frame->grab_end_ts,
ex->frame->encode_begin_ts,
ex->frame->encode_end_ts,
ex->expose_begin_ts,
ex->expose_cmp_ts,
ex->expose_end_ts,
now_ts,
now_ts - ex->frame->grab_ts
now_ts - ex->frame->grab_begin_ts
);
}
}
@@ -880,7 +874,7 @@ static void _http_send_snapshot(us_server_s *server) {
if (!captured_meta.online) {
if (blank == NULL) {
blank = us_blank_init();
us_blank_draw(blank, "< NO SIGNAL >", captured_meta.width, captured_meta.height);
us_blank_draw(blank, "< NO LIVE VIDEO >", captured_meta.width, captured_meta.height);
}
frame = blank->jpeg;
}
@@ -900,7 +894,8 @@ static void _http_send_snapshot(us_server_s *server) {
_A_ADD_HEADER(request, "X-UStreamer-Online", us_bool_to_string(frame->online));
ADD_UNSIGNED_HEADER("X-UStreamer-Width", frame->width);
ADD_UNSIGNED_HEADER("X-UStreamer-Height", frame->height);
ADD_TIME_HEADER("X-UStreamer-Grab-Timestamp", frame->grab_ts);
ADD_TIME_HEADER("X-UStreamer-Grab-Begin-Timestamp", frame->grab_begin_ts);
ADD_TIME_HEADER("X-UStreamer-Grab-End-Timestamp", frame->grab_end_ts);
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Timestamp", frame->encode_begin_ts);
ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", frame->encode_end_ts);
ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", us_get_now_monotonic());
@@ -932,13 +927,15 @@ static void _http_refresher(int fd, short what, void *v_server) {
bool stream_updated = false;
bool frame_updated = false;
const int ri = us_ring_consumer_acquire(ring, 0);
if (ri >= 0) {
int ri;
while ((ri = us_ring_consumer_acquire(ring, 0)) >= 0) {
const us_frame_s *const frame = ring->items[ri];
frame_updated = _expose_frame(server, frame);
stream_updated = true;
us_ring_consumer_release(ring, ri);
} else if (ex->expose_end_ts + 1 < us_get_now_monotonic()) {
}
if (!stream_updated && (ex->expose_end_ts + 1 < us_get_now_monotonic())) {
_LOG_DEBUG("Repeating exposed ...");
ex->expose_begin_ts = us_get_now_monotonic();
ex->expose_cmp_ts = ex->expose_begin_ts;

View File

@@ -43,7 +43,7 @@
static us_m2m_encoder_s *_m2m_encoder_init(
const char *name, const char *path, uint output_format,
uint bitrate, uint gop, uint quality, bool allow_dma);
uint bitrate, uint gop, uint quality, bool allow_dma, bool boost);
static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame);
@@ -63,9 +63,9 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("%s: " x_msg, enc->name, ##__VA_ARGS__)
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, uint bitrate, uint gop) {
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, uint bitrate, uint gop, bool boost) {
bitrate *= 1000; // From Kbps
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_H264, bitrate, gop, 0, true);
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_H264, bitrate, gop, 0, true, boost);
}
us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path, uint quality) {
@@ -76,12 +76,12 @@ us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path,
bitrate = step * round(bitrate / step);
bitrate *= 1000; // From Kbps
assert(bitrate > 0);
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, bitrate, 0, 0, true);
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, bitrate, 0, 0, true, false);
}
us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, uint quality) {
// FIXME: DMA не работает
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_JPEG, 0, 0, quality, false);
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_JPEG, 0, 0, quality, false, false);
}
void us_m2m_encoder_destroy(us_m2m_encoder_s *enc) {
@@ -139,7 +139,7 @@ int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_fra
static us_m2m_encoder_s *_m2m_encoder_init(
const char *name, const char *path, uint output_format,
uint bitrate, uint gop, uint quality, bool allow_dma) {
uint bitrate, uint gop, uint quality, bool allow_dma, bool boost) {
US_LOG_INFO("%s: Initializing encoder ...", name);
@@ -161,6 +161,7 @@ static us_m2m_encoder_s *_m2m_encoder_init(
enc->gop = gop;
enc->quality = quality;
enc->allow_dma = allow_dma;
enc->boost = boost;
enc->run = run;
return enc;
}
@@ -222,7 +223,11 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, enc->gop);
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE);
if (run->p_width * run->p_height <= 1920 * 1080) { // https://forums.raspberrypi.com/viewtopic.php?t=291447#p1762296
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
if (enc->boost) {
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_2);
} else {
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
}
} else {
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_5_1);
}
@@ -276,10 +281,13 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
}
}
if (run->p_width * run->p_height <= 1280 * 720) {
if (
(run->p_width * run->p_height <= 1280 * 720)
|| ((enc->output_format == V4L2_PIX_FMT_H264) && enc->boost)
) {
// H264 требует каких-то лимитов. Больше 30 не поддерживается, а при 0
// через какое-то время начинает производить некорректные фреймы.
// Если же привысить fps, то резко увеличивается время кодирования.
// Если же превысить fps, то резко увеличивается время кодирования.
run->fps_limit = 60;
} else {
run->fps_limit = 30;

View File

@@ -58,12 +58,13 @@ typedef struct {
uint gop;
uint quality;
bool allow_dma;
bool boost;
us_m2m_encoder_runtime_s *run;
} us_m2m_encoder_s;
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, uint bitrate, uint gop);
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, uint bitrate, uint gop, bool boost);
us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path, uint quality);
us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, uint quality);
void us_m2m_encoder_destroy(us_m2m_encoder_s *enc);

View File

@@ -100,6 +100,7 @@ enum _US_OPT_VALUES {
_O_H264_BITRATE,
_O_H264_GOP,
_O_H264_M2M_DEVICE,
_O_H264_BOOST,
# undef ADD_SINK
# ifdef WITH_V4P
@@ -117,6 +118,7 @@ enum _US_OPT_VALUES {
# ifdef WITH_PDEATHSIG
_O_EXIT_ON_PARENT_DEATH,
# endif
_O_EXIT_ON_DEVICE_ERROR,
_O_EXIT_ON_NO_CLIENTS,
# ifdef WITH_SETPROCTITLE
_O_PROCESS_NAME_PREFIX,
@@ -138,7 +140,7 @@ static const struct option _LONG_OPTS[] = {
{"input", required_argument, NULL, _O_INPUT},
{"resolution", required_argument, NULL, _O_RESOLUTION},
{"format", required_argument, NULL, _O_FORMAT},
{"format-swap-rgb", required_argument, NULL, _O_FORMAT_SWAP_RGB},
{"format-swap-rgb", no_argument, NULL, _O_FORMAT_SWAP_RGB},
{"tv-standard", required_argument, NULL, _O_TV_STANDARD},
{"io-method", required_argument, NULL, _O_IO_METHOD},
{"desired-fps", required_argument, NULL, _O_DESIRED_FPS},
@@ -205,6 +207,7 @@ static const struct option _LONG_OPTS[] = {
{"h264-bitrate", required_argument, NULL, _O_H264_BITRATE},
{"h264-gop", required_argument, NULL, _O_H264_GOP},
{"h264-m2m-device", required_argument, NULL, _O_H264_M2M_DEVICE},
{"h264-boost", no_argument, NULL, _O_H264_BOOST},
// Compatibility
{"sink", required_argument, NULL, _O_JPEG_SINK},
{"sink-mode", required_argument, NULL, _O_JPEG_SINK_MODE},
@@ -227,6 +230,7 @@ static const struct option _LONG_OPTS[] = {
# ifdef WITH_PDEATHSIG
{"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH},
# endif
{"exit-on-device-error", no_argument, NULL, _O_EXIT_ON_DEVICE_ERROR},
{"exit-on-no-clients", required_argument, NULL, _O_EXIT_ON_NO_CLIENTS},
# ifdef WITH_SETPROCTITLE
{"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX},
@@ -384,7 +388,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
case _O_FORMAT_SWAP_RGB: OPT_SET(cap->format_swap_rgb, true);
case _O_TV_STANDARD: OPT_PARSE_ENUM("TV standard", cap->standard, us_capture_parse_standard, US_STANDARDS_STR);
case _O_IO_METHOD: OPT_PARSE_ENUM("IO method", cap->io_method, us_capture_parse_io_method, US_IO_METHODS_STR);
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", cap->desired_fps, 0, US_VIDEO_MAX_FPS, 0);
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", stream->desired_fps, 0, US_VIDEO_MAX_FPS, 0);
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", cap->min_frame_size, 1, 8192, 0);
case _O_ALLOW_TRUNCATED_FRAMES: OPT_SET(cap->allow_truncated_frames, true);
case _O_PERSISTENT: OPT_SET(cap->persistent, true);
@@ -467,6 +471,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 20000, 0);
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
case _O_H264_BOOST: OPT_SET(stream->h264_boost, true);
# ifdef WITH_V4P
case _O_V4P:
@@ -490,6 +495,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
};
break;
# endif
case _O_EXIT_ON_DEVICE_ERROR: OPT_SET(stream->exit_on_device_error, true);
case _O_EXIT_ON_NO_CLIENTS: OPT_NUMBER("--exit-on-no-clients", stream->exit_on_no_clients, 0, 86400, 0);
# ifdef WITH_SETPROCTITLE
case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg);
@@ -743,6 +749,7 @@ static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, co
SAY(" --h264-bitrate <kbps> ───────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
SAY(" --h264-gop <N> ──────────────── Interval between keyframes. Default: %u.\n", stream->h264_gop);
SAY(" --h264-m2m-device </dev/path> ─ Path to V4L2 M2M encoder device. Default: auto select.\n");
SAY(" --h264-boost ────────────────── Increase encoder performance on PiKVM V4. Default: disabled.\n");
# ifdef WITH_V4P
SAY("Passthrough options for PiKVM V4:");
SAY("═════════════════════════════════");

View File

@@ -28,9 +28,12 @@
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <math.h>
#include <pthread.h>
#include <event2/event.h> // jpeg_refresher
#include "../libs/types.h"
#include "../libs/errors.h"
#include "../libs/tools.h"
@@ -129,7 +132,7 @@ us_stream_s *us_stream_init(us_capture_s *cap, us_encoder_s *enc) {
void us_stream_update_blank(us_stream_s *stream, const us_capture_s *cap) {
us_stream_runtime_s *const run = stream->run;
us_blank_draw(run->blank, "< NO SIGNAL >", cap->width, cap->height);
us_blank_draw(run->blank, "< NO LIVE VIDEO >", cap->width, cap->height);
us_fpsi_frame_to_meta(run->blank->raw, &run->notify_meta); // Initial "unchanged" meta
_stream_update_captured_fpsi(stream, run->blank->raw, false);
}
@@ -154,7 +157,13 @@ void us_stream_loop(us_stream_s *stream) {
atomic_store(&run->http->last_request_ts, us_get_now_monotonic());
if (stream->h264_sink != NULL) {
run->h264_enc = us_m2m_h264_encoder_init("H264", stream->h264_m2m_path, stream->h264_bitrate, stream->h264_gop);
run->h264_enc = us_m2m_h264_encoder_init(
"H264",
stream->h264_m2m_path,
stream->h264_bitrate,
stream->h264_gop,
stream->h264_boost);
run->h264_tmp_src = us_frame_init();
run->h264_dest = us_frame_init();
}
@@ -312,6 +321,9 @@ static void *_jpeg_thread(void *v_ctx) {
_worker_context_s *ctx = v_ctx;
us_stream_s *stream = ctx->stream;
uint take = 1;
uint step = 1;
ldf grab_after_ts = 0;
uint fluency_passed = 0;
@@ -330,7 +342,7 @@ static void *_jpeg_thread(void *v_ctx) {
atomic_fetch_sub(&stream->run->http->snapshot_requested, 1);
}
US_LOG_PERF("JPEG: ##### Encoded JPEG exposed; worker=%s, latency=%.3Lf",
wr->name, us_get_now_monotonic() - job->dest->grab_ts);
wr->name, us_get_now_monotonic() - job->dest->grab_begin_ts);
} else {
US_LOG_PERF("JPEG: ----- Encoded JPEG dropped; worker=%s", wr->name);
}
@@ -348,6 +360,19 @@ static void *_jpeg_thread(void *v_ctx) {
continue;
}
if (stream->desired_fps > 0) {
const uint captured_fps = us_fpsi_get(stream->run->http->captured_fpsi, NULL);
take = ceilf((float)captured_fps / (float)stream->desired_fps);
if (step < take) {
US_LOG_DEBUG("JPEG: Passed encoding for FPS limit: step=%u, take=%u", step, take);
++step;
us_capture_hwbuf_decref(hw);
continue;
} else {
step = 1;
}
}
const ldf now_ts = us_get_now_monotonic();
if (now_ts < grab_after_ts) {
fluency_passed += 1;
@@ -394,7 +419,9 @@ static void *_h264_thread(void *v_ctx) {
_worker_context_s *ctx = v_ctx;
us_stream_s *stream = ctx->stream;
ldf grab_after_ts = 0;
uint take = 1;
uint step = 1;
while (!atomic_load(ctx->stop)) {
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue);
if (hw == NULL) {
@@ -405,23 +432,25 @@ static void *_h264_thread(void *v_ctx) {
US_LOG_VERBOSE("H264: Passed encoding because nobody is watching");
goto decref;
}
if (hw->raw.grab_ts < grab_after_ts) {
US_LOG_DEBUG("H264: Passed encoding for FPS limit");
goto decref;
uint fps_limit = stream->run->h264_enc->run->fps_limit;
if (stream->desired_fps > 0 && (fps_limit == 0 || stream->desired_fps < fps_limit)) {
fps_limit = stream->desired_fps;
}
if (fps_limit > 0) {
const uint captured_fps = us_fpsi_get(stream->run->http->captured_fpsi, NULL);
take = ceilf((float)captured_fps / (float)fps_limit);
if (step < take) {
US_LOG_DEBUG("H264: Passed encoding for FPS limit: step=%u, take=%u", step, take);
++step;
goto decref;
} else {
step = 1;
}
}
_stream_encode_expose_h264(ctx->stream, &hw->raw, false);
// M2M-енкодер увеличивает задержку на 100 милисекунд при 1080p, если скормить ему больше 30 FPS.
// Поэтому у нас есть два режима: 60 FPS для маленьких видео и 30 для 1920x1080(1200).
// Следующй фрейм захватывается не раньше, чем это требуется по FPS, минус небольшая
// погрешность (если захват неравномерный) - немного меньше 1/60, и примерно треть от 1/30.
const uint fps_limit = stream->run->h264_enc->run->fps_limit;
if (fps_limit > 0) {
const ldf frame_interval = (ldf)1 / fps_limit;
grab_after_ts = hw->raw.grab_ts + frame_interval - 0.01;
}
decref:
us_capture_hwbuf_decref(hw);
}
@@ -529,6 +558,8 @@ static int _stream_init_loop(us_stream_s *stream) {
int once = 0;
while (!atomic_load(&stream->run->stop)) {
const char *blank_reason = "< NO LIVE VIDEO >";
# ifdef WITH_GPIO
us_gpio_set_stream_online(false);
# endif
@@ -554,17 +585,68 @@ static int _stream_init_loop(us_stream_s *stream) {
switch (us_capture_open(stream->cap)) {
case 0: break;
case US_ERROR_NO_DEVICE:
case US_ERROR_NO_DATA:
US_ONCE({ US_LOG_INFO("Waiting for the capture device ..."); });
goto offline_and_retry;
blank_reason = (
"< NO CAPTURE DEVICE >\n \n"
" Possible reasons: \n \n"
" - Device unplugged \n \n"
" - Bad config \n \n"
" - Malfunction "
);
goto silent_error;
case US_ERROR_NO_CABLE:
blank_reason = (
"< NO VIDEO SOURCE >\n \n"
" Possible reasons: \n \n"
" - Source is off \n \n"
" - Cable problems "
);
goto silent_error;
case US_ERROR_NO_SIGNAL:
blank_reason = (
"< NO SIGNAL DETECTED >\n \n"
" Possible reasons: \n \n"
" - Video suspended \n \n"
" - Cable problems "
);
goto silent_error;
case US_ERROR_NO_SYNC:
blank_reason = (
"< NO SYNC WITH SIGNAL >\n \n"
" Possible reasons: \n \n"
" - Source is crazy \n \n"
" - Cable problems "
);
goto silent_error;
case US_ERROR_NO_LANES:
blank_reason = (
"< UNSUPPORTED SIGNAL TIMINGS >\n \n"
" Possible reasons: \n \n"
" - Too high frequency \n \n"
" - Source ignores EDID \n \n"
" - Invalid EDID "
);
goto verbose_error;
default:
once = 0;
goto offline_and_retry;
goto verbose_error;
}
us_encoder_open(stream->enc, stream->cap);
return 0;
silent_error:
if (!stream->exit_on_device_error) {
US_ONCE({ US_LOG_INFO("Waiting for the capture device ..."); });
}
goto offline_and_retry;
verbose_error:
once = 0;
goto offline_and_retry;
offline_and_retry:
if (stream->exit_on_device_error) {
US_LOG_INFO("Device error, exiting ...");
us_process_suicide();
}
for (uint count = 0; count < stream->error_delay * 10; ++count) {
if (atomic_load(&run->stop)) {
break;
@@ -577,7 +659,7 @@ static int _stream_init_loop(us_stream_s *stream) {
width = stream->cap->width;
height = stream->cap->height;
}
us_blank_draw(run->blank, "< NO SIGNAL >", width, 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);
@@ -643,6 +725,7 @@ static void _stream_expose_jpeg(us_stream_s *stream, const us_frame_s *frame) {
us_frame_s *const dest = run->http->jpeg_ring->items[ri];
us_frame_copy(frame, dest);
us_ring_producer_release(run->http->jpeg_ring, ri);
event_active(run->http->jpeg_refresher, 0, 0);
if (stream->jpeg_sink != NULL) {
us_memsink_server_put(stream->jpeg_sink, dest, NULL);
}

View File

@@ -26,6 +26,8 @@
#include <pthread.h>
#include <event2/event.h> // jpeg_refresher
#include "../libs/types.h"
#include "../libs/queue.h"
#include "../libs/ring.h"
@@ -51,6 +53,7 @@ typedef struct {
atomic_bool h264_online;
us_fpsi_s *h264_fpsi;
struct event *jpeg_refresher;
us_ring_s *jpeg_ring;
atomic_bool has_clients;
atomic_uint snapshot_requested;
@@ -77,9 +80,11 @@ typedef struct {
us_capture_s *cap;
us_encoder_s *enc;
uint desired_fps;
bool notify_parent;
bool slowdown;
uint error_delay;
bool exit_on_device_error;
uint exit_on_no_clients;
us_memsink_s *jpeg_sink;
@@ -89,6 +94,7 @@ typedef struct {
uint h264_bitrate;
uint h264_gop;
char *h264_m2m_path;
bool h264_boost;
# ifdef WITH_V4P
us_drm_s *drm;

View File

@@ -37,7 +37,7 @@ static void *_worker_thread(void *v_worker);
us_workers_pool_s *us_workers_pool_init(
const char *name, const char *wr_prefix, uint n_workers, ldf desired_interval,
const char *name, const char *wr_prefix, uint n_workers,
us_workers_pool_job_init_f job_init, void *job_init_arg,
us_workers_pool_job_destroy_f job_destroy,
us_workers_pool_run_job_f run_job) {
@@ -47,7 +47,6 @@ us_workers_pool_s *us_workers_pool_init(
us_workers_pool_s *pool;
US_CALLOC(pool, 1);
pool->name = name;
pool->desired_interval = desired_interval;
pool->job_destroy = job_destroy;
pool->run_job = run_job;
@@ -147,14 +146,8 @@ ldf us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, const us_worker_s
pool->approx_job_time = approx_job_time;
const ldf min_delay = pool->approx_job_time / pool->n_workers; // Среднее время работы размазывается на N воркеров
if (pool->desired_interval > 0 && min_delay > 0 && pool->desired_interval > min_delay) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
// и аппаратный fps не попадает точно в желаемое значение
return pool->desired_interval;
}
return min_delay;
// Среднее время работы размазывается на N воркеров
return (pool->approx_job_time / pool->n_workers);
}
static void *_worker_thread(void *v_worker) {

View File

@@ -56,7 +56,6 @@ typedef bool (*us_workers_pool_run_job_f)(us_worker_s *wr);
typedef struct us_workers_pool_sx {
const char *name;
ldf desired_interval;
us_workers_pool_job_destroy_f job_destroy;
us_workers_pool_run_job_f run_job;
@@ -76,7 +75,7 @@ typedef struct us_workers_pool_sx {
us_workers_pool_s *us_workers_pool_init(
const char *name, const char *wr_prefix, uint n_workers, ldf desired_interval,
const char *name, const char *wr_prefix, uint n_workers,
us_workers_pool_job_init_f job_init, void *job_init_arg,
us_workers_pool_job_destroy_f job_destroy,
us_workers_pool_run_job_f run_job);

View File

@@ -242,7 +242,7 @@ static void _main_loop(void) {
us_drm_destroy(drm);
}
static void *_follower_thread(void *v_unix_follow) {
static void *_follower_thread(void *v_unix_follow) { // cppcheck-suppress constParameterCallback
US_THREAD_SETTLE("follower");
const char *path = v_unix_follow;
assert(path != NULL);