mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 20:26:31 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab437d402b | ||
|
|
79d9214084 | ||
|
|
b5db16e1ba | ||
|
|
a68e27f09e | ||
|
|
4af8c6a121 | ||
|
|
693c89ae6b | ||
|
|
8be5d6d370 | ||
|
|
5a7f3d30a7 | ||
|
|
fb6331b64a |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 0.26
|
current_version = 0.28
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -29,7 +29,8 @@ install: $(PROG)
|
|||||||
|
|
||||||
|
|
||||||
regen:
|
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.h BLANK 640 480
|
||||||
|
tools/make-html-h.py src/data/index.html src/data/html_index.h HTML_INDEX_PAGE
|
||||||
|
|
||||||
|
|
||||||
$(PROG): $(OBJECTS)
|
$(PROG): $(OBJECTS)
|
||||||
|
|||||||
2
PKGBUILD
2
PKGBUILD
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=0.26
|
pkgver=0.28
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||||
url="https://github.com/pi-kvm/ustreamer"
|
url="https://github.com/pi-kvm/ustreamer"
|
||||||
|
|||||||
@@ -21,4 +21,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define VERSION "0.26"
|
#define VERSION "0.28"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
74
src/data/html_index.h
Normal file
74
src/data/html_index.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# 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=\"/ping\"><b>/ping</b></a><br> \
|
||||||
|
Get JSON structure with state of the server. \
|
||||||
|
</li> \
|
||||||
|
<br> \
|
||||||
|
<li> \
|
||||||
|
<a href=\"/snapshot\"><b>/snapshot</b></a><br> \
|
||||||
|
Get a current actual image from server. \
|
||||||
|
</li> \
|
||||||
|
<br> \
|
||||||
|
<li> \
|
||||||
|
<a href=\"/stream\"><b>/stream</b></a><br> \
|
||||||
|
Get a live stream. Query params:<br> \
|
||||||
|
<br> \
|
||||||
|
<ul> \
|
||||||
|
<li> \
|
||||||
|
<i>extra_headers=1</i><br> \
|
||||||
|
Add X-UStreamer-* headers to /stream handle (like on <a href=\"/snapshot\">/snapshot</a>). \
|
||||||
|
</li> \
|
||||||
|
<br> \
|
||||||
|
<li> \
|
||||||
|
<i>advance_headers=1</i><br> \
|
||||||
|
Enable workaround for Chromium/Blink \
|
||||||
|
<a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=527446\">Bug #527446</a>. \
|
||||||
|
</li> \
|
||||||
|
</ul> \
|
||||||
|
</li> \
|
||||||
|
<br> \
|
||||||
|
</ul> \
|
||||||
|
<br> \
|
||||||
|
<hr> \
|
||||||
|
<a href=\"https://github.com/pi-kvm/ustreamer\">Sources & docs</a> \
|
||||||
|
</body> \
|
||||||
|
</html> \
|
||||||
|
";
|
||||||
46
src/data/index.html
Normal file
46
src/data/index.html
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset=\"utf-8\">
|
||||||
|
<title>uStreamer</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h3>µStreamer v%VERSION%</h3>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href=\"/ping\"><b>/ping</b></a><br>
|
||||||
|
Get JSON structure with state of the server.
|
||||||
|
</li>
|
||||||
|
<br>
|
||||||
|
<li>
|
||||||
|
<a href=\"/snapshot\"><b>/snapshot</b></a><br>
|
||||||
|
Get a current actual image from server.
|
||||||
|
</li>
|
||||||
|
<br>
|
||||||
|
<li>
|
||||||
|
<a href=\"/stream\"><b>/stream</b></a><br>
|
||||||
|
Get a live stream. Query params:<br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<i>extra_headers=1</i><br>
|
||||||
|
Add X-UStreamer-* headers to /stream handle (like on <a href=\"/snapshot\">/snapshot</a>).
|
||||||
|
</li>
|
||||||
|
<br>
|
||||||
|
<li>
|
||||||
|
<i>advance_headers=1</i><br>
|
||||||
|
Enable workaround for Chromium/Blink
|
||||||
|
<a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=527446\">Bug #527446</a>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<br>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<hr>
|
||||||
|
<a href=\"https://github.com/pi-kvm/ustreamer\">Sources & docs</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -84,6 +84,7 @@ struct device_t *device_init() {
|
|||||||
dev->standard = V4L2_STD_UNKNOWN;
|
dev->standard = V4L2_STD_UNKNOWN;
|
||||||
dev->n_buffers = max_u(sysconf(_SC_NPROCESSORS_ONLN), 1) + 1;
|
dev->n_buffers = max_u(sysconf(_SC_NPROCESSORS_ONLN), 1) + 1;
|
||||||
dev->n_workers = dev->n_buffers;
|
dev->n_workers = dev->n_buffers;
|
||||||
|
dev->soft_fps = 30;
|
||||||
dev->timeout = 1;
|
dev->timeout = 1;
|
||||||
dev->error_delay = 1;
|
dev->error_delay = 1;
|
||||||
dev->run = run;
|
dev->run = run;
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ struct device_t {
|
|||||||
bool dv_timings;
|
bool dv_timings;
|
||||||
unsigned n_buffers;
|
unsigned n_buffers;
|
||||||
unsigned n_workers;
|
unsigned n_workers;
|
||||||
|
unsigned soft_fps;
|
||||||
unsigned every_frame;
|
unsigned every_frame;
|
||||||
unsigned min_frame_size;
|
unsigned min_frame_size;
|
||||||
bool persistent;
|
bool persistent;
|
||||||
|
|||||||
191
src/http.c
191
src/http.c
@@ -22,6 +22,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
#include <event2/http.h>
|
#include <event2/http.h>
|
||||||
#include <event2/buffer.h>
|
#include <event2/buffer.h>
|
||||||
#include <event2/bufferevent.h>
|
#include <event2/bufferevent.h>
|
||||||
|
#include <event2/keyvalq_struct.h>
|
||||||
|
|
||||||
#include <uuid/uuid.h>
|
#include <uuid/uuid.h>
|
||||||
|
|
||||||
@@ -41,9 +43,12 @@
|
|||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
|
||||||
|
#include "data/html_index.h"
|
||||||
#include "data/blank.h"
|
#include "data/blank.h"
|
||||||
|
|
||||||
|
|
||||||
|
static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
|
||||||
|
|
||||||
static void _http_callback_root(struct evhttp_request *request, void *arg);
|
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_ping(struct evhttp_request *request, void *v_server);
|
||||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server);
|
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server);
|
||||||
@@ -63,7 +68,6 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
|||||||
struct http_server_runtime_t *run;
|
struct http_server_runtime_t *run;
|
||||||
struct http_server_t *server;
|
struct http_server_t *server;
|
||||||
struct exposed_t *exposed;
|
struct exposed_t *exposed;
|
||||||
struct timeval refresh_interval;
|
|
||||||
|
|
||||||
A_CALLOC(exposed, 1);
|
A_CALLOC(exposed, 1);
|
||||||
|
|
||||||
@@ -90,17 +94,15 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
|||||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (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));
|
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;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_server_destroy(struct http_server_t *server) {
|
void http_server_destroy(struct http_server_t *server) {
|
||||||
event_del(server->run->refresh);
|
if (server->run->refresh) {
|
||||||
event_free(server->run->refresh);
|
event_del(server->run->refresh);
|
||||||
|
event_free(server->run->refresh);
|
||||||
|
}
|
||||||
|
|
||||||
evhttp_free(server->run->http);
|
evhttp_free(server->run->http);
|
||||||
event_base_free(server->run->base);
|
event_base_free(server->run->base);
|
||||||
libevent_global_shutdown();
|
libevent_global_shutdown();
|
||||||
@@ -112,6 +114,13 @@ void http_server_destroy(struct http_server_t *server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int http_server_listen(struct http_server_t *server) {
|
int http_server_listen(struct http_server_t *server) {
|
||||||
|
struct timeval refresh_interval;
|
||||||
|
|
||||||
|
refresh_interval.tv_sec = 0;
|
||||||
|
refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->soft_fps * 2);
|
||||||
|
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);
|
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
|
||||||
|
|
||||||
LOG_DEBUG("Binding HTTP to [%s]:%d ...", server->host, server->port);
|
LOG_DEBUG("Binding HTTP to [%s]:%d ...", server->host, server->port);
|
||||||
@@ -137,6 +146,17 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
#define ADD_HEADER(_key, _value) \
|
#define ADD_HEADER(_key, _value) \
|
||||||
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
||||||
|
|
||||||
@@ -153,14 +173,7 @@ static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg
|
|||||||
PROCESS_HEAD_REQUEST;
|
PROCESS_HEAD_REQUEST;
|
||||||
|
|
||||||
assert((buf = evbuffer_new()));
|
assert((buf = evbuffer_new()));
|
||||||
assert(evbuffer_add_printf(buf,
|
assert(evbuffer_add_printf(buf, HTML_INDEX_PAGE));
|
||||||
"<!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>"
|
|
||||||
));
|
|
||||||
ADD_HEADER("Content-Type", "text/html");
|
ADD_HEADER("Content-Type", "text/html");
|
||||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||||
evbuffer_free(buf);
|
evbuffer_free(buf);
|
||||||
@@ -187,8 +200,11 @@ static void _http_callback_ping(struct evhttp_request *request, void *v_server)
|
|||||||
));
|
));
|
||||||
for (struct stream_client_t * 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) {
|
||||||
assert(evbuffer_add_printf(buf,
|
assert(evbuffer_add_printf(buf,
|
||||||
"\"%s\": {\"fps\": %u}%s",
|
"\"%s\": {\"fps\": %u, \"advance_headers\": %s}%s",
|
||||||
client->id, client->fps, (client->next ? ", " : "")
|
client->id,
|
||||||
|
client->fps,
|
||||||
|
(client->advance_headers ? "true" : "false"),
|
||||||
|
(client->next ? ", " : "")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
assert(evbuffer_add_printf(buf, "}}}"));
|
assert(evbuffer_add_printf(buf, "}}}"));
|
||||||
@@ -250,6 +266,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 http_server_t *server = (struct http_server_t *)v_server;
|
||||||
struct evhttp_connection *conn;
|
struct evhttp_connection *conn;
|
||||||
|
struct evkeyvalq params;
|
||||||
struct bufferevent *buf_event;
|
struct bufferevent *buf_event;
|
||||||
struct stream_client_t *client;
|
struct stream_client_t *client;
|
||||||
char *client_addr;
|
char *client_addr;
|
||||||
@@ -266,6 +283,11 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
client->need_initial = true;
|
client->need_initial = true;
|
||||||
client->need_first_frame = true;
|
client->need_first_frame = true;
|
||||||
|
|
||||||
|
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||||
|
client->extra_headers = _http_get_param_true(¶ms, "extra_headers");
|
||||||
|
client->advance_headers = _http_get_param_true(¶ms, "advance_headers");
|
||||||
|
evhttp_clear_headers(¶ms);
|
||||||
|
|
||||||
uuid_generate(uuid);
|
uuid_generate(uuid);
|
||||||
uuid_unparse_lower(uuid, client->id);
|
uuid_unparse_lower(uuid, client->id);
|
||||||
|
|
||||||
@@ -314,6 +336,29 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
|
|
||||||
assert((buf = evbuffer_new()));
|
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) {
|
if (client->need_initial) {
|
||||||
assert(evbuffer_add_printf(buf,
|
assert(evbuffer_add_printf(buf,
|
||||||
"HTTP/1.0 200 OK" RN
|
"HTTP/1.0 200 OK" RN
|
||||||
@@ -327,42 +372,49 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
"--" BOUNDARY RN,
|
"--" BOUNDARY RN,
|
||||||
client->id
|
client->id
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if (client->advance_headers) {
|
||||||
|
ADD_ADVANCE_HEADERS;
|
||||||
|
}
|
||||||
|
|
||||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||||
client->need_initial = false;
|
client->need_initial = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
# define EXPOSED(_next) client->server->run->exposed->_next
|
# define EXPOSED(_next) client->server->run->exposed->_next
|
||||||
|
|
||||||
assert(evbuffer_add_printf(buf,
|
if (!client->advance_headers) {
|
||||||
"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) {
|
|
||||||
assert(evbuffer_add_printf(buf,
|
assert(evbuffer_add_printf(buf,
|
||||||
"X-UStreamer-Online: %s" RN
|
"Content-Type: image/jpeg" RN
|
||||||
"X-UStreamer-Client-FPS: %u" RN
|
"Content-Length: %lu" RN
|
||||||
"X-UStreamer-Grab-Time: %.06Lf" RN
|
"X-Timestamp: %.06Lf" RN
|
||||||
"X-UStreamer-Encode-Begin-Time: %.06Lf" RN
|
"%s",
|
||||||
"X-UStreamer-Encode-End-Time: %.06Lf" RN
|
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data)),
|
||||||
"X-UStreamer-Expose-Begin-Time: %.06Lf" RN
|
get_now_real(), (client->extra_headers ? "" : 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
|
|
||||||
));
|
));
|
||||||
|
if (client->extra_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
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!evbuffer_add(buf,
|
assert(!evbuffer_add(buf,
|
||||||
@@ -371,6 +423,10 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
));
|
));
|
||||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||||
|
|
||||||
|
if (client->advance_headers) {
|
||||||
|
ADD_ADVANCE_HEADERS;
|
||||||
|
}
|
||||||
|
|
||||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||||
evbuffer_free(buf);
|
evbuffer_free(buf);
|
||||||
|
|
||||||
@@ -379,6 +435,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
|
|
||||||
# undef BOUNDARY
|
# undef BOUNDARY
|
||||||
# undef RN
|
# undef RN
|
||||||
|
# undef ADD_ADVANCE_HEADERS
|
||||||
# undef EXPOSED
|
# undef EXPOSED
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,17 +502,13 @@ 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) {
|
static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||||
struct http_server_t *server = (struct http_server_t *)v_server;
|
struct http_server_t *server = (struct http_server_t *)v_server;
|
||||||
bool updated = false;
|
bool updated = false;
|
||||||
bool queue_send = false;
|
|
||||||
|
|
||||||
#define LOCK_STREAM \
|
# define UNLOCK_STREAM \
|
||||||
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
|
{ 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) {
|
if (server->run->stream->updated) {
|
||||||
LOG_DEBUG("Refreshing HTTP exposed ...");
|
LOG_DEBUG("Refreshing HTTP exposed ...");
|
||||||
LOCK_STREAM;
|
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
|
||||||
if (server->run->stream->picture.size > 0) { // If online
|
if (server->run->stream->picture.size > 0) { // If online
|
||||||
updated = _expose_new_picture(server);
|
updated = _expose_new_picture(server);
|
||||||
UNLOCK_STREAM;
|
UNLOCK_STREAM;
|
||||||
@@ -463,33 +516,14 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
|
|||||||
UNLOCK_STREAM;
|
UNLOCK_STREAM;
|
||||||
updated = _expose_blank_picture(server);
|
updated = _expose_blank_picture(server);
|
||||||
}
|
}
|
||||||
queue_send = true;
|
|
||||||
} else if (!server->run->exposed->online) {
|
} else if (!server->run->exposed->online) {
|
||||||
LOG_DEBUG("Refreshing HTTP exposed (BLANK) ...");
|
LOG_DEBUG("Refreshing HTTP exposed (BLANK) ...");
|
||||||
updated = _expose_blank_picture(server);
|
updated = _expose_blank_picture(server);
|
||||||
queue_send = 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
|
# undef UNLOCK_STREAM
|
||||||
|
|
||||||
|
_http_queue_send_stream(server, updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _expose_new_picture(struct http_server_t *server) {
|
static bool _expose_new_picture(struct http_server_t *server) {
|
||||||
@@ -513,7 +547,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
|||||||
) {
|
) {
|
||||||
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
||||||
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
|
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
|
||||||
LOG_PERF(
|
LOG_VERBOSE(
|
||||||
"HTTP: dropped same frame number %u; comparsion time = %.06Lf",
|
"HTTP: dropped same frame number %u; comparsion time = %.06Lf",
|
||||||
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
|
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
|
||||||
);
|
);
|
||||||
@@ -521,7 +555,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
|||||||
return false; // Not updated
|
return false; // Not updated
|
||||||
} else {
|
} else {
|
||||||
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
||||||
LOG_PERF(
|
LOG_VERBOSE(
|
||||||
"HTTP: passed same frame check (frames are differ); comparsion time = %.06Lf",
|
"HTTP: passed same frame check (frames are differ); comparsion time = %.06Lf",
|
||||||
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
|
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)
|
||||||
);
|
);
|
||||||
@@ -550,6 +584,11 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
|||||||
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
||||||
EXPOSED(expose_end_time) = get_now_monotonic();
|
EXPOSED(expose_end_time) = get_now_monotonic();
|
||||||
|
|
||||||
|
LOG_VERBOSE(
|
||||||
|
"HTTP: exposed new frame; full exposition time = %.06Lf",
|
||||||
|
EXPOSED(expose_end_time) - EXPOSED(expose_begin_time)
|
||||||
|
);
|
||||||
|
|
||||||
# undef STREAM
|
# undef STREAM
|
||||||
# undef EXPOSED
|
# undef EXPOSED
|
||||||
return true; // Updated
|
return true; // Updated
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ struct stream_client_t {
|
|||||||
struct http_server_t *server;
|
struct http_server_t *server;
|
||||||
struct evhttp_request *request;
|
struct evhttp_request *request;
|
||||||
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
|
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
|
||||||
|
bool extra_headers;
|
||||||
|
bool advance_headers;
|
||||||
bool need_initial;
|
bool need_initial;
|
||||||
bool need_first_frame;
|
bool need_first_frame;
|
||||||
unsigned fps;
|
unsigned fps;
|
||||||
@@ -70,7 +72,6 @@ struct http_server_t {
|
|||||||
char *host;
|
char *host;
|
||||||
unsigned port;
|
unsigned port;
|
||||||
unsigned drop_same_frames;
|
unsigned drop_same_frames;
|
||||||
bool extra_stream_headers;
|
|
||||||
unsigned fake_width;
|
unsigned fake_width;
|
||||||
unsigned fake_height;
|
unsigned fake_height;
|
||||||
unsigned timeout;
|
unsigned timeout;
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ static const struct option _long_opts[] = {
|
|||||||
{"height", required_argument, NULL, 'y'},
|
{"height", required_argument, NULL, 'y'},
|
||||||
{"format", required_argument, NULL, 'f'},
|
{"format", required_argument, NULL, 'f'},
|
||||||
{"tv-standard", required_argument, NULL, 'a'},
|
{"tv-standard", required_argument, NULL, 'a'},
|
||||||
|
{"soft-fps", required_argument, NULL, 'm'},
|
||||||
{"every-frame", required_argument, NULL, 'e'},
|
{"every-frame", required_argument, NULL, 'e'},
|
||||||
{"min-frame-size", required_argument, NULL, 'z'},
|
{"min-frame-size", required_argument, NULL, 'z'},
|
||||||
{"dv-timings", no_argument, NULL, 't'},
|
{"dv-timings", no_argument, NULL, 't'},
|
||||||
@@ -65,7 +66,6 @@ static const struct option _long_opts[] = {
|
|||||||
{"host", required_argument, NULL, 's'},
|
{"host", required_argument, NULL, 's'},
|
||||||
{"port", required_argument, NULL, 'p'},
|
{"port", required_argument, NULL, 'p'},
|
||||||
{"drop-same-frames", required_argument, NULL, 'r'},
|
{"drop-same-frames", required_argument, NULL, 'r'},
|
||||||
{"extra-stream-headers", no_argument, NULL, 2000},
|
|
||||||
{"fake-width", required_argument, NULL, 2001},
|
{"fake-width", required_argument, NULL, 2001},
|
||||||
{"fake-height", required_argument, NULL, 2002},
|
{"fake-height", required_argument, NULL, 2002},
|
||||||
{"server-timeout", required_argument, NULL, 2003},
|
{"server-timeout", required_argument, NULL, 2003},
|
||||||
@@ -106,6 +106,7 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
|||||||
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
|
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
|
||||||
printf(" -a|--tv-standard <std> -- Force TV standard.\n");
|
printf(" -a|--tv-standard <std> -- Force TV standard.\n");
|
||||||
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
|
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
|
||||||
|
printf(" -m|--soft-fps <N> -- Soft FPS limit; default: %u.\n\n", dev->soft_fps);
|
||||||
printf(" -e|--every-frame <N> -- Drop all input frames except specified. Default: disabled.\n\n");
|
printf(" -e|--every-frame <N> -- Drop all input frames except specified. Default: disabled.\n\n");
|
||||||
printf(" -z|--min-frame-size <N> -- Drop frames smaller then this limit.\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(" Useful if the device produces small-sized garbage frames.\n\n");
|
||||||
@@ -134,8 +135,6 @@ 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(" 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(" the CPU loading. Don't use this option with analog signal sources\n");
|
||||||
printf(" or webcams, it's useless. Default: disabled.\n\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-width <N> -- Override image width for /ping. Default: disabled\n\n");
|
||||||
printf(" --fake-height <N> -- Override image height for /ping. Default: disabled.\n\n");
|
printf(" --fake-height <N> -- Override image height for /ping. Default: disabled.\n\n");
|
||||||
printf(" --server-timeout <seconds> -- Timeout for client connections. Default: %d\n\n", server->timeout);
|
printf(" --server-timeout <seconds> -- Timeout for client connections. Default: %d\n\n", server->timeout);
|
||||||
@@ -181,6 +180,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
|||||||
case 'f': OPT_PARSE(dev->format, device_parse_format, FORMAT_UNKNOWN, "pixel format");
|
case 'f': OPT_PARSE(dev->format, device_parse_format, FORMAT_UNKNOWN, "pixel format");
|
||||||
# pragma GCC diagnostic pop
|
# pragma GCC diagnostic pop
|
||||||
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard");
|
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard");
|
||||||
|
case 'm': OPT_UNSIGNED(dev->soft_fps, "--soft-fps", 1, 30);
|
||||||
case 'e': OPT_UNSIGNED(dev->every_frame, "--every-frame", 1, 30);
|
case 'e': OPT_UNSIGNED(dev->every_frame, "--every-frame", 1, 30);
|
||||||
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192);
|
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192);
|
||||||
case 't': OPT_SET(dev->dv_timings, true);
|
case 't': OPT_SET(dev->dv_timings, true);
|
||||||
@@ -198,7 +198,6 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
|||||||
case 's': OPT_SET(server->host, optarg);
|
case 's': OPT_SET(server->host, optarg);
|
||||||
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535);
|
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535);
|
||||||
case 'r': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
|
case '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 2001: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
||||||
case 2002: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
|
case 2002: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
|
||||||
case 2003: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
case 2003: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
|
||||||
|
|||||||
17
src/stream.c
17
src/stream.c
@@ -321,17 +321,26 @@ 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) {
|
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool) {
|
||||||
long double delay = 0;
|
long double comp_time = 0;
|
||||||
|
long double min_delay;
|
||||||
|
long double soft_delay;
|
||||||
|
|
||||||
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
|
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
|
||||||
A_PTHREAD_M_LOCK(&pool->workers[number].last_comp_time_mutex);
|
A_PTHREAD_M_LOCK(&pool->workers[number].last_comp_time_mutex);
|
||||||
if (pool->workers[number].last_comp_time > 0) {
|
if (pool->workers[number].last_comp_time > 0) {
|
||||||
delay += pool->workers[number].last_comp_time;
|
comp_time += pool->workers[number].last_comp_time;
|
||||||
}
|
}
|
||||||
A_PTHREAD_M_UNLOCK(&pool->workers[number].last_comp_time_mutex);
|
A_PTHREAD_M_UNLOCK(&pool->workers[number].last_comp_time_mutex);
|
||||||
}
|
}
|
||||||
// Среднее арифметическое деленное на количество воркеров
|
comp_time = comp_time / dev->run->n_workers; // Среднее время работы воркеров
|
||||||
return delay / dev->run->n_workers / dev->run->n_workers;
|
|
||||||
|
min_delay = comp_time / dev->run->n_workers; // Минимальное время работы размазывается на N воркеров
|
||||||
|
soft_delay = ((long double)1) / dev->soft_fps; // Искусственное время задержки на основе желаемого FPS
|
||||||
|
|
||||||
|
if (min_delay > 0) {
|
||||||
|
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) {
|
static int _stream_init_loop(struct device_t *dev, struct workers_pool_t *pool) {
|
||||||
|
|||||||
77
tools/make-html-h.py
Executable file
77
tools/make-html-h.py
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/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("%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))
|
rows[-1].append(hex(ch))
|
||||||
|
|
||||||
text = ",\n\t".join(", ".join(row) for row in rows)
|
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 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_HEIGHT = %d;\n\n" % (prefix, height) + text
|
||||||
text = "const unsigned %s_JPG_WIDTH = %d;\n" % (prefix, width) + text
|
text = "const unsigned %s_JPG_WIDTH = %d;\n" % (prefix, width) + text
|
||||||
Reference in New Issue
Block a user