Compare commits

...

90 Commits
v0.67 ... v1.3

Author SHA1 Message Date
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
Devaev Maxim
16d5c81c22 Bump version: 1.0 → 1.1 2019-09-01 22:43:13 +03:00
Devaev Maxim
58e3a77a79 changed upstream url 2019-09-01 22:42:58 +03:00
Devaev Maxim
16105db7a0 refactoring 2019-07-27 18:22:08 +03:00
Devaev Maxim
6e307b1ef4 Bump version: 0.81 → 1.0 2019-07-13 07:15:28 +03:00
Devaev Maxim
554491ff19 messages refactoring 2019-07-13 07:14:33 +03:00
Devaev Maxim
fcd70c3166 Bump version: 0.80 → 0.81 2019-07-13 01:06:35 +03:00
Devaev Maxim
aaed14e9de wider fps and resolution range 2019-07-13 01:06:21 +03:00
Devaev Maxim
76ba25607b Bump version: 0.79 → 0.80 2019-07-13 00:30:13 +03:00
Devaev Maxim
1d8dedea85 --glitched-resolutions 2019-07-12 23:03:42 +03:00
Devaev Maxim
dfe8245181 refactoring 2019-07-12 19:09:54 +03:00
Devaev Maxim
50569a53a0 show gpio feature in version 2019-07-12 05:07:35 +03:00
Devaev Maxim
6ace44e4de refactoring 2019-07-11 19:40:42 +03:00
Devaev Maxim
c4a5eea75b --(fake-)resolution instead of --(fake-)width/height 2019-07-11 16:48:33 +03:00
Devaev Maxim
87de066369 refactoring 2019-07-10 07:08:29 +03:00
Devaev Maxim
c3c15b16bf WITH_OMX_ENCODER -> WITH_OMX 2019-07-10 02:20:46 +03:00
Devaev Maxim
e6dfe3d2b7 gpio 2019-07-09 20:11:41 +03:00
Devaev Maxim
fe8699b7f3 log_level = LOG_LEVEL_INFO 2019-07-03 00:26:55 +03:00
Devaev Maxim
c751e4ff08 Bump version: 0.78 → 0.79 2019-07-01 02:47:22 +03:00
Devaev Maxim
99a00ca57c approx_comp_time 2019-07-01 02:20:13 +03:00
Devaev Maxim
c009a7efe4 Revert "fluency synchronization"
This reverts commit 291d7431b0.
2019-06-30 06:37:24 +03:00
Devaev Maxim
291d7431b0 fluency synchronization 2019-06-29 16:30:43 +03:00
Devaev Maxim
a1cd490fdf using desired_frames_interval instead of soft_delay 2019-06-29 07:02:21 +03:00
Devaev Maxim
7a85774085 fixed mutex routine 2019-06-29 06:59:16 +03:00
Devaev Maxim
2ed3c4815b f-strings 2019-06-28 06:27:46 +03:00
Devaev Maxim
724c6e118f () -> (void) 2019-06-28 05:15:24 +03:00
Devaev Maxim
da3a3adc65 fixed includes order 2019-06-28 05:00:49 +03:00
Devaev Maxim
32013a6360 refactoring 2019-06-27 05:10:19 +03:00
Devaev Maxim
16a2495766 messages refactoring 2019-06-13 14:37:03 +03:00
Devaev Maxim
05246706f0 Bump version: 0.77 → 0.78 2019-06-02 21:07:18 +03:00
Devaev Maxim
9355055a7f Revert "fluency delay based on involved workers counter"
This reverts commit 9f16de13fe.
2019-06-02 21:06:30 +03:00
Devaev Maxim
9f16de13fe fluency delay based on involved workers counter 2019-06-02 20:23:39 +03:00
Devaev Maxim
36a987fb9d locked captured_fps 2019-06-02 19:35:53 +03:00
Devaev Maxim
652397f569 refactoring 2019-06-02 16:31:50 +03:00
Devaev Maxim
3333fc56a3 refactoring 2019-06-02 16:20:30 +03:00
Devaev Maxim
47378a17db refactoring 2019-06-02 15:22:34 +03:00
Devaev Maxim
5f320786f5 fixed workers cleanup on restart 2019-06-01 05:31:44 +03:00
Devaev Maxim
498c6e1f5d refactoring 2019-06-01 05:00:09 +03:00
Devaev Maxim
6c09adc689 free_worker -> ready_worker 2019-06-01 04:58:01 +03:00
Devaev Maxim
8bf7ac3005 refactoring 2019-06-01 04:47:51 +03:00
Devaev Maxim
6aebd7167e config.mk 2019-05-31 01:02:03 +03:00
Devaev Maxim
3f03575222 refactoring 2019-05-30 06:53:01 +03:00
Devaev Maxim
9ca43f17a3 debug workers using gpio on rpi 2019-05-26 22:32:51 +03:00
Devaev Maxim
66ef566c9a Makefile: on/off WITH_* options 2019-05-26 18:47:53 +03:00
Devaev Maxim
07ecc5b0a0 Bump version: 0.76 → 0.77 2019-05-26 06:03:59 +03:00
Devaev Maxim
8d19711f2c fixed pkgbuild 2019-05-26 06:03:48 +03:00
Devaev Maxim
c94117ae1e Bump version: 0.75 → 0.76 2019-05-26 05:55:48 +03:00
Devaev Maxim
06a80df708 More flexible build 2019-05-26 05:55:35 +03:00
Devaev Maxim
13dff256c8 workers by number of cores 2019-05-25 00:52:49 +03:00
Devaev Maxim
07e9dbc0f7 fixed gcc options ordering 2019-05-24 20:13:09 +03:00
Devaev Maxim
c83dddbf0b fixed indent 2019-05-18 23:51:14 +03:00
Devaev Maxim
5672d1aa75 Bump version: 0.74 → 0.75 2019-05-18 18:24:32 +03:00
Devaev Maxim
078097efec Added links to FreeBSD port 2019-05-18 18:22:37 +03:00
Devaev Maxim
3cd8338886 Issue #6: make install-strip and make uninstall 2019-05-18 17:58:23 +03:00
Maxim Devaev
d2b57cc7d5 Update README.md 2019-05-18 17:25:42 +03:00
Maxim Devaev
2c4f59c87a Update README.ru.md 2019-05-18 17:24:53 +03:00
Devaev Maxim
36eb7eeb76 minor help fix 2019-05-18 16:35:10 +03:00
Devaev Maxim
3b6544db8a cpu/encoder: removed TODO about jpeg error handling 2019-05-18 02:12:18 +03:00
Devaev Maxim
8033ab23ed Bump version: 0.73 → 0.74 2019-05-17 17:15:17 +03:00
Devaev Maxim
ddb3db8b20 Issue #5: Added libevent version check 2019-05-17 16:57:53 +03:00
Devaev Maxim
b3c8071edb Bump version: 0.72 → 0.73 2019-05-09 18:51:27 +03:00
Devaev Maxim
bc70faae09 fix 2019-05-05 04:26:25 +03:00
Devaev Maxim
ae7c4c91e0 Bump version: 0.71 → 0.72 2019-05-03 01:49:44 +03:00
Devaev Maxim
613baa4e1e better unix socket handling 2019-05-03 00:10:48 +03:00
Devaev Maxim
33a101b4f7 Bump version: 0.70 → 0.71 2019-05-01 21:00:22 +03:00
Devaev Maxim
9e9039c4e6 y != yes 2019-05-01 20:59:30 +03:00
Maxim Devaev
b3ceec51de Update README.md 2019-04-26 02:24:16 +03:00
Maxim Devaev
e3ac4ba6f5 Update README.ru.md 2019-04-26 02:23:54 +03:00
Devaev Maxim
fa6b8b44c1 Bump version: 0.69 → 0.70 2019-04-18 05:17:00 +03:00
Devaev Maxim
8cb7574af2 encoder refactoring 2019-04-18 05:09:32 +03:00
Devaev Maxim
c34d644c2a Bump version: 0.68 → 0.69 2019-04-17 07:38:37 +03:00
Devaev Maxim
7857fa8f63 blank: handling libjpeg errors 2019-04-17 07:14:19 +03:00
Devaev Maxim
3253de83dc refactoring 2019-04-14 06:09:21 +03:00
Devaev Maxim
030464c3b8 refactoring 2019-04-14 03:41:35 +03:00
Devaev Maxim
0020aa69ec configurable blank page 2019-04-14 03:35:25 +03:00
Devaev Maxim
93bfa56ccf unicode fix 2019-04-13 23:02:26 +03:00
Devaev Maxim
3392ac5fbc refactoring 2019-04-13 23:02:12 +03:00
Devaev Maxim
2077d94edc Bump version: 0.67 → 0.68 2019-04-10 16:47:18 +03:00
Devaev Maxim
defe5eb6fe openwrt package 2019-04-10 16:46:01 +03:00
Devaev Maxim
dee5e18134 better makefile 2019-04-10 02:38:08 +03:00
44 changed files with 2483 additions and 1530 deletions

