Compare commits

...

60 Commits
v6.48 ... v6.61

Author SHA1 Message Date
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
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
44 changed files with 470 additions and 440 deletions

View File

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

1
.gitignore vendored
View File

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

View File

@@ -72,7 +72,7 @@ apps:
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:

View File

@@ -35,13 +35,14 @@ If you're going to live-stream from your backyard webcam and need to control it,
# Installation
## 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.
* Fedora: https://src.fedoraproject.org/rpms/ustreamer.
* Ubuntu: https://packages.ubuntu.com/jammy/ustreamer.
* Arch Linux: https://aur.archlinux.org/packages/ustreamer
* Fedora: https://src.fedoraproject.org/rpms/ustreamer
* Ubuntu: https://packages.ubuntu.com/jammy/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
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
endif

View File

@@ -34,13 +34,13 @@
#include "uslibs/types.h"
#include "uslibs/errors.h"
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/logging.h"
#include "uslibs/array.h"
#include "uslibs/ring.h"
#include "uslibs/threading.h"
#include "rtp.h"
#include "au.h"
#include "logging.h"
static void *_pcm_thread(void *v_acap);
@@ -60,14 +60,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) {
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;
}
assert(!snd_pcm_hw_params_malloc(&acap->dev_params));
# define SET_PARAM(_msg, _func, ...) { \
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; \
} \
}
@@ -78,7 +78,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 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) {
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);
goto error;
}
@@ -93,7 +93,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);
if (err < 0) {
acap->res = NULL;
US_JLOG_PERROR_RES(err, "acap", "Can't create resampler");
US_LOG_PERROR_RES(err, "Can't create resampler");
goto error;
}
}
@@ -110,7 +110,7 @@ us_acap_s *us_acap_init(const char *name, uint pcm_hz) {
// 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;
US_THREAD_CREATE(acap->enc_tid, _encoder_thread, acap);
US_THREAD_CREATE(acap->pcm_tid, _pcm_thread, acap);
@@ -135,7 +135,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->pcm_ring, us_au_pcm_destroy);
if (acap->tids_created) {
US_JLOG_INFO("acap", "Capture closed");
US_LOG_INFO("Capture closed");
}
free(acap);
}
@@ -161,7 +161,7 @@ int us_acap_get_encoded(us_acap_s *acap, u8 *data, uz *size, u64 *pts) {
}
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;
u8 in[US_AU_MAX_BUF8];
@@ -169,10 +169,10 @@ static void *_pcm_thread(void *v_acap) {
while (!atomic_load(&acap->stop)) {
const int frames = snd_pcm_readi(acap->dev, in, acap->pcm_frames);
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;
} 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;
}
@@ -182,7 +182,7 @@ static void *_pcm_thread(void *v_acap) {
memcpy(out->data, in, acap->pcm_size);
us_ring_producer_release(acap->pcm_ring, ri);
} else {
US_JLOG_ERROR("acap", "PCM ring is full");
US_LOG_ERROR("PCM ring is full");
}
}
@@ -191,7 +191,7 @@ static void *_pcm_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;
s16 in_res[US_AU_MAX_BUF16];
@@ -217,7 +217,7 @@ static void *_encoder_thread(void *v_acap) {
const int out_ri = us_ring_producer_acquire(acap->enc_ring, 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);
continue;
}
@@ -233,7 +233,7 @@ static void *_encoder_thread(void *v_acap) {
acap->pts += US_AU_HZ_TO_FRAMES(US_RTP_OPUS_HZ);
} else {
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);
}

View File

