Compare commits

...

49 Commits
v1.7 ... v1.16

Author SHA1 Message Date
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
Devaev Maxim
cc00d0fea3 Bump version: 1.9 → 1.10 2019-10-21 08:20:45 +03:00
Devaev Maxim
91a1e48a7c refactoring; renamed X-UStreamer-*-Time to X-UStreamer-*-Timestamp 2019-10-21 08:16:19 +03:00
Devaev Maxim
f381113d50 new arch pkg extension 2019-10-17 02:58:56 +03:00
Devaev Maxim
a6304959f4 Bump version: 1.8 → 1.9 2019-10-11 23:37:25 +03:00
Devaev Maxim
57bc6e160d keep original argv 2019-10-11 23:37:05 +03:00
Devaev Maxim
03dd5dfebb Bump version: 1.7 → 1.8 2019-10-11 08:23:40 +03:00
Devaev Maxim
00ef8928b3 temporary disabled setproctitle to avoid memory corruption 2019-10-11 08:23:04 +03:00
42 changed files with 607 additions and 242 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion] [bumpversion]
commit = True commit = True
tag = True tag = True
current_version = 1.7 current_version = 1.16
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}

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

3
.gitignore vendored
View File

@@ -1,7 +1,10 @@
/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
/pkg/arch/ustreamer-*.pkg.tar.xz /pkg/arch/ustreamer-*.pkg.tar.xz
/pkg/arch/ustreamer-*.pkg.tar.zst
/build/ /build/
/config.mk /config.mk
/vgcore.* /vgcore.*

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 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 libevent-pthreads-2.1-6-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,10 @@ $ ./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```.
-----
# Tips
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16)
----- -----
# 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 libevent-pthreads-2.1-6-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,10 @@ $ ./ustreamer \
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```. За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
-----
# Советы
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16)
----- -----
# Лицензия # Лицензия
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}$

48
linters/tox.ini Normal file
View File

@@ -0,0 +1,48 @@
[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'
# FIXME: pyflakes from master to support walrus
deps =
git+https://github.com/PyCQA/pyflakes@1911c20#egg=pyflakes
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.7 pkgver=1.16
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.7 PKG_VERSION:=1.16
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.7" # define VERSION "1.16"
#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);
@@ -286,7 +287,7 @@ int device_grab_buffer(struct device_t *dev) {
dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused; dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused;
memcpy(&dev->run->hw_buffers[buf_info.index].buf_info, &buf_info, sizeof(struct v4l2_buffer)); memcpy(&dev->run->hw_buffers[buf_info.index].buf_info, &buf_info, sizeof(struct v4l2_buffer));
dev->run->pictures[buf_info.index]->grab_time = get_now_monotonic(); dev->run->pictures[buf_info.index]->grab_ts = get_now_monotonic();
return buf_info.index; return buf_info.index;
} }
@@ -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
@@ -201,7 +202,7 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN); assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
dev->run->pictures[buf_index]->encode_begin_time = get_now_monotonic(); dev->run->pictures[buf_index]->encode_begin_ts = get_now_monotonic();
if (encoder->run->type == ENCODER_TYPE_CPU) { if (encoder->run->type == ENCODER_TYPE_CPU) {
cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality); cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality);
@@ -216,7 +217,7 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
} }
# endif # endif
dev->run->pictures[buf_index]->encode_end_time = get_now_monotonic(); dev->run->pictures[buf_index]->encode_end_ts = get_now_monotonic();
dev->run->pictures[buf_index]->width = dev->run->width; dev->run->pictures[buf_index]->width = dev->run->width;
dev->run->pictures[buf_index]->height = dev->run->height; dev->run->pictures[buf_index]->height = dev->run->height;
@@ -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

