mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-11-05 04:24:21 +00:00
Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e64f1feda | ||
|
|
cfe83ae0bf | ||
|
|
864aac077e | ||
|
|
6952aff31d | ||
|
|
58e517b306 | ||
|
|
f6025267f6 | ||
|
|
3af6bfac0f | ||
|
|
db87e03ce7 | ||
|
|
27096fc20c | ||
|
|
84dc156b4a | ||
|
|
f627948a04 | ||
|
|
72bfd68514 | ||
|
|
6273fc187a | ||
|
|
5277bc7c59 | ||
|
|
6e0f358fbe | ||
|
|
3106aa7ef0 | ||
|
|
64981efb73 | ||
|
|
2823d811ef | ||
|
|
35fa72ba73 | ||
|
|
e1542dd617 | ||
|
|
6e554e5081 | ||
|
|
b5e12a8412 | ||
|
|
c65004f368 | ||
|
|
fc8550da6f | ||
|
|
604d6613c5 | ||
|
|
5a189866b7 | ||
|
|
db96c0699d | ||
|
|
2e07557c8c | ||
|
|
328e550801 | ||
|
|
b09efaacb8 | ||
|
|
dae9301e63 | ||
|
|
0301608b8f | ||
|
|
dd89d04211 | ||
|
|
6dd5b8709f | ||
|
|
41a8eb46ca | ||
|
|
7cf03cf35a | ||
|
|
654efeeca2 | ||
|
|
0f96532167 | ||
|
|
95566d51df | ||
|
|
b1c5276ab4 | ||
|
|
ce231e7963 | ||
|
|
ff639cce1d | ||
|
|
fdea0d214b | ||
|
|
72e2c6dabe | ||
|
|
2c515eeea5 | ||
|
|
730cc3c89e | ||
|
|
686460fa29 | ||
|
|
54c1c77dfb | ||
|
|
0e0aef7b80 | ||
|
|
31b98f1d59 | ||
|
|
9734c83bd4 | ||
|
|
b88abdcae4 | ||
|
|
6fca17cf52 | ||
|
|
5fe579956e | ||
|
|
52dfa38a8f | ||
|
|
8b76b59393 | ||
|
|
92878e22ab | ||
|
|
4dc0f73e1b | ||
|
|
9fe42b038a | ||
|
|
3750a22091 | ||
|
|
ef59c52a83 | ||
|
|
f296bb57e7 | ||
|
|
f7166b7c41 | ||
|
|
b35200345e | ||
|
|
5bd7c9c5ef | ||
|
|
fc35b1cd87 | ||
|
|
92d9828894 | ||
|
|
e7008c6086 | ||
|
|
d0672e15b9 | ||
|
|
04aa4c166c | ||
|
|
0fbbc27d8c | ||
|
|
b02bb85d8b | ||
|
|
80dce276ed | ||
|
|
e869536a11 | ||
|
|
b0acf0fa4d | ||
|
|
f32e2569d2 | ||
|
|
6d63f8e5d2 | ||
|
|
2dac4db9fc | ||
|
|
23dd2f9e66 | ||
|
|
fdfd4225d0 | ||
|
|
9424b655eb | ||
|
|
f94fe25845 | ||
|
|
bdcbaae8b3 | ||
|
|
52a2326426 | ||
|
|
fa15d9693f | ||
|
|
88460b72e1 | ||
|
|
8c69c77481 | ||
|
|
5331ae14aa | ||
|
|
0127dcf018 | ||
|
|
aa58b1b002 | ||
|
|
a05eab71a8 | ||
|
|
e013356cf0 | ||
|
|
c730981827 | ||
|
|
3bb1ed3ef3 | ||
|
|
1cda22bfd2 | ||
|
|
29ee20e864 | ||
|
|
5e13f4cd58 | ||
|
|
39c1916f61 | ||
|
|
614e83771b | ||
|
|
15a9e28ac6 | ||
|
|
a1ae02de5d | ||
|
|
214708549d | ||
|
|
1be4521664 | ||
|
|
94752dde75 | ||
|
|
b933b7b407 | ||
|
|
61f44b5f97 | ||
|
|
8fef0408b6 | ||
|
|
62028be064 | ||
|
|
3b7592bb31 | ||
|
|
8adca998e9 | ||
|
|
6ac5a5f065 |
@@ -1,39 +0,0 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 6.47
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
[bumpversion:file:src/libs/const.h]
|
||||
parse = (?P<major>\d+)
|
||||
serialize = {major}
|
||||
search = VERSION_MAJOR {current_version}
|
||||
replace = VERSION_MAJOR {new_version}
|
||||
|
||||
[bumpversion:file:./src/libs/const.h]
|
||||
parse = <major>\d+\.(?P<minor>\d+)
|
||||
serialize = {minor}
|
||||
search = VERSION_MINOR {current_version}
|
||||
replace = VERSION_MINOR {new_version}
|
||||
|
||||
[bumpversion:file:python/setup.py]
|
||||
search = version="{current_version}"
|
||||
replace = version="{new_version}"
|
||||
|
||||
[bumpversion:file:pkg/arch/PKGBUILD]
|
||||
search = pkgver={current_version}
|
||||
replace = pkgver={new_version}
|
||||
|
||||
[bumpversion:file:pkg/openwrt/Makefile]
|
||||
search = PKG_VERSION:={current_version}
|
||||
replace = PKG_VERSION:={new_version}
|
||||
|
||||
[bumpversion:file:man/ustreamer.1]
|
||||
search = "version {current_version}"
|
||||
replace = "version {new_version}"
|
||||
|
||||
[bumpversion:file:man/ustreamer-dump.1]
|
||||
search = "version {current_version}"
|
||||
replace = "version {new_version}"
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
/python/dist/
|
||||
/python/ustreamer.egg-info/
|
||||
/python/root/
|
||||
/python/build/
|
||||
/janus/build/
|
||||
/ustreamer
|
||||
/ustreamer-*
|
||||
|
||||
4
Makefile
4
Makefile
@@ -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:
|
||||
@@ -132,7 +132,7 @@ linters:
|
||||
|
||||
|
||||
bump:
|
||||
bumpversion $(if $(V),$(V),minor)
|
||||
./bump.py $(if $(V),$(V),minor)
|
||||
|
||||
|
||||
push:
|
||||
|
||||
11
README.md
11
README.md
@@ -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).
|
||||
|
||||
85
bump.py
Executable file
85
bump.py
Executable file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# KVMD - The main PiKVM daemon. #
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
# ========================================================================== #
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
|
||||
# =====
|
||||
def _run(cmd: list[str]) -> str:
|
||||
return str(subprocess.run(cmd, stdout=subprocess.PIPE, check=True).stdout.decode())
|
||||
|
||||
|
||||
def _get_version() -> tuple[int, int]:
|
||||
ver = _run(["/usr/bin/git", "describe", "--tags", "--abbrev=0"]).strip()
|
||||
assert (m := re.match(r"v(\d+)\.(\d+)", ver)), ver
|
||||
return (int(m.group(1)), int(m.group(2)))
|
||||
|
||||
|
||||
def _make_version(major: int, minor: int) -> str:
|
||||
return f"v{major}.{minor}"
|
||||
|
||||
|
||||
def _patch_file(path: str, replace: str, old: tuple[int, int], new: tuple[int, int]) -> None:
|
||||
with open(path, "r+") as f:
|
||||
old_text = f.read()
|
||||
new_text = old_text.replace(
|
||||
replace.format(major=old[0], minor=old[1]),
|
||||
replace.format(major=new[0], minor=new[1]),
|
||||
)
|
||||
# assert old_text != new_text, path
|
||||
f.seek(0, os.SEEK_SET)
|
||||
f.write(new_text)
|
||||
|
||||
|
||||
def _bump(do_major: bool, items: list[tuple[str, str]]) -> None:
|
||||
_run(["/usr/bin/git", "diff-index", "--quiet", "HEAD", "--"])
|
||||
|
||||
old = _get_version()
|
||||
new = ((old[0] + 1, old[1]) if do_major else (old[0], old[1] + 1))
|
||||
|
||||
for (path, replace) in items:
|
||||
_patch_file(path, replace, old, new)
|
||||
_run(["/usr/bin/git", "add", path])
|
||||
|
||||
ver_new = _make_version(*new)
|
||||
_run(["/usr/bin/git", "commit", "-m", f"Bump version: {_make_version(*old)} -> {ver_new}"])
|
||||
_run(["/usr/bin/git", "tag", ver_new])
|
||||
|
||||
|
||||
# =====
|
||||
if __name__ == "__main__":
|
||||
_bump(
|
||||
do_major=(len(sys.argv) == 2 and sys.argv[1] == "major"),
|
||||
items=[
|
||||
("src/libs/const.h", "define US_VERSION_MAJOR {major}"),
|
||||
("src/libs/const.h", "define US_VERSION_MINOR {minor}"),
|
||||
("python/setup.py", "version=\"{major}.{minor}\""),
|
||||
("man/ustreamer.1", "version {major}.{minor}"),
|
||||
("man/ustreamer-dump.1", "version {major}.{minor}"),
|
||||
("pkg/arch/PKGBUILD", "pkgver={major}.{minor}"),
|
||||
("pkg/openwrt/Makefile", "PKG_VERSION:={major}.{minor}"),
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdatomic.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
@@ -34,13 +33,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 +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) {
|
||||
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));
|
||||
US_A(!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 +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 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 +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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -101,16 +100,16 @@ us_acap_s *us_acap_init(const char *name, uint pcm_hz) {
|
||||
{
|
||||
// OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY
|
||||
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://datatracker.ietf.org/doc/html/rfc7587#section-3.1.1
|
||||
assert(!opus_encoder_ctl(acap->enc, OPUS_SET_BITRATE(128000)));
|
||||
assert(!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_BITRATE(128000)));
|
||||
US_A(!opus_encoder_ctl(acap->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)));
|
||||
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
|
||||
}
|
||||
|
||||
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 +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->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 +160,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 +168,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 +181,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 +190,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];
|
||||
@@ -205,19 +204,19 @@ static void *_encoder_thread(void *v_acap) {
|
||||
|
||||
s16 *in_ptr;
|
||||
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 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);
|
||||
in_ptr = in_res;
|
||||
} else {
|
||||
assert(acap->pcm_hz == US_RTP_OPUS_HZ);
|
||||
US_A(acap->pcm_hz == US_RTP_OPUS_HZ);
|
||||
in_ptr = in->data;
|
||||
}
|
||||
|
||||
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 +232,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);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ bool us_au_probe(const char *name) {
|
||||
// the existence of it in /proc/asound/.
|
||||
// It's enough for our case.
|
||||
|
||||
if (name == NULL) {
|
||||
if (!us_str_is_ok(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
@@ -35,15 +34,18 @@
|
||||
#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 "uslibs/frame.h"
|
||||
|
||||
#include "logging.h"
|
||||
#include "au.h"
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
static us_frame_s *_add_vplay_frame_to_ring(us_frame_s *frame, void *v_client);
|
||||
|
||||
static void *_video_thread(void *v_client);
|
||||
static void *_acap_thread(void *v_client);
|
||||
static void *_video_or_acap_thread(void *v_client, bool video);
|
||||
@@ -55,9 +57,13 @@ 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);
|
||||
atomic_init(&client->transmit_vplay, false);
|
||||
atomic_init(&client->video_orient, 0);
|
||||
|
||||
atomic_init(&client->stop, false);
|
||||
@@ -72,6 +78,8 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
|
||||
US_RING_INIT_WITH_ITEMS(client->aplay_pcm_ring, 64, us_au_pcm_init);
|
||||
US_THREAD_CREATE(client->aplay_tid, _aplay_thread, client);
|
||||
|
||||
client->rtpc = us_rtpc_init(_add_vplay_frame_to_ring, client);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
@@ -88,6 +96,9 @@ void us_janus_client_destroy(us_janus_client_s *client) {
|
||||
US_RING_DELETE_WITH_ITEMS(client->aplay_enc_ring, us_au_encoded_destroy);
|
||||
US_RING_DELETE_WITH_ITEMS(client->aplay_pcm_ring, us_au_pcm_destroy);
|
||||
|
||||
us_rtpc_destroy(client->rtpc);
|
||||
// us_janus_client_stop_vplay(client);
|
||||
|
||||
free(client);
|
||||
}
|
||||
|
||||
@@ -99,7 +110,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;
|
||||
}
|
||||
@@ -108,65 +119,120 @@ void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
|
||||
}
|
||||
}
|
||||
|
||||
static us_frame_s *_add_vplay_frame_to_ring(us_frame_s *ready_frame, void *v_client) {
|
||||
us_janus_client_s *const client = v_client;
|
||||
|
||||
us_ring_s *const ring = client->vplay_enc_ring;
|
||||
if (!ring) {
|
||||
US_LOG_ERROR("Session %p has no vplay ring", client->session);
|
||||
return ready_frame;
|
||||
}
|
||||
|
||||
const int ri = us_ring_producer_acquire(ring, 0);
|
||||
if (ri < 0) {
|
||||
US_LOG_ERROR("Session %p vplay ring is full", client->session);
|
||||
return ready_frame;
|
||||
}
|
||||
|
||||
us_frame_s *const next_frame = ring->items[ri];
|
||||
ring->items[ri] = ready_frame;
|
||||
us_ring_producer_release(ring, ri);
|
||||
|
||||
return next_frame;
|
||||
}
|
||||
|
||||
void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet) {
|
||||
if (
|
||||
packet->video
|
||||
|| packet->length < sizeof(janus_rtp_header)
|
||||
|| !atomic_load(&client->transmit)
|
||||
|| !atomic_load(&client->transmit_aplay)
|
||||
) {
|
||||
if (packet->length < sizeof(janus_rtp_header) || !atomic_load(&client->transmit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int payload_size = 0;
|
||||
const u8 *const payload = (u8 *)janus_rtp_payload(packet->buffer, packet->length, &payload_size);
|
||||
if (payload == NULL || payload_size < 1) {
|
||||
return;
|
||||
}
|
||||
const janus_rtp_header *const header = (janus_rtp_header*)packet->buffer;
|
||||
if (header->type != US_RTP_OPUS_PAYLOAD) {
|
||||
return;
|
||||
}
|
||||
|
||||
const u16 seq = ntohs(header->seq_number);
|
||||
if (
|
||||
seq >= client->aplay_seq_next // In order or missing
|
||||
|| (client->aplay_seq_next - seq) > 50 // In late sequence or sequence wrapped
|
||||
) {
|
||||
client->aplay_seq_next = seq + 1;
|
||||
|
||||
int size = 0;
|
||||
const char *const data = janus_rtp_payload(packet->buffer, packet->length, &size);
|
||||
if (data == NULL || size <= 0) {
|
||||
return;
|
||||
if (packet->video) {
|
||||
if (header->type == US_RTP_H264_PAYLOAD && atomic_load(&client->transmit_vplay)) {
|
||||
const u32 rts = ntohl(header->timestamp); // RTS == RTP TimeStamp
|
||||
bool retry;
|
||||
do {
|
||||
retry = false;
|
||||
switch (us_rtpc_unwrap(client->rtpc, payload, payload_size, seq, rts)) {
|
||||
case US_RUR_SUCCESS:
|
||||
case US_RUR_BAD_PACKET:
|
||||
case US_RUR_INVALID_SEQ:
|
||||
case US_RUR_UNSUPPORTED_UNIT_TYPE:
|
||||
case US_RUR_DEPACKETIZATION_SKIPPED:
|
||||
break;
|
||||
case US_RUR_DEPACKETIZATION_FAILED_RETRY:
|
||||
retry = true;
|
||||
// fall through
|
||||
case US_RUR_DEPACKETIZATION_FAILED:
|
||||
client->last_key_req_ts = us_get_now_monotonic();
|
||||
client->gw->send_pli(client->session);
|
||||
break;
|
||||
}
|
||||
} while (retry);
|
||||
}
|
||||
|
||||
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);
|
||||
return;
|
||||
const ldf now_ts = us_get_now_monotonic();
|
||||
if (client->last_key_req_ts + 2 < now_ts) {
|
||||
client->last_key_req_ts = now_ts;
|
||||
client->gw->send_pli(client->session);
|
||||
}
|
||||
us_au_encoded_s *enc = ring->items[ri];
|
||||
if ((uz)size < US_ARRAY_LEN(enc->data)) {
|
||||
memcpy(enc->data, data, size);
|
||||
enc->used = size;
|
||||
} else {
|
||||
enc->used = 0;
|
||||
} else {
|
||||
if (header->type == US_RTP_OPUS_PAYLOAD && atomic_load(&client->transmit_aplay)) {
|
||||
if (seq >= client->aplay_seq_next || (client->aplay_seq_next - seq) > 50) {
|
||||
// ^^^ In order or missing |OR| ^^^ In late sequence or sequence wrapped
|
||||
client->aplay_seq_next = seq + 1;
|
||||
us_ring_s *const ring = client->aplay_enc_ring;
|
||||
const int ri = us_ring_producer_acquire(ring, 0);
|
||||
if (ri < 0) {
|
||||
// US_LOG_ERROR("Session %p aplay ring is full", client->session);
|
||||
return;
|
||||
}
|
||||
us_au_encoded_s *enc = ring->items[ri];
|
||||
if ((uz)payload_size < US_ARRAY_LEN(enc->data)) {
|
||||
memcpy(enc->data, payload, payload_size);
|
||||
enc->used = payload_size;
|
||||
} else {
|
||||
enc->used = 0;
|
||||
}
|
||||
us_ring_producer_release(ring, ri);
|
||||
}
|
||||
}
|
||||
us_ring_producer_release(ring, ri);
|
||||
}
|
||||
}
|
||||
|
||||
void us_janus_client_start_vplay(us_janus_client_s *client, us_ring_s* vplay_enc_ring) {
|
||||
if (atomic_exchange(&client->transmit_vplay, true)) {
|
||||
return;
|
||||
}
|
||||
US_A(client->vplay_enc_ring == NULL);
|
||||
client->vplay_enc_ring = vplay_enc_ring;
|
||||
}
|
||||
|
||||
/*void us_janus_client_stop_vplay(us_janus_client_s *client) {
|
||||
atomic_store(&client->transmit_vplay, false);
|
||||
client->vplay_enc_ring = NULL;
|
||||
}*/
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void *_video_or_acap_thread(void *v_client, bool video) {
|
||||
us_janus_client_s *const client = v_client;
|
||||
us_ring_s *const ring = (video ? client->video_ring : client->acap_ring);
|
||||
assert(ring != NULL);
|
||||
US_A(ring != NULL);
|
||||
|
||||
while (!atomic_load(&client->stop)) {
|
||||
const int ri = us_ring_consumer_acquire(ring, 0.1);
|
||||
@@ -181,6 +247,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,
|
||||
@@ -193,18 +261,24 @@ static void *_video_or_acap_thread(void *v_client, bool video) {
|
||||
};
|
||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||
|
||||
if (rtp.zero_playout_delay) {
|
||||
// https://github.com/pikvm/pikvm/issues/784
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 0;
|
||||
} else {
|
||||
// Эти дефолты используются в Chrome/Safari/Firefox.
|
||||
// Работает всё одинаково, потому что у них общая кодовая база WebRTC.
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 1000; // == 10s, i.e. 10ms granularity
|
||||
if (rtp.first_of_frame) {
|
||||
if (rtp.zero_playout_delay) {
|
||||
// https://github.com/pikvm/pikvm/issues/784
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 0;
|
||||
} else {
|
||||
// Эти дефолты используются в Chrome/Safari/Firefox.
|
||||
// Работает всё одинаково, потому что у них общая кодовая база WebRTC.
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 1000; // == 10s, i.e. 10ms granularity
|
||||
}
|
||||
}
|
||||
|
||||
if (rtp.video) {
|
||||
if (rtp.video && rtp.first_of_frame) {
|
||||
packet.extensions.abs_capture_ts = rtp.grab_ntp_ts;
|
||||
}
|
||||
|
||||
if (rtp.video && rtp.last_of_frame) {
|
||||
uint video_orient = atomic_load(&client->video_orient);
|
||||
if (video_orient != 0) {
|
||||
// The extension rotates the video clockwise, but want it counterclockwise.
|
||||
@@ -225,13 +299,13 @@ 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;
|
||||
|
||||
int 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)) {
|
||||
const int in_ri = us_ring_consumer_acquire(client->aplay_enc_ring, 0.1);
|
||||
@@ -247,7 +321,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;
|
||||
}
|
||||
@@ -260,7 +334,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);
|
||||
}
|
||||
|
||||
@@ -32,29 +32,41 @@
|
||||
#include "uslibs/ring.h"
|
||||
|
||||
#include "rtp.h"
|
||||
#include "rtpv.h"
|
||||
#include "rtpc.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
typedef struct us_janus_client_sx {
|
||||
janus_callbacks *gw;
|
||||
janus_plugin_session *session;
|
||||
atomic_bool transmit;
|
||||
atomic_bool transmit_acap;
|
||||
atomic_bool transmit_aplay;
|
||||
atomic_uint video_orient;
|
||||
|
||||
pthread_t video_tid;
|
||||
pthread_t acap_tid;
|
||||
pthread_t aplay_tid;
|
||||
atomic_bool stop;
|
||||
u32 video_ssrc;
|
||||
u32 audio_ssrc;
|
||||
|
||||
us_ring_s *video_ring;
|
||||
us_ring_s *acap_ring;
|
||||
atomic_bool transmit;
|
||||
atomic_bool transmit_acap;
|
||||
atomic_bool transmit_vplay;
|
||||
atomic_bool transmit_aplay;
|
||||
atomic_uint video_orient;
|
||||
|
||||
us_ring_s *aplay_enc_ring;
|
||||
u16 aplay_seq_next;
|
||||
us_ring_s *aplay_pcm_ring;
|
||||
pthread_t video_tid;
|
||||
pthread_t acap_tid;
|
||||
pthread_t aplay_tid;
|
||||
atomic_bool stop;
|
||||
|
||||
US_LIST_DECLARE;
|
||||
us_ring_s *video_ring;
|
||||
us_ring_s *acap_ring;
|
||||
|
||||
us_ring_s *aplay_enc_ring;
|
||||
u16 aplay_seq_next;
|
||||
us_ring_s *aplay_pcm_ring;
|
||||
|
||||
us_rtpc_s *rtpc;
|
||||
us_ring_s *vplay_enc_ring;
|
||||
|
||||
ldf last_key_req_ts;
|
||||
|
||||
US_LIST_DECLARE(struct us_janus_client_sx);
|
||||
} us_janus_client_s;
|
||||
|
||||
|
||||
@@ -63,3 +75,6 @@ void us_janus_client_destroy(us_janus_client_s *client);
|
||||
|
||||
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp);
|
||||
void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet);
|
||||
|
||||
void us_janus_client_start_vplay(us_janus_client_s *client, us_ring_s* vplay_enc_ring);
|
||||
// void us_janus_client_stop_vplay(us_janus_client_s *client);
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
|
||||
#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);
|
||||
static uint _get_uint(janus_config *jcfg, const char *section, const char *option, uint def);
|
||||
static uint _get_uint(janus_config *jcfg, const char *section, const char *option, uint def, uint base);
|
||||
// static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
|
||||
|
||||
|
||||
@@ -48,29 +48,37 @@ 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");
|
||||
config->video_sink_name = _get_value(jcfg, "video", "sink");
|
||||
if (!us_str_is_ok(config->video_sink_name)) {
|
||||
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->acap_dev_name = _get_value(jcfg, "acap", "device");
|
||||
if (us_str_is_ok(config->acap_dev_name)) {
|
||||
config->acap_hz = _get_uint(jcfg, "acap", "sampling_rate", 0, 10);
|
||||
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");
|
||||
if (config->acap_hz == 0 && !us_str_is_ok(config->tc358743_dev_path)) {
|
||||
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");
|
||||
config->vplay_sink_mode = _get_uint(jcfg, "vplay", "sink_mode", 0660, 8);
|
||||
config->vplay_dev_path = _get_value(jcfg, "vplay", "device");
|
||||
|
||||
goto ok;
|
||||
|
||||
error:
|
||||
@@ -87,24 +95,26 @@ 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);
|
||||
US_DELETE(config->vplay_dev_path, free);
|
||||
free(config);
|
||||
}
|
||||
|
||||
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_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 us_strdup(option_obj->value);
|
||||
}
|
||||
|
||||
static uint _get_uint(janus_config *jcfg, const char *section, const char *option, uint def) {
|
||||
static uint _get_uint(janus_config *jcfg, const char *section, const char *option, uint def, uint base) {
|
||||
char *const tmp = _get_value(jcfg, section, option);
|
||||
uint value = def;
|
||||
if (tmp != NULL) {
|
||||
errno = 0;
|
||||
value = (uint)strtoul(tmp, NULL, 10);
|
||||
value = (uint)strtoul(tmp, NULL, base);
|
||||
if (errno != 0) {
|
||||
value = def;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ typedef struct {
|
||||
char *tc358743_dev_path;
|
||||
|
||||
char *aplay_dev_name;
|
||||
|
||||
char *vplay_sink_name;
|
||||
uint vplay_sink_mode;
|
||||
char *vplay_dev_path;
|
||||
} us_config_s;
|
||||
|
||||
|
||||
|
||||
270
janus/src/h264.c
Normal file
270
janus/src/h264.c
Normal file
@@ -0,0 +1,270 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# This source file based on code of Janys-Gateway. #
|
||||
# #
|
||||
# Copyright (C) Lorenzo Miniero <lorenzo@meetecho.com> #
|
||||
# Copyright (C) 2026 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2026 Sergey Radionov <rsatom@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# 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 "h264.h"
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
|
||||
static INLINE u32 _eg_get_bit(const u8 *buf, u32 bit_offset) {
|
||||
return ((*(buf + (bit_offset >> 3))) >> (7 - (bit_offset & 0x07))) & 0x01;
|
||||
}
|
||||
|
||||
static INLINE bool _is_valid_eg_offset(u32 bit_offset, uz size) {
|
||||
return (bit_offset >> 3) + (bit_offset % 8 ? 1 : 0) < size;
|
||||
}
|
||||
|
||||
static bool _eg_skip_u1(const u8 *buf, u32 *bit_offset, uz size) {
|
||||
(void)buf;
|
||||
if (!_is_valid_eg_offset(*bit_offset, size)) {
|
||||
return false;
|
||||
}
|
||||
++(*bit_offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _eg_decode_u1(const u8 *buf, u32 *bit_offset, uz size, bool *out) {
|
||||
if (!_is_valid_eg_offset(*bit_offset, size)) {
|
||||
return false;
|
||||
}
|
||||
*out = (_eg_get_bit(buf, *bit_offset) != 0);
|
||||
++(*bit_offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _eg_skip_ev(const u8 *buf, u32 *bit_offset, uz size) {
|
||||
u32 zeros = 0;
|
||||
while (true) {
|
||||
if (!_is_valid_eg_offset(*bit_offset, size)) {
|
||||
return false;
|
||||
}
|
||||
if (_eg_get_bit(buf, (*bit_offset)++) != 0) {
|
||||
break;
|
||||
}
|
||||
++zeros;
|
||||
}
|
||||
for (; zeros; --zeros) {
|
||||
if (!_is_valid_eg_offset((*bit_offset)++, size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _eg_decode_uev(const u8 *buf, u32 *bit_offset, uint size, u32 *out) {
|
||||
u32 zeros = 0;
|
||||
while (true) {
|
||||
if (!_is_valid_eg_offset(*bit_offset, size)) {
|
||||
return false;
|
||||
}
|
||||
if (_eg_get_bit(buf, (*bit_offset)++) != 0) {
|
||||
break;
|
||||
}
|
||||
++zeros;
|
||||
}
|
||||
u32 res = (1 << zeros);
|
||||
for (; zeros; --zeros) {
|
||||
if (!_is_valid_eg_offset(*bit_offset, size)) {
|
||||
return false;
|
||||
}
|
||||
res |= (_eg_get_bit(buf, *bit_offset) << (zeros - 1));
|
||||
++(*bit_offset);
|
||||
}
|
||||
*out = res - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
int us_h264_parse_sps_nalu(const u8 *buf, uz size, uint *width, uint *height) {
|
||||
// NAL header + profile_idc + constraint_setX_flag, reserved_zero_2bits + level_idc
|
||||
if (size < 4) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// NAL header
|
||||
++buf;
|
||||
--size;
|
||||
|
||||
// profile_idc u(8) - Baseline profile
|
||||
const u8 profile_idc = *buf;
|
||||
if (profile_idc != 66) {
|
||||
return -1;
|
||||
}
|
||||
++buf;
|
||||
--size;
|
||||
|
||||
// constraint_set0_flag u(1)
|
||||
// constraint_set1_flag u(1)
|
||||
// constraint_set2_flag u(1)
|
||||
// constraint_set3_flag u(1)
|
||||
// constraint_set4_flag u(1)
|
||||
// constraint_set5_flag u(1)
|
||||
// reserved_zero_2bits u(2)
|
||||
++buf;
|
||||
--size;
|
||||
|
||||
// level_idc u(8)
|
||||
++buf;
|
||||
--size;
|
||||
|
||||
u32 bit_offset = 0;
|
||||
|
||||
// seq_parameter_set_id ue(v)
|
||||
if (!_eg_skip_ev(buf, &bit_offset, size)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// log2_max_frame_num_minus4 ue(v)
|
||||
u32 log2_max_frame_num_minus4;
|
||||
if (!_eg_decode_uev(buf, &bit_offset, size, &log2_max_frame_num_minus4)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// pic_order_cnt_type ue(v)
|
||||
u32 pic_order_cnt_type;
|
||||
if (!_eg_decode_uev(buf, &bit_offset, size, &pic_order_cnt_type)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pic_order_cnt_type == 0) {
|
||||
// log2_max_pic_order_cnt_lsb_minus4 ue(v)
|
||||
if (!_eg_skip_ev(buf, &bit_offset, size)) {
|
||||
return -1;
|
||||
}
|
||||
} else if (pic_order_cnt_type == 1) {
|
||||
// delta_pic_order_always_zero_flag u(1)
|
||||
if (!_eg_skip_u1(buf, &bit_offset, size)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// offset_for_non_ref_pic se(v)
|
||||
if (!_eg_skip_ev(buf, &bit_offset, size)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// offset_for_top_to_bottom_field se(v)
|
||||
if (!_eg_skip_ev(buf, &bit_offset, size)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// num_ref_frames_in_pic_order_cnt_cycle ue(v)
|
||||
u32 num_ref_frames_in_pic_order_cnt_cycle;
|
||||
if (!_eg_decode_uev(buf, &bit_offset, size, &num_ref_frames_in_pic_order_cnt_cycle)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; ++i) {
|
||||
// offset_for_ref_frame[i] se(v)
|
||||
if (!_eg_skip_ev(buf, &bit_offset, size)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// max_num_ref_frames ue(v)
|
||||
if (!_eg_skip_ev(buf, &bit_offset, size)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// gaps_in_frame_num_value_allowed_flag u(1)
|
||||
if (!_eg_skip_u1(buf, &bit_offset, size)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// pic_width_in_mbs_minus1 ue(v)
|
||||
u32 pic_width_in_mbs_minus1;
|
||||
if (!_eg_decode_uev(buf, &bit_offset, size, &pic_width_in_mbs_minus1)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//pic_height_in_map_units_minus1 ue(v)
|
||||
u32 pic_height_in_map_units_minus1;
|
||||
if (!_eg_decode_uev(buf, &bit_offset, size, &pic_height_in_map_units_minus1)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// frame_mbs_only_flag u(1)
|
||||
bool frame_mbs_only_flag;
|
||||
if (!_eg_decode_u1(buf, &bit_offset, size, &frame_mbs_only_flag)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!frame_mbs_only_flag) {
|
||||
// mb_adaptive_frame_field_flag u(1)
|
||||
if (!_eg_skip_u1(buf, &bit_offset, size)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
//direct_8x8_inference_flag u(1)
|
||||
if (!_eg_skip_u1(buf, &bit_offset, size)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// frame_cropping_flag u(1)
|
||||
bool frame_cropping_flag;
|
||||
if (!_eg_decode_u1(buf, &bit_offset, size, &frame_cropping_flag)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 frame_crop_left_offset = 0;
|
||||
u32 frame_crop_right_offset = 0;
|
||||
u32 frame_crop_top_offset = 0;
|
||||
u32 frame_crop_bottom_offset = 0;
|
||||
if (frame_cropping_flag) {
|
||||
// frame_crop_left_offset ue(v)
|
||||
if (!_eg_decode_uev(buf, &bit_offset, size, &frame_crop_left_offset)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// frame_crop_right_offset ue(v)
|
||||
if (!_eg_decode_uev(buf, &bit_offset, size, &frame_crop_right_offset)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// frame_crop_top_offset ue(v)
|
||||
if (!_eg_decode_uev(buf, &bit_offset, size, &frame_crop_top_offset)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// frame_crop_bottom_offset ue(v)
|
||||
if (!_eg_decode_uev(buf, &bit_offset, size, &frame_crop_bottom_offset)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
*width = (
|
||||
(pic_width_in_mbs_minus1 + 1) * 16
|
||||
- frame_crop_left_offset * 2
|
||||
- frame_crop_right_offset * 2
|
||||
);
|
||||
*height = (
|
||||
(pic_height_in_map_units_minus1 + 1) * (2 - frame_mbs_only_flag) * 16
|
||||
- frame_crop_top_offset * 2
|
||||
- frame_crop_bottom_offset * 2
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
29
janus/src/h264.h
Normal file
29
janus/src/h264.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2026 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2026 Sergey Radionov <rsatom@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "uslibs/types.h"
|
||||
|
||||
|
||||
int us_h264_parse_sps_nalu(const u8 *buf, uz size, uint *width, uint *height);
|
||||
@@ -22,14 +22,9 @@
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <jansson.h>
|
||||
@@ -37,30 +32,32 @@
|
||||
#include <janus/rtp.h>
|
||||
#include <janus/rtcp.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/const.h"
|
||||
#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/tc358743.h"
|
||||
#include "uslibs/memsink.h"
|
||||
#include "uslibs/chip.h"
|
||||
#include "uslibs/memsink.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"
|
||||
|
||||
|
||||
static const char *const default_ice_url = "stun:stun.l.google.com:19302";
|
||||
static const char *const _g_default_ice_url = "stun:stun.l.google.com:19302";
|
||||
|
||||
static us_config_s *_g_config = NULL;
|
||||
static const useconds_t _g_watchers_polling = 100000;
|
||||
@@ -68,6 +65,7 @@ static const useconds_t _g_watchers_polling = 100000;
|
||||
static us_janus_client_s *_g_clients = NULL;
|
||||
static janus_callbacks *_g_gw = NULL;
|
||||
static us_ring_s *_g_video_ring = NULL;
|
||||
static us_ring_s *_g_vplay_ring = NULL;
|
||||
static us_rtpv_s *_g_rtpv = NULL;
|
||||
static us_rtpa_s *_g_rtpa = NULL;
|
||||
|
||||
@@ -75,12 +73,15 @@ static pthread_t _g_video_rtp_tid;
|
||||
static atomic_bool _g_video_rtp_tid_created = false;
|
||||
static pthread_t _g_video_sink_tid;
|
||||
static atomic_bool _g_video_sink_tid_created = false;
|
||||
static pthread_t _g_vplay_tid;
|
||||
static atomic_bool _g_vplay_tid_created = false;
|
||||
static pthread_t _g_acap_tid;
|
||||
static atomic_bool _g_acap_tid_created = false;
|
||||
static pthread_t _g_aplay_tid;
|
||||
static atomic_bool _g_aplay_tid_created = false;
|
||||
|
||||
static pthread_mutex_t _g_video_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t _g_vplay_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t _g_acap_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t _g_aplay_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static atomic_bool _g_ready = false;
|
||||
@@ -90,18 +91,29 @@ static atomic_bool _g_has_listeners = false;
|
||||
static atomic_bool _g_has_speakers = false;
|
||||
static atomic_bool _g_key_required = false;
|
||||
|
||||
static struct {
|
||||
bool active;
|
||||
us_janus_client_s *client;
|
||||
uint width;
|
||||
uint height;
|
||||
uint fps;
|
||||
} _g_camera = {0}; // Access should be protected by _g_vplay_lock
|
||||
|
||||
|
||||
#define _LOCK_VIDEO US_MUTEX_LOCK(_g_video_lock)
|
||||
#define _UNLOCK_VIDEO US_MUTEX_UNLOCK(_g_video_lock)
|
||||
|
||||
#define _LOCK_VPLAY US_MUTEX_LOCK(_g_vplay_lock)
|
||||
#define _UNLOCK_VPLAY US_MUTEX_UNLOCK(_g_vplay_lock)
|
||||
|
||||
#define _LOCK_ACAP US_MUTEX_LOCK(_g_acap_lock)
|
||||
#define _UNLOCK_ACAP US_MUTEX_UNLOCK(_g_acap_lock)
|
||||
|
||||
#define _LOCK_APLAY US_MUTEX_LOCK(_g_aplay_lock)
|
||||
#define _UNLOCK_APLAY US_MUTEX_UNLOCK(_g_aplay_lock)
|
||||
|
||||
#define _LOCK_ALL { _LOCK_VIDEO; _LOCK_ACAP; _LOCK_APLAY; }
|
||||
#define _UNLOCK_ALL { _UNLOCK_APLAY; _UNLOCK_ACAP; _UNLOCK_VIDEO; }
|
||||
#define _LOCK_ALL { _LOCK_VIDEO; _LOCK_VPLAY; _LOCK_ACAP; _LOCK_APLAY; }
|
||||
#define _UNLOCK_ALL { _UNLOCK_APLAY; _UNLOCK_ACAP; _UNLOCK_VPLAY; _UNLOCK_VIDEO; }
|
||||
|
||||
#define _READY atomic_load(&_g_ready)
|
||||
#define _STOP atomic_load(&_g_stop)
|
||||
@@ -136,115 +148,66 @@ 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 us_memsink_wants_s w_put = {.key = atomic_load(&_g_key_required)};
|
||||
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);
|
||||
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) {
|
||||
usleep(1000);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
(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);
|
||||
assert(_g_rtpa != NULL);
|
||||
US_A(us_str_is_ok(_g_config->acap_dev_name));
|
||||
US_A(_g_rtpa != NULL);
|
||||
|
||||
int once = 0;
|
||||
|
||||
@@ -254,21 +217,34 @@ static void *_acap_thread(void *arg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint hz = 0;
|
||||
int chip_fd = -1;
|
||||
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;
|
||||
|
||||
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) {
|
||||
goto close_acap;
|
||||
}
|
||||
@@ -276,10 +252,10 @@ static void *_acap_thread(void *arg) {
|
||||
once = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
@@ -294,6 +270,7 @@ static void *_acap_thread(void *arg) {
|
||||
|
||||
close_acap:
|
||||
US_DELETE(acap, us_acap_destroy);
|
||||
US_CLOSE_FD(chip_fd);
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
return NULL;
|
||||
@@ -301,10 +278,10 @@ 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);
|
||||
US_A(us_str_is_ok(_g_config->aplay_dev_name));
|
||||
|
||||
int once = 0;
|
||||
|
||||
@@ -330,10 +307,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 +322,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 +342,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 +356,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,12 +380,195 @@ 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;
|
||||
}
|
||||
|
||||
static void _unsafe_camera_push_event(const char *str) {
|
||||
json_t *const json_event_type = json_string(str);
|
||||
|
||||
json_t *const camera = json_object();
|
||||
json_object_set_new(camera, "action", json_event_type);
|
||||
|
||||
json_t *const result = json_object();
|
||||
json_object_set_new(result, "ustreamer", json_string("event"));
|
||||
json_object_set_new(result, "status", json_string("camera"));
|
||||
json_object_set_new(result, "camera", camera);
|
||||
|
||||
json_t *const event = json_object();
|
||||
json_object_set_new(event, "result", result);
|
||||
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
_g_gw->push_event(client->session, create(), NULL, event, NULL);
|
||||
});
|
||||
|
||||
json_decref(event);
|
||||
}
|
||||
|
||||
static void _camera_push_online(uint width, uint height, uint fps) {
|
||||
bool again = false;
|
||||
_LOCK_VPLAY;
|
||||
if (_g_camera.active) {
|
||||
_unsafe_camera_push_event("offline");
|
||||
again = true;
|
||||
}
|
||||
const uint old_width = _g_camera.width;
|
||||
const uint old_height = _g_camera.height;
|
||||
const uint old_fps = _g_camera.fps;
|
||||
_g_camera.active = true;
|
||||
_g_camera.width = width;
|
||||
_g_camera.height = height;
|
||||
_g_camera.fps = fps;
|
||||
_unsafe_camera_push_event("online");
|
||||
_UNLOCK_VPLAY;
|
||||
if (again) {
|
||||
US_LOG_INFO("Camera changed: %ux%u@%u -> %ux%u@%u",
|
||||
old_width, old_height, old_fps, width, height, fps);
|
||||
} else {
|
||||
US_LOG_INFO("Camera online: %ux%u@%u", width, height, fps);
|
||||
}
|
||||
}
|
||||
|
||||
static void _camera_push_offline() {
|
||||
_LOCK_VPLAY;
|
||||
if (_g_camera.active) {
|
||||
_g_camera.active = false;
|
||||
_unsafe_camera_push_event("offline");
|
||||
US_LOG_INFO("Camera offline");
|
||||
}
|
||||
_UNLOCK_VPLAY;
|
||||
}
|
||||
|
||||
static void _camera_push_lost() {
|
||||
_LOCK_VPLAY;
|
||||
if (_g_camera.active) {
|
||||
_unsafe_camera_push_event("lost");
|
||||
US_LOG_INFO("Camera's source client is lost");
|
||||
}
|
||||
_UNLOCK_VPLAY;
|
||||
}
|
||||
|
||||
static void *_vplay_thread(void *arg) {
|
||||
(void)arg;
|
||||
US_THREAD_SETTLE("us_p_vplay");
|
||||
atomic_store(&_g_vplay_tid_created, true);
|
||||
|
||||
US_A(us_str_is_ok(_g_config->vplay_sink_name));
|
||||
|
||||
us_frame_s *frame = us_frame_init();
|
||||
|
||||
while (!_STOP) {
|
||||
us_memsink_s* sink = us_memsink_init_opened(
|
||||
"H264-CAM",
|
||||
_g_config->vplay_sink_name,
|
||||
true, // Server
|
||||
_g_config->vplay_sink_mode,
|
||||
false, // Don't remote sink on destroy
|
||||
1, // Client TTL
|
||||
1); // Timeout
|
||||
if (sink == NULL) {
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
const ldf memsink_check_interval = 1;
|
||||
ldf last_memsink_check_time = 0;
|
||||
|
||||
int once = 0;
|
||||
while (!_STOP) {
|
||||
_LOCK_VPLAY;
|
||||
const bool c_active = _g_camera.active;
|
||||
const uint c_width = _g_camera.width;
|
||||
const uint c_height = _g_camera.height;
|
||||
_UNLOCK_VPLAY;
|
||||
|
||||
if (!c_active) {
|
||||
frame->used = 0;
|
||||
}
|
||||
|
||||
if (frame->used > 0 && (c_width != frame->width || c_height != frame->height)) {
|
||||
US_ONCE({ US_LOG_INFO("Got WebRTC frame with wrong resolution"); });
|
||||
frame->used = 0;
|
||||
}
|
||||
|
||||
const ldf now_ts = us_get_now_monotonic();
|
||||
if (frame->used > 0 || last_memsink_check_time + memsink_check_interval < now_ts) {
|
||||
last_memsink_check_time = now_ts;
|
||||
|
||||
us_memsink_wants_s w_get = {0};
|
||||
switch (us_memsink_server_x_lock(sink, &w_get)) {
|
||||
case US_MSS_SUCCESS:
|
||||
if (w_get.format == V4L2_PIX_FMT_H264) {
|
||||
if (w_get.width == c_width && w_get.height == c_height) {
|
||||
if (us_memsink_server_x_is_consumed(sink)) {
|
||||
if (!c_active) {
|
||||
_camera_push_online(w_get.width, w_get.height, w_get.fps);
|
||||
} else if (frame->used > 0) {
|
||||
US_ONCE({ US_LOG_INFO("Streaming to the camera ..."); });
|
||||
us_memsink_server_x_put(sink, frame);
|
||||
frame->used = 0;
|
||||
}
|
||||
} else {
|
||||
if (!c_active) {
|
||||
us_memsink_server_x_set_consumed(sink);
|
||||
}
|
||||
}
|
||||
} else { // Notify to changed resolution
|
||||
_camera_push_online(w_get.width, w_get.height, w_get.fps);
|
||||
}
|
||||
} else {
|
||||
US_ONCE({ US_LOG_ERROR("Got invalid format from the camera: %u", w_get.format); });
|
||||
}
|
||||
if (us_memsink_server_x_unlock(sink) < 0) {
|
||||
goto close_memsink;
|
||||
}
|
||||
break;
|
||||
|
||||
case US_MSS_NO_CLIENT:
|
||||
_camera_push_offline();
|
||||
break;
|
||||
|
||||
case US_MSS_BUSY: // Busy by some client
|
||||
break;
|
||||
|
||||
case US_MSS_ERROR:
|
||||
default:
|
||||
goto close_memsink;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame->used == 0) { // Frame sent (or discarded), get a new one from a client
|
||||
const int in_ri = us_ring_consumer_acquire(_g_vplay_ring, 0.1);
|
||||
if (in_ri >= 0) {
|
||||
us_frame_s *tmp_frame = frame;
|
||||
frame = _g_vplay_ring->items[in_ri];
|
||||
_g_vplay_ring->items[in_ri] = tmp_frame;
|
||||
us_ring_consumer_release(_g_vplay_ring, in_ri);
|
||||
/*} else { // FIXME: Leads to spam with "Streaming to the camera ..."
|
||||
US_ONCE({
|
||||
_LOCK_VPLAY;
|
||||
if (_g_camera.active) {
|
||||
US_LOG_INFO("No frames have got from WebRTC");
|
||||
}
|
||||
_UNLOCK_VPLAY;
|
||||
});*/
|
||||
}
|
||||
} else {
|
||||
usleep(1000); // Don't iterate too frequently
|
||||
}
|
||||
}
|
||||
|
||||
close_memsink:
|
||||
US_DELETE(sink, us_memsink_destroy);
|
||||
US_LOG_INFO("Memsink closed");
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
us_frame_destroy(frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void _relay_rtp_clients(const us_rtp_s *rtp) {
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
us_janus_client_send(client, rtp);
|
||||
@@ -430,8 +590,13 @@ 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);
|
||||
if (gw == NULL || config_dir_path == NULL || ((_g_config = us_config_init(config_dir_path)) == NULL)) {
|
||||
US_LOGGING_INIT;
|
||||
|
||||
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;
|
||||
}
|
||||
_g_gw = gw;
|
||||
@@ -440,13 +605,21 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
|
||||
|
||||
US_RING_INIT_WITH_ITEMS(_g_video_ring, 64, us_frame_init);
|
||||
_g_rtpv = us_rtpv_init(_relay_rtp_clients);
|
||||
if (_g_config->acap_dev_name != NULL) {
|
||||
|
||||
if (us_str_is_ok(_g_config->vplay_sink_name)) {
|
||||
US_RING_INIT_WITH_ITEMS(_g_vplay_ring, 15, us_frame_init);
|
||||
US_THREAD_CREATE(_g_vplay_tid, _vplay_thread, NULL);
|
||||
}
|
||||
|
||||
if (us_str_is_ok(_g_config->acap_dev_name)) {
|
||||
_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 (us_str_is_ok(_g_config->aplay_dev_name)) {
|
||||
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,32 +628,39 @@ 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); } }
|
||||
JOIN(_g_aplay_tid);
|
||||
JOIN(_g_acap_tid);
|
||||
JOIN(_g_vplay_tid);
|
||||
JOIN(_g_video_sink_tid);
|
||||
JOIN(_g_video_rtp_tid);
|
||||
JOIN(_g_acap_tid);
|
||||
JOIN(_g_aplay_tid);
|
||||
# undef JOIN
|
||||
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (_g_camera.client == client) {
|
||||
_g_camera.client = NULL;
|
||||
}
|
||||
US_LIST_REMOVE(_g_clients, client);
|
||||
us_janus_client_destroy(client);
|
||||
});
|
||||
|
||||
US_RING_DELETE_WITH_ITEMS(_g_vplay_ring, us_frame_destroy);
|
||||
US_RING_DELETE_WITH_ITEMS(_g_video_ring, us_frame_destroy);
|
||||
|
||||
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);
|
||||
@@ -494,9 +674,14 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
|
||||
bool has_watchers = false;
|
||||
bool has_listeners = false;
|
||||
bool has_speakers = false;
|
||||
bool camera_lost = 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);
|
||||
if (_g_camera.client == client) {
|
||||
_g_camera.client = NULL;
|
||||
camera_lost = true;
|
||||
}
|
||||
US_LIST_REMOVE(_g_clients, client);
|
||||
us_janus_client_destroy(client);
|
||||
found = true;
|
||||
@@ -507,13 +692,16 @@ 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);
|
||||
atomic_store(&_g_has_listeners, has_listeners);
|
||||
atomic_store(&_g_has_speakers, has_speakers);
|
||||
_UNLOCK_ALL;
|
||||
if (camera_lost) {
|
||||
_camera_push_lost();
|
||||
}
|
||||
}
|
||||
|
||||
static json_t *_plugin_query_session(janus_plugin_session *session) {
|
||||
@@ -539,13 +727,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;
|
||||
@@ -554,6 +742,16 @@ static void _set_transmit(janus_plugin_session *session, const char *msg, bool t
|
||||
static void _plugin_setup_media(janus_plugin_session *session) { _set_transmit(session, "Unmuted", true); }
|
||||
static void _plugin_hangup_media(janus_plugin_session *session) { _set_transmit(session, "Muted", false); }
|
||||
|
||||
static bool _is_camera_enabled(void) {
|
||||
bool enabled = us_str_is_ok(_g_config->vplay_sink_name);
|
||||
if (enabled && us_str_is_ok(_g_config->vplay_dev_path)) {
|
||||
if (access(_g_config->vplay_dev_path, F_OK) != 0) {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
static struct janus_plugin_result *_plugin_handle_message(
|
||||
janus_plugin_session *session, char *transaction, json_t *msg, json_t *jsep) {
|
||||
|
||||
@@ -567,7 +765,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 +785,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 +810,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", _is_camera_enabled());
|
||||
# undef READ_BOOL
|
||||
{
|
||||
json_t *const obj = json_object_get(params, "orientation");
|
||||
if (obj != NULL && json_is_integer(obj)) {
|
||||
@@ -640,44 +837,32 @@ 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;
|
||||
with_vplay = (with_vplay && _g_camera.active && !_g_camera.client);
|
||||
|
||||
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);
|
||||
if (with_vplay) {
|
||||
us_janus_client_start_vplay(client, _g_vplay_ring);
|
||||
_g_camera.client = client;
|
||||
}
|
||||
}
|
||||
has_listeners = (has_listeners || atomic_load(&client->transmit_acap));
|
||||
has_speakers = (has_speakers || atomic_load(&client->transmit_aplay));
|
||||
@@ -689,18 +874,36 @@ 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);
|
||||
|
||||
_LOCK_VPLAY
|
||||
json_t *camera_req = NULL;
|
||||
if (_g_camera.active && _g_camera.client == NULL) {
|
||||
json_t *resolution = json_object();
|
||||
json_object_set_new(resolution, "width", json_integer(_g_camera.width));
|
||||
json_object_set_new(resolution, "height", json_integer(_g_camera.height));
|
||||
|
||||
camera_req = json_object();
|
||||
json_object_set_new(camera_req, "resolution", resolution);
|
||||
json_object_set_new(camera_req, "fps", json_integer(_g_camera.fps));
|
||||
}
|
||||
_UNLOCK_VPLAY
|
||||
|
||||
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)),
|
||||
"ice", "url", (ice_url != NULL ? ice_url : default_ice_url)
|
||||
"{s:b, s:b, s:{s:b, s:o*}, s:{s:s?}}",
|
||||
"audio", us_au_probe(_g_config->acap_dev_name),
|
||||
"mic", us_au_probe(_g_config->aplay_dev_name),
|
||||
"camera",
|
||||
"enabled", _is_camera_enabled(),
|
||||
"request", camera_req,
|
||||
"ice",
|
||||
"url", (ice_url != NULL ? ice_url : _g_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 {
|
||||
@@ -722,28 +925,60 @@ done:
|
||||
|
||||
static void _plugin_incoming_rtp(janus_plugin_session *session, janus_plugin_rtp *packet) {
|
||||
_IF_DISABLED({ return; });
|
||||
if (session == NULL || packet == NULL || packet->video) {
|
||||
return; // Accept only valid audio
|
||||
if (session == NULL || packet == NULL) {
|
||||
return; // Accept only valid packets
|
||||
}
|
||||
_LOCK_APLAY;
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
us_janus_client_recv(client, packet);
|
||||
break;
|
||||
if (packet->video) {
|
||||
_LOCK_VPLAY;
|
||||
if (_g_camera.client != NULL && _g_camera.client->session == session) {
|
||||
us_janus_client_recv(_g_camera.client, packet);
|
||||
}
|
||||
});
|
||||
_UNLOCK_APLAY;
|
||||
_UNLOCK_VPLAY;
|
||||
} else {
|
||||
_LOCK_APLAY;
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
us_janus_client_recv(client, packet);
|
||||
break;
|
||||
}
|
||||
});
|
||||
_UNLOCK_APLAY;
|
||||
}
|
||||
}
|
||||
|
||||
static void _plugin_incoming_rtcp(janus_plugin_session *session, janus_plugin_rtcp *packet) {
|
||||
_IF_DISABLED({ return; });
|
||||
if (session == NULL || packet == NULL || !packet->video) {
|
||||
return; // Accept only valid video
|
||||
if (
|
||||
session == NULL || packet == NULL || !packet->video
|
||||
|| packet->length < sizeof(janus_rtcp_header)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
janus_rtcp_header *header = (janus_rtcp_header *)packet->buffer;
|
||||
|
||||
if (header->type == RTCP_SR && janus_rtcp_check_sr(header, packet->length)) {
|
||||
const janus_rtcp_sr *const sr = (janus_rtcp_sr *)header;
|
||||
|
||||
if (sr->si.ntp_ts_msw && sr->si.ntp_ts_lsw) {
|
||||
_LOCK_VPLAY;
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
const ldf ntp_ts = ntohl(sr->si.ntp_ts_msw) + ntohl(sr->si.ntp_ts_lsw) / 4294967296.L;
|
||||
us_rtpc_sync_timestamp(client->rtpc, ntp_ts, ntohl(sr->si.rtp_ts));
|
||||
break;
|
||||
}
|
||||
});
|
||||
_UNLOCK_VPLAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,19 +48,27 @@
|
||||
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;
|
||||
bool last_of_frame;
|
||||
bool zero_playout_delay;
|
||||
u64 grab_ntp_ts;
|
||||
} us_rtp_s;
|
||||
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
334
janus/src/rtpc.c
Normal file
334
janus/src/rtpc.c
Normal file
@@ -0,0 +1,334 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2026 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2026 Sergey Radionov <rsatom@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# 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 "rtpc.h"
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "uslibs/logging.h"
|
||||
|
||||
#include "h264.h"
|
||||
|
||||
|
||||
// H.264 fragment types. Commented types are not used in WebRTC.
|
||||
enum {
|
||||
_FR_REGULAR_NAL_MIN = 1,
|
||||
_FR_REGULAR_NAL_MAX = 23,
|
||||
_FR_STAP_A = 24, // Single Time Aggregation Packet, Type A (STAP-A)
|
||||
// _FR_STAP_B = 25, // Single Time Aggregation Packet, Type B (STAP-B)
|
||||
// _FR_MTAP_16 = 26, // Multi-Time Aggregation Packet, Type A (MTAP-16)
|
||||
// _FR_MTAP_24 = 27, // Multi-Time Aggregation Packet, Type B (MTAP-24)
|
||||
_FR_FU_A = 28, // Fragmentation unit (FU-A)
|
||||
// _FR_FU_B = 29, // Fragmentation unit (FU-B)
|
||||
};
|
||||
|
||||
enum {
|
||||
_NALU_IDR = 5,
|
||||
_NALU_SPS = 7,
|
||||
_NALU_PPS = 8,
|
||||
};
|
||||
|
||||
|
||||
static int _update_sequence_for_new_packet(us_rtpc_s *rtpc, u16 seq);
|
||||
static ldf _rtp_to_local_ts(us_rtpc_s *rtpc, u32 rts);
|
||||
static void _update_timestamps_for_new_packet(us_rtpc_s *rtpc, u32 rts);
|
||||
|
||||
static void _unwrapping_begin(us_rtpc_s *rtpc);
|
||||
static void _unwrapping_end(us_rtpc_s *rtpc);
|
||||
static void _unwrap_whole(us_rtpc_s *rtpc, const u8 *data, u16 size);
|
||||
|
||||
|
||||
us_rtpc_s *us_rtpc_init(us_frame_callback_f callback, void *user_data) {
|
||||
us_rtpc_s *rtpc;
|
||||
US_CALLOC(rtpc, 1);
|
||||
rtpc->user_data = user_data;
|
||||
rtpc->frame = us_frame_init();
|
||||
rtpc->callback = callback;
|
||||
return rtpc;
|
||||
}
|
||||
|
||||
void us_rtpc_destroy(us_rtpc_s *rtpc) {
|
||||
us_frame_destroy(rtpc->frame);
|
||||
free(rtpc);
|
||||
}
|
||||
|
||||
us_rtpc_unwrap_result_e us_rtpc_unwrap(
|
||||
us_rtpc_s *rtpc,
|
||||
const u8 *payload,
|
||||
int payload_size,
|
||||
u16 seq,
|
||||
u32 rts
|
||||
) {
|
||||
US_A(payload != NULL);
|
||||
US_A(payload_size >= 1);
|
||||
|
||||
if (_update_sequence_for_new_packet(rtpc, seq) < 0) {
|
||||
return US_RUR_INVALID_SEQ;
|
||||
}
|
||||
_update_timestamps_for_new_packet(rtpc, rts);
|
||||
|
||||
rtpc->had_packets = true;
|
||||
|
||||
const u8 fragment_type = payload[0] & 0x1F;
|
||||
|
||||
if (rtpc->fu && (rtpc->fu_rts != rts || fragment_type != _FR_FU_A)) {
|
||||
rtpc->fu = false;
|
||||
if (!rtpc->fu_is_bad) { // Not reported yet
|
||||
rtpc->skip_until_key = true;
|
||||
return US_RUR_DEPACKETIZATION_FAILED_RETRY;
|
||||
}
|
||||
}
|
||||
|
||||
if (fragment_type >= _FR_REGULAR_NAL_MIN && fragment_type <= _FR_REGULAR_NAL_MAX) {
|
||||
_unwrap_whole(rtpc, payload, payload_size);
|
||||
|
||||
} else if (fragment_type == _FR_STAP_A) { // Single Time Aggregation Packet, Type A (STAP-A).
|
||||
const u8 *aggregated_unit = payload + 1; // skip aggregation header (1 byte)
|
||||
payload_size -= 1;
|
||||
|
||||
while (payload_size > 0) {
|
||||
if (payload_size < 2) { // Aggregated NAL unit size (2 bytes) is missing
|
||||
US_LOG_ERROR("Aggregated NALU size is missing: seq=%u, rts=%u", seq, rts);
|
||||
return US_RUR_BAD_PACKET;
|
||||
}
|
||||
|
||||
const u16 nalu_size = ntohs(*((const u16 *)aggregated_unit));
|
||||
aggregated_unit += 2;
|
||||
payload_size -= 2;
|
||||
|
||||
if (nalu_size == 0) {
|
||||
US_LOG_ERROR("Aggregated NALU size is zero: seq=%u, rts=%u", seq, rts);
|
||||
return US_RUR_BAD_PACKET;
|
||||
}
|
||||
|
||||
if (payload_size < nalu_size) { // aggregated unit size declared as bigger than available data size
|
||||
US_LOG_ERROR("Aggregated NALU size is too big: seq=%u, rts=%u", seq, rts);
|
||||
return US_RUR_BAD_PACKET;
|
||||
}
|
||||
|
||||
_unwrap_whole(rtpc, aggregated_unit, nalu_size);
|
||||
|
||||
aggregated_unit += nalu_size;
|
||||
payload_size -= nalu_size;
|
||||
}
|
||||
|
||||
} else if (fragment_type == _FR_FU_A) { // Fragmentation unit (FU-A).
|
||||
if (payload_size < 2) { // FU header is missing (2 bytes)
|
||||
US_LOG_ERROR("FU header is missing: seq=%u, rts=%u", seq, rts);
|
||||
return US_RUR_BAD_PACKET;
|
||||
}
|
||||
|
||||
const u8 fu_indicator = payload[0];
|
||||
const u8 fu_header = payload[1];
|
||||
const bool first_fragment = !!(fu_header & 0x80);
|
||||
const bool last_fragment = !!(fu_header & 0x40);
|
||||
|
||||
if (first_fragment && last_fragment) { // should never happen according to RFC
|
||||
return US_RUR_BAD_PACKET;
|
||||
}
|
||||
|
||||
if (rtpc->fu && rtpc->fu_is_bad) {
|
||||
return US_RUR_DEPACKETIZATION_SKIPPED;
|
||||
}
|
||||
|
||||
if (!rtpc->fu) {
|
||||
rtpc->fu = true;
|
||||
rtpc->fu_seq = seq;
|
||||
rtpc->fu_rts = rts;
|
||||
|
||||
if (!first_fragment) {
|
||||
// US_LOG_INFO("Ignoring FU without a first fragment: seq=%u, rts=%u", seq, rts);
|
||||
rtpc->fu_is_bad = true;
|
||||
rtpc->skip_until_key = true;
|
||||
return US_RUR_DEPACKETIZATION_FAILED;
|
||||
}
|
||||
rtpc->fu_is_bad = false;
|
||||
|
||||
const u8 header = (fu_indicator & 0xE0) | (fu_header & 0x1F);
|
||||
_unwrapping_begin(rtpc);
|
||||
us_frame_append_data(rtpc->frame, &header, sizeof(header));
|
||||
// - FU indicator - FU header
|
||||
us_frame_append_data(rtpc->frame, (const u8 *)payload + 2, payload_size - 2);
|
||||
|
||||
} else if (!first_fragment && (seq == rtpc->fu_seq + 1) && (rts == rtpc->fu_rts)) {
|
||||
// ^^^ Already had first fragment
|
||||
rtpc->fu_seq = seq;
|
||||
|
||||
// - FU indicator - FU header
|
||||
us_frame_append_data(rtpc->frame, (const u8 *)payload + 2, payload_size - 2);
|
||||
|
||||
if (last_fragment) {
|
||||
_unwrapping_end(rtpc);
|
||||
rtpc->fu = false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// US_LOG_ERROR("Something went wrong with FU processing: seq=%u, rts=%u", seq, rts);
|
||||
rtpc->fu_is_bad = true;
|
||||
rtpc->skip_until_key = true;
|
||||
return US_RUR_DEPACKETIZATION_FAILED;
|
||||
}
|
||||
|
||||
} else {
|
||||
US_LOG_ERROR("Unsupported video unit type=%x: seq=%u, rts=%u", fragment_type, seq, rts);
|
||||
return US_RUR_UNSUPPORTED_UNIT_TYPE;
|
||||
}
|
||||
return US_RUR_SUCCESS;
|
||||
}
|
||||
|
||||
void us_rtpc_sync_timestamp(us_rtpc_s *rtpc, ldf ntp_ts, u32 packet_rts) {
|
||||
if (!rtpc->had_packets) {
|
||||
return;
|
||||
}
|
||||
if (rtpc->reference_ntp_ts > ntp_ts) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rtpc->reference_ntp_ts == 0.L) {
|
||||
if (rtpc->reference_packet_rts < packet_rts) {
|
||||
const ldf diff = (packet_rts - rtpc->reference_packet_rts) / 90000.L;
|
||||
rtpc->reference_local_ts += diff;
|
||||
rtpc->reference_packet_rts = packet_rts;
|
||||
rtpc->reference_ntp_ts = ntp_ts;
|
||||
}
|
||||
} else {
|
||||
const ldf diff = ntp_ts - rtpc->reference_ntp_ts;
|
||||
rtpc->reference_local_ts += diff;
|
||||
rtpc->reference_packet_rts = packet_rts;
|
||||
rtpc->reference_ntp_ts = ntp_ts;
|
||||
}
|
||||
}
|
||||
|
||||
static int _update_sequence_for_new_packet(us_rtpc_s *rtpc, u16 seq) {
|
||||
if (
|
||||
rtpc->had_packets
|
||||
&& (
|
||||
rtpc->last_packet_seq == seq
|
||||
|| (rtpc->last_packet_seq < seq && seq - rtpc->last_packet_seq > (UINT16_MAX >> 1))
|
||||
|| (seq < rtpc->last_packet_seq && rtpc->last_packet_seq - seq < (UINT16_MAX >> 1))
|
||||
)
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
rtpc->last_packet_seq = seq;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ldf _rtp_to_local_ts(us_rtpc_s *rtpc, u32 rts) {
|
||||
if (rtpc->reference_packet_rts <= rts) {
|
||||
return rtpc->reference_local_ts + (rts - rtpc->reference_packet_rts) / 90000.L;
|
||||
} else {
|
||||
return rtpc->reference_local_ts - (rtpc->reference_packet_rts - rts) / 90000.L;
|
||||
}
|
||||
}
|
||||
|
||||
static void _update_timestamps_for_new_packet(us_rtpc_s *rtpc, u32 rts) {
|
||||
const ldf now_ts = us_get_now_monotonic();
|
||||
ldf ts; // Fill for any goto
|
||||
|
||||
if (!rtpc->had_packets) {
|
||||
ts = _rtp_to_local_ts(rtpc, rts); // Used in packet_update
|
||||
goto full_update;
|
||||
}
|
||||
if (rtpc->last_packet_rts != rts) { // If this is a new frame
|
||||
ts = _rtp_to_local_ts(rtpc, rts);
|
||||
if (rtpc->last_packet_rts > rts || fabsl(now_ts - ts) > .1L) {
|
||||
// ^ RTP timestamp wrapped |OR| ^ Too significant mistake
|
||||
goto full_update;
|
||||
}
|
||||
goto packet_update;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
full_update:
|
||||
rtpc->reference_local_ts = now_ts;
|
||||
rtpc->reference_packet_rts = rts;
|
||||
rtpc->reference_ntp_ts = 0.L; // Force resync
|
||||
// US_LOG_INFO("Reference timestamps reseted");
|
||||
packet_update:
|
||||
rtpc->last_packet_rts = rts;
|
||||
rtpc->frame_ts = ts;
|
||||
}
|
||||
|
||||
static void _unwrapping_begin(us_rtpc_s *rtpc) {
|
||||
us_frame_s *const frame = rtpc->frame;
|
||||
u8 *const data = frame->data;
|
||||
const uz allocated = frame->allocated;
|
||||
|
||||
// Reset the content, start from scratch
|
||||
memset(frame, 0, sizeof(us_frame_s));
|
||||
frame->data = data;
|
||||
frame->allocated = allocated;
|
||||
|
||||
static const u8 nalu_start_code[] = {0, 0, 0, 1};
|
||||
us_frame_set_data(rtpc->frame, nalu_start_code, sizeof(nalu_start_code));
|
||||
}
|
||||
|
||||
static void _unwrapping_end(us_rtpc_s *rtpc) {
|
||||
us_frame_s *const frame = rtpc->frame;
|
||||
|
||||
US_A(frame->used > 4); // NAL unit start code
|
||||
|
||||
const u8 nalu_type = (frame->data[4] & 0x1F);
|
||||
if (rtpc->skip_until_key && nalu_type != _NALU_SPS && nalu_type != _NALU_PPS) {
|
||||
if (nalu_type != _NALU_IDR) {
|
||||
frame->used = 0;
|
||||
return;
|
||||
} else {
|
||||
rtpc->skip_until_key = false;
|
||||
}
|
||||
}
|
||||
|
||||
frame->format = V4L2_PIX_FMT_H264;
|
||||
frame->width = rtpc->frame_width;
|
||||
frame->height = rtpc->frame_height;
|
||||
frame->grab_begin_ts = rtpc->frame_ts;
|
||||
frame->grab_end_ts = rtpc->frame_ts;
|
||||
|
||||
// Exchange frames
|
||||
rtpc->frame = rtpc->callback(frame, rtpc->user_data);
|
||||
US_A(rtpc->frame != NULL);
|
||||
}
|
||||
|
||||
static void _unwrap_whole(us_rtpc_s *rtpc, const u8 *data, u16 size) {
|
||||
US_A(size >= 1);
|
||||
|
||||
const u8 nalu_type = (data[0] & 0x1F);
|
||||
if (nalu_type == _NALU_SPS) {
|
||||
uint width = 0;
|
||||
uint height = 0;
|
||||
if (
|
||||
!us_h264_parse_sps_nalu(data, size, &width, &height)
|
||||
&& (rtpc->frame_width != width || rtpc->frame_height != height)
|
||||
) {
|
||||
US_LOG_INFO("Frame size updated: width=%u, height=%u", width, height);
|
||||
rtpc->frame_width = width;
|
||||
rtpc->frame_height = height;
|
||||
}
|
||||
}
|
||||
|
||||
_unwrapping_begin(rtpc);
|
||||
us_frame_append_data(rtpc->frame, data, size);
|
||||
_unwrapping_end(rtpc);
|
||||
}
|
||||
85
janus/src/rtpc.h
Normal file
85
janus/src/rtpc.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2026 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2026 Sergey Radionov <rsatom@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# 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 <janus/rtp.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/frame.h"
|
||||
|
||||
|
||||
// Should return:
|
||||
// * input frame
|
||||
// * replacement frame - to use it in following parsing
|
||||
typedef us_frame_s *(*us_frame_callback_f)(us_frame_s *frame, void *user_data);
|
||||
|
||||
typedef struct { // *_rts == [R]TP [T]ime[S]tamp
|
||||
us_frame_callback_f callback;
|
||||
void *user_data;
|
||||
|
||||
bool had_packets; // Были ли вообще какие-то пакеты когда-то, взводится один раз
|
||||
|
||||
ldf reference_local_ts; // Локальное время
|
||||
u32 reference_packet_rts;
|
||||
ldf reference_ntp_ts;
|
||||
|
||||
u16 last_packet_seq;
|
||||
u32 last_packet_rts; // Время из последнего полученного пакета
|
||||
|
||||
bool fu;
|
||||
u16 fu_seq;
|
||||
u32 fu_rts;
|
||||
bool fu_is_bad;
|
||||
|
||||
bool skip_until_key;
|
||||
|
||||
us_frame_s *frame;
|
||||
uint frame_width;
|
||||
uint frame_height;
|
||||
ldf frame_ts; // in case of fu, time of very first packet
|
||||
} us_rtpc_s;
|
||||
|
||||
typedef enum {
|
||||
US_RUR_SUCCESS = 0,
|
||||
US_RUR_BAD_PACKET = -1,
|
||||
US_RUR_INVALID_SEQ = -2,
|
||||
US_RUR_DEPACKETIZATION_FAILED = -3,
|
||||
// Incomplete FU found. Current packet not handled.
|
||||
// It's required to call us_rtpc_unwrap one else time with the same packet.
|
||||
US_RUR_DEPACKETIZATION_FAILED_RETRY = -4,
|
||||
US_RUR_DEPACKETIZATION_SKIPPED = -5,
|
||||
US_RUR_UNSUPPORTED_UNIT_TYPE = -6,
|
||||
} us_rtpc_unwrap_result_e;
|
||||
|
||||
us_rtpc_s *us_rtpc_init(us_frame_callback_f callback, void *user_data);
|
||||
void us_rtpc_destroy(us_rtpc_s *rtpc);
|
||||
|
||||
us_rtpc_unwrap_result_e us_rtpc_unwrap(
|
||||
us_rtpc_s *rtpc,
|
||||
const u8 *payload,
|
||||
int payload_size,
|
||||
u16 seq,
|
||||
u32 rts);
|
||||
|
||||
void us_rtpc_sync_timestamp(us_rtpc_s *rtpc, ldf ntp_ts, u32 packet_rts);
|
||||
196
janus/src/rtpv.c
196
janus/src/rtpv.c
@@ -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,85 +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" RN
|
||||
"a=fmtp:%u packetization-mode=1" RN
|
||||
"a=rtcp-fb:%u nack" RN
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=mid:v" RN
|
||||
"a=msid:video v" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
|
||||
"a=extmap:2 urn:3gpp:video-orientation" RN
|
||||
"a=sendonly" RN,
|
||||
pl, 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);
|
||||
|
||||
rtpv->rtp->zero_playout_delay = zero_playout_delay;
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
--size;
|
||||
}
|
||||
_rtpv_process_nalu(rtpv, data, size, pts, false);
|
||||
}
|
||||
|
||||
last_offset = offset;
|
||||
if (frame->used <= _PRE) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
|
||||
// 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.
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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->callback(rtpv->rtp);
|
||||
return;
|
||||
}
|
||||
us_rtp_s *rtp = rtpv->rtp;
|
||||
u8 *dg = rtp->datagram;
|
||||
|
||||
const uz fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
|
||||
|
||||
@@ -142,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);
|
||||
|
||||
@@ -162,25 +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->callback(rtpv->rtp);
|
||||
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
|
||||
|
||||
@@ -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
119
janus/src/sdp.c
Normal 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;
|
||||
}
|
||||
@@ -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/chip.c
Symbolic link
1
janus/src/uslibs/chip.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/chip.c
|
||||
1
janus/src/uslibs/chip.h
Symbolic link
1
janus/src/uslibs/chip.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/chip.h
|
||||
1
janus/src/uslibs/logging.c
Symbolic link
1
janus/src/uslibs/logging.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/logging.c
|
||||
1
janus/src/uslibs/logging.h
Symbolic link
1
janus/src/uslibs/logging.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/logging.h
|
||||
@@ -20,35 +20,13 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "tc358743.h"
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
#include "xioctl.h"
|
||||
#include "threading.h"
|
||||
|
||||
|
||||
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz) {
|
||||
*audio_hz = 0;
|
||||
|
||||
struct v4l2_control ctl = {.id = TC358743_CID_AUDIO_PRESENT};
|
||||
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
|
||||
return -1;
|
||||
#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__); \
|
||||
}
|
||||
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
1
janus/src/uslibs/memsink.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/memsink.c
|
||||
1
janus/src/uslibs/memsink.h
Symbolic link
1
janus/src/uslibs/memsink.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/memsink.h
|
||||
@@ -1 +0,0 @@
|
||||
../../../src/libs/tc358743.c
|
||||
@@ -1 +0,0 @@
|
||||
../../../src/libs/tc358743.h
|
||||
@@ -9,7 +9,7 @@ RUN pacman -Syu --noconfirm \
|
||||
&& pacman -S --needed --noconfirm \
|
||||
vim \
|
||||
git \
|
||||
libjpeg \
|
||||
libjpeg-turbo \
|
||||
libevent \
|
||||
libutil-linux \
|
||||
libbsd \
|
||||
|
||||
@@ -15,8 +15,6 @@ commands = cppcheck \
|
||||
--quiet \
|
||||
--check-level=exhaustive \
|
||||
--enable=warning,portability,performance,style \
|
||||
--suppress=assignmentInAssert \
|
||||
--suppress=assertWithSideEffect \
|
||||
--suppress=variableScope \
|
||||
--inline-suppr \
|
||||
--library=python \
|
||||
@@ -39,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
|
||||
|
||||
|
||||
@@ -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.47" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 6.67" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to 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.47" "November 2020"
|
||||
.TH USTREAMER 1 "version 6.67" "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
|
||||
@@ -120,6 +120,12 @@ Delay before trying to connect to the device again after an error (timeout for e
|
||||
.TP
|
||||
.BR \-\-m2m\-device\ \fI/dev/path
|
||||
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"
|
||||
.TP
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
# Contributor: Maxim Devaev <mdevaev@gmail.com>
|
||||
# Author: Maxim Devaev <mdevaev@gmail.com>
|
||||
# Maintainer: Maxim Devaev <mdevaev@gmail.com> [Software author]
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=6.47
|
||||
pkgver=6.67
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
license=(GPL)
|
||||
license=(GPL-3.0-or-later)
|
||||
arch=(i686 x86_64 armv6h armv7h aarch64)
|
||||
depends=(libjpeg libevent libbsd libgpiod systemd)
|
||||
makedepends=(gcc make pkgconf systemd)
|
||||
depends=(libjpeg-turbo libevent libbsd libgpiod systemd)
|
||||
makedepends=(git)
|
||||
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
||||
md5sums=(SKIP)
|
||||
|
||||
@@ -19,7 +18,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-build python-wheel python-installer)
|
||||
fi
|
||||
if [ -e /usr/include/janus/plugins/plugin.h ];then
|
||||
depends+=(janus-gateway alsa-lib opus)
|
||||
|
||||
@@ -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.47
|
||||
PKG_VERSION:=6.67
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -34,7 +34,7 @@ def main() -> None:
|
||||
flags = _find_flags()
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="6.47",
|
||||
version="6.67",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
@@ -63,7 +63,7 @@ typedef struct {
|
||||
|
||||
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
|
||||
if (self->mem != NULL) {
|
||||
us_memsink_shared_unmap(self->mem, self->data_size);
|
||||
us_memsinksh_unmap(self->mem, self->data_size);
|
||||
self->mem = NULL;
|
||||
}
|
||||
US_CLOSE_FD(self->fd);
|
||||
@@ -94,7 +94,7 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
|
||||
SET_DOUBLE(drop_same_frames, >= 0);
|
||||
# undef SET_DOUBLE
|
||||
|
||||
if ((self->data_size = us_memsink_calculate_size(self->obj)) == 0) {
|
||||
if ((self->data_size = us_memsinksh_calculate_size(self->obj)) == 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "Invalid memsink object suffix");
|
||||
return -1;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
if ((self->mem = us_memsink_shared_map(self->fd, self->data_size)) == NULL) {
|
||||
if ((self->mem = us_memsinksh_map(self->fd, self->data_size)) == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
@@ -165,6 +165,7 @@ static int _wait_frame(_MemsinkObject *self) {
|
||||
|
||||
// Let the sink know that the client is alive
|
||||
mem->last_client_ts = now_ts;
|
||||
mem->client_magic = US_MEMSINK_MAGIC;
|
||||
|
||||
if (mem->id == self->frame_id) {
|
||||
goto retry;
|
||||
@@ -174,7 +175,7 @@ static int _wait_frame(_MemsinkObject *self) {
|
||||
if (
|
||||
US_FRAME_COMPARE_GEOMETRY(self->mem, self->frame)
|
||||
&& (self->frame_ts + self->drop_same_frames > now_ts)
|
||||
&& !memcmp(self->frame->data, us_memsink_get_data(mem), mem->used)
|
||||
&& !memcmp(self->frame->data, us_memsinksh_get_data(mem), mem->used)
|
||||
) {
|
||||
self->frame_id = mem->id;
|
||||
goto retry;
|
||||
@@ -225,12 +226,12 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
|
||||
}
|
||||
|
||||
us_memsink_shared_s *mem = self->mem;
|
||||
us_frame_set_data(self->frame, us_memsink_get_data(mem), mem->used);
|
||||
us_frame_set_data(self->frame, us_memsinksh_get_data(mem), mem->used);
|
||||
US_FRAME_COPY_META(self->mem, self->frame);
|
||||
self->frame_id = mem->id;
|
||||
self->frame_ts = us_get_now_monotonic();
|
||||
if (key_required) {
|
||||
mem->key_requested = true;
|
||||
mem->wants.key = true;
|
||||
}
|
||||
|
||||
if (flock(self->fd, LOCK_UN) < 0) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <float.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/errors.h"
|
||||
@@ -178,14 +177,14 @@ int main(int argc, char *argv[]) {
|
||||
# undef OPT_NUMBER
|
||||
# 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.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
_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) {
|
||||
return 1;
|
||||
}
|
||||
@@ -237,8 +236,9 @@ static int _dump_sink(
|
||||
long double last_ts = 0;
|
||||
|
||||
while (!_g_stop) {
|
||||
bool key_requested;
|
||||
const int got = us_memsink_client_get(sink, frame, &key_requested, key_required);
|
||||
us_memsink_wants_s w_get = {0};
|
||||
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) {
|
||||
key_required = false;
|
||||
|
||||
@@ -253,7 +253,7 @@ static int _dump_sink(
|
||||
frame->height,
|
||||
frame->gop,
|
||||
frame->key,
|
||||
key_requested,
|
||||
w_get.key,
|
||||
frame->grab_end_ts - frame->grab_begin_ts,
|
||||
frame->encode_begin_ts - frame->grab_end_ts,
|
||||
frame->encode_end_ts - frame->encode_begin_ts,
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#define US_ARRAY_LEN(x_array) (sizeof(x_array) / sizeof((x_array)[0]))
|
||||
|
||||
#define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \
|
||||
const int m_len = US_ARRAY_LEN(x_array); \
|
||||
assert(x_start <= m_len); \
|
||||
US_A(x_start <= m_len); \
|
||||
for (int m_i = x_start; m_i < m_len; ++m_i) { \
|
||||
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_i]; \
|
||||
__VA_ARGS__ \
|
||||
|
||||
@@ -30,15 +30,16 @@
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/select.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
#include <linux/media.h>
|
||||
#include <linux/v4l2-subdev.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "errors.h"
|
||||
@@ -48,7 +49,8 @@
|
||||
#include "threading.h"
|
||||
#include "frame.h"
|
||||
#include "xioctl.h"
|
||||
#include "tc358743.h"
|
||||
#include "media.h"
|
||||
#include "chip.h"
|
||||
|
||||
|
||||
static const struct {
|
||||
@@ -64,18 +66,19 @@ static const struct {
|
||||
static const struct {
|
||||
const char *name; // cppcheck-suppress unusedStructMember
|
||||
const uint format; // cppcheck-suppress unusedStructMember
|
||||
const uint media_format; // cppcheck-suppress unusedStructMember
|
||||
} _FORMATS[] = {
|
||||
{"YUYV", V4L2_PIX_FMT_YUYV},
|
||||
{"YVYU", V4L2_PIX_FMT_YVYU},
|
||||
{"UYVY", V4L2_PIX_FMT_UYVY},
|
||||
{"YUV420", V4L2_PIX_FMT_YUV420},
|
||||
{"YVU420", V4L2_PIX_FMT_YVU420},
|
||||
{"GREY", V4L2_PIX_FMT_GREY},
|
||||
{"RGB565", V4L2_PIX_FMT_RGB565},
|
||||
{"RGB24", V4L2_PIX_FMT_RGB24},
|
||||
{"BGR24", V4L2_PIX_FMT_BGR24},
|
||||
{"MJPEG", V4L2_PIX_FMT_MJPEG},
|
||||
{"JPEG", V4L2_PIX_FMT_JPEG},
|
||||
{"YUYV", V4L2_PIX_FMT_YUYV, MEDIA_BUS_FMT_YUYV8_1X16},
|
||||
{"YVYU", V4L2_PIX_FMT_YVYU, MEDIA_BUS_FMT_YVYU8_1X16},
|
||||
{"UYVY", V4L2_PIX_FMT_UYVY, MEDIA_BUS_FMT_UYVY8_1X16},
|
||||
{"YUV420", V4L2_PIX_FMT_YUV420, MEDIA_BUS_FMT_YUV8_1X24},
|
||||
{"YVU420", V4L2_PIX_FMT_YVU420, 0},
|
||||
{"GREY", V4L2_PIX_FMT_GREY, MEDIA_BUS_FMT_Y8_1X8},
|
||||
{"RGB565", V4L2_PIX_FMT_RGB565, MEDIA_BUS_FMT_RGB565_1X16},
|
||||
{"RGB24", V4L2_PIX_FMT_RGB24, MEDIA_BUS_FMT_RGB888_1X24},
|
||||
{"BGR24", V4L2_PIX_FMT_BGR24, MEDIA_BUS_FMT_BGR888_1X24},
|
||||
{"MJPEG", V4L2_PIX_FMT_MJPEG, 0},
|
||||
{"JPEG", V4L2_PIX_FMT_JPEG, MEDIA_BUS_FMT_JPEG_1X8},
|
||||
};
|
||||
|
||||
static const struct {
|
||||
@@ -87,10 +90,12 @@ static const struct {
|
||||
};
|
||||
|
||||
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 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_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_format(us_capture_s *cap, bool first);
|
||||
static void _capture_open_hw_fps(us_capture_s *cap);
|
||||
@@ -102,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_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_supported(uint format);
|
||||
static const char *_standard_to_string(v4l2_std_id standard);
|
||||
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__)
|
||||
@@ -136,10 +125,14 @@ us_capture_s *us_capture_init(void) {
|
||||
us_capture_runtime_s *run;
|
||||
US_CALLOC(run, 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_CALLOC(cap, 1);
|
||||
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->height = 480;
|
||||
cap->format = V4L2_PIX_FMT_YUYV;
|
||||
@@ -149,11 +142,13 @@ us_capture_s *us_capture_init(void) {
|
||||
cap->n_bufs = us_get_cores_available() + 1;
|
||||
cap->min_frame_size = 128;
|
||||
cap->timeout = 1;
|
||||
cap->ctl = us_controls_init();
|
||||
cap->run = run;
|
||||
return cap;
|
||||
}
|
||||
|
||||
void us_capture_destroy(us_capture_s *cap) {
|
||||
us_controls_destroy(cap->ctl);
|
||||
free(cap->run);
|
||||
free(cap);
|
||||
}
|
||||
@@ -202,12 +197,20 @@ int us_capture_open(us_capture_s *cap) {
|
||||
}
|
||||
_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) {
|
||||
struct v4l2_control ctl = {.id = V4L2_CID_DV_RX_POWER_PRESENT};
|
||||
if (!us_xioctl(run->fd, VIDIOC_G_CTRL, &ctl)) {
|
||||
if (!ctl.value) {
|
||||
goto error_no_cable;
|
||||
}
|
||||
_LOG_DEBUG("Probing the cable ...")
|
||||
switch (us_chip_check_cable(run->fd)) {
|
||||
case 0: break;
|
||||
case US_ERROR_NO_CABLE: goto error_no_cable;
|
||||
}
|
||||
_LOG_DEBUG("Probing DV-timings or QuerySTD ...");
|
||||
switch (_capture_open_dv_timings(cap, false)) {
|
||||
@@ -229,16 +232,20 @@ int us_capture_open(us_capture_s *cap) {
|
||||
if (cap->dv_timings && _capture_open_dv_timings(cap, true) < 0) {
|
||||
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) {
|
||||
goto error;
|
||||
}
|
||||
if (cap->dv_timings && cap->persistent) {
|
||||
struct v4l2_control ctl = {.id = TC358743_CID_LANES_ENOUGH};
|
||||
if (!us_xioctl(run->fd, VIDIOC_G_CTRL, &ctl)) {
|
||||
if (!ctl.value) {
|
||||
_LOG_ERROR("Not enough lanes, hardware can't handle this signal");
|
||||
goto error_no_lanes;
|
||||
}
|
||||
switch (us_chip_tc358743_check_lanes(run->fd)) {
|
||||
case 0: break;
|
||||
case US_ERROR_NO_LANES: goto error_no_lanes;
|
||||
}
|
||||
}
|
||||
_capture_open_hw_fps(cap);
|
||||
@@ -256,7 +263,7 @@ int us_capture_open(us_capture_s *cap) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
_capture_apply_controls(cap);
|
||||
us_controls_apply(cap->ctl, cap->run->fd);
|
||||
|
||||
enum v4l2_buf_type type = run->capture_type;
|
||||
if (us_xioctl(run->fd, VIDIOC_STREAMON, &type) < 0) {
|
||||
@@ -288,6 +295,7 @@ error_no_sync:
|
||||
return US_ERROR_NO_SYNC;
|
||||
|
||||
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);
|
||||
return US_ERROR_NO_LANES;
|
||||
|
||||
@@ -338,7 +346,9 @@ void us_capture_close(us_capture_s *cap) {
|
||||
run->n_bufs = 0;
|
||||
}
|
||||
|
||||
US_ARRAY_ITERATE(run->media_pads, 0, pad, { US_CLOSE_FD(pad->fd); });
|
||||
US_CLOSE_FD(run->fd);
|
||||
run->dv_timings_fd = -1; // It's just a copy of the media or device descriptor
|
||||
|
||||
if (say) {
|
||||
_LOG_INFO("Capturing stopped");
|
||||
@@ -471,7 +481,7 @@ int us_capture_hwbuf_grab(us_capture_s *cap, us_capture_hwbuf_s **hw) {
|
||||
}
|
||||
|
||||
int us_capture_hwbuf_release(const us_capture_s *cap, us_capture_hwbuf_s *hw) {
|
||||
assert(atomic_load(&hw->refs) == 0);
|
||||
US_A(atomic_load(&hw->refs) == 0);
|
||||
const uint i = hw->buf.index;
|
||||
_LOG_DEBUG("Releasing HW buffer=%u ...", i);
|
||||
if (us_xioctl(cap->run->fd, VIDIOC_QBUF, &hw->buf) < 0) {
|
||||
@@ -499,6 +509,7 @@ int _capture_wait_buffer(us_capture_s *cap) {
|
||||
INIT_FD_SET(read_fds);
|
||||
INIT_FD_SET(error_fds);
|
||||
# undef INIT_FD_SET
|
||||
FD_SET(run->dv_timings_fd, &error_fds);
|
||||
|
||||
// Раньше мы проверяли и has_write, но потом выяснилось, что libcamerify зачем-то
|
||||
// генерирует эвенты на запись, вероятно ошибочно. Судя по всему, игнорирование
|
||||
@@ -512,10 +523,16 @@ int _capture_wait_buffer(us_capture_s *cap) {
|
||||
|
||||
bool has_read = 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) {
|
||||
has_read = FD_ISSET(run->fd, &read_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);
|
||||
|
||||
@@ -528,16 +545,19 @@ int _capture_wait_buffer(us_capture_s *cap) {
|
||||
_LOG_ERROR("Device select() timeout");
|
||||
return -1;
|
||||
} 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 0;
|
||||
}
|
||||
|
||||
static int _capture_consume_event(const us_capture_s *cap) {
|
||||
static int _v4l2_consume_event(int fd) {
|
||||
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");
|
||||
return -1;
|
||||
}
|
||||
@@ -556,7 +576,7 @@ static void _v4l2_buffer_copy(const struct v4l2_buffer *src, struct v4l2_buffer
|
||||
struct v4l2_plane *dest_planes = dest->m.planes;
|
||||
memcpy(dest, src, sizeof(struct v4l2_buffer));
|
||||
if (src->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
|
||||
assert(dest_planes);
|
||||
US_A(dest_planes);
|
||||
dest->m.planes = dest_planes;
|
||||
memcpy(dest->m.planes, src->m.planes, sizeof(struct v4l2_plane) * VIDEO_MAX_PLANES);
|
||||
}
|
||||
@@ -662,6 +682,192 @@ static int _capture_open_check_cap(us_capture_s *cap) {
|
||||
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) {
|
||||
// Just probe only if @apply is false
|
||||
|
||||
@@ -671,7 +877,7 @@ static int _capture_open_dv_timings(us_capture_s *cap, bool apply) {
|
||||
|
||||
struct v4l2_dv_timings dv = {0};
|
||||
_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):
|
||||
// - ENOLINK: No valid signal (SYS_STATUS & MASK_S_TMDS)
|
||||
// - ENOLCK: No sync on signal (SYS_STATUS & MASK_S_SYNC)
|
||||
@@ -702,7 +908,7 @@ static int _capture_open_dv_timings(us_capture_s *cap, bool apply) {
|
||||
}
|
||||
|
||||
_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");
|
||||
return -1;
|
||||
}
|
||||
@@ -713,7 +919,7 @@ static int _capture_open_dv_timings(us_capture_s *cap, bool apply) {
|
||||
|
||||
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) {
|
||||
char *std_error = us_errno_to_string(errno); // Read the errno first
|
||||
char *dv_error = us_errno_to_string(dv_errno);
|
||||
@@ -725,7 +931,7 @@ querystd:
|
||||
} else if (!apply) {
|
||||
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));
|
||||
return -1;
|
||||
}
|
||||
@@ -735,7 +941,7 @@ subscribe:
|
||||
; // Empty statement for the goto label above
|
||||
struct v4l2_event_subscription sub = {.type = 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");
|
||||
return -1;
|
||||
}
|
||||
@@ -744,6 +950,47 @@ probe_only:
|
||||
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) {
|
||||
us_capture_runtime_s *const run = cap->run;
|
||||
|
||||
@@ -911,7 +1158,7 @@ static int _capture_open_io_method(us_capture_s *cap) {
|
||||
switch (cap->io_method) {
|
||||
case V4L2_MEMORY_MMAP: return _capture_open_io_method_mmap(cap);
|
||||
case V4L2_MEMORY_USERPTR: return _capture_open_io_method_userptr(cap);
|
||||
default: assert(0 && "Unsupported IO method");
|
||||
default: US_RAISE("Unsupported IO method");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -972,7 +1219,7 @@ static int _capture_open_io_method_mmap(us_capture_s *cap) {
|
||||
_LOG_PERROR("Can't map device buffer=%u", run->n_bufs);
|
||||
return -1;
|
||||
}
|
||||
assert(hw->raw.data != NULL);
|
||||
US_A(hw->raw.data != NULL);
|
||||
hw->raw.allocated = buf_size;
|
||||
|
||||
if (run->capture_mplane) {
|
||||
@@ -1014,7 +1261,7 @@ static int _capture_open_io_method_userptr(us_capture_s *cap) {
|
||||
|
||||
for (run->n_bufs = 0; run->n_bufs < req.count; ++run->n_bufs) {
|
||||
us_capture_hwbuf_s *hw = &run->bufs[run->n_bufs];
|
||||
assert((hw->raw.data = aligned_alloc(page_size, buf_size)) != NULL);
|
||||
US_A((hw->raw.data = aligned_alloc(page_size, buf_size)) != NULL);
|
||||
memset(hw->raw.data, 0, buf_size);
|
||||
hw->raw.allocated = buf_size;
|
||||
if (run->capture_mplane) {
|
||||
@@ -1095,111 +1342,6 @@ static int _capture_apply_resolution(us_capture_s *cap, uint width, uint height,
|
||||
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) {
|
||||
US_ARRAY_ITERATE(_FORMATS, 0, item, {
|
||||
if (item->format == format) {
|
||||
@@ -1231,3 +1373,12 @@ static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
#include "types.h"
|
||||
#include "frame.h"
|
||||
#include "media.h"
|
||||
#include "controls.h"
|
||||
|
||||
|
||||
#define US_VIDEO_MIN_WIDTH ((uint)160)
|
||||
@@ -53,6 +55,8 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
int dv_timings_fd;
|
||||
us_media_pad_s media_pads[3]; // for VIDIOC_SUBDEV_S_FMT
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
@@ -69,40 +73,14 @@ typedef struct {
|
||||
int open_error_once;
|
||||
} 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 {
|
||||
char *path;
|
||||
uint input;
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
char *media_path;
|
||||
char *media_entity_name;
|
||||
|
||||
bool format_swap_rgb;
|
||||
uint jpeg_quality;
|
||||
@@ -116,7 +94,7 @@ typedef struct {
|
||||
bool allow_truncated_frames;
|
||||
bool persistent;
|
||||
uint timeout;
|
||||
us_controls_s ctl;
|
||||
us_controls_s *ctl;
|
||||
us_capture_runtime_s *run;
|
||||
} us_capture_s;
|
||||
|
||||
|
||||
@@ -20,61 +20,66 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "memsinkfd.h"
|
||||
#include "chip.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.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 "errors.h"
|
||||
#include "tools.h"
|
||||
#include "xioctl.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;
|
||||
#ifndef V4L2_CID_USER_TC358743_BASE
|
||||
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
|
||||
#endif
|
||||
|
||||
#ifndef TC358743_CID_AUDIO_SAMPLING_RATE
|
||||
# define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
|
||||
#endif
|
||||
|
||||
#ifndef TC358743_CID_AUDIO_PRESENT
|
||||
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
|
||||
#endif
|
||||
|
||||
#ifndef TC358743_CID_LANES_ENOUGH
|
||||
# define TC358743_CID_LANES_ENOUGH (V4L2_CID_USER_TC358743_BASE + 2)
|
||||
#endif
|
||||
|
||||
|
||||
int us_chip_check_cable(int fd) {
|
||||
struct v4l2_control ctl = {.id = V4L2_CID_DV_RX_POWER_PRESENT};
|
||||
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
|
||||
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) {
|
||||
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;
|
||||
int us_chip_tc358743_check_lanes(int fd) {
|
||||
struct v4l2_control ctl = {.id = TC358743_CID_LANES_ENOUGH};
|
||||
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
|
||||
return US_ERROR_COMMON;
|
||||
}
|
||||
return (ctl.value ? 0 : US_ERROR_NO_LANES);
|
||||
}
|
||||
|
||||
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;
|
||||
if (frame->format != V4L2_PIX_FMT_H264) {
|
||||
US_JLOG_ERROR("video", "Got non-H264 frame from memsink");
|
||||
retval = -1;
|
||||
US_MEMSET_ZERO(ctl);
|
||||
ctl.id = TC358743_CID_AUDIO_SAMPLING_RATE;
|
||||
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
|
||||
return US_ERROR_COMMON;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
US_JLOG_PERROR("video", "Can't unlock memsink");
|
||||
retval = -1;
|
||||
}
|
||||
return retval;
|
||||
return (ctl.value ? ctl.value : US_ERROR_NO_SIGNAL);
|
||||
}
|
||||
@@ -22,26 +22,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <linux/v4l2-controls.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
|
||||
#ifndef V4L2_CID_USER_TC358743_BASE
|
||||
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
|
||||
#endif
|
||||
int us_chip_check_cable(int fd);
|
||||
|
||||
#ifndef TC358743_CID_AUDIO_SAMPLING_RATE
|
||||
# define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
|
||||
#endif
|
||||
|
||||
#ifndef TC358743_CID_AUDIO_PRESENT
|
||||
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
|
||||
#endif
|
||||
|
||||
#ifndef TC358743_CID_LANES_ENOUGH
|
||||
# define TC358743_CID_LANES_ENOUGH (V4L2_CID_USER_TC358743_BASE + 2)
|
||||
#endif
|
||||
|
||||
|
||||
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz);
|
||||
int us_chip_tc358743_check_lanes(int fd);
|
||||
int us_chip_tc358743_get_audio_hz(int fd);
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
#define US_VERSION_MAJOR 6
|
||||
#define US_VERSION_MINOR 47
|
||||
#define US_VERSION_MINOR 67
|
||||
|
||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
|
||||
173
src/libs/controls.c
Normal file
173
src/libs/controls.c
Normal 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
60
src/libs/controls.h
Normal 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);
|
||||
@@ -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);
|
||||
|
||||
@@ -99,7 +99,7 @@ void us_drm_destroy(us_drm_s *drm) {
|
||||
int us_drm_open(us_drm_s *drm, const us_capture_s *cap) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd < 0);
|
||||
US_A(run->fd < 0);
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
@@ -200,7 +200,7 @@ void us_drm_close(us_drm_s *drm) {
|
||||
if (run->exposing_dma_fd >= 0) {
|
||||
// Нужно подождать, пока dma_fd не освободится, прежде чем прерывать процесс.
|
||||
// Просто на всякий случай.
|
||||
assert(run->fd >= 0);
|
||||
US_A(run->fd >= 0);
|
||||
us_drm_wait_for_vsync(drm);
|
||||
run->exposing_dma_fd = -1;
|
||||
}
|
||||
@@ -258,8 +258,8 @@ void us_drm_close(us_drm_s *drm) {
|
||||
int us_drm_ensure_no_signal(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd >= 0);
|
||||
assert(run->opened > 0);
|
||||
US_A(run->fd >= 0);
|
||||
US_A(run->opened > 0);
|
||||
|
||||
const ldf now_ts = us_get_now_monotonic();
|
||||
if (run->blank_at_ts == 0) {
|
||||
@@ -284,7 +284,7 @@ int us_drm_ensure_no_signal(us_drm_s *drm) {
|
||||
}
|
||||
|
||||
int us_drm_dpms_power_off(us_drm_s *drm) {
|
||||
assert(drm->run->fd >= 0);
|
||||
US_A(drm->run->fd >= 0);
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case US_ERROR_NO_DEVICE: return 0; // Unplugged, nice
|
||||
@@ -300,7 +300,7 @@ int us_drm_dpms_power_off(us_drm_s *drm) {
|
||||
int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd >= 0);
|
||||
US_A(run->fd >= 0);
|
||||
run->blank_at_ts = 0;
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
@@ -355,8 +355,8 @@ static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *cap) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd >= 0);
|
||||
assert(run->opened > 0);
|
||||
US_A(run->fd >= 0);
|
||||
US_A(run->opened > 0);
|
||||
run->blank_at_ts = 0;
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
@@ -369,7 +369,7 @@ int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *ca
|
||||
# define DRAW_MSG(x_msg) us_frametext_draw(run->ft, (x_msg), run->mode.hdisplay, run->mode.vdisplay)
|
||||
switch (stub) {
|
||||
case US_DRM_STUB_BAD_RESOLUTION: {
|
||||
assert(cap != NULL);
|
||||
US_A(cap != NULL);
|
||||
char msg[1024];
|
||||
US_SNPRINTF(msg, 1023,
|
||||
"=== PiKVM ==="
|
||||
@@ -420,8 +420,8 @@ int us_drm_expose_dma(us_drm_s *drm, const us_capture_hwbuf_s *hw) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
us_drm_buffer_s *const buf = &run->bufs[hw->buf.index];
|
||||
|
||||
assert(run->fd >= 0);
|
||||
assert(run->opened == 0);
|
||||
US_A(run->fd >= 0);
|
||||
US_A(run->opened == 0);
|
||||
run->blank_at_ts = 0;
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
@@ -706,8 +706,8 @@ static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint
|
||||
if (best == NULL) {
|
||||
best = (conn->count_modes > 0 ? &conn->modes[0] : NULL);
|
||||
}
|
||||
assert(best == NULL || best->hdisplay > 0);
|
||||
assert(best == NULL || best->vdisplay > 0);
|
||||
US_A(best == NULL || best->hdisplay > 0);
|
||||
US_A(best == NULL || best->vdisplay > 0);
|
||||
return best;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -56,9 +56,9 @@ void us_fpsi_frame_to_meta(const us_frame_s *frame, us_fpsi_meta_s *meta) {
|
||||
|
||||
void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
|
||||
if (meta != NULL) {
|
||||
assert(fpsi->with_meta);
|
||||
US_A(fpsi->with_meta);
|
||||
} else {
|
||||
assert(!fpsi->with_meta);
|
||||
US_A(!fpsi->with_meta);
|
||||
}
|
||||
|
||||
const sll now_ts = us_floor_ms(us_get_now_monotonic());
|
||||
@@ -68,7 +68,7 @@ void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
|
||||
// Fast mutex-less store method
|
||||
ull state = (ull)fpsi->accum & 0xFFFF;
|
||||
if (fpsi->with_meta) {
|
||||
assert(meta != NULL);
|
||||
US_A(meta != NULL);
|
||||
state |= (ull)(meta->width & 0xFFFF) << 16;
|
||||
state |= (ull)(meta->height & 0xFFFF) << 32;
|
||||
state |= (ull)(meta->online ? 1 : 0) << 48;
|
||||
@@ -84,7 +84,7 @@ void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
|
||||
|
||||
uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta) {
|
||||
if (meta != NULL) {
|
||||
assert(fpsi->with_meta);
|
||||
US_A(fpsi->with_meta);
|
||||
}
|
||||
|
||||
// Между чтением инфы и времени может быть гонка,
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
@@ -107,7 +106,7 @@ uint us_frame_get_padding(const us_frame_s *frame) {
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0 && "Unknown format");
|
||||
US_RAISE("Unknown format");
|
||||
}
|
||||
if (bytes_per_pixel > 0 && frame->stride > frame->width) {
|
||||
return (frame->stride - frame->width * bytes_per_pixel);
|
||||
@@ -120,7 +119,7 @@ bool us_is_jpeg(uint format) {
|
||||
}
|
||||
|
||||
const char *us_fourcc_to_string(uint format, char *buf, uz size) {
|
||||
assert(size >= 8);
|
||||
US_A(size >= 8);
|
||||
buf[0] = format & 0x7F;
|
||||
buf[1] = (format >> 8) & 0x7F;
|
||||
buf[2] = (format >> 16) & 0x7F;
|
||||
|
||||
@@ -84,7 +84,7 @@ typedef struct {
|
||||
|
||||
|
||||
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, uint format) {
|
||||
assert(src->used > 0);
|
||||
US_A(src->used > 0);
|
||||
US_FRAME_COPY_META(src, dest);
|
||||
dest->encode_begin_ts = us_get_now_monotonic();
|
||||
dest->format = format;
|
||||
@@ -93,7 +93,7 @@ static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *de
|
||||
}
|
||||
|
||||
static inline void us_frame_encoding_end(us_frame_s *dest) {
|
||||
assert(dest->used > 0);
|
||||
US_A(dest->used > 0);
|
||||
dest->encode_end_ts = us_get_now_monotonic();
|
||||
}
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ To access the nth pixel in a row, right-shift by n.
|
||||
*/
|
||||
|
||||
void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint height) {
|
||||
assert(width > 0);
|
||||
assert(height > 0);
|
||||
US_A(width > 0);
|
||||
US_A(height > 0);
|
||||
|
||||
us_frame_s *const frame = ft->frame;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#define US_LIST_DECLARE \
|
||||
void *prev; \
|
||||
void *next;
|
||||
#define US_LIST_DECLARE(x_type) \
|
||||
x_type *prev; \
|
||||
x_type *next;
|
||||
|
||||
#define US_LIST_ITERATE(x_first, x_item, ...) { \
|
||||
for (__typeof__(x_first) x_item = x_first; x_item;) { \
|
||||
@@ -71,6 +71,6 @@
|
||||
|
||||
#define US_LIST_REMOVE_C(x_first, x_item, x_count) { \
|
||||
US_LIST_REMOVE(x_first, x_item); \
|
||||
assert((x_count) >= 1); \
|
||||
US_A((x_count) >= 1); \
|
||||
--(x_count); \
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
#include "logging_base.h"
|
||||
|
||||
|
||||
enum us_log_level_t {
|
||||
@@ -73,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; \
|
||||
@@ -111,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__); \
|
||||
}
|
||||
@@ -121,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__); \
|
||||
}
|
||||
|
||||
@@ -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_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); \
|
||||
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); \
|
||||
}
|
||||
|
||||
// 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
215
src/libs/media.c
Normal 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
70
src/libs/media.h
Normal 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);
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -42,8 +41,8 @@
|
||||
|
||||
us_memsink_s *us_memsink_init_opened(
|
||||
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_CALLOC(sink, 1);
|
||||
sink->name = name;
|
||||
@@ -57,7 +56,7 @@ us_memsink_s *us_memsink_init_opened(
|
||||
|
||||
US_LOG_INFO("Using %s-sink: %s", name, obj);
|
||||
|
||||
if ((sink->data_size = us_memsink_calculate_size(obj)) == 0) {
|
||||
if ((sink->data_size = us_memsinksh_calculate_size(obj)) == 0) {
|
||||
US_LOG_ERROR("%s-sink: Invalid object suffix", name);
|
||||
goto error;
|
||||
}
|
||||
@@ -76,7 +75,7 @@ us_memsink_s *us_memsink_init_opened(
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((sink->mem = us_memsink_shared_map(sink->fd, sink->data_size)) == NULL) {
|
||||
if ((sink->mem = us_memsinksh_map(sink->fd, sink->data_size)) == NULL) {
|
||||
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
@@ -89,7 +88,7 @@ error:
|
||||
|
||||
void us_memsink_destroy(us_memsink_s *sink) {
|
||||
if (sink->mem != NULL) {
|
||||
if (us_memsink_shared_unmap(sink->mem, sink->data_size) < 0) {
|
||||
if (us_memsinksh_unmap(sink->mem, sink->data_size) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
|
||||
}
|
||||
}
|
||||
@@ -110,7 +109,7 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
// Если frame == NULL, то только проверяем наличие клиентов
|
||||
// или необходимость инициализировать память.
|
||||
|
||||
assert(sink->server);
|
||||
US_A(sink->server);
|
||||
|
||||
if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) {
|
||||
// Если регион памяти не был инициализирован, то нужно что-то туда положить.
|
||||
@@ -144,11 +143,10 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
}
|
||||
|
||||
// Проверяем, есть ли у нас живой клиент по таймауту
|
||||
const bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic());
|
||||
const bool has_clients = us_memsinksh_has_clients(sink->mem, sink->client_ttl);
|
||||
atomic_store(&sink->has_clients, has_clients);
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
if (us_memsink_server_x_unlock(sink) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (has_clients) {
|
||||
@@ -161,43 +159,37 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested) {
|
||||
assert(sink->server);
|
||||
int us_memsink_server_put(
|
||||
us_memsink_s *sink,
|
||||
const us_frame_s *frame,
|
||||
us_memsink_wants_s *w_get
|
||||
) {
|
||||
US_A(sink->server);
|
||||
|
||||
const ldf now = us_get_now_monotonic();
|
||||
|
||||
if (frame->used > sink->data_size) {
|
||||
US_LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
|
||||
sink->name, frame->used, sink->data_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (us_flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
||||
const bool has_clients = us_memsinksh_has_clients(sink->mem, sink->client_ttl);
|
||||
atomic_store(&sink->has_clients, has_clients);
|
||||
|
||||
US_LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
|
||||
const bool exposed = !us_memsink_server_x_put(sink, frame);
|
||||
|
||||
sink->mem->id = us_get_now_id();
|
||||
if (sink->mem->key_requested && frame->key) {
|
||||
sink->mem->key_requested = false;
|
||||
}
|
||||
if (key_requested != NULL) { // We don't need it for non-H264 sinks
|
||||
*key_requested = sink->mem->key_requested;
|
||||
if (w_get != NULL) {
|
||||
if (has_clients) {
|
||||
memcpy(w_get, &sink->mem->wants, sizeof(us_memsink_wants_s));
|
||||
} else {
|
||||
US_MEMSET_ZERO(*w_get);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(us_memsink_get_data(sink->mem), frame->data, frame->used);
|
||||
sink->mem->used = frame->used;
|
||||
US_FRAME_COPY_META(frame, sink->mem);
|
||||
|
||||
sink->mem->magic = US_MEMSINK_MAGIC;
|
||||
sink->mem->version = US_MEMSINK_VERSION;
|
||||
|
||||
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic()));
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
if (us_memsink_server_x_unlock(sink) < 0) {
|
||||
return -1;
|
||||
}
|
||||
US_LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
|
||||
sink->name, us_get_now_monotonic() - now);
|
||||
if (exposed) {
|
||||
US_LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
|
||||
sink->name, us_get_now_monotonic() - now);
|
||||
}
|
||||
|
||||
} else if (errno == EWOULDBLOCK) {
|
||||
US_LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
|
||||
@@ -209,8 +201,13 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key
|
||||
return 0;
|
||||
}
|
||||
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required) {
|
||||
assert(!sink->server); // Client only
|
||||
int us_memsink_client_get(
|
||||
us_memsink_s *sink,
|
||||
us_frame_s *frame,
|
||||
us_memsink_wants_s *w_get,
|
||||
const us_memsink_wants_s *w_put
|
||||
) {
|
||||
US_A(!sink->server); // Client only
|
||||
|
||||
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
@@ -233,28 +230,121 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_reque
|
||||
goto done;
|
||||
}
|
||||
|
||||
const bool had_client_magic = (sink->mem->client_magic == US_MEMSINK_MAGIC);
|
||||
// Let the sink know that the client is alive
|
||||
sink->mem->last_client_ts = us_get_now_monotonic();
|
||||
sink->mem->client_magic = US_MEMSINK_MAGIC;
|
||||
|
||||
if (sink->mem->id == sink->last_readed_id) {
|
||||
retval = US_ERROR_NO_DATA; // Not updated
|
||||
if (sink->mem->used == 0 || sink->mem->id == sink->last_readed_id) {
|
||||
// ^ Zero frame |OR| ^ Not updated
|
||||
retval = US_ERROR_NO_DATA;
|
||||
goto done;
|
||||
}
|
||||
|
||||
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_memsinksh_get_data(sink->mem), sink->mem->used);
|
||||
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 (w_get != NULL) {
|
||||
if (had_client_magic) { // Чтобы не прочитать мусор, если не было клиентов
|
||||
memcpy(w_get, &sink->mem->wants, sizeof(us_memsink_wants_s));
|
||||
} else {
|
||||
US_MEMSET_ZERO(*w_get);
|
||||
}
|
||||
}
|
||||
if (key_required) {
|
||||
sink->mem->key_requested = true;
|
||||
|
||||
if (w_put != NULL) {
|
||||
const bool key = sink->mem->wants.key;
|
||||
memcpy(&sink->mem->wants, w_put, sizeof(us_memsink_wants_s));
|
||||
if (key) {
|
||||
sink->mem->wants.key = key;
|
||||
}
|
||||
} else {
|
||||
US_MEMSET_ZERO(sink->mem->wants);
|
||||
}
|
||||
|
||||
done:
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
if (us_memsink_server_x_unlock(sink) < 0) {
|
||||
retval = -1;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
us_mss_lock_result_e us_memsink_server_x_lock(
|
||||
us_memsink_s *sink,
|
||||
us_memsink_wants_s *w_get
|
||||
) {
|
||||
US_A(sink->server);
|
||||
|
||||
if (us_flock_timedwait_monotonic(sink->fd, 1) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
return US_MSS_BUSY;
|
||||
}
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return US_MSS_ERROR;
|
||||
}
|
||||
|
||||
if (sink->mem->magic == US_MEMSINK_MAGIC && sink->mem->version == US_MEMSINK_VERSION) {
|
||||
// Если инициализировано - значит клиенту что-то уже могли написать
|
||||
const bool has_clients = us_memsinksh_has_clients(sink->mem, sink->client_ttl);
|
||||
atomic_store(&sink->has_clients, has_clients);
|
||||
if (has_clients) {
|
||||
if (w_get != NULL) {
|
||||
memcpy(w_get, &sink->mem->wants, sizeof(us_memsink_wants_s));
|
||||
}
|
||||
// sink->unsafe_last_client_ts = sink->mem->last_client_ts; // _x_ functions don't use it
|
||||
return US_MSS_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
// А иначе скормим клиентам инициализацию с пустым фреймом,
|
||||
// в следующий раз прочтем от них что-то путное.
|
||||
us_memsink_server_x_put(sink, NULL);
|
||||
}
|
||||
|
||||
if (us_memsink_server_x_unlock(sink) < 0) {
|
||||
return US_MSS_ERROR;
|
||||
}
|
||||
return US_MSS_NO_CLIENT;
|
||||
}
|
||||
|
||||
bool us_memsink_server_x_is_consumed(us_memsink_s *sink) {
|
||||
return (sink->mem->used == 0);
|
||||
}
|
||||
|
||||
void us_memsink_server_x_set_consumed(us_memsink_s *sink) {
|
||||
sink->mem->used = 0;
|
||||
}
|
||||
|
||||
int us_memsink_server_x_put(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
if (frame != NULL) {
|
||||
if (frame->used > sink->data_size) {
|
||||
US_LOG_ERROR("%s-sink: Can't put frame: it's too big (%zu > %zu)",
|
||||
sink->name, frame->used, sink->data_size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(us_memsinksh_get_data(sink->mem), frame->data, frame->used);
|
||||
sink->mem->used = frame->used;
|
||||
US_FRAME_COPY_META(frame, sink->mem);
|
||||
|
||||
if (sink->mem->wants.key && frame->key) {
|
||||
// Можем писать в мусор, но пофигу
|
||||
sink->mem->wants.key = false;
|
||||
}
|
||||
} else {
|
||||
sink->mem->used = 0;
|
||||
}
|
||||
|
||||
sink->mem->id = us_get_now_id();
|
||||
sink->mem->version = US_MEMSINK_VERSION;
|
||||
sink->mem->magic = US_MEMSINK_MAGIC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int us_memsink_server_x_unlock(us_memsink_s *sink) {
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,36 @@ us_memsink_s *us_memsink_init_opened(
|
||||
void us_memsink_destroy(us_memsink_s *sink);
|
||||
|
||||
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 *w_get);
|
||||
|
||||
int us_memsink_client_get(
|
||||
us_memsink_s *sink,
|
||||
us_frame_s *frame,
|
||||
us_memsink_wants_s *w_get,
|
||||
const us_memsink_wants_s *w_put);
|
||||
|
||||
|
||||
// Low-level
|
||||
|
||||
typedef enum {
|
||||
US_MSS_SUCCESS = 0,
|
||||
US_MSS_ERROR = -1,
|
||||
US_MSS_BUSY = -2,
|
||||
US_MSS_NO_CLIENT = -3,
|
||||
} us_mss_lock_result_e;
|
||||
|
||||
|
||||
us_mss_lock_result_e us_memsink_server_x_lock(
|
||||
us_memsink_s *sink,
|
||||
us_memsink_wants_s *w_get);
|
||||
|
||||
bool us_memsink_server_x_is_consumed(us_memsink_s *sink);
|
||||
void us_memsink_server_x_set_consumed(us_memsink_s *sink);
|
||||
|
||||
int us_memsink_server_x_put(us_memsink_s *sink, const us_frame_s *frame);
|
||||
|
||||
int us_memsink_server_x_unlock(us_memsink_s *sink);
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size) {
|
||||
us_memsink_shared_s *us_memsinksh_map(int fd, uz data_size) {
|
||||
us_memsink_shared_s *mem = mmap(
|
||||
NULL,
|
||||
sizeof(us_memsink_shared_s) + data_size,
|
||||
@@ -40,16 +40,16 @@ us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size) {
|
||||
if (mem == MAP_FAILED) {
|
||||
return NULL;
|
||||
}
|
||||
assert(mem != NULL);
|
||||
US_A(mem != NULL);
|
||||
return mem;
|
||||
}
|
||||
|
||||
int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size) {
|
||||
assert(mem != NULL);
|
||||
int us_memsinksh_unmap(us_memsink_shared_s *mem, uz data_size) {
|
||||
US_A(mem != NULL);
|
||||
return munmap(mem, sizeof(us_memsink_shared_s) + data_size);
|
||||
}
|
||||
|
||||
uz us_memsink_calculate_size(const char *obj) {
|
||||
uz us_memsinksh_calculate_size(const char *obj) {
|
||||
const char *ptr = strrchr(obj, ':');
|
||||
if (ptr == NULL) {
|
||||
ptr = strrchr(obj, '.');
|
||||
@@ -61,12 +61,19 @@ 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;
|
||||
}
|
||||
|
||||
u8 *us_memsink_get_data(us_memsink_shared_s *mem) {
|
||||
u8 *us_memsinksh_get_data(us_memsink_shared_s *mem) {
|
||||
return (u8*)(mem) + sizeof(us_memsink_shared_s);
|
||||
}
|
||||
|
||||
bool us_memsinksh_has_clients(const us_memsink_shared_s *mem, u32 ttl) {
|
||||
return (
|
||||
mem->client_magic == US_MEMSINK_MAGIC
|
||||
&& mem->last_client_ts + ttl > us_get_now_monotonic()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,24 +27,34 @@
|
||||
|
||||
|
||||
#define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE)
|
||||
#define US_MEMSINK_VERSION ((u32)7)
|
||||
#define US_MEMSINK_VERSION ((u32)11)
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
uint fps;
|
||||
bool key;
|
||||
} us_memsink_wants_s;
|
||||
|
||||
typedef struct {
|
||||
u64 magic;
|
||||
u32 version;
|
||||
u64 id;
|
||||
uz used;
|
||||
|
||||
u64 client_magic;
|
||||
ldf last_client_ts;
|
||||
bool key_requested;
|
||||
us_memsink_wants_s wants;
|
||||
|
||||
US_FRAME_META_DECLARE;
|
||||
} us_memsink_shared_s;
|
||||
|
||||
|
||||
us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size);
|
||||
int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size);
|
||||
us_memsink_shared_s *us_memsinksh_map(int fd, uz data_size);
|
||||
int us_memsinksh_unmap(us_memsink_shared_s *mem, uz data_size);
|
||||
|
||||
uz us_memsink_calculate_size(const char *obj);
|
||||
u8 *us_memsink_get_data(us_memsink_shared_s *mem);
|
||||
uz us_memsinksh_calculate_size(const char *obj);
|
||||
u8 *us_memsinksh_get_data(us_memsink_shared_s *mem);
|
||||
bool us_memsinksh_has_clients(const us_memsink_shared_s *mem, u32 ttl);
|
||||
|
||||
@@ -25,15 +25,15 @@
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <getopt.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, uz size) {
|
||||
memset(short_opts, 0, size);
|
||||
for (uint short_i = 0, opt_i = 0; opts[opt_i].name != NULL; ++opt_i) {
|
||||
assert(short_i < size - 3);
|
||||
US_A(short_i < size - 3);
|
||||
if (isalpha(opts[opt_i].val)) {
|
||||
short_opts[short_i] = opts[opt_i].val;
|
||||
++short_i;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@@ -41,11 +40,11 @@ us_queue_s *us_queue_init(uint capacity) {
|
||||
US_MUTEX_INIT(q->mutex);
|
||||
|
||||
pthread_condattr_t attrs;
|
||||
assert(!pthread_condattr_init(&attrs));
|
||||
assert(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
|
||||
assert(!pthread_cond_init(&q->full_cond, &attrs));
|
||||
assert(!pthread_cond_init(&q->empty_cond, &attrs));
|
||||
assert(!pthread_condattr_destroy(&attrs));
|
||||
US_A(!pthread_condattr_init(&attrs));
|
||||
US_A(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
|
||||
US_A(!pthread_cond_init(&q->full_cond, &attrs));
|
||||
US_A(!pthread_cond_init(&q->empty_cond, &attrs));
|
||||
US_A(!pthread_condattr_destroy(&attrs));
|
||||
return q;
|
||||
}
|
||||
|
||||
@@ -59,7 +58,7 @@ void us_queue_destroy(us_queue_s *q) {
|
||||
|
||||
#define _WAIT_OR_UNLOCK(x_var, x_cond) { \
|
||||
struct timespec m_ts; \
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
|
||||
US_A(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
|
||||
us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \
|
||||
while (x_var) { \
|
||||
const int err = pthread_cond_timedwait(&(x_cond), &q->mutex, &m_ts); \
|
||||
@@ -67,7 +66,7 @@ void us_queue_destroy(us_queue_s *q) {
|
||||
US_MUTEX_UNLOCK(q->mutex); \
|
||||
return -1; \
|
||||
} \
|
||||
assert(!err); \
|
||||
US_A(!err); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "ring.h"
|
||||
|
||||
#include "types.h"
|
||||
@@ -43,7 +41,7 @@ us_ring_s *us_ring_init(uint capacity) {
|
||||
ring->consumer = us_queue_init(capacity);
|
||||
for (uint ri = 0; ri < capacity; ++ri) {
|
||||
ring->places[ri] = ri; // XXX: Just to avoid casting between pointer and uint
|
||||
assert(!us_queue_put(ring->producer, (void*)(ring->places + ri), 0));
|
||||
US_A(!us_queue_put(ring->producer, (void*)(ring->places + ri), 0));
|
||||
}
|
||||
return ring;
|
||||
}
|
||||
@@ -82,5 +80,5 @@ int _acquire(us_ring_s *ring, us_queue_s *q, ldf timeout) {
|
||||
}
|
||||
|
||||
void _release(us_ring_s *ring, us_queue_s *q, uint ri) {
|
||||
assert(!us_queue_put(q, (void*)(ring->places + ri), 0));
|
||||
US_A(!us_queue_put(q, (void*)(ring->places + ri), 0));
|
||||
}
|
||||
|
||||
@@ -24,9 +24,8 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <assert.h>
|
||||
|
||||
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
|
||||
#if defined(_GNU_SOURCE) && defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
|
||||
# define HAS_SIGABBREV_NP
|
||||
#endif
|
||||
|
||||
@@ -58,25 +57,25 @@ char *us_signum_to_string(int signum) {
|
||||
void us_install_signals_handler(us_signal_handler_f handler, bool ignore_sigpipe) {
|
||||
struct sigaction sig_act = {0};
|
||||
|
||||
assert(!sigemptyset(&sig_act.sa_mask));
|
||||
US_A(!sigemptyset(&sig_act.sa_mask));
|
||||
sig_act.sa_handler = handler;
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||
US_A(!sigaddset(&sig_act.sa_mask, SIGINT));
|
||||
US_A(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||
if (!ignore_sigpipe) {
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
|
||||
US_A(!sigaddset(&sig_act.sa_mask, SIGPIPE));
|
||||
}
|
||||
|
||||
US_LOG_DEBUG("Installing SIGINT handler ...");
|
||||
assert(!sigaction(SIGINT, &sig_act, NULL));
|
||||
US_A(!sigaction(SIGINT, &sig_act, NULL));
|
||||
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGTERM, &sig_act, NULL));
|
||||
US_A(!sigaction(SIGTERM, &sig_act, NULL));
|
||||
|
||||
if (!ignore_sigpipe) {
|
||||
US_LOG_DEBUG("Installing SIGPIPE handler ...");
|
||||
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||
US_A(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||
} else {
|
||||
US_LOG_DEBUG("Ignoring SIGPIPE ...");
|
||||
assert(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
|
||||
US_A(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/syscall.h>
|
||||
|
||||
@@ -47,8 +46,8 @@
|
||||
# define US_THREAD_NAME_SIZE ((uz)16)
|
||||
#endif
|
||||
|
||||
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
|
||||
#define US_THREAD_JOIN(x_tid) assert(!pthread_join((x_tid), NULL))
|
||||
#define US_THREAD_CREATE(x_tid, x_func, x_arg) US_A(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
|
||||
#define US_THREAD_JOIN(x_tid) US_A(!pthread_join((x_tid), NULL))
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# define US_THREAD_RENAME(x_fmt, ...) { \
|
||||
@@ -65,16 +64,16 @@
|
||||
us_thread_block_signals(); \
|
||||
}
|
||||
|
||||
#define US_MUTEX_INIT(x_mutex) assert(!pthread_mutex_init(&(x_mutex), NULL))
|
||||
#define US_MUTEX_DESTROY(x_mutex) assert(!pthread_mutex_destroy(&(x_mutex)))
|
||||
#define US_MUTEX_LOCK(x_mutex) assert(!pthread_mutex_lock(&(x_mutex)))
|
||||
#define US_MUTEX_UNLOCK(x_mutex) assert(!pthread_mutex_unlock(&(x_mutex)))
|
||||
#define US_MUTEX_INIT(x_mutex) US_A(!pthread_mutex_init(&(x_mutex), NULL))
|
||||
#define US_MUTEX_DESTROY(x_mutex) US_A(!pthread_mutex_destroy(&(x_mutex)))
|
||||
#define US_MUTEX_LOCK(x_mutex) US_A(!pthread_mutex_lock(&(x_mutex)))
|
||||
#define US_MUTEX_UNLOCK(x_mutex) US_A(!pthread_mutex_unlock(&(x_mutex)))
|
||||
|
||||
#define US_COND_INIT(x_cond) assert(!pthread_cond_init(&(x_cond), NULL))
|
||||
#define US_COND_DESTROY(x_cond) assert(!pthread_cond_destroy(&(x_cond)))
|
||||
#define US_COND_SIGNAL(x_cond) assert(!pthread_cond_signal(&(x_cond)))
|
||||
#define US_COND_BROADCAST(x_cond) assert(!pthread_cond_broadcast(&(x_cond)))
|
||||
#define US_COND_WAIT_FOR(x_var, x_cond, x_mutex) { while(!(x_var)) assert(!pthread_cond_wait(&(x_cond), &(x_mutex))); }
|
||||
#define US_COND_INIT(x_cond) US_A(!pthread_cond_init(&(x_cond), NULL))
|
||||
#define US_COND_DESTROY(x_cond) US_A(!pthread_cond_destroy(&(x_cond)))
|
||||
#define US_COND_SIGNAL(x_cond) US_A(!pthread_cond_signal(&(x_cond)))
|
||||
#define US_COND_BROADCAST(x_cond) US_A(!pthread_cond_broadcast(&(x_cond)))
|
||||
#define US_COND_WAIT_FOR(x_var, x_cond, x_mutex) { while(!(x_var)) US_A(!pthread_cond_wait(&(x_cond), &(x_mutex))); }
|
||||
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
@@ -95,13 +94,13 @@ 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);
|
||||
if (name[0] != '\0') {
|
||||
pthread_get_name_np(pthread_self(), name, US_THREAD_NAME_SIZE); // Also null-terminated
|
||||
if (us_str_is_ok(name)) {
|
||||
retval = 0;
|
||||
}
|
||||
# else
|
||||
@@ -114,7 +113,7 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
|
||||
const pid_t tid = syscall(SYS_gettid);
|
||||
#elif defined(__FreeBSD__)
|
||||
long id;
|
||||
assert(!syscall(SYS_thr_self, &id));
|
||||
US_A(!syscall(SYS_thr_self, &id));
|
||||
const pid_t tid = id;
|
||||
#elif defined(__OpenBSD__)
|
||||
const pid_t tid = syscall(SYS_getthrid);
|
||||
@@ -135,8 +134,8 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
|
||||
|
||||
INLINE void us_thread_block_signals(void) {
|
||||
sigset_t mask;
|
||||
assert(!sigemptyset(&mask));
|
||||
assert(!sigaddset(&mask, SIGINT));
|
||||
assert(!sigaddset(&mask, SIGTERM));
|
||||
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
||||
US_A(!sigemptyset(&mask));
|
||||
US_A(!sigaddset(&mask, SIGINT));
|
||||
US_A(!sigaddset(&mask, SIGTERM));
|
||||
US_A(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
||||
}
|
||||
|
||||
@@ -51,14 +51,17 @@
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
|
||||
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
||||
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
||||
#define US_A(x_arg) { const bool m_ar = (x_arg); assert(m_ar && #x_arg); }
|
||||
#define US_RAISE(x_msg) assert(0 && x_msg)
|
||||
|
||||
#define US_CALLOC(x_dest, x_nmemb) US_A(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
||||
#define US_REALLOC(x_dest, x_nmemb) US_A(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
||||
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); x_dest = NULL; } }
|
||||
#define US_CLOSE_FD(x_dest) { if (x_dest >= 0) { close(x_dest); x_dest = -1; } }
|
||||
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
|
||||
|
||||
#define US_SNPRINTF(x_dest, x_size, x_fmt, ...) assert(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0)
|
||||
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0)
|
||||
#define US_SNPRINTF(x_dest, x_size, x_fmt, ...) US_A(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0)
|
||||
#define US_ASPRINTF(x_dest, x_fmt, ...) US_A(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0)
|
||||
|
||||
#define US_MIN(x_a, x_b) ({ \
|
||||
__typeof__(x_a) m_a = (x_a); \
|
||||
@@ -85,10 +88,14 @@
|
||||
|
||||
INLINE char *us_strdup(const char *str) {
|
||||
char *const new = strdup(str);
|
||||
assert(new != NULL);
|
||||
US_A(new != NULL);
|
||||
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) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
@@ -115,7 +122,7 @@ INLINE u32 us_triple_u32(u32 x) {
|
||||
|
||||
INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(clk_id, &ts));
|
||||
US_A(!clock_gettime(clk_id, &ts));
|
||||
*sec = ts.tv_sec;
|
||||
*msec = round(ts.tv_nsec / 1.0e6);
|
||||
|
||||
@@ -134,7 +141,7 @@ INLINE ldf us_get_now_monotonic(void) {
|
||||
|
||||
INLINE u64 us_get_now_monotonic_u64(void) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &ts));
|
||||
US_A(!clock_gettime(CLOCK_MONOTONIC, &ts));
|
||||
return (u64)(ts.tv_nsec / 1000) + (u64)ts.tv_sec * 1000000;
|
||||
}
|
||||
|
||||
@@ -150,12 +157,6 @@ INLINE ldf us_get_now_real(void) {
|
||||
return (ldf)sec + ((ldf)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE uint us_get_cores_available(void) {
|
||||
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||
return US_MAX(US_MIN(cores_sysconf, 4), 1);
|
||||
}
|
||||
|
||||
INLINE void us_ld_to_timespec(ldf ld, struct timespec *ts) {
|
||||
ts->tv_sec = (long)ld;
|
||||
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
|
||||
@@ -169,6 +170,28 @@ INLINE ldf us_timespec_to_ld(const struct timespec *ts) {
|
||||
return ts->tv_sec + ((ldf)ts->tv_nsec) / 1000000000;
|
||||
}
|
||||
|
||||
#define NTP_UNIX_TIME_DIFF 2208988800u // Difference between Unix time and NTP time in seconds (1970 - 1900)
|
||||
#define NTP_TICKS_IN_SECOND 4294967296u // Ticks per second in NTP time
|
||||
|
||||
INLINE u64 us_get_now_ntp(void) {
|
||||
struct timespec ts;
|
||||
US_A(!clock_gettime(CLOCK_REALTIME, &ts));
|
||||
return (((u64)ts.tv_sec + NTP_UNIX_TIME_DIFF) << 32) + ((u64)ts.tv_nsec / 1000 * NTP_TICKS_IN_SECOND) / 1000000;
|
||||
}
|
||||
|
||||
INLINE u64 us_ld_to_ntp(ldf ld) {
|
||||
return (ld > 0 ? ld * NTP_TICKS_IN_SECOND : 0);
|
||||
}
|
||||
|
||||
#undef NTP_TICKS_IN_SECOND
|
||||
#undef NTP_UNIX_TIME_DIFF
|
||||
|
||||
INLINE uint us_get_cores_available(void) {
|
||||
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||
return US_MAX(US_MIN(cores_sysconf, 4), 1);
|
||||
}
|
||||
|
||||
INLINE int us_flock_timedwait_monotonic(int fd, ldf timeout) {
|
||||
const ldf deadline_ts = us_get_now_monotonic() + timeout;
|
||||
int retval = -1;
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "frame.h"
|
||||
|
||||
@@ -45,7 +45,7 @@ static void _jpeg_error_handler(j_common_ptr jpeg);
|
||||
|
||||
|
||||
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
|
||||
assert(us_is_jpeg(src->format));
|
||||
US_A(us_is_jpeg(src->format));
|
||||
|
||||
volatile int retval = 0;
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@@ -113,7 +112,7 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
|
||||
us_encoder_runtime_s *const run = enc->run;
|
||||
us_capture_runtime_s *const cr = cap->run;
|
||||
|
||||
assert(run->pool == NULL);
|
||||
US_A(run->pool == NULL);
|
||||
|
||||
us_encoder_type_e type = enc->type;
|
||||
uint quality = cap->jpeg_quality;
|
||||
@@ -176,7 +175,7 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
|
||||
}
|
||||
|
||||
void us_encoder_close(us_encoder_s *enc) {
|
||||
assert(enc->run->pool != NULL);
|
||||
US_A(enc->run->pool != NULL);
|
||||
US_DELETE(enc->run->pool, us_workers_pool_destroy);
|
||||
}
|
||||
|
||||
@@ -226,7 +225,7 @@ static bool _worker_run_job(us_worker_s *wr) {
|
||||
}
|
||||
|
||||
} else {
|
||||
assert(0 && "Unknown encoder type");
|
||||
US_RAISE("Unknown encoder type");
|
||||
}
|
||||
|
||||
US_LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
|
||||
@@ -140,7 +139,8 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, uint quali
|
||||
_jpeg_write_scanlines_bgr24(&jpeg, src);
|
||||
# endif
|
||||
break;
|
||||
default: assert(0 && "Unsupported input format for CPU encoder"); return;
|
||||
default:
|
||||
US_RAISE("Unsupported input format for CPU encoder");
|
||||
}
|
||||
|
||||
jpeg_finish_compress(&jpeg);
|
||||
@@ -151,7 +151,7 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, uint quali
|
||||
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) {
|
||||
if (jpeg->dest == NULL) {
|
||||
assert((jpeg->dest = (struct jpeg_destination_mgr*)(*jpeg->mem->alloc_small)(
|
||||
US_A((jpeg->dest = (struct jpeg_destination_mgr*)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s)
|
||||
)) != NULL);
|
||||
}
|
||||
@@ -192,8 +192,7 @@ static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const u
|
||||
u = data[0];
|
||||
v = data[2];
|
||||
} else {
|
||||
assert(0 && "Unsupported pixel format");
|
||||
return; // Makes linter happy
|
||||
US_RAISE("Unsupported pixel format");
|
||||
}
|
||||
|
||||
ptr[0] = y;
|
||||
@@ -217,17 +216,18 @@ 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) {
|
||||
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;
|
||||
|
||||
for (uint x = 0; x < frame->width; ++x) {
|
||||
@@ -235,7 +235,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:
|
||||
@@ -247,8 +247,7 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
|
||||
v = chroma1_data[chroma_position];
|
||||
break;
|
||||
default:
|
||||
assert(0 && "Unsupported pixel format");
|
||||
return; // Makes linter happy
|
||||
US_RAISE("Unsupported pixel format");
|
||||
}
|
||||
|
||||
ptr[0] = y;
|
||||
@@ -259,11 +258,6 @@ 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;
|
||||
}
|
||||
|
||||
JSAMPROW scanlines[1] = {line_buf};
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
@@ -371,7 +365,7 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s*)jpeg->dest;
|
||||
|
||||
// Allocate the output buffer - it will be released when done with image
|
||||
assert((dest->buf = (JOCTET*)(*jpeg->mem->alloc_small)(
|
||||
US_A((dest->buf = (JOCTET*)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
|
||||
)) != NULL);
|
||||
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
#include "encoder.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../../../libs/types.h"
|
||||
#include "../../../libs/tools.h"
|
||||
#include "../../../libs/frame.h"
|
||||
|
||||
#include "huffman.h"
|
||||
@@ -43,7 +43,7 @@ static bool _is_huffman(const u8 *data);
|
||||
|
||||
|
||||
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest) {
|
||||
assert(us_is_jpeg(src->format));
|
||||
US_A(us_is_jpeg(src->format));
|
||||
_copy_plus_huffman(src, dest);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ static void _gpio_output_destroy(us_gpio_output_s *out);
|
||||
|
||||
void us_gpio_init(void) {
|
||||
# ifndef HAVE_GPIOD2
|
||||
assert(us_g_gpio.chip == NULL);
|
||||
US_A(us_g_gpio.chip == NULL);
|
||||
# endif
|
||||
if (
|
||||
us_g_gpio.prog_running.pin >= 0
|
||||
@@ -96,10 +96,10 @@ int us_gpio_inner_set(us_gpio_output_s *out, bool on) {
|
||||
int retval = 0;
|
||||
|
||||
# ifndef HAVE_GPIOD2
|
||||
assert(us_g_gpio.chip != NULL);
|
||||
US_A(us_g_gpio.chip != NULL);
|
||||
# endif
|
||||
assert(out->line != NULL);
|
||||
assert(out->on != on); // Must be checked in macro for the performance
|
||||
US_A(out->line != NULL);
|
||||
US_A(out->on != on); // Must be checked in macro for the performance
|
||||
US_MUTEX_LOCK(us_g_gpio.mutex);
|
||||
|
||||
# ifdef HAVE_GPIOD2
|
||||
@@ -117,24 +117,24 @@ int us_gpio_inner_set(us_gpio_output_s *out, bool on) {
|
||||
}
|
||||
|
||||
static void _gpio_output_init(us_gpio_output_s *out, struct gpiod_chip *chip) {
|
||||
assert(out->line == NULL);
|
||||
US_A(out->line == NULL);
|
||||
|
||||
US_ASPRINTF(out->consumer, "%s::%s", us_g_gpio.consumer_prefix, out->role);
|
||||
|
||||
if (out->pin >= 0) {
|
||||
# ifdef HAVE_GPIOD2
|
||||
struct gpiod_line_settings *line_settings;
|
||||
assert(line_settings = gpiod_line_settings_new());
|
||||
assert(!gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_OUTPUT));
|
||||
assert(!gpiod_line_settings_set_output_value(line_settings, false));
|
||||
US_A(line_settings = gpiod_line_settings_new());
|
||||
US_A(!gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_OUTPUT));
|
||||
US_A(!gpiod_line_settings_set_output_value(line_settings, false));
|
||||
|
||||
struct gpiod_line_config *line_config;
|
||||
assert(line_config = gpiod_line_config_new());
|
||||
US_A(line_config = gpiod_line_config_new());
|
||||
const unsigned offset = out->pin;
|
||||
assert(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings));
|
||||
US_A(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings));
|
||||
|
||||
struct gpiod_request_config *req_config;
|
||||
assert(req_config = gpiod_request_config_new());
|
||||
US_A(req_config = gpiod_request_config_new());
|
||||
gpiod_request_config_set_consumer(req_config, out->consumer);
|
||||
|
||||
if ((out->line = gpiod_chip_request_lines(chip, req_config, line_config)) == NULL) {
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <gpiod.h>
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -102,12 +101,12 @@ static bool _expose_frame(us_server_s *server, const us_frame_s *frame);
|
||||
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
|
||||
#define _A_EVBUFFER_NEW(x_buf) assert((x_buf = evbuffer_new()) != NULL)
|
||||
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) assert(!evbuffer_add(x_buf, x_data, x_size))
|
||||
#define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) assert(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0)
|
||||
#define _A_EVBUFFER_NEW(x_buf) US_A((x_buf = evbuffer_new()) != NULL)
|
||||
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) US_A(!evbuffer_add(x_buf, x_data, x_size))
|
||||
#define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) US_A(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0)
|
||||
|
||||
#define _A_ADD_HEADER(x_req, x_key, x_value) \
|
||||
assert(!evhttp_add_header(evhttp_request_get_output_headers(x_req), x_key, x_value))
|
||||
US_A(!evhttp_add_header(evhttp_request_get_output_headers(x_req), x_key, x_value))
|
||||
|
||||
|
||||
us_server_s *us_server_init(us_stream_s *stream) {
|
||||
@@ -135,9 +134,9 @@ us_server_s *us_server_init(us_stream_s *stream) {
|
||||
server->stream = stream;
|
||||
server->run = run;
|
||||
|
||||
assert(!evthread_use_pthreads());
|
||||
assert((run->base = event_base_new()) != NULL);
|
||||
assert((run->http = evhttp_new(run->base)) != NULL);
|
||||
US_A(!evthread_use_pthreads());
|
||||
US_A((run->base = event_base_new()) != NULL);
|
||||
US_A((run->http = evhttp_new(run->base)) != NULL);
|
||||
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD|EVHTTP_REQ_OPTIONS);
|
||||
return server;
|
||||
}
|
||||
@@ -184,26 +183,26 @@ int us_server_listen(us_server_s *server) {
|
||||
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);
|
||||
evhttp_set_gencb(run->http, _http_callback_static, (void*)server);
|
||||
} else {
|
||||
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, (void*)server));
|
||||
assert(!evhttp_set_cb(run->http, "/favicon.ico", _http_callback_favicon, (void*)server));
|
||||
US_A(!evhttp_set_cb(run->http, "/", _http_callback_root, (void*)server));
|
||||
US_A(!evhttp_set_cb(run->http, "/favicon.ico", _http_callback_favicon, (void*)server));
|
||||
}
|
||||
assert(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void*)server));
|
||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void*)server));
|
||||
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void*)server));
|
||||
US_A(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void*)server));
|
||||
US_A(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void*)server));
|
||||
US_A(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void*)server));
|
||||
}
|
||||
|
||||
us_frame_copy(stream->run->blank->jpeg, ex->frame);
|
||||
|
||||
assert((run->refresher = event_new(run->base, -1, 0, _http_refresher, server)) != NULL);
|
||||
US_A((run->refresher = event_new(run->base, -1, 0, _http_refresher, server)) != NULL);
|
||||
stream->run->http->jpeg_refresher = run->refresher;
|
||||
|
||||
evhttp_set_timeout(run->http, server->timeout);
|
||||
|
||||
if (server->user[0] != '\0') {
|
||||
if (us_str_is_ok(server->user)) {
|
||||
char *encoded_token = NULL;
|
||||
|
||||
char *raw_token;
|
||||
@@ -217,7 +216,7 @@ int us_server_listen(us_server_s *server) {
|
||||
_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);
|
||||
if ((run->ext_fd = us_evhttp_bind_unix(
|
||||
run->http,
|
||||
@@ -265,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());
|
||||
|
||||
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_method = us_evhttp_get_header(req, "Access-Control-Request-Method");
|
||||
|
||||
@@ -612,7 +611,7 @@ static void _http_callback_stream(struct evhttp_request *req, void *v_server) {
|
||||
if (server->tcp_nodelay && run->ext_fd >= 0) {
|
||||
_LOG_DEBUG("Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
||||
const evutil_socket_t fd = bufferevent_getfd(buf_event);
|
||||
assert(fd >= 0);
|
||||
US_A(fd >= 0);
|
||||
int on = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) != 0) {
|
||||
_LOG_PERROR("Can't set TCP_NODELAY to the client %s", client->hostport);
|
||||
@@ -667,7 +666,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
if (client->need_initial) {
|
||||
_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_method = us_evhttp_get_header(client->req, "Access-Control-Request-Method");
|
||||
|
||||
@@ -694,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
|
||||
RN
|
||||
"--" BOUNDARY RN,
|
||||
(server->instance_id[0] == '\0' ? "" : "_"),
|
||||
server->instance_id,
|
||||
(us_str_is_ok(server->instance_id) ? "_" : ""),
|
||||
(us_str_is_ok(server->instance_id) ? server->instance_id : ""),
|
||||
(client->key != NULL ? client->key : "0"),
|
||||
client->id);
|
||||
|
||||
@@ -703,7 +702,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
ADD_ADVANCE_HEADERS;
|
||||
}
|
||||
|
||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||
US_A(!bufferevent_write_buffer(buf_event, buf));
|
||||
client->need_initial = false;
|
||||
}
|
||||
|
||||
@@ -763,7 +762,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
ADD_ADVANCE_HEADERS;
|
||||
}
|
||||
|
||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||
US_A(!bufferevent_write_buffer(buf_event, buf));
|
||||
evbuffer_free(buf);
|
||||
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void*)client);
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
#include "../stream.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
typedef struct us_stream_client_sx {
|
||||
struct us_server_sx *server;
|
||||
struct evhttp_request *req;
|
||||
|
||||
@@ -54,15 +54,15 @@ typedef struct {
|
||||
|
||||
us_fpsi_s *fpsi;
|
||||
|
||||
US_LIST_DECLARE;
|
||||
US_LIST_DECLARE(struct us_stream_client_sx);
|
||||
} us_stream_client_s;
|
||||
|
||||
typedef struct {
|
||||
typedef struct us_snapshot_client_sx {
|
||||
struct us_server_sx *server;
|
||||
struct evhttp_request *req;
|
||||
ldf req_ts;
|
||||
|
||||
US_LIST_DECLARE;
|
||||
US_LIST_DECLARE(struct us_snapshot_client_sx);
|
||||
} us_snapshot_client_s;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
@@ -40,13 +39,13 @@ char *us_find_static_file_path(const char *root_path, const char *req_path) {
|
||||
char *path = NULL;
|
||||
|
||||
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);
|
||||
goto error;
|
||||
}
|
||||
|
||||
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||
assert(sprintf(path, "%s/%s", root_path, simplified_path) > 0);
|
||||
US_A(sprintf(path, "%s/%s", root_path, simplified_path) > 0);
|
||||
|
||||
struct stat st;
|
||||
# define LOAD_STAT { \
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "systemd.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <event2/http.h>
|
||||
#include <event2/util.h>
|
||||
@@ -47,7 +46,7 @@ evutil_socket_t us_evhttp_bind_systemd(struct evhttp *http) {
|
||||
}
|
||||
fd = SD_LISTEN_FDS_START;
|
||||
|
||||
assert(!evutil_make_socket_nonblocking(fd));
|
||||
US_A(!evutil_make_socket_nonblocking(fd));
|
||||
|
||||
if (evhttp_accept_socket(http, fd) < 0) {
|
||||
US_LOG_PERROR("HTTP: Can't evhttp_accept_socket() systemd socket");
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
@@ -54,8 +53,8 @@ evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
const evutil_socket_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
assert(fd >= 0);
|
||||
assert(!evutil_make_socket_nonblocking(fd));
|
||||
US_A(fd >= 0);
|
||||
US_A(!evutil_make_socket_nonblocking(fd));
|
||||
|
||||
if (rm && unlink(path) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
@@ -99,7 +98,7 @@ char *us_evhttp_get_hostport(struct evhttp_request *req) {
|
||||
const char *xff = us_evhttp_get_header(req, "X-Forwarded-For");
|
||||
if (xff != NULL) {
|
||||
US_DELETE(addr, free);
|
||||
assert((addr = strndup(xff, 1024)) != NULL);
|
||||
US_A((addr = strndup(xff, 1024)) != NULL);
|
||||
for (uint i = 0; addr[i]; ++i) {
|
||||
if (addr[i] == ',') {
|
||||
addr[i] = '\0';
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
@@ -89,7 +88,7 @@ us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path,
|
||||
double bitrate = log10(quality) * (b_max - b_min) / 2 + b_min;
|
||||
bitrate = step * round(bitrate / step);
|
||||
bitrate *= 1000; // From Kbps
|
||||
assert(bitrate > 0);
|
||||
US_A(bitrate > 0);
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, bitrate, 0, 0, true, false);
|
||||
}
|
||||
|
||||
@@ -417,7 +416,7 @@ static int _m2m_encoder_init_buffers(
|
||||
_LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr);
|
||||
goto error;
|
||||
}
|
||||
assert((*bufs_ptr)[*n_bufs_ptr].data != NULL);
|
||||
US_A((*bufs_ptr)[*n_bufs_ptr].data != NULL);
|
||||
(*bufs_ptr)[*n_bufs_ptr].allocated = plane.length;
|
||||
|
||||
_LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
@@ -492,7 +491,7 @@ static int _m2m_encoder_compress_raw(
|
||||
) {
|
||||
us_m2m_encoder_runtime_s *const run = enc->run;
|
||||
|
||||
assert(run->ready);
|
||||
US_A(run->ready);
|
||||
|
||||
if (force_key) {
|
||||
struct v4l2_control ctl = {0};
|
||||
|
||||
@@ -46,10 +46,10 @@ static us_server_s *_g_server = NULL;
|
||||
|
||||
static void _block_thread_signals(void) {
|
||||
sigset_t mask;
|
||||
assert(!sigemptyset(&mask));
|
||||
assert(!sigaddset(&mask, SIGINT));
|
||||
assert(!sigaddset(&mask, SIGTERM));
|
||||
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
||||
US_A(!sigemptyset(&mask));
|
||||
US_A(!sigaddset(&mask, SIGINT));
|
||||
US_A(!sigaddset(&mask, SIGTERM));
|
||||
US_A(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
||||
}
|
||||
|
||||
static void *_stream_loop_thread(void *arg) {
|
||||
@@ -77,7 +77,7 @@ static void _signal_handler(int signum) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
assert(argc >= 0);
|
||||
US_A(argc >= 0);
|
||||
int exit_code = 0;
|
||||
|
||||
US_LOGGING_INIT;
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
#include <limits.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/process.h"
|
||||
#include "../libs/frame.h"
|
||||
@@ -93,6 +93,8 @@ enum _US_OPT_VALUES {
|
||||
_O_DEVICE_ERROR_DELAY,
|
||||
_O_FORMAT_SWAP_RGB,
|
||||
_O_M2M_DEVICE,
|
||||
_O_MEDIA_DEVICE,
|
||||
_O_MEDIA_ENTITY_NAME,
|
||||
|
||||
_O_IMAGE_DEFAULT,
|
||||
_O_BRIGHTNESS,
|
||||
@@ -154,6 +156,9 @@ enum _US_OPT_VALUES {
|
||||
# endif
|
||||
_O_NOTIFY_PARENT,
|
||||
|
||||
_O_CPU_SCALING_GOVERNOR_IDLE,
|
||||
_O_CPU_SCALING_GOVERNOR_ACTIVE,
|
||||
|
||||
_O_LOG_LEVEL,
|
||||
_O_PERF,
|
||||
_O_VERBOSE,
|
||||
@@ -188,6 +193,8 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT},
|
||||
{"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY},
|
||||
{"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},
|
||||
{"brightness", required_argument, NULL, _O_BRIGHTNESS},
|
||||
@@ -266,6 +273,9 @@ static const struct option _LONG_OPTS[] = {
|
||||
# endif
|
||||
{"notify-parent", no_argument, NULL, _O_NOTIFY_PARENT},
|
||||
|
||||
{"cpu-scaling-governor-idle", required_argument, NULL, _O_CPU_SCALING_GOVERNOR_IDLE},
|
||||
{"cpu-scaling-governor-active", required_argument, NULL, _O_CPU_SCALING_GOVERNOR_ACTIVE},
|
||||
|
||||
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
|
||||
{"perf", no_argument, NULL, _O_PERF},
|
||||
{"verbose", no_argument, NULL, _O_VERBOSE},
|
||||
@@ -300,10 +310,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;
|
||||
}
|
||||
|
||||
@@ -358,7 +369,7 @@ int us_options_parse(
|
||||
printf("Invalid height of '%s=%s': min=%u, max=%u\n", x_name, optarg, US_VIDEO_MIN_HEIGHT, US_VIDEO_MAX_HEIGHT); \
|
||||
return -1; \
|
||||
case 0: break; \
|
||||
default: assert(0 && "Unknown error"); \
|
||||
default: US_RAISE("Unknown error"); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
@@ -374,15 +385,15 @@ int us_options_parse(
|
||||
}
|
||||
|
||||
# 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) { \
|
||||
if (!strcasecmp(optarg, "default")) { \
|
||||
OPT_CTL_DEFAULT_NOBREAK(x_dest); \
|
||||
} else { \
|
||||
cap->ctl.x_dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, cap->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
cap->ctl->x_dest.mode = US_CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, cap->ctl->x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
@@ -391,10 +402,10 @@ int us_options_parse(
|
||||
if (!strcasecmp(optarg, "default")) { \
|
||||
OPT_CTL_DEFAULT_NOBREAK(x_dest); \
|
||||
} else if (!strcasecmp(optarg, "auto")) { \
|
||||
cap->ctl.x_dest.mode = CTL_MODE_AUTO; \
|
||||
cap->ctl->x_dest.mode = US_CTL_MODE_AUTO; \
|
||||
} else { \
|
||||
cap->ctl.x_dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, cap->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
cap->ctl->x_dest.mode = US_CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, cap->ctl->x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
@@ -445,6 +456,8 @@ int us_options_parse(
|
||||
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_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:
|
||||
OPT_CTL_DEFAULT_NOBREAK(brightness);
|
||||
@@ -543,6 +556,9 @@ int us_options_parse(
|
||||
# endif
|
||||
case _O_NOTIFY_PARENT: OPT_SET(stream->notify_parent, true);
|
||||
|
||||
case _O_CPU_SCALING_GOVERNOR_IDLE: OPT_SET(stream->governor_idle, optarg);
|
||||
case _O_CPU_SCALING_GOVERNOR_ACTIVE: OPT_SET(stream->governor_active, optarg);
|
||||
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(us_g_log_level, US_LOG_LEVEL_VERBOSE);
|
||||
@@ -562,7 +578,7 @@ int us_options_parse(
|
||||
US_LOG_INFO("Starting PiKVM uStreamer %s ...", US_VERSION);
|
||||
|
||||
# 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( \
|
||||
x_label, \
|
||||
x_prefix##_name, \
|
||||
@@ -704,7 +720,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 +728,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);
|
||||
@@ -737,6 +753,10 @@ static void _help(
|
||||
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(" --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("══════════════════════");
|
||||
SAY(" --image-default ────────────────────── Reset all image settings below to default. Default: no change.\n");
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <pthread.h>
|
||||
@@ -95,7 +94,8 @@ static void _stream_drm_ensure_no_signal(us_stream_s *stream);
|
||||
static void _stream_expose_jpeg(us_stream_s *stream, const us_frame_s *frame);
|
||||
static void _stream_expose_raw(us_stream_s *stream, const us_frame_s *frame);
|
||||
static void _stream_encode_expose_h264(us_stream_s *stream, const us_frame_s *frame, bool force_key);
|
||||
static void _stream_check_suicide(us_stream_s *stream);
|
||||
static void _stream_check_suicide(us_stream_s *stream, bool has_any_clients);
|
||||
static void _stream_set_governor(us_stream_s *stream, bool active);
|
||||
|
||||
|
||||
us_stream_s *us_stream_init(us_capture_s *cap, us_encoder_s *enc) {
|
||||
@@ -199,13 +199,13 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
CREATE_WORKER((stream->raw_sink != NULL), raw_ctx, _raw_thread, 2);
|
||||
CREATE_WORKER((stream->h264_sink != NULL), h264_ctx, _h264_thread, cap->run->n_bufs);
|
||||
# ifdef WITH_V4P
|
||||
CREATE_WORKER((stream->drm != NULL), drm_ctx, _drm_thread, cap->run->n_bufs); // cppcheck-suppress assertWithSideEffect
|
||||
CREATE_WORKER((stream->drm != NULL), drm_ctx, _drm_thread, cap->run->n_bufs);
|
||||
# endif
|
||||
# undef CREATE_WORKER
|
||||
|
||||
US_LOG_INFO("Capturing ...");
|
||||
|
||||
uint slowdown_count = 0;
|
||||
int has_any_clients_prev = -1;
|
||||
while (!atomic_load(&run->stop) && !atomic_load(&threads_stop)) {
|
||||
us_capture_hwbuf_s *hw;
|
||||
switch (us_capture_hwbuf_grab(cap, &hw)) {
|
||||
@@ -234,17 +234,23 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
us_queue_put(releasers[hw->buf.index].q, hw, 0); // Plan to release
|
||||
|
||||
// Мы не обновляем здесь состояние синков, потому что это происходит внутри обслуживающих их потоков
|
||||
_stream_check_suicide(stream);
|
||||
if (stream->slowdown && !_stream_has_any_clients_cached(stream)) {
|
||||
const bool has_any_clients = _stream_has_any_clients_cached(stream);
|
||||
|
||||
if (has_any_clients_prev != has_any_clients) {
|
||||
_stream_set_governor(stream, has_any_clients);
|
||||
has_any_clients_prev = has_any_clients;
|
||||
}
|
||||
|
||||
_stream_check_suicide(stream, has_any_clients);
|
||||
|
||||
if (stream->slowdown && !has_any_clients) {
|
||||
usleep(100 * 1000);
|
||||
slowdown_count = (slowdown_count + 1) % 10;
|
||||
if (slowdown_count > 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close:
|
||||
_stream_set_governor(stream, false);
|
||||
|
||||
atomic_store(&threads_stop, true);
|
||||
|
||||
# define DELETE_WORKER(x_ctx) if (x_ctx != NULL) { \
|
||||
@@ -273,7 +279,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,7 +534,7 @@ static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *q) {
|
||||
}
|
||||
while (!us_queue_is_empty(q)) { // Берем только самый свежий кадр
|
||||
us_capture_hwbuf_decref(hw);
|
||||
assert(!us_queue_get(q, (void**)&hw, 0));
|
||||
US_A(!us_queue_get(q, (void**)&hw, 0));
|
||||
}
|
||||
return hw;
|
||||
}
|
||||
@@ -572,7 +578,7 @@ static int _stream_init_loop(us_stream_s *stream) {
|
||||
UPDATE_SINK(stream->h264_sink);
|
||||
# undef UPDATE_SINK
|
||||
|
||||
_stream_check_suicide(stream);
|
||||
_stream_check_suicide(stream, _stream_has_any_clients_cached(stream));
|
||||
|
||||
stream->cap->dma_export = (
|
||||
stream->enc->type == US_ENCODER_TYPE_M2M_VIDEO
|
||||
@@ -759,22 +765,24 @@ static void _stream_encode_expose_h264(us_stream_s *stream, const us_frame_s *fr
|
||||
force_key = true;
|
||||
}
|
||||
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:
|
||||
us_fpsi_update(run->http->h264_fpsi, meta.online, &meta);
|
||||
}
|
||||
|
||||
static void _stream_check_suicide(us_stream_s *stream) {
|
||||
static void _stream_check_suicide(us_stream_s *stream, bool has_any_clients) {
|
||||
if (stream->exit_on_no_clients == 0) {
|
||||
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)) {
|
||||
if (has_any_clients) {
|
||||
atomic_store(last_req_ts, now_ts);
|
||||
} else if (atomic_load(last_req_ts) + stream->exit_on_no_clients < now_ts) {
|
||||
US_LOG_INFO("No requests or HTTP/sink clients found in last %u seconds, exiting ...",
|
||||
@@ -783,3 +791,26 @@ static void _stream_check_suicide(us_stream_s *stream) {
|
||||
us_process_suicide();
|
||||
}
|
||||
}
|
||||
|
||||
static void _stream_set_governor(us_stream_s *stream, bool active) {
|
||||
if (!us_str_is_ok(stream->governor_idle) || !us_str_is_ok(stream->governor_active)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *const path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor";
|
||||
const int fd = open(path, O_RDWR);
|
||||
if (fd < 0) {
|
||||
US_LOG_PERROR("Can't open CPU governor file");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *const gov = (active ? stream->governor_active : stream->governor_idle);
|
||||
const sz len = strlen(gov);
|
||||
if (write(fd, gov, len) != len) {
|
||||
US_LOG_PERROR("Can't set CPU governor to %s", gov);
|
||||
} else {
|
||||
US_LOG_INFO("CPU governor changed to %s", gov);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,9 @@ typedef struct {
|
||||
bool exit_on_device_error;
|
||||
uint exit_on_no_clients;
|
||||
|
||||
char *governor_idle;
|
||||
char *governor_active;
|
||||
|
||||
us_memsink_s *jpeg_sink;
|
||||
us_memsink_s *raw_sink;
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ us_worker_s *us_workers_pool_wait(us_workers_pool_s *pool) {
|
||||
found = wr;
|
||||
}
|
||||
});
|
||||
assert(found != NULL);
|
||||
US_A(found != NULL);
|
||||
US_LIST_REMOVE(pool->workers, found);
|
||||
US_LIST_APPEND(pool->workers, found); // Перемещаем в конец списка
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ typedef struct us_worker_sx {
|
||||
|
||||
struct us_workers_pool_sx *pool;
|
||||
|
||||
US_LIST_DECLARE;
|
||||
US_LIST_DECLARE(struct us_worker_sx);
|
||||
} us_worker_s;
|
||||
|
||||
typedef void *(*us_workers_pool_job_init_f)(void *arg);
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@@ -245,11 +244,11 @@ static void _main_loop(void) {
|
||||
static void *_follower_thread(void *v_unix_follow) { // cppcheck-suppress constParameterCallback
|
||||
US_THREAD_SETTLE("follower");
|
||||
const char *path = v_unix_follow;
|
||||
assert(path != NULL);
|
||||
US_A(path != NULL);
|
||||
|
||||
while (!atomic_load(&_g_stop)) {
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
assert(fd >= 0);
|
||||
US_A(fd >= 0);
|
||||
|
||||
struct sockaddr_un addr = {0};
|
||||
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
|
||||
|
||||
Reference in New Issue
Block a user