Complete workaround for Chrome frames processor

https://bugs.chromium.org/p/chromium/issues/detail?id=527446
This commit is contained in:
Devaev Maxim
2018-11-03 02:54:30 +03:00
parent fb6331b64a
commit 5a7f3d30a7
2 changed files with 94 additions and 58 deletions

View File

@@ -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>
@@ -44,6 +46,8 @@
#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);
@@ -141,6 +145,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))
@@ -191,8 +206,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, "}}}"));
@@ -254,6 +272,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;
@@ -270,6 +289,10 @@ 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), &params);
client->advance_headers = _http_get_param_true(&params, "advance_headers");
evhttp_clear_headers(&params);
uuid_generate(uuid); uuid_generate(uuid);
uuid_unparse_lower(uuid, client->id); uuid_unparse_lower(uuid, client->id);
@@ -318,6 +341,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
@@ -331,42 +377,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->server->extra_stream_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->server->extra_stream_headers) {
assert(evbuffer_add_printf(buf,
"X-UStreamer-Online: %s" RN
"X-UStreamer-Client-FPS: %u" RN
"X-UStreamer-Grab-Time: %.06Lf" RN
"X-UStreamer-Encode-Begin-Time: %.06Lf" RN
"X-UStreamer-Encode-End-Time: %.06Lf" RN
"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,
@@ -375,6 +428,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);
@@ -383,6 +440,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
} }
@@ -449,17 +507,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;
@@ -467,33 +521,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) {

View File

@@ -32,6 +32,7 @@ struct stream_client_t {
struct http_server_t *server; struct http_server_t *server;
struct evhttp_request *request; struct evhttp_request *request;
char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0" char id[37]; // ex. "1b4e28ba-2fa1-11d2-883f-0016d3cca427" + "\0"
bool advance_headers;
bool need_initial; bool need_initial;
bool need_first_frame; bool need_first_frame;
unsigned fps; unsigned fps;