Compare commits

...

35 Commits
v6.59 ... v6.62

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

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

From the user perspective, this adds two new options: --media-device and
--media-entity-name. When provided, ustreamer will find requested entity
and route it to the device given in --device option.
2026-06-30 15:33:01 +02:00
Marek Marczykowski-Górecki
730cc3c89e Add V4L fmt -> V4L subdev fmt translation
Will be used in the next patch.
I didn't found matching entries for V4L2_PIX_FMT_MJPEG, and
V4L2_PIX_FMT_YVU420 in linux/media-bus-format.h, so those are left as 0
(guaranteed invalid).
2026-06-30 00:35:48 +02:00
Maxim Devaev
686460fa29 lint fix 2026-06-12 21:08:41 +03:00
Maxim Devaev
54c1c77dfb janus: using common memsink lib 2026-06-12 19:01:13 +03:00
Maxim Devaev
0e0aef7b80 janus: using generic logging system 2026-06-12 17:53:32 +03:00
Maxim Devaev
31b98f1d59 fixed thread name getter 2026-06-12 17:31:28 +03:00
Maxim Devaev
9734c83bd4 extracted base logging function into a logging_base.h 2026-06-12 17:31:09 +03:00
Maxim Devaev
b88abdcae4 refactoring 2026-06-12 14:27:42 +03:00
Maxim Devaev
6fca17cf52 janus: PLI handling was fixed in Janus upstream
https://github.com/meetecho/janus-gateway/pull/3642
2026-06-08 13:02:25 +03:00
Maxim Devaev
5fe579956e Bump version: 6.59 → 6.60 2026-05-31 19:09:57 +03:00
Maxim Devaev
52dfa38a8f janus: camera basics 2026-05-31 18:28:53 +03:00
Maxim Devaev
8b76b59393 janus: _aplay_thread(): don't see at has_listeners 2026-05-31 17:05:34 +03:00
49 changed files with 1164 additions and 576 deletions

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <pthread.h> #include <pthread.h>
#include <janus/plugins/plugin.h> #include <janus/plugins/plugin.h>
@@ -35,11 +34,11 @@
#include "uslibs/types.h" #include "uslibs/types.h"
#include "uslibs/tools.h" #include "uslibs/tools.h"
#include "uslibs/threading.h" #include "uslibs/threading.h"
#include "uslibs/logging.h"
#include "uslibs/array.h" #include "uslibs/array.h"
#include "uslibs/list.h" #include "uslibs/list.h"
#include "uslibs/ring.h" #include "uslibs/ring.h"
#include "logging.h"
#include "au.h" #include "au.h"
#include "rtp.h" #include "rtp.h"
@@ -102,7 +101,7 @@ void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
us_ring_s *const ring = (rtp->video ? client->video_ring : client->acap_ring); us_ring_s *const ring = (rtp->video ? client->video_ring : client->acap_ring);
const int ri = us_ring_producer_acquire(ring, 0); const int ri = us_ring_producer_acquire(ring, 0);
if (ri < 0) { if (ri < 0) {
US_JLOG_ERROR("client", "Session %p %s ring is full", US_LOG_ERROR("Session %p %s ring is full",
client->session, (rtp->video ? "video" : "acap")); client->session, (rtp->video ? "video" : "acap"));
return; return;
} }
@@ -142,7 +141,7 @@ void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet) {
us_ring_s *const ring = client->aplay_enc_ring; us_ring_s *const ring = client->aplay_enc_ring;
const int ri = us_ring_producer_acquire(ring, 0); const int ri = us_ring_producer_acquire(ring, 0);
if (ri < 0) { if (ri < 0) {
// US_JLOG_ERROR("client", "Session %p aplay ring is full", client->session); // US_LOG_ERROR("Session %p aplay ring is full", client->session);
return; return;
} }
us_au_encoded_s *enc = ring->items[ri]; us_au_encoded_s *enc = ring->items[ri];
@@ -157,19 +156,19 @@ void us_janus_client_recv(us_janus_client_s *client, janus_plugin_rtp *packet) {
} }
static void *_video_thread(void *v_client) { static void *_video_thread(void *v_client) {
US_THREAD_SETTLE("us_cx_vid"); US_THREAD_SETTLE("us_cx_vcap");
return _video_or_acap_thread(v_client, true); return _video_or_acap_thread(v_client, true);
} }
static void *_acap_thread(void *v_client) { static void *_acap_thread(void *v_client) {
US_THREAD_SETTLE("us_cx_ac"); US_THREAD_SETTLE("us_cx_acap");
return _video_or_acap_thread(v_client, false); return _video_or_acap_thread(v_client, false);
} }
static void *_video_or_acap_thread(void *v_client, bool video) { static void *_video_or_acap_thread(void *v_client, bool video) {
us_janus_client_s *const client = v_client; us_janus_client_s *const client = v_client;
us_ring_s *const ring = (video ? client->video_ring : client->acap_ring); us_ring_s *const ring = (video ? client->video_ring : client->acap_ring);
assert(ring != NULL); US_A(ring != NULL);
while (!atomic_load(&client->stop)) { while (!atomic_load(&client->stop)) {
const int ri = us_ring_consumer_acquire(ring, 0.1); const int ri = us_ring_consumer_acquire(ring, 0.1);
@@ -236,13 +235,13 @@ static void *_video_or_acap_thread(void *v_client, bool video) {
} }
static void *_aplay_thread(void *v_client) { static void *_aplay_thread(void *v_client) {
US_THREAD_SETTLE("us_cx_ap"); US_THREAD_SETTLE("us_cx_aplay");
us_janus_client_s *const client = v_client; us_janus_client_s *const client = v_client;
int err; int err;
OpusDecoder *dec = opus_decoder_create(US_RTP_OPUS_HZ, US_RTP_OPUS_CH, &err); OpusDecoder *dec = opus_decoder_create(US_RTP_OPUS_HZ, US_RTP_OPUS_CH, &err);
assert(err == 0); US_A(err == 0);
while (!atomic_load(&client->stop)) { while (!atomic_load(&client->stop)) {
const int in_ri = us_ring_consumer_acquire(client->aplay_enc_ring, 0.1); const int in_ri = us_ring_consumer_acquire(client->aplay_enc_ring, 0.1);
@@ -258,7 +257,7 @@ static void *_aplay_thread(void *v_client) {
const int out_ri = us_ring_producer_acquire(client->aplay_pcm_ring, 0); const int out_ri = us_ring_producer_acquire(client->aplay_pcm_ring, 0);
if (out_ri < 0) { if (out_ri < 0) {
US_JLOG_ERROR("aplay", "OPUS decoder queue is full"); US_LOG_ERROR("OPUS decoder queue is full");
us_ring_consumer_release(client->aplay_enc_ring, in_ri); us_ring_consumer_release(client->aplay_enc_ring, in_ri);
continue; continue;
} }
@@ -271,7 +270,7 @@ static void *_aplay_thread(void *v_client) {
out->frames = frames; out->frames = frames;
} else { } else {
out->frames = 0; out->frames = 0;
US_JLOG_PERROR_OPUS(frames, "aplay", "Fatal: Can't decode OPUS to PCM frame"); US_LOG_PERROR_OPUS(frames, "Fatal: Can't decode OPUS to PCM frame");
} }
us_ring_producer_release(client->aplay_pcm_ring, out_ri); us_ring_producer_release(client->aplay_pcm_ring, out_ri);
} }

View File

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

View File

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

View File

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

View File

@@ -34,7 +34,7 @@
#include "rtpa.h" #include "rtpa.h"
char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay) { char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay, bool vplay) {
char *video_sdp; char *video_sdp;
{ {
// https://tools.ietf.org/html/rfc6184 // https://tools.ietf.org/html/rfc6184
@@ -56,9 +56,10 @@ char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay) {
"a=extmap:1/sendonly urn:3gpp:video-orientation" 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: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=extmap:3/sendonly http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time" RN
"a=sendonly" RN, "a=%s" RN,
pl, pl, pl, pl, pl, pl, pl, pl, pl, pl, pl, pl, pl, pl,
video_ssrc); video_ssrc,
(vplay ? "sendrecv" : "sendonly"));
} }
char *audio_sdp; char *audio_sdp;

View File

@@ -25,4 +25,4 @@
#include "uslibs/types.h" #include "uslibs/types.h"
char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay); char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay, bool vplay);

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

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

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

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

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

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

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

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

View File

@@ -20,28 +20,13 @@
*****************************************************************************/ *****************************************************************************/
#pragma once #include <janus/plugins/plugin.h>
#include <linux/v4l2-controls.h> #include "threading.h"
#include "types.h"
#ifndef V4L2_CID_USER_TC358743_BASE #define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080) char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
#endif us_thread_get_name(m_tname_buf); \
JANUS_LOG(LOG_INFO, "== ustreamer/%-15s " x_label " -- " x_msg "\n", m_tname_buf, ##__VA_ARGS__); \
#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);

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer. .\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos .\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 6.59" "November 2020" .TH USTREAMER 1 "version 6.62" "November 2020"
.SH NAME .SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network ustreamer \- stream MJPEG video from any V4L2 device to the network
@@ -120,6 +120,12 @@ Delay before trying to connect to the device again after an error (timeout for e
.TP .TP
.BR \-\-m2m\-device\ \fI/dev/path .BR \-\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select. Path to V4L2 mem-to-mem encoder device. Default: auto-select.
.TP
.BR \-\-media\-device \fI/dev/path
Path to V4L2 /dev/media* device for setting subdevices (currently necessary for Raspberry Pi 5). Default: unset.
.TP
.BR \-\-media\-entity\-name \fIname
Name of the V4L2 entity to grab video from, will be router to \-\-device, for example "tc358743 10-000f". Default: unset.
.SS "Image control options" .SS "Image control options"
.TP .TP

View File

@@ -3,13 +3,13 @@
pkgname=ustreamer pkgname=ustreamer
pkgver=6.59 pkgver=6.62
pkgrel=1 pkgrel=1
pkgdesc="Lightweight and fast MJPEG-HTTP streamer" pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
url="https://github.com/pikvm/ustreamer" url="https://github.com/pikvm/ustreamer"
license=(GPL) license=(GPL)
arch=(i686 x86_64 armv6h armv7h aarch64) arch=(i686 x86_64 armv6h armv7h aarch64)
depends=(libjpeg libevent libbsd libgpiod systemd) depends=(libjpeg-turbo libevent libbsd libgpiod systemd)
makedepends=(gcc make pkgconf systemd) makedepends=(gcc make pkgconf systemd)
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}") source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP) md5sums=(SKIP)

View File

@@ -9,7 +9,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer PKG_NAME:=ustreamer
PKG_VERSION:=6.59 PKG_VERSION:=6.62
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com> PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,10 +22,10 @@
#pragma once #pragma once
#include "uslibs/types.h" #include "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_chip_check_cable(int fd);
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required);
int us_chip_tc358743_check_lanes(int fd);
int us_chip_tc358743_get_audio_hz(int fd);

View File

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

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

@@ -0,0 +1,173 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "controls.h"
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "types.h"
#include "tools.h"
#include "logging.h"
#include "xioctl.h"
static int _query_control(
int fd,
struct v4l2_queryctrl *query,
const char *name,
uint cid,
bool quiet);
static void _set_control(
int fd,
const struct v4l2_queryctrl *query,
const char *name,
uint cid,
int value,
bool quiet);
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("CAP: " x_msg, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("CAP: " x_msg, ##__VA_ARGS__)
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("CAP: " x_msg, ##__VA_ARGS__)
us_controls_s *us_controls_init(void) {
us_controls_s *ctl;
US_CALLOC(ctl, 1);
return ctl;
}
void us_controls_destroy(us_controls_s *ctl) {
free(ctl);
}
void us_controls_apply(const us_controls_s *ctl, int fd) {
# define SET_CID_VALUE(x_cid, x_field, x_value, x_quiet) { \
struct v4l2_queryctrl m_query; \
if (_query_control(fd, &m_query, #x_field, x_cid, x_quiet) == 0) { \
_set_control(fd, &m_query, #x_field, x_cid, x_value, x_quiet); \
} \
}
# define SET_CID_DEFAULT(x_cid, x_field, x_quiet) { \
struct v4l2_queryctrl m_query; \
if (_query_control(fd, &m_query, #x_field, x_cid, x_quiet) == 0) { \
_set_control(fd, &m_query, #x_field, x_cid, m_query.default_value, x_quiet); \
} \
}
# define CONTROL_MANUAL_CID(x_cid, x_field) { \
if (ctl->x_field.mode == US_CTL_MODE_VALUE) { \
SET_CID_VALUE(x_cid, x_field, ctl->x_field.value, false); \
} else if (ctl->x_field.mode == US_CTL_MODE_DEFAULT) { \
SET_CID_DEFAULT(x_cid, x_field, false); \
} \
}
# define CONTROL_AUTO_CID(x_cid_auto, x_cid_manual, x_field) { \
if (ctl->x_field.mode == US_CTL_MODE_VALUE) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 0, true); \
SET_CID_VALUE(x_cid_manual, x_field, ctl->x_field.value, false); \
} else if (ctl->x_field.mode == US_CTL_MODE_AUTO) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 1, false); \
} else if (ctl->x_field.mode == US_CTL_MODE_DEFAULT) { \
SET_CID_VALUE(x_cid_auto, x_field##_auto, 0, true); /* Reset inactive flag */ \
SET_CID_DEFAULT(x_cid_manual, x_field, false); \
SET_CID_DEFAULT(x_cid_auto, x_field##_auto, false); \
} \
}
CONTROL_AUTO_CID (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness);
CONTROL_MANUAL_CID ( V4L2_CID_CONTRAST, contrast);
CONTROL_MANUAL_CID ( V4L2_CID_SATURATION, saturation);
CONTROL_AUTO_CID (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue);
CONTROL_MANUAL_CID ( V4L2_CID_GAMMA, gamma);
CONTROL_MANUAL_CID ( V4L2_CID_SHARPNESS, sharpness);
CONTROL_MANUAL_CID ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
CONTROL_AUTO_CID (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
CONTROL_AUTO_CID (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
CONTROL_MANUAL_CID ( V4L2_CID_COLORFX, color_effect);
CONTROL_MANUAL_CID ( V4L2_CID_ROTATE, rotate);
CONTROL_MANUAL_CID ( V4L2_CID_VFLIP, flip_vertical);
CONTROL_MANUAL_CID ( V4L2_CID_HFLIP, flip_horizontal);
# undef CONTROL_AUTO_CID
# undef CONTROL_MANUAL_CID
# undef SET_CID_DEFAULT
# undef SET_CID_VALUE
}
static int _query_control(
int fd,
struct v4l2_queryctrl *query,
const char *name,
uint cid,
bool quiet
) {
// cppcheck-suppress redundantPointerOp
US_MEMSET_ZERO(*query);
query->id = cid;
if (us_xioctl(fd, VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
if (!quiet) {
_LOG_ERROR("Changing control %s is unsupported", name);
}
return -1;
}
return 0;
}
static void _set_control(
int fd,
const struct v4l2_queryctrl *query,
const char *name,
uint cid,
int value,
bool quiet
) {
if (value < query->minimum || value > query->maximum || value % query->step != 0) {
if (!quiet) {
_LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
value, name, query->minimum, query->maximum, query->default_value, query->step);
}
return;
}
struct v4l2_control ctl = {
.id = cid,
.value = value,
};
if (us_xioctl(fd, VIDIOC_S_CTRL, &ctl) < 0) {
if (!quiet) {
_LOG_PERROR("Can't set control %s", name);
}
} else if (!quiet) {
_LOG_INFO("Applying control %s: %d", name, ctl.value);
}
}

View File

@@ -20,24 +20,41 @@
*****************************************************************************/ *****************************************************************************/
#include <janus/plugins/plugin.h> #pragma once
#include "uslibs/tools.h" #include "types.h"
#include "const.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__) typedef enum {
#define US_JLOG_WARN(x_prefix, x_msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__) US_CTL_MODE_NONE = 0,
#define US_JLOG_ERROR(x_prefix, x_msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__) US_CTL_MODE_VALUE,
US_CTL_MODE_AUTO,
US_CTL_MODE_DEFAULT,
} us_control_mode_e;
#define US_JLOG_PERROR(x_prefix, x_msg, ...) { \ typedef struct {
char *const m_perror_str = us_errno_to_string(errno); \ us_control_mode_e mode;
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_str); \ int value;
free(m_perror_str); \ } us_control_s;
}
// We don't include alsa, speex and opus headers here typedef struct {
#define US_JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err)) us_control_s brightness;
#define US_JLOG_PERROR_RES(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err)) us_control_s contrast;
#define US_JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err)) us_control_s saturation;
us_control_s hue;
us_control_s gamma;
us_control_s sharpness;
us_control_s backlight_compensation;
us_control_s white_balance;
us_control_s gain;
us_control_s color_effect;
us_control_s rotate;
us_control_s flip_vertical;
us_control_s flip_horizontal;
} us_controls_s;
us_controls_s *us_controls_init(void);
void us_controls_destroy(us_controls_s *ctl);
void us_controls_apply(const us_controls_s *ctl, int fd);

