Compare commits

...

101 Commits

Author SHA1 Message Date
Maxim Devaev
88460b72e1 Bump version: 6.54 → 6.55 2026-02-13 23:26:35 +02:00
Sergey Radionov
8c69c77481 janus: fixed compatibility with Tailscale MTU (#325) 2026-02-13 17:34:52 +02:00
Maxim Devaev
5331ae14aa Bump version: 6.53 → 6.54 2026-02-12 20:38:03 +02:00
Maxim Devaev
0127dcf018 janus: hotfix: reverted sps/pps logic for first/last packages 2026-02-12 20:36:00 +02:00
Maxim Devaev
aa58b1b002 Bump version: 6.52 → 6.53 2026-02-12 19:47:33 +02:00
Maxim Devaev
a05eab71a8 janus: reserve 50 bytes for RTP extensions 2026-02-12 19:34:39 +02:00
Maxim Devaev
e013356cf0 janus: renamed US_RTP_DATAGRAM_SIZE -> US_RTP_TOTAL_SIZE 2026-02-12 19:27:00 +02:00
Maxim Devaev
c730981827 janus: set first/last_of_frame only for non-sps/pps packets 2026-02-12 18:48:22 +02:00
Maxim Devaev
3bb1ed3ef3 openwrt package 2026-02-09 17:09:09 +02:00
Paul Donald
1cda22bfd2 spell fixes (#323) 2026-02-09 16:59:05 +02:00
Maxim Devaev
29ee20e864 Bump version: 6.51 → 6.52 2026-02-07 17:06:14 +02:00
Maxim Devaev
5e13f4cd58 using CC -dumpmachine instead of uname 2026-02-07 17:02:43 +02:00
Maxim Devaev
39c1916f61 Bump version: 6.50 → 6.51 2026-02-03 19:52:09 +02:00
Ivan Shapovalov
614e83771b ustreamer: options: NULL-terminate the copy of argv (#322)
According to N2176 of ISO/IEC 9899:2017 §5.1.2.2.1 ¶2:

> - argv[argc] shall be a null pointer.

Possibly fixes openwrt/packages#28472.
2026-02-03 19:41:40 +02:00
Maxim Devaev
15a9e28ac6 spelling fix 2026-02-03 10:59:26 +02:00
Maxim Devaev
a1ae02de5d janus: refactored sdp logic 2026-01-31 16:45:26 +02:00
Maxim Devaev
214708549d Bump version: 6.49 → 6.50 2026-01-30 16:21:30 +02:00
Maxim Devaev
1be4521664 Issue #321: Fixed compilation error on FreeBSD 2026-01-30 12:51:25 +02:00
Maxim Devaev
94752dde75 Bump version: 6.48 → 6.49 2026-01-28 15:14:30 +02:00
Maxim Devaev
b933b7b407 Bump version: 6.47 → 6.48 2026-01-28 14:17:18 +02:00
Maxim Devaev
61f44b5f97 janus: don't send rtp extensions with each packet 2026-01-28 12:06:37 +02:00
Maxim Devaev
8fef0408b6 janus: time functions refactored 2026-01-28 11:05:50 +02:00
Maxim Devaev
62028be064 janus: rtpv: mark all extensions as sendonly 2026-01-28 10:57:43 +02:00
Sergey Radionov
3b7592bb31 Janus: "Absolute Capture Time" RTP extension added (#320)
for video.
2026-01-28 09:59:01 +02:00
Maxim Devaev
8adca998e9 janus: fixed sdp for firefox 2026-01-28 08:50:53 +02:00
Maxim Devaev
6ac5a5f065 refactoring 2026-01-28 08:49:41 +02:00
Maxim Devaev
12cf4492bd Bump version: 6.46 → 6.47 2026-01-23 23:15:09 +02:00
Maxim Devaev
a6f111f7cf refactoring 2026-01-23 03:06:16 +02:00
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
Maxim Devaev
602c1747d5 Bump version: 6.30 → 6.31 2025-02-08 15:46:31 +02:00
Maxim Devaev
a2b8b35070 improved build system 2025-02-08 15:44:40 +02:00
Maxim Devaev
dd7701be38 Bump version: 6.29 → 6.30 2025-02-08 13:03:01 +02:00
Maxim Devaev
1c9bd91b31 lint fix 2025-02-08 13:01:32 +02:00
Maxim Devaev
e19a3ca7ff report about all WITH_* flags in --features 2025-02-08 02:21:26 +02:00
Maxim Devaev
b2d1a5612d manual WITH_PDEATHSIG 2025-02-08 01:56:59 +02:00
Maxim Devaev
f3e0613de3 python: expose FEATURES variable 2025-02-08 00:25:17 +02:00
Maxim Devaev
5baf921660 common WITH_* flags 2025-02-07 23:31:36 +02:00
Maxim Devaev
6cabcd39f1 python: fixed uninitialized fd 2025-02-07 23:24:05 +02:00
Maxim Devaev
3df3658e4f python: version constants 2025-02-07 23:20:45 +02:00
Maxim Devaev
f21fc5f6d3 added missing WITH_V4P flag to --features 2025-02-07 18:02:04 +02:00
Maxim Devaev
b70ed98af9 Bump version: 6.28 → 6.29 2025-02-03 08:55:13 +02:00
Maxim Devaev
52cdabe150 janus: counterclockwise video rotation 2025-02-03 08:52:42 +02:00
Maxim Devaev
fe86997d08 Bump version: 6.27 → 6.28 2025-01-28 15:59:57 +02:00
Maxim Devaev
df39b824c6 refactoring 2025-01-27 06:32:26 +02:00
Sam Listopad
db297db52e Add Support for YUV420 and YVU variants. (#276)
* Add Support fo YUV420 and 410 and YVU variants.

* Add new formats to the help messaging

* Remove YUV410 supprt since M2M encoder on Pi cannot convert it

* Cleanups requested by @mdevaev

* Change to use u8 per @mdevaev
2025-01-27 06:14:18 +02:00
Jack Wilsdon
b304364af9 Allow overriding pkg-config (#301) 2025-01-27 02:53:39 +02:00
Maxim Devaev
ddec4e8478 Bump version: 6.26 → 6.27 2025-01-21 05:44:36 +02:00
Maxim Devaev
28ca658621 moved to python-3.13 2025-01-21 05:43:04 +02:00
90 changed files with 1950 additions and 1171 deletions

View File

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

View File

@@ -1,43 +1,64 @@
-include config.mk -include config.mk
# =====
DESTDIR ?= DESTDIR ?=
PREFIX ?= /usr/local PREFIX ?= /usr/local
MANPREFIX ?= $(PREFIX)/share/man MANPREFIX ?= $(PREFIX)/share/man
CC ?= gcc CC ?= gcc
PY ?= python3 PY ?= python3
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O3 CFLAGS ?= -O3
LDFLAGS ?= LDFLAGS ?=
R_DESTDIR = $(if $(DESTDIR),$(shell realpath "$(DESTDIR)"),) R_DESTDIR = $(if $(DESTDIR),$(shell realpath "$(DESTDIR)"),)
WITH_PYTHON ?= 0
WITH_JANUS ?= 0
WITH_V4P ?= 0
WITH_GPIO ?= 0
WITH_SYSTEMD ?= 0
WITH_PTHREAD_NP ?= 1
WITH_SETPROCTITLE ?= 1
WITH_PDEATHSIG ?= 1
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
MK_WITH_PYTHON = $(call optbool,$(WITH_PYTHON))
MK_WITH_JANUS = $(call optbool,$(WITH_JANUS))
MK_WITH_V4P = $(call optbool,$(WITH_V4P))
MK_WITH_GPIO = $(call optbool,$(WITH_GPIO))
MK_WITH_SYSTEMD = $(call optbool,$(WITH_SYSTEMD))
MK_WITH_PTHREAD_NP = $(call optbool,$(WITH_PTHREAD_NP))
MK_WITH_SETPROCTITLE = $(call optbool,$(WITH_SETPROCTITLE))
MK_WITH_PDEATHSIG = $(call optbool,$(WITH_PDEATHSIG))
export export
_LINTERS_IMAGE ?= ustreamer-linters _LINTERS_IMAGE ?= ustreamer-linters
# ===== # =====
ifeq (__not_found__,$(shell which pkg-config 2>/dev/null || echo "__not_found__")) ifeq (__not_found__,$(shell which $(PKG_CONFIG) 2>/dev/null || echo "__not_found__"))
$(error "No pkg-config found in $(PATH)") $(error "No $(PKG_CONFIG) found in $(PATH)")
endif endif
# ===== # =====
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifeq ($(V),) ifeq ($(V),)
ECHO = @ ECHO = @
endif endif
# ===== # =====
all: all:
+ $(MAKE) apps + $(MAKE) apps
ifneq ($(call optbool,$(WITH_PYTHON)),) ifneq ($(MK_WITH_PYTHON),)
+ $(MAKE) python + $(MAKE) python
endif endif
ifneq ($(call optbool,$(WITH_JANUS)),) ifneq ($(MK_WITH_JANUS),)
+ $(MAKE) janus + $(MAKE) janus
endif endif
@@ -61,10 +82,10 @@ janus:
install: all install: all
$(MAKE) -C src install $(MAKE) -C src install
ifneq ($(call optbool,$(WITH_PYTHON)),) ifneq ($(MK_WITH_PYTHON),)
$(MAKE) -C python install $(MAKE) -C python install
endif endif
ifneq ($(call optbool,$(WITH_JANUS)),) ifneq ($(MK_WITH_JANUS),)
$(MAKE) -C janus install $(MAKE) -C janus install
endif endif
mkdir -p $(R_DESTDIR)$(MANPREFIX)/man1 mkdir -p $(R_DESTDIR)$(MANPREFIX)/man1

View File

@@ -11,7 +11,7 @@
|----------|---------------|-------------------| |----------|---------------|-------------------|
| Multithreaded JPEG encoding | ✔ | ✘ | | Multithreaded JPEG encoding | ✔ | ✘ |
| Hardware image encoding<br>on Raspberry Pi | ✔ | ✘ | | 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> | | [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> | ✘ | | Option to skip frames when streaming<br>static images by HTTP to save the traffic | ✔ <sup>2</sup> | ✘ |
| Streaming via UNIX domain socket | ✔ | ✘ | | Streaming via UNIX domain socket | ✔ | ✘ |
@@ -35,16 +35,17 @@ If you're going to live-stream from your backyard webcam and need to control it,
# Installation # Installation
## Building ## Building
You need to download the µStreamer onto your system and build it from the sources. You need to download the µStreamer onto your system and build it from the sources, or use a package:
* AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. * Arch Linux: https://aur.archlinux.org/packages/ustreamer
* Fedora: https://src.fedoraproject.org/rpms/ustreamer. * Fedora: https://src.fedoraproject.org/rpms/ustreamer
* Ubuntu: https://packages.ubuntu.com/jammy/ustreamer. * Ubuntu: https://packages.ubuntu.com/jammy/ustreamer
* Debian: https://packages.debian.org/sid/ustreamer * Debian: https://packages.debian.org/sid/ustreamer
* FreeBSD port: https://www.freshports.org/multimedia/ustreamer. * OpenWRT: https://github.com/openwrt/packages/tree/master/multimedia/ustreamer
* FreeBSD port: https://www.freshports.org/multimedia/ustreamer
### Preconditions ### 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`. * 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`. * 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

@@ -2,6 +2,7 @@ R_DESTDIR ?=
PREFIX ?= /usr/local PREFIX ?= /usr/local
CC ?= gcc CC ?= gcc
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O3 CFLAGS ?= -O3
LDFLAGS ?= LDFLAGS ?=
@@ -9,21 +10,20 @@ LDFLAGS ?=
# ===== # =====
_PLUGIN = libjanus_ustreamer.so _PLUGIN = libjanus_ustreamer.so
_CFLAGS = -fPIC -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(shell pkg-config --cflags glib-2.0) $(CFLAGS) _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 glib-2.0) $(LDFLAGS) _LDFLAGS = -shared -lm -pthread -lrt -ljansson -lopus -lasound -lspeexdsp $(shell $(PKG_CONFIG) --libs janus-gateway) $(LDFLAGS)
_SRCS = $(shell ls src/uslibs/*.c src/*.c) _SRCS = $(shell ls src/uslibs/*.c src/*.c)
_BUILD = build _BUILD = build
define optbool # =====
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1) ifeq ($(findstring bsd,$(shell $(CC) -dumpmachine)),)
endef override _LDFLAGS += -latomic
endif
ifneq ($(MK_WITH_PTHREAD_NP),)
WITH_PTHREAD_NP ?= 1
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
override _CFLAGS += -DWITH_PTHREAD_NP override _CFLAGS += -DWITH_PTHREAD_NP
endif endif

View File

@@ -47,19 +47,6 @@ static void *_pcm_thread(void *v_acap);
static void *_encoder_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 *us_acap_init(const char *name, uint pcm_hz) {
us_acap_s *acap; us_acap_s *acap;
US_CALLOC(acap, 1); US_CALLOC(acap, 1);

View File

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

View File

@@ -23,10 +23,78 @@
#include "au.h" #include "au.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include "uslibs/tools.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 *us_au_pcm_init(void) {
us_au_pcm_s *pcm; us_au_pcm_s *pcm;
US_CALLOC(pcm, 1); US_CALLOC(pcm, 1);
@@ -39,11 +107,14 @@ void us_au_pcm_destroy(us_au_pcm_s *pcm) {
void us_au_pcm_mix(us_au_pcm_s *dest, us_au_pcm_s *src) { void us_au_pcm_mix(us_au_pcm_s *dest, us_au_pcm_s *src) {
const uz size = src->frames * US_RTP_OPUS_CH * 2; // 2 for 16 bit const uz size = src->frames * US_RTP_OPUS_CH * 2; // 2 for 16 bit
if (src->frames == 0) { if (src->frames == 0) {
return; return;
} else if (dest->frames == 0) { } else if (dest->frames == 0) {
memcpy(dest->data, src->data, size); memcpy(dest->data, src->data, size);
dest->frames = src->frames; dest->frames = src->frames;
} else if (dest->frames == src->frames) { } else if (dest->frames == src->frames) {
// https://stackoverflow.com/questions/12089662 // https://stackoverflow.com/questions/12089662
for (uz index = 0; index < size; ++index) { for (uz index = 0; index < size; ++index) {

View File

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

View File

@@ -193,20 +193,33 @@ static void *_video_or_acap_thread(void *v_client, bool video) {
}; };
janus_plugin_rtp_extensions_reset(&packet.extensions); janus_plugin_rtp_extensions_reset(&packet.extensions);
/*if (rtp->zero_playout_delay) { if (rtp.first_of_frame) {
// https://github.com/pikvm/pikvm/issues/784 if (rtp.zero_playout_delay) {
packet.extensions.min_delay = 0; // https://github.com/pikvm/pikvm/issues/784
packet.extensions.max_delay = 0; packet.extensions.min_delay = 0;
} else { packet.extensions.max_delay = 0;
packet.extensions.min_delay = 0; } else {
// 10s - Chromium/WebRTC default // Эти дефолты используются в Chrome/Safari/Firefox.
// 3s - Firefox default // Работает всё одинаково, потому что у них общая кодовая база WebRTC.
packet.extensions.max_delay = 300; // == 3s, i.e. 10ms granularity packet.extensions.min_delay = 0;
}*/ packet.extensions.max_delay = 1000; // == 10s, i.e. 10ms granularity
}
}
if (rtp.video) { if (rtp.video && rtp.first_of_frame) {
const uint video_orient = atomic_load(&client->video_orient); packet.extensions.abs_capture_ts = rtp.grab_ntp_ts;
}
if (rtp.video && rtp.last_of_frame) {
uint video_orient = atomic_load(&client->video_orient);
if (video_orient != 0) { if (video_orient != 0) {
// The extension rotates the video clockwise, but want it counterclockwise.
// It's more intuitive for people who have seen a protractor at least once in their life.
if (video_orient == 90) {
video_orient = 270;
} else if (video_orient == 270) {
video_orient = 90;
}
packet.extensions.video_rotation = video_orient; packet.extensions.video_rotation = video_orient;
} }
} }

View File

@@ -24,11 +24,11 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include <janus/config.h> #include <janus/config.h>
#include <janus/plugins/plugin.h> #include <janus/plugins/plugin.h>
#include "uslibs/types.h"
#include "uslibs/tools.h" #include "uslibs/tools.h"
#include "const.h" #include "const.h"
@@ -36,6 +36,7 @@
static char *_get_value(janus_config *jcfg, const char *section, const char *option); 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); // 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; goto error;
} }
if ((config->acap_dev_name = _get_value(jcfg, "acap", "device")) != NULL) { if ((config->acap_dev_name = _get_value(jcfg, "acap", "device")) != NULL) {
if ((config->tc358743_dev_path = _get_value(jcfg, "acap", "tc358743")) == NULL) { config->acap_hz = _get_uint(jcfg, "acap", "sampling_rate", 0);
US_JLOG_INFO("config", "Missing config value: acap.tc358743"); 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; goto error;
} }
if ((config->aplay_dev_name = _get_value(jcfg, "aplay", "device")) != NULL) { config->aplay_dev_name = _get_value(jcfg, "aplay", "device");
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);
}
}
} }
goto ok; 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); 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) { /*static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def) {
char *const tmp = _get_value(jcfg, section, option); char *const tmp = _get_value(jcfg, section, option);
bool value = def; bool value = def;

View File

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

View File

@@ -22,7 +22,6 @@
#include <stdatomic.h> #include <stdatomic.h>
#include <stdlib.h> #include <stdlib.h>
#include <inttypes.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <errno.h> #include <errno.h>
@@ -56,9 +55,13 @@
#include "rtp.h" #include "rtp.h"
#include "rtpv.h" #include "rtpv.h"
#include "rtpa.h" #include "rtpa.h"
#include "sdp.h"
#include "memsinkfd.h" #include "memsinkfd.h"
#include "config.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 us_config_s *_g_config = NULL;
static const useconds_t _g_watchers_polling = 100000; 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 janus_callbacks *_g_gw = NULL;
static us_ring_s *_g_video_ring = NULL; static us_ring_s *_g_video_ring = NULL;
static us_rtpv_s *_g_rtpv = 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 pthread_t _g_video_rtp_tid;
static atomic_bool _g_video_rtp_tid_created = false; static atomic_bool _g_video_rtp_tid_created = false;
@@ -211,7 +214,15 @@ static void *_video_sink_thread(void *arg) {
return NULL; 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; int fd;
if ((fd = open(_g_config->tc358743_dev_path, O_RDWR)) < 0) { if ((fd = open(_g_config->tc358743_dev_path, O_RDWR)) < 0) {
US_JLOG_PERROR("acap", "Can't open TC358743 V4L2 device"); 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); atomic_store(&_g_acap_tid_created, true);
assert(_g_config->acap_dev_name != NULL); assert(_g_config->acap_dev_name != NULL);
assert(_g_config->tc358743_dev_path != NULL);
assert(_g_rtpa != NULL); assert(_g_rtpa != NULL);
int once = 0; int once = 0;
@@ -247,7 +257,11 @@ static void *_acap_thread(void *arg) {
uint hz = 0; uint hz = 0;
us_acap_s *acap = NULL; 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; goto close_acap;
} }
if (hz == 0) { if (hz == 0) {
@@ -262,10 +276,10 @@ static void *_acap_thread(void *arg) {
once = 0; once = 0;
while (!_STOP && _HAS_WATCHERS && _HAS_LISTENERS) { 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; goto close_acap;
} }
uz size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE; uz size = US_RTP_TOTAL_SIZE - US_RTP_HEADER_SIZE;
u8 data[size]; u8 data[size];
u64 pts; u64 pts;
const int result = us_acap_get_encoded(acap, data, &size, &pts); const int result = us_acap_get_encoded(acap, data, &size, &pts);
@@ -336,6 +350,11 @@ static void *_aplay_thread(void *arg) {
} }
if (dev == NULL) { 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); int err = snd_pcm_open(&dev, _g_config->aplay_dev_name, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) { if (err < 0) {
US_ONCE({ US_JLOG_PERROR_ALSA(err, "aplay", "Can't open PCM playback"); }); 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); US_RING_INIT_WITH_ITEMS(_g_video_ring, 64, us_frame_init);
_g_rtpv = us_rtpv_init(_relay_rtp_clients); _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); _g_rtpa = us_rtpa_init(_relay_rtp_clients);
US_THREAD_CREATE(_g_acap_tid, _acap_thread, NULL); US_THREAD_CREATE(_g_acap_tid, _acap_thread, NULL);
if (_g_config->aplay_dev_name != 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"); json_t *const obj = json_object_get(params, "audio");
if (obj != NULL && json_is_boolean(obj)) { 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"); json_t *const obj = json_object_get(params, "mic");
if (obj != NULL && json_is_boolean(obj)) { 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));
} }
} }
{ {
@@ -622,31 +641,13 @@ static struct janus_plugin_result *_plugin_handle_message(
} }
{ {
char *sdp; char *const sdp = us_sdp_create(
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv); _g_rtpv,
char *const audio_sdp = (with_acap ? us_rtpa_make_sdp(_g_rtpa, with_aplay) : us_strdup("")); (with_acap ? _g_rtpa : NULL),
US_ASPRINTF(sdp, (with_acap && with_aplay));
"v=0" RN
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
"s=PiKVM uStreamer" RN
"t=0 0" RN
"%s%s",
us_get_now_id() >> 1,
# if JANUS_PLUGIN_API_VERSION >= 100
// Place video SDP before audio SDP so that the video and audio streams
// have predictable indices, even if audio is not available.
// See also client.c.
video_sdp, audio_sdp
# else
// For versions of Janus prior to 1.x, place the audio SDP first.
audio_sdp, video_sdp
# endif
);
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp); json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
PUSH_STATUS("started", NULL, offer_jsep); PUSH_STATUS("started", NULL, offer_jsep);
json_decref(offer_jsep); json_decref(offer_jsep);
free(audio_sdp);
free(video_sdp);
free(sdp); free(sdp);
} }
@@ -669,10 +670,13 @@ static struct janus_plugin_result *_plugin_handle_message(
} }
} else if (!strcmp(request_str, "features")) { } 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( json_t *const features = json_pack(
"{sbsb}", "{s:b, s:b, s:{s:s?}}",
"audio", (_g_rtpa != NULL), "audio", acap_avail,
"mic", (_g_rtpa != NULL && _g_config->aplay_dev_name != NULL) "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); PUSH_STATUS("features", features, NULL);
json_decref(features); json_decref(features);

View File

@@ -25,10 +25,17 @@
#include "uslibs/types.h" #include "uslibs/types.h"
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes // Max RTP size for WebRTC is 1200 bytes:
#define US_RTP_DATAGRAM_SIZE 1200 // - https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
// But(!) Tailscale has 1200 MTU. So to fit it required to substract:
// 1. possible RTP extensions (see sdp.c)
// 2. additional SRTP fields (>= 10 bytes)
// 3. UDP header (8 bytes)
// 4. IPv6 header (40 bytes)
// Finally it looks like 100 bytes for all above should be enough
#define US_RTP_TOTAL_SIZE (1200 - 100)
#define US_RTP_HEADER_SIZE 12 #define US_RTP_HEADER_SIZE 12
#define US_RTP_PAYLOAD_SIZE (US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE) #define US_RTP_PAYLOAD_SIZE (US_RTP_TOTAL_SIZE - US_RTP_HEADER_SIZE)
#define US_RTP_H264_PAYLOAD 96 #define US_RTP_H264_PAYLOAD 96
#define US_RTP_OPUS_PAYLOAD 111 #define US_RTP_OPUS_PAYLOAD 111
@@ -43,9 +50,13 @@ typedef struct {
u32 ssrc; u32 ssrc;
u16 seq; u16 seq;
u8 datagram[US_RTP_DATAGRAM_SIZE]; u8 datagram[US_RTP_TOTAL_SIZE];
uz used; uz used;
bool first_of_frame;
bool last_of_frame;
bool zero_playout_delay; bool zero_playout_delay;
u64 grab_ntp_ts;
} us_rtp_s; } us_rtp_s;
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp); typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);

View File

@@ -23,11 +23,12 @@
#include "rtpa.h" #include "rtpa.h"
#include <stdlib.h> #include <stdlib.h>
#include <inttypes.h>
#include "uslibs/types.h" #include "uslibs/types.h"
#include "uslibs/tools.h" #include "uslibs/tools.h"
#include "rtp.h"
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) { us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
us_rtpa_s *rtpa; us_rtpa_s *rtpa;
@@ -43,30 +44,8 @@ void us_rtpa_destroy(us_rtpa_s *rtpa) {
free(rtpa); free(rtpa);
} }
char *us_rtpa_make_sdp(us_rtpa_s *rtpa, bool mic) {
const uint pl = rtpa->rtp->payload;
char *sdp;
US_ASPRINTF(sdp,
"m=audio 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u OPUS/%u/%u" RN
"a=fmtp:%u sprop-stereo=1" RN // useinbandfec=1
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=%s" RN,
pl, pl,
US_RTP_OPUS_HZ, US_RTP_OPUS_CH,
pl, pl, pl, pl,
rtpa->rtp->ssrc,
(mic ? "sendrecv" : "sendonly")
);
return sdp;
}
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts) { void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts) {
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) { if (size + US_RTP_HEADER_SIZE <= US_RTP_TOTAL_SIZE) {
us_rtp_write_header(rtpa->rtp, pts, false); us_rtp_write_header(rtpa->rtp, pts, false);
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size); memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);
rtpa->rtp->used = size + US_RTP_HEADER_SIZE; rtpa->rtp->used = size + US_RTP_HEADER_SIZE;

View File

@@ -36,5 +36,4 @@ typedef struct {
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback); us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
void us_rtpa_destroy(us_rtpa_s *rtpa); void us_rtpa_destroy(us_rtpa_s *rtpa);
char *us_rtpa_make_sdp(us_rtpa_s *rtpa, bool mic);
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts); void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts);

View File

@@ -35,6 +35,8 @@
#include "uslibs/tools.h" #include "uslibs/tools.h"
#include "uslibs/frame.h" #include "uslibs/frame.h"
#include "rtp.h"
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked); void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked);
@@ -55,31 +57,6 @@ void us_rtpv_destroy(us_rtpv_s *rtpv) {
free(rtpv); free(rtpv);
} }
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
const uint pl = rtpv->rtp->payload;
char *sdp;
US_ASPRINTF(sdp,
"m=video 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u H264/90000" RN
"a=fmtp:%u profile-level-id=42E01F" RN
"a=fmtp:%u packetization-mode=1" RN
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" 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
"a=sendonly" RN,
pl, pl, pl, pl,
pl, pl, pl,
rtpv->rtp->ssrc
);
return sdp;
}
#define _PRE 3 // Annex B prefix length #define _PRE 3 // Annex B prefix length
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay) { void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay) {
@@ -88,7 +65,10 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_de
assert(frame->format == V4L2_PIX_FMT_H264); assert(frame->format == V4L2_PIX_FMT_H264);
rtpv->rtp->first_of_frame = true;
rtpv->rtp->last_of_frame = false;
rtpv->rtp->zero_playout_delay = zero_playout_delay; rtpv->rtp->zero_playout_delay = zero_playout_delay;
rtpv->rtp->grab_ntp_ts = us_get_now_ntp() - us_ld_to_ntp(us_get_now_monotonic() - frame->grab_begin_ts);
const u32 pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz const u32 pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
sz last_offset = -_PRE; sz last_offset = -_PRE;
@@ -125,11 +105,30 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool
const uint type = data[0] & 0x1F; const uint type = data[0] & 0x1F;
u8 *dg = rtpv->rtp->datagram; u8 *dg = rtpv->rtp->datagram;
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) { // Set *_of_frame flags only for non-SPS/PPS packages
/*
# define CALL_FOR_SERVICE { \
const bool m_fof = rtpv->rtp->first_of_frame; \
const bool m_lof = rtpv->rtp->last_of_frame; \
rtpv->rtp->first_of_frame = false; \
rtpv->rtp->last_of_frame = false; \
rtpv->callback(rtpv->rtp); \
rtpv->rtp->first_of_frame = m_fof; \
rtpv->rtp->last_of_frame = m_lof; \
}
*/
if (size + US_RTP_HEADER_SIZE <= US_RTP_TOTAL_SIZE) {
us_rtp_write_header(rtpv->rtp, pts, marked); us_rtp_write_header(rtpv->rtp, pts, marked);
memcpy(dg + US_RTP_HEADER_SIZE, data, size); memcpy(dg + US_RTP_HEADER_SIZE, data, size);
rtpv->rtp->used = size + US_RTP_HEADER_SIZE; rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
rtpv->callback(rtpv->rtp); // if (type == 7 || type == 8) {
// CALL_FOR_SERVICE;
// } else {*/
rtpv->rtp->last_of_frame = true;
rtpv->callback(rtpv->rtp);
rtpv->rtp->first_of_frame = false;
// }
return; return;
} }
@@ -140,7 +139,7 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool
bool first = true; bool first = true;
while (remaining > 0) { while (remaining > 0) {
sz frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead; sz frag_size = US_RTP_TOTAL_SIZE - fu_overhead;
const bool last = (remaining <= frag_size); const bool last = (remaining <= frag_size);
if (last) { if (last) {
frag_size = remaining; frag_size = remaining;
@@ -161,12 +160,20 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool
memcpy(dg + fu_overhead, src, frag_size); memcpy(dg + fu_overhead, src, frag_size);
rtpv->rtp->used = fu_overhead + frag_size; rtpv->rtp->used = fu_overhead + frag_size;
rtpv->callback(rtpv->rtp); // if (type == 7 || type == 8) {
// CALL_FOR_SERVICE;
// } else {
rtpv->rtp->last_of_frame = last;
rtpv->callback(rtpv->rtp);
rtpv->rtp->first_of_frame = false;
// }
src += frag_size; src += frag_size;
remaining -= frag_size; remaining -= frag_size;
first = false; first = false;
} }
# undef CALL_FOR_SERVICE
} }
static sz _find_annexb(const u8 *data, uz size) { static sz _find_annexb(const u8 *data, uz size) {

View File

@@ -37,5 +37,4 @@ typedef struct {
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback); us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback);
void us_rtpv_destroy(us_rtpv_s *rtpv); void us_rtpv_destroy(us_rtpv_s *rtpv);
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay); void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay);

110
janus/src/sdp.c Normal file
View File

@@ -0,0 +1,110 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
# #
# 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 #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "sdp.h"
#include <inttypes.h>
#include <janus/plugins/plugin.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "rtp.h"
#include "rtpv.h"
#include "rtpa.h"
char *us_sdp_create(us_rtpv_s *rtpv, us_rtpa_s *rtpa, bool mic) {
char *video_sdp;
{
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
const uint pl = rtpv->rtp->payload;
US_ASPRINTF(
video_sdp,
"m=video 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u H264/90000" RN
"a=fmtp:%u profile-level-id=42E01F;packetization-mode=1" RN
"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/sendonly urn:3gpp:video-orientation" RN
"a=extmap:2/sendonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
"a=extmap:3/sendonly http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time" RN
"a=sendonly" RN,
pl, pl, pl, pl, pl, pl,
rtpv->rtp->ssrc);
}
char *audio_sdp;
if (rtpa == NULL) {
audio_sdp = us_strdup("");
} else {
const uint pl = rtpa->rtp->payload;
US_ASPRINTF(
audio_sdp,
"m=audio 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u OPUS/%u/%u" RN
"a=fmtp:%u sprop-stereo=1" RN // useinbandfec=1
"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,
US_RTP_OPUS_HZ, US_RTP_OPUS_CH,
pl, pl, pl, pl,
rtpa->rtp->ssrc,
(mic ? "sendrecv" : "sendonly"));
}
char *sdp;
US_ASPRINTF(sdp,
"v=0" RN
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
"s=PiKVM uStreamer" RN
"t=0 0" RN
"%s%s",
us_get_now_id() >> 1,
# if JANUS_PLUGIN_API_VERSION >= 100
// Place video SDP before audio SDP so that the video and audio streams
// have predictable indices, even if audio is not available.
// See also client.c.
video_sdp, audio_sdp
# else
// For versions of Janus prior to 1.x, place the audio SDP first.
audio_sdp, video_sdp
# endif
);
free(audio_sdp);
free(video_sdp);
return sdp;
}

31
janus/src/sdp.h Normal file
View File

@@ -0,0 +1,31 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
# #
# 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 #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include "uslibs/types.h"
#include "rtpv.h"
#include "rtpa.h"
char *us_sdp_create(us_rtpv_s *rtpv, us_rtpa_s *rtpa, bool mic);

