mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bce3ae0f21 | ||
|
|
1541921070 | ||
|
|
cc25abcc00 | ||
|
|
e80ee2f574 | ||
|
|
56a95c7f17 | ||
|
|
667e3610b2 | ||
|
|
142c8c84ac | ||
|
|
383075d323 | ||
|
|
e2922aa820 | ||
|
|
dc9667cf0c | ||
|
|
bbbfda0b5c | ||
|
|
aa3f079ee9 | ||
|
|
59bd4e8dd2 | ||
|
|
ce6184b8cd | ||
|
|
9ca511d29d | ||
|
|
74de28c64a | ||
|
|
24060e8068 | ||
|
|
3a03d48855 | ||
|
|
935a9125d6 | ||
|
|
d4ea97ef2c | ||
|
|
867aa4e52a | ||
|
|
bc2bb444dc | ||
|
|
19796f3b64 | ||
|
|
348a6e4a8f | ||
|
|
ba3300ddde | ||
|
|
09526884f4 | ||
|
|
160a0e8d29 | ||
|
|
9dddd0075e | ||
|
|
0e0e3939b2 | ||
|
|
9c8d87412e | ||
|
|
3666d92819 | ||
|
|
9388d4dd22 | ||
|
|
6cf4924e05 | ||
|
|
713e3751b4 | ||
|
|
8e61d7c55e | ||
|
|
4e42c42bae | ||
|
|
b53e3edef1 | ||
|
|
0afbf02451 | ||
|
|
dd79efd6f5 | ||
|
|
97ac19a2fe | ||
|
|
755e0c2a2a | ||
|
|
ccab33a290 | ||
|
|
76a8e65e80 | ||
|
|
3b86e64222 | ||
|
|
020482a05a | ||
|
|
1896e22dff | ||
|
|
56df20fe84 | ||
|
|
ed7dabbfcb | ||
|
|
b1d40d1b3a | ||
|
|
43939c7475 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 0.35
|
||||
current_version = 0.52
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,4 +5,5 @@
|
||||
/ustreamer-*.pkg.tar.xz
|
||||
/vgcore.*
|
||||
/ustreamer
|
||||
/*.sock
|
||||
*.o
|
||||
|
||||
4
Makefile
4
Makefile
@@ -8,7 +8,7 @@ LDFLAGS ?=
|
||||
CC = gcc
|
||||
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)
|
||||
SOURCES = $(shell ls src/*.c src/jpeg/*.c src/hw/*.c)
|
||||
OBJECTS = $(SOURCES:.c=.o)
|
||||
PROG = ustreamer
|
||||
|
||||
@@ -59,5 +59,5 @@ push:
|
||||
|
||||
clean-all: clean
|
||||
clean:
|
||||
rm -f src/*.o src/{jpeg,omx}/*.o vgcore.* $(PROG)
|
||||
rm -f src/*.o src/{jpeg,omx}/*.o vgcore.* *.sock $(PROG)
|
||||
rm -rf pkg src/$(PROG)-* src/v*.tar.gz v*.tar.gz $(PROG)-*.pkg.tar.xz
|
||||
|
||||
2
PKGBUILD
2
PKGBUILD
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=0.35
|
||||
pkgver=0.52
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pi-kvm/ustreamer"
|
||||
|
||||
13
README.md
13
README.md
@@ -10,22 +10,20 @@
|
||||
|----------|---------------|-------------------|
|
||||
| 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> |
|
||||
| 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 |
|
||||
| Streaming via UNIX domain socket |  Yes |  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 |
|
||||
| Option to serve files<br>with a built-in HTTP server, auth settings |  No <sup>3</sup> |  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.
|
||||
* ```3``` ...and there'll never be. µStreamer is designed UNIX-way, so if you need a small website with your broadcast, install NGINX.
|
||||
|
||||
-----
|
||||
# TL;DR
|
||||
@@ -58,8 +56,7 @@ The recommended way of running µStreamer with [Auvidea B101](https://www.raspbe
|
||||
$ ./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
|
||||
--dv-timings \ # Use DV-timings
|
||||
--drop-same-frames=30 # Save that traffic
|
||||
```
|
||||
|
||||
|
||||
@@ -13,19 +13,17 @@
|
||||
| Поведение при физическом отключении устройства<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> |  Нет |
|
||||
| Стрим через UNIX domain socket |  Есть |  Нет |
|
||||
| Дебаг-логи без перекомпиляции,<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> |  Есть |
|
||||
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации |  Нет <sup>3</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.
|
||||
* ```3``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
|
||||
|
||||
-----
|
||||
# TL;DR
|
||||
@@ -59,7 +57,6 @@ $ ./ustreamer \
|
||||
--format=uyvy \ # Настройка входного формата устройства
|
||||
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
|
||||
--dv-timings \ # Включение DV-таймингов
|
||||
--quality=20 \ # У OpenMAX нелинейная шкала качества
|
||||
--drop-same-frames=30 # Экономим трафик
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -21,4 +22,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION "0.35"
|
||||
#define VERSION "0.52"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@
|
||||
<hr>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/ping"><b><samp>/ping</samp></b></a><br>
|
||||
<a href="/state"><b><samp>/state</samp></b></a><br>
|
||||
Get JSON structure with state of the server.
|
||||
</li>
|
||||
<br>
|
||||
@@ -25,6 +25,12 @@
|
||||
Get a live stream. Query params:<br>
|
||||
<br>
|
||||
<ul>
|
||||
<li>
|
||||
<b><samp>key=abc123</samp></b><br>
|
||||
User-defined key, which is part of cookie <samp>stream_client</samp>, which allows<br>
|
||||
the stream client to determine its identifier and view statistics using <a href="/state"><samp>/state</samp></a>.
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<b><samp>extra_headers=1</samp></b><br>
|
||||
Add <samp>X-UStreamer-*</samp> headers to /stream handle (like on <a href="/snapshot"><samp>/snapshot</samp></a>).
|
||||
@@ -40,7 +46,7 @@
|
||||
<b><samp>dual_final_frames=1</samp></b><br>
|
||||
Enable workaround for Safari/WebKit bug when using option <samp>--drop-same-frames</samp>.<br>
|
||||
Without this option, when the frame series is completed, WebKit-based browsers<br>
|
||||
renders the last one with a delay.
|
||||
renders the last frame with a delay.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -38,7 +39,7 @@ const char *HTML_INDEX_PAGE = " \
|
||||
<hr> \
|
||||
<ul> \
|
||||
<li> \
|
||||
<a href=\"/ping\"><b><samp>/ping</samp></b></a><br> \
|
||||
<a href=\"/state\"><b><samp>/state</samp></b></a><br> \
|
||||
Get JSON structure with state of the server. \
|
||||
</li> \
|
||||
<br> \
|
||||
@@ -52,6 +53,12 @@ const char *HTML_INDEX_PAGE = " \
|
||||
Get a live stream. Query params:<br> \
|
||||
<br> \
|
||||
<ul> \
|
||||
<li> \
|
||||
<b><samp>key=abc123</samp></b><br> \
|
||||
User-defined key, which is part of cookie <samp>stream_client</samp>, which allows<br> \
|
||||
the stream client to determine its identifier and view statistics using <a href=\"/state\"><samp>/state</samp></a>. \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
<b><samp>extra_headers=1</samp></b><br> \
|
||||
Add <samp>X-UStreamer-*</samp> headers to /stream handle (like on <a href=\"/snapshot\"><samp>/snapshot</samp></a>). \
|
||||
@@ -67,7 +74,7 @@ const char *HTML_INDEX_PAGE = " \
|
||||
<b><samp>dual_final_frames=1</samp></b><br> \
|
||||
Enable workaround for Safari/WebKit bug when using option <samp>--drop-same-frames</samp>.<br> \
|
||||
Without this option, when the frame series is completed, WebKit-based browsers<br> \
|
||||
renders the last one with a delay. \
|
||||
renders the last frame with a delay. \
|
||||
</li> \
|
||||
</ul> \
|
||||
</li> \
|
||||
|
||||
99
src/device.c
99
src/device.c
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -26,6 +27,7 @@
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <linux/videodev2.h>
|
||||
@@ -53,6 +55,8 @@ static const struct {
|
||||
{"YUYV", V4L2_PIX_FMT_YUYV},
|
||||
{"UYVY", V4L2_PIX_FMT_UYVY},
|
||||
{"RGB565", V4L2_PIX_FMT_RGB565},
|
||||
{"JPEG", V4L2_PIX_FMT_MJPEG},
|
||||
{"JPEG", V4L2_PIX_FMT_JPEG},
|
||||
};
|
||||
|
||||
|
||||
@@ -63,10 +67,11 @@ static int _device_open_format(struct device_t *dev);
|
||||
static void _device_open_alloc_picbufs(struct device_t *dev);
|
||||
static int _device_open_mmap(struct device_t *dev);
|
||||
static int _device_open_queue_buffers(struct device_t *dev);
|
||||
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height);
|
||||
|
||||
static const char *_format_to_string_auto(char *buf, const size_t size, const unsigned format);
|
||||
static const char *_format_to_string_null(const unsigned format);
|
||||
static const char *_standard_to_string(const v4l2_std_id standard);
|
||||
static const char *_format_to_string_auto(char *buf, size_t size, unsigned format);
|
||||
static const char *_format_to_string_null(unsigned format);
|
||||
static const char *_standard_to_string(v4l2_std_id standard);
|
||||
|
||||
|
||||
struct device_t *device_init() {
|
||||
@@ -84,7 +89,6 @@ 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->soft_fps = 30;
|
||||
dev->timeout = 1;
|
||||
dev->error_delay = 1;
|
||||
dev->run = run;
|
||||
@@ -96,8 +100,8 @@ void device_destroy(struct device_t *dev) {
|
||||
free(dev);
|
||||
}
|
||||
|
||||
int device_parse_format(const char *const str) {
|
||||
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
|
||||
int device_parse_format(const char *str) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
|
||||
if (!strcasecmp(str, _FORMATS[index].name)) {
|
||||
return _FORMATS[index].format;
|
||||
}
|
||||
@@ -105,8 +109,8 @@ int device_parse_format(const char *const str) {
|
||||
return FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
v4l2_std_id device_parse_standard(const char *const str) {
|
||||
for (unsigned index = 1; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
|
||||
v4l2_std_id device_parse_standard(const char *str) {
|
||||
for (unsigned index = 1; index < ARRAY_LEN(_STANDARDS); ++index) {
|
||||
if (!strcasecmp(str, _STANDARDS[index].name)) {
|
||||
return _STANDARDS[index].standard;
|
||||
}
|
||||
@@ -162,7 +166,7 @@ void device_close(struct device_t *dev) {
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
if (dev->run->hw_buffers[index].start != MAP_FAILED) {
|
||||
if (munmap(dev->run->hw_buffers[index].start, dev->run->hw_buffers[index].length) < 0) {
|
||||
LOG_PERROR("Can't unmap device buffer %d", index);
|
||||
LOG_PERROR("Can't unmap device buffer %u", index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,6 +227,7 @@ static int _device_open_check_cap(struct device_t *dev) {
|
||||
}
|
||||
|
||||
static int _device_open_dv_timings(struct device_t *dev) {
|
||||
_device_apply_resolution(dev, dev->width, dev->height);
|
||||
if (dev->dv_timings) {
|
||||
LOG_DEBUG("Using DV-timings");
|
||||
|
||||
@@ -240,10 +245,6 @@ static int _device_open_dv_timings(struct device_t *dev) {
|
||||
LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE");
|
||||
return -1;
|
||||
}
|
||||
|
||||
} else {
|
||||
dev->run->width = dev->width;
|
||||
dev->run->height = dev->height;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -256,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",
|
||||
"Got new DV timings: resolution=%ux%u; pixclk=%llu",
|
||||
dv_timings.bt.width,
|
||||
dv_timings.bt.height,
|
||||
dv_timings.bt.pixelclock
|
||||
@@ -268,8 +269,9 @@ static int _device_apply_dv_timings(struct device_t *dev) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dev->run->width = dv_timings.bt.width;
|
||||
dev->run->height = dv_timings.bt.height;
|
||||
if (_device_apply_resolution(dev, dv_timings.bt.width, dv_timings.bt.height) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYSTD) ...");
|
||||
@@ -286,6 +288,7 @@ static int _device_apply_dv_timings(struct device_t *dev) {
|
||||
|
||||
static int _device_open_format(struct device_t *dev) {
|
||||
struct v4l2_format fmt;
|
||||
char format_str[8];
|
||||
|
||||
MEMSET_ZERO(fmt);
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
@@ -297,10 +300,8 @@ static int _device_open_format(struct device_t *dev) {
|
||||
// Set format
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_S_FMT, &fmt) < 0) {
|
||||
char format_str[8];
|
||||
|
||||
LOG_PERROR(
|
||||
"Unable to set format=%s; resolution=%dx%d",
|
||||
"Unable to set format=%s; resolution=%ux%u",
|
||||
_format_to_string_auto(format_str, 8, dev->format),
|
||||
dev->run->width,
|
||||
dev->run->height
|
||||
@@ -310,21 +311,21 @@ static int _device_open_format(struct device_t *dev) {
|
||||
|
||||
// Check resolution
|
||||
if (fmt.fmt.pix.width != dev->run->width || fmt.fmt.pix.height != dev->run->height) {
|
||||
LOG_ERROR("Requested resolution=%dx%d is unavailable", dev->run->width, dev->run->height);
|
||||
LOG_ERROR("Requested resolution=%ux%u is unavailable", dev->run->width, dev->run->height);
|
||||
}
|
||||
dev->run->width = fmt.fmt.pix.width;
|
||||
dev->run->height = fmt.fmt.pix.height;
|
||||
LOG_INFO("Using resolution: %dx%d", dev->run->width, dev->run->height);
|
||||
if (_device_apply_resolution(dev, fmt.fmt.pix.width, fmt.fmt.pix.height) < 0) {
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO("Using resolution: %ux%u", dev->run->width, dev->run->height);
|
||||
|
||||
// Check format
|
||||
if (fmt.fmt.pix.pixelformat != dev->format) {
|
||||
char format_requested_str[8];
|
||||
char format_obtained_str[8];
|
||||
char *format_str_nullable;
|
||||
|
||||
LOG_ERROR(
|
||||
"Could not obtain the requested pixelformat=%s; driver gave us %s",
|
||||
_format_to_string_auto(format_requested_str, 8, dev->format),
|
||||
_format_to_string_auto(format_str, 8, dev->format),
|
||||
_format_to_string_auto(format_obtained_str, 8, fmt.fmt.pix.pixelformat)
|
||||
);
|
||||
|
||||
@@ -339,7 +340,9 @@ static int _device_open_format(struct device_t *dev) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
dev->run->format = fmt.fmt.pix.pixelformat;
|
||||
LOG_INFO("Using pixelformat: %s", _format_to_string_auto(format_str, 8, dev->run->format));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -358,10 +361,10 @@ static int _device_open_mmap(struct device_t *dev) {
|
||||
}
|
||||
|
||||
if (req.count < 1) {
|
||||
LOG_ERROR("Insufficient buffer memory: %d", req.count);
|
||||
LOG_ERROR("Insufficient buffer memory: %u", req.count);
|
||||
return -1;
|
||||
} else {
|
||||
LOG_INFO("Requested %d HW buffers, got %d", dev->n_buffers, req.count);
|
||||
LOG_INFO("Requested %u HW buffers, got %u", dev->n_buffers, req.count);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Allocating HW buffers ...");
|
||||
@@ -375,17 +378,17 @@ static int _device_open_mmap(struct device_t *dev) {
|
||||
buf_info.memory = V4L2_MEMORY_MMAP;
|
||||
buf_info.index = dev->run->n_buffers;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer %d ...", dev->run->n_buffers);
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer %u ...", dev->run->n_buffers);
|
||||
if (xioctl(dev->run->fd, VIDIOC_QUERYBUF, &buf_info) < 0) {
|
||||
LOG_PERROR("Can't VIDIOC_QUERYBUF");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Mapping device buffer %d ...", dev->run->n_buffers);
|
||||
LOG_DEBUG("Mapping device buffer %u ...", dev->run->n_buffers);
|
||||
dev->run->hw_buffers[dev->run->n_buffers].length = buf_info.length;
|
||||
dev->run->hw_buffers[dev->run->n_buffers].start = mmap(NULL, buf_info.length, PROT_READ|PROT_WRITE, MAP_SHARED, dev->run->fd, buf_info.m.offset);
|
||||
if (dev->run->hw_buffers[dev->run->n_buffers].start == MAP_FAILED) {
|
||||
LOG_PERROR("Can't map device buffer %d", dev->run->n_buffers);
|
||||
LOG_PERROR("Can't map device buffer %u", dev->run->n_buffers);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -401,7 +404,7 @@ static int _device_open_queue_buffers(struct device_t *dev) {
|
||||
buf_info.memory = V4L2_MEMORY_MMAP;
|
||||
buf_info.index = index;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %d ...", index);
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %u ...", index);
|
||||
if (xioctl(dev->run->fd, VIDIOC_QBUF, &buf_info) < 0) {
|
||||
LOG_PERROR("Can't VIDIOC_QBUF");
|
||||
return -1;
|
||||
@@ -416,18 +419,34 @@ static void _device_open_alloc_picbufs(struct device_t *dev) {
|
||||
|
||||
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 sized %lu bytes... ", index, dev->run->max_picture_size);
|
||||
LOG_DEBUG("Allocating picture buffer %u 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;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *_format_to_string_auto(char *buf, const size_t size, const unsigned format) {
|
||||
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height) {
|
||||
// Тут VIDEO_MIN_* не используются из-за странностей минимального разрешения при отсутствии сигнала
|
||||
// у некоторых устройств, например Auvidea B101
|
||||
if (
|
||||
width == 0 || width > VIDEO_MAX_WIDTH
|
||||
|| height == 0 || height > VIDEO_MAX_HEIGHT
|
||||
) {
|
||||
LOG_ERROR("Requested forbidden resolution=%ux%u: min=1x1; max=%ux%u",
|
||||
width, height, VIDEO_MAX_WIDTH, VIDEO_MAX_HEIGHT);
|
||||
return -1;
|
||||
}
|
||||
dev->run->width = width;
|
||||
dev->run->height = height;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *_format_to_string_auto(char *buf, size_t size, unsigned format) {
|
||||
assert(size >= 8);
|
||||
buf[0] = format & 0x7f;
|
||||
buf[1] = (format >> 8) & 0x7f;
|
||||
buf[2] = (format >> 16) & 0x7f;
|
||||
buf[3] = (format >> 24) & 0x7f;
|
||||
buf[0] = format & 0x7F;
|
||||
buf[1] = (format >> 8) & 0x7F;
|
||||
buf[2] = (format >> 16) & 0x7F;
|
||||
buf[3] = (format >> 24) & 0x7F;
|
||||
if (format & (1 << 31)) {
|
||||
buf[4] = '-';
|
||||
buf[5] = 'B';
|
||||
@@ -439,8 +458,8 @@ static const char *_format_to_string_auto(char *buf, const size_t size, const un
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *_format_to_string_null(const unsigned format) {
|
||||
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
|
||||
static const char *_format_to_string_null(unsigned format) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
|
||||
if (format == _FORMATS[index].format) {
|
||||
return _FORMATS[index].name;
|
||||
}
|
||||
@@ -449,7 +468,7 @@ static const char *_format_to_string_null(const unsigned format) {
|
||||
}
|
||||
|
||||
static const char *_standard_to_string(v4l2_std_id standard) {
|
||||
for (unsigned index = 0; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_STANDARDS); ++index) {
|
||||
if (standard == _STANDARDS[index].standard) {
|
||||
return _STANDARDS[index].name;
|
||||
}
|
||||
|
||||
18
src/device.h
18
src/device.h
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -28,11 +29,17 @@
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
|
||||
#define VIDEO_MIN_WIDTH 320
|
||||
#define VIDEO_MAX_WIDTH 1920
|
||||
|
||||
#define VIDEO_MIN_HEIGHT 180
|
||||
#define VIDEO_MAX_HEIGHT 1200
|
||||
|
||||
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
|
||||
#define STANDARDS_STR "UNKNOWN, PAL, NTSC, SECAM"
|
||||
|
||||
#define FORMAT_UNKNOWN -1
|
||||
#define FORMATS_STR "YUYV, UYVY, RGB565"
|
||||
#define FORMATS_STR "YUYV, UYVY, RGB565, JPEG"
|
||||
|
||||
|
||||
struct hw_buffer_t {
|
||||
@@ -55,7 +62,7 @@ struct device_runtime_t {
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned n_buffers;
|
||||
unsigned n_workers;
|
||||
// unsigned n_workers; // FIXME
|
||||
struct hw_buffer_t *hw_buffers;
|
||||
struct picture_t *pictures;
|
||||
unsigned long max_picture_size;
|
||||
@@ -72,8 +79,7 @@ struct device_t {
|
||||
bool dv_timings;
|
||||
unsigned n_buffers;
|
||||
unsigned n_workers;
|
||||
unsigned soft_fps;
|
||||
unsigned every_frame;
|
||||
unsigned desired_fps;
|
||||
unsigned min_frame_size;
|
||||
bool persistent;
|
||||
unsigned timeout;
|
||||
@@ -87,8 +93,8 @@ struct device_t {
|
||||
struct device_t *device_init();
|
||||
void device_destroy(struct device_t *dev);
|
||||
|
||||
int device_parse_format(const char *const str);
|
||||
v4l2_std_id device_parse_standard(const char *const str);
|
||||
int device_parse_format(const char *str);
|
||||
v4l2_std_id device_parse_standard(const char *str);
|
||||
|
||||
int device_open(struct device_t *dev);
|
||||
void device_close(struct device_t *dev);
|
||||
|
||||
120
src/encoder.c
120
src/encoder.c
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -22,12 +23,15 @@
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
|
||||
#include "jpeg/encoder.h"
|
||||
#include "hw/encoder.h"
|
||||
|
||||
#ifdef OMX_ENCODER
|
||||
# include "omx/encoder.h"
|
||||
@@ -39,6 +43,7 @@ static const struct {
|
||||
const enum encoder_type_t type;
|
||||
} _ENCODER_TYPES[] = {
|
||||
{"CPU", ENCODER_TYPE_CPU},
|
||||
{"HW", ENCODER_TYPE_HW},
|
||||
# ifdef OMX_ENCODER
|
||||
{"OMX", ENCODER_TYPE_OMX},
|
||||
# endif
|
||||
@@ -46,11 +51,18 @@ static const struct {
|
||||
|
||||
|
||||
struct encoder_t *encoder_init() {
|
||||
struct encoder_runtime_t *run;
|
||||
struct encoder_t *encoder;
|
||||
|
||||
A_CALLOC(run, 1);
|
||||
run->type = ENCODER_TYPE_CPU;
|
||||
run->quality = 80;
|
||||
A_PTHREAD_M_INIT(&run->mutex);
|
||||
|
||||
A_CALLOC(encoder, 1);
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
encoder->quality = 80;
|
||||
encoder->type = run->type;
|
||||
encoder->quality = run->quality;
|
||||
encoder->run = run;
|
||||
return encoder;
|
||||
}
|
||||
|
||||
@@ -60,15 +72,16 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
// XXX: Тут нет гонки, потому что encoder_prepare() запускается еще до существования других потоков
|
||||
encoder->run->type = encoder->type;
|
||||
encoder->run->quality = encoder->quality;
|
||||
|
||||
if (encoder->type != ENCODER_TYPE_CPU) {
|
||||
LOG_DEBUG("Initializing encoder ...");
|
||||
}
|
||||
|
||||
LOG_INFO("Using JPEG quality: %d%%", encoder->quality);
|
||||
LOG_INFO("Using JPEG quality: %u%%", encoder->quality);
|
||||
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||
LOG_DEBUG("Preparing OMX encoder ...");
|
||||
|
||||
if (dev->n_workers > OMX_MAX_ENCODERS) {
|
||||
LOG_INFO(
|
||||
"OMX-based encoder can only work with %u worker threads; forced --workers=%u",
|
||||
@@ -76,11 +89,11 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||
);
|
||||
dev->n_workers = OMX_MAX_ENCODERS;
|
||||
}
|
||||
encoder->n_omxs = dev->n_workers;
|
||||
encoder->run->n_omxs = dev->n_workers;
|
||||
|
||||
A_CALLOC(encoder->omxs, encoder->n_omxs);
|
||||
for (unsigned index = 0; index < encoder->n_omxs; ++index) {
|
||||
if ((encoder->omxs[index] = omx_encoder_init()) == NULL) {
|
||||
A_CALLOC(encoder->run->omxs, encoder->run->n_omxs);
|
||||
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
||||
if ((encoder->run->omxs[index] = omx_encoder_init()) == NULL) {
|
||||
goto use_fallback;
|
||||
}
|
||||
}
|
||||
@@ -93,26 +106,29 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||
# pragma GCC diagnostic push
|
||||
use_fallback:
|
||||
LOG_ERROR("Can't initialize selected encoder, using CPU instead it");
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
encoder->run->type = ENCODER_TYPE_CPU;
|
||||
encoder->run->quality = encoder->quality;
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
void encoder_destroy(struct encoder_t *encoder) {
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->omxs) {
|
||||
for (unsigned index = 0; index < encoder->n_omxs; ++index) {
|
||||
if (encoder->omxs[index]) {
|
||||
omx_encoder_destroy(encoder->omxs[index]);
|
||||
if (encoder->run->omxs) {
|
||||
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
||||
if (encoder->run->omxs[index]) {
|
||||
omx_encoder_destroy(encoder->run->omxs[index]);
|
||||
}
|
||||
}
|
||||
free(encoder->omxs);
|
||||
free(encoder->run->omxs);
|
||||
}
|
||||
# endif
|
||||
A_PTHREAD_M_DESTROY(&encoder->run->mutex);
|
||||
free(encoder->run);
|
||||
free(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) {
|
||||
enum encoder_type_t encoder_parse_type(const char *str) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
||||
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
|
||||
return _ENCODER_TYPES[index].type;
|
||||
}
|
||||
@@ -123,13 +139,35 @@ enum encoder_type_t encoder_parse_type(const char *const str) {
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic push
|
||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
if (
|
||||
(dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG)
|
||||
&& encoder->run->type != ENCODER_TYPE_HW
|
||||
) {
|
||||
LOG_INFO("Switching to HW encoder because the input format is (M)JPEG");
|
||||
A_PTHREAD_M_LOCK(&encoder->run->mutex);
|
||||
encoder->run->type = ENCODER_TYPE_HW;
|
||||
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
|
||||
}
|
||||
|
||||
if (encoder->run->type == ENCODER_TYPE_HW) {
|
||||
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
|
||||
LOG_INFO("Switching to CPU encoder because the input format is not (M)JPEG");
|
||||
goto use_fallback;
|
||||
}
|
||||
if (hw_encoder_prepare_live(dev, encoder->quality) < 0) {
|
||||
A_PTHREAD_M_LOCK(&encoder->run->mutex);
|
||||
encoder->run->quality = 0;
|
||||
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
|
||||
}
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
for (unsigned index = 0; index < encoder->n_omxs; ++index) {
|
||||
if (omx_encoder_prepare_live(encoder->omxs[index], dev, encoder->quality, encoder->omx_use_ijg) < 0) {
|
||||
else if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
||||
if (omx_encoder_prepare_live(encoder->run->omxs[index], dev, encoder->quality) < 0) {
|
||||
LOG_ERROR("Can't prepare OMX encoder, falling back to CPU");
|
||||
goto use_fallback;
|
||||
}
|
||||
}
|
||||
@@ -141,41 +179,43 @@ void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
use_fallback:
|
||||
LOG_ERROR("Can't prepare selected encoder, falling back to CPU");
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
A_PTHREAD_M_LOCK(&encoder->run->mutex);
|
||||
encoder->run->type = ENCODER_TYPE_CPU;
|
||||
encoder->run->quality = encoder->quality;
|
||||
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-label"
|
||||
#pragma GCC diagnostic push
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev,
|
||||
const unsigned worker_number, const unsigned buf_index) {
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index) {
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
dev->run->pictures[buf_index].encode_begin_time = get_now_monotonic();
|
||||
|
||||
if (encoder->type == ENCODER_TYPE_CPU) {
|
||||
if (encoder->run->type == ENCODER_TYPE_CPU) {
|
||||
jpeg_encoder_compress_buffer(dev, buf_index, encoder->quality);
|
||||
} else if (encoder->run->type == ENCODER_TYPE_HW) {
|
||||
hw_encoder_compress_buffer(dev, buf_index);
|
||||
}
|
||||
# ifdef OMX_ENCODER
|
||||
else if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
if (omx_encoder_compress_buffer(encoder->omxs[worker_number], dev, buf_index) < 0) {
|
||||
goto error;
|
||||
else if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
|
||||
goto use_fallback;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
dev->run->pictures[buf_index].encode_end_time = get_now_monotonic();
|
||||
|
||||
return 0;
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
error:
|
||||
LOG_INFO("HW compressing error, falling back to CPU");
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
use_fallback:
|
||||
LOG_INFO("Error while compressing, falling back to CPU");
|
||||
A_PTHREAD_M_LOCK(&encoder->run->mutex);
|
||||
encoder->run->type = ENCODER_TYPE_CPU;
|
||||
encoder->run->quality = encoder->quality;
|
||||
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
|
||||
return -1;
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -21,6 +22,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pthread.h"
|
||||
|
||||
#include "tools.h"
|
||||
#include "device.h"
|
||||
|
||||
@@ -33,35 +36,44 @@
|
||||
|
||||
|
||||
#define ENCODER_TYPES_STR \
|
||||
"CPU" \
|
||||
"CPU, HW" \
|
||||
ENCODER_TYPES_OMX_HINT
|
||||
|
||||
enum encoder_type_t {
|
||||
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
|
||||
ENCODER_TYPE_CPU,
|
||||
ENCODER_TYPE_HW,
|
||||
#ifdef OMX_ENCODER
|
||||
ENCODER_TYPE_OMX,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct encoder_t {
|
||||
enum encoder_type_t type;
|
||||
unsigned quality;
|
||||
struct encoder_runtime_t {
|
||||
enum encoder_type_t type;
|
||||
unsigned quality;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
#ifdef OMX_ENCODER
|
||||
bool omx_use_ijg;
|
||||
unsigned n_omxs;
|
||||
struct omx_encoder_t **omxs;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
struct encoder_t {
|
||||
enum encoder_type_t type;
|
||||
unsigned quality;
|
||||
|
||||
struct encoder_runtime_t *run;
|
||||
};
|
||||
|
||||
|
||||
struct encoder_t *encoder_init();
|
||||
void encoder_destroy(struct encoder_t *encoder);
|
||||
|
||||
enum encoder_type_t encoder_parse_type(const char *const str);
|
||||
enum encoder_type_t encoder_parse_type(const char *str);
|
||||
|
||||
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev);
|
||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev);
|
||||
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev,
|
||||
const unsigned worker_number, const unsigned buf_index);
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index);
|
||||
|
||||
119
src/http.c
119
src/http.c
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -23,8 +24,14 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/thread.h>
|
||||
#include <event2/http.h>
|
||||
@@ -40,6 +47,7 @@
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "encoder.h"
|
||||
#include "stream.h"
|
||||
#include "http.h"
|
||||
|
||||
@@ -51,7 +59,7 @@ static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
|
||||
static char *_http_get_param_uri(struct evkeyvalq *params, const char *key);
|
||||
|
||||
static void _http_callback_root(struct evhttp_request *request, void *arg);
|
||||
static void _http_callback_ping(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_state(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server);
|
||||
|
||||
static void _http_callback_stream(struct evhttp_request *request, void *v_server);
|
||||
@@ -59,7 +67,7 @@ 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, const bool stream_updated, const bool picture_updated);
|
||||
static void _http_queue_send_stream(struct http_server_t *server, bool stream_updated, bool picture_updated);
|
||||
|
||||
static bool _expose_new_picture(struct http_server_t *server);
|
||||
static bool _expose_blank_picture(struct http_server_t *server);
|
||||
@@ -91,7 +99,7 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
||||
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD);
|
||||
|
||||
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, "/state", _http_callback_state, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
|
||||
|
||||
@@ -105,9 +113,20 @@ void http_server_destroy(struct http_server_t *server) {
|
||||
}
|
||||
|
||||
evhttp_free(server->run->http);
|
||||
if (server->run->unix_fd) {
|
||||
close(server->run->unix_fd);
|
||||
}
|
||||
event_base_free(server->run->base);
|
||||
libevent_global_shutdown();
|
||||
|
||||
for (struct stream_client_t *client = server->run->stream_clients; client != NULL;) {
|
||||
struct stream_client_t *next = client->next;
|
||||
|
||||
free(client->key);
|
||||
free(client);
|
||||
client = next;
|
||||
}
|
||||
|
||||
free(server->run->exposed->picture.data);
|
||||
free(server->run->exposed);
|
||||
free(server->run);
|
||||
@@ -118,21 +137,67 @@ int http_server_listen(struct http_server_t *server) {
|
||||
struct timeval refresh_interval;
|
||||
|
||||
refresh_interval.tv_sec = 0;
|
||||
refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->soft_fps * 2);
|
||||
if (server->run->stream->dev->desired_fps > 0) {
|
||||
refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->desired_fps * 2);
|
||||
} else {
|
||||
refresh_interval.tv_usec = 16000; // ~60fps
|
||||
}
|
||||
assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
|
||||
assert(!event_add(server->run->refresh, &refresh_interval));
|
||||
|
||||
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) {
|
||||
LOG_PERROR("Can't listen HTTP on [%s]:%d", server->host, server->port)
|
||||
return -1;
|
||||
if (server->unix_path) {
|
||||
struct sockaddr_un unix_addr;
|
||||
int unix_fd_flags;
|
||||
|
||||
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
||||
|
||||
assert((server->run->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0)));
|
||||
assert((unix_fd_flags = fcntl(server->run->unix_fd, F_GETFL)) >= 0);
|
||||
unix_fd_flags |= O_NONBLOCK;
|
||||
assert(fcntl(server->run->unix_fd, F_SETFL, unix_fd_flags) >= 0);
|
||||
|
||||
strncpy(unix_addr.sun_path, server->unix_path, 107);
|
||||
unix_addr.sun_path[107] = '\0';
|
||||
unix_addr.sun_family = AF_UNIX;
|
||||
|
||||
if (server->unix_rm && unlink(server->unix_path) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_PERROR("Can't remove old UNIX socket '%s'", server->unix_path);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (bind(server->run->unix_fd, (struct sockaddr *)&unix_addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
LOG_PERROR("Can't bind HTTP to UNIX socket '%s'", server->unix_path);
|
||||
return -1;
|
||||
}
|
||||
if (server->unix_mode && chmod(server->unix_path, server->unix_mode) < 0) {
|
||||
LOG_PERROR("Can't set permissions %o to UNIX socket '%s'", server->unix_mode, server->unix_path);
|
||||
return -1;
|
||||
}
|
||||
if (listen(server->run->unix_fd, 128) < 0) {
|
||||
LOG_PERROR("Can't listen UNIX socket '%s'", server->unix_path);
|
||||
return -1;
|
||||
}
|
||||
if (evhttp_accept_socket(server->run->http, server->run->unix_fd) < 0) {
|
||||
LOG_PERROR("Can't evhttp_accept_socket() UNIX socket '%s'", server->unix_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path);
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
||||
if (evhttp_bind_socket(server->run->http, server->host, server->port) < 0) {
|
||||
LOG_PERROR("Can't bind HTTP on [%s]:%u", server->host, server->port)
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO("Listening HTTP on [%s]:%u", server->host, server->port);
|
||||
}
|
||||
|
||||
LOG_INFO("Listening HTTP on [%s]:%d", server->host, server->port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -189,37 +254,46 @@ static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
static void _http_callback_ping(struct evhttp_request *request, void *v_server) {
|
||||
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||
struct evbuffer *buf;
|
||||
|
||||
PROCESS_HEAD_REQUEST;
|
||||
|
||||
A_PTHREAD_M_LOCK(&server->run->stream->encoder->run->mutex);
|
||||
enum encoder_type_t encoder_run_type = server->run->stream->encoder->run->type;
|
||||
unsigned encoder_run_quality = server->run->stream->encoder->run->quality;
|
||||
A_PTHREAD_M_UNLOCK(&server->run->stream->encoder->run->mutex);
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"{\"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"quality\": %u, \"soft_fps\": %u, \"captured_fps\": %u},"
|
||||
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
|
||||
"{\"ok\": true, \"result\": {"
|
||||
" \"encoder\": {\"fallback\": %s, \"quality\": %u},"
|
||||
" \"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u},"
|
||||
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
|
||||
bool_to_string(server->run->stream->encoder->type != encoder_run_type),
|
||||
encoder_run_quality,
|
||||
(server->fake_width ? server->fake_width : server->run->exposed->width),
|
||||
(server->fake_height ? server->fake_height : server->run->exposed->height),
|
||||
bool_to_string(server->run->exposed->online),
|
||||
server->run->stream->encoder->quality,
|
||||
server->run->stream->dev->soft_fps,
|
||||
server->run->stream->dev->desired_fps,
|
||||
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, \"advance_headers\": %s, \"dual_final_frames\": %s}%s",
|
||||
"\"%s\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s, \"dual_final_frames\": %s}%s",
|
||||
client->id,
|
||||
client->fps,
|
||||
bool_to_string(client->extra_headers),
|
||||
bool_to_string(client->advance_headers),
|
||||
bool_to_string(client->dual_final_frames),
|
||||
(client->next ? ", " : "")
|
||||
));
|
||||
}
|
||||
assert(evbuffer_add_printf(buf, "}}}"));
|
||||
assert(evbuffer_add_printf(buf, "}}}}"));
|
||||
|
||||
ADD_HEADER("Content-Type", "application/json");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
@@ -239,7 +313,7 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
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("Cache-Control", "no-store, no-cache, must-revalidate, proxy-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");
|
||||
|
||||
@@ -252,6 +326,7 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
ADD_TIME_HEADER("X-Timestamp", get_now_real());
|
||||
|
||||
ADD_HEADER("X-UStreamer-Online", bool_to_string(EXPOSED(online)));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", EXPOSED(dropped));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(width));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Height", EXPOSED(height));
|
||||
ADD_TIME_HEADER("X-UStreamer-Grab-Time", EXPOSED(picture.grab_time));
|
||||
@@ -389,10 +464,10 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"HTTP/1.0 200 OK" RN
|
||||
"Access-Control-Allow-Origin: *" RN
|
||||
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||
"Pragma: no-cache" RN
|
||||
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
||||
"Set-Cookie: stream_client_key=%s; stream_client_id=%s; path=/; max-age=30" RN
|
||||
"Set-Cookie: stream_client=%s/%s; path=/; max-age=30" RN
|
||||
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
||||
RN
|
||||
"--" BOUNDARY RN,
|
||||
@@ -422,6 +497,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
if (client->extra_headers) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"X-UStreamer-Online: %s" RN
|
||||
"X-UStreamer-Dropped: %u" RN
|
||||
"X-UStreamer-Width: %u" RN
|
||||
"X-UStreamer-Height: %u" RN
|
||||
"X-UStreamer-Client-FPS: %u" RN
|
||||
@@ -434,6 +510,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
"X-UStreamer-Send-Time: %.06Lf" RN
|
||||
RN,
|
||||
bool_to_string(EXPOSED(online)),
|
||||
EXPOSED(dropped),
|
||||
EXPOSED(width),
|
||||
EXPOSED(height),
|
||||
client->fps,
|
||||
@@ -502,7 +579,7 @@ 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, const bool stream_updated, const bool picture_updated) {
|
||||
static void _http_queue_send_stream(struct http_server_t *server, bool stream_updated, bool picture_updated) {
|
||||
struct evhttp_connection *conn;
|
||||
struct bufferevent *buf_event;
|
||||
long long now;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -21,8 +22,11 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/http.h>
|
||||
#include <event2/util.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "stream.h"
|
||||
@@ -65,6 +69,7 @@ struct exposed_t {
|
||||
struct http_server_runtime_t {
|
||||
struct event_base *base;
|
||||
struct evhttp *http;
|
||||
evutil_socket_t unix_fd;
|
||||
struct event *refresh;
|
||||
struct stream_t *stream;
|
||||
struct exposed_t *exposed;
|
||||
@@ -76,6 +81,9 @@ struct http_server_runtime_t {
|
||||
struct http_server_t {
|
||||
char *host;
|
||||
unsigned port;
|
||||
char *unix_path;
|
||||
bool unix_rm;
|
||||
mode_t unix_mode;
|
||||
unsigned drop_same_frames;
|
||||
unsigned fake_width;
|
||||
unsigned fake_height;
|
||||
|
||||
58
src/hw/encoder.c
Normal file
58
src/hw/encoder.c
Normal file
@@ -0,0 +1,58 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../logging.h"
|
||||
#include "../xioctl.h"
|
||||
#include "../device.h"
|
||||
|
||||
|
||||
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality) {
|
||||
struct v4l2_jpegcompression comp;
|
||||
|
||||
MEMSET_ZERO(comp);
|
||||
|
||||
if (xioctl(dev->run->fd, VIDIOC_G_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Can't query HW JPEG compressor params and set quality (unsupported)");
|
||||
return -1;
|
||||
}
|
||||
comp.quality = quality;
|
||||
if (xioctl(dev->run->fd, VIDIOC_S_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Can't set HW JPEG compressor quality (unsopported)");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index) {
|
||||
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
|
||||
assert(0 && "Unsupported input format for HW JPEG compressor");
|
||||
}
|
||||
assert(dev->run->pictures[index].allocated >= dev->run->hw_buffers[index].length);
|
||||
memcpy(dev->run->pictures[index].data, dev->run->hw_buffers[index].start, dev->run->hw_buffers[index].length);
|
||||
dev->run->pictures[index].size = dev->run->hw_buffers[index].length;
|
||||
}
|
||||
29
src/hw/encoder.h
Normal file
29
src/hw/encoder.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../device.h"
|
||||
|
||||
|
||||
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality);
|
||||
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index);
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# This source file based on code of MJPG-Streamer. #
|
||||
@@ -48,24 +49,27 @@ struct _mjpg_destination_mgr {
|
||||
|
||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written);
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height);
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
|
||||
static void _jpeg_write_scanlines_yuyv(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height);
|
||||
unsigned width, unsigned height);
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
|
||||
static void _jpeg_write_scanlines_uyvy(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height);
|
||||
unsigned width, unsigned height);
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
unsigned width, unsigned height);
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg);
|
||||
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, const unsigned index, const unsigned quality) {
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned quality) {
|
||||
// This function based on compress_image_to_jpeg() from mjpg-streamer
|
||||
|
||||
struct jpeg_compress_struct jpeg;
|
||||
@@ -92,6 +96,7 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, co
|
||||
|
||||
# define WRITE_SCANLINES(_func) \
|
||||
_func(&jpeg, line_buffer, dev->run->hw_buffers[index].start, dev->run->width, dev->run->height)
|
||||
|
||||
switch (dev->run->format) {
|
||||
// https://www.fourcc.org/yuv.php
|
||||
case V4L2_PIX_FMT_YUYV: WRITE_SCANLINES(_jpeg_write_scanlines_yuyv); break;
|
||||
@@ -99,6 +104,7 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, co
|
||||
case V4L2_PIX_FMT_RGB565: WRITE_SCANLINES(_jpeg_write_scanlines_rgb565); break;
|
||||
default: assert(0 && "Unsupported input format for JPEG compressor");
|
||||
}
|
||||
|
||||
# undef WRITE_SCANLINES
|
||||
|
||||
// TODO: process jpeg errors:
|
||||
@@ -127,9 +133,12 @@ static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture,
|
||||
dest->written = written;
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
|
||||
#define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x)))
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height) {
|
||||
unsigned width, unsigned height) {
|
||||
|
||||
JSAMPROW scanlines[1];
|
||||
unsigned z = 0;
|
||||
@@ -146,9 +155,9 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
|
||||
int g = (y - (88 * u) - (183 * v)) >> 8;
|
||||
int b = (y + (454 * u)) >> 8;
|
||||
|
||||
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
|
||||
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
|
||||
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
|
||||
*(ptr++) = NORM_COMPONENT(r);
|
||||
*(ptr++) = NORM_COMPONENT(g);
|
||||
*(ptr++) = NORM_COMPONENT(b);
|
||||
|
||||
if (z++) {
|
||||
z = 0;
|
||||
@@ -161,9 +170,10 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
|
||||
}
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
|
||||
static void _jpeg_write_scanlines_uyvy(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height) {
|
||||
unsigned width, unsigned height) {
|
||||
|
||||
JSAMPROW scanlines[1];
|
||||
unsigned z = 0;
|
||||
@@ -180,9 +190,9 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
|
||||
int g = (y - (88 * u) - (183 * v)) >> 8;
|
||||
int b = (y + (454 * u)) >> 8;
|
||||
|
||||
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
|
||||
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
|
||||
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
|
||||
*(ptr++) = NORM_COMPONENT(r);
|
||||
*(ptr++) = NORM_COMPONENT(g);
|
||||
*(ptr++) = NORM_COMPONENT(b);
|
||||
|
||||
if (z++) {
|
||||
z = 0;
|
||||
@@ -195,9 +205,12 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
|
||||
}
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
|
||||
#undef NORM_COMPONENT
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height) {
|
||||
unsigned width, unsigned height) {
|
||||
|
||||
JSAMPROW scanlines[1];
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -24,4 +25,4 @@
|
||||
#include "../device.h"
|
||||
|
||||
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, const unsigned quality);
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned quality);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
|
||||
92
src/main.c
92
src/main.c
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -40,32 +41,31 @@
|
||||
#include "http.h"
|
||||
|
||||
|
||||
static const char _short_opts[] = "d:i:x:y:f:a:e:z:tn:w:q:c:s:p:r:h";
|
||||
static const char _short_opts[] = "d:i:x:y:m:a:f:z:tb:w:q:c:s:p:u:ro:e: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'},
|
||||
{"format", required_argument, NULL, 'm'},
|
||||
{"tv-standard", required_argument, NULL, 'a'},
|
||||
{"soft-fps", required_argument, NULL, 'm'},
|
||||
{"every-frame", required_argument, NULL, 'e'},
|
||||
{"desired-fps", required_argument, NULL, 'f'},
|
||||
{"min-frame-size", required_argument, NULL, 'z'},
|
||||
{"dv-timings", no_argument, NULL, 't'},
|
||||
{"buffers", required_argument, NULL, 'b'},
|
||||
{"workers", required_argument, NULL, 'w'},
|
||||
{"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-persistent", no_argument, NULL, 1001},
|
||||
{"device-error-delay", required_argument, NULL, 1002},
|
||||
|
||||
{"host", required_argument, NULL, 's'},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
{"drop-same-frames", required_argument, NULL, 'r'},
|
||||
{"unix", required_argument, NULL, 'u'},
|
||||
{"unix-rm", no_argument, NULL, 'r'},
|
||||
{"unix-mode", required_argument, NULL, 'o'},
|
||||
{"drop-same-frames", required_argument, NULL, 'e'},
|
||||
{"fake-width", required_argument, NULL, 2001},
|
||||
{"fake-height", required_argument, NULL, 2002},
|
||||
{"server-timeout", required_argument, NULL, 2003},
|
||||
@@ -100,52 +100,50 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
printf("------------------\n");
|
||||
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(" -x|--width <N> -- Initial image width. Default: %u.\n\n", dev->width);
|
||||
printf(" -y|--height <N> -- Initial image height. Default: %u.\n\n", dev->height);
|
||||
printf(" -m|--format <fmt> -- Image format.\n");
|
||||
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
|
||||
printf(" -a|--tv-standard <std> -- Force TV standard.\n");
|
||||
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
|
||||
printf(" -m|--soft-fps <N> -- Soft FPS limit; default: %u.\n\n", dev->soft_fps);
|
||||
printf(" -e|--every-frame <N> -- Drop all input frames except specified. Default: disabled.\n\n");
|
||||
printf(" -f|--desired-fps <N> -- Desired FPS. Default: maximum as possible.\n\n");
|
||||
printf(" -z|--min-frame-size <N> -- Drop frames smaller then this limit.\n");
|
||||
printf(" Useful if the device produces small-sized garbage frames.\n\n");
|
||||
printf(" -t|--dv-timings -- Enable DV timings queriyng and events processing.\n");
|
||||
printf(" Supports automatic resolution changing. Default: disabled.\n\n");
|
||||
printf(" -b|--buffers <N> -- The number of buffers to receive data from the device.\n");
|
||||
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|--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(" Default: %u (number of CPU cores + 1)\n\n", dev->n_buffers);
|
||||
printf(" -w|--workers <N> -- The number of compressing threads. Default: %u (== --buffers).\n\n", dev->n_workers);
|
||||
printf(" -q|--quality <N> -- Set quality of JPEG encoding from 1 to 100 (best). Default: %u.\n\n", encoder->quality);
|
||||
printf(" -c|--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-timeout <seconds> -- Timeout for device querying. Default: %u\n\n", dev->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(" after a timeout. Default: %u\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(" -s|--host <address> -- Listen on Hostname or IP. Default: %s\n\n", server->host);
|
||||
printf(" -p|--port <N> -- Bind to this TCP port. Default: %u\n\n", server->port);
|
||||
printf(" -u|--unix <path> -- Bind to UNIX domain socket. Default: disabled\n\n");
|
||||
printf(" -r|--unix-rm -- Try to remove old UNIX socket file before binding. Default: disabled\n\n");
|
||||
printf(" -o|--unix-mode <mode> -- Set UNIX socket file permissions (like 777). Default: disabled\n\n");
|
||||
printf(" -e|--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(" --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(" --fake-width <N> -- Override image width for /state. Default: disabled\n\n");
|
||||
printf(" --fake-height <N> -- Override image height for /state. Default: disabled.\n\n");
|
||||
printf(" --server-timeout <seconds> -- Timeout for client connections. Default: %u\n\n", server->timeout);
|
||||
printf("Misc options:\n");
|
||||
printf("-------------\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(" Default: %u.\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");
|
||||
}
|
||||
@@ -155,9 +153,9 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
{ _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'; min=%u; max=%u\n", _name, _tmp, _min, _max); return -1; } \
|
||||
{ errno = 0; char *_end = NULL; int _tmp = strtol(optarg, &_end, 0); \
|
||||
if (errno || *_end || _tmp < _min || _tmp > _max) \
|
||||
{ printf("Invalid value for '%s=%s'; min=%u; max=%u\n", _name, optarg, _min, _max); return -1; } \
|
||||
_dest = _tmp; break; }
|
||||
|
||||
# define OPT_PARSE(_dest, _func, _invalid, _name) \
|
||||
@@ -165,6 +163,12 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
{ printf("Unknown " _name ": %s\n", optarg); return -1; } \
|
||||
break; }
|
||||
|
||||
# define OPT_CHMOD(_dest, _name) \
|
||||
{ errno = 0; char *_end = NULL; int _tmp = strtol(optarg, &_end, 8); \
|
||||
if (errno || *_end) \
|
||||
{ printf("Invalid value for '%s=%s'\n", _name, optarg); return -1; } \
|
||||
_dest = _tmp; break; }
|
||||
|
||||
int index;
|
||||
int ch;
|
||||
|
||||
@@ -173,31 +177,30 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
switch (ch) {
|
||||
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);
|
||||
case 'x': OPT_UNSIGNED(dev->width, "--width", VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH);
|
||||
case 'y': OPT_UNSIGNED(dev->height, "--height", VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT);
|
||||
# pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
# pragma GCC diagnostic push
|
||||
case 'f': OPT_PARSE(dev->format, device_parse_format, FORMAT_UNKNOWN, "pixel format");
|
||||
case 'm': OPT_PARSE(dev->format, device_parse_format, FORMAT_UNKNOWN, "pixel format");
|
||||
# pragma GCC diagnostic pop
|
||||
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard");
|
||||
case 'm': OPT_UNSIGNED(dev->soft_fps, "--soft-fps", 1, 30);
|
||||
case 'e': OPT_UNSIGNED(dev->every_frame, "--every-frame", 1, 30);
|
||||
case 'f': OPT_UNSIGNED(dev->desired_fps, "--desired-fps", 0, 30);
|
||||
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192);
|
||||
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: 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': 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 'u': OPT_SET(server->unix_path, optarg);
|
||||
case 'r': OPT_SET(server->unix_rm, true);
|
||||
case 'o': OPT_CHMOD(server->unix_mode, "--unix-mode");
|
||||
case 'e': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
|
||||
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);
|
||||
@@ -213,6 +216,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
}
|
||||
}
|
||||
|
||||
# undef OPT_CHMOD
|
||||
# undef OPT_PARSE
|
||||
# undef OPT_UNSIGNED
|
||||
# undef OPT_SET
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -30,11 +31,11 @@
|
||||
#include "component.h"
|
||||
|
||||
|
||||
static int _component_wait_port_changed(OMX_HANDLETYPE *component, const OMX_U32 port, const OMX_BOOL enabled);
|
||||
static int _component_wait_state_changed(OMX_HANDLETYPE *component, const OMX_STATETYPE wanted);
|
||||
static int _component_wait_port_changed(OMX_HANDLETYPE *component, OMX_U32 port, OMX_BOOL enabled);
|
||||
static int _component_wait_state_changed(OMX_HANDLETYPE *component, OMX_STATETYPE wanted);
|
||||
|
||||
|
||||
int component_enable_port(OMX_HANDLETYPE *component, const OMX_U32 port) {
|
||||
int component_enable_port(OMX_HANDLETYPE *component, OMX_U32 port) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
LOG_DEBUG("Enabling OMX port %u ...", port);
|
||||
@@ -45,7 +46,7 @@ int component_enable_port(OMX_HANDLETYPE *component, const OMX_U32 port) {
|
||||
return _component_wait_port_changed(component, port, OMX_TRUE);
|
||||
}
|
||||
|
||||
int component_disable_port(OMX_HANDLETYPE *component, const OMX_U32 port) {
|
||||
int component_disable_port(OMX_HANDLETYPE *component, OMX_U32 port) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
LOG_DEBUG("Disabling OMX port %u ...", port);
|
||||
@@ -56,7 +57,7 @@ int component_disable_port(OMX_HANDLETYPE *component, const OMX_U32 port) {
|
||||
return _component_wait_port_changed(component, port, OMX_FALSE);
|
||||
}
|
||||
|
||||
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, const OMX_U32 port) {
|
||||
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
OMX_INIT_STRUCTURE(*portdef);
|
||||
@@ -81,8 +82,8 @@ int component_set_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYP
|
||||
return 0;
|
||||
}
|
||||
|
||||
int component_set_state(OMX_HANDLETYPE *component, const OMX_STATETYPE state) {
|
||||
const char *const state_str = omx_state_to_string(state);
|
||||
int component_set_state(OMX_HANDLETYPE *component, OMX_STATETYPE state) {
|
||||
const char *state_str = omx_state_to_string(state);
|
||||
OMX_ERRORTYPE error;
|
||||
int retries = 50;
|
||||
|
||||
@@ -107,7 +108,7 @@ int component_set_state(OMX_HANDLETYPE *component, const OMX_STATETYPE state) {
|
||||
}
|
||||
|
||||
|
||||
static int _component_wait_port_changed(OMX_HANDLETYPE *component, const OMX_U32 port, const OMX_BOOL enabled) {
|
||||
static int _component_wait_port_changed(OMX_HANDLETYPE *component, OMX_U32 port, OMX_BOOL enabled) {
|
||||
OMX_ERRORTYPE error;
|
||||
OMX_PARAM_PORTDEFINITIONTYPE portdef;
|
||||
int retries = 50;
|
||||
@@ -132,7 +133,7 @@ static int _component_wait_port_changed(OMX_HANDLETYPE *component, const OMX_U32
|
||||
return (portdef.bEnabled == enabled ? 0 : -1);
|
||||
}
|
||||
|
||||
static int _component_wait_state_changed(OMX_HANDLETYPE *component, const OMX_STATETYPE wanted) {
|
||||
static int _component_wait_state_changed(OMX_HANDLETYPE *component, OMX_STATETYPE wanted) {
|
||||
OMX_ERRORTYPE error;
|
||||
OMX_STATETYPE state;
|
||||
int retries = 50;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -38,10 +39,10 @@
|
||||
}
|
||||
|
||||
|
||||
int component_enable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
|
||||
int component_disable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
|
||||
int component_enable_port(OMX_HANDLETYPE *component, OMX_U32 port);
|
||||
int component_disable_port(OMX_HANDLETYPE *component, OMX_U32 port);
|
||||
|
||||
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, const OMX_U32 port);
|
||||
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, 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);
|
||||
int component_set_state(OMX_HANDLETYPE *component, OMX_STATETYPE state);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -51,17 +52,20 @@ static int _i_omx = 0;
|
||||
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, const unsigned quality, const bool use_ijg);
|
||||
static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality);
|
||||
static int _omx_encoder_clear_ports(struct omx_encoder_t *omx);
|
||||
|
||||
static OMX_ERRORTYPE _omx_event_handler(UNUSED OMX_HANDLETYPE encoder,
|
||||
static OMX_ERRORTYPE _omx_event_handler(
|
||||
UNUSED OMX_HANDLETYPE encoder,
|
||||
OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1,
|
||||
UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data);
|
||||
|
||||
static OMX_ERRORTYPE _omx_input_required_handler(UNUSED OMX_HANDLETYPE encoder,
|
||||
static OMX_ERRORTYPE _omx_input_required_handler(
|
||||
UNUSED OMX_HANDLETYPE encoder,
|
||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer);
|
||||
|
||||
static OMX_ERRORTYPE _omx_output_available_handler(UNUSED OMX_HANDLETYPE encoder,
|
||||
static OMX_ERRORTYPE _omx_output_available_handler(
|
||||
UNUSED OMX_HANDLETYPE encoder,
|
||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer);
|
||||
|
||||
|
||||
@@ -148,7 +152,7 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
|
||||
free(omx);
|
||||
}
|
||||
|
||||
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality, const bool use_ijg) {
|
||||
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality) {
|
||||
if (component_set_state(&omx->encoder, OMX_StateIdle) < 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -158,7 +162,7 @@ int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, co
|
||||
if (_omx_setup_input(omx, dev) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (_omx_setup_output(omx, quality, use_ijg) < 0) {
|
||||
if (_omx_setup_output(omx, quality) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (component_set_state(&omx->encoder, OMX_StateExecuting) < 0) {
|
||||
@@ -167,7 +171,7 @@ int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, co
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, const unsigned index) {
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index) {
|
||||
OMX_ERRORTYPE error;
|
||||
bool loaded = false;
|
||||
|
||||
@@ -329,7 +333,7 @@ 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, const unsigned quality, const bool use_ijg) {
|
||||
static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
|
||||
OMX_ERRORTYPE error;
|
||||
OMX_PARAM_PORTDEFINITIONTYPE portdef;
|
||||
|
||||
@@ -365,7 +369,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality,
|
||||
}
|
||||
}
|
||||
|
||||
if (use_ijg) {
|
||||
{
|
||||
OMX_PARAM_IJGSCALINGTYPE ijg;
|
||||
|
||||
OMX_INIT_STRUCTURE(ijg);
|
||||
@@ -433,7 +437,8 @@ static int _omx_encoder_clear_ports(struct omx_encoder_t *omx) {
|
||||
return retcode;
|
||||
}
|
||||
|
||||
static OMX_ERRORTYPE _omx_event_handler(UNUSED OMX_HANDLETYPE encoder,
|
||||
static OMX_ERRORTYPE _omx_event_handler(
|
||||
UNUSED OMX_HANDLETYPE encoder,
|
||||
OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1,
|
||||
UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data) {
|
||||
|
||||
@@ -449,7 +454,8 @@ static OMX_ERRORTYPE _omx_event_handler(UNUSED OMX_HANDLETYPE encoder,
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
static OMX_ERRORTYPE _omx_input_required_handler(UNUSED OMX_HANDLETYPE encoder,
|
||||
static OMX_ERRORTYPE _omx_input_required_handler(
|
||||
UNUSED OMX_HANDLETYPE encoder,
|
||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer) {
|
||||
|
||||
// Called by OMX when the encoder component requires
|
||||
@@ -462,7 +468,8 @@ static OMX_ERRORTYPE _omx_input_required_handler(UNUSED OMX_HANDLETYPE encoder,
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
static OMX_ERRORTYPE _omx_output_available_handler(UNUSED OMX_HANDLETYPE encoder,
|
||||
static OMX_ERRORTYPE _omx_output_available_handler(
|
||||
UNUSED OMX_HANDLETYPE encoder,
|
||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer) {
|
||||
|
||||
// Called by OMX when the encoder component has filled
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -51,5 +52,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_live(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);
|
||||
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality);
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -38,7 +39,7 @@
|
||||
assert(0 && _buf); \
|
||||
}
|
||||
|
||||
const char *omx_error_to_string(const OMX_ERRORTYPE error) {
|
||||
const char *omx_error_to_string(OMX_ERRORTYPE error) {
|
||||
switch (error) {
|
||||
CASE_TO_STRING(OMX_ErrorNone);
|
||||
CASE_TO_STRING(OMX_ErrorInsufficientResources);
|
||||
@@ -69,7 +70,7 @@ const char *omx_error_to_string(const OMX_ERRORTYPE error) {
|
||||
}
|
||||
}
|
||||
|
||||
const char *omx_state_to_string(const OMX_STATETYPE state) {
|
||||
const char *omx_state_to_string(OMX_STATETYPE state) {
|
||||
switch (state) {
|
||||
CASE_TO_STRING(OMX_StateLoaded);
|
||||
CASE_TO_STRING(OMX_StateIdle);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -41,5 +42,5 @@
|
||||
}
|
||||
|
||||
|
||||
const char *omx_error_to_string(const OMX_ERRORTYPE error);
|
||||
const char *omx_state_to_string(const OMX_STATETYPE state);
|
||||
const char *omx_error_to_string(OMX_ERRORTYPE error);
|
||||
const char *omx_state_to_string(OMX_STATETYPE state);
|
||||
|
||||
63
src/stream.c
63
src/stream.c
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -48,7 +49,7 @@ static void _stream_init_workers(struct device_t *dev, struct workers_pool_t *po
|
||||
static void *_stream_worker_thread(void *v_ctx);
|
||||
static void _stream_destroy_workers(struct device_t *dev, struct workers_pool_t *pool);
|
||||
|
||||
static int _stream_control(struct device_t *dev, const bool enable);
|
||||
static int _stream_control(struct device_t *dev, bool enable);
|
||||
static int _stream_grab_buffer(struct device_t *dev, struct v4l2_buffer *buf_info);
|
||||
static int _stream_release_buffer(struct device_t *dev, struct v4l2_buffer *buf_info);
|
||||
static int _stream_handle_event(struct device_t *dev);
|
||||
@@ -78,11 +79,11 @@ void stream_loop(struct stream_t *stream) {
|
||||
pool.workers_stop = &workers_stop;
|
||||
|
||||
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
|
||||
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
|
||||
|
||||
while (_stream_init_loop(stream->dev, &pool) == 0) {
|
||||
struct worker_t *oldest_worker = NULL;
|
||||
struct worker_t *last_worker = NULL;
|
||||
unsigned frames_count = 0;
|
||||
long double grab_after = 0;
|
||||
unsigned fluency_passed = 0;
|
||||
unsigned captured_fps_accum = 0;
|
||||
@@ -183,22 +184,13 @@ void stream_loop(struct stream_t *stream) {
|
||||
}
|
||||
stream->dev->run->pictures[buf_info.index].grab_time = now;
|
||||
|
||||
if (stream->dev->every_frame) {
|
||||
if (frames_count < stream->dev->every_frame - 1) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Workaround for broken, corrupted frames:
|
||||
// Under low light conditions corrupted frames may get captured.
|
||||
// The good thing is such frames are quite small compared to the regular pictures.
|
||||
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
|
||||
// corrupted frames are smaller.
|
||||
if (buf_info.bytesused < stream->dev->min_frame_size) {
|
||||
LOG_DEBUG("Dropping too small frame sized %d bytes, assuming it as broken", buf_info.bytesused);
|
||||
LOG_DEBUG("Dropping too small frame sized %u bytes, assuming it as broken", buf_info.bytesused);
|
||||
goto pass_frame;
|
||||
}
|
||||
|
||||
@@ -224,7 +216,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
LOG_VERBOSE("Fluency: delay=%.03Lf; grab_after=%.03Lf", fluency_delay, grab_after);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Grabbed a new frame to buffer %d", buf_info.index);
|
||||
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_info.index);
|
||||
pool.workers[free_worker_number].ctx.buf_info = buf_info;
|
||||
|
||||
if (!oldest_worker) {
|
||||
@@ -321,25 +313,28 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
|
||||
}
|
||||
|
||||
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool) {
|
||||
long double comp_time = 0;
|
||||
long double sum_comp_time = 0;
|
||||
long double avg_comp_time;
|
||||
long double min_delay;
|
||||
long double soft_delay;
|
||||
|
||||
for (unsigned number = 0; number < dev->n_workers; ++number) {
|
||||
A_PTHREAD_M_LOCK(&pool->workers[number].last_comp_time_mutex);
|
||||
if (pool->workers[number].last_comp_time > 0) {
|
||||
comp_time += pool->workers[number].last_comp_time;
|
||||
sum_comp_time += pool->workers[number].last_comp_time;
|
||||
}
|
||||
A_PTHREAD_M_UNLOCK(&pool->workers[number].last_comp_time_mutex);
|
||||
}
|
||||
comp_time = comp_time / dev->n_workers; // Среднее время работы воркеров
|
||||
avg_comp_time = sum_comp_time / dev->n_workers; // Среднее время работы воркеров
|
||||
|
||||
min_delay = comp_time / dev->n_workers; // Минимальное время работы размазывается на N воркеров
|
||||
soft_delay = ((long double)1) / dev->soft_fps; // Искусственное время задержки на основе желаемого FPS
|
||||
min_delay = avg_comp_time / dev->n_workers; // Среднее время работы размазывается на N воркеров
|
||||
|
||||
if (min_delay > 0) {
|
||||
if (dev->desired_fps > 0 && min_delay > 0) {
|
||||
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
|
||||
soft_delay = ((long double) 1) / dev->desired_fps - sum_comp_time;
|
||||
return (min_delay > soft_delay ? min_delay : soft_delay);
|
||||
}
|
||||
|
||||
return min_delay;
|
||||
}
|
||||
|
||||
@@ -349,7 +344,7 @@ 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_delay);
|
||||
LOG_INFO("Sleeping %u seconds before new stream init ...", dev->error_delay);
|
||||
sleep(dev->error_delay);
|
||||
} else {
|
||||
break;
|
||||
@@ -384,7 +379,7 @@ static int _stream_init(struct device_t *dev, struct workers_pool_t *pool) {
|
||||
}
|
||||
|
||||
static void _stream_init_workers(struct device_t *dev, struct workers_pool_t *pool) {
|
||||
LOG_INFO("Spawning %d workers ...", dev->n_workers);
|
||||
LOG_INFO("Spawning %u workers ...", dev->n_workers);
|
||||
|
||||
*pool->workers_stop = false;
|
||||
A_CALLOC(pool->workers, dev->n_workers);
|
||||
@@ -437,26 +432,30 @@ static void *_stream_worker_thread(void *v_ctx) {
|
||||
A_PTHREAD_C_WAIT_TRUE(*ctx->has_job, ctx->has_job_cond, ctx->has_job_mutex);
|
||||
A_PTHREAD_M_UNLOCK(ctx->has_job_mutex);
|
||||
|
||||
if (!*ctx->workers_stop) {
|
||||
LOG_DEBUG("Worker %u compressing JPEG from buffer %d ...", ctx->number, ctx->buf_index);
|
||||
# define PICTURE(_next) ctx->dev->run->pictures[ctx->buf_index]._next
|
||||
|
||||
if (!*ctx->workers_stop) {
|
||||
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", ctx->number, ctx->buf_index);
|
||||
|
||||
PICTURE(encode_begin_time) = get_now_monotonic();
|
||||
if (encoder_compress_buffer(ctx->encoder, ctx->dev, ctx->number, ctx->buf_index) < 0) {
|
||||
*ctx->job_failed = true;
|
||||
}
|
||||
PICTURE(encode_end_time) = get_now_monotonic();
|
||||
|
||||
if (_stream_release_buffer(ctx->dev, &ctx->buf_info) == 0) {
|
||||
*ctx->job_start_time = ctx->dev->run->pictures[ctx->buf_index].encode_begin_time;
|
||||
*ctx->job_start_time = PICTURE(encode_begin_time);
|
||||
*ctx->has_job = false;
|
||||
|
||||
long double last_comp_time = ctx->dev->run->pictures[ctx->buf_index].encode_end_time - *ctx->job_start_time;
|
||||
long double last_comp_time = PICTURE(encode_end_time) - *ctx->job_start_time;
|
||||
|
||||
A_PTHREAD_M_LOCK(ctx->last_comp_time_mutex);
|
||||
*ctx->last_comp_time = last_comp_time;
|
||||
A_PTHREAD_M_UNLOCK(ctx->last_comp_time_mutex);
|
||||
|
||||
LOG_VERBOSE(
|
||||
"Compressed JPEG size=%ld; time=%0.3Lf; worker=%u; buffer=%d",
|
||||
ctx->dev->run->pictures[ctx->buf_index].size, last_comp_time, ctx->number, ctx->buf_index
|
||||
"Compressed JPEG size=%ld; time=%0.3Lf; worker=%u; buffer=%u",
|
||||
PICTURE(size), last_comp_time, ctx->number, ctx->buf_index
|
||||
);
|
||||
} else {
|
||||
*ctx->job_failed = true;
|
||||
@@ -464,13 +463,15 @@ static void *_stream_worker_thread(void *v_ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
# undef PICTURE
|
||||
|
||||
A_PTHREAD_M_LOCK(ctx->free_workers_mutex);
|
||||
*ctx->free_workers += 1;
|
||||
A_PTHREAD_M_UNLOCK(ctx->free_workers_mutex);
|
||||
A_PTHREAD_C_SIGNAL(ctx->free_workers_cond);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Bye-bye (worker %d)", ctx->number);
|
||||
LOG_DEBUG("Bye-bye (worker %u)", ctx->number);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -499,7 +500,7 @@ static void _stream_destroy_workers(struct device_t *dev, struct workers_pool_t
|
||||
pool->workers = NULL;
|
||||
}
|
||||
|
||||
static int _stream_control(struct device_t *dev, const bool enable) {
|
||||
static int _stream_control(struct device_t *dev, bool enable) {
|
||||
if (enable != dev->run->capturing) {
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
@@ -528,9 +529,9 @@ static int _stream_grab_buffer(struct device_t *dev, struct v4l2_buffer *buf_inf
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Got a new frame in buffer index=%d; bytesused=%d", buf_info->index, buf_info->bytesused);
|
||||
LOG_DEBUG("Got a new frame in buffer index=%u; bytesused=%u", buf_info->index, buf_info->bytesused);
|
||||
if (buf_info->index >= dev->run->n_buffers) {
|
||||
LOG_ERROR("Got invalid buffer index=%d; nbuffers=%d", buf_info->index, dev->run->n_buffers);
|
||||
LOG_ERROR("Got invalid buffer index=%u; nbuffers=%u", buf_info->index, dev->run->n_buffers);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -47,18 +48,18 @@
|
||||
#define A_PTHREAD_C_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
|
||||
#define A_PTHREAD_C_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); }
|
||||
|
||||
|
||||
#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(_obj) memset(&(_obj), 0, sizeof(_obj))
|
||||
#define MEMSET_ZERO_PTR(_ptr) memset(_ptr, 0, sizeof(*(_ptr)))
|
||||
|
||||
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
|
||||
INLINE char *bool_to_string(const bool flag) {
|
||||
INLINE char *bool_to_string(bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
@@ -32,7 +33,7 @@
|
||||
#define XIOCTL_RETRIES 4
|
||||
|
||||
|
||||
INLINE int xioctl(const int fd, const int request, void *arg) {
|
||||
INLINE int xioctl(int fd, int request, void *arg) {
|
||||
int retries = XIOCTL_RETRIES;
|
||||
int retval = -1;
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ def main():
|
||||
text = "const char *%s = \" \\\n%s\n\";\n" % (name, text)
|
||||
text = textwrap.dedent("""
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
|
||||
@@ -42,7 +42,7 @@ def main():
|
||||
for ch in jpg_data:
|
||||
if len(rows[-1]) > 20:
|
||||
rows.append([])
|
||||
rows[-1].append(hex(ch))
|
||||
rows[-1].append("0x%.2X" % (ch))
|
||||
|
||||
text = ",\n\t".join(", ".join(row) for row in rows)
|
||||
text = "const unsigned char %s_JPG_DATA[] = {\n\t%s\n};\n" % (prefix, text)
|
||||
@@ -51,6 +51,7 @@ def main():
|
||||
text = "const unsigned %s_JPG_WIDTH = %d;\n" % (prefix, width) + text
|
||||
text = textwrap.dedent("""
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
|
||||
Reference in New Issue
Block a user