View File

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

View File

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

View File

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

View File

@@ -20,35 +20,29 @@
*****************************************************************************/ *****************************************************************************/
#include "tc358743.h" #pragma once
#include <unistd.h> #include <stdio.h>
#include <fcntl.h> #include <stdlib.h>
#include <linux/videodev2.h> #include <pthread.h>
#include <linux/v4l2-controls.h>
#include "types.h" #include "types.h"
#include "tools.h" #include "tools.h"
#include "xioctl.h" #include "threading.h"
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz) { #define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
*audio_hz = 0; char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
us_thread_get_name(m_tname_buf); \
struct v4l2_control ctl = {.id = TC358743_CID_AUDIO_PRESENT}; if (us_g_log_colored) { \
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) { fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
return -1; " [%.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); \
} }
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;
}

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

@@ -0,0 +1,215 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "media.h"
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/sysmacros.h>
#include <linux/videodev2.h>
#include <linux/media.h>
#include <linux/v4l2-subdev.h>
#include "types.h"
#include "errors.h"
#include "tools.h"
#include "logging.h"
#include "xioctl.h"
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("CAP: " x_msg, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("CAP: " x_msg, ##__VA_ARGS__)
int us_media_find_link(
struct media_v2_topology *topology,
u32 link_type,
int source_id,
int sink_id,
u32 *link_flags
) {
struct media_v2_link *links = (struct media_v2_link *)topology->ptr_links;
for (uint i = 0; i < topology->num_links; ++i) {
if ((links[i].flags & MEDIA_LNK_FL_LINK_TYPE) != link_type) {
continue;
}
if (source_id >= 0 && (u32)source_id == links[i].source_id) {
if (link_flags) {
*link_flags = links[i].flags;
}
return links[i].sink_id;
}
if (sink_id >= 0 && (u32)sink_id == links[i].sink_id) {
if (link_flags) {
*link_flags = links[i].flags;
}
return links[i].source_id;
}
}
return -1;
}
int us_media_find_entity_by_name(
const struct media_v2_topology *topology,
const char *name
) {
const struct media_v2_entity *entities = (struct media_v2_entity *)topology->ptr_entities;
for (uint i = 0; i < topology->num_entities; ++i) {
if (!strcmp(entities[i].name, name)) {
return entities[i].id;
}
}
return -1;
}
int us_media_find_entity_by_devnode(
struct media_v2_topology *topology,
const dev_t devnode
) {
const struct media_v2_interface *interfaces = (struct media_v2_interface *)topology->ptr_interfaces;
for (uint i = 0; i < topology->num_interfaces; ++i) {
if (
interfaces[i].intf_type == MEDIA_INTF_T_V4L_VIDEO
&& interfaces[i].devnode.major == major(devnode)
&& interfaces[i].devnode.minor == minor(devnode)
) {
return us_media_find_link(
topology, MEDIA_LNK_FL_INTERFACE_LINK,
interfaces[i].id, -1, NULL);
}
}
return -1;
}
const struct media_v2_pad *us_media_find_pad_by_entity(
const struct media_v2_topology *topology,
u32 pad_type,
u32 entity_id
) {
const struct media_v2_pad *pads = (const struct media_v2_pad *)topology->ptr_pads;
for (uint i = 0; i < topology->num_pads; ++i) {
if ((pads[i].flags & pad_type) && pads[i].entity_id == entity_id) {
return &pads[i];
}
}
return NULL;
}
const struct media_v2_pad *us_media_find_pad(
const struct media_v2_topology *topology,
u32 pad_type,
u32 pad_id
) {
const struct media_v2_pad *pads = (const struct media_v2_pad *)topology->ptr_pads;
for (uint i = 0; i < topology->num_pads; ++i) {
if ((pads[i].flags & pad_type) && pads[i].id == pad_id) {
return &pads[i];
}
}
return NULL;
}
int us_media_xioctl_setup_link(
int fd,
const struct media_v2_pad *source_pad,
const struct media_v2_pad *sink_pad,
u32 link_flags
) {
struct media_link_desc link_desc = {
.source = {
.entity = source_pad->entity_id,
.index = source_pad->index,
},
.sink = {
.entity = sink_pad->entity_id,
.index = sink_pad->index,
},
.flags = link_flags,
};
return us_xioctl(fd, MEDIA_IOC_SETUP_LINK, &link_desc);
}
struct media_v2_topology *us_media_topology_init(int fd) {
struct media_v2_topology *topology = NULL;
struct media_device_info dev_info = {0};
if (us_xioctl(fd, MEDIA_IOC_DEVICE_INFO, &dev_info) < 0) {
_LOG_PERROR("Can't to query media device info");
goto error;
}
if (!MEDIA_V2_PAD_HAS_INDEX(dev_info.media_version)) {
_LOG_ERROR("Media topology doesn't have pad indices, too old kernel?");
goto error;
}
US_CALLOC(topology, 1);
if (us_xioctl(fd, MEDIA_IOC_G_TOPOLOGY, topology) < 0) {
_LOG_PERROR("Can't query media topology info");
goto error;
}
# define INIT_FIELD(x_field, x_type) { \
void *m_ptr = calloc(topology->num_##x_field, sizeof(x_type)); \
US_A(m_ptr); \
topology->ptr_##x_field = (uintptr_t)m_ptr; \
}
INIT_FIELD(entities, struct media_v2_entity);
INIT_FIELD(links, struct media_v2_link);
INIT_FIELD(interfaces, struct media_v2_interface);
INIT_FIELD(pads, struct media_v2_pad);
# undef INIT_FIELD
if (us_xioctl(fd, MEDIA_IOC_G_TOPOLOGY, topology) < 0) {
_LOG_PERROR("Failed to retrieve topology info");
goto error;
}
return topology;
error:
US_DELETE(topology, us_media_topology_destroy);
return NULL;
}
void us_media_topology_destroy(struct media_v2_topology *topology) {
# define DELETE_FIELD(x_field) { \
if (topology->ptr_##x_field) { \
free((void *)topology->ptr_##x_field); \
} \
}
DELETE_FIELD(pads);
DELETE_FIELD(interfaces);
DELETE_FIELD(links);
DELETE_FIELD(entities);
# undef DELETE_FIELD
free(topology);
}

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

@@ -0,0 +1,70 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <linux/videodev2.h>
#include <linux/media.h>
#include <linux/v4l2-subdev.h>
#include "types.h"
typedef struct {
int fd;
uint pad;
} us_media_pad_s;
int us_media_find_link(
struct media_v2_topology *topology,
u32 link_type,
int source_id,
int sink_id,
u32 *link_flags);
int us_media_find_entity_by_name(
const struct media_v2_topology *topology,
const char *name);
int us_media_find_entity_by_devnode(
struct media_v2_topology *topology,
const dev_t devnode);
const struct media_v2_pad *us_media_find_pad_by_entity(
const struct media_v2_topology *topology,
u32 pad_type,
u32 entity_id);
const struct media_v2_pad *us_media_find_pad(
const struct media_v2_topology *topology,
u32 pad_type,
u32 pad_id);
int us_media_xioctl_setup_link(
int fd,
const struct media_v2_pad *source_pad,
const struct media_v2_pad *sink_pad,
u32 link_flags);
struct media_v2_topology *us_media_topology_init(int fd);
void us_media_topology_destroy(struct media_v2_topology *topology);

View File

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

View File

@@ -57,6 +57,14 @@ us_memsink_s *us_memsink_init_opened(
void us_memsink_destroy(us_memsink_s *sink); void us_memsink_destroy(us_memsink_s *sink);
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame); bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested);
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required); int us_memsink_server_put(
us_memsink_s *sink,
const us_frame_s *frame,
us_memsink_wants_s *wants);
int us_memsink_client_get(
us_memsink_s *sink,
us_frame_s *frame,
us_memsink_wants_s *get,
const us_memsink_wants_s *put);

View File

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

View File

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

View File

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

View File

@@ -223,6 +223,11 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
const u8 *chroma2_data = frame->data + luma_array_size + chroma_array_size; const u8 *chroma2_data = frame->data + luma_array_size + chroma_array_size;
while (jpeg->next_scanline < frame->height) { while (jpeg->next_scanline < frame->height) {
if (jpeg->next_scanline > 0 && jpeg->next_scanline % 2 == 0) {
chroma1_data += (frame->width + padding) >> 1;
chroma2_data += (frame->width + padding) >> 1;
}
u8 *ptr = line_buf; u8 *ptr = line_buf;
for (uint x = 0; x < frame->width; ++x) { for (uint x = 0; x < frame->width; ++x) {
@@ -253,11 +258,6 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
data += frame->width + padding; data += frame->width + padding;
if (jpeg->next_scanline > 0 && jpeg->next_scanline % 2 == 0) {
chroma1_data += (frame->width + padding) >> 1;
chroma2_data += (frame->width + padding) >> 1;
}
JSAMPROW scanlines[1] = {line_buf}; JSAMPROW scanlines[1] = {line_buf};
jpeg_write_scanlines(jpeg, scanlines, 1); jpeg_write_scanlines(jpeg, scanlines, 1);
} }

View File

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

View File

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

View File

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

View File

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