@@ -35,11 +35,11 @@
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/logging.h"
#include "uslibs/array.h"
#include "uslibs/list.h"
#include "uslibs/ring.h"
#include "logging.h"
#include "au.h"
#include "rtp.h"
@@ -55,6 +55,9 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
US_CALLOC(client, 1);
client->gw = gw;
client->session = session;
client->video_ssrc = us_get_ssrc();
client->audio_ssrc = us_get_ssrc();
atomic_init(&client->transmit, false);
atomic_init(&client->transmit_acap, false);
atomic_init(&client->transmit_aplay, false);
@@ -99,7 +102,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);
const int ri = us_ring_producer_acquire(ring, 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"));
return;
}
@@ -139,7 +142,7 @@ void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet) {
us_ring_s *const ring = client->aplay_enc_ring;
const int ri = us_ring_producer_acquire(ring, 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;
}
us_au_encoded_s *enc = ring->items[ri];
@@ -154,12 +157,12 @@ void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet) {
}
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);
}
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);
}
@@ -181,6 +184,8 @@ static void *_video_or_acap_thread(void *v_client, bool video) {
atomic_load(&client->transmit)
&& (video || atomic_load(&client->transmit_acap))
) {
us_rtp_write_ssrc(&rtp, (rtp.video ? client->video_ssrc : client->audio_ssrc));
janus_plugin_rtp packet = {
.video = rtp.video,
.buffer = (char*)rtp.datagram,
@@ -231,7 +236,7 @@ static void *_video_or_acap_thread(void *v_client, bool video) {
}
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;
@@ -253,7 +258,7 @@ static void *_aplay_thread(void *v_client) {
const int out_ri = us_ring_producer_acquire(client->aplay_pcm_ring, 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);
continue;
}
@@ -266,7 +271,7 @@ static void *_aplay_thread(void *v_client) {
out->frames = frames;
} else {
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);
}

View File

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

View File

@@ -30,9 +30,9 @@
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/logging.h"
#include "const.h"
#include "logging.h"
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;
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);
if (jcfg == NULL) {
US_JLOG_ERROR("config", "Can't read config");
US_LOG_ERROR("Can't read config");
goto error;
}
janus_config_print(jcfg);
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;
}
if ((config->acap_dev_name = _get_value(jcfg, "acap", "device")) != NULL) {
config->acap_hz = _get_uint(jcfg, "acap", "sampling_rate", 0);
config->tc358743_dev_path = _get_value(jcfg, "acap", "tc358743");
if (config->acap_hz == 0 && config->tc358743_dev_path == NULL) {
US_JLOG_ERROR("config", "Either acap.sampling_rate or acap.tc358743 required");
US_LOG_ERROR("Either acap.sampling_rate or acap.tc358743 required");
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;
@@ -87,6 +88,7 @@ void us_config_destroy(us_config_s *config) {
US_DELETE(config->acap_dev_name, free);
US_DELETE(config->tc358743_dev_path, free);
US_DELETE(config->aplay_dev_name, free);
US_DELETE(config->vplay_sink_name, free);
free(config);
}

View File

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

View File

@@ -22,7 +22,6 @@
#include <stdatomic.h>
#include <stdlib.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
@@ -43,20 +42,20 @@
#include "uslibs/errors.h"
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/logging.h"
#include "uslibs/list.h"
#include "uslibs/ring.h"
#include "uslibs/memsinksh.h"
#include "uslibs/memsink.h"
#include "uslibs/tc358743.h"
#include "const.h"
#include "logging.h"
#include "client.h"
#include "au.h"
#include "acap.h"
#include "rtp.h"
#include "rtpv.h"
#include "rtpa.h"
#include "memsinkfd.h"
#include "sdp.h"
#include "config.h"
@@ -136,81 +135,53 @@ static void *_video_rtp_thread(void *arg) {
static void *_video_sink_thread(void *arg) {
(void)arg;
US_THREAD_SETTLE("us_p_vsink");
US_THREAD_SETTLE("us_p_vcap");
atomic_store(&_g_video_sink_tid_created, true);
us_frame_s *drop = us_frame_init();
u64 frame_id = 0;
us_frame_s *tmp = us_frame_init();
int once = 0;
while (!_STOP) {
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);
continue;
}
int fd = -1;
us_memsink_shared_s *mem = 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"); });
us_memsink_s *sink = us_memsink_init_opened("vcap", _g_config->video_sink_name, false, 0, false, 0, 1);
if (sink == NULL) {
goto close_memsink;
}
once = 0;
US_JLOG_INFO("video", "Memsink opened; reading frames ...");
US_LOG_INFO("Memsink opened; reading frames ...");
while (!_STOP && _HAS_WATCHERS) {
const int waited = us_memsink_fd_wait_frame(fd, mem, frame_id);
if (waited == 0) {
const int got = us_memsink_client_get(sink, tmp, NULL, atomic_load(&_g_key_required));
if (got == 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) {
us_frame_s *dest = _g_video_ring->items[ri];
us_frame_copy(tmp, dest);
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) {
goto close_memsink;
}
if (ri >= 0 && frame->key) {
atomic_store(&_g_key_required, false);
}
} else if (waited != US_ERROR_NO_DATA) {
} else if (got != US_ERROR_NO_DATA) {
goto close_memsink;
}
}
close_memsink:
if (mem != NULL) {
us_memsink_shared_unmap(mem, data_size);
mem = NULL;
}
US_CLOSE_FD(fd);
US_JLOG_INFO("video", "Memsink closed");
US_DELETE(sink, us_memsink_destroy);
US_LOG_INFO("Memsink closed");
sleep(1); // error_delay
}
us_frame_destroy(drop);
us_frame_destroy(tmp);
return NULL;
}
@@ -220,17 +191,17 @@ static int _get_acap_hz(uint *hz) {
return 0;
}
if (_g_config->tc358743_dev_path == NULL) {
US_JLOG_ERROR("acap", "No configured sampling rate");
US_LOG_ERROR("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");
US_LOG_PERROR("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);
US_LOG_PERROR("Can't check TC358743 audio state (%d)", checked);
close(fd);
return -1;
}
@@ -240,7 +211,7 @@ static int _get_acap_hz(uint *hz) {
static void *_acap_thread(void *arg) {
(void)arg;
US_THREAD_SETTLE("us_p_ac");
US_THREAD_SETTLE("us_p_acap");
atomic_store(&_g_acap_tid_created, true);
assert(_g_config->acap_dev_name != NULL);
@@ -258,17 +229,17 @@ static void *_acap_thread(void *arg) {
us_acap_s *acap = NULL;
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;
}
if (_get_acap_hz(&hz) < 0) {
goto close_acap;
}
if (hz == 0) {
US_ONCE({ US_JLOG_INFO("acap", "No audio presented from the host"); });
US_ONCE({ US_LOG_INFO("No audio presented from the host"); });
goto close_acap;
}
US_ONCE({ US_JLOG_INFO("acap", "Detected host audio"); });
US_ONCE({ US_LOG_INFO("Detected host audio"); });
if ((acap = us_acap_init(_g_config->acap_dev_name, hz)) == NULL) {
goto close_acap;
}
@@ -279,7 +250,7 @@ static void *_acap_thread(void *arg) {
if (_get_acap_hz(&hz) < 0 || acap->pcm_hz != hz) {
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];
u64 pts;
const int result = us_acap_get_encoded(acap, data, &size, &pts);
@@ -301,7 +272,7 @@ static void *_acap_thread(void *arg) {
static void *_aplay_thread(void *arg) {
(void)arg;
US_THREAD_SETTLE("us_p_ap");
US_THREAD_SETTLE("us_p_aplay");
atomic_store(&_g_aplay_tid_created, true);
assert(_g_config->aplay_dev_name != NULL);
@@ -330,10 +301,10 @@ static void *_aplay_thread(void *arg) {
}
} while (skip && !_STOP);
us_au_pcm_mix(&mixed, &last);
// US_JLOG_INFO("++++++", "mixed %p", client);
// US_LOG_INFO("++++++ mixed %p", client);
});
_UNLOCK_APLAY;
// US_JLOG_INFO("++++++", "--------------");
// US_LOG_INFO("++++++ --------------");
if (skip) {
static uint skipped = 0;
@@ -345,19 +316,19 @@ static void *_aplay_thread(void *arg) {
}
}
if (!_HAS_WATCHERS || !_HAS_LISTENERS || !_HAS_SPEAKERS) {
if (!_HAS_WATCHERS || !_HAS_SPEAKERS) {
goto close_aplay;
}
if (dev == NULL) {
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;
}
int err = snd_pcm_open(&dev, _g_config->aplay_dev_name, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) {
US_ONCE({ US_JLOG_PERROR_ALSA(err, "aplay", "Can't open PCM playback"); });
US_ONCE({ US_LOG_PERROR_ALSA(err, "Can't open PCM playback"); });
goto close_aplay;
}
@@ -365,11 +336,11 @@ static void *_aplay_thread(void *arg) {
US_RTP_OPUS_CH, US_RTP_OPUS_HZ, 1 /* soft resample */, 50000 /* 50000 = 0.05sec */
);
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;
}
US_JLOG_INFO("aplay", "Playback opened, playing ...");
US_LOG_INFO("Playback opened, playing ...");
once = 0;
}
@@ -379,20 +350,20 @@ static void *_aplay_thread(void *arg) {
frames = snd_pcm_recover(dev, frames, 1);
} else {
if (once != 0) {
US_JLOG_INFO("aplay", "Playing resumed (snd_pcm_writei) ...");
US_LOG_INFO("Playing resumed (snd_pcm_writei) ...");
}
once = 0;
skip = false;
}
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) {
goto close_aplay;
}
skip = true;
} else {
if (once != 0) {
US_JLOG_INFO("aplay", "Playing resumed (snd_pcm_recover) ...");
US_LOG_INFO("Playing resumed (snd_pcm_recover) ...");
}
once = 0;
skip = false;
@@ -403,7 +374,7 @@ static void *_aplay_thread(void *arg) {
close_aplay:
if (dev != NULL) {
US_DELETE(dev, snd_pcm_close);
US_JLOG_INFO("aplay", "Playback closed");
US_LOG_INFO("Playback closed");
}
}
return NULL;
@@ -430,7 +401,9 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
// sysctl -w net.core.rmem_max=1000000
// sysctl -w net.core.wmem_max=1000000
US_JLOG_INFO("main", "Initializing PiKVM uStreamer plugin %s ...", US_VERSION);
US_LOGGING_INIT;
US_LOG_INFO("Initializing PiKVM uStreamer plugin %s ...", US_VERSION);
if (gw == NULL || config_dir_path == NULL || ((_g_config = us_config_init(config_dir_path)) == NULL)) {
return -1;
}
@@ -443,9 +416,9 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
if (_g_config->acap_dev_name != NULL) {
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
US_THREAD_CREATE(_g_acap_tid, _acap_thread, NULL);
if (_g_config->aplay_dev_name != NULL) {
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_sink_tid, _video_sink_thread, NULL);
@@ -455,7 +428,7 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
}
static void _plugin_destroy(void) {
US_JLOG_INFO("main", "Destroying plugin ...");
US_LOG_INFO("Destroying plugin ...");
atomic_store(&_g_stop, true);
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } }
@@ -475,12 +448,14 @@ static void _plugin_destroy(void) {
US_DELETE(_g_rtpa, us_rtpa_destroy);
US_DELETE(_g_rtpv, us_rtpv_destroy);
US_DELETE(_g_config, us_config_destroy);
US_LOGGING_DESTROY;
}
static void _plugin_create_session(janus_plugin_session *session, int *err) {
_IF_DISABLED({ *err = -1; return; });
_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_LIST_APPEND(_g_clients, client);
atomic_store(&_g_has_watchers, true);
@@ -496,7 +471,7 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
bool has_speakers = false;
US_LIST_ITERATE(_g_clients, client, {
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_janus_client_destroy(client);
found = true;
@@ -507,7 +482,7 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
}
});
if (!found) {
US_JLOG_WARN("main", "No session %p", session);
US_LOG_ERROR("No session %p", session);
*err = -2;
}
atomic_store(&_g_has_watchers, has_watchers);
@@ -539,13 +514,13 @@ static void _set_transmit(janus_plugin_session *session, const char *msg, bool t
US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
atomic_store(&client->transmit, transmit);
// US_JLOG_INFO("main", "%s session %p", msg, session);
// US_LOG_INFO("%s session %p", msg, session);
found = true;
}
has_watchers = (has_watchers || atomic_load(&client->transmit));
});
if (!found) {
US_JLOG_WARN("main", "No session %p", session);
US_LOG_ERROR("No session %p", session);
}
atomic_store(&_g_has_watchers, has_watchers);
_UNLOCK_ALL;
@@ -567,7 +542,7 @@ static struct janus_plugin_result *_plugin_handle_message(
}
# 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_object_set_new(m_event, "ustreamer", json_string("event")); \
json_object_set_new(m_event, "error_code", json_integer(x_error)); \
@@ -587,7 +562,7 @@ static struct janus_plugin_result *_plugin_handle_message(
PUSH_ERROR(400, "Request not a string");
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) { \
json_t *const m_event = json_object(); \
@@ -612,21 +587,20 @@ static struct janus_plugin_result *_plugin_handle_message(
uint video_orient = 0;
bool with_acap = false;
bool with_aplay = false;
bool with_vplay = false;
{
json_t *const params = json_object_get(msg, "params");
if (params != NULL) {
{
json_t *const obj = json_object_get(params, "audio");
if (obj != NULL && json_is_boolean(obj)) {
with_acap = (us_au_probe(_g_config->acap_dev_name) && json_boolean_value(obj));
# define READ_BOOL(x_target, x_key, x_cond) { \
json_t *const m_obj = json_object_get(params, x_key); \
if (m_obj != NULL && json_is_boolean(m_obj)) { \
x_target = (json_boolean_value(m_obj) && (x_cond)); \
} \
}
}
{
json_t *const obj = json_object_get(params, "mic");
if (obj != NULL && json_is_boolean(obj)) {
with_aplay = (us_au_probe(_g_config->aplay_dev_name) && json_boolean_value(obj));
}
}
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));
READ_BOOL(with_vplay, "camera", (_g_config->vplay_sink_name != NULL));
# undef READ_BOOL
{
json_t *const obj = json_object_get(params, "orientation");
if (obj != NULL && json_is_integer(obj)) {
@@ -640,41 +614,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;
bool has_listeners = false;
bool has_speakers = false;
US_LIST_ITERATE(_g_clients, client, {
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_aplay, with_aplay);
atomic_store(&client->video_orient, video_orient);
@@ -689,18 +645,17 @@ static struct janus_plugin_result *_plugin_handle_message(
} else if (!strcmp(request_str, "features")) {
const char *const ice_url = getenv("JANUS_USTREAMER_WEB_ICE_URL");
const bool acap_avail = us_au_probe(_g_config->acap_dev_name);
json_t *const features = json_pack(
"{s:b, s:b, s:{s:s?}}",
"audio", acap_avail,
"mic", (acap_avail && us_au_probe(_g_config->aplay_dev_name)),
"audio", us_au_probe(_g_config->acap_dev_name),
"mic", us_au_probe(_g_config->aplay_dev_name),
"ice", "url", (ice_url != NULL ? ice_url : default_ice_url)
);
PUSH_STATUS("features", features, NULL);
json_decref(features);
} 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);
} else {
@@ -740,8 +695,10 @@ static void _plugin_incoming_rtcp(janus_plugin_session *session, janus_plugin_rt
if (session == NULL || packet == NULL || !packet->video) {
return; // Accept only valid video
}
if (janus_rtcp_has_pli(packet->buffer, packet->length)) {
// US_JLOG_INFO("main", "Got video PLI");
if (
janus_rtcp_has_pli(packet->buffer, packet->length)
|| janus_rtcp_has_fir(packet->buffer, packet->length)
) {
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) {
rtp->payload = payload;
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;
if (marked) {
if (last_header) {
word0 |= 1 << 23;
}
word0 |= (rtp->payload & 0x7F) << 16;
word0 |= 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(4, pts);
WRITE_BE_U32(8, rtp->ssrc);
# undef WRITE_BE_U32
WRITE_BE_U32(8, 0); // SSRC should be written separately for each client
}
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
#include "uslibs/types.h"
#include "uslibs/tools.h"
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
#define US_RTP_DATAGRAM_SIZE 1200
// Max RTP size for WebRTC is 1200 bytes:
// - 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_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_OPUS_PAYLOAD 111
@@ -40,10 +48,9 @@
typedef struct {
uint payload;
bool video;
u32 ssrc;
u16 seq;
u8 datagram[US_RTP_DATAGRAM_SIZE];
u8 datagram[US_RTP_TOTAL_SIZE];
uz used;
bool first_of_frame;
@@ -55,8 +62,13 @@ typedef struct {
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);
void us_rtp_destroy(us_rtp_s *rtp);
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 <stdlib.h>
#include <inttypes.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "rtp.h"
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
us_rtpa_s *rtpa;
@@ -43,32 +44,8 @@ void us_rtpa_destroy(us_rtpa_s *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) {
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);
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, 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);
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);

View File

@@ -26,8 +26,6 @@
#include "rtpv.h"
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#include <linux/videodev2.h>
@@ -35,10 +33,15 @@
#include "uslibs/tools.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 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) {
@@ -55,89 +58,112 @@ void us_rtpv_destroy(us_rtpv_s *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
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:
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
US_A(frame->format == V4L2_PIX_FMT_H264);
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->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
sz last_offset = -_PRE;
while (true) { // Find and iterate by nalus
const uz next_start = last_offset + _PRE;
sz offset = _find_annexb(frame->data + next_start, frame->used - next_start);
if (offset < 0) {
break;
}
offset += next_start;
// Bytestream can be like:
// [00-]00-00-01 NALU [00-]00-00-01 NALU
// ... with optional 00 prefix for _PRE.
// We need to iterate by NALUs only and throw away separators.
if (last_offset >= 0) {
const u8 *const data = frame->data + last_offset + _PRE;
uz size = offset - last_offset - _PRE;
if (data[size - 1] == 0) { // Check for extra 00
const sz offset_to_first = _find_annexb(frame->data, frame->used);
if (offset_to_first < 0) {
return;
}
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;
}
_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 type = data[0] & 0x1F;
u8 *dg = rtpv->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;
}
us_rtp_s *rtp = rtpv->rtp;
u8 *dg = rtp->datagram;
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;
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);
if (last) {
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);
@@ -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;
memcpy(dg + fu_overhead, src, frag_size);
rtpv->rtp->used = fu_overhead + frag_size;
rtpv->rtp->last_of_frame = last;
rtpv->callback(rtpv->rtp);
rtpv->rtp->first_of_frame = false;
rtp->used = fu_overhead + frag_size;
rtp->first_of_frame = (first_nalu && first);
rtp->last_of_frame = (last_nalu && last);
rtpv->callback(rtp);
src += frag_size;
remaining -= frag_size;
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);
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);

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
#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);
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required);
char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay, bool vplay);

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

@@ -22,22 +22,11 @@
#include <janus/plugins/plugin.h>
#include "uslibs/tools.h"
#include "const.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_JLOG_WARN(x_prefix, x_msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
#define US_JLOG_ERROR(x_prefix, x_msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
#define US_JLOG_PERROR(x_prefix, x_msg, ...) { \
char *const m_perror_str = us_errno_to_string(errno); \
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_str); \
free(m_perror_str); \
#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); \
JANUS_LOG(LOG_INFO, "== ustreamer/%-15s " x_label " -- " x_msg "\n", m_tname_buf, ##__VA_ARGS__); \
}
// 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))

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

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

View File

@@ -37,7 +37,7 @@ deps =
[testenv:mypy]
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 =
mypy

View File

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

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 6.48" "November 2020"
.TH USTREAMER 1 "version 6.61" "November 2020"
.SH NAME
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
\fB\-\-format=uyvy \e\fR # Device input format
.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
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
.nf
@@ -66,7 +66,7 @@ Available: MMAP, USERPTR; default: MMAP.
Desired FPS. Default: maximum possible.
.TP
.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
.BR \-T ", " \-\-allow\-truncated\-frames
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.
.TP
.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).
.TP
.BR \-w\ \fIN ", " \-\-workers\ \fIN

View File

@@ -3,13 +3,13 @@
pkgname=ustreamer
pkgver=6.48
pkgver=6.61
pkgrel=1
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"
license=(GPL)
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)
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP)
@@ -19,7 +19,7 @@ _options="WITH_GPIO=1 WITH_SYSTEMD=1"
if [ -e /usr/bin/python3 ]; then
_options="$_options WITH_PYTHON=1"
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
if [ -e /usr/include/janus/plugins/plugin.h ];then
depends+=(janus-gateway alsa-lib opus)

