mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 04:06:30 +00:00
Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e7d8da407 | ||
|
|
1c862b21e9 | ||
|
|
b202438a4d | ||
|
|
50ee2ba964 | ||
|
|
9f40713ee1 | ||
|
|
5f393ae972 | ||
|
|
aca3ee9e23 | ||
|
|
284b17d1db | ||
|
|
506a4496d1 | ||
|
|
b5c201d1c1 | ||
|
|
032faa9882 | ||
|
|
414baadc40 | ||
|
|
2d6716aa47 | ||
|
|
260619923a | ||
|
|
3715c89ec8 | ||
|
|
5026108079 | ||
|
|
5f7c556697 | ||
|
|
3c7564da19 | ||
|
|
f0e070be5b | ||
|
|
aa05c470b3 | ||
|
|
13af11a3a6 | ||
|
|
41330940c6 | ||
|
|
46e630d2f6 | ||
|
|
63d87f0526 | ||
|
|
b578e9897e | ||
|
|
b2ebcf99c8 | ||
|
|
6a6b910869 | ||
|
|
4e8acf371f | ||
|
|
c4cb8288c7 | ||
|
|
2dddb879bc | ||
|
|
4d92dc662c | ||
|
|
3cb649d97c | ||
|
|
d9b5f2b03d | ||
|
|
2997906d98 | ||
|
|
bcd3deaa13 | ||
|
|
f8ed7d7b3b | ||
|
|
622f5cf1eb | ||
|
|
81756811f3 | ||
|
|
bd403593a0 | ||
|
|
fc3e0232e1 | ||
|
|
89fd83bfc1 | ||
|
|
83a77ea898 | ||
|
|
33fdf9bf43 | ||
|
|
6bd4ef59c0 | ||
|
|
79987da1bf | ||
|
|
05e5db09e4 | ||
|
|
55e432a529 | ||
|
|
4732c85ec4 | ||
|
|
0ce7f28754 | ||
|
|
a2641dfcb6 | ||
|
|
ec33425c05 | ||
|
|
a4b4dd3932 | ||
|
|
e952f787a0 | ||
|
|
b3e4ea9c0f | ||
|
|
22a816b9b5 | ||
|
|
c96559e4ac | ||
|
|
a52df47b29 | ||
|
|
68e7e97e74 | ||
|
|
35ed415f4c | ||
|
|
121edf5a10 | ||
|
|
aa90ed1fbb | ||
|
|
a102a4a3db | ||
|
|
516c0be6ea | ||
|
|
0745f0a75a | ||
|
|
90e51c0619 | ||
|
|
cb9c1658af | ||
|
|
548c261d92 | ||
|
|
d4560fcba9 | ||
|
|
370434601c | ||
|
|
09359cb957 | ||
|
|
71b93a2a38 | ||
|
|
aeb5930483 | ||
|
|
b17b87018b | ||
|
|
602ca16178 | ||
|
|
28c8599167 | ||
|
|
aa668cec9d | ||
|
|
f6ec0ade38 | ||
|
|
a10df2f01f | ||
|
|
dde2190ac9 | ||
|
|
c24c1ec5ff | ||
|
|
cfd0cdad59 |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 5.36
|
current_version = 5.55
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
|||||||
/pkg/arch/src/
|
/pkg/arch/src/
|
||||||
/src/build/
|
/src/build/
|
||||||
/python/build/
|
/python/build/
|
||||||
|
/python/ustreamer.egg-info/
|
||||||
/janus/build/
|
/janus/build/
|
||||||
/ustreamer
|
/ustreamer
|
||||||
/ustreamer-dump
|
/ustreamer-dump
|
||||||
|
|||||||
15
Makefile
15
Makefile
@@ -9,9 +9,6 @@ PY ?= python3
|
|||||||
CFLAGS ?= -O3
|
CFLAGS ?= -O3
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
|
|
||||||
RPI_VC_HEADERS ?= /opt/vc/include
|
|
||||||
RPI_VC_LIBS ?= /opt/vc/lib
|
|
||||||
|
|
||||||
export
|
export
|
||||||
|
|
||||||
_LINTERS_IMAGE ?= ustreamer-linters
|
_LINTERS_IMAGE ?= ustreamer-linters
|
||||||
@@ -22,6 +19,9 @@ define optbool
|
|||||||
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
ifeq ($(V),)
|
||||||
|
ECHO = @
|
||||||
|
endif
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
all:
|
all:
|
||||||
@@ -36,18 +36,17 @@ endif
|
|||||||
|
|
||||||
apps:
|
apps:
|
||||||
$(MAKE) -C src
|
$(MAKE) -C src
|
||||||
@ ln -sf src/ustreamer.bin ustreamer
|
$(ECHO) ln -sf src/*.bin .
|
||||||
@ ln -sf src/ustreamer-dump.bin ustreamer-dump
|
|
||||||
|
|
||||||
|
|
||||||
python:
|
python:
|
||||||
$(MAKE) -C python
|
$(MAKE) -C python
|
||||||
@ ln -sf python/build/lib.*/*.so .
|
$(ECHO) ln -sf python/build/lib.*/*.so .
|
||||||
|
|
||||||
|
|
||||||
janus:
|
janus:
|
||||||
$(MAKE) -C janus
|
$(MAKE) -C janus
|
||||||
@ ln -sf janus/*.so .
|
$(ECHO) ln -sf janus/*.so .
|
||||||
|
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
@@ -119,7 +118,7 @@ clean-all: linters clean
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
|
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
|
||||||
rm -f ustreamer ustreamer-dump *.so
|
rm -f *.bin *.so
|
||||||
$(MAKE) -C src clean
|
$(MAKE) -C src clean
|
||||||
$(MAKE) -C python clean
|
$(MAKE) -C python clean
|
||||||
$(MAKE) -C janus clean
|
$(MAKE) -C janus clean
|
||||||
|
|||||||
42
README.md
42
README.md
@@ -2,8 +2,6 @@
|
|||||||
[](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
|
[](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
|
||||||
[](https://discord.gg/bpmXfz5)
|
[](https://discord.gg/bpmXfz5)
|
||||||
|
|
||||||
[[Русская версия]](README.ru.md)
|
|
||||||
|
|
||||||
µStreamer is a lightweight and very quick server to stream [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
|
µStreamer is a lightweight and very quick server to stream [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
|
||||||
µStreamer is a part of the [PiKVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
|
µStreamer is a part of the [PiKVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
|
||||||
|
|
||||||
@@ -39,23 +37,23 @@ If you're going to live-stream from your backyard webcam and need to control it,
|
|||||||
## Building
|
## Building
|
||||||
You need to download the µStreamer onto your system and build it from the sources.
|
You need to download the µStreamer onto your system and build it from the sources.
|
||||||
|
|
||||||
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer.
|
* AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer.
|
||||||
|
* Fedora: https://src.fedoraproject.org/rpms/ustreamer.
|
||||||
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
* Ubuntu: https://packages.ubuntu.com/jammy/ustreamer.
|
||||||
|
* Debian: https://packages.debian.org/sid/ustreamer
|
||||||
|
* FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
||||||
|
|
||||||
### Preconditions
|
### Preconditions
|
||||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
|
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` 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 libjpeg9-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
|
* Raspberry OS Bullseye: `sudo apt install libevent-dev libjpeg62-turbo libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
|
||||||
|
* Raspberry OS Bookworm: same as previous but replace `libjpeg62-turbo` to `libjpeg62-turbo-dev`.
|
||||||
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
||||||
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
|
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
|
||||||
|
|
||||||
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```.
|
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```.
|
||||||
|
|
||||||
> **Note**
|
|
||||||
> Raspian: In case your version of Raspian is too told for there to be a libjpeg9 package, use `libjpeg8-dev` instead: `E: Package 'libjpeg9-dev' has no installation candidate`.
|
|
||||||
|
|
||||||
### Make
|
### Make
|
||||||
The most convenient process is to clone the µStreamer Git repository onto your system. If you don't have Git installed and don't want to install it either, you can download and unzip the sources from GitHub using `wget https://github.com/pikvm/ustreamer/archive/refs/heads/master.zip`.
|
The most convenient process is to clone the µStreamer Git repository onto your system. If you don't have Git installed and don't want to install it either, you can download and unzip the sources from GitHub using `wget https://github.com/pikvm/ustreamer/archive/refs/heads/master.zip`.
|
||||||
|
|
||||||
@@ -88,7 +86,7 @@ Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x4
|
|||||||
: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=\*`.
|
: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
|
```
|
||||||
$ ./ustreamer \
|
$ ./ustreamer \
|
||||||
--format=uyvy \ # Device input format
|
--format=uyvy \ # Device input format
|
||||||
--encoder=m2m-image \ # Hardware encoding on V4L2 M2M driver
|
--encoder=m2m-image \ # Hardware encoding on V4L2 M2M driver
|
||||||
@@ -115,7 +113,7 @@ dtoverlay=tc358743
|
|||||||
|
|
||||||
Check size of CMA:
|
Check size of CMA:
|
||||||
|
|
||||||
```bash
|
```
|
||||||
$ dmesg | grep cma-reserved
|
$ dmesg | grep cma-reserved
|
||||||
[ 0.000000] Memory: 7700524K/8244224K available (11772K kernel code, 1278K rwdata, 4320K rodata, 4096K init, 1077K bss, 281556K reserved, 262144K cma-reserved)
|
[ 0.000000] Memory: 7700524K/8244224K available (11772K kernel code, 1278K rwdata, 4320K rodata, 4096K init, 1077K bss, 281556K reserved, 262144K cma-reserved)
|
||||||
```
|
```
|
||||||
@@ -131,14 +129,14 @@ Save changes and reboot.
|
|||||||
## Launch
|
## Launch
|
||||||
Start container:
|
Start container:
|
||||||
|
|
||||||
```bash
|
```
|
||||||
$ docker run --device /dev/video0:/dev/video0 -e EDID=1 -p 8080:8080 pikvm/ustreamer:latest
|
$ docker run --device /dev/video0:/dev/video0 -e EDID=1 -p 8080:8080 pikvm/ustreamer:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Then access the web interface at port 8080 (e.g. http://raspberrypi.local:8080).
|
Then access the web interface at port 8080 (e.g. http://raspberrypi.local:8080).
|
||||||
|
|
||||||
## Custom config
|
## Custom config
|
||||||
```bash
|
```
|
||||||
$ docker run --rm pikvm/ustreamer:latest \
|
$ docker run --rm pikvm/ustreamer:latest \
|
||||||
--format=uyvy \
|
--format=uyvy \
|
||||||
--workers=3 \
|
--workers=3 \
|
||||||
@@ -148,13 +146,21 @@ $ docker run --rm pikvm/ustreamer:latest \
|
|||||||
```
|
```
|
||||||
|
|
||||||
## EDID
|
## EDID
|
||||||
Add `-e EDID=1` to set HDMI EDID before starging ustreamer. Use together with `-e EDID_HEX=xx` to specify custom EDID data.
|
Add `-e EDID=1` to set HDMI EDID before starting ustreamer. Use together with `-e EDID_HEX=xx` to specify custom EDID data.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Raspberry Pi Camera Example
|
# Raspberry Pi Camera Example
|
||||||
|
|
||||||
|
Example usage for the Raspberry Pi v3 camera (required `libcamerify` which is located in `libcamera-tools` and `libcamera-v4l2` (install both) on Raspbian):
|
||||||
|
```
|
||||||
|
$ sudo modprobe bcm2835-v4l2
|
||||||
|
$ libcamerify ./ustreamer --host :: --encoder=m2m-image
|
||||||
|
```
|
||||||
|
|
||||||
|
For v2 camera you can use the same trick with `libcamerify` but enable legacy camera mode in `raspi-config`.
|
||||||
|
|
||||||
Example usage for the Raspberry Pi v1 camera:
|
Example usage for the Raspberry Pi v1 camera:
|
||||||
```bash
|
```
|
||||||
$ sudo modprobe bcm2835-v4l2
|
$ sudo modprobe bcm2835-v4l2
|
||||||
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
||||||
```
|
```
|
||||||
@@ -163,7 +169,7 @@ $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
|||||||
|
|
||||||
:exclamation: If you get a poor framerate, it could be that the camera is switched to photo mode, which produces a low framerate (but a higher quality picture). This is because `bcm2835-v4l2` switches to photo mode at resolutions higher than `1280x720`. To work around this, pass the `max_video_width` and `max_video_height` module parameters like so:
|
:exclamation: If you get a poor framerate, it could be that the camera is switched to photo mode, which produces a low framerate (but a higher quality picture). This is because `bcm2835-v4l2` switches to photo mode at resolutions higher than `1280x720`. To work around this, pass the `max_video_width` and `max_video_height` module parameters like so:
|
||||||
|
|
||||||
```bash
|
```
|
||||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -174,7 +180,7 @@ $ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
|||||||
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server. See the [Janus integration guide](docs/h264.md) for full details.
|
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server. See the [Janus integration guide](docs/h264.md) for full details.
|
||||||
|
|
||||||
## Nginx
|
## Nginx
|
||||||
When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
|
When uStreamer is behind an Nginx proxy, its buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
location /stream {
|
location /stream {
|
||||||
@@ -203,7 +209,7 @@ v4l2 utilities provide the tools to manage USB webcam setting and information. S
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
# License
|
# License
|
||||||
Copyright (C) 2018-2022 by Maxim Devaev mdevaev@gmail.com
|
Copyright (C) 2018-2023 by Maxim Devaev mdevaev@gmail.com
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
145
README.ru.md
145
README.ru.md
@@ -1,145 +0,0 @@
|
|||||||
# µStreamer
|
|
||||||
[](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
|
|
||||||
[](https://discord.gg/bpmXfz5)
|
|
||||||
|
|
||||||
[[English version]](README.md)
|
|
||||||
|
|
||||||
|
|
||||||
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [PiKVM](https://github.com/pikvm/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
|
|
||||||
|
|
||||||
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
|
|
||||||
|
|
||||||
| **Фича** | **µStreamer** | **mjpg-streamer** |
|
|
||||||
|----------|---------------|-------------------|
|
|
||||||
| Многопоточное кодирование JPEG | ✔ | ✘ |
|
|
||||||
| Аппаратное кодирование на Raspberry Pi | ✔ | ✘ |
|
|
||||||
| Поведение при физическом отключении<br>устройства от сервера во время работы | ✔ Транслирует черный экран<br>с надписью ```NO SIGNAL```,<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> | ✘ |
|
|
||||||
| Стрим через UNIX domain socket | ✔ | ✘ |
|
|
||||||
| Systemd socket activation | ✔ | ✘ |
|
|
||||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ✔ | ✘ |
|
|
||||||
| Возможность сервить файлы встроенным<br>HTTP-сервером | ✔ | ☹ Нет каталогов |
|
|
||||||
| Вывод сигналов о состоянии стрима на GPIO<br>с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
|
|
||||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ✘ | ✔ |
|
|
||||||
| Совместимость с API mjpg-streamer'а | ✔ | :) |
|
|
||||||
|
|
||||||
Сносочки:
|
|
||||||
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
|
||||||
|
|
||||||
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
|
|
||||||
|
|
||||||
-----
|
|
||||||
# TL;DR
|
|
||||||
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
|
|
||||||
|
|
||||||
-----
|
|
||||||
# Сборка
|
|
||||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libbsd``` (только для Linux).
|
|
||||||
|
|
||||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
|
||||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Добавьте `libgpiod` для `WITH_GPIO=1` и `libsystemd-dev` для `WITH_SYSTEMD=1`.
|
|
||||||
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
|
||||||
|
|
||||||
Для включения сборки с поддержкой 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
|
|
||||||
$ cd ustreamer
|
|
||||||
$ make
|
|
||||||
$ ./ustreamer --help
|
|
||||||
```
|
|
||||||
|
|
||||||
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer.
|
|
||||||
|
|
||||||
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
|
|
||||||
|
|
||||||
-----
|
|
||||||
# Использование
|
|
||||||
**Для аппаратного кодирования M2M на Raspberry Pi, вам нужно ядро минимальной версии 5.15.32. Поддержка OpenMAX и MMAL для более старых ядер объявлена устаревшей и была удалена.**
|
|
||||||
|
|
||||||
Будучи запущенным без аргументов, ```ustreamer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://127.0.0.1:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--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:
|
|
||||||
```bash
|
|
||||||
$ ./ustreamer \
|
|
||||||
--format=uyvy \ # Настройка входного формата устройства
|
|
||||||
--encoder=m2m-image \ # Аппаратное кодирование с помощью драйвера V4L2 M2M
|
|
||||||
--workers=3 \ # Максимум воркеров
|
|
||||||
--persistent \ # Не переинициализировать устройство при таймауте (например, когда был отключен HDMI-кабель)
|
|
||||||
--dv-timings \ # Включение DV-таймингов
|
|
||||||
--drop-same-frames=30 # Экономим трафик
|
|
||||||
```
|
|
||||||
|
|
||||||
:exclamation: Обратите внимание, что для использования `--drop-same-frames` для разных браузеров нужно использовать ряд специальных параметров в `/stream` (за деталями обратитесь к урлу `/`).
|
|
||||||
|
|
||||||
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
|
||||||
|
|
||||||
-----
|
|
||||||
# Камера Raspberry Pi
|
|
||||||
Пример использования камеры Raspberry Pi v1:
|
|
||||||
```bash
|
|
||||||
$ sudo modprobe bcm2835-v4l2
|
|
||||||
$ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
|
||||||
```
|
|
||||||
|
|
||||||
:exclamation: Обратите внимание что боле новые модели камеры имеют другое максимальное разрешение. Список поддерживаемых разрешений можно найти в [документации PiCamera](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes).
|
|
||||||
|
|
||||||
:exclamation: Если камера выдает низкий фреймрейт, возможно что она работает в фото-режиме, где производит более низкий фпс, но более качественную кратинку. Это происходит потому что `bcm2835-v4l2` переключает камеру в фото-режим на разрешениях выше `1280x720`. Чтобы обойти это, передайте параметры `max_video_width` и `max_video_height` при загрузке модуля, например:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
|
||||||
```
|
|
||||||
|
|
||||||
-----
|
|
||||||
# Интеграция
|
|
||||||
|
|
||||||
## Nginx
|
|
||||||
Если uStreamer находится на Nginx, то последний будет буферизировать поток и создавать дополнительную задержку в стриме. Чтобы задержки не было, буферизацию можно отключить:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
location /stream {
|
|
||||||
postpone_output 0;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_ignore_headers X-Accel-Buffering;
|
|
||||||
proxy_pass http://ustreamer;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
-----
|
|
||||||
# Утилиты V4L2
|
|
||||||
V4L2 предоставляет ряд официальных утилит для управления USB-вебкамерами и получения информации об устройствах. С их помощью можно писать всякие настроечные скрипты и запускать их по крону, если, например, вам требуется изменять настройки экспозиции в зависимости от времени суток. Пакет с этими утилитами доступен на всех дистрибутивах Linux и обычно называется `v4l-utils`.
|
|
||||||
|
|
||||||
* Вывести список видеоустройств: `v4l2-ctl --list-devices`.
|
|
||||||
* Вывести список доступных контролов устройства: `v4l2-ctl -d /dev/video0 --list-ctrls`.
|
|
||||||
* Вывести список доступных форматов видео: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
|
|
||||||
* Показать текущее значение контрола: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
|
|
||||||
* Изменить значение контрола: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
|
|
||||||
|
|
||||||
Больше примеров вы можете найти [здесь](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/), а документацию в [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).
|
|
||||||
|
|
||||||
-----
|
|
||||||
# Смотрите также
|
|
||||||
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
|
|
||||||
|
|
||||||
-----
|
|
||||||
# Лицензия
|
|
||||||
Copyright (C) 2018-2022 by 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/.
|
|
||||||
50
docs/ssl/README.md
Normal file
50
docs/ssl/README.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Adding SSL
|
||||||
|
These days, browsers are not happy if you have HTTP content on an HTTPS page.
|
||||||
|
The browser will not show an HTTP stream on a page if the parent page is from a site which is using HTTPS.
|
||||||
|
|
||||||
|
The files in this folder configure an Nginx proxy in front of the µStreamer stream.
|
||||||
|
Using certbot, an SSL cert is created from Let's Encrypt and installed.
|
||||||
|
These scripts can be modified to add SSL to just about any HTTP server.
|
||||||
|
|
||||||
|
The scripts are not fire and forget.
|
||||||
|
They will require some pre-configuration and are interactive (you'll be asked questions while they're running).
|
||||||
|
They have been tested using the following setup.
|
||||||
|
1. A Raspberry Pi 4
|
||||||
|
1. µStreamer set up and running as a service
|
||||||
|
1. Internally on port 8080
|
||||||
|
1. Public port will be 5101
|
||||||
|
1. Verizon home Wi-Fi router
|
||||||
|
1. Domain registration from GoDaddy
|
||||||
|
|
||||||
|
## The Script
|
||||||
|
Below is an overview of the steps performed by `ssl-config.sh` (for Raspberry OS):
|
||||||
|
1. Install snapd - certbot uses this for installation
|
||||||
|
1. Install certbot
|
||||||
|
1. Get a free cert from Let's Encrypt using certbot
|
||||||
|
1. Install nginx
|
||||||
|
1. Configures nginx to proxy for µStreamer
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
1. Create a public DNS entry.
|
||||||
|
1. Pointing to the Pi itself or the public IP of the router behind which the Pi sits.
|
||||||
|
1. This would be managed in the domain registrar, such as GoDaddy.
|
||||||
|
1. Use a subdomain, such as `webcam.domain.com`
|
||||||
|
1. Port Forwarding
|
||||||
|
1. If using a Wi-Fi router, create a port forwarding rule which passes traffic from port 80 to the Pi. This is needed for certbot to ensure your DNS entry reaches the Pi, even if your final port will be something else.
|
||||||
|
1. Create a second rule for your final setup. For example, forward traffic from the router on port 5101 to the Pi's IP port 8080.
|
||||||
|
1. Update the ustreamer-proxy file in this folder
|
||||||
|
1. Replace `your.domain.com` with a fully qualified domain, it's three places in the proxy file.
|
||||||
|
1. Modify the line `listen 5101 ssl` port if needed. This is the public port, not the port on which the µStreamer service is running
|
||||||
|
1. Modify `proxy_pass http://127.0.0.1:8080;` with the working address of the internal µStreamer service.
|
||||||
|
1. Run the script
|
||||||
|
1. Stand buy, certbot asks some basic questions, such as email, domain, agree to terms, etc.
|
||||||
|
1. `bash ssl-config.sh`
|
||||||
|
1. Test your URL!
|
||||||
|
|
||||||
|
## Down the Road
|
||||||
|
Two important points to keep in mind for the future:
|
||||||
|
1. Dynamic IP - Most routers do not have a static IP address on the WAN side. So, if you reboot your router or if your internet provider gives you a new IP, you'll have to update the DNS entry.
|
||||||
|
1. Many routers have some sort of dynamic DNS feature. This would automatically update the DNS entry for you. That functionality is outside the scope of this document.
|
||||||
|
1. SSL Renewals - certbot automatically creates a task to renew the SSL cert before it expires. Assuming the Pi is running all the time, this shouldn't be an issue.
|
||||||
|
|
||||||
|
## Enjoy!
|
||||||
20
docs/ssl/ssl-config.sh
Normal file
20
docs/ssl/ssl-config.sh
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo -e "\e[32mInstalling snapd...\e[0m"
|
||||||
|
sudo apt install snapd -y
|
||||||
|
sudo snap install core
|
||||||
|
|
||||||
|
|
||||||
|
echo -e "\e[32mInstalling certbot, don't leave, it's going to ask questions...\e[0m"
|
||||||
|
sudo snap install --classic certbot
|
||||||
|
sudo ln -s /snap/bin/certbot /usr/bin/certbot
|
||||||
|
sudo certbot certonly --standalone
|
||||||
|
sudo certbot renew --dry-run
|
||||||
|
|
||||||
|
|
||||||
|
echo -e "\e[32mInstalling nginx...\e[0m"
|
||||||
|
sudo apt-get install nginx -y
|
||||||
|
sudo cp ustreamer-proxy /etc/nginx/sites-available/ustreamer-proxy
|
||||||
|
sudo ln -s /etc/nginx/sites-available/ustreamer-proxy /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
13
docs/ssl/ustreamer-proxy
Normal file
13
docs/ssl/ustreamer-proxy
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
server {
|
||||||
|
listen 5101 ssl;
|
||||||
|
server_name your.domain.com;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8080; # Change this to the uStreamer server address
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
docs/youtube.md
Normal file
97
docs/youtube.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
## Streaming to third party services
|
||||||
|
|
||||||
|
This method provides the ability of streaming a USB Webcam and include audio to large audiences.
|
||||||
|
It uses to two machines. One is a Raspberry Pi and the other a more capable machine to performance
|
||||||
|
the encoding of the video and audio that is streamed to the third party service such as YouTube.
|
||||||
|
|
||||||
|
Another benefit of using a browser (http stream) is the video can have overlays add in the custom ustreamer webpage.
|
||||||
|
For example a cron process that retrieves weather information and updates a file to include on the page, announcements,
|
||||||
|
or other creative ideas. The audio stream can also be something other than the webcam mic (music, voice files, etc.)
|
||||||
|
and easily changed on the second machine setup. In the following example filtering is applied in ffmpeg to
|
||||||
|
improve the sound of the webcam mic making vocals clearer and more intelligible.
|
||||||
|
|
||||||
|
* Machine 1:
|
||||||
|
* USB webcam on the machine (Pi for example) running ustreamer (video) and VLC (audio). Remember to make any needed firewall changes if machine 2 is on a separate network so it can reach the ports for the video and audio.
|
||||||
|
* To stream audio from the Pi.
|
||||||
|
```
|
||||||
|
/usr/bin/vlc -I dummy -vvv alsa://hw:2,0 --sout #transcode{acodec=mp3,ab=128}:standard{access=http,mux=ts,dst=:[PickAPort}
|
||||||
|
```
|
||||||
|
|
||||||
|
* Machine 2:
|
||||||
|
* On a more capable box run the video stream in a browser using ffmpeg to combine the video (browser) and audio and stream to YouTube or other services. In this example a VM with two virtual monitors running the browser full screen one of the monitors is used.
|
||||||
|
|
||||||
|
Script to stream the combination to YouTube:
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
KEY=$1
|
||||||
|
echo
|
||||||
|
echo Cleanup -------------------------------------------------
|
||||||
|
source live-yt.key
|
||||||
|
killall -9 ffmpeg
|
||||||
|
killall -9 chromium
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
echo Setup General--------------------------------------------
|
||||||
|
cd /home/[USER]
|
||||||
|
rm -f nohup.out
|
||||||
|
export DISPLAY=:0.0
|
||||||
|
export $(dbus-launch)
|
||||||
|
|
||||||
|
echo Setup Chromium-------------------------------------------
|
||||||
|
CHROMIUM_TEMP=/home/{USER]/tmp/chromium
|
||||||
|
rm -rf $CHROMIUM_TEMP.bak
|
||||||
|
mv $CHROMIUM_TEMP $CHROMIUM_TEMP.bak
|
||||||
|
mkdir -p $CHROMIUM_TEMP
|
||||||
|
|
||||||
|
echo Start Chromium ------------------------------------------
|
||||||
|
nohup /usr/lib/chromium/chromium \
|
||||||
|
--new-window "http://[ustreamerURL]" \
|
||||||
|
--start-fullscreen \
|
||||||
|
--disable \
|
||||||
|
--disable-translate \
|
||||||
|
--disable-infobars \
|
||||||
|
--disable-suggestions-service \
|
||||||
|
--disable-save-password-bubble \
|
||||||
|
--disable-new-tab-first-run \
|
||||||
|
--disable-session-crashed-bubble \
|
||||||
|
--disable-bundled-ppapi-flash \
|
||||||
|
--disable-gpu \
|
||||||
|
--enable-javascript \
|
||||||
|
--enable-user-scripts \
|
||||||
|
--disk-cache-dir=$CHROMIUM_TEMP/cache/ustreamer/ \
|
||||||
|
--user-data-dir=$CHROMIUM_TEMP/user_data/ustreamer/ \
|
||||||
|
--window-position=1440,12 \
|
||||||
|
>/dev/null 2>&1 &
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
echo Start FFMpeg---------------------------------------------
|
||||||
|
nohup /usr/bin/ffmpeg \
|
||||||
|
-loglevel level+warning \
|
||||||
|
-thread_queue_size 512 \
|
||||||
|
-framerate 30 \
|
||||||
|
-f x11grab \
|
||||||
|
-s 1920x1080 \
|
||||||
|
-probesize 42M \
|
||||||
|
-i :0.0+1024,0 \
|
||||||
|
-i http://[VLCaudioURL] \
|
||||||
|
-filter:a "volume=10, highpass=f=300, lowpass=f=2800" \
|
||||||
|
-c:v libx264 \
|
||||||
|
-pix_fmt yuv420p \
|
||||||
|
-g 60 \
|
||||||
|
-b:v 2500k \
|
||||||
|
-c:a libmp3lame \
|
||||||
|
-ar 44100 \
|
||||||
|
-b:a 32k \
|
||||||
|
-preset ultrafast \
|
||||||
|
-maxrate 5000k \
|
||||||
|
-bufsize 2500k \
|
||||||
|
-preset ultrafast \
|
||||||
|
-flvflags no_duration_filesize \
|
||||||
|
-f flv "rtmp://a.rtmp.youtube.com/live2/$KEY" \
|
||||||
|
>/home/{USER]/ff-audio.log 2>&1 &
|
||||||
|
|
||||||
|
echo Done ----------------------------------------------------
|
||||||
|
echo
|
||||||
|
```
|
||||||
|
|
||||||
|
*PS: Recipe by David Klippel*
|
||||||
@@ -31,13 +31,13 @@ endif
|
|||||||
# =====
|
# =====
|
||||||
$(_PLUGIN): $(_SRCS:%.c=$(_BUILD)/%.o)
|
$(_PLUGIN): $(_SRCS:%.c=$(_BUILD)/%.o)
|
||||||
$(info == SO $@)
|
$(info == SO $@)
|
||||||
@ $(CC) $^ -o $@ $(_LDFLAGS)
|
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS)
|
||||||
|
|
||||||
|
|
||||||
$(_BUILD)/%.o: %.c
|
$(_BUILD)/%.o: %.c
|
||||||
$(info -- CC $<)
|
$(info -- CC $<)
|
||||||
@ mkdir -p $(dir $@) || true
|
$(ECHO) mkdir -p $(dir $@) || true
|
||||||
@ $(CC) $< -o $@ $(_CFLAGS)
|
$(ECHO) $(CC) $< -o $@ $(_CFLAGS)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,6 +22,22 @@
|
|||||||
|
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <janus/plugins/plugin.h>
|
||||||
|
|
||||||
|
#include "uslibs/types.h"
|
||||||
|
#include "uslibs/tools.h"
|
||||||
|
#include "uslibs/threading.h"
|
||||||
|
#include "uslibs/list.h"
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
#include "queue.h"
|
||||||
|
#include "rtp.h"
|
||||||
|
|
||||||
|
|
||||||
static void *_video_thread(void *v_client);
|
static void *_video_thread(void *v_client);
|
||||||
static void *_audio_thread(void *v_client);
|
static void *_audio_thread(void *v_client);
|
||||||
@@ -38,7 +54,7 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
|
|||||||
|
|
||||||
atomic_init(&client->stop, false);
|
atomic_init(&client->stop, false);
|
||||||
|
|
||||||
client->video_queue = us_queue_init(1024);
|
client->video_queue = us_queue_init(2048);
|
||||||
US_THREAD_CREATE(client->video_tid, _video_thread, client);
|
US_THREAD_CREATE(client->video_tid, _video_thread, client);
|
||||||
|
|
||||||
client->audio_queue = us_queue_init(64);
|
client->audio_queue = us_queue_init(64);
|
||||||
@@ -99,16 +115,16 @@ static void *_common_thread(void *v_client, bool video) {
|
|||||||
atomic_load(&client->transmit)
|
atomic_load(&client->transmit)
|
||||||
&& (video || atomic_load(&client->transmit_audio))
|
&& (video || atomic_load(&client->transmit_audio))
|
||||||
) {
|
) {
|
||||||
janus_plugin_rtp packet = {0};
|
janus_plugin_rtp packet = {
|
||||||
packet.video = rtp->video;
|
.video = rtp->video,
|
||||||
packet.buffer = (char *)rtp->datagram;
|
.buffer = (char *)rtp->datagram,
|
||||||
packet.length = rtp->used;
|
.length = rtp->used,
|
||||||
# if JANUS_PLUGIN_API_VERSION >= 100
|
# if JANUS_PLUGIN_API_VERSION >= 100
|
||||||
// The uStreamer Janus plugin places video in stream index 0 and audio
|
// The uStreamer Janus plugin places video in stream index 0 and audio
|
||||||
// (if available) in stream index 1.
|
// (if available) in stream index 1.
|
||||||
packet.mindex = (rtp->video ? 0 : 1);
|
.mindex = (rtp->video ? 0 : 1),
|
||||||
# endif
|
# endif
|
||||||
|
};
|
||||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||||
/*if (rtp->zero_playout_delay) {
|
/*if (rtp->zero_playout_delay) {
|
||||||
// https://github.com/pikvm/pikvm/issues/784
|
// https://github.com/pikvm/pikvm/issues/784
|
||||||
@@ -116,7 +132,9 @@ static void *_common_thread(void *v_client, bool video) {
|
|||||||
packet.extensions.max_delay = 0;
|
packet.extensions.max_delay = 0;
|
||||||
} else {
|
} else {
|
||||||
packet.extensions.min_delay = 0;
|
packet.extensions.min_delay = 0;
|
||||||
packet.extensions.max_delay = 1000;
|
// 10s - Chromium/WebRTC default
|
||||||
|
// 3s - Firefox default
|
||||||
|
packet.extensions.max_delay = 300; // == 3s, i.e. 10ms granularity
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
client->gw->relay_rtp(client->session, &packet);
|
client->gw->relay_rtp(client->session, &packet);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,19 +22,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <janus/plugins/plugin.h>
|
#include <janus/plugins/plugin.h>
|
||||||
|
|
||||||
#include "uslibs/tools.h"
|
#include "uslibs/types.h"
|
||||||
#include "uslibs/threading.h"
|
|
||||||
#include "uslibs/list.h"
|
#include "uslibs/list.h"
|
||||||
|
|
||||||
#include "logging.h"
|
|
||||||
#include "queue.h"
|
#include "queue.h"
|
||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,6 +22,17 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <janus/config.h>
|
||||||
|
#include <janus/plugins/plugin.h>
|
||||||
|
|
||||||
|
#include "uslibs/tools.h"
|
||||||
|
|
||||||
|
#include "const.h"
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
|
||||||
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
|
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
|
||||||
// static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
|
// static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
|
||||||
@@ -59,13 +70,14 @@ us_config_s *us_config_init(const char *config_dir_path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
goto ok;
|
goto ok;
|
||||||
error:
|
|
||||||
us_config_destroy(config);
|
error:
|
||||||
config = NULL;
|
US_DELETE(config, us_config_destroy);
|
||||||
ok:
|
|
||||||
US_DELETE(jcfg, janus_config_destroy);
|
ok:
|
||||||
free(config_file_path);
|
US_DELETE(jcfg, janus_config_destroy);
|
||||||
return config;
|
free(config_file_path);
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
void us_config_destroy(us_config_s *config) {
|
void us_config_destroy(us_config_s *config) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,17 +22,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <janus/config.h>
|
|
||||||
#include <janus/plugins/plugin.h>
|
|
||||||
|
|
||||||
#include "uslibs/tools.h"
|
|
||||||
|
|
||||||
#include "const.h"
|
|
||||||
#include "logging.h"
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *video_sink_name;
|
char *video_sink_name;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,13 +22,24 @@
|
|||||||
|
|
||||||
#include "memsinkfd.h"
|
#include "memsinkfd.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id) {
|
#include <linux/videodev2.h>
|
||||||
const long double deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
|
|
||||||
long double now;
|
#include "uslibs/types.h"
|
||||||
|
#include "uslibs/tools.h"
|
||||||
|
#include "uslibs/frame.h"
|
||||||
|
#include "uslibs/memsinksh.h"
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
|
||||||
|
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, u64 last_id) {
|
||||||
|
const ldf deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
|
||||||
|
ldf now_ts;
|
||||||
do {
|
do {
|
||||||
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
|
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
|
||||||
now = us_get_now_monotonic();
|
now_ts = us_get_now_monotonic();
|
||||||
if (result < 0 && errno != EWOULDBLOCK) {
|
if (result < 0 && errno != EWOULDBLOCK) {
|
||||||
US_JLOG_PERROR("video", "Can't lock memsink");
|
US_JLOG_PERROR("video", "Can't lock memsink");
|
||||||
return -1;
|
return -1;
|
||||||
@@ -42,11 +53,11 @@ int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
usleep(1000); // lock_polling
|
usleep(1000); // lock_polling
|
||||||
} while (now < deadline_ts);
|
} while (now_ts < deadline_ts);
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required) {
|
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, u64 *frame_id, bool key_required) {
|
||||||
us_frame_s *frame = us_frame_init();
|
us_frame_s *frame = us_frame_init();
|
||||||
us_frame_set_data(frame, mem->data, mem->used);
|
us_frame_set_data(frame, mem->data, mem->used);
|
||||||
US_FRAME_COPY_META(mem, frame);
|
US_FRAME_COPY_META(mem, frame);
|
||||||
@@ -66,8 +77,7 @@ us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *
|
|||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
us_frame_destroy(frame);
|
US_DELETE(frame, us_frame_destroy);
|
||||||
frame = NULL;
|
|
||||||
}
|
}
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,18 +22,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include "uslibs/types.h"
|
||||||
#include <stdint.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <linux/videodev2.h>
|
|
||||||
|
|
||||||
#include "uslibs/tools.h"
|
|
||||||
#include "uslibs/frame.h"
|
#include "uslibs/frame.h"
|
||||||
#include "uslibs/memsinksh.h"
|
#include "uslibs/memsinksh.h"
|
||||||
|
|
||||||
#include "logging.h"
|
|
||||||
|
|
||||||
|
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, u64 last_id);
|
||||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id);
|
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, u64 *frame_id, bool key_required);
|
||||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required);
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -20,8 +20,6 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
@@ -37,6 +35,7 @@
|
|||||||
#include <janus/plugins/plugin.h>
|
#include <janus/plugins/plugin.h>
|
||||||
#include <janus/rtcp.h>
|
#include <janus/rtcp.h>
|
||||||
|
|
||||||
|
#include "uslibs/types.h"
|
||||||
#include "uslibs/const.h"
|
#include "uslibs/const.h"
|
||||||
#include "uslibs/tools.h"
|
#include "uslibs/tools.h"
|
||||||
#include "uslibs/threading.h"
|
#include "uslibs/threading.h"
|
||||||
@@ -56,8 +55,8 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
static us_config_s *_g_config = NULL;
|
static us_config_s *_g_config = NULL;
|
||||||
const useconds_t _g_watchers_polling = 100000;
|
static const useconds_t _g_watchers_polling = 100000;
|
||||||
|
|
||||||
static us_janus_client_s *_g_clients = NULL;
|
static us_janus_client_s *_g_clients = NULL;
|
||||||
static janus_callbacks *_g_gw = NULL;
|
static janus_callbacks *_g_gw = NULL;
|
||||||
@@ -99,7 +98,9 @@ static atomic_bool _g_key_required = false;
|
|||||||
janus_plugin *create(void);
|
janus_plugin *create(void);
|
||||||
|
|
||||||
|
|
||||||
static void *_video_rtp_thread(UNUSED void *arg) {
|
static void *_video_rtp_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
US_THREAD_RENAME("us_video_rtp");
|
US_THREAD_RENAME("us_video_rtp");
|
||||||
atomic_store(&_g_video_rtp_tid_created, true);
|
atomic_store(&_g_video_rtp_tid_created, true);
|
||||||
|
|
||||||
@@ -107,7 +108,8 @@ static void *_video_rtp_thread(UNUSED void *arg) {
|
|||||||
us_frame_s *frame;
|
us_frame_s *frame;
|
||||||
if (us_queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
|
if (us_queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
|
||||||
_LOCK_VIDEO;
|
_LOCK_VIDEO;
|
||||||
us_rtpv_wrap(_g_rtpv, frame);
|
const bool zero_playout_delay = (frame->gop == 0);
|
||||||
|
us_rtpv_wrap(_g_rtpv, frame, zero_playout_delay);
|
||||||
_UNLOCK_VIDEO;
|
_UNLOCK_VIDEO;
|
||||||
us_frame_destroy(frame);
|
us_frame_destroy(frame);
|
||||||
}
|
}
|
||||||
@@ -115,11 +117,13 @@ static void *_video_rtp_thread(UNUSED void *arg) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *_video_sink_thread(UNUSED void *arg) {
|
static void *_video_sink_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
US_THREAD_RENAME("us_video_sink");
|
US_THREAD_RENAME("us_video_sink");
|
||||||
atomic_store(&_g_video_sink_tid_created, true);
|
atomic_store(&_g_video_sink_tid_created, true);
|
||||||
|
|
||||||
uint64_t frame_id = 0;
|
u64 frame_id = 0;
|
||||||
int once = 0;
|
int once = 0;
|
||||||
|
|
||||||
while (!_STOP) {
|
while (!_STOP) {
|
||||||
@@ -164,20 +168,18 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close_memsink:
|
close_memsink:
|
||||||
if (mem != NULL) {
|
US_DELETE(mem, us_memsink_shared_unmap);
|
||||||
US_JLOG_INFO("video", "Memsink closed");
|
US_CLOSE_FD(fd, close);
|
||||||
us_memsink_shared_unmap(mem);
|
US_JLOG_INFO("video", "Memsink closed");
|
||||||
}
|
sleep(1); // error_delay
|
||||||
if (fd >= 0) {
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
sleep(1); // error_delay
|
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *_audio_thread(UNUSED void *arg) {
|
static void *_audio_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
US_THREAD_RENAME("us_audio");
|
US_THREAD_RENAME("us_audio");
|
||||||
atomic_store(&_g_audio_tid_created, true);
|
atomic_store(&_g_audio_tid_created, true);
|
||||||
assert(_g_config->audio_dev_name != NULL);
|
assert(_g_config->audio_dev_name != NULL);
|
||||||
@@ -217,9 +219,9 @@ static void *_audio_thread(UNUSED void *arg) {
|
|||||||
goto close_audio;
|
goto close_audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
|
uz size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
|
||||||
uint8_t data[size];
|
u8 data[size];
|
||||||
uint64_t pts;
|
u64 pts;
|
||||||
const int result = us_audio_get_encoded(audio, data, &size, &pts);
|
const int result = us_audio_get_encoded(audio, data, &size, &pts);
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
_LOCK_AUDIO;
|
_LOCK_AUDIO;
|
||||||
@@ -230,9 +232,9 @@ static void *_audio_thread(UNUSED void *arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close_audio:
|
close_audio:
|
||||||
US_DELETE(audio, us_audio_destroy);
|
US_DELETE(audio, us_audio_destroy);
|
||||||
sleep(1); // error_delay
|
sleep(1); // error_delay
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -343,7 +345,8 @@ static json_t *_plugin_query_session(janus_plugin_session *session) {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
|
static void _set_transmit(janus_plugin_session *session, const char *msg, bool transmit) {
|
||||||
|
(void)msg;
|
||||||
_IF_DISABLED({ return; });
|
_IF_DISABLED({ return; });
|
||||||
_LOCK_ALL;
|
_LOCK_ALL;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
@@ -493,16 +496,18 @@ static struct janus_plugin_result *_plugin_handle_message(
|
|||||||
PUSH_ERROR(405, "Not implemented");
|
PUSH_ERROR(405, "Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
ok_wait:
|
ok_wait:
|
||||||
FREE_MSG_JSEP;
|
FREE_MSG_JSEP;
|
||||||
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
|
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
|
||||||
|
|
||||||
# undef PUSH_STATUS
|
# undef PUSH_STATUS
|
||||||
# undef PUSH_ERROR
|
# undef PUSH_ERROR
|
||||||
# undef FREE_MSG_JSEP
|
# undef FREE_MSG_JSEP
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _plugin_incoming_rtcp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtcp *packet) {
|
static void _plugin_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet) {
|
||||||
|
(void)handle;
|
||||||
|
(void)packet;
|
||||||
if (packet->video && janus_rtcp_has_pli(packet->buffer, packet->length)) {
|
if (packet->video && janus_rtcp_has_pli(packet->buffer, packet->length)) {
|
||||||
// US_JLOG_INFO("main", "Got video PLI");
|
// US_JLOG_INFO("main", "Got video PLI");
|
||||||
atomic_store(&_g_key_required, true);
|
atomic_store(&_g_key_required, true);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,8 +22,18 @@
|
|||||||
|
|
||||||
#include "queue.h"
|
#include "queue.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
us_queue_s *us_queue_init(unsigned capacity) {
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include "uslibs/types.h"
|
||||||
|
#include "uslibs/tools.h"
|
||||||
|
#include "uslibs/threading.h"
|
||||||
|
|
||||||
|
|
||||||
|
us_queue_s *us_queue_init(uint capacity) {
|
||||||
us_queue_s *queue;
|
us_queue_s *queue;
|
||||||
US_CALLOC(queue, 1);
|
US_CALLOC(queue, 1);
|
||||||
US_CALLOC(queue->items, capacity);
|
US_CALLOC(queue->items, capacity);
|
||||||
@@ -61,7 +71,7 @@ void us_queue_destroy(us_queue_s *queue) {
|
|||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
|
int us_queue_put(us_queue_s *queue, void *item, ldf timeout) {
|
||||||
US_MUTEX_LOCK(queue->mutex);
|
US_MUTEX_LOCK(queue->mutex);
|
||||||
if (timeout == 0) {
|
if (timeout == 0) {
|
||||||
if (queue->size == queue->capacity) {
|
if (queue->size == queue->capacity) {
|
||||||
@@ -80,7 +90,7 @@ int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
|
int us_queue_get(us_queue_s *queue, void **item, ldf timeout) {
|
||||||
US_MUTEX_LOCK(queue->mutex);
|
US_MUTEX_LOCK(queue->mutex);
|
||||||
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond);
|
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond);
|
||||||
*item = queue->items[queue->out];
|
*item = queue->items[queue->out];
|
||||||
@@ -96,7 +106,7 @@ int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
|
|||||||
|
|
||||||
int us_queue_get_free(us_queue_s *queue) {
|
int us_queue_get_free(us_queue_s *queue) {
|
||||||
US_MUTEX_LOCK(queue->mutex);
|
US_MUTEX_LOCK(queue->mutex);
|
||||||
const unsigned size = queue->size;
|
const uint size = queue->size;
|
||||||
US_MUTEX_UNLOCK(queue->mutex);
|
US_MUTEX_UNLOCK(queue->mutex);
|
||||||
return queue->capacity - size;
|
return queue->capacity - size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,24 +22,20 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include "uslibs/types.h"
|
||||||
#include "uslibs/tools.h"
|
#include "uslibs/tools.h"
|
||||||
#include "uslibs/threading.h"
|
|
||||||
|
|
||||||
|
|
||||||
// Based on https://github.com/seifzadeh/c-pthread-queue/blob/master/queue.h
|
// Based on https://github.com/seifzadeh/c-pthread-queue/blob/master/queue.h
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void **items;
|
void **items;
|
||||||
unsigned size;
|
uint size;
|
||||||
unsigned capacity;
|
uint capacity;
|
||||||
unsigned in;
|
uint in;
|
||||||
unsigned out;
|
uint out;
|
||||||
|
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
pthread_cond_t full_cond;
|
pthread_cond_t full_cond;
|
||||||
@@ -60,9 +56,9 @@ typedef struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
us_queue_s *us_queue_init(unsigned capacity);
|
us_queue_s *us_queue_init(uint capacity);
|
||||||
void us_queue_destroy(us_queue_s *queue);
|
void us_queue_destroy(us_queue_s *queue);
|
||||||
|
|
||||||
int us_queue_put(us_queue_s *queue, void *item, long double timeout);
|
int us_queue_put(us_queue_s *queue, void *item, ldf timeout);
|
||||||
int us_queue_get(us_queue_s *queue, void **item, long double timeout);
|
int us_queue_get(us_queue_s *queue, void **item, ldf timeout);
|
||||||
int us_queue_get_free(us_queue_s *queue);
|
int us_queue_get_free(us_queue_s *queue);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# This source file is partially based on this code: #
|
# This source file is partially based on this code: #
|
||||||
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
|
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -25,8 +25,13 @@
|
|||||||
|
|
||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
us_rtp_s *us_rtp_init(unsigned payload, bool video) {
|
#include "uslibs/types.h"
|
||||||
|
#include "uslibs/tools.h"
|
||||||
|
|
||||||
|
|
||||||
|
us_rtp_s *us_rtp_init(uint payload, bool video) {
|
||||||
us_rtp_s *rtp;
|
us_rtp_s *rtp;
|
||||||
US_CALLOC(rtp, 1);
|
US_CALLOC(rtp, 1);
|
||||||
rtp->payload = payload;
|
rtp->payload = payload;
|
||||||
@@ -46,8 +51,8 @@ void us_rtp_destroy(us_rtp_s *rtp) {
|
|||||||
free(rtp);
|
free(rtp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
|
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked) {
|
||||||
uint32_t word0 = 0x80000000;
|
u32 word0 = 0x80000000;
|
||||||
if (marked) {
|
if (marked) {
|
||||||
word0 |= 1 << 23;
|
word0 |= 1 << 23;
|
||||||
}
|
}
|
||||||
@@ -55,7 +60,8 @@ void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
|
|||||||
word0 |= rtp->seq;
|
word0 |= rtp->seq;
|
||||||
++rtp->seq;
|
++rtp->seq;
|
||||||
|
|
||||||
# define WRITE_BE_U32(_offset, _value) *((uint32_t *)(rtp->datagram + _offset)) = __builtin_bswap32(_value)
|
# define WRITE_BE_U32(x_offset, x_value) \
|
||||||
|
*((u32 *)(rtp->datagram + x_offset)) = __builtin_bswap32(x_value)
|
||||||
WRITE_BE_U32(0, word0);
|
WRITE_BE_U32(0, word0);
|
||||||
WRITE_BE_U32(4, pts);
|
WRITE_BE_U32(4, pts);
|
||||||
WRITE_BE_U32(8, rtp->ssrc);
|
WRITE_BE_U32(8, rtp->ssrc);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,13 +22,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include "uslibs/types.h"
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "uslibs/tools.h"
|
|
||||||
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
|
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
|
||||||
@@ -37,21 +31,21 @@
|
|||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned payload;
|
uint payload;
|
||||||
bool video;
|
bool video;
|
||||||
uint32_t ssrc;
|
u32 ssrc;
|
||||||
|
|
||||||
uint16_t seq;
|
u16 seq;
|
||||||
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
|
u8 datagram[US_RTP_DATAGRAM_SIZE];
|
||||||
size_t used;
|
uz used;
|
||||||
bool zero_playout_delay;
|
bool zero_playout_delay;
|
||||||
} us_rtp_s;
|
} us_rtp_s;
|
||||||
|
|
||||||
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
|
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
|
||||||
|
|
||||||
|
|
||||||
us_rtp_s *us_rtp_init(unsigned payload, bool video);
|
us_rtp_s *us_rtp_init(uint payload, bool video);
|
||||||
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
|
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
|
||||||
void us_rtp_destroy(us_rtp_s *rtp);
|
void us_rtp_destroy(us_rtp_s *rtp);
|
||||||
|
|
||||||
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked);
|
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,6 +22,12 @@
|
|||||||
|
|
||||||
#include "rtpa.h"
|
#include "rtpa.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "uslibs/types.h"
|
||||||
|
#include "uslibs/tools.h"
|
||||||
|
|
||||||
|
|
||||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
|
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
|
||||||
us_rtpa_s *rtpa;
|
us_rtpa_s *rtpa;
|
||||||
@@ -37,7 +43,7 @@ void us_rtpa_destroy(us_rtpa_s *rtpa) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
|
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
|
||||||
# define PAYLOAD rtpa->rtp->payload
|
const uint pl = rtpa->rtp->payload;
|
||||||
char *sdp;
|
char *sdp;
|
||||||
US_ASPRINTF(sdp,
|
US_ASPRINTF(sdp,
|
||||||
"m=audio 1 RTP/SAVPF %u" RN
|
"m=audio 1 RTP/SAVPF %u" RN
|
||||||
@@ -49,14 +55,13 @@ char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
|
|||||||
"a=rtcp-fb:%u goog-remb" RN
|
"a=rtcp-fb:%u goog-remb" RN
|
||||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||||
"a=sendonly" RN,
|
"a=sendonly" RN,
|
||||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, // PAYLOAD,
|
pl, pl, pl, pl, pl, // pl,
|
||||||
rtpa->rtp->ssrc
|
rtpa->rtp->ssrc
|
||||||
);
|
);
|
||||||
# undef PAYLOAD
|
|
||||||
return sdp;
|
return sdp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
|
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts) {
|
||||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||||
us_rtp_write_header(rtpa->rtp, pts, false);
|
us_rtp_write_header(rtpa->rtp, pts, false);
|
||||||
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);
|
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,15 +22,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include "uslibs/types.h"
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "uslibs/tools.h"
|
|
||||||
#include "uslibs/threading.h"
|
|
||||||
|
|
||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
|
|
||||||
@@ -45,4 +37,4 @@ us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
|
|||||||
void us_rtpa_destroy(us_rtpa_s *rtpa);
|
void us_rtpa_destroy(us_rtpa_s *rtpa);
|
||||||
|
|
||||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa);
|
char *us_rtpa_make_sdp(us_rtpa_s *rtpa);
|
||||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);
|
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# This source file is partially based on this code: #
|
# This source file is partially based on this code: #
|
||||||
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
|
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -25,10 +25,20 @@
|
|||||||
|
|
||||||
#include "rtpv.h"
|
#include "rtpv.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
#include "uslibs/types.h"
|
||||||
|
#include "uslibs/tools.h"
|
||||||
|
#include "uslibs/frame.h"
|
||||||
|
|
||||||
|
|
||||||
|
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked);
|
||||||
|
|
||||||
|
static sz _find_annexb(const u8 *data, uz size);
|
||||||
|
|
||||||
|
|
||||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback) {
|
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback) {
|
||||||
@@ -45,9 +55,9 @@ void us_rtpv_destroy(us_rtpv_s *rtpv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||||
# define PAYLOAD rtpv->rtp->payload
|
|
||||||
// https://tools.ietf.org/html/rfc6184
|
// https://tools.ietf.org/html/rfc6184
|
||||||
// https://github.com/meetecho/janus-gateway/issues/2443
|
// https://github.com/meetecho/janus-gateway/issues/2443
|
||||||
|
const uint pl = rtpv->rtp->payload;
|
||||||
char *sdp;
|
char *sdp;
|
||||||
US_ASPRINTF(sdp,
|
US_ASPRINTF(sdp,
|
||||||
"m=video 1 RTP/SAVPF %u" RN
|
"m=video 1 RTP/SAVPF %u" RN
|
||||||
@@ -61,38 +71,37 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
|||||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||||
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
|
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
|
||||||
"a=sendonly" RN,
|
"a=sendonly" RN,
|
||||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
pl, pl, pl, pl,
|
||||||
PAYLOAD, PAYLOAD, PAYLOAD,
|
pl, pl, pl,
|
||||||
rtpv->rtp->ssrc
|
rtpv->rtp->ssrc
|
||||||
);
|
);
|
||||||
return sdp;
|
return sdp;
|
||||||
# undef PAYLOAD
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define _PRE 3 // Annex B prefix length
|
#define _PRE 3 // Annex B prefix length
|
||||||
|
|
||||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
|
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay) {
|
||||||
// There is a complicated logic here but everything works as it should:
|
// There is a complicated logic here but everything works as it should:
|
||||||
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
|
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
|
||||||
|
|
||||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||||
|
|
||||||
rtpv->rtp->zero_playout_delay = (frame->gop == 0);
|
rtpv->rtp->zero_playout_delay = zero_playout_delay;
|
||||||
|
|
||||||
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
const u32 pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||||
ssize_t last_offset = -_PRE;
|
sz last_offset = -_PRE;
|
||||||
|
|
||||||
while (true) { // Find and iterate by nalus
|
while (true) { // Find and iterate by nalus
|
||||||
const size_t next_start = last_offset + _PRE;
|
const uz next_start = last_offset + _PRE;
|
||||||
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
|
sz offset = _find_annexb(frame->data + next_start, frame->used - next_start);
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
offset += next_start;
|
offset += next_start;
|
||||||
|
|
||||||
if (last_offset >= 0) {
|
if (last_offset >= 0) {
|
||||||
const uint8_t *const data = frame->data + last_offset + _PRE;
|
const u8 *const data = frame->data + last_offset + _PRE;
|
||||||
size_t size = offset - last_offset - _PRE;
|
uz size = offset - last_offset - _PRE;
|
||||||
if (data[size - 1] == 0) { // Check for extra 00
|
if (data[size - 1] == 0) { // Check for extra 00
|
||||||
--size;
|
--size;
|
||||||
}
|
}
|
||||||
@@ -103,34 +112,33 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (last_offset >= 0) {
|
if (last_offset >= 0) {
|
||||||
const uint8_t *const data = frame->data + last_offset + _PRE;
|
const u8 *const data = frame->data + last_offset + _PRE;
|
||||||
size_t size = frame->used - last_offset - _PRE;
|
uz size = frame->used - last_offset - _PRE;
|
||||||
_rtpv_process_nalu(rtpv, data, size, pts, true);
|
_rtpv_process_nalu(rtpv, data, size, pts, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
|
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked) {
|
||||||
# define DG rtpv->rtp->datagram
|
const uint ref_idc = (data[0] >> 5) & 3;
|
||||||
|
const uint type = data[0] & 0x1F;
|
||||||
const unsigned ref_idc = (data[0] >> 5) & 3;
|
u8 *dg = rtpv->rtp->datagram;
|
||||||
const unsigned type = data[0] & 0x1F;
|
|
||||||
|
|
||||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||||
us_rtp_write_header(rtpv->rtp, pts, marked);
|
us_rtp_write_header(rtpv->rtp, pts, marked);
|
||||||
memcpy(DG + US_RTP_HEADER_SIZE, data, size);
|
memcpy(dg + US_RTP_HEADER_SIZE, data, size);
|
||||||
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
|
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
|
||||||
rtpv->callback(rtpv->rtp);
|
rtpv->callback(rtpv->rtp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
|
const uz fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
|
||||||
|
|
||||||
const uint8_t *src = data + 1;
|
const u8 *src = data + 1;
|
||||||
ssize_t remaining = size - 1;
|
sz remaining = size - 1;
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
ssize_t frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
|
sz frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
|
||||||
const bool last = (remaining <= frag_size);
|
const bool last = (remaining <= frag_size);
|
||||||
if (last) {
|
if (last) {
|
||||||
frag_size = remaining;
|
frag_size = remaining;
|
||||||
@@ -138,18 +146,18 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint3
|
|||||||
|
|
||||||
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
|
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
|
||||||
|
|
||||||
DG[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
|
dg[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
|
||||||
|
|
||||||
uint8_t fu = type;
|
u8 fu = type;
|
||||||
if (first) {
|
if (first) {
|
||||||
fu |= 0x80;
|
fu |= 0x80;
|
||||||
}
|
}
|
||||||
if (last) {
|
if (last) {
|
||||||
fu |= 0x40;
|
fu |= 0x40;
|
||||||
}
|
}
|
||||||
DG[US_RTP_HEADER_SIZE + 1] = fu;
|
dg[US_RTP_HEADER_SIZE + 1] = fu;
|
||||||
|
|
||||||
memcpy(DG + fu_overhead, src, frag_size);
|
memcpy(dg + fu_overhead, src, frag_size);
|
||||||
rtpv->rtp->used = fu_overhead + frag_size;
|
rtpv->rtp->used = fu_overhead + frag_size;
|
||||||
rtpv->callback(rtpv->rtp);
|
rtpv->callback(rtpv->rtp);
|
||||||
|
|
||||||
@@ -157,14 +165,12 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint3
|
|||||||
remaining -= frag_size;
|
remaining -= frag_size;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
# undef DG
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
|
static sz _find_annexb(const u8 *data, uz size) {
|
||||||
// Parses buffer for 00 00 01 start codes
|
// Parses buffer for 00 00 01 start codes
|
||||||
if (size >= _PRE) {
|
if (size >= _PRE) {
|
||||||
for (size_t index = 0; index <= size - _PRE; ++index) {
|
for (uz index = 0; index <= size - _PRE; ++index) {
|
||||||
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
|
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,16 +22,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include "uslibs/types.h"
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <linux/videodev2.h>
|
|
||||||
|
|
||||||
#include "uslibs/tools.h"
|
|
||||||
#include "uslibs/frame.h"
|
#include "uslibs/frame.h"
|
||||||
|
|
||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
@@ -47,4 +38,4 @@ us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback);
|
|||||||
void us_rtpv_destroy(us_rtpv_s *rtpv);
|
void us_rtpv_destroy(us_rtpv_s *rtpv);
|
||||||
|
|
||||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
|
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
|
||||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame);
|
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,6 +22,18 @@
|
|||||||
|
|
||||||
#include "tc358743.h"
|
#include "tc358743.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <linux/videodev2.h>
|
||||||
|
#include <linux/v4l2-controls.h>
|
||||||
|
|
||||||
|
#include "uslibs/types.h"
|
||||||
|
#include "uslibs/tools.h"
|
||||||
|
#include "uslibs/xioctl.h"
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
|
||||||
#ifndef V4L2_CID_USER_TC358743_BASE
|
#ifndef V4L2_CID_USER_TC358743_BASE
|
||||||
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
|
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
|
||||||
@@ -44,8 +56,7 @@ int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# define READ_CID(x_cid, x_field) { \
|
# define READ_CID(x_cid, x_field) { \
|
||||||
struct v4l2_control m_ctl = {0}; \
|
struct v4l2_control m_ctl = {.id = x_cid}; \
|
||||||
m_ctl.id = x_cid; \
|
|
||||||
if (us_xioctl(fd, VIDIOC_G_CTRL, &m_ctl) < 0) { \
|
if (us_xioctl(fd, VIDIOC_G_CTRL, &m_ctl) < 0) { \
|
||||||
US_JLOG_PERROR("audio", "Can't get value of " #x_cid); \
|
US_JLOG_PERROR("audio", "Can't get value of " #x_cid); \
|
||||||
close(fd); \
|
close(fd); \
|
||||||
@@ -53,10 +64,8 @@ int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
|
|||||||
} \
|
} \
|
||||||
info->x_field = m_ctl.value; \
|
info->x_field = m_ctl.value; \
|
||||||
}
|
}
|
||||||
|
|
||||||
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
|
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
|
||||||
READ_CID(TC358743_CID_AUDIO_SAMPLING_RATE, audio_hz);
|
READ_CID(TC358743_CID_AUDIO_SAMPLING_RATE, audio_hz);
|
||||||
|
|
||||||
# undef READ_CID
|
# undef READ_CID
|
||||||
|
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,24 +22,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <unistd.h>
|
#include "uslibs/types.h"
|
||||||
#include <fcntl.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include <linux/videodev2.h>
|
|
||||||
#include <linux/v4l2-controls.h>
|
|
||||||
|
|
||||||
#include "uslibs/tools.h"
|
|
||||||
#include "uslibs/xioctl.h"
|
|
||||||
|
|
||||||
#include "logging.h"
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool has_audio;
|
bool has_audio;
|
||||||
unsigned audio_hz;
|
uint audio_hz;
|
||||||
} us_tc358743_info_s;
|
} us_tc358743_info_s;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
44
janus/src/uslibs/types.h
Normal file
44
janus/src/uslibs/types.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef long long sll;
|
||||||
|
typedef ssize_t sz;
|
||||||
|
typedef int8_t s8;
|
||||||
|
typedef int16_t s16;
|
||||||
|
typedef int32_t s32;
|
||||||
|
typedef int64_t s64;
|
||||||
|
|
||||||
|
typedef unsigned uint;
|
||||||
|
typedef unsigned long long ull;
|
||||||
|
typedef size_t uz;
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint16_t u16;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef long double ldf;
|
||||||
@@ -2,5 +2,3 @@
|
|||||||
#define WITH_GPIO
|
#define WITH_GPIO
|
||||||
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
|
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
|
||||||
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1
|
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1
|
||||||
#define CLOCK_MONOTONIC_RAW 1
|
|
||||||
#define CLOCK_MONOTONIC_FAST 1
|
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
|
|||||||
skipsdist = true
|
skipsdist = true
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython = python3.10
|
basepython = python3.11
|
||||||
changedir = /src
|
changedir = /src
|
||||||
|
|
||||||
[testenv:cppcheck]
|
[testenv:cppcheck]
|
||||||
whitelist_externals = cppcheck
|
allowlist_externals = cppcheck
|
||||||
commands = cppcheck \
|
commands = cppcheck \
|
||||||
-j4 \
|
|
||||||
--force \
|
--force \
|
||||||
--std=c17 \
|
--std=c17 \
|
||||||
--error-exitcode=1 \
|
--error-exitcode=1 \
|
||||||
@@ -20,33 +19,33 @@ commands = cppcheck \
|
|||||||
--inline-suppr \
|
--inline-suppr \
|
||||||
--library=python \
|
--library=python \
|
||||||
--include=linters/cppcheck.h \
|
--include=linters/cppcheck.h \
|
||||||
src python/*.? janus/*.?
|
src python/src/*.? janus/src/*.?
|
||||||
|
|
||||||
[testenv:flake8]
|
[testenv:flake8]
|
||||||
whitelist_externals = bash
|
allowlist_externals = bash
|
||||||
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py
|
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py
|
||||||
deps =
|
deps =
|
||||||
flake8==5.0.4
|
flake8==5.0.4
|
||||||
flake8-quotes
|
flake8-quotes
|
||||||
|
|
||||||
[testenv:pylint]
|
[testenv:pylint]
|
||||||
whitelist_externals = bash
|
allowlist_externals = bash
|
||||||
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py python/*.py'
|
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py python/*.py'
|
||||||
deps =
|
deps =
|
||||||
pylint
|
pylint
|
||||||
|
|
||||||
[testenv:mypy]
|
[testenv:mypy]
|
||||||
whitelist_externals = bash
|
allowlist_externals = bash
|
||||||
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py python/*.py'
|
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py python/*.py'
|
||||||
deps =
|
deps =
|
||||||
mypy
|
mypy
|
||||||
|
|
||||||
[testenv:vulture]
|
[testenv:vulture]
|
||||||
whitelist_externals = bash
|
allowlist_externals = bash
|
||||||
commands = bash -c 'vulture tools/*.py python/*.py'
|
commands = bash -c 'vulture tools/*.py python/*.py'
|
||||||
deps =
|
deps =
|
||||||
vulture
|
vulture
|
||||||
|
|
||||||
[testenv:htmlhint]
|
[testenv:htmlhint]
|
||||||
whitelist_externals = htmlhint
|
allowlist_externals = htmlhint
|
||||||
commands = htmlhint src/ustreamer/http/data/*.html
|
commands = htmlhint src/ustreamer/http/data/*.html
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" Manpage for ustreamer-dump.
|
.\" Manpage for ustreamer-dump.
|
||||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||||
.TH USTREAMER-DUMP 1 "version 5.36" "January 2021"
|
.TH USTREAMER-DUMP 1 "version 5.55" "January 2021"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" Manpage for ustreamer.
|
.\" Manpage for ustreamer.
|
||||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||||
.TH USTREAMER 1 "version 5.36" "November 2020"
|
.TH USTREAMER 1 "version 5.55" "November 2020"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||||
@@ -52,7 +52,7 @@ Initial image resolution. Default: 640x480.
|
|||||||
.TP
|
.TP
|
||||||
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
|
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
|
||||||
Image format.
|
Image format.
|
||||||
Available: YUYV, UYVY, RGB565, RGB24, JPEG; default: YUYV.
|
Available: YUYV, YVYU, UYVY, RGB565, RGB24, JPEG; default: YUYV.
|
||||||
.TP
|
.TP
|
||||||
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
|
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
|
||||||
Force TV standard.
|
Force TV standard.
|
||||||
@@ -248,7 +248,7 @@ Timeout for lock. Default: 1.
|
|||||||
H264 bitrate in Kbps. Default: 5000.
|
H264 bitrate in Kbps. Default: 5000.
|
||||||
.TP
|
.TP
|
||||||
.BR \-\-h264\-gop\ \fIN
|
.BR \-\-h264\-gop\ \fIN
|
||||||
Intarval between keyframes. Default: 30.
|
Interval between keyframes. Default: 30.
|
||||||
.TP
|
.TP
|
||||||
.BR \-\-h264\-m2m\-device\ \fI/dev/path
|
.BR \-\-h264\-m2m\-device\ \fI/dev/path
|
||||||
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
|
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=5.36
|
pkgver=5.55
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||||
url="https://github.com/pikvm/ustreamer"
|
url="https://github.com/pikvm/ustreamer"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ustreamer
|
PKG_NAME:=ustreamer
|
||||||
PKG_VERSION:=5.36
|
PKG_VERSION:=5.55
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ PY ?= python3
|
|||||||
# =====
|
# =====
|
||||||
all:
|
all:
|
||||||
$(info == PY_BUILD ustreamer-*.so)
|
$(info == PY_BUILD ustreamer-*.so)
|
||||||
@ $(PY) setup.py build
|
$(ECHO) $(PY) setup.py build
|
||||||
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@@ -17,4 +17,4 @@ install:
|
|||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf build
|
rm -rf build ustreamer.egg-info
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
setup(
|
setup(
|
||||||
name="ustreamer",
|
name="ustreamer",
|
||||||
version="5.36",
|
version="5.55",
|
||||||
description="uStreamer tools",
|
description="uStreamer tools",
|
||||||
author="Maxim Devaev",
|
author="Maxim Devaev",
|
||||||
author_email="mdevaev@gmail.com",
|
author_email="mdevaev@gmail.com",
|
||||||
|
|||||||
44
python/src/uslibs/types.h
Normal file
44
python/src/uslibs/types.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef long long sll;
|
||||||
|
typedef ssize_t sz;
|
||||||
|
typedef int8_t s8;
|
||||||
|
typedef int16_t s16;
|
||||||
|
typedef int32_t s32;
|
||||||
|
typedef int64_t s64;
|
||||||
|
|
||||||
|
typedef unsigned uint;
|
||||||
|
typedef unsigned long long ull;
|
||||||
|
typedef size_t uz;
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint16_t u16;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef long double ldf;
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
|
|
||||||
|
#include "uslibs/types.h"
|
||||||
#include "uslibs/tools.h"
|
#include "uslibs/tools.h"
|
||||||
#include "uslibs/frame.h"
|
#include "uslibs/frame.h"
|
||||||
#include "uslibs/memsinksh.h"
|
#include "uslibs/memsinksh.h"
|
||||||
@@ -29,29 +30,16 @@ typedef struct {
|
|||||||
int fd;
|
int fd;
|
||||||
us_memsink_shared_s *mem;
|
us_memsink_shared_s *mem;
|
||||||
|
|
||||||
uint64_t frame_id;
|
u64 frame_id;
|
||||||
long double frame_ts;
|
ldf frame_ts;
|
||||||
us_frame_s *frame;
|
us_frame_s *frame;
|
||||||
} _MemsinkObject;
|
} _MemsinkObject;
|
||||||
|
|
||||||
|
|
||||||
#define _MEM(x_next) self->mem->x_next
|
|
||||||
#define _FRAME(x_next) self->frame->x_next
|
|
||||||
|
|
||||||
|
|
||||||
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
|
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
|
||||||
if (self->mem != NULL) {
|
US_DELETE(self->mem, us_memsink_shared_unmap);
|
||||||
us_memsink_shared_unmap(self->mem);
|
US_CLOSE_FD(self->fd, close);
|
||||||
self->mem = NULL;
|
US_DELETE(self->frame, us_frame_destroy);
|
||||||
}
|
|
||||||
if (self->fd >= 0) {
|
|
||||||
close(self->fd);
|
|
||||||
self->fd = -1;
|
|
||||||
}
|
|
||||||
if (self->frame != NULL) {
|
|
||||||
us_frame_destroy(self->frame);
|
|
||||||
self->frame = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||||
@@ -65,17 +53,15 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
# define SET_DOUBLE(_field, _cond) { \
|
# define SET_DOUBLE(x_field, x_cond) { \
|
||||||
if (!(self->_field _cond)) { \
|
if (!(self->x_field x_cond)) { \
|
||||||
PyErr_SetString(PyExc_ValueError, #_field " must be " #_cond); \
|
PyErr_SetString(PyExc_ValueError, #x_field " must be " #x_cond); \
|
||||||
return -1; \
|
return -1; \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
SET_DOUBLE(lock_timeout, > 0);
|
SET_DOUBLE(lock_timeout, > 0);
|
||||||
SET_DOUBLE(wait_timeout, > 0);
|
SET_DOUBLE(wait_timeout, > 0);
|
||||||
SET_DOUBLE(drop_same_frames, >= 0);
|
SET_DOUBLE(drop_same_frames, >= 0);
|
||||||
|
|
||||||
# undef SET_DOUBLE
|
# undef SET_DOUBLE
|
||||||
|
|
||||||
self->frame = us_frame_init();
|
self->frame = us_frame_init();
|
||||||
@@ -84,22 +70,20 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
|
|||||||
PyErr_SetFromErrno(PyExc_OSError);
|
PyErr_SetFromErrno(PyExc_OSError);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((self->mem = us_memsink_shared_map(self->fd)) == NULL) {
|
if ((self->mem = us_memsink_shared_map(self->fd)) == NULL) {
|
||||||
PyErr_SetFromErrno(PyExc_OSError);
|
PyErr_SetFromErrno(PyExc_OSError);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
_MemsinkObject_destroy_internals(self);
|
_MemsinkObject_destroy_internals(self);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
|
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
|
||||||
char repr[1024];
|
char repr[1024];
|
||||||
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
|
US_SNPRINTF(repr, 1023, "<Memsink(%s)>", self->obj);
|
||||||
return Py_BuildValue("s", repr);
|
return Py_BuildValue("s", repr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +107,7 @@ static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(i
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int _wait_frame(_MemsinkObject *self) {
|
static int _wait_frame(_MemsinkObject *self) {
|
||||||
const long double deadline_ts = us_get_now_monotonic() + self->wait_timeout;
|
const ldf deadline_ts = us_get_now_monotonic() + self->wait_timeout;
|
||||||
|
|
||||||
# define RETURN_OS_ERROR { \
|
# define RETURN_OS_ERROR { \
|
||||||
Py_BLOCK_THREADS \
|
Py_BLOCK_THREADS \
|
||||||
@@ -131,25 +115,26 @@ static int _wait_frame(_MemsinkObject *self) {
|
|||||||
return -1; \
|
return -1; \
|
||||||
}
|
}
|
||||||
|
|
||||||
long double now;
|
ldf now_ts;
|
||||||
do {
|
do {
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
|
||||||
const int retval = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
|
const int retval = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
|
||||||
now = us_get_now_monotonic();
|
now_ts = us_get_now_monotonic();
|
||||||
|
|
||||||
if (retval < 0 && errno != EWOULDBLOCK) {
|
if (retval < 0 && errno != EWOULDBLOCK) {
|
||||||
RETURN_OS_ERROR;
|
RETURN_OS_ERROR;
|
||||||
|
|
||||||
} else if (retval == 0) {
|
} else if (retval == 0) {
|
||||||
if (_MEM(magic) == US_MEMSINK_MAGIC && _MEM(version) == US_MEMSINK_VERSION && _MEM(id) != self->frame_id) {
|
us_memsink_shared_s *mem = self->mem;
|
||||||
|
if (mem->magic == US_MEMSINK_MAGIC && mem->version == US_MEMSINK_VERSION && mem->id != self->frame_id) {
|
||||||
if (self->drop_same_frames > 0) {
|
if (self->drop_same_frames > 0) {
|
||||||
if (
|
if (
|
||||||
US_FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
|
US_FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
|
||||||
&& (self->frame_ts + self->drop_same_frames > now)
|
&& (self->frame_ts + self->drop_same_frames > now_ts)
|
||||||
&& !memcmp(_FRAME(data), _MEM(data), _MEM(used))
|
&& !memcmp(self->frame->data, mem->data, mem->used)
|
||||||
) {
|
) {
|
||||||
self->frame_id = _MEM(id);
|
self->frame_id = mem->id;
|
||||||
goto drop;
|
goto drop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,22 +148,18 @@ static int _wait_frame(_MemsinkObject *self) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drop:
|
drop:
|
||||||
|
|
||||||
if (usleep(1000) < 0) {
|
if (usleep(1000) < 0) {
|
||||||
RETURN_OS_ERROR;
|
RETURN_OS_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
|
|
||||||
if (PyErr_CheckSignals() < 0) {
|
if (PyErr_CheckSignals() < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} while (now < deadline_ts);
|
} while (now_ts < deadline_ts);
|
||||||
|
|
||||||
# undef RETURN_OS_ERROR
|
|
||||||
|
|
||||||
return -2;
|
return -2;
|
||||||
|
# undef RETURN_OS_ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||||
@@ -199,13 +180,14 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
|
|||||||
default: return NULL;
|
default: return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
us_frame_set_data(self->frame, _MEM(data), _MEM(used));
|
us_memsink_shared_s *mem = self->mem;
|
||||||
|
us_frame_set_data(self->frame, mem->data, mem->used);
|
||||||
US_FRAME_COPY_META(self->mem, self->frame);
|
US_FRAME_COPY_META(self->mem, self->frame);
|
||||||
self->frame_id = _MEM(id);
|
self->frame_id = mem->id;
|
||||||
self->frame_ts = us_get_now_monotonic();
|
self->frame_ts = us_get_now_monotonic();
|
||||||
_MEM(last_client_ts) = self->frame_ts;
|
mem->last_client_ts = self->frame_ts;
|
||||||
if (key_required) {
|
if (key_required) {
|
||||||
_MEM(key_requested) = true;
|
mem->key_requested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flock(self->fd, LOCK_UN) < 0) {
|
if (flock(self->fd, LOCK_UN) < 0) {
|
||||||
@@ -217,18 +199,18 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
# define SET_VALUE(_key, _maker) { \
|
# define SET_VALUE(x_key, x_maker) { \
|
||||||
PyObject *_tmp = _maker; \
|
PyObject *m_tmp = x_maker; \
|
||||||
if (_tmp == NULL) { \
|
if (m_tmp == NULL) { \
|
||||||
return NULL; \
|
return NULL; \
|
||||||
} \
|
} \
|
||||||
if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \
|
if (PyDict_SetItemString(dict_frame, x_key, m_tmp) < 0) { \
|
||||||
Py_DECREF(_tmp); \
|
Py_DECREF(m_tmp); \
|
||||||
return NULL; \
|
return NULL; \
|
||||||
} \
|
} \
|
||||||
Py_DECREF(_tmp); \
|
Py_DECREF(m_tmp); \
|
||||||
}
|
}
|
||||||
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(_FRAME(_key)))
|
# define SET_NUMBER(x_key, x_from, x_to) SET_VALUE(#x_key, Py##x_to##_From##x_from(self->frame->x_key))
|
||||||
|
|
||||||
SET_NUMBER(width, Long, Long);
|
SET_NUMBER(width, Long, Long);
|
||||||
SET_NUMBER(height, Long, Long);
|
SET_NUMBER(height, Long, Long);
|
||||||
@@ -240,7 +222,7 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
|
|||||||
SET_NUMBER(grab_ts, Double, Float);
|
SET_NUMBER(grab_ts, Double, Float);
|
||||||
SET_NUMBER(encode_begin_ts, Double, Float);
|
SET_NUMBER(encode_begin_ts, Double, Float);
|
||||||
SET_NUMBER(encode_end_ts, Double, Float);
|
SET_NUMBER(encode_end_ts, Double, Float);
|
||||||
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)_FRAME(data), _FRAME(used)));
|
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)self->frame->data, self->frame->used));
|
||||||
|
|
||||||
# undef SET_NUMBER
|
# undef SET_NUMBER
|
||||||
# undef SET_VALUE
|
# undef SET_VALUE
|
||||||
@@ -252,21 +234,19 @@ static PyObject *_MemsinkObject_is_opened(_MemsinkObject *self, PyObject *Py_UNU
|
|||||||
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
|
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define FIELD_GETTER(_field, _from, _to) \
|
#define FIELD_GETTER(x_field, x_from, x_to) \
|
||||||
static PyObject *_MemsinkObject_getter_##_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
|
static PyObject *_MemsinkObject_getter_##x_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
|
||||||
return Py##_to##_From##_from(self->_field); \
|
return Py##x_to##_From##x_from(self->x_field); \
|
||||||
}
|
}
|
||||||
|
|
||||||
FIELD_GETTER(obj, String, Unicode)
|
FIELD_GETTER(obj, String, Unicode)
|
||||||
FIELD_GETTER(lock_timeout, Double, Float)
|
FIELD_GETTER(lock_timeout, Double, Float)
|
||||||
FIELD_GETTER(wait_timeout, Double, Float)
|
FIELD_GETTER(wait_timeout, Double, Float)
|
||||||
FIELD_GETTER(drop_same_frames, Double, Float)
|
FIELD_GETTER(drop_same_frames, Double, Float)
|
||||||
|
|
||||||
#undef FIELD_GETTER
|
#undef FIELD_GETTER
|
||||||
|
|
||||||
static PyMethodDef _MemsinkObject_methods[] = {
|
static PyMethodDef _MemsinkObject_methods[] = {
|
||||||
# define ADD_METHOD(_name, _method, _flags) \
|
# define ADD_METHOD(x_name, x_method, x_flags) \
|
||||||
{.ml_name = _name, .ml_meth = (PyCFunction)_MemsinkObject_##_method, .ml_flags = (_flags)}
|
{.ml_name = x_name, .ml_meth = (PyCFunction)_MemsinkObject_##x_method, .ml_flags = (x_flags)}
|
||||||
ADD_METHOD("close", close, METH_NOARGS),
|
ADD_METHOD("close", close, METH_NOARGS),
|
||||||
ADD_METHOD("__enter__", enter, METH_NOARGS),
|
ADD_METHOD("__enter__", enter, METH_NOARGS),
|
||||||
ADD_METHOD("__exit__", exit, METH_VARARGS),
|
ADD_METHOD("__exit__", exit, METH_VARARGS),
|
||||||
@@ -277,7 +257,7 @@ static PyMethodDef _MemsinkObject_methods[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static PyGetSetDef _MemsinkObject_getsets[] = {
|
static PyGetSetDef _MemsinkObject_getsets[] = {
|
||||||
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)_MemsinkObject_getter_##_field}
|
# define ADD_GETTER(x_field) {.name = #x_field, .get = (getter)_MemsinkObject_getter_##x_field}
|
||||||
ADD_GETTER(obj),
|
ADD_GETTER(obj),
|
||||||
ADD_GETTER(lock_timeout),
|
ADD_GETTER(lock_timeout),
|
||||||
ADD_GETTER(wait_timeout),
|
ADD_GETTER(wait_timeout),
|
||||||
|
|||||||
45
src/Makefile
45
src/Makefile
@@ -9,6 +9,7 @@ LDFLAGS ?=
|
|||||||
# =====
|
# =====
|
||||||
_USTR = ustreamer.bin
|
_USTR = ustreamer.bin
|
||||||
_DUMP = ustreamer-dump.bin
|
_DUMP = ustreamer-dump.bin
|
||||||
|
_V4P = ustreamer-v4p.bin
|
||||||
|
|
||||||
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
||||||
_LDFLAGS = $(LDFLAGS)
|
_LDFLAGS = $(LDFLAGS)
|
||||||
@@ -32,9 +33,19 @@ _DUMP_SRCS = $(shell ls \
|
|||||||
dump/*.c \
|
dump/*.c \
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_V4P_LIBS = $(_COMMON_LIBS)
|
||||||
|
_V4P_SRCS = $(shell ls \
|
||||||
|
libs/*.c \
|
||||||
|
v4p/*.c \
|
||||||
|
)
|
||||||
|
|
||||||
_BUILD = build
|
_BUILD = build
|
||||||
|
|
||||||
|
|
||||||
|
_TARGETS = $(_USTR) $(_DUMP)
|
||||||
|
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||||
|
|
||||||
|
|
||||||
define optbool
|
define optbool
|
||||||
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||||
endef
|
endef
|
||||||
@@ -42,7 +53,7 @@ endef
|
|||||||
|
|
||||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||||
_USTR_LIBS += -lgpiod
|
_USTR_LIBS += -lgpiod
|
||||||
override _CFLAGS += -DWITH_GPIO
|
override _CFLAGS += -DWITH_GPIO $(shell pkg-config --atleast-version=2 libgpiod 2> /dev/null && echo -DHAVE_GPIOD2)
|
||||||
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
|
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -69,40 +80,50 @@ override _CFLAGS += -DWITH_SETPROCTITLE
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
WITH_V4P ?= 0
|
||||||
|
ifneq ($(call optbool,$(WITH_V4P)),)
|
||||||
|
override _TARGETS += $(_V4P)
|
||||||
|
override _OBJS += $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
|
||||||
|
override _CFLAGS += $(shell pkg-config --cflags libdrm)
|
||||||
|
_V4P_LDFLAGS = $(shell pkg-config --libs libdrm)
|
||||||
|
endif
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
all: $(_USTR) $(_DUMP)
|
all: $(_TARGETS)
|
||||||
|
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||||
install -m755 $(_USTR) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
|
for i in $(subst .bin,,$(_TARGETS)); do install -m755 $$i.bin $(DESTDIR)$(PREFIX)/bin/$$i; done
|
||||||
install -m755 $(_DUMP) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
|
|
||||||
|
|
||||||
|
|
||||||
install-strip: install
|
install-strip: install
|
||||||
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
|
for i in $(subst .bin,,$(_TARGETS)); do strip $(DESTDIR)$(PREFIX)/bin/$$i; done
|
||||||
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
|
|
||||||
|
|
||||||
|
|
||||||
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
|
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
|
||||||
$(info == LD $@)
|
$(info == LD $@)
|
||||||
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
|
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
|
||||||
|
|
||||||
|
|
||||||
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||||
$(info == LD $@)
|
$(info == LD $@)
|
||||||
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
||||||
|
|
||||||
|
|
||||||
|
$(_V4P): $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
|
||||||
|
$(info == LD $@)
|
||||||
|
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_V4P_LDFLAGS) $(_V4P_LIBS)
|
||||||
|
|
||||||
|
|
||||||
$(_BUILD)/%.o: %.c
|
$(_BUILD)/%.o: %.c
|
||||||
$(info -- CC $<)
|
$(info -- CC $<)
|
||||||
@ mkdir -p $(dir $@) || true
|
$(ECHO) mkdir -p $(dir $@) || true
|
||||||
@ $(CC) $< -o $@ $(_CFLAGS)
|
$(ECHO) $(CC) $< -o $@ $(_CFLAGS)
|
||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(_USTR) $(_DUMP) $(_BUILD)
|
rm -rf $(_USTR) $(_DUMP) $(_V4P) $(_BUILD)
|
||||||
|
|
||||||
|
|
||||||
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
|
||||||
-include $(_OBJS:%.o=%.d)
|
-include $(_OBJS:%.o=%.d)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -231,6 +231,8 @@ static int _dump_sink(
|
|||||||
bool key_required,
|
bool key_required,
|
||||||
_output_context_s *ctx) {
|
_output_context_s *ctx) {
|
||||||
|
|
||||||
|
int retval = -1;
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
count = -1;
|
count = -1;
|
||||||
}
|
}
|
||||||
@@ -300,18 +302,13 @@ static int _dump_sink(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int retval = 0;
|
retval = 0;
|
||||||
goto ok;
|
|
||||||
|
|
||||||
error:
|
error:
|
||||||
retval = -1;
|
US_DELETE(sink, us_memsink_destroy);
|
||||||
|
us_frame_destroy(frame);
|
||||||
ok:
|
US_LOG_INFO("Bye-bye");
|
||||||
US_DELETE(sink, us_memsink_destroy);
|
return retval;
|
||||||
us_frame_destroy(frame);
|
|
||||||
|
|
||||||
US_LOG_INFO("Bye-bye");
|
|
||||||
return retval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _help(FILE *fp) {
|
static void _help(FILE *fp) {
|
||||||
@@ -319,7 +316,7 @@ static void _help(FILE *fp) {
|
|||||||
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
|
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
|
||||||
SAY("═════════════════════════════════════════════════════");
|
SAY("═════════════════════════════════════════════════════");
|
||||||
SAY("Version: %s; license: GPLv3", US_VERSION);
|
SAY("Version: %s; license: GPLv3", US_VERSION);
|
||||||
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
|
SAY("Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||||
SAY("Example:");
|
SAY("Example:");
|
||||||
SAY("════════");
|
SAY("════════");
|
||||||
SAY(" ustreamer-dump --sink test --output - \\");
|
SAY(" ustreamer-dump --sink test --output - \\");
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,6 +22,13 @@
|
|||||||
|
|
||||||
#include "base64.h"
|
#include "base64.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "tools.h"
|
||||||
|
|
||||||
|
|
||||||
static const char _ENCODING_TABLE[] = {
|
static const char _ENCODING_TABLE[] = {
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||||
@@ -34,11 +41,11 @@ static const char _ENCODING_TABLE[] = {
|
|||||||
'4', '5', '6', '7', '8', '9', '+', '/',
|
'4', '5', '6', '7', '8', '9', '+', '/',
|
||||||
};
|
};
|
||||||
|
|
||||||
static const unsigned _MOD_TABLE[] = {0, 2, 1};
|
static const uint _MOD_TABLE[] = {0, 2, 1};
|
||||||
|
|
||||||
|
|
||||||
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
|
void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated) {
|
||||||
const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
|
const uz encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
|
||||||
|
|
||||||
if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
|
if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
|
||||||
US_REALLOC(*encoded, encoded_size);
|
US_REALLOC(*encoded, encoded_size);
|
||||||
@@ -47,14 +54,14 @@ void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unsigned data_index = 0, encoded_index = 0; data_index < size;) {
|
for (uint data_index = 0, encoded_index = 0; data_index < size;) {
|
||||||
# define OCTET(_name) unsigned _name = (data_index < size ? (uint8_t)data[data_index++] : 0)
|
# define OCTET(_name) uint _name = (data_index < size ? (u8)data[data_index++] : 0)
|
||||||
OCTET(octet_a);
|
OCTET(octet_a);
|
||||||
OCTET(octet_b);
|
OCTET(octet_b);
|
||||||
OCTET(octet_c);
|
OCTET(octet_c);
|
||||||
# undef OCTET
|
# undef OCTET
|
||||||
|
|
||||||
const unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
const uint triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||||
|
|
||||||
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
|
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
|
||||||
ENCODE(3);
|
ENCODE(3);
|
||||||
@@ -64,7 +71,7 @@ void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *
|
|||||||
# undef ENCODE
|
# undef ENCODE
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unsigned index = 0; index < _MOD_TABLE[size % 3]; index++) {
|
for (uint index = 0; index < _MOD_TABLE[size % 3]; index++) {
|
||||||
(*encoded)[encoded_size - 2 - index] = '=';
|
(*encoded)[encoded_size - 2 - index] = '=';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,13 +22,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include "types.h"
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "tools.h"
|
|
||||||
|
|
||||||
|
|
||||||
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);
|
void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,11 +22,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
|
||||||
#define US_VERSION_MAJOR 5
|
#define US_VERSION_MAJOR 5
|
||||||
#define US_VERSION_MINOR 36
|
#define US_VERSION_MINOR 55
|
||||||
|
|
||||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||||
#define US_VERSION US_MAKE_VERSION1(US_VERSION_MAJOR, US_VERSION_MINOR)
|
#define US_VERSION US_MAKE_VERSION1(US_VERSION_MAJOR, US_VERSION_MINOR)
|
||||||
|
|
||||||
#define US_VERSION_U ((unsigned)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))
|
#define US_VERSION_U ((uint)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))
|
||||||
|
|||||||
1079
src/libs/device.c
Normal file
1079
src/libs/device.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,46 +22,25 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <strings.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <linux/videodev2.h>
|
#include <linux/videodev2.h>
|
||||||
#include <linux/v4l2-controls.h>
|
|
||||||
|
|
||||||
#include "../libs/tools.h"
|
#include "types.h"
|
||||||
#include "../libs/array.h"
|
#include "frame.h"
|
||||||
#include "../libs/logging.h"
|
|
||||||
#include "../libs/threading.h"
|
|
||||||
#include "../libs/frame.h"
|
|
||||||
#include "../libs/xioctl.h"
|
|
||||||
|
|
||||||
|
|
||||||
#define US_VIDEO_MIN_WIDTH ((unsigned)160)
|
#define US_VIDEO_MIN_WIDTH ((uint)160)
|
||||||
#define US_VIDEO_MAX_WIDTH ((unsigned)10240)
|
#define US_VIDEO_MAX_WIDTH ((uint)15360)
|
||||||
|
|
||||||
#define US_VIDEO_MIN_HEIGHT ((unsigned)120)
|
#define US_VIDEO_MIN_HEIGHT ((uint)120)
|
||||||
#define US_VIDEO_MAX_HEIGHT ((unsigned)4320)
|
#define US_VIDEO_MAX_HEIGHT ((uint)8640)
|
||||||
|
|
||||||
#define US_VIDEO_MAX_FPS ((unsigned)120)
|
#define US_VIDEO_MAX_FPS ((uint)120)
|
||||||
|
|
||||||
#define US_STANDARD_UNKNOWN V4L2_STD_UNKNOWN
|
#define US_STANDARD_UNKNOWN V4L2_STD_UNKNOWN
|
||||||
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
|
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
|
||||||
|
|
||||||
#define US_FORMAT_UNKNOWN -1
|
#define US_FORMAT_UNKNOWN -1
|
||||||
#define US_FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG"
|
#define US_FORMATS_STR "YUYV, YVYU, UYVY, RGB565, RGB24, BGR24, MJPEG, JPEG"
|
||||||
|
|
||||||
#define US_IO_METHOD_UNKNOWN -1
|
#define US_IO_METHOD_UNKNOWN -1
|
||||||
#define US_IO_METHODS_STR "MMAP, USERPTR"
|
#define US_IO_METHODS_STR "MMAP, USERPTR"
|
||||||
@@ -75,18 +54,21 @@ typedef struct {
|
|||||||
} us_hw_buffer_s;
|
} us_hw_buffer_s;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int fd;
|
int fd;
|
||||||
unsigned width;
|
uint width;
|
||||||
unsigned height;
|
uint height;
|
||||||
unsigned format;
|
uint format;
|
||||||
unsigned stride;
|
uint stride;
|
||||||
unsigned hw_fps;
|
float hz;
|
||||||
unsigned jpeg_quality;
|
uint hw_fps;
|
||||||
size_t raw_size;
|
uint jpeg_quality;
|
||||||
unsigned n_bufs;
|
uz raw_size;
|
||||||
us_hw_buffer_s *hw_bufs;
|
uint n_bufs;
|
||||||
bool capturing;
|
us_hw_buffer_s *hw_bufs;
|
||||||
bool persistent_timeout_reported;
|
enum v4l2_buf_type capture_type;
|
||||||
|
bool capture_mplane;
|
||||||
|
bool capturing;
|
||||||
|
bool persistent_timeout_reported;
|
||||||
} us_device_runtime_s;
|
} us_device_runtime_s;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -119,22 +101,20 @@ typedef struct {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *path;
|
char *path;
|
||||||
unsigned input;
|
uint input;
|
||||||
unsigned width;
|
uint width;
|
||||||
unsigned height;
|
uint height;
|
||||||
unsigned format;
|
uint format;
|
||||||
unsigned jpeg_quality;
|
uint jpeg_quality;
|
||||||
v4l2_std_id standard;
|
v4l2_std_id standard;
|
||||||
enum v4l2_memory io_method;
|
enum v4l2_memory io_method;
|
||||||
bool dv_timings;
|
bool dv_timings;
|
||||||
unsigned n_bufs;
|
uint n_bufs;
|
||||||
unsigned desired_fps;
|
uint desired_fps;
|
||||||
size_t min_frame_size;
|
uz min_frame_size;
|
||||||
bool persistent;
|
bool persistent;
|
||||||
unsigned timeout;
|
uint timeout;
|
||||||
|
|
||||||
us_controls_s ctl;
|
us_controls_s ctl;
|
||||||
|
|
||||||
us_device_runtime_s *run;
|
us_device_runtime_s *run;
|
||||||
} us_device_s;
|
} us_device_s;
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,6 +22,16 @@
|
|||||||
|
|
||||||
#include "frame.h"
|
#include "frame.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "tools.h"
|
||||||
|
|
||||||
|
|
||||||
us_frame_s *us_frame_init(void) {
|
us_frame_s *us_frame_init(void) {
|
||||||
us_frame_s *frame;
|
us_frame_s *frame;
|
||||||
@@ -36,21 +46,21 @@ void us_frame_destroy(us_frame_s *frame) {
|
|||||||
free(frame);
|
free(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void us_frame_realloc_data(us_frame_s *frame, size_t size) {
|
void us_frame_realloc_data(us_frame_s *frame, uz size) {
|
||||||
if (frame->allocated < size) {
|
if (frame->allocated < size) {
|
||||||
US_REALLOC(frame->data, size);
|
US_REALLOC(frame->data, size);
|
||||||
frame->allocated = size;
|
frame->allocated = size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size) {
|
void us_frame_set_data(us_frame_s *frame, const u8 *data, uz size) {
|
||||||
us_frame_realloc_data(frame, size);
|
us_frame_realloc_data(frame, size);
|
||||||
memcpy(frame->data, data, size);
|
memcpy(frame->data, data, size);
|
||||||
frame->used = size;
|
frame->used = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size) {
|
void us_frame_append_data(us_frame_s *frame, const u8 *data, uz size) {
|
||||||
const size_t new_used = frame->used + size;
|
const uz new_used = frame->used + size;
|
||||||
us_frame_realloc_data(frame, new_used);
|
us_frame_realloc_data(frame, new_used);
|
||||||
memcpy(frame->data + frame->used, data, size);
|
memcpy(frame->data + frame->used, data, size);
|
||||||
frame->used = new_used;
|
frame->used = new_used;
|
||||||
@@ -69,12 +79,14 @@ bool us_frame_compare(const us_frame_s *a, const us_frame_s *b) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned us_frame_get_padding(const us_frame_s *frame) {
|
uint us_frame_get_padding(const us_frame_s *frame) {
|
||||||
unsigned bytes_per_pixel = 0;
|
uint bytes_per_pixel = 0;
|
||||||
switch (frame->format) {
|
switch (frame->format) {
|
||||||
case V4L2_PIX_FMT_YUYV:
|
case V4L2_PIX_FMT_YUYV:
|
||||||
|
case V4L2_PIX_FMT_YVYU:
|
||||||
case V4L2_PIX_FMT_UYVY:
|
case V4L2_PIX_FMT_UYVY:
|
||||||
case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break;
|
case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break;
|
||||||
|
case V4L2_PIX_FMT_BGR24:
|
||||||
case V4L2_PIX_FMT_RGB24: bytes_per_pixel = 3; break;
|
case V4L2_PIX_FMT_RGB24: bytes_per_pixel = 3; break;
|
||||||
// case V4L2_PIX_FMT_H264:
|
// case V4L2_PIX_FMT_H264:
|
||||||
case V4L2_PIX_FMT_MJPEG:
|
case V4L2_PIX_FMT_MJPEG:
|
||||||
@@ -87,13 +99,13 @@ unsigned us_frame_get_padding(const us_frame_s *frame) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size) {
|
const char *us_fourcc_to_string(uint format, char *buf, uz size) {
|
||||||
assert(size >= 8);
|
assert(size >= 8);
|
||||||
buf[0] = format & 0x7F;
|
buf[0] = format & 0x7F;
|
||||||
buf[1] = (format >> 8) & 0x7F;
|
buf[1] = (format >> 8) & 0x7F;
|
||||||
buf[2] = (format >> 16) & 0x7F;
|
buf[2] = (format >> 16) & 0x7F;
|
||||||
buf[3] = (format >> 24) & 0x7F;
|
buf[3] = (format >> 24) & 0x7F;
|
||||||
if (format & ((unsigned)1 << 31)) {
|
if (format & ((uint)1 << 31)) {
|
||||||
buf[4] = '-';
|
buf[4] = '-';
|
||||||
buf[5] = 'B';
|
buf[5] = 'B';
|
||||||
buf[6] = 'E';
|
buf[6] = 'E';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,39 +22,33 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <linux/videodev2.h>
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t *data;
|
u8 *data;
|
||||||
size_t used;
|
uz used;
|
||||||
size_t allocated;
|
uz allocated;
|
||||||
int dma_fd;
|
int dma_fd;
|
||||||
|
|
||||||
unsigned width;
|
uint width;
|
||||||
unsigned height;
|
uint height;
|
||||||
unsigned format;
|
uint format;
|
||||||
unsigned stride;
|
uint stride;
|
||||||
// Stride is a bytesperline in V4L2
|
// Stride is a bytesperline in V4L2
|
||||||
// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html
|
// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html
|
||||||
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
|
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
|
||||||
|
|
||||||
bool online;
|
bool online;
|
||||||
bool key;
|
bool key;
|
||||||
unsigned gop;
|
uint gop;
|
||||||
|
|
||||||
long double grab_ts;
|
ldf grab_ts;
|
||||||
long double encode_begin_ts;
|
ldf encode_begin_ts;
|
||||||
long double encode_end_ts;
|
ldf encode_end_ts;
|
||||||
} us_frame_s;
|
} us_frame_s;
|
||||||
|
|
||||||
|
|
||||||
@@ -87,7 +81,7 @@ static inline void us_frame_copy_meta(const us_frame_s *src, us_frame_s *dest) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, unsigned format) {
|
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, uint format) {
|
||||||
assert(src->used > 0);
|
assert(src->used > 0);
|
||||||
us_frame_copy_meta(src, dest);
|
us_frame_copy_meta(src, dest);
|
||||||
dest->encode_begin_ts = us_get_now_monotonic();
|
dest->encode_begin_ts = us_get_now_monotonic();
|
||||||
@@ -105,17 +99,17 @@ static inline void us_frame_encoding_end(us_frame_s *dest) {
|
|||||||
us_frame_s *us_frame_init(void);
|
us_frame_s *us_frame_init(void);
|
||||||
void us_frame_destroy(us_frame_s *frame);
|
void us_frame_destroy(us_frame_s *frame);
|
||||||
|
|
||||||
void us_frame_realloc_data(us_frame_s *frame, size_t size);
|
void us_frame_realloc_data(us_frame_s *frame, uz size);
|
||||||
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size);
|
void us_frame_set_data(us_frame_s *frame, const u8 *data, uz size);
|
||||||
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size);
|
void us_frame_append_data(us_frame_s *frame, const u8 *data, uz size);
|
||||||
|
|
||||||
void us_frame_copy(const us_frame_s *src, us_frame_s *dest);
|
void us_frame_copy(const us_frame_s *src, us_frame_s *dest);
|
||||||
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b);
|
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b);
|
||||||
|
|
||||||
unsigned us_frame_get_padding(const us_frame_s *frame);
|
uint us_frame_get_padding(const us_frame_s *frame);
|
||||||
|
|
||||||
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size);
|
const char *us_fourcc_to_string(uint format, char *buf, uz size);
|
||||||
|
|
||||||
static inline bool us_is_jpeg(unsigned format) {
|
static inline bool us_is_jpeg(uint format) {
|
||||||
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
|
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,6 +22,10 @@
|
|||||||
|
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
|
||||||
enum us_log_level_t us_g_log_level;
|
enum us_log_level_t us_g_log_level;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -23,7 +23,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -33,6 +32,7 @@
|
|||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
#include "threading.h"
|
#include "threading.h"
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ extern pthread_mutex_t us_g_log_mutex;
|
|||||||
|
|
||||||
|
|
||||||
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
|
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
|
||||||
char m_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
|
||||||
us_thread_get_name(m_tname_buf); \
|
us_thread_get_name(m_tname_buf); \
|
||||||
if (us_g_log_colored) { \
|
if (us_g_log_colored) { \
|
||||||
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
|
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,10 +22,26 @@
|
|||||||
|
|
||||||
#include "memsink.h"
|
#include "memsink.h"
|
||||||
|
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <sys/file.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "tools.h"
|
||||||
|
#include "logging.h"
|
||||||
|
#include "frame.h"
|
||||||
|
#include "memsinksh.h"
|
||||||
|
|
||||||
|
|
||||||
us_memsink_s *us_memsink_init(
|
us_memsink_s *us_memsink_init(
|
||||||
const char *name, const char *obj, bool server,
|
const char *name, const char *obj, bool server,
|
||||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
|
mode_t mode, bool rm, uint client_ttl, uint timeout) {
|
||||||
|
|
||||||
us_memsink_s *sink;
|
us_memsink_s *sink;
|
||||||
US_CALLOC(sink, 1);
|
US_CALLOC(sink, 1);
|
||||||
@@ -58,12 +74,11 @@ us_memsink_s *us_memsink_init(
|
|||||||
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sink;
|
return sink;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
us_memsink_destroy(sink);
|
us_memsink_destroy(sink);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void us_memsink_destroy(us_memsink_s *sink) {
|
void us_memsink_destroy(us_memsink_s *sink) {
|
||||||
@@ -117,7 +132,7 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
|
|||||||
return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
|
return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
|
||||||
}
|
}
|
||||||
|
|
||||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested) {
|
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested) {
|
||||||
assert(sink->server);
|
assert(sink->server);
|
||||||
|
|
||||||
const long double now = us_get_now_monotonic();
|
const long double now = us_get_now_monotonic();
|
||||||
@@ -164,7 +179,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *con
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key_requested, bool key_required) { // cppcheck-suppress unusedFunction
|
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required) { // cppcheck-suppress unusedFunction
|
||||||
assert(!sink->server); // Client only
|
assert(!sink->server); // Client only
|
||||||
|
|
||||||
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||||
@@ -176,6 +191,7 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key
|
|||||||
}
|
}
|
||||||
|
|
||||||
int retval = -2; // Not updated
|
int retval = -2; // Not updated
|
||||||
|
|
||||||
if (sink->mem->magic == US_MEMSINK_MAGIC) {
|
if (sink->mem->magic == US_MEMSINK_MAGIC) {
|
||||||
if (sink->mem->version != US_MEMSINK_VERSION) {
|
if (sink->mem->version != US_MEMSINK_VERSION) {
|
||||||
US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
|
US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
|
||||||
@@ -196,10 +212,10 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||||
return -1;
|
retval = -1;
|
||||||
}
|
}
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,47 +22,37 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <string.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/file.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/mman.h>
|
|
||||||
|
|
||||||
#include "tools.h"
|
#include "types.h"
|
||||||
#include "logging.h"
|
|
||||||
#include "frame.h"
|
#include "frame.h"
|
||||||
#include "memsinksh.h"
|
#include "memsinksh.h"
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *name;
|
const char *name;
|
||||||
const char *obj;
|
const char *obj;
|
||||||
bool server;
|
bool server;
|
||||||
bool rm;
|
bool rm;
|
||||||
unsigned client_ttl; // Only for server
|
uint client_ttl; // Only for server
|
||||||
unsigned timeout;
|
uint timeout;
|
||||||
|
|
||||||
int fd;
|
int fd;
|
||||||
us_memsink_shared_s *mem;
|
us_memsink_shared_s *mem;
|
||||||
uint64_t last_id;
|
u64 last_id;
|
||||||
atomic_bool has_clients; // Only for server
|
atomic_bool has_clients; // Only for server
|
||||||
} us_memsink_s;
|
} us_memsink_s;
|
||||||
|
|
||||||
|
|
||||||
us_memsink_s *us_memsink_init(
|
us_memsink_s *us_memsink_init(
|
||||||
const char *name, const char *obj, bool server,
|
const char *name, const char *obj, bool server,
|
||||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
|
mode_t mode, bool rm, uint client_ttl, uint timeout);
|
||||||
|
|
||||||
void us_memsink_destroy(us_memsink_s *sink);
|
void us_memsink_destroy(us_memsink_s *sink);
|
||||||
|
|
||||||
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
|
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
|
||||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested);
|
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested);
|
||||||
|
|
||||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key_requested, bool key_required);
|
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,45 +22,43 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
|
||||||
#define US_MEMSINK_VERSION ((uint32_t)4)
|
#define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE)
|
||||||
|
#define US_MEMSINK_VERSION ((u32)4)
|
||||||
|
|
||||||
#ifndef US_CFG_MEMSINK_MAX_DATA
|
#ifndef US_CFG_MEMSINK_MAX_DATA
|
||||||
# define US_CFG_MEMSINK_MAX_DATA 33554432
|
# define US_CFG_MEMSINK_MAX_DATA 33554432
|
||||||
#endif
|
#endif
|
||||||
#define US_MEMSINK_MAX_DATA ((size_t)(US_CFG_MEMSINK_MAX_DATA))
|
#define US_MEMSINK_MAX_DATA ((uz)(US_CFG_MEMSINK_MAX_DATA))
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint64_t magic;
|
u64 magic;
|
||||||
uint32_t version;
|
u32 version;
|
||||||
|
|
||||||
uint64_t id;
|
u64 id;
|
||||||
|
|
||||||
size_t used;
|
uz used;
|
||||||
unsigned width;
|
uint width;
|
||||||
unsigned height;
|
uint height;
|
||||||
unsigned format;
|
uint format;
|
||||||
unsigned stride;
|
uint stride;
|
||||||
bool online;
|
bool online;
|
||||||
bool key;
|
bool key;
|
||||||
unsigned gop;
|
uint gop;
|
||||||
|
|
||||||
long double grab_ts;
|
ldf grab_ts;
|
||||||
long double encode_begin_ts;
|
ldf encode_begin_ts;
|
||||||
long double encode_end_ts;
|
ldf encode_end_ts;
|
||||||
|
|
||||||
long double last_client_ts;
|
ldf last_client_ts;
|
||||||
bool key_requested;
|
bool key_requested;
|
||||||
|
|
||||||
uint8_t data[US_MEMSINK_MAX_DATA];
|
u8 data[US_MEMSINK_MAX_DATA];
|
||||||
} us_memsink_shared_s;
|
} us_memsink_shared_s;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,10 +22,17 @@
|
|||||||
|
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
void us_build_short_options(const struct option opts[], char *short_opts, size_t size) {
|
#include "types.h"
|
||||||
|
|
||||||
|
|
||||||
|
void us_build_short_options(const struct option opts[], char *short_opts, uz size) {
|
||||||
memset(short_opts, 0, size);
|
memset(short_opts, 0, size);
|
||||||
for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
|
for (uint short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
|
||||||
assert(short_index < size - 3);
|
assert(short_index < size - 3);
|
||||||
if (isalpha(opts[opt_index].val)) {
|
if (isalpha(opts[opt_index].val)) {
|
||||||
short_opts[short_index] = opts[opt_index].val;
|
short_opts[short_index] = opts[opt_index].val;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,12 +22,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include "types.h"
|
||||||
|
|
||||||
|
|
||||||
void us_build_short_options(const struct option opts[], char *short_opts, size_t size);
|
void us_build_short_options(const struct option opts[], char *short_opts, uz size);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -25,8 +25,6 @@
|
|||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
# define HAS_PDEATHSIG
|
# define HAS_PDEATHSIG
|
||||||
@@ -58,6 +56,7 @@
|
|||||||
# include <sys/procctl.h>
|
# include <sys/procctl.h>
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
#include "types.h"
|
||||||
#ifdef WITH_SETPROCTITLE
|
#ifdef WITH_SETPROCTITLE
|
||||||
# include "tools.h"
|
# include "tools.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -98,18 +97,18 @@ INLINE int us_process_track_parent_death(void) {
|
|||||||
#ifdef WITH_SETPROCTITLE
|
#ifdef WITH_SETPROCTITLE
|
||||||
# pragma GCC diagnostic ignored "-Wunused-parameter"
|
# pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
# pragma GCC diagnostic push
|
# pragma GCC diagnostic push
|
||||||
INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefix) {
|
INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefix) { // cppcheck-suppress constParameter
|
||||||
# pragma GCC diagnostic pop
|
# pragma GCC diagnostic pop
|
||||||
|
|
||||||
char *cmdline = NULL;
|
char *cmdline = NULL;
|
||||||
size_t allocated = 2048;
|
uz allocated = 2048;
|
||||||
size_t used = 0;
|
uz used = 0;
|
||||||
|
|
||||||
US_REALLOC(cmdline, allocated);
|
US_REALLOC(cmdline, allocated);
|
||||||
cmdline[0] = '\0';
|
cmdline[0] = '\0';
|
||||||
|
|
||||||
for (int index = 0; index < argc; ++index) {
|
for (int index = 0; index < argc; ++index) {
|
||||||
size_t arg_len = strlen(argv[index]);
|
uz 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;
|
||||||
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
|
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
@@ -37,13 +36,14 @@
|
|||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
|
||||||
|
|
||||||
#ifdef PTHREAD_MAX_NAMELEN_NP
|
#ifdef PTHREAD_MAX_NAMELEN_NP
|
||||||
# define US_MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
# define US_THREAD_NAME_SIZE ((uz)(PTHREAD_MAX_NAMELEN_NP))
|
||||||
#else
|
#else
|
||||||
# define US_MAX_THREAD_NAME ((size_t)16)
|
# define US_THREAD_NAME_SIZE ((uz)16)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
|
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
|
||||||
@@ -51,8 +51,8 @@
|
|||||||
|
|
||||||
#ifdef WITH_PTHREAD_NP
|
#ifdef WITH_PTHREAD_NP
|
||||||
# define US_THREAD_RENAME(x_fmt, ...) { \
|
# define US_THREAD_RENAME(x_fmt, ...) { \
|
||||||
char m_new_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
char m_new_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
|
||||||
assert(snprintf(m_new_tname_buf, US_MAX_THREAD_NAME, (x_fmt), ##__VA_ARGS__) > 0); \
|
US_SNPRINTF(m_new_tname_buf, (US_THREAD_NAME_SIZE - 1), (x_fmt), ##__VA_ARGS__); \
|
||||||
us_thread_set_name(m_new_tname_buf); \
|
us_thread_set_name(m_new_tname_buf); \
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@@ -89,12 +89,12 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
|
|||||||
#ifdef WITH_PTHREAD_NP
|
#ifdef WITH_PTHREAD_NP
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
# if defined(__linux__) || defined (__NetBSD__)
|
# if defined(__linux__) || defined (__NetBSD__)
|
||||||
retval = pthread_getname_np(pthread_self(), name, US_MAX_THREAD_NAME);
|
retval = pthread_getname_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
|
||||||
# elif \
|
# elif \
|
||||||
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|
||||||
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|
||||||
|| defined(__DragonFly__)
|
|| defined(__DragonFly__)
|
||||||
pthread_get_name_np(pthread_self(), name, US_MAX_THREAD_NAME);
|
pthread_get_name_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
|
||||||
if (name[0] != '\0') {
|
if (name[0] != '\0') {
|
||||||
retval = 0;
|
retval = 0;
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
|
|||||||
const pid_t tid = 0; // Makes cppcheck happy
|
const pid_t tid = 0; // Makes cppcheck happy
|
||||||
# warning gettid() not implemented
|
# warning gettid() not implemented
|
||||||
#endif
|
#endif
|
||||||
assert(snprintf(name, US_MAX_THREAD_NAME, "tid=%d", tid) > 0);
|
US_SNPRINTF(name, (US_THREAD_NAME_SIZE - 1), "tid=%d", tid);
|
||||||
|
|
||||||
#ifdef WITH_PTHREAD_NP
|
#ifdef WITH_PTHREAD_NP
|
||||||
}
|
}
|
||||||
|
|||||||
106
src/libs/tools.h
106
src/libs/tools.h
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -23,19 +23,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <locale.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/file.h>
|
#include <sys/file.h>
|
||||||
|
|
||||||
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
|
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
|
||||||
@@ -44,6 +40,8 @@
|
|||||||
# include <signal.h>
|
# include <signal.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
|
||||||
#ifdef NDEBUG
|
#ifdef NDEBUG
|
||||||
# error WTF dude? Asserts are good things!
|
# error WTF dude? Asserts are good things!
|
||||||
@@ -57,14 +55,27 @@
|
|||||||
#define RN "\r\n"
|
#define RN "\r\n"
|
||||||
|
|
||||||
#define INLINE inline __attribute__((always_inline))
|
#define INLINE inline __attribute__((always_inline))
|
||||||
#define UNUSED __attribute__((unused))
|
|
||||||
|
|
||||||
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
||||||
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
||||||
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
|
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); x_dest = NULL; } }
|
||||||
|
#define US_CLOSE_FD(x_dest, x_close) { if (x_dest >= 0) { x_close(x_dest); x_dest = -1; } }
|
||||||
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
|
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
|
||||||
|
|
||||||
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) >= 0)
|
#define US_SNPRINTF(x_dest, x_size, x_fmt, ...) assert(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0)
|
||||||
|
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0)
|
||||||
|
|
||||||
|
#define US_MIN(x_a, x_b) ({ \
|
||||||
|
__typeof__(x_a) m_a = (x_a); \
|
||||||
|
__typeof__(x_b) m_b = (x_b); \
|
||||||
|
(m_a < m_b ? m_a : m_b); \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define US_MAX(x_a, x_b) ({ \
|
||||||
|
__typeof__(x_a) m_a = (x_a); \
|
||||||
|
__typeof__(x_b) m_b = (x_b); \
|
||||||
|
(m_a > m_b ? m_a : m_b); \
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
INLINE char *us_strdup(const char *str) {
|
INLINE char *us_strdup(const char *str) {
|
||||||
@@ -77,23 +88,15 @@ INLINE const char *us_bool_to_string(bool flag) {
|
|||||||
return (flag ? "true" : "false");
|
return (flag ? "true" : "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE size_t us_align_size(size_t size, size_t to) {
|
INLINE uz us_align_size(uz size, uz to) {
|
||||||
return ((size + (to - 1)) & ~(to - 1));
|
return ((size + (to - 1)) & ~(to - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE unsigned us_min_u(unsigned a, unsigned b) {
|
INLINE sll us_floor_ms(ldf now) {
|
||||||
return (a < b ? a : b);
|
return (sll)now - (now < (sll)now); // floor()
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE unsigned us_max_u(unsigned a, unsigned b) {
|
INLINE u32 us_triple_u32(u32 x) {
|
||||||
return (a > b ? a : b);
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE long long us_floor_ms(long double now) {
|
|
||||||
return (long long)now - (now < (long long)now); // floor()
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE uint32_t us_triple_u32(uint32_t x) {
|
|
||||||
// https://nullprogram.com/blog/2018/07/31/
|
// https://nullprogram.com/blog/2018/07/31/
|
||||||
x ^= x >> 17;
|
x ^= x >> 17;
|
||||||
x *= UINT32_C(0xED5AD4BB);
|
x *= UINT32_C(0xED5AD4BB);
|
||||||
@@ -117,48 +120,38 @@ INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(CLOCK_MONOTONIC_RAW)
|
INLINE ldf us_get_now_monotonic(void) {
|
||||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
|
|
||||||
#elif defined(CLOCK_MONOTONIC_FAST)
|
|
||||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
|
|
||||||
#else
|
|
||||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
INLINE long double us_get_now_monotonic(void) {
|
|
||||||
time_t sec;
|
time_t sec;
|
||||||
long msec;
|
long msec;
|
||||||
us_get_now(_X_CLOCK_MONOTONIC, &sec, &msec);
|
us_get_now(CLOCK_MONOTONIC, &sec, &msec);
|
||||||
return (long double)sec + ((long double)msec) / 1000;
|
return (ldf)sec + ((ldf)msec) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE uint64_t us_get_now_monotonic_u64(void) {
|
INLINE u64 us_get_now_monotonic_u64(void) {
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
assert(!clock_gettime(_X_CLOCK_MONOTONIC, &ts));
|
assert(!clock_gettime(CLOCK_MONOTONIC, &ts));
|
||||||
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
|
return (u64)(ts.tv_nsec / 1000) + (u64)ts.tv_sec * 1000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef _X_CLOCK_MONOTONIC
|
INLINE u64 us_get_now_id(void) {
|
||||||
|
const u64 now = us_get_now_monotonic_u64();
|
||||||
INLINE uint64_t us_get_now_id(void) {
|
return (u64)us_triple_u32(now) | ((u64)us_triple_u32(now + 12345) << 32);
|
||||||
const uint64_t now = us_get_now_monotonic_u64();
|
|
||||||
return (uint64_t)us_triple_u32(now) | ((uint64_t)us_triple_u32(now + 12345) << 32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE long double us_get_now_real(void) {
|
INLINE ldf us_get_now_real(void) {
|
||||||
time_t sec;
|
time_t sec;
|
||||||
long msec;
|
long msec;
|
||||||
us_get_now(CLOCK_REALTIME, &sec, &msec);
|
us_get_now(CLOCK_REALTIME, &sec, &msec);
|
||||||
return (long double)sec + ((long double)msec) / 1000;
|
return (ldf)sec + ((ldf)msec) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE unsigned us_get_cores_available(void) {
|
INLINE uint us_get_cores_available(void) {
|
||||||
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
||||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||||
return us_max_u(us_min_u(cores_sysconf, 4), 1);
|
return US_MAX(US_MIN(cores_sysconf, 4), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
|
INLINE void us_ld_to_timespec(ldf ld, struct timespec *ts) {
|
||||||
ts->tv_sec = (long)ld;
|
ts->tv_sec = (long)ld;
|
||||||
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
|
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
|
||||||
if (ts->tv_nsec > 999999999L) {
|
if (ts->tv_nsec > 999999999L) {
|
||||||
@@ -167,12 +160,12 @@ INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE long double us_timespec_to_ld(const struct timespec *ts) {
|
INLINE ldf us_timespec_to_ld(const struct timespec *ts) {
|
||||||
return ts->tv_sec + ((long double)ts->tv_nsec) / 1000000000;
|
return ts->tv_sec + ((ldf)ts->tv_nsec) / 1000000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
|
INLINE int us_flock_timedwait_monotonic(int fd, ldf timeout) {
|
||||||
const long double deadline_ts = us_get_now_monotonic() + timeout;
|
const ldf deadline_ts = us_get_now_monotonic() + timeout;
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -188,15 +181,16 @@ INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
INLINE char *us_errno_to_string(int error) {
|
INLINE char *us_errno_to_string(int error) {
|
||||||
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
|
char buf[2048];
|
||||||
char *buf;
|
const uz max_len = sizeof(buf) - 1;
|
||||||
if (locale) {
|
# if (_POSIX_C_SOURCE >= 200112L) && ! _GNU_SOURCE
|
||||||
buf = us_strdup(strerror_l(error, locale));
|
if (strerror_r(error, buf, max_len) != 0) {
|
||||||
freelocale(locale);
|
US_SNPRINTF(buf, max_len, "Errno = %d", error);
|
||||||
} else {
|
|
||||||
buf = us_strdup("!!! newlocale() error !!!");
|
|
||||||
}
|
}
|
||||||
return buf;
|
return us_strdup(buf);
|
||||||
|
# else
|
||||||
|
return us_strdup(strerror_r(error, buf, max_len));
|
||||||
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE char *us_signum_to_string(int signum) {
|
INLINE char *us_signum_to_string(int signum) {
|
||||||
|
|||||||
44
src/libs/types.h
Normal file
44
src/libs/types.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef long long sll;
|
||||||
|
typedef ssize_t sz;
|
||||||
|
typedef int8_t s8;
|
||||||
|
typedef int16_t s16;
|
||||||
|
typedef int32_t s32;
|
||||||
|
typedef int64_t s64;
|
||||||
|
|
||||||
|
typedef unsigned uint;
|
||||||
|
typedef unsigned long long ull;
|
||||||
|
typedef size_t uz;
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint16_t u16;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef long double ldf;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,6 +22,17 @@
|
|||||||
|
|
||||||
#include "unjpeg.h"
|
#include "unjpeg.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <jpeglib.h>
|
||||||
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "logging.h"
|
||||||
|
#include "frame.h"
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
struct jpeg_error_mgr mgr; // Default manager
|
struct jpeg_error_mgr mgr; // Default manager
|
||||||
@@ -77,9 +88,9 @@ int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
|
|||||||
jpeg_finish_decompress(&jpeg);
|
jpeg_finish_decompress(&jpeg);
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
jpeg_destroy_decompress(&jpeg);
|
jpeg_destroy_decompress(&jpeg);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _jpeg_error_handler(j_common_ptr jpeg) {
|
static void _jpeg_error_handler(j_common_ptr jpeg) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -22,18 +22,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdio.h>
|
#include "types.h"
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <setjmp.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include <jpeglib.h>
|
|
||||||
#include <linux/videodev2.h>
|
|
||||||
|
|
||||||
#include "logging.h"
|
|
||||||
#include "frame.h"
|
#include "frame.h"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -26,11 +26,13 @@
|
|||||||
|
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
|
||||||
#ifndef US_CFG_XIOCTL_RETRIES
|
#ifndef US_CFG_XIOCTL_RETRIES
|
||||||
# define US_CFG_XIOCTL_RETRIES 4
|
# define US_CFG_XIOCTL_RETRIES 4
|
||||||
#endif
|
#endif
|
||||||
#define _XIOCTL_RETRIES ((unsigned)(US_CFG_XIOCTL_RETRIES))
|
#define _XIOCTL_RETRIES ((uint)(US_CFG_XIOCTL_RETRIES))
|
||||||
|
|
||||||
|
|
||||||
INLINE int us_xioctl(int fd, int request, void *arg) {
|
INLINE int us_xioctl(int fd, int request, void *arg) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -63,16 +63,16 @@ static us_frame_s *_init_external(const char *path) {
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
# define CHUNK_SIZE ((size_t)(100 * 1024))
|
const size_t chunk_size = 100 * 1024;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (blank->used + CHUNK_SIZE >= blank->allocated) {
|
if (blank->used + chunk_size >= blank->allocated) {
|
||||||
us_frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
|
us_frame_realloc_data(blank, blank->used + chunk_size * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
|
const size_t readed = fread(blank->data + blank->used, 1, chunk_size, fp);
|
||||||
blank->used += readed;
|
blank->used += readed;
|
||||||
|
|
||||||
if (readed < CHUNK_SIZE) {
|
if (readed < chunk_size) {
|
||||||
if (feof(fp)) {
|
if (feof(fp)) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
@@ -81,7 +81,6 @@ static us_frame_s *_init_external(const char *path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# undef CHUNK_SIZE
|
|
||||||
|
|
||||||
us_frame_s *const decoded = us_frame_init();
|
us_frame_s *const decoded = us_frame_init();
|
||||||
if (us_unjpeg(blank, decoded, false) < 0) {
|
if (us_unjpeg(blank, decoded, false) < 0) {
|
||||||
@@ -94,12 +93,10 @@ static us_frame_s *_init_external(const char *path) {
|
|||||||
|
|
||||||
goto ok;
|
goto ok;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
us_frame_destroy(blank);
|
US_DELETE(blank, us_frame_destroy);
|
||||||
blank = NULL;
|
|
||||||
|
|
||||||
ok:
|
|
||||||
US_DELETE(fp, fclose);
|
|
||||||
|
|
||||||
|
ok:
|
||||||
|
US_DELETE(fp, fclose);
|
||||||
return blank;
|
return blank;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -1,900 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
# #
|
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
|
||||||
# #
|
|
||||||
# Copyright (C) 2018-2022 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 "device.h"
|
|
||||||
|
|
||||||
|
|
||||||
static const struct {
|
|
||||||
const char *name;
|
|
||||||
const v4l2_std_id standard;
|
|
||||||
} _STANDARDS[] = {
|
|
||||||
{"UNKNOWN", V4L2_STD_UNKNOWN},
|
|
||||||
{"PAL", V4L2_STD_PAL},
|
|
||||||
{"NTSC", V4L2_STD_NTSC},
|
|
||||||
{"SECAM", V4L2_STD_SECAM},
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct {
|
|
||||||
const char *name; // cppcheck-suppress unusedStructMember
|
|
||||||
const unsigned format; // cppcheck-suppress unusedStructMember
|
|
||||||
} _FORMATS[] = {
|
|
||||||
{"YUYV", V4L2_PIX_FMT_YUYV},
|
|
||||||
{"UYVY", V4L2_PIX_FMT_UYVY},
|
|
||||||
{"RGB565", V4L2_PIX_FMT_RGB565},
|
|
||||||
{"RGB24", V4L2_PIX_FMT_RGB24},
|
|
||||||
{"MJPEG", V4L2_PIX_FMT_MJPEG},
|
|
||||||
{"JPEG", V4L2_PIX_FMT_JPEG},
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct {
|
|
||||||
const char *name; // cppcheck-suppress unusedStructMember
|
|
||||||
const enum v4l2_memory io_method; // cppcheck-suppress unusedStructMember
|
|
||||||
} _IO_METHODS[] = {
|
|
||||||
{"MMAP", V4L2_MEMORY_MMAP},
|
|
||||||
{"USERPTR", V4L2_MEMORY_USERPTR},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static int _device_open_check_cap(us_device_s *dev);
|
|
||||||
static int _device_open_dv_timings(us_device_s *dev);
|
|
||||||
static int _device_apply_dv_timings(us_device_s *dev);
|
|
||||||
static int _device_open_format(us_device_s *dev, bool first);
|
|
||||||
static void _device_open_hw_fps(us_device_s *dev);
|
|
||||||
static void _device_open_jpeg_quality(us_device_s *dev);
|
|
||||||
static int _device_open_io_method(us_device_s *dev);
|
|
||||||
static int _device_open_io_method_mmap(us_device_s *dev);
|
|
||||||
static int _device_open_io_method_userptr(us_device_s *dev);
|
|
||||||
static int _device_open_queue_buffers(us_device_s *dev);
|
|
||||||
static int _device_apply_resolution(us_device_s *dev, unsigned width, unsigned height);
|
|
||||||
|
|
||||||
static void _device_apply_controls(us_device_s *dev);
|
|
||||||
static int _device_query_control(
|
|
||||||
us_device_s *dev, struct v4l2_queryctrl *query,
|
|
||||||
const char *name, unsigned cid, bool quiet);
|
|
||||||
static void _device_set_control(
|
|
||||||
us_device_s *dev, struct v4l2_queryctrl *query,
|
|
||||||
const char *name, unsigned cid, int value, bool quiet);
|
|
||||||
|
|
||||||
static const char *_format_to_string_nullable(unsigned format);
|
|
||||||
static const char *_format_to_string_supported(unsigned format);
|
|
||||||
static const char *_standard_to_string(v4l2_std_id standard);
|
|
||||||
static const char *_io_method_to_string_supported(enum v4l2_memory io_method);
|
|
||||||
|
|
||||||
|
|
||||||
#define _RUN(x_next) dev->run->x_next
|
|
||||||
#define _D_XIOCTL(...) us_xioctl(_RUN(fd), __VA_ARGS__)
|
|
||||||
|
|
||||||
|
|
||||||
us_device_s *us_device_init(void) {
|
|
||||||
us_device_runtime_s *run;
|
|
||||||
US_CALLOC(run, 1);
|
|
||||||
run->fd = -1;
|
|
||||||
|
|
||||||
us_device_s *dev;
|
|
||||||
US_CALLOC(dev, 1);
|
|
||||||
dev->path = "/dev/video0";
|
|
||||||
dev->width = 640;
|
|
||||||
dev->height = 480;
|
|
||||||
dev->format = V4L2_PIX_FMT_YUYV;
|
|
||||||
dev->jpeg_quality = 80;
|
|
||||||
dev->standard = V4L2_STD_UNKNOWN;
|
|
||||||
dev->io_method = V4L2_MEMORY_MMAP;
|
|
||||||
dev->n_bufs = us_get_cores_available() + 1;
|
|
||||||
dev->min_frame_size = 128;
|
|
||||||
dev->timeout = 1;
|
|
||||||
dev->run = run;
|
|
||||||
return dev;
|
|
||||||
}
|
|
||||||
|
|
||||||
void us_device_destroy(us_device_s *dev) {
|
|
||||||
free(dev->run);
|
|
||||||
free(dev);
|
|
||||||
}
|
|
||||||
|
|
||||||
int us_device_parse_format(const char *str) {
|
|
||||||
US_ARRAY_ITERATE(_FORMATS, 0, item, {
|
|
||||||
if (!strcasecmp(item->name, str)) {
|
|
||||||
return item->format;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return US_FORMAT_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
v4l2_std_id us_device_parse_standard(const char *str) {
|
|
||||||
US_ARRAY_ITERATE(_STANDARDS, 1, item, {
|
|
||||||
if (!strcasecmp(item->name, str)) {
|
|
||||||
return item->standard;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return US_STANDARD_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
int us_device_parse_io_method(const char *str) {
|
|
||||||
US_ARRAY_ITERATE(_IO_METHODS, 0, item, {
|
|
||||||
if (!strcasecmp(item->name, str)) {
|
|
||||||
return item->io_method;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return US_IO_METHOD_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
int us_device_open(us_device_s *dev) {
|
|
||||||
if ((_RUN(fd) = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
|
|
||||||
US_LOG_PERROR("Can't open device");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
US_LOG_INFO("Device fd=%d opened", _RUN(fd));
|
|
||||||
|
|
||||||
if (_device_open_check_cap(dev) < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
if (_device_open_dv_timings(dev) < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
if (_device_open_format(dev, true) < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
_device_open_hw_fps(dev);
|
|
||||||
_device_open_jpeg_quality(dev);
|
|
||||||
if (_device_open_io_method(dev) < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
if (_device_open_queue_buffers(dev) < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
_device_apply_controls(dev);
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Device fd=%d initialized", _RUN(fd));
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error:
|
|
||||||
us_device_close(dev);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void us_device_close(us_device_s *dev) {
|
|
||||||
_RUN(persistent_timeout_reported) = false;
|
|
||||||
|
|
||||||
if (_RUN(hw_bufs) != NULL) {
|
|
||||||
US_LOG_DEBUG("Releasing device buffers ...");
|
|
||||||
for (unsigned index = 0; index < _RUN(n_bufs); ++index) {
|
|
||||||
# define HW(x_next) _RUN(hw_bufs)[index].x_next
|
|
||||||
|
|
||||||
if (HW(dma_fd) >= 0) {
|
|
||||||
close(HW(dma_fd));
|
|
||||||
HW(dma_fd) = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dev->io_method == V4L2_MEMORY_MMAP) {
|
|
||||||
if (HW(raw.allocated) > 0 && HW(raw.data) != NULL) {
|
|
||||||
if (munmap(HW(raw.data), HW(raw.allocated)) < 0) {
|
|
||||||
US_LOG_PERROR("Can't unmap device buffer=%u", index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // V4L2_MEMORY_USERPTR
|
|
||||||
US_DELETE(HW(raw.data), free);
|
|
||||||
}
|
|
||||||
|
|
||||||
# undef HW
|
|
||||||
}
|
|
||||||
_RUN(n_bufs) = 0;
|
|
||||||
free(_RUN(hw_bufs));
|
|
||||||
_RUN(hw_bufs) = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_RUN(fd) >= 0) {
|
|
||||||
US_LOG_DEBUG("Closing device ...");
|
|
||||||
if (close(_RUN(fd)) < 0) {
|
|
||||||
US_LOG_PERROR("Can't close device fd=%d", _RUN(fd));
|
|
||||||
} else {
|
|
||||||
US_LOG_INFO("Device fd=%d closed", _RUN(fd));
|
|
||||||
}
|
|
||||||
_RUN(fd) = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int us_device_export_to_dma(us_device_s *dev) {
|
|
||||||
# define DMA_FD _RUN(hw_bufs[index].dma_fd)
|
|
||||||
|
|
||||||
for (unsigned index = 0; index < _RUN(n_bufs); ++index) {
|
|
||||||
struct v4l2_exportbuffer exp = {0};
|
|
||||||
exp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
exp.index = index;
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Exporting device buffer=%u to DMA ...", index);
|
|
||||||
if (_D_XIOCTL(VIDIOC_EXPBUF, &exp) < 0) {
|
|
||||||
US_LOG_PERROR("Can't export device buffer=%u to DMA", index);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
DMA_FD = exp.fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error:
|
|
||||||
for (unsigned index = 0; index < _RUN(n_bufs); ++index) {
|
|
||||||
if (DMA_FD >= 0) {
|
|
||||||
close(DMA_FD);
|
|
||||||
DMA_FD = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
# undef DMA_FD
|
|
||||||
}
|
|
||||||
|
|
||||||
int us_device_switch_capturing(us_device_s *dev, bool enable) {
|
|
||||||
if (enable != _RUN(capturing)) {
|
|
||||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
|
|
||||||
US_LOG_DEBUG("%s device capturing ...", (enable ? "Starting" : "Stopping"));
|
|
||||||
if (_D_XIOCTL((enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
|
|
||||||
US_LOG_PERROR("Can't %s capturing", (enable ? "start" : "stop"));
|
|
||||||
if (enable) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_RUN(capturing) = enable;
|
|
||||||
US_LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int us_device_select(us_device_s *dev, bool *has_read, bool *has_write, bool *has_error) {
|
|
||||||
int retval;
|
|
||||||
|
|
||||||
# define INIT_FD_SET(x_set) \
|
|
||||||
fd_set x_set; FD_ZERO(&x_set); FD_SET(_RUN(fd), &x_set);
|
|
||||||
|
|
||||||
INIT_FD_SET(read_fds);
|
|
||||||
INIT_FD_SET(write_fds);
|
|
||||||
INIT_FD_SET(error_fds);
|
|
||||||
|
|
||||||
# undef INIT_FD_SET
|
|
||||||
|
|
||||||
struct timeval timeout;
|
|
||||||
timeout.tv_sec = dev->timeout;
|
|
||||||
timeout.tv_usec = 0;
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Calling select() on video device ...");
|
|
||||||
|
|
||||||
retval = select(_RUN(fd) + 1, &read_fds, &write_fds, &error_fds, &timeout);
|
|
||||||
if (retval > 0) {
|
|
||||||
*has_read = FD_ISSET(_RUN(fd), &read_fds);
|
|
||||||
*has_write = FD_ISSET(_RUN(fd), &write_fds);
|
|
||||||
*has_error = FD_ISSET(_RUN(fd), &error_fds);
|
|
||||||
} else {
|
|
||||||
*has_read = false;
|
|
||||||
*has_write = false;
|
|
||||||
*has_error = false;
|
|
||||||
}
|
|
||||||
US_LOG_DEBUG("Device select() --> %d", retval);
|
|
||||||
|
|
||||||
if (retval > 0) {
|
|
||||||
_RUN(persistent_timeout_reported) = false;
|
|
||||||
} else if (retval == 0) {
|
|
||||||
if (dev->persistent) {
|
|
||||||
if (!_RUN(persistent_timeout_reported)) {
|
|
||||||
US_LOG_ERROR("Persistent device timeout (unplugged)");
|
|
||||||
_RUN(persistent_timeout_reported) = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Если устройство не персистентное, то таймаут является ошибкой
|
|
||||||
retval = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw) {
|
|
||||||
*hw = NULL;
|
|
||||||
|
|
||||||
struct v4l2_buffer buf = {0};
|
|
||||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
buf.memory = dev->io_method;
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Grabbing device buffer ...");
|
|
||||||
if (_D_XIOCTL(VIDIOC_DQBUF, &buf) < 0) {
|
|
||||||
US_LOG_PERROR("Can't grab device buffer");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Grabbed new frame: buffer=%u, bytesused=%u", buf.index, buf.bytesused);
|
|
||||||
|
|
||||||
if (buf.index >= _RUN(n_bufs)) {
|
|
||||||
US_LOG_ERROR("V4L2 error: grabbed invalid device buffer=%u, n_bufs=%u", buf.index, _RUN(n_bufs));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for broken, corrupted frames:
|
|
||||||
// Under low light conditions corrupted frames may get captured.
|
|
||||||
// The good thing is such frames are quite small compared to the regular frames.
|
|
||||||
// For example a VGA (640x480) webcam frame is normally >= 8kByte large,
|
|
||||||
// corrupted frames are smaller.
|
|
||||||
if (buf.bytesused < dev->min_frame_size) {
|
|
||||||
US_LOG_DEBUG("Dropped too small frame, assuming it was broken: buffer=%u, bytesused=%u",
|
|
||||||
buf.index, buf.bytesused);
|
|
||||||
US_LOG_DEBUG("Releasing device buffer=%u (broken frame) ...", buf.index);
|
|
||||||
if (_D_XIOCTL(VIDIOC_QBUF, &buf) < 0) {
|
|
||||||
US_LOG_PERROR("Can't release device buffer=%u (broken frame)", buf.index);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
# define HW(x_next) _RUN(hw_bufs)[buf.index].x_next
|
|
||||||
|
|
||||||
if (HW(grabbed)) {
|
|
||||||
US_LOG_ERROR("V4L2 error: grabbed device buffer=%u is already used", buf.index);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
HW(grabbed) = true;
|
|
||||||
|
|
||||||
HW(raw.dma_fd) = HW(dma_fd);
|
|
||||||
HW(raw.used) = buf.bytesused;
|
|
||||||
HW(raw.width) = _RUN(width);
|
|
||||||
HW(raw.height) = _RUN(height);
|
|
||||||
HW(raw.format) = _RUN(format);
|
|
||||||
HW(raw.stride) = _RUN(stride);
|
|
||||||
HW(raw.online) = true;
|
|
||||||
memcpy(&HW(buf), &buf, sizeof(struct v4l2_buffer));
|
|
||||||
HW(raw.grab_ts) = us_get_now_monotonic();
|
|
||||||
|
|
||||||
# undef HW
|
|
||||||
*hw = &_RUN(hw_bufs[buf.index]);
|
|
||||||
return buf.index;
|
|
||||||
}
|
|
||||||
|
|
||||||
int us_device_release_buffer(us_device_s *dev, us_hw_buffer_s *hw) {
|
|
||||||
const unsigned index = hw->buf.index;
|
|
||||||
US_LOG_DEBUG("Releasing device buffer=%u ...", index);
|
|
||||||
|
|
||||||
if (_D_XIOCTL(VIDIOC_QBUF, &hw->buf) < 0) {
|
|
||||||
US_LOG_PERROR("Can't release device buffer=%u", index);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
hw->grabbed = false;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int us_device_consume_event(us_device_s *dev) {
|
|
||||||
struct v4l2_event event;
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Consuming V4L2 event ...");
|
|
||||||
if (_D_XIOCTL(VIDIOC_DQEVENT, &event) == 0) {
|
|
||||||
switch (event.type) {
|
|
||||||
case V4L2_EVENT_SOURCE_CHANGE:
|
|
||||||
US_LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
|
|
||||||
return -1;
|
|
||||||
case V4L2_EVENT_EOS:
|
|
||||||
US_LOG_INFO("Got V4L2_EVENT_EOS: end of stream (ignored)");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
US_LOG_PERROR("Got some V4L2 device event, but where is it? ");
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _device_open_check_cap(us_device_s *dev) {
|
|
||||||
struct v4l2_capability cap = {0};
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Querying device capabilities ...");
|
|
||||||
if (_D_XIOCTL(VIDIOC_QUERYCAP, &cap) < 0) {
|
|
||||||
US_LOG_PERROR("Can't query device capabilities");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
|
||||||
US_LOG_ERROR("Video capture is not supported by device");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
|
|
||||||
US_LOG_ERROR("Device doesn't support streaming IO");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int input = dev->input; // Needs a pointer to int for ioctl()
|
|
||||||
US_LOG_INFO("Using input channel: %d", input);
|
|
||||||
if (_D_XIOCTL(VIDIOC_S_INPUT, &input) < 0) {
|
|
||||||
US_LOG_ERROR("Can't set input channel");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dev->standard != V4L2_STD_UNKNOWN) {
|
|
||||||
US_LOG_INFO("Using TV standard: %s", _standard_to_string(dev->standard));
|
|
||||||
if (_D_XIOCTL(VIDIOC_S_STD, &dev->standard) < 0) {
|
|
||||||
US_LOG_ERROR("Can't set video standard");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
US_LOG_DEBUG("Using TV standard: DEFAULT");
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _device_open_dv_timings(us_device_s *dev) {
|
|
||||||
_device_apply_resolution(dev, dev->width, dev->height);
|
|
||||||
if (dev->dv_timings) {
|
|
||||||
US_LOG_DEBUG("Using DV-timings");
|
|
||||||
|
|
||||||
if (_device_apply_dv_timings(dev) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct v4l2_event_subscription sub = {0};
|
|
||||||
sub.type = V4L2_EVENT_SOURCE_CHANGE;
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Subscribing to DV-timings events ...")
|
|
||||||
if (_D_XIOCTL(VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
|
|
||||||
US_LOG_PERROR("Can't subscribe to DV-timings events");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _device_apply_dv_timings(us_device_s *dev) {
|
|
||||||
struct v4l2_dv_timings dv = {0};
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Calling us_xioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
|
|
||||||
if (_D_XIOCTL(VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
|
|
||||||
if (dv.type == V4L2_DV_BT_656_1120) {
|
|
||||||
// See v4l2_print_dv_timings() in the kernel
|
|
||||||
const unsigned htot = V4L2_DV_BT_FRAME_WIDTH(&dv.bt);
|
|
||||||
const unsigned vtot = V4L2_DV_BT_FRAME_HEIGHT(&dv.bt) / (dv.bt.interlaced ? 2 : 1);
|
|
||||||
const unsigned fps = ((htot * vtot) > 0 ? ((100 * (uint64_t)dv.bt.pixelclock)) / (htot * vtot) : 0);
|
|
||||||
US_LOG_INFO("Got new DV-timings: %ux%u%s%u.%02u, pixclk=%llu, vsync=%u, hsync=%u",
|
|
||||||
dv.bt.width, dv.bt.height, (dv.bt.interlaced ? "i" : "p"), fps / 100, fps % 100,
|
|
||||||
(unsigned long long)dv.bt.pixelclock, dv.bt.vsync, dv.bt.hsync); // See #11 about %llu
|
|
||||||
} else {
|
|
||||||
US_LOG_INFO("Got new DV-timings: %ux%u, pixclk=%llu, vsync=%u, hsync=%u",
|
|
||||||
dv.bt.width, dv.bt.height,
|
|
||||||
(unsigned long long)dv.bt.pixelclock, dv.bt.vsync, dv.bt.hsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Calling us_xioctl(VIDIOC_S_DV_TIMINGS) ...");
|
|
||||||
if (_D_XIOCTL(VIDIOC_S_DV_TIMINGS, &dv) < 0) {
|
|
||||||
US_LOG_PERROR("Failed to set DV-timings");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_device_apply_resolution(dev, dv.bt.width, dv.bt.height) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
US_LOG_DEBUG("Calling us_xioctl(VIDIOC_QUERYSTD) ...");
|
|
||||||
if (_D_XIOCTL(VIDIOC_QUERYSTD, &dev->standard) == 0) {
|
|
||||||
US_LOG_INFO("Applying the new VIDIOC_S_STD: %s ...", _standard_to_string(dev->standard));
|
|
||||||
if (_D_XIOCTL(VIDIOC_S_STD, &dev->standard) < 0) {
|
|
||||||
US_LOG_PERROR("Can't set video standard");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _device_open_format(us_device_s *dev, bool first) {
|
|
||||||
const unsigned stride = us_align_size(_RUN(width), 32) << 1;
|
|
||||||
|
|
||||||
struct v4l2_format fmt = {0};
|
|
||||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
fmt.fmt.pix.width = _RUN(width);
|
|
||||||
fmt.fmt.pix.height = _RUN(height);
|
|
||||||
fmt.fmt.pix.pixelformat = dev->format;
|
|
||||||
fmt.fmt.pix.field = V4L2_FIELD_ANY;
|
|
||||||
fmt.fmt.pix.bytesperline = stride;
|
|
||||||
|
|
||||||
// Set format
|
|
||||||
US_LOG_DEBUG("Probing device format=%s, stride=%u, resolution=%ux%u ...",
|
|
||||||
_format_to_string_supported(dev->format), stride, _RUN(width), _RUN(height));
|
|
||||||
if (_D_XIOCTL(VIDIOC_S_FMT, &fmt) < 0) {
|
|
||||||
US_LOG_PERROR("Can't set device format");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check resolution
|
|
||||||
bool retry = false;
|
|
||||||
if (fmt.fmt.pix.width != _RUN(width) || fmt.fmt.pix.height != _RUN(height)) {
|
|
||||||
US_LOG_ERROR("Requested resolution=%ux%u is unavailable", _RUN(width), _RUN(height));
|
|
||||||
retry = true;
|
|
||||||
}
|
|
||||||
if (_device_apply_resolution(dev, fmt.fmt.pix.width, fmt.fmt.pix.height) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (first && retry) {
|
|
||||||
return _device_open_format(dev, false);
|
|
||||||
}
|
|
||||||
US_LOG_INFO("Using resolution: %ux%u", _RUN(width), _RUN(height));
|
|
||||||
|
|
||||||
// Check format
|
|
||||||
if (fmt.fmt.pix.pixelformat != dev->format) {
|
|
||||||
US_LOG_ERROR("Could not obtain the requested format=%s; driver gave us %s",
|
|
||||||
_format_to_string_supported(dev->format),
|
|
||||||
_format_to_string_supported(fmt.fmt.pix.pixelformat));
|
|
||||||
|
|
||||||
char *format_str;
|
|
||||||
if ((format_str = (char *)_format_to_string_nullable(fmt.fmt.pix.pixelformat)) != NULL) {
|
|
||||||
US_LOG_INFO("Falling back to format=%s", format_str);
|
|
||||||
} else {
|
|
||||||
char fourcc_str[8];
|
|
||||||
US_LOG_ERROR("Unsupported format=%s (fourcc)",
|
|
||||||
us_fourcc_to_string(fmt.fmt.pix.pixelformat, fourcc_str, 8));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_RUN(format) = fmt.fmt.pix.pixelformat;
|
|
||||||
US_LOG_INFO("Using format: %s", _format_to_string_supported(_RUN(format)));
|
|
||||||
|
|
||||||
_RUN(stride) = fmt.fmt.pix.bytesperline;
|
|
||||||
_RUN(raw_size) = fmt.fmt.pix.sizeimage; // Only for userptr
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _device_open_hw_fps(us_device_s *dev) {
|
|
||||||
_RUN(hw_fps) = 0;
|
|
||||||
|
|
||||||
struct v4l2_streamparm setfps = {0};
|
|
||||||
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Querying HW FPS ...");
|
|
||||||
if (_D_XIOCTL(VIDIOC_G_PARM, &setfps) < 0) {
|
|
||||||
if (errno == ENOTTY) { // Quiet message for TC358743
|
|
||||||
US_LOG_INFO("Querying HW FPS changing is not supported");
|
|
||||||
} else {
|
|
||||||
US_LOG_PERROR("Can't query HW FPS changing");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(setfps.parm.capture.capability & V4L2_CAP_TIMEPERFRAME)) {
|
|
||||||
US_LOG_INFO("Changing HW FPS is not supported");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
# define SETFPS_TPF(x_next) setfps.parm.capture.timeperframe.x_next
|
|
||||||
|
|
||||||
US_MEMSET_ZERO(setfps);
|
|
||||||
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
SETFPS_TPF(numerator) = 1;
|
|
||||||
SETFPS_TPF(denominator) = (dev->desired_fps == 0 ? 255 : dev->desired_fps);
|
|
||||||
|
|
||||||
if (_D_XIOCTL(VIDIOC_S_PARM, &setfps) < 0) {
|
|
||||||
US_LOG_PERROR("Can't set HW FPS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SETFPS_TPF(numerator) != 1) {
|
|
||||||
US_LOG_ERROR("Invalid HW FPS numerator: %u != 1", SETFPS_TPF(numerator));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SETFPS_TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай
|
|
||||||
US_LOG_ERROR("Invalid HW FPS denominator: 0");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_RUN(hw_fps) = SETFPS_TPF(denominator);
|
|
||||||
if (dev->desired_fps != _RUN(hw_fps)) {
|
|
||||||
US_LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, _RUN(hw_fps));
|
|
||||||
} else {
|
|
||||||
US_LOG_INFO("Using HW FPS: %u", _RUN(hw_fps));
|
|
||||||
}
|
|
||||||
|
|
||||||
# undef SETFPS_TPF
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _device_open_jpeg_quality(us_device_s *dev) {
|
|
||||||
unsigned quality = 0;
|
|
||||||
|
|
||||||
if (us_is_jpeg(_RUN(format))) {
|
|
||||||
struct v4l2_jpegcompression comp = {0};
|
|
||||||
|
|
||||||
if (_D_XIOCTL(VIDIOC_G_JPEGCOMP, &comp) < 0) {
|
|
||||||
US_LOG_ERROR("Device doesn't support setting of HW encoding quality parameters");
|
|
||||||
} else {
|
|
||||||
comp.quality = dev->jpeg_quality;
|
|
||||||
if (_D_XIOCTL(VIDIOC_S_JPEGCOMP, &comp) < 0) {
|
|
||||||
US_LOG_ERROR("Can't change MJPEG quality for JPEG source with HW pass-through encoder");
|
|
||||||
} else {
|
|
||||||
quality = dev->jpeg_quality;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_RUN(jpeg_quality) = quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _device_open_io_method(us_device_s *dev) {
|
|
||||||
US_LOG_INFO("Using IO method: %s", _io_method_to_string_supported(dev->io_method));
|
|
||||||
switch (dev->io_method) {
|
|
||||||
case V4L2_MEMORY_MMAP: return _device_open_io_method_mmap(dev);
|
|
||||||
case V4L2_MEMORY_USERPTR: return _device_open_io_method_userptr(dev);
|
|
||||||
default: assert(0 && "Unsupported IO method");
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _device_open_io_method_mmap(us_device_s *dev) {
|
|
||||||
struct v4l2_requestbuffers req = {0};
|
|
||||||
req.count = dev->n_bufs;
|
|
||||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
req.memory = V4L2_MEMORY_MMAP;
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Requesting %u device buffers for MMAP ...", req.count);
|
|
||||||
if (_D_XIOCTL(VIDIOC_REQBUFS, &req) < 0) {
|
|
||||||
US_LOG_PERROR("Device '%s' doesn't support MMAP method", dev->path);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.count < 1) {
|
|
||||||
US_LOG_ERROR("Insufficient buffer memory: %u", req.count);
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
US_LOG_INFO("Requested %u device buffers, got %u", dev->n_bufs, req.count);
|
|
||||||
}
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Allocating device buffers ...");
|
|
||||||
|
|
||||||
US_CALLOC(_RUN(hw_bufs), req.count);
|
|
||||||
for (_RUN(n_bufs) = 0; _RUN(n_bufs) < req.count; ++_RUN(n_bufs)) {
|
|
||||||
struct v4l2_buffer buf = {0};
|
|
||||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
buf.memory = V4L2_MEMORY_MMAP;
|
|
||||||
buf.index = _RUN(n_bufs);
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Calling us_xioctl(VIDIOC_QUERYBUF) for device buffer=%u ...", _RUN(n_bufs));
|
|
||||||
if (_D_XIOCTL(VIDIOC_QUERYBUF, &buf) < 0) {
|
|
||||||
US_LOG_PERROR("Can't VIDIOC_QUERYBUF");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
# define HW(x_next) _RUN(hw_bufs)[_RUN(n_bufs)].x_next
|
|
||||||
|
|
||||||
HW(dma_fd) = -1;
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Mapping device buffer=%u ...", _RUN(n_bufs));
|
|
||||||
if ((HW(raw.data) = mmap(
|
|
||||||
NULL,
|
|
||||||
buf.length,
|
|
||||||
PROT_READ | PROT_WRITE,
|
|
||||||
MAP_SHARED,
|
|
||||||
_RUN(fd),
|
|
||||||
buf.m.offset
|
|
||||||
)) == MAP_FAILED) {
|
|
||||||
US_LOG_PERROR("Can't map device buffer=%u", _RUN(n_bufs));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
assert(HW(raw.data) != NULL);
|
|
||||||
HW(raw.allocated) = buf.length;
|
|
||||||
|
|
||||||
# undef HW
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _device_open_io_method_userptr(us_device_s *dev) {
|
|
||||||
struct v4l2_requestbuffers req = {0};
|
|
||||||
req.count = dev->n_bufs;
|
|
||||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
req.memory = V4L2_MEMORY_USERPTR;
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Requesting %u device buffers for USERPTR ...", req.count);
|
|
||||||
if (_D_XIOCTL(VIDIOC_REQBUFS, &req) < 0) {
|
|
||||||
US_LOG_PERROR("Device '%s' doesn't support USERPTR method", dev->path);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.count < 1) {
|
|
||||||
US_LOG_ERROR("Insufficient buffer memory: %u", req.count);
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
US_LOG_INFO("Requested %u device buffers, got %u", dev->n_bufs, req.count);
|
|
||||||
}
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Allocating device buffers ...");
|
|
||||||
|
|
||||||
US_CALLOC(_RUN(hw_bufs), req.count);
|
|
||||||
|
|
||||||
const unsigned page_size = getpagesize();
|
|
||||||
const unsigned buf_size = us_align_size(_RUN(raw_size), page_size);
|
|
||||||
|
|
||||||
for (_RUN(n_bufs) = 0; _RUN(n_bufs) < req.count; ++_RUN(n_bufs)) {
|
|
||||||
# define HW(x_next) _RUN(hw_bufs)[_RUN(n_bufs)].x_next
|
|
||||||
assert((HW(raw.data) = aligned_alloc(page_size, buf_size)) != NULL);
|
|
||||||
memset(HW(raw.data), 0, buf_size);
|
|
||||||
HW(raw.allocated) = buf_size;
|
|
||||||
# undef HW
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _device_open_queue_buffers(us_device_s *dev) {
|
|
||||||
for (unsigned index = 0; index < _RUN(n_bufs); ++index) {
|
|
||||||
struct v4l2_buffer buf = {0};
|
|
||||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
||||||
buf.memory = dev->io_method;
|
|
||||||
buf.index = index;
|
|
||||||
if (dev->io_method == V4L2_MEMORY_USERPTR) {
|
|
||||||
buf.m.userptr = (unsigned long)_RUN(hw_bufs)[index].raw.data;
|
|
||||||
buf.length = _RUN(hw_bufs)[index].raw.allocated;
|
|
||||||
}
|
|
||||||
|
|
||||||
US_LOG_DEBUG("Calling us_xioctl(VIDIOC_QBUF) for buffer=%u ...", index);
|
|
||||||
if (_D_XIOCTL(VIDIOC_QBUF, &buf) < 0) {
|
|
||||||
US_LOG_PERROR("Can't VIDIOC_QBUF");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _device_apply_resolution(us_device_s *dev, unsigned width, unsigned height) {
|
|
||||||
// Тут VIDEO_MIN_* не используются из-за странностей минимального разрешения при отсутствии сигнала
|
|
||||||
// у некоторых устройств, например TC358743
|
|
||||||
if (
|
|
||||||
width == 0 || width > US_VIDEO_MAX_WIDTH
|
|
||||||
|| height == 0 || height > US_VIDEO_MAX_HEIGHT
|
|
||||||
) {
|
|
||||||
US_LOG_ERROR("Requested forbidden resolution=%ux%u: min=1x1, max=%ux%u",
|
|
||||||
width, height, US_VIDEO_MAX_WIDTH, US_VIDEO_MAX_HEIGHT);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
_RUN(width) = width;
|
|
||||||
_RUN(height) = height;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _device_apply_controls(us_device_s *dev) {
|
|
||||||
# define SET_CID_VALUE(x_cid, x_field, x_value, x_quiet) { \
|
|
||||||
struct v4l2_queryctrl m_query; \
|
|
||||||
if (_device_query_control(dev, &m_query, #x_field, x_cid, x_quiet) == 0) { \
|
|
||||||
_device_set_control(dev, &m_query, #x_field, x_cid, x_value, x_quiet); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
# define SET_CID_DEFAULT(x_cid, x_field, x_quiet) { \
|
|
||||||
struct v4l2_queryctrl m_query; \
|
|
||||||
if (_device_query_control(dev, &m_query, #x_field, x_cid, x_quiet) == 0) { \
|
|
||||||
_device_set_control(dev, &m_query, #x_field, x_cid, m_query.default_value, x_quiet); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
# define CONTROL_MANUAL_CID(x_cid, x_field) { \
|
|
||||||
if (dev->ctl.x_field.mode == CTL_MODE_VALUE) { \
|
|
||||||
SET_CID_VALUE(x_cid, x_field, dev->ctl.x_field.value, false); \
|
|
||||||
} else if (dev->ctl.x_field.mode == CTL_MODE_DEFAULT) { \
|
|
||||||
SET_CID_DEFAULT(x_cid, x_field, false); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
# define CONTROL_AUTO_CID(x_cid_auto, x_cid_manual, x_field) { \
|
|
||||||
if (dev->ctl.x_field.mode == CTL_MODE_VALUE) { \
|
|
||||||
SET_CID_VALUE(x_cid_auto, x_field##_auto, 0, true); \
|
|
||||||
SET_CID_VALUE(x_cid_manual, x_field, dev->ctl.x_field.value, false); \
|
|
||||||
} else if (dev->ctl.x_field.mode == CTL_MODE_AUTO) { \
|
|
||||||
SET_CID_VALUE(x_cid_auto, x_field##_auto, 1, false); \
|
|
||||||
} else if (dev->ctl.x_field.mode == CTL_MODE_DEFAULT) { \
|
|
||||||
SET_CID_VALUE(x_cid_auto, x_field##_auto, 0, true); /* Reset inactive flag */ \
|
|
||||||
SET_CID_DEFAULT(x_cid_manual, x_field, false); \
|
|
||||||
SET_CID_DEFAULT(x_cid_auto, x_field##_auto, false); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
CONTROL_AUTO_CID (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness);
|
|
||||||
CONTROL_MANUAL_CID ( V4L2_CID_CONTRAST, contrast);
|
|
||||||
CONTROL_MANUAL_CID ( V4L2_CID_SATURATION, saturation);
|
|
||||||
CONTROL_AUTO_CID (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue);
|
|
||||||
CONTROL_MANUAL_CID ( V4L2_CID_GAMMA, gamma);
|
|
||||||
CONTROL_MANUAL_CID ( V4L2_CID_SHARPNESS, sharpness);
|
|
||||||
CONTROL_MANUAL_CID ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
|
|
||||||
CONTROL_AUTO_CID (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
|
|
||||||
CONTROL_AUTO_CID (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
|
|
||||||
CONTROL_MANUAL_CID ( V4L2_CID_COLORFX, color_effect);
|
|
||||||
CONTROL_MANUAL_CID ( V4L2_CID_ROTATE, rotate);
|
|
||||||
CONTROL_MANUAL_CID ( V4L2_CID_VFLIP, flip_vertical);
|
|
||||||
CONTROL_MANUAL_CID ( V4L2_CID_HFLIP, flip_horizontal);
|
|
||||||
|
|
||||||
# undef CONTROL_AUTO_CID
|
|
||||||
# undef CONTROL_MANUAL_CID
|
|
||||||
# undef SET_CID_DEFAULT
|
|
||||||
# undef SET_CID_VALUE
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _device_query_control(
|
|
||||||
us_device_s *dev, struct v4l2_queryctrl *query,
|
|
||||||
const char *name, unsigned cid, bool quiet) {
|
|
||||||
|
|
||||||
// cppcheck-suppress redundantPointerOp
|
|
||||||
US_MEMSET_ZERO(*query);
|
|
||||||
query->id = cid;
|
|
||||||
|
|
||||||
if (_D_XIOCTL(VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
|
|
||||||
if (!quiet) {
|
|
||||||
US_LOG_ERROR("Changing control %s is unsupported", name);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _device_set_control(
|
|
||||||
us_device_s *dev, struct v4l2_queryctrl *query,
|
|
||||||
const char *name, unsigned cid, int value, bool quiet) {
|
|
||||||
|
|
||||||
if (value < query->minimum || value > query->maximum || value % query->step != 0) {
|
|
||||||
if (!quiet) {
|
|
||||||
US_LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
|
|
||||||
value, name, query->minimum, query->maximum, query->default_value, query->step);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct v4l2_control ctl = {0};
|
|
||||||
ctl.id = cid;
|
|
||||||
ctl.value = value;
|
|
||||||
|
|
||||||
if (_D_XIOCTL(VIDIOC_S_CTRL, &ctl) < 0) {
|
|
||||||
if (!quiet) {
|
|
||||||
US_LOG_PERROR("Can't set control %s", name);
|
|
||||||
}
|
|
||||||
} else if (!quiet) {
|
|
||||||
US_LOG_INFO("Applying control %s: %d", name, ctl.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *_format_to_string_nullable(unsigned format) {
|
|
||||||
US_ARRAY_ITERATE(_FORMATS, 0, item, {
|
|
||||||
if (item->format == format) {
|
|
||||||
return item->name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *_format_to_string_supported(unsigned format) {
|
|
||||||
const char *const format_str = _format_to_string_nullable(format);
|
|
||||||
return (format_str == NULL ? "unsupported" : format_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *_standard_to_string(v4l2_std_id standard) {
|
|
||||||
US_ARRAY_ITERATE(_STANDARDS, 0, item, {
|
|
||||||
if (item->standard == standard) {
|
|
||||||
return item->name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return _STANDARDS[0].name;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
|
|
||||||
US_ARRAY_ITERATE(_IO_METHODS, 0, item, {
|
|
||||||
if (item->io_method == io_method) {
|
|
||||||
return item->name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return "unsupported";
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -96,7 +96,7 @@ us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *
|
|||||||
|
|
||||||
us_encoder_type_e type = (_ER(cpu_forced) ? US_ENCODER_TYPE_CPU : enc->type);
|
us_encoder_type_e type = (_ER(cpu_forced) ? US_ENCODER_TYPE_CPU : enc->type);
|
||||||
unsigned quality = dev->jpeg_quality;
|
unsigned quality = dev->jpeg_quality;
|
||||||
unsigned n_workers = us_min_u(enc->n_workers, DR(n_bufs));
|
unsigned n_workers = US_MIN(enc->n_workers, DR(n_bufs));
|
||||||
bool cpu_forced = false;
|
bool cpu_forced = false;
|
||||||
|
|
||||||
if (us_is_jpeg(DR(format)) && type != US_ENCODER_TYPE_HW) {
|
if (us_is_jpeg(DR(format)) && type != US_ENCODER_TYPE_HW) {
|
||||||
@@ -120,7 +120,7 @@ us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *
|
|||||||
for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
|
for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
|
||||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||||
char name[32];
|
char name[32];
|
||||||
snprintf(name, 32, "JPEG-%u", _ER(n_m2ms));
|
US_SNPRINTF(name, 31, "JPEG-%u", _ER(n_m2ms));
|
||||||
if (type == US_ENCODER_TYPE_M2M_VIDEO) {
|
if (type == US_ENCODER_TYPE_M2M_VIDEO) {
|
||||||
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
||||||
} else {
|
} else {
|
||||||
@@ -195,7 +195,7 @@ static void _worker_job_destroy(void *v_job) {
|
|||||||
static bool _worker_run_job(us_worker_s *wr) {
|
static bool _worker_run_job(us_worker_s *wr) {
|
||||||
us_encoder_job_s *job = (us_encoder_job_s *)wr->job;
|
us_encoder_job_s *job = (us_encoder_job_s *)wr->job;
|
||||||
us_encoder_s *enc = job->enc; // Just for _ER()
|
us_encoder_s *enc = job->enc; // Just for _ER()
|
||||||
us_frame_s *src = &job->hw->raw;
|
const us_frame_s *src = &job->hw->raw;
|
||||||
us_frame_s *dest = job->dest;
|
us_frame_s *dest = job->dest;
|
||||||
|
|
||||||
assert(_ER(type) != US_ENCODER_TYPE_UNKNOWN);
|
assert(_ER(type) != US_ENCODER_TYPE_UNKNOWN);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
#include "../libs/threading.h"
|
#include "../libs/threading.h"
|
||||||
#include "../libs/logging.h"
|
#include "../libs/logging.h"
|
||||||
#include "../libs/frame.h"
|
#include "../libs/frame.h"
|
||||||
|
#include "../libs/device.h"
|
||||||
|
|
||||||
#include "device.h"
|
|
||||||
#include "workers.h"
|
#include "workers.h"
|
||||||
#include "m2m.h"
|
#include "m2m.h"
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
||||||
# Copyright (C) 2006 Gabriel A. Devenyi #
|
# Copyright (C) 2006 Gabriel A. Devenyi #
|
||||||
# Copyright (C) 2007 Tom Stöveken #
|
# Copyright (C) 2007 Tom Stöveken #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -37,10 +37,10 @@ typedef struct {
|
|||||||
|
|
||||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
|
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
|
||||||
|
|
||||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
|
||||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||||
|
static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||||
|
|
||||||
static void _jpeg_init_destination(j_compress_ptr jpeg);
|
static void _jpeg_init_destination(j_compress_ptr jpeg);
|
||||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
|
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
|
||||||
@@ -63,27 +63,24 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
|
|||||||
jpeg.image_width = src->width;
|
jpeg.image_width = src->width;
|
||||||
jpeg.image_height = src->height;
|
jpeg.image_height = src->height;
|
||||||
jpeg.input_components = 3;
|
jpeg.input_components = 3;
|
||||||
jpeg.in_color_space = JCS_RGB;
|
jpeg.in_color_space = ((src->format == V4L2_PIX_FMT_YUYV || src->format == V4L2_PIX_FMT_UYVY) ? JCS_YCbCr : JCS_RGB);
|
||||||
|
|
||||||
jpeg_set_defaults(&jpeg);
|
jpeg_set_defaults(&jpeg);
|
||||||
jpeg_set_quality(&jpeg, quality, TRUE);
|
jpeg_set_quality(&jpeg, quality, TRUE);
|
||||||
|
|
||||||
jpeg_start_compress(&jpeg, TRUE);
|
jpeg_start_compress(&jpeg, TRUE);
|
||||||
|
|
||||||
# define WRITE_SCANLINES(x_format, x_func) \
|
|
||||||
case x_format: { x_func(&jpeg, src); break; }
|
|
||||||
|
|
||||||
switch (src->format) {
|
switch (src->format) {
|
||||||
// https://www.fourcc.org/yuv.php
|
// https://www.fourcc.org/yuv.php
|
||||||
WRITE_SCANLINES(V4L2_PIX_FMT_YUYV, _jpeg_write_scanlines_yuyv);
|
case V4L2_PIX_FMT_YUYV:
|
||||||
WRITE_SCANLINES(V4L2_PIX_FMT_UYVY, _jpeg_write_scanlines_uyvy);
|
case V4L2_PIX_FMT_YVYU:
|
||||||
WRITE_SCANLINES(V4L2_PIX_FMT_RGB565, _jpeg_write_scanlines_rgb565);
|
case V4L2_PIX_FMT_UYVY: _jpeg_write_scanlines_yuv(&jpeg, src); break;
|
||||||
WRITE_SCANLINES(V4L2_PIX_FMT_RGB24, _jpeg_write_scanlines_rgb24);
|
case V4L2_PIX_FMT_RGB565: _jpeg_write_scanlines_rgb565(&jpeg, src); break;
|
||||||
default: assert(0 && "Unsupported input format for CPU encoder");
|
case V4L2_PIX_FMT_RGB24: _jpeg_write_scanlines_rgb24(&jpeg, src); break;
|
||||||
|
case V4L2_PIX_FMT_BGR24: _jpeg_write_scanlines_bgr24(&jpeg, src); break;
|
||||||
|
default: assert(0 && "Unsupported input format for CPU encoder"); return;
|
||||||
}
|
}
|
||||||
|
|
||||||
# undef WRITE_SCANLINES
|
|
||||||
|
|
||||||
jpeg_finish_compress(&jpeg);
|
jpeg_finish_compress(&jpeg);
|
||||||
jpeg_destroy_compress(&jpeg);
|
jpeg_destroy_compress(&jpeg);
|
||||||
|
|
||||||
@@ -106,39 +103,43 @@ static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) {
|
|||||||
frame->used = 0;
|
frame->used = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define YUV_R(_y, _, _v) (((_y) + (359 * (_v))) >> 8)
|
static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||||
#define YUV_G(_y, _u, _v) (((_y) - (88 * (_u)) - (183 * (_v))) >> 8)
|
|
||||||
#define YUV_B(_y, _u, _) (((_y) + (454 * (_u))) >> 8)
|
|
||||||
#define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x)))
|
|
||||||
|
|
||||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
|
||||||
uint8_t *line_buf;
|
uint8_t *line_buf;
|
||||||
US_CALLOC(line_buf, frame->width * 3);
|
US_CALLOC(line_buf, frame->width * 3);
|
||||||
|
|
||||||
const unsigned padding = us_frame_get_padding(frame);
|
const unsigned padding = us_frame_get_padding(frame);
|
||||||
const uint8_t *data = frame->data;
|
const uint8_t *data = frame->data;
|
||||||
unsigned z = 0;
|
|
||||||
|
|
||||||
while (jpeg->next_scanline < frame->height) {
|
while (jpeg->next_scanline < frame->height) {
|
||||||
uint8_t *ptr = line_buf;
|
uint8_t *ptr = line_buf;
|
||||||
|
|
||||||
for (unsigned x = 0; x < frame->width; ++x) {
|
for (unsigned x = 0; x < frame->width; ++x) {
|
||||||
const int y = (!z ? data[0] << 8 : data[2] << 8);
|
// See also: https://www.kernel.org/doc/html/v4.8/media/uapi/v4l/pixfmt-uyvy.html
|
||||||
const int u = data[1] - 128;
|
const bool is_odd_pixel = x & 1;
|
||||||
const int v = data[3] - 128;
|
uint8_t y, u, v;
|
||||||
|
if (frame->format == V4L2_PIX_FMT_YUYV) {
|
||||||
const int r = YUV_R(y, u, v);
|
y = data[is_odd_pixel ? 2 : 0];
|
||||||
const int g = YUV_G(y, u, v);
|
u = data[1];
|
||||||
const int b = YUV_B(y, u, v);
|
v = data[3];
|
||||||
|
} else if (frame->format == V4L2_PIX_FMT_YVYU) {
|
||||||
*(ptr++) = NORM_COMPONENT(r);
|
y = data[is_odd_pixel ? 2 : 0];
|
||||||
*(ptr++) = NORM_COMPONENT(g);
|
u = data[3];
|
||||||
*(ptr++) = NORM_COMPONENT(b);
|
v = data[1];
|
||||||
|
} else if (frame->format == V4L2_PIX_FMT_UYVY) {
|
||||||
if (z++) {
|
y = data[is_odd_pixel ? 3 : 1];
|
||||||
z = 0;
|
u = data[0];
|
||||||
data += 4;
|
v = data[2];
|
||||||
|
} else {
|
||||||
|
assert(0 && "Unsupported pixel format");
|
||||||
|
return; // Makes linter happy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ptr[0] = y;
|
||||||
|
ptr[1] = u;
|
||||||
|
ptr[2] = v;
|
||||||
|
ptr += 3;
|
||||||
|
|
||||||
|
data += (is_odd_pixel ? 4 : 0);
|
||||||
}
|
}
|
||||||
data += padding;
|
data += padding;
|
||||||
|
|
||||||
@@ -149,49 +150,6 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const
|
|||||||
free(line_buf);
|
free(line_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
|
||||||
uint8_t *line_buf;
|
|
||||||
US_CALLOC(line_buf, frame->width * 3);
|
|
||||||
|
|
||||||
const unsigned padding = us_frame_get_padding(frame);
|
|
||||||
const uint8_t *data = frame->data;
|
|
||||||
unsigned z = 0;
|
|
||||||
|
|
||||||
while (jpeg->next_scanline < frame->height) {
|
|
||||||
uint8_t *ptr = line_buf;
|
|
||||||
|
|
||||||
for (unsigned x = 0; x < frame->width; ++x) {
|
|
||||||
const int y = (!z ? data[1] << 8 : data[3] << 8);
|
|
||||||
const int u = data[0] - 128;
|
|
||||||
const int v = data[2] - 128;
|
|
||||||
|
|
||||||
const int r = YUV_R(y, u, v);
|
|
||||||
const int g = YUV_G(y, u, v);
|
|
||||||
const int b = YUV_B(y, u, v);
|
|
||||||
|
|
||||||
*(ptr++) = NORM_COMPONENT(r);
|
|
||||||
*(ptr++) = NORM_COMPONENT(g);
|
|
||||||
*(ptr++) = NORM_COMPONENT(b);
|
|
||||||
|
|
||||||
if (z++) {
|
|
||||||
z = 0;
|
|
||||||
data += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data += padding;
|
|
||||||
|
|
||||||
JSAMPROW scanlines[1] = {line_buf};
|
|
||||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(line_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef NORM_COMPONENT
|
|
||||||
#undef YUV_B
|
|
||||||
#undef YUV_G
|
|
||||||
#undef YUV_R
|
|
||||||
|
|
||||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||||
uint8_t *line_buf;
|
uint8_t *line_buf;
|
||||||
US_CALLOC(line_buf, frame->width * 3);
|
US_CALLOC(line_buf, frame->width * 3);
|
||||||
@@ -205,9 +163,10 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, cons
|
|||||||
for (unsigned x = 0; x < frame->width; ++x) {
|
for (unsigned x = 0; x < frame->width; ++x) {
|
||||||
const unsigned int two_byte = (data[1] << 8) + data[0];
|
const unsigned int two_byte = (data[1] << 8) + data[0];
|
||||||
|
|
||||||
*(ptr++) = data[1] & 248; // Red
|
ptr[0] = data[1] & 248; // Red
|
||||||
*(ptr++) = (uint8_t)((two_byte & 2016) >> 3); // Green
|
ptr[1] = (uint8_t)((two_byte & 2016) >> 3); // Green
|
||||||
*(ptr++) = (data[0] & 31) * 8; // Blue
|
ptr[2] = (data[0] & 31) * 8; // Blue
|
||||||
|
ptr += 3;
|
||||||
|
|
||||||
data += 2;
|
data += 2;
|
||||||
}
|
}
|
||||||
@@ -228,10 +187,37 @@ static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const
|
|||||||
JSAMPROW scanlines[1] = {data};
|
JSAMPROW scanlines[1] = {data};
|
||||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||||
|
|
||||||
data += (jpeg->next_scanline * frame->width * 3) + padding;
|
data += (frame->width * 3) + padding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||||
|
uint8_t *line_buf;
|
||||||
|
US_CALLOC(line_buf, frame->width * 3);
|
||||||
|
|
||||||
|
const unsigned padding = us_frame_get_padding(frame);
|
||||||
|
uint8_t *data = frame->data;
|
||||||
|
|
||||||
|
while (jpeg->next_scanline < frame->height) {
|
||||||
|
uint8_t *ptr = line_buf;
|
||||||
|
|
||||||
|
// swap B and R values
|
||||||
|
for (unsigned x = 0; x < frame->width * 3; x += 3) {
|
||||||
|
ptr[0] = data[x + 2];
|
||||||
|
ptr[1] = data[x + 1];
|
||||||
|
ptr[2] = data[x];
|
||||||
|
ptr += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSAMPROW scanlines[1] = {line_buf};
|
||||||
|
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||||
|
|
||||||
|
data += (frame->width * 3) + padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(line_buf);
|
||||||
|
}
|
||||||
|
|
||||||
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
|
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
|
||||||
|
|
||||||
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
||||||
# Copyright (C) 2006 Gabriel A. Devenyi #
|
# Copyright (C) 2006 Gabriel A. Devenyi #
|
||||||
# Copyright (C) 2007 Tom Stöveken #
|
# Copyright (C) 2007 Tom Stöveken #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
||||||
# Copyright (C) 2006 Gabriel A. Devenyi #
|
# Copyright (C) 2006 Gabriel A. Devenyi #
|
||||||
# Copyright (C) 2007 Tom Stöveken #
|
# Copyright (C) 2007 Tom Stöveken #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -34,24 +34,26 @@ us_gpio_s us_g_gpio = {
|
|||||||
.line = NULL, \
|
.line = NULL, \
|
||||||
.state = false \
|
.state = false \
|
||||||
}
|
}
|
||||||
|
|
||||||
.prog_running = MAKE_OUTPUT("prog-running"),
|
.prog_running = MAKE_OUTPUT("prog-running"),
|
||||||
.stream_online = MAKE_OUTPUT("stream-online"),
|
.stream_online = MAKE_OUTPUT("stream-online"),
|
||||||
.has_http_clients = MAKE_OUTPUT("has-http-clients"),
|
.has_http_clients = MAKE_OUTPUT("has-http-clients"),
|
||||||
|
|
||||||
# undef MAKE_OUTPUT
|
# undef MAKE_OUTPUT
|
||||||
|
|
||||||
// mutex uninitialized
|
# ifndef HAVE_GPIOD2
|
||||||
.chip = NULL
|
.chip = NULL,
|
||||||
|
# endif
|
||||||
|
.initialized = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static void _gpio_output_init(us_gpio_output_s *output);
|
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip);
|
||||||
static void _gpio_output_destroy(us_gpio_output_s *output);
|
static void _gpio_output_destroy(us_gpio_output_s *output);
|
||||||
|
|
||||||
|
|
||||||
void us_gpio_init(void) {
|
void us_gpio_init(void) {
|
||||||
|
# ifndef HAVE_GPIOD2
|
||||||
assert(us_g_gpio.chip == NULL);
|
assert(us_g_gpio.chip == NULL);
|
||||||
|
# endif
|
||||||
if (
|
if (
|
||||||
us_g_gpio.prog_running.pin >= 0
|
us_g_gpio.prog_running.pin >= 0
|
||||||
|| us_g_gpio.stream_online.pin >= 0
|
|| us_g_gpio.stream_online.pin >= 0
|
||||||
@@ -59,10 +61,17 @@ void us_gpio_init(void) {
|
|||||||
) {
|
) {
|
||||||
US_MUTEX_INIT(us_g_gpio.mutex);
|
US_MUTEX_INIT(us_g_gpio.mutex);
|
||||||
US_LOG_INFO("GPIO: Using chip device: %s", us_g_gpio.path);
|
US_LOG_INFO("GPIO: Using chip device: %s", us_g_gpio.path);
|
||||||
if ((us_g_gpio.chip = gpiod_chip_open(us_g_gpio.path)) != NULL) {
|
struct gpiod_chip *chip;
|
||||||
_gpio_output_init(&us_g_gpio.prog_running);
|
if ((chip = gpiod_chip_open(us_g_gpio.path)) != NULL) {
|
||||||
_gpio_output_init(&us_g_gpio.stream_online);
|
_gpio_output_init(&us_g_gpio.prog_running, chip);
|
||||||
_gpio_output_init(&us_g_gpio.has_http_clients);
|
_gpio_output_init(&us_g_gpio.stream_online, chip);
|
||||||
|
_gpio_output_init(&us_g_gpio.has_http_clients, chip);
|
||||||
|
# ifdef HAVE_GPIOD2
|
||||||
|
gpiod_chip_close(chip);
|
||||||
|
# else
|
||||||
|
us_g_gpio.chip = chip;
|
||||||
|
# endif
|
||||||
|
us_g_gpio.initialized = true;
|
||||||
} else {
|
} else {
|
||||||
US_LOG_PERROR("GPIO: Can't initialize chip device %s", us_g_gpio.path);
|
US_LOG_PERROR("GPIO: Can't initialize chip device %s", us_g_gpio.path);
|
||||||
}
|
}
|
||||||
@@ -73,23 +82,32 @@ void us_gpio_destroy(void) {
|
|||||||
_gpio_output_destroy(&us_g_gpio.prog_running);
|
_gpio_output_destroy(&us_g_gpio.prog_running);
|
||||||
_gpio_output_destroy(&us_g_gpio.stream_online);
|
_gpio_output_destroy(&us_g_gpio.stream_online);
|
||||||
_gpio_output_destroy(&us_g_gpio.has_http_clients);
|
_gpio_output_destroy(&us_g_gpio.has_http_clients);
|
||||||
if (us_g_gpio.chip != NULL) {
|
if (us_g_gpio.initialized) {
|
||||||
|
# ifndef HAVE_GPIOD2
|
||||||
gpiod_chip_close(us_g_gpio.chip);
|
gpiod_chip_close(us_g_gpio.chip);
|
||||||
us_g_gpio.chip = NULL;
|
us_g_gpio.chip = NULL;
|
||||||
|
# endif
|
||||||
US_MUTEX_DESTROY(us_g_gpio.mutex);
|
US_MUTEX_DESTROY(us_g_gpio.mutex);
|
||||||
|
us_g_gpio.initialized = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
|
int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
|
||||||
|
# ifndef HAVE_GPIOD2
|
||||||
assert(us_g_gpio.chip != NULL);
|
assert(us_g_gpio.chip != NULL);
|
||||||
|
# endif
|
||||||
assert(output->line != NULL);
|
assert(output->line != NULL);
|
||||||
assert(output->state != state); // Must be checked in macro for the performance
|
assert(output->state != state); // Must be checked in macro for the performance
|
||||||
US_MUTEX_LOCK(us_g_gpio.mutex);
|
US_MUTEX_LOCK(us_g_gpio.mutex);
|
||||||
|
|
||||||
if (gpiod_line_set_value(output->line, (int)state) < 0) { \
|
# ifdef HAVE_GPIOD2
|
||||||
US_LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
|
if (gpiod_line_request_set_value(output->line, output->pin, state) < 0) {
|
||||||
|
# else
|
||||||
|
if (gpiod_line_set_value(output->line, (int)state) < 0) {
|
||||||
|
# endif
|
||||||
|
US_LOG_PERROR("GPIO: Can't write value %d to line %s", state, output->consumer); \
|
||||||
_gpio_output_destroy(output);
|
_gpio_output_destroy(output);
|
||||||
retval = -1;
|
retval = -1;
|
||||||
}
|
}
|
||||||
@@ -98,14 +116,42 @@ int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _gpio_output_init(us_gpio_output_s *output) {
|
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip) {
|
||||||
assert(us_g_gpio.chip != NULL);
|
|
||||||
assert(output->line == NULL);
|
assert(output->line == NULL);
|
||||||
|
|
||||||
US_ASPRINTF(output->consumer, "%s::%s", us_g_gpio.consumer_prefix, output->role);
|
US_ASPRINTF(output->consumer, "%s::%s", us_g_gpio.consumer_prefix, output->role);
|
||||||
|
|
||||||
if (output->pin >= 0) {
|
if (output->pin >= 0) {
|
||||||
if ((output->line = gpiod_chip_get_line(us_g_gpio.chip, output->pin)) != NULL) {
|
# ifdef HAVE_GPIOD2
|
||||||
|
struct gpiod_line_settings *line_settings;
|
||||||
|
assert(line_settings = gpiod_line_settings_new());
|
||||||
|
assert(!gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_OUTPUT));
|
||||||
|
assert(!gpiod_line_settings_set_output_value(line_settings, false));
|
||||||
|
|
||||||
|
struct gpiod_line_config *line_config;
|
||||||
|
assert(line_config = gpiod_line_config_new());
|
||||||
|
const unsigned offset = output->pin;
|
||||||
|
assert(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings));
|
||||||
|
|
||||||
|
struct gpiod_request_config *request_config;
|
||||||
|
assert(request_config = gpiod_request_config_new());
|
||||||
|
gpiod_request_config_set_consumer(request_config, output->consumer);
|
||||||
|
|
||||||
|
if ((output->line = gpiod_chip_request_lines(chip, request_config, line_config)) == NULL) {
|
||||||
|
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
gpiod_request_config_free(request_config);
|
||||||
|
gpiod_line_config_free(line_config);
|
||||||
|
gpiod_line_settings_free(line_settings);
|
||||||
|
|
||||||
|
if (output->line == NULL) {
|
||||||
|
_gpio_output_destroy(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
# else
|
||||||
|
|
||||||
|
if ((output->line = gpiod_chip_get_line(chip, output->pin)) != NULL) {
|
||||||
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
|
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
|
||||||
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
||||||
_gpio_output_destroy(output);
|
_gpio_output_destroy(output);
|
||||||
@@ -113,12 +159,17 @@ static void _gpio_output_init(us_gpio_output_s *output) {
|
|||||||
} else {
|
} else {
|
||||||
US_LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
|
US_LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
|
||||||
}
|
}
|
||||||
|
# endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _gpio_output_destroy(us_gpio_output_s *output) {
|
static void _gpio_output_destroy(us_gpio_output_s *output) {
|
||||||
if (output->line != NULL) {
|
if (output->line != NULL) {
|
||||||
|
# ifdef HAVE_GPIOD2
|
||||||
|
gpiod_line_request_release(output->line);
|
||||||
|
# else
|
||||||
gpiod_line_release(output->line);
|
gpiod_line_release(output->line);
|
||||||
|
# endif
|
||||||
output->line = NULL;
|
output->line = NULL;
|
||||||
}
|
}
|
||||||
if (output->consumer != NULL) {
|
if (output->consumer != NULL) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -36,11 +36,15 @@
|
|||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int pin;
|
int pin;
|
||||||
const char *role;
|
const char *role;
|
||||||
char *consumer;
|
char *consumer;
|
||||||
struct gpiod_line *line;
|
# ifdef HAVE_GPIOD2
|
||||||
bool state;
|
struct gpiod_line_request *line;
|
||||||
|
# else
|
||||||
|
struct gpiod_line *line;
|
||||||
|
# endif
|
||||||
|
bool state;
|
||||||
} us_gpio_output_s;
|
} us_gpio_output_s;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -52,7 +56,11 @@ typedef struct {
|
|||||||
us_gpio_output_s has_http_clients;
|
us_gpio_output_s has_http_clients;
|
||||||
|
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
|
|
||||||
|
# ifndef HAVE_GPIOD2
|
||||||
struct gpiod_chip *chip;
|
struct gpiod_chip *chip;
|
||||||
|
# endif
|
||||||
|
bool initialized;
|
||||||
} us_gpio_s;
|
} us_gpio_s;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -31,10 +31,9 @@ char *us_bufferevent_format_reason(short what) {
|
|||||||
char *const perror_str = us_errno_to_string(EVUTIL_SOCKET_ERROR());
|
char *const perror_str = us_errno_to_string(EVUTIL_SOCKET_ERROR());
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
strcat(reason, perror_str);
|
strncat(reason, perror_str, 1023);
|
||||||
free(perror_str);
|
free(perror_str);
|
||||||
strcat(reason, " (");
|
strcat(reason, " (");
|
||||||
|
|
||||||
# define FILL_REASON(x_bev, x_name) { \
|
# define FILL_REASON(x_bev, x_name) { \
|
||||||
if (what & x_bev) { \
|
if (what & x_bev) { \
|
||||||
if (first) { \
|
if (first) { \
|
||||||
@@ -51,7 +50,6 @@ char *us_bufferevent_format_reason(short what) {
|
|||||||
FILL_REASON(BEV_EVENT_ERROR, "error");
|
FILL_REASON(BEV_EVENT_ERROR, "error");
|
||||||
FILL_REASON(BEV_EVENT_TIMEOUT, "timeout");
|
FILL_REASON(BEV_EVENT_TIMEOUT, "timeout");
|
||||||
FILL_REASON(BEV_EVENT_EOF, "eof"); // cppcheck-suppress unreadVariable
|
FILL_REASON(BEV_EVENT_EOF, "eof"); // cppcheck-suppress unreadVariable
|
||||||
|
|
||||||
# undef FILL_REASON
|
# undef FILL_REASON
|
||||||
|
|
||||||
strcat(reason, ")");
|
strcat(reason, ")");
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -51,10 +51,8 @@ static char *_http_get_client_hostport(struct evhttp_request *request);
|
|||||||
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) assert(!evbuffer_add(x_buf, x_data, x_size))
|
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) assert(!evbuffer_add(x_buf, x_data, x_size))
|
||||||
#define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) assert(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0)
|
#define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) assert(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0)
|
||||||
|
|
||||||
#define _RUN(x_next) server->run->x_next
|
#define _VID(x_next) server->run->stream->run->video->x_next
|
||||||
#define _STREAM(x_next) _RUN(stream->x_next)
|
#define _EX(x_next) server->run->exposed->x_next
|
||||||
#define _VID(x_next) _STREAM(run->video->x_next)
|
|
||||||
#define _EX(x_next) _RUN(exposed->x_next)
|
|
||||||
|
|
||||||
|
|
||||||
us_server_s *us_server_init(us_stream_s *stream) {
|
us_server_s *us_server_init(us_stream_s *stream) {
|
||||||
@@ -64,6 +62,7 @@ us_server_s *us_server_init(us_stream_s *stream) {
|
|||||||
|
|
||||||
us_server_runtime_s *run;
|
us_server_runtime_s *run;
|
||||||
US_CALLOC(run, 1);
|
US_CALLOC(run, 1);
|
||||||
|
run->ext_fd = -1;
|
||||||
run->stream = stream;
|
run->stream = stream;
|
||||||
run->exposed = exposed;
|
run->exposed = exposed;
|
||||||
|
|
||||||
@@ -88,78 +87,81 @@ us_server_s *us_server_init(us_stream_s *stream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void us_server_destroy(us_server_s *server) {
|
void us_server_destroy(us_server_s *server) {
|
||||||
if (_RUN(refresher) != NULL) {
|
us_server_runtime_s *const run = server->run;
|
||||||
event_del(_RUN(refresher));
|
|
||||||
event_free(_RUN(refresher));
|
if (run->refresher != NULL) {
|
||||||
|
event_del(run->refresher);
|
||||||
|
event_free(run->refresher);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_RUN(request_watcher) != NULL) {
|
if (run->request_watcher != NULL) {
|
||||||
event_del(_RUN(request_watcher));
|
event_del(run->request_watcher);
|
||||||
event_free(_RUN(request_watcher));
|
event_free(run->request_watcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
evhttp_free(_RUN(http));
|
evhttp_free(run->http);
|
||||||
if (_RUN(ext_fd) >= 0) {
|
US_CLOSE_FD(run->ext_fd, close);
|
||||||
close(_RUN(ext_fd));
|
event_base_free(run->base);
|
||||||
}
|
|
||||||
event_base_free(_RUN(base));
|
|
||||||
|
|
||||||
# if LIBEVENT_VERSION_NUMBER >= 0x02010100
|
# if LIBEVENT_VERSION_NUMBER >= 0x02010100
|
||||||
libevent_global_shutdown();
|
libevent_global_shutdown();
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
US_LIST_ITERATE(_RUN(stream_clients), client, {
|
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
|
||||||
free(client->key);
|
free(client->key);
|
||||||
free(client->hostport);
|
free(client->hostport);
|
||||||
free(client);
|
free(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
US_DELETE(_RUN(auth_token), free);
|
US_DELETE(run->auth_token, free);
|
||||||
|
|
||||||
us_frame_destroy(_EX(frame));
|
us_frame_destroy(run->exposed->frame);
|
||||||
free(_RUN(exposed));
|
free(run->exposed);
|
||||||
free(server->run);
|
free(server->run);
|
||||||
free(server);
|
free(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
int us_server_listen(us_server_s *server) {
|
int us_server_listen(us_server_s *server) {
|
||||||
|
us_server_runtime_s *const run = server->run;
|
||||||
|
us_stream_s *const stream = run->stream;
|
||||||
|
|
||||||
{
|
{
|
||||||
if (server->static_path[0] != '\0') {
|
if (server->static_path[0] != '\0') {
|
||||||
US_LOG_INFO("Enabling HTTP file server: %s", server->static_path);
|
US_LOG_INFO("Enabling HTTP file server: %s", server->static_path);
|
||||||
evhttp_set_gencb(_RUN(http), _http_callback_static, (void *)server);
|
evhttp_set_gencb(run->http, _http_callback_static, (void *)server);
|
||||||
} else {
|
} else {
|
||||||
assert(!evhttp_set_cb(_RUN(http), "/", _http_callback_root, (void *)server));
|
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, (void *)server));
|
||||||
assert(!evhttp_set_cb(_RUN(http), "/favicon.ico", _http_callback_favicon, (void *)server));
|
assert(!evhttp_set_cb(run->http, "/favicon.ico", _http_callback_favicon, (void *)server));
|
||||||
}
|
}
|
||||||
assert(!evhttp_set_cb(_RUN(http), "/state", _http_callback_state, (void *)server));
|
assert(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void *)server));
|
||||||
assert(!evhttp_set_cb(_RUN(http), "/snapshot", _http_callback_snapshot, (void *)server));
|
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)server));
|
||||||
assert(!evhttp_set_cb(_RUN(http), "/stream", _http_callback_stream, (void *)server));
|
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
|
||||||
}
|
}
|
||||||
|
|
||||||
us_frame_copy(_STREAM(blank), _EX(frame));
|
us_frame_copy(stream->blank, _EX(frame));
|
||||||
_EX(notify_last_width) = _EX(frame->width);
|
_EX(notify_last_width) = _EX(frame->width);
|
||||||
_EX(notify_last_height) = _EX(frame->height);
|
_EX(notify_last_height) = _EX(frame->height);
|
||||||
|
|
||||||
if (server->exit_on_no_clients > 0) {
|
if (server->exit_on_no_clients > 0) {
|
||||||
_RUN(last_request_ts) = us_get_now_monotonic();
|
run->last_request_ts = us_get_now_monotonic();
|
||||||
struct timeval interval = {0};
|
struct timeval interval = {0};
|
||||||
interval.tv_usec = 100000;
|
interval.tv_usec = 100000;
|
||||||
assert((_RUN(request_watcher) = event_new(_RUN(base), -1, EV_PERSIST, _http_request_watcher, server)) != NULL);
|
assert((run->request_watcher = event_new(run->base, -1, EV_PERSIST, _http_request_watcher, server)) != NULL);
|
||||||
assert(!event_add(_RUN(request_watcher), &interval));
|
assert(!event_add(run->request_watcher, &interval));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
struct timeval interval = {0};
|
struct timeval interval = {0};
|
||||||
if (_STREAM(dev->desired_fps) > 0) {
|
if (stream->dev->desired_fps > 0) {
|
||||||
interval.tv_usec = 1000000 / (_STREAM(dev->desired_fps) * 2);
|
interval.tv_usec = 1000000 / (stream->dev->desired_fps * 2);
|
||||||
} else {
|
} else {
|
||||||
interval.tv_usec = 16000; // ~60fps
|
interval.tv_usec = 16000; // ~60fps
|
||||||
}
|
}
|
||||||
assert((_RUN(refresher) = event_new(_RUN(base), -1, EV_PERSIST, _http_refresher, server)) != NULL);
|
assert((run->refresher = event_new(run->base, -1, EV_PERSIST, _http_refresher, server)) != NULL);
|
||||||
assert(!event_add(_RUN(refresher), &interval));
|
assert(!event_add(run->refresher, &interval));
|
||||||
}
|
}
|
||||||
|
|
||||||
evhttp_set_timeout(_RUN(http), server->timeout);
|
evhttp_set_timeout(run->http, server->timeout);
|
||||||
|
|
||||||
if (server->user[0] != '\0') {
|
if (server->user[0] != '\0') {
|
||||||
char *encoded_token = NULL;
|
char *encoded_token = NULL;
|
||||||
@@ -169,7 +171,7 @@ int us_server_listen(us_server_s *server) {
|
|||||||
us_base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
|
us_base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
|
||||||
free(raw_token);
|
free(raw_token);
|
||||||
|
|
||||||
US_ASPRINTF(_RUN(auth_token), "Basic %s", encoded_token);
|
US_ASPRINTF(run->auth_token, "Basic %s", encoded_token);
|
||||||
free(encoded_token);
|
free(encoded_token);
|
||||||
|
|
||||||
US_LOG_INFO("Using HTTP basic auth");
|
US_LOG_INFO("Using HTTP basic auth");
|
||||||
@@ -177,8 +179,8 @@ int us_server_listen(us_server_s *server) {
|
|||||||
|
|
||||||
if (server->unix_path[0] != '\0') {
|
if (server->unix_path[0] != '\0') {
|
||||||
US_LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
US_LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
||||||
if ((_RUN(ext_fd) = us_evhttp_bind_unix(
|
if ((run->ext_fd = us_evhttp_bind_unix(
|
||||||
_RUN(http),
|
run->http,
|
||||||
server->unix_path,
|
server->unix_path,
|
||||||
server->unix_rm,
|
server->unix_rm,
|
||||||
server->unix_mode)) < 0
|
server->unix_mode)) < 0
|
||||||
@@ -190,7 +192,7 @@ int us_server_listen(us_server_s *server) {
|
|||||||
# ifdef WITH_SYSTEMD
|
# ifdef WITH_SYSTEMD
|
||||||
} else if (server->systemd) {
|
} else if (server->systemd) {
|
||||||
US_LOG_DEBUG("Binding HTTP to systemd socket ...");
|
US_LOG_DEBUG("Binding HTTP to systemd socket ...");
|
||||||
if ((_RUN(ext_fd) = us_evhttp_bind_systemd(_RUN(http))) < 0) {
|
if ((run->ext_fd = us_evhttp_bind_systemd(run->http)) < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
US_LOG_INFO("Listening systemd socket ...");
|
US_LOG_INFO("Listening systemd socket ...");
|
||||||
@@ -198,7 +200,7 @@ int us_server_listen(us_server_s *server) {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
US_LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
US_LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
||||||
if (evhttp_bind_socket(_RUN(http), server->host, server->port) < 0) {
|
if (evhttp_bind_socket(run->http, server->host, server->port) < 0) {
|
||||||
US_LOG_PERROR("Can't bind HTTP on [%s]:%u", server->host, server->port)
|
US_LOG_PERROR("Can't bind HTTP on [%s]:%u", server->host, server->port)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -210,18 +212,20 @@ int us_server_listen(us_server_s *server) {
|
|||||||
|
|
||||||
void us_server_loop(us_server_s *server) {
|
void us_server_loop(us_server_s *server) {
|
||||||
US_LOG_INFO("Starting HTTP eventloop ...");
|
US_LOG_INFO("Starting HTTP eventloop ...");
|
||||||
event_base_dispatch(_RUN(base));
|
event_base_dispatch(server->run->base);
|
||||||
US_LOG_INFO("HTTP eventloop stopped");
|
US_LOG_INFO("HTTP eventloop stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
void us_server_loop_break(us_server_s *server) {
|
void us_server_loop_break(us_server_s *server) {
|
||||||
event_base_loopbreak(_RUN(base));
|
event_base_loopbreak(server->run->base);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ADD_HEADER(x_key, x_value) assert(!evhttp_add_header(evhttp_request_get_output_headers(request), x_key, x_value))
|
#define ADD_HEADER(x_key, x_value) assert(!evhttp_add_header(evhttp_request_get_output_headers(request), x_key, x_value))
|
||||||
|
|
||||||
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server) {
|
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server) {
|
||||||
_RUN(last_request_ts) = us_get_now_monotonic();
|
us_server_runtime_s *const run = server->run;
|
||||||
|
|
||||||
|
run->last_request_ts = us_get_now_monotonic();
|
||||||
|
|
||||||
if (server->allow_origin[0] != '\0') {
|
if (server->allow_origin[0] != '\0') {
|
||||||
const char *const cors_headers = _http_get_header(request, "Access-Control-Request-Headers");
|
const char *const cors_headers = _http_get_header(request, "Access-Control-Request-Headers");
|
||||||
@@ -242,10 +246,10 @@ static int _http_preprocess_request(struct evhttp_request *request, us_server_s
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_RUN(auth_token) != NULL) {
|
if (run->auth_token != NULL) {
|
||||||
const char *const token = _http_get_header(request, "Authorization");
|
const char *const token = _http_get_header(request, "Authorization");
|
||||||
|
|
||||||
if (token == NULL || strcmp(token, _RUN(auth_token)) != 0) {
|
if (token == NULL || strcmp(token, run->auth_token) != 0) {
|
||||||
ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\"");
|
ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\"");
|
||||||
evhttp_send_reply(request, 401, "Unauthorized", NULL);
|
evhttp_send_reply(request, 401, "Unauthorized", NULL);
|
||||||
return -1;
|
return -1;
|
||||||
@@ -269,24 +273,22 @@ static int _http_preprocess_request(struct evhttp_request *request, us_server_s
|
|||||||
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server) {
|
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server) {
|
||||||
// MJPG-Streamer compatibility layer
|
// MJPG-Streamer compatibility layer
|
||||||
|
|
||||||
struct evkeyvalq params;
|
int retval = -1;
|
||||||
int error = 0;
|
|
||||||
|
|
||||||
|
struct evkeyvalq params;
|
||||||
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||||
const char *const action = evhttp_find_header(¶ms, "action");
|
const char *const action = evhttp_find_header(¶ms, "action");
|
||||||
|
|
||||||
if (action && !strcmp(action, "snapshot")) {
|
if (action && !strcmp(action, "snapshot")) {
|
||||||
_http_callback_snapshot(request, v_server);
|
_http_callback_snapshot(request, v_server);
|
||||||
goto ok;
|
retval = 0;
|
||||||
} else if (action && !strcmp(action, "stream")) {
|
} else if (action && !strcmp(action, "stream")) {
|
||||||
_http_callback_stream(request, v_server);
|
_http_callback_stream(request, v_server);
|
||||||
goto ok;
|
retval = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = -1;
|
evhttp_clear_headers(¶ms);
|
||||||
ok:
|
return retval;
|
||||||
evhttp_clear_headers(¶ms);
|
|
||||||
return error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define COMPAT_REQUEST { \
|
#define COMPAT_REQUEST { \
|
||||||
@@ -338,14 +340,12 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
|||||||
|
|
||||||
{
|
{
|
||||||
const char *uri_path;
|
const char *uri_path;
|
||||||
|
|
||||||
if ((uri = evhttp_uri_parse(evhttp_request_get_uri(request))) == NULL) {
|
if ((uri = evhttp_uri_parse(evhttp_request_get_uri(request))) == NULL) {
|
||||||
goto bad_request;
|
goto bad_request;
|
||||||
}
|
}
|
||||||
if ((uri_path = (char *)evhttp_uri_get_path(uri)) == NULL) {
|
if ((uri_path = (char *)evhttp_uri_get_path(uri)) == NULL) {
|
||||||
uri_path = "/";
|
uri_path = "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((decoded_path = evhttp_uridecode(uri_path, 0, NULL)) == NULL) {
|
if ((decoded_path = evhttp_uridecode(uri_path, 0, NULL)) == NULL) {
|
||||||
goto bad_request;
|
goto bad_request;
|
||||||
}
|
}
|
||||||
@@ -383,34 +383,34 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
bad_request:
|
bad_request:
|
||||||
evhttp_send_error(request, HTTP_BADREQUEST, NULL);
|
evhttp_send_error(request, HTTP_BADREQUEST, NULL);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
not_found:
|
not_found:
|
||||||
evhttp_send_error(request, HTTP_NOTFOUND, NULL);
|
evhttp_send_error(request, HTTP_NOTFOUND, NULL);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
if (fd >= 0) {
|
US_CLOSE_FD(fd, close); // cppcheck-suppress unreadVariable
|
||||||
close(fd);
|
US_DELETE(static_path, free);
|
||||||
}
|
US_DELETE(buf, evbuffer_free);
|
||||||
US_DELETE(static_path, free);
|
US_DELETE(decoded_path, free);
|
||||||
US_DELETE(buf, evbuffer_free);
|
US_DELETE(uri, evhttp_uri_free);
|
||||||
US_DELETE(decoded_path, free);
|
|
||||||
US_DELETE(uri, evhttp_uri_free);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef COMPAT_REQUEST
|
#undef COMPAT_REQUEST
|
||||||
|
|
||||||
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
||||||
us_server_s *const server = (us_server_s *)v_server;
|
us_server_s *const server = (us_server_s *)v_server;
|
||||||
|
us_server_runtime_s *const run = server->run;
|
||||||
|
us_stream_s *const stream = run->stream;
|
||||||
|
|
||||||
PREPROCESS_REQUEST;
|
PREPROCESS_REQUEST;
|
||||||
|
|
||||||
us_encoder_type_e enc_type;
|
us_encoder_type_e enc_type;
|
||||||
unsigned enc_quality;
|
unsigned enc_quality;
|
||||||
us_encoder_get_runtime_params(_STREAM(enc), &enc_type, &enc_quality);
|
us_encoder_get_runtime_params(stream->enc, &enc_type, &enc_quality);
|
||||||
|
|
||||||
struct evbuffer *buf;
|
struct evbuffer *buf;
|
||||||
_A_EVBUFFER_NEW(buf);
|
_A_EVBUFFER_NEW(buf);
|
||||||
@@ -424,28 +424,28 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
|||||||
enc_quality
|
enc_quality
|
||||||
);
|
);
|
||||||
|
|
||||||
if (_STREAM(run->h264) != NULL) {
|
if (stream->run->h264 != NULL) {
|
||||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||||
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s},",
|
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s},",
|
||||||
_STREAM(h264_bitrate),
|
stream->h264_bitrate,
|
||||||
_STREAM(h264_gop),
|
stream->h264_gop,
|
||||||
us_bool_to_string(atomic_load(&_STREAM(run->h264->online)))
|
us_bool_to_string(atomic_load(&stream->run->h264->online))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_STREAM(sink) != NULL || _STREAM(h264_sink) != NULL) {
|
if (stream->sink != NULL || stream->h264_sink != NULL) {
|
||||||
_A_EVBUFFER_ADD_PRINTF(buf, " \"sinks\": {");
|
_A_EVBUFFER_ADD_PRINTF(buf, " \"sinks\": {");
|
||||||
if (_STREAM(sink) != NULL) {
|
if (stream->sink != NULL) {
|
||||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||||
"\"jpeg\": {\"has_clients\": %s}",
|
"\"jpeg\": {\"has_clients\": %s}",
|
||||||
us_bool_to_string(atomic_load(&_STREAM(sink->has_clients)))
|
us_bool_to_string(atomic_load(&stream->sink->has_clients))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (_STREAM(h264_sink) != NULL) {
|
if (stream->h264_sink != NULL) {
|
||||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||||
"%s\"h264\": {\"has_clients\": %s}",
|
"%s\"h264\": {\"has_clients\": %s}",
|
||||||
(_STREAM(sink) ? ", " : ""),
|
(stream->sink ? ", " : ""),
|
||||||
us_bool_to_string(atomic_load(&_STREAM(h264_sink->has_clients)))
|
us_bool_to_string(atomic_load(&stream->h264_sink->has_clients))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_A_EVBUFFER_ADD_PRINTF(buf, "},");
|
_A_EVBUFFER_ADD_PRINTF(buf, "},");
|
||||||
@@ -458,13 +458,13 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
|||||||
(server->fake_width ? server->fake_width : _EX(frame->width)),
|
(server->fake_width ? server->fake_width : _EX(frame->width)),
|
||||||
(server->fake_height ? server->fake_height : _EX(frame->height)),
|
(server->fake_height ? server->fake_height : _EX(frame->height)),
|
||||||
us_bool_to_string(_EX(frame->online)),
|
us_bool_to_string(_EX(frame->online)),
|
||||||
_STREAM(dev->desired_fps),
|
stream->dev->desired_fps,
|
||||||
_EX(captured_fps),
|
_EX(captured_fps),
|
||||||
_EX(queued_fps),
|
_EX(queued_fps),
|
||||||
_RUN(stream_clients_count)
|
run->stream_clients_count
|
||||||
);
|
);
|
||||||
|
|
||||||
US_LIST_ITERATE(_RUN(stream_clients), client, {
|
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
|
||||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||||
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
|
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
|
||||||
" \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s",
|
" \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s",
|
||||||
@@ -502,12 +502,12 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
|||||||
char header_buf[256];
|
char header_buf[256];
|
||||||
|
|
||||||
# define ADD_TIME_HEADER(x_key, x_value) { \
|
# define ADD_TIME_HEADER(x_key, x_value) { \
|
||||||
snprintf(header_buf, 255, "%.06Lf", x_value); \
|
US_SNPRINTF(header_buf, 255, "%.06Lf", x_value); \
|
||||||
ADD_HEADER(x_key, header_buf); \
|
ADD_HEADER(x_key, header_buf); \
|
||||||
}
|
}
|
||||||
|
|
||||||
# define ADD_UNSIGNED_HEADER(x_key, x_value) { \
|
# define ADD_UNSIGNED_HEADER(x_key, x_value) { \
|
||||||
snprintf(header_buf, 255, "%u", x_value); \
|
US_SNPRINTF(header_buf, 255, "%u", x_value); \
|
||||||
ADD_HEADER(x_key, header_buf); \
|
ADD_HEADER(x_key, header_buf); \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,6 +544,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L1458
|
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L1458
|
||||||
|
|
||||||
us_server_s *const server = (us_server_s *)v_server;
|
us_server_s *const server = (us_server_s *)v_server;
|
||||||
|
us_server_runtime_s *const run = server->run;
|
||||||
|
|
||||||
PREPROCESS_REQUEST;
|
PREPROCESS_REQUEST;
|
||||||
|
|
||||||
@@ -570,9 +571,9 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
client->hostport = _http_get_client_hostport(request);
|
client->hostport = _http_get_client_hostport(request);
|
||||||
client->id = us_get_now_id();
|
client->id = us_get_now_id();
|
||||||
|
|
||||||
US_LIST_APPEND_C(_RUN(stream_clients), client, _RUN(stream_clients_count));
|
US_LIST_APPEND_C(run->stream_clients, client, run->stream_clients_count);
|
||||||
|
|
||||||
if (_RUN(stream_clients_count) == 1) {
|
if (run->stream_clients_count == 1) {
|
||||||
atomic_store(&_VID(has_clients), true);
|
atomic_store(&_VID(has_clients), true);
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
us_gpio_set_has_http_clients(true);
|
us_gpio_set_has_http_clients(true);
|
||||||
@@ -580,10 +581,10 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
}
|
}
|
||||||
|
|
||||||
US_LOG_INFO("HTTP: NEW client (now=%u): %s, id=%" PRIx64,
|
US_LOG_INFO("HTTP: NEW client (now=%u): %s, id=%" PRIx64,
|
||||||
_RUN(stream_clients_count), client->hostport, client->id);
|
run->stream_clients_count, client->hostport, client->id);
|
||||||
|
|
||||||
struct bufferevent *const buf_event = evhttp_connection_get_bufferevent(conn);
|
struct bufferevent *const buf_event = evhttp_connection_get_bufferevent(conn);
|
||||||
if (server->tcp_nodelay && !_RUN(ext_fd)) {
|
if (server->tcp_nodelay && run->ext_fd >= 0) {
|
||||||
US_LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
US_LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
||||||
const evutil_socket_t fd = bufferevent_getfd(buf_event);
|
const evutil_socket_t fd = bufferevent_getfd(buf_event);
|
||||||
assert(fd >= 0);
|
assert(fd >= 0);
|
||||||
@@ -602,8 +603,6 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
#undef PREPROCESS_REQUEST
|
#undef PREPROCESS_REQUEST
|
||||||
|
|
||||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
||||||
# define BOUNDARY "boundarydonotcross"
|
|
||||||
|
|
||||||
us_stream_client_s *const client = (us_stream_client_s *)v_client;
|
us_stream_client_s *const client = (us_stream_client_s *)v_client;
|
||||||
us_server_s *const server = client->server;
|
us_server_s *const server = client->server;
|
||||||
|
|
||||||
@@ -639,6 +638,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
// Кроме того, advance_headers форсит отключение заголовков X-UStreamer-*
|
// Кроме того, advance_headers форсит отключение заголовков X-UStreamer-*
|
||||||
// по тем же причинам, по которым у нас нет Content-Length.
|
// по тем же причинам, по которым у нас нет Content-Length.
|
||||||
|
|
||||||
|
# define BOUNDARY "boundarydonotcross"
|
||||||
|
|
||||||
# define ADD_ADVANCE_HEADERS \
|
# define ADD_ADVANCE_HEADERS \
|
||||||
_A_EVBUFFER_ADD_PRINTF(buf, \
|
_A_EVBUFFER_ADD_PRINTF(buf, \
|
||||||
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, us_get_now_real())
|
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, us_get_now_real())
|
||||||
@@ -667,10 +668,12 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
"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
|
||||||
"Set-Cookie: stream_client=%s/%" PRIx64 "; path=/; max-age=30" RN
|
"Set-Cookie: stream_client%s%s=%s/%" PRIx64 "; path=/; max-age=30" RN
|
||||||
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
||||||
RN
|
RN
|
||||||
"--" BOUNDARY RN,
|
"--" BOUNDARY RN,
|
||||||
|
(server->instance_id[0] == '\0' ? "" : "_"),
|
||||||
|
server->instance_id,
|
||||||
(client->key != NULL ? client->key : "0"),
|
(client->key != NULL ? client->key : "0"),
|
||||||
client->id
|
client->id
|
||||||
);
|
);
|
||||||
@@ -745,13 +748,17 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
# undef BOUNDARY
|
# undef BOUNDARY
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
|
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_client) {
|
||||||
|
(void)buf_event;
|
||||||
|
(void)what;
|
||||||
|
|
||||||
us_stream_client_s *const client = (us_stream_client_s *)v_client;
|
us_stream_client_s *const client = (us_stream_client_s *)v_client;
|
||||||
us_server_s *const server = client->server;
|
us_server_s *const server = client->server;
|
||||||
|
us_server_runtime_s *const run = server->run;
|
||||||
|
|
||||||
US_LIST_REMOVE_C(_RUN(stream_clients), client, _RUN(stream_clients_count));
|
US_LIST_REMOVE_C(run->stream_clients, client, run->stream_clients_count);
|
||||||
|
|
||||||
if (_RUN(stream_clients_count) == 0) {
|
if (run->stream_clients_count == 0) {
|
||||||
atomic_store(&_VID(has_clients), false);
|
atomic_store(&_VID(has_clients), false);
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
us_gpio_set_has_http_clients(false);
|
us_gpio_set_has_http_clients(false);
|
||||||
@@ -760,10 +767,10 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
|||||||
|
|
||||||
char *const reason = us_bufferevent_format_reason(what);
|
char *const reason = us_bufferevent_format_reason(what);
|
||||||
US_LOG_INFO("HTTP: DEL client (now=%u): %s, id=%" PRIx64 ", %s",
|
US_LOG_INFO("HTTP: DEL client (now=%u): %s, id=%" PRIx64 ", %s",
|
||||||
_RUN(stream_clients_count), client->hostport, client->id, reason);
|
run->stream_clients_count, client->hostport, client->id, reason);
|
||||||
free(reason);
|
free(reason);
|
||||||
|
|
||||||
struct evhttp_connection *const conn = evhttp_request_get_connection(client->request);
|
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
||||||
US_DELETE(conn, evhttp_connection_free);
|
US_DELETE(conn, evhttp_connection_free);
|
||||||
|
|
||||||
free(client->key);
|
free(client->key);
|
||||||
@@ -772,10 +779,12 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bool frame_updated) {
|
static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bool frame_updated) {
|
||||||
|
us_server_runtime_s *const run = server->run;
|
||||||
|
|
||||||
bool has_clients = false;
|
bool has_clients = false;
|
||||||
bool queued = false;
|
bool queued = false;
|
||||||
|
|
||||||
US_LIST_ITERATE(_RUN(stream_clients), client, {
|
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
|
||||||
struct evhttp_connection *const conn = evhttp_request_get_connection(client->request);
|
struct evhttp_connection *const conn = evhttp_request_get_connection(client->request);
|
||||||
if (conn != NULL) {
|
if (conn != NULL) {
|
||||||
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
||||||
@@ -823,21 +832,28 @@ static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_request_watcher(UNUSED int fd, UNUSED short what, void *v_server) {
|
static void _http_request_watcher(int fd, short what, void *v_server) {
|
||||||
us_server_s *server = (us_server_s *)v_server;
|
(void)fd;
|
||||||
|
(void)what;
|
||||||
|
|
||||||
|
us_server_s *const server = (us_server_s *)v_server;
|
||||||
|
us_server_runtime_s *const run = server->run;
|
||||||
const long double now = us_get_now_monotonic();
|
const long double now = us_get_now_monotonic();
|
||||||
|
|
||||||
if (us_stream_has_clients(_RUN(stream))) {
|
if (us_stream_has_clients(run->stream)) {
|
||||||
_RUN(last_request_ts) = now;
|
run->last_request_ts = now;
|
||||||
} else if (_RUN(last_request_ts) + server->exit_on_no_clients < now) {
|
} else if (run->last_request_ts + server->exit_on_no_clients < now) {
|
||||||
US_LOG_INFO("HTTP: No requests or HTTP/sink clients found in last %u seconds, exiting ...",
|
US_LOG_INFO("HTTP: No requests or HTTP/sink clients found in last %u seconds, exiting ...",
|
||||||
server->exit_on_no_clients);
|
server->exit_on_no_clients);
|
||||||
us_process_suicide();
|
us_process_suicide();
|
||||||
_RUN(last_request_ts) = now;
|
run->last_request_ts = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_refresher(UNUSED int fd, UNUSED short what, void *v_server) {
|
static void _http_refresher(int fd, short what, void *v_server) {
|
||||||
|
(void)fd;
|
||||||
|
(void)what;
|
||||||
|
|
||||||
us_server_s *server = (us_server_s *)v_server;
|
us_server_s *server = (us_server_s *)v_server;
|
||||||
bool stream_updated = false;
|
bool stream_updated = false;
|
||||||
bool frame_updated = false;
|
bool frame_updated = false;
|
||||||
@@ -912,10 +928,11 @@ static bool _expose_new_frame(us_server_s *server) {
|
|||||||
_EX(frame->online), _EX(expose_end_ts) - _EX(expose_begin_ts));
|
_EX(frame->online), _EX(expose_end_ts) - _EX(expose_begin_ts));
|
||||||
|
|
||||||
updated = true;
|
updated = true;
|
||||||
not_updated:
|
|
||||||
atomic_store(&_VID(updated), false);
|
not_updated:
|
||||||
US_MUTEX_UNLOCK(_VID(mutex));
|
atomic_store(&_VID(updated), false);
|
||||||
return updated;
|
US_MUTEX_UNLOCK(_VID(mutex));
|
||||||
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *_http_get_header(struct evhttp_request *request, const char *key) {
|
static const char *_http_get_header(struct evhttp_request *request, const char *key) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -125,7 +125,7 @@ typedef struct {
|
|||||||
|
|
||||||
struct event *refresher;
|
struct event *refresher;
|
||||||
us_stream_s *stream;
|
us_stream_s *stream;
|
||||||
us_exposed_s *exposed;
|
us_exposed_s *exposed;
|
||||||
|
|
||||||
us_stream_client_s *stream_clients;
|
us_stream_client_s *stream_clients;
|
||||||
unsigned stream_clients_count;
|
unsigned stream_clients_count;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -33,7 +33,7 @@ char *us_find_static_file_path(const char *root_path, const char *request_path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||||
sprintf(path, "%s/%s", root_path, simplified_path);
|
assert(sprintf(path, "%s/%s", root_path, simplified_path) > 0);
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
# define LOAD_STAT { \
|
# define LOAD_STAT { \
|
||||||
@@ -42,14 +42,12 @@ char *us_find_static_file_path(const char *root_path, const char *request_path)
|
|||||||
goto error; \
|
goto error; \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
LOAD_STAT;
|
LOAD_STAT;
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
US_LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path);
|
US_LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path);
|
||||||
strcat(path, "/index.html");
|
strcat(path, "/index.html");
|
||||||
LOAD_STAT;
|
LOAD_STAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
# undef LOAD_STAT
|
# undef LOAD_STAT
|
||||||
|
|
||||||
if (!S_ISREG(st.st_mode)) {
|
if (!S_ISREG(st.st_mode)) {
|
||||||
@@ -64,12 +62,10 @@ char *us_find_static_file_path(const char *root_path, const char *request_path)
|
|||||||
|
|
||||||
goto ok;
|
goto ok;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
US_DELETE(path, free);
|
US_DELETE(path, free);
|
||||||
path = NULL;
|
|
||||||
|
|
||||||
ok:
|
|
||||||
free(simplified_path);
|
|
||||||
|
|
||||||
|
ok:
|
||||||
|
free(simplified_path);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# #
|
# #
|
||||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
# #
|
# #
|
||||||
# Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com> #
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
# #
|
# #
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
# 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 #
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user