Compare commits

...

17 Commits
v0.79 ... v1.1

Author SHA1 Message Date
Devaev Maxim
16d5c81c22 Bump version: 1.0 → 1.1 2019-09-01 22:43:13 +03:00
Devaev Maxim
58e3a77a79 changed upstream url 2019-09-01 22:42:58 +03:00
Devaev Maxim
16105db7a0 refactoring 2019-07-27 18:22:08 +03:00
Devaev Maxim
6e307b1ef4 Bump version: 0.81 → 1.0 2019-07-13 07:15:28 +03:00
Devaev Maxim
554491ff19 messages refactoring 2019-07-13 07:14:33 +03:00
Devaev Maxim
fcd70c3166 Bump version: 0.80 → 0.81 2019-07-13 01:06:35 +03:00
Devaev Maxim
aaed14e9de wider fps and resolution range 2019-07-13 01:06:21 +03:00
Devaev Maxim
76ba25607b Bump version: 0.79 → 0.80 2019-07-13 00:30:13 +03:00
Devaev Maxim
1d8dedea85 --glitched-resolutions 2019-07-12 23:03:42 +03:00
Devaev Maxim
dfe8245181 refactoring 2019-07-12 19:09:54 +03:00
Devaev Maxim
50569a53a0 show gpio feature in version 2019-07-12 05:07:35 +03:00
Devaev Maxim
6ace44e4de refactoring 2019-07-11 19:40:42 +03:00
Devaev Maxim
c4a5eea75b --(fake-)resolution instead of --(fake-)width/height 2019-07-11 16:48:33 +03:00
Devaev Maxim
87de066369 refactoring 2019-07-10 07:08:29 +03:00
Devaev Maxim
c3c15b16bf WITH_OMX_ENCODER -> WITH_OMX 2019-07-10 02:20:46 +03:00
Devaev Maxim
e6dfe3d2b7 gpio 2019-07-09 20:11:41 +03:00
Devaev Maxim
fe8699b7f3 log_level = LOG_LEVEL_INFO 2019-07-03 00:26:55 +03:00
27 changed files with 1189 additions and 871 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 0.79
current_version = 1.1
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
serialize =
{major}.{minor}

View File

