mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 3.6
|
||||
current_version = 3.17
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
@@ -10,7 +10,7 @@ serialize =
|
||||
search = VERSION "{current_version}"
|
||||
replace = VERSION "{new_version}"
|
||||
|
||||
[bumpversion:file:src/python/setup.py]
|
||||
[bumpversion:file:python/setup.py]
|
||||
search = version="{current_version}"
|
||||
replace = version="{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
|
||||
|
||||
43
Makefile
43
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,7 +20,8 @@ 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
|
||||
|
||||
@@ -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,7 +75,7 @@ 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
|
||||
|
||||
|
||||
@@ -81,16 +83,16 @@ endif
|
||||
all: $(USTR) $(DUMP) python
|
||||
|
||||
|
||||
install: $(USTR) $(DUMP)
|
||||
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 $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
|
||||
gzip $(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 src/python && python3 setup.py install --prefix=$(PREFIX) --root=$(DESTDIR)
|
||||
cd python && $(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
|
||||
endif
|
||||
|
||||
|
||||
@@ -107,33 +109,34 @@ 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)),)
|
||||
cd src/python && python3 setup.py build
|
||||
ln -sf src/python/build/lib.*/*.so .
|
||||
$(info == PY_BUILD ustreamer-*.so)
|
||||
@ cd python && $(PY) setup.py build
|
||||
@ ln -sf python/build/lib.*/*.so .
|
||||
else
|
||||
@ true
|
||||
endif
|
||||
@@ -180,7 +183,7 @@ 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) src/python/build vgcore.* *.sock *.so
|
||||
rm -rf $(USTR) $(DUMP) $(BUILD) python/build vgcore.* *.sock *.so
|
||||
|
||||
|
||||
.PHONY: python linters
|
||||
|
||||
22
README.md
22
README.md
@@ -25,7 +25,7 @@
|
||||
|
||||
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()```.
|
||||
|
||||
-----
|
||||
@@ -39,7 +39,7 @@ You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support,
|
||||
* 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`.
|
||||
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg-dev libjpeg62-dev uuid-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libuuid1 libbsd0`.
|
||||
|
||||
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 +50,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 +77,22 @@ $ ./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
|
||||
```
|
||||
|
||||
-----
|
||||
# See also
|
||||
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
|
||||
|
||||
20
README.ru.md
20
README.ru.md
@@ -25,7 +25,7 @@
|
||||
|
||||
Сносочки:
|
||||
* ```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()```.
|
||||
|
||||
-----
|
||||
@@ -39,7 +39,7 @@
|
||||
* 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`.
|
||||
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg-dev libjpeg62-dev uuid-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libuuid1 libbsd0`.
|
||||
|
||||
На 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 +77,22 @@ $ ./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
|
||||
```
|
||||
|
||||
-----
|
||||
# Смотрите также
|
||||
* [Запуск с помощью 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' src/python/*.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 src/python/*.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 src/python/*.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 src/python/*.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.6" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 3.17" "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.6" "November 2020"
|
||||
.TH USTREAMER 1 "version 3.17" "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
|
||||
@@ -234,6 +234,12 @@ 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,20 +3,20 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=3.6
|
||||
pkgver=3.17
|
||||
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
|
||||
makedepends=(gcc make)
|
||||
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
||||
md5sums=(SKIP)
|
||||
|
||||
|
||||
_options="WITH_GPIO=1"
|
||||
if [ -e /usr/lib/python3 ]; then
|
||||
if [ -e /usr/bin/python3 ]; then
|
||||
_options="$_options WITH_PYTHON=1"
|
||||
depends+=(python)
|
||||
makedepends+=(python-setuptools)
|
||||
@@ -28,20 +28,21 @@ if [ -e /opt/vc/include/IL/OMX_Core.h ]; then
|
||||
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
|
||||
|
||||
# LD does not link mmal with this option
|
||||
LDFLAGS="${LDFLAGS//--as-needed/}"
|
||||
LDFLAGS="${LDFLAGS//,,/,}"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=3.6
|
||||
PKG_VERSION:=3.17
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from distutils.core import setup
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="3.6",
|
||||
version="3.17",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
@@ -17,6 +17,10 @@ if __name__ == "__main__":
|
||||
libraries=["rt", "m", "pthread"],
|
||||
undef_macros=["NDEBUG"],
|
||||
sources=["ustreamer.c"],
|
||||
depends=[
|
||||
"../src/libs/tools.h",
|
||||
"../src/libs/memsinksh.h",
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -2,9 +2,9 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
@@ -13,26 +13,56 @@
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "../libs/tools.h" // Just a header without C-sources
|
||||
#include "../libs/memsinksh.h" // No sources again
|
||||
#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;
|
||||
uint8_t *tmp_data;
|
||||
size_t tmp_data_allocated;
|
||||
uint64_t last_id;
|
||||
|
||||
tmp_frame_s *tmp_frame;
|
||||
PyObject *dict_frame; // PyDict
|
||||
} MemsinkObject;
|
||||
|
||||
|
||||
#define MEM(_next) self->mem->_next
|
||||
#define TMP(_next) self->tmp_frame->_next
|
||||
|
||||
|
||||
static void MemsinkObject_destroy_internals(MemsinkObject *self) {
|
||||
if (self->dict_frame != NULL) {
|
||||
Py_DECREF(self->dict_frame);
|
||||
self->dict_frame = NULL;
|
||||
}
|
||||
if (self->mem != NULL) {
|
||||
munmap(self->mem, sizeof(memsink_shared_s));
|
||||
self->mem = NULL;
|
||||
@@ -41,10 +71,12 @@ static void MemsinkObject_destroy_internals(MemsinkObject *self) {
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
}
|
||||
if (self->tmp_data) {
|
||||
free(self->tmp_data);
|
||||
self->tmp_data = NULL;
|
||||
self->tmp_data_allocated = 0;
|
||||
if (self->tmp_frame) {
|
||||
if (TMP(data)) {
|
||||
free(TMP(data));
|
||||
}
|
||||
free(self->tmp_frame);
|
||||
self->tmp_frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,27 +84,29 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
|
||||
self->lock_timeout = 1;
|
||||
self->wait_timeout = 1;
|
||||
|
||||
static char *kws[] = {"obj", "lock_timeout", "wait_timeout", NULL};
|
||||
static char *kws[] = {"obj", "lock_timeout", "wait_timeout", "drop_same_frames", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, kwargs, "s|dd", kws,
|
||||
&self->obj, &self->lock_timeout, &self->wait_timeout)) {
|
||||
args, kwargs, "s|ddd", kws,
|
||||
&self->obj, &self->lock_timeout, &self->wait_timeout, &self->drop_same_frames)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define SET_TIMEOUT(_timeout) { \
|
||||
if (self->_timeout <= 0) { \
|
||||
PyErr_SetString(PyExc_ValueError, #_timeout " must be > 0"); \
|
||||
# define SET_DOUBLE(_field, _cond) { \
|
||||
if (!(self->_field _cond)) { \
|
||||
PyErr_SetString(PyExc_ValueError, #_field " must be " #_cond); \
|
||||
return -1; \
|
||||
} \
|
||||
}
|
||||
|
||||
SET_TIMEOUT(lock_timeout);
|
||||
SET_TIMEOUT(wait_timeout);
|
||||
SET_DOUBLE(lock_timeout, > 0);
|
||||
SET_DOUBLE(wait_timeout, > 0);
|
||||
SET_DOUBLE(drop_same_frames, >= 0);
|
||||
|
||||
# undef CHECK_TIMEOUT
|
||||
# undef SET_DOUBLE
|
||||
|
||||
self->tmp_data_allocated = 512 * 1024;
|
||||
A_REALLOC(self->tmp_data, self->tmp_data_allocated);
|
||||
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);
|
||||
@@ -96,6 +130,10 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((self->dict_frame = PyDict_New()) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
@@ -128,92 +166,145 @@ static PyObject *MemsinkObject_exit(MemsinkObject *self, PyObject *Py_UNUSED(ign
|
||||
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;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
bool failed = false;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
long double deadline_ts = get_now_monotonic() + self->wait_timeout;
|
||||
do {
|
||||
int retval = flock_timedwait_monotonic(self->fd, self->lock_timeout);
|
||||
if (retval < 0 && errno != EWOULDBLOCK) {
|
||||
failed = true;
|
||||
break;
|
||||
} else if (retval == 0) {
|
||||
if (
|
||||
self->mem->magic == MEMSINK_MAGIC
|
||||
&& self->mem->version == MEMSINK_VERSION
|
||||
&& self->mem->id != self->last_id
|
||||
) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (flock(self->fd, LOCK_UN) < 0) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
errno = 0;
|
||||
usleep(1000);
|
||||
if (errno == EINTR) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
} while (get_now_monotonic() < deadline_ts);
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (failed) {
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
if (!found) {
|
||||
Py_RETURN_NONE;
|
||||
switch (wait_frame(self)) {
|
||||
case 0: break;
|
||||
case -2: Py_RETURN_NONE;
|
||||
default: return NULL;
|
||||
}
|
||||
|
||||
# define COPY(_type, _field) _type tmp_##_field = self->mem->_field
|
||||
COPY(unsigned, width);
|
||||
COPY(unsigned, height);
|
||||
COPY(unsigned, format);
|
||||
COPY(unsigned, stride);
|
||||
COPY(bool, online);
|
||||
COPY(double, grab_ts);
|
||||
COPY(double, encode_begin_ts);
|
||||
COPY(double, encode_end_ts);
|
||||
COPY(unsigned, used);
|
||||
# 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 (self->tmp_data_allocated < self->mem->used) {
|
||||
size_t size = self->mem->used + (512 * 1024);
|
||||
A_REALLOC(self->tmp_data, size);
|
||||
self->tmp_data_allocated = size;
|
||||
if (TMP(allocated) < MEM(used)) {
|
||||
size_t size = MEM(used) + (512 * 1024);
|
||||
A_REALLOC(TMP(data), size);
|
||||
TMP(allocated) = size;
|
||||
}
|
||||
memcpy(self->tmp_data, self->mem->data, self->mem->used);
|
||||
memcpy(TMP(data), MEM(data), MEM(used));
|
||||
TMP(used) = MEM(used);
|
||||
|
||||
self->mem->last_client_ts = get_now_monotonic();
|
||||
self->last_id = self->mem->id;
|
||||
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 *frame = PyDict_New();
|
||||
# define SET_VALUE(_field, _from, _to) PyDict_SetItemString(frame, #_field, Py##_to##_From##_from(tmp_##_field))
|
||||
SET_VALUE(width, Long, Long);
|
||||
SET_VALUE(height, Long, Long);
|
||||
SET_VALUE(format, Long, Long);
|
||||
SET_VALUE(stride, Long, Long);
|
||||
SET_VALUE(online, Long, Bool);
|
||||
SET_VALUE(grab_ts, Double, Float);
|
||||
SET_VALUE(encode_begin_ts, Double, Float);
|
||||
SET_VALUE(encode_end_ts, Double, Float);
|
||||
PyDict_Clear(self->dict_frame);
|
||||
|
||||
# define SET_VALUE(_key, _maker) { \
|
||||
PyObject *_tmp = _maker; \
|
||||
if (_tmp == NULL) { \
|
||||
return NULL; \
|
||||
} \
|
||||
if (PyDict_SetItemString(self->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
|
||||
PyDict_SetItemString(frame, "data", PyBytes_FromStringAndSize((const char *)self->tmp_data, tmp_used));
|
||||
return frame;
|
||||
|
||||
Py_INCREF(self->dict_frame);
|
||||
return self->dict_frame;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
@@ -228,16 +319,18 @@ static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSE
|
||||
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(_meth, _flags) {.ml_name = #_meth, .ml_meth = (PyCFunction)MemsinkObject_##_meth, .ml_flags = (_flags)}
|
||||
ADD_METHOD(close, METH_NOARGS),
|
||||
ADD_METHOD(enter, METH_NOARGS),
|
||||
ADD_METHOD(exit, METH_VARARGS),
|
||||
ADD_METHOD(wait_frame, METH_NOARGS),
|
||||
ADD_METHOD(is_opened, METH_NOARGS),
|
||||
# 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
|
||||
};
|
||||
@@ -247,6 +340,7 @@ static PyGetSetDef MemsinkObject_getsets[] = {
|
||||
ADD_GETTER(obj),
|
||||
ADD_GETTER(lock_timeout),
|
||||
ADD_GETTER(wait_timeout),
|
||||
ADD_GETTER(drop_same_frames),
|
||||
{},
|
||||
# undef ADD_GETTER
|
||||
};
|
||||
@@ -288,3 +382,6 @@ PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
#undef TMP
|
||||
#undef MEM
|
||||
@@ -213,6 +213,8 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_cont
|
||||
unsigned fps_accum = 0;
|
||||
long long fps_second = 0;
|
||||
|
||||
long double last_ts = 0;
|
||||
|
||||
while (!global_stop) {
|
||||
int error = memsink_client_get(sink, frame);
|
||||
if (error == 0) {
|
||||
@@ -220,11 +222,12 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_cont
|
||||
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);
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef VERSION
|
||||
# define VERSION "3.6"
|
||||
# define VERSION "3.17"
|
||||
#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;
|
||||
|
||||
@@ -40,7 +40,11 @@ memsink_s *memsink_init(
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -105,7 +109,7 @@ bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||
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));
|
||||
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) {
|
||||
@@ -138,6 +142,7 @@ 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);
|
||||
@@ -191,6 +196,7 @@ int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress u
|
||||
COPY(format);
|
||||
COPY(stride);
|
||||
COPY(online);
|
||||
COPY(key);
|
||||
COPY(grab_ts);
|
||||
COPY(encode_begin_ts);
|
||||
COPY(encode_end_ts);
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
|
||||
#define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||
#define MEMSINK_VERSION ((uint32_t)1)
|
||||
#define MEMSINK_VERSION ((uint32_t)2)
|
||||
|
||||
#ifndef CFG_MEMSINK_MAX_DATA
|
||||
# define CFG_MEMSINK_MAX_DATA 33554432
|
||||
@@ -49,6 +49,7 @@ typedef struct {
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
bool online;
|
||||
bool key;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
|
||||
@@ -135,7 +135,9 @@ INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
if (retval == 0 || errno != EWOULDBLOCK || get_now_monotonic() > deadline_ts) {
|
||||
break;
|
||||
}
|
||||
usleep(1000);
|
||||
if (usleep(1000) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -275,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;
|
||||
}
|
||||
|
||||
@@ -339,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");
|
||||
@@ -347,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 {
|
||||
|
||||
@@ -617,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);
|
||||
|
||||
@@ -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,16 +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);
|
||||
|
||||
if (stream->raw_sink) {
|
||||
if (memsink_server_check(stream->raw_sink, &hw->raw)) {
|
||||
memsink_server_put(stream->raw_sink, &hw->raw);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -253,18 +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)) {
|
||||
if (stream->raw_sink) {
|
||||
if (memsink_server_check(stream->raw_sink, stream->blank)) {
|
||||
memsink_server_put(stream->raw_sink, stream->blank);
|
||||
}
|
||||
}
|
||||
# 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) {
|
||||
@@ -308,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));
|
||||
|
||||
@@ -322,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");
|
||||
@@ -332,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");
|
||||
@@ -350,26 +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) {
|
||||
if (memsink_server_check(stream->sink, VID(frame))) {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user