mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 04:06:30 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05246706f0 | ||
|
|
9355055a7f | ||
|
|
9f16de13fe | ||
|
|
36a987fb9d | ||
|
|
652397f569 | ||
|
|
3333fc56a3 | ||
|
|
47378a17db | ||
|
|
5f320786f5 | ||
|
|
498c6e1f5d | ||
|
|
6c09adc689 | ||
|
|
8bf7ac3005 | ||
|
|
6aebd7167e | ||
|
|
3f03575222 | ||
|
|
9ca43f17a3 | ||
|
|
66ef566c9a | ||
|
|
07ecc5b0a0 | ||
|
|
8d19711f2c | ||
|
|
c94117ae1e | ||
|
|
06a80df708 | ||
|
|
13dff256c8 | ||
|
|
07e9dbc0f7 | ||
|
|
c83dddbf0b | ||
|
|
5672d1aa75 | ||
|
|
078097efec | ||
|
|
3cd8338886 | ||
|
|
d2b57cc7d5 | ||
|
|
2c4f59c87a | ||
|
|
36eb7eeb76 | ||
|
|
3b6544db8a | ||
|
|
8033ab23ed | ||
|
|
ddb3db8b20 | ||
|
|
b3c8071edb | ||
|
|
bc70faae09 | ||
|
|
ae7c4c91e0 | ||
|
|
613baa4e1e | ||
|
|
33a101b4f7 | ||
|
|
9e9039c4e6 | ||
|
|
b3ceec51de | ||
|
|
e3ac4ba6f5 | ||
|
|
fa6b8b44c1 | ||
|
|
8cb7574af2 |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 0.69
|
current_version = 0.78
|
||||||
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}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
|||||||
/pkg/arch/src/
|
/pkg/arch/src/
|
||||||
/pkg/arch/v*.tar.gz
|
/pkg/arch/v*.tar.gz
|
||||||
/pkg/arch/ustreamer-*.pkg.tar.xz
|
/pkg/arch/ustreamer-*.pkg.tar.xz
|
||||||
|
/config.mk
|
||||||
/vgcore.*
|
/vgcore.*
|
||||||
/ustreamer
|
/ustreamer
|
||||||
/*.sock
|
/*.sock
|
||||||
|
|||||||
54
Makefile
54
Makefile
@@ -1,23 +1,39 @@
|
|||||||
|
-include config.mk
|
||||||
|
|
||||||
|
PROG ?= ustreamer
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
|
|
||||||
|
CC ?= gcc
|
||||||
CFLAGS ?= -O3
|
CFLAGS ?= -O3
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
CC ?= gcc
|
|
||||||
|
RPI_VC_HEADERS ?= /opt/vc/include
|
||||||
|
RPI_VC_LIBS ?= /opt/vc/lib
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
|
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
|
||||||
override CFLAGS += -c -std=c11 -Wall -Wextra -D_GNU_SOURCE
|
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)
|
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)
|
define optbool
|
||||||
LIBS += -lbcm_host -lvcos -lopenmaxil -L/opt/vc/lib
|
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||||
override CFLAGS += -DWITH_OMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
|
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
|
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 -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
|
||||||
|
|
||||||
|
|
||||||
|
install-strip: install
|
||||||
|
strip $(DESTDIR)$(PREFIX)/bin/$(PROG)
|
||||||
|
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm $(DESTDIR)$(PREFIX)/bin/$(PROG)
|
||||||
|
|
||||||
|
|
||||||
regen:
|
regen:
|
||||||
tools/make-jpeg-h.py src/http/data/blank.jpeg src/http/data/blank_jpeg.h BLANK
|
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
|
tools/make-html-h.py src/http/data/index.html src/http/data/index_html.h INDEX
|
||||||
|
|
||||||
|
|
||||||
$(PROG): $(OBJECTS)
|
$(PROG): $(SOURCES:.c=.o)
|
||||||
$(CC) $(LIBS) $(LDFLAGS) $(OBJECTS) -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:
|
.c.o:
|
||||||
$(CC) $(LIBS) $(CFLAGS) $< -o $@
|
$(info -- CC $<)
|
||||||
|
@ $(CC) $< -o $@ $(CFLAGS) $(LIBS)
|
||||||
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
@@ -50,13 +81,14 @@ release:
|
|||||||
|
|
||||||
|
|
||||||
bump:
|
bump:
|
||||||
bumpversion $(if $(V), $(V), minor)
|
bumpversion $(if $(V),$(V),minor)
|
||||||
|
|
||||||
|
|
||||||
push:
|
push:
|
||||||
git push
|
git push
|
||||||
git push --tags
|
git push --tags
|
||||||
|
|
||||||
|
|
||||||
clean-all: clean
|
clean-all: clean
|
||||||
clean:
|
clean:
|
||||||
find src -name '*.o' -exec rm '{}' \;
|
find src -name '*.o' -exec rm '{}' \;
|
||||||
|
|||||||
@@ -31,7 +31,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```.
|
||||||
|
|
||||||
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
|
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
|
||||||
@@ -40,7 +40,8 @@ $ make
|
|||||||
$ ./ustreamer --help
|
$ ./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
|
# Usage
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
# Сборка
|
# Сборка
|
||||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
|
Для сборки вам понадобятся ```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
|
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
|
||||||
@@ -40,7 +40,8 @@ $ make
|
|||||||
$ ./ustreamer --help
|
$ ./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.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
# Использование
|
# Использование
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=0.69
|
pkgver=0.78
|
||||||
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"
|
||||||
@@ -12,19 +12,23 @@ 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
|
||||||
makedepends=(gcc make)
|
makedepends=(gcc make)
|
||||||
source=("$url/archive/v$pkgver.tar.gz")
|
source=(${pkgname}::"git+https://github.com/pi-kvm/ustreamer#commit=v${pkgver}")
|
||||||
md5sums=(SKIP)
|
md5sums=(SKIP)
|
||||||
|
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd $srcdir
|
cd "$srcdir"
|
||||||
rm -rf $pkgname-build
|
rm -rf $pkgname-build
|
||||||
cp -r $pkgname-$pkgver $pkgname-build
|
cp -r $pkgname $pkgname-build
|
||||||
cd $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() {
|
package() {
|
||||||
cd $srcdir/$pkgname-build
|
cd "$srcdir/$pkgname-build"
|
||||||
make DESTDIR="$pkgdir" PREFIX=/usr install
|
make DESTDIR="$pkgdir" PREFIX=/usr install
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ustreamer
|
PKG_NAME:=ustreamer
|
||||||
PKG_VERSION:=0.69
|
PKG_VERSION:=0.78
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
|||||||
@@ -22,4 +22,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define VERSION "0.69"
|
#ifndef VERSION
|
||||||
|
# define VERSION "0.78"
|
||||||
|
#endif
|
||||||
|
|||||||
51
src/device.c
51
src/device.c
@@ -30,6 +30,7 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <sys/select.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <linux/videodev2.h>
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
@@ -85,6 +86,12 @@ struct device_t *device_init() {
|
|||||||
struct controls_t *ctl;
|
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;
|
||||||
|
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);
|
A_CALLOC(ctl, 1);
|
||||||
|
|
||||||
@@ -97,8 +104,8 @@ struct device_t *device_init() {
|
|||||||
dev->height = 480;
|
dev->height = 480;
|
||||||
dev->format = V4L2_PIX_FMT_YUYV;
|
dev->format = V4L2_PIX_FMT_YUYV;
|
||||||
dev->standard = V4L2_STD_UNKNOWN;
|
dev->standard = V4L2_STD_UNKNOWN;
|
||||||
dev->n_buffers = max_u(min_u(sysconf(_SC_NPROCESSORS_ONLN), 4), 1) + 1;
|
dev->n_buffers = cores_available + 1;
|
||||||
dev->n_workers = 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->ctl = ctl;
|
||||||
@@ -156,6 +163,8 @@ int device_open(struct device_t *dev) {
|
|||||||
_device_open_alloc_picbufs(dev);
|
_device_open_alloc_picbufs(dev);
|
||||||
_device_apply_controls(dev);
|
_device_apply_controls(dev);
|
||||||
|
|
||||||
|
dev->run->n_workers = min_u(dev->run->n_buffers, dev->n_workers);
|
||||||
|
|
||||||
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
|
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@@ -165,6 +174,8 @@ int device_open(struct device_t *dev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void device_close(struct device_t *dev) {
|
void device_close(struct device_t *dev) {
|
||||||
|
dev->run->n_workers = 0;
|
||||||
|
|
||||||
if (dev->run->pictures) {
|
if (dev->run->pictures) {
|
||||||
LOG_DEBUG("Releasing picture buffers ...");
|
LOG_DEBUG("Releasing picture buffers ...");
|
||||||
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index].data; ++index) {
|
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index].data; ++index) {
|
||||||
@@ -222,6 +233,39 @@ int device_switch_capturing(struct device_t *dev, bool enable) {
|
|||||||
return 0;
|
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) {
|
int device_grab_buffer(struct device_t *dev) {
|
||||||
struct v4l2_buffer buf_info;
|
struct v4l2_buffer buf_info;
|
||||||
|
|
||||||
@@ -243,6 +287,7 @@ int device_grab_buffer(struct device_t *dev) {
|
|||||||
|
|
||||||
dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused;
|
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));
|
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;
|
return buf_info.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +363,7 @@ static int _device_open_check_cap(struct device_t *dev) {
|
|||||||
static int _device_open_dv_timings(struct device_t *dev) {
|
static int _device_open_dv_timings(struct device_t *dev) {
|
||||||
_device_apply_resolution(dev, dev->width, dev->height);
|
_device_apply_resolution(dev, dev->width, dev->height);
|
||||||
if (dev->dv_timings) {
|
if (dev->dv_timings) {
|
||||||
LOG_DEBUG("Using DV-timings");
|
LOG_DEBUG("Using DV timings");
|
||||||
|
|
||||||
if (_device_apply_dv_timings(dev) < 0) {
|
if (_device_apply_dv_timings(dev) < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ struct device_runtime_t {
|
|||||||
unsigned height;
|
unsigned height;
|
||||||
unsigned format;
|
unsigned format;
|
||||||
unsigned n_buffers;
|
unsigned n_buffers;
|
||||||
// unsigned n_workers; // FIXME
|
unsigned n_workers;
|
||||||
struct hw_buffer_t *hw_buffers;
|
struct hw_buffer_t *hw_buffers;
|
||||||
struct picture_t *pictures;
|
struct picture_t *pictures;
|
||||||
size_t max_raw_image_size;
|
size_t max_raw_image_size;
|
||||||
@@ -120,6 +120,7 @@ int device_open(struct device_t *dev);
|
|||||||
void device_close(struct device_t *dev);
|
void device_close(struct device_t *dev);
|
||||||
|
|
||||||
int device_switch_capturing(struct device_t *dev, bool enable);
|
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_grab_buffer(struct device_t *dev);
|
||||||
int device_release_buffer(struct device_t *dev, unsigned index);
|
int device_release_buffer(struct device_t *dev, unsigned index);
|
||||||
int device_consume_event(struct device_t *dev);
|
int device_consume_event(struct device_t *dev);
|
||||||
|
|||||||
141
src/encoder.c
141
src/encoder.c
@@ -20,6 +20,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
@@ -66,48 +67,6 @@ struct encoder_t *encoder_init() {
|
|||||||
return encoder;
|
return encoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
|
|
||||||
assert(encoder->type != ENCODER_TYPE_UNKNOWN);
|
|
||||||
// XXX: Тут нет гонки, потому что encoder_prepare() запускается еще до существования других потоков
|
|
||||||
encoder->run->type = encoder->type;
|
|
||||||
encoder->run->quality = encoder->quality;
|
|
||||||
|
|
||||||
LOG_INFO("Using JPEG quality: %u%%", encoder->quality);
|
|
||||||
|
|
||||||
# ifdef WITH_OMX_ENCODER
|
|
||||||
if (encoder->run->type == ENCODER_TYPE_OMX) {
|
|
||||||
LOG_DEBUG("Preparing OMX JPEG encoder ...");
|
|
||||||
|
|
||||||
if (dev->n_workers > OMX_MAX_ENCODERS) {
|
|
||||||
LOG_INFO("OMX JPEG encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
|
|
||||||
dev->n_workers = OMX_MAX_ENCODERS;
|
|
||||||
}
|
|
||||||
encoder->run->n_omxs = dev->n_workers;
|
|
||||||
|
|
||||||
A_CALLOC(encoder->run->omxs, encoder->run->n_omxs);
|
|
||||||
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
|
||||||
if ((encoder->run->omxs[index] = omx_encoder_init()) == NULL) {
|
|
||||||
goto use_fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# endif
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
|
||||||
# pragma GCC diagnostic push
|
|
||||||
use_fallback:
|
|
||||||
LOG_ERROR("Can't initialize selected encoder, using CPU instead it");
|
|
||||||
encoder->run->type = ENCODER_TYPE_CPU;
|
|
||||||
encoder->run->quality = encoder->quality;
|
|
||||||
# pragma GCC diagnostic pop
|
|
||||||
}
|
|
||||||
|
|
||||||
void encoder_destroy(struct encoder_t *encoder) {
|
void encoder_destroy(struct encoder_t *encoder) {
|
||||||
# ifdef WITH_OMX_ENCODER
|
# ifdef WITH_OMX_ENCODER
|
||||||
if (encoder->run->omxs) {
|
if (encoder->run->omxs) {
|
||||||
@@ -142,80 +101,118 @@ const char *encoder_type_to_string(enum encoder_type_t type) {
|
|||||||
return _ENCODER_TYPES[0].name;
|
return _ENCODER_TYPES[0].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
|
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
enum encoder_type_t type = (encoder->run->cpu_forced ? ENCODER_TYPE_CPU : encoder->type);
|
||||||
|
unsigned quality = encoder->quality;
|
||||||
|
bool cpu_forced = false;
|
||||||
|
|
||||||
if (
|
if ((dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG) && type != ENCODER_TYPE_HW) {
|
||||||
(dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG)
|
|
||||||
&& encoder->run->type != ENCODER_TYPE_HW
|
|
||||||
) {
|
|
||||||
LOG_INFO("Switching to HW JPEG encoder because the input format is (M)JPEG");
|
LOG_INFO("Switching to HW JPEG encoder because the input format is (M)JPEG");
|
||||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
type = ENCODER_TYPE_HW;
|
||||||
encoder->run->type = ENCODER_TYPE_HW;
|
|
||||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encoder->run->type == ENCODER_TYPE_HW) {
|
if (type == ENCODER_TYPE_HW) {
|
||||||
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
|
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
|
||||||
LOG_INFO("Switching to CPU JPEG encoder because the input format is not (M)JPEG");
|
LOG_INFO("Switching to CPU JPEG encoder because the input format is not (M)JPEG");
|
||||||
goto use_fallback;
|
goto use_cpu;
|
||||||
}
|
}
|
||||||
if (hw_encoder_prepare_live(dev, encoder->quality) < 0) {
|
|
||||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
if (hw_encoder_prepare(dev, quality) < 0) {
|
||||||
encoder->run->quality = 0;
|
quality = 0;
|
||||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
|
||||||
LOG_INFO("Using JPEG quality: HW-default");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dev->run->n_workers = 1;
|
||||||
}
|
}
|
||||||
# ifdef WITH_OMX_ENCODER
|
# ifdef WITH_OMX_ENCODER
|
||||||
else if (encoder->run->type == ENCODER_TYPE_OMX) {
|
else if (type == ENCODER_TYPE_OMX) {
|
||||||
|
LOG_DEBUG("Preparing OMX JPEG encoder ...");
|
||||||
|
|
||||||
|
if (dev->run->n_workers > OMX_MAX_ENCODERS) {
|
||||||
|
LOG_INFO("OMX JPEG encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
|
||||||
|
dev->run->n_workers = OMX_MAX_ENCODERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoder->run->omxs == NULL) {
|
||||||
|
A_CALLOC(encoder->run->omxs, OMX_MAX_ENCODERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||||
|
for (; encoder->run->n_omxs < dev->run->n_workers; ++encoder->run->n_omxs) {
|
||||||
|
if ((encoder->run->omxs[encoder->run->n_omxs] = omx_encoder_init()) == NULL) {
|
||||||
|
LOG_ERROR("Can't initialize OMX JPEG encoder, falling back to CPU");
|
||||||
|
goto force_cpu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
||||||
if (omx_encoder_prepare_live(encoder->run->omxs[index], dev, encoder->quality) < 0) {
|
if (omx_encoder_prepare(encoder->run->omxs[index], dev, quality) < 0) {
|
||||||
LOG_ERROR("Can't prepare OMX JPEG encoder, falling back to CPU");
|
LOG_ERROR("Can't prepare OMX JPEG encoder, falling back to CPU");
|
||||||
goto use_fallback;
|
goto force_cpu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
return;
|
goto ok;
|
||||||
|
|
||||||
|
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||||
|
# pragma GCC diagnostic push
|
||||||
|
force_cpu:
|
||||||
|
cpu_forced = true;
|
||||||
|
# pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
use_cpu:
|
||||||
|
type = ENCODER_TYPE_CPU;
|
||||||
|
quality = encoder->quality;
|
||||||
|
|
||||||
|
ok:
|
||||||
|
if (quality == 0) {
|
||||||
|
LOG_INFO("Using JPEG quality: encoder default");
|
||||||
|
} else {
|
||||||
|
LOG_INFO("Using JPEG quality: %u%%", quality);
|
||||||
|
}
|
||||||
|
|
||||||
use_fallback:
|
|
||||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
A_MUTEX_LOCK(&encoder->run->mutex);
|
||||||
encoder->run->type = ENCODER_TYPE_CPU;
|
encoder->run->type = type;
|
||||||
encoder->run->quality = encoder->quality;
|
encoder->run->quality = quality;
|
||||||
|
if (cpu_forced) {
|
||||||
|
encoder->run->cpu_forced = true;
|
||||||
|
}
|
||||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma GCC diagnostic ignored "-Wunused-label"
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index) {
|
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index) {
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
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) {
|
if (encoder->run->type == ENCODER_TYPE_CPU) {
|
||||||
cpu_encoder_compress_buffer(dev, buf_index, encoder->quality);
|
cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality);
|
||||||
} else if (encoder->run->type == ENCODER_TYPE_HW) {
|
} 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_ENCODER
|
||||||
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 use_fallback;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
|
dev->run->pictures[buf_index].encode_end_time = get_now_monotonic();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||||
# pragma GCC diagnostic push
|
# pragma GCC diagnostic push
|
||||||
use_fallback:
|
error:
|
||||||
LOG_INFO("Error while compressing, falling back to CPU");
|
LOG_INFO("Error while compressing buffer, falling back to CPU");
|
||||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
A_MUTEX_LOCK(&encoder->run->mutex);
|
||||||
encoder->run->type = ENCODER_TYPE_CPU;
|
encoder->run->cpu_forced = true;
|
||||||
encoder->run->quality = encoder->quality;
|
|
||||||
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
A_MUTEX_UNLOCK(&encoder->run->mutex);
|
||||||
return -1;
|
return -1;
|
||||||
# pragma GCC diagnostic pop
|
# pragma GCC diagnostic pop
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "pthread.h"
|
#include "pthread.h"
|
||||||
|
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
@@ -51,6 +53,7 @@ enum encoder_type_t {
|
|||||||
struct encoder_runtime_t {
|
struct encoder_runtime_t {
|
||||||
enum encoder_type_t type;
|
enum encoder_type_t type;
|
||||||
unsigned quality;
|
unsigned quality;
|
||||||
|
bool cpu_forced;
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
|
|
||||||
# ifdef WITH_OMX_ENCODER
|
# ifdef WITH_OMX_ENCODER
|
||||||
@@ -74,6 +77,4 @@ enum encoder_type_t encoder_parse_type(const char *str);
|
|||||||
const char *encoder_type_to_string(enum encoder_type_t type);
|
const char *encoder_type_to_string(enum encoder_type_t type);
|
||||||
|
|
||||||
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev);
|
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev);
|
||||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev);
|
|
||||||
|
|
||||||
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index);
|
int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, unsigned worker_number, unsigned buf_index);
|
||||||
|
|||||||
@@ -105,8 +105,6 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
|
|||||||
|
|
||||||
# undef WRITE_SCANLINES
|
# undef WRITE_SCANLINES
|
||||||
|
|
||||||
// TODO: process jpeg errors:
|
|
||||||
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
|
|
||||||
jpeg_finish_compress(&jpeg);
|
jpeg_finish_compress(&jpeg);
|
||||||
jpeg_destroy_compress(&jpeg);
|
jpeg_destroy_compress(&jpeg);
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ static bool _is_huffman(const unsigned char *data);
|
|||||||
static size_t _memcpy_with_huffman(unsigned char *dest, const unsigned char *src, size_t size);
|
static size_t _memcpy_with_huffman(unsigned char *dest, const unsigned char *src, size_t size);
|
||||||
|
|
||||||
|
|
||||||
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality) {
|
int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
|
||||||
struct v4l2_jpegcompression comp;
|
struct v4l2_jpegcompression comp;
|
||||||
|
|
||||||
MEMSET_ZERO(comp);
|
MEMSET_ZERO(comp);
|
||||||
|
|||||||
@@ -25,5 +25,5 @@
|
|||||||
#include "../../device.h"
|
#include "../../device.h"
|
||||||
|
|
||||||
|
|
||||||
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality);
|
int hw_encoder_prepare(struct device_t *dev, unsigned quality);
|
||||||
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index);
|
void hw_encoder_compress_buffer(struct device_t *dev, unsigned index);
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
|
|||||||
free(omx);
|
free(omx);
|
||||||
}
|
}
|
||||||
|
|
||||||
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality) {
|
int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality) {
|
||||||
if (component_set_state(&omx->encoder, OMX_StateIdle) < 0) {
|
if (component_set_state(&omx->encoder, OMX_StateIdle) < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,5 +53,5 @@ struct omx_encoder_t {
|
|||||||
struct omx_encoder_t *omx_encoder_init();
|
struct omx_encoder_t *omx_encoder_init();
|
||||||
void omx_encoder_destroy(struct omx_encoder_t *omx);
|
void omx_encoder_destroy(struct omx_encoder_t *omx);
|
||||||
|
|
||||||
int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality);
|
int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigned quality);
|
||||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index);
|
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index);
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height) {
|
|||||||
|
|
||||||
jpeg_create_decompress(&jpeg);
|
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.err = jpeg_std_error((struct jpeg_error_mgr *)&jpeg_error);
|
||||||
jpeg_error.mgr.error_exit = _jpeg_error_handler;
|
jpeg_error.mgr.error_exit = _jpeg_error_handler;
|
||||||
if (setjmp(jpeg_error.jmp) < 0) {
|
if (setjmp(jpeg_error.jmp) < 0) {
|
||||||
|
|||||||
@@ -77,7 +77,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_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 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);
|
static bool _expose_blank_picture(struct http_server_t *server);
|
||||||
|
|
||||||
|
|
||||||
@@ -121,7 +121,10 @@ void http_server_destroy(struct http_server_t *server) {
|
|||||||
close(server->run->unix_fd);
|
close(server->run->unix_fd);
|
||||||
}
|
}
|
||||||
event_base_free(server->run->base);
|
event_base_free(server->run->base);
|
||||||
|
|
||||||
|
# if LIBEVENT_VERSION_NUMBER >= 0x02010100
|
||||||
libevent_global_shutdown();
|
libevent_global_shutdown();
|
||||||
|
# endif
|
||||||
|
|
||||||
for (struct stream_client_t *client = server->run->stream_clients; client != NULL;) {
|
for (struct stream_client_t *client = server->run->stream_clients; client != NULL;) {
|
||||||
struct stream_client_t *next = client->next;
|
struct stream_client_t *next = client->next;
|
||||||
@@ -199,19 +202,25 @@ int http_server_listen(struct http_server_t *server) {
|
|||||||
|
|
||||||
if (server->unix_path[0] != '\0') {
|
if (server->unix_path[0] != '\0') {
|
||||||
struct sockaddr_un unix_addr;
|
struct sockaddr_un unix_addr;
|
||||||
int unix_fd_flags;
|
|
||||||
|
|
||||||
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
||||||
|
|
||||||
assert((server->run->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0)));
|
# define MAX_SUN_PATH (sizeof(unix_addr.sun_path) - 1)
|
||||||
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);
|
|
||||||
|
|
||||||
strncpy(unix_addr.sun_path, server->unix_path, 107);
|
if (strlen(server->unix_path) > MAX_SUN_PATH) {
|
||||||
unix_addr.sun_path[107] = '\0';
|
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;
|
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 (server->unix_rm && unlink(server->unix_path) < 0) {
|
||||||
if (errno != ENOENT) {
|
if (errno != ENOENT) {
|
||||||
LOG_PERROR("Can't remove old UNIX socket '%s'", server->unix_path);
|
LOG_PERROR("Can't remove old UNIX socket '%s'", server->unix_path);
|
||||||
@@ -266,7 +275,6 @@ static bool _http_get_param_true(struct evkeyvalq *params, const char *key) {
|
|||||||
if ((value_str = evhttp_find_header(params, key)) != NULL) {
|
if ((value_str = evhttp_find_header(params, key)) != NULL) {
|
||||||
if (
|
if (
|
||||||
value_str[0] == '1'
|
value_str[0] == '1'
|
||||||
|| value_str[0] == 'y'
|
|
||||||
|| !evutil_ascii_strcasecmp(value_str, "true")
|
|| !evutil_ascii_strcasecmp(value_str, "true")
|
||||||
|| !evutil_ascii_strcasecmp(value_str, "yes")
|
|| !evutil_ascii_strcasecmp(value_str, "yes")
|
||||||
) {
|
) {
|
||||||
@@ -799,7 +807,7 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
|
|||||||
LOG_DEBUG("Refreshing HTTP exposed ...");
|
LOG_DEBUG("Refreshing HTTP exposed ...");
|
||||||
A_MUTEX_LOCK(&server->run->stream->mutex);
|
A_MUTEX_LOCK(&server->run->stream->mutex);
|
||||||
if (server->run->stream->picture.used > 0) { // If online
|
if (server->run->stream->picture.used > 0) { // If online
|
||||||
picture_updated = _expose_new_picture(server);
|
picture_updated = _expose_new_picture_unsafe(server);
|
||||||
UNLOCK_STREAM;
|
UNLOCK_STREAM;
|
||||||
} else {
|
} else {
|
||||||
UNLOCK_STREAM;
|
UNLOCK_STREAM;
|
||||||
@@ -817,7 +825,7 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
|
|||||||
_http_queue_send_stream(server, stream_updated, picture_updated);
|
_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 STREAM(_next) server->run->stream->_next
|
||||||
# define EXPOSED(_next) server->run->exposed->_next
|
# define EXPOSED(_next) server->run->exposed->_next
|
||||||
|
|
||||||
|
|||||||
@@ -123,10 +123,10 @@ pthread_mutex_t log_mutex;
|
|||||||
|
|
||||||
|
|
||||||
INLINE char *errno_to_string(char *buf, size_t size) {
|
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);
|
return strerror_r(errno, buf, size);
|
||||||
#else
|
# else
|
||||||
strerror_r(errno, buf, size);
|
strerror_r(errno, buf, size);
|
||||||
return buf;
|
return buf;
|
||||||
#endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/main.c
27
src/main.c
@@ -32,6 +32,10 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@@ -129,18 +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(" -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 queriyng and events processing.\n");
|
printf(" -t|--dv-timings ──────────────── Enable DV timings queriyng and events processing\n");
|
||||||
printf(" Supports automatic resolution changing. 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 intermediate thread.\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. Default: %u (== --buffers).\n\n", dev->n_workers);
|
printf(" -w|--workers <N> ─────────────── The number of worker threads but not more than buffers.\n");
|
||||||
|
printf(" Default: %u (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 affects to workers number.\n");
|
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(" 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(" --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("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");
|
||||||
@@ -376,6 +381,15 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
LOGGING_INIT;
|
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();
|
dev = device_init();
|
||||||
encoder = encoder_init();
|
encoder = encoder_init();
|
||||||
stream = stream_init(dev, encoder);
|
stream = stream_init(dev, encoder);
|
||||||
@@ -383,7 +397,6 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) {
|
if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) {
|
||||||
_install_signal_handlers();
|
_install_signal_handlers();
|
||||||
encoder_prepare(encoder, dev);
|
|
||||||
|
|
||||||
pthread_t stream_loop_tid;
|
pthread_t stream_loop_tid;
|
||||||
pthread_t server_loop_tid;
|
pthread_t server_loop_tid;
|
||||||
|
|||||||
504
src/stream.c
504
src/stream.c
@@ -20,6 +20,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -28,7 +29,6 @@
|
|||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <linux/videodev2.h>
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
@@ -38,16 +38,70 @@
|
|||||||
#include "encoder.h"
|
#include "encoder.h"
|
||||||
#include "stream.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);
|
|
||||||
|
|
||||||
static int _stream_init_loop(struct stream_t *stream, struct workers_pool_t *pool);
|
struct _worker_t {
|
||||||
static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool);
|
pthread_t tid;
|
||||||
|
unsigned number;
|
||||||
|
atomic_bool *proc_stop;
|
||||||
|
atomic_bool *workers_stop;
|
||||||
|
|
||||||
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool);
|
pthread_mutex_t last_comp_time_mutex;
|
||||||
static void *_stream_worker_thread(void *v_worker);
|
long double last_comp_time;
|
||||||
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;
|
||||||
|
|
||||||
|
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 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) {
|
||||||
@@ -74,20 +128,15 @@ void stream_destroy(struct stream_t *stream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void stream_loop(struct stream_t *stream) {
|
void stream_loop(struct stream_t *stream) {
|
||||||
struct workers_pool_t pool;
|
struct _workers_pool_t *pool;
|
||||||
|
|
||||||
MEMSET_ZERO(pool);
|
|
||||||
atomic_init(&pool.workers_stop, false);
|
|
||||||
pool.encoder = stream->encoder;
|
|
||||||
|
|
||||||
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
|
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
|
||||||
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
|
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
|
||||||
|
|
||||||
while (_stream_init_loop(stream, &pool) == 0) {
|
while ((pool = _stream_init_loop(stream)) != NULL) {
|
||||||
struct worker_t *oldest_worker = NULL;
|
|
||||||
struct worker_t *last_worker = NULL;
|
|
||||||
long double grab_after = 0;
|
long double grab_after = 0;
|
||||||
unsigned fluency_passed = 0;
|
unsigned fluency_passed = 0;
|
||||||
|
unsigned captured_fps = 0;
|
||||||
unsigned captured_fps_accum = 0;
|
unsigned captured_fps_accum = 0;
|
||||||
long long captured_fps_second = 0;
|
long long captured_fps_second = 0;
|
||||||
bool persistent_timeout_reported = false;
|
bool persistent_timeout_reported = false;
|
||||||
@@ -98,43 +147,22 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
LOG_INFO("Capturing ...");
|
LOG_INFO("Capturing ...");
|
||||||
|
|
||||||
while (!atomic_load(&stream->proc->stop)) {
|
while (!atomic_load(&stream->proc->stop)) {
|
||||||
int free_worker_number = -1;
|
struct _worker_t *ready_worker;
|
||||||
|
|
||||||
SEP_DEBUG('-');
|
SEP_DEBUG('-');
|
||||||
|
LOG_DEBUG("Waiting for worker ...");
|
||||||
|
|
||||||
LOG_DEBUG("Waiting for workers ...");
|
ready_worker = _workers_pool_wait(pool);
|
||||||
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);
|
|
||||||
|
|
||||||
if (oldest_worker && !atomic_load(&oldest_worker->has_job) && oldest_worker->buf_index >= 0) {
|
if (!ready_worker->job_failed) {
|
||||||
if (oldest_worker->job_failed) {
|
if (ready_worker->job_timely) {
|
||||||
break;
|
_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 {
|
} else {
|
||||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
break;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (atomic_load(&stream->proc->stop)) {
|
if (atomic_load(&stream->proc->stop)) {
|
||||||
@@ -145,30 +173,18 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
usleep(1000000);
|
usleep(1000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
# define INIT_FD_SET(_set) \
|
bool has_read;
|
||||||
fd_set _set; FD_ZERO(&_set); FD_SET(stream->dev->run->fd, &_set);
|
bool has_write;
|
||||||
|
bool has_error;
|
||||||
|
int selected = device_select(stream->dev, &has_read, &has_write, &has_error);
|
||||||
|
|
||||||
INIT_FD_SET(read_fds);
|
if (selected < 0) {
|
||||||
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 (errno != EINTR) {
|
if (errno != EINTR) {
|
||||||
LOG_PERROR("Mainloop select() error");
|
LOG_PERROR("Mainloop select() error");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (retval == 0) {
|
} else if (selected == 0) {
|
||||||
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 ...")
|
||||||
@@ -183,7 +199,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
} else {
|
} else {
|
||||||
persistent_timeout_reported = false;
|
persistent_timeout_reported = false;
|
||||||
|
|
||||||
if (FD_ISSET(stream->dev->run->fd, &read_fds)) {
|
if (has_read) {
|
||||||
LOG_DEBUG("Frame is ready");
|
LOG_DEBUG("Frame is ready");
|
||||||
|
|
||||||
int buf_index;
|
int buf_index;
|
||||||
@@ -193,7 +209,6 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
if ((buf_index = device_grab_buffer(stream->dev)) < 0) {
|
if ((buf_index = device_grab_buffer(stream->dev)) < 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
stream->dev->run->pictures[buf_index].grab_time = now;
|
|
||||||
|
|
||||||
// Workaround for broken, corrupted frames:
|
// Workaround for broken, corrupted frames:
|
||||||
// Under low light conditions corrupted frames may get captured.
|
// Under low light conditions corrupted frames may get captured.
|
||||||
@@ -201,7 +216,7 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
|
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
|
||||||
// corrupted frames are smaller.
|
// corrupted frames are smaller.
|
||||||
if (stream->dev->run->hw_buffers[buf_index].used < stream->dev->min_frame_size) {
|
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);
|
stream->dev->run->hw_buffers[buf_index].used);
|
||||||
goto pass_frame;
|
goto pass_frame;
|
||||||
}
|
}
|
||||||
@@ -215,50 +230,20 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
fluency_passed = 0;
|
fluency_passed = 0;
|
||||||
|
|
||||||
if (now_second != captured_fps_second) {
|
if (now_second != captured_fps_second) {
|
||||||
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("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;
|
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);
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
# define FREE_WORKER(_next) pool.workers[free_worker_number]._next
|
_workers_pool_assign(pool, ready_worker, buf_index);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
goto next_handlers; // Поток сам освободит буфер
|
goto next_handlers; // Поток сам освободит буфер
|
||||||
|
|
||||||
@@ -271,12 +256,12 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
|
|
||||||
next_handlers:
|
next_handlers:
|
||||||
|
|
||||||
if (FD_ISSET(stream->dev->run->fd, &write_fds)) {
|
if (has_write) {
|
||||||
LOG_ERROR("Got unexpected writing event, seems device was disconnected");
|
LOG_ERROR("Got unexpected writing event, seems device was disconnected");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FD_ISSET(stream->dev->run->fd, &error_fds)) {
|
if (has_error) {
|
||||||
LOG_INFO("Got V4L2 event");
|
LOG_INFO("Got V4L2 event");
|
||||||
if (device_consume_event(stream->dev) < 0) {
|
if (device_consume_event(stream->dev) < 0) {
|
||||||
break;
|
break;
|
||||||
@@ -292,11 +277,11 @@ void stream_loop(struct stream_t *stream) {
|
|||||||
stream->height = 0;
|
stream->height = 0;
|
||||||
atomic_store(&stream->updated, true);
|
atomic_store(&stream->updated, true);
|
||||||
A_MUTEX_UNLOCK(&stream->mutex);
|
A_MUTEX_UNLOCK(&stream->mutex);
|
||||||
}
|
|
||||||
|
|
||||||
_stream_destroy_workers(stream, &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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stream_loop_break(struct stream_t *stream) {
|
void stream_loop_break(struct stream_t *stream) {
|
||||||
@@ -307,7 +292,41 @@ void stream_switch_slowdown(struct stream_t *stream, bool slowdown) {
|
|||||||
atomic_store(&stream->proc->slowdown, 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
|
# define PICTURE(_next) stream->dev->run->pictures[buf_index]._next
|
||||||
|
|
||||||
A_MUTEX_LOCK(&stream->mutex);
|
A_MUTEX_LOCK(&stream->mutex);
|
||||||
@@ -323,6 +342,7 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
|
|||||||
|
|
||||||
stream->width = stream->dev->run->width;
|
stream->width = stream->dev->run->width;
|
||||||
stream->height = stream->dev->run->height;
|
stream->height = stream->dev->run->height;
|
||||||
|
stream->captured_fps = captured_fps;
|
||||||
atomic_store(&stream->updated, true);
|
atomic_store(&stream->updated, true);
|
||||||
|
|
||||||
A_MUTEX_UNLOCK(&stream->mutex);
|
A_MUTEX_UNLOCK(&stream->mutex);
|
||||||
@@ -330,118 +350,98 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
|
|||||||
# undef PICTURE
|
# undef PICTURE
|
||||||
}
|
}
|
||||||
|
|
||||||
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool) {
|
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream) {
|
||||||
long double sum_comp_time = 0;
|
struct _workers_pool_t *pool;
|
||||||
long double avg_comp_time;
|
|
||||||
long double min_delay;
|
|
||||||
long double soft_delay;
|
|
||||||
|
|
||||||
for (unsigned number = 0; number < dev->n_workers; ++number) {
|
LOG_INFO("Creating pool with %u workers ...", stream->dev->run->n_workers);
|
||||||
# define WORKER(_next) pool->workers[number]._next
|
|
||||||
|
|
||||||
A_MUTEX_LOCK(&WORKER(last_comp_time_mutex));
|
A_CALLOC(pool, 1);
|
||||||
if (WORKER(last_comp_time) > 0) {
|
|
||||||
sum_comp_time += WORKER(last_comp_time);
|
|
||||||
}
|
|
||||||
A_MUTEX_UNLOCK(&WORKER(last_comp_time_mutex));
|
|
||||||
|
|
||||||
# undef WORKER
|
pool->n_workers = stream->dev->run->n_workers;
|
||||||
}
|
A_CALLOC(pool->workers, pool->n_workers);
|
||||||
avg_comp_time = sum_comp_time / dev->n_workers; // Среднее время работы воркеров
|
|
||||||
|
|
||||||
min_delay = avg_comp_time / dev->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_live(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->n_workers);
|
|
||||||
|
|
||||||
atomic_store(&pool->workers_stop, false);
|
|
||||||
A_CALLOC(pool->workers, stream->dev->n_workers);
|
|
||||||
|
|
||||||
A_MUTEX_INIT(&pool->free_workers_mutex);
|
A_MUTEX_INIT(&pool->free_workers_mutex);
|
||||||
A_COND_INIT(&pool->free_workers_cond);
|
A_COND_INIT(&pool->free_workers_cond);
|
||||||
|
|
||||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
atomic_init(&pool->workers_stop, false);
|
||||||
# define WORKER(_next) pool->workers[number]._next
|
|
||||||
|
|
||||||
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));
|
A_MUTEX_INIT(&WORKER(has_job_mutex));
|
||||||
atomic_init(&WORKER(has_job), false);
|
atomic_init(&WORKER(has_job), false);
|
||||||
A_COND_INIT(&WORKER(has_job_cond));
|
A_COND_INIT(&WORKER(has_job_cond));
|
||||||
|
|
||||||
WORKER(number) = number;
|
WORKER(number) = number;
|
||||||
WORKER(proc_stop) = &stream->proc->stop;
|
WORKER(proc_stop) = &stream->proc->stop;
|
||||||
WORKER(workers_stop) = &pool->workers_stop;
|
WORKER(workers_stop) = &pool->workers_stop;
|
||||||
|
|
||||||
WORKER(free_workers_mutex) = &pool->free_workers_mutex;
|
WORKER(free_workers_mutex) = &pool->free_workers_mutex;
|
||||||
WORKER(free_workers) = &pool->free_workers;
|
WORKER(free_workers) = &pool->free_workers;
|
||||||
WORKER(free_workers_cond) = &pool->free_workers_cond;
|
WORKER(free_workers_cond) = &pool->free_workers_cond;
|
||||||
|
|
||||||
WORKER(dev) = stream->dev;
|
WORKER(dev) = stream->dev;
|
||||||
WORKER(encoder) = pool->encoder;
|
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
|
# undef WORKER
|
||||||
}
|
}
|
||||||
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *_stream_worker_thread(void *v_worker) {
|
static void _workers_pool_destroy(struct _workers_pool_t *pool) {
|
||||||
struct worker_t *worker = (struct worker_t *)v_worker;
|
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);
|
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)) {
|
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;
|
||||||
|
|
||||||
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);
|
||||||
A_MUTEX_UNLOCK(&worker->has_job_mutex);
|
A_MUTEX_UNLOCK(&worker->has_job_mutex);
|
||||||
@@ -451,11 +451,11 @@ static void *_stream_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);
|
||||||
|
|
||||||
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) {
|
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) {
|
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
|
||||||
worker->job_start_time = PICTURE(encode_begin_time);
|
worker->job_start_time = PICTURE(encode_begin_time);
|
||||||
@@ -484,34 +484,96 @@ static void *_stream_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;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool_t *pool) {
|
static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool) {
|
||||||
if (pool->workers) {
|
struct _worker_t *ready_worker = NULL;
|
||||||
LOG_INFO("Destroying workers ...");
|
|
||||||
|
|
||||||
atomic_store(&pool->workers_stop, true);
|
A_MUTEX_LOCK(&pool->free_workers_mutex);
|
||||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
A_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex);
|
||||||
# define WORKER(_next) pool->workers[number]._next
|
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
|
||||||
|
|
||||||
A_MUTEX_LOCK(&WORKER(has_job_mutex));
|
if (pool->oldest_worker && !atomic_load(&pool->oldest_worker->has_job) && pool->oldest_worker->buf_index >= 0) {
|
||||||
atomic_store(&WORKER(has_job), true); // Final job: die
|
ready_worker = pool->oldest_worker;
|
||||||
A_MUTEX_UNLOCK(&WORKER(has_job_mutex));
|
ready_worker->job_timely = true;
|
||||||
A_COND_SIGNAL(&WORKER(has_job_cond));
|
pool->oldest_worker = pool->oldest_worker->order_next;
|
||||||
|
} else {
|
||||||
A_THREAD_JOIN(WORKER(tid));
|
for (unsigned number = 0; number < pool->n_workers; ++number) {
|
||||||
A_MUTEX_DESTROY(&WORKER(has_job_mutex));
|
if (
|
||||||
A_COND_DESTROY(&WORKER(has_job_cond));
|
!atomic_load(&pool->workers[number].has_job) && (
|
||||||
|
ready_worker == NULL
|
||||||
# undef WORKER
|
|| ready_worker->job_start_time < pool->workers[number].job_start_time
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ready_worker = &pool->workers[number];
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
assert(ready_worker != NULL);
|
||||||
A_MUTEX_DESTROY(&pool->free_workers_mutex);
|
ready_worker->job_timely = false; // Освободился воркер, получивший задание позже (или самый первый при самом первом захвате)
|
||||||
A_COND_DESTROY(&pool->free_workers_cond);
|
|
||||||
|
|
||||||
free(pool->workers);
|
|
||||||
}
|
}
|
||||||
pool->free_workers = 0;
|
return ready_worker;
|
||||||
pool->workers = NULL;
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
long double sum_comp_time = 0;
|
||||||
|
long double avg_comp_time;
|
||||||
|
long double min_delay;
|
||||||
|
long double soft_delay;
|
||||||
|
|
||||||
|
for (unsigned number = 0; number < pool->n_workers; ++number) {
|
||||||
|
# define WORKER(_next) pool->workers[number]._next
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
# undef WORKER
|
||||||
|
}
|
||||||
|
|
||||||
|
avg_comp_time = sum_comp_time / pool->n_workers; // Среднее время работы воркеров
|
||||||
|
|
||||||
|
min_delay = avg_comp_time / pool->n_workers; // Среднее время работы размазывается на N воркеров
|
||||||
|
|
||||||
|
if (pool->desired_frames_interval > 0 && min_delay > 0) {
|
||||||
|
// Искусственное время задержки на основе желаемого FPS, если включен --desired-fps
|
||||||
|
soft_delay = pool->desired_frames_interval - sum_comp_time;
|
||||||
|
return (min_delay > soft_delay ? min_delay : soft_delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return min_delay;
|
||||||
}
|
}
|
||||||
|
|||||||
41
src/stream.h
41
src/stream.h
@@ -22,53 +22,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "encoder.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 {
|
struct process_t {
|
||||||
atomic_bool stop;
|
atomic_bool stop;
|
||||||
atomic_bool slowdown;
|
atomic_bool slowdown;
|
||||||
|
|||||||
Reference in New Issue
Block a user