View File

@@ -2,11 +2,14 @@
# This is free software, licensed under the GNU General Public License v2.
# 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
PKG_NAME:=ustreamer
PKG_VERSION:=6.48
PKG_VERSION:=6.61
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

View File

@@ -9,13 +9,12 @@ all: root
root: $(shell find src -type f,l) setup.py
$(info == PY_BUILD ustreamer-*.so)
rm -rf root
$(ECHO) $(PY) -m build --skip-dependency-check --no-isolation
$(ECHO) $(PY) -m pip install dist/*.whl --ignore-installed --root=./root
$(ECHO) $(PY) -m build --wheel --skip-dependency-check --no-isolation
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:
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()
setup(
name="ustreamer",
version="6.48",
version="6.61",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",

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 _DUMP_LDFLAGS += -latomic
override _V4P_LDFLAGS += -latomic
@@ -78,7 +78,7 @@ endif
ifneq ($(MK_WITH_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
endif
endif

View File

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

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 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 float _get_refresh_rate(const drmModeModeInfo *mode);
@@ -726,7 +726,7 @@ static u32 _find_dpms(int fd, drmModeConnector *conn) {
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) {
drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[ei]);
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 *line;
const char *line;
char *rest;
uint block_width = 0;

View File

@@ -34,6 +34,7 @@
#include "types.h"
#include "tools.h"
#include "threading.h"
#include "logging_base.h"
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_SEP_INFO(x_ch) { \
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); \
}
// XXX: See the definition of US_LOG_PRINTF_NOLOCK() in logging_base.h
#define US_LOG_PRINTF(x_label_color, x_label, x_msg_color, x_msg, ...) { \
US_LOGGING_LOCK; \
@@ -110,6 +81,10 @@ extern pthread_mutex_t us_g_log_mutex;
US_LOGGING_UNLOCK; \
}
#define US_LOG_SEP_INFO { \
US_LOG_PRINTF(US_COLOR_GREEN, "INFO ", US_COLOR_GREEN, "============================================="); \
}
#define US_LOG_ERROR(x_msg, ...) { \
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); \
}
// 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, ...) { \
US_LOG_PRINTF(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
}

View File

@@ -20,61 +20,29 @@
*****************************************************************************/
#include "memsinkfd.h"
#pragma once
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/videodev2.h>
#include <pthread.h>
#include "uslibs/types.h"
#include "uslibs/errors.h"
#include "uslibs/tools.h"
#include "uslibs/frame.h"
#include "uslibs/memsinksh.h"
#include "logging.h"
#include "types.h"
#include "tools.h"
#include "threading.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s *mem, u64 last_id) {
const ldf deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
ldf now_ts;
do {
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
now_ts = us_get_now_monotonic();
if (result < 0 && errno != EWOULDBLOCK) {
US_JLOG_PERROR("video", "Can't lock memsink");
return -1;
} else if (result == 0) {
if (mem->magic == US_MEMSINK_MAGIC && mem->version == US_MEMSINK_VERSION && mem->id != last_id) {
return 0;
}
if (flock(fd, LOCK_UN) < 0) {
US_JLOG_PERROR("video", "Can't unlock memsink");
return -1;
}
}
usleep(1000); // lock_polling
} while (now_ts < deadline_ts);
return US_ERROR_NO_DATA;
}
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required) {
us_frame_set_data(frame, us_memsink_get_data(mem), mem->used);
US_FRAME_COPY_META(mem, frame);
*frame_id = mem->id;
mem->last_client_ts = us_get_now_monotonic();
if (key_required) {
mem->key_requested = true;
#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); \
}
bool retval = 0;
if (frame->format != V4L2_PIX_FMT_H264) {
US_JLOG_ERROR("video", "Got non-H264 frame from memsink");
retval = -1;
}
if (flock(fd, LOCK_UN) < 0) {
US_JLOG_PERROR("video", "Can't unlock memsink");
retval = -1;
}
return retval;
}

