mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-26 03:36:31 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c8351c1bc | ||
|
|
4492cc1efe | ||
|
|
b2ca0ea998 | ||
|
|
924665c1a3 | ||
|
|
5d49018bb2 | ||
|
|
3ae8818b3d | ||
|
|
2bb1f71c9c | ||
|
|
537e55afc6 | ||
|
|
6cdaceb561 | ||
|
|
08aacdc9af | ||
|
|
3db57cfa42 | ||
|
|
d8a774e358 | ||
|
|
869d12759c | ||
|
|
bce3ae0f21 | ||
|
|
1541921070 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 0.51
|
||||
current_version = 0.54
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
6
Makefile
6
Makefile
@@ -8,13 +8,13 @@ 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/encoders/cpu/*.c src/encoders/hw/*.c)
|
||||
OBJECTS = $(SOURCES:.c=.o)
|
||||
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
|
||||
endif
|
||||
@@ -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
|
||||
|
||||
4
PKGBUILD
4
PKGBUILD
@@ -3,14 +3,14 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=0.51
|
||||
pkgver=0.54
|
||||
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)
|
||||
|
||||
@@ -11,22 +11,19 @@
|
||||
| Multithreaded JPEG encoding |  Yes |  No |
|
||||
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi |  Yes |  No |
|
||||
| Behavior when the device<br>is disconnected while streaming |  Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected |  Stops the broadcast <sup>1</sup> |
|
||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal |  Yes |  Partially yes <sup>1</sup> |
|
||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal |  Yes |  Partially yes <sup>2</sup> |
|
||||
| Option to skip frames when streaming<br>static images by HTTP to save traffic |  Yes <sup>2</sup> |  No |
|
||||
| Streaming via UNIX domain socket |  Yes |  No |
|
||||
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters |  Yes |  No |
|
||||
| Supported input formats |  YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> |  YUYV, UYVY,<br>RGB565, MJPG |
|
||||
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP |  No |  Yes |
|
||||
| Option to serve files<br>with a built-in HTTP server, auth settings |  No <sup>4</sup> |  Yes |
|
||||
| Option to serve files<br>with a built-in HTTP server, auth settings |  No <sup>3</sup> |  Yes |
|
||||
|
||||
Footnotes:
|
||||
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
|
||||
|
||||
* ```2``` This feature allows to cut down outgoing traffic several-fold when broadcasting HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to broadcast the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
|
||||
|
||||
* ```3``` As µStreamer was made mainly to be used with screencast hardware it supports video formats they usually need. MJPG input means that the screencast device can compress images to JPEG and feed it to the software, which allows to cut down CPU usage and avoid software image encoding. This video format is supported by most webcams, but I've never seen it supported by screencast hardware: neither [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), nor [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC) offer such support. It's not hard to add hardware MJPG sources support, it's just not done yet.
|
||||
|
||||
* ```4``` ...and there'll never be. µStreamer is designed UNIX-way, so if you need a small website with your broadcast, install NGINX.
|
||||
* ```3``` ...and there'll never be. µStreamer is designed UNIX-way, so if you need a small website with your broadcast, install NGINX.
|
||||
|
||||
-----
|
||||
# TL;DR
|
||||
|
||||
@@ -11,22 +11,19 @@
|
||||
| Многопоточное кодирование JPEG |  Есть |  Нет |
|
||||
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi |  Есть |  Нет |
|
||||
| Поведение при физическом отключении устройства<br>от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова |  Прерывает трансляцию <sup>1</sup> |
|
||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) |  Есть |  Условно есть <sup>1</sup> |
|
||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) |  Есть |  Условно есть <sup>2</sup> |
|
||||
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
||||
| Стрим через UNIX domain socket |  Есть |  Нет |
|
||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
||||
| Поддерживаемые входные форматы устройств |  YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> |  YUYV, UYVY,<br>RGB565, MJPG |
|
||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
||||
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации |  Нет <sup>4</sup> |  Есть |
|
||||
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации |  Нет <sup>3</sup> |  Есть |
|
||||
|
||||
Сносочки:
|
||||
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
|
||||
|
||||
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
|
||||
|
||||
* ```3``` Поскольку µStreamer писался в первую очередь для устройств видеозахвата, в нем реализованы только те форматы, которые для них были нужны. MJPG в контексте входных данных означает, что устройство умеет самостоятельно сжимать картинку в JPEG и отдавать ее программе, что позволяет значительно снизить загрузку процессора и избавить его от необходимости кодировать картинку софтом. Этот формат поддерживается большинством веб-камер, но не поддерживается ни одним из встреченных мной устройств видеозахвата; его не умеет ни [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), ни [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC). Нет никаких технических сложностей добавить поддержку аппаратного MJPG источника, но у меня просто пока не дошли до этого руки.
|
||||
|
||||
* ```4``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
|
||||
* ```3``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
|
||||
|
||||
-----
|
||||
# TL;DR
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION "0.51"
|
||||
#define VERSION "0.54"
|
||||
|
||||
35
src/device.c
35
src/device.c
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
@@ -67,8 +70,9 @@ static int _device_open_mmap(struct device_t *dev);
|
||||
static int _device_open_queue_buffers(struct device_t *dev);
|
||||
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height);
|
||||
|
||||
static const char *_format_to_string_auto(char *buf, 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);
|
||||
|
||||
|
||||
@@ -286,7 +290,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;
|
||||
@@ -299,8 +302,8 @@ static int _device_open_format(struct device_t *dev) {
|
||||
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),
|
||||
"Unable to set pixelformat=%s; resolution=%ux%u",
|
||||
_format_to_string_supported(dev->format),
|
||||
dev->run->width,
|
||||
dev->run->height
|
||||
);
|
||||
@@ -323,24 +326,27 @@ static int _device_open_format(struct device_t *dev) {
|
||||
|
||||
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)
|
||||
_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) {
|
||||
if ((format_str_nullable = (char *)_format_to_string_nullable(fmt.fmt.pix.pixelformat)) != NULL) {
|
||||
LOG_INFO(
|
||||
"Falling back to %s mode (consider using '--format=%s' option)",
|
||||
format_str_nullable,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -439,7 +445,7 @@ 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 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 +462,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 +471,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) {
|
||||
|
||||
@@ -39,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, RGB24, JPEG"
|
||||
|
||||
|
||||
struct hw_buffer_t {
|
||||
@@ -62,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;
|
||||
|
||||
@@ -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"
|
||||
# include "encoders/omx/encoder.h"
|
||||
#endif
|
||||
|
||||
|
||||
@@ -40,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
|
||||
@@ -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,16 +72,16 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
// XXX: Тут нет гонки, потому что encoder_prepare() запускается еще до существования других потоков
|
||||
encoder->run->type = encoder->type;
|
||||
|
||||
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
|
||||
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",
|
||||
@@ -101,6 +107,7 @@ 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
|
||||
}
|
||||
|
||||
@@ -115,6 +122,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 +136,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
|
||||
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 OMX_ENCODER
|
||||
if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||
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 +183,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 +198,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
|
||||
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 +214,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
|
||||
}
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pthread.h"
|
||||
|
||||
#include "tools.h"
|
||||
#include "device.h"
|
||||
|
||||
#ifdef OMX_ENCODER
|
||||
# include "omx/encoder.h"
|
||||
# include "encoders/omx/encoder.h"
|
||||
# define ENCODER_TYPES_OMX_HINT ", OMX"
|
||||
#else
|
||||
# define ENCODER_TYPES_OMX_HINT ""
|
||||
@@ -34,19 +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_runtime_t {
|
||||
enum encoder_type_t type;
|
||||
enum encoder_type_t type;
|
||||
unsigned quality;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
#ifdef OMX_ENCODER
|
||||
unsigned n_omxs;
|
||||
struct omx_encoder_t **omxs;
|
||||
@@ -66,6 +72,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);
|
||||
|
||||
@@ -33,13 +33,13 @@
|
||||
#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;
|
||||
@@ -50,18 +50,19 @@ 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,
|
||||
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,14 +70,11 @@ 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);
|
||||
@@ -94,15 +92,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].start, 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 +110,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);
|
||||
}
|
||||
|
||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written) {
|
||||
struct _mjpg_destination_mgr *dest;
|
||||
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;
|
||||
@@ -136,13 +134,15 @@ static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture,
|
||||
#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;
|
||||
|
||||
@@ -168,17 +168,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) {
|
||||
@@ -203,18 +207,22 @@ static void _jpeg_write_scanlines_uyvy(
|
||||
scanlines[0] = line_buffer;
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
|
||||
free(line_buffer);
|
||||
}
|
||||
|
||||
#undef NORM_COMPONENT
|
||||
|
||||
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) {
|
||||
@@ -230,12 +238,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 +271,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 +287,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
|
||||
@@ -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);
|
||||
58
src/encoders/hw/encoder.c
Normal file
58
src/encoders/hw/encoder.c
Normal file
@@ -0,0 +1,58 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../../tools.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../xioctl.h"
|
||||
#include "../../device.h"
|
||||
|
||||
|
||||
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality) {
|
||||
struct v4l2_jpegcompression comp;
|
||||
|
||||
MEMSET_ZERO(comp);
|
||||
|
||||
if (xioctl(dev->run->fd, VIDIOC_G_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Can't query HW JPEG 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");
|
||||
}
|
||||
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/encoders/hw/encoder.h
Normal file
29
src/encoders/hw/encoder.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../device.h"
|
||||
|
||||
|
||||
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality);
|
||||
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index);
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -173,7 +173,8 @@ 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) {
|
||||
OMX_ERRORTYPE error;
|
||||
bool loaded = false;
|
||||
unsigned slice_size = omx->input_buffer->nAllocLen;
|
||||
unsigned 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");
|
||||
@@ -194,7 +195,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
|
||||
memcpy(
|
||||
dev->run->pictures[index].data + dev->run->pictures[index].size,
|
||||
omx->output_buffer->pBuffer,
|
||||
omx->output_buffer->pBuffer + omx->output_buffer->nOffset,
|
||||
omx->output_buffer->nFilledLen
|
||||
);
|
||||
assert(dev->run->pictures[index].size + omx->output_buffer->nFilledLen <= dev->run->max_picture_size);
|
||||
@@ -213,14 +214,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 == dev->run->hw_buffers[index].length) {
|
||||
continue;
|
||||
}
|
||||
loaded = true;
|
||||
|
||||
memcpy(omx->input_buffer->pBuffer, dev->run->hw_buffers[index].start, dev->run->hw_buffers[index].length);
|
||||
memcpy(omx->input_buffer->pBuffer, dev->run->hw_buffers[index].start + pos, slice_size);
|
||||
omx->input_buffer->nOffset = 0;
|
||||
omx->input_buffer->nFilledLen = dev->run->hw_buffers[index].length;
|
||||
omx->input_buffer->nFilledLen = slice_size;
|
||||
|
||||
pos += slice_size;
|
||||
|
||||
if (pos + slice_size > dev->run->hw_buffers[index].length) {
|
||||
slice_size = dev->run->hw_buffers[index].length - 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");
|
||||
@@ -295,23 +302,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;
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <IL/OMX_Component.h>
|
||||
#include <interface/vcos/vcos_semaphore.h>
|
||||
|
||||
#include "../device.h"
|
||||
#include "../../device.h"
|
||||
|
||||
|
||||
#define OMX_MAX_ENCODERS 3
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <IL/OMX_IVCommon.h>
|
||||
#include <IL/OMX_Core.h>
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../../tools.h"
|
||||
#include "formatters.h"
|
||||
|
||||
|
||||
@@ -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, ...) { \
|
||||
22
src/http.c
22
src/http.c
@@ -47,6 +47,7 @@
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "encoder.h"
|
||||
#include "stream.h"
|
||||
#include "http.h"
|
||||
|
||||
@@ -256,18 +257,29 @@ 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\", \"fallback\": %s, \"quality\": %u},"
|
||||
" \"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u},"
|
||||
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
|
||||
bool_to_string(server->run->stream->encoder->type != server->run->stream->encoder->run->type),
|
||||
server->run->stream->encoder->quality,
|
||||
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),
|
||||
@@ -276,6 +288,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 +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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
27
ustreamer.ebuild
Normal file
27
ustreamer.ebuild
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user