mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 20:26:31 +00:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c94117ae1e | ||
|
|
06a80df708 | ||
|
|
13dff256c8 | ||
|
|
07e9dbc0f7 | ||
|
|
c83dddbf0b | ||
|
|
5672d1aa75 | ||
|
|
078097efec | ||
|
|
3cd8338886 | ||
|
|
d2b57cc7d5 | ||
|
|
2c4f59c87a | ||
|
|
36eb7eeb76 | ||
|
|
3b6544db8a | ||
|
|
8033ab23ed | ||
|
|
ddb3db8b20 | ||
|
|
b3c8071edb | ||
|
|
bc70faae09 | ||
|
|
ae7c4c91e0 | ||
|
|
613baa4e1e | ||
|
|
33a101b4f7 | ||
|
|
9e9039c4e6 | ||
|
|
b3ceec51de | ||
|
|
e3ac4ba6f5 | ||
|
|
fa6b8b44c1 | ||
|
|
8cb7574af2 | ||
|
|
c34d644c2a | ||
|
|
7857fa8f63 | ||
|
|
3253de83dc | ||
|
|
030464c3b8 | ||
|
|
0020aa69ec | ||
|
|
93bfa56ccf | ||
|
|
3392ac5fbc | ||
|
|
2077d94edc | ||
|
|
defe5eb6fe | ||
|
|
dee5e18134 | ||
|
|
6ef5a7e440 | ||
|
|
462735147d | ||
|
|
51ca0e4474 | ||
|
|
4ee3b18533 | ||
|
|
cdc9ed54c9 | ||
|
|
d9e7c07851 | ||
|
|
f2debc5d16 | ||
|
|
b3dbaf40cf | ||
|
|
abfc7b917b | ||
|
|
a0e488b0a5 | ||
|
|
6b99df2792 | ||
|
|
6f8434a5c2 | ||
|
|
b15888dbd4 | ||
|
|
2e96d74ac0 | ||
|
|
fc4cbb1fe1 | ||
|
|
67f9bcf4c8 | ||
|
|
da227ec234 | ||
|
|
933be02c86 | ||
|
|
28e979a2be | ||
|
|
8cde363338 | ||
|
|
4741fe1952 | ||
|
|
7d4ae57fbd | ||
|
|
c50388ab9f | ||
|
|
17099e86de | ||
|
|
6528352e04 | ||
|
|
4f7b426068 | ||
|
|
acc8628f3d | ||
|
|
46e99be201 | ||
|
|
7fbeca41fa | ||
|
|
73ceba77a8 | ||
|
|
a3e5d17628 | ||
|
|
c30dea20a5 | ||
|
|
b31450ba41 | ||
|
|
c05457ce2f | ||
|
|
9e63076ec5 |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 0.59
|
current_version = 0.76
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
@@ -10,7 +10,11 @@ serialize =
|
|||||||
search = VERSION "{current_version}"
|
search = VERSION "{current_version}"
|
||||||
replace = VERSION "{new_version}"
|
replace = VERSION "{new_version}"
|
||||||
|
|
||||||
[bumpversion:file:PKGBUILD]
|
[bumpversion:file:pkg/arch/PKGBUILD]
|
||||||
search = pkgver={current_version}
|
search = pkgver={current_version}
|
||||||
replace = pkgver={new_version}
|
replace = pkgver={new_version}
|
||||||
|
|
||||||
|
[bumpversion:file:pkg/openwrt/Makefile]
|
||||||
|
search = PKG_VERSION:={current_version}
|
||||||
|
replace = PKG_VERSION:={new_version}
|
||||||
|
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,8 +1,7 @@
|
|||||||
/pkg/
|
/pkg/arch/pkg/
|
||||||
/src/ustreamer-*/
|
/pkg/arch/src/
|
||||||
/src/v*.tar.gz
|
/pkg/arch/v*.tar.gz
|
||||||
/v*.tar.gz
|
/pkg/arch/ustreamer-*.pkg.tar.xz
|
||||||
/ustreamer-*.pkg.tar.xz
|
|
||||||
/vgcore.*
|
/vgcore.*
|
||||||
/ustreamer
|
/ustreamer
|
||||||
/*.sock
|
/*.sock
|
||||||
|
|||||||
54
Makefile
54
Makefile
@@ -1,24 +1,27 @@
|
|||||||
|
PROG ?= ustreamer
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
|
|
||||||
|
CC ?= gcc
|
||||||
CFLAGS ?= -O3
|
CFLAGS ?= -O3
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
CC ?= gcc
|
|
||||||
|
RPI_VC_HEADERS ?= /opt/vc/include
|
||||||
|
RPI_VC_LIBS ?= /opt/vc/lib
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
|
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
|
||||||
override CFLAGS += -c -std=c11 -Wall -Wextra -D_GNU_SOURCE
|
override CFLAGS += -c -std=c11 -Wall -Wextra -D_GNU_SOURCE
|
||||||
SOURCES = $(shell ls src/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
|
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)
|
ifeq ($(WITH_OMX_ENCODER),)
|
||||||
SOURCES += $(shell ls src/encoders/omx/*.c)
|
else
|
||||||
LIBS += -lbcm_host -lvcos -lopenmaxil -L/opt/vc/lib
|
LIBS += -lbcm_host -lvcos -lopenmaxil -L$(RPI_VC_LIBS)
|
||||||
override CFLAGS += -DWITH_OMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
|
override CFLAGS += -DWITH_OMX_ENCODER -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
|
||||||
|
SOURCES += $(shell ls src/encoders/omx/*.c)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
OBJECTS = $(SOURCES:.c=.o)
|
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
all: $(SOURCES) $(PROG)
|
all: $(SOURCES) $(PROG)
|
||||||
@@ -28,17 +31,32 @@ install: $(PROG)
|
|||||||
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
|
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
|
||||||
|
|
||||||
|
|
||||||
|
install-strip: install
|
||||||
|
strip $(DESTDIR)$(PREFIX)/bin/$(PROG)
|
||||||
|
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm $(DESTDIR)$(PREFIX)/bin/$(PROG)
|
||||||
|
|
||||||
|
|
||||||
regen:
|
regen:
|
||||||
tools/make-jpeg-h.py src/data/blank.jpeg src/data/blank_jpeg.h BLANK 640 480
|
tools/make-jpeg-h.py src/http/data/blank.jpeg src/http/data/blank_jpeg.h BLANK
|
||||||
tools/make-html-h.py src/data/index.html src/data/index_html.h HTML_INDEX_PAGE
|
tools/make-html-h.py src/http/data/index.html src/http/data/index_html.h INDEX
|
||||||
|
|
||||||
|
|
||||||
$(PROG): $(OBJECTS)
|
$(PROG): $(SOURCES:.c=.o)
|
||||||
$(CC) $(LIBS) $(LDFLAGS) $(OBJECTS) -o $@
|
$(info -- LINKING $@)
|
||||||
|
@ $(CC) $(SOURCES:.c=.o) -o $@ $(LDFLAGS) $(LIBS)
|
||||||
|
$(info ===== Build complete =====)
|
||||||
|
$(info == CC = $(CC))
|
||||||
|
$(info == LIBS = $(LIBS))
|
||||||
|
$(info == CFLAGS = $(CFLAGS))
|
||||||
|
$(info == LDFLAGS = $(LDFLAGS))
|
||||||
|
|
||||||
|
|
||||||
.c.o:
|
.c.o:
|
||||||
$(CC) $(LIBS) $(CFLAGS) $< -o $@
|
$(info -- CC $<)
|
||||||
|
@ $(CC) $< -o $@ $(CFLAGS) $(LIBS)
|
||||||
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
@@ -50,14 +68,16 @@ release:
|
|||||||
|
|
||||||
|
|
||||||
bump:
|
bump:
|
||||||
bumpversion minor
|
bumpversion $(if $(V),$(V),minor)
|
||||||
|
|
||||||
|
|
||||||
push:
|
push:
|
||||||
git push
|
git push
|
||||||
git push --tags
|
git push --tags
|
||||||
|
|
||||||
|
|
||||||
clean-all: clean
|
clean-all: clean
|
||||||
clean:
|
clean:
|
||||||
rm -f src/*.o src/encoders/*/*.o vgcore.* *.sock $(PROG)
|
find src -name '*.o' -exec rm '{}' \;
|
||||||
rm -rf pkg src/$(PROG)-* src/v*.tar.gz v*.tar.gz $(PROG)-*.pkg.tar.xz
|
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.xz
|
||||||
|
rm -f vgcore.* *.sock $(PROG)
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -11,20 +11,18 @@
|
|||||||
| Multithreaded JPEG encoding |  Yes |  No |
|
| Multithreaded JPEG encoding |  Yes |  No |
|
||||||
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi |  Yes |  No |
|
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi |  Yes |  No |
|
||||||
| Behavior when the device<br>is disconnected while streaming |  Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected |  Stops the broadcast <sup>1</sup> |
|
| Behavior when the device<br>is disconnected while streaming |  Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected |  Stops the broadcast <sup>1</sup> |
|
||||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal |  Yes |  Partially yes <sup>2</sup> |
|
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal |  Yes |  Partially yes <sup>1</sup> |
|
||||||
| Option to skip frames when streaming<br>static images by HTTP to save traffic |  Yes <sup>2</sup> |  No |
|
| Option to skip frames when streaming<br>static images by HTTP to save traffic |  Yes <sup>2</sup> |  No |
|
||||||
| Streaming via UNIX domain socket |  Yes |  No |
|
| Streaming via UNIX domain socket |  Yes |  No |
|
||||||
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters |  Yes |  No |
|
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters |  Yes |  No |
|
||||||
|
| Option to serve files<br>with a built-in HTTP server |  Yes |  Regular files only |
|
||||||
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP |  No |  Yes |
|
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP |  No |  Yes |
|
||||||
| Option to serve files<br>with a built-in HTTP server, auth settings |  No <sup>3</sup> |  Yes |
|
|
||||||
|
|
||||||
Footnotes:
|
Footnotes:
|
||||||
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
|
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
|
||||||
|
|
||||||
* ```2``` This feature allows to cut down outgoing traffic several-fold when broadcasting HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to broadcast the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
|
* ```2``` This feature allows to cut down outgoing traffic several-fold when broadcasting HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to broadcast the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
|
||||||
|
|
||||||
* ```3``` ...and there'll never be. µStreamer is designed UNIX-way, so if you need a small website with your broadcast, install NGINX.
|
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# TL;DR
|
# TL;DR
|
||||||
If you're going to live-stream from your backyard webcam and need to control it, use mjpg-streamer. If you need a high-quality image with high FPS - µStreamer for the win.
|
If you're going to live-stream from your backyard webcam and need to control it, use mjpg-streamer. If you need a high-quality image with high FPS - µStreamer for the win.
|
||||||
@@ -33,7 +31,7 @@ If you're going to live-stream from your backyard webcam and need to control it,
|
|||||||
# Building
|
# Building
|
||||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libuuid```.
|
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_ENCODER=1``` to ```make```.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
|
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
|
||||||
@@ -42,7 +40,8 @@ $ make
|
|||||||
$ ./ustreamer --help
|
$ ./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```.
|
||||||
|
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Usage
|
# Usage
|
||||||
|
|||||||
13
README.ru.md
13
README.ru.md
@@ -10,21 +10,19 @@
|
|||||||
|----------|---------------|-------------------|
|
|----------|---------------|-------------------|
|
||||||
| Многопоточное кодирование JPEG |  Есть |  Нет |
|
| Многопоточное кодирование JPEG |  Есть |  Нет |
|
||||||
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi |  Есть |  Нет |
|
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi |  Есть |  Нет |
|
||||||
| Поведение при физическом отключении устройства<br>от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова |  Прерывает трансляцию <sup>1</sup> |
|
| Поведение при физическом отключении<br>устройства от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова |  Прерывает трансляцию <sup>1</sup> |
|
||||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) |  Есть |  Условно есть <sup>2</sup> |
|
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) |  Есть |  Условно есть <sup>1</sup> |
|
||||||
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
||||||
| Стрим через UNIX domain socket |  Есть |  Нет |
|
| Стрим через UNIX domain socket |  Есть |  Нет |
|
||||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
||||||
|
| Возможность сервить файлы встроенным<br>HTTP-сервером |  Есть |  Нет каталогов |
|
||||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
||||||
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации |  Нет <sup>3</sup> |  Есть |
|
|
||||||
|
|
||||||
Сносочки:
|
Сносочки:
|
||||||
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
||||||
|
|
||||||
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
|
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
|
||||||
|
|
||||||
* ```3``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
|
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# TL;DR
|
# TL;DR
|
||||||
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
|
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
|
||||||
@@ -33,7 +31,7 @@
|
|||||||
# Сборка
|
# Сборка
|
||||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
|
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
|
||||||
|
|
||||||
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
|
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX_ENCODER=1```.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
|
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
|
||||||
@@ -42,7 +40,8 @@ $ make
|
|||||||
$ ./ustreamer --help
|
$ ./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```.
|
||||||
|
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Использование
|
# Использование
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=0.59
|
pkgver=0.76
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||||
url="https://github.com/pi-kvm/ustreamer"
|
url="https://github.com/pi-kvm/ustreamer"
|
||||||
@@ -12,19 +12,23 @@ arch=(i686 x86_64 armv6h armv7h)
|
|||||||
depends=(libjpeg libevent libutil-linux)
|
depends=(libjpeg libevent libutil-linux)
|
||||||
# optional: raspberrypi-firmware for OMX JPEG encoder
|
# optional: raspberrypi-firmware for OMX JPEG encoder
|
||||||
makedepends=(gcc make)
|
makedepends=(gcc make)
|
||||||
source=("$url/archive/v$pkgver.tar.gz")
|
source=(${pkgname}::"git+https://github.com/pi-kvm/ustreamer#commit=${pkgver}")
|
||||||
md5sums=(SKIP)
|
md5sums=(SKIP)
|
||||||
|
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd $srcdir
|
cd "$srcdir"
|
||||||
rm -rf $pkgname-build
|
rm -rf $pkgname-build
|
||||||
cp -r ustreamer-$pkgver $pkgname-build
|
cp -r $pkgname $pkgname-build
|
||||||
cd $pkgname-build
|
cd $pkgname-build
|
||||||
make CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
|
|
||||||
|
local _options=""
|
||||||
|
[ -d /opt/vc/include ] && _options="$_options WITH_OMX_ENCODER=1"
|
||||||
|
|
||||||
|
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
|
||||||
}
|
}
|
||||||
|
|
||||||
package() {
|
package() {
|
||||||
cd $srcdir/$pkgname-build
|
cd "$srcdir/$pkgname-build"
|
||||||
make DESTDIR="$pkgdir" PREFIX=/usr install
|
make DESTDIR="$pkgdir" PREFIX=/usr install
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ EAPI=7
|
|||||||
|
|
||||||
inherit git-r3
|
inherit git-r3
|
||||||
|
|
||||||
DESCRIPTION="µStreamer - Lightweight and fast MJPG-HTTP streamer"
|
DESCRIPTION="uStreamer - Lightweight and fast MJPG-HTTP streamer"
|
||||||
HOMEPAGE="https://github.com/pi-kvm/ustreamer"
|
HOMEPAGE="https://github.com/pi-kvm/ustreamer"
|
||||||
EGIT_REPO_URI="https://github.com/pi-kvm/ustreamer.git"
|
EGIT_REPO_URI="https://github.com/pi-kvm/ustreamer.git"
|
||||||
|
|
||||||
45
pkg/openwrt/Makefile
Normal file
45
pkg/openwrt/Makefile
Normal 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:=0.76
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
PKG_SOURCE_PROTO:=git
|
||||||
|
PKG_SOURCE_URL:=https://github.com/pi-kvm/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/pi-kvm/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))
|
||||||
20
pkg/openwrt/files/ustreamer.config
Normal file
20
pkg/openwrt/files/ustreamer.config
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
config ustreamer
|
||||||
|
option enabled '0'
|
||||||
|
|
||||||
|
option device '/dev/video0'
|
||||||
|
option device_timeout '5'
|
||||||
|
option input '0'
|
||||||
|
|
||||||
|
option width '640'
|
||||||
|
option height '480'
|
||||||
|
option format 'YUYV'
|
||||||
|
option quality '80'
|
||||||
|
option desired_fps '0'
|
||||||
|
option encoder 'CPU'
|
||||||
|
|
||||||
|
option host '::'
|
||||||
|
option port '8080'
|
||||||
|
option static ''
|
||||||
|
option user ''
|
||||||
|
option password ''
|
||||||
55
pkg/openwrt/files/ustreamer.init
Normal file
55
pkg/openwrt/files/ustreamer.init
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/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 --width='`getcfg width 640`'"
|
||||||
|
options="$options --height='`getcfg height 480`'"
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -22,4 +22,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define VERSION "0.59"
|
#ifndef VERSION
|
||||||
|
# define VERSION "0.76"
|
||||||
|
#endif
|
||||||
|
|||||||
109
src/device.c
109
src/device.c
@@ -27,6 +27,7 @@
|
|||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
@@ -65,6 +66,7 @@ static int _device_open_check_cap(struct device_t *dev);
|
|||||||
static int _device_open_dv_timings(struct device_t *dev);
|
static int _device_open_dv_timings(struct device_t *dev);
|
||||||
static int _device_apply_dv_timings(struct device_t *dev);
|
static int _device_apply_dv_timings(struct device_t *dev);
|
||||||
static int _device_open_format(struct device_t *dev);
|
static int _device_open_format(struct device_t *dev);
|
||||||
|
static void _device_open_hw_fps(struct device_t *dev);
|
||||||
static int _device_open_mmap(struct device_t *dev);
|
static int _device_open_mmap(struct device_t *dev);
|
||||||
static int _device_open_queue_buffers(struct device_t *dev);
|
static int _device_open_queue_buffers(struct device_t *dev);
|
||||||
static void _device_open_alloc_picbufs(struct device_t *dev);
|
static void _device_open_alloc_picbufs(struct device_t *dev);
|
||||||
@@ -83,6 +85,12 @@ struct device_t *device_init() {
|
|||||||
struct controls_t *ctl;
|
struct controls_t *ctl;
|
||||||
struct device_runtime_t *run;
|
struct device_runtime_t *run;
|
||||||
struct device_t *dev;
|
struct device_t *dev;
|
||||||
|
long cores_sysconf;
|
||||||
|
unsigned cores_available;
|
||||||
|
|
||||||
|
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(ctl, 1);
|
A_CALLOC(ctl, 1);
|
||||||
|
|
||||||
@@ -95,8 +103,8 @@ struct device_t *device_init() {
|
|||||||
dev->height = 480;
|
dev->height = 480;
|
||||||
dev->format = V4L2_PIX_FMT_YUYV;
|
dev->format = V4L2_PIX_FMT_YUYV;
|
||||||
dev->standard = V4L2_STD_UNKNOWN;
|
dev->standard = V4L2_STD_UNKNOWN;
|
||||||
dev->n_buffers = max_u(min_u(sysconf(_SC_NPROCESSORS_ONLN), 4), 1) + 1;
|
dev->n_buffers = cores_available + 1;
|
||||||
dev->n_workers = dev->n_buffers;
|
dev->n_workers = min_u(cores_available, dev->n_buffers);
|
||||||
dev->timeout = 1;
|
dev->timeout = 1;
|
||||||
dev->error_delay = 1;
|
dev->error_delay = 1;
|
||||||
dev->ctl = ctl;
|
dev->ctl = ctl;
|
||||||
@@ -144,6 +152,7 @@ int device_open(struct device_t *dev) {
|
|||||||
if (_device_open_format(dev) < 0) {
|
if (_device_open_format(dev) < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
_device_open_hw_fps(dev);
|
||||||
if (_device_open_mmap(dev) < 0) {
|
if (_device_open_mmap(dev) < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
@@ -153,6 +162,8 @@ int device_open(struct device_t *dev) {
|
|||||||
_device_open_alloc_picbufs(dev);
|
_device_open_alloc_picbufs(dev);
|
||||||
_device_apply_controls(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);
|
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@@ -162,6 +173,8 @@ int device_open(struct device_t *dev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void device_close(struct device_t *dev) {
|
void device_close(struct device_t *dev) {
|
||||||
|
dev->run->n_workers = 0;
|
||||||
|
|
||||||
if (dev->run->pictures) {
|
if (dev->run->pictures) {
|
||||||
LOG_DEBUG("Releasing picture buffers ...");
|
LOG_DEBUG("Releasing picture buffers ...");
|
||||||
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index].data; ++index) {
|
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index].data; ++index) {
|
||||||
@@ -177,8 +190,8 @@ void device_close(struct device_t *dev) {
|
|||||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||||
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
|
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
|
||||||
|
|
||||||
if (HW_BUFFER(size) > 0 && HW_BUFFER(data) != MAP_FAILED) {
|
if (HW_BUFFER(allocated) > 0 && HW_BUFFER(data) != MAP_FAILED) {
|
||||||
if (munmap(HW_BUFFER(data), HW_BUFFER(size)) < 0) {
|
if (munmap(HW_BUFFER(data), HW_BUFFER(allocated)) < 0) {
|
||||||
LOG_PERROR("Can't unmap device buffer %u", index);
|
LOG_PERROR("Can't unmap device buffer %u", index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,31 +232,37 @@ int device_switch_capturing(struct device_t *dev, bool enable) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int device_grab_buffer(struct device_t *dev, struct v4l2_buffer *buf_info) {
|
int device_grab_buffer(struct device_t *dev) {
|
||||||
MEMSET_ZERO_PTR(buf_info);
|
struct v4l2_buffer buf_info;
|
||||||
buf_info->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
buf_info->memory = V4L2_MEMORY_MMAP;
|
MEMSET_ZERO(buf_info);
|
||||||
|
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||||
|
buf_info.memory = V4L2_MEMORY_MMAP;
|
||||||
|
|
||||||
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
|
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
|
||||||
if (xioctl(dev->run->fd, VIDIOC_DQBUF, buf_info) < 0) {
|
if (xioctl(dev->run->fd, VIDIOC_DQBUF, &buf_info) < 0) {
|
||||||
LOG_PERROR("Unable to dequeue buffer");
|
LOG_PERROR("Unable to dequeue buffer");
|
||||||
return -1;
|
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) {
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
|
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));
|
||||||
|
return buf_info.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
int device_release_buffer(struct device_t *dev, struct v4l2_buffer *buf_info) {
|
int device_release_buffer(struct device_t *dev, unsigned index) {
|
||||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
|
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
|
||||||
if (xioctl(dev->run->fd, VIDIOC_QBUF, buf_info) < 0) {
|
if (xioctl(dev->run->fd, VIDIOC_QBUF, &dev->run->hw_buffers[index].buf_info) < 0) {
|
||||||
LOG_PERROR("Unable to requeue buffer");
|
LOG_PERROR("Unable to requeue buffer");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
dev->run->hw_buffers[index].used = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +270,7 @@ int device_consume_event(struct device_t *dev) {
|
|||||||
struct v4l2_event event;
|
struct v4l2_event event;
|
||||||
|
|
||||||
LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ...");
|
LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ...");
|
||||||
if (!xioctl(dev->run->fd, VIDIOC_DQEVENT, &event)) {
|
if (xioctl(dev->run->fd, VIDIOC_DQEVENT, &event) == 0) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case V4L2_EVENT_SOURCE_CHANGE:
|
case V4L2_EVENT_SOURCE_CHANGE:
|
||||||
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
|
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
|
||||||
@@ -279,7 +298,7 @@ static int _device_open_check_cap(struct device_t *dev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
||||||
LOG_ERROR("Video capture not supported by our device");
|
LOG_ERROR("Video capture not supported by the device");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +328,7 @@ static int _device_open_check_cap(struct device_t *dev) {
|
|||||||
static int _device_open_dv_timings(struct device_t *dev) {
|
static int _device_open_dv_timings(struct device_t *dev) {
|
||||||
_device_apply_resolution(dev, dev->width, dev->height);
|
_device_apply_resolution(dev, dev->width, dev->height);
|
||||||
if (dev->dv_timings) {
|
if (dev->dv_timings) {
|
||||||
LOG_DEBUG("Using DV-timings");
|
LOG_DEBUG("Using DV timings");
|
||||||
|
|
||||||
if (_device_apply_dv_timings(dev) < 0) {
|
if (_device_apply_dv_timings(dev) < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -416,6 +435,48 @@ static int _device_open_format(struct device_t *dev) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _device_open_hw_fps(struct device_t *dev) {
|
||||||
|
struct v4l2_streamparm setfps;
|
||||||
|
|
||||||
|
MEMSET_ZERO(setfps);
|
||||||
|
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||||
|
|
||||||
|
LOG_DEBUG("Calling ioctl(VIDIOC_G_PARM) ...");
|
||||||
|
if (xioctl(dev->run->fd, VIDIOC_G_PARM, &setfps) < 0) {
|
||||||
|
if (errno == ENOTTY) { // Quiet message for Auvidea B101
|
||||||
|
LOG_INFO("Quierying HW FPS changing is not supported");
|
||||||
|
} else {
|
||||||
|
LOG_PERROR("Unable to query HW FPS changing");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(setfps.parm.capture.capability & V4L2_CAP_TIMEPERFRAME)) {
|
||||||
|
LOG_INFO("Changing HW FPS is not supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# define SETFPS_TPF(_next) setfps.parm.capture.timeperframe._next
|
||||||
|
|
||||||
|
MEMSET_ZERO(setfps);
|
||||||
|
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||||
|
SETFPS_TPF(numerator) = 1;
|
||||||
|
SETFPS_TPF(denominator) = (dev->desired_fps == 0 ? 255 : dev->desired_fps);
|
||||||
|
|
||||||
|
if (xioctl(dev->run->fd, VIDIOC_S_PARM, &setfps) < 0) {
|
||||||
|
LOG_PERROR("Unable to set HW FPS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->desired_fps != SETFPS_TPF(denominator)) {
|
||||||
|
LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, SETFPS_TPF(denominator));
|
||||||
|
} else {
|
||||||
|
LOG_INFO("Using HW FPS: %u", dev->desired_fps);
|
||||||
|
}
|
||||||
|
|
||||||
|
# undef SETFPS_TPF
|
||||||
|
}
|
||||||
|
|
||||||
static int _device_open_mmap(struct device_t *dev) {
|
static int _device_open_mmap(struct device_t *dev) {
|
||||||
struct v4l2_requestbuffers req;
|
struct v4l2_requestbuffers req;
|
||||||
|
|
||||||
@@ -425,7 +486,7 @@ static int _device_open_mmap(struct device_t *dev) {
|
|||||||
req.memory = V4L2_MEMORY_MMAP;
|
req.memory = V4L2_MEMORY_MMAP;
|
||||||
|
|
||||||
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) ...");
|
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) ...");
|
||||||
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req)) {
|
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||||
LOG_PERROR("Device '%s' doesn't support memory mapping", dev->path);
|
LOG_PERROR("Device '%s' doesn't support memory mapping", dev->path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -462,7 +523,7 @@ static int _device_open_mmap(struct device_t *dev) {
|
|||||||
LOG_PERROR("Can't map device buffer %u", dev->run->n_buffers);
|
LOG_PERROR("Can't map device buffer %u", dev->run->n_buffers);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
HW_BUFFER(size) = buf_info.length;
|
HW_BUFFER(allocated) = buf_info.length;
|
||||||
|
|
||||||
# undef HW_BUFFER
|
# undef HW_BUFFER
|
||||||
}
|
}
|
||||||
@@ -491,11 +552,11 @@ static void _device_open_alloc_picbufs(struct device_t *dev) {
|
|||||||
LOG_DEBUG("Allocating picture buffers ...");
|
LOG_DEBUG("Allocating picture buffers ...");
|
||||||
A_CALLOC(dev->run->pictures, dev->run->n_buffers);
|
A_CALLOC(dev->run->pictures, dev->run->n_buffers);
|
||||||
|
|
||||||
dev->run->max_picture_size = ((dev->run->width * dev->run->height) << 1) * 2;
|
dev->run->max_raw_image_size = ((dev->run->width * dev->run->height) << 1) * 2;
|
||||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||||
LOG_DEBUG("Allocating picture buffer %u sized %zu bytes... ", index, dev->run->max_picture_size);
|
LOG_DEBUG("Allocating picture buffer %u sized %zu bytes... ", index, dev->run->max_raw_image_size);
|
||||||
A_CALLOC(dev->run->pictures[index].data, dev->run->max_picture_size);
|
A_CALLOC(dev->run->pictures[index].data, dev->run->max_raw_image_size);
|
||||||
dev->run->pictures[index].allocated = dev->run->max_picture_size;
|
dev->run->pictures[index].allocated = dev->run->max_raw_image_size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
src/device.h
16
src/device.h
@@ -42,13 +42,15 @@
|
|||||||
|
|
||||||
|
|
||||||
struct hw_buffer_t {
|
struct hw_buffer_t {
|
||||||
unsigned char *data;
|
unsigned char *data;
|
||||||
size_t size;
|
size_t used;
|
||||||
|
size_t allocated;
|
||||||
|
struct v4l2_buffer buf_info;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct picture_t {
|
struct picture_t {
|
||||||
unsigned char *data;
|
unsigned char *data;
|
||||||
size_t size;
|
size_t used;
|
||||||
size_t allocated;
|
size_t allocated;
|
||||||
long double grab_time;
|
long double grab_time;
|
||||||
long double encode_begin_time;
|
long double encode_begin_time;
|
||||||
@@ -61,10 +63,10 @@ struct device_runtime_t {
|
|||||||
unsigned height;
|
unsigned height;
|
||||||
unsigned format;
|
unsigned format;
|
||||||
unsigned n_buffers;
|
unsigned n_buffers;
|
||||||
// unsigned n_workers; // FIXME
|
unsigned n_workers;
|
||||||
struct hw_buffer_t *hw_buffers;
|
struct hw_buffer_t *hw_buffers;
|
||||||
struct picture_t *pictures;
|
struct picture_t *pictures;
|
||||||
size_t max_picture_size;
|
size_t max_raw_image_size;
|
||||||
bool capturing;
|
bool capturing;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -118,6 +120,6 @@ int device_open(struct device_t *dev);
|
|||||||
void device_close(struct device_t *dev);
|
void device_close(struct device_t *dev);
|
||||||
|
|
||||||
int device_switch_capturing(struct device_t *dev, bool enable);
|
int device_switch_capturing(struct device_t *dev, bool enable);
|
||||||
int device_grab_buffer(struct device_t *dev, struct v4l2_buffer *buf_info);
|
int device_grab_buffer(struct device_t *dev);
|
||||||
int device_release_buffer(struct device_t *dev, struct v4l2_buffer *buf_info);
|
int device_release_buffer(struct device_t *dev, unsigned index);
|
||||||
int device_consume_event(struct device_t *dev);
|
int device_consume_event(struct device_t *dev);
|
||||||
|
|||||||
137
src/encoder.c
137
src/encoder.c
@@ -20,6 +20,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
@@ -66,48 +67,6 @@ struct encoder_t *encoder_init() {
|
|||||||
return encoder;
|
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) {
|
void encoder_destroy(struct encoder_t *encoder) {
|
||||||
# ifdef WITH_OMX_ENCODER
|
# ifdef WITH_OMX_ENCODER
|
||||||
if (encoder->run->omxs) {
|
if (encoder->run->omxs) {
|
||||||
@@ -142,52 +101,87 @@ const char *encoder_type_to_string(enum encoder_type_t type) {
|
|||||||
return _ENCODER_TYPES[0].name;
|
return _ENCODER_TYPES[0].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
|
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
enum encoder_type_t type = (encoder->run->cpu_forced ? ENCODER_TYPE_CPU : encoder->type);
|
||||||
|
unsigned quality = encoder->quality;
|
||||||
|
bool cpu_forced = false;
|
||||||
|
|
||||||
if (
|
if ((dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG) && type != ENCODER_TYPE_HW) {
|
||||||
(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");
|
LOG_INFO("Switching to HW JPEG encoder because the input format is (M)JPEG");
|
||||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
type = ENCODER_TYPE_HW;
|
||||||
encoder->run->type = ENCODER_TYPE_HW;
|
|
||||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
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");
|
LOG_INFO("Switching to CPU JPEG encoder because the input format is not (M)JPEG");
|
||||||
goto use_fallback;
|
goto use_cpu;
|
||||||
}
|
}
|
||||||
if (hw_encoder_prepare_live(dev, encoder->quality) < 0) {
|
|
||||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
if (hw_encoder_prepare(dev, quality) < 0) {
|
||||||
encoder->run->quality = 0;
|
quality = 0;
|
||||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
|
||||||
LOG_INFO("Using JPEG quality: HW-default");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dev->run->n_workers = 1;
|
||||||
}
|
}
|
||||||
# ifdef WITH_OMX_ENCODER
|
# ifdef WITH_OMX_ENCODER
|
||||||
else if (encoder->run->type == ENCODER_TYPE_OMX) {
|
else if (type == ENCODER_TYPE_OMX) {
|
||||||
|
LOG_DEBUG("Preparing OMX JPEG encoder ...");
|
||||||
|
|
||||||
|
if (dev->run->n_workers > OMX_MAX_ENCODERS) {
|
||||||
|
LOG_INFO("OMX JPEG 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 JPEG encoder, falling back to CPU");
|
||||||
|
goto force_cpu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
||||||
if (omx_encoder_prepare_live(encoder->run->omxs[index], dev, encoder->quality) < 0) {
|
if (omx_encoder_prepare(encoder->run->omxs[index], dev, quality) < 0) {
|
||||||
LOG_ERROR("Can't prepare OMX JPEG encoder, falling back to CPU");
|
LOG_ERROR("Can't prepare OMX JPEG encoder, falling back to CPU");
|
||||||
goto use_fallback;
|
goto force_cpu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# endif
|
# 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);
|
A_MUTEX_LOCK(&encoder->run->mutex);
|
||||||
encoder->run->type = ENCODER_TYPE_CPU;
|
encoder->run->type = type;
|
||||||
encoder->run->quality = encoder->quality;
|
encoder->run->quality = quality;
|
||||||
|
if (cpu_forced) {
|
||||||
|
encoder->run->cpu_forced = true;
|
||||||
|
}
|
||||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma GCC diagnostic ignored "-Wunused-label"
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index) {
|
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index) {
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
@@ -195,14 +189,14 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
|
|||||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
||||||
|
|
||||||
if (encoder->run->type == ENCODER_TYPE_CPU) {
|
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) {
|
} else if (encoder->run->type == ENCODER_TYPE_HW) {
|
||||||
hw_encoder_compress_buffer(dev, buf_index);
|
hw_encoder_compress_buffer(dev, buf_index);
|
||||||
}
|
}
|
||||||
# ifdef WITH_OMX_ENCODER
|
# ifdef WITH_OMX_ENCODER
|
||||||
else if (encoder->run->type == ENCODER_TYPE_OMX) {
|
else if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||||
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
|
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
|
||||||
goto use_fallback;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
@@ -211,11 +205,10 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
|
|||||||
|
|
||||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||||
# pragma GCC diagnostic push
|
# pragma GCC diagnostic push
|
||||||
use_fallback:
|
error:
|
||||||
LOG_INFO("Error while compressing, falling back to CPU");
|
LOG_INFO("Error while compressing buffer, falling back to CPU");
|
||||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
A_MUTEX_LOCK(&encoder->run->mutex);
|
||||||
encoder->run->type = ENCODER_TYPE_CPU;
|
encoder->run->cpu_forced = true;
|
||||||
encoder->run->quality = encoder->quality;
|
|
||||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
||||||
return -1;
|
return -1;
|
||||||
# pragma GCC diagnostic pop
|
# pragma GCC diagnostic pop
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "pthread.h"
|
#include "pthread.h"
|
||||||
|
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
@@ -51,6 +53,7 @@ enum encoder_type_t {
|
|||||||
struct encoder_runtime_t {
|
struct encoder_runtime_t {
|
||||||
enum encoder_type_t type;
|
enum encoder_type_t type;
|
||||||
unsigned quality;
|
unsigned quality;
|
||||||
|
bool cpu_forced;
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
|
|
||||||
# ifdef WITH_OMX_ENCODER
|
# ifdef WITH_OMX_ENCODER
|
||||||
@@ -74,6 +77,4 @@ enum encoder_type_t encoder_parse_type(const char *str);
|
|||||||
const char *encoder_type_to_string(enum encoder_type_t type);
|
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(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);
|
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index);
|
||||||
|
|||||||
@@ -40,14 +40,14 @@
|
|||||||
|
|
||||||
|
|
||||||
struct _jpeg_dest_manager_t {
|
struct _jpeg_dest_manager_t {
|
||||||
struct jpeg_destination_mgr mgr; // Default manager
|
struct jpeg_destination_mgr mgr; // Default manager
|
||||||
JOCTET *buffer; // Start of buffer
|
JOCTET *buffer; // Start of buffer
|
||||||
unsigned char *outbuffer_cursor;
|
struct picture_t *picture;
|
||||||
size_t *written;
|
unsigned char *picture_data_cursor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, size_t *written);
|
static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture);
|
||||||
|
|
||||||
static void _jpeg_write_scanlines_yuyv(
|
static void _jpeg_write_scanlines_yuyv(
|
||||||
struct jpeg_compress_struct *jpeg, const unsigned char *data,
|
struct jpeg_compress_struct *jpeg, const unsigned char *data,
|
||||||
@@ -79,10 +79,7 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
|
|||||||
jpeg.err = jpeg_std_error(&jpeg_error);
|
jpeg.err = jpeg_std_error(&jpeg_error);
|
||||||
jpeg_create_compress(&jpeg);
|
jpeg_create_compress(&jpeg);
|
||||||
|
|
||||||
# define PICTURE(_next) dev->run->pictures[index]._next
|
_jpeg_set_picture(&jpeg, &dev->run->pictures[index]);
|
||||||
|
|
||||||
PICTURE(size) = 0;
|
|
||||||
_jpeg_set_dest_picture(&jpeg, PICTURE(data), &PICTURE(size));
|
|
||||||
|
|
||||||
jpeg.image_width = dev->run->width;
|
jpeg.image_width = dev->run->width;
|
||||||
jpeg.image_height = dev->run->height;
|
jpeg.image_height = dev->run->height;
|
||||||
@@ -108,15 +105,13 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
|
|||||||
|
|
||||||
# undef WRITE_SCANLINES
|
# undef WRITE_SCANLINES
|
||||||
|
|
||||||
// TODO: process jpeg errors:
|
|
||||||
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
|
|
||||||
jpeg_finish_compress(&jpeg);
|
jpeg_finish_compress(&jpeg);
|
||||||
jpeg_destroy_compress(&jpeg);
|
jpeg_destroy_compress(&jpeg);
|
||||||
assert(PICTURE(size) > 0);
|
|
||||||
assert(PICTURE(size) <= dev->run->max_picture_size);
|
assert(dev->run->pictures[index].used > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, size_t *written) {
|
static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture) {
|
||||||
struct _jpeg_dest_manager_t *dest;
|
struct _jpeg_dest_manager_t *dest;
|
||||||
|
|
||||||
if (jpeg->dest == NULL) {
|
if (jpeg->dest == NULL) {
|
||||||
@@ -129,8 +124,10 @@ static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture,
|
|||||||
dest->mgr.init_destination = _jpeg_init_destination;
|
dest->mgr.init_destination = _jpeg_init_destination;
|
||||||
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
|
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
|
||||||
dest->mgr.term_destination = _jpeg_term_destination;
|
dest->mgr.term_destination = _jpeg_term_destination;
|
||||||
dest->outbuffer_cursor = picture;
|
dest->picture = picture;
|
||||||
dest->written = written;
|
dest->picture_data_cursor = picture->data;
|
||||||
|
|
||||||
|
picture->used = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define YUV_R(_y, _, _v) (((_y) + (359 * (_v))) >> 8)
|
#define YUV_R(_y, _, _v) (((_y) + (359 * (_v))) >> 8)
|
||||||
@@ -262,7 +259,7 @@ static void _jpeg_write_scanlines_rgb24(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define JPEG_OUTPUT_BUFFER_SIZE 4096
|
#define JPEG_OUTPUT_BUFFER_SIZE 4096
|
||||||
|
|
||||||
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||||
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
||||||
@@ -280,10 +277,13 @@ static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
|
|||||||
// Called whenever local jpeg buffer fills up
|
// Called whenever local jpeg buffer fills up
|
||||||
|
|
||||||
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
||||||
|
size_t new_used = dest->picture->used + JPEG_OUTPUT_BUFFER_SIZE;
|
||||||
|
|
||||||
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
|
assert(new_used <= dest->picture->allocated);
|
||||||
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
|
|
||||||
*dest->written += JPEG_OUTPUT_BUFFER_SIZE;
|
memcpy(dest->picture_data_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
|
||||||
|
dest->picture_data_cursor += JPEG_OUTPUT_BUFFER_SIZE;
|
||||||
|
dest->picture->used = new_used;
|
||||||
|
|
||||||
dest->mgr.next_output_byte = dest->buffer;
|
dest->mgr.next_output_byte = dest->buffer;
|
||||||
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
|
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
|
||||||
@@ -296,12 +296,15 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
|
|||||||
// Usually needs to flush buffer
|
// Usually needs to flush buffer
|
||||||
|
|
||||||
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
||||||
size_t data_count = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
|
size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
|
||||||
|
size_t new_used = dest->picture->used + final;
|
||||||
|
|
||||||
|
assert(new_used <= dest->picture->allocated);
|
||||||
|
|
||||||
// Write any data remaining in the buffer
|
// Write any data remaining in the buffer
|
||||||
memcpy(dest->outbuffer_cursor, dest->buffer, data_count);
|
memcpy(dest->picture_data_cursor, dest->buffer, final);
|
||||||
dest->outbuffer_cursor += data_count;
|
dest->picture_data_cursor += final;
|
||||||
*dest->written += data_count;
|
dest->picture->used = new_used;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef JPEG_OUTPUT_BUFFER_SIZE
|
#undef JPEG_OUTPUT_BUFFER_SIZE
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ static bool _is_huffman(const unsigned char *data);
|
|||||||
static size_t _memcpy_with_huffman(unsigned char *dest, const unsigned char *src, size_t size);
|
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;
|
struct v4l2_jpegcompression comp;
|
||||||
|
|
||||||
MEMSET_ZERO(comp);
|
MEMSET_ZERO(comp);
|
||||||
@@ -69,8 +69,8 @@ void hw_encoder_compress_buffer(struct device_t *dev, unsigned index) {
|
|||||||
# define PICTURE(_next) dev->run->pictures[index]._next
|
# define PICTURE(_next) dev->run->pictures[index]._next
|
||||||
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
|
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
|
||||||
|
|
||||||
assert(PICTURE(allocated) >= HW_BUFFER(size) + sizeof(HUFFMAN_TABLE));
|
assert(PICTURE(allocated) >= HW_BUFFER(used) + sizeof(HUFFMAN_TABLE));
|
||||||
PICTURE(size) = _memcpy_with_huffman(PICTURE(data), HW_BUFFER(data), HW_BUFFER(size));
|
PICTURE(used) = _memcpy_with_huffman(PICTURE(data), HW_BUFFER(data), HW_BUFFER(used));
|
||||||
|
|
||||||
# undef HW_BUFFER
|
# undef HW_BUFFER
|
||||||
# undef PICTURE
|
# undef PICTURE
|
||||||
|
|||||||
@@ -25,5 +25,5 @@
|
|||||||
#include "../../device.h"
|
#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);
|
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index);
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ struct omx_encoder_t *omx_encoder_init() {
|
|||||||
// - https://github.com/gagle/raspberrypi-openmax-jpeg/blob/master/jpeg.c
|
// - https://github.com/gagle/raspberrypi-openmax-jpeg/blob/master/jpeg.c
|
||||||
// - https://www.raspberrypi.org/forums/viewtopic.php?t=154790
|
// - https://www.raspberrypi.org/forums/viewtopic.php?t=154790
|
||||||
// - https://bitbucket.org/bensch128/omxjpegencode/src/master/jpeg_encoder.cpp
|
// - https://bitbucket.org/bensch128/omxjpegencode/src/master/jpeg_encoder.cpp
|
||||||
|
// - http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html
|
||||||
|
|
||||||
struct omx_encoder_t *omx;
|
struct omx_encoder_t *omx;
|
||||||
OMX_ERRORTYPE error;
|
OMX_ERRORTYPE error;
|
||||||
@@ -152,7 +153,7 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
|
|||||||
free(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) {
|
if (component_set_state(&omx->encoder, OMX_StateIdle) < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -178,7 +179,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
|||||||
# define OUT(_next) omx->output_buffer->_next
|
# define OUT(_next) omx->output_buffer->_next
|
||||||
|
|
||||||
OMX_ERRORTYPE error;
|
OMX_ERRORTYPE error;
|
||||||
size_t slice_size = IN(nAllocLen);
|
size_t slice_size = (IN(nAllocLen) < HW_BUFFER(used) ? IN(nAllocLen) : HW_BUFFER(used));
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
|
|
||||||
if ((error = OMX_FillThisBuffer(omx->encoder, omx->output_buffer)) != OMX_ErrorNone) {
|
if ((error = OMX_FillThisBuffer(omx->encoder, omx->output_buffer)) != OMX_ErrorNone) {
|
||||||
@@ -186,7 +187,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
PICTURE(size) = 0;
|
PICTURE(used) = 0;
|
||||||
omx->output_available = false;
|
omx->output_available = false;
|
||||||
omx->input_required = true;
|
omx->input_required = true;
|
||||||
|
|
||||||
@@ -198,9 +199,9 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
|||||||
if (omx->output_available) {
|
if (omx->output_available) {
|
||||||
omx->output_available = false;
|
omx->output_available = false;
|
||||||
|
|
||||||
assert(PICTURE(size) + OUT(nFilledLen) <= dev->run->max_picture_size);
|
assert(PICTURE(used) + OUT(nFilledLen) <= PICTURE(allocated));
|
||||||
memcpy(PICTURE(data) + PICTURE(size), OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
|
memcpy(PICTURE(data) + PICTURE(used), OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
|
||||||
PICTURE(size) += OUT(nFilledLen);
|
PICTURE(used) += OUT(nFilledLen);
|
||||||
|
|
||||||
if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) {
|
if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) {
|
||||||
OUT(nFlags) = 0;
|
OUT(nFlags) = 0;
|
||||||
@@ -216,7 +217,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
|||||||
if (omx->input_required) {
|
if (omx->input_required) {
|
||||||
omx->input_required = false;
|
omx->input_required = false;
|
||||||
|
|
||||||
if (pos == HW_BUFFER(size)) {
|
if (pos == HW_BUFFER(used)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,8 +227,8 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
|||||||
|
|
||||||
pos += slice_size;
|
pos += slice_size;
|
||||||
|
|
||||||
if (pos + slice_size > HW_BUFFER(size)) {
|
if (pos + slice_size > HW_BUFFER(used)) {
|
||||||
slice_size = HW_BUFFER(size) - pos;
|
slice_size = HW_BUFFER(used) - pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((error = OMX_EmptyThisBuffer(omx->encoder, omx->input_buffer)) != OMX_ErrorNone) {
|
if ((error = OMX_EmptyThisBuffer(omx->encoder, omx->input_buffer)) != OMX_ErrorNone) {
|
||||||
@@ -247,8 +248,6 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int _omx_init_component(struct omx_encoder_t *omx) {
|
static int _omx_init_component(struct omx_encoder_t *omx) {
|
||||||
// http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html
|
|
||||||
|
|
||||||
OMX_ERRORTYPE error;
|
OMX_ERRORTYPE error;
|
||||||
|
|
||||||
OMX_CALLBACKTYPE callbacks;
|
OMX_CALLBACKTYPE callbacks;
|
||||||
@@ -313,7 +312,7 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
|
|||||||
# undef ALIGN_HEIGHT
|
# undef ALIGN_HEIGHT
|
||||||
portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
|
portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
|
||||||
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused;
|
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused;
|
||||||
portdef.nBufferSize = dev->run->max_picture_size;
|
portdef.nBufferSize = dev->run->max_raw_image_size;
|
||||||
|
|
||||||
# define MAP_FORMAT(_v4l2_format, _omx_format) \
|
# define MAP_FORMAT(_v4l2_format, _omx_format) \
|
||||||
case _v4l2_format: { portdef.format.image.eColorFormat = _omx_format; break; }
|
case _v4l2_format: { portdef.format.image.eColorFormat = _omx_format; break; }
|
||||||
|
|||||||
@@ -53,5 +53,5 @@ struct omx_encoder_t {
|
|||||||
struct omx_encoder_t *omx_encoder_init();
|
struct omx_encoder_t *omx_encoder_init();
|
||||||
void omx_encoder_destroy(struct omx_encoder_t *omx);
|
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);
|
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index);
|
||||||
|
|||||||
71
src/http/base64.c
Normal file
71
src/http/base64.c
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# 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 <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "../tools.h"
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const char ENCODING_TABLE[] = {
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||||
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
|
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||||
|
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
|
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||||
|
'4', '5', '6', '7', '8', '9', '+', '/',
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned MOD_TABLE[] = {0, 2, 1};
|
||||||
|
|
||||||
|
|
||||||
|
char *base64_encode(const unsigned char *str) {
|
||||||
|
size_t str_len = strlen((const char *)str);
|
||||||
|
size_t encoded_size = 4 * ((str_len + 2) / 3) + 1; // +1 for '\0'
|
||||||
|
char *encoded;
|
||||||
|
|
||||||
|
A_CALLOC(encoded, encoded_size);
|
||||||
|
|
||||||
|
for (unsigned str_index = 0, encoded_index = 0; str_index < str_len;) {
|
||||||
|
unsigned octet_a = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
|
||||||
|
unsigned octet_b = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
|
||||||
|
unsigned octet_c = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
|
||||||
|
|
||||||
|
unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||||
|
|
||||||
|
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 3 * 6) & 0x3F];
|
||||||
|
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 2 * 6) & 0x3F];
|
||||||
|
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 1 * 6) & 0x3F];
|
||||||
|
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 0 * 6) & 0x3F];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned index = 0; index < MOD_TABLE[str_len % 3]; index++) {
|
||||||
|
encoded[encoded_size - 2 - index] = '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded[encoded_size - 1] = '\0';
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
28
src/http/base64.h
Normal file
28
src/http/base64.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# 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>
|
||||||
|
|
||||||
|
|
||||||
|
char *base64_encode(const unsigned char *str);
|
||||||
174
src/http/blank.c
Normal file
174
src/http/blank.c
Normal 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 <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"
|
||||||
|
|
||||||
|
#include "blank.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct _jpeg_error_manager_t {
|
||||||
|
struct jpeg_error_mgr mgr; // Default manager
|
||||||
|
jmp_buf jmp;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static struct blank_t *_blank_init_internal();
|
||||||
|
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() {
|
||||||
|
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
38
src/http/blank.h
Normal 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);
|
||||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -20,6 +20,9 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
const unsigned BLANK_JPEG_WIDTH = 640;
|
const unsigned BLANK_JPEG_WIDTH = 640;
|
||||||
const unsigned BLANK_JPEG_HEIGHT = 480;
|
const unsigned BLANK_JPEG_HEIGHT = 480;
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../../config.h"
|
||||||
|
|
||||||
|
|
||||||
const char HTML_INDEX_PAGE[] = " \
|
const char HTML_INDEX_PAGE[] = " \
|
||||||
73
src/http/mime.c
Normal file
73
src/http/mime.c
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# 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 <string.h>
|
||||||
|
|
||||||
|
#include <event2/util.h>
|
||||||
|
|
||||||
|
#include "../tools.h"
|
||||||
|
|
||||||
|
#include "mime.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
const char *ext;
|
||||||
|
const char *mime;
|
||||||
|
} _MIME_TYPES[] = {
|
||||||
|
{"html", "text/html"},
|
||||||
|
{"htm", "text/html"},
|
||||||
|
{"css", "text/css"},
|
||||||
|
{"js", "text/javascript"},
|
||||||
|
{"txt", "text/plain"},
|
||||||
|
{"jpg", "image/jpeg"},
|
||||||
|
{"jpeg", "image/jpeg"},
|
||||||
|
{"png", "image/png"},
|
||||||
|
{"gif", "image/gif"},
|
||||||
|
{"ico", "image/x-icon"},
|
||||||
|
{"bmp", "image/bmp"},
|
||||||
|
{"svg", "image/svg+xml"},
|
||||||
|
{"swf", "application/x-shockwave-flash"},
|
||||||
|
{"cab", "application/x-shockwave-flash"},
|
||||||
|
{"jar", "application/java-archive"},
|
||||||
|
{"json", "application/json"},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const char *guess_mime_type(const char *path) {
|
||||||
|
char *dot;
|
||||||
|
char *ext;
|
||||||
|
|
||||||
|
dot = strchr(path, '.');
|
||||||
|
if (dot == NULL || strchr(dot, '/') != NULL) {
|
||||||
|
goto misc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ext = dot + 1;
|
||||||
|
for (unsigned index = 0; index < ARRAY_LEN(_MIME_TYPES); ++index) {
|
||||||
|
if (!evutil_ascii_strcasecmp(ext, _MIME_TYPES[index].ext)) {
|
||||||
|
return _MIME_TYPES[index].mime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
misc:
|
||||||
|
return "application/misc";
|
||||||
|
}
|
||||||
26
src/http/mime.h
Normal file
26
src/http/mime.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
const char *guess_mime_type(const char *str);
|
||||||
183
src/http/path.c
Normal file
183
src/http/path.c
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "../tools.h"
|
||||||
|
|
||||||
|
#include "path.h"
|
||||||
|
|
||||||
|
|
||||||
|
char *simplify_request_path(const char *str) {
|
||||||
|
// Based on Lighttpd sources:
|
||||||
|
// - https://github.com/lighttpd/lighttpd1.4/blob/b31e7840d5403bc640579135b7004793b9ccd6c0/src/buffer.c#L840
|
||||||
|
// - https://github.com/lighttpd/lighttpd1.4/blob/77c01f981725512653c01cde5ca74c11633dfec4/src/t/test_buffer.c
|
||||||
|
|
||||||
|
char ch; // Current character
|
||||||
|
char pre1; // The one before
|
||||||
|
char pre2; // The one before that
|
||||||
|
char *simplified;
|
||||||
|
char *start;
|
||||||
|
char *out;
|
||||||
|
char *slash;
|
||||||
|
|
||||||
|
A_CALLOC(simplified, strlen(str) + 1);
|
||||||
|
|
||||||
|
if (str[0] == '\0') {
|
||||||
|
simplified[0] = '\0';
|
||||||
|
return simplified;
|
||||||
|
}
|
||||||
|
|
||||||
|
start = simplified;
|
||||||
|
out = simplified;
|
||||||
|
slash = simplified;
|
||||||
|
|
||||||
|
// Skip leading spaces
|
||||||
|
for (; *str == ' '; ++str);
|
||||||
|
|
||||||
|
if (*str == '.') {
|
||||||
|
if (str[1] == '/' || str[1] == '\0') {
|
||||||
|
++str;
|
||||||
|
} else if (str[1] == '.' && (str[2] == '/' || str[2] == '\0')) {
|
||||||
|
str += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pre1 = '\0';
|
||||||
|
ch = *(str++);
|
||||||
|
|
||||||
|
while (ch != '\0') {
|
||||||
|
pre2 = pre1;
|
||||||
|
pre1 = ch;
|
||||||
|
|
||||||
|
// Possibly: out == str - need to read first
|
||||||
|
ch = *str;
|
||||||
|
*out = pre1;
|
||||||
|
|
||||||
|
out++;
|
||||||
|
str++;
|
||||||
|
// (out <= str) still true; also now (slash < out)
|
||||||
|
|
||||||
|
if (ch == '/' || ch == '\0') {
|
||||||
|
size_t toklen = out - slash;
|
||||||
|
|
||||||
|
if (toklen == 3 && pre2 == '.' && pre1 == '.' && *slash == '/') {
|
||||||
|
// "/../" or ("/.." at end of string)
|
||||||
|
out = slash;
|
||||||
|
// If there is something before "/..", there is at least one
|
||||||
|
// component, which needs to be removed
|
||||||
|
if (out > start) {
|
||||||
|
--out;
|
||||||
|
for (; out > start && *out != '/'; --out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't kill trailing '/' at end of path
|
||||||
|
if (ch == '\0') {
|
||||||
|
++out;
|
||||||
|
}
|
||||||
|
// slash < out before, so out_new <= slash + 1 <= out_before <= str
|
||||||
|
} else if (toklen == 1 || (pre2 == '/' && pre1 == '.')) {
|
||||||
|
// "//" or "/./" or (("/" or "/.") at end of string)
|
||||||
|
out = slash;
|
||||||
|
// Don't kill trailing '/' at end of path
|
||||||
|
if (ch == '\0') {
|
||||||
|
++out;
|
||||||
|
}
|
||||||
|
// Slash < out before, so out_new <= slash + 1 <= out_before <= str
|
||||||
|
}
|
||||||
|
|
||||||
|
slash = out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = '\0';
|
||||||
|
return simplified;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
int test_simplify_request_path(const char *sample, const char *expected) {
|
||||||
|
char *result = simplify_request_path(sample);
|
||||||
|
int retval = -!!strcmp(result, expected);
|
||||||
|
|
||||||
|
printf("Testing '%s' -> '%s' ... ", sample, expected);
|
||||||
|
if (retval == 0) {
|
||||||
|
printf("ok\n");
|
||||||
|
} else {
|
||||||
|
printf("FAILED; got '%s'\n", result);
|
||||||
|
}
|
||||||
|
free(result);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
int retval = 0;
|
||||||
|
|
||||||
|
# define TEST_SIMPLIFY_REQUEST_PATH(_sample, _expected) { \
|
||||||
|
retval += test_simplify_request_path(_sample, _expected); \
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("", "");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH(" ", "");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("/", "/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("//", "/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("abc", "abc");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("abc//", "abc/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz", "abc/xyz");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("abc/.//xyz", "abc/xyz");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz", "/xyz");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz", "/abc/xyz");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("/abc//./xyz", "/abc/xyz");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz", "/xyz");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz/.", "/xyz/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz/.", "/xyz/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz/..", "abc/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz/..", "/abc/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH(".", "");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("..", "");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("...", "...");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("....", "....");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH(".../", ".../");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("./xyz/..", "/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH(".//xyz/..", "/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("/./xyz/..", "/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH(".././xyz/..", "/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("../../../etc/passwd", "/etc/passwd");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH("/../../../etc/passwd", "/etc/passwd");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH(" ../../../etc/passwd", "/etc/passwd");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH(" /../../../etc/passwd", "/etc/passwd");
|
||||||
|
TEST_SIMPLIFY_REQUEST_PATH(" /foo/bar/../../../etc/passwd", "/etc/passwd");
|
||||||
|
|
||||||
|
# undef TEST_SIMPLIFY_REQUEST_PATH
|
||||||
|
|
||||||
|
if (retval < 0) {
|
||||||
|
printf("===== TEST FAILED =====\n");
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
26
src/http/path.h
Normal file
26
src/http/path.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
char *simplify_request_path(const char *str);
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -46,20 +47,26 @@
|
|||||||
# error Required libevent-pthreads support
|
# error Required libevent-pthreads support
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "tools.h"
|
#include "../tools.h"
|
||||||
#include "logging.h"
|
#include "../logging.h"
|
||||||
#include "encoder.h"
|
#include "../encoder.h"
|
||||||
#include "stream.h"
|
#include "../stream.h"
|
||||||
#include "http.h"
|
|
||||||
|
#include "blank.h"
|
||||||
|
#include "base64.h"
|
||||||
|
#include "mime.h"
|
||||||
|
#include "static.h"
|
||||||
|
#include "server.h"
|
||||||
|
|
||||||
#include "data/index_html.h"
|
#include "data/index_html.h"
|
||||||
#include "data/blank_jpeg.h"
|
|
||||||
|
|
||||||
|
|
||||||
static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
|
static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
|
||||||
static char *_http_get_param_uri(struct evkeyvalq *params, const char *key);
|
static char *_http_get_param_uri(struct evkeyvalq *params, const char *key);
|
||||||
|
static int _http_preprocess_request(struct evhttp_request *request, struct http_server_t *server);
|
||||||
|
|
||||||
static void _http_callback_root(struct evhttp_request *request, void *arg);
|
static void _http_callback_root(struct evhttp_request *request, void *v_server);
|
||||||
|
static void _http_callback_static(struct evhttp_request *request, void *v_server);
|
||||||
static void _http_callback_state(struct evhttp_request *request, void *v_server);
|
static void _http_callback_state(struct evhttp_request *request, void *v_server);
|
||||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server);
|
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server);
|
||||||
|
|
||||||
@@ -89,21 +96,17 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
|||||||
A_CALLOC(server, 1);
|
A_CALLOC(server, 1);
|
||||||
server->host = "127.0.0.1";
|
server->host = "127.0.0.1";
|
||||||
server->port = 8080;
|
server->port = 8080;
|
||||||
|
server->unix_path = "";
|
||||||
|
server->user = "";
|
||||||
|
server->passwd = "";
|
||||||
|
server->static_path = "";
|
||||||
server->timeout = 10;
|
server->timeout = 10;
|
||||||
server->run = run;
|
server->run = run;
|
||||||
|
|
||||||
_expose_blank_picture(server);
|
|
||||||
|
|
||||||
assert(!evthread_use_pthreads());
|
assert(!evthread_use_pthreads());
|
||||||
assert((run->base = event_base_new()));
|
assert((run->base = event_base_new()));
|
||||||
assert((run->http = evhttp_new(run->base)));
|
assert((run->http = evhttp_new(run->base)));
|
||||||
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD);
|
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD);
|
||||||
|
|
||||||
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, NULL));
|
|
||||||
assert(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void *)server));
|
|
||||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)server));
|
|
||||||
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
|
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +121,10 @@ void http_server_destroy(struct http_server_t *server) {
|
|||||||
close(server->run->unix_fd);
|
close(server->run->unix_fd);
|
||||||
}
|
}
|
||||||
event_base_free(server->run->base);
|
event_base_free(server->run->base);
|
||||||
|
|
||||||
|
# if LIBEVENT_VERSION_NUMBER >= 0x02010100
|
||||||
libevent_global_shutdown();
|
libevent_global_shutdown();
|
||||||
|
# endif
|
||||||
|
|
||||||
for (struct stream_client_t *client = server->run->stream_clients; client != NULL;) {
|
for (struct stream_client_t *client = server->run->stream_clients; client != NULL;) {
|
||||||
struct stream_client_t *next = client->next;
|
struct stream_client_t *next = client->next;
|
||||||
@@ -128,6 +134,14 @@ void http_server_destroy(struct http_server_t *server) {
|
|||||||
client = next;
|
client = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (server->run->auth_token) {
|
||||||
|
free(server->run->auth_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server->run->blank) {
|
||||||
|
blank_destroy(server->run->blank);
|
||||||
|
}
|
||||||
|
|
||||||
free(server->run->exposed->picture.data);
|
free(server->run->exposed->picture.data);
|
||||||
free(server->run->exposed);
|
free(server->run->exposed);
|
||||||
free(server->run);
|
free(server->run);
|
||||||
@@ -135,18 +149,34 @@ void http_server_destroy(struct http_server_t *server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int http_server_listen(struct http_server_t *server) {
|
int http_server_listen(struct http_server_t *server) {
|
||||||
struct timeval refresh_interval;
|
{
|
||||||
|
if (server->static_path[0] != '\0') {
|
||||||
refresh_interval.tv_sec = 0;
|
evhttp_set_gencb(server->run->http, _http_callback_static, (void *)server);
|
||||||
if (server->run->stream->dev->desired_fps > 0) {
|
} else {
|
||||||
refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->desired_fps * 2);
|
assert(!evhttp_set_cb(server->run->http, "/", _http_callback_root, (void *)server));
|
||||||
} else {
|
}
|
||||||
refresh_interval.tv_usec = 16000; // ~60fps
|
assert(!evhttp_set_cb(server->run->http, "/state", _http_callback_state, (void *)server));
|
||||||
|
assert(!evhttp_set_cb(server->run->http, "/snapshot", _http_callback_snapshot, (void *)server));
|
||||||
|
assert(!evhttp_set_cb(server->run->http, "/stream", _http_callback_stream, (void *)server));
|
||||||
}
|
}
|
||||||
assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, 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);
|
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;
|
||||||
|
|
||||||
|
refresh_interval.tv_sec = 0;
|
||||||
|
if (server->run->stream->dev->desired_fps > 0) {
|
||||||
|
refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->desired_fps * 2);
|
||||||
|
} else {
|
||||||
|
refresh_interval.tv_usec = 16000; // ~60fps
|
||||||
|
}
|
||||||
|
|
||||||
|
assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
|
||||||
|
assert(!event_add(server->run->refresh, &refresh_interval));
|
||||||
|
}
|
||||||
|
|
||||||
if (server->slowdown) {
|
if (server->slowdown) {
|
||||||
stream_switch_slowdown(server->run->stream, true);
|
stream_switch_slowdown(server->run->stream, true);
|
||||||
@@ -154,21 +184,43 @@ int http_server_listen(struct http_server_t *server) {
|
|||||||
|
|
||||||
evhttp_set_timeout(server->run->http, server->timeout);
|
evhttp_set_timeout(server->run->http, server->timeout);
|
||||||
|
|
||||||
if (server->unix_path) {
|
if (server->user[0] != '\0') {
|
||||||
|
char *raw_token;
|
||||||
|
char *encoded_token;
|
||||||
|
|
||||||
|
A_CALLOC(raw_token, strlen(server->user) + strlen(server->passwd) + 2);
|
||||||
|
sprintf(raw_token, "%s:%s", server->user, server->passwd);
|
||||||
|
encoded_token = base64_encode((unsigned char *)raw_token);
|
||||||
|
free(raw_token);
|
||||||
|
|
||||||
|
A_CALLOC(server->run->auth_token, strlen(encoded_token) + 16);
|
||||||
|
sprintf(server->run->auth_token, "Basic %s", encoded_token);
|
||||||
|
free(encoded_token);
|
||||||
|
|
||||||
|
LOG_INFO("Using HTTP basic auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server->unix_path[0] != '\0') {
|
||||||
struct sockaddr_un unix_addr;
|
struct sockaddr_un unix_addr;
|
||||||
int unix_fd_flags;
|
|
||||||
|
|
||||||
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
||||||
|
|
||||||
assert((server->run->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0)));
|
# define MAX_SUN_PATH (sizeof(unix_addr.sun_path) - 1)
|
||||||
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);
|
|
||||||
|
|
||||||
strncpy(unix_addr.sun_path, server->unix_path, 107);
|
if (strlen(server->unix_path) > MAX_SUN_PATH) {
|
||||||
unix_addr.sun_path[107] = '\0';
|
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;
|
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 (server->unix_rm && unlink(server->unix_path) < 0) {
|
||||||
if (errno != ENOENT) {
|
if (errno != ENOENT) {
|
||||||
LOG_PERROR("Can't remove old UNIX socket '%s'", server->unix_path);
|
LOG_PERROR("Can't remove old UNIX socket '%s'", server->unix_path);
|
||||||
@@ -221,7 +273,11 @@ static bool _http_get_param_true(struct evkeyvalq *params, const char *key) {
|
|||||||
const char *value_str;
|
const char *value_str;
|
||||||
|
|
||||||
if ((value_str = evhttp_find_header(params, key)) != NULL) {
|
if ((value_str = evhttp_find_header(params, key)) != NULL) {
|
||||||
if (!strcasecmp(value_str, "true") || !strcasecmp(value_str, "yes") || value_str[0] == '1') {
|
if (
|
||||||
|
value_str[0] == '1'
|
||||||
|
|| !evutil_ascii_strcasecmp(value_str, "true")
|
||||||
|
|| !evutil_ascii_strcasecmp(value_str, "yes")
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,17 +296,36 @@ static char *_http_get_param_uri(struct evkeyvalq *params, const char *key) {
|
|||||||
#define ADD_HEADER(_key, _value) \
|
#define ADD_HEADER(_key, _value) \
|
||||||
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
||||||
|
|
||||||
#define PROCESS_HEAD_REQUEST { \
|
static int _http_preprocess_request(struct evhttp_request *request, struct http_server_t *server) {
|
||||||
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \
|
if (server->run->auth_token) {
|
||||||
evhttp_send_reply(request, HTTP_OK, "OK", NULL); \
|
const char *token = evhttp_find_header(evhttp_request_get_input_headers(request), "Authorization");
|
||||||
|
|
||||||
|
if (token == NULL || strcmp(token, server->run->auth_token) != 0) {
|
||||||
|
ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\"");
|
||||||
|
evhttp_send_reply(request, 401, "Unauthorized", NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \
|
||||||
|
evhttp_send_reply(request, HTTP_OK, "OK", NULL); \
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PREPROCESS_REQUEST { \
|
||||||
|
if (_http_preprocess_request(request, server) < 0) { \
|
||||||
return; \
|
return; \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg) {
|
static void _http_callback_root(struct evhttp_request *request, void *v_server) {
|
||||||
|
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||||
struct evbuffer *buf;
|
struct evbuffer *buf;
|
||||||
|
|
||||||
PROCESS_HEAD_REQUEST;
|
PREPROCESS_REQUEST;
|
||||||
|
|
||||||
assert((buf = evbuffer_new()));
|
assert((buf = evbuffer_new()));
|
||||||
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
|
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
|
||||||
@@ -259,13 +334,86 @@ static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg
|
|||||||
evbuffer_free(buf);
|
evbuffer_free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
|
||||||
|
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||||
|
struct evbuffer *buf = NULL;
|
||||||
|
struct evhttp_uri *uri = NULL;
|
||||||
|
char *uri_path;
|
||||||
|
char *decoded_path = NULL;
|
||||||
|
char *static_path = NULL;
|
||||||
|
int fd = -1;
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
PREPROCESS_REQUEST;
|
||||||
|
|
||||||
|
if ((uri = evhttp_uri_parse(evhttp_request_get_uri(request))) == NULL) {
|
||||||
|
goto bad_request;
|
||||||
|
}
|
||||||
|
if ((uri_path = (char *)evhttp_uri_get_path(uri)) == NULL) {
|
||||||
|
uri_path = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((decoded_path = evhttp_uridecode(uri_path, 0, NULL)) == NULL) {
|
||||||
|
goto bad_request;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert((buf = evbuffer_new()));
|
||||||
|
|
||||||
|
if ((static_path = find_static_file_path(server->static_path, decoded_path)) == NULL) {
|
||||||
|
goto not_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((fd = open(static_path, O_RDONLY)) < 0) {
|
||||||
|
LOG_PERROR("HTTP: Can't open found static file %s", static_path);
|
||||||
|
goto not_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fstat(fd, &st) < 0) {
|
||||||
|
LOG_PERROR("HTTP: Can't stat() found static file %s", static_path);
|
||||||
|
goto not_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evbuffer_add_file(buf, fd, 0, st.st_size) < 0) {
|
||||||
|
LOG_ERROR("HTTP: Can't serve static file %s", static_path);
|
||||||
|
goto not_found;
|
||||||
|
}
|
||||||
|
ADD_HEADER("Content-Type", guess_mime_type(static_path));
|
||||||
|
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
bad_request:
|
||||||
|
evhttp_send_error(request, HTTP_BADREQUEST, NULL);
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
not_found:
|
||||||
|
evhttp_send_error(request, HTTP_NOTFOUND, NULL);
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (fd >= 0) {
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
if (static_path) {
|
||||||
|
free(static_path);
|
||||||
|
}
|
||||||
|
if (buf) {
|
||||||
|
evbuffer_free(buf);
|
||||||
|
}
|
||||||
|
if (decoded_path) {
|
||||||
|
free(decoded_path);
|
||||||
|
}
|
||||||
|
if (uri) {
|
||||||
|
evhttp_uri_free(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
||||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||||
struct evbuffer *buf;
|
struct evbuffer *buf;
|
||||||
enum encoder_type_t encoder_run_type;
|
enum encoder_type_t encoder_run_type;
|
||||||
unsigned encoder_run_quality;
|
unsigned encoder_run_quality;
|
||||||
|
|
||||||
PROCESS_HEAD_REQUEST;
|
PREPROCESS_REQUEST;
|
||||||
|
|
||||||
# define ENCODER(_next) server->run->stream->encoder->_next
|
# define ENCODER(_next) server->run->stream->encoder->_next
|
||||||
|
|
||||||
@@ -319,12 +467,12 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
|||||||
struct evbuffer *buf;
|
struct evbuffer *buf;
|
||||||
char header_buf[64];
|
char header_buf[64];
|
||||||
|
|
||||||
PROCESS_HEAD_REQUEST;
|
PREPROCESS_REQUEST;
|
||||||
|
|
||||||
# define EXPOSED(_next) server->run->exposed->_next
|
# define EXPOSED(_next) server->run->exposed->_next
|
||||||
|
|
||||||
assert((buf = evbuffer_new()));
|
assert((buf = evbuffer_new()));
|
||||||
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture.data), EXPOSED(picture.size)));
|
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture.data), EXPOSED(picture.used)));
|
||||||
|
|
||||||
ADD_HEADER("Access-Control-Allow-Origin:", "*");
|
ADD_HEADER("Access-Control-Allow-Origin:", "*");
|
||||||
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
|
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
|
||||||
@@ -384,10 +532,10 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
unsigned short client_port;
|
unsigned short client_port;
|
||||||
uuid_t uuid;
|
uuid_t uuid;
|
||||||
|
|
||||||
PROCESS_HEAD_REQUEST;
|
PREPROCESS_REQUEST;
|
||||||
|
|
||||||
conn = evhttp_request_get_connection(request);
|
conn = evhttp_request_get_connection(request);
|
||||||
if (conn != NULL) {
|
if (conn) {
|
||||||
A_CALLOC(client, 1);
|
A_CALLOC(client, 1);
|
||||||
client->server = server;
|
client->server = server;
|
||||||
client->request = request;
|
client->request = request;
|
||||||
@@ -431,7 +579,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef PROCESS_HEAD_REQUEST
|
#undef PREPROCESS_REQUEST
|
||||||
|
|
||||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
||||||
# define BOUNDARY "boundarydonotcross"
|
# define BOUNDARY "boundarydonotcross"
|
||||||
@@ -505,8 +653,9 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
"Content-Length: %zu" RN
|
"Content-Length: %zu" RN
|
||||||
"X-Timestamp: %.06Lf" RN
|
"X-Timestamp: %.06Lf" RN
|
||||||
"%s",
|
"%s",
|
||||||
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data)),
|
EXPOSED(picture.used),
|
||||||
get_now_real(), (client->extra_headers ? "" : RN)
|
get_now_real(),
|
||||||
|
(client->extra_headers ? "" : RN)
|
||||||
));
|
));
|
||||||
if (client->extra_headers) {
|
if (client->extra_headers) {
|
||||||
assert(evbuffer_add_printf(buf,
|
assert(evbuffer_add_printf(buf,
|
||||||
@@ -539,10 +688,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!evbuffer_add(buf,
|
assert(!evbuffer_add(buf, (void *)EXPOSED(picture.data), EXPOSED(picture.used)));
|
||||||
(void *)EXPOSED(picture.data),
|
|
||||||
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data))
|
|
||||||
));
|
|
||||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||||
|
|
||||||
if (client->advance_headers) {
|
if (client->advance_headers) {
|
||||||
@@ -575,12 +721,12 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn = evhttp_request_get_connection(client->request);
|
conn = evhttp_request_get_connection(client->request);
|
||||||
if (conn != NULL) {
|
if (conn) {
|
||||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||||
}
|
}
|
||||||
LOG_INFO("HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
|
LOG_INFO("HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
|
||||||
client_addr, client_port, RUN(stream_clients_count));
|
client_addr, client_port, RUN(stream_clients_count));
|
||||||
if (conn != NULL) {
|
if (conn) {
|
||||||
evhttp_connection_free(conn);
|
evhttp_connection_free(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,7 +754,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) {
|
for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
|
||||||
conn = evhttp_request_get_connection(client->request);
|
conn = evhttp_request_get_connection(client->request);
|
||||||
if (conn != NULL) {
|
if (conn) {
|
||||||
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
||||||
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
|
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
|
||||||
// и нужно послать два фрейма, чтобы серия была вовремя завершена.
|
// и нужно послать два фрейма, чтобы серия была вовремя завершена.
|
||||||
@@ -660,7 +806,7 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
|
|||||||
if (atomic_load(&server->run->stream->updated)) {
|
if (atomic_load(&server->run->stream->updated)) {
|
||||||
LOG_DEBUG("Refreshing HTTP exposed ...");
|
LOG_DEBUG("Refreshing HTTP exposed ...");
|
||||||
A_MUTEX_LOCK(&server->run->stream->mutex);
|
A_MUTEX_LOCK(&server->run->stream->mutex);
|
||||||
if (server->run->stream->picture.size > 0) { // If online
|
if (server->run->stream->picture.used > 0) { // If online
|
||||||
picture_updated = _expose_new_picture(server);
|
picture_updated = _expose_new_picture(server);
|
||||||
UNLOCK_STREAM;
|
UNLOCK_STREAM;
|
||||||
} else {
|
} else {
|
||||||
@@ -683,19 +829,18 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
|||||||
# define STREAM(_next) server->run->stream->_next
|
# define STREAM(_next) server->run->stream->_next
|
||||||
# define EXPOSED(_next) server->run->exposed->_next
|
# define EXPOSED(_next) server->run->exposed->_next
|
||||||
|
|
||||||
assert(STREAM(picture.size) > 0);
|
assert(STREAM(picture.used) > 0);
|
||||||
EXPOSED(captured_fps) = STREAM(captured_fps);
|
EXPOSED(captured_fps) = STREAM(captured_fps);
|
||||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||||
|
|
||||||
# define MEM_STREAM_TO_EXPOSED \
|
# define MEM_STREAM_TO_EXPOSED \
|
||||||
EXPOSED(picture.data), STREAM(picture.data), \
|
EXPOSED(picture.data), STREAM(picture.data), STREAM(picture.used)
|
||||||
STREAM(picture.size) * sizeof(*STREAM(picture.data))
|
|
||||||
|
|
||||||
if (server->drop_same_frames) {
|
if (server->drop_same_frames) {
|
||||||
if (
|
if (
|
||||||
EXPOSED(online)
|
EXPOSED(online)
|
||||||
&& EXPOSED(dropped) < server->drop_same_frames
|
&& EXPOSED(dropped) < server->drop_same_frames
|
||||||
&& EXPOSED(picture.size) == STREAM(picture.size)
|
&& EXPOSED(picture.used) == STREAM(picture.used)
|
||||||
&& !memcmp(MEM_STREAM_TO_EXPOSED)
|
&& !memcmp(MEM_STREAM_TO_EXPOSED)
|
||||||
) {
|
) {
|
||||||
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
||||||
@@ -720,7 +865,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
|||||||
|
|
||||||
# undef MEM_STREAM_TO_EXPOSED
|
# undef MEM_STREAM_TO_EXPOSED
|
||||||
|
|
||||||
EXPOSED(picture.size) = STREAM(picture.size);
|
EXPOSED(picture.used) = STREAM(picture.used);
|
||||||
|
|
||||||
EXPOSED(picture.grab_time) = STREAM(picture.grab_time);
|
EXPOSED(picture.grab_time) = STREAM(picture.grab_time);
|
||||||
EXPOSED(picture.encode_begin_time) = STREAM(picture.encode_begin_time);
|
EXPOSED(picture.encode_begin_time) = STREAM(picture.encode_begin_time);
|
||||||
@@ -742,28 +887,28 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool _expose_blank_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 EXPOSED(_next) server->run->exposed->_next
|
||||||
# define BLANK_JPEG_LEN ARRAY_LEN(BLANK_JPEG_DATA)
|
|
||||||
|
|
||||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||||
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
||||||
|
|
||||||
if (EXPOSED(online) || EXPOSED(picture.size) == 0) {
|
if (EXPOSED(online) || EXPOSED(picture.used) == 0) {
|
||||||
if (EXPOSED(picture.allocated) < BLANK_JPEG_LEN) {
|
if (EXPOSED(picture.allocated) < BLANK(picture.used)) {
|
||||||
A_REALLOC(EXPOSED(picture.data), BLANK_JPEG_LEN);
|
A_REALLOC(EXPOSED(picture.data), BLANK(picture.used));
|
||||||
EXPOSED(picture.allocated) = BLANK_JPEG_LEN;
|
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.size) = BLANK_JPEG_LEN;
|
EXPOSED(picture.used) = BLANK(picture.used);
|
||||||
|
|
||||||
EXPOSED(picture.grab_time) = 0;
|
EXPOSED(picture.grab_time) = 0;
|
||||||
EXPOSED(picture.encode_begin_time) = 0;
|
EXPOSED(picture.encode_begin_time) = 0;
|
||||||
EXPOSED(picture.encode_end_time) = 0;
|
EXPOSED(picture.encode_end_time) = 0;
|
||||||
|
|
||||||
EXPOSED(width) = BLANK_JPEG_WIDTH;
|
EXPOSED(width) = BLANK(width);
|
||||||
EXPOSED(height) = BLANK_JPEG_HEIGHT;
|
EXPOSED(height) = BLANK(height);
|
||||||
EXPOSED(captured_fps) = 0;
|
EXPOSED(captured_fps) = 0;
|
||||||
EXPOSED(online) = false;
|
EXPOSED(online) = false;
|
||||||
goto updated;
|
goto updated;
|
||||||
@@ -781,6 +926,6 @@ static bool _expose_blank_picture(struct http_server_t *server) {
|
|||||||
EXPOSED(expose_end_time) = get_now_monotonic();
|
EXPOSED(expose_end_time) = get_now_monotonic();
|
||||||
return true; // Updated
|
return true; // Updated
|
||||||
|
|
||||||
# undef BLANK_JPEG_LEN
|
|
||||||
# undef EXPOSED
|
# undef EXPOSED
|
||||||
|
# undef BLANK
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,8 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -28,8 +30,10 @@
|
|||||||
#include <event2/http.h>
|
#include <event2/http.h>
|
||||||
#include <event2/util.h>
|
#include <event2/util.h>
|
||||||
|
|
||||||
#include "tools.h"
|
#include "../tools.h"
|
||||||
#include "stream.h"
|
#include "../stream.h"
|
||||||
|
|
||||||
|
#include "blank.h"
|
||||||
|
|
||||||
|
|
||||||
struct stream_client_t {
|
struct stream_client_t {
|
||||||
@@ -70,11 +74,13 @@ struct http_server_runtime_t {
|
|||||||
struct event_base *base;
|
struct event_base *base;
|
||||||
struct evhttp *http;
|
struct evhttp *http;
|
||||||
evutil_socket_t unix_fd;
|
evutil_socket_t unix_fd;
|
||||||
|
char *auth_token;
|
||||||
struct event *refresh;
|
struct event *refresh;
|
||||||
struct stream_t *stream;
|
struct stream_t *stream;
|
||||||
struct exposed_t *exposed;
|
struct exposed_t *exposed;
|
||||||
struct stream_client_t *stream_clients;
|
struct stream_client_t *stream_clients;
|
||||||
unsigned stream_clients_count;
|
unsigned stream_clients_count;
|
||||||
|
struct blank_t *blank;
|
||||||
unsigned drop_same_frames_blank;
|
unsigned drop_same_frames_blank;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,11 +90,17 @@ struct http_server_t {
|
|||||||
char *unix_path;
|
char *unix_path;
|
||||||
bool unix_rm;
|
bool unix_rm;
|
||||||
mode_t unix_mode;
|
mode_t unix_mode;
|
||||||
|
unsigned timeout;
|
||||||
|
|
||||||
|
char *user;
|
||||||
|
char *passwd;
|
||||||
|
char *static_path;
|
||||||
|
|
||||||
|
char *blank_path;
|
||||||
unsigned drop_same_frames;
|
unsigned drop_same_frames;
|
||||||
bool slowdown;
|
bool slowdown;
|
||||||
unsigned fake_width;
|
unsigned fake_width;
|
||||||
unsigned fake_height;
|
unsigned fake_height;
|
||||||
unsigned timeout;
|
|
||||||
|
|
||||||
struct http_server_runtime_t *run;
|
struct http_server_runtime_t *run;
|
||||||
};
|
};
|
||||||
88
src/http/static.c
Normal file
88
src/http/static.c
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../tools.h"
|
||||||
|
#include "../logging.h"
|
||||||
|
|
||||||
|
#include "path.h"
|
||||||
|
#include "static.h"
|
||||||
|
|
||||||
|
|
||||||
|
char *find_static_file_path(const char *root_path, const char *request_path) {
|
||||||
|
char *simplified_path;
|
||||||
|
char *path = NULL;
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
simplified_path = simplify_request_path(request_path);
|
||||||
|
if (simplified_path[0] == '\0') {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 32);
|
||||||
|
sprintf(path, "%s/%s", root_path, simplified_path);
|
||||||
|
|
||||||
|
# define LOAD_STAT { \
|
||||||
|
if (lstat(path, &st) < 0) { \
|
||||||
|
/* LOG_PERROR("Can't stat() file %s", path); */ \
|
||||||
|
goto error; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
LOAD_STAT;
|
||||||
|
if (S_ISDIR(st.st_mode)) {
|
||||||
|
strcat(path, "/index.html");
|
||||||
|
LOAD_STAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
# undef LOAD_STAT
|
||||||
|
|
||||||
|
if (!S_ISREG(st.st_mode)) {
|
||||||
|
// LOG_ERROR("Not a regulary file: %s", path);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (access(path, R_OK) < 0) {
|
||||||
|
// LOG_PERROR("Can't access() R_OK file %s", path);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto ok;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (path) {
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
|
path = NULL;
|
||||||
|
|
||||||
|
ok:
|
||||||
|
free(simplified_path);
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
26
src/http/static.h
Normal file
26
src/http/static.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
char *find_static_file_path(const char *root_path, const char *request_path);
|
||||||
@@ -25,16 +25,12 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
|
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -72,7 +68,7 @@ pthread_mutex_t log_mutex;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define LOG_PRINTF_NOLOCK(_label, _msg, ...) { \
|
#define LOG_PRINTF_NOLOCK(_label, _msg, ...) { \
|
||||||
printf("-- " _label " [%.03Lf tid=%ld] -- " _msg "\n", get_now_monotonic(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
printf("-- " _label " [%.03Lf tid=%d] -- " _msg "\n", get_now_monotonic(), get_thread_id(), ##__VA_ARGS__); \
|
||||||
fflush(stdout); \
|
fflush(stdout); \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +80,9 @@ pthread_mutex_t log_mutex;
|
|||||||
|
|
||||||
#define LOG_PERROR(_msg, ...) { \
|
#define LOG_PERROR(_msg, ...) { \
|
||||||
char _buf[1024] = ""; \
|
char _buf[1024] = ""; \
|
||||||
char *_ptr = strerror_r(errno, _buf, 1024); \
|
char *_ptr = errno_to_string(_buf, 1024); \
|
||||||
LOGGING_LOCK; \
|
LOGGING_LOCK; \
|
||||||
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", get_now_monotonic(), syscall(SYS_gettid), ##__VA_ARGS__, _ptr); \
|
printf("-- ERROR [%.03Lf tid=%d] -- " _msg ": %s\n", get_now_monotonic(), get_thread_id(), ##__VA_ARGS__, _ptr); \
|
||||||
fflush(stdout); \
|
fflush(stdout); \
|
||||||
LOGGING_UNLOCK; \
|
LOGGING_UNLOCK; \
|
||||||
}
|
}
|
||||||
@@ -124,3 +120,13 @@ pthread_mutex_t log_mutex;
|
|||||||
LOGGING_UNLOCK; \
|
LOGGING_UNLOCK; \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
INLINE char *errno_to_string(char *buf, size_t size) {
|
||||||
|
# if defined(__GLIBC__) && defined(_GNU_SOURCE)
|
||||||
|
return strerror_r(errno, buf, size);
|
||||||
|
# else
|
||||||
|
strerror_r(errno, buf, size);
|
||||||
|
return buf;
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|||||||
66
src/main.c
66
src/main.c
@@ -38,10 +38,10 @@
|
|||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "encoder.h"
|
#include "encoder.h"
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
#include "http.h"
|
#include "http/server.h"
|
||||||
|
|
||||||
|
|
||||||
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 char _SHORT_OPTS[] = "d:i:x:y:m:a:f:z:ntb:w:q:c:s:p:u:ro:k:e:lhv";
|
||||||
static const struct option _LONG_OPTS[] = {
|
static const struct option _LONG_OPTS[] = {
|
||||||
{"device", required_argument, NULL, 'd'},
|
{"device", required_argument, NULL, 'd'},
|
||||||
{"input", required_argument, NULL, 'i'},
|
{"input", required_argument, NULL, 'i'},
|
||||||
@@ -79,11 +79,15 @@ static const struct option _LONG_OPTS[] = {
|
|||||||
{"unix", required_argument, NULL, 'u'},
|
{"unix", required_argument, NULL, 'u'},
|
||||||
{"unix-rm", no_argument, NULL, 'r'},
|
{"unix-rm", no_argument, NULL, 'r'},
|
||||||
{"unix-mode", required_argument, NULL, 'o'},
|
{"unix-mode", required_argument, NULL, 'o'},
|
||||||
|
{"user", required_argument, NULL, 3000},
|
||||||
|
{"passwd", required_argument, NULL, 3001},
|
||||||
|
{"static", required_argument, NULL, 3002},
|
||||||
|
{"blank", required_argument, NULL, 'k'},
|
||||||
{"drop-same-frames", required_argument, NULL, 'e'},
|
{"drop-same-frames", required_argument, NULL, 'e'},
|
||||||
{"slowdown", no_argument, NULL, 'l'},
|
{"slowdown", no_argument, NULL, 'l'},
|
||||||
{"fake-width", required_argument, NULL, 3001},
|
{"fake-width", required_argument, NULL, 3003},
|
||||||
{"fake-height", required_argument, NULL, 3002},
|
{"fake-height", required_argument, NULL, 3004},
|
||||||
{"server-timeout", required_argument, NULL, 3003},
|
{"server-timeout", required_argument, NULL, 3005},
|
||||||
|
|
||||||
{"perf", no_argument, NULL, 5000},
|
{"perf", no_argument, NULL, 5000},
|
||||||
{"verbose", no_argument, NULL, 5001},
|
{"verbose", no_argument, NULL, 5001},
|
||||||
@@ -122,21 +126,22 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
|||||||
printf(" -a|--tv-standard <std> ───────── Force TV standard.\n");
|
printf(" -a|--tv-standard <std> ───────── Force TV standard.\n");
|
||||||
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
|
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
|
||||||
printf(" -f|--desired-fps <N> ─────────── Desired FPS. Default: maximum as possible.\n\n");
|
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.\n");
|
printf(" -z|--min-frame-size <N> ──────── Drop frames smaller then this limit. Useful if the device\n");
|
||||||
printf(" Useful if the device produces small-sized garbage frames.\n\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(" -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(" -t|--dv-timings ──────────────── Enable DV timings queriyng and events processing\n");
|
||||||
printf(" Supports automatic resolution changing. Default: disabled.\n\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(" -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(" 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(" 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. Default: %u (== --buffers).\n\n", dev->n_workers);
|
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(" -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(" -c|--encoder <type> ──────────── Use specified encoder. It may affects to workers number.\n");
|
||||||
printf(" Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
|
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-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(" --device-error-delay <seconds> ─ Delay before trying to connect to the device again\n");
|
||||||
printf(" after timeout. Default: %u\n\n", dev->error_delay);
|
printf(" after an error (timeout for example). Default: %u.\n\n", dev->error_delay);
|
||||||
printf("Image control options:\n");
|
printf("Image control options:\n");
|
||||||
printf("══════════════════════\n");
|
printf("══════════════════════\n");
|
||||||
printf(" --brightness <N> ───────────── Set brightness. Default: no change.\n\n");
|
printf(" --brightness <N> ───────────── Set brightness. Default: no change.\n\n");
|
||||||
@@ -154,20 +159,26 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
|||||||
printf(" --gain-auto ────────────────── Enable automatic gain control. Default: no change.\n\n");
|
printf(" --gain-auto ────────────────── Enable automatic gain control. Default: no change.\n\n");
|
||||||
printf("HTTP server options:\n");
|
printf("HTTP server options:\n");
|
||||||
printf("════════════════════\n");
|
printf("════════════════════\n");
|
||||||
printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s\n\n", server->host);
|
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(" -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(" -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(" -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(" -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(" -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 same frames to clients, but no more than specified number.\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(" 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(" the CPU loading. Don't use this option with analog signal sources\n");
|
||||||
printf(" or webcams, it's useless. Default: disabled.\n\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(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients connected.\n");
|
||||||
printf(" Useful to reduce CPU cosumption. Default: disabled.\n\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-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(" --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(" --server-timeout <seconds> ─ Timeout for client connections. Default: %u.\n\n", server->timeout);
|
||||||
printf("Misc options:\n");
|
printf("Misc options:\n");
|
||||||
printf("═════════════\n");
|
printf("═════════════\n");
|
||||||
printf(" --log-level <N> ─ Verbosity level of messages from 0 (info) to 3 (debug).\n");
|
printf(" --log-level <N> ─ Verbosity level of messages from 0 (info) to 3 (debug).\n");
|
||||||
@@ -274,11 +285,15 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
|||||||
case 'u': OPT_SET(server->unix_path, optarg);
|
case 'u': OPT_SET(server->unix_path, optarg);
|
||||||
case 'r': OPT_SET(server->unix_rm, true);
|
case 'r': OPT_SET(server->unix_rm, true);
|
||||||
case 'o': OPT_CHMOD(server->unix_mode, "--unix-mode");
|
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 'k': OPT_SET(server->blank_path, optarg);
|
||||||
case 'e': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
|
case 'e': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
|
||||||
case 3000: OPT_SET(server->slowdown, true);
|
case 'l': OPT_SET(server->slowdown, true);
|
||||||
case 3001: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
case 3003: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
||||||
case 3002: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
|
case 3004: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
|
||||||
case 3003: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
case 3005: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
||||||
|
|
||||||
case 5000: OPT_SET(log_level, LOG_LEVEL_PERF);
|
case 5000: OPT_SET(log_level, LOG_LEVEL_PERF);
|
||||||
case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
|
case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
|
||||||
@@ -369,7 +384,6 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) {
|
if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) {
|
||||||
_install_signal_handlers();
|
_install_signal_handlers();
|
||||||
encoder_prepare(encoder, dev);
|
|
||||||
|
|
||||||
pthread_t stream_loop_tid;
|
pthread_t stream_loop_tid;
|
||||||
pthread_t server_loop_tid;
|
pthread_t server_loop_tid;
|
||||||
|
|||||||
54
src/stream.c
54
src/stream.c
@@ -93,7 +93,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
bool persistent_timeout_reported = false;
|
bool persistent_timeout_reported = false;
|
||||||
|
|
||||||
LOG_DEBUG("Allocation memory for stream picture ...");
|
LOG_DEBUG("Allocation memory for stream picture ...");
|
||||||
A_CALLOC(stream->picture.data, stream->dev->run->max_picture_size);
|
A_CALLOC(stream->picture.data, stream->dev->run->max_raw_image_size);
|
||||||
|
|
||||||
LOG_INFO("Capturing ...");
|
LOG_INFO("Capturing ...");
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
|
|
||||||
LOG_PERF("##### Raw frame accepted; worker = %u", free_worker_number);
|
LOG_PERF("##### Raw frame accepted; worker = %u", free_worker_number);
|
||||||
} else {
|
} else {
|
||||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
|
||||||
if (
|
if (
|
||||||
!atomic_load(&pool.workers[number].has_job) && (
|
!atomic_load(&pool.workers[number].has_job) && (
|
||||||
free_worker_number == -1
|
free_worker_number == -1
|
||||||
@@ -186,22 +186,23 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
if (FD_ISSET(stream->dev->run->fd, &read_fds)) {
|
if (FD_ISSET(stream->dev->run->fd, &read_fds)) {
|
||||||
LOG_DEBUG("Frame is ready");
|
LOG_DEBUG("Frame is ready");
|
||||||
|
|
||||||
struct v4l2_buffer buf_info;
|
int buf_index;
|
||||||
long double now = get_now_monotonic();
|
long double now = get_now_monotonic();
|
||||||
long long now_second = floor_ms(now);
|
long long now_second = floor_ms(now);
|
||||||
|
|
||||||
if (device_grab_buffer(stream->dev, &buf_info) < 0) {
|
if ((buf_index = device_grab_buffer(stream->dev)) < 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
stream->dev->run->pictures[buf_info.index].grab_time = now;
|
stream->dev->run->pictures[buf_index].grab_time = now;
|
||||||
|
|
||||||
// Workaround for broken, corrupted frames:
|
// Workaround for broken, corrupted frames:
|
||||||
// Under low light conditions corrupted frames may get captured.
|
// Under low light conditions corrupted frames may get captured.
|
||||||
// The good thing is such frames are quite small compared to the regular pictures.
|
// The good thing is such frames are quite small compared to the regular pictures.
|
||||||
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
|
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
|
||||||
// corrupted frames are smaller.
|
// corrupted frames are smaller.
|
||||||
if (buf_info.bytesused < stream->dev->min_frame_size) {
|
if (stream->dev->run->hw_buffers[buf_index].used < stream->dev->min_frame_size) {
|
||||||
LOG_DEBUG("Dropping too small frame sized %u bytes, assuming it as broken", buf_info.bytesused);
|
LOG_DEBUG("Dropping too small frame sized %zu bytes, assuming it as broken",
|
||||||
|
stream->dev->run->hw_buffers[buf_index].used);
|
||||||
goto pass_frame;
|
goto pass_frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,10 +230,9 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
|
|
||||||
# define FREE_WORKER(_next) pool.workers[free_worker_number]._next
|
# define FREE_WORKER(_next) pool.workers[free_worker_number]._next
|
||||||
|
|
||||||
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_info.index);
|
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_index);
|
||||||
FREE_WORKER(buf_info) = buf_info;
|
|
||||||
|
|
||||||
if (!oldest_worker) {
|
if (oldest_worker == NULL) {
|
||||||
oldest_worker = &pool.workers[free_worker_number];
|
oldest_worker = &pool.workers[free_worker_number];
|
||||||
last_worker = oldest_worker;
|
last_worker = oldest_worker;
|
||||||
} else {
|
} else {
|
||||||
@@ -249,7 +249,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
last_worker->order_next = NULL;
|
last_worker->order_next = NULL;
|
||||||
|
|
||||||
A_MUTEX_LOCK(&FREE_WORKER(has_job_mutex));
|
A_MUTEX_LOCK(&FREE_WORKER(has_job_mutex));
|
||||||
FREE_WORKER(buf_index) = buf_info.index;
|
FREE_WORKER(buf_index) = buf_index;
|
||||||
atomic_store(&FREE_WORKER(has_job), true);
|
atomic_store(&FREE_WORKER(has_job), true);
|
||||||
A_MUTEX_UNLOCK(&FREE_WORKER(has_job_mutex));
|
A_MUTEX_UNLOCK(&FREE_WORKER(has_job_mutex));
|
||||||
A_COND_SIGNAL(&FREE_WORKER(has_job_cond));
|
A_COND_SIGNAL(&FREE_WORKER(has_job_cond));
|
||||||
@@ -264,7 +264,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
|
|
||||||
pass_frame:
|
pass_frame:
|
||||||
|
|
||||||
if (device_release_buffer(stream->dev, &buf_info) < 0) {
|
if (device_release_buffer(stream->dev, buf_index) < 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,7 +286,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
A_MUTEX_LOCK(&stream->mutex);
|
A_MUTEX_LOCK(&stream->mutex);
|
||||||
stream->picture.size = 0; // On stream offline
|
stream->picture.used = 0; // On stream offline
|
||||||
free(stream->picture.data);
|
free(stream->picture.data);
|
||||||
stream->width = 0;
|
stream->width = 0;
|
||||||
stream->height = 0;
|
stream->height = 0;
|
||||||
@@ -312,10 +312,10 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
|
|||||||
|
|
||||||
A_MUTEX_LOCK(&stream->mutex);
|
A_MUTEX_LOCK(&stream->mutex);
|
||||||
|
|
||||||
stream->picture.size = PICTURE(size);
|
stream->picture.used = PICTURE(used);
|
||||||
stream->picture.allocated = PICTURE(allocated);
|
stream->picture.allocated = PICTURE(allocated);
|
||||||
|
|
||||||
memcpy(stream->picture.data, PICTURE(data), stream->picture.size * sizeof(*stream->picture.data));
|
memcpy(stream->picture.data, PICTURE(data), stream->picture.used);
|
||||||
|
|
||||||
stream->picture.grab_time = PICTURE(grab_time);
|
stream->picture.grab_time = PICTURE(grab_time);
|
||||||
stream->picture.encode_begin_time = PICTURE(encode_begin_time);
|
stream->picture.encode_begin_time = PICTURE(encode_begin_time);
|
||||||
@@ -336,7 +336,7 @@ static long double _stream_get_fluency_delay(struct device_t *dev, struct worker
|
|||||||
long double min_delay;
|
long double min_delay;
|
||||||
long double soft_delay;
|
long double soft_delay;
|
||||||
|
|
||||||
for (unsigned number = 0; number < dev->n_workers; ++number) {
|
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
|
||||||
# define WORKER(_next) pool->workers[number]._next
|
# define WORKER(_next) pool->workers[number]._next
|
||||||
|
|
||||||
A_MUTEX_LOCK(&WORKER(last_comp_time_mutex));
|
A_MUTEX_LOCK(&WORKER(last_comp_time_mutex));
|
||||||
@@ -347,9 +347,9 @@ static long double _stream_get_fluency_delay(struct device_t *dev, struct worker
|
|||||||
|
|
||||||
# undef WORKER
|
# undef WORKER
|
||||||
}
|
}
|
||||||
avg_comp_time = sum_comp_time / dev->n_workers; // Среднее время работы воркеров
|
avg_comp_time = sum_comp_time / dev->run->n_workers; // Среднее время работы воркеров
|
||||||
|
|
||||||
min_delay = avg_comp_time / dev->n_workers; // Среднее время работы размазывается на N воркеров
|
min_delay = avg_comp_time / dev->run->n_workers; // Среднее время работы размазывается на N воркеров
|
||||||
|
|
||||||
if (dev->desired_fps > 0 && min_delay > 0) {
|
if (dev->desired_fps > 0 && min_delay > 0) {
|
||||||
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
|
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
|
||||||
@@ -376,12 +376,12 @@ static int _stream_init_loop(struct stream_t *stream, struct workers_pool_t *poo
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
|
static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
|
||||||
SEP_INFO('=');
|
|
||||||
|
|
||||||
_stream_destroy_workers(stream, pool);
|
_stream_destroy_workers(stream, pool);
|
||||||
device_switch_capturing(stream->dev, false);
|
device_switch_capturing(stream->dev, false);
|
||||||
device_close(stream->dev);
|
device_close(stream->dev);
|
||||||
|
|
||||||
|
SEP_INFO('=');
|
||||||
|
|
||||||
if (device_open(stream->dev) < 0) {
|
if (device_open(stream->dev) < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
@@ -389,7 +389,7 @@ static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder_prepare_live(pool->encoder, stream->dev);
|
encoder_prepare(pool->encoder, stream->dev);
|
||||||
|
|
||||||
_stream_init_workers(stream, pool);
|
_stream_init_workers(stream, pool);
|
||||||
|
|
||||||
@@ -401,15 +401,15 @@ static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool) {
|
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool) {
|
||||||
LOG_INFO("Spawning %u workers ...", stream->dev->n_workers);
|
LOG_INFO("Spawning %u workers ...", stream->dev->run->n_workers);
|
||||||
|
|
||||||
atomic_store(&pool->workers_stop, false);
|
atomic_store(&pool->workers_stop, false);
|
||||||
A_CALLOC(pool->workers, stream->dev->n_workers);
|
A_CALLOC(pool->workers, stream->dev->run->n_workers);
|
||||||
|
|
||||||
A_MUTEX_INIT(&pool->free_workers_mutex);
|
A_MUTEX_INIT(&pool->free_workers_mutex);
|
||||||
A_COND_INIT(&pool->free_workers_cond);
|
A_COND_INIT(&pool->free_workers_cond);
|
||||||
|
|
||||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
|
||||||
# define WORKER(_next) pool->workers[number]._next
|
# define WORKER(_next) pool->workers[number]._next
|
||||||
|
|
||||||
pool->free_workers += 1;
|
pool->free_workers += 1;
|
||||||
@@ -457,7 +457,7 @@ static void *_stream_worker_thread(void *v_worker) {
|
|||||||
}
|
}
|
||||||
PICTURE(encode_end_time) = get_now_monotonic();
|
PICTURE(encode_end_time) = get_now_monotonic();
|
||||||
|
|
||||||
if (device_release_buffer(worker->dev, &worker->buf_info) == 0) {
|
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
|
||||||
worker->job_start_time = PICTURE(encode_begin_time);
|
worker->job_start_time = PICTURE(encode_begin_time);
|
||||||
atomic_store(&worker->has_job, false);
|
atomic_store(&worker->has_job, false);
|
||||||
|
|
||||||
@@ -468,7 +468,7 @@ static void *_stream_worker_thread(void *v_worker) {
|
|||||||
A_MUTEX_UNLOCK(&worker->last_comp_time_mutex);
|
A_MUTEX_UNLOCK(&worker->last_comp_time_mutex);
|
||||||
|
|
||||||
LOG_VERBOSE("Compressed JPEG size=%zu; time=%0.3Lf; worker=%u; buffer=%u",
|
LOG_VERBOSE("Compressed JPEG size=%zu; time=%0.3Lf; worker=%u; buffer=%u",
|
||||||
PICTURE(size), last_comp_time, worker->number, worker->buf_index);
|
PICTURE(used), last_comp_time, worker->number, worker->buf_index);
|
||||||
} else {
|
} else {
|
||||||
worker->job_failed = true;
|
worker->job_failed = true;
|
||||||
atomic_store(&worker->has_job, false);
|
atomic_store(&worker->has_job, false);
|
||||||
@@ -492,7 +492,7 @@ static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool
|
|||||||
LOG_INFO("Destroying workers ...");
|
LOG_INFO("Destroying workers ...");
|
||||||
|
|
||||||
atomic_store(&pool->workers_stop, true);
|
atomic_store(&pool->workers_stop, true);
|
||||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
|
||||||
# define WORKER(_next) pool->workers[number]._next
|
# define WORKER(_next) pool->workers[number]._next
|
||||||
|
|
||||||
A_MUTEX_LOCK(&WORKER(has_job_mutex));
|
A_MUTEX_LOCK(&WORKER(has_job_mutex));
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ struct worker_t {
|
|||||||
|
|
||||||
pthread_mutex_t has_job_mutex;
|
pthread_mutex_t has_job_mutex;
|
||||||
int buf_index;
|
int buf_index;
|
||||||
struct v4l2_buffer buf_info;
|
|
||||||
atomic_bool has_job;
|
atomic_bool has_job;
|
||||||
bool job_failed;
|
bool job_failed;
|
||||||
long double job_start_time;
|
long double job_start_time;
|
||||||
|
|||||||
@@ -25,12 +25,14 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
@@ -51,7 +53,6 @@
|
|||||||
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
|
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
|
||||||
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
|
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
|
||||||
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
|
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
|
||||||
#define MEMSET_ZERO_PTR(_ptr) memset(_ptr, 0, sizeof(*(_ptr)))
|
|
||||||
|
|
||||||
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
|
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
|
||||||
|
|
||||||
@@ -103,3 +104,7 @@ INLINE long double get_now_real(void) {
|
|||||||
get_now(CLOCK_REALTIME, &sec, &msec);
|
get_now(CLOCK_REALTIME, &sec, &msec);
|
||||||
return (long double)sec + ((long double)msec) / 1000;
|
return (long double)sec + ((long double)msec) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INLINE pid_t get_thread_id(void) {
|
||||||
|
return syscall(SYS_gettid);
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,7 +30,9 @@
|
|||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
|
||||||
|
|
||||||
#define XIOCTL_RETRIES 4
|
#ifndef XIOCTL_RETRIES
|
||||||
|
# define XIOCTL_RETRIES 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
INLINE int xioctl(int fd, int request, void *arg) {
|
INLINE int xioctl(int fd, int request, void *arg) {
|
||||||
|
|||||||
53
tools/common.py
Normal file
53
tools/common.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#============================================================================#
|
||||||
|
# #
|
||||||
|
# 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/>. #
|
||||||
|
# #
|
||||||
|
#============================================================================#
|
||||||
|
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
|
# =====
|
||||||
|
def get_prepend() -> str:
|
||||||
|
return textwrap.dedent("""
|
||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# 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
|
||||||
|
""").strip() + "\n"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env -S python3 -B
|
||||||
#============================================================================#
|
#============================================================================#
|
||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||||
@@ -24,54 +24,32 @@
|
|||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
import common
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
def main():
|
def main() -> None:
|
||||||
assert len(sys.argv) == 4, "%s <src> <dest> <name>" % (sys.argv[0])
|
assert len(sys.argv) == 4, "%s <file.html> <file.h> <name>" % (sys.argv[0])
|
||||||
|
html_path = sys.argv[1]
|
||||||
src = sys.argv[1]
|
header_path = sys.argv[2]
|
||||||
dest = sys.argv[2]
|
|
||||||
name = sys.argv[3]
|
name = sys.argv[3]
|
||||||
|
|
||||||
with open(src, "r") as html_file:
|
with open(html_path, "r") as html_file:
|
||||||
text = html_file.read()
|
text = html_file.read()
|
||||||
|
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
text = text.replace("\"", "\\\"")
|
text = text.replace("\"", "\\\"")
|
||||||
text = text.replace("%VERSION%", "\" VERSION \"")
|
text = text.replace("%VERSION%", "\" VERSION \"")
|
||||||
text = textwrap.indent(text, "\t", (lambda line: True))
|
text = textwrap.indent(text, "\t", (lambda line: True))
|
||||||
text = "\n".join(("%s \\" if line.strip() else "%s\\") % (line) for line in text.split("\n"))
|
text = "\n".join(
|
||||||
text = "const char %s[] = \" \\\n%s\n\";\n" % (name, text)
|
("%s \\" if line.strip() else "%s\\") % (line)
|
||||||
text = textwrap.dedent("""
|
for line in text.split("\n")
|
||||||
/*****************************************************************************
|
)
|
||||||
# #
|
text = "const char HTML_%s_PAGE[] = \" \\\n%s\n\";\n" % (name, text)
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
text = common.get_prepend() + "\n#include \"../../config.h\"\n\n\n" + text
|
||||||
# #
|
|
||||||
# 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/>. #
|
|
||||||
# #
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
|
with open(header_path, "w") as header_file:
|
||||||
#pragma once
|
header_file.write(text)
|
||||||
|
|
||||||
#include "../config.h"
|
|
||||||
""").strip() + "\n\n\n" + text
|
|
||||||
|
|
||||||
with open(dest, "w") as h_file:
|
|
||||||
h_file.write(text)
|
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env -S python3 -B
|
||||||
#============================================================================#
|
#============================================================================#
|
||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||||
@@ -22,57 +22,68 @@
|
|||||||
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import io
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from typing import Tuple
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import common
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
def main():
|
def _get_jpeg_size(data: bytes) -> Tuple[int, int]:
|
||||||
assert len(sys.argv) == 6, "%s <src> <dest> <prefix> <width> <height>" % (sys.argv[0])
|
# https://sheep.horse/2013/9/finding_the_dimensions_of_a_jpeg_file_in_python.html
|
||||||
|
|
||||||
src = sys.argv[1]
|
stream = io.BytesIO(data)
|
||||||
dest = sys.argv[2]
|
while True:
|
||||||
prefix = sys.argv[3]
|
marker = struct.unpack(">H", stream.read(2))[0]
|
||||||
width = int(sys.argv[4])
|
if (
|
||||||
height = int(sys.argv[5])
|
marker == 0xFFD8 # Start of image
|
||||||
|
or marker == 0xFF01 # Private marker
|
||||||
|
or (marker >= 0xFFD0 and marker <= 0xFFD7) # Restart markers
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
elif marker == 0xFFD9:
|
||||||
|
raise RuntimeError("Can't find jpeg size")
|
||||||
|
|
||||||
with open(src, "rb") as jpeg_file:
|
# All other markers specify chunks with lengths
|
||||||
|
length = struct.unpack(">H", stream.read(2))[0]
|
||||||
|
|
||||||
|
if marker == 0xFFC0: # Baseline DCT chunk, has the info we want
|
||||||
|
(_, height, width) = struct.unpack(">BHH", stream.read(5))
|
||||||
|
return (width, height)
|
||||||
|
|
||||||
|
# Not the chunk we want, skip it
|
||||||
|
stream.seek(length - 2, 1)
|
||||||
|
|
||||||
|
|
||||||
|
# =====
|
||||||
|
def main() -> None:
|
||||||
|
assert len(sys.argv) == 4, "%s <file.jpeg> <file.h> <name>" % (sys.argv[0])
|
||||||
|
jpeg_path = sys.argv[1]
|
||||||
|
header_path = sys.argv[2]
|
||||||
|
name = sys.argv[3]
|
||||||
|
|
||||||
|
with open(jpeg_path, "rb") as jpeg_file:
|
||||||
jpeg_data = jpeg_file.read()
|
jpeg_data = jpeg_file.read()
|
||||||
|
|
||||||
rows = [[]]
|
(width, height) = _get_jpeg_size(jpeg_data)
|
||||||
|
|
||||||
|
rows: List[List[str]] = [[]]
|
||||||
for ch in jpeg_data:
|
for ch in jpeg_data:
|
||||||
if len(rows[-1]) > 20:
|
if len(rows[-1]) > 20:
|
||||||
rows.append([])
|
rows.append([])
|
||||||
rows[-1].append("0x%.2X" % (ch))
|
rows[-1].append("0x%.2X" % (ch))
|
||||||
|
|
||||||
text = ",\n\t".join(", ".join(row) for row in rows)
|
text = ",\n\t".join(", ".join(row) for row in rows)
|
||||||
text = "const unsigned char %s_JPEG_DATA[] = {\n\t%s\n};\n" % (prefix, text)
|
text = "const unsigned char %s_JPEG_DATA[] = {\n\t%s\n};\n" % (name, text)
|
||||||
text = "const unsigned %s_JPEG_HEIGHT = %d;\n\n" % (prefix, height) + text
|
text = "const unsigned %s_JPEG_HEIGHT = %d;\n\n" % (name, height) + text
|
||||||
text = "const unsigned %s_JPEG_WIDTH = %d;\n" % (prefix, width) + text
|
text = "const unsigned %s_JPEG_WIDTH = %d;\n" % (name, width) + text
|
||||||
text = textwrap.dedent("""
|
text = common.get_prepend() + "\n\n" + text
|
||||||
/*****************************************************************************
|
|
||||||
# #
|
|
||||||
# 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/>. #
|
|
||||||
# #
|
|
||||||
*****************************************************************************/
|
|
||||||
""").strip() + "\n\n\n" + text
|
|
||||||
|
|
||||||
with open(dest, "w") as h_file:
|
with open(header_path, "w") as header_file:
|
||||||
h_file.write(text)
|
header_file.write(text)
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
|
|||||||
Reference in New Issue
Block a user