Compare commits

...

8 Commits
v0.19 ... v0.22

Author SHA1 Message Date
Devaev Maxim
281e1b36ce Bump version: 0.21 → 0.22 2018-10-08 16:33:16 +03:00
Devaev Maxim
160ad5c10f updated dependencies info 2018-10-08 15:39:33 +03:00
Devaev Maxim
667137638b Bump version: 0.20 → 0.21 2018-10-07 11:32:18 +03:00
Devaev Maxim
b5dffa927a changed /ping format 2018-10-07 11:32:07 +03:00
Devaev Maxim
651a82d1ca Bump version: 0.19 → 0.20 2018-10-06 01:17:38 +03:00
Devaev Maxim
d963c95af8 refactoring 2018-10-06 01:12:41 +03:00
Devaev Maxim
7c524a1196 calculating client fps 2018-10-06 00:50:30 +03:00
Maxim Devaev
0c85aad5a2 Update README.md 2018-10-05 11:12:38 +03:00
8 changed files with 59 additions and 29 deletions

View File

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

View File

@@ -6,7 +6,7 @@ LDFLAGS ?=
# =====
CC = gcc
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
override CFLAGS += -c -std=c99 -Wall -Wextra -D_GNU_SOURCE
SOURCES = $(shell ls src/*.c src/jpeg/*.c)
OBJECTS = $(SOURCES:.c=.o)

View File

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

View File

@@ -17,9 +17,9 @@
| Возможность сервить файлы встроенным<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 был написан с нуля.
* ```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()```.
* ```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 источника, но у меня просто пока не дошли до этого руки.
@@ -31,9 +31,9 @@
-----
# Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads``` и ```libjpeg8```/```libjpeg-turbo```.
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX, если обнаружит нужные хедеры в ```/opt/vc/include```.
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer

View File

@@ -21,4 +21,4 @@
#pragma once
#define VERSION "0.19"
#define VERSION "0.22"

View File

@@ -30,6 +30,8 @@
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <uuid/uuid.h>
#ifndef EVTHREAD_USE_PTHREADS_IMPLEMENTED
# error Required libevent-pthreads support
#endif
@@ -172,17 +174,25 @@ static void _http_callback_ping(struct evhttp_request *request, void *v_server)
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf,
"{\"stream\": {\"resolution\":"
" {\"width\": %u, \"height\": %u},"
" \"captured_fps\": %u, \"queued_fps\": %u,"
" \"online\": %s, \"clients\": %u}}",
"{\"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
" \"online\": %s, \"quality\": %u, \"captured_fps\": %u},"
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
(server->fake_width ? server->fake_width : server->run->exposed->width),
(server->fake_height ? server->fake_height : server->run->exposed->height),
(server->run->exposed->online ? "true" : "false"),
server->run->stream->encoder->quality,
server->run->exposed->captured_fps,
server->run->exposed->queued_fps,
(server->run->exposed->online ? "true" : "false"),
server->run->stream_clients_count
));
for (struct stream_client_t * client = server->run->stream_clients; client != NULL; client = client->next) {
assert(evbuffer_add_printf(buf,
"\"%s\": {\"fps\": %u}%s",
client->id, client->fps, (client->next ? ", " : "")
));
}
assert(evbuffer_add_printf(buf, "}}}"));
ADD_HEADER("Content-Type", "application/json");
evhttp_send_reply(request, HTTP_OK, "OK", buf);
evbuffer_free(buf);
@@ -244,6 +254,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
struct stream_client_t *client;
char *client_addr;
unsigned short client_port;
uuid_t uuid;
PROCESS_HEAD_REQUEST;
@@ -255,6 +266,9 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->need_initial = true;
client->need_first_frame = true;
uuid_generate(uuid);
uuid_unparse_lower(uuid, client->id);
if (server->run->stream_clients == NULL) {
server->run->stream_clients = client;
} else {
@@ -268,8 +282,8 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
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
"HTTP: Registered the new stream client: [%s]:%u; id=%s; clients now: %u",
client_addr, client_port, client->id, server->run->stream_clients_count
);
buf_event = evhttp_connection_get_bufferevent(conn);
@@ -288,6 +302,15 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
struct stream_client_t *client = (struct stream_client_t *)v_client;
struct evbuffer *buf;
long double now = get_now_monotonic();
long long now_second = floor_ms(now);
if (now_second != client->fps_accum_second) {
client->fps = client->fps_accum;
client->fps_accum = 0;
client->fps_accum_second = now_second;
}
client->fps_accum += 1;
assert((buf = evbuffer_new()));
@@ -298,9 +321,11 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0" RN
"Pragma: no-cache" RN
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
"Set-Cookie: stream_client_id=%s; path=/; max-age=30" RN
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
RN
"--" BOUNDARY RN
"--" BOUNDARY RN,
client->id
));
assert(!bufferevent_write_buffer(buf_event, buf));
client->need_initial = false;
@@ -319,6 +344,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
if (client->server->extra_stream_headers) {
assert(evbuffer_add_printf(buf,
"X-UStreamer-Online: %s" RN
"X-UStreamer-Client-FPS: %u" RN
"X-UStreamer-Grab-Time: %.06Lf" RN
"X-UStreamer-Encode-Begin-Time: %.06Lf" RN
"X-UStreamer-Encode-End-Time: %.06Lf" RN
@@ -328,13 +354,14 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"X-UStreamer-Send-Time: %.06Lf" RN
RN,
(EXPOSED(online) ? "true" : "false"),
client->fps,
EXPOSED(picture.grab_time),
EXPOSED(picture.encode_begin_time),
EXPOSED(picture.encode_end_time),
EXPOSED(expose_begin_time),
EXPOSED(expose_cmp_time),
EXPOSED(expose_end_time),
get_now_monotonic()
now
));
}
@@ -387,15 +414,14 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
}
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;
long long now;
bool queued = false;
static unsigned queued_fps = 0;
static unsigned queued_fps_accum = 0;
static long long queued_fps_second = 0;
for (client = server->run->stream_clients; client != NULL; client = client->next) {
for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
conn = evhttp_request_get_connection(client->request);
if (conn != NULL && (updated || client->need_first_frame)) {
buf_event = evhttp_connection_get_bufferevent(conn);
@@ -408,11 +434,11 @@ static void _http_queue_send_stream(struct http_server_t *server, const bool upd
if (queued) {
if ((now = floor_ms(get_now_monotonic())) != queued_fps_second) {
server->run->exposed->queued_fps = queued_fps;
queued_fps = 0;
server->run->exposed->queued_fps = queued_fps_accum;
queued_fps_accum = 0;
queued_fps_second = now;
}
queued_fps += 1;
queued_fps_accum += 1;
}
}

View File

@@ -31,8 +31,12 @@
struct stream_client_t {
struct http_server_t *server;
struct evhttp_request *request;
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
bool need_initial;
bool need_first_frame;
unsigned fps;
unsigned fps_accum;
long long fps_accum_second;
struct stream_client_t *prev;
struct stream_client_t *next;

View File

@@ -85,7 +85,7 @@ void stream_loop(struct stream_t *stream) {
unsigned frames_count = 0;
long double grab_after = 0;
unsigned fluency_passed = 0;
unsigned captured_fps = 0;
unsigned captured_fps_accum = 0;
long long captured_fps_second = 0;
LOG_DEBUG("Allocation memory for stream picture ...");
@@ -198,12 +198,12 @@ void stream_loop(struct stream_t *stream) {
fluency_passed = 0;
if (now_second != captured_fps_second) {
LOG_PERF("Oldest worker complete, Captured-FPS = %u", captured_fps);
stream->captured_fps = captured_fps;
captured_fps = 0;
stream->captured_fps = captured_fps_accum;
captured_fps_accum = 0;
captured_fps_second = now_second;
LOG_PERF("Oldest worker complete, Captured-FPS = %u", stream->captured_fps);
}
captured_fps += 1;
captured_fps_accum += 1;
long double fluency_delay = _stream_get_fluency_delay(stream->dev, &pool);