mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 12:16:31 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf78d8f562 | ||
|
|
77a5dbfeae | ||
|
|
3eebeaeedd | ||
|
|
aba8396d60 | ||
|
|
5b33246b6b | ||
|
|
7f3f480d92 | ||
|
|
86fef47018 | ||
|
|
f09bb1ade9 | ||
|
|
fa030147e8 | ||
|
|
f88333b6bf | ||
|
|
9b4f3229f2 | ||
|
|
900d7e1112 | ||
|
|
c0588c6736 | ||
|
|
5bf8c97a1c | ||
|
|
7335a5d2df | ||
|
|
839804b476 | ||
|
|
5bb223506a | ||
|
|
414f536ace | ||
|
|
c1363d55e0 |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 5.31
|
current_version = 5.34
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|||||||
29
README.md
29
README.md
@@ -34,16 +34,31 @@ Footnotes:
|
|||||||
If you're going to live-stream from your backyard webcam and need to control it, use mjpg-streamer. If you need a high-quality image with high FPS - µStreamer for the win.
|
If you're going to live-stream from your backyard webcam and need to control it, use mjpg-streamer. If you need a high-quality image with high FPS - µStreamer for the win.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Building
|
# Installation
|
||||||
|
|
||||||
|
## Building
|
||||||
|
You need to download the µStreamer onto your system and build it from the sources.
|
||||||
|
|
||||||
|
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer.
|
||||||
|
|
||||||
|
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
||||||
|
|
||||||
|
### Preconditions
|
||||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
|
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
|
||||||
|
|
||||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
|
* Raspbian: `sudo apt install libevent-dev libjpeg9-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
|
||||||
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
||||||
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
|
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
|
||||||
|
|
||||||
To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
|
To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> Raspian: In case your version of Raspian is too told for there to be a libjpeg9 package, use `libjpeg8-dev` instead: `E: Package 'libjpeg9-dev' has no installation candidate`.
|
||||||
|
|
||||||
|
### Make
|
||||||
|
The most convenient process is to clone the µStreamer Git repository onto your system. If you don't have Git installed and don't want to install it either, you can download and unzip the sources from GitHub using `wget https://github.com/pikvm/ustreamer/archive/refs/heads/master.zip`.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone --depth=1 https://github.com/pikvm/ustreamer
|
$ git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||||
$ cd ustreamer
|
$ cd ustreamer
|
||||||
@@ -51,9 +66,15 @@ $ make
|
|||||||
$ ./ustreamer --help
|
$ ./ustreamer --help
|
||||||
```
|
```
|
||||||
|
|
||||||
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer.
|
## Update
|
||||||
|
Assuming you have a µStreamer clone as discussed above you can update µStreamer as follows.
|
||||||
|
|
||||||
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
```
|
||||||
|
$ cd ustreamer
|
||||||
|
$ git pull
|
||||||
|
$ make clean
|
||||||
|
$ make
|
||||||
|
```
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Usage
|
# Usage
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ The client-side JavaScript application uses the following control flow:
|
|||||||
success: function (pluginHandle) {
|
success: function (pluginHandle) {
|
||||||
uStreamerPluginHandle = pluginHandle;
|
uStreamerPluginHandle = pluginHandle;
|
||||||
// Instruct the µStreamer Janus plugin to initiate streaming.
|
// Instruct the µStreamer Janus plugin to initiate streaming.
|
||||||
uStreamerPluginHandle.send({ message: { request: "watch" } });
|
uStreamerPluginHandle.send({ message: { request: "watch", params: {audio: true} } });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Callback function if the server fails to attach the plugin.
|
// Callback function if the server fails to attach the plugin.
|
||||||
@@ -197,13 +197,6 @@ The client-side JavaScript application uses the following control flow:
|
|||||||
|
|
||||||
// Callback function for processing messages from the Janus server.
|
// Callback function for processing messages from the Janus server.
|
||||||
onmessage: function (msg, jsepOffer) {
|
onmessage: function (msg, jsepOffer) {
|
||||||
// 503 indicates that the plugin is not ready to stream yet. Retry the
|
|
||||||
// watch request until the video stream is available.
|
|
||||||
if (msg.error_code === 503) {
|
|
||||||
uStreamerPluginHandle.send({ message: { request: "watch" } });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a JSEP offer, respond to it. This starts the WebRTC
|
// If there is a JSEP offer, respond to it. This starts the WebRTC
|
||||||
// connection.
|
// connection.
|
||||||
if (jsepOffer) {
|
if (jsepOffer) {
|
||||||
|
|||||||
@@ -55,6 +55,19 @@ static void *_pcm_thread(void *v_audio);
|
|||||||
static void *_encoder_thread(void *v_audio);
|
static void *_encoder_thread(void *v_audio);
|
||||||
|
|
||||||
|
|
||||||
|
bool us_audio_probe(const char *name) {
|
||||||
|
snd_pcm_t *pcm;
|
||||||
|
int err;
|
||||||
|
US_JLOG_INFO("audio", "Probing PCM capture ...");
|
||||||
|
if ((err = snd_pcm_open(&pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
||||||
|
_JLOG_PERROR_ALSA(err, "audio", "Can't probe PCM capture");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
snd_pcm_close(pcm);
|
||||||
|
US_JLOG_INFO("audio", "PCM capture is available");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz) {
|
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz) {
|
||||||
us_audio_s *audio;
|
us_audio_s *audio;
|
||||||
US_CALLOC(audio, 1);
|
US_CALLOC(audio, 1);
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ typedef struct {
|
|||||||
SpeexResamplerState *res;
|
SpeexResamplerState *res;
|
||||||
OpusEncoder *enc;
|
OpusEncoder *enc;
|
||||||
|
|
||||||
us_queue_s *pcm_queue;
|
us_queue_s *pcm_queue;
|
||||||
us_queue_s *enc_queue;
|
us_queue_s *enc_queue;
|
||||||
uint32_t pts;
|
uint32_t pts;
|
||||||
|
|
||||||
pthread_t pcm_tid;
|
pthread_t pcm_tid;
|
||||||
@@ -63,6 +63,8 @@ typedef struct {
|
|||||||
} us_audio_s;
|
} us_audio_s;
|
||||||
|
|
||||||
|
|
||||||
|
bool us_audio_probe(const char *name);
|
||||||
|
|
||||||
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz);
|
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz);
|
||||||
void us_audio_destroy(us_audio_s *audio);
|
void us_audio_destroy(us_audio_s *audio);
|
||||||
|
|
||||||
|
|||||||
@@ -28,53 +28,50 @@ static void *_audio_thread(void *v_client);
|
|||||||
static void *_common_thread(void *v_client, bool video);
|
static void *_common_thread(void *v_client, bool video);
|
||||||
|
|
||||||
|
|
||||||
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio) {
|
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session) {
|
||||||
us_janus_client_s *client;
|
us_janus_client_s *client;
|
||||||
US_CALLOC(client, 1);
|
US_CALLOC(client, 1);
|
||||||
client->gw = gw;
|
client->gw = gw;
|
||||||
client->session = session;
|
client->session = session;
|
||||||
atomic_init(&client->transmit, true);
|
atomic_init(&client->transmit, false);
|
||||||
|
atomic_init(&client->transmit_audio, false);
|
||||||
|
|
||||||
atomic_init(&client->stop, false);
|
atomic_init(&client->stop, false);
|
||||||
|
|
||||||
client->video_queue = us_queue_init(1024);
|
client->video_queue = us_queue_init(1024);
|
||||||
US_THREAD_CREATE(client->video_tid, _video_thread, client);
|
US_THREAD_CREATE(client->video_tid, _video_thread, client);
|
||||||
|
|
||||||
if (has_audio) {
|
client->audio_queue = us_queue_init(64);
|
||||||
client->audio_queue = us_queue_init(64);
|
US_THREAD_CREATE(client->audio_tid, _audio_thread, client);
|
||||||
US_THREAD_CREATE(client->audio_tid, _audio_thread, client);
|
|
||||||
}
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
void us_janus_client_destroy(us_janus_client_s *client) {
|
void us_janus_client_destroy(us_janus_client_s *client) {
|
||||||
atomic_store(&client->stop, true);
|
atomic_store(&client->stop, true);
|
||||||
us_queue_put(client->video_queue, NULL, 0);
|
us_queue_put(client->video_queue, NULL, 0);
|
||||||
if (client->audio_queue != NULL) {
|
us_queue_put(client->audio_queue, NULL, 0);
|
||||||
us_queue_put(client->audio_queue, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
US_THREAD_JOIN(client->video_tid);
|
US_THREAD_JOIN(client->video_tid);
|
||||||
US_QUEUE_DELETE_WITH_ITEMS(client->video_queue, us_rtp_destroy);
|
US_QUEUE_DELETE_WITH_ITEMS(client->video_queue, us_rtp_destroy);
|
||||||
if (client->audio_queue != NULL) {
|
|
||||||
US_THREAD_JOIN(client->audio_tid);
|
US_THREAD_JOIN(client->audio_tid);
|
||||||
US_QUEUE_DELETE_WITH_ITEMS(client->audio_queue, us_rtp_destroy);
|
US_QUEUE_DELETE_WITH_ITEMS(client->audio_queue, us_rtp_destroy);
|
||||||
}
|
|
||||||
free(client);
|
free(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
|
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
|
||||||
if (
|
if (
|
||||||
!atomic_load(&client->transmit)
|
atomic_load(&client->transmit)
|
||||||
|| (!rtp->video && client->audio_queue == NULL)
|
&& (rtp->video || atomic_load(&client->transmit_audio))
|
||||||
) {
|
) {
|
||||||
return;
|
us_rtp_s *const new = us_rtp_dup(rtp);
|
||||||
}
|
if (us_queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
|
||||||
us_rtp_s *const new = us_rtp_dup(rtp);
|
US_JLOG_ERROR("client", "Session %p %s queue is full",
|
||||||
if (us_queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
|
client->session, (new->video ? "video" : "audio"));
|
||||||
US_JLOG_ERROR("client", "Session %p %s queue is full",
|
us_rtp_destroy(new);
|
||||||
client->session, (new->video ? "video" : "audio"));
|
}
|
||||||
us_rtp_destroy(new);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +94,11 @@ static void *_common_thread(void *v_client, bool video) {
|
|||||||
if (rtp == NULL) {
|
if (rtp == NULL) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (atomic_load(&client->transmit)) {
|
|
||||||
|
if (
|
||||||
|
atomic_load(&client->transmit)
|
||||||
|
&& (video || atomic_load(&client->transmit_audio))
|
||||||
|
) {
|
||||||
janus_plugin_rtp packet = {0};
|
janus_plugin_rtp packet = {0};
|
||||||
packet.video = rtp->video;
|
packet.video = rtp->video;
|
||||||
packet.buffer = (char *)rtp->datagram;
|
packet.buffer = (char *)rtp->datagram;
|
||||||
@@ -107,14 +108,17 @@ static void *_common_thread(void *v_client, bool video) {
|
|||||||
// (if available) in stream index 1.
|
// (if available) in stream index 1.
|
||||||
packet.mindex = (rtp->video ? 0 : 1);
|
packet.mindex = (rtp->video ? 0 : 1);
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||||
// FIXME: Это очень эффективный способ уменьшить задержку, но WebRTC стек в хроме и фоксе
|
/*if (rtp->zero_playout_delay) {
|
||||||
// слишком корявый, чтобы обработать это, из-за чего на кейфреймах начинаются заикания.
|
// https://github.com/pikvm/pikvm/issues/784
|
||||||
// - https://github.com/Glimesh/janus-ftl-plugin/issues/101
|
|
||||||
if (rtp->zero_playout_delay) {
|
|
||||||
packet.extensions.min_delay = 0;
|
packet.extensions.min_delay = 0;
|
||||||
packet.extensions.max_delay = 0;
|
packet.extensions.max_delay = 0;
|
||||||
}
|
} else {
|
||||||
|
packet.extensions.min_delay = 0;
|
||||||
|
packet.extensions.max_delay = 1000;
|
||||||
|
}*/
|
||||||
|
|
||||||
client->gw->relay_rtp(client->session, &packet);
|
client->gw->relay_rtp(client->session, &packet);
|
||||||
}
|
}
|
||||||
us_rtp_destroy(rtp);
|
us_rtp_destroy(rtp);
|
||||||
|
|||||||
@@ -41,21 +41,22 @@
|
|||||||
|
|
||||||
typedef struct us_janus_client_sx {
|
typedef struct us_janus_client_sx {
|
||||||
janus_callbacks *gw;
|
janus_callbacks *gw;
|
||||||
janus_plugin_session *session;
|
janus_plugin_session *session;
|
||||||
atomic_bool transmit;
|
atomic_bool transmit;
|
||||||
|
atomic_bool transmit_audio;
|
||||||
|
|
||||||
pthread_t video_tid;
|
pthread_t video_tid;
|
||||||
pthread_t audio_tid;
|
pthread_t audio_tid;
|
||||||
atomic_bool stop;
|
atomic_bool stop;
|
||||||
|
|
||||||
us_queue_s *video_queue;
|
us_queue_s *video_queue;
|
||||||
us_queue_s *audio_queue;
|
us_queue_s *audio_queue;
|
||||||
|
|
||||||
US_LIST_STRUCT(struct us_janus_client_sx);
|
US_LIST_STRUCT(struct us_janus_client_sx);
|
||||||
} us_janus_client_s;
|
} us_janus_client_s;
|
||||||
|
|
||||||
|
|
||||||
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio);
|
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session);
|
||||||
void us_janus_client_destroy(us_janus_client_s *client);
|
void us_janus_client_destroy(us_janus_client_s *client);
|
||||||
|
|
||||||
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp);
|
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
|
|
||||||
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
|
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
|
||||||
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
|
// static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
|
||||||
|
|
||||||
|
|
||||||
us_config_s *us_config_init(const char *config_dir_path) {
|
us_config_s *us_config_init(const char *config_dir_path) {
|
||||||
@@ -51,11 +51,7 @@ us_config_s *us_config_init(const char *config_dir_path) {
|
|||||||
US_JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
|
US_JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if ((config->video_zero_playout_delay = _get_bool(jcfg, "video", "zero_playout_delay", false)) == true) {
|
|
||||||
US_JLOG_INFO("config", "Enabled the experimental Playout-Delay=0 RTP extension for VIDEO");
|
|
||||||
}
|
|
||||||
if ((config->audio_dev_name = _get_value(jcfg, "audio", "device")) != NULL) {
|
if ((config->audio_dev_name = _get_value(jcfg, "audio", "device")) != NULL) {
|
||||||
US_JLOG_INFO("config", "Enabled the experimental AUDIO feature");
|
|
||||||
if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) {
|
if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) {
|
||||||
US_JLOG_INFO("config", "Missing config value: audio.tc358743");
|
US_JLOG_INFO("config", "Missing config value: audio.tc358743");
|
||||||
goto error;
|
goto error;
|
||||||
@@ -88,7 +84,7 @@ static char *_get_value(janus_config *jcfg, const char *section, const char *opt
|
|||||||
return us_strdup(option_obj->value);
|
return us_strdup(option_obj->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def) {
|
/*static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def) {
|
||||||
char *const tmp = _get_value(jcfg, section, option);
|
char *const tmp = _get_value(jcfg, section, option);
|
||||||
bool value = def;
|
bool value = def;
|
||||||
if (tmp != NULL) {
|
if (tmp != NULL) {
|
||||||
@@ -96,4 +92,4 @@ static bool _get_bool(janus_config *jcfg, const char *section, const char *optio
|
|||||||
free(tmp);
|
free(tmp);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}*/
|
||||||
|
|||||||
@@ -36,7 +36,6 @@
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *video_sink_name;
|
char *video_sink_name;
|
||||||
bool video_zero_playout_delay;
|
|
||||||
|
|
||||||
char *audio_dev_name;
|
char *audio_dev_name;
|
||||||
char *tc358743_dev_path;
|
char *tc358743_dev_path;
|
||||||
|
|||||||
@@ -36,3 +36,11 @@
|
|||||||
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_str); \
|
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_str); \
|
||||||
free(m_perror_str); \
|
free(m_perror_str); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define US_ONCE(...) { \
|
||||||
|
const int m_reported = __LINE__; \
|
||||||
|
if (m_reported != once) { \
|
||||||
|
__VA_ARGS__; \
|
||||||
|
once = m_reported; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ static pthread_mutex_t _g_audio_lock = PTHREAD_MUTEX_INITIALIZER;
|
|||||||
static atomic_bool _g_ready = false;
|
static atomic_bool _g_ready = false;
|
||||||
static atomic_bool _g_stop = false;
|
static atomic_bool _g_stop = false;
|
||||||
static atomic_bool _g_has_watchers = false;
|
static atomic_bool _g_has_watchers = false;
|
||||||
|
static atomic_bool _g_has_listeners = false;
|
||||||
static atomic_bool _g_key_required = false;
|
static atomic_bool _g_key_required = false;
|
||||||
|
|
||||||
|
|
||||||
@@ -92,16 +93,12 @@ static atomic_bool _g_key_required = false;
|
|||||||
#define _READY atomic_load(&_g_ready)
|
#define _READY atomic_load(&_g_ready)
|
||||||
#define _STOP atomic_load(&_g_stop)
|
#define _STOP atomic_load(&_g_stop)
|
||||||
#define _HAS_WATCHERS atomic_load(&_g_has_watchers)
|
#define _HAS_WATCHERS atomic_load(&_g_has_watchers)
|
||||||
|
#define _HAS_LISTENERS atomic_load(&_g_has_listeners)
|
||||||
|
|
||||||
|
|
||||||
janus_plugin *create(void);
|
janus_plugin *create(void);
|
||||||
|
|
||||||
|
|
||||||
#define _IF_NOT_REPORTED(...) { \
|
|
||||||
const unsigned _error_code = __LINE__; \
|
|
||||||
if (error_reported != _error_code) { __VA_ARGS__; error_reported = _error_code; } \
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *_video_rtp_thread(UNUSED void *arg) {
|
static void *_video_rtp_thread(UNUSED void *arg) {
|
||||||
US_THREAD_RENAME("us_video_rtp");
|
US_THREAD_RENAME("us_video_rtp");
|
||||||
atomic_store(&_g_video_rtp_tid_created, true);
|
atomic_store(&_g_video_rtp_tid_created, true);
|
||||||
@@ -123,11 +120,11 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
|||||||
atomic_store(&_g_video_sink_tid_created, true);
|
atomic_store(&_g_video_sink_tid_created, true);
|
||||||
|
|
||||||
uint64_t frame_id = 0;
|
uint64_t frame_id = 0;
|
||||||
unsigned error_reported = 0;
|
int once = 0;
|
||||||
|
|
||||||
while (!_STOP) {
|
while (!_STOP) {
|
||||||
if (!_HAS_WATCHERS) {
|
if (!_HAS_WATCHERS) {
|
||||||
_IF_NOT_REPORTED({ US_JLOG_INFO("video", "No active watchers, memsink disconnected"); });
|
US_ONCE({ US_JLOG_INFO("video", "No active watchers, memsink disconnected"); });
|
||||||
usleep(_g_watchers_polling);
|
usleep(_g_watchers_polling);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -136,16 +133,16 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
|||||||
us_memsink_shared_s *mem = NULL;
|
us_memsink_shared_s *mem = NULL;
|
||||||
|
|
||||||
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
|
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
|
||||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't open memsink"); });
|
US_ONCE({ US_JLOG_PERROR("video", "Can't open memsink"); });
|
||||||
goto close_memsink;
|
goto close_memsink;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((mem = us_memsink_shared_map(fd)) == NULL) {
|
if ((mem = us_memsink_shared_map(fd)) == NULL) {
|
||||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't map memsink"); });
|
US_ONCE({ US_JLOG_PERROR("video", "Can't map memsink"); });
|
||||||
goto close_memsink;
|
goto close_memsink;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_reported = 0;
|
once = 0;
|
||||||
|
|
||||||
US_JLOG_INFO("video", "Memsink opened; reading frames ...");
|
US_JLOG_INFO("video", "Memsink opened; reading frames ...");
|
||||||
while (!_STOP && _HAS_WATCHERS) {
|
while (!_STOP && _HAS_WATCHERS) {
|
||||||
@@ -159,7 +156,7 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
|||||||
atomic_store(&_g_key_required, false);
|
atomic_store(&_g_key_required, false);
|
||||||
}
|
}
|
||||||
if (us_queue_put(_g_video_queue, frame, 0) != 0) {
|
if (us_queue_put(_g_video_queue, frame, 0) != 0) {
|
||||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Video queue is full"); });
|
US_ONCE({ US_JLOG_PERROR("video", "Video queue is full"); });
|
||||||
us_frame_destroy(frame);
|
us_frame_destroy(frame);
|
||||||
}
|
}
|
||||||
} else if (result == -1) {
|
} else if (result == -1) {
|
||||||
@@ -186,10 +183,10 @@ static void *_audio_thread(UNUSED void *arg) {
|
|||||||
assert(_g_config->audio_dev_name != NULL);
|
assert(_g_config->audio_dev_name != NULL);
|
||||||
assert(_g_config->tc358743_dev_path != NULL);
|
assert(_g_config->tc358743_dev_path != NULL);
|
||||||
|
|
||||||
unsigned error_reported = 0;
|
int once = 0;
|
||||||
|
|
||||||
while (!_STOP) {
|
while (!_STOP) {
|
||||||
if (!_HAS_WATCHERS) {
|
if (!_HAS_WATCHERS || !_HAS_LISTENERS) {
|
||||||
usleep(_g_watchers_polling);
|
usleep(_g_watchers_polling);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -201,17 +198,17 @@ static void *_audio_thread(UNUSED void *arg) {
|
|||||||
goto close_audio;
|
goto close_audio;
|
||||||
}
|
}
|
||||||
if (!info.has_audio) {
|
if (!info.has_audio) {
|
||||||
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "No audio presented from the host"); });
|
US_ONCE({ US_JLOG_INFO("audio", "No audio presented from the host"); });
|
||||||
goto close_audio;
|
goto close_audio;
|
||||||
}
|
}
|
||||||
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "Detected host audio"); });
|
US_ONCE({ US_JLOG_INFO("audio", "Detected host audio"); });
|
||||||
if ((audio = us_audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
|
if ((audio = us_audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
|
||||||
goto close_audio;
|
goto close_audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_reported = 0;
|
once = 0;
|
||||||
|
|
||||||
while (!_STOP && _HAS_WATCHERS) {
|
while (!_STOP && _HAS_WATCHERS && _HAS_LISTENERS) {
|
||||||
if (
|
if (
|
||||||
us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|
us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|
||||||
|| !info.has_audio
|
|| !info.has_audio
|
||||||
@@ -240,8 +237,6 @@ static void *_audio_thread(UNUSED void *arg) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef _IF_NOT_REPORTED
|
|
||||||
|
|
||||||
static void _relay_rtp_clients(const us_rtp_s *rtp) {
|
static void _relay_rtp_clients(const us_rtp_s *rtp) {
|
||||||
US_LIST_ITERATE(_g_clients, client, {
|
US_LIST_ITERATE(_g_clients, client, {
|
||||||
us_janus_client_send(client, rtp);
|
us_janus_client_send(client, rtp);
|
||||||
@@ -262,8 +257,8 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
|
|||||||
_g_gw = gw;
|
_g_gw = gw;
|
||||||
|
|
||||||
_g_video_queue = us_queue_init(1024);
|
_g_video_queue = us_queue_init(1024);
|
||||||
_g_rtpv = us_rtpv_init(_relay_rtp_clients, _g_config->video_zero_playout_delay);
|
_g_rtpv = us_rtpv_init(_relay_rtp_clients);
|
||||||
if (_g_config->audio_dev_name != NULL) {
|
if (_g_config->audio_dev_name != NULL && us_audio_probe(_g_config->audio_dev_name)) {
|
||||||
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
|
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
|
||||||
US_THREAD_CREATE(_g_audio_tid, _audio_thread, NULL);
|
US_THREAD_CREATE(_g_audio_tid, _audio_thread, NULL);
|
||||||
}
|
}
|
||||||
@@ -302,7 +297,7 @@ static void _plugin_create_session(janus_plugin_session *session, int *err) {
|
|||||||
_IF_DISABLED({ *err = -1; return; });
|
_IF_DISABLED({ *err = -1; return; });
|
||||||
_LOCK_ALL;
|
_LOCK_ALL;
|
||||||
US_JLOG_INFO("main", "Creating session %p ...", session);
|
US_JLOG_INFO("main", "Creating session %p ...", session);
|
||||||
us_janus_client_s *const client = us_janus_client_init(_g_gw, session, (_g_config->audio_dev_name != NULL));
|
us_janus_client_s *const client = us_janus_client_init(_g_gw, session);
|
||||||
US_LIST_APPEND(_g_clients, client);
|
US_LIST_APPEND(_g_clients, client);
|
||||||
atomic_store(&_g_has_watchers, true);
|
atomic_store(&_g_has_watchers, true);
|
||||||
_UNLOCK_ALL;
|
_UNLOCK_ALL;
|
||||||
@@ -313,6 +308,7 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
|
|||||||
_LOCK_ALL;
|
_LOCK_ALL;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
bool has_watchers = false;
|
bool has_watchers = false;
|
||||||
|
bool has_listeners = false;
|
||||||
US_LIST_ITERATE(_g_clients, client, {
|
US_LIST_ITERATE(_g_clients, client, {
|
||||||
if (client->session == session) {
|
if (client->session == session) {
|
||||||
US_JLOG_INFO("main", "Removing session %p ...", session);
|
US_JLOG_INFO("main", "Removing session %p ...", session);
|
||||||
@@ -321,6 +317,7 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
|
|||||||
found = true;
|
found = true;
|
||||||
} else {
|
} else {
|
||||||
has_watchers = (has_watchers || atomic_load(&client->transmit));
|
has_watchers = (has_watchers || atomic_load(&client->transmit));
|
||||||
|
has_listeners = (has_listeners || atomic_load(&client->transmit_audio));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!found) {
|
if (!found) {
|
||||||
@@ -328,6 +325,7 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
|
|||||||
*err = -2;
|
*err = -2;
|
||||||
}
|
}
|
||||||
atomic_store(&_g_has_watchers, has_watchers);
|
atomic_store(&_g_has_watchers, has_watchers);
|
||||||
|
atomic_store(&_g_has_listeners, has_listeners);
|
||||||
_UNLOCK_ALL;
|
_UNLOCK_ALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,40 +394,54 @@ static struct janus_plugin_result *_plugin_handle_message(
|
|||||||
json_decref(m_event); \
|
json_decref(m_event); \
|
||||||
}
|
}
|
||||||
|
|
||||||
json_t *const request_obj = json_object_get(msg, "request");
|
json_t *const request = json_object_get(msg, "request");
|
||||||
if (request_obj == NULL) {
|
if (request == NULL) {
|
||||||
PUSH_ERROR(400, "Request missing");
|
PUSH_ERROR(400, "Request missing");
|
||||||
goto ok_wait;
|
goto ok_wait;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *const request_str = json_string_value(request_obj);
|
const char *const request_str = json_string_value(request);
|
||||||
if (request_str == NULL) {
|
if (request_str == NULL) {
|
||||||
PUSH_ERROR(400, "Request not a string");
|
PUSH_ERROR(400, "Request not a string");
|
||||||
goto ok_wait;
|
goto ok_wait;
|
||||||
}
|
}
|
||||||
// US_JLOG_INFO("main", "Message: %s", request_str);
|
// US_JLOG_INFO("main", "Message: %s", request_str);
|
||||||
|
|
||||||
# define PUSH_STATUS(x_status, x_jsep) { \
|
# define PUSH_STATUS(x_status, x_payload, x_jsep) { \
|
||||||
json_t *const m_event = json_object(); \
|
json_t *const m_event = json_object(); \
|
||||||
json_object_set_new(m_event, "ustreamer", json_string("event")); \
|
json_object_set_new(m_event, "ustreamer", json_string("event")); \
|
||||||
json_t *const m_result = json_object(); \
|
json_t *const m_result = json_object(); \
|
||||||
json_object_set_new(m_result, "status", json_string(x_status)); \
|
json_object_set_new(m_result, "status", json_string(x_status)); \
|
||||||
|
if (x_payload != NULL) { \
|
||||||
|
json_object_set_new(m_result, x_status, x_payload); \
|
||||||
|
} \
|
||||||
json_object_set_new(m_event, "result", m_result); \
|
json_object_set_new(m_event, "result", m_result); \
|
||||||
_g_gw->push_event(session, create(), transaction, m_event, x_jsep); \
|
_g_gw->push_event(session, create(), transaction, m_event, x_jsep); \
|
||||||
json_decref(m_event); \
|
json_decref(m_event); \
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp(request_str, "start")) {
|
if (!strcmp(request_str, "start")) {
|
||||||
PUSH_STATUS("started", NULL);
|
PUSH_STATUS("started", NULL, NULL);
|
||||||
|
|
||||||
} else if (!strcmp(request_str, "stop")) {
|
} else if (!strcmp(request_str, "stop")) {
|
||||||
PUSH_STATUS("stopped", NULL);
|
PUSH_STATUS("stopped", NULL, NULL);
|
||||||
|
|
||||||
} else if (!strcmp(request_str, "watch")) {
|
} else if (!strcmp(request_str, "watch")) {
|
||||||
char *sdp;
|
bool with_audio = false;
|
||||||
{
|
{
|
||||||
|
json_t *const params = json_object_get(msg, "params");
|
||||||
|
if (params != NULL) {
|
||||||
|
json_t *const audio = json_object_get(params, "audio");
|
||||||
|
if (audio != NULL && json_is_boolean(audio)) {
|
||||||
|
with_audio = (_g_rtpa != NULL && json_boolean_value(audio));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
char *sdp;
|
||||||
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
|
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
|
||||||
char *const audio_sdp = (_g_rtpa ? us_rtpa_make_sdp(_g_rtpa) : us_strdup(""));
|
char *const audio_sdp = (with_audio ? us_rtpa_make_sdp(_g_rtpa) : us_strdup(""));
|
||||||
US_ASPRINTF(sdp,
|
US_ASPRINTF(sdp,
|
||||||
"v=0" RN
|
"v=0" RN
|
||||||
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
|
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
|
||||||
@@ -447,13 +459,31 @@ static struct janus_plugin_result *_plugin_handle_message(
|
|||||||
audio_sdp, video_sdp
|
audio_sdp, video_sdp
|
||||||
# endif
|
# endif
|
||||||
);
|
);
|
||||||
|
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
|
||||||
|
PUSH_STATUS("started", NULL, offer_jsep);
|
||||||
|
json_decref(offer_jsep);
|
||||||
free(audio_sdp);
|
free(audio_sdp);
|
||||||
free(video_sdp);
|
free(video_sdp);
|
||||||
|
free(sdp);
|
||||||
}
|
}
|
||||||
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
|
|
||||||
free(sdp);
|
{
|
||||||
PUSH_STATUS("started", offer_jsep);
|
_LOCK_ALL;
|
||||||
json_decref(offer_jsep);
|
bool has_listeners = false;
|
||||||
|
US_LIST_ITERATE(_g_clients, client, {
|
||||||
|
if (client->session == session) {
|
||||||
|
atomic_store(&client->transmit_audio, with_audio);
|
||||||
|
}
|
||||||
|
has_listeners = (has_listeners || atomic_load(&client->transmit_audio));
|
||||||
|
});
|
||||||
|
atomic_store(&_g_has_listeners, has_listeners);
|
||||||
|
_UNLOCK_ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (!strcmp(request_str, "features")) {
|
||||||
|
json_t *const features = json_pack("{sb}", "audio", (_g_rtpa != NULL));
|
||||||
|
PUSH_STATUS("features", features, NULL);
|
||||||
|
json_decref(features);
|
||||||
|
|
||||||
} else if (!strcmp(request_str, "key_required")) {
|
} else if (!strcmp(request_str, "key_required")) {
|
||||||
// US_JLOG_INFO("main", "Got key_required message");
|
// US_JLOG_INFO("main", "Got key_required message");
|
||||||
|
|||||||
@@ -26,12 +26,11 @@
|
|||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
|
|
||||||
|
|
||||||
us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay) {
|
us_rtp_s *us_rtp_init(unsigned payload, bool video) {
|
||||||
us_rtp_s *rtp;
|
us_rtp_s *rtp;
|
||||||
US_CALLOC(rtp, 1);
|
US_CALLOC(rtp, 1);
|
||||||
rtp->payload = payload;
|
rtp->payload = payload;
|
||||||
rtp->video = video;
|
rtp->video = video;
|
||||||
rtp->zero_playout_delay = zero_playout_delay; // See client.c
|
|
||||||
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
|
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
|
||||||
return rtp;
|
return rtp;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,18 +39,18 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned payload;
|
unsigned payload;
|
||||||
bool video;
|
bool video;
|
||||||
bool zero_playout_delay;
|
|
||||||
uint32_t ssrc;
|
uint32_t ssrc;
|
||||||
|
|
||||||
uint16_t seq;
|
uint16_t seq;
|
||||||
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
|
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
|
||||||
size_t used;
|
size_t used;
|
||||||
|
bool zero_playout_delay;
|
||||||
} us_rtp_s;
|
} us_rtp_s;
|
||||||
|
|
||||||
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
|
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
|
||||||
|
|
||||||
|
|
||||||
us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay);
|
us_rtp_s *us_rtp_init(unsigned payload, bool video);
|
||||||
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
|
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
|
||||||
void us_rtp_destroy(us_rtp_s *rtp);
|
void us_rtp_destroy(us_rtp_s *rtp);
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
|
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
|
||||||
us_rtpa_s *rtpa;
|
us_rtpa_s *rtpa;
|
||||||
US_CALLOC(rtpa, 1);
|
US_CALLOC(rtpa, 1);
|
||||||
rtpa->rtp = us_rtp_init(111, false, false);
|
rtpa->rtp = us_rtp_init(111, false);
|
||||||
rtpa->callback = callback;
|
rtpa->callback = callback;
|
||||||
return rtpa;
|
return rtpa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint3
|
|||||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
||||||
|
|
||||||
|
|
||||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay) {
|
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback) {
|
||||||
us_rtpv_s *rtpv;
|
us_rtpv_s *rtpv;
|
||||||
US_CALLOC(rtpv, 1);
|
US_CALLOC(rtpv, 1);
|
||||||
rtpv->rtp = us_rtp_init(96, true, zero_playout_delay);
|
rtpv->rtp = us_rtp_init(96, true);
|
||||||
rtpv->callback = callback;
|
rtpv->callback = callback;
|
||||||
return rtpv;
|
return rtpv;
|
||||||
}
|
}
|
||||||
@@ -59,12 +59,11 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
|||||||
"a=rtcp-fb:%u nack pli" RN
|
"a=rtcp-fb:%u nack pli" RN
|
||||||
"a=rtcp-fb:%u goog-remb" RN
|
"a=rtcp-fb:%u goog-remb" RN
|
||||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||||
"%s" // playout-delay
|
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
|
||||||
"a=sendonly" RN,
|
"a=sendonly" RN,
|
||||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
||||||
PAYLOAD, PAYLOAD, PAYLOAD,
|
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 : "")
|
|
||||||
);
|
);
|
||||||
return sdp;
|
return sdp;
|
||||||
# undef PAYLOAD
|
# undef PAYLOAD
|
||||||
@@ -78,6 +77,8 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
|
|||||||
|
|
||||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||||
|
|
||||||
|
rtpv->rtp->zero_playout_delay = (frame->gop == 0);
|
||||||
|
|
||||||
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||||
ssize_t last_offset = -_PRE;
|
ssize_t last_offset = -_PRE;
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ typedef struct {
|
|||||||
} us_rtpv_s;
|
} us_rtpv_s;
|
||||||
|
|
||||||
|
|
||||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay);
|
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback);
|
||||||
void us_rtpv_destroy(us_rtpv_s *rtpv);
|
void us_rtpv_destroy(us_rtpv_s *rtpv);
|
||||||
|
|
||||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
|
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ commands = cppcheck \
|
|||||||
whitelist_externals = bash
|
whitelist_externals = bash
|
||||||
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py
|
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py
|
||||||
deps =
|
deps =
|
||||||
flake8
|
flake8==5.0.4
|
||||||
flake8-quotes
|
flake8-quotes
|
||||||
|
|
||||||
[testenv:pylint]
|
[testenv:pylint]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" Manpage for ustreamer-dump.
|
.\" Manpage for ustreamer-dump.
|
||||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||||
.TH USTREAMER-DUMP 1 "version 5.31" "January 2021"
|
.TH USTREAMER-DUMP 1 "version 5.34" "January 2021"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" Manpage for ustreamer.
|
.\" Manpage for ustreamer.
|
||||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||||
.TH USTREAMER 1 "version 5.31" "November 2020"
|
.TH USTREAMER 1 "version 5.34" "November 2020"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=5.31
|
pkgver=5.34
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||||
url="https://github.com/pikvm/ustreamer"
|
url="https://github.com/pikvm/ustreamer"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ RUN apk add --no-cache \
|
|||||||
WORKDIR /ustreamer
|
WORKDIR /ustreamer
|
||||||
COPY --from=build /build/ustreamer/src/ustreamer.bin ustreamer
|
COPY --from=build /build/ustreamer/src/ustreamer.bin ustreamer
|
||||||
|
|
||||||
RUN wget https://raw.githubusercontent.com/pikvm/kvmd/master/configs/kvmd/tc358743-edid.hex -O /edid.hex
|
RUN wget https://raw.githubusercontent.com/pikvm/kvmd/master/configs/kvmd/edid/v3-hdmi.hex -O /edid.hex
|
||||||
COPY pkg/docker/entry.sh /
|
COPY pkg/docker/entry.sh /
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ustreamer
|
PKG_NAME:=ustreamer
|
||||||
PKG_VERSION:=5.31
|
PKG_VERSION:=5.34
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
setup(
|
setup(
|
||||||
name="ustreamer",
|
name="ustreamer",
|
||||||
version="5.31",
|
version="5.34",
|
||||||
description="uStreamer tools",
|
description="uStreamer tools",
|
||||||
author="Maxim Devaev",
|
author="Maxim Devaev",
|
||||||
author_email="mdevaev@gmail.com",
|
author_email="mdevaev@gmail.com",
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
|
|||||||
SET_NUMBER(stride, Long, Long);
|
SET_NUMBER(stride, Long, Long);
|
||||||
SET_NUMBER(online, Long, Bool);
|
SET_NUMBER(online, Long, Bool);
|
||||||
SET_NUMBER(key, Long, Bool);
|
SET_NUMBER(key, Long, Bool);
|
||||||
|
SET_NUMBER(gop, Long, Long);
|
||||||
SET_NUMBER(grab_ts, Double, Float);
|
SET_NUMBER(grab_ts, Double, Float);
|
||||||
SET_NUMBER(encode_begin_ts, Double, Float);
|
SET_NUMBER(encode_begin_ts, Double, Float);
|
||||||
SET_NUMBER(encode_end_ts, Double, Float);
|
SET_NUMBER(encode_end_ts, Double, Float);
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ void us_output_file_write(void *v_output, const us_frame_s *frame) {
|
|||||||
us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
|
us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
|
||||||
fprintf(output->fp,
|
fprintf(output->fp,
|
||||||
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
|
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
|
||||||
" \"format\": %u, \"stride\": %u, \"online\": %u, \"key\": %u,"
|
" \"format\": %u, \"stride\": %u, \"online\": %u, \"key\": %u, \"gop\": %u,"
|
||||||
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
|
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
|
||||||
" \"data\": \"%s\"}\n",
|
" \"data\": \"%s\"}\n",
|
||||||
frame->used, frame->width, frame->height,
|
frame->used, frame->width, frame->height,
|
||||||
frame->format, frame->stride, frame->online, frame->key,
|
frame->format, frame->stride, frame->online, frame->key, frame->gop,
|
||||||
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
|
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
|
||||||
output->base64_data);
|
output->base64_data);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -260,16 +260,16 @@ static int _dump_sink(
|
|||||||
const long long now_second = us_floor_ms(now);
|
const long long now_second = us_floor_ms(now);
|
||||||
|
|
||||||
char fourcc_str[8];
|
char fourcc_str[8];
|
||||||
US_LOG_VERBOSE("Frame: res=%ux%u, fmt=%s, stride=%u, online=%d, key=%d, kr=%d, latency=%.3Lf, diff=%.3Lf, size=%zu",
|
US_LOG_VERBOSE("Frame: %s - %ux%u -- online=%d, key=%d, kr=%d, gop=%u, latency=%.3Lf, backlog=%.3Lf, size=%zu",
|
||||||
frame->width, frame->height,
|
|
||||||
us_fourcc_to_string(frame->format, fourcc_str, 8),
|
us_fourcc_to_string(frame->format, fourcc_str, 8),
|
||||||
frame->stride, frame->online, frame->key, key_requested,
|
frame->width, frame->height,
|
||||||
|
frame->online, frame->key, key_requested, frame->gop,
|
||||||
now - frame->grab_ts, (last_ts ? now - last_ts : 0),
|
now - frame->grab_ts, (last_ts ? now - last_ts : 0),
|
||||||
frame->used);
|
frame->used);
|
||||||
last_ts = now;
|
last_ts = now;
|
||||||
|
|
||||||
US_LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
|
US_LOG_DEBUG(" stride=%u, grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
|
||||||
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
|
frame->stride, frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
|
||||||
|
|
||||||
if (now_second != fps_second) {
|
if (now_second != fps_second) {
|
||||||
fps = fps_accum;
|
fps = fps_accum;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define US_VERSION_MAJOR 5
|
#define US_VERSION_MAJOR 5
|
||||||
#define US_VERSION_MINOR 31
|
#define US_VERSION_MINOR 34
|
||||||
|
|
||||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ typedef struct {
|
|||||||
|
|
||||||
bool online;
|
bool online;
|
||||||
bool key;
|
bool key;
|
||||||
|
unsigned gop;
|
||||||
|
|
||||||
long double grab_ts;
|
long double grab_ts;
|
||||||
long double encode_begin_ts;
|
long double encode_begin_ts;
|
||||||
@@ -64,6 +65,7 @@ typedef struct {
|
|||||||
x_dest->stride = x_src->stride; \
|
x_dest->stride = x_src->stride; \
|
||||||
x_dest->online = x_src->online; \
|
x_dest->online = x_src->online; \
|
||||||
x_dest->key = x_src->key; \
|
x_dest->key = x_src->key; \
|
||||||
|
x_dest->gop = x_src->gop; \
|
||||||
x_dest->grab_ts = x_src->grab_ts; \
|
x_dest->grab_ts = x_src->grab_ts; \
|
||||||
x_dest->encode_begin_ts = x_src->encode_begin_ts; \
|
x_dest->encode_begin_ts = x_src->encode_begin_ts; \
|
||||||
x_dest->encode_end_ts = x_src->encode_end_ts; \
|
x_dest->encode_end_ts = x_src->encode_end_ts; \
|
||||||
@@ -81,6 +83,7 @@ static inline void us_frame_copy_meta(const us_frame_s *src, us_frame_s *dest) {
|
|||||||
&& x_a->stride == x_b->stride \
|
&& x_a->stride == x_b->stride \
|
||||||
&& x_a->online == x_b->online \
|
&& x_a->online == x_b->online \
|
||||||
&& x_a->key == x_b->key \
|
&& x_a->key == x_b->key \
|
||||||
|
&& x_a->gop == x_b->gop \
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
|
|
||||||
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||||
#define US_MEMSINK_VERSION ((uint32_t)3)
|
#define US_MEMSINK_VERSION ((uint32_t)4)
|
||||||
|
|
||||||
#ifndef US_CFG_MEMSINK_MAX_DATA
|
#ifndef US_CFG_MEMSINK_MAX_DATA
|
||||||
# define US_CFG_MEMSINK_MAX_DATA 33554432
|
# define US_CFG_MEMSINK_MAX_DATA 33554432
|
||||||
@@ -51,6 +51,7 @@ typedef struct {
|
|||||||
unsigned stride;
|
unsigned stride;
|
||||||
bool online;
|
bool online;
|
||||||
bool key;
|
bool key;
|
||||||
|
unsigned gop;
|
||||||
|
|
||||||
long double grab_ts;
|
long double grab_ts;
|
||||||
long double encode_begin_ts;
|
long double encode_begin_ts;
|
||||||
|
|||||||
@@ -476,6 +476,7 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
|||||||
} else {
|
} else {
|
||||||
us_frame_set_data(dest, _RUN(output_bufs[output_buf.index].data), output_plane.bytesused);
|
us_frame_set_data(dest, _RUN(output_bufs[output_buf.index].data), output_plane.bytesused);
|
||||||
dest->key = output_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
|
dest->key = output_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
|
||||||
|
dest->gop = enc->gop;
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user