Compare commits

...

53 Commits
v1.21 ... v2.1

Author SHA1 Message Date
Devaev Maxim
d9b91a1d5f Bump version: 2.0 → 2.1 2020-09-29 01:42:40 +03:00
Devaev Maxim
d682a1c173 added info about mjpg-streamer compat and prettify 2020-09-28 13:54:38 +03:00
Devaev Maxim
ba03333623 better access checking 2020-09-28 10:28:42 +03:00
Devaev Maxim
c7e6e5e006 check for device access before open 2020-09-28 04:29:02 +03:00
Devaev Maxim
45b1e2f285 refactoring 2020-09-28 04:23:41 +03:00
Devaev Maxim
d9bbd8a74d refactoring 2020-09-27 22:58:13 +03:00
Devaev Maxim
37179184ae Bump version: 1.26 → 2.0 2020-09-25 00:03:14 +03:00
Devaev Maxim
fc8aba0a12 Bump version: 1.25 → 1.26 2020-09-25 00:01:13 +03:00
Devaev Maxim
0d749eada3 V for make release 2020-09-23 18:11:25 +03:00
Maxim Devaev
da6984d531 Merge pull request #49 from pikvm/allow-origin
Disabled cross-domain requests by default
2020-09-23 00:04:45 +03:00
Devaev Maxim
df14031042 readme fix 2020-09-22 21:43:01 +03:00
Maxim Devaev
03975c1a85 Update README.md 2020-09-22 18:02:27 +03:00
Maxim Devaev
214a924da3 Update README.ru.md 2020-09-22 18:01:48 +03:00
Maxim Devaev
9e6a9a2fd4 Update README.md 2020-09-22 17:59:09 +03:00
Devaev Maxim
b498ae7e38 Issue #48: Disabled cross-domain requests by default 2020-09-22 17:58:10 +03:00
Maxim Devaev
278645ce51 Merge pull request #47 from pikvm/queue-assert
Double dequeue error
2020-09-20 00:53:58 +03:00
Devaev Maxim
f1ee5514e3 style fix 2020-09-20 00:44:21 +03:00
Maxim Devaev
3900728f9f Merge pull request #46 from pikvm/libgpiod
moved from wiringpi to libgpiod
2020-09-19 17:01:28 +03:00
Devaev Maxim
3dc083d2ef Rewrited #44: fixed memory error and leak 2020-09-19 16:55:39 +03:00
Maxim Devaev
653ebd6e88 Merge pull request #44 from schneemaier/master
Added HTTP GET parameter handling to server.c
2020-09-19 13:29:07 +03:00
Devaev Maxim
a770e7675d Issue #43: assert for double VIDIOC_DQBUF 2020-09-19 05:54:47 +03:00
Devaev Maxim
6725083be6 moved from wiringpi to libgpiod 2020-09-19 04:06:14 +03:00
Akos Schneemaier
0b39cadaad Added HTTP GET parameter handling to server.c to make URLcompatibility with mjpg streamer 2020-09-18 00:00:11 -04:00
Devaev Maxim
871b0cf132 improved logs 2020-09-18 04:19:58 +03:00
Maxim Devaev
afa888432a Update README.ru.md 2020-09-15 23:07:22 +03:00
Maxim Devaev
a42bd147ff Update README.md 2020-09-15 23:06:47 +03:00
Devaev Maxim
2ad8871a54 Bump version: 1.24 → 1.25 2020-09-01 08:55:28 +03:00
Devaev Maxim
266e210b04 fixed ld error 2020-09-01 08:53:33 +03:00
Devaev Maxim
0ac9f77619 Bump version: 1.23 → 1.24 2020-09-01 07:57:09 +03:00
Devaev Maxim
c1bc1d9506 fixed linter error 2020-09-01 07:52:29 +03:00
Devaev Maxim
deb37986b6 Issue #39: fixed missing gettid() syscall on *BSD 2020-09-01 06:39:12 +03:00
Devaev Maxim
ee6c555ce0 Issue #32: refactoring 2020-08-24 10:21:47 +03:00
Maxim Devaev
4395b8487f Merge pull request #37 from PascalHonegger/master
Issue #32: Create Dockerfiles
2020-08-24 00:22:52 +03:00
Pascal Honegger
f622d03d1b Issue #32: Create Dockerfiles 2020-08-23 15:36:48 +02:00
Devaev Maxim
36e6fa7b09 added aarch64 to PKGBUILD 2020-08-23 15:56:59 +03:00
Devaev Maxim
8cf6c66f21 Fixed #35: spell fix 2020-08-22 23:32:06 +03:00
Devaev Maxim
ac9761beb2 Bump version: 1.22 → 1.23 2020-08-22 16:53:29 +03:00
Devaev Maxim
90b7a5600f Issue #24: disable software framedrop if hw_fps == desired_fps 2020-08-22 16:17:34 +03:00
Maxim Devaev
4c70baecb1 Update README.ru.md 2020-08-21 23:01:15 +03:00
Maxim Devaev
15c14bfebf Update README.ru.md 2020-08-21 23:00:14 +03:00
Maxim Devaev
eab8043496 Update README.md 2020-08-21 22:59:11 +03:00
Devaev Maxim
53feba1248 Bump version: 1.21 → 1.22 2020-08-20 05:15:24 +03:00
Devaev Maxim
119821d5af queued_fps = 0 for no clients 2020-08-19 14:27:52 +03:00
Maxim Devaev
4faabf27ec Merge pull request #33 from pikvm/sem-timeout
Sem timeout
2020-08-19 13:44:23 +03:00
Devaev Maxim
191f6e3c09 non-zero min-frame-size; default = 128 2020-08-19 13:20:22 +03:00
Devaev Maxim
4e51439118 bsd compat 2020-08-18 15:19:09 +03:00
Devaev Maxim
e184e187a2 option --color-effect 2020-08-18 12:15:44 +03:00
Maxim Devaev
592568c9aa Merge pull request #31 from pikvm/flip
Options to flip image
2020-08-17 18:17:01 +03:00
Devaev Maxim
46c5a547a9 options to flip image 2020-08-17 03:49:29 +03:00
Devaev Maxim
3d097a4ffb more logs 2020-08-15 04:51:00 +03:00
Devaev Maxim
00e32c915c fixed uninitialized value 2020-08-15 04:40:55 +03:00
Devaev Maxim
d44c340dce vcos sem timeout 2020-08-15 00:10:07 +03:00
Devaev Maxim
8c18f1dffe Issue #25: fixed freebsd build 2020-08-14 03:38:05 +03:00
30 changed files with 682 additions and 254 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 1.21
current_version = 2.1
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
serialize =
{major}.{minor}

6
.dockerignore Normal file
View File

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

View File