View File

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

View File

@@ -27,7 +27,7 @@
#define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE)
#define US_MEMSINK_VERSION ((u32)7)
#define US_MEMSINK_VERSION ((u32)8)
typedef struct {

View File

@@ -94,12 +94,12 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
#ifdef WITH_PTHREAD_NP
int retval = -1;
# 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 \
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|| 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') {
retval = 0;
}

View File

@@ -216,15 +216,11 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
US_CALLOC(line_buf, frame->width * 3);
const uint padding = us_frame_get_padding(frame);
const uint image_size = frame->width * frame->height;
const uint chroma_array_size = (frame->used - image_size) / 2;
const uint chroma_matrix_order = (image_size / chroma_array_size) == 16 ? 4 : 2;
const uint luma_array_size = (frame->width + padding) * frame->height;
const uint chroma_array_size = (frame->used - luma_array_size) >> 1;
const u8 *data = frame->data;
const u8 *chroma1_data = frame->data + image_size;
const u8 *chroma2_data = frame->data + image_size + chroma_array_size;
//US_LOG_DEBUG("Planar data: Image Size %u, Chroma Array Size %u, Chroma Matrix Order %u",
// image_size, chroma_array_size, chroma_matrix_order);
const u8 *chroma1_data = frame->data + luma_array_size;
const u8 *chroma2_data = frame->data + luma_array_size + chroma_array_size;
while (jpeg->next_scanline < frame->height) {
u8 *ptr = line_buf;
@@ -234,7 +230,7 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
u8 y = data[x];
u8 u;
u8 v;
uint chroma_position = x / chroma_matrix_order;
uint chroma_position = x >> 1;
switch (frame->format) {
case V4L2_PIX_FMT_YUV420:
@@ -257,9 +253,9 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
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;
if (jpeg->next_scanline > 0 && jpeg->next_scanline % 2 == 0) {
chroma1_data += (frame->width + padding) >> 1;
chroma2_data += (frame->width + padding) >> 1;
}
JSAMPROW scanlines[1] = {line_buf};

View File

@@ -300,10 +300,11 @@ us_options_s *us_options_init(uint argc, char *argv[]) {
opts->argc = argc;
opts->argv = argv;
US_CALLOC(opts->argv_copy, argc);
US_CALLOC(opts->argv_copy, argc + 1);
for (uint i = 0; i < argc; ++i) {
opts->argv_copy[i] = us_strdup(argv[i]);
}
opts->argv_copy[argc] = NULL;
return opts;
}
@@ -704,7 +705,7 @@ static void _help(
SAY(" Changing of this parameter may increase the performance. Or not.");
SAY(" Available: %s; default: MMAP.\n", US_IO_METHODS_STR);
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(" -T|--allow-truncated-frames ───────── Allows to handle truncated frames. Useful if the device");
SAY(" produces incorrect but still acceptable frames. Default: disabled.\n");
@@ -712,7 +713,7 @@ static void _help(
SAY(" -t|--dv-timings ───────────────────── Enable DV-timings querying and events processing");
SAY(" to automatic resolution change. Default: disabled.\n");
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(" -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);

View File

@@ -272,7 +272,7 @@ void us_stream_loop(us_stream_s *stream) {
us_capture_close(cap);
if (!atomic_load(&run->stop)) {
US_SEP_INFO('=');
US_LOG_SEP_INFO;
}
}
@@ -770,7 +770,7 @@ static void _stream_check_suicide(us_stream_s *stream) {
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();
if (_stream_has_any_clients_cached(stream)) {