Compare commits

...

82 Commits
v0.6 ... v0.26

Author SHA1 Message Date
Devaev Maxim
77b5e6eabc Bump version: 0.25 → 0.26 2018-10-28 14:02:50 +03:00
Devaev Maxim
ca07a9155b --device-persistent 2018-10-28 14:02:23 +03:00
Devaev Maxim
6cc202133e using 127.0.0.1 by default 2018-10-28 07:12:19 +03:00
Devaev Maxim
797e9427e5 Bump version: 0.24 → 0.25 2018-10-21 08:15:40 +03:00
Devaev Maxim
b624ea2005 fixed usage of strerror_r() 2018-10-21 08:15:23 +03:00
Devaev Maxim
189aba1488 Bump version: 0.23 → 0.24 2018-10-16 03:26:11 +03:00
Maxim Devaev
5f43fb7d0a Update README.md 2018-10-16 03:24:06 +03:00
Maxim Devaev
86fada184c Merge pull request #1 from goblinqueen/master
Readme English translation
2018-10-16 00:38:42 +03:00
Eltha Black
f0eece484f README.md English translation 2018-10-16 00:11:36 +03:00
Eltha Black
67f8e6665b README.md English translation 2018-10-16 00:06:43 +03:00
Maxim Devaev
6569e27312 Update README.md 2018-10-15 06:50:38 +03:00
Maxim Devaev
4284ad3734 Update README.md 2018-10-15 02:22:24 +03:00
Maxim Devaev
1c2f85cd59 Update README.md 2018-10-15 01:27:25 +03:00
Devaev Maxim
2f83742f1d s/with/+/ 2018-10-14 20:55:31 +03:00
Maxim Devaev
26d884c2da Update README.md 2018-10-13 05:36:19 +03:00
Devaev Maxim
2d054fa9a8 Bump version: 0.22 → 0.23 2018-10-10 00:08:13 +03:00
Devaev Maxim
f2863d1108 --version 2018-10-10 00:07:59 +03:00
Devaev Maxim
281e1b36ce Bump version: 0.21 → 0.22 2018-10-08 16:33:16 +03:00
Devaev Maxim
160ad5c10f updated dependencies info 2018-10-08 15:39:33 +03:00
Devaev Maxim
667137638b Bump version: 0.20 → 0.21 2018-10-07 11:32:18 +03:00
Devaev Maxim
b5dffa927a changed /ping format 2018-10-07 11:32:07 +03:00
Devaev Maxim
651a82d1ca Bump version: 0.19 → 0.20 2018-10-06 01:17:38 +03:00
Devaev Maxim
d963c95af8 refactoring 2018-10-06 01:12:41 +03:00
Devaev Maxim
7c524a1196 calculating client fps 2018-10-06 00:50:30 +03:00
Maxim Devaev
0c85aad5a2 Update README.md 2018-10-05 11:12:38 +03:00
Devaev Maxim
5515654497 Bump version: 0.18 → 0.19 2018-10-04 21:19:21 +03:00
Devaev Maxim
5955310bf3 updated readme 2018-10-04 21:19:09 +03:00
Devaev Maxim
a9df6da912 improved fps counters 2018-10-04 19:43:58 +03:00
Devaev Maxim
f3c56d5774 Bump version: 0.17 → 0.18 2018-10-04 16:03:29 +03:00
Devaev Maxim
9a86793923 Workaround for Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=527446 2018-10-04 15:59:40 +03:00
Devaev Maxim
93c6248fdb better msg 2018-10-04 11:35:18 +03:00
Devaev Maxim
ec738b18dc fps -> cps 2018-10-04 09:20:36 +03:00
Devaev Maxim
6029408564 Bump version: 0.16 → 0.17 2018-10-04 01:33:30 +03:00
Devaev Maxim
8734834341 refactoring 2018-10-04 01:32:02 +03:00
Devaev Maxim
809f86955d refactoring 2018-10-04 01:29:38 +03:00
Devaev Maxim
2f557617d8 --add-x-timings -> --extra-stream-headers 2018-10-04 01:07:06 +03:00
Devaev Maxim
35c8196103 extra headers 2018-10-04 00:59:59 +03:00
Devaev Maxim
c71df1bb25 fixed time rounding 2018-10-03 20:46:13 +03:00
Devaev Maxim
bc107d2870 eps 2018-10-03 20:37:39 +03:00
Devaev Maxim
294ed36b8f clients count in /ping 2018-10-03 19:29:19 +03:00
Devaev Maxim
e01c7640b7 Bump version: 0.15 → 0.16 2018-10-02 23:30:27 +03:00
Devaev Maxim
90125dcce4 refactoring 2018-10-02 23:28:50 +03:00
Devaev Maxim
a0c87c1c04 configurable input channel 2018-10-02 23:28:35 +03:00
Devaev Maxim
8924cdcac4 removed newline 2018-10-02 13:57:28 +03:00
Devaev Maxim
0d396e3f0a Bump version: 0.14 → 0.15 2018-10-01 18:15:52 +03:00
Devaev Maxim
28daefc5ff refactoring 2018-10-01 18:12:43 +03:00
Devaev Maxim
895db6a8c9 logging: flush stdout 2018-10-01 18:12:43 +03:00
Maxim Devaev
73b894419a Update README.md 2018-10-01 07:01:58 +03:00
Devaev Maxim
23b25634e8 Bump version: 0.13 → 0.14 2018-09-30 18:11:25 +03:00
Devaev Maxim
b42a6c4124 refactoring 2018-09-30 18:00:21 +03:00
Devaev Maxim
f18a3ef992 --add-x-timings 2018-09-30 16:58:50 +03:00
Devaev Maxim
5146314725 removed extra newline 2018-09-30 08:38:52 +03:00
Devaev Maxim
4fc59de042 Bump version: 0.12 → 0.13 2018-09-30 06:48:41 +03:00
Devaev Maxim
494993fe39 refacotring 2018-09-30 06:45:27 +03:00
Devaev Maxim
e9ec65cfde report about stream clients 2018-09-30 06:27:58 +03:00
Devaev Maxim
ec2a704ca0 refactoring 2018-09-30 04:29:34 +03:00
Devaev Maxim
05b18fb68d Bump version: 0.11 → 0.12 2018-09-30 03:17:57 +03:00
Maxim Devaev
32120ae12d Update README.md 2018-09-30 03:17:11 +03:00
Maxim Devaev
386396dc84 Update README.md 2018-09-30 03:11:05 +03:00
Devaev Maxim
3c086c2129 refactoring 2018-09-30 02:52:17 +03:00
Devaev Maxim
fb9a8e31b1 --drop-same-frames to reduce the outgoing traffic 2018-09-30 02:49:11 +03:00
Devaev Maxim
0c192ddcfb Bump version: 0.10 → 0.11 2018-09-29 07:33:24 +03:00
Devaev Maxim
38b4246b9b refactoring 2018-09-29 07:33:11 +03:00
Maxim Devaev
a46ca0f702 Update README.md 2018-09-29 03:17:37 +03:00
Maxim Devaev
ddf290e811 Update README.md 2018-09-29 03:04:38 +03:00
Maxim Devaev
98b37cb48f Update README.md 2018-09-29 03:00:34 +03:00
Maxim Devaev
d7ab090a69 Update README.md 2018-09-28 20:29:42 +03:00
Maxim Devaev
1b160eb177 Update README.md 2018-09-28 20:18:20 +03:00
Maxim Devaev
fad5bec927 Update README.md 2018-09-28 19:32:27 +03:00
Maxim Devaev
ac036a04b5 Update README.md 2018-09-28 18:56:28 +03:00
Maxim Devaev
cf5d4b1d2a Update README.md 2018-09-28 18:52:58 +03:00
Maxim Devaev
102edd6880 Update README.md 2018-09-28 18:48:22 +03:00
Maxim Devaev
1e87410035 Create README.md 2018-09-28 18:06:05 +03:00
Devaev Maxim
993ef4e884 Bump version: 0.9 → 0.10 2018-09-28 04:32:53 +03:00
Devaev Maxim
ea5ec96874 omx: disabled exif, optional ijg tables, refactoring 2018-09-28 03:58:11 +03:00
Devaev Maxim
d76c5da4de Bump version: 0.8 → 0.9 2018-09-27 11:55:55 +03:00
Devaev Maxim
4c07d047b4 show fps in /ping 2018-09-27 11:52:11 +03:00
Devaev Maxim
609fa89d09 Bump version: 0.7 → 0.8 2018-09-27 05:27:56 +03:00
Devaev Maxim
ce26bd0a65 added some logging options 2018-09-27 05:19:27 +03:00
Devaev Maxim
74699b63e7 some asserts 2018-09-27 05:08:43 +03:00
Devaev Maxim
43800a5ed6 Bump version: 0.6 → 0.7 2018-09-26 05:21:55 +03:00
Devaev Maxim
0318f33479 increased max buffer size 2018-09-26 05:21:44 +03:00
25 changed files with 779 additions and 265 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 0.6
current_version = 0.26
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
serialize =
{major}.{minor}

