Compare commits

...

28 Commits
v0.44 ... v0.53

Author SHA1 Message Date
Devaev Maxim
6cdaceb561 Bump version: 0.52 → 0.53 2019-03-03 19:06:27 +03:00
Devaev Maxim
08aacdc9af show encoder type in /stat 2019-03-03 19:02:20 +03:00
Devaev Maxim
3db57cfa42 logging; removed some pragmas 2019-03-03 17:09:34 +03:00
Devaev Maxim
d8a774e358 Added ebuild by @chron0 2019-03-03 17:05:29 +03:00
Devaev Maxim
869d12759c fixed remarks 2019-03-03 15:49:06 +03:00
Devaev Maxim
bce3ae0f21 Bump version: 0.51 → 0.52 2019-03-03 15:39:41 +03:00
Devaev Maxim
1541921070 supported hw mjpeg format 2019-03-03 15:39:32 +03:00
Devaev Maxim
cc25abcc00 Bump version: 0.50 → 0.51 2019-03-03 05:23:51 +03:00
Devaev Maxim
e80ee2f574 refactoring 2019-03-03 04:25:32 +03:00
Devaev Maxim
56a95c7f17 ARRAY_LEN() 2019-03-02 11:30:10 +03:00
Devaev Maxim
667e3610b2 encoder runtime 2019-03-02 11:29:57 +03:00
Devaev Maxim
142c8c84ac Bump version: 0.49 → 0.50 2019-03-01 11:36:15 +03:00
Devaev Maxim
383075d323 refactoring 2019-03-01 10:44:50 +03:00
Devaev Maxim
e2922aa820 fixed short options 2019-03-01 07:48:03 +03:00
Devaev Maxim
dc9667cf0c report about using pixelformat 2019-03-01 07:07:16 +03:00
Devaev Maxim
bbbfda0b5c Bump version: 0.48 → 0.49 2019-02-22 02:55:13 +03:00
Devaev Maxim
aa3f079ee9 fixed memory leak in http_server_destroy() 2019-02-22 02:54:03 +03:00
Devaev Maxim
59bd4e8dd2 Bump version: 0.47 → 0.48 2019-02-21 08:02:31 +03:00
Devaev Maxim
ce6184b8cd refactoring 2019-02-21 08:02:21 +03:00
Devaev Maxim
9ca511d29d Bump version: 0.46 → 0.47 2019-02-19 04:38:14 +03:00
Devaev Maxim
74de28c64a refactoring 2019-02-19 04:33:35 +03:00
Maxim Devaev
24060e8068 Update README.md 2019-01-28 17:33:57 +03:00
Maxim Devaev
3a03d48855 Update README.ru.md 2019-01-22 03:32:52 +03:00
Maxim Devaev
935a9125d6 Update README.md 2019-01-22 03:32:17 +03:00
Devaev Maxim
d4ea97ef2c Bump version: 0.45 → 0.46 2018-12-18 03:17:44 +03:00
Devaev Maxim
867aa4e52a proxy-revalidate 2018-12-18 03:17:34 +03:00
Devaev Maxim
bc2bb444dc Bump version: 0.44 → 0.45 2018-12-18 01:37:50 +03:00
Devaev Maxim
19796f3b64 X-UStreamer-Dropped 2018-12-18 01:37:39 +03:00
33 changed files with 962 additions and 722 deletions

View File

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

View File

@@ -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

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=0.44
pkgver=0.53
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pi-kvm/ustreamer"

View File