View File

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

View File

@@ -33,7 +33,7 @@ max-line-length = 160
[BASIC] [BASIC]
# Good variable names which should always be accepted, separated by a comma # Good variable names which should always be accepted, separated by a comma
good-names = _, __, x, y, ws, make-html-h, make-ico-h good-names = _, __, i, x, y, ws, make-html-h, make-ico-h
# Regular expression matching correct method names # Regular expression matching correct method names
method-rgx = [a-z_][a-z0-9_]{2,50}$ method-rgx = [a-z_][a-z0-9_]{2,50}$

View File

@@ -3,7 +3,7 @@ envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
skipsdist = true skipsdist = true
[testenv] [testenv]
basepython = python3.13 basepython = python3.14
changedir = /src changedir = /src
[testenv:cppcheck] [testenv:cppcheck]
@@ -15,8 +15,6 @@ commands = cppcheck \
--quiet \ --quiet \
--check-level=exhaustive \ --check-level=exhaustive \
--enable=warning,portability,performance,style \ --enable=warning,portability,performance,style \
--suppress=assignmentInAssert \
--suppress=assertWithSideEffect \
--suppress=variableScope \ --suppress=variableScope \
--inline-suppr \ --inline-suppr \
--library=python \ --library=python \

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.26" "January 2021" .TH USTREAMER-DUMP 1 "version 6.55" "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.26" "November 2020" .TH USTREAMER 1 "version 6.55" "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
@@ -23,7 +23,7 @@ For example, the recommended way of running µStreamer with TC358743-based captu
.RS .RS
\fB\-\-format=uyvy \e\fR # Device input format \fB\-\-format=uyvy \e\fR # Device input format
.nf .nf
\fB\-\-encoder=m2m-image \e\fR # Hardware encoding with V4L2 M2M intraface \fB\-\-encoder=m2m-image \e\fR # Hardware encoding with V4L2 M2M interface
.nf .nf
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder \fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
.nf .nf
@@ -52,7 +52,7 @@ Initial image resolution. Default: 640x480.
.TP .TP
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt .BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
Image format. Image format.
Available: YUYV, YVYU, UYVY, RGB565, RGB24, JPEG; default: YUYV. Available: YUYV, YVYU, UYVY, YUV420, YVU420, RGB565, RGB24, GREY, MJPEG, JPEG; default: YUYV.
.TP .TP
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd .BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
Force TV standard. Force TV standard.
@@ -66,7 +66,7 @@ Available: MMAP, USERPTR; default: MMAP.
Desired FPS. Default: maximum possible. Desired FPS. Default: maximum possible.
.TP .TP
.BR \-z\ \fIN ", " \-\-min\-frame\-size\ \fIN .BR \-z\ \fIN ", " \-\-min\-frame\-size\ \fIN
Drop frames smaller then this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes. Drop frames smaller than this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes.
.TP .TP
.BR \-T ", " \-\-allow\-truncated\-frames .BR \-T ", " \-\-allow\-truncated\-frames
Allows to handle truncated frames. Useful if the device produces incorrect but still acceptable frames. Default: disabled. Allows to handle truncated frames. Useful if the device produces incorrect but still acceptable frames. Default: disabled.
@@ -78,7 +78,7 @@ Suppress repetitive signal source errors. Default: disabled.
Enable DV-timings querying and events processing to automatic resolution change. Default: disabled. Enable DV-timings querying and events processing to automatic resolution change. Default: disabled.
.TP .TP
.BR \-b\ \fIN ", " \-\-buffers\ \fIN .BR \-b\ \fIN ", " \-\-buffers\ \fIN
The number of buffers to receive data from the device. Each buffer may processed using an independent thread. The number of buffers to receive data from the device. Each buffer may be processed using an independent thread.
Default: 2 (the number of CPU cores (but not more than 4) + 1). Default: 2 (the number of CPU cores (but not more than 4) + 1).
.TP .TP
.BR \-w\ \fIN ", " \-\-workers\ \fIN .BR \-w\ \fIN ", " \-\-workers\ \fIN
@@ -253,6 +253,9 @@ Interval between keyframes. Default: 30.
.TP .TP
.BR \-\-h264\-m2m\-device\ \fI/dev/path .BR \-\-h264\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select. 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" .SS "RAW sink options"
.TP .TP
@@ -274,7 +277,10 @@ Timeout for lock. Default: 1.
.SS "Process options" .SS "Process options"
.TP .TP
.BR \-\-exit\-on\-parent\-death .BR \-\-exit\-on\-parent\-death
Exit the program if the parent process is dead. Required \fBHAS_PDEATHSIG\fR feature. Default: disabled. 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 .TP
.BR \-\-exit\-on\-no\-clients \fIsec .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). Exit the program if there have been no stream or sink clients or any HTTP requests in the last N seconds. Default: 0 (disabled).

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer pkgname=ustreamer
pkgver=6.26 pkgver=6.55
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"
@@ -18,7 +18,7 @@ md5sums=(SKIP)
_options="WITH_GPIO=1 WITH_SYSTEMD=1" _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) depends+=("python>=3.14" "python<3.15")
makedepends+=(python-setuptools python-pip python-build python-wheel) makedepends+=(python-setuptools python-pip python-build python-wheel)
fi fi
if [ -e /usr/include/janus/plugins/plugin.h ];then if [ -e /usr/include/janus/plugins/plugin.h ];then

View File

@@ -2,11 +2,14 @@
# This is free software, licensed under the GNU General Public License v2. # This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information. # See /LICENSE for more information.
# #
# This package is just an example. For OpenWRT it is recommended to use upstream package:
# - https://github.com/openwrt/packages/tree/master/multimedia/ustreamer
#
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer PKG_NAME:=ustreamer
PKG_VERSION:=6.26 PKG_VERSION:=6.55
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com> PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
@@ -25,7 +28,7 @@ define Package/ustreamer
SECTION:=multimedia SECTION:=multimedia
CATEGORY:=Multimedia CATEGORY:=Multimedia
TITLE:=uStreamer 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 URL:=https://github.com/pikvm/ustreamer
endef endef

View File

@@ -1,5 +1,3 @@
-include ../config.mk
R_DESTDIR ?= R_DESTDIR ?=
PREFIX ?= /usr/local PREFIX ?= /usr/local
@@ -8,7 +6,7 @@ PY ?= python3
# ===== # =====
all: root all: root
root: $(shell find src -type f,l) root: $(shell find src -type f,l) setup.py
$(info == PY_BUILD ustreamer-*.so) $(info == PY_BUILD ustreamer-*.so)
rm -rf root rm -rf root
$(ECHO) $(PY) -m build --skip-dependency-check --no-isolation $(ECHO) $(PY) -m build --skip-dependency-check --no-isolation

View File

@@ -5,19 +5,36 @@ from setuptools import setup
# ===== # =====
def _find_sources(suffix: str) -> list[str]: def _find_sources() -> list[str]:
sources: list[str] = [] sources: list[str] = []
for (root_path, _, names) in os.walk("src"): for (root_path, _, names) in os.walk("src"):
for name in names: for name in names:
if name.endswith(suffix): if name.endswith(".c"):
sources.append(os.path.join(root_path, name)) sources.append(os.path.join(root_path, name))
return sources return sources
if __name__ == "__main__": def _find_flags() -> dict[str, bool]:
return {
key[3:]: (value.strip().lower() in ["true", "on", "1"])
for (key, value) in sorted(os.environ.items())
if key.startswith("MK_WITH_")
}
def _make_d_features(flags: dict[str, bool]) -> str:
features = " ".join([
f"{key}={int(value)}"
for (key, value) in flags.items()
])
return f"-DUS_FEATURES=\"{features}\""
def main() -> None:
flags = _find_flags()
setup( setup(
name="ustreamer", name="ustreamer",
version="6.26", version="6.55",
description="uStreamer tools", description="uStreamer tools",
author="Maxim Devaev", author="Maxim Devaev",
author_email="mdevaev@gmail.com", author_email="mdevaev@gmail.com",
@@ -26,9 +43,16 @@ if __name__ == "__main__":
Extension( Extension(
"ustreamer", "ustreamer",
libraries=["rt", "m", "pthread"], libraries=["rt", "m", "pthread"],
extra_compile_args=["-std=c17", "-D_GNU_SOURCE"], extra_compile_args=[
"-std=c17", "-D_GNU_SOURCE",
_make_d_features(flags),
],
undef_macros=["NDEBUG"], undef_macros=["NDEBUG"],
sources=_find_sources(".c"), sources=_find_sources(),
), ),
], ],
) )
if __name__ == "__main__":
main()

1
python/src/uslibs/const.h Symbolic link
View File

@@ -0,0 +1 @@
../../../src/libs/const.h

View File

