Compare commits

..

38 Commits
v0.55 ... v0.63

Author SHA1 Message Date
Devaev Maxim
fc4cbb1fe1 Bump version: 0.62 → 0.63 2019-03-22 05:59:06 +03:00
Devaev Maxim
67f9bcf4c8 fixed help 2019-03-22 05:58:54 +03:00
Maxim Devaev
da227ec234 Update README.ru.md 2019-03-22 05:50:23 +03:00
Maxim Devaev
933be02c86 Update README.md 2019-03-22 05:48:37 +03:00
Devaev Maxim
28e979a2be dots 2019-03-22 05:23:26 +03:00
Devaev Maxim
8cde363338 pkg dir 2019-03-22 05:14:19 +03:00
Devaev Maxim
4741fe1952 http static server 2019-03-22 04:06:41 +03:00
Devaev Maxim
7d4ae57fbd refactoring 2019-03-21 17:54:43 +03:00
Devaev Maxim
c50388ab9f refactoring 2019-03-21 01:28:02 +03:00
Devaev Maxim
17099e86de --static stub 2019-03-20 23:17:41 +03:00
Devaev Maxim
6528352e04 http basic auth 2019-03-20 16:15:26 +03:00
Devaev Maxim
4f7b426068 fixed -l option 2019-03-20 03:29:05 +03:00
Devaev Maxim
acc8628f3d Bump version: 0.61 → 0.62 2019-03-19 22:33:06 +03:00
Devaev Maxim
46e99be201 Supported XSI strerror_r() 2019-03-19 22:26:30 +03:00
Devaev Maxim
7fbeca41fa refactoring 2019-03-18 21:43:23 +03:00
Devaev Maxim
73ceba77a8 Bump version: 0.60 → 0.61 2019-03-18 00:30:13 +03:00
Devaev Maxim
a3e5d17628 redefineable XIOCTL_RETRIES 2019-03-17 22:54:26 +03:00
Devaev Maxim
c30dea20a5 Bump version: 0.59 → 0.60 2019-03-17 20:10:11 +03:00
Devaev Maxim
b31450ba41 size -> used 2019-03-17 19:44:49 +03:00
Devaev Maxim
c05457ce2f separate hw_buffer_t size and allocated 2019-03-17 19:33:56 +03:00
Devaev Maxim
9e63076ec5 fixed omx slice_size 2019-03-17 18:50:12 +03:00
Devaev Maxim
92844fc3db Bump version: 0.58 → 0.59 2019-03-17 15:22:30 +03:00
Devaev Maxim
7bb9434850 shorten log about stream clients 2019-03-17 15:06:08 +03:00
Devaev Maxim
3104a00913 Bump version: 0.57 → 0.58 2019-03-16 19:20:39 +03:00
Devaev Maxim
28c51f3f2f better help 2019-03-16 16:27:45 +03:00
Devaev Maxim
5c035f21c8 some short options 2019-03-16 15:28:11 +03:00
Devaev Maxim
52ecaf7fd3 refactoring 2019-03-16 13:22:06 +03:00
Devaev Maxim
aa007c676f limited default workers and buffers number 2019-03-16 13:21:44 +03:00
Devaev Maxim
1f0b49e5fe refactoring 2019-03-16 13:09:30 +03:00
Devaev Maxim
d979209096 Bump version: 0.56 → 0.57 2019-03-16 01:36:14 +03:00
Devaev Maxim
154d8e4c2b updated readme 2019-03-16 01:35:47 +03:00
Devaev Maxim
27d42d2545 --device-persistent -> -n|--persistent 2019-03-16 01:30:55 +03:00
Devaev Maxim
142670c374 refactoring 2019-03-16 01:21:03 +03:00
Devaev Maxim
2a9c4d7e7a C11 atomic types 2019-03-16 01:02:27 +03:00
Devaev Maxim
dc43f01a7d Bump version: 0.55 → 0.56 2019-03-15 23:59:25 +03:00
Devaev Maxim
bac7a2595e refactoring 2019-03-15 14:21:50 +03:00
Devaev Maxim
50158397a0 refactoring 2019-03-15 13:34:06 +03:00
Devaev Maxim
484f89cb82 slowdown 2019-03-15 13:26:53 +03:00
37 changed files with 1310 additions and 570 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 0.55
current_version = 0.63
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
serialize =
{major}.{minor}
@@ -10,7 +10,7 @@ serialize =
search = VERSION "{current_version}"
replace = VERSION "{new_version}"
[bumpversion:file:PKGBUILD]
[bumpversion:file:pkg/arch/PKGBUILD]
search = pkgver={current_version}
replace = pkgver={new_version}

9
.gitignore vendored
View File

@@ -1,8 +1,7 @@
/pkg/
/src/ustreamer-*/
/src/v*.tar.gz
/v*.tar.gz
/ustreamer-*.pkg.tar.xz
/pkg/arch/pkg/
/pkg/arch/src/
/pkg/arch/v*.tar.gz
/pkg/arch/ustreamer-*.pkg.tar.xz
/vgcore.*
/ustreamer
/*.sock

View File

@@ -7,8 +7,8 @@ CC ?= gcc
# =====
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
override CFLAGS += -c -std=c99 -Wall -Wextra -D_GNU_SOURCE
SOURCES = $(shell ls src/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
override CFLAGS += -c -std=c11 -Wall -Wextra -D_GNU_SOURCE
SOURCES = $(shell ls src/*.c src/http/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
PROG = ustreamer
ifeq ($(shell ls -d /opt/vc/include 2>/dev/null), /opt/vc/include)
@@ -29,8 +29,8 @@ install: $(PROG)
regen:
tools/make-jpeg-h.py src/data/blank.jpeg src/data/blank_jpeg.h BLANK 640 480
tools/make-html-h.py src/data/index.html src/data/index_html.h HTML_INDEX_PAGE
tools/make-jpeg-h.py src/http/data/blank.jpeg src/http/data/blank_jpeg.h BLANK
tools/make-html-h.py src/http/data/index.html src/http/data/index_html.h INDEX
$(PROG): $(OBJECTS)
@@ -59,5 +59,6 @@ push:
clean-all: clean
clean:
rm -f src/*.o src/encoders/*/*.o vgcore.* *.sock $(PROG)
rm -rf pkg src/$(PROG)-* src/v*.tar.gz v*.tar.gz $(PROG)-*.pkg.tar.xz
find src -name '*.o' -exec rm '{}' \;
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.xz
rm -f vgcore.* *.sock $(PROG)

View File

@@ -15,16 +15,14 @@
| Option to skip frames when streaming<br>static images by HTTP to save traffic | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Streaming via UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No |
| Option to serve files<br>with a built-in HTTP server | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Regular files only |
| Access to webcam controls (focus, servos)<br>and settings such as brightness via HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
| Option to serve files<br>with a built-in HTTP server, auth settings | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes |
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()```.
* ```3``` ...and there'll never be. µStreamer is designed UNIX-way, so if you need a small website with your broadcast, install NGINX.
-----
# TL;DR
If you're going to live-stream from your backyard webcam and need to control it, use mjpg-streamer. If you need a high-quality image with high FPS - µStreamer for the win.
@@ -56,6 +54,8 @@ The recommended way of running µStreamer with [Auvidea B101](https://www.raspbe
$ ./ustreamer \
--format=uyvy \ # Device input format
--encoder=omx \ # Hardware encoding with OpenMAX
--workers=3 \ # Maximum workers for OpenMAX
--persistent \ # Don't re-initialize device on timeout (for example when HDMI cable was disconnected)
--dv-timings \ # Use DV-timings
--drop-same-frames=30 # Save that traffic
```

View File

@@ -15,16 +15,14 @@
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Стрим через UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Возможность сервить файлы встроенным<br>HTTP-сервером | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Нет каталогов |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
Сносочки:
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
* ```3``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
-----
# TL;DR
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
@@ -56,6 +54,8 @@ $ ./ustreamer --help
$ ./ustreamer \
--format=uyvy \ # Настройка входного формата устройства
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
--workers=3 \ # Максимум воркеров для OpenMAX
--persistent \ # Не переинициализировать устройство при таймауте (например, когда был отключен HDMI-кабель)
--dv-timings \ # Включение DV-таймингов
--drop-same-frames=30 # Экономим трафик
```

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=0.55
pkgver=0.63
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pi-kvm/ustreamer"

View File

@@ -22,4 +22,4 @@
#pragma once
#define VERSION "0.55"
#define VERSION "0.63"

View File

@@ -95,7 +95,7 @@ struct device_t *device_init() {
dev->height = 480;
dev->format = V4L2_PIX_FMT_YUYV;
dev->standard = V4L2_STD_UNKNOWN;
dev->n_buffers = max_u(sysconf(_SC_NPROCESSORS_ONLN), 1) + 1;
dev->n_buffers = max_u(min_u(sysconf(_SC_NPROCESSORS_ONLN), 4), 1) + 1;
dev->n_workers = dev->n_buffers;
dev->timeout = 1;
dev->error_delay = 1;
@@ -177,8 +177,8 @@ void device_close(struct device_t *dev) {
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
if (HW_BUFFER(size) > 0 && HW_BUFFER(data) != MAP_FAILED) {
if (munmap(HW_BUFFER(data), HW_BUFFER(size)) < 0) {
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);
}
}
@@ -201,6 +201,77 @@ void device_close(struct device_t *dev) {
}
}
int device_switch_capturing(struct device_t *dev, bool enable) {
if (enable != dev->run->capturing) {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
LOG_DEBUG("Calling ioctl(%s) ...", (enable ? "VIDIOC_STREAMON" : "VIDIOC_STREAMOFF"));
if (xioctl(dev->run->fd, (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
LOG_PERROR("Unable to %s capturing", (enable ? "start" : "stop"));
if (enable) {
return -1;
}
}
dev->run->capturing = enable;
LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
}
return 0;
}
int device_grab_buffer(struct device_t *dev) {
struct v4l2_buffer buf_info;
MEMSET_ZERO(buf_info);
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info.memory = V4L2_MEMORY_MMAP;
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_DQBUF, &buf_info) < 0) {
LOG_PERROR("Unable to dequeue buffer");
return -1;
}
LOG_DEBUG("Got a new frame in buffer index=%u; bytesused=%u", buf_info.index, buf_info.bytesused);
if (buf_info.index >= dev->run->n_buffers) {
LOG_ERROR("Got invalid buffer index=%u; nbuffers=%u", buf_info.index, dev->run->n_buffers);
return -1;
}
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));
return buf_info.index;
}
int device_release_buffer(struct device_t *dev, unsigned index) {
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_QBUF, &dev->run->hw_buffers[index].buf_info) < 0) {
LOG_PERROR("Unable to requeue buffer");
return -1;
}
dev->run->hw_buffers[index].used = 0;
return 0;
}
int device_consume_event(struct device_t *dev) {
struct v4l2_event event;
LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ...");
if (!xioctl(dev->run->fd, VIDIOC_DQEVENT, &event)) {
switch (event.type) {
case V4L2_EVENT_SOURCE_CHANGE:
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
return -1;
case V4L2_EVENT_EOS:
LOG_INFO("Got V4L2_EVENT_EOS: end of stream (ignored)");
return 0;
}
} else {
LOG_PERROR("Got some V4L2 device event, but where is it? ");
}
return 0;
}
static int _device_open_check_cap(struct device_t *dev) {
struct v4l2_capability cap;
int input = dev->input; // Needs pointer to int for ioctl()
@@ -397,7 +468,7 @@ static int _device_open_mmap(struct device_t *dev) {
LOG_PERROR("Can't map device buffer %u", dev->run->n_buffers);
return -1;
}
HW_BUFFER(size) = buf_info.length;
HW_BUFFER(allocated) = buf_info.length;
# undef HW_BUFFER
}
@@ -426,11 +497,11 @@ static void _device_open_alloc_picbufs(struct device_t *dev) {
LOG_DEBUG("Allocating picture buffers ...");
A_CALLOC(dev->run->pictures, dev->run->n_buffers);
dev->run->max_picture_size = ((dev->run->width * dev->run->height) << 1) * 2;
dev->run->max_raw_image_size = ((dev->run->width * dev->run->height) << 1) * 2;
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
LOG_DEBUG("Allocating picture buffer %u sized %zu bytes... ", index, dev->run->max_picture_size);
A_CALLOC(dev->run->pictures[index].data, dev->run->max_picture_size);
dev->run->pictures[index].allocated = dev->run->max_picture_size;
LOG_DEBUG("Allocating picture buffer %u sized %zu bytes... ", index, dev->run->max_raw_image_size);
A_CALLOC(dev->run->pictures[index].data, dev->run->max_raw_image_size);
dev->run->pictures[index].allocated = dev->run->max_raw_image_size;
}
}

