Compare commits

...

46 Commits
v0.71 ... v0.79

Author SHA1 Message Date
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
Devaev Maxim
05246706f0 Bump version: 0.77 → 0.78 2019-06-02 21:07:18 +03:00
Devaev Maxim
9355055a7f Revert "fluency delay based on involved workers counter"
This reverts commit 9f16de13fe.
2019-06-02 21:06:30 +03:00
Devaev Maxim
9f16de13fe fluency delay based on involved workers counter 2019-06-02 20:23:39 +03:00
Devaev Maxim
36a987fb9d locked captured_fps 2019-06-02 19:35:53 +03:00
Devaev Maxim
652397f569 refactoring 2019-06-02 16:31:50 +03:00
Devaev Maxim
3333fc56a3 refactoring 2019-06-02 16:20:30 +03:00
Devaev Maxim
47378a17db refactoring 2019-06-02 15:22:34 +03:00
Devaev Maxim
5f320786f5 fixed workers cleanup on restart 2019-06-01 05:31:44 +03:00
Devaev Maxim
498c6e1f5d refactoring 2019-06-01 05:00:09 +03:00
Devaev Maxim
6c09adc689 free_worker -> ready_worker 2019-06-01 04:58:01 +03:00
Devaev Maxim
8bf7ac3005 refactoring 2019-06-01 04:47:51 +03:00
Devaev Maxim
6aebd7167e config.mk 2019-05-31 01:02:03 +03:00
Devaev Maxim
3f03575222 refactoring 2019-05-30 06:53:01 +03:00
Devaev Maxim
9ca43f17a3 debug workers using gpio on rpi 2019-05-26 22:32:51 +03:00
Devaev Maxim
66ef566c9a Makefile: on/off WITH_* options 2019-05-26 18:47:53 +03:00
Devaev Maxim
07ecc5b0a0 Bump version: 0.76 → 0.77 2019-05-26 06:03:59 +03:00
Devaev Maxim
8d19711f2c fixed pkgbuild 2019-05-26 06:03:48 +03:00
Devaev Maxim
c94117ae1e Bump version: 0.75 → 0.76 2019-05-26 05:55:48 +03:00
Devaev Maxim
06a80df708 More flexible build 2019-05-26 05:55:35 +03:00
Devaev Maxim
13dff256c8 workers by number of cores 2019-05-25 00:52:49 +03:00
Devaev Maxim
07e9dbc0f7 fixed gcc options ordering 2019-05-24 20:13:09 +03:00
Devaev Maxim
c83dddbf0b fixed indent 2019-05-18 23:51:14 +03:00
Devaev Maxim
5672d1aa75 Bump version: 0.74 → 0.75 2019-05-18 18:24:32 +03:00
Devaev Maxim
078097efec Added links to FreeBSD port 2019-05-18 18:22:37 +03:00
Devaev Maxim
3cd8338886 Issue #6: make install-strip and make uninstall 2019-05-18 17:58:23 +03:00
Maxim Devaev
d2b57cc7d5 Update README.md 2019-05-18 17:25:42 +03:00
Maxim Devaev
2c4f59c87a Update README.ru.md 2019-05-18 17:24:53 +03:00
Devaev Maxim
36eb7eeb76 minor help fix 2019-05-18 16:35:10 +03:00
Devaev Maxim
3b6544db8a cpu/encoder: removed TODO about jpeg error handling 2019-05-18 02:12:18 +03:00
Devaev Maxim
8033ab23ed Bump version: 0.73 → 0.74 2019-05-17 17:15:17 +03:00
Devaev Maxim
ddb3db8b20 Issue #5: Added libevent version check 2019-05-17 16:57:53 +03:00
Devaev Maxim
b3c8071edb Bump version: 0.72 → 0.73 2019-05-09 18:51:27 +03:00
Devaev Maxim
bc70faae09 fix 2019-05-05 04:26:25 +03:00
Devaev Maxim
ae7c4c91e0 Bump version: 0.71 → 0.72 2019-05-03 01:49:44 +03:00
Devaev Maxim
613baa4e1e better unix socket handling 2019-05-03 00:10:48 +03:00
31 changed files with 500 additions and 378 deletions

