mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 04:06:30 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61ab2a81a5 | ||
|
|
d9b511c69f | ||
|
|
4d9e72e313 | ||
|
|
616a3eb6a6 | ||
|
|
3a8f035014 | ||
|
|
be2e0f11da | ||
|
|
fec76dc9eb | ||
|
|
48826208fd | ||
|
|
dd4fda6f5d | ||
|
|
1dafb54621 | ||
|
|
19f9567098 | ||
|
|
b3d1f06e5d | ||
|
|
f17069153d | ||
|
|
8a7d7b9c54 | ||
|
|
df7649d56f | ||
|
|
58cc227cba | ||
|
|
76be4a1d10 | ||
|
|
496c9300fc | ||
|
|
5b7780cf7c | ||
|
|
421f4a1f2e | ||
|
|
55a5d4bbdd | ||
|
|
666ae0c4f1 | ||
|
|
ca3afc074c | ||
|
|
030077fb47 | ||
|
|
61ddff8b6e | ||
|
|
27fbfe149e | ||
|
|
349f655cd9 | ||
|
|
f5c0a15967 | ||
|
|
d822ae0890 |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 4.7
|
current_version = 4.13
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|||||||
@@ -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` 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```.
|
||||||
@@ -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:
|
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
|
```bash
|
||||||
|
$ export LD_LIBRARY_PATH=/opt/vc/lib/ # on bullseye
|
||||||
$ ./ustreamer \
|
$ ./ustreamer \
|
||||||
--format=uyvy \ # Device input format
|
--format=uyvy \ # Device input format
|
||||||
--encoder=omx \ # Hardware encoding with OpenMAX
|
--encoder=omx \ # Hardware encoding with OpenMAX
|
||||||
@@ -122,7 +124,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
|
||||||
|
|||||||
@@ -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```.
|
||||||
@@ -122,7 +123,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-сервиса.
|
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Лицензия
|
# Лицензия
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
|
|||||||
skipsdist = true
|
skipsdist = true
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython = python3.9
|
basepython = python3.10
|
||||||
changedir = /src
|
changedir = /src
|
||||||
|
|
||||||
[testenv:cppcheck]
|
[testenv:cppcheck]
|
||||||
|
|||||||
@@ -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.7" "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
|
||||||
|
|||||||
@@ -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.7" "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
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=4.7
|
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
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ustreamer
|
PKG_NAME:=ustreamer
|
||||||
PKG_VERSION:=4.7
|
PKG_VERSION:=4.13
|
||||||
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.7",
|
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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define VERSION_MAJOR 4
|
#define VERSION_MAJOR 4
|
||||||
#define VERSION_MINOR 7
|
#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)
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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> \
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ int device_switch_capturing(device_s *dev, bool enable) {
|
|||||||
RUN(capturing) = enable;
|
RUN(capturing) = enable;
|
||||||
LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
|
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) {
|
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) ...");
|
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
|
||||||
if (xioctl(RUN(fd), VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
|
if (xioctl(RUN(fd), VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
|
||||||
LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu",
|
if (dv.type == V4L2_DV_BT_656_1120) {
|
||||||
dv.bt.width, dv.bt.height, (unsigned long long)dv.bt.pixelclock); // Issue #11
|
// 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) ...");
|
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
|
||||||
if (xioctl(RUN(fd), VIDIOC_S_DV_TIMINGS, &dv) < 0) {
|
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) {
|
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) {
|
if (format == _FORMATS[index].format) {
|
||||||
return _FORMATS[index].name;
|
return _FORMATS[index].name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *_format_to_string_supported(unsigned format) {
|
static const char *_format_to_string_supported(unsigned format) {
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
@@ -317,6 +337,10 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
|||||||
goto not_found;
|
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));
|
ADD_HEADER("Content-Type", guess_mime_type(static_path));
|
||||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
@@ -421,13 +445,14 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
|||||||
LIST_ITERATE(RUN(stream_clients), client, {
|
LIST_ITERATE(RUN(stream_clients), client, {
|
||||||
assert(evbuffer_add_printf(buf,
|
assert(evbuffer_add_printf(buf,
|
||||||
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
|
"\"%" 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->id,
|
||||||
client->fps,
|
client->fps,
|
||||||
bool_to_string(client->extra_headers),
|
bool_to_string(client->extra_headers),
|
||||||
bool_to_string(client->advance_headers),
|
bool_to_string(client->advance_headers),
|
||||||
bool_to_string(client->dual_final_frames),
|
bool_to_string(client->dual_final_frames),
|
||||||
bool_to_string(client->zero_data),
|
bool_to_string(client->zero_data),
|
||||||
|
(client->key != NULL ? client->key : "0"),
|
||||||
(client->next ? ", " : "")
|
(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));
|
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;
|
||||||
|
|
||||||
@@ -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;
|
server_s *server = (server_s *)v_server;
|
||||||
bool stream_updated = false;
|
bool stream_updated = false;
|
||||||
bool frame_updated = false;
|
bool frame_updated = false;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
46
src/ustreamer/http/systemd/systemd.c
Normal file
46
src/ustreamer/http/systemd/systemd.c
Normal 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;
|
||||||
|
}
|
||||||
37
src/ustreamer/http/systemd/systemd.h
Normal file
37
src/ustreamer/http/systemd/systemd.h
Normal 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);
|
||||||
@@ -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);
|
||||||
@@ -438,7 +449,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
|||||||
ADD_SINK("raw-", raw_sink, RAW_SINK)
|
ADD_SINK("raw-", raw_sink, RAW_SINK)
|
||||||
# ifdef WITH_OMX
|
# ifdef WITH_OMX
|
||||||
ADD_SINK("h264-", h264_sink, H264_SINK)
|
ADD_SINK("h264-", h264_sink, H264_SINK)
|
||||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 100, 16000, 0);
|
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 25000, 0);
|
||||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||||
# endif
|
# endif
|
||||||
# undef ADD_SINK
|
# undef ADD_SINK
|
||||||
@@ -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");
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user