Compare commits

..

166 Commits
v1.1 ... v2.0

Author SHA1 Message Date
Devaev Maxim
37179184ae Bump version: 1.26 → 2.0 2020-09-25 00:03:14 +03:00
Devaev Maxim
fc8aba0a12 Bump version: 1.25 → 1.26 2020-09-25 00:01:13 +03:00
Devaev Maxim
0d749eada3 V for make release 2020-09-23 18:11:25 +03:00
Maxim Devaev
da6984d531 Merge pull request #49 from pikvm/allow-origin
Disabled cross-domain requests by default
2020-09-23 00:04:45 +03:00
Devaev Maxim
df14031042 readme fix 2020-09-22 21:43:01 +03:00
Maxim Devaev
03975c1a85 Update README.md 2020-09-22 18:02:27 +03:00
Maxim Devaev
214a924da3 Update README.ru.md 2020-09-22 18:01:48 +03:00
Maxim Devaev
9e6a9a2fd4 Update README.md 2020-09-22 17:59:09 +03:00
Devaev Maxim
b498ae7e38 Issue #48: Disabled cross-domain requests by default 2020-09-22 17:58:10 +03:00
Maxim Devaev
278645ce51 Merge pull request #47 from pikvm/queue-assert
Double dequeue error
2020-09-20 00:53:58 +03:00
Devaev Maxim
f1ee5514e3 style fix 2020-09-20 00:44:21 +03:00
Maxim Devaev
3900728f9f Merge pull request #46 from pikvm/libgpiod
moved from wiringpi to libgpiod
2020-09-19 17:01:28 +03:00
Devaev Maxim
3dc083d2ef Rewrited #44: fixed memory error and leak 2020-09-19 16:55:39 +03:00
Maxim Devaev
653ebd6e88 Merge pull request #44 from schneemaier/master
Added HTTP GET parameter handling to server.c
2020-09-19 13:29:07 +03:00
Devaev Maxim
a770e7675d Issue #43: assert for double VIDIOC_DQBUF 2020-09-19 05:54:47 +03:00
Devaev Maxim
6725083be6 moved from wiringpi to libgpiod 2020-09-19 04:06:14 +03:00
Akos Schneemaier
0b39cadaad Added HTTP GET parameter handling to server.c to make URLcompatibility with mjpg streamer 2020-09-18 00:00:11 -04:00
Devaev Maxim
871b0cf132 improved logs 2020-09-18 04:19:58 +03:00
Maxim Devaev
afa888432a Update README.ru.md 2020-09-15 23:07:22 +03:00
Maxim Devaev
a42bd147ff Update README.md 2020-09-15 23:06:47 +03:00
Devaev Maxim
2ad8871a54 Bump version: 1.24 → 1.25 2020-09-01 08:55:28 +03:00
Devaev Maxim
266e210b04 fixed ld error 2020-09-01 08:53:33 +03:00
Devaev Maxim
0ac9f77619 Bump version: 1.23 → 1.24 2020-09-01 07:57:09 +03:00
Devaev Maxim
c1bc1d9506 fixed linter error 2020-09-01 07:52:29 +03:00
Devaev Maxim
deb37986b6 Issue #39: fixed missing gettid() syscall on *BSD 2020-09-01 06:39:12 +03:00
Devaev Maxim
ee6c555ce0 Issue #32: refactoring 2020-08-24 10:21:47 +03:00
Maxim Devaev
4395b8487f Merge pull request #37 from PascalHonegger/master
Issue #32: Create Dockerfiles
2020-08-24 00:22:52 +03:00
Pascal Honegger
f622d03d1b Issue #32: Create Dockerfiles 2020-08-23 15:36:48 +02:00
Devaev Maxim
36e6fa7b09 added aarch64 to PKGBUILD 2020-08-23 15:56:59 +03:00
Devaev Maxim
8cf6c66f21 Fixed #35: spell fix 2020-08-22 23:32:06 +03:00
Devaev Maxim
ac9761beb2 Bump version: 1.22 → 1.23 2020-08-22 16:53:29 +03:00
Devaev Maxim
90b7a5600f Issue #24: disable software framedrop if hw_fps == desired_fps 2020-08-22 16:17:34 +03:00
Maxim Devaev
4c70baecb1 Update README.ru.md 2020-08-21 23:01:15 +03:00
Maxim Devaev
15c14bfebf Update README.ru.md 2020-08-21 23:00:14 +03:00
Maxim Devaev
eab8043496 Update README.md 2020-08-21 22:59:11 +03:00
Devaev Maxim
53feba1248 Bump version: 1.21 → 1.22 2020-08-20 05:15:24 +03:00
Devaev Maxim
119821d5af queued_fps = 0 for no clients 2020-08-19 14:27:52 +03:00
Maxim Devaev
4faabf27ec Merge pull request #33 from pikvm/sem-timeout
Sem timeout
2020-08-19 13:44:23 +03:00
Devaev Maxim
191f6e3c09 non-zero min-frame-size; default = 128 2020-08-19 13:20:22 +03:00
Devaev Maxim
4e51439118 bsd compat 2020-08-18 15:19:09 +03:00
Devaev Maxim
e184e187a2 option --color-effect 2020-08-18 12:15:44 +03:00
Maxim Devaev
592568c9aa Merge pull request #31 from pikvm/flip
Options to flip image
2020-08-17 18:17:01 +03:00
Devaev Maxim
46c5a547a9 options to flip image 2020-08-17 03:49:29 +03:00
Devaev Maxim
3d097a4ffb more logs 2020-08-15 04:51:00 +03:00
Devaev Maxim
00e32c915c fixed uninitialized value 2020-08-15 04:40:55 +03:00
Devaev Maxim
d44c340dce vcos sem timeout 2020-08-15 00:10:07 +03:00
Devaev Maxim
8c18f1dffe Issue #25: fixed freebsd build 2020-08-14 03:38:05 +03:00
Devaev Maxim
c3c386ea5b Bump version: 1.20 → 1.21 2020-08-13 08:59:36 +03:00
Devaev Maxim
fa09992c46 better logging 2020-08-11 06:08:13 +03:00
Devaev Maxim
cefcd0c963 fixed ptr printing 2020-08-11 05:49:03 +03:00
Devaev Maxim
96c806071d Bump version: 1.19 → 1.20 2020-08-11 02:13:16 +03:00
Devaev Maxim
0775b35ef8 option --tcp-nodelay 2020-08-10 08:12:14 +03:00
Maxim Devaev
138d9a74d8 Update README.ru.md 2020-08-01 14:34:15 +03:00
Maxim Devaev
2a668643dc Update README.md 2020-08-01 14:33:52 +03:00
Maxim Devaev
56312cffb5 Update README.md 2020-07-24 13:08:53 +03:00
Maxim Devaev
f553b97dba Update README.md 2020-07-23 22:19:12 +03:00
Maxim Devaev
b619b1e096 Update README.ru.md 2020-07-23 22:18:52 +03:00
Maxim Devaev
06a32fd3ab Update README.md 2020-07-23 22:18:20 +03:00
Devaev Maxim
8637ff5c09 Bump version: 1.18 → 1.19 2020-07-11 04:43:55 +03:00
Devaev Maxim
bd5cf7d3de fixed io method parser 2020-07-08 00:38:49 +03:00
Devaev Maxim
e488eec90c userptr 2020-07-07 09:13:07 +03:00
Devaev Maxim
6615a23361 safer picture_compare(), removed one assert 2020-07-06 14:36:05 +03:00
Devaev Maxim
a91eba8d90 fixed picture_copy(): don't copy garbage 2020-07-06 13:10:49 +03:00
Devaev Maxim
d5aebc1231 Bump version: 1.17 → 1.18 2020-07-03 22:18:48 +03:00
Devaev Maxim
5f4f46bbe6 more omx error codes 2020-07-02 23:02:40 +03:00
Devaev Maxim
61a2fe6546 refactoring 2020-07-02 18:05:41 +03:00
Devaev Maxim
98499b6604 verbose message about disconnecting client 2020-06-28 13:15:24 +03:00
Devaev Maxim
f52d090f9b updated readme 2020-06-23 07:37:20 +03:00
Maxim Devaev
ebb3df46b9 Merge pull request #21 from mtlynch/ansible-note
Add note about uStreamer Ansible role
2020-06-21 03:12:38 +03:00
Michael Lynch
8fd6659cf1 Add note about uStreamer Ansible role 2020-06-20 19:53:22 -04:00
Devaev Maxim
13ce0bbc63 fixed raspbian installation manual 2020-05-30 20:19:03 +03:00
Maxim Devaev
0add4cc25f Update FUNDING.yml 2020-05-27 03:55:15 +03:00
Devaev Maxim
96d84b33bd new pyflakes 2020-05-22 15:43:33 +03:00
Maxim Devaev
f2dfe7641e Create FUNDING.yml 2020-05-19 18:59:08 +03:00
Devaev Maxim
97403cbb75 Bump version: 1.16 → 1.17 2020-05-19 01:08:37 +03:00
Devaev Maxim
0663bb1035 fix for gcc 10 multiple definition errors 2020-05-19 01:07:55 +03:00
Devaev Maxim
06f4017a5b Bump version: 1.15 → 1.16 2020-05-12 09:42:48 +03:00
Devaev Maxim
6493a62c6c badges 2020-05-12 09:39:02 +03:00
Devaev Maxim
66c627c682 lint fixes 2020-05-12 09:37:02 +03:00
Devaev Maxim
8d6f5f7f8f .dockerignore 2020-05-12 09:36:48 +03:00
Devaev Maxim
f1cdfd4223 docker-based linters 2020-05-12 09:26:24 +03:00
Devaev Maxim
972c288df3 action fix 2020-05-12 09:08:23 +03:00
Devaev Maxim
fd1d2dec71 action fix 2020-05-12 09:05:26 +03:00
Devaev Maxim
331bd181bf action fix 2020-05-12 09:01:44 +03:00
Devaev Maxim
231ff37570 action fix 2020-05-12 08:54:01 +03:00
Devaev Maxim
cec0b203b3 action fix 2020-05-12 08:51:32 +03:00
Devaev Maxim
e5c9d699a3 action fix 2020-05-12 08:50:08 +03:00
Maxim Devaev
198fbc1756 Create ccpp.yml 2020-05-12 08:46:39 +03:00
Devaev Maxim
55aa443d68 linters 2020-05-12 08:43:12 +03:00
Devaev Maxim
44d6288416 lint fixes 2020-05-12 08:42:18 +03:00
Devaev Maxim
aeae342853 lint fixes 2020-05-12 08:19:26 +03:00
Devaev Maxim
d22034da96 lint fixes 2020-05-12 07:45:12 +03:00
Devaev Maxim
43788f812d type fixes 2020-05-12 07:27:42 +03:00
Devaev Maxim
95318e14d8 lint fixes 2020-05-12 06:44:59 +03:00
Devaev Maxim
67d6f15776 lint fix 2020-05-11 20:02:30 +03:00
Devaev Maxim
6c2353ce2c Bump version: 1.14 → 1.15 2020-05-03 06:38:11 +03:00
Devaev Maxim
fcdfb2930a refactoring 2020-05-03 06:38:02 +03:00
Devaev Maxim
dcc90341c9 Bump version: 1.13 → 1.14 2020-05-03 06:32:59 +03:00
Devaev Maxim
7e102c88cd log static errors as verbose 2020-05-03 06:17:43 +03:00
Devaev Maxim
e131a3ba49 typo 2020-04-30 17:27:55 +03:00
Devaev Maxim
e393be4c63 Bump version: 1.12 → 1.13 2020-03-04 03:04:44 +03:00
Devaev Maxim
cf4f5f5b2a notify parent using SIGUSR2 2020-03-04 03:04:28 +03:00
Maxim Devaev
1190059359 Update README.ru.md 2020-02-25 16:06:25 +03:00
Maxim Devaev
910b27feb4 Update README.md 2020-02-25 16:05:58 +03:00
Devaev Maxim
60ca92d367 Bump version: 1.11 → 1.12 2020-02-19 16:46:38 +03:00
Devaev Maxim
4f44c5efa1 hint 2020-02-19 16:46:13 +03:00
Devaev Maxim
3504095871 refactoring 2020-02-19 08:56:50 +03:00
Devaev Maxim
6eeb49ef75 merged ctl options 2020-02-19 08:56:50 +03:00
Devaev Maxim
9353b3474a options to reset image settings to default 2020-02-19 08:56:50 +03:00
Maxim Devaev
dfc98a67f2 Update README.ru.md 2020-02-10 06:40:25 +03:00
Maxim Devaev
904c76fa93 Update README.md 2020-02-10 06:39:37 +03:00
Devaev Maxim
f0a7ca5c94 Issue #14: added message about fileserver 2020-01-30 03:53:06 +03:00
Devaev Maxim
bb4e9db7e7 fixed serving of empty static files 2020-01-29 20:20:53 +03:00
Devaev Maxim
61f2bfa00e fixed mime guessing 2020-01-29 20:13:31 +03:00
Devaev Maxim
49eb7f6e51 fixed clang 'linker' input unused 2020-01-19 17:23:41 +03:00
Devaev Maxim
b5375b835a Fixed #11: clang warning 2020-01-19 01:07:57 +03:00
Devaev Maxim
e3e2c5cead Bump version: 1.10 → 1.11 2020-01-14 16:49:50 +03:00
Devaev Maxim
ef4150877b check for 8 bits 2020-01-14 16:46:31 +03:00
Devaev Maxim
cc00d0fea3 Bump version: 1.9 → 1.10 2019-10-21 08:20:45 +03:00
Devaev Maxim
91a1e48a7c refactoring; renamed X-UStreamer-*-Time to X-UStreamer-*-Timestamp 2019-10-21 08:16:19 +03:00
Devaev Maxim
f381113d50 new arch pkg extension 2019-10-17 02:58:56 +03:00
Devaev Maxim
a6304959f4 Bump version: 1.8 → 1.9 2019-10-11 23:37:25 +03:00
Devaev Maxim
57bc6e160d keep original argv 2019-10-11 23:37:05 +03:00
Devaev Maxim
03dd5dfebb Bump version: 1.7 → 1.8 2019-10-11 08:23:40 +03:00
Devaev Maxim
00ef8928b3 temporary disabled setproctitle to avoid memory corruption 2019-10-11 08:23:04 +03:00
Devaev Maxim
2b338cea90 Bump version: 1.6 → 1.7 2019-10-11 06:42:20 +03:00
Devaev Maxim
e64a1a1688 improved readme 2019-10-11 06:42:06 +03:00
Devaev Maxim
2ad196b95e --features 2019-10-11 06:39:15 +03:00
Devaev Maxim
70c7bcc209 --process-name-prefix 2019-10-11 06:23:32 +03:00
Devaev Maxim
890b248563 ifdef 2019-10-10 23:11:15 +03:00
Devaev Maxim
be2de06b09 manual check if the parent process is alive after prctl() 2019-10-10 23:09:24 +03:00
Devaev Maxim
6175e6974c refactoring 2019-10-10 10:44:12 +03:00
Devaev Maxim
6bc26b734e Bump version: 1.5 → 1.6 2019-10-10 02:58:59 +03:00
Devaev Maxim
da6b5fd786 fixed spell 2019-10-10 02:54:49 +03:00
Devaev Maxim
e97b512f79 refactoring 2019-10-10 02:18:37 +03:00
Devaev Maxim
a1c83fc765 --exit-on-parent-death 2019-10-08 23:32:23 +03:00
Devaev Maxim
57132c46ac Bump version: 1.4 → 1.5 2019-09-18 04:55:27 +03:00
Devaev Maxim
6a7a26360f messages fix 2019-09-18 04:51:39 +03:00
Devaev Maxim
7a699833c4 objects in separate directory 2019-09-18 03:43:37 +03:00
Devaev Maxim
fcdb8f5d42 headers sanity 2019-09-17 19:45:56 +03:00
Devaev Maxim
e827ad8a7a refactoring 2019-09-17 17:16:59 +03:00
Devaev Maxim
caf5b9191b refactoring 2019-09-17 10:53:36 +03:00
Devaev Maxim
163aa8b5af refactoring 2019-09-17 10:28:46 +03:00
Devaev Maxim
bc873e31d0 refactoring 2019-09-17 10:21:33 +03:00
Devaev Maxim
947924e900 refactoring 2019-09-17 09:57:54 +03:00
Devaev Maxim
0827cb2b65 Issue #9: --last-as-blank accept timeout or 0 2019-09-17 09:50:14 +03:00
Devaev Maxim
d24d48212f refactoring 2019-09-17 03:19:44 +03:00
Devaev Maxim
a23475be57 thread names 2019-09-14 04:28:11 +03:00
Devaev Maxim
b7d8c5bfa6 Issue #9: Option to show last frame as blank 2019-09-13 03:22:17 +03:00
Devaev Maxim
433e884fad unified picture_t with api 2019-09-13 02:18:24 +03:00
Devaev Maxim
1d1c7c705d refactoring 2019-09-12 18:23:45 +03:00
Devaev Maxim
b02b0f910c Bump version: 1.3 → 1.4 2019-09-12 03:04:29 +03:00
Devaev Maxim
bfbb5dd29d show option choices on fail 2019-09-12 03:04:06 +03:00
Devaev Maxim
f3339f0502 fixed short options 2019-09-12 03:00:13 +03:00
Devaev Maxim
5d270b0029 improved help and messages 2019-09-12 02:45:02 +03:00
Devaev Maxim
dd5a5a079a Bump version: 1.2 → 1.3 2019-09-10 18:38:58 +03:00
Devaev Maxim
a3fcb01b7b renamed log color options 2019-09-10 18:38:49 +03:00
Devaev Maxim
afe5b91f63 Bump version: 1.1 → 1.2 2019-09-10 18:23:00 +03:00
Devaev Maxim
330641ee9f removed legacy --with/--height (with fake-) 2019-09-06 23:28:31 +03:00
Devaev Maxim
d8ec5169e7 refactoring 2019-09-06 22:44:26 +03:00
Devaev Maxim
93d5be6ffc color logging 2019-09-06 22:26:15 +03:00
Devaev Maxim
2dbaf08eb0 refactoring 2019-09-06 02:00:09 +03:00
Devaev Maxim
9a216153dc enum log_level 2019-09-06 01:48:53 +03:00
Devaev Maxim
090ed174af enum for short opts 2019-09-05 22:07:01 +03:00
Devaev Maxim
4292c8a2d1 moved options parser to separate file 2019-09-05 22:06:37 +03:00
Devaev Maxim
856dab30bc refactoring 2019-09-05 16:25:37 +03:00
64 changed files with 2882 additions and 1188 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 1.1
current_version = 2.0
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
serialize =
{major}.{minor}
@@ -17,4 +17,3 @@ replace = pkgver={new_version}
[bumpversion:file:pkg/openwrt/Makefile]
search = PKG_VERSION:={current_version}
replace = PKG_VERSION:={new_version}

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
# Ignore everything
*
# Allow source code
!Makefile
!src/**

4
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
# These are supported funding model platforms
patreon: pikvm
custom: https://www.paypal.me/mdevaev

20
.github/workflows/dockerimage.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Building linters image ...
run: make linters
- name: Running linters ...
run: make tox

5
.gitignore vendored
View File

@@ -1,9 +1,12 @@
/linters/.tox/
/linters/.mypy_cache/
/pkg/arch/pkg/
/pkg/arch/src/
/pkg/arch/v*.tar.gz
/pkg/arch/ustreamer-*.pkg.tar.xz
/pkg/arch/ustreamer-*.pkg.tar.zst
/build/
/config.mk
/vgcore.*
/ustreamer
/*.sock
*.o

View File

@@ -11,11 +11,15 @@ LDFLAGS ?=
RPI_VC_HEADERS ?= /opt/vc/include
RPI_VC_LIBS ?= /opt/vc/lib
BUILD ?= build
LINTERS_IMAGE ?= $(PROG)-linters
# =====
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
_LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
override CFLAGS += -c -std=c11 -Wall -Wextra -D_GNU_SOURCE
SOURCES = $(shell ls src/*.c src/http/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
_SRCS = $(shell ls src/*.c src/http/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
define optbool
@@ -24,20 +28,36 @@ endef
ifneq ($(call optbool,$(WITH_OMX)),)
LIBS += -lbcm_host -lvcos -lopenmaxil -L$(RPI_VC_LIBS)
_LIBS += -lbcm_host -lvcos -lopenmaxil -L$(RPI_VC_LIBS)
override CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
SOURCES += $(shell ls src/encoders/omx/*.c)
_SRCS += $(shell ls src/encoders/omx/*.c)
endif
ifneq ($(call optbool,$(WITH_GPIO)),)
LIBS += -lwiringPi
_LIBS += -lgpiod
override CFLAGS += -DWITH_GPIO
_SRCS += $(shell ls src/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)
_LIBS += -lbsd
endif
override CFLAGS += -DWITH_SETPROCTITLE
endif
# =====
all: $(SOURCES) $(PROG)
all: $(PROG)
install: $(PROG)
@@ -57,29 +77,49 @@ regen:
tools/make-html-h.py src/http/data/index.html src/http/data/index_html.h INDEX
$(PROG): $(SOURCES:.c=.o)
$(info -- LINKING $@)
@ $(CC) $(SOURCES:.c=.o) -o $@ $(LDFLAGS) $(LIBS)
$(PROG): $(_SRCS:%.c=$(BUILD)/%.o)
$(info -- LD $@)
@ $(CC) $^ -o $@ $(LDFLAGS) $(_LIBS)
$(info ===== Build complete =====)
$(info == CC = $(CC))
$(info == LIBS = $(LIBS))
$(info == LIBS = $(_LIBS))
$(info == CFLAGS = $(CFLAGS))
$(info == LDFLAGS = $(LDFLAGS))
.c.o:
$(BUILD)/%.o: %.c
$(info -- CC $<)
@ $(CC) $< -o $@ $(CFLAGS) $(LIBS)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(CFLAGS)
release:
make clean
make tox
make push
make bump
make bump V=$(V)
make push
make clean
tox: linters
time docker run --rm \
--volume `pwd`:/src:ro \
--volume `pwd`/linters:/src/linters:rw \
-t $(LINTERS_IMAGE) bash -c " \
cd /src \
&& tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \
"
linters:
docker build \
$(if $(call optbool,$(NC)),--no-cache,) \
--rm \
--tag $(LINTERS_IMAGE) \
-f linters/Dockerfile linters
bump:
bumpversion $(if $(V),$(V),minor)
@@ -89,8 +129,12 @@ push:
git push --tags
clean-all: clean
clean-all: linters clean
- docker run --rm \
--volume `pwd`:/src \
-it $(LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
clean:
find src -name '*.o' -exec rm '{}' \;
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.xz
rm -f vgcore.* *.sock $(PROG)
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
rm -rf $(PROG) $(BUILD) vgcore.* *.sock
.PHONY: linters

View File

@@ -1,8 +1,11 @@
# µStreamer
[![CI](https://github.com/pikvm/ustreamer/workflows/CI/badge.svg)](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
[![Discord](https://img.shields.io/discord/580094191938437144?logo=discord)](https://discord.gg/bpmXfz5)
[[Русская версия]](README.ru.md)
µStreamer is a lightweight and very quick server to broadcast [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
µStreamer is a part of the [Pi-KVM](https://github.com/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
µStreamer is a lightweight and very quick server to stream [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
µStreamer is a part of the [Pi-KVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
µStreamer is very similar to [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) with ```input_uvc.so``` and ```output_http.so``` plugins, however, there are some major differences. The key ones are:
@@ -10,19 +13,19 @@
|----------|---------------|-------------------|
| Multithreaded JPEG encoding | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Behavior when the device<br>is disconnected while streaming | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Stops the broadcast <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save traffic | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Behavior when the device<br>is disconnected while streaming | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) 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 | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save the traffic | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Streaming via UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Option to serve files<br>with a built-in HTTP server | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Regular files only |
| Signaling about the stream state to GPIO<br>on Raspberry Pi using [wiringPi](http://wiringpi.com) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
Footnotes:
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
* ```2``` This feature allows to cut down outgoing traffic several-fold when broadcasting HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to broadcast the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
* ```2``` This feature allows to cut down outgoing traffic several-fold when streaming HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to stream the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
-----
# TL;DR
@@ -30,9 +33,13 @@ If you're going to live-stream from your backyard webcam and need to control it,
-----
# Building
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libuuid```.
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` and ```libbsd``` (only for Linux).
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 [wiringPi](http://wiringpi.com) and pass option ```WITH_GPIO=1```.
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `libgpiod` for `WITH_GPIO=1`.
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
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```.
```
$ git clone --depth=1 https://github.com/pikvm/ustreamer
@@ -41,16 +48,18 @@ $ make
$ ./ustreamer --help
```
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```. Same with GPIO.
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
-----
# Usage
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start broadcasting on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to broadcast to the world, run:
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start streaming on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to stream to the world, run:
```
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
```
:exclamation: Please note that since µStreamer v2.0 cross-domain requests were disabled by default for [security reasons](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To enable the old behavior, use the option `--allow-origin=\*`.
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
```bash
$ ./ustreamer \
@@ -59,11 +68,18 @@ $ ./ustreamer \
--workers=3 \ # Maximum workers for OpenMAX
--persistent \ # Don't re-initialize device on timeout (for example when HDMI cable was disconnected)
--dv-timings \ # Use DV-timings
--drop-same-frames=30 # Save that traffic
--drop-same-frames=30 # Save the traffic
```
:exclamation: Please note that to use `--drop-same-frames` for different browsers you need to use some specific URL `/stream` parameters (see URL `/` for details).
You can always view the full list of options with ```ustreamer --help```.
-----
# See also
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Use [Ansible](https://docs.ansible.com/ansible/latest/index.html) to compile uStreamer and install it as a systemd service automatically.
-----
# License
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com

View File

@@ -1,8 +1,11 @@
# µStreamer
# µStreamer
[![CI](https://github.com/pikvm/ustreamer/workflows/CI/badge.svg)](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
[![Discord](https://img.shields.io/discord/580094191938437144?logo=discord)](https://discord.gg/bpmXfz5)
[[English version]](README.md)
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pikvm/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
@@ -11,12 +14,12 @@
| Многопоточное кодирование JPEG | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении<br>устройства от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Прерывает трансляцию <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Стрим через UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Возможность сервить файлы встроенным<br>HTTP-сервером | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Нет каталогов |
| Вывод сигналов о состоянии стрима на GPIO<br>на Raspberry Pi с помощью [wiringPi](http://wiringpi.com) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
Сносочки:
@@ -30,9 +33,13 @@
-----
# Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux).
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [wiringPi](http://wiringpi.com) и добавьте параметр ```WITH_GPIO=1```.
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `libgpiod` для `WITH_GPIO=1`.
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
На 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```.
```
$ git clone --depth=1 https://github.com/pikvm/ustreamer
@@ -41,7 +48,7 @@ $ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```. То же самое и с GPIO.
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
-----
@@ -51,6 +58,8 @@ $ ./ustreamer --help
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
```
:exclamation: Обратите внимание, что начиная с версии µStreamer v2.0 кросс-доменные запросы были выключены по умолчанию [по соображениям безопасности](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). Чтобы включить старое поведение, используйте опцию `--allow-origin=\*`.
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
```bash
$ ./ustreamer \
@@ -62,8 +71,15 @@ $ ./ustreamer \
--drop-same-frames=30 # Экономим трафик
```
:exclamation: Обратите внимание, что для использования `--drop-same-frames` для разных браузеров нужно использовать ряд специальных параметров в `/stream` (за деталями обратитесь к урлу `/`).
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
-----
# Смотрите также
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Использование [Ansible](https://docs.ansible.com/ansible/latest/index.html) для сборки и установки стримера как systemd-сервиса.
-----
# Лицензия
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com

2
linters/.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
/.tox/
/.mypy_cache/

21
linters/Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
FROM archlinux/base
RUN echo "Server = http://mirror.yandex.ru/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist
RUN pacman -Syu --noconfirm \
&& pacman -S --needed --noconfirm \
base \
base-devel \
vim \
git \
libjpeg \
libevent \
libutil-linux \
libbsd \
python \
python-pip \
python-tox \
cppcheck \
&& (pacman -Sc --noconfirm || true)
CMD /bin/bash

9
linters/flake8.ini Normal file
View File

@@ -0,0 +1,9 @@
[flake8]
inline-quotes = double
max-line-length = 160
ignore = W503, E227, E241, E252, Q003
# W503 line break before binary operator
# E227 missing whitespace around bitwise or shift operator
# E241 multiple spaces after
# E252 missing whitespace around parameter equals
# Q003 Change outer quotes to avoid escaping inner quotes

5
linters/mypy.ini Normal file
View File

@@ -0,0 +1,5 @@
[mypy]
python_version = 3.8
ignore_missing_imports = true
disallow_untyped_defs = true
strict_optional = true

63
linters/pylint.ini Normal file
View File

@@ -0,0 +1,63 @@
[MASTER]
ignore = .git
[DESIGN]
min-public-methods = 0
max-args = 10
[TYPECHECK]
ignored-classes=
AioQueue,
[MESSAGES CONTROL]
disable =
file-ignored,
locally-disabled,
fixme,
missing-docstring,
no-init,
no-self-use,
superfluous-parens,
abstract-class-not-used,
abstract-class-little-used,
duplicate-code,
bad-continuation,
bad-whitespace,
star-args,
broad-except,
redundant-keyword-arg,
wrong-import-order,
too-many-ancestors,
no-else-return,
len-as-condition,
[REPORTS]
msg-template = {symbol} -- {path}:{line}({obj}): {msg}
[FORMAT]
max-line-length = 160
[BASIC]
# List of builtins function names that should not be used, separated by a comma
bad-functions =
# Good variable names which should always be accepted, separated by a comma
good-names = _, __, x, y, ws, make-html-h, make-jpeg-h
# Regular expression matching correct method names
method-rgx = [a-z_][a-z0-9_]{2,50}$
# Regular expression matching correct function names
function-rgx = [a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct module level names
const-rgx = ([a-zA-Z_][a-zA-Z0-9_]*)$
# Regular expression which should only match correct argument names
argument-rgx = [a-z_][a-z0-9_]{1,30}$
# Regular expression which should only match correct variable names
variable-rgx = [a-z_][a-z0-9_]{1,30}$
# Regular expression which should only match correct instance attribute names
attr-rgx = [a-z_][a-z0-9_]{1,30}$

46
linters/tox.ini Normal file
View File

@@ -0,0 +1,46 @@
[tox]
envlist = cppcheck, flake8, pylint, mypy, vulture
skipsdist = true
[testenv]
basepython = python3.8
changedir = /src
[testenv:cppcheck]
whitelist_externals = cppcheck
commands = cppcheck \
--force \
--std=c11 \
--error-exitcode=1 \
--quiet \
--enable=warning,unusedFunction,portability,performance,style \
--suppress=assignmentInAssert \
--suppress=variableScope \
--inline-suppr \
-DCHAR_BIT=8 \
src
[testenv:flake8]
whitelist_externals = bash
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py'
deps =
flake8
flake8-quotes
[testenv:pylint]
whitelist_externals = bash
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py'
deps =
pylint
[testenv:mypy]
whitelist_externals = bash
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py'
deps =
mypy
[testenv:vulture]
whitelist_externals = bash
commands = bash -c 'vulture tools/*.py'
deps =
vulture

View File

@@ -3,15 +3,14 @@
pkgname=ustreamer
pkgver=1.1
pkgver=2.0
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"
license=(GPL)
arch=(i686 x86_64 armv6h armv7h)
depends=(libjpeg libevent libutil-linux)
arch=(i686 x86_64 armv6h armv7h aarch64)
depends=(libjpeg libevent libutil-linux libbsd libgpiod)
# optional: raspberrypi-firmware for OMX encoder
# optional: wiringpi for GPIO support
makedepends=(gcc make)
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP)
@@ -23,9 +22,8 @@ build() {
cp -r $pkgname $pkgname-build
cd $pkgname-build
local _options=""
local _options="WITH_GPIO=1"
[ -e /opt/vc/include/IL/OMX_Core.h ] && _options="$_options WITH_OMX=1"
[ -e /usr/include/wiringPi.h ] && _options="$_options WITH_GPIO=1"
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
}

View File

@@ -0,0 +1,41 @@
FROM balenalib/raspberrypi3-debian:build as build
RUN ["cross-build-start"]
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc \
libjpeg8-dev \
libbsd-dev \
libraspberrypi-dev \
libgpiod-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build/ustreamer/
COPY . .
RUN make -j5 WITH_OMX=1 WITH_GPIO=1
RUN ["cross-build-end"]
FROM balenalib/raspberrypi3-debian:run as RUN
RUN ["cross-build-start"]
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libevent-2.1 \
libevent-pthreads-2.1-6 \
libjpeg8 \
uuid \
libbsd0 \
libgpiod2 \
&& rm -rf /var/lib/apt/lists/*
RUN ["cross-build-end"]
WORKDIR /ustreamer
COPY --from=build /build/ustreamer/ustreamer .
EXPOSE 8080
ENTRYPOINT ["./ustreamer", "--host=::"]
# vim: syntax=dockerfile

View File

@@ -0,0 +1,34 @@
FROM balenalib/raspberrypi3-debian:build as build
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc \
libjpeg8-dev \
libbsd-dev \
libraspberrypi-dev \
libgpiod-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build/ustreamer/
COPY . .
RUN make -j5 WITH_OMX=1 WITH_GPIO=1
FROM balenalib/raspberrypi3-debian:run as RUN
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libevent-2.1 \
libevent-pthreads-2.1-6 \
libjpeg8 \
uuid \
libbsd0 \
libgpiod2 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /ustreamer
COPY --from=build /build/ustreamer/ustreamer .
EXPOSE 8080
ENTRYPOINT ["./ustreamer", "--host=::"]
# vim: syntax=dockerfile

View File

@@ -0,0 +1,40 @@
FROM debian:buster-slim as build
RUN apt-get update \
&& apt-get install -y \
ca-certificates \
make \
gcc \
git \
libevent-dev \
libjpeg62-turbo-dev \
uuid-dev \
libbsd-dev \
libgpiod-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build/ustreamer/
COPY . .
RUN make -j5 WITH_GPIO=1
FROM debian:buster-slim as run
RUN apt-get update \
&& apt-get install -y \
ca-certificates \
libevent-2.1 \
libevent-pthreads-2.1-6 \
libjpeg62-turbo \
uuid \
libbsd0 \
libgpiod2 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /ustreamer
COPY --from=build /build/ustreamer/ustreamer .
#ENV LD_LIBRARY_PATH=/opt/vc/lib
EXPOSE 8080
ENTRYPOINT ["./ustreamer", "--host=0.0.0.0"]
# vim: syntax=dockerfile

View File

@@ -18,6 +18,7 @@ DEPEND="
>=dev-libs/libevent-2.1.8
>=media-libs/libjpeg-turbo-1.5.3
>=sys-apps/util-linux-2.33
>=dev-libs/libbsd-0.9.1
"
RDEPEND="${DEPEND}"
BDEPEND=""

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=1.1
PKG_VERSION:=2.0
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
@@ -25,7 +25,7 @@ define Package/ustreamer
SECTION:=multimedia
CATEGORY:=Multimedia
TITLE:=uStreamer
DEPENDS:=+libpthread +libjpeg +libv4l +libuuid +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
DEPENDS:=+libpthread +libjpeg +libv4l +libuuid +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
URL:=https://github.com/pikvm/ustreamer
endef

View File

@@ -23,5 +23,5 @@
#pragma once
#ifndef VERSION
# define VERSION "1.1"
# define VERSION "2.0"
#endif

View File

@@ -34,11 +34,16 @@
#include <sys/select.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "tools.h"
#include "logging.h"
#include "threading.h"
#include "xioctl.h"
#include "picture.h"
static const struct {
@@ -63,24 +68,36 @@ static const struct {
{"JPEG", V4L2_PIX_FMT_JPEG},
};
static const struct {
const char *name;
const enum v4l2_memory io_method;
} _IO_METHODS[] = {
{"MMAP", V4L2_MEMORY_MMAP},
{"USERPTR", V4L2_MEMORY_USERPTR},
};
static int _device_open_check_cap(struct device_t *dev);
static int _device_open_dv_timings(struct device_t *dev);
static int _device_apply_dv_timings(struct device_t *dev);
static int _device_open_format(struct device_t *dev);
static void _device_open_hw_fps(struct device_t *dev);
static int _device_open_mmap(struct device_t *dev);
static int _device_open_io_method(struct device_t *dev);
static int _device_open_io_method_mmap(struct device_t *dev);
static int _device_open_io_method_userptr(struct device_t *dev);
static int _device_open_queue_buffers(struct device_t *dev);
static void _device_open_alloc_picbufs(struct device_t *dev);
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height);
static void _device_apply_controls(struct device_t *dev);
static int _device_check_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet);
static void _device_set_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet);
static int _device_query_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, bool quiet);
static void _device_set_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, int value, bool quiet);
static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned format);
static const char *_format_to_string_nullable(unsigned format);
static const char *_format_to_string_supported(unsigned format);
static const char *_standard_to_string(v4l2_std_id standard);
static const char *_io_method_to_string_supported(enum v4l2_memory io_method);
struct device_t *device_init(void) {
@@ -104,8 +121,10 @@ struct device_t *device_init(void) {
dev->standard = V4L2_STD_UNKNOWN;
dev->n_buffers = cores_available + 1;
dev->n_workers = min_u(cores_available, dev->n_buffers);
dev->min_frame_size = 128;
dev->timeout = 1;
dev->error_delay = 1;
dev->io_method = V4L2_MEMORY_MMAP;
dev->run = run;
return dev;
}
@@ -133,6 +152,15 @@ v4l2_std_id device_parse_standard(const char *str) {
return STANDARD_UNKNOWN;
}
int device_parse_io_method(const char *str) {
for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) {
if (!strcasecmp(str, _IO_METHODS[index].name)) {
return _IO_METHODS[index].io_method;
}
}
return IO_METHOD_UNKNOWN;
}
int device_open(struct device_t *dev) {
if ((dev->run->fd = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
LOG_PERROR("Can't open device");
@@ -150,7 +178,7 @@ int device_open(struct device_t *dev) {
goto error;
}
_device_open_hw_fps(dev);
if (_device_open_mmap(dev) < 0) {
if (_device_open_io_method(dev) < 0) {
goto error;
}
if (_device_open_queue_buffers(dev) < 0) {
@@ -174,24 +202,30 @@ void device_close(struct device_t *dev) {
if (dev->run->pictures) {
LOG_DEBUG("Releasing picture buffers ...");
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index].data; ++index) {
free(dev->run->pictures[index].data);
dev->run->pictures[index].data = NULL;
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
picture_destroy(dev->run->pictures[index]);
}
free(dev->run->pictures);
dev->run->pictures = NULL;
}
if (dev->run->hw_buffers) {
LOG_DEBUG("Unmapping HW buffers ...");
LOG_DEBUG("Releasing device buffers ...");
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
if (HW_BUFFER(allocated) > 0 && HW_BUFFER(data) != MAP_FAILED) {
if (munmap(HW_BUFFER(data), HW_BUFFER(allocated)) < 0) {
LOG_PERROR("Can't unmap device buffer %u", index);
if (dev->io_method == V4L2_MEMORY_MMAP) {
if (HW_BUFFER(allocated) > 0 && HW_BUFFER(data) != MAP_FAILED) {
if (munmap(HW_BUFFER(data), HW_BUFFER(allocated)) < 0) {
LOG_PERROR("Can't unmap device buffer %u", index);
}
}
} else { // V4L2_MEMORY_USERPTR
if (HW_BUFFER(data)) {
free(HW_BUFFER(data));
}
}
A_MUTEX_DESTROY(&HW_BUFFER(grabbed_mutex));
# undef HW_BUFFER
}
@@ -253,9 +287,9 @@ int device_select(struct device_t *dev, bool *has_read, bool *has_write, bool *h
*has_write = FD_ISSET(dev->run->fd, &write_fds);
*has_error = FD_ISSET(dev->run->fd, &error_fds);
} else {
has_read = false;
has_write = false;
has_error = false;
*has_read = false;
*has_write = false;
*has_error = false;
}
LOG_DEBUG("Device select() --> %d", retval);
@@ -267,33 +301,59 @@ int device_grab_buffer(struct device_t *dev) {
MEMSET_ZERO(buf_info);
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = V4L2_MEMORY_MMAP;
buf_info.memory = dev->io_method;
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
LOG_DEBUG("Grabbing device buffer ...");
if (xioctl(dev->run->fd, VIDIOC_DQBUF, &buf_info) < 0) {
LOG_PERROR("Unable to dequeue buffer");
LOG_PERROR("Unable to grab device buffer");
return -1;
}
LOG_DEBUG("Got a new frame in buffer: index=%u, bytesused=%u", buf_info.index, buf_info.bytesused);
LOG_DEBUG("Grabbed new frame in device buffer: index=%u, bytesused=%u",
buf_info.index, buf_info.bytesused);
if (buf_info.index >= dev->run->n_buffers) {
LOG_ERROR("Got invalid buffer: index=%u, nbuffers=%u", buf_info.index, dev->run->n_buffers);
LOG_ERROR("V4L2 error: grabbed invalid device buffer: index=%u, nbuffers=%u",
buf_info.index, dev->run->n_buffers);
return -1;
}
dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused;
memcpy(&dev->run->hw_buffers[buf_info.index].buf_info, &buf_info, sizeof(struct v4l2_buffer));
dev->run->pictures[buf_info.index].grab_time = get_now_monotonic();
# define HW_BUFFER(_next) dev->run->hw_buffers[buf_info.index]._next
A_MUTEX_LOCK(&HW_BUFFER(grabbed_mutex));
if (HW_BUFFER(grabbed)) {
LOG_ERROR("V4L2 error: grabbed device buffer is already used: index=%u, bytesused=%u",
buf_info.index, buf_info.bytesused);
A_MUTEX_UNLOCK(&HW_BUFFER(grabbed_mutex));
return -1;
}
HW_BUFFER(grabbed) = true;
A_MUTEX_UNLOCK(&HW_BUFFER(grabbed_mutex));
HW_BUFFER(used) = buf_info.bytesused;
memcpy(&HW_BUFFER(buf_info), &buf_info, sizeof(struct v4l2_buffer));
dev->run->pictures[buf_info.index]->grab_ts = get_now_monotonic();
# undef HW_BUFFER
return buf_info.index;
}
int device_release_buffer(struct device_t *dev, unsigned index) {
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_QBUF, &dev->run->hw_buffers[index].buf_info) < 0) {
LOG_PERROR("Unable to requeue buffer");
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
LOG_DEBUG("Releasing device buffer index=%u ...", index);
A_MUTEX_LOCK(&HW_BUFFER(grabbed_mutex));
if (xioctl(dev->run->fd, VIDIOC_QBUF, &HW_BUFFER(buf_info)) < 0) {
LOG_PERROR("Unable to release device buffer index=%u", index);
A_MUTEX_UNLOCK(&HW_BUFFER(grabbed_mutex));
return -1;
}
dev->run->hw_buffers[index].used = 0;
HW_BUFFER(grabbed) = false;
A_MUTEX_UNLOCK(&HW_BUFFER(grabbed_mutex));
HW_BUFFER(used) = 0;
# undef HW_BUFFER
return 0;
}
@@ -387,7 +447,7 @@ static int _device_apply_dv_timings(struct device_t *dev) {
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu",
dv.bt.width, dv.bt.height, dv.bt.pixelclock);
dv.bt.width, dv.bt.height, (unsigned long long)dv.bt.pixelclock); // Issue #11
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv) < 0) {
@@ -461,12 +521,16 @@ static int _device_open_format(struct device_t *dev) {
dev->run->format = fmt.fmt.pix.pixelformat;
LOG_INFO("Using pixelformat: %s", _format_to_string_supported(dev->run->format));
dev->run->raw_size = fmt.fmt.pix.sizeimage; // Only for userptr
return 0;
}
static void _device_open_hw_fps(struct device_t *dev) {
struct v4l2_streamparm setfps;
dev->run->hw_fps = 0;
MEMSET_ZERO(setfps);
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
@@ -497,16 +561,37 @@ static void _device_open_hw_fps(struct device_t *dev) {
return;
}
if (dev->desired_fps != SETFPS_TPF(denominator)) {
LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, SETFPS_TPF(denominator));
if (SETFPS_TPF(numerator) != 1) {
LOG_ERROR("Invalid HW FPS numerator: %u != 1", SETFPS_TPF(numerator));
return;
}
if (SETFPS_TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай
LOG_ERROR("Invalid HW FPS denominator: 0");
return;
}
dev->run->hw_fps = SETFPS_TPF(denominator);
if (dev->desired_fps != dev->run->hw_fps) {
LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, dev->run->hw_fps);
} else {
LOG_INFO("Using HW FPS: %u", dev->desired_fps);
LOG_INFO("Using HW FPS: %u", dev->run->hw_fps);
}
# undef SETFPS_TPF
}
static int _device_open_mmap(struct device_t *dev) {
static int _device_open_io_method(struct device_t *dev) {
LOG_INFO("Using IO method: %s", _io_method_to_string_supported(dev->io_method));
switch (dev->io_method) {
case V4L2_MEMORY_MMAP: return _device_open_io_method_mmap(dev);
case V4L2_MEMORY_USERPTR: return _device_open_io_method_userptr(dev);
default: assert(0 && "Unsupported IO method");
}
return -1;
}
static int _device_open_io_method_mmap(struct device_t *dev) {
struct v4l2_requestbuffers req;
MEMSET_ZERO(req);
@@ -514,9 +599,9 @@ static int _device_open_mmap(struct device_t *dev) {
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) ...");
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_MMAP ...");
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req) < 0) {
LOG_PERROR("Device '%s' doesn't support memory mapping", dev->path);
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_MMAP", dev->path);
return -1;
}
@@ -524,10 +609,10 @@ static int _device_open_mmap(struct device_t *dev) {
LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
LOG_INFO("Requested %u HW buffers, got %u", dev->n_buffers, req.count);
LOG_INFO("Requested %u device buffers, got %u", dev->n_buffers, req.count);
}
LOG_DEBUG("Allocating HW buffers ...");
LOG_DEBUG("Allocating device buffers ...");
A_CALLOC(dev->run->hw_buffers, req.count);
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
@@ -546,6 +631,8 @@ static int _device_open_mmap(struct device_t *dev) {
# define HW_BUFFER(_next) dev->run->hw_buffers[dev->run->n_buffers]._next
A_MUTEX_INIT(&HW_BUFFER(grabbed_mutex));
LOG_DEBUG("Mapping device buffer %u ...", dev->run->n_buffers);
HW_BUFFER(data) = mmap(NULL, buf_info.length, PROT_READ|PROT_WRITE, MAP_SHARED, dev->run->fd, buf_info.m.offset);
if (HW_BUFFER(data) == MAP_FAILED) {
@@ -559,14 +646,56 @@ static int _device_open_mmap(struct device_t *dev) {
return 0;
}
static int _device_open_io_method_userptr(struct device_t *dev) {
struct v4l2_requestbuffers req;
unsigned page_size = getpagesize();
unsigned buf_size = align_size(dev->run->raw_size, page_size);
MEMSET_ZERO(req);
req.count = dev->n_buffers;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_USERPTR;
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_USERPTR ...");
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req) < 0) {
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_USERPTR", dev->path);
return -1;
}
if (req.count < 1) {
LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
LOG_INFO("Requested %u device buffers, got %u", dev->n_buffers, req.count);
}
LOG_DEBUG("Allocating device buffers ...");
A_CALLOC(dev->run->hw_buffers, req.count);
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
# define HW_BUFFER(_next) dev->run->hw_buffers[dev->run->n_buffers]._next
assert(HW_BUFFER(data) = aligned_alloc(page_size, buf_size));
memset(HW_BUFFER(data), 0, buf_size);
HW_BUFFER(allocated) = buf_size;
# undef HW_BUFFER
}
return 0;
}
static int _device_open_queue_buffers(struct device_t *dev) {
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
struct v4l2_buffer buf_info;
MEMSET_ZERO(buf_info);
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = V4L2_MEMORY_MMAP;
buf_info.memory = dev->io_method;
buf_info.index = index;
if (dev->io_method == V4L2_MEMORY_USERPTR) {
buf_info.m.userptr = (unsigned long)dev->run->hw_buffers[index].data;
buf_info.length = dev->run->hw_buffers[index].allocated;
}
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %u ...", index);
if (xioctl(dev->run->fd, VIDIOC_QBUF, &buf_info) < 0) {
@@ -578,14 +707,15 @@ static int _device_open_queue_buffers(struct device_t *dev) {
}
static void _device_open_alloc_picbufs(struct device_t *dev) {
size_t picture_size = picture_get_generous_size(dev->run->width, dev->run->height);
LOG_DEBUG("Allocating picture buffers ...");
A_CALLOC(dev->run->pictures, dev->run->n_buffers);
dev->run->max_raw_image_size = ((dev->run->width * dev->run->height) << 1) * 2;
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
LOG_DEBUG("Allocating picture buffer %u sized %zu bytes... ", index, dev->run->max_raw_image_size);
A_CALLOC(dev->run->pictures[index].data, dev->run->max_raw_image_size);
dev->run->pictures[index].allocated = dev->run->max_raw_image_size;
dev->run->pictures[index] = picture_init();
LOG_DEBUG("Pre-allocating picture buffer %u sized %zu bytes... ", index, picture_size);
picture_realloc_data(dev->run->pictures[index], picture_size);
}
}
@@ -606,65 +736,85 @@ static int _device_apply_resolution(struct device_t *dev, unsigned width, unsign
}
static void _device_apply_controls(struct device_t *dev) {
# define SET_CID(_cid, _dest, _value, _quiet) { \
if (_device_check_control(dev, #_dest, _cid, _value, _quiet) == 0) { \
_device_set_control(dev, #_dest, _cid, _value, _quiet); \
# define SET_CID_VALUE(_cid, _field, _value, _quiet) { \
struct v4l2_queryctrl query; \
if (_device_query_control(dev, &query, #_field, _cid, _quiet) == 0) { \
_device_set_control(dev, &query, #_field, _cid, _value, _quiet); \
} \
}
# define SET_CID_MANUAL(_cid, _dest) { \
if (dev->ctl._dest.value_set) { \
SET_CID(_cid, _dest, dev->ctl._dest.value, false); \
# define SET_CID_DEFAULT(_cid, _field, _quiet) { \
struct v4l2_queryctrl query; \
if (_device_query_control(dev, &query, #_field, _cid, _quiet) == 0) { \
_device_set_control(dev, &query, #_field, _cid, query.default_value, _quiet); \
} \
}
# define SET_CID_AUTO(_cid_auto, _cid_manual, _dest) { \
if (dev->ctl._dest.value_set || dev->ctl._dest.auto_set) { \
SET_CID(_cid_auto, _dest##_auto, dev->ctl._dest.auto_set, dev->ctl._dest.value_set); \
SET_CID_MANUAL(_cid_manual, _dest); \
# define CONTROL_MANUAL_CID(_cid, _field) { \
if (dev->ctl._field.mode == CTL_MODE_VALUE) { \
SET_CID_VALUE(_cid, _field, dev->ctl._field.value, false); \
} else if (dev->ctl._field.mode == CTL_MODE_DEFAULT) { \
SET_CID_DEFAULT(_cid, _field, false); \
} \
}
SET_CID_AUTO (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness);
SET_CID_MANUAL ( V4L2_CID_CONTRAST, contrast);
SET_CID_MANUAL ( V4L2_CID_SATURATION, saturation);
SET_CID_AUTO (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue);
SET_CID_MANUAL ( V4L2_CID_GAMMA, gamma);
SET_CID_MANUAL ( V4L2_CID_SHARPNESS, sharpness);
SET_CID_MANUAL ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
SET_CID_AUTO (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
SET_CID_AUTO (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
# define CONTROL_AUTO_CID(_cid_auto, _cid_manual, _field) { \
if (dev->ctl._field.mode == CTL_MODE_VALUE) { \
SET_CID_VALUE(_cid_auto, _field##_auto, 0, true); \
SET_CID_VALUE(_cid_manual, _field, dev->ctl._field.value, false); \
} else if (dev->ctl._field.mode == CTL_MODE_AUTO) { \
SET_CID_VALUE(_cid_auto, _field##_auto, 1, false); \
} else if (dev->ctl._field.mode == CTL_MODE_DEFAULT) { \
SET_CID_VALUE(_cid_auto, _field##_auto, 0, true); /* Reset inactive flag */ \
SET_CID_DEFAULT(_cid_manual, _field, false); \
SET_CID_DEFAULT(_cid_auto, _field##_auto, false); \
} \
}
# undef SET_CID_AUTO
# undef SET_CID_MANUAL
# undef SET_CID
CONTROL_AUTO_CID (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness);
CONTROL_MANUAL_CID ( V4L2_CID_CONTRAST, contrast);
CONTROL_MANUAL_CID ( V4L2_CID_SATURATION, saturation);
CONTROL_AUTO_CID (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue);
CONTROL_MANUAL_CID ( V4L2_CID_GAMMA, gamma);
CONTROL_MANUAL_CID ( V4L2_CID_SHARPNESS, sharpness);
CONTROL_MANUAL_CID ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
CONTROL_AUTO_CID (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
CONTROL_AUTO_CID (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
CONTROL_MANUAL_CID ( V4L2_CID_COLORFX, color_effect);
CONTROL_MANUAL_CID ( V4L2_CID_VFLIP, flip_vertical);
CONTROL_MANUAL_CID ( V4L2_CID_HFLIP, flip_horizontal);
# undef CONTROL_AUTO_CID
# undef CONTROL_MANUAL_CID
# undef SET_CID_DEFAULT
# undef SET_CID_VALUE
}
static int _device_check_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet) {
struct v4l2_queryctrl query;
static int _device_query_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, bool quiet) {
// cppcheck-suppress redundantPointerOp
MEMSET_ZERO(*query);
query->id = cid;
MEMSET_ZERO(query);
query.id = cid;
if (xioctl(dev->run->fd, VIDIOC_QUERYCTRL, &query) < 0 || query.flags & V4L2_CTRL_FLAG_DISABLED) {
if (xioctl(dev->run->fd, VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
if (!quiet) {
LOG_ERROR("Changing control %s is unsupported", name);
}
return -1;
}
if (value < query.minimum || value > query.maximum || value % query.step != 0) {
if (!quiet) {
LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
value, name, query.minimum, query.maximum, query.default_value, query.step);
}
return -2;
}
return 0;
}
static void _device_set_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet) {
static void _device_set_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, int value, bool quiet) {
struct v4l2_control ctl;
if (value < query->minimum || value > query->maximum || value % query->step != 0) {
if (!quiet) {
LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
value, name, query->minimum, query->maximum, query->default_value, query->step);
}
return;
}
MEMSET_ZERO(ctl);
ctl.id = cid;
ctl.value = value;
@@ -674,7 +824,7 @@ static void _device_set_control(struct device_t *dev, const char *name, unsigned
LOG_PERROR("Can't set control %s", name);
}
} else if (!quiet) {
LOG_INFO("Using control %s: %d", name, ctl.value);
LOG_INFO("Applying control %s: %d", name, ctl.value);
}
}
@@ -684,7 +834,7 @@ static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned for
buf[1] = (format >> 8) & 0x7F;
buf[2] = (format >> 16) & 0x7F;
buf[3] = (format >> 24) & 0x7F;
if (format & (1 << 31)) {
if (format & ((unsigned)1 << 31)) {
buf[4] = '-';
buf[5] = 'B';
buf[6] = 'E';
@@ -717,3 +867,12 @@ static const char *_standard_to_string(v4l2_std_id standard) {
}
return _STANDARDS[0].name;
}
static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) {
if (io_method == _IO_METHODS[index].io_method) {
return _IO_METHODS[index].name;
}
}
return "unsupported";
}

