Compare commits

...

32 Commits
v6.60 ... 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
46 changed files with 1142 additions and 549 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 "types.h"
#include "threading.h"
#ifndef V4L2_CID_USER_TC358743_BASE
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
#endif
#ifndef TC358743_CID_AUDIO_SAMPLING_RATE
# define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
#endif
#ifndef TC358743_CID_AUDIO_PRESENT
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
#endif
#ifndef TC358743_CID_LANES_ENOUGH
# define TC358743_CID_LANES_ENOUGH (V4L2_CID_USER_TC358743_BASE + 2)
#endif
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz);
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
us_thread_get_name(m_tname_buf); \
JANUS_LOG(LOG_INFO, "== ustreamer/%-15s " x_label " -- " x_msg "\n", m_tname_buf, ##__VA_ARGS__); \
}

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 \
vim \
git \
libjpeg \
libjpeg-turbo \
libevent \
libutil-linux \
libbsd \

View File

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

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 6.60" "November 2020"
.TH USTREAMER 1 "version 6.62" "November 2020"
.SH NAME
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
.BR \-\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
.TP
.BR \-\-media\-device \fI/dev/path
Path to V4L2 /dev/media* device for setting subdevices (currently necessary for Raspberry Pi 5). Default: unset.
.TP
.BR \-\-media\-entity\-name \fIname
Name of the V4L2 entity to grab video from, will be router to \-\-device, for example "tc358743 10-000f". Default: unset.
.SS "Image control options"
.TP

View File

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

View File

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

View File

@@ -34,7 +34,7 @@ def main() -> None:
flags = _find_flags()
setup(
name="ustreamer",
version="6.60",
version="6.62",
description="uStreamer tools",
author="Maxim Devaev",
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_ts = us_get_now_monotonic();
if (key_required) {
mem->key_requested = true;
mem->wants.key = true;
}
if (flock(self->fd, LOCK_UN) < 0) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,10 +22,10 @@
#pragma once
#include "uslibs/types.h"
#include "uslibs/frame.h"
#include "uslibs/memsinksh.h"
#include "types.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s *mem, u64 last_id);
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required);
int us_chip_check_cable(int fd);
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_MINOR 60
#define US_VERSION_MINOR 62
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)

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

View File

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

View File

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

View File

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

View File

