Compare commits

...

83 Commits

Author SHA1 Message Date
Maxim Devaev
b5e12a8412 Bump version: 6.61 → 6.62 2026-08-02 18:02:46 +03:00
Maxim Devaev
c65004f368 janus: disabled vplay config 2026-08-02 17:58:59 +03:00
Maxim Devaev
fc8550da6f janus: bring back delay between us_memsink_client_get() calls 2026-07-23 01:49:34 +03:00
Maxim Devaev
604d6613c5 Merge pull request #339 from 94xhn/fix/yuv420-chroma-scanline-timing
cpu encoder: fix chroma row advance timing in YUV420/YVU420 planar path
2026-07-18 20:47:19 +03:00
94xhn
5a189866b7 cpu encoder: fix chroma row advance timing in YUV420/YVU420 planar path
_jpeg_write_scanlines_yuv_planar() advanced chroma1_data/chroma2_data
after building the current luma row instead of before, so every even
luma row (>=2) read the previous chroma-row-pair's samples instead of
its own. Move the existing advance check to the top of the loop body,
before the row is built, so it takes effect before the chroma pointers
are read for that row. Condition and stride math are unchanged.
2026-07-18 22:08:45 +08:00
Maxim Devaev
db96c0699d janus: fixed int shadowing 2026-07-14 20:59:51 +03:00
Maxim Devaev
2e07557c8c Merge pull request #338 from marmarek/media-codes
Adjust media codes from 2x8 to 1x16 variants
2026-07-09 15:11:15 +03:00
Marek Marczykowski-Górecki
328e550801 Adjust media codes from 2x8 to 1x16 variants
This is what is listed as supported, at least on RPi5.
2026-07-09 13:10:27 +02:00
Maxim Devaev
b09efaacb8 Merge pull request #337 from marmarek/media-fixup
Fix us_media_topology_init()
2026-07-08 21:32:44 +03:00
Marek Marczykowski-Górecki
dae9301e63 Fix us_media_topology_init()
Allocate the topology structure.
2026-07-08 19:33:07 +02:00
Maxim Devaev
0301608b8f refactoring 2026-07-08 17:21:42 +03:00
Maxim Devaev
dd89d04211 refactoring 2026-07-08 13:17:41 +03:00
Maxim Devaev
6dd5b8709f refactoring 2026-07-08 11:03:23 +03:00
Maxim Devaev
41a8eb46ca refactoring 2026-07-08 10:24:41 +03:00
Maxim Devaev
7cf03cf35a refactoring 2026-07-08 03:07:23 +03:00
Maxim Devaev
654efeeca2 refactoring 2026-07-08 02:38:19 +03:00
Maxim Devaev
0f96532167 removed stride from memsink_wants 2026-07-06 23:07:00 +03:00
Maxim Devaev
95566d51df fixed dv_timings_fd reset 2026-07-06 14:10:28 +03:00
Maxim Devaev
b1c5276ab4 memsink: caps protocol 2026-07-06 09:33:35 +03:00
Maxim Devaev
ce231e7963 refactoring 2026-07-06 08:54:15 +03:00
Maxim Devaev
ff639cce1d Merge pull request #336 from marmarek/rpi5
Add support for media topology on Raspberry Pi 5
2026-07-06 01:42:30 +03:00
Maxim Devaev
fdea0d214b Bump version: 6.60 → 6.61 2026-07-02 06:46:30 +03:00
Maxim Devaev
72e2c6dabe fixed arch deps 2026-07-02 06:42:55 +03:00
Marek Marczykowski-Górecki
2c515eeea5 Add support for media topology on Raspberry Pi 5
HDMI input over CSI2 on RPi5 requires quite a bit more setup, because
/dev/video0 (or other /dev/video*) isn't automatically routed to the
video source. This needs finding relevant source media entity, and then
a route to requested video device (usually /dev/video0). Setting video
format gets more complicated too, because it needs to be set on all
involved nodes (aka "pads").

V4L2_EVENT_SOURCE_CHANGE is delivered on a different FD than reading
stream from, so both needs to be select()ed in this case.

