mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a97f08eac8 | ||
|
|
92ff097d7e | ||
|
|
0eb0370bd3 | ||
|
|
b329dfed1f | ||
|
|
62dc2e5ad5 | ||
|
|
7ea81fa627 | ||
|
|
8ddf77a3d3 | ||
|
|
fde89465ac | ||
|
|
aa1d78a3cd | ||
|
|
6dfe077775 | ||
|
|
dd6dc866a6 | ||
|
|
3bf68884f5 | ||
|
|
e1b2eceea5 | ||
|
|
7ce11fecb6 | ||
|
|
cbc6145977 | ||
|
|
24f7fb797b | ||
|
|
71f1d397bf | ||
|
|
00e83c155e | ||
|
|
1f186a0afe | ||
|
|
3c7075d0d2 | ||
|
|
c1d7bd1595 | ||
|
|
04d1d3d5a6 | ||
|
|
01bc0529e7 | ||
|
|
181231f3ff | ||
|
|
58569f0315 | ||
|
|
5903fcf718 | ||
|
|
1a820b23a5 | ||
|
|
67b767b152 | ||
|
|
74bf710bb6 | ||
|
|
1d3b428a75 | ||
|
|
749bc5caf7 | ||
|
|
3b7cbc62c4 | ||
|
|
dff49d8e7b | ||
|
|
b23883e65f | ||
|
|
c2e30c7fc4 | ||
|
|
37216250b8 | ||
|
|
dc82894038 | ||
|
|
f3a350148e | ||
|
|
5e49f50e22 | ||
|
|
91ff97f721 | ||
|
|
daaa488dd6 |
@@ -1,14 +1,22 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 3.18
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
current_version = 3.26
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
[bumpversion:file:src/libs/config.h]
|
||||
search = VERSION "{current_version}"
|
||||
replace = VERSION "{new_version}"
|
||||
parse = (?P<major>\d+)
|
||||
serialize = {major}
|
||||
search = VERSION_MAJOR {current_version}
|
||||
replace = VERSION_MAJOR {new_version}
|
||||
|
||||
[bumpversion:file:./src/libs/config.h]
|
||||
parse = <major>\d+\.(?P<minor>\d+)
|
||||
serialize = {minor}
|
||||
search = VERSION_MINOR {current_version}
|
||||
replace = VERSION_MINOR {new_version}
|
||||
|
||||
[bumpversion:file:python/setup.py]
|
||||
search = version="{current_version}"
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -5,11 +5,12 @@
|
||||
/pkg/arch/v*.tar.gz
|
||||
/pkg/arch/ustreamer-*.pkg.tar.xz
|
||||
/pkg/arch/ustreamer-*.pkg.tar.zst
|
||||
/build/
|
||||
/src/build/
|
||||
/src/*.bin
|
||||
/python/build/
|
||||
/config.mk
|
||||
/vgcore.*
|
||||
/ustreamer
|
||||
/ustreamer-dump
|
||||
/*.so
|
||||
/config.mk
|
||||
/vgcore.*
|
||||
/*.sock
|
||||
/*.so
|
||||
|
||||
168
Makefile
168
Makefile
@@ -1,7 +1,5 @@
|
||||
-include config.mk
|
||||
|
||||
USTR ?= ustreamer
|
||||
DUMP ?= ustreamer-dump
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
MANPREFIX ?= $(PREFIX)/share/man
|
||||
@@ -14,147 +12,71 @@ LDFLAGS ?=
|
||||
RPI_VC_HEADERS ?= /opt/vc/include
|
||||
RPI_VC_LIBS ?= /opt/vc/lib
|
||||
|
||||
BUILD ?= build
|
||||
export
|
||||
|
||||
LINTERS_IMAGE ?= $(USTR)-linters
|
||||
_LINTERS_IMAGE ?= ustreamer-linters
|
||||
|
||||
|
||||
# =====
|
||||
_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
|
||||
_USTR_SRCS = $(shell ls \
|
||||
src/libs/*.c \
|
||||
src/ustreamer/*.c \
|
||||
src/ustreamer/http/*.c \
|
||||
src/ustreamer/data/*.c \
|
||||
src/ustreamer/encoders/cpu/*.c \
|
||||
src/ustreamer/encoders/hw/*.c \
|
||||
)
|
||||
|
||||
_DUMP_LIBS = $(_COMMON_LIBS)
|
||||
_DUMP_SRCS = $(shell ls \
|
||||
src/libs/*.c \
|
||||
src/dump/*.c \
|
||||
)
|
||||
|
||||
|
||||
define optbool
|
||||
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||
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)
|
||||
_USTR_SRCS += $(shell ls \
|
||||
src/ustreamer/encoders/omx/*.c \
|
||||
src/ustreamer/h264/*.c \
|
||||
)
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||
_USTR_LIBS += -lgpiod
|
||||
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
|
||||
endif
|
||||
|
||||
|
||||
WITH_SETPROCTITLE ?= 1
|
||||
ifneq ($(call optbool,$(WITH_SETPROCTITLE)),)
|
||||
ifeq ($(shell uname -s | tr A-Z a-z),linux)
|
||||
_USTR_LIBS += -lbsd
|
||||
endif
|
||||
override _CFLAGS += -DWITH_SETPROCTITLE
|
||||
endif
|
||||
|
||||
|
||||
# =====
|
||||
all: $(USTR) $(DUMP) python
|
||||
|
||||
|
||||
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
|
||||
all:
|
||||
+ $(MAKE) apps
|
||||
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
||||
cd python && $(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
|
||||
+ $(MAKE) python
|
||||
endif
|
||||
|
||||
|
||||
install-strip: install
|
||||
strip $(DESTDIR)$(PREFIX)/bin/$(USTR)
|
||||
strip $(DESTDIR)$(PREFIX)/bin/$(DUMP)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
$(USTR): $(_USTR_SRCS:%.c=$(BUILD)/%.o)
|
||||
# $(info ========================================)
|
||||
$(info == LD $@)
|
||||
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
|
||||
# $(info :: CC = $(CC))
|
||||
# $(info :: LIBS = $(_USTR_LIBS))
|
||||
# $(info :: CFLAGS = $(_CFLAGS))
|
||||
# $(info :: LDFLAGS = $(_LDFLAGS))
|
||||
|
||||
|
||||
$(DUMP): $(_DUMP_SRCS:%.c=$(BUILD)/%.o)
|
||||
# $(info ========================================)
|
||||
$(info == LD $@)
|
||||
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
||||
# $(info :: CC = $(CC))
|
||||
# $(info :: LIBS = $(_DUMP_LIBS))
|
||||
# $(info :: CFLAGS = $(_CFLAGS))
|
||||
# $(info :: LDFLAGS = $(_LDFLAGS))
|
||||
|
||||
|
||||
$(BUILD)/%.o: %.c
|
||||
$(info -- CC $<)
|
||||
@ mkdir -p $(dir $@) || true
|
||||
@ $(CC) $< -o $@ $(_CFLAGS)
|
||||
apps:
|
||||
$(MAKE) -C src
|
||||
@ ln -sf src/ustreamer.bin ustreamer
|
||||
@ ln -sf src/ustreamer-dump.bin ustreamer-dump
|
||||
|
||||
|
||||
python:
|
||||
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
||||
$(info == PY_BUILD ustreamer-*.so)
|
||||
@ cd python && $(PY) setup.py build
|
||||
$(MAKE) -C python
|
||||
@ ln -sf python/build/lib.*/*.so .
|
||||
else
|
||||
@ true
|
||||
|
||||
|
||||
install: all
|
||||
$(MAKE) -C src install
|
||||
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
||||
$(MAKE) -C python install
|
||||
endif
|
||||
mkdir -p $(DESTDIR)$(MANPREFIX)/man1
|
||||
for man in $(shell ls man); do \
|
||||
install -m644 man/$$man $(DESTDIR)$(MANPREFIX)/man1/$$man; \
|
||||
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$$man; \
|
||||
done
|
||||
|
||||
|
||||
install-strip: install
|
||||
$(MAKE) -C src install-strip
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
release:
|
||||
make clean
|
||||
make tox
|
||||
make push
|
||||
make bump V=$(V)
|
||||
make push
|
||||
make clean
|
||||
$(MAKE) clean
|
||||
$(MAKE) tox
|
||||
$(MAKE) push
|
||||
$(MAKE) bump V=$(V)
|
||||
$(MAKE) push
|
||||
$(MAKE) clean
|
||||
|
||||
|
||||
tox: linters
|
||||
time docker run --rm \
|
||||
--volume `pwd`:/src:ro \
|
||||
--volume `pwd`/linters:/src/linters:rw \
|
||||
-t $(LINTERS_IMAGE) bash -c " \
|
||||
-t $(_LINTERS_IMAGE) bash -c " \
|
||||
cd /src \
|
||||
&& tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \
|
||||
"
|
||||
@@ -164,7 +86,7 @@ linters:
|
||||
docker build \
|
||||
$(if $(call optbool,$(NC)),--no-cache,) \
|
||||
--rm \
|
||||
--tag $(LINTERS_IMAGE) \
|
||||
--tag $(_LINTERS_IMAGE) \
|
||||
-f linters/Dockerfile linters
|
||||
|
||||
|
||||
@@ -180,14 +102,14 @@ push:
|
||||
clean-all: linters clean
|
||||
- docker run --rm \
|
||||
--volume `pwd`:/src \
|
||||
-it $(LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
|
||||
-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) python/build vgcore.* *.sock *.so
|
||||
rm -f ustreamer ustreamer-dump *.so
|
||||
$(MAKE) -C src clean
|
||||
$(MAKE) -C python clean
|
||||
|
||||
|
||||
.PHONY: python linters
|
||||
|
||||
|
||||
_OBJS = $(_USTR_SRCS:%.c=$(BUILD)/%.o) $(_DUMP_SRCS:%.c=$(BUILD)/%.o)
|
||||
-include $(_OBJS:%.o=%.d)
|
||||
|
||||
@@ -38,8 +38,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 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 libbsd-dev`.
|
||||
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg-dev libjpeg62-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libbsd0`.
|
||||
* 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```.
|
||||
|
||||
@@ -100,8 +99,8 @@ v4l2 utilities provide the tools to manage USB webcam setting and information. S
|
||||
* 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`.
|
||||
* 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).
|
||||
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
|
||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `libgpiod` для `WITH_GPIO=1`.
|
||||
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev libbsd-dev`.
|
||||
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg-dev libjpeg62-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libbsd0`.
|
||||
* 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```.
|
||||
|
||||
@@ -100,8 +99,8 @@ V4L2 предоставляет ряд официальных утилит дл
|
||||
* Вывести список видеоустройств: `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`.
|
||||
* Показать текущее значение контрола: `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).
|
||||
|
||||
|
||||
3
linters/cppcheck.h
Normal file
3
linters/cppcheck.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#define CHAR_BIT 8
|
||||
#define WITH_OMX
|
||||
#define WITH_GPIO
|
||||
@@ -1,12 +1,12 @@
|
||||
[tox]
|
||||
envlist = cppcheck-src, cppcheck-python, flake8, pylint, mypy, vulture, htmlhint
|
||||
envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
|
||||
skipsdist = true
|
||||
|
||||
[testenv]
|
||||
basepython = python3.9
|
||||
changedir = /src
|
||||
|
||||
[testenv:cppcheck-src]
|
||||
[testenv:cppcheck]
|
||||
whitelist_externals = cppcheck
|
||||
commands = cppcheck \
|
||||
--force \
|
||||
@@ -18,25 +18,8 @@ commands = cppcheck \
|
||||
--suppress=variableScope \
|
||||
--inline-suppr \
|
||||
--library=python \
|
||||
-DCHAR_BIT=8 \
|
||||
-DWITH_OMX \
|
||||
-DWITH_GPIO \
|
||||
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
|
||||
--include=linters/cppcheck.h \
|
||||
src python/ustreamer.c janus/rtp.h janus/rtp.c janus/plugin.c
|
||||
|
||||
[testenv:flake8]
|
||||
whitelist_externals = bash
|
||||
|
||||
@@ -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.18" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 3.26" "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.18" "November 2020"
|
||||
.TH USTREAMER 1 "version 3.26" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPG video from any V4L2 device to the network
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=3.18
|
||||
pkgver=3.26
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=3.18
|
||||
PKG_VERSION:=3.26
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
20
python/Makefile
Normal file
20
python/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
-include ../config.mk
|
||||
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
PY ?= python3
|
||||
|
||||
|
||||
# =====
|
||||
all:
|
||||
$(info == PY_BUILD ustreamer-*.so)
|
||||
@ $(PY) setup.py build
|
||||
|
||||
|
||||
install:
|
||||
$(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
from distutils.core import Extension
|
||||
from distutils.core import setup
|
||||
|
||||
@@ -6,7 +8,7 @@ from distutils.core import setup
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="3.18",
|
||||
version="3.26",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
@@ -16,11 +18,8 @@ if __name__ == "__main__":
|
||||
"ustreamer",
|
||||
libraries=["rt", "m", "pthread"],
|
||||
undef_macros=["NDEBUG"],
|
||||
sources=["ustreamer.c"],
|
||||
depends=[
|
||||
"../src/libs/tools.h",
|
||||
"../src/libs/memsinksh.h",
|
||||
],
|
||||
sources=["src/" + name for name in os.listdir("src") if name.endswith(".c")],
|
||||
depends=["src/" + name for name in os.listdir("src") if name.endswith(".h")],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
1
python/src/frame.c
Symbolic link
1
python/src/frame.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../src/libs/frame.c
|
||||
1
python/src/frame.h
Symbolic link
1
python/src/frame.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../src/libs/frame.h
|
||||
1
python/src/memsinksh.h
Symbolic link
1
python/src/memsinksh.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../src/libs/memsinksh.h
|
||||
1
python/src/tools.h
Symbolic link
1
python/src/tools.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../src/libs/tools.h
|
||||
@@ -13,31 +13,11 @@
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "../src/libs/tools.h" // Just a header without C-sources
|
||||
#include "../src/libs/memsinksh.h" // No sources again
|
||||
#include "tools.h"
|
||||
#include "frame.h"
|
||||
#include "memsinksh.h"
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -49,34 +29,28 @@ typedef struct {
|
||||
int fd;
|
||||
memsink_shared_s *mem;
|
||||
|
||||
tmp_frame_s *tmp_frame;
|
||||
PyObject *dict_frame; // PyDict
|
||||
uint64_t frame_id;
|
||||
long double frame_ts;
|
||||
frame_s *frame;
|
||||
} MemsinkObject;
|
||||
|
||||
|
||||
#define MEM(_next) self->mem->_next
|
||||
#define TMP(_next) self->tmp_frame->_next
|
||||
#define MEM(_next) self->mem->_next
|
||||
#define FRAME(_next) self->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));
|
||||
memsink_shared_unmap(self->mem);
|
||||
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;
|
||||
if (self->frame) {
|
||||
frame_destroy(self->frame);
|
||||
self->frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,33 +78,15 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
|
||||
|
||||
# undef SET_DOUBLE
|
||||
|
||||
A_CALLOC(self->tmp_frame, 1);
|
||||
TMP(allocated) = 512 * 1024;
|
||||
A_REALLOC(TMP(data), TMP(allocated));
|
||||
self->frame = frame_init();
|
||||
|
||||
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) {
|
||||
if ((self->mem = memsink_shared_map(self->fd)) == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
self->mem = NULL;
|
||||
goto error;
|
||||
}
|
||||
if (self->mem == NULL) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Memory mapping is NULL"); \
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((self->dict_frame = PyDict_New()) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -186,24 +142,16 @@ static int wait_frame(MemsinkObject *self) {
|
||||
RETURN_OS_ERROR;
|
||||
|
||||
} else if (retval == 0) {
|
||||
if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && TMP(id) != MEM(id)) {
|
||||
if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && MEM(id) != self->frame_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))
|
||||
FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
|
||||
&& (self->frame_ts + self->drop_same_frames > now)
|
||||
&& !memcmp(FRAME(data), MEM(data), MEM(used))
|
||||
) {
|
||||
TMP(id) = MEM(id);
|
||||
self->frame_id = MEM(id);
|
||||
goto drop;
|
||||
}
|
||||
# undef CMP
|
||||
}
|
||||
|
||||
Py_BLOCK_THREADS
|
||||
@@ -245,49 +193,33 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
|
||||
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);
|
||||
frame_set_data(self->frame, MEM(data), MEM(used));
|
||||
FRAME_COPY_META(self->mem, self->frame);
|
||||
self->frame_id = MEM(id);
|
||||
self->frame_ts = get_now_monotonic();
|
||||
MEM(last_client_ts) = self->frame_ts;
|
||||
|
||||
if (flock(self->fd, LOCK_UN) < 0) {
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
|
||||
PyDict_Clear(self->dict_frame);
|
||||
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(self->dict_frame, _key, _tmp) < 0) { \
|
||||
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)))
|
||||
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(FRAME(_key)))
|
||||
|
||||
SET_NUMBER(width, Long, Long);
|
||||
SET_NUMBER(height, Long, Long);
|
||||
@@ -298,13 +230,12 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
|
||||
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)));
|
||||
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)FRAME(data), FRAME(used)));
|
||||
|
||||
# undef SET_NUMBER
|
||||
# undef SET_VALUE
|
||||
|
||||
Py_INCREF(self->dict_frame);
|
||||
return self->dict_frame;
|
||||
return dict_frame;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
@@ -383,5 +314,5 @@ PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
|
||||
return module;
|
||||
}
|
||||
|
||||
#undef TMP
|
||||
#undef FRAME
|
||||
#undef MEM
|
||||
113
src/Makefile
Normal file
113
src/Makefile
Normal file
@@ -0,0 +1,113 @@
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -O3
|
||||
LDFLAGS ?=
|
||||
|
||||
RPI_VC_HEADERS ?= /opt/vc/include
|
||||
RPI_VC_LIBS ?= /opt/vc/lib
|
||||
|
||||
|
||||
# =====
|
||||
_USTR = ustreamer.bin
|
||||
_DUMP = ustreamer-dump.bin
|
||||
|
||||
_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
|
||||
_USTR_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
ustreamer/*.c \
|
||||
ustreamer/http/*.c \
|
||||
ustreamer/data/*.c \
|
||||
ustreamer/encoders/cpu/*.c \
|
||||
ustreamer/encoders/hw/*.c \
|
||||
)
|
||||
|
||||
_DUMP_LIBS = $(_COMMON_LIBS)
|
||||
_DUMP_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
dump/*.c \
|
||||
)
|
||||
|
||||
_BUILD = build
|
||||
|
||||
|
||||
define optbool
|
||||
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||
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)
|
||||
_USTR_SRCS += $(shell ls \
|
||||
ustreamer/encoders/omx/*.c \
|
||||
ustreamer/h264/*.c \
|
||||
)
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||
_USTR_LIBS += -lgpiod
|
||||
override _CFLAGS += -DWITH_GPIO
|
||||
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
|
||||
endif
|
||||
|
||||
|
||||
WITH_PTHREAD_NP ?= 1
|
||||
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
|
||||
override _CFLAGS += -DWITH_PTHREAD_NP
|
||||
endif
|
||||
|
||||
|
||||
WITH_SETPROCTITLE ?= 1
|
||||
ifneq ($(call optbool,$(WITH_SETPROCTITLE)),)
|
||||
ifeq ($(shell uname -s | tr A-Z a-z),linux)
|
||||
_USTR_LIBS += -lbsd
|
||||
endif
|
||||
override _CFLAGS += -DWITH_SETPROCTITLE
|
||||
endif
|
||||
|
||||
|
||||
# =====
|
||||
all: $(_USTR) $(_DUMP)
|
||||
|
||||
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||
install -m755 $(_USTR) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
|
||||
install -m755 $(_DUMP) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
|
||||
|
||||
|
||||
install-strip: install
|
||||
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
|
||||
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
|
||||
|
||||
|
||||
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
|
||||
|
||||
|
||||
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
||||
|
||||
|
||||
$(_BUILD)/%.o: %.c
|
||||
$(info -- CC $<)
|
||||
@ mkdir -p $(dir $@) || true
|
||||
@ $(CC) $< -o $@ $(_CFLAGS)
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf $(_USTR) $(_DUMP) $(_BUILD)
|
||||
|
||||
|
||||
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||
-include $(_OBJS:%.o=%.d)
|
||||
@@ -128,12 +128,12 @@ int main(int argc, char *argv[]) {
|
||||
case _O_OUTPUT: OPT_SET(output_path, optarg);
|
||||
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
|
||||
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(log_level, LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
|
||||
case _O_DEBUG: OPT_SET(log_level, LOG_LEVEL_DEBUG);
|
||||
case _O_FORCE_LOG_COLORS: OPT_SET(log_colored, true);
|
||||
case _O_NO_LOG_COLORS: OPT_SET(log_colored, false);
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE);
|
||||
case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG);
|
||||
case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true);
|
||||
case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false);
|
||||
|
||||
case _O_HELP: _help(stdout); return 0;
|
||||
case _O_VERSION: puts(VERSION); return 0;
|
||||
@@ -202,7 +202,7 @@ static void _install_signal_handlers(void) {
|
||||
}
|
||||
|
||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_context_s *ctx) {
|
||||
frame_s *frame = frame_init("input");
|
||||
frame_s *frame = frame_init();
|
||||
memsink_s *sink = NULL;
|
||||
|
||||
if ((sink = memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
|
||||
@@ -287,7 +287,7 @@ static void _help(FILE *fp) {
|
||||
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
||||
SAY(" Enabling debugging messages can slow down the program.");
|
||||
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
|
||||
SAY(" Default: %d.\n", log_level);
|
||||
SAY(" Default: %d.\n", us_log_level);
|
||||
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
|
||||
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
|
||||
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef VERSION
|
||||
# define VERSION "3.18"
|
||||
#endif
|
||||
#define VERSION_MAJOR 3
|
||||
#define VERSION_MINOR 26
|
||||
|
||||
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
|
||||
#define VERSION MAKE_VERSION1(VERSION_MAJOR, VERSION_MINOR)
|
||||
|
||||
#define VERSION_U ((unsigned)(VERSION_MAJOR * 1000 + VERSION_MINOR))
|
||||
|
||||
@@ -23,17 +23,14 @@
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
frame_s *frame_init(const char *name) {
|
||||
frame_s *frame_init(void) {
|
||||
frame_s *frame;
|
||||
A_CALLOC(frame, 1);
|
||||
frame->name = name;
|
||||
frame->managed = true;
|
||||
frame_realloc_data(frame, 512 * 1024);
|
||||
return frame;
|
||||
}
|
||||
|
||||
void frame_destroy(frame_s *frame) {
|
||||
assert(frame->managed);
|
||||
if (frame->data) {
|
||||
free(frame->data);
|
||||
}
|
||||
@@ -41,68 +38,36 @@ void frame_destroy(frame_s *frame) {
|
||||
}
|
||||
|
||||
void frame_realloc_data(frame_s *frame, size_t size) {
|
||||
assert(frame->managed);
|
||||
if (frame->allocated < size) {
|
||||
LOG_DEBUG("Increasing frame buffer '%s': %zu -> %zu (+%zu)",
|
||||
frame->name, frame->allocated, size, size - frame->allocated);
|
||||
A_REALLOC(frame->data, size);
|
||||
frame->allocated = size;
|
||||
}
|
||||
}
|
||||
|
||||
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) {
|
||||
assert(frame->managed);
|
||||
frame_realloc_data(frame, size);
|
||||
memcpy(frame->data, data, size);
|
||||
frame->used = size;
|
||||
}
|
||||
|
||||
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size) {
|
||||
assert(frame->managed);
|
||||
size_t new_used = frame->used + size;
|
||||
frame_realloc_data(frame, new_used);
|
||||
memcpy(frame->data + frame->used, data, size);
|
||||
frame->used = new_used;
|
||||
}
|
||||
|
||||
#define COPY(_field) dest->_field = src->_field
|
||||
|
||||
void frame_copy(const frame_s *src, frame_s *dest) {
|
||||
assert(dest->managed);
|
||||
frame_set_data(dest, src->data, src->used);
|
||||
COPY(used);
|
||||
frame_copy_meta(src, dest);
|
||||
FRAME_COPY_META(src, dest);
|
||||
}
|
||||
|
||||
void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
||||
// Don't copy the name
|
||||
COPY(width);
|
||||
COPY(height);
|
||||
COPY(format);
|
||||
COPY(stride);
|
||||
COPY(online);
|
||||
COPY(key);
|
||||
COPY(grab_ts);
|
||||
COPY(encode_begin_ts);
|
||||
COPY(encode_end_ts);
|
||||
}
|
||||
|
||||
#undef COPY
|
||||
|
||||
bool frame_compare(const frame_s *a, const frame_s *b) {
|
||||
# define CMP(_field) (a->_field == b->_field)
|
||||
return (
|
||||
a->allocated && b->allocated
|
||||
&& CMP(used)
|
||||
&& CMP(width)
|
||||
&& CMP(height)
|
||||
&& CMP(format)
|
||||
&& CMP(stride)
|
||||
&& CMP(online)
|
||||
&& CMP(key)
|
||||
&& FRAME_COMPARE_META_USED_NOTS(a, b)
|
||||
&& !memcmp(a->data, b->data, b->used)
|
||||
);
|
||||
# undef CMP
|
||||
}
|
||||
|
||||
unsigned frame_get_padding(const frame_s *frame) {
|
||||
|
||||
@@ -32,12 +32,9 @@
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
|
||||
uint8_t *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
@@ -56,12 +53,37 @@ typedef struct {
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
|
||||
bool managed;
|
||||
} frame_s;
|
||||
|
||||
|
||||
frame_s *frame_init(const char *name);
|
||||
#define FRAME_COPY_META(_src, _dest) { \
|
||||
_dest->width = _src->width; \
|
||||
_dest->height = _src->height; \
|
||||
_dest->format = _src->format; \
|
||||
_dest->stride = _src->stride; \
|
||||
_dest->online = _src->online; \
|
||||
_dest->key = _src->key; \
|
||||
_dest->grab_ts = _src->grab_ts; \
|
||||
_dest->encode_begin_ts = _src->encode_begin_ts; \
|
||||
_dest->encode_end_ts = _src->encode_end_ts; \
|
||||
}
|
||||
|
||||
inline void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
||||
FRAME_COPY_META(src, dest);
|
||||
}
|
||||
|
||||
#define FRAME_COMPARE_META_USED_NOTS(_a, _b) ( \
|
||||
_a->used == _b->used \
|
||||
&& _a->width == _b->width \
|
||||
&& _a->height == _b->height \
|
||||
&& _a->format == _b->format \
|
||||
&& _a->stride == _b->stride \
|
||||
&& _a->online == _b->online \
|
||||
&& _a->key == _b->key \
|
||||
)
|
||||
|
||||
|
||||
frame_s *frame_init(void);
|
||||
void frame_destroy(frame_s *frame);
|
||||
|
||||
void frame_realloc_data(frame_s *frame, size_t size);
|
||||
@@ -69,7 +91,6 @@ void frame_set_data(frame_s *frame, const uint8_t *data, size_t size);
|
||||
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size);
|
||||
|
||||
void frame_copy(const frame_s *src, frame_s *dest);
|
||||
void frame_copy_meta(const frame_s *src, frame_s *dest);
|
||||
bool frame_compare(const frame_s *a, const frame_s *b);
|
||||
|
||||
unsigned frame_get_padding(const frame_s *frame);
|
||||
|
||||
71
src/libs/list.h
Normal file
71
src/libs/list.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 <assert.h>
|
||||
|
||||
|
||||
#define LIST_STRUCT(...) \
|
||||
__VA_ARGS__ *prev; \
|
||||
__VA_ARGS__ *next;
|
||||
|
||||
#define LIST_ITERATE(_first, _item, ...) { \
|
||||
for (__typeof__(_first) _item = _first; _item;) { \
|
||||
__typeof__(_first) _next = _item->next; \
|
||||
__VA_ARGS__ \
|
||||
_item = _next; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LIST_APPEND(_first, _item) { \
|
||||
if (_first == NULL) { \
|
||||
_first = _item; \
|
||||
} else { \
|
||||
__typeof__(_first) _last = _first; \
|
||||
for (; _last->next; _last = _last->next); \
|
||||
_item->prev = _last; \
|
||||
_last->next = _item; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LIST_APPEND_C(_first, _item, _count) { \
|
||||
LIST_APPEND(_first, _item); \
|
||||
++(_count); \
|
||||
}
|
||||
|
||||
#define LIST_REMOVE(_first, _item) { \
|
||||
if (_item->prev == NULL) { \
|
||||
_first = _item->next; \
|
||||
} else { \
|
||||
_item->prev->next = _item->next; \
|
||||
} \
|
||||
if (_item->next != NULL) { \
|
||||
_item->next->prev = _item->prev; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LIST_REMOVE_C(_first, _item, _count) { \
|
||||
LIST_REMOVE(_first, _item); \
|
||||
assert((_count) >= 1); \
|
||||
--(_count); \
|
||||
}
|
||||
@@ -23,8 +23,8 @@
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
enum log_level_t log_level;
|
||||
enum log_level_t us_log_level;
|
||||
|
||||
bool log_colored;
|
||||
bool us_log_colored;
|
||||
|
||||
pthread_mutex_t log_mutex;
|
||||
pthread_mutex_t us_log_mutex;
|
||||
|
||||
@@ -45,23 +45,23 @@ enum log_level_t {
|
||||
};
|
||||
|
||||
|
||||
extern enum log_level_t log_level;
|
||||
extern enum log_level_t us_log_level;
|
||||
|
||||
extern bool log_colored;
|
||||
extern bool us_log_colored;
|
||||
|
||||
extern pthread_mutex_t log_mutex;
|
||||
extern pthread_mutex_t us_log_mutex;
|
||||
|
||||
|
||||
#define LOGGING_INIT { \
|
||||
log_level = LOG_LEVEL_INFO; \
|
||||
log_colored = isatty(2); \
|
||||
A_MUTEX_INIT(&log_mutex); \
|
||||
us_log_level = LOG_LEVEL_INFO; \
|
||||
us_log_colored = isatty(2); \
|
||||
A_MUTEX_INIT(&us_log_mutex); \
|
||||
}
|
||||
|
||||
#define LOGGING_DESTROY A_MUTEX_DESTROY(&log_mutex)
|
||||
#define LOGGING_DESTROY A_MUTEX_DESTROY(&us_log_mutex)
|
||||
|
||||
#define LOGGING_LOCK A_MUTEX_LOCK(&log_mutex)
|
||||
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&log_mutex)
|
||||
#define LOGGING_LOCK A_MUTEX_LOCK(&us_log_mutex)
|
||||
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&us_log_mutex)
|
||||
|
||||
|
||||
#define COLOR_GRAY "\x1b[30;1m"
|
||||
@@ -84,7 +84,7 @@ extern pthread_mutex_t log_mutex;
|
||||
}
|
||||
|
||||
#define SEP_DEBUG(_ch) { \
|
||||
if (log_level >= LOG_LEVEL_DEBUG) { \
|
||||
if (us_log_level >= LOG_LEVEL_DEBUG) { \
|
||||
SEP_INFO(_ch); \
|
||||
} \
|
||||
}
|
||||
@@ -93,7 +93,7 @@ extern pthread_mutex_t log_mutex;
|
||||
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
|
||||
char _tname_buf[MAX_THREAD_NAME] = {0}; \
|
||||
thread_get_name(_tname_buf); \
|
||||
if (log_colored) { \
|
||||
if (us_log_colored) { \
|
||||
fprintf(stderr, COLOR_GRAY "-- " _label_color _label COLOR_GRAY \
|
||||
" [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
|
||||
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
|
||||
@@ -130,25 +130,25 @@ extern pthread_mutex_t log_mutex;
|
||||
}
|
||||
|
||||
#define LOG_PERF(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_PERF) { \
|
||||
if (us_log_level >= LOG_LEVEL_PERF) { \
|
||||
LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_PERF_FPS(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_PERF) { \
|
||||
if (us_log_level >= LOG_LEVEL_PERF) { \
|
||||
LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_VERBOSE(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
if (us_log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_VERBOSE_PERROR(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
if (us_log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
|
||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
||||
@@ -156,17 +156,7 @@ extern pthread_mutex_t log_mutex;
|
||||
}
|
||||
|
||||
#define LOG_DEBUG(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_DEBUG) { \
|
||||
if (us_log_level >= LOG_LEVEL_DEBUG) { \
|
||||
LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
INLINE char *errno_to_string(int error, char *buf, size_t size) {
|
||||
# if defined(__GLIBC__) && defined(_GNU_SOURCE)
|
||||
return strerror_r(error, buf, size);
|
||||
# else
|
||||
strerror_r(error, buf, size);
|
||||
return buf;
|
||||
# endif
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ memsink_s *memsink_init(
|
||||
sink->timeout = timeout;
|
||||
sink->fd = -1;
|
||||
sink->mem = MAP_FAILED;
|
||||
atomic_init(&sink->has_clients, false);
|
||||
|
||||
LOG_INFO("Using %s-sink: %s", name, obj);
|
||||
|
||||
@@ -54,14 +55,7 @@ memsink_s *memsink_init(
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((sink->mem = mmap(
|
||||
NULL,
|
||||
sizeof(memsink_shared_s),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
sink->fd,
|
||||
0
|
||||
)) == MAP_FAILED) {
|
||||
if ((sink->mem = memsink_shared_map(sink->fd)) == NULL) {
|
||||
LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
@@ -75,7 +69,7 @@ memsink_s *memsink_init(
|
||||
|
||||
void memsink_destroy(memsink_s *sink) {
|
||||
if (sink->mem != MAP_FAILED) {
|
||||
if (munmap(sink->mem, sizeof(memsink_shared_s)) < 0) {
|
||||
if (memsink_shared_unmap(sink->mem) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
|
||||
}
|
||||
}
|
||||
@@ -93,24 +87,22 @@ void memsink_destroy(memsink_s *sink) {
|
||||
}
|
||||
|
||||
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||
// Возвращает true, если если клиенты ИЛИ изменились метаданные
|
||||
// Возвращает true, если есть клиенты ИЛИ изменились метаданные
|
||||
|
||||
assert(sink->server);
|
||||
|
||||
if (flock(sink->fd, LOCK_EX | LOCK_NB) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
sink->has_clients = true;
|
||||
atomic_store(&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());
|
||||
atomic_store(&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
|
||||
bool retval = (atomic_load(&sink->has_clients) || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
@@ -133,24 +125,16 @@ int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
||||
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
|
||||
sink->last_id = get_now_id();
|
||||
sink->mem->id = sink->last_id;
|
||||
COPY(used);
|
||||
COPY(width);
|
||||
COPY(height);
|
||||
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_client_ts + sink->client_ttl > get_now_monotonic());
|
||||
|
||||
memcpy(sink->mem->data, frame->data, frame->used);
|
||||
sink->mem->used = frame->used;
|
||||
FRAME_COPY_META(frame, sink->mem);
|
||||
|
||||
sink->mem->magic = MEMSINK_MAGIC;
|
||||
sink->mem->version = MEMSINK_VERSION;
|
||||
# undef COPY
|
||||
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic()));
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
@@ -189,19 +173,9 @@ int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress u
|
||||
goto done;
|
||||
}
|
||||
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
|
||||
FRAME_COPY_META(sink->mem, frame);
|
||||
retval = 0;
|
||||
}
|
||||
sink->mem->last_client_ts = get_now_monotonic();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
@@ -51,7 +52,7 @@ typedef struct {
|
||||
int fd;
|
||||
memsink_shared_s *mem;
|
||||
uint64_t last_id;
|
||||
bool has_clients; // Only for server
|
||||
atomic_bool has_clients; // Only for server
|
||||
} memsink_s;
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
|
||||
#define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||
@@ -59,3 +60,25 @@ typedef struct {
|
||||
|
||||
uint8_t data[MEMSINK_MAX_DATA];
|
||||
} memsink_shared_s;
|
||||
|
||||
|
||||
INLINE memsink_shared_s *memsink_shared_map(int fd) {
|
||||
memsink_shared_s *mem = mmap(
|
||||
NULL,
|
||||
sizeof(memsink_shared_s),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
fd,
|
||||
0
|
||||
);
|
||||
if (mem == MAP_FAILED) {
|
||||
return NULL;
|
||||
}
|
||||
assert(mem != NULL);
|
||||
return mem;
|
||||
}
|
||||
|
||||
INLINE int memsink_shared_unmap(memsink_shared_s *mem) {
|
||||
assert(mem != NULL);
|
||||
return munmap(mem, sizeof(memsink_shared_s));
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <locale.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
@@ -35,17 +37,21 @@
|
||||
#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)))))
|
||||
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
|
||||
|
||||
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
|
||||
#define RN "\r\n"
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
|
||||
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
|
||||
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
|
||||
|
||||
INLINE char *bool_to_string(bool flag) {
|
||||
#define A_ASPRINTF(_dest, _fmt, ...) assert(asprintf(&(_dest), _fmt, ##__VA_ARGS__) >= 0)
|
||||
|
||||
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
|
||||
|
||||
|
||||
INLINE const char *bool_to_string(bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
@@ -141,3 +147,16 @@ INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
INLINE char *errno_to_string(int error, char *buf, size_t size) {
|
||||
assert(buf);
|
||||
assert(size > 0);
|
||||
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
|
||||
char *str = "!!! newlocale() error !!!";
|
||||
strncpy(buf, (locale ? strerror_l(error, locale) : str), size - 1);
|
||||
buf[size - 1] = '\0';
|
||||
if (locale) {
|
||||
freelocale(locale);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,6 @@ static void _jpeg_error_handler(j_common_ptr jpeg) {
|
||||
char msg[JMSG_LENGTH_MAX];
|
||||
|
||||
(*jpeg_error->mgr.format_message)(jpeg, msg);
|
||||
LOG_ERROR("Can't decompress %s JPEG: %s", jpeg_error->frame->name, msg);
|
||||
LOG_ERROR("Can't decompress JPEG: %s", msg);
|
||||
longjmp(jpeg_error->jmp, -1);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ frame_s *blank_frame_init(const char *path) {
|
||||
}
|
||||
|
||||
static frame_s *_init_internal(void) {
|
||||
frame_s *blank = frame_init("blank_internal");
|
||||
frame_s *blank = frame_init();
|
||||
frame_set_data(blank, BLANK_JPEG_DATA, BLANK_JPEG_DATA_SIZE);
|
||||
blank->width = BLANK_JPEG_WIDTH;
|
||||
blank->height = BLANK_JPEG_HEIGHT;
|
||||
@@ -55,7 +55,7 @@ static frame_s *_init_internal(void) {
|
||||
static frame_s *_init_external(const char *path) {
|
||||
FILE *fp = NULL;
|
||||
|
||||
frame_s *blank = frame_init("blank_external");
|
||||
frame_s *blank = frame_init();
|
||||
blank->format = V4L2_PIX_FMT_JPEG;
|
||||
|
||||
if ((fp = fopen(path, "rb")) == NULL) {
|
||||
@@ -83,7 +83,7 @@ static frame_s *_init_external(const char *path) {
|
||||
}
|
||||
# undef CHUNK_SIZE
|
||||
|
||||
frame_s *decoded = frame_init("blank_external_decoded");
|
||||
frame_s *decoded = frame_init();
|
||||
if (unjpeg(blank, decoded, false) < 0) {
|
||||
frame_destroy(decoded);
|
||||
goto error;
|
||||
|
||||
@@ -36,7 +36,7 @@ static const struct {
|
||||
};
|
||||
|
||||
|
||||
static void *_worker_job_init(worker_s *wr, void *v_enc);
|
||||
static void *_worker_job_init(void *v_enc);
|
||||
static void _worker_job_destroy(void *v_job);
|
||||
static bool _worker_run_job(worker_s *wr);
|
||||
|
||||
@@ -212,23 +212,17 @@ void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *
|
||||
A_MUTEX_UNLOCK(&ER(mutex));
|
||||
}
|
||||
|
||||
static void *_worker_job_init(worker_s *wr, void *v_enc) {
|
||||
static void *_worker_job_init(void *v_enc) {
|
||||
encoder_job_s *job;
|
||||
A_CALLOC(job, 1);
|
||||
job->enc = (encoder_s *)v_enc;
|
||||
|
||||
const size_t dest_role_len = strlen(wr->name) + 16;
|
||||
A_CALLOC(job->dest_role, dest_role_len);
|
||||
snprintf(job->dest_role, dest_role_len, "%s_dest", wr->name);
|
||||
job->dest = frame_init(job->dest_role);
|
||||
|
||||
job->dest = frame_init();
|
||||
return (void *)job;
|
||||
}
|
||||
|
||||
static void _worker_job_destroy(void *v_job) {
|
||||
encoder_job_s *job = (encoder_job_s *)v_job;
|
||||
frame_destroy(job->dest);
|
||||
free(job->dest_role);
|
||||
free(job);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "gpio.h"
|
||||
|
||||
|
||||
gpio_s gpio = {
|
||||
gpio_s us_gpio = {
|
||||
.path = "/dev/gpiochip0",
|
||||
.consumer_prefix = "ustreamer",
|
||||
|
||||
@@ -51,42 +51,42 @@ static void _gpio_output_destroy(gpio_output_s *output);
|
||||
|
||||
|
||||
void gpio_init(void) {
|
||||
assert(gpio.chip == NULL);
|
||||
assert(us_gpio.chip == NULL);
|
||||
if (
|
||||
gpio.prog_running.pin >= 0
|
||||
|| gpio.stream_online.pin >= 0
|
||||
|| gpio.has_http_clients.pin >= 0
|
||||
us_gpio.prog_running.pin >= 0
|
||||
|| us_gpio.stream_online.pin >= 0
|
||||
|| us_gpio.has_http_clients.pin >= 0
|
||||
) {
|
||||
A_MUTEX_INIT(&gpio.mutex);
|
||||
LOG_INFO("GPIO: Using chip device: %s", gpio.path);
|
||||
if ((gpio.chip = gpiod_chip_open(gpio.path)) != NULL) {
|
||||
_gpio_output_init(&gpio.prog_running);
|
||||
_gpio_output_init(&gpio.stream_online);
|
||||
_gpio_output_init(&gpio.has_http_clients);
|
||||
A_MUTEX_INIT(&us_gpio.mutex);
|
||||
LOG_INFO("GPIO: Using chip device: %s", us_gpio.path);
|
||||
if ((us_gpio.chip = gpiod_chip_open(us_gpio.path)) != NULL) {
|
||||
_gpio_output_init(&us_gpio.prog_running);
|
||||
_gpio_output_init(&us_gpio.stream_online);
|
||||
_gpio_output_init(&us_gpio.has_http_clients);
|
||||
} else {
|
||||
LOG_PERROR("GPIO: Can't initialize chip device %s", gpio.path);
|
||||
LOG_PERROR("GPIO: Can't initialize chip device %s", us_gpio.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_destroy(void) {
|
||||
_gpio_output_destroy(&gpio.prog_running);
|
||||
_gpio_output_destroy(&gpio.stream_online);
|
||||
_gpio_output_destroy(&gpio.has_http_clients);
|
||||
if (gpio.chip) {
|
||||
gpiod_chip_close(gpio.chip);
|
||||
gpio.chip = NULL;
|
||||
A_MUTEX_DESTROY(&gpio.mutex);
|
||||
_gpio_output_destroy(&us_gpio.prog_running);
|
||||
_gpio_output_destroy(&us_gpio.stream_online);
|
||||
_gpio_output_destroy(&us_gpio.has_http_clients);
|
||||
if (us_gpio.chip) {
|
||||
gpiod_chip_close(us_gpio.chip);
|
||||
us_gpio.chip = NULL;
|
||||
A_MUTEX_DESTROY(&us_gpio.mutex);
|
||||
}
|
||||
}
|
||||
|
||||
int gpio_inner_set(gpio_output_s *output, bool state) {
|
||||
int retval = 0;
|
||||
|
||||
assert(gpio.chip);
|
||||
assert(us_gpio.chip);
|
||||
assert(output->line);
|
||||
assert(output->state != state); // Must be checked in macro for the performance
|
||||
A_MUTEX_LOCK(&gpio.mutex);
|
||||
A_MUTEX_LOCK(&us_gpio.mutex);
|
||||
|
||||
if (gpiod_line_set_value(output->line, (int)state) < 0) { \
|
||||
LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
|
||||
@@ -94,19 +94,18 @@ int gpio_inner_set(gpio_output_s *output, bool state) {
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
A_MUTEX_UNLOCK(&gpio.mutex);
|
||||
A_MUTEX_UNLOCK(&us_gpio.mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _gpio_output_init(gpio_output_s *output) {
|
||||
assert(gpio.chip);
|
||||
assert(us_gpio.chip);
|
||||
assert(output->line == NULL);
|
||||
|
||||
A_CALLOC(output->consumer, strlen(gpio.consumer_prefix) + strlen(output->role) + 16);
|
||||
sprintf(output->consumer, "%s::%s", gpio.consumer_prefix, output->role);
|
||||
A_ASPRINTF(output->consumer, "%s::%s", us_gpio.consumer_prefix, output->role);
|
||||
|
||||
if (output->pin >= 0) {
|
||||
if ((output->line = gpiod_chip_get_line(gpio.chip, output->pin)) != NULL) {
|
||||
if ((output->line = gpiod_chip_get_line(us_gpio.chip, output->pin)) != NULL) {
|
||||
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
|
||||
LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
||||
_gpio_output_destroy(output);
|
||||
|
||||
@@ -56,7 +56,7 @@ typedef struct {
|
||||
} gpio_s;
|
||||
|
||||
|
||||
extern gpio_s gpio;
|
||||
extern gpio_s us_gpio;
|
||||
|
||||
|
||||
void gpio_init(void);
|
||||
@@ -73,15 +73,15 @@ int gpio_inner_set(gpio_output_s *output, bool state);
|
||||
}
|
||||
|
||||
INLINE void gpio_set_prog_running(bool state) {
|
||||
SET_STATE(gpio.prog_running, state);
|
||||
SET_STATE(us_gpio.prog_running, state);
|
||||
}
|
||||
|
||||
INLINE void gpio_set_stream_online(bool state) {
|
||||
SET_STATE(gpio.stream_online, state);
|
||||
SET_STATE(us_gpio.stream_online, state);
|
||||
}
|
||||
|
||||
INLINE void gpio_set_has_http_clients(bool state) {
|
||||
SET_STATE(gpio.has_http_clients, state);
|
||||
SET_STATE(us_gpio.has_http_clients, state);
|
||||
}
|
||||
|
||||
#undef SET_STATE
|
||||
|
||||
@@ -27,12 +27,14 @@ h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop)
|
||||
h264_stream_s *h264;
|
||||
A_CALLOC(h264, 1);
|
||||
h264->sink = sink;
|
||||
h264->tmp_src = frame_init("h264_tmp_src");
|
||||
h264->dest = frame_init("h264_dest");
|
||||
h264->tmp_src = frame_init();
|
||||
h264->dest = frame_init();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -49,7 +51,7 @@ static bool _expose_new_frame(server_s *server);
|
||||
server_s *server_init(stream_s *stream) {
|
||||
exposed_s *exposed;
|
||||
A_CALLOC(exposed, 1);
|
||||
exposed->frame = frame_init("http_exposed");
|
||||
exposed->frame = frame_init();
|
||||
|
||||
server_runtime_s *run;
|
||||
A_CALLOC(run, 1);
|
||||
@@ -91,12 +93,11 @@ void server_destroy(server_s *server) {
|
||||
libevent_global_shutdown();
|
||||
# endif
|
||||
|
||||
for (stream_client_s *client = RUN(stream_clients); client != NULL;) {
|
||||
stream_client_s *next = client->next;
|
||||
LIST_ITERATE(RUN(stream_clients), client, {
|
||||
free(client->key);
|
||||
free(client->hostport);
|
||||
free(client);
|
||||
client = next;
|
||||
}
|
||||
});
|
||||
|
||||
if (RUN(auth_token)) {
|
||||
free(RUN(auth_token));
|
||||
@@ -142,16 +143,14 @@ int server_listen(server_s *server) {
|
||||
evhttp_set_timeout(RUN(http), server->timeout);
|
||||
|
||||
if (server->user[0] != '\0') {
|
||||
char *raw_token;
|
||||
char *encoded_token = NULL;
|
||||
|
||||
A_CALLOC(raw_token, strlen(server->user) + strlen(server->passwd) + 16);
|
||||
sprintf(raw_token, "%s:%s", server->user, server->passwd);
|
||||
char *raw_token;
|
||||
A_ASPRINTF(raw_token, "%s:%s", server->user, server->passwd);
|
||||
base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
|
||||
free(raw_token);
|
||||
|
||||
A_CALLOC(RUN(auth_token), strlen(encoded_token) + 16);
|
||||
sprintf(RUN(auth_token), "Basic %s", encoded_token);
|
||||
A_ASPRINTF(RUN(auth_token), "Basic %s", encoded_token);
|
||||
free(encoded_token);
|
||||
|
||||
LOG_INFO("Using HTTP basic auth");
|
||||
@@ -343,6 +342,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
encoder_type_to_string(enc_type),
|
||||
enc_quality
|
||||
));
|
||||
|
||||
# ifdef WITH_OMX
|
||||
if (STREAM(run->h264)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
@@ -353,6 +353,32 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
));
|
||||
}
|
||||
# endif
|
||||
|
||||
if (
|
||||
STREAM(sink)
|
||||
# ifdef WITH_OMX
|
||||
|| STREAM(h264_sink)
|
||||
# endif
|
||||
) {
|
||||
assert(evbuffer_add_printf(buf, " \"sinks\": {"));
|
||||
if (STREAM(sink)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"\"jpeg\": {\"has_clients\": %s}",
|
||||
bool_to_string(atomic_load(&STREAM(sink->has_clients)))
|
||||
));
|
||||
}
|
||||
# ifdef WITH_OMX
|
||||
if (STREAM(h264_sink)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"%s\"h264\": {\"has_clients\": %s}",
|
||||
(STREAM(sink) ? ", " : ""),
|
||||
bool_to_string(atomic_load(&STREAM(h264_sink->has_clients)))
|
||||
));
|
||||
}
|
||||
# endif
|
||||
assert(evbuffer_add_printf(buf, "},"));
|
||||
}
|
||||
|
||||
assert(evbuffer_add_printf(buf,
|
||||
" \"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u},"
|
||||
@@ -366,7 +392,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
RUN(stream_clients_count)
|
||||
));
|
||||
|
||||
for (stream_client_s * client = RUN(stream_clients); client != NULL; client = client->next) {
|
||||
LIST_ITERATE(RUN(stream_clients), client, {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
|
||||
" \"dual_final_frames\": %s, \"zero_data\": %s}%s",
|
||||
@@ -378,7 +404,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
bool_to_string(client->zero_data),
|
||||
(client->next ? ", " : "")
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
assert(evbuffer_add_printf(buf, "}}}}"));
|
||||
|
||||
@@ -473,18 +499,10 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
# undef PARSE_PARAM
|
||||
evhttp_clear_headers(¶ms);
|
||||
|
||||
client->hostport = _http_get_client_hostport(request);
|
||||
client->id = get_now_id();
|
||||
|
||||
if (RUN(stream_clients) == NULL) {
|
||||
RUN(stream_clients) = client;
|
||||
} else {
|
||||
stream_client_s *last = RUN(stream_clients);
|
||||
|
||||
for (; last->next != NULL; last = last->next);
|
||||
client->prev = last;
|
||||
last->next = client;
|
||||
}
|
||||
RUN(stream_clients_count) += 1;
|
||||
LIST_APPEND_C(RUN(stream_clients), client, RUN(stream_clients_count));
|
||||
|
||||
if (RUN(stream_clients_count) == 1) {
|
||||
atomic_store(&VID(has_clients), true);
|
||||
@@ -493,23 +511,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=%" PRIx64 "; 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);
|
||||
@@ -523,7 +536,6 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
|
||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
||||
# define BOUNDARY "boundarydonotcross"
|
||||
# define RN "\r\n"
|
||||
|
||||
stream_client_s *client = (stream_client_s *)v_client;
|
||||
server_s *server = client->server;
|
||||
@@ -648,7 +660,6 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
bufferevent_enable(buf_event, EV_READ);
|
||||
|
||||
# undef ADD_ADVANCE_HEADERS
|
||||
# undef RN
|
||||
# undef BOUNDARY
|
||||
}
|
||||
|
||||
@@ -656,10 +667,7 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
stream_client_s *client = (stream_client_s *)v_client;
|
||||
server_s *server = client->server;
|
||||
|
||||
char *reason = bufferevent_my_format_reason(what);
|
||||
|
||||
assert(RUN(stream_clients_count) > 0);
|
||||
RUN(stream_clients_count) -= 1;
|
||||
LIST_REMOVE_C(RUN(stream_clients), client, RUN(stream_clients_count));
|
||||
|
||||
if (RUN(stream_clients_count) == 0) {
|
||||
atomic_store(&VID(has_clients), false);
|
||||
@@ -668,40 +676,26 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
# endif
|
||||
}
|
||||
|
||||
char *client_addr = "???";
|
||||
unsigned short client_port = 0;
|
||||
char *reason = bufferevent_my_format_reason(what);
|
||||
LOG_INFO("HTTP: Disconnected client: %s, id=%" PRIx64 ", %s; clients now: %u",
|
||||
client->hostport, client->id, reason, RUN(stream_clients_count));
|
||||
free(reason);
|
||||
|
||||
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=%" PRIx64 ", %s; clients now: %u",
|
||||
client_addr, client_port, client->id, reason, RUN(stream_clients_count));
|
||||
|
||||
if (conn) {
|
||||
evhttp_connection_free(conn);
|
||||
}
|
||||
|
||||
if (client->prev == NULL) {
|
||||
RUN(stream_clients) = client->next;
|
||||
} else {
|
||||
client->prev->next = client->next;
|
||||
}
|
||||
if (client->next != NULL) {
|
||||
client->next->prev = client->prev;
|
||||
}
|
||||
free(client->key);
|
||||
free(client->hostport);
|
||||
free(client);
|
||||
|
||||
free(reason);
|
||||
}
|
||||
|
||||
static void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated) {
|
||||
bool has_clients = false;
|
||||
bool queued = false;
|
||||
|
||||
for (stream_client_s *client = RUN(stream_clients); client != NULL; client = client->next) {
|
||||
LIST_ITERATE(RUN(stream_clients), client, {
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
||||
if (conn) {
|
||||
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
||||
@@ -732,7 +726,7 @@ static void _http_queue_send_stream(server_s *server, bool stream_updated, bool
|
||||
|
||||
has_clients = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (queued) {
|
||||
static unsigned queued_fps_accum = 0;
|
||||
@@ -834,3 +828,37 @@ 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;
|
||||
A_ASPRINTF(hostport, "[%s]:%u", addr, port);
|
||||
free(addr);
|
||||
return hostport;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#include "../../libs/process.h"
|
||||
#include "../../libs/frame.h"
|
||||
#include "../../libs/base64.h"
|
||||
#include "../../libs/list.h"
|
||||
#include "../data/index_html.h"
|
||||
#include "../encoder.h"
|
||||
#include "../stream.h"
|
||||
@@ -82,6 +83,7 @@ typedef struct stream_client_sx {
|
||||
bool dual_final_frames;
|
||||
bool zero_data;
|
||||
|
||||
char *hostport;
|
||||
uint64_t id;
|
||||
bool need_initial;
|
||||
bool need_first_frame;
|
||||
@@ -90,8 +92,7 @@ typedef struct stream_client_sx {
|
||||
unsigned fps_accum;
|
||||
long long fps_accum_second;
|
||||
|
||||
struct stream_client_sx *prev;
|
||||
struct stream_client_sx *next;
|
||||
LIST_STRUCT(struct stream_client_sx);
|
||||
} stream_client_s;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -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) + 16);
|
||||
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||
sprintf(path, "%s/%s", root_path, simplified_path);
|
||||
|
||||
struct stat st;
|
||||
|
||||
@@ -440,11 +440,11 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
case _O_GPIO_DEVICE: OPT_SET(gpio.path, optarg);
|
||||
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(gpio.consumer_prefix, optarg);
|
||||
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio.prog_running.pin, 0, 256, 0);
|
||||
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", gpio.stream_online.pin, 0, 256, 0);
|
||||
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", gpio.has_http_clients.pin, 0, 256, 0);
|
||||
case _O_GPIO_DEVICE: OPT_SET(us_gpio.path, optarg);
|
||||
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(us_gpio.consumer_prefix, optarg);
|
||||
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", us_gpio.prog_running.pin, 0, 256, 0);
|
||||
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", us_gpio.stream_online.pin, 0, 256, 0);
|
||||
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", us_gpio.has_http_clients.pin, 0, 256, 0);
|
||||
# endif
|
||||
|
||||
# ifdef HAS_PDEATHSIG
|
||||
@@ -459,12 +459,12 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
# endif
|
||||
case _O_NOTIFY_PARENT: OPT_SET(server->notify_parent, true);
|
||||
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(log_level, LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
|
||||
case _O_DEBUG: OPT_SET(log_level, LOG_LEVEL_DEBUG);
|
||||
case _O_FORCE_LOG_COLORS: OPT_SET(log_colored, true);
|
||||
case _O_NO_LOG_COLORS: OPT_SET(log_colored, false);
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE);
|
||||
case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG);
|
||||
case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true);
|
||||
case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false);
|
||||
|
||||
case _O_HELP: _help(stdout, dev, enc, stream, server); return 1;
|
||||
case _O_VERSION: puts(VERSION); return 1;
|
||||
@@ -679,8 +679,8 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
# ifdef WITH_GPIO
|
||||
SAY("GPIO options:");
|
||||
SAY("═════════════");
|
||||
SAY(" --gpio-device </dev/path> ───── Path to GPIO character device. Default: %s.\n", gpio.path);
|
||||
SAY(" --gpio-consumer-prefix <str> ── Consumer prefix for GPIO outputs. Default: %s.\n", gpio.consumer_prefix);
|
||||
SAY(" --gpio-device </dev/path> ───── Path to GPIO character device. Default: %s.\n", us_gpio.path);
|
||||
SAY(" --gpio-consumer-prefix <str> ── Consumer prefix for GPIO outputs. Default: %s.\n", us_gpio.consumer_prefix);
|
||||
SAY(" --gpio-prog-running <pin> ───── Set 1 on GPIO pin while uStreamer is running. Default: disabled.\n");
|
||||
SAY(" --gpio-stream-online <pin> ──── Set 1 while streaming. Default: disabled.\n");
|
||||
SAY(" --gpio-has-http-clients <pin> ─ Set 1 while stream has at least one client. Default: disabled.\n");
|
||||
@@ -703,7 +703,7 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
||||
SAY(" Enabling debugging messages can slow down the program.");
|
||||
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
|
||||
SAY(" Default: %d.\n", log_level);
|
||||
SAY(" Default: %d.\n", us_log_level);
|
||||
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
|
||||
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
|
||||
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");
|
||||
|
||||
@@ -52,7 +52,7 @@ stream_s *stream_init(device_s *dev, encoder_s *enc) {
|
||||
|
||||
video_s *video;
|
||||
A_CALLOC(video, 1);
|
||||
video->frame = frame_init("stream_video");
|
||||
video->frame = frame_init();
|
||||
atomic_init(&video->updated, false);
|
||||
A_MUTEX_INIT(&video->mutex);
|
||||
atomic_init(&video->has_clients, false);
|
||||
@@ -136,9 +136,9 @@ void stream_loop(stream_s *stream) {
|
||||
&& !atomic_load(&RUN(stop))
|
||||
&& !atomic_load(&RUN(video->has_clients))
|
||||
// has_clients синков НЕ обновляются в реальном времени
|
||||
&& (stream->sink == NULL || !stream->sink->has_clients)
|
||||
&& (stream->sink == NULL || !atomic_load(&stream->sink->has_clients))
|
||||
# ifdef WITH_OMX
|
||||
&& (RUN(h264) == NULL || /*RUN(h264->sink) == NULL ||*/ !RUN(h264->sink->has_clients))
|
||||
&& (RUN(h264) == NULL || /*RUN(h264->sink) == NULL ||*/ !atomic_load(&RUN(h264->sink->has_clients)))
|
||||
# endif
|
||||
) {
|
||||
usleep(100000);
|
||||
|
||||
@@ -28,9 +28,9 @@ static void *_worker_thread(void *v_worker);
|
||||
|
||||
workers_pool_s *workers_pool_init(
|
||||
const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval,
|
||||
void *(*job_init)(worker_s *wr, void *arg), void *job_init_arg,
|
||||
void (*job_destroy)(void *),
|
||||
bool (*run_job)(worker_s *)) {
|
||||
workers_pool_job_init_f job_init, void *job_init_arg,
|
||||
workers_pool_job_destroy_f job_destroy,
|
||||
workers_pool_run_job_f run_job) {
|
||||
|
||||
LOG_INFO("Creating pool %s with %u workers ...", name, n_workers);
|
||||
|
||||
@@ -49,21 +49,18 @@ workers_pool_s *workers_pool_init(
|
||||
A_MUTEX_INIT(&pool->free_workers_mutex);
|
||||
A_COND_INIT(&pool->free_workers_cond);
|
||||
|
||||
const size_t wr_name_len = strlen(wr_prefix) + 64;
|
||||
|
||||
for (unsigned number = 0; number < pool->n_workers; ++number) {
|
||||
# define WR(_next) pool->workers[number]._next
|
||||
|
||||
WR(number) = number;
|
||||
A_CALLOC(WR(name), wr_name_len);
|
||||
snprintf(WR(name), wr_name_len, "%s-%u", wr_prefix, number);
|
||||
A_ASPRINTF(WR(name), "%s-%u", wr_prefix, number);
|
||||
|
||||
A_MUTEX_INIT(&WR(has_job_mutex));
|
||||
atomic_init(&WR(has_job), false);
|
||||
A_COND_INIT(&WR(has_job_cond));
|
||||
|
||||
WR(pool) = pool;
|
||||
WR(job) = job_init(&pool->workers[number], job_init_arg);
|
||||
WR(job) = job_init(job_init_arg);
|
||||
|
||||
A_THREAD_CREATE(&WR(tid), _worker_thread, (void *)&(pool->workers[number]));
|
||||
pool->free_workers += 1;
|
||||
|
||||
@@ -55,12 +55,16 @@ typedef struct worker_sx {
|
||||
struct workers_pool_sx *pool;
|
||||
} worker_s;
|
||||
|
||||
typedef void *(*workers_pool_job_init_f)(void *arg);
|
||||
typedef void (*workers_pool_job_destroy_f)(void *job);
|
||||
typedef bool (*workers_pool_run_job_f)(worker_s *wr);
|
||||
|
||||
typedef struct workers_pool_sx {
|
||||
const char *name;
|
||||
long double desired_interval;
|
||||
|
||||
bool (*run_job)(worker_s *wr);
|
||||
void (*job_destroy)(void *job);
|
||||
workers_pool_job_destroy_f job_destroy;
|
||||
workers_pool_run_job_f run_job;
|
||||
|
||||
unsigned n_workers;
|
||||
worker_s *workers;
|
||||
@@ -79,9 +83,9 @@ typedef struct workers_pool_sx {
|
||||
|
||||
workers_pool_s *workers_pool_init(
|
||||
const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval,
|
||||
void *(*job_init)(worker_s *wr, void *arg), void *job_init_arg,
|
||||
void (*job_destroy)(void *job),
|
||||
bool (*run_job)(worker_s *));
|
||||
workers_pool_job_init_f job_init, void *job_init_arg,
|
||||
workers_pool_job_destroy_f job_destroy,
|
||||
workers_pool_run_job_f run_job);
|
||||
|
||||
void workers_pool_destroy(workers_pool_s *pool);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user