mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 12:16:31 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fc59de042 | ||
|
|
494993fe39 | ||
|
|
e9ec65cfde | ||
|
|
ec2a704ca0 | ||
|
|
05b18fb68d | ||
|
|
32120ae12d | ||
|
|
386396dc84 | ||
|
|
3c086c2129 | ||
|
|
fb9a8e31b1 | ||
|
|
0c192ddcfb | ||
|
|
38b4246b9b | ||
|
|
a46ca0f702 | ||
|
|
ddf290e811 | ||
|
|
98b37cb48f | ||
|
|
d7ab090a69 | ||
|
|
1b160eb177 | ||
|
|
fad5bec927 | ||
|
|
ac036a04b5 | ||
|
|
cf5d4b1d2a | ||
|
|
102edd6880 | ||
|
|
1e87410035 |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 0.10
|
current_version = 0.13
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|||||||
2
PKGBUILD
2
PKGBUILD
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=0.10
|
pkgver=0.13
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||||
url="https://github.com/pi-kvm/ustreamer"
|
url="https://github.com/pi-kvm/ustreamer"
|
||||||
|
|||||||
79
README.md
Normal file
79
README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# µ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>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
||||||
|
| Дебаг-логи без перекомпиляции,<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> |  Есть |
|
||||||
|
|
||||||
|
Сносочки:
|
||||||
|
* ```1``` Для mjpg-streamer существует [мой патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), предотвращающий зависание при отключении устройства и добавляющий поддержку DV-таймингов, однако трансляция при этом все равно прерывается. В данный момент этот патч не принят в апстрим, и я даже не гарантирую его стопроцентную работоспособность. Код mjpg-streamer очень плохо структурирован и чрезвычайно запутан, и я мог что-то упустить. Собственно, это одна из причин, почему µStreamer написан с нуля.
|
||||||
|
|
||||||
|
* ```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.
|
||||||
|
|
||||||
|
-----
|
||||||
|
# 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
|
#pragma once
|
||||||
|
|
||||||
#define VERSION "0.10"
|
#define VERSION "0.13"
|
||||||
|
|||||||
30
src/device.c
30
src/device.c
@@ -39,7 +39,7 @@
|
|||||||
static const struct {
|
static const struct {
|
||||||
const char *name;
|
const char *name;
|
||||||
const v4l2_std_id standard;
|
const v4l2_std_id standard;
|
||||||
} STANDARDS[] = {
|
} _STANDARDS[] = {
|
||||||
{"UNKNOWN", V4L2_STD_UNKNOWN},
|
{"UNKNOWN", V4L2_STD_UNKNOWN},
|
||||||
{"PAL", V4L2_STD_PAL},
|
{"PAL", V4L2_STD_PAL},
|
||||||
{"NTSC", V4L2_STD_NTSC},
|
{"NTSC", V4L2_STD_NTSC},
|
||||||
@@ -49,7 +49,7 @@ static const struct {
|
|||||||
static const struct {
|
static const struct {
|
||||||
const char *name;
|
const char *name;
|
||||||
const unsigned format;
|
const unsigned format;
|
||||||
} FORMATS[] = {
|
} _FORMATS[] = {
|
||||||
{"YUYV", V4L2_PIX_FMT_YUYV},
|
{"YUYV", V4L2_PIX_FMT_YUYV},
|
||||||
{"UYVY", V4L2_PIX_FMT_UYVY},
|
{"UYVY", V4L2_PIX_FMT_UYVY},
|
||||||
{"RGB565", V4L2_PIX_FMT_RGB565},
|
{"RGB565", V4L2_PIX_FMT_RGB565},
|
||||||
@@ -96,18 +96,18 @@ void device_destroy(struct device_t *dev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int device_parse_format(const char *const str) {
|
int device_parse_format(const char *const str) {
|
||||||
for (unsigned index = 0; index < sizeof(FORMATS) / sizeof(FORMATS[0]); ++index) {
|
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
|
||||||
if (!strcasecmp(str, FORMATS[index].name)) {
|
if (!strcasecmp(str, _FORMATS[index].name)) {
|
||||||
return FORMATS[index].format;
|
return _FORMATS[index].format;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return FORMAT_UNKNOWN;
|
return FORMAT_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
v4l2_std_id device_parse_standard(const char *const str) {
|
v4l2_std_id device_parse_standard(const char *const str) {
|
||||||
for (unsigned index = 1; index < sizeof(STANDARDS) / sizeof(STANDARDS[0]); ++index) {
|
for (unsigned index = 1; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
|
||||||
if (!strcasecmp(str, STANDARDS[index].name)) {
|
if (!strcasecmp(str, _STANDARDS[index].name)) {
|
||||||
return STANDARDS[index].standard;
|
return _STANDARDS[index].standard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return STANDARD_UNKNOWN;
|
return STANDARD_UNKNOWN;
|
||||||
@@ -434,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) {
|
static const char *_format_to_string_null(const unsigned format) {
|
||||||
for (unsigned index = 0; index < sizeof(FORMATS) / sizeof(FORMATS[0]); ++index) {
|
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
|
||||||
if (format == FORMATS[index].format) {
|
if (format == _FORMATS[index].format) {
|
||||||
return FORMATS[index].name;
|
return _FORMATS[index].name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *_standard_to_string(v4l2_std_id standard) {
|
static const char *_standard_to_string(v4l2_std_id standard) {
|
||||||
for (unsigned index = 0; index < sizeof(STANDARDS) / sizeof(STANDARDS[0]); ++index) {
|
for (unsigned index = 0; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
|
||||||
if (standard == STANDARDS[index].standard) {
|
if (standard == _STANDARDS[index].standard) {
|
||||||
return STANDARDS[index].name;
|
return _STANDARDS[index].name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return STANDARDS[0].name;
|
return _STANDARDS[0].name;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
static const struct {
|
static const struct {
|
||||||
const char *name;
|
const char *name;
|
||||||
const enum encoder_type_t type;
|
const enum encoder_type_t type;
|
||||||
} ENCODER_TYPES[] = {
|
} _ENCODER_TYPES[] = {
|
||||||
{"CPU", ENCODER_TYPE_CPU},
|
{"CPU", ENCODER_TYPE_CPU},
|
||||||
# ifdef OMX_ENCODER
|
# ifdef OMX_ENCODER
|
||||||
{"OMX", ENCODER_TYPE_OMX},
|
{"OMX", ENCODER_TYPE_OMX},
|
||||||
@@ -91,9 +91,9 @@ void encoder_destroy(struct encoder_t *encoder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum encoder_type_t encoder_parse_type(const char *const str) {
|
enum encoder_type_t encoder_parse_type(const char *const str) {
|
||||||
for (unsigned index = 0; index < sizeof(ENCODER_TYPES) / sizeof(ENCODER_TYPES[0]); ++index) {
|
for (unsigned index = 0; index < sizeof(_ENCODER_TYPES) / sizeof(_ENCODER_TYPES[0]); ++index) {
|
||||||
if (!strcasecmp(str, ENCODER_TYPES[index].name)) {
|
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
|
||||||
return ENCODER_TYPES[index].type;
|
return _ENCODER_TYPES[index].type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ENCODER_TYPE_UNKNOWN;
|
return ENCODER_TYPE_UNKNOWN;
|
||||||
|
|||||||
122
src/http.c
122
src/http.c
@@ -22,7 +22,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
@@ -52,24 +51,24 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
|
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
|
||||||
|
|
||||||
static void _http_exposed_refresh(int fd, short event, void *v_server);
|
static void _http_exposed_refresh(int fd, short event, void *v_server);
|
||||||
static void _http_queue_send_stream(struct http_server_t *server);
|
static void _http_queue_send_stream(struct http_server_t *server, const bool updated);
|
||||||
|
|
||||||
static void _expose_new_picture(struct http_server_t *server);
|
static bool _expose_new_picture(struct http_server_t *server);
|
||||||
static void _expose_blank_picture(struct http_server_t *server);
|
static bool _expose_blank_picture(struct http_server_t *server);
|
||||||
|
|
||||||
|
|
||||||
struct http_server_t *http_server_init(struct stream_t *stream) {
|
struct http_server_t *http_server_init(struct stream_t *stream) {
|
||||||
struct http_server_runtime_t *run;
|
struct http_server_runtime_t *run;
|
||||||
struct http_server_t *server;
|
struct http_server_t *server;
|
||||||
struct exposed_t *exposed;
|
struct exposed_t *exposed;
|
||||||
|
struct timeval refresh_interval;
|
||||||
|
|
||||||
A_CALLOC(exposed, 1);
|
A_CALLOC(exposed, 1);
|
||||||
|
|
||||||
A_CALLOC(run, 1);
|
A_CALLOC(run, 1);
|
||||||
run->stream = stream;
|
run->stream = stream;
|
||||||
run->exposed = exposed;
|
run->exposed = exposed;
|
||||||
run->refresh_interval.tv_sec = 0;
|
run->drop_same_frames_blank = 10;
|
||||||
run->refresh_interval.tv_usec = 30000; // ~30 refreshes per second
|
|
||||||
|
|
||||||
A_CALLOC(server, 1);
|
A_CALLOC(server, 1);
|
||||||
server->host = "localhost";
|
server->host = "localhost";
|
||||||
@@ -89,8 +88,11 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
|||||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)exposed));
|
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)exposed));
|
||||||
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
|
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
|
||||||
|
|
||||||
|
refresh_interval.tv_sec = 0;
|
||||||
|
refresh_interval.tv_usec = 30000; // ~30 refreshes per second
|
||||||
assert((run->refresh = event_new(run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
|
assert((run->refresh = event_new(run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
|
||||||
assert(!event_add(run->refresh, &run->refresh_interval));
|
assert(!event_add(run->refresh, &refresh_interval));
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +110,16 @@ void http_server_destroy(struct http_server_t *server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int http_server_listen(struct http_server_t *server) {
|
int http_server_listen(struct http_server_t *server) {
|
||||||
|
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
|
||||||
|
|
||||||
LOG_DEBUG("Binding HTTP to [%s]:%d ...", server->host, server->port);
|
LOG_DEBUG("Binding HTTP to [%s]:%d ...", server->host, server->port);
|
||||||
evhttp_set_timeout(server->run->http, server->timeout);
|
evhttp_set_timeout(server->run->http, server->timeout);
|
||||||
|
|
||||||
if (evhttp_bind_socket(server->run->http, server->host, server->port) != 0) {
|
if (evhttp_bind_socket(server->run->http, server->host, server->port) != 0) {
|
||||||
LOG_PERROR("Can't listen HTTP on [%s]:%d", server->host, server->port)
|
LOG_PERROR("Can't listen HTTP on [%s]:%d", server->host, server->port)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("Listening HTTP on [%s]:%d", server->host, server->port);
|
LOG_INFO("Listening HTTP on [%s]:%d", server->host, server->port);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -181,7 +187,6 @@ static void _http_callback_ping(struct evhttp_request *request, void *v_server)
|
|||||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_exposed) {
|
static void _http_callback_snapshot(struct evhttp_request *request, void *v_exposed) {
|
||||||
struct exposed_t *exposed = (struct exposed_t *)v_exposed;
|
struct exposed_t *exposed = (struct exposed_t *)v_exposed;
|
||||||
struct evbuffer *buf;
|
struct evbuffer *buf;
|
||||||
struct timespec x_timestamp_spec;
|
|
||||||
char x_timestamp_buf[64];
|
char x_timestamp_buf[64];
|
||||||
|
|
||||||
PROCESS_HEAD_REQUEST;
|
PROCESS_HEAD_REQUEST;
|
||||||
@@ -189,17 +194,11 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_expo
|
|||||||
assert((buf = evbuffer_new()));
|
assert((buf = evbuffer_new()));
|
||||||
assert(!evbuffer_add(buf, (const void *)exposed->picture.data, exposed->picture.size));
|
assert(!evbuffer_add(buf, (const void *)exposed->picture.data, exposed->picture.size));
|
||||||
|
|
||||||
assert(!clock_gettime(CLOCK_REALTIME, &x_timestamp_spec));
|
|
||||||
sprintf(
|
|
||||||
x_timestamp_buf, "%u.%06u",
|
|
||||||
(unsigned)x_timestamp_spec.tv_sec,
|
|
||||||
(unsigned)(x_timestamp_spec.tv_nsec / 1000) // TODO: round?
|
|
||||||
);
|
|
||||||
|
|
||||||
ADD_HEADER("Access-Control-Allow-Origin:", "*");
|
ADD_HEADER("Access-Control-Allow-Origin:", "*");
|
||||||
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0");
|
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0");
|
||||||
ADD_HEADER("Pragma", "no-cache");
|
ADD_HEADER("Pragma", "no-cache");
|
||||||
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
||||||
|
sprintf(x_timestamp_buf, "%.06Lf", now_real_ms());
|
||||||
ADD_HEADER("X-Timestamp", x_timestamp_buf);
|
ADD_HEADER("X-Timestamp", x_timestamp_buf);
|
||||||
ADD_HEADER("Content-Type", "image/jpeg");
|
ADD_HEADER("Content-Type", "image/jpeg");
|
||||||
|
|
||||||
@@ -220,6 +219,8 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
struct evhttp_connection *conn;
|
struct evhttp_connection *conn;
|
||||||
struct bufferevent *buf_event;
|
struct bufferevent *buf_event;
|
||||||
struct stream_client_t *client;
|
struct stream_client_t *client;
|
||||||
|
char *client_addr;
|
||||||
|
unsigned short client_port;
|
||||||
|
|
||||||
PROCESS_HEAD_REQUEST;
|
PROCESS_HEAD_REQUEST;
|
||||||
|
|
||||||
@@ -229,6 +230,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
client->server = server;
|
client->server = server;
|
||||||
client->request = request;
|
client->request = request;
|
||||||
client->need_initial = true;
|
client->need_initial = true;
|
||||||
|
client->need_first_frame = true;
|
||||||
|
|
||||||
if (server->run->stream_clients == NULL) {
|
if (server->run->stream_clients == NULL) {
|
||||||
server->run->stream_clients = client;
|
server->run->stream_clients = client;
|
||||||
@@ -239,6 +241,13 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
client->prev = last;
|
client->prev = last;
|
||||||
last->next = client;
|
last->next = client;
|
||||||
}
|
}
|
||||||
|
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; clients now: %u",
|
||||||
|
client_addr, client_port, server->run->stream_clients_count
|
||||||
|
);
|
||||||
|
|
||||||
buf_event = evhttp_connection_get_bufferevent(conn);
|
buf_event = evhttp_connection_get_bufferevent(conn);
|
||||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||||
@@ -256,10 +265,8 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
||||||
struct stream_client_t *client = (struct stream_client_t *)v_client;
|
struct stream_client_t *client = (struct stream_client_t *)v_client;
|
||||||
struct evbuffer *buf;
|
struct evbuffer *buf;
|
||||||
struct timespec x_timestamp_spec;
|
|
||||||
|
|
||||||
assert((buf = evbuffer_new()));
|
assert((buf = evbuffer_new()));
|
||||||
assert(!clock_gettime(CLOCK_REALTIME, &x_timestamp_spec));
|
|
||||||
|
|
||||||
if (client->need_initial) {
|
if (client->need_initial) {
|
||||||
assert(evbuffer_add_printf(buf,
|
assert(evbuffer_add_printf(buf,
|
||||||
@@ -279,11 +286,10 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
assert(evbuffer_add_printf(buf,
|
assert(evbuffer_add_printf(buf,
|
||||||
"Content-Type: image/jpeg" RN
|
"Content-Type: image/jpeg" RN
|
||||||
"Content-Length: %lu" RN
|
"Content-Length: %lu" RN
|
||||||
"X-Timestamp: %u.%06u" RN
|
"X-Timestamp: %.06Lf" RN
|
||||||
RN,
|
RN,
|
||||||
client->server->run->exposed->picture.size * sizeof(*client->server->run->exposed->picture.data),
|
client->server->run->exposed->picture.size * sizeof(*client->server->run->exposed->picture.data),
|
||||||
(unsigned)x_timestamp_spec.tv_sec,
|
now_real_ms()
|
||||||
(unsigned)(x_timestamp_spec.tv_nsec / 1000) // TODO: round?
|
|
||||||
));
|
));
|
||||||
assert(!evbuffer_add(buf,
|
assert(!evbuffer_add(buf,
|
||||||
(void *)client->server->run->exposed->picture.data,
|
(void *)client->server->run->exposed->picture.data,
|
||||||
@@ -304,8 +310,19 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
|
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
|
||||||
struct stream_client_t *client = (struct stream_client_t *)v_client;
|
struct stream_client_t *client = (struct stream_client_t *)v_client;
|
||||||
struct evhttp_connection *conn;
|
struct evhttp_connection *conn;
|
||||||
|
char *client_addr = "???";
|
||||||
|
unsigned short client_port = 0;
|
||||||
|
|
||||||
|
client->server->run->stream_clients_count -= 1;
|
||||||
|
|
||||||
conn = evhttp_request_get_connection(client->request);
|
conn = evhttp_request_get_connection(client->request);
|
||||||
|
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
|
||||||
|
);
|
||||||
if (conn != NULL) {
|
if (conn != NULL) {
|
||||||
evhttp_connection_free(conn);
|
evhttp_connection_free(conn);
|
||||||
}
|
}
|
||||||
@@ -321,23 +338,26 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
|||||||
free(client);
|
free(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_queue_send_stream(struct http_server_t *server) {
|
static void _http_queue_send_stream(struct http_server_t *server, const bool updated) {
|
||||||
struct stream_client_t *client;
|
struct stream_client_t *client;
|
||||||
struct evhttp_connection *conn;
|
struct evhttp_connection *conn;
|
||||||
struct bufferevent *buf_event;
|
struct bufferevent *buf_event;
|
||||||
|
|
||||||
for (client = server->run->stream_clients; client != NULL; client = client->next) {
|
for (client = server->run->stream_clients; client != NULL; client = client->next) {
|
||||||
conn = evhttp_request_get_connection(client->request);
|
conn = evhttp_request_get_connection(client->request);
|
||||||
if (conn != NULL) {
|
if (conn != NULL && (updated || client->need_first_frame)) {
|
||||||
buf_event = evhttp_connection_get_bufferevent(conn);
|
buf_event = evhttp_connection_get_bufferevent(conn);
|
||||||
bufferevent_setcb(buf_event, NULL, _http_callback_stream_write, _http_callback_stream_error, (void *)client);
|
bufferevent_setcb(buf_event, NULL, _http_callback_stream_write, _http_callback_stream_error, (void *)client);
|
||||||
bufferevent_enable(buf_event, EV_READ|EV_WRITE);
|
bufferevent_enable(buf_event, EV_READ|EV_WRITE);
|
||||||
|
client->need_first_frame = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_server) {
|
static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||||
|
bool updated = false;
|
||||||
|
bool queue_send = false;
|
||||||
|
|
||||||
#define LOCK_STREAM \
|
#define LOCK_STREAM \
|
||||||
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
|
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
|
||||||
@@ -349,41 +369,66 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
|
|||||||
LOG_DEBUG("Refreshing HTTP exposed ...");
|
LOG_DEBUG("Refreshing HTTP exposed ...");
|
||||||
LOCK_STREAM;
|
LOCK_STREAM;
|
||||||
if (server->run->stream->picture.size > 0) { // If online
|
if (server->run->stream->picture.size > 0) { // If online
|
||||||
_expose_new_picture(server);
|
updated = _expose_new_picture(server);
|
||||||
UNLOCK_STREAM;
|
UNLOCK_STREAM;
|
||||||
} else {
|
} else {
|
||||||
UNLOCK_STREAM;
|
UNLOCK_STREAM;
|
||||||
_expose_blank_picture(server);
|
updated = _expose_blank_picture(server);
|
||||||
}
|
}
|
||||||
_http_queue_send_stream(server);
|
queue_send = true;
|
||||||
} else if (!server->run->exposed->online) {
|
} else if (!server->run->exposed->online) {
|
||||||
LOG_DEBUG("Refreshing HTTP exposed (BLANK) ...");
|
LOG_DEBUG("Refreshing HTTP exposed (BLANK) ...");
|
||||||
_http_queue_send_stream(server);
|
updated = _expose_blank_picture(server);
|
||||||
|
queue_send = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queue_send) {
|
||||||
|
_http_queue_send_stream(server, updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
# undef LOCK_STREAM
|
# undef LOCK_STREAM
|
||||||
# undef UNLOCK_STREAM
|
# undef UNLOCK_STREAM
|
||||||
}
|
}
|
||||||
|
|
||||||
void _expose_new_picture(struct http_server_t *server) {
|
static bool _expose_new_picture(struct http_server_t *server) {
|
||||||
|
assert(server->run->stream->picture.size > 0);
|
||||||
|
server->run->exposed->fps = server->run->stream->fps;
|
||||||
|
|
||||||
|
# define MEM_STREAM_TO_EXPOSED \
|
||||||
|
server->run->exposed->picture.data, server->run->stream->picture.data, \
|
||||||
|
server->run->stream->picture.size * sizeof(*server->run->exposed->picture.data)
|
||||||
|
|
||||||
|
if (server->drop_same_frames) {
|
||||||
|
if (
|
||||||
|
server->run->exposed->online
|
||||||
|
&& server->run->exposed->dropped < server->drop_same_frames
|
||||||
|
&& server->run->exposed->picture.size == server->run->stream->picture.size
|
||||||
|
&& !memcmp(MEM_STREAM_TO_EXPOSED)
|
||||||
|
) {
|
||||||
|
LOG_PERF("HTTP: dropped same frame number %u", server->run->exposed->dropped);
|
||||||
|
server->run->exposed->dropped += 1;
|
||||||
|
return false; // Not updated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (server->run->exposed->picture.allocated < server->run->stream->picture.allocated) {
|
if (server->run->exposed->picture.allocated < server->run->stream->picture.allocated) {
|
||||||
A_REALLOC(server->run->exposed->picture.data, server->run->stream->picture.allocated);
|
A_REALLOC(server->run->exposed->picture.data, server->run->stream->picture.allocated);
|
||||||
server->run->exposed->picture.allocated = server->run->stream->picture.allocated;
|
server->run->exposed->picture.allocated = server->run->stream->picture.allocated;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(
|
memcpy(MEM_STREAM_TO_EXPOSED);
|
||||||
server->run->exposed->picture.data, server->run->stream->picture.data,
|
|
||||||
server->run->stream->picture.size * sizeof(*server->run->exposed->picture.data)
|
# undef MEM_STREAM_TO_EXPOSED
|
||||||
);
|
|
||||||
|
|
||||||
server->run->exposed->picture.size = server->run->stream->picture.size;
|
server->run->exposed->picture.size = server->run->stream->picture.size;
|
||||||
server->run->exposed->width = server->run->stream->width;
|
server->run->exposed->width = server->run->stream->width;
|
||||||
server->run->exposed->height = server->run->stream->height;
|
server->run->exposed->height = server->run->stream->height;
|
||||||
server->run->exposed->fps = server->run->stream->fps;
|
|
||||||
server->run->exposed->online = true;
|
server->run->exposed->online = true;
|
||||||
|
server->run->exposed->dropped = 0;
|
||||||
|
return true; // Updated
|
||||||
}
|
}
|
||||||
|
|
||||||
void _expose_blank_picture(struct http_server_t *server) {
|
static bool _expose_blank_picture(struct http_server_t *server) {
|
||||||
if (server->run->exposed->online || server->run->exposed->picture.size == 0) {
|
if (server->run->exposed->online || server->run->exposed->picture.size == 0) {
|
||||||
if (server->run->exposed->picture.allocated < BLANK_JPG_SIZE) {
|
if (server->run->exposed->picture.allocated < BLANK_JPG_SIZE) {
|
||||||
A_REALLOC(server->run->exposed->picture.data, BLANK_JPG_SIZE);
|
A_REALLOC(server->run->exposed->picture.data, BLANK_JPG_SIZE);
|
||||||
@@ -400,5 +445,16 @@ void _expose_blank_picture(struct http_server_t *server) {
|
|||||||
server->run->exposed->height = BLANK_JPG_HEIGHT;
|
server->run->exposed->height = BLANK_JPG_HEIGHT;
|
||||||
server->run->exposed->fps = 0;
|
server->run->exposed->fps = 0;
|
||||||
server->run->exposed->online = false;
|
server->run->exposed->online = false;
|
||||||
|
goto updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (server->run->exposed->dropped < server->run->drop_same_frames_blank) {
|
||||||
|
LOG_PERF("HTTP: dropped same frame (BLANK) number %u", server->run->exposed->dropped);
|
||||||
|
server->run->exposed->dropped += 1;
|
||||||
|
return false; // Not updated
|
||||||
|
}
|
||||||
|
|
||||||
|
updated:
|
||||||
|
server->run->exposed->dropped = 0;
|
||||||
|
return true; // Updated
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
#include <event2/http.h>
|
#include <event2/http.h>
|
||||||
@@ -33,6 +32,7 @@ struct stream_client_t {
|
|||||||
struct http_server_t *server;
|
struct http_server_t *server;
|
||||||
struct evhttp_request *request;
|
struct evhttp_request *request;
|
||||||
bool need_initial;
|
bool need_initial;
|
||||||
|
bool need_first_frame;
|
||||||
|
|
||||||
struct stream_client_t *prev;
|
struct stream_client_t *prev;
|
||||||
struct stream_client_t *next;
|
struct stream_client_t *next;
|
||||||
@@ -44,6 +44,7 @@ struct exposed_t {
|
|||||||
unsigned height;
|
unsigned height;
|
||||||
unsigned fps;
|
unsigned fps;
|
||||||
bool online;
|
bool online;
|
||||||
|
unsigned dropped;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct http_server_runtime_t {
|
struct http_server_runtime_t {
|
||||||
@@ -52,14 +53,15 @@ struct http_server_runtime_t {
|
|||||||
struct event *refresh;
|
struct event *refresh;
|
||||||
struct stream_t *stream;
|
struct stream_t *stream;
|
||||||
struct exposed_t *exposed;
|
struct exposed_t *exposed;
|
||||||
struct timeval refresh_interval;
|
|
||||||
|
|
||||||
struct stream_client_t *stream_clients;
|
struct stream_client_t *stream_clients;
|
||||||
|
unsigned stream_clients_count;
|
||||||
|
unsigned drop_same_frames_blank;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct http_server_t {
|
struct http_server_t {
|
||||||
char *host;
|
char *host;
|
||||||
unsigned port;
|
unsigned port;
|
||||||
|
unsigned drop_same_frames;
|
||||||
unsigned fake_width;
|
unsigned fake_width;
|
||||||
unsigned fake_height;
|
unsigned fake_height;
|
||||||
unsigned timeout;
|
unsigned timeout;
|
||||||
|
|||||||
@@ -38,10 +38,7 @@
|
|||||||
#include "encoder.h"
|
#include "encoder.h"
|
||||||
|
|
||||||
|
|
||||||
#define JPEG_OUTPUT_BUFFER_SIZE 4096
|
struct _mjpg_destination_mgr {
|
||||||
|
|
||||||
|
|
||||||
struct mjpg_destination_mgr {
|
|
||||||
struct jpeg_destination_mgr mgr; // Default manager
|
struct jpeg_destination_mgr mgr; // Default manager
|
||||||
JOCTET *buffer; // Start of buffer
|
JOCTET *buffer; // Start of buffer
|
||||||
unsigned char *outbuffer_cursor;
|
unsigned char *outbuffer_cursor;
|
||||||
@@ -114,15 +111,15 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, unsigned long *written) {
|
||||||
struct mjpg_destination_mgr *dest;
|
struct _mjpg_destination_mgr *dest;
|
||||||
|
|
||||||
if (jpeg->dest == NULL) {
|
if (jpeg->dest == NULL) {
|
||||||
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
|
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.init_destination = _jpeg_init_destination;
|
||||||
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
|
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
|
||||||
dest->mgr.term_destination = _jpeg_term_destination;
|
dest->mgr.term_destination = _jpeg_term_destination;
|
||||||
@@ -140,7 +137,7 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
|
|||||||
while (jpeg->next_scanline < height) {
|
while (jpeg->next_scanline < height) {
|
||||||
unsigned char *ptr = line_buffer;
|
unsigned char *ptr = line_buffer;
|
||||||
|
|
||||||
for (unsigned x = 0; x < width; x++) {
|
for (unsigned x = 0; x < width; ++x) {
|
||||||
int y = (!z ? data[0] << 8 : data[2] << 8);
|
int y = (!z ? data[0] << 8 : data[2] << 8);
|
||||||
int u = data[1] - 128;
|
int u = data[1] - 128;
|
||||||
int v = data[3] - 128;
|
int v = data[3] - 128;
|
||||||
@@ -174,7 +171,7 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
|
|||||||
while(jpeg->next_scanline < height) {
|
while(jpeg->next_scanline < height) {
|
||||||
unsigned char *ptr = line_buffer;
|
unsigned char *ptr = line_buffer;
|
||||||
|
|
||||||
for(unsigned x = 0; x < width; x++) {
|
for(unsigned x = 0; x < width; ++x) {
|
||||||
int y = (!z ? data[1] << 8 : data[3] << 8);
|
int y = (!z ? data[1] << 8 : data[3] << 8);
|
||||||
int u = data[0] - 128;
|
int u = data[0] - 128;
|
||||||
int v = data[2] - 128;
|
int v = data[2] - 128;
|
||||||
@@ -207,7 +204,7 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
|
|||||||
while(jpeg->next_scanline < height) {
|
while(jpeg->next_scanline < height) {
|
||||||
unsigned char *ptr = line_buffer;
|
unsigned char *ptr = line_buffer;
|
||||||
|
|
||||||
for(unsigned x = 0; x < width; x++) {
|
for(unsigned x = 0; x < width; ++x) {
|
||||||
unsigned int two_byte = (data[1] << 8) + data[0];
|
unsigned int two_byte = (data[1] << 8) + data[0];
|
||||||
|
|
||||||
*(ptr++) = data[1] & 248;
|
*(ptr++) = data[1] & 248;
|
||||||
@@ -222,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) {
|
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
|
// Allocate the output buffer - it will be released when done with image
|
||||||
assert((dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
|
assert((dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
|
||||||
@@ -237,7 +236,7 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
|||||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
|
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
|
||||||
// Called whenever local jpeg buffer fills up
|
// 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);
|
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
|
||||||
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
|
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
|
||||||
@@ -253,7 +252,7 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
|
|||||||
// Called by jpeg_finish_compress after all data has been written.
|
// Called by jpeg_finish_compress after all data has been written.
|
||||||
// Usually needs to flush buffer
|
// 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;
|
size_t data_count = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
|
||||||
|
|
||||||
// Write any data remaining in the buffer
|
// Write any data remaining in the buffer
|
||||||
@@ -261,3 +260,5 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
|
|||||||
dest->outbuffer_cursor += data_count;
|
dest->outbuffer_cursor += data_count;
|
||||||
*dest->written += data_count;
|
*dest->written += data_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef JPEG_OUTPUT_BUFFER_SIZE
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ pthread_mutex_t log_mutex;
|
|||||||
|
|
||||||
#define LOG_ERROR(_x_msg, ...) { \
|
#define LOG_ERROR(_x_msg, ...) { \
|
||||||
LOGGING_LOCK; \
|
LOGGING_LOCK; \
|
||||||
printf("-- ERROR [%.03Lf tid=%ld] -- " _x_msg "\n", now_ms_ld(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
printf("-- ERROR [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||||
LOGGING_UNLOCK; \
|
LOGGING_UNLOCK; \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,24 +79,24 @@ pthread_mutex_t log_mutex;
|
|||||||
char _buf[1024] = ""; \
|
char _buf[1024] = ""; \
|
||||||
strerror_r(errno, _buf, 1024); \
|
strerror_r(errno, _buf, 1024); \
|
||||||
LOGGING_LOCK; \
|
LOGGING_LOCK; \
|
||||||
printf("-- ERROR [%.03Lf tid=%ld] -- " _x_msg ": %s\n", now_ms_ld(), syscall(SYS_gettid), ##__VA_ARGS__, _buf); \
|
printf("-- ERROR [%.03Lf tid=%ld] -- " _x_msg ": %s\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__, _buf); \
|
||||||
LOGGING_UNLOCK; \
|
LOGGING_UNLOCK; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define LOG_INFO(_x_msg, ...) { \
|
#define LOG_INFO(_x_msg, ...) { \
|
||||||
LOGGING_LOCK; \
|
LOGGING_LOCK; \
|
||||||
printf("-- INFO [%.03Lf tid=%ld] -- " _x_msg "\n", now_ms_ld(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
printf("-- INFO [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||||
LOGGING_UNLOCK; \
|
LOGGING_UNLOCK; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define LOG_INFO_NOLOCK(_x_msg, ...) { \
|
#define LOG_INFO_NOLOCK(_x_msg, ...) { \
|
||||||
printf("-- INFO [%.03Lf tid=%ld] -- " _x_msg "\n", now_ms_ld(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
printf("-- INFO [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define LOG_PERF(_x_msg, ...) { \
|
#define LOG_PERF(_x_msg, ...) { \
|
||||||
if (log_level >= LOG_LEVEL_PERF) { \
|
if (log_level >= LOG_LEVEL_PERF) { \
|
||||||
LOGGING_LOCK; \
|
LOGGING_LOCK; \
|
||||||
printf("-- PERF [%.03Lf tid=%ld] -- " _x_msg "\n", now_ms_ld(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
printf("-- PERF [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||||
LOGGING_UNLOCK; \
|
LOGGING_UNLOCK; \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ pthread_mutex_t log_mutex;
|
|||||||
#define LOG_VERBOSE(_x_msg, ...) { \
|
#define LOG_VERBOSE(_x_msg, ...) { \
|
||||||
if (log_level >= LOG_LEVEL_VERBOSE) { \
|
if (log_level >= LOG_LEVEL_VERBOSE) { \
|
||||||
LOGGING_LOCK; \
|
LOGGING_LOCK; \
|
||||||
printf("-- VERB [%.03Lf tid=%ld] -- " _x_msg "\n", now_ms_ld(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
printf("-- VERB [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||||
LOGGING_UNLOCK; \
|
LOGGING_UNLOCK; \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ pthread_mutex_t log_mutex;
|
|||||||
#define LOG_DEBUG(_x_msg, ...) { \
|
#define LOG_DEBUG(_x_msg, ...) { \
|
||||||
if (log_level >= LOG_LEVEL_DEBUG) { \
|
if (log_level >= LOG_LEVEL_DEBUG) { \
|
||||||
LOGGING_LOCK; \
|
LOGGING_LOCK; \
|
||||||
printf("-- DEBUG [%.03Lf tid=%ld] -- " _x_msg "\n", now_ms_ld(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
printf("-- DEBUG [%.03Lf tid=%ld] -- " _x_msg "\n", now_monotonic_ms(), syscall(SYS_gettid), ##__VA_ARGS__); \
|
||||||
LOGGING_UNLOCK; \
|
LOGGING_UNLOCK; \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/main.c
10
src/main.c
@@ -40,7 +40,7 @@
|
|||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
|
||||||
|
|
||||||
static const char _short_opts[] = "d:x:y:f:a:e:z:tn:w:q:c:s:p:h";
|
static const char _short_opts[] = "d:x:y:f:a:e:z:tn:w:q:c:s:p:r:h";
|
||||||
static const struct option _long_opts[] = {
|
static const struct option _long_opts[] = {
|
||||||
{"device", required_argument, NULL, 'd'},
|
{"device", required_argument, NULL, 'd'},
|
||||||
{"width", required_argument, NULL, 'x'},
|
{"width", required_argument, NULL, 'x'},
|
||||||
@@ -62,6 +62,7 @@ static const struct option _long_opts[] = {
|
|||||||
|
|
||||||
{"host", required_argument, NULL, 's'},
|
{"host", required_argument, NULL, 's'},
|
||||||
{"port", required_argument, NULL, 'p'},
|
{"port", required_argument, NULL, 'p'},
|
||||||
|
{"drop-same-frames", required_argument, NULL, 'r'},
|
||||||
{"fake-width", required_argument, NULL, 2000},
|
{"fake-width", required_argument, NULL, 2000},
|
||||||
{"fake-height", required_argument, NULL, 2001},
|
{"fake-height", required_argument, NULL, 2001},
|
||||||
{"server-timeout", required_argument, NULL, 2002},
|
{"server-timeout", required_argument, NULL, 2002},
|
||||||
@@ -111,6 +112,10 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
|||||||
printf("--------------------\n");
|
printf("--------------------\n");
|
||||||
printf(" --host <address> -- Listen on Hostname or IP. Default: %s\n\n", server->host);
|
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(" --port <N> -- Bind to this TCP port. Default: %d\n\n", server->port);
|
||||||
|
printf(" --drop-same-frames <N> -- Don't send same frames to clients, but no more than specified number.\n");
|
||||||
|
printf(" It can significantly reduce the outgoing traffic, but will increase\n");
|
||||||
|
printf(" the CPU loading. Don't use this option with analog signal sources\n");
|
||||||
|
printf(" or webcams, it's useless. Default: disabled.\n\n");
|
||||||
printf(" --fake-width <N> -- Override image width for /ping. Default: disabled\n\n");
|
printf(" --fake-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(" --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(" --server-timeout <seconds> -- Timeout for client connections. Default: %d\n\n", server->timeout);
|
||||||
@@ -136,7 +141,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
|||||||
# define OPT_UNSIGNED(_dest, _name, _min, _max) \
|
# define OPT_UNSIGNED(_dest, _name, _min, _max) \
|
||||||
{ errno = 0; int _tmp = strtol(optarg, NULL, 0); \
|
{ errno = 0; int _tmp = strtol(optarg, NULL, 0); \
|
||||||
if (errno || _tmp < _min || _tmp > _max) \
|
if (errno || _tmp < _min || _tmp > _max) \
|
||||||
{ printf("Invalid value for '%s=%u'; minimal=%u; maximum=%u\n", _name, _tmp, _min, _max); return -1; } \
|
{ printf("Invalid value for '%s=%u'; min=%u; max=%u\n", _name, _tmp, _min, _max); return -1; } \
|
||||||
_dest = _tmp; break; }
|
_dest = _tmp; break; }
|
||||||
|
|
||||||
# define OPT_PARSE(_dest, _func, _invalid, _name) \
|
# define OPT_PARSE(_dest, _func, _invalid, _name) \
|
||||||
@@ -173,6 +178,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
|||||||
|
|
||||||
case 's': server->host = optarg; break;
|
case 's': server->host = optarg; break;
|
||||||
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535);
|
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535);
|
||||||
|
case 'r': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
|
||||||
case 2000: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
case 2000: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
||||||
case 2001: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
|
case 2001: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
|
||||||
case 2002: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
case 2002: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
||||||
|
|||||||
@@ -40,6 +40,8 @@
|
|||||||
|
|
||||||
int component_enable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
|
int component_enable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
|
||||||
int component_disable_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_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_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef);
|
||||||
|
|
||||||
int component_set_state(OMX_HANDLETYPE *component, const OMX_STATETYPE state);
|
int component_set_state(OMX_HANDLETYPE *component, const OMX_STATETYPE state);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
#define LOG_OMX_ERROR(_error, _msg, ...) { \
|
#define LOG_OMX_ERROR(_error, _msg, ...) { \
|
||||||
LOGGING_LOCK; \
|
LOGGING_LOCK; \
|
||||||
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", now_ms_ld(), \
|
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", now_monotonic_ms(), \
|
||||||
syscall(SYS_gettid), ##__VA_ARGS__, omx_error_to_string(_error)); \
|
syscall(SYS_gettid), ##__VA_ARGS__, omx_error_to_string(_error)); \
|
||||||
LOGGING_UNLOCK; \
|
LOGGING_UNLOCK; \
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/stream.c
12
src/stream.c
@@ -169,8 +169,8 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
|
|
||||||
if (stream->dev->every_frame) {
|
if (stream->dev->every_frame) {
|
||||||
if (frames_count < stream->dev->every_frame - 1) {
|
if (frames_count < stream->dev->every_frame - 1) {
|
||||||
LOG_DEBUG("Dropping frame %d for option --every-frame=%d", frames_count + 1, stream->dev->every_frame);
|
frames_count += 1;
|
||||||
++frames_count;
|
LOG_DEBUG("Dropping frame %d for option --every-frame=%d", frames_count, stream->dev->every_frame);
|
||||||
goto pass_frame;
|
goto pass_frame;
|
||||||
}
|
}
|
||||||
frames_count = 0;
|
frames_count = 0;
|
||||||
@@ -187,7 +187,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
long double now = now_ms_ld();
|
long double now = now_monotonic_ms();
|
||||||
|
|
||||||
if (now < grab_after) {
|
if (now < grab_after) {
|
||||||
fluency_passed += 1;
|
fluency_passed += 1;
|
||||||
@@ -202,7 +202,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
fps = 0;
|
fps = 0;
|
||||||
fps_second = (long long)now;
|
fps_second = (long long)now;
|
||||||
}
|
}
|
||||||
++fps;
|
fps += 1;
|
||||||
|
|
||||||
long double fluency_delay = _stream_get_fluency_delay(stream->dev, &pool);
|
long double fluency_delay = _stream_get_fluency_delay(stream->dev, &pool);
|
||||||
|
|
||||||
@@ -407,7 +407,7 @@ static void *_stream_worker_thread(void *v_ctx) {
|
|||||||
long double start_time;
|
long double start_time;
|
||||||
long double last_comp_time;
|
long double last_comp_time;
|
||||||
|
|
||||||
start_time = now_ms_ld();
|
start_time = now_monotonic_ms();
|
||||||
|
|
||||||
LOG_DEBUG("Worker %u compressing JPEG from buffer %d ...", ctx->number, ctx->buf_index);
|
LOG_DEBUG("Worker %u compressing JPEG from buffer %d ...", ctx->number, ctx->buf_index);
|
||||||
|
|
||||||
@@ -419,7 +419,7 @@ static void *_stream_worker_thread(void *v_ctx) {
|
|||||||
*ctx->job_start_time = start_time;
|
*ctx->job_start_time = start_time;
|
||||||
*ctx->has_job = false;
|
*ctx->has_job = false;
|
||||||
|
|
||||||
last_comp_time = now_ms_ld() - start_time;
|
last_comp_time = now_monotonic_ms() - start_time;
|
||||||
|
|
||||||
A_PTHREAD_M_LOCK(ctx->last_comp_time_mutex);
|
A_PTHREAD_M_LOCK(ctx->last_comp_time_mutex);
|
||||||
*ctx->last_comp_time = last_comp_time;
|
*ctx->last_comp_time = last_comp_time;
|
||||||
|
|||||||
16
src/tools.h
16
src/tools.h
@@ -61,10 +61,10 @@ INLINE unsigned max_u(unsigned a, unsigned b) {
|
|||||||
return (a > b ? a : b);
|
return (a > b ? a : b);
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE void now_ms(time_t *sec, long *msec) {
|
INLINE void now_ms(clockid_t clk_id, time_t *sec, long *msec) {
|
||||||
struct timespec spec;
|
struct timespec spec;
|
||||||
|
|
||||||
assert(!clock_gettime(CLOCK_MONOTONIC_RAW, &spec));
|
assert(!clock_gettime(clk_id, &spec));
|
||||||
*sec = spec.tv_sec;
|
*sec = spec.tv_sec;
|
||||||
*msec = round(spec.tv_nsec / 1.0e6);
|
*msec = round(spec.tv_nsec / 1.0e6);
|
||||||
|
|
||||||
@@ -74,10 +74,18 @@ INLINE void now_ms(time_t *sec, long *msec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE long double now_ms_ld(void) {
|
INLINE long double now_monotonic_ms(void) {
|
||||||
time_t sec;
|
time_t sec;
|
||||||
long msec;
|
long msec;
|
||||||
|
|
||||||
now_ms(&sec, &msec);
|
now_ms(CLOCK_MONOTONIC_RAW, &sec, &msec);
|
||||||
|
return (long double)sec + ((long double)msec) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
INLINE long double now_real_ms(void) {
|
||||||
|
time_t sec;
|
||||||
|
long msec;
|
||||||
|
|
||||||
|
now_ms(CLOCK_REALTIME, &sec, &msec);
|
||||||
return (long double)sec + ((long double)msec) / 1000;
|
return (long double)sec + ((long double)msec) / 1000;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user