Compare commits

...

21 Commits
v0.78 ... v0.80

Author SHA1 Message Date
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
Devaev Maxim
c751e4ff08 Bump version: 0.78 → 0.79 2019-07-01 02:47:22 +03:00
Devaev Maxim
99a00ca57c approx_comp_time 2019-07-01 02:20:13 +03:00
Devaev Maxim
c009a7efe4 Revert "fluency synchronization"
This reverts commit 291d7431b0.
2019-06-30 06:37:24 +03:00
Devaev Maxim
291d7431b0 fluency synchronization 2019-06-29 16:30:43 +03:00
Devaev Maxim
a1cd490fdf using desired_frames_interval instead of soft_delay 2019-06-29 07:02:21 +03:00
Devaev Maxim
7a85774085 fixed mutex routine 2019-06-29 06:59:16 +03:00
Devaev Maxim
2ed3c4815b f-strings 2019-06-28 06:27:46 +03:00
Devaev Maxim
724c6e118f () -> (void) 2019-06-28 05:15:24 +03:00
Devaev Maxim
da3a3adc65 fixed includes order 2019-06-28 05:00:49 +03:00
Devaev Maxim
32013a6360 refactoring 2019-06-27 05:10:19 +03:00
Devaev Maxim
16a2495766 messages refactoring 2019-06-13 14:37:03 +03:00
33 changed files with 1234 additions and 925 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion] [bumpversion]
commit = True commit = True
tag = True tag = True
current_version = 0.78 current_version = 0.80
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,7 +3,7 @@
pkgname=ustreamer pkgname=ustreamer
pkgver=0.78 pkgver=0.80
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"
@@ -11,6 +11,7 @@ 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 JPEG 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.78 PKG_VERSION:=0.80
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.78" # define VERSION "0.80"
#endif #endif

View File