View File

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

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
/pkg/arch/src/
/pkg/arch/v*.tar.gz
/pkg/arch/ustreamer-*.pkg.tar.xz
/config.mk
/vgcore.*
/ustreamer
/*.sock

View File

@@ -1,23 +1,39 @@
-include config.mk
PROG ?= ustreamer
DESTDIR ?=
PREFIX ?= /usr/local
CC ?= gcc
CFLAGS ?= -O3
LDFLAGS ?=
CC ?= gcc
RPI_VC_HEADERS ?= /opt/vc/include
RPI_VC_LIBS ?= /opt/vc/lib
# =====
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
override CFLAGS += -c -std=c11 -Wall -Wextra -D_GNU_SOURCE
SOURCES = $(shell ls src/*.c src/http/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
PROG = ustreamer
ifeq ($(shell ls -d /opt/vc/include 2>/dev/null), /opt/vc/include)
SOURCES += $(shell ls src/encoders/omx/*.c)
LIBS += -lbcm_host -lvcos -lopenmaxil -L/opt/vc/lib
override CFLAGS += -DWITH_OMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifneq ($(call optbool,$(WITH_OMX_ENCODER)),)
LIBS += -lbcm_host -lvcos -lopenmaxil -L$(RPI_VC_LIBS)
override CFLAGS += -DWITH_OMX_ENCODER -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
SOURCES += $(shell ls src/encoders/omx/*.c)
endif
OBJECTS = $(SOURCES:.c=.o)
ifneq ($(call optbool,$(WITH_WORKERS_GPIO_DEBUG)),)
LIBS += -lwiringPi
override CFLAGS += -DWITH_WORKERS_GPIO_DEBUG
endif
# =====
@@ -28,17 +44,32 @@ install: $(PROG)
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
install-strip: install
strip $(DESTDIR)$(PREFIX)/bin/$(PROG)
uninstall:
rm $(DESTDIR)$(PREFIX)/bin/$(PROG)
regen:
tools/make-jpeg-h.py src/http/data/blank.jpeg src/http/data/blank_jpeg.h BLANK
tools/make-html-h.py src/http/data/index.html src/http/data/index_html.h INDEX
$(PROG): $(OBJECTS)
$(CC) $(LIBS) $(LDFLAGS) $(OBJECTS) -o $@
$(PROG): $(SOURCES:.c=.o)
$(info -- LINKING $@)
@ $(CC) $(SOURCES:.c=.o) -o $@ $(LDFLAGS) $(LIBS)
$(info ===== Build complete =====)
$(info == CC = $(CC))
$(info == LIBS = $(LIBS))
$(info == CFLAGS = $(CFLAGS))
$(info == LDFLAGS = $(LDFLAGS))
.c.o:
$(CC) $(LIBS) $(CFLAGS) $< -o $@
$(info -- CC $<)
@ $(CC) $< -o $@ $(CFLAGS) $(LIBS)
release:
@@ -50,13 +81,14 @@ release:
bump:
bumpversion $(if $(V), $(V), minor)
bumpversion $(if $(V),$(V),minor)
push:
git push
git push --tags
clean-all: clean
clean:
find src -name '*.o' -exec rm '{}' \;

View File

@@ -11,7 +11,7 @@
| Multithreaded JPEG encoding | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Behavior when the device<br>is disconnected while streaming | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Stops the broadcast <sup>1</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>2</sup> |
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Partially yes <sup>1</sup> |
| Option to skip frames when streaming<br>static images by HTTP to save traffic | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes <sup>2</sup> | ![#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 |
@@ -31,7 +31,7 @@ If you're going to live-stream from your backyard webcam and need to control it,
# Building
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libuuid```.
It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```.
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX_ENCODER=1``` to ```make```.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
@@ -40,7 +40,8 @@ $ make
$ ./ustreamer --help
```
AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer
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```.
FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
-----
# Usage

View File

@@ -11,7 +11,7 @@
| Многопоточное кодирование JPEG | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении<br>устройства от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Прерывает трансляцию <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>2</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности<br>изменения параметров разрешения<br>трансляции на лету по сигналу<br>источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Условно есть <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#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=+) Нет |
@@ -31,7 +31,7 @@
# Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX_ENCODER=1```.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
@@ -40,7 +40,8 @@ $ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```.
Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer.
-----
# Использование

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=0.71
pkgver=0.79
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pi-kvm/ustreamer"
@@ -12,19 +12,23 @@ arch=(i686 x86_64 armv6h armv7h)
depends=(libjpeg libevent libutil-linux)
# optional: raspberrypi-firmware for OMX JPEG encoder
makedepends=(gcc make)
source=("$url/archive/v$pkgver.tar.gz")
source=(${pkgname}::"git+https://github.com/pi-kvm/ustreamer#commit=v${pkgver}")
md5sums=(SKIP)
build() {
cd $srcdir
cd "$srcdir"
rm -rf $pkgname-build
cp -r $pkgname-$pkgver $pkgname-build
cp -r $pkgname $pkgname-build
cd $pkgname-build
make CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
local _options=""
[ -d /opt/vc/include ] && _options="$_options WITH_OMX_ENCODER=1"
make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS
}
package() {
cd $srcdir/$pkgname-build
cd "$srcdir/$pkgname-build"
make DESTDIR="$pkgdir" PREFIX=/usr install
}

View File

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

View File

@@ -22,4 +22,6 @@
#pragma once
#define VERSION "0.71"
#ifndef VERSION
# define VERSION "0.79"
#endif

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "device.h"
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
@@ -30,13 +32,13 @@
#include <errno.h>
#include <assert.h>
#include <sys/select.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include "tools.h"
#include "logging.h"
#include "xioctl.h"
#include "device.h"
static const struct {
@@ -81,10 +83,16 @@ static const char *_format_to_string_supported(unsigned format);
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_t *dev;
long cores_sysconf;
unsigned cores_available;
cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
cores_available = max_u(min_u(cores_sysconf, 4), 1);
A_CALLOC(ctl, 1);
@@ -97,8 +105,8 @@ struct device_t *device_init() {
dev->height = 480;
dev->format = V4L2_PIX_FMT_YUYV;
dev->standard = V4L2_STD_UNKNOWN;
dev->n_buffers = max_u(min_u(sysconf(_SC_NPROCESSORS_ONLN), 4), 1) + 1;
dev->n_workers = dev->n_buffers;
dev->n_buffers = cores_available + 1;
dev->n_workers = min_u(cores_available, dev->n_buffers);
dev->timeout = 1;
dev->error_delay = 1;
dev->ctl = ctl;
@@ -226,6 +234,39 @@ int device_switch_capturing(struct device_t *dev, bool enable) {
return 0;
}
int device_select(struct device_t *dev, bool *has_read, bool *has_write, bool *has_error) {
struct timeval timeout;
int retval;
# define INIT_FD_SET(_set) \
fd_set _set; FD_ZERO(&_set); FD_SET(dev->run->fd, &_set);
INIT_FD_SET(read_fds);
INIT_FD_SET(write_fds);
INIT_FD_SET(error_fds);
# undef INIT_FD_SET
timeout.tv_sec = dev->timeout;
timeout.tv_usec = 0;
LOG_DEBUG("Calling select() on video device ...");
retval = select(dev->run->fd + 1, &read_fds, &write_fds, &error_fds, &timeout);
if (retval > 0) {
*has_read = FD_ISSET(dev->run->fd, &read_fds);
*has_write = FD_ISSET(dev->run->fd, &write_fds);
*has_error = FD_ISSET(dev->run->fd, &error_fds);
} else {
has_read = false;
has_write = false;
has_error = false;
}
LOG_DEBUG("Device select() --> %d", retval);
return retval;
}
int device_grab_buffer(struct device_t *dev) {
struct v4l2_buffer buf_info;
@@ -239,14 +280,15 @@ int device_grab_buffer(struct device_t *dev) {
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) {
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;
}
dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused;
memcpy(&dev->run->hw_buffers[buf_info.index].buf_info, &buf_info, sizeof(struct v4l2_buffer));
dev->run->pictures[buf_info.index].grab_time = get_now_monotonic();
return buf_info.index;
}
@@ -322,7 +364,7 @@ static int _device_open_check_cap(struct device_t *dev) {
static int _device_open_dv_timings(struct device_t *dev) {
_device_apply_resolution(dev, dev->width, dev->height);
if (dev->dv_timings) {
LOG_DEBUG("Using DV-timings");
LOG_DEBUG("Using DV timings");
if (_device_apply_dv_timings(dev) < 0) {
return -1;
@@ -343,24 +385,22 @@ static int _device_open_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) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings) == 0) {
LOG_INFO("Got new DV timings: resolution=%ux%u; pixclk=%llu",
dv_timings.bt.width,
dv_timings.bt.height,
dv_timings.bt.pixelclock);
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu",
dv.bt.width, dv.bt.height, dv.bt.pixelclock);
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");
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;
}
@@ -390,7 +430,7 @@ static int _device_open_format(struct device_t *dev) {
// Set format
LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ...");
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),
dev->run->width,
dev->run->height);
@@ -561,7 +601,7 @@ static int _device_apply_resolution(struct device_t *dev, unsigned width, unsign
width == 0 || width > VIDEO_MAX_WIDTH
|| 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);
return -1;
}

View File

@@ -110,7 +110,7 @@ struct device_t {
};
struct device_t *device_init();
struct device_t *device_init(void);
void device_destroy(struct device_t *dev);
int device_parse_format(const char *str);
@@ -120,6 +120,7 @@ int device_open(struct device_t *dev);
void device_close(struct device_t *dev);
int device_switch_capturing(struct device_t *dev, bool enable);
int device_select(struct device_t *dev, bool *has_read, bool *has_write, bool *has_error);
int device_grab_buffer(struct device_t *dev);
int device_release_buffer(struct device_t *dev, unsigned index);
int device_consume_event(struct device_t *dev);

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "encoder.h"
#include <stdbool.h>
#include <strings.h>
#include <assert.h>
@@ -29,7 +31,6 @@
#include "tools.h"
#include "logging.h"
#include "device.h"
#include "encoder.h"
#include "encoders/cpu/encoder.h"
#include "encoders/hw/encoder.h"
@@ -51,7 +52,7 @@ static const struct {
};
struct encoder_t *encoder_init() {
struct encoder_t *encoder_init(void) {
struct encoder_runtime_t *run;
struct encoder_t *encoder;
@@ -188,6 +189,8 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
dev->run->pictures[buf_index].encode_begin_time = get_now_monotonic();
if (encoder->run->type == ENCODER_TYPE_CPU) {
cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality);
} else if (encoder->run->type == ENCODER_TYPE_HW) {
@@ -201,6 +204,8 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
}
# endif
dev->run->pictures[buf_index].encode_end_time = get_now_monotonic();
return 0;
# pragma GCC diagnostic ignored "-Wunused-label"

View File

@@ -70,7 +70,7 @@ struct encoder_t {
};
struct encoder_t *encoder_init();
struct encoder_t *encoder_init(void);
void encoder_destroy(struct encoder_t *encoder);
enum encoder_type_t encoder_parse_type(const char *str);

View File

@@ -25,6 +25,8 @@
*****************************************************************************/
#include "encoder.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -36,8 +38,6 @@
#include "../../tools.h"
#include "../../device.h"
#include "encoder.h"
struct _jpeg_dest_manager_t {
struct jpeg_destination_mgr mgr; // Default manager
@@ -105,8 +105,6 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
# undef WRITE_SCANLINES
// TODO: process jpeg errors:
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);

