Compare commits

...

14 Commits
v0.79 ... v1.0

Author SHA1 Message Date
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
24 changed files with 1174 additions and 856 deletions

View File

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

View File

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

View File

@@ -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 | | 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 | | 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 | | 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 | | 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: Footnotes:
@@ -31,7 +32,7 @@ If you're going to live-stream from your backyard webcam and need to control it,
# Building # Building
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libuuid```. 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/pi-kvm/ustreamer
@@ -40,7 +41,7 @@ $ make
$ ./ustreamer --help $ ./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. FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
----- -----

View File

@@ -16,6 +16,7 @@
| Стрим через UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | | Стрим через 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>логгирование статистики производительности,<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=+) Нет каталогов | | Возможность сервить файлы встроенным<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=+) Есть | | Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
Сносочки: Сносочки:
@@ -31,7 +32,7 @@
# Сборка # Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```. Для сборки вам понадобятся ```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/pi-kvm/ustreamer
@@ -40,7 +41,7 @@ $ make
$ ./ustreamer --help $ ./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. Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
----- -----

View File

@@ -3,14 +3,15 @@
pkgname=ustreamer pkgname=ustreamer
pkgver=0.79 pkgver=1.0
pkgrel=1 pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer" pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pi-kvm/ustreamer" url="https://github.com/pi-kvm/ustreamer"
license=(GPL) license=(GPL)
arch=(i686 x86_64 armv6h armv7h) arch=(i686 x86_64 armv6h armv7h)
depends=(libjpeg libevent libutil-linux) 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) makedepends=(gcc make)
source=(${pkgname}::"git+https://github.com/pi-kvm/ustreamer#commit=v${pkgver}") source=(${pkgname}::"git+https://github.com/pi-kvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP) md5sums=(SKIP)
@@ -23,7 +24,8 @@ build() {
cd $pkgname-build cd $pkgname-build
local _options="" 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 make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
} }

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer PKG_NAME:=ustreamer
PKG_VERSION:=0.79 PKG_VERSION:=1.0
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com> PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

View File

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

View File

@@ -22,8 +22,7 @@ start_instance() {
options="$options --device-timeout='`getcfg device_timeout 5`'" options="$options --device-timeout='`getcfg device_timeout 5`'"
options="$options --input='`getcfg input 0`'" options="$options --input='`getcfg input 0`'"
options="$options --width='`getcfg width 640`'" options="$options --resolution='`getcfg resolution 640x480`'"
options="$options --height='`getcfg height 480`'"
options="$options --format='`getcfg format YUYV`'" options="$options --format='`getcfg format YUYV`'"
options="$options --quality='`getcfg quality 80`'" options="$options --quality='`getcfg quality 80`'"
options="$options --desired-fps='`getcfg desired_fps 0`'" options="$options --desired-fps='`getcfg desired_fps 0`'"

View File

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

View File

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

View File

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

View File

@@ -35,7 +35,7 @@
#include "encoders/cpu/encoder.h" #include "encoders/cpu/encoder.h"
#include "encoders/hw/encoder.h" #include "encoders/hw/encoder.h"
#ifdef WITH_OMX_ENCODER #ifdef WITH_OMX
# include "encoders/omx/encoder.h" # include "encoders/omx/encoder.h"
#endif #endif
@@ -46,7 +46,7 @@ static const struct {
} _ENCODER_TYPES[] = { } _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU}, {"CPU", ENCODER_TYPE_CPU},
{"HW", ENCODER_TYPE_HW}, {"HW", ENCODER_TYPE_HW},
# ifdef WITH_OMX_ENCODER # ifdef WITH_OMX
{"OMX", ENCODER_TYPE_OMX}, {"OMX", ENCODER_TYPE_OMX},
# endif # endif
}; };
@@ -69,7 +69,7 @@ struct encoder_t *encoder_init(void) {
} }
void encoder_destroy(struct encoder_t *encoder) { void encoder_destroy(struct encoder_t *encoder) {
# ifdef WITH_OMX_ENCODER # ifdef WITH_OMX
if (encoder->run->omxs) { if (encoder->run->omxs) {
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) { for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
if (encoder->run->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; bool cpu_forced = false;
if ((dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG) && 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"); LOG_INFO("Switching to HW encoder because the input format is (M)JPEG");
type = ENCODER_TYPE_HW; type = ENCODER_TYPE_HW;
} }
if (type == ENCODER_TYPE_HW) { if (type == ENCODER_TYPE_HW) {
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) { 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; goto use_cpu;
} }
@@ -124,12 +124,23 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
dev->run->n_workers = 1; dev->run->n_workers = 1;
} }
# ifdef WITH_OMX_ENCODER # ifdef WITH_OMX
else if (type == ENCODER_TYPE_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) { 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; 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) { 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) { 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; goto force_cpu;
} }
} }
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) { for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
if (omx_encoder_prepare(encoder->run->omxs[index], dev, 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"); LOG_ERROR("Can't prepare OMX encoder, falling back to CPU");
goto force_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) { } else if (encoder->run->type == ENCODER_TYPE_HW) {
hw_encoder_compress_buffer(dev, buf_index); hw_encoder_compress_buffer(dev, buf_index);
} }
# ifdef WITH_OMX_ENCODER # ifdef WITH_OMX
else if (encoder->run->type == ENCODER_TYPE_OMX) { else if (encoder->run->type == ENCODER_TYPE_OMX) {
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) { if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
goto error; goto error;

View File

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

View File

@@ -51,12 +51,12 @@ int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
MEMSET_ZERO(comp); MEMSET_ZERO(comp);
if (xioctl(dev->run->fd, VIDIOC_G_JPEGCOMP, &comp) < 0) { 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; return -1;
} }
comp.quality = quality; comp.quality = quality;
if (xioctl(dev->run->fd, VIDIOC_S_JPEGCOMP, &comp) < 0) { 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 -1;
} }
return 0; 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) { 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) { 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 # 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; _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) { if (vcos_semaphore_create(&omx->handler_lock, "handler_lock", 0) != VCOS_SUCCESS) {
LOG_ERROR("Can't create VCOS semaphore"); 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) { void omx_encoder_destroy(struct omx_encoder_t *omx) {
OMX_ERRORTYPE error; OMX_ERRORTYPE error;
LOG_INFO("Destroying OMX JPEG encoder ..."); LOG_INFO("Destroying OMX encoder ...");
component_set_state(&omx->encoder, OMX_StateIdle); component_set_state(&omx->encoder, OMX_StateIdle);
_omx_encoder_clear_ports(omx); _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: RGB24 не работает нормально, нижняя половина экрана зеленая.
// FIXME: Китайский EasyCap тоже не работает, мусор на экране. // FIXME: Китайский EasyCap тоже не работает, мусор на экране.
// Вероятно обе проблемы вызваны некорректной реализацией OMX на пае. // Вероятно обе проблемы вызваны некорректной реализацией OMX на пае.
default: assert(0 && "Unsupported input format for OMX JPEG encoder"); default: assert(0 && "Unsupported input format for OMX encoder");
} }
# undef MAP_FORMAT # 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

@@ -53,6 +53,9 @@
#include "../logging.h" #include "../logging.h"
#include "../encoder.h" #include "../encoder.h"
#include "../stream.h" #include "../stream.h"
#ifdef WITH_GPIO
# include "../gpio.h"
#endif
#include "blank.h" #include "blank.h"
#include "base64.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; server->run->stream_clients_count += 1;
if (server->slowdown && server->run->stream_clients_count == 1) { if (server->run->stream_clients_count == 1) {
stream_switch_slowdown(server->run->stream, false); 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); 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 # define RUN(_next) client->server->run->_next
RUN(stream_clients_count) -= 1; 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); conn = evhttp_request_get_connection(client->request);

View File

@@ -44,8 +44,12 @@ pthread_mutex_t log_mutex;
#define LOG_LEVEL_DEBUG 3 #define LOG_LEVEL_DEBUG 3
#define LOGGING_INIT assert(!pthread_mutex_init(&log_mutex, NULL)) #define LOGGING_INIT { \
#define LOGGING_DESTROY assert(!pthread_mutex_destroy(&log_mutex)) 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_LOCK assert(!pthread_mutex_lock(&log_mutex))
#define LOGGING_UNLOCK assert(!pthread_mutex_unlock(&log_mutex)) #define LOGGING_UNLOCK assert(!pthread_mutex_unlock(&log_mutex))

View File

@@ -27,15 +27,13 @@
#include <stdio.h> #include <stdio.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h>
#include <limits.h>
#include <signal.h> #include <signal.h>
#include <getopt.h> #include <getopt.h>
#include <pthread.h> #include <pthread.h>
#ifdef WITH_WORKERS_GPIO_DEBUG
# include <wiringPi.h>
#endif
#include "config.h" #include "config.h"
#include "tools.h" #include "tools.h"
#include "logging.h" #include "logging.h"
@@ -43,12 +41,20 @@
#include "encoder.h" #include "encoder.h"
#include "stream.h" #include "stream.h"
#include "http/server.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[] = { static const struct option _LONG_OPTS[] = {
{"device", required_argument, NULL, 'd'}, {"device", required_argument, NULL, 'd'},
{"input", required_argument, NULL, 'i'}, {"input", required_argument, NULL, 'i'},
{"resolution", required_argument, NULL, 'r'},
{"width", required_argument, NULL, 'x'}, {"width", required_argument, NULL, 'x'},
{"height", required_argument, NULL, 'y'}, {"height", required_argument, NULL, 'y'},
{"format", required_argument, NULL, 'm'}, {"format", required_argument, NULL, 'm'},
@@ -61,6 +67,9 @@ static const struct option _LONG_OPTS[] = {
{"workers", required_argument, NULL, 'w'}, {"workers", required_argument, NULL, 'w'},
{"quality", required_argument, NULL, 'q'}, {"quality", required_argument, NULL, 'q'},
{"encoder", required_argument, NULL, 'c'}, {"encoder", required_argument, NULL, 'c'},
# ifdef WITH_OMX
{"glitched-resolutions", required_argument, NULL, 'g'},
# endif
{"device-timeout", required_argument, NULL, 1000}, {"device-timeout", required_argument, NULL, 1000},
{"device-error-delay", required_argument, NULL, 1001}, {"device-error-delay", required_argument, NULL, 1001},
@@ -80,19 +89,27 @@ static const struct option _LONG_OPTS[] = {
{"host", required_argument, NULL, 's'}, {"host", required_argument, NULL, 's'},
{"port", required_argument, NULL, 'p'}, {"port", required_argument, NULL, 'p'},
{"unix", required_argument, NULL, 'u'}, {"unix", required_argument, NULL, 'U'},
{"unix-rm", no_argument, NULL, 'r'}, {"unix-rm", no_argument, NULL, 'D'},
{"unix-mode", required_argument, NULL, 'o'}, {"unix-mode", required_argument, NULL, 'M'},
{"user", required_argument, NULL, 3000}, {"user", required_argument, NULL, 3000},
{"passwd", required_argument, NULL, 3001}, {"passwd", required_argument, NULL, 3001},
{"static", required_argument, NULL, 3002}, {"static", required_argument, NULL, 3002},
{"blank", required_argument, NULL, 'k'}, {"blank", required_argument, NULL, 'k'},
{"drop-same-frames", required_argument, NULL, 'e'}, {"drop-same-frames", required_argument, NULL, 'e'},
{"slowdown", no_argument, NULL, 'l'}, {"slowdown", no_argument, NULL, 'l'},
{"fake-resolution", required_argument, NULL, 'R'},
{"fake-width", required_argument, NULL, 3003}, {"fake-width", required_argument, NULL, 3003},
{"fake-height", required_argument, NULL, 3004}, {"fake-height", required_argument, NULL, 3004},
{"server-timeout", required_argument, NULL, 3005}, {"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}, {"perf", no_argument, NULL, 5000},
{"verbose", no_argument, NULL, 5001}, {"verbose", no_argument, NULL, 5001},
{"debug", no_argument, NULL, 5002}, {"debug", no_argument, NULL, 5002},
@@ -104,8 +121,11 @@ static const struct option _LONG_OPTS[] = {
static void _version(bool nl) { static void _version(bool nl) {
printf(VERSION); printf(VERSION);
# ifdef WITH_OMX_ENCODER # ifdef WITH_OMX
printf(" + OMX"); printf(" + OMX");
# endif
# ifdef WITH_GPIO
printf(" + GPIO");
# endif # endif
if (nl) { if (nl) {
putchar('\n'); 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("Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com>\n\n");
printf("Capturing options:\n"); printf("Capturing options:\n");
printf("══════════════════\n"); printf("══════════════════\n");
printf(" -d|--device </dev/path> ──────── Path to V4L2 device. Default: %s.\n\n", dev->path); 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(" -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(" -r|--resolution <WxH> ─────────────── Initial image resolution. Default: %ux%u.\n\n", dev->width, dev->height);
printf(" -y|--height <N> ──────────────── Initial image height. Default: %u.\n\n", dev->height); printf(" -m|--format <fmt> ─────────────────── Image format.\n");
printf(" -m|--format <fmt> ────────────── Image format.\n"); printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR); printf(" -a|--tv-standard <std> ────────────── Force TV standard.\n");
printf(" -a|--tv-standard <std> ───────── Force TV standard.\n"); printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR); printf(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n\n");
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(" -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(" produces small-sized garbage frames. Default: disabled.\n\n"); printf(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. 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(" -t|--dv-timings ──────────────── Enable DV timings queriyng and events processing\n"); printf(" to automatic resolution change. Default: disabled.\n\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(" -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(" 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(" 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(" -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(" 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(" -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");
printf(" -c|--encoder <type> ──────────── Use specified encoder. It may affects to workers number.\n"); # ifdef WITH_OMX
printf(" Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR); printf(" -g|--glitched-resolutions <WxH,...> ─ Comma-separated list of resolutions that require forced\n");
printf(" --device-timeout <seconds> ───── Timeout for device querying. Default: %u.\n\n", dev->timeout); # endif
printf(" --device-error-delay <seconds> ─ Delay before trying to connect to the device again\n"); printf(" encoding on CPU instead of OMX. Default: disabled.\n");
printf(" after an error (timeout for example). Default: %u.\n\n", dev->error_delay); 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("Image control options:\n");
printf("══════════════════════\n"); printf("══════════════════════\n");
printf(" --brightness <N> ───────────── Set brightness. Default: no change.\n\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("════════════════════\n");
printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s.\n\n", server->host); 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(" -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(" -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(" -D|--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(" -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(" --user <name> ────────────── HTTP basic auth user. Default: disabled.\n\n");
printf(" --passwd <str> ───────────── HTTP basic auth passwd. Default: empty.\n\n"); printf(" --passwd <str> ───────────── HTTP basic auth passwd. Default: empty.\n\n");
printf(" --static <path> ───────────── Path to dir with static files instead of embedded root index page.\n"); printf(" --static <path> ───────────── Path to dir with static files instead of embedded root index page.\n");
printf(" Symlinks are not supported for security reasons. Default: disabled.\n\n"); printf(" Symlinks are not supported for security reasons. Default: disabled.\n\n");
printf(" -k|--blank <path> ─────────── Path to JPEG file that will be shown when the device is disconnected\n"); printf(" -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(" 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(" It can significantly reduce the outgoing traffic, but will increase\n");
printf(" the CPU loading. Don't use this option with analog signal sources\n"); printf(" the CPU loading. Don't use this option with analog signal sources\n");
printf(" or webcams, it's useless. Default: disabled.\n\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(" Useful to reduce CPU consumption. Default: disabled.\n\n");
printf(" --fake-width <N> ─────────── Override image width for /state. Default: disabled.\n\n"); printf(" -R|--fake-resolution <WxH> ─ Override image resolution 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); 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("Misc options:\n");
printf("═════════════\n"); printf("═════════════\n");
printf(" --log-level <N> ─ Verbosity level of messages from 0 (info) to 3 (debug).\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(" 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(" Default: %u.\n\n", log_level);
printf(" --perf ────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n"); 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"); 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"); 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) { static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited) {
# define OPT_SET(_dest, _value) \ unsigned tmp_width;
{ _dest = _value; break; } unsigned tmp_height;
# define OPT_UNSIGNED(_dest, _name, _min, _max) { \ if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) {
errno = 0; char *_end = NULL; int _tmp = strtol(optarg, &_end, 0); \ 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) { \ if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%u, max=%u\n", _name, optarg, _min, _max); \ printf("Invalid value for '%s=%s': min=%u, max=%u\n", _name, optarg, _min, _max); \
return -1; \ return -1; \
@@ -210,7 +313,38 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
break; \ 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) { \ if ((_dest = _func(optarg)) == _invalid) { \
printf("Unknown " _name ": %s\n", optarg); \ printf("Unknown " _name ": %s\n", optarg); \
return -1; \ return -1; \
@@ -218,29 +352,16 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
break; \ 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) { \ # define OPT_CTL(_dest) { \
dev->ctl->_dest.value_set = true; \ dev->ctl._dest.value_set = true; \
dev->ctl->_dest.auto_set = false; \ dev->ctl._dest.auto_set = false; \
OPT_INT(dev->ctl->_dest.value, "--"#_dest, 10); \ OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
break; \ break; \
} }
# define OPT_CTL_AUTO(_dest) { \ # define OPT_CTL_AUTO(_dest) { \
dev->ctl->_dest.value_set = false; \ dev->ctl._dest.value_set = false; \
dev->ctl->_dest.auto_set = true; \ dev->ctl._dest.auto_set = true; \
break; \ 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) { while ((ch = getopt_long(argc, argv, _SHORT_OPTS, _LONG_OPTS, &index)) >= 0) {
switch (ch) { switch (ch) {
case 'd': OPT_SET(dev->path, optarg); case 'd': OPT_SET(dev->path, optarg);
case 'i': OPT_UNSIGNED(dev->input, "--input", 0, 128); case 'i': OPT_NUMBER("--input", dev->input, 0, 128, 0);
case 'x': OPT_UNSIGNED(dev->width, "--width", VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH); case 'r': OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
case 'y': OPT_UNSIGNED(dev->height, "--height", VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT); 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 ignored "-Wsign-compare"
# pragma GCC diagnostic push # 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 # pragma GCC diagnostic pop
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard"); case 'a': OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN);
case 'f': OPT_UNSIGNED(dev->desired_fps, "--desired-fps", 0, 30); case 'f': OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192); case 'z': OPT_NUMBER("--min-frame-size", dev->min_frame_size, 0, 8192, 0);
case 'n': OPT_SET(dev->persistent, true); case 'n': OPT_SET(dev->persistent, true);
case 't': OPT_SET(dev->dv_timings, true); case 't': OPT_SET(dev->dv_timings, true);
case 'b': OPT_UNSIGNED(dev->n_buffers, "--buffers", 1, 32); case 'b': OPT_NUMBER("--buffers", dev->n_buffers, 1, 32, 0);
case 'w': OPT_UNSIGNED(dev->n_workers, "--workers", 1, 32); case 'w': OPT_NUMBER("--workers", dev->n_workers, 1, 32, 0);
case 'q': OPT_UNSIGNED(encoder->quality, "--quality", 1, 100); case 'q': OPT_NUMBER("--quality", encoder->quality, 1, 100, 0);
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type"); case 'c': OPT_PARSE("encoder type", encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN);
case 1000: OPT_UNSIGNED(dev->timeout, "--device-timeout", 1, 60); # ifdef WITH_OMX
case 1001: OPT_UNSIGNED(dev->error_delay, "--device-error-delay", 1, 60); 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 2000: OPT_CTL(brightness);
case 2001: OPT_CTL_AUTO(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 2012: OPT_CTL_AUTO(gain);
case 's': OPT_SET(server->host, optarg); case 's': OPT_SET(server->host, optarg);
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535); case 'p': OPT_NUMBER("--port", server->port, 1, 65535, 0);
case 'u': OPT_SET(server->unix_path, optarg); case 'U': OPT_SET(server->unix_path, optarg);
case 'r': OPT_SET(server->unix_rm, true); case 'D': OPT_SET(server->unix_rm, true);
case 'o': OPT_CHMOD(server->unix_mode, "--unix-mode"); case 'M': OPT_NUMBER("--unix-mode", server->unix_mode, INT_MIN, INT_MAX, 8);
case 3000: OPT_SET(server->user, optarg); case 3000: OPT_SET(server->user, optarg);
case 3001: OPT_SET(server->passwd, optarg); case 3001: OPT_SET(server->passwd, optarg);
case 3002: OPT_SET(server->static_path, optarg); case 3002: OPT_SET(server->static_path, optarg);
case 'k': OPT_SET(server->blank_path, optarg); case '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 'l': OPT_SET(server->slowdown, true);
case 3003: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920); case 'R': OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
case 3004: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200); case 3003: OPT_RESOLUTION_OBSOLETE("--fake-width", "--fake-resolution", server->fake_width, 0, UINT_MAX);
case 3005: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60); 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 5000: OPT_SET(log_level, LOG_LEVEL_PERF);
case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE); case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
case 5002: OPT_SET(log_level, LOG_LEVEL_DEBUG); 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 'h': _help(dev, encoder, server); return 1;
case 'v': _version(true); return 1; case 'v': _version(true); return 1;
case 0: break; 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_AUTO
# undef OPT_CTL # undef OPT_CTL
# undef OPT_CHMOD
# undef OPT_INT
# undef OPT_PARSE # 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 # undef OPT_SET
return 0; return 0;
} }
@@ -381,13 +517,8 @@ int main(int argc, char *argv[]) {
LOGGING_INIT; LOGGING_INIT;
# ifdef WITH_WORKERS_GPIO_DEBUG # ifdef WITH_GPIO
if (wiringPiSetupGpio() < 0) { GPIO_INIT;
LOG_PERROR("Can't initialize wiringPi GPIO");
return 1;
} else {
LOG_INFO("Using wiringPi to debug using GPIO");
}
# endif # endif
dev = device_init(); dev = device_init();
@@ -396,6 +527,10 @@ int main(int argc, char *argv[]) {
server = http_server_init(stream); server = http_server_init(stream);
if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) { if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) {
# ifdef WITH_GPIO
GPIO_INIT_PINOUT;
# endif
_install_signal_handlers(); _install_signal_handlers();
pthread_t stream_loop_tid; pthread_t stream_loop_tid;
@@ -407,6 +542,10 @@ int main(int argc, char *argv[]) {
_ctx = &ctx; _ctx = &ctx;
if ((exit_code = http_server_listen(server)) == 0) { 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(&stream_loop_tid, _stream_loop_thread, NULL);
A_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL); A_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL);
A_THREAD_JOIN(server_loop_tid); A_THREAD_JOIN(server_loop_tid);
@@ -419,6 +558,10 @@ int main(int argc, char *argv[]) {
encoder_destroy(encoder); encoder_destroy(encoder);
device_destroy(dev); device_destroy(dev);
# ifdef WITH_GPIO
GPIO_SET_LOW(prog_running);
# endif
LOGGING_DESTROY; LOGGING_DESTROY;
return (exit_code < 0 ? 1 : 0); return (exit_code < 0 ? 1 : 0);
} }

View File

@@ -38,12 +38,8 @@
#include "xioctl.h" #include "xioctl.h"
#include "device.h" #include "device.h"
#include "encoder.h" #include "encoder.h"
#ifdef WITH_GPIO
#ifdef WITH_WORKERS_GPIO_DEBUG # include "gpio.h"
# include <wiringPi.h>
# ifndef WORKERS_GPIO_DEBUG_START_PIN
# define WORKERS_GPIO_DEBUG_START_PIN 5
# endif
#endif #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 struct _workers_pool_t *_workers_pool_init(struct stream_t *stream);
static void _workers_pool_destroy(struct _workers_pool_t *pool); 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 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); 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) { } else if (selected == 0) {
# ifdef WITH_GPIO
GPIO_SET_LOW(stream_online);
# endif
if (stream->dev->persistent) { if (stream->dev->persistent) {
if (!persistent_timeout_reported) { if (!persistent_timeout_reported) {
LOG_ERROR("Mainloop select() timeout, polling ...") LOG_ERROR("Mainloop select() timeout, polling ...")
persistent_timeout_reported = true; persistent_timeout_reported = true;
} }
continue; continue;
} else { } else {
LOG_ERROR("Mainloop select() timeout"); LOG_ERROR("Mainloop select() timeout");
@@ -204,6 +205,10 @@ void stream_loop(struct stream_t *stream) {
if (has_read) { if (has_read) {
LOG_DEBUG("Frame is ready"); LOG_DEBUG("Frame is ready");
# ifdef WITH_GPIO
GPIO_SET_HIGH(stream_online);
# endif
int buf_index; int buf_index;
long double now = get_now_monotonic(); long double now = get_now_monotonic();
long long now_second = floor_ms(now); long long now_second = floor_ms(now);
@@ -283,6 +288,10 @@ void stream_loop(struct stream_t *stream) {
_workers_pool_destroy(pool); _workers_pool_destroy(pool);
device_switch_capturing(stream->dev, false); device_switch_capturing(stream->dev, false);
device_close(stream->dev); 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(dev) = stream->dev;
WORKER(encoder) = stream->encoder; 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; pool->free_workers += 1;
@@ -424,25 +433,21 @@ static void _workers_pool_destroy(struct _workers_pool_t *pool) {
free(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; struct _worker_t *worker = (struct _worker_t *)v_worker;
LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number); LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number);
# ifdef WITH_WORKERS_GPIO_DEBUG # ifdef WITH_GPIO
# define WORKER_GPIO_DEBUG_BUSY digitalWrite(WORKERS_GPIO_DEBUG_START_PIN + worker->number, HIGH) GPIO_INIT_PIN(workers_busy_at, worker->number);
# 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
# endif # endif
while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) { while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) {
LOG_DEBUG("Worker %u waiting for a new job ...", worker->number); 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_MUTEX_LOCK(&worker->has_job_mutex);
A_COND_WAIT_TRUE(atomic_load(&worker->has_job), &worker->has_job_cond, &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); 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) { if (encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index) < 0) {
worker->job_failed = false; worker->job_failed = false;
@@ -482,7 +489,9 @@ static void *__worker_thread(void *v_worker) {
} }
LOG_DEBUG("Bye-bye (worker %u)", worker->number); 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; return NULL;
} }

View File

@@ -35,18 +35,19 @@ def main() -> None:
name = sys.argv[3] name = sys.argv[3]
with open(html_path, "r") as html_file: with open(html_path, "r") as html_file:
text = html_file.read() html = html_file.read()
text = text.strip() html = html.strip()
text = text.replace("\"", "\\\"") html = html.replace("\"", "\\\"")
text = text.replace("%VERSION%", "\" VERSION \"") html = html.replace("%VERSION%", "\" VERSION \"")
text = textwrap.indent(text, "\t", (lambda line: True)) html = textwrap.indent(html, "\t", (lambda line: True))
text = "\n".join( html = "\n".join(
(f"{line} \\" if line.strip() else f"{line}\\") (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: with open(header_path, "w") as header_file:
header_file.write(text) header_file.write(text)

View File

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