mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 04:06:30 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
bad05a6827 | ||
|
|
89d3ed98c7 | ||
|
|
1187ace2a2 | ||
|
|
9704fb054c | ||
|
|
b10148a438 |
@@ -1,14 +1,22 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 3.17
|
current_version = 3.24
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|
||||||
[bumpversion:file:src/libs/config.h]
|
[bumpversion:file:src/libs/config.h]
|
||||||
search = VERSION "{current_version}"
|
parse = (?P<major>\d+)
|
||||||
replace = VERSION "{new_version}"
|
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]
|
[bumpversion:file:python/setup.py]
|
||||||
search = version="{current_version}"
|
search = version="{current_version}"
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -5,11 +5,12 @@
|
|||||||
/pkg/arch/v*.tar.gz
|
/pkg/arch/v*.tar.gz
|
||||||
/pkg/arch/ustreamer-*.pkg.tar.xz
|
/pkg/arch/ustreamer-*.pkg.tar.xz
|
||||||
/pkg/arch/ustreamer-*.pkg.tar.zst
|
/pkg/arch/ustreamer-*.pkg.tar.zst
|
||||||
/build/
|
/src/build/
|
||||||
|
/src/*.bin
|
||||||
/python/build/
|
/python/build/
|
||||||
/config.mk
|
|
||||||
/vgcore.*
|
|
||||||
/ustreamer
|
/ustreamer
|
||||||
/ustreamer-dump
|
/ustreamer-dump
|
||||||
/*.so
|
/config.mk
|
||||||
|
/vgcore.*
|
||||||
/*.sock
|
/*.sock
|
||||||
|
/*.so
|
||||||
|
|||||||
148
Makefile
148
Makefile
@@ -1,7 +1,5 @@
|
|||||||
-include config.mk
|
-include config.mk
|
||||||
|
|
||||||
USTR ?= ustreamer
|
|
||||||
DUMP ?= ustreamer-dump
|
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
MANPREFIX ?= $(PREFIX)/share/man
|
MANPREFIX ?= $(PREFIX)/share/man
|
||||||
@@ -14,91 +12,52 @@ LDFLAGS ?=
|
|||||||
RPI_VC_HEADERS ?= /opt/vc/include
|
RPI_VC_HEADERS ?= /opt/vc/include
|
||||||
RPI_VC_LIBS ?= /opt/vc/lib
|
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 -luuid
|
|
||||||
_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
|
define optbool
|
||||||
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||||
endef
|
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
|
all:
|
||||||
|
+ make apps
|
||||||
|
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
||||||
|
+ make python
|
||||||
|
else
|
||||||
|
@ true
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
apps:
|
||||||
|
make -C src
|
||||||
|
@ ln -sf src/ustreamer.bin ustreamer
|
||||||
|
@ ln -sf src/ustreamer-dump.bin ustreamer-dump
|
||||||
|
|
||||||
|
|
||||||
|
python:
|
||||||
|
make -C python
|
||||||
|
@ ln -sf python/build/lib.*/*.so .
|
||||||
|
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
mkdir -p $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(MANPREFIX)/man1
|
make -C src install
|
||||||
install -m755 $(USTR) $(DESTDIR)$(PREFIX)/bin/$(USTR)
|
|
||||||
install -m755 $(DUMP) $(DESTDIR)$(PREFIX)/bin/$(DUMP)
|
|
||||||
install -m644 man/$(USTR).1 $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
|
|
||||||
install -m644 man/$(DUMP).1 $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
|
|
||||||
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
|
|
||||||
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
|
|
||||||
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
||||||
cd python && $(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
|
make -C python install
|
||||||
endif
|
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
|
install-strip: install
|
||||||
strip $(DESTDIR)$(PREFIX)/bin/$(USTR)
|
make -C src install-strip
|
||||||
strip $(DESTDIR)$(PREFIX)/bin/$(DUMP)
|
|
||||||
|
|
||||||
|
|
||||||
regen:
|
regen:
|
||||||
@@ -106,41 +65,6 @@ regen:
|
|||||||
tools/make-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX
|
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)
|
|
||||||
|
|
||||||
|
|
||||||
python:
|
|
||||||
ifneq ($(call optbool,$(WITH_PYTHON)),)
|
|
||||||
$(info == PY_BUILD ustreamer-*.so)
|
|
||||||
@ cd python && $(PY) setup.py build
|
|
||||||
@ ln -sf python/build/lib.*/*.so .
|
|
||||||
else
|
|
||||||
@ true
|
|
||||||
endif
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
make clean
|
make clean
|
||||||
make tox
|
make tox
|
||||||
@@ -154,7 +78,7 @@ tox: linters
|
|||||||
time docker run --rm \
|
time docker run --rm \
|
||||||
--volume `pwd`:/src:ro \
|
--volume `pwd`:/src:ro \
|
||||||
--volume `pwd`/linters:/src/linters:rw \
|
--volume `pwd`/linters:/src/linters:rw \
|
||||||
-t $(LINTERS_IMAGE) bash -c " \
|
-t $(_LINTERS_IMAGE) bash -c " \
|
||||||
cd /src \
|
cd /src \
|
||||||
&& tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \
|
&& tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \
|
||||||
"
|
"
|
||||||
@@ -164,7 +88,7 @@ linters:
|
|||||||
docker build \
|
docker build \
|
||||||
$(if $(call optbool,$(NC)),--no-cache,) \
|
$(if $(call optbool,$(NC)),--no-cache,) \
|
||||||
--rm \
|
--rm \
|
||||||
--tag $(LINTERS_IMAGE) \
|
--tag $(_LINTERS_IMAGE) \
|
||||||
-f linters/Dockerfile linters
|
-f linters/Dockerfile linters
|
||||||
|
|
||||||
|
|
||||||
@@ -180,14 +104,14 @@ push:
|
|||||||
clean-all: linters clean
|
clean-all: linters clean
|
||||||
- docker run --rm \
|
- docker run --rm \
|
||||||
--volume `pwd`:/src \
|
--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:
|
clean:
|
||||||
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
|
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
|
.PHONY: python linters
|
||||||
|
|
||||||
|
|
||||||
_OBJS = $(_USTR_SRCS:%.c=$(BUILD)/%.o) $(_DUMP_SRCS:%.c=$(BUILD)/%.o)
|
|
||||||
-include $(_OBJS:%.o=%.d)
|
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -11,17 +11,17 @@
|
|||||||
|
|
||||||
| **Feature** | **µStreamer** | **mjpg-streamer** |
|
| **Feature** | **µStreamer** | **mjpg-streamer** |
|
||||||
|----------|---------------|-------------------|
|
|----------|---------------|-------------------|
|
||||||
| Multithreaded JPEG encoding |  Yes |  No |
|
| Multithreaded JPEG encoding | ✔ | ✘ |
|
||||||
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi |  Yes |  No |
|
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ✔ | ✘ |
|
||||||
| Behavior when the device<br>is disconnected while streaming |  Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected |  Stops the streaming <sup>1</sup> |
|
| Behavior when the device<br>is disconnected while streaming | ✔ Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ✘ Stops the streaming <sup>1</sup> |
|
||||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal |  Yes |  Partially yes <sup>1</sup> |
|
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ✔ | ☹ Partially yes <sup>1</sup> |
|
||||||
| Option to skip frames when streaming<br>static images by HTTP to save the traffic |  Yes <sup>2</sup> |  No |
|
| Option to skip frames when streaming<br>static images by HTTP to save the traffic | ✔ <sup>2</sup> | ✘ |
|
||||||
| Streaming via UNIX domain socket |  Yes |  No |
|
| Streaming via UNIX domain socket | ✔ | ✘ |
|
||||||
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters |  Yes |  No |
|
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters | ✔ | ✘ |
|
||||||
| Option to serve files<br>with a built-in HTTP server |  Yes |  Regular files only |
|
| Option to serve files<br>with a built-in HTTP server | ✔ | ☹ Regular files only |
|
||||||
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) |  Yes |  No |
|
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
|
||||||
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP |  No |  Yes |
|
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ✘ | ✔ |
|
||||||
| Compatibility with mjpg-streamer's API |  Yes | :) |
|
| Compatibility with mjpg-streamer's API | ✔ | :) |
|
||||||
|
|
||||||
Footnotes:
|
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.
|
* ```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.
|
||||||
@@ -34,12 +34,11 @@ If you're going to live-stream from your backyard webcam and need to control it,
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
# Building
|
# Building
|
||||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` and ```libbsd``` (only for Linux).
|
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
|
||||||
|
|
||||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
* 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`.
|
* 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 uuid-dev libbsd-dev`.
|
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
||||||
* 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```.
|
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```.
|
||||||
|
|
||||||
@@ -93,6 +92,18 @@ $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
|||||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||||
```
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
# Tips & tricks for v4l2
|
||||||
|
v4l2 utilities provide the tools to manage USB webcam setting and information. Scripts can be use to make adjustments and run manually or with cron. Running in cron for example to change the exposure settings at certain times of day. The package is available in all Linux distributions and is usually called `v4l-utils`.
|
||||||
|
|
||||||
|
* List of available video devices: `v4l2-ctl --list-devices`.
|
||||||
|
* List available control settings: `v4l2-ctl -d /dev/video0 --list-ctrls`.
|
||||||
|
* List available video formats: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
|
||||||
|
* Read the current setting: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
|
||||||
|
* Change the setting value: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
|
||||||
|
|
||||||
|
[Here](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/) you can find more examples. Documentation is available in [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# See also
|
# See also
|
||||||
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
|
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
|
||||||
|
|||||||
41
README.ru.md
41
README.ru.md
@@ -11,17 +11,17 @@
|
|||||||
|
|
||||||
| **Фича** | **µStreamer** | **mjpg-streamer** |
|
| **Фича** | **µStreamer** | **mjpg-streamer** |
|
||||||
|----------|---------------|-------------------|
|
|----------|---------------|-------------------|
|
||||||
| Многопоточное кодирование JPEG |  Есть |  Нет |
|
| Многопоточное кодирование JPEG | ✔ | ✘ |
|
||||||
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi |  Есть |  Нет |
|
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ✔ | ✘ |
|
||||||
| Поведение при физическом отключении<br>устройства от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова |  Прерывает трансляцию <sup>1</sup> |
|
| Поведение при физическом отключении<br>устройства от сервера во время работы | ✔ Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ✘ Прерывает трансляцию <sup>1</sup> |
|
||||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) |  Есть |  Условно есть <sup>1</sup> |
|
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ✔ | ☹ Условно есть <sup>1</sup> |
|
||||||
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ✔ <sup>2</sup> | ✘ |
|
||||||
| Стрим через UNIX domain socket |  Есть |  Нет |
|
| Стрим через UNIX domain socket | ✔ | ✘ |
|
||||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ✔ | ✘ |
|
||||||
| Возможность сервить файлы встроенным<br>HTTP-сервером |  Есть |  Нет каталогов |
|
| Возможность сервить файлы встроенным<br>HTTP-сервером | ✔ | ☹ Нет каталогов |
|
||||||
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) |  Есть |  Нет |
|
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
|
||||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ✘ | ✔ |
|
||||||
| Совместимость с API mjpg-streamer'а |  Есть | :) |
|
| Совместимость с API mjpg-streamer'а | ✔ | :) |
|
||||||
|
|
||||||
Сносочки:
|
Сносочки:
|
||||||
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
||||||
@@ -34,12 +34,11 @@
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
# Сборка
|
# Сборка
|
||||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux).
|
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libbsd``` (только для Linux).
|
||||||
|
|
||||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
* 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`.
|
* 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 uuid-dev libbsd-dev`.
|
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
||||||
* 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```.
|
На 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```.
|
||||||
|
|
||||||
@@ -93,6 +92,18 @@ $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
|||||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||||
```
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
# Утилиты V4L2
|
||||||
|
V4L2 предоставляет ряд официальных утилит для управления USB-вебкамерами и получения информации об устройствах. С их помощью можно писать всякие настроечные скрипты и запускать их по крону, если, например, вам требуется изменять настройки экспозиции в зависимости от времени суток. Пакет с этими утилитами доступен на всех дистрибутивах Linux и обычно называется `v4l-utils`.
|
||||||
|
|
||||||
|
* Вывести список видеоустройств: `v4l2-ctl --list-devices`.
|
||||||
|
* Вывести список доступных контролов устройства: `v4l2-ctl -d /dev/video0 --list-ctrls`.
|
||||||
|
* Вывести список доступных форматов видео: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
|
||||||
|
* Показать текущее значение контрола: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
|
||||||
|
* Изменить значение контрола: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
|
||||||
|
|
||||||
|
Больше примеров вы можете найти [здесь](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/), а документацию в [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Смотрите также
|
# Смотрите также
|
||||||
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
|
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
|
||||||
|
|||||||
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]
|
[tox]
|
||||||
envlist = cppcheck-src, cppcheck-python, flake8, pylint, mypy, vulture, htmlhint
|
envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
|
||||||
skipsdist = true
|
skipsdist = true
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython = python3.9
|
basepython = python3.9
|
||||||
changedir = /src
|
changedir = /src
|
||||||
|
|
||||||
[testenv:cppcheck-src]
|
[testenv:cppcheck]
|
||||||
whitelist_externals = cppcheck
|
whitelist_externals = cppcheck
|
||||||
commands = cppcheck \
|
commands = cppcheck \
|
||||||
--force \
|
--force \
|
||||||
@@ -18,25 +18,8 @@ commands = cppcheck \
|
|||||||
--suppress=variableScope \
|
--suppress=variableScope \
|
||||||
--inline-suppr \
|
--inline-suppr \
|
||||||
--library=python \
|
--library=python \
|
||||||
-DCHAR_BIT=8 \
|
--include=linters/cppcheck.h \
|
||||||
-DWITH_OMX \
|
src python/ustreamer.c janus/rtp.h janus/rtp.c janus/plugin.c
|
||||||
-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
|
|
||||||
|
|
||||||
[testenv:flake8]
|
[testenv:flake8]
|
||||||
whitelist_externals = bash
|
whitelist_externals = bash
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" Manpage for ustreamer-dump.
|
.\" Manpage for ustreamer-dump.
|
||||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||||
.TH USTREAMER-DUMP 1 "version 3.17" "January 2021"
|
.TH USTREAMER-DUMP 1 "version 3.24" "January 2021"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" Manpage for ustreamer.
|
.\" Manpage for ustreamer.
|
||||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||||
.TH USTREAMER 1 "version 3.17" "November 2020"
|
.TH USTREAMER 1 "version 3.24" "November 2020"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer \- stream MJPG video from any V4L2 device to the network
|
ustreamer \- stream MJPG video from any V4L2 device to the network
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=3.17
|
pkgver=3.24
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||||
url="https://github.com/pikvm/ustreamer"
|
url="https://github.com/pikvm/ustreamer"
|
||||||
license=(GPL)
|
license=(GPL)
|
||||||
arch=(i686 x86_64 armv6h armv7h aarch64)
|
arch=(i686 x86_64 armv6h armv7h aarch64)
|
||||||
depends=(libjpeg libevent libutil-linux libbsd libgpiod)
|
depends=(libjpeg libevent libbsd libgpiod)
|
||||||
makedepends=(gcc make)
|
makedepends=(gcc make)
|
||||||
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
||||||
md5sums=(SKIP)
|
md5sums=(SKIP)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ RUN apt-get update \
|
|||||||
libevent-2.1 \
|
libevent-2.1 \
|
||||||
libevent-pthreads-2.1-6 \
|
libevent-pthreads-2.1-6 \
|
||||||
libjpeg8 \
|
libjpeg8 \
|
||||||
uuid \
|
|
||||||
libbsd0 \
|
libbsd0 \
|
||||||
libgpiod2 \
|
libgpiod2 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ RUN apt-get update \
|
|||||||
libevent-2.1 \
|
libevent-2.1 \
|
||||||
libevent-pthreads-2.1-6 \
|
libevent-pthreads-2.1-6 \
|
||||||
libjpeg8 \
|
libjpeg8 \
|
||||||
uuid \
|
|
||||||
libbsd0 \
|
libbsd0 \
|
||||||
libgpiod2 \
|
libgpiod2 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ RUN apt-get update \
|
|||||||
git \
|
git \
|
||||||
libevent-dev \
|
libevent-dev \
|
||||||
libjpeg62-turbo-dev \
|
libjpeg62-turbo-dev \
|
||||||
uuid-dev \
|
|
||||||
libbsd-dev \
|
libbsd-dev \
|
||||||
libgpiod-dev \
|
libgpiod-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
@@ -25,7 +24,6 @@ RUN apt-get update \
|
|||||||
libevent-2.1 \
|
libevent-2.1 \
|
||||||
libevent-pthreads-2.1-6 \
|
libevent-pthreads-2.1-6 \
|
||||||
libjpeg62-turbo \
|
libjpeg62-turbo \
|
||||||
uuid \
|
|
||||||
libbsd0 \
|
libbsd0 \
|
||||||
libgpiod2 \
|
libgpiod2 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ IUSE=""
|
|||||||
DEPEND="
|
DEPEND="
|
||||||
>=dev-libs/libevent-2.1.8
|
>=dev-libs/libevent-2.1.8
|
||||||
>=media-libs/libjpeg-turbo-1.5.3
|
>=media-libs/libjpeg-turbo-1.5.3
|
||||||
>=sys-apps/util-linux-2.33
|
|
||||||
>=dev-libs/libbsd-0.9.1
|
>=dev-libs/libbsd-0.9.1
|
||||||
"
|
"
|
||||||
RDEPEND="${DEPEND}"
|
RDEPEND="${DEPEND}"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ustreamer
|
PKG_NAME:=ustreamer
|
||||||
PKG_VERSION:=3.17
|
PKG_VERSION:=3.24
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ define Package/ustreamer
|
|||||||
SECTION:=multimedia
|
SECTION:=multimedia
|
||||||
CATEGORY:=Multimedia
|
CATEGORY:=Multimedia
|
||||||
TITLE:=uStreamer
|
TITLE:=uStreamer
|
||||||
DEPENDS:=+libpthread +libjpeg +libv4l +libuuid +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
|
DEPENDS:=+libpthread +libjpeg +libv4l +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
|
||||||
URL:=https://github.com/pikvm/ustreamer
|
URL:=https://github.com/pikvm/ustreamer
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
|||||||
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
python/frame.c
Symbolic link
1
python/frame.c
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../src/libs/frame.c
|
||||||
1
python/frame.h
Symbolic link
1
python/frame.h
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../src/libs/frame.h
|
||||||
1
python/memsinksh.h
Symbolic link
1
python/memsinksh.h
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../src/libs/memsinksh.h
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
from distutils.core import Extension
|
from distutils.core import Extension
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
@@ -6,7 +8,7 @@ from distutils.core import setup
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
setup(
|
setup(
|
||||||
name="ustreamer",
|
name="ustreamer",
|
||||||
version="3.17",
|
version="3.24",
|
||||||
description="uStreamer tools",
|
description="uStreamer tools",
|
||||||
author="Maxim Devaev",
|
author="Maxim Devaev",
|
||||||
author_email="mdevaev@gmail.com",
|
author_email="mdevaev@gmail.com",
|
||||||
@@ -16,11 +18,8 @@ if __name__ == "__main__":
|
|||||||
"ustreamer",
|
"ustreamer",
|
||||||
libraries=["rt", "m", "pthread"],
|
libraries=["rt", "m", "pthread"],
|
||||||
undef_macros=["NDEBUG"],
|
undef_macros=["NDEBUG"],
|
||||||
sources=["ustreamer.c"],
|
sources=[name for name in os.listdir(".") if name.endswith(".c")],
|
||||||
depends=[
|
depends=[name for name in os.listdir(".") if name.endswith(".h")],
|
||||||
"../src/libs/tools.h",
|
|
||||||
"../src/libs/memsinksh.h",
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
1
python/tools.h
Symbolic link
1
python/tools.h
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../src/libs/tools.h
|
||||||
@@ -13,31 +13,11 @@
|
|||||||
|
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
|
|
||||||
#include "../src/libs/tools.h" // Just a header without C-sources
|
#include "tools.h"
|
||||||
#include "../src/libs/memsinksh.h" // No sources again
|
#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 {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
|
|
||||||
@@ -49,34 +29,28 @@ typedef struct {
|
|||||||
int fd;
|
int fd;
|
||||||
memsink_shared_s *mem;
|
memsink_shared_s *mem;
|
||||||
|
|
||||||
tmp_frame_s *tmp_frame;
|
uint64_t frame_id;
|
||||||
PyObject *dict_frame; // PyDict
|
long double frame_ts;
|
||||||
|
frame_s *frame;
|
||||||
} MemsinkObject;
|
} MemsinkObject;
|
||||||
|
|
||||||
|
|
||||||
#define MEM(_next) self->mem->_next
|
#define MEM(_next) self->mem->_next
|
||||||
#define TMP(_next) self->tmp_frame->_next
|
#define FRAME(_next) self->frame->_next
|
||||||
|
|
||||||
|
|
||||||
static void MemsinkObject_destroy_internals(MemsinkObject *self) {
|
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) {
|
if (self->mem != NULL) {
|
||||||
munmap(self->mem, sizeof(memsink_shared_s));
|
memsink_shared_unmap(self->mem);
|
||||||
self->mem = NULL;
|
self->mem = NULL;
|
||||||
}
|
}
|
||||||
if (self->fd > 0) {
|
if (self->fd > 0) {
|
||||||
close(self->fd);
|
close(self->fd);
|
||||||
self->fd = -1;
|
self->fd = -1;
|
||||||
}
|
}
|
||||||
if (self->tmp_frame) {
|
if (self->frame) {
|
||||||
if (TMP(data)) {
|
frame_destroy(self->frame);
|
||||||
free(TMP(data));
|
self->frame = NULL;
|
||||||
}
|
|
||||||
free(self->tmp_frame);
|
|
||||||
self->tmp_frame = NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,33 +78,15 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
|
|||||||
|
|
||||||
# undef SET_DOUBLE
|
# undef SET_DOUBLE
|
||||||
|
|
||||||
A_CALLOC(self->tmp_frame, 1);
|
self->frame = frame_init();
|
||||||
TMP(allocated) = 512 * 1024;
|
|
||||||
A_REALLOC(TMP(data), TMP(allocated));
|
|
||||||
|
|
||||||
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
|
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
|
||||||
PyErr_SetFromErrno(PyExc_OSError);
|
PyErr_SetFromErrno(PyExc_OSError);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((self->mem = mmap(
|
if ((self->mem = memsink_shared_map(self->fd)) == NULL) {
|
||||||
NULL,
|
|
||||||
sizeof(memsink_shared_s),
|
|
||||||
PROT_READ | PROT_WRITE,
|
|
||||||
MAP_SHARED,
|
|
||||||
self->fd,
|
|
||||||
0
|
|
||||||
)) == MAP_FAILED) {
|
|
||||||
PyErr_SetFromErrno(PyExc_OSError);
|
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;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,24 +142,16 @@ static int wait_frame(MemsinkObject *self) {
|
|||||||
RETURN_OS_ERROR;
|
RETURN_OS_ERROR;
|
||||||
|
|
||||||
} else if (retval == 0) {
|
} 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) {
|
if (self->drop_same_frames > 0) {
|
||||||
# define CMP(_field) (TMP(_field) == MEM(_field))
|
|
||||||
if (
|
if (
|
||||||
CMP(used)
|
FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
|
||||||
&& CMP(width)
|
&& (self->frame_ts + self->drop_same_frames > now)
|
||||||
&& CMP(height)
|
&& !memcmp(FRAME(data), MEM(data), MEM(used))
|
||||||
&& 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);
|
self->frame_id = MEM(id);
|
||||||
goto drop;
|
goto drop;
|
||||||
}
|
}
|
||||||
# undef CMP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_BLOCK_THREADS
|
Py_BLOCK_THREADS
|
||||||
@@ -245,49 +193,33 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
|
|||||||
default: return NULL;
|
default: return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
# define COPY(_field) TMP(_field) = MEM(_field)
|
frame_set_data(self->frame, MEM(data), MEM(used));
|
||||||
COPY(width);
|
FRAME_COPY_META(self->mem, self->frame);
|
||||||
COPY(height);
|
self->frame_id = MEM(id);
|
||||||
COPY(format);
|
self->frame_ts = get_now_monotonic();
|
||||||
COPY(stride);
|
MEM(last_client_ts) = self->frame_ts;
|
||||||
COPY(online);
|
|
||||||
COPY(key);
|
|
||||||
COPY(grab_ts);
|
|
||||||
COPY(encode_begin_ts);
|
|
||||||
COPY(encode_end_ts);
|
|
||||||
COPY(used);
|
|
||||||
# undef COPY
|
|
||||||
|
|
||||||
if (TMP(allocated) < MEM(used)) {
|
|
||||||
size_t size = MEM(used) + (512 * 1024);
|
|
||||||
A_REALLOC(TMP(data), size);
|
|
||||||
TMP(allocated) = size;
|
|
||||||
}
|
|
||||||
memcpy(TMP(data), MEM(data), MEM(used));
|
|
||||||
TMP(used) = MEM(used);
|
|
||||||
|
|
||||||
TMP(id) = MEM(id);
|
|
||||||
TMP(ts) = get_now_monotonic();
|
|
||||||
MEM(last_client_ts) = TMP(ts);
|
|
||||||
|
|
||||||
if (flock(self->fd, LOCK_UN) < 0) {
|
if (flock(self->fd, LOCK_UN) < 0) {
|
||||||
return PyErr_SetFromErrno(PyExc_OSError);
|
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) { \
|
# define SET_VALUE(_key, _maker) { \
|
||||||
PyObject *_tmp = _maker; \
|
PyObject *_tmp = _maker; \
|
||||||
if (_tmp == NULL) { \
|
if (_tmp == NULL) { \
|
||||||
return NULL; \
|
return NULL; \
|
||||||
} \
|
} \
|
||||||
if (PyDict_SetItemString(self->dict_frame, _key, _tmp) < 0) { \
|
if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \
|
||||||
Py_DECREF(_tmp); \
|
Py_DECREF(_tmp); \
|
||||||
return NULL; \
|
return NULL; \
|
||||||
} \
|
} \
|
||||||
Py_DECREF(_tmp); \
|
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(width, Long, Long);
|
||||||
SET_NUMBER(height, 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(grab_ts, Double, Float);
|
||||||
SET_NUMBER(encode_begin_ts, Double, Float);
|
SET_NUMBER(encode_begin_ts, Double, Float);
|
||||||
SET_NUMBER(encode_end_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_NUMBER
|
||||||
# undef SET_VALUE
|
# undef SET_VALUE
|
||||||
|
|
||||||
Py_INCREF(self->dict_frame);
|
return dict_frame;
|
||||||
return self->dict_frame;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
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;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef TMP
|
#undef FRAME
|
||||||
#undef MEM
|
#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: OPT_SET(output_path, optarg);
|
||||||
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
|
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_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
||||||
case _O_PERF: OPT_SET(log_level, LOG_LEVEL_PERF);
|
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
|
||||||
case _O_VERBOSE: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
|
case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE);
|
||||||
case _O_DEBUG: OPT_SET(log_level, LOG_LEVEL_DEBUG);
|
case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG);
|
||||||
case _O_FORCE_LOG_COLORS: OPT_SET(log_colored, true);
|
case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true);
|
||||||
case _O_NO_LOG_COLORS: OPT_SET(log_colored, false);
|
case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false);
|
||||||
|
|
||||||
case _O_HELP: _help(stdout); return 0;
|
case _O_HELP: _help(stdout); return 0;
|
||||||
case _O_VERSION: puts(VERSION); 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) {
|
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;
|
memsink_s *sink = NULL;
|
||||||
|
|
||||||
if ((sink = memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == 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(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
||||||
SAY(" Enabling debugging messages can slow down the program.");
|
SAY(" Enabling debugging messages can slow down the program.");
|
||||||
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
|
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(" --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(" --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");
|
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");
|
||||||
|
|||||||
@@ -22,6 +22,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifndef VERSION
|
#define VERSION_MAJOR 3
|
||||||
# define VERSION "3.17"
|
#define VERSION_MINOR 24
|
||||||
#endif
|
|
||||||
|
#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"
|
#include "frame.h"
|
||||||
|
|
||||||
|
|
||||||
frame_s *frame_init(const char *name) {
|
frame_s *frame_init(void) {
|
||||||
frame_s *frame;
|
frame_s *frame;
|
||||||
A_CALLOC(frame, 1);
|
A_CALLOC(frame, 1);
|
||||||
frame->name = name;
|
|
||||||
frame->managed = true;
|
|
||||||
frame_realloc_data(frame, 512 * 1024);
|
frame_realloc_data(frame, 512 * 1024);
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
void frame_destroy(frame_s *frame) {
|
void frame_destroy(frame_s *frame) {
|
||||||
assert(frame->managed);
|
|
||||||
if (frame->data) {
|
if (frame->data) {
|
||||||
free(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) {
|
void frame_realloc_data(frame_s *frame, size_t size) {
|
||||||
assert(frame->managed);
|
|
||||||
if (frame->allocated < size) {
|
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);
|
A_REALLOC(frame->data, size);
|
||||||
frame->allocated = size;
|
frame->allocated = size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) {
|
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) {
|
||||||
assert(frame->managed);
|
|
||||||
frame_realloc_data(frame, size);
|
frame_realloc_data(frame, size);
|
||||||
memcpy(frame->data, data, size);
|
memcpy(frame->data, data, size);
|
||||||
frame->used = size;
|
frame->used = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void frame_append_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) {
|
||||||
assert(frame->managed);
|
|
||||||
size_t new_used = frame->used + size;
|
size_t new_used = frame->used + size;
|
||||||
frame_realloc_data(frame, new_used);
|
frame_realloc_data(frame, new_used);
|
||||||
memcpy(frame->data + frame->used, data, size);
|
memcpy(frame->data + frame->used, data, size);
|
||||||
frame->used = new_used;
|
frame->used = new_used;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define COPY(_field) dest->_field = src->_field
|
|
||||||
|
|
||||||
void frame_copy(const frame_s *src, frame_s *dest) {
|
void frame_copy(const frame_s *src, frame_s *dest) {
|
||||||
assert(dest->managed);
|
|
||||||
frame_set_data(dest, src->data, src->used);
|
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) {
|
bool frame_compare(const frame_s *a, const frame_s *b) {
|
||||||
# define CMP(_field) (a->_field == b->_field)
|
|
||||||
return (
|
return (
|
||||||
a->allocated && b->allocated
|
a->allocated && b->allocated
|
||||||
&& CMP(used)
|
&& FRAME_COMPARE_META_USED_NOTS(a, b)
|
||||||
&& CMP(width)
|
|
||||||
&& CMP(height)
|
|
||||||
&& CMP(format)
|
|
||||||
&& CMP(stride)
|
|
||||||
&& CMP(online)
|
|
||||||
&& CMP(key)
|
|
||||||
&& !memcmp(a->data, b->data, b->used)
|
&& !memcmp(a->data, b->data, b->used)
|
||||||
);
|
);
|
||||||
# undef CMP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned frame_get_padding(const frame_s *frame) {
|
unsigned frame_get_padding(const frame_s *frame) {
|
||||||
|
|||||||
@@ -32,12 +32,9 @@
|
|||||||
#include <linux/videodev2.h>
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
#include "logging.h"
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *name;
|
|
||||||
|
|
||||||
uint8_t *data;
|
uint8_t *data;
|
||||||
size_t used;
|
size_t used;
|
||||||
size_t allocated;
|
size_t allocated;
|
||||||
@@ -56,12 +53,37 @@ typedef struct {
|
|||||||
long double grab_ts;
|
long double grab_ts;
|
||||||
long double encode_begin_ts;
|
long double encode_begin_ts;
|
||||||
long double encode_end_ts;
|
long double encode_end_ts;
|
||||||
|
|
||||||
bool managed;
|
|
||||||
} frame_s;
|
} 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_destroy(frame_s *frame);
|
||||||
|
|
||||||
void frame_realloc_data(frame_s *frame, size_t size);
|
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_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(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);
|
bool frame_compare(const frame_s *a, const frame_s *b);
|
||||||
|
|
||||||
unsigned frame_get_padding(const frame_s *frame);
|
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"
|
#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 { \
|
#define LOGGING_INIT { \
|
||||||
log_level = LOG_LEVEL_INFO; \
|
us_log_level = LOG_LEVEL_INFO; \
|
||||||
log_colored = isatty(2); \
|
us_log_colored = isatty(2); \
|
||||||
A_MUTEX_INIT(&log_mutex); \
|
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_LOCK A_MUTEX_LOCK(&us_log_mutex)
|
||||||
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&log_mutex)
|
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&us_log_mutex)
|
||||||
|
|
||||||
|
|
||||||
#define COLOR_GRAY "\x1b[30;1m"
|
#define COLOR_GRAY "\x1b[30;1m"
|
||||||
@@ -84,7 +84,7 @@ extern pthread_mutex_t log_mutex;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define SEP_DEBUG(_ch) { \
|
#define SEP_DEBUG(_ch) { \
|
||||||
if (log_level >= LOG_LEVEL_DEBUG) { \
|
if (us_log_level >= LOG_LEVEL_DEBUG) { \
|
||||||
SEP_INFO(_ch); \
|
SEP_INFO(_ch); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ extern pthread_mutex_t log_mutex;
|
|||||||
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
|
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
|
||||||
char _tname_buf[MAX_THREAD_NAME] = {0}; \
|
char _tname_buf[MAX_THREAD_NAME] = {0}; \
|
||||||
thread_get_name(_tname_buf); \
|
thread_get_name(_tname_buf); \
|
||||||
if (log_colored) { \
|
if (us_log_colored) { \
|
||||||
fprintf(stderr, COLOR_GRAY "-- " _label_color _label COLOR_GRAY \
|
fprintf(stderr, COLOR_GRAY "-- " _label_color _label COLOR_GRAY \
|
||||||
" [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
|
" [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
|
||||||
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
|
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
|
||||||
@@ -130,25 +130,25 @@ extern pthread_mutex_t log_mutex;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define LOG_PERF(_msg, ...) { \
|
#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__); \
|
LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define LOG_PERF_FPS(_msg, ...) { \
|
#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__); \
|
LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define LOG_VERBOSE(_msg, ...) { \
|
#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__); \
|
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define LOG_VERBOSE_PERROR(_msg, ...) { \
|
#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_buf[1024] = {0}; \
|
||||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
|
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
|
||||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
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, ...) { \
|
#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__); \
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -54,14 +54,7 @@ memsink_s *memsink_init(
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((sink->mem = mmap(
|
if ((sink->mem = memsink_shared_map(sink->fd)) == NULL) {
|
||||||
NULL,
|
|
||||||
sizeof(memsink_shared_s),
|
|
||||||
PROT_READ | PROT_WRITE,
|
|
||||||
MAP_SHARED,
|
|
||||||
sink->fd,
|
|
||||||
0
|
|
||||||
)) == MAP_FAILED) {
|
|
||||||
LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
@@ -75,7 +68,7 @@ memsink_s *memsink_init(
|
|||||||
|
|
||||||
void memsink_destroy(memsink_s *sink) {
|
void memsink_destroy(memsink_s *sink) {
|
||||||
if (sink->mem != MAP_FAILED) {
|
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);
|
LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +86,7 @@ void memsink_destroy(memsink_s *sink) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||||
// Возвращает true, если если клиенты ИЛИ изменились метаданные
|
// Возвращает true, если есть клиенты ИЛИ изменились метаданные
|
||||||
|
|
||||||
assert(sink->server);
|
assert(sink->server);
|
||||||
|
|
||||||
@@ -108,9 +101,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());
|
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 || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));
|
||||||
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) {
|
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||||
@@ -133,24 +124,16 @@ int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
|||||||
if (flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
if (flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
||||||
LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
|
LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
|
||||||
|
|
||||||
# define COPY(_field) sink->mem->_field = frame->_field
|
|
||||||
sink->last_id = get_now_id();
|
sink->last_id = get_now_id();
|
||||||
sink->mem->id = sink->last_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);
|
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->magic = MEMSINK_MAGIC;
|
||||||
sink->mem->version = MEMSINK_VERSION;
|
sink->mem->version = MEMSINK_VERSION;
|
||||||
# undef COPY
|
sink->has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
|
||||||
|
|
||||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||||
@@ -189,19 +172,9 @@ int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress u
|
|||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (sink->mem->id != sink->last_id) { // When updated
|
if (sink->mem->id != sink->last_id) { // When updated
|
||||||
# define COPY(_field) frame->_field = sink->mem->_field
|
|
||||||
sink->last_id = sink->mem->id;
|
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);
|
frame_set_data(frame, sink->mem->data, sink->mem->used);
|
||||||
# undef COPY
|
FRAME_COPY_META(sink->mem, frame);
|
||||||
retval = 0;
|
retval = 0;
|
||||||
}
|
}
|
||||||
sink->mem->last_client_ts = get_now_monotonic();
|
sink->mem->last_client_ts = get_now_monotonic();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
|
||||||
#define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
#define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||||
@@ -59,3 +60,25 @@ typedef struct {
|
|||||||
|
|
||||||
uint8_t data[MEMSINK_MAX_DATA];
|
uint8_t data[MEMSINK_MAX_DATA];
|
||||||
} memsink_shared_s;
|
} 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 <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <locale.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
@@ -35,17 +37,21 @@
|
|||||||
#include <sys/file.h>
|
#include <sys/file.h>
|
||||||
|
|
||||||
|
|
||||||
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
|
#define RN "\r\n"
|
||||||
#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 INLINE inline __attribute__((always_inline))
|
#define INLINE inline __attribute__((always_inline))
|
||||||
#define UNUSED __attribute__((unused))
|
#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");
|
return (flag ? "true" : "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,3 +147,16 @@ INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
|
|||||||
}
|
}
|
||||||
return retval;
|
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];
|
char msg[JMSG_LENGTH_MAX];
|
||||||
|
|
||||||
(*jpeg_error->mgr.format_message)(jpeg, msg);
|
(*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);
|
longjmp(jpeg_error->jmp, -1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ frame_s *blank_frame_init(const char *path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static frame_s *_init_internal(void) {
|
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);
|
frame_set_data(blank, BLANK_JPEG_DATA, BLANK_JPEG_DATA_SIZE);
|
||||||
blank->width = BLANK_JPEG_WIDTH;
|
blank->width = BLANK_JPEG_WIDTH;
|
||||||
blank->height = BLANK_JPEG_HEIGHT;
|
blank->height = BLANK_JPEG_HEIGHT;
|
||||||
@@ -55,7 +55,7 @@ static frame_s *_init_internal(void) {
|
|||||||
static frame_s *_init_external(const char *path) {
|
static frame_s *_init_external(const char *path) {
|
||||||
FILE *fp = NULL;
|
FILE *fp = NULL;
|
||||||
|
|
||||||
frame_s *blank = frame_init("blank_external");
|
frame_s *blank = frame_init();
|
||||||
blank->format = V4L2_PIX_FMT_JPEG;
|
blank->format = V4L2_PIX_FMT_JPEG;
|
||||||
|
|
||||||
if ((fp = fopen(path, "rb")) == NULL) {
|
if ((fp = fopen(path, "rb")) == NULL) {
|
||||||
@@ -83,7 +83,7 @@ static frame_s *_init_external(const char *path) {
|
|||||||
}
|
}
|
||||||
# undef CHUNK_SIZE
|
# undef CHUNK_SIZE
|
||||||
|
|
||||||
frame_s *decoded = frame_init("blank_external_decoded");
|
frame_s *decoded = frame_init();
|
||||||
if (unjpeg(blank, decoded, false) < 0) {
|
if (unjpeg(blank, decoded, false) < 0) {
|
||||||
frame_destroy(decoded);
|
frame_destroy(decoded);
|
||||||
goto error;
|
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 void _worker_job_destroy(void *v_job);
|
||||||
static bool _worker_run_job(worker_s *wr);
|
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));
|
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;
|
encoder_job_s *job;
|
||||||
A_CALLOC(job, 1);
|
A_CALLOC(job, 1);
|
||||||
job->enc = (encoder_s *)v_enc;
|
job->enc = (encoder_s *)v_enc;
|
||||||
|
job->dest = frame_init();
|
||||||
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);
|
|
||||||
|
|
||||||
return (void *)job;
|
return (void *)job;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _worker_job_destroy(void *v_job) {
|
static void _worker_job_destroy(void *v_job) {
|
||||||
encoder_job_s *job = (encoder_job_s *)v_job;
|
encoder_job_s *job = (encoder_job_s *)v_job;
|
||||||
frame_destroy(job->dest);
|
frame_destroy(job->dest);
|
||||||
free(job->dest_role);
|
|
||||||
free(job);
|
free(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#include "gpio.h"
|
#include "gpio.h"
|
||||||
|
|
||||||
|
|
||||||
gpio_s gpio = {
|
gpio_s us_gpio = {
|
||||||
.path = "/dev/gpiochip0",
|
.path = "/dev/gpiochip0",
|
||||||
.consumer_prefix = "ustreamer",
|
.consumer_prefix = "ustreamer",
|
||||||
|
|
||||||
@@ -51,42 +51,42 @@ static void _gpio_output_destroy(gpio_output_s *output);
|
|||||||
|
|
||||||
|
|
||||||
void gpio_init(void) {
|
void gpio_init(void) {
|
||||||
assert(gpio.chip == NULL);
|
assert(us_gpio.chip == NULL);
|
||||||
if (
|
if (
|
||||||
gpio.prog_running.pin >= 0
|
us_gpio.prog_running.pin >= 0
|
||||||
|| gpio.stream_online.pin >= 0
|
|| us_gpio.stream_online.pin >= 0
|
||||||
|| gpio.has_http_clients.pin >= 0
|
|| us_gpio.has_http_clients.pin >= 0
|
||||||
) {
|
) {
|
||||||
A_MUTEX_INIT(&gpio.mutex);
|
A_MUTEX_INIT(&us_gpio.mutex);
|
||||||
LOG_INFO("GPIO: Using chip device: %s", gpio.path);
|
LOG_INFO("GPIO: Using chip device: %s", us_gpio.path);
|
||||||
if ((gpio.chip = gpiod_chip_open(gpio.path)) != NULL) {
|
if ((us_gpio.chip = gpiod_chip_open(us_gpio.path)) != NULL) {
|
||||||
_gpio_output_init(&gpio.prog_running);
|
_gpio_output_init(&us_gpio.prog_running);
|
||||||
_gpio_output_init(&gpio.stream_online);
|
_gpio_output_init(&us_gpio.stream_online);
|
||||||
_gpio_output_init(&gpio.has_http_clients);
|
_gpio_output_init(&us_gpio.has_http_clients);
|
||||||
} else {
|
} 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) {
|
void gpio_destroy(void) {
|
||||||
_gpio_output_destroy(&gpio.prog_running);
|
_gpio_output_destroy(&us_gpio.prog_running);
|
||||||
_gpio_output_destroy(&gpio.stream_online);
|
_gpio_output_destroy(&us_gpio.stream_online);
|
||||||
_gpio_output_destroy(&gpio.has_http_clients);
|
_gpio_output_destroy(&us_gpio.has_http_clients);
|
||||||
if (gpio.chip) {
|
if (us_gpio.chip) {
|
||||||
gpiod_chip_close(gpio.chip);
|
gpiod_chip_close(us_gpio.chip);
|
||||||
gpio.chip = NULL;
|
us_gpio.chip = NULL;
|
||||||
A_MUTEX_DESTROY(&gpio.mutex);
|
A_MUTEX_DESTROY(&us_gpio.mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int gpio_inner_set(gpio_output_s *output, bool state) {
|
int gpio_inner_set(gpio_output_s *output, bool state) {
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
|
||||||
assert(gpio.chip);
|
assert(us_gpio.chip);
|
||||||
assert(output->line);
|
assert(output->line);
|
||||||
assert(output->state != state); // Must be checked in macro for the performance
|
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) { \
|
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); \
|
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;
|
retval = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
A_MUTEX_UNLOCK(&gpio.mutex);
|
A_MUTEX_UNLOCK(&us_gpio.mutex);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _gpio_output_init(gpio_output_s *output) {
|
static void _gpio_output_init(gpio_output_s *output) {
|
||||||
assert(gpio.chip);
|
assert(us_gpio.chip);
|
||||||
assert(output->line == NULL);
|
assert(output->line == NULL);
|
||||||
|
|
||||||
A_CALLOC(output->consumer, strlen(gpio.consumer_prefix) + strlen(output->role) + 16);
|
A_ASPRINTF(output->consumer, "%s::%s", us_gpio.consumer_prefix, output->role);
|
||||||
sprintf(output->consumer, "%s::%s", gpio.consumer_prefix, output->role);
|
|
||||||
|
|
||||||
if (output->pin >= 0) {
|
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) {
|
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);
|
LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
||||||
_gpio_output_destroy(output);
|
_gpio_output_destroy(output);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ typedef struct {
|
|||||||
} gpio_s;
|
} gpio_s;
|
||||||
|
|
||||||
|
|
||||||
extern gpio_s gpio;
|
extern gpio_s us_gpio;
|
||||||
|
|
||||||
|
|
||||||
void gpio_init(void);
|
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) {
|
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) {
|
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) {
|
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
|
#undef SET_STATE
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop)
|
|||||||
h264_stream_s *h264;
|
h264_stream_s *h264;
|
||||||
A_CALLOC(h264, 1);
|
A_CALLOC(h264, 1);
|
||||||
h264->sink = sink;
|
h264->sink = sink;
|
||||||
h264->tmp_src = frame_init("h264_tmp_src");
|
h264->tmp_src = frame_init();
|
||||||
h264->dest = frame_init("h264_dest");
|
h264->dest = frame_init();
|
||||||
atomic_init(&h264->online, false);
|
atomic_init(&h264->online, false);
|
||||||
|
|
||||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L210
|
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100
|
||||||
if ((h264->enc = h264_encoder_init(bitrate, gop, 0)) == NULL) {
|
// По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480
|
||||||
|
// енкодер через несколько секунд перестает производить корректные фреймы.
|
||||||
|
if ((h264->enc = h264_encoder_init(bitrate, gop, 30)) == NULL) {
|
||||||
goto error;
|
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 bool _expose_new_frame(server_s *server);
|
||||||
|
|
||||||
|
static char *_http_get_client_hostport(struct evhttp_request *request);
|
||||||
|
|
||||||
|
|
||||||
#define RUN(_next) server->run->_next
|
#define RUN(_next) server->run->_next
|
||||||
#define STREAM(_next) RUN(stream->_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) {
|
server_s *server_init(stream_s *stream) {
|
||||||
exposed_s *exposed;
|
exposed_s *exposed;
|
||||||
A_CALLOC(exposed, 1);
|
A_CALLOC(exposed, 1);
|
||||||
exposed->frame = frame_init("http_exposed");
|
exposed->frame = frame_init();
|
||||||
|
|
||||||
server_runtime_s *run;
|
server_runtime_s *run;
|
||||||
A_CALLOC(run, 1);
|
A_CALLOC(run, 1);
|
||||||
@@ -91,12 +93,11 @@ void server_destroy(server_s *server) {
|
|||||||
libevent_global_shutdown();
|
libevent_global_shutdown();
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
for (stream_client_s *client = RUN(stream_clients); client != NULL;) {
|
LIST_ITERATE(RUN(stream_clients), client, {
|
||||||
stream_client_s *next = client->next;
|
|
||||||
free(client->key);
|
free(client->key);
|
||||||
|
free(client->hostport);
|
||||||
free(client);
|
free(client);
|
||||||
client = next;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (RUN(auth_token)) {
|
if (RUN(auth_token)) {
|
||||||
free(RUN(auth_token));
|
free(RUN(auth_token));
|
||||||
@@ -142,16 +143,14 @@ int server_listen(server_s *server) {
|
|||||||
evhttp_set_timeout(RUN(http), server->timeout);
|
evhttp_set_timeout(RUN(http), server->timeout);
|
||||||
|
|
||||||
if (server->user[0] != '\0') {
|
if (server->user[0] != '\0') {
|
||||||
char *raw_token;
|
|
||||||
char *encoded_token = NULL;
|
char *encoded_token = NULL;
|
||||||
|
|
||||||
A_CALLOC(raw_token, strlen(server->user) + strlen(server->passwd) + 16);
|
char *raw_token;
|
||||||
sprintf(raw_token, "%s:%s", server->user, server->passwd);
|
A_ASPRINTF(raw_token, "%s:%s", server->user, server->passwd);
|
||||||
base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
|
base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
|
||||||
free(raw_token);
|
free(raw_token);
|
||||||
|
|
||||||
A_CALLOC(RUN(auth_token), strlen(encoded_token) + 16);
|
A_ASPRINTF(RUN(auth_token), "Basic %s", encoded_token);
|
||||||
sprintf(RUN(auth_token), "Basic %s", encoded_token);
|
|
||||||
free(encoded_token);
|
free(encoded_token);
|
||||||
|
|
||||||
LOG_INFO("Using HTTP basic auth");
|
LOG_INFO("Using HTTP basic auth");
|
||||||
@@ -366,9 +365,9 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
|||||||
RUN(stream_clients_count)
|
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,
|
assert(evbuffer_add_printf(buf,
|
||||||
"\"%s\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
|
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
|
||||||
" \"dual_final_frames\": %s, \"zero_data\": %s}%s",
|
" \"dual_final_frames\": %s, \"zero_data\": %s}%s",
|
||||||
client->id,
|
client->id,
|
||||||
client->fps,
|
client->fps,
|
||||||
@@ -378,7 +377,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
|||||||
bool_to_string(client->zero_data),
|
bool_to_string(client->zero_data),
|
||||||
(client->next ? ", " : "")
|
(client->next ? ", " : "")
|
||||||
));
|
));
|
||||||
}
|
});
|
||||||
|
|
||||||
assert(evbuffer_add_printf(buf, "}}}}"));
|
assert(evbuffer_add_printf(buf, "}}}}"));
|
||||||
|
|
||||||
@@ -473,20 +472,10 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
# undef PARSE_PARAM
|
# undef PARSE_PARAM
|
||||||
evhttp_clear_headers(¶ms);
|
evhttp_clear_headers(¶ms);
|
||||||
|
|
||||||
uuid_t uuid;
|
client->hostport = _http_get_client_hostport(request);
|
||||||
uuid_generate(uuid);
|
client->id = get_now_id();
|
||||||
uuid_unparse_lower(uuid, client->id);
|
|
||||||
|
|
||||||
if (RUN(stream_clients) == NULL) {
|
LIST_APPEND_C(RUN(stream_clients), client, RUN(stream_clients_count));
|
||||||
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;
|
|
||||||
|
|
||||||
if (RUN(stream_clients_count) == 1) {
|
if (RUN(stream_clients_count) == 1) {
|
||||||
atomic_store(&VID(has_clients), true);
|
atomic_store(&VID(has_clients), true);
|
||||||
@@ -495,23 +484,18 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
char *client_addr;
|
LOG_INFO("HTTP: Registered client: %s, id=%" PRIx64 "; clients now: %u",
|
||||||
unsigned short client_port;
|
client->hostport, client->id, RUN(stream_clients_count));
|
||||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
|
||||||
|
|
||||||
LOG_INFO("HTTP: Registered client: [%s]:%u, id=%s; clients now: %u",
|
|
||||||
client_addr, client_port, client->id, RUN(stream_clients_count));
|
|
||||||
|
|
||||||
|
|
||||||
struct bufferevent *buf_event = evhttp_connection_get_bufferevent(conn);
|
struct bufferevent *buf_event = evhttp_connection_get_bufferevent(conn);
|
||||||
if (server->tcp_nodelay && !RUN(unix_fd)) {
|
if (server->tcp_nodelay && !RUN(unix_fd)) {
|
||||||
evutil_socket_t fd;
|
evutil_socket_t fd;
|
||||||
int on = 1;
|
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);
|
assert((fd = bufferevent_getfd(buf_event)) >= 0);
|
||||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)) != 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);
|
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||||
@@ -525,7 +509,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) {
|
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
||||||
# define BOUNDARY "boundarydonotcross"
|
# define BOUNDARY "boundarydonotcross"
|
||||||
# define RN "\r\n"
|
|
||||||
|
|
||||||
stream_client_s *client = (stream_client_s *)v_client;
|
stream_client_s *client = (stream_client_s *)v_client;
|
||||||
server_s *server = client->server;
|
server_s *server = client->server;
|
||||||
@@ -575,7 +558,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||||
"Pragma: no-cache" RN
|
"Pragma: no-cache" RN
|
||||||
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
||||||
"Set-Cookie: stream_client=%s/%s; path=/; max-age=30" RN
|
"Set-Cookie: stream_client=%s/%" PRIx64 "; path=/; max-age=30" RN
|
||||||
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
||||||
RN
|
RN
|
||||||
"--" BOUNDARY RN,
|
"--" BOUNDARY RN,
|
||||||
@@ -650,7 +633,6 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
bufferevent_enable(buf_event, EV_READ);
|
bufferevent_enable(buf_event, EV_READ);
|
||||||
|
|
||||||
# undef ADD_ADVANCE_HEADERS
|
# undef ADD_ADVANCE_HEADERS
|
||||||
# undef RN
|
|
||||||
# undef BOUNDARY
|
# undef BOUNDARY
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,10 +640,7 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
|||||||
stream_client_s *client = (stream_client_s *)v_client;
|
stream_client_s *client = (stream_client_s *)v_client;
|
||||||
server_s *server = client->server;
|
server_s *server = client->server;
|
||||||
|
|
||||||
char *reason = bufferevent_my_format_reason(what);
|
LIST_REMOVE_C(RUN(stream_clients), client, RUN(stream_clients_count));
|
||||||
|
|
||||||
assert(RUN(stream_clients_count) > 0);
|
|
||||||
RUN(stream_clients_count) -= 1;
|
|
||||||
|
|
||||||
if (RUN(stream_clients_count) == 0) {
|
if (RUN(stream_clients_count) == 0) {
|
||||||
atomic_store(&VID(has_clients), false);
|
atomic_store(&VID(has_clients), false);
|
||||||
@@ -670,40 +649,26 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
|||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
char *client_addr = "???";
|
char *reason = bufferevent_my_format_reason(what);
|
||||||
unsigned short client_port = 0;
|
LOG_INFO("HTTP: Disconnected client: %s, id=%" PRIx64 ", %s; clients now: %u",
|
||||||
|
client->hostport, client->id, reason, RUN(stream_clients_count));
|
||||||
|
free(reason);
|
||||||
|
|
||||||
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
||||||
if (conn) {
|
|
||||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO("HTTP: Disconnected client: [%s]:%u, id=%s, %s; clients now: %u",
|
|
||||||
client_addr, client_port, client->id, reason, RUN(stream_clients_count));
|
|
||||||
|
|
||||||
if (conn) {
|
if (conn) {
|
||||||
evhttp_connection_free(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->key);
|
||||||
|
free(client->hostport);
|
||||||
free(client);
|
free(client);
|
||||||
|
|
||||||
free(reason);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated) {
|
static void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated) {
|
||||||
bool has_clients = false;
|
bool has_clients = false;
|
||||||
bool queued = 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);
|
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
||||||
if (conn) {
|
if (conn) {
|
||||||
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
||||||
@@ -734,7 +699,7 @@ static void _http_queue_send_stream(server_s *server, bool stream_updated, bool
|
|||||||
|
|
||||||
has_clients = true;
|
has_clients = true;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if (queued) {
|
if (queued) {
|
||||||
static unsigned queued_fps_accum = 0;
|
static unsigned queued_fps_accum = 0;
|
||||||
@@ -836,3 +801,37 @@ static bool _expose_new_frame(server_s *server) {
|
|||||||
#undef VID
|
#undef VID
|
||||||
#undef STREAM
|
#undef STREAM
|
||||||
#undef RUN
|
#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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -47,8 +48,6 @@
|
|||||||
#include <event2/bufferevent.h>
|
#include <event2/bufferevent.h>
|
||||||
#include <event2/keyvalq_struct.h>
|
#include <event2/keyvalq_struct.h>
|
||||||
|
|
||||||
#include <uuid/uuid.h>
|
|
||||||
|
|
||||||
#ifndef EVTHREAD_USE_PTHREADS_IMPLEMENTED
|
#ifndef EVTHREAD_USE_PTHREADS_IMPLEMENTED
|
||||||
# error Required libevent-pthreads support
|
# error Required libevent-pthreads support
|
||||||
#endif
|
#endif
|
||||||
@@ -59,6 +58,7 @@
|
|||||||
#include "../../libs/process.h"
|
#include "../../libs/process.h"
|
||||||
#include "../../libs/frame.h"
|
#include "../../libs/frame.h"
|
||||||
#include "../../libs/base64.h"
|
#include "../../libs/base64.h"
|
||||||
|
#include "../../libs/list.h"
|
||||||
#include "../data/index_html.h"
|
#include "../data/index_html.h"
|
||||||
#include "../encoder.h"
|
#include "../encoder.h"
|
||||||
#include "../stream.h"
|
#include "../stream.h"
|
||||||
@@ -83,7 +83,8 @@ typedef struct stream_client_sx {
|
|||||||
bool dual_final_frames;
|
bool dual_final_frames;
|
||||||
bool zero_data;
|
bool zero_data;
|
||||||
|
|
||||||
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
|
char *hostport;
|
||||||
|
uint64_t id;
|
||||||
bool need_initial;
|
bool need_initial;
|
||||||
bool need_first_frame;
|
bool need_first_frame;
|
||||||
bool updated_prev;
|
bool updated_prev;
|
||||||
@@ -91,8 +92,7 @@ typedef struct stream_client_sx {
|
|||||||
unsigned fps_accum;
|
unsigned fps_accum;
|
||||||
long long fps_accum_second;
|
long long fps_accum_second;
|
||||||
|
|
||||||
struct stream_client_sx *prev;
|
LIST_STRUCT(struct stream_client_sx);
|
||||||
struct stream_client_sx *next;
|
|
||||||
} stream_client_s;
|
} stream_client_s;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
|
|||||||
goto error;
|
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);
|
sprintf(path, "%s/%s", root_path, simplified_path);
|
||||||
|
|
||||||
struct stat st;
|
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
|
# undef ADD_SINK
|
||||||
|
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
case _O_GPIO_DEVICE: OPT_SET(gpio.path, optarg);
|
case _O_GPIO_DEVICE: OPT_SET(us_gpio.path, optarg);
|
||||||
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(gpio.consumer_prefix, optarg);
|
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(us_gpio.consumer_prefix, optarg);
|
||||||
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio.prog_running.pin, 0, 256, 0);
|
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", gpio.stream_online.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", gpio.has_http_clients.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
|
# endif
|
||||||
|
|
||||||
# ifdef HAS_PDEATHSIG
|
# ifdef HAS_PDEATHSIG
|
||||||
@@ -459,12 +459,12 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
|||||||
# endif
|
# endif
|
||||||
case _O_NOTIFY_PARENT: OPT_SET(server->notify_parent, true);
|
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_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
||||||
case _O_PERF: OPT_SET(log_level, LOG_LEVEL_PERF);
|
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
|
||||||
case _O_VERBOSE: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
|
case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE);
|
||||||
case _O_DEBUG: OPT_SET(log_level, LOG_LEVEL_DEBUG);
|
case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG);
|
||||||
case _O_FORCE_LOG_COLORS: OPT_SET(log_colored, true);
|
case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true);
|
||||||
case _O_NO_LOG_COLORS: OPT_SET(log_colored, false);
|
case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false);
|
||||||
|
|
||||||
case _O_HELP: _help(stdout, dev, enc, stream, server); return 1;
|
case _O_HELP: _help(stdout, dev, enc, stream, server); return 1;
|
||||||
case _O_VERSION: puts(VERSION); 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
|
# ifdef WITH_GPIO
|
||||||
SAY("GPIO options:");
|
SAY("GPIO options:");
|
||||||
SAY("═════════════");
|
SAY("═════════════");
|
||||||
SAY(" --gpio-device </dev/path> ───── Path to GPIO character device. Default: %s.\n", gpio.path);
|
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", gpio.consumer_prefix);
|
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-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-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");
|
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(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
||||||
SAY(" Enabling debugging messages can slow down the program.");
|
SAY(" Enabling debugging messages can slow down the program.");
|
||||||
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
|
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(" --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(" --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");
|
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;
|
video_s *video;
|
||||||
A_CALLOC(video, 1);
|
A_CALLOC(video, 1);
|
||||||
video->frame = frame_init("stream_video");
|
video->frame = frame_init();
|
||||||
atomic_init(&video->updated, false);
|
atomic_init(&video->updated, false);
|
||||||
A_MUTEX_INIT(&video->mutex);
|
A_MUTEX_INIT(&video->mutex);
|
||||||
atomic_init(&video->has_clients, false);
|
atomic_init(&video->has_clients, false);
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ static void *_worker_thread(void *v_worker);
|
|||||||
|
|
||||||
workers_pool_s *workers_pool_init(
|
workers_pool_s *workers_pool_init(
|
||||||
const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval,
|
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,
|
workers_pool_job_init_f job_init, void *job_init_arg,
|
||||||
void (*job_destroy)(void *),
|
workers_pool_job_destroy_f job_destroy,
|
||||||
bool (*run_job)(worker_s *)) {
|
workers_pool_run_job_f run_job) {
|
||||||
|
|
||||||
LOG_INFO("Creating pool %s with %u workers ...", name, n_workers);
|
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_MUTEX_INIT(&pool->free_workers_mutex);
|
||||||
A_COND_INIT(&pool->free_workers_cond);
|
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) {
|
for (unsigned number = 0; number < pool->n_workers; ++number) {
|
||||||
# define WR(_next) pool->workers[number]._next
|
# define WR(_next) pool->workers[number]._next
|
||||||
|
|
||||||
WR(number) = number;
|
WR(number) = number;
|
||||||
A_CALLOC(WR(name), wr_name_len);
|
A_ASPRINTF(WR(name), "%s-%u", wr_prefix, number);
|
||||||
snprintf(WR(name), wr_name_len, "%s-%u", wr_prefix, number);
|
|
||||||
|
|
||||||
A_MUTEX_INIT(&WR(has_job_mutex));
|
A_MUTEX_INIT(&WR(has_job_mutex));
|
||||||
atomic_init(&WR(has_job), false);
|
atomic_init(&WR(has_job), false);
|
||||||
A_COND_INIT(&WR(has_job_cond));
|
A_COND_INIT(&WR(has_job_cond));
|
||||||
|
|
||||||
WR(pool) = pool;
|
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]));
|
A_THREAD_CREATE(&WR(tid), _worker_thread, (void *)&(pool->workers[number]));
|
||||||
pool->free_workers += 1;
|
pool->free_workers += 1;
|
||||||
|
|||||||
@@ -55,12 +55,16 @@ typedef struct worker_sx {
|
|||||||
struct workers_pool_sx *pool;
|
struct workers_pool_sx *pool;
|
||||||
} worker_s;
|
} 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 {
|
typedef struct workers_pool_sx {
|
||||||
const char *name;
|
const char *name;
|
||||||
long double desired_interval;
|
long double desired_interval;
|
||||||
|
|
||||||
bool (*run_job)(worker_s *wr);
|
workers_pool_job_destroy_f job_destroy;
|
||||||
void (*job_destroy)(void *job);
|
workers_pool_run_job_f run_job;
|
||||||
|
|
||||||
unsigned n_workers;
|
unsigned n_workers;
|
||||||
worker_s *workers;
|
worker_s *workers;
|
||||||
@@ -79,9 +83,9 @@ typedef struct workers_pool_sx {
|
|||||||
|
|
||||||
workers_pool_s *workers_pool_init(
|
workers_pool_s *workers_pool_init(
|
||||||
const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval,
|
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,
|
workers_pool_job_init_f job_init, void *job_init_arg,
|
||||||
void (*job_destroy)(void *job),
|
workers_pool_job_destroy_f job_destroy,
|
||||||
bool (*run_job)(worker_s *));
|
workers_pool_run_job_f run_job);
|
||||||
|
|
||||||
void workers_pool_destroy(workers_pool_s *pool);
|
void workers_pool_destroy(workers_pool_s *pool);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user