@@ -1,3 +1,25 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
# #
# 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 #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
@@ -13,6 +35,7 @@
#include <Python.h> #include <Python.h>
#include "uslibs/const.h"
#include "uslibs/types.h" #include "uslibs/types.h"
#include "uslibs/errors.h" #include "uslibs/errors.h"
#include "uslibs/tools.h" #include "uslibs/tools.h"
@@ -48,6 +71,8 @@ static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
} }
static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) { static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
self->fd = -1;
self->lock_timeout = 1; self->lock_timeout = 1;
self->wait_timeout = 1; self->wait_timeout = 1;
@@ -228,7 +253,8 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
} \ } \
Py_DECREF(m_tmp); \ Py_DECREF(m_tmp); \
} }
# define SET_NUMBER(x_key, x_from, x_to) SET_VALUE(#x_key, Py##x_to##_From##x_from(self->frame->x_key)) # define SET_NUMBER(x_key, x_from, x_to) \
SET_VALUE(#x_key, Py##x_to##_From##x_from(self->frame->x_key))
SET_NUMBER(width, Long, Long); SET_NUMBER(width, Long, Long);
SET_NUMBER(height, Long, Long); SET_NUMBER(height, Long, Long);
@@ -237,7 +263,8 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
SET_NUMBER(online, Long, Bool); SET_NUMBER(online, Long, Bool);
SET_NUMBER(key, Long, Bool); SET_NUMBER(key, Long, Bool);
SET_NUMBER(gop, Long, Long); 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_begin_ts, Double, Float);
SET_NUMBER(encode_end_ts, Double, Float); SET_NUMBER(encode_end_ts, Double, Float);
SET_VALUE("data", PyBytes_FromStringAndSize((const char*)self->frame->data, self->frame->used)); SET_VALUE("data", PyBytes_FromStringAndSize((const char*)self->frame->data, self->frame->used));
@@ -275,7 +302,8 @@ static PyMethodDef _MemsinkObject_methods[] = {
}; };
static PyGetSetDef _MemsinkObject_getsets[] = { static PyGetSetDef _MemsinkObject_getsets[] = {
# define ADD_GETTER(x_field) {.name = #x_field, .get = (getter)_MemsinkObject_getter_##x_field} # define ADD_GETTER(x_field) \
{.name = #x_field, .get = (getter)_MemsinkObject_getter_##x_field}
ADD_GETTER(obj), ADD_GETTER(obj),
ADD_GETTER(lock_timeout), ADD_GETTER(lock_timeout),
ADD_GETTER(wait_timeout), ADD_GETTER(wait_timeout),
@@ -304,20 +332,30 @@ static PyModuleDef _Module = {
}; };
PyMODINIT_FUNC PyInit_ustreamer(void) { PyMODINIT_FUNC PyInit_ustreamer(void) {
PyObject *module = PyModule_Create(&_Module); PyObject *module = NULL;
if (module == NULL) {
return NULL;
}
if (PyType_Ready(&_MemsinkType) < 0) { if (PyType_Ready(&_MemsinkType) < 0) {
return NULL; goto error;
} }
Py_INCREF(&_MemsinkType); if ((module = PyModule_Create(&_Module)) == NULL) {
goto error;
if (PyModule_AddObject(module, "Memsink", (PyObject*)&_MemsinkType) < 0) {
return NULL;
} }
# define ADD(x_what, x_key, x_value) \
{ if (PyModule_Add##x_what(module, x_key, x_value) < 0) { goto error; } }
ADD(StringConstant, "__version__", US_VERSION);
ADD(StringConstant, "VERSION", US_VERSION);
ADD(IntConstant, "VERSION_MAJOR", US_VERSION_MAJOR);
ADD(IntConstant, "VERSION_MINOR", US_VERSION_MINOR);
ADD(StringConstant, "FEATURES", US_FEATURES); // Defined in setup.py
ADD(ObjectRef, "Memsink", (PyObject*)&_MemsinkType);
# undef ADD
return module; return module;
error:
if (module != NULL) {
Py_DECREF(module);
}
return NULL;
} }

View File

@@ -2,6 +2,7 @@ R_DESTDIR ?=
PREFIX ?= /usr/local PREFIX ?= /usr/local
CC ?= gcc CC ?= gcc
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O3 CFLAGS ?= -O3
LDFLAGS ?= LDFLAGS ?=
@@ -13,9 +14,9 @@ _V4P = ustreamer-v4p.bin
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS) _CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
_USTR_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic -levent -levent_pthreads _USTR_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -levent -levent_pthreads
_DUMP_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic _DUMP_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt
_V4P_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic _V4P_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt
_USTR_SRCS = $(shell ls \ _USTR_SRCS = $(shell ls \
libs/*.c \ libs/*.c \
@@ -40,53 +41,59 @@ _V4P_SRCS = $(shell ls \
_BUILD = build _BUILD = build
_TARGETS = $(_USTR) $(_DUMP) _TARGETS = $(_USTR) $(_DUMP)
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o) _OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
define optbool # =====
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1) ifeq ($(findstring bsd,$(shell $(CC) -dumpmachine)),)
endef override _USTR_LDFLAGS += -latomic
override _DUMP_LDFLAGS += -latomic
override _V4P_LDFLAGS += -latomic
endif
ifneq ($(MK_WITH_PYTHON),)
override _CFLAGS += -DMK_WITH_PYTHON
endif
ifneq ($(call optbool,$(WITH_GPIO)),) ifneq ($(MK_WITH_JANUS),)
override _CFLAGS += -DWITH_GPIO $(shell pkg-config --atleast-version=2 libgpiod 2> /dev/null && echo -DHAVE_GPIOD2) override _CFLAGS += -DMK_WITH_JANUS
endif
ifneq ($(MK_WITH_GPIO),)
override _CFLAGS += -DMK_WITH_GPIO -DWITH_GPIO $(shell $(PKG_CONFIG) --atleast-version=2 libgpiod 2> /dev/null && echo -DHAVE_GPIOD2)
override _USTR_LDFLAGS += -lgpiod override _USTR_LDFLAGS += -lgpiod
override _USTR_SRCS += $(shell ls ustreamer/gpio/*.c) override _USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
endif endif
ifneq ($(MK_WITH_SYSTEMD),)
ifneq ($(call optbool,$(WITH_SYSTEMD)),) override _CFLAGS += -DMK_WITH_SYSTEMD -DWITH_SYSTEMD
override _CFLAGS += -DWITH_SYSTEMD
override _USTR_LDFLAGS += -lsystemd override _USTR_LDFLAGS += -lsystemd
override _USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c) override _USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
endif endif
ifneq ($(MK_WITH_PTHREAD_NP),)
WITH_PTHREAD_NP ?= 1 override _CFLAGS += -DMK_WITH_PTHREAD_NP -DWITH_PTHREAD_NP
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
override _CFLAGS += -DWITH_PTHREAD_NP
endif endif
ifneq ($(MK_WITH_SETPROCTITLE),)
WITH_SETPROCTITLE ?= 1 override _CFLAGS += -DMK_WITH_SETPROCTITLE -DWITH_SETPROCTITLE
ifneq ($(call optbool,$(WITH_SETPROCTITLE)),) ifneq ($(findstring linux,$(shell $(CC) -dumpmachine)),)
override _CFLAGS += -DWITH_SETPROCTITLE
ifeq ($(shell uname -s | tr A-Z a-z),linux)
override _USTR_LDFLAGS += -lbsd override _USTR_LDFLAGS += -lbsd
endif endif
endif endif
ifneq ($(MK_WITH_PDEATHSIG),)
override _CFLAGS += -DMK_WITH_PDEATHSIG -DWITH_PDEATHSIG
endif
WITH_V4P ?= 0 ifneq ($(MK_WITH_V4P),)
ifneq ($(call optbool,$(WITH_V4P)),)
override _TARGETS += $(_V4P) override _TARGETS += $(_V4P)
override _OBJS += $(_V4P_SRCS:%.c=$(_BUILD)/%.o) override _OBJS += $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
override _CFLAGS += -DWITH_V4P $(shell pkg-config --cflags libdrm) override _CFLAGS += -DMK_WITH_V4P -DWITH_V4P $(shell $(PKG_CONFIG) --cflags libdrm)
override _V4P_LDFLAGS += $(shell pkg-config --libs libdrm) override _V4P_LDFLAGS += $(shell $(PKG_CONFIG) --libs libdrm)
override _USTR_SRCS += $(shell ls libs/drm/*.c) override _USTR_SRCS += $(shell ls libs/drm/*.c)
override _USTR_LDFLAGS += $(shell pkg-config --libs libdrm) override _USTR_LDFLAGS += $(shell $(PKG_CONFIG) --libs libdrm)
endif endif

View File

@@ -24,54 +24,73 @@
us_output_file_s *us_output_file_init(const char *path, bool json) { us_output_file_s *us_output_file_init(const char *path, bool json) {
us_output_file_s *output; us_output_file_s *out;
US_CALLOC(output, 1); US_CALLOC(out, 1);
if (!strcmp(path, "-")) { if (!strcmp(path, "-")) {
US_LOG_INFO("Using output: <stdout>"); US_LOG_INFO("Using output: <stdout>");
output->fp = stdout; out->fp = stdout;
} else { } else {
US_LOG_INFO("Using output: %s", path); US_LOG_INFO("Using output: %s", path);
if ((output->fp = fopen(path, "wb")) == NULL) { if ((out->fp = fopen(path, "wb")) == NULL) {
US_LOG_PERROR("Can't open output file"); US_LOG_PERROR("Can't open output file");
goto error; goto error;
} }
} }
output->json = json; out->json = json;
return output; return out;
error: error:
us_output_file_destroy(output); us_output_file_destroy(out);
return NULL; return NULL;
} }
void us_output_file_write(void *v_output, const us_frame_s *frame) { void us_output_file_write(void *v_out, const us_frame_s *frame) {
us_output_file_s *output = v_output; us_output_file_s *out = v_out;
if (output->json) { if (out->json) {
us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated); us_base64_encode(frame->data, frame->used, &out->base64_data, &out->base64_allocated);
fprintf(output->fp, fprintf(
"{\"size\": %zu, \"width\": %u, \"height\": %u," out->fp,
" \"format\": %u, \"stride\": %u, \"online\": %u, \"key\": %u, \"gop\": %u," "{\"size\": %zu,"
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf," " \"width\": %u,"
" \"height\": %u,"
" \"format\": %u,"
" \"stride\": %u,"
" \"online\": %u,"
" \"key\": %u,"
" \"gop\": %u,"
" \"grab_begin_ts\": %.3Lf,"
" \"grab_end_ts\": %.3Lf,"
" \"encode_begin_ts\": %.3Lf,"
" \"encode_end_ts\": %.3Lf,"
" \"data\": \"%s\"}\n", " \"data\": \"%s\"}\n",
frame->used, frame->width, frame->height, frame->used,
frame->format, frame->stride, frame->online, frame->key, frame->gop, frame->width,
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts, frame->height,
output->base64_data); frame->format,
frame->stride,
frame->online,
frame->key,
frame->gop,
frame->grab_begin_ts,
frame->grab_end_ts,
frame->encode_begin_ts,
frame->encode_end_ts,
out->base64_data);
} else { } else {
fwrite(frame->data, 1, frame->used, output->fp); fwrite(frame->data, 1, frame->used, out->fp);
} }
fflush(output->fp); fflush(out->fp);
} }
void us_output_file_destroy(void *v_output) { void us_output_file_destroy(void *v_out) {
us_output_file_s *output = v_output; us_output_file_s *out = v_out;
US_DELETE(output->base64_data, free); US_DELETE(out->base64_data, free);
if (output->fp && output->fp != stdout) { if (out->fp && out->fp != stdout) {
if (fclose(output->fp) < 0) { if (fclose(out->fp) < 0) {
US_LOG_PERROR("Can't close output file"); US_LOG_PERROR("Can't close output file");
} }
} }
free(output); free(out);
} }

View File

@@ -45,5 +45,5 @@ typedef struct {
us_output_file_s *us_output_file_init(const char *path, bool json); us_output_file_s *us_output_file_init(const char *path, bool json);
void us_output_file_write(void *v_output, const us_frame_s *frame); void us_output_file_write(void *v_out, const us_frame_s *frame);
void us_output_file_destroy(void *v_output); void us_output_file_destroy(void *v_out);

View File

@@ -28,7 +28,6 @@
#include <float.h> #include <float.h>
#include <getopt.h> #include <getopt.h>
#include <errno.h> #include <errno.h>
#include <assert.h>
#include "../libs/const.h" #include "../libs/const.h"
#include "../libs/errors.h" #include "../libs/errors.h"
@@ -90,17 +89,19 @@ volatile bool _g_stop = false;
typedef struct { typedef struct {
void *v_output; void *v_out;
void (*write)(void *v_output, const us_frame_s *frame); void (*write)(void *v_out, const us_frame_s *frame);
void (*destroy)(void *v_output); void (*destroy)(void *v_out);
} _output_context_s; } _output_context_s;
static void _signal_handler(int signum); static void _signal_handler(int signum);
static int _dump_sink( static int _dump_sink(
const char *sink_name, unsigned sink_timeout, const char *sink_name,
long long count, long double interval, unsigned sink_timeout,
long long count,
long double interval,
bool key_required, bool key_required,
_output_context_s *ctx); _output_context_s *ctx);
@@ -113,8 +114,8 @@ int main(int argc, char *argv[]) {
const char *sink_name = NULL; const char *sink_name = NULL;
unsigned sink_timeout = 1; unsigned sink_timeout = 1;
const char *output_path = NULL; const char *out_path = NULL;
bool output_json = false; bool out_json = false;
long long count = 0; long long count = 0;
long double interval = 0; long double interval = 0;
bool key_required = false; bool key_required = false;
@@ -151,8 +152,8 @@ int main(int argc, char *argv[]) {
switch (ch) { switch (ch) {
case _O_SINK: OPT_SET(sink_name, optarg); case _O_SINK: OPT_SET(sink_name, optarg);
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0); case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
case _O_OUTPUT: OPT_SET(output_path, optarg); case _O_OUTPUT: OPT_SET(out_path, optarg);
case _O_OUTPUT_JSON: OPT_SET(output_json, true); case _O_OUTPUT_JSON: OPT_SET(out_json, true);
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0); case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60); case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
case _O_KEY_REQUIRED: OPT_SET(key_required, true); case _O_KEY_REQUIRED: OPT_SET(key_required, true);
@@ -183,8 +184,8 @@ int main(int argc, char *argv[]) {
_output_context_s ctx = {0}; _output_context_s ctx = {0};
if (output_path && output_path[0] != '\0') { if (out_path && out_path[0] != '\0') {
if ((ctx.v_output = (void*)us_output_file_init(output_path, output_json)) == NULL) { if ((ctx.v_out = (void*)us_output_file_init(out_path, out_json)) == NULL) {
return 1; return 1;
} }
ctx.write = us_output_file_write; ctx.write = us_output_file_write;
@@ -193,8 +194,8 @@ int main(int argc, char *argv[]) {
us_install_signals_handler(_signal_handler, false); us_install_signals_handler(_signal_handler, false);
const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, key_required, &ctx)); const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, key_required, &ctx));
if (ctx.v_output && ctx.destroy) { if (ctx.v_out && ctx.destroy) {
ctx.destroy(ctx.v_output); ctx.destroy(ctx.v_out);
} }
return retval; return retval;
} }
@@ -208,10 +209,13 @@ static void _signal_handler(int signum) {
} }
static int _dump_sink( static int _dump_sink(
const char *sink_name, unsigned sink_timeout, const char *sink_name,
long long count, long double interval, unsigned sink_timeout,
long long count,
long double interval,
bool key_required, bool key_required,
_output_context_s *ctx) { _output_context_s *ctx
) {
int retval = -1; int retval = -1;
@@ -240,21 +244,27 @@ static int _dump_sink(
const long double now = us_get_now_monotonic(); const long double now = us_get_now_monotonic();
char fourcc_str[8]; 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), us_fourcc_to_string(frame->format, fourcc_str, 8),
frame->width, frame->height, frame->width,
frame->online, frame->key, key_requested, frame->gop, frame->height,
now - frame->grab_ts, (last_ts ? now - last_ts : 0), 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); frame->used);
last_ts = now;
US_LOG_DEBUG(" stride=%u, grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf", last_ts = now;
frame->stride, frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
us_fpsi_update(fpsi, true, NULL); us_fpsi_update(fpsi, true, NULL);
if (ctx->v_output != NULL) { if (ctx->v_out != NULL) {
ctx->write(ctx->v_output, frame); ctx->write(ctx->v_out, frame);
} }
if (count >= 0) { if (count >= 0) {

View File

@@ -22,16 +22,16 @@
#pragma once #pragma once
#include <assert.h> #include "tools.h"
#define US_ARRAY_LEN(x_array) (sizeof(x_array) / sizeof((x_array)[0])) #define US_ARRAY_LEN(x_array) (sizeof(x_array) / sizeof((x_array)[0]))
#define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \ #define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \
const int m_len = US_ARRAY_LEN(x_array); \ const int m_len = US_ARRAY_LEN(x_array); \
assert(x_start <= m_len); \ US_A(x_start <= m_len); \
for (int m_index = x_start; m_index < m_len; ++m_index) { \ for (int m_i = x_start; m_i < m_len; ++m_i) { \
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_index]; \ __typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_i]; \
__VA_ARGS__ \ __VA_ARGS__ \
} \ } \
} }

View File

@@ -54,8 +54,8 @@ void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated) {
} }
} }
for (uint data_index = 0, encoded_index = 0; data_index < size;) { for (uint data_i = 0, encoded_i = 0; data_i < size;) {
# define OCTET(_name) uint _name = (data_index < size ? (u8)data[data_index++] : 0) # define OCTET(_name) uint _name = (data_i < size ? (u8)data[data_i++] : 0)
OCTET(octet_a); OCTET(octet_a);
OCTET(octet_b); OCTET(octet_b);
OCTET(octet_c); OCTET(octet_c);
@@ -63,7 +63,7 @@ void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated) {
const uint triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; const uint triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F] # define ENCODE(_offset) (*encoded)[encoded_i++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
ENCODE(3); ENCODE(3);
ENCODE(2); ENCODE(2);
ENCODE(1); ENCODE(1);

View File

@@ -30,7 +30,6 @@
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <errno.h> #include <errno.h>
#include <assert.h>
#include <sys/select.h> #include <sys/select.h>
#include <sys/mman.h> #include <sys/mman.h>
@@ -48,6 +47,7 @@
#include "threading.h" #include "threading.h"
#include "frame.h" #include "frame.h"
#include "xioctl.h" #include "xioctl.h"
#include "tc358743.h"
static const struct { static const struct {
@@ -67,6 +67,9 @@ static const struct {
{"YUYV", V4L2_PIX_FMT_YUYV}, {"YUYV", V4L2_PIX_FMT_YUYV},
{"YVYU", V4L2_PIX_FMT_YVYU}, {"YVYU", V4L2_PIX_FMT_YVYU},
{"UYVY", V4L2_PIX_FMT_UYVY}, {"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}, {"RGB565", V4L2_PIX_FMT_RGB565},
{"RGB24", V4L2_PIX_FMT_RGB24}, {"RGB24", V4L2_PIX_FMT_RGB24},
{"BGR24", V4L2_PIX_FMT_BGR24}, {"BGR24", V4L2_PIX_FMT_BGR24},
@@ -99,12 +102,21 @@ static int _capture_open_export_to_dma(us_capture_s *cap);
static int _capture_apply_resolution(us_capture_s *cap, uint width, uint height, float hz); static int _capture_apply_resolution(us_capture_s *cap, uint width, uint height, float hz);
static void _capture_apply_controls(const us_capture_s *cap); static void _capture_apply_controls(const us_capture_s *cap);
static int _capture_query_control( static int _capture_query_control(
const us_capture_s *cap, struct v4l2_queryctrl *query, const us_capture_s *cap,
const char *name, uint cid, bool quiet); struct v4l2_queryctrl *query,
const char *name,
uint cid,
bool quiet);
static void _capture_set_control( static void _capture_set_control(
const us_capture_s *cap, const struct v4l2_queryctrl *query, const us_capture_s *cap,
const char *name, uint cid, int value, bool quiet); const struct v4l2_queryctrl *query,
const char *name,
uint cid,
int value,
bool quiet);
static const char *_format_to_string_nullable(uint format); static const char *_format_to_string_nullable(uint format);
static const char *_format_to_string_supported(uint format); static const char *_format_to_string_supported(uint format);
@@ -190,12 +202,18 @@ int us_capture_open(us_capture_s *cap) {
_LOG_DEBUG("Capture device fd=%d opened", run->fd); _LOG_DEBUG("Capture device fd=%d opened", run->fd);
if (cap->dv_timings && cap->persistent) { 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 ..."); _LOG_DEBUG("Probing DV-timings or QuerySTD ...");
if (_capture_open_dv_timings(cap, false) < 0) { switch (_capture_open_dv_timings(cap, false)) {
US_ONCE_FOR(run->open_error_once, __LINE__, { case 0: break;
_LOG_ERROR("No signal from source"); case US_ERROR_NO_SIGNAL: goto error_no_signal;
}); case US_ERROR_NO_SYNC: goto error_no_sync;
goto error_no_signal; default: goto error;
} }
} }
@@ -213,6 +231,15 @@ int us_capture_open(us_capture_s *cap) {
if (_capture_open_format(cap, true) < 0) { if (_capture_open_format(cap, true) < 0) {
goto error; 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_hw_fps(cap);
_capture_open_jpeg_quality(cap); _capture_open_jpeg_quality(cap);
if (_capture_open_io_method(cap) < 0) { if (_capture_open_io_method(cap) < 0) {
@@ -245,9 +272,23 @@ error_no_device:
us_capture_close(cap); us_capture_close(cap);
return US_ERROR_NO_DEVICE; return US_ERROR_NO_DEVICE;
error_no_signal: error_no_cable:
us_capture_close(cap); 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: error:
run->open_error_once = 0; run->open_error_once = 0;
@@ -273,15 +314,15 @@ void us_capture_close(us_capture_s *cap) {
if (run->bufs != NULL) { if (run->bufs != NULL) {
say = true; say = true;
_LOG_DEBUG("Releasing HW buffers ..."); _LOG_DEBUG("Releasing HW buffers ...");
for (uint index = 0; index < run->n_bufs; ++index) { for (uint i = 0; i < run->n_bufs; ++i) {
us_capture_hwbuf_s *hw = &run->bufs[index]; us_capture_hwbuf_s *hw = &run->bufs[i];
US_CLOSE_FD(hw->dma_fd); US_CLOSE_FD(hw->dma_fd);
if (cap->io_method == V4L2_MEMORY_MMAP) { if (cap->io_method == V4L2_MEMORY_MMAP) {
if (hw->raw.allocated > 0 && hw->raw.data != NULL) { if (hw->raw.allocated > 0 && hw->raw.data != NULL) {
if (munmap(hw->raw.data, hw->raw.allocated) < 0) { if (munmap(hw->raw.data, hw->raw.allocated) < 0) {
_LOG_PERROR("Can't unmap HW buffer=%u", index); _LOG_PERROR("Can't unmap HW buffer=%u", i);
} }
} }
} else { // V4L2_MEMORY_USERPTR } else { // V4L2_MEMORY_USERPTR
@@ -414,23 +455,30 @@ int us_capture_hwbuf_grab(us_capture_s *cap, us_capture_hwbuf_s **hw) {
(*hw)->raw.stride = run->stride; (*hw)->raw.stride = run->stride;
(*hw)->raw.online = true; (*hw)->raw.online = true;
_v4l2_buffer_copy(&buf, &(*hw)->buf); _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; return buf.index;
} }
int us_capture_hwbuf_release(const us_capture_s *cap, us_capture_hwbuf_s *hw) { int us_capture_hwbuf_release(const us_capture_s *cap, us_capture_hwbuf_s *hw) {
assert(atomic_load(&hw->refs) == 0); US_A(atomic_load(&hw->refs) == 0);
const uint index = hw->buf.index; const uint i = hw->buf.index;
_LOG_DEBUG("Releasing HW buffer=%u ...", index); _LOG_DEBUG("Releasing HW buffer=%u ...", i);
if (us_xioctl(cap->run->fd, VIDIOC_QBUF, &hw->buf) < 0) { if (us_xioctl(cap->run->fd, VIDIOC_QBUF, &hw->buf) < 0) {
_LOG_PERROR("Can't release HW buffer=%u", index); _LOG_PERROR("Can't release HW buffer=%u", i);
return -1; return -1;
} }
hw->grabbed = false; hw->grabbed = false;
_LOG_DEBUG("HW buffer=%u released", index); _LOG_DEBUG("HW buffer=%u released", i);
return 0; return 0;
} }
@@ -507,7 +555,7 @@ static void _v4l2_buffer_copy(const struct v4l2_buffer *src, struct v4l2_buffer
struct v4l2_plane *dest_planes = dest->m.planes; struct v4l2_plane *dest_planes = dest->m.planes;
memcpy(dest, src, sizeof(struct v4l2_buffer)); memcpy(dest, src, sizeof(struct v4l2_buffer));
if (src->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { if (src->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
assert(dest_planes); US_A(dest_planes);
dest->m.planes = dest_planes; dest->m.planes = dest_planes;
memcpy(dest->m.planes, src->m.planes, sizeof(struct v4l2_plane) * VIDEO_MAX_PLANES); memcpy(dest->m.planes, src->m.planes, sizeof(struct v4l2_plane) * VIDEO_MAX_PLANES);
} }
@@ -536,19 +584,28 @@ bool _capture_is_buffer_valid(const us_capture_s *cap, const struct v4l2_buffer
if (us_is_jpeg(cap->run->format)) { if (us_is_jpeg(cap->run->format)) {
if (buf->bytesused < 125) { if (buf->bytesused < 125) {
// https://stackoverflow.com/questions/2253404/what-is-the-smallest-valid-jpeg-file-size-in-bytes // 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; return false;
} }
const u8 *const end_ptr = data + buf->bytesused; const u16 begin_marker = (((u16)(data[0]) << 8) | data[1]);
const u8 *const eoi_ptr = end_ptr - 2; if (begin_marker != 0xFFD8) {
const u16 eoi_marker = (((u16)(eoi_ptr[0]) << 8) | eoi_ptr[1]); _LOG_DEBUG("Discarding JPEG frame with invalid header: begin_marker=0x%04x, bytesused=%u",
if (eoi_marker != 0xFFD9 && eoi_marker != 0xD900 && eoi_marker != 0x0000) { 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) { 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; 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);
} }
} }
@@ -617,6 +674,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): // TC358743 errors here (see in the kernel: drivers/media/i2c/tc358743.c):
// - ENOLINK: No valid signal (SYS_STATUS & MASK_S_TMDS) // - ENOLINK: No valid signal (SYS_STATUS & MASK_S_TMDS)
// - ENOLCK: No sync on signal (SYS_STATUS & MASK_S_SYNC) // - 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; dv_errno = errno;
goto querystd; goto querystd;
} else if (!apply) { } else if (!apply) {
@@ -779,10 +840,8 @@ static int _capture_open_format(us_capture_s *cap, bool first) {
return 0; return 0;
} }
static void _capture_open_hw_fps(us_capture_s *cap) { static void _capture_open_hw_fps(us_capture_s *cap) { // cppcheck-suppress constParameterPointer
us_capture_runtime_s *const run = cap->run; const us_capture_runtime_s *const run = cap->run;
run->hw_fps = 0;
struct v4l2_streamparm setfps = {.type = run->capture_type}; struct v4l2_streamparm setfps = {.type = run->capture_type};
_LOG_DEBUG("Querying HW FPS ..."); _LOG_DEBUG("Querying HW FPS ...");
@@ -800,36 +859,31 @@ static void _capture_open_hw_fps(us_capture_s *cap) {
return; return;
} }
# define SETFPS_TPF(x_next) setfps.parm.capture.timeperframe.x_next # define TPF(x_next) setfps.parm.capture.timeperframe.x_next
US_MEMSET_ZERO(setfps); US_MEMSET_ZERO(setfps);
setfps.type = run->capture_type; setfps.type = run->capture_type;
SETFPS_TPF(numerator) = 1; TPF(numerator) = 1;
SETFPS_TPF(denominator) = (cap->desired_fps == 0 ? 255 : cap->desired_fps); TPF(denominator) = -1; // Request maximum possible FPS
if (us_xioctl(run->fd, VIDIOC_S_PARM, &setfps) < 0) { if (us_xioctl(run->fd, VIDIOC_S_PARM, &setfps) < 0) {
_LOG_PERROR("Can't set HW FPS"); _LOG_PERROR("Can't set HW FPS");
return; return;
} }
if (SETFPS_TPF(numerator) != 1) { if (TPF(numerator) != 1) {
_LOG_ERROR("Invalid HW FPS numerator: %u != 1", SETFPS_TPF(numerator)); _LOG_ERROR("Invalid HW FPS numerator: %u != 1", TPF(numerator));
return; return;
} }
if (SETFPS_TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай if (TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай
_LOG_ERROR("Invalid HW FPS denominator: 0"); _LOG_ERROR("Invalid HW FPS denominator: 0");
return; return;
} }
run->hw_fps = SETFPS_TPF(denominator); _LOG_INFO("Using HW FPS: %u/%u", TPF(numerator), 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);
}
# undef SETFPS_TPF # undef TPF
} }
static void _capture_open_jpeg_quality(us_capture_s *cap) { static void _capture_open_jpeg_quality(us_capture_s *cap) {
@@ -856,7 +910,7 @@ static int _capture_open_io_method(us_capture_s *cap) {
switch (cap->io_method) { switch (cap->io_method) {
case V4L2_MEMORY_MMAP: return _capture_open_io_method_mmap(cap); case V4L2_MEMORY_MMAP: return _capture_open_io_method_mmap(cap);
case V4L2_MEMORY_USERPTR: return _capture_open_io_method_userptr(cap); case V4L2_MEMORY_USERPTR: return _capture_open_io_method_userptr(cap);
default: assert(0 && "Unsupported IO method"); default: US_RAISE("Unsupported IO method");
} }
return -1; return -1;
} }
@@ -917,7 +971,7 @@ static int _capture_open_io_method_mmap(us_capture_s *cap) {
_LOG_PERROR("Can't map device buffer=%u", run->n_bufs); _LOG_PERROR("Can't map device buffer=%u", run->n_bufs);
return -1; return -1;
} }
assert(hw->raw.data != NULL); US_A(hw->raw.data != NULL);
hw->raw.allocated = buf_size; hw->raw.allocated = buf_size;
if (run->capture_mplane) { if (run->capture_mplane) {
@@ -959,7 +1013,7 @@ static int _capture_open_io_method_userptr(us_capture_s *cap) {
for (run->n_bufs = 0; run->n_bufs < req.count; ++run->n_bufs) { for (run->n_bufs = 0; run->n_bufs < req.count; ++run->n_bufs) {
us_capture_hwbuf_s *hw = &run->bufs[run->n_bufs]; us_capture_hwbuf_s *hw = &run->bufs[run->n_bufs];
assert((hw->raw.data = aligned_alloc(page_size, buf_size)) != NULL); US_A((hw->raw.data = aligned_alloc(page_size, buf_size)) != NULL);
memset(hw->raw.data, 0, buf_size); memset(hw->raw.data, 0, buf_size);
hw->raw.allocated = buf_size; hw->raw.allocated = buf_size;
if (run->capture_mplane) { if (run->capture_mplane) {
@@ -972,12 +1026,12 @@ static int _capture_open_io_method_userptr(us_capture_s *cap) {
static int _capture_open_queue_buffers(us_capture_s *cap) { static int _capture_open_queue_buffers(us_capture_s *cap) {
us_capture_runtime_s *const run = cap->run; us_capture_runtime_s *const run = cap->run;
for (uint index = 0; index < run->n_bufs; ++index) { for (uint i = 0; i < run->n_bufs; ++i) {
struct v4l2_buffer buf = {0}; struct v4l2_buffer buf = {0};
struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0};
buf.type = run->capture_type; buf.type = run->capture_type;
buf.memory = cap->io_method; buf.memory = cap->io_method;
buf.index = index; buf.index = i;
if (run->capture_mplane) { if (run->capture_mplane) {
buf.m.planes = planes; buf.m.planes = planes;
buf.length = 1; buf.length = 1;
@@ -986,11 +1040,11 @@ static int _capture_open_queue_buffers(us_capture_s *cap) {
if (cap->io_method == V4L2_MEMORY_USERPTR) { if (cap->io_method == V4L2_MEMORY_USERPTR) {
// I am not sure, may be this is incorrect for mplane device, // I am not sure, may be this is incorrect for mplane device,
// but i don't have one which supports V4L2_MEMORY_USERPTR // but i don't have one which supports V4L2_MEMORY_USERPTR
buf.m.userptr = (unsigned long)run->bufs[index].raw.data; buf.m.userptr = (unsigned long)run->bufs[i].raw.data;
buf.length = run->bufs[index].raw.allocated; buf.length = run->bufs[i].raw.allocated;
} }
_LOG_DEBUG("Calling us_xioctl(VIDIOC_QBUF) for buffer=%u ...", index); _LOG_DEBUG("Calling us_xioctl(VIDIOC_QBUF) for buffer=%u ...", i);
if (us_xioctl(run->fd, VIDIOC_QBUF, &buf) < 0) { if (us_xioctl(run->fd, VIDIOC_QBUF, &buf) < 0) {
_LOG_PERROR("Can't VIDIOC_QBUF"); _LOG_PERROR("Can't VIDIOC_QBUF");
return -1; return -1;
@@ -1002,23 +1056,23 @@ static int _capture_open_queue_buffers(us_capture_s *cap) {
static int _capture_open_export_to_dma(us_capture_s *cap) { static int _capture_open_export_to_dma(us_capture_s *cap) {
us_capture_runtime_s *const run = cap->run; us_capture_runtime_s *const run = cap->run;
for (uint index = 0; index < run->n_bufs; ++index) { for (uint i = 0; i < run->n_bufs; ++i) {
struct v4l2_exportbuffer exp = { struct v4l2_exportbuffer exp = {
.type = run->capture_type, .type = run->capture_type,
.index = index, .index = i,
}; };
_LOG_DEBUG("Exporting device buffer=%u to DMA ...", index); _LOG_DEBUG("Exporting device buffer=%u to DMA ...", i);
if (us_xioctl(run->fd, VIDIOC_EXPBUF, &exp) < 0) { if (us_xioctl(run->fd, VIDIOC_EXPBUF, &exp) < 0) {
_LOG_PERROR("Can't export device buffer=%u to DMA", index); _LOG_PERROR("Can't export device buffer=%u to DMA", i);
goto error; goto error;
} }
run->bufs[index].dma_fd = exp.fd; run->bufs[i].dma_fd = exp.fd;
} }
return 0; return 0;
error: error:
for (uint index = 0; index < run->n_bufs; ++index) { for (uint i = 0; i < run->n_bufs; ++i) {
US_CLOSE_FD(run->bufs[index].dma_fd); US_CLOSE_FD(run->bufs[i].dma_fd);
} }
return -1; return -1;
} }
@@ -1097,9 +1151,12 @@ static void _capture_apply_controls(const us_capture_s *cap) {
} }
static int _capture_query_control( static int _capture_query_control(
const us_capture_s *cap, struct v4l2_queryctrl *query, const us_capture_s *cap,
const char *name, uint cid, bool quiet) { struct v4l2_queryctrl *query,
const char *name,
uint cid,
bool quiet
) {
// cppcheck-suppress redundantPointerOp // cppcheck-suppress redundantPointerOp
US_MEMSET_ZERO(*query); US_MEMSET_ZERO(*query);
query->id = cid; query->id = cid;
@@ -1114,9 +1171,13 @@ static int _capture_query_control(
} }
static void _capture_set_control( static void _capture_set_control(
const us_capture_s *cap, const struct v4l2_queryctrl *query, const us_capture_s *cap,
const char *name, uint cid, int value, bool quiet) { const struct v4l2_queryctrl *query,
const char *name,
uint cid,
int value,
bool quiet
) {
if (value < query->minimum || value > query->maximum || value % query->step != 0) { if (value < query->minimum || value > query->maximum || value % query->step != 0) {
if (!quiet) { if (!quiet) {
_LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u", _LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",

View File

@@ -39,7 +39,7 @@
#define US_VIDEO_MAX_FPS ((uint)120) #define US_VIDEO_MAX_FPS ((uint)120)
#define US_STANDARDS_STR "PAL, NTSC, SECAM" #define US_STANDARDS_STR "PAL, NTSC, SECAM"
#define US_FORMATS_STR "YUYV, YVYU, UYVY, 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" #define US_IO_METHODS_STR "MMAP, USERPTR"
@@ -58,7 +58,6 @@ typedef struct {
uint format; uint format;
uint stride; uint stride;
float hz; float hz;
uint hw_fps;
uint jpeg_quality; uint jpeg_quality;
uz raw_size; uz raw_size;
uint n_bufs; uint n_bufs;
@@ -113,7 +112,6 @@ typedef struct {
uint n_bufs; uint n_bufs;
bool dma_export; bool dma_export;
bool dma_required; bool dma_required;
uint desired_fps;
uz min_frame_size; uz min_frame_size;
bool allow_truncated_frames; bool allow_truncated_frames;
bool persistent; bool persistent;

View File

@@ -26,7 +26,7 @@
#define US_VERSION_MAJOR 6 #define US_VERSION_MAJOR 6
#define US_VERSION_MINOR 26 #define US_VERSION_MINOR 55
#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

@@ -28,7 +28,9 @@
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/sysmacros.h> #ifdef __linux__
# include <sys/sysmacros.h>
#endif
#include <linux/videodev2.h> #include <linux/videodev2.h>
@@ -59,11 +61,11 @@ static const char *_connector_type_to_string(u32 type);
static float _get_refresh_rate(const drmModeModeInfo *mode); static float _get_refresh_rate(const drmModeModeInfo *mode);
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__) #define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__) #define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__) #define _LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__) #define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__) #define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
us_drm_s *us_drm_init(void) { us_drm_s *us_drm_init(void) {
@@ -97,7 +99,7 @@ void us_drm_destroy(us_drm_s *drm) {
int us_drm_open(us_drm_s *drm, const us_capture_s *cap) { int us_drm_open(us_drm_s *drm, const us_capture_s *cap) {
us_drm_runtime_s *const run = drm->run; us_drm_runtime_s *const run = drm->run;
assert(run->fd < 0); US_A(run->fd < 0);
switch (_drm_check_status(drm)) { switch (_drm_check_status(drm)) {
case 0: break; case 0: break;
@@ -162,7 +164,12 @@ int us_drm_open(us_drm_s *drm, const us_capture_s *cap) {
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id); run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
_LOG_DEBUG("Setting up CRTC ..."); _LOG_DEBUG("Setting up CRTC ...");
if (drmModeSetCrtc(run->fd, run->crtc_id, run->bufs[0].id, 0, 0, &run->conn_id, 1, &run->mode) < 0) { if (drmModeSetCrtc(
run->fd,
run->crtc_id, run->bufs[0].id,
0, 0, // X, Y
&run->conn_id, 1, &run->mode
) < 0) {
_LOG_PERROR("Can't set CRTC"); _LOG_PERROR("Can't set CRTC");
goto error; goto error;
} }
@@ -193,14 +200,15 @@ void us_drm_close(us_drm_s *drm) {
if (run->exposing_dma_fd >= 0) { if (run->exposing_dma_fd >= 0) {
// Нужно подождать, пока dma_fd не освободится, прежде чем прерывать процесс. // Нужно подождать, пока dma_fd не освободится, прежде чем прерывать процесс.
// Просто на всякий случай. // Просто на всякий случай.
assert(run->fd >= 0); US_A(run->fd >= 0);
us_drm_wait_for_vsync(drm); us_drm_wait_for_vsync(drm);
run->exposing_dma_fd = -1; run->exposing_dma_fd = -1;
} }
if (run->saved_crtc != NULL) { if (run->saved_crtc != NULL) {
_LOG_DEBUG("Restoring CRTC ..."); _LOG_DEBUG("Restoring CRTC ...");
if (drmModeSetCrtc(run->fd, if (drmModeSetCrtc(
run->fd,
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id, run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
run->saved_crtc->x, run->saved_crtc->y, run->saved_crtc->x, run->saved_crtc->y,
&run->conn_id, 1, &run->saved_crtc->mode &run->conn_id, 1, &run->saved_crtc->mode
@@ -250,8 +258,8 @@ void us_drm_close(us_drm_s *drm) {
int us_drm_ensure_no_signal(us_drm_s *drm) { int us_drm_ensure_no_signal(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run; us_drm_runtime_s *const run = drm->run;
assert(run->fd >= 0); US_A(run->fd >= 0);
assert(run->opened > 0); US_A(run->opened > 0);
const ldf now_ts = us_get_now_monotonic(); const ldf now_ts = us_get_now_monotonic();
if (run->blank_at_ts == 0) { if (run->blank_at_ts == 0) {
@@ -276,7 +284,7 @@ int us_drm_ensure_no_signal(us_drm_s *drm) {
} }
int us_drm_dpms_power_off(us_drm_s *drm) { int us_drm_dpms_power_off(us_drm_s *drm) {
assert(drm->run->fd >= 0); US_A(drm->run->fd >= 0);
switch (_drm_check_status(drm)) { switch (_drm_check_status(drm)) {
case 0: break; case 0: break;
case US_ERROR_NO_DEVICE: return 0; // Unplugged, nice case US_ERROR_NO_DEVICE: return 0; // Unplugged, nice
@@ -292,7 +300,7 @@ int us_drm_dpms_power_off(us_drm_s *drm) {
int us_drm_wait_for_vsync(us_drm_s *drm) { int us_drm_wait_for_vsync(us_drm_s *drm) {
us_drm_runtime_s *const run = drm->run; us_drm_runtime_s *const run = drm->run;
assert(run->fd >= 0); US_A(run->fd >= 0);
run->blank_at_ts = 0; run->blank_at_ts = 0;
switch (_drm_check_status(drm)) { switch (_drm_check_status(drm)) {
@@ -347,8 +355,8 @@ static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *cap) { int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *cap) {
us_drm_runtime_s *const run = drm->run; us_drm_runtime_s *const run = drm->run;
assert(run->fd >= 0); US_A(run->fd >= 0);
assert(run->opened > 0); US_A(run->opened > 0);
run->blank_at_ts = 0; run->blank_at_ts = 0;
switch (_drm_check_status(drm)) { switch (_drm_check_status(drm)) {
@@ -361,7 +369,7 @@ int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *ca
# define DRAW_MSG(x_msg) us_frametext_draw(run->ft, (x_msg), run->mode.hdisplay, run->mode.vdisplay) # define DRAW_MSG(x_msg) us_frametext_draw(run->ft, (x_msg), run->mode.hdisplay, run->mode.vdisplay)
switch (stub) { switch (stub) {
case US_DRM_STUB_BAD_RESOLUTION: { case US_DRM_STUB_BAD_RESOLUTION: {
assert(cap != NULL); US_A(cap != NULL);
char msg[1024]; char msg[1024];
US_SNPRINTF(msg, 1023, US_SNPRINTF(msg, 1023,
"=== PiKVM ===" "=== PiKVM ==="
@@ -376,7 +384,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 >"); DRAW_MSG("=== PiKVM ===\n \n< UNSUPPORTED CAPTURE FORMAT >");
break; break;
case US_DRM_STUB_NO_SIGNAL: case US_DRM_STUB_NO_SIGNAL:
DRAW_MSG("=== PiKVM ===\n \n< NO SIGNAL >"); DRAW_MSG("=== PiKVM ===\n \n< NO LIVE VIDEO >");
break; break;
case US_DRM_STUB_BUSY: case US_DRM_STUB_BUSY:
DRAW_MSG("=== PiKVM ===\n \n< ONLINE IS ACTIVE >"); DRAW_MSG("=== PiKVM ===\n \n< ONLINE IS ACTIVE >");
@@ -412,8 +420,8 @@ int us_drm_expose_dma(us_drm_s *drm, const us_capture_hwbuf_s *hw) {
us_drm_runtime_s *const run = drm->run; us_drm_runtime_s *const run = drm->run;
us_drm_buffer_s *const buf = &run->bufs[hw->buf.index]; us_drm_buffer_s *const buf = &run->bufs[hw->buf.index];
assert(run->fd >= 0); US_A(run->fd >= 0);
assert(run->opened == 0); US_A(run->opened == 0);
run->blank_at_ts = 0; run->blank_at_ts = 0;
switch (_drm_check_status(drm)) { switch (_drm_check_status(drm)) {
@@ -664,6 +672,15 @@ static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint
continue; // Discard interlaced continue; // Discard interlaced
} }
const float mode_hz = _get_refresh_rate(mode); 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) { if (mode->hdisplay == width && mode->vdisplay == height) {
best = mode; // Any mode with exact resolution best = mode; // Any mode with exact resolution
if (hz > 0 && mode_hz == hz) { if (hz > 0 && mode_hz == hz) {
@@ -689,8 +706,8 @@ static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint
if (best == NULL) { if (best == NULL) {
best = (conn->count_modes > 0 ? &conn->modes[0] : NULL); best = (conn->count_modes > 0 ? &conn->modes[0] : NULL);
} }
assert(best == NULL || best->hdisplay > 0); US_A(best == NULL || best->hdisplay > 0);
assert(best == NULL || best->vdisplay > 0); US_A(best == NULL || best->vdisplay > 0);
return best; return best;
} }

View File

@@ -24,4 +24,8 @@
#define US_ERROR_COMMON -1 #define US_ERROR_COMMON -1
#define US_ERROR_NO_DEVICE -2 #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

@@ -38,7 +38,7 @@ us_fpsi_s *us_fpsi_init(const char *name, bool with_meta) {
US_CALLOC(fpsi, 1); US_CALLOC(fpsi, 1);
fpsi->name = us_strdup(name); fpsi->name = us_strdup(name);
fpsi->with_meta = with_meta; fpsi->with_meta = with_meta;
atomic_init(&fpsi->state_sec_ts, 0); atomic_init(&fpsi->state_ts, 0);
atomic_init(&fpsi->state, 0); atomic_init(&fpsi->state, 0);
return fpsi; return fpsi;
} }
@@ -56,25 +56,25 @@ void us_fpsi_frame_to_meta(const us_frame_s *frame, us_fpsi_meta_s *meta) {
void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) { void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
if (meta != NULL) { if (meta != NULL) {
assert(fpsi->with_meta); US_A(fpsi->with_meta);
} else { } else {
assert(!fpsi->with_meta); US_A(!fpsi->with_meta);
} }
const sll now_sec_ts = us_floor_ms(us_get_now_monotonic()); const sll now_ts = us_floor_ms(us_get_now_monotonic());
if (atomic_load(&fpsi->state_sec_ts) != now_sec_ts) { if (atomic_load(&fpsi->state_ts) != now_ts) {
US_LOG_PERF_FPS("FPS: %s: %u", fpsi->name, fpsi->accum); US_LOG_PERF_FPS("FPS: %s: %u", fpsi->name, fpsi->accum);
// Fast mutex-less store method // Fast mutex-less store method
ull state = (ull)fpsi->accum & 0xFFFF; ull state = (ull)fpsi->accum & 0xFFFF;
if (fpsi->with_meta) { if (fpsi->with_meta) {
assert(meta != NULL); US_A(meta != NULL);
state |= (ull)(meta->width & 0xFFFF) << 16; state |= (ull)(meta->width & 0xFFFF) << 16;
state |= (ull)(meta->height & 0xFFFF) << 32; state |= (ull)(meta->height & 0xFFFF) << 32;
state |= (ull)(meta->online ? 1 : 0) << 48; state |= (ull)(meta->online ? 1 : 0) << 48;
} }
atomic_store(&fpsi->state, state); // Сначала инфа atomic_store(&fpsi->state, state); // Сначала инфа
atomic_store(&fpsi->state_sec_ts, now_sec_ts); // Потом время, это важно atomic_store(&fpsi->state_ts, now_ts); // Потом время, это важно
fpsi->accum = 0; fpsi->accum = 0;
} }
if (bump) { if (bump) {
@@ -84,27 +84,24 @@ 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) { uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta) {
if (meta != NULL) { if (meta != NULL) {
assert(fpsi->with_meta); US_A(fpsi->with_meta);
} else {
assert(!fpsi->with_meta);
} }
// Между чтением инфы и времени может быть гонка, // Между чтением инфы и времени может быть гонка,
// но это неважно. Если время свежее, до данные тоже // но это неважно. Если время свежее, до данные тоже
// будут свежмими, обратный случай не так важен. // будут свежмими, обратный случай не так важен.
const sll now_sec_ts = us_floor_ms(us_get_now_monotonic()); const sll now_ts = us_floor_ms(us_get_now_monotonic());
const sll state_sec_ts = atomic_load(&fpsi->state_sec_ts); // Сначала время const sll state_ts = atomic_load(&fpsi->state_ts); // Сначала время
const ull state = atomic_load(&fpsi->state); // Потом инфа const ull state = atomic_load(&fpsi->state); // Потом инфа
uint current = state & 0xFFFF; uint current = state & 0xFFFF;
if (fpsi->with_meta) { if (fpsi->with_meta && meta != NULL) {
assert(meta != NULL);
meta->width = (state >> 16) & 0xFFFF; meta->width = (state >> 16) & 0xFFFF;
meta->height = (state >> 32) & 0xFFFF; meta->height = (state >> 32) & 0xFFFF;
meta->online = (state >> 48) & 1; meta->online = (state >> 48) & 1;
} }
if (state_sec_ts != now_sec_ts && (state_sec_ts + 1) != now_sec_ts) { if (state_ts != now_ts && (state_ts + 1) != now_ts) {
// Только текущая или прошлая секунда // Только текущая или прошлая секунда
current = 0; current = 0;
} }

View File

@@ -38,7 +38,7 @@ typedef struct {
char *name; char *name;
bool with_meta; bool with_meta;
uint accum; uint accum;
atomic_llong state_sec_ts; atomic_llong state_ts;
atomic_ullong state; atomic_ullong state;
} us_fpsi_s; } us_fpsi_s;

View File

@@ -25,7 +25,6 @@
#include <stddef.h> #include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <linux/videodev2.h> #include <linux/videodev2.h>
@@ -36,7 +35,7 @@
us_frame_s *us_frame_init(void) { us_frame_s *us_frame_init(void) {
us_frame_s *frame; us_frame_s *frame;
US_CALLOC(frame, 1); US_CALLOC(frame, 1);
us_frame_realloc_data(frame, 512 * 1024); us_frame_realloc_data(frame, 32 * 1024);
frame->dma_fd = -1; frame->dma_fd = -1;
return frame; return frame;
} }
@@ -82,16 +81,32 @@ bool us_frame_compare(const us_frame_s *a, const us_frame_s *b) {
uint us_frame_get_padding(const us_frame_s *frame) { uint us_frame_get_padding(const us_frame_s *frame) {
uint bytes_per_pixel = 0; uint bytes_per_pixel = 0;
switch (frame->format) { switch (frame->format) {
case V4L2_PIX_FMT_YUV420:
case V4L2_PIX_FMT_YVU420:
case V4L2_PIX_FMT_GREY:
bytes_per_pixel = 1;
break;
case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_YVYU: case V4L2_PIX_FMT_YVYU:
case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break; case V4L2_PIX_FMT_RGB565:
bytes_per_pixel = 2;
break;
case V4L2_PIX_FMT_BGR24: case V4L2_PIX_FMT_BGR24:
case V4L2_PIX_FMT_RGB24: bytes_per_pixel = 3; break; case V4L2_PIX_FMT_RGB24:
bytes_per_pixel = 3;
break;
// case V4L2_PIX_FMT_H264: // case V4L2_PIX_FMT_H264:
case V4L2_PIX_FMT_MJPEG: case V4L2_PIX_FMT_MJPEG:
case V4L2_PIX_FMT_JPEG: bytes_per_pixel = 0; break; case V4L2_PIX_FMT_JPEG:
default: assert(0 && "Unknown format"); bytes_per_pixel = 0;
break;
default:
US_RAISE("Unknown format");
} }
if (bytes_per_pixel > 0 && frame->stride > frame->width) { if (bytes_per_pixel > 0 && frame->stride > frame->width) {
return (frame->stride - frame->width * bytes_per_pixel); return (frame->stride - frame->width * bytes_per_pixel);
@@ -104,7 +119,7 @@ bool us_is_jpeg(uint format) {
} }
const char *us_fourcc_to_string(uint format, char *buf, uz size) { const char *us_fourcc_to_string(uint format, char *buf, uz size) {
assert(size >= 8); US_A(size >= 8);
buf[0] = format & 0x7F; buf[0] = format & 0x7F;
buf[1] = (format >> 8) & 0x7F; buf[1] = (format >> 8) & 0x7F;
buf[2] = (format >> 16) & 0x7F; buf[2] = (format >> 16) & 0x7F;

View File

@@ -38,7 +38,8 @@
bool key; \ bool key; \
uint gop; \ uint gop; \
\ \
ldf grab_ts; \ ldf grab_begin_ts; \
ldf grab_end_ts; \
ldf encode_begin_ts; \ ldf encode_begin_ts; \
ldf encode_end_ts; ldf encode_end_ts;
@@ -62,7 +63,8 @@ typedef struct {
(x_dest)->key = (x_src)->key; \ (x_dest)->key = (x_src)->key; \
(x_dest)->gop = (x_src)->gop; \ (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_begin_ts = (x_src)->encode_begin_ts; \
(x_dest)->encode_end_ts = (x_src)->encode_end_ts; \ (x_dest)->encode_end_ts = (x_src)->encode_end_ts; \
} }
@@ -82,7 +84,7 @@ typedef struct {
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, uint format) { static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, uint format) {
assert(src->used > 0); US_A(src->used > 0);
US_FRAME_COPY_META(src, dest); US_FRAME_COPY_META(src, dest);
dest->encode_begin_ts = us_get_now_monotonic(); dest->encode_begin_ts = us_get_now_monotonic();
dest->format = format; dest->format = format;
@@ -91,7 +93,7 @@ static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *de
} }
static inline void us_frame_encoding_end(us_frame_s *dest) { static inline void us_frame_encoding_end(us_frame_s *dest) {
assert(dest->used > 0); US_A(dest->used > 0);
dest->encode_end_ts = us_get_now_monotonic(); dest->encode_end_ts = us_get_now_monotonic();
} }

View File

@@ -34,9 +34,12 @@
static void _frametext_draw_line( static void _frametext_draw_line(
us_frametext_s *ft, const char *line, us_frametext_s *ft,
uint scale_x, uint scale_y, const char *line,
uint start_x, uint start_y); uint scale_x,
uint scale_y,
uint start_x,
uint start_y);
us_frametext_s *us_frametext_init(void) { us_frametext_s *us_frametext_init(void) {
@@ -81,8 +84,8 @@ To access the nth pixel in a row, right-shift by n.
*/ */
void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint height) { void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint height) {
assert(width > 0); US_A(width > 0);
assert(height > 0); US_A(height > 0);
us_frame_s *const frame = ft->frame; us_frame_s *const frame = ft->frame;
@@ -121,9 +124,16 @@ void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint he
if (block_width == 0 || block_height == 0) { if (block_width == 0 || block_height == 0) {
goto empty; goto empty;
} }
uint scale_x = frame->width / block_width / 2;
uint scale_y = frame->height / block_height / 3; // Ширина текста должна быть от 75%, до половины экрана, в зависимости от длины
if (scale_x < scale_y / 1.5) { 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; scale_y = scale_x * 1.5;
} else if (scale_y < scale_x * 1.5) { } else if (scale_y < scale_x * 1.5) {
scale_x = scale_y / 1.5; scale_x = scale_y / 1.5;
@@ -149,10 +159,13 @@ empty:
} }
void _frametext_draw_line( void _frametext_draw_line(
us_frametext_s *ft, const char *line, us_frametext_s *ft,
uint scale_x, uint scale_y, const char *line,
uint start_x, uint start_y) { uint scale_x,
uint scale_y,
uint start_x,
uint start_y
) {
us_frame_s *const frame = ft->frame; us_frame_s *const frame = ft->frame;
const size_t len = strlen(line); const size_t len = strlen(line);

View File

@@ -22,7 +22,7 @@
#pragma once #pragma once
#include <assert.h> #include "tools.h"
#define US_LIST_DECLARE \ #define US_LIST_DECLARE \
@@ -71,6 +71,6 @@
#define US_LIST_REMOVE_C(x_first, x_item, x_count) { \ #define US_LIST_REMOVE_C(x_first, x_item, x_count) { \
US_LIST_REMOVE(x_first, x_item); \ US_LIST_REMOVE(x_first, x_item); \
assert((x_count) >= 1); \ US_A((x_count) >= 1); \
--(x_count); \ --(x_count); \
} }

View File

@@ -28,7 +28,6 @@
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <time.h> #include <time.h>
#include <assert.h>
#include <pthread.h> #include <pthread.h>
@@ -75,7 +74,7 @@ extern pthread_mutex_t us_g_log_mutex;
#define US_SEP_INFO(x_ch) { \ #define US_SEP_INFO(x_ch) { \
US_LOGGING_LOCK; \ US_LOGGING_LOCK; \
for (int m_count = 0; m_count < 80; ++m_count) { \ for (int m_i = 0; m_i < 80; ++m_i) { \
fputc((x_ch), stderr); \ fputc((x_ch), stderr); \
} \ } \
fputc('\n', stderr); \ fputc('\n', stderr); \

View File

@@ -26,7 +26,6 @@
#include <string.h> #include <string.h>
#include <fcntl.h> #include <fcntl.h>
#include <errno.h> #include <errno.h>
#include <assert.h>
#include <sys/file.h> #include <sys/file.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -110,7 +109,7 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
// Если frame == NULL, то только проверяем наличие клиентов // Если frame == NULL, то только проверяем наличие клиентов
// или необходимость инициализировать память. // или необходимость инициализировать память.
assert(sink->server); US_A(sink->server);
if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) { if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) {
// Если регион памяти не был инициализирован, то нужно что-то туда положить. // Если регион памяти не был инициализирован, то нужно что-то туда положить.
@@ -162,7 +161,7 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
} }
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested) { int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested) {
assert(sink->server); US_A(sink->server);
const ldf now = us_get_now_monotonic(); const ldf now = us_get_now_monotonic();
@@ -210,7 +209,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key
} }
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required) { int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required) {
assert(!sink->server); // Client only US_A(!sink->server); // Client only
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) { if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
if (errno == EWOULDBLOCK) { if (errno == EWOULDBLOCK) {

View File

@@ -24,11 +24,11 @@
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <assert.h>
#include <sys/mman.h> #include <sys/mman.h>
#include "types.h" #include "types.h"
#include "tools.h"
us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size) { us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size) {
@@ -40,12 +40,12 @@ us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size) {
if (mem == MAP_FAILED) { if (mem == MAP_FAILED) {
return NULL; return NULL;
} }
assert(mem != NULL); US_A(mem != NULL);
return mem; return mem;
} }
int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size) { int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size) {
assert(mem != NULL); US_A(mem != NULL);
return munmap(mem, sizeof(us_memsink_shared_s) + data_size); return munmap(mem, sizeof(us_memsink_shared_s) + data_size);
} }

