mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-20 08:46:31 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c8351c1bc | ||
|
|
4492cc1efe | ||
|
|
b2ca0ea998 | ||
|
|
924665c1a3 | ||
|
|
5d49018bb2 | ||
|
|
3ae8818b3d | ||
|
|
2bb1f71c9c | ||
|
|
537e55afc6 | ||
|
|
6cdaceb561 | ||
|
|
08aacdc9af | ||
|
|
3db57cfa42 | ||
|
|
d8a774e358 | ||
|
|
869d12759c |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 0.52
|
||||
current_version = 0.54
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
6
Makefile
6
Makefile
@@ -8,13 +8,13 @@ LDFLAGS ?=
|
||||
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/jpeg/*.c src/hw/*.c)
|
||||
SOURCES = $(shell ls src/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
|
||||
OBJECTS = $(SOURCES:.c=.o)
|
||||
PROG = ustreamer
|
||||
|
||||
|
||||
ifeq ($(shell ls -d /opt/vc/include 2>/dev/null), /opt/vc/include)
|
||||
SOURCES += $(shell ls src/omx/*.c)
|
||||
SOURCES += $(shell ls src/encoders/omx/*.c)
|
||||
LIBS += -lbcm_host -lvcos -lopenmaxil -L/opt/vc/lib
|
||||
override CFLAGS += -DOMX_ENCODER -DOMX_SKIP64BIT -I/opt/vc/include
|
||||
endif
|
||||
@@ -59,5 +59,5 @@ push:
|
||||
|
||||
clean-all: clean
|
||||
clean:
|
||||
rm -f src/*.o src/{jpeg,omx}/*.o vgcore.* *.sock $(PROG)
|
||||
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
|
||||
|
||||
4
PKGBUILD
4
PKGBUILD
@@ -3,14 +3,14 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=0.52
|
||||
pkgver=0.54
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pi-kvm/ustreamer"
|
||||
license=(GPL)
|
||||
arch=(i686 x86_64 armv6h armv7h)
|
||||
depends=(libjpeg libevent libutil-linux)
|
||||
# optional: raspberrypi-firmware for OMX JPEG compressor
|
||||
# optional: raspberrypi-firmware for OMX JPEG encoder
|
||||
makedepends=(gcc make)
|
||||
source=("$url/archive/v$pkgver.tar.gz")
|
||||
md5sums=(SKIP)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
| Multithreaded JPEG encoding |  Yes |  No |
|
||||
| [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration<br>on Raspberry Pi |  Yes |  No |
|
||||
| Behavior when the device<br>is disconnected while streaming |  Shows a black screen<br>with ```NO SIGNAL``` on it<br>until reconnected |  Stops the broadcast <sup>1</sup> |
|
||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal |  Yes |  Partially yes <sup>1</sup> |
|
||||
| [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) support -<br>the ability to change resolution<br>on the fly by source signal |  Yes |  Partially yes <sup>2</sup> |
|
||||
| Option to skip frames when streaming<br>static images by HTTP to save traffic |  Yes <sup>2</sup> |  No |
|
||||
| Streaming via UNIX domain socket |  Yes |  No |
|
||||
| Debug logs without recompiling,<br>performance statistics log,<br>access to HTTP broadcast parameters |  Yes |  No |
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
| Многопоточное кодирование JPEG |  Есть |  Нет |
|
||||
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi |  Есть |  Нет |
|
||||
| Поведение при физическом отключении устройства<br>от сервера во время работы |  Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова |  Прерывает трансляцию <sup>1</sup> |
|
||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) |  Есть |  Условно есть <sup>1</sup> |
|
||||
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) |  Есть |  Условно есть <sup>2</sup> |
|
||||
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика |  Есть <sup>2</sup> |  Нет |
|
||||
| Стрим через UNIX domain socket |  Есть |  Нет |
|
||||
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP |  Есть |  Нет |
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION "0.52"
|
||||
#define VERSION "0.54"
|
||||
|
||||
33
src/device.c
33
src/device.c
@@ -55,6 +55,7 @@ static const struct {
|
||||
{"YUYV", V4L2_PIX_FMT_YUYV},
|
||||
{"UYVY", V4L2_PIX_FMT_UYVY},
|
||||
{"RGB565", V4L2_PIX_FMT_RGB565},
|
||||
{"RGB24", V4L2_PIX_FMT_RGB24},
|
||||
{"JPEG", V4L2_PIX_FMT_MJPEG},
|
||||
{"JPEG", V4L2_PIX_FMT_JPEG},
|
||||
};
|
||||
@@ -69,8 +70,9 @@ static int _device_open_mmap(struct device_t *dev);
|
||||
static int _device_open_queue_buffers(struct device_t *dev);
|
||||
static int _device_apply_resolution(struct device_t *dev, unsigned width, unsigned height);
|
||||
|
||||
static const char *_format_to_string_auto(char *buf, size_t size, unsigned format);
|
||||
static const char *_format_to_string_null(unsigned format);
|
||||
static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned format);
|
||||
static const char *_format_to_string_nullable(unsigned format);
|
||||
static const char *_format_to_string_supported(unsigned format);
|
||||
static const char *_standard_to_string(v4l2_std_id standard);
|
||||
|
||||
|
||||
@@ -288,7 +290,6 @@ static int _device_apply_dv_timings(struct device_t *dev) {
|
||||
|
||||
static int _device_open_format(struct device_t *dev) {
|
||||
struct v4l2_format fmt;
|
||||
char format_str[8];
|
||||
|
||||
MEMSET_ZERO(fmt);
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
@@ -301,8 +302,8 @@ static int _device_open_format(struct device_t *dev) {
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_S_FMT, &fmt) < 0) {
|
||||
LOG_PERROR(
|
||||
"Unable to set format=%s; resolution=%ux%u",
|
||||
_format_to_string_auto(format_str, 8, dev->format),
|
||||
"Unable to set pixelformat=%s; resolution=%ux%u",
|
||||
_format_to_string_supported(dev->format),
|
||||
dev->run->width,
|
||||
dev->run->height
|
||||
);
|
||||
@@ -325,24 +326,27 @@ static int _device_open_format(struct device_t *dev) {
|
||||
|
||||
LOG_ERROR(
|
||||
"Could not obtain the requested pixelformat=%s; driver gave us %s",
|
||||
_format_to_string_auto(format_str, 8, dev->format),
|
||||
_format_to_string_auto(format_obtained_str, 8, fmt.fmt.pix.pixelformat)
|
||||
_format_to_string_supported(dev->format),
|
||||
_format_to_string_supported(fmt.fmt.pix.pixelformat)
|
||||
);
|
||||
|
||||
if ((format_str_nullable = (char *)_format_to_string_null(fmt.fmt.pix.pixelformat)) != NULL) {
|
||||
if ((format_str_nullable = (char *)_format_to_string_nullable(fmt.fmt.pix.pixelformat)) != NULL) {
|
||||
LOG_INFO(
|
||||
"Falling back to %s mode (consider using '--format=%s' option)",
|
||||
format_str_nullable,
|
||||
format_str_nullable
|
||||
);
|
||||
} else {
|
||||
LOG_ERROR("Unsupported pixel format");
|
||||
LOG_ERROR(
|
||||
"Unsupported pixelformat=%s (fourcc)",
|
||||
_format_to_string_fourcc(format_obtained_str, 8, fmt.fmt.pix.pixelformat)
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
dev->run->format = fmt.fmt.pix.pixelformat;
|
||||
LOG_INFO("Using pixelformat: %s", _format_to_string_auto(format_str, 8, dev->run->format));
|
||||
LOG_INFO("Using pixelformat: %s", _format_to_string_supported(dev->run->format));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -441,7 +445,7 @@ static int _device_apply_resolution(struct device_t *dev, unsigned width, unsign
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *_format_to_string_auto(char *buf, size_t size, unsigned format) {
|
||||
static const char *_format_to_string_fourcc(char *buf, size_t size, unsigned format) {
|
||||
assert(size >= 8);
|
||||
buf[0] = format & 0x7F;
|
||||
buf[1] = (format >> 8) & 0x7F;
|
||||
@@ -458,7 +462,7 @@ static const char *_format_to_string_auto(char *buf, size_t size, unsigned forma
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *_format_to_string_null(unsigned format) {
|
||||
static const char *_format_to_string_nullable(unsigned format) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) {
|
||||
if (format == _FORMATS[index].format) {
|
||||
return _FORMATS[index].name;
|
||||
@@ -467,6 +471,11 @@ static const char *_format_to_string_null(unsigned format) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *_format_to_string_supported(unsigned format) {
|
||||
const char *format_str = _format_to_string_nullable(format);
|
||||
return (format_str == NULL ? "unsupported" : format_str);
|
||||
}
|
||||
|
||||
static const char *_standard_to_string(v4l2_std_id standard) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_STANDARDS); ++index) {
|
||||
if (standard == _STANDARDS[index].standard) {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
#define STANDARDS_STR "UNKNOWN, PAL, NTSC, SECAM"
|
||||
|
||||
#define FORMAT_UNKNOWN -1
|
||||
#define FORMATS_STR "YUYV, UYVY, RGB565, JPEG"
|
||||
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, JPEG"
|
||||
|
||||
|
||||
struct hw_buffer_t {
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
|
||||
#include "jpeg/encoder.h"
|
||||
#include "hw/encoder.h"
|
||||
#include "encoders/cpu/encoder.h"
|
||||
#include "encoders/hw/encoder.h"
|
||||
|
||||
#ifdef OMX_ENCODER
|
||||
# include "omx/encoder.h"
|
||||
# include "encoders/omx/encoder.h"
|
||||
#endif
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ void encoder_prepare(struct encoder_t *encoder, struct device_t *dev) {
|
||||
|
||||
# ifdef OMX_ENCODER
|
||||
if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||
LOG_DEBUG("Preparing OMX encoder ...");
|
||||
LOG_DEBUG("Preparing OMX JPEG encoder ...");
|
||||
|
||||
if (dev->n_workers > OMX_MAX_ENCODERS) {
|
||||
LOG_INFO(
|
||||
@@ -136,8 +136,15 @@ enum encoder_type_t encoder_parse_type(const char *str) {
|
||||
return ENCODER_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic push
|
||||
const char *encoder_type_to_string(enum encoder_type_t type) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
||||
if (_ENCODER_TYPES[index].type == type) {
|
||||
return _ENCODER_TYPES[index].name;
|
||||
}
|
||||
}
|
||||
return _ENCODER_TYPES[0].name;
|
||||
}
|
||||
|
||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
|
||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
@@ -145,7 +152,7 @@ void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
|
||||
(dev->run->format == V4L2_PIX_FMT_MJPEG || dev->run->format == V4L2_PIX_FMT_JPEG)
|
||||
&& encoder->run->type != ENCODER_TYPE_HW
|
||||
) {
|
||||
LOG_INFO("Switching to HW encoder because the input format is (M)JPEG");
|
||||
LOG_INFO("Switching to HW JPEG encoder because the input format is (M)JPEG");
|
||||
A_PTHREAD_M_LOCK(&encoder->run->mutex);
|
||||
encoder->run->type = ENCODER_TYPE_HW;
|
||||
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
|
||||
@@ -153,21 +160,21 @@ void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
|
||||
|
||||
if (encoder->run->type == ENCODER_TYPE_HW) {
|
||||
if (dev->run->format != V4L2_PIX_FMT_MJPEG && dev->run->format != V4L2_PIX_FMT_JPEG) {
|
||||
LOG_INFO("Switching to CPU encoder because the input format is not (M)JPEG");
|
||||
LOG_INFO("Switching to CPU JPEG encoder because the input format is not (M)JPEG");
|
||||
goto use_fallback;
|
||||
}
|
||||
if (hw_encoder_prepare_live(dev, encoder->quality) < 0) {
|
||||
A_PTHREAD_M_LOCK(&encoder->run->mutex);
|
||||
encoder->run->quality = 0;
|
||||
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
|
||||
LOG_INFO("Using JPEG quality: HW-default");
|
||||
}
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
# ifdef OMX_ENCODER
|
||||
else if (encoder->run->type == ENCODER_TYPE_OMX) {
|
||||
for (unsigned index = 0; index < encoder->run->n_omxs; ++index) {
|
||||
if (omx_encoder_prepare_live(encoder->run->omxs[index], dev, encoder->quality) < 0) {
|
||||
LOG_ERROR("Can't prepare OMX encoder, falling back to CPU");
|
||||
LOG_ERROR("Can't prepare OMX JPEG encoder, falling back to CPU");
|
||||
goto use_fallback;
|
||||
}
|
||||
}
|
||||
@@ -176,14 +183,11 @@ void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev) {
|
||||
|
||||
return;
|
||||
|
||||
# pragma GCC diagnostic ignored "-Wunused-label"
|
||||
# pragma GCC diagnostic push
|
||||
use_fallback:
|
||||
A_PTHREAD_M_LOCK(&encoder->run->mutex);
|
||||
encoder->run->type = ENCODER_TYPE_CPU;
|
||||
encoder->run->quality = encoder->quality;
|
||||
A_PTHREAD_M_UNLOCK(&encoder->run->mutex);
|
||||
# pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-label"
|
||||
@@ -194,7 +198,7 @@ int encoder_compress_buffer(struct encoder_t *encoder, struct device_t *dev, uns
|
||||
assert(encoder->run->type != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
if (encoder->run->type == ENCODER_TYPE_CPU) {
|
||||
jpeg_encoder_compress_buffer(dev, buf_index, encoder->quality);
|
||||
cpu_encoder_compress_buffer(dev, buf_index, encoder->quality);
|
||||
} else if (encoder->run->type == ENCODER_TYPE_HW) {
|
||||
hw_encoder_compress_buffer(dev, buf_index);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "device.h"
|
||||
|
||||
#ifdef OMX_ENCODER
|
||||
# include "omx/encoder.h"
|
||||
# include "encoders/omx/encoder.h"
|
||||
# define ENCODER_TYPES_OMX_HINT ", OMX"
|
||||
#else
|
||||
# define ENCODER_TYPES_OMX_HINT ""
|
||||
@@ -72,6 +72,7 @@ struct encoder_t *encoder_init();
|
||||
void encoder_destroy(struct encoder_t *encoder);
|
||||
|
||||
enum encoder_type_t encoder_parse_type(const char *str);
|
||||
const char *encoder_type_to_string(enum encoder_type_t type);
|
||||
|
||||
void encoder_prepare(struct encoder_t *encoder, struct device_t *dev);
|
||||
void encoder_prepare_live(struct encoder_t *encoder, struct device_t *dev);
|
||||
|
||||
@@ -33,13 +33,13 @@
|
||||
#include <jpeglib.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../device.h"
|
||||
#include "../../tools.h"
|
||||
#include "../../device.h"
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
|
||||
struct _mjpg_destination_mgr {
|
||||
struct _jpeg_dest_manager_t {
|
||||
struct jpeg_destination_mgr mgr; // Default manager
|
||||
JOCTET *buffer; // Start of buffer
|
||||
unsigned char *outbuffer_cursor;
|
||||
@@ -50,18 +50,19 @@ struct _mjpg_destination_mgr {
|
||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written);
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
struct jpeg_compress_struct *jpeg, const unsigned char *data,
|
||||
unsigned width, unsigned height);
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
struct jpeg_compress_struct *jpeg, const unsigned char *data,
|
||||
unsigned width, unsigned height);
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
struct jpeg_compress_struct *jpeg, const unsigned char *data,
|
||||
unsigned width, unsigned height);
|
||||
|
||||
static void _jpeg_write_scanlines_rgb24(
|
||||
struct jpeg_compress_struct *jpeg, const unsigned char *data,
|
||||
unsigned width, unsigned height);
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg);
|
||||
@@ -69,14 +70,11 @@ static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
|
||||
static void _jpeg_term_destination(j_compress_ptr jpeg);
|
||||
|
||||
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned quality) {
|
||||
void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned quality) {
|
||||
// This function based on compress_image_to_jpeg() from mjpg-streamer
|
||||
|
||||
struct jpeg_compress_struct jpeg;
|
||||
struct jpeg_error_mgr jpeg_error;
|
||||
unsigned char *line_buffer;
|
||||
|
||||
A_CALLOC(line_buffer, dev->run->width * 3);
|
||||
|
||||
jpeg.err = jpeg_std_error(&jpeg_error);
|
||||
jpeg_create_compress(&jpeg);
|
||||
@@ -94,15 +92,16 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
|
||||
|
||||
jpeg_start_compress(&jpeg, TRUE);
|
||||
|
||||
# define WRITE_SCANLINES(_func) \
|
||||
_func(&jpeg, line_buffer, dev->run->hw_buffers[index].start, dev->run->width, dev->run->height)
|
||||
# define WRITE_SCANLINES(_format, _func) \
|
||||
case _format: { _func(&jpeg, dev->run->hw_buffers[index].start, dev->run->width, dev->run->height); break; }
|
||||
|
||||
switch (dev->run->format) {
|
||||
// https://www.fourcc.org/yuv.php
|
||||
case V4L2_PIX_FMT_YUYV: WRITE_SCANLINES(_jpeg_write_scanlines_yuyv); break;
|
||||
case V4L2_PIX_FMT_UYVY: WRITE_SCANLINES(_jpeg_write_scanlines_uyvy); break;
|
||||
case V4L2_PIX_FMT_RGB565: WRITE_SCANLINES(_jpeg_write_scanlines_rgb565); break;
|
||||
default: assert(0 && "Unsupported input format for JPEG compressor");
|
||||
WRITE_SCANLINES(V4L2_PIX_FMT_YUYV, _jpeg_write_scanlines_yuyv);
|
||||
WRITE_SCANLINES(V4L2_PIX_FMT_UYVY, _jpeg_write_scanlines_uyvy);
|
||||
WRITE_SCANLINES(V4L2_PIX_FMT_RGB565, _jpeg_write_scanlines_rgb565);
|
||||
WRITE_SCANLINES(V4L2_PIX_FMT_RGB24, _jpeg_write_scanlines_rgb24);
|
||||
default: assert(0 && "Unsupported input format for CPU JPEG encoder");
|
||||
}
|
||||
|
||||
# undef WRITE_SCANLINES
|
||||
@@ -111,21 +110,20 @@ void jpeg_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);
|
||||
free(line_buffer);
|
||||
assert(dev->run->pictures[index].size > 0);
|
||||
assert(dev->run->pictures[index].size <= dev->run->max_picture_size);
|
||||
}
|
||||
|
||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written) {
|
||||
struct _mjpg_destination_mgr *dest;
|
||||
struct _jpeg_dest_manager_t *dest;
|
||||
|
||||
if (jpeg->dest == NULL) {
|
||||
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct _mjpg_destination_mgr)
|
||||
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct _jpeg_dest_manager_t)
|
||||
)));
|
||||
}
|
||||
|
||||
dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
||||
dest->mgr.init_destination = _jpeg_init_destination;
|
||||
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
|
||||
dest->mgr.term_destination = _jpeg_term_destination;
|
||||
@@ -136,13 +134,15 @@ static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture,
|
||||
#define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x)))
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
struct jpeg_compress_struct *jpeg, const unsigned char *data,
|
||||
unsigned width, unsigned height) {
|
||||
|
||||
unsigned char *line_buffer;
|
||||
JSAMPROW scanlines[1];
|
||||
unsigned z = 0;
|
||||
|
||||
A_CALLOC(line_buffer, width * 3);
|
||||
|
||||
while (jpeg->next_scanline < height) {
|
||||
unsigned char *ptr = line_buffer;
|
||||
|
||||
@@ -168,17 +168,21 @@ static void _jpeg_write_scanlines_yuyv(
|
||||
scanlines[0] = line_buffer;
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
|
||||
free(line_buffer);
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
struct jpeg_compress_struct *jpeg, const unsigned char *data,
|
||||
unsigned width, unsigned height) {
|
||||
|
||||
unsigned char *line_buffer;
|
||||
JSAMPROW scanlines[1];
|
||||
unsigned z = 0;
|
||||
|
||||
while(jpeg->next_scanline < height) {
|
||||
A_CALLOC(line_buffer, width * 3);
|
||||
|
||||
while (jpeg->next_scanline < height) {
|
||||
unsigned char *ptr = line_buffer;
|
||||
|
||||
for(unsigned x = 0; x < width; ++x) {
|
||||
@@ -203,18 +207,22 @@ static void _jpeg_write_scanlines_uyvy(
|
||||
scanlines[0] = line_buffer;
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
|
||||
free(line_buffer);
|
||||
}
|
||||
|
||||
#undef NORM_COMPONENT
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(
|
||||
struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
struct jpeg_compress_struct *jpeg, const unsigned char *data,
|
||||
unsigned width, unsigned height) {
|
||||
|
||||
unsigned char *line_buffer;
|
||||
JSAMPROW scanlines[1];
|
||||
|
||||
while(jpeg->next_scanline < height) {
|
||||
A_CALLOC(line_buffer, width * 3);
|
||||
|
||||
while (jpeg->next_scanline < height) {
|
||||
unsigned char *ptr = line_buffer;
|
||||
|
||||
for(unsigned x = 0; x < width; ++x) {
|
||||
@@ -230,12 +238,26 @@ static void _jpeg_write_scanlines_rgb565(
|
||||
scanlines[0] = line_buffer;
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
|
||||
free(line_buffer);
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_rgb24(
|
||||
struct jpeg_compress_struct *jpeg, const unsigned char *data,
|
||||
unsigned width, unsigned height) {
|
||||
|
||||
JSAMPROW scanlines[1];
|
||||
|
||||
while (jpeg->next_scanline < height) {
|
||||
scanlines[0] = (unsigned char *)(data + jpeg->next_scanline * width * 3);
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#define JPEG_OUTPUT_BUFFER_SIZE 4096
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
||||
|
||||
// Allocate the output buffer - it will be released when done with image
|
||||
assert((dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
|
||||
@@ -249,7 +271,7 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
|
||||
// Called whenever local jpeg buffer fills up
|
||||
|
||||
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
struct _jpeg_dest_manager_t *dest = (struct _jpeg_dest_manager_t *)jpeg->dest;
|
||||
|
||||
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
|
||||
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
|
||||
@@ -265,7 +287,7 @@ 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
|
||||
|
||||
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
|
||||
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;
|
||||
|
||||
// Write any data remaining in the buffer
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../device.h"
|
||||
#include "../../device.h"
|
||||
|
||||
|
||||
void jpeg_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned quality);
|
||||
void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned quality);
|
||||
@@ -25,10 +25,10 @@
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../logging.h"
|
||||
#include "../xioctl.h"
|
||||
#include "../device.h"
|
||||
#include "../../tools.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../xioctl.h"
|
||||
#include "../../device.h"
|
||||
|
||||
|
||||
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality) {
|
||||
@@ -37,12 +37,12 @@ int hw_encoder_prepare_live(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 JPEG compressor params and set quality (unsupported)");
|
||||
LOG_ERROR("Can't query HW JPEG encoder params and set quality (unsupported)");
|
||||
return -1;
|
||||
}
|
||||
comp.quality = quality;
|
||||
if (xioctl(dev->run->fd, VIDIOC_S_JPEGCOMP, &comp) < 0) {
|
||||
LOG_ERROR("Can't set HW JPEG compressor quality (unsopported)");
|
||||
LOG_ERROR("Can't set HW JPEG encoder quality (unsopported)");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@@ -50,7 +50,7 @@ int hw_encoder_prepare_live(struct device_t *dev, unsigned quality) {
|
||||
|
||||
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 JPEG compressor");
|
||||
assert(0 && "Unsupported input format for HW JPEG encoder");
|
||||
}
|
||||
assert(dev->run->pictures[index].allocated >= dev->run->hw_buffers[index].length);
|
||||
memcpy(dev->run->pictures[index].data, dev->run->hw_buffers[index].start, dev->run->hw_buffers[index].length);
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../device.h"
|
||||
#include "../../device.h"
|
||||
|
||||
|
||||
int hw_encoder_prepare_live(struct device_t *dev, unsigned quality);
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <IL/OMX_Core.h>
|
||||
#include <IL/OMX_Component.h>
|
||||
|
||||
#include "../logging.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
#include "formatters.h"
|
||||
#include "component.h"
|
||||
@@ -33,9 +33,9 @@
|
||||
#include <IL/OMX_Broadcom.h>
|
||||
#include <interface/vcos/vcos_semaphore.h>
|
||||
|
||||
#include "../logging.h"
|
||||
#include "../tools.h"
|
||||
#include "../device.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../tools.h"
|
||||
#include "../../device.h"
|
||||
|
||||
#include "formatters.h"
|
||||
#include "component.h"
|
||||
@@ -173,7 +173,8 @@ int omx_encoder_prepare_live(struct omx_encoder_t *omx, struct device_t *dev, un
|
||||
|
||||
int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev, unsigned index) {
|
||||
OMX_ERRORTYPE error;
|
||||
bool loaded = false;
|
||||
unsigned slice_size = omx->input_buffer->nAllocLen;
|
||||
unsigned pos = 0;
|
||||
|
||||
if ((error = OMX_FillThisBuffer(omx->encoder, omx->output_buffer)) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Failed to request filling of the output buffer on encoder");
|
||||
@@ -194,7 +195,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
|
||||
memcpy(
|
||||
dev->run->pictures[index].data + dev->run->pictures[index].size,
|
||||
omx->output_buffer->pBuffer,
|
||||
omx->output_buffer->pBuffer + omx->output_buffer->nOffset,
|
||||
omx->output_buffer->nFilledLen
|
||||
);
|
||||
assert(dev->run->pictures[index].size + omx->output_buffer->nFilledLen <= dev->run->max_picture_size);
|
||||
@@ -213,14 +214,20 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
|
||||
if (omx->input_required) {
|
||||
omx->input_required = false;
|
||||
if (loaded) {
|
||||
|
||||
if (pos == dev->run->hw_buffers[index].length) {
|
||||
continue;
|
||||
}
|
||||
loaded = true;
|
||||
|
||||
memcpy(omx->input_buffer->pBuffer, dev->run->hw_buffers[index].start, dev->run->hw_buffers[index].length);
|
||||
memcpy(omx->input_buffer->pBuffer, dev->run->hw_buffers[index].start + pos, slice_size);
|
||||
omx->input_buffer->nOffset = 0;
|
||||
omx->input_buffer->nFilledLen = dev->run->hw_buffers[index].length;
|
||||
omx->input_buffer->nFilledLen = slice_size;
|
||||
|
||||
pos += slice_size;
|
||||
|
||||
if (pos + slice_size > dev->run->hw_buffers[index].length) {
|
||||
slice_size = dev->run->hw_buffers[index].length - pos;
|
||||
}
|
||||
|
||||
if ((error = OMX_EmptyThisBuffer(omx->encoder, omx->input_buffer)) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Failed to request emptying of the input buffer on encoder");
|
||||
@@ -295,23 +302,32 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
|
||||
portdef.format.image.nFrameWidth = dev->run->width;
|
||||
portdef.format.image.nFrameHeight = dev->run->height;
|
||||
portdef.format.image.nStride = 0;
|
||||
# define ALIGN(_x, _y) (((_x) + ((_y) - 1)) & ~((_y) - 1))
|
||||
portdef.format.image.nSliceHeight = ALIGN(dev->run->height, 16);
|
||||
# undef ALIGN
|
||||
# define ALIGN_HEIGHT(_x, _y) (((_x) + ((_y) - 1)) & ~((_y) - 1))
|
||||
portdef.format.image.nSliceHeight = ALIGN_HEIGHT(dev->run->height, 16);
|
||||
# undef ALIGN_HEIGHT
|
||||
portdef.format.image.bFlagErrorConcealment = OMX_FALSE;
|
||||
portdef.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused;
|
||||
portdef.nBufferSize = dev->run->max_picture_size;
|
||||
|
||||
# define MAP_FORMAT(_v4l2_format, _omx_format) \
|
||||
case _v4l2_format: { portdef.format.image.eColorFormat = _omx_format; break; }
|
||||
|
||||
switch (dev->run->format) {
|
||||
// https://www.fourcc.org/yuv.php
|
||||
// Also see comments inside OMX_IVCommon.h
|
||||
case V4L2_PIX_FMT_YUYV: portdef.format.image.eColorFormat = OMX_COLOR_FormatYCbYCr; break;
|
||||
case V4L2_PIX_FMT_UYVY: portdef.format.image.eColorFormat = OMX_COLOR_FormatCbYCrY; break;
|
||||
case V4L2_PIX_FMT_RGB565: portdef.format.image.eColorFormat = OMX_COLOR_Format16bitRGB565; break;
|
||||
// TODO: Check RGB565 + OMX. I don't have any USB devices which supports it.
|
||||
default: assert(0 && "Unsupported input format for OMX JPEG compressor");
|
||||
MAP_FORMAT(V4L2_PIX_FMT_YUYV, OMX_COLOR_FormatYCbYCr);
|
||||
MAP_FORMAT(V4L2_PIX_FMT_UYVY, OMX_COLOR_FormatCbYCrY);
|
||||
MAP_FORMAT(V4L2_PIX_FMT_RGB565, OMX_COLOR_Format16bitRGB565);
|
||||
MAP_FORMAT(V4L2_PIX_FMT_RGB24, OMX_COLOR_Format24bitRGB888);
|
||||
// TODO: найти устройство с RGB565 и протестить его.
|
||||
// FIXME: RGB24 не работает нормально, нижняя половина экрана зеленая.
|
||||
// FIXME: Китайский EasyCap тоже не работает, мусор на экране.
|
||||
// Вероятно обе проблемы вызваны некорректной реализацией OMX на пае.
|
||||
default: assert(0 && "Unsupported input format for OMX JPEG encoder");
|
||||
}
|
||||
|
||||
# undef MAP_FORMAT
|
||||
|
||||
if (component_set_portdef(&omx->encoder, &portdef) < 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <IL/OMX_Component.h>
|
||||
#include <interface/vcos/vcos_semaphore.h>
|
||||
|
||||
#include "../device.h"
|
||||
#include "../../device.h"
|
||||
|
||||
|
||||
#define OMX_MAX_ENCODERS 3
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <IL/OMX_IVCommon.h>
|
||||
#include <IL/OMX_Core.h>
|
||||
|
||||
#include "../tools.h"
|
||||
#include "../../tools.h"
|
||||
#include "formatters.h"
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
#include <IL/OMX_Core.h>
|
||||
#include <IL/OMX_Image.h>
|
||||
|
||||
#include "../logging.h"
|
||||
#include "../tools.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../tools.h"
|
||||
|
||||
|
||||
#define LOG_OMX_ERROR(_error, _msg, ...) { \
|
||||
22
src/http.c
22
src/http.c
@@ -257,22 +257,28 @@ static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg
|
||||
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;
|
||||
|
||||
A_PTHREAD_M_LOCK(&server->run->stream->encoder->run->mutex);
|
||||
enum encoder_type_t encoder_run_type = server->run->stream->encoder->run->type;
|
||||
unsigned encoder_run_quality = server->run->stream->encoder->run->quality;
|
||||
A_PTHREAD_M_UNLOCK(&server->run->stream->encoder->run->mutex);
|
||||
# define ENCODER(_next) server->run->stream->encoder->_next
|
||||
|
||||
A_PTHREAD_M_LOCK(&ENCODER(run->mutex));
|
||||
encoder_run_type = ENCODER(run->type);
|
||||
encoder_run_quality = ENCODER(run->quality);
|
||||
A_PTHREAD_M_UNLOCK(&ENCODER(run->mutex));
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"{\"ok\": true, \"result\": {"
|
||||
" \"encoder\": {\"fallback\": %s, \"quality\": %u},"
|
||||
" \"encoder\": {\"type\": \"%s\", \"fallback\": %s, \"quality\": %u},"
|
||||
" \"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u},"
|
||||
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
|
||||
bool_to_string(server->run->stream->encoder->type != encoder_run_type),
|
||||
encoder_type_to_string(encoder_run_type),
|
||||
bool_to_string(ENCODER(type) != 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),
|
||||
@@ -282,6 +288,9 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
server->run->exposed->queued_fps,
|
||||
server->run->stream_clients_count
|
||||
));
|
||||
|
||||
# undef ENCODER
|
||||
|
||||
for (struct stream_client_t * client = server->run->stream_clients; client != NULL; client = client->next) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"\"%s\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s, \"dual_final_frames\": %s}%s",
|
||||
@@ -293,6 +302,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
(client->next ? ", " : "")
|
||||
));
|
||||
}
|
||||
|
||||
assert(evbuffer_add_printf(buf, "}}}}"));
|
||||
|
||||
ADD_HEADER("Content-Type", "application/json");
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
#include "encoder.h"
|
||||
#include "stream.h"
|
||||
|
||||
#include "jpeg/encoder.h"
|
||||
|
||||
|
||||
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);
|
||||
|
||||
27
ustreamer.ebuild
Normal file
27
ustreamer.ebuild
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright 2019 Gentoo Authors
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
|
||||
EAPI=7
|
||||
|
||||
inherit git-r3
|
||||
|
||||
DESCRIPTION="µStreamer - Lightweight and fast MJPG-HTTP streamer"
|
||||
HOMEPAGE="https://github.com/pi-kvm/ustreamer"
|
||||
EGIT_REPO_URI="https://github.com/pi-kvm/ustreamer.git"
|
||||
|
||||
LICENSE="GPL-3"
|
||||
SLOT="0"
|
||||
KEYWORDS="~amd64"
|
||||
IUSE=""
|
||||
|
||||
DEPEND="
|
||||
>=dev-libs/libevent-2.1.8
|
||||
>=media-libs/libjpeg-turbo-1.5.3
|
||||
>=sys-apps/util-linux-2.33
|
||||
"
|
||||
RDEPEND="${DEPEND}"
|
||||
BDEPEND=""
|
||||
|
||||
src_install() {
|
||||
dobin ustreamer
|
||||
}
|
||||
Reference in New Issue
Block a user