Compare commits

...

21 Commits
v0.10 ... v0.13

Author SHA1 Message Date
Devaev Maxim
4fc59de042 Bump version: 0.12 → 0.13 2018-09-30 06:48:41 +03:00
Devaev Maxim
494993fe39 refacotring 2018-09-30 06:45:27 +03:00
Devaev Maxim
e9ec65cfde report about stream clients 2018-09-30 06:27:58 +03:00
Devaev Maxim
ec2a704ca0 refactoring 2018-09-30 04:29:34 +03:00
Devaev Maxim
05b18fb68d Bump version: 0.11 → 0.12 2018-09-30 03:17:57 +03:00
Maxim Devaev
32120ae12d Update README.md 2018-09-30 03:17:11 +03:00
Maxim Devaev
386396dc84 Update README.md 2018-09-30 03:11:05 +03:00
Devaev Maxim
3c086c2129 refactoring 2018-09-30 02:52:17 +03:00
Devaev Maxim
fb9a8e31b1 --drop-same-frames to reduce the outgoing traffic 2018-09-30 02:49:11 +03:00
Devaev Maxim
0c192ddcfb Bump version: 0.10 → 0.11 2018-09-29 07:33:24 +03:00
Devaev Maxim
38b4246b9b refactoring 2018-09-29 07:33:11 +03:00
Maxim Devaev
a46ca0f702 Update README.md 2018-09-29 03:17:37 +03:00
Maxim Devaev
ddf290e811 Update README.md 2018-09-29 03:04:38 +03:00
Maxim Devaev
98b37cb48f Update README.md 2018-09-29 03:00:34 +03:00
Maxim Devaev
d7ab090a69 Update README.md 2018-09-28 20:29:42 +03:00
Maxim Devaev
1b160eb177 Update README.md 2018-09-28 20:18:20 +03:00
Maxim Devaev
fad5bec927 Update README.md 2018-09-28 19:32:27 +03:00
Maxim Devaev
ac036a04b5 Update README.md 2018-09-28 18:56:28 +03:00
Maxim Devaev
cf5d4b1d2a Update README.md 2018-09-28 18:52:58 +03:00
Maxim Devaev
102edd6880 Update README.md 2018-09-28 18:48:22 +03:00
Maxim Devaev
1e87410035 Create README.md 2018-09-28 18:06:05 +03:00
15 changed files with 245 additions and 91 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = 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]+))?)?
serialize =
{major}.{minor}

View File

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