@@ -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) {
@@ -76,6 +76,8 @@ const char *omx_state_to_string(OMX_STATETYPE state) {
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);
// 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"
@@ -157,6 +158,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));
@@ -172,9 +174,12 @@ int http_server_listen(struct http_server_t *server) {
# define EXPOSED(_next) server->run->exposed->_next # define EXPOSED(_next) server->run->exposed->_next
// See _expose_blank_picture() // See _expose_blank_picture()
picture_copy(server->run->blank, EXPOSED(picture)); picture_copy(server->run->blank, EXPOSED(picture));
EXPOSED(expose_begin_time) = get_now_monotonic(); EXPOSED(expose_begin_ts) = get_now_monotonic();
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time); EXPOSED(expose_cmp_ts) = EXPOSED(expose_begin_ts);
EXPOSED(expose_end_time) = EXPOSED(expose_begin_time); 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 +331,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;
} }
@@ -446,17 +451,17 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
ADD_TIME_HEADER("X-Timestamp", get_now_real()); ADD_TIME_HEADER("X-Timestamp", get_now_real());
ADD_HEADER("X-UStreamer-Online", bool_to_string(EXPOSED(online))); ADD_HEADER("X-UStreamer-Online", bool_to_string(EXPOSED(online)));
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", EXPOSED(dropped)); ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", EXPOSED(dropped));
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(picture->width)); ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(picture->width));
ADD_UNSIGNED_HEADER("X-UStreamer-Height", EXPOSED(picture->height)); ADD_UNSIGNED_HEADER("X-UStreamer-Height", EXPOSED(picture->height));
ADD_TIME_HEADER("X-UStreamer-Grab-Time", EXPOSED(picture->grab_time)); ADD_TIME_HEADER("X-UStreamer-Grab-Timestamp", EXPOSED(picture->grab_ts));
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Time", EXPOSED(picture->encode_begin_time)); ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Timestamp", EXPOSED(picture->encode_begin_ts));
ADD_TIME_HEADER("X-UStreamer-Encode-End-Time", EXPOSED(picture->encode_end_time)); ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", EXPOSED(picture->encode_end_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Time", EXPOSED(expose_begin_time)); ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Timestamp", EXPOSED(expose_begin_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Time", EXPOSED(expose_cmp_time)); ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Timestamp", EXPOSED(expose_cmp_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-End-Time", EXPOSED(expose_end_time)); ADD_TIME_HEADER("X-UStreamer-Expose-End-Timestamp", EXPOSED(expose_end_ts));
ADD_TIME_HEADER("X-UStreamer-Send-Time", get_now_monotonic()); ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", get_now_monotonic());
# undef ADD_UNSUGNED_HEADER # undef ADD_UNSUGNED_HEADER
# undef ADD_TIME_HEADER # undef ADD_TIME_HEADER
@@ -637,12 +642,12 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
EXPOSED(picture->width), EXPOSED(picture->width),
EXPOSED(picture->height), EXPOSED(picture->height),
client->fps, client->fps,
EXPOSED(picture->grab_time), EXPOSED(picture->grab_ts),
EXPOSED(picture->encode_begin_time), EXPOSED(picture->encode_begin_ts),
EXPOSED(picture->encode_end_time), EXPOSED(picture->encode_end_ts),
EXPOSED(expose_begin_time), EXPOSED(expose_begin_ts),
EXPOSED(expose_cmp_time), EXPOSED(expose_cmp_ts),
EXPOSED(expose_end_time), EXPOSED(expose_end_ts),
now now
)); ));
} }
@@ -675,9 +680,10 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
# define RUN(_next) client->server->run->_next # define RUN(_next) client->server->run->_next
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);
} }
@@ -716,8 +722,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 +755,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 +797,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) {
@@ -797,7 +823,7 @@ static bool _expose_new_picture_unsafe(struct http_server_t *server) {
# define STREAM(_next) server->run->stream->_next # define STREAM(_next) server->run->stream->_next
EXPOSED(captured_fps) = STREAM(captured_fps); EXPOSED(captured_fps) = STREAM(captured_fps);
EXPOSED(expose_begin_time) = get_now_monotonic(); EXPOSED(expose_begin_ts) = get_now_monotonic();
if (server->drop_same_frames) { if (server->drop_same_frames) {
if ( if (
@@ -805,16 +831,16 @@ static bool _expose_new_picture_unsafe(struct http_server_t *server) {
&& EXPOSED(dropped) < server->drop_same_frames && EXPOSED(dropped) < server->drop_same_frames
&& picture_compare(EXPOSED(picture), STREAM(picture)) && picture_compare(EXPOSED(picture), STREAM(picture))
) { ) {
EXPOSED(expose_cmp_time) = get_now_monotonic(); EXPOSED(expose_cmp_ts) = get_now_monotonic();
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time); EXPOSED(expose_end_ts) = EXPOSED(expose_cmp_ts);
LOG_VERBOSE("HTTP: Dropped same frame number %u; cmp_time=%.06Lf", LOG_VERBOSE("HTTP: Dropped same frame number %u; cmp_time=%.06Lf",
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)); EXPOSED(dropped), EXPOSED(expose_cmp_ts) - EXPOSED(expose_begin_ts));
EXPOSED(dropped) += 1; EXPOSED(dropped) += 1;
return false; // Not updated return false; // Not updated
} else { } else {
EXPOSED(expose_cmp_time) = get_now_monotonic(); EXPOSED(expose_cmp_ts) = get_now_monotonic();
LOG_VERBOSE("HTTP: Passed same frame check (frames are differ); cmp_time=%.06Lf", LOG_VERBOSE("HTTP: Passed same frame check (frames are differ); cmp_time=%.06Lf",
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time)); EXPOSED(expose_cmp_ts) - EXPOSED(expose_begin_ts));
} }
} }
@@ -824,11 +850,11 @@ static bool _expose_new_picture_unsafe(struct http_server_t *server) {
EXPOSED(online) = true; EXPOSED(online) = true;
EXPOSED(dropped) = 0; EXPOSED(dropped) = 0;
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time); EXPOSED(expose_cmp_ts) = EXPOSED(expose_begin_ts);
EXPOSED(expose_end_time) = get_now_monotonic(); EXPOSED(expose_end_ts) = get_now_monotonic();
LOG_VERBOSE("HTTP: Exposed new frame; full exposition time = %.06Lf", LOG_VERBOSE("HTTP: Exposed new frame; full exposition time = %.06Lf",
EXPOSED(expose_end_time) - EXPOSED(expose_begin_time)); EXPOSED(expose_end_ts) - EXPOSED(expose_begin_ts));
# undef EXPOSED # undef EXPOSED
return true; // Updated return true; // Updated
@@ -837,8 +863,8 @@ 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) {
# define EXPOSED(_next) server->run->exposed->_next # define EXPOSED(_next) server->run->exposed->_next
EXPOSED(expose_begin_time) = get_now_monotonic(); EXPOSED(expose_begin_ts) = get_now_monotonic();
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time); EXPOSED(expose_cmp_ts) = EXPOSED(expose_begin_ts);
# define EXPOSE_BLANK picture_copy(server->run->blank, EXPOSED(picture)) # define EXPOSE_BLANK picture_copy(server->run->blank, EXPOSED(picture))
@@ -848,7 +874,7 @@ static bool _expose_blank_picture(struct http_server_t *server) {
EXPOSE_BLANK; EXPOSE_BLANK;
} else if (server->last_as_blank > 0) { // Если нужен таймер - запустим } else if (server->last_as_blank > 0) { // Если нужен таймер - запустим
LOG_INFO("HTTP: Freezing last alive frame for %d seconds", server->last_as_blank); LOG_INFO("HTTP: Freezing last alive frame for %d seconds", server->last_as_blank);
EXPOSED(last_as_blank_time) = get_now_monotonic(); EXPOSED(last_as_blank_ts) = get_now_monotonic();
} else { // last_as_blank == 0 - показываем последний фрейм вечно } else { // last_as_blank == 0 - показываем последний фрейм вечно
LOG_INFO("HTTP: Freezing last alive frame forever"); LOG_INFO("HTTP: Freezing last alive frame forever");
} }
@@ -857,12 +883,12 @@ static bool _expose_blank_picture(struct http_server_t *server) {
if ( // Если уже оффлайн, включена фича last_as_blank с таймером и он запущен if ( // Если уже оффлайн, включена фича last_as_blank с таймером и он запущен
server->last_as_blank > 0 server->last_as_blank > 0
&& EXPOSED(last_as_blank_time) > 0 && EXPOSED(last_as_blank_ts) > 0
&& EXPOSED(last_as_blank_time) + server->last_as_blank < EXPOSED(expose_begin_time) && EXPOSED(last_as_blank_ts) + server->last_as_blank < EXPOSED(expose_begin_ts)
) { ) {
LOG_INFO("HTTP: Changed last alive frame to BLANK"); LOG_INFO("HTTP: Changed last alive frame to BLANK");
EXPOSE_BLANK; EXPOSE_BLANK;
EXPOSED(last_as_blank_time) = 0; // Останавливаем таймер EXPOSED(last_as_blank_ts) = 0; // Останавливаем таймер
goto updated; goto updated;
} }
@@ -871,7 +897,7 @@ static bool _expose_blank_picture(struct http_server_t *server) {
if (EXPOSED(dropped) < server->run->drop_same_frames_blank) { if (EXPOSED(dropped) < server->run->drop_same_frames_blank) {
LOG_PERF("HTTP: Dropped same frame (BLANK) number %u", EXPOSED(dropped)); LOG_PERF("HTTP: Dropped same frame (BLANK) number %u", EXPOSED(dropped));
EXPOSED(dropped) += 1; EXPOSED(dropped) += 1;
EXPOSED(expose_end_time) = get_now_monotonic(); EXPOSED(expose_end_ts) = get_now_monotonic();
return false; // Not updated return false; // Not updated
} }
@@ -879,7 +905,7 @@ static bool _expose_blank_picture(struct http_server_t *server) {
EXPOSED(captured_fps) = 0; EXPOSED(captured_fps) = 0;
EXPOSED(online) = false; EXPOSED(online) = false;
EXPOSED(dropped) = 0; EXPOSED(dropped) = 0;
EXPOSED(expose_end_time) = get_now_monotonic(); EXPOSED(expose_end_ts) = get_now_monotonic();
return true; // Updated return true; // Updated
# undef EXPOSED # undef EXPOSED

View File

@@ -61,10 +61,14 @@ struct exposed_t {
unsigned queued_fps; unsigned queued_fps;
bool online; bool online;
unsigned dropped; unsigned dropped;
long double expose_begin_time; long double expose_begin_ts;
long double expose_cmp_time; long double expose_cmp_ts;
long double expose_end_time; long double expose_end_ts;
long double last_as_blank_time; 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;
} }

View File

@@ -88,14 +88,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 +112,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(_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 +143,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(_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__); \

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>
@@ -98,6 +103,7 @@ static void _install_signal_handlers(void) {
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
struct options_t *options;
struct device_t *dev; struct device_t *dev;
struct encoder_t *encoder; struct encoder_t *encoder;
struct stream_t *stream; struct stream_t *stream;
@@ -105,8 +111,8 @@ int main(int argc, char *argv[]) {
int exit_code = 0; int exit_code = 0;
LOGGING_INIT; LOGGING_INIT;
A_THREAD_RENAME("main"); A_THREAD_RENAME("main");
options = options_init(argc, argv);
# ifdef WITH_GPIO # ifdef WITH_GPIO
GPIO_INIT; GPIO_INIT;
@@ -117,7 +123,7 @@ int main(int argc, char *argv[]) {
stream = stream_init(dev, encoder); stream = stream_init(dev, encoder);
server = http_server_init(stream); server = http_server_init(stream);
if ((exit_code = parse_options(argc, argv, dev, encoder, server)) == 0) { if ((exit_code = options_parse(options, dev, encoder, server)) == 0) {
# ifdef WITH_GPIO # ifdef WITH_GPIO
GPIO_INIT_PINOUT; GPIO_INIT_PINOUT;
# endif # endif
@@ -153,6 +159,7 @@ int main(int argc, char *argv[]) {
GPIO_SET_LOW(prog_running); GPIO_SET_LOW(prog_running);
# endif # endif
options_destroy(options);
if (exit_code == 0) { if (exit_code == 0) {
LOG_INFO("Bye-bye"); LOG_INFO("Bye-bye");
} }

View File

@@ -25,9 +25,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
#ifdef WITH_OMX #include <string.h>
# include <string.h> #include <strings.h>
#endif
#include <ctype.h> #include <ctype.h>
#include <limits.h> #include <limits.h>
#include <getopt.h> #include <getopt.h>
@@ -82,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,
@@ -114,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,
@@ -145,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},
@@ -187,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},
@@ -212,7 +207,31 @@ static void _features(void);
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server); static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server);
int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) { struct options_t *options_init(int argc, char *argv[]) {
struct options_t *options;
A_CALLOC(options, 1);
options->argc = argc;
options->argv = argv;
A_CALLOC(options->argv_copy, argc);
for (int index = 0; index < argc; ++index) {
assert(options->argv_copy[index] = strdup(argv[index]));
}
return options;
}
void options_destroy(struct options_t *options) {
for (int index = 0; index < options->argc; ++index) {
free(options->argv_copy[index]);
}
free(options->argv_copy);
free(options);
}
int options_parse(struct options_t *options, struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
# define OPT_SET(_dest, _value) { \ # define OPT_SET(_dest, _value) { \
_dest = _value; \ _dest = _value; \
break; \ break; \
@@ -221,7 +240,7 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
# 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; \
@@ -253,16 +272,29 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
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; \
} }
@@ -285,7 +317,7 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
} }
} }
while ((ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0) { while ((ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0) {
switch (ch) { switch (ch) {
case _O_DEVICE: OPT_SET(dev->path, optarg); case _O_DEVICE: OPT_SET(dev->path, optarg);
case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0); case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0);
@@ -313,19 +345,26 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
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);
@@ -359,6 +398,7 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
# 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);
@@ -378,12 +418,13 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
# ifdef WITH_SETPROCTITLE # ifdef WITH_SETPROCTITLE
if (process_name_prefix != NULL) { if (process_name_prefix != NULL) {
process_set_name_prefix(argc, argv, process_name_prefix); process_set_name_prefix(options->argc, options->argv, process_name_prefix);
} }
# 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
@@ -540,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);
@@ -576,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");
@@ -597,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

@@ -27,4 +27,14 @@
#include "http/server.h" #include "http/server.h"
int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server); struct options_t {
int argc;
char **argv;
char **argv_copy;
};
struct options_t *options_init(int argc, char *argv[]);
void options_destroy(struct options_t *options);
int options_parse(struct options_t *options, struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server);

View File

@@ -84,9 +84,9 @@ void picture_copy(const struct picture_t *src, struct picture_t *dest) {
COPY(width); COPY(width);
COPY(height); COPY(height);
COPY(grab_time); COPY(grab_ts);
COPY(encode_begin_time); COPY(encode_begin_ts);
COPY(encode_end_time); COPY(encode_end_ts);
# undef COPY # undef COPY
} }