View File

@@ -25,21 +25,21 @@
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <getopt.h> #include <getopt.h>
#include <assert.h>
#include "types.h" #include "types.h"
#include "tools.h"
void us_build_short_options(const struct option opts[], char *short_opts, uz size) { void us_build_short_options(const struct option opts[], char *short_opts, uz size) {
memset(short_opts, 0, size); memset(short_opts, 0, size);
for (uint short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) { for (uint short_i = 0, opt_i = 0; opts[opt_i].name != NULL; ++opt_i) {
assert(short_index < size - 3); US_A(short_i < size - 3);
if (isalpha(opts[opt_index].val)) { if (isalpha(opts[opt_i].val)) {
short_opts[short_index] = opts[opt_index].val; short_opts[short_i] = opts[opt_i].val;
++short_index; ++short_i;
if (opts[opt_index].has_arg == required_argument) { if (opts[opt_i].has_arg == required_argument) {
short_opts[short_index] = ':'; short_opts[short_i] = ':';
++short_index; ++short_i;
} }
} }
} }

View File

@@ -25,14 +25,8 @@
#include <signal.h> #include <signal.h>
#include <unistd.h> #include <unistd.h>
#if defined(__FreeBSD__)
#if defined(__linux__)
# define HAS_PDEATHSIG
#elif defined(__FreeBSD__)
# include <sys/param.h> # include <sys/param.h>
# if __FreeBSD_version >= 1102000
# define HAS_PDEATHSIG
# endif
#endif #endif
@@ -49,20 +43,22 @@
# error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0 # error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0
# endif # endif
#endif #endif
#ifdef HAS_PDEATHSIG
#ifdef WITH_PDEATHSIG
# if defined(__linux__) # if defined(__linux__)
# include <sys/prctl.h> # include <sys/prctl.h>
# elif defined(__FreeBSD__) # elif defined(__FreeBSD__) && (__FreeBSD_version >= 1102000)
# include <sys/procctl.h> # include <sys/procctl.h>
# else
# error WITH_PDEATHSIG is not supported on your system
# endif # endif
#endif #endif
#include "types.h" #include "types.h"
#ifdef WITH_SETPROCTITLE #ifdef WITH_SETPROCTITLE
# include "tools.h" # include "tools.h"
#endif #endif
#ifdef HAS_PDEATHSIG #include "logging.h"
# include "logging.h"
#endif
#ifdef WITH_SETPROCTITLE #ifdef WITH_SETPROCTITLE
@@ -70,18 +66,18 @@ extern char **environ;
#endif #endif
#ifdef HAS_PDEATHSIG #ifdef WITH_PDEATHSIG
INLINE int us_process_track_parent_death(void) { INLINE int us_process_track_parent_death(void) {
const pid_t parent = getppid(); const pid_t parent = getppid();
int signum = SIGTERM; int signum = SIGTERM;
# if defined(__linux__) # if defined(__linux__)
const int retval = prctl(PR_SET_PDEATHSIG, signum); const int result = prctl(PR_SET_PDEATHSIG, signum);
# elif defined(__FreeBSD__) # elif defined(__FreeBSD__)
const int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum); const int result = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
# else # else
# error WTF? # error WTF?
# endif # endif
if (retval < 0) { if (result < 0) {
US_LOG_PERROR("Can't set to receive SIGTERM on parent process death"); US_LOG_PERROR("Can't set to receive SIGTERM on parent process death");
return -1; return -1;
} }
@@ -107,15 +103,15 @@ INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefi
US_REALLOC(cmdline, allocated); US_REALLOC(cmdline, allocated);
cmdline[0] = '\0'; cmdline[0] = '\0';
for (int index = 0; index < argc; ++index) { for (int i = 0; i < argc; ++i) {
uz arg_len = strlen(argv[index]); uz arg_len = strlen(argv[i]);
if (used + arg_len + 16 >= allocated) { if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048; allocated += arg_len + 2048;
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert) US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
} }
strcat(cmdline, " "); strcat(cmdline, " ");
strcat(cmdline, argv[index]); strcat(cmdline, argv[i]);
used = strlen(cmdline); // Не считаем вручную, так надежнее used = strlen(cmdline); // Не считаем вручную, так надежнее
} }

View File

@@ -24,7 +24,6 @@
#include <errno.h> #include <errno.h>
#include <time.h> #include <time.h>
#include <assert.h>
#include <pthread.h> #include <pthread.h>
@@ -34,79 +33,79 @@
us_queue_s *us_queue_init(uint capacity) { us_queue_s *us_queue_init(uint capacity) {
us_queue_s *queue; us_queue_s *q;
US_CALLOC(queue, 1); US_CALLOC(q, 1);
US_CALLOC(queue->items, capacity); US_CALLOC(q->items, capacity);
queue->capacity = capacity; q->capacity = capacity;
US_MUTEX_INIT(queue->mutex); US_MUTEX_INIT(q->mutex);
pthread_condattr_t attrs; pthread_condattr_t attrs;
assert(!pthread_condattr_init(&attrs)); US_A(!pthread_condattr_init(&attrs));
assert(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC)); US_A(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
assert(!pthread_cond_init(&queue->full_cond, &attrs)); US_A(!pthread_cond_init(&q->full_cond, &attrs));
assert(!pthread_cond_init(&queue->empty_cond, &attrs)); US_A(!pthread_cond_init(&q->empty_cond, &attrs));
assert(!pthread_condattr_destroy(&attrs)); US_A(!pthread_condattr_destroy(&attrs));
return queue; return q;
} }
void us_queue_destroy(us_queue_s *queue) { void us_queue_destroy(us_queue_s *q) {
US_COND_DESTROY(queue->empty_cond); US_COND_DESTROY(q->empty_cond);
US_COND_DESTROY(queue->full_cond); US_COND_DESTROY(q->full_cond);
US_MUTEX_DESTROY(queue->mutex); US_MUTEX_DESTROY(q->mutex);
free(queue->items); free(q->items);
free(queue); free(q);
} }
#define _WAIT_OR_UNLOCK(x_var, x_cond) { \ #define _WAIT_OR_UNLOCK(x_var, x_cond) { \
struct timespec m_ts; \ struct timespec m_ts; \
assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \ US_A(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \ us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \
while (x_var) { \ while (x_var) { \
const int err = pthread_cond_timedwait(&(x_cond), &queue->mutex, &m_ts); \ const int err = pthread_cond_timedwait(&(x_cond), &q->mutex, &m_ts); \
if (err == ETIMEDOUT) { \ if (err == ETIMEDOUT) { \
US_MUTEX_UNLOCK(queue->mutex); \ US_MUTEX_UNLOCK(q->mutex); \
return -1; \ return -1; \
} \ } \
assert(!err); \ US_A(!err); \
} \ } \
} }
int us_queue_put(us_queue_s *queue, void *item, ldf timeout) { int us_queue_put(us_queue_s *q, void *item, ldf timeout) {
US_MUTEX_LOCK(queue->mutex); US_MUTEX_LOCK(q->mutex);
if (timeout == 0) { if (timeout == 0) {
if (queue->size == queue->capacity) { if (q->size == q->capacity) {
US_MUTEX_UNLOCK(queue->mutex); US_MUTEX_UNLOCK(q->mutex);
return -1; return -1;
} }
} else { } else {
_WAIT_OR_UNLOCK(queue->size == queue->capacity, queue->full_cond); _WAIT_OR_UNLOCK(q->size == q->capacity, q->full_cond);
} }
queue->items[queue->in] = item; q->items[q->in] = item;
++queue->size; ++q->size;
++queue->in; ++q->in;
queue->in %= queue->capacity; q->in %= q->capacity;
US_MUTEX_UNLOCK(queue->mutex); US_MUTEX_UNLOCK(q->mutex);
US_COND_BROADCAST(queue->empty_cond); US_COND_BROADCAST(q->empty_cond);
return 0; return 0;
} }
int us_queue_get(us_queue_s *queue, void **item, ldf timeout) { int us_queue_get(us_queue_s *q, void **item, ldf timeout) {
US_MUTEX_LOCK(queue->mutex); US_MUTEX_LOCK(q->mutex);
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond); _WAIT_OR_UNLOCK(q->size == 0, q->empty_cond);
*item = queue->items[queue->out]; *item = q->items[q->out];
--queue->size; --q->size;
++queue->out; ++q->out;
queue->out %= queue->capacity; q->out %= q->capacity;
US_MUTEX_UNLOCK(queue->mutex); US_MUTEX_UNLOCK(q->mutex);
US_COND_BROADCAST(queue->full_cond); US_COND_BROADCAST(q->full_cond);
return 0; return 0;
} }
#undef _WAIT_OR_UNLOCK #undef _WAIT_OR_UNLOCK
bool us_queue_is_empty(us_queue_s *queue) { bool us_queue_is_empty(us_queue_s *q) {
US_MUTEX_LOCK(queue->mutex); US_MUTEX_LOCK(q->mutex);
const uint size = queue->size; const uint size = q->size;
US_MUTEX_UNLOCK(queue->mutex); US_MUTEX_UNLOCK(q->mutex);
return (bool)(queue->capacity - size); return (bool)(q->capacity - size);
} }

View File

@@ -43,22 +43,22 @@ typedef struct {
} us_queue_s; } us_queue_s;
#define US_QUEUE_DELETE_WITH_ITEMS(x_queue, x_free_item) { \ #define US_QUEUE_DELETE_WITH_ITEMS(x_q, x_free_item) { \
if (x_queue) { \ if (x_q) { \
while (!us_queue_is_empty(x_queue)) { \ while (!us_queue_is_empty(x_q)) { \
void *m_ptr; \ void *m_ptr; \
if (!us_queue_get(x_queue, &m_ptr, 0)) { \ if (!us_queue_get(x_q, &m_ptr, 0)) { \
US_DELETE(m_ptr, x_free_item); \ US_DELETE(m_ptr, x_free_item); \
} \ } \
} \ } \
us_queue_destroy(x_queue); \ us_queue_destroy(x_q); \
} \ } \
} }
us_queue_s *us_queue_init(uint capacity); us_queue_s *us_queue_init(uint capacity);
void us_queue_destroy(us_queue_s *queue); void us_queue_destroy(us_queue_s *q);
int us_queue_put(us_queue_s *queue, void *item, ldf timeout); int us_queue_put(us_queue_s *q, void *item, ldf timeout);
int us_queue_get(us_queue_s *queue, void **item, ldf timeout); int us_queue_get(us_queue_s *q, void **item, ldf timeout);
bool us_queue_is_empty(us_queue_s *queue); bool us_queue_is_empty(us_queue_s *q);

View File

@@ -20,8 +20,6 @@
*****************************************************************************/ *****************************************************************************/
#include <assert.h>
#include "ring.h" #include "ring.h"
#include "types.h" #include "types.h"
@@ -29,8 +27,8 @@
#include "queue.h" #include "queue.h"
int _acquire(us_ring_s *ring, us_queue_s *queue, ldf timeout); int _acquire(us_ring_s *ring, us_queue_s *q, ldf timeout);
void _release(us_ring_s *ring, us_queue_s *queue, uint index); void _release(us_ring_s *ring, us_queue_s *q, uint ri);
us_ring_s *us_ring_init(uint capacity) { us_ring_s *us_ring_init(uint capacity) {
@@ -41,9 +39,9 @@ us_ring_s *us_ring_init(uint capacity) {
ring->capacity = capacity; ring->capacity = capacity;
ring->producer = us_queue_init(capacity); ring->producer = us_queue_init(capacity);
ring->consumer = us_queue_init(capacity); ring->consumer = us_queue_init(capacity);
for (uint index = 0; index < capacity; ++index) { for (uint ri = 0; ri < capacity; ++ri) {
ring->places[index] = index; // XXX: Just to avoid casting between pointer and uint ring->places[ri] = ri; // XXX: Just to avoid casting between pointer and uint
assert(!us_queue_put(ring->producer, (void*)(ring->places + index), 0)); US_A(!us_queue_put(ring->producer, (void*)(ring->places + ri), 0));
} }
return ring; return ring;
} }
@@ -60,27 +58,27 @@ int us_ring_producer_acquire(us_ring_s *ring, ldf timeout) {
return _acquire(ring, ring->producer, timeout); return _acquire(ring, ring->producer, timeout);
} }
void us_ring_producer_release(us_ring_s *ring, uint index) { void us_ring_producer_release(us_ring_s *ring, uint ri) {
_release(ring, ring->consumer, index); _release(ring, ring->consumer, ri);
} }
int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout) { int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout) {
return _acquire(ring, ring->consumer, timeout); return _acquire(ring, ring->consumer, timeout);
} }
void us_ring_consumer_release(us_ring_s *ring, uint index) { void us_ring_consumer_release(us_ring_s *ring, uint ri) {
_release(ring, ring->producer, index); _release(ring, ring->producer, ri);
} }
int _acquire(us_ring_s *ring, us_queue_s *queue, ldf timeout) { int _acquire(us_ring_s *ring, us_queue_s *q, ldf timeout) {
(void)ring; (void)ring;
uint *place; uint *place;
if (us_queue_get(queue, (void**)&place, timeout) < 0) { if (us_queue_get(q, (void**)&place, timeout) < 0) {
return -1; return -1;
} }
return *place; return *place;
} }
void _release(us_ring_s *ring, us_queue_s *queue, uint index) { void _release(us_ring_s *ring, us_queue_s *q, uint ri) {
assert(!us_queue_put(queue, (void*)(ring->places + index), 0)); US_A(!us_queue_put(q, (void*)(ring->places + ri), 0));
} }

View File

@@ -38,15 +38,15 @@ typedef struct {
#define US_RING_INIT_WITH_ITEMS(x_ring, x_capacity, x_init_item) { \ #define US_RING_INIT_WITH_ITEMS(x_ring, x_capacity, x_init_item) { \
(x_ring) = us_ring_init(x_capacity); \ (x_ring) = us_ring_init(x_capacity); \
for (uz m_index = 0; m_index < (x_ring)->capacity; ++m_index) { \ for (uz m_ri = 0; m_ri < (x_ring)->capacity; ++m_ri) { \
(x_ring)->items[m_index] = x_init_item(); \ (x_ring)->items[m_ri] = x_init_item(); \
} \ } \
} }
#define US_RING_DELETE_WITH_ITEMS(x_ring, x_destroy_item) { \ #define US_RING_DELETE_WITH_ITEMS(x_ring, x_destroy_item) { \
if (x_ring) { \ if (x_ring) { \
for (uz m_index = 0; m_index < (x_ring)->capacity; ++m_index) { \ for (uz m_ri = 0; m_ri < (x_ring)->capacity; ++m_ri) { \
x_destroy_item((x_ring)->items[m_index]); \ x_destroy_item((x_ring)->items[m_ri]); \
} \ } \
us_ring_destroy(x_ring); \ us_ring_destroy(x_ring); \
} \ } \
@@ -57,7 +57,7 @@ us_ring_s *us_ring_init(uint capacity);
void us_ring_destroy(us_ring_s *ring); void us_ring_destroy(us_ring_s *ring);
int us_ring_producer_acquire(us_ring_s *ring, ldf timeout); int us_ring_producer_acquire(us_ring_s *ring, ldf timeout);
void us_ring_producer_release(us_ring_s *ring, uint index); void us_ring_producer_release(us_ring_s *ring, uint ri);
int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout); int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout);
void us_ring_consumer_release(us_ring_s *ring, uint index); void us_ring_consumer_release(us_ring_s *ring, uint ri);

View File

@@ -24,7 +24,6 @@
#include <string.h> #include <string.h>
#include <signal.h> #include <signal.h>
#include <assert.h>
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32 #if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
# define HAS_SIGABBREV_NP # define HAS_SIGABBREV_NP
@@ -58,25 +57,25 @@ char *us_signum_to_string(int signum) {
void us_install_signals_handler(us_signal_handler_f handler, bool ignore_sigpipe) { void us_install_signals_handler(us_signal_handler_f handler, bool ignore_sigpipe) {
struct sigaction sig_act = {0}; struct sigaction sig_act = {0};
assert(!sigemptyset(&sig_act.sa_mask)); US_A(!sigemptyset(&sig_act.sa_mask));
sig_act.sa_handler = handler; sig_act.sa_handler = handler;
assert(!sigaddset(&sig_act.sa_mask, SIGINT)); US_A(!sigaddset(&sig_act.sa_mask, SIGINT));
assert(!sigaddset(&sig_act.sa_mask, SIGTERM)); US_A(!sigaddset(&sig_act.sa_mask, SIGTERM));
if (!ignore_sigpipe) { if (!ignore_sigpipe) {
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE)); US_A(!sigaddset(&sig_act.sa_mask, SIGPIPE));
} }
US_LOG_DEBUG("Installing SIGINT handler ..."); US_LOG_DEBUG("Installing SIGINT handler ...");
assert(!sigaction(SIGINT, &sig_act, NULL)); US_A(!sigaction(SIGINT, &sig_act, NULL));
US_LOG_DEBUG("Installing SIGTERM handler ..."); US_LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGTERM, &sig_act, NULL)); US_A(!sigaction(SIGTERM, &sig_act, NULL));
if (!ignore_sigpipe) { if (!ignore_sigpipe) {
US_LOG_DEBUG("Installing SIGPIPE handler ..."); US_LOG_DEBUG("Installing SIGPIPE handler ...");
assert(!sigaction(SIGPIPE, &sig_act, NULL)); US_A(!sigaction(SIGPIPE, &sig_act, NULL));
} else { } else {
US_LOG_DEBUG("Ignoring SIGPIPE ..."); US_LOG_DEBUG("Ignoring SIGPIPE ...");
assert(signal(SIGPIPE, SIG_IGN) != SIG_ERR); US_A(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
} }
} }

View File

@@ -33,17 +33,6 @@
#include "xioctl.h" #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) { int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz) {
*audio_hz = 0; *audio_hz = 0;

View File

@@ -22,7 +22,26 @@
#pragma once #pragma once
#include <linux/v4l2-controls.h>
#include "types.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); int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz);

View File

@@ -25,7 +25,6 @@
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
#include <assert.h>
#include <sys/syscall.h> #include <sys/syscall.h>
@@ -47,8 +46,8 @@
# define US_THREAD_NAME_SIZE ((uz)16) # define US_THREAD_NAME_SIZE ((uz)16)
#endif #endif
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg))) #define US_THREAD_CREATE(x_tid, x_func, x_arg) US_A(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
#define US_THREAD_JOIN(x_tid) assert(!pthread_join((x_tid), NULL)) #define US_THREAD_JOIN(x_tid) US_A(!pthread_join((x_tid), NULL))
#ifdef WITH_PTHREAD_NP #ifdef WITH_PTHREAD_NP
# define US_THREAD_RENAME(x_fmt, ...) { \ # define US_THREAD_RENAME(x_fmt, ...) { \
@@ -65,16 +64,16 @@
us_thread_block_signals(); \ us_thread_block_signals(); \
} }
#define US_MUTEX_INIT(x_mutex) assert(!pthread_mutex_init(&(x_mutex), NULL)) #define US_MUTEX_INIT(x_mutex) US_A(!pthread_mutex_init(&(x_mutex), NULL))
#define US_MUTEX_DESTROY(x_mutex) assert(!pthread_mutex_destroy(&(x_mutex))) #define US_MUTEX_DESTROY(x_mutex) US_A(!pthread_mutex_destroy(&(x_mutex)))
#define US_MUTEX_LOCK(x_mutex) assert(!pthread_mutex_lock(&(x_mutex))) #define US_MUTEX_LOCK(x_mutex) US_A(!pthread_mutex_lock(&(x_mutex)))
#define US_MUTEX_UNLOCK(x_mutex) assert(!pthread_mutex_unlock(&(x_mutex))) #define US_MUTEX_UNLOCK(x_mutex) US_A(!pthread_mutex_unlock(&(x_mutex)))
#define US_COND_INIT(x_cond) assert(!pthread_cond_init(&(x_cond), NULL)) #define US_COND_INIT(x_cond) US_A(!pthread_cond_init(&(x_cond), NULL))
#define US_COND_DESTROY(x_cond) assert(!pthread_cond_destroy(&(x_cond))) #define US_COND_DESTROY(x_cond) US_A(!pthread_cond_destroy(&(x_cond)))
#define US_COND_SIGNAL(x_cond) assert(!pthread_cond_signal(&(x_cond))) #define US_COND_SIGNAL(x_cond) US_A(!pthread_cond_signal(&(x_cond)))
#define US_COND_BROADCAST(x_cond) assert(!pthread_cond_broadcast(&(x_cond))) #define US_COND_BROADCAST(x_cond) US_A(!pthread_cond_broadcast(&(x_cond)))
#define US_COND_WAIT_FOR(x_var, x_cond, x_mutex) { while(!(x_var)) assert(!pthread_cond_wait(&(x_cond), &(x_mutex))); } #define US_COND_WAIT_FOR(x_var, x_cond, x_mutex) { while(!(x_var)) US_A(!pthread_cond_wait(&(x_cond), &(x_mutex))); }
#ifdef WITH_PTHREAD_NP #ifdef WITH_PTHREAD_NP
@@ -114,7 +113,7 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
const pid_t tid = syscall(SYS_gettid); const pid_t tid = syscall(SYS_gettid);
#elif defined(__FreeBSD__) #elif defined(__FreeBSD__)
long id; long id;
assert(!syscall(SYS_thr_self, &id)); US_A(!syscall(SYS_thr_self, &id));
const pid_t tid = id; const pid_t tid = id;
#elif defined(__OpenBSD__) #elif defined(__OpenBSD__)
const pid_t tid = syscall(SYS_getthrid); const pid_t tid = syscall(SYS_getthrid);
@@ -135,8 +134,8 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
INLINE void us_thread_block_signals(void) { INLINE void us_thread_block_signals(void) {
sigset_t mask; sigset_t mask;
assert(!sigemptyset(&mask)); US_A(!sigemptyset(&mask));
assert(!sigaddset(&mask, SIGINT)); US_A(!sigaddset(&mask, SIGINT));
assert(!sigaddset(&mask, SIGTERM)); US_A(!sigaddset(&mask, SIGTERM));
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL)); US_A(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
} }

View File