@@ -23,16 +23,16 @@ $(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifneq ($(call optbool,$(WITH_OMX_ENCODER)),)
LIBS += -lbcm_host -lvcos -lopenmaxil -L$(RPI_VC_LIBS)
override CFLAGS += -DWITH_OMX_ENCODER -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
SOURCES += $(shell ls src/encoders/omx/*.c)
ifneq ($(call optbool,$(WITH_OMX)),)
LIBS += -lbcm_host -lvcos -lopenmaxil -L$(RPI_VC_LIBS)
override CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
SOURCES += $(shell ls src/encoders/omx/*.c)
endif
ifneq ($(call optbool,$(WITH_WORKERS_GPIO_DEBUG)),)
LIBS += -lwiringPi
override CFLAGS += -DWITH_WORKERS_GPIO_DEBUG
ifneq ($(call optbool,$(WITH_GPIO)),)
LIBS += -lwiringPi
override CFLAGS += -DWITH_GPIO
endif

View File

@@ -2,7 +2,7 @@
[[Русская версия]](README.ru.md)
µStreamer is a lightweight and very quick server to broadcast [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
µStreamer is a part of the [Pi-KVM](https://github.com/pi-kvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
µStreamer is a part of the [Pi-KVM](https://github.com/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
µStreamer is very similar to [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) with ```input_uvc.so``` and ```output_http.so``` plugins, however, there are some major differences. The key ones are:
@@ -16,6 +16,7 @@
| Streaming via UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Option to serve files<br>with a built-in HTTP server | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Regular files only |
| Signaling about the stream state to GPIO<br>on Raspberry Pi using [wiringPi](http://wiringpi.com) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
Footnotes:
@@ -31,16 +32,16 @@ If you're going to live-stream from your backyard webcam and need to control it,
# Building
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libuuid```.
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX_ENCODER=1``` to ```make```.
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [wiringPi](http://wiringpi.com) and pass option ```WITH_GPIO=1```.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
$ git clone --depth=1 https://github.com/pikvm/ustreamer
$ cd ustreamer
$ make
$ ./ustreamer --help
```
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```. Same with GPIO.
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
-----

View File

@@ -2,7 +2,7 @@
[[English version]](README.md)
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pi-kvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
@@ -16,6 +16,7 @@
| Стрим через UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Возможность сервить файлы встроенным<br>HTTP-сервером | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Нет каталогов |
| Вывод сигналов о состоянии стрима на GPIO<br>на Raspberry Pi с помощью [wiringPi](http://wiringpi.com) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
Сносочки:
@@ -31,16 +32,16 @@
# Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX_ENCODER=1```.
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [wiringPi](http://wiringpi.com) и добавьте параметр ```WITH_GPIO=1```.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
$ git clone --depth=1 https://github.com/pikvm/ustreamer
$ cd ustreamer
$ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```. То же самое и с GPIO.
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
-----

View File

@@ -3,16 +3,17 @@
pkgname=ustreamer
pkgver=0.79
pkgver=1.1
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pi-kvm/ustreamer"
url="https://github.com/pikvm/ustreamer"
license=(GPL)
arch=(i686 x86_64 armv6h armv7h)
depends=(libjpeg libevent libutil-linux)
# optional: raspberrypi-firmware for OMX JPEG encoder
# optional: raspberrypi-firmware for OMX encoder
# optional: wiringpi for GPIO support
makedepends=(gcc make)
source=(${pkgname}::"git+https://github.com/pi-kvm/ustreamer#commit=v${pkgver}")
source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP)
@@ -23,7 +24,8 @@ build() {
cd $pkgname-build
local _options=""
[ -d /opt/vc/include ] && _options="$_options WITH_OMX_ENCODER=1"
[ -e /opt/vc/include/IL/OMX_Core.h ] && _options="$_options WITH_OMX=1"
[ -e /usr/include/wiringPi.h ] && _options="$_options WITH_GPIO=1"
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
}

View File

@@ -6,8 +6,8 @@ EAPI=7
inherit git-r3
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"
HOMEPAGE="https://github.com/pikvm/ustreamer"
EGIT_REPO_URI="https://github.com/pikvm/ustreamer.git"
LICENSE="GPL-3"
SLOT="0"

View File

@@ -6,12 +6,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=0.79
PKG_VERSION:=1.1
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_URL:=https://github.com/pikvm/ustreamer.git
PKG_SOURCE_VERSION:=v$(PKG_VERSION)
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
@@ -26,7 +26,7 @@ define Package/ustreamer
CATEGORY:=Multimedia
TITLE:=uStreamer
DEPENDS:=+libpthread +libjpeg +libv4l +libuuid +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads
URL:=https://github.com/pi-kvm/ustreamer
URL:=https://github.com/pikvm/ustreamer
endef
define Package/ustreamer/description

View File

@@ -6,8 +6,7 @@ config ustreamer
option device_timeout '5'
option input '0'
option width '640'
option height '480'
option resolution '640x480'
option format 'YUYV'
option quality '80'
option desired_fps '0'

View File

@@ -22,8 +22,7 @@ start_instance() {
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 --resolution='`getcfg resolution 640x480`'"
options="$options --format='`getcfg format YUYV`'"
options="$options --quality='`getcfg quality 80`'"
options="$options --desired-fps='`getcfg desired_fps 0`'"

View File

@@ -23,5 +23,5 @@
#pragma once
#ifndef VERSION
# define VERSION "0.79"
# define VERSION "1.1"
#endif

View File

@@ -84,7 +84,6 @@ static const char *_standard_to_string(v4l2_std_id standard);
struct device_t *device_init(void) {
struct controls_t *ctl;
struct device_runtime_t *run;
struct device_t *dev;
long cores_sysconf;
@@ -94,8 +93,6 @@ struct device_t *device_init(void) {
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
cores_available = max_u(min_u(cores_sysconf, 4), 1);
A_CALLOC(ctl, 1);
A_CALLOC(run, 1);
run->fd = -1;
@@ -109,14 +106,12 @@ struct device_t *device_init(void) {
dev->n_workers = min_u(cores_available, dev->n_buffers);
dev->timeout = 1;
dev->error_delay = 1;
dev->ctl = ctl;
dev->run = run;
return dev;
}
void device_destroy(struct device_t *dev) {
free(dev->run);
free(dev->ctl);
free(dev);
}
@@ -618,14 +613,14 @@ static void _device_apply_controls(struct device_t *dev) {
}
# define SET_CID_MANUAL(_cid, _dest) { \
if (dev->ctl->_dest.value_set) { \
SET_CID(_cid, _dest, dev->ctl->_dest.value, false); \
if (dev->ctl._dest.value_set) { \
SET_CID(_cid, _dest, dev->ctl._dest.value, false); \
} \
}
# define SET_CID_AUTO(_cid_auto, _cid_manual, _dest) { \
if (dev->ctl->_dest.value_set || dev->ctl->_dest.auto_set) { \
SET_CID(_cid_auto, _dest##_auto, dev->ctl->_dest.auto_set, dev->ctl->_dest.value_set); \
if (dev->ctl._dest.value_set || dev->ctl._dest.auto_set) { \
SET_CID(_cid_auto, _dest##_auto, dev->ctl._dest.auto_set, dev->ctl._dest.value_set); \
SET_CID_MANUAL(_cid_manual, _dest); \
} \
}

View File

@@ -28,11 +28,13 @@
#include <linux/videodev2.h>
#define VIDEO_MIN_WIDTH 320
#define VIDEO_MAX_WIDTH 1920
#define VIDEO_MIN_WIDTH 160
#define VIDEO_MAX_WIDTH 10240
#define VIDEO_MIN_HEIGHT 180
#define VIDEO_MAX_HEIGHT 1200
#define VIDEO_MIN_HEIGHT 120
#define VIDEO_MAX_HEIGHT 4320
#define VIDEO_MAX_FPS 120
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define STANDARDS_STR "PAL, NTSC, SECAM"
@@ -104,7 +106,7 @@ struct device_t {
unsigned timeout;
unsigned error_delay;
struct controls_t *ctl;
struct controls_t ctl;
struct device_runtime_t *run;
};

View File

@@ -35,7 +35,7 @@
#include "encoders/cpu/encoder.h"
#include "encoders/hw/encoder.h"
#ifdef WITH_OMX_ENCODER
#ifdef WITH_OMX
# include "encoders/omx/encoder.h"
#endif
@@ -46,7 +46,7 @@ static const struct {
} _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU},
{"HW", ENCODER_TYPE_HW},
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
{"OMX", ENCODER_TYPE_OMX},
# endif
};
@@ -69,7 +69,7 @@ struct encoder_t *encoder_init(void) {
}
void encoder_destroy(struct encoder_t *encoder) {
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
if (encoder->run->omxs) {
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
if (encoder->run->omxs[index]) {
@@ -108,13 +108,13 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
bool cpu_forced = false;
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");
LOG_INFO("Switching to HW encoder because the input format is (M)JPEG");
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");
LOG_INFO("Switching to CPU encoder because the input format is not (M)JPEG");
goto use_cpu;
}
@@ -124,12 +124,23 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
dev->run->n_workers = 1;
}
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
else if (type == ENCODER_TYPE_OMX) {
LOG_DEBUG("Preparing OMX JPEG encoder ...");
for (unsigned index = 0; index < encoder->n_glitched_resolutions; ++index) {
if (
encoder->glitched_resolutions[index][0] == dev->run->width
&& encoder->glitched_resolutions[index][1] == dev->run->height
) {
LOG_INFO("Switching to CPU encoder the resolution %ux%u marked as glitchy for OMX",
dev->run->width, dev->run->height);
goto use_cpu;
}
}
LOG_DEBUG("Preparing OMX encoder ...");
if (dev->run->n_workers > OMX_MAX_ENCODERS) {
LOG_INFO("OMX JPEG encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
LOG_INFO("OMX encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
dev->run->n_workers = OMX_MAX_ENCODERS;
}
@@ -140,14 +151,14 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
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");
LOG_ERROR("Can't initialize OMX encoder, falling back to CPU");
goto force_cpu;
}
}
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
if (omx_encoder_prepare(encoder->run->omxs[index], dev, quality) < 0) {
LOG_ERROR("Can't prepare OMX JPEG encoder, falling back to CPU");
LOG_ERROR("Can't prepare OMX encoder, falling back to CPU");
goto force_cpu;
}
}
@@ -196,7 +207,7 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
} else if (encoder->run->type == ENCODER_TYPE_HW) {
hw_encoder_compress_buffer(dev, buf_index);
}
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
else if (encoder->run->type == ENCODER_TYPE_OMX) {
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
goto error;

View File

@@ -29,9 +29,14 @@
#include "tools.h"
#include "device.h"
#ifdef WITH_OMX_ENCODER
#ifdef WITH_OMX
# include "encoders/omx/encoder.h"
# define ENCODER_TYPES_OMX_HINT ", OMX"
# ifndef MAX_GLITCHED_RESOLUTIONS
# define MAX_GLITCHED_RESOLUTIONS 1024
# endif
#else
# define ENCODER_TYPES_OMX_HINT ""
#endif
@@ -45,7 +50,7 @@ enum encoder_type_t {
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
ENCODER_TYPE_CPU,
ENCODER_TYPE_HW,
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
ENCODER_TYPE_OMX,
# endif
};
@@ -56,7 +61,7 @@ struct encoder_runtime_t {
bool cpu_forced;
pthread_mutex_t mutex;
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
unsigned n_omxs;
struct omx_encoder_t **omxs;
# endif
@@ -65,6 +70,10 @@ struct encoder_runtime_t {
struct encoder_t {
enum encoder_type_t type;
unsigned quality;
# ifdef WITH_OMX
unsigned n_glitched_resolutions;
unsigned glitched_resolutions[2][MAX_GLITCHED_RESOLUTIONS];
# endif
struct encoder_runtime_t *run;
};

View File

@@ -100,7 +100,7 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
WRITE_SCANLINES(V4L2_PIX_FMT_UYVY, _jpeg_write_scanlines_uyvy);
WRITE_SCANLINES(V4L2_PIX_FMT_RGB565, _jpeg_write_scanlines_rgb565);
WRITE_SCANLINES(V4L2_PIX_FMT_RGB24, _jpeg_write_scanlines_rgb24);
default: assert(0 && "Unsupported input format for CPU JPEG encoder");
default: assert(0 && "Unsupported input format for CPU encoder");
}
# undef WRITE_SCANLINES

View File

@@ -51,12 +51,12 @@ int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
MEMSET_ZERO(comp);
if (xioctl(dev->run->fd, VIDIOC_G_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Can't query HW JPEG encoder params and set quality (unsupported)");
LOG_ERROR("Can't query HW encoder params and set quality (unsupported)");
return -1;
}
comp.quality = quality;
if (xioctl(dev->run->fd, VIDIOC_S_JPEGCOMP, &comp) < 0) {
LOG_ERROR("Can't set HW JPEG encoder quality (unsopported)");
LOG_ERROR("Can't set HW encoder quality (unsupported)");
return -1;
}
return 0;
@@ -64,7 +64,7 @@ int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index) {
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
assert(0 && "Unsupported input format for HW JPEG encoder");
assert(0 && "Unsupported input format for HW encoder");
}
# define PICTURE(_next) dev->run->pictures[index]._next

View File

@@ -99,7 +99,7 @@ struct omx_encoder_t *omx_encoder_init(void) {
}
_i_omx += 1;
LOG_INFO("Initializing OMX JPEG encoder ...");
LOG_INFO("Initializing OMX encoder ...");
if (vcos_semaphore_create(&omx->handler_lock, "handler_lock", 0) != VCOS_SUCCESS) {
LOG_ERROR("Can't create VCOS semaphore");
@@ -125,7 +125,7 @@ struct omx_encoder_t *omx_encoder_init(void) {
void omx_encoder_destroy(struct omx_encoder_t *omx) {
OMX_ERRORTYPE error;
LOG_INFO("Destroying OMX JPEG encoder ...");
LOG_INFO("Destroying OMX encoder ...");
component_set_state(&omx->encoder, OMX_StateIdle);
_omx_encoder_clear_ports(omx);
@@ -329,7 +329,7 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
// FIXME: RGB24 не работает нормально, нижняя половина экрана зеленая.
// FIXME: Китайский EasyCap тоже не работает, мусор на экране.
// Вероятно обе проблемы вызваны некорректной реализацией OMX на пае.
default: assert(0 && "Unsupported input format for OMX JPEG encoder");
default: assert(0 && "Unsupported input format for OMX encoder");
}
# undef MAP_FORMAT

92
src/gpio.h Normal file
View File

@@ -0,0 +1,92 @@
/*****************************************************************************
# #
# 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 <wiringPi.h>
#include "logging.h"
int gpio_pin_prog_running;
int gpio_pin_stream_online;
int gpio_pin_has_http_clients;
int gpio_pin_workers_busy_at;
#define GPIO_INIT { \
gpio_pin_prog_running = -1; \
gpio_pin_stream_online = -1; \
gpio_pin_has_http_clients = -1; \
gpio_pin_workers_busy_at = -1; \
}
#define GPIO_INIT_PIN(_role, _offset) { \
if (gpio_pin_##_role >= 0) { \
pinMode(gpio_pin_##_role + _offset, OUTPUT); \
digitalWrite(gpio_pin_##_role + _offset, LOW); \
if (_offset == 0) { \
LOG_INFO("GPIO: Using pin %d as %s", gpio_pin_##_role, #_role); \
} else { \
LOG_INFO("GPIO: Using pin %d+%d as %s", gpio_pin_##_role, _offset, #_role); \
} \
} \
}
#define GPIO_INIT_PINOUT { \
if ( \
gpio_pin_prog_running >= 0 \
|| gpio_pin_stream_online >= 0 \
|| gpio_pin_has_http_clients >= 0 \
|| gpio_pin_workers_busy_at >= 0 \
) { \
LOG_INFO("GPIO: Using wiringPi"); \
if (wiringPiSetupGpio() < 0) { \
LOG_PERROR("GPIO: Can't initialize wiringPi"); \
exit(1); \
} else { \
GPIO_INIT_PIN(prog_running, 0); \
GPIO_INIT_PIN(stream_online, 0); \
GPIO_INIT_PIN(has_http_clients, 0); \
GPIO_INIT_PIN(workers_busy_at, 0); \
} \
} \
}
#define GPIO_SET_STATE(_role, _offset, _state) { \
if (gpio_pin_##_role >= 0) { \
if (_offset == 0) { \
LOG_DEBUG("GPIO: Writing %d to pin %d (%s)", _state, gpio_pin_##_role, #_role); \
} else { \
LOG_DEBUG("GPIO: Writing %d to pin %d+%d (%s)", _state, gpio_pin_##_role, _offset, #_role); \
} \
digitalWrite(gpio_pin_##_role + _offset, _state); \
} \
}
#define GPIO_SET_LOW(_role) GPIO_SET_STATE(_role, 0, LOW)
#define GPIO_SET_HIGH(_role) GPIO_SET_STATE(_role, 0, HIGH)
#define GPIO_SET_LOW_AT(_role, _offset) GPIO_SET_STATE(_role, _offset, LOW)
#define GPIO_SET_HIGH_AT(_role, _offset) GPIO_SET_STATE(_role, _offset, HIGH)

File diff suppressed because it is too large Load Diff

View File

@@ -54,6 +54,6 @@
</ul>
<br>
<hr>
<a href="https://github.com/pi-kvm/ustreamer">Sources &amp; docs</a>
<a href="https://github.com/pikvm/ustreamer">Sources &amp; docs</a>
</body>
</html>

View File

@@ -82,7 +82,7 @@ const char HTML_INDEX_PAGE[] = " \
</ul> \
<br> \
<hr> \
<a href=\"https://github.com/pi-kvm/ustreamer\">Sources &amp; docs</a> \
<a href=\"https://github.com/pikvm/ustreamer\">Sources &amp; docs</a> \
</body> \
</html> \
";

View File

@@ -53,6 +53,9 @@
#include "../logging.h"
#include "../encoder.h"
#include "../stream.h"
#ifdef WITH_GPIO
# include "../gpio.h"
#endif
#include "blank.h"
#include "base64.h"
@@ -564,8 +567,14 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
}
server->run->stream_clients_count += 1;
if (server->slowdown && server->run->stream_clients_count == 1) {
stream_switch_slowdown(server->run->stream, false);
if (server->run->stream_clients_count == 1) {
if (server->slowdown) {
stream_switch_slowdown(server->run->stream, false);
}
# ifdef WITH_GPIO
GPIO_SET_HIGH(has_http_clients);
# endif
}
evhttp_connection_get_peer(conn, &client_addr, &client_port);
@@ -717,8 +726,15 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
# define RUN(_next) client->server->run->_next
RUN(stream_clients_count) -= 1;
if (client->server->slowdown && RUN(stream_clients_count) <= 0) {
stream_switch_slowdown(RUN(stream), true);
if (RUN(stream_clients_count) <= 0) {
if (client->server->slowdown) {
stream_switch_slowdown(RUN(stream), true);
}
# ifdef WITH_GPIO
GPIO_SET_LOW(has_http_clients);
# endif
}
conn = evhttp_request_get_connection(client->request);

View File

@@ -44,8 +44,12 @@ pthread_mutex_t log_mutex;
#define LOG_LEVEL_DEBUG 3
#define LOGGING_INIT assert(!pthread_mutex_init(&log_mutex, NULL))
#define LOGGING_DESTROY assert(!pthread_mutex_destroy(&log_mutex))
#define LOGGING_INIT { \
log_level = LOG_LEVEL_INFO; \
assert(!pthread_mutex_init(&log_mutex, NULL)); \
}
#define LOGGING_DESTROY assert(!pthread_mutex_destroy(&log_mutex))
#define LOGGING_LOCK assert(!pthread_mutex_lock(&log_mutex))
#define LOGGING_UNLOCK assert(!pthread_mutex_unlock(&log_mutex))

View File

@@ -27,15 +27,13 @@
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#include <signal.h>
#include <getopt.h>
#include <pthread.h>
#ifdef WITH_WORKERS_GPIO_DEBUG
# include <wiringPi.h>
#endif
#include "config.h"
#include "tools.h"
#include "logging.h"
@@ -43,12 +41,20 @@
#include "encoder.h"
#include "stream.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio.h"
#endif
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 char _SHORT_OPTS[] = "d:i:r:x:y:m:a:f:z:ntb:w:q:c:s:p:U:DM:k:e:lR:hv"
#ifdef WITH_OMX
"g:"
#endif
;
static const struct option _LONG_OPTS[] = {
{"device", required_argument, NULL, 'd'},
{"input", required_argument, NULL, 'i'},
{"resolution", required_argument, NULL, 'r'},
{"width", required_argument, NULL, 'x'},
{"height", required_argument, NULL, 'y'},
{"format", required_argument, NULL, 'm'},
@@ -61,6 +67,9 @@ static const struct option _LONG_OPTS[] = {
{"workers", required_argument, NULL, 'w'},
{"quality", required_argument, NULL, 'q'},
{"encoder", required_argument, NULL, 'c'},
# ifdef WITH_OMX
{"glitched-resolutions", required_argument, NULL, 'g'},
# endif
{"device-timeout", required_argument, NULL, 1000},
{"device-error-delay", required_argument, NULL, 1001},
@@ -80,19 +89,27 @@ static const struct option _LONG_OPTS[] = {
{"host", required_argument, NULL, 's'},
{"port", required_argument, NULL, 'p'},
{"unix", required_argument, NULL, 'u'},
{"unix-rm", no_argument, NULL, 'r'},
{"unix-mode", required_argument, NULL, 'o'},
{"unix", required_argument, NULL, 'U'},
{"unix-rm", no_argument, NULL, 'D'},
{"unix-mode", required_argument, NULL, 'M'},
{"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-resolution", required_argument, NULL, 'R'},
{"fake-width", required_argument, NULL, 3003},
{"fake-height", required_argument, NULL, 3004},
{"server-timeout", required_argument, NULL, 3005},
#ifdef WITH_GPIO
{"gpio-prog-running", required_argument, NULL, 4000},
{"gpio-stream-online", required_argument, NULL, 4001},
{"gpio-has-http-clients", required_argument, NULL, 4002},
{"gpio-workers-busy-at", required_argument, NULL, 4003},
#endif
{"perf", no_argument, NULL, 5000},
{"verbose", no_argument, NULL, 5001},
{"debug", no_argument, NULL, 5002},
@@ -104,8 +121,11 @@ static const struct option _LONG_OPTS[] = {
static void _version(bool nl) {
printf(VERSION);
# ifdef WITH_OMX_ENCODER
# ifdef WITH_OMX
printf(" + OMX");
# endif
# ifdef WITH_GPIO
printf(" + GPIO");
# endif
if (nl) {
putchar('\n');
@@ -121,31 +141,34 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf("Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com>\n\n");
printf("Capturing options:\n");
printf("══════════════════\n");
printf(" -d|--device </dev/path> ──────── Path to V4L2 device. Default: %s.\n\n", dev->path);
printf(" -i|--input <N> ───────────────── Input channel. Default: %u.\n\n", dev->input);
printf(" -x|--width <N> ───────────────── Initial image width. Default: %u.\n\n", dev->width);
printf(" -y|--height <N> ──────────────── Initial image height. Default: %u.\n\n", dev->height);
printf(" -m|--format <fmt> ────────────── Image format.\n");
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
printf(" -a|--tv-standard <std> ───────── Force TV standard.\n");
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
printf(" -f|--desired-fps <N> ─────────── Desired FPS. Default: maximum as possible.\n\n");
printf(" -z|--min-frame-size <N> ──────── Drop frames smaller then this limit. Useful if the device\n");
printf(" produces small-sized garbage frames. Default: disabled.\n\n");
printf(" -n|--persistent ──────────────── Don't re-initialize device on timeout. Default: disabled.\n\n");
printf(" -t|--dv-timings ──────────────── Enable DV timings queriyng and events processing\n");
printf(" to automatic resolution change. Default: disabled.\n\n");
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 than 4) + 1).\n\n", dev->n_buffers);
printf(" -w|--workers <N> ─────────────── The number of worker threads but not more than buffers.\n");
printf(" Default: %u (the number of CPU cores (but not more than 4)).\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);
printf(" --device-timeout <seconds> ───── Timeout for device querying. Default: %u.\n\n", dev->timeout);
printf(" --device-error-delay <seconds> ─ Delay before trying to connect to the device again\n");
printf(" after an error (timeout for example). Default: %u.\n\n", dev->error_delay);
printf(" -d|--device </dev/path> ───────────── Path to V4L2 device. Default: %s.\n\n", dev->path);
printf(" -i|--input <N> ────────────────────── Input channel. Default: %u.\n\n", dev->input);
printf(" -r|--resolution <WxH> ─────────────── Initial image resolution. Default: %ux%u.\n\n", dev->width, dev->height);
printf(" -m|--format <fmt> ─────────────────── Image format.\n");
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
printf(" -a|--tv-standard <std> ────────────── Force TV standard.\n");
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
printf(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n\n");
printf(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device\n");
printf(" produces small-sized garbage frames. Default: disabled.\n\n");
printf(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. Default: disabled.\n\n");
printf(" -t|--dv-timings ───────────────────── Enable DV timings querying and events processing\n");
printf(" to automatic resolution change. Default: disabled.\n\n");
printf(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device.\n");
printf(" Each buffer may processed using an independent thread.\n");
printf(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n\n", dev->n_buffers);
printf(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers.\n");
printf(" Default: %u (the number of CPU cores (but not more than 4)).\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 affect the number of workers.\n\n");
# ifdef WITH_OMX
printf(" -g|--glitched-resolutions <WxH,...> ─ Comma-separated list of resolutions that require forced\n");
# endif
printf(" encoding on CPU instead of OMX. Default: disabled.\n");
printf(" Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
printf(" --device-timeout <seconds> ────────── Timeout for device querying. Default: %u.\n\n", dev->timeout);
printf(" --device-error-delay <seconds> ────── Delay before trying to connect to the device again\n");
printf(" after an error (timeout for example). Default: %u.\n\n", dev->error_delay);
printf("Image control options:\n");
printf("══════════════════════\n");
printf(" --brightness <N> ───────────── Set brightness. Default: no change.\n\n");
@@ -165,29 +188,37 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf("════════════════════\n");
printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s.\n\n", server->host);
printf(" -p|--port <N> ────────────── Bind to this TCP port. Default: %u.\n\n", server->port);
printf(" -u|--unix <path> ─────────── Bind to UNIX domain socket. Default: disabled.\n\n");
printf(" -r|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n\n");
printf(" -o|--unix-mode <mode> ────── Set UNIX socket file permissions (like 777). Default: disabled.\n\n");
printf(" -U|--unix <path> ─────────── Bind to UNIX domain socket. Default: disabled.\n\n");
printf(" -D|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n\n");
printf(" -M|--unix-mode <mode> ────── Set UNIX socket file permissions (like 777). Default: disabled.\n\n");
printf(" --user <name> ────────────── HTTP basic auth user. Default: disabled.\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(" 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 identical 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(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients are connected.\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(" -R|--fake-resolution <WxH> ─ Override image resolution for state. Default: disabled.\n\n");
printf(" --server-timeout <seconds> ─ Timeout for client connections. Default: %u.\n\n", server->timeout);
#ifdef WITH_GPIO
printf("GPIO options:\n");
printf("═════════════\n");
printf(" --gpio-prog-running <pin> ───── Set 1 on GPIO pin while uStreamer is running. Default: disabled.\n\n");
printf(" --gpio-stream-online <pin> ──── Set 1 while streaming. Default: disabled\n\n");
printf(" --gpio-has-http-clients <pin> ─ Set 1 while stream has at least one client. Default: disabled.\n\n");
printf(" --gpio-workers-busy-at <pin> ── Set 1 on (pin + N) while worker with number N has a job.\n");
printf(" The worker's numbering starts from 0. Default: disabled\n\n");
#endif
printf("Misc options:\n");
printf("═════════════\n");
printf(" --log-level <N> ─ Verbosity level of messages from 0 (info) to 3 (debug).\n");
printf(" Enabling debugging messages can slow down the program.\n");
printf(" Available levels: 0=info, 1=performance, 2=verbose, 3=debug.\n");
printf(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).\n");
printf(" Default: %u.\n\n", log_level);
printf(" --perf ────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n");
printf(" --verbose ─────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n");
@@ -196,12 +227,84 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf(" -v|--version ──── Print version and exit.\n\n");
}
static int _parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
# define OPT_SET(_dest, _value) \
{ _dest = _value; break; }
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited) {
unsigned tmp_width;
unsigned tmp_height;
# define OPT_UNSIGNED(_dest, _name, _min, _max) { \
errno = 0; char *_end = NULL; int _tmp = strtol(optarg, &_end, 0); \
if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) {
return -1;
}
if (limited) {
if (tmp_width < VIDEO_MIN_WIDTH || tmp_width > VIDEO_MAX_WIDTH) {
return -2;
}
if (tmp_height < VIDEO_MIN_HEIGHT || tmp_height > VIDEO_MAX_HEIGHT) {
return -3;
}
}
*width = tmp_width;
*height = tmp_height;
return 0;
}
#ifdef WITH_OMX
static int _parse_glitched_resolutions(const char *str, struct encoder_t *encoder) {
char *str_copy;
char *ptr;
unsigned count = 0;
unsigned width;
unsigned height;
assert((str_copy = strdup(str)) != NULL);
ptr = strtok(str_copy, ",;:\n\t ");
while (ptr != NULL) {
if (count >= MAX_GLITCHED_RESOLUTIONS) {
printf("Too big '--glitched-resolutions' list: maxlen=%u\n", MAX_GLITCHED_RESOLUTIONS);
goto error;
}
switch (_parse_resolution(ptr, &width, &height, true)) {
case -1:
printf("Invalid resolution format of '%s' in '--glitched-resolutions=%s\n", ptr, str_copy);
goto error;
case -2:
printf("Invalid width of '%s' in '--glitched-resolutions=%s: min=%u, max=%u\n",
ptr, str_copy, VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT);
goto error;
case -3:
printf("Invalid width of '%s' in '--glitched-resolutions=%s: min=%u, max=%u\n",
ptr, str_copy, VIDEO_MIN_WIDTH, VIDEO_MIN_HEIGHT);
goto error;
case 0: break;
default: assert(0 && "Unknown error");
}
encoder->glitched_resolutions[count][0] = width;
encoder->glitched_resolutions[count][1] = height;
count += 1;
ptr = strtok(NULL, ",;:\n\t ");
}
encoder->n_glitched_resolutions = count;
free(str_copy);
return 0;
error:
free(str_copy);
return -1;
}
#endif
static int _parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
# define OPT_SET(_dest, _value) { \
_dest = _value; \
break; \
}
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%u, max=%u\n", _name, optarg, _min, _max); \
return -1; \
@@ -210,7 +313,38 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
break; \
}
# define OPT_PARSE(_dest, _func, _invalid, _name) { \
# define OPT_RESOLUTION(_name, _dest_width, _dest_height, _limited) { \
switch (_parse_resolution(optarg, &_dest_width, &_dest_height, _limited)) { \
case -1: \
printf("Invalid resolution format for '%s=%s'\n", _name, optarg); \
return -1; \
case -2: \
printf("Invalid width of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH); \
return -1; \
case -3: \
printf("Invalid height of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT); \
return -1; \
case 0: break; \
default: assert(0 && "Unknown error"); \
} \
break; \
}
# define OPT_RESOLUTION_OBSOLETE(_name, _replace, _dest, _min, _max) { \
printf("\n=== WARNING! The option '%s' is obsolete; use '%s' instead it ===\n\n", _name, _replace); \
OPT_NUMBER(_name, _dest, _min, _max, 0); \
}
# ifdef WITH_OMX
# define OPT_GLITCHED_RESOLUTIONS { \
if (_parse_glitched_resolutions(optarg, encoder) < 0) { \
return -1; \
} \
break; \
}
# endif
# define OPT_PARSE(_name, _dest, _func, _invalid) { \
if ((_dest = _func(optarg)) == _invalid) { \
printf("Unknown " _name ": %s\n", optarg); \
return -1; \
@@ -218,29 +352,16 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
break; \
}
# define OPT_INT(_dest, _name, _base) { \
errno = 0; char *_end = NULL; int _tmp = strtol(optarg, &_end, _base); \
if (errno || *_end) { \
printf("Invalid value for '%s=%s'\n", _name, optarg); \
return -1; \
} \
_dest = _tmp; \
break; \
}
# define OPT_CHMOD(_dest, _name) \
OPT_INT(_dest, _name, 8)
# define OPT_CTL(_dest) { \
dev->ctl->_dest.value_set = true; \
dev->ctl->_dest.auto_set = false; \
OPT_INT(dev->ctl->_dest.value, "--"#_dest, 10); \
dev->ctl._dest.value_set = true; \
dev->ctl._dest.auto_set = false; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
break; \
}
# define OPT_CTL_AUTO(_dest) { \
dev->ctl->_dest.value_set = false; \
dev->ctl->_dest.auto_set = true; \
dev->ctl._dest.value_set = false; \
dev->ctl._dest.auto_set = true; \
break; \
}
@@ -251,24 +372,28 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
while ((ch = getopt_long(argc, argv, _SHORT_OPTS, _LONG_OPTS, &index)) >= 0) {
switch (ch) {
case 'd': OPT_SET(dev->path, optarg);
case 'i': OPT_UNSIGNED(dev->input, "--input", 0, 128);
case 'x': OPT_UNSIGNED(dev->width, "--width", VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH);
case 'y': OPT_UNSIGNED(dev->height, "--height", VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT);
case 'i': OPT_NUMBER("--input", dev->input, 0, 128, 0);
case 'r': OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
case 'x': OPT_RESOLUTION_OBSOLETE("--width", "--resolution", dev->width, VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH);
case 'y': OPT_RESOLUTION_OBSOLETE("--height", "--resolution", dev->height, VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT);
# pragma GCC diagnostic ignored "-Wsign-compare"
# pragma GCC diagnostic push
case 'm': OPT_PARSE(dev->format, device_parse_format, FORMAT_UNKNOWN, "pixel format");
case 'm': OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN);
# pragma GCC diagnostic pop
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard");
case 'f': OPT_UNSIGNED(dev->desired_fps, "--desired-fps", 0, 30);
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192);
case 'a': OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN);
case 'f': OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
case 'z': OPT_NUMBER("--min-frame-size", dev->min_frame_size, 0, 8192, 0);
case 'n': OPT_SET(dev->persistent, true);
case 't': OPT_SET(dev->dv_timings, true);
case 'b': OPT_UNSIGNED(dev->n_buffers, "--buffers", 1, 32);
case 'w': OPT_UNSIGNED(dev->n_workers, "--workers", 1, 32);
case 'q': OPT_UNSIGNED(encoder->quality, "--quality", 1, 100);
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type");
case 1000: OPT_UNSIGNED(dev->timeout, "--device-timeout", 1, 60);
case 1001: OPT_UNSIGNED(dev->error_delay, "--device-error-delay", 1, 60);
case 'b': OPT_NUMBER("--buffers", dev->n_buffers, 1, 32, 0);
case 'w': OPT_NUMBER("--workers", dev->n_workers, 1, 32, 0);
case 'q': OPT_NUMBER("--quality", encoder->quality, 1, 100, 0);
case 'c': OPT_PARSE("encoder type", encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN);
# ifdef WITH_OMX
case 'g': OPT_GLITCHED_RESOLUTIONS;
# endif
case 1000: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0);
case 1001: OPT_NUMBER("--device-error-delay", dev->error_delay, 1, 60, 0);
case 2000: OPT_CTL(brightness);
case 2001: OPT_CTL_AUTO(brightness);
@@ -285,24 +410,32 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
case 2012: OPT_CTL_AUTO(gain);
case 's': OPT_SET(server->host, optarg);
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535);
case 'u': OPT_SET(server->unix_path, optarg);
case 'r': OPT_SET(server->unix_rm, true);
case 'o': OPT_CHMOD(server->unix_mode, "--unix-mode");
case 'p': OPT_NUMBER("--port", server->port, 1, 65535, 0);
case 'U': OPT_SET(server->unix_path, optarg);
case 'D': OPT_SET(server->unix_rm, true);
case 'M': OPT_NUMBER("--unix-mode", server->unix_mode, INT_MIN, INT_MAX, 8);
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 'e': OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0);
case 'l': OPT_SET(server->slowdown, true);
case 3003: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
case 3004: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
case 3005: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
case 'R': OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
case 3003: OPT_RESOLUTION_OBSOLETE("--fake-width", "--fake-resolution", server->fake_width, 0, UINT_MAX);
case 3004: OPT_RESOLUTION_OBSOLETE("--fake-height", "--fake-resolution", server->fake_height, 0, UINT_MAX);
case 3005: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
# ifdef WITH_GPIO
case 4000: OPT_NUMBER("--gpio-prog-running", gpio_pin_prog_running, 0, 256, 0);
case 4001: OPT_NUMBER("--gpio-stream-online", gpio_pin_stream_online, 0, 256, 0);
case 4002: OPT_NUMBER("--gpio-has-http-clients", gpio_pin_has_http_clients, 0, 256, 0);
case 4003: OPT_NUMBER("--gpio-workers-busy-at", gpio_pin_workers_busy_at, 0, 256, 0);
# endif
case 5000: OPT_SET(log_level, LOG_LEVEL_PERF);
case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
case 5002: OPT_SET(log_level, LOG_LEVEL_DEBUG);
case 5010: OPT_UNSIGNED(log_level, "--log-level", 0, 3);
case 5010: OPT_NUMBER("--log-level", log_level, 0, 3, 0);
case 'h': _help(dev, encoder, server); return 1;
case 'v': _version(true); return 1;
case 0: break;
@@ -312,10 +445,13 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
# undef OPT_CTL_AUTO
# undef OPT_CTL
# undef OPT_CHMOD
# undef OPT_INT
# undef OPT_PARSE
# undef OPT_UNSIGNED
# ifdef WITH_OMX
# undef OPT_GLITCHED_RESOLUTIONS
# endif
# undef OPT_RESOLUTION_OBSOLETE
# undef OPT_RESOLUTION
# undef OPT_NUMBER
# undef OPT_SET
return 0;
}
@@ -381,13 +517,8 @@ int main(int argc, char *argv[]) {
LOGGING_INIT;
# ifdef WITH_WORKERS_GPIO_DEBUG
if (wiringPiSetupGpio() < 0) {
LOG_PERROR("Can't initialize wiringPi GPIO");
return 1;
} else {
LOG_INFO("Using wiringPi to debug using GPIO");
}
# ifdef WITH_GPIO
GPIO_INIT;
# endif
dev = device_init();
@@ -396,6 +527,10 @@ int main(int argc, char *argv[]) {
server = http_server_init(stream);
if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) {
# ifdef WITH_GPIO
GPIO_INIT_PINOUT;
# endif
_install_signal_handlers();
pthread_t stream_loop_tid;
@@ -407,6 +542,10 @@ int main(int argc, char *argv[]) {
_ctx = &ctx;
if ((exit_code = http_server_listen(server)) == 0) {
# ifdef WITH_GPIO
GPIO_SET_HIGH(prog_running);
# endif
A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
A_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL);
A_THREAD_JOIN(server_loop_tid);
@@ -419,6 +558,10 @@ int main(int argc, char *argv[]) {
encoder_destroy(encoder);
device_destroy(dev);
# ifdef WITH_GPIO
GPIO_SET_LOW(prog_running);
# endif
LOGGING_DESTROY;
return (exit_code < 0 ? 1 : 0);
}

View File

@@ -38,12 +38,8 @@
#include "xioctl.h"
#include "device.h"
#include "encoder.h"
#ifdef WITH_WORKERS_GPIO_DEBUG
# include <wiringPi.h>
# ifndef WORKERS_GPIO_DEBUG_START_PIN
# define WORKERS_GPIO_DEBUG_START_PIN 5
# endif
#ifdef WITH_GPIO
# include "gpio.h"
#endif
@@ -99,7 +95,7 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index,
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream);
static void _workers_pool_destroy(struct _workers_pool_t *pool);
static void *__worker_thread(void *v_worker);
static void *_worker_thread(void *v_worker);
static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool);
static void _workers_pool_assign(struct _workers_pool_t *pool, struct _worker_t *ready_worker, unsigned buf_index);
@@ -187,11 +183,16 @@ void stream_loop(struct stream_t *stream) {
}
} else if (selected == 0) {
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
# endif
if (stream->dev->persistent) {
if (!persistent_timeout_reported) {
LOG_ERROR("Mainloop select() timeout, polling ...")
persistent_timeout_reported = true;
}
continue;
} else {
LOG_ERROR("Mainloop select() timeout");
@@ -204,6 +205,10 @@ void stream_loop(struct stream_t *stream) {
if (has_read) {
LOG_DEBUG("Frame is ready");
# ifdef WITH_GPIO
GPIO_SET_HIGH(stream_online);
# endif
int buf_index;
long double now = get_now_monotonic();
long long now_second = floor_ms(now);
@@ -283,6 +288,10 @@ void stream_loop(struct stream_t *stream) {
_workers_pool_destroy(pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
# endif
}
}
@@ -389,7 +398,7 @@ static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream) {
WORKER(dev) = stream->dev;
WORKER(encoder) = stream->encoder;
A_THREAD_CREATE(&WORKER(tid), __worker_thread, (void *)&(pool->workers[number]));
A_THREAD_CREATE(&WORKER(tid), _worker_thread, (void *)&(pool->workers[number]));
pool->free_workers += 1;
@@ -424,25 +433,21 @@ static void _workers_pool_destroy(struct _workers_pool_t *pool) {
free(pool);
}
static void *__worker_thread(void *v_worker) {
static void *_worker_thread(void *v_worker) {
struct _worker_t *worker = (struct _worker_t *)v_worker;
LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number);
# ifdef WITH_WORKERS_GPIO_DEBUG
# define WORKER_GPIO_DEBUG_BUSY digitalWrite(WORKERS_GPIO_DEBUG_START_PIN + worker->number, HIGH)
# define WORKER_GPIO_DEBUG_FREE digitalWrite(WORKERS_GPIO_DEBUG_START_PIN + worker->number, LOW)
pinMode(WORKERS_GPIO_DEBUG_START_PIN + worker->number, OUTPUT);
WORKER_GPIO_DEBUG_FREE;
# else
# define WORKER_GPIO_DEBUG_BUSY
# define WORKER_GPIO_DEBUG_FREE
# ifdef WITH_GPIO
GPIO_INIT_PIN(workers_busy_at, worker->number);
# endif
while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) {
LOG_DEBUG("Worker %u waiting for a new job ...", worker->number);
WORKER_GPIO_DEBUG_FREE;
# ifdef WITH_GPIO
GPIO_SET_LOW_AT(workers_busy_at, worker->number);
# endif
A_MUTEX_LOCK(&worker->has_job_mutex);
A_COND_WAIT_TRUE(atomic_load(&worker->has_job), &worker->has_job_cond, &worker->has_job_mutex);
@@ -453,7 +458,9 @@ static void *__worker_thread(void *v_worker) {
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index);
WORKER_GPIO_DEBUG_BUSY;
# ifdef WITH_GPIO
GPIO_SET_HIGH_AT(workers_busy_at, worker->number);
# endif
if (encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index) < 0) {
worker->job_failed = false;
@@ -482,7 +489,9 @@ static void *__worker_thread(void *v_worker) {
}
LOG_DEBUG("Bye-bye (worker %u)", worker->number);
WORKER_GPIO_DEBUG_FREE;
# ifdef WITH_GPIO
GPIO_SET_LOW_AT(workers_busy_at, worker->number);
# endif
return NULL;
}

View File

@@ -35,18 +35,19 @@ def main() -> None:
name = sys.argv[3]
with open(html_path, "r") as html_file:
text = html_file.read()
html = html_file.read()
text = text.strip()
text = text.replace("\"", "\\\"")
text = text.replace("%VERSION%", "\" VERSION \"")
text = textwrap.indent(text, "\t", (lambda line: True))
text = "\n".join(
html = html.strip()
html = html.replace("\"", "\\\"")
html = html.replace("%VERSION%", "\" VERSION \"")
html = textwrap.indent(html, "\t", (lambda line: True))
html = "\n".join(
(f"{line} \\" if line.strip() else f"{line}\\")
for line in text.split("\n")
for line in html.split("\n")
)
text = f"const char HTML_{name}_PAGE[] = \" \\\n{text}\n\";\n"
text = f"{common.C_PREPEND}\n#include \"../../config.h\"\n\n\n{text}"
text = f"{common.C_PREPEND}\n#include \"../../config.h\"\n\n\n"
text += f"const char HTML_{name}_PAGE[] = \" \\\n{html}\n\";\n"
with open(header_path, "w") as header_file:
header_file.write(text)

View File

@@ -70,17 +70,18 @@ def main() -> None:
(width, height) = _get_jpeg_size(jpeg_data)
rows: List[List[str]] = [[]]
for ch in jpeg_data:
if len(rows[-1]) > 20:
rows.append([])
rows[-1].append(f"0x{ch:02X}")
jpeg_data_text = "{\n\t" + ",\n\t".join(
", ".join(
f"0x{ch:02X}"
for ch in jpeg_data[index:index + 20]
)
for index in range(0, len(jpeg_data), 20)
) + ",\n}"
text = ",\n\t".join(", ".join(row) for row in rows)
text = f"const unsigned char {name}_JPEG_DATA[] = {{\n\t{text}\n}};\n"
text = f"const unsigned {name}_JPEG_HEIGHT = {height};\n\n{text}"
text = f"const unsigned {name}_JPEG_WIDTH = {width};\n{text}"
text = f"{common.C_PREPEND}\n\n{text}"
text = f"{common.C_PREPEND}\n\n"
text += f"const unsigned {name}_JPEG_WIDTH = {width};\n"
text += f"const unsigned {name}_JPEG_HEIGHT = {height};\n\n"
text += f"const unsigned char {name}_JPEG_DATA[] = {jpeg_data_text};\n"
with open(header_path, "w") as header_file:
header_file.write(text)