mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
967574a78a | ||
|
|
bb4f6f3993 | ||
|
|
fdc955a713 | ||
|
|
322c67f7a5 | ||
|
|
7aa7f43c7e | ||
|
|
8ef5505a48 | ||
|
|
481f2c9479 | ||
|
|
924ebf7c77 | ||
|
|
d67af7a9dc | ||
|
|
e6be119ab5 | ||
|
|
ead06e741a | ||
|
|
fbabba29ed | ||
|
|
ff9b0f5396 | ||
|
|
52a31f619b | ||
|
|
743e8ac828 | ||
|
|
cecec39281 | ||
|
|
280254e231 | ||
|
|
97a1aa04cb | ||
|
|
e14644572b | ||
|
|
55e0a096e7 | ||
|
|
e68e6b6fae | ||
|
|
aa03e1610f | ||
|
|
2b969dd20d | ||
|
|
c51984c9d9 | ||
|
|
459060532a | ||
|
|
016ee1c554 | ||
|
|
26e0c9d54c | ||
|
|
e6584da7c8 | ||
|
|
630ad66cc8 | ||
|
|
222b9a0309 | ||
|
|
235a1765d2 | ||
|
|
bff8a5d854 | ||
|
|
fd65ed006a | ||
|
|
52478df3cf | ||
|
|
7732841cdf | ||
|
|
10aa33d899 | ||
|
|
bd6fa7b0dc | ||
|
|
536f45eff5 | ||
|
|
bb1dcc005c | ||
|
|
ea313d3517 | ||
|
|
78a12f7ed2 | ||
|
|
281f4eb3a0 | ||
|
|
dabaab4987 | ||
|
|
61ab2a81a5 | ||
|
|
d9b511c69f | ||
|
|
4d9e72e313 | ||
|
|
616a3eb6a6 | ||
|
|
3a8f035014 | ||
|
|
be2e0f11da | ||
|
|
fec76dc9eb | ||
|
|
48826208fd | ||
|
|
dd4fda6f5d | ||
|
|
1dafb54621 | ||
|
|
19f9567098 | ||
|
|
b3d1f06e5d | ||
|
|
f17069153d | ||
|
|
8a7d7b9c54 | ||
|
|
df7649d56f | ||
|
|
58cc227cba | ||
|
|
76be4a1d10 | ||
|
|
496c9300fc | ||
|
|
5b7780cf7c | ||
|
|
421f4a1f2e | ||
|
|
55a5d4bbdd | ||
|
|
666ae0c4f1 | ||
|
|
ca3afc074c | ||
|
|
030077fb47 | ||
|
|
61ddff8b6e | ||
|
|
27fbfe149e | ||
|
|
349f655cd9 | ||
|
|
f5c0a15967 | ||
|
|
d822ae0890 | ||
|
|
2d82adb2ba | ||
|
|
56b21274d1 | ||
|
|
76329ba5a6 | ||
|
|
4d5c5fffb4 | ||
|
|
29234c330b |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 4.6
|
||||
current_version = 5.1
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
36
README.md
36
README.md
@@ -4,7 +4,7 @@
|
||||
|
||||
[[Русская версия]](README.ru.md)
|
||||
|
||||
µStreamer is a lightweight and very quick server to stream [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
|
||||
µStreamer is a 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 [Pi-KVM](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 very similar to [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) with ```input_uvc.so``` and ```output_http.so``` plugins, however, there are some major differences. The key ones are:
|
||||
@@ -12,11 +12,12 @@
|
||||
| **Feature** | **µStreamer** | **mjpg-streamer** |
|
||||
|----------|---------------|-------------------|
|
||||
| Multithreaded JPEG encoding | ✔ | ✘ |
|
||||
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ✔ | ✘ |
|
||||
| Hardware image encoding<br>on Raspberry Pi | ✔ | ✘ |
|
||||
| Behavior when the device<br>is disconnected while streaming | ✔ Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ✘ Stops the streaming <sup>1</sup> |
|
||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ✔ | ☹ Partially yes <sup>1</sup> |
|
||||
| Option to skip frames when streaming<br>static images by HTTP to save the traffic | ✔ <sup>2</sup> | ✘ |
|
||||
| Streaming via UNIX domain socket | ✔ | ✘ |
|
||||
| Systemd socket activation | ✔ | ✘ |
|
||||
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters | ✔ | ✘ |
|
||||
| Option to serve files<br>with a built-in HTTP server | ✔ | ☹ Regular files only |
|
||||
| Signaling about the stream state<br>on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ |
|
||||
@@ -34,13 +35,13 @@ If you're going to live-stream from your backyard webcam and need to control it,
|
||||
|
||||
-----
|
||||
# Building
|
||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```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`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `libgpiod` for `WITH_GPIO=1`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1`.
|
||||
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
||||
|
||||
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [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```.
|
||||
|
||||
```
|
||||
$ git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||
@@ -49,7 +50,8 @@ $ make
|
||||
$ ./ustreamer --help
|
||||
```
|
||||
|
||||
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
|
||||
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer.
|
||||
|
||||
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
||||
|
||||
-----
|
||||
@@ -65,8 +67,8 @@ The recommended way of running µStreamer with [Auvidea B101](https://www.raspbe
|
||||
```bash
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Device input format
|
||||
--encoder=omx \ # Hardware encoding with OpenMAX
|
||||
--workers=3 \ # Maximum workers for OpenMAX
|
||||
--encoder=m2m-image \ # Hardware encoding on V4L2 M2M driver
|
||||
--workers=3 \ # Workers number
|
||||
--persistent \ # Don't re-initialize device on timeout (for example when HDMI cable was disconnected)
|
||||
--dv-timings \ # Use DV-timings
|
||||
--drop-same-frames=30 # Save the traffic
|
||||
@@ -92,6 +94,21 @@ $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
||||
$ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||
```
|
||||
|
||||
-----
|
||||
# Integrations
|
||||
|
||||
## 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:
|
||||
|
||||
```nginx
|
||||
location /stream {
|
||||
postpone_output 0;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers X-Accel-Buffering;
|
||||
proxy_pass http://ustreamer;
|
||||
}
|
||||
```
|
||||
|
||||
-----
|
||||
# Tips & tricks for v4l2
|
||||
v4l2 utilities provide the tools to manage USB webcam setting and information. Scripts can be use to make adjustments and run manually or with cron. Running in cron for example to change the exposure settings at certain times of day. The package is available in all Linux distributions and is usually called `v4l-utils`.
|
||||
@@ -107,11 +124,10 @@ v4l2 utilities provide the tools to manage USB webcam setting and information. S
|
||||
-----
|
||||
# See also
|
||||
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
|
||||
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Use [Ansible](https://docs.ansible.com/ansible/latest/index.html) to compile uStreamer and install it as a systemd service automatically.
|
||||
|
||||
-----
|
||||
# License
|
||||
Copyright (C) 2018-2021 by Maxim Devaev mdevaev@gmail.com
|
||||
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
|
||||
|
||||
34
README.ru.md
34
README.ru.md
@@ -5,18 +5,19 @@
|
||||
[[English version]](README.md)
|
||||
|
||||
|
||||
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](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 - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](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 | ✔ | ✘ |
|
||||
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ✔ | ✘ |
|
||||
| Аппаратное кодирование на 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) | ✔ | ✘ |
|
||||
@@ -37,10 +38,10 @@
|
||||
Для сборки вам понадобятся ```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`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `libgpiod` для `WITH_GPIO=1`.
|
||||
* 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`.
|
||||
|
||||
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.
|
||||
Для включения сборки с поддержкой 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
|
||||
@@ -49,7 +50,8 @@ $ make
|
||||
$ ./ustreamer --help
|
||||
```
|
||||
|
||||
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
|
||||
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer.
|
||||
|
||||
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
|
||||
|
||||
-----
|
||||
@@ -65,8 +67,8 @@ $ ./ustreamer --help
|
||||
```bash
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Настройка входного формата устройства
|
||||
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
|
||||
--workers=3 \ # Максимум воркеров для OpenMAX
|
||||
--encoder=m2m-image \ # Аппаратное кодирование с помощью драйвера V4L2 M2M
|
||||
--workers=3 \ # Максимум воркеров
|
||||
--persistent \ # Не переинициализировать устройство при таймауте (например, когда был отключен HDMI-кабель)
|
||||
--dv-timings \ # Включение DV-таймингов
|
||||
--drop-same-frames=30 # Экономим трафик
|
||||
@@ -92,6 +94,21 @@ $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944
|
||||
$ 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`.
|
||||
@@ -107,11 +124,10 @@ V4L2 предоставляет ряд официальных утилит дл
|
||||
-----
|
||||
# Смотрите также
|
||||
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
|
||||
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Использование [Ansible](https://docs.ansible.com/ansible/latest/index.html) для сборки и установки стримера как systemd-сервиса.
|
||||
|
||||
-----
|
||||
# Лицензия
|
||||
Copyright (C) 2018-2021 by Maxim Devaev mdevaev@gmail.com
|
||||
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
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# This source file is partially based on this code: #
|
||||
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# This source file is partially based on this code: #
|
||||
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#define CHAR_BIT 8
|
||||
#define WITH_OMX
|
||||
#define WITH_GPIO
|
||||
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
|
||||
|
||||
@@ -3,7 +3,7 @@ envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
|
||||
skipsdist = true
|
||||
|
||||
[testenv]
|
||||
basepython = python3.9
|
||||
basepython = python3.10
|
||||
changedir = /src
|
||||
|
||||
[testenv:cppcheck]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer-dump.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER-DUMP 1 "version 4.6" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 5.1" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
.\" Manpage for ustreamer.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER 1 "version 4.6" "November 2020"
|
||||
.TH USTREAMER 1 "version 5.1" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPG video from any V4L2 device to the network
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B ustreamer
|
||||
.RI [OPTIONS]
|
||||
|
||||
.SH DESCRIPTION
|
||||
µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the Pi-KVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible.
|
||||
µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPEG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the Pi-KVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible.
|
||||
|
||||
.SH USAGE
|
||||
Without arguments, \fBustreamer\fR will try to open \fB/dev/video0\fR with 640x480 resolution and start streaming on \fBhttp://127\.0\.0\.1:8080\fR\. You can override this behavior using parameters \fB\-\-device\fR, \fB\-\-host\fR and \fB\-\-port\fR\. For example, to stream to the world, run: \fBustreamer --device=/dev/video1 --host=0.0.0.0 --port=80\fR
|
||||
@@ -23,9 +23,9 @@ For example, the recommended way of running µStreamer with Auvidea B101 on a Ra
|
||||
.RS
|
||||
\fB\-\-format=uyvy \e\fR # Device input format
|
||||
.nf
|
||||
\fB\-\-encoder=omx \e\fR # Hardware encoding with OpenMAX
|
||||
\fB\-\-encoder=m2m-image \e\fR # Hardware encoding with V4L2 M2M intraface
|
||||
.nf
|
||||
\fB\-\-workers=3 \e\fR # Maximum workers for OpenMAX
|
||||
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
|
||||
.nf
|
||||
\fB\-\-persistent \e\fR # Don\'t re\-initialize device on timeout (for example when HDMI cable was disconnected)
|
||||
.nf
|
||||
@@ -72,7 +72,7 @@ Drop frames smaller then this limit. Useful if the device produces small\-sized
|
||||
Don't re\-initialize device on timeout. Default: disabled.
|
||||
.TP
|
||||
.BR \-t ", " \-\-dv\-timings
|
||||
Enable DV timings querying and events processing to automatic resolution change. Default: disabled.
|
||||
Enable DV-timings querying and events processing to automatic resolution change. Default: disabled.
|
||||
.TP
|
||||
.BR \-b\ \fIN ", " \-\-buffers\ \fIN
|
||||
The number of buffers to receive data from the device. Each buffer may processed using an independent thread.
|
||||
@@ -84,21 +84,23 @@ Default: 1 (the number of CPU cores (but not more than 4)).
|
||||
.TP
|
||||
.BR \-q\ \fIN ", " \-\-quality\ \fIN
|
||||
Set quality of JPEG encoding from 1 to 100 (best). Default: 80.
|
||||
Note: If HW encoding is used (JPEG source format selected), this parameter attempts to configure the camera or capture device hardware's internal encoder. It does not re\-encode MJPG to MJPG to change the quality level for sources that already output MJPG.
|
||||
Note: If HW encoding is used (JPEG source format selected), this parameter attempts to configure the camera or capture device hardware's internal encoder. It does not re\-encode MJPEG to MJPEG to change the quality level for sources that already output MJPEG.
|
||||
.TP
|
||||
.BR \-c\ \fItype ", " \-\-encoder\ \fItype
|
||||
Use specified encoder. It may affect the number of workers.
|
||||
|
||||
CPU ─ Software MJPG encoding (default).
|
||||
CPU ─ Software MJPEG encoding (default).
|
||||
|
||||
OMX ─ GPU hardware accelerated MJPG encoding with OpenMax (required \fBWITH_OMX\fR feature).
|
||||
HW ─ Use pre-encoded MJPEG frames directly from camera hardware.
|
||||
|
||||
HW ─ Use pre-encoded MJPG frames directly from camera hardware.
|
||||
M2M-VIDEO ─ GPU-accelerated MJPEG encoding.
|
||||
|
||||
NOOP ─ Don't compress MJPG stream (do nothing).
|
||||
M2M-IMAGE ─ GPU-accelerated JPEG encoding.
|
||||
|
||||
NOOP ─ Don't compress MJPEG stream (do nothing).
|
||||
.TP
|
||||
.BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,...
|
||||
It doesn't do anything. Still here for compatibility. Required \fBWITH_OMX\fR feature.
|
||||
It doesn't do anything. Still here for compatibility.
|
||||
.TP
|
||||
.BR \-k\ \fIpath ", " \-\-blank\ \fIpath
|
||||
Path to JPEG file that will be shown when the device is disconnected during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.
|
||||
@@ -114,6 +116,9 @@ Timeout for device querying. Default: 1.
|
||||
.TP
|
||||
.BR \-\-device\-error\-delay\ \fIsec
|
||||
Delay before trying to connect to the device again after an error (timeout for example). Default: 1.
|
||||
.TP
|
||||
.BR \-\-m2m\-device\ \fI/dev/path
|
||||
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
|
||||
|
||||
.SS "Image control options"
|
||||
.TP
|
||||
@@ -167,12 +172,15 @@ Bind to this TCP port. Default: 8080.
|
||||
.BR \-U\ \fIpath ", " \-\-unix\ \fIpath
|
||||
Bind to UNIX domain socket. Default: disabled.
|
||||
.TP
|
||||
.BR \-d ", " \-\-unix\-rm
|
||||
.BR \-D ", " \-\-unix\-rm
|
||||
Try to remove old unix socket file before binding. default: disabled.
|
||||
.TP
|
||||
.BR \-M\ \fImode ", " \-\-unix\-mode\ \fImode
|
||||
Set UNIX socket file permissions (like 777). Default: disabled.
|
||||
.TP
|
||||
.BR \-S ", " \-\-systemd
|
||||
Bind to systemd socket for socket activation. Required \fBWITH_SYSTEMD\fR feature. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-user\ \fIname
|
||||
HTTP basic auth user. Default: disabled.
|
||||
.TP
|
||||
@@ -189,7 +197,7 @@ Don't send identical frames to clients, but no more than specified number. It ca
|
||||
Override image resolution for the /state. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-tcp\-nodelay
|
||||
Set TCP_NODELAY flag to the client /stream socket. Ignored for \-\-unix.
|
||||
Set TCP_NODELAY flag to the client /stream socket. Only for TCP socket.
|
||||
Default: disabled.
|
||||
.TP
|
||||
.BR \-\-allow\-origin\ \fIstr
|
||||
@@ -218,10 +226,8 @@ Timeout for lock. Default: 1.
|
||||
|
||||
.SS "H264 sink options"
|
||||
.TP
|
||||
Available only if \fBWITH_OMX\fR feature enabled.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\ \fIname
|
||||
Use the specified shared memory object to sink H264 frames encoded by MMAL. Default: disabled.
|
||||
Use the specified shared memory object to sink H264 frames. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\-mode\ \fImode
|
||||
Set H264 sink permissions (like 777). Default: 660.
|
||||
@@ -240,12 +246,19 @@ H264 bitrate in Kbps. Default: 5000.
|
||||
.TP
|
||||
.BR \-\-h264\-gop\ \fIN
|
||||
Intarval between keyframes. Default: 30.
|
||||
.TP
|
||||
.BR \-\-h264\-m2m\-device\ \fI/dev/path
|
||||
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
|
||||
|
||||
|
||||
.SS "Process options"
|
||||
.TP
|
||||
.BR \-\-exit\-on\-parent\-death
|
||||
Exit the program if the parent process is dead. Required \fBHAS_PDEATHSIG\fR feature. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-exit\-on\-no\-clients \fIsec
|
||||
Exit the program if there have been no stream or sink clients or any HTTP requests in the last N seconds. Default: 0 (disabled).
|
||||
.TP
|
||||
.BR \-\-process\-name\-prefix\ \fIstr
|
||||
Set process name prefix which will be displayed in the process list like '\fIstr: ustreamer \-\-blah\-blah\-blah'\fR. Required \fBWITH_SETPROCTITLE\fR feature. Default: disabled.
|
||||
.TP
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=4.6
|
||||
pkgver=5.1
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
license=(GPL)
|
||||
arch=(i686 x86_64 armv6h armv7h aarch64)
|
||||
@@ -21,16 +21,16 @@ if [ -e /usr/bin/python3 ]; then
|
||||
depends+=(python)
|
||||
makedepends+=(python-setuptools)
|
||||
fi
|
||||
if [ -e /opt/vc/include/IL/OMX_Core.h ]; then
|
||||
depends+=(raspberrypi-firmware)
|
||||
makedepends+=(raspberrypi-firmware)
|
||||
_options="$_options WITH_OMX=1"
|
||||
fi
|
||||
if [ -e /usr/include/janus/plugins/plugin.h ];then
|
||||
depends+=(janus-gateway-pikvm)
|
||||
makedepends+=(janus-gateway-pikvm)
|
||||
_options="$_options WITH_JANUS=1"
|
||||
fi
|
||||
if [ -e /usr/include/systemd/sd-daemon.h ];then
|
||||
depends+=(systemd)
|
||||
makedepends+=(systemd)
|
||||
_options="$_options WITH_SYSTEMD=1"
|
||||
fi
|
||||
|
||||
|
||||
# LD does not link mmal with this option
|
||||
|
||||
@@ -7,13 +7,12 @@ RUN apt-get update \
|
||||
gcc \
|
||||
libjpeg8-dev \
|
||||
libbsd-dev \
|
||||
libraspberrypi-dev \
|
||||
libgpiod-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build/ustreamer/
|
||||
COPY . .
|
||||
RUN make -j5 WITH_OMX=1 WITH_GPIO=1
|
||||
RUN make -j5 WITH_GPIO=1
|
||||
RUN ["cross-build-end"]
|
||||
|
||||
FROM balenalib/raspberrypi3-debian:run as RUN
|
||||
|
||||
@@ -5,13 +5,12 @@ RUN apt-get update \
|
||||
gcc \
|
||||
libjpeg8-dev \
|
||||
libbsd-dev \
|
||||
libraspberrypi-dev \
|
||||
libgpiod-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build/ustreamer/
|
||||
COPY . .
|
||||
RUN make -j5 WITH_OMX=1 WITH_GPIO=1
|
||||
RUN make -j5 WITH_GPIO=1
|
||||
|
||||
FROM balenalib/raspberrypi3-debian:run as RUN
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ EAPI=7
|
||||
|
||||
inherit git-r3
|
||||
|
||||
DESCRIPTION="uStreamer - Lightweight and fast MJPG-HTTP streamer"
|
||||
DESCRIPTION="uStreamer - Lightweight and fast MJPEG-HTTP streamer"
|
||||
HOMEPAGE="https://github.com/pikvm/ustreamer"
|
||||
EGIT_REPO_URI="https://github.com/pikvm/ustreamer.git"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=4.6
|
||||
PKG_VERSION:=5.1
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
@@ -30,7 +30,7 @@ define Package/ustreamer
|
||||
endef
|
||||
|
||||
define Package/ustreamer/description
|
||||
µStreamer - Lightweight and fast MJPG-HTTP streamer
|
||||
µStreamer - Lightweight and fast MJPEG-HTTP streamer
|
||||
endef
|
||||
|
||||
define Package/ustreamer/install
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import os
|
||||
|
||||
from distutils.core import Extension
|
||||
from distutils.core import setup
|
||||
from setuptools import Extension
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
# =====
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="4.6",
|
||||
version="5.1",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
21
src/Makefile
21
src/Makefile
@@ -5,9 +5,6 @@ CC ?= gcc
|
||||
CFLAGS ?= -O3
|
||||
LDFLAGS ?=
|
||||
|
||||
RPI_VC_HEADERS ?= /opt/vc/include
|
||||
RPI_VC_LIBS ?= /opt/vc/lib
|
||||
|
||||
|
||||
# =====
|
||||
_USTR = ustreamer.bin
|
||||
@@ -26,6 +23,7 @@ _USTR_SRCS = $(shell ls \
|
||||
ustreamer/data/*.c \
|
||||
ustreamer/encoders/cpu/*.c \
|
||||
ustreamer/encoders/hw/*.c \
|
||||
ustreamer/h264/*.c \
|
||||
)
|
||||
|
||||
_DUMP_LIBS = $(_COMMON_LIBS)
|
||||
@@ -42,16 +40,6 @@ $(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||
endef
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_OMX)),)
|
||||
_USTR_LIBS += -lbcm_host -lvcos -lvcsm -lopenmaxil -lmmal -lmmal_core -lmmal_util -lmmal_vc_client -lmmal_components -L$(RPI_VC_LIBS)
|
||||
override _CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
|
||||
_USTR_SRCS += $(shell ls \
|
||||
ustreamer/encoders/omx/*.c \
|
||||
ustreamer/h264/*.c \
|
||||
)
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||
_USTR_LIBS += -lgpiod
|
||||
override _CFLAGS += -DWITH_GPIO
|
||||
@@ -59,6 +47,13 @@ _USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_SYSTEMD)),)
|
||||
_USTR_LIBS += -lsystemd
|
||||
override _CFLAGS += -DWITH_SYSTEMD
|
||||
_USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
|
||||
endif
|
||||
|
||||
|
||||
WITH_PTHREAD_NP ?= 1
|
||||
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
|
||||
override _CFLAGS += -DWITH_PTHREAD_NP
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -175,8 +175,7 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
_output_context_s ctx;
|
||||
MEMSET_ZERO(ctx);
|
||||
_output_context_s ctx = {0};
|
||||
|
||||
if (output_path && output_path[0] != '\0') {
|
||||
if ((ctx.v_output = (void *)output_file_init(output_path, output_json)) == NULL) {
|
||||
@@ -206,8 +205,7 @@ static void _signal_handler(int signum) {
|
||||
}
|
||||
|
||||
static void _install_signal_handlers(void) {
|
||||
struct sigaction sig_act;
|
||||
MEMSET_ZERO(sig_act);
|
||||
struct sigaction sig_act = {0};
|
||||
|
||||
assert(!sigemptyset(&sig_act.sa_mask));
|
||||
sig_act.sa_handler = _signal_handler;
|
||||
@@ -316,7 +314,7 @@ static void _help(FILE *fp) {
|
||||
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
|
||||
SAY("═════════════════════════════════════════════════════");
|
||||
SAY("Version: %s; license: GPLv3", VERSION);
|
||||
SAY("Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Example:");
|
||||
SAY("════════");
|
||||
SAY(" ustreamer-dump --sink test --output - \\");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -22,8 +22,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION_MAJOR 4
|
||||
#define VERSION_MINOR 6
|
||||
#define VERSION_MAJOR 5
|
||||
#define VERSION_MINOR 1
|
||||
|
||||
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -27,6 +27,7 @@ frame_s *frame_init(void) {
|
||||
frame_s *frame;
|
||||
A_CALLOC(frame, 1);
|
||||
frame_realloc_data(frame, 512 * 1024);
|
||||
frame->dma_fd = -1;
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -80,7 +81,7 @@ unsigned frame_get_padding(const frame_s *frame) {
|
||||
// case V4L2_PIX_FMT_H264:
|
||||
case V4L2_PIX_FMT_MJPEG:
|
||||
case V4L2_PIX_FMT_JPEG: bytes_per_pixel = 0; break;
|
||||
default: assert(0 && "Unknown pixelformat");
|
||||
default: assert(0 && "Unknown format");
|
||||
}
|
||||
if (bytes_per_pixel > 0 && frame->stride > frame->width) {
|
||||
return (frame->stride - frame->width * bytes_per_pixel);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -38,6 +38,7 @@ typedef struct {
|
||||
uint8_t *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
int dma_fd;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
@@ -68,7 +69,7 @@ typedef struct {
|
||||
_dest->encode_end_ts = _src->encode_end_ts; \
|
||||
}
|
||||
|
||||
inline void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
||||
static inline void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
||||
FRAME_COPY_META(src, dest);
|
||||
}
|
||||
|
||||
@@ -83,6 +84,21 @@ inline void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
||||
)
|
||||
|
||||
|
||||
static inline void frame_encoding_begin(const frame_s *src, frame_s *dest, unsigned format) {
|
||||
assert(src->used > 0);
|
||||
frame_copy_meta(src, dest);
|
||||
dest->encode_begin_ts = get_now_monotonic();
|
||||
dest->format = format;
|
||||
dest->stride = 0;
|
||||
dest->used = 0;
|
||||
}
|
||||
|
||||
static inline void frame_encoding_end(frame_s *dest) {
|
||||
assert(dest->used > 0);
|
||||
dest->encode_end_ts = get_now_monotonic();
|
||||
}
|
||||
|
||||
|
||||
frame_s *frame_init(void);
|
||||
void frame_destroy(frame_s *frame);
|
||||
|
||||
@@ -97,6 +113,6 @@ unsigned frame_get_padding(const frame_s *frame);
|
||||
|
||||
const char *fourcc_to_string(unsigned format, char *buf, size_t size);
|
||||
|
||||
inline bool is_jpeg(unsigned format) {
|
||||
static inline bool is_jpeg(unsigned format) {
|
||||
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -137,3 +137,11 @@ INLINE void process_notify_parent(void) {
|
||||
LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void process_suicide(void) {
|
||||
pid_t pid = getpid();
|
||||
|
||||
if (kill(pid, SIGTERM) < 0) {
|
||||
LOG_PERROR("Can't send SIGTERM to own pid %d", pid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -12,30 +12,30 @@
|
||||
<hr>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/state"><b>/state</b></a><br>
|
||||
<a href="state"><b>/state</b></a><br>
|
||||
Get JSON structure with the state of the server.
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<a href="/snapshot"><b>/snapshot</b></a><br>
|
||||
<a href="snapshot"><b>/snapshot</b></a><br>
|
||||
Get a current actual image from the server.
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<a href="/stream"><b>/stream</b></a><br>
|
||||
<a href="stream"><b>/stream</b></a><br>
|
||||
Get a live stream. Query params:<br>
|
||||
<br>
|
||||
<ul>
|
||||
<li>
|
||||
<b>key=abc123</b><br>
|
||||
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br>
|
||||
the stream client to determine its identifier and view statistics using <a href="/state">/state</a>.
|
||||
the stream client to determine its identifier and view statistics using <a href="state">/state</a>.
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<b>extra_headers=1</b><br>
|
||||
Add <i>X-UStreamer-*</i> headers to the <a href="/stream">/stream</a> handle
|
||||
(like with the <a href="/snapshot">/snapshot</a>).
|
||||
Add <i>X-UStreamer-*</i> headers to the <a href="stream">/stream</a> handle
|
||||
(like with the <a href="snapshot">/snapshot</a>).
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
@@ -62,9 +62,9 @@
|
||||
The mjpg-streamer compatibility layer:<br>
|
||||
<br>
|
||||
<ul>
|
||||
<li><a href="/?action=snapshot">/?action=snapshot</a> as alias to the <a href="/snapshot">/snapshot</a>.</li>
|
||||
<li><a href="?action=snapshot">/?action=snapshot</a> as alias to the <a href="snapshot">/snapshot</a>.</li>
|
||||
<br>
|
||||
<li><a href="/?action=stream">/?action=stream</a> as alias to the <a href="/stream">/stream</a>.</li>
|
||||
<li><a href="?action=stream">/?action=stream</a> as alias to the <a href="stream">/stream</a>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -37,30 +37,30 @@ const char *const HTML_INDEX_PAGE = " \
|
||||
<hr> \
|
||||
<ul> \
|
||||
<li> \
|
||||
<a href=\"/state\"><b>/state</b></a><br> \
|
||||
<a href=\"state\"><b>/state</b></a><br> \
|
||||
Get JSON structure with the state of the server. \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
<a href=\"/snapshot\"><b>/snapshot</b></a><br> \
|
||||
<a href=\"snapshot\"><b>/snapshot</b></a><br> \
|
||||
Get a current actual image from the server. \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
<a href=\"/stream\"><b>/stream</b></a><br> \
|
||||
<a href=\"stream\"><b>/stream</b></a><br> \
|
||||
Get a live stream. Query params:<br> \
|
||||
<br> \
|
||||
<ul> \
|
||||
<li> \
|
||||
<b>key=abc123</b><br> \
|
||||
The user-defined key, which is part of cookie <i>stream_client</i>, which allows<br> \
|
||||
the stream client to determine its identifier and view statistics using <a href=\"/state\">/state</a>. \
|
||||
the stream client to determine its identifier and view statistics using <a href=\"state\">/state</a>. \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
<b>extra_headers=1</b><br> \
|
||||
Add <i>X-UStreamer-*</i> headers to the <a href=\"/stream\">/stream</a> handle \
|
||||
(like with the <a href=\"/snapshot\">/snapshot</a>). \
|
||||
Add <i>X-UStreamer-*</i> headers to the <a href=\"stream\">/stream</a> handle \
|
||||
(like with the <a href=\"snapshot\">/snapshot</a>). \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
@@ -87,9 +87,9 @@ const char *const HTML_INDEX_PAGE = " \
|
||||
The mjpg-streamer compatibility layer:<br> \
|
||||
<br> \
|
||||
<ul> \
|
||||
<li><a href=\"/?action=snapshot\">/?action=snapshot</a> as alias to the <a href=\"/snapshot\">/snapshot</a>.</li> \
|
||||
<li><a href=\"?action=snapshot\">/?action=snapshot</a> as alias to the <a href=\"snapshot\">/snapshot</a>.</li> \
|
||||
<br> \
|
||||
<li><a href=\"/?action=stream\">/?action=stream</a> as alias to the <a href=\"/stream\">/stream</a>.</li> \
|
||||
<li><a href=\"?action=stream\">/?action=stream</a> as alias to the <a href=\"stream\">/stream</a>.</li> \
|
||||
</ul> \
|
||||
</li> \
|
||||
</ul> \
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -178,21 +178,15 @@ void device_close(device_s *dev) {
|
||||
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
|
||||
# define HW(_next) RUN(hw_bufs)[index]._next
|
||||
|
||||
# ifdef WITH_OMX
|
||||
if (HW(vcsm_handle) > 0) {
|
||||
vcsm_free(HW(vcsm_handle));
|
||||
HW(vcsm_handle) = -1;
|
||||
}
|
||||
if (HW(dma_fd) >= 0) {
|
||||
close(HW(dma_fd));
|
||||
HW(dma_fd) = -1;
|
||||
}
|
||||
# endif
|
||||
|
||||
if (dev->io_method == V4L2_MEMORY_MMAP) {
|
||||
if (HW(raw.allocated) > 0 && HW(raw.data) != MAP_FAILED) {
|
||||
if (munmap(HW(raw.data), HW(raw.allocated)) < 0) {
|
||||
LOG_PERROR("Can't unmap device buffer %u", index);
|
||||
LOG_PERROR("Can't unmap device buffer=%u", index);
|
||||
}
|
||||
}
|
||||
} else { // V4L2_MEMORY_USERPTR
|
||||
@@ -200,7 +194,6 @@ void device_close(device_s *dev) {
|
||||
free(HW(raw.data));
|
||||
}
|
||||
}
|
||||
A_MUTEX_DESTROY(&HW(grabbed_mutex));
|
||||
|
||||
# undef HW
|
||||
}
|
||||
@@ -220,41 +213,26 @@ void device_close(device_s *dev) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_OMX
|
||||
int device_export_to_vcsm(device_s *dev) {
|
||||
int device_export_to_dma(device_s *dev) {
|
||||
# define DMA_FD RUN(hw_bufs[index].dma_fd)
|
||||
# define VCSM_HANDLE RUN(hw_bufs[index].vcsm_handle)
|
||||
|
||||
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
|
||||
struct v4l2_exportbuffer exp;
|
||||
MEMSET_ZERO(exp);
|
||||
struct v4l2_exportbuffer exp = {0};
|
||||
exp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
exp.index = index;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_EXPBUF) for buffer index=%u ...", index);
|
||||
LOG_DEBUG("Exporting device buffer=%u to DMA ...", index);
|
||||
if (xioctl(RUN(fd), VIDIOC_EXPBUF, &exp) < 0) {
|
||||
LOG_PERROR("Unable to export device buffer index=%u", index);
|
||||
LOG_PERROR("Can't export device buffer=%u to DMA", index);
|
||||
goto error;
|
||||
}
|
||||
DMA_FD = exp.fd;
|
||||
|
||||
LOG_DEBUG("Importing DMA buffer fd=%d into VCSM ...", DMA_FD);
|
||||
int vcsm_handle = vcsm_import_dmabuf(DMA_FD, "v4l2_buf");
|
||||
if (vcsm_handle <= 0) {
|
||||
LOG_PERROR("Unable to import DMA buffer fd=%d into VCSM", DMA_FD);
|
||||
goto error;
|
||||
}
|
||||
VCSM_HANDLE = vcsm_handle;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
|
||||
if (VCSM_HANDLE > 0) {
|
||||
vcsm_free(VCSM_HANDLE);
|
||||
VCSM_HANDLE = -1;
|
||||
}
|
||||
if (DMA_FD >= 0) {
|
||||
close(DMA_FD);
|
||||
DMA_FD = -1;
|
||||
@@ -262,18 +240,16 @@ int device_export_to_vcsm(device_s *dev) {
|
||||
}
|
||||
return -1;
|
||||
|
||||
# undef VCSM_HANDLE
|
||||
# undef DMA_FD
|
||||
}
|
||||
#endif
|
||||
|
||||
int device_switch_capturing(device_s *dev, bool enable) {
|
||||
if (enable != RUN(capturing)) {
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(%s) ...", (enable ? "VIDIOC_STREAMON" : "VIDIOC_STREAMOFF"));
|
||||
LOG_DEBUG("%s device capturing ...", (enable ? "Starting" : "Stopping"));
|
||||
if (xioctl(RUN(fd), (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
|
||||
LOG_PERROR("Unable to %s capturing", (enable ? "start" : "stop"));
|
||||
LOG_PERROR("Can't %s capturing", (enable ? "start" : "stop"));
|
||||
if (enable) {
|
||||
return -1;
|
||||
}
|
||||
@@ -282,7 +258,7 @@ int device_switch_capturing(device_s *dev, bool enable) {
|
||||
RUN(capturing) = enable;
|
||||
LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error) {
|
||||
@@ -334,23 +310,20 @@ int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_erro
|
||||
int device_grab_buffer(device_s *dev, hw_buffer_s **hw) {
|
||||
*hw = NULL;
|
||||
|
||||
struct v4l2_buffer buf_info;
|
||||
MEMSET_ZERO(buf_info);
|
||||
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf_info.memory = dev->io_method;
|
||||
struct v4l2_buffer buf = {0};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = dev->io_method;
|
||||
|
||||
LOG_DEBUG("Grabbing device buffer ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_DQBUF, &buf_info) < 0) {
|
||||
LOG_PERROR("Unable to grab device buffer");
|
||||
if (xioctl(RUN(fd), VIDIOC_DQBUF, &buf) < 0) {
|
||||
LOG_PERROR("Can't grab device buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Grabbed new frame in device buffer: index=%u, bytesused=%u",
|
||||
buf_info.index, buf_info.bytesused);
|
||||
LOG_DEBUG("Grabbed new frame: buffer=%u, bytesused=%u", buf.index, buf.bytesused);
|
||||
|
||||
if (buf_info.index >= RUN(n_bufs)) {
|
||||
LOG_ERROR("V4L2 error: grabbed invalid device buffer: index=%u, n_bufs=%u",
|
||||
buf_info.index, RUN(n_bufs));
|
||||
if (buf.index >= RUN(n_bufs)) {
|
||||
LOG_ERROR("V4L2 error: grabbed invalid device buffer=%u, n_bufs=%u", buf.index, RUN(n_bufs));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -359,61 +332,56 @@ int device_grab_buffer(device_s *dev, hw_buffer_s **hw) {
|
||||
// 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_info.bytesused < dev->min_frame_size) {
|
||||
LOG_DEBUG("Dropped too small frame sized %d bytes, assuming it was broken", buf_info.bytesused);
|
||||
LOG_DEBUG("Releasing device buffer index=%u (broken frame) ...", buf_info.index);
|
||||
if (xioctl(RUN(fd), VIDIOC_QBUF, &buf_info) < 0) {
|
||||
LOG_PERROR("Unable to release device buffer index=%u (broken frame)", buf_info.index);
|
||||
if (buf.bytesused < dev->min_frame_size) {
|
||||
LOG_DEBUG("Dropped too small frame, assuming it was broken: buffer=%u, bytesused=%u",
|
||||
buf.index, buf.bytesused);
|
||||
LOG_DEBUG("Releasing device buffer=%u (broken frame) ...", buf.index);
|
||||
if (xioctl(RUN(fd), VIDIOC_QBUF, &buf) < 0) {
|
||||
LOG_PERROR("Can't release device buffer=%u (broken frame)", buf.index);
|
||||
return -1;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
# define HW(_next) RUN(hw_bufs)[buf_info.index]._next
|
||||
# define HW(_next) RUN(hw_bufs)[buf.index]._next
|
||||
|
||||
A_MUTEX_LOCK(&HW(grabbed_mutex));
|
||||
if (HW(grabbed)) {
|
||||
LOG_ERROR("V4L2 error: grabbed device buffer is already used: index=%u, bytesused=%u",
|
||||
buf_info.index, buf_info.bytesused);
|
||||
A_MUTEX_UNLOCK(&HW(grabbed_mutex));
|
||||
LOG_ERROR("V4L2 error: grabbed device buffer=%u is already used", buf.index);
|
||||
return -1;
|
||||
}
|
||||
HW(grabbed) = true;
|
||||
A_MUTEX_UNLOCK(&HW(grabbed_mutex));
|
||||
|
||||
HW(raw.used) = buf_info.bytesused;
|
||||
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_info), &buf_info, sizeof(struct v4l2_buffer));
|
||||
memcpy(&HW(buf), &buf, sizeof(struct v4l2_buffer));
|
||||
HW(raw.grab_ts) = get_now_monotonic();
|
||||
|
||||
# undef HW
|
||||
*hw = &RUN(hw_bufs[buf_info.index]);
|
||||
return buf_info.index;
|
||||
*hw = &RUN(hw_bufs[buf.index]);
|
||||
return buf.index;
|
||||
}
|
||||
|
||||
int device_release_buffer(device_s *dev, hw_buffer_s *hw) {
|
||||
const unsigned index = hw->buf_info.index;
|
||||
LOG_DEBUG("Releasing device buffer index=%u ...", index);
|
||||
const unsigned index = hw->buf.index;
|
||||
LOG_DEBUG("Releasing device buffer=%u ...", index);
|
||||
|
||||
A_MUTEX_LOCK(&hw->grabbed_mutex);
|
||||
if (xioctl(RUN(fd), VIDIOC_QBUF, &hw->buf_info) < 0) {
|
||||
LOG_PERROR("Unable to release device buffer index=%u", index);
|
||||
A_MUTEX_UNLOCK(&hw->grabbed_mutex);
|
||||
if (xioctl(RUN(fd), VIDIOC_QBUF, &hw->buf) < 0) {
|
||||
LOG_PERROR("Can't release device buffer=%u", index);
|
||||
return -1;
|
||||
}
|
||||
hw->grabbed = false;
|
||||
A_MUTEX_UNLOCK(&hw->grabbed_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int device_consume_event(device_s *dev) {
|
||||
struct v4l2_event event;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ...");
|
||||
LOG_DEBUG("Consuming V4L2 event ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_DQEVENT, &event) == 0) {
|
||||
switch (event.type) {
|
||||
case V4L2_EVENT_SOURCE_CHANGE:
|
||||
@@ -430,22 +398,21 @@ int device_consume_event(device_s *dev) {
|
||||
}
|
||||
|
||||
static int _device_open_check_cap(device_s *dev) {
|
||||
struct v4l2_capability cap;
|
||||
MEMSET_ZERO(cap);
|
||||
struct v4l2_capability cap = {0};
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYCAP) ...");
|
||||
LOG_DEBUG("Querying device capabilities ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERYCAP, &cap) < 0) {
|
||||
LOG_PERROR("Can't query device (VIDIOC_QUERYCAP)");
|
||||
LOG_PERROR("Can't query device capabilities");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
||||
LOG_ERROR("Video capture not supported by the device");
|
||||
LOG_ERROR("Video capture is not supported by device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
|
||||
LOG_ERROR("Device does not support streaming IO");
|
||||
LOG_ERROR("Device doesn't support streaming IO");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -471,20 +438,18 @@ static int _device_open_check_cap(device_s *dev) {
|
||||
static int _device_open_dv_timings(device_s *dev) {
|
||||
_device_apply_resolution(dev, dev->width, dev->height);
|
||||
if (dev->dv_timings) {
|
||||
LOG_DEBUG("Using DV timings");
|
||||
LOG_DEBUG("Using DV-timings");
|
||||
|
||||
if (_device_apply_dv_timings(dev) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct v4l2_event_subscription sub;
|
||||
|
||||
MEMSET_ZERO(sub);
|
||||
struct v4l2_event_subscription sub = {0};
|
||||
sub.type = V4L2_EVENT_SOURCE_CHANGE;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_SUBSCRIBE_EVENT) ...");
|
||||
LOG_DEBUG("Subscribing to DV-timings events ...")
|
||||
if (xioctl(RUN(fd), VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
|
||||
LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE");
|
||||
LOG_PERROR("Can't subscribe to DV-timings events");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -492,17 +457,30 @@ static int _device_open_dv_timings(device_s *dev) {
|
||||
}
|
||||
|
||||
static int _device_apply_dv_timings(device_s *dev) {
|
||||
struct v4l2_dv_timings dv;
|
||||
MEMSET_ZERO(dv);
|
||||
struct v4l2_dv_timings dv = {0};
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
|
||||
LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu",
|
||||
dv.bt.width, dv.bt.height, (unsigned long long)dv.bt.pixelclock); // Issue #11
|
||||
if (dv.type == V4L2_DV_BT_656_1120) {
|
||||
// See v4l2_print_dv_timings() in the kernel
|
||||
unsigned htot = V4L2_DV_BT_FRAME_WIDTH(&dv.bt);
|
||||
unsigned vtot = V4L2_DV_BT_FRAME_HEIGHT(&dv.bt);
|
||||
if (dv.bt.interlaced) {
|
||||
vtot /= 2;
|
||||
}
|
||||
unsigned fps = ((htot * vtot) > 0 ? ((100 * (uint64_t)dv.bt.pixelclock)) / (htot * vtot) : 0);
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_S_DV_TIMINGS, &dv) < 0) {
|
||||
LOG_PERROR("Failed to set DV timings");
|
||||
LOG_PERROR("Failed to set DV-timings");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -526,8 +504,7 @@ static int _device_apply_dv_timings(device_s *dev) {
|
||||
static int _device_open_format(device_s *dev, bool first) {
|
||||
const unsigned stride = align_size(RUN(width), 32) << 1;
|
||||
|
||||
struct v4l2_format fmt;
|
||||
MEMSET_ZERO(fmt);
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = RUN(width);
|
||||
fmt.fmt.pix.height = RUN(height);
|
||||
@@ -536,10 +513,10 @@ static int _device_open_format(device_s *dev, bool first) {
|
||||
fmt.fmt.pix.bytesperline = stride;
|
||||
|
||||
// Set format
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ...");
|
||||
LOG_DEBUG("Probing device format=%s, stride=%u, resolution=%ux%u ...",
|
||||
_format_to_string_supported(dev->format), stride, RUN(width), RUN(height));
|
||||
if (xioctl(RUN(fd), VIDIOC_S_FMT, &fmt) < 0) {
|
||||
LOG_PERROR("Unable to set pixelformat=%s, stride=%u, resolution=%ux%u",
|
||||
_format_to_string_supported(dev->format), stride, RUN(width), RUN(height));
|
||||
LOG_PERROR("Can't set device format");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -559,23 +536,23 @@ static int _device_open_format(device_s *dev, bool first) {
|
||||
|
||||
// Check format
|
||||
if (fmt.fmt.pix.pixelformat != dev->format) {
|
||||
LOG_ERROR("Could not obtain the requested pixelformat=%s; driver gave us %s",
|
||||
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) {
|
||||
LOG_INFO("Falling back to pixelformat=%s", format_str);
|
||||
LOG_INFO("Falling back to format=%s", format_str);
|
||||
} else {
|
||||
char fourcc_str[8];
|
||||
LOG_ERROR("Unsupported pixelformat=%s (fourcc)",
|
||||
LOG_ERROR("Unsupported format=%s (fourcc)",
|
||||
fourcc_to_string(fmt.fmt.pix.pixelformat, fourcc_str, 8));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
RUN(format) = fmt.fmt.pix.pixelformat;
|
||||
LOG_INFO("Using pixelformat: %s", _format_to_string_supported(RUN(format)));
|
||||
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
|
||||
@@ -585,16 +562,15 @@ static int _device_open_format(device_s *dev, bool first) {
|
||||
static void _device_open_hw_fps(device_s *dev) {
|
||||
RUN(hw_fps) = 0;
|
||||
|
||||
struct v4l2_streamparm setfps;
|
||||
MEMSET_ZERO(setfps);
|
||||
struct v4l2_streamparm setfps = {0};
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_G_PARM) ...");
|
||||
LOG_DEBUG("Querying HW FPS ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_G_PARM, &setfps) < 0) {
|
||||
if (errno == ENOTTY) { // Quiet message for Auvidea B101
|
||||
if (errno == ENOTTY) { // Quiet message for TC358743
|
||||
LOG_INFO("Querying HW FPS changing is not supported");
|
||||
} else {
|
||||
LOG_PERROR("Unable to query HW FPS changing");
|
||||
LOG_PERROR("Can't query HW FPS changing");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -612,7 +588,7 @@ static void _device_open_hw_fps(device_s *dev) {
|
||||
SETFPS_TPF(denominator) = (dev->desired_fps == 0 ? 255 : dev->desired_fps);
|
||||
|
||||
if (xioctl(RUN(fd), VIDIOC_S_PARM, &setfps) < 0) {
|
||||
LOG_PERROR("Unable to set HW FPS");
|
||||
LOG_PERROR("Can't set HW FPS");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -640,15 +616,14 @@ static void _device_open_jpeg_quality(device_s *dev) {
|
||||
unsigned quality = 0;
|
||||
|
||||
if (is_jpeg(RUN(format))) {
|
||||
struct v4l2_jpegcompression comp;
|
||||
MEMSET_ZERO(comp);
|
||||
struct v4l2_jpegcompression comp = {0};
|
||||
|
||||
if (xioctl(RUN(fd), VIDIOC_G_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Device does not support setting of HW encoding quality parameters");
|
||||
LOG_ERROR("Device doesn't support setting of HW encoding quality parameters");
|
||||
} else {
|
||||
comp.quality = dev->jpeg_quality;
|
||||
if (xioctl(RUN(fd), VIDIOC_S_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Unable to change MJPG quality for JPEG source with HW pass-through encoder");
|
||||
LOG_ERROR("Can't change MJPEG quality for JPEG source with HW pass-through encoder");
|
||||
} else {
|
||||
quality = dev->jpeg_quality;
|
||||
}
|
||||
@@ -669,15 +644,14 @@ static int _device_open_io_method(device_s *dev) {
|
||||
}
|
||||
|
||||
static int _device_open_io_method_mmap(device_s *dev) {
|
||||
struct v4l2_requestbuffers req;
|
||||
MEMSET_ZERO(req);
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = dev->n_bufs;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_MMAP ...");
|
||||
LOG_DEBUG("Requesting %u device buffers for MMAP ...", req.count);
|
||||
if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_MMAP", dev->path);
|
||||
LOG_PERROR("Device '%s' doesn't support MMAP method", dev->path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -692,40 +666,34 @@ static int _device_open_io_method_mmap(device_s *dev) {
|
||||
|
||||
A_CALLOC(RUN(hw_bufs), req.count);
|
||||
for (RUN(n_bufs) = 0; RUN(n_bufs) < req.count; ++RUN(n_bufs)) {
|
||||
struct v4l2_buffer buf_info;
|
||||
MEMSET_ZERO(buf_info);
|
||||
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf_info.memory = V4L2_MEMORY_MMAP;
|
||||
buf_info.index = 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);
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer %u ...", RUN(n_bufs));
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERYBUF, &buf_info) < 0) {
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer=%u ...", RUN(n_bufs));
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
LOG_PERROR("Can't VIDIOC_QUERYBUF");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define HW(_next) RUN(hw_bufs)[RUN(n_bufs)]._next
|
||||
|
||||
# ifdef WITH_OMX
|
||||
HW(dma_fd) = -1;
|
||||
HW(vcsm_handle) = -1;
|
||||
# endif
|
||||
|
||||
A_MUTEX_INIT(&HW(grabbed_mutex));
|
||||
|
||||
LOG_DEBUG("Mapping device buffer %u ...", RUN(n_bufs));
|
||||
LOG_DEBUG("Mapping device buffer=%u ...", RUN(n_bufs));
|
||||
if ((HW(raw.data) = mmap(
|
||||
NULL,
|
||||
buf_info.length,
|
||||
buf.length,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
RUN(fd),
|
||||
buf_info.m.offset
|
||||
buf.m.offset
|
||||
)) == MAP_FAILED) {
|
||||
LOG_PERROR("Can't map device buffer %u", RUN(n_bufs));
|
||||
LOG_PERROR("Can't map device buffer=%u", RUN(n_bufs));
|
||||
return -1;
|
||||
}
|
||||
HW(raw.allocated) = buf_info.length;
|
||||
HW(raw.allocated) = buf.length;
|
||||
|
||||
# undef HW
|
||||
}
|
||||
@@ -733,15 +701,14 @@ static int _device_open_io_method_mmap(device_s *dev) {
|
||||
}
|
||||
|
||||
static int _device_open_io_method_userptr(device_s *dev) {
|
||||
struct v4l2_requestbuffers req;
|
||||
MEMSET_ZERO(req);
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = dev->n_bufs;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_USERPTR;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_USERPTR ...");
|
||||
LOG_DEBUG("Requesting %u device buffers for USERPTR ...", req.count);
|
||||
if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_USERPTR", dev->path);
|
||||
LOG_PERROR("Device '%s' doesn't support USERPTR method", dev->path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -771,18 +738,17 @@ static int _device_open_io_method_userptr(device_s *dev) {
|
||||
|
||||
static int _device_open_queue_buffers(device_s *dev) {
|
||||
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
|
||||
struct v4l2_buffer buf_info;
|
||||
MEMSET_ZERO(buf_info);
|
||||
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf_info.memory = dev->io_method;
|
||||
buf_info.index = 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_info.m.userptr = (unsigned long)RUN(hw_bufs)[index].raw.data;
|
||||
buf_info.length = RUN(hw_bufs)[index].raw.allocated;
|
||||
buf.m.userptr = (unsigned long)RUN(hw_bufs)[index].raw.data;
|
||||
buf.length = RUN(hw_bufs)[index].raw.allocated;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %u ...", index);
|
||||
if (xioctl(RUN(fd), VIDIOC_QBUF, &buf_info) < 0) {
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer=%u ...", index);
|
||||
if (xioctl(RUN(fd), VIDIOC_QBUF, &buf) < 0) {
|
||||
LOG_PERROR("Can't VIDIOC_QBUF");
|
||||
return -1;
|
||||
}
|
||||
@@ -792,7 +758,7 @@ static int _device_open_queue_buffers(device_s *dev) {
|
||||
|
||||
static int _device_apply_resolution(device_s *dev, unsigned width, unsigned height) {
|
||||
// Тут VIDEO_MIN_* не используются из-за странностей минимального разрешения при отсутствии сигнала
|
||||
// у некоторых устройств, например Auvidea B101
|
||||
// у некоторых устройств, например TC358743
|
||||
if (
|
||||
width == 0 || width > VIDEO_MAX_WIDTH
|
||||
|| height == 0 || height > VIDEO_MAX_HEIGHT
|
||||
@@ -891,8 +857,7 @@ static void _device_set_control(
|
||||
return;
|
||||
}
|
||||
|
||||
struct v4l2_control ctl;
|
||||
MEMSET_ZERO(ctl);
|
||||
struct v4l2_control ctl = {0};
|
||||
ctl.id = cid;
|
||||
ctl.value = value;
|
||||
|
||||
@@ -906,12 +871,12 @@ static void _device_set_control(
|
||||
}
|
||||
|
||||
static const char *_format_to_string_nullable(unsigned format) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
|
||||
if (format == _FORMATS[index].format) {
|
||||
return _FORMATS[index].name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *_format_to_string_supported(unsigned format) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -40,9 +40,6 @@
|
||||
#include <pthread.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
#ifdef WITH_OMX
|
||||
# include <interface/vcsm/user-vcsm.h>
|
||||
#endif
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
@@ -71,17 +68,10 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
frame_s raw;
|
||||
|
||||
struct v4l2_buffer buf_info;
|
||||
|
||||
# ifdef WITH_OMX
|
||||
int dma_fd;
|
||||
int vcsm_handle;
|
||||
# endif
|
||||
|
||||
pthread_mutex_t grabbed_mutex;
|
||||
bool grabbed;
|
||||
frame_s raw;
|
||||
struct v4l2_buffer buf;
|
||||
int dma_fd;
|
||||
bool grabbed;
|
||||
} hw_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
@@ -159,9 +149,7 @@ int device_parse_io_method(const char *str);
|
||||
int device_open(device_s *dev);
|
||||
void device_close(device_s *dev);
|
||||
|
||||
#ifdef WITH_OMX
|
||||
int device_export_to_vcsm(device_s *dev);
|
||||
#endif
|
||||
int device_export_to_dma(device_s *dev);
|
||||
int device_switch_capturing(device_s *dev, bool enable);
|
||||
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error);
|
||||
int device_grab_buffer(device_s *dev, hw_buffer_s **hw);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -27,12 +27,14 @@ static const struct {
|
||||
const char *name;
|
||||
const encoder_type_e type;
|
||||
} _ENCODER_TYPES[] = {
|
||||
{"CPU", ENCODER_TYPE_CPU},
|
||||
{"HW", ENCODER_TYPE_HW},
|
||||
# ifdef WITH_OMX
|
||||
{"OMX", ENCODER_TYPE_OMX},
|
||||
# endif
|
||||
{"NOOP", ENCODER_TYPE_NOOP},
|
||||
{"CPU", ENCODER_TYPE_CPU},
|
||||
{"HW", ENCODER_TYPE_HW},
|
||||
{"M2M-VIDEO", ENCODER_TYPE_M2M_VIDEO},
|
||||
{"M2M-IMAGE", ENCODER_TYPE_M2M_IMAGE},
|
||||
{"M2M-MJPEG", ENCODER_TYPE_M2M_VIDEO},
|
||||
{"M2M-JPEG", ENCODER_TYPE_M2M_IMAGE},
|
||||
{"OMX", ENCODER_TYPE_M2M_IMAGE},
|
||||
{"NOOP", ENCODER_TYPE_NOOP},
|
||||
};
|
||||
|
||||
|
||||
@@ -60,16 +62,14 @@ encoder_s *encoder_init(void) {
|
||||
}
|
||||
|
||||
void encoder_destroy(encoder_s *enc) {
|
||||
# ifdef WITH_OMX
|
||||
if (ER(omxs)) {
|
||||
for (unsigned index = 0; index < ER(n_omxs); ++index) {
|
||||
if (ER(omxs[index])) {
|
||||
omx_encoder_destroy(ER(omxs[index]));
|
||||
if (ER(m2ms)) {
|
||||
for (unsigned index = 0; index < ER(n_m2ms); ++index) {
|
||||
if (ER(m2ms[index])) {
|
||||
m2m_encoder_destroy(ER(m2ms[index]));
|
||||
}
|
||||
}
|
||||
free(ER(omxs));
|
||||
free(ER(m2ms));
|
||||
}
|
||||
# endif
|
||||
A_MUTEX_DESTROY(&ER(mutex));
|
||||
free(enc->run);
|
||||
free(enc);
|
||||
@@ -113,63 +113,30 @@ workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
|
||||
}
|
||||
quality = DR(jpeg_quality);
|
||||
n_workers = 1;
|
||||
}
|
||||
# ifdef WITH_OMX
|
||||
else if (type == ENCODER_TYPE_OMX) {
|
||||
if (align_size(DR(width), 32) != DR(width)) {
|
||||
LOG_INFO("Switching to CPU encoder: OMX can't handle width=%u ...", DR(width));
|
||||
goto use_cpu;
|
||||
|
||||
} else if (type == ENCODER_TYPE_M2M_VIDEO || type == ENCODER_TYPE_M2M_IMAGE) {
|
||||
LOG_DEBUG("Preparing M2M-%s encoder ...", (type == ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
|
||||
if (ER(m2ms) == NULL) {
|
||||
A_CALLOC(ER(m2ms), n_workers);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Preparing OMX encoder ...");
|
||||
|
||||
if (n_workers > OMX_MAX_ENCODERS) {
|
||||
LOG_INFO("OMX encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
|
||||
n_workers = OMX_MAX_ENCODERS;
|
||||
}
|
||||
|
||||
if (ER(omxs) == NULL) {
|
||||
A_CALLOC(ER(omxs), OMX_MAX_ENCODERS);
|
||||
}
|
||||
|
||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||
for (; ER(n_omxs) < n_workers; ++ER(n_omxs)) {
|
||||
if ((ER(omxs[ER(n_omxs)]) = omx_encoder_init()) == NULL) {
|
||||
LOG_ERROR("Can't initialize OMX encoder, falling back to CPU");
|
||||
goto force_cpu;
|
||||
for (; ER(n_m2ms) < n_workers; ++ER(n_m2ms)) {
|
||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||
char name[32];
|
||||
snprintf(name, 32, "JPEG-%u", ER(n_m2ms));
|
||||
if (type == ENCODER_TYPE_M2M_VIDEO) {
|
||||
ER(m2ms[ER(n_m2ms)]) = m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
} else {
|
||||
ER(m2ms[ER(n_m2ms)]) = m2m_jpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
}
|
||||
}
|
||||
|
||||
frame_s frame;
|
||||
MEMSET_ZERO(frame);
|
||||
frame.width = DR(width);
|
||||
frame.height = DR(height);
|
||||
frame.format = DR(format);
|
||||
frame.stride = DR(stride);
|
||||
|
||||
for (unsigned index = 0; index < ER(n_omxs); ++index) {
|
||||
int omx_error = omx_encoder_prepare(ER(omxs[index]), &frame, quality);
|
||||
if (omx_error == -2) {
|
||||
goto use_cpu;
|
||||
} else if (omx_error < 0) {
|
||||
goto force_cpu;
|
||||
}
|
||||
}
|
||||
}
|
||||
# endif
|
||||
else if (type == ENCODER_TYPE_NOOP) {
|
||||
} else if (type == ENCODER_TYPE_NOOP) {
|
||||
n_workers = 1;
|
||||
quality = 0;
|
||||
}
|
||||
|
||||
goto ok;
|
||||
|
||||
# ifdef WITH_OMX
|
||||
force_cpu:
|
||||
LOG_ERROR("Forced CPU encoder permanently");
|
||||
cpu_forced = true;
|
||||
# endif
|
||||
|
||||
use_cpu:
|
||||
type = ENCODER_TYPE_CPU;
|
||||
quality = dev->jpeg_quality;
|
||||
@@ -235,55 +202,47 @@ static bool _worker_run_job(worker_s *wr) {
|
||||
|
||||
# define ER(_next) job->enc->run->_next
|
||||
|
||||
LOG_DEBUG("Worker %s compressing JPEG from buffer %u ...", wr->name, job->hw->buf_info.index);
|
||||
|
||||
assert(ER(type) != ENCODER_TYPE_UNKNOWN);
|
||||
assert(src->used > 0);
|
||||
|
||||
frame_copy_meta(src, dest);
|
||||
dest->format = V4L2_PIX_FMT_JPEG;
|
||||
dest->stride = 0;
|
||||
dest->encode_begin_ts = get_now_monotonic();
|
||||
dest->used = 0;
|
||||
|
||||
if (ER(type) == ENCODER_TYPE_CPU) {
|
||||
LOG_VERBOSE("Compressing buffer using CPU");
|
||||
LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
cpu_encoder_compress(src, dest, ER(quality));
|
||||
|
||||
} else if (ER(type) == ENCODER_TYPE_HW) {
|
||||
LOG_VERBOSE("Compressing buffer using HW (just copying)");
|
||||
LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
hw_encoder_compress(src, dest);
|
||||
}
|
||||
# ifdef WITH_OMX
|
||||
else if (ER(type) == ENCODER_TYPE_OMX) {
|
||||
LOG_VERBOSE("Compressing buffer using OMX");
|
||||
if (omx_encoder_compress(ER(omxs[wr->number]), src, dest) < 0) {
|
||||
|
||||
} else if (ER(type) == ENCODER_TYPE_M2M_VIDEO || ER(type) == ENCODER_TYPE_M2M_IMAGE) {
|
||||
LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
|
||||
(ER(type) == ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index);
|
||||
if (m2m_encoder_compress(ER(m2ms[wr->number]), src, dest, false) < 0) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
else if (ER(type) == ENCODER_TYPE_NOOP) {
|
||||
LOG_VERBOSE("Compressing buffer using NOOP (do nothing)");
|
||||
|
||||
} else if (ER(type) == ENCODER_TYPE_NOOP) {
|
||||
LOG_VERBOSE("Compressing JPEG using NOOP (do nothing): worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
usleep(5000); // Просто чтобы работала логика desired_fps
|
||||
dest->encode_end_ts = get_now_monotonic();
|
||||
}
|
||||
|
||||
dest->encode_end_ts = get_now_monotonic();
|
||||
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
|
||||
job->dest->used,
|
||||
job->dest->encode_end_ts - job->dest->encode_begin_ts,
|
||||
wr->name,
|
||||
job->hw->buf_info.index);
|
||||
job->hw->buf.index);
|
||||
|
||||
return true;
|
||||
|
||||
# ifdef WITH_OMX
|
||||
error:
|
||||
LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf_info.index);
|
||||
LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
|
||||
LOG_ERROR("Error while compressing buffer, falling back to CPU");
|
||||
A_MUTEX_LOCK(&ER(mutex));
|
||||
ER(cpu_forced) = true;
|
||||
A_MUTEX_UNLOCK(&ER(mutex));
|
||||
return false;
|
||||
# endif
|
||||
|
||||
# undef ER
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -37,30 +37,20 @@
|
||||
|
||||
#include "device.h"
|
||||
#include "workers.h"
|
||||
#include "m2m.h"
|
||||
|
||||
#include "encoders/cpu/encoder.h"
|
||||
#include "encoders/hw/encoder.h"
|
||||
|
||||
#ifdef WITH_OMX
|
||||
# include "encoders/omx/encoder.h"
|
||||
# define ENCODER_TYPES_OMX_HINT ", OMX"
|
||||
#else
|
||||
# define ENCODER_TYPES_OMX_HINT ""
|
||||
#endif
|
||||
|
||||
|
||||
#define ENCODER_TYPES_STR \
|
||||
"CPU, HW" \
|
||||
ENCODER_TYPES_OMX_HINT \
|
||||
", NOOP"
|
||||
#define ENCODER_TYPES_STR "CPU, HW, M2M-VIDEO, M2M-IMAGE, NOOP"
|
||||
|
||||
typedef enum {
|
||||
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
|
||||
ENCODER_TYPE_CPU,
|
||||
ENCODER_TYPE_HW,
|
||||
# ifdef WITH_OMX
|
||||
ENCODER_TYPE_OMX,
|
||||
# endif
|
||||
ENCODER_TYPE_M2M_VIDEO,
|
||||
ENCODER_TYPE_M2M_IMAGE,
|
||||
ENCODER_TYPE_NOOP,
|
||||
} encoder_type_e;
|
||||
|
||||
@@ -70,15 +60,14 @@ typedef struct {
|
||||
bool cpu_forced;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
# ifdef WITH_OMX
|
||||
unsigned n_omxs;
|
||||
omx_encoder_s **omxs;
|
||||
# endif
|
||||
unsigned n_m2ms;
|
||||
m2m_encoder_s **m2ms;
|
||||
} encoder_runtime_s;
|
||||
|
||||
typedef struct {
|
||||
encoder_type_e type;
|
||||
unsigned n_workers;
|
||||
char *m2m_path;
|
||||
|
||||
encoder_runtime_s *run;
|
||||
} encoder_s;
|
||||
@@ -86,7 +75,6 @@ typedef struct {
|
||||
typedef struct {
|
||||
encoder_s *enc;
|
||||
hw_buffer_s *hw;
|
||||
char *dest_role;
|
||||
frame_s *dest;
|
||||
} encoder_job_s;
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# This source file based on code of MJPG-Streamer. #
|
||||
# #
|
||||
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
||||
# Copyright (C) 2006 Gabriel A. Devenyi #
|
||||
# Copyright (C) 2007 Tom Stöveken #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -50,6 +50,8 @@ static void _jpeg_term_destination(j_compress_ptr jpeg);
|
||||
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
|
||||
// This function based on compress_image_to_jpeg() from mjpg-streamer
|
||||
|
||||
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
|
||||
struct jpeg_compress_struct jpeg;
|
||||
struct jpeg_error_mgr jpeg_error;
|
||||
|
||||
@@ -85,7 +87,7 @@ void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
|
||||
jpeg_finish_compress(&jpeg);
|
||||
jpeg_destroy_compress(&jpeg);
|
||||
|
||||
assert(dest->used > 0);
|
||||
frame_encoding_end(dest);
|
||||
}
|
||||
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# This source file based on code of MJPG-Streamer. #
|
||||
# #
|
||||
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
||||
# Copyright (C) 2006 Gabriel A. Devenyi #
|
||||
# Copyright (C) 2007 Tom Stöveken #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -38,6 +38,8 @@ void hw_encoder_compress(const frame_s *src, frame_s *dest) {
|
||||
}
|
||||
|
||||
void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
|
||||
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
|
||||
if (!_is_huffman(src->data)) {
|
||||
const uint8_t *src_ptr = src->data;
|
||||
const uint8_t *src_end = src->data + src->used;
|
||||
@@ -55,9 +57,12 @@ void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
|
||||
frame_set_data(dest, src->data, paste);
|
||||
frame_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
|
||||
frame_append_data(dest, src_ptr, src->used - paste);
|
||||
|
||||
} else {
|
||||
frame_set_data(dest, src->data, src->used);
|
||||
}
|
||||
|
||||
frame_encoding_end(dest);
|
||||
}
|
||||
|
||||
static bool _is_huffman(const uint8_t *data) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# This source file based on code of MJPG-Streamer. #
|
||||
# #
|
||||
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
||||
# Copyright (C) 2006 Gabriel A. Devenyi #
|
||||
# Copyright (C) 2007 Tom Stöveken #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 "component.h"
|
||||
|
||||
|
||||
static int _omx_component_wait_port_changed(OMX_HANDLETYPE *comp, OMX_U32 port, OMX_BOOL enabled);
|
||||
static int _omx_component_wait_state_changed(OMX_HANDLETYPE *comp, OMX_STATETYPE wanted);
|
||||
|
||||
|
||||
int omx_component_enable_port(OMX_HANDLETYPE *comp, OMX_U32 port) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
LOG_DEBUG("Enabling OMX port %u ...", port);
|
||||
if ((error = OMX_SendCommand(*comp, OMX_CommandPortEnable, port, NULL)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't enable OMX port %u", port);
|
||||
return -1;
|
||||
}
|
||||
return _omx_component_wait_port_changed(comp, port, OMX_TRUE);
|
||||
}
|
||||
|
||||
int omx_component_disable_port(OMX_HANDLETYPE *comp, OMX_U32 port) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
LOG_DEBUG("Disabling OMX port %u ...", port);
|
||||
if ((error = OMX_SendCommand(*comp, OMX_CommandPortDisable, port, NULL)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't disable OMX port %u", port);
|
||||
return -1;
|
||||
}
|
||||
return _omx_component_wait_port_changed(comp, port, OMX_FALSE);
|
||||
}
|
||||
|
||||
int omx_component_get_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
// cppcheck-suppress redundantPointerOp
|
||||
OMX_INIT_STRUCTURE(*portdef);
|
||||
portdef->nPortIndex = port;
|
||||
|
||||
LOG_DEBUG("Fetching OMX port %u definition ...", port);
|
||||
if ((error = OMX_GetParameter(*comp, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't get OMX port %u definition", port);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omx_component_set_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
LOG_DEBUG("Writing OMX port %u definition ...", portdef->nPortIndex);
|
||||
if ((error = OMX_SetParameter(*comp, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't set OMX port %u definition", portdef->nPortIndex);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omx_component_set_state(OMX_HANDLETYPE *comp, OMX_STATETYPE state) {
|
||||
const char *state_str = omx_state_to_string(state);
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
LOG_DEBUG("Switching component state to %s ...", state_str);
|
||||
|
||||
int retries = 50;
|
||||
do {
|
||||
error = OMX_SendCommand(*comp, OMX_CommandStateSet, state, NULL);
|
||||
if (error == OMX_ErrorNone) {
|
||||
return _omx_component_wait_state_changed(comp, state);
|
||||
} else if (error == OMX_ErrorInsufficientResources && retries) {
|
||||
// Иногда железо не инициализируется, хз почему, просто ретраим, со второй попытки сработает
|
||||
if (retries > 45) {
|
||||
LOG_VERBOSE("Can't switch OMX component state to %s, need to retry: %s",
|
||||
state_str, omx_error_to_string(error));
|
||||
} else {
|
||||
LOG_ERROR_OMX(error, "Can't switch OMX component state to %s, need to retry", state_str);
|
||||
}
|
||||
retries -= 1;
|
||||
usleep(8000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (retries);
|
||||
|
||||
LOG_ERROR_OMX(error, "Can't switch OMX component state to %s", state_str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static int _omx_component_wait_port_changed(OMX_HANDLETYPE *comp, OMX_U32 port, OMX_BOOL enabled) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
OMX_PARAM_PORTDEFINITIONTYPE portdef;
|
||||
OMX_INIT_STRUCTURE(portdef);
|
||||
portdef.nPortIndex = port;
|
||||
|
||||
int retries = 50;
|
||||
do {
|
||||
if ((error = OMX_GetParameter(*comp, OMX_IndexParamPortDefinition, &portdef)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't get OMX port %u definition for waiting", port);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (portdef.bEnabled != enabled) {
|
||||
LOG_DEBUG("Waiting for OMX %s port %u", (enabled ? "enabling" : "disabling"), port);
|
||||
retries -= 1;
|
||||
usleep(8000);
|
||||
}
|
||||
} while (portdef.bEnabled != enabled && retries);
|
||||
|
||||
LOG_DEBUG("OMX port %u %s", port, (enabled ? "enabled" : "disabled"));
|
||||
return (portdef.bEnabled == enabled ? 0 : -1);
|
||||
}
|
||||
|
||||
static int _omx_component_wait_state_changed(OMX_HANDLETYPE *comp, OMX_STATETYPE wanted) {
|
||||
OMX_ERRORTYPE error;
|
||||
OMX_STATETYPE state;
|
||||
|
||||
int retries = 50;
|
||||
do {
|
||||
if ((error = OMX_GetState(*comp, &state)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Failed to get OMX component state");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (state != wanted) {
|
||||
LOG_DEBUG("Waiting when OMX component state changes to %s", omx_state_to_string(wanted));
|
||||
retries -= 1;
|
||||
usleep(8000);
|
||||
}
|
||||
} while (state != wanted && retries);
|
||||
|
||||
LOG_DEBUG("Switched OMX component state to %s", omx_state_to_string(wanted))
|
||||
return (state == wanted ? 0 : -1);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <IL/OMX_Core.h>
|
||||
#include <IL/OMX_Component.h>
|
||||
|
||||
#include "../../../libs/logging.h"
|
||||
|
||||
#include "formatters.h"
|
||||
|
||||
|
||||
#define OMX_INIT_STRUCTURE(_var) { \
|
||||
memset(&(_var), 0, sizeof(_var)); \
|
||||
(_var).nSize = sizeof(_var); \
|
||||
(_var).nVersion.nVersion = OMX_VERSION; \
|
||||
(_var).nVersion.s.nVersionMajor = OMX_VERSION_MAJOR; \
|
||||
(_var).nVersion.s.nVersionMinor = OMX_VERSION_MINOR; \
|
||||
(_var).nVersion.s.nRevision = OMX_VERSION_REVISION; \
|
||||
(_var).nVersion.s.nStep = OMX_VERSION_STEP; \
|
||||
}
|
||||
|
||||
|
||||
int omx_component_enable_port(OMX_HANDLETYPE *comp, OMX_U32 port);
|
||||
int omx_component_disable_port(OMX_HANDLETYPE *comp, OMX_U32 port);
|
||||
|
||||
int omx_component_get_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port);
|
||||
int omx_component_set_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef);
|
||||
|
||||
int omx_component_set_state(OMX_HANDLETYPE *comp, OMX_STATETYPE state);
|
||||
@@ -1,444 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 "encoder.h"
|
||||
|
||||
|
||||
static const OMX_U32 _INPUT_PORT = 340;
|
||||
static const OMX_U32 _OUTPUT_PORT = 341;
|
||||
|
||||
|
||||
static int _omx_init_component(omx_encoder_s *omx);
|
||||
static int _omx_init_disable_ports(omx_encoder_s *omx);
|
||||
static int _omx_setup_input(omx_encoder_s *omx, const frame_s *frame);
|
||||
static int _omx_setup_output(omx_encoder_s *omx, unsigned quality);
|
||||
static int _omx_encoder_clear_ports(omx_encoder_s *omx);
|
||||
|
||||
static OMX_ERRORTYPE _omx_event_handler(
|
||||
UNUSED OMX_HANDLETYPE comp,
|
||||
OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1,
|
||||
UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data);
|
||||
|
||||
static OMX_ERRORTYPE _omx_input_required_handler(
|
||||
UNUSED OMX_HANDLETYPE comp,
|
||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf);
|
||||
|
||||
static OMX_ERRORTYPE _omx_output_available_handler(
|
||||
UNUSED OMX_HANDLETYPE comp,
|
||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf);
|
||||
|
||||
|
||||
omx_encoder_s *omx_encoder_init(void) {
|
||||
// Some theory:
|
||||
// - http://www.fourcc.org/yuv.php
|
||||
// - https://kwasi-ich.de/blog/2017/11/26/omx/
|
||||
// - https://github.com/hopkinskong/rpi-omx-jpeg-encode/blob/master/jpeg_bench.cpp
|
||||
// - https://github.com/kwasmich/OMXPlayground/blob/master/omxJPEGEnc.c
|
||||
// - https://github.com/gagle/raspberrypi-openmax-jpeg/blob/master/jpeg.c
|
||||
// - https://www.raspberrypi.org/forums/viewtopic.php?t=154790
|
||||
// - https://bitbucket.org/bensch128/omxjpegencode/src/master/jpeg_encoder.cpp
|
||||
// - http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html
|
||||
|
||||
LOG_INFO("Initializing OMX encoder ...");
|
||||
|
||||
omx_encoder_s *omx;
|
||||
A_CALLOC(omx, 1);
|
||||
|
||||
if (vcos_semaphore_create(&omx->handler_sem, "handler_sem", 0) != VCOS_SUCCESS) {
|
||||
LOG_ERROR("Can't create VCOS semaphore");
|
||||
goto error;
|
||||
}
|
||||
omx->i_handler_sem = true;
|
||||
|
||||
if (_omx_init_component(omx) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (_omx_init_disable_ports(omx) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return omx;
|
||||
|
||||
error:
|
||||
omx_encoder_destroy(omx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void omx_encoder_destroy(omx_encoder_s *omx) {
|
||||
LOG_INFO("Destroying OMX encoder ...");
|
||||
|
||||
omx_component_set_state(&omx->comp, OMX_StateIdle);
|
||||
_omx_encoder_clear_ports(omx);
|
||||
omx_component_set_state(&omx->comp, OMX_StateLoaded);
|
||||
|
||||
if (omx->i_handler_sem) {
|
||||
vcos_semaphore_delete(&omx->handler_sem);
|
||||
}
|
||||
|
||||
if (omx->i_encoder) {
|
||||
OMX_ERRORTYPE error;
|
||||
if ((error = OMX_FreeHandle(omx->comp)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't free OMX.broadcom.image_encode");
|
||||
}
|
||||
}
|
||||
|
||||
free(omx);
|
||||
}
|
||||
|
||||
int omx_encoder_prepare(omx_encoder_s *omx, const frame_s *frame, unsigned quality) {
|
||||
if (align_size(frame->width, 32) != frame->width && frame_get_padding(frame) == 0) {
|
||||
LOG_ERROR("%u %u", frame->width, frame->stride);
|
||||
LOG_ERROR("OMX encoder can't handle unaligned width");
|
||||
return -2;
|
||||
}
|
||||
if (omx_component_set_state(&omx->comp, OMX_StateIdle) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (_omx_encoder_clear_ports(omx) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (_omx_setup_input(omx, frame) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (_omx_setup_output(omx, quality) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (omx_component_set_state(&omx->comp, OMX_StateExecuting) < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omx_encoder_compress(omx_encoder_s *omx, const frame_s *src, frame_s *dest) {
|
||||
# define IN(_next) omx->input_buf->_next
|
||||
# define OUT(_next) omx->output_buf->_next
|
||||
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
if ((error = OMX_FillThisBuffer(omx->comp, omx->output_buf)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Failed to request filling of the output buffer on encoder");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dest->width = align_size(src->width, 32);
|
||||
dest->used = 0;
|
||||
|
||||
omx->output_available = false;
|
||||
omx->input_required = true;
|
||||
|
||||
size_t slice_size = (IN(nAllocLen) < src->used ? IN(nAllocLen) : src->used);
|
||||
size_t pos = 0;
|
||||
|
||||
while (true) {
|
||||
if (omx->failed) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (omx->output_available) {
|
||||
omx->output_available = false;
|
||||
|
||||
frame_append_data(dest, OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
|
||||
|
||||
if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) {
|
||||
OUT(nFlags) = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((error = OMX_FillThisBuffer(omx->comp, omx->output_buf)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Failed to request filling of the output buffer on encoder");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (omx->input_required) {
|
||||
omx->input_required = false;
|
||||
|
||||
if (pos == src->used) {
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(IN(pBuffer), src->data + pos, slice_size);
|
||||
IN(nOffset) = 0;
|
||||
IN(nFilledLen) = slice_size;
|
||||
|
||||
pos += slice_size;
|
||||
|
||||
if (pos + slice_size > src->used) {
|
||||
slice_size = src->used - pos;
|
||||
}
|
||||
|
||||
if ((error = OMX_EmptyThisBuffer(omx->comp, omx->input_buf)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Failed to request emptying of the input buffer on encoder");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (vcos_my_semwait("", &omx->handler_sem, 1) < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
# undef OUT
|
||||
# undef IN
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _omx_init_component(omx_encoder_s *omx) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
OMX_CALLBACKTYPE callbacks;
|
||||
MEMSET_ZERO(callbacks);
|
||||
callbacks.EventHandler = _omx_event_handler;
|
||||
callbacks.EmptyBufferDone = _omx_input_required_handler;
|
||||
callbacks.FillBufferDone = _omx_output_available_handler;
|
||||
|
||||
LOG_DEBUG("Initializing OMX.broadcom.image_encode ...");
|
||||
if ((error = OMX_GetHandle(&omx->comp, "OMX.broadcom.image_encode", omx, &callbacks)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't initialize OMX.broadcom.image_encode");
|
||||
return -1;
|
||||
}
|
||||
omx->i_encoder = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _omx_init_disable_ports(omx_encoder_s *omx) {
|
||||
OMX_ERRORTYPE error;
|
||||
OMX_INDEXTYPE types[] = {
|
||||
OMX_IndexParamAudioInit, OMX_IndexParamVideoInit,
|
||||
OMX_IndexParamImageInit, OMX_IndexParamOtherInit,
|
||||
};
|
||||
OMX_PORT_PARAM_TYPE ports;
|
||||
|
||||
OMX_INIT_STRUCTURE(ports);
|
||||
if ((error = OMX_GetParameter(omx->comp, OMX_IndexParamImageInit, &ports)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't OMX_GetParameter(OMX_IndexParamImageInit)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (unsigned index = 0; index < 4; ++index) {
|
||||
if ((error = OMX_GetParameter(omx->comp, types[index], &ports)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't OMX_GetParameter(types[%u])", index);
|
||||
return -1;
|
||||
}
|
||||
for (OMX_U32 port = ports.nStartPortNumber; port < ports.nStartPortNumber + ports.nPorts; ++port) {
|
||||
if (omx_component_disable_port(&omx->comp, port) < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _omx_setup_input(omx_encoder_s *omx, const frame_s *frame) {
|
||||
LOG_DEBUG("Setting up OMX JPEG input port ...");
|
||||
|
||||
OMX_ERRORTYPE error;
|
||||
OMX_PARAM_PORTDEFINITIONTYPE portdef;
|
||||
|
||||
if (omx_component_get_portdef(&omx->comp, &portdef, _INPUT_PORT) < 0) {
|
||||
LOG_ERROR("... first");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define IFMT(_next) portdef.format.image._next
|
||||
IFMT(nFrameWidth) = align_size(frame->width, 32);
|
||||
IFMT(nFrameHeight) = frame->height;
|
||||
IFMT(nStride) = align_size(frame->width, 32) << 1;
|
||||
IFMT(nSliceHeight) = align_size(frame->height, 16);
|
||||
IFMT(bFlagErrorConcealment) = OMX_FALSE;
|
||||
IFMT(eCompressionFormat) = OMX_IMAGE_CodingUnused;
|
||||
portdef.nBufferSize = ((frame->width * frame->height) << 1) * 2;
|
||||
switch (frame->format) {
|
||||
// https://www.fourcc.org/yuv.php
|
||||
// Also see comments inside OMX_IVCommon.h
|
||||
case V4L2_PIX_FMT_YUYV: IFMT(eColorFormat) = OMX_COLOR_FormatYCbYCr; break;
|
||||
case V4L2_PIX_FMT_UYVY: IFMT(eColorFormat) = OMX_COLOR_FormatCbYCrY; break;
|
||||
case V4L2_PIX_FMT_RGB565: IFMT(eColorFormat) = OMX_COLOR_Format16bitRGB565; break;
|
||||
case V4L2_PIX_FMT_RGB24: IFMT(eColorFormat) = OMX_COLOR_Format24bitRGB888; break;
|
||||
// TODO: найти устройство с RGB565 и протестить его.
|
||||
// FIXME: RGB24 не работает нормально, нижняя половина экрана зеленая.
|
||||
// FIXME: Китайский EasyCap тоже не работает, мусор на экране.
|
||||
// Вероятно обе проблемы вызваны некорректной реализацией OMX на пае.
|
||||
default: assert(0 && "Unsupported pixelformat");
|
||||
}
|
||||
# undef IFMT
|
||||
|
||||
if (omx_component_set_portdef(&omx->comp, &portdef) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (omx_component_get_portdef(&omx->comp, &portdef, _INPUT_PORT) < 0) {
|
||||
LOG_ERROR("... second");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (omx_component_enable_port(&omx->comp, _INPUT_PORT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
omx->i_input_port_enabled = true;
|
||||
|
||||
if ((error = OMX_AllocateBuffer(omx->comp, &omx->input_buf, _INPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't allocate OMX JPEG input buffer");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _omx_setup_output(omx_encoder_s *omx, unsigned quality) {
|
||||
LOG_DEBUG("Setting up OMX JPEG output port ...");
|
||||
|
||||
OMX_ERRORTYPE error;
|
||||
OMX_PARAM_PORTDEFINITIONTYPE portdef;
|
||||
|
||||
if (omx_component_get_portdef(&omx->comp, &portdef, _OUTPUT_PORT) < 0) {
|
||||
LOG_ERROR("... first");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define OFMT(_next) portdef.format.image._next
|
||||
OFMT(bFlagErrorConcealment) = OMX_FALSE;
|
||||
OFMT(eCompressionFormat) = OMX_IMAGE_CodingJPEG;
|
||||
OFMT(eColorFormat) = OMX_COLOR_FormatYCbYCr;
|
||||
# undef OFMT
|
||||
|
||||
if (omx_component_set_portdef(&omx->comp, &portdef) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (omx_component_get_portdef(&omx->comp, &portdef, _OUTPUT_PORT) < 0) {
|
||||
LOG_ERROR("... second");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define SET_PARAM(_key, _value) { \
|
||||
if ((error = OMX_SetParameter(omx->comp, OMX_IndexParam##_key, _value)) != OMX_ErrorNone) { \
|
||||
LOG_ERROR_OMX(error, "Can't set OMX param %s", #_key); \
|
||||
return -1; \
|
||||
} \
|
||||
}
|
||||
|
||||
OMX_CONFIG_BOOLEANTYPE exif;
|
||||
OMX_INIT_STRUCTURE(exif);
|
||||
exif.bEnabled = OMX_FALSE;
|
||||
SET_PARAM(BrcmDisableEXIF, &exif);
|
||||
|
||||
OMX_PARAM_IJGSCALINGTYPE ijg;
|
||||
OMX_INIT_STRUCTURE(ijg);
|
||||
ijg.nPortIndex = _OUTPUT_PORT;
|
||||
ijg.bEnabled = OMX_TRUE;
|
||||
SET_PARAM(BrcmEnableIJGTableScaling, &ijg);
|
||||
|
||||
OMX_IMAGE_PARAM_QFACTORTYPE qfactor;
|
||||
OMX_INIT_STRUCTURE(qfactor);
|
||||
qfactor.nPortIndex = _OUTPUT_PORT;
|
||||
qfactor.nQFactor = quality;
|
||||
SET_PARAM(QFactor, &qfactor);
|
||||
|
||||
# undef SET_PARAM
|
||||
|
||||
if (omx_component_enable_port(&omx->comp, _OUTPUT_PORT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
omx->i_output_port_enabled = true;
|
||||
|
||||
if ((error = OMX_AllocateBuffer(omx->comp, &omx->output_buf, _OUTPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't allocate OMX JPEG output buffer");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _omx_encoder_clear_ports(omx_encoder_s *omx) {
|
||||
OMX_ERRORTYPE error;
|
||||
int retval = 0;
|
||||
|
||||
if (omx->i_output_port_enabled) {
|
||||
retval -= omx_component_disable_port(&omx->comp, _OUTPUT_PORT);
|
||||
omx->i_output_port_enabled = false;
|
||||
}
|
||||
if (omx->i_input_port_enabled) {
|
||||
retval -= omx_component_disable_port(&omx->comp, _INPUT_PORT);
|
||||
omx->i_input_port_enabled = false;
|
||||
}
|
||||
|
||||
if (omx->input_buf) {
|
||||
if ((error = OMX_FreeBuffer(omx->comp, _INPUT_PORT, omx->input_buf)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't free OMX JPEG input buffer");
|
||||
// retval -= 1;
|
||||
}
|
||||
omx->input_buf = NULL;
|
||||
}
|
||||
if (omx->output_buf) {
|
||||
if ((error = OMX_FreeBuffer(omx->comp, _OUTPUT_PORT, omx->output_buf)) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(error, "Can't free OMX JPEG output buffer");
|
||||
// retval -= 1;
|
||||
}
|
||||
omx->output_buf = NULL;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static OMX_ERRORTYPE _omx_event_handler(
|
||||
UNUSED OMX_HANDLETYPE comp,
|
||||
OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1,
|
||||
UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data) {
|
||||
|
||||
// OMX calls this handler for all the events it emits
|
||||
|
||||
omx_encoder_s *omx = (omx_encoder_s *)v_omx;
|
||||
|
||||
if (event == OMX_EventError) {
|
||||
LOG_ERROR_OMX((OMX_ERRORTYPE)data1, "OMX error event received");
|
||||
omx->failed = true;
|
||||
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
|
||||
}
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
static OMX_ERRORTYPE _omx_input_required_handler(
|
||||
UNUSED OMX_HANDLETYPE comp,
|
||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf) {
|
||||
|
||||
// Called by OMX when the encoder component requires
|
||||
// the input buffer to be filled with RAW image data
|
||||
|
||||
omx_encoder_s *omx = (omx_encoder_s *)v_omx;
|
||||
|
||||
omx->input_required = true;
|
||||
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
static OMX_ERRORTYPE _omx_output_available_handler(
|
||||
UNUSED OMX_HANDLETYPE comp,
|
||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf) {
|
||||
|
||||
// Called by OMX when the encoder component has filled
|
||||
// the output buffer with JPEG data
|
||||
|
||||
omx_encoder_s *omx = (omx_encoder_s *)v_omx;
|
||||
|
||||
omx->output_available = true;
|
||||
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <IL/OMX_Core.h>
|
||||
#include <IL/OMX_Component.h>
|
||||
#include <IL/OMX_Broadcom.h>
|
||||
#include <interface/vcos/vcos_semaphore.h>
|
||||
|
||||
#include "../../../libs/tools.h"
|
||||
#include "../../../libs/logging.h"
|
||||
#include "../../../libs/frame.h"
|
||||
|
||||
#include "vcos.h"
|
||||
#include "formatters.h"
|
||||
#include "component.h"
|
||||
|
||||
|
||||
#ifndef CFG_OMX_MAX_ENCODERS
|
||||
# define CFG_OMX_MAX_ENCODERS 3 // Raspberry Pi limitation
|
||||
#endif
|
||||
#define OMX_MAX_ENCODERS ((unsigned)(CFG_OMX_MAX_ENCODERS))
|
||||
|
||||
|
||||
typedef struct {
|
||||
OMX_HANDLETYPE comp;
|
||||
OMX_BUFFERHEADERTYPE *input_buf;
|
||||
OMX_BUFFERHEADERTYPE *output_buf;
|
||||
bool input_required;
|
||||
bool output_available;
|
||||
bool failed;
|
||||
VCOS_SEMAPHORE_T handler_sem;
|
||||
|
||||
bool i_handler_sem;
|
||||
bool i_encoder;
|
||||
bool i_input_port_enabled;
|
||||
bool i_output_port_enabled;
|
||||
} omx_encoder_s;
|
||||
|
||||
|
||||
omx_encoder_s *omx_encoder_init(void);
|
||||
void omx_encoder_destroy(omx_encoder_s *omx);
|
||||
|
||||
int omx_encoder_prepare(omx_encoder_s *omx, const frame_s *frame, unsigned quality);
|
||||
int omx_encoder_compress(omx_encoder_s *omx, const frame_s *src, frame_s *dest);
|
||||
@@ -1,95 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 "formatters.h"
|
||||
|
||||
|
||||
#define CASE_TO_STRING(_value) \
|
||||
case _value: { return #_value; }
|
||||
|
||||
const char *omx_error_to_string(OMX_ERRORTYPE error) {
|
||||
switch (error) {
|
||||
CASE_TO_STRING(OMX_ErrorNone);
|
||||
CASE_TO_STRING(OMX_ErrorInsufficientResources);
|
||||
CASE_TO_STRING(OMX_ErrorUndefined);
|
||||
CASE_TO_STRING(OMX_ErrorInvalidComponentName);
|
||||
CASE_TO_STRING(OMX_ErrorComponentNotFound);
|
||||
CASE_TO_STRING(OMX_ErrorInvalidComponent);
|
||||
CASE_TO_STRING(OMX_ErrorBadParameter);
|
||||
CASE_TO_STRING(OMX_ErrorNotImplemented);
|
||||
CASE_TO_STRING(OMX_ErrorUnderflow);
|
||||
CASE_TO_STRING(OMX_ErrorOverflow);
|
||||
CASE_TO_STRING(OMX_ErrorHardware);
|
||||
CASE_TO_STRING(OMX_ErrorInvalidState);
|
||||
CASE_TO_STRING(OMX_ErrorStreamCorrupt);
|
||||
CASE_TO_STRING(OMX_ErrorPortsNotCompatible);
|
||||
CASE_TO_STRING(OMX_ErrorResourcesLost);
|
||||
CASE_TO_STRING(OMX_ErrorNoMore);
|
||||
CASE_TO_STRING(OMX_ErrorVersionMismatch);
|
||||
CASE_TO_STRING(OMX_ErrorNotReady);
|
||||
CASE_TO_STRING(OMX_ErrorTimeout);
|
||||
CASE_TO_STRING(OMX_ErrorSameState);
|
||||
CASE_TO_STRING(OMX_ErrorResourcesPreempted);
|
||||
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringAllocation);
|
||||
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringDeallocation);
|
||||
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringStop);
|
||||
CASE_TO_STRING(OMX_ErrorIncorrectStateTransition);
|
||||
CASE_TO_STRING(OMX_ErrorIncorrectStateOperation);
|
||||
CASE_TO_STRING(OMX_ErrorUnsupportedSetting);
|
||||
CASE_TO_STRING(OMX_ErrorUnsupportedIndex);
|
||||
CASE_TO_STRING(OMX_ErrorBadPortIndex);
|
||||
CASE_TO_STRING(OMX_ErrorPortUnpopulated);
|
||||
CASE_TO_STRING(OMX_ErrorComponentSuspended);
|
||||
CASE_TO_STRING(OMX_ErrorDynamicResourcesUnavailable);
|
||||
CASE_TO_STRING(OMX_ErrorMbErrorsInFrame);
|
||||
CASE_TO_STRING(OMX_ErrorFormatNotDetected);
|
||||
CASE_TO_STRING(OMX_ErrorContentPipeOpenFailed);
|
||||
CASE_TO_STRING(OMX_ErrorContentPipeCreationFailed);
|
||||
CASE_TO_STRING(OMX_ErrorSeperateTablesUsed);
|
||||
CASE_TO_STRING(OMX_ErrorTunnelingUnsupported);
|
||||
CASE_TO_STRING(OMX_ErrorKhronosExtensions);
|
||||
CASE_TO_STRING(OMX_ErrorVendorStartUnused);
|
||||
CASE_TO_STRING(OMX_ErrorDiskFull);
|
||||
CASE_TO_STRING(OMX_ErrorMaxFileSize);
|
||||
CASE_TO_STRING(OMX_ErrorDrmUnauthorised);
|
||||
CASE_TO_STRING(OMX_ErrorDrmExpired);
|
||||
CASE_TO_STRING(OMX_ErrorDrmGeneral);
|
||||
default: return "Unknown OMX error";
|
||||
}
|
||||
}
|
||||
|
||||
const char *omx_state_to_string(OMX_STATETYPE state) {
|
||||
switch (state) {
|
||||
CASE_TO_STRING(OMX_StateInvalid);
|
||||
CASE_TO_STRING(OMX_StateLoaded);
|
||||
CASE_TO_STRING(OMX_StateIdle);
|
||||
CASE_TO_STRING(OMX_StateExecuting);
|
||||
CASE_TO_STRING(OMX_StatePause);
|
||||
CASE_TO_STRING(OMX_StateWaitForResources);
|
||||
// cppcheck-suppress constArgument
|
||||
// cppcheck-suppress knownArgument
|
||||
default: break;
|
||||
}
|
||||
assert(0 && "Unsupported OMX state");
|
||||
}
|
||||
|
||||
#undef CASE_TO_STRING
|
||||
@@ -1,55 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 "vcos.h"
|
||||
|
||||
|
||||
int vcos_my_semwait(const char *prefix, VCOS_SEMAPHORE_T *sem, long double timeout) {
|
||||
// vcos_semaphore_wait() can wait infinite
|
||||
// vcos_semaphore_wait_timeout() is broken by design:
|
||||
// - https://github.com/pikvm/ustreamer/issues/56
|
||||
// - https://github.com/raspberrypi/userland/issues/658
|
||||
// - The current approach is an ugly busyloop
|
||||
// Три стула.
|
||||
|
||||
long double deadline_ts = get_now_monotonic() + timeout;
|
||||
VCOS_STATUS_T sem_status;
|
||||
|
||||
while (true) {
|
||||
sem_status = vcos_semaphore_trywait(sem);
|
||||
if (sem_status == VCOS_SUCCESS) {
|
||||
return 0;
|
||||
} else if (sem_status != VCOS_EAGAIN || get_now_monotonic() > deadline_ts) {
|
||||
break;
|
||||
}
|
||||
if (usleep(1000) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (sem_status) {
|
||||
case VCOS_EAGAIN: LOG_ERROR("%sCan't wait VCOS semaphore: EAGAIN (timeout)", prefix); break;
|
||||
case VCOS_EINVAL: LOG_ERROR("%sCan't wait VCOS semaphore: EINVAL", prefix); break;
|
||||
default: LOG_ERROR("%sCan't wait VCOS semaphore: %d", prefix, sem_status); break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,395 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 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 "encoder.h"
|
||||
|
||||
|
||||
static void _h264_encoder_cleanup(h264_encoder_s *enc);
|
||||
static int _h264_encoder_compress_raw(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key);
|
||||
|
||||
static void _mmal_callback(MMAL_WRAPPER_T *wrapper);
|
||||
static const char *_mmal_error_to_string(MMAL_STATUS_T error);
|
||||
|
||||
|
||||
#define LOG_ERROR_MMAL(_error, _msg, ...) { \
|
||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _mmal_error_to_string(_error)); \
|
||||
}
|
||||
|
||||
|
||||
h264_encoder_s *h264_encoder_init(unsigned bitrate, unsigned gop, unsigned fps) {
|
||||
LOG_INFO("H264: Initializing MMAL encoder ...");
|
||||
LOG_INFO("H264: Using bitrate: %u Kbps", bitrate);
|
||||
LOG_INFO("H264: Using GOP: %u", gop);
|
||||
|
||||
h264_encoder_s *enc;
|
||||
A_CALLOC(enc, 1);
|
||||
enc->bitrate = bitrate; // Kbps
|
||||
enc->gop = gop; // Interval between keyframes
|
||||
enc->fps = fps;
|
||||
|
||||
enc->last_online = -1;
|
||||
|
||||
if (vcos_semaphore_create(&enc->handler_sem, "h264_handler_sem", 0) != VCOS_SUCCESS) {
|
||||
LOG_PERROR("H264: Can't create VCOS semaphore");
|
||||
goto error;
|
||||
}
|
||||
enc->i_handler_sem = true;
|
||||
|
||||
MMAL_STATUS_T error = mmal_wrapper_create(&enc->wrapper, MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER);
|
||||
if (error != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't create MMAL wrapper");
|
||||
enc->wrapper = NULL;
|
||||
goto error;
|
||||
}
|
||||
enc->wrapper->user_data = (void *)enc;
|
||||
enc->wrapper->callback = _mmal_callback;
|
||||
|
||||
return enc;
|
||||
|
||||
error:
|
||||
h264_encoder_destroy(enc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void h264_encoder_destroy(h264_encoder_s *enc) {
|
||||
LOG_INFO("H264: Destroying MMAL encoder ...");
|
||||
|
||||
_h264_encoder_cleanup(enc);
|
||||
|
||||
if (enc->wrapper) {
|
||||
MMAL_STATUS_T error = mmal_wrapper_destroy(enc->wrapper);
|
||||
if (error != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't destroy MMAL encoder");
|
||||
}
|
||||
}
|
||||
|
||||
if (enc->i_handler_sem) {
|
||||
vcos_semaphore_delete(&enc->handler_sem);
|
||||
}
|
||||
|
||||
free(enc);
|
||||
}
|
||||
|
||||
bool h264_encoder_is_prepared_for(h264_encoder_s *enc, const frame_s *frame, bool zero_copy) {
|
||||
# define EQ(_field) (enc->_field == frame->_field)
|
||||
return (EQ(width) && EQ(height) && EQ(format) && EQ(stride) && (enc->zero_copy == zero_copy));
|
||||
# undef EQ
|
||||
}
|
||||
|
||||
int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_copy) {
|
||||
LOG_INFO("H264: Configuring MMAL encoder: zero_copy=%d ...", zero_copy);
|
||||
|
||||
_h264_encoder_cleanup(enc);
|
||||
|
||||
enc->width = frame->width;
|
||||
enc->height = frame->height;
|
||||
enc->format = frame->format;
|
||||
enc->stride = frame->stride;
|
||||
enc->zero_copy = zero_copy;
|
||||
|
||||
if (align_size(frame->width, 32) != frame->width && frame_get_padding(frame) == 0) {
|
||||
LOG_ERROR("H264: MMAL encoder can't handle unaligned width");
|
||||
goto error;
|
||||
}
|
||||
|
||||
MMAL_STATUS_T error;
|
||||
|
||||
# define PREPARE_PORT(_id) { \
|
||||
enc->_id##_port = enc->wrapper->_id[0]; \
|
||||
if (enc->_id##_port->is_enabled) { \
|
||||
if ((error = mmal_wrapper_port_disable(enc->_id##_port)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "H264: Can't disable MMAL %s port while configuring", #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
# define COMMIT_PORT(_id) { \
|
||||
if ((error = mmal_port_format_commit(enc->_id##_port)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "H264: Can't commit MMAL %s port", #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
# define SET_PORT_PARAM(_id, _type, _key, _value) { \
|
||||
if ((error = mmal_port_parameter_set_##_type(enc->_id##_port, MMAL_PARAMETER_##_key, _value)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "H264: Can't set %s for the %s port", #_key, #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
# define ENABLE_PORT(_id) { \
|
||||
if ((error = mmal_wrapper_port_enable(enc->_id##_port, MMAL_WRAPPER_FLAG_PAYLOAD_ALLOCATE)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "H264: Can't enable MMAL %s port", #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
{
|
||||
PREPARE_PORT(input);
|
||||
|
||||
# define IFMT(_next) enc->input_port->format->_next
|
||||
IFMT(type) = MMAL_ES_TYPE_VIDEO;
|
||||
switch (frame->format) {
|
||||
case V4L2_PIX_FMT_YUYV: IFMT(encoding) = MMAL_ENCODING_YUYV; break;
|
||||
case V4L2_PIX_FMT_UYVY: IFMT(encoding) = MMAL_ENCODING_UYVY; break;
|
||||
case V4L2_PIX_FMT_RGB565: IFMT(encoding) = MMAL_ENCODING_RGB16; break;
|
||||
case V4L2_PIX_FMT_RGB24: IFMT(encoding) = MMAL_ENCODING_RGB24; break;
|
||||
default: assert(0 && "Unsupported pixelformat");
|
||||
}
|
||||
IFMT(es->video.width) = align_size(frame->width, 32);
|
||||
IFMT(es->video.height) = align_size(frame->height, 16);
|
||||
IFMT(es->video.crop.x) = 0;
|
||||
IFMT(es->video.crop.y) = 0;
|
||||
IFMT(es->video.crop.width) = frame->width;
|
||||
IFMT(es->video.crop.height) = frame->height;
|
||||
IFMT(flags) = MMAL_ES_FORMAT_FLAG_FRAMED;
|
||||
enc->input_port->buffer_size = 1000 * 1000;
|
||||
enc->input_port->buffer_num = enc->input_port->buffer_num_recommended * 4;
|
||||
# undef IFMT
|
||||
|
||||
COMMIT_PORT(input);
|
||||
SET_PORT_PARAM(input, boolean, ZERO_COPY, zero_copy);
|
||||
}
|
||||
|
||||
{
|
||||
PREPARE_PORT(output);
|
||||
|
||||
# define OFMT(_next) enc->output_port->format->_next
|
||||
OFMT(type) = MMAL_ES_TYPE_VIDEO;
|
||||
OFMT(encoding) = MMAL_ENCODING_H264;
|
||||
OFMT(encoding_variant) = MMAL_ENCODING_VARIANT_H264_DEFAULT;
|
||||
OFMT(bitrate) = enc->bitrate * 1000;
|
||||
OFMT(es->video.frame_rate.num) = enc->fps;
|
||||
OFMT(es->video.frame_rate.den) = 1;
|
||||
enc->output_port->buffer_size = enc->output_port->buffer_size_recommended * 4;
|
||||
enc->output_port->buffer_num = enc->output_port->buffer_num_recommended;
|
||||
# undef OFMT
|
||||
|
||||
COMMIT_PORT(output);
|
||||
{
|
||||
MMAL_PARAMETER_VIDEO_PROFILE_T profile;
|
||||
MEMSET_ZERO(profile);
|
||||
profile.hdr.id = MMAL_PARAMETER_PROFILE;
|
||||
profile.hdr.size = sizeof(profile);
|
||||
// http://blog.mediacoderhq.com/h264-profiles-and-levels
|
||||
profile.profile[0].profile = MMAL_VIDEO_PROFILE_H264_CONSTRAINED_BASELINE;
|
||||
profile.profile[0].level = MMAL_VIDEO_LEVEL_H264_4; // Supports 1080p
|
||||
if ((error = mmal_port_parameter_set(enc->output_port, &profile.hdr)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't set MMAL_PARAMETER_PROFILE for the output port");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
SET_PORT_PARAM(output, boolean, ZERO_COPY, MMAL_TRUE);
|
||||
SET_PORT_PARAM(output, uint32, INTRAPERIOD, enc->gop);
|
||||
SET_PORT_PARAM(output, uint32, NALUNITFORMAT, MMAL_VIDEO_NALUNITFORMAT_STARTCODES);
|
||||
SET_PORT_PARAM(output, boolean, MINIMISE_FRAGMENTATION, MMAL_TRUE);
|
||||
SET_PORT_PARAM(output, uint32, MB_ROWS_PER_SLICE, 0);
|
||||
SET_PORT_PARAM(output, boolean, VIDEO_IMMUTABLE_INPUT, MMAL_TRUE);
|
||||
SET_PORT_PARAM(output, boolean, VIDEO_DROPPABLE_PFRAMES, MMAL_FALSE);
|
||||
SET_PORT_PARAM(output, boolean, VIDEO_ENCODE_INLINE_HEADER, MMAL_TRUE); // SPS/PPS: https://github.com/raspberrypi/userland/issues/443
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_BIT_RATE, enc->bitrate * 1000);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_PEAK_RATE, enc->bitrate * 1000);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_MIN_QUANT, 16);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_MAX_QUANT, 34);
|
||||
// Этот параметр с этим значением фризит кодирование изображения из черно-белой консоли
|
||||
// SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_FRAME_LIMIT_BITS, 1000000);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_H264_AU_DELIMITERS, MMAL_FALSE);
|
||||
}
|
||||
|
||||
ENABLE_PORT(input);
|
||||
ENABLE_PORT(output);
|
||||
|
||||
enc->ready = true;
|
||||
return 0;
|
||||
|
||||
error:
|
||||
_h264_encoder_cleanup(enc);
|
||||
LOG_ERROR("H264: Encoder destroyed due an error (prepare)");
|
||||
return -1;
|
||||
|
||||
# undef ENABLE_PORT
|
||||
# undef SET_PORT_PARAM
|
||||
# undef COMMIT_PORT
|
||||
# undef PREPARE_PORT
|
||||
}
|
||||
|
||||
static void _h264_encoder_cleanup(h264_encoder_s *enc) {
|
||||
MMAL_STATUS_T error;
|
||||
|
||||
# define DISABLE_PORT(_id) { \
|
||||
if (enc->_id##_port) { \
|
||||
if ((error = mmal_wrapper_port_disable(enc->_id##_port)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "H264: Can't disable MMAL %s port", #_id); \
|
||||
} \
|
||||
enc->_id##_port = NULL; \
|
||||
} \
|
||||
}
|
||||
|
||||
DISABLE_PORT(input);
|
||||
DISABLE_PORT(output);
|
||||
|
||||
# undef DISABLE_PORT
|
||||
|
||||
if (enc->wrapper) {
|
||||
enc->wrapper->status = MMAL_SUCCESS; // Это реально надо?
|
||||
}
|
||||
|
||||
enc->last_online = -1;
|
||||
enc->ready = false;
|
||||
}
|
||||
|
||||
int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key) {
|
||||
assert(enc->ready);
|
||||
assert(src->used > 0);
|
||||
assert(enc->width == src->width);
|
||||
assert(enc->height == src->height);
|
||||
assert(enc->format == src->format);
|
||||
assert(enc->stride == src->stride);
|
||||
|
||||
frame_copy_meta(src, dest);
|
||||
dest->encode_begin_ts = get_now_monotonic();
|
||||
dest->format = V4L2_PIX_FMT_H264;
|
||||
dest->stride = 0;
|
||||
|
||||
force_key = (force_key || enc->last_online != src->online);
|
||||
|
||||
if (_h264_encoder_compress_raw(enc, src, src_vcsm_handle, dest, force_key) < 0) {
|
||||
_h264_encoder_cleanup(enc);
|
||||
LOG_ERROR("H264: Encoder destroyed due an error (compress)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dest->encode_end_ts = get_now_monotonic();
|
||||
LOG_VERBOSE("H264: Compressed new frame: size=%zu, time=%0.3Lf, force_key=%d",
|
||||
dest->used, dest->encode_end_ts - dest->encode_begin_ts, force_key);
|
||||
|
||||
enc->last_online = src->online;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _h264_encoder_compress_raw(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key) {
|
||||
LOG_DEBUG("H264: Compressing new frame; force_key=%d ...", force_key);
|
||||
|
||||
MMAL_STATUS_T error;
|
||||
|
||||
if (force_key) {
|
||||
if ((error = mmal_port_parameter_set_boolean(
|
||||
enc->output_port,
|
||||
MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME,
|
||||
MMAL_TRUE
|
||||
)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't request keyframe");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
MMAL_BUFFER_HEADER_T *out = NULL;
|
||||
MMAL_BUFFER_HEADER_T *in = NULL;
|
||||
bool eos = false;
|
||||
bool sent = false;
|
||||
|
||||
dest->used = 0;
|
||||
|
||||
while (!eos) {
|
||||
out = NULL;
|
||||
while (mmal_wrapper_buffer_get_empty(enc->output_port, &out, 0) == MMAL_SUCCESS) {
|
||||
if ((error = mmal_port_send_buffer(enc->output_port, out)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't send MMAL output buffer");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
in = NULL;
|
||||
if (!sent && mmal_wrapper_buffer_get_empty(enc->input_port, &in, 0) == MMAL_SUCCESS) {
|
||||
if (enc->zero_copy && src_vcsm_handle > 0) {
|
||||
in->data = (uint8_t *)vcsm_vc_hdl_from_hdl(src_vcsm_handle);
|
||||
} else {
|
||||
in->data = src->data;
|
||||
}
|
||||
in->alloc_size = src->used;
|
||||
in->length = src->used;
|
||||
in->offset = 0;
|
||||
in->flags = MMAL_BUFFER_HEADER_FLAG_EOS;
|
||||
if ((error = mmal_port_send_buffer(enc->input_port, in)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't send MMAL input buffer");
|
||||
return -1;
|
||||
}
|
||||
sent = true;
|
||||
}
|
||||
|
||||
error = mmal_wrapper_buffer_get_full(enc->output_port, &out, 0);
|
||||
if (error == MMAL_EAGAIN) {
|
||||
if (vcos_my_semwait("H264: ", &enc->handler_sem, 1) < 0) {
|
||||
return -1;
|
||||
}
|
||||
continue;
|
||||
} else if (error != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't get MMAL output buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame_append_data(dest, out->data, out->length);
|
||||
dest->key = out->flags & MMAL_BUFFER_HEADER_FLAG_KEYFRAME;
|
||||
|
||||
eos = out->flags & MMAL_BUFFER_HEADER_FLAG_EOS;
|
||||
mmal_buffer_header_release(out);
|
||||
}
|
||||
|
||||
if ((error = mmal_port_flush(enc->output_port)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't flush MMAL output buffer; ignored");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _mmal_callback(MMAL_WRAPPER_T *wrapper) {
|
||||
vcos_semaphore_post(&((h264_encoder_s *)(wrapper->user_data))->handler_sem);
|
||||
}
|
||||
|
||||
static const char *_mmal_error_to_string(MMAL_STATUS_T error) {
|
||||
// http://www.jvcref.com/files/PI/documentation/html/group___mmal_types.html
|
||||
# define CASE_ERROR(_name, _msg) case MMAL_##_name: return "MMAL_" #_name " [" _msg "]"
|
||||
switch (error) {
|
||||
case MMAL_SUCCESS: return "MMAL_SUCCESS";
|
||||
CASE_ERROR(ENOMEM, "Out of memory");
|
||||
CASE_ERROR(ENOSPC, "Out of resources");
|
||||
CASE_ERROR(EINVAL, "Invalid argument");
|
||||
CASE_ERROR(ENOSYS, "Function not implemented");
|
||||
CASE_ERROR(ENOENT, "No such file or directory");
|
||||
CASE_ERROR(ENXIO, "No such device or address");
|
||||
CASE_ERROR(EIO, "IO error");
|
||||
CASE_ERROR(ESPIPE, "Illegal seek");
|
||||
CASE_ERROR(ECORRUPT, "Data is corrupt");
|
||||
CASE_ERROR(ENOTREADY, "Component is not ready");
|
||||
CASE_ERROR(ECONFIG, "Component is not configured");
|
||||
CASE_ERROR(EISCONN, "Port is already connected");
|
||||
CASE_ERROR(ENOTCONN, "Port is disconnected");
|
||||
CASE_ERROR(EAGAIN, "Resource temporarily unavailable");
|
||||
CASE_ERROR(EFAULT, "Bad address");
|
||||
case MMAL_STATUS_MAX: break; // Makes cpplint happy
|
||||
}
|
||||
return "Unknown error";
|
||||
# undef CASE_ERROR
|
||||
}
|
||||
|
||||
#undef LOG_ERROR_MMAL
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -23,74 +23,42 @@
|
||||
#include "stream.h"
|
||||
|
||||
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop) {
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, const char *path, unsigned bitrate, unsigned gop) {
|
||||
h264_stream_s *h264;
|
||||
A_CALLOC(h264, 1);
|
||||
h264->sink = sink;
|
||||
h264->tmp_src = frame_init();
|
||||
h264->dest = frame_init();
|
||||
atomic_init(&h264->online, false);
|
||||
|
||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100
|
||||
// По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480
|
||||
// енкодер через несколько секунд перестает производить корректные фреймы.
|
||||
if ((h264->enc = h264_encoder_init(bitrate, gop, 30)) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
h264->enc = m2m_h264_encoder_init("H264", path, bitrate, gop);
|
||||
return h264;
|
||||
|
||||
error:
|
||||
h264_stream_destroy(h264);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void h264_stream_destroy(h264_stream_s *h264) {
|
||||
if (h264->enc) {
|
||||
h264_encoder_destroy(h264->enc);
|
||||
}
|
||||
m2m_encoder_destroy(h264->enc);
|
||||
frame_destroy(h264->dest);
|
||||
frame_destroy(h264->tmp_src);
|
||||
free(h264);
|
||||
}
|
||||
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_handle, bool force_key) {
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, bool force_key) {
|
||||
if (!memsink_server_check(h264->sink, frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
long double now = get_now_monotonic();
|
||||
bool zero_copy = false;
|
||||
|
||||
if (is_jpeg(frame->format)) {
|
||||
assert(vcsm_handle <= 0);
|
||||
long double now = get_now_monotonic();
|
||||
LOG_DEBUG("H264: Input frame is JPEG; decoding ...");
|
||||
if (unjpeg(frame, h264->tmp_src, true) < 0) {
|
||||
return;
|
||||
}
|
||||
frame = h264->tmp_src;
|
||||
LOG_VERBOSE("H264: JPEG decoded; time=%.3Lf", get_now_monotonic() - now);
|
||||
} else if (vcsm_handle > 0) {
|
||||
LOG_DEBUG("H264: Zero-copy available for the input");
|
||||
zero_copy = true;
|
||||
} else {
|
||||
LOG_DEBUG("H264: Copying source to tmp buffer ...");
|
||||
frame_copy(frame, h264->tmp_src);
|
||||
frame = h264->tmp_src;
|
||||
LOG_VERBOSE("H264: Source copied; time=%.3Lf", get_now_monotonic() - now);
|
||||
}
|
||||
|
||||
bool online = false;
|
||||
|
||||
if (!h264_encoder_is_prepared_for(h264->enc, frame, zero_copy)) {
|
||||
h264_encoder_prepare(h264->enc, frame, zero_copy);
|
||||
if (!m2m_encoder_compress(h264->enc, frame, h264->dest, force_key)) {
|
||||
online = !memsink_server_put(h264->sink, h264->dest);
|
||||
}
|
||||
|
||||
if (h264->enc->ready) {
|
||||
if (h264_encoder_compress(h264->enc, frame, vcsm_handle, h264->dest, force_key) == 0) {
|
||||
online = !memsink_server_put(h264->sink, h264->dest);
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store(&h264->online, online);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -31,19 +31,18 @@
|
||||
#include "../../libs/frame.h"
|
||||
#include "../../libs/memsink.h"
|
||||
#include "../../libs/unjpeg.h"
|
||||
|
||||
#include "encoder.h"
|
||||
#include "../m2m.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
memsink_s *sink;
|
||||
frame_s *tmp_src;
|
||||
frame_s *dest;
|
||||
h264_encoder_s *enc;
|
||||
m2m_encoder_s *enc;
|
||||
atomic_bool online;
|
||||
} h264_stream_s;
|
||||
|
||||
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop);
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, const char *path, unsigned bitrate, unsigned gop);
|
||||
void h264_stream_destroy(h264_stream_s *h264);
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_handle, bool force_key);
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, bool force_key);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
static int _http_preprocess_request(struct evhttp_request *request, server_s *server);
|
||||
|
||||
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server);
|
||||
|
||||
static void _http_callback_root(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_static(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_state(struct evhttp_request *request, void *v_server);
|
||||
@@ -34,7 +36,8 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_ctx);
|
||||
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
|
||||
|
||||
static void _http_exposed_refresh(int fd, short event, void *v_server);
|
||||
static void _http_request_watcher(int fd, short event, void *v_server);
|
||||
static void _http_refresher(int fd, short event, void *v_server);
|
||||
static void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated);
|
||||
|
||||
static bool _expose_new_frame(server_s *server);
|
||||
@@ -78,14 +81,19 @@ server_s *server_init(stream_s *stream) {
|
||||
}
|
||||
|
||||
void server_destroy(server_s *server) {
|
||||
if (RUN(refresh)) {
|
||||
event_del(RUN(refresh));
|
||||
event_free(RUN(refresh));
|
||||
if (RUN(refresher)) {
|
||||
event_del(RUN(refresher));
|
||||
event_free(RUN(refresher));
|
||||
}
|
||||
|
||||
if (RUN(request_watcher)) {
|
||||
event_del(RUN(request_watcher));
|
||||
event_free(RUN(request_watcher));
|
||||
}
|
||||
|
||||
evhttp_free(RUN(http));
|
||||
if (RUN(unix_fd)) {
|
||||
close(RUN(unix_fd));
|
||||
if (RUN(ext_fd)) {
|
||||
close(RUN(ext_fd));
|
||||
}
|
||||
event_base_free(RUN(base));
|
||||
|
||||
@@ -126,18 +134,23 @@ int server_listen(server_s *server) {
|
||||
EX(notify_last_width) = EX(frame->width);
|
||||
EX(notify_last_height) = EX(frame->height);
|
||||
|
||||
if (server->exit_on_no_clients > 0) {
|
||||
RUN(last_request_ts) = get_now_monotonic();
|
||||
struct timeval interval = {0};
|
||||
interval.tv_usec = 100000;
|
||||
assert((RUN(request_watcher) = event_new(RUN(base), -1, EV_PERSIST, _http_request_watcher, server)));
|
||||
assert(!event_add(RUN(request_watcher), &interval));
|
||||
}
|
||||
|
||||
{
|
||||
struct timeval refresh_interval;
|
||||
|
||||
refresh_interval.tv_sec = 0;
|
||||
struct timeval interval = {0};
|
||||
if (STREAM(dev->desired_fps) > 0) {
|
||||
refresh_interval.tv_usec = 1000000 / (STREAM(dev->desired_fps) * 2);
|
||||
interval.tv_usec = 1000000 / (STREAM(dev->desired_fps) * 2);
|
||||
} else {
|
||||
refresh_interval.tv_usec = 16000; // ~60fps
|
||||
interval.tv_usec = 16000; // ~60fps
|
||||
}
|
||||
|
||||
assert((RUN(refresh) = event_new(RUN(base), -1, EV_PERSIST, _http_exposed_refresh, server)));
|
||||
assert(!event_add(RUN(refresh), &refresh_interval));
|
||||
assert((RUN(refresher) = event_new(RUN(base), -1, EV_PERSIST, _http_refresher, server)));
|
||||
assert(!event_add(RUN(refresher), &interval));
|
||||
}
|
||||
|
||||
evhttp_set_timeout(RUN(http), server->timeout);
|
||||
@@ -158,7 +171,7 @@ int server_listen(server_s *server) {
|
||||
|
||||
if (server->unix_path[0] != '\0') {
|
||||
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
||||
if ((RUN(unix_fd) = evhttp_my_bind_unix(
|
||||
if ((RUN(ext_fd) = evhttp_my_bind_unix(
|
||||
RUN(http),
|
||||
server->unix_path,
|
||||
server->unix_rm,
|
||||
@@ -167,9 +180,16 @@ int server_listen(server_s *server) {
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path);
|
||||
if (server->tcp_nodelay) {
|
||||
LOG_ERROR("TCP_NODELAY flag can't be used with UNIX socket and will be ignored");
|
||||
|
||||
# ifdef WITH_SYSTEMD
|
||||
} else if (server->systemd) {
|
||||
LOG_DEBUG("Binding HTTP to systemd socket ...");
|
||||
if ((RUN(ext_fd) = evhttp_my_bind_systemd(RUN(http))) < 0) {
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO("Listening systemd socket ...");
|
||||
# endif
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
||||
if (evhttp_bind_socket(RUN(http), server->host, server->port) < 0) {
|
||||
@@ -196,6 +216,8 @@ void server_loop_break(server_s *server) {
|
||||
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
||||
|
||||
static int _http_preprocess_request(struct evhttp_request *request, server_s *server) {
|
||||
RUN(last_request_ts) = get_now_monotonic();
|
||||
|
||||
if (RUN(auth_token)) {
|
||||
const char *token = evhttp_find_header(evhttp_request_get_input_headers(request), "Authorization");
|
||||
|
||||
@@ -220,41 +242,63 @@ static int _http_preprocess_request(struct evhttp_request *request, server_s *se
|
||||
} \
|
||||
}
|
||||
|
||||
static void _http_callback_root(struct evhttp_request *request, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server) {
|
||||
// MJPG-Streamer compatibility layer
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
struct evkeyvalq params;
|
||||
int error = 0;
|
||||
|
||||
struct evkeyvalq params; // For mjpg-streamer compatibility
|
||||
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||
const char *action = evhttp_find_header(¶ms, "action");
|
||||
|
||||
if (action && !strcmp(action, "snapshot")) {
|
||||
_http_callback_snapshot(request, v_server);
|
||||
goto ok;
|
||||
} else if (action && !strcmp(action, "stream")) {
|
||||
_http_callback_stream(request, v_server);
|
||||
} else {
|
||||
struct evbuffer *buf;
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
|
||||
ADD_HEADER("Content-Type", "text/html");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
evbuffer_free(buf);
|
||||
goto ok;
|
||||
}
|
||||
|
||||
evhttp_clear_headers(¶ms);
|
||||
error = -1;
|
||||
ok:
|
||||
evhttp_clear_headers(¶ms);
|
||||
return error;
|
||||
}
|
||||
|
||||
#define COMPAT_REQUEST { \
|
||||
if (_http_check_run_compat_action(request, v_server) == 0) { \
|
||||
return; \
|
||||
} \
|
||||
}
|
||||
|
||||
static void _http_callback_root(struct evhttp_request *request, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
COMPAT_REQUEST;
|
||||
|
||||
struct evbuffer *buf;
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
|
||||
ADD_HEADER("Content-Type", "text/html");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
COMPAT_REQUEST;
|
||||
|
||||
struct evbuffer *buf = NULL;
|
||||
struct evhttp_uri *uri = NULL;
|
||||
char *decoded_path = NULL;
|
||||
char *static_path = NULL;
|
||||
int fd = -1;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
|
||||
{
|
||||
char *uri_path;
|
||||
|
||||
@@ -293,6 +337,10 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
goto not_found;
|
||||
}
|
||||
|
||||
// evbuffer_add_file() owns the resulting file descriptor
|
||||
// and will close it when finished transferring data
|
||||
fd = -1;
|
||||
|
||||
ADD_HEADER("Content-Type", guess_mime_type(static_path));
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
goto cleanup;
|
||||
@@ -324,6 +372,8 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
}
|
||||
}
|
||||
|
||||
#undef COMPAT_REQUEST
|
||||
|
||||
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
|
||||
@@ -343,7 +393,6 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
enc_quality
|
||||
));
|
||||
|
||||
# ifdef WITH_OMX
|
||||
if (STREAM(run->h264)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s},",
|
||||
@@ -352,14 +401,8 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
bool_to_string(atomic_load(&STREAM(run->h264->online)))
|
||||
));
|
||||
}
|
||||
# endif
|
||||
|
||||
if (
|
||||
STREAM(sink)
|
||||
# ifdef WITH_OMX
|
||||
|| STREAM(h264_sink)
|
||||
# endif
|
||||
) {
|
||||
if (STREAM(sink) || STREAM(h264_sink)) {
|
||||
assert(evbuffer_add_printf(buf, " \"sinks\": {"));
|
||||
if (STREAM(sink)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
@@ -367,7 +410,6 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
bool_to_string(atomic_load(&STREAM(sink->has_clients)))
|
||||
));
|
||||
}
|
||||
# ifdef WITH_OMX
|
||||
if (STREAM(h264_sink)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"%s\"h264\": {\"has_clients\": %s}",
|
||||
@@ -375,7 +417,6 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
bool_to_string(atomic_load(&STREAM(h264_sink->has_clients)))
|
||||
));
|
||||
}
|
||||
# endif
|
||||
assert(evbuffer_add_printf(buf, "},"));
|
||||
}
|
||||
|
||||
@@ -395,13 +436,14 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
LIST_ITERATE(RUN(stream_clients), client, {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
|
||||
" \"dual_final_frames\": %s, \"zero_data\": %s}%s",
|
||||
" \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s",
|
||||
client->id,
|
||||
client->fps,
|
||||
bool_to_string(client->extra_headers),
|
||||
bool_to_string(client->advance_headers),
|
||||
bool_to_string(client->dual_final_frames),
|
||||
bool_to_string(client->zero_data),
|
||||
(client->key != NULL ? client->key : "0"),
|
||||
(client->next ? ", " : "")
|
||||
));
|
||||
});
|
||||
@@ -515,7 +557,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
client->hostport, client->id, RUN(stream_clients_count));
|
||||
|
||||
struct bufferevent *buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
if (server->tcp_nodelay && !RUN(unix_fd)) {
|
||||
if (server->tcp_nodelay && !RUN(ext_fd)) {
|
||||
evutil_socket_t fd;
|
||||
int on = 1;
|
||||
|
||||
@@ -565,7 +607,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
// будущего фрейма сразу после данных текущего, чтобы триггернуть отрисовку.
|
||||
// Естественным следствием этого является невозможность установки заголовка
|
||||
// Content-Length, так как предсказывать будущее мы еще не научились.
|
||||
// Его наличие не требуется RFC, однако никаких стандартов на MJPG over HTTP
|
||||
// Его наличие не требуется RFC, однако никаких стандартов на MJPEG over HTTP
|
||||
// в природе не существует, и никто не может гарантировать, что отсутствие
|
||||
// Content-Length не сломает вещание для каких-нибудь маргинальных браузеров.
|
||||
//
|
||||
@@ -743,7 +785,21 @@ static void _http_queue_send_stream(server_s *server, bool stream_updated, bool
|
||||
}
|
||||
}
|
||||
|
||||
static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||
static void _http_request_watcher(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
const long double now = get_now_monotonic();
|
||||
|
||||
if (stream_has_clients(RUN(stream))) {
|
||||
RUN(last_request_ts) = now;
|
||||
} else if (RUN(last_request_ts) + server->exit_on_no_clients < now) {
|
||||
LOG_INFO("HTTP: No requests or HTTP/sink clients found in last %u seconds, exiting ...",
|
||||
server->exit_on_no_clients);
|
||||
process_suicide();
|
||||
RUN(last_request_ts) = now;
|
||||
}
|
||||
}
|
||||
|
||||
static void _http_refresher(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
bool stream_updated = false;
|
||||
bool frame_updated = false;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -71,6 +71,9 @@
|
||||
#include "uri.h"
|
||||
#include "mime.h"
|
||||
#include "static.h"
|
||||
#ifdef WITH_SYSTEMD
|
||||
# include "systemd/systemd.h"
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct stream_client_sx {
|
||||
@@ -112,11 +115,17 @@ typedef struct {
|
||||
typedef struct {
|
||||
struct event_base *base;
|
||||
struct evhttp *http;
|
||||
evutil_socket_t unix_fd;
|
||||
evutil_socket_t ext_fd; // Unix or socket activation
|
||||
|
||||
char *auth_token;
|
||||
struct event *refresh;
|
||||
|
||||
struct event *request_watcher;
|
||||
long double last_request_ts;
|
||||
|
||||
struct event *refresher;
|
||||
stream_s *stream;
|
||||
exposed_s *exposed;
|
||||
|
||||
stream_client_s *stream_clients;
|
||||
unsigned stream_clients_count;
|
||||
} server_runtime_s;
|
||||
@@ -124,9 +133,15 @@ typedef struct {
|
||||
typedef struct server_sx {
|
||||
char *host;
|
||||
unsigned port;
|
||||
|
||||
char *unix_path;
|
||||
bool unix_rm;
|
||||
mode_t unix_mode;
|
||||
|
||||
# ifdef WITH_SYSTEMD
|
||||
bool systemd;
|
||||
# endif
|
||||
|
||||
bool tcp_nodelay;
|
||||
unsigned timeout;
|
||||
|
||||
@@ -140,6 +155,7 @@ typedef struct server_sx {
|
||||
unsigned fake_height;
|
||||
|
||||
bool notify_parent;
|
||||
unsigned exit_on_no_clients;
|
||||
|
||||
server_runtime_s *run;
|
||||
} server_s;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -20,23 +20,27 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <IL/OMX_IVCommon.h>
|
||||
#include <IL/OMX_Core.h>
|
||||
#include <IL/OMX_Image.h>
|
||||
|
||||
#include "../../../libs/tools.h"
|
||||
#include "../../../libs/logging.h"
|
||||
#include "systemd.h"
|
||||
|
||||
|
||||
#define LOG_ERROR_OMX(_error, _msg, ...) { \
|
||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, omx_error_to_string(_error)); \
|
||||
evutil_socket_t evhttp_my_bind_systemd(struct evhttp *http) {
|
||||
int fds = sd_listen_fds(1);
|
||||
if (fds < 1) {
|
||||
LOG_ERROR("No available systemd sockets");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fd;
|
||||
for (fd = 1; fd < fds; ++fd) {
|
||||
close(SD_LISTEN_FDS_START + fd);
|
||||
}
|
||||
fd = SD_LISTEN_FDS_START;
|
||||
|
||||
const char *omx_error_to_string(OMX_ERRORTYPE error);
|
||||
const char *omx_state_to_string(OMX_STATETYPE state);
|
||||
assert(!evutil_make_socket_nonblocking(fd));
|
||||
|
||||
if (evhttp_accept_socket(http, fd) < 0) {
|
||||
LOG_PERROR("Can't evhttp_accept_socket() systemd socket");
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -22,10 +22,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <interface/vcos/vcos_semaphore.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <event2/http.h>
|
||||
#include <event2/util.h>
|
||||
|
||||
#include <systemd/sd-daemon.h>
|
||||
|
||||
#include "../../../libs/tools.h"
|
||||
#include "../../../libs/logging.h"
|
||||
|
||||
|
||||
int vcos_my_semwait(const char *prefix, VCOS_SEMAPHORE_T *sem, long double timeout);
|
||||
evutil_socket_t evhttp_my_bind_systemd(struct evhttp *http);
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) {
|
||||
struct sockaddr_un addr;
|
||||
struct sockaddr_un addr = {0};
|
||||
|
||||
# define MAX_SUN_PATH (sizeof(addr.sun_path) - 1)
|
||||
|
||||
@@ -33,7 +33,6 @@ evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool
|
||||
return -1;
|
||||
}
|
||||
|
||||
MEMSET_ZERO(addr);
|
||||
strncpy(addr.sun_path, path, MAX_SUN_PATH);
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
511
src/ustreamer/m2m.c
Normal file
511
src/ustreamer/m2m.c
Normal file
@@ -0,0 +1,511 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 "m2m.h"
|
||||
|
||||
|
||||
static m2m_encoder_s *_m2m_encoder_init(
|
||||
const char *name, const char *path, unsigned output_format,
|
||||
unsigned fps, bool allow_dma, m2m_option_s *options);
|
||||
|
||||
static int _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame);
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
m2m_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma);
|
||||
|
||||
static void _m2m_encoder_cleanup(m2m_encoder_s *enc);
|
||||
|
||||
static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key);
|
||||
|
||||
|
||||
#define E_LOG_ERROR(_msg, ...) LOG_ERROR("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_PERROR(_msg, ...) LOG_PERROR("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_INFO(_msg, ...) LOG_INFO("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_VERBOSE(_msg, ...) LOG_VERBOSE("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_DEBUG(_msg, ...) LOG_DEBUG("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
|
||||
|
||||
m2m_encoder_s *m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop) {
|
||||
# define OPTION(_key, _value) {#_key, V4L2_CID_MPEG_VIDEO_##_key, _value}
|
||||
|
||||
m2m_option_s options[] = {
|
||||
OPTION(BITRATE, bitrate * 1000),
|
||||
// OPTION(BITRATE_PEAK, bitrate * 1000),
|
||||
OPTION(H264_I_PERIOD, gop),
|
||||
OPTION(H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE),
|
||||
OPTION(H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0),
|
||||
OPTION(REPEAT_SEQ_HEADER, 1),
|
||||
OPTION(H264_MIN_QP, 16),
|
||||
OPTION(H264_MAX_QP, 32),
|
||||
{NULL, 0, 0},
|
||||
};
|
||||
|
||||
# undef OPTION
|
||||
|
||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100
|
||||
// По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480
|
||||
// енкодер через несколько секунд перестает производить корректные фреймы.
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_H264, 30, true, options);
|
||||
}
|
||||
|
||||
m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
const double b_min = 25;
|
||||
const double b_max = 20000;
|
||||
const double step = 25;
|
||||
double bitrate = log10(quality) * (b_max - b_min) / 2 + b_min;
|
||||
bitrate = step * round(bitrate / step);
|
||||
bitrate *= 1000; // From Kbps
|
||||
assert(bitrate > 0);
|
||||
|
||||
m2m_option_s options[] = {
|
||||
{"BITRATE", V4L2_CID_MPEG_VIDEO_BITRATE, bitrate},
|
||||
{NULL, 0, 0},
|
||||
};
|
||||
|
||||
// FIXME: То же самое про 30 or 0, но еще даже не проверено на низких разрешениях
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, 30, true, options);
|
||||
}
|
||||
|
||||
m2m_encoder_s *m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
m2m_option_s options[] = {
|
||||
{"QUALITY", V4L2_CID_JPEG_COMPRESSION_QUALITY, quality},
|
||||
{NULL, 0, 0},
|
||||
};
|
||||
|
||||
// FIXME: DMA не работает
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_JPEG, 30, false, options);
|
||||
}
|
||||
|
||||
void m2m_encoder_destroy(m2m_encoder_s *enc) {
|
||||
E_LOG_INFO("Destroying encoder ...");
|
||||
_m2m_encoder_cleanup(enc);
|
||||
free(enc->options);
|
||||
free(enc->path);
|
||||
free(enc->name);
|
||||
free(enc);
|
||||
}
|
||||
|
||||
#define RUN(_next) enc->run->_next
|
||||
|
||||
int m2m_encoder_compress(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key) {
|
||||
frame_encoding_begin(src, dest, (enc->output_format == V4L2_PIX_FMT_MJPEG ? V4L2_PIX_FMT_JPEG : enc->output_format));
|
||||
|
||||
if (
|
||||
RUN(width) != src->width
|
||||
|| RUN(height) != src->height
|
||||
|| RUN(input_format) != src->format
|
||||
|| RUN(stride) != src->stride
|
||||
|| RUN(dma) != (enc->allow_dma && src->dma_fd >= 0)
|
||||
) {
|
||||
if (_m2m_encoder_prepare(enc, src) < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
force_key = (enc->output_format == V4L2_PIX_FMT_H264 && (force_key || RUN(last_online) != src->online));
|
||||
|
||||
if (_m2m_encoder_compress_raw(enc, src, dest, force_key) < 0) {
|
||||
_m2m_encoder_cleanup(enc);
|
||||
E_LOG_ERROR("Encoder destroyed due an error (compress)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame_encoding_end(dest);
|
||||
|
||||
E_LOG_VERBOSE("Compressed new frame: size=%zu, time=%0.3Lf, force_key=%d",
|
||||
dest->used, dest->encode_end_ts - dest->encode_begin_ts, force_key);
|
||||
|
||||
RUN(last_online) = src->online;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static m2m_encoder_s *_m2m_encoder_init(
|
||||
const char *name, const char *path, unsigned output_format,
|
||||
unsigned fps, bool allow_dma, m2m_option_s *options) {
|
||||
|
||||
LOG_INFO("%s: Initializing encoder ...", name);
|
||||
|
||||
m2m_encoder_runtime_s *run;
|
||||
A_CALLOC(run, 1);
|
||||
run->last_online = -1;
|
||||
run->fd = -1;
|
||||
|
||||
m2m_encoder_s *enc;
|
||||
A_CALLOC(enc, 1);
|
||||
assert(enc->name = strdup(name));
|
||||
if (path == NULL) {
|
||||
assert(enc->path = strdup(output_format == V4L2_PIX_FMT_JPEG ? "/dev/video31" : "/dev/video11"));
|
||||
} else {
|
||||
assert(enc->path = strdup(path));
|
||||
}
|
||||
enc->output_format = output_format;
|
||||
enc->fps = fps;
|
||||
enc->allow_dma = allow_dma;
|
||||
enc->run = run;
|
||||
|
||||
unsigned count = 0;
|
||||
for (; options[count].name != NULL; ++count);
|
||||
++count;
|
||||
A_CALLOC(enc->options, count);
|
||||
memcpy(enc->options, options, sizeof(m2m_option_s) * count);
|
||||
|
||||
return enc;
|
||||
}
|
||||
|
||||
#define E_XIOCTL(_request, _value, _msg, ...) { \
|
||||
if (xioctl(RUN(fd), _request, _value) < 0) { \
|
||||
E_LOG_PERROR(_msg, ##__VA_ARGS__); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
static int _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
bool dma = (enc->allow_dma && frame->dma_fd >= 0);
|
||||
|
||||
E_LOG_INFO("Configuring encoder: DMA=%d ...", dma);
|
||||
|
||||
_m2m_encoder_cleanup(enc);
|
||||
|
||||
RUN(width) = frame->width;
|
||||
RUN(height) = frame->height;
|
||||
RUN(input_format) = frame->format;
|
||||
RUN(stride) = frame->stride;
|
||||
RUN(dma) = dma;
|
||||
|
||||
if ((RUN(fd) = open(enc->path, O_RDWR)) < 0) {
|
||||
E_LOG_PERROR("Can't open encoder device");
|
||||
goto error;
|
||||
}
|
||||
E_LOG_DEBUG("Encoder device fd=%d opened", RUN(fd));
|
||||
|
||||
for (m2m_option_s *option = enc->options; option->name != NULL; ++option) {
|
||||
struct v4l2_control ctl = {0};
|
||||
ctl.id = option->id;
|
||||
ctl.value = option->value;
|
||||
|
||||
E_LOG_DEBUG("Configuring option %s ...", option->name);
|
||||
E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't set option %s", option->name);
|
||||
}
|
||||
|
||||
{
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
fmt.fmt.pix_mp.width = RUN(width);
|
||||
fmt.fmt.pix_mp.height = RUN(height);
|
||||
fmt.fmt.pix_mp.pixelformat = RUN(input_format);
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // libcamera currently has no means to request the right colour space
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = RUN(stride);
|
||||
E_LOG_DEBUG("Configuring INPUT format ...");
|
||||
E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set INPUT format");
|
||||
}
|
||||
|
||||
{
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
fmt.fmt.pix_mp.width = RUN(width);
|
||||
fmt.fmt.pix_mp.height = RUN(height);
|
||||
fmt.fmt.pix_mp.pixelformat = enc->output_format;
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT;
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0;
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10;
|
||||
E_LOG_DEBUG("Configuring OUTPUT format ...");
|
||||
E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
|
||||
if (fmt.fmt.pix_mp.pixelformat != enc->output_format) {
|
||||
char fourcc_str[8];
|
||||
E_LOG_ERROR("The OUTPUT format can't be configured as %s",
|
||||
fourcc_to_string(enc->output_format, fourcc_str, 8));
|
||||
E_LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (enc->fps > 0) { // TODO: Check this for MJPEG
|
||||
struct v4l2_streamparm setfps = {0};
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
setfps.parm.output.timeperframe.numerator = 1;
|
||||
setfps.parm.output.timeperframe.denominator = enc->fps;
|
||||
E_LOG_DEBUG("Configuring INPUT FPS ...");
|
||||
E_XIOCTL(VIDIOC_S_PARM, &setfps, "Can't set INPUT FPS");
|
||||
}
|
||||
|
||||
if (_m2m_encoder_init_buffers(enc, (dma ? "INPUT-DMA" : "INPUT"), V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
|
||||
&RUN(input_bufs), &RUN(n_input_bufs), dma) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_m2m_encoder_init_buffers(enc, "OUTPUT", V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
|
||||
&RUN(output_bufs), &RUN(n_output_bufs), false) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
{
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
E_LOG_DEBUG("Starting INPUT ...");
|
||||
E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start INPUT");
|
||||
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
E_LOG_DEBUG("Starting OUTPUT ...");
|
||||
E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start OUTPUT");
|
||||
}
|
||||
|
||||
RUN(ready) = true;
|
||||
E_LOG_DEBUG("Encoder state: *** READY ***");
|
||||
return 0;
|
||||
|
||||
error:
|
||||
_m2m_encoder_cleanup(enc);
|
||||
E_LOG_ERROR("Encoder destroyed due an error (prepare)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
m2m_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma) {
|
||||
|
||||
E_LOG_DEBUG("Initializing %s buffers ...", name);
|
||||
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = 1;
|
||||
req.type = type;
|
||||
req.memory = (dma ? V4L2_MEMORY_DMABUF : V4L2_MEMORY_MMAP);
|
||||
|
||||
E_LOG_DEBUG("Requesting %u %s buffers ...", req.count, name);
|
||||
E_XIOCTL(VIDIOC_REQBUFS, &req, "Can't request %s buffers", name);
|
||||
if (req.count < 1) {
|
||||
E_LOG_ERROR("Insufficient %s buffer memory: %u", name, req.count);
|
||||
goto error;
|
||||
}
|
||||
E_LOG_DEBUG("Got %u %s buffers", req.count, name);
|
||||
|
||||
if (dma) {
|
||||
*n_bufs_ptr = req.count;
|
||||
} else {
|
||||
A_CALLOC(*bufs_ptr, req.count);
|
||||
for (*n_bufs_ptr = 0; *n_bufs_ptr < req.count; ++(*n_bufs_ptr)) {
|
||||
struct v4l2_buffer buf = {0};
|
||||
struct v4l2_plane plane = {0};
|
||||
buf.type = type;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = *n_bufs_ptr;
|
||||
buf.length = 1;
|
||||
buf.m.planes = &plane;
|
||||
|
||||
E_LOG_DEBUG("Querying %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
E_XIOCTL(VIDIOC_QUERYBUF, &buf, "Can't query %s buffer=%u", name, *n_bufs_ptr);
|
||||
|
||||
E_LOG_DEBUG("Mapping %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
if (((*bufs_ptr)[*n_bufs_ptr].data = mmap(
|
||||
NULL,
|
||||
plane.length,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
RUN(fd),
|
||||
plane.m.mem_offset
|
||||
)) == MAP_FAILED) {
|
||||
E_LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr);
|
||||
goto error;
|
||||
}
|
||||
(*bufs_ptr)[*n_bufs_ptr].allocated = plane.length;
|
||||
|
||||
E_LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
E_XIOCTL(VIDIOC_QBUF, &buf, "Can't queue %s buffer=%u", name, *n_bufs_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void _m2m_encoder_cleanup(m2m_encoder_s *enc) {
|
||||
if (RUN(ready)) {
|
||||
# define STOP_STREAM(_name, _type) { \
|
||||
enum v4l2_buf_type _type_var = _type; \
|
||||
E_LOG_DEBUG("Stopping %s ...", _name); \
|
||||
if (xioctl(RUN(fd), VIDIOC_STREAMOFF, &_type_var) < 0) { \
|
||||
E_LOG_PERROR("Can't stop %s", _name); \
|
||||
} \
|
||||
}
|
||||
|
||||
STOP_STREAM("OUTPUT", V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
|
||||
STOP_STREAM("INPUT", V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
|
||||
|
||||
# undef STOP_STREAM
|
||||
}
|
||||
|
||||
# define DESTROY_BUFFERS(_name, _target) { \
|
||||
if (RUN(_target##_bufs)) { \
|
||||
for (unsigned index = 0; index < RUN(n_##_target##_bufs); ++index) { \
|
||||
if (RUN(_target##_bufs[index].allocated) > 0 && RUN(_target##_bufs[index].data) != MAP_FAILED) { \
|
||||
if (munmap(RUN(_target##_bufs[index].data), RUN(_target##_bufs[index].allocated)) < 0) { \
|
||||
E_LOG_PERROR("Can't unmap %s buffer=%u", #_name, index); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
free(RUN(_target##_bufs)); \
|
||||
RUN(_target##_bufs) = NULL; \
|
||||
} \
|
||||
RUN(n_##_target##_bufs) = 0; \
|
||||
}
|
||||
|
||||
DESTROY_BUFFERS("OUTPUT", output);
|
||||
DESTROY_BUFFERS("INPUT", input);
|
||||
|
||||
# undef DESTROY_BUFFERS
|
||||
|
||||
if (RUN(fd) >= 0) {
|
||||
if (close(RUN(fd)) < 0) {
|
||||
E_LOG_PERROR("Can't close encoder device");
|
||||
}
|
||||
RUN(fd) = -1;
|
||||
}
|
||||
|
||||
RUN(last_online) = -1;
|
||||
RUN(ready) = false;
|
||||
|
||||
E_LOG_DEBUG("Encoder state: ~~~ NOT READY ~~~");
|
||||
}
|
||||
|
||||
static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key) {
|
||||
assert(RUN(ready));
|
||||
|
||||
E_LOG_DEBUG("Compressing new frame; force_key=%d ...", force_key);
|
||||
|
||||
if (force_key) {
|
||||
struct v4l2_control ctl = {0};
|
||||
ctl.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME;
|
||||
ctl.value = 1;
|
||||
E_LOG_DEBUG("Forcing keyframe ...")
|
||||
E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe");
|
||||
}
|
||||
|
||||
struct v4l2_buffer input_buf = {0};
|
||||
struct v4l2_plane input_plane = {0};
|
||||
input_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
input_buf.length = 1;
|
||||
input_buf.m.planes = &input_plane;
|
||||
|
||||
if (RUN(dma)) {
|
||||
input_buf.index = 0;
|
||||
input_buf.memory = V4L2_MEMORY_DMABUF;
|
||||
input_buf.field = V4L2_FIELD_NONE;
|
||||
input_plane.m.fd = src->dma_fd;
|
||||
E_LOG_DEBUG("Using INPUT-DMA buffer=%u", input_buf.index);
|
||||
} else {
|
||||
input_buf.memory = V4L2_MEMORY_MMAP;
|
||||
E_LOG_DEBUG("Grabbing INPUT buffer ...");
|
||||
E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't grab INPUT buffer");
|
||||
if (input_buf.index >= RUN(n_input_bufs)) {
|
||||
E_LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u",
|
||||
input_buf.index, RUN(n_input_bufs));
|
||||
goto error;
|
||||
}
|
||||
E_LOG_DEBUG("Grabbed INPUT buffer=%u", input_buf.index);
|
||||
}
|
||||
|
||||
uint64_t now = get_now_monotonic_u64();
|
||||
struct timeval ts = {
|
||||
.tv_sec = now / 1000000,
|
||||
.tv_usec = now % 1000000,
|
||||
};
|
||||
|
||||
input_buf.timestamp.tv_sec = ts.tv_sec;
|
||||
input_buf.timestamp.tv_usec = ts.tv_usec;
|
||||
input_plane.bytesused = src->used;
|
||||
input_plane.length = src->used;
|
||||
if (!RUN(dma)) {
|
||||
memcpy(RUN(input_bufs[input_buf.index].data), src->data, src->used);
|
||||
}
|
||||
|
||||
const char *input_name = (RUN(dma) ? "INPUT-DMA" : "INPUT");
|
||||
|
||||
E_LOG_DEBUG("Sending%s %s buffer ...", (!RUN(dma) ? " (releasing)" : ""), input_name);
|
||||
E_XIOCTL(VIDIOC_QBUF, &input_buf, "Can't send %s buffer", input_name);
|
||||
|
||||
// Для не-DMA отправка буфера по факту являтся освобождением этого буфера
|
||||
bool input_released = !RUN(dma);
|
||||
|
||||
while (true) {
|
||||
struct pollfd enc_poll = {RUN(fd), POLLIN, 0};
|
||||
|
||||
E_LOG_DEBUG("Polling encoder ...");
|
||||
if (poll(&enc_poll, 1, 1000) < 0 && errno != EINTR) {
|
||||
E_LOG_PERROR("Can't poll encoder");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (enc_poll.revents & POLLIN) {
|
||||
if (!input_released) {
|
||||
E_LOG_DEBUG("Releasing %s buffer=%u ...", input_name, input_buf.index);
|
||||
E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't release %s buffer=%u",
|
||||
input_name, input_buf.index);
|
||||
input_released = true;
|
||||
}
|
||||
|
||||
struct v4l2_buffer output_buf = {0};
|
||||
struct v4l2_plane output_plane = {0};
|
||||
output_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
output_buf.memory = V4L2_MEMORY_MMAP;
|
||||
output_buf.length = 1;
|
||||
output_buf.m.planes = &output_plane;
|
||||
E_LOG_DEBUG("Fetching OUTPUT buffer ...");
|
||||
E_XIOCTL(VIDIOC_DQBUF, &output_buf, "Can't fetch OUTPUT buffer");
|
||||
|
||||
bool done = false;
|
||||
if (ts.tv_sec != output_buf.timestamp.tv_sec || ts.tv_usec != output_buf.timestamp.tv_usec) {
|
||||
// Енкодер первый раз может выдать буфер с мусором и нулевым таймстампом,
|
||||
// так что нужно убедиться, что мы читаем выходной буфер, соответствующий
|
||||
// входному (с тем же таймстампом).
|
||||
E_LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch");
|
||||
} else {
|
||||
frame_set_data(dest, RUN(output_bufs[output_buf.index].data), output_plane.bytesused);
|
||||
dest->key = output_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
|
||||
done = true;
|
||||
}
|
||||
|
||||
E_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", output_buf.index);
|
||||
E_XIOCTL(VIDIOC_QBUF, &output_buf, "Can't release OUTPUT buffer=%u", output_buf.index);
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef E_XIOCTL
|
||||
|
||||
#undef RUN
|
||||
|
||||
#undef E_LOG_DEBUG
|
||||
#undef E_LOG_VERBOSE
|
||||
#undef E_LOG_INFO
|
||||
#undef E_LOG_PERROR
|
||||
#undef E_LOG_ERROR
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -24,48 +24,68 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <interface/mmal/mmal.h>
|
||||
#include <interface/mmal/mmal_format.h>
|
||||
#include <interface/mmal/util/mmal_default_components.h>
|
||||
#include <interface/mmal/util/mmal_component_wrapper.h>
|
||||
#include <interface/mmal/util/mmal_util_params.h>
|
||||
#include <interface/vcsm/user-vcsm.h>
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
|
||||
#include "../../libs/tools.h"
|
||||
#include "../../libs/logging.h"
|
||||
#include "../../libs/frame.h"
|
||||
#include "../encoders/omx/vcos.h"
|
||||
#include "xioctl.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned bitrate; // Kbit-per-sec
|
||||
unsigned gop; // Interval between keyframes
|
||||
unsigned fps;
|
||||
uint8_t *data;
|
||||
size_t allocated;
|
||||
} m2m_buffer_s;
|
||||
|
||||
MMAL_WRAPPER_T *wrapper;
|
||||
MMAL_PORT_T *input_port;
|
||||
MMAL_PORT_T *output_port;
|
||||
VCOS_SEMAPHORE_T handler_sem;
|
||||
bool i_handler_sem;
|
||||
typedef struct {
|
||||
char *name;
|
||||
uint32_t id;
|
||||
int32_t value;
|
||||
} m2m_option_s;
|
||||
|
||||
int last_online;
|
||||
typedef struct {
|
||||
int fd;
|
||||
m2m_buffer_s *input_bufs;
|
||||
unsigned n_input_bufs;
|
||||
m2m_buffer_s *output_bufs;
|
||||
unsigned n_output_bufs;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned input_format;
|
||||
unsigned stride;
|
||||
bool zero_copy;
|
||||
bool dma;
|
||||
bool ready;
|
||||
} h264_encoder_s;
|
||||
|
||||
int last_online;
|
||||
} m2m_encoder_runtime_s;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *path;
|
||||
unsigned output_format;
|
||||
unsigned fps;
|
||||
bool allow_dma;
|
||||
m2m_option_s *options;
|
||||
|
||||
m2m_encoder_runtime_s *run;
|
||||
} m2m_encoder_s;
|
||||
|
||||
|
||||
h264_encoder_s *h264_encoder_init(unsigned bitrate, unsigned gop, unsigned fps);
|
||||
void h264_encoder_destroy(h264_encoder_s *enc);
|
||||
m2m_encoder_s *m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop);
|
||||
m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality);
|
||||
m2m_encoder_s *m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality);
|
||||
void m2m_encoder_destroy(m2m_encoder_s *enc);
|
||||
|
||||
bool h264_encoder_is_prepared_for(h264_encoder_s *enc, const frame_s *frame, bool zero_copy);
|
||||
int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_copy);
|
||||
int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key);
|
||||
int m2m_encoder_compress(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key);
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -35,10 +35,6 @@
|
||||
#include <signal.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#ifdef WITH_OMX
|
||||
# include <bcm_host.h>
|
||||
# include <IL/OMX_Core.h>
|
||||
#endif
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/threading.h"
|
||||
@@ -94,8 +90,7 @@ static void _signal_handler(int signum) {
|
||||
}
|
||||
|
||||
static void _install_signal_handlers(void) {
|
||||
struct sigaction sig_act;
|
||||
MEMSET_ZERO(sig_act);
|
||||
struct sigaction sig_act = {0};
|
||||
|
||||
assert(!sigemptyset(&sig_act.sa_mask));
|
||||
sig_act.sa_handler = _signal_handler;
|
||||
@@ -125,25 +120,7 @@ int main(int argc, char *argv[]) {
|
||||
stream_s *stream = stream_init(dev, enc);
|
||||
server_s *server = server_init(stream);
|
||||
|
||||
# ifdef WITH_OMX
|
||||
bool i_bcm_host = false;
|
||||
OMX_ERRORTYPE omx_error = OMX_ErrorUndefined;
|
||||
# endif
|
||||
|
||||
if ((exit_code = options_parse(options, dev, enc, stream, server)) == 0) {
|
||||
# ifdef WITH_OMX
|
||||
if (enc->type == ENCODER_TYPE_OMX || stream->h264_sink) {
|
||||
bcm_host_init();
|
||||
i_bcm_host = true;
|
||||
}
|
||||
if (enc->type == ENCODER_TYPE_OMX) {
|
||||
if ((omx_error = OMX_Init()) != OMX_ErrorNone) {
|
||||
LOG_ERROR_OMX(omx_error, "Can't initialize OMX Core; forced CPU encoder");
|
||||
enc->type = ENCODER_TYPE_CPU;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
gpio_init();
|
||||
# endif
|
||||
@@ -180,15 +157,6 @@ int main(int argc, char *argv[]) {
|
||||
device_destroy(dev);
|
||||
options_destroy(options);
|
||||
|
||||
# ifdef WITH_OMX
|
||||
if (omx_error == OMX_ErrorNone) {
|
||||
OMX_Deinit();
|
||||
}
|
||||
if (i_bcm_host) {
|
||||
bcm_host_deinit();
|
||||
}
|
||||
# endif
|
||||
|
||||
if (exit_code == 0) {
|
||||
LOG_INFO("Bye-bye");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -38,9 +38,7 @@ enum _OPT_VALUES {
|
||||
_O_WORKERS = 'w',
|
||||
_O_QUALITY = 'q',
|
||||
_O_ENCODER = 'c',
|
||||
# ifdef WITH_OMX
|
||||
_O_GLITCHED_RESOLUTIONS = 'g',
|
||||
# endif
|
||||
_O_GLITCHED_RESOLUTIONS = 'g', // Deprecated
|
||||
_O_BLANK = 'k',
|
||||
_O_LAST_AS_BLANK = 'K',
|
||||
_O_SLOWDOWN = 'l',
|
||||
@@ -50,6 +48,9 @@ enum _OPT_VALUES {
|
||||
_O_UNIX = 'U',
|
||||
_O_UNIX_RM = 'D',
|
||||
_O_UNIX_MODE = 'M',
|
||||
# ifdef WITH_SYSTEMD
|
||||
_O_SYSTEMD = 'S',
|
||||
# endif
|
||||
_O_DROP_SAME_FRAMES = 'e',
|
||||
_O_FAKE_RESOLUTION = 'R',
|
||||
|
||||
@@ -60,6 +61,7 @@ enum _OPT_VALUES {
|
||||
|
||||
_O_DEVICE_TIMEOUT = 10000,
|
||||
_O_DEVICE_ERROR_DELAY,
|
||||
_O_M2M_DEVICE,
|
||||
|
||||
_O_IMAGE_DEFAULT,
|
||||
_O_BRIGHTNESS,
|
||||
@@ -91,11 +93,10 @@ enum _OPT_VALUES {
|
||||
_O_##_prefix##_TIMEOUT,
|
||||
ADD_SINK(SINK)
|
||||
ADD_SINK(RAW_SINK)
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK(H264_SINK)
|
||||
_O_H264_BITRATE,
|
||||
_O_H264_GOP,
|
||||
# endif
|
||||
_O_H264_M2M_DEVICE,
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@@ -109,6 +110,7 @@ enum _OPT_VALUES {
|
||||
# ifdef HAS_PDEATHSIG
|
||||
_O_EXIT_ON_PARENT_DEATH,
|
||||
# endif
|
||||
_O_EXIT_ON_NO_CLIENTS,
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
_O_PROCESS_NAME_PREFIX,
|
||||
# endif
|
||||
@@ -139,14 +141,13 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"workers", required_argument, NULL, _O_WORKERS},
|
||||
{"quality", required_argument, NULL, _O_QUALITY},
|
||||
{"encoder", required_argument, NULL, _O_ENCODER},
|
||||
# ifdef WITH_OMX
|
||||
{"glitched-resolutions", required_argument, NULL, _O_GLITCHED_RESOLUTIONS},
|
||||
# endif
|
||||
{"glitched-resolutions", required_argument, NULL, _O_GLITCHED_RESOLUTIONS}, // Deprecated
|
||||
{"blank", required_argument, NULL, _O_BLANK},
|
||||
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
|
||||
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
|
||||
{"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT},
|
||||
{"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY},
|
||||
{"m2m-device", required_argument, NULL, _O_M2M_DEVICE},
|
||||
|
||||
{"image-default", no_argument, NULL, _O_IMAGE_DEFAULT},
|
||||
{"brightness", required_argument, NULL, _O_BRIGHTNESS},
|
||||
@@ -159,7 +160,7 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"white-balance", required_argument, NULL, _O_WHITE_BALANCE},
|
||||
{"gain", required_argument, NULL, _O_GAIN},
|
||||
{"color-effect", required_argument, NULL, _O_COLOR_EFFECT},
|
||||
{"rotate", required_argument, NULL, _O_ROTATE},
|
||||
{"rotate", required_argument, NULL, _O_ROTATE},
|
||||
{"flip-vertical", required_argument, NULL, _O_FLIP_VERTICAL},
|
||||
{"flip-horizontal", required_argument, NULL, _O_FLIP_HORIZONTAL},
|
||||
|
||||
@@ -168,6 +169,9 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"unix", required_argument, NULL, _O_UNIX},
|
||||
{"unix-rm", no_argument, NULL, _O_UNIX_RM},
|
||||
{"unix-mode", required_argument, NULL, _O_UNIX_MODE},
|
||||
# ifdef WITH_SYSTEMD
|
||||
{"systemd", no_argument, NULL, _O_SYSTEMD},
|
||||
# endif
|
||||
{"user", required_argument, NULL, _O_USER},
|
||||
{"passwd", required_argument, NULL, _O_PASSWD},
|
||||
{"static", required_argument, NULL, _O_STATIC},
|
||||
@@ -185,11 +189,10 @@ static const struct option _LONG_OPTS[] = {
|
||||
{_opt "sink-timeout", required_argument, NULL, _O_##_prefix##_TIMEOUT},
|
||||
ADD_SINK("", SINK)
|
||||
ADD_SINK("raw-", RAW_SINK)
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("h264-", H264_SINK)
|
||||
{"h264-bitrate", required_argument, NULL, _O_H264_BITRATE},
|
||||
{"h264-gop", required_argument, NULL, _O_H264_GOP},
|
||||
# endif
|
||||
{"h264-m2m-device", required_argument, NULL, _O_H264_M2M_DEVICE},
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@@ -203,6 +206,7 @@ static const struct option _LONG_OPTS[] = {
|
||||
# ifdef HAS_PDEATHSIG
|
||||
{"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH},
|
||||
# endif
|
||||
{"exit-on-no-clients", required_argument, NULL, _O_EXIT_ON_NO_CLIENTS},
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
{"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX},
|
||||
# endif
|
||||
@@ -250,9 +254,7 @@ void options_destroy(options_s *options) {
|
||||
}
|
||||
ADD_SINK(sink);
|
||||
ADD_SINK(raw_sink);
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK(h264_sink);
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
|
||||
if (options->blank) {
|
||||
@@ -345,9 +347,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
unsigned _prefix##_timeout = 1;
|
||||
ADD_SINK(sink);
|
||||
ADD_SINK(raw_sink);
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK(h264_sink);
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
@@ -376,14 +376,13 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
case _O_WORKERS: OPT_NUMBER("--workers", enc->n_workers, 1, 32, 0);
|
||||
case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0);
|
||||
case _O_ENCODER: OPT_PARSE("encoder type", enc->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
|
||||
# ifdef WITH_OMX
|
||||
case _O_GLITCHED_RESOLUTIONS: break;
|
||||
# endif
|
||||
case _O_GLITCHED_RESOLUTIONS: break; // Deprecated
|
||||
case _O_BLANK: OPT_SET(blank_path, optarg);
|
||||
case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", stream->last_as_blank, 0, 86400, 0);
|
||||
case _O_SLOWDOWN: OPT_SET(stream->slowdown, true);
|
||||
case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0);
|
||||
case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", stream->error_delay, 1, 60, 0);
|
||||
case _O_M2M_DEVICE: OPT_SET(enc->m2m_path, optarg);
|
||||
|
||||
case _O_IMAGE_DEFAULT:
|
||||
OPT_CTL_DEFAULT_NOBREAK(brightness);
|
||||
@@ -419,6 +418,9 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
case _O_UNIX: OPT_SET(server->unix_path, optarg);
|
||||
case _O_UNIX_RM: OPT_SET(server->unix_rm, true);
|
||||
case _O_UNIX_MODE: OPT_NUMBER("--unix-mode", server->unix_mode, INT_MIN, INT_MAX, 8);
|
||||
# ifdef WITH_SYSTEMD
|
||||
case _O_SYSTEMD: OPT_SET(server->systemd, true);
|
||||
# endif
|
||||
case _O_USER: OPT_SET(server->user, optarg);
|
||||
case _O_PASSWD: OPT_SET(server->passwd, optarg);
|
||||
case _O_STATIC: OPT_SET(server->static_path, optarg);
|
||||
@@ -436,11 +438,10 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
case _O_##_up##_TIMEOUT: OPT_NUMBER("--" #_opt "sink-timeout", _lp##_timeout, 1, 60, 0);
|
||||
ADD_SINK("", sink, SINK)
|
||||
ADD_SINK("raw-", raw_sink, RAW_SINK)
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("h264-", h264_sink, H264_SINK)
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 100, 16000, 0);
|
||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||
# endif
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 20000, 0);
|
||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@@ -458,6 +459,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
};
|
||||
break;
|
||||
# endif
|
||||
case _O_EXIT_ON_NO_CLIENTS: OPT_NUMBER("--exit-on-no-clients", server->exit_on_no_clients, 0, 86400, 0);
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg);
|
||||
# endif
|
||||
@@ -498,9 +500,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
}
|
||||
ADD_SINK("JPEG", sink);
|
||||
ADD_SINK("RAW", raw_sink);
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("H264", h264_sink);
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
@@ -539,18 +539,18 @@ static int _parse_resolution(const char *str, unsigned *width, unsigned *height,
|
||||
}
|
||||
|
||||
static void _features(void) {
|
||||
# ifdef WITH_OMX
|
||||
puts("+ WITH_OMX");
|
||||
# else
|
||||
puts("- WITH_OMX");
|
||||
# endif
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
puts("+ WITH_GPIO");
|
||||
# else
|
||||
puts("- WITH_GPIO");
|
||||
# endif
|
||||
|
||||
# ifdef WITH_SYSTEMD
|
||||
puts("+ WITH_SYSTEMD");
|
||||
# else
|
||||
puts("- WITH_SYSTEMD");
|
||||
# endif
|
||||
|
||||
# ifdef WITH_PTHREAD_NP
|
||||
puts("+ WITH_PTHREAD_NP");
|
||||
# else
|
||||
@@ -572,10 +572,10 @@ static void _features(void) {
|
||||
|
||||
static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server) {
|
||||
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer - Lightweight and fast MJPG-HTTP streamer");
|
||||
SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer");
|
||||
SAY("═══════════════════════════════════════════════════");
|
||||
SAY("Version: %s; license: GPLv3", VERSION);
|
||||
SAY("Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Capturing options:");
|
||||
SAY("══════════════════");
|
||||
SAY(" -d|--device </dev/path> ───────────── Path to V4L2 device. Default: %s.\n", dev->path);
|
||||
@@ -592,7 +592,7 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device");
|
||||
SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", dev->min_frame_size);
|
||||
SAY(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. Default: disabled.\n");
|
||||
SAY(" -t|--dv-timings ───────────────────── Enable DV timings querying and events processing");
|
||||
SAY(" -t|--dv-timings ───────────────────── Enable DV-timings querying and events processing");
|
||||
SAY(" to automatic resolution change. Default: disabled.\n");
|
||||
SAY(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device.");
|
||||
SAY(" Each buffer may processed using an independent thread.");
|
||||
@@ -603,19 +603,16 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" Note: If HW encoding is used (JPEG source format selected),");
|
||||
SAY(" this parameter attempts to configure the camera");
|
||||
SAY(" or capture device hardware's internal encoder.");
|
||||
SAY(" It does not re-encode MJPG to MJPG to change the quality level");
|
||||
SAY(" for sources that already output MJPG.\n");
|
||||
SAY(" It does not re-encode MJPEG to MJPEG to change the quality level");
|
||||
SAY(" for sources that already output MJPEG.\n");
|
||||
SAY(" -c|--encoder <type> ───────────────── Use specified encoder. It may affect the number of workers.");
|
||||
SAY(" Available:");
|
||||
SAY(" * CPU ── Software MJPG encoding (default);");
|
||||
# ifdef WITH_OMX
|
||||
SAY(" * OMX ── GPU hardware accelerated MJPG encoding with OpenMax;");
|
||||
# endif
|
||||
SAY(" * HW ─── Use pre-encoded MJPG frames directly from camera hardware.");
|
||||
SAY(" * NOOP ─ Don't compress MJPG stream (do nothing).\n");
|
||||
# ifdef WITH_OMX
|
||||
SAY(" * CPU ──────── Software MJPEG encoding (default);");
|
||||
SAY(" * HW ───────── Use pre-encoded MJPEG frames directly from camera hardware;");
|
||||
SAY(" * M2M-VIDEO ── GPU-accelerated MJPEG encoding using V4L2 M2M video interface;");
|
||||
SAY(" * M2M-IMAGE ── GPU-accelerated JPEG encoding using V4L2 M2M image interface;");
|
||||
SAY(" * NOOP ─────── Don't compress MJPEG stream (do nothing).\n");
|
||||
SAY(" -g|--glitched-resolutions <WxH,...> ─ It doesn't do anything. Still here for compatibility.\n");
|
||||
# endif
|
||||
SAY(" -k|--blank <path> ─────────────────── Path to JPEG file that will be shown when the device is disconnected");
|
||||
SAY(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n");
|
||||
SAY(" -K|--last-as-blank <sec> ──────────── Show the last frame received from the camera after it was disconnected,");
|
||||
@@ -628,6 +625,7 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" --device-timeout <sec> ────────────── Timeout for device querying. Default: %u.\n", dev->timeout);
|
||||
SAY(" --device-error-delay <sec> ────────── Delay before trying to connect to the device again");
|
||||
SAY(" after an error (timeout for example). Default: %u.\n", stream->error_delay);
|
||||
SAY(" --m2m-device </dev/path> ──────────── Path to V4L2 M2M encoder device. Default: auto select.\n");
|
||||
SAY("Image control options:");
|
||||
SAY("══════════════════════");
|
||||
SAY(" --image-default ────────────────────── Reset all image settings below to default. Default: no change.\n");
|
||||
@@ -652,6 +650,9 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" -U|--unix <path> ─────────── Bind to UNIX domain socket. Default: disabled.\n");
|
||||
SAY(" -D|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n");
|
||||
SAY(" -M|--unix-mode <mode> ────── Set UNIX socket file permissions (like 777). Default: disabled.\n");
|
||||
# ifdef WITH_SYSTEMD
|
||||
SAY(" -S|--systemd ─────────────── Bind to systemd socket for socket activation.\n");
|
||||
# endif
|
||||
SAY(" --user <name> ────────────── HTTP basic auth user. Default: disabled.\n");
|
||||
SAY(" --passwd <str> ───────────── HTTP basic auth passwd. Default: empty.\n");
|
||||
SAY(" --static <path> ───────────── Path to dir with static files instead of embedded root index page.");
|
||||
@@ -661,25 +662,24 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" the CPU loading. Don't use this option with analog signal sources");
|
||||
SAY(" or webcams, it's useless. Default: disabled.\n");
|
||||
SAY(" -R|--fake-resolution <WxH> ─ Override image resolution for the /state. Default: disabled.\n");
|
||||
SAY(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Ignored for --unix.");
|
||||
SAY(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Only for TCP socket.");
|
||||
SAY(" Default: disabled.\n");
|
||||
SAY(" --allow-origin <str> ─────── Set Access-Control-Allow-Origin header. Default: disabled.\n");
|
||||
SAY(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n", server->timeout);
|
||||
# define ADD_SINK(_name, _opt) \
|
||||
SAY(_name " sink options:"); \
|
||||
SAY("══════════════════"); \
|
||||
SAY(" --" _opt "sink <name> ─────────── Use the shared memory to sink " _name " frames. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-mode <mode> ────── Set " _name " sink permissions (like 777). Default: 660.\n"); \
|
||||
SAY(" --" _opt "sink-rm ─────────────── Remove shared memory on stop. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-client-ttl <sec> ─ Client TTL. Default: 10.\n"); \
|
||||
SAY(" --" _opt "sink-timeout <sec> ──── Timeout for lock. Default: 1.\n");
|
||||
SAY(" --" _opt "sink <name> ──────────── Use the shared memory to sink " _name " frames. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-mode <mode> ─────── Set " _name " sink permissions (like 777). Default: 660.\n"); \
|
||||
SAY(" --" _opt "sink-rm ──────────────── Remove shared memory on stop. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-client-ttl <sec> ── Client TTL. Default: 10.\n"); \
|
||||
SAY(" --" _opt "sink-timeout <sec> ───── Timeout for lock. Default: 1.\n");
|
||||
ADD_SINK("JPEG", "")
|
||||
ADD_SINK("RAW", "raw-")
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("H264", "h264-")
|
||||
SAY(" --h264-bitrate <kbps> ──────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
|
||||
SAY(" --h264-gop <N> ─────────────── Intarval between keyframes. Default: %u.\n", stream->h264_gop);
|
||||
# endif
|
||||
SAY(" --h264-bitrate <kbps> ───────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
|
||||
SAY(" --h264-gop <N> ──────────────── Intarval between keyframes. Default: %u.\n", stream->h264_gop);
|
||||
SAY(" --h264-m2m-device </dev/path> ─ Path to V4L2 M2M encoder device. Default: auto select.\n");
|
||||
# undef ADD_SINK
|
||||
# ifdef WITH_GPIO
|
||||
SAY("GPIO options:");
|
||||
@@ -697,6 +697,8 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
# ifdef HAS_PDEATHSIG
|
||||
SAY(" --exit-on-parent-death ─────── Exit the program if the parent process is dead. Default: disabled.\n");
|
||||
# endif
|
||||
SAY(" --exit-on-no-clients <sec> ──── Exit the program if there have been no stream or sink clients");
|
||||
SAY(" or any HTTP requests in the last N seconds. Default: 0 (disabled)\n");
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
SAY(" --process-name-prefix <str> ── Set process name prefix which will be displayed in the process list");
|
||||
SAY(" like '<str>: ustreamer --blah-blah-blah'. Default: disabled.\n");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -56,9 +56,7 @@ typedef struct {
|
||||
frame_s *blank;
|
||||
memsink_s *sink;
|
||||
memsink_s *raw_sink;
|
||||
# ifdef WITH_OMX
|
||||
memsink_s *h264_sink;
|
||||
# endif
|
||||
} options_s;
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -36,13 +36,11 @@ static void _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned capt
|
||||
} \
|
||||
}
|
||||
|
||||
#ifdef WITH_OMX
|
||||
# define H264_PUT(_frame, _vcsm_handle, _force_key) { \
|
||||
if (RUN(h264)) { \
|
||||
h264_stream_process(RUN(h264), _frame, _vcsm_handle, _force_key); \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
#define H264_PUT(_frame, _force_key) { \
|
||||
if (RUN(h264)) { \
|
||||
h264_stream_process(RUN(h264), _frame, _force_key); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
stream_s *stream_init(device_s *dev, encoder_s *enc) {
|
||||
@@ -64,10 +62,8 @@ stream_s *stream_init(device_s *dev, encoder_s *enc) {
|
||||
stream->enc = enc;
|
||||
stream->last_as_blank = -1;
|
||||
stream->error_delay = 1;
|
||||
# ifdef WITH_OMX
|
||||
stream->h264_bitrate = 5000; // Kbps
|
||||
stream->h264_gop = 30;
|
||||
# endif
|
||||
stream->run = run;
|
||||
return stream;
|
||||
}
|
||||
@@ -86,11 +82,9 @@ void stream_loop(stream_s *stream) {
|
||||
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
|
||||
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
|
||||
|
||||
# ifdef WITH_OMX
|
||||
if (stream->h264_sink) {
|
||||
RUN(h264) = h264_stream_init(stream->h264_sink, stream->h264_bitrate, stream->h264_gop);
|
||||
RUN(h264) = h264_stream_init(stream->h264_sink, stream->h264_m2m_path, stream->h264_bitrate, stream->h264_gop);
|
||||
}
|
||||
# endif
|
||||
|
||||
for (workers_pool_s *pool; (pool = _stream_init_loop(stream)) != NULL;) {
|
||||
long double grab_after = 0;
|
||||
@@ -126,27 +120,14 @@ void stream_loop(stream_s *stream) {
|
||||
}
|
||||
}
|
||||
|
||||
# ifdef WITH_OMX
|
||||
bool h264_force_key = false;
|
||||
# endif
|
||||
if (stream->slowdown) {
|
||||
unsigned slc = 0;
|
||||
while (
|
||||
slc < 10
|
||||
&& !atomic_load(&RUN(stop))
|
||||
&& !atomic_load(&RUN(video->has_clients))
|
||||
// has_clients синков НЕ обновляются в реальном времени
|
||||
&& (stream->sink == NULL || !atomic_load(&stream->sink->has_clients))
|
||||
# ifdef WITH_OMX
|
||||
&& (RUN(h264) == NULL || /*RUN(h264->sink) == NULL ||*/ !atomic_load(&RUN(h264->sink->has_clients)))
|
||||
# endif
|
||||
) {
|
||||
for (; slc < 10 && !atomic_load(&RUN(stop)) && !stream_has_clients(stream); ++slc) {
|
||||
usleep(100000);
|
||||
++slc;
|
||||
}
|
||||
# ifdef WITH_OMX
|
||||
h264_force_key = (slc == 10);
|
||||
# endif
|
||||
}
|
||||
|
||||
if (atomic_load(&RUN(stop))) {
|
||||
@@ -206,12 +187,10 @@ void stream_loop(stream_s *stream) {
|
||||
|
||||
ready_job->hw = hw;
|
||||
workers_pool_assign(pool, ready_wr);
|
||||
LOG_DEBUG("Assigned new frame in buffer %d to worker %s", buf_index, ready_wr->name);
|
||||
LOG_DEBUG("Assigned new frame in buffer=%d to worker=%s", buf_index, ready_wr->name);
|
||||
|
||||
SINK_PUT(raw_sink, &hw->raw);
|
||||
# ifdef WITH_OMX
|
||||
H264_PUT(&hw->raw, hw->vcsm_handle, h264_force_key);
|
||||
# endif
|
||||
H264_PUT(&hw->raw, h264_force_key);
|
||||
}
|
||||
} else if (buf_index != -2) { // -2 for broken frame
|
||||
break;
|
||||
@@ -241,17 +220,24 @@ void stream_loop(stream_s *stream) {
|
||||
# endif
|
||||
}
|
||||
|
||||
# ifdef WITH_OMX
|
||||
if (RUN(h264)) {
|
||||
h264_stream_destroy(RUN(h264));
|
||||
}
|
||||
# endif
|
||||
}
|
||||
|
||||
void stream_loop_break(stream_s *stream) {
|
||||
atomic_store(&RUN(stop), true);
|
||||
}
|
||||
|
||||
bool stream_has_clients(stream_s *stream) {
|
||||
return (
|
||||
atomic_load(&RUN(video->has_clients))
|
||||
// has_clients синков НЕ обновляются в реальном времени
|
||||
|| (stream->sink != NULL && atomic_load(&stream->sink->has_clients))
|
||||
|| (RUN(h264) != NULL && /*RUN(h264->sink) == NULL ||*/ atomic_load(&RUN(h264->sink->has_clients)))
|
||||
);
|
||||
}
|
||||
|
||||
static workers_pool_s *_stream_init_loop(stream_s *stream) {
|
||||
|
||||
workers_pool_s *pool = NULL;
|
||||
@@ -290,11 +276,13 @@ static workers_pool_s *_stream_init_one(stream_s *stream) {
|
||||
if (device_open(stream->dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
# ifdef WITH_OMX
|
||||
if (RUN(h264) && !is_jpeg(stream->dev->run->format)) {
|
||||
device_export_to_vcsm(stream->dev);
|
||||
if (
|
||||
stream->enc->type == ENCODER_TYPE_M2M_VIDEO
|
||||
|| stream->enc->type == ENCODER_TYPE_M2M_IMAGE
|
||||
|| (RUN(h264) && !is_jpeg(stream->dev->run->format))
|
||||
) {
|
||||
device_export_to_dma(stream->dev);
|
||||
}
|
||||
# endif
|
||||
if (device_switch_capturing(stream->dev, true) < 0) {
|
||||
goto error;
|
||||
}
|
||||
@@ -362,16 +350,12 @@ static void _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned capt
|
||||
|
||||
if (frame == NULL) {
|
||||
SINK_PUT(raw_sink, stream->blank);
|
||||
# ifdef WITH_OMX
|
||||
H264_PUT(stream->blank, -1, false);
|
||||
# endif
|
||||
H264_PUT(stream->blank, false);
|
||||
}
|
||||
|
||||
# undef VID
|
||||
}
|
||||
|
||||
#ifdef WITH_OMX
|
||||
# undef H264_PUT
|
||||
#endif
|
||||
#undef H264_PUT
|
||||
#undef SINK_PUT
|
||||
#undef RUN
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -42,9 +42,7 @@
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
#include "workers.h"
|
||||
#ifdef WITH_OMX
|
||||
# include "h264/stream.h"
|
||||
#endif
|
||||
#include "h264/stream.h"
|
||||
#ifdef WITH_GPIO
|
||||
# include "gpio/gpio.h"
|
||||
#endif
|
||||
@@ -63,9 +61,7 @@ typedef struct {
|
||||
video_s *video;
|
||||
long double last_as_blank_ts;
|
||||
|
||||
# ifdef WITH_OMX
|
||||
h264_stream_s *h264;
|
||||
# endif
|
||||
|
||||
atomic_bool stop;
|
||||
} stream_runtime_s;
|
||||
@@ -82,11 +78,10 @@ typedef struct {
|
||||
memsink_s *sink;
|
||||
memsink_s *raw_sink;
|
||||
|
||||
# ifdef WITH_OMX
|
||||
memsink_s *h264_sink;
|
||||
unsigned h264_bitrate;
|
||||
unsigned h264_gop;
|
||||
# endif
|
||||
char *h264_m2m_path;
|
||||
|
||||
stream_runtime_s *run;
|
||||
} stream_s;
|
||||
@@ -97,3 +92,5 @@ void stream_destroy(stream_s *stream);
|
||||
|
||||
void stream_loop(stream_s *stream);
|
||||
void stream_loop_break(stream_s *stream);
|
||||
|
||||
bool stream_has_clients(stream_s *stream);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
@@ -28,9 +28,9 @@ import textwrap
|
||||
C_PREPEND = textwrap.dedent("""
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env -S python3 -B
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env -S python3 -B
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# 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 #
|
||||
|
||||
Reference in New Issue
Block a user