Compare commits

...

72 Commits
v5.4 ... v5.17

Author SHA1 Message Date
Maxim Devaev
11f0b80228 Bump version: 5.16 → 5.17 2022-07-20 16:46:58 +03:00
Maxim Devaev
85e2dbd69e lint fix 2022-07-20 16:42:49 +03:00
Maxim Devaev
b693c24411 refactoring 2022-07-20 14:55:47 +03:00
Maxim Devaev
54af47fc43 fix 2022-07-20 13:04:18 +03:00
Maxim Devaev
1c1e3b0875 US_ARRAY_ITERATE() 2022-07-20 12:54:13 +03:00
Maxim Devaev
2c9334d53f refactoring, const 2022-07-20 11:20:48 +03:00
Maxim Devaev
5c747a5b5d refactoring 2022-07-20 06:05:05 +03:00
Maxim Devaev
cbee3adb2e using us_ prefixes 2022-07-19 11:02:36 +03:00
Maxim Devaev
e3293d6887 Bump version: 5.15 → 5.16 2022-07-16 23:01:02 +03:00
Maxim Devaev
9d1a42631e option for zero playout-delay 2022-07-16 12:15:51 +03:00
Maxim Devaev
13f522e81d check queue before free in macro 2022-07-16 12:02:55 +03:00
Maxim Devaev
28f13f7514 config structure 2022-07-16 11:51:43 +03:00
Maxim Devaev
2f86f818cc Bump version: 5.14 → 5.15 2022-07-16 06:31:36 +03:00
Maxim Devaev
1ffcd83993 commented playout-delay 2022-07-16 06:17:02 +03:00
Maxim Devaev
9b46c2e597 Bump version: 5.13 → 5.14 2022-07-16 02:16:02 +03:00
Maxim Devaev
ad1b63890a playout delay 2022-07-15 22:07:27 +03:00
Maxim Devaev
ad79bd0957 Revert "latency test"
This reverts commit 021823bcba.
2022-07-13 15:28:52 +03:00
Maxim Devaev
021823bcba latency test 2022-07-13 15:08:25 +03:00
Maxim Devaev
7b0e171e74 refactoring 2022-07-13 14:41:54 +03:00
Maxim Devaev
b24f106ce7 moved ready flag to _plugin_init() 2022-07-13 08:42:24 +03:00
Maxim Devaev
20f056668f separate memsink and rtp threads 2022-07-13 06:41:04 +03:00
Maxim Devaev
f9439c785f refactoring 2022-07-12 10:02:26 +03:00
Maxim Devaev
5e364fb88b refactoring 2022-07-12 08:58:55 +03:00
Maxim Devaev
69dc9b8b49 separate locks for audio and video 2022-07-11 07:48:23 +03:00
Maxim Devaev
42237d9728 reverted client lock 2022-07-11 00:22:16 +03:00
Maxim Devaev
17bd25d497 increased video queue size 2022-07-11 00:18:31 +03:00
Maxim Devaev
a2ac1f8067 fix 2022-07-10 23:35:48 +03:00
Maxim Devaev
fdb1b2d562 using 0 timeout in audio.c 2022-07-10 23:32:00 +03:00
Maxim Devaev
fd2bf5ea25 separate thread for each client 2022-07-10 23:31:35 +03:00
Maxim Devaev
c874929e9d long double queue timeout 2022-07-10 03:24:38 +03:00
Maxim Devaev
db5b9d3cd7 renamed jlogging.h to logging.h 2022-07-08 22:51:50 +03:00
Maxim Devaev
12ab66be43 refactoring 2022-07-08 22:48:03 +03:00
Maxim Devaev
27d25a59d8 refactoring 2022-07-08 20:50:38 +03:00
Maxim Devaev
71991254a5 renamed config.h to const.h 2022-07-08 20:33:30 +03:00
Maxim Devaev
50e8469a59 refactoring 2022-07-08 20:29:57 +03:00
Maxim Devaev
3f45debca0 refactoring 2022-07-08 07:43:32 +03:00
Maxim Devaev
627b614ab5 refactoring 2022-07-08 04:29:13 +03:00
Maxim Devaev
f11d390b22 Bump version: 5.12 → 5.13 2022-07-05 08:08:20 +03:00
Maxim Devaev
f1e50b6f9b refactoring, using h264 5.1 profile for resolutions > 1920x1080 2022-07-05 07:53:41 +03:00
Maxim Devaev
fdf3340a7d Bump version: 5.11 → 5.12 2022-07-05 00:50:48 +03:00
Maxim Devaev
02513be220 don't assert if m2m encoder is not successfully prepared 2022-07-05 00:48:22 +03:00
Maxim Devaev
d29ce42f08 Bump version: 5.10 → 5.11 2022-06-28 23:12:59 +03:00
Maxim Devaev
aa6fc7fe04 Bump version: 5.9 → 5.10 2022-06-28 23:03:46 +03:00
Maxim Devaev
c91341a375 fixed missing frame_encoding_begin() for noop encoder 2022-06-28 18:51:30 +03:00
Maxim Devaev
3de7e26a36 Bump version: 5.8 → 5.9 2022-06-09 03:37:10 +03:00
Maxim Devaev
63cc66e8a7 improved logging 2022-06-09 03:30:35 +03:00
Maxim Devaev
92a090dec3 Bump version: 5.7 → 5.8 2022-06-07 07:53:00 +03:00
Maxim Devaev
8b0ef8a271 renambed memsink.object to video.sink 2022-06-07 07:48:48 +03:00
Maxim Devaev
a360f1901e Bump version: 5.6 → 5.7 2022-06-07 05:32:38 +03:00
Maxim Devaev
ed2d5f3af4 not based 2022-06-07 05:25:05 +03:00
Maxim Devaev
b935dd1fe8 refactoring 2022-06-07 05:00:35 +03:00
Maxim Devaev
6e1f60a36d get rid of ATOMIC_VAR_INIT 2022-06-07 04:58:09 +03:00
Maxim Devaev
210dfcfa4f lint fix 2022-06-07 04:51:46 +03:00
Maxim Devaev
ec10a9e3fe using c17 2022-06-07 04:48:28 +03:00
Maxim Devaev
217d146378 log fix 2022-06-07 03:01:27 +03:00
Maxim Devaev
3e2a43e2af speed up cppcheck 2022-06-07 02:54:39 +03:00
Maxim Devaev
2e0a19c1cb tc358743 hacks 2022-06-06 20:26:09 +03:00
Maxim Devaev
054748234e refactoring 2022-06-06 19:15:55 +03:00
Maxim Devaev
53873e9ddb refactoring 2022-06-06 17:39:03 +03:00
Maxim Devaev
c21d0aef7e moved xioctl() to libs 2022-06-06 17:06:00 +03:00
Maxim Devaev
e505a56910 refactoring 2022-06-06 16:59:03 +03:00
Maxim Devaev
f4278f32c4 refactoring 2022-06-06 16:36:55 +03:00
Maxim Devaev
e9a6db02f6 refactoring 2022-06-06 16:17:34 +03:00
Maxim Devaev
63fe32ddd9 refactoring 2022-06-06 15:15:37 +03:00
Maxim Devaev
710652073a refactoring 2022-06-06 14:35:31 +03:00
Maxim Devaev
dded49cd83 resampler 2022-06-06 14:19:11 +03:00
Maxim Devaev
0f753dc654 queue fix 2022-06-06 02:04:16 +03:00
Maxim Devaev
c505a423af refactoring 2022-06-03 07:25:41 +03:00
Maxim Devaev
1cff2545b1 Bump version: 5.5 → 5.6 2022-06-02 02:18:13 +03:00
Maxim Devaev
3d994d6e67 fixed deps 2022-06-02 02:15:50 +03:00
Maxim Devaev
7175f8d569 Bump version: 5.4 → 5.5 2022-06-02 01:47:54 +03:00
Maxim Devaev
d4a9862a18 webrtc audio 2022-06-01 07:57:34 +03:00
129 changed files with 4002 additions and 2753 deletions

View File

@@ -1,18 +1,18 @@
[bumpversion] [bumpversion]
commit = True commit = True
tag = True tag = True
current_version = 5.4 current_version = 5.17
parse = (?P<major>\d+)\.(?P<minor>\d+) parse = (?P<major>\d+)\.(?P<minor>\d+)
serialize = serialize =
{major}.{minor} {major}.{minor}
[bumpversion:file:src/libs/config.h] [bumpversion:file:src/libs/const.h]
parse = (?P<major>\d+) parse = (?P<major>\d+)
serialize = {major} serialize = {major}
search = VERSION_MAJOR {current_version} search = VERSION_MAJOR {current_version}
replace = VERSION_MAJOR {new_version} replace = VERSION_MAJOR {new_version}
[bumpversion:file:./src/libs/config.h] [bumpversion:file:./src/libs/const.h]
parse = <major>\d+\.(?P<minor>\d+) parse = <major>\d+\.(?P<minor>\d+)
serialize = {minor} serialize = {minor}
search = VERSION_MINOR {current_version} search = VERSION_MINOR {current_version}

View File

