Compare commits

...

36 Commits
v0.51 ... v0.55

Author SHA1 Message Date
Devaev Maxim
fd7b5e9c59 Bump version: 0.54 → 0.55 2019-03-14 10:34:06 +03:00
Devaev Maxim
966f226dde refactoring 2019-03-13 22:10:36 +03:00
Devaev Maxim
8a2a0474b2 big controls refactoring 2019-03-12 23:46:19 +03:00
Devaev Maxim
22be6443ef Makefile: optional CC 2019-03-12 13:23:25 +03:00
Devaev Maxim
da1348d25f fixed potential placeholder error with evbuffer_add_printf() 2019-03-12 13:23:05 +03:00
Devaev Maxim
02604d4ef3 refactoring 2019-03-06 06:57:59 +03:00
Devaev Maxim
351da0dce0 refactoring 2019-03-06 05:18:26 +03:00
Devaev Maxim
01e21e419d refactoring 2019-03-06 03:07:02 +03:00
Devaev Maxim
a5bca4cca3 huffman 2019-03-06 01:33:20 +03:00
Devaev Maxim
f439f37526 refactoring 2019-03-05 14:23:11 +03:00
Devaev Maxim
502aa3a0cb refactoring 2019-03-05 13:14:17 +03:00
Devaev Maxim
84a7bcc2a4 help fix 2019-03-05 12:48:11 +03:00
Devaev Maxim
388198a504 show query result of image settings 2019-03-05 12:43:13 +03:00
Devaev Maxim
69d6fda56b refactoring 2019-03-05 12:42:03 +03:00
Devaev Maxim
9a38078c72 Added backlight_compensation, white_balance and gain 2019-03-05 10:47:43 +03:00
Devaev Maxim
af2dd0d9a3 refactoring 2019-03-05 10:09:07 +03:00
Devaev Maxim
09fc14d63d First implementation of image settings 2019-03-05 09:54:15 +03:00
Devaev Maxim
e683d1d370 Makefile: OBJECTS after SOURCES 2019-03-05 02:08:32 +03:00
Devaev Maxim
24fed54cae OMX_ENCODER -> WITH_OMX_ENCODER 2019-03-05 01:15:57 +03:00
Devaev Maxim
b76b34ad65 removed fallback field from /state 2019-03-05 00:30:54 +03:00
Devaev Maxim
ef65812ec7 removed UNKNOWN from STANDARDS_STR 2019-03-05 00:28:43 +03:00
Devaev Maxim
4c8351c1bc Bump version: 0.53 → 0.54 2019-03-04 15:47:02 +03:00
Devaev Maxim
4492cc1efe refactoring 2019-03-04 15:42:47 +03:00
Devaev Maxim
b2ca0ea998 encoders subdir 2019-03-04 15:29:11 +03:00
Devaev Maxim
924665c1a3 refactoring 2019-03-04 15:16:36 +03:00
Devaev Maxim
5d49018bb2 refactoring 2019-03-04 15:07:16 +03:00
Devaev Maxim
3ae8818b3d consistent using fourcc 2019-03-04 15:06:29 +03:00
Devaev Maxim
2bb1f71c9c minor omx fixes 2019-03-04 14:25:57 +03:00
Devaev Maxim
537e55afc6 RGB24 2019-03-04 11:34:25 +03:00
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
29 changed files with 842 additions and 317 deletions

View File

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

View File