From the user perspective, this adds two new options: --media-device and
--media-entity-name. When provided, ustreamer will find requested entity
and route it to the device given in --device option.
2026-06-30 15:33:01 +02:00
Marek Marczykowski-Górecki
730cc3c89e Add V4L fmt -> V4L subdev fmt translation
Will be used in the next patch.
I didn't found matching entries for V4L2_PIX_FMT_MJPEG, and
V4L2_PIX_FMT_YVU420 in linux/media-bus-format.h, so those are left as 0
(guaranteed invalid).
2026-06-30 00:35:48 +02:00
Maxim Devaev
686460fa29 lint fix 2026-06-12 21:08:41 +03:00
Maxim Devaev
54c1c77dfb janus: using common memsink lib 2026-06-12 19:01:13 +03:00
Maxim Devaev
0e0aef7b80 janus: using generic logging system 2026-06-12 17:53:32 +03:00
Maxim Devaev
31b98f1d59 fixed thread name getter 2026-06-12 17:31:28 +03:00
Maxim Devaev
9734c83bd4 extracted base logging function into a logging_base.h 2026-06-12 17:31:09 +03:00
Maxim Devaev
b88abdcae4 refactoring 2026-06-12 14:27:42 +03:00
Maxim Devaev
6fca17cf52 janus: PLI handling was fixed in Janus upstream
https://github.com/meetecho/janus-gateway/pull/3642
2026-06-08 13:02:25 +03:00
Maxim Devaev
5fe579956e Bump version: 6.59 → 6.60 2026-05-31 19:09:57 +03:00
Maxim Devaev
52dfa38a8f janus: camera basics 2026-05-31 18:28:53 +03:00
Maxim Devaev
8b76b59393 janus: _aplay_thread(): don't see at has_listeners 2026-05-31 17:05:34 +03:00
Maxim Devaev
92878e22ab Bump version: 6.58 → 6.59 2026-05-31 07:23:12 +03:00
Maxim Devaev
4dc0f73e1b janus: ssrc per client 2026-05-31 07:11:44 +03:00
Maxim Devaev
9fe42b038a ignore python build 2026-05-31 05:33:55 +03:00
Maxim Devaev
3750a22091 janus: separate audio of mic 2026-05-31 05:33:42 +03:00
Maxim Devaev
ef59c52a83 Bump version: 6.57 → 6.58 2026-05-30 12:01:35 +03:00
Maxim Devaev
f296bb57e7 added python-installer 2026-05-30 11:58:34 +03:00
Maxim Devaev
f7166b7c41 Bump version: 6.56 → 6.57 2026-05-30 11:49:10 +03:00
Maxim Devaev
b35200345e Merge pull request #334 from RSATom/master
cpu/encoder: fixed luma plane size calculation
2026-05-30 07:53:40 +03:00
sergey radionov
5bd7c9c5ef cpu/encoder: fixed luma plane size calculation
for 4:2:0 pixel formats,
and removed logic related to not used 4:1:0 pixel formats.
2026-05-28 13:46:27 +07:00
Maxim Devaev
fc35b1cd87 Merge pull request #333 from intelfx/work/fix-makefile
python: Makefile: use `python -m installer`; do not install while building
2026-05-18 08:21:39 +03:00
Maxim Devaev
92d9828894 janus: lost static 2026-05-18 08:21:09 +03:00
Maxim Devaev
e7008c6086 janus: splitted _rtpv_process_nalu() 2026-05-18 08:15:52 +03:00
Ivan Shapovalov
d0672e15b9 python: Makefile: use python -m installer; do not install while building
When building a Python wheel using `python -m build --wheel ...`,
temporary build files (including extension .so) are placed in ./build/...
from where they can be retrieved without having to install the just-built
wheel. Do both and get rid of `pip` invocations.
2026-05-18 08:13:47 +03:00
Maxim Devaev
04aa4c166c Merge pull request #332 from intelfx/work/fix-janus-rtpv
janus: fix rtpv chunking
2026-05-18 07:47:54 +03:00
Ivan Shapovalov
0fbbc27d8c janus: rtpv: fix and rework {first,last}_of_frame determination
Fixes: 2dac4db ("janus: fixed first/last frame logic for sps/pps")
Fixes: f32e256 ("janus: refactoring")
2026-05-18 07:45:09 +03:00
Ivan Shapovalov
b02bb85d8b janus: rtpv: marked -> last_nalu 2026-05-18 07:44:49 +03:00
Ivan Shapovalov
80dce276ed janus: rtp: marked -> last_header 2026-05-18 07:44:49 +03:00
Maxim Devaev
e869536a11 janus: refactoring 2026-05-16 16:14:56 +03:00
Maxim Devaev
b0acf0fa4d janus: refactoring 2026-05-15 21:43:42 +03:00
Maxim Devaev
f32e2569d2 janus: refactoring 2026-05-15 21:15:50 +03:00
Maxim Devaev
6d63f8e5d2 janus: refactoring 2026-05-15 19:32:37 +03:00
Maxim Devaev
2dac4db9fc janus: fixed first/last frame logic for sps/pps 2026-05-15 01:07:59 +03:00
Maxim Devaev
23dd2f9e66 Bump version: 6.55 → 6.56 2026-04-24 17:46:09 +03:00
Maxim Devaev
fdfd4225d0 lint fix 2026-04-24 17:43:02 +03:00
Maxim Devaev
9424b655eb janus: workaround for broken PLIs on Chromium 147 2026-04-24 17:16:19 +03:00
Maxim Devaev
f94fe25845 janus: don't advertise nack/nack-pli/goog-remb for audio 2026-04-24 17:11:24 +03:00
Maxim Devaev
bdcbaae8b3 janus: handle FIR 2026-04-24 17:10:42 +03:00
Marek Marczykowski-Górecki
52a2326426 Increase default raw memsink size to support 4k video (#327)
With this change, it's possible to use ustreamer to handle 4k HDMI input
on RK3588-based boards (tested on Orange Pi 5+ and Radxa ROCK 5B+).
2026-03-12 02:13:42 +02:00
Sergey Radionov
fa15d9693f janus: Tailscale has 1280 MTU actually (#326) 2026-03-10 15:28:13 +02:00
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
64 changed files with 1497 additions and 757 deletions

View File

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

1
.gitignore vendored
View File

@@ -6,6 +6,7 @@
/python/dist/ /python/dist/
/python/ustreamer.egg-info/ /python/ustreamer.egg-info/
/python/root/ /python/root/
/python/build/
/janus/build/ /janus/build/
/ustreamer /ustreamer
/ustreamer-* /ustreamer-*

View File

@@ -72,7 +72,7 @@ apps:
python: python:
$(MAKE) -C python $(MAKE) -C python
$(ECHO) ln -sf python/root/usr/lib/python*/site-packages/*.so . $(ECHO) ln -sf python/build/lib.linux-*/*.so -t .
janus: janus:

View File

@@ -35,13 +35,14 @@ 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```, ```pkg-config```, ```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).

View File

@@ -19,7 +19,7 @@ _BUILD = build
# ===== # =====
ifneq ($(shell sh -c 'uname 2>/dev/null || echo Unknown'),FreeBSD) ifeq ($(findstring bsd,$(shell $(CC) -dumpmachine)),)
override _LDFLAGS += -latomic override _LDFLAGS += -latomic
endif endif

View File

@@ -24,7 +24,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <assert.h>
#include <pthread.h> #include <pthread.h>
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
@@ -34,13 +33,13 @@
#include "uslibs/types.h" #include "uslibs/types.h"
#include "uslibs/errors.h" #include "uslibs/errors.h"
#include "uslibs/tools.h" #include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/logging.h"
#include "uslibs/array.h" #include "uslibs/array.h"
#include "uslibs/ring.h" #include "uslibs/ring.h"
#include "uslibs/threading.h"
#include "rtp.h" #include "rtp.h"
#include "au.h" #include "au.h"
#include "logging.h"
static void *_pcm_thread(void *v_acap); static void *_pcm_thread(void *v_acap);
@@ -60,14 +59,14 @@ us_acap_s *us_acap_init(const char *name, uint pcm_hz) {
{ {
if ((err = snd_pcm_open(&acap->dev, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) { if ((err = snd_pcm_open(&acap->dev, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
acap->dev = NULL; acap->dev = NULL;
US_JLOG_PERROR_ALSA(err, "acap", "Can't open PCM capture"); US_LOG_PERROR_ALSA(err, "Can't open PCM capture");
goto error; goto error;
} }
assert(!snd_pcm_hw_params_malloc(&acap->dev_params)); US_A(!snd_pcm_hw_params_malloc(&acap->dev_params));
# define SET_PARAM(_msg, _func, ...) { \ # define SET_PARAM(_msg, _func, ...) { \
if ((err = _func(acap->dev, acap->dev_params, ##__VA_ARGS__)) < 0) { \ if ((err = _func(acap->dev, acap->dev_params, ##__VA_ARGS__)) < 0) { \
US_JLOG_PERROR_ALSA(err, "acap", _msg); \ US_LOG_PERROR_ALSA(err, _msg); \
goto error; \ goto error; \
} \ } \
} }
@@ -78,7 +77,7 @@ us_acap_s *us_acap_init(const char *name, uint pcm_hz) {
SET_PARAM("Can't set PCM sampling format", snd_pcm_hw_params_set_format, SND_PCM_FORMAT_S16_LE); SET_PARAM("Can't set PCM sampling format", snd_pcm_hw_params_set_format, SND_PCM_FORMAT_S16_LE);
SET_PARAM("Can't set PCM sampling rate", snd_pcm_hw_params_set_rate_near, &acap->pcm_hz, 0); SET_PARAM("Can't set PCM sampling rate", snd_pcm_hw_params_set_rate_near, &acap->pcm_hz, 0);
if (acap->pcm_hz < US_AU_MIN_PCM_HZ || acap->pcm_hz > US_AU_MAX_PCM_HZ) { if (acap->pcm_hz < US_AU_MIN_PCM_HZ || acap->pcm_hz > US_AU_MAX_PCM_HZ) {
US_JLOG_ERROR("acap", "Unsupported PCM freq: %u; should be: %u <= F <= %u", US_LOG_ERROR("Unsupported PCM freq: %u; should be: %u <= F <= %u",
acap->pcm_hz, US_AU_MIN_PCM_HZ, US_AU_MAX_PCM_HZ); acap->pcm_hz, US_AU_MIN_PCM_HZ, US_AU_MAX_PCM_HZ);
goto error; goto error;
} }
@@ -93,7 +92,7 @@ us_acap_s *us_acap_init(const char *name, uint pcm_hz) {
acap->res = speex_resampler_init(US_RTP_OPUS_CH, acap->pcm_hz, US_RTP_OPUS_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err); acap->res = speex_resampler_init(US_RTP_OPUS_CH, acap->pcm_hz, US_RTP_OPUS_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
if (err < 0) { if (err < 0) {
acap->res = NULL; acap->res = NULL;
US_JLOG_PERROR_RES(err, "acap", "Can't create resampler"); US_LOG_PERROR_RES(err, "Can't create resampler");
goto error; goto error;
} }
} }
@@ -101,16 +100,16 @@ us_acap_s *us_acap_init(const char *name, uint pcm_hz) {
{ {
// OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY // OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY
acap->enc = opus_encoder_create(US_RTP_OPUS_HZ, US_RTP_OPUS_CH, OPUS_APPLICATION_AUDIO, &err); acap->enc = opus_encoder_create(US_RTP_OPUS_HZ, US_RTP_OPUS_CH, OPUS_APPLICATION_AUDIO, &err);
assert(err == 0); US_A(err == 0);
// https://github.com/meetecho/janus-gateway/blob/3cdd6ff/src/plugins/janus_audiobridge.c#L2272 // https://github.com/meetecho/janus-gateway/blob/3cdd6ff/src/plugins/janus_audiobridge.c#L2272
// https://datatracker.ietf.org/doc/html/rfc7587#section-3.1.1 // https://datatracker.ietf.org/doc/html/rfc7587#section-3.1.1
assert(!opus_encoder_ctl(acap->enc, OPUS_SET_BITRATE(128000))); US_A(!opus_encoder_ctl(acap->enc, OPUS_SET_BITRATE(128000)));
assert(!opus_encoder_ctl(acap->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND))); US_A(!opus_encoder_ctl(acap->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)));
assert(!opus_encoder_ctl(acap->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC))); US_A(!opus_encoder_ctl(acap->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)));
// OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c // OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c
} }
US_JLOG_INFO("acap", "Capture configured on %uHz; capturing ...", acap->pcm_hz); US_LOG_INFO("Capture configured on %uHz; capturing ...", acap->pcm_hz);
acap->tids_created = true; acap->tids_created = true;
US_THREAD_CREATE(acap->enc_tid, _encoder_thread, acap); US_THREAD_CREATE(acap->enc_tid, _encoder_thread, acap);
US_THREAD_CREATE(acap->pcm_tid, _pcm_thread, acap); US_THREAD_CREATE(acap->pcm_tid, _pcm_thread, acap);
@@ -135,7 +134,7 @@ void us_acap_destroy(us_acap_s *acap) {
US_RING_DELETE_WITH_ITEMS(acap->enc_ring, us_au_encoded_destroy); US_RING_DELETE_WITH_ITEMS(acap->enc_ring, us_au_encoded_destroy);
US_RING_DELETE_WITH_ITEMS(acap->pcm_ring, us_au_pcm_destroy); US_RING_DELETE_WITH_ITEMS(acap->pcm_ring, us_au_pcm_destroy);
if (acap->tids_created) { if (acap->tids_created) {
US_JLOG_INFO("acap", "Capture closed"); US_LOG_INFO("Capture closed");
} }
free(acap); free(acap);
} }
@@ -161,7 +160,7 @@ int us_acap_get_encoded(us_acap_s *acap, u8 *data, uz *size, u64 *pts) {
} }
static void *_pcm_thread(void *v_acap) { static void *_pcm_thread(void *v_acap) {
US_THREAD_SETTLE("us_ac_pcm"); US_THREAD_SETTLE("us_acap_pcm");
us_acap_s *const acap = v_acap; us_acap_s *const acap = v_acap;
u8 in[US_AU_MAX_BUF8]; u8 in[US_AU_MAX_BUF8];
@@ -169,10 +168,10 @@ static void *_pcm_thread(void *v_acap) {
while (!atomic_load(&acap->stop)) { while (!atomic_load(&acap->stop)) {
const int frames = snd_pcm_readi(acap->dev, in, acap->pcm_frames); const int frames = snd_pcm_readi(acap->dev, in, acap->pcm_frames);
if (frames < 0) { if (frames < 0) {
US_JLOG_PERROR_ALSA(frames, "acap", "Fatal: Can't capture PCM frames"); US_LOG_PERROR_ALSA(frames, "Fatal: Can't capture PCM frames");
break; break;
} else if (frames < (int)acap->pcm_frames) { } else if (frames < (int)acap->pcm_frames) {
US_JLOG_ERROR("acap", "Fatal: Too few PCM frames captured"); US_LOG_ERROR("Fatal: Too few PCM frames captured");
break; break;
} }
@@ -182,7 +181,7 @@ static void *_pcm_thread(void *v_acap) {
memcpy(out->data, in, acap->pcm_size); memcpy(out->data, in, acap->pcm_size);
us_ring_producer_release(acap->pcm_ring, ri); us_ring_producer_release(acap->pcm_ring, ri);
} else { } else {
US_JLOG_ERROR("acap", "PCM ring is full"); US_LOG_ERROR("PCM ring is full");
} }
} }
@@ -191,7 +190,7 @@ static void *_pcm_thread(void *v_acap) {
} }
static void *_encoder_thread(void *v_acap) { static void *_encoder_thread(void *v_acap) {
US_THREAD_SETTLE("us_ac_enc"); US_THREAD_SETTLE("us_acap_enc");
us_acap_s *const acap = v_acap; us_acap_s *const acap = v_acap;
s16 in_res[US_AU_MAX_BUF16]; s16 in_res[US_AU_MAX_BUF16];
@@ -205,19 +204,19 @@ static void *_encoder_thread(void *v_acap) {
s16 *in_ptr; s16 *in_ptr;
if (acap->res != NULL) { if (acap->res != NULL) {
assert(acap->pcm_hz != US_RTP_OPUS_HZ); US_A(acap->pcm_hz != US_RTP_OPUS_HZ);
u32 in_count = acap->pcm_frames; u32 in_count = acap->pcm_frames;
u32 out_count = US_AU_HZ_TO_FRAMES(US_RTP_OPUS_HZ); u32 out_count = US_AU_HZ_TO_FRAMES(US_RTP_OPUS_HZ);
speex_resampler_process_interleaved_int(acap->res, in->data, &in_count, in_res, &out_count); speex_resampler_process_interleaved_int(acap->res, in->data, &in_count, in_res, &out_count);
in_ptr = in_res; in_ptr = in_res;
} else { } else {
assert(acap->pcm_hz == US_RTP_OPUS_HZ); US_A(acap->pcm_hz == US_RTP_OPUS_HZ);
in_ptr = in->data; in_ptr = in->data;
} }
const int out_ri = us_ring_producer_acquire(acap->enc_ring, 0); const int out_ri = us_ring_producer_acquire(acap->enc_ring, 0);
if (out_ri < 0) { if (out_ri < 0) {
US_JLOG_ERROR("acap", "OPUS encoder queue is full"); US_LOG_ERROR("OPUS encoder queue is full");
us_ring_consumer_release(acap->pcm_ring, in_ri); us_ring_consumer_release(acap->pcm_ring, in_ri);
continue; continue;
} }
@@ -233,7 +232,7 @@ static void *_encoder_thread(void *v_acap) {
acap->pts += US_AU_HZ_TO_FRAMES(US_RTP_OPUS_HZ); acap->pts += US_AU_HZ_TO_FRAMES(US_RTP_OPUS_HZ);
} else { } else {
out->used = 0; out->used = 0;
US_JLOG_PERROR_OPUS(size, "acap", "Fatal: Can't encode PCM frame to OPUS"); US_LOG_PERROR_OPUS(size, "Fatal: Can't encode PCM frame to OPUS");
} }
us_ring_producer_release(acap->enc_ring, out_ri); us_ring_producer_release(acap->enc_ring, out_ri);
} }

View File

@@ -38,7 +38,7 @@ bool us_au_probe(const char *name) {
// the existence of it in /proc/asound/. // the existence of it in /proc/asound/.
// It's enough for our case. // It's enough for our case.
if (name == NULL) { if (!us_str_is_ok(name)) {
return false; return false;
} }

View File

@@ -25,7 +25,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <pthread.h> #include <pthread.h>
#include <janus/plugins/plugin.h> #include <janus/plugins/plugin.h>
@@ -35,11 +34,11 @@
#include "uslibs/types.h" #include "uslibs/types.h"
#include "uslibs/tools.h" #include "uslibs/tools.h"
#include "uslibs/threading.h" #include "uslibs/threading.h"
#include "uslibs/logging.h"
#include "uslibs/array.h" #include "uslibs/array.h"
#include "uslibs/list.h" #include "uslibs/list.h"
#include "uslibs/ring.h" #include "uslibs/ring.h"
#include "logging.h"
#include "au.h" #include "au.h"
#include "rtp.h" #include "rtp.h"
@@ -55,6 +54,9 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
US_CALLOC(client, 1); US_CALLOC(client, 1);
client->gw = gw; client->gw = gw;
client->session = session; client->session = session;
client->video_ssrc = us_get_ssrc();
client->audio_ssrc = us_get_ssrc();
atomic_init(&client->transmit, false); atomic_init(&client->transmit, false);
atomic_init(&client->transmit_acap, false); atomic_init(&client->transmit_acap, false);
atomic_init(&client->transmit_aplay, false); atomic_init(&client->transmit_aplay, false);
@@ -99,7 +101,7 @@ void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
us_ring_s *const ring = (rtp->video ? client->video_ring : client->acap_ring); us_ring_s *const ring = (rtp->video ? client->video_ring : client->acap_ring);
const int ri = us_ring_producer_acquire(ring, 0); const int ri = us_ring_producer_acquire(ring, 0);
if (ri < 0) { if (ri < 0) {
US_JLOG_ERROR("client", "Session %p %s ring is full", US_LOG_ERROR("Session %p %s ring is full",
client->session, (rtp->video ? "video" : "acap")); client->session, (rtp->video ? "video" : "acap"));
return; return;
} }
@@ -139,7 +141,7 @@ void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet) {
us_ring_s *const ring = client->aplay_enc_ring; us_ring_s *const ring = client->aplay_enc_ring;
const int ri = us_ring_producer_acquire(ring, 0); const int ri = us_ring_producer_acquire(ring, 0);
if (ri < 0) { if (ri < 0) {
// US_JLOG_ERROR("client", "Session %p aplay ring is full", client->session); // US_LOG_ERROR("Session %p aplay ring is full", client->session);
return; return;
} }
us_au_encoded_s *enc = ring->items[ri]; us_au_encoded_s *enc = ring->items[ri];
@@ -154,19 +156,19 @@ void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet) {
} }
static void *_video_thread(void *v_client) { static void *_video_thread(void *v_client) {
US_THREAD_SETTLE("us_cx_vid"); US_THREAD_SETTLE("us_cx_vcap");
return _video_or_acap_thread(v_client, true); return _video_or_acap_thread(v_client, true);
} }
static void *_acap_thread(void *v_client) { static void *_acap_thread(void *v_client) {
US_THREAD_SETTLE("us_cx_ac"); US_THREAD_SETTLE("us_cx_acap");
return _video_or_acap_thread(v_client, false); return _video_or_acap_thread(v_client, false);
} }
static void *_video_or_acap_thread(void *v_client, bool video) { static void *_video_or_acap_thread(void *v_client, bool video) {
us_janus_client_s *const client = v_client; us_janus_client_s *const client = v_client;
us_ring_s *const ring = (video ? client->video_ring : client->acap_ring); us_ring_s *const ring = (video ? client->video_ring : client->acap_ring);
assert(ring != NULL); US_A(ring != NULL);
while (!atomic_load(&client->stop)) { while (!atomic_load(&client->stop)) {
const int ri = us_ring_consumer_acquire(ring, 0.1); const int ri = us_ring_consumer_acquire(ring, 0.1);
@@ -181,6 +183,8 @@ static void *_video_or_acap_thread(void *v_client, bool video) {
atomic_load(&client->transmit) atomic_load(&client->transmit)
&& (video || atomic_load(&client->transmit_acap)) && (video || atomic_load(&client->transmit_acap))
) { ) {
us_rtp_write_ssrc(&rtp, (rtp.video ? client->video_ssrc : client->audio_ssrc));
janus_plugin_rtp packet = { janus_plugin_rtp packet = {
.video = rtp.video, .video = rtp.video,
.buffer = (char*)rtp.datagram, .buffer = (char*)rtp.datagram,
@@ -231,13 +235,13 @@ static void *_video_or_acap_thread(void *v_client, bool video) {
} }
static void *_aplay_thread(void *v_client) { static void *_aplay_thread(void *v_client) {
US_THREAD_SETTLE("us_cx_ap"); US_THREAD_SETTLE("us_cx_aplay");
us_janus_client_s *const client = v_client; us_janus_client_s *const client = v_client;
int err; int err;
OpusDecoder *dec = opus_decoder_create(US_RTP_OPUS_HZ, US_RTP_OPUS_CH, &err); OpusDecoder *dec = opus_decoder_create(US_RTP_OPUS_HZ, US_RTP_OPUS_CH, &err);
assert(err == 0); US_A(err == 0);
while (!atomic_load(&client->stop)) { while (!atomic_load(&client->stop)) {
const int in_ri = us_ring_consumer_acquire(client->aplay_enc_ring, 0.1); const int in_ri = us_ring_consumer_acquire(client->aplay_enc_ring, 0.1);
@@ -253,7 +257,7 @@ static void *_aplay_thread(void *v_client) {
const int out_ri = us_ring_producer_acquire(client->aplay_pcm_ring, 0); const int out_ri = us_ring_producer_acquire(client->aplay_pcm_ring, 0);
if (out_ri < 0) { if (out_ri < 0) {
US_JLOG_ERROR("aplay", "OPUS decoder queue is full"); US_LOG_ERROR("OPUS decoder queue is full");
us_ring_consumer_release(client->aplay_enc_ring, in_ri); us_ring_consumer_release(client->aplay_enc_ring, in_ri);
continue; continue;
} }
@@ -266,7 +270,7 @@ static void *_aplay_thread(void *v_client) {
out->frames = frames; out->frames = frames;
} else { } else {
out->frames = 0; out->frames = 0;
US_JLOG_PERROR_OPUS(frames, "aplay", "Fatal: Can't decode OPUS to PCM frame"); US_LOG_PERROR_OPUS(frames, "Fatal: Can't decode OPUS to PCM frame");
} }
us_ring_producer_release(client->aplay_pcm_ring, out_ri); us_ring_producer_release(client->aplay_pcm_ring, out_ri);
} }

View File

@@ -37,6 +37,9 @@
typedef struct { typedef struct {
janus_callbacks *gw; janus_callbacks *gw;
janus_plugin_session *session; janus_plugin_session *session;
u32 video_ssrc;
u32 audio_ssrc;
atomic_bool transmit; atomic_bool transmit;
atomic_bool transmit_acap; atomic_bool transmit_acap;
atomic_bool transmit_aplay; atomic_bool transmit_aplay;

View File

@@ -30,9 +30,9 @@
#include "uslibs/types.h" #include "uslibs/types.h"
#include "uslibs/tools.h" #include "uslibs/tools.h"
#include "uslibs/logging.h"
#include "const.h" #include "const.h"
#include "logging.h"
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);
@@ -48,28 +48,29 @@ us_config_s *us_config_init(const char *config_dir_path) {
janus_config *jcfg = NULL; janus_config *jcfg = NULL;
US_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, US_PLUGIN_PACKAGE); US_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, US_PLUGIN_PACKAGE);
US_JLOG_INFO("config", "Reading config file '%s' ...", config_file_path); US_LOG_INFO("Reading config file '%s' ...", config_file_path);
jcfg = janus_config_parse(config_file_path); jcfg = janus_config_parse(config_file_path);
if (jcfg == NULL) { if (jcfg == NULL) {
US_JLOG_ERROR("config", "Can't read config"); US_LOG_ERROR("Can't read config");
goto error; goto error;
} }
janus_config_print(jcfg); janus_config_print(jcfg);
if ((config->video_sink_name = _get_value(jcfg, "video", "sink")) == NULL) { if ((config->video_sink_name = _get_value(jcfg, "video", "sink")) == NULL) {
US_JLOG_ERROR("config", "Missing config value: video.sink"); US_LOG_ERROR("Missing config value: video.sink");
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) {
config->acap_hz = _get_uint(jcfg, "acap", "sampling_rate", 0); config->acap_hz = _get_uint(jcfg, "acap", "sampling_rate", 0);
config->tc358743_dev_path = _get_value(jcfg, "acap", "tc358743"); config->tc358743_dev_path = _get_value(jcfg, "acap", "tc358743");
if (config->acap_hz == 0 && config->tc358743_dev_path == NULL) { if (config->acap_hz == 0 && !us_str_is_ok(config->tc358743_dev_path)) {
US_JLOG_ERROR("config", "Either acap.sampling_rate or acap.tc358743 required"); US_LOG_ERROR("Either acap.sampling_rate or acap.tc358743 required");
goto error; goto error;
} }
config->aplay_dev_name = _get_value(jcfg, "aplay", "device");
} }
config->aplay_dev_name = _get_value(jcfg, "aplay", "device");
// config->vplay_sink_name = _get_value(jcfg, "vplay", "sink");
goto ok; goto ok;
@@ -87,13 +88,14 @@ void us_config_destroy(us_config_s *config) {
US_DELETE(config->acap_dev_name, free); US_DELETE(config->acap_dev_name, free);
US_DELETE(config->tc358743_dev_path, free); US_DELETE(config->tc358743_dev_path, free);
US_DELETE(config->aplay_dev_name, free); US_DELETE(config->aplay_dev_name, free);
US_DELETE(config->vplay_sink_name, free);
free(config); free(config);
} }
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) {
janus_config_category *section_obj = janus_config_get_create(jcfg, NULL, janus_config_type_category, section); janus_config_category *section_obj = janus_config_get_create(jcfg, NULL, janus_config_type_category, section);
janus_config_item *option_obj = janus_config_get(jcfg, section_obj, janus_config_type_item, option); janus_config_item *option_obj = janus_config_get(jcfg, section_obj, janus_config_type_item, option);
if (option_obj == NULL || option_obj->value == NULL || option_obj->value[0] == '\0') { if (option_obj == NULL || !us_str_is_ok(option_obj->value)) {
return NULL; return NULL;
} }
return us_strdup(option_obj->value); return us_strdup(option_obj->value);

View File

@@ -34,6 +34,7 @@ typedef struct {
char *tc358743_dev_path; char *tc358743_dev_path;
char *aplay_dev_name; char *aplay_dev_name;
char *vplay_sink_name;
} us_config_s; } us_config_s;

View File

@@ -22,11 +22,9 @@
#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>
#include <assert.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -43,20 +41,20 @@
#include "uslibs/errors.h" #include "uslibs/errors.h"
#include "uslibs/tools.h" #include "uslibs/tools.h"
#include "uslibs/threading.h" #include "uslibs/threading.h"
#include "uslibs/logging.h"
#include "uslibs/list.h" #include "uslibs/list.h"
#include "uslibs/ring.h" #include "uslibs/ring.h"
#include "uslibs/memsinksh.h" #include "uslibs/memsink.h"
#include "uslibs/tc358743.h" #include "uslibs/chip.h"
#include "const.h" #include "const.h"
#include "logging.h"
#include "client.h" #include "client.h"
#include "au.h" #include "au.h"
#include "acap.h" #include "acap.h"
#include "rtp.h" #include "rtp.h"
#include "rtpv.h" #include "rtpv.h"
#include "rtpa.h" #include "rtpa.h"
#include "memsinkfd.h" #include "sdp.h"
#include "config.h" #include "config.h"
@@ -136,115 +134,66 @@ static void *_video_rtp_thread(void *arg) {
static void *_video_sink_thread(void *arg) { static void *_video_sink_thread(void *arg) {
(void)arg; (void)arg;
US_THREAD_SETTLE("us_p_vsink"); US_THREAD_SETTLE("us_p_vcap");
atomic_store(&_g_video_sink_tid_created, true); atomic_store(&_g_video_sink_tid_created, true);
us_frame_s *drop = us_frame_init(); us_frame_s *tmp = us_frame_init();
u64 frame_id = 0;
int once = 0; int once = 0;
while (!_STOP) { while (!_STOP) {
if (!_HAS_WATCHERS) { if (!_HAS_WATCHERS) {
US_ONCE({ US_JLOG_INFO("video", "No active watchers, memsink disconnected"); }); US_ONCE({ US_LOG_INFO("No active watchers, memsink disconnected"); });
usleep(_g_watchers_polling); usleep(_g_watchers_polling);
continue; continue;
} }
int fd = -1; us_memsink_s *sink = us_memsink_init_opened("vcap", _g_config->video_sink_name, false, 0, false, 0, 1);
us_memsink_shared_s *mem = NULL; if (sink == NULL) {
const uz data_size = us_memsink_calculate_size(_g_config->video_sink_name);
if (data_size == 0) {
US_ONCE({ US_JLOG_ERROR("video", "Invalid memsink object suffix"); });
goto close_memsink;
}
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
US_ONCE({ US_JLOG_PERROR("video", "Can't open memsink"); });
goto close_memsink;
}
if ((mem = us_memsink_shared_map(fd, data_size)) == NULL) {
US_ONCE({ US_JLOG_PERROR("video", "Can't map memsink"); });
goto close_memsink; goto close_memsink;
} }
once = 0; once = 0;
US_JLOG_INFO("video", "Memsink opened; reading frames ..."); US_LOG_INFO("Memsink opened; reading frames ...");
while (!_STOP && _HAS_WATCHERS) { while (!_STOP && _HAS_WATCHERS) {
const int waited = us_memsink_fd_wait_frame(fd, mem, frame_id); const us_memsink_wants_s w_put = {.key = atomic_load(&_g_key_required)};
if (waited == 0) { const int got = us_memsink_client_get(sink, tmp, NULL, &w_put);
if (got == 0) {
const int ri = us_ring_producer_acquire(_g_video_ring, 0); const int ri = us_ring_producer_acquire(_g_video_ring, 0);
us_frame_s *frame;
if (ri >= 0) {
frame = _g_video_ring->items[ri];
} else {
US_ONCE({ US_JLOG_PERROR("video", "Video ring is full"); });
frame = drop;
}
const int got = us_memsink_fd_get_frame(fd, mem, frame, &frame_id, atomic_load(&_g_key_required));
if (ri >= 0) { if (ri >= 0) {
us_frame_s *dest = _g_video_ring->items[ri];
us_frame_copy(tmp, dest);
us_ring_producer_release(_g_video_ring, ri); us_ring_producer_release(_g_video_ring, ri);
if (tmp->key) {
atomic_store(&_g_key_required, false);
}
} else {
US_ONCE({ US_LOG_ERROR("Video ring is full"); });
} }
if (got < 0) { } else if (got == US_ERROR_NO_DATA) {
goto close_memsink; usleep(1000);
} } else {
if (ri >= 0 && frame->key) {
atomic_store(&_g_key_required, false);
}
} else if (waited != US_ERROR_NO_DATA) {
goto close_memsink; goto close_memsink;
} }
} }
close_memsink: close_memsink:
if (mem != NULL) { US_DELETE(sink, us_memsink_destroy);
us_memsink_shared_unmap(mem, data_size); US_LOG_INFO("Memsink closed");
mem = NULL;
}
US_CLOSE_FD(fd);
US_JLOG_INFO("video", "Memsink closed");
sleep(1); // error_delay sleep(1); // error_delay
} }
us_frame_destroy(drop); us_frame_destroy(tmp);
return NULL; return NULL;
} }
static int _get_acap_hz(uint *hz) {
if (_g_config->acap_hz != 0) {
*hz = _g_config->acap_hz;
return 0;
}
if (_g_config->tc358743_dev_path == NULL) {
US_JLOG_ERROR("acap", "No configured sampling rate");
return -1;
}
int fd;
if ((fd = open(_g_config->tc358743_dev_path, O_RDWR)) < 0) {
US_JLOG_PERROR("acap", "Can't open TC358743 V4L2 device");
return -1;
}
const int checked = us_tc358743_xioctl_get_audio_hz(fd, hz);
if (checked < 0) {
US_JLOG_PERROR("acap", "Can't check TC358743 audio state (%d)", checked);
close(fd);
return -1;
}
close(fd);
return 0;
}
static void *_acap_thread(void *arg) { static void *_acap_thread(void *arg) {
(void)arg; (void)arg;
US_THREAD_SETTLE("us_p_ac"); US_THREAD_SETTLE("us_p_acap");
atomic_store(&_g_acap_tid_created, true); atomic_store(&_g_acap_tid_created, true);
assert(_g_config->acap_dev_name != NULL); US_A(_g_config->acap_dev_name != NULL);
assert(_g_rtpa != NULL); US_A(_g_rtpa != NULL);
int once = 0; int once = 0;
@@ -254,21 +203,34 @@ static void *_acap_thread(void *arg) {
continue; continue;
} }
uint hz = 0; int chip_fd = -1;
us_acap_s *acap = NULL; us_acap_s *acap = NULL;
if (!us_au_probe(_g_config->acap_dev_name)) { if (!us_au_probe(_g_config->acap_dev_name)) {
US_ONCE({ US_JLOG_ERROR("acap", "No PCM capture device"); }); US_ONCE({ US_LOG_ERROR("No PCM capture device"); });
goto close_acap; goto close_acap;
} }
if (_get_acap_hz(&hz) < 0) {
goto close_acap; int hz = _g_config->acap_hz;
if (hz <= 0) {
US_A(us_str_is_ok(_g_config->tc358743_dev_path));
chip_fd = open(_g_config->tc358743_dev_path, O_RDWR);
if (chip_fd < 0) {
US_ONCE({ US_LOG_PERROR("Can't open TC358743 for the audio request"); });
goto close_acap;
}
hz = us_chip_tc358743_get_audio_hz(chip_fd);
if (hz == US_ERROR_NO_SIGNAL) {
US_ONCE({ US_LOG_INFO("No audio presented from the host"); });
goto close_acap;
} else if (hz <= 0) {
US_ONCE({ US_LOG_PERROR("Can't get audio HZ"); });
goto close_acap;
}
US_ONCE({ US_LOG_INFO("Detected host audio"); });
} }
if (hz == 0) {
US_ONCE({ US_JLOG_INFO("acap", "No audio presented from the host"); });
goto close_acap;
}
US_ONCE({ US_JLOG_INFO("acap", "Detected host audio"); });
if ((acap = us_acap_init(_g_config->acap_dev_name, hz)) == NULL) { if ((acap = us_acap_init(_g_config->acap_dev_name, hz)) == NULL) {
goto close_acap; goto close_acap;
} }
@@ -276,10 +238,10 @@ static void *_acap_thread(void *arg) {
once = 0; once = 0;
while (!_STOP && _HAS_WATCHERS && _HAS_LISTENERS) { while (!_STOP && _HAS_WATCHERS && _HAS_LISTENERS) {
if (_get_acap_hz(&hz) < 0 || acap->pcm_hz != hz) { if (chip_fd >= 0 && us_chip_tc358743_get_audio_hz(chip_fd) != (int)acap->pcm_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);
@@ -294,6 +256,7 @@ static void *_acap_thread(void *arg) {
close_acap: close_acap:
US_DELETE(acap, us_acap_destroy); US_DELETE(acap, us_acap_destroy);
US_CLOSE_FD(chip_fd);
sleep(1); // error_delay sleep(1); // error_delay
} }
return NULL; return NULL;
@@ -301,10 +264,10 @@ static void *_acap_thread(void *arg) {
static void *_aplay_thread(void *arg) { static void *_aplay_thread(void *arg) {
(void)arg; (void)arg;
US_THREAD_SETTLE("us_p_ap"); US_THREAD_SETTLE("us_p_aplay");
atomic_store(&_g_aplay_tid_created, true); atomic_store(&_g_aplay_tid_created, true);
assert(_g_config->aplay_dev_name != NULL); US_A(_g_config->aplay_dev_name != NULL);
int once = 0; int once = 0;
@@ -330,10 +293,10 @@ static void *_aplay_thread(void *arg) {
} }
} while (skip && !_STOP); } while (skip && !_STOP);
us_au_pcm_mix(&mixed, &last); us_au_pcm_mix(&mixed, &last);
// US_JLOG_INFO("++++++", "mixed %p", client); // US_LOG_INFO("++++++ mixed %p", client);
}); });
_UNLOCK_APLAY; _UNLOCK_APLAY;
// US_JLOG_INFO("++++++", "--------------"); // US_LOG_INFO("++++++ --------------");
if (skip) { if (skip) {
static uint skipped = 0; static uint skipped = 0;
@@ -345,19 +308,19 @@ static void *_aplay_thread(void *arg) {
} }
} }
if (!_HAS_WATCHERS || !_HAS_LISTENERS || !_HAS_SPEAKERS) { if (!_HAS_WATCHERS || !_HAS_SPEAKERS) {
goto close_aplay; goto close_aplay;
} }
if (dev == NULL) { if (dev == NULL) {
if (!us_au_probe(_g_config->aplay_dev_name)) { if (!us_au_probe(_g_config->aplay_dev_name)) {
US_ONCE({ US_JLOG_ERROR("aplay", "No PCM playback device"); }); US_ONCE({ US_LOG_ERROR("No PCM playback device"); });
goto close_aplay; 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_LOG_PERROR_ALSA(err, "Can't open PCM playback"); });
goto close_aplay; goto close_aplay;
} }
@@ -365,11 +328,11 @@ static void *_aplay_thread(void *arg) {
US_RTP_OPUS_CH, US_RTP_OPUS_HZ, 1 /* soft resample */, 50000 /* 50000 = 0.05sec */ US_RTP_OPUS_CH, US_RTP_OPUS_HZ, 1 /* soft resample */, 50000 /* 50000 = 0.05sec */
); );
if (err < 0) { if (err < 0) {
US_ONCE({ US_JLOG_PERROR_ALSA(err, "aplay", "Can't configure PCM playback"); }); US_ONCE({ US_LOG_PERROR_ALSA(err, "Can't configure PCM playback"); });
goto close_aplay; goto close_aplay;
} }
US_JLOG_INFO("aplay", "Playback opened, playing ..."); US_LOG_INFO("Playback opened, playing ...");
once = 0; once = 0;
} }
@@ -379,20 +342,20 @@ static void *_aplay_thread(void *arg) {
frames = snd_pcm_recover(dev, frames, 1); frames = snd_pcm_recover(dev, frames, 1);
} else { } else {
if (once != 0) { if (once != 0) {
US_JLOG_INFO("aplay", "Playing resumed (snd_pcm_writei) ..."); US_LOG_INFO("Playing resumed (snd_pcm_writei) ...");
} }
once = 0; once = 0;
skip = false; skip = false;
} }
if (frames < 0) { if (frames < 0) {
US_ONCE({ US_JLOG_PERROR_ALSA(frames, "aplay", "Can't play to PCM playback"); }); US_ONCE({ US_LOG_PERROR_ALSA(frames, "Can't play to PCM playback"); });
if (frames == -ENODEV) { if (frames == -ENODEV) {
goto close_aplay; goto close_aplay;
} }
skip = true; skip = true;
} else { } else {
if (once != 0) { if (once != 0) {
US_JLOG_INFO("aplay", "Playing resumed (snd_pcm_recover) ..."); US_LOG_INFO("Playing resumed (snd_pcm_recover) ...");
} }
once = 0; once = 0;
skip = false; skip = false;
@@ -403,7 +366,7 @@ static void *_aplay_thread(void *arg) {
close_aplay: close_aplay:
if (dev != NULL) { if (dev != NULL) {
US_DELETE(dev, snd_pcm_close); US_DELETE(dev, snd_pcm_close);
US_JLOG_INFO("aplay", "Playback closed"); US_LOG_INFO("Playback closed");
} }
} }
return NULL; return NULL;
@@ -430,8 +393,13 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
// sysctl -w net.core.rmem_max=1000000 // sysctl -w net.core.rmem_max=1000000
// sysctl -w net.core.wmem_max=1000000 // sysctl -w net.core.wmem_max=1000000
US_JLOG_INFO("main", "Initializing PiKVM uStreamer plugin %s ...", US_VERSION); US_LOGGING_INIT;
if (gw == NULL || config_dir_path == NULL || ((_g_config = us_config_init(config_dir_path)) == NULL)) {
US_LOG_INFO("Initializing PiKVM uStreamer plugin %s ...", US_VERSION);
if (gw == NULL || !us_str_is_ok(config_dir_path)) {
return -1;
}
if ((_g_config = us_config_init(config_dir_path)) == NULL) {
return -1; return -1;
} }
_g_gw = gw; _g_gw = gw;
@@ -443,9 +411,9 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
if (_g_config->acap_dev_name != NULL) { 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) { }
US_THREAD_CREATE(_g_aplay_tid, _aplay_thread, NULL); if (_g_config->aplay_dev_name != NULL) {
} US_THREAD_CREATE(_g_aplay_tid, _aplay_thread, NULL);
} }
US_THREAD_CREATE(_g_video_rtp_tid, _video_rtp_thread, NULL); US_THREAD_CREATE(_g_video_rtp_tid, _video_rtp_thread, NULL);
US_THREAD_CREATE(_g_video_sink_tid, _video_sink_thread, NULL); US_THREAD_CREATE(_g_video_sink_tid, _video_sink_thread, NULL);
@@ -455,7 +423,7 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
} }
static void _plugin_destroy(void) { static void _plugin_destroy(void) {
US_JLOG_INFO("main", "Destroying plugin ..."); US_LOG_INFO("Destroying plugin ...");
atomic_store(&_g_stop, true); atomic_store(&_g_stop, true);
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } } # define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } }
@@ -475,12 +443,14 @@ static void _plugin_destroy(void) {
US_DELETE(_g_rtpa, us_rtpa_destroy); US_DELETE(_g_rtpa, us_rtpa_destroy);
US_DELETE(_g_rtpv, us_rtpv_destroy); US_DELETE(_g_rtpv, us_rtpv_destroy);
US_DELETE(_g_config, us_config_destroy); US_DELETE(_g_config, us_config_destroy);
US_LOGGING_DESTROY;
} }
static void _plugin_create_session(janus_plugin_session *session, int *err) { static void _plugin_create_session(janus_plugin_session *session, int *err) {
_IF_DISABLED({ *err = -1; return; }); _IF_DISABLED({ *err = -1; return; });
_LOCK_ALL; _LOCK_ALL;
US_JLOG_INFO("main", "Creating session %p ...", session); US_LOG_INFO("Creating session %p ...", session);
us_janus_client_s *const client = us_janus_client_init(_g_gw, session); us_janus_client_s *const client = us_janus_client_init(_g_gw, session);
US_LIST_APPEND(_g_clients, client); US_LIST_APPEND(_g_clients, client);
atomic_store(&_g_has_watchers, true); atomic_store(&_g_has_watchers, true);
@@ -496,7 +466,7 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
bool has_speakers = false; bool has_speakers = false;
US_LIST_ITERATE(_g_clients, client, { US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) { if (client->session == session) {
US_JLOG_INFO("main", "Removing session %p ...", session); US_LOG_INFO("Removing session %p ...", session);
US_LIST_REMOVE(_g_clients, client); US_LIST_REMOVE(_g_clients, client);
us_janus_client_destroy(client); us_janus_client_destroy(client);
found = true; found = true;
@@ -507,7 +477,7 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
} }
}); });
if (!found) { if (!found) {
US_JLOG_WARN("main", "No session %p", session); US_LOG_ERROR("No session %p", session);
*err = -2; *err = -2;
} }
atomic_store(&_g_has_watchers, has_watchers); atomic_store(&_g_has_watchers, has_watchers);
@@ -539,13 +509,13 @@ static void _set_transmit(janus_plugin_session *session, const char *msg, bool t
US_LIST_ITERATE(_g_clients, client, { US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) { if (client->session == session) {
atomic_store(&client->transmit, transmit); atomic_store(&client->transmit, transmit);
// US_JLOG_INFO("main", "%s session %p", msg, session); // US_LOG_INFO("%s session %p", msg, session);
found = true; found = true;
} }
has_watchers = (has_watchers || atomic_load(&client->transmit)); has_watchers = (has_watchers || atomic_load(&client->transmit));
}); });
if (!found) { if (!found) {
US_JLOG_WARN("main", "No session %p", session); US_LOG_ERROR("No session %p", session);
} }
atomic_store(&_g_has_watchers, has_watchers); atomic_store(&_g_has_watchers, has_watchers);
_UNLOCK_ALL; _UNLOCK_ALL;
@@ -567,7 +537,7 @@ static struct janus_plugin_result *_plugin_handle_message(
} }
# define PUSH_ERROR(x_error, x_reason) { \ # define PUSH_ERROR(x_error, x_reason) { \
/*US_JLOG_ERROR("main", "Message error in session %p: %s", session, x_reason);*/ \ /*US_LOG_ERROR("Message error in session %p: %s", session, x_reason);*/ \
json_t *m_event = json_object(); \ json_t *m_event = json_object(); \
json_object_set_new(m_event, "ustreamer", json_string("event")); \ json_object_set_new(m_event, "ustreamer", json_string("event")); \
json_object_set_new(m_event, "error_code", json_integer(x_error)); \ json_object_set_new(m_event, "error_code", json_integer(x_error)); \
@@ -587,7 +557,7 @@ static struct janus_plugin_result *_plugin_handle_message(
PUSH_ERROR(400, "Request not a string"); PUSH_ERROR(400, "Request not a string");
goto done; goto done;
} }
// US_JLOG_INFO("main", "Message: %s", request_str); // US_LOG_INFO("Message: %s", request_str);
# define PUSH_STATUS(x_status, x_payload, x_jsep) { \ # define PUSH_STATUS(x_status, x_payload, x_jsep) { \
json_t *const m_event = json_object(); \ json_t *const m_event = json_object(); \
@@ -612,21 +582,20 @@ static struct janus_plugin_result *_plugin_handle_message(
uint video_orient = 0; uint video_orient = 0;
bool with_acap = false; bool with_acap = false;
bool with_aplay = false; bool with_aplay = false;
bool with_vplay = false;
{ {
json_t *const params = json_object_get(msg, "params"); json_t *const params = json_object_get(msg, "params");
if (params != NULL) { if (params != NULL) {
{ # define READ_BOOL(x_target, x_key, x_cond) { \
json_t *const obj = json_object_get(params, "audio"); json_t *const m_obj = json_object_get(params, x_key); \
if (obj != NULL && json_is_boolean(obj)) { if (m_obj != NULL && json_is_boolean(m_obj)) { \
with_acap = (us_au_probe(_g_config->acap_dev_name) && json_boolean_value(obj)); x_target = (json_boolean_value(m_obj) && (x_cond)); \
} \
} }
} READ_BOOL(with_acap, "audio", us_au_probe(_g_config->acap_dev_name));
{ READ_BOOL(with_aplay, "mic", us_au_probe(_g_config->aplay_dev_name));
json_t *const obj = json_object_get(params, "mic"); READ_BOOL(with_vplay, "camera", (_g_config->vplay_sink_name != NULL));
if (obj != NULL && json_is_boolean(obj)) { # undef READ_BOOL
with_aplay = (us_au_probe(_g_config->aplay_dev_name) && json_boolean_value(obj));
}
}
{ {
json_t *const obj = json_object_get(params, "orientation"); json_t *const obj = json_object_get(params, "orientation");
if (obj != NULL && json_is_integer(obj)) { if (obj != NULL && json_is_integer(obj)) {
@@ -640,41 +609,23 @@ static struct janus_plugin_result *_plugin_handle_message(
} }
} }
{
char *sdp;
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
char *const audio_sdp = (with_acap ? us_rtpa_make_sdp(_g_rtpa, with_aplay) : us_strdup(""));
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
);
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
PUSH_STATUS("started", NULL, offer_jsep);
json_decref(offer_jsep);
free(audio_sdp);
free(video_sdp);
free(sdp);
}
{ {
_LOCK_ALL; _LOCK_ALL;
bool has_listeners = false; bool has_listeners = false;
bool has_speakers = false; bool has_speakers = false;
US_LIST_ITERATE(_g_clients, client, { US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) { if (client->session == session) {
char *const sdp = us_sdp_create(
client->video_ssrc,
client->audio_ssrc,
with_acap,
with_aplay,
with_vplay);
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
PUSH_STATUS("started", NULL, offer_jsep);
json_decref(offer_jsep);
free(sdp);
atomic_store(&client->transmit_acap, with_acap); atomic_store(&client->transmit_acap, with_acap);
atomic_store(&client->transmit_aplay, with_aplay); atomic_store(&client->transmit_aplay, with_aplay);
atomic_store(&client->video_orient, video_orient); atomic_store(&client->video_orient, video_orient);
@@ -689,18 +640,17 @@ 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 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(
"{s:b, s:b, s:{s:s?}}", "{s:b, s:b, s:{s:s?}}",
"audio", acap_avail, "audio", us_au_probe(_g_config->acap_dev_name),
"mic", (acap_avail && us_au_probe(_g_config->aplay_dev_name)), "mic", us_au_probe(_g_config->aplay_dev_name),
"ice", "url", (ice_url != NULL ? ice_url : default_ice_url) "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);
} else if (!strcmp(request_str, "key_required")) { } else if (!strcmp(request_str, "key_required")) {
// US_JLOG_INFO("main", "Got key_required message"); // US_LOG_INFO("Got key_required message");
atomic_store(&_g_key_required, true); atomic_store(&_g_key_required, true);
} else { } else {
@@ -740,8 +690,10 @@ static void _plugin_incoming_rtcp(janus_plugin_session *session, janus_plugin_rt
if (session == NULL || packet == NULL || !packet->video) { if (session == NULL || packet == NULL || !packet->video) {
return; // Accept only valid video return; // Accept only valid video
} }
if (janus_rtcp_has_pli(packet->buffer, packet->length)) { if (
// US_JLOG_INFO("main", "Got video PLI"); janus_rtcp_has_pli(packet->buffer, packet->length)
|| janus_rtcp_has_fir(packet->buffer, packet->length)
) {
atomic_store(&_g_key_required, true); atomic_store(&_g_key_required, true);
} }
} }

View File

@@ -44,22 +44,27 @@ void us_rtp_destroy(us_rtp_s *rtp) {
void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video) { void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video) {
rtp->payload = payload; rtp->payload = payload;
rtp->video = video; rtp->video = video;
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
} }
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked) { #define WRITE_BE_U32(x_offset, x_value) \
*((u32*)(rtp->datagram + x_offset)) = __builtin_bswap32(x_value)
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool last_header) {
u32 word0 = 0x80000000; u32 word0 = 0x80000000;
if (marked) { if (last_header) {
word0 |= 1 << 23; word0 |= 1 << 23;
} }
word0 |= (rtp->payload & 0x7F) << 16; word0 |= (rtp->payload & 0x7F) << 16;
word0 |= rtp->seq; word0 |= rtp->seq;
++rtp->seq; ++rtp->seq;
# define WRITE_BE_U32(x_offset, x_value) \
*((u32*)(rtp->datagram + x_offset)) = __builtin_bswap32(x_value)
WRITE_BE_U32(0, word0); WRITE_BE_U32(0, word0);
WRITE_BE_U32(4, pts); WRITE_BE_U32(4, pts);
WRITE_BE_U32(8, rtp->ssrc); WRITE_BE_U32(8, 0); // SSRC should be written separately for each client
# undef WRITE_BE_U32
} }
void us_rtp_write_ssrc(us_rtp_s *rtp, u32 ssrc) {
WRITE_BE_U32(8, ssrc);
}
#undef WRITE_BE_U32

View File

@@ -23,12 +23,20 @@
#pragma once #pragma once
#include "uslibs/types.h" #include "uslibs/types.h"
#include "uslibs/tools.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 1280 MTU. So to fit it required to subtract:
// 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 below value should be good enought for all cases
#define US_RTP_TOTAL_SIZE 1150
#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
@@ -40,10 +48,9 @@
typedef struct { typedef struct {
uint payload; uint payload;
bool video; bool video;
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 first_of_frame;
@@ -55,8 +62,13 @@ typedef struct {
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp); typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
static inline u32 us_get_ssrc(void) {
return us_triple_u32(us_get_now_monotonic_u64());
}
us_rtp_s *us_rtp_init(void); us_rtp_s *us_rtp_init(void);
void us_rtp_destroy(us_rtp_s *rtp); void us_rtp_destroy(us_rtp_s *rtp);
void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video); void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video);
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked); void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool last_header);
void us_rtp_write_ssrc(us_rtp_s *rtp, u32 ssrc);

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,32 +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=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")
);
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

@@ -26,8 +26,6 @@
#include "rtpv.h" #include "rtpv.h"
#include <stdlib.h> #include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#include <linux/videodev2.h> #include <linux/videodev2.h>
@@ -35,10 +33,15 @@
#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);
static sz _find_annexb(const u8 *data, uz size); static sz _find_annexb(const u8 *data, uz size);
static bool _is_nalu_small(uz size);
static void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu);
static void _rtpv_process_nalu_small(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu);
static void _rtpv_process_nalu_big(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu);
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback) { us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback) {
@@ -55,89 +58,112 @@ 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;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
);
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) {
// There is a complicated logic here but everything works as it should: US_A(frame->format == V4L2_PIX_FMT_H264);
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
assert(frame->format == V4L2_PIX_FMT_H264); if (frame->used <= _PRE) {
return;
}
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); 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;
while (true) { // Find and iterate by nalus // Bytestream can be like:
const uz next_start = last_offset + _PRE; // [00-]00-00-01 NALU [00-]00-00-01 NALU
sz offset = _find_annexb(frame->data + next_start, frame->used - next_start); // ... with optional 00 prefix for _PRE.
if (offset < 0) { // We need to iterate by NALUs only and throw away separators.
break;
}
offset += next_start;
if (last_offset >= 0) { const sz offset_to_first = _find_annexb(frame->data, frame->used);
const u8 *const data = frame->data + last_offset + _PRE; if (offset_to_first < 0) {
uz size = offset - last_offset - _PRE; return;
if (data[size - 1] == 0) { // Check for extra 00 }
uz begin = offset_to_first + _PRE;
bool first_nalu = true;
while (begin < frame->used) { // Find and iterate by NALUs
const u8 *const data = frame->data + begin;
const sz offset_to_next = _find_annexb(data, frame->used - begin);
if (offset_to_next >= 0) { // Process NALUs between prefixes
uz size = offset_to_next;
if (size > 1 && data[size - 1] == 0) { // Check for extra trailing zero
--size; --size;
} }
_rtpv_process_nalu(rtpv, data, size, pts, false); if (size > 1) { // Skip too short NALUs
_rtpv_process_nalu(rtpv, data, size, pts, first_nalu, false);
first_nalu = false;
}
begin += offset_to_next + _PRE;
} else { // Process the tail
const uz size = frame->used - begin;
if (size > 1) {
_rtpv_process_nalu(rtpv, data, size, pts, first_nalu, true);
first_nalu = false;
}
break;
} }
last_offset = offset;
}
if (last_offset >= 0) {
const u8 *const data = frame->data + last_offset + _PRE;
uz size = frame->used - last_offset - _PRE;
_rtpv_process_nalu(rtpv, data, size, pts, true);
} }
} }
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked) { static sz _find_annexb(const u8 *data, uz size) {
// Parses buffer for 00 00 01 start codes
if (size >= _PRE) {
for (uz i = 0; i <= size - _PRE; ++i) {
if (
data[i] == 0
&& data[i + 1] == 0
&& data[i + 2] == 1
) {
return i;
}
}
}
return -1;
}
#undef _PRE
static bool _is_nalu_small(uz size) {
return (size + US_RTP_HEADER_SIZE <= US_RTP_TOTAL_SIZE);
}
static void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu) {
US_A(size > 1);
if (_is_nalu_small(size)) {
_rtpv_process_nalu_small(rtpv, data, size, pts, first_nalu, last_nalu);
} else {
_rtpv_process_nalu_big(rtpv, data, size, pts, first_nalu, last_nalu);
}
}
static void _rtpv_process_nalu_small(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu) {
US_A(size > 1);
US_A(_is_nalu_small(size));
us_rtp_s *rtp = rtpv->rtp;
u8 *dg = rtp->datagram;
us_rtp_write_header(rtp, pts, last_nalu);
memcpy(dg + US_RTP_HEADER_SIZE, data, size);
rtp->used = size + US_RTP_HEADER_SIZE;
rtp->first_of_frame = first_nalu;
rtp->last_of_frame = last_nalu;
rtpv->callback(rtp);
}
static void _rtpv_process_nalu_big(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu) {
US_A(size > 1);
US_A(!_is_nalu_small(size));
const uint ref_idc = (data[0] >> 5) & 3; const uint ref_idc = (data[0] >> 5) & 3;
const uint type = data[0] & 0x1F; const uint type = data[0] & 0x1F;
u8 *dg = rtpv->rtp->datagram; us_rtp_s *rtp = rtpv->rtp;
u8 *dg = rtp->datagram;
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
us_rtp_write_header(rtpv->rtp, pts, marked);
memcpy(dg + US_RTP_HEADER_SIZE, data, size);
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
rtpv->rtp->last_of_frame = true;
rtpv->callback(rtpv->rtp);
rtpv->rtp->first_of_frame = false;
return;
}
const uz fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead const uz fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
@@ -146,13 +172,13 @@ 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;
} }
us_rtp_write_header(rtpv->rtp, pts, (marked && last)); us_rtp_write_header(rtp, pts, (last_nalu && last));
dg[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5); dg[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
@@ -166,27 +192,13 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool
dg[US_RTP_HEADER_SIZE + 1] = fu; dg[US_RTP_HEADER_SIZE + 1] = fu;
memcpy(dg + fu_overhead, src, frag_size); memcpy(dg + fu_overhead, src, frag_size);
rtpv->rtp->used = fu_overhead + frag_size; rtp->used = fu_overhead + frag_size;
rtpv->rtp->last_of_frame = last; rtp->first_of_frame = (first_nalu && first);
rtpv->callback(rtpv->rtp); rtp->last_of_frame = (last_nalu && last);
rtpv->rtp->first_of_frame = false; rtpv->callback(rtp);
src += frag_size; src += frag_size;
remaining -= frag_size; remaining -= frag_size;
first = false; first = false;
} }
} }
static sz _find_annexb(const u8 *data, uz size) {
// Parses buffer for 00 00 01 start codes
if (size >= _PRE) {
for (uz index = 0; index <= size - _PRE; ++index) {
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
return index;
}
}
}
return -1;
}
#undef _PRE

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

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