@@ -20,6 +20,8 @@
*****************************************************************************/ *****************************************************************************/
#include "device.h"
#include <stdlib.h> #include <stdlib.h>
#include <stddef.h> #include <stddef.h>
#include <stdbool.h> #include <stdbool.h>
@@ -37,7 +39,6 @@
#include "tools.h" #include "tools.h"
#include "logging.h" #include "logging.h"
#include "xioctl.h" #include "xioctl.h"
#include "device.h"
static const struct { static const struct {
@@ -82,8 +83,7 @@ static const char *_format_to_string_supported(unsigned format);
static const char *_standard_to_string(v4l2_std_id standard); static const char *_standard_to_string(v4l2_std_id standard);
struct device_t *device_init() { 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;
@@ -93,8 +93,6 @@ struct device_t *device_init() {
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;
@@ -108,14 +106,12 @@ struct device_t *device_init() {
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);
} }
@@ -279,9 +275,9 @@ int device_grab_buffer(struct device_t *dev) {
return -1; return -1;
} }
LOG_DEBUG("Got a new frame in buffer index=%u; bytesused=%u", buf_info.index, buf_info.bytesused); LOG_DEBUG("Got a new frame in buffer: index=%u, bytesused=%u", buf_info.index, buf_info.bytesused);
if (buf_info.index >= dev->run->n_buffers) { if (buf_info.index >= dev->run->n_buffers) {
LOG_ERROR("Got invalid buffer index=%u; nbuffers=%u", buf_info.index, dev->run->n_buffers); LOG_ERROR("Got invalid buffer: index=%u, nbuffers=%u", buf_info.index, dev->run->n_buffers);
return -1; return -1;
} }
@@ -384,24 +380,22 @@ static int _device_open_dv_timings(struct device_t *dev) {
} }
static int _device_apply_dv_timings(struct device_t *dev) { static int _device_apply_dv_timings(struct device_t *dev) {
struct v4l2_dv_timings dv_timings; struct v4l2_dv_timings dv;
MEMSET_ZERO(dv_timings); MEMSET_ZERO(dv);
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ..."); LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings) == 0) { if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
LOG_INFO("Got new DV timings: resolution=%ux%u; pixclk=%llu", LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu",
dv_timings.bt.width, dv.bt.width, dv.bt.height, dv.bt.pixelclock);
dv_timings.bt.height,
dv_timings.bt.pixelclock);
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ..."); LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv_timings) < 0) { if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv) < 0) {
LOG_PERROR("Failed to set DV timings"); LOG_PERROR("Failed to set DV timings");
return -1; return -1;
} }
if (_device_apply_resolution(dev, dv_timings.bt.width, dv_timings.bt.height) < 0) { if (_device_apply_resolution(dev, dv.bt.width, dv.bt.height) < 0) {
return -1; return -1;
} }
@@ -431,7 +425,7 @@ static int _device_open_format(struct device_t *dev) {
// Set format // Set format
LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ..."); LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ...");
if (xioctl(dev->run->fd, VIDIOC_S_FMT, &fmt) < 0) { if (xioctl(dev->run->fd, VIDIOC_S_FMT, &fmt) < 0) {
LOG_PERROR("Unable to set pixelformat=%s; resolution=%ux%u", LOG_PERROR("Unable to set pixelformat=%s, resolution=%ux%u",
_format_to_string_supported(dev->format), _format_to_string_supported(dev->format),
dev->run->width, dev->run->width,
dev->run->height); dev->run->height);
@@ -602,7 +596,7 @@ static int _device_apply_resolution(struct device_t *dev, unsigned width, unsign
width == 0 || width > VIDEO_MAX_WIDTH width == 0 || width > VIDEO_MAX_WIDTH
|| height == 0 || height > VIDEO_MAX_HEIGHT || height == 0 || height > VIDEO_MAX_HEIGHT
) { ) {
LOG_ERROR("Requested forbidden resolution=%ux%u: min=1x1; max=%ux%u", LOG_ERROR("Requested forbidden resolution=%ux%u: min=1x1, max=%ux%u",
width, height, VIDEO_MAX_WIDTH, VIDEO_MAX_HEIGHT); width, height, VIDEO_MAX_WIDTH, VIDEO_MAX_HEIGHT);
return -1; return -1;
} }
@@ -619,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

@@ -104,13 +104,13 @@ 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;
}; };
struct device_t *device_init(); struct device_t *device_init(void);
void device_destroy(struct device_t *dev); void device_destroy(struct device_t *dev);
int device_parse_format(const char *str); int device_parse_format(const char *str);

View File

@@ -20,6 +20,8 @@
*****************************************************************************/ *****************************************************************************/
#include "encoder.h"
#include <stdbool.h> #include <stdbool.h>
#include <strings.h> #include <strings.h>
#include <assert.h> #include <assert.h>
@@ -29,12 +31,11 @@
#include "tools.h" #include "tools.h"
#include "logging.h" #include "logging.h"
#include "device.h" #include "device.h"
#include "encoder.h"
#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
@@ -45,13 +46,13 @@ 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
}; };
struct encoder_t *encoder_init() { struct encoder_t *encoder_init(void) {
struct encoder_runtime_t *run; struct encoder_runtime_t *run;
struct encoder_t *encoder; struct encoder_t *encoder;
@@ -68,7 +69,7 @@ struct encoder_t *encoder_init() {
} }
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]) {
@@ -123,8 +124,19 @@ 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) {
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 JPEG encoder the resolution %ux%u marked as glitchy for OMX",
dev->run->width, dev->run->height);
goto use_cpu;
}
}
LOG_DEBUG("Preparing OMX JPEG encoder ..."); LOG_DEBUG("Preparing OMX JPEG encoder ...");
if (dev->run->n_workers > OMX_MAX_ENCODERS) { if (dev->run->n_workers > OMX_MAX_ENCODERS) {
@@ -195,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,12 +70,16 @@ 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;
}; };
struct encoder_t *encoder_init(); struct encoder_t *encoder_init(void);
void encoder_destroy(struct encoder_t *encoder); void encoder_destroy(struct encoder_t *encoder);
enum encoder_type_t encoder_parse_type(const char *str); enum encoder_type_t encoder_parse_type(const char *str);

