mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57132c46ac | ||
|
|
6a7a26360f | ||
|
|
7a699833c4 | ||
|
|
fcdb8f5d42 | ||
|
|
e827ad8a7a | ||
|
|
caf5b9191b | ||
|
|
163aa8b5af | ||
|
|
bc873e31d0 | ||
|
|
947924e900 | ||
|
|
0827cb2b65 | ||
|
|
d24d48212f | ||
|
|
a23475be57 | ||
|
|
b7d8c5bfa6 | ||
|
|
433e884fad | ||
|
|
1d1c7c705d | ||
|
|
b02b0f910c | ||
|
|
bfbb5dd29d | ||
|
|
f3339f0502 | ||
|
|
5d270b0029 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 1.3
|
||||
current_version = 1.5
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,8 +2,8 @@
|
||||
/pkg/arch/src/
|
||||
/pkg/arch/v*.tar.gz
|
||||
/pkg/arch/ustreamer-*.pkg.tar.xz
|
||||
/build/
|
||||
/config.mk
|
||||
/vgcore.*
|
||||
/ustreamer
|
||||
/*.sock
|
||||
*.o
|
||||
|
||||
34
Makefile
34
Makefile
@@ -11,11 +11,13 @@ LDFLAGS ?=
|
||||
RPI_VC_HEADERS ?= /opt/vc/include
|
||||
RPI_VC_LIBS ?= /opt/vc/lib
|
||||
|
||||
BUILD ?= build
|
||||
|
||||
|
||||
# =====
|
||||
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
|
||||
_LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
|
||||
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)
|
||||
_SRCS = $(shell ls src/*.c src/http/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
|
||||
|
||||
|
||||
define optbool
|
||||
@@ -24,20 +26,26 @@ endef
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_OMX)),)
|
||||
LIBS += -lbcm_host -lvcos -lopenmaxil -L$(RPI_VC_LIBS)
|
||||
_LIBS += -lbcm_host -lvcos -lopenmaxil -L$(RPI_VC_LIBS)
|
||||
override CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
|
||||
SOURCES += $(shell ls src/encoders/omx/*.c)
|
||||
_SRCS += $(shell ls src/encoders/omx/*.c)
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||
LIBS += -lwiringPi
|
||||
_LIBS += -lwiringPi
|
||||
override CFLAGS += -DWITH_GPIO
|
||||
endif
|
||||
|
||||
|
||||
WITH_PTHREAD_NP ?= 1
|
||||
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
|
||||
override CFLAGS += -DWITH_PTHREAD_NP
|
||||
endif
|
||||
|
||||
|
||||
# =====
|
||||
all: $(SOURCES) $(PROG)
|
||||
all: $(PROG)
|
||||
|
||||
|
||||
install: $(PROG)
|
||||
@@ -57,19 +65,20 @@ regen:
|
||||
tools/make-html-h.py src/http/data/index.html src/http/data/index_html.h INDEX
|
||||
|
||||
|
||||
$(PROG): $(SOURCES:.c=.o)
|
||||
$(PROG): $(_SRCS:%.c=$(BUILD)/%.o)
|
||||
$(info -- LD $@)
|
||||
@ $(CC) $(SOURCES:.c=.o) -o $@ $(LDFLAGS) $(LIBS)
|
||||
@ $(CC) $^ -o $@ $(LDFLAGS) $(_LIBS)
|
||||
$(info ===== Build complete =====)
|
||||
$(info == CC = $(CC))
|
||||
$(info == LIBS = $(LIBS))
|
||||
$(info == LIBS = $(_LIBS))
|
||||
$(info == CFLAGS = $(CFLAGS))
|
||||
$(info == LDFLAGS = $(LDFLAGS))
|
||||
|
||||
|
||||
.c.o:
|
||||
$(BUILD)/%.o: %.c
|
||||
$(info -- CC $<)
|
||||
@ $(CC) $< -o $@ $(CFLAGS) $(LIBS)
|
||||
@ mkdir -p $(dir $@) || true
|
||||
@ $(CC) $< -o $@ $(CFLAGS) $(_LIBS)
|
||||
|
||||
|
||||
release:
|
||||
@@ -91,6 +100,5 @@ push:
|
||||
|
||||
clean-all: clean
|
||||
clean:
|
||||
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)
|
||||
rm -rf $(PROG) $(BUILD) vgcore.* *.sock
|
||||
|
||||
@@ -32,7 +32,7 @@ If you're going to live-stream from your backyard webcam and need to control it,
|
||||
# Building
|
||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libuuid```.
|
||||
|
||||
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [wiringPi](http://wiringpi.com) and pass option ```WITH_GPIO=1```.
|
||||
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [wiringPi](http://wiringpi.com) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default).
|
||||
|
||||
```
|
||||
$ git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
# Сборка
|
||||
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```.
|
||||
|
||||
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [wiringPi](http://wiringpi.com) и добавьте параметр ```WITH_GPIO=1```.
|
||||
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [wiringPi](http://wiringpi.com) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен).
|
||||
|
||||
```
|
||||
$ git clone --depth=1 https://github.com/pikvm/ustreamer
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=1.3
|
||||
pkgver=1.5
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=1.3
|
||||
PKG_VERSION:=1.5
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef VERSION
|
||||
# define VERSION "1.3"
|
||||
# define VERSION "1.5"
|
||||
#endif
|
||||
|
||||
20
src/device.c
20
src/device.c
@@ -34,11 +34,15 @@
|
||||
|
||||
#include <sys/select.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "xioctl.h"
|
||||
#include "picture.h"
|
||||
|
||||
|
||||
static const struct {
|
||||
@@ -174,9 +178,8 @@ void device_close(struct device_t *dev) {
|
||||
|
||||
if (dev->run->pictures) {
|
||||
LOG_DEBUG("Releasing picture buffers ...");
|
||||
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index].data; ++index) {
|
||||
free(dev->run->pictures[index].data);
|
||||
dev->run->pictures[index].data = NULL;
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
picture_destroy(dev->run->pictures[index]);
|
||||
}
|
||||
free(dev->run->pictures);
|
||||
dev->run->pictures = NULL;
|
||||
@@ -283,7 +286,7 @@ int device_grab_buffer(struct device_t *dev) {
|
||||
|
||||
dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused;
|
||||
memcpy(&dev->run->hw_buffers[buf_info.index].buf_info, &buf_info, sizeof(struct v4l2_buffer));
|
||||
dev->run->pictures[buf_info.index].grab_time = get_now_monotonic();
|
||||
dev->run->pictures[buf_info.index]->grab_time = get_now_monotonic();
|
||||
return buf_info.index;
|
||||
}
|
||||
|
||||
@@ -578,14 +581,15 @@ static int _device_open_queue_buffers(struct device_t *dev) {
|
||||
}
|
||||
|
||||
static void _device_open_alloc_picbufs(struct device_t *dev) {
|
||||
size_t picture_size = picture_get_generous_size(dev->run->width, dev->run->height);
|
||||
|
||||
LOG_DEBUG("Allocating picture buffers ...");
|
||||
A_CALLOC(dev->run->pictures, dev->run->n_buffers);
|
||||
|
||||
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_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;
|
||||
dev->run->pictures[index] = picture_init();
|
||||
LOG_DEBUG("Pre-allocating picture buffer %u sized %zu bytes... ", index, picture_size);
|
||||
picture_realloc_data(dev->run->pictures[index], picture_size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
14
src/device.h
14
src/device.h
@@ -27,6 +27,8 @@
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "picture.h"
|
||||
|
||||
|
||||
#define VIDEO_MIN_WIDTH 160
|
||||
#define VIDEO_MAX_WIDTH 10240
|
||||
@@ -50,15 +52,6 @@ struct hw_buffer_t {
|
||||
struct v4l2_buffer buf_info;
|
||||
};
|
||||
|
||||
struct picture_t {
|
||||
unsigned char *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
long double grab_time;
|
||||
long double encode_begin_time;
|
||||
long double encode_end_time;
|
||||
};
|
||||
|
||||
struct device_runtime_t {
|
||||
int fd;
|
||||
unsigned width;
|
||||
@@ -67,8 +60,7 @@ struct device_runtime_t {
|
||||
unsigned n_buffers;
|
||||
unsigned n_workers;
|
||||
struct hw_buffer_t *hw_buffers;
|
||||
struct picture_t *pictures;
|
||||
size_t max_raw_image_size;
|
||||
struct picture_t **pictures;
|
||||
bool capturing;
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
@@ -29,12 +30,12 @@
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
#include "logging.h"
|
||||
#include "device.h"
|
||||
|
||||
#include "encoders/cpu/encoder.h"
|
||||
#include "encoders/hw/encoder.h"
|
||||
|
||||
#ifdef WITH_OMX
|
||||
# include "encoders/omx/encoder.h"
|
||||
#endif
|
||||
@@ -200,7 +201,7 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
|
||||
|
||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
dev->run->pictures[buf_index].encode_begin_time = get_now_monotonic();
|
||||
dev->run->pictures[buf_index]->encode_begin_time = get_now_monotonic();
|
||||
|
||||
if (encoder->run->type == ENCODER_TYPE_CPU) {
|
||||
cpu_encoder_compress_buffer(dev, buf_index, encoder->run->quality);
|
||||
@@ -215,7 +216,10 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
|
||||
}
|
||||
# endif
|
||||
|
||||
dev->run->pictures[buf_index].encode_end_time = get_now_monotonic();
|
||||
dev->run->pictures[buf_index]->encode_end_time = get_now_monotonic();
|
||||
|
||||
dev->run->pictures[buf_index]->width = dev->run->width;
|
||||
dev->run->pictures[buf_index]->height = dev->run->height;
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -24,9 +24,8 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "pthread.h"
|
||||
#include <pthread.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "device.h"
|
||||
|
||||
#ifdef WITH_OMX
|
||||
|
||||
@@ -29,13 +29,14 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../../tools.h"
|
||||
#include "../../picture.h"
|
||||
#include "../../device.h"
|
||||
|
||||
|
||||
@@ -43,7 +44,6 @@ struct _jpeg_dest_manager_t {
|
||||
struct jpeg_destination_mgr mgr; // Default manager
|
||||
JOCTET *buffer; // Start of buffer
|
||||
struct picture_t *picture;
|
||||
unsigned char *picture_data_cursor;
|
||||
};
|
||||
|
||||
|
||||
@@ -79,7 +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);
|
||||
|
||||
_jpeg_set_picture(&jpeg, &dev->run->pictures[index]);
|
||||
_jpeg_set_picture(&jpeg, dev->run->pictures[index]);
|
||||
|
||||
jpeg.image_width = dev->run->width;
|
||||
jpeg.image_height = dev->run->height;
|
||||
@@ -108,7 +108,7 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
|
||||
jpeg_finish_compress(&jpeg);
|
||||
jpeg_destroy_compress(&jpeg);
|
||||
|
||||
assert(dev->run->pictures[index].used > 0);
|
||||
assert(dev->run->pictures[index]->used > 0);
|
||||
}
|
||||
|
||||
static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture) {
|
||||
@@ -125,7 +125,6 @@ static void _jpeg_set_picture(j_compress_ptr jpeg, struct picture_t *picture) {
|
||||
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
|
||||
dest->mgr.term_destination = _jpeg_term_destination;
|
||||
dest->picture = picture;
|
||||
dest->picture_data_cursor = picture->data;
|
||||
|
||||
picture->used = 0;
|
||||
}
|
||||
@@ -277,13 +276,8 @@ 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;
|
||||
|
||||
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;
|
||||
picture_append_data(dest->picture, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
|
||||
|
||||
dest->mgr.next_output_byte = dest->buffer;
|
||||
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
|
||||
@@ -293,18 +287,13 @@ static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
|
||||
|
||||
static void _jpeg_term_destination(j_compress_ptr jpeg) {
|
||||
// Called by jpeg_finish_compress after all data has been written.
|
||||
// Usually needs to flush buffer
|
||||
// Usually needs to flush buffer.
|
||||
|
||||
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
||||
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->picture_data_cursor, dest->buffer, final);
|
||||
dest->picture_data_cursor += final;
|
||||
dest->picture->used = new_used;
|
||||
// Write any data remaining in the buffer.
|
||||
picture_append_data(dest->picture, dest->buffer, final);
|
||||
}
|
||||
|
||||
#undef JPEG_OUTPUT_BUFFER_SIZE
|
||||
|
||||
@@ -36,13 +36,14 @@
|
||||
#include "../../tools.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../xioctl.h"
|
||||
#include "../../picture.h"
|
||||
#include "../../device.h"
|
||||
|
||||
#include "huffman.h"
|
||||
|
||||
|
||||
void _copy_plus_huffman(const struct hw_buffer_t *src, struct picture_t *dest);
|
||||
static bool _is_huffman(const unsigned char *data);
|
||||
static size_t _memcpy_with_huffman(unsigned char *dest, const unsigned char *src, size_t size);
|
||||
|
||||
|
||||
int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
|
||||
@@ -51,12 +52,12 @@ int hw_encoder_prepare(struct device_t *dev, unsigned quality) {
|
||||
MEMSET_ZERO(comp);
|
||||
|
||||
if (xioctl(dev->run->fd, VIDIOC_G_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Can't query HW encoder params and set quality (unsupported)");
|
||||
LOG_ERROR("Device does not support setting of HW encoding quality parameters");
|
||||
return -1;
|
||||
}
|
||||
comp.quality = quality;
|
||||
if (xioctl(dev->run->fd, VIDIOC_S_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Can't set HW encoder quality (unsupported)");
|
||||
LOG_ERROR("Unable to change MJPG quality for JPEG source with HW pass-through encoder");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@@ -66,15 +67,30 @@ void hw_encoder_compress_buffer(struct device_t *dev, unsigned index) {
|
||||
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
|
||||
assert(0 && "Unsupported input format for HW encoder");
|
||||
}
|
||||
_copy_plus_huffman(&dev->run->hw_buffers[index], dev->run->pictures[index]);
|
||||
}
|
||||
|
||||
# define PICTURE(_next) dev->run->pictures[index]._next
|
||||
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
|
||||
void _copy_plus_huffman(const struct hw_buffer_t *src, struct picture_t *dest) {
|
||||
if (!_is_huffman(src->data)) {
|
||||
const unsigned char *src_ptr = src->data;
|
||||
const unsigned char *src_end = src->data + src->used;
|
||||
size_t paste;
|
||||
|
||||
assert(PICTURE(allocated) >= HW_BUFFER(used) + sizeof(HUFFMAN_TABLE));
|
||||
PICTURE(used) = _memcpy_with_huffman(PICTURE(data), HW_BUFFER(data), HW_BUFFER(used));
|
||||
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
|
||||
src_ptr += 1;
|
||||
}
|
||||
if (src_ptr >= src_end) {
|
||||
dest->used = 0; // Error
|
||||
return;
|
||||
}
|
||||
paste = src_ptr - src->data;
|
||||
|
||||
# undef HW_BUFFER
|
||||
# undef PICTURE
|
||||
picture_set_data(dest, src->data, paste);
|
||||
picture_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
|
||||
picture_append_data(dest, src_ptr, src->used - paste);
|
||||
} else {
|
||||
picture_set_data(dest, src->data, src->used);
|
||||
}
|
||||
}
|
||||
|
||||
static bool _is_huffman(const unsigned char *data) {
|
||||
@@ -91,27 +107,3 @@ static bool _is_huffman(const unsigned char *data) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static size_t _memcpy_with_huffman(unsigned char *dest, const unsigned char *src, size_t size) {
|
||||
if (!_is_huffman(src)) {
|
||||
const unsigned char *src_ptr = src;
|
||||
const unsigned char *src_end = src + size;
|
||||
size_t paste;
|
||||
|
||||
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
|
||||
src_ptr += 1;
|
||||
}
|
||||
if (src_ptr >= src_end) {
|
||||
return 0;
|
||||
}
|
||||
paste = src_ptr - src;
|
||||
|
||||
memcpy(dest, src, paste);
|
||||
memcpy(dest + paste, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
|
||||
memcpy(dest + paste + sizeof(HUFFMAN_TABLE), src_ptr, size - paste);
|
||||
return (size + sizeof(HUFFMAN_TABLE));
|
||||
} else {
|
||||
memcpy(dest, src, size);
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
#include "../../logging.h"
|
||||
#include "../../tools.h"
|
||||
#include "../../picture.h"
|
||||
#include "../../device.h"
|
||||
|
||||
#include "formatters.h"
|
||||
@@ -174,7 +175,6 @@ int omx_encoder_prepare(struct omx_encoder_t *omx, struct device_t *dev, unsigne
|
||||
}
|
||||
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index) {
|
||||
# define PICTURE(_next) dev->run->pictures[index]._next
|
||||
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
|
||||
# define IN(_next) omx->input_buffer->_next
|
||||
# define OUT(_next) omx->output_buffer->_next
|
||||
@@ -188,7 +188,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
return -1;
|
||||
}
|
||||
|
||||
PICTURE(used) = 0;
|
||||
dev->run->pictures[index]->used = 0;
|
||||
omx->output_available = false;
|
||||
omx->input_required = true;
|
||||
|
||||
@@ -200,9 +200,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
if (omx->output_available) {
|
||||
omx->output_available = false;
|
||||
|
||||
assert(PICTURE(used) + OUT(nFilledLen) <= PICTURE(allocated));
|
||||
memcpy(PICTURE(data) + PICTURE(used), OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
|
||||
PICTURE(used) += OUT(nFilledLen);
|
||||
picture_append_data(dev->run->pictures[index], OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
|
||||
|
||||
if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) {
|
||||
OUT(nFlags) = 0;
|
||||
@@ -244,7 +242,6 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
# undef OUT
|
||||
# undef IN
|
||||
# undef HW_BUFFER
|
||||
# undef PICTURE
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -313,7 +310,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_raw_image_size;
|
||||
portdef.nBufferSize = picture_get_generous_size(dev->run->width, dev->run->height);
|
||||
|
||||
# define MAP_FORMAT(_v4l2_format, _omx_format) \
|
||||
case _v4l2_format: { portdef.format.image.eColorFormat = _omx_format; break; }
|
||||
|
||||
@@ -24,12 +24,11 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../tools.h"
|
||||
|
||||
|
||||
static const char ENCODING_TABLE[] = {
|
||||
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',
|
||||
@@ -40,7 +39,7 @@ static const char ENCODING_TABLE[] = {
|
||||
'4', '5', '6', '7', '8', '9', '+', '/',
|
||||
};
|
||||
|
||||
static const unsigned MOD_TABLE[] = {0, 2, 1};
|
||||
static const unsigned _MOD_TABLE[] = {0, 2, 1};
|
||||
|
||||
|
||||
char *base64_encode(const unsigned char *str) {
|
||||
@@ -57,13 +56,13 @@ char *base64_encode(const unsigned char *str) {
|
||||
|
||||
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];
|
||||
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++) {
|
||||
for (unsigned index = 0; index < _MOD_TABLE[str_len % 3]; index++) {
|
||||
encoded[encoded_size - 2 - index] = '=';
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,5 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
char *base64_encode(const unsigned char *str);
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../logging.h"
|
||||
#include "../picture.h"
|
||||
|
||||
#include "data/blank_jpeg.h"
|
||||
|
||||
@@ -41,55 +41,44 @@ struct _jpeg_error_manager_t {
|
||||
};
|
||||
|
||||
|
||||
static struct blank_t *_blank_init_internal(void);
|
||||
static struct blank_t *_blank_init_external(const char *path);
|
||||
static struct picture_t *_init_internal(void);
|
||||
static struct picture_t *_init_external(const char *path);
|
||||
|
||||
static int _jpeg_read_geometry(FILE *fp, unsigned *width, unsigned *height);
|
||||
static void _jpeg_error_handler(j_common_ptr jpeg);
|
||||
|
||||
|
||||
struct blank_t *blank_init(const char *path) {
|
||||
struct blank_t *blank = NULL;
|
||||
struct picture_t *blank_picture_init(const char *path) {
|
||||
struct picture_t *blank = NULL;
|
||||
|
||||
if (path) {
|
||||
blank = _blank_init_external(path);
|
||||
blank = _init_external(path);
|
||||
}
|
||||
|
||||
if (blank) {
|
||||
LOG_INFO("Using external blank placeholder: %s", path);
|
||||
} else {
|
||||
blank = _blank_init_internal();
|
||||
blank = _init_internal();
|
||||
LOG_INFO("Using internal blank placeholder");
|
||||
}
|
||||
return blank;
|
||||
}
|
||||
|
||||
void blank_destroy(struct blank_t *blank) {
|
||||
free(blank->picture.data);
|
||||
free(blank);
|
||||
}
|
||||
|
||||
static struct blank_t *_blank_init_internal(void) {
|
||||
struct blank_t *blank;
|
||||
|
||||
A_CALLOC(blank, 1);
|
||||
|
||||
A_CALLOC(blank->picture.data, ARRAY_LEN(BLANK_JPEG_DATA));
|
||||
memcpy(blank->picture.data, BLANK_JPEG_DATA, ARRAY_LEN(BLANK_JPEG_DATA));
|
||||
|
||||
blank->picture.used = ARRAY_LEN(BLANK_JPEG_DATA);
|
||||
blank->picture.allocated = ARRAY_LEN(BLANK_JPEG_DATA);
|
||||
static struct picture_t *_init_internal(void) {
|
||||
struct picture_t *blank;
|
||||
|
||||
blank = picture_init();
|
||||
picture_set_data(blank, BLANK_JPEG_DATA, ARRAY_LEN(BLANK_JPEG_DATA));
|
||||
blank->width = BLANK_JPEG_WIDTH;
|
||||
blank->height = BLANK_JPEG_HEIGHT;
|
||||
|
||||
return blank;
|
||||
}
|
||||
|
||||
static struct blank_t *_blank_init_external(const char *path) {
|
||||
static struct picture_t *_init_external(const char *path) {
|
||||
FILE *fp = NULL;
|
||||
struct blank_t *blank;
|
||||
struct picture_t *blank;
|
||||
|
||||
A_CALLOC(blank, 1);
|
||||
blank = picture_init();
|
||||
|
||||
if ((fp = fopen(path, "rb")) == NULL) {
|
||||
LOG_PERROR("Can't open blank placeholder '%s'", path);
|
||||
@@ -107,13 +96,12 @@ static struct blank_t *_blank_init_external(const char *path) {
|
||||
|
||||
# define CHUNK_SIZE (100 * 1024)
|
||||
while (true) {
|
||||
if (blank->picture.used + CHUNK_SIZE >= blank->picture.allocated) {
|
||||
blank->picture.allocated = blank->picture.used + CHUNK_SIZE * 2;
|
||||
A_REALLOC(blank->picture.data, blank->picture.allocated);
|
||||
if (blank->used + CHUNK_SIZE >= blank->allocated) {
|
||||
picture_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
|
||||
}
|
||||
|
||||
size_t readed = fread(blank->picture.data + blank->picture.used, 1, CHUNK_SIZE, fp);
|
||||
blank->picture.used += readed;
|
||||
size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
|
||||
blank->used += readed;
|
||||
|
||||
if (readed < CHUNK_SIZE) {
|
||||
if (feof(fp)) {
|
||||
@@ -127,8 +115,7 @@ static struct blank_t *_blank_init_external(const char *path) {
|
||||
# undef CHUNK_SIZE
|
||||
|
||||
error:
|
||||
free(blank->picture.data);
|
||||
free(blank);
|
||||
picture_destroy(blank);
|
||||
blank = NULL;
|
||||
|
||||
ok:
|
||||
|
||||
@@ -22,17 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../device.h"
|
||||
#include "../picture.h"
|
||||
|
||||
|
||||
struct blank_t {
|
||||
struct picture_t picture;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
};
|
||||
|
||||
|
||||
struct blank_t *blank_init(const char *path);
|
||||
void blank_destroy(struct blank_t *blank);
|
||||
struct picture_t *blank_picture_init(const char *path);
|
||||
|
||||
@@ -22,10 +22,11 @@
|
||||
|
||||
#include "path.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef TEST_HTTP_PATH
|
||||
# include <stdio.h>
|
||||
# include <stdlib.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../tools.h"
|
||||
|
||||
@@ -116,7 +117,7 @@ char *simplify_request_path(const char *str) {
|
||||
return simplified;
|
||||
}
|
||||
|
||||
#if 0
|
||||
#if TEST_HTTP_PATH
|
||||
|
||||
int test_simplify_request_path(const char *sample, const char *expected) {
|
||||
char *result = simplify_request_path(sample);
|
||||
|
||||
@@ -22,19 +22,17 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#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>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/thread.h>
|
||||
@@ -50,23 +48,25 @@
|
||||
#endif
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../threading.h"
|
||||
#include "../logging.h"
|
||||
#include "../picture.h"
|
||||
#include "../encoder.h"
|
||||
#include "../stream.h"
|
||||
#ifdef WITH_GPIO
|
||||
# include "../gpio.h"
|
||||
#endif
|
||||
|
||||
#include "blank.h"
|
||||
#include "unix.h"
|
||||
#include "uri.h"
|
||||
#include "base64.h"
|
||||
#include "mime.h"
|
||||
#include "static.h"
|
||||
#include "blank.h"
|
||||
|
||||
#include "data/index_html.h"
|
||||
|
||||
|
||||
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 *v_server);
|
||||
@@ -91,6 +91,7 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
||||
struct exposed_t *exposed;
|
||||
|
||||
A_CALLOC(exposed, 1);
|
||||
exposed->picture = picture_init();
|
||||
|
||||
A_CALLOC(run, 1);
|
||||
run->stream = stream;
|
||||
@@ -105,6 +106,7 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
|
||||
server->passwd = "";
|
||||
server->static_path = "";
|
||||
server->timeout = 10;
|
||||
server->last_as_blank = -1;
|
||||
server->run = run;
|
||||
|
||||
assert(!evthread_use_pthreads());
|
||||
@@ -143,10 +145,10 @@ void http_server_destroy(struct http_server_t *server) {
|
||||
}
|
||||
|
||||
if (server->run->blank) {
|
||||
blank_destroy(server->run->blank);
|
||||
picture_destroy(server->run->blank);
|
||||
}
|
||||
|
||||
free(server->run->exposed->picture.data);
|
||||
picture_destroy(server->run->exposed->picture);
|
||||
free(server->run->exposed);
|
||||
free(server->run);
|
||||
free(server);
|
||||
@@ -165,8 +167,15 @@ int http_server_listen(struct http_server_t *server) {
|
||||
}
|
||||
|
||||
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
|
||||
server->run->blank = blank_init(server->blank_path);
|
||||
_expose_blank_picture(server);
|
||||
server->run->blank = blank_picture_init(server->blank_path);
|
||||
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
// See _expose_blank_picture()
|
||||
picture_copy(server->run->blank, EXPOSED(picture));
|
||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
||||
EXPOSED(expose_end_time) = EXPOSED(expose_begin_time);
|
||||
# undef EXPOSED
|
||||
|
||||
{
|
||||
struct timeval refresh_interval;
|
||||
@@ -205,51 +214,16 @@ int http_server_listen(struct http_server_t *server) {
|
||||
}
|
||||
|
||||
if (server->unix_path[0] != '\0') {
|
||||
struct sockaddr_un unix_addr;
|
||||
|
||||
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
||||
|
||||
# define MAX_SUN_PATH (sizeof(unix_addr.sun_path) - 1)
|
||||
|
||||
if (strlen(server->unix_path) > MAX_SUN_PATH) {
|
||||
LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
|
||||
if ((server->run->unix_fd = evhttp_my_bind_unix(
|
||||
server->run->http,
|
||||
server->unix_path,
|
||||
server->unix_rm,
|
||||
server->unix_mode)) < 0
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
MEMSET_ZERO(unix_addr);
|
||||
strncpy(unix_addr.sun_path, server->unix_path, MAX_SUN_PATH);
|
||||
unix_addr.sun_family = AF_UNIX;
|
||||
|
||||
# undef MAX_SUN_PATH
|
||||
|
||||
assert((server->run->unix_fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0);
|
||||
assert(!evutil_make_socket_nonblocking(server->run->unix_fd));
|
||||
|
||||
if (server->unix_rm && unlink(server->unix_path) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_PERROR("Can't remove old UNIX socket '%s'", server->unix_path);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (bind(server->run->unix_fd, (struct sockaddr *)&unix_addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
LOG_PERROR("Can't bind HTTP to UNIX socket '%s'", server->unix_path);
|
||||
return -1;
|
||||
}
|
||||
if (server->unix_mode && chmod(server->unix_path, server->unix_mode) < 0) {
|
||||
LOG_PERROR("Can't set permissions %o to UNIX socket '%s'", server->unix_mode, server->unix_path);
|
||||
return -1;
|
||||
}
|
||||
if (listen(server->run->unix_fd, 128) < 0) {
|
||||
LOG_PERROR("Can't listen UNIX socket '%s'", server->unix_path);
|
||||
return -1;
|
||||
}
|
||||
if (evhttp_accept_socket(server->run->http, server->run->unix_fd) < 0) {
|
||||
LOG_PERROR("Can't evhttp_accept_socket() UNIX socket '%s'", server->unix_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path);
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
||||
if (evhttp_bind_socket(server->run->http, server->host, server->port) < 0) {
|
||||
@@ -272,31 +246,6 @@ void http_server_loop_break(struct http_server_t *server) {
|
||||
event_base_loopbreak(server->run->base);
|
||||
}
|
||||
|
||||
|
||||
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 (
|
||||
value_str[0] == '1'
|
||||
|| !evutil_ascii_strcasecmp(value_str, "true")
|
||||
|| !evutil_ascii_strcasecmp(value_str, "yes")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static char *_http_get_param_uri(struct evkeyvalq *params, const char *key) {
|
||||
const char *value_str;
|
||||
|
||||
if ((value_str = evhttp_find_header(params, key)) != NULL) {
|
||||
return evhttp_encode_uri(value_str);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define ADD_HEADER(_key, _value) \
|
||||
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
||||
|
||||
@@ -420,6 +369,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
PREPROCESS_REQUEST;
|
||||
|
||||
# define ENCODER(_next) server->run->stream->encoder->_next
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
|
||||
A_MUTEX_LOCK(&ENCODER(run->mutex));
|
||||
encoder_run_type = ENCODER(run->type);
|
||||
@@ -436,15 +386,16 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
|
||||
encoder_type_to_string(encoder_run_type),
|
||||
encoder_run_quality,
|
||||
(server->fake_width ? server->fake_width : server->run->exposed->width),
|
||||
(server->fake_height ? server->fake_height : server->run->exposed->height),
|
||||
bool_to_string(server->run->exposed->online),
|
||||
(server->fake_width ? server->fake_width : EXPOSED(picture->width)),
|
||||
(server->fake_height ? server->fake_height : EXPOSED(picture->height)),
|
||||
bool_to_string(EXPOSED(online)),
|
||||
server->run->stream->dev->desired_fps,
|
||||
server->run->exposed->captured_fps,
|
||||
server->run->exposed->queued_fps,
|
||||
EXPOSED(captured_fps),
|
||||
EXPOSED(queued_fps),
|
||||
server->run->stream_clients_count
|
||||
));
|
||||
|
||||
# undef EXPOSED
|
||||
# undef ENCODER
|
||||
|
||||
for (struct stream_client_t * client = server->run->stream_clients; client != NULL; client = client->next) {
|
||||
@@ -476,7 +427,7 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture.data), EXPOSED(picture.used)));
|
||||
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");
|
||||
@@ -497,11 +448,11 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
|
||||
ADD_HEADER("X-UStreamer-Online", bool_to_string(EXPOSED(online)));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", EXPOSED(dropped));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(width));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Height", EXPOSED(height));
|
||||
ADD_TIME_HEADER("X-UStreamer-Grab-Time", EXPOSED(picture.grab_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Time", EXPOSED(picture.encode_begin_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-End-Time", EXPOSED(picture.encode_end_time));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EXPOSED(picture->width));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Height", EXPOSED(picture->height));
|
||||
ADD_TIME_HEADER("X-UStreamer-Grab-Time", EXPOSED(picture->grab_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Time", EXPOSED(picture->encode_begin_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-End-Time", EXPOSED(picture->encode_end_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Time", EXPOSED(expose_begin_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Time", EXPOSED(expose_cmp_time));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-End-Time", EXPOSED(expose_end_time));
|
||||
@@ -509,13 +460,12 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
|
||||
# undef ADD_UNSUGNED_HEADER
|
||||
# undef ADD_TIME_HEADER
|
||||
# undef EXPOSED
|
||||
|
||||
ADD_HEADER("Content-Type", "image/jpeg");
|
||||
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
evbuffer_free(buf);
|
||||
|
||||
# undef EXPOSED
|
||||
}
|
||||
|
||||
#undef ADD_HEADER
|
||||
@@ -547,10 +497,10 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
client->need_first_frame = true;
|
||||
|
||||
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||
client->key = _http_get_param_uri(¶ms, "key");
|
||||
client->extra_headers = _http_get_param_true(¶ms, "extra_headers");
|
||||
client->advance_headers = _http_get_param_true(¶ms, "advance_headers");
|
||||
client->dual_final_frames = _http_get_param_true(¶ms, "dual_final_frames");
|
||||
client->key = uri_get_string(¶ms, "key");
|
||||
client->extra_headers = uri_get_true(¶ms, "extra_headers");
|
||||
client->advance_headers = uri_get_true(¶ms, "advance_headers");
|
||||
client->dual_final_frames = uri_get_true(¶ms, "dual_final_frames");
|
||||
evhttp_clear_headers(¶ms);
|
||||
|
||||
uuid_generate(uuid);
|
||||
@@ -663,7 +613,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.used),
|
||||
EXPOSED(picture->used),
|
||||
get_now_real(),
|
||||
(client->extra_headers ? "" : RN)
|
||||
));
|
||||
@@ -684,12 +634,12 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
RN,
|
||||
bool_to_string(EXPOSED(online)),
|
||||
EXPOSED(dropped),
|
||||
EXPOSED(width),
|
||||
EXPOSED(height),
|
||||
EXPOSED(picture->width),
|
||||
EXPOSED(picture->height),
|
||||
client->fps,
|
||||
EXPOSED(picture.grab_time),
|
||||
EXPOSED(picture.encode_begin_time),
|
||||
EXPOSED(picture.encode_end_time),
|
||||
EXPOSED(picture->grab_time),
|
||||
EXPOSED(picture->encode_begin_time),
|
||||
EXPOSED(picture->encode_end_time),
|
||||
EXPOSED(expose_begin_time),
|
||||
EXPOSED(expose_cmp_time),
|
||||
EXPOSED(expose_end_time),
|
||||
@@ -698,7 +648,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
}
|
||||
}
|
||||
|
||||
assert(!evbuffer_add(buf, (void *)EXPOSED(picture.data), EXPOSED(picture.used)));
|
||||
assert(!evbuffer_add(buf, (void *)EXPOSED(picture->data), EXPOSED(picture->used)));
|
||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||
|
||||
if (client->advance_headers) {
|
||||
@@ -823,7 +773,7 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
|
||||
if (atomic_load(&server->run->stream->updated)) {
|
||||
LOG_DEBUG("Refreshing HTTP exposed ...");
|
||||
A_MUTEX_LOCK(&server->run->stream->mutex);
|
||||
if (server->run->stream->picture.used > 0) { // If online
|
||||
if (server->run->stream->online) {
|
||||
picture_updated = _expose_new_picture_unsafe(server);
|
||||
UNLOCK_STREAM;
|
||||
} else {
|
||||
@@ -843,106 +793,94 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
|
||||
}
|
||||
|
||||
static bool _expose_new_picture_unsafe(struct http_server_t *server) {
|
||||
# define STREAM(_next) server->run->stream->_next
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
# define STREAM(_next) server->run->stream->_next
|
||||
|
||||
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.used)
|
||||
|
||||
if (server->drop_same_frames) {
|
||||
if (
|
||||
EXPOSED(online)
|
||||
&& EXPOSED(dropped) < server->drop_same_frames
|
||||
&& EXPOSED(picture.used) == STREAM(picture.used)
|
||||
&& !memcmp(MEM_STREAM_TO_EXPOSED)
|
||||
&& picture_compare(EXPOSED(picture), STREAM(picture))
|
||||
) {
|
||||
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
||||
EXPOSED(expose_end_time) = EXPOSED(expose_cmp_time);
|
||||
LOG_VERBOSE("HTTP: dropped same frame number %u; cmp_time=%.06Lf",
|
||||
LOG_VERBOSE("HTTP: Dropped same frame number %u; cmp_time=%.06Lf",
|
||||
EXPOSED(dropped), EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
|
||||
EXPOSED(dropped) += 1;
|
||||
return false; // Not updated
|
||||
} else {
|
||||
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
||||
LOG_VERBOSE("HTTP: passed same frame check (frames are differ); cmp_time=%.06Lf",
|
||||
LOG_VERBOSE("HTTP: Passed same frame check (frames are differ); cmp_time=%.06Lf",
|
||||
EXPOSED(expose_cmp_time) - EXPOSED(expose_begin_time));
|
||||
}
|
||||
}
|
||||
|
||||
if (EXPOSED(picture.allocated) < STREAM(picture.allocated)) {
|
||||
A_REALLOC(EXPOSED(picture.data), STREAM(picture.allocated));
|
||||
EXPOSED(picture.allocated) = STREAM(picture.allocated);
|
||||
}
|
||||
picture_copy(STREAM(picture), EXPOSED(picture));
|
||||
|
||||
memcpy(MEM_STREAM_TO_EXPOSED);
|
||||
# undef STREAM
|
||||
|
||||
# undef MEM_STREAM_TO_EXPOSED
|
||||
|
||||
EXPOSED(picture.used) = STREAM(picture.used);
|
||||
|
||||
EXPOSED(picture.grab_time) = STREAM(picture.grab_time);
|
||||
EXPOSED(picture.encode_begin_time) = STREAM(picture.encode_begin_time);
|
||||
EXPOSED(picture.encode_end_time) = STREAM(picture.encode_end_time);
|
||||
|
||||
EXPOSED(width) = STREAM(width);
|
||||
EXPOSED(height) = STREAM(height);
|
||||
EXPOSED(online) = true;
|
||||
EXPOSED(dropped) = 0;
|
||||
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
||||
EXPOSED(expose_end_time) = get_now_monotonic();
|
||||
|
||||
LOG_VERBOSE("HTTP: exposed new frame; full exposition time = %.06Lf",
|
||||
LOG_VERBOSE("HTTP: Exposed new frame; full exposition time = %.06Lf",
|
||||
EXPOSED(expose_end_time) - EXPOSED(expose_begin_time));
|
||||
|
||||
# undef EXPOSED
|
||||
# undef STREAM
|
||||
return true; // Updated
|
||||
}
|
||||
|
||||
static bool _expose_blank_picture(struct http_server_t *server) {
|
||||
# define BLANK(_next) server->run->blank->_next
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
|
||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
||||
|
||||
if (EXPOSED(online) || EXPOSED(picture.used) == 0) {
|
||||
if (EXPOSED(picture.allocated) < BLANK(picture.used)) {
|
||||
A_REALLOC(EXPOSED(picture.data), BLANK(picture.used));
|
||||
EXPOSED(picture.allocated) = BLANK(picture.used);
|
||||
# define EXPOSE_BLANK picture_copy(server->run->blank, EXPOSED(picture))
|
||||
|
||||
if (EXPOSED(online)) { // Если переходим из online в offline
|
||||
if (server->last_as_blank < 0) { // Если last_as_blank выключено, просто покажем картинку
|
||||
LOG_INFO("HTTP: Changed picture to BLANK");
|
||||
EXPOSE_BLANK;
|
||||
} else if (server->last_as_blank > 0) { // Если нужен таймер - запустим
|
||||
LOG_INFO("HTTP: Freezing last alive frame for %d seconds", server->last_as_blank);
|
||||
EXPOSED(last_as_blank_time) = get_now_monotonic();
|
||||
} else { // last_as_blank == 0 - показываем последний фрейм вечно
|
||||
LOG_INFO("HTTP: Freezing last alive frame forever");
|
||||
}
|
||||
|
||||
memcpy(EXPOSED(picture.data), BLANK(picture.data), BLANK(picture.used));
|
||||
|
||||
EXPOSED(picture.used) = BLANK(picture.used);
|
||||
|
||||
EXPOSED(picture.grab_time) = 0;
|
||||
EXPOSED(picture.encode_begin_time) = 0;
|
||||
EXPOSED(picture.encode_end_time) = 0;
|
||||
|
||||
EXPOSED(width) = BLANK(width);
|
||||
EXPOSED(height) = BLANK(height);
|
||||
EXPOSED(captured_fps) = 0;
|
||||
EXPOSED(online) = false;
|
||||
goto updated;
|
||||
}
|
||||
|
||||
if ( // Если уже оффлайн, включена фича last_as_blank с таймером и он запущен
|
||||
server->last_as_blank > 0
|
||||
&& EXPOSED(last_as_blank_time) > 0
|
||||
&& EXPOSED(last_as_blank_time) + server->last_as_blank < EXPOSED(expose_begin_time)
|
||||
) {
|
||||
LOG_INFO("HTTP: Changed last alive frame to BLANK");
|
||||
EXPOSE_BLANK;
|
||||
EXPOSED(last_as_blank_time) = 0; // Останавливаем таймер
|
||||
goto updated;
|
||||
}
|
||||
|
||||
# undef EXPOSE_BLANK
|
||||
|
||||
if (EXPOSED(dropped) < server->run->drop_same_frames_blank) {
|
||||
LOG_PERF("HTTP: dropped same frame (BLANK) number %u", EXPOSED(dropped));
|
||||
LOG_PERF("HTTP: Dropped same frame (BLANK) number %u", EXPOSED(dropped));
|
||||
EXPOSED(dropped) += 1;
|
||||
EXPOSED(expose_end_time) = get_now_monotonic();
|
||||
return false; // Not updated
|
||||
}
|
||||
|
||||
updated:
|
||||
EXPOSED(captured_fps) = 0;
|
||||
EXPOSED(online) = false;
|
||||
EXPOSED(dropped) = 0;
|
||||
EXPOSED(expose_end_time) = get_now_monotonic();
|
||||
return true; // Updated
|
||||
|
||||
# undef EXPOSED
|
||||
# undef BLANK
|
||||
}
|
||||
|
||||
@@ -30,11 +30,9 @@
|
||||
#include <event2/http.h>
|
||||
#include <event2/util.h>
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../picture.h"
|
||||
#include "../stream.h"
|
||||
|
||||
#include "blank.h"
|
||||
|
||||
|
||||
struct stream_client_t {
|
||||
struct http_server_t *server;
|
||||
@@ -58,9 +56,7 @@ struct stream_client_t {
|
||||
};
|
||||
|
||||
struct exposed_t {
|
||||
struct picture_t picture;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
struct picture_t *picture;
|
||||
unsigned captured_fps;
|
||||
unsigned queued_fps;
|
||||
bool online;
|
||||
@@ -68,6 +64,7 @@ struct exposed_t {
|
||||
long double expose_begin_time;
|
||||
long double expose_cmp_time;
|
||||
long double expose_end_time;
|
||||
long double last_as_blank_time;
|
||||
};
|
||||
|
||||
struct http_server_runtime_t {
|
||||
@@ -80,7 +77,7 @@ struct http_server_runtime_t {
|
||||
struct exposed_t *exposed;
|
||||
struct stream_client_t *stream_clients;
|
||||
unsigned stream_clients_count;
|
||||
struct blank_t *blank;
|
||||
struct picture_t *blank;
|
||||
unsigned drop_same_frames_blank;
|
||||
};
|
||||
|
||||
@@ -97,6 +94,7 @@ struct http_server_t {
|
||||
char *static_path;
|
||||
|
||||
char *blank_path;
|
||||
int last_as_blank;
|
||||
unsigned drop_same_frames;
|
||||
bool slowdown;
|
||||
unsigned fake_width;
|
||||
|
||||
@@ -27,11 +27,10 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../logging.h"
|
||||
// #include "../logging.h"
|
||||
|
||||
#include "path.h"
|
||||
|
||||
|
||||
85
src/http/unix.c
Normal file
85
src/http/unix.c
Normal file
@@ -0,0 +1,85 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 "unix.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <event2/http.h>
|
||||
#include <event2/util.h>
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../logging.h"
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) {
|
||||
evutil_socket_t fd = -1;
|
||||
struct sockaddr_un addr;
|
||||
|
||||
# define MAX_SUN_PATH (sizeof(addr.sun_path) - 1)
|
||||
|
||||
if (strlen(path) > MAX_SUN_PATH) {
|
||||
LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
|
||||
return -1;
|
||||
}
|
||||
|
||||
MEMSET_ZERO(addr);
|
||||
strncpy(addr.sun_path, path, MAX_SUN_PATH);
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
# undef MAX_SUN_PATH
|
||||
|
||||
assert((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0);
|
||||
assert(!evutil_make_socket_nonblocking(fd));
|
||||
|
||||
if (rm && unlink(path) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_PERROR("Can't remove old UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
LOG_PERROR("Can't bind HTTP to UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
if (mode && chmod(path, mode) < 0) {
|
||||
LOG_PERROR("Can't set permissions %o to UNIX socket '%s'", mode, path);
|
||||
return -1;
|
||||
}
|
||||
if (listen(fd, 128) < 0) {
|
||||
LOG_PERROR("Can't listen UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
if (evhttp_accept_socket(http, fd) < 0) {
|
||||
LOG_PERROR("Can't evhttp_accept_socket() UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
33
src/http/unix.h
Normal file
33
src/http/unix.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 <stdbool.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <event2/http.h>
|
||||
#include <event2/util.h>
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode);
|
||||
54
src/http/uri.c
Normal file
54
src/http/uri.c
Normal file
@@ -0,0 +1,54 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 "uri.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <event2/http.h>
|
||||
#include <event2/util.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
|
||||
|
||||
bool uri_get_true(struct evkeyvalq *params, const char *key) {
|
||||
const char *value_str;
|
||||
|
||||
if ((value_str = evhttp_find_header(params, key)) != NULL) {
|
||||
if (
|
||||
value_str[0] == '1'
|
||||
|| !evutil_ascii_strcasecmp(value_str, "true")
|
||||
|| !evutil_ascii_strcasecmp(value_str, "yes")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
char *uri_get_string(struct evkeyvalq *params, const char *key) {
|
||||
const char *value_str;
|
||||
|
||||
if ((value_str = evhttp_find_header(params, key)) != NULL) {
|
||||
return evhttp_encode_uri(value_str);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
32
src/http/uri.h
Normal file
32
src/http/uri.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 <stdbool.h>
|
||||
|
||||
#include <event2/http.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
|
||||
|
||||
bool uri_get_true(struct evkeyvalq *params, const char *key);
|
||||
char *uri_get_string(struct evkeyvalq *params, const char *key);
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <pthread.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
|
||||
|
||||
enum {
|
||||
@@ -51,13 +52,13 @@ pthread_mutex_t log_mutex;
|
||||
#define LOGGING_INIT { \
|
||||
log_level = LOG_LEVEL_INFO; \
|
||||
log_colored = isatty(1); \
|
||||
assert(!pthread_mutex_init(&log_mutex, NULL)); \
|
||||
A_MUTEX_INIT(&log_mutex); \
|
||||
}
|
||||
|
||||
#define LOGGING_DESTROY assert(!pthread_mutex_destroy(&log_mutex))
|
||||
#define LOGGING_DESTROY A_MUTEX_DESTROY(&log_mutex)
|
||||
|
||||
#define LOGGING_LOCK assert(!pthread_mutex_lock(&log_mutex))
|
||||
#define LOGGING_UNLOCK assert(!pthread_mutex_unlock(&log_mutex))
|
||||
#define LOGGING_LOCK A_MUTEX_LOCK(&log_mutex)
|
||||
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&log_mutex)
|
||||
|
||||
|
||||
#define COLOR_GRAY "\x1b[30;1m"
|
||||
@@ -87,12 +88,14 @@ pthread_mutex_t log_mutex;
|
||||
|
||||
|
||||
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
|
||||
char _buf[MAX_THREAD_NAME] = {0}; \
|
||||
thread_get_name(_buf); \
|
||||
if (log_colored) { \
|
||||
printf(COLOR_GRAY "-- " _label_color _label COLOR_GRAY " [%.03Lf tid=%d]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
|
||||
get_now_monotonic(), get_thread_id(), ##__VA_ARGS__); \
|
||||
printf(COLOR_GRAY "-- " _label_color _label COLOR_GRAY " [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
|
||||
get_now_monotonic(), _buf, ##__VA_ARGS__); \
|
||||
} else { \
|
||||
printf("-- " _label " [%.03Lf tid=%d] -- " _msg, \
|
||||
get_now_monotonic(), get_thread_id(), ##__VA_ARGS__); \
|
||||
printf("-- " _label " [%.03Lf %9s] -- " _msg, \
|
||||
get_now_monotonic(), _buf, ##__VA_ARGS__); \
|
||||
} \
|
||||
putchar('\n'); \
|
||||
fflush(stdout); \
|
||||
@@ -109,7 +112,7 @@ pthread_mutex_t log_mutex;
|
||||
}
|
||||
|
||||
#define LOG_PERROR(_msg, ...) { \
|
||||
char _buf[1024] = ""; \
|
||||
char _buf[1024] = {0}; \
|
||||
char *_ptr = errno_to_string(_buf, 1024); \
|
||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _ptr); \
|
||||
}
|
||||
|
||||
10
src/main.c
10
src/main.c
@@ -26,13 +26,12 @@
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
#include "logging.h"
|
||||
#include "options.h"
|
||||
#include "device.h"
|
||||
@@ -60,12 +59,14 @@ static void _block_thread_signals(void) {
|
||||
}
|
||||
|
||||
static void *_stream_loop_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("stream");
|
||||
_block_thread_signals();
|
||||
stream_loop(_ctx->stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_server_loop_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("http");
|
||||
_block_thread_signals();
|
||||
http_server_loop(_ctx->server);
|
||||
return NULL;
|
||||
@@ -105,6 +106,8 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
LOGGING_INIT;
|
||||
|
||||
A_THREAD_RENAME("main");
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
GPIO_INIT;
|
||||
# endif
|
||||
@@ -150,6 +153,9 @@ int main(int argc, char *argv[]) {
|
||||
GPIO_SET_LOW(prog_running);
|
||||
# endif
|
||||
|
||||
if (exit_code == 0) {
|
||||
LOG_INFO("Bye-bye");
|
||||
}
|
||||
LOGGING_DESTROY;
|
||||
return (exit_code < 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -23,20 +23,21 @@
|
||||
#include "options.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#ifdef WITH_OMX
|
||||
# include <string.h>
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
#include "stream.h"
|
||||
#include "http/server.h"
|
||||
#ifdef WITH_GPIO
|
||||
# include "gpio.h"
|
||||
@@ -67,6 +68,7 @@ enum _OPT_VALUES {
|
||||
_O_UNIX_RM = 'D',
|
||||
_O_UNIX_MODE = 'M',
|
||||
_O_BLANK = 'k',
|
||||
_O_LAST_AS_BLANK = 'K',
|
||||
_O_DROP_SAME_FRAMES = 'e',
|
||||
_O_SLOWDOWN = 'l',
|
||||
_O_FAKE_RESOLUTION = 'R',
|
||||
@@ -156,6 +158,7 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"passwd", required_argument, NULL, _O_PASSWD},
|
||||
{"static", required_argument, NULL, _O_STATIC},
|
||||
{"blank", required_argument, NULL, _O_BLANK},
|
||||
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
|
||||
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
|
||||
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
|
||||
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
|
||||
@@ -233,9 +236,9 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
|
||||
}
|
||||
# endif
|
||||
|
||||
# define OPT_PARSE(_name, _dest, _func, _invalid) { \
|
||||
# define OPT_PARSE(_name, _dest, _func, _invalid, _available) { \
|
||||
if ((_dest = _func(optarg)) == _invalid) { \
|
||||
printf("Unknown " _name ": %s\n", optarg); \
|
||||
printf("Unknown " _name ": %s; available: %s\n", optarg, _available); \
|
||||
return -1; \
|
||||
} \
|
||||
break; \
|
||||
@@ -263,10 +266,10 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
|
||||
if (isalpha(_LONG_OPTS[opt_index].val)) {
|
||||
short_opts[short_index] = _LONG_OPTS[opt_index].val;
|
||||
++short_index;
|
||||
}
|
||||
if (_LONG_OPTS[opt_index].has_arg == required_argument) {
|
||||
short_opts[short_index] = ':';
|
||||
++short_index;
|
||||
if (_LONG_OPTS[opt_index].has_arg == required_argument) {
|
||||
short_opts[short_index] = ':';
|
||||
++short_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,9 +280,9 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
|
||||
case _O_RESOLUTION: OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
|
||||
# pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
# pragma GCC diagnostic push
|
||||
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN);
|
||||
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN, FORMATS_STR);
|
||||
# pragma GCC diagnostic pop
|
||||
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN);
|
||||
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN, STANDARDS_STR);
|
||||
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
|
||||
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 0, 8192, 0);
|
||||
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
|
||||
@@ -287,7 +290,7 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
|
||||
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_buffers, 1, 32, 0);
|
||||
case _O_WORKERS: OPT_NUMBER("--workers", dev->n_workers, 1, 32, 0);
|
||||
case _O_QUALITY: OPT_NUMBER("--quality", encoder->quality, 1, 100, 0);
|
||||
case _O_ENCODER: OPT_PARSE("encoder type", encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN);
|
||||
case _O_ENCODER: OPT_PARSE("encoder type", encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
|
||||
# ifdef WITH_OMX
|
||||
case _O_GLITCHED_RESOLUTIONS: OPT_GLITCHED_RESOLUTIONS;
|
||||
# endif
|
||||
@@ -317,6 +320,7 @@ int parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t
|
||||
case _O_PASSWD: OPT_SET(server->passwd, optarg);
|
||||
case _O_STATIC: OPT_SET(server->static_path, optarg);
|
||||
case _O_BLANK: OPT_SET(server->blank_path, optarg);
|
||||
case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", server->last_as_blank, 0, 86400, 0);
|
||||
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0);
|
||||
case _O_SLOWDOWN: OPT_SET(server->slowdown, true);
|
||||
case _O_FAKE_RESOLUTION: OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
|
||||
@@ -466,15 +470,25 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
printf(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n\n", dev->n_buffers);
|
||||
printf(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers.\n");
|
||||
printf(" Default: %u (the number of CPU cores (but not more than 4)).\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 affect the number of workers.\n\n");
|
||||
printf(" -q|--quality <N> ──────────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.\n", encoder->quality);
|
||||
printf(" Note: If HW encoding is used (JPEG source format selected),\n");
|
||||
printf(" this parameter attempts to configure the camera\n");
|
||||
printf(" or capture device hardware's internal encoder.\n");
|
||||
printf(" It does not re-encode MJPG to MJPG to change the quality level\n");
|
||||
printf(" for sources that already output MJPG.\n\n");
|
||||
printf(" -c|--encoder <type> ───────────────── Use specified encoder. It may affect the number of workers.\n");
|
||||
printf(" Available:\n");
|
||||
printf(" * CPU ─ Software MJPG encoding (default);\n");
|
||||
# ifdef WITH_OMX
|
||||
printf(" * OMX ─ GPU hardware accelerated MJPG encoding with OpenMax;\n");
|
||||
# endif
|
||||
printf(" * HW ── Use pre-encoded MJPG frames directly from camera hardware.\n\n");
|
||||
# ifdef WITH_OMX
|
||||
printf(" -g|--glitched-resolutions <WxH,...> ─ Comma-separated list of resolutions that require forced\n");
|
||||
printf(" encoding on CPU instead of OMX. Default: disabled.\n\n");
|
||||
# endif
|
||||
printf(" encoding on CPU instead of OMX. Default: disabled.\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(" --device-timeout <sec> ────────────── Timeout for device querying. Default: %u.\n\n", dev->timeout);
|
||||
printf(" --device-error-delay <sec> ────────── 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");
|
||||
@@ -502,8 +516,12 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
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(" -k|--blank <path> ─────────── Path to JPEG file that will be shown when the device is disconnected\n");
|
||||
printf(" -k|--blank <path> ────────── Path to JPEG file that will be shown when the device is disconnected\n");
|
||||
printf(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n\n");
|
||||
printf(" -K|--last-as-blank <sec> ─── Show the last frame received from the camera after it was disconnected,\n");
|
||||
printf(" but no more than specified time (or endlessly if 0 is specified).\n");
|
||||
printf(" If the device has not yet been online, display 'NO SIGNAL' or the image\n");
|
||||
printf(" specified by option --blank. Default: disabled.\n\n");
|
||||
printf(" -e|--drop-same-frames <N> ── Don't send identical 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");
|
||||
@@ -511,7 +529,7 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
|
||||
printf(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients are connected.\n");
|
||||
printf(" Useful to reduce CPU consumption. Default: disabled.\n\n");
|
||||
printf(" -R|--fake-resolution <WxH> ─ Override image resolution for state. Default: disabled.\n\n");
|
||||
printf(" --server-timeout <seconds> ─ Timeout for client connections. Default: %u.\n\n", server->timeout);
|
||||
printf(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n\n", server->timeout);
|
||||
#ifdef WITH_GPIO
|
||||
printf("GPIO options:\n");
|
||||
printf("═════════════\n");
|
||||
|
||||
96
src/picture.c
Normal file
96
src/picture.c
Normal file
@@ -0,0 +1,96 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 "picture.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
struct picture_t *picture_init(void) {
|
||||
struct picture_t *picture;
|
||||
|
||||
A_CALLOC(picture, 1);
|
||||
return picture;
|
||||
}
|
||||
|
||||
void picture_destroy(struct picture_t *picture) {
|
||||
if (picture->data) {
|
||||
free(picture->data);
|
||||
}
|
||||
free(picture);
|
||||
}
|
||||
|
||||
size_t picture_get_generous_size(unsigned width, unsigned height) {
|
||||
return ((width * height) << 1) * 2;
|
||||
}
|
||||
|
||||
void picture_realloc_data(struct picture_t *picture, size_t size) {
|
||||
if (picture->allocated < size) {
|
||||
LOG_DEBUG("Increasing picture 0x%p buffer: %zu -> %zu (+%zu)",
|
||||
picture, picture->allocated, size, size - picture->allocated);
|
||||
A_REALLOC(picture->data, size);
|
||||
picture->allocated = size;
|
||||
}
|
||||
}
|
||||
|
||||
void picture_set_data(struct picture_t *picture, const unsigned char *data, size_t size) {
|
||||
picture_realloc_data(picture, size);
|
||||
memcpy(picture->data, data, size);
|
||||
picture->used = size;
|
||||
}
|
||||
|
||||
void picture_append_data(struct picture_t *picture, const unsigned char *data, size_t size) {
|
||||
size_t new_used = picture->used + size;
|
||||
|
||||
picture_realloc_data(picture, new_used);
|
||||
memcpy(picture->data + picture->used, data, size);
|
||||
picture->used = new_used;
|
||||
}
|
||||
|
||||
void picture_copy(const struct picture_t *src, struct picture_t *dest) {
|
||||
assert(src->allocated);
|
||||
|
||||
picture_set_data(dest, src->data, src->allocated);
|
||||
|
||||
# define COPY(_field) dest->_field = src->_field
|
||||
|
||||
COPY(used);
|
||||
|
||||
COPY(width);
|
||||
COPY(height);
|
||||
|
||||
COPY(grab_time);
|
||||
COPY(encode_begin_time);
|
||||
COPY(encode_end_time);
|
||||
|
||||
# undef COPY
|
||||
}
|
||||
|
||||
bool picture_compare(const struct picture_t *a, const struct picture_t *b) {
|
||||
return (a->used == b->used && !memcmp(a->data, b->data, b->used));
|
||||
}
|
||||
51
src/picture.h
Normal file
51
src/picture.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
struct picture_t {
|
||||
unsigned char *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
long double grab_time;
|
||||
long double encode_begin_time;
|
||||
long double encode_end_time;
|
||||
};
|
||||
|
||||
|
||||
struct picture_t *picture_init(void);
|
||||
void picture_destroy(struct picture_t *picture);
|
||||
|
||||
size_t picture_get_generous_size(unsigned width, unsigned height);
|
||||
|
||||
void picture_realloc_data(struct picture_t *picture, size_t size);
|
||||
void picture_set_data(struct picture_t *picture, const unsigned char *data, size_t size);
|
||||
void picture_append_data(struct picture_t *picture, const unsigned char *data, size_t size);
|
||||
|
||||
void picture_copy(const struct picture_t *src, struct picture_t *dest);
|
||||
bool picture_compare(const struct picture_t *a, const struct picture_t *b);
|
||||
39
src/stream.c
39
src/stream.c
@@ -22,20 +22,19 @@
|
||||
|
||||
#include "stream.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
#include "logging.h"
|
||||
#include "xioctl.h"
|
||||
#include "picture.h"
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
#ifdef WITH_GPIO
|
||||
@@ -111,6 +110,7 @@ struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) {
|
||||
atomic_init(&proc->slowdown, false);
|
||||
|
||||
A_CALLOC(stream, 1);
|
||||
stream->picture = picture_init();
|
||||
stream->dev = dev;
|
||||
stream->encoder = encoder;
|
||||
atomic_init(&stream->updated, false);
|
||||
@@ -121,6 +121,7 @@ struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) {
|
||||
|
||||
void stream_destroy(struct stream_t *stream) {
|
||||
A_MUTEX_DESTROY(&stream->mutex);
|
||||
picture_destroy(stream->picture);
|
||||
free(stream->proc);
|
||||
free(stream);
|
||||
}
|
||||
@@ -139,11 +140,11 @@ void stream_loop(struct stream_t *stream) {
|
||||
long long captured_fps_second = 0;
|
||||
bool persistent_timeout_reported = false;
|
||||
|
||||
LOG_DEBUG("Allocation memory for stream picture ...");
|
||||
A_CALLOC(stream->picture.data, stream->dev->run->max_raw_image_size);
|
||||
|
||||
LOG_INFO("Capturing ...");
|
||||
|
||||
LOG_DEBUG("Pre-allocating memory for stream picture ...");
|
||||
picture_realloc_data(stream->picture, picture_get_generous_size(stream->dev->run->width, stream->dev->run->height));
|
||||
|
||||
while (!atomic_load(&stream->proc->stop)) {
|
||||
struct _worker_t *ready_worker;
|
||||
|
||||
@@ -278,10 +279,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
}
|
||||
|
||||
A_MUTEX_LOCK(&stream->mutex);
|
||||
stream->picture.used = 0; // On stream offline
|
||||
free(stream->picture.data);
|
||||
stream->width = 0;
|
||||
stream->height = 0;
|
||||
stream->online = false;
|
||||
atomic_store(&stream->updated, true);
|
||||
A_MUTEX_UNLOCK(&stream->mutex);
|
||||
|
||||
@@ -338,27 +336,15 @@ static struct _workers_pool_t *_stream_init(struct stream_t *stream) {
|
||||
}
|
||||
|
||||
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index, unsigned captured_fps) {
|
||||
# define PICTURE(_next) stream->dev->run->pictures[buf_index]._next
|
||||
|
||||
A_MUTEX_LOCK(&stream->mutex);
|
||||
|
||||
stream->picture.used = PICTURE(used);
|
||||
stream->picture.allocated = PICTURE(allocated);
|
||||
picture_copy(stream->dev->run->pictures[buf_index], stream->picture);
|
||||
|
||||
memcpy(stream->picture.data, PICTURE(data), stream->picture.used);
|
||||
|
||||
stream->picture.grab_time = PICTURE(grab_time);
|
||||
stream->picture.encode_begin_time = PICTURE(encode_begin_time);
|
||||
stream->picture.encode_end_time = PICTURE(encode_end_time);
|
||||
|
||||
stream->width = stream->dev->run->width;
|
||||
stream->height = stream->dev->run->height;
|
||||
stream->online = true;
|
||||
stream->captured_fps = captured_fps;
|
||||
atomic_store(&stream->updated, true);
|
||||
|
||||
A_MUTEX_UNLOCK(&stream->mutex);
|
||||
|
||||
# undef PICTURE
|
||||
}
|
||||
|
||||
static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream) {
|
||||
@@ -436,6 +422,7 @@ static void _workers_pool_destroy(struct _workers_pool_t *pool) {
|
||||
static void *_worker_thread(void *v_worker) {
|
||||
struct _worker_t *worker = (struct _worker_t *)v_worker;
|
||||
|
||||
A_THREAD_RENAME("worker-%u", worker->number);
|
||||
LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number);
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@@ -454,7 +441,7 @@ static void *_worker_thread(void *v_worker) {
|
||||
A_MUTEX_UNLOCK(&worker->has_job_mutex);
|
||||
|
||||
if (!atomic_load(worker->workers_stop)) {
|
||||
# define PICTURE(_next) worker->dev->run->pictures[worker->buf_index]._next
|
||||
# define PICTURE(_next) worker->dev->run->pictures[worker->buf_index]->_next
|
||||
|
||||
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index);
|
||||
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "picture.h"
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
|
||||
@@ -34,9 +38,8 @@ struct process_t {
|
||||
};
|
||||
|
||||
struct stream_t {
|
||||
struct picture_t picture;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
struct picture_t *picture;
|
||||
bool online;
|
||||
unsigned captured_fps;
|
||||
atomic_bool updated;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
109
src/threading.h
Normal file
109
src/threading.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
# include <pthread_np.h>
|
||||
# include <sys/param.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#ifdef PTHREAD_MAX_NAMELEN_NP
|
||||
# define MAX_THREAD_NAME PTHREAD_MAX_NAMELEN_NP
|
||||
#else
|
||||
# define MAX_THREAD_NAME 16
|
||||
#endif
|
||||
|
||||
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
|
||||
#define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# define A_THREAD_RENAME(_fmt, ...) { \
|
||||
char _buf[MAX_THREAD_NAME] = {0}; \
|
||||
assert(snprintf(_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \
|
||||
thread_set_name(_buf); \
|
||||
}
|
||||
#else
|
||||
# define A_THREAD_RENAME(_fmt, ...)
|
||||
#endif
|
||||
|
||||
#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_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)); }
|
||||
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
INLINE void thread_set_name(const char *name) {
|
||||
# if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
pthread_set_name_np(pthread_self(), name);
|
||||
# elif defined(__NetBSD__)
|
||||
pthread_setname_np(pthread_self(), "%s", (void *)name);
|
||||
# else
|
||||
# error thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
INLINE void thread_get_name(char *name) { // Always required for logging
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
int retval = -1;
|
||||
# if defined(__linux__) || defined (__NetBSD__)
|
||||
retval = pthread_getname_np(pthread_self(), name, MAX_THREAD_NAME);
|
||||
# elif \
|
||||
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|
||||
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|
||||
|| defined(__DragonFly__)
|
||||
pthread_get_name_np(pthread_self(), name, MAX_THREAD_NAME);
|
||||
if (name[0] != '\0') {
|
||||
retval = 0;
|
||||
}
|
||||
# else
|
||||
# error thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# endif
|
||||
if (retval < 0) {
|
||||
#endif
|
||||
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", (pid_t)syscall(SYS_gettid)) > 0);
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
}
|
||||
#endif
|
||||
}
|
||||
23
src/tools.h
23
src/tools.h
@@ -25,30 +25,11 @@
|
||||
#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_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
|
||||
#define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
|
||||
|
||||
#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_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)))))
|
||||
@@ -104,7 +85,3 @@ 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user