@@ -9,10 +9,10 @@ LDFLAGS ?=
# ===== # =====
_PLUGIN = libjanus_ustreamer.so _PLUGIN = libjanus_ustreamer.so
_CFLAGS = -fPIC -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(shell pkg-config --cflags glib-2.0) $(CFLAGS) _CFLAGS = -fPIC -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(shell pkg-config --cflags glib-2.0) $(CFLAGS)
_LDFLAGS = -shared -lm -pthread -lrt -ljansson $(shell pkg-config --libs glib-2.0) $(LDFLAGS) _LDFLAGS = -shared -lm -pthread -lrt -ljansson -lopus -lasound -lspeexdsp $(shell pkg-config --libs glib-2.0) $(LDFLAGS)
_SRCS = $(shell ls src/*.c) _SRCS = $(shell ls src/uslibs/*.c src/*.c)
_BUILD = build _BUILD = build

242
janus/src/audio.c Normal file
View File

@@ -0,0 +1,242 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 "audio.h"
#define _JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
#define _JLOG_PERROR_RES(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
#define _JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err))
// A number of frames per 1 channel:
// - https://github.com/xiph/opus/blob/7b05f44/src/opus_demo.c#L368
#define _HZ_TO_FRAMES(_hz) (6 * (_hz) / 50) // 120ms
#define _HZ_TO_BUF16(_hz) (_HZ_TO_FRAMES(_hz) * 2) // One stereo frame = (16bit L) + (16bit R)
#define _HZ_TO_BUF8(_hz) (_HZ_TO_BUF16(_hz) * sizeof(int16_t))
#define _MIN_PCM_HZ 8000
#define _MAX_PCM_HZ 192000
#define _MAX_BUF16 _HZ_TO_BUF16(_MAX_PCM_HZ)
#define _MAX_BUF8 _HZ_TO_BUF8(_MAX_PCM_HZ)
#define _ENCODER_INPUT_HZ 48000
typedef struct {
int16_t data[_MAX_BUF16];
} _pcm_buffer_s;
typedef struct {
uint8_t data[_MAX_BUF8]; // Worst case
size_t used;
uint64_t pts;
} _enc_buffer_s;
static void *_pcm_thread(void *v_audio);
static void *_encoder_thread(void *v_audio);
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz) {
us_audio_s *audio;
US_CALLOC(audio, 1);
audio->pcm_hz = pcm_hz;
audio->pcm_queue = us_queue_init(8);
audio->enc_queue = us_queue_init(8);
atomic_init(&audio->stop, false);
int err;
{
if ((err = snd_pcm_open(&audio->pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
audio->pcm = NULL;
_JLOG_PERROR_ALSA(err, "audio", "Can't open PCM capture");
goto error;
}
assert(!snd_pcm_hw_params_malloc(&audio->pcm_params));
# define SET_PARAM(_msg, _func, ...) { \
if ((err = _func(audio->pcm, audio->pcm_params, ##__VA_ARGS__)) < 0) { \
_JLOG_PERROR_ALSA(err, "audio", _msg); \
goto error; \
} \
}
SET_PARAM("Can't initialize PCM params", snd_pcm_hw_params_any);
SET_PARAM("Can't set PCM access type", snd_pcm_hw_params_set_access, SND_PCM_ACCESS_RW_INTERLEAVED);
SET_PARAM("Can't set PCM channels numbre", snd_pcm_hw_params_set_channels, 2);
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, &audio->pcm_hz, 0);
if (audio->pcm_hz < _MIN_PCM_HZ || audio->pcm_hz > _MAX_PCM_HZ) {
US_JLOG_ERROR("audio", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
audio->pcm_hz, _MIN_PCM_HZ, _MAX_PCM_HZ);
goto error;
}
audio->pcm_frames = _HZ_TO_FRAMES(audio->pcm_hz);
audio->pcm_size = _HZ_TO_BUF8(audio->pcm_hz);
SET_PARAM("Can't apply PCM params", snd_pcm_hw_params);
# undef SET_PARAM
}
if (audio->pcm_hz != _ENCODER_INPUT_HZ) {
audio->res = speex_resampler_init(2, audio->pcm_hz, _ENCODER_INPUT_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
if (err < 0) {
audio->res = NULL;
_JLOG_PERROR_RES(err, "audio", "Can't create resampler");
goto error;
}
}
{
// OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY
audio->enc = opus_encoder_create(_ENCODER_INPUT_HZ, 2, OPUS_APPLICATION_AUDIO, &err);
assert(err == 0);
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_BITRATE(48000)));
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)));
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)));
// OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c
}
US_JLOG_INFO("audio", "Pipeline configured on %uHz; capturing ...", audio->pcm_hz);
audio->tids_created = true;
US_THREAD_CREATE(audio->enc_tid, _encoder_thread, audio);
US_THREAD_CREATE(audio->pcm_tid, _pcm_thread, audio);
return audio;
error:
us_audio_destroy(audio);
return NULL;
}
void us_audio_destroy(us_audio_s *audio) {
if (audio->tids_created) {
atomic_store(&audio->stop, true);
US_THREAD_JOIN(audio->pcm_tid);
US_THREAD_JOIN(audio->enc_tid);
}
US_DELETE(audio->enc, opus_encoder_destroy);
US_DELETE(audio->res, speex_resampler_destroy);
US_DELETE(audio->pcm, snd_pcm_close);
US_DELETE(audio->pcm_params, snd_pcm_hw_params_free);
US_QUEUE_DELETE_WITH_ITEMS(audio->enc_queue, free);
US_QUEUE_DELETE_WITH_ITEMS(audio->pcm_queue, free);
if (audio->tids_created) {
US_JLOG_INFO("audio", "Pipeline closed");
}
free(audio);
}
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
if (atomic_load(&audio->stop)) {
return -1;
}
_enc_buffer_s *buf;
if (!us_queue_get(audio->enc_queue, (void **)&buf, 0.1)) {
if (*size < buf->used) {
free(buf);
return -3;
}
memcpy(data, buf->data, buf->used);
*size = buf->used;
*pts = buf->pts;
free(buf);
return 0;
}
return -2;
}
static void *_pcm_thread(void *v_audio) {
US_THREAD_RENAME("us_a_pcm");
us_audio_s *const audio = (us_audio_s *)v_audio;
uint8_t in[_MAX_BUF8];
while (!atomic_load(&audio->stop)) {
const int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames);
if (frames < 0) {
_JLOG_PERROR_ALSA(frames, "audio", "Fatal: Can't capture PCM frames");
break;
} else if (frames < (int)audio->pcm_frames) {
US_JLOG_ERROR("audio", "Fatal: Too few PCM frames captured");
break;
}
if (us_queue_get_free(audio->pcm_queue)) {
_pcm_buffer_s *out;
US_CALLOC(out, 1);
memcpy(out->data, in, audio->pcm_size);
assert(!us_queue_put(audio->pcm_queue, out, 0));
} else {
US_JLOG_ERROR("audio", "PCM queue is full");
}
}
atomic_store(&audio->stop, true);
return NULL;
}
static void *_encoder_thread(void *v_audio) {
US_THREAD_RENAME("us_a_enc");
us_audio_s *const audio = (us_audio_s *)v_audio;
int16_t in_res[_MAX_BUF16];
while (!atomic_load(&audio->stop)) {
_pcm_buffer_s *in;
if (!us_queue_get(audio->pcm_queue, (void **)&in, 0.1)) {
int16_t *in_ptr;
if (audio->res != NULL) {
assert(audio->pcm_hz != _ENCODER_INPUT_HZ);
uint32_t in_count = audio->pcm_frames;
uint32_t out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
speex_resampler_process_interleaved_int(audio->res, in->data, &in_count, in_res, &out_count);
in_ptr = in_res;
} else {
assert(audio->pcm_hz == _ENCODER_INPUT_HZ);
in_ptr = in->data;
}
_enc_buffer_s *out;
US_CALLOC(out, 1);
const int size = opus_encode(audio->enc, in_ptr, _HZ_TO_FRAMES(_ENCODER_INPUT_HZ), out->data, US_ARRAY_LEN(out->data));
free(in);
if (size < 0) {
_JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
free(out);
break;
}
out->used = size;
out->pts = audio->pts;
// https://datatracker.ietf.org/doc/html/rfc7587#section-4.2
audio->pts += _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
if (us_queue_put(audio->enc_queue, out, 0) != 0) {
US_JLOG_ERROR("audio", "OPUS encoder queue is full");
free(out);
}
}
}
atomic_store(&audio->stop, true);
return NULL;
}

69
janus/src/audio.h Normal file
View File

@@ -0,0 +1,69 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <assert.h>
#include <sys/types.h>
#include <pthread.h>
#include <alsa/asoundlib.h>
#include <speex/speex_resampler.h>
#include <opus/opus.h>
#include "uslibs/tools.h"
#include "uslibs/array.h"
#include "uslibs/threading.h"
#include "logging.h"
#include "queue.h"
typedef struct {
snd_pcm_t *pcm;
unsigned pcm_hz;
unsigned pcm_frames;
size_t pcm_size;
snd_pcm_hw_params_t *pcm_params;
SpeexResamplerState *res;
OpusEncoder *enc;
us_queue_s *pcm_queue;
us_queue_s *enc_queue;
uint32_t pts;
pthread_t pcm_tid;
pthread_t enc_tid;
bool tids_created;
atomic_bool stop;
} us_audio_s;
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz);
void us_audio_destroy(us_audio_s *audio);
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);

View File

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

View File

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

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

@@ -0,0 +1,119 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 "client.h"
static void *_video_thread(void *v_client);
static void *_audio_thread(void *v_client);
static void *_common_thread(void *v_client, bool video);
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio) {
us_janus_client_s *client;
US_CALLOC(client, 1);
client->gw = gw;
client->session = session;
atomic_init(&client->transmit, true);
atomic_init(&client->stop, false);
client->video_queue = us_queue_init(1024);
US_THREAD_CREATE(client->video_tid, _video_thread, client);
if (has_audio) {
client->audio_queue = us_queue_init(64);
US_THREAD_CREATE(client->audio_tid, _audio_thread, client);
}
return client;
}
void us_janus_client_destroy(us_janus_client_s *client) {
atomic_store(&client->stop, true);
us_queue_put(client->video_queue, NULL, 0);
if (client->audio_queue != NULL) {
us_queue_put(client->audio_queue, NULL, 0);
}
US_THREAD_JOIN(client->video_tid);
US_QUEUE_DELETE_WITH_ITEMS(client->video_queue, us_rtp_destroy);
if (client->audio_queue != NULL) {
US_THREAD_JOIN(client->audio_tid);
US_QUEUE_DELETE_WITH_ITEMS(client->audio_queue, us_rtp_destroy);
}
free(client);
}
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
if (
!atomic_load(&client->transmit)
|| (!rtp->video && client->audio_queue == NULL)
) {
return;
}
us_rtp_s *const new = us_rtp_dup(rtp);
if (us_queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
US_JLOG_ERROR("client", "Session %p %s queue is full",
client->session, (new->video ? "video" : "audio"));
us_rtp_destroy(new);
}
}
static void *_video_thread(void *v_client) {
return _common_thread(v_client, true);
}
static void *_audio_thread(void *v_client) {
return _common_thread(v_client, false);
}
static void *_common_thread(void *v_client, bool video) {
us_janus_client_s *const client = (us_janus_client_s *)v_client;
us_queue_s *const queue = (video ? client->video_queue : client->audio_queue);
assert(queue != NULL); // Audio may be NULL
while (!atomic_load(&client->stop)) {
us_rtp_s *rtp;
if (!us_queue_get(queue, (void **)&rtp, 0.1)) {
if (rtp == NULL) {
break;
}
if (atomic_load(&client->transmit)) {
janus_plugin_rtp packet = {0};
packet.video = rtp->video;
packet.buffer = (char *)rtp->datagram;
packet.length = rtp->used;
janus_plugin_rtp_extensions_reset(&packet.extensions);
// FIXME: Это очень эффективный способ уменьшить задержку, но WebRTC стек в хроме и фоксе
// слишком корявый, чтобы обработать это, из-за чего на кейфреймах начинаются заикания.
// - https://github.com/Glimesh/janus-ftl-plugin/issues/101
if (rtp->zero_playout_delay) {
packet.extensions.min_delay = 0;
packet.extensions.max_delay = 0;
}
client->gw->relay_rtp(client->session, &packet);
}
us_rtp_destroy(rtp);
}
}
return NULL;
}

61
janus/src/client.h Normal file
View File

@@ -0,0 +1,61 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <pthread.h>
#include <janus/plugins/plugin.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/list.h"
#include "logging.h"
#include "queue.h"
#include "rtp.h"
typedef struct us_janus_client_sx {
janus_callbacks *gw;
janus_plugin_session *session;
atomic_bool transmit;
pthread_t video_tid;
pthread_t audio_tid;
atomic_bool stop;
us_queue_s *video_queue;
us_queue_s *audio_queue;
US_LIST_STRUCT(struct us_janus_client_sx);
} us_janus_client_s;
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio);
void us_janus_client_destroy(us_janus_client_s *client);
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp);

99
janus/src/config.c Normal file
View File

@@ -0,0 +1,99 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 "config.h"
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
us_config_s *us_config_init(const char *config_dir_path) {
us_config_s *config;
US_CALLOC(config, 1);
char *config_file_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);
jcfg = janus_config_parse(config_file_path);
if (jcfg == NULL) {
US_JLOG_ERROR("config", "Can't read config");
goto error;
}
janus_config_print(jcfg);
if (
(config->video_sink_name = _get_value(jcfg, "memsink", "object")) == NULL
&& (config->video_sink_name = _get_value(jcfg, "video", "sink")) == NULL
) {
US_JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
goto error;
}
if ((config->video_zero_playout_delay = _get_bool(jcfg, "video", "zero_playout_delay", false)) == true) {
US_JLOG_INFO("config", "Enabled the experimental Playout-Delay=0 RTP extension for VIDEO");
}
if ((config->audio_dev_name = _get_value(jcfg, "audio", "device")) != NULL) {
US_JLOG_INFO("config", "Enabled the experimental AUDIO feature");
if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) {
US_JLOG_INFO("config", "Missing config value: audio.tc358743");
goto error;
}
}
goto ok;
error:
us_config_destroy(config);
config = NULL;
ok:
US_DELETE(jcfg, janus_config_destroy);
free(config_file_path);
return config;
}
void us_config_destroy(us_config_s *config) {
US_DELETE(config->video_sink_name, free);
US_DELETE(config->audio_dev_name, free);
US_DELETE(config->tc358743_dev_path, free);
free(config);
}
static char *_get_value(janus_config *jcfg, const char *section, const char *option) {
janus_config_category *section_obj = janus_config_get_create(jcfg, NULL, janus_config_type_category, section);
janus_config_item *option_obj = janus_config_get(jcfg, section_obj, janus_config_type_item, option);
if (option_obj == NULL || option_obj->value == NULL || option_obj->value[0] == '\0') {
return NULL;
}
return us_strdup(option_obj->value);
}
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def) {
char *const tmp = _get_value(jcfg, section, option);
bool value = def;
if (tmp != NULL) {
value = (!strcasecmp(tmp, "1") || !strcasecmp(tmp, "true") || !strcasecmp(tmp, "yes"));
free(tmp);
}
return value;
}

View File

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

47
janus/src/config.h Normal file
View File

@@ -0,0 +1,47 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 <stdlib.h>
#include <string.h>
#include <janus/config.h>
#include <janus/plugins/plugin.h>
#include "uslibs/tools.h"
#include "const.h"
#include "logging.h"
typedef struct {
char *video_sink_name;
bool video_zero_playout_delay;
char *audio_dev_name;
char *tc358743_dev_path;
} us_config_s;
us_config_s *us_config_init(const char *config_dir_path);
void us_config_destroy(us_config_s *config);

View File

@@ -22,11 +22,5 @@
#pragma once #pragma once
#define VERSION_MAJOR 5 #define US_PLUGIN_NAME "ustreamer"
#define VERSION_MINOR 4 #define US_PLUGIN_PACKAGE "janus.plugin.ustreamer"
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
#define VERSION MAKE_VERSION1(VERSION_MAJOR, VERSION_MINOR)
#define VERSION_U ((unsigned)(VERSION_MAJOR * 1000 + VERSION_MINOR))

View File

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

View File

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

View File

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

38
janus/src/logging.h Normal file
View File

@@ -0,0 +1,38 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 <janus/plugins/plugin.h>
#include "uslibs/tools.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__)
#define US_JLOG_WARN(x_prefix, x_msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
#define US_JLOG_ERROR(x_prefix, x_msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
#define US_JLOG_PERROR(x_prefix, x_msg, ...) { \
char m_perror_buf[1024] = {0}; \
char *m_perror_ptr = us_errno_to_string(errno, m_perror_buf, 1023); \
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_ptr); \
}

70
janus/src/memsinkfd.c Normal file
View File

@@ -0,0 +1,70 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 "memsinkfd.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id) {
const long double deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
long double now;
do {
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
now = 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 < deadline_ts);
return -2;
}
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id) {
us_frame_s *frame = us_frame_init();
us_frame_set_data(frame, mem->data, mem->used);
US_FRAME_COPY_META(mem, frame);
*frame_id = mem->id;
mem->last_client_ts = us_get_now_monotonic();
bool ok = true;
if (frame->format != V4L2_PIX_FMT_H264) {
US_JLOG_ERROR("video", "Got non-H264 frame from memsink");
ok = false;
}
if (flock(fd, LOCK_UN) < 0) {
US_JLOG_PERROR("video", "Can't unlock memsink");
ok = false;
}
if (!ok) {
us_frame_destroy(frame);
frame = NULL;
}
return frame;
}

38
janus/src/memsinkfd.h Normal file
View File

@@ -0,0 +1,38 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 <stdint.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include "uslibs/tools.h"
#include "uslibs/frame.h"
#include "uslibs/memsinksh.h"
#include "logging.h"
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id);
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id);

View File

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

View File

@@ -24,227 +24,139 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <stdlib.h> #include <stdlib.h>
#include <inttypes.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <assert.h> #include <assert.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <linux/videodev2.h>
#include <pthread.h> #include <pthread.h>
#include <jansson.h> #include <jansson.h>
#include <janus/config.h>
#include <janus/plugins/plugin.h> #include <janus/plugins/plugin.h>
#include "config.h" #include "uslibs/const.h"
#include "tools.h" #include "uslibs/tools.h"
#include "threading.h" #include "uslibs/threading.h"
#include "list.h" #include "uslibs/list.h"
#include "memsinksh.h" #include "uslibs/memsinksh.h"
#include "const.h"
#include "logging.h"
#include "queue.h"
#include "client.h"
#include "audio.h"
#include "tc358743.h"
#include "rtp.h" #include "rtp.h"
#include "rtpv.h"
#include "rtpa.h"
#include "memsinkfd.h"
#include "config.h"
static int _plugin_init(janus_callbacks *gw, const char *config_file_path); static us_config_s *_g_config = NULL;
static void _plugin_destroy(void); const useconds_t _g_watchers_polling = 100000;
static void _plugin_create_session(janus_plugin_session *session, int *error); static us_janus_client_s *_g_clients = NULL;
static void _plugin_destroy_session(janus_plugin_session *session, int *error); static janus_callbacks *_g_gw = NULL;
static json_t *_plugin_query_session(janus_plugin_session *session); static us_queue_s *_g_video_queue = NULL;
static us_rtpv_s *_g_rtpv = NULL;
static us_rtpa_s *_g_rtpa = NULL;
static void _plugin_setup_media(janus_plugin_session *session); static pthread_t _g_video_rtp_tid;
static void _plugin_hangup_media(janus_plugin_session *session); static atomic_bool _g_video_rtp_tid_created = false;
static pthread_t _g_video_sink_tid;
static atomic_bool _g_video_sink_tid_created = false;
static pthread_t _g_audio_tid;
static atomic_bool _g_audio_tid_created = false;
static struct janus_plugin_result *_plugin_handle_message( static pthread_mutex_t _g_video_lock = PTHREAD_MUTEX_INITIALIZER;
janus_plugin_session *session, char *transaction, json_t *msg, json_t *jsep); static pthread_mutex_t _g_audio_lock = PTHREAD_MUTEX_INITIALIZER;
static atomic_bool _g_ready = false;
static int _plugin_get_api_compatibility(void); static atomic_bool _g_stop = false;
static int _plugin_get_version(void); static atomic_bool _g_has_watchers = false;
static const char *_plugin_get_version_string(void);
static const char *_plugin_get_description(void);
static const char *_plugin_get_name(void);
static const char *_plugin_get_author(void);
static const char *_plugin_get_package(void);
#pragma GCC diagnostic push #define _LOCK_VIDEO US_MUTEX_LOCK(_g_video_lock)
#pragma GCC diagnostic ignored "-Woverride-init" #define _UNLOCK_VIDEO US_MUTEX_UNLOCK(_g_video_lock)
static janus_plugin _plugin = JANUS_PLUGIN_INIT(
.init = _plugin_init,
.destroy = _plugin_destroy,
.create_session = _plugin_create_session, #define _LOCK_AUDIO US_MUTEX_LOCK(_g_audio_lock)
.destroy_session = _plugin_destroy_session, #define _UNLOCK_AUDIO US_MUTEX_UNLOCK(_g_audio_lock)
.query_session = _plugin_query_session,
.setup_media = _plugin_setup_media, #define _LOCK_ALL { _LOCK_VIDEO; _LOCK_AUDIO; }
.hangup_media = _plugin_hangup_media, #define _UNLOCK_ALL { _UNLOCK_AUDIO; _UNLOCK_VIDEO; }
.handle_message = _plugin_handle_message, #define _READY atomic_load(&_g_ready)
#define _STOP atomic_load(&_g_stop)
.get_api_compatibility = _plugin_get_api_compatibility, #define _HAS_WATCHERS atomic_load(&_g_has_watchers)
.get_version = _plugin_get_version,
.get_version_string = _plugin_get_version_string,
.get_description = _plugin_get_description,
.get_name = _plugin_get_name,
.get_author = _plugin_get_author,
.get_package = _plugin_get_package,
);
#pragma GCC diagnostic pop
janus_plugin *create(void) { // cppcheck-suppress unusedFunction
return &_plugin;
}
typedef struct _client_sx { janus_plugin *create(void);
janus_plugin_session *session;
bool transmit;
LIST_STRUCT(struct _client_sx);
} _client_s;
static char *_g_memsink_obj = NULL; #define _IF_NOT_REPORTED(...) { \
const long double _g_wait_timeout = 1; const unsigned _error_code = __LINE__; \
const long double _g_lock_timeout = 1; if (error_reported != _error_code) { __VA_ARGS__; error_reported = _error_code; } \
const useconds_t _g_lock_polling = 1000;
const useconds_t _g_watchers_polling = 100000;
static _client_s *_g_clients = NULL;
static janus_callbacks *_g_gw = NULL;
static rtp_s *_g_rtp = NULL;
static pthread_t _g_tid;
static pthread_mutex_t _g_lock = PTHREAD_MUTEX_INITIALIZER;
static atomic_bool _g_ready = ATOMIC_VAR_INIT(false);
static atomic_bool _g_stop = ATOMIC_VAR_INIT(false);
static atomic_bool _g_has_watchers = ATOMIC_VAR_INIT(false);
#define JLOG_INFO(_msg, ...) JANUS_LOG(LOG_INFO, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__)
#define JLOG_WARN(_msg, ...) JANUS_LOG(LOG_WARN, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__)
#define JLOG_ERROR(_msg, ...) JANUS_LOG(LOG_ERR, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__)
#define JLOG_PERROR(_msg, ...) { \
char _perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
JANUS_LOG(LOG_ERR, "[%s] " _msg ": %s\n", _plugin_get_name(), ##__VA_ARGS__, _perror_ptr); \
} }
#define LOCK A_MUTEX_LOCK(&_g_lock) static void *_video_rtp_thread(UNUSED void *arg) {
#define UNLOCK A_MUTEX_UNLOCK(&_g_lock) US_THREAD_RENAME("us_video_rtp");
#define READY atomic_load(&_g_ready) atomic_store(&_g_video_rtp_tid_created, true);
#define STOP atomic_load(&_g_stop)
#define HAS_WATCHERS atomic_load(&_g_has_watchers)
while (!_STOP) {
static void _relay_rtp_clients(const uint8_t *datagram, size_t size) { us_frame_s *frame;
janus_plugin_rtp packet = { if (us_queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
.video = true, _LOCK_VIDEO;
.buffer = (char *)datagram, us_rtpv_wrap(_g_rtpv, frame);
.length = size, _UNLOCK_VIDEO;
}; us_frame_destroy(frame);
janus_plugin_rtp_extensions_reset(&packet.extensions);
LIST_ITERATE(_g_clients, client, {
if (client->transmit) {
_g_gw->relay_rtp(client->session, &packet);
} }
});
}
static int _wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id) {
long double deadline_ts = get_now_monotonic() + _g_wait_timeout;
long double now;
do {
int retval = flock_timedwait_monotonic(fd, _g_lock_timeout);
now = get_now_monotonic();
if (retval < 0 && errno != EWOULDBLOCK) {
JLOG_PERROR("Can't lock memsink");
return -1;
} else if (retval == 0) {
if (mem->magic == MEMSINK_MAGIC && mem->version == MEMSINK_VERSION && mem->id != last_id) {
return 0;
}
if (flock(fd, LOCK_UN) < 0) {
JLOG_PERROR("Can't unlock memsink");
return -1;
}
}
usleep(_g_lock_polling);
} while (now < deadline_ts);
return -2;
}
static int _get_frame(int fd, memsink_shared_s *mem, frame_s *frame, uint64_t *frame_id) {
frame_set_data(frame, mem->data, mem->used);
FRAME_COPY_META(mem, frame);
*frame_id = mem->id;
mem->last_client_ts = get_now_monotonic();
int retval = 0;
if (frame->format != V4L2_PIX_FMT_H264) {
JLOG_ERROR("Got non-H264 frame from memsink");
retval = -1;
} }
if (flock(fd, LOCK_UN) < 0) { return NULL;
JLOG_PERROR("Can't unlock memsink");
retval = -1;
}
return retval;
} }
static void *_clients_thread(UNUSED void *arg) { static void *_video_sink_thread(UNUSED void *arg) {
A_THREAD_RENAME("us_clients"); US_THREAD_RENAME("us_video_sink");
atomic_store(&_g_ready, true); atomic_store(&_g_video_sink_tid_created, true);
frame_s *frame = frame_init();
uint64_t frame_id = 0; uint64_t frame_id = 0;
unsigned error_reported = 0; unsigned error_reported = 0;
# define IF_NOT_REPORTED(_error, ...) { \ while (!_STOP) {
if (error_reported != _error) { __VA_ARGS__; error_reported = _error; } \ if (!_HAS_WATCHERS) {
} _IF_NOT_REPORTED({ US_JLOG_INFO("video", "No active watchers, memsink disconnected"); });
while (!STOP) {
if (!HAS_WATCHERS) {
IF_NOT_REPORTED(1, {
JLOG_INFO("No active watchers, memsink disconnected");
});
usleep(_g_watchers_polling); usleep(_g_watchers_polling);
continue; continue;
} }
int fd = -1; int fd = -1;
memsink_shared_s *mem = NULL; us_memsink_shared_s *mem = NULL;
if ((fd = shm_open(_g_memsink_obj, O_RDWR, 0)) <= 0) { if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
IF_NOT_REPORTED(2, { _IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't open memsink"); });
JLOG_PERROR("Can't open memsink");
});
goto close_memsink; goto close_memsink;
} }
if ((mem = memsink_shared_map(fd)) == NULL) { if ((mem = us_memsink_shared_map(fd)) == NULL) {
IF_NOT_REPORTED(3, { _IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't map memsink"); });
JLOG_PERROR("Can't map memsink");
});
goto close_memsink; goto close_memsink;
} }
error_reported = 0; error_reported = 0;
JLOG_INFO("Memsink opened; reading frames ..."); US_JLOG_INFO("video", "Memsink opened; reading frames ...");
while (!STOP && HAS_WATCHERS) { while (!_STOP && _HAS_WATCHERS) {
int result = _wait_frame(fd, mem, frame_id); const int result = us_memsink_fd_wait_frame(fd, mem, frame_id);
if (result == 0) { if (result == 0) {
if (_get_frame(fd, mem, frame, &frame_id) != 0) { us_frame_s *const frame = us_memsink_fd_get_frame(fd, mem, &frame_id);
if (frame == NULL) {
goto close_memsink; goto close_memsink;
} }
LOCK; if (us_queue_put(_g_video_queue, frame, 0) != 0) {
rtp_wrap_h264(_g_rtp, frame, _relay_rtp_clients); _IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Video queue is full"); });
UNLOCK; us_frame_destroy(frame);
}
} else if (result == -1) { } else if (result == -1) {
goto close_memsink; goto close_memsink;
} }
@@ -252,55 +164,83 @@ static void *_clients_thread(UNUSED void *arg) {
close_memsink: close_memsink:
if (mem != NULL) { if (mem != NULL) {
JLOG_INFO("Memsink closed"); US_JLOG_INFO("video", "Memsink closed");
memsink_shared_unmap(mem); us_memsink_shared_unmap(mem);
mem = NULL;
} }
if (fd > 0) { if (fd >= 0) {
close(fd); close(fd);
fd = -1;
} }
sleep(1); // error_delay sleep(1); // error_delay
} }
# undef IF_NOT_REPORTED
frame_destroy(frame);
return NULL; return NULL;
} }
static int _read_config(const char *config_dir_path) { static void *_audio_thread(UNUSED void *arg) {
char *config_file_path; US_THREAD_RENAME("us_audio");
janus_config *config = NULL; atomic_store(&_g_audio_tid_created, true);
assert(_g_config->audio_dev_name != NULL);
assert(_g_config->tc358743_dev_path != NULL);
A_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, _plugin_get_package()); unsigned error_reported = 0;
JLOG_INFO("Reading config file '%s' ...", config_file_path);
config = janus_config_parse(config_file_path); while (!_STOP) {
if (config == NULL) { if (!_HAS_WATCHERS) {
JLOG_ERROR("Can't read config"); usleep(_g_watchers_polling);
goto error; continue;
}
janus_config_print(config);
janus_config_category *config_memsink = janus_config_get_create(config, NULL, janus_config_type_category, "memsink");
janus_config_item *config_memsink_obj = janus_config_get(config, config_memsink, janus_config_type_item, "object");
if (config_memsink_obj == NULL || config_memsink_obj->value == NULL || config_memsink_obj->value[0] == '\0') {
JLOG_ERROR("Missing config value: memsink.object");
goto error;
}
_g_memsink_obj = strdup(config_memsink_obj->value);
int retval = 0;
goto ok;
error:
retval = -1;
ok:
if (config) {
janus_config_destroy(config);
} }
free(config_file_path);
return retval; us_tc358743_info_s info = {0};
us_audio_s *audio = NULL;
if (us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0) {
goto close_audio;
}
if (!info.has_audio) {
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "No audio presented from the host"); });
goto close_audio;
}
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "Detected host audio"); });
if ((audio = us_audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
goto close_audio;
}
error_reported = 0;
while (!_STOP && _HAS_WATCHERS) {
if (
us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|| !info.has_audio
|| audio->pcm_hz != info.audio_hz
) {
goto close_audio;
}
size_t size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
uint8_t data[size];
uint64_t pts;
const int result = us_audio_get_encoded(audio, data, &size, &pts);
if (result == 0) {
_LOCK_AUDIO;
us_rtpa_wrap(_g_rtpa, data, size, pts);
_UNLOCK_AUDIO;
} else if (result == -1) {
goto close_audio;
}
}
close_audio:
US_DELETE(audio, us_audio_destroy);
sleep(1); // error_delay
}
return NULL;
}
#undef _IF_NOT_REPORTED
static void _relay_rtp_clients(const us_rtp_s *rtp) {
US_LIST_ITERATE(_g_clients, client, {
us_janus_client_send(client, rtp);
});
} }
static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) { static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
@@ -310,115 +250,117 @@ 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
JLOG_INFO("Initializing plugin ..."); US_JLOG_INFO("main", "Initializing plugin ...");
assert(!READY); if (gw == NULL || config_dir_path == NULL || ((_g_config = us_config_init(config_dir_path)) == NULL)) {
assert(!STOP);
if (gw == NULL || config_dir_path == NULL || _read_config(config_dir_path) < 0) {
return -1; return -1;
} }
_g_gw = gw; _g_gw = gw;
_g_rtp = rtp_init();
A_THREAD_CREATE(&_g_tid, _clients_thread, NULL); _g_video_queue = us_queue_init(1024);
_g_rtpv = us_rtpv_init(_relay_rtp_clients, _g_config->video_zero_playout_delay);
if (_g_config->audio_dev_name != NULL) {
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
US_THREAD_CREATE(_g_audio_tid, _audio_thread, NULL);
}
US_THREAD_CREATE(_g_video_rtp_tid, _video_rtp_thread, NULL);
US_THREAD_CREATE(_g_video_sink_tid, _video_sink_thread, NULL);
atomic_store(&_g_ready, true);
return 0; return 0;
} }
static void _plugin_destroy(void) { static void _plugin_destroy(void) {
JLOG_INFO("Destroying plugin ..."); US_JLOG_INFO("main", "Destroying plugin ...");
atomic_store(&_g_stop, true); atomic_store(&_g_stop, true);
if (READY) { # define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } }
A_THREAD_JOIN(_g_tid); JOIN(_g_video_sink_tid);
} JOIN(_g_video_rtp_tid);
JOIN(_g_audio_tid);
# undef JOIN
LIST_ITERATE(_g_clients, client, { US_LIST_ITERATE(_g_clients, client, {
LIST_REMOVE(_g_clients, client); US_LIST_REMOVE(_g_clients, client);
free(client); us_janus_client_destroy(client);
}); });
_g_clients = NULL;
rtp_destroy(_g_rtp); US_QUEUE_DELETE_WITH_ITEMS(_g_video_queue, us_frame_destroy);
_g_rtp = NULL;
_g_gw = NULL; US_DELETE(_g_rtpa, us_rtpa_destroy);
US_DELETE(_g_rtpv, us_rtpv_destroy);
if (_g_memsink_obj) { US_DELETE(_g_config, us_config_destroy);
free(_g_memsink_obj);
_g_memsink_obj = NULL;
}
} }
#define IF_DISABLED(...) { if (!READY || STOP) { __VA_ARGS__ } } #define _IF_DISABLED(...) { if (!_READY || _STOP) { __VA_ARGS__ } }
static void _plugin_create_session(janus_plugin_session *session, int *error) { static void _plugin_create_session(janus_plugin_session *session, int *err) {
IF_DISABLED({ *error = -1; return; }); _IF_DISABLED({ *err = -1; return; });
LOCK; _LOCK_ALL;
JLOG_INFO("Creating session %p ...", session); US_JLOG_INFO("main", "Creating session %p ...", session);
_client_s *client; us_janus_client_s *const client = us_janus_client_init(_g_gw, session, (_g_config->audio_dev_name != NULL));
A_CALLOC(client, 1); US_LIST_APPEND(_g_clients, client);
client->session = session;
client->transmit = true;
LIST_APPEND(_g_clients, client);
atomic_store(&_g_has_watchers, true); atomic_store(&_g_has_watchers, true);
UNLOCK; _UNLOCK_ALL;
} }
static void _plugin_destroy_session(janus_plugin_session* session, int *error) { static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
IF_DISABLED({ *error = -1; return; }); _IF_DISABLED({ *err = -1; return; });
LOCK; _LOCK_ALL;
bool found = false; bool found = false;
bool has_watchers = false; bool has_watchers = false;
LIST_ITERATE(_g_clients, client, { US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) { if (client->session == session) {
JLOG_INFO("Removing session %p ...", session); US_JLOG_INFO("main", "Removing session %p ...", session);
LIST_REMOVE(_g_clients, client); US_LIST_REMOVE(_g_clients, client);
free(client); us_janus_client_destroy(client);
found = true; found = true;
} else { } else {
has_watchers = (has_watchers || client->transmit); has_watchers = (has_watchers || atomic_load(&client->transmit));
} }
}); });
if (!found) { if (!found) {
JLOG_WARN("No session %p", session); US_JLOG_WARN("main", "No session %p", session);
*error = -2; *err = -2;
} }
atomic_store(&_g_has_watchers, has_watchers); atomic_store(&_g_has_watchers, has_watchers);
UNLOCK; _UNLOCK_ALL;
} }
static json_t *_plugin_query_session(janus_plugin_session *session) { static json_t *_plugin_query_session(janus_plugin_session *session) {
IF_DISABLED({ return NULL; }); _IF_DISABLED({ return NULL; });
json_t *info = NULL; json_t *info = NULL;
LOCK; _LOCK_ALL;
LIST_ITERATE(_g_clients, client, { US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) { if (client->session == session) {
info = json_string("session_found"); info = json_string("session_found");
break; break;
} }
}); });
UNLOCK; _UNLOCK_ALL;
return info; return info;
} }
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) { static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
IF_DISABLED({ return; }); _IF_DISABLED({ return; });
LOCK; _LOCK_ALL;
bool found = false; bool found = false;
bool has_watchers = false; bool has_watchers = false;
LIST_ITERATE(_g_clients, client, { US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) { if (client->session == session) {
client->transmit = transmit; atomic_store(&client->transmit, transmit);
//JLOG_INFO("%s session %p", msg, session); // US_JLOG_INFO("main", "%s session %p", msg, session);
found = true; found = true;
} }
has_watchers = (has_watchers || client->transmit); has_watchers = (has_watchers || atomic_load(&client->transmit));
}); });
if (!found) { if (!found) {
JLOG_WARN("No session %p", session); US_JLOG_WARN("main", "No session %p", session);
} }
atomic_store(&_g_has_watchers, has_watchers); atomic_store(&_g_has_watchers, has_watchers);
UNLOCK; _UNLOCK_ALL;
} }
#undef IF_DISABLED #undef _IF_DISABLED
static void _plugin_setup_media(janus_plugin_session *session) { _set_transmit(session, "Unmuted", true); } static void _plugin_setup_media(janus_plugin_session *session) { _set_transmit(session, "Unmuted", true); }
static void _plugin_hangup_media(janus_plugin_session *session) { _set_transmit(session, "Muted", false); } static void _plugin_hangup_media(janus_plugin_session *session) { _set_transmit(session, "Muted", false); }
@@ -429,8 +371,8 @@ static struct janus_plugin_result *_plugin_handle_message(
assert(transaction != NULL); assert(transaction != NULL);
# define FREE_MSG_JSEP { \ # define FREE_MSG_JSEP { \
if (msg) json_decref(msg); \ US_DELETE(msg, json_decref); \
if (jsep) json_decref(jsep); \ US_DELETE(jsep, json_decref); \
} }
if (session == NULL || msg == NULL) { if (session == NULL || msg == NULL) {
@@ -439,37 +381,37 @@ static struct janus_plugin_result *_plugin_handle_message(
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, (msg ? "No session" : "No message"), NULL); return janus_plugin_result_new(JANUS_PLUGIN_ERROR, (msg ? "No session" : "No message"), NULL);
} }
# define PUSH_ERROR(_error, _reason) { \ # define PUSH_ERROR(x_error, x_reason) { \
/*JLOG_ERROR("Message error in session %p: %s", session, _reason);*/ \ /*US_JLOG_ERROR("main", "Message error in session %p: %s", session, x_reason);*/ \
json_t *_event = json_object(); \ json_t *m_event = json_object(); \
json_object_set_new(_event, "ustreamer", json_string("event")); \ json_object_set_new(m_event, "ustreamer", json_string("event")); \
json_object_set_new(_event, "error_code", json_integer(_error)); \ json_object_set_new(m_event, "error_code", json_integer(x_error)); \
json_object_set_new(_event, "error", json_string(_reason)); \ json_object_set_new(m_event, "error", json_string(x_reason)); \
_g_gw->push_event(session, &_plugin, transaction, _event, NULL); \ _g_gw->push_event(session, create(), transaction, m_event, NULL); \
json_decref(_event); \ json_decref(m_event); \
} }
json_t *request_obj = json_object_get(msg, "request"); json_t *const request_obj = json_object_get(msg, "request");
if (request_obj == NULL) { if (request_obj == NULL) {
PUSH_ERROR(400, "Request missing"); PUSH_ERROR(400, "Request missing");
goto ok_wait; goto ok_wait;
} }
const char *request_str = json_string_value(request_obj); const char *const request_str = json_string_value(request_obj);
if (!request_str) { if (request_str == NULL) {
PUSH_ERROR(400, "Request not a string"); PUSH_ERROR(400, "Request not a string");
goto ok_wait; goto ok_wait;
} }
//JLOG_INFO("Message: %s", request_str); // US_JLOG_INFO("main", "Message: %s", request_str);
# define PUSH_STATUS(_status, _jsep) { \ # define PUSH_STATUS(x_status, x_jsep) { \
json_t *_event = json_object(); \ json_t *const m_event = json_object(); \
json_object_set_new(_event, "ustreamer", json_string("event")); \ json_object_set_new(m_event, "ustreamer", json_string("event")); \
json_t *_result = json_object(); \ json_t *const m_result = json_object(); \
json_object_set_new(_result, "status", json_string(_status)); \ json_object_set_new(m_result, "status", json_string(x_status)); \
json_object_set_new(_event, "result", _result); \ json_object_set_new(m_event, "result", m_result); \
_g_gw->push_event(session, &_plugin, transaction, _event, _jsep); \ _g_gw->push_event(session, create(), transaction, m_event, x_jsep); \
json_decref(_event); \ json_decref(m_event); \
} }
if (!strcmp(request_str, "start")) { if (!strcmp(request_str, "start")) {
@@ -479,13 +421,26 @@ static struct janus_plugin_result *_plugin_handle_message(
PUSH_STATUS("stopped", NULL); PUSH_STATUS("stopped", NULL);
} else if (!strcmp(request_str, "watch")) { } else if (!strcmp(request_str, "watch")) {
char *sdp = rtp_make_sdp(_g_rtp); char *sdp;
if (sdp == NULL) { {
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet"); char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
goto ok_wait; if (video_sdp == NULL) {
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");
goto ok_wait;
}
char *const audio_sdp = (_g_rtpa ? us_rtpa_make_sdp(_g_rtpa) : us_strdup(""));
US_ASPRINTF(sdp,
"v=0" RN
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
"s=PiKVM uStreamer" RN
"t=0 0" RN
"%s%s",
us_get_now_id() >> 1, audio_sdp, video_sdp
);
free(audio_sdp);
free(video_sdp);
} }
//JLOG_INFO("SDP generated:\n%s", sdp); json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
json_t *offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
free(sdp); free(sdp);
PUSH_STATUS("started", offer_jsep); PUSH_STATUS("started", offer_jsep);
json_decref(offer_jsep); json_decref(offer_jsep);
@@ -498,25 +453,52 @@ static struct janus_plugin_result *_plugin_handle_message(
FREE_MSG_JSEP; FREE_MSG_JSEP;
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL); return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
# undef PUSH_STATUS
# undef PUSH_ERROR # undef PUSH_ERROR
# undef FREE_MSG_JSEP # undef FREE_MSG_JSEP
} }
// ***** Plugin *****
static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; } static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; }
static int _plugin_get_version(void) { return VERSION_U; } static int _plugin_get_version(void) { return US_VERSION_U; }
static const char *_plugin_get_version_string(void) { return VERSION; } static const char *_plugin_get_version_string(void) { return US_VERSION; }
static const char *_plugin_get_description(void) { return "Pi-KVM uStreamer Janus plugin for H.264 video"; } static const char *_plugin_get_description(void) { return "PiKVM uStreamer Janus plugin for H.264 video"; }
static const char *_plugin_get_name(void) { return "ustreamer"; } static const char *_plugin_get_name(void) { return US_PLUGIN_NAME; }
static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; } static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
static const char *_plugin_get_package(void) { return "janus.plugin.ustreamer"; } static const char *_plugin_get_package(void) { return US_PLUGIN_PACKAGE; }
static void _plugin_incoming_rtp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtp *packet) {
// Just a stub to avoid logging spam about the plugin's purpose
}
#undef STOP janus_plugin *create(void) {
#undef READY # pragma GCC diagnostic push
#undef UNLOCK # pragma GCC diagnostic ignored "-Woverride-init"
#undef LOCK static janus_plugin plugin = JANUS_PLUGIN_INIT(
.init = _plugin_init,
.destroy = _plugin_destroy,
#undef JLOG_PERROR .create_session = _plugin_create_session,
#undef JLOG_ERROR .destroy_session = _plugin_destroy_session,
#undef JLOG_WARN .query_session = _plugin_query_session,
#undef JLOG_INFO
.setup_media = _plugin_setup_media,
.hangup_media = _plugin_hangup_media,
.handle_message = _plugin_handle_message,
.get_api_compatibility = _plugin_get_api_compatibility,
.get_version = _plugin_get_version,
.get_version_string = _plugin_get_version_string,
.get_description = _plugin_get_description,
.get_name = _plugin_get_name,
.get_author = _plugin_get_author,
.get_package = _plugin_get_package,
.incoming_rtp = _plugin_incoming_rtp,
);
# pragma GCC diagnostic pop
return &plugin;
}

