mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53feba1248 | ||
|
|
119821d5af | ||
|
|
4faabf27ec | ||
|
|
191f6e3c09 | ||
|
|
4e51439118 | ||
|
|
e184e187a2 | ||
|
|
592568c9aa | ||
|
|
46c5a547a9 | ||
|
|
3d097a4ffb | ||
|
|
00e32c915c | ||
|
|
d44c340dce | ||
|
|
8c18f1dffe | ||
|
|
c3c386ea5b | ||
|
|
fa09992c46 | ||
|
|
cefcd0c963 | ||
|
|
96c806071d | ||
|
|
0775b35ef8 | ||
|
|
138d9a74d8 | ||
|
|
2a668643dc | ||
|
|
56312cffb5 | ||
|
|
f553b97dba | ||
|
|
b619b1e096 | ||
|
|
06a32fd3ab | ||
|
|
8637ff5c09 | ||
|
|
bd5cf7d3de | ||
|
|
e488eec90c | ||
|
|
6615a23361 | ||
|
|
a91eba8d90 | ||
|
|
d5aebc1231 | ||
|
|
5f4f46bbe6 | ||
|
|
61a2fe6546 | ||
|
|
98499b6604 | ||
|
|
f52d090f9b | ||
|
|
ebb3df46b9 | ||
|
|
8fd6659cf1 | ||
|
|
13ce0bbc63 | ||
|
|
0add4cc25f | ||
|
|
96d84b33bd | ||
|
|
f2dfe7641e | ||
|
|
97403cbb75 | ||
|
|
0663bb1035 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 1.16
|
||||
current_version = 1.22
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
@@ -17,4 +17,3 @@ replace = pkgver={new_version}
|
||||
[bumpversion:file:pkg/openwrt/Makefile]
|
||||
search = PKG_VERSION:={current_version}
|
||||
replace = PKG_VERSION:={new_version}
|
||||
|
||||
|
||||
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: pikvm
|
||||
custom: https://www.paypal.me/mdevaev
|
||||
19
README.md
19
README.md
@@ -4,8 +4,8 @@
|
||||
|
||||
[[Русская версия]](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 part of the [Pi-KVM](https://github.com/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
|
||||
µStreamer is a lightweight and very quick server to stream [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 part of the [Pi-KVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
|
||||
|
||||
µStreamer is very similar to [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) with ```input_uvc.so``` and ```output_http.so``` plugins, however, there are some major differences. The key ones are:
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
|----------|---------------|-------------------|
|
||||
| Multithreaded JPEG encoding |  Yes |  No |
|
||||
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi |  Yes |  No |
|
||||
| Behavior when the device<br>is disconnected while streaming |  Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected |  Stops the broadcast <sup>1</sup> |
|
||||
| Behavior when the device<br>is disconnected while streaming |  Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected |  Stops the streaming <sup>1</sup> |
|
||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal |  Yes |  Partially yes <sup>1</sup> |
|
||||
| Option to skip frames when streaming<br>static images by HTTP to save the traffic |  Yes <sup>2</sup> |  No |
|
||||
| Streaming via UNIX domain socket |  Yes |  No |
|
||||
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters |  Yes |  No |
|
||||
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP streaming parameters |  Yes |  No |
|
||||
| Option to serve files<br>with a built-in HTTP server |  Yes |  Regular files only |
|
||||
| Signaling about the stream state to GPIO<br>on Raspberry Pi using [wiringPi](http://wiringpi.com) |  Yes |  No |
|
||||
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP |  No |  Yes |
|
||||
@@ -25,7 +25,7 @@
|
||||
Footnotes:
|
||||
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
|
||||
|
||||
* ```2``` This feature allows to cut down outgoing traffic several-fold when broadcasting HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to broadcast the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
|
||||
* ```2``` This feature allows to cut down outgoing traffic several-fold when streaming HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to stream the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
|
||||
|
||||
-----
|
||||
# TL;DR
|
||||
@@ -36,7 +36,7 @@ If you're going to live-stream from your backyard webcam and need to control it,
|
||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` and ```libbsd``` (only for Linux).
|
||||
|
||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||
* Raspbian: `sudo apt install libevent-dev libevent-pthreads-2.1-6-dev libjpeg8-dev uuid-dev libbsd-dev`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `wiringpi` for `WITH_GPIO=1`.
|
||||
* 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```.
|
||||
@@ -53,7 +53,7 @@ FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
||||
|
||||
-----
|
||||
# Usage
|
||||
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start broadcasting on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to broadcast to the world, run:
|
||||
Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start streaming on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to stream to the world, run:
|
||||
```
|
||||
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
|
||||
```
|
||||
@@ -72,8 +72,9 @@ $ ./ustreamer \
|
||||
You can always view the full list of options with ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Tips
|
||||
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16)
|
||||
# See also
|
||||
* [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16).
|
||||
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Use [Ansible](https://docs.ansible.com/ansible/latest/index.html) to compile uStreamer and install it as a systemd service automatically.
|
||||
|
||||
-----
|
||||
# License
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
[[English version]](README.md)
|
||||
|
||||
|
||||
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
|
||||
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pikvm/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
|
||||
|
||||
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo```, ```libuuid``` и ```libbsd``` (только для Linux).
|
||||
|
||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||
* Raspbian: `sudo apt install libevent-dev libevent-pthreads-2.1-6-dev libjpeg8-dev uuid-dev libbsd-dev`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev uuid-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `wiringpi` для `WITH_GPIO=1`.
|
||||
* 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```.
|
||||
@@ -72,8 +72,9 @@ $ ./ustreamer \
|
||||
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
|
||||
|
||||
-----
|
||||
# Советы
|
||||
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16)
|
||||
# Смотрите также
|
||||
* [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16).
|
||||
* [uStreamer Ansible Role](https://github.com/mtlynch/ansible-role-ustreamer): Использование [Ansible](https://docs.ansible.com/ansible/latest/index.html) для сборки и установки стримера как systemd-сервиса.
|
||||
|
||||
-----
|
||||
# Лицензия
|
||||
|
||||
@@ -23,9 +23,7 @@ commands = cppcheck \
|
||||
[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
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=1.16
|
||||
pkgver=1.22
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=1.16
|
||||
PKG_VERSION:=1.22
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef VERSION
|
||||
# define VERSION "1.16"
|
||||
# define VERSION "1.22"
|
||||
#endif
|
||||
|
||||
116
src/device.c
116
src/device.c
@@ -67,13 +67,23 @@ static const struct {
|
||||
{"JPEG", V4L2_PIX_FMT_JPEG},
|
||||
};
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
const enum v4l2_memory io_method;
|
||||
} _IO_METHODS[] = {
|
||||
{"MMAP", V4L2_MEMORY_MMAP},
|
||||
{"USERPTR", V4L2_MEMORY_USERPTR},
|
||||
};
|
||||
|
||||
|
||||
static int _device_open_check_cap(struct device_t *dev);
|
||||
static int _device_open_dv_timings(struct device_t *dev);
|
||||
static int _device_apply_dv_timings(struct device_t *dev);
|
||||
static int _device_open_format(struct device_t *dev);
|
||||
static void _device_open_hw_fps(struct device_t *dev);
|
||||
static int _device_open_mmap(struct device_t *dev);
|
||||
static int _device_open_io_method(struct device_t *dev);
|
||||
static int _device_open_io_method_mmap(struct device_t *dev);
|
||||
static int _device_open_io_method_userptr(struct device_t *dev);
|
||||
static int _device_open_queue_buffers(struct device_t *dev);
|
||||
static void _device_open_alloc_picbufs(struct device_t *dev);
|
||||
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height);
|
||||
@@ -86,6 +96,7 @@ static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned for
|
||||
static const char *_format_to_string_nullable(unsigned format);
|
||||
static const char *_format_to_string_supported(unsigned format);
|
||||
static const char *_standard_to_string(v4l2_std_id standard);
|
||||
static const char *_io_method_to_string_supported(enum v4l2_memory io_method);
|
||||
|
||||
|
||||
struct device_t *device_init(void) {
|
||||
@@ -109,8 +120,10 @@ struct device_t *device_init(void) {
|
||||
dev->standard = V4L2_STD_UNKNOWN;
|
||||
dev->n_buffers = cores_available + 1;
|
||||
dev->n_workers = min_u(cores_available, dev->n_buffers);
|
||||
dev->min_frame_size = 128;
|
||||
dev->timeout = 1;
|
||||
dev->error_delay = 1;
|
||||
dev->io_method = V4L2_MEMORY_MMAP;
|
||||
dev->run = run;
|
||||
return dev;
|
||||
}
|
||||
@@ -138,6 +151,15 @@ v4l2_std_id device_parse_standard(const char *str) {
|
||||
return STANDARD_UNKNOWN;
|
||||
}
|
||||
|
||||
int device_parse_io_method(const char *str) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) {
|
||||
if (!strcasecmp(str, _IO_METHODS[index].name)) {
|
||||
return _IO_METHODS[index].io_method;
|
||||
}
|
||||
}
|
||||
return IO_METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
int device_open(struct device_t *dev) {
|
||||
if ((dev->run->fd = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
|
||||
LOG_PERROR("Can't open device");
|
||||
@@ -155,7 +177,7 @@ int device_open(struct device_t *dev) {
|
||||
goto error;
|
||||
}
|
||||
_device_open_hw_fps(dev);
|
||||
if (_device_open_mmap(dev) < 0) {
|
||||
if (_device_open_io_method(dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_device_open_queue_buffers(dev) < 0) {
|
||||
@@ -187,13 +209,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +299,7 @@ int device_grab_buffer(struct device_t *dev) {
|
||||
|
||||
MEMSET_ZERO(buf_info);
|
||||
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf_info.memory = V4L2_MEMORY_MMAP;
|
||||
buf_info.memory = dev->io_method;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_DQBUF, &buf_info) < 0) {
|
||||
@@ -465,6 +493,8 @@ static int _device_open_format(struct device_t *dev) {
|
||||
|
||||
dev->run->format = fmt.fmt.pix.pixelformat;
|
||||
LOG_INFO("Using pixelformat: %s", _format_to_string_supported(dev->run->format));
|
||||
|
||||
dev->run->raw_size = fmt.fmt.pix.sizeimage; // Only for userptr
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -510,7 +540,17 @@ static void _device_open_hw_fps(struct device_t *dev) {
|
||||
# undef SETFPS_TPF
|
||||
}
|
||||
|
||||
static int _device_open_mmap(struct device_t *dev) {
|
||||
static int _device_open_io_method(struct device_t *dev) {
|
||||
LOG_INFO("Using IO method: %s", _io_method_to_string_supported(dev->io_method));
|
||||
switch (dev->io_method) {
|
||||
case V4L2_MEMORY_MMAP: return _device_open_io_method_mmap(dev);
|
||||
case V4L2_MEMORY_USERPTR: return _device_open_io_method_userptr(dev);
|
||||
default: assert(0 && "Unsupported IO method");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int _device_open_io_method_mmap(struct device_t *dev) {
|
||||
struct v4l2_requestbuffers req;
|
||||
|
||||
MEMSET_ZERO(req);
|
||||
@@ -518,9 +558,9 @@ static int _device_open_mmap(struct device_t *dev) {
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) ...");
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_MMAP ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||
LOG_PERROR("Device '%s' doesn't support memory mapping", dev->path);
|
||||
LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_MMAP", dev->path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -563,14 +603,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) {
|
||||
@@ -655,6 +737,9 @@ static void _device_apply_controls(struct device_t *dev) {
|
||||
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);
|
||||
CONTROL_MANUAL_CID ( V4L2_CID_COLORFX, color_effect);
|
||||
CONTROL_MANUAL_CID ( V4L2_CID_VFLIP, flip_vertical);
|
||||
CONTROL_MANUAL_CID ( V4L2_CID_HFLIP, flip_horizontal);
|
||||
|
||||
# undef CONTROL_AUTO_CID
|
||||
# undef CONTROL_MANUAL_CID
|
||||
@@ -739,3 +824,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";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -86,6 +90,9 @@ struct controls_t {
|
||||
struct control_t backlight_compensation;
|
||||
struct control_t white_balance;
|
||||
struct control_t gain;
|
||||
struct control_t color_effect;
|
||||
struct control_t flip_vertical;
|
||||
struct control_t flip_horizontal;
|
||||
};
|
||||
|
||||
struct device_t {
|
||||
@@ -95,6 +102,7 @@ struct device_t {
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
v4l2_std_id standard;
|
||||
enum v4l2_memory io_method;
|
||||
bool dv_timings;
|
||||
unsigned n_buffers;
|
||||
unsigned n_workers;
|
||||
@@ -115,6 +123,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);
|
||||
|
||||
@@ -201,16 +201,20 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
||||
assert(dev->run->hw_buffers[buf_index].used > 0);
|
||||
|
||||
dev->run->pictures[buf_index]->encode_begin_ts = get_now_monotonic();
|
||||
|
||||
if (encoder->run->type == ENCODER_TYPE_CPU) {
|
||||
LOG_VERBOSE("Compressing buffer %u using CPU", buf_index);
|
||||
cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality);
|
||||
} else if (encoder->run->type == ENCODER_TYPE_HW) {
|
||||
LOG_VERBOSE("Compressing buffer %u using HW (just copying)", buf_index);
|
||||
hw_encoder_compress_buffer(dev, buf_index);
|
||||
}
|
||||
# ifdef WITH_OMX
|
||||
else if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||
LOG_VERBOSE("Compressing buffer %u using OMX", buf_index);
|
||||
if (omx_encoder_compress_buffer(encoder->run->omxs[worker_number], dev, buf_index) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -102,11 +102,11 @@ struct omx_encoder_t *omx_encoder_init(void) {
|
||||
|
||||
LOG_INFO("Initializing OMX encoder ...");
|
||||
|
||||
if (vcos_semaphore_create(&omx->handler_lock, "handler_lock", 0) != VCOS_SUCCESS) {
|
||||
if (vcos_semaphore_create(&omx->handler_sem, "handler_sem", 0) != VCOS_SUCCESS) {
|
||||
LOG_ERROR("Can't create VCOS semaphore");
|
||||
goto error;
|
||||
}
|
||||
omx->i_handler_lock = true;
|
||||
omx->i_handler_sem = true;
|
||||
|
||||
if (_omx_init_component(omx) < 0) {
|
||||
goto error;
|
||||
@@ -132,8 +132,8 @@ void omx_encoder_destroy(struct omx_encoder_t *omx) {
|
||||
_omx_encoder_clear_ports(omx);
|
||||
component_set_state(&omx->encoder, OMX_StateLoaded);
|
||||
|
||||
if (omx->i_handler_lock) {
|
||||
vcos_semaphore_delete(&omx->handler_lock);
|
||||
if (omx->i_handler_sem) {
|
||||
vcos_semaphore_delete(&omx->handler_sem);
|
||||
}
|
||||
|
||||
if (omx->i_encoder) {
|
||||
@@ -180,6 +180,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
# define OUT(_next) omx->output_buffer->_next
|
||||
|
||||
OMX_ERRORTYPE error;
|
||||
VCOS_STATUS_T sem_status;
|
||||
size_t slice_size = (IN(nAllocLen) < HW_BUFFER(used) ? IN(nAllocLen) : HW_BUFFER(used));
|
||||
size_t pos = 0;
|
||||
|
||||
@@ -236,7 +237,13 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
}
|
||||
}
|
||||
|
||||
vcos_semaphore_wait(&omx->handler_lock);
|
||||
// vcos_semaphore_wait(&omx->handler_sem);
|
||||
switch (sem_status = vcos_semaphore_wait_timeout(&omx->handler_sem, 3000)) {
|
||||
case VCOS_SUCCESS: break;
|
||||
case VCOS_EAGAIN: LOG_ERROR("Can't wait VCOS semaphore: EAGAIN (timeout)"); return -1;
|
||||
case VCOS_EINVAL: LOG_ERROR("Can't wait VCOS semaphore: EINTVAL"); return -1;
|
||||
default: LOG_ERROR("Can't wait VCOS semaphore: %d", sem_status); return -1;
|
||||
}
|
||||
}
|
||||
|
||||
# undef OUT
|
||||
@@ -305,9 +312,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);
|
||||
@@ -468,7 +473,7 @@ static OMX_ERRORTYPE _omx_event_handler(
|
||||
if (event == OMX_EventError) {
|
||||
LOG_ERROR_OMX((OMX_ERRORTYPE)data1, "OMX error event received");
|
||||
omx->failed = true;
|
||||
vcos_semaphore_post(&omx->handler_lock);
|
||||
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
|
||||
}
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
@@ -483,7 +488,7 @@ static OMX_ERRORTYPE _omx_input_required_handler(
|
||||
struct omx_encoder_t *omx = (struct omx_encoder_t *)v_omx;
|
||||
|
||||
omx->input_required = true;
|
||||
vcos_semaphore_post(&omx->handler_lock);
|
||||
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
@@ -497,6 +502,6 @@ static OMX_ERRORTYPE _omx_output_available_handler(
|
||||
struct omx_encoder_t *omx = (struct omx_encoder_t *)v_omx;
|
||||
|
||||
omx->output_available = true;
|
||||
vcos_semaphore_post(&omx->handler_lock);
|
||||
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
@@ -42,9 +42,9 @@ struct omx_encoder_t {
|
||||
bool input_required;
|
||||
bool output_available;
|
||||
bool failed;
|
||||
VCOS_SEMAPHORE_T handler_lock;
|
||||
VCOS_SEMAPHORE_T handler_sem;
|
||||
|
||||
bool i_handler_lock;
|
||||
bool i_handler_sem;
|
||||
bool i_encoder;
|
||||
bool i_input_port_enabled;
|
||||
bool i_output_port_enabled;
|
||||
|
||||
@@ -67,15 +67,38 @@ 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);
|
||||
|
||||
@@ -31,8 +31,12 @@
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/thread.h>
|
||||
@@ -85,6 +89,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;
|
||||
@@ -229,6 +235,9 @@ int http_server_listen(struct http_server_t *server) {
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path);
|
||||
if (server->tcp_nodelay) {
|
||||
LOG_ERROR("TCP_NODELAY flag can't be used with UNIX socket and will be ignored");
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
||||
if (evhttp_bind_socket(server->run->http, server->host, server->port) < 0) {
|
||||
@@ -533,10 +542,20 @@ 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);
|
||||
if (server->tcp_nodelay && !server->run->unix_fd) {
|
||||
evutil_socket_t fd;
|
||||
int on = 1;
|
||||
|
||||
LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client [%s]:%u ...", client_addr, client_port);
|
||||
assert((fd = bufferevent_getfd(buf_event)) >= 0);
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)) != 0) {
|
||||
LOG_PERROR("HTTP: Can't set TCP_NODELAY to the client [%s]:%u", client_addr, client_port);
|
||||
}
|
||||
}
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||
bufferevent_enable(buf_event, EV_READ);
|
||||
} else {
|
||||
@@ -677,6 +696,9 @@ 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
|
||||
|
||||
@@ -697,8 +719,9 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
if (conn) {
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
}
|
||||
LOG_INFO("HTTP: Disconnected the stream client: [%s]:%u; clients now: %u",
|
||||
client_addr, client_port, RUN(stream_clients_count));
|
||||
|
||||
LOG_INFO("HTTP: Disconnected client: [%s]:%u, id=%s, %s; clients now: %u",
|
||||
client_addr, client_port, client->id, reason, RUN(stream_clients_count));
|
||||
if (conn) {
|
||||
evhttp_connection_free(conn);
|
||||
}
|
||||
@@ -721,6 +744,7 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
|
||||
struct evhttp_connection *conn;
|
||||
struct bufferevent *buf_event;
|
||||
long long now;
|
||||
bool has_clients = false;
|
||||
bool queued = false;
|
||||
|
||||
for (struct stream_client_t *client = server->run->stream_clients; client != NULL; client = client->next) {
|
||||
@@ -751,6 +775,8 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
|
||||
} else if (stream_updated) { // Для dual
|
||||
client->updated_prev = false;
|
||||
}
|
||||
|
||||
has_clients = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -764,6 +790,8 @@ static void _http_queue_send_stream(struct http_server_t *server, bool stream_up
|
||||
queued_fps_second = now;
|
||||
}
|
||||
queued_fps_accum += 1;
|
||||
} else if (!has_clients) {
|
||||
server->run->exposed->queued_fps = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -910,3 +938,33 @@ static bool _expose_blank_picture(struct http_server_t *server) {
|
||||
|
||||
# undef EXPOSED
|
||||
}
|
||||
|
||||
static void _format_bufferevent_reason(short what, char *reason) {
|
||||
char perror_buf[1024] = {0};
|
||||
char *perror_ptr = errno_to_string(EVUTIL_SOCKET_ERROR(), perror_buf, 1024); // evutil_socket_error_to_string() is not thread-sage
|
||||
bool first = true;
|
||||
|
||||
strcat(reason, perror_ptr);
|
||||
strcat(reason, " (");
|
||||
|
||||
# define FILL_REASON(_bev, _name) { \
|
||||
if (what & _bev) { \
|
||||
if (first) { \
|
||||
first = false; \
|
||||
} else { \
|
||||
strcat(reason, ","); \
|
||||
} \
|
||||
strcat(reason, _name); \
|
||||
} \
|
||||
}
|
||||
|
||||
FILL_REASON(BEV_EVENT_READING, "reading");
|
||||
FILL_REASON(BEV_EVENT_WRITING, "writing");
|
||||
FILL_REASON(BEV_EVENT_ERROR, "error");
|
||||
FILL_REASON(BEV_EVENT_TIMEOUT, "timeout");
|
||||
FILL_REASON(BEV_EVENT_EOF, "eof"); // cppcheck-suppress unreadVariable
|
||||
|
||||
# undef FILL_REASON
|
||||
|
||||
strcat(reason, ")");
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ struct http_server_t {
|
||||
char *unix_path;
|
||||
bool unix_rm;
|
||||
mode_t unix_mode;
|
||||
bool tcp_nodelay;
|
||||
unsigned timeout;
|
||||
|
||||
char *user;
|
||||
|
||||
34
src/logging.c
Normal file
34
src/logging.c
Normal file
@@ -0,0 +1,34 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
enum log_level_t log_level;
|
||||
|
||||
bool log_colored;
|
||||
|
||||
pthread_mutex_t log_mutex;
|
||||
@@ -37,16 +37,19 @@
|
||||
#include "threading.h"
|
||||
|
||||
|
||||
enum {
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_PERF,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
LOG_LEVEL_DEBUG,
|
||||
} log_level;
|
||||
enum log_level_t {
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_PERF,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
LOG_LEVEL_DEBUG,
|
||||
};
|
||||
|
||||
bool log_colored;
|
||||
|
||||
pthread_mutex_t log_mutex;
|
||||
extern enum log_level_t log_level;
|
||||
|
||||
extern bool log_colored;
|
||||
|
||||
extern pthread_mutex_t log_mutex;
|
||||
|
||||
|
||||
#define LOGGING_INIT { \
|
||||
@@ -113,7 +116,7 @@ pthread_mutex_t log_mutex;
|
||||
|
||||
#define LOG_PERROR(_msg, ...) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(_perror_buf, 1024); \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
|
||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
||||
}
|
||||
|
||||
@@ -146,7 +149,7 @@ pthread_mutex_t log_mutex;
|
||||
#define LOG_VERBOSE_PERROR(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(_perror_buf, 1024); \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
|
||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
||||
} \
|
||||
}
|
||||
@@ -158,11 +161,11 @@ pthread_mutex_t log_mutex;
|
||||
}
|
||||
|
||||
|
||||
INLINE char *errno_to_string(char *buf, size_t size) {
|
||||
INLINE char *errno_to_string(int error, char *buf, size_t size) {
|
||||
# if defined(__GLIBC__) && defined(_GNU_SOURCE)
|
||||
return strerror_r(errno, buf, size);
|
||||
return strerror_r(error, buf, size);
|
||||
# else
|
||||
strerror_r(errno, buf, size);
|
||||
strerror_r(error, buf, size);
|
||||
return buf;
|
||||
# endif
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ enum _OPT_VALUES {
|
||||
_O_RESOLUTION = 'r',
|
||||
_O_FORMAT = 'm',
|
||||
_O_TV_STANDARD = 'a',
|
||||
_O_IO_METHOD = 'I',
|
||||
_O_DESIRED_FPS = 'f',
|
||||
_O_MIN_FRAME_SIZE = 'z',
|
||||
_O_PERSISTENT = 'n',
|
||||
@@ -91,10 +92,14 @@ enum _OPT_VALUES {
|
||||
_O_BACKLIGHT_COMPENSATION,
|
||||
_O_WHITE_BALANCE,
|
||||
_O_GAIN,
|
||||
_O_COLOR_EFFECT,
|
||||
_O_FLIP_VERTICAL,
|
||||
_O_FLIP_HORIZONTAL,
|
||||
|
||||
_O_USER,
|
||||
_O_PASSWD,
|
||||
_O_STATIC,
|
||||
_O_TCP_NODELAY,
|
||||
_O_SERVER_TIMEOUT,
|
||||
|
||||
#ifdef WITH_GPIO
|
||||
@@ -128,6 +133,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},
|
||||
@@ -152,6 +158,9 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"backlight-compensation", required_argument, NULL, _O_BACKLIGHT_COMPENSATION},
|
||||
{"white-balance", required_argument, NULL, _O_WHITE_BALANCE},
|
||||
{"gain", required_argument, NULL, _O_GAIN},
|
||||
{"color-effect", required_argument, NULL, _O_COLOR_EFFECT},
|
||||
{"flip-vertical", required_argument, NULL, _O_FLIP_VERTICAL},
|
||||
{"flip-horizontal", required_argument, NULL, _O_FLIP_HORIZONTAL},
|
||||
|
||||
{"host", required_argument, NULL, _O_HOST},
|
||||
{"port", required_argument, NULL, _O_PORT},
|
||||
@@ -166,6 +175,7 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
|
||||
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
|
||||
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
|
||||
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
|
||||
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
|
||||
|
||||
#ifdef WITH_GPIO
|
||||
@@ -327,8 +337,9 @@ 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_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 1, 8192, 0);
|
||||
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
|
||||
case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true);
|
||||
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_buffers, 1, 32, 0);
|
||||
@@ -355,6 +366,9 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
|
||||
OPT_CTL_DEFAULT_NOBREAK(backlight_compensation);
|
||||
OPT_CTL_DEFAULT_NOBREAK(white_balance);
|
||||
OPT_CTL_DEFAULT_NOBREAK(gain);
|
||||
OPT_CTL_DEFAULT_NOBREAK(color_effect);
|
||||
OPT_CTL_DEFAULT_NOBREAK(flip_vertical);
|
||||
OPT_CTL_DEFAULT_NOBREAK(flip_horizontal);
|
||||
break;
|
||||
case _O_BRIGHTNESS: OPT_CTL_AUTO(brightness);
|
||||
case _O_CONTRAST: OPT_CTL_MANUAL(contrast);
|
||||
@@ -365,6 +379,9 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
|
||||
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_COLOR_EFFECT: OPT_CTL_MANUAL(color_effect);
|
||||
case _O_FLIP_VERTICAL: OPT_CTL_MANUAL(flip_vertical);
|
||||
case _O_FLIP_HORIZONTAL: OPT_CTL_MANUAL(flip_horizontal);
|
||||
|
||||
case _O_HOST: OPT_SET(server->host, optarg);
|
||||
case _O_PORT: OPT_NUMBER("--port", server->port, 1, 65535, 0);
|
||||
@@ -379,6 +396,7 @@ int options_parse(struct options_t *options, struct device_t *dev, struct encode
|
||||
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0);
|
||||
case _O_SLOWDOWN: OPT_SET(server->slowdown, true);
|
||||
case _O_FAKE_RESOLUTION: OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
|
||||
case _O_TCP_NODELAY: OPT_SET(server->tcp_nodelay, true);
|
||||
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@@ -548,9 +566,12 @@ 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");
|
||||
printf(" produces small-sized garbage frames. Default: %zu bytes.\n\n", dev->min_frame_size);
|
||||
printf(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. Default: disabled.\n\n");
|
||||
printf(" -t|--dv-timings ───────────────────── Enable DV timings querying and events processing\n");
|
||||
printf(" to automatic resolution change. Default: disabled.\n\n");
|
||||
@@ -591,6 +612,9 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
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(" --color-effect <N|default> ─────────── Set color effect. Default: no change.\n\n");
|
||||
printf(" --flip-vertical <1|0|default> ──────── Set vertical flip. Default: no change.\n\n");
|
||||
printf(" --flip-horizontal <1|0|default> ────── Set horizontal flip. 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");
|
||||
@@ -616,6 +640,8 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
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 the /state. Default: disabled.\n\n");
|
||||
printf(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Ignored for --unix.\n");
|
||||
printf(" 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");
|
||||
|
||||
@@ -51,7 +51,7 @@ size_t picture_get_generous_size(unsigned width, unsigned height) {
|
||||
|
||||
void picture_realloc_data(struct picture_t *picture, size_t size) {
|
||||
if (picture->allocated < size) {
|
||||
LOG_DEBUG("Increasing picture 0x%p buffer: %zu -> %zu (+%zu)",
|
||||
LOG_DEBUG("Increasing picture %p buffer: %zu -> %zu (+%zu)",
|
||||
picture, picture->allocated, size, size - picture->allocated);
|
||||
A_REALLOC(picture->data, size);
|
||||
picture->allocated = size;
|
||||
@@ -73,9 +73,7 @@ void picture_append_data(struct picture_t *picture, const unsigned char *data, s
|
||||
}
|
||||
|
||||
void picture_copy(const struct picture_t *src, struct picture_t *dest) {
|
||||
assert(src->allocated);
|
||||
|
||||
picture_set_data(dest, src->data, src->allocated);
|
||||
picture_set_data(dest, src->data, src->used);
|
||||
|
||||
# define COPY(_field) dest->_field = src->_field
|
||||
|
||||
@@ -92,5 +90,9 @@ void picture_copy(const struct picture_t *src, struct picture_t *dest) {
|
||||
}
|
||||
|
||||
bool picture_compare(const struct picture_t *a, const struct picture_t *b) {
|
||||
return (a->used == b->used && !memcmp(a->data, b->data, b->used));
|
||||
return (
|
||||
a->allocated && b->allocated
|
||||
&& a->used == b->used
|
||||
&& !memcmp(a->data, b->data, b->used)
|
||||
);
|
||||
}
|
||||
|
||||
21
src/stream.c
21
src/stream.c
@@ -449,23 +449,24 @@ static void *_worker_thread(void *v_worker) {
|
||||
GPIO_SET_HIGH_AT(workers_busy_at, worker->number);
|
||||
# endif
|
||||
|
||||
if (encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index) < 0) {
|
||||
worker->job_failed = false;
|
||||
}
|
||||
worker->job_failed = (bool)encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index);
|
||||
|
||||
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
|
||||
worker->job_start_ts = PICTURE(encode_begin_ts);
|
||||
atomic_store(&worker->has_job, false);
|
||||
if (!worker->job_failed) {
|
||||
worker->job_start_ts = PICTURE(encode_begin_ts);
|
||||
worker->last_comp_time = PICTURE(encode_end_ts) - worker->job_start_ts;
|
||||
|
||||
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);
|
||||
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);
|
||||
} else {
|
||||
LOG_VERBOSE("Compression failed: worker=%u, buffer=%u", worker->number, worker->buf_index);
|
||||
}
|
||||
} else {
|
||||
worker->job_failed = true;
|
||||
atomic_store(&worker->has_job, false);
|
||||
}
|
||||
|
||||
atomic_store(&worker->has_job, false);
|
||||
|
||||
# undef PICTURE
|
||||
}
|
||||
|
||||
|
||||
10
src/tools.h
10
src/tools.h
@@ -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);
|
||||
}
|
||||
@@ -74,7 +78,13 @@ INLINE long double get_now_monotonic(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
|
||||
# if defined(CLOCK_MONOTONIC_RAW)
|
||||
get_now(CLOCK_MONOTONIC_RAW, &sec, &msec);
|
||||
# elif defined(CLOCK_MONOTONIC_FAST)
|
||||
get_now(CLOCK_MONOTONIC_FAST, &sec, &msec);
|
||||
# else
|
||||
get_now(CLOCK_MONOTONIC, &sec, &msec);
|
||||
# endif
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user