mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa6fc7fe04 | ||
|
|
c91341a375 | ||
|
|
3de7e26a36 | ||
|
|
63cc66e8a7 | ||
|
|
92a090dec3 | ||
|
|
8b0ef8a271 | ||
|
|
a360f1901e | ||
|
|
ed2d5f3af4 | ||
|
|
b935dd1fe8 | ||
|
|
6e1f60a36d | ||
|
|
210dfcfa4f | ||
|
|
ec10a9e3fe | ||
|
|
217d146378 | ||
|
|
3e2a43e2af | ||
|
|
2e0a19c1cb | ||
|
|
054748234e | ||
|
|
53873e9ddb | ||
|
|
c21d0aef7e | ||
|
|
e505a56910 | ||
|
|
f4278f32c4 | ||
|
|
e9a6db02f6 | ||
|
|
63fe32ddd9 | ||
|
|
710652073a | ||
|
|
dded49cd83 | ||
|
|
0f753dc654 | ||
|
|
c505a423af | ||
|
|
1cff2545b1 | ||
|
|
3d994d6e67 | ||
|
|
7175f8d569 | ||
|
|
d4a9862a18 | ||
|
|
3d4e9fbb1a | ||
|
|
4a59b038ec | ||
|
|
6795744aea | ||
|
|
798fe04905 | ||
|
|
1460de95c1 | ||
|
|
358950d0c2 | ||
|
|
caef82d96f | ||
|
|
dff7dd7087 | ||
|
|
79bb881a34 | ||
|
|
f0763e3865 | ||
|
|
a8dfa96db0 | ||
|
|
7ceb8a3da5 | ||
|
|
9344059e0e | ||
|
|
ee2b1afe5b | ||
|
|
31f47f2eac | ||
|
|
69a9bafcd5 | ||
|
|
dbecdf5d9b | ||
|
|
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 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 4.13
|
||||
current_version = 5.10
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,4 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: pikvm
|
||||
custom: https://www.paypal.me/mdevaev
|
||||
#custom: https://www.paypal.me/mdevaev
|
||||
|
||||
26
README.md
26
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,7 +12,7 @@
|
||||
| **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> | ✘ |
|
||||
@@ -35,13 +35,14 @@ 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`, `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=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`.
|
||||
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
|
||||
|
||||
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
|
||||
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
|
||||
@@ -50,11 +51,14 @@ $ 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.
|
||||
|
||||
-----
|
||||
# Usage
|
||||
**For M2M hardware encoding on Raspberry Pi, you need at least 5.15.32 kernel. OpenMAX and MMAL support on older kernels is deprecated and removed.**
|
||||
|
||||
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start streaming on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to stream to the world, run:
|
||||
```
|
||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
||||
@@ -64,11 +68,10 @@ Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x4
|
||||
|
||||
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
|
||||
```bash
|
||||
$ export LD_LIBRARY_PATH=/opt/vc/lib/ # on bullseye
|
||||
$ ./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
|
||||
@@ -97,6 +100,9 @@ $ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||
-----
|
||||
# Integrations
|
||||
|
||||
## Janus
|
||||
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server. See the [Janus integration guide](docs/h264.md) for full details.
|
||||
|
||||
## Nginx
|
||||
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:
|
||||
|
||||
@@ -127,7 +133,7 @@ v4l2 utilities provide the tools to manage USB webcam setting and information. S
|
||||
|
||||
-----
|
||||
# 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
|
||||
|
||||
19
README.ru.md
19
README.ru.md
@@ -5,14 +5,14 @@
|
||||
[[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> | ✘ |
|
||||
@@ -38,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` и `libsystemd-dev` для `WITH_SYSTEMD=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
|
||||
@@ -50,11 +50,14 @@ $ 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.
|
||||
|
||||
-----
|
||||
# Использование
|
||||
**Для аппаратного кодирования M2M на Raspberry Pi, вам нужно ядро минимальной версии 5.15.32. Поддержка OpenMAX и MMAL для более старых ядер объявлена устаревшей и была удалена.**
|
||||
|
||||
Будучи запущенным без аргументов, ```ustreamer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://127.0.0.1:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80-м порту:
|
||||
```
|
||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
||||
@@ -66,8 +69,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 # Экономим трафик
|
||||
@@ -126,7 +129,7 @@ V4L2 предоставляет ряд официальных утилит дл
|
||||
|
||||
-----
|
||||
# Лицензия
|
||||
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
|
||||
|
||||
231
docs/h264.md
Normal file
231
docs/h264.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# Streaming H.264 Video over WebRTC with Janus
|
||||
|
||||
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server.
|
||||
|
||||
This guide explains how to configure µStreamer to provide an H.264 video stream and consume it within a web application using WebRTC.
|
||||
|
||||
## Components
|
||||
|
||||
In addition to µStreamer, H.264 streaming involves the following components:
|
||||
|
||||
- [**Janus**](https://janus.conf.meetecho.com/): A general-purpose WebRTC server.
|
||||
- [**µStreamer Janus Plugin**](https://github.com/pikvm/ustreamer/tree/master/janus): A Janus plugin that allows Janus to consume a video stream from µStreamer via shared memory.
|
||||
- [**Janus JavaScript Client**](https://janus.conf.meetecho.com/docs/JS.html): A frontend library that communicates with the Janus server and the µStreamer Janus plugin.
|
||||
|
||||
## Control Flow
|
||||
|
||||
To connect to a µStreamer video stream over WebRTC, an application must establish the following control flow:
|
||||
|
||||
1. The backend server starts the µStreamer service.
|
||||
1. The backend server starts the Janus WebRTC server with the µStreamer Janus plugin.
|
||||
1. The client-side JavaScript application establishes a connection to the Janus server.
|
||||
1. The client-side JavaScript application instructs the Janus server to attach the µStreamer Janus plugin and start the video stream.
|
||||
1. The client-side JavaScript application renders the video stream in the web browser.
|
||||
|
||||
## Server Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To integrate with Janus, µStreamer has the following prerequisites:
|
||||
|
||||
- The system packages that µStreamer depends on (see the [main build instructions](../README.md#building)).
|
||||
- The Janus WebRTC server with WebSockets transport enabled (see the [Janus documentation](https://github.com/meetecho/janus-gateway)).
|
||||
|
||||
### Fixing Janus C Headers
|
||||
|
||||
To compile µStreamer with the Janus option, (see [“Installation”](#installation)), you need to make the Janus header files available within µStreamer's build context.
|
||||
|
||||
First, create a symbolic link from `/usr/include` to the Janus install directory. By default, Janus installs to `/opt/janus`.
|
||||
|
||||
```sh
|
||||
ln -s /opt/janus/include/janus /usr/include/janus
|
||||
```
|
||||
|
||||
Janus uses `#include` paths that make it difficult for third-party libraries to build against its headers. To fix this, modify the `#include` directive in `janus/plugins/plugin.h` to prepend a `../` to the included file name (`#include "refcount.h"` → `#include "../refcount.h"`).
|
||||
|
||||
```sh
|
||||
sed \
|
||||
--in-place \
|
||||
--expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' \
|
||||
/usr/include/janus/plugins/plugin.h
|
||||
```
|
||||
|
||||
### Installation
|
||||
|
||||
First, compile µStreamer with the Janus plugin option (`WITH_JANUS`).
|
||||
|
||||
```sh
|
||||
git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||
cd ustreamer
|
||||
make WITH_JANUS=1
|
||||
```
|
||||
|
||||
The `WITH_JANUS` option compiles the µStreamer Janus plugin and outputs it to `janus/libjanus_ustreamer.so`. Move this file to the plugin directory of your Janus installation.
|
||||
|
||||
```sh
|
||||
mv \
|
||||
janus/libjanus_ustreamer.so \
|
||||
/opt/janus/lib/janus/plugins/libjanus_ustreamer.so
|
||||
```
|
||||
|
||||
Finally, specify a qualifier for the shared memory object so that the µStreamer Janus plugin can read µStreamer's video data.
|
||||
|
||||
```sh
|
||||
cat > /opt/janus/lib/janus/configs/janus.plugin.ustreamer.jcfg <<EOF
|
||||
memsink: {
|
||||
object = "demo::ustreamer::h264"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Start µStreamer and the Janus WebRTC Server
|
||||
|
||||
For µStreamer to share the video stream with the µStreamer Janus plugin, µStreamer must run with the following command-line flags:
|
||||
|
||||
- `--h264-sink` with the qualifier of the shared memory object you specified above (`demo::ustreamer::h264`)
|
||||
- `--h264-sink-mode` with the permissions bitmask for the shared memory object (e.g., `660`)
|
||||
- `--h264-sink-rm` to clean up the shared memory object when the µStreamer process exits
|
||||
|
||||
To load the µStreamer Janus plugin and configuration, the Janus WebRTC server must run with the following command-line flags:
|
||||
|
||||
- `--configs-folder` with the path to the Janus configuration directory (e.g., `/opt/janus/lib/janus/configs/`)
|
||||
- `--plugins-folder` with the path to the Janus plugin directory (e.g., `/opt/janus/lib/janus/plugins/`)
|
||||
|
||||
## Client Setup
|
||||
|
||||
Once an application's backend server is running µStreamer and Janus, a browser-based JavaScript application can consume the server's video stream.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
The client needs to load the following JavaScript libraries:
|
||||
|
||||
- [WebRTC Adapter library](https://webrtc.github.io/adapter/adapter-8.1.0.js) (`webrtc-adapter.js`, version `8.1.0`)
|
||||
- [Janus Gateway JavaScript library](https://raw.githubusercontent.com/meetecho/janus-gateway/v1.0.0/html/janus.js) (`janus.js`, version `1.0.0`)
|
||||
|
||||
### Control Flow
|
||||
|
||||
The client-side JavaScript application uses the following control flow:
|
||||
|
||||
1. The client loads and initializes the Janus client library.
|
||||
1. The client establishes a WebSockets connection to the Janus server.
|
||||
1. The client instructs the Janus server to attach the µStreamer Janus plugin.
|
||||
1. On success, the client obtains a plugin handle through which it can send requests directly to the µStreamer Janus plugin. The client processes responses via the `attach` callbacks:
|
||||
- `onmessage` for general messages
|
||||
- `onremotetrack` for the H.264 video stream
|
||||
1. The client issues a `watch` request to the µStreamer Janus plugin, which initiates the H.264 stream in the plugin itself.
|
||||
- It takes a few seconds for uStreamer's video stream to become available to Janus. The first `watch` request may fail, so the client must retry the `watch` request.
|
||||
1. The client and server negotiate the underlying parameters of the WebRTC session. This procedure is called JavaScript Session Establishment Protocol (JSEP). The server makes a `jsepOffer` to the client, and the client responds with a `jsepAnswer`.
|
||||
1. The client issues a `start` request to the µStreamer Janus plugin to indicate that the client wants to begin consuming the video stream.
|
||||
1. The µStreamer Janus plugin delivers the H.264 video stream to the client via WebRTC.
|
||||
1. The Janus client library invokes the `onremotetrack` callback. The client attaches the video stream to the `<video>` element, rendering the video stream in the browser window.
|
||||
|
||||
### Sample Code
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>µStreamer H.264 demo</title>
|
||||
<script src="https://webrtc.github.io/adapter/adapter-8.1.0.js"></script>
|
||||
<!-- janus.js is the JavaScript client library of Janus, as specified above in
|
||||
the prerequisites section of the client setup. You might need to change
|
||||
the `src` path, depending on where you serve this file from. -->
|
||||
<script src="janus.js"></script>
|
||||
<style>
|
||||
video {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<video id="webrtc-output" autoplay playsinline muted></video>
|
||||
<script type="text/javascript">
|
||||
// Initialize Janus library.
|
||||
Janus.init({
|
||||
// Turn on debug logs in the browser console.
|
||||
debug: true,
|
||||
|
||||
// Configure Janus to use standard browser APIs internally.
|
||||
dependencies: Janus.useDefaultDependencies(),
|
||||
});
|
||||
|
||||
// Establish a WebSockets connection to the server.
|
||||
const janus = new Janus({
|
||||
// Specify the URL of the Janus server’s WebSockets endpoint.
|
||||
server: `ws://${window.location.hostname}:8188/`,
|
||||
|
||||
// Callback function if the client connects successfully.
|
||||
success: attachUStreamerPlugin,
|
||||
|
||||
// Callback function if the client fails to connect.
|
||||
error: console.error,
|
||||
});
|
||||
|
||||
let uStreamerPluginHandle = null;
|
||||
|
||||
function attachUStreamerPlugin() {
|
||||
// Instruct the server to attach the µStreamer Janus plugin.
|
||||
janus.attach({
|
||||
// Qualifier of the plugin.
|
||||
plugin: "janus.plugin.ustreamer",
|
||||
|
||||
// Callback function, for when the server attached the plugin
|
||||
// successfully.
|
||||
success: function (pluginHandle) {
|
||||
uStreamerPluginHandle = pluginHandle;
|
||||
// Instruct the µStreamer Janus plugin to initiate the video stream.
|
||||
uStreamerPluginHandle.send({ message: { request: "watch" } });
|
||||
},
|
||||
|
||||
// Callback function if the server fails to attach the plugin.
|
||||
error: console.error,
|
||||
|
||||
// Callback function for processing messages from the Janus server.
|
||||
onmessage: function (msg, jsepOffer) {
|
||||
// 503 indicates that the plugin is not ready to stream yet. Retry the
|
||||
// watch request until the video stream is available.
|
||||
if (msg.error_code === 503) {
|
||||
uStreamerPluginHandle.send({ message: { request: "watch" } });
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is a JSEP offer, respond to it. This starts the WebRTC
|
||||
// connection.
|
||||
if (jsepOffer) {
|
||||
uStreamerPluginHandle.createAnswer({
|
||||
jsep: jsepOffer,
|
||||
// Prevent the client from sending audio and video, as this would
|
||||
// trigger a permission dialog in the browser.
|
||||
media: { audioSend: false, videoSend: false },
|
||||
success: function (jsepAnswer) {
|
||||
uStreamerPluginHandle.send({
|
||||
message: { request: "start" },
|
||||
jsep: jsepAnswer,
|
||||
});
|
||||
},
|
||||
error: console.error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Callback function, for when the video stream arrives.
|
||||
onremotetrack: function (mediaStreamTrack, mediaId, isAdded) {
|
||||
if (isAdded) {
|
||||
// Attach the received media track to the video element. Cloning the
|
||||
// mediaStreamTrack creates a new object with a distinct, globally
|
||||
// unique stream identifier.
|
||||
const videoElement = document.getElementById("webrtc-output");
|
||||
const stream = new MediaStream();
|
||||
stream.addTrack(mediaStreamTrack.clone());
|
||||
videoElement.srcObject = stream;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -9,10 +9,10 @@ LDFLAGS ?=
|
||||
# =====
|
||||
_PLUGIN = libjanus_ustreamer.so
|
||||
|
||||
_CFLAGS = -fPIC -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(shell pkg-config --cflags glib-2.0) $(CFLAGS)
|
||||
_LDFLAGS = -shared -lm -pthread -lrt -ljansson $(shell pkg-config --libs glib-2.0) $(LDFLAGS)
|
||||
_CFLAGS = -fPIC -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(shell pkg-config --cflags glib-2.0) $(CFLAGS)
|
||||
_LDFLAGS = -shared -lm -pthread -lrt -ljansson -lopus -lasound -lspeexdsp $(shell pkg-config --libs glib-2.0) $(LDFLAGS)
|
||||
|
||||
_SRCS = $(shell ls src/*.c)
|
||||
_SRCS = $(shell ls src/uslibs/*.c src/*.c)
|
||||
|
||||
_BUILD = build
|
||||
|
||||
|
||||
261
janus/src/audio.c
Normal file
261
janus/src/audio.c
Normal file
@@ -0,0 +1,261 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 "audio.h"
|
||||
|
||||
|
||||
#define JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
|
||||
#define JLOG_PERROR_RES(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
|
||||
#define JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err))
|
||||
|
||||
// A number of frames per 1 channel:
|
||||
// - https://github.com/xiph/opus/blob/7b05f44/src/opus_demo.c#L368
|
||||
#define HZ_TO_FRAMES(_hz) (6 * (_hz) / 50) // 120ms
|
||||
#define HZ_TO_BUF16(_hz) (HZ_TO_FRAMES(_hz) * 2) // One stereo frame = (16bit L) + (16bit R)
|
||||
#define HZ_TO_BUF8(_hz) (HZ_TO_BUF16(_hz) * sizeof(int16_t))
|
||||
|
||||
#define MIN_PCM_HZ 8000
|
||||
#define MAX_PCM_HZ 192000
|
||||
#define MAX_BUF16 HZ_TO_BUF16(MAX_PCM_HZ)
|
||||
#define MAX_BUF8 HZ_TO_BUF8(MAX_PCM_HZ)
|
||||
#define ENCODER_INPUT_HZ 48000
|
||||
|
||||
|
||||
typedef struct {
|
||||
int16_t data[MAX_BUF16];
|
||||
} _pcm_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MAX_BUF8]; // Worst case
|
||||
size_t used;
|
||||
uint64_t pts;
|
||||
} _enc_buffer_s;
|
||||
|
||||
|
||||
static void *_pcm_thread(void *v_audio);
|
||||
static void *_encoder_thread(void *v_audio);
|
||||
|
||||
|
||||
audio_s *audio_init(const char *name, unsigned pcm_hz) {
|
||||
audio_s *audio;
|
||||
A_CALLOC(audio, 1);
|
||||
audio->pcm_hz = pcm_hz;
|
||||
audio->pcm_queue = queue_init(8);
|
||||
audio->enc_queue = queue_init(8);
|
||||
atomic_init(&audio->stop, false);
|
||||
|
||||
int err;
|
||||
|
||||
{
|
||||
if ((err = snd_pcm_open(&audio->pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
||||
audio->pcm = NULL;
|
||||
JLOG_PERROR_ALSA(err, "audio", "Can't open PCM capture");
|
||||
goto error;
|
||||
}
|
||||
assert(!snd_pcm_hw_params_malloc(&audio->pcm_params));
|
||||
|
||||
# define SET_PARAM(_msg, _func, ...) { \
|
||||
if ((err = _func(audio->pcm, audio->pcm_params, ##__VA_ARGS__)) < 0) { \
|
||||
JLOG_PERROR_ALSA(err, "audio", _msg); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
SET_PARAM("Can't initialize PCM params", snd_pcm_hw_params_any);
|
||||
SET_PARAM("Can't set PCM access type", snd_pcm_hw_params_set_access, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
SET_PARAM("Can't set PCM channels numbre", snd_pcm_hw_params_set_channels, 2);
|
||||
SET_PARAM("Can't set PCM sampling format", snd_pcm_hw_params_set_format, SND_PCM_FORMAT_S16_LE);
|
||||
SET_PARAM("Can't set PCM sampling rate", snd_pcm_hw_params_set_rate_near, &audio->pcm_hz, 0);
|
||||
if (audio->pcm_hz < MIN_PCM_HZ || audio->pcm_hz > MAX_PCM_HZ) {
|
||||
JLOG_ERROR("audio", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
|
||||
audio->pcm_hz, MIN_PCM_HZ, MAX_PCM_HZ);
|
||||
goto error;
|
||||
}
|
||||
audio->pcm_frames = HZ_TO_FRAMES(audio->pcm_hz);
|
||||
audio->pcm_size = HZ_TO_BUF8(audio->pcm_hz);
|
||||
SET_PARAM("Can't apply PCM params", snd_pcm_hw_params);
|
||||
|
||||
# undef SET_PARAM
|
||||
}
|
||||
|
||||
if (audio->pcm_hz != ENCODER_INPUT_HZ) {
|
||||
audio->res = speex_resampler_init(2, audio->pcm_hz, ENCODER_INPUT_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
|
||||
if (err < 0) {
|
||||
audio->res = NULL;
|
||||
JLOG_PERROR_RES(err, "audio", "Can't create resampler");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY
|
||||
audio->enc = opus_encoder_create(ENCODER_INPUT_HZ, 2, OPUS_APPLICATION_AUDIO, &err);
|
||||
assert(err == 0);
|
||||
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_BITRATE(48000)));
|
||||
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)));
|
||||
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)));
|
||||
// OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c
|
||||
}
|
||||
|
||||
JLOG_INFO("audio", "Pipeline configured on %uHz; capturing ...", audio->pcm_hz);
|
||||
audio->tids_created = true;
|
||||
A_THREAD_CREATE(&audio->enc_tid, _encoder_thread, audio);
|
||||
A_THREAD_CREATE(&audio->pcm_tid, _pcm_thread, audio);
|
||||
|
||||
return audio;
|
||||
|
||||
error:
|
||||
audio_destroy(audio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void audio_destroy(audio_s *audio) {
|
||||
if (audio->tids_created) {
|
||||
atomic_store(&audio->stop, true);
|
||||
A_THREAD_JOIN(audio->pcm_tid);
|
||||
A_THREAD_JOIN(audio->enc_tid);
|
||||
}
|
||||
if (audio->enc) {
|
||||
opus_encoder_destroy(audio->enc);
|
||||
}
|
||||
if (audio->res) {
|
||||
speex_resampler_destroy(audio->res);
|
||||
}
|
||||
if (audio->pcm) {
|
||||
snd_pcm_close(audio->pcm);
|
||||
}
|
||||
if (audio->pcm_params) {
|
||||
snd_pcm_hw_params_free(audio->pcm_params);
|
||||
}
|
||||
# define FREE_QUEUE(_suffix) { \
|
||||
while (!queue_get_free(audio->_suffix##_queue)) { \
|
||||
_##_suffix##_buffer_s *ptr; \
|
||||
assert(!queue_get(audio->_suffix##_queue, (void **)&ptr, 1)); \
|
||||
free(ptr); \
|
||||
} \
|
||||
queue_destroy(audio->_suffix##_queue); \
|
||||
}
|
||||
FREE_QUEUE(enc);
|
||||
FREE_QUEUE(pcm);
|
||||
# undef FREE_QUEUE
|
||||
if (audio->tids_created) {
|
||||
JLOG_INFO("audio", "Pipeline closed");
|
||||
}
|
||||
free(audio);
|
||||
}
|
||||
|
||||
int audio_get_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
|
||||
if (atomic_load(&audio->stop)) {
|
||||
return -1;
|
||||
}
|
||||
_enc_buffer_s *buf;
|
||||
if (!queue_get(audio->enc_queue, (void **)&buf, 1)) {
|
||||
if (*size < buf->used) {
|
||||
free(buf);
|
||||
return -3;
|
||||
}
|
||||
memcpy(data, buf->data, buf->used);
|
||||
*size = buf->used;
|
||||
*pts = buf->pts;
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
static void *_pcm_thread(void *v_audio) {
|
||||
A_THREAD_RENAME("us_a_pcm");
|
||||
|
||||
audio_s *audio = (audio_s *)v_audio;
|
||||
uint8_t in[MAX_BUF8];
|
||||
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames);
|
||||
if (frames < 0) {
|
||||
JLOG_PERROR_ALSA(frames, "audio", "Fatal: Can't capture PCM frames");
|
||||
break;
|
||||
} else if (frames < (int)audio->pcm_frames) {
|
||||
JLOG_ERROR("audio", "Fatal: Too few PCM frames captured");
|
||||
break;
|
||||
}
|
||||
|
||||
if (queue_get_free(audio->pcm_queue)) {
|
||||
_pcm_buffer_s *out;
|
||||
A_CALLOC(out, 1);
|
||||
memcpy(out->data, in, audio->pcm_size);
|
||||
assert(!queue_put(audio->pcm_queue, out, 1));
|
||||
} else {
|
||||
JLOG_ERROR("audio", "PCM queue is full");
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store(&audio->stop, true);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_encoder_thread(void *v_audio) {
|
||||
A_THREAD_RENAME("us_a_enc");
|
||||
|
||||
audio_s *audio = (audio_s *)v_audio;
|
||||
int16_t in_res[MAX_BUF16];
|
||||
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
_pcm_buffer_s *in;
|
||||
if (!queue_get(audio->pcm_queue, (void **)&in, 1)) {
|
||||
int16_t *in_ptr;
|
||||
if (audio->res) {
|
||||
assert(audio->pcm_hz != ENCODER_INPUT_HZ);
|
||||
uint32_t in_count = audio->pcm_frames;
|
||||
uint32_t out_count = HZ_TO_FRAMES(ENCODER_INPUT_HZ);
|
||||
speex_resampler_process_interleaved_int(audio->res, in->data, &in_count, in_res, &out_count);
|
||||
in_ptr = in_res;
|
||||
} else {
|
||||
assert(audio->pcm_hz == ENCODER_INPUT_HZ);
|
||||
in_ptr = in->data;
|
||||
}
|
||||
|
||||
_enc_buffer_s *out;
|
||||
A_CALLOC(out, 1);
|
||||
int size = opus_encode(audio->enc, in_ptr, HZ_TO_FRAMES(ENCODER_INPUT_HZ), out->data, ARRAY_LEN(out->data));
|
||||
free(in);
|
||||
if (size < 0) {
|
||||
JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
|
||||
free(out);
|
||||
break;
|
||||
}
|
||||
out->used = size;
|
||||
out->pts = audio->pts;
|
||||
// https://datatracker.ietf.org/doc/html/rfc7587#section-4.2
|
||||
audio->pts += HZ_TO_FRAMES(ENCODER_INPUT_HZ);
|
||||
|
||||
if (queue_get_free(audio->enc_queue)) {
|
||||
assert(!queue_put(audio->enc_queue, out, 1));
|
||||
} else {
|
||||
JLOG_ERROR("audio", "OPUS encoder queue is full");
|
||||
free(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store(&audio->stop, true);
|
||||
return NULL;
|
||||
}
|
||||
@@ -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,32 +22,47 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <IL/OMX_Core.h>
|
||||
#include <IL/OMX_Component.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "../../../libs/logging.h"
|
||||
#include <pthread.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <speex/speex_resampler.h>
|
||||
#include <opus/opus.h>
|
||||
|
||||
#include "formatters.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
#include "jlogging.h"
|
||||
#include "queue.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; \
|
||||
}
|
||||
typedef struct {
|
||||
snd_pcm_t *pcm;
|
||||
unsigned pcm_hz;
|
||||
unsigned pcm_frames;
|
||||
size_t pcm_size;
|
||||
snd_pcm_hw_params_t *pcm_params;
|
||||
SpeexResamplerState *res;
|
||||
OpusEncoder *enc;
|
||||
|
||||
queue_s *pcm_queue;
|
||||
queue_s *enc_queue;
|
||||
uint32_t pts;
|
||||
|
||||
pthread_t pcm_tid;
|
||||
pthread_t enc_tid;
|
||||
bool tids_created;
|
||||
atomic_bool stop;
|
||||
} audio_s;
|
||||
|
||||
|
||||
int omx_component_enable_port(OMX_HANDLETYPE *comp, OMX_U32 port);
|
||||
int omx_component_disable_port(OMX_HANDLETYPE *comp, OMX_U32 port);
|
||||
audio_s *audio_init(const char *name, unsigned pcm_hz);
|
||||
void audio_destroy(audio_s *audio);
|
||||
|
||||
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);
|
||||
int audio_get_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/base64.c
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/base64.h
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/config.h
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/frame.c
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/frame.h
|
||||
36
janus/src/jlogging.h
Normal file
36
janus/src/jlogging.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
|
||||
#define JLOG_INFO(_prefix, _msg, ...) JANUS_LOG(LOG_INFO, "== ustreamer/%-9s -- " _msg "\n", _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_WARN(_prefix, _msg, ...) JANUS_LOG(LOG_WARN, "== ustreamer/%-9s -- " _msg "\n", _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_ERROR(_prefix, _msg, ...) JANUS_LOG(LOG_ERR, "== ustreamer/%-9s -- " _msg "\n", _prefix, ##__VA_ARGS__)
|
||||
|
||||
#define JLOG_PERROR(_prefix, _msg, ...) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
|
||||
JANUS_LOG(LOG_ERR, "[ustreamer/%-9s] " _msg ": %s\n", _prefix, ##__VA_ARGS__, _perror_ptr); \
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/list.h
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/memsinksh.h
|
||||
@@ -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,6 +24,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
@@ -37,20 +38,24 @@
|
||||
#include <janus/config.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
#include "list.h"
|
||||
#include "memsinksh.h"
|
||||
#include "uslibs/config.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/list.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
|
||||
#include "rtp.h"
|
||||
#include "jlogging.h"
|
||||
#include "audio.h"
|
||||
#include "tc358743.h"
|
||||
#include "rtpv.h"
|
||||
#include "rtpa.h"
|
||||
|
||||
|
||||
static int _plugin_init(janus_callbacks *gw, const char *config_file_path);
|
||||
static void _plugin_destroy(void);
|
||||
|
||||
static void _plugin_create_session(janus_plugin_session *session, int *error);
|
||||
static void _plugin_destroy_session(janus_plugin_session *session, int *error);
|
||||
static void _plugin_create_session(janus_plugin_session *session, int *err);
|
||||
static void _plugin_destroy_session(janus_plugin_session *session, int *err);
|
||||
static json_t *_plugin_query_session(janus_plugin_session *session);
|
||||
|
||||
static void _plugin_setup_media(janus_plugin_session *session);
|
||||
@@ -59,14 +64,17 @@ static void _plugin_hangup_media(janus_plugin_session *session);
|
||||
static struct janus_plugin_result *_plugin_handle_message(
|
||||
janus_plugin_session *session, char *transaction, json_t *msg, json_t *jsep);
|
||||
|
||||
static int _plugin_get_api_compatibility(void);
|
||||
static int _plugin_get_version(void);
|
||||
static const char *_plugin_get_version_string(void);
|
||||
static const char *_plugin_get_description(void);
|
||||
static const char *_plugin_get_name(void);
|
||||
static const char *_plugin_get_author(void);
|
||||
static const char *_plugin_get_package(void);
|
||||
|
||||
static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; }
|
||||
static int _plugin_get_version(void) { return VERSION_U; }
|
||||
static const char *_plugin_get_version_string(void) { return VERSION; }
|
||||
static const char *_plugin_get_description(void) { return "PiKVM uStreamer Janus plugin for H.264 video"; }
|
||||
static const char *_plugin_get_name(void) { return "ustreamer"; }
|
||||
static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
|
||||
static const char *_plugin_get_package(void) { return "janus.plugin.ustreamer"; }
|
||||
|
||||
// Just a stub to avoid logging spam about the plugin's purpose.
|
||||
static void _plugin_incoming_rtp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtp *packet) {}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Woverride-init"
|
||||
@@ -90,6 +98,8 @@ static janus_plugin _plugin = JANUS_PLUGIN_INIT(
|
||||
.get_name = _plugin_get_name,
|
||||
.get_author = _plugin_get_author,
|
||||
.get_package = _plugin_get_package,
|
||||
|
||||
.incoming_rtp = _plugin_incoming_rtp,
|
||||
);
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
@@ -106,33 +116,32 @@ typedef struct _client_sx {
|
||||
} _client_s;
|
||||
|
||||
|
||||
static char *_g_memsink_obj = NULL;
|
||||
const long double _g_wait_timeout = 1;
|
||||
const long double _g_lock_timeout = 1;
|
||||
const useconds_t _g_lock_polling = 1000;
|
||||
static char *_g_video_sink_name = NULL;
|
||||
const long double _g_sink_wait_timeout = 1;
|
||||
const long double _g_sink_lock_timeout = 1;
|
||||
const useconds_t _g_sink_lock_polling = 1000;
|
||||
|
||||
static char *_g_audio_dev_name = NULL;
|
||||
static char *_g_tc358743_dev_path = NULL;
|
||||
|
||||
const useconds_t _g_watchers_polling = 100000;
|
||||
|
||||
static _client_s *_g_clients = NULL;
|
||||
static janus_callbacks *_g_gw = NULL;
|
||||
static rtp_s *_g_rtp = NULL;
|
||||
static rtpv_s *_g_rtpv = NULL;
|
||||
static rtpa_s *_g_rtpa = NULL;
|
||||
|
||||
static pthread_t _g_video_tid;
|
||||
static atomic_bool _g_video_tid_created = false;
|
||||
static pthread_t _g_audio_tid;
|
||||
static atomic_bool _g_audio_tid_created = false;
|
||||
|
||||
static pthread_t _g_tid;
|
||||
static pthread_mutex_t _g_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static atomic_bool _g_ready = ATOMIC_VAR_INIT(false);
|
||||
static atomic_bool _g_stop = ATOMIC_VAR_INIT(false);
|
||||
static atomic_bool _g_has_watchers = ATOMIC_VAR_INIT(false);
|
||||
static atomic_bool _g_ready = false;
|
||||
static atomic_bool _g_stop = false;
|
||||
static atomic_bool _g_has_watchers = false;
|
||||
|
||||
|
||||
#define JLOG_INFO(_msg, ...) JANUS_LOG(LOG_INFO, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__)
|
||||
#define JLOG_WARN(_msg, ...) JANUS_LOG(LOG_WARN, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__)
|
||||
#define JLOG_ERROR(_msg, ...) JANUS_LOG(LOG_ERR, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__)
|
||||
|
||||
#define JLOG_PERROR(_msg, ...) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
|
||||
JANUS_LOG(LOG_ERR, "[%s] " _msg ": %s\n", _plugin_get_name(), ##__VA_ARGS__, _perror_ptr); \
|
||||
}
|
||||
|
||||
#define LOCK A_MUTEX_LOCK(&_g_lock)
|
||||
#define UNLOCK A_MUTEX_UNLOCK(&_g_lock)
|
||||
#define READY atomic_load(&_g_ready)
|
||||
@@ -140,39 +149,25 @@ static atomic_bool _g_has_watchers = ATOMIC_VAR_INIT(false);
|
||||
#define HAS_WATCHERS atomic_load(&_g_has_watchers)
|
||||
|
||||
|
||||
static void _relay_rtp_clients(const uint8_t *datagram, size_t size) {
|
||||
janus_plugin_rtp packet = {
|
||||
.video = true,
|
||||
.buffer = (char *)datagram,
|
||||
.length = size,
|
||||
};
|
||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
if (client->transmit) {
|
||||
_g_gw->relay_rtp(client->session, &packet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static int _wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id) {
|
||||
long double deadline_ts = get_now_monotonic() + _g_wait_timeout;
|
||||
long double deadline_ts = get_now_monotonic() + _g_sink_wait_timeout;
|
||||
long double now;
|
||||
do {
|
||||
int retval = flock_timedwait_monotonic(fd, _g_lock_timeout);
|
||||
int result = flock_timedwait_monotonic(fd, _g_sink_lock_timeout);
|
||||
now = get_now_monotonic();
|
||||
if (retval < 0 && errno != EWOULDBLOCK) {
|
||||
JLOG_PERROR("Can't lock memsink");
|
||||
if (result < 0 && errno != EWOULDBLOCK) {
|
||||
JLOG_PERROR("video", "Can't lock memsink");
|
||||
return -1;
|
||||
} else if (retval == 0) {
|
||||
} else if (result == 0) {
|
||||
if (mem->magic == MEMSINK_MAGIC && mem->version == MEMSINK_VERSION && mem->id != last_id) {
|
||||
return 0;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
JLOG_PERROR("Can't unlock memsink");
|
||||
JLOG_PERROR("video", "Can't unlock memsink");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
usleep(_g_lock_polling);
|
||||
usleep(_g_sink_lock_polling);
|
||||
} while (now < deadline_ts);
|
||||
return -2;
|
||||
}
|
||||
@@ -184,18 +179,37 @@ static int _get_frame(int fd, memsink_shared_s *mem, frame_s *frame, uint64_t *f
|
||||
mem->last_client_ts = get_now_monotonic();
|
||||
int retval = 0;
|
||||
if (frame->format != V4L2_PIX_FMT_H264) {
|
||||
JLOG_ERROR("Got non-H264 frame from memsink");
|
||||
JLOG_ERROR("video", "Got non-H264 frame from memsink");
|
||||
retval = -1;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
JLOG_PERROR("Can't unlock memsink");
|
||||
JLOG_PERROR("video", "Can't unlock memsink");
|
||||
retval = -1;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void *_clients_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_clients");
|
||||
static void _relay_rtp_clients(const rtp_s *rtp) {
|
||||
janus_plugin_rtp packet = {0};
|
||||
packet.video = rtp->video;
|
||||
packet.buffer = (char *)rtp->datagram;
|
||||
packet.length = rtp->used;
|
||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
if (client->transmit) {
|
||||
_g_gw->relay_rtp(client->session, &packet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#define IF_NOT_REPORTED(...) { \
|
||||
unsigned _error_code = __LINE__; \
|
||||
if (error_reported != _error_code) { __VA_ARGS__; error_reported = _error_code; } \
|
||||
}
|
||||
|
||||
static void *_clients_video_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_v_clients");
|
||||
atomic_store(&_g_video_tid_created, true);
|
||||
atomic_store(&_g_ready, true);
|
||||
|
||||
frame_s *frame = frame_init();
|
||||
@@ -203,15 +217,9 @@ static void *_clients_thread(UNUSED void *arg) {
|
||||
|
||||
unsigned error_reported = 0;
|
||||
|
||||
# define IF_NOT_REPORTED(_error, ...) { \
|
||||
if (error_reported != _error) { __VA_ARGS__; error_reported = _error; } \
|
||||
}
|
||||
|
||||
while (!STOP) {
|
||||
if (!HAS_WATCHERS) {
|
||||
IF_NOT_REPORTED(1, {
|
||||
JLOG_INFO("No active watchers, memsink disconnected");
|
||||
});
|
||||
IF_NOT_REPORTED({ JLOG_INFO("video", "No active watchers, memsink disconnected"); });
|
||||
usleep(_g_watchers_polling);
|
||||
continue;
|
||||
}
|
||||
@@ -219,23 +227,19 @@ static void *_clients_thread(UNUSED void *arg) {
|
||||
int fd = -1;
|
||||
memsink_shared_s *mem = NULL;
|
||||
|
||||
if ((fd = shm_open(_g_memsink_obj, O_RDWR, 0)) <= 0) {
|
||||
IF_NOT_REPORTED(2, {
|
||||
JLOG_PERROR("Can't open memsink");
|
||||
});
|
||||
if ((fd = shm_open(_g_video_sink_name, O_RDWR, 0)) <= 0) {
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Can't open memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
if ((mem = memsink_shared_map(fd)) == NULL) {
|
||||
IF_NOT_REPORTED(3, {
|
||||
JLOG_PERROR("Can't map memsink");
|
||||
});
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Can't map memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
error_reported = 0;
|
||||
|
||||
JLOG_INFO("Memsink opened; reading frames ...");
|
||||
JLOG_INFO("video", "Memsink opened; reading frames ...");
|
||||
while (!STOP && HAS_WATCHERS) {
|
||||
int result = _wait_frame(fd, mem, frame_id);
|
||||
if (result == 0) {
|
||||
@@ -243,7 +247,7 @@ static void *_clients_thread(UNUSED void *arg) {
|
||||
goto close_memsink;
|
||||
}
|
||||
LOCK;
|
||||
rtp_wrap_h264(_g_rtp, frame, _relay_rtp_clients);
|
||||
rtpv_wrap(_g_rtpv, frame);
|
||||
UNLOCK;
|
||||
} else if (result == -1) {
|
||||
goto close_memsink;
|
||||
@@ -252,7 +256,7 @@ static void *_clients_thread(UNUSED void *arg) {
|
||||
|
||||
close_memsink:
|
||||
if (mem != NULL) {
|
||||
JLOG_INFO("Memsink closed");
|
||||
JLOG_INFO("video", "Memsink closed");
|
||||
memsink_shared_unmap(mem);
|
||||
mem = NULL;
|
||||
}
|
||||
@@ -263,35 +267,114 @@ static void *_clients_thread(UNUSED void *arg) {
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
|
||||
# undef IF_NOT_REPORTED
|
||||
|
||||
frame_destroy(frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_clients_audio_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_a_clients");
|
||||
atomic_store(&_g_audio_tid_created, true);
|
||||
assert(_g_audio_dev_name);
|
||||
assert(_g_tc358743_dev_path);
|
||||
|
||||
unsigned error_reported = 0;
|
||||
|
||||
while (!STOP) {
|
||||
if (!HAS_WATCHERS) {
|
||||
usleep(_g_watchers_polling);
|
||||
continue;
|
||||
}
|
||||
|
||||
tc358743_info_s info = {0};
|
||||
audio_s *audio = NULL;
|
||||
|
||||
if (tc358743_read_info(_g_tc358743_dev_path, &info) < 0) {
|
||||
goto close_audio;
|
||||
}
|
||||
if (!info.has_audio) {
|
||||
IF_NOT_REPORTED({ JLOG_INFO("audio", "No audio presented from the host"); });
|
||||
goto close_audio;
|
||||
}
|
||||
IF_NOT_REPORTED({ JLOG_INFO("audio", "Detected host audio"); });
|
||||
if ((audio = audio_init(_g_audio_dev_name, info.audio_hz)) == NULL) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
error_reported = 0;
|
||||
|
||||
while (!STOP && HAS_WATCHERS) {
|
||||
if (
|
||||
tc358743_read_info(_g_tc358743_dev_path, &info) < 0
|
||||
|| !info.has_audio
|
||||
|| audio->pcm_hz != info.audio_hz
|
||||
) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
size_t size = RTP_DATAGRAM_SIZE - RTP_HEADER_SIZE;
|
||||
uint8_t data[size];
|
||||
uint64_t pts;
|
||||
int result = audio_get_encoded(audio, data, &size, &pts);
|
||||
if (result == 0) {
|
||||
LOCK;
|
||||
rtpa_wrap(_g_rtpa, data, size, pts);
|
||||
UNLOCK;
|
||||
} else if (result == -1) {
|
||||
goto close_audio;
|
||||
}
|
||||
}
|
||||
|
||||
close_audio:
|
||||
if (audio != NULL) {
|
||||
audio_destroy(audio);
|
||||
}
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#undef IF_NOT_REPORTED
|
||||
|
||||
static char *_get_config_value(janus_config *config, const char *section, const char *option) {
|
||||
janus_config_category *section_obj = janus_config_get_create(config, NULL, janus_config_type_category, section);
|
||||
janus_config_item *option_obj = janus_config_get(config, section_obj, janus_config_type_item, option);
|
||||
if (option_obj == NULL || option_obj->value == NULL || option_obj->value[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
return strdup(option_obj->value);
|
||||
}
|
||||
|
||||
static int _read_config(const char *config_dir_path) {
|
||||
int retval = 0;
|
||||
|
||||
char *config_file_path;
|
||||
janus_config *config = NULL;
|
||||
|
||||
A_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, _plugin_get_package());
|
||||
JLOG_INFO("Reading config file '%s' ...", config_file_path);
|
||||
JLOG_INFO("main", "Reading config file '%s' ...", config_file_path);
|
||||
|
||||
config = janus_config_parse(config_file_path);
|
||||
if (config == NULL) {
|
||||
JLOG_ERROR("Can't read config");
|
||||
JLOG_ERROR("main", "Can't read config");
|
||||
goto error;
|
||||
}
|
||||
janus_config_print(config);
|
||||
|
||||
janus_config_category *config_memsink = janus_config_get_create(config, NULL, janus_config_type_category, "memsink");
|
||||
janus_config_item *config_memsink_obj = janus_config_get(config, config_memsink, janus_config_type_item, "object");
|
||||
if (config_memsink_obj == NULL || config_memsink_obj->value == NULL || config_memsink_obj->value[0] == '\0') {
|
||||
JLOG_ERROR("Missing config value: memsink.object");
|
||||
if (
|
||||
(_g_video_sink_name = _get_config_value(config, "memsink", "object")) == NULL
|
||||
&& (_g_video_sink_name = _get_config_value(config, "video", "sink")) == NULL
|
||||
) {
|
||||
JLOG_ERROR("main", "Missing config value: video.sink (ex. memsink.object)");
|
||||
goto error;
|
||||
}
|
||||
_g_memsink_obj = strdup(config_memsink_obj->value);
|
||||
if ((_g_audio_dev_name = _get_config_value(config, "audio", "device")) != NULL) {
|
||||
JLOG_INFO("main", "Enabled the experimental AUDIO feature");
|
||||
if ((_g_tc358743_dev_path = _get_config_value(config, "audio", "tc358743")) == NULL) {
|
||||
JLOG_INFO("main", "Missing config value: audio.tc358743");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
int retval = 0;
|
||||
goto ok;
|
||||
error:
|
||||
retval = -1;
|
||||
@@ -310,23 +393,32 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
|
||||
// sysctl -w net.core.rmem_max=1000000
|
||||
// sysctl -w net.core.wmem_max=1000000
|
||||
|
||||
JLOG_INFO("Initializing plugin ...");
|
||||
JLOG_INFO("main", "Initializing plugin ...");
|
||||
assert(!atomic_load(&_g_video_tid_created));
|
||||
assert(!atomic_load(&_g_audio_tid_created));
|
||||
assert(!READY);
|
||||
assert(!STOP);
|
||||
if (gw == NULL || config_dir_path == NULL || _read_config(config_dir_path) < 0) {
|
||||
return -1;
|
||||
}
|
||||
_g_gw = gw;
|
||||
_g_rtp = rtp_init();
|
||||
A_THREAD_CREATE(&_g_tid, _clients_thread, NULL);
|
||||
_g_rtpv = rtpv_init(_relay_rtp_clients);
|
||||
if (_g_audio_dev_name) {
|
||||
_g_rtpa = rtpa_init(_relay_rtp_clients);
|
||||
A_THREAD_CREATE(&_g_audio_tid, _clients_audio_thread, NULL);
|
||||
}
|
||||
A_THREAD_CREATE(&_g_video_tid, _clients_video_thread, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _plugin_destroy(void) {
|
||||
JLOG_INFO("Destroying plugin ...");
|
||||
JLOG_INFO("main", "Destroying plugin ...");
|
||||
atomic_store(&_g_stop, true);
|
||||
if (READY) {
|
||||
A_THREAD_JOIN(_g_tid);
|
||||
if (atomic_load(&_g_video_tid_created)) {
|
||||
A_THREAD_JOIN(_g_video_tid);
|
||||
}
|
||||
if (atomic_load(&_g_audio_tid_created)) {
|
||||
A_THREAD_JOIN(_g_audio_tid);
|
||||
}
|
||||
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
@@ -335,23 +427,22 @@ static void _plugin_destroy(void) {
|
||||
});
|
||||
_g_clients = NULL;
|
||||
|
||||
rtp_destroy(_g_rtp);
|
||||
_g_rtp = NULL;
|
||||
|
||||
# define DEL(_func, _var) { if (_var) { _func(_var); _var = NULL; } }
|
||||
DEL(rtpa_destroy, _g_rtpa);
|
||||
DEL(rtpv_destroy, _g_rtpv);
|
||||
_g_gw = NULL;
|
||||
|
||||
if (_g_memsink_obj) {
|
||||
free(_g_memsink_obj);
|
||||
_g_memsink_obj = NULL;
|
||||
}
|
||||
DEL(free, _g_tc358743_dev_path);
|
||||
DEL(free, _g_audio_dev_name);
|
||||
DEL(free, _g_video_sink_name);
|
||||
# undef DEL
|
||||
}
|
||||
|
||||
#define IF_DISABLED(...) { if (!READY || STOP) { __VA_ARGS__ } }
|
||||
|
||||
static void _plugin_create_session(janus_plugin_session *session, int *error) {
|
||||
IF_DISABLED({ *error = -1; return; });
|
||||
static void _plugin_create_session(janus_plugin_session *session, int *err) {
|
||||
IF_DISABLED({ *err = -1; return; });
|
||||
LOCK;
|
||||
JLOG_INFO("Creating session %p ...", session);
|
||||
JLOG_INFO("main", "Creating session %p ...", session);
|
||||
_client_s *client;
|
||||
A_CALLOC(client, 1);
|
||||
client->session = session;
|
||||
@@ -361,14 +452,14 @@ static void _plugin_create_session(janus_plugin_session *session, int *error) {
|
||||
UNLOCK;
|
||||
}
|
||||
|
||||
static void _plugin_destroy_session(janus_plugin_session* session, int *error) {
|
||||
IF_DISABLED({ *error = -1; return; });
|
||||
static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
|
||||
IF_DISABLED({ *err = -1; return; });
|
||||
LOCK;
|
||||
bool found = false;
|
||||
bool has_watchers = false;
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
JLOG_INFO("Removing session %p ...", session);
|
||||
JLOG_INFO("main", "Removing session %p ...", session);
|
||||
LIST_REMOVE(_g_clients, client);
|
||||
free(client);
|
||||
found = true;
|
||||
@@ -377,8 +468,8 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *error) {
|
||||
}
|
||||
});
|
||||
if (!found) {
|
||||
JLOG_WARN("No session %p", session);
|
||||
*error = -2;
|
||||
JLOG_WARN("main", "No session %p", session);
|
||||
*err = -2;
|
||||
}
|
||||
atomic_store(&_g_has_watchers, has_watchers);
|
||||
UNLOCK;
|
||||
@@ -406,13 +497,13 @@ static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg,
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
client->transmit = transmit;
|
||||
//JLOG_INFO("%s session %p", msg, session);
|
||||
// JLOG_INFO("main", "%s session %p", msg, session);
|
||||
found = true;
|
||||
}
|
||||
has_watchers = (has_watchers || client->transmit);
|
||||
});
|
||||
if (!found) {
|
||||
JLOG_WARN("No session %p", session);
|
||||
JLOG_WARN("main", "No session %p", session);
|
||||
}
|
||||
atomic_store(&_g_has_watchers, has_watchers);
|
||||
UNLOCK;
|
||||
@@ -440,7 +531,7 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
}
|
||||
|
||||
# define PUSH_ERROR(_error, _reason) { \
|
||||
/*JLOG_ERROR("Message error in session %p: %s", session, _reason);*/ \
|
||||
/*JLOG_ERROR("main", "Message error in session %p: %s", session, _reason);*/ \
|
||||
json_t *_event = json_object(); \
|
||||
json_object_set_new(_event, "ustreamer", json_string("event")); \
|
||||
json_object_set_new(_event, "error_code", json_integer(_error)); \
|
||||
@@ -460,7 +551,7 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
PUSH_ERROR(400, "Request not a string");
|
||||
goto ok_wait;
|
||||
}
|
||||
//JLOG_INFO("Message: %s", request_str);
|
||||
// JLOG_INFO("main", "Message: %s", request_str);
|
||||
|
||||
# define PUSH_STATUS(_status, _jsep) { \
|
||||
json_t *_event = json_object(); \
|
||||
@@ -479,12 +570,25 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
PUSH_STATUS("stopped", NULL);
|
||||
|
||||
} else if (!strcmp(request_str, "watch")) {
|
||||
char *sdp = rtp_make_sdp(_g_rtp);
|
||||
if (sdp == NULL) {
|
||||
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");
|
||||
goto ok_wait;
|
||||
char *sdp;
|
||||
{
|
||||
char *video_sdp = rtpv_make_sdp(_g_rtpv);
|
||||
if (video_sdp == NULL) {
|
||||
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");
|
||||
goto ok_wait;
|
||||
}
|
||||
char *audio_sdp = (_g_rtpa ? rtpa_make_sdp(_g_rtpa) : strdup(""));
|
||||
A_ASPRINTF(sdp,
|
||||
"v=0" RN
|
||||
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
|
||||
"s=PiKVM uStreamer" RN
|
||||
"t=0 0" RN
|
||||
"%s%s",
|
||||
get_now_id() >> 1, audio_sdp, video_sdp
|
||||
);
|
||||
free(audio_sdp);
|
||||
free(video_sdp);
|
||||
}
|
||||
//JLOG_INFO("SDP generated:\n%s", sdp);
|
||||
json_t *offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
|
||||
free(sdp);
|
||||
PUSH_STATUS("started", offer_jsep);
|
||||
@@ -501,22 +605,3 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
# undef PUSH_ERROR
|
||||
# undef FREE_MSG_JSEP
|
||||
}
|
||||
|
||||
static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; }
|
||||
static int _plugin_get_version(void) { return VERSION_U; }
|
||||
static const char *_plugin_get_version_string(void) { return VERSION; }
|
||||
static const char *_plugin_get_description(void) { return "Pi-KVM uStreamer Janus plugin for H.264 video"; }
|
||||
static const char *_plugin_get_name(void) { return "ustreamer"; }
|
||||
static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
|
||||
static const char *_plugin_get_package(void) { return "janus.plugin.ustreamer"; }
|
||||
|
||||
|
||||
#undef STOP
|
||||
#undef READY
|
||||
#undef UNLOCK
|
||||
#undef LOCK
|
||||
|
||||
#undef JLOG_PERROR
|
||||
#undef JLOG_ERROR
|
||||
#undef JLOG_WARN
|
||||
#undef JLOG_INFO
|
||||
|
||||
92
janus/src/queue.c
Normal file
92
janus/src/queue.c
Normal file
@@ -0,0 +1,92 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 "queue.h"
|
||||
|
||||
|
||||
queue_s *queue_init(unsigned capacity) {
|
||||
queue_s *queue;
|
||||
A_CALLOC(queue, 1);
|
||||
A_CALLOC(queue->items, capacity);
|
||||
queue->capacity = capacity;
|
||||
A_MUTEX_INIT(&queue->mutex);
|
||||
|
||||
pthread_condattr_t attrs;
|
||||
assert(!pthread_condattr_init(&attrs));
|
||||
assert(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
|
||||
assert(!pthread_cond_init(&queue->full_cond, &attrs));
|
||||
assert(!pthread_cond_init(&queue->empty_cond, &attrs));
|
||||
assert(!pthread_condattr_destroy(&attrs));
|
||||
return queue;
|
||||
}
|
||||
|
||||
void queue_destroy(queue_s *queue) {
|
||||
free(queue->items);
|
||||
free(queue);
|
||||
}
|
||||
|
||||
#define WAIT_OR_UNLOCK(_var, _cond) { \
|
||||
struct timespec ts; \
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &ts)); \
|
||||
ts.tv_sec += timeout; \
|
||||
while (_var) { \
|
||||
int err = pthread_cond_timedwait(_cond, &queue->mutex, &ts); \
|
||||
if (err == ETIMEDOUT) { \
|
||||
A_MUTEX_UNLOCK(&queue->mutex); \
|
||||
return -1; \
|
||||
} \
|
||||
assert(!err); \
|
||||
} \
|
||||
}
|
||||
|
||||
int queue_put(queue_s *queue, void *item, unsigned timeout) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
WAIT_OR_UNLOCK(queue->size == queue->capacity, &queue->full_cond);
|
||||
queue->items[queue->in] = item;
|
||||
++queue->size;
|
||||
++queue->in;
|
||||
queue->in %= queue->capacity;
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
assert(!pthread_cond_broadcast(&queue->empty_cond));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int queue_get(queue_s *queue, void **item, unsigned timeout) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
WAIT_OR_UNLOCK(queue->size == 0, &queue->empty_cond);
|
||||
*item = queue->items[queue->out];
|
||||
--queue->size;
|
||||
++queue->out;
|
||||
queue->out %= queue->capacity;
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
assert(!pthread_cond_broadcast(&queue->full_cond));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef WAIT_OR_UNLOCK
|
||||
|
||||
int queue_get_free(queue_s *queue) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
unsigned size = queue->size;
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
return queue->capacity - size;
|
||||
}
|
||||
54
janus/src/queue.h
Normal file
54
janus/src/queue.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
|
||||
// Based on https://github.com/seifzadeh/c-pthread-queue/blob/master/queue.h
|
||||
|
||||
typedef struct {
|
||||
void **items;
|
||||
unsigned size;
|
||||
unsigned capacity;
|
||||
unsigned in;
|
||||
unsigned out;
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t full_cond;
|
||||
pthread_cond_t empty_cond;
|
||||
} queue_s;
|
||||
|
||||
|
||||
queue_s *queue_init(unsigned capacity);
|
||||
void queue_destroy(queue_s *queue);
|
||||
|
||||
int queue_put(queue_s *queue, void *item, unsigned timeout);
|
||||
int queue_get(queue_s *queue, void **item, unsigned timeout);
|
||||
int queue_get_free(queue_s *queue);
|
||||
186
janus/src/rtp.c
186
janus/src/rtp.c
@@ -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 #
|
||||
@@ -26,182 +26,25 @@
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
#define PAYLOAD 96 // Payload type
|
||||
#define PRE 3 // Annex B prefix length
|
||||
|
||||
|
||||
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback);
|
||||
static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked);
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
||||
|
||||
|
||||
rtp_s *rtp_init(void) {
|
||||
rtp_s *rtp_init(unsigned payload, bool video) {
|
||||
rtp_s *rtp;
|
||||
A_CALLOC(rtp, 1);
|
||||
rtp->payload = payload;
|
||||
rtp->video = video;
|
||||
rtp->ssrc = triple_u32(get_now_monotonic_u64());
|
||||
rtp->sps = frame_init();
|
||||
rtp->pps = frame_init();
|
||||
A_MUTEX_INIT(&rtp->mutex);
|
||||
return rtp;
|
||||
}
|
||||
|
||||
void rtp_destroy(rtp_s *rtp) {
|
||||
A_MUTEX_DESTROY(&rtp->mutex);
|
||||
frame_destroy(rtp->pps);
|
||||
frame_destroy(rtp->sps);
|
||||
free(rtp);
|
||||
}
|
||||
|
||||
char *rtp_make_sdp(rtp_s *rtp) {
|
||||
A_MUTEX_LOCK(&rtp->mutex);
|
||||
|
||||
if (rtp->sps->used == 0 || rtp->pps->used == 0) {
|
||||
A_MUTEX_UNLOCK(&rtp->mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *sps = NULL;
|
||||
char *pps = NULL;
|
||||
base64_encode(rtp->sps->data, rtp->sps->used, &sps, NULL);
|
||||
base64_encode(rtp->pps->data, rtp->pps->used, &pps, NULL);
|
||||
|
||||
A_MUTEX_UNLOCK(&rtp->mutex);
|
||||
|
||||
// https://tools.ietf.org/html/rfc6184
|
||||
// https://github.com/meetecho/janus-gateway/issues/2443
|
||||
char *sdp;
|
||||
A_ASPRINTF(sdp,
|
||||
"v=0" RN
|
||||
"o=- %" PRIu64 " 1 IN IP4 127.0.0.1" RN
|
||||
"s=Pi-KVM uStreamer" RN
|
||||
"t=0 0" RN
|
||||
"m=video 1 RTP/SAVPF %d" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%d H264/90000" RN
|
||||
"a=fmtp:%d profile-level-id=42E01F" RN
|
||||
"a=fmtp:%d packetization-mode=1" RN
|
||||
"a=fmtp:%d sprop-sps=%s" RN
|
||||
"a=fmtp:%d sprop-pps=%s" RN
|
||||
"a=rtcp-fb:%d nack" RN
|
||||
"a=rtcp-fb:%d nack pli" RN
|
||||
"a=rtcp-fb:%d goog-remb" RN
|
||||
"a=sendonly" RN,
|
||||
get_now_id() >> 1, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
PAYLOAD, sps,
|
||||
PAYLOAD, pps,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD
|
||||
);
|
||||
|
||||
free(sps);
|
||||
free(pps);
|
||||
return sdp;
|
||||
}
|
||||
|
||||
void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) {
|
||||
// There is a complicated logic here but everything works as it should:
|
||||
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
|
||||
|
||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||
|
||||
const uint32_t pts = get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
ssize_t last_offset = -PRE;
|
||||
|
||||
while (true) { // Find and iterate by nalus
|
||||
const size_t next_start = last_offset + PRE;
|
||||
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
|
||||
if (offset < 0) {
|
||||
break;
|
||||
}
|
||||
offset += next_start;
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *data = frame->data + last_offset + PRE;
|
||||
size_t size = offset - last_offset - PRE;
|
||||
if (data[size - 1] == 0) { // Check for extra 00
|
||||
--size;
|
||||
}
|
||||
_rtp_process_nalu(rtp, data, size, pts, false, callback);
|
||||
}
|
||||
|
||||
last_offset = offset;
|
||||
}
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *data = frame->data + last_offset + PRE;
|
||||
size_t size = frame->used - last_offset - PRE;
|
||||
_rtp_process_nalu(rtp, data, size, pts, true, callback);
|
||||
}
|
||||
}
|
||||
|
||||
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback) {
|
||||
const unsigned ref_idc = (data[0] >> 5) & 3;
|
||||
const unsigned type = data[0] & 0x1F;
|
||||
|
||||
frame_s *ps = NULL;
|
||||
switch (type) {
|
||||
case 7: ps = rtp->sps; break;
|
||||
case 8: ps = rtp->pps; break;
|
||||
}
|
||||
if (ps) {
|
||||
A_MUTEX_LOCK(&rtp->mutex);
|
||||
frame_set_data(ps, data, size);
|
||||
A_MUTEX_UNLOCK(&rtp->mutex);
|
||||
}
|
||||
|
||||
# define HEADER_SIZE 12
|
||||
|
||||
if (size + HEADER_SIZE <= RTP_DATAGRAM_SIZE) {
|
||||
_rtp_write_header(rtp, pts, marked);
|
||||
memcpy(rtp->datagram + HEADER_SIZE, data, size);
|
||||
callback(rtp->datagram, size + HEADER_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t fu_overhead = HEADER_SIZE + 2; // FU-A overhead
|
||||
|
||||
const uint8_t *src = data + 1;
|
||||
ssize_t remaining = size - 1;
|
||||
|
||||
bool first = true;
|
||||
while (remaining > 0) {
|
||||
ssize_t frag_size = RTP_DATAGRAM_SIZE - fu_overhead;
|
||||
const bool last = (remaining <= frag_size);
|
||||
if (last) {
|
||||
frag_size = remaining;
|
||||
}
|
||||
|
||||
_rtp_write_header(rtp, pts, (marked && last));
|
||||
|
||||
rtp->datagram[HEADER_SIZE] = 28 | (ref_idc << 5);
|
||||
|
||||
uint8_t fu = type;
|
||||
if (first) {
|
||||
fu |= 0x80;
|
||||
}
|
||||
if (last) {
|
||||
fu |= 0x40;
|
||||
}
|
||||
rtp->datagram[HEADER_SIZE + 1] = fu;
|
||||
|
||||
memcpy(rtp->datagram + fu_overhead, src, frag_size);
|
||||
|
||||
callback(rtp->datagram, fu_overhead + frag_size);
|
||||
|
||||
src += frag_size;
|
||||
remaining -= frag_size;
|
||||
first = false;
|
||||
}
|
||||
|
||||
# undef HEADER_SIZE
|
||||
}
|
||||
|
||||
static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked) {
|
||||
void rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked) {
|
||||
uint32_t word0 = 0x80000000;
|
||||
if (marked) {
|
||||
word0 |= 1 << 23;
|
||||
}
|
||||
word0 |= (PAYLOAD & 0x7F) << 16;
|
||||
word0 |= (rtp->payload & 0x7F) << 16;
|
||||
word0 |= rtp->seq;
|
||||
++rtp->seq;
|
||||
|
||||
@@ -211,18 +54,3 @@ static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked) {
|
||||
WRITE_BE_U32(8, rtp->ssrc);
|
||||
# undef WRITE_BE_U32
|
||||
}
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
|
||||
// Parses buffer for 00 00 01 start codes
|
||||
if (size >= PRE) {
|
||||
for (size_t index = 0; index <= size - PRE; ++index) {
|
||||
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef PRE
|
||||
#undef PAYLOAD
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 #
|
||||
@@ -26,44 +23,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
#include "frame.h"
|
||||
#include "base64.h"
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
|
||||
#define RTP_DATAGRAM_SIZE 1200
|
||||
#define RTP_DATAGRAM_SIZE 1200
|
||||
#define RTP_HEADER_SIZE 12
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t ssrc;
|
||||
uint16_t seq;
|
||||
unsigned payload;
|
||||
bool video;
|
||||
uint32_t ssrc;
|
||||
|
||||
uint8_t datagram[RTP_DATAGRAM_SIZE];
|
||||
|
||||
frame_s *sps; // Actually not a frame, just a bytes storage
|
||||
frame_s *pps;
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
uint16_t seq;
|
||||
uint8_t datagram[RTP_DATAGRAM_SIZE];
|
||||
size_t used;
|
||||
} rtp_s;
|
||||
|
||||
typedef void (*rtp_callback_f)(const uint8_t *datagram, size_t size);
|
||||
typedef void (*rtp_callback_f)(const rtp_s *rtp);
|
||||
|
||||
|
||||
rtp_s *rtp_init(void);
|
||||
rtp_s *rtp_init(unsigned payload, bool video);
|
||||
void rtp_destroy(rtp_s *rtp);
|
||||
|
||||
char *rtp_make_sdp(rtp_s *rtp);
|
||||
void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback);
|
||||
void rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked);
|
||||
|
||||
66
janus/src/rtpa.c
Normal file
66
janus/src/rtpa.c
Normal file
@@ -0,0 +1,66 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 "rtpa.h"
|
||||
|
||||
|
||||
rtpa_s *rtpa_init(rtp_callback_f callback) {
|
||||
rtpa_s *rtpa;
|
||||
A_CALLOC(rtpa, 1);
|
||||
rtpa->rtp = rtp_init(111, false);
|
||||
rtpa->callback = callback;
|
||||
return rtpa;
|
||||
}
|
||||
|
||||
void rtpa_destroy(rtpa_s *rtpa) {
|
||||
rtp_destroy(rtpa->rtp);
|
||||
free(rtpa);
|
||||
}
|
||||
|
||||
char *rtpa_make_sdp(rtpa_s *rtpa) {
|
||||
# define PAYLOAD rtpa->rtp->payload
|
||||
char *sdp;
|
||||
A_ASPRINTF(sdp,
|
||||
"m=audio 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u OPUS/48000/2" RN
|
||||
// "a=fmtp:%u useinbandfec=1" RN
|
||||
"a=rtcp-fb:%u nack" RN
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=sendonly" RN,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, // PAYLOAD,
|
||||
rtpa->rtp->ssrc
|
||||
);
|
||||
# undef PAYLOAD
|
||||
return sdp;
|
||||
}
|
||||
|
||||
void rtpa_wrap(rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
|
||||
if (size + RTP_HEADER_SIZE <= RTP_DATAGRAM_SIZE) {
|
||||
rtp_write_header(rtpa->rtp, pts, false);
|
||||
memcpy(rtpa->rtp->datagram + RTP_HEADER_SIZE, data, size);
|
||||
rtpa->rtp->used = size + RTP_HEADER_SIZE;
|
||||
rtpa->callback(rtpa->rtp);
|
||||
}
|
||||
}
|
||||
@@ -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,21 +22,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <IL/OMX_IVCommon.h>
|
||||
#include <IL/OMX_Core.h>
|
||||
#include <IL/OMX_Image.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "../../../libs/tools.h"
|
||||
#include "../../../libs/logging.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
#define LOG_ERROR_OMX(_error, _msg, ...) { \
|
||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, omx_error_to_string(_error)); \
|
||||
}
|
||||
typedef struct {
|
||||
rtp_s *rtp;
|
||||
rtp_callback_f callback;
|
||||
} rtpa_s;
|
||||
|
||||
|
||||
const char *omx_error_to_string(OMX_ERRORTYPE error);
|
||||
const char *omx_state_to_string(OMX_STATETYPE state);
|
||||
rtpa_s *rtpa_init(rtp_callback_f callback);
|
||||
void rtpa_destroy(rtpa_s *rtpa);
|
||||
|
||||
char *rtpa_make_sdp(rtpa_s *rtpa);
|
||||
void rtpa_wrap(rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);
|
||||
211
janus/src/rtpv.c
Normal file
211
janus/src/rtpv.c
Normal file
@@ -0,0 +1,211 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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-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 "rtpv.h"
|
||||
|
||||
|
||||
void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
||||
|
||||
|
||||
rtpv_s *rtpv_init(rtp_callback_f callback) {
|
||||
rtpv_s *rtpv;
|
||||
A_CALLOC(rtpv, 1);
|
||||
rtpv->rtp = rtp_init(96, true);
|
||||
rtpv->callback = callback;
|
||||
rtpv->sps = frame_init();
|
||||
rtpv->pps = frame_init();
|
||||
A_MUTEX_INIT(&rtpv->mutex);
|
||||
return rtpv;
|
||||
}
|
||||
|
||||
void rtpv_destroy(rtpv_s *rtpv) {
|
||||
A_MUTEX_DESTROY(&rtpv->mutex);
|
||||
frame_destroy(rtpv->pps);
|
||||
frame_destroy(rtpv->sps);
|
||||
rtp_destroy(rtpv->rtp);
|
||||
free(rtpv);
|
||||
}
|
||||
|
||||
char *rtpv_make_sdp(rtpv_s *rtpv) {
|
||||
A_MUTEX_LOCK(&rtpv->mutex);
|
||||
|
||||
if (rtpv->sps->used == 0 || rtpv->pps->used == 0) {
|
||||
A_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *sps = NULL;
|
||||
char *pps = NULL;
|
||||
base64_encode(rtpv->sps->data, rtpv->sps->used, &sps, NULL);
|
||||
base64_encode(rtpv->pps->data, rtpv->pps->used, &pps, NULL);
|
||||
|
||||
A_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
|
||||
# define PAYLOAD rtpv->rtp->payload
|
||||
// https://tools.ietf.org/html/rfc6184
|
||||
// https://github.com/meetecho/janus-gateway/issues/2443
|
||||
char *sdp;
|
||||
A_ASPRINTF(sdp,
|
||||
"m=video 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u H264/90000" RN
|
||||
"a=fmtp:%u profile-level-id=42E01F" RN
|
||||
"a=fmtp:%u packetization-mode=1" RN
|
||||
"a=fmtp:%u sprop-sps=%s" RN
|
||||
"a=fmtp:%u sprop-pps=%s" RN
|
||||
"a=rtcp-fb:%u nack" RN
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=sendonly" RN,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
PAYLOAD, sps,
|
||||
PAYLOAD, pps,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
rtpv->rtp->ssrc
|
||||
);
|
||||
# undef PAYLOAD
|
||||
|
||||
free(sps);
|
||||
free(pps);
|
||||
return sdp;
|
||||
}
|
||||
|
||||
#define PRE 3 // Annex B prefix length
|
||||
|
||||
void rtpv_wrap(rtpv_s *rtpv, const frame_s *frame) {
|
||||
// There is a complicated logic here but everything works as it should:
|
||||
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
|
||||
|
||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||
|
||||
const uint32_t pts = get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
ssize_t last_offset = -PRE;
|
||||
|
||||
while (true) { // Find and iterate by nalus
|
||||
const size_t next_start = last_offset + PRE;
|
||||
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
|
||||
if (offset < 0) {
|
||||
break;
|
||||
}
|
||||
offset += next_start;
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *data = frame->data + last_offset + PRE;
|
||||
size_t size = offset - last_offset - PRE;
|
||||
if (data[size - 1] == 0) { // Check for extra 00
|
||||
--size;
|
||||
}
|
||||
_rtpv_process_nalu(rtpv, data, size, pts, false);
|
||||
}
|
||||
|
||||
last_offset = offset;
|
||||
}
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *data = frame->data + last_offset + PRE;
|
||||
size_t size = frame->used - last_offset - PRE;
|
||||
_rtpv_process_nalu(rtpv, data, size, pts, true);
|
||||
}
|
||||
}
|
||||
|
||||
void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
|
||||
const unsigned ref_idc = (data[0] >> 5) & 3;
|
||||
const unsigned type = data[0] & 0x1F;
|
||||
|
||||
frame_s *ps = NULL;
|
||||
switch (type) {
|
||||
case 7: ps = rtpv->sps; break;
|
||||
case 8: ps = rtpv->pps; break;
|
||||
}
|
||||
if (ps) {
|
||||
A_MUTEX_LOCK(&rtpv->mutex);
|
||||
frame_set_data(ps, data, size);
|
||||
A_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
}
|
||||
|
||||
# define DG rtpv->rtp->datagram
|
||||
|
||||
if (size + RTP_HEADER_SIZE <= RTP_DATAGRAM_SIZE) {
|
||||
rtp_write_header(rtpv->rtp, pts, marked);
|
||||
memcpy(DG + RTP_HEADER_SIZE, data, size);
|
||||
rtpv->rtp->used = size + RTP_HEADER_SIZE;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t fu_overhead = RTP_HEADER_SIZE + 2; // FU-A overhead
|
||||
|
||||
const uint8_t *src = data + 1;
|
||||
ssize_t remaining = size - 1;
|
||||
|
||||
bool first = true;
|
||||
while (remaining > 0) {
|
||||
ssize_t frag_size = RTP_DATAGRAM_SIZE - fu_overhead;
|
||||
const bool last = (remaining <= frag_size);
|
||||
if (last) {
|
||||
frag_size = remaining;
|
||||
}
|
||||
|
||||
rtp_write_header(rtpv->rtp, pts, (marked && last));
|
||||
|
||||
DG[RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
|
||||
|
||||
uint8_t fu = type;
|
||||
if (first) {
|
||||
fu |= 0x80;
|
||||
}
|
||||
if (last) {
|
||||
fu |= 0x40;
|
||||
}
|
||||
DG[RTP_HEADER_SIZE + 1] = fu;
|
||||
|
||||
memcpy(DG + fu_overhead, src, frag_size);
|
||||
rtpv->rtp->used = fu_overhead + frag_size;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
|
||||
src += frag_size;
|
||||
remaining -= frag_size;
|
||||
first = false;
|
||||
}
|
||||
|
||||
# undef DG
|
||||
}
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
|
||||
// Parses buffer for 00 00 01 start codes
|
||||
if (size >= PRE) {
|
||||
for (size_t index = 0; index <= size - PRE; ++index) {
|
||||
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef PRE
|
||||
57
janus/src/rtpv.h
Normal file
57
janus/src/rtpv.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/base64.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
rtp_s *rtp;
|
||||
rtp_callback_f callback;
|
||||
frame_s *sps; // Actually not a frame, just a bytes storage
|
||||
frame_s *pps;
|
||||
pthread_mutex_t mutex;
|
||||
} rtpv_s;
|
||||
|
||||
|
||||
rtpv_s *rtpv_init(rtp_callback_f callback);
|
||||
void rtpv_destroy(rtpv_s *rtpv);
|
||||
|
||||
char *rtpv_make_sdp(rtpv_s *rtpv);
|
||||
void rtpv_wrap(rtpv_s *rtpv, const 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 #
|
||||
@@ -20,36 +20,45 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "vcos.h"
|
||||
#include "tc358743.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
|
||||
// Три стула.
|
||||
#ifndef V4L2_CID_USER_TC358743_BASE
|
||||
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
|
||||
#endif
|
||||
#ifndef TC358743_CID_AUDIO_PRESENT
|
||||
# define TC358743_CID_AUDIO_PRESENT (V4L2_CID_USER_TC358743_BASE + 1)
|
||||
#endif
|
||||
#ifndef TC358743_CID_AUDIO_SAMPLING_RATE
|
||||
# define TC358743_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_TC358743_BASE + 0)
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
int tc358743_read_info(const char *path, tc358743_info_s *info) {
|
||||
MEMSET_ZERO(*info);
|
||||
|
||||
int fd = -1;
|
||||
if ((fd = open(path, O_RDWR)) < 0) {
|
||||
JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
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;
|
||||
# define READ_CID(_cid, _field) { \
|
||||
struct v4l2_control ctl = {0}; \
|
||||
ctl.id = _cid; \
|
||||
if (xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) { \
|
||||
JLOG_PERROR("audio", "Can't get value of " #_cid); \
|
||||
close(fd); \
|
||||
return -1; \
|
||||
} \
|
||||
info->_field = ctl.value; \
|
||||
}
|
||||
|
||||
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
|
||||
READ_CID(TC358743_CID_AUDIO_SAMPLING_RATE, audio_hz);
|
||||
|
||||
# undef READ_CID
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
@@ -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,25 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <interface/vcos/vcos_semaphore.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "../../../libs/tools.h"
|
||||
#include "../../../libs/logging.h"
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/xioctl.h"
|
||||
|
||||
#include "jlogging.h"
|
||||
|
||||
|
||||
int vcos_my_semwait(const char *prefix, VCOS_SEMAPHORE_T *sem, long double timeout);
|
||||
typedef struct {
|
||||
bool has_audio;
|
||||
unsigned audio_hz;
|
||||
} tc358743_info_s;
|
||||
|
||||
|
||||
int tc358743_read_info(const char *path, tc358743_info_s *info);
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/threading.h
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/tools.h
|
||||
1
janus/src/uslibs/base64.c
Symbolic link
1
janus/src/uslibs/base64.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/base64.c
|
||||
1
janus/src/uslibs/base64.h
Symbolic link
1
janus/src/uslibs/base64.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/base64.h
|
||||
1
janus/src/uslibs/config.h
Symbolic link
1
janus/src/uslibs/config.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/config.h
|
||||
1
janus/src/uslibs/frame.c
Symbolic link
1
janus/src/uslibs/frame.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.c
|
||||
1
janus/src/uslibs/frame.h
Symbolic link
1
janus/src/uslibs/frame.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.h
|
||||
1
janus/src/uslibs/list.h
Symbolic link
1
janus/src/uslibs/list.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/list.h
|
||||
1
janus/src/uslibs/memsinksh.h
Symbolic link
1
janus/src/uslibs/memsinksh.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/memsinksh.h
|
||||
1
janus/src/uslibs/threading.h
Symbolic link
1
janus/src/uslibs/threading.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/threading.h
|
||||
1
janus/src/uslibs/tools.h
Symbolic link
1
janus/src/uslibs/tools.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/tools.h
|
||||
1
janus/src/uslibs/xioctl.h
Symbolic link
1
janus/src/uslibs/xioctl.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/xioctl.h
|
||||
@@ -1,4 +1,6 @@
|
||||
#define CHAR_BIT 8
|
||||
#define WITH_OMX
|
||||
#define WITH_GPIO
|
||||
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
|
||||
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1
|
||||
#define CLOCK_MONOTONIC_RAW 1
|
||||
#define CLOCK_MONOTONIC_FAST 1
|
||||
|
||||
@@ -15,15 +15,8 @@ disable =
|
||||
locally-disabled,
|
||||
fixme,
|
||||
missing-docstring,
|
||||
no-init,
|
||||
no-self-use,
|
||||
superfluous-parens,
|
||||
abstract-class-not-used,
|
||||
abstract-class-little-used,
|
||||
duplicate-code,
|
||||
bad-continuation,
|
||||
bad-whitespace,
|
||||
star-args,
|
||||
broad-except,
|
||||
redundant-keyword-arg,
|
||||
wrong-import-order,
|
||||
@@ -39,9 +32,6 @@ msg-template = {symbol} -- {path}:{line}({obj}): {msg}
|
||||
max-line-length = 160
|
||||
|
||||
[BASIC]
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions =
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names = _, __, x, y, ws, make-html-h, make-jpeg-h
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ changedir = /src
|
||||
[testenv:cppcheck]
|
||||
whitelist_externals = cppcheck
|
||||
commands = cppcheck \
|
||||
-j4 \
|
||||
--force \
|
||||
--std=c11 \
|
||||
--std=c17 \
|
||||
--error-exitcode=1 \
|
||||
--quiet \
|
||||
--enable=warning,unusedFunction,portability,performance,style \
|
||||
@@ -19,7 +20,7 @@ commands = cppcheck \
|
||||
--inline-suppr \
|
||||
--library=python \
|
||||
--include=linters/cppcheck.h \
|
||||
src python/ustreamer.c janus/rtp.h janus/rtp.c janus/plugin.c
|
||||
src python/*.? janus/*.?
|
||||
|
||||
[testenv:flake8]
|
||||
whitelist_externals = bash
|
||||
|
||||
@@ -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.13" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 5.10" "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.13" "November 2020"
|
||||
.TH USTREAMER 1 "version 5.10" "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
|
||||
@@ -221,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.
|
||||
@@ -243,6 +246,10 @@ 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
|
||||
|
||||
@@ -3,39 +3,29 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=4.13
|
||||
pkgver=5.10
|
||||
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)
|
||||
depends=(libjpeg libevent libbsd libgpiod)
|
||||
makedepends=(gcc make)
|
||||
depends=(libjpeg libevent libbsd libgpiod systemd)
|
||||
makedepends=(gcc make systemd)
|
||||
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
|
||||
md5sums=(SKIP)
|
||||
|
||||
|
||||
_options="WITH_GPIO=1"
|
||||
_options="WITH_GPIO=1 WITH_SYSTEMD=1"
|
||||
if [ -e /usr/bin/python3 ]; then
|
||||
_options="$_options WITH_PYTHON=1"
|
||||
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)
|
||||
depends+=(janus-gateway-pikvm alsa-lib opus)
|
||||
makedepends+=(janus-gateway-pikvm alsa-lib opus)
|
||||
_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.13
|
||||
PKG_VERSION:=5.10
|
||||
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,25 @@
|
||||
import os
|
||||
|
||||
from distutils.core import Extension
|
||||
from distutils.core import setup
|
||||
from typing import List
|
||||
|
||||
from setuptools import Extension
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
# =====
|
||||
def _find_sources(suffix: str) -> List[str]:
|
||||
sources: List[str] = []
|
||||
for (root_path, _, names) in os.walk("src"):
|
||||
for name in names:
|
||||
if name.endswith(suffix):
|
||||
sources.append(os.path.join(root_path, name))
|
||||
return sources
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="4.13",
|
||||
version="5.10",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
@@ -17,9 +28,10 @@ if __name__ == "__main__":
|
||||
Extension(
|
||||
"ustreamer",
|
||||
libraries=["rt", "m", "pthread"],
|
||||
extra_compile_args=["-std=c17", "-D_GNU_SOURCE"],
|
||||
undef_macros=["NDEBUG"],
|
||||
sources=["src/" + name for name in os.listdir("src") if name.endswith(".c")],
|
||||
depends=["src/" + name for name in os.listdir("src") if name.endswith(".h")],
|
||||
sources=_find_sources(".c"),
|
||||
depends=_find_sources(".h"),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/frame.c
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/frame.h
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/memsinksh.h
|
||||
@@ -1 +0,0 @@
|
||||
../../src/libs/tools.h
|
||||
1
python/src/uslibs/frame.c
Symbolic link
1
python/src/uslibs/frame.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.c
|
||||
1
python/src/uslibs/frame.h
Symbolic link
1
python/src/uslibs/frame.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/frame.h
|
||||
1
python/src/uslibs/memsinksh.h
Symbolic link
1
python/src/uslibs/memsinksh.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/memsinksh.h
|
||||
1
python/src/uslibs/tools.h
Symbolic link
1
python/src/uslibs/tools.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/tools.h
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "frame.h"
|
||||
#include "memsinksh.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
||||
16
src/Makefile
16
src/Makefile
@@ -5,15 +5,12 @@ CC ?= gcc
|
||||
CFLAGS ?= -O3
|
||||
LDFLAGS ?=
|
||||
|
||||
RPI_VC_HEADERS ?= /opt/vc/include
|
||||
RPI_VC_LIBS ?= /opt/vc/lib
|
||||
|
||||
|
||||
# =====
|
||||
_USTR = ustreamer.bin
|
||||
_DUMP = ustreamer-dump.bin
|
||||
|
||||
_CFLAGS = -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
||||
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
||||
_LDFLAGS = $(LDFLAGS)
|
||||
|
||||
_COMMON_LIBS = -lm -ljpeg -pthread -lrt
|
||||
@@ -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
|
||||
|
||||
@@ -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 13
|
||||
#define VERSION_MAJOR 5
|
||||
#define VERSION_MINOR 10
|
||||
|
||||
#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;
|
||||
@@ -83,6 +84,21 @@ static 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);
|
||||
|
||||
|
||||
@@ -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 #
|
||||
|
||||
@@ -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 #
|
||||
@@ -67,7 +67,7 @@
|
||||
#define A_COND_INIT(_cond) assert(!pthread_cond_init(_cond, NULL))
|
||||
#define A_COND_DESTROY(_cond) assert(!pthread_cond_destroy(_cond))
|
||||
#define A_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
|
||||
#define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); }
|
||||
#define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!(_var)) assert(!pthread_cond_wait(_cond, _mutex)); }
|
||||
|
||||
|
||||
#ifdef WITH_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 #
|
||||
@@ -28,15 +28,26 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <locale.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
|
||||
#ifdef NDEBUG
|
||||
# error WTF dude? Asserts are good things!
|
||||
#endif
|
||||
|
||||
#if CHAR_BIT != 8
|
||||
# error There are not 8 bits in a char!
|
||||
#endif
|
||||
|
||||
|
||||
#define RN "\r\n"
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
@@ -116,13 +127,13 @@ INLINE uint64_t get_now_monotonic_u64(void) {
|
||||
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
|
||||
}
|
||||
|
||||
#undef X_CLOCK_MONOTONIC
|
||||
|
||||
INLINE uint64_t get_now_id(void) {
|
||||
uint64_t now = get_now_monotonic_u64();
|
||||
return (uint64_t)triple_u32(now) | ((uint64_t)triple_u32(now + 12345) << 32);
|
||||
}
|
||||
|
||||
#undef X_CLOCK_MONOTONIC
|
||||
|
||||
INLINE long double get_now_real(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
|
||||
@@ -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 #
|
||||
@@ -26,8 +26,7 @@
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#ifndef CFG_XIOCTL_RETRIES
|
||||
@@ -51,10 +50,5 @@ INLINE int xioctl(int fd, int request, void *arg) {
|
||||
|| errno == ETIMEDOUT
|
||||
)
|
||||
);
|
||||
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (retval && retries <= 0) {
|
||||
LOG_PERROR("ioctl(%d) retried %u times; giving up", request, XIOCTL_RETRIES);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
@@ -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 #
|
||||
@@ -80,7 +80,8 @@ static const char *_standard_to_string(v4l2_std_id standard);
|
||||
static const char *_io_method_to_string_supported(enum v4l2_memory io_method);
|
||||
|
||||
|
||||
#define RUN(_next) dev->run->_next
|
||||
#define RUN(_next) dev->run->_next
|
||||
#define D_XIOCTL(...) xioctl(RUN(fd), __VA_ARGS__)
|
||||
|
||||
|
||||
device_s *device_init(void) {
|
||||
@@ -178,21 +179,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 +195,6 @@ void device_close(device_s *dev) {
|
||||
free(HW(raw.data));
|
||||
}
|
||||
}
|
||||
A_MUTEX_DESTROY(&HW(grabbed_mutex));
|
||||
|
||||
# undef HW
|
||||
}
|
||||
@@ -220,41 +214,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);
|
||||
if (xioctl(RUN(fd), VIDIOC_EXPBUF, &exp) < 0) {
|
||||
LOG_PERROR("Unable to export device buffer index=%u", index);
|
||||
LOG_DEBUG("Exporting device buffer=%u to DMA ...", index);
|
||||
if (D_XIOCTL(VIDIOC_EXPBUF, &exp) < 0) {
|
||||
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 +241,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"));
|
||||
if (xioctl(RUN(fd), (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
|
||||
LOG_PERROR("Unable to %s capturing", (enable ? "start" : "stop"));
|
||||
LOG_DEBUG("%s device capturing ...", (enable ? "Starting" : "Stopping"));
|
||||
if (D_XIOCTL((enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
|
||||
LOG_PERROR("Can't %s capturing", (enable ? "start" : "stop"));
|
||||
if (enable) {
|
||||
return -1;
|
||||
}
|
||||
@@ -334,23 +311,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 (D_XIOCTL(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,62 +333,57 @@ 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 (D_XIOCTL(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 (D_XIOCTL(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) ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_DQEVENT, &event) == 0) {
|
||||
LOG_DEBUG("Consuming V4L2 event ...");
|
||||
if (D_XIOCTL(VIDIOC_DQEVENT, &event) == 0) {
|
||||
switch (event.type) {
|
||||
case V4L2_EVENT_SOURCE_CHANGE:
|
||||
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
|
||||
@@ -430,35 +399,34 @@ 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) ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERYCAP, &cap) < 0) {
|
||||
LOG_PERROR("Can't query device (VIDIOC_QUERYCAP)");
|
||||
LOG_DEBUG("Querying device capabilities ...");
|
||||
if (D_XIOCTL(VIDIOC_QUERYCAP, &cap) < 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
int input = dev->input; // Needs a pointer to int for ioctl()
|
||||
LOG_INFO("Using input channel: %d", input);
|
||||
if (xioctl(RUN(fd), VIDIOC_S_INPUT, &input) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_INPUT, &input) < 0) {
|
||||
LOG_ERROR("Can't set input channel");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (dev->standard != V4L2_STD_UNKNOWN) {
|
||||
LOG_INFO("Using TV standard: %s", _standard_to_string(dev->standard));
|
||||
if (xioctl(RUN(fd), VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
LOG_ERROR("Can't set video standard");
|
||||
return -1;
|
||||
}
|
||||
@@ -471,20 +439,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) ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
|
||||
LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE");
|
||||
LOG_DEBUG("Subscribing to DV-timings events ...")
|
||||
if (D_XIOCTL(VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) {
|
||||
LOG_PERROR("Can't subscribe to DV-timings events");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -492,11 +458,10 @@ 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_DEBUG("Calling xioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
|
||||
if (D_XIOCTL(VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
|
||||
if (dv.type == V4L2_DV_BT_656_1120) {
|
||||
// See v4l2_print_dv_timings() in the kernel
|
||||
unsigned htot = V4L2_DV_BT_FRAME_WIDTH(&dv.bt);
|
||||
@@ -514,9 +479,9 @@ static int _device_apply_dv_timings(device_s *dev) {
|
||||
(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_DEBUG("Calling xioctl(VIDIOC_S_DV_TIMINGS) ...");
|
||||
if (D_XIOCTL(VIDIOC_S_DV_TIMINGS, &dv) < 0) {
|
||||
LOG_PERROR("Failed to set DV-timings");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -525,10 +490,10 @@ static int _device_apply_dv_timings(device_s *dev) {
|
||||
}
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYSTD) ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERYSTD, &dev->standard) == 0) {
|
||||
LOG_DEBUG("Calling xioctl(VIDIOC_QUERYSTD) ...");
|
||||
if (D_XIOCTL(VIDIOC_QUERYSTD, &dev->standard) == 0) {
|
||||
LOG_INFO("Applying the new VIDIOC_S_STD: %s ...", _standard_to_string(dev->standard));
|
||||
if (xioctl(RUN(fd), VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
LOG_PERROR("Can't set video standard");
|
||||
return -1;
|
||||
}
|
||||
@@ -540,8 +505,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);
|
||||
@@ -550,10 +514,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) ...");
|
||||
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_DEBUG("Probing device format=%s, stride=%u, resolution=%ux%u ...",
|
||||
_format_to_string_supported(dev->format), stride, RUN(width), RUN(height));
|
||||
if (D_XIOCTL(VIDIOC_S_FMT, &fmt) < 0) {
|
||||
LOG_PERROR("Can't set device format");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -573,23 +537,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
|
||||
@@ -599,16 +563,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) ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_G_PARM, &setfps) < 0) {
|
||||
if (errno == ENOTTY) { // Quiet message for Auvidea B101
|
||||
LOG_DEBUG("Querying HW FPS ...");
|
||||
if (D_XIOCTL(VIDIOC_G_PARM, &setfps) < 0) {
|
||||
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;
|
||||
}
|
||||
@@ -625,8 +588,8 @@ static void _device_open_hw_fps(device_s *dev) {
|
||||
SETFPS_TPF(numerator) = 1;
|
||||
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");
|
||||
if (D_XIOCTL(VIDIOC_S_PARM, &setfps) < 0) {
|
||||
LOG_PERROR("Can't set HW FPS");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -654,15 +617,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");
|
||||
if (D_XIOCTL(VIDIOC_G_JPEGCOMP, &comp) < 0) {
|
||||
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");
|
||||
if (D_XIOCTL(VIDIOC_S_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Can't change MJPEG quality for JPEG source with HW pass-through encoder");
|
||||
} else {
|
||||
quality = dev->jpeg_quality;
|
||||
}
|
||||
@@ -683,15 +645,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 ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_MMAP", dev->path);
|
||||
LOG_DEBUG("Requesting %u device buffers for MMAP ...", req.count);
|
||||
if (D_XIOCTL(VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support MMAP method", dev->path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -706,40 +667,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 xioctl(VIDIOC_QUERYBUF) for device buffer=%u ...", RUN(n_bufs));
|
||||
if (D_XIOCTL(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
|
||||
}
|
||||
@@ -747,15 +702,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 ...");
|
||||
if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_USERPTR", dev->path);
|
||||
LOG_DEBUG("Requesting %u device buffers for USERPTR ...", req.count);
|
||||
if (D_XIOCTL(VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support USERPTR method", dev->path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -785,18 +739,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 xioctl(VIDIOC_QBUF) for buffer=%u ...", index);
|
||||
if (D_XIOCTL(VIDIOC_QBUF, &buf) < 0) {
|
||||
LOG_PERROR("Can't VIDIOC_QBUF");
|
||||
return -1;
|
||||
}
|
||||
@@ -806,7 +759,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
|
||||
@@ -884,7 +837,7 @@ static int _device_query_control(
|
||||
MEMSET_ZERO(*query);
|
||||
query->id = cid;
|
||||
|
||||
if (xioctl(RUN(fd), VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
|
||||
if (D_XIOCTL(VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
|
||||
if (!quiet) {
|
||||
LOG_ERROR("Changing control %s is unsupported", name);
|
||||
}
|
||||
@@ -905,12 +858,11 @@ static void _device_set_control(
|
||||
return;
|
||||
}
|
||||
|
||||
struct v4l2_control ctl;
|
||||
MEMSET_ZERO(ctl);
|
||||
struct v4l2_control ctl = {0};
|
||||
ctl.id = cid;
|
||||
ctl.value = value;
|
||||
|
||||
if (xioctl(RUN(fd), VIDIOC_S_CTRL, &ctl) < 0) {
|
||||
if (D_XIOCTL(VIDIOC_S_CTRL, &ctl) < 0) {
|
||||
if (!quiet) {
|
||||
LOG_PERROR("Can't set control %s", name);
|
||||
}
|
||||
@@ -951,4 +903,5 @@ static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
|
||||
return "unsupported";
|
||||
}
|
||||
|
||||
# undef RUN
|
||||
#undef D_XIOCTL
|
||||
#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 #
|
||||
@@ -40,16 +40,12 @@
|
||||
#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"
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/frame.h"
|
||||
|
||||
#include "xioctl.h"
|
||||
#include "../libs/xioctl.h"
|
||||
|
||||
|
||||
#define VIDEO_MIN_WIDTH ((unsigned)160)
|
||||
@@ -71,17 +67,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 +148,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,48 @@ 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);
|
||||
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
usleep(5000); // Просто чтобы работала логика desired_fps
|
||||
dest->encode_end_ts = get_now_monotonic(); // frame_encoding_end()
|
||||
}
|
||||
|
||||
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,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);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user