Compare commits

...

55 Commits
v1.10 ... v1.18

Author SHA1 Message Date
Devaev Maxim
d5aebc1231 Bump version: 1.17 → 1.18 2020-07-03 22:18:48 +03:00
Devaev Maxim
5f4f46bbe6 more omx error codes 2020-07-02 23:02:40 +03:00
Devaev Maxim
61a2fe6546 refactoring 2020-07-02 18:05:41 +03:00
Devaev Maxim
98499b6604 verbose message about disconnecting client 2020-06-28 13:15:24 +03:00
Devaev Maxim
f52d090f9b updated readme 2020-06-23 07:37:20 +03:00
Maxim Devaev
ebb3df46b9 Merge pull request #21 from mtlynch/ansible-note
Add note about uStreamer Ansible role
2020-06-21 03:12:38 +03:00
Michael Lynch
8fd6659cf1 Add note about uStreamer Ansible role 2020-06-20 19:53:22 -04:00
Devaev Maxim
13ce0bbc63 fixed raspbian installation manual 2020-05-30 20:19:03 +03:00
Maxim Devaev
0add4cc25f Update FUNDING.yml 2020-05-27 03:55:15 +03:00
Devaev Maxim
96d84b33bd new pyflakes 2020-05-22 15:43:33 +03:00
Maxim Devaev
f2dfe7641e Create FUNDING.yml 2020-05-19 18:59:08 +03:00
Devaev Maxim
97403cbb75 Bump version: 1.16 → 1.17 2020-05-19 01:08:37 +03:00
Devaev Maxim
0663bb1035 fix for gcc 10 multiple definition errors 2020-05-19 01:07:55 +03:00
Devaev Maxim
06f4017a5b Bump version: 1.15 → 1.16 2020-05-12 09:42:48 +03:00
Devaev Maxim
6493a62c6c badges 2020-05-12 09:39:02 +03:00
Devaev Maxim
66c627c682 lint fixes 2020-05-12 09:37:02 +03:00
Devaev Maxim
8d6f5f7f8f .dockerignore 2020-05-12 09:36:48 +03:00
Devaev Maxim
f1cdfd4223 docker-based linters 2020-05-12 09:26:24 +03:00
Devaev Maxim
972c288df3 action fix 2020-05-12 09:08:23 +03:00
Devaev Maxim
fd1d2dec71 action fix 2020-05-12 09:05:26 +03:00
Devaev Maxim
331bd181bf action fix 2020-05-12 09:01:44 +03:00
Devaev Maxim
231ff37570 action fix 2020-05-12 08:54:01 +03:00
Devaev Maxim
cec0b203b3 action fix 2020-05-12 08:51:32 +03:00
Devaev Maxim
e5c9d699a3 action fix 2020-05-12 08:50:08 +03:00
Maxim Devaev
198fbc1756 Create ccpp.yml 2020-05-12 08:46:39 +03:00
Devaev Maxim
55aa443d68 linters 2020-05-12 08:43:12 +03:00
Devaev Maxim
44d6288416 lint fixes 2020-05-12 08:42:18 +03:00
Devaev Maxim
aeae342853 lint fixes 2020-05-12 08:19:26 +03:00
Devaev Maxim
d22034da96 lint fixes 2020-05-12 07:45:12 +03:00
Devaev Maxim
43788f812d type fixes 2020-05-12 07:27:42 +03:00
Devaev Maxim
95318e14d8 lint fixes 2020-05-12 06:44:59 +03:00
Devaev Maxim
67d6f15776 lint fix 2020-05-11 20:02:30 +03:00
Devaev Maxim
6c2353ce2c Bump version: 1.14 → 1.15 2020-05-03 06:38:11 +03:00
Devaev Maxim
fcdfb2930a refactoring 2020-05-03 06:38:02 +03:00
Devaev Maxim
dcc90341c9 Bump version: 1.13 → 1.14 2020-05-03 06:32:59 +03:00
Devaev Maxim
7e102c88cd log static errors as verbose 2020-05-03 06:17:43 +03:00
Devaev Maxim
e131a3ba49 typo 2020-04-30 17:27:55 +03:00
Devaev Maxim
e393be4c63 Bump version: 1.12 → 1.13 2020-03-04 03:04:44 +03:00
Devaev Maxim
cf4f5f5b2a notify parent using SIGUSR2 2020-03-04 03:04:28 +03:00
Maxim Devaev
1190059359 Update README.ru.md 2020-02-25 16:06:25 +03:00
Maxim Devaev
910b27feb4 Update README.md 2020-02-25 16:05:58 +03:00
Devaev Maxim
60ca92d367 Bump version: 1.11 → 1.12 2020-02-19 16:46:38 +03:00
Devaev Maxim
4f44c5efa1 hint 2020-02-19 16:46:13 +03:00
Devaev Maxim
3504095871 refactoring 2020-02-19 08:56:50 +03:00
Devaev Maxim
6eeb49ef75 merged ctl options 2020-02-19 08:56:50 +03:00
Devaev Maxim
9353b3474a options to reset image settings to default 2020-02-19 08:56:50 +03:00
Maxim Devaev
dfc98a67f2 Update README.ru.md 2020-02-10 06:40:25 +03:00
Maxim Devaev
904c76fa93 Update README.md 2020-02-10 06:39:37 +03:00
Devaev Maxim
f0a7ca5c94 Issue #14: added message about fileserver 2020-01-30 03:53:06 +03:00
Devaev Maxim
bb4e9db7e7 fixed serving of empty static files 2020-01-29 20:20:53 +03:00
Devaev Maxim
61f2bfa00e fixed mime guessing 2020-01-29 20:13:31 +03:00
Devaev Maxim
49eb7f6e51 fixed clang 'linker' input unused 2020-01-19 17:23:41 +03:00
Devaev Maxim
b5375b835a Fixed #11: clang warning 2020-01-19 01:07:57 +03:00
Devaev Maxim
e3e2c5cead Bump version: 1.10 → 1.11 2020-01-14 16:49:50 +03:00
Devaev Maxim
ef4150877b check for 8 bits 2020-01-14 16:46:31 +03:00
43 changed files with 627 additions and 196 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion] [bumpversion]
commit = True commit = True
tag = True tag = True
current_version = 1.10 current_version = 1.18
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}
@@ -17,4 +17,3 @@ replace = pkgver={new_version}
[bumpversion:file:pkg/openwrt/Makefile] [bumpversion:file:pkg/openwrt/Makefile]
search = PKG_VERSION:={current_version} search = PKG_VERSION:={current_version}
replace = PKG_VERSION:={new_version} replace = PKG_VERSION:={new_version}

4
.github/FUNDING.yml vendored Normal file
View 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
View 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
View File

@@ -1,3 +1,5 @@
/linters/.tox/
/linters/.mypy_cache/
/pkg/arch/pkg/ /pkg/arch/pkg/
/pkg/arch/src/ /pkg/arch/src/
/pkg/arch/v*.tar.gz /pkg/arch/v*.tar.gz

View File

@@ -13,6 +13,8 @@ RPI_VC_LIBS ?= /opt/vc/lib
BUILD ?= build BUILD ?= build
LINTERS_IMAGE ?= $(PROG)-linters
# ===== # =====
_LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid _LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
@@ -87,17 +89,36 @@ $(PROG): $(_SRCS:%.c=$(BUILD)/%.o)
$(BUILD)/%.o: %.c $(BUILD)/%.o: %.c
$(info -- CC $<) $(info -- CC $<)
@ mkdir -p $(dir $@) || true @ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(CFLAGS) $(_LIBS) @ $(CC) $< -o $@ $(CFLAGS)
release: release:
make clean make clean
make tox
make push make push
make bump make bump
make push make push
make clean 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: bump:
bumpversion $(if $(V),$(V),minor) bumpversion $(if $(V),$(V),minor)
@@ -107,7 +128,12 @@ push:
git push --tags 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: clean:
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst} 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 rm -rf $(PROG) $(BUILD) vgcore.* *.sock
.PHONY: linters

