mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e61d7c55e | ||
|
|
4e42c42bae | ||
|
|
b53e3edef1 | ||
|
|
0afbf02451 | ||
|
|
dd79efd6f5 | ||
|
|
97ac19a2fe | ||
|
|
755e0c2a2a | ||
|
|
ccab33a290 | ||
|
|
76a8e65e80 | ||
|
|
3b86e64222 | ||
|
|
020482a05a | ||
|
|
1896e22dff | ||
|
|
56df20fe84 | ||
|
|
ed7dabbfcb | ||
|
|
b1d40d1b3a | ||
|
|
43939c7475 | ||
|
|
8fa6db0be1 | ||
|
|
d57e9864a4 | ||
|
|
077f236a43 | ||
|
|
d57277877e | ||
|
|
1b0db859b2 | ||
|
|
d1d8c645a8 | ||
|
|
a54541ff10 | ||
|
|
e5a57ac2e0 | ||
|
|
da0e00252d | ||
|
|
526ae8c3e6 | ||
|
|
d5081b2c18 | ||
|
|
758b5558f9 | ||
|
|
97b2183038 | ||
|
|
2732482d36 | ||
|
|
ca52f12378 | ||
|
|
ab437d402b | ||
|
|
79d9214084 | ||
|
|
b5db16e1ba | ||
|
|
a68e27f09e | ||
|
|
4af8c6a121 | ||
|
|
693c89ae6b | ||
|
|
8be5d6d370 | ||
|
|
5a7f3d30a7 | ||
|
|
fb6331b64a |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 0.26
|
||||
current_version = 0.41
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
5
Makefile
5
Makefile
@@ -29,7 +29,8 @@ install: $(PROG)
|
||||
|
||||
|
||||
regen:
|
||||
tools/make-jpg-h.py src/data/blank.jpg src/data/blank.h BLANK 640 480
|
||||
tools/make-jpeg-h.py src/data/blank.jpeg src/data/blank_jpeg.h BLANK 640 480
|
||||
tools/make-html-h.py src/data/index.html src/data/index_html.h HTML_INDEX_PAGE
|
||||
|
||||
|
||||
$(PROG): $(OBJECTS)
|
||||
@@ -56,7 +57,7 @@ push:
|
||||
git push
|
||||
git push --tags
|
||||
|
||||
|
||||
clean-all: clean
|
||||
clean:
|
||||
rm -f src/*.o src/{jpeg,omx}/*.o vgcore.* $(PROG)
|
||||
rm -rf pkg src/$(PROG)-* src/v*.tar.gz v*.tar.gz $(PROG)-*.pkg.tar.xz
|
||||
|
||||
2
PKGBUILD
2
PKGBUILD
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=0.26
|
||||
pkgver=0.41
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pi-kvm/ustreamer"
|
||||
|
||||
@@ -58,7 +58,7 @@ The recommended way of running µStreamer with [Auvidea B101](https://www.raspbe
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Device input format
|
||||
--encoder=omx \ # Hardware encoding with OpenMAX
|
||||
--dv-timings \ # use DV-timings
|
||||
--dv-timings \ # Use DV-timings
|
||||
--quality=20 \ # OpenMAX has a non-linear quality scale
|
||||
--drop-same-frames=30 # Save that traffic
|
||||
```
|
||||
|
||||
@@ -21,4 +21,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION "0.26"
|
||||
#define VERSION "0.41"
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -685,4 +685,4 @@ const unsigned char BLANK_JPG_DATA[] = {
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x7, 0xff, 0xd9
|
||||
};
|
||||
};
|
||||
59
src/data/index.html
Normal file
59
src/data/index.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>uStreamer</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h3>µStreamer v%VERSION%</h3>
|
||||
<hr>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/state"><b><samp>/state</samp></b></a><br>
|
||||
Get JSON structure with state of the server.
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<a href="/snapshot"><b><samp>/snapshot</samp></b></a><br>
|
||||
Get a current actual image from the server.
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<a href="/stream"><b><samp>/stream</samp></b></a><br>
|
||||
Get a live stream. Query params:<br>
|
||||
<br>
|
||||
<ul>
|
||||
<li>
|
||||
<b><samp>key=abc123</samp></b><br>
|
||||
User-defined key, which is part of cookie <samp>stream_client</samp>, which allows<br>
|
||||
the stream client to determine its identifier and view statistics using <a href="/state"><samp>/state</samp></a>.
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<b><samp>extra_headers=1</samp></b><br>
|
||||
Add <samp>X-UStreamer-*</samp> headers to /stream handle (like on <a href="/snapshot"><samp>/snapshot</samp></a>).
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<b><samp>advance_headers=1</samp></b><br>
|
||||
Enable workaround for Chromium/Blink
|
||||
<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=527446">Bug #527446</a>.
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<b><samp>dual_final_frames=1</samp></b><br>
|
||||
Enable workaround for Safari/WebKit bug when using option <samp>--drop-same-frames</samp>.<br>
|
||||
Without this option, when the frame series is completed, WebKit-based browsers<br>
|
||||
renders the last frame with a delay.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<br>
|
||||
</ul>
|
||||
<br>
|
||||
<hr>
|
||||
<a href="https://github.com/pi-kvm/ustreamer">Sources & docs</a>
|
||||
</body>
|
||||
</html>
|
||||
87
src/data/index_html.h
Normal file
87
src/data/index_html.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../config.h"
|
||||
|
||||
|
||||
const char *HTML_INDEX_PAGE = " \
|
||||
<!DOCTYPE html> \
|
||||
\
|
||||
<html> \
|
||||
<head> \
|
||||
<meta charset=\"utf-8\" /> \
|
||||
<title>uStreamer</title> \
|
||||
</head> \
|
||||
\
|
||||
<body> \
|
||||
<h3>µStreamer v" VERSION "</h3> \
|
||||
<hr> \
|
||||
<ul> \
|
||||
<li> \
|
||||
<a href=\"/state\"><b><samp>/state</samp></b></a><br> \
|
||||
Get JSON structure with state of the server. \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
<a href=\"/snapshot\"><b><samp>/snapshot</samp></b></a><br> \
|
||||
Get a current actual image from the server. \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
<a href=\"/stream\"><b><samp>/stream</samp></b></a><br> \
|
||||
Get a live stream. Query params:<br> \
|
||||
<br> \
|
||||
<ul> \
|
||||
<li> \
|
||||
<b><samp>key=abc123</samp></b><br> \
|
||||
User-defined key, which is part of cookie <samp>stream_client</samp>, which allows<br> \
|
||||
the stream client to determine its identifier and view statistics using <a href=\"/state\"><samp>/state</samp></a>. \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
<b><samp>extra_headers=1</samp></b><br> \
|
||||
Add <samp>X-UStreamer-*</samp> headers to /stream handle (like on <a href=\"/snapshot\"><samp>/snapshot</samp></a>). \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
<b><samp>advance_headers=1</samp></b><br> \
|
||||
Enable workaround for Chromium/Blink \
|
||||
<a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=527446\">Bug #527446</a>. \
|
||||
</li> \
|
||||
<br> \
|
||||
<li> \
|
||||
<b><samp>dual_final_frames=1</samp></b><br> \
|
||||
Enable workaround for Safari/WebKit bug when using option <samp>--drop-same-frames</samp>.<br> \
|
||||
Without this option, when the frame series is completed, WebKit-based browsers<br> \
|
||||
renders the last frame with a delay. \
|
||||
</li> \
|
||||
</ul> \
|
||||
</li> \
|
||||
<br> \
|
||||
</ul> \
|
||||
<br> \
|
||||
<hr> \
|
||||
<a href=\"https://github.com/pi-kvm/ustreamer\">Sources & docs</a> \
|
||||
</body> \
|
||||
</html> \
|
||||
";
|
||||
@@ -137,8 +137,6 @@ int device_open(struct device_t *dev) {
|
||||
}
|
||||
_device_open_alloc_picbufs(dev);
|
||||
|
||||
dev->run->n_workers = dev->n_workers;
|
||||
|
||||
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ struct device_t {
|
||||
bool dv_timings;
|
||||
unsigned n_buffers;
|
||||
unsigned n_workers;
|
||||
unsigned every_frame;
|
||||
unsigned desired_fps;
|
||||
unsigned min_frame_size;
|
||||
bool persistent;
|
||||
unsigned timeout;
|
||||
|
||||
@@ -54,7 +54,11 @@ struct encoder_t *encoder_init() {
|
||||
return encoder;
|
||||
}
|
||||
|
||||
void encoder_prepare(struct encoder_t *encoder) {
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic push
|
||||
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
if (encoder->type != ENCODER_TYPE_CPU) {
|
||||
@@ -65,8 +69,20 @@ void encoder_prepare(struct encoder_t *encoder) {
|
||||
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
if ((encoder->omx = omx_encoder_init()) == NULL) {
|
||||
goto use_fallback;
|
||||
if (dev->n_workers > OMX_MAX_ENCODERS) {
|
||||
LOG_INFO(
|
||||
"OMX-based encoder can only work with %u worker threads; forced --workers=%u",
|
||||
OMX_MAX_ENCODERS, OMX_MAX_ENCODERS
|
||||
);
|
||||
dev->n_workers = OMX_MAX_ENCODERS;
|
||||
}
|
||||
encoder->n_omxs = dev->n_workers;
|
||||
|
||||
A_CALLOC(encoder->omxs, encoder->n_omxs);
|
||||
for (unsigned index = 0; index < encoder->n_omxs; ++index) {
|
||||
if ((encoder->omxs[index] = omx_encoder_init()) == NULL) {
|
||||
goto use_fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
# endif
|
||||
@@ -83,8 +99,13 @@ void encoder_prepare(struct encoder_t *encoder) {
|
||||
|
||||
void encoder_destroy(struct encoder_t *encoder) {
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->omx) {
|
||||
omx_encoder_destroy(encoder->omx);
|
||||
if (encoder->omxs) {
|
||||
for (unsigned index = 0; index < encoder->n_omxs; ++index) {
|
||||
if (encoder->omxs[index]) {
|
||||
omx_encoder_destroy(encoder->omxs[index]);
|
||||
}
|
||||
}
|
||||
free(encoder->omxs);
|
||||
}
|
||||
# endif
|
||||
free(encoder);
|
||||
@@ -101,18 +122,16 @@ enum encoder_type_t encoder_parse_type(const char *const str) {
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic push
|
||||
void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev) {
|
||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
if (omx_encoder_prepare_for_device(encoder->omx, dev, encoder->quality, encoder->omx_use_ijg) < 0) {
|
||||
goto use_fallback;
|
||||
}
|
||||
if (dev->run->n_workers > 1) {
|
||||
LOG_INFO("OMX encoder can only work with one worker thread; forcing n_workers to 1");
|
||||
dev->run->n_workers = 1;
|
||||
for (unsigned index = 0; index < encoder->n_omxs; ++index) {
|
||||
if (omx_encoder_prepare_live(encoder->omxs[index], dev, encoder->quality) < 0) {
|
||||
goto use_fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
# endif
|
||||
@@ -124,28 +143,28 @@ void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev)
|
||||
use_fallback:
|
||||
LOG_ERROR("Can't prepare selected encoder, falling back to CPU");
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
dev->run->n_workers = dev->n_workers;
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, const unsigned index) {
|
||||
#pragma GCC diagnostic ignored "-Wunused-label"
|
||||
#pragma GCC diagnostic push
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev,
|
||||
const unsigned worker_number, const unsigned buf_index) {
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
dev->run->pictures[index].encode_begin_time = get_now_monotonic();
|
||||
|
||||
if (encoder->type == ENCODER_TYPE_CPU) {
|
||||
jpeg_encoder_compress_buffer(dev, index, encoder->quality);
|
||||
jpeg_encoder_compress_buffer(dev, buf_index, encoder->quality);
|
||||
}
|
||||
# ifdef OMX_ENCODER
|
||||
else if (encoder->type == ENCODER_TYPE_OMX) {
|
||||
if (omx_encoder_compress_buffer(encoder->omx, dev, index) < 0) {
|
||||
if (omx_encoder_compress_buffer(encoder->omxs[worker_number], dev, buf_index) < 0) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
dev->run->pictures[index].encode_end_time = get_now_monotonic();
|
||||
|
||||
return 0;
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
@@ -153,7 +172,6 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, con
|
||||
error:
|
||||
LOG_INFO("HW compressing error, falling back to CPU");
|
||||
encoder->type = ENCODER_TYPE_CPU;
|
||||
dev->run->n_workers = dev->n_workers;
|
||||
return -1;
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ struct encoder_t {
|
||||
enum encoder_type_t type;
|
||||
unsigned quality;
|
||||
#ifdef OMX_ENCODER
|
||||
bool omx_use_ijg;
|
||||
struct omx_encoder_t *omx;
|
||||
unsigned n_omxs;
|
||||
struct omx_encoder_t **omxs;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -59,6 +59,8 @@ void encoder_destroy(struct encoder_t *encoder);
|
||||
|
||||
enum encoder_type_t encoder_parse_type(const char *const str);
|
||||
|
||||
void encoder_prepare(struct encoder_t *encoder);
|
||||
void encoder_prepare_for_device(struct encoder_t *encoder, struct device_t *dev);
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, const unsigned index);
|
||||
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev);
|
||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev);
|
||||
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev,
|
||||
const unsigned worker_number, const unsigned buf_index);
|
||||
|
||||
312
src/http.c
312
src/http.c
@@ -22,6 +22,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
@@ -29,6 +30,7 @@
|
||||
#include <event2/http.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
@@ -41,11 +43,15 @@
|
||||
#include "stream.h"
|
||||
#include "http.h"
|
||||
|
||||
#include "data/blank.h"
|
||||
#include "data/index_html.h"
|
||||
#include "data/blank_jpeg.h"
|
||||
|
||||
|
||||
static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
|
||||
static char *_http_get_param_uri(struct evkeyvalq *params, const char *key);
|
||||
|
||||
static void _http_callback_root(struct evhttp_request *request, void *arg);
|
||||
static void _http_callback_ping(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_state(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server);
|
||||
|
||||
static void _http_callback_stream(struct evhttp_request *request, void *v_server);
|
||||
@@ -53,7 +59,7 @@ 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, const bool updated);
|
||||
static void _http_queue_send_stream(struct http_server_t *server, const bool stream_updated, const bool picture_updated);
|
||||
|
||||
static bool _expose_new_picture(struct http_server_t *server);
|
||||
static bool _expose_blank_picture(struct http_server_t *server);
|
||||
@@ -63,7 +69,6 @@ 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);
|
||||
|
||||
@@ -86,21 +91,19 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
||||
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD);
|
||||
|
||||
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, NULL));
|
||||
assert(!evhttp_set_cb(run->http, "/ping", _http_callback_ping, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (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(!event_add(run->refresh, &refresh_interval));
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void http_server_destroy(struct http_server_t *server) {
|
||||
event_del(server->run->refresh);
|
||||
event_free(server->run->refresh);
|
||||
if (server->run->refresh) {
|
||||
event_del(server->run->refresh);
|
||||
event_free(server->run->refresh);
|
||||
}
|
||||
|
||||
evhttp_free(server->run->http);
|
||||
event_base_free(server->run->base);
|
||||
libevent_global_shutdown();
|
||||
@@ -112,6 +115,17 @@ void http_server_destroy(struct http_server_t *server) {
|
||||
}
|
||||
|
||||
int http_server_listen(struct http_server_t *server) {
|
||||
struct timeval refresh_interval;
|
||||
|
||||
refresh_interval.tv_sec = 0;
|
||||
if (server->run->stream->dev->desired_fps > 0) {
|
||||
refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->desired_fps * 2);
|
||||
} else {
|
||||
refresh_interval.tv_usec = 16000; // ~60fps
|
||||
}
|
||||
assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
|
||||
assert(!event_add(server->run->refresh, &refresh_interval));
|
||||
|
||||
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);
|
||||
@@ -137,6 +151,26 @@ void http_server_loop_break(struct http_server_t *server) {
|
||||
}
|
||||
|
||||
|
||||
static bool _http_get_param_true(struct evkeyvalq *params, const char *key) {
|
||||
const char *value_str;
|
||||
|
||||
if ((value_str = evhttp_find_header(params, key)) != NULL) {
|
||||
if (!strcasecmp(value_str, "true") || !strcasecmp(value_str, "yes") || value_str[0] == '1') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static char *_http_get_param_uri(struct evkeyvalq *params, const char *key) {
|
||||
const char *value_str;
|
||||
|
||||
if ((value_str = evhttp_find_header(params, key)) != NULL) {
|
||||
return evhttp_encode_uri(value_str);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define ADD_HEADER(_key, _value) \
|
||||
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
||||
|
||||
@@ -153,20 +187,13 @@ static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg
|
||||
PROCESS_HEAD_REQUEST;
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"<!DOCTYPE html><html><head><meta charset=\"utf-8\">"
|
||||
"<title>uStreamer</title></head><body><ul>"
|
||||
"<li><a href=\"/ping\">/ping</a></li>"
|
||||
"<li><a href=\"/snapshot\">/snapshot</a></li>"
|
||||
"<li><a href=\"/stream\">/stream</a></li>"
|
||||
"</body></html>"
|
||||
));
|
||||
assert(evbuffer_add_printf(buf, HTML_INDEX_PAGE));
|
||||
ADD_HEADER("Content-Type", "text/html");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
static void _http_callback_ping(struct evhttp_request *request, void *v_server) {
|
||||
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||
struct evbuffer *buf;
|
||||
|
||||
@@ -174,24 +201,31 @@ static void _http_callback_ping(struct evhttp_request *request, void *v_server)
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"{\"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"quality\": %u, \"captured_fps\": %u},"
|
||||
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
|
||||
"{\"ok\": true, \"result\":"
|
||||
" {\"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"quality\": %u, \"desired_fps\": %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"),
|
||||
bool_to_string(server->run->exposed->online),
|
||||
server->run->stream->encoder->quality,
|
||||
server->run->stream->dev->desired_fps,
|
||||
server->run->exposed->captured_fps,
|
||||
server->run->exposed->queued_fps,
|
||||
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 ? ", " : "")
|
||||
"\"%s\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s, \"dual_final_frames\": %s}%s",
|
||||
client->id,
|
||||
client->fps,
|
||||
bool_to_string(client->extra_headers),
|
||||
bool_to_string(client->advance_headers),
|
||||
bool_to_string(client->dual_final_frames),
|
||||
(client->next ? ", " : "")
|
||||
));
|
||||
}
|
||||
assert(evbuffer_add_printf(buf, "}}}"));
|
||||
assert(evbuffer_add_printf(buf, "}}}}"));
|
||||
|
||||
ADD_HEADER("Content-Type", "application/json");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
@@ -201,7 +235,7 @@ static void _http_callback_ping(struct evhttp_request *request, void *v_server)
|
||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server) {
|
||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||
struct evbuffer *buf;
|
||||
char time_buf[64];
|
||||
char header_buf[64];
|
||||
|
||||
PROCESS_HEAD_REQUEST;
|
||||
|
||||
@@ -216,11 +250,16 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
||||
|
||||
# define ADD_TIME_HEADER(_key, _value) \
|
||||
{ sprintf(time_buf, "%.06Lf", _value); ADD_HEADER(_key, time_buf); }
|
||||
{ sprintf(header_buf, "%.06Lf", _value); ADD_HEADER(_key, header_buf); }
|
||||
|
||||
# define ADD_UNSIGNED_HEADER(_key, _value) \
|
||||
{ sprintf(header_buf, "%u", _value); ADD_HEADER(_key, header_buf); }
|
||||
|
||||
ADD_TIME_HEADER("X-Timestamp", get_now_real());
|
||||
|
||||
ADD_HEADER("X-UStreamer-Online", (EXPOSED(online) ? "true" : "false"));
|
||||
ADD_HEADER("X-UStreamer-Online", bool_to_string(EXPOSED(online)));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(width));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Height", EXPOSED(height));
|
||||
ADD_TIME_HEADER("X-UStreamer-Grab-Time", EXPOSED(picture.grab_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Time", EXPOSED(picture.encode_begin_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-End-Time", EXPOSED(picture.encode_end_time));
|
||||
@@ -229,6 +268,7 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-End-Time", EXPOSED(expose_end_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Send-Time", get_now_monotonic());
|
||||
|
||||
# undef ADD_UNSUGNED_HEADER
|
||||
# undef ADD_TIME_HEADER
|
||||
|
||||
ADD_HEADER("Content-Type", "image/jpeg");
|
||||
@@ -250,6 +290,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
|
||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||
struct evhttp_connection *conn;
|
||||
struct evkeyvalq params;
|
||||
struct bufferevent *buf_event;
|
||||
struct stream_client_t *client;
|
||||
char *client_addr;
|
||||
@@ -266,6 +307,13 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
client->need_initial = true;
|
||||
client->need_first_frame = true;
|
||||
|
||||
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||
client->key = _http_get_param_uri(¶ms, "key");
|
||||
client->extra_headers = _http_get_param_true(¶ms, "extra_headers");
|
||||
client->advance_headers = _http_get_param_true(¶ms, "advance_headers");
|
||||
client->dual_final_frames = _http_get_param_true(¶ms, "dual_final_frames");
|
||||
evhttp_clear_headers(¶ms);
|
||||
|
||||
uuid_generate(uuid);
|
||||
uuid_unparse_lower(uuid, client->id);
|
||||
|
||||
@@ -282,8 +330,14 @@ 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; id=%s; clients now: %u",
|
||||
client_addr, client_port, client->id, server->run->stream_clients_count
|
||||
"HTTP: Registered the new stream client: [%s]:%u; id=%s;"
|
||||
" advance_headers=%s; dual_final_frames=%s; clients now: %u",
|
||||
client_addr,
|
||||
client_port,
|
||||
client->id,
|
||||
bool_to_string(client->advance_headers),
|
||||
bool_to_string(client->dual_final_frames),
|
||||
server->run->stream_clients_count
|
||||
);
|
||||
|
||||
buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
@@ -314,6 +368,29 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
|
||||
// В хроме и его производных есть фундаментальный баг: он отрисовывает
|
||||
// фрейм с задержкой на один, как только ему придут заголовки следующего.
|
||||
// В сочетании с drop_same_frames это дает значительный лаг стрима
|
||||
// при большом количестве дропов (на статичном изображении, где внезапно
|
||||
// что-то изменилось.
|
||||
//
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=527446
|
||||
//
|
||||
// Включение advance_headers заставляет стример отсылать заголовки
|
||||
// будущего фрейма сразу после данных текущего, чтобы триггернуть отрисовку.
|
||||
// Естественным следствием этого является невозможность установки заголовка
|
||||
// Content-Length, так как предсказывать будущее мы еще не научились.
|
||||
// Его наличие не требуется RFC, однако никаких стандартов на MJPG over HTTP
|
||||
// в природе не существует, и никто не может гарантировать, что отсутствие
|
||||
// Content-Length не сломает вещание для каких-нибудь маргинальных браузеров.
|
||||
//
|
||||
// Кроме того, advance_headers форсит отключение заголовков X-UStreamer-*
|
||||
// по тем же причинам, по которым у нас нет Content-Length.
|
||||
|
||||
# define ADD_ADVANCE_HEADERS \
|
||||
{ assert(evbuffer_add_printf(buf, \
|
||||
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real())); }
|
||||
|
||||
if (client->need_initial) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"HTTP/1.0 200 OK" RN
|
||||
@@ -321,48 +398,60 @@ 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
|
||||
"Set-Cookie: stream_client=%s/%s; path=/; max-age=30" RN
|
||||
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
||||
RN
|
||||
"--" BOUNDARY RN,
|
||||
(client->key != NULL ? client->key : "0"),
|
||||
client->id
|
||||
));
|
||||
|
||||
if (client->advance_headers) {
|
||||
ADD_ADVANCE_HEADERS;
|
||||
}
|
||||
|
||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||
client->need_initial = false;
|
||||
}
|
||||
|
||||
# define EXPOSED(_next) client->server->run->exposed->_next
|
||||
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"Content-Type: image/jpeg" RN
|
||||
"Content-Length: %lu" RN
|
||||
"X-Timestamp: %.06Lf" RN
|
||||
"%s",
|
||||
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data)),
|
||||
get_now_real(), (client->server->extra_stream_headers ? "" : RN)
|
||||
));
|
||||
if (client->server->extra_stream_headers) {
|
||||
if (!client->advance_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
|
||||
"X-UStreamer-Expose-Begin-Time: %.06Lf" RN
|
||||
"X-UStreamer-Expose-Cmp-Time: %.06Lf" RN
|
||||
"X-UStreamer-Expose-End-Time: %.06Lf" RN
|
||||
"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),
|
||||
now
|
||||
"Content-Type: image/jpeg" RN
|
||||
"Content-Length: %lu" RN
|
||||
"X-Timestamp: %.06Lf" RN
|
||||
"%s",
|
||||
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data)),
|
||||
get_now_real(), (client->extra_headers ? "" : RN)
|
||||
));
|
||||
if (client->extra_headers) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"X-UStreamer-Online: %s" RN
|
||||
"X-UStreamer-Width: %u" RN
|
||||
"X-UStreamer-Height: %u" 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
|
||||
"X-UStreamer-Expose-Begin-Time: %.06Lf" RN
|
||||
"X-UStreamer-Expose-Cmp-Time: %.06Lf" RN
|
||||
"X-UStreamer-Expose-End-Time: %.06Lf" RN
|
||||
"X-UStreamer-Send-Time: %.06Lf" RN
|
||||
RN,
|
||||
bool_to_string(EXPOSED(online)),
|
||||
EXPOSED(width),
|
||||
EXPOSED(height),
|
||||
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),
|
||||
now
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
assert(!evbuffer_add(buf,
|
||||
@@ -371,15 +460,20 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
));
|
||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||
|
||||
if (client->advance_headers) {
|
||||
ADD_ADVANCE_HEADERS;
|
||||
}
|
||||
|
||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||
evbuffer_free(buf);
|
||||
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||
bufferevent_enable(buf_event, EV_READ);
|
||||
|
||||
# undef BOUNDARY
|
||||
# undef RN
|
||||
# undef EXPOSED
|
||||
# undef ADD_ADVANCE_HEADERS
|
||||
# undef RN
|
||||
# undef BOUNDARY
|
||||
}
|
||||
|
||||
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
|
||||
@@ -410,10 +504,11 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
if (client->next != NULL) {
|
||||
client->next->prev = client->prev;
|
||||
}
|
||||
free(client->key);
|
||||
free(client);
|
||||
}
|
||||
|
||||
static void _http_queue_send_stream(struct http_server_t *server, const bool updated) {
|
||||
static void _http_queue_send_stream(struct http_server_t *server, const bool stream_updated, const bool picture_updated) {
|
||||
struct evhttp_connection *conn;
|
||||
struct bufferevent *buf_event;
|
||||
long long now;
|
||||
@@ -423,12 +518,32 @@ static void _http_queue_send_stream(struct http_server_t *server, const bool upd
|
||||
|
||||
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);
|
||||
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;
|
||||
queued = true;
|
||||
if (conn != NULL) {
|
||||
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
||||
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
|
||||
// и нужно послать два фрейма, чтобы серия была вовремя завершена.
|
||||
// Это похоже на баг Blink (см. _http_callback_stream_write() и advance_headers),
|
||||
// но фикс для него не лечит проблему вебкита. Такие дела.
|
||||
|
||||
bool dual_update = (
|
||||
server->drop_same_frames
|
||||
&& client->dual_final_frames
|
||||
&& stream_updated
|
||||
&& client->updated_prev
|
||||
&& !picture_updated
|
||||
);
|
||||
|
||||
if (dual_update || picture_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;
|
||||
client->updated_prev = (picture_updated || client->need_first_frame); // Игнорировать dual
|
||||
queued = true;
|
||||
} else if (stream_updated) { // Для dual
|
||||
client->updated_prev = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,52 +559,32 @@ static void _http_queue_send_stream(struct http_server_t *server, const bool upd
|
||||
|
||||
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;
|
||||
bool stream_updated = false;
|
||||
bool picture_updated = false;
|
||||
|
||||
#define LOCK_STREAM \
|
||||
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
|
||||
|
||||
#define UNLOCK_STREAM \
|
||||
{ server->run->stream->updated = false; A_PTHREAD_M_UNLOCK(&server->run->stream->mutex); }
|
||||
# define UNLOCK_STREAM \
|
||||
{ server->run->stream->updated = false; A_PTHREAD_M_UNLOCK(&server->run->stream->mutex); }
|
||||
|
||||
if (server->run->stream->updated) {
|
||||
LOG_DEBUG("Refreshing HTTP exposed ...");
|
||||
LOCK_STREAM;
|
||||
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
|
||||
if (server->run->stream->picture.size > 0) { // If online
|
||||
updated = _expose_new_picture(server);
|
||||
picture_updated = _expose_new_picture(server);
|
||||
UNLOCK_STREAM;
|
||||
} else {
|
||||
UNLOCK_STREAM;
|
||||
updated = _expose_blank_picture(server);
|
||||
picture_updated = _expose_blank_picture(server);
|
||||
}
|
||||
queue_send = true;
|
||||
stream_updated = true;
|
||||
} else if (!server->run->exposed->online) {
|
||||
LOG_DEBUG("Refreshing HTTP exposed (BLANK) ...");
|
||||
updated = _expose_blank_picture(server);
|
||||
queue_send = true;
|
||||
picture_updated = _expose_blank_picture(server);
|
||||
stream_updated = true;
|
||||
}
|
||||
|
||||
if (queue_send) {
|
||||
if (server->drop_same_frames) {
|
||||
// Хром всегда показывает не новый пришедший фрейм, а предыдущий.
|
||||
// При updated == false нужно еще один раз послать предыдущий фрейм
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=527446
|
||||
|
||||
static bool updated_prev = false;
|
||||
bool updated_orig = updated;
|
||||
|
||||
if (updated_prev && !updated_orig) {
|
||||
updated = true;
|
||||
}
|
||||
updated_prev = updated_orig;
|
||||
}
|
||||
|
||||
_http_queue_send_stream(server, updated);
|
||||
}
|
||||
|
||||
# undef LOCK_STREAM
|
||||
# undef UNLOCK_STREAM
|
||||
|
||||
_http_queue_send_stream(server, stream_updated, picture_updated);
|
||||
}
|
||||
|
||||
static bool _expose_new_picture(struct http_server_t *server) {
|
||||
@@ -513,7 +608,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
||||
) {
|
||||
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
||||
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
|
||||
LOG_PERF(
|
||||
LOG_VERBOSE(
|
||||
"HTTP: dropped same frame number %u; comparsion time = %.06Lf",
|
||||
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
|
||||
);
|
||||
@@ -521,7 +616,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
||||
return false; // Not updated
|
||||
} else {
|
||||
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
||||
LOG_PERF(
|
||||
LOG_VERBOSE(
|
||||
"HTTP: passed same frame check (frames are differ); comparsion time = %.06Lf",
|
||||
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
|
||||
);
|
||||
@@ -550,8 +645,13 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
||||
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
||||
EXPOSED(expose_end_time) = get_now_monotonic();
|
||||
|
||||
# undef STREAM
|
||||
LOG_VERBOSE(
|
||||
"HTTP: exposed new frame; full exposition time = %.06Lf",
|
||||
EXPOSED(expose_end_time) - EXPOSED(expose_begin_time)
|
||||
);
|
||||
|
||||
# undef EXPOSED
|
||||
# undef STREAM
|
||||
return true; // Updated
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,16 @@
|
||||
struct stream_client_t {
|
||||
struct http_server_t *server;
|
||||
struct evhttp_request *request;
|
||||
|
||||
char *key;
|
||||
bool extra_headers;
|
||||
bool advance_headers;
|
||||
bool dual_final_frames;
|
||||
|
||||
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
|
||||
bool need_initial;
|
||||
bool need_first_frame;
|
||||
bool updated_prev;
|
||||
unsigned fps;
|
||||
unsigned fps_accum;
|
||||
long long fps_accum_second;
|
||||
@@ -70,7 +77,6 @@ struct http_server_t {
|
||||
char *host;
|
||||
unsigned port;
|
||||
unsigned drop_same_frames;
|
||||
bool extra_stream_headers;
|
||||
unsigned fake_width;
|
||||
unsigned fake_height;
|
||||
unsigned timeout;
|
||||
|
||||
38
src/main.c
38
src/main.c
@@ -40,24 +40,21 @@
|
||||
#include "http.h"
|
||||
|
||||
|
||||
static const char _short_opts[] = "d:i:x:y:f:a:e:z:tn:w:q:c:s:p:r:h";
|
||||
static const char _short_opts[] = "d:i:x:y:f:a:z:tn:w:q:c:s:p:r:h";
|
||||
static const struct option _long_opts[] = {
|
||||
{"device", required_argument, NULL, 'd'},
|
||||
{"input", required_argument, NULL, 'i'},
|
||||
{"width", required_argument, NULL, 'x'},
|
||||
{"height", required_argument, NULL, 'y'},
|
||||
{"format", required_argument, NULL, 'f'},
|
||||
{"format", required_argument, NULL, 'm'},
|
||||
{"tv-standard", required_argument, NULL, 'a'},
|
||||
{"every-frame", required_argument, NULL, 'e'},
|
||||
{"desired-fps", required_argument, NULL, 'f'},
|
||||
{"min-frame-size", required_argument, NULL, 'z'},
|
||||
{"dv-timings", no_argument, NULL, 't'},
|
||||
{"buffers", required_argument, NULL, 'b'},
|
||||
{"workers", required_argument, NULL, 'w'},
|
||||
{"quality", required_argument, NULL, 'q'},
|
||||
{"encoder", required_argument, NULL, 'c'},
|
||||
# ifdef OMX_ENCODER
|
||||
{"encoder-omx-use-ijg", required_argument, NULL, 500},
|
||||
# endif
|
||||
{"device-timeout", required_argument, NULL, 1000},
|
||||
{"device-persistent", no_argument, NULL, 1001},
|
||||
{"device-error-delay", required_argument, NULL, 1002},
|
||||
@@ -65,7 +62,6 @@ static const struct option _long_opts[] = {
|
||||
{"host", required_argument, NULL, 's'},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
{"drop-same-frames", required_argument, NULL, 'r'},
|
||||
{"extra-stream-headers", no_argument, NULL, 2000},
|
||||
{"fake-width", required_argument, NULL, 2001},
|
||||
{"fake-height", required_argument, NULL, 2002},
|
||||
{"server-timeout", required_argument, NULL, 2003},
|
||||
@@ -102,11 +98,11 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
printf(" -i|--input <N> -- Input channel. Default: %u.\n\n", dev->input);
|
||||
printf(" -x|--width <N> -- Initial image width. Default: %d.\n\n", dev->width);
|
||||
printf(" -y|--height <N> -- Initial image height. Default: %d.\n\n", dev->height);
|
||||
printf(" -f|--format <fmt> -- Image format.\n");
|
||||
printf(" -m|--format <fmt> -- Image format.\n");
|
||||
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
|
||||
printf(" -a|--tv-standard <std> -- Force TV standard.\n");
|
||||
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
|
||||
printf(" -e|--every-frame <N> -- Drop all input frames except specified. Default: disabled.\n\n");
|
||||
printf(" -f|--desired-fps <N> -- Desired FPS; default: maximum as possible.\n\n");
|
||||
printf(" -z|--min-frame-size <N> -- Drop frames smaller then this limit.\n");
|
||||
printf(" Useful if the device produces small-sized garbage frames.\n\n");
|
||||
printf(" -t|--dv-timings -- Enable DV timings queriyng and events processing.\n");
|
||||
@@ -118,10 +114,6 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
printf(" -q|--quality <N> -- Set quality of JPEG encoding from 1 to 100 (best). Default: %d.\n\n", encoder->quality);
|
||||
printf(" --encoder <type> -- Use specified encoder. It may affects to workers number.\n");
|
||||
printf(" -- Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
|
||||
# ifdef OMX_ENCODER
|
||||
printf(" --encoder-omx-use-ijg -- Use the standard IJG quality tables when encoding images using OMX.\n");
|
||||
printf(" Default: disabled.\n\n");
|
||||
# endif
|
||||
printf(" --device-timeout <seconds> -- Timeout for device querying. Default: %d\n\n", dev->timeout);
|
||||
printf(" --device-persistent -- Don't re-initialize device on timeout. Default: disabled.\n\n");
|
||||
printf(" --device-error-delay <seconds> -- Delay before trying to connect to the device again\n");
|
||||
@@ -134,10 +126,8 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
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(" --extra-stream-headers -- Add X-UStreamer-* headers to /stream handle (like /snapshot).\n");
|
||||
printf(" 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-width <N> -- Override image width for /state. Default: disabled\n\n");
|
||||
printf(" --fake-height <N> -- Override image height for /state. Default: disabled.\n\n");
|
||||
printf(" --server-timeout <seconds> -- Timeout for client connections. Default: %d\n\n", server->timeout);
|
||||
printf("Misc options:\n");
|
||||
printf("-------------\n");
|
||||
@@ -178,19 +168,16 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
case 'y': OPT_UNSIGNED(dev->height, "--height", 180, 1200);
|
||||
# pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
# pragma GCC diagnostic push
|
||||
case 'f': OPT_PARSE(dev->format, device_parse_format, FORMAT_UNKNOWN, "pixel format");
|
||||
case 'm': OPT_PARSE(dev->format, device_parse_format, FORMAT_UNKNOWN, "pixel format");
|
||||
# pragma GCC diagnostic pop
|
||||
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard");
|
||||
case 'e': OPT_UNSIGNED(dev->every_frame, "--every-frame", 1, 30);
|
||||
case 'f': OPT_UNSIGNED(dev->desired_fps, "--desired-fps", 0, 30);
|
||||
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192);
|
||||
case 't': OPT_SET(dev->dv_timings, true);
|
||||
case 'b': OPT_UNSIGNED(dev->n_buffers, "--buffers", 1, 32);
|
||||
case 'w': OPT_UNSIGNED(dev->n_workers, "--workers", 1, 32);
|
||||
case 'q': OPT_UNSIGNED(encoder->quality, "--quality", 1, 100);
|
||||
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type");
|
||||
# ifdef OMX_ENCODER
|
||||
case 500: OPT_SET(encoder->omx_use_ijg, true);
|
||||
# endif
|
||||
case 1000: OPT_UNSIGNED(dev->timeout, "--device-timeout", 1, 60);
|
||||
case 1001: OPT_SET(dev->persistent, true);
|
||||
case 1002: OPT_UNSIGNED(dev->error_delay, "--device-error-delay", 1, 60);
|
||||
@@ -198,7 +185,6 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
case 's': OPT_SET(server->host, optarg);
|
||||
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_SET(server->extra_stream_headers, true);
|
||||
case 2001: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
||||
case 2002: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
|
||||
case 2003: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
||||
@@ -214,9 +200,9 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
}
|
||||
}
|
||||
|
||||
# undef OPT_SET
|
||||
# undef OPT_UNSIGNED
|
||||
# undef OPT_PARSE
|
||||
# undef OPT_UNSIGNED
|
||||
# undef OPT_SET
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -288,7 +274,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) {
|
||||
_install_signal_handlers();
|
||||
encoder_prepare(encoder);
|
||||
encoder_prepare(encoder, dev);
|
||||
|
||||
pthread_t stream_loop_tid;
|
||||
pthread_t server_loop_tid;
|
||||
|
||||
@@ -45,10 +45,13 @@
|
||||
#define OUTPUT_PORT 341
|
||||
|
||||
|
||||
static int _i_omx = 0;
|
||||
|
||||
|
||||
static int _omx_init_component(struct omx_encoder_t *omx);
|
||||
static int _omx_init_disable_ports(struct omx_encoder_t *omx);
|
||||
static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev);
|
||||
static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality, const bool use_ijg);
|
||||
static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality);
|
||||
static int _omx_encoder_clear_ports(struct omx_encoder_t *omx);
|
||||
|
||||
static OMX_ERRORTYPE _omx_event_handler(UNUSED OMX_HANDLETYPE encoder,
|
||||
@@ -77,17 +80,20 @@ struct omx_encoder_t *omx_encoder_init() {
|
||||
|
||||
A_CALLOC(omx, 1);
|
||||
|
||||
LOG_INFO("Initializing OMX JPEG encoder ...");
|
||||
assert(_i_omx >= 0);
|
||||
if (_i_omx == 0) {
|
||||
LOG_INFO("Initializing BCM ...");
|
||||
bcm_host_init();
|
||||
|
||||
LOG_DEBUG("Initializing BCM ...");
|
||||
bcm_host_init();
|
||||
|
||||
LOG_DEBUG("Initializing OMX ...");
|
||||
if ((error = OMX_Init()) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Can't initialize OMX");
|
||||
goto error;
|
||||
LOG_INFO("Initializing OMX ...");
|
||||
if ((error = OMX_Init()) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Can't initialize OMX");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
omx->i_omx = true;
|
||||
_i_omx += 1;
|
||||
|
||||
LOG_INFO("Initializing OMX JPEG encoder ...");
|
||||
|
||||
if (vcos_semaphore_create(&omx->handler_lock, "handler_lock", 0) != VCOS_SUCCESS) {
|
||||
LOG_ERROR("Can't create VCOS semaphore");
|
||||
@@ -129,14 +135,20 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
|
||||
}
|
||||
}
|
||||
|
||||
if (omx->i_omx) {
|
||||
assert(_i_omx >= 0);
|
||||
_i_omx -= 1;
|
||||
if (_i_omx == 0) {
|
||||
LOG_INFO("Destroying OMX ...");
|
||||
OMX_Deinit();
|
||||
|
||||
LOG_INFO("Destroying BCM ...");
|
||||
bcm_host_deinit();
|
||||
}
|
||||
bcm_host_deinit();
|
||||
|
||||
free(omx);
|
||||
}
|
||||
|
||||
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality, const bool use_ijg) {
|
||||
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality) {
|
||||
if (component_set_state(&omx->encoder, OMX_StateIdle) < 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -146,7 +158,7 @@ int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *d
|
||||
if (_omx_setup_input(omx, dev) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (_omx_setup_output(omx, quality, use_ijg) < 0) {
|
||||
if (_omx_setup_output(omx, quality) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (component_set_state(&omx->encoder, OMX_StateExecuting) < 0) {
|
||||
@@ -317,7 +329,7 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality, const bool use_ijg) {
|
||||
static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality) {
|
||||
OMX_ERRORTYPE error;
|
||||
OMX_PARAM_PORTDEFINITIONTYPE portdef;
|
||||
|
||||
@@ -353,7 +365,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, const unsigned quality,
|
||||
}
|
||||
}
|
||||
|
||||
if (use_ijg) {
|
||||
{
|
||||
OMX_PARAM_IJGSCALINGTYPE ijg;
|
||||
|
||||
OMX_INIT_STRUCTURE(ijg);
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
#include "../device.h"
|
||||
|
||||
|
||||
#define OMX_MAX_ENCODERS 3
|
||||
|
||||
|
||||
struct omx_encoder_t {
|
||||
OMX_HANDLETYPE encoder;
|
||||
OMX_BUFFERHEADERTYPE *input_buffer;
|
||||
@@ -38,7 +41,6 @@ struct omx_encoder_t {
|
||||
bool failed;
|
||||
VCOS_SEMAPHORE_T handler_lock;
|
||||
|
||||
bool i_omx;
|
||||
bool i_handler_lock;
|
||||
bool i_encoder;
|
||||
bool i_input_port_enabled;
|
||||
@@ -49,5 +51,5 @@ struct omx_encoder_t {
|
||||
struct omx_encoder_t *omx_encoder_init();
|
||||
void omx_encoder_destroy(struct omx_encoder_t *omx);
|
||||
|
||||
int omx_encoder_prepare_for_device(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality, const bool use_ijg);
|
||||
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, const unsigned quality);
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, const unsigned index);
|
||||
|
||||
@@ -78,5 +78,5 @@ const char *omx_state_to_string(const OMX_STATETYPE state) {
|
||||
}
|
||||
}
|
||||
|
||||
#undef CASE_TO_STRING
|
||||
#undef CASE_ASSERT
|
||||
#undef CASE_TO_STRING
|
||||
|
||||
59
src/stream.c
59
src/stream.c
@@ -78,11 +78,11 @@ void stream_loop(struct stream_t *stream) {
|
||||
pool.workers_stop = &workers_stop;
|
||||
|
||||
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
|
||||
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
|
||||
|
||||
while (_stream_init_loop(stream->dev, &pool) == 0) {
|
||||
struct worker_t *oldest_worker = NULL;
|
||||
struct worker_t *last_worker = NULL;
|
||||
unsigned frames_count = 0;
|
||||
long double grab_after = 0;
|
||||
unsigned fluency_passed = 0;
|
||||
unsigned captured_fps_accum = 0;
|
||||
@@ -116,7 +116,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
|
||||
LOG_PERF("##### Raw frame accepted; worker = %u", free_worker_number);
|
||||
} else {
|
||||
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
|
||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
||||
if (!pool.workers[number].has_job && (free_worker_number == -1
|
||||
|| pool.workers[free_worker_number].job_start_time < pool.workers[number].job_start_time
|
||||
)) {
|
||||
@@ -183,15 +183,6 @@ void stream_loop(struct stream_t *stream) {
|
||||
}
|
||||
stream->dev->run->pictures[buf_info.index].grab_time = now;
|
||||
|
||||
if (stream->dev->every_frame) {
|
||||
if (frames_count < stream->dev->every_frame - 1) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Workaround for broken, corrupted frames:
|
||||
// Under low light conditions corrupted frames may get captured.
|
||||
// The good thing is such frames are quite small compared to the regular pictures.
|
||||
@@ -321,17 +312,29 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
|
||||
}
|
||||
|
||||
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool) {
|
||||
long double delay = 0;
|
||||
long double sum_comp_time = 0;
|
||||
long double avg_comp_time;
|
||||
long double min_delay;
|
||||
long double soft_delay;
|
||||
|
||||
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
|
||||
for (unsigned number = 0; number < dev->n_workers; ++number) {
|
||||
A_PTHREAD_M_LOCK(&pool->workers[number].last_comp_time_mutex);
|
||||
if (pool->workers[number].last_comp_time > 0) {
|
||||
delay += pool->workers[number].last_comp_time;
|
||||
sum_comp_time += pool->workers[number].last_comp_time;
|
||||
}
|
||||
A_PTHREAD_M_UNLOCK(&pool->workers[number].last_comp_time_mutex);
|
||||
}
|
||||
// Среднее арифметическое деленное на количество воркеров
|
||||
return delay / dev->run->n_workers / dev->run->n_workers;
|
||||
avg_comp_time = sum_comp_time / dev->n_workers; // Среднее время работы воркеров
|
||||
|
||||
min_delay = avg_comp_time / dev->n_workers; // Среднее время работы размазывается на N воркеров
|
||||
|
||||
if (dev->desired_fps > 0 && min_delay > 0) {
|
||||
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
|
||||
soft_delay = ((long double) 1) / dev->desired_fps - sum_comp_time;
|
||||
return (min_delay > soft_delay ? min_delay : soft_delay);
|
||||
}
|
||||
|
||||
return min_delay;
|
||||
}
|
||||
|
||||
static int _stream_init_loop(struct device_t *dev, struct workers_pool_t *pool) {
|
||||
@@ -363,7 +366,7 @@ static int _stream_init(struct device_t *dev, struct workers_pool_t *pool) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
encoder_prepare_for_device(pool->encoder, dev);
|
||||
encoder_prepare_live(pool->encoder, dev);
|
||||
|
||||
_stream_init_workers(dev, pool);
|
||||
|
||||
@@ -375,15 +378,15 @@ static int _stream_init(struct device_t *dev, struct workers_pool_t *pool) {
|
||||
}
|
||||
|
||||
static void _stream_init_workers(struct device_t *dev, struct workers_pool_t *pool) {
|
||||
LOG_INFO("Spawning %d workers ...", dev->run->n_workers);
|
||||
LOG_INFO("Spawning %d workers ...", dev->n_workers);
|
||||
|
||||
*pool->workers_stop = false;
|
||||
A_CALLOC(pool->workers, dev->run->n_workers);
|
||||
A_CALLOC(pool->workers, dev->n_workers);
|
||||
|
||||
A_PTHREAD_M_INIT(&pool->free_workers_mutex);
|
||||
A_PTHREAD_C_INIT(&pool->free_workers_cond);
|
||||
|
||||
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
|
||||
for (unsigned number = 0; number < dev->n_workers; ++number) {
|
||||
pool->free_workers += 1;
|
||||
|
||||
A_PTHREAD_M_INIT(&pool->workers[number].has_job_mutex);
|
||||
@@ -428,18 +431,22 @@ static void *_stream_worker_thread(void *v_ctx) {
|
||||
A_PTHREAD_C_WAIT_TRUE(*ctx->has_job, ctx->has_job_cond, ctx->has_job_mutex);
|
||||
A_PTHREAD_M_UNLOCK(ctx->has_job_mutex);
|
||||
|
||||
# define PICTURE(_next) ctx->dev->run->pictures[ctx->buf_index]._next
|
||||
|
||||
if (!*ctx->workers_stop) {
|
||||
LOG_DEBUG("Worker %u compressing JPEG from buffer %d ...", ctx->number, ctx->buf_index);
|
||||
|
||||
if (encoder_compress_buffer(ctx->encoder, ctx->dev, ctx->buf_index) < 0) {
|
||||
PICTURE(encode_begin_time) = get_now_monotonic();
|
||||
if (encoder_compress_buffer(ctx->encoder, ctx->dev, ctx->number, ctx->buf_index) < 0) {
|
||||
*ctx->job_failed = true;
|
||||
}
|
||||
PICTURE(encode_end_time) = get_now_monotonic();
|
||||
|
||||
if (_stream_release_buffer(ctx->dev, &ctx->buf_info) == 0) {
|
||||
*ctx->job_start_time = ctx->dev->run->pictures[ctx->buf_index].encode_begin_time;
|
||||
*ctx->job_start_time = PICTURE(encode_begin_time);
|
||||
*ctx->has_job = false;
|
||||
|
||||
long double last_comp_time = ctx->dev->run->pictures[ctx->buf_index].encode_end_time - *ctx->job_start_time;
|
||||
long double last_comp_time = PICTURE(encode_end_time) - *ctx->job_start_time;
|
||||
|
||||
A_PTHREAD_M_LOCK(ctx->last_comp_time_mutex);
|
||||
*ctx->last_comp_time = last_comp_time;
|
||||
@@ -447,7 +454,7 @@ static void *_stream_worker_thread(void *v_ctx) {
|
||||
|
||||
LOG_VERBOSE(
|
||||
"Compressed JPEG size=%ld; time=%0.3Lf; worker=%u; buffer=%d",
|
||||
ctx->dev->run->pictures[ctx->buf_index].size, last_comp_time, ctx->number, ctx->buf_index
|
||||
PICTURE(size), last_comp_time, ctx->number, ctx->buf_index
|
||||
);
|
||||
} else {
|
||||
*ctx->job_failed = true;
|
||||
@@ -455,6 +462,8 @@ static void *_stream_worker_thread(void *v_ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
# undef PICTURE
|
||||
|
||||
A_PTHREAD_M_LOCK(ctx->free_workers_mutex);
|
||||
*ctx->free_workers += 1;
|
||||
A_PTHREAD_M_UNLOCK(ctx->free_workers_mutex);
|
||||
@@ -470,7 +479,7 @@ static void _stream_destroy_workers(struct device_t *dev, struct workers_pool_t
|
||||
LOG_INFO("Destroying workers ...");
|
||||
|
||||
*pool->workers_stop = true;
|
||||
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
|
||||
for (unsigned number = 0; number < dev->n_workers; ++number) {
|
||||
A_PTHREAD_M_LOCK(&pool->workers[number].has_job_mutex);
|
||||
pool->workers[number].has_job = true; // Final job: die
|
||||
A_PTHREAD_M_UNLOCK(&pool->workers[number].has_job_mutex);
|
||||
|
||||
@@ -72,7 +72,7 @@ struct worker_t {
|
||||
};
|
||||
|
||||
struct workers_pool_t {
|
||||
struct worker_t*workers;
|
||||
struct worker_t *workers;
|
||||
bool *workers_stop;
|
||||
|
||||
pthread_mutex_t free_workers_mutex;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
@@ -57,6 +58,10 @@
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
|
||||
INLINE char *bool_to_string(const bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
INLINE unsigned max_u(unsigned a, unsigned b) {
|
||||
return (a > b ? a : b);
|
||||
}
|
||||
|
||||
78
tools/make-html-h.py
Executable file
78
tools/make-html-h.py
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
#============================================================================#
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
#============================================================================#
|
||||
|
||||
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
|
||||
# =====
|
||||
def main():
|
||||
assert len(sys.argv) == 4, "%s <src> <dest> <name>" % (sys.argv[0])
|
||||
|
||||
src = sys.argv[1]
|
||||
dest = sys.argv[2]
|
||||
name = sys.argv[3]
|
||||
|
||||
with open(src, "r") as html_file:
|
||||
text = html_file.read()
|
||||
|
||||
text = text.strip()
|
||||
text = text.replace("\"", "\\\"")
|
||||
text = text.replace("%VERSION%", "\" VERSION \"")
|
||||
text = textwrap.indent(text, "\t", (lambda line: True))
|
||||
text = "\n".join(("%s \\" if line.strip() else "%s\\") % (line) for line in text.split("\n"))
|
||||
text = "const char *%s = \" \\\n%s\n\";\n" % (name, text)
|
||||
text = textwrap.dedent("""
|
||||
/*****************************************************************************
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../config.h"
|
||||
""").strip() + "\n\n\n" + text
|
||||
|
||||
with open(dest, "w") as h_file:
|
||||
h_file.write(text)
|
||||
|
||||
|
||||
# =====
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -45,7 +45,7 @@ def main():
|
||||
rows[-1].append(hex(ch))
|
||||
|
||||
text = ",\n\t".join(", ".join(row) for row in rows)
|
||||
text = "const unsigned char %s_JPG_DATA[] = {\n\t%s\n};" % (prefix, text)
|
||||
text = "const unsigned char %s_JPG_DATA[] = {\n\t%s\n};\n" % (prefix, text)
|
||||
text = "const unsigned long %s_JPG_SIZE = %d;\n\n" % (prefix, len(jpg_data)) + text
|
||||
text = "const unsigned %s_JPG_HEIGHT = %d;\n\n" % (prefix, height) + text
|
||||
text = "const unsigned %s_JPG_WIDTH = %d;\n" % (prefix, width) + text
|
||||
Reference in New Issue
Block a user