Compare commits

...

63 Commits
v1.9 ... v1.19

Author SHA1 Message Date
Devaev Maxim
8637ff5c09 Bump version: 1.18 → 1.19 2020-07-11 04:43:55 +03:00
Devaev Maxim
bd5cf7d3de fixed io method parser 2020-07-08 00:38:49 +03:00
Devaev Maxim
e488eec90c userptr 2020-07-07 09:13:07 +03:00
Devaev Maxim
6615a23361 safer picture_compare(), removed one assert 2020-07-06 14:36:05 +03:00
Devaev Maxim
a91eba8d90 fixed picture_copy(): don't copy garbage 2020-07-06 13:10:49 +03:00
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
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
45 changed files with 802 additions and 266 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 1.9
current_version = 1.19
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
serialize =
{major}.{minor}
@@ -17,4 +17,3 @@ replace = pkgver={new_version}
[bumpversion:file:pkg/openwrt/Makefile]
search = PKG_VERSION:={current_version}
replace = PKG_VERSION:={new_version}

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

3
.gitignore vendored
View File

@@ -1,7 +1,10 @@
/linters/.tox/
/linters/.mypy_cache/
/pkg/arch/pkg/
/pkg/arch/src/
/pkg/arch/v*.tar.gz
/pkg/arch/ustreamer-*.pkg.tar.xz
/pkg/arch/ustreamer-*.pkg.tar.zst
/build/
/config.mk
/vgcore.*

View File

@@ -13,6 +13,8 @@ RPI_VC_LIBS ?= /opt/vc/lib
BUILD ?= build
LINTERS_IMAGE ?= $(PROG)-linters
# =====
_LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
@@ -87,17 +89,36 @@ $(PROG): $(_SRCS:%.c=$(BUILD)/%.o)
$(BUILD)/%.o: %.c
$(info -- CC $<)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(CFLAGS) $(_LIBS)
@ $(CC) $< -o $@ $(CFLAGS)
release:
make clean
make tox
make push
make bump
make push
make clean
tox: linters
time docker run --rm \
--volume `pwd`:/src:ro \
--volume `pwd`/linters:/src/linters:rw \
-t $(LINTERS_IMAGE) bash -c " \
cd /src \
&& tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \
"
linters:
docker build \
$(if $(call optbool,$(NC)),--no-cache,) \
--rm \
--tag $(LINTERS_IMAGE) \
-f linters/Dockerfile linters
bump:
bumpversion $(if $(V),$(V),minor)
@@ -107,7 +128,12 @@ push:
git push --tags
clean-all: clean
clean-all: linters clean
- docker run --rm \
--volume `pwd`:/src \
-it $(LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
clean:
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.xz
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
rm -rf $(PROG) $(BUILD) vgcore.* *.sock
.PHONY: linters

View File

@@ -1,4 +1,7 @@
# µ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)
µ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
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```.
```
@@ -64,6 +71,11 @@ $ ./ustreamer \
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
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)
@@ -32,6 +35,10 @@
# Сборка
Для сборки вам понадобятся ```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```.
```
@@ -64,6 +71,11 @@ $ ./ustreamer \
За полным списком опций обращайтесь ко встроенной справке: ```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

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
pkgver=1.9
pkgver=1.19
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"

View File

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

View File

@@ -23,5 +23,5 @@
#pragma once
#ifndef VERSION
# define VERSION "1.9"
# define VERSION "1.19"
#endif

View File

