mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f86f818cc | ||
|
|
1ffcd83993 | ||
|
|
9b46c2e597 | ||
|
|
ad1b63890a | ||
|
|
ad79bd0957 | ||
|
|
021823bcba | ||
|
|
7b0e171e74 | ||
|
|
b24f106ce7 | ||
|
|
20f056668f | ||
|
|
f9439c785f | ||
|
|
5e364fb88b | ||
|
|
69dc9b8b49 | ||
|
|
42237d9728 | ||
|
|
17bd25d497 | ||
|
|
a2ac1f8067 | ||
|
|
fdb1b2d562 | ||
|
|
fd2bf5ea25 | ||
|
|
c874929e9d | ||
|
|
db5b9d3cd7 | ||
|
|
12ab66be43 | ||
|
|
27d25a59d8 | ||
|
|
71991254a5 | ||
|
|
50e8469a59 | ||
|
|
3f45debca0 | ||
|
|
627b614ab5 | ||
|
|
f11d390b22 | ||
|
|
f1e50b6f9b | ||
|
|
fdf3340a7d | ||
|
|
02513be220 | ||
|
|
d29ce42f08 | ||
|
|
aa6fc7fe04 | ||
|
|
c91341a375 | ||
|
|
3de7e26a36 | ||
|
|
63cc66e8a7 | ||
|
|
92a090dec3 | ||
|
|
8b0ef8a271 | ||
|
|
a360f1901e | ||
|
|
ed2d5f3af4 | ||
|
|
b935dd1fe8 | ||
|
|
6e1f60a36d | ||
|
|
210dfcfa4f | ||
|
|
ec10a9e3fe | ||
|
|
217d146378 | ||
|
|
3e2a43e2af | ||
|
|
2e0a19c1cb | ||
|
|
054748234e | ||
|
|
53873e9ddb | ||
|
|
c21d0aef7e | ||
|
|
e505a56910 | ||
|
|
f4278f32c4 | ||
|
|
e9a6db02f6 | ||
|
|
63fe32ddd9 | ||
|
|
710652073a | ||
|
|
dded49cd83 | ||
|
|
0f753dc654 | ||
|
|
c505a423af | ||
|
|
1cff2545b1 | ||
|
|
3d994d6e67 |
@@ -1,18 +1,18 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 5.5
|
||||
current_version = 5.15
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
[bumpversion:file:src/libs/config.h]
|
||||
[bumpversion:file:src/libs/const.h]
|
||||
parse = (?P<major>\d+)
|
||||
serialize = {major}
|
||||
search = VERSION_MAJOR {current_version}
|
||||
replace = VERSION_MAJOR {new_version}
|
||||
|
||||
[bumpversion:file:./src/libs/config.h]
|
||||
[bumpversion:file:./src/libs/const.h]
|
||||
parse = <major>\d+\.(?P<minor>\d+)
|
||||
serialize = {minor}
|
||||
search = VERSION_MINOR {current_version}
|
||||
|
||||
@@ -9,10 +9,10 @@ LDFLAGS ?=
|
||||
# =====
|
||||
_PLUGIN = libjanus_ustreamer.so
|
||||
|
||||
_CFLAGS = -fPIC -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(shell pkg-config --cflags glib-2.0) $(CFLAGS)
|
||||
_LDFLAGS = -shared -lm -pthread -lrt -ljansson -lopus -lasound $(shell pkg-config --libs glib-2.0) $(LDFLAGS)
|
||||
_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 -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
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
# #
|
||||
# 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 #
|
||||
@@ -27,40 +24,48 @@
|
||||
|
||||
|
||||
#define JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
|
||||
#define JLOG_PERROR_RES(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
|
||||
#define JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) 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))
|
||||
|
||||
// https://github.com/xiph/opus/blob/7b05f44/src/opus_demo.c#L368
|
||||
#define PCM_BITRATE 48000
|
||||
#define PCM_FRAMES (6 * PCM_BITRATE / 50) // 120ms
|
||||
#define PCM_DATA_SIZE (PCM_FRAMES * 2)
|
||||
#define RAW_DATA_SIZE (PCM_DATA_SIZE * sizeof(opus_int16))
|
||||
#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 {
|
||||
opus_int16 data[PCM_DATA_SIZE];
|
||||
} _pcm_buf_s;
|
||||
int16_t data[MAX_BUF16];
|
||||
} _pcm_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[RAW_DATA_SIZE]; // Worst
|
||||
uint8_t data[MAX_BUF8]; // Worst case
|
||||
size_t used;
|
||||
uint64_t pts;
|
||||
} _enc_buf_s;
|
||||
} _enc_buffer_s;
|
||||
|
||||
|
||||
static void *_pcm_thread(void *v_audio);
|
||||
static void *_encoder_thread(void *v_audio);
|
||||
|
||||
|
||||
audio_s *audio_init(const char *name) {
|
||||
audio_s *audio_init(const char *name, unsigned pcm_hz) {
|
||||
audio_s *audio;
|
||||
A_CALLOC(audio, 1);
|
||||
audio->pcm_hz = pcm_hz;
|
||||
audio->pcm_queue = queue_init(8);
|
||||
audio->enc_queue = queue_init(8);
|
||||
atomic_init(&audio->run, true);
|
||||
atomic_init(&audio->stop, false);
|
||||
|
||||
int err;
|
||||
|
||||
{
|
||||
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");
|
||||
@@ -79,46 +84,39 @@ audio_s *audio_init(const char *name) {
|
||||
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);
|
||||
unsigned pcm_bitrate = PCM_BITRATE;
|
||||
SET_PARAM("Can't set PCM sampling rate", snd_pcm_hw_params_set_rate_near, &pcm_bitrate, 0);
|
||||
if (pcm_bitrate != PCM_BITRATE) {
|
||||
JLOG_ERROR("audio", "PCM bitrate mismatch: %u, should be %u", pcm_bitrate, PCM_BITRATE);
|
||||
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) {
|
||||
JLOG_ERROR("audio", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
|
||||
audio->pcm_hz, MIN_PCM_HZ, MAX_PCM_HZ);
|
||||
goto error;
|
||||
}
|
||||
SET_PARAM("Can't apply PCM params", snd_pcm_hw_params);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int err;
|
||||
// OPUS_APPLICATION_VOIP
|
||||
// OPUS_APPLICATION_RESTRICTED_LOWDELAY
|
||||
audio->enc = opus_encoder_create(PCM_BITRATE, 2, OPUS_APPLICATION_AUDIO, &err);
|
||||
if (err < 0) {
|
||||
audio->enc = NULL;
|
||||
JLOG_PERROR_OPUS(err, "audio", "Can't create OPUS encoder");
|
||||
goto error;
|
||||
}
|
||||
|
||||
# define SET_PARAM(_msg, _ctl) { \
|
||||
if ((err = opus_encoder_ctl(audio->enc, _ctl)) < 0) { \
|
||||
JLOG_PERROR_OPUS(err, "audio", _msg); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
SET_PARAM("Can't set OPUS bitrate", OPUS_SET_BITRATE(48000));
|
||||
SET_PARAM("Can't set OPUS max bandwidth", OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND));
|
||||
SET_PARAM("Can't set OPUS signal type", OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC));
|
||||
// Also see rtpa.c
|
||||
// SET_PARAM("Can't set OPUS FEC", OPUS_SET_INBAND_FEC(1));
|
||||
// SET_PARAM("Can't set OPUS exploss", OPUS_SET_PACKET_LOSS_PERC(10));
|
||||
|
||||
# undef SET_PARAM
|
||||
// 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
|
||||
}
|
||||
|
||||
JLOG_INFO("audio", "PCM & OPUS prepared; capturing ...");
|
||||
JLOG_INFO("audio", "Pipeline configured on %uHz; capturing ...", audio->pcm_hz);
|
||||
audio->tids_created = true;
|
||||
A_THREAD_CREATE(&audio->enc_tid, _encoder_thread, audio);
|
||||
A_THREAD_CREATE(&audio->pcm_tid, _pcm_thread, audio);
|
||||
@@ -132,50 +130,44 @@ audio_s *audio_init(const char *name) {
|
||||
|
||||
void audio_destroy(audio_s *audio) {
|
||||
if (audio->tids_created) {
|
||||
atomic_store(&audio->run, false);
|
||||
atomic_store(&audio->stop, true);
|
||||
A_THREAD_JOIN(audio->pcm_tid);
|
||||
A_THREAD_JOIN(audio->enc_tid);
|
||||
}
|
||||
if (audio->enc) {
|
||||
opus_encoder_destroy(audio->enc);
|
||||
}
|
||||
if (audio->res) {
|
||||
speex_resampler_destroy(audio->res);
|
||||
}
|
||||
if (audio->pcm) {
|
||||
snd_pcm_close(audio->pcm);
|
||||
}
|
||||
if (audio->pcm_params) {
|
||||
snd_pcm_hw_params_free(audio->pcm_params);
|
||||
}
|
||||
# define FREE_QUEUE(_suffix) { \
|
||||
while (!queue_get_free(audio->_suffix##_queue)) { \
|
||||
_##_suffix##_buf_s *ptr; \
|
||||
assert(!queue_get(audio->_suffix##_queue, (void **)&ptr, 1)); \
|
||||
free(ptr); \
|
||||
} \
|
||||
queue_destroy(audio->_suffix##_queue); \
|
||||
}
|
||||
FREE_QUEUE(enc);
|
||||
FREE_QUEUE(pcm);
|
||||
# undef FREE_QUEUE
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(audio->enc_queue, free);
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(audio->pcm_queue, free);
|
||||
if (audio->tids_created) {
|
||||
JLOG_INFO("audio", "Pipeline closed");
|
||||
}
|
||||
free(audio);
|
||||
}
|
||||
|
||||
int audio_copy_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
|
||||
if (!atomic_load(&audio->run)) {
|
||||
int audio_get_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
|
||||
if (atomic_load(&audio->stop)) {
|
||||
return -1;
|
||||
}
|
||||
_enc_buf_s *in;
|
||||
if (!queue_get(audio->enc_queue, (void **)&in, 1)) {
|
||||
if (*size < in->used) {
|
||||
free(in);
|
||||
_enc_buffer_s *buf;
|
||||
if (!queue_get(audio->enc_queue, (void **)&buf, 0.1)) {
|
||||
if (*size < buf->used) {
|
||||
free(buf);
|
||||
return -3;
|
||||
}
|
||||
memcpy(data, in->data, in->used);
|
||||
*size = in->used;
|
||||
*pts = in->pts;
|
||||
free(in);
|
||||
memcpy(data, buf->data, buf->used);
|
||||
*size = buf->used;
|
||||
*pts = buf->pts;
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
return -2;
|
||||
@@ -185,32 +177,29 @@ static void *_pcm_thread(void *v_audio) {
|
||||
A_THREAD_RENAME("us_a_pcm");
|
||||
|
||||
audio_s *audio = (audio_s *)v_audio;
|
||||
uint8_t in[MAX_BUF8];
|
||||
|
||||
while (atomic_load(&audio->run)) {
|
||||
uint8_t in[RAW_DATA_SIZE];
|
||||
int frames = snd_pcm_readi(audio->pcm, in, PCM_FRAMES);
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames);
|
||||
if (frames < 0) {
|
||||
JLOG_PERROR_ALSA(frames, "audio", "Can't capture PCM frames; breaking audio ...");
|
||||
JLOG_PERROR_ALSA(frames, "audio", "Fatal: Can't capture PCM frames");
|
||||
break;
|
||||
} else if (frames < PCM_FRAMES) {
|
||||
JLOG_ERROR("audio", "Too few PCM frames captured; breaking audio ...");
|
||||
} else if (frames < (int)audio->pcm_frames) {
|
||||
JLOG_ERROR("audio", "Fatal: Too few PCM frames captured");
|
||||
break;
|
||||
}
|
||||
|
||||
if (queue_get_free(audio->pcm_queue)) {
|
||||
_pcm_buf_s *out;
|
||||
_pcm_buffer_s *out;
|
||||
A_CALLOC(out, 1);
|
||||
/*for (unsigned index = 0; index < RAW_DATA_SIZE; ++index) {
|
||||
out->data[index] = (opus_int16)in[index * 2 + 1] << 8 | in[index * 2];
|
||||
}*/
|
||||
memcpy(out->data, in, RAW_DATA_SIZE);
|
||||
assert(!queue_put(audio->pcm_queue, out, 1));
|
||||
memcpy(out->data, in, audio->pcm_size);
|
||||
assert(!queue_put(audio->pcm_queue, out, 0));
|
||||
} else {
|
||||
JLOG_ERROR("audio", "PCM queue is full");
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store(&audio->run, false);
|
||||
atomic_store(&audio->stop, true);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -218,32 +207,44 @@ static void *_encoder_thread(void *v_audio) {
|
||||
A_THREAD_RENAME("us_a_enc");
|
||||
|
||||
audio_s *audio = (audio_s *)v_audio;
|
||||
int16_t in_res[MAX_BUF16];
|
||||
|
||||
while (atomic_load(&audio->run)) {
|
||||
_pcm_buf_s *in;
|
||||
if (!queue_get(audio->pcm_queue, (void **)&in, 1)) {
|
||||
_enc_buf_s *out;
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
_pcm_buffer_s *in;
|
||||
if (!queue_get(audio->pcm_queue, (void **)&in, 0.1)) {
|
||||
int16_t *in_ptr;
|
||||
if (audio->res) {
|
||||
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;
|
||||
A_CALLOC(out, 1);
|
||||
int size = opus_encode(audio->enc, in->data, PCM_FRAMES, out->data, RAW_DATA_SIZE);
|
||||
int size = opus_encode(audio->enc, in_ptr, HZ_TO_FRAMES(ENCODER_INPUT_HZ), out->data, ARRAY_LEN(out->data));
|
||||
free(in);
|
||||
if (size < 0) {
|
||||
JLOG_PERROR_OPUS(size, "audio", "Can't encode PCM frame to OPUS; breaking audio ...");
|
||||
JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
|
||||
free(out);
|
||||
break;
|
||||
}
|
||||
out->used = size;
|
||||
out->pts = audio->pts;
|
||||
audio->pts += PCM_FRAMES;
|
||||
// https://datatracker.ietf.org/doc/html/rfc7587#section-4.2
|
||||
audio->pts += HZ_TO_FRAMES(ENCODER_INPUT_HZ);
|
||||
|
||||
if (queue_get_free(audio->enc_queue)) {
|
||||
assert(!queue_put(audio->enc_queue, out, 1));
|
||||
} else {
|
||||
if (queue_put(audio->enc_queue, out, 0) != 0) {
|
||||
JLOG_ERROR("audio", "OPUS encoder queue is full");
|
||||
free(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store(&audio->run, false);
|
||||
atomic_store(&audio->stop, true);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
# #
|
||||
# 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 #
|
||||
@@ -35,17 +32,23 @@
|
||||
|
||||
#include <pthread.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <speex/speex_resampler.h>
|
||||
#include <opus/opus.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "jlogging.h"
|
||||
#include "threading.h"
|
||||
#include "uslibs/tools.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;
|
||||
|
||||
queue_s *pcm_queue;
|
||||
@@ -55,11 +58,11 @@ typedef struct {
|
||||
pthread_t pcm_tid;
|
||||
pthread_t enc_tid;
|
||||
bool tids_created;
|
||||
atomic_bool run;
|
||||
atomic_bool stop;
|
||||
} audio_s;
|
||||
|
||||
|
||||
audio_s *audio_init(const char *name);
|
||||
audio_s *audio_init(const char *name, unsigned pcm_hz);
|
||||
void audio_destroy(audio_s *audio);
|
||||
|
||||
int audio_copy_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
|
||||
int audio_get_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/base64.c
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/base64.h
|
||||
120
janus/src/client.c
Normal file
120
janus/src/client.c
Normal file
@@ -0,0 +1,120 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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);
|
||||
|
||||
|
||||
client_s *client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio) {
|
||||
client_s *client;
|
||||
A_CALLOC(client, 1);
|
||||
client->gw = gw;
|
||||
client->session = session;
|
||||
atomic_init(&client->transmit, true);
|
||||
|
||||
atomic_init(&client->stop, false);
|
||||
|
||||
client->video_queue = queue_init(1024);
|
||||
A_THREAD_CREATE(&client->video_tid, _video_thread, client);
|
||||
|
||||
if (has_audio) {
|
||||
client->audio_queue = queue_init(64);
|
||||
A_THREAD_CREATE(&client->audio_tid, _audio_thread, client);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
void client_destroy(client_s *client) {
|
||||
atomic_store(&client->stop, true);
|
||||
queue_put(client->video_queue, NULL, 0);
|
||||
if (client->audio_queue != NULL) {
|
||||
queue_put(client->audio_queue, NULL, 0);
|
||||
}
|
||||
|
||||
A_THREAD_JOIN(client->video_tid);
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(client->video_queue, rtp_destroy);
|
||||
if (client->audio_queue != NULL) {
|
||||
A_THREAD_JOIN(client->audio_tid);
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(client->audio_queue, rtp_destroy);
|
||||
}
|
||||
free(client);
|
||||
}
|
||||
|
||||
void client_send(client_s *client, const rtp_s *rtp) {
|
||||
if (
|
||||
!atomic_load(&client->transmit)
|
||||
|| (!rtp->video && client->audio_queue == NULL)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
rtp_s *new = rtp_dup(rtp);
|
||||
if (queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
|
||||
JLOG_ERROR("client", "Session %p %s queue is full",
|
||||
client->session, (new->video ? "video" : "audio"));
|
||||
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) {
|
||||
client_s *client = (client_s *)v_client;
|
||||
queue_s *queue = (video ? client->video_queue : client->audio_queue);
|
||||
assert(queue != NULL); // Audio may be NULL
|
||||
|
||||
while (!atomic_load(&client->stop)) {
|
||||
rtp_s *rtp;
|
||||
if (!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: See rtpv.c
|
||||
// Это очень эффективный способ уменьшить задержку, но WebRTC стек в хроме и фоксе
|
||||
// слишком корявый, чтобы обработать это, тз-за чего на кейфреймах начинаются заикания.
|
||||
// - https://github.com/Glimesh/janus-ftl-plugin/issues/101
|
||||
/*if (video) {
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 0;
|
||||
}*/
|
||||
client->gw->relay_rtp(client->session, &packet);
|
||||
}
|
||||
rtp_destroy(rtp);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
61
janus/src/client.h
Normal file
61
janus/src/client.h
Normal 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 client_sx {
|
||||
janus_callbacks *gw;
|
||||
janus_plugin_session *session;
|
||||
atomic_bool transmit;
|
||||
|
||||
pthread_t video_tid;
|
||||
pthread_t audio_tid;
|
||||
atomic_bool stop;
|
||||
|
||||
queue_s *video_queue;
|
||||
queue_s *audio_queue;
|
||||
|
||||
LIST_STRUCT(struct client_sx);
|
||||
} client_s;
|
||||
|
||||
|
||||
client_s *client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio);
|
||||
void client_destroy(client_s *client);
|
||||
|
||||
void client_send(client_s *client, const rtp_s *rtp);
|
||||
78
janus/src/config.c
Normal file
78
janus/src/config.c
Normal file
@@ -0,0 +1,78 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 *config, const char *section, const char *option);
|
||||
|
||||
|
||||
int read_config(const char *config_dir_path, char **video_sink_name, char **audio_dev_name, char **tc358743_dev_path) {
|
||||
int retval = 0;
|
||||
|
||||
char *config_file_path;
|
||||
janus_config *config = NULL;
|
||||
|
||||
A_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, PLUGIN_PACKAGE);
|
||||
JLOG_INFO("config", "Reading config file '%s' ...", config_file_path);
|
||||
|
||||
config = janus_config_parse(config_file_path);
|
||||
if (config == NULL) {
|
||||
JLOG_ERROR("config", "Can't read config");
|
||||
goto error;
|
||||
}
|
||||
janus_config_print(config);
|
||||
|
||||
if (
|
||||
(*video_sink_name = _get_value(config, "memsink", "object")) == NULL
|
||||
&& (*video_sink_name = _get_value(config, "video", "sink")) == NULL
|
||||
) {
|
||||
JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
|
||||
goto error;
|
||||
}
|
||||
if ((*audio_dev_name = _get_value(config, "audio", "device")) != NULL) {
|
||||
JLOG_INFO("config", "Enabled the experimental AUDIO feature");
|
||||
if ((*tc358743_dev_path = _get_value(config, "audio", "tc358743")) == NULL) {
|
||||
JLOG_INFO("config", "Missing config value: audio.tc358743");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
goto ok;
|
||||
error:
|
||||
retval = -1;
|
||||
ok:
|
||||
if (config) {
|
||||
janus_config_destroy(config);
|
||||
}
|
||||
free(config_file_path);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static char *_get_value(janus_config *config, const char *section, const char *option) {
|
||||
janus_config_category *section_obj = janus_config_get_create(config, NULL, janus_config_type_category, section);
|
||||
janus_config_item *option_obj = janus_config_get(config, section_obj, janus_config_type_item, option);
|
||||
if (option_obj == NULL || option_obj->value == NULL || option_obj->value[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
return strdup(option_obj->value);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/config.h
|
||||
37
janus/src/config.h
Normal file
37
janus/src/config.h
Normal 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <janus/config.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
#include "const.h"
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
int read_config(const char *config_dir_path, char **video_sink_name, char **audio_dev_name, char **tc358743_dev_path);
|
||||
26
janus/src/const.h
Normal file
26
janus/src/const.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 PLUGIN_NAME "ustreamer"
|
||||
#define PLUGIN_PACKAGE "janus.plugin.ustreamer"
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/frame.c
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/frame.h
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/list.h
|
||||
@@ -22,15 +22,17 @@
|
||||
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
#include "const.h"
|
||||
|
||||
|
||||
#define JLOG_INFO(_prefix, _msg, ...) JANUS_LOG(LOG_INFO, "== ustreamer/%-9s -- " _msg "\n", _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_WARN(_prefix, _msg, ...) JANUS_LOG(LOG_WARN, "== ustreamer/%-9s -- " _msg "\n", _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_ERROR(_prefix, _msg, ...) JANUS_LOG(LOG_ERR, "== ustreamer/%-9s -- " _msg "\n", _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_INFO(_prefix, _msg, ...) JANUS_LOG(LOG_INFO, "== %s/%-9s -- " _msg "\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_WARN(_prefix, _msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " _msg "\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_ERROR(_prefix, _msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " _msg "\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__)
|
||||
|
||||
#define JLOG_PERROR(_prefix, _msg, ...) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
|
||||
JANUS_LOG(LOG_ERR, "[ustreamer/%-9s] " _msg ": %s\n", _prefix, ##__VA_ARGS__, _perror_ptr); \
|
||||
JANUS_LOG(LOG_ERR, "[%s/%-9s] " _msg ": %s\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__, _perror_ptr); \
|
||||
}
|
||||
70
janus/src/memsinkfd.c
Normal file
70
janus/src/memsinkfd.c
Normal 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 memsink_fd_wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id) {
|
||||
long double deadline_ts = get_now_monotonic() + 1; // wait_timeout
|
||||
long double now;
|
||||
do {
|
||||
int result = flock_timedwait_monotonic(fd, 1); // lock_timeout
|
||||
now = get_now_monotonic();
|
||||
if (result < 0 && errno != EWOULDBLOCK) {
|
||||
JLOG_PERROR("video", "Can't lock memsink");
|
||||
return -1;
|
||||
} else if (result == 0) {
|
||||
if (mem->magic == MEMSINK_MAGIC && mem->version == MEMSINK_VERSION && mem->id != last_id) {
|
||||
return 0;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
JLOG_PERROR("video", "Can't unlock memsink");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
usleep(1000); // lock_polling
|
||||
} while (now < deadline_ts);
|
||||
return -2;
|
||||
}
|
||||
|
||||
frame_s *memsink_fd_get_frame(int fd, memsink_shared_s *mem, uint64_t *frame_id) {
|
||||
frame_s *frame = frame_init();
|
||||
frame_set_data(frame, mem->data, mem->used);
|
||||
FRAME_COPY_META(mem, frame);
|
||||
*frame_id = mem->id;
|
||||
mem->last_client_ts = get_now_monotonic();
|
||||
|
||||
bool ok = true;
|
||||
if (frame->format != V4L2_PIX_FMT_H264) {
|
||||
JLOG_ERROR("video", "Got non-H264 frame from memsink");
|
||||
ok = false;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
JLOG_PERROR("video", "Can't unlock memsink");
|
||||
ok = false;
|
||||
}
|
||||
if (!ok) {
|
||||
frame_destroy(frame);
|
||||
frame = NULL;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
38
janus/src/memsinkfd.h
Normal file
38
janus/src/memsinkfd.h
Normal 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 memsink_fd_wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id);
|
||||
frame_s *memsink_fd_get_frame(int fd, memsink_shared_s *mem, uint64_t *frame_id);
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/memsinksh.h
|
||||
@@ -31,190 +31,104 @@
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <jansson.h>
|
||||
#include <janus/config.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "tools.h"
|
||||
#include "jlogging.h"
|
||||
#include "threading.h"
|
||||
#include "list.h"
|
||||
#include "memsinksh.h"
|
||||
#include "uslibs/const.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/list.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 "rtpv.h"
|
||||
#include "rtpa.h"
|
||||
#include "memsinkfd.h"
|
||||
#include "config.h"
|
||||
|
||||
|
||||
static int _plugin_init(janus_callbacks *gw, const char *config_file_path);
|
||||
static void _plugin_destroy(void);
|
||||
static char *_g_video_sink_name = NULL;
|
||||
static char *_g_audio_dev_name = NULL;
|
||||
static char *_g_tc358743_dev_path = NULL;
|
||||
|
||||
static void _plugin_create_session(janus_plugin_session *session, int *err);
|
||||
static void _plugin_destroy_session(janus_plugin_session *session, int *err);
|
||||
static json_t *_plugin_query_session(janus_plugin_session *session);
|
||||
|
||||
static void _plugin_setup_media(janus_plugin_session *session);
|
||||
static void _plugin_hangup_media(janus_plugin_session *session);
|
||||
|
||||
static struct janus_plugin_result *_plugin_handle_message(
|
||||
janus_plugin_session *session, char *transaction, json_t *msg, json_t *jsep);
|
||||
|
||||
|
||||
static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; }
|
||||
static int _plugin_get_version(void) { return VERSION_U; }
|
||||
static const char *_plugin_get_version_string(void) { return VERSION; }
|
||||
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_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
|
||||
static const char *_plugin_get_package(void) { return "janus.plugin.ustreamer"; }
|
||||
|
||||
// Just a stub to avoid logging spam about the plugin's purpose.
|
||||
static void _plugin_incoming_rtp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtp *packet) {}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Woverride-init"
|
||||
static janus_plugin _plugin = JANUS_PLUGIN_INIT(
|
||||
.init = _plugin_init,
|
||||
.destroy = _plugin_destroy,
|
||||
|
||||
.create_session = _plugin_create_session,
|
||||
.destroy_session = _plugin_destroy_session,
|
||||
.query_session = _plugin_query_session,
|
||||
|
||||
.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
|
||||
|
||||
janus_plugin *create(void) { // cppcheck-suppress unusedFunction
|
||||
return &_plugin;
|
||||
}
|
||||
|
||||
|
||||
typedef struct _client_sx {
|
||||
janus_plugin_session *session;
|
||||
bool transmit;
|
||||
|
||||
LIST_STRUCT(struct _client_sx);
|
||||
} _client_s;
|
||||
|
||||
|
||||
static char *_g_memsink_obj = NULL;
|
||||
const long double _g_wait_timeout = 1;
|
||||
const long double _g_lock_timeout = 1;
|
||||
const useconds_t _g_lock_polling = 1000;
|
||||
const useconds_t _g_watchers_polling = 100000;
|
||||
|
||||
static char *_g_audio_dev = NULL;
|
||||
|
||||
static _client_s *_g_clients = NULL;
|
||||
static client_s *_g_clients = NULL;
|
||||
static janus_callbacks *_g_gw = NULL;
|
||||
static queue_s *_g_video_queue = NULL;
|
||||
static rtpv_s *_g_rtpv = NULL;
|
||||
static rtpa_s *_g_rtpa = NULL;
|
||||
|
||||
static pthread_t _g_video_tid;
|
||||
static atomic_bool _g_video_tid_created = ATOMIC_VAR_INIT(false);
|
||||
static pthread_t _g_video_rtp_tid;
|
||||
static atomic_bool _g_video_rtp_tid_created = false;
|
||||
static pthread_t _g_video_sink_tid;
|
||||
static atomic_bool _g_video_sink_tid_created = false;
|
||||
static pthread_t _g_audio_tid;
|
||||
static atomic_bool _g_audio_tid_created = ATOMIC_VAR_INIT(false);
|
||||
static atomic_bool _g_audio_tid_created = false;
|
||||
|
||||
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);
|
||||
static pthread_mutex_t _g_video_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t _g_audio_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static atomic_bool _g_ready = false;
|
||||
static atomic_bool _g_stop = false;
|
||||
static atomic_bool _g_has_watchers = false;
|
||||
|
||||
|
||||
#define LOCK A_MUTEX_LOCK(&_g_lock)
|
||||
#define UNLOCK A_MUTEX_UNLOCK(&_g_lock)
|
||||
#define LOCK_VIDEO A_MUTEX_LOCK(&_g_video_lock)
|
||||
#define UNLOCK_VIDEO A_MUTEX_UNLOCK(&_g_video_lock)
|
||||
|
||||
#define LOCK_AUDIO A_MUTEX_LOCK(&_g_audio_lock)
|
||||
#define UNLOCK_AUDIO A_MUTEX_UNLOCK(&_g_audio_lock)
|
||||
|
||||
#define LOCK_ALL { LOCK_VIDEO; LOCK_AUDIO; }
|
||||
#define UNLOCK_ALL { UNLOCK_AUDIO; UNLOCK_VIDEO; }
|
||||
|
||||
#define READY atomic_load(&_g_ready)
|
||||
#define STOP atomic_load(&_g_stop)
|
||||
#define HAS_WATCHERS atomic_load(&_g_has_watchers)
|
||||
|
||||
|
||||
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("video", "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("video", "Can't unlock memsink");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
usleep(_g_lock_polling);
|
||||
} while (now < deadline_ts);
|
||||
return -2;
|
||||
}
|
||||
janus_plugin *create(void);
|
||||
|
||||
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("video", "Got non-H264 frame from memsink");
|
||||
retval = -1;
|
||||
|
||||
#define IF_NOT_REPORTED(...) { \
|
||||
unsigned _error_code = __LINE__; \
|
||||
if (error_reported != _error_code) { __VA_ARGS__; error_reported = _error_code; } \
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
JLOG_PERROR("video", "Can't unlock memsink");
|
||||
retval = -1;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _relay_rtp_clients(const rtp_s *rtp) {
|
||||
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);
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
if (client->transmit) {
|
||||
_g_gw->relay_rtp(client->session, &packet);
|
||||
static void *_video_rtp_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_video_rtp");
|
||||
atomic_store(&_g_video_rtp_tid_created, true);
|
||||
|
||||
while (!STOP) {
|
||||
frame_s *frame;
|
||||
if (queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
|
||||
LOCK_VIDEO;
|
||||
rtpv_wrap(_g_rtpv, frame);
|
||||
UNLOCK_VIDEO;
|
||||
frame_destroy(frame);
|
||||
}
|
||||
});
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_clients_video_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_v_clients");
|
||||
atomic_store(&_g_video_tid_created, true);
|
||||
atomic_store(&_g_ready, true);
|
||||
static void *_video_sink_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_video_sink");
|
||||
atomic_store(&_g_video_sink_tid_created, true);
|
||||
|
||||
frame_s *frame = frame_init();
|
||||
uint64_t frame_id = 0;
|
||||
|
||||
unsigned error_reported = 0;
|
||||
|
||||
# define IF_NOT_REPORTED(_error, ...) { \
|
||||
if (error_reported != _error) { __VA_ARGS__; error_reported = _error; } \
|
||||
}
|
||||
|
||||
while (!STOP) {
|
||||
if (!HAS_WATCHERS) {
|
||||
IF_NOT_REPORTED(1, { JLOG_INFO("video", "No active watchers, memsink disconnected"); });
|
||||
IF_NOT_REPORTED({ JLOG_INFO("video", "No active watchers, memsink disconnected"); });
|
||||
usleep(_g_watchers_polling);
|
||||
continue;
|
||||
}
|
||||
@@ -222,13 +136,13 @@ static void *_clients_video_thread(UNUSED void *arg) {
|
||||
int fd = -1;
|
||||
memsink_shared_s *mem = NULL;
|
||||
|
||||
if ((fd = shm_open(_g_memsink_obj, O_RDWR, 0)) <= 0) {
|
||||
IF_NOT_REPORTED(2, { JLOG_PERROR("video", "Can't open memsink"); });
|
||||
if ((fd = shm_open(_g_video_sink_name, O_RDWR, 0)) <= 0) {
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Can't open memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
if ((mem = memsink_shared_map(fd)) == NULL) {
|
||||
IF_NOT_REPORTED(3, { JLOG_PERROR("video", "Can't map memsink"); });
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Can't map memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
@@ -236,14 +150,16 @@ static void *_clients_video_thread(UNUSED void *arg) {
|
||||
|
||||
JLOG_INFO("video", "Memsink opened; reading frames ...");
|
||||
while (!STOP && HAS_WATCHERS) {
|
||||
int result = _wait_frame(fd, mem, frame_id);
|
||||
int result = memsink_fd_wait_frame(fd, mem, frame_id);
|
||||
if (result == 0) {
|
||||
if (_get_frame(fd, mem, frame, &frame_id) != 0) {
|
||||
frame_s *frame = memsink_fd_get_frame(fd, mem, &frame_id);
|
||||
if (frame == NULL) {
|
||||
goto close_memsink;
|
||||
}
|
||||
LOCK;
|
||||
rtpv_wrap(_g_rtpv, frame);
|
||||
UNLOCK;
|
||||
if (queue_put(_g_video_queue, frame, 0) != 0) {
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Video queue is full"); });
|
||||
frame_destroy(frame);
|
||||
}
|
||||
} else if (result == -1) {
|
||||
goto close_memsink;
|
||||
}
|
||||
@@ -261,17 +177,16 @@ static void *_clients_video_thread(UNUSED void *arg) {
|
||||
}
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
|
||||
# undef IF_NOT_REPORTED
|
||||
|
||||
frame_destroy(frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_clients_audio_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_a_clients");
|
||||
static void *_audio_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_audio");
|
||||
atomic_store(&_g_audio_tid_created, true);
|
||||
assert(_g_audio_dev);
|
||||
assert(_g_audio_dev_name);
|
||||
assert(_g_tc358743_dev_path);
|
||||
|
||||
unsigned error_reported = 0;
|
||||
|
||||
while (!STOP) {
|
||||
if (!HAS_WATCHERS) {
|
||||
@@ -279,20 +194,40 @@ static void *_clients_audio_thread(UNUSED void *arg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tc358743_info_s info = {0};
|
||||
audio_s *audio = NULL;
|
||||
if ((audio = audio_init(_g_audio_dev)) == NULL) {
|
||||
|
||||
if (tc358743_read_info(_g_tc358743_dev_path, &info) < 0) {
|
||||
goto close_audio;
|
||||
}
|
||||
if (!info.has_audio) {
|
||||
IF_NOT_REPORTED({ JLOG_INFO("audio", "No audio presented from the host"); });
|
||||
goto close_audio;
|
||||
}
|
||||
IF_NOT_REPORTED({ JLOG_INFO("audio", "Detected host audio"); });
|
||||
if ((audio = audio_init(_g_audio_dev_name, info.audio_hz)) == NULL) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
error_reported = 0;
|
||||
|
||||
while (!STOP && HAS_WATCHERS) {
|
||||
if (
|
||||
tc358743_read_info(_g_tc358743_dev_path, &info) < 0
|
||||
|| !info.has_audio
|
||||
|| audio->pcm_hz != info.audio_hz
|
||||
) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
size_t size = RTP_DATAGRAM_SIZE - RTP_HEADER_SIZE;
|
||||
uint8_t data[size];
|
||||
uint64_t pts;
|
||||
int result = audio_copy_encoded(audio, data, &size, &pts);
|
||||
int result = audio_get_encoded(audio, data, &size, &pts);
|
||||
if (result == 0) {
|
||||
LOCK;
|
||||
LOCK_AUDIO;
|
||||
rtpa_wrap(_g_rtpa, data, size, pts);
|
||||
UNLOCK;
|
||||
UNLOCK_AUDIO;
|
||||
} else if (result == -1) {
|
||||
goto close_audio;
|
||||
}
|
||||
@@ -307,47 +242,12 @@ static void *_clients_audio_thread(UNUSED void *arg) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *_get_config_value(janus_config *config, const char *section, const char *option) {
|
||||
janus_config_category *section_obj = janus_config_get_create(config, NULL, janus_config_type_category, section);
|
||||
janus_config_item *option_obj = janus_config_get(config, section_obj, janus_config_type_item, option);
|
||||
if (option_obj == NULL || option_obj->value == NULL || option_obj->value[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
return strdup(option_obj->value);
|
||||
}
|
||||
#undef IF_NOT_REPORTED
|
||||
|
||||
static int _read_config(const char *config_dir_path) {
|
||||
char *config_file_path;
|
||||
janus_config *config = NULL;
|
||||
|
||||
A_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, _plugin_get_package());
|
||||
JLOG_INFO("main", "Reading config file '%s' ...", config_file_path);
|
||||
|
||||
config = janus_config_parse(config_file_path);
|
||||
if (config == NULL) {
|
||||
JLOG_ERROR("main", "Can't read config");
|
||||
goto error;
|
||||
}
|
||||
janus_config_print(config);
|
||||
|
||||
if ((_g_memsink_obj = _get_config_value(config, "memsink", "object")) == NULL) {
|
||||
JLOG_ERROR("main", "Missing config value: memsink.object");
|
||||
goto error;
|
||||
}
|
||||
if ((_g_audio_dev = _get_config_value(config, "audio", "device")) != NULL) {
|
||||
JLOG_INFO("main", "Enabled the experimental AUDIO feature");
|
||||
}
|
||||
|
||||
int retval = 0;
|
||||
goto ok;
|
||||
error:
|
||||
retval = -1;
|
||||
ok:
|
||||
if (config) {
|
||||
janus_config_destroy(config);
|
||||
}
|
||||
free(config_file_path);
|
||||
return retval;
|
||||
static void _relay_rtp_clients(const rtp_s *rtp) {
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
client_send(client, rtp);
|
||||
});
|
||||
}
|
||||
|
||||
static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
|
||||
@@ -358,88 +258,79 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
|
||||
// sysctl -w net.core.wmem_max=1000000
|
||||
|
||||
JLOG_INFO("main", "Initializing plugin ...");
|
||||
assert(!atomic_load(&_g_video_tid_created));
|
||||
assert(!atomic_load(&_g_audio_tid_created));
|
||||
assert(!READY);
|
||||
assert(!STOP);
|
||||
if (gw == NULL || config_dir_path == NULL || _read_config(config_dir_path) < 0) {
|
||||
if (gw == NULL || config_dir_path == NULL || read_config(config_dir_path,
|
||||
&_g_video_sink_name,
|
||||
&_g_audio_dev_name,
|
||||
&_g_tc358743_dev_path
|
||||
) < 0) {
|
||||
return -1;
|
||||
}
|
||||
_g_gw = gw;
|
||||
|
||||
_g_video_queue = queue_init(1024);
|
||||
_g_rtpv = rtpv_init(_relay_rtp_clients);
|
||||
if (_g_audio_dev) {
|
||||
if (_g_audio_dev_name) {
|
||||
_g_rtpa = rtpa_init(_relay_rtp_clients);
|
||||
A_THREAD_CREATE(&_g_audio_tid, _clients_audio_thread, NULL);
|
||||
A_THREAD_CREATE(&_g_audio_tid, _audio_thread, NULL);
|
||||
}
|
||||
A_THREAD_CREATE(&_g_video_tid, _clients_video_thread, NULL);
|
||||
A_THREAD_CREATE(&_g_video_rtp_tid, _video_rtp_thread, NULL);
|
||||
A_THREAD_CREATE(&_g_video_sink_tid, _video_sink_thread, NULL);
|
||||
|
||||
atomic_store(&_g_ready, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _plugin_destroy(void) {
|
||||
JLOG_INFO("main", "Destroying plugin ...");
|
||||
|
||||
atomic_store(&_g_stop, true);
|
||||
if (atomic_load(&_g_video_tid_created)) {
|
||||
A_THREAD_JOIN(_g_video_tid);
|
||||
}
|
||||
if (atomic_load(&_g_audio_tid_created)) {
|
||||
A_THREAD_JOIN(_g_audio_tid);
|
||||
}
|
||||
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { A_THREAD_JOIN(_tid); } }
|
||||
JOIN(_g_video_sink_tid);
|
||||
JOIN(_g_video_rtp_tid);
|
||||
JOIN(_g_audio_tid);
|
||||
# undef JOIN
|
||||
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
LIST_REMOVE(_g_clients, client);
|
||||
free(client);
|
||||
client_destroy(client);
|
||||
});
|
||||
_g_clients = NULL;
|
||||
|
||||
if (_g_rtpa) {
|
||||
rtpa_destroy(_g_rtpa);
|
||||
_g_rtpa = NULL;
|
||||
}
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(_g_video_queue, frame_destroy);
|
||||
|
||||
rtpv_destroy(_g_rtpv);
|
||||
_g_rtpv = NULL;
|
||||
|
||||
_g_gw = NULL;
|
||||
|
||||
if (_g_audio_dev) {
|
||||
free(_g_audio_dev);
|
||||
_g_audio_dev = NULL;
|
||||
}
|
||||
|
||||
if (_g_memsink_obj) {
|
||||
free(_g_memsink_obj);
|
||||
_g_memsink_obj = NULL;
|
||||
}
|
||||
# define DEL(_func, _var) { if (_var) { _func(_var); } }
|
||||
DEL(rtpa_destroy, _g_rtpa);
|
||||
DEL(rtpv_destroy, _g_rtpv);
|
||||
DEL(free, _g_tc358743_dev_path);
|
||||
DEL(free, _g_audio_dev_name);
|
||||
DEL(free, _g_video_sink_name);
|
||||
# undef DEL
|
||||
}
|
||||
|
||||
#define IF_DISABLED(...) { if (!READY || STOP) { __VA_ARGS__ } }
|
||||
|
||||
static void _plugin_create_session(janus_plugin_session *session, int *err) {
|
||||
IF_DISABLED({ *err = -1; return; });
|
||||
LOCK;
|
||||
LOCK_ALL;
|
||||
JLOG_INFO("main", "Creating session %p ...", session);
|
||||
_client_s *client;
|
||||
A_CALLOC(client, 1);
|
||||
client->session = session;
|
||||
client->transmit = true;
|
||||
client_s *client = client_init(_g_gw, session, (_g_audio_dev_name != NULL));
|
||||
LIST_APPEND(_g_clients, client);
|
||||
atomic_store(&_g_has_watchers, true);
|
||||
UNLOCK;
|
||||
UNLOCK_ALL;
|
||||
}
|
||||
|
||||
static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
|
||||
IF_DISABLED({ *err = -1; return; });
|
||||
LOCK;
|
||||
LOCK_ALL;
|
||||
bool found = false;
|
||||
bool has_watchers = false;
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
JLOG_INFO("main", "Removing session %p ...", session);
|
||||
LIST_REMOVE(_g_clients, client);
|
||||
free(client);
|
||||
client_destroy(client);
|
||||
found = true;
|
||||
} else {
|
||||
has_watchers = (has_watchers || client->transmit);
|
||||
has_watchers = (has_watchers || atomic_load(&client->transmit));
|
||||
}
|
||||
});
|
||||
if (!found) {
|
||||
@@ -447,41 +338,41 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
|
||||
*err = -2;
|
||||
}
|
||||
atomic_store(&_g_has_watchers, has_watchers);
|
||||
UNLOCK;
|
||||
UNLOCK_ALL;
|
||||
}
|
||||
|
||||
static json_t *_plugin_query_session(janus_plugin_session *session) {
|
||||
IF_DISABLED({ return NULL; });
|
||||
json_t *info = NULL;
|
||||
LOCK;
|
||||
LOCK_ALL;
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
info = json_string("session_found");
|
||||
break;
|
||||
}
|
||||
});
|
||||
UNLOCK;
|
||||
UNLOCK_ALL;
|
||||
return info;
|
||||
}
|
||||
|
||||
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
|
||||
IF_DISABLED({ return; });
|
||||
LOCK;
|
||||
LOCK_ALL;
|
||||
bool found = false;
|
||||
bool has_watchers = false;
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
client->transmit = transmit;
|
||||
atomic_store(&client->transmit, transmit);
|
||||
// JLOG_INFO("main", "%s session %p", msg, session);
|
||||
found = true;
|
||||
}
|
||||
has_watchers = (has_watchers || client->transmit);
|
||||
has_watchers = (has_watchers || atomic_load(&client->transmit));
|
||||
});
|
||||
if (!found) {
|
||||
JLOG_WARN("main", "No session %p", session);
|
||||
}
|
||||
atomic_store(&_g_has_watchers, has_watchers);
|
||||
UNLOCK;
|
||||
UNLOCK_ALL;
|
||||
}
|
||||
|
||||
#undef IF_DISABLED
|
||||
@@ -511,7 +402,7 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
json_object_set_new(_event, "ustreamer", json_string("event")); \
|
||||
json_object_set_new(_event, "error_code", json_integer(_error)); \
|
||||
json_object_set_new(_event, "error", json_string(_reason)); \
|
||||
_g_gw->push_event(session, &_plugin, transaction, _event, NULL); \
|
||||
_g_gw->push_event(session, create(), transaction, _event, NULL); \
|
||||
json_decref(_event); \
|
||||
}
|
||||
|
||||
@@ -534,7 +425,7 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
json_t *_result = json_object(); \
|
||||
json_object_set_new(_result, "status", json_string(_status)); \
|
||||
json_object_set_new(_event, "result", _result); \
|
||||
_g_gw->push_event(session, &_plugin, transaction, _event, _jsep); \
|
||||
_g_gw->push_event(session, create(), transaction, _event, _jsep); \
|
||||
json_decref(_event); \
|
||||
}
|
||||
|
||||
@@ -577,6 +468,52 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
FREE_MSG_JSEP;
|
||||
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
|
||||
|
||||
# undef PUSH_STATUS
|
||||
# undef PUSH_ERROR
|
||||
# undef FREE_MSG_JSEP
|
||||
}
|
||||
|
||||
|
||||
// ***** Plugin *****
|
||||
|
||||
static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; }
|
||||
static int _plugin_get_version(void) { return VERSION_U; }
|
||||
static const char *_plugin_get_version_string(void) { return VERSION; }
|
||||
static const char *_plugin_get_description(void) { return "PiKVM uStreamer Janus plugin for H.264 video"; }
|
||||
static const char *_plugin_get_name(void) { return PLUGIN_NAME; }
|
||||
static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
|
||||
static const char *_plugin_get_package(void) { return 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
|
||||
}
|
||||
|
||||
janus_plugin *create(void) {
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Woverride-init"
|
||||
static janus_plugin plugin = JANUS_PLUGIN_INIT(
|
||||
.init = _plugin_init,
|
||||
.destroy = _plugin_destroy,
|
||||
|
||||
.create_session = _plugin_create_session,
|
||||
.destroy_session = _plugin_destroy_session,
|
||||
.query_session = _plugin_query_session,
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
@@ -44,22 +44,30 @@ void queue_destroy(queue_s *queue) {
|
||||
free(queue);
|
||||
}
|
||||
|
||||
#define WAIT_LOOP(_var, _cond) { \
|
||||
struct timespec ts; \
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &ts)); \
|
||||
ts.tv_sec += timeout; \
|
||||
#define WAIT_OR_UNLOCK(_var, _cond) { \
|
||||
struct timespec _ts; \
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &_ts)); \
|
||||
ld_to_timespec(timespec_to_ld(&_ts) + timeout, &_ts); \
|
||||
while (_var) { \
|
||||
int err = pthread_cond_timedwait(_cond, &queue->mutex, &ts); \
|
||||
int err = pthread_cond_timedwait(_cond, &queue->mutex, &_ts); \
|
||||
if (err == ETIMEDOUT) { \
|
||||
A_MUTEX_UNLOCK(&queue->mutex); \
|
||||
return -1; \
|
||||
} \
|
||||
assert(!err); \
|
||||
} \
|
||||
}
|
||||
|
||||
int queue_put(queue_s *queue, void *item, unsigned timeout) {
|
||||
int queue_put(queue_s *queue, void *item, long double timeout) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
WAIT_LOOP(queue->size == queue->capacity, &queue->full_cond);
|
||||
if (timeout == 0) {
|
||||
if (queue->size == queue->capacity) {
|
||||
A_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;
|
||||
@@ -69,9 +77,9 @@ int queue_put(queue_s *queue, void *item, unsigned timeout) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int queue_get(queue_s *queue, void **item, unsigned timeout) {
|
||||
int queue_get(queue_s *queue, void **item, long double timeout) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
WAIT_LOOP(queue->size == 0, &queue->empty_cond);
|
||||
WAIT_OR_UNLOCK(queue->size == 0, &queue->empty_cond);
|
||||
*item = queue->items[queue->out];
|
||||
--queue->size;
|
||||
++queue->out;
|
||||
@@ -81,7 +89,7 @@ int queue_get(queue_s *queue, void **item, unsigned timeout) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef WAIT_LOOP
|
||||
#undef WAIT_OR_UNLOCK
|
||||
|
||||
int queue_get_free(queue_s *queue) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
|
||||
@@ -24,11 +24,12 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
|
||||
// Based on https://github.com/seifzadeh/c-pthread-queue/blob/master/queue.h
|
||||
@@ -46,9 +47,21 @@ typedef struct {
|
||||
} queue_s;
|
||||
|
||||
|
||||
#define QUEUE_FREE_ITEMS_AND_DESTROY(_queue, _free_item) { \
|
||||
while (!queue_get_free(_queue)) { \
|
||||
void *_ptr; \
|
||||
assert(!queue_get(_queue, &_ptr, 0.1)); \
|
||||
if (_ptr != NULL) { \
|
||||
_free_item(_ptr); \
|
||||
} \
|
||||
} \
|
||||
queue_destroy(_queue); \
|
||||
}
|
||||
|
||||
|
||||
queue_s *queue_init(unsigned capacity);
|
||||
void queue_destroy(queue_s *queue);
|
||||
|
||||
int queue_put(queue_s *queue, void *item, unsigned timeout);
|
||||
int queue_get(queue_s *queue, void **item, unsigned timeout);
|
||||
int queue_put(queue_s *queue, void *item, long double timeout);
|
||||
int queue_get(queue_s *queue, void **item, long double timeout);
|
||||
int queue_get_free(queue_s *queue);
|
||||
|
||||
@@ -35,6 +35,13 @@ rtp_s *rtp_init(unsigned payload, bool video) {
|
||||
return rtp;
|
||||
}
|
||||
|
||||
rtp_s *rtp_dup(const rtp_s *rtp) {
|
||||
rtp_s *new;
|
||||
A_CALLOC(new, 1);
|
||||
memcpy(new, rtp, sizeof(rtp_s));
|
||||
return new;
|
||||
}
|
||||
|
||||
void rtp_destroy(rtp_s *rtp) {
|
||||
free(rtp);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
# #
|
||||
# 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 #
|
||||
@@ -31,7 +28,7 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
|
||||
@@ -53,6 +50,7 @@ typedef void (*rtp_callback_f)(const rtp_s *rtp);
|
||||
|
||||
|
||||
rtp_s *rtp_init(unsigned payload, bool video);
|
||||
rtp_s *rtp_dup(const rtp_s *rtp);
|
||||
void rtp_destroy(rtp_s *rtp);
|
||||
|
||||
void rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked);
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
# #
|
||||
# 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 #
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
# #
|
||||
# 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 #
|
||||
@@ -32,9 +29,9 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
#include "audio.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
|
||||
@@ -81,6 +81,8 @@ char *rtpv_make_sdp(rtpv_s *rtpv) {
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
// XXX: See client.c
|
||||
// "a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
|
||||
"a=sendonly" RN,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
PAYLOAD, sps,
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
# #
|
||||
# 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 #
|
||||
@@ -36,10 +33,11 @@
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
#include "frame.h"
|
||||
#include "base64.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/base64.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
|
||||
64
janus/src/tc358743.c
Normal file
64
janus/src/tc358743.c
Normal 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 tc358743_read_info(const char *path, tc358743_info_s *info) {
|
||||
MEMSET_ZERO(*info);
|
||||
|
||||
int fd = -1;
|
||||
if ((fd = open(path, O_RDWR)) < 0) {
|
||||
JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define READ_CID(_cid, _field) { \
|
||||
struct v4l2_control ctl = {0}; \
|
||||
ctl.id = _cid; \
|
||||
if (xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) { \
|
||||
JLOG_PERROR("audio", "Can't get value of " #_cid); \
|
||||
close(fd); \
|
||||
return -1; \
|
||||
} \
|
||||
info->_field = 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
46
janus/src/tc358743.h
Normal 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;
|
||||
} tc358743_info_s;
|
||||
|
||||
|
||||
int tc358743_read_info(const char *path, tc358743_info_s *info);
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/threading.h
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/tools.h
|
||||
1
janus/src/uslibs/base64.c
Symbolic link
1
janus/src/uslibs/base64.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/base64.c
|
||||
1
janus/src/uslibs/base64.h
Symbolic link
1
janus/src/uslibs/base64.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/base64.h
|
||||
1
janus/src/uslibs/const.h
Symbolic link
1
janus/src/uslibs/const.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/const.h
|
||||
1
janus/src/uslibs/frame.c
Symbolic link
1
janus/src/uslibs/frame.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.c
|
||||
1
janus/src/uslibs/frame.h
Symbolic link
1
janus/src/uslibs/frame.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.h
|
||||
1
janus/src/uslibs/list.h
Symbolic link
1
janus/src/uslibs/list.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/list.h
|
||||
1
janus/src/uslibs/memsinksh.h
Symbolic link
1
janus/src/uslibs/memsinksh.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/memsinksh.h
|
||||
1
janus/src/uslibs/threading.h
Symbolic link
1
janus/src/uslibs/threading.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/threading.h
|
||||
1
janus/src/uslibs/tools.h
Symbolic link
1
janus/src/uslibs/tools.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/tools.h
|
||||
1
janus/src/uslibs/xioctl.h
Symbolic link
1
janus/src/uslibs/xioctl.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/xioctl.h
|
||||
@@ -1,3 +1,6 @@
|
||||
#define CHAR_BIT 8
|
||||
#define WITH_GPIO
|
||||
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
|
||||
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1
|
||||
#define CLOCK_MONOTONIC_RAW 1
|
||||
#define CLOCK_MONOTONIC_FAST 1
|
||||
|
||||
@@ -15,15 +15,8 @@ disable =
|
||||
locally-disabled,
|
||||
fixme,
|
||||
missing-docstring,
|
||||
no-init,
|
||||
no-self-use,
|
||||
superfluous-parens,
|
||||
abstract-class-not-used,
|
||||
abstract-class-little-used,
|
||||
duplicate-code,
|
||||
bad-continuation,
|
||||
bad-whitespace,
|
||||
star-args,
|
||||
broad-except,
|
||||
redundant-keyword-arg,
|
||||
wrong-import-order,
|
||||
@@ -39,9 +32,6 @@ msg-template = {symbol} -- {path}:{line}({obj}): {msg}
|
||||
max-line-length = 160
|
||||
|
||||
[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-names = _, __, x, y, ws, make-html-h, make-jpeg-h
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ changedir = /src
|
||||
[testenv:cppcheck]
|
||||
whitelist_externals = cppcheck
|
||||
commands = cppcheck \
|
||||
-j4 \
|
||||
--force \
|
||||
--std=c11 \
|
||||
--std=c17 \
|
||||
--error-exitcode=1 \
|
||||
--quiet \
|
||||
--enable=warning,unusedFunction,portability,performance,style \
|
||||
@@ -19,7 +20,7 @@ commands = cppcheck \
|
||||
--inline-suppr \
|
||||
--library=python \
|
||||
--include=linters/cppcheck.h \
|
||||
src python/ustreamer.c janus/rtp.h janus/rtp.c janus/plugin.c
|
||||
src python/*.? janus/*.?
|
||||
|
||||
[testenv:flake8]
|
||||
whitelist_externals = bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer-dump.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER-DUMP 1 "version 5.5" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 5.15" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER 1 "version 5.5" "November 2020"
|
||||
.TH USTREAMER 1 "version 5.15" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=5.5
|
||||
pkgver=5.15
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
@@ -22,8 +22,8 @@ if [ -e /usr/bin/python3 ]; then
|
||||
makedepends+=(python-setuptools)
|
||||
fi
|
||||
if [ -e /usr/include/janus/plugins/plugin.h ];then
|
||||
depends+=(janus-gateway-pikvm)
|
||||
makedepends+=(janus-gateway-pikvm)
|
||||
depends+=(janus-gateway-pikvm alsa-lib opus)
|
||||
makedepends+=(janus-gateway-pikvm alsa-lib opus)
|
||||
_options="$_options WITH_JANUS=1"
|
||||
fi
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=5.5
|
||||
PKG_VERSION:=5.15
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import os
|
||||
|
||||
from typing import List
|
||||
|
||||
from setuptools import Extension
|
||||
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__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="5.5",
|
||||
version="5.15",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
@@ -17,9 +28,10 @@ if __name__ == "__main__":
|
||||
Extension(
|
||||
"ustreamer",
|
||||
libraries=["rt", "m", "pthread"],
|
||||
extra_compile_args=["-std=c17", "-D_GNU_SOURCE"],
|
||||
undef_macros=["NDEBUG"],
|
||||
sources=["src/" + name for name in os.listdir("src") if name.endswith(".c")],
|
||||
depends=["src/" + name for name in os.listdir("src") if name.endswith(".h")],
|
||||
sources=_find_sources(".c"),
|
||||
depends=_find_sources(".h"),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/frame.c
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/frame.h
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/memsinksh.h
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/tools.h
|
||||
1
python/src/uslibs/frame.c
Symbolic link
1
python/src/uslibs/frame.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.c
|
||||
1
python/src/uslibs/frame.h
Symbolic link
1
python/src/uslibs/frame.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.h
|
||||
1
python/src/uslibs/memsinksh.h
Symbolic link
1
python/src/uslibs/memsinksh.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/memsinksh.h
|
||||
1
python/src/uslibs/tools.h
Symbolic link
1
python/src/uslibs/tools.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/tools.h
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "frame.h"
|
||||
#include "memsinksh.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -10,7 +10,7 @@ LDFLAGS ?=
|
||||
_USTR = ustreamer.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)
|
||||
|
||||
_COMMON_LIBS = -lm -ljpeg -pthread -lrt
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../libs/config.h"
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION_MAJOR 5
|
||||
#define VERSION_MINOR 5
|
||||
#define VERSION_MINOR 15
|
||||
|
||||
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
|
||||
@@ -146,6 +147,19 @@ INLINE unsigned get_cores_available(void) {
|
||||
return max_u(min_u(cores_sysconf, 4), 1);
|
||||
}
|
||||
|
||||
INLINE void ld_to_timespec(long double ld, struct timespec *ts) {
|
||||
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 timespec_to_ld(const struct timespec *ts) {
|
||||
return ts->tv_sec + ((long double)ts->tv_nsec) / 1000000000;
|
||||
}
|
||||
|
||||
INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
long double deadline_ts = get_now_monotonic() + timeout;
|
||||
int retval = -1;
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#ifndef CFG_XIOCTL_RETRIES
|
||||
@@ -51,10 +50,5 @@ INLINE int xioctl(int fd, int request, void *arg) {
|
||||
|| errno == ETIMEDOUT
|
||||
)
|
||||
);
|
||||
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (retval && retries <= 0) {
|
||||
LOG_PERROR("ioctl(%d) retried %u times; giving up", request, XIOCTL_RETRIES);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "../../libs/config.h"
|
||||
#include "../../libs/const.h"
|
||||
|
||||
|
||||
extern const char *const HTML_INDEX_PAGE;
|
||||
|
||||
@@ -80,7 +80,8 @@ static const char *_standard_to_string(v4l2_std_id standard);
|
||||
static const char *_io_method_to_string_supported(enum v4l2_memory io_method);
|
||||
|
||||
|
||||
#define RUN(_next) dev->run->_next
|
||||
#define RUN(_next) dev->run->_next
|
||||
#define D_XIOCTL(...) xioctl(RUN(fd), __VA_ARGS__)
|
||||
|
||||
|
||||
device_s *device_init(void) {
|
||||
@@ -222,7 +223,7 @@ int device_export_to_dma(device_s *dev) {
|
||||
exp.index = index;
|
||||
|
||||
LOG_DEBUG("Exporting device buffer=%u to DMA ...", index);
|
||||
if (xioctl(RUN(fd), VIDIOC_EXPBUF, &exp) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_EXPBUF, &exp) < 0) {
|
||||
LOG_PERROR("Can't export device buffer=%u to DMA", index);
|
||||
goto error;
|
||||
}
|
||||
@@ -248,7 +249,7 @@ int device_switch_capturing(device_s *dev, bool enable) {
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
LOG_DEBUG("%s device capturing ...", (enable ? "Starting" : "Stopping"));
|
||||
if (xioctl(RUN(fd), (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
|
||||
if (D_XIOCTL((enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
|
||||
LOG_PERROR("Can't %s capturing", (enable ? "start" : "stop"));
|
||||
if (enable) {
|
||||
return -1;
|
||||
@@ -315,7 +316,7 @@ int device_grab_buffer(device_s *dev, hw_buffer_s **hw) {
|
||||
buf.memory = dev->io_method;
|
||||
|
||||
LOG_DEBUG("Grabbing device buffer ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_DQBUF, &buf) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_DQBUF, &buf) < 0) {
|
||||
LOG_PERROR("Can't grab device buffer");
|
||||
return -1;
|
||||
}
|
||||
@@ -336,7 +337,7 @@ int device_grab_buffer(device_s *dev, hw_buffer_s **hw) {
|
||||
LOG_DEBUG("Dropped too small frame, assuming it was broken: buffer=%u, bytesused=%u",
|
||||
buf.index, buf.bytesused);
|
||||
LOG_DEBUG("Releasing device buffer=%u (broken frame) ...", buf.index);
|
||||
if (xioctl(RUN(fd), VIDIOC_QBUF, &buf) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_QBUF, &buf) < 0) {
|
||||
LOG_PERROR("Can't release device buffer=%u (broken frame)", buf.index);
|
||||
return -1;
|
||||
}
|
||||
@@ -370,7 +371,7 @@ int device_release_buffer(device_s *dev, hw_buffer_s *hw) {
|
||||
const unsigned index = hw->buf.index;
|
||||
LOG_DEBUG("Releasing device buffer=%u ...", index);
|
||||
|
||||
if (xioctl(RUN(fd), VIDIOC_QBUF, &hw->buf) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_QBUF, &hw->buf) < 0) {
|
||||
LOG_PERROR("Can't release device buffer=%u", index);
|
||||
return -1;
|
||||
}
|
||||
@@ -382,7 +383,7 @@ int device_consume_event(device_s *dev) {
|
||||
struct v4l2_event event;
|
||||
|
||||
LOG_DEBUG("Consuming V4L2 event ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_DQEVENT, &event) == 0) {
|
||||
if (D_XIOCTL(VIDIOC_DQEVENT, &event) == 0) {
|
||||
switch (event.type) {
|
||||
case V4L2_EVENT_SOURCE_CHANGE:
|
||||
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
|
||||
@@ -401,7 +402,7 @@ static int _device_open_check_cap(device_s *dev) {
|
||||
struct v4l2_capability cap = {0};
|
||||
|
||||
LOG_DEBUG("Querying device capabilities ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERYCAP, &cap) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_QUERYCAP, &cap) < 0) {
|
||||
LOG_PERROR("Can't query device capabilities");
|
||||
return -1;
|
||||
}
|
||||
@@ -418,14 +419,14 @@ static int _device_open_check_cap(device_s *dev) {
|
||||
|
||||
int input = dev->input; // Needs a pointer to int for ioctl()
|
||||
LOG_INFO("Using input channel: %d", input);
|
||||
if (xioctl(RUN(fd), VIDIOC_S_INPUT, &input) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_INPUT, &input) < 0) {
|
||||
LOG_ERROR("Can't set input channel");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (dev->standard != V4L2_STD_UNKNOWN) {
|
||||
LOG_INFO("Using TV standard: %s", _standard_to_string(dev->standard));
|
||||
if (xioctl(RUN(fd), VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
LOG_ERROR("Can't set video standard");
|
||||
return -1;
|
||||
}
|
||||
@@ -448,7 +449,7 @@ static int _device_open_dv_timings(device_s *dev) {
|
||||
sub.type = V4L2_EVENT_SOURCE_CHANGE;
|
||||
|
||||
LOG_DEBUG("Subscribing to DV-timings events ...")
|
||||
if (xioctl(RUN(fd), VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
|
||||
LOG_PERROR("Can't subscribe to DV-timings events");
|
||||
return -1;
|
||||
}
|
||||
@@ -459,8 +460,8 @@ static int _device_open_dv_timings(device_s *dev) {
|
||||
static int _device_apply_dv_timings(device_s *dev) {
|
||||
struct v4l2_dv_timings dv = {0};
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
|
||||
LOG_DEBUG("Calling xioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
|
||||
if (D_XIOCTL(VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
|
||||
if (dv.type == V4L2_DV_BT_656_1120) {
|
||||
// See v4l2_print_dv_timings() in the kernel
|
||||
unsigned htot = V4L2_DV_BT_FRAME_WIDTH(&dv.bt);
|
||||
@@ -478,8 +479,8 @@ static int _device_apply_dv_timings(device_s *dev) {
|
||||
(unsigned long long)dv.bt.pixelclock, dv.bt.vsync, dv.bt.hsync);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_S_DV_TIMINGS, &dv) < 0) {
|
||||
LOG_DEBUG("Calling xioctl(VIDIOC_S_DV_TIMINGS) ...");
|
||||
if (D_XIOCTL(VIDIOC_S_DV_TIMINGS, &dv) < 0) {
|
||||
LOG_PERROR("Failed to set DV-timings");
|
||||
return -1;
|
||||
}
|
||||
@@ -489,10 +490,10 @@ static int _device_apply_dv_timings(device_s *dev) {
|
||||
}
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYSTD) ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERYSTD, &dev->standard) == 0) {
|
||||
LOG_DEBUG("Calling xioctl(VIDIOC_QUERYSTD) ...");
|
||||
if (D_XIOCTL(VIDIOC_QUERYSTD, &dev->standard) == 0) {
|
||||
LOG_INFO("Applying the new VIDIOC_S_STD: %s ...", _standard_to_string(dev->standard));
|
||||
if (xioctl(RUN(fd), VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
LOG_PERROR("Can't set video standard");
|
||||
return -1;
|
||||
}
|
||||
@@ -515,7 +516,7 @@ static int _device_open_format(device_s *dev, bool first) {
|
||||
// Set format
|
||||
LOG_DEBUG("Probing device format=%s, stride=%u, resolution=%ux%u ...",
|
||||
_format_to_string_supported(dev->format), stride, RUN(width), RUN(height));
|
||||
if (xioctl(RUN(fd), VIDIOC_S_FMT, &fmt) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_FMT, &fmt) < 0) {
|
||||
LOG_PERROR("Can't set device format");
|
||||
return -1;
|
||||
}
|
||||
@@ -566,7 +567,7 @@ static void _device_open_hw_fps(device_s *dev) {
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
LOG_DEBUG("Querying HW FPS ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_G_PARM, &setfps) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_G_PARM, &setfps) < 0) {
|
||||
if (errno == ENOTTY) { // Quiet message for TC358743
|
||||
LOG_INFO("Querying HW FPS changing is not supported");
|
||||
} else {
|
||||
@@ -587,7 +588,7 @@ static void _device_open_hw_fps(device_s *dev) {
|
||||
SETFPS_TPF(numerator) = 1;
|
||||
SETFPS_TPF(denominator) = (dev->desired_fps == 0 ? 255 : dev->desired_fps);
|
||||
|
||||
if (xioctl(RUN(fd), VIDIOC_S_PARM, &setfps) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_PARM, &setfps) < 0) {
|
||||
LOG_PERROR("Can't set HW FPS");
|
||||
return;
|
||||
}
|
||||
@@ -618,11 +619,11 @@ static void _device_open_jpeg_quality(device_s *dev) {
|
||||
if (is_jpeg(RUN(format))) {
|
||||
struct v4l2_jpegcompression comp = {0};
|
||||
|
||||
if (xioctl(RUN(fd), VIDIOC_G_JPEGCOMP, &comp) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_G_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Device doesn't support setting of HW encoding quality parameters");
|
||||
} else {
|
||||
comp.quality = dev->jpeg_quality;
|
||||
if (xioctl(RUN(fd), VIDIOC_S_JPEGCOMP, &comp) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Can't change MJPEG quality for JPEG source with HW pass-through encoder");
|
||||
} else {
|
||||
quality = dev->jpeg_quality;
|
||||
@@ -650,7 +651,7 @@ static int _device_open_io_method_mmap(device_s *dev) {
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
LOG_DEBUG("Requesting %u device buffers for MMAP ...", req.count);
|
||||
if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support MMAP method", dev->path);
|
||||
return -1;
|
||||
}
|
||||
@@ -671,8 +672,8 @@ static int _device_open_io_method_mmap(device_s *dev) {
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = RUN(n_bufs);
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer=%u ...", RUN(n_bufs));
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
LOG_DEBUG("Calling xioctl(VIDIOC_QUERYBUF) for device buffer=%u ...", RUN(n_bufs));
|
||||
if (D_XIOCTL(VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
LOG_PERROR("Can't VIDIOC_QUERYBUF");
|
||||
return -1;
|
||||
}
|
||||
@@ -707,7 +708,7 @@ static int _device_open_io_method_userptr(device_s *dev) {
|
||||
req.memory = V4L2_MEMORY_USERPTR;
|
||||
|
||||
LOG_DEBUG("Requesting %u device buffers for USERPTR ...", req.count);
|
||||
if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support USERPTR method", dev->path);
|
||||
return -1;
|
||||
}
|
||||
@@ -747,8 +748,8 @@ static int _device_open_queue_buffers(device_s *dev) {
|
||||
buf.length = RUN(hw_bufs)[index].raw.allocated;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer=%u ...", index);
|
||||
if (xioctl(RUN(fd), VIDIOC_QBUF, &buf) < 0) {
|
||||
LOG_DEBUG("Calling xioctl(VIDIOC_QBUF) for buffer=%u ...", index);
|
||||
if (D_XIOCTL(VIDIOC_QBUF, &buf) < 0) {
|
||||
LOG_PERROR("Can't VIDIOC_QBUF");
|
||||
return -1;
|
||||
}
|
||||
@@ -836,7 +837,7 @@ static int _device_query_control(
|
||||
MEMSET_ZERO(*query);
|
||||
query->id = cid;
|
||||
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
|
||||
if (D_XIOCTL(VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
|
||||
if (!quiet) {
|
||||
LOG_ERROR("Changing control %s is unsupported", name);
|
||||
}
|
||||
@@ -861,7 +862,7 @@ static void _device_set_control(
|
||||
ctl.id = cid;
|
||||
ctl.value = value;
|
||||
|
||||
if (xioctl(RUN(fd), VIDIOC_S_CTRL, &ctl) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_CTRL, &ctl) < 0) {
|
||||
if (!quiet) {
|
||||
LOG_PERROR("Can't set control %s", name);
|
||||
}
|
||||
@@ -902,4 +903,5 @@ static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
|
||||
return "unsupported";
|
||||
}
|
||||
|
||||
# undef RUN
|
||||
#undef D_XIOCTL
|
||||
#undef RUN
|
||||
|
||||
@@ -45,8 +45,7 @@
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/frame.h"
|
||||
|
||||
#include "xioctl.h"
|
||||
#include "../libs/xioctl.h"
|
||||
|
||||
|
||||
#define VIDEO_MIN_WIDTH ((unsigned)160)
|
||||
|
||||
@@ -224,8 +224,9 @@ static bool _worker_run_job(worker_s *wr) {
|
||||
} else if (ER(type) == ENCODER_TYPE_NOOP) {
|
||||
LOG_VERBOSE("Compressing JPEG using NOOP (do nothing): worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
usleep(5000); // Просто чтобы работала логика desired_fps
|
||||
dest->encode_end_ts = get_now_monotonic();
|
||||
dest->encode_end_ts = get_now_monotonic(); // frame_encoding_end()
|
||||
}
|
||||
|
||||
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
|
||||
static m2m_encoder_s *_m2m_encoder_init(
|
||||
const char *name, const char *path, unsigned output_format,
|
||||
unsigned fps, bool allow_dma, m2m_option_s *options);
|
||||
unsigned fps, unsigned bitrate, unsigned gop, unsigned quality, bool allow_dma);
|
||||
|
||||
static int _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame);
|
||||
static void _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame);
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
@@ -46,26 +46,11 @@ static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, fra
|
||||
|
||||
|
||||
m2m_encoder_s *m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop) {
|
||||
# define OPTION(_key, _value) {#_key, V4L2_CID_MPEG_VIDEO_##_key, _value}
|
||||
|
||||
m2m_option_s options[] = {
|
||||
OPTION(BITRATE, bitrate * 1000),
|
||||
// OPTION(BITRATE_PEAK, bitrate * 1000),
|
||||
OPTION(H264_I_PERIOD, gop),
|
||||
OPTION(H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE),
|
||||
OPTION(H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0),
|
||||
OPTION(REPEAT_SEQ_HEADER, 1),
|
||||
OPTION(H264_MIN_QP, 16),
|
||||
OPTION(H264_MAX_QP, 32),
|
||||
{NULL, 0, 0},
|
||||
};
|
||||
|
||||
# undef OPTION
|
||||
|
||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100
|
||||
// По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480
|
||||
// енкодер через несколько секунд перестает производить корректные фреймы.
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_H264, 30, true, options);
|
||||
bitrate *= 1000; // From Kbps
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_H264, 30, bitrate, gop, 0, true);
|
||||
}
|
||||
|
||||
m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
@@ -76,30 +61,18 @@ m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsign
|
||||
bitrate = step * round(bitrate / step);
|
||||
bitrate *= 1000; // From Kbps
|
||||
assert(bitrate > 0);
|
||||
|
||||
m2m_option_s options[] = {
|
||||
{"BITRATE", V4L2_CID_MPEG_VIDEO_BITRATE, bitrate},
|
||||
{NULL, 0, 0},
|
||||
};
|
||||
|
||||
// FIXME: То же самое про 30 or 0, но еще даже не проверено на низких разрешениях
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, 30, true, options);
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, 30, bitrate, 0, 0, true);
|
||||
}
|
||||
|
||||
m2m_encoder_s *m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
m2m_option_s options[] = {
|
||||
{"QUALITY", V4L2_CID_JPEG_COMPRESSION_QUALITY, quality},
|
||||
{NULL, 0, 0},
|
||||
};
|
||||
|
||||
// FIXME: DMA не работает
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_JPEG, 30, false, options);
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_JPEG, 30, 0, 0, quality, false);
|
||||
}
|
||||
|
||||
void m2m_encoder_destroy(m2m_encoder_s *enc) {
|
||||
E_LOG_INFO("Destroying encoder ...");
|
||||
_m2m_encoder_cleanup(enc);
|
||||
free(enc->options);
|
||||
free(enc->path);
|
||||
free(enc->name);
|
||||
free(enc);
|
||||
@@ -117,9 +90,10 @@ int m2m_encoder_compress(m2m_encoder_s *enc, const frame_s *src, frame_s *dest,
|
||||
|| RUN(stride) != src->stride
|
||||
|| RUN(dma) != (enc->allow_dma && src->dma_fd >= 0)
|
||||
) {
|
||||
if (_m2m_encoder_prepare(enc, src) < 0) {
|
||||
return -1;
|
||||
}
|
||||
_m2m_encoder_prepare(enc, src);
|
||||
}
|
||||
if (!RUN(ready)) { // Already prepared but failed
|
||||
return -1;
|
||||
}
|
||||
|
||||
force_key = (enc->output_format == V4L2_PIX_FMT_H264 && (force_key || RUN(last_online) != src->online));
|
||||
@@ -141,7 +115,7 @@ int m2m_encoder_compress(m2m_encoder_s *enc, const frame_s *src, frame_s *dest,
|
||||
|
||||
static m2m_encoder_s *_m2m_encoder_init(
|
||||
const char *name, const char *path, unsigned output_format,
|
||||
unsigned fps, bool allow_dma, m2m_option_s *options) {
|
||||
unsigned fps, unsigned bitrate, unsigned gop, unsigned quality, bool allow_dma) {
|
||||
|
||||
LOG_INFO("%s: Initializing encoder ...", name);
|
||||
|
||||
@@ -160,15 +134,11 @@ static m2m_encoder_s *_m2m_encoder_init(
|
||||
}
|
||||
enc->output_format = output_format;
|
||||
enc->fps = fps;
|
||||
enc->bitrate = bitrate;
|
||||
enc->gop = gop;
|
||||
enc->quality = quality;
|
||||
enc->allow_dma = allow_dma;
|
||||
enc->run = run;
|
||||
|
||||
unsigned count = 0;
|
||||
for (; options[count].name != NULL; ++count);
|
||||
++count;
|
||||
A_CALLOC(enc->options, count);
|
||||
memcpy(enc->options, options, sizeof(m2m_option_s) * count);
|
||||
|
||||
return enc;
|
||||
}
|
||||
|
||||
@@ -179,7 +149,7 @@ static m2m_encoder_s *_m2m_encoder_init(
|
||||
} \
|
||||
}
|
||||
|
||||
static int _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
static void _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
bool dma = (enc->allow_dma && frame->dma_fd >= 0);
|
||||
|
||||
E_LOG_INFO("Configuring encoder: DMA=%d ...", dma);
|
||||
@@ -198,15 +168,34 @@ static int _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
}
|
||||
E_LOG_DEBUG("Encoder device fd=%d opened", RUN(fd));
|
||||
|
||||
for (m2m_option_s *option = enc->options; option->name != NULL; ++option) {
|
||||
struct v4l2_control ctl = {0};
|
||||
ctl.id = option->id;
|
||||
ctl.value = option->value;
|
||||
# define SET_OPTION(_cid, _value) { \
|
||||
struct v4l2_control _ctl = {0}; \
|
||||
_ctl.id = _cid; \
|
||||
_ctl.value = _value; \
|
||||
E_LOG_DEBUG("Configuring option " #_cid " ..."); \
|
||||
E_XIOCTL(VIDIOC_S_CTRL, &_ctl, "Can't set option " #_cid); \
|
||||
}
|
||||
|
||||
E_LOG_DEBUG("Configuring option %s ...", option->name);
|
||||
E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't set option %s", option->name);
|
||||
if (enc->output_format == V4L2_PIX_FMT_H264) {
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, enc->gop);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE);
|
||||
if (RUN(width) * RUN(height) <= 1920 * 1080) { // https://forums.raspberrypi.com/viewtopic.php?t=291447#p1762296
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
|
||||
} else {
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_5_1);
|
||||
}
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, 1);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 16);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MAX_QP, 32);
|
||||
} else if (enc->output_format == V4L2_PIX_FMT_MJPEG) {
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate);
|
||||
} else if (enc->output_format == V4L2_PIX_FMT_JPEG) {
|
||||
SET_OPTION(V4L2_CID_JPEG_COMPRESSION_QUALITY, enc->quality);
|
||||
}
|
||||
|
||||
# undef SET_OPTION
|
||||
|
||||
{
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
@@ -273,12 +262,11 @@ static int _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
|
||||
RUN(ready) = true;
|
||||
E_LOG_DEBUG("Encoder state: *** READY ***");
|
||||
return 0;
|
||||
return;
|
||||
|
||||
error:
|
||||
_m2m_encoder_cleanup(enc);
|
||||
E_LOG_ERROR("Encoder destroyed due an error (prepare)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
|
||||
#include "xioctl.h"
|
||||
#include "../libs/xioctl.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
@@ -48,12 +47,6 @@ typedef struct {
|
||||
size_t allocated;
|
||||
} m2m_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
uint32_t id;
|
||||
int32_t value;
|
||||
} m2m_option_s;
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
m2m_buffer_s *input_bufs;
|
||||
@@ -76,8 +69,10 @@ typedef struct {
|
||||
char *path;
|
||||
unsigned output_format;
|
||||
unsigned fps;
|
||||
unsigned bitrate;
|
||||
unsigned gop;
|
||||
unsigned quality;
|
||||
bool allow_dma;
|
||||
m2m_option_s *options;
|
||||
|
||||
m2m_encoder_runtime_s *run;
|
||||
} m2m_encoder_s;
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../libs/config.h"
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/process.h"
|
||||
#include "../libs/frame.h"
|
||||
|
||||
Reference in New Issue
Block a user