mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-27 04:06:30 +00:00
Issue #83: Moved H.264 to h264_v4l2m2m
This commit is contained in:
@@ -221,7 +221,8 @@ Timeout for lock. Default: 1.
|
||||
|
||||
.SS "H264 sink options"
|
||||
.TP
|
||||
Available only if \fBWITH_OMX\fR feature enabled.
|
||||
.BR \-\-h264\-device\ \fI/dev/path
|
||||
Path to V4L2 mem-to-mem encoder device. Default: /dev/video11.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\ \fIname
|
||||
Use the specified shared memory object to sink H264 frames encoded by MMAL. Default: disabled.
|
||||
|
||||
@@ -26,6 +26,7 @@ _USTR_SRCS = $(shell ls \
|
||||
ustreamer/data/*.c \
|
||||
ustreamer/encoders/cpu/*.c \
|
||||
ustreamer/encoders/hw/*.c \
|
||||
ustreamer/h264/*.c \
|
||||
)
|
||||
|
||||
_DUMP_LIBS = $(_COMMON_LIBS)
|
||||
@@ -43,11 +44,10 @@ endef
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_OMX)),)
|
||||
_USTR_LIBS += -lbcm_host -lvcos -lvcsm -lopenmaxil -lmmal -lmmal_core -lmmal_util -lmmal_vc_client -lmmal_components -L$(RPI_VC_LIBS)
|
||||
_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 \
|
||||
ustreamer/h264/*.c \
|
||||
)
|
||||
endif
|
||||
|
||||
|
||||
@@ -178,16 +178,10 @@ void device_close(device_s *dev) {
|
||||
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
|
||||
# define HW(_next) RUN(hw_bufs)[index]._next
|
||||
|
||||
# ifdef WITH_OMX
|
||||
if (HW(vcsm_handle) > 0) {
|
||||
vcsm_free(HW(vcsm_handle));
|
||||
HW(vcsm_handle) = -1;
|
||||
}
|
||||
if (HW(dma_fd) >= 0) {
|
||||
close(HW(dma_fd));
|
||||
HW(dma_fd) = -1;
|
||||
}
|
||||
# endif
|
||||
|
||||
if (dev->io_method == V4L2_MEMORY_MMAP) {
|
||||
if (HW(raw.allocated) > 0 && HW(raw.data) != MAP_FAILED) {
|
||||
@@ -220,10 +214,8 @@ void device_close(device_s *dev) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_OMX
|
||||
int device_export_to_vcsm(device_s *dev) {
|
||||
int device_export_to_dma(device_s *dev) {
|
||||
# define DMA_FD RUN(hw_bufs[index].dma_fd)
|
||||
# define VCSM_HANDLE RUN(hw_bufs[index].vcsm_handle)
|
||||
|
||||
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
|
||||
struct v4l2_exportbuffer exp = {0};
|
||||
@@ -236,24 +228,12 @@ int device_export_to_vcsm(device_s *dev) {
|
||||
goto error;
|
||||
}
|
||||
DMA_FD = exp.fd;
|
||||
|
||||
LOG_DEBUG("Importing DMA buffer fd=%d into VCSM ...", DMA_FD);
|
||||
int vcsm_handle = vcsm_import_dmabuf(DMA_FD, "v4l2_buf");
|
||||
if (vcsm_handle <= 0) {
|
||||
LOG_PERROR("Unable to import DMA buffer fd=%d into VCSM", DMA_FD);
|
||||
goto error;
|
||||
}
|
||||
VCSM_HANDLE = vcsm_handle;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
for (unsigned index = 0; index < RUN(n_bufs); ++index) {
|
||||
if (VCSM_HANDLE > 0) {
|
||||
vcsm_free(VCSM_HANDLE);
|
||||
VCSM_HANDLE = -1;
|
||||
}
|
||||
if (DMA_FD >= 0) {
|
||||
close(DMA_FD);
|
||||
DMA_FD = -1;
|
||||
@@ -261,10 +241,8 @@ int device_export_to_vcsm(device_s *dev) {
|
||||
}
|
||||
return -1;
|
||||
|
||||
# undef VCSM_HANDLE
|
||||
# undef DMA_FD
|
||||
}
|
||||
#endif
|
||||
|
||||
int device_switch_capturing(device_s *dev, bool enable) {
|
||||
if (enable != RUN(capturing)) {
|
||||
@@ -709,10 +687,7 @@ static int _device_open_io_method_mmap(device_s *dev) {
|
||||
|
||||
# define HW(_next) RUN(hw_bufs)[RUN(n_bufs)]._next
|
||||
|
||||
# ifdef WITH_OMX
|
||||
HW(dma_fd) = -1;
|
||||
HW(vcsm_handle) = -1;
|
||||
# endif
|
||||
|
||||
A_MUTEX_INIT(&HW(grabbed_mutex));
|
||||
|
||||
|
||||
@@ -40,9 +40,6 @@
|
||||
#include <pthread.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
#ifdef WITH_OMX
|
||||
# include <interface/vcsm/user-vcsm.h>
|
||||
#endif
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
@@ -73,12 +70,8 @@
|
||||
typedef struct {
|
||||
frame_s raw;
|
||||
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
# ifdef WITH_OMX
|
||||
int dma_fd;
|
||||
int vcsm_handle;
|
||||
# endif
|
||||
struct v4l2_buffer buf;
|
||||
int dma_fd;
|
||||
|
||||
pthread_mutex_t grabbed_mutex;
|
||||
bool grabbed;
|
||||
@@ -159,9 +152,7 @@ int device_parse_io_method(const char *str);
|
||||
int device_open(device_s *dev);
|
||||
void device_close(device_s *dev);
|
||||
|
||||
#ifdef WITH_OMX
|
||||
int device_export_to_vcsm(device_s *dev);
|
||||
#endif
|
||||
int device_export_to_dma(device_s *dev);
|
||||
int device_switch_capturing(device_s *dev, bool enable);
|
||||
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error);
|
||||
int device_grab_buffer(device_s *dev, hw_buffer_s **hw);
|
||||
|
||||
@@ -23,80 +23,54 @@
|
||||
#include "encoder.h"
|
||||
|
||||
|
||||
static int _h264_encoder_init_buffers(
|
||||
h264_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
h264_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma);
|
||||
|
||||
static void _h264_encoder_cleanup(h264_encoder_s *enc);
|
||||
static int _h264_encoder_compress_raw(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key);
|
||||
|
||||
static void _mmal_callback(MMAL_WRAPPER_T *wrapper);
|
||||
static const char *_mmal_error_to_string(MMAL_STATUS_T error);
|
||||
static int _h264_encoder_compress_raw(
|
||||
h264_encoder_s *enc, const frame_s *src, int src_dma_fd,
|
||||
frame_s *dest, bool force_key);
|
||||
|
||||
|
||||
#define LOG_ERROR_MMAL(_error, _msg, ...) { \
|
||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _mmal_error_to_string(_error)); \
|
||||
}
|
||||
|
||||
|
||||
h264_encoder_s *h264_encoder_init(unsigned bitrate, unsigned gop, unsigned fps) {
|
||||
LOG_INFO("H264: Initializing MMAL encoder ...");
|
||||
h264_encoder_s *h264_encoder_init(const char *path, unsigned bitrate, unsigned gop, unsigned fps) {
|
||||
LOG_INFO("H264: Initializing encoder ...");
|
||||
LOG_INFO("H264: Using bitrate: %u Kbps", bitrate);
|
||||
LOG_INFO("H264: Using GOP: %u", gop);
|
||||
|
||||
h264_encoder_s *enc;
|
||||
A_CALLOC(enc, 1);
|
||||
assert(enc->path = strdup(path));
|
||||
enc->bitrate = bitrate; // Kbps
|
||||
enc->gop = gop; // Interval between keyframes
|
||||
enc->fps = fps;
|
||||
|
||||
enc->last_online = -1;
|
||||
|
||||
if (vcos_semaphore_create(&enc->handler_sem, "h264_handler_sem", 0) != VCOS_SUCCESS) {
|
||||
LOG_PERROR("H264: Can't create VCOS semaphore");
|
||||
goto error;
|
||||
}
|
||||
enc->i_handler_sem = true;
|
||||
|
||||
MMAL_STATUS_T error = mmal_wrapper_create(&enc->wrapper, MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER);
|
||||
if (error != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't create MMAL wrapper");
|
||||
enc->wrapper = NULL;
|
||||
goto error;
|
||||
}
|
||||
enc->wrapper->user_data = (void *)enc;
|
||||
enc->wrapper->callback = _mmal_callback;
|
||||
|
||||
return enc;
|
||||
|
||||
error:
|
||||
h264_encoder_destroy(enc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void h264_encoder_destroy(h264_encoder_s *enc) {
|
||||
LOG_INFO("H264: Destroying MMAL encoder ...");
|
||||
|
||||
LOG_INFO("H264: Destroying encoder ...");
|
||||
_h264_encoder_cleanup(enc);
|
||||
|
||||
if (enc->wrapper) {
|
||||
MMAL_STATUS_T error = mmal_wrapper_destroy(enc->wrapper);
|
||||
if (error != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't destroy MMAL encoder");
|
||||
}
|
||||
}
|
||||
|
||||
if (enc->i_handler_sem) {
|
||||
vcos_semaphore_delete(&enc->handler_sem);
|
||||
}
|
||||
|
||||
free(enc->path);
|
||||
free(enc);
|
||||
}
|
||||
|
||||
bool h264_encoder_is_prepared_for(h264_encoder_s *enc, const frame_s *frame, bool zero_copy) {
|
||||
bool h264_encoder_is_prepared_for(h264_encoder_s *enc, const frame_s *frame, bool dma) {
|
||||
# define EQ(_field) (enc->_field == frame->_field)
|
||||
return (EQ(width) && EQ(height) && EQ(format) && EQ(stride) && (enc->zero_copy == zero_copy));
|
||||
return (EQ(width) && EQ(height) && EQ(format) && EQ(stride) && (enc->dma == dma));
|
||||
# undef EQ
|
||||
}
|
||||
|
||||
int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_copy) {
|
||||
LOG_INFO("H264: Configuring MMAL encoder: zero_copy=%d ...", zero_copy);
|
||||
#define ENCODER_XIOCTL(_request, _value, _msg, ...) { \
|
||||
if (xioctl(enc->fd, _request, _value) < 0) { \
|
||||
LOG_PERROR(_msg, ##__VA_ARGS__); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool dma) {
|
||||
LOG_INFO("H264: Configuring encoder: dma=%d ...", dma);
|
||||
|
||||
_h264_encoder_cleanup(enc);
|
||||
|
||||
@@ -104,121 +78,86 @@ int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_co
|
||||
enc->height = frame->height;
|
||||
enc->format = frame->format;
|
||||
enc->stride = frame->stride;
|
||||
enc->zero_copy = zero_copy;
|
||||
enc->dma = dma;
|
||||
|
||||
if (align_size(frame->width, 32) != frame->width && frame_get_padding(frame) == 0) {
|
||||
LOG_ERROR("H264: MMAL encoder can't handle unaligned width");
|
||||
if ((enc->fd = open(enc->path, O_RDWR)) < 0) {
|
||||
LOG_PERROR("H264: Can't open encoder device");
|
||||
goto error;
|
||||
}
|
||||
|
||||
MMAL_STATUS_T error;
|
||||
|
||||
# define PREPARE_PORT(_id) { \
|
||||
enc->_id##_port = enc->wrapper->_id[0]; \
|
||||
if (enc->_id##_port->is_enabled) { \
|
||||
if ((error = mmal_wrapper_port_disable(enc->_id##_port)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "H264: Can't disable MMAL %s port while configuring", #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
# define COMMIT_PORT(_id) { \
|
||||
if ((error = mmal_port_format_commit(enc->_id##_port)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "H264: Can't commit MMAL %s port", #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
# define SET_PORT_PARAM(_id, _type, _key, _value) { \
|
||||
if ((error = mmal_port_parameter_set_##_type(enc->_id##_port, MMAL_PARAMETER_##_key, _value)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "H264: Can't set %s for the %s port", #_key, #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
# define ENABLE_PORT(_id) { \
|
||||
if ((error = mmal_wrapper_port_enable(enc->_id##_port, MMAL_WRAPPER_FLAG_PAYLOAD_ALLOCATE)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "H264: Can't enable MMAL %s port", #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
{
|
||||
PREPARE_PORT(input);
|
||||
|
||||
# define IFMT(_next) enc->input_port->format->_next
|
||||
IFMT(type) = MMAL_ES_TYPE_VIDEO;
|
||||
switch (frame->format) {
|
||||
case V4L2_PIX_FMT_YUYV: IFMT(encoding) = MMAL_ENCODING_YUYV; break;
|
||||
case V4L2_PIX_FMT_UYVY: IFMT(encoding) = MMAL_ENCODING_UYVY; break;
|
||||
case V4L2_PIX_FMT_RGB565: IFMT(encoding) = MMAL_ENCODING_RGB16; break;
|
||||
case V4L2_PIX_FMT_RGB24: IFMT(encoding) = MMAL_ENCODING_RGB24; break;
|
||||
default: assert(0 && "Unsupported pixelformat");
|
||||
}
|
||||
IFMT(es->video.width) = align_size(frame->width, 32);
|
||||
IFMT(es->video.height) = align_size(frame->height, 16);
|
||||
IFMT(es->video.crop.x) = 0;
|
||||
IFMT(es->video.crop.y) = 0;
|
||||
IFMT(es->video.crop.width) = frame->width;
|
||||
IFMT(es->video.crop.height) = frame->height;
|
||||
IFMT(flags) = MMAL_ES_FORMAT_FLAG_FRAMED;
|
||||
enc->input_port->buffer_size = 1000 * 1000;
|
||||
enc->input_port->buffer_num = enc->input_port->buffer_num_recommended * 4;
|
||||
# undef IFMT
|
||||
|
||||
COMMIT_PORT(input);
|
||||
SET_PORT_PARAM(input, boolean, ZERO_COPY, zero_copy);
|
||||
}
|
||||
|
||||
{
|
||||
PREPARE_PORT(output);
|
||||
|
||||
# define OFMT(_next) enc->output_port->format->_next
|
||||
OFMT(type) = MMAL_ES_TYPE_VIDEO;
|
||||
OFMT(encoding) = MMAL_ENCODING_H264;
|
||||
OFMT(encoding_variant) = MMAL_ENCODING_VARIANT_H264_DEFAULT;
|
||||
OFMT(bitrate) = enc->bitrate * 1000;
|
||||
OFMT(es->video.frame_rate.num) = enc->fps;
|
||||
OFMT(es->video.frame_rate.den) = 1;
|
||||
enc->output_port->buffer_size = enc->output_port->buffer_size_recommended * 4;
|
||||
enc->output_port->buffer_num = enc->output_port->buffer_num_recommended;
|
||||
# undef OFMT
|
||||
|
||||
COMMIT_PORT(output);
|
||||
{
|
||||
MMAL_PARAMETER_VIDEO_PROFILE_T profile;
|
||||
MEMSET_ZERO(profile);
|
||||
profile.hdr.id = MMAL_PARAMETER_PROFILE;
|
||||
profile.hdr.size = sizeof(profile);
|
||||
// http://blog.mediacoderhq.com/h264-profiles-and-levels
|
||||
profile.profile[0].profile = MMAL_VIDEO_PROFILE_H264_CONSTRAINED_BASELINE;
|
||||
profile.profile[0].level = MMAL_VIDEO_LEVEL_H264_4; // Supports 1080p
|
||||
if ((error = mmal_port_parameter_set(enc->output_port, &profile.hdr)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't set MMAL_PARAMETER_PROFILE for the output port");
|
||||
goto error;
|
||||
# define SET_OPTION(_cid, _value) { \
|
||||
struct v4l2_control _ctl = {0}; \
|
||||
_ctl.id = _cid; \
|
||||
_ctl.value = _value; \
|
||||
LOG_DEBUG("H264: Configuring option %s ...", #_cid); \
|
||||
ENCODER_XIOCTL(VIDIOC_S_CTRL, &_ctl, "H264: Can't set option " #_cid); \
|
||||
}
|
||||
}
|
||||
|
||||
SET_PORT_PARAM(output, boolean, ZERO_COPY, MMAL_TRUE);
|
||||
SET_PORT_PARAM(output, uint32, INTRAPERIOD, enc->gop);
|
||||
SET_PORT_PARAM(output, uint32, NALUNITFORMAT, MMAL_VIDEO_NALUNITFORMAT_STARTCODES);
|
||||
SET_PORT_PARAM(output, boolean, MINIMISE_FRAGMENTATION, MMAL_TRUE);
|
||||
SET_PORT_PARAM(output, uint32, MB_ROWS_PER_SLICE, 0);
|
||||
SET_PORT_PARAM(output, boolean, VIDEO_IMMUTABLE_INPUT, MMAL_TRUE);
|
||||
SET_PORT_PARAM(output, boolean, VIDEO_DROPPABLE_PFRAMES, MMAL_FALSE);
|
||||
SET_PORT_PARAM(output, boolean, VIDEO_ENCODE_INLINE_HEADER, MMAL_TRUE); // SPS/PPS: https://github.com/raspberrypi/userland/issues/443
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_BIT_RATE, enc->bitrate * 1000);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_PEAK_RATE, enc->bitrate * 1000);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_MIN_QUANT, 16);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_MAX_QUANT, 34);
|
||||
// Этот параметр с этим значением фризит кодирование изображения из черно-белой консоли
|
||||
// SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_FRAME_LIMIT_BITS, 1000000);
|
||||
SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_H264_AU_DELIMITERS, MMAL_FALSE);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate * 1000);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, enc->gop);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, 1);
|
||||
|
||||
# undef SET_OPTION
|
||||
}
|
||||
|
||||
ENABLE_PORT(input);
|
||||
ENABLE_PORT(output);
|
||||
{
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
fmt.fmt.pix_mp.width = frame->width;
|
||||
fmt.fmt.pix_mp.height = frame->height;
|
||||
fmt.fmt.pix_mp.pixelformat = frame->format;
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // libcamera currently has no means to request the right colour space
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
LOG_DEBUG("H264: Configuring input format ...");
|
||||
ENCODER_XIOCTL(VIDIOC_S_FMT, &fmt, "H264: Can't set input format");
|
||||
}
|
||||
|
||||
{
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
fmt.fmt.pix_mp.width = frame->width;
|
||||
fmt.fmt.pix_mp.height = frame->height;
|
||||
fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264;
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT;
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0;
|
||||
fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10;
|
||||
LOG_DEBUG("H264: Configuring output format ...");
|
||||
ENCODER_XIOCTL(VIDIOC_S_FMT, &fmt, "H264: Can't set output format");
|
||||
}
|
||||
|
||||
{
|
||||
struct v4l2_streamparm setfps = {0};
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
setfps.parm.output.timeperframe.numerator = 1;
|
||||
setfps.parm.output.timeperframe.denominator = enc->fps;
|
||||
LOG_DEBUG("H264: Configuring input FPS ...");
|
||||
ENCODER_XIOCTL(VIDIOC_S_PARM, &setfps, "H264: Can't set input FPS");
|
||||
}
|
||||
|
||||
if (_h264_encoder_init_buffers(enc, (dma ? "input-dma" : "input"), V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
|
||||
&enc->input_bufs, &enc->n_input_bufs, dma) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_h264_encoder_init_buffers(enc, "output", V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
|
||||
&enc->output_bufs, &enc->n_output_bufs, false) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
{
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
LOG_DEBUG("H264: Starting input ...");
|
||||
ENCODER_XIOCTL(VIDIOC_STREAMON, &type, "H264: Can't start input");
|
||||
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
LOG_DEBUG("H264: Starting output ...");
|
||||
ENCODER_XIOCTL(VIDIOC_STREAMON, &type, "H264: Can't start output");
|
||||
}
|
||||
|
||||
enc->ready = true;
|
||||
return 0;
|
||||
@@ -227,45 +166,126 @@ int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_co
|
||||
_h264_encoder_cleanup(enc);
|
||||
LOG_ERROR("H264: Encoder destroyed due an error (prepare)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# undef ENABLE_PORT
|
||||
# undef SET_PORT_PARAM
|
||||
# undef COMMIT_PORT
|
||||
# undef PREPARE_PORT
|
||||
static int _h264_encoder_init_buffers(
|
||||
h264_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
h264_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma) {
|
||||
|
||||
LOG_DEBUG("H264: Initializing %s buffers: ...", name);
|
||||
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = 1;
|
||||
req.type = type;
|
||||
req.memory = (dma ? V4L2_MEMORY_DMABUF : V4L2_MEMORY_MMAP);
|
||||
|
||||
LOG_DEBUG("H264: Requesting %u %s buffers ...", req.count, name);
|
||||
ENCODER_XIOCTL(VIDIOC_REQBUFS, &req, "H264: Can't request %s buffers", name);
|
||||
if (req.count < 1) {
|
||||
LOG_ERROR("H264: Insufficient %s buffer memory: %u", name, req.count);
|
||||
goto error;
|
||||
}
|
||||
LOG_DEBUG("H264: Got %u %s buffers", req.count, name);
|
||||
|
||||
if (dma) {
|
||||
*n_bufs_ptr = req.count;
|
||||
} else {
|
||||
A_CALLOC(*bufs_ptr, req.count);
|
||||
for (*n_bufs_ptr = 0; *n_bufs_ptr < req.count; ++(*n_bufs_ptr)) {
|
||||
struct v4l2_buffer buf = {0};
|
||||
struct v4l2_plane plane = {0};
|
||||
buf.type = type;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = *n_bufs_ptr;
|
||||
buf.length = 1;
|
||||
buf.m.planes = &plane;
|
||||
|
||||
LOG_DEBUG("H264: Querying %s buffer %u ...", name, *n_bufs_ptr);
|
||||
ENCODER_XIOCTL(VIDIOC_QUERYBUF, &buf, "H264: Can't query %s buffer %u", name, *n_bufs_ptr);
|
||||
|
||||
LOG_DEBUG("H264: Mapping %s buffer %u ...", name, *n_bufs_ptr);
|
||||
if (((*bufs_ptr)[*n_bufs_ptr].data = mmap(
|
||||
NULL,
|
||||
plane.length,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
enc->fd,
|
||||
plane.m.mem_offset
|
||||
)) == MAP_FAILED) {
|
||||
LOG_PERROR("H264: Can't map %s buffer %u", name, *n_bufs_ptr);
|
||||
goto error;
|
||||
}
|
||||
(*bufs_ptr)[*n_bufs_ptr].allocated = plane.length;
|
||||
|
||||
LOG_DEBUG("H264: Queuing %s buffer %u ...", name, *n_bufs_ptr);
|
||||
ENCODER_XIOCTL(VIDIOC_QBUF, &buf, "H264: Can't queue %s buffer %u", name, *n_bufs_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void _h264_encoder_cleanup(h264_encoder_s *enc) {
|
||||
MMAL_STATUS_T error;
|
||||
|
||||
# define DISABLE_PORT(_id) { \
|
||||
if (enc->_id##_port) { \
|
||||
if ((error = mmal_wrapper_port_disable(enc->_id##_port)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "H264: Can't disable MMAL %s port", #_id); \
|
||||
if (enc->ready) {
|
||||
# define STOP_STREAM(_name, _type) { \
|
||||
enum v4l2_buf_type _type_var = _type; \
|
||||
LOG_DEBUG("H264: Stopping %s ...", _name); \
|
||||
if (xioctl(enc->fd, VIDIOC_STREAMOFF, &_type_var) < 0) { \
|
||||
LOG_PERROR("H264: Can't stop %s", _name); \
|
||||
} \
|
||||
}
|
||||
|
||||
STOP_STREAM("output", V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
|
||||
STOP_STREAM("input", V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
|
||||
|
||||
# undef STOP_STREAM
|
||||
}
|
||||
|
||||
# define DESTROY_BUFFERS(_target) { \
|
||||
if (enc->_target##_bufs) { \
|
||||
for (unsigned index = 0; index < enc->n_##_target##_bufs; ++index) { \
|
||||
if (enc->_target##_bufs[index].allocated > 0 && enc->_target##_bufs[index].data != MAP_FAILED) { \
|
||||
if (munmap(enc->_target##_bufs[index].data, enc->_target##_bufs[index].allocated) < 0) { \
|
||||
LOG_PERROR("H264: Can't unmap %s buffer %u", #_target, index); \
|
||||
} \
|
||||
} \
|
||||
enc->_id##_port = NULL; \
|
||||
} \
|
||||
free(enc->_target##_bufs); \
|
||||
enc->_target##_bufs = NULL; \
|
||||
} \
|
||||
enc->n_##_target##_bufs = 0; \
|
||||
}
|
||||
|
||||
DESTROY_BUFFERS(output);
|
||||
DESTROY_BUFFERS(input);
|
||||
|
||||
# undef DESTROY_BUFFERS
|
||||
|
||||
if (enc->fd >= 0) {
|
||||
if (close(enc->fd) < 0) {
|
||||
LOG_PERROR("H264: Can't close encoder device");
|
||||
}
|
||||
|
||||
DISABLE_PORT(input);
|
||||
DISABLE_PORT(output);
|
||||
|
||||
# undef DISABLE_PORT
|
||||
|
||||
if (enc->wrapper) {
|
||||
enc->wrapper->status = MMAL_SUCCESS; // Это реально надо?
|
||||
enc->fd = -1;
|
||||
}
|
||||
|
||||
enc->last_online = -1;
|
||||
enc->ready = false;
|
||||
}
|
||||
|
||||
int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key) {
|
||||
int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_dma_fd, frame_s *dest, bool force_key) {
|
||||
assert(enc->ready);
|
||||
assert(src->used > 0);
|
||||
assert(enc->width == src->width);
|
||||
assert(enc->height == src->height);
|
||||
assert(enc->format == src->format);
|
||||
assert(enc->stride == src->stride);
|
||||
if (enc->dma) {
|
||||
assert(src_dma_fd >= 0);
|
||||
} else {
|
||||
assert(src_dma_fd < 0);
|
||||
}
|
||||
|
||||
frame_copy_meta(src, dest);
|
||||
dest->encode_begin_ts = get_now_monotonic();
|
||||
@@ -274,7 +294,7 @@ int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_
|
||||
|
||||
force_key = (force_key || enc->last_online != src->online);
|
||||
|
||||
if (_h264_encoder_compress_raw(enc, src, src_vcsm_handle, dest, force_key) < 0) {
|
||||
if (_h264_encoder_compress_raw(enc, src, src_dma_fd, dest, force_key) < 0) {
|
||||
_h264_encoder_cleanup(enc);
|
||||
LOG_ERROR("H264: Encoder destroyed due an error (compress)");
|
||||
return -1;
|
||||
@@ -288,108 +308,98 @@ int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _h264_encoder_compress_raw(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key) {
|
||||
static int _h264_encoder_compress_raw(
|
||||
h264_encoder_s *enc, const frame_s *src, int src_dma_fd,
|
||||
frame_s *dest, bool force_key) {
|
||||
|
||||
LOG_DEBUG("H264: Compressing new frame; force_key=%d ...", force_key);
|
||||
|
||||
MMAL_STATUS_T error;
|
||||
|
||||
if (force_key) {
|
||||
if ((error = mmal_port_parameter_set_boolean(
|
||||
enc->output_port,
|
||||
MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME,
|
||||
MMAL_TRUE
|
||||
)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't request keyframe");
|
||||
return -1;
|
||||
struct v4l2_control ctl = {0};
|
||||
ctl.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME;
|
||||
ctl.value = 1;
|
||||
LOG_DEBUG("H264: Forcing keyframe ...")
|
||||
ENCODER_XIOCTL(VIDIOC_S_CTRL, &ctl, "H264: Can't force keyframe");
|
||||
}
|
||||
|
||||
struct v4l2_buffer input_buf = {0};
|
||||
struct v4l2_plane input_plane = {0};
|
||||
input_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
input_buf.length = 1;
|
||||
input_buf.m.planes = &input_plane;
|
||||
|
||||
if (enc->dma) {
|
||||
assert(src_dma_fd >= 0);
|
||||
input_buf.index = 0;
|
||||
input_buf.memory = V4L2_MEMORY_DMABUF;
|
||||
input_buf.field = V4L2_FIELD_NONE;
|
||||
input_plane.m.fd = src_dma_fd;
|
||||
LOG_DEBUG("H264: Using input-dma buffer %u", input_buf.index);
|
||||
} else {
|
||||
assert(src_dma_fd < 0);
|
||||
input_buf.memory = V4L2_MEMORY_MMAP;
|
||||
LOG_DEBUG("H264: Grabbing input buffer ...");
|
||||
ENCODER_XIOCTL(VIDIOC_DQBUF, &input_buf, "H264: Can't grab input buffer");
|
||||
if (input_buf.index >= enc->n_input_bufs) {
|
||||
LOG_ERROR("H264: V4L2 error: grabbed invalid input buffer: index=%u, n_bufs=%u",
|
||||
input_buf.index, enc->n_input_bufs);
|
||||
goto error;
|
||||
}
|
||||
LOG_DEBUG("H264: Grabbed input buffer %u", input_buf.index);
|
||||
}
|
||||
|
||||
uint64_t now = get_now_monotonic_u64();
|
||||
input_buf.timestamp.tv_sec = now / 1000000;
|
||||
input_buf.timestamp.tv_usec = now % 1000000;
|
||||
input_plane.bytesused = src->used;
|
||||
input_plane.length = src->used;
|
||||
if (!enc->dma) {
|
||||
memcpy(enc->input_bufs[input_buf.index].data, src->data, src->used);
|
||||
}
|
||||
|
||||
const char *input_name = (enc->dma ? "input-dma" : "input");
|
||||
|
||||
LOG_DEBUG("H264: Sending %s buffer ...", input_name);
|
||||
ENCODER_XIOCTL(VIDIOC_QBUF, &input_buf, "H264: Can't send %s buffer", input_name);
|
||||
|
||||
bool input_released = false;
|
||||
while (true) {
|
||||
struct pollfd enc_poll = {enc->fd, POLLIN, 0};
|
||||
|
||||
if (poll(&enc_poll, 1, 200) < 0 && errno != EINTR) {
|
||||
LOG_PERROR("H264: Can't poll encoder");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (enc_poll.revents & POLLIN) {
|
||||
if (!input_released) {
|
||||
LOG_DEBUG("H264: Releasing %s buffer %u ...", input_name, input_buf.index);
|
||||
ENCODER_XIOCTL(VIDIOC_DQBUF, &input_buf, "H264: Can't release %s buffer %u",
|
||||
input_name, input_buf.index);
|
||||
input_released = true;
|
||||
}
|
||||
|
||||
struct v4l2_buffer output_buf = {0};
|
||||
struct v4l2_plane output_plane = {0};
|
||||
output_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
output_buf.memory = V4L2_MEMORY_MMAP;
|
||||
output_buf.length = 1;
|
||||
output_buf.m.planes = &output_plane;
|
||||
LOG_DEBUG("H264: Fetching output buffer ...");
|
||||
ENCODER_XIOCTL(VIDIOC_DQBUF, &output_buf, "H264: Can't fetch output buffer");
|
||||
|
||||
frame_set_data(dest, enc->output_bufs[output_buf.index].data, output_plane.bytesused);
|
||||
dest->key = output_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
|
||||
|
||||
LOG_DEBUG("H264: Releasing output buffer %u ...", output_buf.index);
|
||||
ENCODER_XIOCTL(VIDIOC_QBUF, &output_buf, "H264: Can't release output buffer %u", output_buf.index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MMAL_BUFFER_HEADER_T *out = NULL;
|
||||
MMAL_BUFFER_HEADER_T *in = NULL;
|
||||
bool eos = false;
|
||||
bool sent = false;
|
||||
|
||||
dest->used = 0;
|
||||
|
||||
while (!eos) {
|
||||
out = NULL;
|
||||
while (mmal_wrapper_buffer_get_empty(enc->output_port, &out, 0) == MMAL_SUCCESS) {
|
||||
if ((error = mmal_port_send_buffer(enc->output_port, out)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't send MMAL output buffer");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
in = NULL;
|
||||
if (!sent && mmal_wrapper_buffer_get_empty(enc->input_port, &in, 0) == MMAL_SUCCESS) {
|
||||
if (enc->zero_copy && src_vcsm_handle > 0) {
|
||||
in->data = (uint8_t *)vcsm_vc_hdl_from_hdl(src_vcsm_handle);
|
||||
} else {
|
||||
in->data = src->data;
|
||||
}
|
||||
in->alloc_size = src->used;
|
||||
in->length = src->used;
|
||||
in->offset = 0;
|
||||
in->flags = MMAL_BUFFER_HEADER_FLAG_EOS;
|
||||
if ((error = mmal_port_send_buffer(enc->input_port, in)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't send MMAL input buffer");
|
||||
return -1;
|
||||
}
|
||||
sent = true;
|
||||
}
|
||||
|
||||
error = mmal_wrapper_buffer_get_full(enc->output_port, &out, 0);
|
||||
if (error == MMAL_EAGAIN) {
|
||||
if (vcos_my_semwait("H264: ", &enc->handler_sem, 1) < 0) {
|
||||
return -1;
|
||||
}
|
||||
continue;
|
||||
} else if (error != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't get MMAL output buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame_append_data(dest, out->data, out->length);
|
||||
dest->key = out->flags & MMAL_BUFFER_HEADER_FLAG_KEYFRAME;
|
||||
|
||||
eos = out->flags & MMAL_BUFFER_HEADER_FLAG_EOS;
|
||||
mmal_buffer_header_release(out);
|
||||
}
|
||||
|
||||
if ((error = mmal_port_flush(enc->output_port)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "H264: Can't flush MMAL output buffer; ignored");
|
||||
}
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void _mmal_callback(MMAL_WRAPPER_T *wrapper) {
|
||||
vcos_semaphore_post(&((h264_encoder_s *)(wrapper->user_data))->handler_sem);
|
||||
}
|
||||
|
||||
static const char *_mmal_error_to_string(MMAL_STATUS_T error) {
|
||||
// http://www.jvcref.com/files/PI/documentation/html/group___mmal_types.html
|
||||
# define CASE_ERROR(_name, _msg) case MMAL_##_name: return "MMAL_" #_name " [" _msg "]"
|
||||
switch (error) {
|
||||
case MMAL_SUCCESS: return "MMAL_SUCCESS";
|
||||
CASE_ERROR(ENOMEM, "Out of memory");
|
||||
CASE_ERROR(ENOSPC, "Out of resources");
|
||||
CASE_ERROR(EINVAL, "Invalid argument");
|
||||
CASE_ERROR(ENOSYS, "Function not implemented");
|
||||
CASE_ERROR(ENOENT, "No such file or directory");
|
||||
CASE_ERROR(ENXIO, "No such device or address");
|
||||
CASE_ERROR(EIO, "IO error");
|
||||
CASE_ERROR(ESPIPE, "Illegal seek");
|
||||
CASE_ERROR(ECORRUPT, "Data is corrupt");
|
||||
CASE_ERROR(ENOTREADY, "Component is not ready");
|
||||
CASE_ERROR(ECONFIG, "Component is not configured");
|
||||
CASE_ERROR(EISCONN, "Port is already connected");
|
||||
CASE_ERROR(ENOTCONN, "Port is disconnected");
|
||||
CASE_ERROR(EAGAIN, "Resource temporarily unavailable");
|
||||
CASE_ERROR(EFAULT, "Bad address");
|
||||
case MMAL_STATUS_MAX: break; // Makes cpplint happy
|
||||
}
|
||||
return "Unknown error";
|
||||
# undef CASE_ERROR
|
||||
}
|
||||
|
||||
#undef LOG_ERROR_MMAL
|
||||
#undef ENCODER_XIOCTL
|
||||
|
||||
@@ -24,33 +24,39 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <interface/mmal/mmal.h>
|
||||
#include <interface/mmal/mmal_format.h>
|
||||
#include <interface/mmal/util/mmal_default_components.h>
|
||||
#include <interface/mmal/util/mmal_component_wrapper.h>
|
||||
#include <interface/mmal/util/mmal_util_params.h>
|
||||
#include <interface/vcsm/user-vcsm.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../../libs/tools.h"
|
||||
#include "../../libs/logging.h"
|
||||
#include "../../libs/frame.h"
|
||||
#include "../encoders/omx/vcos.h"
|
||||
|
||||
#include "../xioctl.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned bitrate; // Kbit-per-sec
|
||||
unsigned gop; // Interval between keyframes
|
||||
unsigned fps;
|
||||
uint8_t *data;
|
||||
size_t allocated;
|
||||
} h264_buffer_s;
|
||||
|
||||
MMAL_WRAPPER_T *wrapper;
|
||||
MMAL_PORT_T *input_port;
|
||||
MMAL_PORT_T *output_port;
|
||||
VCOS_SEMAPHORE_T handler_sem;
|
||||
bool i_handler_sem;
|
||||
typedef struct {
|
||||
char *path;
|
||||
unsigned bitrate; // Kbit-per-sec
|
||||
unsigned gop; // Interval between keyframes
|
||||
unsigned fps;
|
||||
|
||||
int fd;
|
||||
h264_buffer_s *input_bufs;
|
||||
unsigned n_input_bufs;
|
||||
h264_buffer_s *output_bufs;
|
||||
unsigned n_output_bufs;
|
||||
|
||||
int last_online;
|
||||
|
||||
@@ -58,14 +64,14 @@ typedef struct {
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
bool zero_copy;
|
||||
bool dma;
|
||||
bool ready;
|
||||
} h264_encoder_s;
|
||||
|
||||
|
||||
h264_encoder_s *h264_encoder_init(unsigned bitrate, unsigned gop, unsigned fps);
|
||||
h264_encoder_s *h264_encoder_init(const char *path, unsigned bitrate, unsigned gop, unsigned fps);
|
||||
void h264_encoder_destroy(h264_encoder_s *enc);
|
||||
|
||||
bool h264_encoder_is_prepared_for(h264_encoder_s *enc, const frame_s *frame, bool zero_copy);
|
||||
int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_copy);
|
||||
int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key);
|
||||
bool h264_encoder_is_prepared_for(h264_encoder_s *enc, const frame_s *frame, bool dma);
|
||||
int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool dma);
|
||||
int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_dma_fd, frame_s *dest, bool force_key);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "stream.h"
|
||||
|
||||
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop) {
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, const char *path, unsigned bitrate, unsigned gop) {
|
||||
h264_stream_s *h264;
|
||||
A_CALLOC(h264, 1);
|
||||
h264->sink = sink;
|
||||
@@ -34,15 +34,9 @@ h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop)
|
||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100
|
||||
// По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480
|
||||
// енкодер через несколько секунд перестает производить корректные фреймы.
|
||||
if ((h264->enc = h264_encoder_init(bitrate, gop, 30)) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
// TODO: Это было актуально для MMAL, надо проверить для V4L2.
|
||||
h264->enc = h264_encoder_init(path, bitrate, gop, 30);
|
||||
return h264;
|
||||
|
||||
error:
|
||||
h264_stream_destroy(h264);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void h264_stream_destroy(h264_stream_s *h264) {
|
||||
@@ -54,7 +48,7 @@ void h264_stream_destroy(h264_stream_s *h264) {
|
||||
free(h264);
|
||||
}
|
||||
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_handle, bool force_key) {
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int dma_fd, bool force_key) {
|
||||
if (!memsink_server_check(h264->sink, frame)) {
|
||||
return;
|
||||
}
|
||||
@@ -63,14 +57,14 @@ void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_han
|
||||
bool zero_copy = false;
|
||||
|
||||
if (is_jpeg(frame->format)) {
|
||||
assert(vcsm_handle <= 0);
|
||||
assert(dma_fd <= 0);
|
||||
LOG_DEBUG("H264: Input frame is JPEG; decoding ...");
|
||||
if (unjpeg(frame, h264->tmp_src, true) < 0) {
|
||||
return;
|
||||
}
|
||||
frame = h264->tmp_src;
|
||||
LOG_VERBOSE("H264: JPEG decoded; time=%.3Lf", get_now_monotonic() - now);
|
||||
} else if (vcsm_handle > 0) {
|
||||
} else if (dma_fd > 0) {
|
||||
LOG_DEBUG("H264: Zero-copy available for the input");
|
||||
zero_copy = true;
|
||||
} else {
|
||||
@@ -87,7 +81,7 @@ void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_han
|
||||
}
|
||||
|
||||
if (h264->enc->ready) {
|
||||
if (h264_encoder_compress(h264->enc, frame, vcsm_handle, h264->dest, force_key) == 0) {
|
||||
if (h264_encoder_compress(h264->enc, frame, dma_fd, h264->dest, force_key) == 0) {
|
||||
online = !memsink_server_put(h264->sink, h264->dest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,6 @@ typedef struct {
|
||||
} h264_stream_s;
|
||||
|
||||
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop);
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, const char *path, unsigned bitrate, unsigned gop);
|
||||
void h264_stream_destroy(h264_stream_s *h264);
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_handle, bool force_key);
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int dma_fd, bool force_key);
|
||||
|
||||
@@ -393,7 +393,6 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
enc_quality
|
||||
));
|
||||
|
||||
# ifdef WITH_OMX
|
||||
if (STREAM(run->h264)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s},",
|
||||
@@ -402,14 +401,8 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
bool_to_string(atomic_load(&STREAM(run->h264->online)))
|
||||
));
|
||||
}
|
||||
# endif
|
||||
|
||||
if (
|
||||
STREAM(sink)
|
||||
# ifdef WITH_OMX
|
||||
|| STREAM(h264_sink)
|
||||
# endif
|
||||
) {
|
||||
if (STREAM(sink) || STREAM(h264_sink)) {
|
||||
assert(evbuffer_add_printf(buf, " \"sinks\": {"));
|
||||
if (STREAM(sink)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
@@ -417,7 +410,6 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
bool_to_string(atomic_load(&STREAM(sink->has_clients)))
|
||||
));
|
||||
}
|
||||
# ifdef WITH_OMX
|
||||
if (STREAM(h264_sink)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"%s\"h264\": {\"has_clients\": %s}",
|
||||
@@ -425,7 +417,6 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
bool_to_string(atomic_load(&STREAM(h264_sink->has_clients)))
|
||||
));
|
||||
}
|
||||
# endif
|
||||
assert(evbuffer_add_printf(buf, "},"));
|
||||
}
|
||||
|
||||
|
||||
@@ -131,11 +131,9 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if ((exit_code = options_parse(options, dev, enc, stream, server)) == 0) {
|
||||
# ifdef WITH_OMX
|
||||
if (enc->type == ENCODER_TYPE_OMX || stream->h264_sink) {
|
||||
if (enc->type == ENCODER_TYPE_OMX) {
|
||||
bcm_host_init();
|
||||
i_bcm_host = true;
|
||||
}
|
||||
if (enc->type == ENCODER_TYPE_OMX) {
|
||||
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;
|
||||
|
||||
@@ -94,11 +94,10 @@ enum _OPT_VALUES {
|
||||
_O_##_prefix##_TIMEOUT,
|
||||
ADD_SINK(SINK)
|
||||
ADD_SINK(RAW_SINK)
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK(H264_SINK)
|
||||
_O_H264_DEVICE,
|
||||
_O_H264_BITRATE,
|
||||
_O_H264_GOP,
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@@ -192,11 +191,10 @@ static const struct option _LONG_OPTS[] = {
|
||||
{_opt "sink-timeout", required_argument, NULL, _O_##_prefix##_TIMEOUT},
|
||||
ADD_SINK("", SINK)
|
||||
ADD_SINK("raw-", RAW_SINK)
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("h264-", H264_SINK)
|
||||
{"h264-device", required_argument, NULL, _O_H264_DEVICE},
|
||||
{"h264-bitrate", required_argument, NULL, _O_H264_BITRATE},
|
||||
{"h264-gop", required_argument, NULL, _O_H264_GOP},
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@@ -258,9 +256,7 @@ void options_destroy(options_s *options) {
|
||||
}
|
||||
ADD_SINK(sink);
|
||||
ADD_SINK(raw_sink);
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK(h264_sink);
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
|
||||
if (options->blank) {
|
||||
@@ -353,9 +349,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
unsigned _prefix##_timeout = 1;
|
||||
ADD_SINK(sink);
|
||||
ADD_SINK(raw_sink);
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK(h264_sink);
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
@@ -447,11 +441,10 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
case _O_##_up##_TIMEOUT: OPT_NUMBER("--" #_opt "sink-timeout", _lp##_timeout, 1, 60, 0);
|
||||
ADD_SINK("", sink, SINK)
|
||||
ADD_SINK("raw-", raw_sink, RAW_SINK)
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("h264-", h264_sink, H264_SINK)
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 25000, 0);
|
||||
case _O_H264_DEVICE: OPT_SET(stream->h264_path, optarg);
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 100, 16000, 0);
|
||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@@ -510,9 +503,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
}
|
||||
ADD_SINK("JPEG", sink);
|
||||
ADD_SINK("RAW", raw_sink);
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("H264", h264_sink);
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
@@ -696,11 +687,10 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" --" _opt "sink-timeout <sec> ──── Timeout for lock. Default: 1.\n");
|
||||
ADD_SINK("JPEG", "")
|
||||
ADD_SINK("RAW", "raw-")
|
||||
# ifdef WITH_OMX
|
||||
ADD_SINK("H264", "h264-")
|
||||
SAY(" --h264-device </dev/path> ──── Path to V4L2 H.264 encoder device. Default: %s.\n", stream->h264_path);
|
||||
SAY(" --h264-bitrate <kbps> ──────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
|
||||
SAY(" --h264-gop <N> ─────────────── Intarval between keyframes. Default: %u.\n", stream->h264_gop);
|
||||
# endif
|
||||
# undef ADD_SINK
|
||||
# ifdef WITH_GPIO
|
||||
SAY("GPIO options:");
|
||||
|
||||
@@ -56,9 +56,7 @@ typedef struct {
|
||||
frame_s *blank;
|
||||
memsink_s *sink;
|
||||
memsink_s *raw_sink;
|
||||
# ifdef WITH_OMX
|
||||
memsink_s *h264_sink;
|
||||
# endif
|
||||
} options_s;
|
||||
|
||||
|
||||
|
||||
@@ -36,13 +36,11 @@ static void _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned capt
|
||||
} \
|
||||
}
|
||||
|
||||
#ifdef WITH_OMX
|
||||
# define H264_PUT(_frame, _vcsm_handle, _force_key) { \
|
||||
if (RUN(h264)) { \
|
||||
h264_stream_process(RUN(h264), _frame, _vcsm_handle, _force_key); \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
#define H264_PUT(_frame, _dma_fd, _force_key) { \
|
||||
if (RUN(h264)) { \
|
||||
h264_stream_process(RUN(h264), _frame, _dma_fd, _force_key); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
stream_s *stream_init(device_s *dev, encoder_s *enc) {
|
||||
@@ -64,10 +62,9 @@ stream_s *stream_init(device_s *dev, encoder_s *enc) {
|
||||
stream->enc = enc;
|
||||
stream->last_as_blank = -1;
|
||||
stream->error_delay = 1;
|
||||
# ifdef WITH_OMX
|
||||
stream->h264_path = "/dev/video11";
|
||||
stream->h264_bitrate = 5000; // Kbps
|
||||
stream->h264_gop = 30;
|
||||
# endif
|
||||
stream->run = run;
|
||||
return stream;
|
||||
}
|
||||
@@ -86,11 +83,9 @@ void stream_loop(stream_s *stream) {
|
||||
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
|
||||
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
|
||||
|
||||
# ifdef WITH_OMX
|
||||
if (stream->h264_sink) {
|
||||
RUN(h264) = h264_stream_init(stream->h264_sink, stream->h264_bitrate, stream->h264_gop);
|
||||
RUN(h264) = h264_stream_init(stream->h264_sink, stream->h264_path, stream->h264_bitrate, stream->h264_gop);
|
||||
}
|
||||
# endif
|
||||
|
||||
for (workers_pool_s *pool; (pool = _stream_init_loop(stream)) != NULL;) {
|
||||
long double grab_after = 0;
|
||||
@@ -126,18 +121,14 @@ void stream_loop(stream_s *stream) {
|
||||
}
|
||||
}
|
||||
|
||||
# ifdef WITH_OMX
|
||||
bool h264_force_key = false;
|
||||
# endif
|
||||
if (stream->slowdown) {
|
||||
unsigned slc = 0;
|
||||
for (; slc < 10 && !atomic_load(&RUN(stop)) && !stream_has_clients(stream); ++slc) {
|
||||
usleep(100000);
|
||||
++slc;
|
||||
}
|
||||
# ifdef WITH_OMX
|
||||
h264_force_key = (slc == 10);
|
||||
# endif
|
||||
}
|
||||
|
||||
if (atomic_load(&RUN(stop))) {
|
||||
@@ -200,9 +191,7 @@ void stream_loop(stream_s *stream) {
|
||||
LOG_DEBUG("Assigned new frame in buffer %d to worker %s", buf_index, ready_wr->name);
|
||||
|
||||
SINK_PUT(raw_sink, &hw->raw);
|
||||
# ifdef WITH_OMX
|
||||
H264_PUT(&hw->raw, hw->vcsm_handle, h264_force_key);
|
||||
# endif
|
||||
H264_PUT(&hw->raw, hw->dma_fd, h264_force_key);
|
||||
}
|
||||
} else if (buf_index != -2) { // -2 for broken frame
|
||||
break;
|
||||
@@ -232,11 +221,9 @@ void stream_loop(stream_s *stream) {
|
||||
# endif
|
||||
}
|
||||
|
||||
# ifdef WITH_OMX
|
||||
if (RUN(h264)) {
|
||||
h264_stream_destroy(RUN(h264));
|
||||
}
|
||||
# endif
|
||||
}
|
||||
|
||||
void stream_loop_break(stream_s *stream) {
|
||||
@@ -248,9 +235,7 @@ bool stream_has_clients(stream_s *stream) {
|
||||
atomic_load(&RUN(video->has_clients))
|
||||
// has_clients синков НЕ обновляются в реальном времени
|
||||
|| (stream->sink != NULL && atomic_load(&stream->sink->has_clients))
|
||||
# ifdef WITH_OMX
|
||||
|| (RUN(h264) != NULL && /*RUN(h264->sink) == NULL ||*/ atomic_load(&RUN(h264->sink->has_clients)))
|
||||
# endif
|
||||
);
|
||||
}
|
||||
|
||||
@@ -292,11 +277,9 @@ static workers_pool_s *_stream_init_one(stream_s *stream) {
|
||||
if (device_open(stream->dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
# ifdef WITH_OMX
|
||||
if (RUN(h264) && !is_jpeg(stream->dev->run->format)) {
|
||||
device_export_to_vcsm(stream->dev);
|
||||
device_export_to_dma(stream->dev);
|
||||
}
|
||||
# endif
|
||||
if (device_switch_capturing(stream->dev, true) < 0) {
|
||||
goto error;
|
||||
}
|
||||
@@ -364,16 +347,12 @@ static void _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned capt
|
||||
|
||||
if (frame == NULL) {
|
||||
SINK_PUT(raw_sink, stream->blank);
|
||||
# ifdef WITH_OMX
|
||||
H264_PUT(stream->blank, -1, false);
|
||||
# endif
|
||||
}
|
||||
|
||||
# undef VID
|
||||
}
|
||||
|
||||
#ifdef WITH_OMX
|
||||
# undef H264_PUT
|
||||
#endif
|
||||
#undef H264_PUT
|
||||
#undef SINK_PUT
|
||||
#undef RUN
|
||||
|
||||
@@ -42,9 +42,7 @@
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
#include "workers.h"
|
||||
#ifdef WITH_OMX
|
||||
# include "h264/stream.h"
|
||||
#endif
|
||||
#include "h264/stream.h"
|
||||
#ifdef WITH_GPIO
|
||||
# include "gpio/gpio.h"
|
||||
#endif
|
||||
@@ -63,9 +61,7 @@ typedef struct {
|
||||
video_s *video;
|
||||
long double last_as_blank_ts;
|
||||
|
||||
# ifdef WITH_OMX
|
||||
h264_stream_s *h264;
|
||||
# endif
|
||||
|
||||
atomic_bool stop;
|
||||
} stream_runtime_s;
|
||||
@@ -82,11 +78,10 @@ typedef struct {
|
||||
memsink_s *sink;
|
||||
memsink_s *raw_sink;
|
||||
|
||||
# ifdef WITH_OMX
|
||||
memsink_s *h264_sink;
|
||||
char *h264_path;
|
||||
unsigned h264_bitrate;
|
||||
unsigned h264_gop;
|
||||
# endif
|
||||
|
||||
stream_runtime_s *run;
|
||||
} stream_s;
|
||||
|
||||
Reference in New Issue
Block a user