Compare commits

...

19 Commits
v4.10 ... v4.13

Author SHA1 Message Date
Maxim Devaev
61ab2a81a5 Bump version: 4.12 → 4.13 2022-02-21 05:06:06 +03:00
Maxim Devaev
d9b511c69f doc update 2022-02-21 05:04:42 +03:00
Maxim Devaev
4d9e72e313 man fix 2022-02-15 01:33:04 +03:00
Maxim Devaev
616a3eb6a6 new option --exit-on-no-clients 2022-02-14 02:44:58 +03:00
Maxim Devaev
3a8f035014 refactoring 2022-02-14 01:47:13 +03:00
Maxim Devaev
be2e0f11da systemd socket activation 2022-02-13 18:59:33 +03:00
Maxim Devaev
fec76dc9eb Bump version: 4.11 → 4.12 2022-02-11 20:53:52 +03:00
Maxim Devaev
48826208fd Fixed #143: Show HDMI info 2022-02-11 20:00:47 +03:00
Maxim Devaev
dd4fda6f5d fixed indent 2022-02-11 19:57:15 +03:00
Maxim Devaev
1dafb54621 Merge pull request #140 from fphammerle/readme-libopenmaxil-on-bullseye
readme: suggest adding `/opt/vc/lib/` to library search path on bullseye
2022-02-05 14:44:26 +03:00
Maxim Devaev
19f9567098 added comment about evbuffer_add_file() 2022-02-05 14:35:07 +03:00
Maxim Devaev
b3d1f06e5d Merge pull request #141 from russdill/evbuffer_add_file_fix
Don't close evbuffer_add_file's file descriptor
2022-02-05 14:30:24 +03:00
Maxim Devaev
f17069153d fixed quotes 2022-02-05 14:12:54 +03:00
Maxim Devaev
8a7d7b9c54 Merge pull request #142 from russdill/client-key-in-state
Expose supplied client key in state
2022-02-05 14:09:34 +03:00
Russ Dill
df7649d56f Expose supplied client key in state
The client key and id is currently supplied in a cookie. This
provides a way for a client to determine it's id in order to match
it within the state response. However if cookies are disabled or
the source domain differs from the ustreamer domain the cookie will
not be set.

This provides an alternate way for a client to find the state
response associated with it's connection by including the client
provided key in the state response. If the client does not supply
a key, the value 0 is supplied.

Signed-off-by: Russ Dill <russ.dill@gmail.com>
2022-02-04 23:07:41 -08:00
Russ Dill
58cc227cba Don't close evbuffer_add_file's file descriptor
evbuffer_add_file takes ownership for the file descriptor we pass
and manages closing it. Closing it ourselves will lead to the function
only being able to make very small transfers.

Signed-off-by: Russ Dill <russ.dill@gmail.com>
2022-02-04 23:06:22 -08:00
Fabian Peter Hammerle
76be4a1d10 readme: suggest adding /opt/vc/lib/ to library search path on bullseye
On Raspberry Pi OS Bullseye:
```sh
$ make WITH_OMX=1
[...]
== LD ustreamer.bin
/usr/bin/ld: warning: libbrcmGLESv2.so, needed by /opt/vc/lib/libopenmaxil.so, not found (try using -rpath or -rpath-link)
/usr/bin/ld: warning: libbrcmEGL.so, needed by /opt/vc/lib/libopenmaxil.so, not found (try using -rpath or -rpath-link)
[...]
$ ./ustreamer --features
./ustreamer: error while loading shared libraries: libopenmaxil.so: cannot open shared object file: No such file or directory
$ LD_LIBRARY_PATH=/opt/vc/lib/ ./ustreamer --features
+ WITH_OMX
- WITH_GPIO
+ WITH_PTHREAD_NP
+ WITH_SETPROCTITLE
+ HAS_PDEATHSIG
```
2022-01-30 19:57:43 +01:00
Maxim Devaev
496c9300fc Bump version: 4.10 → 4.11 2021-12-31 01:28:51 +03:00
Maxim Devaev
5b7780cf7c using python-3.10 2021-12-31 01:26:47 +03:00
20 changed files with 259 additions and 51 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 4.10
current_version = 4.13
parse = (?P<major>\d+)\.(?P<minor>\d+)
serialize =
{major}.{minor}

View File

