mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 12:16:31 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
030077fb47 | ||
|
|
61ddff8b6e | ||
|
|
27fbfe149e | ||
|
|
349f655cd9 | ||
|
|
f5c0a15967 | ||
|
|
d822ae0890 | ||
|
|
2d82adb2ba | ||
|
|
56b21274d1 | ||
|
|
76329ba5a6 | ||
|
|
4d5c5fffb4 | ||
|
|
29234c330b | ||
|
|
690d291818 | ||
|
|
8cfb3fc301 | ||
|
|
69ab3fa831 | ||
|
|
79d6c76084 | ||
|
|
f15d2fc194 | ||
|
|
99aa4828c5 | ||
|
|
93969f487c | ||
|
|
61407c6596 | ||
|
|
6c95a56f3a | ||
|
|
283f53a961 | ||
|
|
ef8fb8216e |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 4.4
|
current_version = 4.9
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|||||||
@@ -4,3 +4,6 @@
|
|||||||
# Allow source code
|
# Allow source code
|
||||||
!Makefile
|
!Makefile
|
||||||
!src/**
|
!src/**
|
||||||
|
!python/**
|
||||||
|
!janus/**
|
||||||
|
!man/**
|
||||||
|
|||||||
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_file = lf
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.{py,yaml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -2,16 +2,16 @@
|
|||||||
/linters/.mypy_cache/
|
/linters/.mypy_cache/
|
||||||
/pkg/arch/pkg/
|
/pkg/arch/pkg/
|
||||||
/pkg/arch/src/
|
/pkg/arch/src/
|
||||||
/pkg/arch/v*.tar.gz
|
|
||||||
/pkg/arch/ustreamer-*.pkg.tar.xz
|
|
||||||
/pkg/arch/ustreamer-*.pkg.tar.zst
|
|
||||||
/src/build/
|
/src/build/
|
||||||
/src/*.bin
|
|
||||||
/python/build/
|
/python/build/
|
||||||
/janus/build/
|
/janus/build/
|
||||||
/ustreamer
|
/ustreamer
|
||||||
/ustreamer-dump
|
/ustreamer-dump
|
||||||
/config.mk
|
/config.mk
|
||||||
/vgcore.*
|
vgcore.*
|
||||||
/*.sock
|
*.sock
|
||||||
/*.so
|
*.so
|
||||||
|
*.bin
|
||||||
|
*.pkg.tar.xz
|
||||||
|
*.pkg.tar.zst
|
||||||
|
*.tar.gz
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -92,6 +92,21 @@ $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
|||||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||||
```
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
# Integrations
|
||||||
|
|
||||||
|
## Nginx
|
||||||
|
When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /stream {
|
||||||
|
postpone_output 0;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_ignore_headers X-Accel-Buffering;
|
||||||
|
proxy_pass http://ustreamer;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Tips & tricks for v4l2
|
# Tips & tricks for v4l2
|
||||||
v4l2 utilities provide the tools to manage USB webcam setting and information. Scripts can be use to make adjustments and run manually or with cron. Running in cron for example to change the exposure settings at certain times of day. The package is available in all Linux distributions and is usually called `v4l-utils`.
|
v4l2 utilities provide the tools to manage USB webcam setting and information. Scripts can be use to make adjustments and run manually or with cron. Running in cron for example to change the exposure settings at certain times of day. The package is available in all Linux distributions and is usually called `v4l-utils`.
|
||||||
@@ -107,7 +122,6 @@ v4l2 utilities provide the tools to manage USB webcam setting and information. S
|
|||||||
-----
|
-----
|
||||||
# See also
|
# See also
|
||||||
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
|
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
|
||||||
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Use [Ansible](https://docs.ansible.com/ansible/latest/index.html) to compile uStreamer and install it as a systemd service automatically.
|
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# License
|
# License
|
||||||
|
|||||||
16
README.ru.md
16
README.ru.md
@@ -92,6 +92,21 @@ $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
|||||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||||
```
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
# Интеграция
|
||||||
|
|
||||||
|
## Nginx
|
||||||
|
Если uStreamer находится на Nginx, то последний будет буферизировать поток и создавать дополнительную задержку в стриме. Чтобы задержки не было, буферизацию можно отключить:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /stream {
|
||||||
|
postpone_output 0;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_ignore_headers X-Accel-Buffering;
|
||||||
|
proxy_pass http://ustreamer;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Утилиты V4L2
|
# Утилиты V4L2
|
||||||
V4L2 предоставляет ряд официальных утилит для управления USB-вебкамерами и получения информации об устройствах. С их помощью можно писать всякие настроечные скрипты и запускать их по крону, если, например, вам требуется изменять настройки экспозиции в зависимости от времени суток. Пакет с этими утилитами доступен на всех дистрибутивах Linux и обычно называется `v4l-utils`.
|
V4L2 предоставляет ряд официальных утилит для управления USB-вебкамерами и получения информации об устройствах. С их помощью можно писать всякие настроечные скрипты и запускать их по крону, если, например, вам требуется изменять настройки экспозиции в зависимости от времени суток. Пакет с этими утилитами доступен на всех дистрибутивах Linux и обычно называется `v4l-utils`.
|
||||||
@@ -107,7 +122,6 @@ V4L2 предоставляет ряд официальных утилит дл
|
|||||||
-----
|
-----
|
||||||
# Смотрите также
|
# Смотрите также
|
||||||
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
|
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
|
||||||
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Использование [Ansible](https://docs.ansible.com/ansible/latest/index.html) для сборки и установки стримера как systemd-сервиса.
|
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Лицензия
|
# Лицензия
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
#define PRE 3 // Annex B prefix length
|
#define PRE 3 // Annex B prefix length
|
||||||
|
|
||||||
|
|
||||||
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, rtp_callback_f callback);
|
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback);
|
||||||
static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked);
|
static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked);
|
||||||
|
|
||||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
||||||
@@ -99,6 +99,9 @@ char *rtp_make_sdp(rtp_s *rtp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) {
|
void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) {
|
||||||
|
// There is a complicated logic here but everything works as it should:
|
||||||
|
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
|
||||||
|
|
||||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||||
|
|
||||||
const uint32_t pts = get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
const uint32_t pts = get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||||
@@ -118,7 +121,7 @@ void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) {
|
|||||||
if (data[size - 1] == 0) { // Check for extra 00
|
if (data[size - 1] == 0) { // Check for extra 00
|
||||||
--size;
|
--size;
|
||||||
}
|
}
|
||||||
_rtp_process_nalu(rtp, data, size, pts, callback);
|
_rtp_process_nalu(rtp, data, size, pts, false, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
last_offset = offset;
|
last_offset = offset;
|
||||||
@@ -127,18 +130,16 @@ void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) {
|
|||||||
if (last_offset >= 0) {
|
if (last_offset >= 0) {
|
||||||
const uint8_t *data = frame->data + last_offset + PRE;
|
const uint8_t *data = frame->data + last_offset + PRE;
|
||||||
size_t size = frame->used - last_offset - PRE;
|
size_t size = frame->used - last_offset - PRE;
|
||||||
_rtp_process_nalu(rtp, data, size, pts, callback);
|
_rtp_process_nalu(rtp, data, size, pts, true, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, rtp_callback_f callback) {
|
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback) {
|
||||||
const unsigned ref_idc = (data[0] >> 5) & 3;
|
const unsigned ref_idc = (data[0] >> 5) & 3;
|
||||||
const unsigned type = data[0] & 0x1F;
|
const unsigned type = data[0] & 0x1F;
|
||||||
|
|
||||||
bool marked = false; // M=1 if this is an access unit
|
|
||||||
frame_s *ps = NULL;
|
frame_s *ps = NULL;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 1 ... 5: marked = true; break;
|
|
||||||
case 7: ps = rtp->sps; break;
|
case 7: ps = rtp->sps; break;
|
||||||
case 8: ps = rtp->pps; break;
|
case 8: ps = rtp->pps; break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ disable =
|
|||||||
too-many-ancestors,
|
too-many-ancestors,
|
||||||
no-else-return,
|
no-else-return,
|
||||||
len-as-condition,
|
len-as-condition,
|
||||||
|
unspecified-encoding,
|
||||||
|
|
||||||
[REPORTS]
|
[REPORTS]
|
||||||
msg-template = {symbol} -- {path}:{line}({obj}): {msg}
|
msg-template = {symbol} -- {path}:{line}({obj}): {msg}
|
||||||
|
|||||||
@@ -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 4.4" "January 2021"
|
.TH USTREAMER-DUMP 1 "version 4.9" "January 2021"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||||
@@ -38,6 +38,12 @@ Filename to dump output to. Use '-' for stdout. Default: just consume the sink.
|
|||||||
.TP
|
.TP
|
||||||
.BR \-j ", " \-\-output-json
|
.BR \-j ", " \-\-output-json
|
||||||
Format output as JSON. Required option --output. Default: disabled.
|
Format output as JSON. Required option --output. Default: disabled.
|
||||||
|
.TP
|
||||||
|
.BR \-c ", " \-\-count\ \fIN
|
||||||
|
Limit the number of frames. Default: 0 (infinite).
|
||||||
|
.TP
|
||||||
|
.BR \-i ", "\-\-interval\ \fIsec
|
||||||
|
Delay between reading frames (float). Default: 0.
|
||||||
|
|
||||||
.SS "Logging options"
|
.SS "Logging options"
|
||||||
.TP
|
.TP
|
||||||
|
|||||||
@@ -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 4.4" "November 2020"
|
.TH USTREAMER 1 "version 4.9" "November 2020"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer \- stream MJPG video from any V4L2 device to the network
|
ustreamer \- stream MJPG video from any V4L2 device to the network
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=4.4
|
pkgver=4.9
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||||
url="https://github.com/pikvm/ustreamer"
|
url="https://github.com/pikvm/ustreamer"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ustreamer
|
PKG_NAME:=ustreamer
|
||||||
PKG_VERSION:=4.4
|
PKG_VERSION:=4.9
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from distutils.core import setup
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
setup(
|
setup(
|
||||||
name="ustreamer",
|
name="ustreamer",
|
||||||
version="4.4",
|
version="4.9",
|
||||||
description="uStreamer tools",
|
description="uStreamer tools",
|
||||||
author="Maxim Devaev",
|
author="Maxim Devaev",
|
||||||
author_email="mdevaev@gmail.com",
|
author_email="mdevaev@gmail.com",
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <float.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -44,6 +46,8 @@ enum _OPT_VALUES {
|
|||||||
_O_SINK_TIMEOUT = 't',
|
_O_SINK_TIMEOUT = 't',
|
||||||
_O_OUTPUT = 'o',
|
_O_OUTPUT = 'o',
|
||||||
_O_OUTPUT_JSON = 'j',
|
_O_OUTPUT_JSON = 'j',
|
||||||
|
_O_COUNT = 'c',
|
||||||
|
_O_INTERVAL = 'i',
|
||||||
|
|
||||||
_O_HELP = 'h',
|
_O_HELP = 'h',
|
||||||
_O_VERSION = 'v',
|
_O_VERSION = 'v',
|
||||||
@@ -61,6 +65,8 @@ static const struct option _LONG_OPTS[] = {
|
|||||||
{"sink-timeout", required_argument, NULL, _O_SINK_TIMEOUT},
|
{"sink-timeout", required_argument, NULL, _O_SINK_TIMEOUT},
|
||||||
{"output", required_argument, NULL, _O_OUTPUT},
|
{"output", required_argument, NULL, _O_OUTPUT},
|
||||||
{"output-json", no_argument, NULL, _O_OUTPUT_JSON},
|
{"output-json", no_argument, NULL, _O_OUTPUT_JSON},
|
||||||
|
{"count", required_argument, NULL, _O_COUNT},
|
||||||
|
{"interval", required_argument, NULL, _O_INTERVAL},
|
||||||
|
|
||||||
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
|
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
|
||||||
{"perf", no_argument, NULL, _O_PERF},
|
{"perf", no_argument, NULL, _O_PERF},
|
||||||
@@ -89,7 +95,10 @@ typedef struct {
|
|||||||
static void _signal_handler(int signum);
|
static void _signal_handler(int signum);
|
||||||
static void _install_signal_handlers(void);
|
static void _install_signal_handlers(void);
|
||||||
|
|
||||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_context_s *ctx);
|
static int _dump_sink(
|
||||||
|
const char *sink_name, unsigned sink_timeout,
|
||||||
|
long long count, long double interval,
|
||||||
|
_output_context_s *ctx);
|
||||||
|
|
||||||
static void _help(FILE *fp);
|
static void _help(FILE *fp);
|
||||||
|
|
||||||
@@ -102,6 +111,8 @@ int main(int argc, char *argv[]) {
|
|||||||
unsigned sink_timeout = 1;
|
unsigned sink_timeout = 1;
|
||||||
char *output_path = NULL;
|
char *output_path = NULL;
|
||||||
bool output_json = false;
|
bool output_json = false;
|
||||||
|
long long count = 0;
|
||||||
|
long double interval = 0;
|
||||||
|
|
||||||
# define OPT_SET(_dest, _value) { \
|
# define OPT_SET(_dest, _value) { \
|
||||||
_dest = _value; \
|
_dest = _value; \
|
||||||
@@ -118,6 +129,16 @@ int main(int argc, char *argv[]) {
|
|||||||
break; \
|
break; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# define OPT_LDOUBLE(_name, _dest, _min, _max) { \
|
||||||
|
errno = 0; char *_end = NULL; long double _tmp = strtold(optarg, &_end); \
|
||||||
|
if (errno || *_end || _tmp < _min || _tmp > _max) { \
|
||||||
|
printf("Invalid value for '%s=%s': min=%Lf, max=%Lf\n", _name, optarg, (long double)_min, (long double)_max); \
|
||||||
|
return 1; \
|
||||||
|
} \
|
||||||
|
_dest = _tmp; \
|
||||||
|
break; \
|
||||||
|
}
|
||||||
|
|
||||||
char short_opts[128];
|
char short_opts[128];
|
||||||
build_short_options(_LONG_OPTS, short_opts, 128);
|
build_short_options(_LONG_OPTS, short_opts, 128);
|
||||||
|
|
||||||
@@ -127,6 +148,8 @@ int main(int argc, char *argv[]) {
|
|||||||
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
|
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
|
||||||
case _O_OUTPUT: OPT_SET(output_path, optarg);
|
case _O_OUTPUT: OPT_SET(output_path, optarg);
|
||||||
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
|
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
|
||||||
|
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
|
||||||
|
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
|
||||||
|
|
||||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
||||||
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
|
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
|
||||||
@@ -143,6 +166,7 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# undef OPT_LDOUBLE
|
||||||
# undef OPT_NUMBER
|
# undef OPT_NUMBER
|
||||||
# undef OPT_SET
|
# undef OPT_SET
|
||||||
|
|
||||||
@@ -163,7 +187,7 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_install_signal_handlers();
|
_install_signal_handlers();
|
||||||
int retval = abs(_dump_sink(sink_name, sink_timeout, &ctx));
|
int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, &ctx));
|
||||||
if (ctx.v_output && ctx.destroy) {
|
if (ctx.v_output && ctx.destroy) {
|
||||||
ctx.destroy(ctx.v_output);
|
ctx.destroy(ctx.v_output);
|
||||||
}
|
}
|
||||||
@@ -201,7 +225,17 @@ static void _install_signal_handlers(void) {
|
|||||||
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_context_s *ctx) {
|
static int _dump_sink(
|
||||||
|
const char *sink_name, unsigned sink_timeout,
|
||||||
|
long long count, long double interval,
|
||||||
|
_output_context_s *ctx) {
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
count = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
useconds_t interval_us = interval * 1000000;
|
||||||
|
|
||||||
frame_s *frame = frame_init();
|
frame_s *frame = frame_init();
|
||||||
memsink_s *sink = NULL;
|
memsink_s *sink = NULL;
|
||||||
|
|
||||||
@@ -243,6 +277,17 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_cont
|
|||||||
if (ctx->v_output) {
|
if (ctx->v_output) {
|
||||||
ctx->write(ctx->v_output, frame);
|
ctx->write(ctx->v_output, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (count >= 0) {
|
||||||
|
--count;
|
||||||
|
if (count <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interval_us > 0) {
|
||||||
|
usleep(interval_us);
|
||||||
|
}
|
||||||
} else if (error == -2) {
|
} else if (error == -2) {
|
||||||
usleep(1000);
|
usleep(1000);
|
||||||
} else {
|
} else {
|
||||||
@@ -282,6 +327,8 @@ static void _help(FILE *fp) {
|
|||||||
SAY(" -t|--sink-timeout <sec> ─ Timeout for the upcoming frame. Default: 1.\n");
|
SAY(" -t|--sink-timeout <sec> ─ Timeout for the upcoming frame. Default: 1.\n");
|
||||||
SAY(" -o|--output <filename> ─── Filename to dump output to. Use '-' for stdout. Default: just consume the sink.\n");
|
SAY(" -o|--output <filename> ─── Filename to dump output to. Use '-' for stdout. Default: just consume the sink.\n");
|
||||||
SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n");
|
SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n");
|
||||||
|
SAY(" -c|--count <N> ───────── Limit the number of frames. Default: 0 (infinite).\n");
|
||||||
|
SAY(" -i|--interval <sec> ───── Delay between reading frames (float). Default: 0.\n");
|
||||||
SAY("Logging options:");
|
SAY("Logging options:");
|
||||||
SAY("════════════════");
|
SAY("════════════════");
|
||||||
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define VERSION_MAJOR 4
|
#define VERSION_MAJOR 4
|
||||||
#define VERSION_MINOR 4
|
#define VERSION_MINOR 9
|
||||||
|
|
||||||
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||||
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
|
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ typedef struct {
|
|||||||
_dest->encode_end_ts = _src->encode_end_ts; \
|
_dest->encode_end_ts = _src->encode_end_ts; \
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
static inline void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
||||||
FRAME_COPY_META(src, dest);
|
FRAME_COPY_META(src, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +97,6 @@ unsigned frame_get_padding(const frame_s *frame);
|
|||||||
|
|
||||||
const char *fourcc_to_string(unsigned format, char *buf, size_t size);
|
const char *fourcc_to_string(unsigned format, char *buf, size_t size);
|
||||||
|
|
||||||
inline bool is_jpeg(unsigned format) {
|
static inline bool is_jpeg(unsigned format) {
|
||||||
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
|
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,11 @@ void memsink_destroy(memsink_s *sink) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||||
// Возвращает true, если есть клиенты ИЛИ изменились метаданные
|
// Return true (the need to write to memsink) on any of these conditions:
|
||||||
|
// - EWOULDBLOCK - we have an active client;
|
||||||
|
// - Incorrect magic or version - need to first write;
|
||||||
|
// - We have some active clients by last_client_ts;
|
||||||
|
// - Frame meta differs (like size, format, but not timestamp).
|
||||||
|
|
||||||
assert(sink->server);
|
assert(sink->server);
|
||||||
|
|
||||||
@@ -100,15 +104,18 @@ bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic()));
|
if (sink->mem->magic != MEMSINK_MAGIC || sink->mem->version != MEMSINK_VERSION) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool retval = (atomic_load(&sink->has_clients) || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));
|
bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
|
||||||
|
atomic_store(&sink->has_clients, has_clients);
|
||||||
|
|
||||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return retval;
|
return (has_clients || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
|
||||||
}
|
}
|
||||||
|
|
||||||
int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
||||||
|
|||||||
@@ -12,30 +12,30 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="/state"><b>/state</b></a><br>
|
<a href="state"><b>/state</b></a><br>
|
||||||
Get JSON structure with the state of the server.
|
Get JSON structure with the state of the server.
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
<li>
|
<li>
|
||||||
<a href="/snapshot"><b>/snapshot</b></a><br>
|
<a href="snapshot"><b>/snapshot</b></a><br>
|
||||||
Get a current actual image from the server.
|
Get a current actual image from the server.
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
<li>
|
<li>
|
||||||
<a href="/stream"><b>/stream</b></a><br>
|
<a href="stream"><b>/stream</b></a><br>
|
||||||
Get a live stream. Query params:<br>
|
Get a live stream. Query params:<br>
|
||||||
<br>
|
<br>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<b>key=abc123</b><br>
|
<b>key=abc123</b><br>
|
||||||
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br>
|
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br>
|
||||||
the stream client to determine its identifier and view statistics using <a href="/state">/state</a>.
|
the stream client to determine its identifier and view statistics using <a href="state">/state</a>.
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
<li>
|
<li>
|
||||||
<b>extra_headers=1</b><br>
|
<b>extra_headers=1</b><br>
|
||||||
Add <i>X-UStreamer-*</i> headers to the <a href="/stream">/stream</a> handle
|
Add <i>X-UStreamer-*</i> headers to the <a href="stream">/stream</a> handle
|
||||||
(like with the <a href="/snapshot">/snapshot</a>).
|
(like with the <a href="snapshot">/snapshot</a>).
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
<li>
|
<li>
|
||||||
@@ -62,9 +62,9 @@
|
|||||||
The mjpg-streamer compatibility layer:<br>
|
The mjpg-streamer compatibility layer:<br>
|
||||||
<br>
|
<br>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/?action=snapshot">/?action=snapshot</a> as alias to the <a href="/snapshot">/snapshot</a>.</li>
|
<li><a href="?action=snapshot">/?action=snapshot</a> as alias to the <a href="snapshot">/snapshot</a>.</li>
|
||||||
<br>
|
<br>
|
||||||
<li><a href="/?action=stream">/?action=stream</a> as alias to the <a href="/stream">/stream</a>.</li>
|
<li><a href="?action=stream">/?action=stream</a> as alias to the <a href="stream">/stream</a>.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -37,30 +37,30 @@ const char *const HTML_INDEX_PAGE = " \
|
|||||||
<hr> \
|
<hr> \
|
||||||
<ul> \
|
<ul> \
|
||||||
<li> \
|
<li> \
|
||||||
<a href=\"/state\"><b>/state</b></a><br> \
|
<a href=\"state\"><b>/state</b></a><br> \
|
||||||
Get JSON structure with the state of the server. \
|
Get JSON structure with the state of the server. \
|
||||||
</li> \
|
</li> \
|
||||||
<br> \
|
<br> \
|
||||||
<li> \
|
<li> \
|
||||||
<a href=\"/snapshot\"><b>/snapshot</b></a><br> \
|
<a href=\"snapshot\"><b>/snapshot</b></a><br> \
|
||||||
Get a current actual image from the server. \
|
Get a current actual image from the server. \
|
||||||
</li> \
|
</li> \
|
||||||
<br> \
|
<br> \
|
||||||
<li> \
|
<li> \
|
||||||
<a href=\"/stream\"><b>/stream</b></a><br> \
|
<a href=\"stream\"><b>/stream</b></a><br> \
|
||||||
Get a live stream. Query params:<br> \
|
Get a live stream. Query params:<br> \
|
||||||
<br> \
|
<br> \
|
||||||
<ul> \
|
<ul> \
|
||||||
<li> \
|
<li> \
|
||||||
<b>key=abc123</b><br> \
|
<b>key=abc123</b><br> \
|
||||||
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br> \
|
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br> \
|
||||||
the stream client to determine its identifier and view statistics using <a href=\"/state\">/state</a>. \
|
the stream client to determine its identifier and view statistics using <a href=\"state\">/state</a>. \
|
||||||
</li> \
|
</li> \
|
||||||
<br> \
|
<br> \
|
||||||
<li> \
|
<li> \
|
||||||
<b>extra_headers=1</b><br> \
|
<b>extra_headers=1</b><br> \
|
||||||
Add <i>X-UStreamer-*</i> headers to the <a href=\"/stream\">/stream</a> handle \
|
Add <i>X-UStreamer-*</i> headers to the <a href=\"stream\">/stream</a> handle \
|
||||||
(like with the <a href=\"/snapshot\">/snapshot</a>). \
|
(like with the <a href=\"snapshot\">/snapshot</a>). \
|
||||||
</li> \
|
</li> \
|
||||||
<br> \
|
<br> \
|
||||||
<li> \
|
<li> \
|
||||||
@@ -87,9 +87,9 @@ const char *const HTML_INDEX_PAGE = " \
|
|||||||
The mjpg-streamer compatibility layer:<br> \
|
The mjpg-streamer compatibility layer:<br> \
|
||||||
<br> \
|
<br> \
|
||||||
<ul> \
|
<ul> \
|
||||||
<li><a href=\"/?action=snapshot\">/?action=snapshot</a> as alias to the <a href=\"/snapshot\">/snapshot</a>.</li> \
|
<li><a href=\"?action=snapshot\">/?action=snapshot</a> as alias to the <a href=\"snapshot\">/snapshot</a>.</li> \
|
||||||
<br> \
|
<br> \
|
||||||
<li><a href=\"/?action=stream\">/?action=stream</a> as alias to the <a href=\"/stream\">/stream</a>.</li> \
|
<li><a href=\"?action=stream\">/?action=stream</a> as alias to the <a href=\"stream\">/stream</a>.</li> \
|
||||||
</ul> \
|
</ul> \
|
||||||
</li> \
|
</li> \
|
||||||
</ul> \
|
</ul> \
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
static int _http_preprocess_request(struct evhttp_request *request, server_s *server);
|
static int _http_preprocess_request(struct evhttp_request *request, server_s *server);
|
||||||
|
|
||||||
|
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server);
|
||||||
|
|
||||||
static void _http_callback_root(struct evhttp_request *request, void *v_server);
|
static void _http_callback_root(struct evhttp_request *request, void *v_server);
|
||||||
static void _http_callback_static(struct evhttp_request *request, void *v_server);
|
static void _http_callback_static(struct evhttp_request *request, void *v_server);
|
||||||
static void _http_callback_state(struct evhttp_request *request, void *v_server);
|
static void _http_callback_state(struct evhttp_request *request, void *v_server);
|
||||||
@@ -220,41 +222,63 @@ static int _http_preprocess_request(struct evhttp_request *request, server_s *se
|
|||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_callback_root(struct evhttp_request *request, void *v_server) {
|
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server) {
|
||||||
server_s *server = (server_s *)v_server;
|
// MJPG-Streamer compatibility layer
|
||||||
|
|
||||||
PREPROCESS_REQUEST;
|
struct evkeyvalq params;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
struct evkeyvalq params; // For mjpg-streamer compatibility
|
|
||||||
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||||
const char *action = evhttp_find_header(¶ms, "action");
|
const char *action = evhttp_find_header(¶ms, "action");
|
||||||
|
|
||||||
if (action && !strcmp(action, "snapshot")) {
|
if (action && !strcmp(action, "snapshot")) {
|
||||||
_http_callback_snapshot(request, v_server);
|
_http_callback_snapshot(request, v_server);
|
||||||
|
goto ok;
|
||||||
} else if (action && !strcmp(action, "stream")) {
|
} else if (action && !strcmp(action, "stream")) {
|
||||||
_http_callback_stream(request, v_server);
|
_http_callback_stream(request, v_server);
|
||||||
} else {
|
goto ok;
|
||||||
struct evbuffer *buf;
|
|
||||||
assert((buf = evbuffer_new()));
|
|
||||||
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
|
|
||||||
ADD_HEADER("Content-Type", "text/html");
|
|
||||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
|
||||||
evbuffer_free(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
evhttp_clear_headers(¶ms);
|
error = -1;
|
||||||
|
ok:
|
||||||
|
evhttp_clear_headers(¶ms);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define COMPAT_REQUEST { \
|
||||||
|
if (_http_check_run_compat_action(request, v_server) == 0) { \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _http_callback_root(struct evhttp_request *request, void *v_server) {
|
||||||
|
server_s *server = (server_s *)v_server;
|
||||||
|
|
||||||
|
PREPROCESS_REQUEST;
|
||||||
|
COMPAT_REQUEST;
|
||||||
|
|
||||||
|
struct evbuffer *buf;
|
||||||
|
|
||||||
|
assert((buf = evbuffer_new()));
|
||||||
|
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
|
||||||
|
ADD_HEADER("Content-Type", "text/html");
|
||||||
|
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||||
|
|
||||||
|
evbuffer_free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
|
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
|
||||||
server_s *server = (server_s *)v_server;
|
server_s *server = (server_s *)v_server;
|
||||||
|
|
||||||
|
PREPROCESS_REQUEST;
|
||||||
|
COMPAT_REQUEST;
|
||||||
|
|
||||||
struct evbuffer *buf = NULL;
|
struct evbuffer *buf = NULL;
|
||||||
struct evhttp_uri *uri = NULL;
|
struct evhttp_uri *uri = NULL;
|
||||||
char *decoded_path = NULL;
|
char *decoded_path = NULL;
|
||||||
char *static_path = NULL;
|
char *static_path = NULL;
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
|
|
||||||
PREPROCESS_REQUEST;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
char *uri_path;
|
char *uri_path;
|
||||||
|
|
||||||
@@ -324,6 +348,8 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef COMPAT_REQUEST
|
||||||
|
|
||||||
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
||||||
server_s *server = (server_s *)v_server;
|
server_s *server = (server_s *)v_server;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user