mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 12:16:31 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bc4afca9d | ||
|
|
f43afababa | ||
|
|
1b2de09438 | ||
|
|
b0c54b18a5 | ||
|
|
f8e26d785f | ||
|
|
28563abdbc | ||
|
|
f1a869a215 | ||
|
|
9778a805ca | ||
|
|
a008dcf99d | ||
|
|
71c64e668d | ||
|
|
d9b91a1d5f | ||
|
|
d682a1c173 | ||
|
|
ba03333623 | ||
|
|
c7e6e5e006 | ||
|
|
45b1e2f285 | ||
|
|
d9bbd8a74d | ||
|
|
37179184ae | ||
|
|
fc8aba0a12 | ||
|
|
0d749eada3 | ||
|
|
da6984d531 | ||
|
|
df14031042 | ||
|
|
03975c1a85 | ||
|
|
214a924da3 | ||
|
|
9e6a9a2fd4 | ||
|
|
b498ae7e38 | ||
|
|
278645ce51 | ||
|
|
f1ee5514e3 | ||
|
|
3900728f9f | ||
|
|
3dc083d2ef | ||
|
|
653ebd6e88 | ||
|
|
a770e7675d | ||
|
|
6725083be6 | ||
|
|
0b39cadaad | ||
|
|
871b0cf132 | ||
|
|
afa888432a | ||
|
|
a42bd147ff | ||
|
|
2ad8871a54 | ||
|
|
266e210b04 |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 1.24
|
current_version = 2.2
|
||||||
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}
|
||||||
|
|||||||
5
Makefile
5
Makefile
@@ -35,8 +35,9 @@ endif
|
|||||||
|
|
||||||
|
|
||||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||||
_LIBS += -lwiringPi
|
_LIBS += -lgpiod
|
||||||
override CFLAGS += -DWITH_GPIO
|
override CFLAGS += -DWITH_GPIO
|
||||||
|
_SRCS += $(shell ls src/gpio/*.c)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ release:
|
|||||||
make clean
|
make clean
|
||||||
make tox
|
make tox
|
||||||
make push
|
make push
|
||||||
make bump
|
make bump V=$(V)
|
||||||
make push
|
make push
|
||||||
make clean
|
make clean
|
||||||
|
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -14,13 +14,14 @@
|
|||||||
| 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 streaming <sup>1</sup> |
|
| Behavior when the device<br>is disconnected while streaming |  Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected |  Stops the streaming <sup>1</sup> |
|
||||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/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> |
|
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal |  Yes |  Partially yes <sup>1</sup> |
|
||||||
| Option to skip frames when streaming<br>static images by HTTP to save the traffic |  Yes <sup>2</sup> |  No |
|
| Option to skip frames when streaming<br>static images by HTTP to save the traffic |  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 streaming parameters |  Yes |  No |
|
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters |  Yes |  No |
|
||||||
| Option to serve files<br>with a built-in HTTP server |  Yes |  Regular files only |
|
| Option to serve files<br>with a built-in HTTP server |  Yes |  Regular files only |
|
||||||
| Signaling about the stream state to GPIO<br>on Raspberry Pi using [wiringPi](http://wiringpi.com) |  Yes |  No |
|
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) |  Yes |  No |
|
||||||
| 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 |
|
||||||
|
| Compatibility with mjpg-streamer's API |  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.
|
||||||
@@ -36,10 +37,11 @@ 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).
|
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`.
|
* 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`.
|
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
|
||||||
|
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg62-dev uuid-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libuuid1 libbsd0`.
|
||||||
|
|
||||||
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [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
|
$ git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||||
@@ -48,7 +50,7 @@ $ make
|
|||||||
$ ./ustreamer --help
|
$ ./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.
|
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@@ -58,6 +60,8 @@ Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x4
|
|||||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
# ./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:
|
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
|
```bash
|
||||||
$ ./ustreamer \
|
$ ./ustreamer \
|
||||||
@@ -69,7 +73,7 @@ $ ./ustreamer \
|
|||||||
--drop-same-frames=30 # Save the traffic
|
--drop-same-frames=30 # Save the traffic
|
||||||
```
|
```
|
||||||
|
|
||||||
Please note that to use `--drop-same-frames` for different browsers you need to use some specific URL `/stream` parameters (see URL `/` for details).
|
: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```.
|
You can always view the full list of options with ```ustreamer --help```.
|
||||||
|
|
||||||
|
|||||||
16
README.ru.md
16
README.ru.md
@@ -14,13 +14,14 @@
|
|||||||
| Многопоточное кодирование 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>трансляции на лету по сигналу<br>источника (устройства видеозахвата) |  Есть |  Условно есть <sup>1</sup> |
|
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) |  Есть |  Условно есть <sup>1</sup> |
|
||||||
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
||||||
| Стрим через UNIX domain socket |  Есть |  Нет |
|
| Стрим через UNIX domain socket |  Есть |  Нет |
|
||||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
||||||
| Возможность сервить файлы встроенным<br>HTTP-сервером |  Есть |  Нет каталогов |
|
| Возможность сервить файлы встроенным<br>HTTP-сервером |  Есть |  Нет каталогов |
|
||||||
| Вывод сигналов о состоянии стрима на GPIO<br>на Raspberry Pi с помощью [wiringPi](http://wiringpi.com) |  Есть |  Нет |
|
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) |  Есть |  Нет |
|
||||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
||||||
|
| Совместимость с API mjpg-streamer'а |  Есть | :) |
|
||||||
|
|
||||||
Сносочки:
|
Сносочки:
|
||||||
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
||||||
@@ -36,10 +37,11 @@
|
|||||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux).
|
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux).
|
||||||
|
|
||||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `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`.
|
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
|
||||||
|
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg62-dev uuid-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libuuid1 libbsd0`.
|
||||||
|
|
||||||
На 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
|
$ git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||||
@@ -48,7 +50,7 @@ $ make
|
|||||||
$ ./ustreamer --help
|
$ ./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.
|
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@@ -58,6 +60,8 @@ $ ./ustreamer --help
|
|||||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
# ./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:
|
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
|
||||||
```bash
|
```bash
|
||||||
$ ./ustreamer \
|
$ ./ustreamer \
|
||||||
@@ -69,7 +73,7 @@ $ ./ustreamer \
|
|||||||
--drop-same-frames=30 # Экономим трафик
|
--drop-same-frames=30 # Экономим трафик
|
||||||
```
|
```
|
||||||
|
|
||||||
Обратите внимание что для использования `--drop-same-frames` для разных браузеров нужно использовать ряд специальных параметров в `/stream` (за деталями обратитесь к урлу `/`).
|
:exclamation: Обратите внимание, что для использования `--drop-same-frames` для разных браузеров нужно использовать ряд специальных параметров в `/stream` (за деталями обратитесь к урлу `/`).
|
||||||
|
|
||||||
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ RUN pacman -Syu --noconfirm \
|
|||||||
python-pip \
|
python-pip \
|
||||||
python-tox \
|
python-tox \
|
||||||
cppcheck \
|
cppcheck \
|
||||||
|
npm \
|
||||||
&& (pacman -Sc --noconfirm || true)
|
&& (pacman -Sc --noconfirm || true)
|
||||||
|
|
||||||
|
RUN npm install htmlhint -g
|
||||||
|
|
||||||
CMD /bin/bash
|
CMD /bin/bash
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = cppcheck, flake8, pylint, mypy, vulture
|
envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
|
||||||
skipsdist = true
|
skipsdist = true
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
@@ -44,3 +44,7 @@ whitelist_externals = bash
|
|||||||
commands = bash -c 'vulture tools/*.py'
|
commands = bash -c 'vulture tools/*.py'
|
||||||
deps =
|
deps =
|
||||||
vulture
|
vulture
|
||||||
|
|
||||||
|
[testenv:htmlhint]
|
||||||
|
whitelist_externals = htmlhint
|
||||||
|
commands = htmlhint src/http/data/*.html
|
||||||
|
|||||||
@@ -3,15 +3,14 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=1.24
|
pkgver=2.2
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||||
url="https://github.com/pikvm/ustreamer"
|
url="https://github.com/pikvm/ustreamer"
|
||||||
license=(GPL)
|
license=(GPL)
|
||||||
arch=(i686 x86_64 armv6h armv7h aarch64)
|
arch=(i686 x86_64 armv6h armv7h aarch64)
|
||||||
depends=(libjpeg libevent libutil-linux libbsd)
|
depends=(libjpeg libevent libutil-linux libbsd libgpiod)
|
||||||
# optional: raspberrypi-firmware for OMX encoder
|
# optional: raspberrypi-firmware for OMX encoder
|
||||||
# optional: wiringpi for GPIO support
|
|
||||||
makedepends=(gcc make)
|
makedepends=(gcc make)
|
||||||
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
||||||
md5sums=(SKIP)
|
md5sums=(SKIP)
|
||||||
@@ -23,9 +22,8 @@ build() {
|
|||||||
cp -r $pkgname $pkgname-build
|
cp -r $pkgname $pkgname-build
|
||||||
cd $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 /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
|
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ RUN apt-get update \
|
|||||||
libjpeg8-dev \
|
libjpeg8-dev \
|
||||||
libbsd-dev \
|
libbsd-dev \
|
||||||
libraspberrypi-dev \
|
libraspberrypi-dev \
|
||||||
wiringpi \
|
libgpiod-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /build/ustreamer/
|
WORKDIR /build/ustreamer/
|
||||||
@@ -27,7 +27,7 @@ RUN apt-get update \
|
|||||||
libjpeg8 \
|
libjpeg8 \
|
||||||
uuid \
|
uuid \
|
||||||
libbsd0 \
|
libbsd0 \
|
||||||
wiringpi \
|
libgpiod2 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN ["cross-build-end"]
|
RUN ["cross-build-end"]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ RUN apt-get update \
|
|||||||
libjpeg8-dev \
|
libjpeg8-dev \
|
||||||
libbsd-dev \
|
libbsd-dev \
|
||||||
libraspberrypi-dev \
|
libraspberrypi-dev \
|
||||||
wiringpi \
|
libgpiod-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /build/ustreamer/
|
WORKDIR /build/ustreamer/
|
||||||
@@ -22,7 +22,7 @@ RUN apt-get update \
|
|||||||
libjpeg8 \
|
libjpeg8 \
|
||||||
uuid \
|
uuid \
|
||||||
libbsd0 \
|
libbsd0 \
|
||||||
wiringpi \
|
libgpiod2 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /ustreamer
|
WORKDIR /ustreamer
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ RUN apt-get update \
|
|||||||
libjpeg62-turbo-dev \
|
libjpeg62-turbo-dev \
|
||||||
uuid-dev \
|
uuid-dev \
|
||||||
libbsd-dev \
|
libbsd-dev \
|
||||||
|
libgpiod-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /build/ustreamer/
|
WORKDIR /build/ustreamer/
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN make -j5
|
RUN make -j5 WITH_GPIO=1
|
||||||
|
|
||||||
FROM debian:buster-slim as run
|
FROM debian:buster-slim as run
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ RUN apt-get update \
|
|||||||
libjpeg62-turbo \
|
libjpeg62-turbo \
|
||||||
uuid \
|
uuid \
|
||||||
libbsd0 \
|
libbsd0 \
|
||||||
|
libgpiod2 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /ustreamer
|
WORKDIR /ustreamer
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ustreamer
|
PKG_NAME:=ustreamer
|
||||||
PKG_VERSION:=1.24
|
PKG_VERSION:=2.2
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
|||||||
@@ -23,5 +23,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifndef VERSION
|
#ifndef VERSION
|
||||||
# define VERSION "1.24"
|
# define VERSION "2.2"
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
62
src/device.c
62
src/device.c
@@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
#include "threading.h"
|
||||||
#include "xioctl.h"
|
#include "xioctl.h"
|
||||||
#include "picture.h"
|
#include "picture.h"
|
||||||
|
|
||||||
@@ -209,7 +210,7 @@ void device_close(struct device_t *dev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dev->run->hw_buffers) {
|
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) {
|
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
|
||||||
|
|
||||||
@@ -224,6 +225,7 @@ void device_close(struct device_t *dev) {
|
|||||||
free(HW_BUFFER(data));
|
free(HW_BUFFER(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
A_MUTEX_DESTROY(&HW_BUFFER(grabbed_mutex));
|
||||||
|
|
||||||
# undef HW_BUFFER
|
# undef HW_BUFFER
|
||||||
}
|
}
|
||||||
@@ -301,31 +303,57 @@ int device_grab_buffer(struct device_t *dev) {
|
|||||||
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||||
buf_info.memory = dev->io_method;
|
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) {
|
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;
|
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) {
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused;
|
# define HW_BUFFER(_next) dev->run->hw_buffers[buf_info.index]._next
|
||||||
memcpy(&dev->run->hw_buffers[buf_info.index].buf_info, &buf_info, sizeof(struct v4l2_buffer));
|
|
||||||
|
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();
|
dev->run->pictures[buf_info.index]->grab_ts = get_now_monotonic();
|
||||||
|
|
||||||
|
# undef HW_BUFFER
|
||||||
return buf_info.index;
|
return buf_info.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
int device_release_buffer(struct device_t *dev, unsigned index) {
|
int device_release_buffer(struct device_t *dev, unsigned index) {
|
||||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
|
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
|
||||||
if (xioctl(dev->run->fd, VIDIOC_QBUF, &dev->run->hw_buffers[index].buf_info) < 0) {
|
|
||||||
LOG_PERROR("Unable to requeue buffer");
|
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;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,7 +537,7 @@ static void _device_open_hw_fps(struct device_t *dev) {
|
|||||||
LOG_DEBUG("Calling ioctl(VIDIOC_G_PARM) ...");
|
LOG_DEBUG("Calling ioctl(VIDIOC_G_PARM) ...");
|
||||||
if (xioctl(dev->run->fd, VIDIOC_G_PARM, &setfps) < 0) {
|
if (xioctl(dev->run->fd, VIDIOC_G_PARM, &setfps) < 0) {
|
||||||
if (errno == ENOTTY) { // Quiet message for Auvidea B101
|
if (errno == ENOTTY) { // Quiet message for Auvidea B101
|
||||||
LOG_INFO("Quierying HW FPS changing is not supported");
|
LOG_INFO("Querying HW FPS changing is not supported");
|
||||||
} else {
|
} else {
|
||||||
LOG_PERROR("Unable to query HW FPS changing");
|
LOG_PERROR("Unable to query HW FPS changing");
|
||||||
}
|
}
|
||||||
@@ -581,10 +609,10 @@ static int _device_open_io_method_mmap(struct device_t *dev) {
|
|||||||
LOG_ERROR("Insufficient buffer memory: %u", req.count);
|
LOG_ERROR("Insufficient buffer memory: %u", req.count);
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} 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);
|
A_CALLOC(dev->run->hw_buffers, req.count);
|
||||||
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
|
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
|
||||||
@@ -603,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
|
# 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);
|
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);
|
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) {
|
if (HW_BUFFER(data) == MAP_FAILED) {
|
||||||
@@ -636,10 +666,10 @@ static int _device_open_io_method_userptr(struct device_t *dev) {
|
|||||||
LOG_ERROR("Insufficient buffer memory: %u", req.count);
|
LOG_ERROR("Insufficient buffer memory: %u", req.count);
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} 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);
|
A_CALLOC(dev->run->hw_buffers, req.count);
|
||||||
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
|
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
#include <linux/videodev2.h>
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
#include "picture.h"
|
#include "picture.h"
|
||||||
@@ -53,6 +54,9 @@ struct hw_buffer_t {
|
|||||||
size_t used;
|
size_t used;
|
||||||
size_t allocated;
|
size_t allocated;
|
||||||
struct v4l2_buffer buf_info;
|
struct v4l2_buffer buf_info;
|
||||||
|
|
||||||
|
pthread_mutex_t grabbed_mutex;
|
||||||
|
bool grabbed;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct device_runtime_t {
|
struct device_runtime_t {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "encoder.h"
|
#include "encoder.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -51,6 +52,7 @@ static const OMX_U32 _OUTPUT_PORT = 341;
|
|||||||
static int _i_omx = 0;
|
static int _i_omx = 0;
|
||||||
|
|
||||||
|
|
||||||
|
static int _vcos_semwait(VCOS_SEMAPHORE_T *sem);
|
||||||
static int _omx_init_component(struct omx_encoder_t *omx);
|
static int _omx_init_component(struct omx_encoder_t *omx);
|
||||||
static int _omx_init_disable_ports(struct omx_encoder_t *omx);
|
static int _omx_init_disable_ports(struct omx_encoder_t *omx);
|
||||||
static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev);
|
static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev);
|
||||||
@@ -180,7 +182,6 @@ 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;
|
||||||
VCOS_STATUS_T sem_status;
|
|
||||||
size_t slice_size = (IN(nAllocLen) < HW_BUFFER(used) ? IN(nAllocLen) : HW_BUFFER(used));
|
size_t slice_size = (IN(nAllocLen) < HW_BUFFER(used) ? IN(nAllocLen) : HW_BUFFER(used));
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
|
|
||||||
@@ -237,12 +238,8 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// vcos_semaphore_wait(&omx->handler_sem);
|
if (_vcos_semwait(&omx->handler_sem) != 0) {
|
||||||
switch (sem_status = vcos_semaphore_wait_timeout(&omx->handler_sem, 3000)) {
|
return -1;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +249,40 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _vcos_semwait(VCOS_SEMAPHORE_T *sem) {
|
||||||
|
// vcos_semaphore_wait() can wait infinite
|
||||||
|
// vcos_semaphore_wait_timeout() is broken by design:
|
||||||
|
// - https://github.com/pikvm/ustreamer/issues/56
|
||||||
|
// - https://github.com/raspberrypi/userland/issues/658
|
||||||
|
// CFG_OMX_SEMWAIT_TIMEOUT is ugly busyloop
|
||||||
|
// Три стула.
|
||||||
|
|
||||||
|
# ifdef CFG_OMX_SEMWAIT_TIMEOUT
|
||||||
|
long double deadline_ts = get_now_monotonic() + (long double)CFG_OMX_SEMWAIT_TIMEOUT; // Seconds
|
||||||
|
VCOS_STATUS_T sem_status;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
sem_status = vcos_semaphore_trywait(sem);
|
||||||
|
if (sem_status == VCOS_SUCCESS) {
|
||||||
|
return 0;
|
||||||
|
} else if (sem_status != VCOS_EAGAIN || get_now_monotonic() > deadline_ts) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
error:
|
||||||
|
switch (sem_status) {
|
||||||
|
case VCOS_EAGAIN: LOG_ERROR("Can't wait VCOS semaphore: EAGAIN (timeout)"); break;
|
||||||
|
case VCOS_EINVAL: LOG_ERROR("Can't wait VCOS semaphore: EINVAL"); break;
|
||||||
|
default: LOG_ERROR("Can't wait VCOS semaphore: %d", sem_status); break;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
# else
|
||||||
|
return (vcos_semaphore_wait(sem) == VCOS_SUCCESS ? 0 : -1);
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
static int _omx_init_component(struct omx_encoder_t *omx) {
|
static int _omx_init_component(struct omx_encoder_t *omx) {
|
||||||
OMX_ERRORTYPE error;
|
OMX_ERRORTYPE error;
|
||||||
|
|
||||||
@@ -433,32 +464,32 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
|
|||||||
|
|
||||||
static int _omx_encoder_clear_ports(struct omx_encoder_t *omx) {
|
static int _omx_encoder_clear_ports(struct omx_encoder_t *omx) {
|
||||||
OMX_ERRORTYPE error;
|
OMX_ERRORTYPE error;
|
||||||
int retcode = 0;
|
int retval = 0;
|
||||||
|
|
||||||
if (omx->i_output_port_enabled) {
|
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;
|
omx->i_output_port_enabled = false;
|
||||||
}
|
}
|
||||||
if (omx->i_input_port_enabled) {
|
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;
|
omx->i_input_port_enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (omx->input_buffer) {
|
if (omx->input_buffer) {
|
||||||
if ((error = OMX_FreeBuffer(omx->encoder, _INPUT_PORT, omx->input_buffer)) != OMX_ErrorNone) {
|
if ((error = OMX_FreeBuffer(omx->encoder, _INPUT_PORT, omx->input_buffer)) != OMX_ErrorNone) {
|
||||||
LOG_ERROR_OMX(error, "Can't free OMX JPEG input buffer");
|
LOG_ERROR_OMX(error, "Can't free OMX JPEG input buffer");
|
||||||
// retcode -= 1;
|
// retval -= 1;
|
||||||
}
|
}
|
||||||
omx->input_buffer = NULL;
|
omx->input_buffer = NULL;
|
||||||
}
|
}
|
||||||
if (omx->output_buffer) {
|
if (omx->output_buffer) {
|
||||||
if ((error = OMX_FreeBuffer(omx->encoder, _OUTPUT_PORT, omx->output_buffer)) != OMX_ErrorNone) {
|
if ((error = OMX_FreeBuffer(omx->encoder, _OUTPUT_PORT, omx->output_buffer)) != OMX_ErrorNone) {
|
||||||
LOG_ERROR_OMX(error, "Can't free OMX JPEG output buffer");
|
LOG_ERROR_OMX(error, "Can't free OMX JPEG output buffer");
|
||||||
// retcode -= 1;
|
// retval -= 1;
|
||||||
}
|
}
|
||||||
omx->output_buffer = NULL;
|
omx->output_buffer = NULL;
|
||||||
}
|
}
|
||||||
return retcode;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static OMX_ERRORTYPE _omx_event_handler(
|
static OMX_ERRORTYPE _omx_event_handler(
|
||||||
|
|||||||
96
src/gpio.h
96
src/gpio.h
@@ -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
142
src/gpio/gpio.c
Normal 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
83
src/gpio/gpio.h
Normal 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
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>uStreamer</title>
|
<title>uStreamer</title>
|
||||||
|
<style>body {font-family: monospace;}</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -11,46 +12,56 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="/state"><b><samp>/state</samp></b></a><br>
|
<a href="/state"><b>/state</b></a><br>
|
||||||
Get JSON structure with state of the server.
|
Get JSON structure with the state of the server.
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
<li>
|
<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.
|
Get a current actual image from the server.
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
<li>
|
<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>
|
Get a live stream. Query params:<br>
|
||||||
<br>
|
<br>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<b><samp>key=abc123</samp></b><br>
|
<b>key=abc123</b><br>
|
||||||
User-defined key, which is part of cookie <samp>stream_client</samp>, which allows<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"><samp>/state</samp></a>.
|
the stream client to determine its identifier and view statistics using <a href="/state">/state</a>.
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
<li>
|
<li>
|
||||||
<b><samp>extra_headers=1</samp></b><br>
|
<b>extra_headers=1</b><br>
|
||||||
Add <samp>X-UStreamer-*</samp> headers to /stream handle (like on <a href="/snapshot"><samp>/snapshot</samp></a>).
|
Add <i>X-UStreamer-*</i> headers to the <a href="/stream">/stream</a> handle
|
||||||
|
(like with the <a href="/snapshot">/snapshot</a>).
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
<li>
|
<li>
|
||||||
<b><samp>advance_headers=1</samp></b><br>
|
<b>advance_headers=1</b><br>
|
||||||
Enable workaround for Chromium/Blink
|
Enable workaround for the Chromium/Blink bug
|
||||||
<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=527446">Bug #527446</a>.
|
<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=527446">#527446</a>.
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
<li>
|
<li>
|
||||||
<b><samp>dual_final_frames=1</samp></b><br>
|
<b>dual_final_frames=1</b><br>
|
||||||
Enable workaround for Safari/WebKit bug when using option <samp>--drop-same-frames</samp>.<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>
|
Without this option, when the frame series is completed, WebKit-based browsers<br>
|
||||||
renders the last frame with a delay.
|
renders the last frame with a delay.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<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>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ const char HTML_INDEX_PAGE[] = " \
|
|||||||
<head> \
|
<head> \
|
||||||
<meta charset=\"utf-8\" /> \
|
<meta charset=\"utf-8\" /> \
|
||||||
<title>uStreamer</title> \
|
<title>uStreamer</title> \
|
||||||
|
<style>body {font-family: monospace;}</style> \
|
||||||
</head> \
|
</head> \
|
||||||
\
|
\
|
||||||
<body> \
|
<body> \
|
||||||
@@ -39,46 +40,56 @@ const char HTML_INDEX_PAGE[] = " \
|
|||||||
<hr> \
|
<hr> \
|
||||||
<ul> \
|
<ul> \
|
||||||
<li> \
|
<li> \
|
||||||
<a href=\"/state\"><b><samp>/state</samp></b></a><br> \
|
<a href=\"/state\"><b>/state</b></a><br> \
|
||||||
Get JSON structure with state of the server. \
|
Get JSON structure with the state of the server. \
|
||||||
</li> \
|
</li> \
|
||||||
<br> \
|
<br> \
|
||||||
<li> \
|
<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. \
|
Get a current actual image from the server. \
|
||||||
</li> \
|
</li> \
|
||||||
<br> \
|
<br> \
|
||||||
<li> \
|
<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> \
|
Get a live stream. Query params:<br> \
|
||||||
<br> \
|
<br> \
|
||||||
<ul> \
|
<ul> \
|
||||||
<li> \
|
<li> \
|
||||||
<b><samp>key=abc123</samp></b><br> \
|
<b>key=abc123</b><br> \
|
||||||
User-defined key, which is part of cookie <samp>stream_client</samp>, which allows<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\"><samp>/state</samp></a>. \
|
the stream client to determine its identifier and view statistics using <a href=\"/state\">/state</a>. \
|
||||||
</li> \
|
</li> \
|
||||||
<br> \
|
<br> \
|
||||||
<li> \
|
<li> \
|
||||||
<b><samp>extra_headers=1</samp></b><br> \
|
<b>extra_headers=1</b><br> \
|
||||||
Add <samp>X-UStreamer-*</samp> headers to /stream handle (like on <a href=\"/snapshot\"><samp>/snapshot</samp></a>). \
|
Add <i>X-UStreamer-*</i> headers to the <a href=\"/stream\">/stream</a> handle \
|
||||||
|
(like with the <a href=\"/snapshot\">/snapshot</a>). \
|
||||||
</li> \
|
</li> \
|
||||||
<br> \
|
<br> \
|
||||||
<li> \
|
<li> \
|
||||||
<b><samp>advance_headers=1</samp></b><br> \
|
<b>advance_headers=1</b><br> \
|
||||||
Enable workaround for Chromium/Blink \
|
Enable workaround for the Chromium/Blink bug \
|
||||||
<a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=527446\">Bug #527446</a>. \
|
<a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=527446\">#527446</a>. \
|
||||||
</li> \
|
</li> \
|
||||||
<br> \
|
<br> \
|
||||||
<li> \
|
<li> \
|
||||||
<b><samp>dual_final_frames=1</samp></b><br> \
|
<b>dual_final_frames=1</b><br> \
|
||||||
Enable workaround for Safari/WebKit bug when using option <samp>--drop-same-frames</samp>.<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> \
|
Without this option, when the frame series is completed, WebKit-based browsers<br> \
|
||||||
renders the last frame with a delay. \
|
renders the last frame with a delay. \
|
||||||
</li> \
|
</li> \
|
||||||
</ul> \
|
</ul> \
|
||||||
</li> \
|
</li> \
|
||||||
<br> \
|
<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> \
|
</ul> \
|
||||||
<br> \
|
<br> \
|
||||||
<hr> \
|
<hr> \
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
#include "../encoder.h"
|
#include "../encoder.h"
|
||||||
#include "../stream.h"
|
#include "../stream.h"
|
||||||
#ifdef WITH_GPIO
|
#ifdef WITH_GPIO
|
||||||
# include "../gpio.h"
|
# include "../gpio/gpio.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "unix.h"
|
#include "unix.h"
|
||||||
@@ -112,6 +112,7 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
|||||||
server->user = "";
|
server->user = "";
|
||||||
server->passwd = "";
|
server->passwd = "";
|
||||||
server->static_path = "";
|
server->static_path = "";
|
||||||
|
server->allow_origin = "";
|
||||||
server->timeout = 10;
|
server->timeout = 10;
|
||||||
server->last_as_blank = -1;
|
server->last_as_blank = -1;
|
||||||
server->run = run;
|
server->run = run;
|
||||||
@@ -291,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) {
|
static void _http_callback_root(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;
|
||||||
|
struct evkeyvalq params; // For mjpg-streamer compatibility
|
||||||
|
const char *action; // Ditto
|
||||||
|
|
||||||
PREPROCESS_REQUEST;
|
PREPROCESS_REQUEST;
|
||||||
|
|
||||||
assert((buf = evbuffer_new()));
|
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||||
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
|
action = evhttp_find_header(¶ms, "action");
|
||||||
ADD_HEADER("Content-Type", "text/html");
|
|
||||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
if (action && !strcmp(action, "snapshot")) {
|
||||||
evbuffer_free(buf);
|
_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(¶ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
|
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
|
||||||
@@ -443,7 +457,9 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
|||||||
assert((buf = evbuffer_new()));
|
assert((buf = evbuffer_new()));
|
||||||
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture->data), EXPOSED(picture->used)));
|
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture->data), EXPOSED(picture->used)));
|
||||||
|
|
||||||
ADD_HEADER("Access-Control-Allow-Origin:", "*");
|
if (server->allow_origin[0] != '\0') {
|
||||||
|
ADD_HEADER("Access-Control-Allow-Origin", server->allow_origin);
|
||||||
|
}
|
||||||
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
|
ADD_HEADER("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("Pragma", "no-cache");
|
||||||
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
||||||
@@ -537,7 +553,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
GPIO_SET_HIGH(has_http_clients);
|
gpio_set_has_http_clients(true);
|
||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,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()))
|
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real()))
|
||||||
|
|
||||||
if (client->need_initial) {
|
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,
|
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
|
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||||
"Pragma: no-cache" RN
|
"Pragma: no-cache" RN
|
||||||
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
||||||
@@ -711,7 +729,7 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
GPIO_SET_LOW(has_http_clients);
|
gpio_set_has_http_clients(false);
|
||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ struct http_server_t {
|
|||||||
char *user;
|
char *user;
|
||||||
char *passwd;
|
char *passwd;
|
||||||
char *static_path;
|
char *static_path;
|
||||||
|
char *allow_origin;
|
||||||
|
|
||||||
char *blank_path;
|
char *blank_path;
|
||||||
int last_as_blank;
|
int last_as_blank;
|
||||||
|
|||||||
20
src/main.c
20
src/main.c
@@ -31,6 +31,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
#include "http/server.h"
|
#include "http/server.h"
|
||||||
#ifdef WITH_GPIO
|
#ifdef WITH_GPIO
|
||||||
# include "gpio.h"
|
# include "gpio/gpio.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@@ -114,10 +115,6 @@ int main(int argc, char *argv[]) {
|
|||||||
A_THREAD_RENAME("main");
|
A_THREAD_RENAME("main");
|
||||||
options = options_init(argc, argv);
|
options = options_init(argc, argv);
|
||||||
|
|
||||||
# ifdef WITH_GPIO
|
|
||||||
GPIO_INIT;
|
|
||||||
# endif
|
|
||||||
|
|
||||||
dev = device_init();
|
dev = device_init();
|
||||||
encoder = encoder_init();
|
encoder = encoder_init();
|
||||||
stream = stream_init(dev, encoder);
|
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) {
|
if ((exit_code = options_parse(options, dev, encoder, server)) == 0) {
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
GPIO_INIT_PINOUT;
|
gpio_init();
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
_install_signal_handlers();
|
_install_signal_handlers();
|
||||||
@@ -140,7 +137,7 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
if ((exit_code = http_server_listen(server)) == 0) {
|
if ((exit_code = http_server_listen(server)) == 0) {
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
GPIO_SET_HIGH(prog_running);
|
gpio_set_prog_running(true);
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
|
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(server_loop_tid);
|
||||||
A_THREAD_JOIN(stream_loop_tid);
|
A_THREAD_JOIN(stream_loop_tid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ifdef WITH_GPIO
|
||||||
|
gpio_set_prog_running(false);
|
||||||
|
gpio_destroy();
|
||||||
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
http_server_destroy(server);
|
http_server_destroy(server);
|
||||||
@@ -155,10 +157,6 @@ int main(int argc, char *argv[]) {
|
|||||||
encoder_destroy(encoder);
|
encoder_destroy(encoder);
|
||||||
device_destroy(dev);
|
device_destroy(dev);
|
||||||
|
|
||||||
# ifdef WITH_GPIO
|
|
||||||
GPIO_SET_LOW(prog_running);
|
|
||||||
# endif
|
|
||||||
|
|
||||||
options_destroy(options);
|
options_destroy(options);
|
||||||
if (exit_code == 0) {
|
if (exit_code == 0) {
|
||||||
LOG_INFO("Bye-bye");
|
LOG_INFO("Bye-bye");
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
#include "encoder.h"
|
#include "encoder.h"
|
||||||
#include "http/server.h"
|
#include "http/server.h"
|
||||||
#ifdef WITH_GPIO
|
#ifdef WITH_GPIO
|
||||||
# include "gpio.h"
|
# include "gpio/gpio.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@@ -99,14 +99,16 @@ enum _OPT_VALUES {
|
|||||||
_O_USER,
|
_O_USER,
|
||||||
_O_PASSWD,
|
_O_PASSWD,
|
||||||
_O_STATIC,
|
_O_STATIC,
|
||||||
|
_O_ALLOW_ORIGIN,
|
||||||
_O_TCP_NODELAY,
|
_O_TCP_NODELAY,
|
||||||
_O_SERVER_TIMEOUT,
|
_O_SERVER_TIMEOUT,
|
||||||
|
|
||||||
#ifdef WITH_GPIO
|
#ifdef WITH_GPIO
|
||||||
|
_O_GPIO_DEVICE,
|
||||||
|
_O_GPIO_CONSUMER_PREFIX,
|
||||||
_O_GPIO_PROG_RUNNING,
|
_O_GPIO_PROG_RUNNING,
|
||||||
_O_GPIO_STREAM_ONLINE,
|
_O_GPIO_STREAM_ONLINE,
|
||||||
_O_GPIO_HAS_HTTP_CLIENTS,
|
_O_GPIO_HAS_HTTP_CLIENTS,
|
||||||
_O_GPIO_WORKERS_BUSY_AT,
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_PDEATHSIG
|
#ifdef HAS_PDEATHSIG
|
||||||
@@ -174,15 +176,17 @@ static const struct option _LONG_OPTS[] = {
|
|||||||
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
|
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
|
||||||
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
|
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
|
||||||
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
|
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
|
||||||
|
{"allow-origin", required_argument, NULL, _O_ALLOW_ORIGIN},
|
||||||
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
|
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
|
||||||
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
|
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
|
||||||
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
|
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
|
||||||
|
|
||||||
#ifdef WITH_GPIO
|
#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-prog-running", required_argument, NULL, _O_GPIO_PROG_RUNNING},
|
||||||
{"gpio-stream-online", required_argument, NULL, _O_GPIO_STREAM_ONLINE},
|
{"gpio-stream-online", required_argument, NULL, _O_GPIO_STREAM_ONLINE},
|
||||||
{"gpio-has-http-clients", required_argument, NULL, _O_GPIO_HAS_HTTP_CLIENTS},
|
{"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
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_PDEATHSIG
|
#ifdef HAS_PDEATHSIG
|
||||||
@@ -396,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_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_SLOWDOWN: OPT_SET(server->slowdown, true);
|
||||||
case _O_FAKE_RESOLUTION: OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
|
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_TCP_NODELAY: OPT_SET(server->tcp_nodelay, true);
|
||||||
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
|
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
|
||||||
|
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio_pin_prog_running, 0, 256, 0);
|
case _O_GPIO_DEVICE: OPT_SET(gpio.path, optarg);
|
||||||
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", gpio_pin_stream_online, 0, 256, 0);
|
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(gpio.consumer_prefix, optarg);
|
||||||
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", gpio_pin_has_http_clients, 0, 256, 0);
|
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio.prog_running.pin, 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_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
|
# endif
|
||||||
|
|
||||||
# ifdef HAS_PDEATHSIG
|
# ifdef HAS_PDEATHSIG
|
||||||
@@ -642,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(" -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(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Ignored for --unix.\n");
|
||||||
printf(" Default: disabled.\n\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);
|
printf(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n\n", server->timeout);
|
||||||
#ifdef WITH_GPIO
|
#ifdef WITH_GPIO
|
||||||
printf("GPIO options:\n");
|
printf("GPIO options:\n");
|
||||||
printf("═════════════\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-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-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-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
|
#endif
|
||||||
#if (defined(HAS_PDEATHSIG) || defined(WITH_SETPROCTITLE))
|
#if (defined(HAS_PDEATHSIG) || defined(WITH_SETPROCTITLE))
|
||||||
printf("Process options:\n");
|
printf("Process options:\n");
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
|
|||||||
size_t arg_len = strlen(argv[index]);
|
size_t arg_len = strlen(argv[index]);
|
||||||
if (used + arg_len + 16 >= allocated) {
|
if (used + arg_len + 16 >= allocated) {
|
||||||
allocated += arg_len + 2048;
|
allocated += arg_len + 2048;
|
||||||
A_REALLOC(cmdline, allocated);
|
A_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
|
||||||
}
|
}
|
||||||
|
|
||||||
strcat(cmdline, " ");
|
strcat(cmdline, " ");
|
||||||
|
|||||||
44
src/stream.c
44
src/stream.c
@@ -38,7 +38,7 @@
|
|||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "encoder.h"
|
#include "encoder.h"
|
||||||
#ifdef WITH_GPIO
|
#ifdef WITH_GPIO
|
||||||
# include "gpio.h"
|
# include "gpio/gpio.h"
|
||||||
#endif
|
#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_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 void _stream_expose_picture(struct stream_t *stream, unsigned buf_index, unsigned captured_fps);
|
||||||
|
|
||||||
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream);
|
static 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) {
|
} else if (selected == 0) {
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
GPIO_SET_LOW(stream_online);
|
gpio_set_stream_online(false);
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
if (stream->dev->persistent) {
|
if (stream->dev->persistent) {
|
||||||
@@ -207,7 +207,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
LOG_DEBUG("Frame is ready");
|
LOG_DEBUG("Frame is ready");
|
||||||
|
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
GPIO_SET_HIGH(stream_online);
|
gpio_set_stream_online(true);
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
int buf_index;
|
int buf_index;
|
||||||
@@ -288,7 +288,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
device_close(stream->dev);
|
device_close(stream->dev);
|
||||||
|
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
GPIO_SET_LOW(stream_online);
|
gpio_set_stream_online(false);
|
||||||
# endif
|
# 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) {
|
static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream) {
|
||||||
struct _workers_pool_t *pool = NULL;
|
struct _workers_pool_t *pool = NULL;
|
||||||
|
int access_error = 0;
|
||||||
|
|
||||||
LOG_DEBUG("%s: stream->proc->stop=%d", __FUNCTION__, atomic_load(&stream->proc->stop));
|
LOG_DEBUG("%s: stream->proc->stop=%d", __FUNCTION__, atomic_load(&stream->proc->stop));
|
||||||
|
|
||||||
while (!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);
|
LOG_INFO("Sleeping %u seconds before new stream init ...", stream->dev->error_delay);
|
||||||
sleep(stream->dev->error_delay);
|
sleep(stream->dev->error_delay);
|
||||||
} else {
|
} else {
|
||||||
@@ -319,7 +332,7 @@ static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream) {
|
|||||||
return pool;
|
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) {
|
if (device_open(stream->dev) < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
@@ -431,17 +444,9 @@ static void *_worker_thread(void *v_worker) {
|
|||||||
A_THREAD_RENAME("worker-%u", worker->number);
|
A_THREAD_RENAME("worker-%u", worker->number);
|
||||||
LOG_DEBUG("Hello! I am a 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)) {
|
while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) {
|
||||||
LOG_DEBUG("Worker %u waiting for a new job ...", worker->number);
|
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_MUTEX_LOCK(&worker->has_job_mutex);
|
||||||
A_COND_WAIT_TRUE(atomic_load(&worker->has_job), &worker->has_job_cond, &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);
|
A_MUTEX_UNLOCK(&worker->has_job_mutex);
|
||||||
@@ -451,10 +456,6 @@ static void *_worker_thread(void *v_worker) {
|
|||||||
|
|
||||||
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index);
|
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
|
|
||||||
|
|
||||||
worker->job_failed = (bool)encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index);
|
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) {
|
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
|
||||||
@@ -483,9 +484,6 @@ static void *_worker_thread(void *v_worker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG("Bye-bye (worker %u)", worker->number);
|
LOG_DEBUG("Bye-bye (worker %u)", worker->number);
|
||||||
# ifdef WITH_GPIO
|
|
||||||
GPIO_SET_LOW_AT(workers_busy_at, worker->number);
|
|
||||||
# endif
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user