99
janus/src/queue.c Normal file
View File

@@ -0,0 +1,99 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 "queue.h"
us_queue_s *us_queue_init(unsigned capacity) {
us_queue_s *queue;
US_CALLOC(queue, 1);
US_CALLOC(queue->items, capacity);
queue->capacity = capacity;
US_MUTEX_INIT(queue->mutex);
pthread_condattr_t attrs;
assert(!pthread_condattr_init(&attrs));
assert(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
assert(!pthread_cond_init(&queue->full_cond, &attrs));
assert(!pthread_cond_init(&queue->empty_cond, &attrs));
assert(!pthread_condattr_destroy(&attrs));
return queue;
}
void us_queue_destroy(us_queue_s *queue) {
free(queue->items);
free(queue);
}
#define _WAIT_OR_UNLOCK(x_var, x_cond) { \
struct timespec m_ts; \
assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \
while (x_var) { \
const int err = pthread_cond_timedwait(&(x_cond), &queue->mutex, &m_ts); \
if (err == ETIMEDOUT) { \
US_MUTEX_UNLOCK(queue->mutex); \
return -1; \
} \
assert(!err); \
} \
}
int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
US_MUTEX_LOCK(queue->mutex);
if (timeout == 0) {
if (queue->size == queue->capacity) {
US_MUTEX_UNLOCK(queue->mutex);
return -1;
}
} else {
_WAIT_OR_UNLOCK(queue->size == queue->capacity, queue->full_cond);
}
queue->items[queue->in] = item;
++queue->size;
++queue->in;
queue->in %= queue->capacity;
US_MUTEX_UNLOCK(queue->mutex);
assert(!pthread_cond_broadcast(&queue->empty_cond));
return 0;
}
int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
US_MUTEX_LOCK(queue->mutex);
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond);
*item = queue->items[queue->out];
--queue->size;
++queue->out;
queue->out %= queue->capacity;
US_MUTEX_UNLOCK(queue->mutex);
assert(!pthread_cond_broadcast(&queue->full_cond));
return 0;
}
#undef _WAIT_OR_UNLOCK
int us_queue_get_free(us_queue_s *queue) {
US_MUTEX_LOCK(queue->mutex);
const unsigned size = queue->size;
US_MUTEX_UNLOCK(queue->mutex);
return queue->capacity - size;
}

68
janus/src/queue.h Normal file
View File

@@ -0,0 +1,68 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 <errno.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
// Based on https://github.com/seifzadeh/c-pthread-queue/blob/master/queue.h
typedef struct {
void **items;
unsigned size;
unsigned capacity;
unsigned in;
unsigned out;
pthread_mutex_t mutex;
pthread_cond_t full_cond;
pthread_cond_t empty_cond;
} us_queue_s;
#define US_QUEUE_DELETE_WITH_ITEMS(x_queue, x_free_item) { \
if (x_queue) { \
while (!us_queue_get_free(x_queue)) { \
void *m_ptr; \
if (!us_queue_get(x_queue, &m_ptr, 0)) { \
US_DELETE(m_ptr, x_free_item); \
} \
} \
us_queue_destroy(x_queue); \
} \
}
us_queue_s *us_queue_init(unsigned capacity);
void us_queue_destroy(us_queue_s *queue);
int us_queue_put(us_queue_s *queue, void *item, long double timeout);
int us_queue_get(us_queue_s *queue, void **item, long double timeout);
int us_queue_get_free(us_queue_s *queue);

View File

@@ -26,182 +26,33 @@
#include "rtp.h" #include "rtp.h"
#define PAYLOAD 96 // Payload type us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay) {
#define PRE 3 // Annex B prefix length us_rtp_s *rtp;
US_CALLOC(rtp, 1);
rtp->payload = payload;
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback); rtp->video = video;
static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked); rtp->zero_playout_delay = zero_playout_delay; // See client.c
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
static ssize_t _find_annexb(const uint8_t *data, size_t size);
rtp_s *rtp_init(void) {
rtp_s *rtp;
A_CALLOC(rtp, 1);
rtp->ssrc = triple_u32(get_now_monotonic_u64());
rtp->sps = frame_init();
rtp->pps = frame_init();
A_MUTEX_INIT(&rtp->mutex);
return rtp; return rtp;
} }
void rtp_destroy(rtp_s *rtp) { us_rtp_s *us_rtp_dup(const us_rtp_s *rtp) {
A_MUTEX_DESTROY(&rtp->mutex); us_rtp_s *new;
frame_destroy(rtp->pps); US_CALLOC(new, 1);
frame_destroy(rtp->sps); memcpy(new, rtp, sizeof(us_rtp_s));
return new;
}
void us_rtp_destroy(us_rtp_s *rtp) {
free(rtp); free(rtp);
} }
char *rtp_make_sdp(rtp_s *rtp) { void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
A_MUTEX_LOCK(&rtp->mutex);
if (rtp->sps->used == 0 || rtp->pps->used == 0) {
A_MUTEX_UNLOCK(&rtp->mutex);
return NULL;
}
char *sps = NULL;
char *pps = NULL;
base64_encode(rtp->sps->data, rtp->sps->used, &sps, NULL);
base64_encode(rtp->pps->data, rtp->pps->used, &pps, NULL);
A_MUTEX_UNLOCK(&rtp->mutex);
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
char *sdp;
A_ASPRINTF(sdp,
"v=0" RN
"o=- %" PRIu64 " 1 IN IP4 127.0.0.1" RN
"s=Pi-KVM uStreamer" RN
"t=0 0" RN
"m=video 1 RTP/SAVPF %d" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%d H264/90000" RN
"a=fmtp:%d profile-level-id=42E01F" RN
"a=fmtp:%d packetization-mode=1" RN
"a=fmtp:%d sprop-sps=%s" RN
"a=fmtp:%d sprop-pps=%s" RN
"a=rtcp-fb:%d nack" RN
"a=rtcp-fb:%d nack pli" RN
"a=rtcp-fb:%d goog-remb" RN
"a=sendonly" RN,
get_now_id() >> 1, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
PAYLOAD, sps,
PAYLOAD, pps,
PAYLOAD, PAYLOAD, PAYLOAD
);
free(sps);
free(pps);
return sdp;
}
void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) {
// There is a complicated logic here but everything works as it should:
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
assert(frame->format == V4L2_PIX_FMT_H264);
const uint32_t pts = get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
ssize_t last_offset = -PRE;
while (true) { // Find and iterate by nalus
const size_t next_start = last_offset + PRE;
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
if (offset < 0) {
break;
}
offset += next_start;
if (last_offset >= 0) {
const uint8_t *data = frame->data + last_offset + PRE;
size_t size = offset - last_offset - PRE;
if (data[size - 1] == 0) { // Check for extra 00
--size;
}
_rtp_process_nalu(rtp, data, size, pts, false, callback);
}
last_offset = offset;
}
if (last_offset >= 0) {
const uint8_t *data = frame->data + last_offset + PRE;
size_t size = frame->used - last_offset - PRE;
_rtp_process_nalu(rtp, data, size, pts, true, callback);
}
}
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback) {
const unsigned ref_idc = (data[0] >> 5) & 3;
const unsigned type = data[0] & 0x1F;
frame_s *ps = NULL;
switch (type) {
case 7: ps = rtp->sps; break;
case 8: ps = rtp->pps; break;
}
if (ps) {
A_MUTEX_LOCK(&rtp->mutex);
frame_set_data(ps, data, size);
A_MUTEX_UNLOCK(&rtp->mutex);
}
# define HEADER_SIZE 12
if (size + HEADER_SIZE <= RTP_DATAGRAM_SIZE) {
_rtp_write_header(rtp, pts, marked);
memcpy(rtp->datagram + HEADER_SIZE, data, size);
callback(rtp->datagram, size + HEADER_SIZE);
return;
}
const size_t fu_overhead = HEADER_SIZE + 2; // FU-A overhead
const uint8_t *src = data + 1;
ssize_t remaining = size - 1;
bool first = true;
while (remaining > 0) {
ssize_t frag_size = RTP_DATAGRAM_SIZE - fu_overhead;
const bool last = (remaining <= frag_size);
if (last) {
frag_size = remaining;
}
_rtp_write_header(rtp, pts, (marked && last));
rtp->datagram[HEADER_SIZE] = 28 | (ref_idc << 5);
uint8_t fu = type;
if (first) {
fu |= 0x80;
}
if (last) {
fu |= 0x40;
}
rtp->datagram[HEADER_SIZE + 1] = fu;
memcpy(rtp->datagram + fu_overhead, src, frag_size);
callback(rtp->datagram, fu_overhead + frag_size);
src += frag_size;
remaining -= frag_size;
first = false;
}
# undef HEADER_SIZE
}
static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked) {
uint32_t word0 = 0x80000000; uint32_t word0 = 0x80000000;
if (marked) { if (marked) {
word0 |= 1 << 23; word0 |= 1 << 23;
} }
word0 |= (PAYLOAD & 0x7F) << 16; word0 |= (rtp->payload & 0x7F) << 16;
word0 |= rtp->seq; word0 |= rtp->seq;
++rtp->seq; ++rtp->seq;
@@ -211,18 +62,3 @@ static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked) {
WRITE_BE_U32(8, rtp->ssrc); WRITE_BE_U32(8, rtp->ssrc);
# undef WRITE_BE_U32 # undef WRITE_BE_U32
} }
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
// Parses buffer for 00 00 01 start codes
if (size >= PRE) {
for (size_t index = 0; index <= size - PRE; ++index) {
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
return index;
}
}
}
return -1;
}
#undef PRE
#undef PAYLOAD

View File

@@ -2,9 +2,6 @@
# # # #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. # # uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# # # #
# This source file is partially based on this code: #
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> # # Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# # # #
# This program is free software: you can redistribute it and/or modify # # This program is free software: you can redistribute it and/or modify #
@@ -26,44 +23,35 @@
#pragma once #pragma once
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <inttypes.h> #include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h> #include <sys/types.h>
#include <linux/videodev2.h>
#include <pthread.h> #include "uslibs/tools.h"
#include "tools.h"
#include "threading.h"
#include "frame.h"
#include "base64.h"
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes // https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
#define RTP_DATAGRAM_SIZE 1200 #define US_RTP_DATAGRAM_SIZE 1200
#define US_RTP_HEADER_SIZE 12
typedef struct { typedef struct {
uint32_t ssrc; unsigned payload;
uint16_t seq; bool video;
bool zero_playout_delay;
uint32_t ssrc;
uint8_t datagram[RTP_DATAGRAM_SIZE]; uint16_t seq;
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
size_t used;
} us_rtp_s;
frame_s *sps; // Actually not a frame, just a bytes storage typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
frame_s *pps;
pthread_mutex_t mutex;
} rtp_s;
typedef void (*rtp_callback_f)(const uint8_t *datagram, size_t size);
rtp_s *rtp_init(void); us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay);
void rtp_destroy(rtp_s *rtp); us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
void us_rtp_destroy(us_rtp_s *rtp);
char *rtp_make_sdp(rtp_s *rtp); void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked);
void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback);

66
janus/src/rtpa.c Normal file
View File

@@ -0,0 +1,66 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 "rtpa.h"
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
us_rtpa_s *rtpa;
US_CALLOC(rtpa, 1);
rtpa->rtp = us_rtp_init(111, false, false);
rtpa->callback = callback;
return rtpa;
}
void us_rtpa_destroy(us_rtpa_s *rtpa) {
us_rtp_destroy(rtpa->rtp);
free(rtpa);
}
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
# define PAYLOAD rtpa->rtp->payload
char *sdp;
US_ASPRINTF(sdp,
"m=audio 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u OPUS/48000/2" RN
// "a=fmtp:%u useinbandfec=1" RN
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=sendonly" RN,
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, // PAYLOAD,
rtpa->rtp->ssrc
);
# undef PAYLOAD
return sdp;
}
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
us_rtp_write_header(rtpa->rtp, pts, false);
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);
rtpa->rtp->used = size + US_RTP_HEADER_SIZE;
rtpa->callback(rtpa->rtp);
}
}

48
janus/src/rtpa.h Normal file
View File

@@ -0,0 +1,48 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <sys/types.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "rtp.h"
typedef struct {
us_rtp_s *rtp;
us_rtp_callback_f callback;
} us_rtpa_s;
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
void us_rtpa_destroy(us_rtpa_s *rtpa);
char *us_rtpa_make_sdp(us_rtpa_s *rtpa);
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);

213
janus/src/rtpv.c Normal file
View File

@@ -0,0 +1,213 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# This source file is partially based on this code: #
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
# #
# Copyright (C) 2018-2022 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 "rtpv.h"
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
static ssize_t _find_annexb(const uint8_t *data, size_t size);
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay) {
us_rtpv_s *rtpv;
US_CALLOC(rtpv, 1);
rtpv->rtp = us_rtp_init(96, true, zero_playout_delay);
rtpv->callback = callback;
rtpv->sps = us_frame_init();
rtpv->pps = us_frame_init();
US_MUTEX_INIT(rtpv->mutex);
return rtpv;
}
void us_rtpv_destroy(us_rtpv_s *rtpv) {
US_MUTEX_DESTROY(rtpv->mutex);
us_frame_destroy(rtpv->pps);
us_frame_destroy(rtpv->sps);
us_rtp_destroy(rtpv->rtp);
free(rtpv);
}
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
US_MUTEX_LOCK(rtpv->mutex);
if (rtpv->sps->used == 0 || rtpv->pps->used == 0) {
US_MUTEX_UNLOCK(rtpv->mutex);
return NULL;
}
char *sps = NULL;
char *pps = NULL;
us_base64_encode(rtpv->sps->data, rtpv->sps->used, &sps, NULL);
us_base64_encode(rtpv->pps->data, rtpv->pps->used, &pps, NULL);
US_MUTEX_UNLOCK(rtpv->mutex);
# define PAYLOAD rtpv->rtp->payload
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
char *sdp;
US_ASPRINTF(sdp,
"m=video 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u H264/90000" RN
"a=fmtp:%u profile-level-id=42E01F" RN
"a=fmtp:%u packetization-mode=1" RN
"a=fmtp:%u sprop-sps=%s" RN
"a=fmtp:%u sprop-pps=%s" RN
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"%s" // playout-delay
"a=sendonly" RN,
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
PAYLOAD, sps,
PAYLOAD, pps,
PAYLOAD, PAYLOAD, PAYLOAD,
rtpv->rtp->ssrc,
(rtpv->rtp->zero_playout_delay ? "a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN : "")
);
# undef PAYLOAD
free(sps);
free(pps);
return sdp;
}
#define _PRE 3 // Annex B prefix length
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
// There is a complicated logic here but everything works as it should:
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
assert(frame->format == V4L2_PIX_FMT_H264);
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
ssize_t last_offset = -_PRE;
while (true) { // Find and iterate by nalus
const size_t next_start = last_offset + _PRE;
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
if (offset < 0) {
break;
}
offset += next_start;
if (last_offset >= 0) {
const uint8_t *const data = frame->data + last_offset + _PRE;
size_t size = offset - last_offset - _PRE;
if (data[size - 1] == 0) { // Check for extra 00
--size;
}
_rtpv_process_nalu(rtpv, data, size, pts, false);
}
last_offset = offset;
}
if (last_offset >= 0) {
const uint8_t *const data = frame->data + last_offset + _PRE;
size_t size = frame->used - last_offset - _PRE;
_rtpv_process_nalu(rtpv, data, size, pts, true);
}
}
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
const unsigned ref_idc = (data[0] >> 5) & 3;
const unsigned type = data[0] & 0x1F;
us_frame_s *ps = NULL;
switch (type) {
case 7: ps = rtpv->sps; break;
case 8: ps = rtpv->pps; break;
}
if (ps != NULL) {
US_MUTEX_LOCK(rtpv->mutex);
us_frame_set_data(ps, data, size);
US_MUTEX_UNLOCK(rtpv->mutex);
}
# define DG rtpv->rtp->datagram
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
us_rtp_write_header(rtpv->rtp, pts, marked);
memcpy(DG + US_RTP_HEADER_SIZE, data, size);
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
rtpv->callback(rtpv->rtp);
return;
}
const size_t fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
const uint8_t *src = data + 1;
ssize_t remaining = size - 1;
bool first = true;
while (remaining > 0) {
ssize_t frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
const bool last = (remaining <= frag_size);
if (last) {
frag_size = remaining;
}
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
DG[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
uint8_t fu = type;
if (first) {
fu |= 0x80;
}
if (last) {
fu |= 0x40;
}
DG[US_RTP_HEADER_SIZE + 1] = fu;
memcpy(DG + fu_overhead, src, frag_size);
rtpv->rtp->used = fu_overhead + frag_size;
rtpv->callback(rtpv->rtp);
src += frag_size;
remaining -= frag_size;
first = false;
}
# undef DG
}
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
// Parses buffer for 00 00 01 start codes
if (size >= _PRE) {
for (size_t index = 0; index <= size - _PRE; ++index) {
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
return index;
}
}
}
return -1;
}
#undef _PRE

57
janus/src/rtpv.h Normal file
View File

@@ -0,0 +1,57 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <assert.h>
#include <sys/types.h>
#include <linux/videodev2.h>
#include <pthread.h>
#include "uslibs/tools.h"
#include "uslibs/threading.h"
#include "uslibs/frame.h"
#include "uslibs/base64.h"
#include "rtp.h"
typedef struct {
us_rtp_s *rtp;
us_rtp_callback_f callback;
us_frame_s *sps; // Actually not a frame, just a bytes storage
us_frame_s *pps;
pthread_mutex_t mutex;
} us_rtpv_s;
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay);
void us_rtpv_destroy(us_rtpv_s *rtpv);
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame);

64
janus/src/tc358743.c Normal file
View File