@@ -51,14 +51,17 @@
#define INLINE inline __attribute__((always_inline)) #define INLINE inline __attribute__((always_inline))
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL) #define US_A(x_arg) { const bool m_ar = (x_arg); assert(m_ar && #x_arg); }
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL) #define US_RAISE(x_msg) assert(0 && x_msg)
#define US_CALLOC(x_dest, x_nmemb) US_A(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
#define US_REALLOC(x_dest, x_nmemb) US_A(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); x_dest = NULL; } } #define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); x_dest = NULL; } }
#define US_CLOSE_FD(x_dest) { if (x_dest >= 0) { close(x_dest); x_dest = -1; } } #define US_CLOSE_FD(x_dest) { if (x_dest >= 0) { close(x_dest); x_dest = -1; } }
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj)) #define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
#define US_SNPRINTF(x_dest, x_size, x_fmt, ...) assert(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0) #define US_SNPRINTF(x_dest, x_size, x_fmt, ...) US_A(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0)
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0) #define US_ASPRINTF(x_dest, x_fmt, ...) US_A(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0)
#define US_MIN(x_a, x_b) ({ \ #define US_MIN(x_a, x_b) ({ \
__typeof__(x_a) m_a = (x_a); \ __typeof__(x_a) m_a = (x_a); \
@@ -85,7 +88,7 @@
INLINE char *us_strdup(const char *str) { INLINE char *us_strdup(const char *str) {
char *const new = strdup(str); char *const new = strdup(str);
assert(new != NULL); US_A(new != NULL);
return new; return new;
} }
@@ -115,7 +118,7 @@ INLINE u32 us_triple_u32(u32 x) {
INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) { INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
struct timespec ts; struct timespec ts;
assert(!clock_gettime(clk_id, &ts)); US_A(!clock_gettime(clk_id, &ts));
*sec = ts.tv_sec; *sec = ts.tv_sec;
*msec = round(ts.tv_nsec / 1.0e6); *msec = round(ts.tv_nsec / 1.0e6);
@@ -134,7 +137,7 @@ INLINE ldf us_get_now_monotonic(void) {
INLINE u64 us_get_now_monotonic_u64(void) { INLINE u64 us_get_now_monotonic_u64(void) {
struct timespec ts; struct timespec ts;
assert(!clock_gettime(CLOCK_MONOTONIC, &ts)); US_A(!clock_gettime(CLOCK_MONOTONIC, &ts));
return (u64)(ts.tv_nsec / 1000) + (u64)ts.tv_sec * 1000000; return (u64)(ts.tv_nsec / 1000) + (u64)ts.tv_sec * 1000000;
} }
@@ -150,12 +153,6 @@ INLINE ldf us_get_now_real(void) {
return (ldf)sec + ((ldf)msec) / 1000; return (ldf)sec + ((ldf)msec) / 1000;
} }
INLINE uint us_get_cores_available(void) {
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
return US_MAX(US_MIN(cores_sysconf, 4), 1);
}
INLINE void us_ld_to_timespec(ldf ld, struct timespec *ts) { INLINE void us_ld_to_timespec(ldf ld, struct timespec *ts) {
ts->tv_sec = (long)ld; ts->tv_sec = (long)ld;
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L; ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
@@ -169,6 +166,28 @@ INLINE ldf us_timespec_to_ld(const struct timespec *ts) {
return ts->tv_sec + ((ldf)ts->tv_nsec) / 1000000000; return ts->tv_sec + ((ldf)ts->tv_nsec) / 1000000000;
} }
#define NTP_UNIX_TIME_DIFF 2208988800u // Difference between Unix time and NTP time in seconds (1970 - 1900)
#define NTP_TICKS_IN_SECOND 4294967296u // Ticks per second in NTP time
INLINE u64 us_get_now_ntp(void) {
struct timespec ts;
US_A(!clock_gettime(CLOCK_REALTIME, &ts));
return (((u64)ts.tv_sec + NTP_UNIX_TIME_DIFF) << 32) + ((u64)ts.tv_nsec / 1000 * NTP_TICKS_IN_SECOND) / 1000000;
}
INLINE u64 us_ld_to_ntp(ldf ld) {
return (ld > 0 ? ld * NTP_TICKS_IN_SECOND : 0);
}
#undef NTP_TICKS_IN_SECOND
#undef NTP_UNIX_TIME_DIFF
INLINE uint us_get_cores_available(void) {
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
return US_MAX(US_MIN(cores_sysconf, 4), 1);
}
INLINE int us_flock_timedwait_monotonic(int fd, ldf timeout) { INLINE int us_flock_timedwait_monotonic(int fd, ldf timeout) {
const ldf deadline_ts = us_get_now_monotonic() + timeout; const ldf deadline_ts = us_get_now_monotonic() + timeout;
int retval = -1; int retval = -1;

View File

@@ -24,12 +24,12 @@
#include <stdio.h> #include <stdio.h>
#include <setjmp.h> #include <setjmp.h>
#include <assert.h>
#include <jpeglib.h> #include <jpeglib.h>
#include <linux/videodev2.h> #include <linux/videodev2.h>
#include "types.h" #include "types.h"
#include "tools.h"
#include "logging.h" #include "logging.h"
#include "frame.h" #include "frame.h"
@@ -45,7 +45,7 @@ static void _jpeg_error_handler(j_common_ptr jpeg);
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) { int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
assert(us_is_jpeg(src->format)); US_A(us_is_jpeg(src->format));
volatile int retval = 0; volatile int retval = 0;
@@ -77,7 +77,7 @@ int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
if (decode) { if (decode) {
JSAMPARRAY scanlines; JSAMPARRAY scanlines;
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1); scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr)&jpeg, JPOOL_IMAGE, dest->stride, 1);
us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2); us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
while (jpeg.output_scanline < jpeg.output_height) { while (jpeg.output_scanline < jpeg.output_height) {

View File

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

View File

@@ -24,7 +24,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <strings.h> #include <strings.h>
#include <assert.h>
#include <pthread.h> #include <pthread.h>
@@ -81,8 +80,8 @@ us_encoder_s *us_encoder_init(void) {
void us_encoder_destroy(us_encoder_s *enc) { void us_encoder_destroy(us_encoder_s *enc) {
us_encoder_runtime_s *const run = enc->run; us_encoder_runtime_s *const run = enc->run;
if (run->m2ms != NULL) { if (run->m2ms != NULL) {
for (uint index = 0; index < run->n_m2ms; ++index) { for (uint i = 0; i < run->n_m2ms; ++i) {
US_DELETE(run->m2ms[index], us_m2m_encoder_destroy); US_DELETE(run->m2ms[i], us_m2m_encoder_destroy);
} }
free(run->m2ms); free(run->m2ms);
} }
@@ -113,7 +112,7 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
us_encoder_runtime_s *const run = enc->run; us_encoder_runtime_s *const run = enc->run;
us_capture_runtime_s *const cr = cap->run; us_capture_runtime_s *const cr = cap->run;
assert(run->pool == NULL); US_A(run->pool == NULL);
us_encoder_type_e type = enc->type; us_encoder_type_e type = enc->type;
uint quality = cap->jpeg_quality; uint quality = cap->jpeg_quality;
@@ -131,14 +130,17 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
} else { } else {
US_LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ..."); US_LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
type = US_ENCODER_TYPE_CPU; 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) { } else if (type == US_ENCODER_TYPE_M2M_VIDEO || type == US_ENCODER_TYPE_M2M_IMAGE) {
US_LOG_DEBUG("Preparing M2M-%s encoder ...", (type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE")); US_LOG_DEBUG("Preparing M2M-%s encoder ...",
(type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
if (run->m2ms == NULL) { if (run->m2ms == NULL) {
US_CALLOC(run->m2ms, n_workers); US_CALLOC(run->m2ms, n_workers);
} }
for (; run->n_m2ms < n_workers; ++run->n_m2ms) { for (; run->n_m2ms < n_workers; ++run->n_m2ms) {
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости // Начинаем с нуля и доинициализируем на следующих заходах при необходимости
char name[32]; char name[32];
@@ -162,21 +164,18 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
run->quality = quality; run->quality = quality;
US_MUTEX_UNLOCK(run->mutex); 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( enc->run->pool = us_workers_pool_init(
"JPEG", "jw", n_workers, desired_interval, "JPEG",
_worker_job_init, (void*)enc, "jw",
n_workers,
_worker_job_init,
(void*)enc,
_worker_job_destroy, _worker_job_destroy,
_worker_run_job); _worker_run_job);
} }
void us_encoder_close(us_encoder_s *enc) { void us_encoder_close(us_encoder_s *enc) {
assert(enc->run->pool != NULL); US_A(enc->run->pool != NULL);
US_DELETE(enc->run->pool, us_workers_pool_destroy); US_DELETE(enc->run->pool, us_workers_pool_destroy);
} }
@@ -207,36 +206,35 @@ static bool _worker_run_job(us_worker_s *wr) {
us_encoder_runtime_s *const run = job->enc->run; us_encoder_runtime_s *const run = job->enc->run;
const us_frame_s *const src = &job->hw->raw; const us_frame_s *const src = &job->hw->raw;
us_frame_s *const dest = job->dest; us_frame_s *const dest = job->dest;
const uint i = job->hw->buf.index;
if (run->type == US_ENCODER_TYPE_CPU) { if (run->type == US_ENCODER_TYPE_CPU) {
US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u", US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u", wr->name, i);
wr->name, job->hw->buf.index);
us_cpu_encoder_compress(src, dest, run->quality); us_cpu_encoder_compress(src, dest, run->quality);
} else if (run->type == US_ENCODER_TYPE_HW) { } else if (run->type == US_ENCODER_TYPE_HW) {
US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u", US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u", wr->name, i);
wr->name, job->hw->buf.index);
us_hw_encoder_compress(src, dest); us_hw_encoder_compress(src, dest);
} else if (run->type == US_ENCODER_TYPE_M2M_VIDEO || run->type == US_ENCODER_TYPE_M2M_IMAGE) { } else if (run->type == US_ENCODER_TYPE_M2M_VIDEO || run->type == US_ENCODER_TYPE_M2M_IMAGE) {
US_LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u", US_LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
(run->type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index); (run->type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"),
wr->name, i);
if (us_m2m_encoder_compress(run->m2ms[wr->number], src, dest, false) < 0) { if (us_m2m_encoder_compress(run->m2ms[wr->number], src, dest, false) < 0) {
goto error; goto error;
} }
} else { } else {
assert(0 && "Unknown encoder type"); US_RAISE("Unknown encoder type");
} }
US_LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u", US_LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
job->dest->used, job->dest->used,
job->dest->encode_end_ts - job->dest->encode_begin_ts, job->dest->encode_end_ts - job->dest->encode_begin_ts,
wr->name, wr->name, i);
job->hw->buf.index);
return true; return true;
error: error:
US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index); US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, i);
return false; return false;
} }

View File

@@ -27,6 +27,18 @@
#include "encoder.h" #include "encoder.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "../../../libs/types.h"
#include "../../../libs/tools.h"
#include "../../../libs/frame.h"
typedef struct { typedef struct {
struct jpeg_destination_mgr mgr; // Default manager struct jpeg_destination_mgr mgr; // Default manager
@@ -38,6 +50,8 @@ typedef struct {
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame); 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(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_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); static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
#ifndef JCS_EXTENSIONS #ifndef JCS_EXTENSIONS
@@ -50,7 +64,7 @@ static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
static void _jpeg_term_destination(j_compress_ptr jpeg); static void _jpeg_term_destination(j_compress_ptr jpeg);
void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned quality) { void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, uint quality) {
// This function based on compress_image_to_jpeg() from mjpg-streamer // This function based on compress_image_to_jpeg() from mjpg-streamer
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG); us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
@@ -69,11 +83,23 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
switch (src->format) { switch (src->format) {
case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_YVYU: case V4L2_PIX_FMT_YVYU:
case V4L2_PIX_FMT_UYVY: jpeg.in_color_space = JCS_YCbCr; break; case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_YUV420:
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 # ifdef JCS_EXTENSIONS
case V4L2_PIX_FMT_BGR24: jpeg.in_color_space = JCS_EXT_BGR; break; case V4L2_PIX_FMT_BGR24:
jpeg.in_color_space = JCS_EXT_BGR;
break;
# endif # endif
default: jpeg.in_color_space = JCS_RGB; break; default:
jpeg.in_color_space = JCS_RGB;
break;
} }
jpeg_set_defaults(&jpeg); jpeg_set_defaults(&jpeg);
@@ -85,9 +111,27 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
// https://www.fourcc.org/yuv.php // https://www.fourcc.org/yuv.php
case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_YVYU: case V4L2_PIX_FMT_YVYU:
case V4L2_PIX_FMT_UYVY: _jpeg_write_scanlines_yuv(&jpeg, src); break; case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_RGB565: _jpeg_write_scanlines_rgb565(&jpeg, src); break; _jpeg_write_scanlines_yuv(&jpeg, src);
case V4L2_PIX_FMT_RGB24: _jpeg_write_scanlines_rgb24(&jpeg, src); break; break;
case V4L2_PIX_FMT_YUV420:
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);
break;
case V4L2_PIX_FMT_RGB24:
_jpeg_write_scanlines_rgb24(&jpeg, src);
break;
case V4L2_PIX_FMT_BGR24: case V4L2_PIX_FMT_BGR24:
# ifdef JCS_EXTENSIONS # ifdef JCS_EXTENSIONS
_jpeg_write_scanlines_rgb24(&jpeg, src); // Use native JCS_EXT_BGR _jpeg_write_scanlines_rgb24(&jpeg, src); // Use native JCS_EXT_BGR
@@ -95,7 +139,8 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
_jpeg_write_scanlines_bgr24(&jpeg, src); _jpeg_write_scanlines_bgr24(&jpeg, src);
# endif # endif
break; break;
default: assert(0 && "Unsupported input format for CPU encoder"); return; default:
US_RAISE("Unsupported input format for CPU encoder");
} }
jpeg_finish_compress(&jpeg); jpeg_finish_compress(&jpeg);
@@ -106,7 +151,7 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) { static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) {
if (jpeg->dest == NULL) { if (jpeg->dest == NULL) {
assert((jpeg->dest = (struct jpeg_destination_mgr*)(*jpeg->mem->alloc_small)( US_A((jpeg->dest = (struct jpeg_destination_mgr*)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s) (j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s)
)) != NULL); )) != NULL);
} }
@@ -121,19 +166,19 @@ 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(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf; u8 *line_buf;
US_CALLOC(line_buf, frame->width * 3); US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = us_frame_get_padding(frame); const uint padding = us_frame_get_padding(frame);
const uint8_t *data = frame->data; const u8 *data = frame->data;
while (jpeg->next_scanline < frame->height) { while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf; u8 *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) { for (uint x = 0; x < frame->width; ++x) {
// See also: https://www.kernel.org/doc/html/v4.8/media/uapi/v4l/pixfmt-uyvy.html // See also: https://www.kernel.org/doc/html/v4.8/media/uapi/v4l/pixfmt-uyvy.html
const bool is_odd_pixel = x & 1; const bool is_odd_pixel = x & 1;
uint8_t y, u, v; u8 y, u, v;
if (frame->format == V4L2_PIX_FMT_YUYV) { if (frame->format == V4L2_PIX_FMT_YUYV) {
y = data[is_odd_pixel ? 2 : 0]; y = data[is_odd_pixel ? 2 : 0];
u = data[1]; u = data[1];
@@ -147,8 +192,7 @@ static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const u
u = data[0]; u = data[0];
v = data[2]; v = data[2];
} else { } else {
assert(0 && "Unsupported pixel format"); US_RAISE("Unsupported pixel format");
return; // Makes linter happy
} }
ptr[0] = y; ptr[0] = y;
@@ -167,21 +211,103 @@ static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const u
free(line_buf); free(line_buf);
} }
static void _jpeg_write_scanlines_rgb565(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) {
uint8_t *line_buf; u8 *line_buf;
US_CALLOC(line_buf, frame->width * 3); US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = us_frame_get_padding(frame); const uint padding = us_frame_get_padding(frame);
const uint8_t *data = frame->data; const uint image_size = frame->width * frame->height;
const uint chroma_array_size = (frame->used - image_size) / 2;
const uint chroma_matrix_order = (image_size / chroma_array_size) == 16 ? 4 : 2;
const u8 *data = frame->data;
const u8 *chroma1_data = frame->data + image_size;
const u8 *chroma2_data = frame->data + image_size + chroma_array_size;
//US_LOG_DEBUG("Planar data: Image Size %u, Chroma Array Size %u, Chroma Matrix Order %u",
// image_size, chroma_array_size, chroma_matrix_order);
while (jpeg->next_scanline < frame->height) { while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf; u8 *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) { for (uint x = 0; x < frame->width; ++x) {
const unsigned int two_byte = (data[1] << 8) + data[0]; // See also: https://www.kernel.org/doc/html/v4.8/media/uapi/v4l/pixfmt-yuv420.html
u8 y = data[x];
u8 u;
u8 v;
uint chroma_position = x / chroma_matrix_order;
switch (frame->format) {
case V4L2_PIX_FMT_YUV420:
u = chroma1_data[chroma_position];
v = chroma2_data[chroma_position];
break;
case V4L2_PIX_FMT_YVU420:
u = chroma2_data[chroma_position];
v = chroma1_data[chroma_position];
break;
default:
US_RAISE("Unsupported pixel format");
}
ptr[0] = y;
ptr[1] = u;
ptr[2] = v;
ptr += 3;
}
data += frame->width + padding;
if (jpeg->next_scanline > 0 && jpeg->next_scanline % chroma_matrix_order == 0) {
chroma1_data += (frame->width + padding) / chroma_matrix_order;
chroma2_data += (frame->width + padding) / chroma_matrix_order;
}
JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1);
}
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);
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) {
const uint two_byte = (data[1] << 8) + data[0];
ptr[0] = data[1] & 248; // Red ptr[0] = data[1] & 248; // Red
ptr[1] = (uint8_t)((two_byte & 2016) >> 3); // Green ptr[1] = (u8)((two_byte & 2016) >> 3); // Green
ptr[2] = (data[0] & 31) * 8; // Blue ptr[2] = (data[0] & 31) * 8; // Blue
ptr += 3; ptr += 3;
@@ -197,8 +323,8 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, cons
} }
static void _jpeg_write_scanlines_rgb24(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) {
const unsigned padding = us_frame_get_padding(frame); const uint padding = us_frame_get_padding(frame);
uint8_t *data = frame->data; u8 *data = frame->data;
while (jpeg->next_scanline < frame->height) { while (jpeg->next_scanline < frame->height) {
JSAMPROW scanlines[1] = {data}; JSAMPROW scanlines[1] = {data};
@@ -210,17 +336,17 @@ static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const
#ifndef JCS_EXTENSIONS #ifndef JCS_EXTENSIONS
static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) { static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf; u8 *line_buf;
US_CALLOC(line_buf, frame->width * 3); US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = us_frame_get_padding(frame); const uint padding = us_frame_get_padding(frame);
uint8_t *data = frame->data; u8 *data = frame->data;
while (jpeg->next_scanline < frame->height) { while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf; u8 *ptr = line_buf;
// swap B and R values // swap B and R values
for (unsigned x = 0; x < frame->width * 3; x += 3) { for (uint x = 0; x < frame->width * 3; x += 3) {
ptr[0] = data[x + 2]; ptr[0] = data[x + 2];
ptr[1] = data[x + 1]; ptr[1] = data[x + 1];
ptr[2] = data[x]; ptr[2] = data[x];
@@ -243,7 +369,7 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s*)jpeg->dest; _jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s*)jpeg->dest;
// Allocate the output buffer - it will be released when done with image // Allocate the output buffer - it will be released when done with image
assert((dest->buf = (JOCTET*)(*jpeg->mem->alloc_small)( US_A((dest->buf = (JOCTET*)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET) (j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
)) != NULL); )) != NULL);

View File

@@ -22,17 +22,8 @@
#pragma once #pragma once
#include <stdio.h> #include "../../../libs/types.h"
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "../../../libs/tools.h"
#include "../../../libs/frame.h" #include "../../../libs/frame.h"
void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned quality); void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, uint quality);

View File

@@ -27,13 +27,23 @@
#include "encoder.h" #include "encoder.h"
#include <string.h>
#include <linux/videodev2.h>
#include "../../../libs/types.h"
#include "../../../libs/tools.h"
#include "../../../libs/frame.h"
#include "huffman.h"
void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest); void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest);
static bool _is_huffman(const uint8_t *data); static bool _is_huffman(const u8 *data);
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest) { void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest) {
assert(us_is_jpeg(src->format)); US_A(us_is_jpeg(src->format));
_copy_plus_huffman(src, dest); _copy_plus_huffman(src, dest);
} }
@@ -41,8 +51,8 @@ void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG); us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
if (!_is_huffman(src->data)) { if (!_is_huffman(src->data)) {
const uint8_t *src_ptr = src->data; const u8 *src_ptr = src->data;
const uint8_t *const src_end = src->data + src->used; const u8 *const src_end = src->data + src->used;
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) { while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
src_ptr += 1; src_ptr += 1;
@@ -52,7 +62,7 @@ void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
return; return;
} }
const size_t paste = src_ptr - src->data; const uz paste = src_ptr - src->data;
us_frame_set_data(dest, src->data, paste); us_frame_set_data(dest, src->data, paste);
us_frame_append_data(dest, US_HUFFMAN_TABLE, sizeof(US_HUFFMAN_TABLE)); us_frame_append_data(dest, US_HUFFMAN_TABLE, sizeof(US_HUFFMAN_TABLE));
@@ -65,14 +75,14 @@ void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
us_frame_encoding_end(dest); us_frame_encoding_end(dest);
} }
static bool _is_huffman(const uint8_t *data) { static bool _is_huffman(const u8 *data) {
unsigned count = 0; uint count = 0;
while ((((uint16_t)data[0] << 8) | data[1]) != 0xFFDA) { while ((((u16)data[0] << 8) | data[1]) != 0xFFDA) {
if (count++ > 2048) { if (count++ > 2048) {
return false; return false;
} }
if ((((uint16_t)data[0] << 8) | data[1]) == 0xFFC4) { if ((((u16)data[0] << 8) | data[1]) == 0xFFC4) {
return true; return true;
} }
data += 1; data += 1;

View File

@@ -22,16 +22,7 @@
#pragma once #pragma once
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "../../../libs/frame.h" #include "../../../libs/frame.h"
#include "huffman.h"
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest); void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest);

View File

@@ -27,10 +27,10 @@
#pragma once #pragma once
#include <stdint.h> #include "../../../libs/types.h"
static const uint8_t US_HUFFMAN_TABLE[] = { static const u8 US_HUFFMAN_TABLE[] = {
0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03,

View File

@@ -32,7 +32,7 @@ us_gpio_s us_g_gpio = {
.role = x_role, \ .role = x_role, \
.consumer = NULL, \ .consumer = NULL, \
.line = NULL, \ .line = NULL, \
.state = false \ .on = false \
} }
.prog_running = MAKE_OUTPUT("prog-running"), .prog_running = MAKE_OUTPUT("prog-running"),
.stream_online = MAKE_OUTPUT("stream-online"), .stream_online = MAKE_OUTPUT("stream-online"),
@@ -46,13 +46,13 @@ us_gpio_s us_g_gpio = {
}; };
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip); static void _gpio_output_init(us_gpio_output_s *out, struct gpiod_chip *chip);
static void _gpio_output_destroy(us_gpio_output_s *output); static void _gpio_output_destroy(us_gpio_output_s *out);
void us_gpio_init(void) { void us_gpio_init(void) {
# ifndef HAVE_GPIOD2 # ifndef HAVE_GPIOD2
assert(us_g_gpio.chip == NULL); US_A(us_g_gpio.chip == NULL);
# endif # endif
if ( if (
us_g_gpio.prog_running.pin >= 0 us_g_gpio.prog_running.pin >= 0
@@ -92,23 +92,23 @@ void us_gpio_destroy(void) {
} }
} }
int us_gpio_inner_set(us_gpio_output_s *output, bool state) { int us_gpio_inner_set(us_gpio_output_s *out, bool on) {
int retval = 0; int retval = 0;
# ifndef HAVE_GPIOD2 # ifndef HAVE_GPIOD2
assert(us_g_gpio.chip != NULL); US_A(us_g_gpio.chip != NULL);
# endif # endif
assert(output->line != NULL); US_A(out->line != NULL);
assert(output->state != state); // Must be checked in macro for the performance US_A(out->on != on); // Must be checked in macro for the performance
US_MUTEX_LOCK(us_g_gpio.mutex); US_MUTEX_LOCK(us_g_gpio.mutex);
# ifdef HAVE_GPIOD2 # ifdef HAVE_GPIOD2
if (gpiod_line_request_set_value(output->line, output->pin, state) < 0) { if (gpiod_line_request_set_value(out->line, out->pin, on) < 0) {
# else # else
if (gpiod_line_set_value(output->line, (int)state) < 0) { if (gpiod_line_set_value(out->line, (int)on) < 0) {
# endif # endif
US_LOG_PERROR("GPIO: Can't write value %d to line %s", state, output->consumer); \ US_LOG_PERROR("GPIO: Can't write value %d to line %s", on, out->consumer); \
_gpio_output_destroy(output); _gpio_output_destroy(out);
retval = -1; retval = -1;
} }
@@ -116,65 +116,65 @@ int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
return retval; return retval;
} }
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip) { static void _gpio_output_init(us_gpio_output_s *out, struct gpiod_chip *chip) {
assert(output->line == NULL); US_A(out->line == NULL);
US_ASPRINTF(output->consumer, "%s::%s", us_g_gpio.consumer_prefix, output->role); US_ASPRINTF(out->consumer, "%s::%s", us_g_gpio.consumer_prefix, out->role);
if (output->pin >= 0) { if (out->pin >= 0) {
# ifdef HAVE_GPIOD2 # ifdef HAVE_GPIOD2
struct gpiod_line_settings *line_settings; struct gpiod_line_settings *line_settings;
assert(line_settings = gpiod_line_settings_new()); US_A(line_settings = gpiod_line_settings_new());
assert(!gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_OUTPUT)); US_A(!gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_OUTPUT));
assert(!gpiod_line_settings_set_output_value(line_settings, false)); US_A(!gpiod_line_settings_set_output_value(line_settings, false));
struct gpiod_line_config *line_config; struct gpiod_line_config *line_config;
assert(line_config = gpiod_line_config_new()); US_A(line_config = gpiod_line_config_new());
const unsigned offset = output->pin; const unsigned offset = out->pin;
assert(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings)); US_A(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings));
struct gpiod_request_config *request_config; struct gpiod_request_config *req_config;
assert(request_config = gpiod_request_config_new()); US_A(req_config = gpiod_request_config_new());
gpiod_request_config_set_consumer(request_config, output->consumer); gpiod_request_config_set_consumer(req_config, out->consumer);
if ((output->line = gpiod_chip_request_lines(chip, request_config, line_config)) == NULL) { if ((out->line = gpiod_chip_request_lines(chip, req_config, line_config)) == NULL) {
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer); US_LOG_PERROR("GPIO: Can't request pin=%d as %s", out->pin, out->consumer);
} }
gpiod_request_config_free(request_config); gpiod_request_config_free(req_config);
gpiod_line_config_free(line_config); gpiod_line_config_free(line_config);
gpiod_line_settings_free(line_settings); gpiod_line_settings_free(line_settings);
if (output->line == NULL) { if (out->line == NULL) {
_gpio_output_destroy(output); _gpio_output_destroy(out);
} }
# else # else
if ((output->line = gpiod_chip_get_line(chip, output->pin)) != NULL) { if ((out->line = gpiod_chip_get_line(chip, out->pin)) != NULL) {
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) { if (gpiod_line_request_output(out->line, out->consumer, 0) < 0) {
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer); US_LOG_PERROR("GPIO: Can't request pin=%d as %s", out->pin, out->consumer);
_gpio_output_destroy(output); _gpio_output_destroy(out);
} }
} else { } else {
US_LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer); US_LOG_PERROR("GPIO: Can't get pin=%d as %s", out->pin, out->consumer);
} }
# endif # endif
} }
} }
static void _gpio_output_destroy(us_gpio_output_s *output) { static void _gpio_output_destroy(us_gpio_output_s *out) {
if (output->line != NULL) { if (out->line != NULL) {
# ifdef HAVE_GPIOD2 # ifdef HAVE_GPIOD2
gpiod_line_request_release(output->line); gpiod_line_request_release(out->line);
# else # else
gpiod_line_release(output->line); gpiod_line_release(out->line);
# endif # endif
output->line = NULL; out->line = NULL;
} }
if (output->consumer != NULL) { if (out->consumer != NULL) {
free(output->consumer); free(out->consumer);
output->consumer = NULL; out->consumer = NULL;
} }
output->state = false; out->on = false;
} }

View File

@@ -25,7 +25,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <pthread.h> #include <pthread.h>
#include <gpiod.h> #include <gpiod.h>
@@ -44,7 +43,7 @@ typedef struct {
# else # else
struct gpiod_line *line; struct gpiod_line *line;
# endif # endif
bool state; bool on;
} us_gpio_output_s; } us_gpio_output_s;
typedef struct { typedef struct {
@@ -69,27 +68,27 @@ extern us_gpio_s us_g_gpio;
void us_gpio_init(void); void us_gpio_init(void);
void us_gpio_destroy(void); void us_gpio_destroy(void);
int us_gpio_inner_set(us_gpio_output_s *output, bool state); int us_gpio_inner_set(us_gpio_output_s *out, bool on);
#define SET_STATE(x_output, x_state) { \ #define SET_ON(x_out, x_on) { \
if (x_output.line && x_output.state != x_state) { \ if (x_out.line && x_out.on != x_on) { \
if (!us_gpio_inner_set(&x_output, x_state)) { \ if (!us_gpio_inner_set(&x_out, x_on)) { \
x_output.state = x_state; \ x_out.on = x_on; \
} \ } \
} \ } \
} }
INLINE void us_gpio_set_prog_running(bool state) { INLINE void us_gpio_set_prog_running(bool on) {
SET_STATE(us_g_gpio.prog_running, state); SET_ON(us_g_gpio.prog_running, on);
} }
INLINE void us_gpio_set_stream_online(bool state) { INLINE void us_gpio_set_stream_online(bool on) {
SET_STATE(us_g_gpio.stream_online, state); SET_ON(us_g_gpio.stream_online, on);
} }
INLINE void us_gpio_set_has_http_clients(bool state) { INLINE void us_gpio_set_has_http_clients(bool on) {
SET_STATE(us_g_gpio.has_http_clients, state); SET_ON(us_g_gpio.has_http_clients, on);
} }
#undef SET_STATE #undef SET_ON

View File

@@ -67,6 +67,6 @@ const char *us_guess_mime_type(const char *path) {
} }
}); });
misc: misc:
return "application/misc"; return "application/misc";
} }

View File