@@ -0,0 +1,119 @@
/*****************************************************************************
# #
# 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(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay, bool vplay) {
char *video_sdp;
{
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
const uint pl = US_RTP_H264_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 ccm fir" 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=%s" RN,
pl, pl, pl, pl, pl, pl, pl,
video_ssrc,
(vplay ? "sendrecv" : "sendonly"));
}
char *audio_sdp;
if (acap || aplay) {
const uint pl = US_RTP_OPUS_PAYLOAD;
char *dir = "";
if (acap && aplay) {
dir = "sendrecv";
} else if (acap && !aplay) {
dir = "sendonly";
} else if (!acap && aplay) {
dir = "recvonly";
}
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=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,
audio_ssrc,
dir);
} else {
audio_sdp = us_strdup("");
}
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;
}

View File

@@ -23,9 +23,6 @@
#pragma once #pragma once
#include "uslibs/types.h" #include "uslibs/types.h"
#include "uslibs/frame.h"
#include "uslibs/memsinksh.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s *mem, u64 last_id); char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay, bool vplay);
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required);

1
janus/src/uslibs/chip.c Symbolic link
View File

@@ -0,0 +1 @@
../../../src/libs/chip.c

1
janus/src/uslibs/chip.h Symbolic link
View File

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

1
janus/src/uslibs/logging.c Symbolic link
View File

@@ -0,0 +1 @@
../../../src/libs/logging.c

1
janus/src/uslibs/logging.h Symbolic link
View File

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

View File

@@ -20,35 +20,13 @@
*****************************************************************************/ *****************************************************************************/
#include "tc358743.h" #include <janus/plugins/plugin.h>
#include <unistd.h> #include "threading.h"
#include <fcntl.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "types.h"
#include "tools.h"
#include "xioctl.h"
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz) { #define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
*audio_hz = 0; char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
us_thread_get_name(m_tname_buf); \
struct v4l2_control ctl = {.id = TC358743_CID_AUDIO_PRESENT}; JANUS_LOG(LOG_INFO, "== ustreamer/%-15s " x_label " -- " x_msg "\n", m_tname_buf, ##__VA_ARGS__); \
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
return -1;
} }
if (!ctl.value) {
return 0; // No audio
}
US_MEMSET_ZERO(ctl);
ctl.id = TC358743_CID_AUDIO_SAMPLING_RATE;
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
return -1;
}
*audio_hz = ctl.value;
return 0;
}