@@ -67,24 +67,36 @@ static const struct {
{"JPEG", V4L2_PIX_FMT_JPEG},
};
static const struct {
const char *name;
const enum v4l2_memory io_method;
} _IO_METHODS[] = {
{"MMAP", V4L2_MEMORY_MMAP},
{"USERPTR", V4L2_MEMORY_USERPTR},
};
static int _device_open_check_cap(struct device_t *dev);
static int _device_open_dv_timings(struct device_t *dev);
static int _device_apply_dv_timings(struct device_t *dev);
static int _device_open_format(struct device_t *dev);
static void _device_open_hw_fps(struct device_t *dev);
static int _device_open_mmap(struct device_t *dev);
static int _device_open_io_method(struct device_t *dev);
static int _device_open_io_method_mmap(struct device_t *dev);
static int _device_open_io_method_userptr(struct device_t *dev);
static int _device_open_queue_buffers(struct device_t *dev);
static void _device_open_alloc_picbufs(struct device_t *dev);
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height);
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 void _device_set_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, 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_nullable(unsigned format);
static const char *_format_to_string_supported(unsigned format);
static const char *_standard_to_string(v4l2_std_id standard);
static const char *_io_method_to_string_supported(enum v4l2_memory io_method);
struct device_t *device_init(void) {
@@ -110,6 +122,7 @@ struct device_t *device_init(void) {
dev->n_workers = min_u(cores_available, dev->n_buffers);
dev->timeout = 1;
dev->error_delay = 1;
dev->io_method = V4L2_MEMORY_MMAP;
dev->run = run;
return dev;
}
@@ -137,6 +150,15 @@ v4l2_std_id device_parse_standard(const char *str) {
return STANDARD_UNKNOWN;
}
int device_parse_io_method(const char *str) {
for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) {
if (!strcasecmp(str, _IO_METHODS[index].name)) {
return _IO_METHODS[index].io_method;
}
}
return IO_METHOD_UNKNOWN;
}
int device_open(struct device_t *dev) {
if ((dev->run->fd = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
LOG_PERROR("Can't open device");
@@ -154,7 +176,7 @@ int device_open(struct device_t *dev) {
goto error;
}
_device_open_hw_fps(dev);
if (_device_open_mmap(dev) < 0) {
if (_device_open_io_method(dev) < 0) {
goto error;
}
if (_device_open_queue_buffers(dev) < 0) {
@@ -186,13 +208,19 @@ void device_close(struct device_t *dev) {
}
if (dev->run->hw_buffers) {
LOG_DEBUG("Unmapping HW buffers ...");
LOG_DEBUG("Releasing HW buffers ...");
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
if (HW_BUFFER(allocated) > 0 && HW_BUFFER(data) != MAP_FAILED) {
if (munmap(HW_BUFFER(data), HW_BUFFER(allocated)) < 0) {
LOG_PERROR("Can't unmap device buffer %u", index);
if (dev->io_method == V4L2_MEMORY_MMAP) {
if (HW_BUFFER(allocated) > 0 && HW_BUFFER(data) != MAP_FAILED) {
if (munmap(HW_BUFFER(data), HW_BUFFER(allocated)) < 0) {
LOG_PERROR("Can't unmap device buffer %u", index);
}
}
} else { // V4L2_MEMORY_USERPTR
if (HW_BUFFER(data)) {
free(HW_BUFFER(data));
}
}
@@ -256,9 +284,9 @@ int device_select(struct device_t *dev, bool *has_read, bool *has_write, bool *h
*has_write = FD_ISSET(dev->run->fd, &write_fds);
*has_error = FD_ISSET(dev->run->fd, &error_fds);
} else {
has_read = false;
has_write = false;
has_error = false;
*has_read = false;
*has_write = false;
*has_error = false;
}
LOG_DEBUG("Device select() --> %d", retval);
@@ -270,7 +298,7 @@ int device_grab_buffer(struct device_t *dev) {
MEMSET_ZERO(buf_info);
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = V4L2_MEMORY_MMAP;
buf_info.memory = dev->io_method;
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_DQBUF, &buf_info) < 0) {
@@ -286,7 +314,7 @@ int device_grab_buffer(struct device_t *dev) {
dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused;
memcpy(&dev->run->hw_buffers[buf_info.index].buf_info, &buf_info, sizeof(struct v4l2_buffer));
dev->run->pictures[buf_info.index]->grab_time = get_now_monotonic();
dev->run->pictures[buf_info.index]->grab_ts = get_now_monotonic();
return buf_info.index;
}
@@ -390,7 +418,7 @@ static int _device_apply_dv_timings(struct device_t *dev) {
LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) {
LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu",
dv.bt.width, dv.bt.height, dv.bt.pixelclock);
dv.bt.width, dv.bt.height, (unsigned long long)dv.bt.pixelclock); // Issue #11
LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ...");
if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv) < 0) {
@@ -464,6 +492,8 @@ static int _device_open_format(struct device_t *dev) {
dev->run->format = fmt.fmt.pix.pixelformat;
LOG_INFO("Using pixelformat: %s", _format_to_string_supported(dev->run->format));
dev->run->raw_size = fmt.fmt.pix.sizeimage; // Only for userptr
return 0;
}
@@ -509,7 +539,17 @@ static void _device_open_hw_fps(struct device_t *dev) {
# undef SETFPS_TPF
}
static int _device_open_mmap(struct device_t *dev) {
static int _device_open_io_method(struct device_t *dev) {
LOG_INFO("Using IO method: %s", _io_method_to_string_supported(dev->io_method));
switch (dev->io_method) {
case V4L2_MEMORY_MMAP: return _device_open_io_method_mmap(dev);
case V4L2_MEMORY_USERPTR: return _device_open_io_method_userptr(dev);
default: assert(0 && "Unsupported IO method");
}
return -1;
}
static int _device_open_io_method_mmap(struct device_t *dev) {
struct v4l2_requestbuffers req;
MEMSET_ZERO(req);
@@ -517,9 +557,9 @@ static int _device_open_mmap(struct device_t *dev) {
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) ...");
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_MMAP ...");
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req) < 0) {
LOG_PERROR("Device '%s' doesn't support memory mapping", dev->path);
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_MMAP", dev->path);
return -1;
}
@@ -562,14 +602,56 @@ static int _device_open_mmap(struct device_t *dev) {
return 0;
}
static int _device_open_io_method_userptr(struct device_t *dev) {
struct v4l2_requestbuffers req;
unsigned page_size = getpagesize();
unsigned buf_size = align_size(dev->run->raw_size, page_size);
MEMSET_ZERO(req);
req.count = dev->n_buffers;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_USERPTR;
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_USERPTR ...");
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req) < 0) {
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_USERPTR", dev->path);
return -1;
}
if (req.count < 1) {
LOG_ERROR("Insufficient buffer memory: %u", req.count);
return -1;
} else {
LOG_INFO("Requested %u HW buffers, got %u", dev->n_buffers, req.count);
}
LOG_DEBUG("Allocating HW buffers ...");
A_CALLOC(dev->run->hw_buffers, req.count);
for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) {
# define HW_BUFFER(_next) dev->run->hw_buffers[dev->run->n_buffers]._next
assert(HW_BUFFER(data) = aligned_alloc(page_size, buf_size));
memset(HW_BUFFER(data), 0, buf_size);
HW_BUFFER(allocated) = buf_size;
# undef HW_BUFFER
}
return 0;
}
static int _device_open_queue_buffers(struct device_t *dev) {
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
struct v4l2_buffer buf_info;
MEMSET_ZERO(buf_info);
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = V4L2_MEMORY_MMAP;
buf_info.memory = dev->io_method;
buf_info.index = index;
if (dev->io_method == V4L2_MEMORY_USERPTR) {
buf_info.m.userptr = (unsigned long)dev->run->hw_buffers[index].data;
buf_info.length = dev->run->hw_buffers[index].allocated;
}
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %u ...", index);
if (xioctl(dev->run->fd, VIDIOC_QBUF, &buf_info) < 0) {
@@ -610,65 +692,82 @@ static int _device_apply_resolution(struct device_t *dev, unsigned width, unsign
}
static void _device_apply_controls(struct device_t *dev) {
# define SET_CID(_cid, _dest, _value, _quiet) { \
if (_device_check_control(dev, #_dest, _cid, _value, _quiet) == 0) { \
_device_set_control(dev, #_dest, _cid, _value, _quiet); \
# define SET_CID_VALUE(_cid, _field, _value, _quiet) { \
struct v4l2_queryctrl query; \
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) { \
if (dev->ctl._dest.value_set) { \
SET_CID(_cid, _dest, dev->ctl._dest.value, false); \
# define SET_CID_DEFAULT(_cid, _field, _quiet) { \
struct v4l2_queryctrl query; \
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) { \
if (dev->ctl._dest.value_set || dev->ctl._dest.auto_set) { \
SET_CID(_cid_auto, _dest##_auto, dev->ctl._dest.auto_set, dev->ctl._dest.value_set); \
SET_CID_MANUAL(_cid_manual, _dest); \
# define CONTROL_MANUAL_CID(_cid, _field) { \
if (dev->ctl._field.mode == CTL_MODE_VALUE) { \
SET_CID_VALUE(_cid, _field, dev->ctl._field.value, false); \
} 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);
SET_CID_MANUAL ( V4L2_CID_CONTRAST, contrast);
SET_CID_MANUAL ( V4L2_CID_SATURATION, saturation);
SET_CID_AUTO (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue);
SET_CID_MANUAL ( V4L2_CID_GAMMA, gamma);
SET_CID_MANUAL ( V4L2_CID_SHARPNESS, sharpness);
SET_CID_MANUAL ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation);
SET_CID_AUTO (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance);
SET_CID_AUTO (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain);
# define CONTROL_AUTO_CID(_cid_auto, _cid_manual, _field) { \
if (dev->ctl._field.mode == CTL_MODE_VALUE) { \
SET_CID_VALUE(_cid_auto, _field##_auto, 0, true); \
SET_CID_VALUE(_cid_manual, _field, dev->ctl._field.value, false); \
} else if (dev->ctl._field.mode == CTL_MODE_AUTO) { \
SET_CID_VALUE(_cid_auto, _field##_auto, 1, false); \
} else if (dev->ctl._field.mode == CTL_MODE_DEFAULT) { \
SET_CID_VALUE(_cid_auto, _field##_auto, 0, true); /* Reset inactive flag */ \
SET_CID_DEFAULT(_cid_manual, _field, false); \
SET_CID_DEFAULT(_cid_auto, _field##_auto, false); \
} \
}
# undef SET_CID_AUTO
# undef SET_CID_MANUAL
# undef SET_CID
CONTROL_AUTO_CID (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness);
CONTROL_MANUAL_CID ( V4L2_CID_CONTRAST, contrast);
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) {
struct v4l2_queryctrl query;
static int _device_query_control(struct device_t *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, bool quiet) {
// cppcheck-suppress redundantPointerOp
MEMSET_ZERO(*query);
query->id = cid;
MEMSET_ZERO(query);
query.id = cid;
if (xioctl(dev->run->fd, VIDIOC_QUERYCTRL, &query) < 0 || query.flags & V4L2_CTRL_FLAG_DISABLED) {
if (xioctl(dev->run->fd, VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) {
if (!quiet) {
LOG_ERROR("Changing control %s is unsupported", name);
}
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;
}
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;
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);
ctl.id = cid;
ctl.value = value;
@@ -678,7 +777,7 @@ static void _device_set_control(struct device_t *dev, const char *name, unsigned
LOG_PERROR("Can't set control %s", name);
}
} else if (!quiet) {
LOG_INFO("Using control %s: %d", name, ctl.value);
LOG_INFO("Applying control %s: %d", name, ctl.value);
}
}
@@ -688,7 +787,7 @@ static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned for
buf[1] = (format >> 8) & 0x7F;
buf[2] = (format >> 16) & 0x7F;
buf[3] = (format >> 24) & 0x7F;
if (format & (1 << 31)) {
if (format & ((unsigned)1 << 31)) {
buf[4] = '-';
buf[5] = 'B';
buf[6] = 'E';
@@ -721,3 +820,12 @@ static const char *_standard_to_string(v4l2_std_id standard) {
}
return _STANDARDS[0].name;
}
static const char *_io_method_to_string_supported(enum v4l2_memory io_method) {
for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) {
if (io_method == _IO_METHODS[index].io_method) {
return _IO_METHODS[index].name;
}
}
return "unsupported";
}

View File

@@ -30,13 +30,13 @@
#include "picture.h"
#define VIDEO_MIN_WIDTH 160
#define VIDEO_MAX_WIDTH 10240
#define VIDEO_MIN_WIDTH ((unsigned)160)
#define VIDEO_MAX_WIDTH ((unsigned)10240)
#define VIDEO_MIN_HEIGHT 120
#define VIDEO_MAX_HEIGHT 4320
#define VIDEO_MIN_HEIGHT ((unsigned)120)
#define VIDEO_MAX_HEIGHT ((unsigned)4320)
#define VIDEO_MAX_FPS 120
#define VIDEO_MAX_FPS ((unsigned)120)
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
#define STANDARDS_STR "PAL, NTSC, SECAM"
@@ -44,6 +44,9 @@
#define FORMAT_UNKNOWN -1
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, JPEG"
#define IO_METHOD_UNKNOWN -1
#define IO_METHODS_STR "MMAP, USERPTR"
struct hw_buffer_t {
unsigned char *data;
@@ -57,6 +60,7 @@ struct device_runtime_t {
unsigned width;
unsigned height;
unsigned format;
size_t raw_size;
unsigned n_buffers;
unsigned n_workers;
struct hw_buffer_t *hw_buffers;
@@ -64,10 +68,16 @@ struct device_runtime_t {
bool capturing;
};
enum control_mode_t {
CTL_MODE_NONE = 0,
CTL_MODE_VALUE,
CTL_MODE_AUTO,
CTL_MODE_DEFAULT,
};
struct control_t {
int value;
bool value_set;
bool auto_set;
enum control_mode_t mode;
int value;
};
struct controls_t {
@@ -89,6 +99,7 @@ struct device_t {
unsigned height;
unsigned format;
v4l2_std_id standard;
enum v4l2_memory io_method;
bool dv_timings;
unsigned n_buffers;
unsigned n_workers;
@@ -109,6 +120,7 @@ void device_destroy(struct device_t *dev);
int device_parse_format(const char *str);
v4l2_std_id device_parse_standard(const char *str);
int device_parse_io_method(const char *str);
int device_open(struct device_t *dev);
void device_close(struct device_t *dev);

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 push
// cppcheck-suppress unusedLabel
force_cpu:
cpu_forced = true;
# 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);
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) {
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
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]->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 push
// cppcheck-suppress unusedLabel
error:
LOG_INFO("Error while compressing buffer, falling back to CPU");
A_MUTEX_LOCK(&encoder->run->mutex);

View File

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

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) {
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) {
OMX_ERRORTYPE error;
// cppcheck-suppress redundantPointerOp
OMX_INIT_STRUCTURE(*portdef);
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.nFrameHeight = dev->run->height;
portdef.format.image.nStride = 0;
# define ALIGN_HEIGHT(_x, _y) (((_x) + ((_y) - 1)) & ~((_y) - 1))
portdef.format.image.nSliceHeight = ALIGN_HEIGHT(dev->run->height, 16);
# undef ALIGN_HEIGHT
portdef.format.image.nSliceHeight = align_size(dev->run->height, 16);
portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused;
portdef.nBufferSize = picture_get_generous_size(dev->run->width, dev->run->height);

View File

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

View File

@@ -35,9 +35,9 @@
case _value: { return #_value; }
#define CASE_ASSERT(_msg, _value) default: { \
char *_buf; A_CALLOC(_buf, 128); \
sprintf(_buf, _msg ": 0x%08x", _value); \
assert(0 && _buf); \
char *_assert_buf; A_CALLOC(_assert_buf, 128); \
sprintf(_assert_buf, _msg ": 0x%08x", _value); \
assert(0 && _assert_buf); \
}
const char *omx_error_to_string(OMX_ERRORTYPE error) {
@@ -67,15 +67,40 @@ const char *omx_error_to_string(OMX_ERRORTYPE error) {
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringDeallocation);
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringStop);
CASE_TO_STRING(OMX_ErrorIncorrectStateTransition);
CASE_TO_STRING(OMX_ErrorIncorrectStateOperation);
CASE_TO_STRING(OMX_ErrorUnsupportedSetting);
CASE_TO_STRING(OMX_ErrorUnsupportedIndex);
CASE_TO_STRING(OMX_ErrorBadPortIndex);
CASE_TO_STRING(OMX_ErrorPortUnpopulated);
CASE_TO_STRING(OMX_ErrorComponentSuspended);
CASE_TO_STRING(OMX_ErrorDynamicResourcesUnavailable);
CASE_TO_STRING(OMX_ErrorMbErrorsInFrame);
CASE_TO_STRING(OMX_ErrorFormatNotDetected);
CASE_TO_STRING(OMX_ErrorContentPipeOpenFailed);
CASE_TO_STRING(OMX_ErrorContentPipeCreationFailed);
CASE_TO_STRING(OMX_ErrorSeperateTablesUsed);
CASE_TO_STRING(OMX_ErrorTunnelingUnsupported);
CASE_TO_STRING(OMX_ErrorKhronosExtensions);
CASE_TO_STRING(OMX_ErrorVendorStartUnused);
CASE_TO_STRING(OMX_ErrorDiskFull);
CASE_TO_STRING(OMX_ErrorMaxFileSize);
CASE_TO_STRING(OMX_ErrorDrmUnauthorised);
CASE_TO_STRING(OMX_ErrorDrmExpired);
CASE_TO_STRING(OMX_ErrorDrmGeneral);
default: return "Unknown OMX error";
}
}
const char *omx_state_to_string(OMX_STATETYPE state) {
switch (state) {
CASE_TO_STRING(OMX_StateInvalid);
CASE_TO_STRING(OMX_StateLoaded);
CASE_TO_STRING(OMX_StateIdle);
CASE_TO_STRING(OMX_StateExecuting);
CASE_TO_STRING(OMX_StatePause);
CASE_TO_STRING(OMX_StateWaitForResources);
// cppcheck-suppress constArgument
// cppcheck-suppress knownArgument
CASE_ASSERT("Unsupported OMX state", state);
}
}

View File

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

View File

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

View File

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

View File

@@ -50,6 +50,7 @@
#include "../tools.h"
#include "../threading.h"
#include "../logging.h"
#include "../process.h"
#include "../picture.h"
#include "../encoder.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_blank_picture(struct http_server_t *server);
static void _format_bufferevent_reason(short what, char *reason);
struct http_server_t *http_server_init(struct stream_t *stream) {
struct http_server_runtime_t *run;
@@ -157,6 +160,7 @@ void http_server_destroy(struct http_server_t *server) {
int http_server_listen(struct http_server_t *server) {
{
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);
} else {
assert(!evhttp_set_cb(server->run->http, "/", _http_callback_root, (void *)server));
@@ -172,9 +176,12 @@ int http_server_listen(struct http_server_t *server) {
# define EXPOSED(_next) server->run->exposed->_next
// See _expose_blank_picture()
picture_copy(server->run->blank, EXPOSED(picture));
EXPOSED(expose_begin_time) = get_now_monotonic();
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
EXPOSED(expose_end_time) = EXPOSED(expose_begin_time);
EXPOSED(expose_begin_ts) = get_now_monotonic();
EXPOSED(expose_cmp_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
{
@@ -326,7 +333,7 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
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);
goto not_found;
}
@@ -446,17 +453,17 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
ADD_TIME_HEADER("X-Timestamp", get_now_real());
ADD_HEADER("X-UStreamer-Online", bool_to_string(EXPOSED(online)));
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", EXPOSED(dropped));
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(picture->width));
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-Encode-Begin-Time", EXPOSED(picture->encode_begin_time));
ADD_TIME_HEADER("X-UStreamer-Encode-End-Time", EXPOSED(picture->encode_end_time));
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Time", EXPOSED(expose_begin_time));
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Time", EXPOSED(expose_cmp_time));
ADD_TIME_HEADER("X-UStreamer-Expose-End-Time", EXPOSED(expose_end_time));
ADD_TIME_HEADER("X-UStreamer-Send-Time", get_now_monotonic());
ADD_HEADER("X-UStreamer-Online", bool_to_string(EXPOSED(online)));
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", EXPOSED(dropped));
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(picture->width));
ADD_UNSIGNED_HEADER("X-UStreamer-Height", EXPOSED(picture->height));
ADD_TIME_HEADER("X-UStreamer-Grab-Timestamp", EXPOSED(picture->grab_ts));
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Timestamp", EXPOSED(picture->encode_begin_ts));
ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", EXPOSED(picture->encode_end_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Timestamp", EXPOSED(expose_begin_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Timestamp", EXPOSED(expose_cmp_ts));
ADD_TIME_HEADER("X-UStreamer-Expose-End-Timestamp", EXPOSED(expose_end_ts));
ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", get_now_monotonic());
# undef ADD_UNSUGNED_HEADER
# undef ADD_TIME_HEADER
@@ -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);
LOG_INFO("HTTP: Registered the new stream client: [%s]:%u, id=%s; clients now: %u",
LOG_INFO("HTTP: Registered client: [%s]:%u, id=%s; clients now: %u",
client_addr, client_port, client->id, server->run->stream_clients_count);
buf_event = evhttp_connection_get_bufferevent(conn);
@@ -637,12 +644,12 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
EXPOSED(picture->width),
EXPOSED(picture->height),
client->fps,
EXPOSED(picture->grab_time),
EXPOSED(picture->encode_begin_time),
EXPOSED(picture->encode_end_time),
EXPOSED(expose_begin_time),
EXPOSED(expose_cmp_time),
EXPOSED(expose_end_time),
EXPOSED(picture->grab_ts),
EXPOSED(picture->encode_begin_ts),
EXPOSED(picture->encode_end_ts),
EXPOSED(expose_begin_ts),
EXPOSED(expose_cmp_ts),
EXPOSED(expose_end_ts),
now
));
}
@@ -672,12 +679,16 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
struct evhttp_connection *conn;
char *client_addr = "???";
unsigned short client_port = 0;
char reason[2048] = {0};
_format_bufferevent_reason(what, reason);
# define RUN(_next) client->server->run->_next
assert(RUN(stream_clients_count) > 0);
RUN(stream_clients_count) -= 1;
if (RUN(stream_clients_count) <= 0) {
if (RUN(stream_clients_count) == 0) {
if (client->server->slowdown) {
stream_switch_slowdown(RUN(stream), true);
}
@@ -691,8 +702,9 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
if (conn) {
evhttp_connection_get_peer(conn, &client_addr, &client_port);
}
LOG_INFO("HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
client_addr, client_port, RUN(stream_clients_count));
LOG_INFO("HTTP: Disconnected client: [%s]:%u, id=%s, %s; clients now: %u",
client_addr, client_port, client->id, reason, RUN(stream_clients_count));
if (conn) {
evhttp_connection_free(conn);
}
@@ -716,8 +728,6 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
struct bufferevent *buf_event;
long long now;
bool queued = false;
static unsigned queued_fps_accum = 0;
static long long queued_fps_second = 0;
for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
conn = evhttp_request_get_connection(client->request);
@@ -751,6 +761,9 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
}
if (queued) {
static unsigned queued_fps_accum = 0;
static long long queued_fps_second = 0;
if ((now = floor_ms(get_now_monotonic())) != queued_fps_second) {
server->run->exposed->queued_fps = queued_fps_accum;
queued_fps_accum = 0;
@@ -790,6 +803,25 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
# undef UNLOCK_STREAM
_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) {
@@ -797,7 +829,7 @@ static bool _expose_new_picture_unsafe(struct http_server_t *server) {
# define STREAM(_next) server->run->stream->_next
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 (
@@ -805,16 +837,16 @@ static bool _expose_new_picture_unsafe(struct http_server_t *server) {
&& EXPOSED(dropped) < server->drop_same_frames
&& picture_compare(EXPOSED(picture), STREAM(picture))
) {
EXPOSED(expose_cmp_time) = get_now_monotonic();
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
EXPOSED(expose_cmp_ts) = get_now_monotonic();
EXPOSED(expose_end_ts) = EXPOSED(expose_cmp_ts);
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;
return false; // Not updated
} 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",
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
EXPOSED(expose_cmp_ts) - EXPOSED(expose_begin_ts));
}
}
@@ -824,11 +856,11 @@ static bool _expose_new_picture_unsafe(struct http_server_t *server) {
EXPOSED(online) = true;
EXPOSED(dropped) = 0;
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
EXPOSED(expose_end_time) = get_now_monotonic();
EXPOSED(expose_cmp_ts) = EXPOSED(expose_begin_ts);
EXPOSED(expose_end_ts) = get_now_monotonic();
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
return true; // Updated
@@ -837,8 +869,8 @@ static bool _expose_new_picture_unsafe(struct http_server_t *server) {
static bool _expose_blank_picture(struct http_server_t *server) {
# define EXPOSED(_next) server->run->exposed->_next
EXPOSED(expose_begin_time) = get_now_monotonic();
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
EXPOSED(expose_begin_ts) = get_now_monotonic();
EXPOSED(expose_cmp_ts) = EXPOSED(expose_begin_ts);
# define EXPOSE_BLANK picture_copy(server->run->blank, EXPOSED(picture))
@@ -848,7 +880,7 @@ static bool _expose_blank_picture(struct http_server_t *server) {
EXPOSE_BLANK;
} else if (server->last_as_blank > 0) { // Если нужен таймер - запустим
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 - показываем последний фрейм вечно
LOG_INFO("HTTP: Freezing last alive frame forever");
}
@@ -857,12 +889,12 @@ static bool _expose_blank_picture(struct http_server_t *server) {
if ( // Если уже оффлайн, включена фича last_as_blank с таймером и он запущен
server->last_as_blank > 0
&& EXPOSED(last_as_blank_time) > 0
&& EXPOSED(last_as_blank_time) + server->last_as_blank < EXPOSED(expose_begin_time)
&& EXPOSED(last_as_blank_ts) > 0
&& EXPOSED(last_as_blank_ts) + server->last_as_blank < EXPOSED(expose_begin_ts)
) {
LOG_INFO("HTTP: Changed last alive frame to BLANK");
EXPOSE_BLANK;
EXPOSED(last_as_blank_time) = 0; // Останавливаем таймер
EXPOSED(last_as_blank_ts) = 0; // Останавливаем таймер
goto updated;
}
@@ -871,7 +903,7 @@ static bool _expose_blank_picture(struct http_server_t *server) {
if (EXPOSED(dropped) < server->run->drop_same_frames_blank) {
LOG_PERF("HTTP: Dropped same frame (BLANK) number %u", EXPOSED(dropped));
EXPOSED(dropped) += 1;
EXPOSED(expose_end_time) = get_now_monotonic();
EXPOSED(expose_end_ts) = get_now_monotonic();
return false; // Not updated
}
@@ -879,8 +911,38 @@ static bool _expose_blank_picture(struct http_server_t *server) {
EXPOSED(captured_fps) = 0;
EXPOSED(online) = false;
EXPOSED(dropped) = 0;
EXPOSED(expose_end_time) = get_now_monotonic();
EXPOSED(expose_end_ts) = get_now_monotonic();
return true; // Updated
# 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

@@ -61,10 +61,14 @@ struct exposed_t {
unsigned queued_fps;
bool online;
unsigned dropped;
long double expose_begin_time;
long double expose_cmp_time;
long double expose_end_time;
long double last_as_blank_time;
long double expose_begin_ts;
long double expose_cmp_ts;
long double expose_end_ts;
long double last_as_blank_ts;
bool notify_last_online;
unsigned notify_last_width;
unsigned notify_last_height;
};
struct http_server_runtime_t {
@@ -100,6 +104,8 @@ struct http_server_t {
unsigned fake_width;
unsigned fake_height;
bool notify_parent;
struct http_server_runtime_t *run;
};

View File

@@ -30,7 +30,7 @@
#include <sys/stat.h>
#include "../tools.h"
// #include "../logging.h"
#include "../logging.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);
if (simplified_path[0] == '\0') {
LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path);
goto error;
}
@@ -50,13 +51,14 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
# define LOAD_STAT { \
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; \
} \
}
LOAD_STAT;
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");
LOAD_STAT;
}
@@ -64,12 +66,12 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
# undef LOAD_STAT
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;
}
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;
}

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"
enum {
LOG_LEVEL_INFO,
LOG_LEVEL_PERF,
LOG_LEVEL_VERBOSE,
LOG_LEVEL_DEBUG,
} log_level;
enum log_level_t {
LOG_LEVEL_INFO,
LOG_LEVEL_PERF,
LOG_LEVEL_VERBOSE,
LOG_LEVEL_DEBUG,
};
bool log_colored;
pthread_mutex_t log_mutex;
extern enum log_level_t log_level;
extern bool log_colored;
extern pthread_mutex_t log_mutex;
#define LOGGING_INIT { \
@@ -88,14 +91,14 @@ pthread_mutex_t log_mutex;
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
char _buf[MAX_THREAD_NAME] = {0}; \
thread_get_name(_buf); \
char _tname_buf[MAX_THREAD_NAME] = {0}; \
thread_get_name(_tname_buf); \
if (log_colored) { \
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 { \
printf("-- " _label " [%.03Lf %9s] -- " _msg, \
get_now_monotonic(), _buf, ##__VA_ARGS__); \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
} \
putchar('\n'); \
fflush(stdout); \
@@ -112,9 +115,9 @@ pthread_mutex_t log_mutex;
}
#define LOG_PERROR(_msg, ...) { \
char _buf[1024] = {0}; \
char *_ptr = errno_to_string(_buf, 1024); \
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _ptr); \
char _perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \
}
#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, ...) { \
if (log_level >= LOG_LEVEL_DEBUG) { \
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)
return strerror_r(errno, buf, size);
return strerror_r(error, buf, size);
# else
strerror_r(errno, buf, size);
strerror_r(error, buf, size);
return buf;
# endif
}

View File

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

View File

@@ -26,6 +26,7 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <limits.h>
#include <getopt.h>
@@ -49,6 +50,7 @@ enum _OPT_VALUES {
_O_RESOLUTION = 'r',
_O_FORMAT = 'm',
_O_TV_STANDARD = 'a',
_O_IO_METHOD = 'I',
_O_DESIRED_FPS = 'f',
_O_MIN_FRAME_SIZE = 'z',
_O_PERSISTENT = 'n',
@@ -80,19 +82,16 @@ enum _OPT_VALUES {
_O_DEVICE_TIMEOUT = 10000,
_O_DEVICE_ERROR_DELAY,
_O_IMAGE_DEFAULT,
_O_BRIGHTNESS,
_O_BRIGHTNESS_AUTO,
_O_CONTRAST,
_O_SATURATION,
_O_HUE,
_O_HUE_AUTO,
_O_GAMMA,
_O_SHARPNESS,
_O_BACKLIGHT_COMPENSATION,
_O_WHITE_BALANCE,
_O_WHITE_BALANCE_AUTO,
_O_GAIN,
_O_GAIN_AUTO,
_O_USER,
_O_PASSWD,
@@ -112,6 +111,7 @@ enum _OPT_VALUES {
#ifdef WITH_SETPROCTITLE
_O_PROCESS_NAME_PREFIX,
#endif
_O_NOTIFY_PARENT,
_O_LOG_LEVEL,
_O_PERF,
@@ -129,6 +129,7 @@ static const struct option _LONG_OPTS[] = {
{"resolution", required_argument, NULL, _O_RESOLUTION},
{"format", required_argument, NULL, _O_FORMAT},
{"tv-standard", required_argument, NULL, _O_TV_STANDARD},
{"io-method", required_argument, NULL, _O_IO_METHOD},
{"desired-fps", required_argument, NULL, _O_DESIRED_FPS},
{"min-frame-size", required_argument, NULL, _O_MIN_FRAME_SIZE},
{"persistent", no_argument, NULL, _O_PERSISTENT},
@@ -143,19 +144,16 @@ static const struct option _LONG_OPTS[] = {
{"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT},
{"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-auto", no_argument, NULL, _O_BRIGHTNESS_AUTO},
{"contrast", required_argument, NULL, _O_CONTRAST},
{"saturation", required_argument, NULL, _O_SATURATION},
{"hue", required_argument, NULL, _O_HUE},
{"hue-auto", no_argument, NULL, _O_HUE_AUTO},
{"gamma", required_argument, NULL, _O_GAMMA},
{"sharpness", required_argument, NULL, _O_SHARPNESS},
{"backlight-compensation", required_argument, NULL, _O_BACKLIGHT_COMPENSATION},
{"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-auto", no_argument, NULL, _O_GAIN_AUTO},
{"host", required_argument, NULL, _O_HOST},
{"port", required_argument, NULL, _O_PORT},
@@ -185,6 +183,7 @@ static const struct option _LONG_OPTS[] = {
#ifdef WITH_SETPROCTITLE
{"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX},
#endif
{"notify-parent", no_argument, NULL, _O_NOTIFY_PARENT},
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
{"perf", no_argument, NULL, _O_PERF},
@@ -243,7 +242,7 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
if (errno || *_end || _tmp < _min || _tmp > _max) { \
printf("Invalid value for '%s=%s': min=%u, max=%u\n", _name, optarg, _min, _max); \
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
return -1; \
} \
_dest = _tmp; \
@@ -275,16 +274,29 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
break; \
}
# define OPT_CTL(_dest) { \
dev->ctl._dest.value_set = true; \
dev->ctl._dest.auto_set = false; \
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
# define OPT_CTL_DEFAULT_NOBREAK(_dest) { \
dev->ctl._dest.mode = CTL_MODE_DEFAULT; \
}
# 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; \
}
# define OPT_CTL_AUTO(_dest) { \
dev->ctl._dest.value_set = false; \
dev->ctl._dest.auto_set = true; \
if (!strcasecmp(optarg, "default")) { \
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; \
}
@@ -317,6 +329,7 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN, FORMATS_STR);
# pragma GCC diagnostic pop
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN, STANDARDS_STR);
case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, device_parse_io_method, IO_METHOD_UNKNOWN, IO_METHODS_STR);
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 0, 8192, 0);
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
@@ -335,19 +348,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_ERROR_DELAY: OPT_NUMBER("--device-error-delay", dev->error_delay, 1, 60, 0);
case _O_BRIGHTNESS: OPT_CTL(brightness);
case _O_BRIGHTNESS_AUTO: OPT_CTL_AUTO(brightness);
case _O_CONTRAST: OPT_CTL(contrast);
case _O_SATURATION: OPT_CTL(saturation);
case _O_HUE: OPT_CTL(hue);
case _O_HUE_AUTO: OPT_CTL_AUTO(hue);
case _O_GAMMA: OPT_CTL(gamma);
case _O_SHARPNESS: OPT_CTL(sharpness);
case _O_BACKLIGHT_COMPENSATION: OPT_CTL(backlight_compensation);
case _O_WHITE_BALANCE: OPT_CTL(white_balance);
case _O_WHITE_BALANCE_AUTO: OPT_CTL_AUTO(white_balance);
case _O_GAIN: OPT_CTL(gain);
case _O_GAIN_AUTO: OPT_CTL_AUTO(gain);
case _O_IMAGE_DEFAULT:
OPT_CTL_DEFAULT_NOBREAK(brightness);
OPT_CTL_DEFAULT_NOBREAK(contrast);
OPT_CTL_DEFAULT_NOBREAK(saturation);
OPT_CTL_DEFAULT_NOBREAK(hue);
OPT_CTL_DEFAULT_NOBREAK(gamma);
OPT_CTL_DEFAULT_NOBREAK(sharpness);
OPT_CTL_DEFAULT_NOBREAK(backlight_compensation);
OPT_CTL_DEFAULT_NOBREAK(white_balance);
OPT_CTL_DEFAULT_NOBREAK(gain);
break;
case _O_BRIGHTNESS: OPT_CTL_AUTO(brightness);
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_PORT: OPT_NUMBER("--port", server->port, 1, 65535, 0);
@@ -381,6 +401,7 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
# ifdef WITH_SETPROCTITLE
case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg);
# 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_PERF: OPT_SET(log_level, LOG_LEVEL_PERF);
@@ -405,7 +426,8 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
# endif
# undef OPT_CTL_AUTO
# undef OPT_CTL
# undef OPT_CTL_MANUAL
# undef OPT_CTL_DEFAULT_NOBREAK
# undef OPT_PARSE
# undef OPT_RESOLUTION
# undef OPT_NUMBER
@@ -529,6 +551,9 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
printf(" -a|--tv-standard <std> ────────────── Force TV standard.\n");
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
printf(" -I|--io-method <method> ───────────── Set V4L2 IO method (see kernel documentation).\n");
printf(" Changing of this parameter may increase the performance. Or not.\n");
printf(" Available: %s; default: MMAP\n\n", IO_METHODS_STR);
printf(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n\n");
printf(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device\n");
printf(" produces small-sized garbage frames. Default: disabled.\n\n");
@@ -562,19 +587,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("Image control options:\n");
printf("══════════════════════\n");
printf(" --brightness <N> ───────────── Set brightness. Default: no change.\n\n");
printf(" --brightness-auto ──────────── Enable automatic brightness control. Default: no change.\n\n");
printf(" --contrast <N> ─────────────── Set contrast. Default: no change.\n\n");
printf(" --saturation <N> ───────────── Set saturation. Default: no change.\n\n");
printf(" --hue <N> ──────────────────── Set hue. Default: no change.\n\n");
printf(" --hue-auto ─────────────────── Enable automatic hue control. Default: no change.\n\n");
printf(" --gamma <N> ────────────────── Set gamma. Default: no change.\n\n");
printf(" --sharpness <N> ────────────── Set sharpness. Default: no change.\n\n");
printf(" --backlight-compensation <N> ─ Set backlight compensation. Default: no change.\n\n");
printf(" --white-balance <N> ────────── Set white balance. Default: no change.\n\n");
printf(" --white-balance-auto ───────── Enable automatic white balance control. Default: no change.\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(" --image-default ────────────────────── Reset all image settings bellow to default. Default: no change.\n\n");
printf(" --brightness <N|auto|default> ──────── Set brightness. Default: no change.\n\n");
printf(" --contrast <N|default> ─────────────── Set contrast. Default: no change.\n\n");
printf(" --saturation <N|default> ───────────── Set saturation. Default: no change.\n\n");
printf(" --hue <N|auto|default> ─────────────── Set hue. Default: no change.\n\n");
printf(" --gamma <N|default> ─────────────────── Set gamma. Default: no change.\n\n");
printf(" --sharpness <N|default> ────────────── Set sharpness. Default: no change.\n\n");
printf(" --backlight-compensation <N|default> ─ Set backlight compensation. Default: no change.\n\n");
printf(" --white-balance <N|auto|default> ───── Set white balance. Default: no change.\n\n");
printf(" --gain <N|auto|default> ────────────── Set gain. Default: no change.\n\n");
printf(" Hint: use v4l2-ctl --list-ctrls-menus to query available controls of the device.\n\n");
printf("HTTP server options:\n");
printf("════════════════════\n");
printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s.\n\n", server->host);
@@ -598,7 +621,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(" -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(" -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);
#ifdef WITH_GPIO
printf("GPIO options:\n");
@@ -619,13 +642,15 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
#ifdef WITH_SETPROCTITLE
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(" --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
printf("Logging options:\n");
printf("════════════════\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(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).\n");
printf(" Default: %u.\n\n", log_level);
printf(" Default: %d.\n\n", log_level);
printf(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n");
printf(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n");
printf(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");

View File

@@ -73,9 +73,7 @@ void picture_append_data(struct picture_t *picture, const unsigned char *data, s
}
void picture_copy(const struct picture_t *src, struct picture_t *dest) {
assert(src->allocated);
picture_set_data(dest, src->data, src->allocated);
picture_set_data(dest, src->data, src->used);
# define COPY(_field) dest->_field = src->_field
@@ -84,13 +82,17 @@ void picture_copy(const struct picture_t *src, struct picture_t *dest) {
COPY(width);
COPY(height);
COPY(grab_time);
COPY(encode_begin_time);
COPY(encode_end_time);
COPY(grab_ts);
COPY(encode_begin_ts);
COPY(encode_end_ts);
# undef COPY
}
bool picture_compare(const struct picture_t *a, const struct picture_t *b) {
return (a->used == b->used && !memcmp(a->data, b->data, b->used));
return (
a->allocated && b->allocated
&& a->used == b->used
&& !memcmp(a->data, b->data, b->used)
);
}

View File

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

View File

@@ -22,6 +22,12 @@
#pragma once
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#if defined(__linux__)
# define HAS_PDEATHSIG
#elif defined(__FreeBSD__)
@@ -32,19 +38,14 @@
#endif
#ifdef HAS_PDEATHSIG
# include <signal.h>
# include <unistd.h>
#endif
#ifdef WITH_SETPROCTITLE
# include <stdlib.h>
# include <string.h>
# if defined(__linux__)
# include <bsd/unistd.h>
# include <sys/types.h>
# elif (defined(__FreeBSD__) || defined(__DragonFly__))
# include <unistd.h>
# include <sys/types.h>
//# include <unistd.h>
//# include <sys/types.h>
# elif (defined(__NetBSD__) || defined(__OpenBSD__)) // setproctitle() placed in stdlib.h
# else
# error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0
@@ -72,6 +73,7 @@ extern char **environ;
#ifdef HAS_PDEATHSIG
INLINE int process_track_parent_death(void) {
pid_t parent = getppid();
int signum = SIGTERM;
# if defined(__linux__)
int retval = prctl(PR_SET_PDEATHSIG, signum);
@@ -85,8 +87,8 @@ INLINE int process_track_parent_death(void) {
return -1;
}
if (kill(getppid(), 0) < 0) {
LOG_PERROR("The parent process is already dead");
if (kill(parent, 0) < 0) {
LOG_PERROR("The parent process %d is already dead", parent);
return -1;
}
@@ -103,13 +105,12 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
char *cmdline = NULL;
size_t allocated = 2048;
size_t used = 0;
size_t arg_len = 0;
A_REALLOC(cmdline, allocated);
cmdline[0] = '\0';
for (int index = 0; index < argc; ++index) {
arg_len = strlen(argv[index]);
size_t arg_len = strlen(argv[index]);
if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048;
A_REALLOC(cmdline, allocated);
@@ -128,3 +129,11 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
free(cmdline);
}
#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;
pthread_mutex_t has_job_mutex;
int buf_index;
unsigned buf_index;
atomic_bool has_job;
bool job_timely;
bool job_failed;
long double job_start_time;
long double job_start_ts;
pthread_cond_t has_job_cond;
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) {
worker->job_start_time = PICTURE(encode_begin_time);
worker->job_start_ts = PICTURE(encode_begin_ts);
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",
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_MUTEX_UNLOCK(&pool->free_workers_mutex);
if (pool->oldest_worker && !atomic_load(&pool->oldest_worker->has_job) && pool->oldest_worker->buf_index >= 0) {
if (pool->oldest_worker && !atomic_load(&pool->oldest_worker->has_job)) {
ready_worker = pool->oldest_worker;
ready_worker->job_timely = true;
pool->oldest_worker = pool->oldest_worker->order_next;
@@ -498,7 +498,7 @@ static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool) {
if (
!atomic_load(&pool->workers[number].has_job) && (
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];

View File

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

View File

@@ -45,6 +45,10 @@ INLINE char *bool_to_string(bool flag) {
return (flag ? "true" : "false");
}
INLINE size_t align_size(size_t size, size_t to) {
return ((size + (to - 1)) & ~(to - 1));
}
INLINE unsigned min_u(unsigned a, unsigned b) {
return (a < b ? a : b);
}

View File

@@ -30,9 +30,10 @@
#include "logging.h"
#ifndef XIOCTL_RETRIES
# define XIOCTL_RETRIES 4
#ifndef CFG_XIOCTL_RETRIES
# define CFG_XIOCTL_RETRIES 4
#endif
#define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES))
INLINE int xioctl(int fd, int request, void *arg) {
@@ -51,8 +52,9 @@ INLINE int xioctl(int fd, int request, void *arg) {
)
);
// cppcheck-suppress knownConditionTrueFalse
if (retval && retries <= 0) {
LOG_PERROR("ioctl(%d) retried %d times; giving up", request, XIOCTL_RETRIES);
LOG_PERROR("ioctl(%d) retried %u times; giving up", request, XIOCTL_RETRIES);
}
return retval;
}

View File

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

View File

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

View File

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