79
README.md Normal file
View 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 | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении устройства<br>от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Необратимо зависает <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поддерживаемые входные форматы устройств | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) YUYV, UYVY,<br>RGB565, MJPG |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>4</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
Сносочки:
* ```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/.

View File

@@ -21,4 +21,4 @@
#pragma once
#define VERSION "0.10"
#define VERSION "0.13"

View File

@@ -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},
@@ -96,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;
@@ -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) {
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;
}

View File

@@ -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},
@@ -91,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;

View File

@@ -22,7 +22,6 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <assert.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_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 void _expose_blank_picture(struct http_server_t *server);
static bool _expose_new_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_runtime_t *run;
struct http_server_t *server;
struct exposed_t *exposed;
struct timeval refresh_interval;
A_CALLOC(exposed, 1);
A_CALLOC(run, 1);
run->stream = stream;
run->exposed = exposed;
run->refresh_interval.tv_sec = 0;
run->refresh_interval.tv_usec = 30000; // ~30 refreshes per second
run->drop_same_frames_blank = 10;
A_CALLOC(server, 1);
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, "/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(!event_add(run->refresh, &run->refresh_interval));
assert(!event_add(run->refresh, &refresh_interval));
return server;
}
@@ -108,12 +110,16 @@ void http_server_destroy(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);
evhttp_set_timeout(server->run->http, server->timeout);
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)
return -1;
}
LOG_INFO("Listening HTTP on [%s]:%d", server->host, server->port);
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) {
struct exposed_t *exposed = (struct exposed_t *)v_exposed;
struct evbuffer *buf;
struct timespec x_timestamp_spec;
char x_timestamp_buf[64];
PROCESS_HEAD_REQUEST;
@@ -189,17 +194,11 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_expo
assert((buf = evbuffer_new()));
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("Cache-Control", "no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0");
ADD_HEADER("Pragma", "no-cache");
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
sprintf(x_timestamp_buf, "%.06Lf", now_real_ms());
ADD_HEADER("X-Timestamp", x_timestamp_buf);
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 bufferevent *buf_event;
struct stream_client_t *client;
char *client_addr;
unsigned short client_port;
PROCESS_HEAD_REQUEST;
@@ -229,6 +230,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->server = server;
client->request = request;
client->need_initial = true;
client->need_first_frame = true;
if (server->run->stream_clients == NULL) {
server->run->stream_clients = client;
@@ -239,6 +241,13 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->prev = last;
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);
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) {
struct stream_client_t *client = (struct stream_client_t *)v_client;
struct evbuffer *buf;
struct timespec x_timestamp_spec;
assert((buf = evbuffer_new()));
assert(!clock_gettime(CLOCK_REALTIME, &x_timestamp_spec));
if (client->need_initial) {
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,
"Content-Type: image/jpeg" RN
"Content-Length: %lu" RN
"X-Timestamp: %u.%06u" RN
"X-Timestamp: %.06Lf" RN
RN,
client->server->run->exposed->picture.size * sizeof(*client->server->run->exposed->picture.data),
(unsigned)x_timestamp_spec.tv_sec,
(unsigned)(x_timestamp_spec.tv_nsec / 1000) // TODO: round?
now_real_ms()
));
assert(!evbuffer_add(buf,
(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) {
struct stream_client_t *client = (struct stream_client_t *)v_client;
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);
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) {
evhttp_connection_free(conn);
}
@@ -321,23 +338,26 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
free(client);
}
static void _http_queue_send_stream(struct http_server_t *server) {
static void _http_queue_send_stream(struct http_server_t *server, const bool updated) {
struct stream_client_t *client;
struct evhttp_connection *conn;
struct bufferevent *buf_event;
for (client = server->run->stream_clients; client != NULL; client = client->next) {
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);
bufferevent_setcb(buf_event, NULL, _http_callback_stream_write, _http_callback_stream_error, (void *)client);
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) {
struct http_server_t *server = (struct http_server_t *)v_server;
bool updated = false;
bool queue_send = false;
#define LOCK_STREAM \
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 ...");
LOCK_STREAM;
if (server->run->stream->picture.size > 0) { // If online
_expose_new_picture(server);
updated = _expose_new_picture(server);
UNLOCK_STREAM;
} else {
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) {
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 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) {
A_REALLOC(server->run->exposed->picture.data, server->run->stream->picture.allocated);
server->run->exposed->picture.allocated = server->run->stream->picture.allocated;
}
memcpy(
server->run->exposed->picture.data, server->run->stream->picture.data,
server->run->stream->picture.size * sizeof(*server->run->exposed->picture.data)
);
memcpy(MEM_STREAM_TO_EXPOSED);
# undef MEM_STREAM_TO_EXPOSED
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;
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->picture.allocated < 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->fps = 0;
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
}

View File

@@ -20,7 +20,6 @@
#include <stdbool.h>
#include <time.h>
#include <event2/event.h>
#include <event2/http.h>
@@ -33,6 +32,7 @@ struct stream_client_t {
struct http_server_t *server;
struct evhttp_request *request;
bool need_initial;
bool need_first_frame;
struct stream_client_t *prev;
struct stream_client_t *next;
@@ -44,6 +44,7 @@ struct exposed_t {
unsigned height;
unsigned fps;
bool online;
unsigned dropped;
};
struct http_server_runtime_t {
@@ -52,14 +53,15 @@ struct http_server_runtime_t {
struct event *refresh;
struct stream_t *stream;
struct exposed_t *exposed;
struct timeval refresh_interval;
struct stream_client_t *stream_clients;
unsigned stream_clients_count;
unsigned drop_same_frames_blank;
};
struct http_server_t {
char *host;
unsigned port;
unsigned drop_same_frames;
unsigned fake_width;
unsigned fake_height;
unsigned timeout;

View File

@@ -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;
@@ -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) {
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;
@@ -140,7 +137,7 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
while (jpeg->next_scanline < height) {
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 u = data[1] - 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) {
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 u = data[0] - 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) {
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];
*(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) {
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)(
@@ -237,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;
@@ -253,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
@@ -261,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

View File

@@ -71,7 +71,7 @@ pthread_mutex_t log_mutex;
#define LOG_ERROR(_x_msg, ...) { \
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; \
}
@@ -79,24 +79,24 @@ pthread_mutex_t log_mutex;
char _buf[1024] = ""; \
strerror_r(errno, _buf, 1024); \
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; \
}
#define LOG_INFO(_x_msg, ...) { \
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; \
}
#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, ...) { \
if (log_level >= LOG_LEVEL_PERF) { \
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; \
} \
}
@@ -104,7 +104,7 @@ pthread_mutex_t log_mutex;
#define LOG_VERBOSE(_x_msg, ...) { \
if (log_level >= LOG_LEVEL_VERBOSE) { \
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; \
} \
}
@@ -112,7 +112,7 @@ pthread_mutex_t log_mutex;
#define LOG_DEBUG(_x_msg, ...) { \
if (log_level >= LOG_LEVEL_DEBUG) { \
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; \
} \
}

View File

@@ -40,7 +40,7 @@
#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[] = {
{"device", required_argument, NULL, 'd'},
{"width", required_argument, NULL, 'x'},
@@ -62,6 +62,7 @@ static const struct option _long_opts[] = {
{"host", required_argument, NULL, 's'},
{"port", required_argument, NULL, 'p'},
{"drop-same-frames", required_argument, NULL, 'r'},
{"fake-width", required_argument, NULL, 2000},
{"fake-height", required_argument, NULL, 2001},
{"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(" --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(" --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-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);
@@ -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) \
{ errno = 0; int _tmp = strtol(optarg, NULL, 0); \
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; }
# 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 '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 2001: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
case 2002: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);

View File

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

View File

@@ -35,7 +35,7 @@
#define LOG_OMX_ERROR(_error, _msg, ...) { \
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)); \
LOGGING_UNLOCK; \
}

View File

@@ -169,8 +169,8 @@ void stream_loop(struct stream_t *stream) {
if (stream->dev->every_frame) {
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;
frames_count += 1;
LOG_DEBUG("Dropping frame %d for option --every-frame=%d", frames_count, stream->dev->every_frame);
goto pass_frame;
}
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) {
fluency_passed += 1;
@@ -202,7 +202,7 @@ void stream_loop(struct stream_t *stream) {
fps = 0;
fps_second = (long long)now;
}
++fps;
fps += 1;
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 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);
@@ -419,7 +419,7 @@ static void *_stream_worker_thread(void *v_ctx) {
*ctx->job_start_time = start_time;
*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);
*ctx->last_comp_time = last_comp_time;

View File

@@ -61,10 +61,10 @@ INLINE unsigned max_u(unsigned a, unsigned 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;
assert(!clock_gettime(CLOCK_MONOTONIC_RAW, &spec));
assert(!clock_gettime(clk_id, &spec));
*sec = spec.tv_sec;
*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;
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;
}