1
janus/src/uslibs/memsink.c Symbolic link
View File

@@ -0,0 +1 @@
../../../src/libs/memsink.c

1
janus/src/uslibs/memsink.h Symbolic link
View File

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

View File

@@ -1 +0,0 @@
../../../src/libs/tc358743.c

View File

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

View File

@@ -9,7 +9,7 @@ RUN pacman -Syu --noconfirm \
&& pacman -S --needed --noconfirm \ && pacman -S --needed --noconfirm \
vim \ vim \
git \ git \
libjpeg \ libjpeg-turbo \
libevent \ libevent \
libutil-linux \ libutil-linux \
libbsd \ libbsd \

View File

@@ -37,7 +37,7 @@ deps =
[testenv:mypy] [testenv:mypy]
allowlist_externals = bash allowlist_externals = bash
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py python/*.py' commands = bash -c 'mypy --config-file=linters/mypy.ini --cache-dir=/dev/null tools/*.py python/*.py'
deps = deps =
mypy mypy

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.48" "January 2021" .TH USTREAMER-DUMP 1 "version 6.62" "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.48" "November 2020" .TH USTREAMER 1 "version 6.62" "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
@@ -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
@@ -120,6 +120,12 @@ Delay before trying to connect to the device again after an error (timeout for e
.TP .TP
.BR \-\-m2m\-device\ \fI/dev/path .BR \-\-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 \-\-media\-device \fI/dev/path
Path to V4L2 /dev/media* device for setting subdevices (currently necessary for Raspberry Pi 5). Default: unset.
.TP
.BR \-\-media\-entity\-name \fIname
Name of the V4L2 entity to grab video from, will be router to \-\-device, for example "tc358743 10-000f". Default: unset.
.SS "Image control options" .SS "Image control options"
.TP .TP

View File

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

View File

@@ -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.48 PKG_VERSION:=6.62
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com> PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

View File

@@ -9,13 +9,12 @@ all: root
root: $(shell find src -type f,l) setup.py 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 --wheel --skip-dependency-check --no-isolation
$(ECHO) $(PY) -m pip install dist/*.whl --ignore-installed --root=./root
install: install:
$(PY) -m pip install dist/*.whl --ignore-installed --prefix=$(PREFIX) --root=$(if $(R_DESTDIR),$(R_DESTDIR),/) $(PY) -m installer --prefix=$(PREFIX) $(if $(R_DESTDIR),--destdir=$(R_DESTDIR),) dist/*.whl
clean: clean:
rm -rf root dist ustreamer.egg-info rm -rf root dist build ustreamer.egg-info

View File

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

View File

@@ -230,7 +230,7 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
self->frame_id = mem->id; self->frame_id = mem->id;
self->frame_ts = us_get_now_monotonic(); self->frame_ts = us_get_now_monotonic();
if (key_required) { if (key_required) {
mem->key_requested = true; mem->wants.key = true;
} }
if (flock(self->fd, LOCK_UN) < 0) { if (flock(self->fd, LOCK_UN) < 0) {

View File

@@ -46,7 +46,7 @@ _OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
# ===== # =====
ifneq ($(shell sh -c 'uname 2>/dev/null || echo Unknown'),FreeBSD) ifeq ($(findstring bsd,$(shell $(CC) -dumpmachine)),)
override _USTR_LDFLAGS += -latomic override _USTR_LDFLAGS += -latomic
override _DUMP_LDFLAGS += -latomic override _DUMP_LDFLAGS += -latomic
override _V4P_LDFLAGS += -latomic override _V4P_LDFLAGS += -latomic
@@ -78,7 +78,7 @@ endif
ifneq ($(MK_WITH_SETPROCTITLE),) ifneq ($(MK_WITH_SETPROCTITLE),)
override _CFLAGS += -DMK_WITH_SETPROCTITLE -DWITH_SETPROCTITLE override _CFLAGS += -DMK_WITH_SETPROCTITLE -DWITH_SETPROCTITLE
ifeq ($(shell uname -s | tr A-Z a-z),linux) ifneq ($(findstring linux,$(shell $(CC) -dumpmachine)),)
override _USTR_LDFLAGS += -lbsd override _USTR_LDFLAGS += -lbsd
endif endif
endif endif

View File

@@ -177,14 +177,14 @@ int main(int argc, char *argv[]) {
# undef OPT_NUMBER # undef OPT_NUMBER
# undef OPT_SET # undef OPT_SET
if (sink_name == NULL || sink_name[0] == '\0') { if (!us_str_is_ok(sink_name)) {
puts("Missing option --sink. See --help for details."); puts("Missing option --sink. See --help for details.");
return 1; return 1;
} }
_output_context_s ctx = {0}; _output_context_s ctx = {0};
if (out_path && out_path[0] != '\0') { if (us_str_is_ok(out_path)) {
if ((ctx.v_out = (void*)us_output_file_init(out_path, out_json)) == NULL) { if ((ctx.v_out = (void*)us_output_file_init(out_path, out_json)) == NULL) {
return 1; return 1;
} }
@@ -236,8 +236,9 @@ static int _dump_sink(
long double last_ts = 0; long double last_ts = 0;
while (!_g_stop) { while (!_g_stop) {
bool key_requested; us_memsink_wants_s w_get = {0};
const int got = us_memsink_client_get(sink, frame, &key_requested, key_required); us_memsink_wants_s w_put = {.key = key_required};
const int got = us_memsink_client_get(sink, frame, &w_get, (key_required ? &w_put : NULL));
if (got == 0) { if (got == 0) {
key_required = false; key_required = false;
@@ -252,7 +253,7 @@ static int _dump_sink(
frame->height, frame->height,
frame->gop, frame->gop,
frame->key, frame->key,
key_requested, w_get.key,
frame->grab_end_ts - frame->grab_begin_ts, frame->grab_end_ts - frame->grab_begin_ts,
frame->encode_begin_ts - frame->grab_end_ts, frame->encode_begin_ts - frame->grab_end_ts,
frame->encode_end_ts - frame->encode_begin_ts, frame->encode_end_ts - frame->encode_begin_ts,

View File

@@ -34,10 +34,12 @@
#include <sys/select.h> #include <sys/select.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/stat.h>
#include <pthread.h> #include <pthread.h>
#include <linux/videodev2.h> #include <linux/videodev2.h>
#include <linux/v4l2-controls.h> #include <linux/media.h>
#include <linux/v4l2-subdev.h>
#include "types.h" #include "types.h"
#include "errors.h" #include "errors.h"
@@ -47,7 +49,8 @@
#include "threading.h" #include "threading.h"
#include "frame.h" #include "frame.h"
#include "xioctl.h" #include "xioctl.h"
#include "tc358743.h" #include "media.h"
#include "chip.h"
static const struct { static const struct {
@@ -63,18 +66,19 @@ static const struct {
static const struct { static const struct {
const char *name; // cppcheck-suppress unusedStructMember const char *name; // cppcheck-suppress unusedStructMember
const uint format; // cppcheck-suppress unusedStructMember const uint format; // cppcheck-suppress unusedStructMember
const uint media_format; // cppcheck-suppress unusedStructMember
} _FORMATS[] = { } _FORMATS[] = {
{"YUYV", V4L2_PIX_FMT_YUYV}, {"YUYV", V4L2_PIX_FMT_YUYV, MEDIA_BUS_FMT_YUYV8_1X16},
{"YVYU", V4L2_PIX_FMT_YVYU}, {"YVYU", V4L2_PIX_FMT_YVYU, MEDIA_BUS_FMT_YVYU8_1X16},
{"UYVY", V4L2_PIX_FMT_UYVY}, {"UYVY", V4L2_PIX_FMT_UYVY, MEDIA_BUS_FMT_UYVY8_1X16},
{"YUV420", V4L2_PIX_FMT_YUV420}, {"YUV420", V4L2_PIX_FMT_YUV420, MEDIA_BUS_FMT_YUV8_1X24},
{"YVU420", V4L2_PIX_FMT_YVU420}, {"YVU420", V4L2_PIX_FMT_YVU420, 0},
{"GREY", V4L2_PIX_FMT_GREY}, {"GREY", V4L2_PIX_FMT_GREY, MEDIA_BUS_FMT_Y8_1X8},
{"RGB565", V4L2_PIX_FMT_RGB565}, {"RGB565", V4L2_PIX_FMT_RGB565, MEDIA_BUS_FMT_RGB565_1X16},
{"RGB24", V4L2_PIX_FMT_RGB24}, {"RGB24", V4L2_PIX_FMT_RGB24, MEDIA_BUS_FMT_RGB888_1X24},
{"BGR24", V4L2_PIX_FMT_BGR24}, {"BGR24", V4L2_PIX_FMT_BGR24, MEDIA_BUS_FMT_BGR888_1X24},
{"MJPEG", V4L2_PIX_FMT_MJPEG}, {"MJPEG", V4L2_PIX_FMT_MJPEG, 0},
{"JPEG", V4L2_PIX_FMT_JPEG}, {"JPEG", V4L2_PIX_FMT_JPEG, MEDIA_BUS_FMT_JPEG_1X8},
}; };
static const struct { static const struct {
@@ -86,10 +90,12 @@ static const struct {
}; };
static int _capture_wait_buffer(us_capture_s *cap); static int _capture_wait_buffer(us_capture_s *cap);
static int _capture_consume_event(const us_capture_s *cap); static int _v4l2_consume_event(int fd);
static void _v4l2_buffer_copy(const struct v4l2_buffer *src, struct v4l2_buffer *dest); static void _v4l2_buffer_copy(const struct v4l2_buffer *src, struct v4l2_buffer *dest);
static bool _capture_is_buffer_valid(const us_capture_s *cap, const struct v4l2_buffer *buf, const u8 *data); static bool _capture_is_buffer_valid(const us_capture_s *cap, const struct v4l2_buffer *buf, const u8 *data);
static int _capture_open_check_cap(us_capture_s *cap); static int _capture_open_check_cap(us_capture_s *cap);
static int _capture_open_media_pads(const us_capture_s *cap);
static int _capture_open_media_format(const us_capture_s *cap, const us_media_pad_s *pad);
static int _capture_open_dv_timings(us_capture_s *cap, bool apply); static int _capture_open_dv_timings(us_capture_s *cap, bool apply);
static int _capture_open_format(us_capture_s *cap, bool first); static int _capture_open_format(us_capture_s *cap, bool first);
static void _capture_open_hw_fps(us_capture_s *cap); static void _capture_open_hw_fps(us_capture_s *cap);
@@ -101,27 +107,11 @@ 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);
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 int _capture_query_control(
const us_capture_s *cap,
struct v4l2_queryctrl *query,
const char *name,
uint cid,
bool quiet);
static void _capture_set_control(
const us_capture_s *cap,
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);
static const char *_standard_to_string(v4l2_std_id standard); static const char *_standard_to_string(v4l2_std_id standard);
static const char *_io_method_to_string_supported(enum v4l2_memory io_method); static const char *_io_method_to_string_supported(enum v4l2_memory io_method);
static uint _format_to_media(uint format);
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("CAP: " x_msg, ##__VA_ARGS__) #define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("CAP: " x_msg, ##__VA_ARGS__)
@@ -135,10 +125,14 @@ us_capture_s *us_capture_init(void) {
us_capture_runtime_s *run; us_capture_runtime_s *run;
US_CALLOC(run, 1); US_CALLOC(run, 1);
run->fd = -1; run->fd = -1;
run->dv_timings_fd = -1;
US_ARRAY_ITERATE(run->media_pads, 0, item, { item->fd = -1; });
us_capture_s *cap; us_capture_s *cap;
US_CALLOC(cap, 1); US_CALLOC(cap, 1);
cap->path = "/dev/video0"; cap->path = "/dev/video0";
cap->media_path = "";
cap->media_entity_name = "tc358743 10-000f"; // Will not be enabled without media_path
cap->width = 640; cap->width = 640;
cap->height = 480; cap->height = 480;
cap->format = V4L2_PIX_FMT_YUYV; cap->format = V4L2_PIX_FMT_YUYV;
@@ -148,11 +142,13 @@ us_capture_s *us_capture_init(void) {
cap->n_bufs = us_get_cores_available() + 1; cap->n_bufs = us_get_cores_available() + 1;
cap->min_frame_size = 128; cap->min_frame_size = 128;
cap->timeout = 1; cap->timeout = 1;
cap->ctl = us_controls_init();
cap->run = run; cap->run = run;
return cap; return cap;
} }
void us_capture_destroy(us_capture_s *cap) { void us_capture_destroy(us_capture_s *cap) {
us_controls_destroy(cap->ctl);
free(cap->run); free(cap->run);
free(cap); free(cap);
} }
@@ -201,12 +197,20 @@ 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 && us_str_is_ok(cap->media_path)) {
if (_capture_open_media_pads(cap) < 0) {
goto error_no_device;
}
run->dv_timings_fd = run->media_pads[0].fd;
} else {
run->dv_timings_fd = 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}; _LOG_DEBUG("Probing the cable ...")
if (!us_xioctl(run->fd, VIDIOC_G_CTRL, &ctl)) { switch (us_chip_check_cable(run->fd)) {
if (!ctl.value) { case 0: break;
goto error_no_cable; case US_ERROR_NO_CABLE: goto error_no_cable;
}
} }
_LOG_DEBUG("Probing DV-timings or QuerySTD ..."); _LOG_DEBUG("Probing DV-timings or QuerySTD ...");
switch (_capture_open_dv_timings(cap, false)) { switch (_capture_open_dv_timings(cap, false)) {
@@ -228,16 +232,20 @@ int us_capture_open(us_capture_s *cap) {
if (cap->dv_timings && _capture_open_dv_timings(cap, true) < 0) { if (cap->dv_timings && _capture_open_dv_timings(cap, true) < 0) {
goto error; goto error;
} }
if (cap->dv_timings && us_str_is_ok(cap->media_path)) {
US_ARRAY_ITERATE(run->media_pads, 0, pad, {
if (pad->fd >= -1 && _capture_open_media_format(cap, pad) < 0) {
goto error;
}
});
}
if (_capture_open_format(cap, true) < 0) { if (_capture_open_format(cap, true) < 0) {
goto error; goto error;
} }
if (cap->dv_timings && cap->persistent) { if (cap->dv_timings && cap->persistent) {
struct v4l2_control ctl = {.id = TC358743_CID_LANES_ENOUGH}; switch (us_chip_tc358743_check_lanes(run->fd)) {
if (!us_xioctl(run->fd, VIDIOC_G_CTRL, &ctl)) { case 0: break;
if (!ctl.value) { case US_ERROR_NO_LANES: goto error_no_lanes;
_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);
@@ -255,7 +263,7 @@ int us_capture_open(us_capture_s *cap) {
goto error; goto error;
} }
} }
_capture_apply_controls(cap); us_controls_apply(cap->ctl, cap->run->fd);
enum v4l2_buf_type type = run->capture_type; enum v4l2_buf_type type = run->capture_type;
if (us_xioctl(run->fd, VIDIOC_STREAMON, &type) < 0) { if (us_xioctl(run->fd, VIDIOC_STREAMON, &type) < 0) {
@@ -287,6 +295,7 @@ error_no_sync:
return US_ERROR_NO_SYNC; return US_ERROR_NO_SYNC;
error_no_lanes: error_no_lanes:
US_ONCE_FOR(run->open_error_once, __LINE__, { _LOG_ERROR("Not enough lanes, the hardware can't handle this signal"); });
us_capture_close(cap); us_capture_close(cap);
return US_ERROR_NO_LANES; return US_ERROR_NO_LANES;
@@ -337,7 +346,9 @@ void us_capture_close(us_capture_s *cap) {
run->n_bufs = 0; run->n_bufs = 0;
} }
US_ARRAY_ITERATE(run->media_pads, 0, pad, { US_CLOSE_FD(pad->fd); });
US_CLOSE_FD(run->fd); US_CLOSE_FD(run->fd);
run->dv_timings_fd = -1; // It's just a copy of the media or device descriptor
if (say) { if (say) {
_LOG_INFO("Capturing stopped"); _LOG_INFO("Capturing stopped");
@@ -498,6 +509,7 @@ int _capture_wait_buffer(us_capture_s *cap) {
INIT_FD_SET(read_fds); INIT_FD_SET(read_fds);
INIT_FD_SET(error_fds); INIT_FD_SET(error_fds);
# undef INIT_FD_SET # undef INIT_FD_SET
FD_SET(run->dv_timings_fd, &error_fds);
// Раньше мы проверяли и has_write, но потом выяснилось, что libcamerify зачем-то // Раньше мы проверяли и has_write, но потом выяснилось, что libcamerify зачем-то
// генерирует эвенты на запись, вероятно ошибочно. Судя по всему, игнорирование // генерирует эвенты на запись, вероятно ошибочно. Судя по всему, игнорирование
@@ -511,10 +523,16 @@ int _capture_wait_buffer(us_capture_s *cap) {
bool has_read = false; bool has_read = false;
bool has_error = false; bool has_error = false;
const int selected = select(run->fd + 1, &read_fds, NULL, &error_fds, &timeout); bool has_dv_error = false;
const int max_fd = US_MAX(run->fd, run->dv_timings_fd);
const int selected = select(max_fd + 1, &read_fds, NULL, &error_fds, &timeout);
if (selected > 0) { if (selected > 0) {
has_read = FD_ISSET(run->fd, &read_fds); has_read = FD_ISSET(run->fd, &read_fds);
has_error = FD_ISSET(run->fd, &error_fds); has_error = FD_ISSET(run->fd, &error_fds);
if (run->fd != run->dv_timings_fd) {
has_dv_error = FD_ISSET(run->dv_timings_fd, &error_fds);
}
} }
_LOG_DEBUG("Device select() --> %d; has_read=%d, has_error=%d", selected, has_read, has_error); _LOG_DEBUG("Device select() --> %d; has_read=%d, has_error=%d", selected, has_read, has_error);
@@ -527,16 +545,19 @@ int _capture_wait_buffer(us_capture_s *cap) {
_LOG_ERROR("Device select() timeout"); _LOG_ERROR("Device select() timeout");
return -1; return -1;
} else { } else {
if (has_error && _capture_consume_event(cap) < 0) { if (has_error && _v4l2_consume_event(run->fd) < 0) {
return -1; // Restart required
}
if (has_dv_error && _v4l2_consume_event(run->dv_timings_fd) < 0) {
return -1; // Restart required return -1; // Restart required
} }
} }
return 0; return 0;
} }
static int _capture_consume_event(const us_capture_s *cap) { static int _v4l2_consume_event(int fd) {
struct v4l2_event event; struct v4l2_event event;
if (us_xioctl(cap->run->fd, VIDIOC_DQEVENT, &event) < 0) { if (us_xioctl(fd, VIDIOC_DQEVENT, &event) < 0) {
_LOG_PERROR("Can't consume V4L2 event"); _LOG_PERROR("Can't consume V4L2 event");
return -1; return -1;
} }
@@ -661,6 +682,192 @@ static int _capture_open_check_cap(us_capture_s *cap) {
return 0; return 0;
} }
static int _open_subdev_for_entity(
struct media_v2_topology *topology,
int entity_id
) {
struct media_v2_interface *interfaces = (struct media_v2_interface *)topology->ptr_interfaces;
int subdev_intf = us_media_find_link(topology, MEDIA_LNK_FL_INTERFACE_LINK, -1, entity_id, NULL);
int dev_major = -1;
int dev_minor = -1;
char *dev_path = NULL;
int fd;
if (subdev_intf < 0) {
_LOG_ERROR("Failed to find interface for entity %d", entity_id);
return -1;
}
for (uint i = 0; i < topology->num_interfaces; ++i) {
if (
interfaces[i].intf_type == MEDIA_INTF_T_V4L_SUBDEV
&& interfaces[i].id == (u32)subdev_intf
) {
dev_major = interfaces[i].devnode.major;
dev_minor = interfaces[i].devnode.minor;
}
}
if (dev_major < 0 || dev_minor < 0) {
_LOG_ERROR("Cannot find interface %d", subdev_intf);
return -1;
}
US_ASPRINTF(dev_path, "/dev/char/%d:%d", dev_major, dev_minor);
if ((fd = open(dev_path, O_RDWR | O_NONBLOCK)) < 0) {
_LOG_PERROR("Failed to open %s", dev_path);
free(dev_path);
return -1;
}
free(dev_path);
return fd;
}
static int _capture_open_media_pads(const us_capture_s *cap) {
// This function needs to:
// 1. Find entity related to actual input signal
// 2. Find entity related to the selected v4l2 device (cap->path)
// 3. Figure out links to connect them
// The whole path is:
// - source_intf_id (points at relevant /dev/v4l-subdev*)
// - source_entity_id
// - source_pad
// - mux_source_pad_id, mux_source_pad
// - mux_entity_id (usually named "csi2" here)
// - mux_sink_pad_id, mux_sink_pad
// - sink_pad
// - sink_entity_id
// - sink_intf_id (points at cap->path / run->fd)
// Pads need saving both ID (for links lookup) and index (for MEDIA_IOC_SETUP_LINK ioctl),
// so lookup relevant media_v2_pad structure.
// Walk the relevant paths from both ends in parallel to reduce numbers of loops.
us_capture_runtime_s *const run = cap->run;
struct media_v2_topology *topology = NULL;
int media_fd = -1;
int mux_subdev_fd = -1;
int source_subdev_fd = -1;
if ((media_fd = open(cap->media_path, O_RDWR | O_NONBLOCK)) < 0) {
_LOG_PERROR("Can't open media device");
goto error;
}
if ((topology = us_media_topology_init(media_fd)) == NULL) {
goto error;
}
const int source_entity_id = us_media_find_entity_by_name(topology, cap->media_entity_name);
if (source_entity_id == -1) {
_LOG_ERROR("Can't find source media entity '%s'", cap->media_entity_name);
goto error;
}
const struct media_v2_pad *source_pad = us_media_find_pad_by_entity(topology, MEDIA_PAD_FL_SOURCE, source_entity_id);
if (source_pad == NULL) {
_LOG_ERROR("Failed to find pad for entity %d ('%s')", source_entity_id, cap->media_entity_name);
goto error;
}
struct stat st = {0};
if (fstat(run->fd, &st) < 0) {
_LOG_PERROR("Can't stat() video device");
goto error;
}
const int sink_entity_id = us_media_find_entity_by_devnode(topology, st.st_rdev);
if (sink_entity_id == -1) {
_LOG_ERROR("Can't find sink media entity '%s'", cap->path);
goto error;
}
const struct media_v2_pad *sink_pad = us_media_find_pad_by_entity(topology, MEDIA_PAD_FL_SINK, sink_entity_id);
if (sink_pad == NULL) {
_LOG_ERROR("Failed to find pad for entity %d ('%s')", sink_entity_id, cap->path);
goto error;
}
u32 source_link_flags = 0;
u32 sink_link_flags = 0;
int mux_source_pad_id = us_media_find_link(topology, MEDIA_LNK_FL_DATA_LINK, source_pad->id, -1, &source_link_flags);
int mux_sink_pad_id = us_media_find_link(topology, MEDIA_LNK_FL_DATA_LINK, -1, sink_pad->id, &sink_link_flags);
const struct media_v2_pad *mux_source_pad = us_media_find_pad(topology, MEDIA_PAD_FL_SINK, mux_source_pad_id);
const struct media_v2_pad *mux_sink_pad = us_media_find_pad(topology, MEDIA_PAD_FL_SOURCE, mux_sink_pad_id);
// Those were referenced by relevant links, not finding them means kernel gave us incomplete info
US_A(mux_source_pad);
US_A(mux_sink_pad);
if (mux_source_pad->entity_id != mux_sink_pad->entity_id) {
_LOG_ERROR(
"Media source entity %d '%s' connected to a different entity (%d) "
"than the sink entity %d '%s' (connected to %d). Select a different device.",
source_entity_id, cap->media_entity_name, mux_source_pad->entity_id,
sink_entity_id, cap->path, mux_sink_pad->entity_id);
goto error;
}
const int mux_entity_id = mux_source_pad->entity_id;
if (mux_entity_id == -1) {
_LOG_ERROR("Failed to find media mux entity");
goto error;
}
// this one is usually MEDIA_LNK_FL_IMMUTABLE, so it's always enabled,
// but check anyway
if (!(source_link_flags & MEDIA_LNK_FL_ENABLED)) {
if (us_media_xioctl_setup_link(
media_fd,
source_pad, mux_source_pad,
source_link_flags | MEDIA_LNK_FL_ENABLED
) < 0) {
_LOG_PERROR("Failed to enable source link");
goto error;
}
}
if (!(sink_link_flags & MEDIA_LNK_FL_ENABLED)) {
if (us_media_xioctl_setup_link(
media_fd,
mux_sink_pad, sink_pad,
sink_link_flags | MEDIA_LNK_FL_ENABLED
) < 0) {
_LOG_PERROR("Failed to enable sink link");
goto error;
}
}
if ((source_subdev_fd = _open_subdev_for_entity(topology, source_entity_id)) < 0) {
_LOG_PERROR("Failed to open source v4l2 subdevice");
goto error;
}
if ((mux_subdev_fd = _open_subdev_for_entity(topology, mux_entity_id)) < 0) {
_LOG_PERROR("Failed to open mux v4l2 subdevice");
goto error;
}
// Save PAD indices for VIDIOC_SUBDEV_S_FMT
run->media_pads[0].fd = source_subdev_fd;
run->media_pads[0].pad = source_pad->index;
run->media_pads[1].fd = mux_subdev_fd;
run->media_pads[1].pad = mux_source_pad->index;
run->media_pads[2].fd = mux_subdev_fd;
run->media_pads[2].pad = mux_sink_pad->index;
goto ok;
error:
US_CLOSE_FD(source_subdev_fd);
US_CLOSE_FD(mux_subdev_fd); // cppcheck-suppress unreadVariable
source_subdev_fd = -1; // cppcheck-suppress redundantAssignment
ok:
US_CLOSE_FD(media_fd); // cppcheck-suppress unreadVariable
US_DELETE(topology, us_media_topology_destroy);
return (source_subdev_fd >= 0 ? 0 : -1);
}
static int _capture_open_dv_timings(us_capture_s *cap, bool apply) { static int _capture_open_dv_timings(us_capture_s *cap, bool apply) {
// Just probe only if @apply is false // Just probe only if @apply is false
@@ -670,7 +877,7 @@ static int _capture_open_dv_timings(us_capture_s *cap, bool apply) {
struct v4l2_dv_timings dv = {0}; struct v4l2_dv_timings dv = {0};
_LOG_DEBUG("Querying DV-timings (apply=%u) ...", apply); _LOG_DEBUG("Querying DV-timings (apply=%u) ...", apply);
if (us_xioctl(run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv) < 0) { if (us_xioctl(run->dv_timings_fd, VIDIOC_QUERY_DV_TIMINGS, &dv) < 0) {
// 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)
@@ -701,7 +908,7 @@ static int _capture_open_dv_timings(us_capture_s *cap, bool apply) {
} }
_LOG_DEBUG("Applying DV-timings ..."); _LOG_DEBUG("Applying DV-timings ...");
if (us_xioctl(run->fd, VIDIOC_S_DV_TIMINGS, &dv) < 0) { if (us_xioctl(run->dv_timings_fd, VIDIOC_S_DV_TIMINGS, &dv) < 0) {
_LOG_PERROR("Failed to apply DV-timings"); _LOG_PERROR("Failed to apply DV-timings");
return -1; return -1;
} }
@@ -712,7 +919,7 @@ static int _capture_open_dv_timings(us_capture_s *cap, bool apply) {
querystd: querystd:
_LOG_DEBUG("Failed to query DV-timings, trying QuerySTD ..."); _LOG_DEBUG("Failed to query DV-timings, trying QuerySTD ...");
if (us_xioctl(run->fd, VIDIOC_QUERYSTD, &cap->standard) < 0) { if (us_xioctl(run->dv_timings_fd, VIDIOC_QUERYSTD, &cap->standard) < 0) {
if (apply) { if (apply) {
char *std_error = us_errno_to_string(errno); // Read the errno first char *std_error = us_errno_to_string(errno); // Read the errno first
char *dv_error = us_errno_to_string(dv_errno); char *dv_error = us_errno_to_string(dv_errno);
@@ -724,7 +931,7 @@ querystd:
} else if (!apply) { } else if (!apply) {
goto probe_only; goto probe_only;
} }
if (us_xioctl(run->fd, VIDIOC_S_STD, &cap->standard) < 0) { if (us_xioctl(run->dv_timings_fd, VIDIOC_S_STD, &cap->standard) < 0) {
_LOG_PERROR("Can't set apply standard: %s", _standard_to_string(cap->standard)); _LOG_PERROR("Can't set apply standard: %s", _standard_to_string(cap->standard));
return -1; return -1;
} }
@@ -734,7 +941,7 @@ subscribe:
; // Empty statement for the goto label above ; // Empty statement for the goto label above
struct v4l2_event_subscription sub = {.type = V4L2_EVENT_SOURCE_CHANGE}; struct v4l2_event_subscription sub = {.type = V4L2_EVENT_SOURCE_CHANGE};
_LOG_DEBUG("Subscribing to V4L2_EVENT_SOURCE_CHANGE ...") _LOG_DEBUG("Subscribing to V4L2_EVENT_SOURCE_CHANGE ...")
if (us_xioctl(cap->run->fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) { if (us_xioctl(cap->run->dv_timings_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
_LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE"); _LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE");
return -1; return -1;
} }
@@ -743,6 +950,47 @@ probe_only:
return 0; return 0;
} }
static int _capture_open_media_format(const us_capture_s *cap, const us_media_pad_s *pad) {
const us_capture_runtime_s *const run = cap->run;
uint media_format = _format_to_media(cap->format);
if (media_format == 0) {
_LOG_ERROR("Format not supported for V4L2-subdev: %#x", cap->format);
return -1;
}
struct v4l2_subdev_format fmt = {
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
.pad = pad->pad,
.format.width = run->width,
.format.height = run->height,
.format.code = media_format,
.format.field = V4L2_FIELD_NONE,
};
// Thankfully fmt.stream seems to be irrelevant on RPi5:
// # v4l2-ctl -d /dev/v4l-subdev0 --get-routing
// Streams API not supported.
if (us_xioctl(pad->fd, VIDIOC_SUBDEV_S_FMT, &fmt) < 0) {
_LOG_PERROR("Can't set device format");
return -1;
}
// This could use negotiation similar to _capture_open_format
if (
fmt.format.width != run->width ||
fmt.format.height != run->height ||
fmt.format.code != media_format
) {
_LOG_ERROR("Format not accepted by pad %u: attempted %ux%u format %#x, got %ux%u format %#x",
pad->pad,
run->width, run->height, media_format,
fmt.format.width, fmt.format.height, fmt.format.code);
return -1;
}
return 0;
}
static int _capture_open_format(us_capture_s *cap, bool first) { static int _capture_open_format(us_capture_s *cap, bool first) {
us_capture_runtime_s *const run = cap->run; us_capture_runtime_s *const run = cap->run;
@@ -1094,111 +1342,6 @@ static int _capture_apply_resolution(us_capture_s *cap, uint width, uint height,
return 0; return 0;
} }
static void _capture_apply_controls(const us_capture_s *cap) {
# define SET_CID_VALUE(x_cid, x_field, x_value, x_quiet) { \
struct v4l2_queryctrl m_query; \
if (_capture_query_control(cap, &m_query, #x_field, x_cid, x_quiet) == 0) { \
_capture_set_control(cap, &m_query, #x_field, x_cid, x_value, x_quiet); \
} \
}
# define SET_CID_DEFAULT(x_cid, x_field, x_quiet) { \
struct v4l2_queryctrl m_query; \
if (_capture_query_control(cap, &m_query, #x_field, x_cid, x_quiet) == 0) { \
_capture_set_control(cap, &m_query, #x_field, x_cid, m_query.default_value, x_quiet); \
} \
}
# define CONTROL_MANUAL_CID(x_cid, x_field) { \
if (cap->ctl.x_field.mode == CTL_MODE_VALUE) { \
SET_CID_VALUE(x_cid, x_field, cap->ctl.x_field.value, false); \
} else if (cap->ctl.x_field.mode == CTL_MODE_DEFAULT) { \
SET_CID_DEFAULT(x_cid, x_field, false); \
} \
}
# define CONTROL_AUTO_CID(x_cid_auto, x_cid_manual, x_field) { \
if (cap->ctl.x_field.mode == CTL_MODE_VALUE) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 0, true); \
SET_CID_VALUE(x_cid_manual, x_field, cap->ctl.x_field.value, false); \
} else if (cap->ctl.x_field.mode == CTL_MODE_AUTO) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 1, false); \
} else if (cap->ctl.x_field.mode == CTL_MODE_DEFAULT) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 0, true); /* Reset inactive flag */ \
SET_CID_DEFAULT(x_cid_manual, x_field, false); \
SET_CID_DEFAULT(x_cid_auto, x_field##_auto, false); \
} \
}
CONTROL_AUTO_CID (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness);
CONTROL_MANUAL_CID ( V4L2_CID_CONTRAST, contrast);
CONTROL_MANUAL_CID ( V4L2_CID_SATURATION, saturation);
CONTROL_AUTO_CID (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue);
CONTROL_MANUAL_CID ( V4L2_CID_GAMMA, gamma);
CONTROL_MANUAL_CID ( V4L2_CID_SHARPNESS, sharpness);
CONTROL_MANUAL_CID ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
CONTROL_AUTO_CID (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
CONTROL_AUTO_CID (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
CONTROL_MANUAL_CID ( V4L2_CID_COLORFX, color_effect);
CONTROL_MANUAL_CID ( V4L2_CID_ROTATE, rotate);
CONTROL_MANUAL_CID ( V4L2_CID_VFLIP, flip_vertical);
CONTROL_MANUAL_CID ( V4L2_CID_HFLIP, flip_horizontal);
# undef CONTROL_AUTO_CID
# undef CONTROL_MANUAL_CID
# undef SET_CID_DEFAULT
# undef SET_CID_VALUE
}
static int _capture_query_control(
const us_capture_s *cap,
struct v4l2_queryctrl *query,
const char *name,
uint cid,
bool quiet
) {
// cppcheck-suppress redundantPointerOp
US_MEMSET_ZERO(*query);
query->id = cid;
if (us_xioctl(cap->run->fd, VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
if (!quiet) {
_LOG_ERROR("Changing control %s is unsupported", name);
}
return -1;
}
return 0;
}
static void _capture_set_control(
const us_capture_s *cap,
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 (!quiet) {
_LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
value, name, query->minimum, query->maximum, query->default_value, query->step);
}
return;
}
struct v4l2_control ctl = {
.id = cid,
.value = value,
};
if (us_xioctl(cap->run->fd, VIDIOC_S_CTRL, &ctl) < 0) {
if (!quiet) {
_LOG_PERROR("Can't set control %s", name);
}
} else if (!quiet) {
_LOG_INFO("Applying control %s: %d", name, ctl.value);
}
}
static const char *_format_to_string_nullable(uint format) { static const char *_format_to_string_nullable(uint format) {
US_ARRAY_ITERATE(_FORMATS, 0, item, { US_ARRAY_ITERATE(_FORMATS, 0, item, {
if (item->format == format) { if (item->format == format) {
@@ -1230,3 +1373,12 @@ static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
}); });
return "unsupported"; return "unsupported";
} }
static uint _format_to_media(uint format) {
US_ARRAY_ITERATE(_FORMATS, 0, item, {
if (item->format == format) {
return item->media_format;
}
});
return 0;
}

View File

@@ -28,6 +28,8 @@
#include "types.h" #include "types.h"
#include "frame.h" #include "frame.h"
#include "media.h"
#include "controls.h"
#define US_VIDEO_MIN_WIDTH ((uint)160) #define US_VIDEO_MIN_WIDTH ((uint)160)
@@ -53,6 +55,8 @@ typedef struct {
typedef struct { typedef struct {
int fd; int fd;
int dv_timings_fd;
us_media_pad_s media_pads[3]; // for VIDIOC_SUBDEV_S_FMT
uint width; uint width;
uint height; uint height;
uint format; uint format;
@@ -69,40 +73,14 @@ typedef struct {
int open_error_once; int open_error_once;
} us_capture_runtime_s; } us_capture_runtime_s;
typedef enum {
CTL_MODE_NONE = 0,
CTL_MODE_VALUE,
CTL_MODE_AUTO,
CTL_MODE_DEFAULT,
} us_control_mode_e;
typedef struct {
us_control_mode_e mode;
int value;
} us_control_s;
typedef struct {
us_control_s brightness;
us_control_s contrast;
us_control_s saturation;
us_control_s hue;
us_control_s gamma;
us_control_s sharpness;
us_control_s backlight_compensation;
us_control_s white_balance;
us_control_s gain;
us_control_s color_effect;
us_control_s rotate;
us_control_s flip_vertical;
us_control_s flip_horizontal;
} us_controls_s;
typedef struct { typedef struct {
char *path; char *path;
uint input; uint input;
uint width; uint width;
uint height; uint height;
uint format; uint format;
char *media_path;
char *media_entity_name;
bool format_swap_rgb; bool format_swap_rgb;
uint jpeg_quality; uint jpeg_quality;
@@ -116,7 +94,7 @@ typedef struct {
bool allow_truncated_frames; bool allow_truncated_frames;
bool persistent; bool persistent;
uint timeout; uint timeout;
us_controls_s ctl; us_controls_s *ctl;
us_capture_runtime_s *run; us_capture_runtime_s *run;
} us_capture_s; } us_capture_s;

View File

@@ -20,61 +20,66 @@
*****************************************************************************/ *****************************************************************************/
#include "memsinkfd.h" #include "chip.h"
#include <unistd.h> #include <unistd.h>
#include <fcntl.h>
#include <linux/videodev2.h> #include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "uslibs/types.h" #include "types.h"
#include "uslibs/errors.h" #include "errors.h"
#include "uslibs/tools.h" #include "tools.h"
#include "uslibs/frame.h" #include "xioctl.h"
#include "uslibs/memsinksh.h"
#include "logging.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s *mem, u64 last_id) { #ifndef V4L2_CID_USER_TC358743_BASE
const ldf deadline_ts = us_get_now_monotonic() + 1; // wait_timeout # define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
ldf now_ts; #endif
do {
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout #ifndef TC358743_CID_AUDIO_SAMPLING_RATE
now_ts = us_get_now_monotonic(); # define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
if (result < 0 && errno != EWOULDBLOCK) { #endif
US_JLOG_PERROR("video", "Can't lock memsink");
return -1; #ifndef TC358743_CID_AUDIO_PRESENT
} else if (result == 0) { # define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
if (mem->magic == US_MEMSINK_MAGIC && mem->version == US_MEMSINK_VERSION && mem->id != last_id) { #endif
return 0;
} #ifndef TC358743_CID_LANES_ENOUGH
if (flock(fd, LOCK_UN) < 0) { # define TC358743_CID_LANES_ENOUGH (V4L2_CID_USER_TC358743_BASE + 2)
US_JLOG_PERROR("video", "Can't unlock memsink"); #endif
return -1;
}
} int us_chip_check_cable(int fd) {
usleep(1000); // lock_polling struct v4l2_control ctl = {.id = V4L2_CID_DV_RX_POWER_PRESENT};
} while (now_ts < deadline_ts); if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
return US_ERROR_NO_DATA; return US_ERROR_COMMON;
}
return (ctl.value ? 0 : US_ERROR_NO_CABLE);
} }
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required) { int us_chip_tc358743_check_lanes(int fd) {
us_frame_set_data(frame, us_memsink_get_data(mem), mem->used); struct v4l2_control ctl = {.id = TC358743_CID_LANES_ENOUGH};
US_FRAME_COPY_META(mem, frame); if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
*frame_id = mem->id; return US_ERROR_COMMON;
mem->last_client_ts = us_get_now_monotonic(); }
if (key_required) { return (ctl.value ? 0 : US_ERROR_NO_LANES);
mem->key_requested = true; }
int us_chip_tc358743_get_audio_hz(int fd) {
struct v4l2_control ctl = {.id = TC358743_CID_AUDIO_PRESENT};
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
return US_ERROR_COMMON;
}
if (!ctl.value) {
return US_ERROR_NO_SIGNAL; // No audio
} }
bool retval = 0; US_MEMSET_ZERO(ctl);
if (frame->format != V4L2_PIX_FMT_H264) { ctl.id = TC358743_CID_AUDIO_SAMPLING_RATE;
US_JLOG_ERROR("video", "Got non-H264 frame from memsink"); if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
retval = -1; return US_ERROR_COMMON;
} }
if (flock(fd, LOCK_UN) < 0) { return (ctl.value ? ctl.value : US_ERROR_NO_SIGNAL);
US_JLOG_PERROR("video", "Can't unlock memsink");
retval = -1;
}
return retval;
} }