@@ -2,23 +2,23 @@ DESTDIR ?=
PREFIX ?= /usr/local
CFLAGS ?= -O3
LDFLAGS ?=
CC ?= gcc
# =====
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)
OBJECTS = $(SOURCES:.c=.o)
SOURCES = $(shell ls src/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
PROG = ustreamer
ifeq ($(shell ls -d /opt/vc/include 2>/dev/null), /opt/vc/include)
SOURCES += $(shell ls src/omx/*.c)
SOURCES += $(shell ls src/encoders/omx/*.c)
LIBS += -lbcm_host -lvcos -lopenmaxil -L/opt/vc/lib
override CFLAGS += -DOMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
override CFLAGS += -DWITH_OMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
endif
OBJECTS = $(SOURCES:.c=.o)
# =====
all: $(SOURCES) $(PROG)
@@ -59,5 +59,5 @@ push:
clean-all: clean
clean:
rm -f src/*.o src/{jpeg,omx}/*.o vgcore.* *.sock $(PROG)
rm -f src/*.o src/encoders/*/*.o vgcore.* *.sock $(PROG)
rm -rf pkg src/$(PROG)-* src/v*.tar.gz v*.tar.gz $(PROG)-*.pkg.tar.xz

View File

@@ -3,14 +3,14 @@
pkgname=ustreamer
pkgver=0.51
pkgver=0.55
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pi-kvm/ustreamer"
license=(GPL)
arch=(i686 x86_64 armv6h armv7h)
depends=(libjpeg libevent libutil-linux)
# optional: raspberrypi-firmware for OMX JPEG compressor
# optional: raspberrypi-firmware for OMX JPEG encoder
makedepends=(gcc make)
source=("$url/archive/v$pkgver.tar.gz")
md5sums=(SKIP)

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 formats | ![#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

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

View File

@@ -22,4 +22,4 @@
#pragma once
#define VERSION "0.51"
#define VERSION "0.55"

View File

@@ -20,12 +20,10 @@
*****************************************************************************/
const unsigned BLANK_JPG_WIDTH = 640;
const unsigned BLANK_JPG_HEIGHT = 480;
const unsigned BLANK_JPEG_WIDTH = 640;
const unsigned BLANK_JPEG_HEIGHT = 480;
const unsigned long BLANK_JPG_SIZE = 13845;
const unsigned char BLANK_JPG_DATA[] = {
const unsigned char BLANK_JPEG_DATA[] = {
0xFF, 0xD8, 0xFF, 0xE1, 0x09, 0x50, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62, 0x65,
0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x78, 0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x00, 0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B,
0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D, 0x22, 0xEF, 0xBB, 0xBF, 0x22, 0x20, 0x69, 0x64, 0x3D, 0x22, 0x57, 0x35,

View File

@@ -25,7 +25,7 @@
#include "../config.h"
const char *HTML_INDEX_PAGE = " \
const char HTML_INDEX_PAGE[] = " \
<!DOCTYPE html> \
\
<html> \

View File

@@ -55,6 +55,9 @@ static const struct {
{"YUYV", V4L2_PIX_FMT_YUYV},
{"UYVY", V4L2_PIX_FMT_UYVY},
{"RGB565", V4L2_PIX_FMT_RGB565},
{"RGB24", V4L2_PIX_FMT_RGB24},
{"JPEG", V4L2_PIX_FMT_MJPEG},
{"JPEG", V4L2_PIX_FMT_JPEG},
};
@@ -62,20 +65,27 @@ static int _device_open_check_cap(struct device_t *dev);
static int _device_open_dv_timings(struct device_t *dev);
static int _device_apply_dv_timings(struct device_t *dev);
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 void _device_open_alloc_picbufs(struct device_t *dev);
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height);
static void _device_apply_controls(struct device_t *dev);
static int _device_check_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet);
static void _device_set_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet);
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 *_format_to_string_fourcc(char *buf, size_t size, unsigned format);
static const char *_format_to_string_nullable(unsigned format);
static const char *_format_to_string_supported(unsigned format);
static const char *_standard_to_string(v4l2_std_id standard);
struct device_t *device_init() {
struct controls_t *ctl;
struct device_runtime_t *run;
struct device_t *dev;
A_CALLOC(ctl, 1);
A_CALLOC(run, 1);
run->fd = -1;
@@ -89,12 +99,14 @@ struct device_t *device_init() {
dev->n_workers = dev->n_buffers;
dev->timeout = 1;
dev->error_delay = 1;
dev->ctl = ctl;
dev->run = run;
return dev;
}
void device_destroy(struct device_t *dev) {
free(dev->run);
free(dev->ctl);
free(dev);
}
@@ -139,6 +151,7 @@ int device_open(struct device_t *dev) {
goto error;
}
_device_open_alloc_picbufs(dev);
_device_apply_controls(dev);
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
return 0;
@@ -162,11 +175,15 @@ void device_close(struct device_t *dev) {
if (dev->run->hw_buffers) {
LOG_DEBUG("Unmapping HW buffers ...");
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) {
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
if (HW_BUFFER(size) > 0 && HW_BUFFER(data) != MAP_FAILED) {
if (munmap(HW_BUFFER(data), HW_BUFFER(size)) < 0) {
LOG_PERROR("Can't unmap device buffer %u", index);
}
}
# undef HW_BUFFER
}
dev->run->n_buffers = 0;
free(dev->run->hw_buffers);
@@ -254,12 +271,10 @@ 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=%ux%u; pixclk=%llu",
LOG_INFO("Got new DV timings: resolution=%ux%u; pixclk=%llu",
dv_timings.bt.width,
dv_timings.bt.height,
dv_timings.bt.pixelclock
);
dv_timings.bt.pixelclock);
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv_timings) < 0) {
@@ -286,7 +301,6 @@ 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;
@@ -298,12 +312,10 @@ 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) {
LOG_PERROR(
"Unable to set format=%s; resolution=%ux%u",
_format_to_string_auto(format_str, 8, dev->format),
LOG_PERROR("Unable to set pixelformat=%s; resolution=%ux%u",
_format_to_string_supported(dev->format),
dev->run->width,
dev->run->height
);
dev->run->height);
return -1;
}
@@ -321,26 +333,21 @@ static int _device_open_format(struct device_t *dev) {
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_str, 8, dev->format),
_format_to_string_auto(format_obtained_str, 8, fmt.fmt.pix.pixelformat)
);
LOG_ERROR("Could not obtain the requested pixelformat=%s; driver gave us %s",
_format_to_string_supported(dev->format),
_format_to_string_supported(fmt.fmt.pix.pixelformat));
if ((format_str_nullable = (char *)_format_to_string_null(fmt.fmt.pix.pixelformat)) != NULL) {
LOG_INFO(
"Falling back to %s mode (consider using '--format=%s' option)",
format_str_nullable,
format_str_nullable
);
if ((format_str_nullable = (char *)_format_to_string_nullable(fmt.fmt.pix.pixelformat)) != NULL) {
LOG_INFO("Falling back to pixelformat=%s", format_str_nullable);
} else {
LOG_ERROR("Unsupported pixel format");
LOG_ERROR("Unsupported pixelformat=%s (fourcc)",
_format_to_string_fourcc(format_obtained_str, 8, fmt.fmt.pix.pixelformat));
return -1;
}
}
dev->run->format = fmt.fmt.pix.pixelformat;
LOG_INFO("Using pixelformat: %s", _format_to_string_auto(format_str, 8, dev->run->format));
LOG_INFO("Using pixelformat: %s", _format_to_string_supported(dev->run->format));
return 0;
}
@@ -382,13 +389,17 @@ static int _device_open_mmap(struct device_t *dev) {
return -1;
}
# define HW_BUFFER(_next) dev->run->hw_buffers[dev->run->n_buffers]._next
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) {
HW_BUFFER(data) = mmap(NULL, buf_info.length, PROT_READ|PROT_WRITE, MAP_SHARED, dev->run->fd, buf_info.m.offset);
if (HW_BUFFER(data) == MAP_FAILED) {
LOG_PERROR("Can't map device buffer %u", dev->run->n_buffers);
return -1;
}
HW_BUFFER(size) = buf_info.length;
# undef HW_BUFFER
}
return 0;
}
@@ -417,7 +428,7 @@ 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 %u sized %lu bytes... ", index, dev->run->max_picture_size);
LOG_DEBUG("Allocating picture buffer %u sized %zu 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;
}
@@ -439,7 +450,80 @@ static int _device_apply_resolution(struct device_t *dev, unsigned width, unsign
return 0;
}
static const char *_format_to_string_auto(char *buf, size_t size, unsigned format) {
static void _device_apply_controls(struct device_t *dev) {
# define SET_CID(_cid, _dest, _value, _quiet) { \
if (_device_check_control(dev, #_dest, _cid, _value, _quiet) == 0) { \
_device_set_control(dev, #_dest, _cid, _value, _quiet); \
} \
}
# define SET_CID_MANUAL(_cid, _dest) { \
if (dev->ctl->_dest.value_set) { \
SET_CID(_cid, _dest, dev->ctl->_dest.value, false); \
} \
}
# define SET_CID_AUTO(_cid_auto, _cid_manual, _dest) { \
if (dev->ctl->_dest.value_set || dev->ctl->_dest.auto_set) { \
SET_CID(_cid_auto, _dest##_auto, dev->ctl->_dest.auto_set, dev->ctl->_dest.value_set); \
SET_CID_MANUAL(_cid_manual, _dest); \
} \
}
SET_CID_AUTO (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness);
SET_CID_MANUAL ( V4L2_CID_CONTRAST, contrast);
SET_CID_MANUAL ( V4L2_CID_SATURATION, saturation);
SET_CID_AUTO (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue);
SET_CID_MANUAL ( V4L2_CID_GAMMA, gamma);
SET_CID_MANUAL ( V4L2_CID_SHARPNESS, sharpness);
SET_CID_MANUAL ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
SET_CID_AUTO (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
SET_CID_AUTO (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
# undef SET_CID_AUTO
# undef SET_CID_MANUAL
# undef SET_CID
}
static int _device_check_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet) {
struct v4l2_queryctrl query;
MEMSET_ZERO(query);
query.id = cid;
if (xioctl(dev->run->fd, VIDIOC_QUERYCTRL, &query) < 0 || query.flags & V4L2_CTRL_FLAG_DISABLED) {
if (!quiet) {
LOG_ERROR("Changing control %s is unsupported", name);
}
return -1;
}
if (value < query.minimum || value > query.maximum || value % query.step != 0) {
if (!quiet) {
LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
value, name, query.minimum, query.maximum, query.default_value, query.step);
}
return -2;
}
return 0;
}
static void _device_set_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet) {
struct v4l2_control ctl;
MEMSET_ZERO(ctl);
ctl.id = cid;
ctl.value = value;
if (xioctl(dev->run->fd, VIDIOC_S_CTRL, &ctl) < 0) {
if (!quiet) {
LOG_PERROR("Can't set control %s", name);
}
} else if (!quiet) {
LOG_INFO("Using control %s: %d", name, ctl.value);
}
}
static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned format) {
assert(size >= 8);
buf[0] = format & 0x7F;
buf[1] = (format >> 8) & 0x7F;
@@ -456,7 +540,7 @@ static const char *_format_to_string_auto(char *buf, size_t size, unsigned forma
return buf;
}
static const char *_format_to_string_null(unsigned format) {
static const char *_format_to_string_nullable(unsigned format) {
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
if (format == _FORMATS[index].format) {
return _FORMATS[index].name;
@@ -465,6 +549,11 @@ static const char *_format_to_string_null(unsigned format) {
return NULL;
}
static const char *_format_to_string_supported(unsigned format) {
const char *format_str = _format_to_string_nullable(format);
return (format_str == NULL ? "unsupported" : format_str);
}
static const char *_standard_to_string(v4l2_std_id standard) {
for (unsigned index = 0; index < ARRAY_LEN(_STANDARDS); ++index) {
if (standard == _STANDARDS[index].standard) {

View File

@@ -36,21 +36,21 @@
#define VIDEO_MAX_HEIGHT 1200
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define STANDARDS_STR "UNKNOWN, PAL, NTSC, SECAM"
#define STANDARDS_STR "PAL, NTSC, SECAM"
#define FORMAT_UNKNOWN -1
#define FORMATS_STR "YUYV, UYVY, RGB565"
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, JPEG"
struct hw_buffer_t {
void *start;
size_t length;
unsigned char *data;
size_t size;
};
struct picture_t {
unsigned char *data;
unsigned long size;
unsigned long allocated;
size_t size;
size_t allocated;
long double grab_time;
long double encode_begin_time;
long double encode_end_time;
@@ -62,13 +62,31 @@ 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;
size_t max_picture_size;
bool capturing;
};
struct control_t {
int value;
bool value_set;
bool auto_set;
};
struct controls_t {
struct control_t brightness;
struct control_t contrast;
struct control_t saturation;
struct control_t hue;
struct control_t gamma;
struct control_t sharpness;
struct control_t backlight_compensation;
struct control_t white_balance;
struct control_t gain;
};
struct device_t {
char *path;
unsigned input;
@@ -80,11 +98,13 @@ struct device_t {
unsigned n_buffers;
unsigned n_workers;
unsigned desired_fps;
unsigned min_frame_size;
size_t min_frame_size;
bool persistent;
unsigned timeout;
unsigned error_delay;
struct controls_t *ctl;
struct device_runtime_t *run;
sig_atomic_t volatile stop;
};

View File

@@ -23,15 +23,18 @@
#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 "encoders/cpu/encoder.h"
#include "encoders/hw/encoder.h"
#ifdef OMX_ENCODER
# include "omx/encoder.h"
#ifdef WITH_OMX_ENCODER
# include "encoders/omx/encoder.h"
#endif
@@ -40,7 +43,8 @@ static const struct {
const enum encoder_type_t type;
} _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU},
# ifdef OMX_ENCODER
{"HW", ENCODER_TYPE_HW},
# ifdef WITH_OMX_ENCODER
{"OMX", ENCODER_TYPE_OMX},
# endif
};
@@ -52,10 +56,12 @@ struct encoder_t *encoder_init() {
A_CALLOC(run, 1);
run->type = ENCODER_TYPE_CPU;
run->quality = 80;
A_PTHREAD_M_INIT(&run->mutex);
A_CALLOC(encoder, 1);
encoder->type = run->type;
encoder->quality = 80;
encoder->quality = run->quality;
encoder->run = run;
return encoder;
}
@@ -66,21 +72,18 @@ 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;
if (encoder->run->type != ENCODER_TYPE_CPU) {
LOG_DEBUG("Initializing encoder ...");
}
encoder->run->quality = encoder->quality;
LOG_INFO("Using JPEG quality: %u%%", encoder->quality);
# ifdef OMX_ENCODER
# ifdef WITH_OMX_ENCODER
if (encoder->run->type == ENCODER_TYPE_OMX) {
LOG_DEBUG("Preparing OMX JPEG encoder ...");
if (dev->n_workers > OMX_MAX_ENCODERS) {
LOG_INFO(
"OMX-based encoder can only work with %u worker threads; forced --workers=%u",
OMX_MAX_ENCODERS, OMX_MAX_ENCODERS
);
LOG_INFO("OMX JPEG encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
dev->n_workers = OMX_MAX_ENCODERS;
}
encoder->run->n_omxs = dev->n_workers;
@@ -101,11 +104,12 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
use_fallback:
LOG_ERROR("Can't initialize selected encoder, using CPU instead it");
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
# ifdef WITH_OMX_ENCODER
if (encoder->run->omxs) {
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
if (encoder->run->omxs[index]) {
@@ -115,6 +119,7 @@ void encoder_destroy(struct encoder_t *encoder) {
free(encoder->run->omxs);
}
# endif
A_PTHREAD_M_DESTROY(&encoder->run->mutex);
free(encoder->run);
free(encoder);
}
@@ -128,16 +133,45 @@ enum encoder_type_t encoder_parse_type(const char *str) {
return ENCODER_TYPE_UNKNOWN;
}
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic push
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;
}
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
#pragma GCC diagnostic pop
# ifdef OMX_ENCODER
if (encoder->run->type == ENCODER_TYPE_OMX) {
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 JPEG 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 JPEG 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 WITH_OMX_ENCODER
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 JPEG encoder, falling back to CPU");
goto use_fallback;
}
}
@@ -146,12 +180,11 @@ 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");
A_PTHREAD_M_LOCK(&encoder->run->mutex);
encoder->run->type = ENCODER_TYPE_CPU;
# pragma GCC diagnostic pop
encoder->run->quality = encoder->quality;
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
}
#pragma GCC diagnostic ignored "-Wunused-label"
@@ -162,12 +195,14 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
if (encoder->run->type == ENCODER_TYPE_CPU) {
jpeg_encoder_compress_buffer(dev, buf_index, encoder->quality);
cpu_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
# ifdef WITH_OMX_ENCODER
else if (encoder->run->type == ENCODER_TYPE_OMX) {
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
goto error;
goto use_fallback;
}
}
# endif
@@ -176,9 +211,12 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
# pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push
error:
LOG_INFO("HW compressing error, falling back to 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

@@ -22,11 +22,13 @@
#pragma once
#include "pthread.h"
#include "tools.h"
#include "device.h"
#ifdef OMX_ENCODER
# include "omx/encoder.h"
#ifdef WITH_OMX_ENCODER
# include "encoders/omx/encoder.h"
# define ENCODER_TYPES_OMX_HINT ", OMX"
#else
# define ENCODER_TYPES_OMX_HINT ""
@@ -34,26 +36,29 @@
#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,
#ifdef OMX_ENCODER
ENCODER_TYPE_HW,
# ifdef WITH_OMX_ENCODER
ENCODER_TYPE_OMX,
#endif
# endif
};
struct encoder_runtime_t {
enum encoder_type_t type;
#ifdef OMX_ENCODER
enum encoder_type_t type;
unsigned quality;
pthread_mutex_t mutex;
# ifdef WITH_OMX_ENCODER
unsigned n_omxs;
struct omx_encoder_t **omxs;
#endif
# endif
};
struct encoder_t {
enum encoder_type_t type;
unsigned quality;
@@ -66,6 +71,7 @@ struct encoder_t *encoder_init();
void encoder_destroy(struct encoder_t *encoder);
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);

View File

@@ -33,35 +33,36 @@
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "../tools.h"
#include "../device.h"
#include "../../tools.h"
#include "../../device.h"
#include "encoder.h"
struct _mjpg_destination_mgr {
struct _jpeg_dest_manager_t {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buffer; // Start of buffer
unsigned char *outbuffer_cursor;
unsigned long *written;
size_t *written;
};
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written);
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, size_t *written);
static void _jpeg_write_scanlines_yuyv(
struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height);
static void _jpeg_write_scanlines_uyvy(
struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height);
static void _jpeg_write_scanlines_rgb565(
struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height);
static void _jpeg_write_scanlines_rgb24(
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height);
static void _jpeg_init_destination(j_compress_ptr jpeg);
@@ -69,20 +70,19 @@ 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, unsigned index, unsigned quality) {
void cpu_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;
struct jpeg_error_mgr jpeg_error;
unsigned char *line_buffer;
A_CALLOC(line_buffer, dev->run->width * 3);
jpeg.err = jpeg_std_error(&jpeg_error);
jpeg_create_compress(&jpeg);
dev->run->pictures[index].size = 0;
_jpeg_set_dest_picture(&jpeg, dev->run->pictures[index].data, &dev->run->pictures[index].size);
# define PICTURE(_next) dev->run->pictures[index]._next
PICTURE(size) = 0;
_jpeg_set_dest_picture(&jpeg, PICTURE(data), &PICTURE(size));
jpeg.image_width = dev->run->width;
jpeg.image_height = dev->run->height;
@@ -94,15 +94,16 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
jpeg_start_compress(&jpeg, TRUE);
# define WRITE_SCANLINES(_func) \
_func(&jpeg, line_buffer, dev->run->hw_buffers[index].start, dev->run->width, dev->run->height)
# define WRITE_SCANLINES(_format, _func) \
case _format: { _func(&jpeg, dev->run->hw_buffers[index].data, dev->run->width, dev->run->height); break; }
switch (dev->run->format) {
// https://www.fourcc.org/yuv.php
case V4L2_PIX_FMT_YUYV: WRITE_SCANLINES(_jpeg_write_scanlines_yuyv); break;
case V4L2_PIX_FMT_UYVY: WRITE_SCANLINES(_jpeg_write_scanlines_uyvy); break;
case V4L2_PIX_FMT_RGB565: WRITE_SCANLINES(_jpeg_write_scanlines_rgb565); break;
default: assert(0 && "Unsupported input format for JPEG compressor");
WRITE_SCANLINES(V4L2_PIX_FMT_YUYV, _jpeg_write_scanlines_yuyv);
WRITE_SCANLINES(V4L2_PIX_FMT_UYVY, _jpeg_write_scanlines_uyvy);
WRITE_SCANLINES(V4L2_PIX_FMT_RGB565, _jpeg_write_scanlines_rgb565);
WRITE_SCANLINES(V4L2_PIX_FMT_RGB24, _jpeg_write_scanlines_rgb24);
default: assert(0 && "Unsupported input format for CPU JPEG encoder");
}
# undef WRITE_SCANLINES
@@ -111,21 +112,20 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);
free(line_buffer);
assert(dev->run->pictures[index].size > 0);
assert(dev->run->pictures[index].size <= dev->run->max_picture_size);
assert(PICTURE(size) > 0);
assert(PICTURE(size) <= dev->run->max_picture_size);
}
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written) {
struct _mjpg_destination_mgr *dest;
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, size_t *written) {
struct _jpeg_dest_manager_t *dest;
if (jpeg->dest == NULL) {
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct _mjpg_destination_mgr)
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct _jpeg_dest_manager_t)
)));
}
dest = (struct _mjpg_destination_mgr *) jpeg->dest;
dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
dest->mgr.init_destination = _jpeg_init_destination;
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
dest->mgr.term_destination = _jpeg_term_destination;
@@ -133,16 +133,21 @@ static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture,
dest->written = written;
}
#define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x)))
#define YUV_R(_y, _, _v) (((_y) + (359 * (_v))) >> 8)
#define YUV_G(_y, _u, _v) (((_y) - (88 * (_u)) - (183 * (_v))) >> 8)
#define YUV_B(_y, _u, _) (((_y) + (454 * (_u))) >> 8)
#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,
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height) {
unsigned char *line_buffer;
JSAMPROW scanlines[1];
unsigned z = 0;
A_CALLOC(line_buffer, width * 3);
while (jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
@@ -151,9 +156,9 @@ static void _jpeg_write_scanlines_yuyv(
int u = data[1] - 128;
int v = data[3] - 128;
int r = (y + (359 * v)) >> 8;
int g = (y - (88 * u) - (183 * v)) >> 8;
int b = (y + (454 * u)) >> 8;
int r = YUV_R(y, u, v);
int g = YUV_G(y, u, v);
int b = YUV_B(y, u, v);
*(ptr++) = NORM_COMPONENT(r);
*(ptr++) = NORM_COMPONENT(g);
@@ -168,17 +173,21 @@ static void _jpeg_write_scanlines_yuyv(
scanlines[0] = line_buffer;
jpeg_write_scanlines(jpeg, scanlines, 1);
}
free(line_buffer);
}
static void _jpeg_write_scanlines_uyvy(
struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height) {
unsigned char *line_buffer;
JSAMPROW scanlines[1];
unsigned z = 0;
while(jpeg->next_scanline < height) {
A_CALLOC(line_buffer, width * 3);
while (jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for(unsigned x = 0; x < width; ++x) {
@@ -186,9 +195,9 @@ static void _jpeg_write_scanlines_uyvy(
int u = data[0] - 128;
int v = data[2] - 128;
int r = (y + (359 * v)) >> 8;
int g = (y - (88 * u) - (183 * v)) >> 8;
int b = (y + (454 * u)) >> 8;
int r = YUV_R(y, u, v);
int g = YUV_G(y, u, v);
int b = YUV_B(y, u, v);
*(ptr++) = NORM_COMPONENT(r);
*(ptr++) = NORM_COMPONENT(g);
@@ -203,26 +212,33 @@ static void _jpeg_write_scanlines_uyvy(
scanlines[0] = line_buffer;
jpeg_write_scanlines(jpeg, scanlines, 1);
}
free(line_buffer);
}
#undef NORM_COMPONENT
#undef YUV_B
#undef YUV_G
#undef YUV_R
static void _jpeg_write_scanlines_rgb565(
struct jpeg_compress_struct *jpeg,
unsigned char *line_buffer, const unsigned char *data,
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height) {
unsigned char *line_buffer;
JSAMPROW scanlines[1];
while(jpeg->next_scanline < height) {
A_CALLOC(line_buffer, width * 3);
while (jpeg->next_scanline < height) {
unsigned char *ptr = line_buffer;
for(unsigned x = 0; x < width; ++x) {
unsigned int two_byte = (data[1] << 8) + data[0];
*(ptr++) = data[1] & 248;
*(ptr++) = (unsigned char)((two_byte & 2016) >> 3);
*(ptr++) = (data[0] & 31) * 8;
*(ptr++) = data[1] & 248; // Red
*(ptr++) = (unsigned char)((two_byte & 2016) >> 3); // Green
*(ptr++) = (data[0] & 31) * 8; // Blue
data += 2;
}
@@ -230,12 +246,26 @@ static void _jpeg_write_scanlines_rgb565(
scanlines[0] = line_buffer;
jpeg_write_scanlines(jpeg, scanlines, 1);
}
free(line_buffer);
}
static void _jpeg_write_scanlines_rgb24(
struct jpeg_compress_struct *jpeg, const unsigned char *data,
unsigned width, unsigned height) {
JSAMPROW scanlines[1];
while (jpeg->next_scanline < height) {
scanlines[0] = (unsigned char *)(data + jpeg->next_scanline * width * 3);
jpeg_write_scanlines(jpeg, scanlines, 1);
}
}
#define JPEG_OUTPUT_BUFFER_SIZE 4096
static void _jpeg_init_destination(j_compress_ptr jpeg) {
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
// Allocate the output buffer - it will be released when done with image
assert((dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
@@ -249,7 +279,7 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
// Called whenever local jpeg buffer fills up
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
@@ -265,7 +295,7 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
// Called by jpeg_finish_compress after all data has been written.
// Usually needs to flush buffer
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
size_t data_count = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
// Write any data remaining in the buffer

View File

@@ -22,7 +22,7 @@
#pragma once
#include "../device.h"
#include "../../device.h"
void jpeg_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned quality);
void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned quality);

116
src/encoders/hw/encoder.c Normal file
View File

@@ -0,0 +1,116 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# This source file based on code of MJPG-Streamer. #
# #
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018 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 <stdbool.h>
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "../../tools.h"
#include "../../logging.h"
#include "../../xioctl.h"
#include "../../device.h"
#include "huffman.h"
#include "encoder.h"
static bool _is_huffman(const unsigned char *data);
static size_t _memcpy_with_huffman(unsigned char *dest, const unsigned char *src, size_t size);
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 encoder 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 encoder 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 encoder");
}
# define PICTURE(_next) dev->run->pictures[index]._next
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
assert(PICTURE(allocated) >= HW_BUFFER(size) + sizeof(HUFFMAN_TABLE));
PICTURE(size) = _memcpy_with_huffman(PICTURE(data), HW_BUFFER(data), HW_BUFFER(size));
# undef HW_BUFFER
# undef PICTURE
}
static bool _is_huffman(const unsigned char *data) {
unsigned count = 0;
while (((data[0] << 8) | data[1]) != 0xFFDA) {
if (count++ > 2048) {
return false;
}
if (((data[0] << 8) | data[1]) == 0xFFC4) {
return true;
}
data += 1;
}
return false;
}
static size_t _memcpy_with_huffman(unsigned char *dest, const unsigned char *src, size_t size) {
if (!_is_huffman(src)) {
const unsigned char *src_ptr = src;
const unsigned char *src_end = src + size;
size_t paste;
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
src_ptr += 1;
}
if (src_ptr >= src_end) {
return 0;
}
paste = src_ptr - src;
memcpy(dest, src, paste);
memcpy(dest + paste, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
memcpy(dest + paste + sizeof(HUFFMAN_TABLE), src_ptr, size - paste);
return (size + sizeof(HUFFMAN_TABLE));
} else {
memcpy(dest, src, size);
return size;
}
}

29
src/encoders/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);

66
src/encoders/hw/huffman.h Normal file
View File

@@ -0,0 +1,66 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# This source file based on code of MJPG-Streamer. #
# #
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
# Copyright (C) 2006 Gabriel A. Devenyi #
# Copyright (C) 2007 Tom Stöveken #
# Copyright (C) 2018 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
static const unsigned char HUFFMAN_TABLE[] = {
0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0A, 0x0B, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04,
0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15,
0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95,
0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2,
0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5,
0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9,
0xFA, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05,
0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33,
0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25,
0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94,
0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA,
0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4,
0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA,
};

View File

@@ -25,7 +25,7 @@
#include <IL/OMX_Core.h>
#include <IL/OMX_Component.h>
#include "../logging.h"
#include "../../logging.h"
#include "formatters.h"
#include "component.h"

View File

@@ -33,9 +33,9 @@
#include <IL/OMX_Broadcom.h>
#include <interface/vcos/vcos_semaphore.h>
#include "../logging.h"
#include "../tools.h"
#include "../device.h"
#include "../../logging.h"
#include "../../tools.h"
#include "../../device.h"
#include "formatters.h"
#include "component.h"
@@ -172,15 +172,21 @@ int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, un
}
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index) {
# define PICTURE(_next) dev->run->pictures[index]._next
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
# define IN(_next) omx->input_buffer->_next
# define OUT(_next) omx->output_buffer->_next
OMX_ERRORTYPE error;
bool loaded = false;
size_t slice_size = IN(nAllocLen);
size_t pos = 0;
if ((error = OMX_FillThisBuffer(omx->encoder, omx->output_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Failed to request filling of the output buffer on encoder");
return -1;
}
dev->run->pictures[index].size = 0;
PICTURE(size) = 0;
omx->output_available = false;
omx->input_required = true;
@@ -192,16 +198,12 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
if (omx->output_available) {
omx->output_available = false;
memcpy(
dev->run->pictures[index].data + dev->run->pictures[index].size,
omx->output_buffer->pBuffer,
omx->output_buffer->nFilledLen
);
assert(dev->run->pictures[index].size + omx->output_buffer->nFilledLen <= dev->run->max_picture_size);
dev->run->pictures[index].size += omx->output_buffer->nFilledLen;
assert(PICTURE(size) + OUT(nFilledLen) <= dev->run->max_picture_size);
memcpy(PICTURE(data) + PICTURE(size), OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
PICTURE(size) += OUT(nFilledLen);
if (omx->output_buffer->nFlags & OMX_BUFFERFLAG_ENDOFFRAME) {
omx->output_buffer->nFlags = 0;
if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) {
OUT(nFlags) = 0;
break;
}
@@ -213,14 +215,20 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
if (omx->input_required) {
omx->input_required = false;
if (loaded) {
if (pos == HW_BUFFER(size)) {
continue;
}
loaded = true;
memcpy(omx->input_buffer->pBuffer, dev->run->hw_buffers[index].start, dev->run->hw_buffers[index].length);
omx->input_buffer->nOffset = 0;
omx->input_buffer->nFilledLen = dev->run->hw_buffers[index].length;
memcpy(IN(pBuffer), HW_BUFFER(data) + pos, slice_size);
IN(nOffset) = 0;
IN(nFilledLen) = slice_size;
pos += slice_size;
if (pos + slice_size > HW_BUFFER(size)) {
slice_size = HW_BUFFER(size) - pos;
}
if ((error = OMX_EmptyThisBuffer(omx->encoder, omx->input_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Failed to request emptying of the input buffer on encoder");
@@ -230,6 +238,11 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
vcos_semaphore_wait(&omx->handler_lock);
}
# undef OUT
# undef IN
# undef HW_BUFFER
# undef PICTURE
return 0;
}
@@ -295,23 +308,32 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
portdef.format.image.nFrameWidth = dev->run->width;
portdef.format.image.nFrameHeight = dev->run->height;
portdef.format.image.nStride = 0;
# define ALIGN(_x, _y) (((_x) + ((_y) - 1)) & ~((_y) - 1))
portdef.format.image.nSliceHeight = ALIGN(dev->run->height, 16);
# undef ALIGN
# define ALIGN_HEIGHT(_x, _y) (((_x) + ((_y) - 1)) & ~((_y) - 1))
portdef.format.image.nSliceHeight = ALIGN_HEIGHT(dev->run->height, 16);
# undef ALIGN_HEIGHT
portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused;
portdef.nBufferSize = dev->run->max_picture_size;
# define MAP_FORMAT(_v4l2_format, _omx_format) \
case _v4l2_format: { portdef.format.image.eColorFormat = _omx_format; break; }
switch (dev->run->format) {
// https://www.fourcc.org/yuv.php
// Also see comments inside OMX_IVCommon.h
case V4L2_PIX_FMT_YUYV: portdef.format.image.eColorFormat = OMX_COLOR_FormatYCbYCr; break;
case V4L2_PIX_FMT_UYVY: portdef.format.image.eColorFormat = OMX_COLOR_FormatCbYCrY; break;
case V4L2_PIX_FMT_RGB565: portdef.format.image.eColorFormat = OMX_COLOR_Format16bitRGB565; break;
// TODO: Check RGB565 + OMX. I don't have any USB devices which supports it.
default: assert(0 && "Unsupported input format for OMX JPEG compressor");
MAP_FORMAT(V4L2_PIX_FMT_YUYV, OMX_COLOR_FormatYCbYCr);
MAP_FORMAT(V4L2_PIX_FMT_UYVY, OMX_COLOR_FormatCbYCrY);
MAP_FORMAT(V4L2_PIX_FMT_RGB565, OMX_COLOR_Format16bitRGB565);
MAP_FORMAT(V4L2_PIX_FMT_RGB24, OMX_COLOR_Format24bitRGB888);
// TODO: найти устройство с RGB565 и протестить его.
// FIXME: RGB24 не работает нормально, нижняя половина экрана зеленая.
// FIXME: Китайский EasyCap тоже не работает, мусор на экране.
// Вероятно обе проблемы вызваны некорректной реализацией OMX на пае.
default: assert(0 && "Unsupported input format for OMX JPEG encoder");
}
# undef MAP_FORMAT
if (component_set_portdef(&omx->encoder, &portdef) < 0) {
return -1;
}

View File

@@ -27,10 +27,11 @@
#include <IL/OMX_Component.h>
#include <interface/vcos/vcos_semaphore.h>
#include "../device.h"
#include "../../device.h"
#define OMX_MAX_ENCODERS 3
#ifndef OMX_MAX_ENCODERS
# define OMX_MAX_ENCODERS 3 // Raspberry Pi limitation
#endif
struct omx_encoder_t {

View File

@@ -26,7 +26,7 @@
#include <IL/OMX_IVCommon.h>
#include <IL/OMX_Core.h>
#include "../tools.h"
#include "../../tools.h"
#include "formatters.h"

View File

@@ -30,8 +30,8 @@
#include <IL/OMX_Core.h>
#include <IL/OMX_Image.h>
#include "../logging.h"
#include "../tools.h"
#include "../../logging.h"
#include "../../tools.h"
#define LOG_OMX_ERROR(_error, _msg, ...) { \

View File

@@ -47,6 +47,7 @@
#include "tools.h"
#include "logging.h"
#include "encoder.h"
#include "stream.h"
#include "http.h"
@@ -247,7 +248,7 @@ static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg
PROCESS_HEAD_REQUEST;
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf, HTML_INDEX_PAGE));
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
ADD_HEADER("Content-Type", "text/html");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
@@ -256,18 +257,28 @@ 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\": {"
" \"encoder\": {\"fallback\": %s, \"quality\": %u},"
" \"encoder\": {\"type\": \"%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 != server->run->stream->encoder->run->type),
server->run->stream->encoder->quality,
encoder_type_to_string(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),
@@ -276,6 +287,9 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
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",
@@ -287,6 +301,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");
@@ -392,16 +407,13 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
server->run->stream_clients_count += 1;
evhttp_connection_get_peer(conn, &client_addr, &client_port);
LOG_INFO(
"HTTP: Registered the new stream client: [%s]:%u; id=%s;"
" advance_headers=%s; dual_final_frames=%s; clients now: %u",
LOG_INFO("HTTP: Registered the new stream client: [%s]:%u; id=%s; advance_headers=%s; dual_final_frames=%s; clients now: %u",
client_addr,
client_port,
client->id,
bool_to_string(client->advance_headers),
bool_to_string(client->dual_final_frames),
server->run->stream_clients_count
);
server->run->stream_clients_count);
buf_event = evhttp_connection_get_bufferevent(conn);
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
@@ -482,7 +494,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
if (!client->advance_headers) {
assert(evbuffer_add_printf(buf,
"Content-Type: image/jpeg" RN
"Content-Length: %lu" RN
"Content-Length: %zu" RN
"X-Timestamp: %.06Lf" RN
"%s",
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data)),
@@ -553,10 +565,8 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
if (conn != NULL) {
evhttp_connection_get_peer(conn, &client_addr, &client_port);
}
LOG_INFO(
"HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
client_addr, client_port, client->server->run->stream_clients_count
);
LOG_INFO("HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
client_addr, client_port, client->server->run->stream_clients_count);
if (conn != NULL) {
evhttp_connection_free(conn);
}
@@ -673,18 +683,14 @@ static bool _expose_new_picture(struct http_server_t *server) {
) {
EXPOSED(expose_cmp_time) = get_now_monotonic();
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
LOG_VERBOSE(
"HTTP: dropped same frame number %u; comparsion time = %.06Lf",
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
);
LOG_VERBOSE("HTTP: dropped same frame number %u; comparsion time = %.06Lf",
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
EXPOSED(dropped) += 1;
return false; // Not updated
} else {
EXPOSED(expose_cmp_time) = get_now_monotonic();
LOG_VERBOSE(
"HTTP: passed same frame check (frames are differ); comparsion time = %.06Lf",
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
);
LOG_VERBOSE("HTTP: passed same frame check (frames are differ); comparsion time = %.06Lf",
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
}
}
@@ -710,10 +716,8 @@ static bool _expose_new_picture(struct http_server_t *server) {
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
EXPOSED(expose_end_time) = get_now_monotonic();
LOG_VERBOSE(
"HTTP: exposed new frame; full exposition time = %.06Lf",
EXPOSED(expose_end_time) - EXPOSED(expose_begin_time)
);
LOG_VERBOSE("HTTP: exposed new frame; full exposition time = %.06Lf",
EXPOSED(expose_end_time) - EXPOSED(expose_begin_time));
# undef EXPOSED
# undef STREAM
@@ -721,30 +725,28 @@ static bool _expose_new_picture(struct http_server_t *server) {
}
static bool _expose_blank_picture(struct http_server_t *server) {
# define EXPOSED(_next) server->run->exposed->_next
# define EXPOSED(_next) server->run->exposed->_next
# define BLANK_JPEG_LEN ARRAY_LEN(BLANK_JPEG_DATA)
EXPOSED(expose_begin_time) = get_now_monotonic();
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
if (EXPOSED(online) || EXPOSED(picture.size) == 0) {
if (EXPOSED(picture.allocated) < BLANK_JPG_SIZE) {
A_REALLOC(EXPOSED(picture.data), BLANK_JPG_SIZE);
EXPOSED(picture.allocated) = BLANK_JPG_SIZE;
if (EXPOSED(picture.allocated) < BLANK_JPEG_LEN) {
A_REALLOC(EXPOSED(picture.data), BLANK_JPEG_LEN);
EXPOSED(picture.allocated) = BLANK_JPEG_LEN;
}
memcpy(
EXPOSED(picture.data), BLANK_JPG_DATA,
BLANK_JPG_SIZE * sizeof(*EXPOSED(picture.data))
);
memcpy(EXPOSED(picture.data), BLANK_JPEG_DATA, BLANK_JPEG_LEN * sizeof(*EXPOSED(picture.data)));
EXPOSED(picture.size) = BLANK_JPG_SIZE;
EXPOSED(picture.size) = BLANK_JPEG_LEN;
EXPOSED(picture.grab_time) = 0;
EXPOSED(picture.encode_begin_time) = 0;
EXPOSED(picture.encode_end_time) = 0;
EXPOSED(width) = BLANK_JPG_WIDTH;
EXPOSED(height) = BLANK_JPG_HEIGHT;
EXPOSED(width) = BLANK_JPEG_WIDTH;
EXPOSED(height) = BLANK_JPEG_HEIGHT;
EXPOSED(captured_fps) = 0;
EXPOSED(online) = false;
goto updated;
@@ -762,5 +764,6 @@ static bool _expose_blank_picture(struct http_server_t *server) {
EXPOSED(expose_end_time) = get_now_monotonic();
return true; // Updated
# undef BLANK_JPEG_LEN
# undef EXPOSED
}

View File

@@ -60,15 +60,29 @@ static const struct option _long_opts[] = {
{"device-persistent", no_argument, NULL, 1001},
{"device-error-delay", required_argument, NULL, 1002},
{"brightness", required_argument, NULL, 2000},
{"brightness-auto", no_argument, NULL, 2001},
{"contrast", required_argument, NULL, 2002},
{"saturation", required_argument, NULL, 2003},
{"hue", required_argument, NULL, 2004},
{"hue-auto", no_argument, NULL, 2005},
{"gamma", required_argument, NULL, 2006},
{"sharpness", required_argument, NULL, 2007},
{"backlight-compensation", required_argument, NULL, 2008},
{"white-balance", required_argument, NULL, 2009},
{"white-balance-auto", no_argument, NULL, 2010},
{"gain", required_argument, NULL, 2011},
{"gain-auto", no_argument, NULL, 2012},
{"host", required_argument, NULL, 's'},
{"port", required_argument, NULL, 'p'},
{"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},
{"fake-width", required_argument, NULL, 3001},
{"fake-height", required_argument, NULL, 3002},
{"server-timeout", required_argument, NULL, 3003},
{"perf", no_argument, NULL, 5000},
{"verbose", no_argument, NULL, 5001},
@@ -81,7 +95,7 @@ static const struct option _long_opts[] = {
static void _version(bool nl) {
printf(VERSION);
# ifdef OMX_ENCODER
# ifdef WITH_OMX_ENCODER
printf(" + OMX");
# endif
if (nl) {
@@ -122,6 +136,21 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
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: %u\n\n", dev->error_delay);
printf("Image control options:\n");
printf("---------------\n");
printf(" --brightness <N> -- Set brightness. Default: no change.\n\n");
printf(" --brightness-auto -- Enable automatic brightness control. Default: no change.\n\n");
printf(" --contrast <N> -- Set contrast. Default: no change.\n\n");
printf(" --saturation <N> -- Set saturation. Default: no change.\n\n");
printf(" --hue <N> -- Set hue. Default: no change.\n\n");
printf(" --hue-auto -- Enable automatic hue control. Default: no change.\n\n");
printf(" --gamma <N> -- Set gamma. Default: no change.\n\n");
printf(" --sharpness <N> -- Set sharpness. Default: no change.\n\n");
printf(" --backlight-compensation <N> -- Set backlight compensation. Default: no change.\n\n");
printf(" --white-balance <N> -- Set white balance. Default: no change.\n\n");
printf(" --white-balance-auto -- Enable automatic white balance control. Default: no change.\n\n");
printf(" --gain <N> -- Set gain. Default: no change.\n\n");
printf(" --gain-auto -- Enable automatic gain control. Default: no change.\n\n");
printf("HTTP server options:\n");
printf("--------------------\n");
printf(" -s|--host <address> -- Listen on Hostname or IP. Default: %s\n\n", server->host);
@@ -152,22 +181,49 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
# define OPT_SET(_dest, _value) \
{ _dest = _value; break; }
# define OPT_UNSIGNED(_dest, _name, _min, _max) \
{ 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_UNSIGNED(_dest, _name, _min, _max) { \
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) \
{ if ((_dest = _func(optarg)) == _invalid) \
{ printf("Unknown " _name ": %s\n", optarg); return -1; } \
break; }
# define OPT_PARSE(_dest, _func, _invalid, _name) { \
if ((_dest = _func(optarg)) == _invalid) { \
printf("Unknown " _name ": %s\n", optarg); \
return -1; \
} \
break; \
}
# define OPT_INT(_dest, _name, _base) { \
errno = 0; char *_end = NULL; int _tmp = strtol(optarg, &_end, _base); \
if (errno || *_end) { \
printf("Invalid value for '%s=%s'\n", _name, optarg); \
return -1; \
} \
_dest = _tmp; \
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; }
OPT_INT(_dest, _name, 8)
# define OPT_CTL(_dest) { \
dev->ctl->_dest.value_set = true; \
dev->ctl->_dest.auto_set = false; \
OPT_INT(dev->ctl->_dest.value, "--"#_dest, 10); \
break; \
}
# define OPT_CTL_AUTO(_dest) { \
dev->ctl->_dest.value_set = false; \
dev->ctl->_dest.auto_set = true; \
break; \
}
int index;
int ch;
@@ -195,15 +251,29 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
case 1001: OPT_SET(dev->persistent, true);
case 1002: OPT_UNSIGNED(dev->error_delay, "--device-error-delay", 1, 60);
case 2000: OPT_CTL(brightness);
case 2001: OPT_CTL_AUTO(brightness);
case 2002: OPT_CTL(contrast);
case 2003: OPT_CTL(saturation);
case 2004: OPT_CTL(hue);
case 2005: OPT_CTL_AUTO(hue);
case 2006: OPT_CTL(gamma);
case 2007: OPT_CTL(sharpness);
case 2008: OPT_CTL(backlight_compensation);
case 2009: OPT_CTL(white_balance);
case 2010: OPT_CTL_AUTO(white_balance);
case 2011: OPT_CTL(gain);
case 2012: OPT_CTL_AUTO(gain);
case 's': OPT_SET(server->host, optarg);
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535);
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);
case 3001: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
case 3002: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
case 3003: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
case 5000: OPT_SET(log_level, LOG_LEVEL_PERF);
case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
@@ -216,7 +286,10 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
}
}
# undef OPT_CTL_AUTO
# undef OPT_CTL
# undef OPT_CHMOD
# undef OPT_INT
# undef OPT_PARSE
# undef OPT_UNSIGNED
# undef OPT_SET

View File

@@ -36,8 +36,6 @@
#include "encoder.h"
#include "stream.h"
#include "jpeg/encoder.h"
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool);
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index);
@@ -118,9 +116,12 @@ void stream_loop(struct stream_t *stream) {
LOG_PERF("##### Raw frame accepted; worker = %u", free_worker_number);
} else {
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
if (!pool.workers[number].has_job && (free_worker_number == -1
|| pool.workers[free_worker_number].job_start_time < pool.workers[number].job_start_time
)) {
if (
!pool.workers[number].has_job && (
free_worker_number == -1
|| pool.workers[free_worker_number].job_start_time < pool.workers[number].job_start_time
)
) {
free_worker_number = number;
break;
}
@@ -136,12 +137,14 @@ void stream_loop(struct stream_t *stream) {
break;
}
# define INIT_FD_SET(_set) \
fd_set _set; FD_ZERO(&_set); FD_SET(stream->dev->run->fd, &_set);
# define INIT_FD_SET(_set) \
fd_set _set; FD_ZERO(&_set); FD_SET(stream->dev->run->fd, &_set);
INIT_FD_SET(read_fds);
INIT_FD_SET(write_fds);
INIT_FD_SET(error_fds);
# undef INIT_FD_SET
# undef INIT_FD_SET
struct timeval timeout;
timeout.tv_sec = stream->dev->timeout;
@@ -216,30 +219,34 @@ void stream_loop(struct stream_t *stream) {
LOG_VERBOSE("Fluency: delay=%.03Lf; grab_after=%.03Lf", fluency_delay, grab_after);
}
# define FREE_WORKER(_next) pool.workers[free_worker_number]._next
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_info.index);
pool.workers[free_worker_number].ctx.buf_info = buf_info;
FREE_WORKER(ctx.buf_info) = buf_info;
if (!oldest_worker) {
oldest_worker = &pool.workers[free_worker_number];
last_worker = oldest_worker;
} else {
if (pool.workers[free_worker_number].order_next) {
pool.workers[free_worker_number].order_next->order_prev = pool.workers[free_worker_number].order_prev;
if (FREE_WORKER(order_next)) {
FREE_WORKER(order_next->order_prev) = FREE_WORKER(order_prev);
}
if (pool.workers[free_worker_number].order_prev) {
pool.workers[free_worker_number].order_prev->order_next = pool.workers[free_worker_number].order_next;
if (FREE_WORKER(order_prev)) {
FREE_WORKER(order_prev->order_next) = FREE_WORKER(order_next);
}
pool.workers[free_worker_number].order_prev = last_worker;
FREE_WORKER(order_prev) = last_worker;
last_worker->order_next = &pool.workers[free_worker_number];
last_worker = &pool.workers[free_worker_number];
}
last_worker->order_next = NULL;
A_PTHREAD_M_LOCK(&pool.workers[free_worker_number].has_job_mutex);
pool.workers[free_worker_number].ctx.buf_index = buf_info.index;
pool.workers[free_worker_number].has_job = true;
A_PTHREAD_M_UNLOCK(&pool.workers[free_worker_number].has_job_mutex);
A_PTHREAD_C_SIGNAL(&pool.workers[free_worker_number].has_job_cond);
A_PTHREAD_M_LOCK(&FREE_WORKER(has_job_mutex));
FREE_WORKER(ctx.buf_index) = buf_info.index;
FREE_WORKER(has_job) = true;
A_PTHREAD_M_UNLOCK(&FREE_WORKER(has_job_mutex));
A_PTHREAD_C_SIGNAL(&FREE_WORKER(has_job_cond));
# undef FREE_WORKER
A_PTHREAD_M_LOCK(&pool.free_workers_mutex);
pool.free_workers -= 1;
@@ -295,10 +302,9 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
stream->picture.size = PICTURE(size);
stream->picture.allocated = PICTURE(allocated);
memcpy(
stream->picture.data, PICTURE(data),
stream->picture.size * sizeof(*stream->picture.data)
);
memcpy(stream->picture.data, PICTURE(data), stream->picture.size * sizeof(*stream->picture.data));
stream->picture.grab_time = PICTURE(grab_time);
stream->picture.encode_begin_time = PICTURE(encode_begin_time);
stream->picture.encode_end_time = PICTURE(encode_end_time);
@@ -319,11 +325,15 @@ static long double _stream_get_fluency_delay(struct device_t *dev, struct worker
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) {
sum_comp_time += pool->workers[number].last_comp_time;
# define WORKER(_next) pool->workers[number]._next
A_PTHREAD_M_LOCK(&WORKER(last_comp_time_mutex));
if (WORKER(last_comp_time) > 0) {
sum_comp_time += WORKER(last_comp_time);
}
A_PTHREAD_M_UNLOCK(&pool->workers[number].last_comp_time_mutex);
A_PTHREAD_M_UNLOCK(&WORKER(last_comp_time_mutex));
# undef WORKER
}
avg_comp_time = sum_comp_time / dev->n_workers; // Среднее время работы воркеров
@@ -388,12 +398,13 @@ static void _stream_init_workers(struct device_t *dev, struct workers_pool_t *po
A_PTHREAD_C_INIT(&pool->free_workers_cond);
for (unsigned number = 0; number < dev->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
# define CTX(_next) WORKER(ctx._next)
pool->free_workers += 1;
A_PTHREAD_M_INIT(&pool->workers[number].has_job_mutex);
A_PTHREAD_C_INIT(&pool->workers[number].has_job_cond);
# define CTX(_next) pool->workers[number].ctx._next
A_PTHREAD_M_INIT(&WORKER(has_job_mutex));
A_PTHREAD_C_INIT(&WORKER(has_job_cond));
CTX(number) = number;
CTX(dev) = dev;
@@ -402,22 +413,23 @@ static void _stream_init_workers(struct device_t *dev, struct workers_pool_t *po
CTX(encoder) = pool->encoder;
CTX(last_comp_time_mutex) = &pool->workers[number].last_comp_time_mutex;
CTX(last_comp_time) = &pool->workers[number].last_comp_time;
CTX(last_comp_time_mutex) = &WORKER(last_comp_time_mutex);
CTX(last_comp_time) = &WORKER(last_comp_time);
CTX(has_job_mutex) = &pool->workers[number].has_job_mutex;
CTX(has_job) = &pool->workers[number].has_job;
CTX(job_failed) = &pool->workers[number].job_failed;
CTX(job_start_time) = &pool->workers[number].job_start_time;
CTX(has_job_cond) = &pool->workers[number].has_job_cond;
CTX(has_job_mutex) = &WORKER(has_job_mutex);
CTX(has_job) = &WORKER(has_job);
CTX(job_failed) = &WORKER(job_failed);
CTX(job_start_time) = &WORKER(job_start_time);
CTX(has_job_cond) = &WORKER(has_job_cond);
CTX(free_workers_mutex) = &pool->free_workers_mutex;
CTX(free_workers) = &pool->free_workers;
CTX(free_workers_cond) = &pool->free_workers_cond;
# undef CTX
A_PTHREAD_CREATE(&WORKER(tid), _stream_worker_thread, (void *)&WORKER(ctx));
A_PTHREAD_CREATE(&pool->workers[number].tid, _stream_worker_thread, (void *)&pool->workers[number].ctx);
# undef CTX
# undef WORKER
}
}
@@ -432,9 +444,9 @@ 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);
# define PICTURE(_next) ctx->dev->run->pictures[ctx->buf_index]._next
if (!*ctx->workers_stop) {
# define PICTURE(_next) ctx->dev->run->pictures[ctx->buf_index]._next
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", ctx->number, ctx->buf_index);
PICTURE(encode_begin_time) = get_now_monotonic();
@@ -453,17 +465,15 @@ static void *_stream_worker_thread(void *v_ctx) {
*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=%u",
PICTURE(size), last_comp_time, ctx->number, ctx->buf_index
);
LOG_VERBOSE("Compressed JPEG size=%zu; time=%0.3Lf; worker=%u; buffer=%u",
PICTURE(size), last_comp_time, ctx->number, ctx->buf_index);
} else {
*ctx->job_failed = true;
*ctx->has_job = false;
}
}
# undef PICTURE
# undef PICTURE
}
A_PTHREAD_M_LOCK(ctx->free_workers_mutex);
*ctx->free_workers += 1;
@@ -481,14 +491,18 @@ static void _stream_destroy_workers(struct device_t *dev, struct workers_pool_t
*pool->workers_stop = true;
for (unsigned number = 0; number < dev->n_workers; ++number) {
A_PTHREAD_M_LOCK(&pool->workers[number].has_job_mutex);
pool->workers[number].has_job = true; // Final job: die
A_PTHREAD_M_UNLOCK(&pool->workers[number].has_job_mutex);
A_PTHREAD_C_SIGNAL(&pool->workers[number].has_job_cond);
# define WORKER(_next) pool->workers[number]._next
A_PTHREAD_JOIN(pool->workers[number].tid);
A_PTHREAD_M_DESTROY(&pool->workers[number].has_job_mutex);
A_PTHREAD_C_DESTROY(&pool->workers[number].has_job_cond);
A_PTHREAD_M_LOCK(&WORKER(has_job_mutex));
WORKER(has_job) = true; // Final job: die
A_PTHREAD_M_UNLOCK(&WORKER(has_job_mutex));
A_PTHREAD_C_SIGNAL(&WORKER(has_job_cond));
A_PTHREAD_JOIN(WORKER(tid));
A_PTHREAD_M_DESTROY(&WORKER(has_job_mutex));
A_PTHREAD_C_DESTROY(&WORKER(has_job_cond));
# undef WORKER
}
A_PTHREAD_M_DESTROY(&pool->free_workers_mutex);

View File

@@ -41,7 +41,7 @@ def main():
text = text.replace("%VERSION%", "\" VERSION \"")
text = textwrap.indent(text, "\t", (lambda line: True))
text = "\n".join(("%s \\" if line.strip() else "%s\\") % (line) for line in text.split("\n"))
text = "const char *%s = \" \\\n%s\n\";\n" % (name, text)
text = "const char %s[] = \" \\\n%s\n\";\n" % (name, text)
text = textwrap.dedent("""
/*****************************************************************************
# #

View File

@@ -35,20 +35,19 @@ def main():
width = int(sys.argv[4])
height = int(sys.argv[5])
with open(src, "rb") as jpg_file:
jpg_data = jpg_file.read()
with open(src, "rb") as jpeg_file:
jpeg_data = jpeg_file.read()
rows = [[]]
for ch in jpg_data:
for ch in jpeg_data:
if len(rows[-1]) > 20:
rows.append([])
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)
text = "const unsigned long %s_JPG_SIZE = %d;\n\n" % (prefix, len(jpg_data)) + text
text = "const unsigned %s_JPG_HEIGHT = %d;\n\n" % (prefix, height) + text
text = "const unsigned %s_JPG_WIDTH = %d;\n" % (prefix, width) + text
text = "const unsigned char %s_JPEG_DATA[] = {\n\t%s\n};\n" % (prefix, text)
text = "const unsigned %s_JPEG_HEIGHT = %d;\n\n" % (prefix, height) + text
text = "const unsigned %s_JPEG_WIDTH = %d;\n" % (prefix, width) + text
text = textwrap.dedent("""
/*****************************************************************************
# #

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
}