View File

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

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
/pkg/arch/src/
/pkg/arch/v*.tar.gz
/pkg/arch/ustreamer-*.pkg.tar.xz
/config.mk
/vgcore.*
/ustreamer
/*.sock

View File

@@ -1,23 +1,39 @@
-include config.mk
PROG ?= ustreamer
DESTDIR ?=
PREFIX ?= /usr/local
CC ?= gcc
CFLAGS ?= -O3
LDFLAGS ?=
CC ?= gcc
RPI_VC_HEADERS ?= /opt/vc/include
RPI_VC_LIBS ?= /opt/vc/lib
# =====
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)
PROG = ustreamer
ifeq ($(shell ls -d /opt/vc/include 2>/dev/null), /opt/vc/include)
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifneq ($(call optbool,$(WITH_OMX)),)
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)
LIBS += -lbcm_host -lvcos -lopenmaxil -L/opt/vc/lib
override CFLAGS += -DWITH_OMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
endif
OBJECTS = $(SOURCES:.c=.o)
ifneq ($(call optbool,$(WITH_GPIO)),)
LIBS += -lwiringPi
override CFLAGS += -DWITH_GPIO
endif
# =====
@@ -28,17 +44,32 @@ install: $(PROG)
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
install-strip: install
strip $(DESTDIR)$(PREFIX)/bin/$(PROG)
uninstall:
rm $(DESTDIR)$(PREFIX)/bin/$(PROG)
regen:
tools/make-jpeg-h.py src/http/data/blank.jpeg src/http/data/blank_jpeg.h BLANK
tools/make-html-h.py src/http/data/index.html src/http/data/index_html.h INDEX
$(PROG): $(OBJECTS)
$(CC) $(LIBS) $(LDFLAGS) $(OBJECTS) -o $@
$(PROG): $(SOURCES:.c=.o)
$(info -- LD $@)
@ $(CC) $(SOURCES:.c=.o) -o $@ $(LDFLAGS) $(LIBS)
$(info ===== Build complete =====)
$(info == CC = $(CC))
$(info == LIBS = $(LIBS))
$(info == CFLAGS = $(CFLAGS))
$(info == LDFLAGS = $(LDFLAGS))
.c.o:
$(CC) $(LIBS) $(CFLAGS) $< -o $@
$(info -- CC $<)
@ $(CC) $< -o $@ $(CFLAGS) $(LIBS)
release:
@@ -50,13 +81,14 @@ release:
bump:
bumpversion minor
bumpversion $(if $(V),$(V),minor)
push:
git push
git push --tags
clean-all: clean
clean:
find src -name '*.o' -exec rm '{}' \;

View File

@@ -2,7 +2,7 @@
[[Русская версия]](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/pi-kvm) 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 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 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:
@@ -16,6 +16,7 @@
| 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 |
| 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 |
| 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:
@@ -31,16 +32,17 @@ 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```.
It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
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```.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
$ git clone --depth=1 https://github.com/pikvm/ustreamer
$ cd ustreamer
$ make
$ ./ustreamer --help
```
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer
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.
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
-----
# Usage

View File

@@ -2,7 +2,7 @@
[[English version]](README.md)
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pi-kvm) специально для стриминга с устройств видеозахвата [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) специально для стриминга с устройств видеозахвата [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```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
@@ -16,6 +16,7 @@
| Стрим через 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=+) Нет |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
Сносочки:
@@ -31,16 +32,17 @@
# Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [wiringPi](http://wiringpi.com) и добавьте параметр ```WITH_GPIO=1```.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
$ git clone --depth=1 https://github.com/pikvm/ustreamer
$ cd ustreamer
$ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```. То же самое и с GPIO.
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
-----
# Использование

View File

@@ -3,28 +3,34 @@
pkgname=ustreamer
pkgver=0.67
pkgver=1.3
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pi-kvm/ustreamer"
url="https://github.com/pikvm/ustreamer"
license=(GPL)
arch=(i686 x86_64 armv6h armv7h)
depends=(libjpeg libevent libutil-linux)
# optional: raspberrypi-firmware for OMX JPEG encoder
# optional: raspberrypi-firmware for OMX encoder
# optional: wiringpi for GPIO support
makedepends=(gcc make)
source=("$url/archive/v$pkgver.tar.gz")
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP)
build() {
cd $srcdir
cd "$srcdir"
rm -rf $pkgname-build
cp -r ustreamer-$pkgver $pkgname-build
cp -r $pkgname $pkgname-build
cd $pkgname-build
make CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
local _options=""
[ -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
}
package() {
cd $srcdir/$pkgname-build
cd "$srcdir/$pkgname-build"
make DESTDIR="$pkgdir" PREFIX=/usr install
}

View File

@@ -5,9 +5,9 @@ EAPI=7
inherit git-r3
DESCRIPTION="µStreamer - Lightweight and fast MJPG-HTTP streamer"
HOMEPAGE="https://github.com/pi-kvm/ustreamer"
EGIT_REPO_URI="https://github.com/pi-kvm/ustreamer.git"
DESCRIPTION="uStreamer - Lightweight and fast MJPG-HTTP streamer"
HOMEPAGE="https://github.com/pikvm/ustreamer"
EGIT_REPO_URI="https://github.com/pikvm/ustreamer.git"
LICENSE="GPL-3"
SLOT="0"

45
pkg/openwrt/Makefile Normal file
View File

@@ -0,0 +1,45 @@
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=1.3
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/pikvm/ustreamer.git
PKG_SOURCE_VERSION:=v$(PKG_VERSION)
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_LICENSE:=GPL-3.0
PKG_LICENSE_FILES:=LICENSE
include $(INCLUDE_DIR)/package.mk
define Package/ustreamer
SECTION:=multimedia
CATEGORY:=Multimedia
TITLE:=uStreamer
DEPENDS:=+libpthread +libjpeg +libv4l +libuuid +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
URL:=https://github.com/pikvm/ustreamer
endef
define Package/ustreamer/description
µStreamer - Lightweight and fast MJPG-HTTP streamer
endef
define Package/ustreamer/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer $(1)/usr/bin/
$(INSTALL_DIR) $(1)/etc/config
$(CP) ./files/ustreamer.config $(1)/etc/config/ustreamer
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/ustreamer.init $(1)/etc/init.d/ustreamer
endef
$(eval $(call BuildPackage,ustreamer))

View File

@@ -0,0 +1,19 @@
config ustreamer
option enabled '0'
option device '/dev/video0'
option device_timeout '5'
option input '0'
option resolution '640x480'
option format 'YUYV'
option quality '80'
option desired_fps '0'
option encoder 'CPU'
option host '::'
option port '8080'
option static ''
option user ''
option password ''

View File

@@ -0,0 +1,54 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2009-2019 OpenWrt.org
START=90
STOP=10
USE_PROCD=1
PROG=/usr/bin/ustreamer
getcfg() {
config_get value ustreamer $1 $2
return "$value"
}
start_instance() {
config_get_bool enabled ustreamer enabled 0
[ "$enabled" -eq 0 ] && return
local options=""
options="$options --device='`getcfg device /dev/video0`'"
options="$options --device-timeout='`getcfg device_timeout 5`'"
options="$options --input='`getcfg input 0`'"
options="$options --resolution='`getcfg resolution 640x480`'"
options="$options --format='`getcfg format YUYV`'"
options="$options --quality='`getcfg quality 80`'"
options="$options --desired-fps='`getcfg desired_fps 0`'"
options="$options --encoder='`getcfg encoder CPU`'"
options="$options --host='`getcfg host '::'`'"
local port=`getcfg port 8080`
options="$options --port='$port'"
options="$options --static='`getcfg static ''`'"
options="$options --user='`getcfg user ''`'"
options="$options --passwd='`getcfg password ''`'"
config-get-bool opt_slowdown ustreamer slowdown 1
[ "$slowdown" -eq 1 ] && options="$options --slowdown"
procd_open_instance
procd_set_param command "$PROG" $options
procd_add_mdns http tcp "$port" daemon=ustreamer
procd_close_instance
}
start_service() {
config_load ustreamer
config_foreach start_instance ustreamer
}
service_triggers() {
procd_add_reload_trigger ustreamer
}

View File

@@ -22,4 +22,6 @@
#pragma once
#define VERSION "0.67"
#ifndef VERSION
# define VERSION "1.3"
#endif

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "device.h"
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
@@ -30,13 +32,13 @@
#include <errno.h>
#include <assert.h>
#include <sys/select.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include "tools.h"
#include "logging.h"
#include "xioctl.h"
#include "device.h"
static const struct {
@@ -81,12 +83,15 @@ static const char *_format_to_string_supported(unsigned format);
static const char *_standard_to_string(v4l2_std_id standard);
struct device_t *device_init() {
struct controls_t *ctl;
struct device_t *device_init(void) {
struct device_runtime_t *run;
struct device_t *dev;
long cores_sysconf;
unsigned cores_available;
A_CALLOC(ctl, 1);
cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
cores_available = max_u(min_u(cores_sysconf, 4), 1);
A_CALLOC(run, 1);
run->fd = -1;
@@ -97,18 +102,16 @@ struct device_t *device_init() {
dev->height = 480;
dev->format = V4L2_PIX_FMT_YUYV;
dev->standard = V4L2_STD_UNKNOWN;
dev->n_buffers = max_u(min_u(sysconf(_SC_NPROCESSORS_ONLN), 4), 1) + 1;
dev->n_workers = dev->n_buffers;
dev->n_buffers = cores_available + 1;
dev->n_workers = min_u(cores_available, dev->n_buffers);
dev->timeout = 1;
dev->error_delay = 1;
dev->ctl = ctl;
dev->run = run;
return dev;
}
void device_destroy(struct device_t *dev) {
free(dev->run);
free(dev->ctl);
free(dev);
}
@@ -156,6 +159,8 @@ int device_open(struct device_t *dev) {
_device_open_alloc_picbufs(dev);
_device_apply_controls(dev);
dev->run->n_workers = min_u(dev->run->n_buffers, dev->n_workers);
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
return 0;
@@ -165,6 +170,8 @@ int device_open(struct device_t *dev) {
}
void device_close(struct device_t *dev) {
dev->run->n_workers = 0;
if (dev->run->pictures) {
LOG_DEBUG("Releasing picture buffers ...");
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index].data; ++index) {
@@ -222,6 +229,39 @@ int device_switch_capturing(struct device_t *dev, bool enable) {
return 0;
}
int device_select(struct device_t *dev, bool *has_read, bool *has_write, bool *has_error) {
struct timeval timeout;
int retval;
# define INIT_FD_SET(_set) \
fd_set _set; FD_ZERO(&_set); FD_SET(dev->run->fd, &_set);
INIT_FD_SET(read_fds);
INIT_FD_SET(write_fds);
INIT_FD_SET(error_fds);
# undef INIT_FD_SET
timeout.tv_sec = dev->timeout;
timeout.tv_usec = 0;
LOG_DEBUG("Calling select() on video device ...");
retval = select(dev->run->fd + 1, &read_fds, &write_fds, &error_fds, &timeout);
if (retval > 0) {
*has_read = FD_ISSET(dev->run->fd, &read_fds);
*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;
}
LOG_DEBUG("Device select() --> %d", retval);
return retval;
}
int device_grab_buffer(struct device_t *dev) {
struct v4l2_buffer buf_info;
@@ -235,14 +275,15 @@ int device_grab_buffer(struct device_t *dev) {
return -1;
}
LOG_DEBUG("Got a new frame in buffer index=%u; bytesused=%u", buf_info.index, buf_info.bytesused);
LOG_DEBUG("Got a new frame in 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("Got invalid 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();
return buf_info.index;
}
@@ -318,7 +359,7 @@ static int _device_open_check_cap(struct device_t *dev) {
static int _device_open_dv_timings(struct device_t *dev) {
_device_apply_resolution(dev, dev->width, dev->height);
if (dev->dv_timings) {
LOG_DEBUG("Using DV-timings");
LOG_DEBUG("Using DV timings");
if (_device_apply_dv_timings(dev) < 0) {
return -1;
@@ -339,24 +380,22 @@ static int _device_open_dv_timings(struct device_t *dev) {
}
static int _device_apply_dv_timings(struct device_t *dev) {
struct v4l2_dv_timings dv_timings;
struct v4l2_dv_timings dv;
MEMSET_ZERO(dv_timings);
MEMSET_ZERO(dv);
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings) == 0) {
LOG_INFO("Got new DV timings: resolution=%ux%u; pixclk=%llu",
dv_timings.bt.width,
dv_timings.bt.height,
dv_timings.bt.pixelclock);
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);
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv_timings) < 0) {
if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv) < 0) {
LOG_PERROR("Failed to set DV timings");
return -1;
}
if (_device_apply_resolution(dev, dv_timings.bt.width, dv_timings.bt.height) < 0) {
if (_device_apply_resolution(dev, dv.bt.width, dv.bt.height) < 0) {
return -1;
}
@@ -386,7 +425,7 @@ static int _device_open_format(struct device_t *dev) {
// Set format
LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ...");
if (xioctl(dev->run->fd, VIDIOC_S_FMT, &fmt) < 0) {
LOG_PERROR("Unable to set pixelformat=%s; resolution=%ux%u",
LOG_PERROR("Unable to set pixelformat=%s, resolution=%ux%u",
_format_to_string_supported(dev->format),
dev->run->width,
dev->run->height);
@@ -557,7 +596,7 @@ static int _device_apply_resolution(struct device_t *dev, unsigned width, unsign
width == 0 || width > VIDEO_MAX_WIDTH
|| height == 0 || height > VIDEO_MAX_HEIGHT
) {
LOG_ERROR("Requested forbidden resolution=%ux%u: min=1x1; max=%ux%u",
LOG_ERROR("Requested forbidden resolution=%ux%u: min=1x1, max=%ux%u",
width, height, VIDEO_MAX_WIDTH, VIDEO_MAX_HEIGHT);
return -1;
}
@@ -574,14 +613,14 @@ static void _device_apply_controls(struct device_t *dev) {
}
# define SET_CID_MANUAL(_cid, _dest) { \
if (dev->ctl->_dest.value_set) { \
SET_CID(_cid, _dest, dev->ctl->_dest.value, false); \
if (dev->ctl._dest.value_set) { \
SET_CID(_cid, _dest, dev->ctl._dest.value, false); \
} \
}
# 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); \
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); \
} \
}

View File

@@ -28,11 +28,13 @@
#include <linux/videodev2.h>
#define VIDEO_MIN_WIDTH 320
#define VIDEO_MAX_WIDTH 1920
#define VIDEO_MIN_WIDTH 160
#define VIDEO_MAX_WIDTH 10240
#define VIDEO_MIN_HEIGHT 180
#define VIDEO_MAX_HEIGHT 1200
#define VIDEO_MIN_HEIGHT 120
#define VIDEO_MAX_HEIGHT 4320
#define VIDEO_MAX_FPS 120
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define STANDARDS_STR "PAL, NTSC, SECAM"
@@ -63,7 +65,7 @@ struct device_runtime_t {
unsigned height;
unsigned format;
unsigned n_buffers;
// unsigned n_workers; // FIXME
unsigned n_workers;
struct hw_buffer_t *hw_buffers;
struct picture_t *pictures;
size_t max_raw_image_size;
@@ -104,13 +106,13 @@ struct device_t {
unsigned timeout;
unsigned error_delay;
struct controls_t *ctl;
struct controls_t ctl;
struct device_runtime_t *run;
};
struct device_t *device_init();
struct device_t *device_init(void);
void device_destroy(struct device_t *dev);
int device_parse_format(const char *str);
@@ -120,6 +122,7 @@ int device_open(struct device_t *dev);
void device_close(struct device_t *dev);
int device_switch_capturing(struct device_t *dev, bool enable);
int device_select(struct device_t *dev, bool *has_read, bool *has_write, bool *has_error);
int device_grab_buffer(struct device_t *dev);
int device_release_buffer(struct device_t *dev, unsigned index);
int device_consume_event(struct device_t *dev);

View File

@@ -20,6 +20,9 @@
*****************************************************************************/
#include "encoder.h"
#include <stdbool.h>
#include <strings.h>
#include <assert.h>
@@ -28,12 +31,11 @@
#include "tools.h"
#include "logging.h"
#include "device.h"
#include "encoder.h"
#include "encoders/cpu/encoder.h"
#include "encoders/hw/encoder.h"
#ifdef WITH_OMX_ENCODER
#ifdef WITH_OMX
# include "encoders/omx/encoder.h"
#endif
@@ -44,13 +46,13 @@ static const struct {
} _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU},
{"HW", ENCODER_TYPE_HW},
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
{"OMX", ENCODER_TYPE_OMX},
# endif
};
struct encoder_t *encoder_init() {
struct encoder_t *encoder_init(void) {
struct encoder_runtime_t *run;
struct encoder_t *encoder;
@@ -66,50 +68,8 @@ struct encoder_t *encoder_init() {
return encoder;
}
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic push
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
#pragma GCC diagnostic pop
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
// XXX: Тут нет гонки, потому что encoder_prepare() запускается еще до существования других потоков
encoder->run->type = encoder->type;
encoder->run->quality = encoder->quality;
LOG_INFO("Using JPEG quality: %u%%", encoder->quality);
# ifdef WITH_OMX_ENCODER
if (encoder->run->type == ENCODER_TYPE_OMX) {
LOG_DEBUG("Preparing OMX JPEG encoder ...");
if (dev->n_workers > OMX_MAX_ENCODERS) {
LOG_INFO("OMX JPEG encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
dev->n_workers = OMX_MAX_ENCODERS;
}
encoder->run->n_omxs = dev->n_workers;
A_CALLOC(encoder->run->omxs, encoder->run->n_omxs);
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
if ((encoder->run->omxs[index] = omx_encoder_init()) == NULL) {
goto use_fallback;
}
}
}
# endif
return;
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
use_fallback:
LOG_ERROR("Can't initialize selected encoder, using CPU instead it");
encoder->run->type = ENCODER_TYPE_CPU;
encoder->run->quality = encoder->quality;
# pragma GCC diagnostic pop
}
void encoder_destroy(struct encoder_t *encoder) {
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
if (encoder->run->omxs) {
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
if (encoder->run->omxs[index]) {
@@ -142,80 +102,129 @@ const char *encoder_type_to_string(enum encoder_type_t type) {
return _ENCODER_TYPES[0].name;
}
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
enum encoder_type_t type = (encoder->run->cpu_forced ? ENCODER_TYPE_CPU : encoder->type);
unsigned quality = encoder->quality;
bool cpu_forced = false;
if (
(dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG)
&& encoder->run->type != ENCODER_TYPE_HW
) {
LOG_INFO("Switching to HW JPEG encoder because the input format is (M)JPEG");
A_MUTEX_LOCK(&encoder->run->mutex);
encoder->run->type = ENCODER_TYPE_HW;
A_MUTEX_UNLOCK(&encoder->run->mutex);
if ((dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG) && type != ENCODER_TYPE_HW) {
LOG_INFO("Switching to HW encoder because the input format is (M)JPEG");
type = ENCODER_TYPE_HW;
}
if (encoder->run->type == ENCODER_TYPE_HW) {
if (type == ENCODER_TYPE_HW) {
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
LOG_INFO("Switching to CPU JPEG encoder because the input format is not (M)JPEG");
goto use_fallback;
LOG_INFO("Switching to CPU encoder because the input format is not (M)JPEG");
goto use_cpu;
}
if (hw_encoder_prepare_live(dev, encoder->quality) < 0) {
A_MUTEX_LOCK(&encoder->run->mutex);
encoder->run->quality = 0;
A_MUTEX_UNLOCK(&encoder->run->mutex);
LOG_INFO("Using JPEG quality: HW-default");
if (hw_encoder_prepare(dev, quality) < 0) {
quality = 0;
}
dev->run->n_workers = 1;
}
# ifdef WITH_OMX_ENCODER
else if (encoder->run->type == ENCODER_TYPE_OMX) {
# ifdef WITH_OMX
else if (type == ENCODER_TYPE_OMX) {
for (unsigned index = 0; index < encoder->n_glitched_resolutions; ++index) {
if (
encoder->glitched_resolutions[index][0] == dev->run->width
&& encoder->glitched_resolutions[index][1] == dev->run->height
) {
LOG_INFO("Switching to CPU encoder the resolution %ux%u marked as glitchy for OMX",
dev->run->width, dev->run->height);
goto use_cpu;
}
}
LOG_DEBUG("Preparing OMX encoder ...");
if (dev->run->n_workers > OMX_MAX_ENCODERS) {
LOG_INFO("OMX encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
dev->run->n_workers = OMX_MAX_ENCODERS;
}
if (encoder->run->omxs == NULL) {
A_CALLOC(encoder->run->omxs, OMX_MAX_ENCODERS);
}
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
for (; encoder->run->n_omxs < dev->run->n_workers; ++encoder->run->n_omxs) {
if ((encoder->run->omxs[encoder->run->n_omxs] = omx_encoder_init()) == NULL) {
LOG_ERROR("Can't initialize OMX encoder, falling back to CPU");
goto force_cpu;
}
}
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
if (omx_encoder_prepare_live(encoder->run->omxs[index], dev, encoder->quality) < 0) {
LOG_ERROR("Can't prepare OMX JPEG encoder, falling back to CPU");
goto use_fallback;
if (omx_encoder_prepare(encoder->run->omxs[index], dev, quality) < 0) {
LOG_ERROR("Can't prepare OMX encoder, falling back to CPU");
goto force_cpu;
}
}
}
# endif
return;
goto ok;
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
force_cpu:
cpu_forced = true;
# pragma GCC diagnostic pop
use_cpu:
type = ENCODER_TYPE_CPU;
quality = encoder->quality;
ok:
if (quality == 0) {
LOG_INFO("Using JPEG quality: encoder default");
} else {
LOG_INFO("Using JPEG quality: %u%%", quality);
}
use_fallback:
A_MUTEX_LOCK(&encoder->run->mutex);
encoder->run->type = ENCODER_TYPE_CPU;
encoder->run->quality = encoder->quality;
encoder->run->type = type;
encoder->run->quality = quality;
if (cpu_forced) {
encoder->run->cpu_forced = true;
}
A_MUTEX_UNLOCK(&encoder->run->mutex);
}
#pragma GCC diagnostic ignored "-Wunused-label"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic push
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index) {
#pragma GCC diagnostic pop
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
dev->run->pictures[buf_index].encode_begin_time = get_now_monotonic();
if (encoder->run->type == ENCODER_TYPE_CPU) {
cpu_encoder_compress_buffer(dev, buf_index, encoder->quality);
cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality);
} else if (encoder->run->type == ENCODER_TYPE_HW) {
hw_encoder_compress_buffer(dev, buf_index);
}
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
else if (encoder->run->type == ENCODER_TYPE_OMX) {
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
goto use_fallback;
goto error;
}
}
# endif
dev->run->pictures[buf_index].encode_end_time = get_now_monotonic();
return 0;
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
use_fallback:
LOG_INFO("Error while compressing, falling back to CPU");
error:
LOG_INFO("Error while compressing buffer, falling back to CPU");
A_MUTEX_LOCK(&encoder->run->mutex);
encoder->run->type = ENCODER_TYPE_CPU;
encoder->run->quality = encoder->quality;
encoder->run->cpu_forced = true;
A_MUTEX_UNLOCK(&encoder->run->mutex);
return -1;
# pragma GCC diagnostic pop

View File

@@ -22,14 +22,21 @@
#pragma once
#include <stdbool.h>
#include "pthread.h"
#include "tools.h"
#include "device.h"
#ifdef WITH_OMX_ENCODER
#ifdef WITH_OMX
# include "encoders/omx/encoder.h"
# define ENCODER_TYPES_OMX_HINT ", OMX"
# ifndef MAX_GLITCHED_RESOLUTIONS
# define MAX_GLITCHED_RESOLUTIONS 1024
# endif
#else
# define ENCODER_TYPES_OMX_HINT ""
#endif
@@ -43,7 +50,7 @@ enum encoder_type_t {
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
ENCODER_TYPE_CPU,
ENCODER_TYPE_HW,
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
ENCODER_TYPE_OMX,
# endif
};
@@ -51,9 +58,10 @@ enum encoder_type_t {
struct encoder_runtime_t {
enum encoder_type_t type;
unsigned quality;
bool cpu_forced;
pthread_mutex_t mutex;
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
unsigned n_omxs;
struct omx_encoder_t **omxs;
# endif
@@ -62,18 +70,20 @@ struct encoder_runtime_t {
struct encoder_t {
enum encoder_type_t type;
unsigned quality;
# ifdef WITH_OMX
unsigned n_glitched_resolutions;
unsigned glitched_resolutions[2][MAX_GLITCHED_RESOLUTIONS];
# endif
struct encoder_runtime_t *run;
};
struct encoder_t *encoder_init();
struct encoder_t *encoder_init(void);
void encoder_destroy(struct encoder_t *encoder);
enum encoder_type_t encoder_parse_type(const char *str);
const char *encoder_type_to_string(enum encoder_type_t type);
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev);
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev);
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index);

View File

@@ -25,6 +25,8 @@
*****************************************************************************/
#include "encoder.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -36,8 +38,6 @@
#include "../../tools.h"
#include "../../device.h"
#include "encoder.h"
struct _jpeg_dest_manager_t {
struct jpeg_destination_mgr mgr; // Default manager
@@ -100,13 +100,11 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
WRITE_SCANLINES(V4L2_PIX_FMT_UYVY, _jpeg_write_scanlines_uyvy);
WRITE_SCANLINES(V4L2_PIX_FMT_RGB565, _jpeg_write_scanlines_rgb565);
WRITE_SCANLINES(V4L2_PIX_FMT_RGB24, _jpeg_write_scanlines_rgb24);
default: assert(0 && "Unsupported input format for CPU JPEG encoder");
default: assert(0 && "Unsupported input format for CPU encoder");
}
# undef WRITE_SCANLINES
// TODO: process jpeg errors:
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);

View File

@@ -25,6 +25,8 @@
*****************************************************************************/
#include "encoder.h"
#include <stdbool.h>
#include <string.h>
#include <assert.h>
@@ -37,25 +39,24 @@
#include "../../device.h"
#include "huffman.h"
#include "encoder.h"
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_live(struct device_t *dev, unsigned quality) {
int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
struct v4l2_jpegcompression comp;
MEMSET_ZERO(comp);
if (xioctl(dev->run->fd, VIDIOC_G_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Can't query HW JPEG encoder params and set quality (unsupported)");
LOG_ERROR("Can't query HW encoder params and set quality (unsupported)");
return -1;
}
comp.quality = quality;
if (xioctl(dev->run->fd, VIDIOC_S_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Can't set HW JPEG encoder quality (unsopported)");
LOG_ERROR("Can't set HW encoder quality (unsupported)");
return -1;
}
return 0;
@@ -63,7 +64,7 @@ int hw_encoder_prepare_live(struct device_t *dev, unsigned quality) {
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 JPEG encoder");
assert(0 && "Unsupported input format for HW encoder");
}
# define PICTURE(_next) dev->run->pictures[index]._next

View File

@@ -25,5 +25,5 @@
#include "../../device.h"
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality);
int hw_encoder_prepare(struct device_t *dev, unsigned quality);
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index);

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "component.h"
#include <unistd.h>
#include <IL/OMX_Core.h>
@@ -28,7 +30,6 @@
#include "../../logging.h"
#include "formatters.h"
#include "component.h"
static int _component_wait_port_changed(OMX_HANDLETYPE *component, OMX_U32 port, OMX_BOOL enabled);
@@ -40,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);
@@ -51,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);
@@ -65,7 +66,7 @@ int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYP
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;
@@ -76,7 +77,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;
@@ -95,7 +96,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 {
@@ -103,7 +104,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;
}
@@ -118,7 +119,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;
}
@@ -140,7 +141,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

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "encoder.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -39,7 +41,6 @@
#include "formatters.h"
#include "component.h"
#include "encoder.h"
static const OMX_U32 _INPUT_PORT = 340;
@@ -69,7 +70,7 @@ static OMX_ERRORTYPE _omx_output_available_handler(
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer);
struct omx_encoder_t *omx_encoder_init() {
struct omx_encoder_t *omx_encoder_init(void) {
// Some theory:
// - http://www.fourcc.org/yuv.php
// - https://kwasi-ich.de/blog/2017/11/26/omx/
@@ -92,13 +93,13 @@ struct omx_encoder_t *omx_encoder_init() {
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;
}
}
_i_omx += 1;
LOG_INFO("Initializing OMX JPEG encoder ...");
LOG_INFO("Initializing OMX encoder ...");
if (vcos_semaphore_create(&omx->handler_lock, "handler_lock", 0) != VCOS_SUCCESS) {
LOG_ERROR("Can't create VCOS semaphore");
@@ -124,7 +125,7 @@ struct omx_encoder_t *omx_encoder_init() {
void omx_encoder_destroy(struct omx_encoder_t *omx) {
OMX_ERRORTYPE error;
LOG_INFO("Destroying OMX JPEG encoder ...");
LOG_INFO("Destroying OMX encoder ...");
component_set_state(&omx->encoder, OMX_StateIdle);
_omx_encoder_clear_ports(omx);
@@ -136,7 +137,7 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
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");
}
}
@@ -153,7 +154,7 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
free(omx);
}
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality) {
int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality) {
if (component_set_state(&omx->encoder, OMX_StateIdle) < 0) {
return -1;
}
@@ -183,7 +184,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
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;
}
@@ -209,7 +210,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;
}
}
@@ -232,7 +233,7 @@ 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;
}
}
@@ -258,7 +259,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;
@@ -275,13 +276,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) {
@@ -328,7 +329,7 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
// FIXME: RGB24 не работает нормально, нижняя половина экрана зеленая.
// FIXME: Китайский EasyCap тоже не работает, мусор на экране.
// Вероятно обе проблемы вызваны некорректной реализацией OMX на пае.
default: assert(0 && "Unsupported input format for OMX JPEG encoder");
default: assert(0 && "Unsupported input format for OMX encoder");
}
# undef MAP_FORMAT
@@ -348,7 +349,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;
@@ -385,7 +386,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;
}
}
@@ -398,7 +399,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;
}
}
@@ -411,7 +412,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;
}
}
@@ -422,7 +423,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;
@@ -443,14 +444,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;
@@ -468,7 +469,7 @@ 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);
}