View File

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

View File

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

173
src/libs/controls.c Normal file
View File

@@ -0,0 +1,173 @@
/*****************************************************************************
# #
# 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 "controls.h"
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "types.h"
#include "tools.h"
#include "logging.h"
#include "xioctl.h"
static int _query_control(
int fd,
struct v4l2_queryctrl *query,
const char *name,
uint cid,
bool quiet);
static void _set_control(
int fd,
const struct v4l2_queryctrl *query,
const char *name,
uint cid,
int value,
bool quiet);
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("CAP: " x_msg, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("CAP: " x_msg, ##__VA_ARGS__)
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("CAP: " x_msg, ##__VA_ARGS__)
us_controls_s *us_controls_init(void) {
us_controls_s *ctl;
US_CALLOC(ctl, 1);
return ctl;
}
void us_controls_destroy(us_controls_s *ctl) {
free(ctl);
}
void us_controls_apply(const us_controls_s *ctl, int fd) {
# define SET_CID_VALUE(x_cid, x_field, x_value, x_quiet) { \
struct v4l2_queryctrl m_query; \
if (_query_control(fd, &m_query, #x_field, x_cid, x_quiet) == 0) { \
_set_control(fd, &m_query, #x_field, x_cid, x_value, x_quiet); \
} \
}
# define SET_CID_DEFAULT(x_cid, x_field, x_quiet) { \
struct v4l2_queryctrl m_query; \
if (_query_control(fd, &m_query, #x_field, x_cid, x_quiet) == 0) { \
_set_control(fd, &m_query, #x_field, x_cid, m_query.default_value, x_quiet); \
} \
}
# define CONTROL_MANUAL_CID(x_cid, x_field) { \
if (ctl->x_field.mode == US_CTL_MODE_VALUE) { \
SET_CID_VALUE(x_cid, x_field, ctl->x_field.value, false); \
} else if (ctl->x_field.mode == US_CTL_MODE_DEFAULT) { \
SET_CID_DEFAULT(x_cid, x_field, false); \
} \
}
# define CONTROL_AUTO_CID(x_cid_auto, x_cid_manual, x_field) { \
if (ctl->x_field.mode == US_CTL_MODE_VALUE) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 0, true); \
SET_CID_VALUE(x_cid_manual, x_field, ctl->x_field.value, false); \
} else if (ctl->x_field.mode == US_CTL_MODE_AUTO) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 1, false); \
} else if (ctl->x_field.mode == US_CTL_MODE_DEFAULT) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 0, true); /* Reset inactive flag */ \
SET_CID_DEFAULT(x_cid_manual, x_field, false); \
SET_CID_DEFAULT(x_cid_auto, x_field##_auto, false); \
} \
}
CONTROL_AUTO_CID (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness);
CONTROL_MANUAL_CID ( V4L2_CID_CONTRAST, contrast);
CONTROL_MANUAL_CID ( V4L2_CID_SATURATION, saturation);
CONTROL_AUTO_CID (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue);
CONTROL_MANUAL_CID ( V4L2_CID_GAMMA, gamma);
CONTROL_MANUAL_CID ( V4L2_CID_SHARPNESS, sharpness);
CONTROL_MANUAL_CID ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
CONTROL_AUTO_CID (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
CONTROL_AUTO_CID (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
CONTROL_MANUAL_CID ( V4L2_CID_COLORFX, color_effect);
CONTROL_MANUAL_CID ( V4L2_CID_ROTATE, rotate);
CONTROL_MANUAL_CID ( V4L2_CID_VFLIP, flip_vertical);
CONTROL_MANUAL_CID ( V4L2_CID_HFLIP, flip_horizontal);
# undef CONTROL_AUTO_CID
# undef CONTROL_MANUAL_CID
# undef SET_CID_DEFAULT
# undef SET_CID_VALUE
}
static int _query_control(
int fd,
struct v4l2_queryctrl *query,
const char *name,
uint cid,
bool quiet
) {
// cppcheck-suppress redundantPointerOp
US_MEMSET_ZERO(*query);
query->id = cid;
if (us_xioctl(fd, VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
if (!quiet) {
_LOG_ERROR("Changing control %s is unsupported", name);
}
return -1;
}
return 0;
}
static void _set_control(
int fd,
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 (!quiet) {
_LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
value, name, query->minimum, query->maximum, query->default_value, query->step);
}
return;
}
struct v4l2_control ctl = {
.id = cid,
.value = value,
};
if (us_xioctl(fd, VIDIOC_S_CTRL, &ctl) < 0) {
if (!quiet) {
_LOG_PERROR("Can't set control %s", name);
}
} else if (!quiet) {
_LOG_INFO("Applying control %s: %d", name, ctl.value);
}
}