View File

@@ -25,6 +25,8 @@
*****************************************************************************/ *****************************************************************************/
#include "encoder.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -36,8 +38,6 @@
#include "../../tools.h" #include "../../tools.h"
#include "../../device.h" #include "../../device.h"
#include "encoder.h"
struct _jpeg_dest_manager_t { struct _jpeg_dest_manager_t {
struct jpeg_destination_mgr mgr; // Default manager struct jpeg_destination_mgr mgr; // Default manager

View File

@@ -25,6 +25,8 @@
*****************************************************************************/ *****************************************************************************/
#include "encoder.h"
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
@@ -37,7 +39,6 @@
#include "../../device.h" #include "../../device.h"
#include "huffman.h" #include "huffman.h"
#include "encoder.h"
static bool _is_huffman(const unsigned char *data); static bool _is_huffman(const unsigned char *data);

View File

@@ -20,6 +20,8 @@
*****************************************************************************/ *****************************************************************************/
#include "component.h"
#include <unistd.h> #include <unistd.h>
#include <IL/OMX_Core.h> #include <IL/OMX_Core.h>
@@ -28,7 +30,6 @@
#include "../../logging.h" #include "../../logging.h"
#include "formatters.h" #include "formatters.h"
#include "component.h"
static int _component_wait_port_changed(OMX_HANDLETYPE *component, OMX_U32 port, OMX_BOOL enabled); static int _component_wait_port_changed(OMX_HANDLETYPE *component, OMX_U32 port, OMX_BOOL enabled);

View File

@@ -20,6 +20,8 @@
*****************************************************************************/ *****************************************************************************/
#include "encoder.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
@@ -39,7 +41,6 @@
#include "formatters.h" #include "formatters.h"
#include "component.h" #include "component.h"
#include "encoder.h"
static const OMX_U32 _INPUT_PORT = 340; static const OMX_U32 _INPUT_PORT = 340;
@@ -69,7 +70,7 @@ static OMX_ERRORTYPE _omx_output_available_handler(
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer); OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buffer);
struct omx_encoder_t *omx_encoder_init() { struct omx_encoder_t *omx_encoder_init(void) {
// Some theory: // Some theory:
// - http://www.fourcc.org/yuv.php // - http://www.fourcc.org/yuv.php
// - https://kwasi-ich.de/blog/2017/11/26/omx/ // - https://kwasi-ich.de/blog/2017/11/26/omx/

View File

@@ -50,7 +50,7 @@ struct omx_encoder_t {
}; };
struct omx_encoder_t *omx_encoder_init(); 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);
int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality); int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality);

View File

@@ -20,6 +20,8 @@
*****************************************************************************/ *****************************************************************************/
#include "formatters.h"
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
@@ -27,7 +29,6 @@
#include <IL/OMX_Core.h> #include <IL/OMX_Core.h>
#include "../../tools.h" #include "../../tools.h"
#include "formatters.h"
#define CASE_TO_STRING(_value) \ #define CASE_TO_STRING(_value) \

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)

View File