@@ -35,8 +35,9 @@ endif
ifneq ($(call optbool,$(WITH_GPIO)),)
_LIBS += -lwiringPi
_LIBS += -lgpiod
override CFLAGS += -DWITH_GPIO
_SRCS += $(shell ls src/gpio/*.c)
endif
@@ -96,7 +97,7 @@ release:
make clean
make tox
make push
make bump
make bump V=$(V)
make push
make clean

View File

@@ -14,13 +14,14 @@
| Multithreaded JPEG encoding | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Behavior when the device<br>is disconnected while streaming | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Stops the streaming <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save the traffic | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Streaming via UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Option to serve files<br>with a built-in HTTP server | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Regular files only |
| Signaling about the stream state to GPIO<br>on Raspberry Pi using [wiringPi](http://wiringpi.com) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
| Compatibility with mjpg-streamer's API | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | :) |
Footnotes:
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
@@ -36,10 +37,10 @@ If you're going to live-stream from your backyard webcam and need to control it,
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` and ```libbsd``` (only for Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `wiringpi` for `WITH_GPIO=1`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `libgpiod` for `WITH_GPIO=1`.
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [wiringPi](http://wiringpi.com) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
```
$ git clone --depth=1 https://github.com/pikvm/ustreamer
@@ -48,7 +49,7 @@ $ make
$ ./ustreamer --help
```
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```. Same with GPIO.
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
-----
@@ -58,6 +59,8 @@ Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x4
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
```
:exclamation: Please note that since µStreamer v2.0 cross-domain requests were disabled by default for [security reasons](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To enable the old behavior, use the option `--allow-origin=\*`.
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
```bash
$ ./ustreamer \
@@ -69,6 +72,8 @@ $ ./ustreamer \
--drop-same-frames=30 # Save the traffic
```
:exclamation: Please note that to use `--drop-same-frames` for different browsers you need to use some specific URL `/stream` parameters (see URL `/` for details).
You can always view the full list of options with ```ustreamer --help```.
-----

View File

@@ -14,13 +14,14 @@
| Многопоточное кодирование JPEG | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении<br>устройства от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Прерывает трансляцию <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Стрим через UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Возможность сервить файлы встроенным<br>HTTP-сервером | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Нет каталогов |
| Вывод сигналов о состоянии стрима на GPIO<br>на Raspberry Pi с помощью [wiringPi](http://wiringpi.com) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Совместимость с API mjpg-streamer'а | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | :) |
Сносочки:
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
@@ -36,10 +37,10 @@
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `wiringpi` для `WITH_GPIO=1`.
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `libgpiod` для `WITH_GPIO=1`.
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [wiringPi](http://wiringpi.com) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.
```
$ git clone --depth=1 https://github.com/pikvm/ustreamer
@@ -48,7 +49,7 @@ $ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```. То же самое и с GPIO.
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
-----
@@ -58,6 +59,8 @@ $ ./ustreamer --help
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
```
:exclamation: Обратите внимание, что начиная с версии µStreamer v2.0 кросс-доменные запросы были выключены по умолчанию [по соображениям безопасности](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). Чтобы включить старое поведение, используйте опцию `--allow-origin=\*`.
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
```bash
$ ./ustreamer \
@@ -69,6 +72,8 @@ $ ./ustreamer \
--drop-same-frames=30 # Экономим трафик
```
:exclamation: Обратите внимание, что для использования `--drop-same-frames` для разных браузеров нужно использовать ряд специальных параметров в `/stream` (за деталями обратитесь к урлу `/`).
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
-----

View File

@@ -16,6 +16,9 @@ RUN pacman -Syu --noconfirm \
python-pip \
python-tox \
cppcheck \
npm \
&& (pacman -Sc --noconfirm || true)
RUN npm install htmlhint -g
CMD /bin/bash

View File

@@ -1,5 +1,5 @@
[tox]
envlist = cppcheck, flake8, pylint, mypy, vulture
envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
skipsdist = true
[testenv]
@@ -44,3 +44,7 @@ whitelist_externals = bash
commands = bash -c 'vulture tools/*.py'
deps =
vulture
[testenv:htmlhint]
whitelist_externals = htmlhint
commands = htmlhint src/http/data/*.html

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=1.21
PKG_VERSION:=2.1
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

View File

@@ -23,5 +23,5 @@
#pragma once
#ifndef VERSION
# define VERSION "1.21"
# define VERSION "2.1"
#endif

View File

@@ -41,6 +41,7 @@
#include "tools.h"
#include "logging.h"
#include "threading.h"
#include "xioctl.h"
#include "picture.h"
@@ -120,6 +121,7 @@ struct device_t *device_init(void) {
dev->standard = V4L2_STD_UNKNOWN;
dev->n_buffers = cores_available + 1;
dev->n_workers = min_u(cores_available, dev->n_buffers);
dev->min_frame_size = 128;
dev->timeout = 1;
dev->error_delay = 1;
dev->io_method = V4L2_MEMORY_MMAP;
@@ -208,7 +210,7 @@ void device_close(struct device_t *dev) {
}
if (dev->run->hw_buffers) {
LOG_DEBUG("Releasing HW buffers ...");
LOG_DEBUG("Releasing device buffers ...");
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
@@ -223,6 +225,7 @@ void device_close(struct device_t *dev) {
free(HW_BUFFER(data));
}
}
A_MUTEX_DESTROY(&HW_BUFFER(grabbed_mutex));
# undef HW_BUFFER
}
@@ -300,31 +303,57 @@ int device_grab_buffer(struct device_t *dev) {
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = dev->io_method;
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
LOG_DEBUG("Grabbing device buffer ...");
if (xioctl(dev->run->fd, VIDIOC_DQBUF, &buf_info) < 0) {
LOG_PERROR("Unable to dequeue buffer");
LOG_PERROR("Unable to grab device buffer");
return -1;
}
LOG_DEBUG("Got a new frame in buffer: index=%u, bytesused=%u", buf_info.index, buf_info.bytesused);
LOG_DEBUG("Grabbed new frame in device buffer: index=%u, bytesused=%u",
buf_info.index, buf_info.bytesused);
if (buf_info.index >= dev->run->n_buffers) {
LOG_ERROR("Got invalid buffer: index=%u, nbuffers=%u", buf_info.index, dev->run->n_buffers);
LOG_ERROR("V4L2 error: grabbed invalid device buffer: index=%u, nbuffers=%u",
buf_info.index, dev->run->n_buffers);
return -1;
}
dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused;
memcpy(&dev->run->hw_buffers[buf_info.index].buf_info, &buf_info, sizeof(struct v4l2_buffer));
# define HW_BUFFER(_next) dev->run->hw_buffers[buf_info.index]._next
A_MUTEX_LOCK(&HW_BUFFER(grabbed_mutex));
if (HW_BUFFER(grabbed)) {
LOG_ERROR("V4L2 error: grabbed device buffer is already used: index=%u, bytesused=%u",
buf_info.index, buf_info.bytesused);
A_MUTEX_UNLOCK(&HW_BUFFER(grabbed_mutex));
return -1;
}
HW_BUFFER(grabbed) = true;
A_MUTEX_UNLOCK(&HW_BUFFER(grabbed_mutex));
HW_BUFFER(used) = buf_info.bytesused;
memcpy(&HW_BUFFER(buf_info), &buf_info, sizeof(struct v4l2_buffer));
dev->run->pictures[buf_info.index]->grab_ts = get_now_monotonic();
# undef HW_BUFFER
return buf_info.index;
}
int device_release_buffer(struct device_t *dev, unsigned index) {
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_QBUF, &dev->run->hw_buffers[index].buf_info) < 0) {
LOG_PERROR("Unable to requeue buffer");
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
LOG_DEBUG("Releasing device buffer index=%u ...", index);
A_MUTEX_LOCK(&HW_BUFFER(grabbed_mutex));
if (xioctl(dev->run->fd, VIDIOC_QBUF, &HW_BUFFER(buf_info)) < 0) {
LOG_PERROR("Unable to release device buffer index=%u", index);
A_MUTEX_UNLOCK(&HW_BUFFER(grabbed_mutex));
return -1;
}
dev->run->hw_buffers[index].used = 0;
HW_BUFFER(grabbed) = false;
A_MUTEX_UNLOCK(&HW_BUFFER(grabbed_mutex));
HW_BUFFER(used) = 0;
# undef HW_BUFFER
return 0;
}
@@ -500,6 +529,8 @@ static int _device_open_format(struct device_t *dev) {
static void _device_open_hw_fps(struct device_t *dev) {
struct v4l2_streamparm setfps;
dev->run->hw_fps = 0;
MEMSET_ZERO(setfps);
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
@@ -530,10 +561,21 @@ static void _device_open_hw_fps(struct device_t *dev) {
return;
}
if (dev->desired_fps != SETFPS_TPF(denominator)) {
LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, SETFPS_TPF(denominator));
if (SETFPS_TPF(numerator) != 1) {
LOG_ERROR("Invalid HW FPS numerator: %u != 1", SETFPS_TPF(numerator));
return;
}
if (SETFPS_TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай
LOG_ERROR("Invalid HW FPS denominator: 0");
return;
}
dev->run->hw_fps = SETFPS_TPF(denominator);
if (dev->desired_fps != dev->run->hw_fps) {
LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, dev->run->hw_fps);
} else {
LOG_INFO("Using HW FPS: %u", dev->desired_fps);
LOG_INFO("Using HW FPS: %u", dev->run->hw_fps);
}
# undef SETFPS_TPF
@@ -567,10 +609,10 @@ static int _device_open_io_method_mmap(struct device_t *dev) {
LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
LOG_INFO("Requested %u HW buffers, got %u", dev->n_buffers, req.count);
LOG_INFO("Requested %u device buffers, got %u", dev->n_buffers, req.count);
}
LOG_DEBUG("Allocating HW buffers ...");
LOG_DEBUG("Allocating device buffers ...");
A_CALLOC(dev->run->hw_buffers, req.count);
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
@@ -589,6 +631,8 @@ static int _device_open_io_method_mmap(struct device_t *dev) {
# define HW_BUFFER(_next) dev->run->hw_buffers[dev->run->n_buffers]._next
A_MUTEX_INIT(&HW_BUFFER(grabbed_mutex));
LOG_DEBUG("Mapping device buffer %u ...", dev->run->n_buffers);
HW_BUFFER(data) = mmap(NULL, buf_info.length, PROT_READ|PROT_WRITE, MAP_SHARED, dev->run->fd, buf_info.m.offset);
if (HW_BUFFER(data) == MAP_FAILED) {
@@ -622,10 +666,10 @@ static int _device_open_io_method_userptr(struct device_t *dev) {
LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
LOG_INFO("Requested %u HW buffers, got %u", dev->n_buffers, req.count);
LOG_INFO("Requested %u device buffers, got %u", dev->n_buffers, req.count);
}
LOG_DEBUG("Allocating HW buffers ...");
LOG_DEBUG("Allocating device buffers ...");
A_CALLOC(dev->run->hw_buffers, req.count);
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
@@ -736,6 +780,9 @@ static void _device_apply_controls(struct device_t *dev) {
CONTROL_MANUAL_CID ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
CONTROL_AUTO_CID (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
CONTROL_AUTO_CID (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
CONTROL_MANUAL_CID ( V4L2_CID_COLORFX, color_effect);
CONTROL_MANUAL_CID ( V4L2_CID_VFLIP, flip_vertical);
CONTROL_MANUAL_CID ( V4L2_CID_HFLIP, flip_horizontal);
# undef CONTROL_AUTO_CID
# undef CONTROL_MANUAL_CID

View File

@@ -25,6 +25,7 @@
#include <stddef.h>
#include <stdbool.h>
#include <pthread.h>
#include <linux/videodev2.h>
#include "picture.h"
@@ -53,6 +54,9 @@ struct hw_buffer_t {
size_t used;
size_t allocated;
struct v4l2_buffer buf_info;
pthread_mutex_t grabbed_mutex;
bool grabbed;
};
struct device_runtime_t {
@@ -60,6 +64,7 @@ struct device_runtime_t {
unsigned width;
unsigned height;
unsigned format;
unsigned hw_fps;
size_t raw_size;
unsigned n_buffers;
unsigned n_workers;
@@ -90,6 +95,9 @@ struct controls_t {
struct control_t backlight_compensation;
struct control_t white_balance;
struct control_t gain;
struct control_t color_effect;
struct control_t flip_vertical;
struct control_t flip_horizontal;
};
struct device_t {

View File

@@ -201,16 +201,20 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
#pragma GCC diagnostic pop
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
assert(dev->run->hw_buffers[buf_index].used > 0);
dev->run->pictures[buf_index]->encode_begin_ts = get_now_monotonic();
if (encoder->run->type == ENCODER_TYPE_CPU) {
LOG_VERBOSE("Compressing buffer %u using CPU", buf_index);
cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality);
} else if (encoder->run->type == ENCODER_TYPE_HW) {
LOG_VERBOSE("Compressing buffer %u using HW (just copying)", buf_index);
hw_encoder_compress_buffer(dev, buf_index);
}
# ifdef WITH_OMX
else if (encoder->run->type == ENCODER_TYPE_OMX) {
LOG_VERBOSE("Compressing buffer %u using OMX", buf_index);
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
goto error;
}

View File

@@ -102,11 +102,11 @@ struct omx_encoder_t *omx_encoder_init(void) {
LOG_INFO("Initializing OMX encoder ...");
if (vcos_semaphore_create(&omx->handler_lock, "handler_lock", 0) != VCOS_SUCCESS) {
if (vcos_semaphore_create(&omx->handler_sem, "handler_sem", 0) != VCOS_SUCCESS) {
LOG_ERROR("Can't create VCOS semaphore");
goto error;
}
omx->i_handler_lock = true;
omx->i_handler_sem = true;
if (_omx_init_component(omx) < 0) {
goto error;
@@ -132,8 +132,8 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
_omx_encoder_clear_ports(omx);
component_set_state(&omx->encoder, OMX_StateLoaded);
if (omx->i_handler_lock) {
vcos_semaphore_delete(&omx->handler_lock);
if (omx->i_handler_sem) {
vcos_semaphore_delete(&omx->handler_sem);
}
if (omx->i_encoder) {
@@ -180,6 +180,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
# define OUT(_next) omx->output_buffer->_next
OMX_ERRORTYPE error;
VCOS_STATUS_T sem_status;
size_t slice_size = (IN(nAllocLen) < HW_BUFFER(used) ? IN(nAllocLen) : HW_BUFFER(used));
size_t pos = 0;
@@ -236,7 +237,13 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
}
}
vcos_semaphore_wait(&omx->handler_lock);
// vcos_semaphore_wait(&omx->handler_sem);
switch (sem_status = vcos_semaphore_wait_timeout(&omx->handler_sem, 3000)) {
case VCOS_SUCCESS: break;
case VCOS_EAGAIN: LOG_ERROR("Can't wait VCOS semaphore: EAGAIN (timeout)"); return -1;
case VCOS_EINVAL: LOG_ERROR("Can't wait VCOS semaphore: EINTVAL"); return -1;
default: LOG_ERROR("Can't wait VCOS semaphore: %d", sem_status); return -1;
}
}
# undef OUT
@@ -426,32 +433,32 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
static int _omx_encoder_clear_ports(struct omx_encoder_t *omx) {
OMX_ERRORTYPE error;
int retcode = 0;
int retval = 0;
if (omx->i_output_port_enabled) {
retcode -= component_disable_port(&omx->encoder, _OUTPUT_PORT);
retval -= component_disable_port(&omx->encoder, _OUTPUT_PORT);
omx->i_output_port_enabled = false;
}
if (omx->i_input_port_enabled) {
retcode -= component_disable_port(&omx->encoder, _INPUT_PORT);
retval -= component_disable_port(&omx->encoder, _INPUT_PORT);
omx->i_input_port_enabled = false;
}
if (omx->input_buffer) {
if ((error = OMX_FreeBuffer(omx->encoder, _INPUT_PORT, omx->input_buffer)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't free OMX JPEG input buffer");
// retcode -= 1;
// retval -= 1;
}
omx->input_buffer = NULL;
}
if (omx->output_buffer) {
if ((error = OMX_FreeBuffer(omx->encoder, _OUTPUT_PORT, omx->output_buffer)) != OMX_ErrorNone) {
LOG_ERROR_OMX(error, "Can't free OMX JPEG output buffer");
// retcode -= 1;
// retval -= 1;
}
omx->output_buffer = NULL;
}
return retcode;
return retval;
}
static OMX_ERRORTYPE _omx_event_handler(
@@ -466,7 +473,7 @@ static OMX_ERRORTYPE _omx_event_handler(
if (event == OMX_EventError) {
LOG_ERROR_OMX((OMX_ERRORTYPE)data1, "OMX error event received");
omx->failed = true;
vcos_semaphore_post(&omx->handler_lock);
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
}
return OMX_ErrorNone;
}
@@ -481,7 +488,7 @@ static OMX_ERRORTYPE _omx_input_required_handler(
struct omx_encoder_t *omx = (struct omx_encoder_t *)v_omx;
omx->input_required = true;
vcos_semaphore_post(&omx->handler_lock);
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
return OMX_ErrorNone;
}
@@ -495,6 +502,6 @@ static OMX_ERRORTYPE _omx_output_available_handler(
struct omx_encoder_t *omx = (struct omx_encoder_t *)v_omx;
omx->output_available = true;
vcos_semaphore_post(&omx->handler_lock);
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
return OMX_ErrorNone;
}

View File

@@ -42,9 +42,9 @@ struct omx_encoder_t {
bool input_required;
bool output_available;
bool failed;
VCOS_SEMAPHORE_T handler_lock;
VCOS_SEMAPHORE_T handler_sem;
bool i_handler_lock;
bool i_handler_sem;
bool i_encoder;
bool i_input_port_enabled;
bool i_output_port_enabled;

View File

@@ -1,96 +0,0 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdlib.h>
#include <wiringPi.h>
#include "tools.h"
#include "logging.h"
int gpio_pin_prog_running;
int gpio_pin_stream_online;
int gpio_pin_has_http_clients;
int gpio_pin_workers_busy_at;
#define GPIO_INIT { \
gpio_pin_prog_running = -1; \
gpio_pin_stream_online = -1; \
gpio_pin_has_http_clients = -1; \
gpio_pin_workers_busy_at = -1; \
}
#define GPIO_INIT_PIN(_role, _offset) _gpio_init_pin(#_role, gpio_pin_##_role, _offset)
INLINE void _gpio_init_pin(const char *role, int base, unsigned offset) {
if (base >= 0) {
pinMode(base + offset, OUTPUT);
if (offset == 0) {
LOG_INFO("GPIO: Using pin %d as %s", base, role);
} else {
LOG_INFO("GPIO: Using pin %d+%u as %s", base, offset, role);
}
}
}
#define GPIO_INIT_PINOUT { \
if ( \
gpio_pin_prog_running >= 0 \
|| gpio_pin_stream_online >= 0 \
|| gpio_pin_has_http_clients >= 0 \
|| gpio_pin_workers_busy_at >= 0 \
) { \
LOG_INFO("GPIO: Using wiringPi"); \
if (wiringPiSetupGpio() < 0) { \
LOG_PERROR("GPIO: Can't initialize wiringPi"); \
exit(1); \
} else { \
GPIO_INIT_PIN(prog_running, 0); \
GPIO_INIT_PIN(stream_online, 0); \
GPIO_INIT_PIN(has_http_clients, 0); \
GPIO_INIT_PIN(workers_busy_at, 0); \
} \
} \
}
#define GPIO_SET_STATE(_role, _offset, _state) _gpio_set_state(#_role, gpio_pin_##_role, _offset, _state)
INLINE void _gpio_set_state(const char *role, int base, unsigned offset, int state) {
if (base >= 0) {
if (offset == 0) {
LOG_DEBUG("GPIO: Writing %d to pin %d (%s)", state, base, role);
} else {
LOG_DEBUG("GPIO: Writing %d to pin %d+%u (%s)", state, base, offset, role);
}
digitalWrite(base + offset, state);
}
}
#define GPIO_SET_LOW(_role) GPIO_SET_STATE(_role, 0, LOW)
#define GPIO_SET_HIGH(_role) GPIO_SET_STATE(_role, 0, HIGH)
#define GPIO_SET_LOW_AT(_role, _offset) GPIO_SET_STATE(_role, _offset, LOW)
#define GPIO_SET_HIGH_AT(_role, _offset) GPIO_SET_STATE(_role, _offset, HIGH)

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

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

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

@@ -0,0 +1,83 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <stdbool.h>
#include <pthread.h>
#include <gpiod.h>
#include "../tools.h"
#include "../logging.h"
struct gpio_output_t {
int pin;
const char *role;
char *consumer;
struct gpiod_line *line;
bool state;
};
struct gpio_t {
char *path;
char *consumer_prefix;
struct gpio_output_t prog_running;
struct gpio_output_t stream_online;
struct gpio_output_t has_http_clients;
pthread_mutex_t mutex;
struct gpiod_chip *chip;
};
extern struct gpio_t gpio;
void gpio_init(void);
void gpio_destroy(void);
int gpio_inner_set(struct gpio_output_t *output, bool state);
#define SET_STATE(_output, _state) { \
if (_output.line && _output.state != _state) { \
if (!gpio_inner_set(&_output, _state)) { \
_output.state = _state; \
} \
} \
}
INLINE void gpio_set_prog_running(bool state) {
SET_STATE(gpio.prog_running, state);
}
INLINE void gpio_set_stream_online(bool state) {
SET_STATE(gpio.stream_online, state);
}
INLINE void gpio_set_has_http_clients(bool state) {
SET_STATE(gpio.has_http_clients, state);
}
#undef SET_STATE

View File

@@ -4,6 +4,7 @@
<head>
<meta charset="utf-8" />
<title>uStreamer</title>
<style>body {font-family: monospace;}</style>
</head>
<body>
@@ -11,46 +12,56 @@
<hr>
<ul>
<li>
<a href="/state"><b><samp>/state</samp></b></a><br>
Get JSON structure with state of the server.
<a href="/state"><b>/state</b></a><br>
Get JSON structure with the state of the server.
</li>
<br>
<li>
<a href="/snapshot"><b><samp>/snapshot</samp></b></a><br>
<a href="/snapshot"><b>/snapshot</b></a><br>
Get a current actual image from the server.
</li>
<br>
<li>
<a href="/stream"><b><samp>/stream</samp></b></a><br>
<a href="/stream"><b>/stream</b></a><br>
Get a live stream. Query params:<br>
<br>
<ul>
<li>
<b><samp>key=abc123</samp></b><br>
User-defined key, which is part of cookie <samp>stream_client</samp>, which allows<br>
the stream client to determine its identifier and view statistics using <a href="/state"><samp>/state</samp></a>.
<b>key=abc123</b><br>
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br>
the stream client to determine its identifier and view statistics using <a href="/state">/state</a>.
</li>
<br>
<li>
<b><samp>extra_headers=1</samp></b><br>
Add <samp>X-UStreamer-*</samp> headers to /stream handle (like on <a href="/snapshot"><samp>/snapshot</samp></a>).
<b>extra_headers=1</b><br>
Add <i>X-UStreamer-*</i> headers to the <a href="/stream">/stream</a> handle
(like with the <a href="/snapshot">/snapshot</a>).
</li>
<br>
<li>
<b><samp>advance_headers=1</samp></b><br>
Enable workaround for Chromium/Blink
<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=527446">Bug #527446</a>.
<b>advance_headers=1</b><br>
Enable workaround for the Chromium/Blink bug
<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=527446">#527446</a>.
</li>
<br>
<li>
<b><samp>dual_final_frames=1</samp></b><br>
Enable workaround for Safari/WebKit bug when using option <samp>--drop-same-frames</samp>.<br>
<b>dual_final_frames=1</b><br>
Enable workaround for the Safari/WebKit bug when using option <i>--drop-same-frames</i>.<br>
Without this option, when the frame series is completed, WebKit-based browsers<br>
renders the last frame with a delay.
</li>
</ul>
</li>
<br>
<li>
The mjpg-streamer compatibility layer:<br>
<br>
<ul>
<li><a href="/?action=snapshot">/?action=snapshot</a> as alias to the <a href="/snapshot">/snapshot</a>.</li>
<br>
<li><a href="/?action=stream">/?action=stream</a> as alias to the <a href="/stream">/stream</a>.</li>
</ul>
</li>
</ul>
<br>
<hr>

View File

@@ -32,6 +32,7 @@ const char HTML_INDEX_PAGE[] = " \
<head> \
<meta charset=\"utf-8\" /> \
<title>uStreamer</title> \
<style>body {font-family: monospace;}</style> \
</head> \
\
<body> \
@@ -39,46 +40,56 @@ const char HTML_INDEX_PAGE[] = " \
<hr> \
<ul> \
<li> \
<a href=\"/state\"><b><samp>/state</samp></b></a><br> \
Get JSON structure with state of the server. \
<a href=\"/state\"><b>/state</b></a><br> \
Get JSON structure with the state of the server. \
</li> \
<br> \
<li> \
<a href=\"/snapshot\"><b><samp>/snapshot</samp></b></a><br> \
<a href=\"/snapshot\"><b>/snapshot</b></a><br> \
Get a current actual image from the server. \
</li> \
<br> \
<li> \
<a href=\"/stream\"><b><samp>/stream</samp></b></a><br> \
<a href=\"/stream\"><b>/stream</b></a><br> \
Get a live stream. Query params:<br> \
<br> \
<ul> \
<li> \
<b><samp>key=abc123</samp></b><br> \
User-defined key, which is part of cookie <samp>stream_client</samp>, which allows<br> \
the stream client to determine its identifier and view statistics using <a href=\"/state\"><samp>/state</samp></a>. \
<b>key=abc123</b><br> \
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br> \
the stream client to determine its identifier and view statistics using <a href=\"/state\">/state</a>. \
</li> \
<br> \
<li> \
<b><samp>extra_headers=1</samp></b><br> \
Add <samp>X-UStreamer-*</samp> headers to /stream handle (like on <a href=\"/snapshot\"><samp>/snapshot</samp></a>). \
<b>extra_headers=1</b><br> \
Add <i>X-UStreamer-*</i> headers to the <a href=\"/stream\">/stream</a> handle \
(like with the <a href=\"/snapshot\">/snapshot</a>). \
</li> \
<br> \
<li> \
<b><samp>advance_headers=1</samp></b><br> \
Enable workaround for Chromium/Blink \
<a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=527446\">Bug #527446</a>. \
<b>advance_headers=1</b><br> \
Enable workaround for the Chromium/Blink bug \
<a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=527446\">#527446</a>. \
</li> \
<br> \
<li> \
<b><samp>dual_final_frames=1</samp></b><br> \
Enable workaround for Safari/WebKit bug when using option <samp>--drop-same-frames</samp>.<br> \
<b>dual_final_frames=1</b><br> \
Enable workaround for the Safari/WebKit bug when using option <i>--drop-same-frames</i>.<br> \
Without this option, when the frame series is completed, WebKit-based browsers<br> \
renders the last frame with a delay. \
</li> \
</ul> \
</li> \
<br> \
<li> \
The mjpg-streamer compatibility layer:<br> \
<br> \
<ul> \
<li><a href=\"/?action=snapshot\">/?action=snapshot</a> as alias to the <a href=\"/snapshot\">/snapshot</a>.</li> \
<br> \
<li><a href=\"/?action=stream\">/?action=stream</a> as alias to the <a href=\"/stream\">/stream</a>.</li> \
</ul> \
</li> \
</ul> \
<br> \
<hr> \

View File

@@ -31,10 +31,12 @@
#include <fcntl.h>
#include <assert.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <event2/event.h>
#include <event2/thread.h>
@@ -57,7 +59,7 @@
#include "../encoder.h"
#include "../stream.h"
#ifdef WITH_GPIO
# include "../gpio.h"
# include "../gpio/gpio.h"
#endif
#include "unix.h"
@@ -110,6 +112,7 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
server->user = "";
server->passwd = "";
server->static_path = "";
server->allow_origin = "";
server->timeout = 10;
server->last_as_blank = -1;
server->run = run;
@@ -289,14 +292,27 @@ static int _http_preprocess_request(struct evhttp_request *request, struct http_
static void _http_callback_root(struct evhttp_request *request, void *v_server) {
struct http_server_t *server = (struct http_server_t *)v_server;
struct evbuffer *buf;
struct evkeyvalq params; // For mjpg-streamer compatibility
const char *action; // Ditto
PREPROCESS_REQUEST;
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
ADD_HEADER("Content-Type", "text/html");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
evhttp_parse_query(evhttp_request_get_uri(request), &params);
action = evhttp_find_header(&params, "action");
if (action && !strcmp(action, "snapshot")) {
_http_callback_snapshot(request, v_server);
} else if (action && !strcmp(action, "stream")) {
_http_callback_stream(request, v_server);
} else {
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
ADD_HEADER("Content-Type", "text/html");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
}
evhttp_clear_headers(&params);
}
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
@@ -441,7 +457,9 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
assert((buf = evbuffer_new()));
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture->data), EXPOSED(picture->used)));
ADD_HEADER("Access-Control-Allow-Origin:", "*");
if (server->allow_origin[0] != '\0') {
ADD_HEADER("Access-Control-Allow-Origin", server->allow_origin);
}
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
ADD_HEADER("Pragma", "no-cache");
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
@@ -535,7 +553,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
}
# ifdef WITH_GPIO
GPIO_SET_HIGH(has_http_clients);
gpio_set_has_http_clients(true);
# endif
}
@@ -605,9 +623,11 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real()))
if (client->need_initial) {
assert(evbuffer_add_printf(buf, "HTTP/1.0 200 OK" RN));
if (client->server->allow_origin[0] != '\0') {
assert(evbuffer_add_printf(buf, "Access-Control-Allow-Origin: %s" RN, client->server->allow_origin));
}
assert(evbuffer_add_printf(buf,
"HTTP/1.0 200 OK" RN
"Access-Control-Allow-Origin: *" RN
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
"Pragma: no-cache" RN
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
@@ -709,7 +729,7 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
}
# ifdef WITH_GPIO
GPIO_SET_LOW(has_http_clients);
gpio_set_has_http_clients(false);
# endif
}
@@ -742,6 +762,7 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
struct evhttp_connection *conn;
struct bufferevent *buf_event;
long long now;
bool has_clients = false;
bool queued = false;
for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
@@ -772,6 +793,8 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
} else if (stream_updated) { // Для dual
client->updated_prev = false;
}
has_clients = true;
}
}
@@ -785,6 +808,8 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
queued_fps_second = now;
}
queued_fps_accum += 1;
} else if (!has_clients) {
server->run->exposed->queued_fps = 0;
}
}

View File

@@ -97,6 +97,7 @@ struct http_server_t {
char *user;
char *passwd;
char *static_path;
char *allow_origin;
char *blank_path;
int last_as_blank;

View File

@@ -31,6 +31,7 @@
#endif
#include <stdio.h>
#include <stdbool.h>
#include <signal.h>
#include <pthread.h>
@@ -44,7 +45,7 @@
#include "stream.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio.h"
# include "gpio/gpio.h"
#endif
@@ -114,10 +115,6 @@ int main(int argc, char *argv[]) {
A_THREAD_RENAME("main");
options = options_init(argc, argv);
# ifdef WITH_GPIO
GPIO_INIT;
# endif
dev = device_init();
encoder = encoder_init();
stream = stream_init(dev, encoder);
@@ -125,7 +122,7 @@ int main(int argc, char *argv[]) {
if ((exit_code = options_parse(options, dev, encoder, server)) == 0) {
# ifdef WITH_GPIO
GPIO_INIT_PINOUT;
gpio_init();
# endif
_install_signal_handlers();
@@ -140,7 +137,7 @@ int main(int argc, char *argv[]) {
if ((exit_code = http_server_listen(server)) == 0) {
# ifdef WITH_GPIO
GPIO_SET_HIGH(prog_running);
gpio_set_prog_running(true);
# endif
A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
@@ -148,6 +145,11 @@ int main(int argc, char *argv[]) {
A_THREAD_JOIN(server_loop_tid);
A_THREAD_JOIN(stream_loop_tid);
}
# ifdef WITH_GPIO
gpio_set_prog_running(false);
gpio_destroy();
# endif
}
http_server_destroy(server);
@@ -155,10 +157,6 @@ int main(int argc, char *argv[]) {
encoder_destroy(encoder);
device_destroy(dev);
# ifdef WITH_GPIO
GPIO_SET_LOW(prog_running);
# endif
options_destroy(options);
if (exit_code == 0) {
LOG_INFO("Bye-bye");

View File

@@ -40,7 +40,7 @@
#include "encoder.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio.h"
# include "gpio/gpio.h"
#endif
@@ -92,18 +92,23 @@ enum _OPT_VALUES {
_O_BACKLIGHT_COMPENSATION,
_O_WHITE_BALANCE,
_O_GAIN,
_O_COLOR_EFFECT,
_O_FLIP_VERTICAL,
_O_FLIP_HORIZONTAL,
_O_USER,
_O_PASSWD,
_O_STATIC,
_O_ALLOW_ORIGIN,
_O_TCP_NODELAY,
_O_SERVER_TIMEOUT,
#ifdef WITH_GPIO
_O_GPIO_DEVICE,
_O_GPIO_CONSUMER_PREFIX,
_O_GPIO_PROG_RUNNING,
_O_GPIO_STREAM_ONLINE,
_O_GPIO_HAS_HTTP_CLIENTS,
_O_GPIO_WORKERS_BUSY_AT,
#endif
#ifdef HAS_PDEATHSIG
@@ -155,6 +160,9 @@ static const struct option _LONG_OPTS[] = {
{"backlight-compensation", required_argument, NULL, _O_BACKLIGHT_COMPENSATION},
{"white-balance", required_argument, NULL, _O_WHITE_BALANCE},
{"gain", required_argument, NULL, _O_GAIN},
{"color-effect", required_argument, NULL, _O_COLOR_EFFECT},
{"flip-vertical", required_argument, NULL, _O_FLIP_VERTICAL},
{"flip-horizontal", required_argument, NULL, _O_FLIP_HORIZONTAL},
{"host", required_argument, NULL, _O_HOST},
{"port", required_argument, NULL, _O_PORT},
@@ -168,15 +176,17 @@ static const struct option _LONG_OPTS[] = {
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
{"allow-origin", required_argument, NULL, _O_ALLOW_ORIGIN},
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
#ifdef WITH_GPIO
{"gpio-device", required_argument, NULL, _O_GPIO_DEVICE},
{"gpio-consumer-prefix", required_argument, NULL, _O_GPIO_CONSUMER_PREFIX},
{"gpio-prog-running", required_argument, NULL, _O_GPIO_PROG_RUNNING},
{"gpio-stream-online", required_argument, NULL, _O_GPIO_STREAM_ONLINE},
{"gpio-has-http-clients", required_argument, NULL, _O_GPIO_HAS_HTTP_CLIENTS},
{"gpio-workers-busy-at", required_argument, NULL, _O_GPIO_WORKERS_BUSY_AT},
#endif
#ifdef HAS_PDEATHSIG
@@ -333,7 +343,7 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN, STANDARDS_STR);
case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, device_parse_io_method, IO_METHOD_UNKNOWN, IO_METHODS_STR);
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 0, 8192, 0);
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 1, 8192, 0);
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true);
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_buffers, 1, 32, 0);
@@ -360,6 +370,9 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
OPT_CTL_DEFAULT_NOBREAK(backlight_compensation);
OPT_CTL_DEFAULT_NOBREAK(white_balance);
OPT_CTL_DEFAULT_NOBREAK(gain);
OPT_CTL_DEFAULT_NOBREAK(color_effect);
OPT_CTL_DEFAULT_NOBREAK(flip_vertical);
OPT_CTL_DEFAULT_NOBREAK(flip_horizontal);
break;
case _O_BRIGHTNESS: OPT_CTL_AUTO(brightness);
case _O_CONTRAST: OPT_CTL_MANUAL(contrast);
@@ -370,6 +383,9 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
case _O_BACKLIGHT_COMPENSATION: OPT_CTL_MANUAL(backlight_compensation);
case _O_WHITE_BALANCE: OPT_CTL_AUTO(white_balance);
case _O_GAIN: OPT_CTL_AUTO(gain);
case _O_COLOR_EFFECT: OPT_CTL_MANUAL(color_effect);
case _O_FLIP_VERTICAL: OPT_CTL_MANUAL(flip_vertical);
case _O_FLIP_HORIZONTAL: OPT_CTL_MANUAL(flip_horizontal);
case _O_HOST: OPT_SET(server->host, optarg);
case _O_PORT: OPT_NUMBER("--port", server->port, 1, 65535, 0);
@@ -384,14 +400,16 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0);
case _O_SLOWDOWN: OPT_SET(server->slowdown, true);
case _O_FAKE_RESOLUTION: OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
case _O_ALLOW_ORIGIN: OPT_SET(server->allow_origin, optarg);
case _O_TCP_NODELAY: OPT_SET(server->tcp_nodelay, true);
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
# ifdef WITH_GPIO
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio_pin_prog_running, 0, 256, 0);
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", gpio_pin_stream_online, 0, 256, 0);
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", gpio_pin_has_http_clients, 0, 256, 0);
case _O_GPIO_WORKERS_BUSY_AT: OPT_NUMBER("--gpio-workers-busy-at", gpio_pin_workers_busy_at, 0, 256, 0);
case _O_GPIO_DEVICE: OPT_SET(gpio.path, optarg);
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(gpio.consumer_prefix, optarg);
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio.prog_running.pin, 0, 256, 0);
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", gpio.stream_online.pin, 0, 256, 0);
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", gpio.has_http_clients.pin, 0, 256, 0);
# endif
# ifdef HAS_PDEATHSIG
@@ -559,7 +577,7 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf(" Available: %s; default: MMAP\n\n", IO_METHODS_STR);
printf(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n\n");
printf(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device\n");
printf(" produces small-sized garbage frames. Default: disabled.\n\n");
printf(" produces small-sized garbage frames. Default: %zu bytes.\n\n", dev->min_frame_size);
printf(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. Default: disabled.\n\n");
printf(" -t|--dv-timings ───────────────────── Enable DV timings querying and events processing\n");
printf(" to automatic resolution change. Default: disabled.\n\n");
@@ -600,6 +618,9 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf(" --backlight-compensation <N|default> ─ Set backlight compensation. Default: no change.\n\n");
printf(" --white-balance <N|auto|default> ───── Set white balance. Default: no change.\n\n");
printf(" --gain <N|auto|default> ────────────── Set gain. Default: no change.\n\n");
printf(" --color-effect <N|default> ─────────── Set color effect. Default: no change.\n\n");
printf(" --flip-vertical <1|0|default> ──────── Set vertical flip. Default: no change.\n\n");
printf(" --flip-horizontal <1|0|default> ────── Set horizontal flip. Default: no change.\n\n");
printf(" Hint: use v4l2-ctl --list-ctrls-menus to query available controls of the device.\n\n");
printf("HTTP server options:\n");
printf("════════════════════\n");
@@ -627,15 +648,16 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf(" -R|--fake-resolution <WxH> ─ Override image resolution for the /state. Default: disabled.\n\n");
printf(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Ignored for --unix.\n");
printf(" Default: disabled.\n\n");
printf(" --allow-origin <str> ─────── Set Access-Control-Allow-Origin header. Default: disabled.\n\n");
printf(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n\n", server->timeout);
#ifdef WITH_GPIO
printf("GPIO options:\n");
printf("═════════════\n");
printf(" --gpio-device </dev/path> ───── Path to GPIO character device. Default: %s.\n\n", gpio.path);
printf(" --gpio-consumer-prefix <str> ── Consumer prefix for GPIO outputs. Default: %s.\n\n", gpio.consumer_prefix);
printf(" --gpio-prog-running <pin> ───── Set 1 on GPIO pin while uStreamer is running. Default: disabled.\n\n");
printf(" --gpio-stream-online <pin> ──── Set 1 while streaming. Default: disabled\n\n");
printf(" --gpio-has-http-clients <pin> ─ Set 1 while stream has at least one client. Default: disabled.\n\n");
printf(" --gpio-workers-busy-at <pin> ── Set 1 on (pin + N) while worker with number N has a job.\n");
printf(" The worker's numbering starts from 0. Default: disabled\n\n");
#endif
#if (defined(HAS_PDEATHSIG) || defined(WITH_SETPROCTITLE))
printf("Process options:\n");
@@ -665,5 +687,5 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf("═════════════\n");
printf(" -h|--help ─────── Print this text and exit.\n\n");
printf(" -v|--version ──── Print version and exit.\n\n");
printf(" --features ────── Print list of supporeted features.\n\n");
printf(" --features ────── Print list of supported features.\n\n");
}

View File

@@ -38,7 +38,7 @@
#include "device.h"
#include "encoder.h"
#ifdef WITH_GPIO
# include "gpio.h"
# include "gpio/gpio.h"
#endif
@@ -88,7 +88,7 @@ struct _workers_pool_t {
static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream);
static struct _workers_pool_t *_stream_init(struct stream_t *stream);
static struct _workers_pool_t *_stream_init_one(struct stream_t *stream);
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index, unsigned captured_fps);
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream);
@@ -185,7 +185,7 @@ void stream_loop(struct stream_t *stream) {
} else if (selected == 0) {
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
gpio_set_stream_online(false);
# endif
if (stream->dev->persistent) {
@@ -207,7 +207,7 @@ void stream_loop(struct stream_t *stream) {
LOG_DEBUG("Frame is ready");
# ifdef WITH_GPIO
GPIO_SET_HIGH(stream_online);
gpio_set_stream_online(true);
# endif
int buf_index;
@@ -288,7 +288,7 @@ void stream_loop(struct stream_t *stream) {
device_close(stream->dev);
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
gpio_set_stream_online(false);
# endif
}
}
@@ -303,13 +303,26 @@ void stream_switch_slowdown(struct stream_t *stream, bool slowdown) {
static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream) {
struct _workers_pool_t *pool = NULL;
int access_error = 0;
LOG_DEBUG("%s: stream->proc->stop=%d", __FUNCTION__, atomic_load(&stream->proc->stop));
while (!atomic_load(&stream->proc->stop)) {
SEP_INFO('=');
if (access(stream->dev->path, R_OK|W_OK) < 0) {
if (access_error != errno) {
SEP_INFO('=');
LOG_PERROR("Can't access device");
LOG_INFO("Waiting for the device access ...");
access_error = errno;
}
sleep(stream->dev->error_delay);
continue;
} else {
SEP_INFO('=');
access_error = 0;
}
if ((pool = _stream_init(stream)) == NULL) {
if ((pool = _stream_init_one(stream)) == NULL) {
LOG_INFO("Sleeping %u seconds before new stream init ...", stream->dev->error_delay);
sleep(stream->dev->error_delay);
} else {
@@ -319,7 +332,7 @@ static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream) {
return pool;
}
static struct _workers_pool_t *_stream_init(struct stream_t *stream) {
static struct _workers_pool_t *_stream_init_one(struct stream_t *stream) {
if (device_open(stream->dev) < 0) {
goto error;
}
@@ -350,11 +363,14 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index,
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream) {
struct _workers_pool_t *pool;
LOG_INFO("Creating pool with %u workers ...", stream->dev->run->n_workers);
# define DEV(_next) stream->dev->_next
# define RUN(_next) stream->dev->run->_next
LOG_INFO("Creating pool with %u workers ...", RUN(n_workers));
A_CALLOC(pool, 1);
pool->n_workers = stream->dev->run->n_workers;
pool->n_workers = RUN(n_workers);
A_CALLOC(pool->workers, pool->n_workers);
A_MUTEX_INIT(&pool->free_workers_mutex);
@@ -362,10 +378,13 @@ static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream) {
atomic_init(&pool->workers_stop, false);
if (stream->dev->desired_fps > 0) {
pool->desired_frames_interval = (long double)1 / stream->dev->desired_fps;
if (DEV(desired_fps) > 0 && (DEV(desired_fps) < RUN(hw_fps) || RUN(hw_fps) == 0)) {
pool->desired_frames_interval = (long double)1 / DEV(desired_fps);
}
# undef RUN
# undef DEV
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
@@ -425,17 +444,9 @@ static void *_worker_thread(void *v_worker) {
A_THREAD_RENAME("worker-%u", worker->number);
LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number);
# ifdef WITH_GPIO
GPIO_INIT_PIN(workers_busy_at, worker->number);
# endif
while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) {
LOG_DEBUG("Worker %u waiting for a new job ...", worker->number);
# ifdef WITH_GPIO
GPIO_SET_LOW_AT(workers_busy_at, worker->number);
# endif
A_MUTEX_LOCK(&worker->has_job_mutex);
A_COND_WAIT_TRUE(atomic_load(&worker->has_job), &worker->has_job_cond, &worker->has_job_mutex);
A_MUTEX_UNLOCK(&worker->has_job_mutex);
@@ -445,27 +456,24 @@ static void *_worker_thread(void *v_worker) {
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index);
# ifdef WITH_GPIO
GPIO_SET_HIGH_AT(workers_busy_at, worker->number);
# endif
if (encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index) < 0) {
worker->job_failed = false;
}
worker->job_failed = (bool)encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index);
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
worker->job_start_ts = PICTURE(encode_begin_ts);
atomic_store(&worker->has_job, false);
if (!worker->job_failed) {
worker->job_start_ts = PICTURE(encode_begin_ts);
worker->last_comp_time = PICTURE(encode_end_ts) - worker->job_start_ts;
worker->last_comp_time = PICTURE(encode_end_ts) - worker->job_start_ts;
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%u, buffer=%u",
PICTURE(used), worker->last_comp_time, worker->number, worker->buf_index);
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%u, buffer=%u",
PICTURE(used), worker->last_comp_time, worker->number, worker->buf_index);
} else {
LOG_VERBOSE("Compression failed: worker=%u, buffer=%u", worker->number, worker->buf_index);
}
} else {
worker->job_failed = true;
atomic_store(&worker->has_job, false);
}
atomic_store(&worker->has_job, false);
# undef PICTURE
}
@@ -476,9 +484,6 @@ static void *_worker_thread(void *v_worker) {
}
LOG_DEBUG("Bye-bye (worker %u)", worker->number);
# ifdef WITH_GPIO
GPIO_SET_LOW_AT(workers_busy_at, worker->number);
# endif
return NULL;
}
@@ -556,6 +561,7 @@ static long double _workers_pool_get_fluency_delay(struct _workers_pool_t *pool,
if (pool->desired_frames_interval > 0 && min_delay > 0 && pool->desired_frames_interval > min_delay) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
// и аппаратный fps не попадает точно в желаемое значение
return pool->desired_frames_interval;
}
return min_delay;

View File

@@ -102,7 +102,23 @@ INLINE void thread_get_name(char *name) { // Always required for logging
# endif
if (retval < 0) {
#endif
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", (pid_t)syscall(SYS_gettid)) > 0);
#if defined(__linux__)
pid_t tid = syscall(SYS_gettid);
#elif defined(__FreeBSD__)
pid_t tid = syscall(SYS_thr_self);
#elif defined(__OpenBSD__)
pid_t tid = syscall(SYS_getthrid);
#elif defined(__NetBSD__)
pid_t tid = syscall(SYS__lwp_self);
#elif defined(__DragonFly__)
pid_t tid = syscall(SYS_lwp_gettid);
#else
pid_t tid = 0; // Makes cppcheck happy
# warning gettid() not implemented
#endif
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", tid) > 0);
#ifdef WITH_PTHREAD_NP
}
#endif

View File

@@ -78,7 +78,13 @@ INLINE long double get_now_monotonic(void) {
time_t sec;
long msec;
# if defined(CLOCK_MONOTONIC_RAW)
get_now(CLOCK_MONOTONIC_RAW, &sec, &msec);
# elif defined(CLOCK_MONOTONIC_FAST)
get_now(CLOCK_MONOTONIC_FAST, &sec, &msec);
# else
get_now(CLOCK_MONOTONIC, &sec, &msec);
# endif
return (long double)sec + ((long double)msec) / 1000;
}