mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-20 08:46:31 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8637ff5c09 | ||
|
|
bd5cf7d3de | ||
|
|
e488eec90c | ||
|
|
6615a23361 | ||
|
|
a91eba8d90 | ||
|
|
d5aebc1231 | ||
|
|
5f4f46bbe6 | ||
|
|
61a2fe6546 | ||
|
|
98499b6604 | ||
|
|
f52d090f9b | ||
|
|
ebb3df46b9 | ||
|
|
8fd6659cf1 | ||
|
|
13ce0bbc63 | ||
|
|
0add4cc25f | ||
|
|
96d84b33bd | ||
|
|
f2dfe7641e | ||
|
|
97403cbb75 | ||
|
|
0663bb1035 | ||
|
|
06f4017a5b | ||
|
|
6493a62c6c | ||
|
|
66c627c682 | ||
|
|
8d6f5f7f8f | ||
|
|
f1cdfd4223 | ||
|
|
972c288df3 | ||
|
|
fd1d2dec71 | ||
|
|
331bd181bf | ||
|
|
231ff37570 | ||
|
|
cec0b203b3 | ||
|
|
e5c9d699a3 | ||
|
|
198fbc1756 | ||
|
|
55aa443d68 | ||
|
|
44d6288416 | ||
|
|
aeae342853 | ||
|
|
d22034da96 | ||
|
|
43788f812d | ||
|
|
95318e14d8 | ||
|
|
67d6f15776 | ||
|
|
6c2353ce2c | ||
|
|
fcdfb2930a |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 1.14
|
||||
current_version = 1.19
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
@@ -17,4 +17,3 @@ replace = pkgver={new_version}
|
||||
[bumpversion:file:pkg/openwrt/Makefile]
|
||||
search = PKG_VERSION:={current_version}
|
||||
replace = PKG_VERSION:={new_version}
|
||||
|
||||
|
||||
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: pikvm
|
||||
custom: https://www.paypal.me/mdevaev
|
||||
20
.github/workflows/dockerimage.yml
vendored
Normal file
20
.github/workflows/dockerimage.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Building linters image ...
|
||||
run: make linters
|
||||
|
||||
- name: Running linters ...
|
||||
run: make tox
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
/linters/.tox/
|
||||
/linters/.mypy_cache/
|
||||
/pkg/arch/pkg/
|
||||
/pkg/arch/src/
|
||||
/pkg/arch/v*.tar.gz
|
||||
|
||||
28
Makefile
28
Makefile
@@ -13,6 +13,8 @@ RPI_VC_LIBS ?= /opt/vc/lib
|
||||
|
||||
BUILD ?= build
|
||||
|
||||
LINTERS_IMAGE ?= $(PROG)-linters
|
||||
|
||||
|
||||
# =====
|
||||
_LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
|
||||
@@ -92,12 +94,31 @@ $(BUILD)/%.o: %.c
|
||||
|
||||
release:
|
||||
make clean
|
||||
make tox
|
||||
make push
|
||||
make bump
|
||||
make push
|
||||
make clean
|
||||
|
||||
|
||||
tox: linters
|
||||
time docker run --rm \
|
||||
--volume `pwd`:/src:ro \
|
||||
--volume `pwd`/linters:/src/linters:rw \
|
||||
-t $(LINTERS_IMAGE) bash -c " \
|
||||
cd /src \
|
||||
&& tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \
|
||||
"
|
||||
|
||||
|
||||
linters:
|
||||
docker build \
|
||||
$(if $(call optbool,$(NC)),--no-cache,) \
|
||||
--rm \
|
||||
--tag $(LINTERS_IMAGE) \
|
||||
-f linters/Dockerfile linters
|
||||
|
||||
|
||||
bump:
|
||||
bumpversion $(if $(V),$(V),minor)
|
||||
|
||||
@@ -107,7 +128,12 @@ push:
|
||||
git push --tags
|
||||
|
||||
|
||||
clean-all: clean
|
||||
clean-all: linters clean
|
||||
- docker run --rm \
|
||||
--volume `pwd`:/src \
|
||||
-it $(LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
|
||||
clean:
|
||||
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
|
||||
rm -rf $(PROG) $(BUILD) vgcore.* *.sock
|
||||
|
||||
.PHONY: linters
|
||||
|
||||
10
README.md
10
README.md
@@ -1,4 +1,7 @@
|
||||
# µStreamer
|
||||
[](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
|
||||
[](https://discord.gg/bpmXfz5)
|
||||
|
||||
[[Русская версия]](README.ru.md)
|
||||
|
||||
µStreamer is a lightweight and very quick server to broadcast [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
|
||||
@@ -33,7 +36,7 @@ If you're going to live-stream from your backyard webcam and need to control it,
|
||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` and ```libbsd``` (only for Linux).
|
||||
|
||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||
* Raspbian: `sudo apt install libevent-dev libevent-pthreads-2.1-6-dev libjpeg8-dev uuid-dev libbsd-dev`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`.
|
||||
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
|
||||
|
||||
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [wiringPi](http://wiringpi.com) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
|
||||
@@ -69,8 +72,9 @@ $ ./ustreamer \
|
||||
You can always view the full list of options with ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Tips
|
||||
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16)
|
||||
# See also
|
||||
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
|
||||
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Use [Ansible](https://docs.ansible.com/ansible/latest/index.html) to compile uStreamer and install it as a systemd service automatically.
|
||||
|
||||
-----
|
||||
# License
|
||||
|
||||
12
README.ru.md
12
README.ru.md
@@ -1,4 +1,7 @@
|
||||
# µStreamer
|
||||
# µStreamer
|
||||
[](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
|
||||
[](https://discord.gg/bpmXfz5)
|
||||
|
||||
[[English version]](README.md)
|
||||
|
||||
|
||||
@@ -33,7 +36,7 @@
|
||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux).
|
||||
|
||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||
* Raspbian: `sudo apt install libevent-dev libevent-pthreads-2.1-6-dev libjpeg8-dev uuid-dev libbsd-dev`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`.
|
||||
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev uuid-dev libbsd-dev`.
|
||||
|
||||
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [wiringPi](http://wiringpi.com) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.
|
||||
@@ -69,8 +72,9 @@ $ ./ustreamer \
|
||||
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Советы
|
||||
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16)
|
||||
# Смотрите также
|
||||
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
|
||||
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Использование [Ansible](https://docs.ansible.com/ansible/latest/index.html) для сборки и установки стримера как systemd-сервиса.
|
||||
|
||||
-----
|
||||
# Лицензия
|
||||
|
||||
2
linters/.dockerignore
Normal file
2
linters/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
/.tox/
|
||||
/.mypy_cache/
|
||||
21
linters/Dockerfile
Normal file
21
linters/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM archlinux/base
|
||||
|
||||
RUN echo "Server = http://mirror.yandex.ru/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist
|
||||
|
||||
RUN pacman -Syu --noconfirm \
|
||||
&& pacman -S --needed --noconfirm \
|
||||
base \
|
||||
base-devel \
|
||||
vim \
|
||||
git \
|
||||
libjpeg \
|
||||
libevent \
|
||||
libutil-linux \
|
||||
libbsd \
|
||||
python \
|
||||
python-pip \
|
||||
python-tox \
|
||||
cppcheck \
|
||||
&& (pacman -Sc --noconfirm || true)
|
||||
|
||||
CMD /bin/bash
|
||||
9
linters/flake8.ini
Normal file
9
linters/flake8.ini
Normal file
@@ -0,0 +1,9 @@
|
||||
[flake8]
|
||||
inline-quotes = double
|
||||
max-line-length = 160
|
||||
ignore = W503, E227, E241, E252, Q003
|
||||
# W503 line break before binary operator
|
||||
# E227 missing whitespace around bitwise or shift operator
|
||||
# E241 multiple spaces after
|
||||
# E252 missing whitespace around parameter equals
|
||||
# Q003 Change outer quotes to avoid escaping inner quotes
|
||||
5
linters/mypy.ini
Normal file
5
linters/mypy.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[mypy]
|
||||
python_version = 3.8
|
||||
ignore_missing_imports = true
|
||||
disallow_untyped_defs = true
|
||||
strict_optional = true
|
||||
63
linters/pylint.ini
Normal file
63
linters/pylint.ini
Normal file
@@ -0,0 +1,63 @@
|
||||
[MASTER]
|
||||
ignore = .git
|
||||
|
||||
[DESIGN]
|
||||
min-public-methods = 0
|
||||
max-args = 10
|
||||
|
||||
[TYPECHECK]
|
||||
ignored-classes=
|
||||
AioQueue,
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable =
|
||||
file-ignored,
|
||||
locally-disabled,
|
||||
fixme,
|
||||
missing-docstring,
|
||||
no-init,
|
||||
no-self-use,
|
||||
superfluous-parens,
|
||||
abstract-class-not-used,
|
||||
abstract-class-little-used,
|
||||
duplicate-code,
|
||||
bad-continuation,
|
||||
bad-whitespace,
|
||||
star-args,
|
||||
broad-except,
|
||||
redundant-keyword-arg,
|
||||
wrong-import-order,
|
||||
too-many-ancestors,
|
||||
no-else-return,
|
||||
len-as-condition,
|
||||
|
||||
[REPORTS]
|
||||
msg-template = {symbol} -- {path}:{line}({obj}): {msg}
|
||||
|
||||
[FORMAT]
|
||||
max-line-length = 160
|
||||
|
||||
[BASIC]
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions =
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names = _, __, x, y, ws, make-html-h, make-jpeg-h
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx = [a-z_][a-z0-9_]{2,50}$
|
||||
|
||||
# Regular expression matching correct function names
|
||||
function-rgx = [a-z_][a-z0-9_]{2,50}$
|
||||
|
||||
# Regular expression which should only match correct module level names
|
||||
const-rgx = ([a-zA-Z_][a-zA-Z0-9_]*)$
|
||||
|
||||
# Regular expression which should only match correct argument names
|
||||
argument-rgx = [a-z_][a-z0-9_]{1,30}$
|
||||
|
||||
# Regular expression which should only match correct variable names
|
||||
variable-rgx = [a-z_][a-z0-9_]{1,30}$
|
||||
|
||||
# Regular expression which should only match correct instance attribute names
|
||||
attr-rgx = [a-z_][a-z0-9_]{1,30}$
|
||||
46
linters/tox.ini
Normal file
46
linters/tox.ini
Normal file
@@ -0,0 +1,46 @@
|
||||
[tox]
|
||||
envlist = cppcheck, flake8, pylint, mypy, vulture
|
||||
skipsdist = true
|
||||
|
||||
[testenv]
|
||||
basepython = python3.8
|
||||
changedir = /src
|
||||
|
||||
[testenv:cppcheck]
|
||||
whitelist_externals = cppcheck
|
||||
commands = cppcheck \
|
||||
--force \
|
||||
--std=c11 \
|
||||
--error-exitcode=1 \
|
||||
--quiet \
|
||||
--enable=warning,unusedFunction,portability,performance,style \
|
||||
--suppress=assignmentInAssert \
|
||||
--suppress=variableScope \
|
||||
--inline-suppr \
|
||||
-DCHAR_BIT=8 \
|
||||
src
|
||||
|
||||
[testenv:flake8]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py'
|
||||
deps =
|
||||
flake8
|
||||
flake8-quotes
|
||||
|
||||
[testenv:pylint]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py'
|
||||
deps =
|
||||
pylint
|
||||
|
||||
[testenv:mypy]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py'
|
||||
deps =
|
||||
mypy
|
||||
|
||||
[testenv:vulture]
|
||||
whitelist_externals = bash
|
||||
commands = bash -c 'vulture tools/*.py'
|
||||
deps =
|
||||
vulture
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=1.14
|
||||
pkgver=1.19
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=1.14
|
||||
PKG_VERSION:=1.19
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef VERSION
|
||||
# define VERSION "1.14"
|
||||
# define VERSION "1.19"
|
||||
#endif
|
||||
|
||||
121
src/device.c
121
src/device.c
@@ -67,13 +67,23 @@ static const struct {
|
||||
{"JPEG", V4L2_PIX_FMT_JPEG},
|
||||
};
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
const enum v4l2_memory io_method;
|
||||
} _IO_METHODS[] = {
|
||||
{"MMAP", V4L2_MEMORY_MMAP},
|
||||
{"USERPTR", V4L2_MEMORY_USERPTR},
|
||||
};
|
||||
|
||||
|
||||
static int _device_open_check_cap(struct device_t *dev);
|
||||
static int _device_open_dv_timings(struct device_t *dev);
|
||||
static int _device_apply_dv_timings(struct device_t *dev);
|
||||
static int _device_open_format(struct device_t *dev);
|
||||
static void _device_open_hw_fps(struct device_t *dev);
|
||||
static int _device_open_mmap(struct device_t *dev);
|
||||
static int _device_open_io_method(struct device_t *dev);
|
||||
static int _device_open_io_method_mmap(struct device_t *dev);
|
||||
static int _device_open_io_method_userptr(struct device_t *dev);
|
||||
static int _device_open_queue_buffers(struct device_t *dev);
|
||||
static void _device_open_alloc_picbufs(struct device_t *dev);
|
||||
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height);
|
||||
@@ -86,6 +96,7 @@ static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned for
|
||||
static const char *_format_to_string_nullable(unsigned format);
|
||||
static const char *_format_to_string_supported(unsigned format);
|
||||
static const char *_standard_to_string(v4l2_std_id standard);
|
||||
static const char *_io_method_to_string_supported(enum v4l2_memory io_method);
|
||||
|
||||
|
||||
struct device_t *device_init(void) {
|
||||
@@ -111,6 +122,7 @@ struct device_t *device_init(void) {
|
||||
dev->n_workers = min_u(cores_available, dev->n_buffers);
|
||||
dev->timeout = 1;
|
||||
dev->error_delay = 1;
|
||||
dev->io_method = V4L2_MEMORY_MMAP;
|
||||
dev->run = run;
|
||||
return dev;
|
||||
}
|
||||
@@ -138,6 +150,15 @@ v4l2_std_id device_parse_standard(const char *str) {
|
||||
return STANDARD_UNKNOWN;
|
||||
}
|
||||
|
||||
int device_parse_io_method(const char *str) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) {
|
||||
if (!strcasecmp(str, _IO_METHODS[index].name)) {
|
||||
return _IO_METHODS[index].io_method;
|
||||
}
|
||||
}
|
||||
return IO_METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
int device_open(struct device_t *dev) {
|
||||
if ((dev->run->fd = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
|
||||
LOG_PERROR("Can't open device");
|
||||
@@ -155,7 +176,7 @@ int device_open(struct device_t *dev) {
|
||||
goto error;
|
||||
}
|
||||
_device_open_hw_fps(dev);
|
||||
if (_device_open_mmap(dev) < 0) {
|
||||
if (_device_open_io_method(dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_device_open_queue_buffers(dev) < 0) {
|
||||
@@ -187,13 +208,19 @@ void device_close(struct device_t *dev) {
|
||||
}
|
||||
|
||||
if (dev->run->hw_buffers) {
|
||||
LOG_DEBUG("Unmapping HW buffers ...");
|
||||
LOG_DEBUG("Releasing HW buffers ...");
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
|
||||
|
||||
if (HW_BUFFER(allocated) > 0 && HW_BUFFER(data) != MAP_FAILED) {
|
||||
if (munmap(HW_BUFFER(data), HW_BUFFER(allocated)) < 0) {
|
||||
LOG_PERROR("Can't unmap device buffer %u", index);
|
||||
if (dev->io_method == V4L2_MEMORY_MMAP) {
|
||||
if (HW_BUFFER(allocated) > 0 && HW_BUFFER(data) != MAP_FAILED) {
|
||||
if (munmap(HW_BUFFER(data), HW_BUFFER(allocated)) < 0) {
|
||||
LOG_PERROR("Can't unmap device buffer %u", index);
|
||||
}
|
||||
}
|
||||
} else { // V4L2_MEMORY_USERPTR
|
||||
if (HW_BUFFER(data)) {
|
||||
free(HW_BUFFER(data));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,9 +284,9 @@ int device_select(struct device_t *dev, bool *has_read, bool *has_write, bool *h
|
||||
*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;
|
||||
*has_read = false;
|
||||
*has_write = false;
|
||||
*has_error = false;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Device select() --> %d", retval);
|
||||
@@ -271,7 +298,7 @@ int device_grab_buffer(struct device_t *dev) {
|
||||
|
||||
MEMSET_ZERO(buf_info);
|
||||
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf_info.memory = V4L2_MEMORY_MMAP;
|
||||
buf_info.memory = dev->io_method;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_DQBUF, &buf_info) < 0) {
|
||||
@@ -465,6 +492,8 @@ static int _device_open_format(struct device_t *dev) {
|
||||
|
||||
dev->run->format = fmt.fmt.pix.pixelformat;
|
||||
LOG_INFO("Using pixelformat: %s", _format_to_string_supported(dev->run->format));
|
||||
|
||||
dev->run->raw_size = fmt.fmt.pix.sizeimage; // Only for userptr
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -510,7 +539,17 @@ static void _device_open_hw_fps(struct device_t *dev) {
|
||||
# undef SETFPS_TPF
|
||||
}
|
||||
|
||||
static int _device_open_mmap(struct device_t *dev) {
|
||||
static int _device_open_io_method(struct device_t *dev) {
|
||||
LOG_INFO("Using IO method: %s", _io_method_to_string_supported(dev->io_method));
|
||||
switch (dev->io_method) {
|
||||
case V4L2_MEMORY_MMAP: return _device_open_io_method_mmap(dev);
|
||||
case V4L2_MEMORY_USERPTR: return _device_open_io_method_userptr(dev);
|
||||
default: assert(0 && "Unsupported IO method");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int _device_open_io_method_mmap(struct device_t *dev) {
|
||||
struct v4l2_requestbuffers req;
|
||||
|
||||
MEMSET_ZERO(req);
|
||||
@@ -518,9 +557,9 @@ static int _device_open_mmap(struct device_t *dev) {
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) ...");
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_MMAP ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support memory mapping", dev->path);
|
||||
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_MMAP", dev->path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -563,14 +602,56 @@ static int _device_open_mmap(struct device_t *dev) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _device_open_io_method_userptr(struct device_t *dev) {
|
||||
struct v4l2_requestbuffers req;
|
||||
unsigned page_size = getpagesize();
|
||||
unsigned buf_size = align_size(dev->run->raw_size, page_size);
|
||||
|
||||
MEMSET_ZERO(req);
|
||||
req.count = dev->n_buffers;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_USERPTR;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_USERPTR ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_USERPTR", dev->path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (req.count < 1) {
|
||||
LOG_ERROR("Insufficient buffer memory: %u", req.count);
|
||||
return -1;
|
||||
} else {
|
||||
LOG_INFO("Requested %u HW buffers, got %u", dev->n_buffers, req.count);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Allocating HW buffers ...");
|
||||
|
||||
A_CALLOC(dev->run->hw_buffers, req.count);
|
||||
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
|
||||
# define HW_BUFFER(_next) dev->run->hw_buffers[dev->run->n_buffers]._next
|
||||
|
||||
assert(HW_BUFFER(data) = aligned_alloc(page_size, buf_size));
|
||||
memset(HW_BUFFER(data), 0, buf_size);
|
||||
HW_BUFFER(allocated) = buf_size;
|
||||
|
||||
# undef HW_BUFFER
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _device_open_queue_buffers(struct device_t *dev) {
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
struct v4l2_buffer buf_info;
|
||||
|
||||
MEMSET_ZERO(buf_info);
|
||||
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf_info.memory = V4L2_MEMORY_MMAP;
|
||||
buf_info.memory = dev->io_method;
|
||||
buf_info.index = index;
|
||||
if (dev->io_method == V4L2_MEMORY_USERPTR) {
|
||||
buf_info.m.userptr = (unsigned long)dev->run->hw_buffers[index].data;
|
||||
buf_info.length = dev->run->hw_buffers[index].allocated;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %u ...", index);
|
||||
if (xioctl(dev->run->fd, VIDIOC_QBUF, &buf_info) < 0) {
|
||||
@@ -663,6 +744,7 @@ static void _device_apply_controls(struct device_t *dev) {
|
||||
}
|
||||
|
||||
static int _device_query_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, bool quiet) {
|
||||
// cppcheck-suppress redundantPointerOp
|
||||
MEMSET_ZERO(*query);
|
||||
query->id = cid;
|
||||
|
||||
@@ -705,7 +787,7 @@ static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned for
|
||||
buf[1] = (format >> 8) & 0x7F;
|
||||
buf[2] = (format >> 16) & 0x7F;
|
||||
buf[3] = (format >> 24) & 0x7F;
|
||||
if (format & (1 << 31)) {
|
||||
if (format & ((unsigned)1 << 31)) {
|
||||
buf[4] = '-';
|
||||
buf[5] = 'B';
|
||||
buf[6] = 'E';
|
||||
@@ -738,3 +820,12 @@ static const char *_standard_to_string(v4l2_std_id standard) {
|
||||
}
|
||||
return _STANDARDS[0].name;
|
||||
}
|
||||
|
||||
static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) {
|
||||
if (io_method == _IO_METHODS[index].io_method) {
|
||||
return _IO_METHODS[index].name;
|
||||
}
|
||||
}
|
||||
return "unsupported";
|
||||
}
|
||||
|
||||
16
src/device.h
16
src/device.h
@@ -30,13 +30,13 @@
|
||||
#include "picture.h"
|
||||
|
||||
|
||||
#define VIDEO_MIN_WIDTH 160
|
||||
#define VIDEO_MAX_WIDTH 10240
|
||||
#define VIDEO_MIN_WIDTH ((unsigned)160)
|
||||
#define VIDEO_MAX_WIDTH ((unsigned)10240)
|
||||
|
||||
#define VIDEO_MIN_HEIGHT 120
|
||||
#define VIDEO_MAX_HEIGHT 4320
|
||||
#define VIDEO_MIN_HEIGHT ((unsigned)120)
|
||||
#define VIDEO_MAX_HEIGHT ((unsigned)4320)
|
||||
|
||||
#define VIDEO_MAX_FPS 120
|
||||
#define VIDEO_MAX_FPS ((unsigned)120)
|
||||
|
||||
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
|
||||
#define STANDARDS_STR "PAL, NTSC, SECAM"
|
||||
@@ -44,6 +44,9 @@
|
||||
#define FORMAT_UNKNOWN -1
|
||||
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, JPEG"
|
||||
|
||||
#define IO_METHOD_UNKNOWN -1
|
||||
#define IO_METHODS_STR "MMAP, USERPTR"
|
||||
|
||||
|
||||
struct hw_buffer_t {
|
||||
unsigned char *data;
|
||||
@@ -57,6 +60,7 @@ struct device_runtime_t {
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
size_t raw_size;
|
||||
unsigned n_buffers;
|
||||
unsigned n_workers;
|
||||
struct hw_buffer_t *hw_buffers;
|
||||
@@ -95,6 +99,7 @@ struct device_t {
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
v4l2_std_id standard;
|
||||
enum v4l2_memory io_method;
|
||||
bool dv_timings;
|
||||
unsigned n_buffers;
|
||||
unsigned n_workers;
|
||||
@@ -115,6 +120,7 @@ void device_destroy(struct device_t *dev);
|
||||
|
||||
int device_parse_format(const char *str);
|
||||
v4l2_std_id device_parse_standard(const char *str);
|
||||
int device_parse_io_method(const char *str);
|
||||
|
||||
int device_open(struct device_t *dev);
|
||||
void device_close(struct device_t *dev);
|
||||
|
||||
@@ -170,6 +170,7 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
// cppcheck-suppress unusedLabel
|
||||
force_cpu:
|
||||
cpu_forced = true;
|
||||
# pragma GCC diagnostic pop
|
||||
@@ -225,6 +226,7 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
// cppcheck-suppress unusedLabel
|
||||
error:
|
||||
LOG_INFO("Error while compressing buffer, falling back to CPU");
|
||||
A_MUTEX_LOCK(&encoder->run->mutex);
|
||||
|
||||
@@ -33,9 +33,10 @@
|
||||
|
||||
# define ENCODER_TYPES_OMX_HINT ", OMX"
|
||||
|
||||
# ifndef MAX_GLITCHED_RESOLUTIONS
|
||||
# define MAX_GLITCHED_RESOLUTIONS 1024
|
||||
# ifndef CFG_MAX_GLITCHED_RESOLUTIONS
|
||||
# define CFG_MAX_GLITCHED_RESOLUTIONS 1024
|
||||
# endif
|
||||
# define MAX_GLITCHED_RESOLUTIONS ((unsigned)(CFG_MAX_GLITCHED_RESOLUTIONS))
|
||||
#else
|
||||
# define ENCODER_TYPES_OMX_HINT ""
|
||||
#endif
|
||||
|
||||
@@ -258,7 +258,7 @@ static void _jpeg_write_scanlines_rgb24(
|
||||
}
|
||||
}
|
||||
|
||||
#define JPEG_OUTPUT_BUFFER_SIZE 4096
|
||||
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
||||
|
||||
@@ -61,6 +61,7 @@ int component_disable_port(OMX_HANDLETYPE *component, OMX_U32 port) {
|
||||
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port) {
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
// cppcheck-suppress redundantPointerOp
|
||||
OMX_INIT_STRUCTURE(*portdef);
|
||||
portdef->nPortIndex = port;
|
||||
|
||||
|
||||
@@ -305,9 +305,7 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
|
||||
portdef.format.image.nFrameWidth = dev->run->width;
|
||||
portdef.format.image.nFrameHeight = dev->run->height;
|
||||
portdef.format.image.nStride = 0;
|
||||
# define ALIGN_HEIGHT(_x, _y) (((_x) + ((_y) - 1)) & ~((_y) - 1))
|
||||
portdef.format.image.nSliceHeight = ALIGN_HEIGHT(dev->run->height, 16);
|
||||
# undef ALIGN_HEIGHT
|
||||
portdef.format.image.nSliceHeight = align_size(dev->run->height, 16);
|
||||
portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
|
||||
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused;
|
||||
portdef.nBufferSize = picture_get_generous_size(dev->run->width, dev->run->height);
|
||||
|
||||
@@ -29,9 +29,10 @@
|
||||
|
||||
#include "../../device.h"
|
||||
|
||||
#ifndef OMX_MAX_ENCODERS
|
||||
# define OMX_MAX_ENCODERS 3 // Raspberry Pi limitation
|
||||
#ifndef CFG_OMX_MAX_ENCODERS
|
||||
# define CFG_OMX_MAX_ENCODERS 3 // Raspberry Pi limitation
|
||||
#endif
|
||||
#define OMX_MAX_ENCODERS ((unsigned)(CFG_OMX_MAX_ENCODERS))
|
||||
|
||||
|
||||
struct omx_encoder_t {
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
case _value: { return #_value; }
|
||||
|
||||
#define CASE_ASSERT(_msg, _value) default: { \
|
||||
char *_buf; A_CALLOC(_buf, 128); \
|
||||
sprintf(_buf, _msg ": 0x%08x", _value); \
|
||||
assert(0 && _buf); \
|
||||
char *_assert_buf; A_CALLOC(_assert_buf, 128); \
|
||||
sprintf(_assert_buf, _msg ": 0x%08x", _value); \
|
||||
assert(0 && _assert_buf); \
|
||||
}
|
||||
|
||||
const char *omx_error_to_string(OMX_ERRORTYPE error) {
|
||||
@@ -67,15 +67,40 @@ const char *omx_error_to_string(OMX_ERRORTYPE error) {
|
||||
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringDeallocation);
|
||||
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringStop);
|
||||
CASE_TO_STRING(OMX_ErrorIncorrectStateTransition);
|
||||
CASE_TO_STRING(OMX_ErrorIncorrectStateOperation);
|
||||
CASE_TO_STRING(OMX_ErrorUnsupportedSetting);
|
||||
CASE_TO_STRING(OMX_ErrorUnsupportedIndex);
|
||||
CASE_TO_STRING(OMX_ErrorBadPortIndex);
|
||||
CASE_TO_STRING(OMX_ErrorPortUnpopulated);
|
||||
CASE_TO_STRING(OMX_ErrorComponentSuspended);
|
||||
CASE_TO_STRING(OMX_ErrorDynamicResourcesUnavailable);
|
||||
CASE_TO_STRING(OMX_ErrorMbErrorsInFrame);
|
||||
CASE_TO_STRING(OMX_ErrorFormatNotDetected);
|
||||
CASE_TO_STRING(OMX_ErrorContentPipeOpenFailed);
|
||||
CASE_TO_STRING(OMX_ErrorContentPipeCreationFailed);
|
||||
CASE_TO_STRING(OMX_ErrorSeperateTablesUsed);
|
||||
CASE_TO_STRING(OMX_ErrorTunnelingUnsupported);
|
||||
CASE_TO_STRING(OMX_ErrorKhronosExtensions);
|
||||
CASE_TO_STRING(OMX_ErrorVendorStartUnused);
|
||||
CASE_TO_STRING(OMX_ErrorDiskFull);
|
||||
CASE_TO_STRING(OMX_ErrorMaxFileSize);
|
||||
CASE_TO_STRING(OMX_ErrorDrmUnauthorised);
|
||||
CASE_TO_STRING(OMX_ErrorDrmExpired);
|
||||
CASE_TO_STRING(OMX_ErrorDrmGeneral);
|
||||
default: return "Unknown OMX error";
|
||||
}
|
||||
}
|
||||
|
||||
const char *omx_state_to_string(OMX_STATETYPE state) {
|
||||
switch (state) {
|
||||
CASE_TO_STRING(OMX_StateInvalid);
|
||||
CASE_TO_STRING(OMX_StateLoaded);
|
||||
CASE_TO_STRING(OMX_StateIdle);
|
||||
CASE_TO_STRING(OMX_StateExecuting);
|
||||
CASE_TO_STRING(OMX_StatePause);
|
||||
CASE_TO_STRING(OMX_StateWaitForResources);
|
||||
// cppcheck-suppress constArgument
|
||||
// cppcheck-suppress knownArgument
|
||||
CASE_ASSERT("Unsupported OMX state", state);
|
||||
}
|
||||
}
|
||||
|
||||
42
src/gpio.h
42
src/gpio.h
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <wiringPi.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
@@ -42,17 +43,18 @@ int gpio_pin_workers_busy_at;
|
||||
gpio_pin_workers_busy_at = -1; \
|
||||
}
|
||||
|
||||
#define GPIO_INIT_PIN(_role, _offset) { \
|
||||
if (gpio_pin_##_role >= 0) { \
|
||||
pinMode(gpio_pin_##_role + _offset, OUTPUT); \
|
||||
digitalWrite(gpio_pin_##_role + _offset, LOW); \
|
||||
if (_offset == 0) { \
|
||||
LOG_INFO("GPIO: Using pin %d as %s", gpio_pin_##_role, #_role); \
|
||||
} else { \
|
||||
LOG_INFO("GPIO: Using pin %d+%d as %s", gpio_pin_##_role, _offset, #_role); \
|
||||
} \
|
||||
} \
|
||||
#define GPIO_INIT_PIN(_role, _offset) _gpio_init_pin(#_role, gpio_pin_##_role, _offset)
|
||||
|
||||
INLINE void _gpio_init_pin(const char *role, int base, unsigned offset) {
|
||||
if (base >= 0) {
|
||||
pinMode(base + offset, OUTPUT);
|
||||
if (offset == 0) {
|
||||
LOG_INFO("GPIO: Using pin %d as %s", base, role);
|
||||
} else {
|
||||
LOG_INFO("GPIO: Using pin %d+%u as %s", base, offset, role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define GPIO_INIT_PINOUT { \
|
||||
if ( \
|
||||
@@ -74,16 +76,18 @@ int gpio_pin_workers_busy_at;
|
||||
} \
|
||||
}
|
||||
|
||||
#define GPIO_SET_STATE(_role, _offset, _state) { \
|
||||
if (gpio_pin_##_role >= 0) { \
|
||||
if (_offset == 0) { \
|
||||
LOG_DEBUG("GPIO: Writing %d to pin %d (%s)", _state, gpio_pin_##_role, #_role); \
|
||||
} else { \
|
||||
LOG_DEBUG("GPIO: Writing %d to pin %d+%d (%s)", _state, gpio_pin_##_role, _offset, #_role); \
|
||||
} \
|
||||
digitalWrite(gpio_pin_##_role + _offset, _state); \
|
||||
} \
|
||||
#define GPIO_SET_STATE(_role, _offset, _state) _gpio_set_state(#_role, gpio_pin_##_role, _offset, _state)
|
||||
|
||||
INLINE void _gpio_set_state(const char *role, int base, unsigned offset, int state) {
|
||||
if (base >= 0) {
|
||||
if (offset == 0) {
|
||||
LOG_DEBUG("GPIO: Writing %d to pin %d (%s)", state, base, role);
|
||||
} else {
|
||||
LOG_DEBUG("GPIO: Writing %d to pin %d+%u (%s)", state, base, offset, role);
|
||||
}
|
||||
digitalWrite(base + offset, state);
|
||||
}
|
||||
}
|
||||
|
||||
#define GPIO_SET_LOW(_role) GPIO_SET_STATE(_role, 0, LOW)
|
||||
#define GPIO_SET_HIGH(_role) GPIO_SET_STATE(_role, 0, HIGH)
|
||||
|
||||
@@ -94,7 +94,7 @@ static struct picture_t *_init_external(const char *path) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
# define CHUNK_SIZE (100 * 1024)
|
||||
# define CHUNK_SIZE ((size_t)(100 * 1024))
|
||||
while (true) {
|
||||
if (blank->used + CHUNK_SIZE >= blank->allocated) {
|
||||
picture_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
|
||||
|
||||
@@ -85,6 +85,8 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
|
||||
static bool _expose_new_picture_unsafe(struct http_server_t *server);
|
||||
static bool _expose_blank_picture(struct http_server_t *server);
|
||||
|
||||
static void _format_bufferevent_reason(short what, char *reason);
|
||||
|
||||
|
||||
struct http_server_t *http_server_init(struct stream_t *stream) {
|
||||
struct http_server_runtime_t *run;
|
||||
@@ -533,7 +535,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 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);
|
||||
@@ -677,12 +679,16 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
struct evhttp_connection *conn;
|
||||
char *client_addr = "???";
|
||||
unsigned short client_port = 0;
|
||||
char reason[2048] = {0};
|
||||
|
||||
_format_bufferevent_reason(what, reason);
|
||||
|
||||
# define RUN(_next) client->server->run->_next
|
||||
|
||||
assert(RUN(stream_clients_count) > 0);
|
||||
RUN(stream_clients_count) -= 1;
|
||||
|
||||
if (RUN(stream_clients_count) <= 0) {
|
||||
if (RUN(stream_clients_count) == 0) {
|
||||
if (client->server->slowdown) {
|
||||
stream_switch_slowdown(RUN(stream), true);
|
||||
}
|
||||
@@ -696,8 +702,9 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
if (conn) {
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
}
|
||||
LOG_INFO("HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
|
||||
client_addr, client_port, RUN(stream_clients_count));
|
||||
|
||||
LOG_INFO("HTTP: Disconnected client: [%s]:%u, id=%s, %s; clients now: %u",
|
||||
client_addr, client_port, client->id, reason, RUN(stream_clients_count));
|
||||
if (conn) {
|
||||
evhttp_connection_free(conn);
|
||||
}
|
||||
@@ -721,8 +728,6 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
|
||||
struct bufferevent *buf_event;
|
||||
long long now;
|
||||
bool queued = false;
|
||||
static unsigned queued_fps_accum = 0;
|
||||
static long long queued_fps_second = 0;
|
||||
|
||||
for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
|
||||
conn = evhttp_request_get_connection(client->request);
|
||||
@@ -756,6 +761,9 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
|
||||
}
|
||||
|
||||
if (queued) {
|
||||
static unsigned queued_fps_accum = 0;
|
||||
static long long queued_fps_second = 0;
|
||||
|
||||
if ((now = floor_ms(get_now_monotonic())) != queued_fps_second) {
|
||||
server->run->exposed->queued_fps = queued_fps_accum;
|
||||
queued_fps_accum = 0;
|
||||
@@ -908,3 +916,33 @@ static bool _expose_blank_picture(struct http_server_t *server) {
|
||||
|
||||
# undef EXPOSED
|
||||
}
|
||||
|
||||
static void _format_bufferevent_reason(short what, char *reason) {
|
||||
char perror_buf[1024] = {0};
|
||||
char *perror_ptr = errno_to_string(EVUTIL_SOCKET_ERROR(), perror_buf, 1024); // evutil_socket_error_to_string() is not thread-sage
|
||||
bool first = true;
|
||||
|
||||
strcat(reason, perror_ptr);
|
||||
strcat(reason, " (");
|
||||
|
||||
# define FILL_REASON(_bev, _name) { \
|
||||
if (what & _bev) { \
|
||||
if (first) { \
|
||||
first = false; \
|
||||
} else { \
|
||||
strcat(reason, ","); \
|
||||
} \
|
||||
strcat(reason, _name); \
|
||||
} \
|
||||
}
|
||||
|
||||
FILL_REASON(BEV_EVENT_READING, "reading");
|
||||
FILL_REASON(BEV_EVENT_WRITING, "writing");
|
||||
FILL_REASON(BEV_EVENT_ERROR, "error");
|
||||
FILL_REASON(BEV_EVENT_TIMEOUT, "timeout");
|
||||
FILL_REASON(BEV_EVENT_EOF, "eof"); // cppcheck-suppress unreadVariable
|
||||
|
||||
# undef FILL_REASON
|
||||
|
||||
strcat(reason, ")");
|
||||
}
|
||||
|
||||
34
src/logging.c
Normal file
34
src/logging.c
Normal file
@@ -0,0 +1,34 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
enum log_level_t log_level;
|
||||
|
||||
bool log_colored;
|
||||
|
||||
pthread_mutex_t log_mutex;
|
||||
@@ -37,16 +37,19 @@
|
||||
#include "threading.h"
|
||||
|
||||
|
||||
enum {
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_PERF,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
LOG_LEVEL_DEBUG,
|
||||
} log_level;
|
||||
enum log_level_t {
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_PERF,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
LOG_LEVEL_DEBUG,
|
||||
};
|
||||
|
||||
bool log_colored;
|
||||
|
||||
pthread_mutex_t log_mutex;
|
||||
extern enum log_level_t log_level;
|
||||
|
||||
extern bool log_colored;
|
||||
|
||||
extern pthread_mutex_t log_mutex;
|
||||
|
||||
|
||||
#define LOGGING_INIT { \
|
||||
@@ -113,7 +116,7 @@ pthread_mutex_t log_mutex;
|
||||
|
||||
#define LOG_PERROR(_msg, ...) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(_perror_buf, 1024); \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
|
||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
||||
}
|
||||
|
||||
@@ -146,7 +149,7 @@ pthread_mutex_t log_mutex;
|
||||
#define LOG_VERBOSE_PERROR(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(_perror_buf, 1024); \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
|
||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
||||
} \
|
||||
}
|
||||
@@ -158,11 +161,11 @@ pthread_mutex_t log_mutex;
|
||||
}
|
||||
|
||||
|
||||
INLINE char *errno_to_string(char *buf, size_t size) {
|
||||
INLINE char *errno_to_string(int error, char *buf, size_t size) {
|
||||
# if defined(__GLIBC__) && defined(_GNU_SOURCE)
|
||||
return strerror_r(errno, buf, size);
|
||||
return strerror_r(error, buf, size);
|
||||
# else
|
||||
strerror_r(errno, buf, size);
|
||||
strerror_r(error, buf, size);
|
||||
return buf;
|
||||
# endif
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ enum _OPT_VALUES {
|
||||
_O_RESOLUTION = 'r',
|
||||
_O_FORMAT = 'm',
|
||||
_O_TV_STANDARD = 'a',
|
||||
_O_IO_METHOD = 'I',
|
||||
_O_DESIRED_FPS = 'f',
|
||||
_O_MIN_FRAME_SIZE = 'z',
|
||||
_O_PERSISTENT = 'n',
|
||||
@@ -128,6 +129,7 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"resolution", required_argument, NULL, _O_RESOLUTION},
|
||||
{"format", required_argument, NULL, _O_FORMAT},
|
||||
{"tv-standard", required_argument, NULL, _O_TV_STANDARD},
|
||||
{"io-method", required_argument, NULL, _O_IO_METHOD},
|
||||
{"desired-fps", required_argument, NULL, _O_DESIRED_FPS},
|
||||
{"min-frame-size", required_argument, NULL, _O_MIN_FRAME_SIZE},
|
||||
{"persistent", no_argument, NULL, _O_PERSISTENT},
|
||||
@@ -240,7 +242,7 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
|
||||
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
|
||||
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
|
||||
if (errno || *_end || _tmp < _min || _tmp > _max) { \
|
||||
printf("Invalid value for '%s=%s': min=%u, max=%u\n", _name, optarg, _min, _max); \
|
||||
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
|
||||
return -1; \
|
||||
} \
|
||||
_dest = _tmp; \
|
||||
@@ -327,6 +329,7 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
|
||||
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN, FORMATS_STR);
|
||||
# pragma GCC diagnostic pop
|
||||
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN, STANDARDS_STR);
|
||||
case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, device_parse_io_method, IO_METHOD_UNKNOWN, IO_METHODS_STR);
|
||||
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
|
||||
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 0, 8192, 0);
|
||||
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
|
||||
@@ -548,6 +551,9 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
|
||||
printf(" -a|--tv-standard <std> ────────────── Force TV standard.\n");
|
||||
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
|
||||
printf(" -I|--io-method <method> ───────────── Set V4L2 IO method (see kernel documentation).\n");
|
||||
printf(" Changing of this parameter may increase the performance. Or not.\n");
|
||||
printf(" Available: %s; default: MMAP\n\n", IO_METHODS_STR);
|
||||
printf(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n\n");
|
||||
printf(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device\n");
|
||||
printf(" produces small-sized garbage frames. Default: disabled.\n\n");
|
||||
@@ -644,7 +650,7 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
printf(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).\n");
|
||||
printf(" Enabling debugging messages can slow down the program.\n");
|
||||
printf(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).\n");
|
||||
printf(" Default: %u.\n\n", log_level);
|
||||
printf(" Default: %d.\n\n", log_level);
|
||||
printf(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n");
|
||||
printf(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n");
|
||||
printf(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");
|
||||
|
||||
@@ -73,9 +73,7 @@ void picture_append_data(struct picture_t *picture, const unsigned char *data, s
|
||||
}
|
||||
|
||||
void picture_copy(const struct picture_t *src, struct picture_t *dest) {
|
||||
assert(src->allocated);
|
||||
|
||||
picture_set_data(dest, src->data, src->allocated);
|
||||
picture_set_data(dest, src->data, src->used);
|
||||
|
||||
# define COPY(_field) dest->_field = src->_field
|
||||
|
||||
@@ -92,5 +90,9 @@ void picture_copy(const struct picture_t *src, struct picture_t *dest) {
|
||||
}
|
||||
|
||||
bool picture_compare(const struct picture_t *a, const struct picture_t *b) {
|
||||
return (a->used == b->used && !memcmp(a->data, b->data, b->used));
|
||||
return (
|
||||
a->allocated && b->allocated
|
||||
&& a->used == b->used
|
||||
&& !memcmp(a->data, b->data, b->used)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,13 +105,12 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
|
||||
char *cmdline = NULL;
|
||||
size_t allocated = 2048;
|
||||
size_t used = 0;
|
||||
size_t arg_len = 0;
|
||||
|
||||
A_REALLOC(cmdline, allocated);
|
||||
cmdline[0] = '\0';
|
||||
|
||||
for (int index = 0; index < argc; ++index) {
|
||||
arg_len = strlen(argv[index]);
|
||||
size_t arg_len = strlen(argv[index]);
|
||||
if (used + arg_len + 16 >= allocated) {
|
||||
allocated += arg_len + 2048;
|
||||
A_REALLOC(cmdline, allocated);
|
||||
|
||||
@@ -51,7 +51,7 @@ struct _worker_t {
|
||||
long double last_comp_time;
|
||||
|
||||
pthread_mutex_t has_job_mutex;
|
||||
int buf_index;
|
||||
unsigned buf_index;
|
||||
atomic_bool has_job;
|
||||
bool job_timely;
|
||||
bool job_failed;
|
||||
@@ -489,7 +489,7 @@ static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool) {
|
||||
A_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex);
|
||||
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
|
||||
|
||||
if (pool->oldest_worker && !atomic_load(&pool->oldest_worker->has_job) && pool->oldest_worker->buf_index >= 0) {
|
||||
if (pool->oldest_worker && !atomic_load(&pool->oldest_worker->has_job)) {
|
||||
ready_worker = pool->oldest_worker;
|
||||
ready_worker->job_timely = true;
|
||||
pool->oldest_worker = pool->oldest_worker->order_next;
|
||||
|
||||
@@ -41,9 +41,9 @@
|
||||
|
||||
|
||||
#ifdef PTHREAD_MAX_NAMELEN_NP
|
||||
# define MAX_THREAD_NAME PTHREAD_MAX_NAMELEN_NP
|
||||
# define MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
||||
#else
|
||||
# define MAX_THREAD_NAME 16
|
||||
# define MAX_THREAD_NAME ((size_t)16)
|
||||
#endif
|
||||
|
||||
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
|
||||
@@ -51,9 +51,9 @@
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# define A_THREAD_RENAME(_fmt, ...) { \
|
||||
char _buf[MAX_THREAD_NAME] = {0}; \
|
||||
assert(snprintf(_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \
|
||||
thread_set_name(_buf); \
|
||||
char _new_tname_buf[MAX_THREAD_NAME] = {0}; \
|
||||
assert(snprintf(_new_tname_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \
|
||||
thread_set_name(_new_tname_buf); \
|
||||
}
|
||||
#else
|
||||
# define A_THREAD_RENAME(_fmt, ...)
|
||||
|
||||
@@ -45,6 +45,10 @@ INLINE char *bool_to_string(bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
INLINE size_t align_size(size_t size, size_t to) {
|
||||
return ((size + (to - 1)) & ~(to - 1));
|
||||
}
|
||||
|
||||
INLINE unsigned min_u(unsigned a, unsigned b) {
|
||||
return (a < b ? a : b);
|
||||
}
|
||||
|
||||
@@ -30,9 +30,10 @@
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
#ifndef XIOCTL_RETRIES
|
||||
# define XIOCTL_RETRIES 4
|
||||
#ifndef CFG_XIOCTL_RETRIES
|
||||
# define CFG_XIOCTL_RETRIES 4
|
||||
#endif
|
||||
#define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES))
|
||||
|
||||
|
||||
INLINE int xioctl(int fd, int request, void *arg) {
|
||||
@@ -51,8 +52,9 @@ INLINE int xioctl(int fd, int request, void *arg) {
|
||||
)
|
||||
);
|
||||
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (retval && retries <= 0) {
|
||||
LOG_PERROR("ioctl(%d) retried %d times; giving up", request, XIOCTL_RETRIES);
|
||||
LOG_PERROR("ioctl(%d) retried %u times; giving up", request, XIOCTL_RETRIES);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#============================================================================#
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
@@ -18,7 +18,7 @@
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
#============================================================================#
|
||||
# ========================================================================== #
|
||||
|
||||
|
||||
import textwrap
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env -S python3 -B
|
||||
#============================================================================#
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
@@ -18,7 +18,7 @@
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
#============================================================================#
|
||||
# ========================================================================== #
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env -S python3 -B
|
||||
#============================================================================#
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
@@ -18,7 +18,7 @@
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
#============================================================================#
|
||||
# ========================================================================== #
|
||||
|
||||
|
||||
import sys
|
||||
@@ -26,7 +26,6 @@ import io
|
||||
import struct
|
||||
|
||||
from typing import Tuple
|
||||
from typing import List
|
||||
|
||||
import common
|
||||
|
||||
@@ -38,14 +37,15 @@ def _get_jpeg_size(data: bytes) -> Tuple[int, int]:
|
||||
stream = io.BytesIO(data)
|
||||
while True:
|
||||
marker = struct.unpack(">H", stream.read(2))[0]
|
||||
if marker == 0xFFD9:
|
||||
raise RuntimeError("Can't find jpeg size")
|
||||
|
||||
if (
|
||||
marker == 0xFFD8 # Start of image
|
||||
or marker == 0xFF01 # Private marker
|
||||
or (marker >= 0xFFD0 and marker <= 0xFFD7) # Restart markers
|
||||
or 0xFFD0 <= marker <= 0xFFD7 # Restart markers
|
||||
):
|
||||
continue
|
||||
elif marker == 0xFFD9:
|
||||
raise RuntimeError("Can't find jpeg size")
|
||||
|
||||
# All other markers specify chunks with lengths
|
||||
length = struct.unpack(">H", stream.read(2))[0]
|
||||
|
||||
Reference in New Issue
Block a user