@@ -11,22 +11,19 @@
| Multithreaded JPEG encoding | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Behavior when the device<br>is disconnected while streaming | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Stops the broadcast <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>2</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save traffic | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Streaming via UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Supported input devices | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) YUYV, UYVY,<br>RGB565, MJPG |
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
| Option to serve files<br>with a built-in HTTP server, auth settings | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No <sup>4</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
| Option to serve files<br>with a built-in HTTP server, auth settings | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
Footnotes:
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
* ```2``` This feature allows to cut down outgoing traffic several-fold when broadcasting HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to broadcast the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
* ```3``` As µStreamer was made mainly to be used with screencast hardware it supports video formats they usually need. MJPG input means that the screencast device can compress images to JPEG and feed it to the software, which allows to cut down CPU usage and avoid software image encoding. This video format is supported by most webcams, but I've never seen it supported by screencast hardware: neither [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), nor [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC) offer such support. It's not hard to add hardware MJPG sources support, it's just not done yet.
* ```4``` ...and there'll never be. µStreamer is designed UNIX-way, so if you need a small website with your broadcast, install NGINX.
* ```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
@@ -60,7 +57,6 @@ $ ./ustreamer \
--format=uyvy \ # Device input format
--encoder=omx \ # Hardware encoding with OpenMAX
--dv-timings \ # Use DV-timings
--quality=20 \ # OpenMAX has a non-linear quality scale
--drop-same-frames=30 # Save that traffic
```

View File