View File

@@ -24,7 +24,6 @@
#include <stddef.h>
#include <stdbool.h>
#include <signal.h>
#include <linux/videodev2.h>
@@ -43,13 +42,15 @@
struct hw_buffer_t {
unsigned char *data;
size_t size;
unsigned char *data;
size_t used;
size_t allocated;
struct v4l2_buffer buf_info;
};
struct picture_t {
unsigned char *data;
size_t size;
size_t used;
size_t allocated;
long double grab_time;
long double encode_begin_time;
@@ -65,7 +66,7 @@ struct device_runtime_t {
// unsigned n_workers; // FIXME
struct hw_buffer_t *hw_buffers;
struct picture_t *pictures;
size_t max_picture_size;
size_t max_raw_image_size;
bool capturing;
};
@@ -106,7 +107,6 @@ struct device_t {
struct controls_t *ctl;
struct device_runtime_t *run;
sig_atomic_t volatile stop;
};
@@ -118,3 +118,8 @@ v4l2_std_id device_parse_standard(const char *str);
int device_open(struct device_t *dev);
void device_close(struct device_t *dev);
int device_switch_capturing(struct device_t *dev, bool enable);
int device_grab_buffer(struct device_t *dev);
int device_release_buffer(struct device_t *dev, unsigned index);
int device_consume_event(struct device_t *dev);

View File

@@ -57,7 +57,7 @@ struct encoder_t *encoder_init() {
A_CALLOC(run, 1);
run->type = ENCODER_TYPE_CPU;
run->quality = 80;
A_PTHREAD_M_INIT(&run->mutex);
A_MUTEX_INIT(&run->mutex);
A_CALLOC(encoder, 1);
encoder->type = run->type;
@@ -119,7 +119,7 @@ void encoder_destroy(struct encoder_t *encoder) {
free(encoder->run->omxs);
}
# endif
A_PTHREAD_M_DESTROY(&encoder->run->mutex);
A_MUTEX_DESTROY(&encoder->run->mutex);
free(encoder->run);
free(encoder);
}
@@ -150,9 +150,9 @@ void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
&& encoder->run->type != ENCODER_TYPE_HW
) {
LOG_INFO("Switching to HW JPEG encoder because the input format is (M)JPEG");
A_PTHREAD_M_LOCK(&encoder->run->mutex);
A_MUTEX_LOCK(&encoder->run->mutex);
encoder->run->type = ENCODER_TYPE_HW;
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
A_MUTEX_UNLOCK(&encoder->run->mutex);
}
if (encoder->run->type == ENCODER_TYPE_HW) {
@@ -161,9 +161,9 @@ void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
goto use_fallback;
}
if (hw_encoder_prepare_live(dev, encoder->quality) < 0) {
A_PTHREAD_M_LOCK(&encoder->run->mutex);
A_MUTEX_LOCK(&encoder->run->mutex);
encoder->run->quality = 0;
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
A_MUTEX_UNLOCK(&encoder->run->mutex);
LOG_INFO("Using JPEG quality: HW-default");
}
}
@@ -181,10 +181,10 @@ void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
return;
use_fallback:
A_PTHREAD_M_LOCK(&encoder->run->mutex);
A_MUTEX_LOCK(&encoder->run->mutex);
encoder->run->type = ENCODER_TYPE_CPU;
encoder->run->quality = encoder->quality;
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
A_MUTEX_UNLOCK(&encoder->run->mutex);
}
#pragma GCC diagnostic ignored "-Wunused-label"
@@ -213,10 +213,10 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
# pragma GCC diagnostic push
use_fallback:
LOG_INFO("Error while compressing, falling back to CPU");
A_PTHREAD_M_LOCK(&encoder->run->mutex);
A_MUTEX_LOCK(&encoder->run->mutex);
encoder->run->type = ENCODER_TYPE_CPU;
encoder->run->quality = encoder->quality;
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
A_MUTEX_UNLOCK(&encoder->run->mutex);
return -1;
# pragma GCC diagnostic pop
}

View File

