mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b7cbc62c4 | ||
|
|
dff49d8e7b | ||
|
|
b23883e65f | ||
|
|
c2e30c7fc4 | ||
|
|
37216250b8 | ||
|
|
dc82894038 | ||
|
|
f3a350148e | ||
|
|
5e49f50e22 | ||
|
|
91ff97f721 | ||
|
|
daaa488dd6 | ||
|
|
bad05a6827 | ||
|
|
89d3ed98c7 | ||
|
|
1187ace2a2 | ||
|
|
9704fb054c | ||
|
|
b10148a438 | ||
|
|
1b4bcd09fe | ||
|
|
245cd94a4c | ||
|
|
8a24a0b129 | ||
|
|
b362c1fa50 | ||
|
|
0414fa5a51 | ||
|
|
191dd16d7b | ||
|
|
31a03928c9 | ||
|
|
185a8b6773 | ||
|
|
b7501cfbda | ||
|
|
1dc90dad7a | ||
|
|
45a4d148f5 | ||
|
|
93ab12b54b | ||
|
|
fb07444b70 | ||
|
|
a8ba2f7364 | ||
|
|
171bcb315a | ||
|
|
911df1af15 | ||
|
|
a165ff4523 | ||
|
|
5cfb3b1e60 | ||
|
|
82b3b78238 | ||
|
|
8a82ff6691 | ||
|
|
2807678551 | ||
|
|
e96e0aa73c | ||
|
|
d06c2619b2 | ||
|
|
6b18455d11 | ||
|
|
217977d9cb | ||
|
|
b9f186e47c | ||
|
|
d7d56f3536 | ||
|
|
2e3c764369 | ||
|
|
7236e53813 | ||
|
|
e7f7350405 | ||
|
|
81f0266a87 | ||
|
|
eb1a2e3695 | ||
|
|
6cfd3739d3 | ||
|
|
77b8386de7 | ||
|
|
a002bd3427 | ||
|
|
202b907430 | ||
|
|
d9f4aba953 | ||
|
|
1b08857534 | ||
|
|
eec19892fa | ||
|
|
40abe73391 | ||
|
|
e4f1ef654f | ||
|
|
fa6afb96ce | ||
|
|
c3f98b34f2 | ||
|
|
b7b3e8e87d | ||
|
|
94383a2d54 | ||
|
|
184a4879eb | ||
|
|
bf48908c59 | ||
|
|
66afbccf21 | ||
|
|
97dbe59aea | ||
|
|
d874fdeaec | ||
|
|
97e3938d56 | ||
|
|
87c7e8063f | ||
|
|
fe5beb0114 | ||
|
|
eec8e41b2b | ||
|
|
87bff56a78 | ||
|
|
34e0e4dab4 | ||
|
|
e08ac1467f | ||
|
|
bc25e787cc | ||
|
|
5f11caf6fc | ||
|
|
9be264e176 | ||
|
|
3d28dcbaff | ||
|
|
61c3b44c8a | ||
|
|
e26973a9f1 | ||
|
|
598e2372e5 | ||
|
|
4fb8c7745c | ||
|
|
14131f0b54 | ||
|
|
de41c9653e | ||
|
|
66e0bb0a2c | ||
|
|
5af18e8b70 | ||
|
|
b746bc307c | ||
|
|
3fd3aab909 | ||
|
|
2f1afb6044 | ||
|
|
a016c1040e | ||
|
|
d4e9948220 | ||
|
|
5d1183f5c6 | ||
|
|
c75863d4bd | ||
|
|
5576cbb3b8 | ||
|
|
4a156a692a | ||
|
|
1a8dfb1f1b |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 3.0
|
||||
current_version = 3.22
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
@@ -10,6 +10,10 @@ serialize =
|
||||
search = VERSION "{current_version}"
|
||||
replace = VERSION "{new_version}"
|
||||
|
||||
[bumpversion:file:python/setup.py]
|
||||
search = version="{current_version}"
|
||||
replace = version="{new_version}"
|
||||
|
||||
[bumpversion:file:pkg/arch/PKGBUILD]
|
||||
search = pkgver={current_version}
|
||||
replace = pkgver={new_version}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,8 +6,10 @@
|
||||
/pkg/arch/ustreamer-*.pkg.tar.xz
|
||||
/pkg/arch/ustreamer-*.pkg.tar.zst
|
||||
/build/
|
||||
/python/build/
|
||||
/config.mk
|
||||
/vgcore.*
|
||||
/ustreamer
|
||||
/ustreamer-dump
|
||||
/*.so
|
||||
/*.sock
|
||||
|
||||
70
Makefile
70
Makefile
@@ -7,7 +7,8 @@ PREFIX ?= /usr/local
|
||||
MANPREFIX ?= $(PREFIX)/share/man
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -O3 -MD
|
||||
PY ?= python3
|
||||
CFLAGS ?= -O3
|
||||
LDFLAGS ?=
|
||||
|
||||
RPI_VC_HEADERS ?= /opt/vc/include
|
||||
@@ -19,11 +20,12 @@ LINTERS_IMAGE ?= $(USTR)-linters
|
||||
|
||||
|
||||
# =====
|
||||
override CFLAGS += -c -std=c11 -Wall -Wextra -D_GNU_SOURCE
|
||||
_CFLAGS = -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
||||
_LDFLAGS = $(LDFLAGS)
|
||||
|
||||
_COMMON_LIBS = -lm -ljpeg -pthread -lrt
|
||||
|
||||
_USTR_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads -luuid
|
||||
_USTR_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads
|
||||
_USTR_SRCS = $(shell ls \
|
||||
src/libs/*.c \
|
||||
src/ustreamer/*.c \
|
||||
@@ -47,7 +49,7 @@ endef
|
||||
|
||||
ifneq ($(call optbool,$(WITH_OMX)),)
|
||||
_USTR_LIBS += -lbcm_host -lvcos -lvcsm -lopenmaxil -lmmal -lmmal_core -lmmal_util -lmmal_vc_client -lmmal_components -L$(RPI_VC_LIBS)
|
||||
override CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
|
||||
override _CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
|
||||
_USTR_SRCS += $(shell ls \
|
||||
src/ustreamer/encoders/omx/*.c \
|
||||
src/ustreamer/h264/*.c \
|
||||
@@ -57,14 +59,14 @@ endif
|
||||
|
||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||
_USTR_LIBS += -lgpiod
|
||||
override CFLAGS += -DWITH_GPIO
|
||||
override _CFLAGS += -DWITH_GPIO
|
||||
_USTR_SRCS += $(shell ls src/ustreamer/gpio/*.c)
|
||||
endif
|
||||
|
||||
|
||||
WITH_PTHREAD_NP ?= 1
|
||||
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
|
||||
override CFLAGS += -DWITH_PTHREAD_NP
|
||||
override _CFLAGS += -DWITH_PTHREAD_NP
|
||||
endif
|
||||
|
||||
|
||||
@@ -73,21 +75,25 @@ ifneq ($(call optbool,$(WITH_SETPROCTITLE)),)
|
||||
ifeq ($(shell uname -s | tr A-Z a-z),linux)
|
||||
_USTR_LIBS += -lbsd
|
||||
endif
|
||||
override CFLAGS += -DWITH_SETPROCTITLE
|
||||
override _CFLAGS += -DWITH_SETPROCTITLE
|
||||
endif
|
||||
|
||||
|
||||
# =====
|
||||
all: $(USTR) $(DUMP)
|
||||
all: $(USTR) $(DUMP) python
|
||||
|
||||
|
||||
install: $(USTR) $(DUMP)
|
||||
install -Dm755 $(USTR) $(DESTDIR)$(PREFIX)/bin/$(USTR)
|
||||
install -Dm755 $(DUMP) $(DESTDIR)$(PREFIX)/bin/$(DUMP)
|
||||
install -Dm644 man/$(USTR).1 $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
|
||||
install -Dm644 man/$(DUMP).1 $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
|
||||
gzip $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
|
||||
gzip $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(MANPREFIX)/man1
|
||||
install -m755 $(USTR) $(DESTDIR)$(PREFIX)/bin/$(USTR)
|
||||
install -m755 $(DUMP) $(DESTDIR)$(PREFIX)/bin/$(DUMP)
|
||||
install -m644 man/$(USTR).1 $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
|
||||
install -m644 man/$(DUMP).1 $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
|
||||
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
|
||||
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
|
||||
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
||||
cd python && $(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
|
||||
endif
|
||||
|
||||
|
||||
install-strip: install
|
||||
@@ -95,13 +101,6 @@ install-strip: install
|
||||
strip $(DESTDIR)$(PREFIX)/bin/$(DUMP)
|
||||
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/$(USTR) \
|
||||
$(DESTDIR)$(PREFIX)/bin/$(DUMP) \
|
||||
$(DESTDIR)$(MANPREFIX)/man1/$(USTR).1 \
|
||||
$(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
|
||||
|
||||
|
||||
regen:
|
||||
tools/make-jpeg-h.py src/ustreamer/data/blank.jpeg src/ustreamer/data/blank_jpeg.c BLANK
|
||||
tools/make-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX
|
||||
@@ -110,29 +109,38 @@ regen:
|
||||
$(USTR): $(_USTR_SRCS:%.c=$(BUILD)/%.o)
|
||||
# $(info ========================================)
|
||||
$(info == LD $@)
|
||||
@ $(CC) $^ -o $@ $(LDFLAGS) $(_USTR_LIBS)
|
||||
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
|
||||
# $(info :: CC = $(CC))
|
||||
# $(info :: LIBS = $(_USTR_LIBS))
|
||||
# $(info :: CFLAGS = $(CFLAGS))
|
||||
# $(info :: LDFLAGS = $(LDFLAGS))
|
||||
# $(info :: CFLAGS = $(_CFLAGS))
|
||||
# $(info :: LDFLAGS = $(_LDFLAGS))
|
||||
|
||||
|
||||
$(DUMP): $(_DUMP_SRCS:%.c=$(BUILD)/%.o)
|
||||
# $(info ========================================)
|
||||
$(info == LD $@)
|
||||
@ $(CC) $^ -o $@ $(LDFLAGS) $(_DUMP_LIBS)
|
||||
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
||||
# $(info :: CC = $(CC))
|
||||
# $(info :: LIBS = $(_DUMP_LIBS))
|
||||
# $(info :: CFLAGS = $(CFLAGS))
|
||||
# $(info :: LDFLAGS = $(LDFLAGS))
|
||||
# $(info :: CFLAGS = $(_CFLAGS))
|
||||
# $(info :: LDFLAGS = $(_LDFLAGS))
|
||||
|
||||
|
||||
$(BUILD)/%.o: %.c
|
||||
$(info -- CC $<)
|
||||
@ mkdir -p $(dir $@) || true
|
||||
@ $(CC) $< -o $@ $(CFLAGS)
|
||||
@ $(CC) $< -o $@ $(_CFLAGS)
|
||||
|
||||
|
||||
python:
|
||||
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
||||
$(info == PY_BUILD ustreamer-*.so)
|
||||
@ cd python && $(PY) setup.py build
|
||||
@ ln -sf python/build/lib.*/*.so .
|
||||
else
|
||||
@ true
|
||||
endif
|
||||
|
||||
release:
|
||||
make clean
|
||||
make tox
|
||||
@@ -175,10 +183,10 @@ clean-all: linters clean
|
||||
-it $(LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
|
||||
clean:
|
||||
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
|
||||
rm -rf $(USTR) $(DUMP) $(BUILD) vgcore.* *.sock
|
||||
rm -rf $(USTR) $(DUMP) $(BUILD) python/build vgcore.* *.sock *.so
|
||||
|
||||
|
||||
.PHONY: linters
|
||||
.PHONY: python linters
|
||||
|
||||
|
||||
_OBJS = $(_USTR_SRCS:%.c=$(BUILD)/%.o) $(_DUMP_SRCS:%.c=$(BUILD)/%.o)
|
||||
|
||||
61
README.md
61
README.md
@@ -11,21 +11,21 @@
|
||||
|
||||
| **Feature** | **µStreamer** | **mjpg-streamer** |
|
||||
|----------|---------------|-------------------|
|
||||
| Multithreaded JPEG encoding |  Yes |  No |
|
||||
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi |  Yes |  No |
|
||||
| Behavior when the device<br>is disconnected while streaming |  Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected |  Stops the streaming <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 |  Yes |  Partially yes <sup>1</sup> |
|
||||
| Option to skip frames when streaming<br>static images by HTTP to save the traffic |  Yes <sup>2</sup> |  No |
|
||||
| Streaming via UNIX domain socket |  Yes |  No |
|
||||
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters |  Yes |  No |
|
||||
| Option to serve files<br>with a built-in HTTP server |  Yes |  Regular files only |
|
||||
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) |  Yes |  No |
|
||||
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP |  No |  Yes |
|
||||
| Compatibility with mjpg-streamer's API |  Yes | :) |
|
||||
| Multithreaded JPEG encoding | ✔ | ✘ |
|
||||
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ✔ | ✘ |
|
||||
| Behavior when the device<br>is disconnected while streaming | ✔ Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ✘ Stops the streaming <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> | ✘ |
|
||||
| Streaming via UNIX domain socket | ✔ | ✘ |
|
||||
| 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) | ✔ | ✘ |
|
||||
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ✘ | ✔ |
|
||||
| Compatibility with mjpg-streamer's API | ✔ | :) |
|
||||
|
||||
Footnotes:
|
||||
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
|
||||
|
||||
|
||||
* ```2``` This feature allows to cut down outgoing traffic several-fold when streaming HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to stream the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
|
||||
|
||||
-----
|
||||
@@ -34,12 +34,11 @@ If you're going to live-stream from your backyard webcam and need to control it,
|
||||
|
||||
-----
|
||||
# Building
|
||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` 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`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `libgpiod` for `WITH_GPIO=1`.
|
||||
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
|
||||
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg62-dev uuid-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libuuid1 libbsd0`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `libgpiod` for `WITH_GPIO=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```.
|
||||
|
||||
@@ -50,7 +49,7 @@ $ make
|
||||
$ ./ustreamer --help
|
||||
```
|
||||
|
||||
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
|
||||
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
|
||||
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
||||
|
||||
-----
|
||||
@@ -77,6 +76,34 @@ $ ./ustreamer \
|
||||
|
||||
You can always view the full list of options with ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Raspberry Pi Camera Example
|
||||
Example usage for the Raspberry Pi v1 camera:
|
||||
```bash
|
||||
$ sudo modprobe bcm2835-v4l2
|
||||
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
||||
```
|
||||
|
||||
:exclamation: Please note that newer camera models have a different maximum resolution. You can see the supported resolutions at the [PiCamera documentation](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes).
|
||||
|
||||
:exclamation: If you get a poor framerate, it could be that the camera is switched to photo mode, which produces a low framerate (but a higher quality picture). This is because `bcm2835-v4l2` switches to photo mode at resolutions higher than `1280x720`. To work around this, pass the `max_video_width` and `max_video_height` module parameters like so:
|
||||
|
||||
```bash
|
||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||
```
|
||||
|
||||
-----
|
||||
# Tips & tricks for v4l2
|
||||
v4l2 utilities provide the tools to manage USB webcam setting and information. Scripts can be use to make adjustments and run manually or with cron. Running in cron for example to change the exposure settings at certain times of day. The package is available in all Linux distributions and is usually called `v4l-utils`.
|
||||
|
||||
* List of available video devices: `v4l2-ctl --list-devices`.
|
||||
* List available control settings: `v4l2-ctl -d /dev/video0 --list-ctrls`.
|
||||
* List available video formats: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
|
||||
* Read the current setting: `v4l2-ctl d /dev/video0 --get-ctrl=exposure_auto`.
|
||||
* Change the setting value: `v4l2-ctl d /dev/video0 --set-ctrl=exposure_auto=1`.
|
||||
|
||||
[Here](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/) you can find more examples. Documentation is available in [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
|
||||
|
||||
-----
|
||||
# See also
|
||||
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
|
||||
|
||||
59
README.ru.md
59
README.ru.md
@@ -11,21 +11,21 @@
|
||||
|
||||
| **Фича** | **µStreamer** | **mjpg-streamer** |
|
||||
|----------|---------------|-------------------|
|
||||
| Многопоточное кодирование JPEG |  Есть |  Нет |
|
||||
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi |  Есть |  Нет |
|
||||
| Поведение при физическом отключении<br>устройства от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<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> |  Нет |
|
||||
| Стрим через UNIX domain socket |  Есть |  Нет |
|
||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
||||
| Возможность сервить файлы встроенным<br>HTTP-сервером |  Есть |  Нет каталогов |
|
||||
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) |  Есть |  Нет |
|
||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
||||
| Совместимость с API mjpg-streamer'а |  Есть | :) |
|
||||
| Многопоточное кодирование JPEG | ✔ | ✘ |
|
||||
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ✔ | ✘ |
|
||||
| Поведение при физическом отключении<br>устройства от сервера во время работы | ✔ Транслирует черный экран<br>с надписью ```NO SIGNAL```,<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> | ✘ |
|
||||
| Стрим через UNIX domain socket | ✔ | ✘ |
|
||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ✔ | ✘ |
|
||||
| Возможность сервить файлы встроенным<br>HTTP-сервером | ✔ | ☹ Нет каталогов |
|
||||
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
|
||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ✘ | ✔ |
|
||||
| Совместимость с API mjpg-streamer'а | ✔ | :) |
|
||||
|
||||
Сносочки:
|
||||
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
||||
|
||||
|
||||
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
|
||||
|
||||
-----
|
||||
@@ -34,12 +34,11 @@
|
||||
|
||||
-----
|
||||
# Сборка
|
||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux).
|
||||
Для сборки вам понадобятся ```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 uuid-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `libgpiod` для `WITH_GPIO=1`.
|
||||
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
|
||||
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg62-dev uuid-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libuuid1 libbsd0`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `libgpiod` для `WITH_GPIO=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```.
|
||||
|
||||
@@ -77,6 +76,34 @@ $ ./ustreamer \
|
||||
|
||||
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Камера Raspberry Pi
|
||||
Пример использования камеры Raspberry Pi v1:
|
||||
```bash
|
||||
$ sudo modprobe bcm2835-v4l2
|
||||
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
||||
```
|
||||
|
||||
:exclamation: Обратите внимание что боле новые модели камеры имеют другое максимальное разрешение. Список поддерживаемых разрешений можно найти в [документации PiCamera](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes).
|
||||
|
||||
:exclamation: Если камера выдает низкий фреймрейт, возможно что она работает в фото-режиме, где производит более низкий фпс, но более качественную кратинку. Это происходит потому что `bcm2835-v4l2` переключает камеру в фото-режим на разрешениях выше `1280x720`. Чтобы обойти это, передайте параметры `max_video_width` и `max_video_height` при загрузке модуля, например:
|
||||
|
||||
```bash
|
||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||
```
|
||||
|
||||
-----
|
||||
# Утилиты V4L2
|
||||
V4L2 предоставляет ряд официальных утилит для управления USB-вебкамерами и получения информации об устройствах. С их помощью можно писать всякие настроечные скрипты и запускать их по крону, если, например, вам требуется изменять настройки экспозиции в зависимости от времени суток. Пакет с этими утилитами доступен на всех дистрибутивах Linux и обычно называется `v4l-utils`.
|
||||
|
||||
* Вывести список видеоустройств: `v4l2-ctl --list-devices`.
|
||||
* Вывести список доступных контролов устройства: `v4l2-ctl -d /dev/video0 --list-ctrls`.
|
||||
* Вывести список доступных форматов видео: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
|
||||
* Показать текущее значение контрола: `v4l2-ctl d /dev/video0 --get-ctrl=exposure_auto`.
|
||||
* Изменить значение контрола: `v4l2-ctl d /dev/video0 --set-ctrl=exposure_auto=1`.
|
||||
|
||||
Больше примеров вы можете найти [здесь](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/), а документацию в [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
|
||||
|
||||
-----
|
||||
# Смотрите также
|
||||
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
FROM archlinux/base
|
||||
FROM archlinux/archlinux:base-devel
|
||||
|
||||
RUN mkdir -p /etc/pacman.d/hooks \
|
||||
&& ln -s /dev/null /etc/pacman.d/hooks/30-systemd-tmpfiles.hook
|
||||
|
||||
RUN echo "Server = http://mirror.yandex.ru/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist
|
||||
|
||||
RUN pacman -Syu --noconfirm \
|
||||
&& pacman -S --needed --noconfirm \
|
||||
base \
|
||||
base-devel \
|
||||
vim \
|
||||
git \
|
||||
libjpeg \
|
||||
@@ -17,7 +18,8 @@ RUN pacman -Syu --noconfirm \
|
||||
python-tox \
|
||||
cppcheck \
|
||||
npm \
|
||||
&& (pacman -Sc --noconfirm || true)
|
||||
&& (pacman -Sc --noconfirm || true) \
|
||||
&& rm -rf /var/cache/pacman/pkg/*
|
||||
|
||||
RUN npm install htmlhint -g
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[tox]
|
||||
envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
|
||||
envlist = cppcheck-src, cppcheck-python, flake8, pylint, mypy, vulture, htmlhint
|
||||
skipsdist = true
|
||||
|
||||
[testenv]
|
||||
basepython = python3.9
|
||||
changedir = /src
|
||||
|
||||
[testenv:cppcheck]
|
||||
[testenv:cppcheck-src]
|
||||
whitelist_externals = cppcheck
|
||||
commands = cppcheck \
|
||||
--force \
|
||||
@@ -17,33 +17,49 @@ commands = cppcheck \
|
||||
--suppress=assignmentInAssert \
|
||||
--suppress=variableScope \
|
||||
--inline-suppr \
|
||||
--library=python \
|
||||
-DCHAR_BIT=8 \
|
||||
-DWITH_OMX \
|
||||
-DWITH_GPIO \
|
||||
src
|
||||
src python
|
||||
|
||||
[testenv:cppcheck-python]
|
||||
whitelist_externals = cppcheck
|
||||
commands = cppcheck \
|
||||
--force \
|
||||
--std=c11 \
|
||||
--error-exitcode=1 \
|
||||
--quiet \
|
||||
--enable=warning,unusedFunction,portability,performance,style \
|
||||
--suppress=assignmentInAssert \
|
||||
--suppress=variableScope \
|
||||
--inline-suppr \
|
||||
--library=python \
|
||||
-DCHAR_BIT=8 \
|
||||
python
|
||||
|
||||
[testenv:flake8]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py'
|
||||
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py
|
||||
deps =
|
||||
flake8
|
||||
flake8-quotes
|
||||
|
||||
[testenv:pylint]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py'
|
||||
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py python/*.py'
|
||||
deps =
|
||||
pylint
|
||||
|
||||
[testenv:mypy]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py'
|
||||
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py python/*.py'
|
||||
deps =
|
||||
mypy
|
||||
|
||||
[testenv:vulture]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'vulture tools/*.py'
|
||||
commands = bash -c 'vulture tools/*.py python/*.py'
|
||||
deps =
|
||||
vulture
|
||||
|
||||
|
||||
@@ -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 3.0" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 3.22" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to 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 3.0" "November 2020"
|
||||
.TH USTREAMER 1 "version 3.22" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPG video from any V4L2 device to the network
|
||||
@@ -104,7 +104,7 @@ It doesn't do anything. Still here for compatibility. Required \fBWITH_OMX\fR fe
|
||||
Path to JPEG file that will be shown when the device is disconnected during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.
|
||||
.TP
|
||||
.BR \-K\ \fIsec ", " \-\-last\-as\-blank\ \fIsec
|
||||
Show the last frame received from the camera after it was disconnected, but no more than specified time (or endlessly if 0 is specified). If the device has not yet been online, display 'NO SIGNAL' or the image specified by option \-\-blank. Default: disabled.
|
||||
Show the last frame received from the camera after it was disconnected, but no more than specified time (or endlessly if 0 is specified). If the device has not yet been online, display 'NO SIGNAL' or the image specified by option \-\-blank. Note: currently this option has no effect on memory sinks. Default: disabled.
|
||||
.TP
|
||||
.BR \-l ", " \-\-slowdown
|
||||
Slowdown capturing to 1 FPS or less when no stream or sink clients are connected. Useful to reduce CPU consumption. Default: disabled.
|
||||
@@ -118,7 +118,7 @@ Delay before trying to connect to the device again after an error (timeout for e
|
||||
.SS "Image control options"
|
||||
.TP
|
||||
.BR \-\-image\-default
|
||||
Reset all image settings bellow to default. Default: no change.
|
||||
Reset all image settings below to default. Default: no change.
|
||||
.TP
|
||||
.BR \-\-brightness\ \fIN ", " \fIauto ", " \fIdefault
|
||||
Set brightness. Default: no change.
|
||||
@@ -166,8 +166,8 @@ Bind to this TCP port. Default: 8080.
|
||||
.TP
|
||||
.BR \-U\ \fIpath ", " \-\-unix\ \fIpath
|
||||
Bind to UNIX domain socket. Default: disabled.
|
||||
.tp
|
||||
.br \-d ", " \-\-unix\-rm
|
||||
.TP
|
||||
.BR \-d ", " \-\-unix\-rm
|
||||
Try to remove old unix socket file before binding. default: disabled.
|
||||
.TP
|
||||
.BR \-M\ \fImode ", " \-\-unix\-mode\ \fImode
|
||||
@@ -210,6 +210,9 @@ Set JPEG sink permissions (like 777). Default: 660.
|
||||
.BR \-\-sink\-rm
|
||||
Remove shared memory on stop. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-sink\-client\-ttl\ \fIsec
|
||||
Client TTL. Default: 10.
|
||||
.TP
|
||||
.BR \-\-sink\-timeout\ \fIsec
|
||||
Timeout for lock. Default: 1.
|
||||
|
||||
@@ -226,8 +229,17 @@ Set H264 sink permissions (like 777). Default: 660.
|
||||
.BR \-\-h264\-sink\-rm
|
||||
Remove shared memory on stop. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\-client\-ttl\ \fIsec
|
||||
Client TTL. Default: 10.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\-timeout\ \fIsec
|
||||
Timeout for lock. Default: 1.
|
||||
.TP
|
||||
.BR \-\-h264\-bitrate\ \fIkbps
|
||||
H264 bitrate in Kbps. Default: 5000.
|
||||
.TP
|
||||
.BR \-\-h264\-gop\ \fIN
|
||||
Intarval between keyframes. Default: 30.
|
||||
|
||||
.SS "Process options"
|
||||
.TP
|
||||
|
||||
@@ -3,32 +3,46 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=3.0
|
||||
pkgver=3.22
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
license=(GPL)
|
||||
arch=(i686 x86_64 armv6h armv7h aarch64)
|
||||
depends=(libjpeg libevent libutil-linux libbsd libgpiod)
|
||||
# optional: raspberrypi-firmware for OMX encoder
|
||||
depends=(libjpeg libevent libbsd libgpiod)
|
||||
makedepends=(gcc make)
|
||||
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
||||
md5sums=(SKIP)
|
||||
|
||||
|
||||
_options="WITH_GPIO=1"
|
||||
if [ -e /usr/bin/python3 ]; then
|
||||
_options="$_options WITH_PYTHON=1"
|
||||
depends+=(python)
|
||||
makedepends+=(python-setuptools)
|
||||
fi
|
||||
if [ -e /opt/vc/include/IL/OMX_Core.h ]; then
|
||||
depends+=(raspberrypi-firmware)
|
||||
makedepends+=(raspberrypi-firmware)
|
||||
_options="$_options WITH_OMX=1"
|
||||
fi
|
||||
|
||||
|
||||
# LD does not link mmal with this option
|
||||
# This DOESN'T affect setup.py
|
||||
LDFLAGS="${LDFLAGS//--as-needed/}"
|
||||
export LDFLAGS="${LDFLAGS//,,/,}"
|
||||
|
||||
|
||||
build() {
|
||||
cd "$srcdir"
|
||||
rm -rf $pkgname-build
|
||||
cp -r $pkgname $pkgname-build
|
||||
cd $pkgname-build
|
||||
|
||||
local _options="WITH_GPIO=1"
|
||||
[ -e /opt/vc/include/IL/OMX_Core.h ] && _options="$_options WITH_OMX=1"
|
||||
|
||||
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$srcdir/$pkgname-build"
|
||||
make DESTDIR="$pkgdir" PREFIX=/usr install
|
||||
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" DESTDIR="$pkgdir" PREFIX=/usr install
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ RUN apt-get update \
|
||||
libevent-2.1 \
|
||||
libevent-pthreads-2.1-6 \
|
||||
libjpeg8 \
|
||||
uuid \
|
||||
libbsd0 \
|
||||
libgpiod2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -20,7 +20,6 @@ RUN apt-get update \
|
||||
libevent-2.1 \
|
||||
libevent-pthreads-2.1-6 \
|
||||
libjpeg8 \
|
||||
uuid \
|
||||
libbsd0 \
|
||||
libgpiod2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -8,7 +8,6 @@ RUN apt-get update \
|
||||
git \
|
||||
libevent-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
uuid-dev \
|
||||
libbsd-dev \
|
||||
libgpiod-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
@@ -25,7 +24,6 @@ RUN apt-get update \
|
||||
libevent-2.1 \
|
||||
libevent-pthreads-2.1-6 \
|
||||
libjpeg62-turbo \
|
||||
uuid \
|
||||
libbsd0 \
|
||||
libgpiod2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -17,7 +17,6 @@ IUSE=""
|
||||
DEPEND="
|
||||
>=dev-libs/libevent-2.1.8
|
||||
>=media-libs/libjpeg-turbo-1.5.3
|
||||
>=sys-apps/util-linux-2.33
|
||||
>=dev-libs/libbsd-0.9.1
|
||||
"
|
||||
RDEPEND="${DEPEND}"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=3.0
|
||||
PKG_VERSION:=3.22
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
@@ -25,7 +25,7 @@ define Package/ustreamer
|
||||
SECTION:=multimedia
|
||||
CATEGORY:=Multimedia
|
||||
TITLE:=uStreamer
|
||||
DEPENDS:=+libpthread +libjpeg +libv4l +libuuid +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
|
||||
DEPENDS:=+libpthread +libjpeg +libv4l +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
|
||||
URL:=https://github.com/pikvm/ustreamer
|
||||
endef
|
||||
|
||||
|
||||
26
python/setup.py
Normal file
26
python/setup.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from distutils.core import Extension
|
||||
from distutils.core import setup
|
||||
|
||||
|
||||
# =====
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="3.22",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
url="https://github.com/pikvm/ustreamer",
|
||||
ext_modules=[
|
||||
Extension(
|
||||
"ustreamer",
|
||||
libraries=["rt", "m", "pthread"],
|
||||
undef_macros=["NDEBUG"],
|
||||
sources=["ustreamer.c"],
|
||||
depends=[
|
||||
"../src/libs/tools.h",
|
||||
"../src/libs/memsinksh.h",
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
380
python/ustreamer.c
Normal file
380
python/ustreamer.c
Normal file
@@ -0,0 +1,380 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "../src/libs/tools.h" // Just a header without C-sources
|
||||
#include "../src/libs/memsinksh.h" // No sources again
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint64_t id;
|
||||
long double ts;
|
||||
|
||||
uint8_t *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
|
||||
bool online;
|
||||
bool key;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
} tmp_frame_s;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
char *obj;
|
||||
double lock_timeout;
|
||||
double wait_timeout;
|
||||
double drop_same_frames;
|
||||
|
||||
int fd;
|
||||
memsink_shared_s *mem;
|
||||
|
||||
tmp_frame_s *tmp_frame;
|
||||
} MemsinkObject;
|
||||
|
||||
|
||||
#define MEM(_next) self->mem->_next
|
||||
#define TMP(_next) self->tmp_frame->_next
|
||||
|
||||
|
||||
static void MemsinkObject_destroy_internals(MemsinkObject *self) {
|
||||
if (self->mem != NULL) {
|
||||
munmap(self->mem, sizeof(memsink_shared_s));
|
||||
self->mem = NULL;
|
||||
}
|
||||
if (self->fd > 0) {
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
}
|
||||
if (self->tmp_frame) {
|
||||
if (TMP(data)) {
|
||||
free(TMP(data));
|
||||
}
|
||||
free(self->tmp_frame);
|
||||
self->tmp_frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||
self->lock_timeout = 1;
|
||||
self->wait_timeout = 1;
|
||||
|
||||
static char *kws[] = {"obj", "lock_timeout", "wait_timeout", "drop_same_frames", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, kwargs, "s|ddd", kws,
|
||||
&self->obj, &self->lock_timeout, &self->wait_timeout, &self->drop_same_frames)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define SET_DOUBLE(_field, _cond) { \
|
||||
if (!(self->_field _cond)) { \
|
||||
PyErr_SetString(PyExc_ValueError, #_field " must be " #_cond); \
|
||||
return -1; \
|
||||
} \
|
||||
}
|
||||
|
||||
SET_DOUBLE(lock_timeout, > 0);
|
||||
SET_DOUBLE(wait_timeout, > 0);
|
||||
SET_DOUBLE(drop_same_frames, >= 0);
|
||||
|
||||
# undef SET_DOUBLE
|
||||
|
||||
A_CALLOC(self->tmp_frame, 1);
|
||||
TMP(allocated) = 512 * 1024;
|
||||
A_REALLOC(TMP(data), TMP(allocated));
|
||||
|
||||
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((self->mem = mmap(
|
||||
NULL,
|
||||
sizeof(memsink_shared_s),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
self->fd,
|
||||
0
|
||||
)) == MAP_FAILED) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
self->mem = NULL;
|
||||
goto error;
|
||||
}
|
||||
if (self->mem == NULL) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Memory mapping is NULL"); \
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
MemsinkObject_destroy_internals(self);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_repr(MemsinkObject *self) {
|
||||
char repr[1024];
|
||||
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
|
||||
return Py_BuildValue("s", repr);
|
||||
}
|
||||
|
||||
static void MemsinkObject_dealloc(MemsinkObject *self) {
|
||||
MemsinkObject_destroy_internals(self);
|
||||
PyObject_Del(self);
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_close(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
MemsinkObject_destroy_internals(self);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_enter(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
Py_INCREF(self);
|
||||
return (PyObject *)self;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_exit(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
return PyObject_CallMethod((PyObject *)self, "close", "");
|
||||
}
|
||||
|
||||
static int wait_frame(MemsinkObject *self) {
|
||||
long double deadline_ts = get_now_monotonic() + self->wait_timeout;
|
||||
|
||||
# define RETURN_OS_ERROR { \
|
||||
Py_BLOCK_THREADS \
|
||||
PyErr_SetFromErrno(PyExc_OSError); \
|
||||
return -1; \
|
||||
}
|
||||
|
||||
long double now;
|
||||
do {
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
|
||||
int retval = flock_timedwait_monotonic(self->fd, self->lock_timeout);
|
||||
now = get_now_monotonic();
|
||||
|
||||
if (retval < 0 && errno != EWOULDBLOCK) {
|
||||
RETURN_OS_ERROR;
|
||||
|
||||
} else if (retval == 0) {
|
||||
if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && TMP(id) != MEM(id)) {
|
||||
if (self->drop_same_frames > 0) {
|
||||
# define CMP(_field) (TMP(_field) == MEM(_field))
|
||||
if (
|
||||
CMP(used)
|
||||
&& CMP(width)
|
||||
&& CMP(height)
|
||||
&& CMP(format)
|
||||
&& CMP(stride)
|
||||
&& CMP(online)
|
||||
&& CMP(key)
|
||||
&& (TMP(ts) + self->drop_same_frames > now)
|
||||
&& !memcmp(TMP(data), MEM(data), MEM(used))
|
||||
) {
|
||||
TMP(id) = MEM(id);
|
||||
goto drop;
|
||||
}
|
||||
# undef CMP
|
||||
}
|
||||
|
||||
Py_BLOCK_THREADS
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (flock(self->fd, LOCK_UN) < 0) {
|
||||
RETURN_OS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
drop:
|
||||
|
||||
if (usleep(1000) < 0) {
|
||||
RETURN_OS_ERROR;
|
||||
}
|
||||
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (PyErr_CheckSignals() < 0) {
|
||||
return -1;
|
||||
}
|
||||
} while (now < deadline_ts);
|
||||
|
||||
# undef RETURN_OS_ERROR
|
||||
|
||||
return -2;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
if (self->mem == NULL || self->fd <= 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Closed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (wait_frame(self)) {
|
||||
case 0: break;
|
||||
case -2: Py_RETURN_NONE;
|
||||
default: return NULL;
|
||||
}
|
||||
|
||||
# define COPY(_field) TMP(_field) = MEM(_field)
|
||||
COPY(width);
|
||||
COPY(height);
|
||||
COPY(format);
|
||||
COPY(stride);
|
||||
COPY(online);
|
||||
COPY(key);
|
||||
COPY(grab_ts);
|
||||
COPY(encode_begin_ts);
|
||||
COPY(encode_end_ts);
|
||||
COPY(used);
|
||||
# undef COPY
|
||||
|
||||
if (TMP(allocated) < MEM(used)) {
|
||||
size_t size = MEM(used) + (512 * 1024);
|
||||
A_REALLOC(TMP(data), size);
|
||||
TMP(allocated) = size;
|
||||
}
|
||||
memcpy(TMP(data), MEM(data), MEM(used));
|
||||
TMP(used) = MEM(used);
|
||||
|
||||
TMP(id) = MEM(id);
|
||||
TMP(ts) = get_now_monotonic();
|
||||
MEM(last_client_ts) = TMP(ts);
|
||||
|
||||
if (flock(self->fd, LOCK_UN) < 0) {
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
|
||||
PyObject *dict_frame = PyDict_New();
|
||||
if (dict_frame == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
# define SET_VALUE(_key, _maker) { \
|
||||
PyObject *_tmp = _maker; \
|
||||
if (_tmp == NULL) { \
|
||||
return NULL; \
|
||||
} \
|
||||
if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \
|
||||
Py_DECREF(_tmp); \
|
||||
return NULL; \
|
||||
} \
|
||||
Py_DECREF(_tmp); \
|
||||
}
|
||||
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(TMP(_key)))
|
||||
|
||||
SET_NUMBER(width, Long, Long);
|
||||
SET_NUMBER(height, Long, Long);
|
||||
SET_NUMBER(format, Long, Long);
|
||||
SET_NUMBER(stride, Long, Long);
|
||||
SET_NUMBER(online, Long, Bool);
|
||||
SET_NUMBER(key, Long, Bool);
|
||||
SET_NUMBER(grab_ts, Double, Float);
|
||||
SET_NUMBER(encode_begin_ts, Double, Float);
|
||||
SET_NUMBER(encode_end_ts, Double, Float);
|
||||
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)TMP(data), TMP(used)));
|
||||
|
||||
# undef SET_NUMBER
|
||||
# undef SET_VALUE
|
||||
|
||||
return dict_frame;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
|
||||
}
|
||||
|
||||
#define FIELD_GETTER(_field, _from, _to) \
|
||||
static PyObject *MemsinkObject_getter_##_field(MemsinkObject *self, void *Py_UNUSED(closure)) { \
|
||||
return Py##_to##_From##_from(self->_field); \
|
||||
}
|
||||
|
||||
FIELD_GETTER(obj, String, Unicode)
|
||||
FIELD_GETTER(lock_timeout, Double, Float)
|
||||
FIELD_GETTER(wait_timeout, Double, Float)
|
||||
FIELD_GETTER(drop_same_frames, Double, Float)
|
||||
|
||||
#undef FIELD_GETTER
|
||||
|
||||
static PyMethodDef MemsinkObject_methods[] = {
|
||||
# define ADD_METHOD(_name, _method, _flags) \
|
||||
{.ml_name = _name, .ml_meth = (PyCFunction)MemsinkObject_##_method, .ml_flags = (_flags)}
|
||||
ADD_METHOD("close", close, METH_NOARGS),
|
||||
ADD_METHOD("__enter__", enter, METH_NOARGS),
|
||||
ADD_METHOD("__exit__", exit, METH_VARARGS),
|
||||
ADD_METHOD("wait_frame", wait_frame, METH_NOARGS),
|
||||
ADD_METHOD("is_opened", is_opened, METH_NOARGS),
|
||||
{},
|
||||
# undef ADD_METHOD
|
||||
};
|
||||
|
||||
static PyGetSetDef MemsinkObject_getsets[] = {
|
||||
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)MemsinkObject_getter_##_field}
|
||||
ADD_GETTER(obj),
|
||||
ADD_GETTER(lock_timeout),
|
||||
ADD_GETTER(wait_timeout),
|
||||
ADD_GETTER(drop_same_frames),
|
||||
{},
|
||||
# undef ADD_GETTER
|
||||
};
|
||||
|
||||
static PyTypeObject MemsinkType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "ustreamer.Memsink",
|
||||
.tp_basicsize = sizeof(MemsinkObject),
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_new = PyType_GenericNew,
|
||||
.tp_init = (initproc)MemsinkObject_init,
|
||||
.tp_dealloc = (destructor)MemsinkObject_dealloc,
|
||||
.tp_repr = (reprfunc)MemsinkObject_repr,
|
||||
.tp_methods = MemsinkObject_methods,
|
||||
.tp_getset = MemsinkObject_getsets,
|
||||
};
|
||||
|
||||
static PyModuleDef ustreamer_Module = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
.m_name = "ustreamer",
|
||||
.m_size = -1,
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
|
||||
PyObject *module = PyModule_Create(&ustreamer_Module);
|
||||
if (module == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyType_Ready(&MemsinkType) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_INCREF(&MemsinkType);
|
||||
|
||||
if (PyModule_AddObject(module, "Memsink", (PyObject *)&MemsinkType) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
#undef TMP
|
||||
#undef MEM
|
||||
79
src/dump/file.c
Normal file
79
src/dump/file.c
Normal file
@@ -0,0 +1,79 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 "file.h"
|
||||
|
||||
|
||||
output_file_s *output_file_init(const char *path, bool json) {
|
||||
output_file_s *output;
|
||||
A_CALLOC(output, 1);
|
||||
|
||||
if (!strcmp(path, "-")) {
|
||||
LOG_INFO("Using output: <stdout>");
|
||||
output->fp = stdout;
|
||||
} else {
|
||||
LOG_INFO("Using output: %s", path);
|
||||
if ((output->fp = fopen(path, "wb")) == NULL) {
|
||||
LOG_PERROR("Can't open output file");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
output->json = json;
|
||||
return output;
|
||||
|
||||
error:
|
||||
output_file_destroy(output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void output_file_write(void *v_output, const frame_s *frame) {
|
||||
output_file_s *output = (output_file_s *)v_output;
|
||||
if (output->json) {
|
||||
base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
|
||||
fprintf(output->fp,
|
||||
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
|
||||
" \"format\": %u, \"stride\": %u, \"online\": %u,"
|
||||
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
|
||||
" \"data\": \"%s\"}\n",
|
||||
frame->used, frame->width, frame->height,
|
||||
frame->format, frame->stride, frame->online,
|
||||
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
|
||||
output->base64_data);
|
||||
} else {
|
||||
fwrite(frame->data, 1, frame->used, output->fp);
|
||||
}
|
||||
fflush(output->fp);
|
||||
}
|
||||
|
||||
void output_file_destroy(void *v_output) {
|
||||
output_file_s *output = (output_file_s *)v_output;
|
||||
if (output->base64_data) {
|
||||
free(output->base64_data);
|
||||
}
|
||||
if (output->fp && output->fp != stdout) {
|
||||
if (fclose(output->fp) < 0) {
|
||||
LOG_PERROR("Can't close output file");
|
||||
}
|
||||
}
|
||||
free(output);
|
||||
}
|
||||
49
src/dump/file.h
Normal file
49
src/dump/file.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/base64.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
bool json;
|
||||
|
||||
FILE *fp;
|
||||
char *base64_data;
|
||||
size_t base64_allocated;
|
||||
} output_file_s;
|
||||
|
||||
|
||||
output_file_s *output_file_init(const char *path, bool json);
|
||||
void output_file_write(void *v_output, const frame_s *frame);
|
||||
void output_file_destroy(void *v_output);
|
||||
103
src/dump/main.c
103
src/dump/main.c
@@ -23,6 +23,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
@@ -33,7 +34,9 @@
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/base64.h"
|
||||
#include "../libs/options.h"
|
||||
|
||||
#include "file.h"
|
||||
|
||||
|
||||
enum _OPT_VALUES {
|
||||
@@ -73,13 +76,20 @@ static const struct option _LONG_OPTS[] = {
|
||||
};
|
||||
|
||||
|
||||
volatile bool stop = false;
|
||||
volatile bool global_stop = false;
|
||||
|
||||
|
||||
typedef struct {
|
||||
void *v_output;
|
||||
void (*write)(void *v_output, const frame_s *frame);
|
||||
void (*destroy)(void *v_output);
|
||||
} _output_context_s;
|
||||
|
||||
|
||||
static void _signal_handler(int signum);
|
||||
static void _install_signal_handlers(void);
|
||||
|
||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *output_path, bool output_json);
|
||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_context_s *ctx);
|
||||
|
||||
static void _help(FILE *fp);
|
||||
|
||||
@@ -108,7 +118,10 @@ int main(int argc, char *argv[]) {
|
||||
break; \
|
||||
}
|
||||
|
||||
for (int ch; (ch = getopt_long(argc, argv, "s:t:o:jhv", _LONG_OPTS, NULL)) >= 0;) {
|
||||
char short_opts[128];
|
||||
build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
|
||||
for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||
switch (ch) {
|
||||
case _O_SINK: OPT_SET(sink_name, optarg);
|
||||
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
|
||||
@@ -126,7 +139,7 @@ int main(int argc, char *argv[]) {
|
||||
case _O_VERSION: puts(VERSION); return 0;
|
||||
|
||||
case 0: break;
|
||||
default: _help(stderr); return 1;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,8 +151,23 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
_output_context_s ctx;
|
||||
MEMSET_ZERO(ctx);
|
||||
|
||||
if (output_path && output_path[0] != '\0') {
|
||||
if ((ctx.v_output = (void *)output_file_init(output_path, output_json)) == NULL) {
|
||||
return 1;
|
||||
}
|
||||
ctx.write = output_file_write;
|
||||
ctx.destroy = output_file_destroy;
|
||||
}
|
||||
|
||||
_install_signal_handlers();
|
||||
return abs(_dump_sink(sink_name, sink_timeout, output_path, output_json));
|
||||
int retval = abs(_dump_sink(sink_name, sink_timeout, &ctx));
|
||||
if (ctx.v_output && ctx.destroy) {
|
||||
ctx.destroy(ctx.v_output);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +178,7 @@ static void _signal_handler(int signum) {
|
||||
case SIGPIPE: LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break;
|
||||
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
|
||||
}
|
||||
stop = true;
|
||||
global_stop = true;
|
||||
}
|
||||
|
||||
static void _install_signal_handlers(void) {
|
||||
@@ -173,27 +201,11 @@ static void _install_signal_handlers(void) {
|
||||
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||
}
|
||||
|
||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *output_path, bool output_json) {
|
||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_context_s *ctx) {
|
||||
frame_s *frame = frame_init("input");
|
||||
memsink_s *sink = NULL;
|
||||
FILE *output_fp = NULL;
|
||||
char *base64_data = NULL;
|
||||
size_t base64_allocated = 0;
|
||||
|
||||
if (output_path && output_path[0] != '\0') {
|
||||
if (!strcmp(output_path, "-")) {
|
||||
LOG_INFO("Using output: <stdout>");
|
||||
output_fp = stdout;
|
||||
} else {
|
||||
LOG_INFO("Using output: %s", output_path);
|
||||
if ((output_fp = fopen(output_path, "wb")) == NULL) {
|
||||
LOG_PERROR("Can't open output file");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((sink = memsink_init("input", sink_name, false, 0, false, sink_timeout)) == NULL) {
|
||||
if ((sink = memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -201,18 +213,21 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *
|
||||
unsigned fps_accum = 0;
|
||||
long long fps_second = 0;
|
||||
|
||||
while (!stop) {
|
||||
long double last_ts = 0;
|
||||
|
||||
while (!global_stop) {
|
||||
int error = memsink_client_get(sink, frame);
|
||||
if (error == 0) {
|
||||
const long double now = get_now_monotonic();
|
||||
const long long now_second = floor_ms(now);
|
||||
|
||||
char fourcc_str[8];
|
||||
LOG_VERBOSE("Frame: size=%zu, resolution=%ux%u, fourcc=%s, stride=%u, online=%d, latency=%.3Lf",
|
||||
LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf",
|
||||
frame->used, frame->width, frame->height,
|
||||
fourcc_to_string(frame->format, fourcc_str, 8),
|
||||
frame->stride, frame->online,
|
||||
now - frame->grab_ts);
|
||||
frame->stride, frame->online, frame->key,
|
||||
now - frame->grab_ts, (last_ts ? now - last_ts : 0));
|
||||
last_ts = now;
|
||||
|
||||
LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
|
||||
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
|
||||
@@ -225,24 +240,12 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *
|
||||
}
|
||||
fps_accum += 1;
|
||||
|
||||
if (output_fp) {
|
||||
if (output_json) {
|
||||
base64_encode(frame->data, frame->used, &base64_data, &base64_allocated);
|
||||
fprintf(output_fp,
|
||||
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
|
||||
" \"format\": %u, \"stride\": %u, \"online\": %u,"
|
||||
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
|
||||
" \"data\": \"%s\"}\n",
|
||||
frame->used, frame->width, frame->height,
|
||||
frame->format, frame->stride, frame->online,
|
||||
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
|
||||
base64_data);
|
||||
} else {
|
||||
fwrite(frame->data, 1, frame->used, output_fp);
|
||||
}
|
||||
fflush(output_fp);
|
||||
if (ctx->v_output) {
|
||||
ctx->write(ctx->v_output, frame);
|
||||
}
|
||||
} else if (error != -2) {
|
||||
} else if (error == -2) {
|
||||
usleep(1000);
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@@ -254,14 +257,6 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *
|
||||
retval = -1;
|
||||
|
||||
ok:
|
||||
if (base64_data) {
|
||||
free(base64_data);
|
||||
}
|
||||
if (output_fp && output_fp != stdout) {
|
||||
if (fclose(output_fp) < 0) {
|
||||
LOG_PERROR("Can't close output file");
|
||||
}
|
||||
}
|
||||
if (sink) {
|
||||
memsink_destroy(sink);
|
||||
}
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef VERSION
|
||||
# define VERSION "3.0"
|
||||
# define VERSION "3.22"
|
||||
#endif
|
||||
|
||||
@@ -81,6 +81,7 @@ void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
||||
COPY(format);
|
||||
COPY(stride);
|
||||
COPY(online);
|
||||
COPY(key);
|
||||
COPY(grab_ts);
|
||||
COPY(encode_begin_ts);
|
||||
COPY(encode_end_ts);
|
||||
@@ -98,6 +99,7 @@ bool frame_compare(const frame_s *a, const frame_s *b) {
|
||||
&& CMP(format)
|
||||
&& CMP(stride)
|
||||
&& CMP(online)
|
||||
&& CMP(key)
|
||||
&& !memcmp(a->data, b->data, b->used)
|
||||
);
|
||||
# undef CMP
|
||||
|
||||
@@ -51,6 +51,7 @@ typedef struct {
|
||||
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
|
||||
|
||||
bool online;
|
||||
bool key;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
|
||||
@@ -150,7 +150,7 @@ extern pthread_mutex_t log_mutex;
|
||||
#define LOG_VERBOSE_PERROR(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
|
||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
||||
} \
|
||||
}
|
||||
|
||||
@@ -23,23 +23,28 @@
|
||||
#include "memsink.h"
|
||||
|
||||
|
||||
static int _flock_timedwait_monotonic(int fd, long double timeout);
|
||||
memsink_s *memsink_init(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
|
||||
|
||||
|
||||
memsink_s *memsink_init(const char *name, const char *obj, bool server, mode_t mode, bool rm, unsigned timeout) {
|
||||
memsink_s *sink;
|
||||
A_CALLOC(sink, 1);
|
||||
sink->name = name;
|
||||
sink->obj = obj;
|
||||
sink->server = server;
|
||||
sink->rm = rm;
|
||||
sink->client_ttl = client_ttl;
|
||||
sink->timeout = timeout;
|
||||
sink->fd = -1;
|
||||
sink->mem = MAP_FAILED;
|
||||
|
||||
LOG_INFO("Using %s-sink: %s", name, obj);
|
||||
|
||||
if ((sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode)) == -1) {
|
||||
mode_t mask = umask(0);
|
||||
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
|
||||
umask(mask);
|
||||
if (sink->fd == -1) {
|
||||
umask(mask);
|
||||
LOG_PERROR("%s-sink: Can't open shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
@@ -87,6 +92,33 @@ void memsink_destroy(memsink_s *sink) {
|
||||
free(sink);
|
||||
}
|
||||
|
||||
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||
// Возвращает true, если если клиенты ИЛИ изменились метаданные
|
||||
|
||||
assert(sink->server);
|
||||
|
||||
if (flock(sink->fd, LOCK_EX | LOCK_NB) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
sink->has_clients = true;
|
||||
return true;
|
||||
}
|
||||
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
sink->has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
|
||||
|
||||
# define NEQ(_field) (sink->mem->_field != frame->_field)
|
||||
bool retval = (sink->has_clients || NEQ(width) || NEQ(height) || NEQ(format) || NEQ(stride) || NEQ(online) || NEQ(key));
|
||||
# undef NEQ
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return false;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
||||
assert(sink->server);
|
||||
|
||||
@@ -98,7 +130,7 @@ int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
||||
return 0; // -2
|
||||
}
|
||||
|
||||
if (_flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
||||
if (flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
||||
LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
|
||||
|
||||
# define COPY(_field) sink->mem->_field = frame->_field
|
||||
@@ -110,11 +142,14 @@ int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
||||
COPY(format);
|
||||
COPY(stride);
|
||||
COPY(online);
|
||||
COPY(key);
|
||||
COPY(grab_ts);
|
||||
COPY(encode_begin_ts);
|
||||
COPY(encode_end_ts);
|
||||
sink->has_clients = (sink->mem->last_consumed_ts + 10 > get_now_monotonic());
|
||||
sink->has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
|
||||
memcpy(sink->mem->data, frame->data, frame->used);
|
||||
sink->mem->magic = MEMSINK_MAGIC;
|
||||
sink->mem->version = MEMSINK_VERSION;
|
||||
# undef COPY
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
@@ -137,7 +172,7 @@ int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
||||
int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress unusedFunction
|
||||
assert(!sink->server); // Client only
|
||||
|
||||
if (_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
if (flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
return -2;
|
||||
}
|
||||
@@ -145,48 +180,37 @@ int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress u
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool same = false;
|
||||
|
||||
if (sink->mem->id == sink->last_id) {
|
||||
same = true;
|
||||
} else {
|
||||
# define COPY(_field) frame->_field = sink->mem->_field
|
||||
sink->last_id = sink->mem->id;
|
||||
COPY(width);
|
||||
COPY(height);
|
||||
COPY(format);
|
||||
COPY(stride);
|
||||
COPY(online);
|
||||
COPY(grab_ts);
|
||||
COPY(encode_begin_ts);
|
||||
COPY(encode_end_ts);
|
||||
sink->mem->last_consumed_ts = get_now_monotonic();
|
||||
frame_set_data(frame, sink->mem->data, sink->mem->used);
|
||||
# undef COPY
|
||||
}
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (same) {
|
||||
usleep(1000);
|
||||
return -2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
long double deadline_ts = get_now_monotonic() + timeout;
|
||||
int retval = -1;
|
||||
|
||||
while (true) {
|
||||
retval = flock(fd, LOCK_EX | LOCK_NB);
|
||||
if (retval == 0 || errno != EWOULDBLOCK || get_now_monotonic() > deadline_ts) {
|
||||
break;
|
||||
int retval = -2; // Not updated
|
||||
if (sink->mem->magic == MEMSINK_MAGIC) {
|
||||
if (sink->mem->version != MEMSINK_VERSION) {
|
||||
LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
|
||||
sink->name, sink->mem->version, MEMSINK_VERSION);
|
||||
retval = -1;
|
||||
goto done;
|
||||
}
|
||||
usleep(1000);
|
||||
if (sink->mem->id != sink->last_id) { // When updated
|
||||
# define COPY(_field) frame->_field = sink->mem->_field
|
||||
sink->last_id = sink->mem->id;
|
||||
COPY(width);
|
||||
COPY(height);
|
||||
COPY(format);
|
||||
COPY(stride);
|
||||
COPY(online);
|
||||
COPY(key);
|
||||
COPY(grab_ts);
|
||||
COPY(encode_begin_ts);
|
||||
COPY(encode_end_ts);
|
||||
frame_set_data(frame, sink->mem->data, sink->mem->used);
|
||||
# undef COPY
|
||||
retval = 0;
|
||||
}
|
||||
sink->mem->last_client_ts = get_now_monotonic();
|
||||
}
|
||||
return retval;
|
||||
|
||||
done:
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -37,34 +37,15 @@
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "frame.h"
|
||||
#include "memsinksh.h"
|
||||
|
||||
|
||||
#ifndef CFG_MEMSINK_MAX_DATA
|
||||
# define CFG_MEMSINK_MAX_DATA 33554432
|
||||
#endif
|
||||
#define MEMSINK_MAX_DATA ((size_t)(CFG_MEMSINK_MAX_DATA))
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint64_t id;
|
||||
size_t used;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
bool online;
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
long double last_consumed_ts;
|
||||
uint8_t data[MEMSINK_MAX_DATA];
|
||||
} memsink_shared_s;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *obj;
|
||||
bool server;
|
||||
bool rm;
|
||||
unsigned client_ttl; // Only for server
|
||||
unsigned timeout;
|
||||
|
||||
int fd;
|
||||
@@ -74,8 +55,13 @@ typedef struct {
|
||||
} memsink_s;
|
||||
|
||||
|
||||
memsink_s *memsink_init(const char *name, const char *obj, bool server, mode_t mode, bool rm, unsigned timeout);
|
||||
memsink_s *memsink_init(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
|
||||
|
||||
void memsink_destroy(memsink_s *sink);
|
||||
|
||||
bool memsink_server_check(memsink_s *sink, const frame_s *frame);
|
||||
int memsink_server_put(memsink_s *sink, const frame_s *frame);
|
||||
|
||||
int memsink_client_get(memsink_s *sink, frame_s *frame);
|
||||
|
||||
61
src/libs/memsinksh.h
Normal file
61
src/libs/memsinksh.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
#define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||
#define MEMSINK_VERSION ((uint32_t)2)
|
||||
|
||||
#ifndef CFG_MEMSINK_MAX_DATA
|
||||
# define CFG_MEMSINK_MAX_DATA 33554432
|
||||
#endif
|
||||
#define MEMSINK_MAX_DATA ((size_t)(CFG_MEMSINK_MAX_DATA))
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint64_t magic;
|
||||
uint32_t version;
|
||||
|
||||
uint64_t id;
|
||||
|
||||
size_t used;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
bool online;
|
||||
bool key;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
|
||||
long double last_client_ts;
|
||||
|
||||
uint8_t data[MEMSINK_MAX_DATA];
|
||||
} memsink_shared_s;
|
||||
39
src/libs/options.c
Normal file
39
src/libs/options.c
Normal file
@@ -0,0 +1,39 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 "options.h"
|
||||
|
||||
|
||||
void build_short_options(const struct option opts[], char *short_opts, size_t size) {
|
||||
memset(short_opts, 0, size);
|
||||
for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
|
||||
assert(short_index < size - 3);
|
||||
if (isalpha(opts[opt_index].val)) {
|
||||
short_opts[short_index] = opts[opt_index].val;
|
||||
++short_index;
|
||||
if (opts[opt_index].has_arg == required_argument) {
|
||||
short_opts[short_index] = ':';
|
||||
++short_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/libs/options.h
Normal file
33
src/libs/options.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 <string.h>
|
||||
#include <ctype.h>
|
||||
#include <getopt.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
void build_short_options(const struct option opts[], char *short_opts, size_t size);
|
||||
@@ -32,6 +32,8 @@
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/file.h>
|
||||
|
||||
|
||||
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
|
||||
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
|
||||
@@ -123,3 +125,19 @@ INLINE unsigned get_cores_available(void) {
|
||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||
return max_u(min_u(cores_sysconf, 4), 1);
|
||||
}
|
||||
|
||||
INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
long double deadline_ts = get_now_monotonic() + timeout;
|
||||
int retval = -1;
|
||||
|
||||
while (true) {
|
||||
retval = flock(fd, LOCK_EX | LOCK_NB);
|
||||
if (retval == 0 || errno != EWOULDBLOCK || get_now_monotonic() > deadline_ts) {
|
||||
break;
|
||||
}
|
||||
if (usleep(1000) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,11 @@
|
||||
Without this option, when the frame series is completed, WebKit-based browsers<br>
|
||||
renders the last frame with a delay.
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<b>zero_data=1</b><br>
|
||||
Disables the actual sending of JPEG data and leaves only response headers.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<br>
|
||||
|
||||
@@ -75,6 +75,11 @@ const char *const HTML_INDEX_PAGE = " \
|
||||
Without this option, when the frame series is completed, WebKit-based browsers<br> \
|
||||
renders the last frame with a delay. \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
<b>zero_data=1</b><br> \
|
||||
Disables the actual sending of JPEG data and leaves only response headers. \
|
||||
</li> \
|
||||
</ul> \
|
||||
</li> \
|
||||
<br> \
|
||||
|
||||
@@ -41,7 +41,7 @@ static const struct {
|
||||
{"UYVY", V4L2_PIX_FMT_UYVY},
|
||||
{"RGB565", V4L2_PIX_FMT_RGB565},
|
||||
{"RGB24", V4L2_PIX_FMT_RGB24},
|
||||
{"JPEG", V4L2_PIX_FMT_MJPEG},
|
||||
{"MJPEG", V4L2_PIX_FMT_MJPEG},
|
||||
{"JPEG", V4L2_PIX_FMT_JPEG},
|
||||
};
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
#define STANDARDS_STR "PAL, NTSC, SECAM"
|
||||
|
||||
#define FORMAT_UNKNOWN -1
|
||||
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, JPEG"
|
||||
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG"
|
||||
|
||||
#define IO_METHOD_UNKNOWN -1
|
||||
#define IO_METHODS_STR "MMAP, USERPTR"
|
||||
|
||||
@@ -27,7 +27,6 @@ static const OMX_U32 _INPUT_PORT = 340;
|
||||
static const OMX_U32 _OUTPUT_PORT = 341;
|
||||
|
||||
|
||||
static int _vcos_semwait(VCOS_SEMAPHORE_T *sem);
|
||||
static int _omx_init_component(omx_encoder_s *omx);
|
||||
static int _omx_init_disable_ports(omx_encoder_s *omx);
|
||||
static int _omx_setup_input(omx_encoder_s *omx, const frame_s *frame);
|
||||
@@ -194,7 +193,7 @@ int omx_encoder_compress(omx_encoder_s *omx, const frame_s *src, frame_s *dest)
|
||||
}
|
||||
}
|
||||
|
||||
if (_vcos_semwait(&omx->handler_sem) != 0) {
|
||||
if (vcos_my_semwait("", &omx->handler_sem, 1) < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -204,40 +203,6 @@ int omx_encoder_compress(omx_encoder_s *omx, const frame_s *src, frame_s *dest)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _vcos_semwait(VCOS_SEMAPHORE_T *sem) {
|
||||
// vcos_semaphore_wait() can wait infinite
|
||||
// vcos_semaphore_wait_timeout() is broken by design:
|
||||
// - https://github.com/pikvm/ustreamer/issues/56
|
||||
// - https://github.com/raspberrypi/userland/issues/658
|
||||
// CFG_OMX_SEMWAIT_TIMEOUT is ugly busyloop
|
||||
// Три стула.
|
||||
|
||||
# ifdef CFG_OMX_SEMWAIT_TIMEOUT
|
||||
long double deadline_ts = get_now_monotonic() + (long double)CFG_OMX_SEMWAIT_TIMEOUT; // Seconds
|
||||
VCOS_STATUS_T sem_status;
|
||||
|
||||
while (true) {
|
||||
sem_status = vcos_semaphore_trywait(sem);
|
||||
if (sem_status == VCOS_SUCCESS) {
|
||||
return 0;
|
||||
} else if (sem_status != VCOS_EAGAIN || get_now_monotonic() > deadline_ts) {
|
||||
goto error;
|
||||
}
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
error:
|
||||
switch (sem_status) {
|
||||
case VCOS_EAGAIN: LOG_ERROR("Can't wait VCOS semaphore: EAGAIN (timeout)"); break;
|
||||
case VCOS_EINVAL: LOG_ERROR("Can't wait VCOS semaphore: EINVAL"); break;
|
||||
default: LOG_ERROR("Can't wait VCOS semaphore: %d", sem_status); break;
|
||||
}
|
||||
return -1;
|
||||
# else
|
||||
return (vcos_semaphore_wait(sem) == VCOS_SUCCESS ? 0 : -1);
|
||||
# endif
|
||||
}
|
||||
|
||||
static int _omx_init_component(omx_encoder_s *omx) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "../../../libs/logging.h"
|
||||
#include "../../../libs/frame.h"
|
||||
|
||||
#include "vcos.h"
|
||||
#include "formatters.h"
|
||||
#include "component.h"
|
||||
|
||||
|
||||
55
src/ustreamer/encoders/omx/vcos.c
Normal file
55
src/ustreamer/encoders/omx/vcos.c
Normal file
@@ -0,0 +1,55 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 "vcos.h"
|
||||
|
||||
|
||||
int vcos_my_semwait(const char *prefix, VCOS_SEMAPHORE_T *sem, long double timeout) {
|
||||
// vcos_semaphore_wait() can wait infinite
|
||||
// vcos_semaphore_wait_timeout() is broken by design:
|
||||
// - https://github.com/pikvm/ustreamer/issues/56
|
||||
// - https://github.com/raspberrypi/userland/issues/658
|
||||
// - The current approach is an ugly busyloop
|
||||
// Три стула.
|
||||
|
||||
long double deadline_ts = get_now_monotonic() + timeout;
|
||||
VCOS_STATUS_T sem_status;
|
||||
|
||||
while (true) {
|
||||
sem_status = vcos_semaphore_trywait(sem);
|
||||
if (sem_status == VCOS_SUCCESS) {
|
||||
return 0;
|
||||
} else if (sem_status != VCOS_EAGAIN || get_now_monotonic() > deadline_ts) {
|
||||
break;
|
||||
}
|
||||
if (usleep(1000) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (sem_status) {
|
||||
case VCOS_EAGAIN: LOG_ERROR("%sCan't wait VCOS semaphore: EAGAIN (timeout)", prefix); break;
|
||||
case VCOS_EINVAL: LOG_ERROR("%sCan't wait VCOS semaphore: EINVAL", prefix); break;
|
||||
default: LOG_ERROR("%sCan't wait VCOS semaphore: %d", prefix, sem_status); break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
31
src/ustreamer/encoders/omx/vcos.h
Normal file
31
src/ustreamer/encoders/omx/vcos.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 <interface/vcos/vcos_semaphore.h>
|
||||
|
||||
#include "../../../libs/tools.h"
|
||||
#include "../../../libs/logging.h"
|
||||
|
||||
|
||||
int vcos_my_semwait(const char *prefix, VCOS_SEMAPHORE_T *sem, long double timeout);
|
||||
@@ -212,7 +212,8 @@ int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_co
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_PEAK_RATE, enc->bitrate * 1000);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_MIN_QUANT, 16);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_MAX_QUANT, 34);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_FRAME_LIMIT_BITS, 1000000);
|
||||
// Этот параметр с этим значением фризит кодирование изображения из черно-белой консоли
|
||||
// SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_FRAME_LIMIT_BITS, 1000000);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_H264_AU_DELIMITERS, MMAL_FALSE);
|
||||
}
|
||||
|
||||
@@ -224,7 +225,7 @@ int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_co
|
||||
|
||||
error:
|
||||
_h264_encoder_cleanup(enc);
|
||||
LOG_ERROR("H264: Encoder disabled due error (prepare)");
|
||||
LOG_ERROR("H264: Encoder destroyed due an error (prepare)");
|
||||
return -1;
|
||||
|
||||
# undef ENABLE_PORT
|
||||
@@ -250,7 +251,9 @@ static void _h264_encoder_cleanup(h264_encoder_s *enc) {
|
||||
|
||||
# undef DISABLE_PORT
|
||||
|
||||
enc->wrapper->status = MMAL_SUCCESS; // Это реально надо?
|
||||
if (enc->wrapper) {
|
||||
enc->wrapper->status = MMAL_SUCCESS; // Это реально надо?
|
||||
}
|
||||
|
||||
enc->last_online = -1;
|
||||
enc->ready = false;
|
||||
@@ -273,7 +276,7 @@ int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_
|
||||
|
||||
if (_h264_encoder_compress_raw(enc, src, src_vcsm_handle, dest, force_key) < 0) {
|
||||
_h264_encoder_cleanup(enc);
|
||||
LOG_ERROR("H264: Encoder disabled due error (compress)");
|
||||
LOG_ERROR("H264: Encoder destroyed due an error (compress)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -337,7 +340,9 @@ static int _h264_encoder_compress_raw(h264_encoder_s *enc, const frame_s *src, i
|
||||
|
||||
error = mmal_wrapper_buffer_get_full(enc->output_port, &out, 0);
|
||||
if (error == MMAL_EAGAIN) {
|
||||
vcos_semaphore_wait(&enc->handler_sem);
|
||||
if (vcos_my_semwait("H264: ", &enc->handler_sem, 1) < 0) {
|
||||
return -1;
|
||||
}
|
||||
continue;
|
||||
} else if (error != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't get MMAL output buffer");
|
||||
@@ -345,6 +350,7 @@ static int _h264_encoder_compress_raw(h264_encoder_s *enc, const frame_s *src, i
|
||||
}
|
||||
|
||||
frame_append_data(dest, out->data, out->length);
|
||||
dest->key = out->flags & MMAL_BUFFER_HEADER_FLAG_KEYFRAME;
|
||||
|
||||
eos = out->flags & MMAL_BUFFER_HEADER_FLAG_EOS;
|
||||
mmal_buffer_header_release(out);
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "../../libs/tools.h"
|
||||
#include "../../libs/logging.h"
|
||||
#include "../../libs/frame.h"
|
||||
#include "../encoders/omx/vcos.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -29,9 +29,12 @@ h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop)
|
||||
h264->sink = sink;
|
||||
h264->tmp_src = frame_init("h264_tmp_src");
|
||||
h264->dest = frame_init("h264_dest");
|
||||
atomic_init(&h264->online, false);
|
||||
|
||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L210
|
||||
if ((h264->enc = h264_encoder_init(bitrate, gop, 0)) == NULL) {
|
||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100
|
||||
// По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480
|
||||
// енкодер через несколько секунд перестает производить корректные фреймы.
|
||||
if ((h264->enc = h264_encoder_init(bitrate, gop, 30)) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -52,6 +55,10 @@ void h264_stream_destroy(h264_stream_s *h264) {
|
||||
}
|
||||
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_handle, bool force_key) {
|
||||
if (!memsink_server_check(h264->sink, frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
long double now = get_now_monotonic();
|
||||
bool zero_copy = false;
|
||||
|
||||
@@ -73,13 +80,17 @@ void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_han
|
||||
LOG_VERBOSE("H264: Source copied; time=%.3Lf", get_now_monotonic() - now);
|
||||
}
|
||||
|
||||
bool online = false;
|
||||
|
||||
if (!h264_encoder_is_prepared_for(h264->enc, frame, zero_copy)) {
|
||||
h264_encoder_prepare(h264->enc, frame, zero_copy);
|
||||
}
|
||||
|
||||
if (h264->enc->ready) {
|
||||
if (h264_encoder_compress(h264->enc, frame, vcsm_handle, h264->dest, force_key) == 0) {
|
||||
memsink_server_put(h264->sink, h264->dest);
|
||||
online = !memsink_server_put(h264->sink, h264->dest);
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store(&h264->online, online);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../../libs/tools.h"
|
||||
@@ -39,6 +40,7 @@ typedef struct {
|
||||
frame_s *tmp_src;
|
||||
frame_s *dest;
|
||||
h264_encoder_s *enc;
|
||||
atomic_bool online;
|
||||
} h264_stream_s;
|
||||
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ static void _http_queue_send_stream(server_s *server, bool stream_updated, bool
|
||||
|
||||
static bool _expose_new_frame(server_s *server);
|
||||
|
||||
static char *_http_get_client_hostport(struct evhttp_request *request);
|
||||
|
||||
|
||||
#define RUN(_next) server->run->_next
|
||||
#define STREAM(_next) RUN(stream->_next)
|
||||
@@ -94,6 +96,7 @@ void server_destroy(server_s *server) {
|
||||
for (stream_client_s *client = RUN(stream_clients); client != NULL;) {
|
||||
stream_client_s *next = client->next;
|
||||
free(client->key);
|
||||
free(client->hostport);
|
||||
free(client);
|
||||
client = next;
|
||||
}
|
||||
@@ -145,7 +148,7 @@ int server_listen(server_s *server) {
|
||||
char *raw_token;
|
||||
char *encoded_token = NULL;
|
||||
|
||||
A_CALLOC(raw_token, strlen(server->user) + strlen(server->passwd) + 2);
|
||||
A_CALLOC(raw_token, strlen(server->user) + strlen(server->passwd) + 16);
|
||||
sprintf(raw_token, "%s:%s", server->user, server->passwd);
|
||||
base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
|
||||
free(raw_token);
|
||||
@@ -339,12 +342,24 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"{\"ok\": true, \"result\": {"
|
||||
" \"encoder\": {\"type\": \"%s\", \"quality\": %u},"
|
||||
" \"encoder\": {\"type\": \"%s\", \"quality\": %u},",
|
||||
encoder_type_to_string(enc_type),
|
||||
enc_quality
|
||||
));
|
||||
# ifdef WITH_OMX
|
||||
if (STREAM(run->h264)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s},",
|
||||
STREAM(h264_bitrate),
|
||||
STREAM(h264_gop),
|
||||
bool_to_string(atomic_load(&STREAM(run->h264->online)))
|
||||
));
|
||||
}
|
||||
# endif
|
||||
assert(evbuffer_add_printf(buf,
|
||||
" \"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u},"
|
||||
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
|
||||
encoder_type_to_string(enc_type),
|
||||
enc_quality,
|
||||
(server->fake_width ? server->fake_width : EX(frame->width)),
|
||||
(server->fake_height ? server->fake_height : EX(frame->height)),
|
||||
bool_to_string(EX(frame->online)),
|
||||
@@ -356,12 +371,14 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
|
||||
for (stream_client_s * client = RUN(stream_clients); client != NULL; client = client->next) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"\"%s\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s, \"dual_final_frames\": %s}%s",
|
||||
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
|
||||
" \"dual_final_frames\": %s, \"zero_data\": %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->next ? ", " : "")
|
||||
));
|
||||
}
|
||||
@@ -392,12 +409,12 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
char header_buf[256];
|
||||
|
||||
# define ADD_TIME_HEADER(_key, _value) { \
|
||||
sprintf(header_buf, "%.06Lf", _value); \
|
||||
snprintf(header_buf, 255, "%.06Lf", _value); \
|
||||
ADD_HEADER(_key, header_buf); \
|
||||
}
|
||||
|
||||
# define ADD_UNSIGNED_HEADER(_key, _value) { \
|
||||
sprintf(header_buf, "%u", _value); \
|
||||
snprintf(header_buf, 255, "%u", _value); \
|
||||
ADD_HEADER(_key, header_buf); \
|
||||
}
|
||||
|
||||
@@ -450,15 +467,17 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
|
||||
struct evkeyvalq params;
|
||||
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||
client->key = uri_get_string(¶ms, "key");
|
||||
client->extra_headers = uri_get_true(¶ms, "extra_headers");
|
||||
client->advance_headers = uri_get_true(¶ms, "advance_headers");
|
||||
client->dual_final_frames = uri_get_true(¶ms, "dual_final_frames");
|
||||
# define PARSE_PARAM(_type, _name) client->_name = uri_get_##_type(¶ms, #_name)
|
||||
PARSE_PARAM(string, key);
|
||||
PARSE_PARAM(true, extra_headers);
|
||||
PARSE_PARAM(true, advance_headers);
|
||||
PARSE_PARAM(true, dual_final_frames);
|
||||
PARSE_PARAM(true, zero_data);
|
||||
# undef PARSE_PARAM
|
||||
evhttp_clear_headers(¶ms);
|
||||
|
||||
uuid_t uuid;
|
||||
uuid_generate(uuid);
|
||||
uuid_unparse_lower(uuid, client->id);
|
||||
client->hostport = _http_get_client_hostport(request);
|
||||
client->id = get_now_id();
|
||||
|
||||
if (RUN(stream_clients) == NULL) {
|
||||
RUN(stream_clients) = client;
|
||||
@@ -478,23 +497,18 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
# endif
|
||||
}
|
||||
|
||||
char *client_addr;
|
||||
unsigned short client_port;
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
|
||||
LOG_INFO("HTTP: Registered client: [%s]:%u, id=%s; clients now: %u",
|
||||
client_addr, client_port, client->id, RUN(stream_clients_count));
|
||||
|
||||
LOG_INFO("HTTP: Registered client: %s, id=%" PRIx64 "; clients now: %u",
|
||||
client->hostport, client->id, RUN(stream_clients_count));
|
||||
|
||||
struct bufferevent *buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
if (server->tcp_nodelay && !RUN(unix_fd)) {
|
||||
evutil_socket_t fd;
|
||||
int on = 1;
|
||||
|
||||
LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client [%s]:%u ...", client_addr, client_port);
|
||||
LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
||||
assert((fd = bufferevent_getfd(buf_event)) >= 0);
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)) != 0) {
|
||||
LOG_PERROR("HTTP: Can't set TCP_NODELAY to the client [%s]:%u", client_addr, client_port);
|
||||
LOG_PERROR("HTTP: Can't set TCP_NODELAY to the client %s", client->hostport);
|
||||
}
|
||||
}
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||
@@ -558,7 +572,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||
"Pragma: no-cache" RN
|
||||
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
||||
"Set-Cookie: stream_client=%s/%s; path=/; max-age=30" RN
|
||||
"Set-Cookie: stream_client=%s/%" PRIx64 "; path=/; max-age=30" RN
|
||||
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
||||
RN
|
||||
"--" BOUNDARY RN,
|
||||
@@ -580,7 +594,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
"Content-Length: %zu" RN
|
||||
"X-Timestamp: %.06Lf" RN
|
||||
"%s",
|
||||
EX(frame->used),
|
||||
(!client->zero_data ? EX(frame->used) : 0),
|
||||
get_now_real(),
|
||||
(client->extra_headers ? "" : RN)
|
||||
));
|
||||
@@ -598,6 +612,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
"X-UStreamer-Expose-Cmp-Time: %.06Lf" RN
|
||||
"X-UStreamer-Expose-End-Time: %.06Lf" RN
|
||||
"X-UStreamer-Send-Time: %.06Lf" RN
|
||||
"X-UStreamer-Latency: %.06Lf" RN
|
||||
RN,
|
||||
bool_to_string(EX(frame->online)),
|
||||
EX(dropped),
|
||||
@@ -610,12 +625,15 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
EX(expose_begin_ts),
|
||||
EX(expose_cmp_ts),
|
||||
EX(expose_end_ts),
|
||||
now
|
||||
now,
|
||||
now - EX(frame->grab_ts)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
assert(!evbuffer_add(buf, (void *)EX(frame->data), EX(frame->used)));
|
||||
if (!client->zero_data) {
|
||||
assert(!evbuffer_add(buf, (void *)EX(frame->data), EX(frame->used)));
|
||||
}
|
||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||
|
||||
if (client->advance_headers) {
|
||||
@@ -649,17 +667,10 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
# endif
|
||||
}
|
||||
|
||||
char *client_addr = "???";
|
||||
unsigned short client_port = 0;
|
||||
LOG_INFO("HTTP: Disconnected client: %s, id=%" PRIx64 ", %s; clients now: %u",
|
||||
client->hostport, client->id, reason, RUN(stream_clients_count));
|
||||
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
||||
if (conn) {
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
}
|
||||
|
||||
LOG_INFO("HTTP: Disconnected client: [%s]:%u, id=%s, %s; clients now: %u",
|
||||
client_addr, client_port, client->id, reason, RUN(stream_clients_count));
|
||||
|
||||
if (conn) {
|
||||
evhttp_connection_free(conn);
|
||||
}
|
||||
@@ -673,6 +684,7 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
client->next->prev = client->prev;
|
||||
}
|
||||
free(client->key);
|
||||
free(client->hostport);
|
||||
free(client);
|
||||
|
||||
free(reason);
|
||||
@@ -815,3 +827,40 @@ static bool _expose_new_frame(server_s *server) {
|
||||
#undef VID
|
||||
#undef STREAM
|
||||
#undef RUN
|
||||
|
||||
static char *_http_get_client_hostport(struct evhttp_request *request) {
|
||||
char *addr = NULL;
|
||||
unsigned short port = 0;
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(request);
|
||||
if (conn) {
|
||||
char *peer;
|
||||
evhttp_connection_get_peer(conn, &peer, &port);
|
||||
assert(addr = strdup(peer));
|
||||
}
|
||||
|
||||
const char *xff = evhttp_find_header(evhttp_request_get_input_headers(request), "X-Forwarded-For");
|
||||
if (xff) {
|
||||
if (addr) {
|
||||
free(addr);
|
||||
}
|
||||
assert(addr = strndup(xff, 1024));
|
||||
for (unsigned index = 0; addr[index]; ++index) {
|
||||
if (addr[index] == ',') {
|
||||
addr[index] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addr == NULL) {
|
||||
assert(addr = strdup("???"));
|
||||
}
|
||||
|
||||
char *hostport;
|
||||
size_t hostport_len = strlen(addr) + 64;
|
||||
A_CALLOC(hostport, hostport_len);
|
||||
|
||||
snprintf(hostport, hostport_len, "[%s]:%u", addr, port);
|
||||
free(addr);
|
||||
return hostport;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
@@ -47,8 +48,6 @@
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
#ifndef EVTHREAD_USE_PTHREADS_IMPLEMENTED
|
||||
# error Required libevent-pthreads support
|
||||
#endif
|
||||
@@ -81,8 +80,10 @@ typedef struct stream_client_sx {
|
||||
bool extra_headers;
|
||||
bool advance_headers;
|
||||
bool dual_final_frames;
|
||||
bool zero_data;
|
||||
|
||||
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
|
||||
char *hostport;
|
||||
uint64_t id;
|
||||
bool need_initial;
|
||||
bool need_first_frame;
|
||||
bool updated_prev;
|
||||
|
||||
@@ -32,7 +32,7 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 32);
|
||||
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16);
|
||||
sprintf(path, "%s/%s", root_path, simplified_path);
|
||||
|
||||
struct stat st;
|
||||
|
||||
@@ -119,23 +119,31 @@ int main(int argc, char *argv[]) {
|
||||
LOGGING_INIT;
|
||||
A_THREAD_RENAME("main");
|
||||
|
||||
# ifdef WITH_OMX
|
||||
bcm_host_init();
|
||||
OMX_ERRORTYPE omx_error;
|
||||
if ((omx_error = OMX_Init()) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(omx_error, "Can't initialize OMX Core");
|
||||
exit_code = -1;
|
||||
goto omx_error;
|
||||
}
|
||||
# endif
|
||||
|
||||
options_s *options = options_init(argc, argv);
|
||||
device_s *dev = device_init();
|
||||
encoder_s *enc = encoder_init();
|
||||
stream_s *stream = stream_init(dev, enc);
|
||||
server_s *server = server_init(stream);
|
||||
|
||||
# ifdef WITH_OMX
|
||||
bool i_bcm_host = false;
|
||||
OMX_ERRORTYPE omx_error = OMX_ErrorUndefined;
|
||||
# endif
|
||||
|
||||
if ((exit_code = options_parse(options, dev, enc, stream, server)) == 0) {
|
||||
# ifdef WITH_OMX
|
||||
if (enc->type == ENCODER_TYPE_OMX || stream->h264_sink) {
|
||||
bcm_host_init();
|
||||
i_bcm_host = true;
|
||||
}
|
||||
if (enc->type == ENCODER_TYPE_OMX) {
|
||||
if ((omx_error = OMX_Init()) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(omx_error, "Can't initialize OMX Core; forced CPU encoder");
|
||||
enc->type = ENCODER_TYPE_CPU;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
gpio_init();
|
||||
# endif
|
||||
@@ -173,9 +181,12 @@ int main(int argc, char *argv[]) {
|
||||
options_destroy(options);
|
||||
|
||||
# ifdef WITH_OMX
|
||||
OMX_Deinit();
|
||||
omx_error:
|
||||
bcm_host_deinit();
|
||||
if (omx_error == OMX_ErrorNone) {
|
||||
OMX_Deinit();
|
||||
}
|
||||
if (i_bcm_host) {
|
||||
bcm_host_deinit();
|
||||
}
|
||||
# endif
|
||||
|
||||
if (exit_code == 0) {
|
||||
|
||||
@@ -86,8 +86,10 @@ enum _OPT_VALUES {
|
||||
_O_##_prefix, \
|
||||
_O_##_prefix##_MODE, \
|
||||
_O_##_prefix##_RM, \
|
||||
_O_##_prefix##_CLIENT_TTL, \
|
||||
_O_##_prefix##_TIMEOUT,
|
||||
ADD_SINK(SINK)
|
||||
ADD_SINK(RAW_SINK)
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK(H264_SINK)
|
||||
_O_H264_BITRATE,
|
||||
@@ -174,11 +176,13 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
|
||||
|
||||
# define ADD_SINK(_opt, _prefix) \
|
||||
{_opt "sink", required_argument, NULL, _O_##_prefix}, \
|
||||
{_opt "sink-mode", required_argument, NULL, _O_##_prefix##_MODE}, \
|
||||
{_opt "sink-rm", no_argument, NULL, _O_##_prefix##_RM}, \
|
||||
{_opt "sink-timeout", required_argument, NULL, _O_##_prefix##_TIMEOUT},
|
||||
{_opt "sink", required_argument, NULL, _O_##_prefix}, \
|
||||
{_opt "sink-mode", required_argument, NULL, _O_##_prefix##_MODE}, \
|
||||
{_opt "sink-rm", no_argument, NULL, _O_##_prefix##_RM}, \
|
||||
{_opt "sink-client-ttl", required_argument, NULL, _O_##_prefix##_CLIENT_TTL}, \
|
||||
{_opt "sink-timeout", required_argument, NULL, _O_##_prefix##_TIMEOUT},
|
||||
ADD_SINK("", SINK)
|
||||
ADD_SINK("raw-", RAW_SINK)
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("h264-", H264_SINK)
|
||||
{"h264-bitrate", required_argument, NULL, _O_H264_BITRATE},
|
||||
@@ -243,6 +247,7 @@ void options_destroy(options_s *options) {
|
||||
} \
|
||||
}
|
||||
ADD_SINK(sink);
|
||||
ADD_SINK(raw_sink);
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK(h264_sink);
|
||||
# endif
|
||||
@@ -334,8 +339,10 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
char *_prefix##_name = NULL; \
|
||||
mode_t _prefix##_mode = 0660; \
|
||||
bool _prefix##_rm = false; \
|
||||
unsigned _prefix##_client_ttl = 10; \
|
||||
unsigned _prefix##_timeout = 1;
|
||||
ADD_SINK(sink);
|
||||
ADD_SINK(raw_sink);
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK(h264_sink);
|
||||
# endif
|
||||
@@ -345,18 +352,8 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
char *process_name_prefix = NULL;
|
||||
# endif
|
||||
|
||||
char short_opts[1024] = {0};
|
||||
|
||||
for (int short_index = 0, opt_index = 0; _LONG_OPTS[opt_index].name != NULL; ++opt_index) {
|
||||
if (isalpha(_LONG_OPTS[opt_index].val)) {
|
||||
short_opts[short_index] = _LONG_OPTS[opt_index].val;
|
||||
++short_index;
|
||||
if (_LONG_OPTS[opt_index].has_arg == required_argument) {
|
||||
short_opts[short_index] = ':';
|
||||
++short_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
char short_opts[128];
|
||||
build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
|
||||
for (int ch; (ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||
switch (ch) {
|
||||
@@ -431,8 +428,10 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
case _O_##_up: OPT_SET(_lp##_name, optarg); \
|
||||
case _O_##_up##_MODE: OPT_NUMBER("--" #_opt "sink-mode", _lp##_mode, INT_MIN, INT_MAX, 8); \
|
||||
case _O_##_up##_RM: OPT_SET(_lp##_rm, true); \
|
||||
case _O_##_up##_CLIENT_TTL: OPT_NUMBER("--" #_opt "sink-client-ttl", _lp##_client_ttl, 1, 60, 0); \
|
||||
case _O_##_up##_TIMEOUT: OPT_NUMBER("--" #_opt "sink-timeout", _lp##_timeout, 1, 60, 0);
|
||||
ADD_SINK("", sink, SINK)
|
||||
ADD_SINK("raw-", raw_sink, RAW_SINK)
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("h264-", h264_sink, H264_SINK)
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 100, 16000, 0);
|
||||
@@ -472,7 +471,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
case _O_FEATURES: _features(); return 1;
|
||||
|
||||
case 0: break;
|
||||
default: _help(stderr, dev, enc, stream, server); return -1;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,12 +486,14 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
true, \
|
||||
_prefix##_mode, \
|
||||
_prefix##_rm, \
|
||||
_prefix##_client_ttl, \
|
||||
_prefix##_timeout \
|
||||
); \
|
||||
} \
|
||||
stream->_prefix = options->_prefix; \
|
||||
}
|
||||
ADD_SINK("JPEG", sink);
|
||||
ADD_SINK("RAW", raw_sink);
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("H264", h264_sink);
|
||||
# endif
|
||||
@@ -616,7 +617,8 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" -K|--last-as-blank <sec> ──────────── Show the last frame received from the camera after it was disconnected,");
|
||||
SAY(" but no more than specified time (or endlessly if 0 is specified).");
|
||||
SAY(" If the device has not yet been online, display 'NO SIGNAL' or the image");
|
||||
SAY(" specified by option --blank. Default: disabled.\n");
|
||||
SAY(" specified by option --blank. Default: disabled.");
|
||||
SAY(" Note: currently this option has no effect on memory sinks.\n");
|
||||
SAY(" -l|--slowdown ─────────────────────── Slowdown capturing to 1 FPS or less when no stream or sink clients");
|
||||
SAY(" are connected. Useful to reduce CPU consumption. Default: disabled.\n");
|
||||
SAY(" --device-timeout <sec> ────────────── Timeout for device querying. Default: %u.\n", dev->timeout);
|
||||
@@ -661,15 +663,17 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
# define ADD_SINK(_name, _opt) \
|
||||
SAY(_name " sink options:"); \
|
||||
SAY("══════════════════"); \
|
||||
SAY(" --" _opt "sink <name> ──────── Use the shared memory to sink " _name " frames. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-mode <mode> ─── Set " _name " sink permissions (like 777). Default: 660.\n"); \
|
||||
SAY(" --" _opt "sink-rm ──────────── Remove shared memory on stop. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-timeout <sec> ─ Timeout for lock. Default: 1.\n");
|
||||
SAY(" --" _opt "sink <name> ─────────── Use the shared memory to sink " _name " frames. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-mode <mode> ────── Set " _name " sink permissions (like 777). Default: 660.\n"); \
|
||||
SAY(" --" _opt "sink-rm ─────────────── Remove shared memory on stop. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-client-ttl <sec> ─ Client TTL. Default: 10.\n"); \
|
||||
SAY(" --" _opt "sink-timeout <sec> ──── Timeout for lock. Default: 1.\n");
|
||||
ADD_SINK("JPEG", "")
|
||||
ADD_SINK("RAW", "raw-")
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("H264", "h264-")
|
||||
SAY(" --h264-bitrate <kbps> ───── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
|
||||
SAY(" --h264-gop <N> ──────────── Intarval between keyframes. Default: %u.\n", stream->h264_gop);
|
||||
SAY(" --h264-bitrate <kbps> ──────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
|
||||
SAY(" --h264-gop <N> ─────────────── Intarval between keyframes. Default: %u.\n", stream->h264_gop);
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
# ifdef WITH_GPIO
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
@@ -38,6 +37,7 @@
|
||||
#include "../libs/process.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/options.h"
|
||||
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
@@ -55,6 +55,7 @@ typedef struct {
|
||||
char **argv_copy;
|
||||
frame_s *blank;
|
||||
memsink_s *sink;
|
||||
memsink_s *raw_sink;
|
||||
# ifdef WITH_OMX
|
||||
memsink_s *h264_sink;
|
||||
# endif
|
||||
|
||||
@@ -25,11 +25,25 @@
|
||||
|
||||
static workers_pool_s *_stream_init_loop(stream_s *stream);
|
||||
static workers_pool_s *_stream_init_one(stream_s *stream);
|
||||
static bool _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned captured_fps);
|
||||
static void _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned captured_fps);
|
||||
|
||||
|
||||
#define RUN(_next) stream->run->_next
|
||||
|
||||
#define SINK_PUT(_sink, _frame) { \
|
||||
if (stream->_sink && memsink_server_check(stream->_sink, _frame)) {\
|
||||
memsink_server_put(stream->_sink, _frame); \
|
||||
} \
|
||||
}
|
||||
|
||||
#ifdef WITH_OMX
|
||||
# define H264_PUT(_frame, _vcsm_handle, _force_key) { \
|
||||
if (RUN(h264)) { \
|
||||
h264_stream_process(RUN(h264), _frame, _vcsm_handle, _force_key); \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
stream_s *stream_init(device_s *dev, encoder_s *enc) {
|
||||
stream_runtime_s *run;
|
||||
@@ -194,10 +208,9 @@ void stream_loop(stream_s *stream) {
|
||||
workers_pool_assign(pool, ready_wr);
|
||||
LOG_DEBUG("Assigned new frame in buffer %d to worker %s", buf_index, ready_wr->name);
|
||||
|
||||
SINK_PUT(raw_sink, &hw->raw);
|
||||
# ifdef WITH_OMX
|
||||
if (RUN(h264)) {
|
||||
h264_stream_process(RUN(h264), &hw->raw, hw->vcsm_handle, h264_force_key);
|
||||
}
|
||||
H264_PUT(&hw->raw, hw->vcsm_handle, h264_force_key);
|
||||
# endif
|
||||
}
|
||||
} else if (buf_index != -2) { // -2 for broken frame
|
||||
@@ -247,13 +260,7 @@ static workers_pool_s *_stream_init_loop(stream_s *stream) {
|
||||
LOG_DEBUG("%s: stream->run->stop=%d", __FUNCTION__, atomic_load(&RUN(stop)));
|
||||
|
||||
while (!atomic_load(&RUN(stop))) {
|
||||
if (_stream_expose_frame(stream, NULL, 0)) {
|
||||
# ifdef WITH_OMX
|
||||
if (RUN(h264)) {
|
||||
h264_stream_process(RUN(h264), stream->blank, -1, false);
|
||||
}
|
||||
# endif
|
||||
}
|
||||
_stream_expose_frame(stream, NULL, 0);
|
||||
|
||||
if (access(stream->dev->path, R_OK|W_OK) < 0) {
|
||||
if (access_error != errno) {
|
||||
@@ -297,11 +304,10 @@ static workers_pool_s *_stream_init_one(stream_s *stream) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned captured_fps) {
|
||||
static void _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned captured_fps) {
|
||||
# define VID(_next) RUN(video->_next)
|
||||
|
||||
frame_s *new = NULL;
|
||||
bool changed = false;
|
||||
|
||||
A_MUTEX_LOCK(&VID(mutex));
|
||||
|
||||
@@ -311,7 +317,11 @@ static bool _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned capt
|
||||
LOG_DEBUG("Exposed ALIVE video frame");
|
||||
|
||||
} else {
|
||||
if (VID(frame->online)) { // Если переходим из online в offline
|
||||
if (VID(frame->used == 0)) {
|
||||
new = stream->blank; // Инициализация
|
||||
RUN(last_as_blank_ts) = 0;
|
||||
|
||||
} else if (VID(frame->online)) { // Если переходим из online в offline
|
||||
if (stream->last_as_blank < 0) { // Если last_as_blank выключен, просто покажем старую картинку
|
||||
new = stream->blank;
|
||||
LOG_INFO("Changed video frame to BLANK");
|
||||
@@ -321,6 +331,7 @@ static bool _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned capt
|
||||
} else { // last_as_blank == 0 - показываем последний фрейм вечно
|
||||
LOG_INFO("Freezed last ALIVE video frame forever");
|
||||
}
|
||||
|
||||
} else if (stream->last_as_blank < 0) {
|
||||
new = stream->blank;
|
||||
// LOG_INFO("Changed video frame to BLANK");
|
||||
@@ -339,24 +350,28 @@ static bool _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned capt
|
||||
|
||||
if (new) {
|
||||
frame_copy(new, VID(frame));
|
||||
changed = true;
|
||||
} else if (VID(frame->used) == 0) { // Инициализация
|
||||
frame_copy(stream->blank, VID(frame));
|
||||
frame = NULL;
|
||||
changed = true;
|
||||
}
|
||||
VID(frame->online) = frame;
|
||||
VID(frame->online) = (bool)frame;
|
||||
VID(captured_fps) = captured_fps;
|
||||
atomic_store(&VID(updated), true);
|
||||
|
||||
A_MUTEX_UNLOCK(&VID(mutex));
|
||||
|
||||
if (changed && stream->sink) {
|
||||
memsink_server_put(stream->sink, VID(frame));
|
||||
}
|
||||
new = (frame ? frame : stream->blank);
|
||||
SINK_PUT(sink, new);
|
||||
|
||||
return changed;
|
||||
if (frame == NULL) {
|
||||
SINK_PUT(raw_sink, stream->blank);
|
||||
# ifdef WITH_OMX
|
||||
H264_PUT(stream->blank, -1, false);
|
||||
# endif
|
||||
}
|
||||
|
||||
# undef VID
|
||||
}
|
||||
|
||||
#ifdef WITH_OMX
|
||||
# undef H264_PUT
|
||||
#endif
|
||||
#undef SINK_PUT
|
||||
#undef RUN
|
||||
|
||||
@@ -80,6 +80,7 @@ typedef struct {
|
||||
unsigned error_delay;
|
||||
|
||||
memsink_s *sink;
|
||||
memsink_s *raw_sink;
|
||||
|
||||
# ifdef WITH_OMX
|
||||
memsink_s *h264_sink;
|
||||
|
||||
Reference in New Issue
Block a user