@@ -0,0 +1,64 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 "tc358743.h"
#ifndef V4L2_CID_USER_TC358743_BASE
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
#endif
#ifndef TC358743_CID_AUDIO_PRESENT
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
#endif
#ifndef TC358743_CID_AUDIO_SAMPLING_RATE
# define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
#endif
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
US_MEMSET_ZERO(*info);
int fd = -1;
if ((fd = open(path, O_RDWR)) < 0) {
US_JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
return -1;
}
# define READ_CID(x_cid, x_field) { \
struct v4l2_control m_ctl = {0}; \
m_ctl.id = x_cid; \
if (us_xioctl(fd, VIDIOC_G_CTRL, &m_ctl) < 0) { \
US_JLOG_PERROR("audio", "Can't get value of " #x_cid); \
close(fd); \
return -1; \
} \
info->x_field = m_ctl.value; \
}
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
READ_CID(TC358743_CID_AUDIO_SAMPLING_RATE, audio_hz);
# undef READ_CID
close(fd);
return 0;
}

46
janus/src/tc358743.h Normal file
View File

@@ -0,0 +1,46 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 <unistd.h>
#include <fcntl.h>
#include <stdbool.h>
#include <sys/types.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "uslibs/tools.h"
#include "uslibs/xioctl.h"
#include "logging.h"
typedef struct {
bool has_audio;
unsigned audio_hz;
} us_tc358743_info_s;
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info);

View File

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

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

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

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

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

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

View File

@@ -1,3 +1,6 @@
#define CHAR_BIT 8 #define CHAR_BIT 8
#define WITH_GPIO #define WITH_GPIO
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ } #define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1
#define CLOCK_MONOTONIC_RAW 1
#define CLOCK_MONOTONIC_FAST 1

View File

@@ -15,15 +15,8 @@ disable =
locally-disabled, locally-disabled,
fixme, fixme,
missing-docstring, missing-docstring,
no-init,
no-self-use,
superfluous-parens, superfluous-parens,
abstract-class-not-used,
abstract-class-little-used,
duplicate-code, duplicate-code,
bad-continuation,
bad-whitespace,
star-args,
broad-except, broad-except,
redundant-keyword-arg, redundant-keyword-arg,
wrong-import-order, wrong-import-order,
@@ -39,9 +32,6 @@ msg-template = {symbol} -- {path}:{line}({obj}): {msg}
max-line-length = 160 max-line-length = 160
[BASIC] [BASIC]
# List of builtins function names that should not be used, separated by a comma
bad-functions =
# Good variable names which should always be accepted, separated by a comma # Good variable names which should always be accepted, separated by a comma
good-names = _, __, x, y, ws, make-html-h, make-jpeg-h good-names = _, __, x, y, ws, make-html-h, make-jpeg-h

View File