@@ -11,22 +11,19 @@
| Многопоточное кодирование JPEG | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении устройства<br>от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Прерывает трансляцию <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>2</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Стрим через UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поддерживаемые входные форматы устройств | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) YUYV, UYVY,<br>RGB565, MJPG |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>4</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
Сносочки:
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
* ```3``` Поскольку µStreamer писался в первую очередь для устройств видеозахвата, в нем реализованы только те форматы, которые для них были нужны. MJPG в контексте входных данных означает, что устройство умеет самостоятельно сжимать картинку в JPEG и отдавать ее программе, что позволяет значительно снизить загрузку процессора и избавить его от необходимости кодировать картинку софтом. Этот формат поддерживается большинством веб-камер, но не поддерживается ни одним из встреченных мной устройств видеозахвата; его не умеет ни [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), ни [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC). Нет никаких технических сложностей добавить поддержку аппаратного MJPG источника, но у меня просто пока не дошли до этого руки.
* ```4``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
* ```3``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
-----
# TL;DR
@@ -60,7 +57,6 @@ $ ./ustreamer \
--format=uyvy \ # Настройка входного формата устройства
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
--dv-timings \ # Включение DV-таймингов
--quality=20 \ # У OpenMAX нелинейная шкала качества
--drop-same-frames=30 # Экономим трафик
```

View File

@@ -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.44"
#define VERSION "0.53"

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #

View File

@@ -1,4 +1,5 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
@@ -54,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},
};
@@ -64,11 +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, const unsigned width, const unsigned height);
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() {
@@ -97,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;
}
@@ -106,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;
}
@@ -285,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;
@@ -296,8 +300,6 @@ 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=%ux%u",
_format_to_string_auto(format_str, 8, dev->format),
@@ -318,13 +320,12 @@ static int _device_open_format(struct device_t *dev) {
// 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;
}
@@ -422,7 +425,7 @@ static void _device_open_alloc_picbufs(struct device_t *dev) {
}
}
static int _device_apply_resolution(struct device_t *dev, const unsigned width, const unsigned height) {
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height) {
// Тут VIDEO_MIN_* не используются из-за странностей минимального разрешения при отсутствии сигнала
// у некоторых устройств, например Auvidea B101
if (
@@ -438,12 +441,12 @@ static int _device_apply_resolution(struct device_t *dev, const unsigned width,
return 0;
}
static const char *_format_to_string_auto(char *buf, const size_t size, const unsigned format) {
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';
@@ -455,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;
}
@@ -465,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;
}

View File

@@ -1,4 +1,5 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
@@ -38,7 +39,7 @@
#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 {
@@ -61,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;
@@ -92,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);

View File

@@ -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);
if (encoder->type != ENCODER_TYPE_CPU) {
LOG_DEBUG("Initializing encoder ...");
}
// XXX: Тут нет гонки, потому что encoder_prepare() запускается еще до существования других потоков
encoder->run->type = encoder->type;
encoder->run->quality = 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;
}
@@ -120,16 +136,45 @@ enum encoder_type_t encoder_parse_type(const char *const str) {
return ENCODER_TYPE_UNKNOWN;
}
#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);
const char *encoder_type_to_string(enum encoder_type_t type) {
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
if (_ENCODER_TYPES[index].type == type) {
return _ENCODER_TYPES[index].name;
}
}
return _ENCODER_TYPES[0].name;
}
#pragma GCC diagnostic pop
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
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);
LOG_INFO("Using JPEG quality: HW default");
}
}
# 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) < 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;
}
}
@@ -138,29 +183,29 @@ void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
return;
# 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;
# pragma GCC diagnostic pop
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 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);
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
@@ -169,9 +214,12 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev,
# 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
}

View File

@@ -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,20 +36,23 @@
#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
unsigned n_omxs;
struct omx_encoder_t **omxs;
@@ -54,13 +60,21 @@ struct encoder_t {
};
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);
const char *encoder_type_to_string(enum encoder_type_t type);
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);

View File

@@ -1,4 +1,5 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
@@ -46,6 +47,7 @@
#include "tools.h"
#include "logging.h"
#include "encoder.h"
#include "stream.h"
#include "http.h"
@@ -65,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);
@@ -117,6 +119,14 @@ void http_server_destroy(struct http_server_t *server) {
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);
@@ -247,24 +257,40 @@ static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg
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;
enum encoder_type_t encoder_run_type;
unsigned encoder_run_quality;
PROCESS_HEAD_REQUEST;
# define ENCODER(_next) server->run->stream->encoder->_next
A_PTHREAD_M_LOCK(&ENCODER(run->mutex));
encoder_run_type = ENCODER(run->type);
encoder_run_quality = ENCODER(run->quality);
A_PTHREAD_M_UNLOCK(&ENCODER(run->mutex));
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf,
"{\"ok\": true, \"result\":"
" {\"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
" \"online\": %s, \"quality\": %u, \"desired_fps\": %u, \"captured_fps\": %u},"
"{\"ok\": true, \"result\": {"
" \"encoder\": {\"type\": \"%s\", \"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\": {",
encoder_type_to_string(encoder_run_type),
bool_to_string(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->desired_fps,
server->run->exposed->captured_fps,
server->run->exposed->queued_fps,
server->run->stream_clients_count
));
# undef ENCODER
for (struct stream_client_t * client = server->run->stream_clients; client != NULL; client = client->next) {
assert(evbuffer_add_printf(buf,
"\"%s\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s, \"dual_final_frames\": %s}%s",
@@ -276,6 +302,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
(client->next ? ", " : "")
));
}
assert(evbuffer_add_printf(buf, "}}}}"));
ADD_HEADER("Content-Type", "application/json");
@@ -296,7 +323,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");
@@ -309,6 +336,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));
@@ -446,7 +474,7 @@ 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=%s/%s; path=/; max-age=30" RN
@@ -479,6 +507,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
@@ -491,6 +520,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,
@@ -559,7 +589,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;

View File

@@ -1,4 +1,5 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #

58
src/hw/encoder.c Normal file
View 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
View 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);

View File

@@ -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];

View File

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

View File

@@ -1,4 +1,5 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #

View File

@@ -1,4 +1,5 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
@@ -40,7 +41,7 @@
#include "http.h"
static const char _short_opts[] = "d:i:x:y:f:a:z:tn:w:q:c:s:p:u:ro:e: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'},

View File

@@ -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;

View File

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

View File

@@ -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);
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) {
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;
}
@@ -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) {
static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
OMX_ERRORTYPE error;
OMX_PARAM_PORTDEFINITIONTYPE portdef;
@@ -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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #

View File

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

View File

@@ -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;

View File

@@ -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> #

View File

@@ -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> #

27
ustreamer.ebuild Normal file
View File

@@ -0,0 +1,27 @@
# Copyright 2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI=7
inherit git-r3
DESCRIPTION="µStreamer - Lightweight and fast MJPG-HTTP streamer"
HOMEPAGE="https://github.com/pi-kvm/ustreamer"
EGIT_REPO_URI="https://github.com/pi-kvm/ustreamer.git"
LICENSE="GPL-3"
SLOT="0"
KEYWORDS="~amd64"
IUSE=""
DEPEND="
>=dev-libs/libevent-2.1.8
>=media-libs/libjpeg-turbo-1.5.3
>=sys-apps/util-linux-2.33
"
RDEPEND="${DEPEND}"
BDEPEND=""
src_install() {
dobin ustreamer
}