View File

@@ -50,8 +50,8 @@ struct omx_encoder_t {
};
struct omx_encoder_t *omx_encoder_init();
struct omx_encoder_t *omx_encoder_init(void);
void omx_encoder_destroy(struct omx_encoder_t *omx);
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality);
int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality);
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index);

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "formatters.h"
#include <stdio.h>
#include <assert.h>
@@ -27,7 +29,6 @@
#include <IL/OMX_Core.h>
#include "../../tools.h"
#include "formatters.h"
#define CASE_TO_STRING(_value) \

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)); \
}

92
src/gpio.h Normal file
View File

@@ -0,0 +1,92 @@
/*****************************************************************************
# #
# 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)

View File

@@ -20,12 +20,13 @@
*****************************************************************************/
#include "base64.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "../tools.h"
#include "base64.h"
static const char ENCODING_TABLE[] = {

174
src/http/blank.c Normal file
View File

@@ -0,0 +1,174 @@
/*****************************************************************************
# #
# 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 "blank.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <setjmp.h>
#include <jpeglib.h>
#include "../tools.h"
#include "../logging.h"
#include "data/blank_jpeg.h"
struct _jpeg_error_manager_t {
struct jpeg_error_mgr mgr; // Default manager
jmp_buf jmp;
};
static struct blank_t *_blank_init_internal(void);
static struct blank_t *_blank_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;
if (path) {
blank = _blank_init_external(path);
}
if (blank) {
LOG_INFO("Using external blank placeholder: %s", path);
} else {
blank = _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);
blank->width = BLANK_JPEG_WIDTH;
blank->height = BLANK_JPEG_HEIGHT;
return blank;
}
static struct blank_t *_blank_init_external(const char *path) {
FILE *fp = NULL;
struct blank_t *blank;
A_CALLOC(blank, 1);
if ((fp = fopen(path, "rb")) == NULL) {
LOG_PERROR("Can't open blank placeholder '%s'", path);
goto error;
}
if (_jpeg_read_geometry(fp, &blank->width, &blank->height) < 0) {
goto error;
}
if (fseek(fp, 0, SEEK_SET) < 0) {
LOG_PERROR("Can't seek to begin of the blank placeholder");
goto error;
}
# define CHUNK_SIZE (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);
}
size_t readed = fread(blank->picture.data + blank->picture.used, 1, CHUNK_SIZE, fp);
blank->picture.used += readed;
if (readed < CHUNK_SIZE) {
if (feof(fp)) {
goto ok;
} else {
LOG_PERROR("Can't read blank placeholder");
goto error;
}
}
}
# undef CHUNK_SIZE
error:
free(blank->picture.data);
free(blank);
blank = NULL;
ok:
if (fp) {
fclose(fp);
}
return blank;
}
static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height) {
struct jpeg_decompress_struct jpeg;
struct _jpeg_error_manager_t jpeg_error;
jpeg_create_decompress(&jpeg);
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
jpeg.err = jpeg_std_error((struct jpeg_error_mgr *)&jpeg_error);
jpeg_error.mgr.error_exit = _jpeg_error_handler;
if (setjmp(jpeg_error.jmp) < 0) {
jpeg_destroy_decompress(&jpeg);
return -1;
}
jpeg_stdio_src(&jpeg, fp);
jpeg_read_header(&jpeg, TRUE);
jpeg_start_decompress(&jpeg);
*width = jpeg.output_width;
*height = jpeg.output_height;
jpeg_destroy_decompress(&jpeg);
return 0;
}
static void _jpeg_error_handler(j_common_ptr jpeg) {
struct _jpeg_error_manager_t *jpeg_error = (struct _jpeg_error_manager_t *)jpeg->err;
char msg[JMSG_LENGTH_MAX];
(*jpeg_error->mgr.format_message)(jpeg, msg);
LOG_ERROR("Invalid blank placeholder: %s", msg);
longjmp(jpeg_error->jmp, -1);
}

38
src/http/blank.h Normal file
View File

@@ -0,0 +1,38 @@
/*****************************************************************************
# #
# 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 "../device.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);

File diff suppressed because it is too large Load Diff

View File

@@ -54,6 +54,6 @@
</ul>
<br>
<hr>
<a href="https://github.com/pi-kvm/ustreamer">Sources &amp; docs</a>
<a href="https://github.com/pikvm/ustreamer">Sources &amp; docs</a>
</body>
</html>

View File

@@ -82,7 +82,7 @@ const char HTML_INDEX_PAGE[] = " \
</ul> \
<br> \
<hr> \
<a href=\"https://github.com/pi-kvm/ustreamer\">Sources &amp; docs</a> \
<a href=\"https://github.com/pikvm/ustreamer\">Sources &amp; docs</a> \
</body> \
</html> \
";

View File

@@ -20,14 +20,14 @@
*****************************************************************************/
#include "mime.h"
#include <string.h>
#include <event2/util.h>
#include "../tools.h"
#include "mime.h"
static const struct {
const char *ext;

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "path.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -27,8 +29,6 @@
#include "../tools.h"
#include "path.h"
char *simplify_request_path(const char *str) {
// Based on Lighttpd sources:

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "server.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
@@ -51,14 +53,16 @@
#include "../logging.h"
#include "../encoder.h"
#include "../stream.h"
#ifdef WITH_GPIO
# include "../gpio.h"
#endif
#include "blank.h"
#include "base64.h"
#include "mime.h"
#include "static.h"
#include "server.h"
#include "data/index_html.h"
#include "data/blank_jpeg.h"
static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
@@ -77,7 +81,7 @@ static void _http_callback_stream_error(struct bufferevent *buf_event, short wha
static void _http_exposed_refresh(int fd, short event, void *v_server);
static void _http_queue_send_stream(struct http_server_t *server, bool stream_updated, bool picture_updated);
static bool _expose_new_picture(struct http_server_t *server);
static bool _expose_new_picture_unsafe(struct http_server_t *server);
static bool _expose_blank_picture(struct http_server_t *server);
@@ -103,8 +107,6 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
server->timeout = 10;
server->run = run;
_expose_blank_picture(server);
assert(!evthread_use_pthreads());
assert((run->base = event_base_new()));
assert((run->http = evhttp_new(run->base)));
@@ -123,7 +125,10 @@ void http_server_destroy(struct http_server_t *server) {
close(server->run->unix_fd);
}
event_base_free(server->run->base);
# if LIBEVENT_VERSION_NUMBER >= 0x02010100
libevent_global_shutdown();
# endif
for (struct stream_client_t *client = server->run->stream_clients; client != NULL;) {
struct stream_client_t *next = client->next;
@@ -137,6 +142,10 @@ void http_server_destroy(struct http_server_t *server) {
free(server->run->auth_token);
}
if (server->run->blank) {
blank_destroy(server->run->blank);
}
free(server->run->exposed->picture.data);
free(server->run->exposed);
free(server->run);
@@ -155,6 +164,10 @@ int http_server_listen(struct http_server_t *server) {
assert(!evhttp_set_cb(server->run->http, "/stream", _http_callback_stream, (void *)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);
{
struct timeval refresh_interval;
@@ -169,8 +182,6 @@ int http_server_listen(struct http_server_t *server) {
assert(!event_add(server->run->refresh, &refresh_interval));
}
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
if (server->slowdown) {
stream_switch_slowdown(server->run->stream, true);
}
@@ -195,19 +206,25 @@ int http_server_listen(struct http_server_t *server) {
if (server->unix_path[0] != '\0') {
struct sockaddr_un unix_addr;
int unix_fd_flags;
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
assert((server->run->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0)));
assert((unix_fd_flags = fcntl(server->run->unix_fd, F_GETFL)) >= 0);
unix_fd_flags |= O_NONBLOCK;
assert(fcntl(server->run->unix_fd, F_SETFL, unix_fd_flags) >= 0);
# define MAX_SUN_PATH (sizeof(unix_addr.sun_path) - 1)
strncpy(unix_addr.sun_path, server->unix_path, 107);
unix_addr.sun_path[107] = '\0';
if (strlen(server->unix_path) > MAX_SUN_PATH) {
LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
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);
@@ -262,7 +279,6 @@ static bool _http_get_param_true(struct evkeyvalq *params, const char *key) {
if ((value_str = evhttp_find_header(params, key)) != NULL) {
if (
value_str[0] == '1'
|| value_str[0] == 'y'
|| !evutil_ascii_strcasecmp(value_str, "true")
|| !evutil_ascii_strcasecmp(value_str, "yes")
) {
@@ -523,7 +539,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
PREPROCESS_REQUEST;
conn = evhttp_request_get_connection(request);
if (conn != NULL) {
if (conn) {
A_CALLOC(client, 1);
client->server = server;
client->request = request;
@@ -551,12 +567,18 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
}
server->run->stream_clients_count += 1;
if (server->slowdown && server->run->stream_clients_count == 1) {
stream_switch_slowdown(server->run->stream, false);
if (server->run->stream_clients_count == 1) {
if (server->slowdown) {
stream_switch_slowdown(server->run->stream, false);
}
# ifdef WITH_GPIO
GPIO_SET_HIGH(has_http_clients);
# 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 the new stream 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);
@@ -641,8 +663,9 @@ 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) * sizeof(*EXPOSED(picture.data)),
get_now_real(), (client->extra_headers ? "" : RN)
EXPOSED(picture.used),
get_now_real(),
(client->extra_headers ? "" : RN)
));
if (client->extra_headers) {
assert(evbuffer_add_printf(buf,
@@ -675,10 +698,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
}
}
assert(!evbuffer_add(buf,
(void *)EXPOSED(picture.data),
EXPOSED(picture.used) * sizeof(*EXPOSED(picture.data))
));
assert(!evbuffer_add(buf, (void *)EXPOSED(picture.data), EXPOSED(picture.used)));
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
if (client->advance_headers) {
@@ -706,17 +726,24 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
# define RUN(_next) client->server->run->_next
RUN(stream_clients_count) -= 1;
if (client->server->slowdown && RUN(stream_clients_count) <= 0) {
stream_switch_slowdown(RUN(stream), true);
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);
# endif
}
conn = evhttp_request_get_connection(client->request);
if (conn != NULL) {
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));
if (conn != NULL) {
if (conn) {
evhttp_connection_free(conn);
}
@@ -744,7 +771,7 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
conn = evhttp_request_get_connection(client->request);
if (conn != NULL) {
if (conn) {
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
// и нужно послать два фрейма, чтобы серия была вовремя завершена.
@@ -797,7 +824,7 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
LOG_DEBUG("Refreshing HTTP exposed ...");
A_MUTEX_LOCK(&server->run->stream->mutex);
if (server->run->stream->picture.used > 0) { // If online
picture_updated = _expose_new_picture(server);
picture_updated = _expose_new_picture_unsafe(server);
UNLOCK_STREAM;
} else {
UNLOCK_STREAM;
@@ -815,7 +842,7 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
_http_queue_send_stream(server, stream_updated, picture_updated);
}
static bool _expose_new_picture(struct http_server_t *server) {
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
@@ -824,8 +851,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
EXPOSED(expose_begin_time) = get_now_monotonic();
# define MEM_STREAM_TO_EXPOSED \
EXPOSED(picture.data), STREAM(picture.data), \
STREAM(picture.used) * sizeof(*STREAM(picture.data))
EXPOSED(picture.data), STREAM(picture.data), STREAM(picture.used)
if (server->drop_same_frames) {
if (
@@ -836,13 +862,13 @@ static bool _expose_new_picture(struct http_server_t *server) {
) {
EXPOSED(expose_cmp_time) = get_now_monotonic();
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
LOG_VERBOSE("HTTP: dropped same frame number %u; comparsion time = %.06Lf",
LOG_VERBOSE("HTTP: dropped same frame number %u; cmp_time=%.06Lf",
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
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); comparsion time = %.06Lf",
LOG_VERBOSE("HTTP: passed same frame check (frames are differ); cmp_time=%.06Lf",
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
}
}
@@ -869,7 +895,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
EXPOSED(expose_end_time) = get_now_monotonic();
LOG_VERBOSE("HTTP: exposed new frame; full exposition time = %.06Lf",
LOG_VERBOSE("HTTP: exposed new frame; full exposition time = %.06Lf",
EXPOSED(expose_end_time) - EXPOSED(expose_begin_time));
# undef EXPOSED
@@ -878,28 +904,28 @@ static bool _expose_new_picture(struct http_server_t *server) {
}
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 BLANK_JPEG_LEN ARRAY_LEN(BLANK_JPEG_DATA)
EXPOSED(expose_begin_time) = get_now_monotonic();
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
if (EXPOSED(online) || EXPOSED(picture.used) == 0) {
if (EXPOSED(picture.allocated) < BLANK_JPEG_LEN) {
A_REALLOC(EXPOSED(picture.data), BLANK_JPEG_LEN);
EXPOSED(picture.allocated) = BLANK_JPEG_LEN;
if (EXPOSED(picture.allocated) < BLANK(picture.used)) {
A_REALLOC(EXPOSED(picture.data), BLANK(picture.used));
EXPOSED(picture.allocated) = BLANK(picture.used);
}
memcpy(EXPOSED(picture.data), BLANK_JPEG_DATA, BLANK_JPEG_LEN * sizeof(*EXPOSED(picture.data)));
memcpy(EXPOSED(picture.data), BLANK(picture.data), BLANK(picture.used));
EXPOSED(picture.used) = BLANK_JPEG_LEN;
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_JPEG_WIDTH;
EXPOSED(height) = BLANK_JPEG_HEIGHT;
EXPOSED(width) = BLANK(width);
EXPOSED(height) = BLANK(height);
EXPOSED(captured_fps) = 0;
EXPOSED(online) = false;
goto updated;
@@ -917,6 +943,6 @@ static bool _expose_blank_picture(struct http_server_t *server) {
EXPOSED(expose_end_time) = get_now_monotonic();
return true; // Updated
# undef BLANK_JPEG_LEN
# undef EXPOSED
# undef BLANK
}

View File

@@ -33,6 +33,8 @@
#include "../tools.h"
#include "../stream.h"
#include "blank.h"
struct stream_client_t {
struct http_server_t *server;
@@ -78,6 +80,7 @@ struct http_server_runtime_t {
struct exposed_t *exposed;
struct stream_client_t *stream_clients;
unsigned stream_clients_count;
struct blank_t *blank;
unsigned drop_same_frames_blank;
};
@@ -87,14 +90,17 @@ struct http_server_t {
char *unix_path;
bool unix_rm;
mode_t unix_mode;
unsigned timeout;
char *user;
char *passwd;
char *static_path;
char *blank_path;
unsigned drop_same_frames;
bool slowdown;
unsigned fake_width;
unsigned fake_height;
unsigned timeout;
struct http_server_runtime_t *run;
};

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "static.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -32,7 +34,6 @@
#include "../logging.h"
#include "path.h"
#include "static.h"
char *find_static_file_path(const char *root_path, const char *request_path) {

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>
@@ -34,23 +36,39 @@
#include "tools.h"
unsigned log_level;
enum {
LOG_LEVEL_INFO,
LOG_LEVEL_PERF,
LOG_LEVEL_VERBOSE,
LOG_LEVEL_DEBUG,
} log_level;
bool log_colored;
pthread_mutex_t log_mutex;
#define LOG_LEVEL_INFO 0
#define LOG_LEVEL_PERF 1
#define LOG_LEVEL_VERBOSE 2
#define LOG_LEVEL_DEBUG 3
#define LOGGING_INIT { \
log_level = LOG_LEVEL_INFO; \
log_colored = isatty(1); \
assert(!pthread_mutex_init(&log_mutex, NULL)); \
}
#define LOGGING_INIT assert(!pthread_mutex_init(&log_mutex, NULL))
#define LOGGING_DESTROY assert(!pthread_mutex_destroy(&log_mutex))
#define LOGGING_DESTROY assert(!pthread_mutex_destroy(&log_mutex))
#define LOGGING_LOCK assert(!pthread_mutex_lock(&log_mutex))
#define LOGGING_UNLOCK assert(!pthread_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) { \
LOGGING_LOCK; \
for (int _i = 0; _i < 80; ++_i) { \
@@ -67,66 +85,73 @@ 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, ...) { \
if (log_colored) { \
printf(COLOR_GRAY "-- " _label_color _label COLOR_GRAY " [%.03Lf tid=%d]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
get_now_monotonic(), get_thread_id(), ##__VA_ARGS__); \
} else { \
printf("-- " _label " [%.03Lf tid=%d] -- " _msg, \
get_now_monotonic(), get_thread_id(), ##__VA_ARGS__); \
} \
putchar('\n'); \
fflush(stdout); \
}
#define LOG_ERROR(_msg, ...) { \
#define LOG_PRINTF(_label_color, _label, _msg_color, _msg, ...) { \
LOGGING_LOCK; \
LOG_PRINTF_NOLOCK("ERROR", _msg, ##__VA_ARGS__); \
LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \
}
#define LOG_ERROR(_msg, ...) { \
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; \
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _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_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) {
#if defined(__GLIBC__) && defined(_GNU_SOURCE)
# if defined(__GLIBC__) && defined(_GNU_SOURCE)
return strerror_r(errno, buf, size);
#else
# else
strerror_r(errno, buf, size);
return buf;
#endif
# endif
}

View File

@@ -27,298 +27,31 @@
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>
#include <pthread.h>
#include "config.h"
#include "tools.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"
#endif
static const char _SHORT_OPTS[] = "d:i:x:y:m:a:f:z:ntb:w:q:c:s:p:u:ro:e:lhv";
static const struct option _LONG_OPTS[] = {
{"device", required_argument, NULL, 'd'},
{"input", required_argument, NULL, 'i'},
{"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'},
{"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, 'r'},
{"unix-mode", required_argument, NULL, 'o'},
{"user", required_argument, NULL, 3000},
{"passwd", required_argument, NULL, 3001},
{"static", required_argument, NULL, 3002},
{"drop-same-frames", required_argument, NULL, 'e'},
{"slowdown", no_argument, NULL, 'l'},
{"fake-width", required_argument, NULL, 3003},
{"fake-height", required_argument, NULL, 3004},
{"server-timeout", required_argument, NULL, 3005},
{"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_ENCODER
printf(" + OMX");
# 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(" -x|--width <N> ───────────────── Initial image width. Default: %u.\n\n", dev->width);
printf(" -y|--height <N> ──────────────── Initial image height. Default: %u.\n\n", 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 as 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 queriyng and events processing.\n");
printf(" Supports automatic resolution changing. 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 intermediate thread.\n");
printf(" Default: %u (the number of CPU cores (but not more 4) + 1).\n\n", dev->n_buffers);
printf(" -w|--workers <N> ─────────────── The number of worker threads. Default: %u (== --buffers).\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 affects to workers number.\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(" -r|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n\n");
printf(" -o|--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(" -e|--drop-same-frames <N> ── Don't send same 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 connected.\n");
printf(" Useful to reduce CPU consumption. Default: disabled.\n\n");
printf(" --fake-width <N> ─────────── Override image width for /state. Default: disabled.\n\n");
printf(" --fake-height <N> ────────── Override image height for /state. Default: disabled.\n\n");
printf(" --server-timeout <seconds> ─ Timeout for client connections. Default: %u.\n\n", server->timeout);
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_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_UNSIGNED(_dest, _name, _min, _max) { \
errno = 0; char *_end = NULL; int _tmp = strtol(optarg, &_end, 0); \
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_PARSE(_dest, _func, _invalid, _name) { \
if ((_dest = _func(optarg)) == _invalid) { \
printf("Unknown " _name ": %s\n", optarg); \
return -1; \
} \
break; \
}
# define OPT_INT(_dest, _name, _base) { \
errno = 0; char *_end = NULL; int _tmp = strtol(optarg, &_end, _base); \
if (errno || *_end) { \
printf("Invalid value for '%s=%s'\n", _name, optarg); \
return -1; \
} \
_dest = _tmp; \
break; \
}
# define OPT_CHMOD(_dest, _name) \
OPT_INT(_dest, _name, 8)
# define OPT_CTL(_dest) { \
dev->ctl->_dest.value_set = true; \
dev->ctl->_dest.auto_set = false; \
OPT_INT(dev->ctl->_dest.value, "--"#_dest, 10); \
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_UNSIGNED(dev->input, "--input", 0, 128);
case 'x': OPT_UNSIGNED(dev->width, "--width", VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH);
case 'y': OPT_UNSIGNED(dev->height, "--height", VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT);
# pragma GCC diagnostic ignored "-Wsign-compare"
# pragma GCC diagnostic push
case 'm': OPT_PARSE(dev->format, device_parse_format, FORMAT_UNKNOWN, "pixel format");
# pragma GCC diagnostic pop
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard");
case 'f': OPT_UNSIGNED(dev->desired_fps, "--desired-fps", 0, 30);
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192);
case 'n': OPT_SET(dev->persistent, true);
case 't': OPT_SET(dev->dv_timings, true);
case 'b': OPT_UNSIGNED(dev->n_buffers, "--buffers", 1, 32);
case 'w': OPT_UNSIGNED(dev->n_workers, "--workers", 1, 32);
case 'q': OPT_UNSIGNED(encoder->quality, "--quality", 1, 100);
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type");
case 1000: OPT_UNSIGNED(dev->timeout, "--device-timeout", 1, 60);
case 1001: OPT_UNSIGNED(dev->error_delay, "--device-error-delay", 1, 60);
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_UNSIGNED(server->port, "--port", 1, 65535);
case 'u': OPT_SET(server->unix_path, optarg);
case 'r': OPT_SET(server->unix_rm, true);
case 'o': OPT_CHMOD(server->unix_mode, "--unix-mode");
case 3000: OPT_SET(server->user, optarg);
case 3001: OPT_SET(server->passwd, optarg);
case 3002: OPT_SET(server->static_path, optarg);
case 'e': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
case 'l': OPT_SET(server->slowdown, true);
case 3003: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
case 3004: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
case 3005: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
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_UNSIGNED(log_level, "--log-level", 0, 3);
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_CHMOD
# undef OPT_INT
# undef OPT_PARSE
# undef OPT_UNSIGNED
# 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() {
static void _block_thread_signals(void) {
sigset_t mask;
assert(!sigemptyset(&mask));
assert(!sigaddset(&mask, SIGINT));
@@ -344,7 +77,7 @@ static void _signal_handler(int signum) {
http_server_loop_break(_ctx->server);
}
static void _install_signal_handlers() {
static void _install_signal_handlers(void) {
struct sigaction sig_act;
MEMSET_ZERO(sig_act);
@@ -372,24 +105,35 @@ int main(int argc, char *argv[]) {
LOGGING_INIT;
# ifdef WITH_GPIO
GPIO_INIT;
# endif
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 = parse_options(argc, argv, dev, encoder, server)) == 0) {
# ifdef WITH_GPIO
GPIO_INIT_PINOUT;
# endif
_install_signal_handlers();
encoder_prepare(encoder, dev);
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;
_ctx = &ctx;
if ((exit_code = http_server_listen(server)) == 0) {
# ifdef WITH_GPIO
GPIO_SET_HIGH(prog_running);
# endif
A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
A_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL);
A_THREAD_JOIN(server_loop_tid);
@@ -402,6 +146,10 @@ int main(int argc, char *argv[]) {
encoder_destroy(encoder);
device_destroy(dev);
# ifdef WITH_GPIO
GPIO_SET_LOW(prog_running);
# endif
LOGGING_DESTROY;
return (exit_code < 0 ? 1 : 0);
}

539
src/options.c Normal file
View File

@@ -0,0 +1,539 @@
/*****************************************************************************
# #
# 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 <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <getopt.h>
#include <assert.h>
#include "config.h"
#include "tools.h"
#include "logging.h"
#include "device.h"
#include "encoder.h"
#include "stream.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio.h"
#endif
enum _OPT_VALUES {
_O_DEVICE = 'd',
_O_INPUT = 'i',
_O_RESOLUTION = 'r',
_O_FORMAT = 'm',
_O_TV_STANDARD = 'a',
_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_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_BRIGHTNESS,
_O_BRIGHTNESS_AUTO,
_O_CONTRAST,
_O_SATURATION,
_O_HUE,
_O_HUE_AUTO,
_O_GAMMA,
_O_SHARPNESS,
_O_BACKLIGHT_COMPENSATION,
_O_WHITE_BALANCE,
_O_WHITE_BALANCE_AUTO,
_O_GAIN,
_O_GAIN_AUTO,
_O_USER,
_O_PASSWD,
_O_STATIC,
_O_SERVER_TIMEOUT,
#ifdef WITH_GPIO
_O_GPIO_PROG_RUNNING,
_O_GPIO_STREAM_ONLINE,
_O_GPIO_HAS_HTTP_CLIENTS,
_O_GPIO_WORKERS_BUSY_AT,
#endif
_O_LOG_LEVEL,
_O_PERF,
_O_VERBOSE,
_O_DEBUG,
_O_FORCE_LOG_COLORS,
_O_NO_LOG_COLORS,
};
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},
{"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},
{"brightness", required_argument, NULL, _O_BRIGHTNESS},
{"brightness-auto", no_argument, NULL, _O_BRIGHTNESS_AUTO},
{"contrast", required_argument, NULL, _O_CONTRAST},
{"saturation", required_argument, NULL, _O_SATURATION},
{"hue", required_argument, NULL, _O_HUE},
{"hue-auto", no_argument, NULL, _O_HUE_AUTO},
{"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},
{"white-balance-auto", no_argument, NULL, _O_WHITE_BALANCE_AUTO},
{"gain", required_argument, NULL, _O_GAIN},
{"gain-auto", no_argument, NULL, _O_GAIN_AUTO},
{"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},
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
#ifdef WITH_GPIO
{"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},
{"gpio-workers-busy-at", required_argument, NULL, _O_GPIO_WORKERS_BUSY_AT},
#endif
{"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},
{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 _version(bool nl);
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server);
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; \
}
# 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 ch;
int short_index;
int opt_index;
char short_opts[1024] = {0};
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(argc, argv, 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);
# pragma GCC diagnostic pop
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN);
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, 0, 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);
# ifdef WITH_OMX
case _O_GLITCHED_RESOLUTIONS: OPT_GLITCHED_RESOLUTIONS;
# 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_BRIGHTNESS: OPT_CTL(brightness);
case _O_BRIGHTNESS_AUTO: OPT_CTL_AUTO(brightness);
case _O_CONTRAST: OPT_CTL(contrast);
case _O_SATURATION: OPT_CTL(saturation);
case _O_HUE: OPT_CTL(hue);
case _O_HUE_AUTO: OPT_CTL_AUTO(hue);
case _O_GAMMA: OPT_CTL(gamma);
case _O_SHARPNESS: OPT_CTL(sharpness);
case _O_BACKLIGHT_COMPENSATION: OPT_CTL(backlight_compensation);
case _O_WHITE_BALANCE: OPT_CTL(white_balance);
case _O_WHITE_BALANCE_AUTO: OPT_CTL_AUTO(white_balance);
case _O_GAIN: OPT_CTL(gain);
case _O_GAIN_AUTO: OPT_CTL_AUTO(gain);
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_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_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
# ifdef WITH_GPIO
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio_pin_prog_running, 0, 256, 0);
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", gpio_pin_stream_online, 0, 256, 0);
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", gpio_pin_has_http_clients, 0, 256, 0);
case _O_GPIO_WORKERS_BUSY_AT: OPT_NUMBER("--gpio-workers-busy-at", gpio_pin_workers_busy_at, 0, 256, 0);
# endif
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: _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
# 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 _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("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: %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(" --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");
}

30
src/options.h Normal file
View File

@@ -0,0 +1,30 @@
/*****************************************************************************
# #
# 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"
int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server);

View File

@@ -20,6 +20,9 @@
*****************************************************************************/
#include "stream.h"
#include <stdbool.h>
#include <stdatomic.h>
#include <unistd.h>
#include <errno.h>
@@ -28,7 +31,6 @@
#include <pthread.h>
#include <sys/select.h>
#include <linux/videodev2.h>
#include "tools.h"
@@ -36,18 +38,68 @@
#include "xioctl.h"
#include "device.h"
#include "encoder.h"
#include "stream.h"
#ifdef WITH_GPIO
# include "gpio.h"
#endif
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool);
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index);
struct _worker_t {
pthread_t tid;
unsigned number;
atomic_bool *proc_stop;
atomic_bool *workers_stop;
static int _stream_init_loop(struct stream_t *stream, struct workers_pool_t *pool);
static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool);
long double last_comp_time;
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool);
static void *_stream_worker_thread(void *v_worker);
static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool_t *pool);
pthread_mutex_t has_job_mutex;
int buf_index;
atomic_bool has_job;
bool job_timely;
bool job_failed;
long double job_start_time;
pthread_cond_t has_job_cond;
pthread_mutex_t *free_workers_mutex;
unsigned *free_workers;
pthread_cond_t *free_workers_cond;
struct _worker_t *order_prev;
struct _worker_t *order_next;
struct device_t *dev;
struct encoder_t *encoder;
};
struct _workers_pool_t {
unsigned n_workers;
struct _worker_t *workers;
struct _worker_t *oldest_worker;
struct _worker_t *latest_worker;
long double approx_comp_time;
pthread_mutex_t free_workers_mutex;
unsigned free_workers;
pthread_cond_t free_workers_cond;
atomic_bool workers_stop;
long double desired_frames_interval;
};
static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream);
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);
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream);
static void _workers_pool_destroy(struct _workers_pool_t *pool);
static void *_worker_thread(void *v_worker);
static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool);
static void _workers_pool_assign(struct _workers_pool_t *pool, struct _worker_t *ready_worker, unsigned buf_index);
static long double _workers_pool_get_fluency_delay(struct _workers_pool_t *pool, struct _worker_t *ready_worker);
struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) {
@@ -74,20 +126,15 @@ void stream_destroy(struct stream_t *stream) {
}
void stream_loop(struct stream_t *stream) {
struct workers_pool_t pool;
MEMSET_ZERO(pool);
atomic_init(&pool.workers_stop, false);
pool.encoder = stream->encoder;
struct _workers_pool_t *pool;
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
while (_stream_init_loop(stream, &pool) == 0) {
struct worker_t *oldest_worker = NULL;
struct worker_t *last_worker = NULL;
while ((pool = _stream_init_loop(stream)) != NULL) {
long double grab_after = 0;
unsigned fluency_passed = 0;
unsigned captured_fps = 0;
unsigned captured_fps_accum = 0;
long long captured_fps_second = 0;
bool persistent_timeout_reported = false;
@@ -98,43 +145,22 @@ void stream_loop(struct stream_t *stream) {
LOG_INFO("Capturing ...");
while (!atomic_load(&stream->proc->stop)) {
int free_worker_number = -1;
struct _worker_t *ready_worker;
SEP_DEBUG('-');
LOG_DEBUG("Waiting for worker ...");
LOG_DEBUG("Waiting for workers ...");
A_MUTEX_LOCK(&pool.free_workers_mutex);
A_COND_WAIT_TRUE(pool.free_workers, &pool.free_workers_cond, &pool.free_workers_mutex);
A_MUTEX_UNLOCK(&pool.free_workers_mutex);
ready_worker = _workers_pool_wait(pool);
if (oldest_worker && !atomic_load(&oldest_worker->has_job) && oldest_worker->buf_index >= 0) {
if (oldest_worker->job_failed) {
break;
if (!ready_worker->job_failed) {
if (ready_worker->job_timely) {
_stream_expose_picture(stream, ready_worker->buf_index, captured_fps);
LOG_PERF("##### Encoded picture exposed; worker=%u", ready_worker->number);
} else {
LOG_PERF("----- Encoded picture dropped; worker=%u", ready_worker->number);
}
_stream_expose_picture(stream, oldest_worker->buf_index);
free_worker_number = oldest_worker->number;
oldest_worker = oldest_worker->order_next;
LOG_PERF("##### Raw frame accepted; worker = %u", free_worker_number);
} else {
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
if (
!atomic_load(&pool.workers[number].has_job) && (
free_worker_number == -1
|| pool.workers[free_worker_number].job_start_time < pool.workers[number].job_start_time
)
) {
free_worker_number = number;
break;
}
}
assert(free_worker_number >= 0);
assert(!atomic_load(&pool.workers[free_worker_number].has_job));
LOG_PERF("----- Raw frame dropped; worker = %u", free_worker_number);
break;
}
if (atomic_load(&stream->proc->stop)) {
@@ -145,35 +171,28 @@ void stream_loop(struct stream_t *stream) {
usleep(1000000);
}
# define INIT_FD_SET(_set) \
fd_set _set; FD_ZERO(&_set); FD_SET(stream->dev->run->fd, &_set);
bool has_read;
bool has_write;
bool has_error;
int selected = device_select(stream->dev, &has_read, &has_write, &has_error);
INIT_FD_SET(read_fds);
INIT_FD_SET(write_fds);
INIT_FD_SET(error_fds);
# undef INIT_FD_SET
struct timeval timeout;
timeout.tv_sec = stream->dev->timeout;
timeout.tv_usec = 0;
LOG_DEBUG("Calling select() on video device ...");
int retval = select(stream->dev->run->fd + 1, &read_fds, &write_fds, &error_fds, &timeout);
LOG_DEBUG("Device select() --> %d", retval);
if (retval < 0) {
if (selected < 0) {
if (errno != EINTR) {
LOG_PERROR("Mainloop select() error");
break;
}
} else if (retval == 0) {
} else if (selected == 0) {
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
# endif
if (stream->dev->persistent) {
if (!persistent_timeout_reported) {
LOG_ERROR("Mainloop select() timeout, polling ...")
persistent_timeout_reported = true;
}
continue;
} else {
LOG_ERROR("Mainloop select() timeout");
@@ -183,9 +202,13 @@ void stream_loop(struct stream_t *stream) {
} else {
persistent_timeout_reported = false;
if (FD_ISSET(stream->dev->run->fd, &read_fds)) {
if (has_read) {
LOG_DEBUG("Frame is ready");
# ifdef WITH_GPIO
GPIO_SET_HIGH(stream_online);
# endif
int buf_index;
long double now = get_now_monotonic();
long long now_second = floor_ms(now);
@@ -193,7 +216,6 @@ void stream_loop(struct stream_t *stream) {
if ((buf_index = device_grab_buffer(stream->dev)) < 0) {
break;
}
stream->dev->run->pictures[buf_index].grab_time = now;
// Workaround for broken, corrupted frames:
// Under low light conditions corrupted frames may get captured.
@@ -201,7 +223,7 @@ void stream_loop(struct stream_t *stream) {
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
// corrupted frames are smaller.
if (stream->dev->run->hw_buffers[buf_index].used < stream->dev->min_frame_size) {
LOG_DEBUG("Dropping too small frame sized %zu bytes, assuming it as broken",
LOG_DEBUG("Dropped too small frame sized %zu bytes, assuming it was broken",
stream->dev->run->hw_buffers[buf_index].used);
goto pass_frame;
}
@@ -209,56 +231,26 @@ void stream_loop(struct stream_t *stream) {
{
if (now < grab_after) {
fluency_passed += 1;
LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf; grab_after=%.03Lf", fluency_passed, now, grab_after);
LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf, grab_after=%.03Lf", fluency_passed, now, grab_after);
goto pass_frame;
}
fluency_passed = 0;
if (now_second != captured_fps_second) {
stream->captured_fps = captured_fps_accum;
captured_fps = captured_fps_accum;
captured_fps_accum = 0;
captured_fps_second = now_second;
LOG_PERF("Oldest worker complete, Captured-FPS = %u", stream->captured_fps);
LOG_PERF_FPS("A new second has come; captured_fps=%u", captured_fps);
}
captured_fps_accum += 1;
long double fluency_delay = _stream_get_fluency_delay(stream->dev, &pool);
long double fluency_delay = _workers_pool_get_fluency_delay(pool, ready_worker);
grab_after = now + fluency_delay;
LOG_VERBOSE("Fluency: delay=%.03Lf; grab_after=%.03Lf", fluency_delay, grab_after);
LOG_VERBOSE("Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after);
}
# define FREE_WORKER(_next) pool.workers[free_worker_number]._next
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_index);
if (!oldest_worker) {
oldest_worker = &pool.workers[free_worker_number];
last_worker = oldest_worker;
} else {
if (FREE_WORKER(order_next)) {
FREE_WORKER(order_next->order_prev) = FREE_WORKER(order_prev);
}
if (FREE_WORKER(order_prev)) {
FREE_WORKER(order_prev->order_next) = FREE_WORKER(order_next);
}
FREE_WORKER(order_prev) = last_worker;
last_worker->order_next = &pool.workers[free_worker_number];
last_worker = &pool.workers[free_worker_number];
}
last_worker->order_next = NULL;
A_MUTEX_LOCK(&FREE_WORKER(has_job_mutex));
FREE_WORKER(buf_index) = buf_index;
atomic_store(&FREE_WORKER(has_job), true);
A_MUTEX_UNLOCK(&FREE_WORKER(has_job_mutex));
A_COND_SIGNAL(&FREE_WORKER(has_job_cond));
# undef FREE_WORKER
A_MUTEX_LOCK(&pool.free_workers_mutex);
pool.free_workers -= 1;
A_MUTEX_UNLOCK(&pool.free_workers_mutex);
_workers_pool_assign(pool, ready_worker, buf_index);
goto next_handlers; // Поток сам освободит буфер
@@ -271,12 +263,12 @@ void stream_loop(struct stream_t *stream) {
next_handlers:
if (FD_ISSET(stream->dev->run->fd, &write_fds)) {
if (has_write) {
LOG_ERROR("Got unexpected writing event, seems device was disconnected");
break;
}
if (FD_ISSET(stream->dev->run->fd, &error_fds)) {
if (has_error) {
LOG_INFO("Got V4L2 event");
if (device_consume_event(stream->dev) < 0) {
break;
@@ -292,11 +284,15 @@ void stream_loop(struct stream_t *stream) {
stream->height = 0;
atomic_store(&stream->updated, true);
A_MUTEX_UNLOCK(&stream->mutex);
}
_stream_destroy_workers(stream, &pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
_workers_pool_destroy(pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
# endif
}
}
void stream_loop_break(struct stream_t *stream) {
@@ -307,81 +303,25 @@ void stream_switch_slowdown(struct stream_t *stream, bool slowdown) {
atomic_store(&stream->proc->slowdown, slowdown);
}
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index) {
# define PICTURE(_next) stream->dev->run->pictures[buf_index]._next
static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream) {
struct _workers_pool_t *pool = NULL;
A_MUTEX_LOCK(&stream->mutex);
LOG_DEBUG("%s: stream->proc->stop=%d", __FUNCTION__, atomic_load(&stream->proc->stop));
stream->picture.used = PICTURE(used);
stream->picture.allocated = PICTURE(allocated);
memcpy(stream->picture.data, PICTURE(data), stream->picture.used * sizeof(*stream->picture.data));
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;
atomic_store(&stream->updated, true);
A_MUTEX_UNLOCK(&stream->mutex);
# undef PICTURE
}
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool) {
long double sum_comp_time = 0;
long double avg_comp_time;
long double min_delay;
long double soft_delay;
for (unsigned number = 0; number < dev->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_MUTEX_LOCK(&WORKER(last_comp_time_mutex));
if (WORKER(last_comp_time) > 0) {
sum_comp_time += WORKER(last_comp_time);
}
A_MUTEX_UNLOCK(&WORKER(last_comp_time_mutex));
# undef WORKER
}
avg_comp_time = sum_comp_time / dev->n_workers; // Среднее время работы воркеров
min_delay = avg_comp_time / dev->n_workers; // Среднее время работы размазывается на N воркеров
if (dev->desired_fps > 0 && min_delay > 0) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
soft_delay = ((long double) 1) / dev->desired_fps - sum_comp_time;
return (min_delay > soft_delay ? min_delay : soft_delay);
}
return min_delay;
}
static int _stream_init_loop(struct stream_t *stream, struct workers_pool_t *pool) {
int retval = -1;
LOG_DEBUG("%s: stream->proc->stop = %d", __FUNCTION__, atomic_load(&stream->proc->stop));
while (!atomic_load(&stream->proc->stop)) {
if ((retval = _stream_init(stream, pool)) < 0) {
SEP_INFO('=');
if ((pool = _stream_init(stream)) == NULL) {
LOG_INFO("Sleeping %u seconds before new stream init ...", stream->dev->error_delay);
sleep(stream->dev->error_delay);
} else {
break;
}
}
return retval;
return pool;
}
static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
_stream_destroy_workers(stream, pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
SEP_INFO('=');
static struct _workers_pool_t *_stream_init(struct stream_t *stream) {
if (device_open(stream->dev) < 0) {
goto error;
}
@@ -389,59 +329,126 @@ static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
goto error;
}
encoder_prepare_live(pool->encoder, stream->dev);
_stream_init_workers(stream, pool);
return 0;
encoder_prepare(stream->encoder, stream->dev);
return _workers_pool_init(stream);
error:
device_close(stream->dev);
return -1;
return NULL;
}
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool) {
LOG_INFO("Spawning %u workers ...", stream->dev->n_workers);
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
atomic_store(&pool->workers_stop, false);
A_CALLOC(pool->workers, stream->dev->n_workers);
A_MUTEX_LOCK(&stream->mutex);
stream->picture.used = PICTURE(used);
stream->picture.allocated = PICTURE(allocated);
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->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);
A_CALLOC(pool, 1);
pool->n_workers = stream->dev->run->n_workers;
A_CALLOC(pool->workers, pool->n_workers);
A_MUTEX_INIT(&pool->free_workers_mutex);
A_COND_INIT(&pool->free_workers_cond);
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
atomic_init(&pool->workers_stop, false);
pool->free_workers += 1;
if (stream->dev->desired_fps > 0) {
pool->desired_frames_interval = (long double)1 / stream->dev->desired_fps;
}
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_MUTEX_INIT(&WORKER(has_job_mutex));
atomic_init(&WORKER(has_job), false);
A_COND_INIT(&WORKER(has_job_cond));
WORKER(number) = number;
WORKER(proc_stop) = &stream->proc->stop;
WORKER(workers_stop) = &pool->workers_stop;
WORKER(number) = number;
WORKER(proc_stop) = &stream->proc->stop;
WORKER(workers_stop) = &pool->workers_stop;
WORKER(free_workers_mutex) = &pool->free_workers_mutex;
WORKER(free_workers) = &pool->free_workers;
WORKER(free_workers_cond) = &pool->free_workers_cond;
WORKER(free_workers_mutex) = &pool->free_workers_mutex;
WORKER(free_workers) = &pool->free_workers;
WORKER(free_workers_cond) = &pool->free_workers_cond;
WORKER(dev) = stream->dev;
WORKER(encoder) = pool->encoder;
WORKER(dev) = stream->dev;
WORKER(encoder) = stream->encoder;
A_THREAD_CREATE(&WORKER(tid), _stream_worker_thread, (void *)&(pool->workers[number]));
A_THREAD_CREATE(&WORKER(tid), _worker_thread, (void *)&(pool->workers[number]));
pool->free_workers += 1;
# undef WORKER
}
return pool;
}
static void *_stream_worker_thread(void *v_worker) {
struct worker_t *worker = (struct worker_t *)v_worker;
static void _workers_pool_destroy(struct _workers_pool_t *pool) {
LOG_INFO("Destroying workers pool ...");
atomic_store(&pool->workers_stop, true);
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_MUTEX_LOCK(&WORKER(has_job_mutex));
atomic_store(&WORKER(has_job), true); // Final job: die
A_MUTEX_UNLOCK(&WORKER(has_job_mutex));
A_COND_SIGNAL(&WORKER(has_job_cond));
A_THREAD_JOIN(WORKER(tid));
A_MUTEX_DESTROY(&WORKER(has_job_mutex));
A_COND_DESTROY(&WORKER(has_job_cond));
# undef WORKER
}
A_MUTEX_DESTROY(&pool->free_workers_mutex);
A_COND_DESTROY(&pool->free_workers_cond);
free(pool->workers);
free(pool);
}
static void *_worker_thread(void *v_worker) {
struct _worker_t *worker = (struct _worker_t *)v_worker;
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);
@@ -451,24 +458,22 @@ static void *_stream_worker_thread(void *v_worker) {
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index);
PICTURE(encode_begin_time) = get_now_monotonic();
# 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 = true;
worker->job_failed = false;
}
PICTURE(encode_end_time) = get_now_monotonic();
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
worker->job_start_time = PICTURE(encode_begin_time);
atomic_store(&worker->has_job, false);
long double last_comp_time = PICTURE(encode_end_time) - worker->job_start_time;
worker->last_comp_time = PICTURE(encode_end_time) - worker->job_start_time;
A_MUTEX_LOCK(&worker->last_comp_time_mutex);
worker->last_comp_time = last_comp_time;
A_MUTEX_UNLOCK(&worker->last_comp_time_mutex);
LOG_VERBOSE("Compressed JPEG size=%zu; time=%0.3Lf; worker=%u; buffer=%u",
PICTURE(used), 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 {
worker->job_failed = true;
atomic_store(&worker->has_job, false);
@@ -484,34 +489,87 @@ static void *_stream_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;
}
static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool_t *pool) {
if (pool->workers) {
LOG_INFO("Destroying workers ...");
static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool) {
struct _worker_t *ready_worker = NULL;
atomic_store(&pool->workers_stop, true);
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_MUTEX_LOCK(&pool->free_workers_mutex);
A_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex);
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
A_MUTEX_LOCK(&WORKER(has_job_mutex));
atomic_store(&WORKER(has_job), true); // Final job: die
A_MUTEX_UNLOCK(&WORKER(has_job_mutex));
A_COND_SIGNAL(&WORKER(has_job_cond));
A_THREAD_JOIN(WORKER(tid));
A_MUTEX_DESTROY(&WORKER(has_job_mutex));
A_COND_DESTROY(&WORKER(has_job_cond));
# undef WORKER
if (pool->oldest_worker && !atomic_load(&pool->oldest_worker->has_job) && pool->oldest_worker->buf_index >= 0) {
ready_worker = pool->oldest_worker;
ready_worker->job_timely = true;
pool->oldest_worker = pool->oldest_worker->order_next;
} else {
for (unsigned number = 0; number < pool->n_workers; ++number) {
if (
!atomic_load(&pool->workers[number].has_job) && (
ready_worker == NULL
|| ready_worker->job_start_time < pool->workers[number].job_start_time
)
) {
ready_worker = &pool->workers[number];
break;
}
}
A_MUTEX_DESTROY(&pool->free_workers_mutex);
A_COND_DESTROY(&pool->free_workers_cond);
free(pool->workers);
assert(ready_worker != NULL);
ready_worker->job_timely = false; // Освободился воркер, получивший задание позже (или самый первый при самом первом захвате)
}
pool->free_workers = 0;
pool->workers = NULL;
return ready_worker;
}
static void _workers_pool_assign(struct _workers_pool_t *pool, struct _worker_t *ready_worker, unsigned buf_index) {
if (pool->oldest_worker == NULL) {
pool->oldest_worker = ready_worker;
pool->latest_worker = pool->oldest_worker;
} else {
if (ready_worker->order_next) {
ready_worker->order_next->order_prev = ready_worker->order_prev;
}
if (ready_worker->order_prev) {
ready_worker->order_prev->order_next = ready_worker->order_next;
}
ready_worker->order_prev = pool->latest_worker;
pool->latest_worker->order_next = ready_worker;
pool->latest_worker = ready_worker;
}
pool->latest_worker->order_next = NULL;
A_MUTEX_LOCK(&ready_worker->has_job_mutex);
ready_worker->buf_index = buf_index;
atomic_store(&ready_worker->has_job, true);
A_MUTEX_UNLOCK(&ready_worker->has_job_mutex);
A_COND_SIGNAL(&ready_worker->has_job_cond);
A_MUTEX_LOCK(&pool->free_workers_mutex);
pool->free_workers -= 1;
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
LOG_DEBUG("Assigned new frame in buffer %u to worker %u", buf_index, ready_worker->number);
}
static long double _workers_pool_get_fluency_delay(struct _workers_pool_t *pool, struct _worker_t *ready_worker) {
long double approx_comp_time;
long double min_delay;
approx_comp_time = pool->approx_comp_time * 0.9 + ready_worker->last_comp_time * 0.1;
LOG_VERBOSE("Correcting approx_comp_time: %.3Lf -> %.3Lf (last_comp_time=%.3Lf)",
pool->approx_comp_time, approx_comp_time, ready_worker->last_comp_time);
pool->approx_comp_time = approx_comp_time;
min_delay = pool->approx_comp_time / pool->n_workers; // Среднее время работы размазывается на N воркеров
if (pool->desired_frames_interval > 0 && min_delay > 0 && pool->desired_frames_interval > min_delay) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
return pool->desired_frames_interval;
}
return min_delay;
}

View File

@@ -22,53 +22,12 @@
#pragma once
#include <stdbool.h>
#include <stdatomic.h>
#include <pthread.h>
#include "device.h"
#include "encoder.h"
struct worker_t {
pthread_t tid;
unsigned number;
atomic_bool *proc_stop;
atomic_bool *workers_stop;
pthread_mutex_t last_comp_time_mutex;
long double last_comp_time;
pthread_mutex_t has_job_mutex;
int buf_index;
atomic_bool has_job;
bool job_failed;
long double job_start_time;
pthread_cond_t has_job_cond;
pthread_mutex_t *free_workers_mutex;
unsigned *free_workers;
pthread_cond_t *free_workers_cond;
struct worker_t *order_prev;
struct worker_t *order_next;
struct device_t *dev;
struct encoder_t *encoder;
};
struct workers_pool_t {
struct worker_t *workers;
atomic_bool workers_stop;
pthread_mutex_t free_workers_mutex;
unsigned free_workers;
pthread_cond_t free_workers_cond;
struct encoder_t *encoder;
};
struct process_t {
atomic_bool stop;
atomic_bool slowdown;

View File

@@ -25,8 +25,7 @@ import textwrap
# =====
def get_prepend() -> str:
return textwrap.dedent("""
C_PREPEND = textwrap.dedent("""
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #

View File

@@ -29,24 +29,25 @@ import common
# =====
def main() -> None:
assert len(sys.argv) == 4, "%s <file.html> <file.h> <name>" % (sys.argv[0])
assert len(sys.argv) == 4, f"{sys.argv[0]} <file.html> <file.h> <name>"
html_path = sys.argv[1]
header_path = sys.argv[2]
name = sys.argv[3]
with open(html_path, "r") as html_file:
text = html_file.read()
html = html_file.read()
text = text.strip()
text = text.replace("\"", "\\\"")
text = text.replace("%VERSION%", "\" VERSION \"")
text = textwrap.indent(text, "\t", (lambda line: True))
text = "\n".join(
("%s \\" if line.strip() else "%s\\") % (line)
for line in text.split("\n")
html = html.strip()
html = html.replace("\"", "\\\"")
html = html.replace("%VERSION%", "\" VERSION \"")
html = textwrap.indent(html, "\t", (lambda line: True))
html = "\n".join(
(f"{line} \\" if line.strip() else f"{line}\\")
for line in html.split("\n")
)
text = "const char HTML_%s_PAGE[] = \" \\\n%s\n\";\n" % (name, text)
text = common.get_prepend() + "\n#include \"../../config.h\"\n\n\n" + text
text = f"{common.C_PREPEND}\n#include \"../../config.h\"\n\n\n"
text += f"const char HTML_{name}_PAGE[] = \" \\\n{html}\n\";\n"
with open(header_path, "w") as header_file:
header_file.write(text)

View File

@@ -60,7 +60,7 @@ def _get_jpeg_size(data: bytes) -> Tuple[int, int]:
# =====
def main() -> None:
assert len(sys.argv) == 4, "%s <file.jpeg> <file.h> <name>" % (sys.argv[0])
assert len(sys.argv) == 4, f"{sys.argv[0]} <file.jpeg> <file.h> <name>"
jpeg_path = sys.argv[1]
header_path = sys.argv[2]
name = sys.argv[3]
@@ -70,17 +70,18 @@ def main() -> None:
(width, height) = _get_jpeg_size(jpeg_data)
rows: List[List[str]] = [[]]
for ch in jpeg_data:
if len(rows[-1]) > 20:
rows.append([])
rows[-1].append("0x%.2X" % (ch))
jpeg_data_text = "{\n\t" + ",\n\t".join(
", ".join(
f"0x{ch:02X}"
for ch in jpeg_data[index:index + 20]
)
for index in range(0, len(jpeg_data), 20)
) + ",\n}"
text = ",\n\t".join(", ".join(row) for row in rows)
text = "const unsigned char %s_JPEG_DATA[] = {\n\t%s\n};\n" % (name, text)
text = "const unsigned %s_JPEG_HEIGHT = %d;\n\n" % (name, height) + text
text = "const unsigned %s_JPEG_WIDTH = %d;\n" % (name, width) + text
text = common.get_prepend() + "\n\n" + text
text = f"{common.C_PREPEND}\n\n"
text += f"const unsigned {name}_JPEG_WIDTH = {width};\n"
text += f"const unsigned {name}_JPEG_HEIGHT = {height};\n\n"
text += f"const unsigned char {name}_JPEG_DATA[] = {jpeg_data_text};\n"
with open(header_path, "w") as header_file:
header_file.write(text)