View File

@@ -32,9 +32,9 @@ struct picture_t {
size_t allocated; size_t allocated;
unsigned width; unsigned width;
unsigned height; unsigned height;
long double grab_time; long double grab_ts;
long double encode_begin_time; long double encode_begin_ts;
long double encode_end_time; long double encode_end_ts;
}; };

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,11 +51,11 @@ 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;
long double job_start_time; long double job_start_ts;
pthread_cond_t has_job_cond; pthread_cond_t has_job_cond;
pthread_mutex_t *free_workers_mutex; pthread_mutex_t *free_workers_mutex;
@@ -454,10 +454,10 @@ static void *_worker_thread(void *v_worker) {
} }
if (device_release_buffer(worker->dev, worker->buf_index) == 0) { if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
worker->job_start_time = PICTURE(encode_begin_time); worker->job_start_ts = PICTURE(encode_begin_ts);
atomic_store(&worker->has_job, false); atomic_store(&worker->has_job, false);
worker->last_comp_time = PICTURE(encode_end_time) - worker->job_start_time; worker->last_comp_time = PICTURE(encode_end_ts) - worker->job_start_ts;
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%u, buffer=%u", LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%u, buffer=%u",
PICTURE(used), worker->last_comp_time, worker->number, worker->buf_index); PICTURE(used), worker->last_comp_time, worker->number, worker->buf_index);
@@ -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;
@@ -498,7 +498,7 @@ static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool) {
if ( if (
!atomic_load(&pool->workers[number].has_job) && ( !atomic_load(&pool->workers[number].has_job) && (
ready_worker == NULL ready_worker == NULL
|| ready_worker->job_start_time < pool->workers[number].job_start_time || ready_worker->job_start_ts < pool->workers[number].job_start_ts
) )
) { ) {
ready_worker = &pool->workers[number]; ready_worker = &pool->workers[number];

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

@@ -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]