@@ -40,14 +40,14 @@
struct _jpeg_dest_manager_t {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buffer; // Start of buffer
unsigned char *outbuffer_cursor;
size_t *written;
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buffer; // Start of buffer
struct picture_t *picture;
unsigned char *picture_data_cursor;
};
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, size_t *written);
static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture);
static void _jpeg_write_scanlines_yuyv(
struct jpeg_compress_struct *jpeg, const unsigned char *data,
@@ -79,10 +79,7 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
jpeg.err = jpeg_std_error(&jpeg_error);
jpeg_create_compress(&jpeg);
# define PICTURE(_next) dev->run->pictures[index]._next
PICTURE(size) = 0;
_jpeg_set_dest_picture(&jpeg, PICTURE(data), &PICTURE(size));
_jpeg_set_picture(&jpeg, &dev->run->pictures[index]);
jpeg.image_width = dev->run->width;
jpeg.image_height = dev->run->height;
@@ -112,11 +109,11 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);
assert(PICTURE(size) > 0);
assert(PICTURE(size) <= dev->run->max_picture_size);
assert(dev->run->pictures[index].used > 0);
}
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, size_t *written) {
static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture) {
struct _jpeg_dest_manager_t *dest;
if (jpeg->dest == NULL) {
@@ -129,8 +126,10 @@ static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture,
dest->mgr.init_destination = _jpeg_init_destination;
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
dest->mgr.term_destination = _jpeg_term_destination;
dest->outbuffer_cursor = picture;
dest->written = written;
dest->picture = picture;
dest->picture_data_cursor = picture->data;
picture->used = 0;
}
#define YUV_R(_y, _, _v) (((_y) + (359 * (_v))) >> 8)
@@ -262,7 +261,7 @@ static void _jpeg_write_scanlines_rgb24(
}
}
#define JPEG_OUTPUT_BUFFER_SIZE 4096
#define JPEG_OUTPUT_BUFFER_SIZE 4096
static void _jpeg_init_destination(j_compress_ptr jpeg) {
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
@@ -280,10 +279,13 @@ static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
// Called whenever local jpeg buffer fills up
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
size_t new_used = dest->picture->used + JPEG_OUTPUT_BUFFER_SIZE;
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
*dest->written += JPEG_OUTPUT_BUFFER_SIZE;
assert(new_used <= dest->picture->allocated);
memcpy(dest->picture_data_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
dest->picture_data_cursor += JPEG_OUTPUT_BUFFER_SIZE;
dest->picture->used = new_used;
dest->mgr.next_output_byte = dest->buffer;
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
@@ -296,12 +298,15 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
// Usually needs to flush buffer
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
size_t data_count = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
size_t new_used = dest->picture->used + final;
assert(new_used <= dest->picture->allocated);
// Write any data remaining in the buffer
memcpy(dest->outbuffer_cursor, dest->buffer, data_count);
dest->outbuffer_cursor += data_count;
*dest->written += data_count;
memcpy(dest->picture_data_cursor, dest->buffer, final);
dest->picture_data_cursor += final;
dest->picture->used = new_used;
}
#undef JPEG_OUTPUT_BUFFER_SIZE

View File

@@ -69,8 +69,8 @@ void hw_encoder_compress_buffer(struct device_t *dev, unsigned index) {
# define PICTURE(_next) dev->run->pictures[index]._next
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
assert(PICTURE(allocated) >= HW_BUFFER(size) + sizeof(HUFFMAN_TABLE));
PICTURE(size) = _memcpy_with_huffman(PICTURE(data), HW_BUFFER(data), HW_BUFFER(size));
assert(PICTURE(allocated) >= HW_BUFFER(used) + sizeof(HUFFMAN_TABLE));
PICTURE(used) = _memcpy_with_huffman(PICTURE(data), HW_BUFFER(data), HW_BUFFER(used));
# undef HW_BUFFER
# undef PICTURE

View File

@@ -42,8 +42,8 @@
#include "encoder.h"
#define INPUT_PORT 340
#define OUTPUT_PORT 341
static const OMX_U32 _INPUT_PORT = 340;
static const OMX_U32 _OUTPUT_PORT = 341;
static int _i_omx = 0;
@@ -78,6 +78,7 @@ struct omx_encoder_t *omx_encoder_init() {
// - https://github.com/gagle/raspberrypi-openmax-jpeg/blob/master/jpeg.c
// - https://www.raspberrypi.org/forums/viewtopic.php?t=154790
// - https://bitbucket.org/bensch128/omxjpegencode/src/master/jpeg_encoder.cpp
// - http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html
struct omx_encoder_t *omx;
OMX_ERRORTYPE error;
@@ -178,7 +179,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;
size_t slice_size = IN(nAllocLen);
size_t slice_size = (IN(nAllocLen) < HW_BUFFER(used) ? IN(nAllocLen) : HW_BUFFER(used));
size_t pos = 0;
if ((error = OMX_FillThisBuffer(omx->encoder, omx->output_buffer)) != OMX_ErrorNone) {
@@ -186,7 +187,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
return -1;
}
PICTURE(size) = 0;
PICTURE(used) = 0;
omx->output_available = false;
omx->input_required = true;
@@ -198,9 +199,9 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
if (omx->output_available) {
omx->output_available = false;
assert(PICTURE(size) + OUT(nFilledLen) <= dev->run->max_picture_size);
memcpy(PICTURE(data) + PICTURE(size), OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
PICTURE(size) += OUT(nFilledLen);
assert(PICTURE(used) + OUT(nFilledLen) <= PICTURE(allocated));
memcpy(PICTURE(data) + PICTURE(used), OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
PICTURE(used) += OUT(nFilledLen);
if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) {
OUT(nFlags) = 0;
@@ -216,7 +217,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
if (omx->input_required) {
omx->input_required = false;
if (pos == HW_BUFFER(size)) {
if (pos == HW_BUFFER(used)) {
continue;
}
@@ -226,8 +227,8 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
pos += slice_size;
if (pos + slice_size > HW_BUFFER(size)) {
slice_size = HW_BUFFER(size) - pos;
if (pos + slice_size > HW_BUFFER(used)) {
slice_size = HW_BUFFER(used) - pos;
}
if ((error = OMX_EmptyThisBuffer(omx->encoder, omx->input_buffer)) != OMX_ErrorNone) {
@@ -247,8 +248,6 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
}
static int _omx_init_component(struct omx_encoder_t *omx) {
// http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html
OMX_ERRORTYPE error;
OMX_CALLBACKTYPE callbacks;
@@ -300,7 +299,7 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
LOG_DEBUG("Setting up OMX JPEG input port ...");
if (component_get_portdef(&omx->encoder, &portdef, INPUT_PORT) < 0) {
if (component_get_portdef(&omx->encoder, &portdef, _INPUT_PORT) < 0) {
LOG_ERROR("... first");
return -1;
}
@@ -313,7 +312,7 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
# undef ALIGN_HEIGHT
portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused;
portdef.nBufferSize = dev->run->max_picture_size;
portdef.nBufferSize = dev->run->max_raw_image_size;
# define MAP_FORMAT(_v4l2_format, _omx_format) \
case _v4l2_format: { portdef.format.image.eColorFormat = _omx_format; break; }
@@ -338,17 +337,17 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
return -1;
}
if (component_get_portdef(&omx->encoder, &portdef, INPUT_PORT) < 0) {
if (component_get_portdef(&omx->encoder, &portdef, _INPUT_PORT) < 0) {
LOG_ERROR("... second");
return -1;
}
if (component_enable_port(&omx->encoder, INPUT_PORT) < 0) {
if (component_enable_port(&omx->encoder, _INPUT_PORT) < 0) {
return -1;
}
omx->i_input_port_enabled = true;
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->input_buffer, INPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->input_buffer, _INPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't allocate OMX JPEG input buffer");
return -1;
}
@@ -361,7 +360,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
LOG_DEBUG("Setting up OMX JPEG output port ...");
if (component_get_portdef(&omx->encoder, &portdef, OUTPUT_PORT) < 0) {
if (component_get_portdef(&omx->encoder, &portdef, _OUTPUT_PORT) < 0) {
LOG_ERROR("... first");
return -1;
}
@@ -374,7 +373,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
return -1;
}
if (component_get_portdef(&omx->encoder, &portdef, OUTPUT_PORT) < 0) {
if (component_get_portdef(&omx->encoder, &portdef, _OUTPUT_PORT) < 0) {
LOG_ERROR("... second");
return -1;
}
@@ -395,7 +394,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
OMX_PARAM_IJGSCALINGTYPE ijg;
OMX_INIT_STRUCTURE(ijg);
ijg.nPortIndex = OUTPUT_PORT;
ijg.nPortIndex = _OUTPUT_PORT;
ijg.bEnabled = OMX_TRUE;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamBrcmEnableIJGTableScaling, &ijg)) != OMX_ErrorNone) {
@@ -408,7 +407,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
OMX_IMAGE_PARAM_QFACTORTYPE qfactor;
OMX_INIT_STRUCTURE(qfactor);
qfactor.nPortIndex = OUTPUT_PORT;
qfactor.nPortIndex = _OUTPUT_PORT;
qfactor.nQFactor = quality;
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamQFactor, &qfactor)) != OMX_ErrorNone) {
@@ -417,12 +416,12 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
}
}
if (component_enable_port(&omx->encoder, OUTPUT_PORT) < 0) {
if (component_enable_port(&omx->encoder, _OUTPUT_PORT) < 0) {
return -1;
}
omx->i_output_port_enabled = true;
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->output_buffer, OUTPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->output_buffer, _OUTPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't allocate OMX JPEG output buffer");
return -1;
}
@@ -434,23 +433,23 @@ static int _omx_encoder_clear_ports(struct omx_encoder_t *omx) {
int retcode = 0;
if (omx->i_output_port_enabled) {
retcode -= component_disable_port(&omx->encoder, OUTPUT_PORT);
retcode -= component_disable_port(&omx->encoder, _OUTPUT_PORT);
omx->i_output_port_enabled = false;
}
if (omx->i_input_port_enabled) {
retcode -= component_disable_port(&omx->encoder, INPUT_PORT);
retcode -= component_disable_port(&omx->encoder, _INPUT_PORT);
omx->i_input_port_enabled = false;
}
if (omx->input_buffer) {
if ((error = OMX_FreeBuffer(omx->encoder, INPUT_PORT, omx->input_buffer)) != OMX_ErrorNone) {
if ((error = OMX_FreeBuffer(omx->encoder, _INPUT_PORT, omx->input_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't free OMX JPEG input buffer");
// retcode -= 1;
}
omx->input_buffer = NULL;
}
if (omx->output_buffer) {
if ((error = OMX_FreeBuffer(omx->encoder, OUTPUT_PORT, omx->output_buffer)) != OMX_ErrorNone) {
if ((error = OMX_FreeBuffer(omx->encoder, _OUTPUT_PORT, omx->output_buffer)) != OMX_ErrorNone) {
LOG_OMX_ERROR(error, "Can't free OMX JPEG output buffer");
// retcode -= 1;
}

71
src/http/base64.c Normal file
View File

@@ -0,0 +1,71 @@
/*****************************************************************************
# #
# 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 <stdlib.h>
#include <string.h>
#include <assert.h>
#include "../tools.h"
#include "base64.h"
static const char ENCODING_TABLE[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/',
};
static const unsigned MOD_TABLE[] = {0, 2, 1};
char *base64_encode(const unsigned char *str) {
size_t str_len = strlen((const char *)str);
size_t encoded_size = 4 * ((str_len + 2) / 3) + 1; // +1 for '\0'
char *encoded;
A_CALLOC(encoded, encoded_size);
for (unsigned str_index = 0, encoded_index = 0; str_index < str_len;) {
unsigned octet_a = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
unsigned octet_b = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
unsigned octet_c = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 3 * 6) & 0x3F];
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 2 * 6) & 0x3F];
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 1 * 6) & 0x3F];
encoded[encoded_index++] = ENCODING_TABLE[(triple >> 0 * 6) & 0x3F];
}
for (unsigned index = 0; index < MOD_TABLE[str_len % 3]; index++) {
encoded[encoded_size - 2 - index] = '=';
}
encoded[encoded_size - 1] = '\0';
return encoded;
}

28
src/http/base64.h Normal file
View File

@@ -0,0 +1,28 @@
/*****************************************************************************
# #
# 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/>. #
# #
*****************************************************************************/
#pragma once
#include <stdlib.h>
char *base64_encode(const unsigned char *str);

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -20,6 +20,9 @@
*****************************************************************************/
#pragma once
const unsigned BLANK_JPEG_WIDTH = 640;
const unsigned BLANK_JPEG_HEIGHT = 480;

View File

@@ -22,7 +22,7 @@
#pragma once
#include "../config.h"
#include "../../config.h"
const char HTML_INDEX_PAGE[] = " \

73
src/http/mime.c Normal file
View File

@@ -0,0 +1,73 @@
/*****************************************************************************
# #
# 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 <string.h>
#include <event2/util.h>
#include "../tools.h"
#include "mime.h"
static const struct {
const char *ext;
const char *mime;
} _MIME_TYPES[] = {
{"html", "text/html"},
{"htm", "text/html"},
{"css", "text/css"},
{"js", "text/javascript"},
{"txt", "text/plain"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"png", "image/png"},
{"gif", "image/gif"},
{"ico", "image/x-icon"},
{"bmp", "image/bmp"},
{"svg", "image/svg+xml"},
{"swf", "application/x-shockwave-flash"},
{"cab", "application/x-shockwave-flash"},
{"jar", "application/java-archive"},
{"json", "application/json"},
};
const char *guess_mime_type(const char *path) {
char *dot;
char *ext;
dot = strchr(path, '.');
if (dot == NULL || strchr(dot, '/') != NULL) {
goto misc;
}
ext = dot + 1;
for (unsigned index = 0; index < ARRAY_LEN(_MIME_TYPES); ++index) {
if (!evutil_ascii_strcasecmp(ext, _MIME_TYPES[index].ext)) {
return _MIME_TYPES[index].mime;
}
}
misc:
return "application/misc";
}

26
src/http/mime.h Normal file
View File

@@ -0,0 +1,26 @@
/*****************************************************************************
# #
# 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/>. #
# #
*****************************************************************************/
#pragma once
const char *guess_mime_type(const char *str);

183
src/http/path.c Normal file
View File

@@ -0,0 +1,183 @@
/*****************************************************************************
# #
# 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "../tools.h"
#include "path.h"
char *simplify_request_path(const char *str) {
// Based on Lighttpd sources:
// - https://github.com/lighttpd/lighttpd1.4/blob/b31e7840d5403bc640579135b7004793b9ccd6c0/src/buffer.c#L840
// - https://github.com/lighttpd/lighttpd1.4/blob/77c01f981725512653c01cde5ca74c11633dfec4/src/t/test_buffer.c
char ch; // Current character
char pre1; // The one before
char pre2; // The one before that
char *simplified;
char *start;
char *out;
char *slash;
A_CALLOC(simplified, strlen(str) + 1);
if (str[0] == '\0') {
simplified[0] = '\0';
return simplified;
}
start = simplified;
out = simplified;
slash = simplified;
// Skip leading spaces
for (; *str == ' '; ++str);
if (*str == '.') {
if (str[1] == '/' || str[1] == '\0') {
++str;
} else if (str[1] == '.' && (str[2] == '/' || str[2] == '\0')) {
str += 2;
}
}
pre1 = '\0';
ch = *(str++);
while (ch != '\0') {
pre2 = pre1;
pre1 = ch;
// Possibly: out == str - need to read first
ch = *str;
*out = pre1;
out++;
str++;
// (out <= str) still true; also now (slash < out)
if (ch == '/' || ch == '\0') {
size_t toklen = out - slash;
if (toklen == 3 && pre2 == '.' && pre1 == '.' && *slash == '/') {
// "/../" or ("/.." at end of string)
out = slash;
// If there is something before "/..", there is at least one
// component, which needs to be removed
if (out > start) {
--out;
for (; out > start && *out != '/'; --out);
}
// Don't kill trailing '/' at end of path
if (ch == '\0') {
++out;
}
// slash < out before, so out_new <= slash + 1 <= out_before <= str
} else if (toklen == 1 || (pre2 == '/' && pre1 == '.')) {
// "//" or "/./" or (("/" or "/.") at end of string)
out = slash;
// Don't kill trailing '/' at end of path
if (ch == '\0') {
++out;
}
// Slash < out before, so out_new <= slash + 1 <= out_before <= str
}
slash = out;
}
}
*out = '\0';
return simplified;
}
#if 0
int test_simplify_request_path(const char *sample, const char *expected) {
char *result = simplify_request_path(sample);
int retval = -!!strcmp(result, expected);
printf("Testing '%s' -> '%s' ... ", sample, expected);
if (retval == 0) {
printf("ok\n");
} else {
printf("FAILED; got '%s'\n", result);
}
free(result);
return retval;
}
int main(void) {
int retval = 0;
# define TEST_SIMPLIFY_REQUEST_PATH(_sample, _expected) { \
retval += test_simplify_request_path(_sample, _expected); \
}
TEST_SIMPLIFY_REQUEST_PATH("", "");
TEST_SIMPLIFY_REQUEST_PATH(" ", "");
TEST_SIMPLIFY_REQUEST_PATH("/", "/");
TEST_SIMPLIFY_REQUEST_PATH("//", "/");
TEST_SIMPLIFY_REQUEST_PATH("abc", "abc");
TEST_SIMPLIFY_REQUEST_PATH("abc//", "abc/");
TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz", "abc/xyz");
TEST_SIMPLIFY_REQUEST_PATH("abc/.//xyz", "abc/xyz");
TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz", "/xyz");
TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz", "/abc/xyz");
TEST_SIMPLIFY_REQUEST_PATH("/abc//./xyz", "/abc/xyz");
TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz", "/xyz");
TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz/.", "/xyz/");
TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz/.", "/xyz/");
TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz/..", "abc/");
TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz/..", "/abc/");
TEST_SIMPLIFY_REQUEST_PATH(".", "");
TEST_SIMPLIFY_REQUEST_PATH("..", "");
TEST_SIMPLIFY_REQUEST_PATH("...", "...");
TEST_SIMPLIFY_REQUEST_PATH("....", "....");
TEST_SIMPLIFY_REQUEST_PATH(".../", ".../");
TEST_SIMPLIFY_REQUEST_PATH("./xyz/..", "/");
TEST_SIMPLIFY_REQUEST_PATH(".//xyz/..", "/");
TEST_SIMPLIFY_REQUEST_PATH("/./xyz/..", "/");
TEST_SIMPLIFY_REQUEST_PATH(".././xyz/..", "/");
TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/");
TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/");
TEST_SIMPLIFY_REQUEST_PATH("../../../etc/passwd", "/etc/passwd");
TEST_SIMPLIFY_REQUEST_PATH("/../../../etc/passwd", "/etc/passwd");
TEST_SIMPLIFY_REQUEST_PATH(" ../../../etc/passwd", "/etc/passwd");
TEST_SIMPLIFY_REQUEST_PATH(" /../../../etc/passwd", "/etc/passwd");
TEST_SIMPLIFY_REQUEST_PATH(" /foo/bar/../../../etc/passwd", "/etc/passwd");
# undef TEST_SIMPLIFY_REQUEST_PATH
if (retval < 0) {
printf("===== TEST FAILED =====\n");
}
return retval;
}
#endif

26
src/http/path.h Normal file
View File

@@ -0,0 +1,26 @@
/*****************************************************************************
# #
# 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/>. #
# #
*****************************************************************************/
#pragma once
char *simplify_request_path(const char *str);

View File

@@ -22,12 +22,14 @@
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
@@ -45,11 +47,15 @@
# error Required libevent-pthreads support
#endif
#include "tools.h"
#include "logging.h"
#include "encoder.h"
#include "stream.h"
#include "http.h"
#include "../tools.h"
#include "../logging.h"
#include "../encoder.h"
#include "../stream.h"
#include "base64.h"
#include "mime.h"
#include "static.h"
#include "server.h"
#include "data/index_html.h"
#include "data/blank_jpeg.h"
@@ -57,8 +63,10 @@
static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
static char *_http_get_param_uri(struct evkeyvalq *params, const char *key);
static int _http_preprocess_request(struct evhttp_request *request, struct http_server_t *server);
static void _http_callback_root(struct evhttp_request *request, void *arg);
static void _http_callback_root(struct evhttp_request *request, void *v_server);
static void _http_callback_static(struct evhttp_request *request, void *v_server);
static void _http_callback_state(struct evhttp_request *request, void *v_server);
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server);
@@ -88,6 +96,10 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
A_CALLOC(server, 1);
server->host = "127.0.0.1";
server->port = 8080;
server->unix_path = "";
server->user = "";
server->passwd = "";
server->static_path = "";
server->timeout = 10;
server->run = run;
@@ -97,12 +109,6 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
assert((run->base = event_base_new()));
assert((run->http = evhttp_new(run->base)));
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD);
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, NULL));
assert(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void *)server));
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)server));
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
return server;
}
@@ -127,6 +133,10 @@ void http_server_destroy(struct http_server_t *server) {
client = next;
}
if (server->run->auth_token) {
free(server->run->auth_token);
}
free(server->run->exposed->picture.data);
free(server->run->exposed);
free(server->run);
@@ -134,22 +144,56 @@ void http_server_destroy(struct http_server_t *server) {
}
int http_server_listen(struct http_server_t *server) {
struct timeval refresh_interval;
refresh_interval.tv_sec = 0;
if (server->run->stream->dev->desired_fps > 0) {
refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->desired_fps * 2);
} else {
refresh_interval.tv_usec = 16000; // ~60fps
{
if (server->static_path[0] != '\0') {
evhttp_set_gencb(server->run->http, _http_callback_static, (void *)server);
} else {
assert(!evhttp_set_cb(server->run->http, "/", _http_callback_root, (void *)server));
}
assert(!evhttp_set_cb(server->run->http, "/state", _http_callback_state, (void *)server));
assert(!evhttp_set_cb(server->run->http, "/snapshot", _http_callback_snapshot, (void *)server));
assert(!evhttp_set_cb(server->run->http, "/stream", _http_callback_stream, (void *)server));
}
{
struct timeval refresh_interval;
refresh_interval.tv_sec = 0;
if (server->run->stream->dev->desired_fps > 0) {
refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->desired_fps * 2);
} else {
refresh_interval.tv_usec = 16000; // ~60fps
}
assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
assert(!event_add(server->run->refresh, &refresh_interval));
}
assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
assert(!event_add(server->run->refresh, &refresh_interval));
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
if (server->slowdown) {
stream_switch_slowdown(server->run->stream, true);
}
evhttp_set_timeout(server->run->http, server->timeout);
if (server->unix_path) {
if (server->user[0] != '\0') {
char *raw_token;
char *encoded_token;
A_CALLOC(raw_token, strlen(server->user) + strlen(server->passwd) + 2);
sprintf(raw_token, "%s:%s", server->user, server->passwd);
encoded_token = base64_encode((unsigned char *)raw_token);
free(raw_token);
A_CALLOC(server->run->auth_token, strlen(encoded_token) + 16);
sprintf(server->run->auth_token, "Basic %s", encoded_token);
free(encoded_token);
LOG_INFO("Using HTTP basic auth");
}
if (server->unix_path[0] != '\0') {
struct sockaddr_un unix_addr;
int unix_fd_flags;
@@ -216,7 +260,12 @@ static bool _http_get_param_true(struct evkeyvalq *params, const char *key) {
const char *value_str;
if ((value_str = evhttp_find_header(params, key)) != NULL) {
if (!strcasecmp(value_str, "true") || !strcasecmp(value_str, "yes") || value_str[0] == '1') {
if (
value_str[0] == '1'
|| value_str[0] == 'y'
|| !evutil_ascii_strcasecmp(value_str, "true")
|| !evutil_ascii_strcasecmp(value_str, "yes")
) {
return true;
}
}
@@ -235,17 +284,36 @@ static char *_http_get_param_uri(struct evkeyvalq *params, const char *key) {
#define ADD_HEADER(_key, _value) \
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
#define PROCESS_HEAD_REQUEST { \
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \
evhttp_send_reply(request, HTTP_OK, "OK", NULL); \
static int _http_preprocess_request(struct evhttp_request *request, struct http_server_t *server) {
if (server->run->auth_token) {
const char *token = evhttp_find_header(evhttp_request_get_input_headers(request), "Authorization");
if (token == NULL || strcmp(token, server->run->auth_token) != 0) {
ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\"");
evhttp_send_reply(request, 401, "Unauthorized", NULL);
return -1;
}
}
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \
evhttp_send_reply(request, HTTP_OK, "OK", NULL); \
return -1;
}
return 0;
}
#define PREPROCESS_REQUEST { \
if (_http_preprocess_request(request, server) < 0) { \
return; \
} \
}
static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg) {
static void _http_callback_root(struct evhttp_request *request, void *v_server) {
struct http_server_t *server = (struct http_server_t *)v_server;
struct evbuffer *buf;
PROCESS_HEAD_REQUEST;
PREPROCESS_REQUEST;
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
@@ -254,20 +322,93 @@ static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg
evbuffer_free(buf);
}
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
struct http_server_t *server = (struct http_server_t *)v_server;
struct evbuffer *buf = NULL;
struct evhttp_uri *uri = NULL;
char *uri_path;
char *decoded_path = NULL;
char *static_path = NULL;
int fd = -1;
struct stat st;
PREPROCESS_REQUEST;
if ((uri = evhttp_uri_parse(evhttp_request_get_uri(request))) == NULL) {
goto bad_request;
}
if ((uri_path = (char *)evhttp_uri_get_path(uri)) == NULL) {
uri_path = "/";
}
if ((decoded_path = evhttp_uridecode(uri_path, 0, NULL)) == NULL) {
goto bad_request;
}
assert((buf = evbuffer_new()));
if ((static_path = find_static_file_path(server->static_path, decoded_path)) == NULL) {
goto not_found;
}
if ((fd = open(static_path, O_RDONLY)) < 0) {
LOG_PERROR("HTTP: Can't open found static file %s", static_path);
goto not_found;
}
if (fstat(fd, &st) < 0) {
LOG_PERROR("HTTP: Can't stat() found static file %s", static_path);
goto not_found;
}
if (evbuffer_add_file(buf, fd, 0, st.st_size) < 0) {
LOG_ERROR("HTTP: Can't serve static file %s", static_path);
goto not_found;
}
ADD_HEADER("Content-Type", guess_mime_type(static_path));
evhttp_send_reply(request, HTTP_OK, "OK", buf);
goto cleanup;
bad_request:
evhttp_send_error(request, HTTP_BADREQUEST, NULL);
goto cleanup;
not_found:
evhttp_send_error(request, HTTP_NOTFOUND, NULL);
goto cleanup;
cleanup:
if (fd >= 0) {
close(fd);
}
if (static_path) {
free(static_path);
}
if (buf) {
evbuffer_free(buf);
}
if (decoded_path) {
free(decoded_path);
}
if (uri) {
evhttp_uri_free(uri);
}
}
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
struct http_server_t *server = (struct http_server_t *)v_server;
struct evbuffer *buf;
enum encoder_type_t encoder_run_type;
unsigned encoder_run_quality;
PROCESS_HEAD_REQUEST;
PREPROCESS_REQUEST;
# define ENCODER(_next) server->run->stream->encoder->_next
A_PTHREAD_M_LOCK(&ENCODER(run->mutex));
A_MUTEX_LOCK(&ENCODER(run->mutex));
encoder_run_type = ENCODER(run->type);
encoder_run_quality = ENCODER(run->quality);
A_PTHREAD_M_UNLOCK(&ENCODER(run->mutex));
A_MUTEX_UNLOCK(&ENCODER(run->mutex));
assert((buf = evbuffer_new()));
@@ -314,23 +455,27 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
struct evbuffer *buf;
char header_buf[64];
PROCESS_HEAD_REQUEST;
PREPROCESS_REQUEST;
# define EXPOSED(_next) server->run->exposed->_next
assert((buf = evbuffer_new()));
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture.data), EXPOSED(picture.size)));
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture.data), EXPOSED(picture.used)));
ADD_HEADER("Access-Control-Allow-Origin:", "*");
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
ADD_HEADER("Pragma", "no-cache");
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
# define ADD_TIME_HEADER(_key, _value) \
{ sprintf(header_buf, "%.06Lf", _value); ADD_HEADER(_key, header_buf); }
# define ADD_TIME_HEADER(_key, _value) { \
sprintf(header_buf, "%.06Lf", _value); \
ADD_HEADER(_key, header_buf); \
}
# define ADD_UNSIGNED_HEADER(_key, _value) \
{ sprintf(header_buf, "%u", _value); ADD_HEADER(_key, header_buf); }
# define ADD_UNSIGNED_HEADER(_key, _value) { \
sprintf(header_buf, "%u", _value); \
ADD_HEADER(_key, header_buf); \
}
ADD_TIME_HEADER("X-Timestamp", get_now_real());
@@ -375,7 +520,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
unsigned short client_port;
uuid_t uuid;
PROCESS_HEAD_REQUEST;
PREPROCESS_REQUEST;
conn = evhttp_request_get_connection(request);
if (conn != NULL) {
@@ -406,14 +551,13 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
}
server->run->stream_clients_count += 1;
if (server->slowdown && server->run->stream_clients_count == 1) {
stream_switch_slowdown(server->run->stream, false);
}
evhttp_connection_get_peer(conn, &client_addr, &client_port);
LOG_INFO("HTTP: Registered the new stream client: [%s]:%u; id=%s; advance_headers=%s; dual_final_frames=%s; clients now: %u",
client_addr,
client_port,
client->id,
bool_to_string(client->advance_headers),
bool_to_string(client->dual_final_frames),
server->run->stream_clients_count);
LOG_INFO("HTTP: Registered the new stream 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);
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
@@ -423,7 +567,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
}
}
#undef PROCESS_HEAD_REQUEST
#undef PREPROCESS_REQUEST
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
# define BOUNDARY "boundarydonotcross"
@@ -463,8 +607,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
// по тем же причинам, по которым у нас нет Content-Length.
# define ADD_ADVANCE_HEADERS \
{ assert(evbuffer_add_printf(buf, \
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real())); }
assert(evbuffer_add_printf(buf, \
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real()))
if (client->need_initial) {
assert(evbuffer_add_printf(buf,
@@ -497,7 +641,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
"Content-Length: %zu" RN
"X-Timestamp: %.06Lf" RN
"%s",
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data)),
EXPOSED(picture.used) * sizeof(*EXPOSED(picture.data)),
get_now_real(), (client->extra_headers ? "" : RN)
));
if (client->extra_headers) {
@@ -533,7 +677,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
assert(!evbuffer_add(buf,
(void *)EXPOSED(picture.data),
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data))
EXPOSED(picture.used) * sizeof(*EXPOSED(picture.data))
));
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
@@ -559,20 +703,25 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
char *client_addr = "???";
unsigned short client_port = 0;
client->server->run->stream_clients_count -= 1;
# define RUN(_next) client->server->run->_next
RUN(stream_clients_count) -= 1;
if (client->server->slowdown && RUN(stream_clients_count) <= 0) {
stream_switch_slowdown(RUN(stream), true);
}
conn = evhttp_request_get_connection(client->request);
if (conn != NULL) {
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, client->server->run->stream_clients_count);
client_addr, client_port, RUN(stream_clients_count));
if (conn != NULL) {
evhttp_connection_free(conn);
}
if (client->prev == NULL) {
client->server->run->stream_clients = client->next;
RUN(stream_clients) = client->next;
} else {
client->prev->next = client->next;
}
@@ -581,6 +730,8 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
}
free(client->key);
free(client);
# undef RUN
}
static void _http_queue_send_stream(struct http_server_t *server, bool stream_updated, bool picture_updated) {
@@ -637,13 +788,15 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
bool stream_updated = false;
bool picture_updated = false;
# define UNLOCK_STREAM \
{ server->run->stream->updated = false; A_PTHREAD_M_UNLOCK(&server->run->stream->mutex); }
# define UNLOCK_STREAM { \
atomic_store(&server->run->stream->updated, false); \
A_MUTEX_UNLOCK(&server->run->stream->mutex); \
}
if (server->run->stream->updated) {
if (atomic_load(&server->run->stream->updated)) {
LOG_DEBUG("Refreshing HTTP exposed ...");
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
if (server->run->stream->picture.size > 0) { // If online
A_MUTEX_LOCK(&server->run->stream->mutex);
if (server->run->stream->picture.used > 0) { // If online
picture_updated = _expose_new_picture(server);
UNLOCK_STREAM;
} else {
@@ -666,19 +819,19 @@ static bool _expose_new_picture(struct http_server_t *server) {
# define STREAM(_next) server->run->stream->_next
# define EXPOSED(_next) server->run->exposed->_next
assert(STREAM(picture.size) > 0);
assert(STREAM(picture.used) > 0);
EXPOSED(captured_fps) = STREAM(captured_fps);
EXPOSED(expose_begin_time) = get_now_monotonic();
# define MEM_STREAM_TO_EXPOSED \
EXPOSED(picture.data), STREAM(picture.data), \
STREAM(picture.size) * sizeof(*STREAM(picture.data))
STREAM(picture.used) * sizeof(*STREAM(picture.data))
if (server->drop_same_frames) {
if (
EXPOSED(online)
&& EXPOSED(dropped) < server->drop_same_frames
&& EXPOSED(picture.size) == STREAM(picture.size)
&& EXPOSED(picture.used) == STREAM(picture.used)
&& !memcmp(MEM_STREAM_TO_EXPOSED)
) {
EXPOSED(expose_cmp_time) = get_now_monotonic();
@@ -703,7 +856,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
# undef MEM_STREAM_TO_EXPOSED
EXPOSED(picture.size) = STREAM(picture.size);
EXPOSED(picture.used) = STREAM(picture.used);
EXPOSED(picture.grab_time) = STREAM(picture.grab_time);
EXPOSED(picture.encode_begin_time) = STREAM(picture.encode_begin_time);
@@ -731,7 +884,7 @@ static bool _expose_blank_picture(struct http_server_t *server) {
EXPOSED(expose_begin_time) = get_now_monotonic();
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
if (EXPOSED(online) || EXPOSED(picture.size) == 0) {
if (EXPOSED(online) || EXPOSED(picture.used) == 0) {
if (EXPOSED(picture.allocated) < BLANK_JPEG_LEN) {
A_REALLOC(EXPOSED(picture.data), BLANK_JPEG_LEN);
EXPOSED(picture.allocated) = BLANK_JPEG_LEN;
@@ -739,7 +892,7 @@ static bool _expose_blank_picture(struct http_server_t *server) {
memcpy(EXPOSED(picture.data), BLANK_JPEG_DATA, BLANK_JPEG_LEN * sizeof(*EXPOSED(picture.data)));
EXPOSED(picture.size) = BLANK_JPEG_LEN;
EXPOSED(picture.used) = BLANK_JPEG_LEN;
EXPOSED(picture.grab_time) = 0;
EXPOSED(picture.encode_begin_time) = 0;

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#pragma once
#include <stdbool.h>
#include <sys/stat.h>
@@ -28,8 +30,8 @@
#include <event2/http.h>
#include <event2/util.h>
#include "tools.h"
#include "stream.h"
#include "../tools.h"
#include "../stream.h"
struct stream_client_t {
@@ -70,6 +72,7 @@ struct http_server_runtime_t {
struct event_base *base;
struct evhttp *http;
evutil_socket_t unix_fd;
char *auth_token;
struct event *refresh;
struct stream_t *stream;
struct exposed_t *exposed;
@@ -84,7 +87,11 @@ struct http_server_t {
char *unix_path;
bool unix_rm;
mode_t unix_mode;
char *user;
char *passwd;
char *static_path;
unsigned drop_same_frames;
bool slowdown;
unsigned fake_width;
unsigned fake_height;
unsigned timeout;

88
src/http/static.c Normal file
View File

@@ -0,0 +1,88 @@
/*****************************************************************************
# #
# 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../tools.h"
#include "../logging.h"
#include "path.h"
#include "static.h"
char *find_static_file_path(const char *root_path, const char *request_path) {
char *simplified_path;
char *path = NULL;
struct stat st;
simplified_path = simplify_request_path(request_path);
if (simplified_path[0] == '\0') {
goto error;
}
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 32);
sprintf(path, "%s/%s", root_path, simplified_path);
# define LOAD_STAT { \
if (lstat(path, &st) < 0) { \
/* LOG_PERROR("Can't stat() file %s", path); */ \
goto error; \
} \
}
LOAD_STAT;
if (S_ISDIR(st.st_mode)) {
strcat(path, "/index.html");
LOAD_STAT;
}
# undef LOAD_STAT
if (!S_ISREG(st.st_mode)) {
// LOG_ERROR("Not a regulary file: %s", path);
goto error;
}
if (access(path, R_OK) < 0) {
// LOG_PERROR("Can't access() R_OK file %s", path);
goto error;
}
goto ok;
error:
if (path) {
free(path);
}
path = NULL;
ok:
free(simplified_path);
return path;
}

26
src/http/static.h Normal file
View File

@@ -0,0 +1,26 @@
/*****************************************************************************
# #
# 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/>. #
# #
*****************************************************************************/
#pragma once
char *find_static_file_path(const char *root_path, const char *request_path);

View File

@@ -25,16 +25,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include "tools.h"
@@ -72,7 +68,7 @@ pthread_mutex_t log_mutex;
}
#define LOG_PRINTF_NOLOCK(_label, _msg, ...) { \
printf("-- " _label " [%.03Lf tid=%ld] -- " _msg "\n", get_now_monotonic(), syscall(SYS_gettid), ##__VA_ARGS__); \
printf("-- " _label " [%.03Lf tid=%d] -- " _msg "\n", get_now_monotonic(), get_thread_id(), ##__VA_ARGS__); \
fflush(stdout); \
}
@@ -84,9 +80,9 @@ pthread_mutex_t log_mutex;
#define LOG_PERROR(_msg, ...) { \
char _buf[1024] = ""; \
char *_ptr = strerror_r(errno, _buf, 1024); \
char *_ptr = errno_to_string(_buf, 1024); \
LOGGING_LOCK; \
printf("-- ERROR [%.03Lf tid=%ld] -- " _msg ": %s\n", get_now_monotonic(), syscall(SYS_gettid), ##__VA_ARGS__, _ptr); \
printf("-- ERROR [%.03Lf tid=%d] -- " _msg ": %s\n", get_now_monotonic(), get_thread_id(), ##__VA_ARGS__, _ptr); \
fflush(stdout); \
LOGGING_UNLOCK; \
}
@@ -124,3 +120,13 @@ pthread_mutex_t log_mutex;
LOGGING_UNLOCK; \
} \
}
INLINE char *errno_to_string(char *buf, size_t size) {
#if defined(__GLIBC__) && defined(_GNU_SOURCE)
return strerror_r(errno, buf, size);
#else
strerror_r(errno, buf, size);
return buf;
#endif
}

View File

@@ -38,11 +38,11 @@
#include "device.h"
#include "encoder.h"
#include "stream.h"
#include "http.h"
#include "http/server.h"
static const char _short_opts[] = "d:i:x:y:m:a:f:z:tb:w:q:c:s:p:u:ro:e:h";
static const struct option _long_opts[] = {
static const char _SHORT_OPTS[] = "d:i:x:y:m:a:f:z:ntb:w:q:c:s:p:u:ro:e:lhv";
static const struct option _LONG_OPTS[] = {
{"device", required_argument, NULL, 'd'},
{"input", required_argument, NULL, 'i'},
{"width", required_argument, NULL, 'x'},
@@ -51,14 +51,14 @@ static const struct option _long_opts[] = {
{"tv-standard", required_argument, NULL, 'a'},
{"desired-fps", required_argument, NULL, 'f'},
{"min-frame-size", required_argument, NULL, 'z'},
{"persistent", no_argument, NULL, 'n'},
{"dv-timings", no_argument, NULL, 't'},
{"buffers", required_argument, NULL, 'b'},
{"workers", required_argument, NULL, 'w'},
{"quality", required_argument, NULL, 'q'},
{"encoder", required_argument, NULL, 'c'},
{"device-timeout", required_argument, NULL, 1000},
{"device-persistent", no_argument, NULL, 1001},
{"device-error-delay", required_argument, NULL, 1002},
{"device-error-delay", required_argument, NULL, 1001},
{"brightness", required_argument, NULL, 2000},
{"brightness-auto", no_argument, NULL, 2001},
@@ -79,17 +79,21 @@ static const struct option _long_opts[] = {
{"unix", required_argument, NULL, 'u'},
{"unix-rm", no_argument, NULL, 'r'},
{"unix-mode", required_argument, NULL, 'o'},
{"user", required_argument, NULL, 3000},
{"passwd", required_argument, NULL, 3001},
{"static", required_argument, NULL, 3002},
{"drop-same-frames", required_argument, NULL, 'e'},
{"fake-width", required_argument, NULL, 3001},
{"fake-height", required_argument, NULL, 3002},
{"server-timeout", required_argument, NULL, 3003},
{"slowdown", no_argument, NULL, 'l'},
{"fake-width", required_argument, NULL, 3003},
{"fake-height", required_argument, NULL, 3004},
{"server-timeout", required_argument, NULL, 3005},
{"perf", no_argument, NULL, 5000},
{"verbose", no_argument, NULL, 5001},
{"debug", no_argument, NULL, 5002},
{"log-level", required_argument, NULL, 5010},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 6000},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0},
};
@@ -105,76 +109,83 @@ static void _version(bool nl) {
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
printf("\nuStreamer - Lightweight and fast MJPG-HTTP streamer\n");
printf("===================================================\n\n");
printf("═══════════════════════════════════════════════════\n\n");
printf("Version: ");
_version(false);
printf("; license: GPLv3\n");
printf("Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com>\n\n");
printf("Capturing options:\n");
printf("------------------\n");
printf(" -d|--device </dev/path> -- Path to V4L2 device. Default: %s.\n\n", dev->path);
printf(" -i|--input <N> -- Input channel. Default: %u.\n\n", dev->input);
printf(" -x|--width <N> -- Initial image width. Default: %u.\n\n", dev->width);
printf(" -y|--height <N> -- Initial image height. Default: %u.\n\n", dev->height);
printf(" -m|--format <fmt> -- Image format.\n");
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(" -f|--desired-fps <N> -- Desired FPS. Default: maximum as possible.\n\n");
printf(" -z|--min-frame-size <N> -- Drop frames smaller then this limit.\n");
printf(" Useful if the device produces small-sized garbage frames.\n\n");
printf(" -t|--dv-timings -- Enable DV timings queriyng and events processing.\n");
printf(" Supports automatic resolution changing. Default: disabled.\n\n");
printf(" -b|--buffers <N> -- The number of buffers to receive data from the device.\n");
printf(" Each buffer may processed using an intermediate thread.\n");
printf(" Default: %u (number of CPU cores + 1)\n\n", dev->n_buffers);
printf(" -w|--workers <N> -- The number of compressing threads. Default: %u (== --buffers).\n\n", dev->n_workers);
printf(" -q|--quality <N> -- Set quality of JPEG encoding from 1 to 100 (best). Default: %u.\n\n", encoder->quality);
printf(" -c|--encoder <type> -- Use specified encoder. It may affects to workers number.\n");
printf(" -- Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
printf(" --device-timeout <seconds> -- Timeout for device querying. Default: %u\n\n", dev->timeout);
printf(" --device-persistent -- Don't re-initialize device on timeout. Default: disabled.\n\n");
printf(" --device-error-delay <seconds> -- Delay before trying to connect to the device again\n");
printf(" after a timeout. Default: %u\n\n", dev->error_delay);
printf("══════════════════\n");
printf(" -d|--device </dev/path> ──────── Path to V4L2 device. Default: %s.\n\n", dev->path);
printf(" -i|--input <N> ───────────────── Input channel. Default: %u.\n\n", dev->input);
printf(" -x|--width <N> ───────────────── Initial image width. Default: %u.\n\n", dev->width);
printf(" -y|--height <N> ──────────────── Initial image height. Default: %u.\n\n", dev->height);
printf(" -m|--format <fmt> ────────────── Image format.\n");
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(" -f|--desired-fps <N> ─────────── Desired FPS. Default: maximum as 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(" -n|--persistent ──────────────── Don't re-initialize device on timeout. Default: disabled.\n\n");
printf(" -t|--dv-timings ──────────────── Enable DV timings queriyng and events processing.\n");
printf(" Supports automatic resolution changing. Default: disabled.\n\n");
printf(" -b|--buffers <N> ─────────────── The number of buffers to receive data from the device.\n");
printf(" Each buffer may processed using an intermediate thread.\n");
printf(" Default: %u (the number of CPU cores (but not more 4) + 1).\n\n", dev->n_buffers);
printf(" -w|--workers <N> ─────────────── The number of worker threads. Default: %u (== --buffers).\n\n", dev->n_workers);
printf(" -q|--quality <N> ─────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.\n\n", encoder->quality);
printf(" -c|--encoder <type> ──────────── Use specified encoder. It may affects to workers number.\n");
printf(" Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
printf(" --device-timeout <seconds> ───── Timeout for device querying. Default: %u\n\n", dev->timeout);
printf(" --device-error-delay <seconds> Delay before trying to connect to the device again\n");
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("══════════════════════\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("HTTP server options:\n");
printf("--------------------\n");
printf(" -s|--host <address> -- Listen on Hostname or IP. Default: %s\n\n", server->host);
printf(" -p|--port <N> -- Bind to this TCP port. Default: %u\n\n", server->port);
printf(" -u|--unix <path> -- Bind to UNIX domain socket. Default: disabled\n\n");
printf(" -r|--unix-rm -- Try to remove old UNIX socket file before binding. Default: disabled\n\n");
printf(" -o|--unix-mode <mode> -- Set UNIX socket file permissions (like 777). Default: disabled\n\n");
printf(" -e|--drop-same-frames <N> -- Don't send same frames to clients, but no more than specified number.\n");
printf("════════════════════\n");
printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s.\n\n", server->host);
printf(" -p|--port <N> ────────────── Bind to this TCP port. Default: %u.\n\n", server->port);
printf(" -u|--unix <path> ─────────── Bind to UNIX domain socket. Default: disabled.\n\n");
printf(" -r|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n\n");
printf(" -o|--unix-mode <mode> ────── Set UNIX socket file permissions (like 777). Default: disabled.\n\n");
printf(" --user <name> ────────────── HTTP basic auth user. Default: disabled.\n\n");
printf(" --passwd <str> ───────────── HTTP basic auth passwd. Default: empty.\n\n");
printf(" --static <path> ───────────── Path to dir with static files instead of embedded root index page.\n");
printf(" Symlinks are not supported for security reasons. Default: disabled.\n\n");
printf(" -e|--drop-same-frames <N> ── Don't send same frames to clients, but no more than specified number.\n");
printf(" It can significantly reduce the outgoing traffic, but will increase\n");
printf(" the CPU loading. Don't use this option with analog signal sources\n");
printf(" or webcams, it's useless. Default: disabled.\n\n");
printf(" --fake-width <N> -- Override image width for /state. Default: disabled\n\n");
printf(" --fake-height <N> -- Override image height for /state. Default: disabled.\n\n");
printf(" --server-timeout <seconds> -- Timeout for client connections. Default: %u\n\n", server->timeout);
printf(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients connected.\n");
printf(" Useful to reduce CPU cosumption. Default: disabled.\n\n");
printf(" --fake-width <N> ─────────── Override image width for /state. Default: disabled.\n\n");
printf(" --fake-height <N> ────────── Override image height for /state. Default: disabled.\n\n");
printf(" --server-timeout <seconds> ─ Timeout for client connections. Default: %u.\n\n", server->timeout);
printf("Misc options:\n");
printf("-------------\n");
printf(" --log-level <N> -- Verbosity level of messages from 0 (info) to 3 (debug).\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(" --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");
printf(" -h|--help -- Print this messages and exit.\n\n");
printf(" --perf ────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n");
printf(" --verbose ─────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n");
printf(" --debug ───────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");
printf(" -h|--help ─────── Print this text and exit.\n\n");
printf(" -v|--version ──── Print version and exit.\n\n");
}
static int _parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
@@ -229,7 +240,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
int ch;
log_level = LOG_LEVEL_INFO;
while ((ch = getopt_long(argc, argv, _short_opts, _long_opts, &index)) >= 0) {
while ((ch = getopt_long(argc, argv, _SHORT_OPTS, _LONG_OPTS, &index)) >= 0) {
switch (ch) {
case 'd': OPT_SET(dev->path, optarg);
case 'i': OPT_UNSIGNED(dev->input, "--input", 0, 128);
@@ -242,14 +253,14 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard");
case 'f': OPT_UNSIGNED(dev->desired_fps, "--desired-fps", 0, 30);
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192);
case 'n': OPT_SET(dev->persistent, true);
case 't': OPT_SET(dev->dv_timings, true);
case 'b': OPT_UNSIGNED(dev->n_buffers, "--buffers", 1, 32);
case 'w': OPT_UNSIGNED(dev->n_workers, "--workers", 1, 32);
case 'q': OPT_UNSIGNED(encoder->quality, "--quality", 1, 100);
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type");
case 1000: OPT_UNSIGNED(dev->timeout, "--device-timeout", 1, 60);
case 1001: OPT_SET(dev->persistent, true);
case 1002: OPT_UNSIGNED(dev->error_delay, "--device-error-delay", 1, 60);
case 1001: OPT_UNSIGNED(dev->error_delay, "--device-error-delay", 1, 60);
case 2000: OPT_CTL(brightness);
case 2001: OPT_CTL_AUTO(brightness);
@@ -270,17 +281,21 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
case 'u': OPT_SET(server->unix_path, optarg);
case 'r': OPT_SET(server->unix_rm, true);
case 'o': OPT_CHMOD(server->unix_mode, "--unix-mode");
case 3000: OPT_SET(server->user, optarg);
case 3001: OPT_SET(server->passwd, optarg);
case 3002: OPT_SET(server->static_path, optarg);
case 'e': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
case 3001: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
case 3002: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
case 3003: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
case 'l': OPT_SET(server->slowdown, true);
case 3003: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
case 3004: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
case 3005: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
case 5000: OPT_SET(log_level, LOG_LEVEL_PERF);
case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
case 5002: OPT_SET(log_level, LOG_LEVEL_DEBUG);
case 5010: OPT_UNSIGNED(log_level, "--log-level", 0, 3);
case 'h': _help(dev, encoder, server); return 1;
case 6000: _version(true); return 1;
case 'v': _version(true); return 1;
case 0: break;
default: _help(dev, encoder, server); return -1;
}
@@ -375,10 +390,10 @@ int main(int argc, char *argv[]) {
_ctx = &ctx;
if ((exit_code = http_server_listen(server)) == 0) {
A_PTHREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
A_PTHREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL);
A_PTHREAD_JOIN(stream_loop_tid);
A_PTHREAD_JOIN(server_loop_tid);
A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
A_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL);
A_THREAD_JOIN(server_loop_tid);
A_THREAD_JOIN(stream_loop_tid);
}
}

View File

@@ -20,6 +20,8 @@
*****************************************************************************/
#include <stdatomic.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
@@ -40,46 +42,48 @@
static long double _stream_get_fluency_delay(struct device_t *dev, struct workers_pool_t *pool);
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index);
static int _stream_init_loop(struct device_t *dev, struct workers_pool_t *pool);
static int _stream_init(struct device_t *dev, struct workers_pool_t *pool);
static int _stream_init_loop(struct stream_t *stream, struct workers_pool_t *pool);
static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool);
static void _stream_init_workers(struct device_t *dev, struct workers_pool_t *pool);
static void *_stream_worker_thread(void *v_ctx);
static void _stream_destroy_workers(struct device_t *dev, struct workers_pool_t *pool);
static int _stream_control(struct device_t *dev, bool enable);
static int _stream_grab_buffer(struct device_t *dev, struct v4l2_buffer *buf_info);
static int _stream_release_buffer(struct device_t *dev, struct v4l2_buffer *buf_info);
static int _stream_handle_event(struct device_t *dev);
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool);
static void *_stream_worker_thread(void *v_worker);
static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool_t *pool);
struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) {
struct process_t *proc;
struct stream_t *stream;
A_CALLOC(proc, 1);
atomic_init(&proc->stop, false);
atomic_init(&proc->slowdown, false);
A_CALLOC(stream, 1);
stream->dev = dev;
stream->encoder = encoder;
A_PTHREAD_M_INIT(&stream->mutex);
atomic_init(&stream->updated, false);
A_MUTEX_INIT(&stream->mutex);
stream->proc = proc;
return stream;
}
void stream_destroy(struct stream_t *stream) {
A_PTHREAD_M_DESTROY(&stream->mutex);
A_MUTEX_DESTROY(&stream->mutex);
free(stream->proc);
free(stream);
}
void stream_loop(struct stream_t *stream) {
struct workers_pool_t pool;
bool workers_stop;
MEMSET_ZERO(pool);
atomic_init(&pool.workers_stop, false);
pool.encoder = stream->encoder;
pool.workers_stop = &workers_stop;
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
while (_stream_init_loop(stream->dev, &pool) == 0) {
while (_stream_init_loop(stream, &pool) == 0) {
struct worker_t *oldest_worker = NULL;
struct worker_t *last_worker = NULL;
long double grab_after = 0;
@@ -89,35 +93,35 @@ void stream_loop(struct stream_t *stream) {
bool persistent_timeout_reported = false;
LOG_DEBUG("Allocation memory for stream picture ...");
A_CALLOC(stream->picture.data, stream->dev->run->max_picture_size);
A_CALLOC(stream->picture.data, stream->dev->run->max_raw_image_size);
LOG_INFO("Capturing ...");
while (!stream->dev->stop) {
while (!atomic_load(&stream->proc->stop)) {
int free_worker_number = -1;
SEP_DEBUG('-');
LOG_DEBUG("Waiting for workers ...");
A_PTHREAD_M_LOCK(&pool.free_workers_mutex);
A_PTHREAD_C_WAIT_TRUE(pool.free_workers, &pool.free_workers_cond, &pool.free_workers_mutex);
A_PTHREAD_M_UNLOCK(&pool.free_workers_mutex);
A_MUTEX_LOCK(&pool.free_workers_mutex);
A_COND_WAIT_TRUE(pool.free_workers, &pool.free_workers_cond, &pool.free_workers_mutex);
A_MUTEX_UNLOCK(&pool.free_workers_mutex);
if (oldest_worker && !oldest_worker->has_job && oldest_worker->ctx.buf_index >= 0) {
if (oldest_worker && !atomic_load(&oldest_worker->has_job) && oldest_worker->buf_index >= 0) {
if (oldest_worker->job_failed) {
break;
}
_stream_expose_picture(stream, oldest_worker->ctx.buf_index);
_stream_expose_picture(stream, oldest_worker->buf_index);
free_worker_number = oldest_worker->ctx.number;
free_worker_number = oldest_worker->number;
oldest_worker = oldest_worker->order_next;
LOG_PERF("##### Raw frame accepted; worker = %u", free_worker_number);
} else {
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
if (
!pool.workers[number].has_job && (
!atomic_load(&pool.workers[number].has_job) && (
free_worker_number == -1
|| pool.workers[free_worker_number].job_start_time < pool.workers[number].job_start_time
)
@@ -128,15 +132,19 @@ void stream_loop(struct stream_t *stream) {
}
assert(free_worker_number >= 0);
assert(!pool.workers[free_worker_number].has_job);
assert(!atomic_load(&pool.workers[free_worker_number].has_job));
LOG_PERF("----- Raw frame dropped; worker = %u", free_worker_number);
}
if (stream->dev->stop) {
if (atomic_load(&stream->proc->stop)) {
break;
}
if (atomic_load(&stream->proc->slowdown)) {
usleep(1000000);
}
# define INIT_FD_SET(_set) \
fd_set _set; FD_ZERO(&_set); FD_SET(stream->dev->run->fd, &_set);
@@ -178,22 +186,23 @@ void stream_loop(struct stream_t *stream) {
if (FD_ISSET(stream->dev->run->fd, &read_fds)) {
LOG_DEBUG("Frame is ready");
struct v4l2_buffer buf_info;
int buf_index;
long double now = get_now_monotonic();
long long now_second = floor_ms(now);
if (_stream_grab_buffer(stream->dev, &buf_info) < 0) {
if ((buf_index = device_grab_buffer(stream->dev)) < 0) {
break;
}
stream->dev->run->pictures[buf_info.index].grab_time = now;
stream->dev->run->pictures[buf_index].grab_time = now;
// Workaround for broken, corrupted frames:
// Under low light conditions corrupted frames may get captured.
// The good thing is such frames are quite small compared to the regular pictures.
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
// corrupted frames are smaller.
if (buf_info.bytesused < stream->dev->min_frame_size) {
LOG_DEBUG("Dropping too small frame sized %u bytes, assuming it as broken", buf_info.bytesused);
if (stream->dev->run->hw_buffers[buf_index].used < stream->dev->min_frame_size) {
LOG_DEBUG("Dropping too small frame sized %zu bytes, assuming it as broken",
stream->dev->run->hw_buffers[buf_index].used);
goto pass_frame;
}
@@ -221,8 +230,7 @@ void stream_loop(struct stream_t *stream) {
# define FREE_WORKER(_next) pool.workers[free_worker_number]._next
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_info.index);
FREE_WORKER(ctx.buf_info) = buf_info;
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_index);
if (!oldest_worker) {
oldest_worker = &pool.workers[free_worker_number];
@@ -240,23 +248,23 @@ void stream_loop(struct stream_t *stream) {
}
last_worker->order_next = NULL;
A_PTHREAD_M_LOCK(&FREE_WORKER(has_job_mutex));
FREE_WORKER(ctx.buf_index) = buf_info.index;
FREE_WORKER(has_job) = true;
A_PTHREAD_M_UNLOCK(&FREE_WORKER(has_job_mutex));
A_PTHREAD_C_SIGNAL(&FREE_WORKER(has_job_cond));
A_MUTEX_LOCK(&FREE_WORKER(has_job_mutex));
FREE_WORKER(buf_index) = buf_index;
atomic_store(&FREE_WORKER(has_job), true);
A_MUTEX_UNLOCK(&FREE_WORKER(has_job_mutex));
A_COND_SIGNAL(&FREE_WORKER(has_job_cond));
# undef FREE_WORKER
A_PTHREAD_M_LOCK(&pool.free_workers_mutex);
A_MUTEX_LOCK(&pool.free_workers_mutex);
pool.free_workers -= 1;
A_PTHREAD_M_UNLOCK(&pool.free_workers_mutex);
A_MUTEX_UNLOCK(&pool.free_workers_mutex);
goto next_handlers; // Поток сам освободит буфер
pass_frame:
if (_stream_release_buffer(stream->dev, &buf_info) < 0) {
if (device_release_buffer(stream->dev, buf_index) < 0) {
break;
}
}
@@ -270,40 +278,44 @@ void stream_loop(struct stream_t *stream) {
if (FD_ISSET(stream->dev->run->fd, &error_fds)) {
LOG_INFO("Got V4L2 event");
if (_stream_handle_event(stream->dev) < 0) {
if (device_consume_event(stream->dev) < 0) {
break;
}
}
}
}
A_PTHREAD_M_LOCK(&stream->mutex);
stream->picture.size = 0; // On stream offline
A_MUTEX_LOCK(&stream->mutex);
stream->picture.used = 0; // On stream offline
free(stream->picture.data);
stream->width = 0;
stream->height = 0;
stream->updated = true;
A_PTHREAD_M_UNLOCK(&stream->mutex);
atomic_store(&stream->updated, true);
A_MUTEX_UNLOCK(&stream->mutex);
}
_stream_destroy_workers(stream->dev, &pool);
_stream_control(stream->dev, false);
_stream_destroy_workers(stream, &pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
}
void stream_loop_break(struct stream_t *stream) {
stream->dev->stop = 1;
atomic_store(&stream->proc->stop, true);
}
void stream_switch_slowdown(struct stream_t *stream, bool slowdown) {
atomic_store(&stream->proc->slowdown, slowdown);
}
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index) {
# define PICTURE(_next) stream->dev->run->pictures[buf_index]._next
A_PTHREAD_M_LOCK(&stream->mutex);
A_MUTEX_LOCK(&stream->mutex);
stream->picture.size = PICTURE(size);
stream->picture.used = PICTURE(used);
stream->picture.allocated = PICTURE(allocated);
memcpy(stream->picture.data, PICTURE(data), stream->picture.size * sizeof(*stream->picture.data));
memcpy(stream->picture.data, PICTURE(data), stream->picture.used * sizeof(*stream->picture.data));
stream->picture.grab_time = PICTURE(grab_time);
stream->picture.encode_begin_time = PICTURE(encode_begin_time);
@@ -311,9 +323,9 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
stream->width = stream->dev->run->width;
stream->height = stream->dev->run->height;
stream->updated = true;
atomic_store(&stream->updated, true);
A_PTHREAD_M_UNLOCK(&stream->mutex);
A_MUTEX_UNLOCK(&stream->mutex);
# undef PICTURE
}
@@ -327,11 +339,11 @@ static long double _stream_get_fluency_delay(struct device_t *dev, struct worker
for (unsigned number = 0; number < dev->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_PTHREAD_M_LOCK(&WORKER(last_comp_time_mutex));
A_MUTEX_LOCK(&WORKER(last_comp_time_mutex));
if (WORKER(last_comp_time) > 0) {
sum_comp_time += WORKER(last_comp_time);
}
A_PTHREAD_M_UNLOCK(&WORKER(last_comp_time_mutex));
A_MUTEX_UNLOCK(&WORKER(last_comp_time_mutex));
# undef WORKER
}
@@ -348,14 +360,14 @@ static long double _stream_get_fluency_delay(struct device_t *dev, struct worker
return min_delay;
}
static int _stream_init_loop(struct device_t *dev, struct workers_pool_t *pool) {
static int _stream_init_loop(struct stream_t *stream, struct workers_pool_t *pool) {
int retval = -1;
LOG_DEBUG("%s: *dev->stop = %d", __FUNCTION__, dev->stop);
while (!dev->stop) {
if ((retval = _stream_init(dev, pool)) < 0) {
LOG_INFO("Sleeping %u seconds before new stream init ...", dev->error_delay);
sleep(dev->error_delay);
LOG_DEBUG("%s: stream->proc->stop = %d", __FUNCTION__, atomic_load(&stream->proc->stop));
while (!atomic_load(&stream->proc->stop)) {
if ((retval = _stream_init(stream, pool)) < 0) {
LOG_INFO("Sleeping %u seconds before new stream init ...", stream->dev->error_delay);
sleep(stream->dev->error_delay);
} else {
break;
}
@@ -363,218 +375,143 @@ static int _stream_init_loop(struct device_t *dev, struct workers_pool_t *pool)
return retval;
}
static int _stream_init(struct device_t *dev, struct workers_pool_t *pool) {
static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
SEP_INFO('=');
_stream_destroy_workers(dev, pool);
_stream_control(dev, false);
device_close(dev);
_stream_destroy_workers(stream, pool);
device_switch_capturing(stream->dev, false);
device_close(stream->dev);
if (device_open(dev) < 0) {
if (device_open(stream->dev) < 0) {
goto error;
}
if (_stream_control(dev, true) < 0) {
if (device_switch_capturing(stream->dev, true) < 0) {
goto error;
}
encoder_prepare_live(pool->encoder, dev);
encoder_prepare_live(pool->encoder, stream->dev);
_stream_init_workers(dev, pool);
_stream_init_workers(stream, pool);
return 0;
error:
device_close(dev);
device_close(stream->dev);
return -1;
}
static void _stream_init_workers(struct device_t *dev, struct workers_pool_t *pool) {
LOG_INFO("Spawning %u workers ...", dev->n_workers);
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool) {
LOG_INFO("Spawning %u workers ...", stream->dev->n_workers);
*pool->workers_stop = false;
A_CALLOC(pool->workers, dev->n_workers);
atomic_store(&pool->workers_stop, false);
A_CALLOC(pool->workers, stream->dev->n_workers);
A_PTHREAD_M_INIT(&pool->free_workers_mutex);
A_PTHREAD_C_INIT(&pool->free_workers_cond);
A_MUTEX_INIT(&pool->free_workers_mutex);
A_COND_INIT(&pool->free_workers_cond);
for (unsigned number = 0; number < dev->n_workers; ++number) {
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
# define CTX(_next) WORKER(ctx._next)
pool->free_workers += 1;
A_PTHREAD_M_INIT(&WORKER(has_job_mutex));
A_PTHREAD_C_INIT(&WORKER(has_job_cond));
A_MUTEX_INIT(&WORKER(has_job_mutex));
atomic_init(&WORKER(has_job), false);
A_COND_INIT(&WORKER(has_job_cond));
CTX(number) = number;
CTX(dev) = dev;
CTX(dev_stop) = (sig_atomic_t *volatile)&dev->stop;
CTX(workers_stop) = pool->workers_stop;
WORKER(number) = number;
WORKER(proc_stop) = &stream->proc->stop;
WORKER(workers_stop) = &pool->workers_stop;
CTX(encoder) = pool->encoder;
WORKER(free_workers_mutex) = &pool->free_workers_mutex;
WORKER(free_workers) = &pool->free_workers;
WORKER(free_workers_cond) = &pool->free_workers_cond;
CTX(last_comp_time_mutex) = &WORKER(last_comp_time_mutex);
CTX(last_comp_time) = &WORKER(last_comp_time);
WORKER(dev) = stream->dev;
WORKER(encoder) = pool->encoder;
CTX(has_job_mutex) = &WORKER(has_job_mutex);
CTX(has_job) = &WORKER(has_job);
CTX(job_failed) = &WORKER(job_failed);
CTX(job_start_time) = &WORKER(job_start_time);
CTX(has_job_cond) = &WORKER(has_job_cond);
A_THREAD_CREATE(&WORKER(tid), _stream_worker_thread, (void *)&(pool->workers[number]));
CTX(free_workers_mutex) = &pool->free_workers_mutex;
CTX(free_workers) = &pool->free_workers;
CTX(free_workers_cond) = &pool->free_workers_cond;
A_PTHREAD_CREATE(&WORKER(tid), _stream_worker_thread, (void *)&WORKER(ctx));
# undef CTX
# undef WORKER
}
}
static void *_stream_worker_thread(void *v_ctx) {
struct worker_context_t *ctx = (struct worker_context_t *)v_ctx;
static void *_stream_worker_thread(void *v_worker) {
struct worker_t *worker = (struct worker_t *)v_worker;
LOG_DEBUG("Hello! I am a worker #%u ^_^", ctx->number);
LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number);
while (!*ctx->dev_stop && !*ctx->workers_stop) {
LOG_DEBUG("Worker %u waiting for a new job ...", ctx->number);
A_PTHREAD_M_LOCK(ctx->has_job_mutex);
A_PTHREAD_C_WAIT_TRUE(*ctx->has_job, ctx->has_job_cond, ctx->has_job_mutex);
A_PTHREAD_M_UNLOCK(ctx->has_job_mutex);
while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) {
LOG_DEBUG("Worker %u waiting for a new job ...", worker->number);
A_MUTEX_LOCK(&worker->has_job_mutex);
A_COND_WAIT_TRUE(atomic_load(&worker->has_job), &worker->has_job_cond, &worker->has_job_mutex);
A_MUTEX_UNLOCK(&worker->has_job_mutex);
if (!*ctx->workers_stop) {
# define PICTURE(_next) ctx->dev->run->pictures[ctx->buf_index]._next
if (!atomic_load(worker->workers_stop)) {
# define PICTURE(_next) worker->dev->run->pictures[worker->buf_index]._next
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", ctx->number, ctx->buf_index);
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index);
PICTURE(encode_begin_time) = get_now_monotonic();
if (encoder_compress_buffer(ctx->encoder, ctx->dev, ctx->number, ctx->buf_index) < 0) {
*ctx->job_failed = true;
if (encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index) < 0) {
worker->job_failed = true;
}
PICTURE(encode_end_time) = get_now_monotonic();
if (_stream_release_buffer(ctx->dev, &ctx->buf_info) == 0) {
*ctx->job_start_time = PICTURE(encode_begin_time);
*ctx->has_job = false;
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
worker->job_start_time = PICTURE(encode_begin_time);
atomic_store(&worker->has_job, false);
long double last_comp_time = PICTURE(encode_end_time) - *ctx->job_start_time;
long double last_comp_time = PICTURE(encode_end_time) - worker->job_start_time;
A_PTHREAD_M_LOCK(ctx->last_comp_time_mutex);
*ctx->last_comp_time = last_comp_time;
A_PTHREAD_M_UNLOCK(ctx->last_comp_time_mutex);
A_MUTEX_LOCK(&worker->last_comp_time_mutex);
worker->last_comp_time = last_comp_time;
A_MUTEX_UNLOCK(&worker->last_comp_time_mutex);
LOG_VERBOSE("Compressed JPEG size=%zu; time=%0.3Lf; worker=%u; buffer=%u",
PICTURE(size), last_comp_time, ctx->number, ctx->buf_index);
PICTURE(used), last_comp_time, worker->number, worker->buf_index);
} else {
*ctx->job_failed = true;
*ctx->has_job = false;
worker->job_failed = true;
atomic_store(&worker->has_job, false);
}
# undef PICTURE
}
A_PTHREAD_M_LOCK(ctx->free_workers_mutex);
*ctx->free_workers += 1;
A_PTHREAD_M_UNLOCK(ctx->free_workers_mutex);
A_PTHREAD_C_SIGNAL(ctx->free_workers_cond);
A_MUTEX_LOCK(worker->free_workers_mutex);
*worker->free_workers += 1;
A_MUTEX_UNLOCK(worker->free_workers_mutex);
A_COND_SIGNAL(worker->free_workers_cond);
}
LOG_DEBUG("Bye-bye (worker %u)", ctx->number);
LOG_DEBUG("Bye-bye (worker %u)", worker->number);
return NULL;
}
static void _stream_destroy_workers(struct device_t *dev, struct workers_pool_t *pool) {
static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool_t *pool) {
if (pool->workers) {
LOG_INFO("Destroying workers ...");
*pool->workers_stop = true;
for (unsigned number = 0; number < dev->n_workers; ++number) {
atomic_store(&pool->workers_stop, true);
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
# define WORKER(_next) pool->workers[number]._next
A_PTHREAD_M_LOCK(&WORKER(has_job_mutex));
WORKER(has_job) = true; // Final job: die
A_PTHREAD_M_UNLOCK(&WORKER(has_job_mutex));
A_PTHREAD_C_SIGNAL(&WORKER(has_job_cond));
A_MUTEX_LOCK(&WORKER(has_job_mutex));
atomic_store(&WORKER(has_job), true); // Final job: die
A_MUTEX_UNLOCK(&WORKER(has_job_mutex));
A_COND_SIGNAL(&WORKER(has_job_cond));
A_PTHREAD_JOIN(WORKER(tid));
A_PTHREAD_M_DESTROY(&WORKER(has_job_mutex));
A_PTHREAD_C_DESTROY(&WORKER(has_job_cond));
A_THREAD_JOIN(WORKER(tid));
A_MUTEX_DESTROY(&WORKER(has_job_mutex));
A_COND_DESTROY(&WORKER(has_job_cond));
# undef WORKER
}
A_PTHREAD_M_DESTROY(&pool->free_workers_mutex);
A_PTHREAD_C_DESTROY(&pool->free_workers_cond);
A_MUTEX_DESTROY(&pool->free_workers_mutex);
A_COND_DESTROY(&pool->free_workers_cond);
free(pool->workers);
}
pool->free_workers = 0;
pool->workers = NULL;
}
static int _stream_control(struct device_t *dev, bool enable) {
if (enable != dev->run->capturing) {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
LOG_DEBUG("Calling ioctl(%s) ...", (enable ? "VIDIOC_STREAMON" : "VIDIOC_STREAMOFF"));
if (xioctl(dev->run->fd, (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
LOG_PERROR("Unable to %s capturing", (enable ? "start" : "stop"));
if (enable) {
return -1;
}
}
dev->run->capturing = enable;
LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
}
return 0;
}
static int _stream_grab_buffer(struct device_t *dev, struct v4l2_buffer *buf_info) {
MEMSET_ZERO_PTR(buf_info);
buf_info->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_info->memory = V4L2_MEMORY_MMAP;
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_DQBUF, buf_info) < 0) {
LOG_PERROR("Unable to dequeue buffer");
return -1;
}
LOG_DEBUG("Got a new frame in buffer index=%u; bytesused=%u", buf_info->index, buf_info->bytesused);
if (buf_info->index >= dev->run->n_buffers) {
LOG_ERROR("Got invalid buffer index=%u; nbuffers=%u", buf_info->index, dev->run->n_buffers);
return -1;
}
return 0;
}
static int _stream_release_buffer(struct device_t *dev, struct v4l2_buffer *buf_info) {
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
if (xioctl(dev->run->fd, VIDIOC_QBUF, buf_info) < 0) {
LOG_PERROR("Unable to requeue buffer");
return -1;
}
return 0;
}
static int _stream_handle_event(struct device_t *dev) {
struct v4l2_event event;
LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ...");
if (!xioctl(dev->run->fd, VIDIOC_DQEVENT, &event)) {
switch (event.type) {
case V4L2_EVENT_SOURCE_CHANGE:
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
return -1;
case V4L2_EVENT_EOS:
LOG_INFO("Got V4L2_EVENT_EOS: end of stream (ignored)");
return 0;
}
} else {
LOG_PERROR("Got some V4L2 device event, but where is it? ");
}
return 0;
}

View File

@@ -23,7 +23,7 @@
#pragma once
#include <stdbool.h>
#include <signal.h>
#include <stdatomic.h>
#include <pthread.h>
@@ -31,50 +31,36 @@
#include "encoder.h"
struct worker_context_t {
struct worker_t {
pthread_t tid;
unsigned number;
struct device_t *dev;
atomic_bool *proc_stop;
atomic_bool *workers_stop;
pthread_mutex_t last_comp_time_mutex;
long double last_comp_time;
pthread_mutex_t has_job_mutex;
int buf_index;
struct v4l2_buffer buf_info;
sig_atomic_t *volatile dev_stop;
bool *workers_stop;
struct encoder_t *encoder;
pthread_mutex_t *last_comp_time_mutex;
long double *last_comp_time;
pthread_mutex_t *has_job_mutex;
bool *has_job;
bool *job_failed;
long double *job_start_time;
pthread_cond_t *has_job_cond;
atomic_bool has_job;
bool job_failed;
long double job_start_time;
pthread_cond_t has_job_cond;
pthread_mutex_t *free_workers_mutex;
unsigned *free_workers;
pthread_cond_t *free_workers_cond;
};
struct worker_t {
struct worker_context_t ctx;
pthread_t tid;
struct worker_t *order_prev;
struct worker_t *order_next;
pthread_mutex_t last_comp_time_mutex;
long double last_comp_time;
pthread_mutex_t has_job_mutex;
bool has_job;
bool job_failed;
long double job_start_time;
pthread_cond_t has_job_cond;
struct worker_t *order_prev;
struct worker_t *order_next;
struct device_t *dev;
struct encoder_t *encoder;
};
struct workers_pool_t {
struct worker_t *workers;
bool *workers_stop;
atomic_bool workers_stop;
pthread_mutex_t free_workers_mutex;
unsigned free_workers;
@@ -83,13 +69,20 @@ struct workers_pool_t {
struct encoder_t *encoder;
};
struct process_t {
atomic_bool stop;
atomic_bool slowdown;
};
struct stream_t {
struct picture_t picture;
unsigned width;
unsigned height;
unsigned captured_fps;
bool updated;
atomic_bool updated;
pthread_mutex_t mutex;
struct process_t *proc;
struct device_t *dev;
struct encoder_t *encoder;
};
@@ -100,3 +93,4 @@ void stream_destroy(struct stream_t *stream);
void stream_loop(struct stream_t *stream);
void stream_loop_break(struct stream_t *stream);
void stream_switch_slowdown(struct stream_t *stream, bool slowdown);

View File

@@ -25,33 +25,34 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <time.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#define A_PTHREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
#define A_PTHREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
#define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
#define A_PTHREAD_M_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL))
#define A_PTHREAD_M_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex))
#define A_PTHREAD_M_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex))
#define A_PTHREAD_M_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex))
#define A_MUTEX_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL))
#define A_MUTEX_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex))
#define A_MUTEX_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex))
#define A_MUTEX_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex))
#define A_PTHREAD_C_INIT(_cond) assert(!pthread_cond_init(_cond, NULL))
#define A_PTHREAD_C_DESTROY(_cond) assert(!pthread_cond_destroy(_cond))
#define A_PTHREAD_C_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
#define A_PTHREAD_C_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); }
#define A_COND_INIT(_cond) assert(!pthread_cond_init(_cond, NULL))
#define A_COND_DESTROY(_cond) assert(!pthread_cond_destroy(_cond))
#define A_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
#define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); }
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
#define MEMSET_ZERO_PTR(_ptr) memset(_ptr, 0, sizeof(*(_ptr)))
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
@@ -63,12 +64,16 @@ INLINE char *bool_to_string(bool flag) {
return (flag ? "true" : "false");
}
INLINE unsigned min_u(unsigned a, unsigned b) {
return (a < b ? a : b);
}
INLINE unsigned max_u(unsigned a, unsigned b) {
return (a > b ? a : b);
}
INLINE long long floor_ms(long double now) {
return (long long) now - (now < (long long) now); // floor()
return (long long)now - (now < (long long)now); // floor()
}
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
@@ -99,3 +104,7 @@ INLINE long double get_now_real(void) {
get_now(CLOCK_REALTIME, &sec, &msec);
return (long double)sec + ((long double)msec) / 1000;
}
INLINE pid_t get_thread_id(void) {
return syscall(SYS_gettid);
}

View File

@@ -30,7 +30,9 @@
#include "logging.h"
#define XIOCTL_RETRIES 4
#ifndef XIOCTL_RETRIES
# define XIOCTL_RETRIES 4
#endif
INLINE int xioctl(int fd, int request, void *arg) {

View File

@@ -22,56 +22,36 @@
import sys
import os
import textwrap
# =====
def main():
assert len(sys.argv) == 4, "%s <src> <dest> <name>" % (sys.argv[0])
src = sys.argv[1]
dest = sys.argv[2]
assert len(sys.argv) == 4, "%s <file.html> <file.h> <name>" % (sys.argv[0])
html_path = sys.argv[1]
header_path = sys.argv[2]
name = sys.argv[3]
with open(src, "r") as html_file:
with open(html_path, "r") as html_file:
text = html_file.read()
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "prepend.h")) as prepend_file:
prepend = prepend_file.read()
text = text.strip()
text = text.replace("\"", "\\\"")
text = text.replace("%VERSION%", "\" VERSION \"")
text = textwrap.indent(text, "\t", (lambda line: True))
text = "\n".join(("%s \\" if line.strip() else "%s\\") % (line) for line in text.split("\n"))
text = "const char %s[] = \" \\\n%s\n\";\n" % (name, text)
text = textwrap.dedent("""
/*****************************************************************************
# #
# 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/>. #
# #
*****************************************************************************/
text = "\n".join(
("%s \\" if line.strip() else "%s\\") % (line)
for line in text.split("\n")
)
text = "const char HTML_%s_PAGE[] = \" \\\n%s\n\";\n" % (name, text)
text = prepend + "\n#include \"../../config.h\"\n\n\n" + text
#pragma once
#include "../config.h"
""").strip() + "\n\n\n" + text
with open(dest, "w") as h_file:
h_file.write(text)
with open(header_path, "w") as header_file:
header_file.write(text)
# =====

View File

@@ -22,22 +22,53 @@
import sys
import textwrap
import os
import io
import struct
# =====
def _get_jpeg_size(data):
# https://sheep.horse/2013/9/finding_the_dimensions_of_a_jpeg_file_in_python.html
stream = io.BytesIO(data)
while True:
marker = struct.unpack(">H", stream.read(2))[0]
if (
marker == 0xFFD8 # Start of image
or marker == 0xFF01 # Private marker
or (marker >= 0xFFD0 and 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]
if marker == 0xFFC0: # Baseline DCT chunk, has the info we want
(_, height, width) = struct.unpack(">BHH", stream.read(5))
return (width, height)
# Not the chunk we want, skip it
stream.seek(length - 2, 1)
# =====
def main():
assert len(sys.argv) == 6, "%s <src> <dest> <prefix> <width> <height>" % (sys.argv[0])
assert len(sys.argv) == 4, "%s <file.jpeg> <file.h> <name>" % (sys.argv[0])
jpeg_path = sys.argv[1]
header_path = sys.argv[2]
name = sys.argv[3]
src = sys.argv[1]
dest = sys.argv[2]
prefix = sys.argv[3]
width = int(sys.argv[4])
height = int(sys.argv[5])
with open(src, "rb") as jpeg_file:
with open(jpeg_path, "rb") as jpeg_file:
jpeg_data = jpeg_file.read()
(width, height) = _get_jpeg_size(jpeg_data)
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "prepend.h")) as prepend_file:
prepend = prepend_file.read()
rows = [[]]
for ch in jpeg_data:
if len(rows[-1]) > 20:
@@ -45,34 +76,13 @@ def main():
rows[-1].append("0x%.2X" % (ch))
text = ",\n\t".join(", ".join(row) for row in rows)
text = "const unsigned char %s_JPEG_DATA[] = {\n\t%s\n};\n" % (prefix, text)
text = "const unsigned %s_JPEG_HEIGHT = %d;\n\n" % (prefix, height) + text
text = "const unsigned %s_JPEG_WIDTH = %d;\n" % (prefix, width) + text
text = textwrap.dedent("""
/*****************************************************************************
# #
# 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/>. #
# #
*****************************************************************************/
""").strip() + "\n\n\n" + text
text = "const unsigned char %s_JPEG_DATA[] = {\n\t%s\n};\n" % (name, text)
text = "const unsigned %s_JPEG_HEIGHT = %d;\n\n" % (name, height) + text
text = "const unsigned %s_JPEG_WIDTH = %d;\n" % (name, width) + text
text = prepend + "\n\n" + text
with open(dest, "w") as h_file:
h_file.write(text)
with open(header_path, "w") as header_file:
header_file.write(text)
# =====

23
tools/prepend.h Normal file
View File

@@ -0,0 +1,23 @@
/*****************************************************************************
# #
# 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/>. #
# #
*****************************************************************************/
#pragma once