mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-03-17 13:03:43 +00:00
mjpeg v4l2 encoding
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
#define CHAR_BIT 8
|
#define CHAR_BIT 8
|
||||||
#define WITH_OMX
|
|
||||||
#define WITH_GPIO
|
#define WITH_GPIO
|
||||||
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
|
#define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ }
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ For example, the recommended way of running µStreamer with Auvidea B101 on a Ra
|
|||||||
.RS
|
.RS
|
||||||
\fB\-\-format=uyvy \e\fR # Device input format
|
\fB\-\-format=uyvy \e\fR # Device input format
|
||||||
.nf
|
.nf
|
||||||
\fB\-\-encoder=omx \e\fR # Hardware encoding with OpenMAX
|
\fB\-\-encoder=v4l2 \e\fR # Hardware encoding with V4L2 M2M intraface
|
||||||
.nf
|
.nf
|
||||||
\fB\-\-workers=3 \e\fR # Maximum workers for OpenMAX
|
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
|
||||||
.nf
|
.nf
|
||||||
\fB\-\-persistent \e\fR # Don\'t re\-initialize device on timeout (for example when HDMI cable was disconnected)
|
\fB\-\-persistent \e\fR # Don\'t re\-initialize device on timeout (for example when HDMI cable was disconnected)
|
||||||
.nf
|
.nf
|
||||||
@@ -91,14 +91,14 @@ Use specified encoder. It may affect the number of workers.
|
|||||||
|
|
||||||
CPU ─ Software MJPG encoding (default).
|
CPU ─ Software MJPG encoding (default).
|
||||||
|
|
||||||
OMX ─ GPU hardware accelerated MJPG encoding with OpenMax (required \fBWITH_OMX\fR feature).
|
|
||||||
|
|
||||||
HW ─ Use pre-encoded MJPG frames directly from camera hardware.
|
HW ─ Use pre-encoded MJPG frames directly from camera hardware.
|
||||||
|
|
||||||
|
V4L2 ─ GPU-accelerated MJPG encoding.
|
||||||
|
|
||||||
NOOP ─ Don't compress MJPG stream (do nothing).
|
NOOP ─ Don't compress MJPG stream (do nothing).
|
||||||
.TP
|
.TP
|
||||||
.BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,...
|
.BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,...
|
||||||
It doesn't do anything. Still here for compatibility. Required \fBWITH_OMX\fR feature.
|
It doesn't do anything. Still here for compatibility.
|
||||||
.TP
|
.TP
|
||||||
.BR \-k\ \fIpath ", " \-\-blank\ \fIpath
|
.BR \-k\ \fIpath ", " \-\-blank\ \fIpath
|
||||||
Path to JPEG file that will be shown when the device is disconnected during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.
|
Path to JPEG file that will be shown when the device is disconnected during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.
|
||||||
|
|||||||
@@ -21,11 +21,6 @@ if [ -e /usr/bin/python3 ]; then
|
|||||||
depends+=(python)
|
depends+=(python)
|
||||||
makedepends+=(python-setuptools)
|
makedepends+=(python-setuptools)
|
||||||
fi
|
fi
|
||||||
if [ -e /opt/vc/include/IL/OMX_Core.h ]; then
|
|
||||||
depends+=(raspberrypi-firmware)
|
|
||||||
makedepends+=(raspberrypi-firmware)
|
|
||||||
_options="$_options WITH_OMX=1"
|
|
||||||
fi
|
|
||||||
if [ -e /usr/include/janus/plugins/plugin.h ];then
|
if [ -e /usr/include/janus/plugins/plugin.h ];then
|
||||||
depends+=(janus-gateway-pikvm)
|
depends+=(janus-gateway-pikvm)
|
||||||
makedepends+=(janus-gateway-pikvm)
|
makedepends+=(janus-gateway-pikvm)
|
||||||
|
|||||||
@@ -7,13 +7,12 @@ RUN apt-get update \
|
|||||||
gcc \
|
gcc \
|
||||||
libjpeg8-dev \
|
libjpeg8-dev \
|
||||||
libbsd-dev \
|
libbsd-dev \
|
||||||
libraspberrypi-dev \
|
|
||||||
libgpiod-dev \
|
libgpiod-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /build/ustreamer/
|
WORKDIR /build/ustreamer/
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN make -j5 WITH_OMX=1 WITH_GPIO=1
|
RUN make -j5 WITH_GPIO=1
|
||||||
RUN ["cross-build-end"]
|
RUN ["cross-build-end"]
|
||||||
|
|
||||||
FROM balenalib/raspberrypi3-debian:run as RUN
|
FROM balenalib/raspberrypi3-debian:run as RUN
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ RUN apt-get update \
|
|||||||
gcc \
|
gcc \
|
||||||
libjpeg8-dev \
|
libjpeg8-dev \
|
||||||
libbsd-dev \
|
libbsd-dev \
|
||||||
libraspberrypi-dev \
|
|
||||||
libgpiod-dev \
|
libgpiod-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /build/ustreamer/
|
WORKDIR /build/ustreamer/
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN make -j5 WITH_OMX=1 WITH_GPIO=1
|
RUN make -j5 WITH_GPIO=1
|
||||||
|
|
||||||
FROM balenalib/raspberrypi3-debian:run as RUN
|
FROM balenalib/raspberrypi3-debian:run as RUN
|
||||||
|
|
||||||
|
|||||||
13
src/Makefile
13
src/Makefile
@@ -5,9 +5,6 @@ CC ?= gcc
|
|||||||
CFLAGS ?= -O3
|
CFLAGS ?= -O3
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
|
|
||||||
RPI_VC_HEADERS ?= /opt/vc/include
|
|
||||||
RPI_VC_LIBS ?= /opt/vc/lib
|
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
_USTR = ustreamer.bin
|
_USTR = ustreamer.bin
|
||||||
@@ -26,6 +23,7 @@ _USTR_SRCS = $(shell ls \
|
|||||||
ustreamer/data/*.c \
|
ustreamer/data/*.c \
|
||||||
ustreamer/encoders/cpu/*.c \
|
ustreamer/encoders/cpu/*.c \
|
||||||
ustreamer/encoders/hw/*.c \
|
ustreamer/encoders/hw/*.c \
|
||||||
|
ustreamer/encoders/v4l2/*.c \
|
||||||
ustreamer/h264/*.c \
|
ustreamer/h264/*.c \
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,15 +41,6 @@ $(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
|||||||
endef
|
endef
|
||||||
|
|
||||||
|
|
||||||
ifneq ($(call optbool,$(WITH_OMX)),)
|
|
||||||
_USTR_LIBS += -lbcm_host -lvcos -lvcsm -lopenmaxil -L$(RPI_VC_LIBS)
|
|
||||||
override _CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
|
|
||||||
_USTR_SRCS += $(shell ls \
|
|
||||||
ustreamer/encoders/omx/*.c \
|
|
||||||
)
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||||
_USTR_LIBS += -lgpiod
|
_USTR_LIBS += -lgpiod
|
||||||
override _CFLAGS += -DWITH_GPIO
|
override _CFLAGS += -DWITH_GPIO
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ static const struct {
|
|||||||
} _ENCODER_TYPES[] = {
|
} _ENCODER_TYPES[] = {
|
||||||
{"CPU", ENCODER_TYPE_CPU},
|
{"CPU", ENCODER_TYPE_CPU},
|
||||||
{"HW", ENCODER_TYPE_HW},
|
{"HW", ENCODER_TYPE_HW},
|
||||||
# ifdef WITH_OMX
|
{"V4L2", ENCODER_TYPE_V4L2},
|
||||||
{"OMX", ENCODER_TYPE_OMX},
|
|
||||||
# endif
|
|
||||||
{"NOOP", ENCODER_TYPE_NOOP},
|
{"NOOP", ENCODER_TYPE_NOOP},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,22 +58,23 @@ encoder_s *encoder_init(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void encoder_destroy(encoder_s *enc) {
|
void encoder_destroy(encoder_s *enc) {
|
||||||
# ifdef WITH_OMX
|
if (ER(m2ms)) {
|
||||||
if (ER(omxs)) {
|
for (unsigned index = 0; index < ER(n_m2ms); ++index) {
|
||||||
for (unsigned index = 0; index < ER(n_omxs); ++index) {
|
if (ER(m2ms[index])) {
|
||||||
if (ER(omxs[index])) {
|
m2m_encoder_destroy(ER(m2ms[index]));
|
||||||
omx_encoder_destroy(ER(omxs[index]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(ER(omxs));
|
free(ER(m2ms));
|
||||||
}
|
}
|
||||||
# endif
|
|
||||||
A_MUTEX_DESTROY(&ER(mutex));
|
A_MUTEX_DESTROY(&ER(mutex));
|
||||||
free(enc->run);
|
free(enc->run);
|
||||||
free(enc);
|
free(enc);
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder_type_e encoder_parse_type(const char *str) {
|
encoder_type_e encoder_parse_type(const char *str) {
|
||||||
|
if (!strcasecmp(str, "OMX")) {
|
||||||
|
return ENCODER_TYPE_V4L2; // Just for compatibility
|
||||||
|
}
|
||||||
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
||||||
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
|
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
|
||||||
return _ENCODER_TYPES[index].type;
|
return _ENCODER_TYPES[index].type;
|
||||||
@@ -113,62 +112,27 @@ workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
|
|||||||
}
|
}
|
||||||
quality = DR(jpeg_quality);
|
quality = DR(jpeg_quality);
|
||||||
n_workers = 1;
|
n_workers = 1;
|
||||||
}
|
|
||||||
# ifdef WITH_OMX
|
} else if (type == ENCODER_TYPE_V4L2) {
|
||||||
else if (type == ENCODER_TYPE_OMX) {
|
LOG_DEBUG("Preparing V4L2 encoder ...");
|
||||||
if (align_size(DR(width), 32) != DR(width)) {
|
if (ER(m2ms) == NULL) {
|
||||||
LOG_INFO("Switching to CPU encoder: OMX can't handle width=%u ...", DR(width));
|
A_CALLOC(ER(m2ms), n_workers);
|
||||||
goto use_cpu;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG("Preparing OMX encoder ...");
|
|
||||||
|
|
||||||
if (n_workers > OMX_MAX_ENCODERS) {
|
|
||||||
LOG_INFO("OMX encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS);
|
|
||||||
n_workers = OMX_MAX_ENCODERS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ER(omxs) == NULL) {
|
|
||||||
A_CALLOC(ER(omxs), OMX_MAX_ENCODERS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||||
for (; ER(n_omxs) < n_workers; ++ER(n_omxs)) {
|
for (; ER(n_m2ms) < n_workers; ++ER(n_m2ms)) {
|
||||||
if ((ER(omxs[ER(n_omxs)]) = omx_encoder_init()) == NULL) {
|
char name[32];
|
||||||
LOG_ERROR("Can't initialize OMX encoder, falling back to CPU");
|
snprintf(name, 32, "JPEG-%u", ER(n_m2ms));
|
||||||
goto force_cpu;
|
m2m_option_s options[] = {{NULL, false, 0, 0}};
|
||||||
}
|
ER(m2ms[ER(n_m2ms)]) = m2m_encoder_init(name, "/dev/video11", V4L2_PIX_FMT_MJPEG, 0, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
frame_s frame = {0};
|
} else if (type == ENCODER_TYPE_NOOP) {
|
||||||
frame.width = DR(width);
|
|
||||||
frame.height = DR(height);
|
|
||||||
frame.format = DR(format);
|
|
||||||
frame.stride = DR(stride);
|
|
||||||
|
|
||||||
for (unsigned index = 0; index < ER(n_omxs); ++index) {
|
|
||||||
int omx_error = omx_encoder_prepare(ER(omxs[index]), &frame, quality);
|
|
||||||
if (omx_error == -2) {
|
|
||||||
goto use_cpu;
|
|
||||||
} else if (omx_error < 0) {
|
|
||||||
goto force_cpu;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# endif
|
|
||||||
else if (type == ENCODER_TYPE_NOOP) {
|
|
||||||
n_workers = 1;
|
n_workers = 1;
|
||||||
quality = 0;
|
quality = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
goto ok;
|
goto ok;
|
||||||
|
|
||||||
# ifdef WITH_OMX
|
|
||||||
force_cpu:
|
|
||||||
LOG_ERROR("Forced CPU encoder permanently");
|
|
||||||
cpu_forced = true;
|
|
||||||
# endif
|
|
||||||
|
|
||||||
use_cpu:
|
use_cpu:
|
||||||
type = ENCODER_TYPE_CPU;
|
type = ENCODER_TYPE_CPU;
|
||||||
quality = dev->jpeg_quality;
|
quality = dev->jpeg_quality;
|
||||||
@@ -248,19 +212,20 @@ static bool _worker_run_job(worker_s *wr) {
|
|||||||
if (ER(type) == ENCODER_TYPE_CPU) {
|
if (ER(type) == ENCODER_TYPE_CPU) {
|
||||||
LOG_VERBOSE("Compressing buffer using CPU");
|
LOG_VERBOSE("Compressing buffer using CPU");
|
||||||
cpu_encoder_compress(src, dest, ER(quality));
|
cpu_encoder_compress(src, dest, ER(quality));
|
||||||
|
|
||||||
} else if (ER(type) == ENCODER_TYPE_HW) {
|
} else if (ER(type) == ENCODER_TYPE_HW) {
|
||||||
LOG_VERBOSE("Compressing buffer using HW (just copying)");
|
LOG_VERBOSE("Compressing buffer using HW (just copying)");
|
||||||
hw_encoder_compress(src, dest);
|
hw_encoder_compress(src, dest);
|
||||||
}
|
|
||||||
# ifdef WITH_OMX
|
} else if (ER(type) == ENCODER_TYPE_V4L2) {
|
||||||
else if (ER(type) == ENCODER_TYPE_OMX) {
|
LOG_VERBOSE("Compressing buffer using V4L2");
|
||||||
LOG_VERBOSE("Compressing buffer using OMX");
|
if (!m2m_encoder_ensure_ready(ER(m2ms[wr->number]), src)) {
|
||||||
if (omx_encoder_compress(ER(omxs[wr->number]), src, dest) < 0) {
|
if (m2m_encoder_compress(ER(m2ms[wr->number]), src, dest, false) < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
# endif
|
} else if (ER(type) == ENCODER_TYPE_NOOP) {
|
||||||
else if (ER(type) == ENCODER_TYPE_NOOP) {
|
|
||||||
LOG_VERBOSE("Compressing buffer using NOOP (do nothing)");
|
LOG_VERBOSE("Compressing buffer using NOOP (do nothing)");
|
||||||
usleep(5000); // Просто чтобы работала логика desired_fps
|
usleep(5000); // Просто чтобы работала логика desired_fps
|
||||||
}
|
}
|
||||||
@@ -274,7 +239,6 @@ static bool _worker_run_job(worker_s *wr) {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
# ifdef WITH_OMX
|
|
||||||
error:
|
error:
|
||||||
LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
|
LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
|
||||||
LOG_ERROR("Error while compressing buffer, falling back to CPU");
|
LOG_ERROR("Error while compressing buffer, falling back to CPU");
|
||||||
@@ -282,7 +246,6 @@ static bool _worker_run_job(worker_s *wr) {
|
|||||||
ER(cpu_forced) = true;
|
ER(cpu_forced) = true;
|
||||||
A_MUTEX_UNLOCK(&ER(mutex));
|
A_MUTEX_UNLOCK(&ER(mutex));
|
||||||
return false;
|
return false;
|
||||||
# endif
|
|
||||||
|
|
||||||
# undef ER
|
# undef ER
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,30 +37,19 @@
|
|||||||
|
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "workers.h"
|
#include "workers.h"
|
||||||
|
#include "m2m.h"
|
||||||
|
|
||||||
#include "encoders/cpu/encoder.h"
|
#include "encoders/cpu/encoder.h"
|
||||||
#include "encoders/hw/encoder.h"
|
#include "encoders/hw/encoder.h"
|
||||||
|
|
||||||
#ifdef WITH_OMX
|
|
||||||
# include "encoders/omx/encoder.h"
|
|
||||||
# define ENCODER_TYPES_OMX_HINT ", OMX"
|
|
||||||
#else
|
|
||||||
# define ENCODER_TYPES_OMX_HINT ""
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
#define ENCODER_TYPES_STR "CPU, HW, V4L2, NOOP"
|
||||||
#define ENCODER_TYPES_STR \
|
|
||||||
"CPU, HW" \
|
|
||||||
ENCODER_TYPES_OMX_HINT \
|
|
||||||
", NOOP"
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
|
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
|
||||||
ENCODER_TYPE_CPU,
|
ENCODER_TYPE_CPU,
|
||||||
ENCODER_TYPE_HW,
|
ENCODER_TYPE_HW,
|
||||||
# ifdef WITH_OMX
|
ENCODER_TYPE_V4L2,
|
||||||
ENCODER_TYPE_OMX,
|
|
||||||
# endif
|
|
||||||
ENCODER_TYPE_NOOP,
|
ENCODER_TYPE_NOOP,
|
||||||
} encoder_type_e;
|
} encoder_type_e;
|
||||||
|
|
||||||
@@ -70,10 +59,8 @@ typedef struct {
|
|||||||
bool cpu_forced;
|
bool cpu_forced;
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
|
|
||||||
# ifdef WITH_OMX
|
unsigned n_m2ms;
|
||||||
unsigned n_omxs;
|
m2m_encoder_s **m2ms;
|
||||||
omx_encoder_s **omxs;
|
|
||||||
# endif
|
|
||||||
} encoder_runtime_s;
|
} encoder_runtime_s;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
# #
|
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
|
||||||
# #
|
|
||||||
# Copyright (C) 2018-2021 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 "component.h"
|
|
||||||
|
|
||||||
|
|
||||||
static int _omx_component_wait_port_changed(OMX_HANDLETYPE *comp, OMX_U32 port, OMX_BOOL enabled);
|
|
||||||
static int _omx_component_wait_state_changed(OMX_HANDLETYPE *comp, OMX_STATETYPE wanted);
|
|
||||||
|
|
||||||
|
|
||||||
int omx_component_enable_port(OMX_HANDLETYPE *comp, OMX_U32 port) {
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
|
|
||||||
LOG_DEBUG("Enabling OMX port %u ...", port);
|
|
||||||
if ((error = OMX_SendCommand(*comp, OMX_CommandPortEnable, port, NULL)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't enable OMX port %u", port);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return _omx_component_wait_port_changed(comp, port, OMX_TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
int omx_component_disable_port(OMX_HANDLETYPE *comp, OMX_U32 port) {
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
|
|
||||||
LOG_DEBUG("Disabling OMX port %u ...", port);
|
|
||||||
if ((error = OMX_SendCommand(*comp, OMX_CommandPortDisable, port, NULL)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't disable OMX port %u", port);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return _omx_component_wait_port_changed(comp, port, OMX_FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
int omx_component_get_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port) {
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
|
|
||||||
// cppcheck-suppress redundantPointerOp
|
|
||||||
OMX_INIT_STRUCTURE(*portdef);
|
|
||||||
portdef->nPortIndex = port;
|
|
||||||
|
|
||||||
LOG_DEBUG("Fetching OMX port %u definition ...", port);
|
|
||||||
if ((error = OMX_GetParameter(*comp, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't get OMX port %u definition", port);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int omx_component_set_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef) {
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
|
|
||||||
LOG_DEBUG("Writing OMX port %u definition ...", portdef->nPortIndex);
|
|
||||||
if ((error = OMX_SetParameter(*comp, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't set OMX port %u definition", portdef->nPortIndex);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int omx_component_set_state(OMX_HANDLETYPE *comp, OMX_STATETYPE state) {
|
|
||||||
const char *state_str = omx_state_to_string(state);
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
|
|
||||||
LOG_DEBUG("Switching component state to %s ...", state_str);
|
|
||||||
|
|
||||||
int retries = 50;
|
|
||||||
do {
|
|
||||||
error = OMX_SendCommand(*comp, OMX_CommandStateSet, state, NULL);
|
|
||||||
if (error == OMX_ErrorNone) {
|
|
||||||
return _omx_component_wait_state_changed(comp, state);
|
|
||||||
} else if (error == OMX_ErrorInsufficientResources && retries) {
|
|
||||||
// Иногда железо не инициализируется, хз почему, просто ретраим, со второй попытки сработает
|
|
||||||
if (retries > 45) {
|
|
||||||
LOG_VERBOSE("Can't switch OMX component state to %s, need to retry: %s",
|
|
||||||
state_str, omx_error_to_string(error));
|
|
||||||
} else {
|
|
||||||
LOG_ERROR_OMX(error, "Can't switch OMX component state to %s, need to retry", state_str);
|
|
||||||
}
|
|
||||||
retries -= 1;
|
|
||||||
usleep(8000);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (retries);
|
|
||||||
|
|
||||||
LOG_ERROR_OMX(error, "Can't switch OMX component state to %s", state_str);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int _omx_component_wait_port_changed(OMX_HANDLETYPE *comp, OMX_U32 port, OMX_BOOL enabled) {
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
|
|
||||||
OMX_PARAM_PORTDEFINITIONTYPE portdef;
|
|
||||||
OMX_INIT_STRUCTURE(portdef);
|
|
||||||
portdef.nPortIndex = port;
|
|
||||||
|
|
||||||
int retries = 50;
|
|
||||||
do {
|
|
||||||
if ((error = OMX_GetParameter(*comp, OMX_IndexParamPortDefinition, &portdef)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't get OMX port %u definition for waiting", port);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (portdef.bEnabled != enabled) {
|
|
||||||
LOG_DEBUG("Waiting for OMX %s port %u", (enabled ? "enabling" : "disabling"), port);
|
|
||||||
retries -= 1;
|
|
||||||
usleep(8000);
|
|
||||||
}
|
|
||||||
} while (portdef.bEnabled != enabled && retries);
|
|
||||||
|
|
||||||
LOG_DEBUG("OMX port %u %s", port, (enabled ? "enabled" : "disabled"));
|
|
||||||
return (portdef.bEnabled == enabled ? 0 : -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _omx_component_wait_state_changed(OMX_HANDLETYPE *comp, OMX_STATETYPE wanted) {
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
OMX_STATETYPE state;
|
|
||||||
|
|
||||||
int retries = 50;
|
|
||||||
do {
|
|
||||||
if ((error = OMX_GetState(*comp, &state)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Failed to get OMX component state");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state != wanted) {
|
|
||||||
LOG_DEBUG("Waiting when OMX component state changes to %s", omx_state_to_string(wanted));
|
|
||||||
retries -= 1;
|
|
||||||
usleep(8000);
|
|
||||||
}
|
|
||||||
} while (state != wanted && retries);
|
|
||||||
|
|
||||||
LOG_DEBUG("Switched OMX component state to %s", omx_state_to_string(wanted))
|
|
||||||
return (state == wanted ? 0 : -1);
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
# #
|
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
|
||||||
# #
|
|
||||||
# Copyright (C) 2018-2021 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 <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <IL/OMX_Core.h>
|
|
||||||
#include <IL/OMX_Component.h>
|
|
||||||
|
|
||||||
#include "../../../libs/logging.h"
|
|
||||||
|
|
||||||
#include "formatters.h"
|
|
||||||
|
|
||||||
|
|
||||||
#define OMX_INIT_STRUCTURE(_var) { \
|
|
||||||
memset(&(_var), 0, sizeof(_var)); \
|
|
||||||
(_var).nSize = sizeof(_var); \
|
|
||||||
(_var).nVersion.nVersion = OMX_VERSION; \
|
|
||||||
(_var).nVersion.s.nVersionMajor = OMX_VERSION_MAJOR; \
|
|
||||||
(_var).nVersion.s.nVersionMinor = OMX_VERSION_MINOR; \
|
|
||||||
(_var).nVersion.s.nRevision = OMX_VERSION_REVISION; \
|
|
||||||
(_var).nVersion.s.nStep = OMX_VERSION_STEP; \
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int omx_component_enable_port(OMX_HANDLETYPE *comp, OMX_U32 port);
|
|
||||||
int omx_component_disable_port(OMX_HANDLETYPE *comp, OMX_U32 port);
|
|
||||||
|
|
||||||
int omx_component_get_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port);
|
|
||||||
int omx_component_set_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef);
|
|
||||||
|
|
||||||
int omx_component_set_state(OMX_HANDLETYPE *comp, OMX_STATETYPE state);
|
|
||||||
@@ -1,444 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
# #
|
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
|
||||||
# #
|
|
||||||
# Copyright (C) 2018-2021 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 "encoder.h"
|
|
||||||
|
|
||||||
|
|
||||||
static const OMX_U32 _INPUT_PORT = 340;
|
|
||||||
static const OMX_U32 _OUTPUT_PORT = 341;
|
|
||||||
|
|
||||||
|
|
||||||
static int _omx_init_component(omx_encoder_s *omx);
|
|
||||||
static int _omx_init_disable_ports(omx_encoder_s *omx);
|
|
||||||
static int _omx_setup_input(omx_encoder_s *omx, const frame_s *frame);
|
|
||||||
static int _omx_setup_output(omx_encoder_s *omx, unsigned quality);
|
|
||||||
static int _omx_encoder_clear_ports(omx_encoder_s *omx);
|
|
||||||
|
|
||||||
static OMX_ERRORTYPE _omx_event_handler(
|
|
||||||
UNUSED OMX_HANDLETYPE comp,
|
|
||||||
OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1,
|
|
||||||
UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data);
|
|
||||||
|
|
||||||
static OMX_ERRORTYPE _omx_input_required_handler(
|
|
||||||
UNUSED OMX_HANDLETYPE comp,
|
|
||||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf);
|
|
||||||
|
|
||||||
static OMX_ERRORTYPE _omx_output_available_handler(
|
|
||||||
UNUSED OMX_HANDLETYPE comp,
|
|
||||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf);
|
|
||||||
|
|
||||||
|
|
||||||
omx_encoder_s *omx_encoder_init(void) {
|
|
||||||
// Some theory:
|
|
||||||
// - http://www.fourcc.org/yuv.php
|
|
||||||
// - https://kwasi-ich.de/blog/2017/11/26/omx/
|
|
||||||
// - https://github.com/hopkinskong/rpi-omx-jpeg-encode/blob/master/jpeg_bench.cpp
|
|
||||||
// - https://github.com/kwasmich/OMXPlayground/blob/master/omxJPEGEnc.c
|
|
||||||
// - https://github.com/gagle/raspberrypi-openmax-jpeg/blob/master/jpeg.c
|
|
||||||
// - https://www.raspberrypi.org/forums/viewtopic.php?t=154790
|
|
||||||
// - https://bitbucket.org/bensch128/omxjpegencode/src/master/jpeg_encoder.cpp
|
|
||||||
// - http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html
|
|
||||||
|
|
||||||
LOG_INFO("Initializing OMX encoder ...");
|
|
||||||
|
|
||||||
omx_encoder_s *omx;
|
|
||||||
A_CALLOC(omx, 1);
|
|
||||||
|
|
||||||
if (vcos_semaphore_create(&omx->handler_sem, "handler_sem", 0) != VCOS_SUCCESS) {
|
|
||||||
LOG_ERROR("Can't create VCOS semaphore");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
omx->i_handler_sem = true;
|
|
||||||
|
|
||||||
if (_omx_init_component(omx) < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_omx_init_disable_ports(omx) < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return omx;
|
|
||||||
|
|
||||||
error:
|
|
||||||
omx_encoder_destroy(omx);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void omx_encoder_destroy(omx_encoder_s *omx) {
|
|
||||||
LOG_INFO("Destroying OMX encoder ...");
|
|
||||||
|
|
||||||
omx_component_set_state(&omx->comp, OMX_StateIdle);
|
|
||||||
_omx_encoder_clear_ports(omx);
|
|
||||||
omx_component_set_state(&omx->comp, OMX_StateLoaded);
|
|
||||||
|
|
||||||
if (omx->i_handler_sem) {
|
|
||||||
vcos_semaphore_delete(&omx->handler_sem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (omx->i_encoder) {
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
if ((error = OMX_FreeHandle(omx->comp)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't free OMX.broadcom.image_encode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(omx);
|
|
||||||
}
|
|
||||||
|
|
||||||
int omx_encoder_prepare(omx_encoder_s *omx, const frame_s *frame, unsigned quality) {
|
|
||||||
if (align_size(frame->width, 32) != frame->width && frame_get_padding(frame) == 0) {
|
|
||||||
LOG_ERROR("%u %u", frame->width, frame->stride);
|
|
||||||
LOG_ERROR("OMX encoder can't handle unaligned width");
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
if (omx_component_set_state(&omx->comp, OMX_StateIdle) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (_omx_encoder_clear_ports(omx) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (_omx_setup_input(omx, frame) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (_omx_setup_output(omx, quality) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (omx_component_set_state(&omx->comp, OMX_StateExecuting) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int omx_encoder_compress(omx_encoder_s *omx, const frame_s *src, frame_s *dest) {
|
|
||||||
# define IN(_next) omx->input_buf->_next
|
|
||||||
# define OUT(_next) omx->output_buf->_next
|
|
||||||
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
|
|
||||||
if ((error = OMX_FillThisBuffer(omx->comp, omx->output_buf)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Failed to request filling of the output buffer on encoder");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
dest->width = align_size(src->width, 32);
|
|
||||||
dest->used = 0;
|
|
||||||
|
|
||||||
omx->output_available = false;
|
|
||||||
omx->input_required = true;
|
|
||||||
|
|
||||||
size_t slice_size = (IN(nAllocLen) < src->used ? IN(nAllocLen) : src->used);
|
|
||||||
size_t pos = 0;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (omx->failed) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (omx->output_available) {
|
|
||||||
omx->output_available = false;
|
|
||||||
|
|
||||||
frame_append_data(dest, OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
|
|
||||||
|
|
||||||
if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) {
|
|
||||||
OUT(nFlags) = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((error = OMX_FillThisBuffer(omx->comp, omx->output_buf)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Failed to request filling of the output buffer on encoder");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (omx->input_required) {
|
|
||||||
omx->input_required = false;
|
|
||||||
|
|
||||||
if (pos == src->used) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(IN(pBuffer), src->data + pos, slice_size);
|
|
||||||
IN(nOffset) = 0;
|
|
||||||
IN(nFilledLen) = slice_size;
|
|
||||||
|
|
||||||
pos += slice_size;
|
|
||||||
|
|
||||||
if (pos + slice_size > src->used) {
|
|
||||||
slice_size = src->used - pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((error = OMX_EmptyThisBuffer(omx->comp, omx->input_buf)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Failed to request emptying of the input buffer on encoder");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vcos_my_semwait("", &omx->handler_sem, 1) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# undef OUT
|
|
||||||
# undef IN
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _omx_init_component(omx_encoder_s *omx) {
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
|
|
||||||
OMX_CALLBACKTYPE callbacks;
|
|
||||||
MEMSET_ZERO(callbacks);
|
|
||||||
callbacks.EventHandler = _omx_event_handler;
|
|
||||||
callbacks.EmptyBufferDone = _omx_input_required_handler;
|
|
||||||
callbacks.FillBufferDone = _omx_output_available_handler;
|
|
||||||
|
|
||||||
LOG_DEBUG("Initializing OMX.broadcom.image_encode ...");
|
|
||||||
if ((error = OMX_GetHandle(&omx->comp, "OMX.broadcom.image_encode", omx, &callbacks)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't initialize OMX.broadcom.image_encode");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
omx->i_encoder = true;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _omx_init_disable_ports(omx_encoder_s *omx) {
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
OMX_INDEXTYPE types[] = {
|
|
||||||
OMX_IndexParamAudioInit, OMX_IndexParamVideoInit,
|
|
||||||
OMX_IndexParamImageInit, OMX_IndexParamOtherInit,
|
|
||||||
};
|
|
||||||
OMX_PORT_PARAM_TYPE ports;
|
|
||||||
|
|
||||||
OMX_INIT_STRUCTURE(ports);
|
|
||||||
if ((error = OMX_GetParameter(omx->comp, OMX_IndexParamImageInit, &ports)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't OMX_GetParameter(OMX_IndexParamImageInit)");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned index = 0; index < 4; ++index) {
|
|
||||||
if ((error = OMX_GetParameter(omx->comp, types[index], &ports)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't OMX_GetParameter(types[%u])", index);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
for (OMX_U32 port = ports.nStartPortNumber; port < ports.nStartPortNumber + ports.nPorts; ++port) {
|
|
||||||
if (omx_component_disable_port(&omx->comp, port) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _omx_setup_input(omx_encoder_s *omx, const frame_s *frame) {
|
|
||||||
LOG_DEBUG("Setting up OMX JPEG input port ...");
|
|
||||||
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
OMX_PARAM_PORTDEFINITIONTYPE portdef;
|
|
||||||
|
|
||||||
if (omx_component_get_portdef(&omx->comp, &portdef, _INPUT_PORT) < 0) {
|
|
||||||
LOG_ERROR("... first");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
# define IFMT(_next) portdef.format.image._next
|
|
||||||
IFMT(nFrameWidth) = align_size(frame->width, 32);
|
|
||||||
IFMT(nFrameHeight) = frame->height;
|
|
||||||
IFMT(nStride) = align_size(frame->width, 32) << 1;
|
|
||||||
IFMT(nSliceHeight) = align_size(frame->height, 16);
|
|
||||||
IFMT(bFlagErrorConcealment) = OMX_FALSE;
|
|
||||||
IFMT(eCompressionFormat) = OMX_IMAGE_CodingUnused;
|
|
||||||
portdef.nBufferSize = ((frame->width * frame->height) << 1) * 2;
|
|
||||||
switch (frame->format) {
|
|
||||||
// https://www.fourcc.org/yuv.php
|
|
||||||
// Also see comments inside OMX_IVCommon.h
|
|
||||||
case V4L2_PIX_FMT_YUYV: IFMT(eColorFormat) = OMX_COLOR_FormatYCbYCr; break;
|
|
||||||
case V4L2_PIX_FMT_UYVY: IFMT(eColorFormat) = OMX_COLOR_FormatCbYCrY; break;
|
|
||||||
case V4L2_PIX_FMT_RGB565: IFMT(eColorFormat) = OMX_COLOR_Format16bitRGB565; break;
|
|
||||||
case V4L2_PIX_FMT_RGB24: IFMT(eColorFormat) = OMX_COLOR_Format24bitRGB888; break;
|
|
||||||
// TODO: найти устройство с RGB565 и протестить его.
|
|
||||||
// FIXME: RGB24 не работает нормально, нижняя половина экрана зеленая.
|
|
||||||
// FIXME: Китайский EasyCap тоже не работает, мусор на экране.
|
|
||||||
// Вероятно обе проблемы вызваны некорректной реализацией OMX на пае.
|
|
||||||
default: assert(0 && "Unsupported format");
|
|
||||||
}
|
|
||||||
# undef IFMT
|
|
||||||
|
|
||||||
if (omx_component_set_portdef(&omx->comp, &portdef) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (omx_component_get_portdef(&omx->comp, &portdef, _INPUT_PORT) < 0) {
|
|
||||||
LOG_ERROR("... second");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (omx_component_enable_port(&omx->comp, _INPUT_PORT) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
omx->i_input_port_enabled = true;
|
|
||||||
|
|
||||||
if ((error = OMX_AllocateBuffer(omx->comp, &omx->input_buf, _INPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't allocate OMX JPEG input buffer");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _omx_setup_output(omx_encoder_s *omx, unsigned quality) {
|
|
||||||
LOG_DEBUG("Setting up OMX JPEG output port ...");
|
|
||||||
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
OMX_PARAM_PORTDEFINITIONTYPE portdef;
|
|
||||||
|
|
||||||
if (omx_component_get_portdef(&omx->comp, &portdef, _OUTPUT_PORT) < 0) {
|
|
||||||
LOG_ERROR("... first");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
# define OFMT(_next) portdef.format.image._next
|
|
||||||
OFMT(bFlagErrorConcealment) = OMX_FALSE;
|
|
||||||
OFMT(eCompressionFormat) = OMX_IMAGE_CodingJPEG;
|
|
||||||
OFMT(eColorFormat) = OMX_COLOR_FormatYCbYCr;
|
|
||||||
# undef OFMT
|
|
||||||
|
|
||||||
if (omx_component_set_portdef(&omx->comp, &portdef) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (omx_component_get_portdef(&omx->comp, &portdef, _OUTPUT_PORT) < 0) {
|
|
||||||
LOG_ERROR("... second");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
# define SET_PARAM(_key, _value) { \
|
|
||||||
if ((error = OMX_SetParameter(omx->comp, OMX_IndexParam##_key, _value)) != OMX_ErrorNone) { \
|
|
||||||
LOG_ERROR_OMX(error, "Can't set OMX param %s", #_key); \
|
|
||||||
return -1; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
OMX_CONFIG_BOOLEANTYPE exif;
|
|
||||||
OMX_INIT_STRUCTURE(exif);
|
|
||||||
exif.bEnabled = OMX_FALSE;
|
|
||||||
SET_PARAM(BrcmDisableEXIF, &exif);
|
|
||||||
|
|
||||||
OMX_PARAM_IJGSCALINGTYPE ijg;
|
|
||||||
OMX_INIT_STRUCTURE(ijg);
|
|
||||||
ijg.nPortIndex = _OUTPUT_PORT;
|
|
||||||
ijg.bEnabled = OMX_TRUE;
|
|
||||||
SET_PARAM(BrcmEnableIJGTableScaling, &ijg);
|
|
||||||
|
|
||||||
OMX_IMAGE_PARAM_QFACTORTYPE qfactor;
|
|
||||||
OMX_INIT_STRUCTURE(qfactor);
|
|
||||||
qfactor.nPortIndex = _OUTPUT_PORT;
|
|
||||||
qfactor.nQFactor = quality;
|
|
||||||
SET_PARAM(QFactor, &qfactor);
|
|
||||||
|
|
||||||
# undef SET_PARAM
|
|
||||||
|
|
||||||
if (omx_component_enable_port(&omx->comp, _OUTPUT_PORT) < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
omx->i_output_port_enabled = true;
|
|
||||||
|
|
||||||
if ((error = OMX_AllocateBuffer(omx->comp, &omx->output_buf, _OUTPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't allocate OMX JPEG output buffer");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _omx_encoder_clear_ports(omx_encoder_s *omx) {
|
|
||||||
OMX_ERRORTYPE error;
|
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
if (omx->i_output_port_enabled) {
|
|
||||||
retval -= omx_component_disable_port(&omx->comp, _OUTPUT_PORT);
|
|
||||||
omx->i_output_port_enabled = false;
|
|
||||||
}
|
|
||||||
if (omx->i_input_port_enabled) {
|
|
||||||
retval -= omx_component_disable_port(&omx->comp, _INPUT_PORT);
|
|
||||||
omx->i_input_port_enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (omx->input_buf) {
|
|
||||||
if ((error = OMX_FreeBuffer(omx->comp, _INPUT_PORT, omx->input_buf)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't free OMX JPEG input buffer");
|
|
||||||
// retval -= 1;
|
|
||||||
}
|
|
||||||
omx->input_buf = NULL;
|
|
||||||
}
|
|
||||||
if (omx->output_buf) {
|
|
||||||
if ((error = OMX_FreeBuffer(omx->comp, _OUTPUT_PORT, omx->output_buf)) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(error, "Can't free OMX JPEG output buffer");
|
|
||||||
// retval -= 1;
|
|
||||||
}
|
|
||||||
omx->output_buf = NULL;
|
|
||||||
}
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static OMX_ERRORTYPE _omx_event_handler(
|
|
||||||
UNUSED OMX_HANDLETYPE comp,
|
|
||||||
OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1,
|
|
||||||
UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data) {
|
|
||||||
|
|
||||||
// OMX calls this handler for all the events it emits
|
|
||||||
|
|
||||||
omx_encoder_s *omx = (omx_encoder_s *)v_omx;
|
|
||||||
|
|
||||||
if (event == OMX_EventError) {
|
|
||||||
LOG_ERROR_OMX((OMX_ERRORTYPE)data1, "OMX error event received");
|
|
||||||
omx->failed = true;
|
|
||||||
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
|
|
||||||
}
|
|
||||||
return OMX_ErrorNone;
|
|
||||||
}
|
|
||||||
|
|
||||||
static OMX_ERRORTYPE _omx_input_required_handler(
|
|
||||||
UNUSED OMX_HANDLETYPE comp,
|
|
||||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf) {
|
|
||||||
|
|
||||||
// Called by OMX when the encoder component requires
|
|
||||||
// the input buffer to be filled with RAW image data
|
|
||||||
|
|
||||||
omx_encoder_s *omx = (omx_encoder_s *)v_omx;
|
|
||||||
|
|
||||||
omx->input_required = true;
|
|
||||||
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
|
|
||||||
return OMX_ErrorNone;
|
|
||||||
}
|
|
||||||
|
|
||||||
static OMX_ERRORTYPE _omx_output_available_handler(
|
|
||||||
UNUSED OMX_HANDLETYPE comp,
|
|
||||||
OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf) {
|
|
||||||
|
|
||||||
// Called by OMX when the encoder component has filled
|
|
||||||
// the output buffer with JPEG data
|
|
||||||
|
|
||||||
omx_encoder_s *omx = (omx_encoder_s *)v_omx;
|
|
||||||
|
|
||||||
omx->output_available = true;
|
|
||||||
assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS);
|
|
||||||
return OMX_ErrorNone;
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
# #
|
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
|
||||||
# #
|
|
||||||
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
|
||||||
# #
|
|
||||||
# This program is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or #
|
|
||||||
# (at your option) any later version. #
|
|
||||||
# #
|
|
||||||
# This program is distributed in the hope that it will be useful, #
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
|
||||||
# GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the GNU General Public License #
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <linux/videodev2.h>
|
|
||||||
|
|
||||||
#include <IL/OMX_Core.h>
|
|
||||||
#include <IL/OMX_Component.h>
|
|
||||||
#include <IL/OMX_Broadcom.h>
|
|
||||||
#include <interface/vcos/vcos_semaphore.h>
|
|
||||||
|
|
||||||
#include "../../../libs/tools.h"
|
|
||||||
#include "../../../libs/logging.h"
|
|
||||||
#include "../../../libs/frame.h"
|
|
||||||
|
|
||||||
#include "vcos.h"
|
|
||||||
#include "formatters.h"
|
|
||||||
#include "component.h"
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef CFG_OMX_MAX_ENCODERS
|
|
||||||
# define CFG_OMX_MAX_ENCODERS 3 // Raspberry Pi limitation
|
|
||||||
#endif
|
|
||||||
#define OMX_MAX_ENCODERS ((unsigned)(CFG_OMX_MAX_ENCODERS))
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
OMX_HANDLETYPE comp;
|
|
||||||
OMX_BUFFERHEADERTYPE *input_buf;
|
|
||||||
OMX_BUFFERHEADERTYPE *output_buf;
|
|
||||||
bool input_required;
|
|
||||||
bool output_available;
|
|
||||||
bool failed;
|
|
||||||
VCOS_SEMAPHORE_T handler_sem;
|
|
||||||
|
|
||||||
bool i_handler_sem;
|
|
||||||
bool i_encoder;
|
|
||||||
bool i_input_port_enabled;
|
|
||||||
bool i_output_port_enabled;
|
|
||||||
} omx_encoder_s;
|
|
||||||
|
|
||||||
|
|
||||||
omx_encoder_s *omx_encoder_init(void);
|
|
||||||
void omx_encoder_destroy(omx_encoder_s *omx);
|
|
||||||
|
|
||||||
int omx_encoder_prepare(omx_encoder_s *omx, const frame_s *frame, unsigned quality);
|
|
||||||
int omx_encoder_compress(omx_encoder_s *omx, const frame_s *src, frame_s *dest);
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
# #
|
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
|
||||||
# #
|
|
||||||
# Copyright (C) 2018-2021 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 "formatters.h"
|
|
||||||
|
|
||||||
|
|
||||||
#define CASE_TO_STRING(_value) \
|
|
||||||
case _value: { return #_value; }
|
|
||||||
|
|
||||||
const char *omx_error_to_string(OMX_ERRORTYPE error) {
|
|
||||||
switch (error) {
|
|
||||||
CASE_TO_STRING(OMX_ErrorNone);
|
|
||||||
CASE_TO_STRING(OMX_ErrorInsufficientResources);
|
|
||||||
CASE_TO_STRING(OMX_ErrorUndefined);
|
|
||||||
CASE_TO_STRING(OMX_ErrorInvalidComponentName);
|
|
||||||
CASE_TO_STRING(OMX_ErrorComponentNotFound);
|
|
||||||
CASE_TO_STRING(OMX_ErrorInvalidComponent);
|
|
||||||
CASE_TO_STRING(OMX_ErrorBadParameter);
|
|
||||||
CASE_TO_STRING(OMX_ErrorNotImplemented);
|
|
||||||
CASE_TO_STRING(OMX_ErrorUnderflow);
|
|
||||||
CASE_TO_STRING(OMX_ErrorOverflow);
|
|
||||||
CASE_TO_STRING(OMX_ErrorHardware);
|
|
||||||
CASE_TO_STRING(OMX_ErrorInvalidState);
|
|
||||||
CASE_TO_STRING(OMX_ErrorStreamCorrupt);
|
|
||||||
CASE_TO_STRING(OMX_ErrorPortsNotCompatible);
|
|
||||||
CASE_TO_STRING(OMX_ErrorResourcesLost);
|
|
||||||
CASE_TO_STRING(OMX_ErrorNoMore);
|
|
||||||
CASE_TO_STRING(OMX_ErrorVersionMismatch);
|
|
||||||
CASE_TO_STRING(OMX_ErrorNotReady);
|
|
||||||
CASE_TO_STRING(OMX_ErrorTimeout);
|
|
||||||
CASE_TO_STRING(OMX_ErrorSameState);
|
|
||||||
CASE_TO_STRING(OMX_ErrorResourcesPreempted);
|
|
||||||
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringAllocation);
|
|
||||||
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringDeallocation);
|
|
||||||
CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringStop);
|
|
||||||
CASE_TO_STRING(OMX_ErrorIncorrectStateTransition);
|
|
||||||
CASE_TO_STRING(OMX_ErrorIncorrectStateOperation);
|
|
||||||
CASE_TO_STRING(OMX_ErrorUnsupportedSetting);
|
|
||||||
CASE_TO_STRING(OMX_ErrorUnsupportedIndex);
|
|
||||||
CASE_TO_STRING(OMX_ErrorBadPortIndex);
|
|
||||||
CASE_TO_STRING(OMX_ErrorPortUnpopulated);
|
|
||||||
CASE_TO_STRING(OMX_ErrorComponentSuspended);
|
|
||||||
CASE_TO_STRING(OMX_ErrorDynamicResourcesUnavailable);
|
|
||||||
CASE_TO_STRING(OMX_ErrorMbErrorsInFrame);
|
|
||||||
CASE_TO_STRING(OMX_ErrorFormatNotDetected);
|
|
||||||
CASE_TO_STRING(OMX_ErrorContentPipeOpenFailed);
|
|
||||||
CASE_TO_STRING(OMX_ErrorContentPipeCreationFailed);
|
|
||||||
CASE_TO_STRING(OMX_ErrorSeperateTablesUsed);
|
|
||||||
CASE_TO_STRING(OMX_ErrorTunnelingUnsupported);
|
|
||||||
CASE_TO_STRING(OMX_ErrorKhronosExtensions);
|
|
||||||
CASE_TO_STRING(OMX_ErrorVendorStartUnused);
|
|
||||||
CASE_TO_STRING(OMX_ErrorDiskFull);
|
|
||||||
CASE_TO_STRING(OMX_ErrorMaxFileSize);
|
|
||||||
CASE_TO_STRING(OMX_ErrorDrmUnauthorised);
|
|
||||||
CASE_TO_STRING(OMX_ErrorDrmExpired);
|
|
||||||
CASE_TO_STRING(OMX_ErrorDrmGeneral);
|
|
||||||
default: return "Unknown OMX error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *omx_state_to_string(OMX_STATETYPE state) {
|
|
||||||
switch (state) {
|
|
||||||
CASE_TO_STRING(OMX_StateInvalid);
|
|
||||||
CASE_TO_STRING(OMX_StateLoaded);
|
|
||||||
CASE_TO_STRING(OMX_StateIdle);
|
|
||||||
CASE_TO_STRING(OMX_StateExecuting);
|
|
||||||
CASE_TO_STRING(OMX_StatePause);
|
|
||||||
CASE_TO_STRING(OMX_StateWaitForResources);
|
|
||||||
// cppcheck-suppress constArgument
|
|
||||||
// cppcheck-suppress knownArgument
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
assert(0 && "Unsupported OMX state");
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef CASE_TO_STRING
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
# #
|
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
|
||||||
# #
|
|
||||||
# Copyright (C) 2018-2021 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 <assert.h>
|
|
||||||
|
|
||||||
#include <IL/OMX_IVCommon.h>
|
|
||||||
#include <IL/OMX_Core.h>
|
|
||||||
#include <IL/OMX_Image.h>
|
|
||||||
|
|
||||||
#include "../../../libs/tools.h"
|
|
||||||
#include "../../../libs/logging.h"
|
|
||||||
|
|
||||||
|
|
||||||
#define LOG_ERROR_OMX(_error, _msg, ...) { \
|
|
||||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, omx_error_to_string(_error)); \
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const char *omx_error_to_string(OMX_ERRORTYPE error);
|
|
||||||
const char *omx_state_to_string(OMX_STATETYPE state);
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
# #
|
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
|
||||||
# #
|
|
||||||
# Copyright (C) 2018-2021 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 "vcos.h"
|
|
||||||
|
|
||||||
|
|
||||||
int vcos_my_semwait(const char *prefix, VCOS_SEMAPHORE_T *sem, long double timeout) {
|
|
||||||
// vcos_semaphore_wait() can wait infinite
|
|
||||||
// vcos_semaphore_wait_timeout() is broken by design:
|
|
||||||
// - https://github.com/pikvm/ustreamer/issues/56
|
|
||||||
// - https://github.com/raspberrypi/userland/issues/658
|
|
||||||
// - The current approach is an ugly busyloop
|
|
||||||
// Три стула.
|
|
||||||
|
|
||||||
long double deadline_ts = get_now_monotonic() + timeout;
|
|
||||||
VCOS_STATUS_T sem_status;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
sem_status = vcos_semaphore_trywait(sem);
|
|
||||||
if (sem_status == VCOS_SUCCESS) {
|
|
||||||
return 0;
|
|
||||||
} else if (sem_status != VCOS_EAGAIN || get_now_monotonic() > deadline_ts) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (usleep(1000) < 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (sem_status) {
|
|
||||||
case VCOS_EAGAIN: LOG_ERROR("%sCan't wait VCOS semaphore: EAGAIN (timeout)", prefix); break;
|
|
||||||
case VCOS_EINVAL: LOG_ERROR("%sCan't wait VCOS semaphore: EINVAL", prefix); break;
|
|
||||||
default: LOG_ERROR("%sCan't wait VCOS semaphore: %d", prefix, sem_status); break;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
# #
|
|
||||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
|
||||||
# #
|
|
||||||
# Copyright (C) 2018-2021 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 <interface/vcos/vcos_semaphore.h>
|
|
||||||
|
|
||||||
#include "../../../libs/tools.h"
|
|
||||||
#include "../../../libs/logging.h"
|
|
||||||
|
|
||||||
|
|
||||||
int vcos_my_semwait(const char *prefix, VCOS_SEMAPHORE_T *sem, long double timeout);
|
|
||||||
@@ -159,7 +159,7 @@ static int _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
|||||||
E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
|
E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if (enc->fps > 0) { // TODO: Check this for MJPEG
|
||||||
struct v4l2_streamparm setfps = {0};
|
struct v4l2_streamparm setfps = {0};
|
||||||
setfps.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
setfps.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||||
setfps.parm.output.timeperframe.numerator = 1;
|
setfps.parm.output.timeperframe.numerator = 1;
|
||||||
@@ -316,7 +316,7 @@ int m2m_encoder_compress(m2m_encoder_s *enc, const frame_s *src, frame_s *dest,
|
|||||||
|
|
||||||
frame_copy_meta(src, dest);
|
frame_copy_meta(src, dest);
|
||||||
dest->encode_begin_ts = get_now_monotonic();
|
dest->encode_begin_ts = get_now_monotonic();
|
||||||
dest->format = enc->output_format;
|
dest->format = (enc->output_format == V4L2_PIX_FMT_MJPEG ? V4L2_PIX_FMT_JPEG : enc->output_format);
|
||||||
dest->stride = 0;
|
dest->stride = 0;
|
||||||
|
|
||||||
force_key = (force_key || enc->last_online != src->online);
|
force_key = (force_key || enc->last_online != src->online);
|
||||||
|
|||||||
@@ -35,10 +35,6 @@
|
|||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#ifdef WITH_OMX
|
|
||||||
# include <bcm_host.h>
|
|
||||||
# include <IL/OMX_Core.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "../libs/tools.h"
|
#include "../libs/tools.h"
|
||||||
#include "../libs/threading.h"
|
#include "../libs/threading.h"
|
||||||
@@ -124,23 +120,7 @@ int main(int argc, char *argv[]) {
|
|||||||
stream_s *stream = stream_init(dev, enc);
|
stream_s *stream = stream_init(dev, enc);
|
||||||
server_s *server = server_init(stream);
|
server_s *server = server_init(stream);
|
||||||
|
|
||||||
# ifdef WITH_OMX
|
|
||||||
bool i_bcm_host = false;
|
|
||||||
OMX_ERRORTYPE omx_error = OMX_ErrorUndefined;
|
|
||||||
# endif
|
|
||||||
|
|
||||||
if ((exit_code = options_parse(options, dev, enc, stream, server)) == 0) {
|
if ((exit_code = options_parse(options, dev, enc, stream, server)) == 0) {
|
||||||
# ifdef WITH_OMX
|
|
||||||
if (enc->type == ENCODER_TYPE_OMX) {
|
|
||||||
bcm_host_init();
|
|
||||||
i_bcm_host = true;
|
|
||||||
if ((omx_error = OMX_Init()) != OMX_ErrorNone) {
|
|
||||||
LOG_ERROR_OMX(omx_error, "Can't initialize OMX Core; forced CPU encoder");
|
|
||||||
enc->type = ENCODER_TYPE_CPU;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# endif
|
|
||||||
|
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
gpio_init();
|
gpio_init();
|
||||||
# endif
|
# endif
|
||||||
@@ -177,15 +157,6 @@ int main(int argc, char *argv[]) {
|
|||||||
device_destroy(dev);
|
device_destroy(dev);
|
||||||
options_destroy(options);
|
options_destroy(options);
|
||||||
|
|
||||||
# ifdef WITH_OMX
|
|
||||||
if (omx_error == OMX_ErrorNone) {
|
|
||||||
OMX_Deinit();
|
|
||||||
}
|
|
||||||
if (i_bcm_host) {
|
|
||||||
bcm_host_deinit();
|
|
||||||
}
|
|
||||||
# endif
|
|
||||||
|
|
||||||
if (exit_code == 0) {
|
if (exit_code == 0) {
|
||||||
LOG_INFO("Bye-bye");
|
LOG_INFO("Bye-bye");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,7 @@ enum _OPT_VALUES {
|
|||||||
_O_WORKERS = 'w',
|
_O_WORKERS = 'w',
|
||||||
_O_QUALITY = 'q',
|
_O_QUALITY = 'q',
|
||||||
_O_ENCODER = 'c',
|
_O_ENCODER = 'c',
|
||||||
# ifdef WITH_OMX
|
_O_GLITCHED_RESOLUTIONS = 'g', // Deprecated
|
||||||
_O_GLITCHED_RESOLUTIONS = 'g',
|
|
||||||
# endif
|
|
||||||
_O_BLANK = 'k',
|
_O_BLANK = 'k',
|
||||||
_O_LAST_AS_BLANK = 'K',
|
_O_LAST_AS_BLANK = 'K',
|
||||||
_O_SLOWDOWN = 'l',
|
_O_SLOWDOWN = 'l',
|
||||||
@@ -142,9 +140,7 @@ static const struct option _LONG_OPTS[] = {
|
|||||||
{"workers", required_argument, NULL, _O_WORKERS},
|
{"workers", required_argument, NULL, _O_WORKERS},
|
||||||
{"quality", required_argument, NULL, _O_QUALITY},
|
{"quality", required_argument, NULL, _O_QUALITY},
|
||||||
{"encoder", required_argument, NULL, _O_ENCODER},
|
{"encoder", required_argument, NULL, _O_ENCODER},
|
||||||
# ifdef WITH_OMX
|
{"glitched-resolutions", required_argument, NULL, _O_GLITCHED_RESOLUTIONS}, // Deprecated
|
||||||
{"glitched-resolutions", required_argument, NULL, _O_GLITCHED_RESOLUTIONS},
|
|
||||||
# endif
|
|
||||||
{"blank", required_argument, NULL, _O_BLANK},
|
{"blank", required_argument, NULL, _O_BLANK},
|
||||||
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
|
{"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK},
|
||||||
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
|
{"slowdown", no_argument, NULL, _O_SLOWDOWN},
|
||||||
@@ -378,9 +374,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
|||||||
case _O_WORKERS: OPT_NUMBER("--workers", enc->n_workers, 1, 32, 0);
|
case _O_WORKERS: OPT_NUMBER("--workers", enc->n_workers, 1, 32, 0);
|
||||||
case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0);
|
case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0);
|
||||||
case _O_ENCODER: OPT_PARSE("encoder type", enc->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
|
case _O_ENCODER: OPT_PARSE("encoder type", enc->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
|
||||||
# ifdef WITH_OMX
|
case _O_GLITCHED_RESOLUTIONS: break; // Deprecated
|
||||||
case _O_GLITCHED_RESOLUTIONS: break;
|
|
||||||
# endif
|
|
||||||
case _O_BLANK: OPT_SET(blank_path, optarg);
|
case _O_BLANK: OPT_SET(blank_path, optarg);
|
||||||
case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", stream->last_as_blank, 0, 86400, 0);
|
case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", stream->last_as_blank, 0, 86400, 0);
|
||||||
case _O_SLOWDOWN: OPT_SET(stream->slowdown, true);
|
case _O_SLOWDOWN: OPT_SET(stream->slowdown, true);
|
||||||
@@ -542,12 +536,6 @@ static int _parse_resolution(const char *str, unsigned *width, unsigned *height,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void _features(void) {
|
static void _features(void) {
|
||||||
# ifdef WITH_OMX
|
|
||||||
puts("+ WITH_OMX");
|
|
||||||
# else
|
|
||||||
puts("- WITH_OMX");
|
|
||||||
# endif
|
|
||||||
|
|
||||||
# ifdef WITH_GPIO
|
# ifdef WITH_GPIO
|
||||||
puts("+ WITH_GPIO");
|
puts("+ WITH_GPIO");
|
||||||
# else
|
# else
|
||||||
@@ -617,14 +605,10 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
|||||||
SAY(" -c|--encoder <type> ───────────────── Use specified encoder. It may affect the number of workers.");
|
SAY(" -c|--encoder <type> ───────────────── Use specified encoder. It may affect the number of workers.");
|
||||||
SAY(" Available:");
|
SAY(" Available:");
|
||||||
SAY(" * CPU ── Software MJPG encoding (default);");
|
SAY(" * CPU ── Software MJPG encoding (default);");
|
||||||
# ifdef WITH_OMX
|
SAY(" * HW ─── Use pre-encoded MJPG frames directly from camera hardware;");
|
||||||
SAY(" * OMX ── GPU hardware accelerated MJPG encoding with OpenMax;");
|
SAY(" * V4L2 ─ GPU-accelerated MJPG encoding using V4L2 M2M interface;");
|
||||||
# endif
|
|
||||||
SAY(" * HW ─── Use pre-encoded MJPG frames directly from camera hardware.");
|
|
||||||
SAY(" * NOOP ─ Don't compress MJPG stream (do nothing).\n");
|
SAY(" * NOOP ─ Don't compress MJPG stream (do nothing).\n");
|
||||||
# ifdef WITH_OMX
|
|
||||||
SAY(" -g|--glitched-resolutions <WxH,...> ─ It doesn't do anything. Still here for compatibility.\n");
|
SAY(" -g|--glitched-resolutions <WxH,...> ─ It doesn't do anything. Still here for compatibility.\n");
|
||||||
# endif
|
|
||||||
SAY(" -k|--blank <path> ─────────────────── Path to JPEG file that will be shown when the device is disconnected");
|
SAY(" -k|--blank <path> ─────────────────── Path to JPEG file that will be shown when the device is disconnected");
|
||||||
SAY(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n");
|
SAY(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n");
|
||||||
SAY(" -K|--last-as-blank <sec> ──────────── Show the last frame received from the camera after it was disconnected,");
|
SAY(" -K|--last-as-blank <sec> ──────────── Show the last frame received from the camera after it was disconnected,");
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ static workers_pool_s *_stream_init_one(stream_s *stream) {
|
|||||||
if (device_open(stream->dev) < 0) {
|
if (device_open(stream->dev) < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if (RUN(h264) && !is_jpeg(stream->dev->run->format)) {
|
if (stream->enc->type == ENCODER_TYPE_V4L2 || (RUN(h264) && !is_jpeg(stream->dev->run->format))) {
|
||||||
device_export_to_dma(stream->dev);
|
device_export_to_dma(stream->dev);
|
||||||
}
|
}
|
||||||
if (device_switch_capturing(stream->dev, true) < 0) {
|
if (device_switch_capturing(stream->dev, true) < 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user