mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3293d6887 | ||
|
|
9d1a42631e | ||
|
|
13f522e81d | ||
|
|
28f13f7514 | ||
|
|
2f86f818cc | ||
|
|
1ffcd83993 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 5.14
|
||||
current_version = 5.16
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
@@ -103,7 +103,10 @@ static void *_common_thread(void *v_client, bool video) {
|
||||
packet.buffer = (char *)rtp->datagram;
|
||||
packet.length = rtp->used;
|
||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||
if (video) {
|
||||
// FIXME: Это очень эффективный способ уменьшить задержку, но WebRTC стек в хроме и фоксе
|
||||
// слишком корявый, чтобы обработать это, из-за чего на кейфреймах начинаются заикания.
|
||||
// - https://github.com/Glimesh/janus-ftl-plugin/issues/101
|
||||
if (rtp->zero_playout_delay) {
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 0;
|
||||
}
|
||||
|
||||
@@ -23,35 +23,40 @@
|
||||
#include "config.h"
|
||||
|
||||
|
||||
static char *_get_value(janus_config *config, const char *section, const char *option);
|
||||
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
|
||||
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
|
||||
|
||||
|
||||
int read_config(const char *config_dir_path, char **video_sink_name, char **audio_dev_name, char **tc358743_dev_path) {
|
||||
int retval = 0;
|
||||
plugin_config_s *plugin_config_init(const char *config_dir_path) {
|
||||
plugin_config_s *config;
|
||||
A_CALLOC(config, 1);
|
||||
|
||||
char *config_file_path;
|
||||
janus_config *config = NULL;
|
||||
janus_config *jcfg = 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) {
|
||||
jcfg = janus_config_parse(config_file_path);
|
||||
if (jcfg == NULL) {
|
||||
JLOG_ERROR("config", "Can't read config");
|
||||
goto error;
|
||||
}
|
||||
janus_config_print(config);
|
||||
janus_config_print(jcfg);
|
||||
|
||||
if (
|
||||
(*video_sink_name = _get_value(config, "memsink", "object")) == NULL
|
||||
&& (*video_sink_name = _get_value(config, "video", "sink")) == NULL
|
||||
(config->video_sink_name = _get_value(jcfg, "memsink", "object")) == NULL
|
||||
&& (config->video_sink_name = _get_value(jcfg, "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) {
|
||||
if ((config->video_zero_playout_delay = _get_bool(jcfg, "video", "zero_playout_delay", false)) == true) {
|
||||
JLOG_INFO("config", "Enabled the experimental Playout-Delay=0 RTP extension for VIDEO");
|
||||
}
|
||||
if ((config->audio_dev_name = _get_value(jcfg, "audio", "device")) != NULL) {
|
||||
JLOG_INFO("config", "Enabled the experimental AUDIO feature");
|
||||
if ((*tc358743_dev_path = _get_value(config, "audio", "tc358743")) == NULL) {
|
||||
if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) {
|
||||
JLOG_INFO("config", "Missing config value: audio.tc358743");
|
||||
goto error;
|
||||
}
|
||||
@@ -59,20 +64,38 @@ int read_config(const char *config_dir_path, char **video_sink_name, char **audi
|
||||
|
||||
goto ok;
|
||||
error:
|
||||
retval = -1;
|
||||
plugin_config_destroy(config);
|
||||
config = NULL;
|
||||
ok:
|
||||
if (config) {
|
||||
janus_config_destroy(config);
|
||||
if (jcfg) {
|
||||
janus_config_destroy(jcfg);
|
||||
}
|
||||
free(config_file_path);
|
||||
return retval;
|
||||
return config;
|
||||
}
|
||||
|
||||
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);
|
||||
void plugin_config_destroy(plugin_config_s *config) {
|
||||
DELETE(config->video_sink_name, free);
|
||||
DELETE(config->audio_dev_name, free);
|
||||
DELETE(config->tc358743_dev_path, free);
|
||||
free(config);
|
||||
}
|
||||
|
||||
static char *_get_value(janus_config *jcfg, const char *section, const char *option) {
|
||||
janus_config_category *section_obj = janus_config_get_create(jcfg, NULL, janus_config_type_category, section);
|
||||
janus_config_item *option_obj = janus_config_get(jcfg, section_obj, janus_config_type_item, option);
|
||||
if (option_obj == NULL || option_obj->value == NULL || option_obj->value[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
return strdup(option_obj->value);
|
||||
}
|
||||
|
||||
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def) {
|
||||
char *tmp = _get_value(jcfg, section, option);
|
||||
bool value = def;
|
||||
if (tmp != NULL) {
|
||||
value = (!strcasecmp(tmp, "1") || !strcasecmp(tmp, "true") || !strcasecmp(tmp, "yes"));
|
||||
free(tmp);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -34,4 +34,15 @@
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
int read_config(const char *config_dir_path, char **video_sink_name, char **audio_dev_name, char **tc358743_dev_path);
|
||||
typedef struct {
|
||||
char *video_sink_name;
|
||||
bool video_zero_playout_delay;
|
||||
|
||||
char *audio_dev_name;
|
||||
char *tc358743_dev_path;
|
||||
} plugin_config_s;
|
||||
|
||||
|
||||
// config_init() conflicts with something
|
||||
plugin_config_s *plugin_config_init(const char *config_dir_path);
|
||||
void plugin_config_destroy(plugin_config_s *config);
|
||||
|
||||
@@ -55,10 +55,7 @@
|
||||
#include "config.h"
|
||||
|
||||
|
||||
static char *_g_video_sink_name = NULL;
|
||||
static char *_g_audio_dev_name = NULL;
|
||||
static char *_g_tc358743_dev_path = NULL;
|
||||
|
||||
static plugin_config_s *_g_config = NULL;
|
||||
const useconds_t _g_watchers_polling = 100000;
|
||||
|
||||
static client_s *_g_clients = NULL;
|
||||
@@ -136,7 +133,7 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
||||
int fd = -1;
|
||||
memsink_shared_s *mem = NULL;
|
||||
|
||||
if ((fd = shm_open(_g_video_sink_name, O_RDWR, 0)) <= 0) {
|
||||
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Can't open memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
@@ -183,8 +180,8 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
||||
static void *_audio_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_audio");
|
||||
atomic_store(&_g_audio_tid_created, true);
|
||||
assert(_g_audio_dev_name);
|
||||
assert(_g_tc358743_dev_path);
|
||||
assert(_g_config->audio_dev_name);
|
||||
assert(_g_config->tc358743_dev_path);
|
||||
|
||||
unsigned error_reported = 0;
|
||||
|
||||
@@ -197,7 +194,7 @@ static void *_audio_thread(UNUSED void *arg) {
|
||||
tc358743_info_s info = {0};
|
||||
audio_s *audio = NULL;
|
||||
|
||||
if (tc358743_read_info(_g_tc358743_dev_path, &info) < 0) {
|
||||
if (tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0) {
|
||||
goto close_audio;
|
||||
}
|
||||
if (!info.has_audio) {
|
||||
@@ -205,7 +202,7 @@ static void *_audio_thread(UNUSED void *arg) {
|
||||
goto close_audio;
|
||||
}
|
||||
IF_NOT_REPORTED({ JLOG_INFO("audio", "Detected host audio"); });
|
||||
if ((audio = audio_init(_g_audio_dev_name, info.audio_hz)) == NULL) {
|
||||
if ((audio = audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
@@ -213,7 +210,7 @@ static void *_audio_thread(UNUSED void *arg) {
|
||||
|
||||
while (!STOP && HAS_WATCHERS) {
|
||||
if (
|
||||
tc358743_read_info(_g_tc358743_dev_path, &info) < 0
|
||||
tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|
||||
|| !info.has_audio
|
||||
|| audio->pcm_hz != info.audio_hz
|
||||
) {
|
||||
@@ -258,18 +255,14 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
|
||||
// sysctl -w net.core.wmem_max=1000000
|
||||
|
||||
JLOG_INFO("main", "Initializing plugin ...");
|
||||
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) {
|
||||
if (gw == NULL || config_dir_path == NULL || ((_g_config = plugin_config_init(config_dir_path)) == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
_g_gw = gw;
|
||||
|
||||
_g_video_queue = queue_init(1024);
|
||||
_g_rtpv = rtpv_init(_relay_rtp_clients);
|
||||
if (_g_audio_dev_name) {
|
||||
_g_rtpv = rtpv_init(_relay_rtp_clients, _g_config->video_zero_playout_delay);
|
||||
if (_g_config->audio_dev_name) {
|
||||
_g_rtpa = rtpa_init(_relay_rtp_clients);
|
||||
A_THREAD_CREATE(&_g_audio_tid, _audio_thread, NULL);
|
||||
}
|
||||
@@ -297,13 +290,9 @@ static void _plugin_destroy(void) {
|
||||
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(_g_video_queue, frame_destroy);
|
||||
|
||||
# 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
|
||||
DELETE(_g_rtpa, rtpa_destroy);
|
||||
DELETE(_g_rtpv, rtpv_destroy);
|
||||
DELETE(_g_config, plugin_config_destroy);
|
||||
}
|
||||
|
||||
#define IF_DISABLED(...) { if (!READY || STOP) { __VA_ARGS__ } }
|
||||
@@ -312,7 +301,7 @@ static void _plugin_create_session(janus_plugin_session *session, int *err) {
|
||||
IF_DISABLED({ *err = -1; return; });
|
||||
LOCK_ALL;
|
||||
JLOG_INFO("main", "Creating session %p ...", session);
|
||||
client_s *client = client_init(_g_gw, session, (_g_audio_dev_name != NULL));
|
||||
client_s *client = client_init(_g_gw, session, (_g_config->audio_dev_name != NULL));
|
||||
LIST_APPEND(_g_clients, client);
|
||||
atomic_store(&_g_has_watchers, true);
|
||||
UNLOCK_ALL;
|
||||
|
||||
@@ -48,14 +48,16 @@ typedef struct {
|
||||
|
||||
|
||||
#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); \
|
||||
if (_queue) { \
|
||||
while (!queue_get_free(_queue)) { \
|
||||
void *_ptr; \
|
||||
assert(!queue_get(_queue, &_ptr, 0.1)); \
|
||||
if (_ptr != NULL) { \
|
||||
_free_item(_ptr); \
|
||||
} \
|
||||
} \
|
||||
queue_destroy(_queue); \
|
||||
} \
|
||||
queue_destroy(_queue); \
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -26,11 +26,12 @@
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
rtp_s *rtp_init(unsigned payload, bool video) {
|
||||
rtp_s *rtp_init(unsigned payload, bool video, bool zero_playout_delay) {
|
||||
rtp_s *rtp;
|
||||
A_CALLOC(rtp, 1);
|
||||
rtp->payload = payload;
|
||||
rtp->video = video;
|
||||
rtp->zero_playout_delay = zero_playout_delay; // See client.c
|
||||
rtp->ssrc = triple_u32(get_now_monotonic_u64());
|
||||
return rtp;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
typedef struct {
|
||||
unsigned payload;
|
||||
bool video;
|
||||
bool zero_playout_delay;
|
||||
uint32_t ssrc;
|
||||
|
||||
uint16_t seq;
|
||||
@@ -49,7 +50,7 @@ typedef struct {
|
||||
typedef void (*rtp_callback_f)(const rtp_s *rtp);
|
||||
|
||||
|
||||
rtp_s *rtp_init(unsigned payload, bool video);
|
||||
rtp_s *rtp_init(unsigned payload, bool video, bool zero_playout_delay);
|
||||
rtp_s *rtp_dup(const rtp_s *rtp);
|
||||
void rtp_destroy(rtp_s *rtp);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
rtpa_s *rtpa_init(rtp_callback_f callback) {
|
||||
rtpa_s *rtpa;
|
||||
A_CALLOC(rtpa, 1);
|
||||
rtpa->rtp = rtp_init(111, false);
|
||||
rtpa->rtp = rtp_init(111, false, false);
|
||||
rtpa->callback = callback;
|
||||
return rtpa;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
||||
|
||||
|
||||
rtpv_s *rtpv_init(rtp_callback_f callback) {
|
||||
rtpv_s *rtpv_init(rtp_callback_f callback, bool zero_playout_delay) {
|
||||
rtpv_s *rtpv;
|
||||
A_CALLOC(rtpv, 1);
|
||||
rtpv->rtp = rtp_init(96, true);
|
||||
rtpv->rtp = rtp_init(96, true, zero_playout_delay);
|
||||
rtpv->callback = callback;
|
||||
rtpv->sps = frame_init();
|
||||
rtpv->pps = frame_init();
|
||||
@@ -81,13 +81,14 @@ 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
|
||||
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
|
||||
"%s" // playout-delay
|
||||
"a=sendonly" RN,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
PAYLOAD, sps,
|
||||
PAYLOAD, pps,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
rtpv->rtp->ssrc
|
||||
rtpv->rtp->ssrc,
|
||||
(rtpv->rtp->zero_playout_delay ? "a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN : "")
|
||||
);
|
||||
# undef PAYLOAD
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ typedef struct {
|
||||
} rtpv_s;
|
||||
|
||||
|
||||
rtpv_s *rtpv_init(rtp_callback_f callback);
|
||||
rtpv_s *rtpv_init(rtp_callback_f callback, bool zero_playout_delay);
|
||||
void rtpv_destroy(rtpv_s *rtpv);
|
||||
|
||||
char *rtpv_make_sdp(rtpv_s *rtpv);
|
||||
|
||||
@@ -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.14" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 5.16" "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.14" "November 2020"
|
||||
.TH USTREAMER 1 "version 5.16" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=5.14
|
||||
pkgver=5.16
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=5.14
|
||||
PKG_VERSION:=5.16
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ def _find_sources(suffix: str) -> List[str]:
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="5.14",
|
||||
version="5.16",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION_MAJOR 5
|
||||
#define VERSION_MINOR 14
|
||||
#define VERSION_MINOR 16
|
||||
|
||||
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
|
||||
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
|
||||
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
|
||||
#define DELETE(_dest, _free) { if (_dest) { _free(_dest); } }
|
||||
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
|
||||
|
||||
#define A_ASPRINTF(_dest, _fmt, ...) assert(asprintf(&(_dest), _fmt, ##__VA_ARGS__) >= 0)
|
||||
|
||||
Reference in New Issue
Block a user