View File

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

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "component.h"
#include <unistd.h>
#include <IL/OMX_Core.h>
@@ -28,7 +30,6 @@
#include "../../logging.h"
#include "formatters.h"
#include "component.h"
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 <string.h>
#include <unistd.h>
@@ -39,7 +41,6 @@
#include "formatters.h"
#include "component.h"
#include "encoder.h"
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);
struct omx_encoder_t *omx_encoder_init() {
struct omx_encoder_t *omx_encoder_init(void) {
// Some theory:
// - http://www.fourcc.org/yuv.php
// - 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);
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 <assert.h>
@@ -27,7 +29,6 @@
#include <IL/OMX_Core.h>
#include "../../tools.h"
#include "formatters.h"
#define CASE_TO_STRING(_value) \

View File

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

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "blank.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
@@ -32,8 +34,6 @@
#include "data/blank_jpeg.h"
#include "blank.h"
struct _jpeg_error_manager_t {
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 int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height);
static void _jpeg_error_handler(j_common_ptr jpeg);
@@ -68,7 +68,7 @@ void blank_destroy(struct blank_t *blank) {
free(blank);
}
static struct blank_t *_blank_init_internal() {
static struct blank_t *_blank_init_internal(void) {
struct blank_t *blank;
A_CALLOC(blank, 1);
@@ -145,6 +145,7 @@ static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height) {
jpeg_create_decompress(&jpeg);
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
jpeg.err = jpeg_std_error((struct jpeg_error_mgr *)&jpeg_error);
jpeg_error.mgr.error_exit = _jpeg_error_handler;
if (setjmp(jpeg_error.jmp) < 0) {

View File

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

View File

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

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include "server.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
@@ -56,7 +58,6 @@
#include "base64.h"
#include "mime.h"
#include "static.h"
#include "server.h"
#include "data/index_html.h"
@@ -77,7 +78,7 @@ static void _http_callback_stream_error(struct bufferevent *buf_event, short wha
static void _http_exposed_refresh(int fd, short event, void *v_server);
static void _http_queue_send_stream(struct http_server_t *server, bool stream_updated, bool picture_updated);
static bool _expose_new_picture(struct http_server_t *server);
static bool _expose_new_picture_unsafe(struct http_server_t *server);
static bool _expose_blank_picture(struct http_server_t *server);
@@ -121,7 +122,10 @@ void http_server_destroy(struct http_server_t *server) {
close(server->run->unix_fd);
}
event_base_free(server->run->base);
# if LIBEVENT_VERSION_NUMBER >= 0x02010100
libevent_global_shutdown();
# endif
for (struct stream_client_t *client = server->run->stream_clients; client != NULL;) {
struct stream_client_t *next = client->next;
@@ -199,19 +203,25 @@ int http_server_listen(struct http_server_t *server) {
if (server->unix_path[0] != '\0') {
struct sockaddr_un unix_addr;
int unix_fd_flags;
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
assert((server->run->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0)));
assert((unix_fd_flags = fcntl(server->run->unix_fd, F_GETFL)) >= 0);
unix_fd_flags |= O_NONBLOCK;
assert(fcntl(server->run->unix_fd, F_SETFL, unix_fd_flags) >= 0);
# define MAX_SUN_PATH (sizeof(unix_addr.sun_path) - 1)
strncpy(unix_addr.sun_path, server->unix_path, 107);
unix_addr.sun_path[107] = '\0';
if (strlen(server->unix_path) > MAX_SUN_PATH) {
LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
return -1;
}
MEMSET_ZERO(unix_addr);
strncpy(unix_addr.sun_path, server->unix_path, MAX_SUN_PATH);
unix_addr.sun_family = AF_UNIX;
# undef MAX_SUN_PATH
assert((server->run->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0);
assert(!evutil_make_socket_nonblocking(server->run->unix_fd));
if (server->unix_rm && unlink(server->unix_path) < 0) {
if (errno != ENOENT) {
LOG_PERROR("Can't remove old UNIX socket '%s'", server->unix_path);
@@ -559,7 +569,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
}
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);
buf_event = evhttp_connection_get_bufferevent(conn);
@@ -798,7 +808,7 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
LOG_DEBUG("Refreshing HTTP exposed ...");
A_MUTEX_LOCK(&server->run->stream->mutex);
if (server->run->stream->picture.used > 0) { // If online
picture_updated = _expose_new_picture(server);
picture_updated = _expose_new_picture_unsafe(server);
UNLOCK_STREAM;
} else {
UNLOCK_STREAM;
@@ -816,7 +826,7 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
_http_queue_send_stream(server, stream_updated, picture_updated);
}
static bool _expose_new_picture(struct http_server_t *server) {
static bool _expose_new_picture_unsafe(struct http_server_t *server) {
# define STREAM(_next) server->run->stream->_next
# define EXPOSED(_next) server->run->exposed->_next
@@ -836,13 +846,13 @@ static bool _expose_new_picture(struct http_server_t *server) {
) {
EXPOSED(expose_cmp_time) = get_now_monotonic();
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) += 1;
return false; // Not updated
} else {
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));
}
}

View File

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

View File

@@ -123,10 +123,10 @@ pthread_mutex_t log_mutex;
INLINE char *errno_to_string(char *buf, size_t size) {
#if defined(__GLIBC__) && defined(_GNU_SOURCE)
# if defined(__GLIBC__) && defined(_GNU_SOURCE)
return strerror_r(errno, buf, size);
#else
# else
strerror_r(errno, buf, size);
return buf;
#endif
# endif
}

View File

@@ -32,6 +32,10 @@
#include <pthread.h>
#ifdef WITH_WORKERS_GPIO_DEBUG
# include <wiringPi.h>
#endif
#include "config.h"
#include "tools.h"
#include "logging.h"
@@ -129,19 +133,19 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf(" -z|--min-frame-size <N> ──────── Drop frames smaller then this limit. Useful if the device\n");
printf(" produces small-sized garbage frames. Default: disabled.\n\n");
printf(" -n|--persistent ──────────────── Don't re-initialize device on timeout. Default: disabled.\n\n");
printf(" -t|--dv-timings ──────────────── Enable DV timings queriyng and events processing.\n");
printf(" Supports automatic resolution changing. Default: disabled.\n\n");
printf(" -t|--dv-timings ──────────────── Enable DV timings queriyng and events processing\n");
printf(" to automatic resolution change. Default: disabled.\n\n");
printf(" -b|--buffers <N> ─────────────── The number of buffers to receive data from the device.\n");
printf(" Each buffer may processed using an intermediate thread.\n");
printf(" Default: %u (the number of CPU cores (but not more 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(" Default: %u (== --buffers).\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(" -c|--encoder <type> ──────────── Use specified encoder. It may affects to workers number.\n");
printf(" Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
printf(" --device-timeout <seconds> ───── Timeout for device querying. Default: %u\n\n", dev->timeout);
printf(" --device-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(" after an error (timeout for example). Default: %u.\n\n", dev->error_delay);
printf("Image control options:\n");
printf("══════════════════════\n");
printf(" --brightness <N> ───────────── Set brightness. Default: no change.\n\n");
@@ -199,7 +203,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
# define OPT_UNSIGNED(_dest, _name, _min, _max) { \
errno = 0; char *_end = NULL; int _tmp = strtol(optarg, &_end, 0); \
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; \
} \
_dest = _tmp; \
@@ -323,7 +327,7 @@ struct main_context_t {
static struct main_context_t *_ctx;
static void _block_thread_signals() {
static void _block_thread_signals(void) {
sigset_t mask;
assert(!sigemptyset(&mask));
assert(!sigaddset(&mask, SIGINT));
@@ -349,7 +353,7 @@ static void _signal_handler(int signum) {
http_server_loop_break(_ctx->server);
}
static void _install_signal_handlers() {
static void _install_signal_handlers(void) {
struct sigaction sig_act;
MEMSET_ZERO(sig_act);
@@ -377,6 +381,15 @@ int main(int argc, char *argv[]) {
LOGGING_INIT;
# ifdef WITH_WORKERS_GPIO_DEBUG
if (wiringPiSetupGpio() < 0) {
LOG_PERROR("Can't initialize wiringPi GPIO");
return 1;
} else {
LOG_INFO("Using wiringPi to debug using GPIO");
}
# endif
dev = device_init();
encoder = encoder_init();
stream = stream_init(dev, encoder);

View File

@@ -20,6 +20,9 @@
*****************************************************************************/
#include "stream.h"
#include <stdbool.h>
#include <stdatomic.h>
#include <unistd.h>
#include <errno.h>
@@ -28,7 +31,6 @@
#include <pthread.h>
#include <sys/select.h>
#include <linux/videodev2.h>
#include "tools.h"
@@ -36,18 +38,72 @@
#include "xioctl.h"
#include "device.h"
#include "encoder.h"
#include "stream.h"
#ifdef WITH_WORKERS_GPIO_DEBUG
# include <wiringPi.h>
# ifndef WORKERS_GPIO_DEBUG_START_PIN
# define WORKERS_GPIO_DEBUG_START_PIN 5
# endif
#endif
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool);
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index);
struct _worker_t {
pthread_t tid;
unsigned number;
atomic_bool *proc_stop;
atomic_bool *workers_stop;
static int _stream_init_loop(struct stream_t *stream, struct workers_pool_t *pool);
static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool);
long double last_comp_time;
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool);
static void *_stream_worker_thread(void *v_worker);
static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool_t *pool);
pthread_mutex_t has_job_mutex;
int buf_index;
atomic_bool has_job;
bool job_timely;
bool job_failed;
long double job_start_time;
pthread_cond_t has_job_cond;
pthread_mutex_t *free_workers_mutex;
unsigned *free_workers;
pthread_cond_t *free_workers_cond;
struct _worker_t *order_prev;
struct _worker_t *order_next;
struct device_t *dev;
struct encoder_t *encoder;
};
struct _workers_pool_t {
unsigned n_workers;
struct _worker_t *workers;
struct _worker_t *oldest_worker;
struct _worker_t *latest_worker;
long double approx_comp_time;
pthread_mutex_t free_workers_mutex;
unsigned free_workers;
pthread_cond_t free_workers_cond;
atomic_bool workers_stop;
long double desired_frames_interval;
};
static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream);
static struct _workers_pool_t *_stream_init(struct stream_t *stream);
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index, unsigned captured_fps);
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream);
static void _workers_pool_destroy(struct _workers_pool_t *pool);
static void *__worker_thread(void *v_worker);
static 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 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) {
@@ -74,20 +130,15 @@ void stream_destroy(struct stream_t *stream) {
}
void stream_loop(struct stream_t *stream) {
struct workers_pool_t pool;
MEMSET_ZERO(pool);
atomic_init(&pool.workers_stop, false);
pool.encoder = stream->encoder;
struct _workers_pool_t *pool;
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
while (_stream_init_loop(stream, &pool) == 0) {
struct worker_t *oldest_worker = NULL;
struct worker_t *last_worker = NULL;
while ((pool = _stream_init_loop(stream)) != NULL) {
long double grab_after = 0;
unsigned fluency_passed = 0;
unsigned captured_fps = 0;
unsigned captured_fps_accum = 0;
long long captured_fps_second = 0;
bool persistent_timeout_reported = false;
@@ -98,43 +149,22 @@ void stream_loop(struct stream_t *stream) {
LOG_INFO("Capturing ...");
while (!atomic_load(&stream->proc->stop)) {
int free_worker_number = -1;
struct _worker_t *ready_worker;
SEP_DEBUG('-');
LOG_DEBUG("Waiting for worker ...");
LOG_DEBUG("Waiting for workers ...");
A_MUTEX_LOCK(&pool.free_workers_mutex);
A_COND_WAIT_TRUE(pool.free_workers, &pool.free_workers_cond, &pool.free_workers_mutex);
A_MUTEX_UNLOCK(&pool.free_workers_mutex);
ready_worker = _workers_pool_wait(pool);
if (oldest_worker && !atomic_load(&oldest_worker->has_job) && oldest_worker->buf_index >= 0) {
if (oldest_worker->job_failed) {
break;
if (!ready_worker->job_failed) {
if (ready_worker->job_timely) {
_stream_expose_picture(stream, ready_worker->buf_index, captured_fps);
LOG_PERF("##### Encoded picture exposed; worker=%u", ready_worker->number);
} else {
LOG_PERF("----- Encoded picture dropped; worker=%u", ready_worker->number);
}
_stream_expose_picture(stream, oldest_worker->buf_index);
free_worker_number = oldest_worker->number;
oldest_worker = oldest_worker->order_next;
LOG_PERF("##### Raw frame accepted; worker = %u", free_worker_number);
} else {
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
if (
!atomic_load(&pool.workers[number].has_job) && (
free_worker_number == -1
|| pool.workers[free_worker_number].job_start_time < pool.workers[number].job_start_time
)
) {
free_worker_number = number;
break;
}
}
assert(free_worker_number >= 0);
assert(!atomic_load(&pool.workers[free_worker_number].has_job));
LOG_PERF("----- Raw frame dropped; worker = %u", free_worker_number);
break;
}
if (atomic_load(&stream->proc->stop)) {
@@ -145,30 +175,18 @@ void stream_loop(struct stream_t *stream) {
usleep(1000000);
}
# define INIT_FD_SET(_set) \
fd_set _set; FD_ZERO(&_set); FD_SET(stream->dev->run->fd, &_set);
bool has_read;
bool has_write;
bool has_error;
int selected = device_select(stream->dev, &has_read, &has_write, &has_error);
INIT_FD_SET(read_fds);
INIT_FD_SET(write_fds);
INIT_FD_SET(error_fds);
# undef INIT_FD_SET
struct timeval timeout;
timeout.tv_sec = stream->dev->timeout;
timeout.tv_usec = 0;
LOG_DEBUG("Calling select() on video device ...");
int retval = select(stream->dev->run->fd + 1, &read_fds, &write_fds, &error_fds, &timeout);
LOG_DEBUG("Device select() --> %d", retval);
if (retval < 0) {
if (selected < 0) {
if (errno != EINTR) {
LOG_PERROR("Mainloop select() error");
break;
}
} else if (retval == 0) {
} else if (selected == 0) {
if (stream->dev->persistent) {
if (!persistent_timeout_reported) {
LOG_ERROR("Mainloop select() timeout, polling ...")
@@ -183,7 +201,7 @@ void stream_loop(struct stream_t *stream) {
} else {
persistent_timeout_reported = false;
if (FD_ISSET(stream->dev->run->fd, &read_fds)) {
if (has_read) {
LOG_DEBUG("Frame is ready");
int buf_index;
@@ -193,7 +211,6 @@ void stream_loop(struct stream_t *stream) {
if ((buf_index = device_grab_buffer(stream->dev)) < 0) {
break;
}
stream->dev->run->pictures[buf_index].grab_time = now;
// Workaround for broken, corrupted frames:
// Under low light conditions corrupted frames may get captured.
@@ -201,7 +218,7 @@ void stream_loop(struct stream_t *stream) {
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
// corrupted frames are smaller.
if (stream->dev->run->hw_buffers[buf_index].used < stream->dev->min_frame_size) {
LOG_DEBUG("Dropping too small frame sized %zu bytes, assuming it as broken",
LOG_DEBUG("Dropped too small frame sized %zu bytes, assuming it was broken",
stream->dev->run->hw_buffers[buf_index].used);
goto pass_frame;
}
@@ -209,56 +226,26 @@ void stream_loop(struct stream_t *stream) {
{
if (now < grab_after) {
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;
}
fluency_passed = 0;
if (now_second != captured_fps_second) {
stream->captured_fps = captured_fps_accum;
captured_fps = captured_fps_accum;
captured_fps_accum = 0;
captured_fps_second = now_second;
LOG_PERF("Oldest worker complete, Captured-FPS = %u", stream->captured_fps);
LOG_PERF("A new second has come; captured_fps=%u", captured_fps);
}
captured_fps_accum += 1;
long double fluency_delay = _stream_get_fluency_delay(stream->dev, &pool);
long double fluency_delay = _workers_pool_get_fluency_delay(pool, ready_worker);
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);
}
# define FREE_WORKER(_next) pool.workers[free_worker_number]._next
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_index);
if (oldest_worker == NULL) {
oldest_worker = &pool.workers[free_worker_number];
last_worker = oldest_worker;
} else {
if (FREE_WORKER(order_next)) {
FREE_WORKER(order_next->order_prev) = FREE_WORKER(order_prev);
}
if (FREE_WORKER(order_prev)) {
FREE_WORKER(order_prev->order_next) = FREE_WORKER(order_next);
}
FREE_WORKER(order_prev) = last_worker;
last_worker->order_next = &pool.workers[free_worker_number];
last_worker = &pool.workers[free_worker_number];
}
last_worker->order_next = NULL;
A_MUTEX_LOCK(&FREE_WORKER(has_job_mutex));
FREE_WORKER(buf_index) = buf_index;
atomic_store(&FREE_WORKER(has_job), true);
A_MUTEX_UNLOCK(&FREE_WORKER(has_job_mutex));
A_COND_SIGNAL(&FREE_WORKER(has_job_cond));
# undef FREE_WORKER
A_MUTEX_LOCK(&pool.free_workers_mutex);
pool.free_workers -= 1;
A_MUTEX_UNLOCK(&pool.free_workers_mutex);
_workers_pool_assign(pool, ready_worker, buf_index);
goto next_handlers; // Поток сам освободит буфер
@@ -271,12 +258,12 @@ void stream_loop(struct stream_t *stream) {
next_handlers:
if (FD_ISSET(stream->dev->run->fd, &write_fds)) {
if (has_write) {
LOG_ERROR("Got unexpected writing event, seems device was disconnected");
break;
}
if (FD_ISSET(stream->dev->run->fd, &error_fds)) {
if (has_error) {
LOG_INFO("Got V4L2 event");
if (device_consume_event(stream->dev) < 0) {
break;
@@ -292,11 +279,11 @@ void stream_loop(struct stream_t *stream) {
stream->height = 0;
atomic_store(&stream->updated, true);
A_MUTEX_UNLOCK(&stream->mutex);
}
_stream_destroy_workers(stream, &pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
_workers_pool_destroy(pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
}
}
void stream_loop_break(struct stream_t *stream) {
@@ -307,7 +294,41 @@ void stream_switch_slowdown(struct stream_t *stream, bool slowdown) {
atomic_store(&stream->proc->slowdown, slowdown);
}
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index) {
static struct _workers_pool_t *_stream_init_loop(struct stream_t *stream) {
struct _workers_pool_t *pool = NULL;
LOG_DEBUG("%s: stream->proc->stop=%d", __FUNCTION__, atomic_load(&stream->proc->stop));
while (!atomic_load(&stream->proc->stop)) {
SEP_INFO('=');
if ((pool = _stream_init(stream)) == NULL) {
LOG_INFO("Sleeping %u seconds before new stream init ...", stream->dev->error_delay);
sleep(stream->dev->error_delay);
} else {
break;
}
}
return pool;
}
static struct _workers_pool_t *_stream_init(struct stream_t *stream) {
if (device_open(stream->dev) < 0) {
goto error;
}
if (device_switch_capturing(stream->dev, true) < 0) {
goto error;
}
encoder_prepare(stream->encoder, stream->dev);
return _workers_pool_init(stream);
error:
device_close(stream->dev);
return NULL;
}
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index, unsigned captured_fps) {
# define PICTURE(_next) stream->dev->run->pictures[buf_index]._next
A_MUTEX_LOCK(&stream->mutex);
@@ -323,6 +344,7 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
stream->width = stream->dev->run->width;
stream->height = stream->dev->run->height;
stream->captured_fps = captured_fps;
atomic_store(&stream->updated, true);
A_MUTEX_UNLOCK(&stream->mutex);
@@ -330,118 +352,98 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
# undef PICTURE
}
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool) {
long double sum_comp_time = 0;
long double avg_comp_time;
long double min_delay;
long double soft_delay;
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream) {
struct _workers_pool_t *pool;
for (unsigned number = 0; number < dev->run->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
LOG_INFO("Creating pool with %u workers ...", stream->dev->run->n_workers);
A_MUTEX_LOCK(&WORKER(last_comp_time_mutex));
if (WORKER(last_comp_time) > 0) {
sum_comp_time += WORKER(last_comp_time);
}
A_MUTEX_UNLOCK(&WORKER(last_comp_time_mutex));
A_CALLOC(pool, 1);
# undef WORKER
}
avg_comp_time = sum_comp_time / dev->run->n_workers; // Среднее время работы воркеров
min_delay = avg_comp_time / dev->run->n_workers; // Среднее время работы размазывается на N воркеров
if (dev->desired_fps > 0 && min_delay > 0) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
soft_delay = ((long double) 1) / dev->desired_fps - sum_comp_time;
return (min_delay > soft_delay ? min_delay : soft_delay);
}
return min_delay;
}
static int _stream_init_loop(struct stream_t *stream, struct workers_pool_t *pool) {
int retval = -1;
LOG_DEBUG("%s: stream->proc->stop = %d", __FUNCTION__, atomic_load(&stream->proc->stop));
while (!atomic_load(&stream->proc->stop)) {
if ((retval = _stream_init(stream, pool)) < 0) {
LOG_INFO("Sleeping %u seconds before new stream init ...", stream->dev->error_delay);
sleep(stream->dev->error_delay);
} else {
break;
}
}
return retval;
}
static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
_stream_destroy_workers(stream, pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
SEP_INFO('=');
if (device_open(stream->dev) < 0) {
goto error;
}
if (device_switch_capturing(stream->dev, true) < 0) {
goto error;
}
encoder_prepare(pool->encoder, stream->dev);
_stream_init_workers(stream, pool);
return 0;
error:
device_close(stream->dev);
return -1;
}
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool) {
LOG_INFO("Spawning %u workers ...", stream->dev->run->n_workers);
atomic_store(&pool->workers_stop, false);
A_CALLOC(pool->workers, stream->dev->run->n_workers);
pool->n_workers = stream->dev->run->n_workers;
A_CALLOC(pool->workers, pool->n_workers);
A_MUTEX_INIT(&pool->free_workers_mutex);
A_COND_INIT(&pool->free_workers_cond);
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
atomic_init(&pool->workers_stop, false);
pool->free_workers += 1;
if (stream->dev->desired_fps > 0) {
pool->desired_frames_interval = (long double)1 / stream->dev->desired_fps;
}
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_MUTEX_INIT(&WORKER(has_job_mutex));
atomic_init(&WORKER(has_job), false);
A_COND_INIT(&WORKER(has_job_cond));
WORKER(number) = number;
WORKER(proc_stop) = &stream->proc->stop;
WORKER(workers_stop) = &pool->workers_stop;
WORKER(number) = number;
WORKER(proc_stop) = &stream->proc->stop;
WORKER(workers_stop) = &pool->workers_stop;
WORKER(free_workers_mutex) = &pool->free_workers_mutex;
WORKER(free_workers) = &pool->free_workers;
WORKER(free_workers_cond) = &pool->free_workers_cond;
WORKER(free_workers_mutex) = &pool->free_workers_mutex;
WORKER(free_workers) = &pool->free_workers;
WORKER(free_workers_cond) = &pool->free_workers_cond;
WORKER(dev) = stream->dev;
WORKER(encoder) = pool->encoder;
WORKER(dev) = stream->dev;
WORKER(encoder) = stream->encoder;
A_THREAD_CREATE(&WORKER(tid), _stream_worker_thread, (void *)&(pool->workers[number]));
A_THREAD_CREATE(&WORKER(tid), __worker_thread, (void *)&(pool->workers[number]));
pool->free_workers += 1;
# undef WORKER
}
return pool;
}
static void *_stream_worker_thread(void *v_worker) {
struct worker_t *worker = (struct worker_t *)v_worker;
static void _workers_pool_destroy(struct _workers_pool_t *pool) {
LOG_INFO("Destroying workers pool ...");
atomic_store(&pool->workers_stop, true);
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_MUTEX_LOCK(&WORKER(has_job_mutex));
atomic_store(&WORKER(has_job), true); // Final job: die
A_MUTEX_UNLOCK(&WORKER(has_job_mutex));
A_COND_SIGNAL(&WORKER(has_job_cond));
A_THREAD_JOIN(WORKER(tid));
A_MUTEX_DESTROY(&WORKER(has_job_mutex));
A_COND_DESTROY(&WORKER(has_job_cond));
# undef WORKER
}
A_MUTEX_DESTROY(&pool->free_workers_mutex);
A_COND_DESTROY(&pool->free_workers_cond);
free(pool->workers);
free(pool);
}
static void *__worker_thread(void *v_worker) {
struct _worker_t *worker = (struct _worker_t *)v_worker;
LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number);
# ifdef WITH_WORKERS_GPIO_DEBUG
# define WORKER_GPIO_DEBUG_BUSY digitalWrite(WORKERS_GPIO_DEBUG_START_PIN + worker->number, HIGH)
# define WORKER_GPIO_DEBUG_FREE digitalWrite(WORKERS_GPIO_DEBUG_START_PIN + worker->number, LOW)
pinMode(WORKERS_GPIO_DEBUG_START_PIN + worker->number, OUTPUT);
WORKER_GPIO_DEBUG_FREE;
# else
# define WORKER_GPIO_DEBUG_BUSY
# define WORKER_GPIO_DEBUG_FREE
# endif
while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) {
LOG_DEBUG("Worker %u waiting for a new job ...", worker->number);
WORKER_GPIO_DEBUG_FREE;
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_MUTEX_UNLOCK(&worker->has_job_mutex);
@@ -451,24 +453,20 @@ static void *_stream_worker_thread(void *v_worker) {
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index);
PICTURE(encode_begin_time) = get_now_monotonic();
WORKER_GPIO_DEBUG_BUSY;
if (encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index) < 0) {
worker->job_failed = true;
worker->job_failed = false;
}
PICTURE(encode_end_time) = get_now_monotonic();
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
worker->job_start_time = PICTURE(encode_begin_time);
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);
worker->last_comp_time = last_comp_time;
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);
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%u, buffer=%u",
PICTURE(used), worker->last_comp_time, worker->number, worker->buf_index);
} else {
worker->job_failed = true;
atomic_store(&worker->has_job, false);
@@ -484,34 +482,85 @@ static void *_stream_worker_thread(void *v_worker) {
}
LOG_DEBUG("Bye-bye (worker %u)", worker->number);
WORKER_GPIO_DEBUG_FREE;
return NULL;
}
static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool_t *pool) {
if (pool->workers) {
LOG_INFO("Destroying workers ...");
static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool) {
struct _worker_t *ready_worker = NULL;
atomic_store(&pool->workers_stop, true);
for (unsigned number = 0; number < stream->dev->run->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_MUTEX_LOCK(&pool->free_workers_mutex);
A_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex);
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
A_MUTEX_LOCK(&WORKER(has_job_mutex));
atomic_store(&WORKER(has_job), true); // Final job: die
A_MUTEX_UNLOCK(&WORKER(has_job_mutex));
A_COND_SIGNAL(&WORKER(has_job_cond));
A_THREAD_JOIN(WORKER(tid));
A_MUTEX_DESTROY(&WORKER(has_job_mutex));
A_COND_DESTROY(&WORKER(has_job_cond));
# undef WORKER
if (pool->oldest_worker && !atomic_load(&pool->oldest_worker->has_job) && pool->oldest_worker->buf_index >= 0) {
ready_worker = pool->oldest_worker;
ready_worker->job_timely = true;
pool->oldest_worker = pool->oldest_worker->order_next;
} else {
for (unsigned number = 0; number < pool->n_workers; ++number) {
if (
!atomic_load(&pool->workers[number].has_job) && (
ready_worker == NULL
|| ready_worker->job_start_time < pool->workers[number].job_start_time
)
) {
ready_worker = &pool->workers[number];
break;
}
}
A_MUTEX_DESTROY(&pool->free_workers_mutex);
A_COND_DESTROY(&pool->free_workers_cond);
free(pool->workers);
assert(ready_worker != NULL);
ready_worker->job_timely = false; // Освободился воркер, получивший задание позже (или самый первый при самом первом захвате)
}
pool->free_workers = 0;
pool->workers = NULL;
return ready_worker;
}
static void _workers_pool_assign(struct _workers_pool_t *pool, struct _worker_t *ready_worker, unsigned buf_index) {
if (pool->oldest_worker == NULL) {
pool->oldest_worker = ready_worker;
pool->latest_worker = pool->oldest_worker;
} else {
if (ready_worker->order_next) {
ready_worker->order_next->order_prev = ready_worker->order_prev;
}
if (ready_worker->order_prev) {
ready_worker->order_prev->order_next = ready_worker->order_next;
}
ready_worker->order_prev = pool->latest_worker;
pool->latest_worker->order_next = ready_worker;
pool->latest_worker = ready_worker;
}
pool->latest_worker->order_next = NULL;
A_MUTEX_LOCK(&ready_worker->has_job_mutex);
ready_worker->buf_index = buf_index;
atomic_store(&ready_worker->has_job, true);
A_MUTEX_UNLOCK(&ready_worker->has_job_mutex);
A_COND_SIGNAL(&ready_worker->has_job_cond);
A_MUTEX_LOCK(&pool->free_workers_mutex);
pool->free_workers -= 1;
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
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, struct _worker_t *ready_worker) {
long double approx_comp_time;
long double min_delay;
approx_comp_time = pool->approx_comp_time * 0.9 + ready_worker->last_comp_time * 0.1;
LOG_VERBOSE("Correcting approx_comp_time: %.3Lf -> %.3Lf (last_comp_time=%.3Lf)",
pool->approx_comp_time, approx_comp_time, ready_worker->last_comp_time);
pool->approx_comp_time = approx_comp_time;
min_delay = pool->approx_comp_time / pool->n_workers; // Среднее время работы размазывается на N воркеров
if (pool->desired_frames_interval > 0 && min_delay > 0 && pool->desired_frames_interval > min_delay) {
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
return pool->desired_frames_interval;
}
return min_delay;
}

View File

@@ -22,53 +22,12 @@
#pragma once
#include <stdbool.h>
#include <stdatomic.h>
#include <pthread.h>
#include "device.h"
#include "encoder.h"
struct worker_t {
pthread_t tid;
unsigned number;
atomic_bool *proc_stop;
atomic_bool *workers_stop;
pthread_mutex_t last_comp_time_mutex;
long double last_comp_time;
pthread_mutex_t has_job_mutex;
int buf_index;
atomic_bool has_job;
bool job_failed;
long double job_start_time;
pthread_cond_t has_job_cond;
pthread_mutex_t *free_workers_mutex;
unsigned *free_workers;
pthread_cond_t *free_workers_cond;
struct worker_t *order_prev;
struct worker_t *order_next;
struct device_t *dev;
struct encoder_t *encoder;
};
struct workers_pool_t {
struct worker_t *workers;
atomic_bool workers_stop;
pthread_mutex_t free_workers_mutex;
unsigned free_workers;
pthread_cond_t free_workers_cond;
struct encoder_t *encoder;
};
struct process_t {
atomic_bool stop;
atomic_bool slowdown;

View File

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

View File

@@ -29,7 +29,7 @@ import common
# =====
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]
header_path = sys.argv[2]
name = sys.argv[3]
@@ -42,11 +42,11 @@ def main() -> None:
text = text.replace("%VERSION%", "\" VERSION \"")
text = textwrap.indent(text, "\t", (lambda line: True))
text = "\n".join(
("%s \\" if line.strip() else "%s\\") % (line)
(f"{line} \\" if line.strip() else f"{line}\\")
for line in text.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"const char HTML_{name}_PAGE[] = \" \\\n{text}\n\";\n"
text = f"{common.C_PREPEND}\n#include \"../../config.h\"\n\n\n{text}"
with open(header_path, "w") as header_file:
header_file.write(text)

View File

@@ -60,7 +60,7 @@ def _get_jpeg_size(data: bytes) -> Tuple[int, int]:
# =====
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]
header_path = sys.argv[2]
name = sys.argv[3]
@@ -74,13 +74,13 @@ def main() -> None:
for ch in jpeg_data:
if len(rows[-1]) > 20:
rows.append([])
rows[-1].append("0x%.2X" % (ch))
rows[-1].append(f"0x{ch:02X}")
text = ",\n\t".join(", ".join(row) for row in rows)
text = "const unsigned char %s_JPEG_DATA[] = {\n\t%s\n};\n" % (name, text)
text = "const unsigned %s_JPEG_HEIGHT = %d;\n\n" % (name, height) + text
text = "const unsigned %s_JPEG_WIDTH = %d;\n" % (name, width) + text
text = common.get_prepend() + "\n\n" + text
text = f"const unsigned char {name}_JPEG_DATA[] = {{\n\t{text}\n}};\n"
text = f"const unsigned {name}_JPEG_HEIGHT = {height};\n\n{text}"
text = f"const unsigned {name}_JPEG_WIDTH = {width};\n{text}"
text = f"{common.C_PREPEND}\n\n{text}"
with open(header_path, "w") as header_file:
header_file.write(text)