View File

@@ -3,8 +3,10 @@ PREFIX ?= /usr/local
CFLAGS ?= -O3
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)
@@ -18,6 +20,7 @@ override CFLAGS += -DOMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
endif
# =====
all: $(SOURCES) $(PROG)

View File

@@ -3,13 +3,13 @@
pkgname=ustreamer
pkgver=0.6
pkgver=0.26
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")

83
README.md Normal file
View File

@@ -0,0 +1,83 @@
# µStreamer
[[Русская версия]](README.ru.md)
µ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 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:
| **Feature** | **µStreamer** | **mjpg-streamer** |
|----------|---------------|-------------------|
| Multithreaded JPEG encoding | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Behavior when the device<br>is disconnected while streaming | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) 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 | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save traffic | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Supported input devices | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) YUYV, UYVY,<br>RGB565, MJPG |
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
| Option to serve files<br>with a built-in HTTP server, auth settings | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No <sup>4</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
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``` 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``` 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``` ...and there'll never be. µStreamer is designed UNIX-way, so if you need a small website with your broadcast, install NGINX.
-----
# TL;DR
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.
-----
# Building
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libuuid```.
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
$ cd ustreamer
$ make
$ ./ustreamer --help
```
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer
-----
# Usage
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start broadcasting on ```http://127.0.0.1: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
```
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 \ # 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
```
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
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/.

