mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-20 16:56:30 +00:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
189aba1488 | ||
|
|
5f43fb7d0a | ||
|
|
86fada184c | ||
|
|
f0eece484f | ||
|
|
67f8e6665b | ||
|
|
6569e27312 | ||
|
|
4284ad3734 | ||
|
|
1c2f85cd59 | ||
|
|
2f83742f1d | ||
|
|
26d884c2da | ||
|
|
2d054fa9a8 | ||
|
|
f2863d1108 | ||
|
|
281e1b36ce | ||
|
|
160ad5c10f | ||
|
|
667137638b | ||
|
|
b5dffa927a | ||
|
|
651a82d1ca | ||
|
|
d963c95af8 | ||
|
|
7c524a1196 | ||
|
|
0c85aad5a2 | ||
|
|
5515654497 | ||
|
|
5955310bf3 | ||
|
|
a9df6da912 | ||
|
|
f3c56d5774 | ||
|
|
9a86793923 | ||
|
|
93c6248fdb | ||
|
|
ec738b18dc | ||
|
|
6029408564 | ||
|
|
8734834341 | ||
|
|
809f86955d | ||
|
|
2f557617d8 | ||
|
|
35c8196103 | ||
|
|
c71df1bb25 | ||
|
|
bc107d2870 | ||
|
|
294ed36b8f | ||
|
|
e01c7640b7 | ||
|
|
90125dcce4 | ||
|
|
a0c87c1c04 | ||
|
|
8924cdcac4 | ||
|
|
0d396e3f0a | ||
|
|
28daefc5ff | ||
|
|
895db6a8c9 | ||
|
|
73b894419a | ||
|
|
23b25634e8 | ||
|
|
b42a6c4124 | ||
|
|
f18a3ef992 | ||
|
|
5146314725 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 0.13
|
||||
current_version = 0.24
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
2
Makefile
2
Makefile
@@ -6,7 +6,7 @@ LDFLAGS ?=
|
||||
|
||||
# =====
|
||||
CC = gcc
|
||||
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads
|
||||
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
|
||||
override CFLAGS += -c -std=c99 -Wall -Wextra -D_GNU_SOURCE
|
||||
SOURCES = $(shell ls src/*.c src/jpeg/*.c)
|
||||
OBJECTS = $(SOURCES:.c=.o)
|
||||
|
||||
4
PKGBUILD
4
PKGBUILD
@@ -3,13 +3,13 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=0.13
|
||||
pkgver=0.24
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pi-kvm/ustreamer"
|
||||
license=(GPL)
|
||||
arch=(i686 x86_64 armv6h armv7h)
|
||||
depends=(libjpeg libevent)
|
||||
depends=(libjpeg libevent libutil-linux)
|
||||
# optional: raspberrypi-firmware for OMX JPEG compressor
|
||||
makedepends=(gcc make)
|
||||
source=("$url/archive/v$pkgver.tar.gz")
|
||||
|
||||
64
README.md
64
README.md
@@ -1,39 +1,41 @@
|
||||
# µStreamer
|
||||
[[Русская версия]](README.ru.md)
|
||||
|
||||
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pi-kvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
|
||||
µStreamer is a lightweight and very quick server to broadcast [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 part of the [Pi-KVM](https://github.com/pi-kvm) 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 очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
|
||||
µ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:
|
||||
|
||||
| **Фича** | **µStreamer** | **mjpg-streamer** |
|
||||
| **Feature** | **µStreamer** | **mjpg-streamer** |
|
||||
|----------|---------------|-------------------|
|
||||
| Многопоточное кодирование JPEG |  Есть |  Нет |
|
||||
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi |  Есть |  Нет |
|
||||
| Поведение при физическом отключении устройства<br>от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова |  Необратимо зависает <sup>1</sup> |
|
||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) |  Есть |  Нет <sup>1</sup> |
|
||||
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
||||
| Поддерживаемые входные форматы устройств |  YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> |  YUYV, UYVY,<br>RGB565, MJPG |
|
||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
||||
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации |  Нет <sup>4</sup> |  Есть |
|
||||
| Multithreaded JPEG encoding |  Yes |  No |
|
||||
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi |  Yes |  No |
|
||||
| Behavior when the device<br>is disconnected while streaming |  Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected |  Stops the broadcast<sup>1</sup> |
|
||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal |  Yes |  Partially yes <sup>1</sup> |
|
||||
| Option to skip frames when streaming<br>static images by HTTP to save traffic |  Yes <sup>2</sup> |  No |
|
||||
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters |  Yes |  No |
|
||||
| Supported input devices |  YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> |  YUYV, UYVY,<br>RGB565, MJPG |
|
||||
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP |  No |  Yes |
|
||||
| Option to serve files<br>with a built-in HTTP server, auth settings |  No <sup>4</sup> |  Yes |
|
||||
|
||||
Сносочки:
|
||||
* ```1``` Для mjpg-streamer существует [мой патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), предотвращающий зависание при отключении устройства и добавляющий поддержку DV-таймингов, однако трансляция при этом все равно прерывается. В данный момент этот патч не принят в апстрим, и я даже не гарантирую его стопроцентную работоспособность. Код mjpg-streamer очень плохо структурирован и чрезвычайно запутан, и я мог что-то упустить. Собственно, это одна из причин, почему µStreamer написан с нуля.
|
||||
Footnotes:
|
||||
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
|
||||
|
||||
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако увеличивает загрузку процессора и добавляет небольшую задержку. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
|
||||
* ```2``` This feature allows to cut down outgoing traffic several-fold when broadcasting HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to broadcast the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
|
||||
|
||||
* ```3``` Поскольку µStreamer писался в первую очередь для устройств видеозахвата, в нем реализованы только те форматы, которые для них были нужны. MJPG в контексте входных данных означает, что устройство умеет самостоятельно сжимать картинку в JPEG и отдавать ее программе, что позволяет значительно снизить загрузку процессора и избавить его от необходимости кодировать картинку софтом. Этот формат поддерживается большинством веб-камер, но не поддерживается ни одним из встреченных мной устройств видеозахвата; его не умеет ни [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), ни [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC). Нет никаких технических сложностей добавить поддержку аппаратного MJPG источника, но у меня просто пока не дошли до этого руки.
|
||||
* ```3``` As µStreamer was made mainly to be used with screencast hardware it supports video formats they usually need. MJPG input means that the screencast device can compress images to JPEG and feed it to the software, which allows to cut down CPU usage and avoid software image encoding. This video format is supported by most webcams, but I've never seen it supported by screencast hardware: neither [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), nor [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC) offer such support. It's not hard to add hardware MJPG sources support, it's just not done yet.
|
||||
|
||||
* ```4``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
|
||||
* ```4``` ...and there'll never be. µStreamer is designed UNIX-way, so if you need a small website with your broadcast, install NGINX.
|
||||
|
||||
-----
|
||||
# TL;DR
|
||||
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
|
||||
If you're going to live-stream from your backyard webcam and need to control it, use mjpg-streamer. If you need a high-quality image with high FPS - µStreamer for the win.
|
||||
|
||||
-----
|
||||
# Сборка
|
||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads``` и ```libjpeg8```/```libjpeg-turbo```.
|
||||
# Building
|
||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libuuid```.
|
||||
|
||||
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX, если обнаружит нужные хедеры в ```/opt/vc/include```.
|
||||
It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
|
||||
|
||||
```
|
||||
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
|
||||
@@ -42,27 +44,29 @@ $ make
|
||||
$ ./ustreamer --help
|
||||
```
|
||||
|
||||
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer
|
||||
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer
|
||||
|
||||
-----
|
||||
# Использование
|
||||
Будучи запущенным без аргументов, ```ustremaer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://localhost:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80 порту:
|
||||
# Usage
|
||||
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start broadcasting on ```http://localhost:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to broadcast to the world, run:
|
||||
```
|
||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
||||
```
|
||||
|
||||
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
|
||||
The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
|
||||
```bash
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Настройка входного формата устройства
|
||||
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
|
||||
--dv-timings # Включение DV-таймингов
|
||||
--format=uyvy \ # Device input format
|
||||
--encoder=omx \ # Hardware encoding with OpenMAX
|
||||
--dv-timings \ # use DV-timings
|
||||
--quality=20 \ # OpenMAX has a non-linear quality scale
|
||||
--drop-same-frames=30 # Save that traffic
|
||||
```
|
||||
|
||||
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
||||
You can always view the full list of options with ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Лицензия
|
||||
# License
|
||||
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
||||
83
README.ru.md
Normal file
83
README.ru.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# µStreamer
|
||||
[[English version]](README.md)
|
||||
|
||||
|
||||
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pi-kvm) специально для стриминга с устройств видеозахвата [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 |  Есть |  Нет |
|
||||
| Поведение при физическом отключении устройства<br>от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова |  Прерывает трансляцию <sup>1</sup> |
|
||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) |  Есть |  Условно есть <sup>1</sup> |
|
||||
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
||||
| Поддерживаемые входные форматы устройств |  YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> |  YUYV, UYVY,<br>RGB565, MJPG |
|
||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
||||
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации |  Нет <sup>4</sup> |  Есть |
|
||||
|
||||
Сносочки:
|
||||
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
||||
|
||||
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
|
||||
|
||||
* ```3``` Поскольку µStreamer писался в первую очередь для устройств видеозахвата, в нем реализованы только те форматы, которые для них были нужны. MJPG в контексте входных данных означает, что устройство умеет самостоятельно сжимать картинку в JPEG и отдавать ее программе, что позволяет значительно снизить загрузку процессора и избавить его от необходимости кодировать картинку софтом. Этот формат поддерживается большинством веб-камер, но не поддерживается ни одним из встреченных мной устройств видеозахвата; его не умеет ни [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), ни [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC). Нет никаких технических сложностей добавить поддержку аппаратного MJPG источника, но у меня просто пока не дошли до этого руки.
|
||||
|
||||
* ```4``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
|
||||
|
||||
-----
|
||||
# TL;DR
|
||||
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
|
||||
|
||||
-----
|
||||
# Сборка
|
||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
|
||||
|
||||
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
|
||||
|
||||
```
|
||||
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
|
||||
$ cd ustreamer
|
||||
$ make
|
||||
$ ./ustreamer --help
|
||||
```
|
||||
|
||||
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer
|
||||
|
||||
-----
|
||||
# Использование
|
||||
Будучи запущенным без аргументов, ```ustreamer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://localhost:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80-м порту:
|
||||
```
|
||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
||||
```
|
||||
|
||||
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
|
||||
```bash
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Настройка входного формата устройства
|
||||
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
|
||||
--dv-timings \ # Включение DV-таймингов
|
||||
--quality=20 \ # У OpenMAX нелинейная шкала качества
|
||||
--drop-same-frames=30 # Экономим трафик
|
||||
```
|
||||
|
||||
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Лицензия
|
||||
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
@@ -21,4 +21,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION "0.13"
|
||||
#define VERSION "0.24"
|
||||
|
||||
11
src/device.c
11
src/device.c
@@ -185,6 +185,7 @@ void device_close(struct device_t *dev) {
|
||||
|
||||
static int _device_open_check_cap(struct device_t *dev) {
|
||||
struct v4l2_capability cap;
|
||||
int input = dev->input; // Needs pointer to int for ioctl()
|
||||
|
||||
MEMSET_ZERO(cap);
|
||||
|
||||
@@ -204,10 +205,16 @@ static int _device_open_check_cap(struct device_t *dev) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_INFO("Using input channel: %d", input);
|
||||
if (xioctl(dev->run->fd, 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(dev->run->fd, VIDIOC_S_STD, &dev->standard) < 0) {
|
||||
LOG_PERROR("Can't set video standard");
|
||||
LOG_ERROR("Can't set video standard");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
@@ -250,7 +257,7 @@ static int _device_apply_dv_timings(struct device_t *dev) {
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings) == 0) {
|
||||
LOG_INFO(
|
||||
"Got new DV timings: resolution=%dx%d; pixclk=%llu\n",
|
||||
"Got new DV timings: resolution=%dx%d; pixclk=%llu",
|
||||
dv_timings.bt.width,
|
||||
dv_timings.bt.height,
|
||||
dv_timings.bt.pixelclock
|
||||
|
||||
@@ -44,6 +44,9 @@ struct picture_t {
|
||||
unsigned char *data;
|
||||
unsigned long size;
|
||||
unsigned long allocated;
|
||||
long double grab_time;
|
||||
long double encode_begin_time;
|
||||
long double encode_end_time;
|
||||
};
|
||||
|
||||
struct device_runtime_t {
|
||||
@@ -61,6 +64,7 @@ struct device_runtime_t {
|
||||
|
||||
struct device_t {
|
||||
char *path;
|
||||
unsigned input;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
|
||||
@@ -131,6 +131,8 @@ void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev)
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, const unsigned index) {
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
dev->run->pictures[index].encode_begin_time = get_now_monotonic();
|
||||
|
||||
if (encoder->type == ENCODER_TYPE_CPU) {
|
||||
jpeg_encoder_compress_buffer(dev, index, encoder->quality);
|
||||
}
|
||||
@@ -142,6 +144,8 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, con
|
||||
}
|
||||
# endif
|
||||
|
||||
dev->run->pictures[index].encode_end_time = get_now_monotonic();
|
||||
|
||||
return 0;
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
|
||||
261
src/http.c
261
src/http.c
@@ -30,6 +30,8 @@
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
#ifndef EVTHREAD_USE_PTHREADS_IMPLEMENTED
|
||||
# error Required libevent-pthreads support
|
||||
#endif
|
||||
@@ -85,7 +87,7 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
||||
|
||||
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, NULL));
|
||||
assert(!evhttp_set_cb(run->http, "/ping", _http_callback_ping, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)exposed));
|
||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
|
||||
|
||||
refresh_interval.tv_sec = 0;
|
||||
@@ -115,7 +117,7 @@ int http_server_listen(struct http_server_t *server) {
|
||||
LOG_DEBUG("Binding HTTP to [%s]:%d ...", server->host, server->port);
|
||||
evhttp_set_timeout(server->run->http, server->timeout);
|
||||
|
||||
if (evhttp_bind_socket(server->run->http, server->host, server->port) != 0) {
|
||||
if (evhttp_bind_socket(server->run->http, server->host, server->port) < 0) {
|
||||
LOG_PERROR("Can't listen HTTP on [%s]:%d", server->host, server->port)
|
||||
return -1;
|
||||
}
|
||||
@@ -172,38 +174,69 @@ static void _http_callback_ping(struct evhttp_request *request, void *v_server)
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"{\"stream\": {\"resolution\":"
|
||||
" {\"width\": %u, \"height\": %u},"
|
||||
" \"fps\": %u, \"online\": %s}}",
|
||||
"{\"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"quality\": %u, \"captured_fps\": %u},"
|
||||
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
|
||||
(server->fake_width ? server->fake_width : server->run->exposed->width),
|
||||
(server->fake_height ? server->fake_height : server->run->exposed->height),
|
||||
server->run->exposed->fps, (server->run->exposed->online ? "true" : "false")
|
||||
(server->run->exposed->online ? "true" : "false"),
|
||||
server->run->stream->encoder->quality,
|
||||
server->run->exposed->captured_fps,
|
||||
server->run->exposed->queued_fps,
|
||||
server->run->stream_clients_count
|
||||
));
|
||||
for (struct stream_client_t * client = server->run->stream_clients; client != NULL; client = client->next) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"\"%s\": {\"fps\": %u}%s",
|
||||
client->id, client->fps, (client->next ? ", " : "")
|
||||
));
|
||||
}
|
||||
assert(evbuffer_add_printf(buf, "}}}"));
|
||||
|
||||
ADD_HEADER("Content-Type", "application/json");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_exposed) {
|
||||
struct exposed_t *exposed = (struct exposed_t *)v_exposed;
|
||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server) {
|
||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||
struct evbuffer *buf;
|
||||
char x_timestamp_buf[64];
|
||||
char time_buf[64];
|
||||
|
||||
PROCESS_HEAD_REQUEST;
|
||||
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(!evbuffer_add(buf, (const void *)exposed->picture.data, exposed->picture.size));
|
||||
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture.data), EXPOSED(picture.size)));
|
||||
|
||||
ADD_HEADER("Access-Control-Allow-Origin:", "*");
|
||||
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0");
|
||||
ADD_HEADER("Pragma", "no-cache");
|
||||
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
||||
sprintf(x_timestamp_buf, "%.06Lf", now_real_ms());
|
||||
ADD_HEADER("X-Timestamp", x_timestamp_buf);
|
||||
|
||||
# define ADD_TIME_HEADER(_key, _value) \
|
||||
{ sprintf(time_buf, "%.06Lf", _value); ADD_HEADER(_key, time_buf); }
|
||||
|
||||
ADD_TIME_HEADER("X-Timestamp", get_now_real());
|
||||
|
||||
ADD_HEADER("X-UStreamer-Online", (EXPOSED(online) ? "true" : "false"));
|
||||
ADD_TIME_HEADER("X-UStreamer-Grab-Time", EXPOSED(picture.grab_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Time", EXPOSED(picture.encode_begin_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-End-Time", EXPOSED(picture.encode_end_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Time", EXPOSED(expose_begin_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Time", EXPOSED(expose_cmp_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-End-Time", EXPOSED(expose_end_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Send-Time", get_now_monotonic());
|
||||
|
||||
# undef ADD_TIME_HEADER
|
||||
|
||||
ADD_HEADER("Content-Type", "image/jpeg");
|
||||
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
evbuffer_free(buf);
|
||||
|
||||
# undef EXPOSED
|
||||
}
|
||||
|
||||
#undef ADD_HEADER
|
||||
@@ -221,6 +254,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
struct stream_client_t *client;
|
||||
char *client_addr;
|
||||
unsigned short client_port;
|
||||
uuid_t uuid;
|
||||
|
||||
PROCESS_HEAD_REQUEST;
|
||||
|
||||
@@ -232,6 +266,9 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
client->need_initial = true;
|
||||
client->need_first_frame = true;
|
||||
|
||||
uuid_generate(uuid);
|
||||
uuid_unparse_lower(uuid, client->id);
|
||||
|
||||
if (server->run->stream_clients == NULL) {
|
||||
server->run->stream_clients = client;
|
||||
} else {
|
||||
@@ -245,8 +282,8 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
LOG_INFO(
|
||||
"HTTP: Registered the new stream client: [%s]:%u; clients now: %u",
|
||||
client_addr, client_port, server->run->stream_clients_count
|
||||
"HTTP: Registered the new stream client: [%s]:%u; id=%s; clients now: %u",
|
||||
client_addr, client_port, client->id, server->run->stream_clients_count
|
||||
);
|
||||
|
||||
buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
@@ -259,12 +296,21 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
|
||||
#undef PROCESS_HEAD_REQUEST
|
||||
|
||||
#define BOUNDARY "boundarydonotcross"
|
||||
#define RN "\r\n"
|
||||
|
||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
||||
# define BOUNDARY "boundarydonotcross"
|
||||
# define RN "\r\n"
|
||||
|
||||
struct stream_client_t *client = (struct stream_client_t *)v_client;
|
||||
struct evbuffer *buf;
|
||||
long double now = get_now_monotonic();
|
||||
long long now_second = floor_ms(now);
|
||||
|
||||
if (now_second != client->fps_accum_second) {
|
||||
client->fps = client->fps_accum;
|
||||
client->fps_accum = 0;
|
||||
client->fps_accum_second = now_second;
|
||||
}
|
||||
client->fps_accum += 1;
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
|
||||
@@ -275,25 +321,53 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||
"Pragma: no-cache" RN
|
||||
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
||||
"Set-Cookie: stream_client_id=%s; path=/; max-age=30" RN
|
||||
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
||||
RN
|
||||
"--" BOUNDARY RN
|
||||
"--" BOUNDARY RN,
|
||||
client->id
|
||||
));
|
||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||
client->need_initial = false;
|
||||
}
|
||||
|
||||
# define EXPOSED(_next) client->server->run->exposed->_next
|
||||
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"Content-Type: image/jpeg" RN
|
||||
"Content-Length: %lu" RN
|
||||
"X-Timestamp: %.06Lf" RN
|
||||
RN,
|
||||
client->server->run->exposed->picture.size * sizeof(*client->server->run->exposed->picture.data),
|
||||
now_real_ms()
|
||||
"%s",
|
||||
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data)),
|
||||
get_now_real(), (client->server->extra_stream_headers ? "" : RN)
|
||||
));
|
||||
if (client->server->extra_stream_headers) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"X-UStreamer-Online: %s" RN
|
||||
"X-UStreamer-Client-FPS: %u" RN
|
||||
"X-UStreamer-Grab-Time: %.06Lf" RN
|
||||
"X-UStreamer-Encode-Begin-Time: %.06Lf" RN
|
||||
"X-UStreamer-Encode-End-Time: %.06Lf" RN
|
||||
"X-UStreamer-Expose-Begin-Time: %.06Lf" RN
|
||||
"X-UStreamer-Expose-Cmp-Time: %.06Lf" RN
|
||||
"X-UStreamer-Expose-End-Time: %.06Lf" RN
|
||||
"X-UStreamer-Send-Time: %.06Lf" RN
|
||||
RN,
|
||||
(EXPOSED(online) ? "true" : "false"),
|
||||
client->fps,
|
||||
EXPOSED(picture.grab_time),
|
||||
EXPOSED(picture.encode_begin_time),
|
||||
EXPOSED(picture.encode_end_time),
|
||||
EXPOSED(expose_begin_time),
|
||||
EXPOSED(expose_cmp_time),
|
||||
EXPOSED(expose_end_time),
|
||||
now
|
||||
));
|
||||
}
|
||||
|
||||
assert(!evbuffer_add(buf,
|
||||
(void *)client->server->run->exposed->picture.data,
|
||||
client->server->run->exposed->picture.size * sizeof(*client->server->run->exposed->picture.data)
|
||||
(void *)EXPOSED(picture.data),
|
||||
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data))
|
||||
));
|
||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||
|
||||
@@ -302,10 +376,11 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||
bufferevent_enable(buf_event, EV_READ);
|
||||
}
|
||||
|
||||
#undef BOUNDARY
|
||||
#undef RN
|
||||
# undef BOUNDARY
|
||||
# undef RN
|
||||
# undef EXPOSED
|
||||
}
|
||||
|
||||
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
|
||||
struct stream_client_t *client = (struct stream_client_t *)v_client;
|
||||
@@ -339,19 +414,32 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
}
|
||||
|
||||
static void _http_queue_send_stream(struct http_server_t *server, const bool updated) {
|
||||
struct stream_client_t *client;
|
||||
struct evhttp_connection *conn;
|
||||
struct bufferevent *buf_event;
|
||||
long long now;
|
||||
bool queued = false;
|
||||
static unsigned queued_fps_accum = 0;
|
||||
static long long queued_fps_second = 0;
|
||||
|
||||
for (client = server->run->stream_clients; client != NULL; client = client->next) {
|
||||
for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
|
||||
conn = evhttp_request_get_connection(client->request);
|
||||
if (conn != NULL && (updated || client->need_first_frame)) {
|
||||
buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
bufferevent_setcb(buf_event, NULL, _http_callback_stream_write, _http_callback_stream_error, (void *)client);
|
||||
bufferevent_enable(buf_event, EV_READ|EV_WRITE);
|
||||
client->need_first_frame = false;
|
||||
queued = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (queued) {
|
||||
if ((now = floor_ms(get_now_monotonic())) != queued_fps_second) {
|
||||
server->run->exposed->queued_fps = queued_fps_accum;
|
||||
queued_fps_accum = 0;
|
||||
queued_fps_second = now;
|
||||
}
|
||||
queued_fps_accum += 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||
@@ -383,6 +471,20 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
|
||||
}
|
||||
|
||||
if (queue_send) {
|
||||
if (server->drop_same_frames) {
|
||||
// Хром всегда показывает не новый пришедший фрейм, а предыдущий.
|
||||
// При updated == false нужно еще один раз послать предыдущий фрейм
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=527446
|
||||
|
||||
static bool updated_prev = false;
|
||||
bool updated_orig = updated;
|
||||
|
||||
if (updated_prev && !updated_orig) {
|
||||
updated = true;
|
||||
}
|
||||
updated_prev = updated_orig;
|
||||
}
|
||||
|
||||
_http_queue_send_stream(server, updated);
|
||||
}
|
||||
|
||||
@@ -391,70 +493,109 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
|
||||
}
|
||||
|
||||
static bool _expose_new_picture(struct http_server_t *server) {
|
||||
assert(server->run->stream->picture.size > 0);
|
||||
server->run->exposed->fps = server->run->stream->fps;
|
||||
# define STREAM(_next) server->run->stream->_next
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
|
||||
assert(STREAM(picture.size) > 0);
|
||||
EXPOSED(captured_fps) = STREAM(captured_fps);
|
||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||
|
||||
# define MEM_STREAM_TO_EXPOSED \
|
||||
server->run->exposed->picture.data, server->run->stream->picture.data, \
|
||||
server->run->stream->picture.size * sizeof(*server->run->exposed->picture.data)
|
||||
EXPOSED(picture.data), STREAM(picture.data), \
|
||||
STREAM(picture.size) * sizeof(*STREAM(picture.data))
|
||||
|
||||
if (server->drop_same_frames) {
|
||||
if (
|
||||
server->run->exposed->online
|
||||
&& server->run->exposed->dropped < server->drop_same_frames
|
||||
&& server->run->exposed->picture.size == server->run->stream->picture.size
|
||||
EXPOSED(online)
|
||||
&& EXPOSED(dropped) < server->drop_same_frames
|
||||
&& EXPOSED(picture.size) == STREAM(picture.size)
|
||||
&& !memcmp(MEM_STREAM_TO_EXPOSED)
|
||||
) {
|
||||
LOG_PERF("HTTP: dropped same frame number %u", server->run->exposed->dropped);
|
||||
server->run->exposed->dropped += 1;
|
||||
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
||||
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
|
||||
LOG_PERF(
|
||||
"HTTP: dropped same frame number %u; comparsion time = %.06Lf",
|
||||
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
|
||||
);
|
||||
EXPOSED(dropped) += 1;
|
||||
return false; // Not updated
|
||||
} else {
|
||||
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
||||
LOG_PERF(
|
||||
"HTTP: passed same frame check (frames are differ); comparsion time = %.06Lf",
|
||||
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (server->run->exposed->picture.allocated < server->run->stream->picture.allocated) {
|
||||
A_REALLOC(server->run->exposed->picture.data, server->run->stream->picture.allocated);
|
||||
server->run->exposed->picture.allocated = server->run->stream->picture.allocated;
|
||||
if (EXPOSED(picture.allocated) < STREAM(picture.allocated)) {
|
||||
A_REALLOC(EXPOSED(picture.data), STREAM(picture.allocated));
|
||||
EXPOSED(picture.allocated) = STREAM(picture.allocated);
|
||||
}
|
||||
|
||||
memcpy(MEM_STREAM_TO_EXPOSED);
|
||||
|
||||
# undef MEM_STREAM_TO_EXPOSED
|
||||
|
||||
server->run->exposed->picture.size = server->run->stream->picture.size;
|
||||
server->run->exposed->width = server->run->stream->width;
|
||||
server->run->exposed->height = server->run->stream->height;
|
||||
server->run->exposed->online = true;
|
||||
server->run->exposed->dropped = 0;
|
||||
EXPOSED(picture.size) = STREAM(picture.size);
|
||||
|
||||
EXPOSED(picture.grab_time) = STREAM(picture.grab_time);
|
||||
EXPOSED(picture.encode_begin_time) = STREAM(picture.encode_begin_time);
|
||||
EXPOSED(picture.encode_end_time) = STREAM(picture.encode_end_time);
|
||||
|
||||
EXPOSED(width) = STREAM(width);
|
||||
EXPOSED(height) = STREAM(height);
|
||||
EXPOSED(online) = true;
|
||||
EXPOSED(dropped) = 0;
|
||||
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
||||
EXPOSED(expose_end_time) = get_now_monotonic();
|
||||
|
||||
# undef STREAM
|
||||
# undef EXPOSED
|
||||
return true; // Updated
|
||||
}
|
||||
|
||||
static bool _expose_blank_picture(struct http_server_t *server) {
|
||||
if (server->run->exposed->online || server->run->exposed->picture.size == 0) {
|
||||
if (server->run->exposed->picture.allocated < BLANK_JPG_SIZE) {
|
||||
A_REALLOC(server->run->exposed->picture.data, BLANK_JPG_SIZE);
|
||||
server->run->exposed->picture.allocated = BLANK_JPG_SIZE;
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
|
||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
||||
|
||||
if (EXPOSED(online) || EXPOSED(picture.size) == 0) {
|
||||
if (EXPOSED(picture.allocated) < BLANK_JPG_SIZE) {
|
||||
A_REALLOC(EXPOSED(picture.data), BLANK_JPG_SIZE);
|
||||
EXPOSED(picture.allocated) = BLANK_JPG_SIZE;
|
||||
}
|
||||
|
||||
memcpy(
|
||||
server->run->exposed->picture.data, BLANK_JPG_DATA,
|
||||
BLANK_JPG_SIZE * sizeof(*server->run->exposed->picture.data)
|
||||
EXPOSED(picture.data), BLANK_JPG_DATA,
|
||||
BLANK_JPG_SIZE * sizeof(*EXPOSED(picture.data))
|
||||
);
|
||||
|
||||
server->run->exposed->picture.size = BLANK_JPG_SIZE;
|
||||
server->run->exposed->width = BLANK_JPG_WIDTH;
|
||||
server->run->exposed->height = BLANK_JPG_HEIGHT;
|
||||
server->run->exposed->fps = 0;
|
||||
server->run->exposed->online = false;
|
||||
EXPOSED(picture.size) = BLANK_JPG_SIZE;
|
||||
|
||||
EXPOSED(picture.grab_time) = 0;
|
||||
EXPOSED(picture.encode_begin_time) = 0;
|
||||
EXPOSED(picture.encode_end_time) = 0;
|
||||
|
||||
EXPOSED(width) = BLANK_JPG_WIDTH;
|
||||
EXPOSED(height) = BLANK_JPG_HEIGHT;
|
||||
EXPOSED(captured_fps) = 0;
|
||||
EXPOSED(online) = false;
|
||||
goto updated;
|
||||
}
|
||||
|
||||
if (server->run->exposed->dropped < server->run->drop_same_frames_blank) {
|
||||
LOG_PERF("HTTP: dropped same frame (BLANK) number %u", server->run->exposed->dropped);
|
||||
server->run->exposed->dropped += 1;
|
||||
if (EXPOSED(dropped) < server->run->drop_same_frames_blank) {
|
||||
LOG_PERF("HTTP: dropped same frame (BLANK) number %u", EXPOSED(dropped));
|
||||
EXPOSED(dropped) += 1;
|
||||
EXPOSED(expose_end_time) = get_now_monotonic();
|
||||
return false; // Not updated
|
||||
}
|
||||
|
||||
updated:
|
||||
server->run->exposed->dropped = 0;
|
||||
EXPOSED(dropped) = 0;
|
||||
EXPOSED(expose_end_time) = get_now_monotonic();
|
||||
return true; // Updated
|
||||
|
||||
# undef EXPOSED
|
||||
}
|
||||
|
||||
11
src/http.h
11
src/http.h
@@ -31,8 +31,12 @@
|
||||
struct stream_client_t {
|
||||
struct http_server_t *server;
|
||||
struct evhttp_request *request;
|
||||
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
|
||||
bool need_initial;
|
||||
bool need_first_frame;
|
||||
unsigned fps;
|
||||
unsigned fps_accum;
|
||||
long long fps_accum_second;
|
||||
|
||||
struct stream_client_t *prev;
|
||||
struct stream_client_t *next;
|
||||
@@ -42,9 +46,13 @@ struct exposed_t {
|
||||
struct picture_t picture;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned fps;
|
||||
unsigned captured_fps;
|
||||
unsigned queued_fps;
|
||||
bool online;
|
||||
unsigned dropped;
|
||||
long double expose_begin_time;
|
||||
long double expose_cmp_time;
|
||||
long double expose_end_time;
|
||||
};
|
||||
|
||||
struct http_server_runtime_t {
|
||||
@@ -62,6 +70,7 @@ struct http_server_t {
|
||||
char *host;
|
||||
unsigned port;
|
||||
unsigned drop_same_frames;
|
||||
bool extra_stream_headers;
|
||||
unsigned fake_width;
|
||||
unsigned fake_height;
|
||||
unsigned timeout;
|
||||
|
||||
@@ -54,65 +54,72 @@ pthread_mutex_t log_mutex;
|
||||
#define LOGGING_UNLOCK assert(!pthread_mutex_unlock(&log_mutex))
|
||||
|
||||
|
||||
#define SEP_INFO(_x_ch) { \
|
||||
#define SEP_INFO(_ch) { \
|
||||
LOGGING_LOCK; \
|
||||
for (int _i = 0; _i < 80; ++_i) { \
|
||||
putchar(_x_ch); \
|
||||
putchar(_ch); \
|
||||
} \
|
||||
putchar('\n'); \
|
||||
fflush(stdout); \
|
||||
LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define SEP_DEBUG(_x_ch) { \
|
||||
#define SEP_DEBUG(_ch) { \
|
||||
if (log_level >= LOG_LEVEL_DEBUG) { \
|
||||
SEP_INFO(_x_ch); \
|
||||
SEP_INFO(_ch); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_ERROR(_x_msg, ...) { \
|
||||
#define LOG_PRINTF_NOLOCK(_label, _msg, ...) { \
|
||||
printf("-- " _label " [%.03Lf tid=%ld] -- " _msg "\n", get_now_monotonic(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||
fflush(stdout); \
|
||||
}
|
||||
|
||||
#define LOG_ERROR(_msg, ...) { \
|
||||
LOGGING_LOCK; \
|
||||
printf("-- ERROR [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||
LOG_PRINTF_NOLOCK("ERROR", _msg, ##__VA_ARGS__); \
|
||||
LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define LOG_PERROR(_x_msg, ...) { \
|
||||
#define LOG_PERROR(_msg, ...) { \
|
||||
char _buf[1024] = ""; \
|
||||
strerror_r(errno, _buf, 1024); \
|
||||
LOGGING_LOCK; \
|
||||
printf("-- ERROR [%.03Lf tid=%ld] -- " _x_msg ": %s\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__, _buf); \
|
||||
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", get_now_monotonic(), syscall(SYS_gettid), ##__VA_ARGS__, _buf); \
|
||||
fflush(stdout); \
|
||||
LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define LOG_INFO(_x_msg, ...) { \
|
||||
#define LOG_INFO(_msg, ...) { \
|
||||
LOGGING_LOCK; \
|
||||
printf("-- INFO [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||
LOG_PRINTF_NOLOCK("INFO ", _msg, ##__VA_ARGS__); \
|
||||
LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define LOG_INFO_NOLOCK(_x_msg, ...) { \
|
||||
printf("-- INFO [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||
#define LOG_INFO_NOLOCK(_msg, ...) { \
|
||||
LOG_PRINTF_NOLOCK("INFO ", _msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_PERF(_x_msg, ...) { \
|
||||
#define LOG_PERF(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_PERF) { \
|
||||
LOGGING_LOCK; \
|
||||
printf("-- PERF [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||
LOG_PRINTF_NOLOCK("PERF ", _msg, ##__VA_ARGS__); \
|
||||
LOGGING_UNLOCK; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_VERBOSE(_x_msg, ...) { \
|
||||
#define LOG_VERBOSE(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
LOGGING_LOCK; \
|
||||
printf("-- VERB [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||
LOG_PRINTF_NOLOCK("VERB ", _msg, ##__VA_ARGS__); \
|
||||
LOGGING_UNLOCK; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_DEBUG(_x_msg, ...) { \
|
||||
#define LOG_DEBUG(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_DEBUG) { \
|
||||
LOGGING_LOCK; \
|
||||
printf("-- DEBUG [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||
LOG_PRINTF_NOLOCK("DEBUG", _msg, ##__VA_ARGS__); \
|
||||
LOGGING_UNLOCK; \
|
||||
} \
|
||||
}
|
||||
|
||||
76
src/main.c
76
src/main.c
@@ -25,7 +25,7 @@
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
#include <getopt.h>
|
||||
|
||||
@@ -40,9 +40,10 @@
|
||||
#include "http.h"
|
||||
|
||||
|
||||
static const char _short_opts[] = "d:x:y:f:a:e:z:tn:w:q:c:s:p:r:h";
|
||||
static const char _short_opts[] = "d:i:x:y:f:a:e:z:tn:w:q:c:s:p:r:h";
|
||||
static const struct option _long_opts[] = {
|
||||
{"device", required_argument, NULL, 'd'},
|
||||
{"input", required_argument, NULL, 'i'},
|
||||
{"width", required_argument, NULL, 'x'},
|
||||
{"height", required_argument, NULL, 'y'},
|
||||
{"format", required_argument, NULL, 'f'},
|
||||
@@ -63,28 +64,43 @@ static const struct option _long_opts[] = {
|
||||
{"host", required_argument, NULL, 's'},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
{"drop-same-frames", required_argument, NULL, 'r'},
|
||||
{"fake-width", required_argument, NULL, 2000},
|
||||
{"fake-height", required_argument, NULL, 2001},
|
||||
{"server-timeout", required_argument, NULL, 2002},
|
||||
{"extra-stream-headers", no_argument, NULL, 2000},
|
||||
{"fake-width", required_argument, NULL, 2001},
|
||||
{"fake-height", required_argument, NULL, 2002},
|
||||
{"server-timeout", required_argument, NULL, 2003},
|
||||
|
||||
{"perf", no_argument, NULL, 5000},
|
||||
{"verbose", no_argument, NULL, 5001},
|
||||
{"debug", no_argument, NULL, 5002},
|
||||
{"log-level", required_argument, NULL, 5010},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"version", no_argument, NULL, 6000},
|
||||
{NULL, 0, NULL, 0},
|
||||
};
|
||||
|
||||
static void _version(bool nl) {
|
||||
printf(VERSION);
|
||||
# ifdef OMX_ENCODER
|
||||
printf(" + OMX");
|
||||
# endif
|
||||
if (nl) {
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
|
||||
printf("\nuStreamer - Lightweight and fast MJPG-HTTP streamer\n");
|
||||
printf("===================================================\n\n");
|
||||
printf("Version: %s; license: GPLv3\n", VERSION);
|
||||
printf("Version: ");
|
||||
_version(false);
|
||||
printf("; license: GPLv3\n");
|
||||
printf("Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com>\n\n");
|
||||
printf("Capturing options:\n");
|
||||
printf("------------------\n");
|
||||
printf(" -d|--device </dev/path> -- Path to V4L2 device. Default: %s\n\n", dev->path);
|
||||
printf(" -x|--width <N> -- Initial image width. Default: %d\n\n", dev->width);
|
||||
printf(" -y|--height <N> -- Initial image height. Default: %d\n\n", dev->height);
|
||||
printf(" -d|--device </dev/path> -- Path to V4L2 device. Default: %s.\n\n", dev->path);
|
||||
printf(" -i|--input <N> -- Input channel. Default: %u.\n\n", dev->input);
|
||||
printf(" -x|--width <N> -- Initial image width. Default: %d.\n\n", dev->width);
|
||||
printf(" -y|--height <N> -- Initial image height. Default: %d.\n\n", dev->height);
|
||||
printf(" -f|--format <fmt> -- Image format.\n");
|
||||
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
|
||||
printf(" -a|--tv-standard <std> -- Force TV standard.\n");
|
||||
@@ -116,6 +132,8 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
printf(" It can significantly reduce the outgoing traffic, but will increase\n");
|
||||
printf(" the CPU loading. Don't use this option with analog signal sources\n");
|
||||
printf(" or webcams, it's useless. Default: disabled.\n\n");
|
||||
printf(" --extra-stream-headers -- Add X-UStreamer-* headers to /stream handle (like /snapshot).\n");
|
||||
printf(" Default: disabled.\n\n");
|
||||
printf(" --fake-width <N> -- Override image width for /ping. Default: disabled\n\n");
|
||||
printf(" --fake-height <N> -- Override image height for /ping. Default: disabled.\n\n");
|
||||
printf(" --server-timeout <seconds> -- Timeout for client connections. Default: %d\n\n", server->timeout);
|
||||
@@ -132,11 +150,8 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
}
|
||||
|
||||
static int _parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
|
||||
# define OPT_ARG(_dest) \
|
||||
{ _dest = optarg; break; }
|
||||
|
||||
# define OPT_TRUE(_dest) \
|
||||
{ _dest = true; break; }
|
||||
# define OPT_SET(_dest, _value) \
|
||||
{ _dest = _value; break; }
|
||||
|
||||
# define OPT_UNSIGNED(_dest, _name, _min, _max) \
|
||||
{ errno = 0; int _tmp = strtol(optarg, NULL, 0); \
|
||||
@@ -155,7 +170,8 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
log_level = LOG_LEVEL_INFO;
|
||||
while ((ch = getopt_long(argc, argv, _short_opts, _long_opts, &index)) >= 0) {
|
||||
switch (ch) {
|
||||
case 'd': OPT_ARG(dev->path);
|
||||
case 'd': OPT_SET(dev->path, optarg);
|
||||
case 'i': OPT_UNSIGNED(dev->input, "--input", 0, 128);
|
||||
case 'x': OPT_UNSIGNED(dev->width, "--width", 320, 1920);
|
||||
case 'y': OPT_UNSIGNED(dev->height, "--height", 180, 1200);
|
||||
# pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
@@ -165,37 +181,39 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard");
|
||||
case 'e': OPT_UNSIGNED(dev->every_frame, "--every-frame", 1, 30);
|
||||
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192);
|
||||
case 't': OPT_TRUE(dev->dv_timings);
|
||||
case 't': OPT_SET(dev->dv_timings, true);
|
||||
case 'b': OPT_UNSIGNED(dev->n_buffers, "--buffers", 1, 32);
|
||||
case 'w': OPT_UNSIGNED(dev->n_workers, "--workers", 1, 32);
|
||||
case 'q': OPT_UNSIGNED(encoder->quality, "--quality", 1, 100);
|
||||
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type");
|
||||
# ifdef OMX_ENCODER
|
||||
case 500: encoder->omx_use_ijg = true; break;
|
||||
case 500: OPT_SET(encoder->omx_use_ijg, true);
|
||||
# endif
|
||||
case 1000: OPT_UNSIGNED(dev->timeout, "--timeout", 1, 60);
|
||||
case 1001: OPT_UNSIGNED(dev->error_timeout, "--error-timeout", 1, 60);
|
||||
|
||||
case 's': server->host = optarg; break;
|
||||
case 's': OPT_SET(server->host, optarg);
|
||||
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535);
|
||||
case 'r': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
|
||||
case 2000: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
||||
case 2001: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
|
||||
case 2002: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
||||
case 2000: OPT_SET(server->extra_stream_headers, true);
|
||||
case 2001: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
||||
case 2002: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
|
||||
case 2003: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
||||
|
||||
case 5000: log_level = LOG_LEVEL_PERF; break;
|
||||
case 5001: log_level = LOG_LEVEL_VERBOSE; break;
|
||||
case 5002: log_level = LOG_LEVEL_DEBUG; break;
|
||||
case 5000: OPT_SET(log_level, LOG_LEVEL_PERF);
|
||||
case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
|
||||
case 5002: OPT_SET(log_level, LOG_LEVEL_DEBUG);
|
||||
case 5010: OPT_UNSIGNED(log_level, "--log-level", 0, 3);
|
||||
case 'h': _help(dev, encoder, server); return 1;
|
||||
case 6000: _version(true); return 1;
|
||||
case 0: break;
|
||||
case 'h': default: _help(dev, encoder, server); return -1;
|
||||
default: _help(dev, encoder, server); return -1;
|
||||
}
|
||||
}
|
||||
|
||||
# undef OPT_PARSE
|
||||
# undef OPT_SET
|
||||
# undef OPT_UNSIGNED
|
||||
# undef OPT_TRUE
|
||||
# undef OPT_ARG
|
||||
# undef OPT_PARSE
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -291,5 +309,5 @@ int main(int argc, char *argv[]) {
|
||||
device_destroy(dev);
|
||||
|
||||
LOGGING_DESTROY;
|
||||
return abs(exit_code);
|
||||
return (exit_code < 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
#include "formatters.h"
|
||||
|
||||
|
||||
#define CASE_TO_STRING(_val) \
|
||||
case _val: { return #_val; }
|
||||
#define CASE_TO_STRING(_value) \
|
||||
case _value: { return #_value; }
|
||||
|
||||
#define CASE_ASSERT(_msg, _val) default: { \
|
||||
#define CASE_ASSERT(_msg, _value) default: { \
|
||||
char *_buf; A_CALLOC(_buf, 128); \
|
||||
sprintf(_buf, _msg ": 0x%08x", _val); \
|
||||
sprintf(_buf, _msg ": 0x%08x", _value); \
|
||||
assert(0 && _buf); \
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
#define LOG_OMX_ERROR(_error, _msg, ...) { \
|
||||
LOGGING_LOCK; \
|
||||
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", now_monotonic_ms(), \
|
||||
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", get_now_monotonic(), \
|
||||
syscall(SYS_gettid), ##__VA_ARGS__, omx_error_to_string(_error)); \
|
||||
LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
81
src/stream.c
81
src/stream.c
@@ -85,8 +85,8 @@ void stream_loop(struct stream_t *stream) {
|
||||
unsigned frames_count = 0;
|
||||
long double grab_after = 0;
|
||||
unsigned fluency_passed = 0;
|
||||
unsigned fps = 0;
|
||||
long long fps_second = 0;
|
||||
unsigned captured_fps_accum = 0;
|
||||
long long captured_fps_second = 0;
|
||||
|
||||
LOG_DEBUG("Allocation memory for stream picture ...");
|
||||
A_CALLOC(stream->picture.data, stream->dev->run->max_picture_size);
|
||||
@@ -111,7 +111,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
free_worker_number = oldest_worker->ctx.number;
|
||||
oldest_worker = oldest_worker->order_next;
|
||||
|
||||
LOG_PERF("##### ACCEPT : %u", free_worker_number);
|
||||
LOG_PERF("##### Raw frame accepted; worker = %u", free_worker_number);
|
||||
} else {
|
||||
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
|
||||
if (!pool.workers[number].has_job && (free_worker_number == -1
|
||||
@@ -125,7 +125,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
assert(free_worker_number >= 0);
|
||||
assert(!pool.workers[free_worker_number].has_job);
|
||||
|
||||
LOG_PERF("----- DROP : %u", free_worker_number);
|
||||
LOG_PERF("----- Raw frame dropped; worker = %u", free_worker_number);
|
||||
}
|
||||
|
||||
if (stream->dev->stop) {
|
||||
@@ -162,10 +162,13 @@ void stream_loop(struct stream_t *stream) {
|
||||
LOG_DEBUG("Frame is ready");
|
||||
|
||||
struct v4l2_buffer buf_info;
|
||||
long double now = get_now_monotonic();
|
||||
long long now_second = floor_ms(now);
|
||||
|
||||
if (_stream_grab_buffer(stream->dev, &buf_info) < 0) {
|
||||
break;
|
||||
}
|
||||
stream->dev->run->pictures[buf_info.index].grab_time = now;
|
||||
|
||||
if (stream->dev->every_frame) {
|
||||
if (frames_count < stream->dev->every_frame - 1) {
|
||||
@@ -187,8 +190,6 @@ void stream_loop(struct stream_t *stream) {
|
||||
}
|
||||
|
||||
{
|
||||
long double now = now_monotonic_ms();
|
||||
|
||||
if (now < grab_after) {
|
||||
fluency_passed += 1;
|
||||
LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf; grab_after=%.03Lf", fluency_passed, now, grab_after);
|
||||
@@ -196,13 +197,13 @@ void stream_loop(struct stream_t *stream) {
|
||||
}
|
||||
fluency_passed = 0;
|
||||
|
||||
if ((long long)now != fps_second) {
|
||||
LOG_PERF("Oldest worker complete, encoding FPS = %u", fps);
|
||||
stream->fps = fps;
|
||||
fps = 0;
|
||||
fps_second = (long long)now;
|
||||
if (now_second != captured_fps_second) {
|
||||
stream->captured_fps = captured_fps_accum;
|
||||
captured_fps_accum = 0;
|
||||
captured_fps_second = now_second;
|
||||
LOG_PERF("Oldest worker complete, Captured-FPS = %u", stream->captured_fps);
|
||||
}
|
||||
fps += 1;
|
||||
captured_fps_accum += 1;
|
||||
|
||||
long double fluency_delay = _stream_get_fluency_delay(stream->dev, &pool);
|
||||
|
||||
@@ -283,20 +284,27 @@ void stream_loop_break(struct stream_t *stream) {
|
||||
}
|
||||
|
||||
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index) {
|
||||
# define PICTURE(_next) stream->dev->run->pictures[buf_index]._next
|
||||
|
||||
A_PTHREAD_M_LOCK(&stream->mutex);
|
||||
|
||||
stream->picture.size = stream->dev->run->pictures[buf_index].size;
|
||||
stream->picture.allocated = stream->dev->run->pictures[buf_index].allocated;
|
||||
stream->picture.size = PICTURE(size);
|
||||
stream->picture.allocated = PICTURE(allocated);
|
||||
memcpy(
|
||||
stream->picture.data, stream->dev->run->pictures[buf_index].data,
|
||||
stream->picture.data, PICTURE(data),
|
||||
stream->picture.size * sizeof(*stream->picture.data)
|
||||
);
|
||||
stream->picture.grab_time = PICTURE(grab_time);
|
||||
stream->picture.encode_begin_time = PICTURE(encode_begin_time);
|
||||
stream->picture.encode_end_time = PICTURE(encode_end_time);
|
||||
|
||||
stream->width = stream->dev->run->width;
|
||||
stream->height = stream->dev->run->height;
|
||||
stream->updated = true;
|
||||
|
||||
A_PTHREAD_M_UNLOCK(&stream->mutex);
|
||||
|
||||
# undef PICTURE
|
||||
}
|
||||
|
||||
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool) {
|
||||
@@ -368,25 +376,29 @@ static void _stream_init_workers(struct device_t *dev, struct workers_pool_t *po
|
||||
A_PTHREAD_M_INIT(&pool->workers[number].has_job_mutex);
|
||||
A_PTHREAD_C_INIT(&pool->workers[number].has_job_cond);
|
||||
|
||||
pool->workers[number].ctx.number = number;
|
||||
pool->workers[number].ctx.dev = dev;
|
||||
pool->workers[number].ctx.dev_stop = (sig_atomic_t *volatile)&dev->stop;
|
||||
pool->workers[number].ctx.workers_stop = pool->workers_stop;
|
||||
# define CTX(_next) pool->workers[number].ctx._next
|
||||
|
||||
pool->workers[number].ctx.encoder = pool->encoder;
|
||||
CTX(number) = number;
|
||||
CTX(dev) = dev;
|
||||
CTX(dev_stop) = (sig_atomic_t *volatile)&dev->stop;
|
||||
CTX(workers_stop) = pool->workers_stop;
|
||||
|
||||
pool->workers[number].ctx.last_comp_time_mutex = &pool->workers[number].last_comp_time_mutex;
|
||||
pool->workers[number].ctx.last_comp_time = &pool->workers[number].last_comp_time;
|
||||
CTX(encoder) = pool->encoder;
|
||||
|
||||
pool->workers[number].ctx.has_job_mutex = &pool->workers[number].has_job_mutex;
|
||||
pool->workers[number].ctx.has_job = &pool->workers[number].has_job;
|
||||
pool->workers[number].ctx.job_failed = &pool->workers[number].job_failed;
|
||||
pool->workers[number].ctx.job_start_time = &pool->workers[number].job_start_time;
|
||||
pool->workers[number].ctx.has_job_cond = &pool->workers[number].has_job_cond;
|
||||
CTX(last_comp_time_mutex) = &pool->workers[number].last_comp_time_mutex;
|
||||
CTX(last_comp_time) = &pool->workers[number].last_comp_time;
|
||||
|
||||
pool->workers[number].ctx.free_workers_mutex = &pool->free_workers_mutex;
|
||||
pool->workers[number].ctx.free_workers = &pool->free_workers;
|
||||
pool->workers[number].ctx.free_workers_cond = &pool->free_workers_cond;
|
||||
CTX(has_job_mutex) = &pool->workers[number].has_job_mutex;
|
||||
CTX(has_job) = &pool->workers[number].has_job;
|
||||
CTX(job_failed) = &pool->workers[number].job_failed;
|
||||
CTX(job_start_time) = &pool->workers[number].job_start_time;
|
||||
CTX(has_job_cond) = &pool->workers[number].has_job_cond;
|
||||
|
||||
CTX(free_workers_mutex) = &pool->free_workers_mutex;
|
||||
CTX(free_workers) = &pool->free_workers;
|
||||
CTX(free_workers_cond) = &pool->free_workers_cond;
|
||||
|
||||
# undef CTX
|
||||
|
||||
A_PTHREAD_CREATE(&pool->workers[number].tid, _stream_worker_thread, (void *)&pool->workers[number].ctx);
|
||||
}
|
||||
@@ -404,11 +416,6 @@ static void *_stream_worker_thread(void *v_ctx) {
|
||||
A_PTHREAD_M_UNLOCK(ctx->has_job_mutex);
|
||||
|
||||
if (!*ctx->workers_stop) {
|
||||
long double start_time;
|
||||
long double last_comp_time;
|
||||
|
||||
start_time = now_monotonic_ms();
|
||||
|
||||
LOG_DEBUG("Worker %u compressing JPEG from buffer %d ...", ctx->number, ctx->buf_index);
|
||||
|
||||
if (encoder_compress_buffer(ctx->encoder, ctx->dev, ctx->buf_index) < 0) {
|
||||
@@ -416,10 +423,10 @@ static void *_stream_worker_thread(void *v_ctx) {
|
||||
}
|
||||
|
||||
if (_stream_release_buffer(ctx->dev, &ctx->buf_info) == 0) {
|
||||
*ctx->job_start_time = start_time;
|
||||
*ctx->job_start_time = ctx->dev->run->pictures[ctx->buf_index].encode_begin_time;
|
||||
*ctx->has_job = false;
|
||||
|
||||
last_comp_time = now_monotonic_ms() - start_time;
|
||||
long double last_comp_time = ctx->dev->run->pictures[ctx->buf_index].encode_end_time - *ctx->job_start_time;
|
||||
|
||||
A_PTHREAD_M_LOCK(ctx->last_comp_time_mutex);
|
||||
*ctx->last_comp_time = last_comp_time;
|
||||
|
||||
@@ -86,7 +86,7 @@ struct stream_t {
|
||||
struct picture_t picture;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned fps;
|
||||
unsigned captured_fps;
|
||||
bool updated;
|
||||
pthread_mutex_t mutex;
|
||||
struct device_t *dev;
|
||||
|
||||
18
src/tools.h
18
src/tools.h
@@ -49,8 +49,8 @@
|
||||
|
||||
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
|
||||
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
|
||||
#define MEMSET_ZERO(_x_obj) memset(&(_x_obj), 0, sizeof(_x_obj))
|
||||
#define MEMSET_ZERO_PTR(_x_ptr) memset(_x_ptr, 0, sizeof(*(_x_ptr)))
|
||||
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
|
||||
#define MEMSET_ZERO_PTR(_ptr) memset(_ptr, 0, sizeof(*(_ptr)))
|
||||
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
@@ -61,7 +61,11 @@ INLINE unsigned max_u(unsigned a, unsigned b) {
|
||||
return (a > b ? a : b);
|
||||
}
|
||||
|
||||
INLINE void now_ms(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
INLINE long long floor_ms(long double now) {
|
||||
return (long long) now - (now < (long long) now); // floor()
|
||||
}
|
||||
|
||||
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
struct timespec spec;
|
||||
|
||||
assert(!clock_gettime(clk_id, &spec));
|
||||
@@ -74,18 +78,18 @@ INLINE void now_ms(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
}
|
||||
}
|
||||
|
||||
INLINE long double now_monotonic_ms(void) {
|
||||
INLINE long double get_now_monotonic(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
|
||||
now_ms(CLOCK_MONOTONIC_RAW, &sec, &msec);
|
||||
get_now(CLOCK_MONOTONIC_RAW, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE long double now_real_ms(void) {
|
||||
INLINE long double get_now_real(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
|
||||
now_ms(CLOCK_REALTIME, &sec, &msec);
|
||||
get_now(CLOCK_REALTIME, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ def main():
|
||||
*****************************************************************************/
|
||||
""").strip() + "\n\n\n" + text
|
||||
|
||||
|
||||
with open(dest, "w") as h_file:
|
||||
h_file.write(text)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user