@@ -20,12 +20,13 @@
*****************************************************************************/ *****************************************************************************/
#include "base64.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include "../tools.h" #include "../tools.h"
#include "base64.h"
static const char ENCODING_TABLE[] = { static const char ENCODING_TABLE[] = {

View File

@@ -20,6 +20,8 @@
*****************************************************************************/ *****************************************************************************/
#include "blank.h"
#include <stdio.h> #include <stdio.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
@@ -32,8 +34,6 @@
#include "data/blank_jpeg.h" #include "data/blank_jpeg.h"
#include "blank.h"
struct _jpeg_error_manager_t { struct _jpeg_error_manager_t {
struct jpeg_error_mgr mgr; // Default manager struct jpeg_error_mgr mgr; // Default manager
@@ -41,7 +41,7 @@ struct _jpeg_error_manager_t {
}; };
static struct blank_t *_blank_init_internal(); static struct blank_t *_blank_init_internal(void);
static struct blank_t *_blank_init_external(const char *path); static struct blank_t *_blank_init_external(const char *path);
static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height); static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height);
static void _jpeg_error_handler(j_common_ptr jpeg); static void _jpeg_error_handler(j_common_ptr jpeg);
@@ -68,7 +68,7 @@ void blank_destroy(struct blank_t *blank) {
free(blank); free(blank);
} }
static struct blank_t *_blank_init_internal() { static struct blank_t *_blank_init_internal(void) {
struct blank_t *blank; struct blank_t *blank;
A_CALLOC(blank, 1); A_CALLOC(blank, 1);

File diff suppressed because it is too large Load Diff

View File

@@ -20,14 +20,14 @@
*****************************************************************************/ *****************************************************************************/
#include "mime.h"
#include <string.h> #include <string.h>
#include <event2/util.h> #include <event2/util.h>
#include "../tools.h" #include "../tools.h"
#include "mime.h"
static const struct { static const struct {
const char *ext; const char *ext;

View File

@@ -20,6 +20,8 @@
*****************************************************************************/ *****************************************************************************/
#include "path.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -27,8 +29,6 @@
#include "../tools.h" #include "../tools.h"
#include "path.h"
char *simplify_request_path(const char *str) { char *simplify_request_path(const char *str) {
// Based on Lighttpd sources: // Based on Lighttpd sources:

View File

@@ -20,6 +20,8 @@
*****************************************************************************/ *****************************************************************************/
#include "server.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdatomic.h> #include <stdatomic.h>
@@ -51,12 +53,14 @@
#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"
#include "mime.h" #include "mime.h"
#include "static.h" #include "static.h"
#include "server.h"
#include "data/index_html.h" #include "data/index_html.h"
@@ -208,7 +212,7 @@ int http_server_listen(struct http_server_t *server) {
# define MAX_SUN_PATH (sizeof(unix_addr.sun_path) - 1) # define MAX_SUN_PATH (sizeof(unix_addr.sun_path) - 1)
if (strlen(server->unix_path) > MAX_SUN_PATH) { if (strlen(server->unix_path) > MAX_SUN_PATH) {
LOG_ERROR("UNIX socket path is too long, max=%zu", MAX_SUN_PATH); LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
return -1; return -1;
} }
@@ -563,12 +567,18 @@ 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);
LOG_INFO("HTTP: Registered the new stream client: [%s]:%u; id=%s; clients now: %u", LOG_INFO("HTTP: Registered the new stream client: [%s]:%u, id=%s; clients now: %u",
client_addr, client_port, client->id, server->run->stream_clients_count); client_addr, client_port, client->id, server->run->stream_clients_count);
buf_event = evhttp_connection_get_bufferevent(conn); buf_event = evhttp_connection_get_bufferevent(conn);
@@ -716,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);
@@ -845,13 +862,13 @@ static bool _expose_new_picture_unsafe(struct http_server_t *server) {
) { ) {
EXPOSED(expose_cmp_time) = get_now_monotonic(); EXPOSED(expose_cmp_time) = get_now_monotonic();
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time); EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
LOG_VERBOSE("HTTP: dropped same frame number %u; comparsion time = %.06Lf", LOG_VERBOSE("HTTP: dropped same frame number %u; cmp_time=%.06Lf",
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)); EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
EXPOSED(dropped) += 1; EXPOSED(dropped) += 1;
return false; // Not updated return false; // Not updated
} else { } else {
EXPOSED(expose_cmp_time) = get_now_monotonic(); EXPOSED(expose_cmp_time) = get_now_monotonic();
LOG_VERBOSE("HTTP: passed same frame check (frames are differ); comparsion time = %.06Lf", LOG_VERBOSE("HTTP: passed same frame check (frames are differ); cmp_time=%.06Lf",
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)); EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
} }
} }

View File

@@ -20,6 +20,8 @@
*****************************************************************************/ *****************************************************************************/
#include "static.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -32,7 +34,6 @@
#include "../logging.h" #include "../logging.h"
#include "path.h" #include "path.h"
#include "static.h"
char *find_static_file_path(const char *root_path, const char *request_path) { char *find_static_file_path(const char *root_path, const char *request_path) {

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,21 +227,124 @@ 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; \
} \ } \
_dest = _tmp; \ _dest = _tmp; \
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, 30, 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, 30, 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;
} }
@@ -327,7 +463,7 @@ struct main_context_t {
static struct main_context_t *_ctx; static struct main_context_t *_ctx;
static void _block_thread_signals() { static void _block_thread_signals(void) {
sigset_t mask; sigset_t mask;
assert(!sigemptyset(&mask)); assert(!sigemptyset(&mask));
assert(!sigaddset(&mask, SIGINT)); assert(!sigaddset(&mask, SIGINT));
@@ -353,7 +489,7 @@ static void _signal_handler(int signum) {
http_server_loop_break(_ctx->server); http_server_loop_break(_ctx->server);
} }
static void _install_signal_handlers() { static void _install_signal_handlers(void) {
struct sigaction sig_act; struct sigaction sig_act;
MEMSET_ZERO(sig_act); MEMSET_ZERO(sig_act);
@@ -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

@@ -20,6 +20,8 @@
*****************************************************************************/ *****************************************************************************/
#include "stream.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <unistd.h> #include <unistd.h>
@@ -36,13 +38,8 @@
#include "xioctl.h" #include "xioctl.h"
#include "device.h" #include "device.h"
#include "encoder.h" #include "encoder.h"
#include "stream.h" #ifdef WITH_GPIO
# include "gpio.h"
#ifdef WITH_WORKERS_GPIO_DEBUG
# include <wiringPi.h>
# ifndef WORKERS_GPIO_DEBUG_START_PIN
# define WORKERS_GPIO_DEBUG_START_PIN 5
# endif
#endif #endif
@@ -52,7 +49,6 @@ struct _worker_t {
atomic_bool *proc_stop; atomic_bool *proc_stop;
atomic_bool *workers_stop; atomic_bool *workers_stop;
pthread_mutex_t last_comp_time_mutex;
long double last_comp_time; long double last_comp_time;
pthread_mutex_t has_job_mutex; pthread_mutex_t has_job_mutex;
@@ -80,6 +76,8 @@ struct _workers_pool_t {
struct _worker_t *oldest_worker; struct _worker_t *oldest_worker;
struct _worker_t *latest_worker; struct _worker_t *latest_worker;
long double approx_comp_time;
pthread_mutex_t free_workers_mutex; pthread_mutex_t free_workers_mutex;
unsigned free_workers; unsigned free_workers;
pthread_cond_t free_workers_cond; pthread_cond_t free_workers_cond;
@@ -97,11 +95,11 @@ 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);
static long double _workers_pool_get_fluency_delay(struct _workers_pool_t *pool); static long double _workers_pool_get_fluency_delay(struct _workers_pool_t *pool, struct _worker_t *ready_worker);
struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) { struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) {
@@ -157,9 +155,9 @@ void stream_loop(struct stream_t *stream) {
if (!ready_worker->job_failed) { if (!ready_worker->job_failed) {
if (ready_worker->job_timely) { if (ready_worker->job_timely) {
_stream_expose_picture(stream, ready_worker->buf_index, captured_fps); _stream_expose_picture(stream, ready_worker->buf_index, captured_fps);
LOG_PERF("##### Encoded picture exposed; worker = %u", ready_worker->number); LOG_PERF("##### Encoded picture exposed; worker=%u", ready_worker->number);
} else { } else {
LOG_PERF("----- Encoded picture dropped; worker = %u", ready_worker->number); LOG_PERF("----- Encoded picture dropped; worker=%u", ready_worker->number);
} }
} else { } else {
break; break;
@@ -185,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");
@@ -202,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);
@@ -224,7 +231,7 @@ void stream_loop(struct stream_t *stream) {
{ {
if (now < grab_after) { if (now < grab_after) {
fluency_passed += 1; fluency_passed += 1;
LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf; grab_after=%.03Lf", fluency_passed, now, grab_after); LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf, grab_after=%.03Lf", fluency_passed, now, grab_after);
goto pass_frame; goto pass_frame;
} }
fluency_passed = 0; fluency_passed = 0;
@@ -233,14 +240,14 @@ void stream_loop(struct stream_t *stream) {
captured_fps = captured_fps_accum; captured_fps = captured_fps_accum;
captured_fps_accum = 0; captured_fps_accum = 0;
captured_fps_second = now_second; captured_fps_second = now_second;
LOG_PERF("A new second has come, Captured-FPS = %u", captured_fps); LOG_PERF("A new second has come; captured_fps=%u", captured_fps);
} }
captured_fps_accum += 1; captured_fps_accum += 1;
long double fluency_delay = _workers_pool_get_fluency_delay(pool); long double fluency_delay = _workers_pool_get_fluency_delay(pool, ready_worker);
grab_after = now + fluency_delay; grab_after = now + fluency_delay;
LOG_VERBOSE("Fluency: delay=%.03Lf; grab_after=%.03Lf", fluency_delay, grab_after); LOG_VERBOSE("Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after);
} }
_workers_pool_assign(pool, ready_worker, buf_index); _workers_pool_assign(pool, ready_worker, buf_index);
@@ -281,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
} }
} }
@@ -295,7 +306,7 @@ void stream_switch_slowdown(struct stream_t *stream, bool slowdown) {
static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream) { static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream) {
struct _workers_pool_t *pool = NULL; struct _workers_pool_t *pool = NULL;
LOG_DEBUG("%s: stream->proc->stop = %d", __FUNCTION__, atomic_load(&stream->proc->stop)); LOG_DEBUG("%s: stream->proc->stop=%d", __FUNCTION__, atomic_load(&stream->proc->stop));
while (!atomic_load(&stream->proc->stop)) { while (!atomic_load(&stream->proc->stop)) {
SEP_INFO('='); SEP_INFO('=');
@@ -387,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;
@@ -422,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);
@@ -451,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;
@@ -461,14 +470,10 @@ static void *__worker_thread(void *v_worker) {
worker->job_start_time = PICTURE(encode_begin_time); worker->job_start_time = PICTURE(encode_begin_time);
atomic_store(&worker->has_job, false); atomic_store(&worker->has_job, false);
long double last_comp_time = PICTURE(encode_end_time) - worker->job_start_time; worker->last_comp_time = PICTURE(encode_end_time) - worker->job_start_time;
A_MUTEX_LOCK(&worker->last_comp_time_mutex); LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%u, buffer=%u",
worker->last_comp_time = last_comp_time; PICTURE(used), worker->last_comp_time, worker->number, worker->buf_index);
A_MUTEX_UNLOCK(&worker->last_comp_time_mutex);
LOG_VERBOSE("Compressed JPEG size=%zu; time=%0.3Lf; worker=%u; buffer=%u",
PICTURE(used), last_comp_time, worker->number, worker->buf_index);
} else { } else {
worker->job_failed = true; worker->job_failed = true;
atomic_store(&worker->has_job, false); atomic_store(&worker->has_job, false);
@@ -484,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;
} }
@@ -547,33 +554,22 @@ static void _workers_pool_assign(struct _workers_pool_t *pool, struct _worker_t
LOG_DEBUG("Assigned new frame in buffer %u to worker %u", buf_index, ready_worker->number); LOG_DEBUG("Assigned new frame in buffer %u to worker %u", buf_index, ready_worker->number);
} }
static long double _workers_pool_get_fluency_delay(struct _workers_pool_t *pool) { static long double _workers_pool_get_fluency_delay(struct _workers_pool_t *pool, struct _worker_t *ready_worker) {
long double sum_comp_time = 0; long double approx_comp_time;
long double avg_comp_time;
long double min_delay; long double min_delay;
long double soft_delay;
for (unsigned number = 0; number < pool->n_workers; ++number) { approx_comp_time = pool->approx_comp_time * 0.9 + ready_worker->last_comp_time * 0.1;
# define WORKER(_next) pool->workers[number]._next
A_MUTEX_LOCK(&WORKER(last_comp_time_mutex)); LOG_VERBOSE("Correcting approx_comp_time: %.3Lf -> %.3Lf (last_comp_time=%.3Lf)",
if (WORKER(last_comp_time) > 0) { pool->approx_comp_time, approx_comp_time, ready_worker->last_comp_time);
sum_comp_time += WORKER(last_comp_time);
}
A_MUTEX_UNLOCK(&WORKER(last_comp_time_mutex));
# undef WORKER pool->approx_comp_time = approx_comp_time;
}
avg_comp_time = sum_comp_time / pool->n_workers; // Среднее время работы воркеров min_delay = pool->approx_comp_time / pool->n_workers; // Среднее время работы размазывается на N воркеров
min_delay = avg_comp_time / pool->n_workers; // Среднее время работы размазывается на N воркеров if (pool->desired_frames_interval > 0 && min_delay > 0 && pool->desired_frames_interval > min_delay) {
if (pool->desired_frames_interval > 0 && min_delay > 0) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps // Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
soft_delay = pool->desired_frames_interval - sum_comp_time; return pool->desired_frames_interval;
return (min_delay > soft_delay ? min_delay : soft_delay);
} }
return min_delay; return min_delay;
} }