60
src/libs/controls.h Normal file
View File

@@ -0,0 +1,60 @@
/*****************************************************************************
# #
# 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 "types.h"
typedef enum {
US_CTL_MODE_NONE = 0,
US_CTL_MODE_VALUE,
US_CTL_MODE_AUTO,
US_CTL_MODE_DEFAULT,
} us_control_mode_e;
typedef struct {
us_control_mode_e mode;
int value;
} us_control_s;
typedef struct {
us_control_s brightness;
us_control_s contrast;
us_control_s saturation;
us_control_s hue;
us_control_s gamma;
us_control_s sharpness;
us_control_s backlight_compensation;
us_control_s white_balance;
us_control_s gain;
us_control_s color_effect;
us_control_s rotate;
us_control_s flip_vertical;
us_control_s flip_horizontal;
} us_controls_s;
us_controls_s *us_controls_init(void);
void us_controls_destroy(us_controls_s *ctl);
void us_controls_apply(const us_controls_s *ctl, int fd);

View File

@@ -56,7 +56,7 @@ static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz);
static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint height, float hz); static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint height, float hz);
static u32 _find_dpms(int fd, drmModeConnector *conn); static u32 _find_dpms(int fd, drmModeConnector *conn);
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs); static u32 _find_crtc(int fd, const drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs);
static const char *_connector_type_to_string(u32 type); 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);
@@ -726,7 +726,7 @@ static u32 _find_dpms(int fd, drmModeConnector *conn) {
return 0; return 0;
} }
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs) { static u32 _find_crtc(int fd, const drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs) {
for (int ei = 0; ei < conn->count_encoders; ++ei) { for (int ei = 0; ei < conn->count_encoders; ++ei) {
drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[ei]); drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[ei]);
if (enc == NULL) { if (enc == NULL) {

View File

@@ -112,7 +112,7 @@ void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint he
} }
char *str = us_strdup(text); char *str = us_strdup(text);
char *line; const char *line;
char *rest; char *rest;
uint block_width = 0; uint block_width = 0;

View File

@@ -34,6 +34,7 @@
#include "types.h" #include "types.h"
#include "tools.h" #include "tools.h"
#include "threading.h" #include "threading.h"
#include "logging_base.h"
enum us_log_level_t { enum us_log_level_t {
@@ -72,37 +73,7 @@ extern pthread_mutex_t us_g_log_mutex;
#define US_COLOR_RESET "\x1b[0m" #define US_COLOR_RESET "\x1b[0m"
#define US_SEP_INFO(x_ch) { \ // XXX: See the definition of US_LOG_PRINTF_NOLOCK() in logging_base.h
US_LOGGING_LOCK; \
for (int m_i = 0; m_i < 80; ++m_i) { \
fputc((x_ch), stderr); \
} \
fputc('\n', stderr); \
fflush(stderr); \
US_LOGGING_UNLOCK; \
}
#define US_SEP_DEBUG(x_ch) { \
if (us_g_log_level >= US_LOG_LEVEL_DEBUG) { \
US_SEP_INFO(x_ch); \
} \
}
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
us_thread_get_name(m_tname_buf); \
if (us_g_log_colored) { \
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
" [%.03Lf %9s]" " -- " US_COLOR_RESET x_msg_color x_msg US_COLOR_RESET, \
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
} else { \
fprintf(stderr, "-- " x_label " [%.03Lf %9s] -- " x_msg, \
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
} \
fputc('\n', stderr); \
fflush(stderr); \
}
#define US_LOG_PRINTF(x_label_color, x_label, x_msg_color, x_msg, ...) { \ #define US_LOG_PRINTF(x_label_color, x_label, x_msg_color, x_msg, ...) { \
US_LOGGING_LOCK; \ US_LOGGING_LOCK; \
@@ -110,6 +81,10 @@ extern pthread_mutex_t us_g_log_mutex;
US_LOGGING_UNLOCK; \ US_LOGGING_UNLOCK; \
} }
#define US_LOG_SEP_INFO { \
US_LOG_PRINTF(US_COLOR_GREEN, "INFO ", US_COLOR_GREEN, "============================================="); \
}
#define US_LOG_ERROR(x_msg, ...) { \ #define US_LOG_ERROR(x_msg, ...) { \
US_LOG_PRINTF(US_COLOR_RED, "ERROR", US_COLOR_RED, x_msg, ##__VA_ARGS__); \ US_LOG_PRINTF(US_COLOR_RED, "ERROR", US_COLOR_RED, x_msg, ##__VA_ARGS__); \
} }
@@ -120,6 +95,11 @@ extern pthread_mutex_t us_g_log_mutex;
free(m_perror_str); \ free(m_perror_str); \
} }
// We don't include alsa, speex and opus headers here
#define US_LOG_PERROR_ALSA(x_err, x_msg, ...) US_LOG_ERROR(x_msg ": %s", ##__VA_ARGS__, snd_strerror(x_err))
#define US_LOG_PERROR_RES(x_err, x_msg, ...) US_LOG_ERROR(x_msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(x_err))
#define US_LOG_PERROR_OPUS(x_err, x_msg, ...) US_LOG_ERROR(x_msg ": %s", ##__VA_ARGS__, opus_strerror(x_err))
#define US_LOG_INFO(x_msg, ...) { \ #define US_LOG_INFO(x_msg, ...) { \
US_LOG_PRINTF(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \ US_LOG_PRINTF(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
} }

View File

@@ -20,24 +20,29 @@
*****************************************************************************/ *****************************************************************************/
#include <janus/plugins/plugin.h> #pragma once
#include "uslibs/tools.h" #include <stdio.h>
#include <stdlib.h>
#include "const.h" #include <pthread.h>
#include "types.h"
#include "tools.h"
#include "threading.h"
#define US_JLOG_INFO(x_prefix, x_msg, ...) JANUS_LOG(LOG_INFO, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__) #define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
#define US_JLOG_WARN(x_prefix, x_msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__) char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
#define US_JLOG_ERROR(x_prefix, x_msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__) us_thread_get_name(m_tname_buf); \
if (us_g_log_colored) { \
#define US_JLOG_PERROR(x_prefix, x_msg, ...) { \ fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
char *const m_perror_str = us_errno_to_string(errno); \ " [%.03Lf %9s]" " -- " US_COLOR_RESET x_msg_color x_msg US_COLOR_RESET, \
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_str); \ us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
free(m_perror_str); \ } else { \
fprintf(stderr, "-- " x_label " [%.03Lf %9s] -- " x_msg, \
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
} \
fputc('\n', stderr); \
fflush(stderr); \
} }
// We don't include alsa, speex and opus headers here
#define US_JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
#define US_JLOG_PERROR_RES(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
#define US_JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err))

