mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c192ddcfb | ||
|
|
38b4246b9b | ||
|
|
a46ca0f702 | ||
|
|
ddf290e811 | ||
|
|
98b37cb48f | ||
|
|
d7ab090a69 | ||
|
|
1b160eb177 | ||
|
|
fad5bec927 | ||
|
|
ac036a04b5 | ||
|
|
cf5d4b1d2a | ||
|
|
102edd6880 | ||
|
|
1e87410035 | ||
|
|
993ef4e884 | ||
|
|
ea5ec96874 | ||
|
|
d76c5da4de | ||
|
|
4c07d047b4 | ||
|
|
609fa89d09 | ||
|
|
ce26bd0a65 | ||
|
|
74699b63e7 | ||
|
|
43800a5ed6 | ||
|
|
0318f33479 | ||
|
|
892f87bb17 | ||
|
|
09cda92c9a | ||
|
|
2eb57866c2 | ||
|
|
962a32e877 | ||
|
|
3ddd1f603e | ||
|
|
18368a00f5 | ||
|
|
810a80ab09 | ||
|
|
f7e30b5897 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 0.2
|
||||
current_version = 0.11
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,8 @@
|
||||
/pkg/
|
||||
/src/ustreamer-*/
|
||||
/src/v*.tar.gz
|
||||
/v*.tar.gz
|
||||
/ustreamer-*.pkg.tar.xz
|
||||
/vgcore.*
|
||||
/ustreamer
|
||||
*.o
|
||||
|
||||
8
Makefile
8
Makefile
@@ -3,9 +3,11 @@ PREFIX ?= /usr/local
|
||||
CFLAGS ?= -O3
|
||||
LDFLAGS ?=
|
||||
|
||||
|
||||
# =====
|
||||
CC = gcc
|
||||
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads
|
||||
CFLAGS += -c -std=c99 -Wall -Wextra -D_GNU_SOURCE
|
||||
override CFLAGS += -c -std=c99 -Wall -Wextra -D_GNU_SOURCE
|
||||
SOURCES = $(shell ls src/*.c src/jpeg/*.c)
|
||||
OBJECTS = $(SOURCES:.c=.o)
|
||||
PROG = ustreamer
|
||||
@@ -14,10 +16,11 @@ PROG = ustreamer
|
||||
ifeq ($(shell ls -d /opt/vc/include 2>/dev/null), /opt/vc/include)
|
||||
SOURCES += $(shell ls src/omx/*.c)
|
||||
LIBS += -lbcm_host -lvcos -lopenmaxil -L/opt/vc/lib
|
||||
CFLAGS += -DOMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
|
||||
override CFLAGS += -DOMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
|
||||
endif
|
||||
|
||||
|
||||
# =====
|
||||
all: $(SOURCES) $(PROG)
|
||||
|
||||
|
||||
@@ -56,3 +59,4 @@ push:
|
||||
|
||||
clean:
|
||||
rm -f src/*.o src/{jpeg,omx}/*.o vgcore.* $(PROG)
|
||||
rm -rf pkg src/$(PROG)-* src/v*.tar.gz v*.tar.gz $(PROG)-*.pkg.tar.xz
|
||||
|
||||
2
PKGBUILD
2
PKGBUILD
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=0.2
|
||||
pkgver=0.11
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pi-kvm/ustreamer"
|
||||
|
||||
76
README.md
Normal file
76
README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# µStreamer
|
||||
|
||||
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pi-kvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
|
||||
|
||||
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
|
||||
|
||||
| **Фича** | **µStreamer** | **mjpg-streamer** |
|
||||
|----------|---------------|-------------------|
|
||||
| Многопоточное кодирование JPEG |  Есть |  Нет |
|
||||
| Аппаратное кодирование с помощью [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> |
|
||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
||||
| Поддерживаемые входные форматы устройств |  YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>2</sup> |  YUYV, UYVY,<br>RGB565, MJPG |
|
||||
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP |  Нет |  Есть |
|
||||
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации |  Нет <sup>3</sup> |  Есть |
|
||||
|
||||
Сносочки:
|
||||
* ```1``` Для mjpg-streamer существует [мой патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), предотвращающий зависание при отключении устройства и добавляющий поддержку DV-таймингов, однако трансляция при этом все равно прерывается. В данный момент этот патч не принят в апстрим, и я даже не гарантирую его стопроцентную работоспособность. Код mjpg-streamer очень плохо структурирован и чрезвычайно запутан, и я мог что-то упустить. Собственно, это одна из причин, почему µStreamer написан с нуля.
|
||||
|
||||
* ```2``` Поскольку µ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 источника, но у меня просто пока не дошли до этого руки.
|
||||
|
||||
* ```3``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
|
||||
|
||||
-----
|
||||
# TL;DR
|
||||
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
|
||||
|
||||
-----
|
||||
# Сборка
|
||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads``` и ```libjpeg8```/```libjpeg-turbo```.
|
||||
|
||||
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX, если обнаружит нужные хедеры в ```/opt/vc/include```.
|
||||
|
||||
```
|
||||
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
|
||||
$ cd ustreamer
|
||||
$ make
|
||||
$ ./ustreamer --help
|
||||
```
|
||||
|
||||
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer
|
||||
|
||||
-----
|
||||
# Использование
|
||||
Будучи запущенным без аргументов, ```ustremaer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://localhost:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80 порту:
|
||||
```
|
||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
||||
```
|
||||
|
||||
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
|
||||
```bash
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Настройка входного формата устройства
|
||||
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
|
||||
--dv-timings # Включение DV-таймингов
|
||||
```
|
||||
|
||||
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Лицензия
|
||||
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
@@ -21,4 +21,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION "0.2"
|
||||
#define VERSION "0.11"
|
||||
|
||||
35
src/device.c
35
src/device.c
@@ -39,7 +39,7 @@
|
||||
static const struct {
|
||||
const char *name;
|
||||
const v4l2_std_id standard;
|
||||
} STANDARDS[] = {
|
||||
} _STANDARDS[] = {
|
||||
{"UNKNOWN", V4L2_STD_UNKNOWN},
|
||||
{"PAL", V4L2_STD_PAL},
|
||||
{"NTSC", V4L2_STD_NTSC},
|
||||
@@ -49,7 +49,7 @@ static const struct {
|
||||
static const struct {
|
||||
const char *name;
|
||||
const unsigned format;
|
||||
} FORMATS[] = {
|
||||
} _FORMATS[] = {
|
||||
{"YUYV", V4L2_PIX_FMT_YUYV},
|
||||
{"UYVY", V4L2_PIX_FMT_UYVY},
|
||||
{"RGB565", V4L2_PIX_FMT_RGB565},
|
||||
@@ -84,7 +84,6 @@ struct device_t *device_init() {
|
||||
dev->standard = V4L2_STD_UNKNOWN;
|
||||
dev->n_buffers = max_u(sysconf(_SC_NPROCESSORS_ONLN), 1) + 1;
|
||||
dev->n_workers = dev->n_buffers;
|
||||
dev->jpeg_quality = 80;
|
||||
dev->timeout = 1;
|
||||
dev->error_timeout = 1;
|
||||
dev->run = run;
|
||||
@@ -97,18 +96,18 @@ void device_destroy(struct device_t *dev) {
|
||||
}
|
||||
|
||||
int device_parse_format(const char *const str) {
|
||||
for (unsigned index = 0; index < sizeof(FORMATS) / sizeof(FORMATS[0]); ++index) {
|
||||
if (!strcasecmp(str, FORMATS[index].name)) {
|
||||
return FORMATS[index].format;
|
||||
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
|
||||
if (!strcasecmp(str, _FORMATS[index].name)) {
|
||||
return _FORMATS[index].format;
|
||||
}
|
||||
}
|
||||
return FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
v4l2_std_id device_parse_standard(const char *const str) {
|
||||
for (unsigned index = 1; index < sizeof(STANDARDS) / sizeof(STANDARDS[0]); ++index) {
|
||||
if (!strcasecmp(str, STANDARDS[index].name)) {
|
||||
return STANDARDS[index].standard;
|
||||
for (unsigned index = 1; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
|
||||
if (!strcasecmp(str, _STANDARDS[index].name)) {
|
||||
return _STANDARDS[index].standard;
|
||||
}
|
||||
}
|
||||
return STANDARD_UNKNOWN;
|
||||
@@ -409,9 +408,9 @@ static void _device_open_alloc_picbufs(struct device_t *dev) {
|
||||
LOG_DEBUG("Allocating picture buffers ...");
|
||||
A_CALLOC(dev->run->pictures, dev->run->n_buffers);
|
||||
|
||||
dev->run->max_picture_size = (dev->run->width * dev->run->height) << 1;
|
||||
dev->run->max_picture_size = ((dev->run->width * dev->run->height) << 1) * 2;
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
LOG_DEBUG("Allocating picture buffer %d ...", index);
|
||||
LOG_DEBUG("Allocating picture buffer %d sized %lu bytes... ", index, dev->run->max_picture_size);
|
||||
A_CALLOC(dev->run->pictures[index].data, dev->run->max_picture_size);
|
||||
dev->run->pictures[index].allocated = dev->run->max_picture_size;
|
||||
}
|
||||
@@ -435,19 +434,19 @@ static const char *_format_to_string_auto(char *buf, const size_t size, const un
|
||||
}
|
||||
|
||||
static const char *_format_to_string_null(const unsigned format) {
|
||||
for (unsigned index = 0; index < sizeof(FORMATS) / sizeof(FORMATS[0]); ++index) {
|
||||
if (format == FORMATS[index].format) {
|
||||
return FORMATS[index].name;
|
||||
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
|
||||
if (format == _FORMATS[index].format) {
|
||||
return _FORMATS[index].name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *_standard_to_string(v4l2_std_id standard) {
|
||||
for (unsigned index = 0; index < sizeof(STANDARDS) / sizeof(STANDARDS[0]); ++index) {
|
||||
if (standard == STANDARDS[index].standard) {
|
||||
return STANDARDS[index].name;
|
||||
for (unsigned index = 0; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
|
||||
if (standard == _STANDARDS[index].standard) {
|
||||
return _STANDARDS[index].name;
|
||||
}
|
||||
}
|
||||
return STANDARDS[0].name;
|
||||
return _STANDARDS[0].name;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ struct device_t {
|
||||
unsigned n_workers;
|
||||
unsigned every_frame;
|
||||
unsigned min_frame_size;
|
||||
unsigned jpeg_quality;
|
||||
unsigned timeout;
|
||||
unsigned error_timeout;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
static const struct {
|
||||
const char *name;
|
||||
const enum encoder_type_t type;
|
||||
} ENCODER_TYPES[] = {
|
||||
} _ENCODER_TYPES[] = {
|
||||
{"CPU", ENCODER_TYPE_CPU},
|
||||
# ifdef OMX_ENCODER
|
||||
{"OMX", ENCODER_TYPE_OMX},
|
||||
@@ -50,6 +50,7 @@ struct encoder_t *encoder_init() {
|
||||
|
||||
A_CALLOC(encoder, 1);
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
encoder->quality = 80;
|
||||
return encoder;
|
||||
}
|
||||
|
||||
@@ -60,6 +61,8 @@ void encoder_prepare(struct encoder_t *encoder) {
|
||||
LOG_DEBUG("Initializing encoder ...");
|
||||
}
|
||||
|
||||
LOG_INFO("Using JPEG quality: %d%%", encoder->quality);
|
||||
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
if ((encoder->omx = omx_encoder_init()) == NULL) {
|
||||
@@ -88,9 +91,9 @@ void encoder_destroy(struct encoder_t *encoder) {
|
||||
}
|
||||
|
||||
enum encoder_type_t encoder_parse_type(const char *const str) {
|
||||
for (unsigned index = 0; index < sizeof(ENCODER_TYPES) / sizeof(ENCODER_TYPES[0]); ++index) {
|
||||
if (!strcasecmp(str, ENCODER_TYPES[index].name)) {
|
||||
return ENCODER_TYPES[index].type;
|
||||
for (unsigned index = 0; index < sizeof(_ENCODER_TYPES) / sizeof(_ENCODER_TYPES[0]); ++index) {
|
||||
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
|
||||
return _ENCODER_TYPES[index].type;
|
||||
}
|
||||
}
|
||||
return ENCODER_TYPE_UNKNOWN;
|
||||
@@ -104,7 +107,7 @@ void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev)
|
||||
#pragma GCC diagnostic pop
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
if (omx_encoder_prepare_for_device(encoder->omx, dev) < 0) {
|
||||
if (omx_encoder_prepare_for_device(encoder->omx, dev, encoder->quality, encoder->omx_use_ijg) < 0) {
|
||||
goto use_fallback;
|
||||
}
|
||||
if (dev->run->n_workers > 1) {
|
||||
@@ -125,11 +128,11 @@ void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev)
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, int index) {
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, const unsigned index) {
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
if (encoder->type == ENCODER_TYPE_CPU) {
|
||||
jpeg_encoder_compress_buffer(dev, index);
|
||||
jpeg_encoder_compress_buffer(dev, index, encoder->quality);
|
||||
}
|
||||
# ifdef OMX_ENCODER
|
||||
else if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
|
||||
@@ -39,17 +39,17 @@
|
||||
enum encoder_type_t {
|
||||
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
|
||||
ENCODER_TYPE_CPU,
|
||||
|
||||
#ifdef OMX_ENCODER
|
||||
ENCODER_TYPE_OMX,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct encoder_t {
|
||||
enum encoder_type_t type;
|
||||
|
||||
enum encoder_type_t type;
|
||||
unsigned quality;
|
||||
#ifdef OMX_ENCODER
|
||||
struct omx_encoder_t *omx;
|
||||
bool omx_use_ijg;
|
||||
struct omx_encoder_t *omx;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -61,4 +61,4 @@ enum encoder_type_t encoder_parse_type(const char *const str);
|
||||
|
||||
void encoder_prepare(struct encoder_t *encoder);
|
||||
void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev);
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, int index);
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, const unsigned index);
|
||||
|
||||
15
src/http.c
15
src/http.c
@@ -85,7 +85,7 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
||||
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD);
|
||||
|
||||
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, NULL));
|
||||
assert(!evhttp_set_cb(run->http, "/ping", _http_callback_ping, (void *)exposed));
|
||||
assert(!evhttp_set_cb(run->http, "/ping", _http_callback_ping, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)exposed));
|
||||
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
|
||||
|
||||
@@ -158,8 +158,8 @@ static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
static void _http_callback_ping(struct evhttp_request *request, void *v_exposed) {
|
||||
struct exposed_t *exposed = (struct exposed_t *)v_exposed;
|
||||
static void _http_callback_ping(struct evhttp_request *request, void *v_server) {
|
||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||
struct evbuffer *buf;
|
||||
|
||||
PROCESS_HEAD_REQUEST;
|
||||
@@ -168,9 +168,10 @@ static void _http_callback_ping(struct evhttp_request *request, void *v_exposed)
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"{\"stream\": {\"resolution\":"
|
||||
" {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s}}",
|
||||
exposed->width, exposed->height,
|
||||
(exposed->online ? "true" : "false")
|
||||
" \"fps\": %u, \"online\": %s}}",
|
||||
(server->fake_width ? server->fake_width : server->run->exposed->width),
|
||||
(server->fake_height ? server->fake_height : server->run->exposed->height),
|
||||
server->run->exposed->fps, (server->run->exposed->online ? "true" : "false")
|
||||
));
|
||||
ADD_HEADER("Content-Type", "application/json");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
@@ -378,6 +379,7 @@ void _expose_new_picture(struct http_server_t *server) {
|
||||
server->run->exposed->picture.size = server->run->stream->picture.size;
|
||||
server->run->exposed->width = server->run->stream->width;
|
||||
server->run->exposed->height = server->run->stream->height;
|
||||
server->run->exposed->fps = server->run->stream->fps;
|
||||
server->run->exposed->online = true;
|
||||
}
|
||||
|
||||
@@ -396,6 +398,7 @@ void _expose_blank_picture(struct http_server_t *server) {
|
||||
server->run->exposed->picture.size = BLANK_JPG_SIZE;
|
||||
server->run->exposed->width = BLANK_JPG_WIDTH;
|
||||
server->run->exposed->height = BLANK_JPG_HEIGHT;
|
||||
server->run->exposed->fps = 0;
|
||||
server->run->exposed->online = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ struct exposed_t {
|
||||
struct picture_t picture;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned fps;
|
||||
bool online;
|
||||
};
|
||||
|
||||
@@ -59,6 +60,8 @@ struct http_server_runtime_t {
|
||||
struct http_server_t {
|
||||
char *host;
|
||||
unsigned port;
|
||||
unsigned fake_width;
|
||||
unsigned fake_height;
|
||||
unsigned timeout;
|
||||
|
||||
struct http_server_runtime_t *run;
|
||||
|
||||
@@ -38,10 +38,7 @@
|
||||
#include "encoder.h"
|
||||
|
||||
|
||||
#define JPEG_OUTPUT_BUFFER_SIZE 4096
|
||||
|
||||
|
||||
struct mjpg_destination_mgr {
|
||||
struct _mjpg_destination_mgr {
|
||||
struct jpeg_destination_mgr mgr; // Default manager
|
||||
JOCTET *buffer; // Start of buffer
|
||||
unsigned char *outbuffer_cursor;
|
||||
@@ -68,7 +65,7 @@ static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
|
||||
static void _jpeg_term_destination(j_compress_ptr jpeg);
|
||||
|
||||
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, int index) {
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, const unsigned quality) {
|
||||
// This function based on compress_image_to_jpeg() from mjpg-streamer
|
||||
|
||||
struct jpeg_compress_struct jpeg;
|
||||
@@ -89,7 +86,7 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, int index) {
|
||||
jpeg.in_color_space = JCS_RGB;
|
||||
|
||||
jpeg_set_defaults(&jpeg);
|
||||
jpeg_set_quality(&jpeg, dev->jpeg_quality, TRUE);
|
||||
jpeg_set_quality(&jpeg, quality, TRUE);
|
||||
|
||||
jpeg_start_compress(&jpeg, TRUE);
|
||||
|
||||
@@ -110,18 +107,19 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, int index) {
|
||||
jpeg_destroy_compress(&jpeg);
|
||||
free(line_buffer);
|
||||
assert(dev->run->pictures[index].size > 0);
|
||||
assert(dev->run->pictures[index].size <= dev->run->max_picture_size);
|
||||
}
|
||||
|
||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written) {
|
||||
struct mjpg_destination_mgr *dest;
|
||||
struct _mjpg_destination_mgr *dest;
|
||||
|
||||
if (jpeg->dest == NULL) {
|
||||
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct mjpg_destination_mgr)
|
||||
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct _mjpg_destination_mgr)
|
||||
)));
|
||||
}
|
||||
|
||||
dest = (struct mjpg_destination_mgr *) jpeg->dest;
|
||||
dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
dest->mgr.init_destination = _jpeg_init_destination;
|
||||
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
|
||||
dest->mgr.term_destination = _jpeg_term_destination;
|
||||
@@ -221,8 +219,10 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
|
||||
}
|
||||
}
|
||||
|
||||
#define JPEG_OUTPUT_BUFFER_SIZE 4096
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
|
||||
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
|
||||
// Allocate the output buffer - it will be released when done with image
|
||||
assert((dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
|
||||
@@ -236,7 +236,7 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
|
||||
// Called whenever local jpeg buffer fills up
|
||||
|
||||
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
|
||||
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
|
||||
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
|
||||
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
|
||||
@@ -252,7 +252,7 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
|
||||
// Called by jpeg_finish_compress after all data has been written.
|
||||
// Usually needs to flush buffer
|
||||
|
||||
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
|
||||
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
size_t data_count = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
|
||||
|
||||
// Write any data remaining in the buffer
|
||||
@@ -260,3 +260,5 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
|
||||
dest->outbuffer_cursor += data_count;
|
||||
*dest->written += data_count;
|
||||
}
|
||||
|
||||
#undef JPEG_OUTPUT_BUFFER_SIZE
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
#include "../device.h"
|
||||
|
||||
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, int index);
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, const unsigned quality);
|
||||
|
||||
54
src/main.c
54
src/main.c
@@ -52,22 +52,29 @@ static const struct option _long_opts[] = {
|
||||
{"dv-timings", no_argument, NULL, 't'},
|
||||
{"buffers", required_argument, NULL, 'b'},
|
||||
{"workers", required_argument, NULL, 'w'},
|
||||
{"jpeg-quality", required_argument, NULL, 'q'},
|
||||
{"quality", required_argument, NULL, 'q'},
|
||||
{"encoder", required_argument, NULL, 'c'},
|
||||
# ifdef OMX_ENCODER
|
||||
{"encoder-omx-use-ijg", required_argument, NULL, 500},
|
||||
# endif
|
||||
{"device-timeout", required_argument, NULL, 1000},
|
||||
{"device-error-timeout", required_argument, NULL, 1001},
|
||||
|
||||
{"host", required_argument, NULL, 's'},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
{"server-timeout", required_argument, NULL, 2000},
|
||||
{"fake-width", required_argument, NULL, 2000},
|
||||
{"fake-height", required_argument, NULL, 2001},
|
||||
{"server-timeout", required_argument, NULL, 2002},
|
||||
|
||||
{"debug", no_argument, NULL, 5000},
|
||||
{"log-level", required_argument, NULL, 5001},
|
||||
{"perf", no_argument, NULL, 5000},
|
||||
{"verbose", no_argument, NULL, 5001},
|
||||
{"debug", no_argument, NULL, 5002},
|
||||
{"log-level", required_argument, NULL, 5010},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, 0},
|
||||
};
|
||||
|
||||
static void _help(struct device_t *dev, struct http_server_t *server) {
|
||||
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
|
||||
printf("\nuStreamer - Lightweight and fast MJPG-HTTP streamer\n");
|
||||
printf("===================================================\n\n");
|
||||
printf("Version: %s; license: GPLv3\n", VERSION);
|
||||
@@ -90,9 +97,13 @@ static void _help(struct device_t *dev, struct http_server_t *server) {
|
||||
printf(" Each buffer may processed using an intermediate thread.\n");
|
||||
printf(" Default: %d (number of CPU cores + 1)\n\n", dev->n_buffers);
|
||||
printf(" -w|--workers <N> -- The number of compressing threads. Default: %d (== --buffers).\n\n", dev->n_workers);
|
||||
printf(" -q|--jpeg-quality <N> -- Set quality of JPEG encoding from 1 to 100 (best). Default: %d\n\n", dev->jpeg_quality);
|
||||
printf(" -q|--quality <N> -- Set quality of JPEG encoding from 1 to 100 (best). Default: %d.\n\n", encoder->quality);
|
||||
printf(" --encoder <type> -- Use specified encoder. It may affects to workers number.\n");
|
||||
printf(" -- Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
|
||||
# ifdef OMX_ENCODER
|
||||
printf(" --encoder-omx-use-ijg -- Use the standard IJG quality tables when encoding images using OMX.\n");
|
||||
printf(" Default: disabled.\n\n");
|
||||
# endif
|
||||
printf(" --device-timeout <seconds> -- Timeout for device querying. Default: %d\n\n", dev->timeout);
|
||||
printf(" --device-error-timeout <seconds> -- Delay before trying to connect to the device again\n");
|
||||
printf(" after a timeout. Default: %d\n\n", dev->error_timeout);
|
||||
@@ -100,15 +111,19 @@ static void _help(struct device_t *dev, struct http_server_t *server) {
|
||||
printf("--------------------\n");
|
||||
printf(" --host <address> -- Listen on Hostname or IP. Default: %s\n\n", server->host);
|
||||
printf(" --port <N> -- Bind to this TCP port. Default: %d\n\n", server->port);
|
||||
printf(" --fake-width <N> -- Override image width for /ping. Default: disabled\n\n");
|
||||
printf(" --fake-height <N> -- Override image height for /ping. Default: disabled.\n\n");
|
||||
printf(" --server-timeout <seconds> -- Timeout for client connections. Default: %d\n\n", server->timeout);
|
||||
printf("Misc options:\n");
|
||||
printf("-------------\n");
|
||||
printf(" --debug -- Enabled debug messages (same as --log-level=3). Default: disabled.\n\n");
|
||||
printf(" --log-level <N> -- Verbosity level of messages from 0 (info) to 3 (debug).\n");
|
||||
printf(" Enabling debugging messages can slow down the program.\n");
|
||||
printf(" Available levels: 0=info, 1=performance, 2=verbose, 3=debug.\n");
|
||||
printf(" Default: %d\n\n", log_level);
|
||||
printf(" -h|--help -- Print this messages and exit\n\n");
|
||||
printf(" Default: %d.\n\n", log_level);
|
||||
printf(" --perf -- Enable performance messages (same as log-level=1). Default: disabled.\n\n");
|
||||
printf(" --verbose -- Enable verbose messages and lower (same as log-level=2). Default: disabled.\n\n");
|
||||
printf(" --debug -- Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");
|
||||
printf(" -h|--help -- Print this messages and exit.\n\n");
|
||||
}
|
||||
|
||||
static int _parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
|
||||
@@ -148,19 +163,26 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
case 't': OPT_TRUE(dev->dv_timings);
|
||||
case 'b': OPT_UNSIGNED(dev->n_buffers, "--buffers", 1, 32);
|
||||
case 'w': OPT_UNSIGNED(dev->n_workers, "--workers", 1, 32);
|
||||
case 'q': OPT_UNSIGNED(dev->jpeg_quality, "--jpeg-quality", 1, 100);
|
||||
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type")
|
||||
case 'q': OPT_UNSIGNED(encoder->quality, "--quality", 1, 100);
|
||||
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type");
|
||||
# ifdef OMX_ENCODER
|
||||
case 500: encoder->omx_use_ijg = true; break;
|
||||
# endif
|
||||
case 1000: OPT_UNSIGNED(dev->timeout, "--timeout", 1, 60);
|
||||
case 1001: OPT_UNSIGNED(dev->error_timeout, "--error-timeout", 1, 60);
|
||||
|
||||
case 's': server->host = optarg; break;
|
||||
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535);
|
||||
case 2000: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
||||
case 2000: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
||||
case 2001: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
|
||||
case 2002: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
||||
|
||||
case 5000: log_level = LOG_LEVEL_DEBUG; break;
|
||||
case 5001: OPT_UNSIGNED(log_level, "--log-level", 0, 3);
|
||||
case 5000: log_level = LOG_LEVEL_PERF; break;
|
||||
case 5001: log_level = LOG_LEVEL_VERBOSE; break;
|
||||
case 5002: log_level = LOG_LEVEL_DEBUG; break;
|
||||
case 5010: OPT_UNSIGNED(log_level, "--log-level", 0, 3);
|
||||
case 0: break;
|
||||
case 'h': default: _help(dev, server); return -1;
|
||||
case 'h': default: _help(dev, encoder, server); return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +255,7 @@ int main(int argc, char *argv[]) {
|
||||
LOGGING_INIT;
|
||||
|
||||
dev = device_init();
|
||||
encoder = encoder_init(ENCODER_TYPE_CPU);
|
||||
encoder = encoder_init();
|
||||
stream = stream_init(dev, encoder);
|
||||
server = http_server_init(stream);
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
|
||||
int component_enable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
|
||||
int component_disable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
|
||||
|
||||
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, const OMX_U32 port);
|
||||
int component_set_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef);
|
||||
|
||||
int component_set_state(OMX_HANDLETYPE *component, const OMX_STATETYPE state);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
@@ -28,6 +29,7 @@
|
||||
#include <bcm_host.h>
|
||||
#include <IL/OMX_Core.h>
|
||||
#include <IL/OMX_Component.h>
|
||||
#include <IL/OMX_Broadcom.h>
|
||||
#include <interface/vcos/vcos_semaphore.h>
|
||||
|
||||
#include "../logging.h"
|
||||
@@ -46,7 +48,7 @@
|
||||
static int _omx_init_component(struct omx_encoder_t *omx);
|
||||
static int _omx_init_disable_ports(struct omx_encoder_t *omx);
|
||||
static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev);
|
||||
static int _omx_setup_output(struct omx_encoder_t *omx, struct device_t *dev);
|
||||
static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality, const bool use_ijg);
|
||||
static int _omx_encoder_clear_ports(struct omx_encoder_t *omx);
|
||||
|
||||
static OMX_ERRORTYPE _omx_event_handler(UNUSED OMX_HANDLETYPE encoder,
|
||||
@@ -134,7 +136,7 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
|
||||
free(omx);
|
||||
}
|
||||
|
||||
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev) {
|
||||
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality, const bool use_ijg) {
|
||||
if (component_set_state(&omx->encoder, OMX_StateIdle) < 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -144,7 +146,7 @@ int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *d
|
||||
if (_omx_setup_input(omx, dev) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (_omx_setup_output(omx, dev) < 0) {
|
||||
if (_omx_setup_output(omx, quality, use_ijg) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (component_set_state(&omx->encoder, OMX_StateExecuting) < 0) {
|
||||
@@ -153,7 +155,7 @@ int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *d
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, int index) {
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, const unsigned index) {
|
||||
OMX_ERRORTYPE error;
|
||||
bool loaded = false;
|
||||
|
||||
@@ -179,6 +181,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
omx->output_buffer->pBuffer,
|
||||
omx->output_buffer->nFilledLen
|
||||
);
|
||||
assert(dev->run->pictures[index].size + omx->output_buffer->nFilledLen <= dev->run->max_picture_size);
|
||||
dev->run->pictures[index].size += omx->output_buffer->nFilledLen;
|
||||
|
||||
if (omx->output_buffer->nFlags & OMX_BUFFERFLAG_ENDOFFRAME) {
|
||||
@@ -215,6 +218,8 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
}
|
||||
|
||||
static int _omx_init_component(struct omx_encoder_t *omx) {
|
||||
// http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html
|
||||
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
OMX_CALLBACKTYPE callbacks;
|
||||
@@ -312,10 +317,9 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _omx_setup_output(struct omx_encoder_t *omx, struct device_t *dev) {
|
||||
static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality, const bool use_ijg) {
|
||||
OMX_ERRORTYPE error;
|
||||
OMX_PARAM_PORTDEFINITIONTYPE portdef;
|
||||
OMX_IMAGE_PARAM_QFACTORTYPE quality_factor;
|
||||
|
||||
LOG_DEBUG("Setting up OMX JPEG output port ...");
|
||||
|
||||
@@ -337,13 +341,42 @@ static int _omx_setup_output(struct omx_encoder_t *omx, struct device_t *dev) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
OMX_INIT_STRUCTURE(quality_factor);
|
||||
quality_factor.nPortIndex = OUTPUT_PORT;
|
||||
quality_factor.nQFactor = dev->jpeg_quality;
|
||||
{
|
||||
OMX_CONFIG_BOOLEANTYPE exif;
|
||||
|
||||
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamQFactor, &quality_factor)) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Can't set OMX JPEG quality");
|
||||
return -1;
|
||||
OMX_INIT_STRUCTURE(exif);
|
||||
exif.bEnabled = OMX_FALSE;
|
||||
|
||||
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamBrcmDisableEXIF, &exif)) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Can't disable EXIF on OMX JPEG");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_ijg) {
|
||||
OMX_PARAM_IJGSCALINGTYPE ijg;
|
||||
|
||||
OMX_INIT_STRUCTURE(ijg);
|
||||
ijg.nPortIndex = OUTPUT_PORT;
|
||||
ijg.bEnabled = OMX_TRUE;
|
||||
|
||||
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamBrcmEnableIJGTableScaling, &ijg)) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Can't set OMX JPEG IJG settings");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
OMX_IMAGE_PARAM_QFACTORTYPE qfactor;
|
||||
|
||||
OMX_INIT_STRUCTURE(qfactor);
|
||||
qfactor.nPortIndex = OUTPUT_PORT;
|
||||
qfactor.nQFactor = quality;
|
||||
|
||||
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamQFactor, &qfactor)) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Can't set OMX JPEG quality");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (component_enable_port(&omx->encoder, OUTPUT_PORT) < 0) {
|
||||
|
||||
@@ -49,5 +49,5 @@ struct omx_encoder_t {
|
||||
struct omx_encoder_t *omx_encoder_init();
|
||||
void omx_encoder_destroy(struct omx_encoder_t *omx);
|
||||
|
||||
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev);
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, int index);
|
||||
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality, const bool use_ijg);
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, const unsigned index);
|
||||
|
||||
14
src/stream.c
14
src/stream.c
@@ -78,7 +78,6 @@ void stream_loop(struct stream_t *stream) {
|
||||
pool.workers_stop = &workers_stop;
|
||||
|
||||
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
|
||||
LOG_INFO("Using JPEG quality: %d%%", stream->dev->jpeg_quality);
|
||||
|
||||
while (_stream_init_loop(stream->dev, &pool) == 0) {
|
||||
struct worker_t *oldest_worker = NULL;
|
||||
@@ -197,14 +196,13 @@ void stream_loop(struct stream_t *stream) {
|
||||
}
|
||||
fluency_passed = 0;
|
||||
|
||||
if (log_level >= LOG_LEVEL_PERF) {
|
||||
if ((long long)now != fps_second) {
|
||||
LOG_PERF("Oldest worker complete, encoding FPS = %u", fps);
|
||||
fps = 0;
|
||||
fps_second = (long long)now;
|
||||
}
|
||||
++fps;
|
||||
if ((long long)now != fps_second) {
|
||||
LOG_PERF("Oldest worker complete, encoding FPS = %u", fps);
|
||||
stream->fps = fps;
|
||||
fps = 0;
|
||||
fps_second = (long long)now;
|
||||
}
|
||||
++fps;
|
||||
|
||||
long double fluency_delay = _stream_get_fluency_delay(stream->dev, &pool);
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ struct stream_t {
|
||||
struct picture_t picture;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned fps;
|
||||
bool updated;
|
||||
pthread_mutex_t mutex;
|
||||
struct device_t *dev;
|
||||
|
||||
Reference in New Issue
Block a user