View File

@@ -25,8 +25,7 @@ import textwrap
# ===== # =====
def get_prepend() -> str: C_PREPEND = textwrap.dedent("""
return textwrap.dedent("""
/***************************************************************************** /*****************************************************************************
# # # #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. # # uStreamer - Lightweight and fast MJPG-HTTP streamer. #

View File

@@ -29,24 +29,25 @@ import common
# ===== # =====
def main() -> None: def main() -> None:
assert len(sys.argv) == 4, "%s <file.html> <file.h> <name>" % (sys.argv[0]) assert len(sys.argv) == 4, f"{sys.argv[0]} <file.html> <file.h> <name>"
html_path = sys.argv[1] html_path = sys.argv[1]
header_path = sys.argv[2] header_path = sys.argv[2]
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(
("%s \\" if line.strip() else "%s\\") % (line) (f"{line} \\" if line.strip() else f"{line}\\")
for line in text.split("\n") for line in html.split("\n")
) )
text = "const char HTML_%s_PAGE[] = \" \\\n%s\n\";\n" % (name, text)
text = common.get_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

@@ -60,7 +60,7 @@ def _get_jpeg_size(data: bytes) -> Tuple[int, int]:
# ===== # =====
def main() -> None: def main() -> None:
assert len(sys.argv) == 4, "%s <file.jpeg> <file.h> <name>" % (sys.argv[0]) assert len(sys.argv) == 4, f"{sys.argv[0]} <file.jpeg> <file.h> <name>"
jpeg_path = sys.argv[1] jpeg_path = sys.argv[1]
header_path = sys.argv[2] header_path = sys.argv[2]
name = sys.argv[3] name = sys.argv[3]
@@ -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("0x%.2X" % (ch)) )
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 = "const unsigned char %s_JPEG_DATA[] = {\n\t%s\n};\n" % (name, text) text += f"const unsigned {name}_JPEG_WIDTH = {width};\n"
text = "const unsigned %s_JPEG_HEIGHT = %d;\n\n" % (name, height) + text text += f"const unsigned {name}_JPEG_HEIGHT = {height};\n\n"
text = "const unsigned %s_JPEG_WIDTH = %d;\n" % (name, width) + text text += f"const unsigned char {name}_JPEG_DATA[] = {jpeg_data_text};\n"
text = common.get_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)