@@ -9,8 +9,9 @@ changedir = /src
[testenv:cppcheck] [testenv:cppcheck]
whitelist_externals = cppcheck whitelist_externals = cppcheck
commands = cppcheck \ commands = cppcheck \
-j4 \
--force \ --force \
--std=c11 \ --std=c17 \
--error-exitcode=1 \ --error-exitcode=1 \
--quiet \ --quiet \
--enable=warning,unusedFunction,portability,performance,style \ --enable=warning,unusedFunction,portability,performance,style \
@@ -19,7 +20,7 @@ commands = cppcheck \
--inline-suppr \ --inline-suppr \
--library=python \ --library=python \
--include=linters/cppcheck.h \ --include=linters/cppcheck.h \
src python/ustreamer.c janus/rtp.h janus/rtp.c janus/plugin.c src python/*.? janus/*.?
[testenv:flake8] [testenv:flake8]
whitelist_externals = bash whitelist_externals = bash

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 5.4" "January 2021" .TH USTREAMER-DUMP 1 "version 5.17" "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 5.4" "November 2020" .TH USTREAMER 1 "version 5.17" "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

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer pkgname=ustreamer
pkgver=5.4 pkgver=5.17
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"
@@ -22,8 +22,8 @@ if [ -e /usr/bin/python3 ]; then
makedepends+=(python-setuptools) makedepends+=(python-setuptools)
fi fi
if [ -e /usr/include/janus/plugins/plugin.h ];then if [ -e /usr/include/janus/plugins/plugin.h ];then
depends+=(janus-gateway-pikvm) depends+=(janus-gateway-pikvm alsa-lib opus)
makedepends+=(janus-gateway-pikvm) makedepends+=(janus-gateway-pikvm alsa-lib opus)
_options="$_options WITH_JANUS=1" _options="$_options WITH_JANUS=1"
fi fi

View File

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

View File

@@ -1,14 +1,25 @@
import os import os
from typing import List
from setuptools import Extension from setuptools import Extension
from setuptools import setup from setuptools import setup
# ===== # =====
def _find_sources(suffix: str) -> List[str]:
sources: List[str] = []
for (root_path, _, names) in os.walk("src"):
for name in names:
if name.endswith(suffix):
sources.append(os.path.join(root_path, name))
return sources
if __name__ == "__main__": if __name__ == "__main__":
setup( setup(
name="ustreamer", name="ustreamer",
version="5.4", version="5.17",
description="uStreamer tools", description="uStreamer tools",
author="Maxim Devaev", author="Maxim Devaev",
author_email="mdevaev@gmail.com", author_email="mdevaev@gmail.com",
@@ -17,9 +28,10 @@ if __name__ == "__main__":
Extension( Extension(
"ustreamer", "ustreamer",
libraries=["rt", "m", "pthread"], libraries=["rt", "m", "pthread"],
extra_compile_args=["-std=c17", "-D_GNU_SOURCE"],
undef_macros=["NDEBUG"], undef_macros=["NDEBUG"],
sources=["src/" + name for name in os.listdir("src") if name.endswith(".c")], sources=_find_sources(".c"),
depends=["src/" + name for name in os.listdir("src") if name.endswith(".h")], depends=_find_sources(".h"),
), ),
], ],
) )

View File

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

View File

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

View File

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

View File

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

1
python/src/uslibs/frame.c Symbolic link
View File

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

1
python/src/uslibs/frame.h Symbolic link
View File

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

View File

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

1
python/src/uslibs/tools.h Symbolic link
View File

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

View File

@@ -13,9 +13,9 @@
#include <Python.h> #include <Python.h>
#include "tools.h" #include "uslibs/tools.h"
#include "frame.h" #include "uslibs/frame.h"
#include "memsinksh.h" #include "uslibs/memsinksh.h"
typedef struct { typedef struct {
@@ -27,34 +27,34 @@ typedef struct {
double drop_same_frames; double drop_same_frames;
int fd; int fd;
memsink_shared_s *mem; us_memsink_shared_s *mem;
uint64_t frame_id; uint64_t frame_id;
long double frame_ts; long double frame_ts;
frame_s *frame; us_frame_s *frame;
} MemsinkObject; } _MemsinkObject;
#define MEM(_next) self->mem->_next #define _MEM(x_next) self->mem->x_next
#define FRAME(_next) self->frame->_next #define _FRAME(x_next) self->frame->x_next
static void MemsinkObject_destroy_internals(MemsinkObject *self) { static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
if (self->mem != NULL) { if (self->mem != NULL) {
memsink_shared_unmap(self->mem); us_memsink_shared_unmap(self->mem);
self->mem = NULL; self->mem = NULL;
} }
if (self->fd > 0) { if (self->fd >= 0) {
close(self->fd); close(self->fd);
self->fd = -1; self->fd = -1;
} }
if (self->frame) { if (self->frame != NULL) {
frame_destroy(self->frame); us_frame_destroy(self->frame);
self->frame = NULL; self->frame = NULL;
} }
} }
static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwargs) { static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
self->lock_timeout = 1; self->lock_timeout = 1;
self->wait_timeout = 1; self->wait_timeout = 1;
@@ -78,14 +78,14 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
# undef SET_DOUBLE # undef SET_DOUBLE
self->frame = frame_init(); self->frame = us_frame_init();
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) { if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
PyErr_SetFromErrno(PyExc_OSError); PyErr_SetFromErrno(PyExc_OSError);
goto error; goto error;
} }
if ((self->mem = memsink_shared_map(self->fd)) == NULL) { if ((self->mem = us_memsink_shared_map(self->fd)) == NULL) {
PyErr_SetFromErrno(PyExc_OSError); PyErr_SetFromErrno(PyExc_OSError);
goto error; goto error;
} }
@@ -93,37 +93,37 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
return 0; return 0;
error: error:
MemsinkObject_destroy_internals(self); _MemsinkObject_destroy_internals(self);
return -1; return -1;
} }
static PyObject *MemsinkObject_repr(MemsinkObject *self) { static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
char repr[1024]; char repr[1024];
snprintf(repr, 1023, "<Memsink(%s)>", self->obj); snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
return Py_BuildValue("s", repr); return Py_BuildValue("s", repr);
} }
static void MemsinkObject_dealloc(MemsinkObject *self) { static void _MemsinkObject_dealloc(_MemsinkObject *self) {
MemsinkObject_destroy_internals(self); _MemsinkObject_destroy_internals(self);
PyObject_Del(self); PyObject_Del(self);
} }
static PyObject *MemsinkObject_close(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { static PyObject *_MemsinkObject_close(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
MemsinkObject_destroy_internals(self); _MemsinkObject_destroy_internals(self);
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject *MemsinkObject_enter(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { static PyObject *_MemsinkObject_enter(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
Py_INCREF(self); Py_INCREF(self);
return (PyObject *)self; return (PyObject *)self;
} }
static PyObject *MemsinkObject_exit(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
return PyObject_CallMethod((PyObject *)self, "close", ""); return PyObject_CallMethod((PyObject *)self, "close", "");
} }
static int wait_frame(MemsinkObject *self) { static int _wait_frame(_MemsinkObject *self) {
long double deadline_ts = get_now_monotonic() + self->wait_timeout; const long double deadline_ts = us_get_now_monotonic() + self->wait_timeout;
# define RETURN_OS_ERROR { \ # define RETURN_OS_ERROR { \
Py_BLOCK_THREADS \ Py_BLOCK_THREADS \
@@ -135,21 +135,21 @@ static int wait_frame(MemsinkObject *self) {
do { do {
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
int retval = flock_timedwait_monotonic(self->fd, self->lock_timeout); const int retval = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
now = get_now_monotonic(); now = us_get_now_monotonic();
if (retval < 0 && errno != EWOULDBLOCK) { if (retval < 0 && errno != EWOULDBLOCK) {
RETURN_OS_ERROR; RETURN_OS_ERROR;
} else if (retval == 0) { } else if (retval == 0) {
if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && MEM(id) != self->frame_id) { if (_MEM(magic) == US_MEMSINK_MAGIC && _MEM(version) == US_MEMSINK_VERSION && _MEM(id) != self->frame_id) {
if (self->drop_same_frames > 0) { if (self->drop_same_frames > 0) {
if ( if (
FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame) US_FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
&& (self->frame_ts + self->drop_same_frames > now) && (self->frame_ts + self->drop_same_frames > now)
&& !memcmp(FRAME(data), MEM(data), MEM(used)) && !memcmp(_FRAME(data), _MEM(data), _MEM(used))
) { ) {
self->frame_id = MEM(id); self->frame_id = _MEM(id);
goto drop; goto drop;
} }
} }
@@ -181,23 +181,23 @@ static int wait_frame(MemsinkObject *self) {
return -2; return -2;
} }
static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
if (self->mem == NULL || self->fd <= 0) { if (self->mem == NULL || self->fd <= 0) {
PyErr_SetString(PyExc_RuntimeError, "Closed"); PyErr_SetString(PyExc_RuntimeError, "Closed");
return NULL; return NULL;
} }
switch (wait_frame(self)) { switch (_wait_frame(self)) {
case 0: break; case 0: break;
case -2: Py_RETURN_NONE; case -2: Py_RETURN_NONE;
default: return NULL; default: return NULL;
} }
frame_set_data(self->frame, MEM(data), MEM(used)); us_frame_set_data(self->frame, _MEM(data), _MEM(used));
FRAME_COPY_META(self->mem, self->frame); US_FRAME_COPY_META(self->mem, self->frame);
self->frame_id = MEM(id); self->frame_id = _MEM(id);
self->frame_ts = get_now_monotonic(); self->frame_ts = us_get_now_monotonic();
MEM(last_client_ts) = self->frame_ts; _MEM(last_client_ts) = self->frame_ts;
if (flock(self->fd, LOCK_UN) < 0) { if (flock(self->fd, LOCK_UN) < 0) {
return PyErr_SetFromErrno(PyExc_OSError); return PyErr_SetFromErrno(PyExc_OSError);
@@ -219,7 +219,7 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
} \ } \
Py_DECREF(_tmp); \ Py_DECREF(_tmp); \
} }
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(FRAME(_key))) # define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(_FRAME(_key)))
SET_NUMBER(width, Long, Long); SET_NUMBER(width, Long, Long);
SET_NUMBER(height, Long, Long); SET_NUMBER(height, Long, Long);
@@ -230,7 +230,7 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
SET_NUMBER(grab_ts, Double, Float); SET_NUMBER(grab_ts, Double, Float);
SET_NUMBER(encode_begin_ts, Double, Float); SET_NUMBER(encode_begin_ts, Double, Float);
SET_NUMBER(encode_end_ts, Double, Float); SET_NUMBER(encode_end_ts, Double, Float);
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)FRAME(data), FRAME(used))); SET_VALUE("data", PyBytes_FromStringAndSize((const char *)_FRAME(data), _FRAME(used)));
# undef SET_NUMBER # undef SET_NUMBER
# undef SET_VALUE # undef SET_VALUE
@@ -238,12 +238,12 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
return dict_frame; return dict_frame;
} }
static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { static PyObject *_MemsinkObject_is_opened(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
return PyBool_FromLong(self->mem != NULL && self->fd > 0); return PyBool_FromLong(self->mem != NULL && self->fd > 0);
} }
#define FIELD_GETTER(_field, _from, _to) \ #define FIELD_GETTER(_field, _from, _to) \
static PyObject *MemsinkObject_getter_##_field(MemsinkObject *self, void *Py_UNUSED(closure)) { \ static PyObject *_MemsinkObject_getter_##_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
return Py##_to##_From##_from(self->_field); \ return Py##_to##_From##_from(self->_field); \
} }
@@ -254,9 +254,9 @@ FIELD_GETTER(drop_same_frames, Double, Float)
#undef FIELD_GETTER #undef FIELD_GETTER
static PyMethodDef MemsinkObject_methods[] = { static PyMethodDef _MemsinkObject_methods[] = {
# define ADD_METHOD(_name, _method, _flags) \ # define ADD_METHOD(_name, _method, _flags) \
{.ml_name = _name, .ml_meth = (PyCFunction)MemsinkObject_##_method, .ml_flags = (_flags)} {.ml_name = _name, .ml_meth = (PyCFunction)_MemsinkObject_##_method, .ml_flags = (_flags)}
ADD_METHOD("close", close, METH_NOARGS), ADD_METHOD("close", close, METH_NOARGS),
ADD_METHOD("__enter__", enter, METH_NOARGS), ADD_METHOD("__enter__", enter, METH_NOARGS),
ADD_METHOD("__exit__", exit, METH_VARARGS), ADD_METHOD("__exit__", exit, METH_VARARGS),
@@ -266,8 +266,8 @@ static PyMethodDef MemsinkObject_methods[] = {
# undef ADD_METHOD # undef ADD_METHOD
}; };
static PyGetSetDef MemsinkObject_getsets[] = { static PyGetSetDef _MemsinkObject_getsets[] = {
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)MemsinkObject_getter_##_field} # define ADD_GETTER(_field) {.name = #_field, .get = (getter)_MemsinkObject_getter_##_field}
ADD_GETTER(obj), ADD_GETTER(obj),
ADD_GETTER(lock_timeout), ADD_GETTER(lock_timeout),
ADD_GETTER(wait_timeout), ADD_GETTER(wait_timeout),
@@ -276,43 +276,40 @@ static PyGetSetDef MemsinkObject_getsets[] = {
# undef ADD_GETTER # undef ADD_GETTER
}; };
static PyTypeObject MemsinkType = { static PyTypeObject _MemsinkType = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "ustreamer.Memsink", .tp_name = "ustreamer.Memsink",
.tp_basicsize = sizeof(MemsinkObject), .tp_basicsize = sizeof(_MemsinkObject),
.tp_flags = Py_TPFLAGS_DEFAULT, .tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew, .tp_new = PyType_GenericNew,
.tp_init = (initproc)MemsinkObject_init, .tp_init = (initproc)_MemsinkObject_init,
.tp_dealloc = (destructor)MemsinkObject_dealloc, .tp_dealloc = (destructor)_MemsinkObject_dealloc,
.tp_repr = (reprfunc)MemsinkObject_repr, .tp_repr = (reprfunc)_MemsinkObject_repr,
.tp_methods = MemsinkObject_methods, .tp_methods = _MemsinkObject_methods,
.tp_getset = MemsinkObject_getsets, .tp_getset = _MemsinkObject_getsets,
}; };
static PyModuleDef ustreamer_Module = { static PyModuleDef _Module = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "ustreamer", .m_name = "ustreamer",
.m_size = -1, .m_size = -1,
}; };
PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
PyObject *module = PyModule_Create(&ustreamer_Module); PyObject *module = PyModule_Create(&_Module);
if (module == NULL) { if (module == NULL) {
return NULL; return NULL;
} }
if (PyType_Ready(&MemsinkType) < 0) { if (PyType_Ready(&_MemsinkType) < 0) {
return NULL; return NULL;
} }
Py_INCREF(&MemsinkType); Py_INCREF(&_MemsinkType);
if (PyModule_AddObject(module, "Memsink", (PyObject *)&MemsinkType) < 0) { if (PyModule_AddObject(module, "Memsink", (PyObject *)&_MemsinkType) < 0) {
return NULL; return NULL;
} }
return module; return module;
} }
#undef FRAME
#undef MEM

View File

@@ -10,7 +10,7 @@ LDFLAGS ?=
_USTR = ustreamer.bin _USTR = ustreamer.bin
_DUMP = ustreamer-dump.bin _DUMP = ustreamer-dump.bin
_CFLAGS = -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS) _CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
_LDFLAGS = $(LDFLAGS) _LDFLAGS = $(LDFLAGS)
_COMMON_LIBS = -lm -ljpeg -pthread -lrt _COMMON_LIBS = -lm -ljpeg -pthread -lrt

View File

@@ -23,17 +23,17 @@
#include "file.h" #include "file.h"
output_file_s *output_file_init(const char *path, bool json) { us_output_file_s *us_output_file_init(const char *path, bool json) {
output_file_s *output; us_output_file_s *output;
A_CALLOC(output, 1); US_CALLOC(output, 1);
if (!strcmp(path, "-")) { if (!strcmp(path, "-")) {
LOG_INFO("Using output: <stdout>"); US_LOG_INFO("Using output: <stdout>");
output->fp = stdout; output->fp = stdout;
} else { } else {
LOG_INFO("Using output: %s", path); US_LOG_INFO("Using output: %s", path);
if ((output->fp = fopen(path, "wb")) == NULL) { if ((output->fp = fopen(path, "wb")) == NULL) {
LOG_PERROR("Can't open output file"); US_LOG_PERROR("Can't open output file");
goto error; goto error;
} }
} }
@@ -42,14 +42,14 @@ output_file_s *output_file_init(const char *path, bool json) {
return output; return output;
error: error:
output_file_destroy(output); us_output_file_destroy(output);
return NULL; return NULL;
} }
void output_file_write(void *v_output, const frame_s *frame) { void us_output_file_write(void *v_output, const us_frame_s *frame) {
output_file_s *output = (output_file_s *)v_output; us_output_file_s *output = (us_output_file_s *)v_output;
if (output->json) { if (output->json) {
base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated); us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
fprintf(output->fp, fprintf(output->fp,
"{\"size\": %zu, \"width\": %u, \"height\": %u," "{\"size\": %zu, \"width\": %u, \"height\": %u,"
" \"format\": %u, \"stride\": %u, \"online\": %u," " \"format\": %u, \"stride\": %u, \"online\": %u,"
@@ -65,14 +65,12 @@ void output_file_write(void *v_output, const frame_s *frame) {
fflush(output->fp); fflush(output->fp);
} }
void output_file_destroy(void *v_output) { void us_output_file_destroy(void *v_output) {
output_file_s *output = (output_file_s *)v_output; us_output_file_s *output = (us_output_file_s *)v_output;
if (output->base64_data) { US_DELETE(output->base64_data, free);
free(output->base64_data);
}
if (output->fp && output->fp != stdout) { if (output->fp && output->fp != stdout) {
if (fclose(output->fp) < 0) { if (fclose(output->fp) < 0) {
LOG_PERROR("Can't close output file"); US_LOG_PERROR("Can't close output file");
} }
} }
free(output); free(output);

View File

@@ -41,9 +41,9 @@ typedef struct {
FILE *fp; FILE *fp;
char *base64_data; char *base64_data;
size_t base64_allocated; size_t base64_allocated;
} output_file_s; } us_output_file_s;
output_file_s *output_file_init(const char *path, bool json); us_output_file_s *us_output_file_init(const char *path, bool json);
void output_file_write(void *v_output, const frame_s *frame); void us_output_file_write(void *v_output, const us_frame_s *frame);
void output_file_destroy(void *v_output); void us_output_file_destroy(void *v_output);

View File

@@ -31,7 +31,7 @@
#include <errno.h> #include <errno.h>
#include <assert.h> #include <assert.h>
#include "../libs/config.h" #include "../libs/const.h"
#include "../libs/tools.h" #include "../libs/tools.h"
#include "../libs/logging.h" #include "../libs/logging.h"
#include "../libs/frame.h" #include "../libs/frame.h"
@@ -82,12 +82,12 @@ static const struct option _LONG_OPTS[] = {
}; };
volatile bool global_stop = false; volatile bool _g_stop = false;
typedef struct { typedef struct {
void *v_output; void *v_output;
void (*write)(void *v_output, const frame_s *frame); void (*write)(void *v_output, const us_frame_s *frame);
void (*destroy)(void *v_output); void (*destroy)(void *v_output);
} _output_context_s; } _output_context_s;
@@ -104,8 +104,8 @@ static void _help(FILE *fp);
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
LOGGING_INIT; US_LOGGING_INIT;
A_THREAD_RENAME("main"); US_THREAD_RENAME("main");
char *sink_name = NULL; char *sink_name = NULL;
unsigned sink_timeout = 1; unsigned sink_timeout = 1;
@@ -140,7 +140,7 @@ int main(int argc, char *argv[]) {
} }
char short_opts[128]; char short_opts[128];
build_short_options(_LONG_OPTS, short_opts, 128); us_build_short_options(_LONG_OPTS, short_opts, 128);
for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) { for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) {
switch (ch) { switch (ch) {
@@ -151,15 +151,15 @@ int main(int argc, char *argv[]) {
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0); case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60); case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0); case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF); case _O_PERF: OPT_SET(us_log_level, US_LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE); case _O_VERBOSE: OPT_SET(us_log_level, US_LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG); case _O_DEBUG: OPT_SET(us_log_level, US_LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true); case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false); case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false);
case _O_HELP: _help(stdout); return 0; case _O_HELP: _help(stdout); return 0;
case _O_VERSION: puts(VERSION); return 0; case _O_VERSION: puts(US_VERSION); return 0;
case 0: break; case 0: break;
default: return 1; default: return 1;
@@ -178,15 +178,15 @@ int main(int argc, char *argv[]) {
_output_context_s ctx = {0}; _output_context_s ctx = {0};
if (output_path && output_path[0] != '\0') { if (output_path && output_path[0] != '\0') {
if ((ctx.v_output = (void *)output_file_init(output_path, output_json)) == NULL) { if ((ctx.v_output = (void *)us_output_file_init(output_path, output_json)) == NULL) {
return 1; return 1;
} }
ctx.write = output_file_write; ctx.write = us_output_file_write;
ctx.destroy = output_file_destroy; ctx.destroy = us_output_file_destroy;
} }
_install_signal_handlers(); _install_signal_handlers();
int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, &ctx)); const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, &ctx));
if (ctx.v_output && ctx.destroy) { if (ctx.v_output && ctx.destroy) {
ctx.destroy(ctx.v_output); ctx.destroy(ctx.v_output);
} }
@@ -196,12 +196,12 @@ int main(int argc, char *argv[]) {
static void _signal_handler(int signum) { static void _signal_handler(int signum) {
switch (signum) { switch (signum) {
case SIGTERM: LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break; case SIGTERM: US_LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
case SIGINT: LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break; case SIGINT: US_LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
case SIGPIPE: LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break; case SIGPIPE: US_LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break;
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break; default: US_LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
} }
global_stop = true; _g_stop = true;
} }
static void _install_signal_handlers(void) { static void _install_signal_handlers(void) {
@@ -213,13 +213,13 @@ static void _install_signal_handlers(void) {
assert(!sigaddset(&sig_act.sa_mask, SIGTERM)); assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE)); assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
LOG_DEBUG("Installing SIGINT handler ..."); US_LOG_DEBUG("Installing SIGINT handler ...");
assert(!sigaction(SIGINT, &sig_act, NULL)); assert(!sigaction(SIGINT, &sig_act, NULL));
LOG_DEBUG("Installing SIGTERM handler ..."); US_LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGTERM, &sig_act, NULL)); assert(!sigaction(SIGTERM, &sig_act, NULL));
LOG_DEBUG("Installing SIGTERM handler ..."); US_LOG_DEBUG("Installing SIGTERM handler ...");
assert(!sigaction(SIGPIPE, &sig_act, NULL)); assert(!sigaction(SIGPIPE, &sig_act, NULL));
} }
@@ -232,12 +232,12 @@ static int _dump_sink(
count = -1; count = -1;
} }
useconds_t interval_us = interval * 1000000; const useconds_t interval_us = interval * 1000000;
frame_s *frame = frame_init(); us_frame_s *frame = us_frame_init();
memsink_s *sink = NULL; us_memsink_s *sink = NULL;
if ((sink = memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) { if ((sink = us_memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
goto error; goto error;
} }
@@ -247,32 +247,32 @@ static int _dump_sink(
long double last_ts = 0; long double last_ts = 0;
while (!global_stop) { while (!_g_stop) {
int error = memsink_client_get(sink, frame); int error = us_memsink_client_get(sink, frame);
if (error == 0) { if (error == 0) {
const long double now = get_now_monotonic(); const long double now = us_get_now_monotonic();
const long long now_second = floor_ms(now); const long long now_second = us_floor_ms(now);
char fourcc_str[8]; char fourcc_str[8];
LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf", US_LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf",
frame->used, frame->width, frame->height, frame->used, frame->width, frame->height,
fourcc_to_string(frame->format, fourcc_str, 8), us_fourcc_to_string(frame->format, fourcc_str, 8),
frame->stride, frame->online, frame->key, frame->stride, frame->online, frame->key,
now - frame->grab_ts, (last_ts ? now - last_ts : 0)); now - frame->grab_ts, (last_ts ? now - last_ts : 0));
last_ts = now; last_ts = now;
LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf", US_LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts); frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
if (now_second != fps_second) { if (now_second != fps_second) {
fps = fps_accum; fps = fps_accum;
fps_accum = 0; fps_accum = 0;
fps_second = now_second; fps_second = now_second;
LOG_PERF_FPS("A new second has come; captured_fps=%u", fps); US_LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
} }
fps_accum += 1; fps_accum += 1;
if (ctx->v_output) { if (ctx->v_output != NULL) {
ctx->write(ctx->v_output, frame); ctx->write(ctx->v_output, frame);
} }
@@ -300,12 +300,10 @@ static int _dump_sink(
retval = -1; retval = -1;
ok: ok:
if (sink) { US_DELETE(sink, us_memsink_destroy);
memsink_destroy(sink); us_frame_destroy(frame);
}
frame_destroy(frame);
LOG_INFO("Bye-bye"); US_LOG_INFO("Bye-bye");
return retval; return retval;
} }
@@ -313,7 +311,7 @@ static void _help(FILE *fp) {
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__) # define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file"); SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
SAY("═════════════════════════════════════════════════════"); SAY("═════════════════════════════════════════════════════");
SAY("Version: %s; license: GPLv3", VERSION); SAY("Version: %s; license: GPLv3", US_VERSION);
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n"); SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
SAY("Example:"); SAY("Example:");
SAY("════════"); SAY("════════");

37
src/libs/array.h Normal file
View File

@@ -0,0 +1,37 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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 <assert.h>
#define US_ARRAY_LEN(x_array) (sizeof(x_array) / sizeof((x_array)[0]))
#define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \
const int m_len = US_ARRAY_LEN(x_array); \
assert(x_start <= m_len); \
for (int m_index = x_start; m_index < m_len; ++m_index) { \
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_index]; \
__VA_ARGS__ \
} \
}

View File

@@ -37,11 +37,11 @@ static const char _ENCODING_TABLE[] = {
static const unsigned _MOD_TABLE[] = {0, 2, 1}; static const unsigned _MOD_TABLE[] = {0, 2, 1};
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) { void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0' const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
if (*encoded == NULL || (allocated && *allocated < encoded_size)) { if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
A_REALLOC(*encoded, encoded_size); US_REALLOC(*encoded, encoded_size);
if (allocated) { if (allocated) {
*allocated = encoded_size; *allocated = encoded_size;
} }
@@ -54,7 +54,7 @@ void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *all
OCTET(octet_c); OCTET(octet_c);
# undef OCTET # undef OCTET
unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; const unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F] # define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
ENCODE(3); ENCODE(3);

View File

@@ -31,4 +31,4 @@
#include "tools.h" #include "tools.h"
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated); void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);

32
src/libs/const.h Normal file
View File

@@ -0,0 +1,32 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 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
#define US_VERSION_MAJOR 5
#define US_VERSION_MINOR 17
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
#define US_VERSION US_MAKE_VERSION1(US_VERSION_MAJOR, US_VERSION_MINOR)
#define US_VERSION_U ((unsigned)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))

View File

@@ -23,55 +23,53 @@
#include "frame.h" #include "frame.h"
frame_s *frame_init(void) { us_frame_s *us_frame_init(void) {
frame_s *frame; us_frame_s *frame;
A_CALLOC(frame, 1); US_CALLOC(frame, 1);
frame_realloc_data(frame, 512 * 1024); us_frame_realloc_data(frame, 512 * 1024);
frame->dma_fd = -1; frame->dma_fd = -1;
return frame; return frame;
} }
void frame_destroy(frame_s *frame) { void us_frame_destroy(us_frame_s *frame) {
if (frame->data) { US_DELETE(frame->data, free);
free(frame->data);
}
free(frame); free(frame);
} }
void frame_realloc_data(frame_s *frame, size_t size) { void us_frame_realloc_data(us_frame_s *frame, size_t size) {
if (frame->allocated < size) { if (frame->allocated < size) {
A_REALLOC(frame->data, size); US_REALLOC(frame->data, size);
frame->allocated = size; frame->allocated = size;
} }
} }
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) { void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size) {
frame_realloc_data(frame, size); us_frame_realloc_data(frame, size);
memcpy(frame->data, data, size); memcpy(frame->data, data, size);
frame->used = size; frame->used = size;
} }
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size) { void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size) {
size_t new_used = frame->used + size; const size_t new_used = frame->used + size;
frame_realloc_data(frame, new_used); us_frame_realloc_data(frame, new_used);
memcpy(frame->data + frame->used, data, size); memcpy(frame->data + frame->used, data, size);
frame->used = new_used; frame->used = new_used;
} }
void frame_copy(const frame_s *src, frame_s *dest) { void us_frame_copy(const us_frame_s *src, us_frame_s *dest) {
frame_set_data(dest, src->data, src->used); us_frame_set_data(dest, src->data, src->used);
FRAME_COPY_META(src, dest); US_FRAME_COPY_META(src, dest);
} }
bool frame_compare(const frame_s *a, const frame_s *b) { bool us_frame_compare(const us_frame_s *a, const us_frame_s *b) {
return ( return (
a->allocated && b->allocated a->allocated && b->allocated
&& FRAME_COMPARE_META_USED_NOTS(a, b) && US_FRAME_COMPARE_META_USED_NOTS(a, b)
&& !memcmp(a->data, b->data, b->used) && !memcmp(a->data, b->data, b->used)
); );
} }
unsigned frame_get_padding(const frame_s *frame) { unsigned us_frame_get_padding(const us_frame_s *frame) {
unsigned bytes_per_pixel = 0; unsigned bytes_per_pixel = 0;
switch (frame->format) { switch (frame->format) {
case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_YUYV:
@@ -89,7 +87,7 @@ unsigned frame_get_padding(const frame_s *frame) {
return 0; return 0;
} }
const char *fourcc_to_string(unsigned format, char *buf, size_t size) { const char *us_fourcc_to_string(unsigned format, char *buf, size_t size) {
assert(size >= 8); assert(size >= 8);
buf[0] = format & 0x7F; buf[0] = format & 0x7F;
buf[1] = (format >> 8) & 0x7F; buf[1] = (format >> 8) & 0x7F;

View File

@@ -35,84 +35,84 @@
typedef struct { typedef struct {
uint8_t *data; uint8_t *data;
size_t used; size_t used;
size_t allocated; size_t allocated;
int dma_fd; int dma_fd;
unsigned width; unsigned width;
unsigned height; unsigned height;
unsigned format; unsigned format;
unsigned stride; unsigned stride;
// Stride is a bytesperline in V4L2 // Stride is a bytesperline in V4L2
// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html // https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd // https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
bool online; bool online;
bool key; bool key;
long double grab_ts; long double grab_ts;
long double encode_begin_ts; long double encode_begin_ts;
long double encode_end_ts; long double encode_end_ts;
} frame_s; } us_frame_s;
#define FRAME_COPY_META(_src, _dest) { \ #define US_FRAME_COPY_META(x_src, x_dest) { \
_dest->width = _src->width; \ x_dest->width = x_src->width; \
_dest->height = _src->height; \ x_dest->height = x_src->height; \
_dest->format = _src->format; \ x_dest->format = x_src->format; \
_dest->stride = _src->stride; \ x_dest->stride = x_src->stride; \
_dest->online = _src->online; \ x_dest->online = x_src->online; \
_dest->key = _src->key; \ x_dest->key = x_src->key; \
_dest->grab_ts = _src->grab_ts; \ x_dest->grab_ts = x_src->grab_ts; \
_dest->encode_begin_ts = _src->encode_begin_ts; \ x_dest->encode_begin_ts = x_src->encode_begin_ts; \
_dest->encode_end_ts = _src->encode_end_ts; \ x_dest->encode_end_ts = x_src->encode_end_ts; \
} }
static inline void frame_copy_meta(const frame_s *src, frame_s *dest) { static inline void us_frame_copy_meta(const us_frame_s *src, us_frame_s *dest) {
FRAME_COPY_META(src, dest); US_FRAME_COPY_META(src, dest);
} }
#define FRAME_COMPARE_META_USED_NOTS(_a, _b) ( \ #define US_FRAME_COMPARE_META_USED_NOTS(x_a, x_b) ( \
_a->used == _b->used \ x_a->used == x_b->used \
&& _a->width == _b->width \ && x_a->width == x_b->width \
&& _a->height == _b->height \ && x_a->height == x_b->height \
&& _a->format == _b->format \ && x_a->format == x_b->format \
&& _a->stride == _b->stride \ && x_a->stride == x_b->stride \
&& _a->online == _b->online \ && x_a->online == x_b->online \
&& _a->key == _b->key \ && x_a->key == x_b->key \
) )
static inline void frame_encoding_begin(const frame_s *src, frame_s *dest, unsigned format) { static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, unsigned format) {
assert(src->used > 0); assert(src->used > 0);
frame_copy_meta(src, dest); us_frame_copy_meta(src, dest);
dest->encode_begin_ts = get_now_monotonic(); dest->encode_begin_ts = us_get_now_monotonic();
dest->format = format; dest->format = format;
dest->stride = 0; dest->stride = 0;
dest->used = 0; dest->used = 0;
} }
static inline void frame_encoding_end(frame_s *dest) { static inline void us_frame_encoding_end(us_frame_s *dest) {
assert(dest->used > 0); assert(dest->used > 0);
dest->encode_end_ts = get_now_monotonic(); dest->encode_end_ts = us_get_now_monotonic();
} }
frame_s *frame_init(void); us_frame_s *us_frame_init(void);
void frame_destroy(frame_s *frame); void us_frame_destroy(us_frame_s *frame);
void frame_realloc_data(frame_s *frame, size_t size); void us_frame_realloc_data(us_frame_s *frame, size_t size);
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size); void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size);
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size); void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size);
void frame_copy(const frame_s *src, frame_s *dest); void us_frame_copy(const us_frame_s *src, us_frame_s *dest);
bool frame_compare(const frame_s *a, const frame_s *b); bool us_frame_compare(const us_frame_s *a, const us_frame_s *b);
unsigned frame_get_padding(const frame_s *frame); unsigned us_frame_get_padding(const us_frame_s *frame);
const char *fourcc_to_string(unsigned format, char *buf, size_t size); const char *us_fourcc_to_string(unsigned format, char *buf, size_t size);
static inline bool is_jpeg(unsigned format) { static inline bool us_is_jpeg(unsigned format) {
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG); return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
} }

View File

@@ -25,47 +25,47 @@
#include <assert.h> #include <assert.h>
#define LIST_STRUCT(...) \ #define US_LIST_STRUCT(...) \
__VA_ARGS__ *prev; \ __VA_ARGS__ *prev; \
__VA_ARGS__ *next; __VA_ARGS__ *next;
#define LIST_ITERATE(_first, _item, ...) { \ #define US_LIST_ITERATE(x_first, x_item, ...) { \
for (__typeof__(_first) _item = _first; _item;) { \ for (__typeof__(x_first) x_item = x_first; x_item;) { \
__typeof__(_first) _next = _item->next; \ __typeof__(x_first) m_next = x_item->next; \
__VA_ARGS__ \ __VA_ARGS__ \
_item = _next; \ x_item = m_next; \
} \ } \
} }
#define LIST_APPEND(_first, _item) { \ #define US_LIST_APPEND(x_first, x_item) { \
if (_first == NULL) { \ if (x_first == NULL) { \
_first = _item; \ x_first = x_item; \
} else { \ } else { \
__typeof__(_first) _last = _first; \ __typeof__(x_first) m_last = x_first; \
for (; _last->next; _last = _last->next); \ for (; m_last->next; m_last = m_last->next); \
_item->prev = _last; \ x_item->prev = m_last; \
_last->next = _item; \ m_last->next = x_item; \
} \ } \
} }
#define LIST_APPEND_C(_first, _item, _count) { \ #define US_LIST_APPEND_C(x_first, x_item, x_count) { \
LIST_APPEND(_first, _item); \ US_LIST_APPEND(x_first, x_item); \
++(_count); \ ++(x_count); \
} }
#define LIST_REMOVE(_first, _item) { \ #define US_LIST_REMOVE(x_first, x_item) { \
if (_item->prev == NULL) { \ if (x_item->prev == NULL) { \
_first = _item->next; \ x_first = x_item->next; \
} else { \ } else { \
_item->prev->next = _item->next; \ x_item->prev->next = x_item->next; \
} \ } \
if (_item->next != NULL) { \ if (x_item->next != NULL) { \
_item->next->prev = _item->prev; \ x_item->next->prev = x_item->prev; \
} \ } \
} }
#define LIST_REMOVE_C(_first, _item, _count) { \ #define US_LIST_REMOVE_C(x_first, x_item, x_count) { \
LIST_REMOVE(_first, _item); \ US_LIST_REMOVE(x_first, x_item); \
assert((_count) >= 1); \ assert((x_count) >= 1); \
--(_count); \ --(x_count); \
} }

View File

@@ -23,7 +23,7 @@
#include "logging.h" #include "logging.h"
enum log_level_t us_log_level; enum us_log_level_t us_log_level;
bool us_log_colored; bool us_log_colored;

View File

@@ -37,126 +37,126 @@
#include "threading.h" #include "threading.h"
enum log_level_t { enum us_log_level_t {
LOG_LEVEL_INFO, US_LOG_LEVEL_INFO,
LOG_LEVEL_PERF, US_LOG_LEVEL_PERF,
LOG_LEVEL_VERBOSE, US_LOG_LEVEL_VERBOSE,
LOG_LEVEL_DEBUG, US_LOG_LEVEL_DEBUG,
}; };
extern enum log_level_t us_log_level; extern enum us_log_level_t us_log_level;
extern bool us_log_colored; extern bool us_log_colored;
extern pthread_mutex_t us_log_mutex; extern pthread_mutex_t us_log_mutex;
#define LOGGING_INIT { \ #define US_LOGGING_INIT { \
us_log_level = LOG_LEVEL_INFO; \ us_log_level = US_LOG_LEVEL_INFO; \
us_log_colored = isatty(2); \ us_log_colored = isatty(2); \
A_MUTEX_INIT(&us_log_mutex); \ US_MUTEX_INIT(us_log_mutex); \
} }
#define LOGGING_DESTROY A_MUTEX_DESTROY(&us_log_mutex) #define US_LOGGING_DESTROY US_MUTEX_DESTROY(us_log_mutex)
#define LOGGING_LOCK A_MUTEX_LOCK(&us_log_mutex) #define US_LOGGING_LOCK US_MUTEX_LOCK(us_log_mutex)
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&us_log_mutex) #define US_LOGGING_UNLOCK US_MUTEX_UNLOCK(us_log_mutex)
#define COLOR_GRAY "\x1b[30;1m" #define US_COLOR_GRAY "\x1b[30;1m"
#define COLOR_RED "\x1b[31;1m" #define US_COLOR_RED "\x1b[31;1m"
#define COLOR_GREEN "\x1b[32;1m" #define US_COLOR_GREEN "\x1b[32;1m"
#define COLOR_YELLOW "\x1b[33;1m" #define US_COLOR_YELLOW "\x1b[33;1m"
#define COLOR_BLUE "\x1b[34;1m" #define US_COLOR_BLUE "\x1b[34;1m"
#define COLOR_CYAN "\x1b[36;1m" #define US_COLOR_CYAN "\x1b[36;1m"
#define COLOR_RESET "\x1b[0m" #define US_COLOR_RESET "\x1b[0m"
#define SEP_INFO(_ch) { \ #define US_SEP_INFO(x_ch) { \
LOGGING_LOCK; \ US_LOGGING_LOCK; \
for (int _i = 0; _i < 80; ++_i) { \ for (int m_count = 0; m_count < 80; ++m_count) { \
fputc(_ch, stderr); \ fputc((x_ch), stderr); \
} \ } \
fputc('\n', stderr); \ fputc('\n', stderr); \
fflush(stderr); \ fflush(stderr); \
LOGGING_UNLOCK; \ US_LOGGING_UNLOCK; \
} }
#define SEP_DEBUG(_ch) { \ #define US_SEP_DEBUG(x_ch) { \
if (us_log_level >= LOG_LEVEL_DEBUG) { \ if (us_log_level >= US_LOG_LEVEL_DEBUG) { \
SEP_INFO(_ch); \ US_SEP_INFO(x_ch); \
} \ } \
} }
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \ #define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
char _tname_buf[MAX_THREAD_NAME] = {0}; \ char m_tname_buf[US_MAX_THREAD_NAME] = {0}; \
thread_get_name(_tname_buf); \ us_thread_get_name(m_tname_buf); \
if (us_log_colored) { \ if (us_log_colored) { \
fprintf(stderr, COLOR_GRAY "-- " _label_color _label COLOR_GRAY \ fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
" [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \ " [%.03Lf %9s]" " -- " US_COLOR_RESET x_msg_color x_msg US_COLOR_RESET, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \ us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
} else { \ } else { \
fprintf(stderr, "-- " _label " [%.03Lf %9s] -- " _msg, \ fprintf(stderr, "-- " x_label " [%.03Lf %9s] -- " x_msg, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \ us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
} \ } \
fputc('\n', stderr); \ fputc('\n', stderr); \
fflush(stderr); \ fflush(stderr); \
} }
#define LOG_PRINTF(_label_color, _label, _msg_color, _msg, ...) { \ #define US_LOG_PRINTF(x_label_color, x_label, x_msg_color, x_msg, ...) { \
LOGGING_LOCK; \ US_LOGGING_LOCK; \
LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ##__VA_ARGS__); \ US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \ US_LOGGING_UNLOCK; \
} }
#define LOG_ERROR(_msg, ...) { \ #define US_LOG_ERROR(x_msg, ...) { \
LOG_PRINTF(COLOR_RED, "ERROR", COLOR_RED, _msg, ##__VA_ARGS__); \ US_LOG_PRINTF(US_COLOR_RED, "ERROR", US_COLOR_RED, x_msg, ##__VA_ARGS__); \
} }
#define LOG_PERROR(_msg, ...) { \ #define US_LOG_PERROR(x_msg, ...) { \
char _perror_buf[1024] = {0}; \ char m_perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \ char *m_perror_ptr = us_errno_to_string(errno, m_perror_buf, 1024); \
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \ US_LOG_ERROR(x_msg ": %s", ##__VA_ARGS__, m_perror_ptr); \
} }
#define LOG_INFO(_msg, ...) { \ #define US_LOG_INFO(x_msg, ...) { \
LOG_PRINTF(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \ US_LOG_PRINTF(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
} }
#define LOG_INFO_NOLOCK(_msg, ...) { \ #define US_LOG_INFO_NOLOCK(x_msg, ...) { \
LOG_PRINTF_NOLOCK(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \ US_LOG_PRINTF_NOLOCK(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
} }
#define LOG_PERF(_msg, ...) { \ #define US_LOG_PERF(x_msg, ...) { \
if (us_log_level >= LOG_LEVEL_PERF) { \ if (us_log_level >= US_LOG_LEVEL_PERF) { \
LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \ US_LOG_PRINTF(US_COLOR_CYAN, "PERF ", US_COLOR_CYAN, x_msg, ##__VA_ARGS__); \
} \ } \
} }
#define LOG_PERF_FPS(_msg, ...) { \ #define US_LOG_PERF_FPS(x_msg, ...) { \
if (us_log_level >= LOG_LEVEL_PERF) { \ if (us_log_level >= US_LOG_LEVEL_PERF) { \
LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \ US_LOG_PRINTF(US_COLOR_YELLOW, "PERF ", US_COLOR_YELLOW, x_msg, ##__VA_ARGS__); \
} \ } \
} }
#define LOG_VERBOSE(_msg, ...) { \ #define US_LOG_VERBOSE(x_msg, ...) { \
if (us_log_level >= LOG_LEVEL_VERBOSE) { \ if (us_log_level >= US_LOG_LEVEL_VERBOSE) { \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \ US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg, ##__VA_ARGS__); \
} \ } \
} }
#define LOG_VERBOSE_PERROR(_msg, ...) { \ #define US_LOG_VERBOSE_PERROR(x_msg, ...) { \
if (us_log_level >= LOG_LEVEL_VERBOSE) { \ if (us_log_level >= US_LOG_LEVEL_VERBOSE) { \
char _perror_buf[1024] = {0}; \ char m_perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \ char *m_perror_ptr = us_errno_to_string(errno, m_perror_buf, 1023); \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \ US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg ": %s", ##__VA_ARGS__, m_perror_ptr); \
} \ } \
} }
#define LOG_DEBUG(_msg, ...) { \ #define US_LOG_DEBUG(x_msg, ...) { \
if (us_log_level >= LOG_LEVEL_DEBUG) { \ if (us_log_level >= US_LOG_LEVEL_DEBUG) { \
LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \ US_LOG_PRINTF(US_COLOR_GRAY, "DEBUG", US_COLOR_GRAY, x_msg, ##__VA_ARGS__); \
} \ } \
} }

View File

@@ -23,12 +23,12 @@
#include "memsink.h" #include "memsink.h"
memsink_s *memsink_init( us_memsink_s *us_memsink_init(
const char *name, const char *obj, bool server, const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) { mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
memsink_s *sink; us_memsink_s *sink;
A_CALLOC(sink, 1); US_CALLOC(sink, 1);
sink->name = name; sink->name = name;
sink->obj = obj; sink->obj = obj;
sink->server = server; sink->server = server;
@@ -36,57 +36,56 @@ memsink_s *memsink_init(
sink->client_ttl = client_ttl; sink->client_ttl = client_ttl;
sink->timeout = timeout; sink->timeout = timeout;
sink->fd = -1; sink->fd = -1;
sink->mem = MAP_FAILED;
atomic_init(&sink->has_clients, false); atomic_init(&sink->has_clients, false);
LOG_INFO("Using %s-sink: %s", name, obj); US_LOG_INFO("Using %s-sink: %s", name, obj);
mode_t mask = umask(0); const mode_t mask = umask(0);
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode); sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
umask(mask); umask(mask);
if (sink->fd == -1) { if (sink->fd == -1) {
umask(mask); umask(mask);
LOG_PERROR("%s-sink: Can't open shared memory", name); US_LOG_PERROR("%s-sink: Can't open shared memory", name);
goto error; goto error;
} }
if (sink->server && ftruncate(sink->fd, sizeof(memsink_shared_s)) < 0) { if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s)) < 0) {
LOG_PERROR("%s-sink: Can't truncate shared memory", name); US_LOG_PERROR("%s-sink: Can't truncate shared memory", name);
goto error; goto error;
} }
if ((sink->mem = memsink_shared_map(sink->fd)) == NULL) { if ((sink->mem = us_memsink_shared_map(sink->fd)) == NULL) {
LOG_PERROR("%s-sink: Can't mmap shared memory", name); US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
goto error; goto error;
} }
return sink; return sink;
error: error:
memsink_destroy(sink); us_memsink_destroy(sink);
return NULL; return NULL;
} }
void memsink_destroy(memsink_s *sink) { void us_memsink_destroy(us_memsink_s *sink) {
if (sink->mem != MAP_FAILED) { if (sink->mem != NULL) {
if (memsink_shared_unmap(sink->mem) < 0) { if (us_memsink_shared_unmap(sink->mem) < 0) {
LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name); US_LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
} }
} }
if (sink->fd >= 0) { if (sink->fd >= 0) {
if (close(sink->fd) < 0) { if (close(sink->fd) < 0) {
LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name); US_LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name);
} }
if (sink->rm && shm_unlink(sink->obj) < 0) { if (sink->rm && shm_unlink(sink->obj) < 0) {
if (errno != ENOENT) { if (errno != ENOENT) {
LOG_PERROR("%s-sink: Can't remove shared memory", sink->name); US_LOG_PERROR("%s-sink: Can't remove shared memory", sink->name);
} }
} }
} }
free(sink); free(sink);
} }
bool memsink_server_check(memsink_s *sink, const frame_s *frame) { bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
// Return true (the need to write to memsink) on any of these conditions: // Return true (the need to write to memsink) on any of these conditions:
// - EWOULDBLOCK - we have an active client; // - EWOULDBLOCK - we have an active client;
// - Incorrect magic or version - need to first write; // - Incorrect magic or version - need to first write;
@@ -100,97 +99,97 @@ bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
atomic_store(&sink->has_clients, true); atomic_store(&sink->has_clients, true);
return true; return true;
} }
LOG_PERROR("%s-sink: Can't lock memory", sink->name); US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return false; return false;
} }
if (sink->mem->magic != MEMSINK_MAGIC || sink->mem->version != MEMSINK_VERSION) { if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) {
return true; return true;
} }
bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > 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); atomic_store(&sink->has_clients, has_clients);
if (flock(sink->fd, LOCK_UN) < 0) { if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name); US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return false; return false;
} }
return (has_clients || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));; return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
} }
int memsink_server_put(memsink_s *sink, const frame_s *frame) { int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) {
assert(sink->server); assert(sink->server);
const long double now = get_now_monotonic(); const long double now = us_get_now_monotonic();
if (frame->used > MEMSINK_MAX_DATA) { if (frame->used > US_MEMSINK_MAX_DATA) {
LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)", US_LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
sink->name, frame->used, MEMSINK_MAX_DATA); sink->name, frame->used, US_MEMSINK_MAX_DATA);
return 0; // -2 return 0; // -2
} }
if (flock_timedwait_monotonic(sink->fd, 1) == 0) { if (us_flock_timedwait_monotonic(sink->fd, 1) == 0) {
LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name); US_LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
sink->last_id = get_now_id(); sink->last_id = us_get_now_id();
sink->mem->id = sink->last_id; sink->mem->id = sink->last_id;
memcpy(sink->mem->data, frame->data, frame->used); memcpy(sink->mem->data, frame->data, frame->used);
sink->mem->used = frame->used; sink->mem->used = frame->used;
FRAME_COPY_META(frame, sink->mem); US_FRAME_COPY_META(frame, sink->mem);
sink->mem->magic = MEMSINK_MAGIC; sink->mem->magic = US_MEMSINK_MAGIC;
sink->mem->version = MEMSINK_VERSION; sink->mem->version = US_MEMSINK_VERSION;
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic())); atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic()));
if (flock(sink->fd, LOCK_UN) < 0) { if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name); US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1; return -1;
} }
LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf", US_LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
sink->name, get_now_monotonic() - now); sink->name, us_get_now_monotonic() - now);
} else if (errno == EWOULDBLOCK) { } else if (errno == EWOULDBLOCK) {
LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name); US_LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
} else { } else {
LOG_PERROR("%s-sink: Can't lock memory", sink->name); US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return -1; return -1;
} }
return 0; return 0;
} }
int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress unusedFunction int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame) { // cppcheck-suppress unusedFunction
assert(!sink->server); // Client only assert(!sink->server); // Client only
if (flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) { if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
if (errno == EWOULDBLOCK) { if (errno == EWOULDBLOCK) {
return -2; return -2;
} }
LOG_PERROR("%s-sink: Can't lock memory", sink->name); US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
return -1; return -1;
} }
int retval = -2; // Not updated int retval = -2; // Not updated
if (sink->mem->magic == MEMSINK_MAGIC) { if (sink->mem->magic == US_MEMSINK_MAGIC) {
if (sink->mem->version != MEMSINK_VERSION) { if (sink->mem->version != US_MEMSINK_VERSION) {
LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u", US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
sink->name, sink->mem->version, MEMSINK_VERSION); sink->name, sink->mem->version, US_MEMSINK_VERSION);
retval = -1; retval = -1;
goto done; goto done;
} }
if (sink->mem->id != sink->last_id) { // When updated if (sink->mem->id != sink->last_id) { // When updated
sink->last_id = sink->mem->id; sink->last_id = sink->mem->id;
frame_set_data(frame, sink->mem->data, sink->mem->used); us_frame_set_data(frame, sink->mem->data, sink->mem->used);
FRAME_COPY_META(sink->mem, frame); US_FRAME_COPY_META(sink->mem, frame);
retval = 0; retval = 0;
} }
sink->mem->last_client_ts = get_now_monotonic(); sink->mem->last_client_ts = us_get_now_monotonic();
} }
done: done:
if (flock(sink->fd, LOCK_UN) < 0) { if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name); US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
return -1; return -1;
} }
return retval; return retval;

View File

@@ -42,27 +42,27 @@
typedef struct { typedef struct {
const char *name; const char *name;
const char *obj; const char *obj;
bool server; bool server;
bool rm; bool rm;
unsigned client_ttl; // Only for server unsigned client_ttl; // Only for server
unsigned timeout; unsigned timeout;
int fd; int fd;
memsink_shared_s *mem; us_memsink_shared_s *mem;
uint64_t last_id; uint64_t last_id;
atomic_bool has_clients; // Only for server atomic_bool has_clients; // Only for server
} memsink_s; } us_memsink_s;
memsink_s *memsink_init( us_memsink_s *us_memsink_init(
const char *name, const char *obj, bool server, const char *name, const char *obj, bool server,
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout); mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
void memsink_destroy(memsink_s *sink); void us_memsink_destroy(us_memsink_s *sink);
bool memsink_server_check(memsink_s *sink, const frame_s *frame); bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
int memsink_server_put(memsink_s *sink, const frame_s *frame); int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame);
int memsink_client_get(memsink_s *sink, frame_s *frame); int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame);

View File

@@ -29,13 +29,13 @@
#include <sys/mman.h> #include <sys/mman.h>
#define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE) #define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
#define MEMSINK_VERSION ((uint32_t)2) #define US_MEMSINK_VERSION ((uint32_t)2)
#ifndef CFG_MEMSINK_MAX_DATA #ifndef US_CFG_MEMSINK_MAX_DATA
# define CFG_MEMSINK_MAX_DATA 33554432 # define US_CFG_MEMSINK_MAX_DATA 33554432
#endif #endif
#define MEMSINK_MAX_DATA ((size_t)(CFG_MEMSINK_MAX_DATA)) #define US_MEMSINK_MAX_DATA ((size_t)(US_CFG_MEMSINK_MAX_DATA))
typedef struct { typedef struct {
@@ -58,14 +58,14 @@ typedef struct {
long double last_client_ts; long double last_client_ts;
uint8_t data[MEMSINK_MAX_DATA]; uint8_t data[US_MEMSINK_MAX_DATA];
} memsink_shared_s; } us_memsink_shared_s;
INLINE memsink_shared_s *memsink_shared_map(int fd) { INLINE us_memsink_shared_s *us_memsink_shared_map(int fd) {
memsink_shared_s *mem = mmap( us_memsink_shared_s *mem = mmap(
NULL, NULL,
sizeof(memsink_shared_s), sizeof(us_memsink_shared_s),
PROT_READ | PROT_WRITE, PROT_READ | PROT_WRITE,
MAP_SHARED, MAP_SHARED,
fd, fd,
@@ -78,7 +78,7 @@ INLINE memsink_shared_s *memsink_shared_map(int fd) {
return mem; return mem;
} }
INLINE int memsink_shared_unmap(memsink_shared_s *mem) { INLINE int us_memsink_shared_unmap(us_memsink_shared_s *mem) {
assert(mem != NULL); assert(mem != NULL);
return munmap(mem, sizeof(memsink_shared_s)); return munmap(mem, sizeof(us_memsink_shared_s));
} }

View File

@@ -23,7 +23,7 @@
#include "options.h" #include "options.h"
void build_short_options(const struct option opts[], char *short_opts, size_t size) { void us_build_short_options(const struct option opts[], char *short_opts, size_t size) {
memset(short_opts, 0, size); memset(short_opts, 0, size);
for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) { for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
assert(short_index < size - 3); assert(short_index < size - 3);

View File

@@ -30,4 +30,4 @@
#include <sys/types.h> #include <sys/types.h>
void build_short_options(const struct option opts[], char *short_opts, size_t size); void us_build_short_options(const struct option opts[], char *short_opts, size_t size);

View File

@@ -72,26 +72,25 @@ extern char **environ;
#ifdef HAS_PDEATHSIG #ifdef HAS_PDEATHSIG
INLINE int process_track_parent_death(void) { INLINE int us_process_track_parent_death(void) {
pid_t parent = getppid(); const pid_t parent = getppid();
int signum = SIGTERM; int signum = SIGTERM;
# if defined(__linux__) # if defined(__linux__)
int retval = prctl(PR_SET_PDEATHSIG, signum); const int retval = prctl(PR_SET_PDEATHSIG, signum);
# elif defined(__FreeBSD__) # elif defined(__FreeBSD__)
int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum); const int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
# else # else
# error WTF? # error WTF?
# endif # endif
if (retval < 0) { if (retval < 0) {
LOG_PERROR("Can't set to receive SIGTERM on parent process death"); US_LOG_PERROR("Can't set to receive SIGTERM on parent process death");
return -1; return -1;
} }
if (kill(parent, 0) < 0) { if (kill(parent, 0) < 0) {
LOG_PERROR("The parent process %d is already dead", parent); US_LOG_PERROR("The parent process %d is already dead", parent);
return -1; return -1;
} }
return 0; return 0;
} }
#endif #endif
@@ -99,21 +98,21 @@ INLINE int process_track_parent_death(void) {
#ifdef WITH_SETPROCTITLE #ifdef WITH_SETPROCTITLE
# pragma GCC diagnostic ignored "-Wunused-parameter" # pragma GCC diagnostic ignored "-Wunused-parameter"
# pragma GCC diagnostic push # pragma GCC diagnostic push
INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix) { INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefix) {
# pragma GCC diagnostic pop # pragma GCC diagnostic pop
char *cmdline = NULL; char *cmdline = NULL;
size_t allocated = 2048; size_t allocated = 2048;
size_t used = 0; size_t used = 0;
A_REALLOC(cmdline, allocated); US_REALLOC(cmdline, allocated);
cmdline[0] = '\0'; cmdline[0] = '\0';
for (int index = 0; index < argc; ++index) { for (int index = 0; index < argc; ++index) {
size_t arg_len = strlen(argv[index]); size_t arg_len = strlen(argv[index]);
if (used + arg_len + 16 >= allocated) { if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048; allocated += arg_len + 2048;
A_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert) US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
} }
strcat(cmdline, " "); strcat(cmdline, " ");
@@ -130,18 +129,16 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
} }
#endif #endif
INLINE void process_notify_parent(void) { INLINE void us_process_notify_parent(void) {
pid_t parent = getppid(); const pid_t parent = getppid();
if (kill(parent, SIGUSR2) < 0) { if (kill(parent, SIGUSR2) < 0) {
LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent); US_LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
} }
} }
INLINE void process_suicide(void) { INLINE void us_process_suicide(void) {
pid_t pid = getpid(); const pid_t pid = getpid();
if (kill(pid, SIGTERM) < 0) { if (kill(pid, SIGTERM) < 0) {
LOG_PERROR("Can't send SIGTERM to own pid %d", pid); US_LOG_PERROR("Can't send SIGTERM to own pid %d", pid);
} }
} }

View File

@@ -41,37 +41,37 @@
#ifdef PTHREAD_MAX_NAMELEN_NP #ifdef PTHREAD_MAX_NAMELEN_NP
# define MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP)) # define US_MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
#else #else
# define MAX_THREAD_NAME ((size_t)16) # define US_MAX_THREAD_NAME ((size_t)16)
#endif #endif
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg)) #define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
#define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL)) #define US_THREAD_JOIN(x_tid) assert(!pthread_join((x_tid), NULL))
#ifdef WITH_PTHREAD_NP #ifdef WITH_PTHREAD_NP
# define A_THREAD_RENAME(_fmt, ...) { \ # define US_THREAD_RENAME(x_fmt, ...) { \
char _new_tname_buf[MAX_THREAD_NAME] = {0}; \ char m_new_tname_buf[US_MAX_THREAD_NAME] = {0}; \
assert(snprintf(_new_tname_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \ assert(snprintf(m_new_tname_buf, US_MAX_THREAD_NAME, (x_fmt), ##__VA_ARGS__) > 0); \
thread_set_name(_new_tname_buf); \ us_thread_set_name(m_new_tname_buf); \
} }
#else #else
# define A_THREAD_RENAME(_fmt, ...) # define US_THREAD_RENAME(_fmt, ...)
#endif #endif
#define A_MUTEX_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL)) #define US_MUTEX_INIT(x_mutex) assert(!pthread_mutex_init(&(x_mutex), NULL))
#define A_MUTEX_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex)) #define US_MUTEX_DESTROY(x_mutex) assert(!pthread_mutex_destroy(&(x_mutex)))
#define A_MUTEX_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex)) #define US_MUTEX_LOCK(x_mutex) assert(!pthread_mutex_lock(&(x_mutex)))
#define A_MUTEX_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex)) #define US_MUTEX_UNLOCK(x_mutex) assert(!pthread_mutex_unlock(&(x_mutex)))
#define A_COND_INIT(_cond) assert(!pthread_cond_init(_cond, NULL)) #define US_COND_INIT(x_cond) assert(!pthread_cond_init(&(x_cond), NULL))
#define A_COND_DESTROY(_cond) assert(!pthread_cond_destroy(_cond)) #define US_COND_DESTROY(x_cond) assert(!pthread_cond_destroy(&(x_cond)))
#define A_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__)) #define US_COND_SIGNAL(x_cond) assert(!pthread_cond_signal(&(x_cond)))
#define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); } #define US_COND_WAIT_TRUE(x_var, x_cond, x_mutex) { while(!(x_var)) assert(!pthread_cond_wait(&(x_cond), &(x_mutex))); }
#ifdef WITH_PTHREAD_NP #ifdef WITH_PTHREAD_NP
INLINE void thread_set_name(const char *name) { INLINE void us_thread_set_name(const char *name) {
# if defined(__linux__) # if defined(__linux__)
pthread_setname_np(pthread_self(), name); pthread_setname_np(pthread_self(), name);
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) # elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
@@ -79,45 +79,45 @@ INLINE void thread_set_name(const char *name) {
# elif defined(__NetBSD__) # elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void *)name); pthread_setname_np(pthread_self(), "%s", (void *)name);
# else # else
# error thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0 # error us_thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# endif # endif
} }
#endif #endif
INLINE void thread_get_name(char *name) { // Always required for logging 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, MAX_THREAD_NAME); retval = pthread_getname_np(pthread_self(), name, US_MAX_THREAD_NAME);
# 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, MAX_THREAD_NAME); pthread_get_name_np(pthread_self(), name, US_MAX_THREAD_NAME);
if (name[0] != '\0') { if (name[0] != '\0') {
retval = 0; retval = 0;
} }
# else # else
# error thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0 # error us_thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# endif # endif
if (retval < 0) { if (retval < 0) {
#endif #endif
#if defined(__linux__) #if defined(__linux__)
pid_t tid = syscall(SYS_gettid); const pid_t tid = syscall(SYS_gettid);
#elif defined(__FreeBSD__) #elif defined(__FreeBSD__)
pid_t tid = syscall(SYS_thr_self); const pid_t tid = syscall(SYS_thr_self);
#elif defined(__OpenBSD__) #elif defined(__OpenBSD__)
pid_t tid = syscall(SYS_getthrid); const pid_t tid = syscall(SYS_getthrid);
#elif defined(__NetBSD__) #elif defined(__NetBSD__)
pid_t tid = syscall(SYS__lwp_self); const pid_t tid = syscall(SYS__lwp_self);
#elif defined(__DragonFly__) #elif defined(__DragonFly__)
pid_t tid = syscall(SYS_lwp_gettid); const pid_t tid = syscall(SYS_lwp_gettid);
#else #else
pid_t tid = 0; // Makes cppcheck happy const pid_t tid = 0; // Makes cppcheck happy
# warning gettid() not implemented # warning gettid() not implemented
#endif #endif
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", tid) > 0); assert(snprintf(name, US_MAX_THREAD_NAME, "tid=%d", tid) > 0);
#ifdef WITH_PTHREAD_NP #ifdef WITH_PTHREAD_NP
} }

View File

@@ -35,6 +35,7 @@
#include <time.h> #include <time.h>
#include <assert.h> #include <assert.h>
#include <sys/types.h>
#include <sys/file.h> #include <sys/file.h>
@@ -52,36 +53,41 @@
#define INLINE inline __attribute__((always_inline)) #define INLINE inline __attribute__((always_inline))
#define UNUSED __attribute__((unused)) #define UNUSED __attribute__((unused))
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest))))) #define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest))))) #define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj)) #define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
#define A_ASPRINTF(_dest, _fmt, ...) assert(asprintf(&(_dest), _fmt, ##__VA_ARGS__) >= 0) #define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) >= 0)
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
INLINE const char *bool_to_string(bool flag) { INLINE char *us_strdup(const char *str) {
char *const new = strdup(str);
assert(new != NULL);
return new;
}
INLINE const char *us_bool_to_string(bool flag) {
return (flag ? "true" : "false"); return (flag ? "true" : "false");
} }
INLINE size_t align_size(size_t size, size_t to) { INLINE size_t us_align_size(size_t size, size_t to) {
return ((size + (to - 1)) & ~(to - 1)); return ((size + (to - 1)) & ~(to - 1));
} }
INLINE unsigned min_u(unsigned a, unsigned b) { INLINE unsigned us_min_u(unsigned a, unsigned b) {
return (a < b ? a : b); return (a < b ? a : b);
} }
INLINE unsigned max_u(unsigned a, unsigned b) { INLINE unsigned us_max_u(unsigned a, unsigned b) {
return (a > b ? a : b); return (a > b ? a : b);
} }
INLINE long long floor_ms(long double now) { INLINE long long us_floor_ms(long double now) {
return (long long)now - (now < (long long)now); // floor() return (long long)now - (now < (long long)now); // floor()
} }
INLINE uint32_t triple_u32(uint32_t x) { INLINE uint32_t us_triple_u32(uint32_t x) {
// https://nullprogram.com/blog/2018/07/31/ // https://nullprogram.com/blog/2018/07/31/
x ^= x >> 17; x ^= x >> 17;
x *= UINT32_C(0xED5AD4BB); x *= UINT32_C(0xED5AD4BB);
@@ -93,7 +99,7 @@ INLINE uint32_t triple_u32(uint32_t x) {
return x; return x;
} }
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) { INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
struct timespec ts; struct timespec ts;
assert(!clock_gettime(clk_id, &ts)); assert(!clock_gettime(clk_id, &ts));
*sec = ts.tv_sec; *sec = ts.tv_sec;
@@ -106,53 +112,66 @@ INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
} }
#if defined(CLOCK_MONOTONIC_RAW) #if defined(CLOCK_MONOTONIC_RAW)
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW # define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
#elif defined(CLOCK_MONOTONIC_FAST) #elif defined(CLOCK_MONOTONIC_FAST)
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST # define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
#else #else
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC # define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC
#endif #endif
INLINE long double get_now_monotonic(void) { INLINE long double us_get_now_monotonic(void) {
time_t sec; time_t sec;
long msec; long msec;
get_now(X_CLOCK_MONOTONIC, &sec, &msec); us_get_now(_X_CLOCK_MONOTONIC, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000; return (long double)sec + ((long double)msec) / 1000;
} }
INLINE uint64_t get_now_monotonic_u64(void) { INLINE uint64_t us_get_now_monotonic_u64(void) {
struct timespec ts; struct timespec ts;
assert(!clock_gettime(X_CLOCK_MONOTONIC, &ts)); assert(!clock_gettime(_X_CLOCK_MONOTONIC, &ts));
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000; return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
} }
INLINE uint64_t get_now_id(void) { #undef _X_CLOCK_MONOTONIC
uint64_t now = get_now_monotonic_u64();
return (uint64_t)triple_u32(now) | ((uint64_t)triple_u32(now + 12345) << 32); INLINE uint64_t us_get_now_id(void) {
const uint64_t now = us_get_now_monotonic_u64();
return (uint64_t)us_triple_u32(now) | ((uint64_t)us_triple_u32(now + 12345) << 32);
} }
#undef X_CLOCK_MONOTONIC INLINE long double us_get_now_real(void) {
INLINE long double get_now_real(void) {
time_t sec; time_t sec;
long msec; long msec;
get_now(CLOCK_REALTIME, &sec, &msec); us_get_now(CLOCK_REALTIME, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000; return (long double)sec + ((long double)msec) / 1000;
} }
INLINE unsigned get_cores_available(void) { INLINE unsigned us_get_cores_available(void) {
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN); long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf); cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
return max_u(min_u(cores_sysconf, 4), 1); return us_max_u(us_min_u(cores_sysconf, 4), 1);
} }
INLINE int flock_timedwait_monotonic(int fd, long double timeout) { INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
long double deadline_ts = get_now_monotonic() + timeout; ts->tv_sec = (long)ld;
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
if (ts->tv_nsec > 999999999L) {
ts->tv_sec += 1;
ts->tv_nsec = 0;
}
}
INLINE long double us_timespec_to_ld(const struct timespec *ts) {
return ts->tv_sec + ((long double)ts->tv_nsec) / 1000000000;
}
INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
const long double deadline_ts = us_get_now_monotonic() + timeout;
int retval = -1; int retval = -1;
while (true) { while (true) {
retval = flock(fd, LOCK_EX | LOCK_NB); retval = flock(fd, LOCK_EX | LOCK_NB);
if (retval == 0 || errno != EWOULDBLOCK || get_now_monotonic() > deadline_ts) { if (retval == 0 || errno != EWOULDBLOCK || us_get_now_monotonic() > deadline_ts) {
break; break;
} }
if (usleep(1000) < 0) { if (usleep(1000) < 0) {
@@ -162,11 +181,11 @@ INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
return retval; return retval;
} }
INLINE char *errno_to_string(int error, char *buf, size_t size) { INLINE char *us_errno_to_string(int error, char *buf, size_t size) {
assert(buf); assert(buf != NULL);
assert(size > 0); assert(size > 0);
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL); locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
char *str = "!!! newlocale() error !!!"; const char *str = "!!! newlocale() error !!!";
strncpy(buf, (locale ? strerror_l(error, locale) : str), size - 1); strncpy(buf, (locale ? strerror_l(error, locale) : str), size - 1);
buf[size - 1] = '\0'; buf[size - 1] = '\0';
if (locale) { if (locale) {

View File

@@ -26,15 +26,15 @@
typedef struct { typedef struct {
struct jpeg_error_mgr mgr; // Default manager struct jpeg_error_mgr mgr; // Default manager
jmp_buf jmp; jmp_buf jmp;
const frame_s *frame; const us_frame_s *frame;
} _jpeg_error_manager_s; } _jpeg_error_manager_s;
static void _jpeg_error_handler(j_common_ptr jpeg); static void _jpeg_error_handler(j_common_ptr jpeg);
int unjpeg(const frame_s *src, frame_s *dest, bool decode) { int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
assert(is_jpeg(src->format)); assert(us_is_jpeg(src->format));
volatile int retval = 0; volatile int retval = 0;
@@ -57,7 +57,7 @@ int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
jpeg_start_decompress(&jpeg); jpeg_start_decompress(&jpeg);
frame_copy_meta(src, dest); us_frame_copy_meta(src, dest);
dest->format = V4L2_PIX_FMT_RGB24; dest->format = V4L2_PIX_FMT_RGB24;
dest->width = jpeg.output_width; dest->width = jpeg.output_width;
dest->height = jpeg.output_height; dest->height = jpeg.output_height;
@@ -68,10 +68,10 @@ int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
JSAMPARRAY scanlines; JSAMPARRAY scanlines;
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1); scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1);
frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2); us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
while (jpeg.output_scanline < jpeg.output_height) { while (jpeg.output_scanline < jpeg.output_height) {
jpeg_read_scanlines(&jpeg, scanlines, 1); jpeg_read_scanlines(&jpeg, scanlines, 1);
frame_append_data(dest, scanlines[0], dest->stride); us_frame_append_data(dest, scanlines[0], dest->stride);
} }
jpeg_finish_decompress(&jpeg); jpeg_finish_decompress(&jpeg);
@@ -87,6 +87,6 @@ static void _jpeg_error_handler(j_common_ptr jpeg) {
char msg[JMSG_LENGTH_MAX]; char msg[JMSG_LENGTH_MAX];
(*jpeg_error->mgr.format_message)(jpeg, msg); (*jpeg_error->mgr.format_message)(jpeg, msg);
LOG_ERROR("Can't decompress JPEG: %s", msg); US_LOG_ERROR("Can't decompress JPEG: %s", msg);
longjmp(jpeg_error->jmp, -1); longjmp(jpeg_error->jmp, -1);
} }

View File

@@ -37,4 +37,4 @@
#include "frame.h" #include "frame.h"
int unjpeg(const frame_s *src, frame_s *dest, bool decode); int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode);

View File

@@ -26,18 +26,15 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include "../libs/tools.h"
#include "../libs/logging.h"
#ifndef US_CFG_XIOCTL_RETRIES
#ifndef CFG_XIOCTL_RETRIES # define US_CFG_XIOCTL_RETRIES 4
# define CFG_XIOCTL_RETRIES 4
#endif #endif
#define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES)) #define _XIOCTL_RETRIES ((unsigned)(US_CFG_XIOCTL_RETRIES))
INLINE int xioctl(int fd, int request, void *arg) { INLINE int us_xioctl(int fd, int request, void *arg) {
int retries = XIOCTL_RETRIES; int retries = _XIOCTL_RETRIES;
int retval = -1; int retval = -1;
do { do {
@@ -51,10 +48,5 @@ INLINE int xioctl(int fd, int request, void *arg) {
|| errno == ETIMEDOUT || errno == ETIMEDOUT
) )
); );
// cppcheck-suppress knownConditionTrueFalse
if (retval && retries <= 0) {
LOG_PERROR("ioctl(%d) retried %u times; giving up", request, XIOCTL_RETRIES);
}
return retval; return retval;
} }

View File

@@ -23,85 +23,83 @@
#include "blank.h" #include "blank.h"
static frame_s *_init_internal(void); static us_frame_s *_init_internal(void);
static frame_s *_init_external(const char *path); static us_frame_s *_init_external(const char *path);
frame_s *blank_frame_init(const char *path) { us_frame_s *us_blank_frame_init(const char *path) {
frame_s *blank = NULL; us_frame_s *blank = NULL;
if (path && path[0] != '\0') { if (path && path[0] != '\0') {
blank = _init_external(path); blank = _init_external(path);
} }
if (blank) { if (blank != NULL) {
LOG_INFO("Using external blank placeholder: %s", path); US_LOG_INFO("Using external blank placeholder: %s", path);
} else { } else {
blank = _init_internal(); blank = _init_internal();
LOG_INFO("Using internal blank placeholder"); US_LOG_INFO("Using internal blank placeholder");
} }
return blank; return blank;
} }
static frame_s *_init_internal(void) { static us_frame_s *_init_internal(void) {
frame_s *blank = frame_init(); us_frame_s *const blank = us_frame_init();
frame_set_data(blank, BLANK_JPEG_DATA, BLANK_JPEG_DATA_SIZE); us_frame_set_data(blank, US_BLANK_JPEG_DATA, US_BLANK_JPEG_DATA_SIZE);
blank->width = BLANK_JPEG_WIDTH; blank->width = US_BLANK_JPEG_WIDTH;
blank->height = BLANK_JPEG_HEIGHT; blank->height = US_BLANK_JPEG_HEIGHT;
blank->format = V4L2_PIX_FMT_JPEG; blank->format = V4L2_PIX_FMT_JPEG;
return blank; return blank;
} }
static frame_s *_init_external(const char *path) { static us_frame_s *_init_external(const char *path) {
FILE *fp = NULL; FILE *fp = NULL;
frame_s *blank = frame_init(); us_frame_s *blank = us_frame_init();
blank->format = V4L2_PIX_FMT_JPEG; blank->format = V4L2_PIX_FMT_JPEG;
if ((fp = fopen(path, "rb")) == NULL) { if ((fp = fopen(path, "rb")) == NULL) {
LOG_PERROR("Can't open blank placeholder '%s'", path); US_LOG_PERROR("Can't open blank placeholder '%s'", path);
goto error; goto error;
} }
# define CHUNK_SIZE ((size_t)(100 * 1024)) # define CHUNK_SIZE ((size_t)(100 * 1024))
while (true) { while (true) {
if (blank->used + CHUNK_SIZE >= blank->allocated) { if (blank->used + CHUNK_SIZE >= blank->allocated) {
frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2); us_frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
} }
size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp); const size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
blank->used += readed; blank->used += readed;
if (readed < CHUNK_SIZE) { if (readed < CHUNK_SIZE) {
if (feof(fp)) { if (feof(fp)) {
break; break;
} else { } else {
LOG_PERROR("Can't read blank placeholder"); US_LOG_PERROR("Can't read blank placeholder");
goto error; goto error;
} }
} }
} }
# undef CHUNK_SIZE # undef CHUNK_SIZE
frame_s *decoded = frame_init(); us_frame_s *const decoded = us_frame_init();
if (unjpeg(blank, decoded, false) < 0) { if (us_unjpeg(blank, decoded, false) < 0) {
frame_destroy(decoded); us_frame_destroy(decoded);
goto error; goto error;
} }
blank->width = decoded->width; blank->width = decoded->width;
blank->height = decoded->height; blank->height = decoded->height;
frame_destroy(decoded); us_frame_destroy(decoded);
goto ok; goto ok;
error: error:
frame_destroy(blank); us_frame_destroy(blank);
blank = NULL; blank = NULL;
ok: ok:
if (fp) { US_DELETE(fp, fclose);
fclose(fp);
}
return blank; return blank;
} }

View File

@@ -35,4 +35,4 @@
#include "data/blank_jpeg.h" #include "data/blank_jpeg.h"
frame_s *blank_frame_init(const char *path); us_frame_s *us_blank_frame_init(const char *path);

View File

@@ -22,11 +22,11 @@
#include "blank_jpeg.h" #include "blank_jpeg.h"
const unsigned BLANK_JPEG_WIDTH = 640; const unsigned US_BLANK_JPEG_WIDTH = 640;
const unsigned BLANK_JPEG_HEIGHT = 480; const unsigned US_BLANK_JPEG_HEIGHT = 480;
const size_t BLANK_JPEG_DATA_SIZE = 13845; const size_t US_BLANK_JPEG_DATA_SIZE = 13845;
const uint8_t BLANK_JPEG_DATA[] = { const uint8_t US_BLANK_JPEG_DATA[] = {
0xFF, 0xD8, 0xFF, 0xE1, 0x09, 0x50, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62, 0xFF, 0xD8, 0xFF, 0xE1, 0x09, 0x50, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62,
0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x78, 0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x00, 0x3C, 0x3F, 0x78, 0x70, 0x61, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x78, 0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x00, 0x3C, 0x3F, 0x78, 0x70, 0x61,
0x63, 0x6B, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D, 0x22, 0xEF, 0xBB, 0xBF, 0x22, 0x20, 0x69, 0x64, 0x3D, 0x63, 0x6B, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D, 0x22, 0xEF, 0xBB, 0xBF, 0x22, 0x20, 0x69, 0x64, 0x3D,

View File

@@ -27,8 +27,8 @@
#include <sys/types.h> #include <sys/types.h>
extern const unsigned BLANK_JPEG_WIDTH; extern const unsigned US_BLANK_JPEG_WIDTH;
extern const unsigned BLANK_JPEG_HEIGHT; extern const unsigned US_BLANK_JPEG_HEIGHT;
extern const size_t BLANK_JPEG_DATA_SIZE; extern const size_t US_BLANK_JPEG_DATA_SIZE;
extern const uint8_t BLANK_JPEG_DATA[]; extern const uint8_t US_BLANK_JPEG_DATA[];

View File

@@ -22,7 +22,7 @@
#include "index_html.h" #include "index_html.h"
const char *const HTML_INDEX_PAGE = " \ const char *const US_HTML_INDEX_PAGE = " \
<!DOCTYPE html> \ <!DOCTYPE html> \
\ \
<html> \ <html> \
@@ -33,7 +33,7 @@ const char *const HTML_INDEX_PAGE = " \
</head> \ </head> \
\ \
<body> \ <body> \
<h3>&micro;Streamer v" VERSION "</h3> \ <h3>&micro;Streamer v" US_VERSION "</h3> \
<hr> \ <hr> \
<ul> \ <ul> \
<li> \ <li> \

View File

@@ -24,7 +24,7 @@
#include <sys/types.h> #include <sys/types.h>
#include "../../libs/config.h" #include "../../libs/const.h"
extern const char *const HTML_INDEX_PAGE; extern const char *const US_HTML_INDEX_PAGE;

File diff suppressed because it is too large Load Diff

View File

@@ -42,80 +42,80 @@
#include <linux/v4l2-controls.h> #include <linux/v4l2-controls.h>
#include "../libs/tools.h" #include "../libs/tools.h"
#include "../libs/array.h"
#include "../libs/logging.h" #include "../libs/logging.h"
#include "../libs/threading.h" #include "../libs/threading.h"
#include "../libs/frame.h" #include "../libs/frame.h"
#include "../libs/xioctl.h"
#include "xioctl.h"
#define VIDEO_MIN_WIDTH ((unsigned)160) #define US_VIDEO_MIN_WIDTH ((unsigned)160)
#define VIDEO_MAX_WIDTH ((unsigned)10240) #define US_VIDEO_MAX_WIDTH ((unsigned)10240)
#define VIDEO_MIN_HEIGHT ((unsigned)120) #define US_VIDEO_MIN_HEIGHT ((unsigned)120)
#define VIDEO_MAX_HEIGHT ((unsigned)4320) #define US_VIDEO_MAX_HEIGHT ((unsigned)4320)
#define VIDEO_MAX_FPS ((unsigned)120) #define US_VIDEO_MAX_FPS ((unsigned)120)
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN #define US_STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define STANDARDS_STR "PAL, NTSC, SECAM" #define US_STANDARDS_STR "PAL, NTSC, SECAM"
#define FORMAT_UNKNOWN -1 #define US_FORMAT_UNKNOWN -1
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG" #define US_FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG"
#define IO_METHOD_UNKNOWN -1 #define US_IO_METHOD_UNKNOWN -1
#define IO_METHODS_STR "MMAP, USERPTR" #define US_IO_METHODS_STR "MMAP, USERPTR"
typedef struct { typedef struct {
frame_s raw; us_frame_s raw;
struct v4l2_buffer buf; struct v4l2_buffer buf;
int dma_fd; int dma_fd;
bool grabbed; bool grabbed;
} hw_buffer_s; } us_hw_buffer_s;
typedef struct { typedef struct {
int fd; int fd;
unsigned width; unsigned width;
unsigned height; unsigned height;
unsigned format; unsigned format;
unsigned stride; unsigned stride;
unsigned hw_fps; unsigned hw_fps;
unsigned jpeg_quality; unsigned jpeg_quality;
size_t raw_size; size_t raw_size;
unsigned n_bufs; unsigned n_bufs;
hw_buffer_s *hw_bufs; us_hw_buffer_s *hw_bufs;
bool capturing; bool capturing;
bool persistent_timeout_reported; bool persistent_timeout_reported;
} device_runtime_s; } us_device_runtime_s;
typedef enum { typedef enum {
CTL_MODE_NONE = 0, CTL_MODE_NONE = 0,
CTL_MODE_VALUE, CTL_MODE_VALUE,
CTL_MODE_AUTO, CTL_MODE_AUTO,
CTL_MODE_DEFAULT, CTL_MODE_DEFAULT,
} control_mode_e; } us_control_mode_e;
typedef struct { typedef struct {
control_mode_e mode; us_control_mode_e mode;
int value; int value;
} control_s; } us_control_s;
typedef struct { typedef struct {
control_s brightness; us_control_s brightness;
control_s contrast; us_control_s contrast;
control_s saturation; us_control_s saturation;
control_s hue; us_control_s hue;
control_s gamma; us_control_s gamma;
control_s sharpness; us_control_s sharpness;
control_s backlight_compensation; us_control_s backlight_compensation;
control_s white_balance; us_control_s white_balance;
control_s gain; us_control_s gain;
control_s color_effect; us_control_s color_effect;
control_s rotate; us_control_s rotate;
control_s flip_vertical; us_control_s flip_vertical;
control_s flip_horizontal; us_control_s flip_horizontal;
} controls_s; } us_controls_s;
typedef struct { typedef struct {
char *path; char *path;
@@ -133,25 +133,25 @@ typedef struct {
bool persistent; bool persistent;
unsigned timeout; unsigned timeout;
controls_s ctl; us_controls_s ctl;
device_runtime_s *run; us_device_runtime_s *run;
} device_s; } us_device_s;
device_s *device_init(void); us_device_s *us_device_init(void);
void device_destroy(device_s *dev); void us_device_destroy(us_device_s *dev);
int device_parse_format(const char *str); int us_device_parse_format(const char *str);
v4l2_std_id device_parse_standard(const char *str); v4l2_std_id us_device_parse_standard(const char *str);
int device_parse_io_method(const char *str); int us_device_parse_io_method(const char *str);
int device_open(device_s *dev); int us_device_open(us_device_s *dev);
void device_close(device_s *dev); void us_device_close(us_device_s *dev);
int device_export_to_dma(device_s *dev); int us_device_export_to_dma(us_device_s *dev);
int device_switch_capturing(device_s *dev, bool enable); int us_device_switch_capturing(us_device_s *dev, bool enable);
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error); int us_device_select(us_device_s *dev, bool *has_read, bool *has_write, bool *has_error);
int device_grab_buffer(device_s *dev, hw_buffer_s **hw); int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw);
int device_release_buffer(device_s *dev, hw_buffer_s *hw); int us_device_release_buffer(us_device_s *dev, us_hw_buffer_s *hw);
int device_consume_event(device_s *dev); int us_device_consume_event(us_device_s *dev);

View File

@@ -25,112 +25,110 @@
static const struct { static const struct {
const char *name; const char *name;
const encoder_type_e type; const us_encoder_type_e type; // cppcheck-suppress unusedStructMember
} _ENCODER_TYPES[] = { } _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU}, {"CPU", US_ENCODER_TYPE_CPU},
{"HW", ENCODER_TYPE_HW}, {"HW", US_ENCODER_TYPE_HW},
{"M2M-VIDEO", ENCODER_TYPE_M2M_VIDEO}, {"M2M-VIDEO", US_ENCODER_TYPE_M2M_VIDEO},
{"M2M-IMAGE", ENCODER_TYPE_M2M_IMAGE}, {"M2M-IMAGE", US_ENCODER_TYPE_M2M_IMAGE},
{"M2M-MJPEG", ENCODER_TYPE_M2M_VIDEO}, {"M2M-MJPEG", US_ENCODER_TYPE_M2M_VIDEO},
{"M2M-JPEG", ENCODER_TYPE_M2M_IMAGE}, {"M2M-JPEG", US_ENCODER_TYPE_M2M_IMAGE},
{"OMX", ENCODER_TYPE_M2M_IMAGE}, {"OMX", US_ENCODER_TYPE_M2M_IMAGE},
{"NOOP", ENCODER_TYPE_NOOP}, {"NOOP", US_ENCODER_TYPE_NOOP},
}; };
static void *_worker_job_init(void *v_enc); static void *_worker_job_init(void *v_enc);
static void _worker_job_destroy(void *v_job); static void _worker_job_destroy(void *v_job);
static bool _worker_run_job(worker_s *wr); static bool _worker_run_job(us_worker_s *wr);
#define ER(_next) enc->run->_next #define _ER(x_next) enc->run->x_next
encoder_s *encoder_init(void) { us_encoder_s *us_encoder_init(void) {
encoder_runtime_s *run; us_encoder_runtime_s *run;
A_CALLOC(run, 1); US_CALLOC(run, 1);
run->type = ENCODER_TYPE_CPU; run->type = US_ENCODER_TYPE_CPU;
run->quality = 80; run->quality = 80;
A_MUTEX_INIT(&run->mutex); US_MUTEX_INIT(run->mutex);
encoder_s *enc; us_encoder_s *enc;
A_CALLOC(enc, 1); US_CALLOC(enc, 1);
enc->type = run->type; enc->type = run->type;
enc->n_workers = get_cores_available(); enc->n_workers = us_get_cores_available();
enc->run = run; enc->run = run;
return enc; return enc;
} }
void encoder_destroy(encoder_s *enc) { void us_encoder_destroy(us_encoder_s *enc) {
if (ER(m2ms)) { if (_ER(m2ms) != NULL) {
for (unsigned index = 0; index < ER(n_m2ms); ++index) { for (unsigned index = 0; index < _ER(n_m2ms); ++index) {
if (ER(m2ms[index])) { US_DELETE(_ER(m2ms[index]), us_m2m_encoder_destroy)
m2m_encoder_destroy(ER(m2ms[index]));
}
} }
free(ER(m2ms)); free(_ER(m2ms));
} }
A_MUTEX_DESTROY(&ER(mutex)); US_MUTEX_DESTROY(_ER(mutex));
free(enc->run); free(enc->run);
free(enc); free(enc);
} }
encoder_type_e encoder_parse_type(const char *str) { us_encoder_type_e us_encoder_parse_type(const char *str) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) { US_ARRAY_ITERATE(_ENCODER_TYPES, 0, item, {
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) { if (!strcasecmp(item->name, str)) {
return _ENCODER_TYPES[index].type; return item->type;
} }
} });
return ENCODER_TYPE_UNKNOWN; return US_ENCODER_TYPE_UNKNOWN;
} }
const char *encoder_type_to_string(encoder_type_e type) { const char *us_encoder_type_to_string(us_encoder_type_e type) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) { US_ARRAY_ITERATE(_ENCODER_TYPES, 0, item, {
if (_ENCODER_TYPES[index].type == type) { if (item->type == type) {
return _ENCODER_TYPES[index].name; return item->name;
} }
} });
return _ENCODER_TYPES[0].name; return _ENCODER_TYPES[0].name;
} }
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) { us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *dev) {
# define DR(_next) dev->run->_next # define DR(x_next) dev->run->x_next
encoder_type_e type = (ER(cpu_forced) ? ENCODER_TYPE_CPU : enc->type); us_encoder_type_e type = (_ER(cpu_forced) ? US_ENCODER_TYPE_CPU : enc->type);
unsigned quality = dev->jpeg_quality; unsigned quality = dev->jpeg_quality;
unsigned n_workers = min_u(enc->n_workers, DR(n_bufs)); unsigned n_workers = us_min_u(enc->n_workers, DR(n_bufs));
bool cpu_forced = false; bool cpu_forced = false;
if (is_jpeg(DR(format)) && type != ENCODER_TYPE_HW) { if (us_is_jpeg(DR(format)) && type != US_ENCODER_TYPE_HW) {
LOG_INFO("Switching to HW encoder: the input is (M)JPEG ..."); US_LOG_INFO("Switching to HW encoder: the input is (M)JPEG ...");
type = ENCODER_TYPE_HW; type = US_ENCODER_TYPE_HW;
} }
if (type == ENCODER_TYPE_HW) { if (type == US_ENCODER_TYPE_HW) {
if (!is_jpeg(DR(format))) { if (!us_is_jpeg(DR(format))) {
LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ..."); US_LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
goto use_cpu; goto use_cpu;
} }
quality = DR(jpeg_quality); quality = DR(jpeg_quality);
n_workers = 1; n_workers = 1;
} else if (type == ENCODER_TYPE_M2M_VIDEO || type == ENCODER_TYPE_M2M_IMAGE) { } else if (type == US_ENCODER_TYPE_M2M_VIDEO || type == US_ENCODER_TYPE_M2M_IMAGE) {
LOG_DEBUG("Preparing M2M-%s encoder ...", (type == ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE")); US_LOG_DEBUG("Preparing M2M-%s encoder ...", (type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
if (ER(m2ms) == NULL) { if (_ER(m2ms) == NULL) {
A_CALLOC(ER(m2ms), n_workers); US_CALLOC(_ER(m2ms), n_workers);
} }
for (; ER(n_m2ms) < n_workers; ++ER(n_m2ms)) { for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости // Начинаем с нуля и доинициализируем на следующих заходах при необходимости
char name[32]; char name[32];
snprintf(name, 32, "JPEG-%u", ER(n_m2ms)); snprintf(name, 32, "JPEG-%u", _ER(n_m2ms));
if (type == ENCODER_TYPE_M2M_VIDEO) { if (type == US_ENCODER_TYPE_M2M_VIDEO) {
ER(m2ms[ER(n_m2ms)]) = m2m_mjpeg_encoder_init(name, enc->m2m_path, quality); _ER(m2ms[_ER(n_m2ms)]) = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
} else { } else {
ER(m2ms[ER(n_m2ms)]) = m2m_jpeg_encoder_init(name, enc->m2m_path, quality); _ER(m2ms[_ER(n_m2ms)]) = us_m2m_jpeg_encoder_init(name, enc->m2m_path, quality);
} }
} }
} else if (type == ENCODER_TYPE_NOOP) { } else if (type == US_ENCODER_TYPE_NOOP) {
n_workers = 1; n_workers = 1;
quality = 0; quality = 0;
} }
@@ -138,32 +136,33 @@ workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
goto ok; goto ok;
use_cpu: use_cpu:
type = ENCODER_TYPE_CPU; type = US_ENCODER_TYPE_CPU;
quality = dev->jpeg_quality; quality = dev->jpeg_quality;
ok: ok:
if (type == ENCODER_TYPE_NOOP) { if (type == US_ENCODER_TYPE_NOOP) {
LOG_INFO("Using JPEG NOOP encoder"); US_LOG_INFO("Using JPEG NOOP encoder");
} else if (quality == 0) { } else if (quality == 0) {
LOG_INFO("Using JPEG quality: encoder default"); US_LOG_INFO("Using JPEG quality: encoder default");
} else { } else {
LOG_INFO("Using JPEG quality: %u%%", quality); US_LOG_INFO("Using JPEG quality: %u%%", quality);
} }
A_MUTEX_LOCK(&ER(mutex)); US_MUTEX_LOCK(_ER(mutex));
ER(type) = type; _ER(type) = type;
ER(quality) = quality; _ER(quality) = quality;
if (cpu_forced) { if (cpu_forced) {
ER(cpu_forced) = true; _ER(cpu_forced) = true;
} }
A_MUTEX_UNLOCK(&ER(mutex)); US_MUTEX_UNLOCK(_ER(mutex));
long double desired_interval = 0; const long double desired_interval = (
if (dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)) { dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)
desired_interval = (long double)1 / dev->desired_fps; ? (long double)1 / dev->desired_fps
} : 0
);
return workers_pool_init( return us_workers_pool_init(
"JPEG", "jw", n_workers, desired_interval, "JPEG", "jw", n_workers, desired_interval,
_worker_job_init, (void *)enc, _worker_job_init, (void *)enc,
_worker_job_destroy, _worker_job_destroy,
@@ -172,63 +171,61 @@ workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
# undef DR # undef DR
} }
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality) { void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, unsigned *quality) {
A_MUTEX_LOCK(&ER(mutex)); US_MUTEX_LOCK(_ER(mutex));
*type = ER(type); *type = _ER(type);
*quality = ER(quality); *quality = _ER(quality);
A_MUTEX_UNLOCK(&ER(mutex)); US_MUTEX_UNLOCK(_ER(mutex));
} }
static void *_worker_job_init(void *v_enc) { static void *_worker_job_init(void *v_enc) {
encoder_job_s *job; us_encoder_job_s *job;
A_CALLOC(job, 1); US_CALLOC(job, 1);
job->enc = (encoder_s *)v_enc; job->enc = (us_encoder_s *)v_enc;
job->dest = frame_init(); job->dest = us_frame_init();
return (void *)job; return (void *)job;
} }
static void _worker_job_destroy(void *v_job) { static void _worker_job_destroy(void *v_job) {
encoder_job_s *job = (encoder_job_s *)v_job; us_encoder_job_s *job = (us_encoder_job_s *)v_job;
frame_destroy(job->dest); us_frame_destroy(job->dest);
free(job); free(job);
} }
#undef ER static bool _worker_run_job(us_worker_s *wr) {
us_encoder_job_s *job = (us_encoder_job_s *)wr->job;
us_encoder_s *enc = job->enc; // Just for _ER()
us_frame_s *src = &job->hw->raw;
us_frame_s *dest = job->dest;
static bool _worker_run_job(worker_s *wr) { assert(_ER(type) != US_ENCODER_TYPE_UNKNOWN);
encoder_job_s *job = (encoder_job_s *)wr->job;
frame_s *src = &job->hw->raw;
frame_s *dest = job->dest;
# define ER(_next) job->enc->run->_next if (_ER(type) == US_ENCODER_TYPE_CPU) {
US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
assert(ER(type) != ENCODER_TYPE_UNKNOWN);
if (ER(type) == ENCODER_TYPE_CPU) {
LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
wr->name, job->hw->buf.index); wr->name, job->hw->buf.index);
cpu_encoder_compress(src, dest, ER(quality)); us_cpu_encoder_compress(src, dest, _ER(quality));
} else if (ER(type) == ENCODER_TYPE_HW) { } else if (_ER(type) == US_ENCODER_TYPE_HW) {
LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u", US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u",
wr->name, job->hw->buf.index); wr->name, job->hw->buf.index);
hw_encoder_compress(src, dest); us_hw_encoder_compress(src, dest);
} else if (ER(type) == ENCODER_TYPE_M2M_VIDEO || ER(type) == ENCODER_TYPE_M2M_IMAGE) { } else if (_ER(type) == US_ENCODER_TYPE_M2M_VIDEO || _ER(type) == US_ENCODER_TYPE_M2M_IMAGE) {
LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u", US_LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
(ER(type) == ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index); (_ER(type) == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index);
if (m2m_encoder_compress(ER(m2ms[wr->number]), src, dest, false) < 0) { if (us_m2m_encoder_compress(_ER(m2ms[wr->number]), src, dest, false) < 0) {
goto error; goto error;
} }
} else if (ER(type) == ENCODER_TYPE_NOOP) { } else if (_ER(type) == US_ENCODER_TYPE_NOOP) {
LOG_VERBOSE("Compressing JPEG using NOOP (do nothing): worker=%s, buffer=%u", US_LOG_VERBOSE("Compressing JPEG using NOOP (do nothing): worker=%s, buffer=%u",
wr->name, job->hw->buf.index); wr->name, job->hw->buf.index);
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
usleep(5000); // Просто чтобы работала логика desired_fps usleep(5000); // Просто чтобы работала логика desired_fps
dest->encode_end_ts = get_now_monotonic(); dest->encode_end_ts = us_get_now_monotonic(); // us_frame_encoding_end()
} }
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u", US_LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
job->dest->used, job->dest->used,
job->dest->encode_end_ts - job->dest->encode_begin_ts, job->dest->encode_end_ts - job->dest->encode_begin_ts,
wr->name, wr->name,
@@ -237,12 +234,10 @@ static bool _worker_run_job(worker_s *wr) {
return true; return true;
error: error:
LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index); US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
LOG_ERROR("Error while compressing buffer, falling back to CPU"); US_LOG_ERROR("Error while compressing buffer, falling back to CPU");
A_MUTEX_LOCK(&ER(mutex)); US_MUTEX_LOCK(_ER(mutex));
ER(cpu_forced) = true; _ER(cpu_forced) = true;
A_MUTEX_UNLOCK(&ER(mutex)); US_MUTEX_UNLOCK(_ER(mutex));
return false; return false;
# undef ER
} }

View File

@@ -31,6 +31,7 @@
#include <linux/videodev2.h> #include <linux/videodev2.h>
#include "../libs/tools.h" #include "../libs/tools.h"
#include "../libs/array.h"
#include "../libs/threading.h" #include "../libs/threading.h"
#include "../libs/logging.h" #include "../libs/logging.h"
#include "../libs/frame.h" #include "../libs/frame.h"
@@ -46,46 +47,46 @@
#define ENCODER_TYPES_STR "CPU, HW, M2M-VIDEO, M2M-IMAGE, NOOP" #define ENCODER_TYPES_STR "CPU, HW, M2M-VIDEO, M2M-IMAGE, NOOP"
typedef enum { typedef enum {
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main() US_ENCODER_TYPE_UNKNOWN, // Only for us_encoder_parse_type() and main()
ENCODER_TYPE_CPU, US_ENCODER_TYPE_CPU,
ENCODER_TYPE_HW, US_ENCODER_TYPE_HW,
ENCODER_TYPE_M2M_VIDEO, US_ENCODER_TYPE_M2M_VIDEO,
ENCODER_TYPE_M2M_IMAGE, US_ENCODER_TYPE_M2M_IMAGE,
ENCODER_TYPE_NOOP, US_ENCODER_TYPE_NOOP,
} encoder_type_e; } us_encoder_type_e;
typedef struct { typedef struct {
encoder_type_e type; us_encoder_type_e type;
unsigned quality; unsigned quality;
bool cpu_forced; bool cpu_forced;
pthread_mutex_t mutex; pthread_mutex_t mutex;
unsigned n_m2ms; unsigned n_m2ms;
m2m_encoder_s **m2ms; us_m2m_encoder_s **m2ms;
} encoder_runtime_s; } us_encoder_runtime_s;
typedef struct { typedef struct {
encoder_type_e type; us_encoder_type_e type;
unsigned n_workers; unsigned n_workers;
char *m2m_path; char *m2m_path;
encoder_runtime_s *run; us_encoder_runtime_s *run;
} encoder_s; } us_encoder_s;
typedef struct { typedef struct {
encoder_s *enc; us_encoder_s *enc;
hw_buffer_s *hw; us_hw_buffer_s *hw;
frame_s *dest; us_frame_s *dest;
} encoder_job_s; } us_encoder_job_s;
encoder_s *encoder_init(void); us_encoder_s *us_encoder_init(void);
void encoder_destroy(encoder_s *enc); void us_encoder_destroy(us_encoder_s *enc);
encoder_type_e encoder_parse_type(const char *str); us_encoder_type_e us_encoder_parse_type(const char *str);
const char *encoder_type_to_string(encoder_type_e type); const char *us_encoder_type_to_string(us_encoder_type_e type);
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev); us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *dev);
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality); void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, unsigned *quality);
int encoder_compress(encoder_s *enc, unsigned worker_number, frame_s *src, frame_s *dest); int us_encoder_compress(us_encoder_s *enc, unsigned worker_number, us_frame_s *src, us_frame_s *dest);

View File

@@ -31,26 +31,26 @@
typedef struct { typedef struct {
struct jpeg_destination_mgr mgr; // Default manager struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buf; // Start of buffer JOCTET *buf; // Start of buffer
frame_s *frame; us_frame_s *frame;
} _jpeg_dest_manager_s; } _jpeg_dest_manager_s;
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame); static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame); static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame); static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame); static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame); static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
static void _jpeg_init_destination(j_compress_ptr jpeg); static void _jpeg_init_destination(j_compress_ptr jpeg);
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg); static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
static void _jpeg_term_destination(j_compress_ptr jpeg); static void _jpeg_term_destination(j_compress_ptr jpeg);
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) { void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned quality) {
// This function based on compress_image_to_jpeg() from mjpg-streamer // This function based on compress_image_to_jpeg() from mjpg-streamer
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG); us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
struct jpeg_compress_struct jpeg; struct jpeg_compress_struct jpeg;
struct jpeg_error_mgr jpeg_error; struct jpeg_error_mgr jpeg_error;
@@ -70,8 +70,8 @@ void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
jpeg_start_compress(&jpeg, TRUE); jpeg_start_compress(&jpeg, TRUE);
# define WRITE_SCANLINES(_format, _func) \ # define WRITE_SCANLINES(x_format, x_func) \
case _format: { _func(&jpeg, src); break; } case x_format: { x_func(&jpeg, src); break; }
switch (src->format) { switch (src->format) {
// https://www.fourcc.org/yuv.php // https://www.fourcc.org/yuv.php
@@ -87,17 +87,17 @@ void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
jpeg_finish_compress(&jpeg); jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg); jpeg_destroy_compress(&jpeg);
frame_encoding_end(dest); us_frame_encoding_end(dest);
} }
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) { static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) {
if (jpeg->dest == NULL) { if (jpeg->dest == NULL) {
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)( assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s) (j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s)
))); )) != NULL);
} }
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest; _jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
dest->mgr.init_destination = _jpeg_init_destination; dest->mgr.init_destination = _jpeg_init_destination;
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer; dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
dest->mgr.term_destination = _jpeg_term_destination; dest->mgr.term_destination = _jpeg_term_destination;
@@ -111,11 +111,11 @@ static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) {
#define YUV_B(_y, _u, _) (((_y) + (454 * (_u))) >> 8) #define YUV_B(_y, _u, _) (((_y) + (454 * (_u))) >> 8)
#define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x))) #define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x)))
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame) { static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf; uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3); US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = frame_get_padding(frame); const unsigned padding = us_frame_get_padding(frame);
const uint8_t *data = frame->data; const uint8_t *data = frame->data;
unsigned z = 0; unsigned z = 0;
@@ -123,13 +123,13 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const
uint8_t *ptr = line_buf; uint8_t *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) { for (unsigned x = 0; x < frame->width; ++x) {
int y = (!z ? data[0] << 8 : data[2] << 8); const int y = (!z ? data[0] << 8 : data[2] << 8);
int u = data[1] - 128; const int u = data[1] - 128;
int v = data[3] - 128; const int v = data[3] - 128;
int r = YUV_R(y, u, v); const int r = YUV_R(y, u, v);
int g = YUV_G(y, u, v); const int g = YUV_G(y, u, v);
int b = YUV_B(y, u, v); const int b = YUV_B(y, u, v);
*(ptr++) = NORM_COMPONENT(r); *(ptr++) = NORM_COMPONENT(r);
*(ptr++) = NORM_COMPONENT(g); *(ptr++) = NORM_COMPONENT(g);
@@ -149,11 +149,11 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const
free(line_buf); free(line_buf);
} }
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame) { static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf; uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3); US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = frame_get_padding(frame); const unsigned padding = us_frame_get_padding(frame);
const uint8_t *data = frame->data; const uint8_t *data = frame->data;
unsigned z = 0; unsigned z = 0;
@@ -161,13 +161,13 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const
uint8_t *ptr = line_buf; uint8_t *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) { for (unsigned x = 0; x < frame->width; ++x) {
int y = (!z ? data[1] << 8 : data[3] << 8); const int y = (!z ? data[1] << 8 : data[3] << 8);
int u = data[0] - 128; const int u = data[0] - 128;
int v = data[2] - 128; const int v = data[2] - 128;
int r = YUV_R(y, u, v); const int r = YUV_R(y, u, v);
int g = YUV_G(y, u, v); const int g = YUV_G(y, u, v);
int b = YUV_B(y, u, v); const int b = YUV_B(y, u, v);
*(ptr++) = NORM_COMPONENT(r); *(ptr++) = NORM_COMPONENT(r);
*(ptr++) = NORM_COMPONENT(g); *(ptr++) = NORM_COMPONENT(g);
@@ -192,18 +192,18 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const
#undef YUV_G #undef YUV_G
#undef YUV_R #undef YUV_R
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame) { static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
uint8_t *line_buf; uint8_t *line_buf;
A_CALLOC(line_buf, frame->width * 3); US_CALLOC(line_buf, frame->width * 3);
const unsigned padding = frame_get_padding(frame); const unsigned padding = us_frame_get_padding(frame);
const uint8_t *data = frame->data; const uint8_t *data = frame->data;
while (jpeg->next_scanline < frame->height) { while (jpeg->next_scanline < frame->height) {
uint8_t *ptr = line_buf; uint8_t *ptr = line_buf;
for (unsigned x = 0; x < frame->width; ++x) { for (unsigned x = 0; x < frame->width; ++x) {
unsigned int two_byte = (data[1] << 8) + data[0]; const unsigned int two_byte = (data[1] << 8) + data[0];
*(ptr++) = data[1] & 248; // Red *(ptr++) = data[1] & 248; // Red
*(ptr++) = (uint8_t)((two_byte & 2016) >> 3); // Green *(ptr++) = (uint8_t)((two_byte & 2016) >> 3); // Green
@@ -220,8 +220,8 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, cons
free(line_buf); free(line_buf);
} }
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame) { static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
const unsigned padding = frame_get_padding(frame); const unsigned padding = us_frame_get_padding(frame);
uint8_t *data = frame->data; uint8_t *data = frame->data;
while (jpeg->next_scanline < frame->height) { while (jpeg->next_scanline < frame->height) {
@@ -235,12 +235,12 @@ static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096) #define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
static void _jpeg_init_destination(j_compress_ptr jpeg) { static void _jpeg_init_destination(j_compress_ptr jpeg) {
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest; _jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
// Allocate the output buffer - it will be released when done with image // Allocate the output buffer - it will be released when done with image
assert((dest->buf = (JOCTET *)(*jpeg->mem->alloc_small)( assert((dest->buf = (JOCTET *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET) (j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
))); )) != NULL);
dest->mgr.next_output_byte = dest->buf; dest->mgr.next_output_byte = dest->buf;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE; dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
@@ -249,9 +249,9 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) { static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
// Called whenever local jpeg buffer fills up // Called whenever local jpeg buffer fills up
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest; _jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE); us_frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
dest->mgr.next_output_byte = dest->buf; dest->mgr.next_output_byte = dest->buf;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE; dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
@@ -263,11 +263,11 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
// Called by jpeg_finish_compress after all data has been written. // Called by jpeg_finish_compress after all data has been written.
// Usually needs to flush buffer. // Usually needs to flush buffer.
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest; _jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer; const size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
// Write any data remaining in the buffer. // Write any data remaining in the buffer.
frame_append_data(dest->frame, dest->buf, final); us_frame_append_data(dest->frame, dest->buf, final);
} }
#undef JPEG_OUTPUT_BUFFER_SIZE #undef JPEG_OUTPUT_BUFFER_SIZE

View File

@@ -35,4 +35,4 @@
#include "../../../libs/frame.h" #include "../../../libs/frame.h"
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality); void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned quality);

View File

@@ -28,21 +28,21 @@
#include "encoder.h" #include "encoder.h"
void _copy_plus_huffman(const frame_s *src, frame_s *dest); void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest);
static bool _is_huffman(const uint8_t *data); static bool _is_huffman(const uint8_t *data);
void hw_encoder_compress(const frame_s *src, frame_s *dest) { void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest) {
assert(is_jpeg(src->format)); assert(us_is_jpeg(src->format));
_copy_plus_huffman(src, dest); _copy_plus_huffman(src, dest);
} }
void _copy_plus_huffman(const frame_s *src, frame_s *dest) { void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG); us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
if (!_is_huffman(src->data)) { if (!_is_huffman(src->data)) {
const uint8_t *src_ptr = src->data; const uint8_t *src_ptr = src->data;
const uint8_t *src_end = src->data + src->used; const uint8_t *const src_end = src->data + src->used;
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) { while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
src_ptr += 1; src_ptr += 1;
@@ -54,15 +54,15 @@ void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
const size_t paste = src_ptr - src->data; const size_t paste = src_ptr - src->data;
frame_set_data(dest, src->data, paste); us_frame_set_data(dest, src->data, paste);
frame_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE)); us_frame_append_data(dest, US_HUFFMAN_TABLE, sizeof(US_HUFFMAN_TABLE));
frame_append_data(dest, src_ptr, src->used - paste); us_frame_append_data(dest, src_ptr, src->used - paste);
} else { } else {
frame_set_data(dest, src->data, src->used); us_frame_set_data(dest, src->data, src->used);
} }
frame_encoding_end(dest); us_frame_encoding_end(dest);
} }
static bool _is_huffman(const uint8_t *data) { static bool _is_huffman(const uint8_t *data) {

View File

@@ -34,4 +34,4 @@
#include "huffman.h" #include "huffman.h"
void hw_encoder_compress(const frame_s *src, frame_s *dest); void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest);

View File

@@ -30,7 +30,7 @@
#include <stdint.h> #include <stdint.h>
static const uint8_t HUFFMAN_TABLE[] = { static const uint8_t US_HUFFMAN_TABLE[] = {
0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03,

View File

@@ -23,13 +23,13 @@
#include "gpio.h" #include "gpio.h"
gpio_s us_gpio = { us_gpio_s us_gpio = {
.path = "/dev/gpiochip0", .path = "/dev/gpiochip0",
.consumer_prefix = "ustreamer", .consumer_prefix = "ustreamer",
# define MAKE_OUTPUT(_role) { \ # define MAKE_OUTPUT(x_role) { \
.pin = -1, \ .pin = -1, \
.role = _role, \ .role = x_role, \
.consumer = NULL, \ .consumer = NULL, \
.line = NULL, \ .line = NULL, \
.state = false \ .state = false \
@@ -46,82 +46,82 @@ gpio_s us_gpio = {
}; };
static void _gpio_output_init(gpio_output_s *output); static void _gpio_output_init(us_gpio_output_s *output);
static void _gpio_output_destroy(gpio_output_s *output); static void _gpio_output_destroy(us_gpio_output_s *output);
void gpio_init(void) { void us_gpio_init(void) {
assert(us_gpio.chip == NULL); assert(us_gpio.chip == NULL);
if ( if (
us_gpio.prog_running.pin >= 0 us_gpio.prog_running.pin >= 0
|| us_gpio.stream_online.pin >= 0 || us_gpio.stream_online.pin >= 0
|| us_gpio.has_http_clients.pin >= 0 || us_gpio.has_http_clients.pin >= 0
) { ) {
A_MUTEX_INIT(&us_gpio.mutex); US_MUTEX_INIT(us_gpio.mutex);
LOG_INFO("GPIO: Using chip device: %s", us_gpio.path); US_LOG_INFO("GPIO: Using chip device: %s", us_gpio.path);
if ((us_gpio.chip = gpiod_chip_open(us_gpio.path)) != NULL) { if ((us_gpio.chip = gpiod_chip_open(us_gpio.path)) != NULL) {
_gpio_output_init(&us_gpio.prog_running); _gpio_output_init(&us_gpio.prog_running);
_gpio_output_init(&us_gpio.stream_online); _gpio_output_init(&us_gpio.stream_online);
_gpio_output_init(&us_gpio.has_http_clients); _gpio_output_init(&us_gpio.has_http_clients);
} else { } else {
LOG_PERROR("GPIO: Can't initialize chip device %s", us_gpio.path); US_LOG_PERROR("GPIO: Can't initialize chip device %s", us_gpio.path);
} }
} }
} }
void gpio_destroy(void) { void us_gpio_destroy(void) {
_gpio_output_destroy(&us_gpio.prog_running); _gpio_output_destroy(&us_gpio.prog_running);
_gpio_output_destroy(&us_gpio.stream_online); _gpio_output_destroy(&us_gpio.stream_online);
_gpio_output_destroy(&us_gpio.has_http_clients); _gpio_output_destroy(&us_gpio.has_http_clients);
if (us_gpio.chip) { if (us_gpio.chip != NULL) {
gpiod_chip_close(us_gpio.chip); gpiod_chip_close(us_gpio.chip);
us_gpio.chip = NULL; us_gpio.chip = NULL;
A_MUTEX_DESTROY(&us_gpio.mutex); US_MUTEX_DESTROY(us_gpio.mutex);
} }
} }
int gpio_inner_set(gpio_output_s *output, bool state) { int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
int retval = 0; int retval = 0;
assert(us_gpio.chip); assert(us_gpio.chip != NULL);
assert(output->line); assert(output->line != NULL);
assert(output->state != state); // Must be checked in macro for the performance assert(output->state != state); // Must be checked in macro for the performance
A_MUTEX_LOCK(&us_gpio.mutex); US_MUTEX_LOCK(us_gpio.mutex);
if (gpiod_line_set_value(output->line, (int)state) < 0) { \ if (gpiod_line_set_value(output->line, (int)state) < 0) { \
LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \ US_LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
_gpio_output_destroy(output); _gpio_output_destroy(output);
retval = -1; retval = -1;
} }
A_MUTEX_UNLOCK(&us_gpio.mutex); US_MUTEX_UNLOCK(us_gpio.mutex);
return retval; return retval;
} }
static void _gpio_output_init(gpio_output_s *output) { static void _gpio_output_init(us_gpio_output_s *output) {
assert(us_gpio.chip); assert(us_gpio.chip != NULL);
assert(output->line == NULL); assert(output->line == NULL);
A_ASPRINTF(output->consumer, "%s::%s", us_gpio.consumer_prefix, output->role); US_ASPRINTF(output->consumer, "%s::%s", us_gpio.consumer_prefix, output->role);
if (output->pin >= 0) { if (output->pin >= 0) {
if ((output->line = gpiod_chip_get_line(us_gpio.chip, output->pin)) != NULL) { if ((output->line = gpiod_chip_get_line(us_gpio.chip, output->pin)) != NULL) {
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) { if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer); US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
_gpio_output_destroy(output); _gpio_output_destroy(output);
} }
} else { } else {
LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer); US_LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
} }
} }
} }
static void _gpio_output_destroy(gpio_output_s *output) { static void _gpio_output_destroy(us_gpio_output_s *output) {
if (output->line) { if (output->line != NULL) {
gpiod_line_release(output->line); gpiod_line_release(output->line);
output->line = NULL; output->line = NULL;
} }
if (output->consumer) { if (output->consumer != NULL) {
free(output->consumer); free(output->consumer);
output->consumer = NULL; output->consumer = NULL;
} }

Some files were not shown because too many files have changed in this diff Show More