From 0020aa69ec23b8b3ffc5312120fd523a00075f33 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Sun, 14 Apr 2019 03:35:25 +0300 Subject: [PATCH] configurable blank page --- src/http/blank.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++ src/http/blank.h | 38 +++++++++++++ src/http/server.c | 32 ++++++----- src/http/server.h | 8 ++- src/main.c | 6 +- 5 files changed, 208 insertions(+), 16 deletions(-) create mode 100644 src/http/blank.c create mode 100644 src/http/blank.h diff --git a/src/http/blank.c b/src/http/blank.c new file mode 100644 index 0000000..e3955d9 --- /dev/null +++ b/src/http/blank.c @@ -0,0 +1,140 @@ +/***************************************************************************** +# # +# uStreamer - Lightweight and fast MJPG-HTTP streamer. # +# # +# Copyright (C) 2018 Maxim Devaev # +# # +# 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 . # +# # +*****************************************************************************/ + + +#include +#include +#include + +#include + +#include "../tools.h" +#include "../logging.h" + +#include "data/blank_jpeg.h" + +#include "blank.h" + + +static struct blank_t *_blank_init_internal(); +static struct blank_t *_blank_init_external(const char *path); + + +struct blank_t *blank_init(const char *path) { + struct blank_t *blank = NULL; + + if (path) { + blank = _blank_init_external(path); + } + + if (blank) { + LOG_INFO("Using external blank placeholder: %s", path); + } else { + blank = _blank_init_internal(); + LOG_INFO("Using internal blank placeholder"); + } + return blank; +} + +void blank_destroy(struct blank_t *blank) { + free(blank->picture.data); + free(blank); +} + +static struct blank_t *_blank_init_internal() { + struct blank_t *blank; + + A_CALLOC(blank, 1); + + A_CALLOC(blank->picture.data, ARRAY_LEN(BLANK_JPEG_DATA)); + memcpy(blank->picture.data, BLANK_JPEG_DATA, ARRAY_LEN(BLANK_JPEG_DATA) * sizeof(*blank->picture.data)); + + blank->picture.used = ARRAY_LEN(BLANK_JPEG_DATA); + blank->picture.allocated = ARRAY_LEN(BLANK_JPEG_DATA); + + blank->width = BLANK_JPEG_WIDTH; + blank->height = BLANK_JPEG_HEIGHT; + + return blank; +} + +static struct blank_t *_blank_init_external(const char *path) { + FILE *fp = NULL; + struct jpeg_error_mgr jpeg_error; + struct jpeg_decompress_struct jpeg; + struct blank_t *blank; + + A_CALLOC(blank, 1); + + if ((fp = fopen(path, "rb")) == NULL) { + LOG_PERROR("Can't open blank placeholder '%s'", path); + goto error; + } + + jpeg_create_decompress(&jpeg); + jpeg.err = jpeg_std_error(&jpeg_error); + jpeg_stdio_src(&jpeg, fp); + jpeg_read_header(&jpeg, TRUE); + jpeg_start_decompress(&jpeg); + + blank->width = jpeg.output_width; + blank->height = jpeg.output_height; + + jpeg_destroy_decompress(&jpeg); + + if (fseek(fp, 0, SEEK_SET) < 0) { + LOG_PERROR("Can't seek to begin of the blank placeholder"); + goto error; + } + +# define CHUNK_SIZE (100 * 1024) + while (true) { + if (blank->picture.used + CHUNK_SIZE >= blank->picture.allocated) { + blank->picture.allocated = blank->picture.used + CHUNK_SIZE * 2; + A_REALLOC(blank->picture.data, blank->picture.allocated); + } + + size_t readed = fread(blank->picture.data + blank->picture.used, sizeof(*blank->picture.data), CHUNK_SIZE, fp); + blank->picture.used += readed; + + if (readed < CHUNK_SIZE) { + if (feof(fp)) { + goto ok; + } else { + LOG_PERROR("Can't read blank placeholder"); + goto error; + } + } + } +# undef CHUNK_SIZE + + error: + free(blank->picture.data); + free(blank); + blank = NULL; + + ok: + if (fp) { + fclose(fp); + } + + return blank; +} diff --git a/src/http/blank.h b/src/http/blank.h new file mode 100644 index 0000000..5fa495c --- /dev/null +++ b/src/http/blank.h @@ -0,0 +1,38 @@ +/***************************************************************************** +# # +# uStreamer - Lightweight and fast MJPG-HTTP streamer. # +# # +# Copyright (C) 2018 Maxim Devaev # +# # +# 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 . # +# # +*****************************************************************************/ + + +#pragma once + +#include + +#include "../device.h" + + +struct blank_t { + struct picture_t picture; + unsigned width; + unsigned height; +}; + + +struct blank_t *blank_init(const char *path); +void blank_destroy(struct blank_t *blank); diff --git a/src/http/server.c b/src/http/server.c index d3cbea2..636a579 100644 --- a/src/http/server.c +++ b/src/http/server.c @@ -52,13 +52,13 @@ #include "../encoder.h" #include "../stream.h" +#include "blank.h" #include "base64.h" #include "mime.h" #include "static.h" #include "server.h" #include "data/index_html.h" -#include "data/blank_jpeg.h" static bool _http_get_param_true(struct evkeyvalq *params, const char *key); @@ -103,8 +103,6 @@ struct http_server_t *http_server_init(struct stream_t *stream) { server->timeout = 10; server->run = run; - _expose_blank_picture(server); - assert(!evthread_use_pthreads()); assert((run->base = event_base_new())); assert((run->http = evhttp_new(run->base))); @@ -137,6 +135,10 @@ void http_server_destroy(struct http_server_t *server) { free(server->run->auth_token); } + if (server->run->blank) { + blank_destroy(server->run->blank); + } + free(server->run->exposed->picture.data); free(server->run->exposed); free(server->run); @@ -155,6 +157,10 @@ int http_server_listen(struct http_server_t *server) { assert(!evhttp_set_cb(server->run->http, "/stream", _http_callback_stream, (void *)server)); } + server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank); + server->run->blank = blank_init(server->blank_path); + _expose_blank_picture(server); + { struct timeval refresh_interval; @@ -169,8 +175,6 @@ int http_server_listen(struct http_server_t *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); - if (server->slowdown) { stream_switch_slowdown(server->run->stream, true); } @@ -878,28 +882,28 @@ static bool _expose_new_picture(struct http_server_t *server) { } static bool _expose_blank_picture(struct http_server_t *server) { +# define BLANK(_next) server->run->blank->_next # define EXPOSED(_next) server->run->exposed->_next -# define BLANK_JPEG_LEN ARRAY_LEN(BLANK_JPEG_DATA) EXPOSED(expose_begin_time) = get_now_monotonic(); EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time); if (EXPOSED(online) || EXPOSED(picture.used) == 0) { - if (EXPOSED(picture.allocated) < BLANK_JPEG_LEN) { - A_REALLOC(EXPOSED(picture.data), BLANK_JPEG_LEN); - EXPOSED(picture.allocated) = BLANK_JPEG_LEN; + if (EXPOSED(picture.allocated) < BLANK(picture.used)) { + A_REALLOC(EXPOSED(picture.data), BLANK(picture.used)); + EXPOSED(picture.allocated) = BLANK(picture.used); } - memcpy(EXPOSED(picture.data), BLANK_JPEG_DATA, BLANK_JPEG_LEN * sizeof(*EXPOSED(picture.data))); + memcpy(EXPOSED(picture.data), BLANK(picture.data), BLANK(picture.used) * sizeof(*EXPOSED(picture.data))); - EXPOSED(picture.used) = BLANK_JPEG_LEN; + EXPOSED(picture.used) = BLANK(picture.used); EXPOSED(picture.grab_time) = 0; EXPOSED(picture.encode_begin_time) = 0; EXPOSED(picture.encode_end_time) = 0; - EXPOSED(width) = BLANK_JPEG_WIDTH; - EXPOSED(height) = BLANK_JPEG_HEIGHT; + EXPOSED(width) = BLANK(width); + EXPOSED(height) = BLANK(height); EXPOSED(captured_fps) = 0; EXPOSED(online) = false; goto updated; @@ -917,6 +921,6 @@ static bool _expose_blank_picture(struct http_server_t *server) { EXPOSED(expose_end_time) = get_now_monotonic(); return true; // Updated -# undef BLANK_JPEG_LEN # undef EXPOSED +# undef BLANK } diff --git a/src/http/server.h b/src/http/server.h index 9ca4622..1f9713c 100644 --- a/src/http/server.h +++ b/src/http/server.h @@ -33,6 +33,8 @@ #include "../tools.h" #include "../stream.h" +#include "blank.h" + struct stream_client_t { struct http_server_t *server; @@ -78,6 +80,7 @@ struct http_server_runtime_t { struct exposed_t *exposed; struct stream_client_t *stream_clients; unsigned stream_clients_count; + struct blank_t *blank; unsigned drop_same_frames_blank; }; @@ -87,14 +90,17 @@ struct http_server_t { char *unix_path; bool unix_rm; mode_t unix_mode; + unsigned timeout; + char *user; char *passwd; char *static_path; + + char *blank_path; unsigned drop_same_frames; bool slowdown; unsigned fake_width; unsigned fake_height; - unsigned timeout; struct http_server_runtime_t *run; }; diff --git a/src/main.c b/src/main.c index 8f98556..aae4bfd 100644 --- a/src/main.c +++ b/src/main.c @@ -41,7 +41,7 @@ #include "http/server.h" -static const char _SHORT_OPTS[] = "d:i:x:y:m:a:f:z:ntb:w:q:c:s:p:u:ro:e:lhv"; +static const char _SHORT_OPTS[] = "d:i:x:y:m:a:f:z:ntb:w:q:c:s:p:u:ro:k:e:lhv"; static const struct option _LONG_OPTS[] = { {"device", required_argument, NULL, 'd'}, {"input", required_argument, NULL, 'i'}, @@ -82,6 +82,7 @@ static const struct option _LONG_OPTS[] = { {"user", required_argument, NULL, 3000}, {"passwd", required_argument, NULL, 3001}, {"static", required_argument, NULL, 3002}, + {"blank", required_argument, NULL, 'k'}, {"drop-same-frames", required_argument, NULL, 'e'}, {"slowdown", no_argument, NULL, 'l'}, {"fake-width", required_argument, NULL, 3003}, @@ -166,6 +167,8 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s printf(" --passwd ───────────── HTTP basic auth passwd. Default: empty.\n\n"); printf(" --static ───────────── Path to dir with static files instead of embedded root index page.\n"); printf(" Symlinks are not supported for security reasons. Default: disabled.\n\n"); + printf(" -k|--blank ─────────── Path to JPEG file that will be shown when the device is disconnected\n"); + printf(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n\n"); printf(" -e|--drop-same-frames ── Don't send same frames to clients, but no more than specified number.\n"); printf(" It can significantly reduce the outgoing traffic, but will increase\n"); printf(" the CPU loading. Don't use this option with analog signal sources\n"); @@ -284,6 +287,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e case 3000: OPT_SET(server->user, optarg); case 3001: OPT_SET(server->passwd, optarg); case 3002: OPT_SET(server->static_path, optarg); + case 'k': OPT_SET(server->blank_path, optarg); case 'e': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30); case 'l': OPT_SET(server->slowdown, true); case 3003: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);