configurable blank page

This commit is contained in:
Devaev Maxim
2019-04-14 03:35:25 +03:00
parent 93bfa56ccf
commit 0020aa69ec
5 changed files with 208 additions and 16 deletions

140
src/http/blank.c Normal file
View File

@@ -0,0 +1,140 @@
/*****************************************************************************
# #
# 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/>. #
# #
*****************************************************************************/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <jpeglib.h>
#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;
}

38
src/http/blank.h Normal file
View File

@@ -0,0 +1,38 @@
/*****************************************************************************
# #
# 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 <stdlib.h>
#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);

View File

@@ -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
}

View File

@@ -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;
};

View File

@@ -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 <str> ───────────── HTTP basic auth passwd. Default: empty.\n\n");
printf(" --static <path> ───────────── 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> ─────────── 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 <N> ── 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);