@@ -20,35 +20,29 @@
*****************************************************************************/
#include "tc358743.h"
#pragma once
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include <pthread.h>
#include "types.h"
#include "tools.h"
#include "xioctl.h"
#include "threading.h"
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz) {
*audio_hz = 0;
struct v4l2_control ctl = {.id = TC358743_CID_AUDIO_PRESENT};
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
return -1;
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
us_thread_get_name(m_tname_buf); \
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); \
}
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(
const char *name, const char *obj, bool server,
mode_t mode, bool rm, uint client_ttl, uint timeout) {
mode_t mode, bool rm, uint client_ttl, uint timeout
) {
us_memsink_s *sink;
US_CALLOC(sink, 1);
sink->name = name;
@@ -160,7 +160,11 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
return false;
}
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested) {
int us_memsink_server_put(
us_memsink_s *sink,
const us_frame_s *frame,
us_memsink_wants_s *wants
) {
US_A(sink->server);
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);
sink->mem->id = us_get_now_id();
if (sink->mem->key_requested && frame->key) {
sink->mem->key_requested = false;
if (sink->mem->wants.key && frame->key) {
sink->mem->wants.key = false;
}
if (key_requested != NULL) { // We don't need it for non-H264 sinks
*key_requested = sink->mem->key_requested;
if (wants != NULL) {
memcpy(wants, &sink->mem->wants, sizeof(us_memsink_wants_s));
}
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->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) {
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;
}
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
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;
us_frame_set_data(frame, us_memsink_get_data(sink->mem), sink->mem->used);
US_FRAME_COPY_META(sink->mem, frame);
if (key_requested != NULL) { // We don't need it for non-H264 sinks
*key_requested = sink->mem->key_requested;
if (get != NULL) {
memcpy(get, &sink->mem->wants, sizeof(us_memsink_wants_s));
}
if (key_required) {
sink->mem->key_requested = true;
if (put != NULL) {
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:

View File

@@ -57,6 +57,14 @@ us_memsink_s *us_memsink_init_opened(
void us_memsink_destroy(us_memsink_s *sink);
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested);
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required);
int us_memsink_server_put(
us_memsink_s *sink,
const us_frame_s *frame,
us_memsink_wants_s *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_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 {
u64 magic;
u32 version;
@@ -37,7 +45,7 @@ typedef struct {
uz used;
ldf last_client_ts;
bool key_requested;
us_memsink_wants_s wants;
US_FRAME_META_DECLARE;
} 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
int retval = -1;
# if defined(__linux__) || defined (__NetBSD__)
retval = pthread_getname_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
retval = pthread_getname_np(pthread_self(), name, US_THREAD_NAME_SIZE); // Always null-terminated
# elif \
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|| defined(__DragonFly__)
pthread_get_name_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
if (name[0] != '\0') {
pthread_get_name_np(pthread_self(), name, US_THREAD_NAME_SIZE); // Also null-terminated
if (us_str_is_ok(name)) {
retval = 0;
}
# else

View File

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

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;
while (jpeg->next_scanline < frame->height) {
if (jpeg->next_scanline > 0 && jpeg->next_scanline % 2 == 0) {
chroma1_data += (frame->width + padding) >> 1;
chroma2_data += (frame->width + padding) >> 1;
}
u8 *ptr = line_buf;
for (uint x = 0; x < frame->width; ++x) {
@@ -253,11 +258,6 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
data += frame->width + padding;
if (jpeg->next_scanline > 0 && jpeg->next_scanline % 2 == 0) {
chroma1_data += (frame->width + padding) >> 1;
chroma2_data += (frame->width + padding) >> 1;
}
JSAMPROW scanlines[1] = {line_buf};
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;
{
if (server->static_path[0] != '\0') {
if (us_str_is_ok(server->static_path)) {
_LOG_INFO("Enabling the file server: %s", server->static_path);
evhttp_set_gencb(run->http, _http_callback_static, (void*)server);
} else {
@@ -202,7 +202,7 @@ int us_server_listen(us_server_s *server) {
evhttp_set_timeout(run->http, server->timeout);
if (server->user[0] != '\0') {
if (us_str_is_ok(server->user)) {
char *encoded_token = NULL;
char *raw_token;
@@ -216,7 +216,7 @@ int us_server_listen(us_server_s *server) {
_LOG_INFO("Using HTTP basic auth");
}
if (server->unix_path[0] != '\0') {
if (us_str_is_ok(server->unix_path)) {
_LOG_DEBUG("Binding server to UNIX socket '%s' ...", server->unix_path);
if ((run->ext_fd = us_evhttp_bind_unix(
run->http,
@@ -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());
if (server->allow_origin[0] != '\0') {
if (us_str_is_ok(server->allow_origin)) {
const char *const cors_headers = us_evhttp_get_header(req, "Access-Control-Request-Headers");
const char *const cors_method = us_evhttp_get_header(req, "Access-Control-Request-Method");
@@ -666,7 +666,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
if (client->need_initial) {
_A_EVBUFFER_ADD_PRINTF(buf, "HTTP/1.0 200 OK" RN);
if (client->server->allow_origin[0] != '\0') {
if (us_str_is_ok(client->server->allow_origin)) {
const char *const cors_headers = us_evhttp_get_header(client->req, "Access-Control-Request-Headers");
const char *const cors_method = us_evhttp_get_header(client->req, "Access-Control-Request-Method");
@@ -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
RN
"--" BOUNDARY RN,
(server->instance_id[0] == '\0' ? "" : "_"),
server->instance_id,
(us_str_is_ok(server->instance_id) ? "_" : ""),
(us_str_is_ok(server->instance_id) ? server->instance_id : ""),
(client->key != NULL ? client->key : "0"),
client->id);

View File

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

View File

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

View File

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