mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-28 12:46:32 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c34d644c2a | ||
|
|
7857fa8f63 | ||
|
|
3253de83dc | ||
|
|
030464c3b8 | ||
|
|
0020aa69ec | ||
|
|
93bfa56ccf | ||
|
|
3392ac5fbc | ||
|
|
2077d94edc | ||
|
|
defe5eb6fe | ||
|
|
dee5e18134 |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 0.67
|
current_version = 0.69
|
||||||
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}
|
||||||
@@ -14,3 +14,7 @@ replace = VERSION "{new_version}"
|
|||||||
search = pkgver={current_version}
|
search = pkgver={current_version}
|
||||||
replace = pkgver={new_version}
|
replace = pkgver={new_version}
|
||||||
|
|
||||||
|
[bumpversion:file:pkg/openwrt/Makefile]
|
||||||
|
search = PKG_VERSION:={current_version}
|
||||||
|
replace = PKG_VERSION:={new_version}
|
||||||
|
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -50,7 +50,7 @@ release:
|
|||||||
|
|
||||||
|
|
||||||
bump:
|
bump:
|
||||||
bumpversion minor
|
bumpversion $(if $(V), $(V), minor)
|
||||||
|
|
||||||
|
|
||||||
push:
|
push:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=0.67
|
pkgver=0.69
|
||||||
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"
|
||||||
@@ -19,7 +19,7 @@ md5sums=(SKIP)
|
|||||||
build() {
|
build() {
|
||||||
cd $srcdir
|
cd $srcdir
|
||||||
rm -rf $pkgname-build
|
rm -rf $pkgname-build
|
||||||
cp -r ustreamer-$pkgver $pkgname-build
|
cp -r $pkgname-$pkgver $pkgname-build
|
||||||
cd $pkgname-build
|
cd $pkgname-build
|
||||||
make CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
|
make CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ EAPI=7
|
|||||||
|
|
||||||
inherit git-r3
|
inherit git-r3
|
||||||
|
|
||||||
DESCRIPTION="µStreamer - Lightweight and fast MJPG-HTTP streamer"
|
DESCRIPTION="uStreamer - Lightweight and fast MJPG-HTTP streamer"
|
||||||
HOMEPAGE="https://github.com/pi-kvm/ustreamer"
|
HOMEPAGE="https://github.com/pi-kvm/ustreamer"
|
||||||
EGIT_REPO_URI="https://github.com/pi-kvm/ustreamer.git"
|
EGIT_REPO_URI="https://github.com/pi-kvm/ustreamer.git"
|
||||||
|
|
||||||
|
|||||||
45
pkg/openwrt/Makefile
Normal file
45
pkg/openwrt/Makefile
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#
|
||||||
|
# This is free software, licensed under the GNU General Public License v2.
|
||||||
|
# See /LICENSE for more information.
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=ustreamer
|
||||||
|
PKG_VERSION:=0.69
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
PKG_SOURCE_PROTO:=git
|
||||||
|
PKG_SOURCE_URL:=https://github.com/pi-kvm/ustreamer.git
|
||||||
|
PKG_SOURCE_VERSION:=v$(PKG_VERSION)
|
||||||
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
|
||||||
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||||
|
|
||||||
|
PKG_LICENSE:=GPL-3.0
|
||||||
|
PKG_LICENSE_FILES:=LICENSE
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
define Package/ustreamer
|
||||||
|
SECTION:=multimedia
|
||||||
|
CATEGORY:=Multimedia
|
||||||
|
TITLE:=uStreamer
|
||||||
|
DEPENDS:=+libpthread +libjpeg +libv4l +libuuid +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
|
||||||
|
URL:=https://github.com/pi-kvm/ustreamer
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/ustreamer/description
|
||||||
|
µStreamer - Lightweight and fast MJPG-HTTP streamer
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/ustreamer/install
|
||||||
|
$(INSTALL_DIR) $(1)/usr/bin
|
||||||
|
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer $(1)/usr/bin/
|
||||||
|
$(INSTALL_DIR) $(1)/etc/config
|
||||||
|
$(CP) ./files/ustreamer.config $(1)/etc/config/ustreamer
|
||||||
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
|
$(INSTALL_BIN) ./files/ustreamer.init $(1)/etc/init.d/ustreamer
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,ustreamer))
|
||||||
20
pkg/openwrt/files/ustreamer.config
Normal file
20
pkg/openwrt/files/ustreamer.config
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
config ustreamer
|
||||||
|
option enabled '0'
|
||||||
|
|
||||||
|
option device '/dev/video0'
|
||||||
|
option device_timeout '5'
|
||||||
|
option input '0'
|
||||||
|
|
||||||
|
option width '640'
|
||||||
|
option height '480'
|
||||||
|
option format 'YUYV'
|
||||||
|
option quality '80'
|
||||||
|
option desired_fps '0'
|
||||||
|
option encoder 'CPU'
|
||||||
|
|
||||||
|
option host '::'
|
||||||
|
option port '8080'
|
||||||
|
option static ''
|
||||||
|
option user ''
|
||||||
|
option password ''
|
||||||
55
pkg/openwrt/files/ustreamer.init
Normal file
55
pkg/openwrt/files/ustreamer.init
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# Copyright (C) 2009-2019 OpenWrt.org
|
||||||
|
|
||||||
|
START=90
|
||||||
|
STOP=10
|
||||||
|
|
||||||
|
USE_PROCD=1
|
||||||
|
PROG=/usr/bin/ustreamer
|
||||||
|
|
||||||
|
getcfg() {
|
||||||
|
config_get value ustreamer $1 $2
|
||||||
|
return "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_instance() {
|
||||||
|
config_get_bool enabled ustreamer enabled 0
|
||||||
|
[ "$enabled" -eq 0 ] && return
|
||||||
|
|
||||||
|
local options=""
|
||||||
|
|
||||||
|
options="$options --device='`getcfg device /dev/video0`'"
|
||||||
|
options="$options --device-timeout='`getcfg device_timeout 5`'"
|
||||||
|
options="$options --input='`getcfg input 0`'"
|
||||||
|
|
||||||
|
options="$options --width='`getcfg width 640`'"
|
||||||
|
options="$options --height='`getcfg height 480`'"
|
||||||
|
options="$options --format='`getcfg format YUYV`'"
|
||||||
|
options="$options --quality='`getcfg quality 80`'"
|
||||||
|
options="$options --desired-fps='`getcfg desired_fps 0`'"
|
||||||
|
options="$options --encoder='`getcfg encoder CPU`'"
|
||||||
|
|
||||||
|
options="$options --host='`getcfg host '::'`'"
|
||||||
|
local port=`getcfg port 8080`
|
||||||
|
options="$options --port='$port'"
|
||||||
|
options="$options --static='`getcfg static ''`'"
|
||||||
|
options="$options --user='`getcfg user ''`'"
|
||||||
|
options="$options --passwd='`getcfg password ''`'"
|
||||||
|
|
||||||
|
config-get-bool opt_slowdown ustreamer slowdown 1
|
||||||
|
[ "$slowdown" -eq 1 ] && options="$options --slowdown"
|
||||||
|
|
||||||
|
procd_open_instance
|
||||||
|
procd_set_param command "$PROG" $options
|
||||||
|
procd_add_mdns http tcp "$port" daemon=ustreamer
|
||||||
|
procd_close_instance
|
||||||
|
}
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
config_load ustreamer
|
||||||
|
config_foreach start_instance ustreamer
|
||||||
|
}
|
||||||
|
|
||||||
|
service_triggers() {
|
||||||
|
procd_add_reload_trigger ustreamer
|
||||||
|
}
|
||||||
@@ -22,4 +22,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define VERSION "0.67"
|
#define VERSION "0.69"
|
||||||
|
|||||||
173
src/http/blank.c
Normal file
173
src/http/blank.c
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# 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 <setjmp.h>
|
||||||
|
|
||||||
|
#include <jpeglib.h>
|
||||||
|
|
||||||
|
#include "../tools.h"
|
||||||
|
#include "../logging.h"
|
||||||
|
|
||||||
|
#include "data/blank_jpeg.h"
|
||||||
|
|
||||||
|
#include "blank.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct _jpeg_error_manager_t {
|
||||||
|
struct jpeg_error_mgr mgr; // Default manager
|
||||||
|
jmp_buf jmp;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static struct blank_t *_blank_init_internal();
|
||||||
|
static struct blank_t *_blank_init_external(const char *path);
|
||||||
|
static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height);
|
||||||
|
static void _jpeg_error_handler(j_common_ptr jpeg);
|
||||||
|
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
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 blank_t *blank;
|
||||||
|
|
||||||
|
A_CALLOC(blank, 1);
|
||||||
|
|
||||||
|
if ((fp = fopen(path, "rb")) == NULL) {
|
||||||
|
LOG_PERROR("Can't open blank placeholder '%s'", path);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_jpeg_read_geometry(fp, &blank->width, &blank->height) < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 1, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height) {
|
||||||
|
struct jpeg_decompress_struct jpeg;
|
||||||
|
struct _jpeg_error_manager_t jpeg_error;
|
||||||
|
|
||||||
|
jpeg_create_decompress(&jpeg);
|
||||||
|
|
||||||
|
jpeg.err = jpeg_std_error((struct jpeg_error_mgr *)&jpeg_error);
|
||||||
|
jpeg_error.mgr.error_exit = _jpeg_error_handler;
|
||||||
|
if (setjmp(jpeg_error.jmp) < 0) {
|
||||||
|
jpeg_destroy_decompress(&jpeg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
jpeg_stdio_src(&jpeg, fp);
|
||||||
|
jpeg_read_header(&jpeg, TRUE);
|
||||||
|
jpeg_start_decompress(&jpeg);
|
||||||
|
|
||||||
|
*width = jpeg.output_width;
|
||||||
|
*height = jpeg.output_height;
|
||||||
|
|
||||||
|
jpeg_destroy_decompress(&jpeg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _jpeg_error_handler(j_common_ptr jpeg) {
|
||||||
|
struct _jpeg_error_manager_t *jpeg_error = (struct _jpeg_error_manager_t *)jpeg->err;
|
||||||
|
char msg[JMSG_LENGTH_MAX];
|
||||||
|
|
||||||
|
(*jpeg_error->mgr.format_message)(jpeg, msg);
|
||||||
|
LOG_ERROR("Invalid blank placeholder: %s", msg);
|
||||||
|
longjmp(jpeg_error->jmp, -1);
|
||||||
|
}
|
||||||
38
src/http/blank.h
Normal file
38
src/http/blank.h
Normal 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);
|
||||||
@@ -52,13 +52,13 @@
|
|||||||
#include "../encoder.h"
|
#include "../encoder.h"
|
||||||
#include "../stream.h"
|
#include "../stream.h"
|
||||||
|
|
||||||
|
#include "blank.h"
|
||||||
#include "base64.h"
|
#include "base64.h"
|
||||||
#include "mime.h"
|
#include "mime.h"
|
||||||
#include "static.h"
|
#include "static.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
#include "data/index_html.h"
|
#include "data/index_html.h"
|
||||||
#include "data/blank_jpeg.h"
|
|
||||||
|
|
||||||
|
|
||||||
static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
|
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->timeout = 10;
|
||||||
server->run = run;
|
server->run = run;
|
||||||
|
|
||||||
_expose_blank_picture(server);
|
|
||||||
|
|
||||||
assert(!evthread_use_pthreads());
|
assert(!evthread_use_pthreads());
|
||||||
assert((run->base = event_base_new()));
|
assert((run->base = event_base_new()));
|
||||||
assert((run->http = evhttp_new(run->base)));
|
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);
|
free(server->run->auth_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (server->run->blank) {
|
||||||
|
blank_destroy(server->run->blank);
|
||||||
|
}
|
||||||
|
|
||||||
free(server->run->exposed->picture.data);
|
free(server->run->exposed->picture.data);
|
||||||
free(server->run->exposed);
|
free(server->run->exposed);
|
||||||
free(server->run);
|
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));
|
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;
|
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));
|
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) {
|
if (server->slowdown) {
|
||||||
stream_switch_slowdown(server->run->stream, true);
|
stream_switch_slowdown(server->run->stream, true);
|
||||||
}
|
}
|
||||||
@@ -523,7 +527,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
|||||||
PREPROCESS_REQUEST;
|
PREPROCESS_REQUEST;
|
||||||
|
|
||||||
conn = evhttp_request_get_connection(request);
|
conn = evhttp_request_get_connection(request);
|
||||||
if (conn != NULL) {
|
if (conn) {
|
||||||
A_CALLOC(client, 1);
|
A_CALLOC(client, 1);
|
||||||
client->server = server;
|
client->server = server;
|
||||||
client->request = request;
|
client->request = request;
|
||||||
@@ -641,8 +645,9 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
"Content-Length: %zu" RN
|
"Content-Length: %zu" RN
|
||||||
"X-Timestamp: %.06Lf" RN
|
"X-Timestamp: %.06Lf" RN
|
||||||
"%s",
|
"%s",
|
||||||
EXPOSED(picture.used) * sizeof(*EXPOSED(picture.data)),
|
EXPOSED(picture.used),
|
||||||
get_now_real(), (client->extra_headers ? "" : RN)
|
get_now_real(),
|
||||||
|
(client->extra_headers ? "" : RN)
|
||||||
));
|
));
|
||||||
if (client->extra_headers) {
|
if (client->extra_headers) {
|
||||||
assert(evbuffer_add_printf(buf,
|
assert(evbuffer_add_printf(buf,
|
||||||
@@ -675,10 +680,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!evbuffer_add(buf,
|
assert(!evbuffer_add(buf, (void *)EXPOSED(picture.data), EXPOSED(picture.used)));
|
||||||
(void *)EXPOSED(picture.data),
|
|
||||||
EXPOSED(picture.used) * sizeof(*EXPOSED(picture.data))
|
|
||||||
));
|
|
||||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||||
|
|
||||||
if (client->advance_headers) {
|
if (client->advance_headers) {
|
||||||
@@ -711,12 +713,12 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn = evhttp_request_get_connection(client->request);
|
conn = evhttp_request_get_connection(client->request);
|
||||||
if (conn != NULL) {
|
if (conn) {
|
||||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||||
}
|
}
|
||||||
LOG_INFO("HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
|
LOG_INFO("HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
|
||||||
client_addr, client_port, RUN(stream_clients_count));
|
client_addr, client_port, RUN(stream_clients_count));
|
||||||
if (conn != NULL) {
|
if (conn) {
|
||||||
evhttp_connection_free(conn);
|
evhttp_connection_free(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,7 +746,7 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
|
|||||||
|
|
||||||
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) {
|
||||||
conn = evhttp_request_get_connection(client->request);
|
conn = evhttp_request_get_connection(client->request);
|
||||||
if (conn != NULL) {
|
if (conn) {
|
||||||
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
||||||
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
|
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
|
||||||
// и нужно послать два фрейма, чтобы серия была вовремя завершена.
|
// и нужно послать два фрейма, чтобы серия была вовремя завершена.
|
||||||
@@ -824,8 +826,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
|||||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||||
|
|
||||||
# define MEM_STREAM_TO_EXPOSED \
|
# define MEM_STREAM_TO_EXPOSED \
|
||||||
EXPOSED(picture.data), STREAM(picture.data), \
|
EXPOSED(picture.data), STREAM(picture.data), STREAM(picture.used)
|
||||||
STREAM(picture.used) * sizeof(*STREAM(picture.data))
|
|
||||||
|
|
||||||
if (server->drop_same_frames) {
|
if (server->drop_same_frames) {
|
||||||
if (
|
if (
|
||||||
@@ -878,28 +879,28 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool _expose_blank_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 EXPOSED(_next) server->run->exposed->_next
|
||||||
# define BLANK_JPEG_LEN ARRAY_LEN(BLANK_JPEG_DATA)
|
|
||||||
|
|
||||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||||
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
||||||
|
|
||||||
if (EXPOSED(online) || EXPOSED(picture.used) == 0) {
|
if (EXPOSED(online) || EXPOSED(picture.used) == 0) {
|
||||||
if (EXPOSED(picture.allocated) < BLANK_JPEG_LEN) {
|
if (EXPOSED(picture.allocated) < BLANK(picture.used)) {
|
||||||
A_REALLOC(EXPOSED(picture.data), BLANK_JPEG_LEN);
|
A_REALLOC(EXPOSED(picture.data), BLANK(picture.used));
|
||||||
EXPOSED(picture.allocated) = BLANK_JPEG_LEN;
|
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));
|
||||||
|
|
||||||
EXPOSED(picture.used) = BLANK_JPEG_LEN;
|
EXPOSED(picture.used) = BLANK(picture.used);
|
||||||
|
|
||||||
EXPOSED(picture.grab_time) = 0;
|
EXPOSED(picture.grab_time) = 0;
|
||||||
EXPOSED(picture.encode_begin_time) = 0;
|
EXPOSED(picture.encode_begin_time) = 0;
|
||||||
EXPOSED(picture.encode_end_time) = 0;
|
EXPOSED(picture.encode_end_time) = 0;
|
||||||
|
|
||||||
EXPOSED(width) = BLANK_JPEG_WIDTH;
|
EXPOSED(width) = BLANK(width);
|
||||||
EXPOSED(height) = BLANK_JPEG_HEIGHT;
|
EXPOSED(height) = BLANK(height);
|
||||||
EXPOSED(captured_fps) = 0;
|
EXPOSED(captured_fps) = 0;
|
||||||
EXPOSED(online) = false;
|
EXPOSED(online) = false;
|
||||||
goto updated;
|
goto updated;
|
||||||
@@ -917,6 +918,6 @@ static bool _expose_blank_picture(struct http_server_t *server) {
|
|||||||
EXPOSED(expose_end_time) = get_now_monotonic();
|
EXPOSED(expose_end_time) = get_now_monotonic();
|
||||||
return true; // Updated
|
return true; // Updated
|
||||||
|
|
||||||
# undef BLANK_JPEG_LEN
|
|
||||||
# undef EXPOSED
|
# undef EXPOSED
|
||||||
|
# undef BLANK
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
#include "../tools.h"
|
#include "../tools.h"
|
||||||
#include "../stream.h"
|
#include "../stream.h"
|
||||||
|
|
||||||
|
#include "blank.h"
|
||||||
|
|
||||||
|
|
||||||
struct stream_client_t {
|
struct stream_client_t {
|
||||||
struct http_server_t *server;
|
struct http_server_t *server;
|
||||||
@@ -78,6 +80,7 @@ struct http_server_runtime_t {
|
|||||||
struct exposed_t *exposed;
|
struct exposed_t *exposed;
|
||||||
struct stream_client_t *stream_clients;
|
struct stream_client_t *stream_clients;
|
||||||
unsigned stream_clients_count;
|
unsigned stream_clients_count;
|
||||||
|
struct blank_t *blank;
|
||||||
unsigned drop_same_frames_blank;
|
unsigned drop_same_frames_blank;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,14 +90,17 @@ struct http_server_t {
|
|||||||
char *unix_path;
|
char *unix_path;
|
||||||
bool unix_rm;
|
bool unix_rm;
|
||||||
mode_t unix_mode;
|
mode_t unix_mode;
|
||||||
|
unsigned timeout;
|
||||||
|
|
||||||
char *user;
|
char *user;
|
||||||
char *passwd;
|
char *passwd;
|
||||||
char *static_path;
|
char *static_path;
|
||||||
|
|
||||||
|
char *blank_path;
|
||||||
unsigned drop_same_frames;
|
unsigned drop_same_frames;
|
||||||
bool slowdown;
|
bool slowdown;
|
||||||
unsigned fake_width;
|
unsigned fake_width;
|
||||||
unsigned fake_height;
|
unsigned fake_height;
|
||||||
unsigned timeout;
|
|
||||||
|
|
||||||
struct http_server_runtime_t *run;
|
struct http_server_runtime_t *run;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
#include "http/server.h"
|
#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[] = {
|
static const struct option _LONG_OPTS[] = {
|
||||||
{"device", required_argument, NULL, 'd'},
|
{"device", required_argument, NULL, 'd'},
|
||||||
{"input", required_argument, NULL, 'i'},
|
{"input", required_argument, NULL, 'i'},
|
||||||
@@ -82,6 +82,7 @@ static const struct option _LONG_OPTS[] = {
|
|||||||
{"user", required_argument, NULL, 3000},
|
{"user", required_argument, NULL, 3000},
|
||||||
{"passwd", required_argument, NULL, 3001},
|
{"passwd", required_argument, NULL, 3001},
|
||||||
{"static", required_argument, NULL, 3002},
|
{"static", required_argument, NULL, 3002},
|
||||||
|
{"blank", required_argument, NULL, 'k'},
|
||||||
{"drop-same-frames", required_argument, NULL, 'e'},
|
{"drop-same-frames", required_argument, NULL, 'e'},
|
||||||
{"slowdown", no_argument, NULL, 'l'},
|
{"slowdown", no_argument, NULL, 'l'},
|
||||||
{"fake-width", required_argument, NULL, 3003},
|
{"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(" --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(" --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(" 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(" -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(" 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");
|
||||||
@@ -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 3000: OPT_SET(server->user, optarg);
|
||||||
case 3001: OPT_SET(server->passwd, optarg);
|
case 3001: OPT_SET(server->passwd, optarg);
|
||||||
case 3002: OPT_SET(server->static_path, 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 'e': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
|
||||||
case 'l': OPT_SET(server->slowdown, true);
|
case 'l': OPT_SET(server->slowdown, true);
|
||||||
case 3003: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
case 3003: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
|
|
||||||
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_index);
|
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_index);
|
||||||
|
|
||||||
if (!oldest_worker) {
|
if (oldest_worker == NULL) {
|
||||||
oldest_worker = &pool.workers[free_worker_number];
|
oldest_worker = &pool.workers[free_worker_number];
|
||||||
last_worker = oldest_worker;
|
last_worker = oldest_worker;
|
||||||
} else {
|
} else {
|
||||||
@@ -315,7 +315,7 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
|
|||||||
stream->picture.used = PICTURE(used);
|
stream->picture.used = PICTURE(used);
|
||||||
stream->picture.allocated = PICTURE(allocated);
|
stream->picture.allocated = PICTURE(allocated);
|
||||||
|
|
||||||
memcpy(stream->picture.data, PICTURE(data), stream->picture.used * sizeof(*stream->picture.data));
|
memcpy(stream->picture.data, PICTURE(data), stream->picture.used);
|
||||||
|
|
||||||
stream->picture.grab_time = PICTURE(grab_time);
|
stream->picture.grab_time = PICTURE(grab_time);
|
||||||
stream->picture.encode_begin_time = PICTURE(encode_begin_time);
|
stream->picture.encode_begin_time = PICTURE(encode_begin_time);
|
||||||
|
|||||||
Reference in New Issue
Block a user