@@ -17,6 +17,7 @@
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ✔ | ☹ Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save the traffic | ✔ <sup>2</sup> | ✘ |
| Streaming via UNIX domain socket | ✔ | ✘ |
| Systemd socket activation | ✔ | ✘ |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters | ✔ | ✘ |
| Option to serve files<br>with a built-in HTTP server | ✔ | ☹ Regular files only |
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
@@ -37,7 +38,7 @@ If you're going to live-stream from your backyard webcam and need to control it,
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `libgpiod-dev` for `WITH_GPIO=1`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1`, `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1`.
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. 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```.
@@ -63,6 +64,7 @@ Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x4
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
```bash
$ export LD_LIBRARY_PATH=/opt/vc/lib/ # on bullseye
$ ./ustreamer \
--format=uyvy \ # Device input format
--encoder=omx \ # Hardware encoding with OpenMAX

View File

@@ -17,6 +17,7 @@
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ✔ | ☹ Условно есть <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ✔ <sup>2</sup> | ✘ |
| Стрим через UNIX domain socket | ✔ | ✘ |
| Systemd socket activation | ✔ | ✘ |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ✔ | ✘ |
| Возможность сервить файлы встроенным<br>HTTP-сервером | ✔ | ☹ Нет каталогов |
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
@@ -37,7 +38,7 @@
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libbsd``` (только для Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `libgpiod` для `WITH_GPIO=1`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1`, `libgpiod` для `WITH_GPIO=1` и `libsystemd-dev` для `WITH_SYSTEMD=1`.
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.

View File

@@ -3,7 +3,7 @@ envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
skipsdist = true
[testenv]
basepython = python3.9
basepython = python3.10
changedir = /src
[testenv:cppcheck]

View File

@@ -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 4.10" "January 2021"
.TH USTREAMER-DUMP 1 "version 4.13" "January 2021"
.SH NAME
ustreamer-dump \- Dump uStreamer's memory sink to file

View 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 4.10" "November 2020"
.TH USTREAMER 1 "version 4.13" "November 2020"
.SH NAME
ustreamer \- stream MJPG video from any V4L2 device to the network
@@ -167,12 +167,15 @@ Bind to this TCP port. Default: 8080.
.BR \-U\ \fIpath ", " \-\-unix\ \fIpath
Bind to UNIX domain socket. Default: disabled.
.TP
.BR \-d ", " \-\-unix\-rm
.BR \-D ", " \-\-unix\-rm
Try to remove old unix socket file before binding. default: disabled.
.TP
.BR \-M\ \fImode ", " \-\-unix\-mode\ \fImode
Set UNIX socket file permissions (like 777). Default: disabled.
.TP
.BR \-S ", " \-\-systemd
Bind to systemd socket for socket activation. Required \fBWITH_SYSTEMD\fR feature. Default: disabled.
.TP
.BR \-\-user\ \fIname
HTTP basic auth user. Default: disabled.
.TP
@@ -189,7 +192,7 @@ Don't send identical frames to clients, but no more than specified number. It ca
Override image resolution for the /state. Default: disabled.
.TP
.BR \-\-tcp\-nodelay
Set TCP_NODELAY flag to the client /stream socket. Ignored for \-\-unix.
Set TCP_NODELAY flag to the client /stream socket. Only for TCP socket.
Default: disabled.
.TP
.BR \-\-allow\-origin\ \fIstr
@@ -246,6 +249,9 @@ Intarval between keyframes. Default: 30.
.BR \-\-exit\-on\-parent\-death
Exit the program if the parent process is dead. Required \fBHAS_PDEATHSIG\fR feature. Default: disabled.
.TP
.BR \-\-exit\-on\-no\-clients \fIsec
Exit the program if there have been no stream or sink clients or any HTTP requests in the last N seconds. Default: 0 (disabled).
.TP
.BR \-\-process\-name\-prefix\ \fIstr
Set process name prefix which will be displayed in the process list like '\fIstr: ustreamer \-\-blah\-blah\-blah'\fR. Required \fBWITH_SETPROCTITLE\fR feature. Default: disabled.
.TP

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=4.10
pkgver=4.13
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"
@@ -31,6 +31,11 @@ if [ -e /usr/include/janus/plugins/plugin.h ];then
makedepends+=(janus-gateway-pikvm)
_options="$_options WITH_JANUS=1"
fi
if [ -e /usr/include/systemd/sd-daemon.h ];then
depends+=(systemd)
makedepends+=(systemd)
_options="$_options WITH_SYSTEMD=1"
fi
# LD does not link mmal with this option

View File

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

View File

@@ -8,7 +8,7 @@ from distutils.core import setup
if __name__ == "__main__":
setup(
name="ustreamer",
version="4.10",
version="4.13",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",

View File

@@ -59,6 +59,13 @@ _USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
endif
ifneq ($(call optbool,$(WITH_SYSTEMD)),)
_USTR_LIBS += -lsystemd
override _CFLAGS += -DWITH_SYSTEMD
_USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
endif
WITH_PTHREAD_NP ?= 1
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
override _CFLAGS += -DWITH_PTHREAD_NP

View File

@@ -23,7 +23,7 @@
#pragma once
#define VERSION_MAJOR 4
#define VERSION_MINOR 10
#define VERSION_MINOR 13
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)

View File

@@ -137,3 +137,11 @@ INLINE void process_notify_parent(void) {
LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
}
}
INLINE void process_suicide(void) {
pid_t pid = getpid();
if (kill(pid, SIGTERM) < 0) {
LOG_PERROR("Can't send SIGTERM to own pid %d", pid);
}
}

View File

@@ -282,7 +282,7 @@ int device_switch_capturing(device_s *dev, bool enable) {
RUN(capturing) = enable;
LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
}
return 0;
return 0;
}
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error) {
@@ -497,8 +497,22 @@ static int _device_apply_dv_timings(device_s *dev) {
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
if (xioctl(RUN(fd), VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu",
dv.bt.width, dv.bt.height, (unsigned long long)dv.bt.pixelclock); // Issue #11
if (dv.type == V4L2_DV_BT_656_1120) {
// See v4l2_print_dv_timings() in the kernel
unsigned htot = V4L2_DV_BT_FRAME_WIDTH(&dv.bt);
unsigned vtot = V4L2_DV_BT_FRAME_HEIGHT(&dv.bt);
if (dv.bt.interlaced) {
vtot /= 2;
}
unsigned fps = ((htot * vtot) > 0 ? ((100 * (uint64_t)dv.bt.pixelclock)) / (htot * vtot) : 0);
LOG_INFO("Got new DV-timings: %ux%u%s%u.%02u, pixclk=%llu, vsync=%u, hsync=%u",
dv.bt.width, dv.bt.height, (dv.bt.interlaced ? "i" : "p"), fps / 100, fps % 100,
(unsigned long long)dv.bt.pixelclock, dv.bt.vsync, dv.bt.hsync); // See #11 about %llu
} else {
LOG_INFO("Got new DV-timings: %ux%u, pixclk=%llu, vsync=%u, hsync=%u",
dv.bt.width, dv.bt.height,
(unsigned long long)dv.bt.pixelclock, dv.bt.vsync, dv.bt.hsync);
}
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
if (xioctl(RUN(fd), VIDIOC_S_DV_TIMINGS, &dv) < 0) {
@@ -906,12 +920,12 @@ static void _device_set_control(
}
static const char *_format_to_string_nullable(unsigned format) {
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
if (format == _FORMATS[index].format) {
return _FORMATS[index].name;
}
}
return NULL;
}
return NULL;
}
static const char *_format_to_string_supported(unsigned format) {

View File

@@ -36,7 +36,8 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_ctx);
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
static void _http_exposed_refresh(int fd, short event, void *v_server);
static void _http_request_watcher(int fd, short event, void *v_server);
static void _http_refresher(int fd, short event, void *v_server);
static void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated);
static bool _expose_new_frame(server_s *server);
@@ -80,14 +81,19 @@ server_s *server_init(stream_s *stream) {
}
void server_destroy(server_s *server) {
if (RUN(refresh)) {
event_del(RUN(refresh));
event_free(RUN(refresh));
if (RUN(refresher)) {
event_del(RUN(refresher));
event_free(RUN(refresher));
}
if (RUN(request_watcher)) {
event_del(RUN(request_watcher));
event_free(RUN(request_watcher));
}
evhttp_free(RUN(http));
if (RUN(unix_fd)) {
close(RUN(unix_fd));
if (RUN(ext_fd)) {
close(RUN(ext_fd));
}
event_base_free(RUN(base));
@@ -128,18 +134,23 @@ int server_listen(server_s *server) {
EX(notify_last_width) = EX(frame->width);
EX(notify_last_height) = EX(frame->height);
if (server->exit_on_no_clients > 0) {
RUN(last_request_ts) = get_now_monotonic();
struct timeval interval = {0};
interval.tv_usec = 100000;
assert((RUN(request_watcher) = event_new(RUN(base), -1, EV_PERSIST, _http_request_watcher, server)));
assert(!event_add(RUN(request_watcher), &interval));
}
{
struct timeval refresh_interval;
refresh_interval.tv_sec = 0;
struct timeval interval = {0};
if (STREAM(dev->desired_fps) > 0) {
refresh_interval.tv_usec = 1000000 / (STREAM(dev->desired_fps) * 2);
interval.tv_usec = 1000000 / (STREAM(dev->desired_fps) * 2);
} else {
refresh_interval.tv_usec = 16000; // ~60fps
interval.tv_usec = 16000; // ~60fps
}
assert((RUN(refresh) = event_new(RUN(base), -1, EV_PERSIST, _http_exposed_refresh, server)));
assert(!event_add(RUN(refresh), &refresh_interval));
assert((RUN(refresher) = event_new(RUN(base), -1, EV_PERSIST, _http_refresher, server)));
assert(!event_add(RUN(refresher), &interval));
}
evhttp_set_timeout(RUN(http), server->timeout);
@@ -160,7 +171,7 @@ int server_listen(server_s *server) {
if (server->unix_path[0] != '\0') {
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
if ((RUN(unix_fd) = evhttp_my_bind_unix(
if ((RUN(ext_fd) = evhttp_my_bind_unix(
RUN(http),
server->unix_path,
server->unix_rm,
@@ -169,9 +180,16 @@ int server_listen(server_s *server) {
return -1;
}
LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path);
if (server->tcp_nodelay) {
LOG_ERROR("TCP_NODELAY flag can't be used with UNIX socket and will be ignored");
# ifdef WITH_SYSTEMD
} else if (server->systemd) {
LOG_DEBUG("Binding HTTP to systemd socket ...");
if ((RUN(ext_fd) = evhttp_my_bind_systemd(RUN(http))) < 0) {
return -1;
}
LOG_INFO("Listening systemd socket ...");
# endif
} else {
LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
if (evhttp_bind_socket(RUN(http), server->host, server->port) < 0) {
@@ -198,6 +216,8 @@ void server_loop_break(server_s *server) {
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
static int _http_preprocess_request(struct evhttp_request *request, server_s *server) {
RUN(last_request_ts) = get_now_monotonic();
if (RUN(auth_token)) {
const char *token = evhttp_find_header(evhttp_request_get_input_headers(request), "Authorization");
@@ -317,6 +337,10 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
goto not_found;
}
// evbuffer_add_file() owns the resulting file descriptor
// and will close it when finished transferring data
fd = -1;
ADD_HEADER("Content-Type", guess_mime_type(static_path));
evhttp_send_reply(request, HTTP_OK, "OK", buf);
goto cleanup;
@@ -421,13 +445,14 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
LIST_ITERATE(RUN(stream_clients), client, {
assert(evbuffer_add_printf(buf,
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
" \"dual_final_frames\": %s, \"zero_data\": %s}%s",
" \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s",
client->id,
client->fps,
bool_to_string(client->extra_headers),
bool_to_string(client->advance_headers),
bool_to_string(client->dual_final_frames),
bool_to_string(client->zero_data),
(client->key != NULL ? client->key : "0"),
(client->next ? ", " : "")
));
});
@@ -541,7 +566,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->hostport, client->id, RUN(stream_clients_count));
struct bufferevent *buf_event = evhttp_connection_get_bufferevent(conn);
if (server->tcp_nodelay && !RUN(unix_fd)) {
if (server->tcp_nodelay && !RUN(ext_fd)) {
evutil_socket_t fd;
int on = 1;
@@ -769,7 +794,21 @@ static void _http_queue_send_stream(server_s *server, bool stream_updated, bool
}
}
static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_server) {
static void _http_request_watcher(UNUSED int fd, UNUSED short what, void *v_server) {
server_s *server = (server_s *)v_server;
const long double now = get_now_monotonic();
if (stream_has_clients(RUN(stream))) {
RUN(last_request_ts) = now;
} else if (RUN(last_request_ts) + server->exit_on_no_clients < now) {
LOG_INFO("HTTP: No requests or HTTP/sink clients found in last %u seconds, exiting ...",
server->exit_on_no_clients);
process_suicide();
RUN(last_request_ts) = now;
}
}
static void _http_refresher(UNUSED int fd, UNUSED short what, void *v_server) {
server_s *server = (server_s *)v_server;
bool stream_updated = false;
bool frame_updated = false;

View File

@@ -71,6 +71,9 @@
#include "uri.h"
#include "mime.h"
#include "static.h"
#ifdef WITH_SYSTEMD
# include "systemd/systemd.h"
#endif
typedef struct stream_client_sx {
@@ -112,11 +115,17 @@ typedef struct {
typedef struct {
struct event_base *base;
struct evhttp *http;
evutil_socket_t unix_fd;
evutil_socket_t ext_fd; // Unix or socket activation
char *auth_token;
struct event *refresh;
struct event *request_watcher;
long double last_request_ts;
struct event *refresher;
stream_s *stream;
exposed_s *exposed;
stream_client_s *stream_clients;
unsigned stream_clients_count;
} server_runtime_s;
@@ -124,9 +133,15 @@ typedef struct {
typedef struct server_sx {
char *host;
unsigned port;
char *unix_path;
bool unix_rm;
mode_t unix_mode;
# ifdef WITH_SYSTEMD
bool systemd;
# endif
bool tcp_nodelay;
unsigned timeout;
@@ -140,6 +155,7 @@ typedef struct server_sx {
unsigned fake_height;
bool notify_parent;
unsigned exit_on_no_clients;
server_runtime_s *run;
} server_s;

View File

@@ -0,0 +1,46 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "systemd.h"
evutil_socket_t evhttp_my_bind_systemd(struct evhttp *http) {
int fds = sd_listen_fds(1);
if (fds < 1) {
LOG_ERROR("No available systemd sockets");
return -1;
}
int fd;
for (fd = 1; fd < fds; ++fd) {
close(SD_LISTEN_FDS_START + fd);
}
fd = SD_LISTEN_FDS_START;
assert(!evutil_make_socket_nonblocking(fd));
if (evhttp_accept_socket(http, fd) < 0) {
LOG_PERROR("Can't evhttp_accept_socket() systemd socket");
return -1;
}
return fd;
}

View File

@@ -0,0 +1,37 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <unistd.h>
#include <assert.h>
#include <event2/http.h>
#include <event2/util.h>
#include <systemd/sd-daemon.h>
#include "../../../libs/tools.h"
#include "../../../libs/logging.h"
evutil_socket_t evhttp_my_bind_systemd(struct evhttp *http);

View File

@@ -50,6 +50,9 @@ enum _OPT_VALUES {
_O_UNIX = 'U',
_O_UNIX_RM = 'D',
_O_UNIX_MODE = 'M',
# ifdef WITH_SYSTEMD
_O_SYSTEMD = 'S',
# endif
_O_DROP_SAME_FRAMES = 'e',
_O_FAKE_RESOLUTION = 'R',
@@ -109,6 +112,7 @@ enum _OPT_VALUES {
# ifdef HAS_PDEATHSIG
_O_EXIT_ON_PARENT_DEATH,
# endif
_O_EXIT_ON_NO_CLIENTS,
# ifdef WITH_SETPROCTITLE
_O_PROCESS_NAME_PREFIX,
# endif
@@ -168,6 +172,9 @@ static const struct option _LONG_OPTS[] = {
{"unix", required_argument, NULL, _O_UNIX},
{"unix-rm", no_argument, NULL, _O_UNIX_RM},
{"unix-mode", required_argument, NULL, _O_UNIX_MODE},
# ifdef WITH_SYSTEMD
{"systemd", no_argument, NULL, _O_SYSTEMD},
# endif
{"user", required_argument, NULL, _O_USER},
{"passwd", required_argument, NULL, _O_PASSWD},
{"static", required_argument, NULL, _O_STATIC},
@@ -203,6 +210,7 @@ static const struct option _LONG_OPTS[] = {
# ifdef HAS_PDEATHSIG
{"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH},
# endif
{"exit-on-no-clients", required_argument, NULL, _O_EXIT_ON_NO_CLIENTS},
# ifdef WITH_SETPROCTITLE
{"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX},
# endif
@@ -419,6 +427,9 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
case _O_UNIX: OPT_SET(server->unix_path, optarg);
case _O_UNIX_RM: OPT_SET(server->unix_rm, true);
case _O_UNIX_MODE: OPT_NUMBER("--unix-mode", server->unix_mode, INT_MIN, INT_MAX, 8);
# ifdef WITH_SYSTEMD
case _O_SYSTEMD: OPT_SET(server->systemd, true);
# endif
case _O_USER: OPT_SET(server->user, optarg);
case _O_PASSWD: OPT_SET(server->passwd, optarg);
case _O_STATIC: OPT_SET(server->static_path, optarg);
@@ -458,6 +469,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
};
break;
# endif
case _O_EXIT_ON_NO_CLIENTS: OPT_NUMBER("--exit-on-no-clients", server->exit_on_no_clients, 0, 86400, 0);
# ifdef WITH_SETPROCTITLE
case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg);
# endif
@@ -551,6 +563,12 @@ static void _features(void) {
puts("- WITH_GPIO");
# endif
# ifdef WITH_SYSTEMD
puts("+ WITH_SYSTEMD");
# else
puts("- WITH_SYSTEMD");
# endif
# ifdef WITH_PTHREAD_NP
puts("+ WITH_PTHREAD_NP");
# else
@@ -652,6 +670,9 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
SAY(" -U|--unix <path> ─────────── Bind to UNIX domain socket. Default: disabled.\n");
SAY(" -D|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n");
SAY(" -M|--unix-mode <mode> ────── Set UNIX socket file permissions (like 777). Default: disabled.\n");
# ifdef WITH_SYSTEMD
SAY(" -S|--systemd ─────────────── Bind to systemd socket for socket activation.\n");
# endif
SAY(" --user <name> ────────────── HTTP basic auth user. Default: disabled.\n");
SAY(" --passwd <str> ───────────── HTTP basic auth passwd. Default: empty.\n");
SAY(" --static <path> ───────────── Path to dir with static files instead of embedded root index page.");
@@ -661,7 +682,7 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
SAY(" the CPU loading. Don't use this option with analog signal sources");
SAY(" or webcams, it's useless. Default: disabled.\n");
SAY(" -R|--fake-resolution <WxH> ─ Override image resolution for the /state. Default: disabled.\n");
SAY(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Ignored for --unix.");
SAY(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Only for TCP socket.");
SAY(" Default: disabled.\n");
SAY(" --allow-origin <str> ─────── Set Access-Control-Allow-Origin header. Default: disabled.\n");
SAY(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n", server->timeout);
@@ -697,6 +718,8 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
# ifdef HAS_PDEATHSIG
SAY(" --exit-on-parent-death ─────── Exit the program if the parent process is dead. Default: disabled.\n");
# endif
SAY(" --exit-on-no-clients <sec> ──── Exit the program if there have been no stream or sink clients");
SAY(" or any HTTP requests in the last N seconds. Default: 0 (disabled)\n");
# ifdef WITH_SETPROCTITLE
SAY(" --process-name-prefix <str> ── Set process name prefix which will be displayed in the process list");
SAY(" like '<str>: ustreamer --blah-blah-blah'. Default: disabled.\n");

View File

@@ -131,16 +131,7 @@ void stream_loop(stream_s *stream) {
# endif
if (stream->slowdown) {
unsigned slc = 0;
while (
slc < 10
&& !atomic_load(&RUN(stop))
&& !atomic_load(&RUN(video->has_clients))
// has_clients синков НЕ обновляются в реальном времени
&& (stream->sink == NULL || !atomic_load(&stream->sink->has_clients))
# ifdef WITH_OMX
&& (RUN(h264) == NULL || /*RUN(h264->sink) == NULL ||*/ !atomic_load(&RUN(h264->sink->has_clients)))
# endif
) {
for (; slc < 10 && !atomic_load(&RUN(stop)) && !stream_has_clients(stream); ++slc) {
usleep(100000);
++slc;
}
@@ -252,6 +243,17 @@ void stream_loop_break(stream_s *stream) {
atomic_store(&RUN(stop), true);
}
bool stream_has_clients(stream_s *stream) {
return (
atomic_load(&RUN(video->has_clients))
// has_clients синков НЕ обновляются в реальном времени
|| (stream->sink != NULL && atomic_load(&stream->sink->has_clients))
# ifdef WITH_OMX
|| (RUN(h264) != NULL && /*RUN(h264->sink) == NULL ||*/ atomic_load(&RUN(h264->sink->has_clients)))
# endif
);
}
static workers_pool_s *_stream_init_loop(stream_s *stream) {
workers_pool_s *pool = NULL;

View File

@@ -97,3 +97,5 @@ void stream_destroy(stream_s *stream);
void stream_loop(stream_s *stream);
void stream_loop_break(stream_s *stream);
bool stream_has_clients(stream_s *stream);