Compare commits

...

6 Commits
v4.12 ... 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
18 changed files with 231 additions and 43 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion] [bumpversion]
commit = True commit = True
tag = True tag = True
current_version = 4.12 current_version = 4.13
parse = (?P<major>\d+)\.(?P<minor>\d+) parse = (?P<major>\d+)\.(?P<minor>\d+)
serialize = serialize =
{major}.{minor} {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> | | [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> | ✘ | | Option to skip frames when streaming<br>static images by HTTP to save the traffic | ✔ <sup>2</sup> | ✘ |
| Streaming via UNIX domain socket | ✔ | ✘ | | Streaming via UNIX domain socket | ✔ | ✘ |
| Systemd socket activation | ✔ | ✘ |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters | ✔ | ✘ | | 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 | | 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) | ✔ | ✘ | | 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). 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`. * 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`. * 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```. 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```.

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> | | Поддержка [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> | ✘ | | Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ✔ <sup>2</sup> | ✘ |
| Стрим через UNIX domain socket | ✔ | ✘ | | Стрим через UNIX domain socket | ✔ | ✘ |
| Systemd socket activation | ✔ | ✘ |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ✔ | ✘ | | Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ✔ | ✘ |
| Возможность сервить файлы встроенным<br>HTTP-сервером | ✔ | ☹ Нет каталогов | | Возможность сервить файлы встроенным<br>HTTP-сервером | ✔ | ☹ Нет каталогов |
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ | | Вывод сигналов о состоянии стрима на 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). Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libbsd``` (только для 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`. Добавьте `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`. * 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```. На 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

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

View 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 4.12" "November 2020" .TH USTREAMER 1 "version 4.13" "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
@@ -167,12 +167,15 @@ Bind to this TCP port. Default: 8080.
.BR \-U\ \fIpath ", " \-\-unix\ \fIpath .BR \-U\ \fIpath ", " \-\-unix\ \fIpath
Bind to UNIX domain socket. Default: disabled. Bind to UNIX domain socket. Default: disabled.
.TP .TP
.BR \-d ", " \-\-unix\-rm .BR \-D ", " \-\-unix\-rm
Try to remove old unix socket file before binding. default: disabled. Try to remove old unix socket file before binding. default: disabled.
.TP .TP
.BR \-M\ \fImode ", " \-\-unix\-mode\ \fImode .BR \-M\ \fImode ", " \-\-unix\-mode\ \fImode
Set UNIX socket file permissions (like 777). Default: disabled. Set UNIX socket file permissions (like 777). Default: disabled.
.TP .TP
.BR \-S ", " \-\-systemd
Bind to systemd socket for socket activation. Required \fBWITH_SYSTEMD\fR feature. Default: disabled.
.TP
.BR \-\-user\ \fIname .BR \-\-user\ \fIname
HTTP basic auth user. Default: disabled. HTTP basic auth user. Default: disabled.
.TP .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. Override image resolution for the /state. Default: disabled.
.TP .TP
.BR \-\-tcp\-nodelay .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. Default: disabled.
.TP .TP
.BR \-\-allow\-origin\ \fIstr .BR \-\-allow\-origin\ \fIstr
@@ -246,6 +249,9 @@ Intarval between keyframes. Default: 30.
.BR \-\-exit\-on\-parent\-death .BR \-\-exit\-on\-parent\-death
Exit the program if the parent process is dead. Required \fBHAS_PDEATHSIG\fR feature. Default: disabled. Exit the program if the parent process is dead. Required \fBHAS_PDEATHSIG\fR feature. Default: disabled.
.TP .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 .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. 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 .TP

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer pkgname=ustreamer
pkgver=4.12 pkgver=4.13
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"
@@ -31,6 +31,11 @@ if [ -e /usr/include/janus/plugins/plugin.h ];then
makedepends+=(janus-gateway-pikvm) makedepends+=(janus-gateway-pikvm)
_options="$_options WITH_JANUS=1" _options="$_options WITH_JANUS=1"
fi 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 # LD does not link mmal with this option

View File

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

View File

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

View File

@@ -59,6 +59,13 @@ _USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
endif 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 WITH_PTHREAD_NP ?= 1
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),) ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
override _CFLAGS += -DWITH_PTHREAD_NP override _CFLAGS += -DWITH_PTHREAD_NP

View File

@@ -23,7 +23,7 @@
#pragma once #pragma once
#define VERSION_MAJOR 4 #define VERSION_MAJOR 4
#define VERSION_MINOR 12 #define VERSION_MINOR 13
#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)

View File

@@ -137,3 +137,11 @@ INLINE void process_notify_parent(void) {
LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent); 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

@@ -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_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_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 void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated);
static bool _expose_new_frame(server_s *server); 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) { void server_destroy(server_s *server) {
if (RUN(refresh)) { if (RUN(refresher)) {
event_del(RUN(refresh)); event_del(RUN(refresher));
event_free(RUN(refresh)); event_free(RUN(refresher));
}
if (RUN(request_watcher)) {
event_del(RUN(request_watcher));
event_free(RUN(request_watcher));
} }
evhttp_free(RUN(http)); evhttp_free(RUN(http));
if (RUN(unix_fd)) { if (RUN(ext_fd)) {
close(RUN(unix_fd)); close(RUN(ext_fd));
} }
event_base_free(RUN(base)); 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_width) = EX(frame->width);
EX(notify_last_height) = EX(frame->height); 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; struct timeval interval = {0};
refresh_interval.tv_sec = 0;
if (STREAM(dev->desired_fps) > 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 { } else {
refresh_interval.tv_usec = 16000; // ~60fps interval.tv_usec = 16000; // ~60fps
} }
assert((RUN(refresher) = event_new(RUN(base), -1, EV_PERSIST, _http_refresher, server)));
assert((RUN(refresh) = event_new(RUN(base), -1, EV_PERSIST, _http_exposed_refresh, server))); assert(!event_add(RUN(refresher), &interval));
assert(!event_add(RUN(refresh), &refresh_interval));
} }
evhttp_set_timeout(RUN(http), server->timeout); evhttp_set_timeout(RUN(http), server->timeout);
@@ -160,7 +171,7 @@ int server_listen(server_s *server) {
if (server->unix_path[0] != '\0') { if (server->unix_path[0] != '\0') {
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path); 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), RUN(http),
server->unix_path, server->unix_path,
server->unix_rm, server->unix_rm,
@@ -169,9 +180,16 @@ int server_listen(server_s *server) {
return -1; return -1;
} }
LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path); 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 { } else {
LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port); LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
if (evhttp_bind_socket(RUN(http), server->host, server->port) < 0) { 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)) assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
static int _http_preprocess_request(struct evhttp_request *request, server_s *server) { static int _http_preprocess_request(struct evhttp_request *request, server_s *server) {
RUN(last_request_ts) = get_now_monotonic();
if (RUN(auth_token)) { if (RUN(auth_token)) {
const char *token = evhttp_find_header(evhttp_request_get_input_headers(request), "Authorization"); const char *token = evhttp_find_header(evhttp_request_get_input_headers(request), "Authorization");
@@ -546,7 +566,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->hostport, client->id, RUN(stream_clients_count)); client->hostport, client->id, RUN(stream_clients_count));
struct bufferevent *buf_event = evhttp_connection_get_bufferevent(conn); 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; evutil_socket_t fd;
int on = 1; int on = 1;
@@ -774,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; server_s *server = (server_s *)v_server;
bool stream_updated = false; bool stream_updated = false;
bool frame_updated = false; bool frame_updated = false;

View File

@@ -71,6 +71,9 @@
#include "uri.h" #include "uri.h"
#include "mime.h" #include "mime.h"
#include "static.h" #include "static.h"
#ifdef WITH_SYSTEMD
# include "systemd/systemd.h"
#endif
typedef struct stream_client_sx { typedef struct stream_client_sx {
@@ -112,11 +115,17 @@ typedef struct {
typedef struct { typedef struct {
struct event_base *base; struct event_base *base;
struct evhttp *http; struct evhttp *http;
evutil_socket_t unix_fd; evutil_socket_t ext_fd; // Unix or socket activation
char *auth_token; char *auth_token;
struct event *refresh;
struct event *request_watcher;
long double last_request_ts;
struct event *refresher;
stream_s *stream; stream_s *stream;
exposed_s *exposed; exposed_s *exposed;
stream_client_s *stream_clients; stream_client_s *stream_clients;
unsigned stream_clients_count; unsigned stream_clients_count;
} server_runtime_s; } server_runtime_s;
@@ -124,9 +133,15 @@ typedef struct {
typedef struct server_sx { typedef struct server_sx {
char *host; char *host;
unsigned port; unsigned port;
char *unix_path; char *unix_path;
bool unix_rm; bool unix_rm;
mode_t unix_mode; mode_t unix_mode;
# ifdef WITH_SYSTEMD
bool systemd;
# endif
bool tcp_nodelay; bool tcp_nodelay;
unsigned timeout; unsigned timeout;
@@ -140,6 +155,7 @@ typedef struct server_sx {
unsigned fake_height; unsigned fake_height;
bool notify_parent; bool notify_parent;
unsigned exit_on_no_clients;
server_runtime_s *run; server_runtime_s *run;
} server_s; } 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 = 'U',
_O_UNIX_RM = 'D', _O_UNIX_RM = 'D',
_O_UNIX_MODE = 'M', _O_UNIX_MODE = 'M',
# ifdef WITH_SYSTEMD
_O_SYSTEMD = 'S',
# endif
_O_DROP_SAME_FRAMES = 'e', _O_DROP_SAME_FRAMES = 'e',
_O_FAKE_RESOLUTION = 'R', _O_FAKE_RESOLUTION = 'R',
@@ -109,6 +112,7 @@ enum _OPT_VALUES {
# ifdef HAS_PDEATHSIG # ifdef HAS_PDEATHSIG
_O_EXIT_ON_PARENT_DEATH, _O_EXIT_ON_PARENT_DEATH,
# endif # endif
_O_EXIT_ON_NO_CLIENTS,
# ifdef WITH_SETPROCTITLE # ifdef WITH_SETPROCTITLE
_O_PROCESS_NAME_PREFIX, _O_PROCESS_NAME_PREFIX,
# endif # endif
@@ -168,6 +172,9 @@ static const struct option _LONG_OPTS[] = {
{"unix", required_argument, NULL, _O_UNIX}, {"unix", required_argument, NULL, _O_UNIX},
{"unix-rm", no_argument, NULL, _O_UNIX_RM}, {"unix-rm", no_argument, NULL, _O_UNIX_RM},
{"unix-mode", required_argument, NULL, _O_UNIX_MODE}, {"unix-mode", required_argument, NULL, _O_UNIX_MODE},
# ifdef WITH_SYSTEMD
{"systemd", no_argument, NULL, _O_SYSTEMD},
# endif
{"user", required_argument, NULL, _O_USER}, {"user", required_argument, NULL, _O_USER},
{"passwd", required_argument, NULL, _O_PASSWD}, {"passwd", required_argument, NULL, _O_PASSWD},
{"static", required_argument, NULL, _O_STATIC}, {"static", required_argument, NULL, _O_STATIC},
@@ -203,6 +210,7 @@ static const struct option _LONG_OPTS[] = {
# ifdef HAS_PDEATHSIG # ifdef HAS_PDEATHSIG
{"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH}, {"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH},
# endif # endif
{"exit-on-no-clients", required_argument, NULL, _O_EXIT_ON_NO_CLIENTS},
# ifdef WITH_SETPROCTITLE # ifdef WITH_SETPROCTITLE
{"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX}, {"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX},
# endif # 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: OPT_SET(server->unix_path, optarg);
case _O_UNIX_RM: OPT_SET(server->unix_rm, true); 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); 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_USER: OPT_SET(server->user, optarg);
case _O_PASSWD: OPT_SET(server->passwd, optarg); case _O_PASSWD: OPT_SET(server->passwd, optarg);
case _O_STATIC: OPT_SET(server->static_path, 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; break;
# endif # endif
case _O_EXIT_ON_NO_CLIENTS: OPT_NUMBER("--exit-on-no-clients", server->exit_on_no_clients, 0, 86400, 0);
# ifdef WITH_SETPROCTITLE # ifdef WITH_SETPROCTITLE
case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg); case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg);
# endif # endif
@@ -551,6 +563,12 @@ static void _features(void) {
puts("- WITH_GPIO"); puts("- WITH_GPIO");
# endif # endif
# ifdef WITH_SYSTEMD
puts("+ WITH_SYSTEMD");
# else
puts("- WITH_SYSTEMD");
# endif
# ifdef WITH_PTHREAD_NP # ifdef WITH_PTHREAD_NP
puts("+ WITH_PTHREAD_NP"); puts("+ WITH_PTHREAD_NP");
# else # 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(" -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(" -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"); 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(" --user <name> ────────────── HTTP basic auth user. Default: disabled.\n");
SAY(" --passwd <str> ───────────── HTTP basic auth passwd. Default: empty.\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."); 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(" the CPU loading. Don't use this option with analog signal sources");
SAY(" or webcams, it's useless. Default: disabled.\n"); SAY(" or webcams, it's useless. Default: disabled.\n");
SAY(" -R|--fake-resolution <WxH> ─ Override image resolution for the /state. 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(" Default: disabled.\n");
SAY(" --allow-origin <str> ─────── Set Access-Control-Allow-Origin header. 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); 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 # ifdef HAS_PDEATHSIG
SAY(" --exit-on-parent-death ─────── Exit the program if the parent process is dead. Default: disabled.\n"); SAY(" --exit-on-parent-death ─────── Exit the program if the parent process is dead. Default: disabled.\n");
# endif # 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 # ifdef WITH_SETPROCTITLE
SAY(" --process-name-prefix <str> ── Set process name prefix which will be displayed in the process list"); 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"); SAY(" like '<str>: ustreamer --blah-blah-blah'. Default: disabled.\n");

View File

@@ -131,16 +131,7 @@ void stream_loop(stream_s *stream) {
# endif # endif
if (stream->slowdown) { if (stream->slowdown) {
unsigned slc = 0; unsigned slc = 0;
while ( for (; slc < 10 && !atomic_load(&RUN(stop)) && !stream_has_clients(stream); ++slc) {
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
) {
usleep(100000); usleep(100000);
++slc; ++slc;
} }
@@ -252,6 +243,17 @@ void stream_loop_break(stream_s *stream) {
atomic_store(&RUN(stop), true); 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) { static workers_pool_s *_stream_init_loop(stream_s *stream) {
workers_pool_s *pool = NULL; 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(stream_s *stream);
void stream_loop_break(stream_s *stream); void stream_loop_break(stream_s *stream);
bool stream_has_clients(stream_s *stream);