83
README.ru.md Normal file
View 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 | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении устройства<br>от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Прерывает трансляцию <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поддерживаемые входные форматы устройств | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) YUYV, UYVY,<br>RGB565, MJPG |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>4</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
Сносочки:
* ```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://127.0.0.1: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/.

View File

@@ -21,4 +21,4 @@
#pragma once
#define VERSION "0.6"
#define VERSION "0.26"

View File

@@ -39,7 +39,7 @@
static const struct {
const char *name;
const v4l2_std_id standard;
} STANDARDS[] = {
} _STANDARDS[] = {
{"UNKNOWN", V4L2_STD_UNKNOWN},
{"PAL", V4L2_STD_PAL},
{"NTSC", V4L2_STD_NTSC},
@@ -49,7 +49,7 @@ static const struct {
static const struct {
const char *name;
const unsigned format;
} FORMATS[] = {
} _FORMATS[] = {
{"YUYV", V4L2_PIX_FMT_YUYV},
{"UYVY", V4L2_PIX_FMT_UYVY},
{"RGB565", V4L2_PIX_FMT_RGB565},
@@ -84,9 +84,8 @@ struct device_t *device_init() {
dev->standard = V4L2_STD_UNKNOWN;
dev->n_buffers = max_u(sysconf(_SC_NPROCESSORS_ONLN), 1) + 1;
dev->n_workers = dev->n_buffers;
dev->jpeg_quality = 80;
dev->timeout = 1;
dev->error_timeout = 1;
dev->error_delay = 1;
dev->run = run;
return dev;
}
@@ -97,18 +96,18 @@ void device_destroy(struct device_t *dev) {
}
int device_parse_format(const char *const str) {
for (unsigned index = 0; index < sizeof(FORMATS) / sizeof(FORMATS[0]); ++index) {
if (!strcasecmp(str, FORMATS[index].name)) {
return FORMATS[index].format;
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
if (!strcasecmp(str, _FORMATS[index].name)) {
return _FORMATS[index].format;
}
}
return FORMAT_UNKNOWN;
}
v4l2_std_id device_parse_standard(const char *const str) {
for (unsigned index = 1; index < sizeof(STANDARDS) / sizeof(STANDARDS[0]); ++index) {
if (!strcasecmp(str, STANDARDS[index].name)) {
return STANDARDS[index].standard;
for (unsigned index = 1; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
if (!strcasecmp(str, _STANDARDS[index].name)) {
return _STANDARDS[index].standard;
}
}
return STANDARD_UNKNOWN;
@@ -186,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);
@@ -205,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 {
@@ -251,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
@@ -409,9 +415,9 @@ static void _device_open_alloc_picbufs(struct device_t *dev) {
LOG_DEBUG("Allocating picture buffers ...");
A_CALLOC(dev->run->pictures, dev->run->n_buffers);
dev->run->max_picture_size = (dev->run->width * dev->run->height) << 1;
dev->run->max_picture_size = ((dev->run->width * dev->run->height) << 1) * 2;
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
LOG_DEBUG("Allocating picture buffer %d ...", index);
LOG_DEBUG("Allocating picture buffer %d sized %lu bytes... ", index, dev->run->max_picture_size);
A_CALLOC(dev->run->pictures[index].data, dev->run->max_picture_size);
dev->run->pictures[index].allocated = dev->run->max_picture_size;
}
@@ -435,19 +441,19 @@ static const char *_format_to_string_auto(char *buf, const size_t size, const un
}
static const char *_format_to_string_null(const unsigned format) {
for (unsigned index = 0; index < sizeof(FORMATS) / sizeof(FORMATS[0]); ++index) {
if (format == FORMATS[index].format) {
return FORMATS[index].name;
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
if (format == _FORMATS[index].format) {
return _FORMATS[index].name;
}
}
return NULL;
}
static const char *_standard_to_string(v4l2_std_id standard) {
for (unsigned index = 0; index < sizeof(STANDARDS) / sizeof(STANDARDS[0]); ++index) {
if (standard == STANDARDS[index].standard) {
return STANDARDS[index].name;
for (unsigned index = 0; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
if (standard == _STANDARDS[index].standard) {
return _STANDARDS[index].name;
}
}
return STANDARDS[0].name;
return _STANDARDS[0].name;
}

View File

@@ -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;
@@ -70,9 +74,9 @@ struct device_t {
unsigned n_workers;
unsigned every_frame;
unsigned min_frame_size;
unsigned jpeg_quality;
bool persistent;
unsigned timeout;
unsigned error_timeout;
unsigned error_delay;
struct device_runtime_t *run;
sig_atomic_t volatile stop;

View File

@@ -37,7 +37,7 @@
static const struct {
const char *name;
const enum encoder_type_t type;
} ENCODER_TYPES[] = {
} _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU},
# ifdef OMX_ENCODER
{"OMX", ENCODER_TYPE_OMX},
@@ -50,6 +50,7 @@ struct encoder_t *encoder_init() {
A_CALLOC(encoder, 1);
encoder->type = ENCODER_TYPE_CPU;
encoder->quality = 80;
return encoder;
}
@@ -60,6 +61,8 @@ void encoder_prepare(struct encoder_t *encoder) {
LOG_DEBUG("Initializing encoder ...");
}
LOG_INFO("Using JPEG quality: %d%%", encoder->quality);
# ifdef OMX_ENCODER
if (encoder->type == ENCODER_TYPE_OMX) {
if ((encoder->omx = omx_encoder_init()) == NULL) {
@@ -88,9 +91,9 @@ void encoder_destroy(struct encoder_t *encoder) {
}
enum encoder_type_t encoder_parse_type(const char *const str) {
for (unsigned index = 0; index < sizeof(ENCODER_TYPES) / sizeof(ENCODER_TYPES[0]); ++index) {
if (!strcasecmp(str, ENCODER_TYPES[index].name)) {
return ENCODER_TYPES[index].type;
for (unsigned index = 0; index < sizeof(_ENCODER_TYPES) / sizeof(_ENCODER_TYPES[0]); ++index) {
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
return _ENCODER_TYPES[index].type;
}
}
return ENCODER_TYPE_UNKNOWN;
@@ -104,7 +107,7 @@ void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev)
#pragma GCC diagnostic pop
# ifdef OMX_ENCODER
if (encoder->type == ENCODER_TYPE_OMX) {
if (omx_encoder_prepare_for_device(encoder->omx, dev) < 0) {
if (omx_encoder_prepare_for_device(encoder->omx, dev, encoder->quality, encoder->omx_use_ijg) < 0) {
goto use_fallback;
}
if (dev->run->n_workers > 1) {
@@ -125,11 +128,13 @@ void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev)
# pragma GCC diagnostic pop
}
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, int index) {
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);
jpeg_encoder_compress_buffer(dev, index, encoder->quality);
}
# ifdef OMX_ENCODER
else if (encoder->type == ENCODER_TYPE_OMX) {
@@ -139,6 +144,8 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, int
}
# endif
dev->run->pictures[index].encode_end_time = get_now_monotonic();
return 0;
# pragma GCC diagnostic ignored "-Wunused-label"

View File

@@ -39,17 +39,17 @@
enum encoder_type_t {
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
ENCODER_TYPE_CPU,
#ifdef OMX_ENCODER
ENCODER_TYPE_OMX,
#endif
};
struct encoder_t {
enum encoder_type_t type;
enum encoder_type_t type;
unsigned quality;
#ifdef OMX_ENCODER
struct omx_encoder_t *omx;
bool omx_use_ijg;
struct omx_encoder_t *omx;
#endif
};
@@ -61,4 +61,4 @@ enum encoder_type_t encoder_parse_type(const char *const str);
void encoder_prepare(struct encoder_t *encoder);
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, int index);
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, const unsigned index);

View File

@@ -22,7 +22,6 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <event2/event.h>
@@ -31,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
@@ -52,27 +53,27 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
static void _http_exposed_refresh(int fd, short event, void *v_server);
static void _http_queue_send_stream(struct http_server_t *server);
static void _http_queue_send_stream(struct http_server_t *server, const bool updated);
static void _expose_new_picture(struct http_server_t *server);
static void _expose_blank_picture(struct http_server_t *server);
static bool _expose_new_picture(struct http_server_t *server);
static bool _expose_blank_picture(struct http_server_t *server);
struct http_server_t *http_server_init(struct stream_t *stream) {
struct http_server_runtime_t *run;
struct http_server_t *server;
struct exposed_t *exposed;
struct timeval refresh_interval;
A_CALLOC(exposed, 1);
A_CALLOC(run, 1);
run->stream = stream;
run->exposed = exposed;
run->refresh_interval.tv_sec = 0;
run->refresh_interval.tv_usec = 30000; // ~30 refreshes per second
run->drop_same_frames_blank = 10;
A_CALLOC(server, 1);
server->host = "localhost";
server->host = "127.0.0.1";
server->port = 8080;
server->timeout = 10;
server->run = run;
@@ -86,11 +87,14 @@ 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;
refresh_interval.tv_usec = 30000; // ~30 refreshes per second
assert((run->refresh = event_new(run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
assert(!event_add(run->refresh, &run->refresh_interval));
assert(!event_add(run->refresh, &refresh_interval));
return server;
}
@@ -108,12 +112,16 @@ void http_server_destroy(struct http_server_t *server) {
}
int http_server_listen(struct http_server_t *server) {
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
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;
}
LOG_INFO("Listening HTTP on [%s]:%d", server->host, server->port);
return 0;
}
@@ -166,45 +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},"
" \"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->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;
struct timespec x_timestamp_spec;
char x_timestamp_buf[64];
char time_buf[64];
PROCESS_HEAD_REQUEST;
assert((buf = evbuffer_new()));
assert(!evbuffer_add(buf, (const void *)exposed->picture.data, exposed->picture.size));
# define EXPOSED(_next) server->run->exposed->_next
assert(!clock_gettime(CLOCK_REALTIME, &x_timestamp_spec));
sprintf(
x_timestamp_buf, "%u.%06u",
(unsigned)x_timestamp_spec.tv_sec,
(unsigned)(x_timestamp_spec.tv_nsec / 1000) // TODO: round?
);
assert((buf = evbuffer_new()));
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");
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
@@ -220,6 +252,9 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
struct evhttp_connection *conn;
struct bufferevent *buf_event;
struct stream_client_t *client;
char *client_addr;
unsigned short client_port;
uuid_t uuid;
PROCESS_HEAD_REQUEST;
@@ -229,6 +264,10 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->server = server;
client->request = request;
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;
@@ -239,6 +278,13 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->prev = last;
last->next = client;
}
server->run->stream_clients_count += 1;
evhttp_connection_get_peer(conn, &client_addr, &client_port);
LOG_INFO(
"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);
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
@@ -250,16 +296,23 @@ 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;
struct timespec x_timestamp_spec;
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()));
assert(!clock_gettime(CLOCK_REALTIME, &x_timestamp_spec));
if (client->need_initial) {
assert(evbuffer_add_printf(buf,
@@ -268,26 +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: %u.%06u" RN
RN,
client->server->run->exposed->picture.size * sizeof(*client->server->run->exposed->picture.data),
(unsigned)x_timestamp_spec.tv_sec,
(unsigned)(x_timestamp_spec.tv_nsec / 1000) // TODO: round?
"X-Timestamp: %.06Lf" RN
"%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));
@@ -296,16 +376,28 @@ 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;
struct evhttp_connection *conn;
char *client_addr = "???";
unsigned short client_port = 0;
client->server->run->stream_clients_count -= 1;
conn = evhttp_request_get_connection(client->request);
if (conn != NULL) {
evhttp_connection_get_peer(conn, &client_addr, &client_port);
}
LOG_INFO(
"HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
client_addr, client_port, client->server->run->stream_clients_count
);
if (conn != NULL) {
evhttp_connection_free(conn);
}
@@ -321,23 +413,39 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
free(client);
}
static void _http_queue_send_stream(struct http_server_t *server) {
struct stream_client_t *client;
static void _http_queue_send_stream(struct http_server_t *server, const bool updated) {
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) {
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) {
struct http_server_t *server = (struct http_server_t *)v_server;
bool updated = false;
bool queue_send = false;
#define LOCK_STREAM \
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
@@ -349,54 +457,145 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
LOG_DEBUG("Refreshing HTTP exposed ...");
LOCK_STREAM;
if (server->run->stream->picture.size > 0) { // If online
_expose_new_picture(server);
updated = _expose_new_picture(server);
UNLOCK_STREAM;
} else {
UNLOCK_STREAM;
_expose_blank_picture(server);
updated = _expose_blank_picture(server);
}
_http_queue_send_stream(server);
queue_send = true;
} else if (!server->run->exposed->online) {
LOG_DEBUG("Refreshing HTTP exposed (BLANK) ...");
_http_queue_send_stream(server);
updated = _expose_blank_picture(server);
queue_send = true;
}
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);
}
# undef LOCK_STREAM
# undef UNLOCK_STREAM
}
void _expose_new_picture(struct http_server_t *server) {
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;
static bool _expose_new_picture(struct http_server_t *server) {
# 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 \
EXPOSED(picture.data), STREAM(picture.data), \
STREAM(picture.size) * sizeof(*STREAM(picture.data))
if (server->drop_same_frames) {
if (
EXPOSED(online)
&& EXPOSED(dropped) < server->drop_same_frames
&& EXPOSED(picture.size) == STREAM(picture.size)
&& !memcmp(MEM_STREAM_TO_EXPOSED)
) {
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)
);
}
}
memcpy(
server->run->exposed->picture.data, server->run->stream->picture.data,
server->run->stream->picture.size * sizeof(*server->run->exposed->picture.data)
);
if (EXPOSED(picture.allocated) < STREAM(picture.allocated)) {
A_REALLOC(EXPOSED(picture.data), STREAM(picture.allocated));
EXPOSED(picture.allocated) = STREAM(picture.allocated);
}
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;
memcpy(MEM_STREAM_TO_EXPOSED);
# undef MEM_STREAM_TO_EXPOSED
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
}
void _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;
static bool _expose_blank_picture(struct http_server_t *server) {
# 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->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 (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:
EXPOSED(dropped) = 0;
EXPOSED(expose_end_time) = get_now_monotonic();
return true; // Updated
# undef EXPOSED
}

View File

@@ -20,7 +20,6 @@
#include <stdbool.h>
#include <time.h>
#include <event2/event.h>
#include <event2/http.h>
@@ -32,7 +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,7 +46,13 @@ struct exposed_t {
struct picture_t picture;
unsigned width;
unsigned height;
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 {
@@ -51,14 +61,16 @@ struct http_server_runtime_t {
struct event *refresh;
struct stream_t *stream;
struct exposed_t *exposed;
struct timeval refresh_interval;
struct stream_client_t *stream_clients;
unsigned stream_clients_count;
unsigned drop_same_frames_blank;
};
struct http_server_t {
char *host;
unsigned port;
unsigned drop_same_frames;
bool extra_stream_headers;
unsigned fake_width;
unsigned fake_height;
unsigned timeout;

View File

@@ -38,10 +38,7 @@
#include "encoder.h"
#define JPEG_OUTPUT_BUFFER_SIZE 4096
struct mjpg_destination_mgr {
struct _mjpg_destination_mgr {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buffer; // Start of buffer
unsigned char *outbuffer_cursor;
@@ -68,7 +65,7 @@ static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
static void _jpeg_term_destination(j_compress_ptr jpeg);
void jpeg_encoder_compress_buffer(struct device_t *dev, int index) {
void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, const unsigned quality) {
// This function based on compress_image_to_jpeg() from mjpg-streamer
struct jpeg_compress_struct jpeg;
@@ -89,7 +86,7 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, int index) {
jpeg.in_color_space = JCS_RGB;
jpeg_set_defaults(&jpeg);
jpeg_set_quality(&jpeg, dev->jpeg_quality, TRUE);
jpeg_set_quality(&jpeg, quality, TRUE);
jpeg_start_compress(&jpeg, TRUE);
@@ -110,18 +107,19 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, int index) {
jpeg_destroy_compress(&jpeg);
free(line_buffer);
assert(dev->run->pictures[index].size > 0);
assert(dev->run->pictures[index].size <= dev->run->max_picture_size);
}
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written) {
struct mjpg_destination_mgr *dest;
struct _mjpg_destination_mgr *dest;
if (jpeg->dest == NULL) {
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct mjpg_destination_mgr)
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct _mjpg_destination_mgr)
)));
}
dest = (struct mjpg_destination_mgr *) jpeg->dest;
dest = (struct _mjpg_destination_mgr *) jpeg->dest;
dest->mgr.init_destination = _jpeg_init_destination;
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
dest->mgr.term_destination = _jpeg_term_destination;
@@ -139,7 +137,7 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
while (jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for (unsigned x = 0; x < width; x++) {
for (unsigned x = 0; x < width; ++x) {
int y = (!z ? data[0] << 8 : data[2] << 8);
int u = data[1] - 128;
int v = data[3] - 128;
@@ -173,7 +171,7 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
while(jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for(unsigned x = 0; x < width; x++) {
for(unsigned x = 0; x < width; ++x) {
int y = (!z ? data[1] << 8 : data[3] << 8);
int u = data[0] - 128;
int v = data[2] - 128;
@@ -206,7 +204,7 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
while(jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for(unsigned x = 0; x < width; x++) {
for(unsigned x = 0; x < width; ++x) {
unsigned int two_byte = (data[1] << 8) + data[0];
*(ptr++) = data[1] & 248;
@@ -221,8 +219,10 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
}
}
#define JPEG_OUTPUT_BUFFER_SIZE 4096
static void _jpeg_init_destination(j_compress_ptr jpeg) {
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
// Allocate the output buffer - it will be released when done with image
assert((dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
@@ -236,7 +236,7 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
// Called whenever local jpeg buffer fills up
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
@@ -252,7 +252,7 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
// Called by jpeg_finish_compress after all data has been written.
// Usually needs to flush buffer
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
size_t data_count = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
// Write any data remaining in the buffer
@@ -260,3 +260,5 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
dest->outbuffer_cursor += data_count;
*dest->written += data_count;
}
#undef JPEG_OUTPUT_BUFFER_SIZE

View File

@@ -24,4 +24,4 @@
#include "../device.h"
void jpeg_encoder_compress_buffer(struct device_t *dev, int index);
void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, const unsigned quality);

View File

@@ -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_ms_ld(), 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); \
char *_ptr = strerror_r(errno, _buf, 1024); \
LOGGING_LOCK; \
printf("-- ERROR [%.03Lf tid=%ld] -- " _x_msg ": %s\n", now_ms_ld(), syscall(SYS_gettid), ##__VA_ARGS__, _buf); \
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", get_now_monotonic(), syscall(SYS_gettid), ##__VA_ARGS__, _ptr); \
fflush(stdout); \
LOGGING_UNLOCK; \
}
#define LOG_INFO(_x_msg, ...) { \
#define LOG_INFO(_msg, ...) { \
LOGGING_LOCK; \
printf("-- INFO [%.03Lf tid=%ld] -- " _x_msg "\n", now_ms_ld(), 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_ms_ld(), 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_ms_ld(), 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_ms_ld(), 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_ms_ld(), syscall(SYS_gettid), ##__VA_ARGS__); \
LOG_PRINTF_NOLOCK("DEBUG", _msg, ##__VA_ARGS__); \
LOGGING_UNLOCK; \
} \
}

View File

@@ -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: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'},
@@ -52,33 +53,55 @@ static const struct option _long_opts[] = {
{"dv-timings", no_argument, NULL, 't'},
{"buffers", required_argument, NULL, 'b'},
{"workers", required_argument, NULL, 'w'},
{"jpeg-quality", required_argument, NULL, 'q'},
{"quality", required_argument, NULL, 'q'},
{"encoder", required_argument, NULL, 'c'},
# ifdef OMX_ENCODER
{"encoder-omx-use-ijg", required_argument, NULL, 500},
# endif
{"device-timeout", required_argument, NULL, 1000},
{"device-error-timeout", required_argument, NULL, 1001},
{"device-persistent", no_argument, NULL, 1001},
{"device-error-delay", required_argument, NULL, 1002},
{"host", required_argument, NULL, 's'},
{"port", required_argument, NULL, 'p'},
{"fake-width", required_argument, NULL, 2000},
{"fake-height", required_argument, NULL, 2001},
{"server-timeout", required_argument, NULL, 2002},
{"drop-same-frames", required_argument, NULL, 'r'},
{"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},
{"debug", no_argument, NULL, 5000},
{"log-level", required_argument, NULL, 5001},
{"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 _help(struct device_t *dev, struct http_server_t *server) {
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");
@@ -92,40 +115,50 @@ static void _help(struct device_t *dev, struct http_server_t *server) {
printf(" Each buffer may processed using an intermediate thread.\n");
printf(" Default: %d (number of CPU cores + 1)\n\n", dev->n_buffers);
printf(" -w|--workers <N> -- The number of compressing threads. Default: %d (== --buffers).\n\n", dev->n_workers);
printf(" -q|--jpeg-quality <N> -- Set quality of JPEG encoding from 1 to 100 (best). Default: %d.\n\n", dev->jpeg_quality);
printf(" -q|--quality <N> -- Set quality of JPEG encoding from 1 to 100 (best). Default: %d.\n\n", encoder->quality);
printf(" --encoder <type> -- Use specified encoder. It may affects to workers number.\n");
printf(" -- Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
# ifdef OMX_ENCODER
printf(" --encoder-omx-use-ijg -- Use the standard IJG quality tables when encoding images using OMX.\n");
printf(" Default: disabled.\n\n");
# endif
printf(" --device-timeout <seconds> -- Timeout for device querying. Default: %d\n\n", dev->timeout);
printf(" --device-error-timeout <seconds> -- Delay before trying to connect to the device again\n");
printf(" after a timeout. Default: %d\n\n", dev->error_timeout);
printf(" --device-persistent -- Don't re-initialize device on timeout. Default: disabled.\n\n");
printf(" --device-error-delay <seconds> -- Delay before trying to connect to the device again\n");
printf(" after a timeout. Default: %d\n\n", dev->error_delay);
printf("HTTP server options:\n");
printf("--------------------\n");
printf(" --host <address> -- Listen on Hostname or IP. Default: %s\n\n", server->host);
printf(" --port <N> -- Bind to this TCP port. Default: %d\n\n", server->port);
printf(" --drop-same-frames <N> -- Don't send same frames to clients, but no more than specified number.\n");
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);
printf("Misc options:\n");
printf("-------------\n");
printf(" --debug -- Enabled debug messages (same as --log-level=3). Default: disabled.\n\n");
printf(" --log-level <N> -- Verbosity level of messages from 0 (info) to 3 (debug).\n");
printf(" Enabling debugging messages can slow down the program.\n");
printf(" Available levels: 0=info, 1=performance, 2=verbose, 3=debug.\n");
printf(" Default: %d.\n\n", log_level);
printf(" --perf -- Enable performance messages (same as log-level=1). Default: disabled.\n\n");
printf(" --verbose -- Enable verbose messages and lower (same as log-level=2). Default: disabled.\n\n");
printf(" --debug -- Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");
printf(" -h|--help -- Print this messages and exit.\n\n");
}
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); \
if (errno || _tmp < _min || _tmp > _max) \
{ printf("Invalid value for '%s=%u'; minimal=%u; maximum=%u\n", _name, _tmp, _min, _max); return -1; } \
{ printf("Invalid value for '%s=%u'; min=%u; max=%u\n", _name, _tmp, _min, _max); return -1; } \
_dest = _tmp; break; }
# define OPT_PARSE(_dest, _func, _invalid, _name) \
@@ -139,7 +172,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"
@@ -149,31 +183,40 @@ 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(dev->jpeg_quality, "--jpeg-quality", 1, 100);
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type")
case 1000: OPT_UNSIGNED(dev->timeout, "--timeout", 1, 60);
case 1001: OPT_UNSIGNED(dev->error_timeout, "--error-timeout", 1, 60);
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: OPT_SET(encoder->omx_use_ijg, true);
# endif
case 1000: OPT_UNSIGNED(dev->timeout, "--device-timeout", 1, 60);
case 1001: OPT_SET(dev->persistent, true);
case 1002: OPT_UNSIGNED(dev->error_delay, "--device-error-delay", 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 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 'r': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
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_DEBUG; break;
case 5001: OPT_UNSIGNED(log_level, "--log-level", 0, 3);
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, 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;
}
@@ -239,7 +282,7 @@ int main(int argc, char *argv[]) {
LOGGING_INIT;
dev = device_init();
encoder = encoder_init(ENCODER_TYPE_CPU);
encoder = encoder_init();
stream = stream_init(dev, encoder);
server = http_server_init(stream);
@@ -269,5 +312,5 @@ int main(int argc, char *argv[]) {
device_destroy(dev);
LOGGING_DESTROY;
return abs(exit_code);
return (exit_code < 0 ? 1 : 0);
}

View File

@@ -40,6 +40,8 @@
int component_enable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
int component_disable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, const OMX_U32 port);
int component_set_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef);
int component_set_state(OMX_HANDLETYPE *component, const OMX_STATETYPE state);

View File

@@ -19,6 +19,7 @@
*****************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
@@ -28,6 +29,7 @@
#include <bcm_host.h>
#include <IL/OMX_Core.h>
#include <IL/OMX_Component.h>
#include <IL/OMX_Broadcom.h>
#include <interface/vcos/vcos_semaphore.h>
#include "../logging.h"
@@ -46,7 +48,7 @@
static int _omx_init_component(struct omx_encoder_t *omx);
static int _omx_init_disable_ports(struct omx_encoder_t *omx);
static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev);
static int _omx_setup_output(struct omx_encoder_t *omx, struct device_t *dev);
static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality, const bool use_ijg);
static int _omx_encoder_clear_ports(struct omx_encoder_t *omx);
static OMX_ERRORTYPE _omx_event_handler(UNUSED OMX_HANDLETYPE encoder,
@@ -134,7 +136,7 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
free(omx);
}
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev) {
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality, const bool use_ijg) {
if (component_set_state(&omx->encoder, OMX_StateIdle) < 0) {
return -1;
}
@@ -144,7 +146,7 @@ int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *d
if (_omx_setup_input(omx, dev) < 0) {
return -1;
}
if (_omx_setup_output(omx, dev) < 0) {
if (_omx_setup_output(omx, quality, use_ijg) < 0) {
return -1;
}
if (component_set_state(&omx->encoder, OMX_StateExecuting) < 0) {
@@ -153,7 +155,7 @@ int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *d
return 0;
}
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, int index) {
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, const unsigned index) {
OMX_ERRORTYPE error;
bool loaded = false;
@@ -179,6 +181,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
omx->output_buffer->pBuffer,
omx->output_buffer->nFilledLen
);
assert(dev->run->pictures[index].size + omx->output_buffer->nFilledLen <= dev->run->max_picture_size);
dev->run->pictures[index].size += omx->output_buffer->nFilledLen;
if (omx->output_buffer->nFlags & OMX_BUFFERFLAG_ENDOFFRAME) {
@@ -215,6 +218,8 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
}
static int _omx_init_component(struct omx_encoder_t *omx) {
// http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html
OMX_ERRORTYPE error;
OMX_CALLBACKTYPE callbacks;
@@ -312,10 +317,9 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
return 0;
}
static int _omx_setup_output(struct omx_encoder_t *omx, struct device_t *dev) {
static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality, const bool use_ijg) {
OMX_ERRORTYPE error;
OMX_PARAM_PORTDEFINITIONTYPE portdef;
OMX_IMAGE_PARAM_QFACTORTYPE quality_factor;
LOG_DEBUG("Setting up OMX JPEG output port ...");
@@ -337,13 +341,42 @@ static int _omx_setup_output(struct omx_encoder_t *omx, struct device_t *dev) {
return -1;
}
OMX_INIT_STRUCTURE(quality_factor);
quality_factor.nPortIndex = OUTPUT_PORT;
quality_factor.nQFactor = dev->jpeg_quality;
{
OMX_CONFIG_BOOLEANTYPE exif;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamQFactor, &quality_factor)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't set OMX JPEG quality");
return -1;
OMX_INIT_STRUCTURE(exif);
exif.bEnabled = OMX_FALSE;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamBrcmDisableEXIF, &exif)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't disable EXIF on OMX JPEG");
return -1;
}
}
if (use_ijg) {
OMX_PARAM_IJGSCALINGTYPE ijg;
OMX_INIT_STRUCTURE(ijg);
ijg.nPortIndex = OUTPUT_PORT;
ijg.bEnabled = OMX_TRUE;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamBrcmEnableIJGTableScaling, &ijg)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't set OMX JPEG IJG settings");
return -1;
}
}
{
OMX_IMAGE_PARAM_QFACTORTYPE qfactor;
OMX_INIT_STRUCTURE(qfactor);
qfactor.nPortIndex = OUTPUT_PORT;
qfactor.nQFactor = quality;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamQFactor, &qfactor)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't set OMX JPEG quality");
return -1;
}
}
if (component_enable_port(&omx->encoder, OUTPUT_PORT) < 0) {

View File

@@ -49,5 +49,5 @@ struct omx_encoder_t {
struct omx_encoder_t *omx_encoder_init();
void omx_encoder_destroy(struct omx_encoder_t *omx);
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev);
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, int index);
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality, const bool use_ijg);
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, const unsigned index);

View File

@@ -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); \
}

View File

@@ -35,7 +35,7 @@
#define LOG_OMX_ERROR(_error, _msg, ...) { \
LOGGING_LOCK; \
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", now_ms_ld(), \
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", get_now_monotonic(), \
syscall(SYS_gettid), ##__VA_ARGS__, omx_error_to_string(_error)); \
LOGGING_UNLOCK; \
}

View File

@@ -78,7 +78,6 @@ void stream_loop(struct stream_t *stream) {
pool.workers_stop = &workers_stop;
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
LOG_INFO("Using JPEG quality: %d%%", stream->dev->jpeg_quality);
while (_stream_init_loop(stream->dev, &pool) == 0) {
struct worker_t *oldest_worker = NULL;
@@ -86,12 +85,15 @@ 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;
bool persistent_timeout_reported = false;
LOG_DEBUG("Allocation memory for stream picture ...");
A_CALLOC(stream->picture.data, stream->dev->run->max_picture_size);
LOG_INFO("Capturing ...");
while (!stream->dev->stop) {
int free_worker_number = -1;
@@ -112,7 +114,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
@@ -126,7 +128,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) {
@@ -155,23 +157,36 @@ void stream_loop(struct stream_t *stream) {
}
} else if (retval == 0) {
LOG_ERROR("Mainloop select() timeout");
break;
if (stream->dev->persistent) {
if (!persistent_timeout_reported) {
LOG_ERROR("Mainloop select() timeout, polling ...")
persistent_timeout_reported = true;
}
continue;
} else {
LOG_ERROR("Mainloop select() timeout");
break;
}
} else {
persistent_timeout_reported = false;
if (FD_ISSET(stream->dev->run->fd, &read_fds)) {
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) {
LOG_DEBUG("Dropping frame %d for option --every-frame=%d", frames_count + 1, stream->dev->every_frame);
++frames_count;
frames_count += 1;
LOG_DEBUG("Dropping frame %d for option --every-frame=%d", frames_count, stream->dev->every_frame);
goto pass_frame;
}
frames_count = 0;
@@ -188,8 +203,6 @@ void stream_loop(struct stream_t *stream) {
}
{
long double now = now_ms_ld();
if (now < grab_after) {
fluency_passed += 1;
LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf; grab_after=%.03Lf", fluency_passed, now, grab_after);
@@ -197,14 +210,13 @@ void stream_loop(struct stream_t *stream) {
}
fluency_passed = 0;
if (log_level >= LOG_LEVEL_PERF) {
if ((long long)now != fps_second) {
LOG_PERF("Oldest worker complete, encoding FPS = %u", fps);
fps = 0;
fps_second = (long long)now;
}
++fps;
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);
}
captured_fps_accum += 1;
long double fluency_delay = _stream_get_fluency_delay(stream->dev, &pool);
@@ -285,20 +297,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) {
@@ -321,8 +340,8 @@ static int _stream_init_loop(struct device_t *dev, struct workers_pool_t *pool)
LOG_DEBUG("%s: *dev->stop = %d", __FUNCTION__, dev->stop);
while (!dev->stop) {
if ((retval = _stream_init(dev, pool)) < 0) {
LOG_INFO("Sleeping %d seconds before new stream init ...", dev->error_timeout);
sleep(dev->error_timeout);
LOG_INFO("Sleeping %d seconds before new stream init ...", dev->error_delay);
sleep(dev->error_delay);
} else {
break;
}
@@ -370,25 +389,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);
}
@@ -406,11 +429,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_ms_ld();
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) {
@@ -418,10 +436,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_ms_ld() - 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;

View File

@@ -86,6 +86,7 @@ struct stream_t {
struct picture_t picture;
unsigned width;
unsigned height;
unsigned captured_fps;
bool updated;
pthread_mutex_t mutex;
struct device_t *dev;

View File

@@ -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,10 +61,14 @@ INLINE unsigned max_u(unsigned a, unsigned b) {
return (a > b ? a : b);
}
INLINE void now_ms(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(CLOCK_MONOTONIC_RAW, &spec));
assert(!clock_gettime(clk_id, &spec));
*sec = spec.tv_sec;
*msec = round(spec.tv_nsec / 1.0e6);
@@ -74,10 +78,18 @@ INLINE void now_ms(time_t *sec, long *msec) {
}
}
INLINE long double now_ms_ld(void) {
INLINE long double get_now_monotonic(void) {
time_t sec;
long msec;
now_ms(&sec, &msec);
get_now(CLOCK_MONOTONIC_RAW, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}
INLINE long double get_now_real(void) {
time_t sec;
long msec;
get_now(CLOCK_REALTIME, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}

View File

@@ -71,7 +71,6 @@ def main():
*****************************************************************************/
""").strip() + "\n\n\n" + text
with open(dest, "w") as h_file:
h_file.write(text)