215
src/libs/media.c Normal file
View File

@@ -0,0 +1,215 @@
/*****************************************************************************
# #
# 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 "media.h"
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/sysmacros.h>
#include <linux/videodev2.h>
#include <linux/media.h>
#include <linux/v4l2-subdev.h>
#include "types.h"
#include "errors.h"
#include "tools.h"
#include "logging.h"
#include "xioctl.h"
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("CAP: " x_msg, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("CAP: " x_msg, ##__VA_ARGS__)
int us_media_find_link(
struct media_v2_topology *topology,
u32 link_type,
int source_id,
int sink_id,
u32 *link_flags
) {
struct media_v2_link *links = (struct media_v2_link *)topology->ptr_links;
for (uint i = 0; i < topology->num_links; ++i) {
if ((links[i].flags & MEDIA_LNK_FL_LINK_TYPE) != link_type) {
continue;
}
if (source_id >= 0 && (u32)source_id == links[i].source_id) {
if (link_flags) {
*link_flags = links[i].flags;
}
return links[i].sink_id;
}
if (sink_id >= 0 && (u32)sink_id == links[i].sink_id) {
if (link_flags) {
*link_flags = links[i].flags;
}
return links[i].source_id;
}
}
return -1;
}
int us_media_find_entity_by_name(
const struct media_v2_topology *topology,
const char *name
) {
const struct media_v2_entity *entities = (struct media_v2_entity *)topology->ptr_entities;
for (uint i = 0; i < topology->num_entities; ++i) {
if (!strcmp(entities[i].name, name)) {
return entities[i].id;
}
}
return -1;
}
int us_media_find_entity_by_devnode(
struct media_v2_topology *topology,
const dev_t devnode
) {
const struct media_v2_interface *interfaces = (struct media_v2_interface *)topology->ptr_interfaces;
for (uint i = 0; i < topology->num_interfaces; ++i) {
if (
interfaces[i].intf_type == MEDIA_INTF_T_V4L_VIDEO
&& interfaces[i].devnode.major == major(devnode)
&& interfaces[i].devnode.minor == minor(devnode)
) {
return us_media_find_link(
topology, MEDIA_LNK_FL_INTERFACE_LINK,
interfaces[i].id, -1, NULL);
}
}
return -1;
}
const struct media_v2_pad *us_media_find_pad_by_entity(
const struct media_v2_topology *topology,
u32 pad_type,
u32 entity_id
) {
const struct media_v2_pad *pads = (const struct media_v2_pad *)topology->ptr_pads;
for (uint i = 0; i < topology->num_pads; ++i) {
if ((pads[i].flags & pad_type) && pads[i].entity_id == entity_id) {
return &pads[i];
}
}
return NULL;
}
const struct media_v2_pad *us_media_find_pad(
const struct media_v2_topology *topology,
u32 pad_type,
u32 pad_id
) {
const struct media_v2_pad *pads = (const struct media_v2_pad *)topology->ptr_pads;
for (uint i = 0; i < topology->num_pads; ++i) {
if ((pads[i].flags & pad_type) && pads[i].id == pad_id) {
return &pads[i];
}
}
return NULL;
}
int us_media_xioctl_setup_link(
int fd,
const struct media_v2_pad *source_pad,
const struct media_v2_pad *sink_pad,
u32 link_flags
) {
struct media_link_desc link_desc = {
.source = {
.entity = source_pad->entity_id,
.index = source_pad->index,
},
.sink = {
.entity = sink_pad->entity_id,
.index = sink_pad->index,
},
.flags = link_flags,
};
return us_xioctl(fd, MEDIA_IOC_SETUP_LINK, &link_desc);
}
struct media_v2_topology *us_media_topology_init(int fd) {
struct media_v2_topology *topology = NULL;
struct media_device_info dev_info = {0};
if (us_xioctl(fd, MEDIA_IOC_DEVICE_INFO, &dev_info) < 0) {
_LOG_PERROR("Can't to query media device info");
goto error;
}
if (!MEDIA_V2_PAD_HAS_INDEX(dev_info.media_version)) {
_LOG_ERROR("Media topology doesn't have pad indices, too old kernel?");
goto error;
}
US_CALLOC(topology, 1);
if (us_xioctl(fd, MEDIA_IOC_G_TOPOLOGY, topology) < 0) {
_LOG_PERROR("Can't query media topology info");
goto error;
}
# define INIT_FIELD(x_field, x_type) { \
void *m_ptr = calloc(topology->num_##x_field, sizeof(x_type)); \
US_A(m_ptr); \
topology->ptr_##x_field = (uintptr_t)m_ptr; \
}
INIT_FIELD(entities, struct media_v2_entity);
INIT_FIELD(links, struct media_v2_link);
INIT_FIELD(interfaces, struct media_v2_interface);
INIT_FIELD(pads, struct media_v2_pad);
# undef INIT_FIELD
if (us_xioctl(fd, MEDIA_IOC_G_TOPOLOGY, topology) < 0) {
_LOG_PERROR("Failed to retrieve topology info");
goto error;
}
return topology;
error:
US_DELETE(topology, us_media_topology_destroy);
return NULL;
}
void us_media_topology_destroy(struct media_v2_topology *topology) {
# define DELETE_FIELD(x_field) { \
if (topology->ptr_##x_field) { \
free((void *)topology->ptr_##x_field); \
} \
}
DELETE_FIELD(pads);
DELETE_FIELD(interfaces);
DELETE_FIELD(links);
DELETE_FIELD(entities);
# undef DELETE_FIELD
free(topology);
}

70
src/libs/media.h Normal file
View File

@@ -0,0 +1,70 @@
/*****************************************************************************
# #
# 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 <linux/videodev2.h>
#include <linux/media.h>
#include <linux/v4l2-subdev.h>
#include "types.h"
typedef struct {
int fd;
uint pad;
} us_media_pad_s;
int us_media_find_link(
struct media_v2_topology *topology,
u32 link_type,
int source_id,
int sink_id,
u32 *link_flags);
int us_media_find_entity_by_name(
const struct media_v2_topology *topology,
const char *name);
int us_media_find_entity_by_devnode(
struct media_v2_topology *topology,
const dev_t devnode);
const struct media_v2_pad *us_media_find_pad_by_entity(
const struct media_v2_topology *topology,
u32 pad_type,
u32 entity_id);
const struct media_v2_pad *us_media_find_pad(
const struct media_v2_topology *topology,
u32 pad_type,
u32 pad_id);
int us_media_xioctl_setup_link(
int fd,
const struct media_v2_pad *source_pad,
const struct media_v2_pad *sink_pad,
u32 link_flags);
struct media_v2_topology *us_media_topology_init(int fd);
void us_media_topology_destroy(struct media_v2_topology *topology);

View File

@@ -41,8 +41,8 @@
us_memsink_s *us_memsink_init_opened( us_memsink_s *us_memsink_init_opened(
const char *name, const char *obj, bool server, const char *name, const char *obj, bool server,
mode_t mode, bool rm, uint client_ttl, uint timeout) { mode_t mode, bool rm, uint client_ttl, uint timeout
) {
us_memsink_s *sink; us_memsink_s *sink;
US_CALLOC(sink, 1); US_CALLOC(sink, 1);
sink->name = name; sink->name = name;
@@ -160,7 +160,11 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
return false; return false;
} }
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,
us_memsink_wants_s *wants
) {
US_A(sink->server); US_A(sink->server);
const ldf now = us_get_now_monotonic(); const ldf now = us_get_now_monotonic();
@@ -175,11 +179,11 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key
US_LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name); US_LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
sink->mem->id = us_get_now_id(); sink->mem->id = us_get_now_id();
if (sink->mem->key_requested && frame->key) { if (sink->mem->wants.key && frame->key) {
sink->mem->key_requested = false; sink->mem->wants.key = false;
} }
if (key_requested != NULL) { // We don't need it for non-H264 sinks if (wants != NULL) {
*key_requested = sink->mem->key_requested; memcpy(wants, &sink->mem->wants, sizeof(us_memsink_wants_s));
} }
memcpy(us_memsink_get_data(sink->mem), frame->data, frame->used); memcpy(us_memsink_get_data(sink->mem), frame->data, frame->used);
@@ -189,7 +193,8 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key
sink->mem->magic = US_MEMSINK_MAGIC; sink->mem->magic = US_MEMSINK_MAGIC;
sink->mem->version = US_MEMSINK_VERSION; sink->mem->version = US_MEMSINK_VERSION;
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic())); const bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic());
atomic_store(&sink->has_clients, has_clients);
if (flock(sink->fd, LOCK_UN) < 0) { if (flock(sink->fd, LOCK_UN) < 0) {
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name); US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
@@ -208,7 +213,12 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key
return 0; return 0;
} }
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,
us_memsink_wants_s *get,
const us_memsink_wants_s *put
) {
US_A(!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) {
@@ -243,11 +253,16 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_reque
sink->last_readed_id = sink->mem->id; sink->last_readed_id = sink->mem->id;
us_frame_set_data(frame, us_memsink_get_data(sink->mem), sink->mem->used); us_frame_set_data(frame, us_memsink_get_data(sink->mem), sink->mem->used);
US_FRAME_COPY_META(sink->mem, frame); US_FRAME_COPY_META(sink->mem, frame);
if (key_requested != NULL) { // We don't need it for non-H264 sinks
*key_requested = sink->mem->key_requested; if (get != NULL) {
memcpy(get, &sink->mem->wants, sizeof(us_memsink_wants_s));
} }
if (key_required) { if (put != NULL) {
sink->mem->key_requested = true; const bool key = sink->mem->wants.key;
memcpy(&sink->mem->wants, put, sizeof(us_memsink_wants_s));
if (key) {
sink->mem->wants.key = key;
}
} }
done: done:

View File

@@ -57,6 +57,14 @@ us_memsink_s *us_memsink_init_opened(
void us_memsink_destroy(us_memsink_s *sink); void us_memsink_destroy(us_memsink_s *sink);
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame); 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_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required); int us_memsink_server_put(
us_memsink_s *sink,
const us_frame_s *frame,
us_memsink_wants_s *wants);
int us_memsink_client_get(
us_memsink_s *sink,
us_frame_s *frame,
us_memsink_wants_s *get,
const us_memsink_wants_s *put);

View File

@@ -61,7 +61,7 @@ uz us_memsink_calculate_size(const char *obj) {
} else if (!strcasecmp(ptr, "h264")) { } else if (!strcasecmp(ptr, "h264")) {
return 2 * 1024 * 1024; return 2 * 1024 * 1024;
} else if (!strcasecmp(ptr, "raw")) { } else if (!strcasecmp(ptr, "raw")) {
return 1920 * 1200 * 3; // RGB return 3840 * 2160 * 3; // RGB
} }
} }
return 0; return 0;

View File

@@ -27,9 +27,17 @@
#define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE) #define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE)
#define US_MEMSINK_VERSION ((u32)7) #define US_MEMSINK_VERSION ((u32)10)
typedef struct {
uint width;
uint height;
uint format;
uint fps;
bool key;
} us_memsink_wants_s;
typedef struct { typedef struct {
u64 magic; u64 magic;
u32 version; u32 version;
@@ -37,7 +45,7 @@ typedef struct {
uz used; uz used;
ldf last_client_ts; ldf last_client_ts;
bool key_requested; us_memsink_wants_s wants;
US_FRAME_META_DECLARE; US_FRAME_META_DECLARE;
} us_memsink_shared_s; } us_memsink_shared_s;

View File

@@ -94,13 +94,13 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
#ifdef WITH_PTHREAD_NP #ifdef WITH_PTHREAD_NP
int retval = -1; int retval = -1;
# if defined(__linux__) || defined (__NetBSD__) # if defined(__linux__) || defined (__NetBSD__)
retval = pthread_getname_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1); retval = pthread_getname_np(pthread_self(), name, US_THREAD_NAME_SIZE); // Always null-terminated
# elif \ # elif \
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \ (defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \ || (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|| defined(__DragonFly__) || defined(__DragonFly__)
pthread_get_name_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1); pthread_get_name_np(pthread_self(), name, US_THREAD_NAME_SIZE); // Also null-terminated
if (name[0] != '\0') { if (us_str_is_ok(name)) {
retval = 0; retval = 0;
} }
# else # else

View File

@@ -92,6 +92,10 @@ INLINE char *us_strdup(const char *str) {
return new; return new;
} }
INLINE bool us_str_is_ok(const char *str) {
return (str != NULL && str[0] != '\0');
}
INLINE const char *us_bool_to_string(bool flag) { INLINE const char *us_bool_to_string(bool flag) {
return (flag ? "true" : "false"); return (flag ? "true" : "false");
} }

View File

@@ -216,17 +216,18 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
US_CALLOC(line_buf, frame->width * 3); US_CALLOC(line_buf, frame->width * 3);
const uint padding = us_frame_get_padding(frame); const uint padding = us_frame_get_padding(frame);
const uint image_size = frame->width * frame->height; const uint luma_array_size = (frame->width + padding) * frame->height;
const uint chroma_array_size = (frame->used - image_size) / 2; const uint chroma_array_size = (frame->used - luma_array_size) >> 1;
const uint chroma_matrix_order = (image_size / chroma_array_size) == 16 ? 4 : 2;
const u8 *data = frame->data; const u8 *data = frame->data;
const u8 *chroma1_data = frame->data + image_size; const u8 *chroma1_data = frame->data + luma_array_size;
const u8 *chroma2_data = frame->data + image_size + chroma_array_size; const u8 *chroma2_data = frame->data + luma_array_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) {
if (jpeg->next_scanline > 0 && jpeg->next_scanline % 2 == 0) {
chroma1_data += (frame->width + padding) >> 1;
chroma2_data += (frame->width + padding) >> 1;
}
u8 *ptr = line_buf; u8 *ptr = line_buf;
for (uint x = 0; x < frame->width; ++x) { for (uint x = 0; x < frame->width; ++x) {
@@ -234,7 +235,7 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
u8 y = data[x]; u8 y = data[x];
u8 u; u8 u;
u8 v; u8 v;
uint chroma_position = x / chroma_matrix_order; uint chroma_position = x >> 1;
switch (frame->format) { switch (frame->format) {
case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_YUV420:
@@ -257,11 +258,6 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
data += frame->width + padding; 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}; JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1); jpeg_write_scanlines(jpeg, scanlines, 1);
} }

View File

@@ -183,7 +183,7 @@ int us_server_listen(us_server_s *server) {
us_stream_s *const stream = server->stream; us_stream_s *const stream = server->stream;
{ {
if (server->static_path[0] != '\0') { if (us_str_is_ok(server->static_path)) {
_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 {
@@ -202,7 +202,7 @@ int us_server_listen(us_server_s *server) {
evhttp_set_timeout(run->http, server->timeout); evhttp_set_timeout(run->http, server->timeout);
if (server->user[0] != '\0') { if (us_str_is_ok(server->user)) {
char *encoded_token = NULL; char *encoded_token = NULL;
char *raw_token; char *raw_token;
@@ -216,7 +216,7 @@ int us_server_listen(us_server_s *server) {
_LOG_INFO("Using HTTP basic auth"); _LOG_INFO("Using HTTP basic auth");
} }
if (server->unix_path[0] != '\0') { if (us_str_is_ok(server->unix_path)) {
_LOG_DEBUG("Binding server to UNIX socket '%s' ...", server->unix_path); _LOG_DEBUG("Binding server to UNIX socket '%s' ...", server->unix_path);
if ((run->ext_fd = us_evhttp_bind_unix( if ((run->ext_fd = us_evhttp_bind_unix(
run->http, run->http,
@@ -264,7 +264,7 @@ static int _http_preprocess_request(struct evhttp_request *req, us_server_s *ser
atomic_store(&server->stream->run->http->last_req_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 (us_str_is_ok(server->allow_origin)) {
const char *const cors_headers = us_evhttp_get_header(req, "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(req, "Access-Control-Request-Method"); const char *const cors_method = us_evhttp_get_header(req, "Access-Control-Request-Method");
@@ -666,7 +666,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
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 (us_str_is_ok(client->server->allow_origin)) {
const char *const cors_headers = us_evhttp_get_header(client->req, "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->req, "Access-Control-Request-Method"); const char *const cors_method = us_evhttp_get_header(client->req, "Access-Control-Request-Method");
@@ -693,8 +693,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
RN RN
"--" BOUNDARY RN, "--" BOUNDARY RN,
(server->instance_id[0] == '\0' ? "" : "_"), (us_str_is_ok(server->instance_id) ? "_" : ""),
server->instance_id, (us_str_is_ok(server->instance_id) ? server->instance_id : ""),
(client->key != NULL ? client->key : "0"), (client->key != NULL ? client->key : "0"),
client->id); client->id);

View File

@@ -39,7 +39,7 @@ 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(req_path); char *const simplified_path = us_simplify_request_path(req_path);
if (simplified_path[0] == '\0') { if (!us_str_is_ok(simplified_path)) {
US_LOG_VERBOSE("HTTP: Invalid request path %s to static", req_path); US_LOG_VERBOSE("HTTP: Invalid request path %s to static", req_path);
goto error; goto error;
} }

View File

@@ -93,6 +93,8 @@ enum _US_OPT_VALUES {
_O_DEVICE_ERROR_DELAY, _O_DEVICE_ERROR_DELAY,
_O_FORMAT_SWAP_RGB, _O_FORMAT_SWAP_RGB,
_O_M2M_DEVICE, _O_M2M_DEVICE,
_O_MEDIA_DEVICE,
_O_MEDIA_ENTITY_NAME,
_O_IMAGE_DEFAULT, _O_IMAGE_DEFAULT,
_O_BRIGHTNESS, _O_BRIGHTNESS,
@@ -188,6 +190,8 @@ static const struct option _LONG_OPTS[] = {
{"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT}, {"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT},
{"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY}, {"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY},
{"m2m-device", required_argument, NULL, _O_M2M_DEVICE}, {"m2m-device", required_argument, NULL, _O_M2M_DEVICE},
{"media-device", required_argument, NULL, _O_MEDIA_DEVICE},
{"media-entity-name", required_argument, NULL, _O_MEDIA_ENTITY_NAME},
{"image-default", no_argument, NULL, _O_IMAGE_DEFAULT}, {"image-default", no_argument, NULL, _O_IMAGE_DEFAULT},
{"brightness", required_argument, NULL, _O_BRIGHTNESS}, {"brightness", required_argument, NULL, _O_BRIGHTNESS},
@@ -300,10 +304,11 @@ us_options_s *us_options_init(uint argc, char *argv[]) {
opts->argc = argc; opts->argc = argc;
opts->argv = argv; opts->argv = argv;
US_CALLOC(opts->argv_copy, argc); US_CALLOC(opts->argv_copy, argc + 1);
for (uint i = 0; i < argc; ++i) { for (uint i = 0; i < argc; ++i) {
opts->argv_copy[i] = us_strdup(argv[i]); opts->argv_copy[i] = us_strdup(argv[i]);
} }
opts->argv_copy[argc] = NULL;
return opts; return opts;
} }
@@ -374,15 +379,15 @@ int us_options_parse(
} }
# define OPT_CTL_DEFAULT_NOBREAK(x_dest) { \ # define OPT_CTL_DEFAULT_NOBREAK(x_dest) { \
cap->ctl.x_dest.mode = CTL_MODE_DEFAULT; \ cap->ctl->x_dest.mode = US_CTL_MODE_DEFAULT; \
} }
# define OPT_CTL_MANUAL(x_dest) { \ # define OPT_CTL_MANUAL(x_dest) { \
if (!strcasecmp(optarg, "default")) { \ if (!strcasecmp(optarg, "default")) { \
OPT_CTL_DEFAULT_NOBREAK(x_dest); \ OPT_CTL_DEFAULT_NOBREAK(x_dest); \
} else { \ } else { \
cap->ctl.x_dest.mode = CTL_MODE_VALUE; \ cap->ctl->x_dest.mode = US_CTL_MODE_VALUE; \
OPT_NUMBER("--"#x_dest, cap->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \ OPT_NUMBER("--"#x_dest, cap->ctl->x_dest.value, INT_MIN, INT_MAX, 0); \
} \ } \
break; \ break; \
} }
@@ -391,10 +396,10 @@ int us_options_parse(
if (!strcasecmp(optarg, "default")) { \ if (!strcasecmp(optarg, "default")) { \
OPT_CTL_DEFAULT_NOBREAK(x_dest); \ OPT_CTL_DEFAULT_NOBREAK(x_dest); \
} else if (!strcasecmp(optarg, "auto")) { \ } else if (!strcasecmp(optarg, "auto")) { \
cap->ctl.x_dest.mode = CTL_MODE_AUTO; \ cap->ctl->x_dest.mode = US_CTL_MODE_AUTO; \
} else { \ } else { \
cap->ctl.x_dest.mode = CTL_MODE_VALUE; \ cap->ctl->x_dest.mode = US_CTL_MODE_VALUE; \
OPT_NUMBER("--"#x_dest, cap->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \ OPT_NUMBER("--"#x_dest, cap->ctl->x_dest.value, INT_MIN, INT_MAX, 0); \
} \ } \
break; \ break; \
} }
@@ -445,6 +450,8 @@ int us_options_parse(
case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", cap->timeout, 1, 60, 0); case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", cap->timeout, 1, 60, 0);
case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", stream->error_delay, 1, 60, 0); case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", stream->error_delay, 1, 60, 0);
case _O_M2M_DEVICE: OPT_SET(enc->m2m_path, optarg); case _O_M2M_DEVICE: OPT_SET(enc->m2m_path, optarg);
case _O_MEDIA_DEVICE: OPT_SET(cap->media_path, optarg);
case _O_MEDIA_ENTITY_NAME: OPT_SET(cap->media_entity_name, optarg);
case _O_IMAGE_DEFAULT: case _O_IMAGE_DEFAULT:
OPT_CTL_DEFAULT_NOBREAK(brightness); OPT_CTL_DEFAULT_NOBREAK(brightness);
@@ -562,7 +569,7 @@ int us_options_parse(
US_LOG_INFO("Starting PiKVM uStreamer %s ...", US_VERSION); US_LOG_INFO("Starting PiKVM uStreamer %s ...", US_VERSION);
# define ADD_SINK(x_label, x_prefix) { \ # define ADD_SINK(x_label, x_prefix) { \
if (x_prefix##_name && x_prefix##_name[0] != '\0') { \ if (us_str_is_ok(x_prefix##_name)) { \
opts->x_prefix = us_memsink_init_opened( \ opts->x_prefix = us_memsink_init_opened( \
x_label, \ x_label, \
x_prefix##_name, \ x_prefix##_name, \
@@ -704,7 +711,7 @@ static void _help(
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");
@@ -712,7 +719,7 @@ static void _help(
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);
@@ -737,6 +744,10 @@ static void _help(
SAY(" --device-error-delay <sec> ────────── Delay before trying to connect to the device again"); SAY(" --device-error-delay <sec> ────────── Delay before trying to connect to the device again");
SAY(" after an error (timeout for example). Default: %u.\n", stream->error_delay); SAY(" after an error (timeout for example). Default: %u.\n", stream->error_delay);
SAY(" --m2m-device </dev/path> ──────────── Path to V4L2 M2M encoder device. Default: auto select.\n"); SAY(" --m2m-device </dev/path> ──────────── Path to V4L2 M2M encoder device. Default: auto select.\n");
SAY(" --media-device </dev/path> ────────── Path to V4L2 /dev/media* device for setting subdevices");
SAY(" (currently necessary for RPi5). Default: unset.\n");
SAY(" --media-entity-name <name> ────────── Name of the V4L2 entity to grab video from,");
SAY(" will be routed to --device. Default: none.\n");
SAY("Image control options:"); SAY("Image control options:");
SAY("══════════════════════"); SAY("══════════════════════");
SAY(" --image-default ────────────────────── Reset all image settings below to default. Default: no change.\n"); SAY(" --image-default ────────────────────── Reset all image settings below to default. Default: no change.\n");

View File

@@ -272,7 +272,7 @@ void us_stream_loop(us_stream_s *stream) {
us_capture_close(cap); us_capture_close(cap);
if (!atomic_load(&run->stop)) { if (!atomic_load(&run->stop)) {
US_SEP_INFO('='); US_LOG_SEP_INFO;
} }
} }
@@ -758,7 +758,9 @@ static void _stream_encode_expose_h264(us_stream_s *stream, const us_frame_s *fr
force_key = true; force_key = true;
} }
if (!us_m2m_encoder_compress(run->h264_enc, frame, run->h264_dest, force_key)) { if (!us_m2m_encoder_compress(run->h264_enc, frame, run->h264_dest, force_key)) {
meta.online = !us_memsink_server_put(stream->h264_sink, run->h264_dest, &run->h264_key_requested); us_memsink_wants_s wants = {0};
meta.online = !us_memsink_server_put(stream->h264_sink, run->h264_dest, &wants);
run->h264_key_requested = wants.key;
} }
done: done:
@@ -770,7 +772,7 @@ static void _stream_check_suicide(us_stream_s *stream) {
return; return;
} }
const atomic_ullong *last_req_ts = &stream->run->http->last_req_ts; atomic_ullong *last_req_ts = &stream->run->http->last_req_ts;
const ldf now_ts = us_get_now_monotonic(); const ldf now_ts = us_get_now_monotonic();
if (_stream_has_any_clients_cached(stream)) { if (_stream_has_any_clients_cached(stream)) {