@@ -31,7 +31,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <assert.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -75,17 +74,17 @@
#endif #endif
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server); static int _http_preprocess_request(struct evhttp_request *req, us_server_s *server);
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server); static int _http_check_run_compat_action(struct evhttp_request *req, void *v_server);
static void _http_callback_root(struct evhttp_request *request, void *v_server); static void _http_callback_root(struct evhttp_request *req, void *v_server);
static void _http_callback_favicon(struct evhttp_request *request, void *v_server); static void _http_callback_favicon(struct evhttp_request *req, void *v_server);
static void _http_callback_static(struct evhttp_request *request, void *v_server); static void _http_callback_static(struct evhttp_request *req, void *v_server);
static void _http_callback_state(struct evhttp_request *request, void *v_server); static void _http_callback_state(struct evhttp_request *req, void *v_server);
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server); static void _http_callback_snapshot(struct evhttp_request *req, void *v_server);
static void _http_callback_stream(struct evhttp_request *request, void *v_server); static void _http_callback_stream(struct evhttp_request *req, void *v_server);
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_ctx); static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_ctx);
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx); static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
@@ -96,18 +95,18 @@ static void _http_send_snapshot(us_server_s *server);
static bool _expose_frame(us_server_s *server, const us_frame_s *frame); static bool _expose_frame(us_server_s *server, const us_frame_s *frame);
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("HTTP: " x_msg, ##__VA_ARGS__) #define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("HTTP: " x_msg, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("HTTP: " x_msg, ##__VA_ARGS__) #define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("HTTP: " x_msg, ##__VA_ARGS__)
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("HTTP: " x_msg, ##__VA_ARGS__) #define _LOG_INFO(x_msg, ...) US_LOG_INFO("HTTP: " x_msg, ##__VA_ARGS__)
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("HTTP: " x_msg, ##__VA_ARGS__) #define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("HTTP: " x_msg, ##__VA_ARGS__)
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("HTTP: " x_msg, ##__VA_ARGS__) #define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("HTTP: " x_msg, ##__VA_ARGS__)
#define _A_EVBUFFER_NEW(x_buf) assert((x_buf = evbuffer_new()) != NULL) #define _A_EVBUFFER_NEW(x_buf) US_A((x_buf = evbuffer_new()) != NULL)
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) assert(!evbuffer_add(x_buf, x_data, x_size)) #define _A_EVBUFFER_ADD(x_buf, x_data, x_size) US_A(!evbuffer_add(x_buf, x_data, x_size))
#define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) assert(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0) #define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) US_A(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0)
#define _A_ADD_HEADER(x_request, x_key, x_value) \ #define _A_ADD_HEADER(x_req, x_key, x_value) \
assert(!evhttp_add_header(evhttp_request_get_output_headers(x_request), x_key, x_value)) US_A(!evhttp_add_header(evhttp_request_get_output_headers(x_req), x_key, x_value))
us_server_s *us_server_init(us_stream_s *stream) { us_server_s *us_server_init(us_stream_s *stream) {
@@ -135,9 +134,9 @@ us_server_s *us_server_init(us_stream_s *stream) {
server->stream = stream; server->stream = stream;
server->run = run; server->run = run;
assert(!evthread_use_pthreads()); US_A(!evthread_use_pthreads());
assert((run->base = event_base_new()) != NULL); US_A((run->base = event_base_new()) != NULL);
assert((run->http = evhttp_new(run->base)) != NULL); US_A((run->http = evhttp_new(run->base)) != NULL);
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD|EVHTTP_REQ_OPTIONS); evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD|EVHTTP_REQ_OPTIONS);
return server; return server;
} }
@@ -188,26 +187,18 @@ int us_server_listen(us_server_s *server) {
_LOG_INFO("Enabling the file server: %s", server->static_path); _LOG_INFO("Enabling the file server: %s", server->static_path);
evhttp_set_gencb(run->http, _http_callback_static, (void*)server); evhttp_set_gencb(run->http, _http_callback_static, (void*)server);
} else { } else {
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, (void*)server)); US_A(!evhttp_set_cb(run->http, "/", _http_callback_root, (void*)server));
assert(!evhttp_set_cb(run->http, "/favicon.ico", _http_callback_favicon, (void*)server)); US_A(!evhttp_set_cb(run->http, "/favicon.ico", _http_callback_favicon, (void*)server));
} }
assert(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void*)server)); US_A(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void*)server));
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void*)server)); US_A(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void*)server));
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void*)server)); US_A(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void*)server));
} }
us_frame_copy(stream->run->blank->jpeg, ex->frame); us_frame_copy(stream->run->blank->jpeg, ex->frame);
{ US_A((run->refresher = event_new(run->base, -1, 0, _http_refresher, server)) != NULL);
struct timeval interval = {0}; stream->run->http->jpeg_refresher = run->refresher;
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));
}
evhttp_set_timeout(run->http, server->timeout); evhttp_set_timeout(run->http, server->timeout);
@@ -268,66 +259,66 @@ void us_server_loop_break(us_server_s *server) {
event_base_loopbreak(server->run->base); event_base_loopbreak(server->run->base);
} }
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server) { static int _http_preprocess_request(struct evhttp_request *req, us_server_s *server) {
const us_server_runtime_s *const run = server->run; const us_server_runtime_s *const run = server->run;
atomic_store(&server->stream->run->http->last_request_ts, us_get_now_monotonic()); atomic_store(&server->stream->run->http->last_req_ts, us_get_now_monotonic());
if (server->allow_origin[0] != '\0') { if (server->allow_origin[0] != '\0') {
const char *const cors_headers = us_evhttp_get_header(request, "Access-Control-Request-Headers"); const char *const cors_headers = us_evhttp_get_header(req, "Access-Control-Request-Headers");
const char *const cors_method = us_evhttp_get_header(request, "Access-Control-Request-Method"); const char *const cors_method = us_evhttp_get_header(req, "Access-Control-Request-Method");
_A_ADD_HEADER(request, "Access-Control-Allow-Origin", server->allow_origin); _A_ADD_HEADER(req, "Access-Control-Allow-Origin", server->allow_origin);
_A_ADD_HEADER(request, "Access-Control-Allow-Credentials", "true"); _A_ADD_HEADER(req, "Access-Control-Allow-Credentials", "true");
if (cors_headers != NULL) { if (cors_headers != NULL) {
_A_ADD_HEADER(request, "Access-Control-Allow-Headers", cors_headers); _A_ADD_HEADER(req, "Access-Control-Allow-Headers", cors_headers);
} }
if (cors_method != NULL) { if (cors_method != NULL) {
_A_ADD_HEADER(request, "Access-Control-Allow-Methods", cors_method); _A_ADD_HEADER(req, "Access-Control-Allow-Methods", cors_method);
} }
} }
if (evhttp_request_get_command(request) == EVHTTP_REQ_OPTIONS) { if (evhttp_request_get_command(req) == EVHTTP_REQ_OPTIONS) {
evhttp_send_reply(request, HTTP_OK, "OK", NULL); evhttp_send_reply(req, HTTP_OK, "OK", NULL);
return -1; return -1;
} }
if (run->auth_token != NULL) { if (run->auth_token != NULL) {
const char *const token = us_evhttp_get_header(request, "Authorization"); const char *const token = us_evhttp_get_header(req, "Authorization");
if (token == NULL || strcmp(token, run->auth_token) != 0) { if (token == NULL || strcmp(token, run->auth_token) != 0) {
_A_ADD_HEADER(request, "WWW-Authenticate", "Basic realm=\"Restricted area\""); _A_ADD_HEADER(req, "WWW-Authenticate", "Basic realm=\"Restricted area\"");
evhttp_send_reply(request, 401, "Unauthorized", NULL); evhttp_send_reply(req, 401, "Unauthorized", NULL);
return -1; return -1;
} }
} }
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { if (evhttp_request_get_command(req) == EVHTTP_REQ_HEAD) {
evhttp_send_reply(request, HTTP_OK, "OK", NULL); evhttp_send_reply(req, HTTP_OK, "OK", NULL);
return -1; return -1;
} }
return 0; return 0;
} }
#define PREPROCESS_REQUEST { \ #define PREPROCESS_REQUEST { \
if (_http_preprocess_request(request, server) < 0) { \ if (_http_preprocess_request(req, server) < 0) { \
return; \ return; \
} \ } \
} }
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server) { static int _http_check_run_compat_action(struct evhttp_request *req, void *v_server) {
// MJPG-Streamer compatibility layer // MJPG-Streamer compatibility layer
int retval = -1; int retval = -1;
struct evkeyvalq params; struct evkeyvalq params;
evhttp_parse_query(evhttp_request_get_uri(request), &params); evhttp_parse_query(evhttp_request_get_uri(req), &params);
const char *const action = evhttp_find_header(&params, "action"); const char *const action = evhttp_find_header(&params, "action");
if (action && !strcmp(action, "snapshot")) { if (action && !strcmp(action, "snapshot")) {
_http_callback_snapshot(request, v_server); _http_callback_snapshot(req, v_server);
retval = 0; retval = 0;
} else if (action && !strcmp(action, "stream")) { } else if (action && !strcmp(action, "stream")) {
_http_callback_stream(request, v_server); _http_callback_stream(req, v_server);
retval = 0; retval = 0;
} }
@@ -336,12 +327,12 @@ static int _http_check_run_compat_action(struct evhttp_request *request, void *v
} }
#define COMPAT_REQUEST { \ #define COMPAT_REQUEST { \
if (_http_check_run_compat_action(request, v_server) == 0) { \ if (_http_check_run_compat_action(req, v_server) == 0) { \
return; \ return; \
} \ } \
} }
static void _http_callback_root(struct evhttp_request *request, void *v_server) { static void _http_callback_root(struct evhttp_request *req, void *v_server) {
us_server_s *const server = v_server; us_server_s *const server = v_server;
PREPROCESS_REQUEST; PREPROCESS_REQUEST;
@@ -350,13 +341,13 @@ static void _http_callback_root(struct evhttp_request *request, void *v_server)
struct evbuffer *buf; struct evbuffer *buf;
_A_EVBUFFER_NEW(buf); _A_EVBUFFER_NEW(buf);
_A_EVBUFFER_ADD_PRINTF(buf, "%s", US_HTML_INDEX_PAGE); _A_EVBUFFER_ADD_PRINTF(buf, "%s", US_HTML_INDEX_PAGE);
_A_ADD_HEADER(request, "Content-Type", "text/html"); _A_ADD_HEADER(req, "Content-Type", "text/html");
evhttp_send_reply(request, HTTP_OK, "OK", buf); evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf); evbuffer_free(buf);
} }
static void _http_callback_favicon(struct evhttp_request *request, void *v_server) { static void _http_callback_favicon(struct evhttp_request *req, void *v_server) {
us_server_s *const server = v_server; us_server_s *const server = v_server;
PREPROCESS_REQUEST; PREPROCESS_REQUEST;
@@ -364,13 +355,13 @@ static void _http_callback_favicon(struct evhttp_request *request, void *v_serve
struct evbuffer *buf; struct evbuffer *buf;
_A_EVBUFFER_NEW(buf); _A_EVBUFFER_NEW(buf);
_A_EVBUFFER_ADD(buf, (const void*)US_FAVICON_ICO_DATA, US_FAVICON_ICO_DATA_SIZE); _A_EVBUFFER_ADD(buf, (const void*)US_FAVICON_ICO_DATA, US_FAVICON_ICO_DATA_SIZE);
_A_ADD_HEADER(request, "Content-Type", "image/x-icon"); _A_ADD_HEADER(req, "Content-Type", "image/x-icon");
evhttp_send_reply(request, HTTP_OK, "OK", buf); evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf); evbuffer_free(buf);
} }
static void _http_callback_static(struct evhttp_request *request, void *v_server) { static void _http_callback_static(struct evhttp_request *req, void *v_server) {
us_server_s *const server = v_server; us_server_s *const server = v_server;
PREPROCESS_REQUEST; PREPROCESS_REQUEST;
@@ -384,7 +375,7 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
{ {
const char *uri_path; const char *uri_path;
if ((uri = evhttp_uri_parse(evhttp_request_get_uri(request))) == NULL) { if ((uri = evhttp_uri_parse(evhttp_request_get_uri(req))) == NULL) {
goto bad_request; goto bad_request;
} }
if ((uri_path = (char*)evhttp_uri_get_path(uri)) == NULL) { if ((uri_path = (char*)evhttp_uri_get_path(uri)) == NULL) {
@@ -421,17 +412,17 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
// and will close it when finished transferring data // and will close it when finished transferring data
fd = -1; fd = -1;
_A_ADD_HEADER(request, "Content-Type", us_guess_mime_type(static_path)); _A_ADD_HEADER(req, "Content-Type", us_guess_mime_type(static_path));
evhttp_send_reply(request, HTTP_OK, "OK", buf); evhttp_send_reply(req, HTTP_OK, "OK", buf);
goto cleanup; goto cleanup;
} }
bad_request: bad_request:
evhttp_send_error(request, HTTP_BADREQUEST, NULL); evhttp_send_error(req, HTTP_BADREQUEST, NULL);
goto cleanup; goto cleanup;
not_found: not_found:
evhttp_send_error(request, HTTP_NOTFOUND, NULL); evhttp_send_error(req, HTTP_NOTFOUND, NULL);
goto cleanup; goto cleanup;
cleanup: cleanup:
@@ -444,7 +435,7 @@ cleanup:
#undef COMPAT_REQUEST #undef COMPAT_REQUEST
static void _http_callback_state(struct evhttp_request *request, void *v_server) { static void _http_callback_state(struct evhttp_request *req, void *v_server) {
us_server_s *const server = v_server; us_server_s *const server = v_server;
us_server_runtime_s *const run = server->run; us_server_runtime_s *const run = server->run;
us_server_exposed_s *const ex = run->exposed; us_server_exposed_s *const ex = run->exposed;
@@ -459,74 +450,75 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
struct evbuffer *buf; struct evbuffer *buf;
_A_EVBUFFER_NEW(buf); _A_EVBUFFER_NEW(buf);
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"{\"ok\": true, \"result\": {" "{\"ok\": true, \"result\": {"
" \"instance_id\": \"%s\"," " \"instance_id\": \"%s\","
" \"encoder\": {\"type\": \"%s\", \"quality\": %u},", " \"encoder\": {\"type\": \"%s\", \"quality\": %u},",
server->instance_id, server->instance_id,
us_encoder_type_to_string(enc_type), us_encoder_type_to_string(enc_type),
enc_quality enc_quality);
);
# ifdef WITH_V4P # ifdef WITH_V4P
if (stream->drm != NULL) { if (stream->drm != NULL) {
us_fpsi_meta_s meta; us_fpsi_meta_s meta;
const uint fps = us_fpsi_get(stream->run->http->drm_fpsi, &meta); const uint fps = us_fpsi_get(stream->run->http->drm_fpsi, &meta);
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
" \"drm\": {\"live\": %s, \"fps\": %u},", " \"drm\": {\"live\": %s, \"fps\": %u},",
us_bool_to_string(meta.online), us_bool_to_string(meta.online),
fps fps);
);
} }
# endif # endif
if (stream->h264_sink != NULL) { if (stream->h264_sink != NULL) {
us_fpsi_meta_s meta; us_fpsi_meta_s meta;
const uint fps = us_fpsi_get(stream->run->http->h264_fpsi, &meta); const uint fps = us_fpsi_get(stream->run->http->h264_fpsi, &meta);
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s, \"fps\": %u},", " \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s, \"fps\": %u},",
stream->h264_bitrate, stream->h264_bitrate,
stream->h264_gop, stream->h264_gop,
us_bool_to_string(meta.online), us_bool_to_string(meta.online),
fps fps);
);
} }
if (stream->jpeg_sink != NULL || stream->h264_sink != NULL) { if (stream->jpeg_sink != NULL || stream->h264_sink != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf, " \"sinks\": {"); _A_EVBUFFER_ADD_PRINTF(buf, " \"sinks\": {");
if (stream->jpeg_sink != NULL) { if (stream->jpeg_sink != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"\"jpeg\": {\"has_clients\": %s}", "\"jpeg\": {\"has_clients\": %s}",
us_bool_to_string(atomic_load(&stream->jpeg_sink->has_clients)) us_bool_to_string(atomic_load(&stream->jpeg_sink->has_clients)));
);
} }
if (stream->h264_sink != NULL) { if (stream->h264_sink != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"%s\"h264\": {\"has_clients\": %s}", "%s\"h264\": {\"has_clients\": %s}",
(stream->jpeg_sink ? ", " : ""), (stream->jpeg_sink ? ", " : ""),
us_bool_to_string(atomic_load(&stream->h264_sink->has_clients)) us_bool_to_string(atomic_load(&stream->h264_sink->has_clients)));
);
} }
_A_EVBUFFER_ADD_PRINTF(buf, "},"); _A_EVBUFFER_ADD_PRINTF(buf, "},");
} }
us_fpsi_meta_s captured_meta; us_fpsi_meta_s captured_meta;
const uint captured_fps = us_fpsi_get(stream->run->http->captured_fpsi, &captured_meta); const uint captured_fps = us_fpsi_get(stream->run->http->captured_fpsi, &captured_meta);
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
" \"source\": {\"resolution\": {\"width\": %u, \"height\": %u}," " \"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
" \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u}," " \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u},"
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {", " \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
(server->fake_width ? server->fake_width : captured_meta.width), (server->fake_width ? server->fake_width : captured_meta.width),
(server->fake_height ? server->fake_height : captured_meta.height), (server->fake_height ? server->fake_height : captured_meta.height),
us_bool_to_string(captured_meta.online), us_bool_to_string(captured_meta.online),
stream->cap->desired_fps, stream->desired_fps,
captured_fps, captured_fps,
us_fpsi_get(ex->queued_fpsi, NULL), us_fpsi_get(ex->queued_fpsi, NULL),
run->stream_clients_count run->stream_clients_count);
);
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s," "\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
" \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s", " \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s",
client->id, client->id,
@@ -536,18 +528,17 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
us_bool_to_string(client->dual_final_frames), us_bool_to_string(client->dual_final_frames),
us_bool_to_string(client->zero_data), us_bool_to_string(client->zero_data),
(client->key != NULL ? client->key : "0"), (client->key != NULL ? client->key : "0"),
(client->next ? ", " : "") (client->next ? ", " : ""));
);
}); });
_A_EVBUFFER_ADD_PRINTF(buf, "}}}}"); _A_EVBUFFER_ADD_PRINTF(buf, "}}}}");
_A_ADD_HEADER(request, "Content-Type", "application/json"); _A_ADD_HEADER(req, "Content-Type", "application/json");
evhttp_send_reply(request, HTTP_OK, "OK", buf); evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf); evbuffer_free(buf);
} }
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server) { static void _http_callback_snapshot(struct evhttp_request *req, void *v_server) {
us_server_s *const server = v_server; us_server_s *const server = v_server;
PREPROCESS_REQUEST; PREPROCESS_REQUEST;
@@ -555,14 +546,14 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
us_snapshot_client_s *client; us_snapshot_client_s *client;
US_CALLOC(client, 1); US_CALLOC(client, 1);
client->server = server; client->server = server;
client->request = request; client->req = req;
client->request_ts = us_get_now_monotonic(); client->req_ts = us_get_now_monotonic();
atomic_fetch_add(&server->stream->run->http->snapshot_requested, 1); atomic_fetch_add(&server->stream->run->http->snapshot_requested, 1);
US_LIST_APPEND(server->run->snapshot_clients, client); US_LIST_APPEND(server->run->snapshot_clients, client);
} }
static void _http_callback_stream(struct evhttp_request *request, void *v_server) { static void _http_callback_stream(struct evhttp_request *req, void *v_server) {
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2814 // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2814
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2789 // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2789
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L362 // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L362
@@ -574,17 +565,17 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
PREPROCESS_REQUEST; PREPROCESS_REQUEST;
struct evhttp_connection *const conn = evhttp_request_get_connection(request); struct evhttp_connection *const conn = evhttp_request_get_connection(req);
if (conn != NULL) { if (conn != NULL) {
us_stream_client_s *client; us_stream_client_s *client;
US_CALLOC(client, 1); US_CALLOC(client, 1);
client->server = server; client->server = server;
client->request = request; client->req = req;
client->need_initial = true; client->need_initial = true;
client->need_first_frame = true; client->need_first_frame = true;
struct evkeyvalq params; struct evkeyvalq params;
evhttp_parse_query(evhttp_request_get_uri(request), &params); evhttp_parse_query(evhttp_request_get_uri(req), &params);
# define PARSE_PARAM(x_type, x_name) client->x_name = us_evkeyvalq_get_##x_type(&params, #x_name) # define PARSE_PARAM(x_type, x_name) client->x_name = us_evkeyvalq_get_##x_type(&params, #x_name)
PARSE_PARAM(string, key); PARSE_PARAM(string, key);
PARSE_PARAM(true, extra_headers); PARSE_PARAM(true, extra_headers);
@@ -594,7 +585,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
# undef PARSE_PARAM # undef PARSE_PARAM
evhttp_clear_headers(&params); evhttp_clear_headers(&params);
client->hostport = us_evhttp_get_hostport(request); client->hostport = us_evhttp_get_hostport(req);
client->id = us_get_now_id(); client->id = us_get_now_id();
{ {
@@ -620,7 +611,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
if (server->tcp_nodelay && run->ext_fd >= 0) { if (server->tcp_nodelay && run->ext_fd >= 0) {
_LOG_DEBUG("Setting up TCP_NODELAY to the client %s ...", client->hostport); _LOG_DEBUG("Setting up TCP_NODELAY to the client %s ...", client->hostport);
const evutil_socket_t fd = bufferevent_getfd(buf_event); const evutil_socket_t fd = bufferevent_getfd(buf_event);
assert(fd >= 0); US_A(fd >= 0);
int on = 1; int on = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) != 0) { if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) != 0) {
_LOG_PERROR("Can't set TCP_NODELAY to the client %s", client->hostport); _LOG_PERROR("Can't set TCP_NODELAY to the client %s", client->hostport);
@@ -629,7 +620,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void*)client); bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void*)client);
bufferevent_enable(buf_event, EV_READ); bufferevent_enable(buf_event, EV_READ);
} else { } else {
evhttp_request_free(request); evhttp_request_free(req);
} }
} }
@@ -667,21 +658,24 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
# define BOUNDARY "boundarydonotcross" # define BOUNDARY "boundarydonotcross"
# define ADD_ADVANCE_HEADERS \ # define ADD_ADVANCE_HEADERS \
_A_EVBUFFER_ADD_PRINTF(buf, \ _A_EVBUFFER_ADD_PRINTF( \
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, us_get_now_real()) buf, \
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, \
us_get_now_real())
if (client->need_initial) { if (client->need_initial) {
_A_EVBUFFER_ADD_PRINTF(buf, "HTTP/1.0 200 OK" RN); _A_EVBUFFER_ADD_PRINTF(buf, "HTTP/1.0 200 OK" RN);
if (client->server->allow_origin[0] != '\0') { if (client->server->allow_origin[0] != '\0') {
const char *const cors_headers = us_evhttp_get_header(client->request, "Access-Control-Request-Headers"); const char *const cors_headers = us_evhttp_get_header(client->req, "Access-Control-Request-Headers");
const char *const cors_method = us_evhttp_get_header(client->request, "Access-Control-Request-Method"); const char *const cors_method = us_evhttp_get_header(client->req, "Access-Control-Request-Method");
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"Access-Control-Allow-Origin: %s" RN "Access-Control-Allow-Origin: %s" RN
"Access-Control-Allow-Credentials: true" RN, "Access-Control-Allow-Credentials: true" RN,
client->server->allow_origin client->server->allow_origin);
);
if (cors_headers != NULL) { if (cors_headers != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf, "Access-Control-Allow-Headers: %s" RN, cors_headers); _A_EVBUFFER_ADD_PRINTF(buf, "Access-Control-Allow-Headers: %s" RN, cors_headers);
} }
@@ -690,7 +684,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
} }
} }
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN "Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
"Pragma: no-cache" RN "Pragma: no-cache" RN
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN "Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
@@ -701,36 +696,38 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
(server->instance_id[0] == '\0' ? "" : "_"), (server->instance_id[0] == '\0' ? "" : "_"),
server->instance_id, server->instance_id,
(client->key != NULL ? client->key : "0"), (client->key != NULL ? client->key : "0"),
client->id client->id);
);
if (client->advance_headers) { if (client->advance_headers) {
ADD_ADVANCE_HEADERS; ADD_ADVANCE_HEADERS;
} }
assert(!bufferevent_write_buffer(buf_event, buf)); US_A(!bufferevent_write_buffer(buf_event, buf));
client->need_initial = false; client->need_initial = false;
} }
if (!client->advance_headers) { if (!client->advance_headers) {
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"Content-Type: image/jpeg" RN "Content-Type: image/jpeg" RN
"Content-Length: %zu" RN "Content-Length: %zu" RN
"X-Timestamp: %.06Lf" RN "X-Timestamp: %.06Lf" RN
"%s", "%s",
(!client->zero_data ? ex->frame->used : 0), (!client->zero_data ? ex->frame->used : 0),
us_get_now_real(), us_get_now_real(),
(client->extra_headers ? "" : RN) (client->extra_headers ? "" : RN));
);
const ldf now_ts = us_get_now_monotonic(); const ldf now_ts = us_get_now_monotonic();
if (client->extra_headers) { if (client->extra_headers) {
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"X-UStreamer-Online: %s" RN "X-UStreamer-Online: %s" RN
"X-UStreamer-Dropped: %u" RN "X-UStreamer-Dropped: %u" RN
"X-UStreamer-Width: %u" RN "X-UStreamer-Width: %u" RN
"X-UStreamer-Height: %u" RN "X-UStreamer-Height: %u" RN
"X-UStreamer-Client-FPS: %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-Begin-Time: %.06Lf" RN
"X-UStreamer-Encode-End-Time: %.06Lf" RN "X-UStreamer-Encode-End-Time: %.06Lf" RN
"X-UStreamer-Expose-Begin-Time: %.06Lf" RN "X-UStreamer-Expose-Begin-Time: %.06Lf" RN
@@ -744,15 +741,15 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
ex->frame->width, ex->frame->width,
ex->frame->height, ex->frame->height,
us_fpsi_get(client->fpsi, NULL), 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_begin_ts,
ex->frame->encode_end_ts, ex->frame->encode_end_ts,
ex->expose_begin_ts, ex->expose_begin_ts,
ex->expose_cmp_ts, ex->expose_cmp_ts,
ex->expose_end_ts, ex->expose_end_ts,
now_ts, now_ts,
now_ts - ex->frame->grab_ts now_ts - ex->frame->grab_begin_ts);
);
} }
} }
@@ -765,7 +762,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
ADD_ADVANCE_HEADERS; ADD_ADVANCE_HEADERS;
} }
assert(!bufferevent_write_buffer(buf_event, buf)); US_A(!bufferevent_write_buffer(buf_event, buf));
evbuffer_free(buf); evbuffer_free(buf);
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void*)client); bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void*)client);
@@ -797,7 +794,7 @@ static void _http_callback_stream_error(struct bufferevent *buf_event, short wha
run->stream_clients_count, client->hostport, client->id, reason); run->stream_clients_count, client->hostport, client->id, reason);
free(reason); free(reason);
struct evhttp_connection *conn = evhttp_request_get_connection(client->request); struct evhttp_connection *conn = evhttp_request_get_connection(client->req);
US_DELETE(conn, evhttp_connection_free); US_DELETE(conn, evhttp_connection_free);
us_fpsi_destroy(client->fpsi); us_fpsi_destroy(client->fpsi);
@@ -814,7 +811,7 @@ static void _http_send_stream(us_server_s *server, bool stream_updated, bool fra
bool has_clients = true; bool has_clients = true;
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
struct evhttp_connection *const conn = evhttp_request_get_connection(client->request); struct evhttp_connection *const conn = evhttp_request_get_connection(client->req);
if (conn != NULL) { if (conn != NULL) {
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов, // Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой, // WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
@@ -858,29 +855,29 @@ static void _http_send_snapshot(us_server_s *server) {
# define ADD_TIME_HEADER(x_key, x_value) { \ # define ADD_TIME_HEADER(x_key, x_value) { \
US_SNPRINTF(header_buf, 255, "%.06Lf", x_value); \ US_SNPRINTF(header_buf, 255, "%.06Lf", x_value); \
_A_ADD_HEADER(request, x_key, header_buf); \ _A_ADD_HEADER(req, x_key, header_buf); \
} }
# define ADD_UNSIGNED_HEADER(x_key, x_value) { \ # define ADD_UNSIGNED_HEADER(x_key, x_value) { \
US_SNPRINTF(header_buf, 255, "%u", x_value); \ US_SNPRINTF(header_buf, 255, "%u", x_value); \
_A_ADD_HEADER(request, x_key, header_buf); \ _A_ADD_HEADER(req, x_key, header_buf); \
} }
us_fpsi_meta_s captured_meta; us_fpsi_meta_s captured_meta;
us_fpsi_get(server->stream->run->http->captured_fpsi, &captured_meta); us_fpsi_get(server->stream->run->http->captured_fpsi, &captured_meta);
US_LIST_ITERATE(server->run->snapshot_clients, client, { // cppcheck-suppress constStatement US_LIST_ITERATE(server->run->snapshot_clients, client, { // cppcheck-suppress constStatement
struct evhttp_request *request = client->request; struct evhttp_request *req = client->req;
const bool has_fresh_snapshot = (atomic_load(&server->stream->run->http->snapshot_requested) == 0); const bool has_fresh_snapshot = (atomic_load(&server->stream->run->http->snapshot_requested) == 0);
const bool timed_out = (client->request_ts + US_MAX((uint)1, server->stream->error_delay * 3) < us_get_now_monotonic()); const bool timed_out = (client->req_ts + US_MAX((uint)1, server->stream->error_delay * 3) < us_get_now_monotonic());
if (has_fresh_snapshot || timed_out) { if (has_fresh_snapshot || timed_out) {
us_frame_s *frame = ex->frame; us_frame_s *frame = ex->frame;
if (!captured_meta.online) { if (!captured_meta.online) {
if (blank == NULL) { if (blank == NULL) {
blank = us_blank_init(); 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; frame = blank->jpeg;
} }
@@ -889,25 +886,26 @@ static void _http_send_snapshot(us_server_s *server) {
_A_EVBUFFER_NEW(buf); _A_EVBUFFER_NEW(buf);
_A_EVBUFFER_ADD(buf, (const void*)frame->data, frame->used); _A_EVBUFFER_ADD(buf, (const void*)frame->data, frame->used);
_A_ADD_HEADER(request, "Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0"); _A_ADD_HEADER(req, "Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
_A_ADD_HEADER(request, "Pragma", "no-cache"); _A_ADD_HEADER(req, "Pragma", "no-cache");
_A_ADD_HEADER(request, "Expires", "Mon, 3 Jan 2000 12:34:56 GMT"); _A_ADD_HEADER(req, "Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
char header_buf[256]; char header_buf[256];
ADD_TIME_HEADER("X-Timestamp", us_get_now_real()); ADD_TIME_HEADER("X-Timestamp", us_get_now_real());
_A_ADD_HEADER(request, "X-UStreamer-Online", us_bool_to_string(frame->online)); _A_ADD_HEADER(req, "X-UStreamer-Online", us_bool_to_string(frame->online));
ADD_UNSIGNED_HEADER("X-UStreamer-Width", frame->width); ADD_UNSIGNED_HEADER("X-UStreamer-Width", frame->width);
ADD_UNSIGNED_HEADER("X-UStreamer-Height", frame->height); 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-Begin-Timestamp", frame->encode_begin_ts);
ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", frame->encode_end_ts); ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", frame->encode_end_ts);
ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", us_get_now_monotonic()); ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", us_get_now_monotonic());
_A_ADD_HEADER(request, "Content-Type", "image/jpeg"); _A_ADD_HEADER(req, "Content-Type", "image/jpeg");
evhttp_send_reply(request, HTTP_OK, "OK", buf); evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf); evbuffer_free(buf);
US_LIST_REMOVE(server->run->snapshot_clients, client); US_LIST_REMOVE(server->run->snapshot_clients, client);
@@ -932,13 +930,15 @@ static void _http_refresher(int fd, short what, void *v_server) {
bool stream_updated = false; bool stream_updated = false;
bool frame_updated = false; bool frame_updated = false;
const int ri = us_ring_consumer_acquire(ring, 0); int ri;
if (ri >= 0) { while ((ri = us_ring_consumer_acquire(ring, 0)) >= 0) {
const us_frame_s *const frame = ring->items[ri]; const us_frame_s *const frame = ring->items[ri];
frame_updated = _expose_frame(server, frame); frame_updated = _expose_frame(server, frame);
stream_updated = true; stream_updated = true;
us_ring_consumer_release(ring, ri); 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 ..."); _LOG_DEBUG("Repeating exposed ...");
ex->expose_begin_ts = us_get_now_monotonic(); ex->expose_begin_ts = us_get_now_monotonic();
ex->expose_cmp_ts = ex->expose_begin_ts; ex->expose_cmp_ts = ex->expose_begin_ts;

View File

@@ -38,7 +38,7 @@
typedef struct { typedef struct {
struct us_server_sx *server; struct us_server_sx *server;
struct evhttp_request *request; struct evhttp_request *req;
char *key; char *key;
bool extra_headers; bool extra_headers;
@@ -59,8 +59,8 @@ typedef struct {
typedef struct { typedef struct {
struct us_server_sx *server; struct us_server_sx *server;
struct evhttp_request *request; struct evhttp_request *req;
ldf request_ts; ldf req_ts;
US_LIST_DECLARE; US_LIST_DECLARE;
} us_snapshot_client_s; } us_snapshot_client_s;

View File

@@ -26,7 +26,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <assert.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -36,17 +35,17 @@
#include "path.h" #include "path.h"
char *us_find_static_file_path(const char *root_path, const char *request_path) { char *us_find_static_file_path(const char *root_path, const char *req_path) {
char *path = NULL; char *path = NULL;
char *const simplified_path = us_simplify_request_path(request_path); char *const simplified_path = us_simplify_request_path(req_path);
if (simplified_path[0] == '\0') { if (simplified_path[0] == '\0') {
US_LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path); US_LOG_VERBOSE("HTTP: Invalid request path %s to static", req_path);
goto error; goto error;
} }
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
assert(sprintf(path, "%s/%s", root_path, simplified_path) > 0); US_A(sprintf(path, "%s/%s", root_path, simplified_path) > 0);
struct stat st; struct stat st;
# define LOAD_STAT { \ # define LOAD_STAT { \

View File

@@ -23,4 +23,4 @@
#pragma once #pragma once
char *us_find_static_file_path(const char *root_path, const char *request_path); char *us_find_static_file_path(const char *root_path, const char *req_path);

View File

@@ -23,7 +23,6 @@
#include "systemd.h" #include "systemd.h"
#include <unistd.h> #include <unistd.h>
#include <assert.h>
#include <event2/http.h> #include <event2/http.h>
#include <event2/util.h> #include <event2/util.h>
@@ -47,7 +46,7 @@ evutil_socket_t us_evhttp_bind_systemd(struct evhttp *http) {
} }
fd = SD_LISTEN_FDS_START; fd = SD_LISTEN_FDS_START;
assert(!evutil_make_socket_nonblocking(fd)); US_A(!evutil_make_socket_nonblocking(fd));
if (evhttp_accept_socket(http, fd) < 0) { if (evhttp_accept_socket(http, fd) < 0) {
US_LOG_PERROR("HTTP: Can't evhttp_accept_socket() systemd socket"); US_LOG_PERROR("HTTP: Can't evhttp_accept_socket() systemd socket");

View File

@@ -25,7 +25,6 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <assert.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
@@ -54,8 +53,8 @@ evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool
addr.sun_family = AF_UNIX; addr.sun_family = AF_UNIX;
const evutil_socket_t fd = socket(AF_UNIX, SOCK_STREAM, 0); const evutil_socket_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
assert(fd >= 0); US_A(fd >= 0);
assert(!evutil_make_socket_nonblocking(fd)); US_A(!evutil_make_socket_nonblocking(fd));
if (rm && unlink(path) < 0) { if (rm && unlink(path) < 0) {
if (errno != ENOENT) { if (errno != ENOENT) {
@@ -82,27 +81,27 @@ evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool
return fd; return fd;
} }
const char *us_evhttp_get_header(struct evhttp_request *request, const char *key) { const char *us_evhttp_get_header(struct evhttp_request *req, const char *key) {
return evhttp_find_header(evhttp_request_get_input_headers(request), key); return evhttp_find_header(evhttp_request_get_input_headers(req), key);
} }
char *us_evhttp_get_hostport(struct evhttp_request *request) { char *us_evhttp_get_hostport(struct evhttp_request *req) {
char *addr = NULL; char *addr = NULL;
unsigned short port = 0; unsigned short port = 0;
struct evhttp_connection *conn = evhttp_request_get_connection(request); struct evhttp_connection *conn = evhttp_request_get_connection(req);
if (conn != NULL) { if (conn != NULL) {
char *peer; char *peer;
evhttp_connection_get_peer(conn, &peer, &port); evhttp_connection_get_peer(conn, &peer, &port);
addr = us_strdup(peer); addr = us_strdup(peer);
} }
const char *xff = us_evhttp_get_header(request, "X-Forwarded-For"); const char *xff = us_evhttp_get_header(req, "X-Forwarded-For");
if (xff != NULL) { if (xff != NULL) {
US_DELETE(addr, free); US_DELETE(addr, free);
assert((addr = strndup(xff, 1024)) != NULL); US_A((addr = strndup(xff, 1024)) != NULL);
for (uint index = 0; addr[index]; ++index) { for (uint i = 0; addr[i]; ++i) {
if (addr[index] == ',') { if (addr[i] == ',') {
addr[index] = '\0'; addr[i] = '\0';
break; break;
} }
} }

View File

@@ -32,8 +32,8 @@
evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode); evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode);
const char *us_evhttp_get_header(struct evhttp_request *request, const char *key); const char *us_evhttp_get_header(struct evhttp_request *req, const char *key);
char *us_evhttp_get_hostport(struct evhttp_request *request); char *us_evhttp_get_hostport(struct evhttp_request *req);
bool us_evkeyvalq_get_true(struct evkeyvalq *params, const char *key); bool us_evkeyvalq_get_true(struct evkeyvalq *params, const char *key);
char *us_evkeyvalq_get_string(struct evkeyvalq *params, const char *key); char *us_evkeyvalq_get_string(struct evkeyvalq *params, const char *key);

View File

@@ -28,7 +28,6 @@
#include <fcntl.h> #include <fcntl.h>
#include <poll.h> #include <poll.h>
#include <errno.h> #include <errno.h>
#include <assert.h>
#include <sys/mman.h> #include <sys/mman.h>
@@ -42,30 +41,44 @@
static us_m2m_encoder_s *_m2m_encoder_init( static us_m2m_encoder_s *_m2m_encoder_init(
const char *name, const char *path, uint output_format, const char *name,
uint bitrate, uint gop, uint quality, bool allow_dma); const char *path,
uint out_format,
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); static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame);
static int _m2m_encoder_init_buffers( static int _m2m_encoder_init_buffers(
us_m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type, us_m2m_encoder_s *enc,
us_m2m_buffer_s **bufs_ptr, uint *n_bufs_ptr, bool dma); const char *name,
enum v4l2_buf_type type,
us_m2m_buffer_s **bufs_ptr,
uint *n_bufs_ptr,
bool dma);
static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc); static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc);
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key); static int _m2m_encoder_compress_raw(
us_m2m_encoder_s *enc,
const us_frame_s *src,
us_frame_s *dest,
bool force_key);
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("%s: " x_msg, enc->name, ##__VA_ARGS__) #define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("%s: " x_msg, enc->name, ##__VA_ARGS__) #define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("%s: " x_msg, enc->name, ##__VA_ARGS__) #define _LOG_INFO(x_msg, ...) US_LOG_INFO("%s: " x_msg, enc->name, ##__VA_ARGS__)
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("%s: " x_msg, enc->name, ##__VA_ARGS__) #define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("%s: " x_msg, enc->name, ##__VA_ARGS__)
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("%s: " x_msg, enc->name, ##__VA_ARGS__) #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 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) { us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path, uint quality) {
@@ -75,13 +88,13 @@ us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path,
double bitrate = log10(quality) * (b_max - b_min) / 2 + b_min; double bitrate = log10(quality) * (b_max - b_min) / 2 + b_min;
bitrate = step * round(bitrate / step); bitrate = step * round(bitrate / step);
bitrate *= 1000; // From Kbps bitrate *= 1000; // From Kbps
assert(bitrate > 0); US_A(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) { us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, uint quality) {
// FIXME: DMA не работает // 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) { void us_m2m_encoder_destroy(us_m2m_encoder_s *enc) {
@@ -95,8 +108,8 @@ void us_m2m_encoder_destroy(us_m2m_encoder_s *enc) {
int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) { int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) {
us_m2m_encoder_runtime_s *const run = enc->run; us_m2m_encoder_runtime_s *const run = enc->run;
uint dest_format = enc->output_format; uint dest_format = enc->out_format;
switch (enc->output_format) { switch (enc->out_format) {
case V4L2_PIX_FMT_JPEG: case V4L2_PIX_FMT_JPEG:
force_key = false; force_key = false;
// fall through // fall through
@@ -138,9 +151,15 @@ 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( static us_m2m_encoder_s *_m2m_encoder_init(
const char *name, const char *path, uint output_format, const char *name,
uint bitrate, uint gop, uint quality, bool allow_dma) { const char *path,
uint out_format,
uint bitrate,
uint gop,
uint quality,
bool allow_dma,
bool boost
) {
US_LOG_INFO("%s: Initializing encoder ...", name); US_LOG_INFO("%s: Initializing encoder ...", name);
us_m2m_encoder_runtime_s *run; us_m2m_encoder_runtime_s *run;
@@ -152,15 +171,16 @@ static us_m2m_encoder_s *_m2m_encoder_init(
US_CALLOC(enc, 1); US_CALLOC(enc, 1);
enc->name = us_strdup(name); enc->name = us_strdup(name);
if (path == NULL) { if (path == NULL) {
enc->path = us_strdup(output_format == V4L2_PIX_FMT_JPEG ? "/dev/video31" : "/dev/video11"); enc->path = us_strdup(out_format == V4L2_PIX_FMT_JPEG ? "/dev/video31" : "/dev/video11");
} else { } else {
enc->path = us_strdup(path); enc->path = us_strdup(path);
} }
enc->output_format = output_format; enc->out_format = out_format;
enc->bitrate = bitrate; enc->bitrate = bitrate;
enc->gop = gop; enc->gop = gop;
enc->quality = quality; enc->quality = quality;
enc->allow_dma = allow_dma; enc->allow_dma = allow_dma;
enc->boost = boost;
enc->run = run; enc->run = run;
return enc; return enc;
} }
@@ -179,7 +199,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
if ( if (
run->p_width == frame->width run->p_width == frame->width
&& run->p_height == frame->height && run->p_height == frame->height
&& run->p_input_format == frame->format && run->p_in_format == frame->format
&& run->p_stride == frame->stride && run->p_stride == frame->stride
&& run->p_dma == dma && run->p_dma == dma
) { ) {
@@ -188,18 +208,18 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
_LOG_INFO("Configuring encoder: DMA=%d ...", dma); _LOG_INFO("Configuring encoder: DMA=%d ...", dma);
_LOG_DEBUG("Encoder changes: width=%u->%u, height=%u->%u, input_format=%u->%u, stride=%u->%u, dma=%u->%u", _LOG_DEBUG("Encoder changes: width=%u->%u, height=%u->%u, in_format=%u->%u, stride=%u->%u, dma=%u->%u",
run->p_width, frame->width, run->p_width, frame->width,
run->p_height, frame->height, run->p_height, frame->height,
run->p_input_format, frame->format, run->p_in_format, frame->format,
run->p_stride, frame->stride, run->p_stride, frame->stride,
run->p_dma, dma); run->p_dma, dma);
_m2m_encoder_cleanup(enc); _m2m_encoder_cleanup(enc);
run->p_width = frame->width; run->p_width = frame->width;
run->p_height = frame->height; run->p_height = frame->height;
run->p_input_format = frame->format; run->p_in_format = frame->format;
run->p_stride = frame->stride; run->p_stride = frame->stride;
run->p_dma = dma; run->p_dma = dma;
@@ -217,21 +237,25 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
_LOG_DEBUG("Configuring option " #x_cid " ..."); \ _LOG_DEBUG("Configuring option " #x_cid " ..."); \
_E_XIOCTL(VIDIOC_S_CTRL, &m_ctl, "Can't set option " #x_cid); \ _E_XIOCTL(VIDIOC_S_CTRL, &m_ctl, "Can't set option " #x_cid); \
} }
if (enc->output_format == V4L2_PIX_FMT_H264) { if (enc->out_format == V4L2_PIX_FMT_H264) {
SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate); SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate);
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, enc->gop); 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); 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 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 { } else {
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_5_1); SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_5_1);
} }
SET_OPTION(V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, 1); SET_OPTION(V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, 1);
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 16); SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 16);
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MAX_QP, 32); SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MAX_QP, 32);
} else if (enc->output_format == V4L2_PIX_FMT_MJPEG) { } else if (enc->out_format == V4L2_PIX_FMT_MJPEG) {
SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate); SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate);
} else if (enc->output_format == V4L2_PIX_FMT_JPEG) { } else if (enc->out_format == V4L2_PIX_FMT_JPEG) {
SET_OPTION(V4L2_CID_JPEG_COMPRESSION_QUALITY, enc->quality); SET_OPTION(V4L2_CID_JPEG_COMPRESSION_QUALITY, enc->quality);
} }
# undef SET_OPTION # undef SET_OPTION
@@ -241,7 +265,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
fmt.fmt.pix_mp.width = run->p_width; fmt.fmt.pix_mp.width = run->p_width;
fmt.fmt.pix_mp.height = run->p_height; fmt.fmt.pix_mp.height = run->p_height;
fmt.fmt.pix_mp.pixelformat = run->p_input_format; fmt.fmt.pix_mp.pixelformat = run->p_in_format;
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // FIXME: Wrong colors fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // FIXME: Wrong colors
fmt.fmt.pix_mp.num_planes = 1; fmt.fmt.pix_mp.num_planes = 1;
@@ -255,31 +279,34 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix_mp.width = run->p_width; fmt.fmt.pix_mp.width = run->p_width;
fmt.fmt.pix_mp.height = run->p_height; fmt.fmt.pix_mp.height = run->p_height;
fmt.fmt.pix_mp.pixelformat = enc->output_format; fmt.fmt.pix_mp.pixelformat = enc->out_format;
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT; fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT;
fmt.fmt.pix_mp.num_planes = 1; fmt.fmt.pix_mp.num_planes = 1;
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0; // fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0;
if (enc->output_format == V4L2_PIX_FMT_H264) { if (enc->out_format == V4L2_PIX_FMT_H264) {
// https://github.com/pikvm/ustreamer/issues/169 // https://github.com/pikvm/ustreamer/issues/169
// https://github.com/raspberrypi/linux/pull/5232 // https://github.com/raspberrypi/linux/pull/5232
fmt.fmt.pix_mp.plane_fmt[0].sizeimage = (1024 + 512) << 10; // 1.5Mb fmt.fmt.pix_mp.plane_fmt[0].sizeimage = (1024 + 512) << 10; // 1.5Mb
} }
_LOG_DEBUG("Configuring OUTPUT format ..."); _LOG_DEBUG("Configuring OUTPUT format ...");
_E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format"); _E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
if (fmt.fmt.pix_mp.pixelformat != enc->output_format) { if (fmt.fmt.pix_mp.pixelformat != enc->out_format) {
char fourcc_str[8]; char fourcc_str[8];
_LOG_ERROR("The OUTPUT format can't be configured as %s", _LOG_ERROR("The OUTPUT format can't be configured as %s",
us_fourcc_to_string(enc->output_format, fourcc_str, 8)); us_fourcc_to_string(enc->out_format, fourcc_str, 8));
_LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt"); _LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt");
goto error; goto error;
} }
} }
if (run->p_width * run->p_height <= 1280 * 720) { if (
(run->p_width * run->p_height <= 1280 * 720)
|| ((enc->out_format == V4L2_PIX_FMT_H264) && enc->boost)
) {
// H264 требует каких-то лимитов. Больше 30 не поддерживается, а при 0 // H264 требует каких-то лимитов. Больше 30 не поддерживается, а при 0
// через какое-то время начинает производить некорректные фреймы. // через какое-то время начинает производить некорректные фреймы.
// Если же привысить fps, то резко увеличивается время кодирования. // Если же превысить fps, то резко увеличивается время кодирования.
run->fps_limit = 60; run->fps_limit = 60;
} else { } else {
run->fps_limit = 30; run->fps_limit = 30;
@@ -298,14 +325,22 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
} }
if (_m2m_encoder_init_buffers( if (_m2m_encoder_init_buffers(
enc, (dma ? "INPUT-DMA" : "INPUT"), V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, enc,
&run->input_bufs, &run->n_input_bufs, dma (dma ? "INPUT-DMA" : "INPUT"),
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
&run->in_bufs,
&run->n_in_bufs,
dma
) < 0) { ) < 0) {
goto error; goto error;
} }
if (_m2m_encoder_init_buffers( if (_m2m_encoder_init_buffers(
enc, "OUTPUT", V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, enc,
&run->output_bufs, &run->n_output_bufs, false "OUTPUT",
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
&run->out_bufs,
&run->n_out_bufs,
false
) < 0) { ) < 0) {
goto error; goto error;
} }
@@ -330,9 +365,13 @@ error:
} }
static int _m2m_encoder_init_buffers( static int _m2m_encoder_init_buffers(
us_m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type, us_m2m_encoder_s *enc,
us_m2m_buffer_s **bufs_ptr, uint *n_bufs_ptr, bool dma) { const char *name,
enum v4l2_buf_type type,
us_m2m_buffer_s **bufs_ptr,
uint *n_bufs_ptr,
bool dma
) {
us_m2m_encoder_runtime_s *const run = enc->run; us_m2m_encoder_runtime_s *const run = enc->run;
_LOG_DEBUG("Initializing %s buffers ...", name); _LOG_DEBUG("Initializing %s buffers ...", name);
@@ -377,7 +416,7 @@ static int _m2m_encoder_init_buffers(
_LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr); _LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr);
goto error; goto error;
} }
assert((*bufs_ptr)[*n_bufs_ptr].data != NULL); US_A((*bufs_ptr)[*n_bufs_ptr].data != NULL);
(*bufs_ptr)[*n_bufs_ptr].allocated = plane.length; (*bufs_ptr)[*n_bufs_ptr].allocated = plane.length;
_LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr); _LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr);
@@ -412,11 +451,11 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
# define DELETE_BUFFERS(x_name, x_target) { \ # define DELETE_BUFFERS(x_name, x_target) { \
if (run->x_target##_bufs != NULL) { \ if (run->x_target##_bufs != NULL) { \
say = true; \ say = true; \
for (uint m_index = 0; m_index < run->n_##x_target##_bufs; ++m_index) { \ for (uint m_i = 0; m_i < run->n_##x_target##_bufs; ++m_i) { \
us_m2m_buffer_s *m_buf = &run->x_target##_bufs[m_index]; \ us_m2m_buffer_s *m_buf = &run->x_target##_bufs[m_i]; \
if (m_buf->allocated > 0 && m_buf->data != NULL) { \ if (m_buf->allocated > 0 && m_buf->data != NULL) { \
if (munmap(m_buf->data, m_buf->allocated) < 0) { \ if (munmap(m_buf->data, m_buf->allocated) < 0) { \
_LOG_PERROR("Can't unmap %s buffer=%u", #x_name, m_index); \ _LOG_PERROR("Can't unmap %s buffer=%u", #x_name, m_i); \
} \ } \
} \ } \
} \ } \
@@ -424,8 +463,8 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
} \ } \
run->n_##x_target##_bufs = 0; \ run->n_##x_target##_bufs = 0; \
} }
DELETE_BUFFERS("OUTPUT", output); DELETE_BUFFERS("OUTPUT", out);
DELETE_BUFFERS("INPUT", input); DELETE_BUFFERS("INPUT", in);
# undef DELETE_BUFFERS # undef DELETE_BUFFERS
if (run->fd >= 0) { if (run->fd >= 0) {
@@ -444,10 +483,15 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
} }
} }
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) { static int _m2m_encoder_compress_raw(
us_m2m_encoder_s *enc,
const us_frame_s *src,
us_frame_s *dest,
bool force_key
) {
us_m2m_encoder_runtime_s *const run = enc->run; us_m2m_encoder_runtime_s *const run = enc->run;
assert(run->ready); US_A(run->ready);
if (force_key) { if (force_key) {
struct v4l2_control ctl = {0}; struct v4l2_control ctl = {0};
@@ -457,28 +501,28 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
_E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe"); _E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe");
} }
struct v4l2_buffer input_buf = {0}; struct v4l2_buffer in_buf = {0};
struct v4l2_plane input_plane = {0}; struct v4l2_plane in_plane = {0};
input_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; in_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
input_buf.length = 1; in_buf.length = 1;
input_buf.m.planes = &input_plane; in_buf.m.planes = &in_plane;
if (run->p_dma) { if (run->p_dma) {
input_buf.index = 0; in_buf.index = 0;
input_buf.memory = V4L2_MEMORY_DMABUF; in_buf.memory = V4L2_MEMORY_DMABUF;
input_buf.field = V4L2_FIELD_NONE; in_buf.field = V4L2_FIELD_NONE;
input_plane.m.fd = src->dma_fd; in_plane.m.fd = src->dma_fd;
_LOG_DEBUG("Using INPUT-DMA buffer=%u", input_buf.index); _LOG_DEBUG("Using INPUT-DMA buffer=%u", in_buf.index);
} else { } else {
input_buf.memory = V4L2_MEMORY_MMAP; in_buf.memory = V4L2_MEMORY_MMAP;
_LOG_DEBUG("Grabbing INPUT buffer ..."); _LOG_DEBUG("Grabbing INPUT buffer ...");
_E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't grab INPUT buffer"); _E_XIOCTL(VIDIOC_DQBUF, &in_buf, "Can't grab INPUT buffer");
if (input_buf.index >= run->n_input_bufs) { if (in_buf.index >= run->n_in_bufs) {
_LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u", _LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u",
input_buf.index, run->n_input_bufs); in_buf.index, run->n_in_bufs);
goto error; goto error;
} }
_LOG_DEBUG("Grabbed INPUT buffer=%u", input_buf.index); _LOG_DEBUG("Grabbed INPUT buffer=%u", in_buf.index);
} }
const u64 now_ts = us_get_now_monotonic_u64(); const u64 now_ts = us_get_now_monotonic_u64();
@@ -487,21 +531,21 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
.tv_usec = now_ts % 1000000, .tv_usec = now_ts % 1000000,
}; };
input_buf.timestamp.tv_sec = ts.tv_sec; in_buf.timestamp.tv_sec = ts.tv_sec;
input_buf.timestamp.tv_usec = ts.tv_usec; in_buf.timestamp.tv_usec = ts.tv_usec;
input_plane.bytesused = src->used; in_plane.bytesused = src->used;
input_plane.length = src->used; in_plane.length = src->used;
if (!run->p_dma) { if (!run->p_dma) {
memcpy(run->input_bufs[input_buf.index].data, src->data, src->used); memcpy(run->in_bufs[in_buf.index].data, src->data, src->used);
} }
const char *input_name = (run->p_dma ? "INPUT-DMA" : "INPUT"); const char *in_name = (run->p_dma ? "INPUT-DMA" : "INPUT");
_LOG_DEBUG("Sending%s %s buffer ...", (!run->p_dma ? " (releasing)" : ""), input_name); _LOG_DEBUG("Sending%s %s buffer ...", (!run->p_dma ? " (releasing)" : ""), in_name);
_E_XIOCTL(VIDIOC_QBUF, &input_buf, "Can't send %s buffer", input_name); _E_XIOCTL(VIDIOC_QBUF, &in_buf, "Can't send %s buffer", in_name);
// Для не-DMA отправка буфера по факту являтся освобождением этого буфера // Для не-DMA отправка буфера по факту являтся освобождением этого буфера
bool input_released = !run->p_dma; bool in_released = !run->p_dma;
// https://github.com/pikvm/ustreamer/issues/253 // https://github.com/pikvm/ustreamer/issues/253
// За секунду точно должно закодироваться. // За секунду точно должно закодироваться.
@@ -521,37 +565,37 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
} }
if (enc_poll.revents & POLLIN) { if (enc_poll.revents & POLLIN) {
if (!input_released) { if (!in_released) {
_LOG_DEBUG("Releasing %s buffer=%u ...", input_name, input_buf.index); _LOG_DEBUG("Releasing %s buffer=%u ...", in_name, in_buf.index);
_E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't release %s buffer=%u", _E_XIOCTL(VIDIOC_DQBUF, &in_buf, "Can't release %s buffer=%u",
input_name, input_buf.index); in_name, in_buf.index);
input_released = true; in_released = true;
} }
struct v4l2_buffer output_buf = {0}; struct v4l2_buffer out_buf = {0};
struct v4l2_plane output_plane = {0}; struct v4l2_plane out_plane = {0};
output_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; out_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
output_buf.memory = V4L2_MEMORY_MMAP; out_buf.memory = V4L2_MEMORY_MMAP;
output_buf.length = 1; out_buf.length = 1;
output_buf.m.planes = &output_plane; out_buf.m.planes = &out_plane;
_LOG_DEBUG("Fetching OUTPUT buffer ..."); _LOG_DEBUG("Fetching OUTPUT buffer ...");
_E_XIOCTL(VIDIOC_DQBUF, &output_buf, "Can't fetch OUTPUT buffer"); _E_XIOCTL(VIDIOC_DQBUF, &out_buf, "Can't fetch OUTPUT buffer");
bool done = false; bool done = false;
if (ts.tv_sec != output_buf.timestamp.tv_sec || ts.tv_usec != output_buf.timestamp.tv_usec) { if (ts.tv_sec != out_buf.timestamp.tv_sec || ts.tv_usec != out_buf.timestamp.tv_usec) {
// Енкодер первый раз может выдать буфер с мусором и нулевым таймстампом, // Енкодер первый раз может выдать буфер с мусором и нулевым таймстампом,
// так что нужно убедиться, что мы читаем выходной буфер, соответствующий // так что нужно убедиться, что мы читаем выходной буфер, соответствующий
// входному (с тем же таймстампом). // входному (с тем же таймстампом).
_LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch"); _LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch");
} else { } else {
us_frame_set_data(dest, run->output_bufs[output_buf.index].data, output_plane.bytesused); us_frame_set_data(dest, run->out_bufs[out_buf.index].data, out_plane.bytesused);
dest->key = output_buf.flags & V4L2_BUF_FLAG_KEYFRAME; dest->key = out_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
dest->gop = enc->gop; dest->gop = enc->gop;
done = true; done = true;
} }
_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", output_buf.index); _LOG_DEBUG("Releasing OUTPUT buffer=%u ...", out_buf.index);
_E_XIOCTL(VIDIOC_QBUF, &output_buf, "Can't release OUTPUT buffer=%u", output_buf.index); _E_XIOCTL(VIDIOC_QBUF, &out_buf, "Can't release OUTPUT buffer=%u", out_buf.index);
if (done) { if (done) {
break; break;

View File

@@ -34,14 +34,14 @@ typedef struct {
typedef struct { typedef struct {
int fd; int fd;
uint fps_limit; uint fps_limit;
us_m2m_buffer_s *input_bufs; us_m2m_buffer_s *in_bufs;
uint n_input_bufs; uint n_in_bufs;
us_m2m_buffer_s *output_bufs; us_m2m_buffer_s *out_bufs;
uint n_output_bufs; uint n_out_bufs;
uint p_width; uint p_width;
uint p_height; uint p_height;
uint p_input_format; uint p_in_format;
uint p_stride; uint p_stride;
bool p_dma; bool p_dma;
@@ -53,17 +53,18 @@ typedef struct {
typedef struct { typedef struct {
char *name; char *name;
char *path; char *path;
uint output_format; uint out_format;
uint bitrate; uint bitrate;
uint gop; uint gop;
uint quality; uint quality;
bool allow_dma; bool allow_dma;
bool boost;
us_m2m_encoder_runtime_s *run; us_m2m_encoder_runtime_s *run;
} us_m2m_encoder_s; } 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_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); 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); void us_m2m_encoder_destroy(us_m2m_encoder_s *enc);

View File

@@ -46,10 +46,10 @@ static us_server_s *_g_server = NULL;
static void _block_thread_signals(void) { static void _block_thread_signals(void) {
sigset_t mask; sigset_t mask;
assert(!sigemptyset(&mask)); US_A(!sigemptyset(&mask));
assert(!sigaddset(&mask, SIGINT)); US_A(!sigaddset(&mask, SIGINT));
assert(!sigaddset(&mask, SIGTERM)); US_A(!sigaddset(&mask, SIGTERM));
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL)); US_A(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
} }
static void *_stream_loop_thread(void *arg) { static void *_stream_loop_thread(void *arg) {
@@ -77,19 +77,19 @@ static void _signal_handler(int signum) {
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
assert(argc >= 0); US_A(argc >= 0);
int exit_code = 0; int exit_code = 0;
US_LOGGING_INIT; US_LOGGING_INIT;
US_THREAD_RENAME("main"); US_THREAD_RENAME("main");
us_options_s *options = us_options_init(argc, argv); us_options_s *opts = us_options_init(argc, argv);
us_capture_s *cap = us_capture_init(); us_capture_s *cap = us_capture_init();
us_encoder_s *enc = us_encoder_init(); us_encoder_s *enc = us_encoder_init();
_g_stream = us_stream_init(cap, enc); _g_stream = us_stream_init(cap, enc);
_g_server = us_server_init(_g_stream); _g_server = us_server_init(_g_stream);
if ((exit_code = options_parse(options, cap, enc, _g_stream, _g_server)) == 0) { if ((exit_code = us_options_parse(opts, cap, enc, _g_stream, _g_server)) == 0) {
us_stream_update_blank(_g_stream, cap); us_stream_update_blank(_g_stream, cap);
# ifdef WITH_GPIO # ifdef WITH_GPIO
us_gpio_init(); us_gpio_init();
@@ -120,7 +120,7 @@ int main(int argc, char *argv[]) {
us_stream_destroy(_g_stream); us_stream_destroy(_g_stream);
us_encoder_destroy(enc); us_encoder_destroy(enc);
us_capture_destroy(cap); us_capture_destroy(cap);
us_options_destroy(options); us_options_destroy(opts);
if (exit_code == 0) { if (exit_code == 0) {
US_LOG_INFO("Bye-bye"); US_LOG_INFO("Bye-bye");

View File

@@ -22,6 +22,35 @@
#include "options.h" #include "options.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <limits.h>
#include <getopt.h>
#include <errno.h>
#include "../libs/types.h"
#include "../libs/const.h"
#include "../libs/tools.h"
#include "../libs/logging.h"
#include "../libs/process.h"
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "../libs/options.h"
#include "../libs/capture.h"
#ifdef WITH_V4P
# include "../libs/drm/drm.h"
#endif
#include "encoder.h"
#include "stream.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio/gpio.h"
#endif
enum _US_OPT_VALUES { enum _US_OPT_VALUES {
_O_DEVICE = 'd', _O_DEVICE = 'd',
@@ -100,6 +129,7 @@ enum _US_OPT_VALUES {
_O_H264_BITRATE, _O_H264_BITRATE,
_O_H264_GOP, _O_H264_GOP,
_O_H264_M2M_DEVICE, _O_H264_M2M_DEVICE,
_O_H264_BOOST,
# undef ADD_SINK # undef ADD_SINK
# ifdef WITH_V4P # ifdef WITH_V4P
@@ -114,9 +144,10 @@ enum _US_OPT_VALUES {
_O_GPIO_HAS_HTTP_CLIENTS, _O_GPIO_HAS_HTTP_CLIENTS,
# endif # endif
# ifdef HAS_PDEATHSIG # ifdef WITH_PDEATHSIG
_O_EXIT_ON_PARENT_DEATH, _O_EXIT_ON_PARENT_DEATH,
# endif # endif
_O_EXIT_ON_DEVICE_ERROR,
_O_EXIT_ON_NO_CLIENTS, _O_EXIT_ON_NO_CLIENTS,
# ifdef WITH_SETPROCTITLE # ifdef WITH_SETPROCTITLE
_O_PROCESS_NAME_PREFIX, _O_PROCESS_NAME_PREFIX,
@@ -138,7 +169,7 @@ static const struct option _LONG_OPTS[] = {
{"input", required_argument, NULL, _O_INPUT}, {"input", required_argument, NULL, _O_INPUT},
{"resolution", required_argument, NULL, _O_RESOLUTION}, {"resolution", required_argument, NULL, _O_RESOLUTION},
{"format", required_argument, NULL, _O_FORMAT}, {"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}, {"tv-standard", required_argument, NULL, _O_TV_STANDARD},
{"io-method", required_argument, NULL, _O_IO_METHOD}, {"io-method", required_argument, NULL, _O_IO_METHOD},
{"desired-fps", required_argument, NULL, _O_DESIRED_FPS}, {"desired-fps", required_argument, NULL, _O_DESIRED_FPS},
@@ -205,6 +236,7 @@ static const struct option _LONG_OPTS[] = {
{"h264-bitrate", required_argument, NULL, _O_H264_BITRATE}, {"h264-bitrate", required_argument, NULL, _O_H264_BITRATE},
{"h264-gop", required_argument, NULL, _O_H264_GOP}, {"h264-gop", required_argument, NULL, _O_H264_GOP},
{"h264-m2m-device", required_argument, NULL, _O_H264_M2M_DEVICE}, {"h264-m2m-device", required_argument, NULL, _O_H264_M2M_DEVICE},
{"h264-boost", no_argument, NULL, _O_H264_BOOST},
// Compatibility // Compatibility
{"sink", required_argument, NULL, _O_JPEG_SINK}, {"sink", required_argument, NULL, _O_JPEG_SINK},
{"sink-mode", required_argument, NULL, _O_JPEG_SINK_MODE}, {"sink-mode", required_argument, NULL, _O_JPEG_SINK_MODE},
@@ -224,9 +256,10 @@ static const struct option _LONG_OPTS[] = {
{"gpio-has-http-clients", required_argument, NULL, _O_GPIO_HAS_HTTP_CLIENTS}, {"gpio-has-http-clients", required_argument, NULL, _O_GPIO_HAS_HTTP_CLIENTS},
# endif # endif
# ifdef HAS_PDEATHSIG # ifdef WITH_PDEATHSIG
{"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH}, {"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH},
# endif # 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}, {"exit-on-no-clients", required_argument, NULL, _O_EXIT_ON_NO_CLIENTS},
# ifdef WITH_SETPROCTITLE # ifdef WITH_SETPROCTITLE
{"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX}, {"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX},
@@ -248,53 +281,66 @@ static const struct option _LONG_OPTS[] = {
}; };
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited); static int _parse_resolution(const char *str, uint *width, uint *height, bool limited);
static int _check_instance_id(const char *str); static int _check_instance_id(const char *str);
static void _features(void); static void _features(void);
static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server);
static void _help(
FILE *fp,
const us_capture_s *cap,
const us_encoder_s *enc,
const us_stream_s *stream,
const us_server_s *server);
us_options_s *us_options_init(unsigned argc, char *argv[]) { us_options_s *us_options_init(uint argc, char *argv[]) {
us_options_s *options; us_options_s *opts;
US_CALLOC(options, 1); US_CALLOC(opts, 1);
options->argc = argc; opts->argc = argc;
options->argv = argv; opts->argv = argv;
US_CALLOC(options->argv_copy, argc); US_CALLOC(opts->argv_copy, argc + 1);
for (unsigned index = 0; index < argc; ++index) { for (uint i = 0; i < argc; ++i) {
options->argv_copy[index] = us_strdup(argv[index]); opts->argv_copy[i] = us_strdup(argv[i]);
} }
return options; opts->argv_copy[argc] = NULL;
return opts;
} }
void us_options_destroy(us_options_s *options) { void us_options_destroy(us_options_s *opts) {
US_DELETE(options->jpeg_sink, us_memsink_destroy); US_DELETE(opts->jpeg_sink, us_memsink_destroy);
US_DELETE(options->raw_sink, us_memsink_destroy); US_DELETE(opts->raw_sink, us_memsink_destroy);
US_DELETE(options->h264_sink, us_memsink_destroy); US_DELETE(opts->h264_sink, us_memsink_destroy);
# ifdef WITH_V4P # ifdef WITH_V4P
US_DELETE(options->drm, us_drm_destroy); US_DELETE(opts->drm, us_drm_destroy);
# endif # endif
for (unsigned index = 0; index < options->argc; ++index) { for (uint i = 0; i < opts->argc; ++i) {
free(options->argv_copy[index]); free(opts->argv_copy[i]);
} }
free(options->argv_copy); free(opts->argv_copy);
free(options); free(opts);
} }
int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) { int us_options_parse(
us_options_s *opts,
us_capture_s *cap,
us_encoder_s *enc,
us_stream_s *stream,
us_server_s *server
) {
# define OPT_SET(x_dest, x_value) { \ # define OPT_SET(x_dest, x_value) { \
x_dest = x_value; \ x_dest = x_value; \
break; \ break; \
} }
# define OPT_NUMBER(x_name, x_dest, x_min, x_max, x_base) { \ # define OPT_NUMBER(x_name, x_dest, x_min, x_max, x_base) { \
errno = 0; char *m_end = NULL; const long long m_tmp = strtoll(optarg, &m_end, x_base); \ errno = 0; char *m_end = NULL; const sll m_tmp = strtoll(optarg, &m_end, x_base); \
if (errno || *m_end || m_tmp < x_min || m_tmp > x_max) { \ if (errno || *m_end || m_tmp < x_min || m_tmp > x_max) { \
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", x_name, optarg, (long long)x_min, (long long)x_max); \ printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", x_name, optarg, (sll)x_min, (sll)x_max); \
return -1; \ return -1; \
} \ } \
x_dest = m_tmp; \ x_dest = m_tmp; \
@@ -313,7 +359,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
printf("Invalid height of '%s=%s': min=%u, max=%u\n", x_name, optarg, US_VIDEO_MIN_HEIGHT, US_VIDEO_MAX_HEIGHT); \ printf("Invalid height of '%s=%s': min=%u, max=%u\n", x_name, optarg, US_VIDEO_MIN_HEIGHT, US_VIDEO_MAX_HEIGHT); \
return -1; \ return -1; \
case 0: break; \ case 0: break; \
default: assert(0 && "Unknown error"); \ default: US_RAISE("Unknown error"); \
} \ } \
break; \ break; \
} }
@@ -358,8 +404,8 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
const char *x_prefix##_name = NULL; \ const char *x_prefix##_name = NULL; \
mode_t x_prefix##_mode = 0660; \ mode_t x_prefix##_mode = 0660; \
bool x_prefix##_rm = false; \ bool x_prefix##_rm = false; \
unsigned x_prefix##_client_ttl = 10; \ uint x_prefix##_client_ttl = 10; \
unsigned x_prefix##_timeout = 1; uint x_prefix##_timeout = 1;
ADD_SINK(jpeg_sink); ADD_SINK(jpeg_sink);
ADD_SINK(raw_sink); ADD_SINK(raw_sink);
ADD_SINK(h264_sink); ADD_SINK(h264_sink);
@@ -372,7 +418,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
char short_opts[128]; char short_opts[128];
us_build_short_options(_LONG_OPTS, short_opts, 128); us_build_short_options(_LONG_OPTS, short_opts, 128);
for (int ch; (ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) { for (int ch; (ch = getopt_long(opts->argc, opts->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) {
switch (ch) { switch (ch) {
case _O_DEVICE: OPT_SET(cap->path, optarg); case _O_DEVICE: OPT_SET(cap->path, optarg);
case _O_INPUT: OPT_NUMBER("--input", cap->input, 0, 128, 0); case _O_INPUT: OPT_NUMBER("--input", cap->input, 0, 128, 0);
@@ -384,7 +430,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_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_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_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_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_ALLOW_TRUNCATED_FRAMES: OPT_SET(cap->allow_truncated_frames, true);
case _O_PERSISTENT: OPT_SET(cap->persistent, true); case _O_PERSISTENT: OPT_SET(cap->persistent, true);
@@ -467,11 +513,12 @@ 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_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_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_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
case _O_H264_BOOST: OPT_SET(stream->h264_boost, true);
# ifdef WITH_V4P # ifdef WITH_V4P
case _O_V4P: case _O_V4P:
options->drm = us_drm_init(); opts->drm = us_drm_init();
stream->drm = options->drm; stream->drm = opts->drm;
break; break;
# endif # endif
@@ -483,13 +530,14 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", us_g_gpio.has_http_clients.pin, 0, 256, 0); case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", us_g_gpio.has_http_clients.pin, 0, 256, 0);
# endif # endif
# ifdef HAS_PDEATHSIG # ifdef WITH_PDEATHSIG
case _O_EXIT_ON_PARENT_DEATH: case _O_EXIT_ON_PARENT_DEATH:
if (us_process_track_parent_death() < 0) { if (us_process_track_parent_death() < 0) {
return -1; return -1;
}; };
break; break;
# endif # 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); case _O_EXIT_ON_NO_CLIENTS: OPT_NUMBER("--exit-on-no-clients", stream->exit_on_no_clients, 0, 86400, 0);
# ifdef WITH_SETPROCTITLE # ifdef WITH_SETPROCTITLE
case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg); case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg);
@@ -516,7 +564,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
# define ADD_SINK(x_label, x_prefix) { \ # define ADD_SINK(x_label, x_prefix) { \
if (x_prefix##_name && x_prefix##_name[0] != '\0') { \ if (x_prefix##_name && x_prefix##_name[0] != '\0') { \
options->x_prefix = us_memsink_init_opened( \ opts->x_prefix = us_memsink_init_opened( \
x_label, \ x_label, \
x_prefix##_name, \ x_prefix##_name, \
true, \ true, \
@@ -526,7 +574,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
x_prefix##_timeout \ x_prefix##_timeout \
); \ ); \
} \ } \
stream->x_prefix = options->x_prefix; \ stream->x_prefix = opts->x_prefix; \
} }
ADD_SINK("JPEG", jpeg_sink); ADD_SINK("JPEG", jpeg_sink);
ADD_SINK("RAW", raw_sink); ADD_SINK("RAW", raw_sink);
@@ -535,7 +583,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
# ifdef WITH_SETPROCTITLE # ifdef WITH_SETPROCTITLE
if (process_name_prefix != NULL) { if (process_name_prefix != NULL) {
us_process_set_name_prefix(options->argc, options->argv, process_name_prefix); us_process_set_name_prefix(opts->argc, opts->argv, process_name_prefix);
} }
# endif # endif
@@ -549,9 +597,9 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
return 0; return 0;
} }
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited) { static int _parse_resolution(const char *str, uint *width, uint *height, bool limited) {
unsigned tmp_width; uint tmp_width;
unsigned tmp_height; uint tmp_height;
if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) { if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) {
return -1; return -1;
} }
@@ -581,38 +629,62 @@ static int _check_instance_id(const char *str) {
} }
static void _features(void) { static void _features(void) {
# ifdef WITH_GPIO # ifdef MK_WITH_PYTHON
puts("+ WITH_PYTHON");
# else
puts("- WITH_PYTHON");
# endif
# ifdef MK_WITH_JANUS
puts("+ WITH_JANUS");
# else
puts("- WITH_JANUS");
# endif
# ifdef MK_WITH_V4P
puts("+ WITH_V4P");
# else
puts("- WITH_V4P");
# endif
# ifdef MK_WITH_GPIO
puts("+ WITH_GPIO"); puts("+ WITH_GPIO");
# else # else
puts("- WITH_GPIO"); puts("- WITH_GPIO");
# endif # endif
# ifdef WITH_SYSTEMD # ifdef MK_WITH_SYSTEMD
puts("+ WITH_SYSTEMD"); puts("+ WITH_SYSTEMD");
# else # else
puts("- WITH_SYSTEMD"); puts("- WITH_SYSTEMD");
# endif # endif
# ifdef WITH_PTHREAD_NP # ifdef MK_WITH_PTHREAD_NP
puts("+ WITH_PTHREAD_NP"); puts("+ WITH_PTHREAD_NP");
# else # else
puts("- WITH_PTHREAD_NP"); puts("- WITH_PTHREAD_NP");
# endif # endif
# ifdef WITH_SETPROCTITLE # ifdef MK_WITH_SETPROCTITLE
puts("+ WITH_SETPROCTITLE"); puts("+ WITH_SETPROCTITLE");
# else # else
puts("- WITH_SETPROCTITLE"); puts("- WITH_SETPROCTITLE");
# endif # endif
# ifdef HAS_PDEATHSIG # ifdef MK_WITH_PDEATHSIG
puts("+ HAS_PDEATHSIG"); puts("+ WITH_PDEATHSIG");
# else # else
puts("- HAS_PDEATHSIG"); puts("- WITH_PDEATHSIG");
# endif # endif
} }
static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server) { static void _help(
FILE *fp,
const us_capture_s *cap,
const us_encoder_s *enc,
const us_stream_s *stream,
const us_server_s *server
) {
# define SAY(x_msg, ...) fprintf(fp, x_msg "\n", ##__VA_ARGS__) # define SAY(x_msg, ...) fprintf(fp, x_msg "\n", ##__VA_ARGS__)
SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer"); SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer");
SAY("═══════════════════════════════════════════════════"); SAY("═══════════════════════════════════════════════════");
@@ -633,7 +705,7 @@ static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, co
SAY(" Changing of this parameter may increase the performance. Or not."); SAY(" Changing of this parameter may increase the performance. Or not.");
SAY(" Available: %s; default: MMAP.\n", US_IO_METHODS_STR); SAY(" Available: %s; default: MMAP.\n", US_IO_METHODS_STR);
SAY(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n"); SAY(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n");
SAY(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device"); SAY(" -z|--min-frame-size <N> ───────────── Drop frames smaller than this limit. Useful if the device");
SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", cap->min_frame_size); SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", cap->min_frame_size);
SAY(" -T|--allow-truncated-frames ───────── Allows to handle truncated frames. Useful if the device"); SAY(" -T|--allow-truncated-frames ───────── Allows to handle truncated frames. Useful if the device");
SAY(" produces incorrect but still acceptable frames. Default: disabled.\n"); SAY(" produces incorrect but still acceptable frames. Default: disabled.\n");
@@ -641,7 +713,7 @@ static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, co
SAY(" -t|--dv-timings ───────────────────── Enable DV-timings querying and events processing"); SAY(" -t|--dv-timings ───────────────────── Enable DV-timings querying and events processing");
SAY(" to automatic resolution change. Default: disabled.\n"); SAY(" to automatic resolution change. Default: disabled.\n");
SAY(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device."); SAY(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device.");
SAY(" Each buffer may processed using an independent thread."); SAY(" Each buffer may be processed using an independent thread.");
SAY(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n", cap->n_bufs); SAY(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n", cap->n_bufs);
SAY(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers."); SAY(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers.");
SAY(" Default: %u (the number of CPU cores (but not more than 4)).\n", enc->n_workers); SAY(" Default: %u (the number of CPU cores (but not more than 4)).\n", enc->n_workers);
@@ -725,6 +797,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-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-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-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 # ifdef WITH_V4P
SAY("Passthrough options for PiKVM V4:"); SAY("Passthrough options for PiKVM V4:");
SAY("═════════════════════════════════"); SAY("═════════════════════════════════");
@@ -740,11 +813,11 @@ static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, co
SAY(" --gpio-stream-online <pin> ──── Set 1 while streaming. Default: disabled.\n"); SAY(" --gpio-stream-online <pin> ──── Set 1 while streaming. Default: disabled.\n");
SAY(" --gpio-has-http-clients <pin> ─ Set 1 while stream has at least one client. Default: disabled.\n"); SAY(" --gpio-has-http-clients <pin> ─ Set 1 while stream has at least one client. Default: disabled.\n");
# endif # endif
# if (defined(HAS_PDEATHSIG) || defined(WITH_SETPROCTITLE)) # if (defined(WITH_PDEATHSIG) || defined(WITH_SETPROCTITLE))
SAY("Process options:"); SAY("Process options:");
SAY("════════════════"); SAY("════════════════");
# endif # endif
# ifdef HAS_PDEATHSIG # ifdef WITH_PDEATHSIG
SAY(" --exit-on-parent-death ─────── Exit the program if the parent process is dead. Default: disabled.\n"); SAY(" --exit-on-parent-death ─────── Exit the program if the parent process is dead. Default: disabled.\n");
# endif # endif
SAY(" --exit-on-no-clients <sec> ──── Exit the program if there have been no stream or sink clients"); SAY(" --exit-on-no-clients <sec> ──── Exit the program if there have been no stream or sink clients");

View File

@@ -22,23 +22,8 @@
#pragma once #pragma once
#include <stdio.h> #include "../libs/types.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <limits.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include "../libs/const.h"
#include "../libs/logging.h"
#include "../libs/process.h"
#include "../libs/frame.h"
#include "../libs/memsink.h" #include "../libs/memsink.h"
#include "../libs/options.h"
#include "../libs/capture.h" #include "../libs/capture.h"
#ifdef WITH_V4P #ifdef WITH_V4P
# include "../libs/drm/drm.h" # include "../libs/drm/drm.h"
@@ -47,13 +32,10 @@
#include "encoder.h" #include "encoder.h"
#include "stream.h" #include "stream.h"
#include "http/server.h" #include "http/server.h"
#ifdef WITH_GPIO
# include "gpio/gpio.h"
#endif
typedef struct { typedef struct {
unsigned argc; uint argc;
char **argv; char **argv;
char **argv_copy; char **argv_copy;
us_memsink_s *jpeg_sink; us_memsink_s *jpeg_sink;
@@ -65,7 +47,12 @@ typedef struct {
} us_options_s; } us_options_s;
us_options_s *us_options_init(unsigned argc, char *argv[]); us_options_s *us_options_init(uint argc, char *argv[]);
void us_options_destroy(us_options_s *options); void us_options_destroy(us_options_s *options);
int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, us_stream_s *stream, us_server_s *server); int us_options_parse(
us_options_s *options,
us_capture_s *cap,
us_encoder_s *enc,
us_stream_s *stream,
us_server_s *server);

View File

@@ -27,10 +27,12 @@
#include <limits.h> #include <limits.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <assert.h> #include <math.h>
#include <pthread.h> #include <pthread.h>
#include <event2/event.h> // jpeg_refresher
#include "../libs/types.h" #include "../libs/types.h"
#include "../libs/errors.h" #include "../libs/errors.h"
#include "../libs/tools.h" #include "../libs/tools.h"
@@ -59,14 +61,14 @@
typedef struct { typedef struct {
pthread_t tid; pthread_t tid;
us_capture_s *cap; us_capture_s *cap;
us_queue_s *queue; us_queue_s *q;
pthread_mutex_t *mutex; pthread_mutex_t *mutex;
atomic_bool *stop; atomic_bool *stop;
} _releaser_context_s; } _releaser_context_s;
typedef struct { typedef struct {
pthread_t tid; pthread_t tid;
us_queue_s *queue; us_queue_s *q;
us_stream_s *stream; us_stream_s *stream;
atomic_bool *stop; atomic_bool *stop;
} _worker_context_s; } _worker_context_s;
@@ -80,7 +82,7 @@ static void *_h264_thread(void *v_ctx);
static void *_drm_thread(void *v_ctx); static void *_drm_thread(void *v_ctx);
#endif #endif
static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *queue); static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *q);
static bool _stream_has_jpeg_clients_cached(us_stream_s *stream); static bool _stream_has_jpeg_clients_cached(us_stream_s *stream);
static bool _stream_has_any_clients_cached(us_stream_s *stream); static bool _stream_has_any_clients_cached(us_stream_s *stream);
@@ -105,7 +107,7 @@ us_stream_s *us_stream_init(us_capture_s *cap, us_encoder_s *enc) {
US_RING_INIT_WITH_ITEMS(http->jpeg_ring, 4, us_frame_init); US_RING_INIT_WITH_ITEMS(http->jpeg_ring, 4, us_frame_init);
atomic_init(&http->has_clients, false); atomic_init(&http->has_clients, false);
atomic_init(&http->snapshot_requested, 0); atomic_init(&http->snapshot_requested, 0);
atomic_init(&http->last_request_ts, 0); atomic_init(&http->last_req_ts, 0);
http->captured_fpsi = us_fpsi_init("STREAM-CAPTURED", true); http->captured_fpsi = us_fpsi_init("STREAM-CAPTURED", true);
us_stream_runtime_s *run; us_stream_runtime_s *run;
@@ -129,7 +131,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) { void us_stream_update_blank(us_stream_s *stream, const us_capture_s *cap) {
us_stream_runtime_s *const run = stream->run; 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 us_fpsi_frame_to_meta(run->blank->raw, &run->notify_meta); // Initial "unchanged" meta
_stream_update_captured_fpsi(stream, run->blank->raw, false); _stream_update_captured_fpsi(stream, run->blank->raw, false);
} }
@@ -151,10 +153,16 @@ void us_stream_loop(us_stream_s *stream) {
us_stream_runtime_s *const run = stream->run; us_stream_runtime_s *const run = stream->run;
us_capture_s *const cap = stream->cap; us_capture_s *const cap = stream->cap;
atomic_store(&run->http->last_request_ts, us_get_now_monotonic()); atomic_store(&run->http->last_req_ts, us_get_now_monotonic());
if (stream->h264_sink != NULL) { 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_tmp_src = us_frame_init();
run->h264_dest = us_frame_init(); run->h264_dest = us_frame_init();
} }
@@ -168,10 +176,10 @@ void us_stream_loop(us_stream_s *stream) {
const uint n_releasers = cap->run->n_bufs; const uint n_releasers = cap->run->n_bufs;
_releaser_context_s *releasers; _releaser_context_s *releasers;
US_CALLOC(releasers, n_releasers); US_CALLOC(releasers, n_releasers);
for (uint index = 0; index < n_releasers; ++index) { for (uint i = 0; i < n_releasers; ++i) {
_releaser_context_s *ctx = &releasers[index]; _releaser_context_s *ctx = &releasers[i];
ctx->cap = cap; ctx->cap = cap;
ctx->queue = us_queue_init(1); ctx->q = us_queue_init(1);
ctx->mutex = &release_mutex; ctx->mutex = &release_mutex;
ctx->stop = &threads_stop; ctx->stop = &threads_stop;
US_THREAD_CREATE(ctx->tid, _releaser_thread, ctx); US_THREAD_CREATE(ctx->tid, _releaser_thread, ctx);
@@ -181,7 +189,7 @@ void us_stream_loop(us_stream_s *stream) {
_worker_context_s *x_ctx = NULL; \ _worker_context_s *x_ctx = NULL; \
if (x_cond) { \ if (x_cond) { \
US_CALLOC(x_ctx, 1); \ US_CALLOC(x_ctx, 1); \
x_ctx->queue = us_queue_init(x_capacity); \ x_ctx->q = us_queue_init(x_capacity); \
x_ctx->stream = stream; \ x_ctx->stream = stream; \
x_ctx->stop = &threads_stop; \ x_ctx->stop = &threads_stop; \
US_THREAD_CREATE(x_ctx->tid, (x_thread), x_ctx); \ US_THREAD_CREATE(x_ctx->tid, (x_thread), x_ctx); \
@@ -190,7 +198,7 @@ void us_stream_loop(us_stream_s *stream) {
CREATE_WORKER((stream->raw_sink != NULL), raw_ctx, _raw_thread, 2); CREATE_WORKER((stream->raw_sink != NULL), raw_ctx, _raw_thread, 2);
CREATE_WORKER((stream->h264_sink != NULL), h264_ctx, _h264_thread, cap->run->n_bufs); CREATE_WORKER((stream->h264_sink != NULL), h264_ctx, _h264_thread, cap->run->n_bufs);
# ifdef WITH_V4P # ifdef WITH_V4P
CREATE_WORKER((stream->drm != NULL), drm_ctx, _drm_thread, cap->run->n_bufs); // cppcheck-suppress assertWithSideEffect CREATE_WORKER((stream->drm != NULL), drm_ctx, _drm_thread, cap->run->n_bufs);
# endif # endif
# undef CREATE_WORKER # undef CREATE_WORKER
@@ -213,7 +221,7 @@ void us_stream_loop(us_stream_s *stream) {
# define QUEUE_HW(x_ctx) if (x_ctx != NULL) { \ # define QUEUE_HW(x_ctx) if (x_ctx != NULL) { \
us_capture_hwbuf_incref(hw); \ us_capture_hwbuf_incref(hw); \
us_queue_put(x_ctx->queue, hw, 0); \ us_queue_put(x_ctx->q, hw, 0); \
} }
QUEUE_HW(jpeg_ctx); QUEUE_HW(jpeg_ctx);
QUEUE_HW(raw_ctx); QUEUE_HW(raw_ctx);
@@ -222,7 +230,7 @@ void us_stream_loop(us_stream_s *stream) {
QUEUE_HW(drm_ctx); QUEUE_HW(drm_ctx);
# endif # endif
# undef QUEUE_HW # undef QUEUE_HW
us_queue_put(releasers[hw->buf.index].queue, hw, 0); // Plan to release us_queue_put(releasers[hw->buf.index].q, hw, 0); // Plan to release
// Мы не обновляем здесь состояние синков, потому что это происходит внутри обслуживающих их потоков // Мы не обновляем здесь состояние синков, потому что это происходит внутри обслуживающих их потоков
_stream_check_suicide(stream); _stream_check_suicide(stream);
@@ -240,7 +248,7 @@ void us_stream_loop(us_stream_s *stream) {
# define DELETE_WORKER(x_ctx) if (x_ctx != NULL) { \ # define DELETE_WORKER(x_ctx) if (x_ctx != NULL) { \
US_THREAD_JOIN(x_ctx->tid); \ US_THREAD_JOIN(x_ctx->tid); \
us_queue_destroy(x_ctx->queue); \ us_queue_destroy(x_ctx->q); \
free(x_ctx); \ free(x_ctx); \
} }
# ifdef WITH_V4P # ifdef WITH_V4P
@@ -251,9 +259,9 @@ void us_stream_loop(us_stream_s *stream) {
DELETE_WORKER(jpeg_ctx); DELETE_WORKER(jpeg_ctx);
# undef DELETE_WORKER # undef DELETE_WORKER
for (uint index = 0; index < n_releasers; ++index) { for (uint i = 0; i < n_releasers; ++i) {
US_THREAD_JOIN(releasers[index].tid); US_THREAD_JOIN(releasers[i].tid);
us_queue_destroy(releasers[index].queue); us_queue_destroy(releasers[i].q);
} }
free(releasers); free(releasers);
US_MUTEX_DESTROY(release_mutex); US_MUTEX_DESTROY(release_mutex);
@@ -283,7 +291,7 @@ static void *_releaser_thread(void *v_ctx) {
while (!atomic_load(ctx->stop)) { while (!atomic_load(ctx->stop)) {
us_capture_hwbuf_s *hw; us_capture_hwbuf_s *hw;
if (us_queue_get(ctx->queue, (void**)&hw, 0.1) < 0) { if (us_queue_get(ctx->q, (void**)&hw, 0.1) < 0) {
continue; continue;
} }
@@ -312,6 +320,9 @@ static void *_jpeg_thread(void *v_ctx) {
_worker_context_s *ctx = v_ctx; _worker_context_s *ctx = v_ctx;
us_stream_s *stream = ctx->stream; us_stream_s *stream = ctx->stream;
uint take = 1;
uint step = 1;
ldf grab_after_ts = 0; ldf grab_after_ts = 0;
uint fluency_passed = 0; uint fluency_passed = 0;
@@ -330,13 +341,13 @@ static void *_jpeg_thread(void *v_ctx) {
atomic_fetch_sub(&stream->run->http->snapshot_requested, 1); atomic_fetch_sub(&stream->run->http->snapshot_requested, 1);
} }
US_LOG_PERF("JPEG: ##### Encoded JPEG exposed; worker=%s, latency=%.3Lf", 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 { } else {
US_LOG_PERF("JPEG: ----- Encoded JPEG dropped; worker=%s", wr->name); US_LOG_PERF("JPEG: ----- Encoded JPEG dropped; worker=%s", wr->name);
} }
} }
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue); us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
if (hw == NULL) { if (hw == NULL) {
continue; continue;
} }
@@ -348,6 +359,19 @@ static void *_jpeg_thread(void *v_ctx) {
continue; 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(); const ldf now_ts = us_get_now_monotonic();
if (now_ts < grab_after_ts) { if (now_ts < grab_after_ts) {
fluency_passed += 1; fluency_passed += 1;
@@ -374,7 +398,7 @@ static void *_raw_thread(void *v_ctx) {
_worker_context_s *ctx = v_ctx; _worker_context_s *ctx = v_ctx;
while (!atomic_load(ctx->stop)) { while (!atomic_load(ctx->stop)) {
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue); us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
if (hw == NULL) { if (hw == NULL) {
continue; continue;
} }
@@ -394,9 +418,11 @@ static void *_h264_thread(void *v_ctx) {
_worker_context_s *ctx = v_ctx; _worker_context_s *ctx = v_ctx;
us_stream_s *stream = ctx->stream; us_stream_s *stream = ctx->stream;
ldf grab_after_ts = 0; uint take = 1;
uint step = 1;
while (!atomic_load(ctx->stop)) { while (!atomic_load(ctx->stop)) {
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue); us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
if (hw == NULL) { if (hw == NULL) {
continue; continue;
} }
@@ -405,23 +431,25 @@ static void *_h264_thread(void *v_ctx) {
US_LOG_VERBOSE("H264: Passed encoding because nobody is watching"); US_LOG_VERBOSE("H264: Passed encoding because nobody is watching");
goto decref; goto decref;
} }
if (hw->raw.grab_ts < grab_after_ts) {
US_LOG_DEBUG("H264: Passed encoding for FPS limit"); uint fps_limit = stream->run->h264_enc->run->fps_limit;
goto decref; 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); _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: decref:
us_capture_hwbuf_decref(hw); us_capture_hwbuf_decref(hw);
} }
@@ -443,7 +471,7 @@ static void *_drm_thread(void *v_ctx) {
# define SLOWDOWN { \ # define SLOWDOWN { \
const ldf m_next_ts = us_get_now_monotonic() + 1; \ const ldf m_next_ts = us_get_now_monotonic() + 1; \
while (!atomic_load(ctx->stop) && us_get_now_monotonic() < m_next_ts) { \ while (!atomic_load(ctx->stop) && us_get_now_monotonic() < m_next_ts) { \
us_capture_hwbuf_s *m_pass_hw = _get_latest_hw(ctx->queue); \ us_capture_hwbuf_s *m_pass_hw = _get_latest_hw(ctx->q); \
if (m_pass_hw != NULL) { \ if (m_pass_hw != NULL) { \
us_capture_hwbuf_decref(m_pass_hw); \ us_capture_hwbuf_decref(m_pass_hw); \
} \ } \
@@ -456,7 +484,7 @@ static void *_drm_thread(void *v_ctx) {
CHECK(us_drm_wait_for_vsync(stream->drm)); CHECK(us_drm_wait_for_vsync(stream->drm));
US_DELETE(prev_hw, us_capture_hwbuf_decref); US_DELETE(prev_hw, us_capture_hwbuf_decref);
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue); us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
if (hw == NULL) { if (hw == NULL) {
continue; continue;
} }
@@ -492,14 +520,14 @@ static void *_drm_thread(void *v_ctx) {
} }
#endif #endif
static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *queue) { static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *q) {
us_capture_hwbuf_s *hw; us_capture_hwbuf_s *hw;
if (us_queue_get(queue, (void**)&hw, 0.1) < 0) { if (us_queue_get(q, (void**)&hw, 0.1) < 0) {
return NULL; return NULL;
} }
while (!us_queue_is_empty(queue)) { // Берем только самый свежий кадр while (!us_queue_is_empty(q)) { // Берем только самый свежий кадр
us_capture_hwbuf_decref(hw); us_capture_hwbuf_decref(hw);
assert(!us_queue_get(queue, (void**)&hw, 0)); US_A(!us_queue_get(q, (void**)&hw, 0));
} }
return hw; return hw;
} }
@@ -529,6 +557,8 @@ static int _stream_init_loop(us_stream_s *stream) {
int once = 0; int once = 0;
while (!atomic_load(&stream->run->stop)) { while (!atomic_load(&stream->run->stop)) {
const char *blank_reason = "< NO LIVE VIDEO >";
# ifdef WITH_GPIO # ifdef WITH_GPIO
us_gpio_set_stream_online(false); us_gpio_set_stream_online(false);
# endif # endif
@@ -554,22 +584,73 @@ static int _stream_init_loop(us_stream_s *stream) {
switch (us_capture_open(stream->cap)) { switch (us_capture_open(stream->cap)) {
case 0: break; case 0: break;
case US_ERROR_NO_DEVICE: case US_ERROR_NO_DEVICE:
case US_ERROR_NO_DATA: blank_reason = (
US_ONCE({ US_LOG_INFO("Waiting for the capture device ..."); }); "< NO CAPTURE DEVICE >\n \n"
goto offline_and_retry; " 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: default:
once = 0; goto verbose_error;
goto offline_and_retry;
} }
us_encoder_open(stream->enc, stream->cap); us_encoder_open(stream->enc, stream->cap);
return 0; 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: offline_and_retry:
for (uint count = 0; count < stream->error_delay * 10; ++count) { if (stream->exit_on_device_error) {
US_LOG_INFO("Device error, exiting ...");
us_process_suicide();
}
for (uint i = 0; i < stream->error_delay * 10; ++i) {
if (atomic_load(&run->stop)) { if (atomic_load(&run->stop)) {
break; break;
} }
if (count % 10 == 0) { if (i % 10 == 0) {
// Каждую секунду повторяем blank // Каждую секунду повторяем blank
uint width = stream->cap->run->width; uint width = stream->cap->run->width;
uint height = stream->cap->run->height; uint height = stream->cap->run->height;
@@ -577,7 +658,7 @@ static int _stream_init_loop(us_stream_s *stream) {
width = stream->cap->width; width = stream->cap->width;
height = stream->cap->height; 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_update_captured_fpsi(stream, run->blank->raw, false);
_stream_expose_jpeg(stream, run->blank->jpeg); _stream_expose_jpeg(stream, run->blank->jpeg);
@@ -634,15 +715,18 @@ close:
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) {
us_stream_runtime_s *const run = stream->run; us_stream_runtime_s *const run = stream->run;
int ri; int ri;
while ((ri = us_ring_producer_acquire(run->http->jpeg_ring, 0)) < 0) { while ((ri = us_ring_producer_acquire(run->http->jpeg_ring, 0)) < 0) {
if (atomic_load(&run->stop)) { if (atomic_load(&run->stop)) {
return; return;
} }
} }
us_frame_s *const dest = run->http->jpeg_ring->items[ri]; us_frame_s *const dest = run->http->jpeg_ring->items[ri];
us_frame_copy(frame, dest); us_frame_copy(frame, dest);
us_ring_producer_release(run->http->jpeg_ring, ri); us_ring_producer_release(run->http->jpeg_ring, ri);
event_active(run->http->jpeg_refresher, 0, 0);
if (stream->jpeg_sink != NULL) { if (stream->jpeg_sink != NULL) {
us_memsink_server_put(stream->jpeg_sink, dest, NULL); us_memsink_server_put(stream->jpeg_sink, dest, NULL);
} }
@@ -658,6 +742,7 @@ static void _stream_encode_expose_h264(us_stream_s *stream, const us_frame_s *fr
if (stream->h264_sink == NULL) { if (stream->h264_sink == NULL) {
return; return;
} }
us_stream_runtime_s *run = stream->run; us_stream_runtime_s *run = stream->run;
us_fpsi_meta_s meta = {.online = false}; us_fpsi_meta_s meta = {.online = false};
@@ -684,16 +769,16 @@ static void _stream_check_suicide(us_stream_s *stream) {
if (stream->exit_on_no_clients == 0) { if (stream->exit_on_no_clients == 0) {
return; return;
} }
us_stream_runtime_s *const run = stream->run;
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();
const ull http_last_request_ts = atomic_load(&run->http->last_request_ts); // Seconds
if (_stream_has_any_clients_cached(stream)) { if (_stream_has_any_clients_cached(stream)) {
atomic_store(&run->http->last_request_ts, now_ts); atomic_store(last_req_ts, now_ts);
} else if (http_last_request_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 ...",
stream->exit_on_no_clients); stream->exit_on_no_clients);
atomic_store(last_req_ts, now_ts); // Prevent a signal spam
us_process_suicide(); us_process_suicide();
atomic_store(&run->http->last_request_ts, now_ts);
} }
} }

View File

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

View File

@@ -37,17 +37,19 @@ static void *_worker_thread(void *v_worker);
us_workers_pool_s *us_workers_pool_init( us_workers_pool_s *us_workers_pool_init(
const char *name, const char *wr_prefix, uint n_workers, ldf desired_interval, const char *name,
us_workers_pool_job_init_f job_init, void *job_init_arg, 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_job_destroy_f job_destroy,
us_workers_pool_run_job_f run_job) { us_workers_pool_run_job_f run_job
) {
US_LOG_INFO("Creating pool %s with %u workers ...", name, n_workers); US_LOG_INFO("Creating pool %s with %u workers ...", name, n_workers);
us_workers_pool_s *pool; us_workers_pool_s *pool;
US_CALLOC(pool, 1); US_CALLOC(pool, 1);
pool->name = name; pool->name = name;
pool->desired_interval = desired_interval;
pool->job_destroy = job_destroy; pool->job_destroy = job_destroy;
pool->run_job = run_job; pool->run_job = run_job;
@@ -58,12 +60,12 @@ us_workers_pool_s *us_workers_pool_init(
US_MUTEX_INIT(pool->free_workers_mutex); US_MUTEX_INIT(pool->free_workers_mutex);
US_COND_INIT(pool->free_workers_cond); US_COND_INIT(pool->free_workers_cond);
for (uint index = 0; index < pool->n_workers; ++index) { for (uint i = 0; i < pool->n_workers; ++i) {
us_worker_s *wr; us_worker_s *wr;
US_CALLOC(wr, 1); US_CALLOC(wr, 1);
wr->number = index; wr->number = i;
US_ASPRINTF(wr->name, "%s-%u", wr_prefix, index); US_ASPRINTF(wr->name, "%s-%u", wr_prefix, i);
US_MUTEX_INIT(wr->has_job_mutex); US_MUTEX_INIT(wr->has_job_mutex);
atomic_init(&wr->has_job, false); atomic_init(&wr->has_job, false);
@@ -113,11 +115,14 @@ us_worker_s *us_workers_pool_wait(us_workers_pool_s *pool) {
us_worker_s *found = NULL; us_worker_s *found = NULL;
US_LIST_ITERATE(pool->workers, wr, { // cppcheck-suppress constStatement US_LIST_ITERATE(pool->workers, wr, { // cppcheck-suppress constStatement
if (!atomic_load(&wr->has_job) && (found == NULL || found->job_start_ts <= wr->job_start_ts)) { if (
!atomic_load(&wr->has_job)
&& (found == NULL || found->job_start_ts <= wr->job_start_ts)
) {
found = wr; found = wr;
} }
}); });
assert(found != NULL); US_A(found != NULL);
US_LIST_REMOVE(pool->workers, found); US_LIST_REMOVE(pool->workers, found);
US_LIST_APPEND(pool->workers, found); // Перемещаем в конец списка US_LIST_APPEND(pool->workers, found); // Перемещаем в конец списка
@@ -147,14 +152,8 @@ ldf us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, const us_worker_s
pool->approx_job_time = approx_job_time; pool->approx_job_time = approx_job_time;
const ldf min_delay = pool->approx_job_time / pool->n_workers; // Среднее время работы размазывается на N воркеров // Среднее время работы размазывается на N воркеров
return (pool->approx_job_time / pool->n_workers);
if (pool->desired_interval > 0 && min_delay > 0 && pool->desired_interval > min_delay) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
// и аппаратный fps не попадает точно в желаемое значение
return pool->desired_interval;
}
return min_delay;
} }
static void *_worker_thread(void *v_worker) { 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 { typedef struct us_workers_pool_sx {
const char *name; const char *name;
ldf desired_interval;
us_workers_pool_job_destroy_f job_destroy; us_workers_pool_job_destroy_f job_destroy;
us_workers_pool_run_job_f run_job; us_workers_pool_run_job_f run_job;
@@ -76,8 +75,11 @@ typedef struct us_workers_pool_sx {
us_workers_pool_s *us_workers_pool_init( us_workers_pool_s *us_workers_pool_init(
const char *name, const char *wr_prefix, uint n_workers, ldf desired_interval, const char *name,
us_workers_pool_job_init_f job_init, void *job_init_arg, 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_job_destroy_f job_destroy,
us_workers_pool_run_job_f run_job); us_workers_pool_run_job_f run_job);

View File

@@ -28,7 +28,6 @@
#include <unistd.h> #include <unistd.h>
#include <getopt.h> #include <getopt.h>
#include <errno.h> #include <errno.h>
#include <assert.h>
#include <pthread.h> #include <pthread.h>
@@ -242,14 +241,14 @@ static void _main_loop(void) {
us_drm_destroy(drm); 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"); US_THREAD_SETTLE("follower");
const char *path = v_unix_follow; const char *path = v_unix_follow;
assert(path != NULL); US_A(path != NULL);
while (!atomic_load(&_g_stop)) { while (!atomic_load(&_g_stop)) {
int fd = socket(AF_UNIX, SOCK_STREAM, 0); int fd = socket(AF_UNIX, SOCK_STREAM, 0);
assert(fd >= 0); US_A(fd >= 0);
struct sockaddr_un addr = {0}; struct sockaddr_un addr = {0};
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);

View File

@@ -41,9 +41,9 @@ def main() -> None:
data_text = "{\n\t" + ",\n\t".join( data_text = "{\n\t" + ",\n\t".join(
", ".join( ", ".join(
f"0x{ch:02X}" f"0x{ch:02X}"
for ch in data[index:index + 20] for ch in data[i:i + 20]
) )
for index in range(0, len(data), 20) for i in range(0, len(data), 20)
) + ",\n}" ) + ",\n}"
text = f"{common.C_PREPEND}\n" text = f"{common.C_PREPEND}\n"