View File

@@ -1,4 +1,7 @@
# µStreamer # µStreamer
[![CI](https://github.com/pikvm/ustreamer/workflows/CI/badge.svg)](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
[![Discord](https://img.shields.io/discord/580094191938437144?logo=discord)](https://discord.gg/bpmXfz5)
[[Русская версия]](README.ru.md) [[Русская версия]](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. µ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.
@@ -32,6 +35,10 @@ 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```, ```libuuid``` and ```libbsd``` (only for Linux). 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 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```. 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```.
``` ```
@@ -64,6 +71,11 @@ $ ./ustreamer \
You can always view the full list of options with ```ustreamer --help```. You can always view the full list of options with ```ustreamer --help```.
-----
# 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 # License
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com

View File

@@ -1,4 +1,7 @@
# µStreamer # µStreamer
[![CI](https://github.com/pikvm/ustreamer/workflows/CI/badge.svg)](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI)
[![Discord](https://img.shields.io/discord/580094191938437144?logo=discord)](https://discord.gg/bpmXfz5)
[[English version]](README.md) [[English version]](README.md)
@@ -32,6 +35,10 @@
# Сборка # Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux). Для сборки вам понадобятся ```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 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```. На 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```.
``` ```
@@ -64,6 +71,11 @@ $ ./ustreamer \
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```. За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
-----
# Смотрите также
* [Запуск с помощью 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-сервиса.
----- -----
# Лицензия # Лицензия
Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com Copyright (C) 2018 by Maxim Devaev mdevaev@gmail.com

2
linters/.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
/.tox/
/.mypy_cache/

21
linters/Dockerfile Normal file
View 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
View 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
View 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
View 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
View 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

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer pkgname=ustreamer
pkgver=1.10 pkgver=1.18
pkgrel=1 pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer" pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pikvm/ustreamer" url="https://github.com/pikvm/ustreamer"

View File

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

View File

@@ -23,5 +23,5 @@
#pragma once #pragma once
#ifndef VERSION #ifndef VERSION
# define VERSION "1.10" # define VERSION "1.18"
#endif #endif

View File

@@ -77,9 +77,10 @@ static int _device_open_mmap(struct device_t *dev);
static int _device_open_queue_buffers(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 void _device_open_alloc_picbufs(struct device_t *dev);
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height); static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height);
static void _device_apply_controls(struct device_t *dev); static void _device_apply_controls(struct device_t *dev);
static int _device_check_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet); static int _device_query_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, bool quiet);
static void _device_set_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet); static void _device_set_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, int value, bool quiet);
static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned format); static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned format);
static const char *_format_to_string_nullable(unsigned format); static const char *_format_to_string_nullable(unsigned format);
@@ -256,9 +257,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_write = FD_ISSET(dev->run->fd, &write_fds);
*has_error = FD_ISSET(dev->run->fd, &error_fds); *has_error = FD_ISSET(dev->run->fd, &error_fds);
} else { } else {
has_read = false; *has_read = false;
has_write = false; *has_write = false;
has_error = false; *has_error = false;
} }
LOG_DEBUG("Device select() --> %d", retval); LOG_DEBUG("Device select() --> %d", retval);
@@ -390,7 +391,7 @@ static int _device_apply_dv_timings(struct device_t *dev) {
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ..."); LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) { if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu", LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu",
dv.bt.width, dv.bt.height, dv.bt.pixelclock); dv.bt.width, dv.bt.height, (unsigned long long)dv.bt.pixelclock); // Issue #11
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ..."); LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv) < 0) { if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv) < 0) {
@@ -610,65 +611,82 @@ static int _device_apply_resolution(struct device_t *dev, unsigned width, unsign
} }
static void _device_apply_controls(struct device_t *dev) { static void _device_apply_controls(struct device_t *dev) {
# define SET_CID(_cid, _dest, _value, _quiet) { \ # define SET_CID_VALUE(_cid, _field, _value, _quiet) { \
if (_device_check_control(dev, #_dest, _cid, _value, _quiet) == 0) { \ struct v4l2_queryctrl query; \
_device_set_control(dev, #_dest, _cid, _value, _quiet); \ if (_device_query_control(dev, &query, #_field, _cid, _quiet) == 0) { \
_device_set_control(dev, &query, #_field, _cid, _value, _quiet); \
} \ } \
} }
# define SET_CID_MANUAL(_cid, _dest) { \ # define SET_CID_DEFAULT(_cid, _field, _quiet) { \
if (dev->ctl._dest.value_set) { \ struct v4l2_queryctrl query; \
SET_CID(_cid, _dest, dev->ctl._dest.value, false); \ if (_device_query_control(dev, &query, #_field, _cid, _quiet) == 0) { \
_device_set_control(dev, &query, #_field, _cid, query.default_value, _quiet); \
} \ } \
} }
# define SET_CID_AUTO(_cid_auto, _cid_manual, _dest) { \ # define CONTROL_MANUAL_CID(_cid, _field) { \
if (dev->ctl._dest.value_set || dev->ctl._dest.auto_set) { \ if (dev->ctl._field.mode == CTL_MODE_VALUE) { \
SET_CID(_cid_auto, _dest##_auto, dev->ctl._dest.auto_set, dev->ctl._dest.value_set); \ SET_CID_VALUE(_cid, _field, dev->ctl._field.value, false); \
SET_CID_MANUAL(_cid_manual, _dest); \ } else if (dev->ctl._field.mode == CTL_MODE_DEFAULT) { \
SET_CID_DEFAULT(_cid, _field, false); \
} \ } \
} }
SET_CID_AUTO (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness); # define CONTROL_AUTO_CID(_cid_auto, _cid_manual, _field) { \
SET_CID_MANUAL ( V4L2_CID_CONTRAST, contrast); if (dev->ctl._field.mode == CTL_MODE_VALUE) { \
SET_CID_MANUAL ( V4L2_CID_SATURATION, saturation); SET_CID_VALUE(_cid_auto, _field##_auto, 0, true); \
SET_CID_AUTO (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue); SET_CID_VALUE(_cid_manual, _field, dev->ctl._field.value, false); \
SET_CID_MANUAL ( V4L2_CID_GAMMA, gamma); } else if (dev->ctl._field.mode == CTL_MODE_AUTO) { \
SET_CID_MANUAL ( V4L2_CID_SHARPNESS, sharpness); SET_CID_VALUE(_cid_auto, _field##_auto, 1, false); \
SET_CID_MANUAL ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation); } else if (dev->ctl._field.mode == CTL_MODE_DEFAULT) { \
SET_CID_AUTO (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance); SET_CID_VALUE(_cid_auto, _field##_auto, 0, true); /* Reset inactive flag */ \
SET_CID_AUTO (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain); SET_CID_DEFAULT(_cid_manual, _field, false); \
SET_CID_DEFAULT(_cid_auto, _field##_auto, false); \
} \
}
# undef SET_CID_AUTO CONTROL_AUTO_CID (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness);
# undef SET_CID_MANUAL CONTROL_MANUAL_CID ( V4L2_CID_CONTRAST, contrast);
# undef SET_CID CONTROL_MANUAL_CID ( V4L2_CID_SATURATION, saturation);
CONTROL_AUTO_CID (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue);
CONTROL_MANUAL_CID ( V4L2_CID_GAMMA, gamma);
CONTROL_MANUAL_CID ( V4L2_CID_SHARPNESS, sharpness);
CONTROL_MANUAL_CID ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
CONTROL_AUTO_CID (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
CONTROL_AUTO_CID (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
# undef CONTROL_AUTO_CID
# undef CONTROL_MANUAL_CID
# undef SET_CID_DEFAULT
# undef SET_CID_VALUE
} }
static int _device_check_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet) { static int _device_query_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, bool quiet) {
struct v4l2_queryctrl query; // cppcheck-suppress redundantPointerOp
MEMSET_ZERO(*query);
query->id = cid;
MEMSET_ZERO(query); if (xioctl(dev->run->fd, VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
query.id = cid;
if (xioctl(dev->run->fd, VIDIOC_QUERYCTRL, &query) < 0 || query.flags & V4L2_CTRL_FLAG_DISABLED) {
if (!quiet) { if (!quiet) {
LOG_ERROR("Changing control %s is unsupported", name); LOG_ERROR("Changing control %s is unsupported", name);
} }
return -1; return -1;
} }
if (value < query.minimum || value > query.maximum || value % query.step != 0) {
if (!quiet) {
LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
value, name, query.minimum, query.maximum, query.default_value, query.step);
}
return -2;
}
return 0; return 0;
} }
static void _device_set_control(struct device_t *dev, const char *name, unsigned cid, int value, bool quiet) { static void _device_set_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, int value, bool quiet) {
struct v4l2_control ctl; struct v4l2_control ctl;
if (value < query->minimum || value > query->maximum || value % query->step != 0) {
if (!quiet) {
LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
value, name, query->minimum, query->maximum, query->default_value, query->step);
}
return;
}
MEMSET_ZERO(ctl); MEMSET_ZERO(ctl);
ctl.id = cid; ctl.id = cid;
ctl.value = value; ctl.value = value;
@@ -678,7 +696,7 @@ static void _device_set_control(struct device_t *dev, const char *name, unsigned
LOG_PERROR("Can't set control %s", name); LOG_PERROR("Can't set control %s", name);
} }
} else if (!quiet) { } else if (!quiet) {
LOG_INFO("Using control %s: %d", name, ctl.value); LOG_INFO("Applying control %s: %d", name, ctl.value);
} }
} }
@@ -688,7 +706,7 @@ static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned for
buf[1] = (format >> 8) & 0x7F; buf[1] = (format >> 8) & 0x7F;
buf[2] = (format >> 16) & 0x7F; buf[2] = (format >> 16) & 0x7F;
buf[3] = (format >> 24) & 0x7F; buf[3] = (format >> 24) & 0x7F;
if (format & (1 << 31)) { if (format & ((unsigned)1 << 31)) {
buf[4] = '-'; buf[4] = '-';
buf[5] = 'B'; buf[5] = 'B';
buf[6] = 'E'; buf[6] = 'E';

View File

@@ -30,13 +30,13 @@
#include "picture.h" #include "picture.h"
#define VIDEO_MIN_WIDTH 160 #define VIDEO_MIN_WIDTH ((unsigned)160)
#define VIDEO_MAX_WIDTH 10240 #define VIDEO_MAX_WIDTH ((unsigned)10240)
#define VIDEO_MIN_HEIGHT 120 #define VIDEO_MIN_HEIGHT ((unsigned)120)
#define VIDEO_MAX_HEIGHT 4320 #define VIDEO_MAX_HEIGHT ((unsigned)4320)
#define VIDEO_MAX_FPS 120 #define VIDEO_MAX_FPS ((unsigned)120)
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN #define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define STANDARDS_STR "PAL, NTSC, SECAM" #define STANDARDS_STR "PAL, NTSC, SECAM"
@@ -64,10 +64,16 @@ struct device_runtime_t {
bool capturing; bool capturing;
}; };
enum control_mode_t {
CTL_MODE_NONE = 0,
CTL_MODE_VALUE,
CTL_MODE_AUTO,
CTL_MODE_DEFAULT,
};
struct control_t { struct control_t {
int value; enum control_mode_t mode;
bool value_set; int value;
bool auto_set;
}; };
struct controls_t { struct controls_t {

View File

@@ -170,6 +170,7 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
# pragma GCC diagnostic ignored "-Wunused-label" # pragma GCC diagnostic ignored "-Wunused-label"
# pragma GCC diagnostic push # pragma GCC diagnostic push
// cppcheck-suppress unusedLabel
force_cpu: force_cpu:
cpu_forced = true; cpu_forced = true;
# pragma GCC diagnostic pop # 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 ignored "-Wunused-label"
# pragma GCC diagnostic push # pragma GCC diagnostic push
// cppcheck-suppress unusedLabel
error: error:
LOG_INFO("Error while compressing buffer, 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);

View File

@@ -33,9 +33,10 @@
# define ENCODER_TYPES_OMX_HINT ", OMX" # define ENCODER_TYPES_OMX_HINT ", OMX"
# ifndef MAX_GLITCHED_RESOLUTIONS # ifndef CFG_MAX_GLITCHED_RESOLUTIONS
# define MAX_GLITCHED_RESOLUTIONS 1024 # define CFG_MAX_GLITCHED_RESOLUTIONS 1024
# endif # endif
# define MAX_GLITCHED_RESOLUTIONS ((unsigned)(CFG_MAX_GLITCHED_RESOLUTIONS))
#else #else
# define ENCODER_TYPES_OMX_HINT "" # define ENCODER_TYPES_OMX_HINT ""
#endif #endif

View File

@@ -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) { static void _jpeg_init_destination(j_compress_ptr jpeg) {
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest; struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;

View File

@@ -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) { int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port) {
OMX_ERRORTYPE error; OMX_ERRORTYPE error;
// cppcheck-suppress redundantPointerOp
OMX_INIT_STRUCTURE(*portdef); OMX_INIT_STRUCTURE(*portdef);
portdef->nPortIndex = port; portdef->nPortIndex = port;

View File

@@ -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.nFrameWidth = dev->run->width;
portdef.format.image.nFrameHeight = dev->run->height; portdef.format.image.nFrameHeight = dev->run->height;
portdef.format.image.nStride = 0; portdef.format.image.nStride = 0;
# define ALIGN_HEIGHT(_x, _y) (((_x) + ((_y) - 1)) & ~((_y) - 1)) portdef.format.image.nSliceHeight = align_size(dev->run->height, 16);
portdef.format.image.nSliceHeight = ALIGN_HEIGHT(dev->run->height, 16);
# undef ALIGN_HEIGHT
portdef.format.image.bFlagErrorConcealment = OMX_FALSE; portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused; portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused;
portdef.nBufferSize = picture_get_generous_size(dev->run->width, dev->run->height); portdef.nBufferSize = picture_get_generous_size(dev->run->width, dev->run->height);

View File

@@ -29,9 +29,10 @@
#include "../../device.h" #include "../../device.h"
#ifndef OMX_MAX_ENCODERS #ifndef CFG_OMX_MAX_ENCODERS
# define OMX_MAX_ENCODERS 3 // Raspberry Pi limitation # define CFG_OMX_MAX_ENCODERS 3 // Raspberry Pi limitation
#endif #endif
#define OMX_MAX_ENCODERS ((unsigned)(CFG_OMX_MAX_ENCODERS))
struct omx_encoder_t { struct omx_encoder_t {

View File

@@ -35,9 +35,9 @@
case _value: { return #_value; } case _value: { return #_value; }
#define CASE_ASSERT(_msg, _value) default: { \ #define CASE_ASSERT(_msg, _value) default: { \
char *_buf; A_CALLOC(_buf, 128); \ char *_assert_buf; A_CALLOC(_assert_buf, 128); \
sprintf(_buf, _msg ": 0x%08x", _value); \ sprintf(_assert_buf, _msg ": 0x%08x", _value); \
assert(0 && _buf); \ assert(0 && _assert_buf); \
} }
const char *omx_error_to_string(OMX_ERRORTYPE error) { 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_ErrorPortUnresponsiveDuringDeallocation);
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringStop); CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringStop);
CASE_TO_STRING(OMX_ErrorIncorrectStateTransition); 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"; default: return "Unknown OMX error";
} }
} }
const char *omx_state_to_string(OMX_STATETYPE state) { const char *omx_state_to_string(OMX_STATETYPE state) {
switch (state) { switch (state) {
CASE_TO_STRING(OMX_StateInvalid);
CASE_TO_STRING(OMX_StateLoaded); CASE_TO_STRING(OMX_StateLoaded);
CASE_TO_STRING(OMX_StateIdle); CASE_TO_STRING(OMX_StateIdle);
CASE_TO_STRING(OMX_StateExecuting); 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); CASE_ASSERT("Unsupported OMX state", state);
} }
} }

View File

@@ -26,6 +26,7 @@
#include <wiringPi.h> #include <wiringPi.h>
#include "tools.h"
#include "logging.h" #include "logging.h"
@@ -42,17 +43,18 @@ int gpio_pin_workers_busy_at;
gpio_pin_workers_busy_at = -1; \ gpio_pin_workers_busy_at = -1; \
} }
#define GPIO_INIT_PIN(_role, _offset) { \ #define GPIO_INIT_PIN(_role, _offset) _gpio_init_pin(#_role, gpio_pin_##_role, _offset)
if (gpio_pin_##_role >= 0) { \
pinMode(gpio_pin_##_role + _offset, OUTPUT); \ INLINE void _gpio_init_pin(const char *role, int base, unsigned offset) {
digitalWrite(gpio_pin_##_role + _offset, LOW); \ if (base >= 0) {
if (_offset == 0) { \ pinMode(base + offset, OUTPUT);
LOG_INFO("GPIO: Using pin %d as %s", gpio_pin_##_role, #_role); \ if (offset == 0) {
} else { \ LOG_INFO("GPIO: Using pin %d as %s", base, role);
LOG_INFO("GPIO: Using pin %d+%d as %s", gpio_pin_##_role, _offset, #_role); \ } else {
} \ LOG_INFO("GPIO: Using pin %d+%u as %s", base, offset, role);
} \ }
} }
}
#define GPIO_INIT_PINOUT { \ #define GPIO_INIT_PINOUT { \
if ( \ if ( \
@@ -74,16 +76,18 @@ int gpio_pin_workers_busy_at;
} \ } \
} }
#define GPIO_SET_STATE(_role, _offset, _state) { \ #define GPIO_SET_STATE(_role, _offset, _state) _gpio_set_state(#_role, gpio_pin_##_role, _offset, _state)
if (gpio_pin_##_role >= 0) { \
if (_offset == 0) { \ INLINE void _gpio_set_state(const char *role, int base, unsigned offset, int state) {
LOG_DEBUG("GPIO: Writing %d to pin %d (%s)", _state, gpio_pin_##_role, #_role); \ if (base >= 0) {
} else { \ if (offset == 0) {
LOG_DEBUG("GPIO: Writing %d to pin %d+%d (%s)", _state, gpio_pin_##_role, _offset, #_role); \ LOG_DEBUG("GPIO: Writing %d to pin %d (%s)", state, base, role);
} \ } else {
digitalWrite(gpio_pin_##_role + _offset, _state); \ 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_LOW(_role) GPIO_SET_STATE(_role, 0, LOW)
#define GPIO_SET_HIGH(_role) GPIO_SET_STATE(_role, 0, HIGH) #define GPIO_SET_HIGH(_role) GPIO_SET_STATE(_role, 0, HIGH)

View File

@@ -94,7 +94,7 @@ static struct picture_t *_init_external(const char *path) {
goto error; goto error;
} }
# define CHUNK_SIZE (100 * 1024) # define CHUNK_SIZE ((size_t)(100 * 1024))
while (true) { while (true) {
if (blank->used + CHUNK_SIZE >= blank->allocated) { if (blank->used + CHUNK_SIZE >= blank->allocated) {
picture_realloc_data(blank, blank->used + CHUNK_SIZE * 2); picture_realloc_data(blank, blank->used + CHUNK_SIZE * 2);

View File

@@ -56,7 +56,7 @@ const char *guess_mime_type(const char *path) {
char *dot; char *dot;
char *ext; char *ext;
dot = strchr(path, '.'); dot = strrchr(path, '.');
if (dot == NULL || strchr(dot, '/') != NULL) { if (dot == NULL || strchr(dot, '/') != NULL) {
goto misc; goto misc;
} }

View File

@@ -50,6 +50,7 @@
#include "../tools.h" #include "../tools.h"
#include "../threading.h" #include "../threading.h"
#include "../logging.h" #include "../logging.h"
#include "../process.h"
#include "../picture.h" #include "../picture.h"
#include "../encoder.h" #include "../encoder.h"
#include "../stream.h" #include "../stream.h"
@@ -84,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_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);
static void _format_bufferevent_reason(short what, char *reason);
struct http_server_t *http_server_init(struct stream_t *stream) { struct http_server_t *http_server_init(struct stream_t *stream) {
struct http_server_runtime_t *run; struct http_server_runtime_t *run;
@@ -157,6 +160,7 @@ void http_server_destroy(struct http_server_t *server) {
int http_server_listen(struct http_server_t *server) { int http_server_listen(struct http_server_t *server) {
{ {
if (server->static_path[0] != '\0') { if (server->static_path[0] != '\0') {
LOG_INFO("Enabling HTTP file server: %s", server->static_path);
evhttp_set_gencb(server->run->http, _http_callback_static, (void *)server); evhttp_set_gencb(server->run->http, _http_callback_static, (void *)server);
} else { } else {
assert(!evhttp_set_cb(server->run->http, "/", _http_callback_root, (void *)server)); assert(!evhttp_set_cb(server->run->http, "/", _http_callback_root, (void *)server));
@@ -175,6 +179,9 @@ int http_server_listen(struct http_server_t *server) {
EXPOSED(expose_begin_ts) = get_now_monotonic(); EXPOSED(expose_begin_ts) = get_now_monotonic();
EXPOSED(expose_cmp_ts) = EXPOSED(expose_begin_ts); EXPOSED(expose_cmp_ts) = EXPOSED(expose_begin_ts);
EXPOSED(expose_end_ts) = EXPOSED(expose_begin_ts); EXPOSED(expose_end_ts) = EXPOSED(expose_begin_ts);
// See _http_exposed_refresh()
EXPOSED(notify_last_width) = EXPOSED(picture->width);
EXPOSED(notify_last_height) = EXPOSED(picture->height);
# undef EXPOSED # undef EXPOSED
{ {
@@ -326,7 +333,7 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
goto not_found; goto not_found;
} }
if (evbuffer_add_file(buf, fd, 0, st.st_size) < 0) { if (st.st_size > 0 && evbuffer_add_file(buf, fd, 0, st.st_size) < 0) {
LOG_ERROR("HTTP: Can't serve static file %s", static_path); LOG_ERROR("HTTP: Can't serve static file %s", static_path);
goto not_found; goto not_found;
} }
@@ -528,7 +535,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
} }
evhttp_connection_get_peer(conn, &client_addr, &client_port); evhttp_connection_get_peer(conn, &client_addr, &client_port);
LOG_INFO("HTTP: Registered the new stream client: [%s]:%u, id=%s; clients now: %u", LOG_INFO("HTTP: Registered client: [%s]:%u, id=%s; clients now: %u",
client_addr, client_port, client->id, server->run->stream_clients_count); client_addr, client_port, client->id, server->run->stream_clients_count);
buf_event = evhttp_connection_get_bufferevent(conn); buf_event = evhttp_connection_get_bufferevent(conn);
@@ -672,12 +679,16 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
struct evhttp_connection *conn; struct evhttp_connection *conn;
char *client_addr = "???"; char *client_addr = "???";
unsigned short client_port = 0; unsigned short client_port = 0;
char reason[2048] = {0};
_format_bufferevent_reason(what, reason);
# define RUN(_next) client->server->run->_next # define RUN(_next) client->server->run->_next
assert(RUN(stream_clients_count) > 0);
RUN(stream_clients_count) -= 1; RUN(stream_clients_count) -= 1;
if (RUN(stream_clients_count) <= 0) { if (RUN(stream_clients_count) == 0) {
if (client->server->slowdown) { if (client->server->slowdown) {
stream_switch_slowdown(RUN(stream), true); stream_switch_slowdown(RUN(stream), true);
} }
@@ -691,8 +702,9 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
if (conn) { if (conn) {
evhttp_connection_get_peer(conn, &client_addr, &client_port); 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) { if (conn) {
evhttp_connection_free(conn); evhttp_connection_free(conn);
} }
@@ -716,8 +728,6 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
struct bufferevent *buf_event; struct bufferevent *buf_event;
long long now; long long now;
bool queued = false; 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) { for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
conn = evhttp_request_get_connection(client->request); conn = evhttp_request_get_connection(client->request);
@@ -751,6 +761,9 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
} }
if (queued) { 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) { if ((now = floor_ms(get_now_monotonic())) != queued_fps_second) {
server->run->exposed->queued_fps = queued_fps_accum; server->run->exposed->queued_fps = queued_fps_accum;
queued_fps_accum = 0; queued_fps_accum = 0;
@@ -790,6 +803,25 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
# undef UNLOCK_STREAM # undef UNLOCK_STREAM
_http_queue_send_stream(server, stream_updated, picture_updated); _http_queue_send_stream(server, stream_updated, picture_updated);
# define EXPOSED(_next) server->run->exposed->_next
if (
picture_updated
&& server->notify_parent
&& (
EXPOSED(notify_last_online) != EXPOSED(online)
|| EXPOSED(notify_last_width) != EXPOSED(picture->width)
|| EXPOSED(notify_last_height) != EXPOSED(picture->height)
)
) {
EXPOSED(notify_last_online) = EXPOSED(online);
EXPOSED(notify_last_width) = EXPOSED(picture->width);
EXPOSED(notify_last_height) = EXPOSED(picture->height);
process_notify_parent();
}
# undef EXPOSED
} }
static bool _expose_new_picture_unsafe(struct http_server_t *server) { static bool _expose_new_picture_unsafe(struct http_server_t *server) {
@@ -884,3 +916,33 @@ static bool _expose_blank_picture(struct http_server_t *server) {
# undef EXPOSED # 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, ")");
}

View File

@@ -65,6 +65,10 @@ struct exposed_t {
long double expose_cmp_ts; long double expose_cmp_ts;
long double expose_end_ts; long double expose_end_ts;
long double last_as_blank_ts; long double last_as_blank_ts;
bool notify_last_online;
unsigned notify_last_width;
unsigned notify_last_height;
}; };
struct http_server_runtime_t { struct http_server_runtime_t {
@@ -100,6 +104,8 @@ struct http_server_t {
unsigned fake_width; unsigned fake_width;
unsigned fake_height; unsigned fake_height;
bool notify_parent;
struct http_server_runtime_t *run; struct http_server_runtime_t *run;
}; };

View File

@@ -30,7 +30,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include "../tools.h" #include "../tools.h"
// #include "../logging.h" #include "../logging.h"
#include "path.h" #include "path.h"
@@ -42,6 +42,7 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
simplified_path = simplify_request_path(request_path); simplified_path = simplify_request_path(request_path);
if (simplified_path[0] == '\0') { if (simplified_path[0] == '\0') {
LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path);
goto error; goto error;
} }
@@ -50,13 +51,14 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
# define LOAD_STAT { \ # define LOAD_STAT { \
if (lstat(path, &st) < 0) { \ if (lstat(path, &st) < 0) { \
/* LOG_PERROR("Can't stat() file %s", path); */ \ LOG_VERBOSE_PERROR("HTTP: Can't stat() static path %s", path); \
goto error; \ goto error; \
} \ } \
} }
LOAD_STAT; LOAD_STAT;
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path);
strcat(path, "/index.html"); strcat(path, "/index.html");
LOAD_STAT; LOAD_STAT;
} }
@@ -64,12 +66,12 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
# undef LOAD_STAT # undef LOAD_STAT
if (!S_ISREG(st.st_mode)) { if (!S_ISREG(st.st_mode)) {
// LOG_ERROR("Not a regulary file: %s", path); LOG_VERBOSE("HTTP: Not a regular file: %s", path);
goto error; goto error;
} }
if (access(path, R_OK) < 0) { if (access(path, R_OK) < 0) {
// LOG_PERROR("Can't access() R_OK file %s", path); LOG_VERBOSE_PERROR("HTTP: Can't access() R_OK file %s", path);
goto error; goto error;
} }

34
src/logging.c Normal file
View 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;

View File

@@ -37,16 +37,19 @@
#include "threading.h" #include "threading.h"
enum { enum log_level_t {
LOG_LEVEL_INFO, LOG_LEVEL_INFO,
LOG_LEVEL_PERF, LOG_LEVEL_PERF,
LOG_LEVEL_VERBOSE, LOG_LEVEL_VERBOSE,
LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG,
} log_level; };
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 { \ #define LOGGING_INIT { \
@@ -88,14 +91,14 @@ pthread_mutex_t log_mutex;
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \ #define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
char _buf[MAX_THREAD_NAME] = {0}; \ char _tname_buf[MAX_THREAD_NAME] = {0}; \
thread_get_name(_buf); \ thread_get_name(_tname_buf); \
if (log_colored) { \ if (log_colored) { \
printf(COLOR_GRAY "-- " _label_color _label COLOR_GRAY " [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \ printf(COLOR_GRAY "-- " _label_color _label COLOR_GRAY " [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
get_now_monotonic(), _buf, ##__VA_ARGS__); \ get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
} else { \ } else { \
printf("-- " _label " [%.03Lf %9s] -- " _msg, \ printf("-- " _label " [%.03Lf %9s] -- " _msg, \
get_now_monotonic(), _buf, ##__VA_ARGS__); \ get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
} \ } \
putchar('\n'); \ putchar('\n'); \
fflush(stdout); \ fflush(stdout); \
@@ -112,9 +115,9 @@ pthread_mutex_t log_mutex;
} }
#define LOG_PERROR(_msg, ...) { \ #define LOG_PERROR(_msg, ...) { \
char _buf[1024] = {0}; \ char _perror_buf[1024] = {0}; \
char *_ptr = errno_to_string(_buf, 1024); \ char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _ptr); \ LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \
} }
#define LOG_INFO(_msg, ...) { \ #define LOG_INFO(_msg, ...) { \
@@ -143,6 +146,14 @@ 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(errno, _perror_buf, 1024); \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
} \
}
#define LOG_DEBUG(_msg, ...) { \ #define LOG_DEBUG(_msg, ...) { \
if (log_level >= LOG_LEVEL_DEBUG) { \ if (log_level >= LOG_LEVEL_DEBUG) { \
LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \ LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \
@@ -150,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) # if defined(__GLIBC__) && defined(_GNU_SOURCE)
return strerror_r(errno, buf, size); return strerror_r(error, buf, size);
# else # else
strerror_r(errno, buf, size); strerror_r(error, buf, size);
return buf; return buf;
# endif # endif
} }

View File

@@ -25,6 +25,11 @@
# error WTF dude? Asserts are good things! # error WTF dude? Asserts are good things!
#endif #endif
#include <limits.h>
#if CHAR_BIT != 8
# error There are not 8 bits in a char!
#endif
#include <stdio.h> #include <stdio.h>
#include <signal.h> #include <signal.h>

View File

@@ -26,6 +26,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <strings.h>
#include <ctype.h> #include <ctype.h>
#include <limits.h> #include <limits.h>
#include <getopt.h> #include <getopt.h>
@@ -80,19 +81,16 @@ enum _OPT_VALUES {
_O_DEVICE_TIMEOUT = 10000, _O_DEVICE_TIMEOUT = 10000,
_O_DEVICE_ERROR_DELAY, _O_DEVICE_ERROR_DELAY,
_O_IMAGE_DEFAULT,
_O_BRIGHTNESS, _O_BRIGHTNESS,
_O_BRIGHTNESS_AUTO,
_O_CONTRAST, _O_CONTRAST,
_O_SATURATION, _O_SATURATION,
_O_HUE, _O_HUE,
_O_HUE_AUTO,
_O_GAMMA, _O_GAMMA,
_O_SHARPNESS, _O_SHARPNESS,
_O_BACKLIGHT_COMPENSATION, _O_BACKLIGHT_COMPENSATION,
_O_WHITE_BALANCE, _O_WHITE_BALANCE,
_O_WHITE_BALANCE_AUTO,
_O_GAIN, _O_GAIN,
_O_GAIN_AUTO,
_O_USER, _O_USER,
_O_PASSWD, _O_PASSWD,
@@ -112,6 +110,7 @@ enum _OPT_VALUES {
#ifdef WITH_SETPROCTITLE #ifdef WITH_SETPROCTITLE
_O_PROCESS_NAME_PREFIX, _O_PROCESS_NAME_PREFIX,
#endif #endif
_O_NOTIFY_PARENT,
_O_LOG_LEVEL, _O_LOG_LEVEL,
_O_PERF, _O_PERF,
@@ -143,19 +142,16 @@ static const struct option _LONG_OPTS[] = {
{"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT}, {"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT},
{"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY}, {"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY},
{"image-default", no_argument, NULL, _O_IMAGE_DEFAULT},
{"brightness", required_argument, NULL, _O_BRIGHTNESS}, {"brightness", required_argument, NULL, _O_BRIGHTNESS},
{"brightness-auto", no_argument, NULL, _O_BRIGHTNESS_AUTO},
{"contrast", required_argument, NULL, _O_CONTRAST}, {"contrast", required_argument, NULL, _O_CONTRAST},
{"saturation", required_argument, NULL, _O_SATURATION}, {"saturation", required_argument, NULL, _O_SATURATION},
{"hue", required_argument, NULL, _O_HUE}, {"hue", required_argument, NULL, _O_HUE},
{"hue-auto", no_argument, NULL, _O_HUE_AUTO},
{"gamma", required_argument, NULL, _O_GAMMA}, {"gamma", required_argument, NULL, _O_GAMMA},
{"sharpness", required_argument, NULL, _O_SHARPNESS}, {"sharpness", required_argument, NULL, _O_SHARPNESS},
{"backlight-compensation", required_argument, NULL, _O_BACKLIGHT_COMPENSATION}, {"backlight-compensation", required_argument, NULL, _O_BACKLIGHT_COMPENSATION},
{"white-balance", required_argument, NULL, _O_WHITE_BALANCE}, {"white-balance", required_argument, NULL, _O_WHITE_BALANCE},
{"white-balance-auto", no_argument, NULL, _O_WHITE_BALANCE_AUTO},
{"gain", required_argument, NULL, _O_GAIN}, {"gain", required_argument, NULL, _O_GAIN},
{"gain-auto", no_argument, NULL, _O_GAIN_AUTO},
{"host", required_argument, NULL, _O_HOST}, {"host", required_argument, NULL, _O_HOST},
{"port", required_argument, NULL, _O_PORT}, {"port", required_argument, NULL, _O_PORT},
@@ -185,6 +181,7 @@ static const struct option _LONG_OPTS[] = {
#ifdef WITH_SETPROCTITLE #ifdef WITH_SETPROCTITLE
{"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX}, {"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX},
#endif #endif
{"notify-parent", no_argument, NULL, _O_NOTIFY_PARENT},
{"log-level", required_argument, NULL, _O_LOG_LEVEL}, {"log-level", required_argument, NULL, _O_LOG_LEVEL},
{"perf", no_argument, NULL, _O_PERF}, {"perf", no_argument, NULL, _O_PERF},
@@ -243,7 +240,7 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \ # define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \ errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
if (errno || *_end || _tmp < _min || _tmp > _max) { \ if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%u, max=%u\n", _name, optarg, _min, _max); \ printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
return -1; \ return -1; \
} \ } \
_dest = _tmp; \ _dest = _tmp; \
@@ -275,16 +272,29 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
break; \ break; \
} }
# define OPT_CTL(_dest) { \ # define OPT_CTL_DEFAULT_NOBREAK(_dest) { \
dev->ctl._dest.value_set = true; \ dev->ctl._dest.mode = CTL_MODE_DEFAULT; \
dev->ctl._dest.auto_set = false; \ }
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
# define OPT_CTL_MANUAL(_dest) { \
if (!strcasecmp(optarg, "default")) { \
OPT_CTL_DEFAULT_NOBREAK(_dest); \
} else { \
dev->ctl._dest.mode = CTL_MODE_VALUE; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
} \
break; \ break; \
} }
# define OPT_CTL_AUTO(_dest) { \ # define OPT_CTL_AUTO(_dest) { \
dev->ctl._dest.value_set = false; \ if (!strcasecmp(optarg, "default")) { \
dev->ctl._dest.auto_set = true; \ OPT_CTL_DEFAULT_NOBREAK(_dest); \
} else if (!strcasecmp(optarg, "auto")) { \
dev->ctl._dest.mode = CTL_MODE_AUTO; \
} else { \
dev->ctl._dest.mode = CTL_MODE_VALUE; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
} \
break; \ break; \
} }
@@ -335,19 +345,26 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0); case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0);
case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", dev->error_delay, 1, 60, 0); case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", dev->error_delay, 1, 60, 0);
case _O_BRIGHTNESS: OPT_CTL(brightness); case _O_IMAGE_DEFAULT:
case _O_BRIGHTNESS_AUTO: OPT_CTL_AUTO(brightness); OPT_CTL_DEFAULT_NOBREAK(brightness);
case _O_CONTRAST: OPT_CTL(contrast); OPT_CTL_DEFAULT_NOBREAK(contrast);
case _O_SATURATION: OPT_CTL(saturation); OPT_CTL_DEFAULT_NOBREAK(saturation);
case _O_HUE: OPT_CTL(hue); OPT_CTL_DEFAULT_NOBREAK(hue);
case _O_HUE_AUTO: OPT_CTL_AUTO(hue); OPT_CTL_DEFAULT_NOBREAK(gamma);
case _O_GAMMA: OPT_CTL(gamma); OPT_CTL_DEFAULT_NOBREAK(sharpness);
case _O_SHARPNESS: OPT_CTL(sharpness); OPT_CTL_DEFAULT_NOBREAK(backlight_compensation);
case _O_BACKLIGHT_COMPENSATION: OPT_CTL(backlight_compensation); OPT_CTL_DEFAULT_NOBREAK(white_balance);
case _O_WHITE_BALANCE: OPT_CTL(white_balance); OPT_CTL_DEFAULT_NOBREAK(gain);
case _O_WHITE_BALANCE_AUTO: OPT_CTL_AUTO(white_balance); break;
case _O_GAIN: OPT_CTL(gain); case _O_BRIGHTNESS: OPT_CTL_AUTO(brightness);
case _O_GAIN_AUTO: OPT_CTL_AUTO(gain); case _O_CONTRAST: OPT_CTL_MANUAL(contrast);
case _O_SATURATION: OPT_CTL_MANUAL(saturation);
case _O_HUE: OPT_CTL_AUTO(hue);
case _O_GAMMA: OPT_CTL_MANUAL(gamma);
case _O_SHARPNESS: OPT_CTL_MANUAL(sharpness);
case _O_BACKLIGHT_COMPENSATION: OPT_CTL_MANUAL(backlight_compensation);
case _O_WHITE_BALANCE: OPT_CTL_AUTO(white_balance);
case _O_GAIN: OPT_CTL_AUTO(gain);
case _O_HOST: OPT_SET(server->host, optarg); case _O_HOST: OPT_SET(server->host, optarg);
case _O_PORT: OPT_NUMBER("--port", server->port, 1, 65535, 0); case _O_PORT: OPT_NUMBER("--port", server->port, 1, 65535, 0);
@@ -381,6 +398,7 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
# ifdef WITH_SETPROCTITLE # ifdef WITH_SETPROCTITLE
case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg); case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg);
# endif # endif
case _O_NOTIFY_PARENT: OPT_SET(server->notify_parent, true);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0); case _O_LOG_LEVEL: OPT_NUMBER("--log-level", log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(log_level, LOG_LEVEL_PERF); case _O_PERF: OPT_SET(log_level, LOG_LEVEL_PERF);
@@ -405,7 +423,8 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
# endif # endif
# undef OPT_CTL_AUTO # undef OPT_CTL_AUTO
# undef OPT_CTL # undef OPT_CTL_MANUAL
# undef OPT_CTL_DEFAULT_NOBREAK
# undef OPT_PARSE # undef OPT_PARSE
# undef OPT_RESOLUTION # undef OPT_RESOLUTION
# undef OPT_NUMBER # undef OPT_NUMBER
@@ -562,19 +581,17 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
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(" --image-default ────────────────────── Reset all image settings bellow to default. Default: no change.\n\n");
printf(" --brightness-auto ──────────── Enable automatic brightness control. Default: no change.\n\n"); printf(" --brightness <N|auto|default> ──────── Set brightness. Default: no change.\n\n");
printf(" --contrast <N> ─────────────── Set contrast. Default: no change.\n\n"); printf(" --contrast <N|default> ─────────────── Set contrast. Default: no change.\n\n");
printf(" --saturation <N> ───────────── Set saturation. Default: no change.\n\n"); printf(" --saturation <N|default> ───────────── Set saturation. Default: no change.\n\n");
printf(" --hue <N> ──────────────────── Set hue. Default: no change.\n\n"); printf(" --hue <N|auto|default> ─────────────── Set hue. Default: no change.\n\n");
printf(" --hue-auto ─────────────────── Enable automatic hue control. Default: no change.\n\n"); printf(" --gamma <N|default> ─────────────────── Set gamma. Default: no change.\n\n");
printf(" --gamma <N> ────────────────── Set gamma. Default: no change.\n\n"); printf(" --sharpness <N|default> ────────────── Set sharpness. Default: no change.\n\n");
printf(" --sharpness <N> ────────────── Set sharpness. Default: no change.\n\n"); printf(" --backlight-compensation <N|default> ─ Set backlight compensation. Default: no change.\n\n");
printf(" --backlight-compensation <N> ─ Set backlight compensation. Default: no change.\n\n"); printf(" --white-balance <N|auto|default> ───── Set white balance. Default: no change.\n\n");
printf(" --white-balance <N> ────────── Set white balance. Default: no change.\n\n"); printf(" --gain <N|auto|default> ────────────── Set gain. Default: no change.\n\n");
printf(" --white-balance-auto ───────── Enable automatic white balance control. Default: no change.\n\n"); printf(" Hint: use v4l2-ctl --list-ctrls-menus to query available controls of the device.\n\n");
printf(" --gain <N> ─────────────────── Set gain. Default: no change.\n\n");
printf(" --gain-auto ────────────────── Enable automatic gain control. Default: no change.\n\n");
printf("HTTP server options:\n"); printf("HTTP server options:\n");
printf("════════════════════\n"); printf("════════════════════\n");
printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s.\n\n", server->host); printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s.\n\n", server->host);
@@ -598,7 +615,7 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf(" or webcams, it's useless. Default: disabled.\n\n"); printf(" or webcams, it's useless. Default: disabled.\n\n");
printf(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients are connected.\n"); printf(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients are connected.\n");
printf(" Useful to reduce CPU consumption. Default: disabled.\n\n"); printf(" Useful to reduce CPU consumption. Default: disabled.\n\n");
printf(" -R|--fake-resolution <WxH> ─ Override image resolution for state. Default: disabled.\n\n"); printf(" -R|--fake-resolution <WxH> ─ Override image resolution for the /state. Default: disabled.\n\n");
printf(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n\n", server->timeout); printf(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n\n", server->timeout);
#ifdef WITH_GPIO #ifdef WITH_GPIO
printf("GPIO options:\n"); printf("GPIO options:\n");
@@ -619,13 +636,15 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
#ifdef WITH_SETPROCTITLE #ifdef WITH_SETPROCTITLE
printf(" --process-name-prefix <str> ── Set process name prefix which will be displayed in the process list\n"); printf(" --process-name-prefix <str> ── Set process name prefix which will be displayed in the process list\n");
printf(" like '<str>: ustreamer --blah-blah-blah'. Default: disabled.\n\n"); printf(" like '<str>: ustreamer --blah-blah-blah'. Default: disabled.\n\n");
printf(" --notify-parent ────────────── Send SIGUSR2 to the parent process when the stream parameters are changed.\n");
printf(" Checking changes is performed for the online flag and image resolution.\n\n");
#endif #endif
printf("Logging options:\n"); printf("Logging options:\n");
printf("════════════════\n"); printf("════════════════\n");
printf(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).\n"); printf(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).\n");
printf(" Enabling debugging messages can slow down the program.\n"); printf(" Enabling debugging messages can slow down the program.\n");
printf(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).\n"); printf(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).\n");
printf(" Default: %u.\n\n", log_level); printf(" Default: %d.\n\n", log_level);
printf(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n"); printf(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n");
printf(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n"); printf(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n");
printf(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n"); printf(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");

View File

@@ -22,6 +22,12 @@
#pragma once #pragma once
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#if defined(__linux__) #if defined(__linux__)
# define HAS_PDEATHSIG # define HAS_PDEATHSIG
#elif defined(__FreeBSD__) #elif defined(__FreeBSD__)
@@ -32,19 +38,14 @@
#endif #endif
#ifdef HAS_PDEATHSIG
# include <signal.h>
# include <unistd.h>
#endif
#ifdef WITH_SETPROCTITLE #ifdef WITH_SETPROCTITLE
# include <stdlib.h> # include <stdlib.h>
# include <string.h> # include <string.h>
# if defined(__linux__) # if defined(__linux__)
# include <bsd/unistd.h> # include <bsd/unistd.h>
# include <sys/types.h>
# elif (defined(__FreeBSD__) || defined(__DragonFly__)) # elif (defined(__FreeBSD__) || defined(__DragonFly__))
# include <unistd.h> //# include <unistd.h>
# include <sys/types.h> //# include <sys/types.h>
# elif (defined(__NetBSD__) || defined(__OpenBSD__)) // setproctitle() placed in stdlib.h # elif (defined(__NetBSD__) || defined(__OpenBSD__)) // setproctitle() placed in stdlib.h
# else # else
# error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0 # error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0
@@ -72,6 +73,7 @@ extern char **environ;
#ifdef HAS_PDEATHSIG #ifdef HAS_PDEATHSIG
INLINE int process_track_parent_death(void) { INLINE int process_track_parent_death(void) {
pid_t parent = getppid();
int signum = SIGTERM; int signum = SIGTERM;
# if defined(__linux__) # if defined(__linux__)
int retval = prctl(PR_SET_PDEATHSIG, signum); int retval = prctl(PR_SET_PDEATHSIG, signum);
@@ -85,8 +87,8 @@ INLINE int process_track_parent_death(void) {
return -1; return -1;
} }
if (kill(getppid(), 0) < 0) { if (kill(parent, 0) < 0) {
LOG_PERROR("The parent process is already dead"); LOG_PERROR("The parent process %d is already dead", parent);
return -1; return -1;
} }
@@ -103,13 +105,12 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
char *cmdline = NULL; char *cmdline = NULL;
size_t allocated = 2048; size_t allocated = 2048;
size_t used = 0; size_t used = 0;
size_t arg_len = 0;
A_REALLOC(cmdline, allocated); A_REALLOC(cmdline, allocated);
cmdline[0] = '\0'; cmdline[0] = '\0';
for (int index = 0; index < argc; ++index) { 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) { if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048; allocated += arg_len + 2048;
A_REALLOC(cmdline, allocated); A_REALLOC(cmdline, allocated);
@@ -128,3 +129,11 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
free(cmdline); free(cmdline);
} }
#endif #endif
INLINE void process_notify_parent(void) {
pid_t parent = getppid();
if (kill(parent, SIGUSR2) < 0) {
LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
}
}

View File

@@ -51,7 +51,7 @@ struct _worker_t {
long double last_comp_time; long double last_comp_time;
pthread_mutex_t has_job_mutex; pthread_mutex_t has_job_mutex;
int buf_index; unsigned buf_index;
atomic_bool has_job; atomic_bool has_job;
bool job_timely; bool job_timely;
bool job_failed; 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_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex);
A_MUTEX_UNLOCK(&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 = pool->oldest_worker;
ready_worker->job_timely = true; ready_worker->job_timely = true;
pool->oldest_worker = pool->oldest_worker->order_next; pool->oldest_worker = pool->oldest_worker->order_next;

View File

@@ -41,9 +41,9 @@
#ifdef PTHREAD_MAX_NAMELEN_NP #ifdef PTHREAD_MAX_NAMELEN_NP
# define MAX_THREAD_NAME PTHREAD_MAX_NAMELEN_NP # define MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
#else #else
# define MAX_THREAD_NAME 16 # define MAX_THREAD_NAME ((size_t)16)
#endif #endif
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg)) #define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
@@ -51,9 +51,9 @@
#ifdef WITH_PTHREAD_NP #ifdef WITH_PTHREAD_NP
# define A_THREAD_RENAME(_fmt, ...) { \ # define A_THREAD_RENAME(_fmt, ...) { \
char _buf[MAX_THREAD_NAME] = {0}; \ char _new_tname_buf[MAX_THREAD_NAME] = {0}; \
assert(snprintf(_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \ assert(snprintf(_new_tname_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \
thread_set_name(_buf); \ thread_set_name(_new_tname_buf); \
} }
#else #else
# define A_THREAD_RENAME(_fmt, ...) # define A_THREAD_RENAME(_fmt, ...)

View File

@@ -45,6 +45,10 @@ INLINE char *bool_to_string(bool flag) {
return (flag ? "true" : "false"); 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) { INLINE unsigned min_u(unsigned a, unsigned b) {
return (a < b ? a : b); return (a < b ? a : b);
} }

View File

@@ -30,9 +30,10 @@
#include "logging.h" #include "logging.h"
#ifndef XIOCTL_RETRIES #ifndef CFG_XIOCTL_RETRIES
# define XIOCTL_RETRIES 4 # define CFG_XIOCTL_RETRIES 4
#endif #endif
#define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES))
INLINE int xioctl(int fd, int request, void *arg) { 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) { 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; return retval;
} }

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
#============================================================================# # ========================================================================== #
# # # #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. # # uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# # # #
@@ -18,7 +18,7 @@
# You should have received a copy of the GNU General Public License # # You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. # # along with this program. If not, see <https://www.gnu.org/licenses/>. #
# # # #
#============================================================================# # ========================================================================== #
import textwrap import textwrap

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env -S python3 -B #!/usr/bin/env -S python3 -B
#============================================================================# # ========================================================================== #
# # # #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. # # uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# # # #
@@ -18,7 +18,7 @@
# You should have received a copy of the GNU General Public License # # You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. # # along with this program. If not, see <https://www.gnu.org/licenses/>. #
# # # #
#============================================================================# # ========================================================================== #
import sys import sys

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env -S python3 -B #!/usr/bin/env -S python3 -B
#============================================================================# # ========================================================================== #
# # # #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. # # uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# # # #
@@ -18,7 +18,7 @@
# You should have received a copy of the GNU General Public License # # You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. # # along with this program. If not, see <https://www.gnu.org/licenses/>. #
# # # #
#============================================================================# # ========================================================================== #
import sys import sys
@@ -26,7 +26,6 @@ import io
import struct import struct
from typing import Tuple from typing import Tuple
from typing import List
import common import common
@@ -38,14 +37,15 @@ def _get_jpeg_size(data: bytes) -> Tuple[int, int]:
stream = io.BytesIO(data) stream = io.BytesIO(data)
while True: while True:
marker = struct.unpack(">H", stream.read(2))[0] marker = struct.unpack(">H", stream.read(2))[0]
if marker == 0xFFD9:
raise RuntimeError("Can't find jpeg size")
if ( if (
marker == 0xFFD8 # Start of image marker == 0xFFD8 # Start of image
or marker == 0xFF01 # Private marker or marker == 0xFF01 # Private marker
or (marker >= 0xFFD0 and marker <= 0xFFD7) # Restart markers or 0xFFD0 <= marker <= 0xFFD7 # Restart markers
): ):
continue continue
elif marker == 0xFFD9:
raise RuntimeError("Can't find jpeg size")
# All other markers specify chunks with lengths # All other markers specify chunks with lengths
length = struct.unpack(">H", stream.read(2))[0] length = struct.unpack(">H", stream.read(2))[0]