mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8033ab23ed | ||
|
|
ddb3db8b20 | ||
|
|
b3c8071edb | ||
|
|
bc70faae09 | ||
|
|
ae7c4c91e0 | ||
|
|
613baa4e1e | ||
|
|
33a101b4f7 | ||
|
|
9e9039c4e6 | ||
|
|
b3ceec51de | ||
|
|
e3ac4ba6f5 | ||
|
|
fa6b8b44c1 | ||
|
|
8cb7574af2 | ||
|
|
c34d644c2a | ||
|
|
7857fa8f63 | ||
|
|
3253de83dc | ||
|
|
030464c3b8 | ||
|
|
0020aa69ec | ||
|
|
93bfa56ccf | ||
|
|
3392ac5fbc | ||
|
|
2077d94edc | ||
|
|
defe5eb6fe | ||
|
|
dee5e18134 | ||
|
|
6ef5a7e440 | ||
|
|
462735147d | ||
|
|
51ca0e4474 | ||
|
|
4ee3b18533 | ||
|
|
cdc9ed54c9 | ||
|
|
d9e7c07851 | ||
|
|
f2debc5d16 | ||
|
|
b3dbaf40cf | ||
|
|
abfc7b917b | ||
|
|
a0e488b0a5 | ||
|
|
6b99df2792 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 0.64
|
||||
current_version = 0.74
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
@@ -14,3 +14,7 @@ replace = VERSION "{new_version}"
|
||||
search = pkgver={current_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:
|
||||
bumpversion minor
|
||||
bumpversion $(if $(V), $(V), minor)
|
||||
|
||||
|
||||
push:
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|----------|---------------|-------------------|
|
||||
| Многопоточное кодирование JPEG |  Есть |  Нет |
|
||||
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi |  Есть |  Нет |
|
||||
| Поведение при физическом отключении устройства<br>от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова |  Прерывает трансляцию <sup>1</sup> |
|
||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) |  Есть |  Условно есть <sup>2</sup> |
|
||||
| Поведение при физическом отключении<br>устройства от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова |  Прерывает трансляцию <sup>1</sup> |
|
||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) |  Есть |  Условно есть <sup>2</sup> |
|
||||
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
||||
| Стрим через UNIX domain socket |  Есть |  Нет |
|
||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=0.64
|
||||
pkgver=0.74
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pi-kvm/ustreamer"
|
||||
@@ -19,7 +19,7 @@ md5sums=(SKIP)
|
||||
build() {
|
||||
cd $srcdir
|
||||
rm -rf $pkgname-build
|
||||
cp -r ustreamer-$pkgver $pkgname-build
|
||||
cp -r $pkgname-$pkgver $pkgname-build
|
||||
cd $pkgname-build
|
||||
make CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ EAPI=7
|
||||
|
||||
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"
|
||||
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.74
|
||||
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
|
||||
|
||||
#define VERSION "0.64"
|
||||
#define VERSION "0.74"
|
||||
|
||||
15
src/device.c
15
src/device.c
@@ -27,6 +27,7 @@
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
@@ -155,6 +156,8 @@ int device_open(struct device_t *dev) {
|
||||
_device_open_alloc_picbufs(dev);
|
||||
_device_apply_controls(dev);
|
||||
|
||||
dev->run->n_workers = min_u(dev->run->n_buffers, dev->n_workers);
|
||||
|
||||
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
|
||||
return 0;
|
||||
|
||||
@@ -164,6 +167,8 @@ int device_open(struct device_t *dev) {
|
||||
}
|
||||
|
||||
void device_close(struct device_t *dev) {
|
||||
dev->run->n_workers = 0;
|
||||
|
||||
if (dev->run->pictures) {
|
||||
LOG_DEBUG("Releasing picture buffers ...");
|
||||
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index].data; ++index) {
|
||||
@@ -317,7 +322,7 @@ static int _device_open_check_cap(struct device_t *dev) {
|
||||
static int _device_open_dv_timings(struct device_t *dev) {
|
||||
_device_apply_resolution(dev, dev->width, dev->height);
|
||||
if (dev->dv_timings) {
|
||||
LOG_DEBUG("Using DV-timings");
|
||||
LOG_DEBUG("Using DV timings");
|
||||
|
||||
if (_device_apply_dv_timings(dev) < 0) {
|
||||
return -1;
|
||||
@@ -432,7 +437,11 @@ static void _device_open_hw_fps(struct device_t *dev) {
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_G_PARM) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_G_PARM, &setfps) < 0) {
|
||||
LOG_PERROR("Unable to query HW FPS changing");
|
||||
if (errno == ENOTTY) { // Quiet message for Auvidea B101
|
||||
LOG_INFO("Quierying HW FPS changing is not supported");
|
||||
} else {
|
||||
LOG_PERROR("Unable to query HW FPS changing");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -454,7 +463,7 @@ static void _device_open_hw_fps(struct device_t *dev) {
|
||||
}
|
||||
|
||||
if (dev->desired_fps != SETFPS_TPF(denominator)) {
|
||||
LOG_INFO("Using HW FPS (coerced): %u -> %u", dev->desired_fps, SETFPS_TPF(denominator));
|
||||
LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, SETFPS_TPF(denominator));
|
||||
} else {
|
||||
LOG_INFO("Using HW FPS: %u", dev->desired_fps);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ struct device_runtime_t {
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned n_buffers;
|
||||
// unsigned n_workers; // FIXME
|
||||
unsigned n_workers;
|
||||
struct hw_buffer_t *hw_buffers;
|
||||
struct picture_t *pictures;
|
||||
size_t max_raw_image_size;
|
||||
|
||||
137
src/encoder.c
137
src/encoder.c
@@ -20,6 +20,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
@@ -66,48 +67,6 @@ struct encoder_t *encoder_init() {
|
||||
return encoder;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic push
|
||||
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
||||
// XXX: Тут нет гонки, потому что encoder_prepare() запускается еще до существования других потоков
|
||||
encoder->run->type = encoder->type;
|
||||
encoder->run->quality = encoder->quality;
|
||||
|
||||
LOG_INFO("Using JPEG quality: %u%%", encoder->quality);
|
||||
|
||||
# ifdef WITH_OMX_ENCODER
|
||||
if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||
LOG_DEBUG("Preparing OMX JPEG encoder ...");
|
||||
|
||||
if (dev->n_workers > OMX_MAX_ENCODERS) {
|
||||
LOG_INFO("OMX JPEG encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
|
||||
dev->n_workers = OMX_MAX_ENCODERS;
|
||||
}
|
||||
encoder->run->n_omxs = dev->n_workers;
|
||||
|
||||
A_CALLOC(encoder->run->omxs, encoder->run->n_omxs);
|
||||
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
||||
if ((encoder->run->omxs[index] = omx_encoder_init()) == NULL) {
|
||||
goto use_fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
return;
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
use_fallback:
|
||||
LOG_ERROR("Can't initialize selected encoder, using CPU instead it");
|
||||
encoder->run->type = ENCODER_TYPE_CPU;
|
||||
encoder->run->quality = encoder->quality;
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
void encoder_destroy(struct encoder_t *encoder) {
|
||||
# ifdef WITH_OMX_ENCODER
|
||||
if (encoder->run->omxs) {
|
||||
@@ -142,52 +101,87 @@ const char *encoder_type_to_string(enum encoder_type_t type) {
|
||||
return _ENCODER_TYPES[0].name;
|
||||
}
|
||||
|
||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
|
||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
||||
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||
enum encoder_type_t type = (encoder->run->cpu_forced ? ENCODER_TYPE_CPU : encoder->type);
|
||||
unsigned quality = encoder->quality;
|
||||
bool cpu_forced = false;
|
||||
|
||||
if (
|
||||
(dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG)
|
||||
&& encoder->run->type != ENCODER_TYPE_HW
|
||||
) {
|
||||
if ((dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG) && type != ENCODER_TYPE_HW) {
|
||||
LOG_INFO("Switching to HW JPEG encoder because the input format is (M)JPEG");
|
||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
||||
encoder->run->type = ENCODER_TYPE_HW;
|
||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
||||
type = ENCODER_TYPE_HW;
|
||||
}
|
||||
|
||||
if (encoder->run->type == ENCODER_TYPE_HW) {
|
||||
if (type == ENCODER_TYPE_HW) {
|
||||
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
|
||||
LOG_INFO("Switching to CPU JPEG encoder because the input format is not (M)JPEG");
|
||||
goto use_fallback;
|
||||
goto use_cpu;
|
||||
}
|
||||
if (hw_encoder_prepare_live(dev, encoder->quality) < 0) {
|
||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
||||
encoder->run->quality = 0;
|
||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
||||
LOG_INFO("Using JPEG quality: HW-default");
|
||||
|
||||
if (hw_encoder_prepare(dev, quality) < 0) {
|
||||
quality = 0;
|
||||
}
|
||||
|
||||
dev->run->n_workers = 1;
|
||||
}
|
||||
# ifdef WITH_OMX_ENCODER
|
||||
else if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||
else if (type == ENCODER_TYPE_OMX) {
|
||||
LOG_DEBUG("Preparing OMX JPEG encoder ...");
|
||||
|
||||
if (dev->run->n_workers > OMX_MAX_ENCODERS) {
|
||||
LOG_INFO("OMX JPEG encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
|
||||
dev->run->n_workers = OMX_MAX_ENCODERS;
|
||||
}
|
||||
|
||||
if (encoder->run->omxs == NULL) {
|
||||
A_CALLOC(encoder->run->omxs, OMX_MAX_ENCODERS);
|
||||
}
|
||||
|
||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||
for (; encoder->run->n_omxs < dev->run->n_workers; ++encoder->run->n_omxs) {
|
||||
if ((encoder->run->omxs[encoder->run->n_omxs] = omx_encoder_init()) == NULL) {
|
||||
LOG_ERROR("Can't initialize OMX JPEG encoder, falling back to CPU");
|
||||
goto force_cpu;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
||||
if (omx_encoder_prepare_live(encoder->run->omxs[index], dev, encoder->quality) < 0) {
|
||||
if (omx_encoder_prepare(encoder->run->omxs[index], dev, quality) < 0) {
|
||||
LOG_ERROR("Can't prepare OMX JPEG encoder, falling back to CPU");
|
||||
goto use_fallback;
|
||||
goto force_cpu;
|
||||
}
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
return;
|
||||
goto ok;
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
force_cpu:
|
||||
cpu_forced = true;
|
||||
# pragma GCC diagnostic pop
|
||||
|
||||
use_cpu:
|
||||
type = ENCODER_TYPE_CPU;
|
||||
quality = encoder->quality;
|
||||
|
||||
ok:
|
||||
if (quality == 0) {
|
||||
LOG_INFO("Using JPEG quality: encoder default");
|
||||
} else {
|
||||
LOG_INFO("Using JPEG quality: %u%%", quality);
|
||||
}
|
||||
|
||||
use_fallback:
|
||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
||||
encoder->run->type = ENCODER_TYPE_CPU;
|
||||
encoder->run->quality = encoder->quality;
|
||||
encoder->run->type = type;
|
||||
encoder->run->quality = quality;
|
||||
if (cpu_forced) {
|
||||
encoder->run->cpu_forced = true;
|
||||
}
|
||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-label"
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic push
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index) {
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -195,14 +189,14 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
|
||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
if (encoder->run->type == ENCODER_TYPE_CPU) {
|
||||
cpu_encoder_compress_buffer(dev, buf_index, encoder->quality);
|
||||
cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality);
|
||||
} else if (encoder->run->type == ENCODER_TYPE_HW) {
|
||||
hw_encoder_compress_buffer(dev, buf_index);
|
||||
}
|
||||
# ifdef WITH_OMX_ENCODER
|
||||
else if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
|
||||
goto use_fallback;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
@@ -211,11 +205,10 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
use_fallback:
|
||||
LOG_INFO("Error while compressing, falling back to CPU");
|
||||
error:
|
||||
LOG_INFO("Error while compressing buffer, falling back to CPU");
|
||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
||||
encoder->run->type = ENCODER_TYPE_CPU;
|
||||
encoder->run->quality = encoder->quality;
|
||||
encoder->run->cpu_forced = true;
|
||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
||||
return -1;
|
||||
# pragma GCC diagnostic pop
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "pthread.h"
|
||||
|
||||
#include "tools.h"
|
||||
@@ -51,6 +53,7 @@ enum encoder_type_t {
|
||||
struct encoder_runtime_t {
|
||||
enum encoder_type_t type;
|
||||
unsigned quality;
|
||||
bool cpu_forced;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
# ifdef WITH_OMX_ENCODER
|
||||
@@ -74,6 +77,4 @@ enum encoder_type_t encoder_parse_type(const char *str);
|
||||
const char *encoder_type_to_string(enum encoder_type_t type);
|
||||
|
||||
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev);
|
||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev);
|
||||
|
||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index);
|
||||
|
||||
@@ -44,7 +44,7 @@ static bool _is_huffman(const unsigned char *data);
|
||||
static size_t _memcpy_with_huffman(unsigned char *dest, const unsigned char *src, size_t size);
|
||||
|
||||
|
||||
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality) {
|
||||
int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
|
||||
struct v4l2_jpegcompression comp;
|
||||
|
||||
MEMSET_ZERO(comp);
|
||||
|
||||
@@ -25,5 +25,5 @@
|
||||
#include "../../device.h"
|
||||
|
||||
|
||||
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality);
|
||||
int hw_encoder_prepare(struct device_t *dev, unsigned quality);
|
||||
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index);
|
||||
|
||||
@@ -153,7 +153,7 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
|
||||
free(omx);
|
||||
}
|
||||
|
||||
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality) {
|
||||
int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality) {
|
||||
if (component_set_state(&omx->encoder, OMX_StateIdle) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -53,5 +53,5 @@ struct omx_encoder_t {
|
||||
struct omx_encoder_t *omx_encoder_init();
|
||||
void omx_encoder_destroy(struct omx_encoder_t *omx);
|
||||
|
||||
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality);
|
||||
int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality);
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -21,3 +21,18 @@
|
||||
|
||||
|
||||
#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 "../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)));
|
||||
@@ -123,7 +121,10 @@ void http_server_destroy(struct http_server_t *server) {
|
||||
close(server->run->unix_fd);
|
||||
}
|
||||
event_base_free(server->run->base);
|
||||
|
||||
# if LIBEVENT_VERSION_NUMBER >= 0x02010100
|
||||
libevent_global_shutdown();
|
||||
# endif
|
||||
|
||||
for (struct stream_client_t *client = server->run->stream_clients; client != NULL;) {
|
||||
struct stream_client_t *next = client->next;
|
||||
@@ -137,6 +138,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 +160,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 +178,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);
|
||||
}
|
||||
@@ -195,19 +202,25 @@ int http_server_listen(struct http_server_t *server) {
|
||||
|
||||
if (server->unix_path[0] != '\0') {
|
||||
struct sockaddr_un unix_addr;
|
||||
int unix_fd_flags;
|
||||
|
||||
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
||||
|
||||
assert((server->run->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0)));
|
||||
assert((unix_fd_flags = fcntl(server->run->unix_fd, F_GETFL)) >= 0);
|
||||
unix_fd_flags |= O_NONBLOCK;
|
||||
assert(fcntl(server->run->unix_fd, F_SETFL, unix_fd_flags) >= 0);
|
||||
# define MAX_SUN_PATH (sizeof(unix_addr.sun_path) - 1)
|
||||
|
||||
strncpy(unix_addr.sun_path, server->unix_path, 107);
|
||||
unix_addr.sun_path[107] = '\0';
|
||||
if (strlen(server->unix_path) > MAX_SUN_PATH) {
|
||||
LOG_ERROR("UNIX socket path is too long, max=%zu", MAX_SUN_PATH);
|
||||
return -1;
|
||||
}
|
||||
|
||||
MEMSET_ZERO(unix_addr);
|
||||
strncpy(unix_addr.sun_path, server->unix_path, MAX_SUN_PATH);
|
||||
unix_addr.sun_family = AF_UNIX;
|
||||
|
||||
# undef MAX_SUN_PATH
|
||||
|
||||
assert((server->run->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0);
|
||||
assert(!evutil_make_socket_nonblocking(server->run->unix_fd));
|
||||
|
||||
if (server->unix_rm && unlink(server->unix_path) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_PERROR("Can't remove old UNIX socket '%s'", server->unix_path);
|
||||
@@ -262,7 +275,6 @@ static bool _http_get_param_true(struct evkeyvalq *params, const char *key) {
|
||||
if ((value_str = evhttp_find_header(params, key)) != NULL) {
|
||||
if (
|
||||
value_str[0] == '1'
|
||||
|| value_str[0] == 'y'
|
||||
|| !evutil_ascii_strcasecmp(value_str, "true")
|
||||
|| !evutil_ascii_strcasecmp(value_str, "yes")
|
||||
) {
|
||||
@@ -523,7 +535,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
PREPROCESS_REQUEST;
|
||||
|
||||
conn = evhttp_request_get_connection(request);
|
||||
if (conn != NULL) {
|
||||
if (conn) {
|
||||
A_CALLOC(client, 1);
|
||||
client->server = server;
|
||||
client->request = request;
|
||||
@@ -641,8 +653,9 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
"Content-Length: %zu" RN
|
||||
"X-Timestamp: %.06Lf" RN
|
||||
"%s",
|
||||
EXPOSED(picture.used) * sizeof(*EXPOSED(picture.data)),
|
||||
get_now_real(), (client->extra_headers ? "" : RN)
|
||||
EXPOSED(picture.used),
|
||||
get_now_real(),
|
||||
(client->extra_headers ? "" : RN)
|
||||
));
|
||||
if (client->extra_headers) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
@@ -675,10 +688,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
}
|
||||
}
|
||||
|
||||
assert(!evbuffer_add(buf,
|
||||
(void *)EXPOSED(picture.data),
|
||||
EXPOSED(picture.used) * sizeof(*EXPOSED(picture.data))
|
||||
));
|
||||
assert(!evbuffer_add(buf, (void *)EXPOSED(picture.data), EXPOSED(picture.used)));
|
||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||
|
||||
if (client->advance_headers) {
|
||||
@@ -711,12 +721,12 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
}
|
||||
|
||||
conn = evhttp_request_get_connection(client->request);
|
||||
if (conn != NULL) {
|
||||
if (conn) {
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
}
|
||||
LOG_INFO("HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
|
||||
client_addr, client_port, RUN(stream_clients_count));
|
||||
if (conn != NULL) {
|
||||
if (conn) {
|
||||
evhttp_connection_free(conn);
|
||||
}
|
||||
|
||||
@@ -744,7 +754,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) {
|
||||
conn = evhttp_request_get_connection(client->request);
|
||||
if (conn != NULL) {
|
||||
if (conn) {
|
||||
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
||||
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
|
||||
// и нужно послать два фрейма, чтобы серия была вовремя завершена.
|
||||
@@ -824,8 +834,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||
|
||||
# define MEM_STREAM_TO_EXPOSED \
|
||||
EXPOSED(picture.data), STREAM(picture.data), \
|
||||
STREAM(picture.used) * sizeof(*STREAM(picture.data))
|
||||
EXPOSED(picture.data), STREAM(picture.data), STREAM(picture.used)
|
||||
|
||||
if (server->drop_same_frames) {
|
||||
if (
|
||||
@@ -878,28 +887,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));
|
||||
|
||||
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 +926,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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
12
src/main.c
12
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},
|
||||
@@ -133,7 +134,8 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
printf(" -b|--buffers <N> ─────────────── The number of buffers to receive data from the device.\n");
|
||||
printf(" Each buffer may processed using an intermediate thread.\n");
|
||||
printf(" Default: %u (the number of CPU cores (but not more 4) + 1).\n\n", dev->n_buffers);
|
||||
printf(" -w|--workers <N> ─────────────── The number of worker threads. Default: %u (== --buffers).\n\n", dev->n_workers);
|
||||
printf(" -w|--workers <N> ─────────────── The number of worker threads but not more than buffers.\n");
|
||||
printf(" Default: %u (== --buffers).\n\n", dev->n_workers);
|
||||
printf(" -q|--quality <N> ─────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.\n\n", encoder->quality);
|
||||
printf(" -c|--encoder <type> ──────────── Use specified encoder. It may affects to workers number.\n");
|
||||
printf(" Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
|
||||
@@ -166,12 +168,14 @@ 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");
|
||||
printf(" or webcams, it's useless. Default: disabled.\n\n");
|
||||
printf(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients connected.\n");
|
||||
printf(" Useful to reduce CPU cosumption. Default: disabled.\n\n");
|
||||
printf(" Useful to reduce CPU consumption. Default: disabled.\n\n");
|
||||
printf(" --fake-width <N> ─────────── Override image width for /state. Default: disabled.\n\n");
|
||||
printf(" --fake-height <N> ────────── Override image height for /state. Default: disabled.\n\n");
|
||||
printf(" --server-timeout <seconds> ─ Timeout for client connections. Default: %u.\n\n", server->timeout);
|
||||
@@ -284,6 +288,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);
|
||||
@@ -379,7 +384,6 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) {
|
||||
_install_signal_handlers();
|
||||
encoder_prepare(encoder, dev);
|
||||
|
||||
pthread_t stream_loop_tid;
|
||||
pthread_t server_loop_tid;
|
||||
|
||||
26
src/stream.c
26
src/stream.c
@@ -119,7 +119,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
|
||||
LOG_PERF("##### Raw frame accepted; worker = %u", free_worker_number);
|
||||
} else {
|
||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
||||
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
|
||||
if (
|
||||
!atomic_load(&pool.workers[number].has_job) && (
|
||||
free_worker_number == -1
|
||||
@@ -232,7 +232,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
|
||||
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];
|
||||
last_worker = oldest_worker;
|
||||
} else {
|
||||
@@ -315,7 +315,7 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
|
||||
stream->picture.used = PICTURE(used);
|
||||
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.encode_begin_time = PICTURE(encode_begin_time);
|
||||
@@ -336,7 +336,7 @@ static long double _stream_get_fluency_delay(struct device_t *dev, struct worker
|
||||
long double min_delay;
|
||||
long double soft_delay;
|
||||
|
||||
for (unsigned number = 0; number < dev->n_workers; ++number) {
|
||||
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
|
||||
# define WORKER(_next) pool->workers[number]._next
|
||||
|
||||
A_MUTEX_LOCK(&WORKER(last_comp_time_mutex));
|
||||
@@ -347,9 +347,9 @@ static long double _stream_get_fluency_delay(struct device_t *dev, struct worker
|
||||
|
||||
# undef WORKER
|
||||
}
|
||||
avg_comp_time = sum_comp_time / dev->n_workers; // Среднее время работы воркеров
|
||||
avg_comp_time = sum_comp_time / dev->run->n_workers; // Среднее время работы воркеров
|
||||
|
||||
min_delay = avg_comp_time / dev->n_workers; // Среднее время работы размазывается на N воркеров
|
||||
min_delay = avg_comp_time / dev->run->n_workers; // Среднее время работы размазывается на N воркеров
|
||||
|
||||
if (dev->desired_fps > 0 && min_delay > 0) {
|
||||
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
|
||||
@@ -376,12 +376,12 @@ static int _stream_init_loop(struct stream_t *stream, struct workers_pool_t *poo
|
||||
}
|
||||
|
||||
static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
|
||||
SEP_INFO('=');
|
||||
|
||||
_stream_destroy_workers(stream, pool);
|
||||
device_switch_capturing(stream->dev, false);
|
||||
device_close(stream->dev);
|
||||
|
||||
SEP_INFO('=');
|
||||
|
||||
if (device_open(stream->dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
@@ -389,7 +389,7 @@ static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
encoder_prepare_live(pool->encoder, stream->dev);
|
||||
encoder_prepare(pool->encoder, stream->dev);
|
||||
|
||||
_stream_init_workers(stream, pool);
|
||||
|
||||
@@ -401,15 +401,15 @@ static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
|
||||
}
|
||||
|
||||
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool) {
|
||||
LOG_INFO("Spawning %u workers ...", stream->dev->n_workers);
|
||||
LOG_INFO("Spawning %u workers ...", stream->dev->run->n_workers);
|
||||
|
||||
atomic_store(&pool->workers_stop, false);
|
||||
A_CALLOC(pool->workers, stream->dev->n_workers);
|
||||
A_CALLOC(pool->workers, stream->dev->run->n_workers);
|
||||
|
||||
A_MUTEX_INIT(&pool->free_workers_mutex);
|
||||
A_COND_INIT(&pool->free_workers_cond);
|
||||
|
||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
||||
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
|
||||
# define WORKER(_next) pool->workers[number]._next
|
||||
|
||||
pool->free_workers += 1;
|
||||
@@ -492,7 +492,7 @@ static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool
|
||||
LOG_INFO("Destroying workers ...");
|
||||
|
||||
atomic_store(&pool->workers_stop, true);
|
||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
||||
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
|
||||
# define WORKER(_next) pool->workers[number]._next
|
||||
|
||||
A_MUTEX_LOCK(&WORKER(has_job_mutex));
|
||||
|
||||
53
tools/common.py
Normal file
53
tools/common.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/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 textwrap
|
||||
|
||||
|
||||
# =====
|
||||
def get_prepend() -> str:
|
||||
return 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
|
||||
""").strip() + "\n"
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env -S python3 -B
|
||||
#============================================================================#
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
@@ -22,12 +22,13 @@
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
import textwrap
|
||||
|
||||
import common
|
||||
|
||||
|
||||
# =====
|
||||
def main():
|
||||
def main() -> None:
|
||||
assert len(sys.argv) == 4, "%s <file.html> <file.h> <name>" % (sys.argv[0])
|
||||
html_path = sys.argv[1]
|
||||
header_path = sys.argv[2]
|
||||
@@ -36,9 +37,6 @@ def main():
|
||||
with open(html_path, "r") as html_file:
|
||||
text = html_file.read()
|
||||
|
||||
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "prepend.h")) as prepend_file:
|
||||
prepend = prepend_file.read()
|
||||
|
||||
text = text.strip()
|
||||
text = text.replace("\"", "\\\"")
|
||||
text = text.replace("%VERSION%", "\" VERSION \"")
|
||||
@@ -48,7 +46,7 @@ def main():
|
||||
for line in text.split("\n")
|
||||
)
|
||||
text = "const char HTML_%s_PAGE[] = \" \\\n%s\n\";\n" % (name, text)
|
||||
text = prepend + "\n#include \"../../config.h\"\n\n\n" + text
|
||||
text = common.get_prepend() + "\n#include \"../../config.h\"\n\n\n" + text
|
||||
|
||||
with open(header_path, "w") as header_file:
|
||||
header_file.write(text)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env -S python3 -B
|
||||
#============================================================================#
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
@@ -22,13 +22,17 @@
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
import io
|
||||
import struct
|
||||
|
||||
from typing import Tuple
|
||||
from typing import List
|
||||
|
||||
import common
|
||||
|
||||
|
||||
# =====
|
||||
def _get_jpeg_size(data):
|
||||
def _get_jpeg_size(data: bytes) -> Tuple[int, int]:
|
||||
# https://sheep.horse/2013/9/finding_the_dimensions_of_a_jpeg_file_in_python.html
|
||||
|
||||
stream = io.BytesIO(data)
|
||||
@@ -55,7 +59,7 @@ def _get_jpeg_size(data):
|
||||
|
||||
|
||||
# =====
|
||||
def main():
|
||||
def main() -> None:
|
||||
assert len(sys.argv) == 4, "%s <file.jpeg> <file.h> <name>" % (sys.argv[0])
|
||||
jpeg_path = sys.argv[1]
|
||||
header_path = sys.argv[2]
|
||||
@@ -66,10 +70,7 @@ def main():
|
||||
|
||||
(width, height) = _get_jpeg_size(jpeg_data)
|
||||
|
||||
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "prepend.h")) as prepend_file:
|
||||
prepend = prepend_file.read()
|
||||
|
||||
rows = [[]]
|
||||
rows: List[List[str]] = [[]]
|
||||
for ch in jpeg_data:
|
||||
if len(rows[-1]) > 20:
|
||||
rows.append([])
|
||||
@@ -79,7 +80,7 @@ def main():
|
||||
text = "const unsigned char %s_JPEG_DATA[] = {\n\t%s\n};\n" % (name, text)
|
||||
text = "const unsigned %s_JPEG_HEIGHT = %d;\n\n" % (name, height) + text
|
||||
text = "const unsigned %s_JPEG_WIDTH = %d;\n" % (name, width) + text
|
||||
text = prepend + "\n\n" + text
|
||||
text = common.get_prepend() + "\n\n" + text
|
||||
|
||||
with open(header_path, "w") as header_file:
|
||||
header_file.write(text)
|
||||
|
||||
Reference in New Issue
Block a user