View File

@@ -25,16 +25,19 @@
#include <stddef.h>
#include <stdbool.h>
#include <pthread.h>
#include <linux/videodev2.h>
#include "picture.h"
#define VIDEO_MIN_WIDTH 160
#define VIDEO_MAX_WIDTH 10240
#define VIDEO_MIN_HEIGHT 120
#define VIDEO_MAX_HEIGHT 4320
#define VIDEO_MIN_WIDTH ((unsigned)160)
#define VIDEO_MAX_WIDTH ((unsigned)10240)
#define VIDEO_MAX_FPS 120
#define VIDEO_MIN_HEIGHT ((unsigned)120)
#define VIDEO_MAX_HEIGHT ((unsigned)4320)
#define VIDEO_MAX_FPS ((unsigned)120)
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define STANDARDS_STR "PAL, NTSC, SECAM"
@@ -42,21 +45,18 @@
#define FORMAT_UNKNOWN -1
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, JPEG"
#define IO_METHOD_UNKNOWN -1
#define IO_METHODS_STR "MMAP, USERPTR"
struct hw_buffer_t {
unsigned char *data;
size_t used;
size_t allocated;
struct v4l2_buffer buf_info;
};
struct picture_t {
unsigned char *data;
size_t used;
size_t allocated;
long double grab_time;
long double encode_begin_time;
long double encode_end_time;
pthread_mutex_t grabbed_mutex;
bool grabbed;
};
struct device_runtime_t {
@@ -64,18 +64,25 @@ struct device_runtime_t {
unsigned width;
unsigned height;
unsigned format;
unsigned hw_fps;
size_t raw_size;
unsigned n_buffers;
unsigned n_workers;
struct hw_buffer_t *hw_buffers;
struct picture_t *pictures;
size_t max_raw_image_size;
struct picture_t **pictures;
bool capturing;
};
enum control_mode_t {
CTL_MODE_NONE = 0,
CTL_MODE_VALUE,
CTL_MODE_AUTO,
CTL_MODE_DEFAULT,
};
struct control_t {
int value;
bool value_set;
bool auto_set;
enum control_mode_t mode;
int value;
};
struct controls_t {
@@ -88,6 +95,9 @@ struct controls_t {
struct control_t backlight_compensation;
struct control_t white_balance;
struct control_t gain;
struct control_t color_effect;
struct control_t flip_vertical;
struct control_t flip_horizontal;
};
struct device_t {
@@ -97,6 +107,7 @@ struct device_t {
unsigned height;
unsigned format;
v4l2_std_id standard;
enum v4l2_memory io_method;
bool dv_timings;
unsigned n_buffers;
unsigned n_workers;
@@ -117,6 +128,7 @@ void device_destroy(struct device_t *dev);
int device_parse_format(const char *str);
v4l2_std_id device_parse_standard(const char *str);
int device_parse_io_method(const char *str);
int device_open(struct device_t *dev);
void device_close(struct device_t *dev);

View File

@@ -22,6 +22,7 @@
#include "encoder.h"
#include <stdlib.h>
#include <stdbool.h>
#include <strings.h>
#include <assert.h>
@@ -29,12 +30,12 @@
#include <linux/videodev2.h>
#include "tools.h"
#include "threading.h"
#include "logging.h"
#include "device.h"
#include "encoders/cpu/encoder.h"
#include "encoders/hw/encoder.h"
#ifdef WITH_OMX
# include "encoders/omx/encoder.h"
#endif
@@ -169,6 +170,7 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
// cppcheck-suppress unusedLabel
force_cpu:
cpu_forced = true;
# pragma GCC diagnostic pop
@@ -199,28 +201,36 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
#pragma GCC diagnostic pop
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
assert(dev->run->hw_buffers[buf_index].used > 0);
dev->run->pictures[buf_index].encode_begin_time = get_now_monotonic();
dev->run->pictures[buf_index]->encode_begin_ts = get_now_monotonic();
if (encoder->run->type == ENCODER_TYPE_CPU) {
LOG_VERBOSE("Compressing buffer %u using CPU", buf_index);
cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality);
} else if (encoder->run->type == ENCODER_TYPE_HW) {
LOG_VERBOSE("Compressing buffer %u using HW (just copying)", buf_index);
hw_encoder_compress_buffer(dev, buf_index);
}
# ifdef WITH_OMX
else if (encoder->run->type == ENCODER_TYPE_OMX) {
LOG_VERBOSE("Compressing buffer %u using OMX", buf_index);
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
goto error;
}
}
# endif
dev->run->pictures[buf_index].encode_end_time = get_now_monotonic();
dev->run->pictures[buf_index]->encode_end_ts = get_now_monotonic();
dev->run->pictures[buf_index]->width = dev->run->width;
dev->run->pictures[buf_index]->height = dev->run->height;
return 0;
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
// cppcheck-suppress unusedLabel
error:
LOG_INFO("Error while compressing buffer, falling back to CPU");
A_MUTEX_LOCK(&encoder->run->mutex);

View File

@@ -24,9 +24,8 @@
#include <stdbool.h>
#include "pthread.h"
#include <pthread.h>
#include "tools.h"
#include "device.h"
#ifdef WITH_OMX
@@ -34,9 +33,10 @@
# define ENCODER_TYPES_OMX_HINT ", OMX"
# ifndef MAX_GLITCHED_RESOLUTIONS
# define MAX_GLITCHED_RESOLUTIONS 1024
# ifndef CFG_MAX_GLITCHED_RESOLUTIONS
# define CFG_MAX_GLITCHED_RESOLUTIONS 1024
# endif
# define MAX_GLITCHED_RESOLUTIONS ((unsigned)(CFG_MAX_GLITCHED_RESOLUTIONS))
#else
# define ENCODER_TYPES_OMX_HINT ""
#endif

View File

@@ -29,13 +29,14 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "../../tools.h"
#include "../../picture.h"
#include "../../device.h"
@@ -43,7 +44,6 @@ struct _jpeg_dest_manager_t {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buffer; // Start of buffer
struct picture_t *picture;
unsigned char *picture_data_cursor;
};
@@ -79,7 +79,7 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
jpeg.err = jpeg_std_error(&jpeg_error);
jpeg_create_compress(&jpeg);
_jpeg_set_picture(&jpeg, &dev->run->pictures[index]);
_jpeg_set_picture(&jpeg, dev->run->pictures[index]);
jpeg.image_width = dev->run->width;
jpeg.image_height = dev->run->height;
@@ -108,7 +108,7 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);
assert(dev->run->pictures[index].used > 0);
assert(dev->run->pictures[index]->used > 0);
}
static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture) {
@@ -125,7 +125,6 @@ static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture) {
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
dest->mgr.term_destination = _jpeg_term_destination;
dest->picture = picture;
dest->picture_data_cursor = picture->data;
picture->used = 0;
}
@@ -259,7 +258,7 @@ static void _jpeg_write_scanlines_rgb24(
}
}
#define JPEG_OUTPUT_BUFFER_SIZE 4096
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
static void _jpeg_init_destination(j_compress_ptr jpeg) {
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
@@ -277,13 +276,8 @@ static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
// Called whenever local jpeg buffer fills up
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
size_t new_used = dest->picture->used + JPEG_OUTPUT_BUFFER_SIZE;
assert(new_used <= dest->picture->allocated);
memcpy(dest->picture_data_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
dest->picture_data_cursor += JPEG_OUTPUT_BUFFER_SIZE;
dest->picture->used = new_used;
picture_append_data(dest->picture, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
dest->mgr.next_output_byte = dest->buffer;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
@@ -293,18 +287,13 @@ static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
static void _jpeg_term_destination(j_compress_ptr jpeg) {
// Called by jpeg_finish_compress after all data has been written.
// Usually needs to flush buffer
// Usually needs to flush buffer.
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
size_t new_used = dest->picture->used + final;
assert(new_used <= dest->picture->allocated);
// Write any data remaining in the buffer
memcpy(dest->picture_data_cursor, dest->buffer, final);
dest->picture_data_cursor += final;
dest->picture->used = new_used;
// Write any data remaining in the buffer.
picture_append_data(dest->picture, dest->buffer, final);
}
#undef JPEG_OUTPUT_BUFFER_SIZE

View File

@@ -36,13 +36,14 @@
#include "../../tools.h"
#include "../../logging.h"
#include "../../xioctl.h"
#include "../../picture.h"
#include "../../device.h"
#include "huffman.h"
void _copy_plus_huffman(const struct hw_buffer_t *src, struct picture_t *dest);
static bool _is_huffman(const unsigned char *data);
static size_t _memcpy_with_huffman(unsigned char *dest, const unsigned char *src, size_t size);
int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
@@ -51,12 +52,12 @@ int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
MEMSET_ZERO(comp);
if (xioctl(dev->run->fd, VIDIOC_G_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Can't query HW encoder params and set quality (unsupported)");
LOG_ERROR("Device does not support setting of HW encoding quality parameters");
return -1;
}
comp.quality = quality;
if (xioctl(dev->run->fd, VIDIOC_S_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Can't set HW encoder quality (unsupported)");
LOG_ERROR("Unable to change MJPG quality for JPEG source with HW pass-through encoder");
return -1;
}
return 0;
@@ -66,15 +67,30 @@ void hw_encoder_compress_buffer(struct device_t *dev, unsigned index) {
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
assert(0 && "Unsupported input format for HW encoder");
}
_copy_plus_huffman(&dev->run->hw_buffers[index], dev->run->pictures[index]);
}
# define PICTURE(_next) dev->run->pictures[index]._next
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
void _copy_plus_huffman(const struct hw_buffer_t *src, struct picture_t *dest) {
if (!_is_huffman(src->data)) {
const unsigned char *src_ptr = src->data;
const unsigned char *src_end = src->data + src->used;
size_t paste;
assert(PICTURE(allocated) >= HW_BUFFER(used) + sizeof(HUFFMAN_TABLE));
PICTURE(used) = _memcpy_with_huffman(PICTURE(data), HW_BUFFER(data), HW_BUFFER(used));
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
src_ptr += 1;
}
if (src_ptr >= src_end) {
dest->used = 0; // Error
return;
}
paste = src_ptr - src->data;
# undef HW_BUFFER
# undef PICTURE
picture_set_data(dest, src->data, paste);
picture_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
picture_append_data(dest, src_ptr, src->used - paste);
} else {
picture_set_data(dest, src->data, src->used);
}
}
static bool _is_huffman(const unsigned char *data) {
@@ -91,27 +107,3 @@ static bool _is_huffman(const unsigned char *data) {
}
return false;
}
static size_t _memcpy_with_huffman(unsigned char *dest, const unsigned char *src, size_t size) {
if (!_is_huffman(src)) {
const unsigned char *src_ptr = src;
const unsigned char *src_end = src + size;
size_t paste;
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
src_ptr += 1;
}
if (src_ptr >= src_end) {
return 0;
}
paste = src_ptr - src;
memcpy(dest, src, paste);
memcpy(dest + paste, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
memcpy(dest + paste + sizeof(HUFFMAN_TABLE), src_ptr, size - paste);
return (size + sizeof(HUFFMAN_TABLE));
} else {
memcpy(dest, src, size);
return size;
}
}

View File

@@ -41,7 +41,7 @@ int component_enable_port(OMX_HANDLETYPE *component, OMX_U32 port) {
LOG_DEBUG("Enabling OMX port %u ...", port);
if ((error = OMX_SendCommand(*component, OMX_CommandPortEnable, port, NULL)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't enable OMX port %u", port);
LOG_ERROR_OMX(error, "Can't enable OMX port %u", port);
return -1;
}
return _component_wait_port_changed(component, port, OMX_TRUE);
@@ -52,7 +52,7 @@ int component_disable_port(OMX_HANDLETYPE *component, OMX_U32 port) {
LOG_DEBUG("Disabling OMX port %u ...", port);
if ((error = OMX_SendCommand(*component, OMX_CommandPortDisable, port, NULL)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't disable OMX port %u", port);
LOG_ERROR_OMX(error, "Can't disable OMX port %u", port);
return -1;
}
return _component_wait_port_changed(component, port, OMX_FALSE);
@@ -61,12 +61,13 @@ int component_disable_port(OMX_HANDLETYPE *component, OMX_U32 port) {
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port) {
OMX_ERRORTYPE error;
// cppcheck-suppress redundantPointerOp
OMX_INIT_STRUCTURE(*portdef);
portdef->nPortIndex = port;
LOG_DEBUG("Fetching OMX port %u definition ...", port);
if ((error = OMX_GetParameter(*component, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't get OMX port %u definition", port);
LOG_ERROR_OMX(error, "Can't get OMX port %u definition", port);
return -1;
}
return 0;
@@ -77,7 +78,7 @@ int component_set_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYP
LOG_DEBUG("Writing OMX port %u definition ...", portdef->nPortIndex);
if ((error = OMX_SetParameter(*component, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't set OMX port %u definition", portdef->nPortIndex);
LOG_ERROR_OMX(error, "Can't set OMX port %u definition", portdef->nPortIndex);
return -1;
}
return 0;
@@ -96,7 +97,7 @@ int component_set_state(OMX_HANDLETYPE *component, OMX_STATETYPE state) {
return _component_wait_state_changed(component, state);
} else if (error == OMX_ErrorInsufficientResources && retries) {
// Иногда железо не инициализируется, хз почему, просто ретраим, со второй попытки сработает
LOG_OMX_ERROR(error, "Can't switch OMX component state to %s, need to retry", state_str);
LOG_ERROR_OMX(error, "Can't switch OMX component state to %s, need to retry", state_str);
retries -= 1;
usleep(8000);
} else {
@@ -104,7 +105,7 @@ int component_set_state(OMX_HANDLETYPE *component, OMX_STATETYPE state) {
}
} while (retries);
LOG_OMX_ERROR(error, "Can't switch OMX component state to %s", state_str);
LOG_ERROR_OMX(error, "Can't switch OMX component state to %s", state_str);
return -1;
}
@@ -119,7 +120,7 @@ static int _component_wait_port_changed(OMX_HANDLETYPE *component, OMX_U32 port,
do {
if ((error = OMX_GetParameter(*component, OMX_IndexParamPortDefinition, &portdef)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't get OMX port %u definition for waiting", port);
LOG_ERROR_OMX(error, "Can't get OMX port %u definition for waiting", port);
return -1;
}
@@ -141,7 +142,7 @@ static int _component_wait_state_changed(OMX_HANDLETYPE *component, OMX_STATETYP
do {
if ((error = OMX_GetState(*component, &state)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Failed to get OMX component state");
LOG_ERROR_OMX(error, "Failed to get OMX component state");
return -1;
}

View File

@@ -37,6 +37,7 @@
#include "../../logging.h"
#include "../../tools.h"
#include "../../picture.h"
#include "../../device.h"
#include "formatters.h"
@@ -93,7 +94,7 @@ struct omx_encoder_t *omx_encoder_init(void) {
LOG_INFO("Initializing OMX ...");
if ((error = OMX_Init()) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't initialize OMX");
LOG_ERROR_OMX(error, "Can't initialize OMX");
goto error;
}
}
@@ -101,11 +102,11 @@ struct omx_encoder_t *omx_encoder_init(void) {
LOG_INFO("Initializing OMX encoder ...");
if (vcos_semaphore_create(&omx->handler_lock, "handler_lock", 0) != VCOS_SUCCESS) {
if (vcos_semaphore_create(&omx->handler_sem, "handler_sem", 0) != VCOS_SUCCESS) {
LOG_ERROR("Can't create VCOS semaphore");
goto error;
}
omx->i_handler_lock = true;
omx->i_handler_sem = true;
if (_omx_init_component(omx) < 0) {
goto error;
@@ -131,13 +132,13 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
_omx_encoder_clear_ports(omx);
component_set_state(&omx->encoder, OMX_StateLoaded);
if (omx->i_handler_lock) {
vcos_semaphore_delete(&omx->handler_lock);
if (omx->i_handler_sem) {
vcos_semaphore_delete(&omx->handler_sem);
}
if (omx->i_encoder) {
if ((error = OMX_FreeHandle(omx->encoder)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't free OMX.broadcom.image_encode");
LOG_ERROR_OMX(error, "Can't free OMX.broadcom.image_encode");
}
}
@@ -174,21 +175,21 @@ int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigne
}
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index) {
# define PICTURE(_next) dev->run->pictures[index]._next
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
# define IN(_next) omx->input_buffer->_next
# define OUT(_next) omx->output_buffer->_next
OMX_ERRORTYPE error;
VCOS_STATUS_T sem_status;
size_t slice_size = (IN(nAllocLen) < HW_BUFFER(used) ? IN(nAllocLen) : HW_BUFFER(used));
size_t pos = 0;
if ((error = OMX_FillThisBuffer(omx->encoder, omx->output_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Failed to request filling of the output buffer on encoder");
LOG_ERROR_OMX(error, "Failed to request filling of the output buffer on encoder");
return -1;
}
PICTURE(used) = 0;
dev->run->pictures[index]->used = 0;
omx->output_available = false;
omx->input_required = true;
@@ -200,9 +201,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
if (omx->output_available) {
omx->output_available = false;
assert(PICTURE(used) + OUT(nFilledLen) <= PICTURE(allocated));
memcpy(PICTURE(data) + PICTURE(used), OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
PICTURE(used) += OUT(nFilledLen);
picture_append_data(dev->run->pictures[index], OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) {
OUT(nFlags) = 0;
@@ -210,7 +209,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
}
if ((error = OMX_FillThisBuffer(omx->encoder, omx->output_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Failed to request filling of the output buffer on encoder");
LOG_ERROR_OMX(error, "Failed to request filling of the output buffer on encoder");
return -1;
}
}
@@ -233,18 +232,23 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
}
if ((error = OMX_EmptyThisBuffer(omx->encoder, omx->input_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Failed to request emptying of the input buffer on encoder");
LOG_ERROR_OMX(error, "Failed to request emptying of the input buffer on encoder");
return -1;
}
}
vcos_semaphore_wait(&omx->handler_lock);
// vcos_semaphore_wait(&omx->handler_sem);
switch (sem_status = vcos_semaphore_wait_timeout(&omx->handler_sem, 3000)) {
case VCOS_SUCCESS: break;
case VCOS_EAGAIN: LOG_ERROR("Can't wait VCOS semaphore: EAGAIN (timeout)"); return -1;
case VCOS_EINVAL: LOG_ERROR("Can't wait VCOS semaphore: EINTVAL"); return -1;
default: LOG_ERROR("Can't wait VCOS semaphore: %d", sem_status); return -1;
}
}
# undef OUT
# undef IN
# undef HW_BUFFER
# undef PICTURE
return 0;
}
@@ -259,7 +263,7 @@ static int _omx_init_component(struct omx_encoder_t *omx) {
LOG_DEBUG("Initializing OMX.broadcom.image_encode ...");
if ((error = OMX_GetHandle(&omx->encoder, "OMX.broadcom.image_encode", omx, &callbacks)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't initialize OMX.broadcom.image_encode");
LOG_ERROR_OMX(error, "Can't initialize OMX.broadcom.image_encode");
return -1;
}
omx->i_encoder = true;
@@ -276,13 +280,13 @@ static int _omx_init_disable_ports(struct omx_encoder_t *omx) {
OMX_INIT_STRUCTURE(ports);
if ((error = OMX_GetParameter(omx->encoder, OMX_IndexParamImageInit, &ports)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't OMX_GetParameter(OMX_IndexParamImageInit)");
LOG_ERROR_OMX(error, "Can't OMX_GetParameter(OMX_IndexParamImageInit)");
return -1;
}
for (unsigned index = 0; index < 4; ++index) {
if ((error = OMX_GetParameter(omx->encoder, types[index], &ports)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't OMX_GetParameter(types[%u])", index);
LOG_ERROR_OMX(error, "Can't OMX_GetParameter(types[%u])", index);
return -1;
}
for (OMX_U32 port = ports.nStartPortNumber; port < ports.nStartPortNumber + ports.nPorts; ++port) {
@@ -308,12 +312,10 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
portdef.format.image.nFrameWidth = dev->run->width;
portdef.format.image.nFrameHeight = dev->run->height;
portdef.format.image.nStride = 0;
# define ALIGN_HEIGHT(_x, _y) (((_x) + ((_y) - 1)) & ~((_y) - 1))
portdef.format.image.nSliceHeight = ALIGN_HEIGHT(dev->run->height, 16);
# undef ALIGN_HEIGHT
portdef.format.image.nSliceHeight = align_size(dev->run->height, 16);
portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused;
portdef.nBufferSize = dev->run->max_raw_image_size;
portdef.nBufferSize = picture_get_generous_size(dev->run->width, dev->run->height);
# define MAP_FORMAT(_v4l2_format, _omx_format) \
case _v4l2_format: { portdef.format.image.eColorFormat = _omx_format; break; }
@@ -349,7 +351,7 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
omx->i_input_port_enabled = true;
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->input_buffer, _INPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't allocate OMX JPEG input buffer");
LOG_ERROR_OMX(error, "Can't allocate OMX JPEG input buffer");
return -1;
}
return 0;
@@ -386,7 +388,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
exif.bEnabled = OMX_FALSE;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamBrcmDisableEXIF, &exif)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't disable EXIF on OMX JPEG");
LOG_ERROR_OMX(error, "Can't disable EXIF on OMX JPEG");
return -1;
}
}
@@ -399,7 +401,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
ijg.bEnabled = OMX_TRUE;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamBrcmEnableIJGTableScaling, &ijg)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't set OMX JPEG IJG settings");
LOG_ERROR_OMX(error, "Can't set OMX JPEG IJG settings");
return -1;
}
}
@@ -412,7 +414,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
qfactor.nQFactor = quality;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamQFactor, &qfactor)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't set OMX JPEG quality");
LOG_ERROR_OMX(error, "Can't set OMX JPEG quality");
return -1;
}
}
@@ -423,7 +425,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
omx->i_output_port_enabled = true;
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->output_buffer, _OUTPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't allocate OMX JPEG output buffer");
LOG_ERROR_OMX(error, "Can't allocate OMX JPEG output buffer");
return -1;
}
return 0;
@@ -444,14 +446,14 @@ static int _omx_encoder_clear_ports(struct omx_encoder_t *omx) {
if (omx->input_buffer) {
if ((error = OMX_FreeBuffer(omx->encoder, _INPUT_PORT, omx->input_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't free OMX JPEG input buffer");
LOG_ERROR_OMX(error, "Can't free OMX JPEG input buffer");
// retcode -= 1;
}
omx->input_buffer = NULL;
}
if (omx->output_buffer) {
if ((error = OMX_FreeBuffer(omx->encoder, _OUTPUT_PORT, omx->output_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't free OMX JPEG output buffer");
LOG_ERROR_OMX(error, "Can't free OMX JPEG output buffer");
// retcode -= 1;
}
omx->output_buffer = NULL;
@@ -469,9 +471,9 @@ static OMX_ERRORTYPE _omx_event_handler(
struct omx_encoder_t *omx = (struct omx_encoder_t *)v_omx;
if (event == OMX_EventError) {
LOG_OMX_ERROR((OMX_ERRORTYPE)data1, "OMX error event received");
LOG_ERROR_OMX((OMX_ERRORTYPE)data1, "OMX error event received");
omx->failed = true;
vcos_semaphore_post(&omx->handler_lock);
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
}
return OMX_ErrorNone;
}
@@ -486,7 +488,7 @@ static OMX_ERRORTYPE _omx_input_required_handler(
struct omx_encoder_t *omx = (struct omx_encoder_t *)v_omx;
omx->input_required = true;
vcos_semaphore_post(&omx->handler_lock);
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
return OMX_ErrorNone;
}
@@ -500,6 +502,6 @@ static OMX_ERRORTYPE _omx_output_available_handler(
struct omx_encoder_t *omx = (struct omx_encoder_t *)v_omx;
omx->output_available = true;
vcos_semaphore_post(&omx->handler_lock);
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
return OMX_ErrorNone;
}

View File

@@ -29,9 +29,10 @@
#include "../../device.h"
#ifndef OMX_MAX_ENCODERS
# define OMX_MAX_ENCODERS 3 // Raspberry Pi limitation
#ifndef CFG_OMX_MAX_ENCODERS
# define CFG_OMX_MAX_ENCODERS 3 // Raspberry Pi limitation
#endif
#define OMX_MAX_ENCODERS ((unsigned)(CFG_OMX_MAX_ENCODERS))
struct omx_encoder_t {
@@ -41,9 +42,9 @@ struct omx_encoder_t {
bool input_required;
bool output_available;
bool failed;
VCOS_SEMAPHORE_T handler_lock;
VCOS_SEMAPHORE_T handler_sem;
bool i_handler_lock;
bool i_handler_sem;
bool i_encoder;
bool i_input_port_enabled;
bool i_output_port_enabled;

View File

@@ -35,9 +35,9 @@
case _value: { return #_value; }
#define CASE_ASSERT(_msg, _value) default: { \
char *_buf; A_CALLOC(_buf, 128); \
sprintf(_buf, _msg ": 0x%08x", _value); \
assert(0 && _buf); \
char *_assert_buf; A_CALLOC(_assert_buf, 128); \
sprintf(_assert_buf, _msg ": 0x%08x", _value); \
assert(0 && _assert_buf); \
}
const char *omx_error_to_string(OMX_ERRORTYPE error) {
@@ -67,15 +67,40 @@ const char *omx_error_to_string(OMX_ERRORTYPE error) {
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringDeallocation);
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringStop);
CASE_TO_STRING(OMX_ErrorIncorrectStateTransition);
CASE_TO_STRING(OMX_ErrorIncorrectStateOperation);
CASE_TO_STRING(OMX_ErrorUnsupportedSetting);
CASE_TO_STRING(OMX_ErrorUnsupportedIndex);
CASE_TO_STRING(OMX_ErrorBadPortIndex);
CASE_TO_STRING(OMX_ErrorPortUnpopulated);
CASE_TO_STRING(OMX_ErrorComponentSuspended);
CASE_TO_STRING(OMX_ErrorDynamicResourcesUnavailable);
CASE_TO_STRING(OMX_ErrorMbErrorsInFrame);
CASE_TO_STRING(OMX_ErrorFormatNotDetected);
CASE_TO_STRING(OMX_ErrorContentPipeOpenFailed);
CASE_TO_STRING(OMX_ErrorContentPipeCreationFailed);
CASE_TO_STRING(OMX_ErrorSeperateTablesUsed);
CASE_TO_STRING(OMX_ErrorTunnelingUnsupported);
CASE_TO_STRING(OMX_ErrorKhronosExtensions);
CASE_TO_STRING(OMX_ErrorVendorStartUnused);
CASE_TO_STRING(OMX_ErrorDiskFull);
CASE_TO_STRING(OMX_ErrorMaxFileSize);
CASE_TO_STRING(OMX_ErrorDrmUnauthorised);
CASE_TO_STRING(OMX_ErrorDrmExpired);
CASE_TO_STRING(OMX_ErrorDrmGeneral);
default: return "Unknown OMX error";
}
}
const char *omx_state_to_string(OMX_STATETYPE state) {
switch (state) {
CASE_TO_STRING(OMX_StateInvalid);
CASE_TO_STRING(OMX_StateLoaded);
CASE_TO_STRING(OMX_StateIdle);
CASE_TO_STRING(OMX_StateExecuting);
CASE_TO_STRING(OMX_StatePause);
CASE_TO_STRING(OMX_StateWaitForResources);
// cppcheck-suppress constArgument
// cppcheck-suppress knownArgument
CASE_ASSERT("Unsupported OMX state", state);
}
}

View File

@@ -22,23 +22,15 @@
#pragma once
#include <stdio.h>
#include <sys/syscall.h>
#include <IL/OMX_IVCommon.h>
#include <IL/OMX_Core.h>
#include <IL/OMX_Image.h>
#include "../../logging.h"
#include "../../tools.h"
#define LOG_OMX_ERROR(_error, _msg, ...) { \
LOGGING_LOCK; \
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", get_now_monotonic(), \
syscall(SYS_gettid), ##__VA_ARGS__, omx_error_to_string(_error)); \
LOGGING_UNLOCK; \
#define LOG_ERROR_OMX(_error, _msg, ...) { \
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, omx_error_to_string(_error)); \
}

View File

@@ -1,92 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 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 <stdlib.h>
#include <wiringPi.h>
#include "logging.h"
int gpio_pin_prog_running;
int gpio_pin_stream_online;
int gpio_pin_has_http_clients;
int gpio_pin_workers_busy_at;
#define GPIO_INIT { \
gpio_pin_prog_running = -1; \
gpio_pin_stream_online = -1; \
gpio_pin_has_http_clients = -1; \
gpio_pin_workers_busy_at = -1; \
}
#define GPIO_INIT_PIN(_role, _offset) { \
if (gpio_pin_##_role >= 0) { \
pinMode(gpio_pin_##_role + _offset, OUTPUT); \
digitalWrite(gpio_pin_##_role + _offset, LOW); \
if (_offset == 0) { \
LOG_INFO("GPIO: Using pin %d as %s", gpio_pin_##_role, #_role); \
} else { \
LOG_INFO("GPIO: Using pin %d+%d as %s", gpio_pin_##_role, _offset, #_role); \
} \
} \
}
#define GPIO_INIT_PINOUT { \
if ( \
gpio_pin_prog_running >= 0 \
|| gpio_pin_stream_online >= 0 \
|| gpio_pin_has_http_clients >= 0 \
|| gpio_pin_workers_busy_at >= 0 \
) { \
LOG_INFO("GPIO: Using wiringPi"); \
if (wiringPiSetupGpio() < 0) { \
LOG_PERROR("GPIO: Can't initialize wiringPi"); \
exit(1); \
} else { \
GPIO_INIT_PIN(prog_running, 0); \
GPIO_INIT_PIN(stream_online, 0); \
GPIO_INIT_PIN(has_http_clients, 0); \
GPIO_INIT_PIN(workers_busy_at, 0); \
} \
} \
}
#define GPIO_SET_STATE(_role, _offset, _state) { \
if (gpio_pin_##_role >= 0) { \
if (_offset == 0) { \
LOG_DEBUG("GPIO: Writing %d to pin %d (%s)", _state, gpio_pin_##_role, #_role); \
} else { \
LOG_DEBUG("GPIO: Writing %d to pin %d+%d (%s)", _state, gpio_pin_##_role, _offset, #_role); \
} \
digitalWrite(gpio_pin_##_role + _offset, _state); \
} \
}
#define GPIO_SET_LOW(_role) GPIO_SET_STATE(_role, 0, LOW)
#define GPIO_SET_HIGH(_role) GPIO_SET_STATE(_role, 0, HIGH)
#define GPIO_SET_LOW_AT(_role, _offset) GPIO_SET_STATE(_role, _offset, LOW)
#define GPIO_SET_HIGH_AT(_role, _offset) GPIO_SET_STATE(_role, _offset, HIGH)

142
src/gpio/gpio.c Normal file
View File

@@ -0,0 +1,142 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "gpio.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>
#include <gpiod.h>
#include "../tools.h"
#include "../logging.h"
#include "../threading.h"
struct gpio_t gpio = {
.path = "/dev/gpiochip0",
.consumer_prefix = "ustreamer",
# define MAKE_OUTPUT(_role) { \
.pin = -1, \
.role = _role, \
.consumer = NULL, \
.line = NULL, \
.state = false \
}
.prog_running = MAKE_OUTPUT("prog-running"),
.stream_online = MAKE_OUTPUT("stream-online"),
.has_http_clients = MAKE_OUTPUT("has-http-clients"),
# undef MAKE_OUTPUT
// mutex uninitialized
.chip = NULL
};
static void _gpio_output_init(struct gpio_output_t *output);
static void _gpio_output_destroy(struct gpio_output_t *output);
void gpio_init(void) {
assert(gpio.chip == NULL);
if (
gpio.prog_running.pin >= 0
|| gpio.stream_online.pin >= 0
|| gpio.has_http_clients.pin >= 0
) {
A_MUTEX_INIT(&gpio.mutex);
LOG_INFO("GPIO: Using chip device: %s", gpio.path);
if ((gpio.chip = gpiod_chip_open(gpio.path)) != NULL) {
_gpio_output_init(&gpio.prog_running);
_gpio_output_init(&gpio.stream_online);
_gpio_output_init(&gpio.has_http_clients);
} else {
LOG_PERROR("GPIO: Can't initialize chip device %s", gpio.path);
}
}
}
void gpio_destroy(void) {
_gpio_output_destroy(&gpio.prog_running);
_gpio_output_destroy(&gpio.stream_online);
_gpio_output_destroy(&gpio.has_http_clients);
if (gpio.chip) {
gpiod_chip_close(gpio.chip);
gpio.chip = NULL;
A_MUTEX_DESTROY(&gpio.mutex);
}
}
int gpio_inner_set(struct gpio_output_t *output, bool state) {
int retval = 0;
assert(gpio.chip);
assert(output->line);
assert(output->state != state); // Must be checked in macro for the performance
A_MUTEX_LOCK(&gpio.mutex);
if (gpiod_line_set_value(output->line, (int)state) < 0) { \
LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
_gpio_output_destroy(output);
retval = -1;
}
A_MUTEX_UNLOCK(&gpio.mutex);
return retval;
}
static void _gpio_output_init(struct gpio_output_t *output) {
assert(gpio.chip);
assert(output->line == NULL);
A_CALLOC(output->consumer, strlen(gpio.consumer_prefix) + strlen(output->role) + 16);
sprintf(output->consumer, "%s::%s", gpio.consumer_prefix, output->role);
if (output->pin >= 0) {
if ((output->line = gpiod_chip_get_line(gpio.chip, output->pin)) != NULL) {
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
_gpio_output_destroy(output);
}
} else {
LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
}
}
}
static void _gpio_output_destroy(struct gpio_output_t *output) {
if (output->line) {
gpiod_line_release(output->line);
output->line = NULL;
}
if (output->consumer) {
free(output->consumer);
output->consumer = NULL;
}
output->state = false;
}

83
src/gpio/gpio.h Normal file
View File

@@ -0,0 +1,83 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 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 <stdbool.h>
#include <pthread.h>
#include <gpiod.h>
#include "../tools.h"
#include "../logging.h"
struct gpio_output_t {
int pin;
const char *role;
char *consumer;
struct gpiod_line *line;
bool state;
};
struct gpio_t {
char *path;
char *consumer_prefix;
struct gpio_output_t prog_running;
struct gpio_output_t stream_online;
struct gpio_output_t has_http_clients;
pthread_mutex_t mutex;
struct gpiod_chip *chip;
};
extern struct gpio_t gpio;
void gpio_init(void);
void gpio_destroy(void);
int gpio_inner_set(struct gpio_output_t *output, bool state);
#define SET_STATE(_output, _state) { \
if (_output.line && _output.state != _state) { \
if (!gpio_inner_set(&_output, _state)) { \
_output.state = _state; \
} \
} \
}
INLINE void gpio_set_prog_running(bool state) {
SET_STATE(gpio.prog_running, state);
}
INLINE void gpio_set_stream_online(bool state) {
SET_STATE(gpio.stream_online, state);
}
INLINE void gpio_set_has_http_clients(bool state) {
SET_STATE(gpio.has_http_clients, state);
}
#undef SET_STATE

View File

@@ -24,12 +24,11 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "../tools.h"
static const char ENCODING_TABLE[] = {
static const char _ENCODING_TABLE[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
@@ -40,7 +39,7 @@ static const char ENCODING_TABLE[] = {
'4', '5', '6', '7', '8', '9', '+', '/',
};
static const unsigned MOD_TABLE[] = {0, 2, 1};
static const unsigned _MOD_TABLE[] = {0, 2, 1};
char *base64_encode(const unsigned char *str) {
@@ -57,13 +56,13 @@ char *base64_encode(const unsigned char *str) {
unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 3 * 6) & 0x3F];
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 2 * 6) & 0x3F];
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 1 * 6) & 0x3F];
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 0 * 6) & 0x3F];
encoded[encoded_index++] = _ENCODING_TABLE[(triple >> 3 * 6) & 0x3F];
encoded[encoded_index++] = _ENCODING_TABLE[(triple >> 2 * 6) & 0x3F];
encoded[encoded_index++] = _ENCODING_TABLE[(triple >> 1 * 6) & 0x3F];
encoded[encoded_index++] = _ENCODING_TABLE[(triple >> 0 * 6) & 0x3F];
}
for (unsigned index = 0; index < MOD_TABLE[str_len % 3]; index++) {
for (unsigned index = 0; index < _MOD_TABLE[str_len % 3]; index++) {
encoded[encoded_size - 2 - index] = '=';
}

View File

@@ -22,7 +22,5 @@
#pragma once
#include <stdlib.h>
char *base64_encode(const unsigned char *str);

View File

@@ -24,13 +24,13 @@
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <setjmp.h>
#include <jpeglib.h>
#include "../tools.h"
#include "../logging.h"
#include "../picture.h"
#include "data/blank_jpeg.h"
@@ -41,55 +41,44 @@ struct _jpeg_error_manager_t {
};
static struct blank_t *_blank_init_internal(void);
static struct blank_t *_blank_init_external(const char *path);
static struct picture_t *_init_internal(void);
static struct picture_t *_init_external(const char *path);
static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height);
static void _jpeg_error_handler(j_common_ptr jpeg);
struct blank_t *blank_init(const char *path) {
struct blank_t *blank = NULL;
struct picture_t *blank_picture_init(const char *path) {
struct picture_t *blank = NULL;
if (path) {
blank = _blank_init_external(path);
blank = _init_external(path);
}
if (blank) {
LOG_INFO("Using external blank placeholder: %s", path);
} else {
blank = _blank_init_internal();
blank = _init_internal();
LOG_INFO("Using internal blank placeholder");
}
return blank;
}
void blank_destroy(struct blank_t *blank) {
free(blank->picture.data);
free(blank);
}
static struct blank_t *_blank_init_internal(void) {
struct blank_t *blank;
A_CALLOC(blank, 1);
A_CALLOC(blank->picture.data, ARRAY_LEN(BLANK_JPEG_DATA));
memcpy(blank->picture.data, BLANK_JPEG_DATA, ARRAY_LEN(BLANK_JPEG_DATA));
blank->picture.used = ARRAY_LEN(BLANK_JPEG_DATA);
blank->picture.allocated = ARRAY_LEN(BLANK_JPEG_DATA);
static struct picture_t *_init_internal(void) {
struct picture_t *blank;
blank = picture_init();
picture_set_data(blank, BLANK_JPEG_DATA, ARRAY_LEN(BLANK_JPEG_DATA));
blank->width = BLANK_JPEG_WIDTH;
blank->height = BLANK_JPEG_HEIGHT;
return blank;
}
static struct blank_t *_blank_init_external(const char *path) {
static struct picture_t *_init_external(const char *path) {
FILE *fp = NULL;
struct blank_t *blank;
struct picture_t *blank;
A_CALLOC(blank, 1);
blank = picture_init();
if ((fp = fopen(path, "rb")) == NULL) {
LOG_PERROR("Can't open blank placeholder '%s'", path);
@@ -105,15 +94,14 @@ static struct blank_t *_blank_init_external(const char *path) {
goto error;
}
# define CHUNK_SIZE (100 * 1024)
# define CHUNK_SIZE ((size_t)(100 * 1024))
while (true) {
if (blank->picture.used + CHUNK_SIZE >= blank->picture.allocated) {
blank->picture.allocated = blank->picture.used + CHUNK_SIZE * 2;
A_REALLOC(blank->picture.data, blank->picture.allocated);
if (blank->used + CHUNK_SIZE >= blank->allocated) {
picture_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
}
size_t readed = fread(blank->picture.data + blank->picture.used, 1, CHUNK_SIZE, fp);
blank->picture.used += readed;
size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
blank->used += readed;
if (readed < CHUNK_SIZE) {
if (feof(fp)) {
@@ -127,8 +115,7 @@ static struct blank_t *_blank_init_external(const char *path) {
# undef CHUNK_SIZE
error:
free(blank->picture.data);
free(blank);
picture_destroy(blank);
blank = NULL;
ok:

View File

@@ -22,17 +22,7 @@
#pragma once
#include <stdlib.h>
#include "../device.h"
#include "../picture.h"
struct blank_t {
struct picture_t picture;
unsigned width;
unsigned height;
};
struct blank_t *blank_init(const char *path);
void blank_destroy(struct blank_t *blank);
struct picture_t *blank_picture_init(const char *path);

View File

@@ -56,7 +56,7 @@ const char *guess_mime_type(const char *path) {
char *dot;
char *ext;
dot = strchr(path, '.');
dot = strrchr(path, '.');
if (dot == NULL || strchr(dot, '/') != NULL) {
goto misc;
}

View File

@@ -22,10 +22,11 @@
#include "path.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef TEST_HTTP_PATH
# include <stdio.h>
# include <stdlib.h>
#endif
#include <string.h>
#include <assert.h>
#include "../tools.h"
@@ -116,7 +117,7 @@ char *simplify_request_path(const char *str) {
return simplified;
}
#if 0
#ifdef TEST_HTTP_PATH
int test_simplify_request_path(const char *sample, const char *expected) {
char *result = simplify_request_path(sample);

View File

@@ -22,19 +22,21 @@
#include "server.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <event2/event.h>
#include <event2/thread.h>
@@ -50,23 +52,26 @@
#endif
#include "../tools.h"
#include "../threading.h"
#include "../logging.h"
#include "../process.h"
#include "../picture.h"
#include "../encoder.h"
#include "../stream.h"
#ifdef WITH_GPIO
# include "../gpio.h"
# include "../gpio/gpio.h"
#endif
#include "blank.h"
#include "unix.h"
#include "uri.h"
#include "base64.h"
#include "mime.h"
#include "static.h"
#include "blank.h"
#include "data/index_html.h"
static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
static char *_http_get_param_uri(struct evkeyvalq *params, const char *key);
static int _http_preprocess_request(struct evhttp_request *request, struct http_server_t *server);
static void _http_callback_root(struct evhttp_request *request, void *v_server);
@@ -84,6 +89,8 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
static bool _expose_new_picture_unsafe(struct http_server_t *server);
static bool _expose_blank_picture(struct http_server_t *server);
static void _format_bufferevent_reason(short what, char *reason);
struct http_server_t *http_server_init(struct stream_t *stream) {
struct http_server_runtime_t *run;
@@ -91,6 +98,7 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
struct exposed_t *exposed;
A_CALLOC(exposed, 1);
exposed->picture = picture_init();
A_CALLOC(run, 1);
run->stream = stream;
@@ -104,7 +112,9 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
server->user = "";
server->passwd = "";
server->static_path = "";
server->allow_origin = "";
server->timeout = 10;
server->last_as_blank = -1;
server->run = run;
assert(!evthread_use_pthreads());
@@ -143,10 +153,10 @@ void http_server_destroy(struct http_server_t *server) {
}
if (server->run->blank) {
blank_destroy(server->run->blank);
picture_destroy(server->run->blank);
}
free(server->run->exposed->picture.data);
picture_destroy(server->run->exposed->picture);
free(server->run->exposed);
free(server->run);
free(server);
@@ -155,6 +165,7 @@ void http_server_destroy(struct http_server_t *server) {
int http_server_listen(struct http_server_t *server) {
{
if (server->static_path[0] != '\0') {
LOG_INFO("Enabling HTTP file server: %s", server->static_path);
evhttp_set_gencb(server->run->http, _http_callback_static, (void *)server);
} else {
assert(!evhttp_set_cb(server->run->http, "/", _http_callback_root, (void *)server));
@@ -165,8 +176,18 @@ int http_server_listen(struct http_server_t *server) {
}
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
server->run->blank = blank_init(server->blank_path);
_expose_blank_picture(server);
server->run->blank = blank_picture_init(server->blank_path);
# define EXPOSED(_next) server->run->exposed->_next
// See _expose_blank_picture()
picture_copy(server->run->blank, EXPOSED(picture));
EXPOSED(expose_begin_ts) = get_now_monotonic();
EXPOSED(expose_cmp_ts) = EXPOSED(expose_begin_ts);
EXPOSED(expose_end_ts) = EXPOSED(expose_begin_ts);
// See _http_exposed_refresh()
EXPOSED(notify_last_width) = EXPOSED(picture->width);
EXPOSED(notify_last_height) = EXPOSED(picture->height);
# undef EXPOSED
{
struct timeval refresh_interval;
@@ -205,51 +226,19 @@ int http_server_listen(struct http_server_t *server) {
}
if (server->unix_path[0] != '\0') {
struct sockaddr_un unix_addr;
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
# define MAX_SUN_PATH (sizeof(unix_addr.sun_path) - 1)
if (strlen(server->unix_path) > MAX_SUN_PATH) {
LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
if ((server->run->unix_fd = evhttp_my_bind_unix(
server->run->http,
server->unix_path,
server->unix_rm,
server->unix_mode)) < 0
) {
return -1;
}
MEMSET_ZERO(unix_addr);
strncpy(unix_addr.sun_path, server->unix_path, MAX_SUN_PATH);
unix_addr.sun_family = AF_UNIX;
# undef MAX_SUN_PATH
assert((server->run->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0);
assert(!evutil_make_socket_nonblocking(server->run->unix_fd));
if (server->unix_rm && unlink(server->unix_path) < 0) {
if (errno != ENOENT) {
LOG_PERROR("Can't remove old UNIX socket '%s'", server->unix_path);
return -1;
}
}
if (bind(server->run->unix_fd, (struct sockaddr *)&unix_addr, sizeof(struct sockaddr_un)) < 0) {
LOG_PERROR("Can't bind HTTP to UNIX socket '%s'", server->unix_path);
return -1;
}
if (server->unix_mode && chmod(server->unix_path, server->unix_mode) < 0) {
LOG_PERROR("Can't set permissions %o to UNIX socket '%s'", server->unix_mode, server->unix_path);
return -1;
}
if (listen(server->run->unix_fd, 128) < 0) {
LOG_PERROR("Can't listen UNIX socket '%s'", server->unix_path);
return -1;
}
if (evhttp_accept_socket(server->run->http, server->run->unix_fd) < 0) {
LOG_PERROR("Can't evhttp_accept_socket() UNIX socket '%s'", server->unix_path);
return -1;
}
LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path);
if (server->tcp_nodelay) {
LOG_ERROR("TCP_NODELAY flag can't be used with UNIX socket and will be ignored");
}
} else {
LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
if (evhttp_bind_socket(server->run->http, server->host, server->port) < 0) {
@@ -272,31 +261,6 @@ void http_server_loop_break(struct http_server_t *server) {
event_base_loopbreak(server->run->base);
}
static bool _http_get_param_true(struct evkeyvalq *params, const char *key) {
const char *value_str;
if ((value_str = evhttp_find_header(params, key)) != NULL) {
if (
value_str[0] == '1'
|| !evutil_ascii_strcasecmp(value_str, "true")
|| !evutil_ascii_strcasecmp(value_str, "yes")
) {
return true;
}
}
return false;
}
static char *_http_get_param_uri(struct evkeyvalq *params, const char *key) {
const char *value_str;
if ((value_str = evhttp_find_header(params, key)) != NULL) {
return evhttp_encode_uri(value_str);
}
return NULL;
}
#define ADD_HEADER(_key, _value) \
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
@@ -328,14 +292,27 @@ static int _http_preprocess_request(struct evhttp_request *request, struct http_
static void _http_callback_root(struct evhttp_request *request, void *v_server) {
struct http_server_t *server = (struct http_server_t *)v_server;
struct evbuffer *buf;
struct evkeyvalq params; // For mjpg-streamer compatibility
const char *action; // Ditto
PREPROCESS_REQUEST;
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
ADD_HEADER("Content-Type", "text/html");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
evhttp_parse_query(evhttp_request_get_uri(request), &params);
action = evhttp_find_header(&params, "action");
if (action && !strcmp(action, "snapshot")) {
_http_callback_snapshot(request, v_server);
} else if (action && !strcmp(action, "stream")) {
_http_callback_stream(request, v_server);
} else {
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
ADD_HEADER("Content-Type", "text/html");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
}
evhttp_clear_headers(&params);
}
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
@@ -377,7 +354,7 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
goto not_found;
}
if (evbuffer_add_file(buf, fd, 0, st.st_size) < 0) {
if (st.st_size > 0 && evbuffer_add_file(buf, fd, 0, st.st_size) < 0) {
LOG_ERROR("HTTP: Can't serve static file %s", static_path);
goto not_found;
}
@@ -420,6 +397,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
PREPROCESS_REQUEST;
# define ENCODER(_next) server->run->stream->encoder->_next
# define EXPOSED(_next) server->run->exposed->_next
A_MUTEX_LOCK(&ENCODER(run->mutex));
encoder_run_type = ENCODER(run->type);
@@ -436,15 +414,16 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
encoder_type_to_string(encoder_run_type),
encoder_run_quality,
(server->fake_width ? server->fake_width : server->run->exposed->width),
(server->fake_height ? server->fake_height : server->run->exposed->height),
bool_to_string(server->run->exposed->online),
(server->fake_width ? server->fake_width : EXPOSED(picture->width)),
(server->fake_height ? server->fake_height : EXPOSED(picture->height)),
bool_to_string(EXPOSED(online)),
server->run->stream->dev->desired_fps,
server->run->exposed->captured_fps,
server->run->exposed->queued_fps,
EXPOSED(captured_fps),
EXPOSED(queued_fps),
server->run->stream_clients_count
));
# undef EXPOSED
# undef ENCODER
for (struct stream_client_t * client = server->run->stream_clients; client != NULL; client = client->next) {
@@ -476,9 +455,11 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
# define EXPOSED(_next) server->run->exposed->_next
assert((buf = evbuffer_new()));
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture.data), EXPOSED(picture.used)));
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture->data), EXPOSED(picture->used)));
ADD_HEADER("Access-Control-Allow-Origin:", "*");
if (server->allow_origin[0] != '\0') {
ADD_HEADER("Access-Control-Allow-Origin", server->allow_origin);
}
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
ADD_HEADER("Pragma", "no-cache");
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
@@ -495,27 +476,26 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
ADD_TIME_HEADER("X-Timestamp", get_now_real());
ADD_HEADER("X-UStreamer-Online", bool_to_string(EXPOSED(online)));
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", EXPOSED(dropped));
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(width));
ADD_UNSIGNED_HEADER("X-UStreamer-Height", EXPOSED(height));
ADD_TIME_HEADER("X-UStreamer-Grab-Time", EXPOSED(picture.grab_time));
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Time", EXPOSED(picture.encode_begin_time));
ADD_TIME_HEADER("X-UStreamer-Encode-End-Time", EXPOSED(picture.encode_end_time));
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Time", EXPOSED(expose_begin_time));
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Time", EXPOSED(expose_cmp_time));
ADD_TIME_HEADER("X-UStreamer-Expose-End-Time", EXPOSED(expose_end_time));
ADD_TIME_HEADER("X-UStreamer-Send-Time", get_now_monotonic());
ADD_HEADER("X-UStreamer-Online", bool_to_string(EXPOSED(online)));
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", EXPOSED(dropped));
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(picture->width));
ADD_UNSIGNED_HEADER("X-UStreamer-Height", EXPOSED(picture->height));
ADD_TIME_HEADER("X-UStreamer-Grab-Timestamp", EXPOSED(picture->grab_ts));
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Timestamp", EXPOSED(picture->encode_begin_ts));
ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", EXPOSED(picture->encode_end_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Timestamp", EXPOSED(expose_begin_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Timestamp", EXPOSED(expose_cmp_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-End-Timestamp", EXPOSED(expose_end_ts));
ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", get_now_monotonic());
# undef ADD_UNSUGNED_HEADER
# undef ADD_TIME_HEADER
# undef EXPOSED
ADD_HEADER("Content-Type", "image/jpeg");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
# undef EXPOSED
}
#undef ADD_HEADER
@@ -547,10 +527,10 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->need_first_frame = true;
evhttp_parse_query(evhttp_request_get_uri(request), &params);
client->key = _http_get_param_uri(&params, "key");
client->extra_headers = _http_get_param_true(&params, "extra_headers");
client->advance_headers = _http_get_param_true(&params, "advance_headers");
client->dual_final_frames = _http_get_param_true(&params, "dual_final_frames");
client->key = uri_get_string(&params, "key");
client->extra_headers = uri_get_true(&params, "extra_headers");
client->advance_headers = uri_get_true(&params, "advance_headers");
client->dual_final_frames = uri_get_true(&params, "dual_final_frames");
evhttp_clear_headers(&params);
uuid_generate(uuid);
@@ -573,15 +553,25 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
}
# ifdef WITH_GPIO
GPIO_SET_HIGH(has_http_clients);
gpio_set_has_http_clients(true);
# endif
}
evhttp_connection_get_peer(conn, &client_addr, &client_port);
LOG_INFO("HTTP: Registered the new stream client: [%s]:%u, id=%s; clients now: %u",
LOG_INFO("HTTP: Registered client: [%s]:%u, id=%s; clients now: %u",
client_addr, client_port, client->id, server->run->stream_clients_count);
buf_event = evhttp_connection_get_bufferevent(conn);
if (server->tcp_nodelay && !server->run->unix_fd) {
evutil_socket_t fd;
int on = 1;
LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client [%s]:%u ...", client_addr, client_port);
assert((fd = bufferevent_getfd(buf_event)) >= 0);
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)) != 0) {
LOG_PERROR("HTTP: Can't set TCP_NODELAY to the client [%s]:%u", client_addr, client_port);
}
}
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
bufferevent_enable(buf_event, EV_READ);
} else {
@@ -633,9 +623,11 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real()))
if (client->need_initial) {
assert(evbuffer_add_printf(buf, "HTTP/1.0 200 OK" RN));
if (client->server->allow_origin[0] != '\0') {
assert(evbuffer_add_printf(buf, "Access-Control-Allow-Origin: %s" RN, client->server->allow_origin));
}
assert(evbuffer_add_printf(buf,
"HTTP/1.0 200 OK" RN
"Access-Control-Allow-Origin: *" 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
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
@@ -663,7 +655,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"Content-Length: %zu" RN
"X-Timestamp: %.06Lf" RN
"%s",
EXPOSED(picture.used),
EXPOSED(picture->used),
get_now_real(),
(client->extra_headers ? "" : RN)
));
@@ -684,21 +676,21 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
RN,
bool_to_string(EXPOSED(online)),
EXPOSED(dropped),
EXPOSED(width),
EXPOSED(height),
EXPOSED(picture->width),
EXPOSED(picture->height),
client->fps,
EXPOSED(picture.grab_time),
EXPOSED(picture.encode_begin_time),
EXPOSED(picture.encode_end_time),
EXPOSED(expose_begin_time),
EXPOSED(expose_cmp_time),
EXPOSED(expose_end_time),
EXPOSED(picture->grab_ts),
EXPOSED(picture->encode_begin_ts),
EXPOSED(picture->encode_end_ts),
EXPOSED(expose_begin_ts),
EXPOSED(expose_cmp_ts),
EXPOSED(expose_end_ts),
now
));
}
}
assert(!evbuffer_add(buf, (void *)EXPOSED(picture.data), EXPOSED(picture.used)));
assert(!evbuffer_add(buf, (void *)EXPOSED(picture->data), EXPOSED(picture->used)));
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
if (client->advance_headers) {
@@ -722,18 +714,22 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
struct evhttp_connection *conn;
char *client_addr = "???";
unsigned short client_port = 0;
char reason[2048] = {0};
_format_bufferevent_reason(what, reason);
# define RUN(_next) client->server->run->_next
assert(RUN(stream_clients_count) > 0);
RUN(stream_clients_count) -= 1;
if (RUN(stream_clients_count) <= 0) {
if (RUN(stream_clients_count) == 0) {
if (client->server->slowdown) {
stream_switch_slowdown(RUN(stream), true);
}
# ifdef WITH_GPIO
GPIO_SET_LOW(has_http_clients);
gpio_set_has_http_clients(false);
# endif
}
@@ -741,8 +737,9 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
if (conn) {
evhttp_connection_get_peer(conn, &client_addr, &client_port);
}
LOG_INFO("HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
client_addr, client_port, RUN(stream_clients_count));
LOG_INFO("HTTP: Disconnected client: [%s]:%u, id=%s, %s; clients now: %u",
client_addr, client_port, client->id, reason, RUN(stream_clients_count));
if (conn) {
evhttp_connection_free(conn);
}
@@ -765,9 +762,8 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
struct evhttp_connection *conn;
struct bufferevent *buf_event;
long long now;
bool has_clients = false;
bool queued = false;
static unsigned queued_fps_accum = 0;
static long long queued_fps_second = 0;
for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
conn = evhttp_request_get_connection(client->request);
@@ -797,16 +793,23 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
} else if (stream_updated) { // Для dual
client->updated_prev = false;
}
has_clients = true;
}
}
if (queued) {
static unsigned queued_fps_accum = 0;
static long long queued_fps_second = 0;
if ((now = floor_ms(get_now_monotonic())) != queued_fps_second) {
server->run->exposed->queued_fps = queued_fps_accum;
queued_fps_accum = 0;
queued_fps_second = now;
}
queued_fps_accum += 1;
} else if (!has_clients) {
server->run->exposed->queued_fps = 0;
}
}
@@ -823,7 +826,7 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
if (atomic_load(&server->run->stream->updated)) {
LOG_DEBUG("Refreshing HTTP exposed ...");
A_MUTEX_LOCK(&server->run->stream->mutex);
if (server->run->stream->picture.used > 0) { // If online
if (server->run->stream->online) {
picture_updated = _expose_new_picture_unsafe(server);
UNLOCK_STREAM;
} else {
@@ -840,109 +843,146 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
# undef UNLOCK_STREAM
_http_queue_send_stream(server, stream_updated, picture_updated);
# define EXPOSED(_next) server->run->exposed->_next
if (
picture_updated
&& server->notify_parent
&& (
EXPOSED(notify_last_online) != EXPOSED(online)
|| EXPOSED(notify_last_width) != EXPOSED(picture->width)
|| EXPOSED(notify_last_height) != EXPOSED(picture->height)
)
) {
EXPOSED(notify_last_online) = EXPOSED(online);
EXPOSED(notify_last_width) = EXPOSED(picture->width);
EXPOSED(notify_last_height) = EXPOSED(picture->height);
process_notify_parent();
}
# undef EXPOSED
}
static bool _expose_new_picture_unsafe(struct http_server_t *server) {
# define STREAM(_next) server->run->stream->_next
# define EXPOSED(_next) server->run->exposed->_next
# define STREAM(_next) server->run->stream->_next
assert(STREAM(picture.used) > 0);
EXPOSED(captured_fps) = STREAM(captured_fps);
EXPOSED(expose_begin_time) = get_now_monotonic();
# define MEM_STREAM_TO_EXPOSED \
EXPOSED(picture.data), STREAM(picture.data), STREAM(picture.used)
EXPOSED(expose_begin_ts) = get_now_monotonic();
if (server->drop_same_frames) {
if (
EXPOSED(online)
&& EXPOSED(dropped) < server->drop_same_frames
&& EXPOSED(picture.used) == STREAM(picture.used)
&& !memcmp(MEM_STREAM_TO_EXPOSED)
&& picture_compare(EXPOSED(picture), STREAM(picture))
) {
EXPOSED(expose_cmp_time) = get_now_monotonic();
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
LOG_VERBOSE("HTTP: dropped same frame number %u; cmp_time=%.06Lf",
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
EXPOSED(expose_cmp_ts) = get_now_monotonic();
EXPOSED(expose_end_ts) = EXPOSED(expose_cmp_ts);
LOG_VERBOSE("HTTP: Dropped same frame number %u; cmp_time=%.06Lf",
EXPOSED(dropped), EXPOSED(expose_cmp_ts) - EXPOSED(expose_begin_ts));
EXPOSED(dropped) += 1;
return false; // Not updated
} else {
EXPOSED(expose_cmp_time) = get_now_monotonic();
LOG_VERBOSE("HTTP: passed same frame check (frames are differ); cmp_time=%.06Lf",
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
EXPOSED(expose_cmp_ts) = get_now_monotonic();
LOG_VERBOSE("HTTP: Passed same frame check (frames are differ); cmp_time=%.06Lf",
EXPOSED(expose_cmp_ts) - EXPOSED(expose_begin_ts));
}
}
if (EXPOSED(picture.allocated) < STREAM(picture.allocated)) {
A_REALLOC(EXPOSED(picture.data), STREAM(picture.allocated));
EXPOSED(picture.allocated) = STREAM(picture.allocated);
}
picture_copy(STREAM(picture), EXPOSED(picture));
memcpy(MEM_STREAM_TO_EXPOSED);
# undef STREAM
# undef MEM_STREAM_TO_EXPOSED
EXPOSED(picture.used) = STREAM(picture.used);
EXPOSED(picture.grab_time) = STREAM(picture.grab_time);
EXPOSED(picture.encode_begin_time) = STREAM(picture.encode_begin_time);
EXPOSED(picture.encode_end_time) = STREAM(picture.encode_end_time);
EXPOSED(width) = STREAM(width);
EXPOSED(height) = STREAM(height);
EXPOSED(online) = true;
EXPOSED(dropped) = 0;
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
EXPOSED(expose_end_time) = get_now_monotonic();
EXPOSED(expose_cmp_ts) = EXPOSED(expose_begin_ts);
EXPOSED(expose_end_ts) = get_now_monotonic();
LOG_VERBOSE("HTTP: exposed new frame; full exposition time = %.06Lf",
EXPOSED(expose_end_time) - EXPOSED(expose_begin_time));
LOG_VERBOSE("HTTP: Exposed new frame; full exposition time = %.06Lf",
EXPOSED(expose_end_ts) - EXPOSED(expose_begin_ts));
# undef EXPOSED
# undef STREAM
return true; // Updated
}
static bool _expose_blank_picture(struct http_server_t *server) {
# define BLANK(_next) server->run->blank->_next
# define EXPOSED(_next) server->run->exposed->_next
# define EXPOSED(_next) server->run->exposed->_next
EXPOSED(expose_begin_time) = get_now_monotonic();
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
EXPOSED(expose_begin_ts) = get_now_monotonic();
EXPOSED(expose_cmp_ts) = EXPOSED(expose_begin_ts);
if (EXPOSED(online) || EXPOSED(picture.used) == 0) {
if (EXPOSED(picture.allocated) < BLANK(picture.used)) {
A_REALLOC(EXPOSED(picture.data), BLANK(picture.used));
EXPOSED(picture.allocated) = BLANK(picture.used);
# define EXPOSE_BLANK picture_copy(server->run->blank, EXPOSED(picture))
if (EXPOSED(online)) { // Если переходим из online в offline
if (server->last_as_blank < 0) { // Если last_as_blank выключено, просто покажем картинку
LOG_INFO("HTTP: Changed picture to BLANK");
EXPOSE_BLANK;
} else if (server->last_as_blank > 0) { // Если нужен таймер - запустим
LOG_INFO("HTTP: Freezing last alive frame for %d seconds", server->last_as_blank);
EXPOSED(last_as_blank_ts) = get_now_monotonic();
} else { // last_as_blank == 0 - показываем последний фрейм вечно
LOG_INFO("HTTP: Freezing last alive frame forever");
}
memcpy(EXPOSED(picture.data), BLANK(picture.data), BLANK(picture.used));
EXPOSED(picture.used) = BLANK(picture.used);
EXPOSED(picture.grab_time) = 0;
EXPOSED(picture.encode_begin_time) = 0;
EXPOSED(picture.encode_end_time) = 0;
EXPOSED(width) = BLANK(width);
EXPOSED(height) = BLANK(height);
EXPOSED(captured_fps) = 0;
EXPOSED(online) = false;
goto updated;
}
if ( // Если уже оффлайн, включена фича last_as_blank с таймером и он запущен
server->last_as_blank > 0
&& EXPOSED(last_as_blank_ts) > 0
&& EXPOSED(last_as_blank_ts) + server->last_as_blank < EXPOSED(expose_begin_ts)
) {
LOG_INFO("HTTP: Changed last alive frame to BLANK");
EXPOSE_BLANK;
EXPOSED(last_as_blank_ts) = 0; // Останавливаем таймер
goto updated;
}
# undef EXPOSE_BLANK
if (EXPOSED(dropped) < server->run->drop_same_frames_blank) {
LOG_PERF("HTTP: dropped same frame (BLANK) number %u", EXPOSED(dropped));
LOG_PERF("HTTP: Dropped same frame (BLANK) number %u", EXPOSED(dropped));
EXPOSED(dropped) += 1;
EXPOSED(expose_end_time) = get_now_monotonic();
EXPOSED(expose_end_ts) = get_now_monotonic();
return false; // Not updated
}
updated:
EXPOSED(captured_fps) = 0;
EXPOSED(online) = false;
EXPOSED(dropped) = 0;
EXPOSED(expose_end_time) = get_now_monotonic();
EXPOSED(expose_end_ts) = get_now_monotonic();
return true; // Updated
# undef EXPOSED
# undef BLANK
}
static void _format_bufferevent_reason(short what, char *reason) {
char perror_buf[1024] = {0};
char *perror_ptr = errno_to_string(EVUTIL_SOCKET_ERROR(), perror_buf, 1024); // evutil_socket_error_to_string() is not thread-sage
bool first = true;
strcat(reason, perror_ptr);
strcat(reason, " (");
# define FILL_REASON(_bev, _name) { \
if (what & _bev) { \
if (first) { \
first = false; \
} else { \
strcat(reason, ","); \
} \
strcat(reason, _name); \
} \
}
FILL_REASON(BEV_EVENT_READING, "reading");
FILL_REASON(BEV_EVENT_WRITING, "writing");
FILL_REASON(BEV_EVENT_ERROR, "error");
FILL_REASON(BEV_EVENT_TIMEOUT, "timeout");
FILL_REASON(BEV_EVENT_EOF, "eof"); // cppcheck-suppress unreadVariable
# undef FILL_REASON
strcat(reason, ")");
}

View File

@@ -30,11 +30,9 @@
#include <event2/http.h>
#include <event2/util.h>
#include "../tools.h"
#include "../picture.h"
#include "../stream.h"
#include "blank.h"
struct stream_client_t {
struct http_server_t *server;
@@ -58,16 +56,19 @@ struct stream_client_t {
};
struct exposed_t {
struct picture_t picture;
unsigned width;
unsigned height;
struct picture_t *picture;
unsigned captured_fps;
unsigned queued_fps;
bool online;
unsigned dropped;
long double expose_begin_time;
long double expose_cmp_time;
long double expose_end_time;
long double expose_begin_ts;
long double expose_cmp_ts;
long double expose_end_ts;
long double last_as_blank_ts;
bool notify_last_online;
unsigned notify_last_width;
unsigned notify_last_height;
};
struct http_server_runtime_t {
@@ -80,7 +81,7 @@ struct http_server_runtime_t {
struct exposed_t *exposed;
struct stream_client_t *stream_clients;
unsigned stream_clients_count;
struct blank_t *blank;
struct picture_t *blank;
unsigned drop_same_frames_blank;
};
@@ -90,18 +91,23 @@ struct http_server_t {
char *unix_path;
bool unix_rm;
mode_t unix_mode;
bool tcp_nodelay;
unsigned timeout;
char *user;
char *passwd;
char *static_path;
char *allow_origin;
char *blank_path;
int last_as_blank;
unsigned drop_same_frames;
bool slowdown;
unsigned fake_width;
unsigned fake_height;
bool notify_parent;
struct http_server_runtime_t *run;
};

View File

@@ -27,7 +27,6 @@
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../tools.h"
@@ -43,6 +42,7 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
simplified_path = simplify_request_path(request_path);
if (simplified_path[0] == '\0') {
LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path);
goto error;
}
@@ -51,13 +51,14 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
# define LOAD_STAT { \
if (lstat(path, &st) < 0) { \
/* LOG_PERROR("Can't stat() file %s", path); */ \
LOG_VERBOSE_PERROR("HTTP: Can't stat() static path %s", path); \
goto error; \
} \
}
LOAD_STAT;
if (S_ISDIR(st.st_mode)) {
LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path);
strcat(path, "/index.html");
LOAD_STAT;
}
@@ -65,12 +66,12 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
# undef LOAD_STAT
if (!S_ISREG(st.st_mode)) {
// LOG_ERROR("Not a regulary file: %s", path);
LOG_VERBOSE("HTTP: Not a regular file: %s", path);
goto error;
}
if (access(path, R_OK) < 0) {
// LOG_PERROR("Can't access() R_OK file %s", path);
LOG_VERBOSE_PERROR("HTTP: Can't access() R_OK file %s", path);
goto error;
}

85
src/http/unix.c Normal file
View File

@@ -0,0 +1,85 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "unix.h"
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <event2/http.h>
#include <event2/util.h>
#include "../tools.h"
#include "../logging.h"
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) {
evutil_socket_t fd = -1;
struct sockaddr_un addr;
# define MAX_SUN_PATH (sizeof(addr.sun_path) - 1)
if (strlen(path) > MAX_SUN_PATH) {
LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
return -1;
}
MEMSET_ZERO(addr);
strncpy(addr.sun_path, path, MAX_SUN_PATH);
addr.sun_family = AF_UNIX;
# undef MAX_SUN_PATH
assert((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0);
assert(!evutil_make_socket_nonblocking(fd));
if (rm && unlink(path) < 0) {
if (errno != ENOENT) {
LOG_PERROR("Can't remove old UNIX socket '%s'", path);
return -1;
}
}
if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
LOG_PERROR("Can't bind HTTP to UNIX socket '%s'", path);
return -1;
}
if (mode && chmod(path, mode) < 0) {
LOG_PERROR("Can't set permissions %o to UNIX socket '%s'", mode, path);
return -1;
}
if (listen(fd, 128) < 0) {
LOG_PERROR("Can't listen UNIX socket '%s'", path);
return -1;
}
if (evhttp_accept_socket(http, fd) < 0) {
LOG_PERROR("Can't evhttp_accept_socket() UNIX socket '%s'", path);
return -1;
}
return fd;
}

33
src/http/unix.h Normal file
View File

@@ -0,0 +1,33 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 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 <stdbool.h>
#include <sys/stat.h>
#include <event2/http.h>
#include <event2/util.h>
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode);

54
src/http/uri.c Normal file
View File

@@ -0,0 +1,54 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "uri.h"
#include <stdbool.h>
#include <event2/http.h>
#include <event2/util.h>
#include <event2/keyvalq_struct.h>
bool uri_get_true(struct evkeyvalq *params, const char *key) {
const char *value_str;
if ((value_str = evhttp_find_header(params, key)) != NULL) {
if (
value_str[0] == '1'
|| !evutil_ascii_strcasecmp(value_str, "true")
|| !evutil_ascii_strcasecmp(value_str, "yes")
) {
return true;
}
}
return false;
}
char *uri_get_string(struct evkeyvalq *params, const char *key) {
const char *value_str;
if ((value_str = evhttp_find_header(params, key)) != NULL) {
return evhttp_encode_uri(value_str);
}
return NULL;
}

32
src/http/uri.h Normal file
View File

@@ -0,0 +1,32 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 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 <stdbool.h>
#include <event2/http.h>
#include <event2/keyvalq_struct.h>
bool uri_get_true(struct evkeyvalq *params, const char *key);
char *uri_get_string(struct evkeyvalq *params, const char *key);

34
src/logging.c Normal file
View File

@@ -0,0 +1,34 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include <stdbool.h>
#include <pthread.h>
#include "logging.h"
enum log_level_t log_level;
bool log_colored;
pthread_mutex_t log_mutex;

View File

@@ -23,8 +23,10 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
@@ -32,27 +34,43 @@
#include <pthread.h>
#include "tools.h"
#include "threading.h"
unsigned log_level;
pthread_mutex_t log_mutex;
enum log_level_t {
LOG_LEVEL_INFO,
LOG_LEVEL_PERF,
LOG_LEVEL_VERBOSE,
LOG_LEVEL_DEBUG,
};
#define LOG_LEVEL_INFO 0
#define LOG_LEVEL_PERF 1
#define LOG_LEVEL_VERBOSE 2
#define LOG_LEVEL_DEBUG 3
extern enum log_level_t log_level;
extern bool log_colored;
extern pthread_mutex_t log_mutex;
#define LOGGING_INIT { \
log_level = LOG_LEVEL_INFO; \
assert(!pthread_mutex_init(&log_mutex, NULL)); \
log_colored = isatty(1); \
A_MUTEX_INIT(&log_mutex); \
}
#define LOGGING_DESTROY assert(!pthread_mutex_destroy(&log_mutex))
#define LOGGING_DESTROY A_MUTEX_DESTROY(&log_mutex)
#define LOGGING_LOCK assert(!pthread_mutex_lock(&log_mutex))
#define LOGGING_UNLOCK assert(!pthread_mutex_unlock(&log_mutex))
#define LOGGING_LOCK A_MUTEX_LOCK(&log_mutex)
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&log_mutex)
#define COLOR_GRAY "\x1b[30;1m"
#define COLOR_RED "\x1b[31;1m"
#define COLOR_GREEN "\x1b[32;1m"
#define COLOR_YELLOW "\x1b[33;1m"
#define COLOR_BLUE "\x1b[34;1m"
#define COLOR_CYAN "\x1b[36;1m"
#define COLOR_RESET "\x1b[0m"
#define SEP_INFO(_ch) { \
@@ -71,66 +89,83 @@ pthread_mutex_t log_mutex;
} \
}
#define LOG_PRINTF_NOLOCK(_label, _msg, ...) { \
printf("-- " _label " [%.03Lf tid=%d] -- " _msg "\n", get_now_monotonic(), get_thread_id(), ##__VA_ARGS__); \
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
char _tname_buf[MAX_THREAD_NAME] = {0}; \
thread_get_name(_tname_buf); \
if (log_colored) { \
printf(COLOR_GRAY "-- " _label_color _label COLOR_GRAY " [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
} else { \
printf("-- " _label " [%.03Lf %9s] -- " _msg, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
} \
putchar('\n'); \
fflush(stdout); \
}
#define LOG_PRINTF(_label_color, _label, _msg_color, _msg, ...) { \
LOGGING_LOCK; \
LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \
}
#define LOG_ERROR(_msg, ...) { \
LOGGING_LOCK; \
LOG_PRINTF_NOLOCK("ERROR", _msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \
LOG_PRINTF(COLOR_RED, "ERROR", COLOR_RED, _msg, ##__VA_ARGS__); \
}
#define LOG_PERROR(_msg, ...) { \
char _buf[1024] = ""; \
char *_ptr = errno_to_string(_buf, 1024); \
LOGGING_LOCK; \
printf("-- ERROR [%.03Lf tid=%d] -- " _msg ": %s\n", get_now_monotonic(), get_thread_id(), ##__VA_ARGS__, _ptr); \
fflush(stdout); \
LOGGING_UNLOCK; \
char _perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \
}
#define LOG_INFO(_msg, ...) { \
LOGGING_LOCK; \
LOG_PRINTF_NOLOCK("INFO ", _msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \
LOG_PRINTF(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
}
#define LOG_INFO_NOLOCK(_msg, ...) { \
LOG_PRINTF_NOLOCK("INFO ", _msg, ##__VA_ARGS__); \
LOG_PRINTF_NOLOCK(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
}
#define LOG_PERF(_msg, ...) { \
if (log_level >= LOG_LEVEL_PERF) { \
LOGGING_LOCK; \
LOG_PRINTF_NOLOCK("PERF ", _msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \
LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \
} \
}
#define LOG_PERF_FPS(_msg, ...) { \
if (log_level >= LOG_LEVEL_PERF) { \
LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \
} \
}
#define LOG_VERBOSE(_msg, ...) { \
if (log_level >= LOG_LEVEL_VERBOSE) { \
LOGGING_LOCK; \
LOG_PRINTF_NOLOCK("VERB ", _msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \
} \
}
#define LOG_VERBOSE_PERROR(_msg, ...) { \
if (log_level >= LOG_LEVEL_VERBOSE) { \
char _perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
} \
}
#define LOG_DEBUG(_msg, ...) { \
if (log_level >= LOG_LEVEL_DEBUG) { \
LOGGING_LOCK; \
LOG_PRINTF_NOLOCK("DEBUG", _msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \
LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \
} \
}
INLINE char *errno_to_string(char *buf, size_t size) {
INLINE char *errno_to_string(int error, char *buf, size_t size) {
# if defined(__GLIBC__) && defined(_GNU_SOURCE)
return strerror_r(errno, buf, size);
return strerror_r(error, buf, size);
# else
strerror_r(errno, buf, size);
strerror_r(error, buf, size);
return buf;
# endif
}

View File

@@ -25,443 +25,36 @@
# error WTF dude? Asserts are good things!
#endif
#include <limits.h>
#if CHAR_BIT != 8
# error There are not 8 bits in a char!
#endif
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#include <signal.h>
#include <getopt.h>
#include <pthread.h>
#include "config.h"
#include "tools.h"
#include "threading.h"
#include "logging.h"
#include "options.h"
#include "device.h"
#include "encoder.h"
#include "stream.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio.h"
# include "gpio/gpio.h"
#endif
static const char _SHORT_OPTS[] = "d:i:r:x:y:m:a:f:z:ntb:w:q:c:s:p:U:DM:k:e:lR:hv"
#ifdef WITH_OMX
"g:"
#endif
;
static const struct option _LONG_OPTS[] = {
{"device", required_argument, NULL, 'd'},
{"input", required_argument, NULL, 'i'},
{"resolution", required_argument, NULL, 'r'},
{"width", required_argument, NULL, 'x'},
{"height", required_argument, NULL, 'y'},
{"format", required_argument, NULL, 'm'},
{"tv-standard", required_argument, NULL, 'a'},
{"desired-fps", required_argument, NULL, 'f'},
{"min-frame-size", required_argument, NULL, 'z'},
{"persistent", no_argument, NULL, 'n'},
{"dv-timings", no_argument, NULL, 't'},
{"buffers", required_argument, NULL, 'b'},
{"workers", required_argument, NULL, 'w'},
{"quality", required_argument, NULL, 'q'},
{"encoder", required_argument, NULL, 'c'},
# ifdef WITH_OMX
{"glitched-resolutions", required_argument, NULL, 'g'},
# endif
{"device-timeout", required_argument, NULL, 1000},
{"device-error-delay", required_argument, NULL, 1001},
{"brightness", required_argument, NULL, 2000},
{"brightness-auto", no_argument, NULL, 2001},
{"contrast", required_argument, NULL, 2002},
{"saturation", required_argument, NULL, 2003},
{"hue", required_argument, NULL, 2004},
{"hue-auto", no_argument, NULL, 2005},
{"gamma", required_argument, NULL, 2006},
{"sharpness", required_argument, NULL, 2007},
{"backlight-compensation", required_argument, NULL, 2008},
{"white-balance", required_argument, NULL, 2009},
{"white-balance-auto", no_argument, NULL, 2010},
{"gain", required_argument, NULL, 2011},
{"gain-auto", no_argument, NULL, 2012},
{"host", required_argument, NULL, 's'},
{"port", required_argument, NULL, 'p'},
{"unix", required_argument, NULL, 'U'},
{"unix-rm", no_argument, NULL, 'D'},
{"unix-mode", required_argument, NULL, 'M'},
{"user", required_argument, NULL, 3000},
{"passwd", required_argument, NULL, 3001},
{"static", required_argument, NULL, 3002},
{"blank", required_argument, NULL, 'k'},
{"drop-same-frames", required_argument, NULL, 'e'},
{"slowdown", no_argument, NULL, 'l'},
{"fake-resolution", required_argument, NULL, 'R'},
{"fake-width", required_argument, NULL, 3003},
{"fake-height", required_argument, NULL, 3004},
{"server-timeout", required_argument, NULL, 3005},
#ifdef WITH_GPIO
{"gpio-prog-running", required_argument, NULL, 4000},
{"gpio-stream-online", required_argument, NULL, 4001},
{"gpio-has-http-clients", required_argument, NULL, 4002},
{"gpio-workers-busy-at", required_argument, NULL, 4003},
#endif
{"perf", no_argument, NULL, 5000},
{"verbose", no_argument, NULL, 5001},
{"debug", no_argument, NULL, 5002},
{"log-level", required_argument, NULL, 5010},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0},
};
static void _version(bool nl) {
printf(VERSION);
# ifdef WITH_OMX
printf(" + OMX");
# endif
# ifdef WITH_GPIO
printf(" + GPIO");
# endif
if (nl) {
putchar('\n');
}
}
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
printf("\nuStreamer - Lightweight and fast MJPG-HTTP streamer\n");
printf("═══════════════════════════════════════════════════\n\n");
printf("Version: ");
_version(false);
printf("; license: GPLv3\n");
printf("Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com>\n\n");
printf("Capturing options:\n");
printf("══════════════════\n");
printf(" -d|--device </dev/path> ───────────── Path to V4L2 device. Default: %s.\n\n", dev->path);
printf(" -i|--input <N> ────────────────────── Input channel. Default: %u.\n\n", dev->input);
printf(" -r|--resolution <WxH> ─────────────── Initial image resolution. Default: %ux%u.\n\n", dev->width, dev->height);
printf(" -m|--format <fmt> ─────────────────── Image format.\n");
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
printf(" -a|--tv-standard <std> ────────────── Force TV standard.\n");
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
printf(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n\n");
printf(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device\n");
printf(" produces small-sized garbage frames. Default: disabled.\n\n");
printf(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. Default: disabled.\n\n");
printf(" -t|--dv-timings ───────────────────── Enable DV timings querying and events processing\n");
printf(" to automatic resolution change. Default: disabled.\n\n");
printf(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device.\n");
printf(" Each buffer may processed using an independent thread.\n");
printf(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n\n", dev->n_buffers);
printf(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers.\n");
printf(" Default: %u (the number of CPU cores (but not more than 4)).\n\n", dev->n_workers);
printf(" -q|--quality <N> ──────────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.\n\n", encoder->quality);
printf(" -c|--encoder <type> ───────────────── Use specified encoder. It may affect the number of workers.\n\n");
# ifdef WITH_OMX
printf(" -g|--glitched-resolutions <WxH,...> ─ Comma-separated list of resolutions that require forced\n");
# endif
printf(" encoding on CPU instead of OMX. Default: disabled.\n");
printf(" Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
printf(" --device-timeout <seconds> ────────── Timeout for device querying. Default: %u.\n\n", dev->timeout);
printf(" --device-error-delay <seconds> ────── Delay before trying to connect to the device again\n");
printf(" after an error (timeout for example). Default: %u.\n\n", dev->error_delay);
printf("Image control options:\n");
printf("══════════════════════\n");
printf(" --brightness <N> ───────────── Set brightness. Default: no change.\n\n");
printf(" --brightness-auto ──────────── Enable automatic brightness control. Default: no change.\n\n");
printf(" --contrast <N> ─────────────── Set contrast. Default: no change.\n\n");
printf(" --saturation <N> ───────────── Set saturation. Default: no change.\n\n");
printf(" --hue <N> ──────────────────── Set hue. Default: no change.\n\n");
printf(" --hue-auto ─────────────────── Enable automatic hue control. Default: no change.\n\n");
printf(" --gamma <N> ────────────────── Set gamma. Default: no change.\n\n");
printf(" --sharpness <N> ────────────── Set sharpness. Default: no change.\n\n");
printf(" --backlight-compensation <N> ─ Set backlight compensation. Default: no change.\n\n");
printf(" --white-balance <N> ────────── Set white balance. Default: no change.\n\n");
printf(" --white-balance-auto ───────── Enable automatic white balance control. Default: no change.\n\n");
printf(" --gain <N> ─────────────────── Set gain. Default: no change.\n\n");
printf(" --gain-auto ────────────────── Enable automatic gain control. Default: no change.\n\n");
printf("HTTP server options:\n");
printf("════════════════════\n");
printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s.\n\n", server->host);
printf(" -p|--port <N> ────────────── Bind to this TCP port. Default: %u.\n\n", server->port);
printf(" -U|--unix <path> ─────────── Bind to UNIX domain socket. Default: disabled.\n\n");
printf(" -D|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n\n");
printf(" -M|--unix-mode <mode> ────── Set UNIX socket file permissions (like 777). Default: disabled.\n\n");
printf(" --user <name> ────────────── HTTP basic auth user. Default: disabled.\n\n");
printf(" --passwd <str> ───────────── HTTP basic auth passwd. Default: empty.\n\n");
printf(" --static <path> ───────────── Path to dir with static files instead of embedded root index page.\n");
printf(" Symlinks are not supported for security reasons. Default: disabled.\n\n");
printf(" -k|--blank <path> ─────────── Path to JPEG file that will be shown when the device is disconnected\n");
printf(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n\n");
printf(" -e|--drop-same-frames <N> ── Don't send identical frames to clients, but no more than specified number.\n");
printf(" It can significantly reduce the outgoing traffic, but will increase\n");
printf(" the CPU loading. Don't use this option with analog signal sources\n");
printf(" or webcams, it's useless. Default: disabled.\n\n");
printf(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients are connected.\n");
printf(" Useful to reduce CPU consumption. Default: disabled.\n\n");
printf(" -R|--fake-resolution <WxH> ─ Override image resolution for state. Default: disabled.\n\n");
printf(" --server-timeout <seconds> ─ Timeout for client connections. Default: %u.\n\n", server->timeout);
#ifdef WITH_GPIO
printf("GPIO options:\n");
printf("═════════════\n");
printf(" --gpio-prog-running <pin> ───── Set 1 on GPIO pin while uStreamer is running. Default: disabled.\n\n");
printf(" --gpio-stream-online <pin> ──── Set 1 while streaming. Default: disabled\n\n");
printf(" --gpio-has-http-clients <pin> ─ Set 1 while stream has at least one client. Default: disabled.\n\n");
printf(" --gpio-workers-busy-at <pin> ── Set 1 on (pin + N) while worker with number N has a job.\n");
printf(" The worker's numbering starts from 0. Default: disabled\n\n");
#endif
printf("Misc options:\n");
printf("═════════════\n");
printf(" --log-level <N> ─ Verbosity level of messages from 0 (info) to 3 (debug).\n");
printf(" Enabling debugging messages can slow down the program.\n");
printf(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).\n");
printf(" Default: %u.\n\n", log_level);
printf(" --perf ────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n");
printf(" --verbose ─────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n");
printf(" --debug ───────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");
printf(" -h|--help ─────── Print this text and exit.\n\n");
printf(" -v|--version ──── Print version and exit.\n\n");
}
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited) {
unsigned tmp_width;
unsigned tmp_height;
if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) {
return -1;
}
if (limited) {
if (tmp_width < VIDEO_MIN_WIDTH || tmp_width > VIDEO_MAX_WIDTH) {
return -2;
}
if (tmp_height < VIDEO_MIN_HEIGHT || tmp_height > VIDEO_MAX_HEIGHT) {
return -3;
}
}
*width = tmp_width;
*height = tmp_height;
return 0;
}
#ifdef WITH_OMX
static int _parse_glitched_resolutions(const char *str, struct encoder_t *encoder) {
char *str_copy;
char *ptr;
unsigned count = 0;
unsigned width;
unsigned height;
assert((str_copy = strdup(str)) != NULL);
ptr = strtok(str_copy, ",;:\n\t ");
while (ptr != NULL) {
if (count >= MAX_GLITCHED_RESOLUTIONS) {
printf("Too big '--glitched-resolutions' list: maxlen=%u\n", MAX_GLITCHED_RESOLUTIONS);
goto error;
}
switch (_parse_resolution(ptr, &width, &height, true)) {
case -1:
printf("Invalid resolution format of '%s' in '--glitched-resolutions=%s\n", ptr, str_copy);
goto error;
case -2:
printf("Invalid width of '%s' in '--glitched-resolutions=%s: min=%u, max=%u\n",
ptr, str_copy, VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT);
goto error;
case -3:
printf("Invalid width of '%s' in '--glitched-resolutions=%s: min=%u, max=%u\n",
ptr, str_copy, VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT);
goto error;
case 0: break;
default: assert(0 && "Unknown error");
}
encoder->glitched_resolutions[count][0] = width;
encoder->glitched_resolutions[count][1] = height;
count += 1;
ptr = strtok(NULL, ",;:\n\t ");
}
encoder->n_glitched_resolutions = count;
free(str_copy);
return 0;
error:
free(str_copy);
return -1;
}
#endif
static int _parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
# define OPT_SET(_dest, _value) { \
_dest = _value; \
break; \
}
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%u, max=%u\n", _name, optarg, _min, _max); \
return -1; \
} \
_dest = _tmp; \
break; \
}
# define OPT_RESOLUTION(_name, _dest_width, _dest_height, _limited) { \
switch (_parse_resolution(optarg, &_dest_width, &_dest_height, _limited)) { \
case -1: \
printf("Invalid resolution format for '%s=%s'\n", _name, optarg); \
return -1; \
case -2: \
printf("Invalid width of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH); \
return -1; \
case -3: \
printf("Invalid height of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT); \
return -1; \
case 0: break; \
default: assert(0 && "Unknown error"); \
} \
break; \
}
# define OPT_RESOLUTION_OBSOLETE(_name, _replace, _dest, _min, _max) { \
printf("\n=== WARNING! The option '%s' is obsolete; use '%s' instead it ===\n\n", _name, _replace); \
OPT_NUMBER(_name, _dest, _min, _max, 0); \
}
# ifdef WITH_OMX
# define OPT_GLITCHED_RESOLUTIONS { \
if (_parse_glitched_resolutions(optarg, encoder) < 0) { \
return -1; \
} \
break; \
}
# endif
# define OPT_PARSE(_name, _dest, _func, _invalid) { \
if ((_dest = _func(optarg)) == _invalid) { \
printf("Unknown " _name ": %s\n", optarg); \
return -1; \
} \
break; \
}
# define OPT_CTL(_dest) { \
dev->ctl._dest.value_set = true; \
dev->ctl._dest.auto_set = false; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
break; \
}
# define OPT_CTL_AUTO(_dest) { \
dev->ctl._dest.value_set = false; \
dev->ctl._dest.auto_set = true; \
break; \
}
int index;
int ch;
log_level = LOG_LEVEL_INFO;
while ((ch = getopt_long(argc, argv, _SHORT_OPTS, _LONG_OPTS, &index)) >= 0) {
switch (ch) {
case 'd': OPT_SET(dev->path, optarg);
case 'i': OPT_NUMBER("--input", dev->input, 0, 128, 0);
case 'r': OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
case 'x': OPT_RESOLUTION_OBSOLETE("--width", "--resolution", dev->width, VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH);
case 'y': OPT_RESOLUTION_OBSOLETE("--height", "--resolution", dev->height, VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT);
# pragma GCC diagnostic ignored "-Wsign-compare"
# pragma GCC diagnostic push
case 'm': OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN);
# pragma GCC diagnostic pop
case 'a': OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN);
case 'f': OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
case 'z': OPT_NUMBER("--min-frame-size", dev->min_frame_size, 0, 8192, 0);
case 'n': OPT_SET(dev->persistent, true);
case 't': OPT_SET(dev->dv_timings, true);
case 'b': OPT_NUMBER("--buffers", dev->n_buffers, 1, 32, 0);
case 'w': OPT_NUMBER("--workers", dev->n_workers, 1, 32, 0);
case 'q': OPT_NUMBER("--quality", encoder->quality, 1, 100, 0);
case 'c': OPT_PARSE("encoder type", encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN);
# ifdef WITH_OMX
case 'g': OPT_GLITCHED_RESOLUTIONS;
# endif
case 1000: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0);
case 1001: OPT_NUMBER("--device-error-delay", dev->error_delay, 1, 60, 0);
case 2000: OPT_CTL(brightness);
case 2001: OPT_CTL_AUTO(brightness);
case 2002: OPT_CTL(contrast);
case 2003: OPT_CTL(saturation);
case 2004: OPT_CTL(hue);
case 2005: OPT_CTL_AUTO(hue);
case 2006: OPT_CTL(gamma);
case 2007: OPT_CTL(sharpness);
case 2008: OPT_CTL(backlight_compensation);
case 2009: OPT_CTL(white_balance);
case 2010: OPT_CTL_AUTO(white_balance);
case 2011: OPT_CTL(gain);
case 2012: OPT_CTL_AUTO(gain);
case 's': OPT_SET(server->host, optarg);
case 'p': OPT_NUMBER("--port", server->port, 1, 65535, 0);
case 'U': OPT_SET(server->unix_path, optarg);
case 'D': OPT_SET(server->unix_rm, true);
case 'M': OPT_NUMBER("--unix-mode", server->unix_mode, INT_MIN, INT_MAX, 8);
case 3000: OPT_SET(server->user, optarg);
case 3001: OPT_SET(server->passwd, optarg);
case 3002: OPT_SET(server->static_path, optarg);
case 'k': OPT_SET(server->blank_path, optarg);
case 'e': OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0);
case 'l': OPT_SET(server->slowdown, true);
case 'R': OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
case 3003: OPT_RESOLUTION_OBSOLETE("--fake-width", "--fake-resolution", server->fake_width, 0, UINT_MAX);
case 3004: OPT_RESOLUTION_OBSOLETE("--fake-height", "--fake-resolution", server->fake_height, 0, UINT_MAX);
case 3005: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
# ifdef WITH_GPIO
case 4000: OPT_NUMBER("--gpio-prog-running", gpio_pin_prog_running, 0, 256, 0);
case 4001: OPT_NUMBER("--gpio-stream-online", gpio_pin_stream_online, 0, 256, 0);
case 4002: OPT_NUMBER("--gpio-has-http-clients", gpio_pin_has_http_clients, 0, 256, 0);
case 4003: OPT_NUMBER("--gpio-workers-busy-at", gpio_pin_workers_busy_at, 0, 256, 0);
# endif
case 5000: OPT_SET(log_level, LOG_LEVEL_PERF);
case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
case 5002: OPT_SET(log_level, LOG_LEVEL_DEBUG);
case 5010: OPT_NUMBER("--log-level", log_level, 0, 3, 0);
case 'h': _help(dev, encoder, server); return 1;
case 'v': _version(true); return 1;
case 0: break;
default: _help(dev, encoder, server); return -1;
}
}
# undef OPT_CTL_AUTO
# undef OPT_CTL
# undef OPT_PARSE
# ifdef WITH_OMX
# undef OPT_GLITCHED_RESOLUTIONS
# endif
# undef OPT_RESOLUTION_OBSOLETE
# undef OPT_RESOLUTION
# undef OPT_NUMBER
# undef OPT_SET
return 0;
}
struct main_context_t {
struct _main_context_t {
struct stream_t *stream;
struct http_server_t *server;
};
static struct main_context_t *_ctx;
static struct _main_context_t *_ctx;
static void _block_thread_signals(void) {
sigset_t mask;
@@ -472,12 +65,14 @@ static void _block_thread_signals(void) {
}
static void *_stream_loop_thread(UNUSED void *arg) {
A_THREAD_RENAME("stream");
_block_thread_signals();
stream_loop(_ctx->stream);
return NULL;
}
static void *_server_loop_thread(UNUSED void *arg) {
A_THREAD_RENAME("http");
_block_thread_signals();
http_server_loop(_ctx->server);
return NULL;
@@ -509,6 +104,7 @@ static void _install_signal_handlers(void) {
}
int main(int argc, char *argv[]) {
struct options_t *options;
struct device_t *dev;
struct encoder_t *encoder;
struct stream_t *stream;
@@ -516,26 +112,24 @@ int main(int argc, char *argv[]) {
int exit_code = 0;
LOGGING_INIT;
# ifdef WITH_GPIO
GPIO_INIT;
# endif
A_THREAD_RENAME("main");
options = options_init(argc, argv);
dev = device_init();
encoder = encoder_init();
stream = stream_init(dev, encoder);
server = http_server_init(stream);
if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) {
if ((exit_code = options_parse(options, dev, encoder, server)) == 0) {
# ifdef WITH_GPIO
GPIO_INIT_PINOUT;
gpio_init();
# endif
_install_signal_handlers();
pthread_t stream_loop_tid;
pthread_t server_loop_tid;
struct main_context_t ctx;
struct _main_context_t ctx;
ctx.stream = stream;
ctx.server = server;
@@ -543,7 +137,7 @@ int main(int argc, char *argv[]) {
if ((exit_code = http_server_listen(server)) == 0) {
# ifdef WITH_GPIO
GPIO_SET_HIGH(prog_running);
gpio_set_prog_running(true);
# endif
A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
@@ -551,6 +145,11 @@ int main(int argc, char *argv[]) {
A_THREAD_JOIN(server_loop_tid);
A_THREAD_JOIN(stream_loop_tid);
}
# ifdef WITH_GPIO
gpio_set_prog_running(false);
gpio_destroy();
# endif
}
http_server_destroy(server);
@@ -558,10 +157,10 @@ int main(int argc, char *argv[]) {
encoder_destroy(encoder);
device_destroy(dev);
# ifdef WITH_GPIO
GPIO_SET_LOW(prog_running);
# endif
options_destroy(options);
if (exit_code == 0) {
LOG_INFO("Bye-bye");
}
LOGGING_DESTROY;
return (exit_code < 0 ? 1 : 0);
}

691
src/options.c Normal file
View File

@@ -0,0 +1,691 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "options.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <limits.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include "config.h"
#include "logging.h"
#include "process.h"
#include "device.h"
#include "encoder.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio/gpio.h"
#endif
enum _OPT_VALUES {
_O_DEVICE = 'd',
_O_INPUT = 'i',
_O_RESOLUTION = 'r',
_O_FORMAT = 'm',
_O_TV_STANDARD = 'a',
_O_IO_METHOD = 'I',
_O_DESIRED_FPS = 'f',
_O_MIN_FRAME_SIZE = 'z',
_O_PERSISTENT = 'n',
_O_DV_TIMINGS = 't',
_O_BUFFERS = 'b',
_O_WORKERS = 'w',
_O_QUALITY = 'q',
_O_ENCODER = 'c',
#ifdef WITH_OMX
_O_GLITCHED_RESOLUTIONS = 'g',
#endif
_O_HOST = 's',
_O_PORT = 'p',
_O_UNIX = 'U',
_O_UNIX_RM = 'D',
_O_UNIX_MODE = 'M',
_O_BLANK = 'k',
_O_LAST_AS_BLANK = 'K',
_O_DROP_SAME_FRAMES = 'e',
_O_SLOWDOWN = 'l',
_O_FAKE_RESOLUTION = 'R',
_O_HELP = 'h',
_O_VERSION = 'v',
// Longs only
_O_DEVICE_TIMEOUT = 10000,
_O_DEVICE_ERROR_DELAY,
_O_IMAGE_DEFAULT,
_O_BRIGHTNESS,
_O_CONTRAST,
_O_SATURATION,
_O_HUE,
_O_GAMMA,
_O_SHARPNESS,
_O_BACKLIGHT_COMPENSATION,
_O_WHITE_BALANCE,
_O_GAIN,
_O_COLOR_EFFECT,
_O_FLIP_VERTICAL,
_O_FLIP_HORIZONTAL,
_O_USER,
_O_PASSWD,
_O_STATIC,
_O_ALLOW_ORIGIN,
_O_TCP_NODELAY,
_O_SERVER_TIMEOUT,
#ifdef WITH_GPIO
_O_GPIO_DEVICE,
_O_GPIO_CONSUMER_PREFIX,
_O_GPIO_PROG_RUNNING,
_O_GPIO_STREAM_ONLINE,
_O_GPIO_HAS_HTTP_CLIENTS,
#endif
#ifdef HAS_PDEATHSIG
_O_EXIT_ON_PARENT_DEATH,
#endif
#ifdef WITH_SETPROCTITLE
_O_PROCESS_NAME_PREFIX,
#endif
_O_NOTIFY_PARENT,
_O_LOG_LEVEL,
_O_PERF,
_O_VERBOSE,
_O_DEBUG,
_O_FORCE_LOG_COLORS,
_O_NO_LOG_COLORS,
_O_FEATURES,
};
static const struct option _LONG_OPTS[] = {
{"device", required_argument, NULL, _O_DEVICE},
{"input", required_argument, NULL, _O_INPUT},
{"resolution", required_argument, NULL, _O_RESOLUTION},
{"format", required_argument, NULL, _O_FORMAT},
{"tv-standard", required_argument, NULL, _O_TV_STANDARD},
{"io-method", required_argument, NULL, _O_IO_METHOD},
{"desired-fps", required_argument, NULL, _O_DESIRED_FPS},
{"min-frame-size", required_argument, NULL, _O_MIN_FRAME_SIZE},
{"persistent", no_argument, NULL, _O_PERSISTENT},
{"dv-timings", no_argument, NULL, _O_DV_TIMINGS},
{"buffers", required_argument, NULL, _O_BUFFERS},
{"workers", required_argument, NULL, _O_WORKERS},
{"quality", required_argument, NULL, _O_QUALITY},
{"encoder", required_argument, NULL, _O_ENCODER},
# ifdef WITH_OMX
{"glitched-resolutions", required_argument, NULL, _O_GLITCHED_RESOLUTIONS},
# endif
{"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT},
{"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY},
{"image-default", no_argument, NULL, _O_IMAGE_DEFAULT},
{"brightness", required_argument, NULL, _O_BRIGHTNESS},
{"contrast", required_argument, NULL, _O_CONTRAST},
{"saturation", required_argument, NULL, _O_SATURATION},
{"hue", required_argument, NULL, _O_HUE},
{"gamma", required_argument, NULL, _O_GAMMA},
{"sharpness", required_argument, NULL, _O_SHARPNESS},
{"backlight-compensation", required_argument, NULL, _O_BACKLIGHT_COMPENSATION},
{"white-balance", required_argument, NULL, _O_WHITE_BALANCE},
{"gain", required_argument, NULL, _O_GAIN},
{"color-effect", required_argument, NULL, _O_COLOR_EFFECT},
{"flip-vertical", required_argument, NULL, _O_FLIP_VERTICAL},
{"flip-horizontal", required_argument, NULL, _O_FLIP_HORIZONTAL},
{"host", required_argument, NULL, _O_HOST},
{"port", required_argument, NULL, _O_PORT},
{"unix", required_argument, NULL, _O_UNIX},
{"unix-rm", no_argument, NULL, _O_UNIX_RM},
{"unix-mode", required_argument, NULL, _O_UNIX_MODE},
{"user", required_argument, NULL, _O_USER},
{"passwd", required_argument, NULL, _O_PASSWD},
{"static", required_argument, NULL, _O_STATIC},
{"blank", required_argument, NULL, _O_BLANK},
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
{"allow-origin", required_argument, NULL, _O_ALLOW_ORIGIN},
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
#ifdef WITH_GPIO
{"gpio-device", required_argument, NULL, _O_GPIO_DEVICE},
{"gpio-consumer-prefix", required_argument, NULL, _O_GPIO_CONSUMER_PREFIX},
{"gpio-prog-running", required_argument, NULL, _O_GPIO_PROG_RUNNING},
{"gpio-stream-online", required_argument, NULL, _O_GPIO_STREAM_ONLINE},
{"gpio-has-http-clients", required_argument, NULL, _O_GPIO_HAS_HTTP_CLIENTS},
#endif
#ifdef HAS_PDEATHSIG
{"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH},
#endif
#ifdef WITH_SETPROCTITLE
{"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX},
#endif
{"notify-parent", no_argument, NULL, _O_NOTIFY_PARENT},
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
{"perf", no_argument, NULL, _O_PERF},
{"verbose", no_argument, NULL, _O_VERBOSE},
{"debug", no_argument, NULL, _O_DEBUG},
{"force-log-colors", no_argument, NULL, _O_FORCE_LOG_COLORS},
{"no-log-colors", no_argument, NULL, _O_NO_LOG_COLORS},
{"help", no_argument, NULL, _O_HELP},
{"version", no_argument, NULL, _O_VERSION},
{"features", no_argument, NULL, _O_FEATURES},
{NULL, 0, NULL, 0},
};
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited);
#ifdef WITH_OMX
static int _parse_glitched_resolutions(const char *str, struct encoder_t *encoder);
#endif
static void _features(void);
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server);
struct options_t *options_init(int argc, char *argv[]) {
struct options_t *options;
A_CALLOC(options, 1);
options->argc = argc;
options->argv = argv;
A_CALLOC(options->argv_copy, argc);
for (int index = 0; index < argc; ++index) {
assert(options->argv_copy[index] = strdup(argv[index]));
}
return options;
}
void options_destroy(struct options_t *options) {
for (int index = 0; index < options->argc; ++index) {
free(options->argv_copy[index]);
}
free(options->argv_copy);
free(options);
}
int options_parse(struct options_t *options, struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
# define OPT_SET(_dest, _value) { \
_dest = _value; \
break; \
}
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
return -1; \
} \
_dest = _tmp; \
break; \
}
# define OPT_RESOLUTION(_name, _dest_width, _dest_height, _limited) { \
switch (_parse_resolution(optarg, &_dest_width, &_dest_height, _limited)) { \
case -1: \
printf("Invalid resolution format for '%s=%s'\n", _name, optarg); \
return -1; \
case -2: \
printf("Invalid width of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH); \
return -1; \
case -3: \
printf("Invalid height of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT); \
return -1; \
case 0: break; \
default: assert(0 && "Unknown error"); \
} \
break; \
}
# define OPT_PARSE(_name, _dest, _func, _invalid, _available) { \
if ((_dest = _func(optarg)) == _invalid) { \
printf("Unknown " _name ": %s; available: %s\n", optarg, _available); \
return -1; \
} \
break; \
}
# define OPT_CTL_DEFAULT_NOBREAK(_dest) { \
dev->ctl._dest.mode = CTL_MODE_DEFAULT; \
}
# define OPT_CTL_MANUAL(_dest) { \
if (!strcasecmp(optarg, "default")) { \
OPT_CTL_DEFAULT_NOBREAK(_dest); \
} else { \
dev->ctl._dest.mode = CTL_MODE_VALUE; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
} \
break; \
}
# define OPT_CTL_AUTO(_dest) { \
if (!strcasecmp(optarg, "default")) { \
OPT_CTL_DEFAULT_NOBREAK(_dest); \
} else if (!strcasecmp(optarg, "auto")) { \
dev->ctl._dest.mode = CTL_MODE_AUTO; \
} else { \
dev->ctl._dest.mode = CTL_MODE_VALUE; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
} \
break; \
}
int ch;
int short_index;
int opt_index;
char short_opts[1024] = {0};
# ifdef WITH_SETPROCTITLE
char *process_name_prefix = NULL;
# endif
for (short_index = 0, opt_index = 0; _LONG_OPTS[opt_index].name != NULL; ++opt_index) {
if (isalpha(_LONG_OPTS[opt_index].val)) {
short_opts[short_index] = _LONG_OPTS[opt_index].val;
++short_index;
if (_LONG_OPTS[opt_index].has_arg == required_argument) {
short_opts[short_index] = ':';
++short_index;
}
}
}
while ((ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0) {
switch (ch) {
case _O_DEVICE: OPT_SET(dev->path, optarg);
case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0);
case _O_RESOLUTION: OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
# pragma GCC diagnostic ignored "-Wsign-compare"
# pragma GCC diagnostic push
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN, FORMATS_STR);
# pragma GCC diagnostic pop
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN, STANDARDS_STR);
case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, device_parse_io_method, IO_METHOD_UNKNOWN, IO_METHODS_STR);
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 1, 8192, 0);
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true);
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_buffers, 1, 32, 0);
case _O_WORKERS: OPT_NUMBER("--workers", dev->n_workers, 1, 32, 0);
case _O_QUALITY: OPT_NUMBER("--quality", encoder->quality, 1, 100, 0);
case _O_ENCODER: OPT_PARSE("encoder type", encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
# ifdef WITH_OMX
case _O_GLITCHED_RESOLUTIONS:
if (_parse_glitched_resolutions(optarg, encoder) < 0) {
return -1;
}
break;
# endif
case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0);
case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", dev->error_delay, 1, 60, 0);
case _O_IMAGE_DEFAULT:
OPT_CTL_DEFAULT_NOBREAK(brightness);
OPT_CTL_DEFAULT_NOBREAK(contrast);
OPT_CTL_DEFAULT_NOBREAK(saturation);
OPT_CTL_DEFAULT_NOBREAK(hue);
OPT_CTL_DEFAULT_NOBREAK(gamma);
OPT_CTL_DEFAULT_NOBREAK(sharpness);
OPT_CTL_DEFAULT_NOBREAK(backlight_compensation);
OPT_CTL_DEFAULT_NOBREAK(white_balance);
OPT_CTL_DEFAULT_NOBREAK(gain);
OPT_CTL_DEFAULT_NOBREAK(color_effect);
OPT_CTL_DEFAULT_NOBREAK(flip_vertical);
OPT_CTL_DEFAULT_NOBREAK(flip_horizontal);
break;
case _O_BRIGHTNESS: OPT_CTL_AUTO(brightness);
case _O_CONTRAST: OPT_CTL_MANUAL(contrast);
case _O_SATURATION: OPT_CTL_MANUAL(saturation);
case _O_HUE: OPT_CTL_AUTO(hue);
case _O_GAMMA: OPT_CTL_MANUAL(gamma);
case _O_SHARPNESS: OPT_CTL_MANUAL(sharpness);
case _O_BACKLIGHT_COMPENSATION: OPT_CTL_MANUAL(backlight_compensation);
case _O_WHITE_BALANCE: OPT_CTL_AUTO(white_balance);
case _O_GAIN: OPT_CTL_AUTO(gain);
case _O_COLOR_EFFECT: OPT_CTL_MANUAL(color_effect);
case _O_FLIP_VERTICAL: OPT_CTL_MANUAL(flip_vertical);
case _O_FLIP_HORIZONTAL: OPT_CTL_MANUAL(flip_horizontal);
case _O_HOST: OPT_SET(server->host, optarg);
case _O_PORT: OPT_NUMBER("--port", server->port, 1, 65535, 0);
case _O_UNIX: OPT_SET(server->unix_path, optarg);
case _O_UNIX_RM: OPT_SET(server->unix_rm, true);
case _O_UNIX_MODE: OPT_NUMBER("--unix-mode", server->unix_mode, INT_MIN, INT_MAX, 8);
case _O_USER: OPT_SET(server->user, optarg);
case _O_PASSWD: OPT_SET(server->passwd, optarg);
case _O_STATIC: OPT_SET(server->static_path, optarg);
case _O_BLANK: OPT_SET(server->blank_path, optarg);
case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", server->last_as_blank, 0, 86400, 0);
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0);
case _O_SLOWDOWN: OPT_SET(server->slowdown, true);
case _O_FAKE_RESOLUTION: OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
case _O_ALLOW_ORIGIN: OPT_SET(server->allow_origin, optarg);
case _O_TCP_NODELAY: OPT_SET(server->tcp_nodelay, true);
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
# ifdef WITH_GPIO
case _O_GPIO_DEVICE: OPT_SET(gpio.path, optarg);
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(gpio.consumer_prefix, optarg);
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio.prog_running.pin, 0, 256, 0);
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", gpio.stream_online.pin, 0, 256, 0);
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", gpio.has_http_clients.pin, 0, 256, 0);
# endif
# ifdef HAS_PDEATHSIG
case _O_EXIT_ON_PARENT_DEATH:
if (process_track_parent_death() < 0) {
return -1;
};
break;
# endif
# ifdef WITH_SETPROCTITLE
case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg);
# endif
case _O_NOTIFY_PARENT: OPT_SET(server->notify_parent, true);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(log_level, LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(log_level, LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(log_colored, false);
case _O_HELP: _help(dev, encoder, server); return 1;
case _O_VERSION: puts(VERSION); return 1;
case _O_FEATURES: _features(); return 1;
case 0: break;
default: _help(dev, encoder, server); return -1;
}
}
# ifdef WITH_SETPROCTITLE
if (process_name_prefix != NULL) {
process_set_name_prefix(options->argc, options->argv, process_name_prefix);
}
# endif
# undef OPT_CTL_AUTO
# undef OPT_CTL_MANUAL
# undef OPT_CTL_DEFAULT_NOBREAK
# undef OPT_PARSE
# undef OPT_RESOLUTION
# undef OPT_NUMBER
# undef OPT_SET
return 0;
}
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited) {
unsigned tmp_width;
unsigned tmp_height;
if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) {
return -1;
}
if (limited) {
if (tmp_width < VIDEO_MIN_WIDTH || tmp_width > VIDEO_MAX_WIDTH) {
return -2;
}
if (tmp_height < VIDEO_MIN_HEIGHT || tmp_height > VIDEO_MAX_HEIGHT) {
return -3;
}
}
*width = tmp_width;
*height = tmp_height;
return 0;
}
#ifdef WITH_OMX
static int _parse_glitched_resolutions(const char *str, struct encoder_t *encoder) {
char *str_copy;
char *ptr;
unsigned count = 0;
unsigned width;
unsigned height;
assert((str_copy = strdup(str)) != NULL);
ptr = strtok(str_copy, ",;:\n\t ");
while (ptr != NULL) {
if (count >= MAX_GLITCHED_RESOLUTIONS) {
printf("Too big '--glitched-resolutions' list: maxlen=%u\n", MAX_GLITCHED_RESOLUTIONS);
goto error;
}
switch (_parse_resolution(ptr, &width, &height, true)) {
case -1:
printf("Invalid resolution format of '%s' in '--glitched-resolutions=%s\n", ptr, str_copy);
goto error;
case -2:
printf("Invalid width of '%s' in '--glitched-resolutions=%s: min=%u, max=%u\n",
ptr, str_copy, VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT);
goto error;
case -3:
printf("Invalid width of '%s' in '--glitched-resolutions=%s: min=%u, max=%u\n",
ptr, str_copy, VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT);
goto error;
case 0: break;
default: assert(0 && "Unknown error");
}
encoder->glitched_resolutions[count][0] = width;
encoder->glitched_resolutions[count][1] = height;
count += 1;
ptr = strtok(NULL, ",;:\n\t ");
}
encoder->n_glitched_resolutions = count;
free(str_copy);
return 0;
error:
free(str_copy);
return -1;
}
#endif
static void _features(void) {
# ifdef WITH_OMX
puts("+ WITH_OMX");
# else
puts("- WITH_OMX");
# endif
# ifdef WITH_GPIO
puts("+ WITH_GPIO");
# else
puts("- WITH_GPIO");
# endif
# ifdef WITH_PTHREAD_NP
puts("+ WITH_PTHREAD_NP");
# else
puts("- WITH_PTHREAD_NP");
# endif
# ifdef WITH_SETPROCTITLE
puts("+ WITH_SETPROCTITLE");
# else
puts("- WITH_SETPROCTITLE");
# endif
# ifdef HAS_PDEATHSIG
puts("+ HAS_PDEATHSIG");
# else
puts("- HAS_PDEATHSIG");
# endif
}
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
printf("\nuStreamer - Lightweight and fast MJPG-HTTP streamer\n");
printf("═══════════════════════════════════════════════════\n\n");
printf("Version: %s; license: GPLv3\n", VERSION);
printf("Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com>\n\n");
printf("Capturing options:\n");
printf("══════════════════\n");
printf(" -d|--device </dev/path> ───────────── Path to V4L2 device. Default: %s.\n\n", dev->path);
printf(" -i|--input <N> ────────────────────── Input channel. Default: %u.\n\n", dev->input);
printf(" -r|--resolution <WxH> ─────────────── Initial image resolution. Default: %ux%u.\n\n", dev->width, dev->height);
printf(" -m|--format <fmt> ─────────────────── Image format.\n");
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
printf(" -a|--tv-standard <std> ────────────── Force TV standard.\n");
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
printf(" -I|--io-method <method> ───────────── Set V4L2 IO method (see kernel documentation).\n");
printf(" Changing of this parameter may increase the performance. Or not.\n");
printf(" Available: %s; default: MMAP\n\n", IO_METHODS_STR);
printf(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n\n");
printf(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device\n");
printf(" produces small-sized garbage frames. Default: %zu bytes.\n\n", dev->min_frame_size);
printf(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. Default: disabled.\n\n");
printf(" -t|--dv-timings ───────────────────── Enable DV timings querying and events processing\n");
printf(" to automatic resolution change. Default: disabled.\n\n");
printf(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device.\n");
printf(" Each buffer may processed using an independent thread.\n");
printf(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n\n", dev->n_buffers);
printf(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers.\n");
printf(" Default: %u (the number of CPU cores (but not more than 4)).\n\n", dev->n_workers);
printf(" -q|--quality <N> ──────────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.\n", encoder->quality);
printf(" Note: If HW encoding is used (JPEG source format selected),\n");
printf(" this parameter attempts to configure the camera\n");
printf(" or capture device hardware's internal encoder.\n");
printf(" It does not re-encode MJPG to MJPG to change the quality level\n");
printf(" for sources that already output MJPG.\n\n");
printf(" -c|--encoder <type> ───────────────── Use specified encoder. It may affect the number of workers.\n");
printf(" Available:\n");
printf(" * CPU ─ Software MJPG encoding (default);\n");
# ifdef WITH_OMX
printf(" * OMX ─ GPU hardware accelerated MJPG encoding with OpenMax;\n");
# endif
printf(" * HW ── Use pre-encoded MJPG frames directly from camera hardware.\n\n");
# ifdef WITH_OMX
printf(" -g|--glitched-resolutions <WxH,...> ─ Comma-separated list of resolutions that require forced\n");
printf(" encoding on CPU instead of OMX. Default: disabled.\n\n");
# endif
printf(" --device-timeout <sec> ────────────── Timeout for device querying. Default: %u.\n\n", dev->timeout);
printf(" --device-error-delay <sec> ────────── Delay before trying to connect to the device again\n");
printf(" after an error (timeout for example). Default: %u.\n\n", dev->error_delay);
printf("Image control options:\n");
printf("══════════════════════\n");
printf(" --image-default ────────────────────── Reset all image settings bellow to default. Default: no change.\n\n");
printf(" --brightness <N|auto|default> ──────── Set brightness. Default: no change.\n\n");
printf(" --contrast <N|default> ─────────────── Set contrast. Default: no change.\n\n");
printf(" --saturation <N|default> ───────────── Set saturation. Default: no change.\n\n");
printf(" --hue <N|auto|default> ─────────────── Set hue. Default: no change.\n\n");
printf(" --gamma <N|default> ─────────────────── Set gamma. Default: no change.\n\n");
printf(" --sharpness <N|default> ────────────── Set sharpness. Default: no change.\n\n");
printf(" --backlight-compensation <N|default> ─ Set backlight compensation. Default: no change.\n\n");
printf(" --white-balance <N|auto|default> ───── Set white balance. Default: no change.\n\n");
printf(" --gain <N|auto|default> ────────────── Set gain. Default: no change.\n\n");
printf(" --color-effect <N|default> ─────────── Set color effect. Default: no change.\n\n");
printf(" --flip-vertical <1|0|default> ──────── Set vertical flip. Default: no change.\n\n");
printf(" --flip-horizontal <1|0|default> ────── Set horizontal flip. Default: no change.\n\n");
printf(" Hint: use v4l2-ctl --list-ctrls-menus to query available controls of the device.\n\n");
printf("HTTP server options:\n");
printf("════════════════════\n");
printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s.\n\n", server->host);
printf(" -p|--port <N> ────────────── Bind to this TCP port. Default: %u.\n\n", server->port);
printf(" -U|--unix <path> ─────────── Bind to UNIX domain socket. Default: disabled.\n\n");
printf(" -D|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n\n");
printf(" -M|--unix-mode <mode> ────── Set UNIX socket file permissions (like 777). Default: disabled.\n\n");
printf(" --user <name> ────────────── HTTP basic auth user. Default: disabled.\n\n");
printf(" --passwd <str> ───────────── HTTP basic auth passwd. Default: empty.\n\n");
printf(" --static <path> ───────────── Path to dir with static files instead of embedded root index page.\n");
printf(" Symlinks are not supported for security reasons. Default: disabled.\n\n");
printf(" -k|--blank <path> ────────── Path to JPEG file that will be shown when the device is disconnected\n");
printf(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n\n");
printf(" -K|--last-as-blank <sec> ─── Show the last frame received from the camera after it was disconnected,\n");
printf(" but no more than specified time (or endlessly if 0 is specified).\n");
printf(" If the device has not yet been online, display 'NO SIGNAL' or the image\n");
printf(" specified by option --blank. Default: disabled.\n\n");
printf(" -e|--drop-same-frames <N> ── Don't send identical frames to clients, but no more than specified number.\n");
printf(" It can significantly reduce the outgoing traffic, but will increase\n");
printf(" the CPU loading. Don't use this option with analog signal sources\n");
printf(" or webcams, it's useless. Default: disabled.\n\n");
printf(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients are connected.\n");
printf(" Useful to reduce CPU consumption. Default: disabled.\n\n");
printf(" -R|--fake-resolution <WxH> ─ Override image resolution for the /state. Default: disabled.\n\n");
printf(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Ignored for --unix.\n");
printf(" Default: disabled.\n\n");
printf(" --allow-origin <str> ─────── Set Access-Control-Allow-Origin header. Default: disabled.\n\n");
printf(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n\n", server->timeout);
#ifdef WITH_GPIO
printf("GPIO options:\n");
printf("═════════════\n");
printf(" --gpio-device </dev/path> ───── Path to GPIO character device. Default: %s.\n\n", gpio.path);
printf(" --gpio-consumer-prefix <str> ── Consumer prefix for GPIO outputs. Default: %s.\n\n", gpio.consumer_prefix);
printf(" --gpio-prog-running <pin> ───── Set 1 on GPIO pin while uStreamer is running. Default: disabled.\n\n");
printf(" --gpio-stream-online <pin> ──── Set 1 while streaming. Default: disabled\n\n");
printf(" --gpio-has-http-clients <pin> ─ Set 1 while stream has at least one client. Default: disabled.\n\n");
#endif
#if (defined(HAS_PDEATHSIG) || defined(WITH_SETPROCTITLE))
printf("Process options:\n");
printf("════════════════\n");
#endif
#ifdef HAS_PDEATHSIG
printf(" --exit-on-parent-death ─────── Exit the program if the parent process is dead. Default: disabled.\n\n");
#endif
#ifdef WITH_SETPROCTITLE
printf(" --process-name-prefix <str> ── Set process name prefix which will be displayed in the process list\n");
printf(" like '<str>: ustreamer --blah-blah-blah'. Default: disabled.\n\n");
printf(" --notify-parent ────────────── Send SIGUSR2 to the parent process when the stream parameters are changed.\n");
printf(" Checking changes is performed for the online flag and image resolution.\n\n");
#endif
printf("Logging options:\n");
printf("════════════════\n");
printf(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).\n");
printf(" Enabling debugging messages can slow down the program.\n");
printf(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).\n");
printf(" Default: %d.\n\n", log_level);
printf(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n");
printf(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n");
printf(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");
printf(" --force-log-colors ─ Force color logging. Default: colored if stdout is a TTY.\n\n");
printf(" --no-log-colors ──── Disable color logging. Default: ditto.\n\n");
printf("Help options:\n");
printf("═════════════\n");
printf(" -h|--help ─────── Print this text and exit.\n\n");
printf(" -v|--version ──── Print version and exit.\n\n");
printf(" --features ────── Print list of supported features.\n\n");
}

40
src/options.h Normal file
View File

@@ -0,0 +1,40 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 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 "device.h"
#include "encoder.h"
#include "http/server.h"
struct options_t {
int argc;
char **argv;
char **argv_copy;
};
struct options_t *options_init(int argc, char *argv[]);
void options_destroy(struct options_t *options);
int options_parse(struct options_t *options, struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server);

98
src/picture.c Normal file
View File

@@ -0,0 +1,98 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "picture.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include "tools.h"
#include "logging.h"
struct picture_t *picture_init(void) {
struct picture_t *picture;
A_CALLOC(picture, 1);
return picture;
}
void picture_destroy(struct picture_t *picture) {
if (picture->data) {
free(picture->data);
}
free(picture);
}
size_t picture_get_generous_size(unsigned width, unsigned height) {
return ((width * height) << 1) * 2;
}
void picture_realloc_data(struct picture_t *picture, size_t size) {
if (picture->allocated < size) {
LOG_DEBUG("Increasing picture %p buffer: %zu -> %zu (+%zu)",
picture, picture->allocated, size, size - picture->allocated);
A_REALLOC(picture->data, size);
picture->allocated = size;
}
}
void picture_set_data(struct picture_t *picture, const unsigned char *data, size_t size) {
picture_realloc_data(picture, size);
memcpy(picture->data, data, size);
picture->used = size;
}
void picture_append_data(struct picture_t *picture, const unsigned char *data, size_t size) {
size_t new_used = picture->used + size;
picture_realloc_data(picture, new_used);
memcpy(picture->data + picture->used, data, size);
picture->used = new_used;
}
void picture_copy(const struct picture_t *src, struct picture_t *dest) {
picture_set_data(dest, src->data, src->used);
# define COPY(_field) dest->_field = src->_field
COPY(used);
COPY(width);
COPY(height);
COPY(grab_ts);
COPY(encode_begin_ts);
COPY(encode_end_ts);
# undef COPY
}
bool picture_compare(const struct picture_t *a, const struct picture_t *b) {
return (
a->allocated && b->allocated
&& a->used == b->used
&& !memcmp(a->data, b->data, b->used)
);
}

51
src/picture.h Normal file
View File

@@ -0,0 +1,51 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 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 <stddef.h>
#include <stdbool.h>
struct picture_t {
unsigned char *data;
size_t used;
size_t allocated;
unsigned width;
unsigned height;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
};
struct picture_t *picture_init(void);
void picture_destroy(struct picture_t *picture);
size_t picture_get_generous_size(unsigned width, unsigned height);
void picture_realloc_data(struct picture_t *picture, size_t size);
void picture_set_data(struct picture_t *picture, const unsigned char *data, size_t size);
void picture_append_data(struct picture_t *picture, const unsigned char *data, size_t size);
void picture_copy(const struct picture_t *src, struct picture_t *dest);
bool picture_compare(const struct picture_t *a, const struct picture_t *b);

139
src/process.h Normal file
View File

@@ -0,0 +1,139 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 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 <signal.h>
#include <unistd.h>
#include <sys/types.h>
#if defined(__linux__)
# define HAS_PDEATHSIG
#elif defined(__FreeBSD__)
# include <sys/param.h>
# if __FreeBSD_version >= 1102000
# define HAS_PDEATHSIG
# endif
#endif
#ifdef WITH_SETPROCTITLE
# include <stdlib.h>
# include <string.h>
# if defined(__linux__)
# include <bsd/unistd.h>
# elif (defined(__FreeBSD__) || defined(__DragonFly__))
//# include <unistd.h>
//# include <sys/types.h>
# elif (defined(__NetBSD__) || defined(__OpenBSD__)) // setproctitle() placed in stdlib.h
# else
# error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0
# endif
#endif
#ifdef HAS_PDEATHSIG
# if defined(__linux__)
# include <sys/prctl.h>
# elif defined(__FreeBSD__)
# include <sys/procctl.h>
# endif
#endif
#ifdef WITH_SETPROCTITLE
# include "tools.h"
#endif
#ifdef HAS_PDEATHSIG
# include "logging.h"
#endif
#ifdef WITH_SETPROCTITLE
extern char **environ;
#endif
#ifdef HAS_PDEATHSIG
INLINE int process_track_parent_death(void) {
pid_t parent = getppid();
int signum = SIGTERM;
# if defined(__linux__)
int retval = prctl(PR_SET_PDEATHSIG, signum);
# elif defined(__FreeBSD__)
int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
# else
# error WTF?
# endif
if (retval < 0) {
LOG_PERROR("Can't set to receive SIGTERM on parent process death");
return -1;
}
if (kill(parent, 0) < 0) {
LOG_PERROR("The parent process %d is already dead", parent);
return -1;
}
return 0;
}
#endif
#ifdef WITH_SETPROCTITLE
# pragma GCC diagnostic ignored "-Wunused-parameter"
# pragma GCC diagnostic push
INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix) {
# pragma GCC diagnostic pop
char *cmdline = NULL;
size_t allocated = 2048;
size_t used = 0;
A_REALLOC(cmdline, allocated);
cmdline[0] = '\0';
for (int index = 0; index < argc; ++index) {
size_t arg_len = strlen(argv[index]);
if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048;
A_REALLOC(cmdline, allocated);
}
strcat(cmdline, " ");
strcat(cmdline, argv[index]);
used = strlen(cmdline); // Не считаем вручную, так надежнее
}
# ifdef __linux__
setproctitle_init(argc, argv, environ);
# endif
setproctitle("-%s:%s", prefix, cmdline);
free(cmdline);
}
#endif
INLINE void process_notify_parent(void) {
pid_t parent = getppid();
if (kill(parent, SIGUSR2) < 0) {
LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
}
}

View File

@@ -22,24 +22,23 @@
#include "stream.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include <linux/videodev2.h>
#include "tools.h"
#include "threading.h"
#include "logging.h"
#include "xioctl.h"
#include "picture.h"
#include "device.h"
#include "encoder.h"
#ifdef WITH_GPIO
# include "gpio.h"
# include "gpio/gpio.h"
#endif
@@ -52,11 +51,11 @@ struct _worker_t {
long double last_comp_time;
pthread_mutex_t has_job_mutex;
int buf_index;
unsigned buf_index;
atomic_bool has_job;
bool job_timely;
bool job_failed;
long double job_start_time;
long double job_start_ts;
pthread_cond_t has_job_cond;
pthread_mutex_t *free_workers_mutex;
@@ -111,6 +110,7 @@ struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) {
atomic_init(&proc->slowdown, false);
A_CALLOC(stream, 1);
stream->picture = picture_init();
stream->dev = dev;
stream->encoder = encoder;
atomic_init(&stream->updated, false);
@@ -121,6 +121,7 @@ struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) {
void stream_destroy(struct stream_t *stream) {
A_MUTEX_DESTROY(&stream->mutex);
picture_destroy(stream->picture);
free(stream->proc);
free(stream);
}
@@ -139,11 +140,11 @@ void stream_loop(struct stream_t *stream) {
long long captured_fps_second = 0;
bool persistent_timeout_reported = false;
LOG_DEBUG("Allocation memory for stream picture ...");
A_CALLOC(stream->picture.data, stream->dev->run->max_raw_image_size);
LOG_INFO("Capturing ...");
LOG_DEBUG("Pre-allocating memory for stream picture ...");
picture_realloc_data(stream->picture, picture_get_generous_size(stream->dev->run->width, stream->dev->run->height));
while (!atomic_load(&stream->proc->stop)) {
struct _worker_t *ready_worker;
@@ -184,7 +185,7 @@ void stream_loop(struct stream_t *stream) {
} else if (selected == 0) {
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
gpio_set_stream_online(false);
# endif
if (stream->dev->persistent) {
@@ -206,7 +207,7 @@ void stream_loop(struct stream_t *stream) {
LOG_DEBUG("Frame is ready");
# ifdef WITH_GPIO
GPIO_SET_HIGH(stream_online);
gpio_set_stream_online(true);
# endif
int buf_index;
@@ -240,7 +241,7 @@ void stream_loop(struct stream_t *stream) {
captured_fps = captured_fps_accum;
captured_fps_accum = 0;
captured_fps_second = now_second;
LOG_PERF("A new second has come; captured_fps=%u", captured_fps);
LOG_PERF_FPS("A new second has come; captured_fps=%u", captured_fps);
}
captured_fps_accum += 1;
@@ -278,10 +279,7 @@ void stream_loop(struct stream_t *stream) {
}
A_MUTEX_LOCK(&stream->mutex);
stream->picture.used = 0; // On stream offline
free(stream->picture.data);
stream->width = 0;
stream->height = 0;
stream->online = false;
atomic_store(&stream->updated, true);
A_MUTEX_UNLOCK(&stream->mutex);
@@ -290,7 +288,7 @@ void stream_loop(struct stream_t *stream) {
device_close(stream->dev);
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
gpio_set_stream_online(false);
# endif
}
}
@@ -338,37 +336,28 @@ static struct _workers_pool_t *_stream_init(struct stream_t *stream) {
}
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index, unsigned captured_fps) {
# define PICTURE(_next) stream->dev->run->pictures[buf_index]._next
A_MUTEX_LOCK(&stream->mutex);
stream->picture.used = PICTURE(used);
stream->picture.allocated = PICTURE(allocated);
picture_copy(stream->dev->run->pictures[buf_index], stream->picture);
memcpy(stream->picture.data, PICTURE(data), stream->picture.used);
stream->picture.grab_time = PICTURE(grab_time);
stream->picture.encode_begin_time = PICTURE(encode_begin_time);
stream->picture.encode_end_time = PICTURE(encode_end_time);
stream->width = stream->dev->run->width;
stream->height = stream->dev->run->height;
stream->online = true;
stream->captured_fps = captured_fps;
atomic_store(&stream->updated, true);
A_MUTEX_UNLOCK(&stream->mutex);
# undef PICTURE
}
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream) {
struct _workers_pool_t *pool;
LOG_INFO("Creating pool with %u workers ...", stream->dev->run->n_workers);
# define DEV(_next) stream->dev->_next
# define RUN(_next) stream->dev->run->_next
LOG_INFO("Creating pool with %u workers ...", RUN(n_workers));
A_CALLOC(pool, 1);
pool->n_workers = stream->dev->run->n_workers;
pool->n_workers = RUN(n_workers);
A_CALLOC(pool->workers, pool->n_workers);
A_MUTEX_INIT(&pool->free_workers_mutex);
@@ -376,10 +365,13 @@ static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream) {
atomic_init(&pool->workers_stop, false);
if (stream->dev->desired_fps > 0) {
pool->desired_frames_interval = (long double)1 / stream->dev->desired_fps;
if (DEV(desired_fps) > 0 && (DEV(desired_fps) < RUN(hw_fps) || RUN(hw_fps) == 0)) {
pool->desired_frames_interval = (long double)1 / DEV(desired_fps);
}
# undef RUN
# undef DEV
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
@@ -436,49 +428,39 @@ static void _workers_pool_destroy(struct _workers_pool_t *pool) {
static void *_worker_thread(void *v_worker) {
struct _worker_t *worker = (struct _worker_t *)v_worker;
A_THREAD_RENAME("worker-%u", worker->number);
LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number);
# ifdef WITH_GPIO
GPIO_INIT_PIN(workers_busy_at, worker->number);
# endif
while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) {
LOG_DEBUG("Worker %u waiting for a new job ...", worker->number);
# ifdef WITH_GPIO
GPIO_SET_LOW_AT(workers_busy_at, worker->number);
# endif
A_MUTEX_LOCK(&worker->has_job_mutex);
A_COND_WAIT_TRUE(atomic_load(&worker->has_job), &worker->has_job_cond, &worker->has_job_mutex);
A_MUTEX_UNLOCK(&worker->has_job_mutex);
if (!atomic_load(worker->workers_stop)) {
# define PICTURE(_next) worker->dev->run->pictures[worker->buf_index]._next
# define PICTURE(_next) worker->dev->run->pictures[worker->buf_index]->_next
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index);
# ifdef WITH_GPIO
GPIO_SET_HIGH_AT(workers_busy_at, worker->number);
# endif
if (encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index) < 0) {
worker->job_failed = false;
}
worker->job_failed = (bool)encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index);
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
worker->job_start_time = PICTURE(encode_begin_time);
atomic_store(&worker->has_job, false);
if (!worker->job_failed) {
worker->job_start_ts = PICTURE(encode_begin_ts);
worker->last_comp_time = PICTURE(encode_end_ts) - worker->job_start_ts;
worker->last_comp_time = PICTURE(encode_end_time) - worker->job_start_time;
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%u, buffer=%u",
PICTURE(used), worker->last_comp_time, worker->number, worker->buf_index);
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%u, buffer=%u",
PICTURE(used), worker->last_comp_time, worker->number, worker->buf_index);
} else {
LOG_VERBOSE("Compression failed: worker=%u, buffer=%u", worker->number, worker->buf_index);
}
} else {
worker->job_failed = true;
atomic_store(&worker->has_job, false);
}
atomic_store(&worker->has_job, false);
# undef PICTURE
}
@@ -489,9 +471,6 @@ static void *_worker_thread(void *v_worker) {
}
LOG_DEBUG("Bye-bye (worker %u)", worker->number);
# ifdef WITH_GPIO
GPIO_SET_LOW_AT(workers_busy_at, worker->number);
# endif
return NULL;
}
@@ -502,7 +481,7 @@ static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool) {
A_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex);
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
if (pool->oldest_worker && !atomic_load(&pool->oldest_worker->has_job) && pool->oldest_worker->buf_index >= 0) {
if (pool->oldest_worker && !atomic_load(&pool->oldest_worker->has_job)) {
ready_worker = pool->oldest_worker;
ready_worker->job_timely = true;
pool->oldest_worker = pool->oldest_worker->order_next;
@@ -511,7 +490,7 @@ static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool) {
if (
!atomic_load(&pool->workers[number].has_job) && (
ready_worker == NULL
|| ready_worker->job_start_time < pool->workers[number].job_start_time
|| ready_worker->job_start_ts < pool->workers[number].job_start_ts
)
) {
ready_worker = &pool->workers[number];
@@ -569,6 +548,7 @@ static long double _workers_pool_get_fluency_delay(struct _workers_pool_t *pool,
if (pool->desired_frames_interval > 0 && min_delay > 0 && pool->desired_frames_interval > min_delay) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
// и аппаратный fps не попадает точно в желаемое значение
return pool->desired_frames_interval;
}
return min_delay;

View File

@@ -22,8 +22,12 @@
#pragma once
#include <stdbool.h>
#include <stdatomic.h>
#include <pthread.h>
#include "picture.h"
#include "device.h"
#include "encoder.h"
@@ -34,9 +38,8 @@ struct process_t {
};
struct stream_t {
struct picture_t picture;
unsigned width;
unsigned height;
struct picture_t *picture;
bool online;
unsigned captured_fps;
atomic_bool updated;
pthread_mutex_t mutex;

125
src/threading.h Normal file
View File

@@ -0,0 +1,125 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>
#ifdef WITH_PTHREAD_NP
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
# include <pthread_np.h>
# include <sys/param.h>
# endif
#endif
#include "tools.h"
#ifdef PTHREAD_MAX_NAMELEN_NP
# define MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
#else
# define MAX_THREAD_NAME ((size_t)16)
#endif
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
#define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
#ifdef WITH_PTHREAD_NP
# define A_THREAD_RENAME(_fmt, ...) { \
char _new_tname_buf[MAX_THREAD_NAME] = {0}; \
assert(snprintf(_new_tname_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \
thread_set_name(_new_tname_buf); \
}
#else
# define A_THREAD_RENAME(_fmt, ...)
#endif
#define A_MUTEX_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL))
#define A_MUTEX_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex))
#define A_MUTEX_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex))
#define A_MUTEX_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex))
#define A_COND_INIT(_cond) assert(!pthread_cond_init(_cond, NULL))
#define A_COND_DESTROY(_cond) assert(!pthread_cond_destroy(_cond))
#define A_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
#define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); }
#ifdef WITH_PTHREAD_NP
INLINE void thread_set_name(const char *name) {
# if defined(__linux__)
pthread_setname_np(pthread_self(), name);
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
pthread_set_name_np(pthread_self(), name);
# elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void *)name);
# else
# error thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# endif
}
#endif
INLINE void thread_get_name(char *name) { // Always required for logging
#ifdef WITH_PTHREAD_NP
int retval = -1;
# if defined(__linux__) || defined (__NetBSD__)
retval = pthread_getname_np(pthread_self(), name, MAX_THREAD_NAME);
# elif \
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|| defined(__DragonFly__)
pthread_get_name_np(pthread_self(), name, MAX_THREAD_NAME);
if (name[0] != '\0') {
retval = 0;
}
# else
# error thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# endif
if (retval < 0) {
#endif
#if defined(__linux__)
pid_t tid = syscall(SYS_gettid);
#elif defined(__FreeBSD__)
pid_t tid = syscall(SYS_thr_self);
#elif defined(__OpenBSD__)
pid_t tid = syscall(SYS_getthrid);
#elif defined(__NetBSD__)
pid_t tid = syscall(SYS__lwp_self);
#elif defined(__DragonFly__)
pid_t tid = syscall(SYS_lwp_gettid);
#else
pid_t tid = 0; // Makes cppcheck happy
# warning gettid() not implemented
#endif
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", tid) > 0);
#ifdef WITH_PTHREAD_NP
}
#endif
}

View File

@@ -25,30 +25,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <time.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
#define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
#define A_MUTEX_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL))
#define A_MUTEX_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex))
#define A_MUTEX_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex))
#define A_MUTEX_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex))
#define A_COND_INIT(_cond) assert(!pthread_cond_init(_cond, NULL))
#define A_COND_DESTROY(_cond) assert(!pthread_cond_destroy(_cond))
#define A_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
#define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); }
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
@@ -64,6 +45,10 @@ INLINE char *bool_to_string(bool flag) {
return (flag ? "true" : "false");
}
INLINE size_t align_size(size_t size, size_t to) {
return ((size + (to - 1)) & ~(to - 1));
}
INLINE unsigned min_u(unsigned a, unsigned b) {
return (a < b ? a : b);
}
@@ -93,7 +78,13 @@ INLINE long double get_now_monotonic(void) {
time_t sec;
long msec;
# if defined(CLOCK_MONOTONIC_RAW)
get_now(CLOCK_MONOTONIC_RAW, &sec, &msec);
# elif defined(CLOCK_MONOTONIC_FAST)
get_now(CLOCK_MONOTONIC_FAST, &sec, &msec);
# else
get_now(CLOCK_MONOTONIC, &sec, &msec);
# endif
return (long double)sec + ((long double)msec) / 1000;
}
@@ -104,7 +95,3 @@ INLINE long double get_now_real(void) {
get_now(CLOCK_REALTIME, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}
INLINE pid_t get_thread_id(void) {
return syscall(SYS_gettid);
}

View File

@@ -30,9 +30,10 @@
#include "logging.h"
#ifndef XIOCTL_RETRIES
# define XIOCTL_RETRIES 4
#ifndef CFG_XIOCTL_RETRIES
# define CFG_XIOCTL_RETRIES 4
#endif
#define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES))
INLINE int xioctl(int fd, int request, void *arg) {
@@ -51,8 +52,9 @@ INLINE int xioctl(int fd, int request, void *arg) {
)
);
// cppcheck-suppress knownConditionTrueFalse
if (retval && retries <= 0) {
LOG_PERROR("ioctl(%d) retried %d times; giving up", request, XIOCTL_RETRIES);
LOG_PERROR("ioctl(%d) retried %u times; giving up", request, XIOCTL_RETRIES);
}
return retval;
}

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3
#============================================================================#
# ========================================================================== #
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
@@ -18,7 +18,7 @@
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
#============================================================================#
# ========================================================================== #
import textwrap

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env -S python3 -B
#============================================================================#
# ========================================================================== #
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
@@ -18,7 +18,7 @@
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
#============================================================================#
# ========================================================================== #
import sys

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env -S python3 -B
#============================================================================#
# ========================================================================== #
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
@@ -18,7 +18,7 @@
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
#============================================================================#
# ========================================================================== #
import sys
@@ -26,7 +26,6 @@ import io
import struct
from typing import Tuple
from typing import List
import common
@@ -38,14 +37,15 @@ def _get_jpeg_size(data: bytes) -> Tuple[int, int]:
stream = io.BytesIO(data)
while True:
marker = struct.unpack(">H", stream.read(2))[0]
if marker == 0xFFD9:
raise RuntimeError("Can't find jpeg size")
if (
marker == 0xFFD8 # Start of image
or marker == 0xFF01 # Private marker
or (marker >= 0xFFD0 and marker <= 0xFFD7) # Restart markers
or 0xFFD0 <= marker <= 0xFFD7 # Restart markers
):
continue
elif marker == 0xFFD9:
raise RuntimeError("Can't find jpeg size")
# All other markers specify chunks with lengths
length = struct.unpack(">H", stream.read(2))[0]