mirror of
https://github.com/videolan/vlc.git
synced 2024-12-01 13:55:14 +08:00
989 lines
31 KiB
C
989 lines
31 KiB
C
/*****************************************************************************
|
|
* converter.c: MMAL-based resizer
|
|
*****************************************************************************
|
|
* Copyright © 2014 jusst technologies GmbH
|
|
*
|
|
* Authors: Dennis Hamester <dennis.hamester@gmail.com>
|
|
* Julian Scheel <julian@jusst.de>
|
|
* John Cox <jc@kynesim.co.uk>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdatomic.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_codec.h>
|
|
#include <vlc_filter.h>
|
|
#include <vlc_plugin.h>
|
|
|
|
#include <interface/mmal/mmal.h>
|
|
#include <interface/mmal/util/mmal_util.h>
|
|
#include <interface/mmal/util/mmal_default_components.h>
|
|
|
|
#include "mmal_picture.h"
|
|
|
|
#include "subpic.h"
|
|
|
|
typedef enum filter_resizer_e {
|
|
FILTER_RESIZER_HVS,
|
|
FILTER_RESIZER_ISP,
|
|
FILTER_RESIZER_RESIZER,
|
|
} filter_resizer_t;
|
|
|
|
#ifndef NDEBUG
|
|
#define MMAL_CONVERTER_TYPE_NAME "mmal-hw-converter"
|
|
|
|
/* Note: Skip translation of these - too technical */
|
|
#define MMAL_CONVERTER_TYPE_TEXT "Hardware used for MMAL conversions"
|
|
#define MMAL_CONVERTER_TYPE_LONGTEXT "Hardware component used for MMAL conversions. Hardware Video Scaler"\
|
|
" (default) gives the best result and allows blending, Resizer is slower but uses less memory."
|
|
#endif
|
|
|
|
static const int pi_converter_modes[] = {
|
|
FILTER_RESIZER_HVS, FILTER_RESIZER_ISP, FILTER_RESIZER_RESIZER,
|
|
};
|
|
static const char * const ppsz_converter_text[] = {
|
|
"Hardware Video Scaler", "ISP", "Resizer"
|
|
};
|
|
|
|
static int OpenConverter(filter_t *);
|
|
|
|
vlc_module_begin()
|
|
set_subcategory( SUBCAT_VIDEO_VFILTER )
|
|
set_shortname(N_("MMAL resizer"))
|
|
set_description(N_("MMAL resizing conversion filter"))
|
|
add_shortcut("mmal_converter")
|
|
#ifndef NDEBUG
|
|
add_integer( MMAL_CONVERTER_TYPE_NAME, FILTER_RESIZER_HVS, MMAL_CONVERTER_TYPE_TEXT, MMAL_CONVERTER_TYPE_LONGTEXT )
|
|
change_integer_list( pi_converter_modes, ppsz_converter_text )
|
|
#endif
|
|
set_callback_video_converter(OpenConverter, 900)
|
|
vlc_module_end()
|
|
|
|
#define MMAL_SLICE_HEIGHT 16
|
|
|
|
#define CONV_MAX_LATENCY 1 // In frames
|
|
|
|
static MMAL_FOURCC_T pic_to_slice_mmal_fourcc(MMAL_FOURCC_T fcc)
|
|
{
|
|
switch (fcc){
|
|
case MMAL_ENCODING_I420:
|
|
return MMAL_ENCODING_I420_SLICE;
|
|
case MMAL_ENCODING_I422:
|
|
return MMAL_ENCODING_I422_SLICE;
|
|
case MMAL_ENCODING_ARGB:
|
|
return MMAL_ENCODING_ARGB_SLICE;
|
|
case MMAL_ENCODING_RGBA:
|
|
return MMAL_ENCODING_RGBA_SLICE;
|
|
case MMAL_ENCODING_ABGR:
|
|
return MMAL_ENCODING_ABGR_SLICE;
|
|
case MMAL_ENCODING_BGRA:
|
|
return MMAL_ENCODING_BGRA_SLICE;
|
|
case MMAL_ENCODING_RGB16:
|
|
return MMAL_ENCODING_RGB16_SLICE;
|
|
case MMAL_ENCODING_RGB24:
|
|
return MMAL_ENCODING_RGB24_SLICE;
|
|
case MMAL_ENCODING_RGB32:
|
|
return MMAL_ENCODING_RGB32_SLICE;
|
|
case MMAL_ENCODING_BGR16:
|
|
return MMAL_ENCODING_BGR16_SLICE;
|
|
case MMAL_ENCODING_BGR24:
|
|
return MMAL_ENCODING_BGR24_SLICE;
|
|
case MMAL_ENCODING_BGR32:
|
|
return MMAL_ENCODING_BGR32_SLICE;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static picture_t * pic_fifo_get(vlc_picture_chain_t * const pf)
|
|
{
|
|
return vlc_picture_chain_PopFront( pf );
|
|
}
|
|
|
|
static void pic_fifo_release_all(vlc_picture_chain_t * const pf)
|
|
{
|
|
picture_t * pic;
|
|
while ((pic = pic_fifo_get(pf)) != NULL) {
|
|
picture_Release(pic);
|
|
}
|
|
}
|
|
|
|
static void pic_fifo_init(vlc_picture_chain_t * const pf)
|
|
{
|
|
vlc_picture_chain_Init( pf );
|
|
}
|
|
|
|
static void pic_fifo_put(vlc_picture_chain_t * const pf, picture_t * pic)
|
|
{
|
|
vlc_picture_chain_Append( pf, pic );
|
|
}
|
|
|
|
#define SUBS_MAX 3
|
|
|
|
typedef struct conv_frame_stash_s
|
|
{
|
|
vlc_tick_t pts;
|
|
MMAL_BUFFER_HEADER_T * sub_bufs[SUBS_MAX];
|
|
} conv_frame_stash_t;
|
|
|
|
typedef struct
|
|
{
|
|
filter_resizer_t resizer_type;
|
|
MMAL_COMPONENT_T *component;
|
|
MMAL_PORT_T *input;
|
|
MMAL_PORT_T *output;
|
|
MMAL_POOL_T *out_pool; // Free output buffers
|
|
MMAL_POOL_T *in_pool; // Input pool to get BH for replication
|
|
|
|
cma_buf_pool_t * cma_in_pool;
|
|
cma_buf_pool_t * cma_out_pool;
|
|
|
|
subpic_reg_stash_t subs[SUBS_MAX];
|
|
|
|
vlc_picture_chain_t ret_pics;
|
|
|
|
unsigned int pic_n;
|
|
vlc_sem_t sem;
|
|
vlc_mutex_t lock;
|
|
|
|
MMAL_STATUS_T err_stream;
|
|
|
|
bool needs_copy_in;
|
|
bool is_sliced;
|
|
bool out_fmt_set;
|
|
MMAL_PORT_BH_CB_T in_port_cb_fn;
|
|
MMAL_PORT_BH_CB_T out_port_cb_fn;
|
|
|
|
uint64_t frame_seq;
|
|
conv_frame_stash_t stash[16];
|
|
|
|
// Slice specific tracking stuff
|
|
struct {
|
|
vlc_picture_chain_t pics;
|
|
unsigned int line; // Lines filled
|
|
} slice;
|
|
|
|
vlc_decoder_device *dec_dev;
|
|
} converter_sys_t;
|
|
|
|
|
|
static void cma_buf_pool_deletez(cma_buf_pool_t ** const pp)
|
|
{
|
|
cma_buf_pool_t * const p = *pp;
|
|
if (p != NULL) {
|
|
*pp = NULL;
|
|
cma_buf_pool_delete(p);
|
|
}
|
|
}
|
|
|
|
static MMAL_STATUS_T pic_to_format(MMAL_ES_FORMAT_T * const es_fmt, const picture_t * const pic)
|
|
{
|
|
unsigned int bpp = (pic->format.i_bits_per_pixel + 7) >> 3;
|
|
MMAL_VIDEO_FORMAT_T * const v_fmt = &es_fmt->es->video;
|
|
|
|
es_fmt->type = MMAL_ES_TYPE_VIDEO;
|
|
es_fmt->encoding = vlc_to_mmal_video_fourcc(&pic->format);
|
|
es_fmt->encoding_variant = 0;
|
|
|
|
// Fill in crop etc.
|
|
hw_mmal_vlc_fmt_to_mmal_fmt(es_fmt, &pic->format);
|
|
// Override width / height with strides if appropriate
|
|
if (bpp != 0) {
|
|
v_fmt->width = pic->p[0].i_pitch / bpp;
|
|
v_fmt->height = pic->p[0].i_lines;
|
|
}
|
|
return MMAL_SUCCESS;
|
|
}
|
|
|
|
|
|
static MMAL_STATUS_T conv_enable_in(filter_t * const p_filter, converter_sys_t * const sys)
|
|
{
|
|
MMAL_STATUS_T err = MMAL_SUCCESS;
|
|
|
|
if (!sys->input->is_enabled &&
|
|
(err = mmal_port_enable(sys->input, sys->in_port_cb_fn)) != MMAL_SUCCESS)
|
|
{
|
|
msg_Err(p_filter, "Failed to enable input port %s (status=%"PRIx32" %s)",
|
|
sys->input->name, err, mmal_status_to_string(err));
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static MMAL_STATUS_T conv_enable_out(filter_t * const p_filter, converter_sys_t * const sys)
|
|
{
|
|
MMAL_STATUS_T err = MMAL_SUCCESS;
|
|
|
|
{
|
|
cma_buf_pool_deletez(&sys->cma_out_pool);
|
|
}
|
|
|
|
if (!sys->output->is_enabled &&
|
|
(err = mmal_port_enable(sys->output, sys->out_port_cb_fn)) != MMAL_SUCCESS)
|
|
{
|
|
msg_Err(p_filter, "Failed to enable output port %s (status=%"PRIx32" %s)",
|
|
sys->output->name, err, mmal_status_to_string(err));
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static void conv_control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
|
|
{
|
|
filter_t * const p_filter = (filter_t *)port->userdata;
|
|
|
|
if (buffer->cmd == MMAL_EVENT_ERROR) {
|
|
MMAL_STATUS_T status = *(uint32_t *)buffer->data;
|
|
|
|
converter_sys_t * sys = p_filter->p_sys;
|
|
sys->err_stream = status;
|
|
|
|
msg_Err(p_filter, "MMAL error %"PRIx32" \"%s\"", status,
|
|
mmal_status_to_string(status));
|
|
}
|
|
|
|
mmal_buffer_header_release(buffer);
|
|
}
|
|
|
|
static void conv_input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
|
|
{
|
|
VLC_UNUSED(port);
|
|
|
|
mmal_buffer_header_release(buf);
|
|
}
|
|
|
|
static void conv_out_q_pic(converter_sys_t * const sys, picture_t * const pic)
|
|
{
|
|
vlc_mutex_lock(&sys->lock);
|
|
pic_fifo_put(&sys->ret_pics, pic);
|
|
vlc_mutex_unlock(&sys->lock);
|
|
|
|
vlc_sem_post(&sys->sem);
|
|
}
|
|
|
|
static void conv_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
|
|
{
|
|
filter_t * const p_filter = (filter_t *)port->userdata;
|
|
converter_sys_t * const sys = p_filter->p_sys;
|
|
|
|
if (buf->cmd == 0) {
|
|
picture_t * const pic = (picture_t *)buf->user_data;
|
|
|
|
if (pic == NULL) {
|
|
msg_Err(p_filter, "Buffer has no attached picture");
|
|
}
|
|
else if (buf->data != NULL && buf->length != 0)
|
|
{
|
|
buf_to_pic_copy_props(pic, buf);
|
|
|
|
buf->user_data = NULL; // Responsibility for this pic no longer with buffer
|
|
conv_out_q_pic(sys, pic);
|
|
}
|
|
}
|
|
|
|
mmal_buffer_header_release(buf);
|
|
}
|
|
|
|
|
|
static void slice_output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
|
|
{
|
|
filter_t * const p_filter = (filter_t *)port->userdata;
|
|
converter_sys_t * const sys = p_filter->p_sys;
|
|
|
|
if (buf->cmd != 0)
|
|
{
|
|
mmal_buffer_header_release(buf);
|
|
return;
|
|
}
|
|
|
|
if (buf->data != NULL && buf->length != 0)
|
|
{
|
|
// Got slice
|
|
picture_t *pic = vlc_picture_chain_PeekFront( &sys->slice.pics );
|
|
const unsigned int scale_lines = sys->output->format->es->video.height; // Expected lines of callback
|
|
|
|
if (pic == NULL) {
|
|
msg_Err(p_filter, "No output picture");
|
|
goto fail;
|
|
}
|
|
|
|
// Copy lines
|
|
// * single plane only - fix for I420
|
|
{
|
|
const unsigned int scale_n = __MIN(scale_lines - sys->slice.line, MMAL_SLICE_HEIGHT);
|
|
const unsigned int pic_lines = pic->p[0].i_lines;
|
|
const unsigned int copy_n = sys->slice.line + scale_n <= pic_lines ? scale_n :
|
|
sys->slice.line >= pic_lines ? 0 :
|
|
pic_lines - sys->slice.line;
|
|
|
|
const unsigned int src_stride = buf->type->video.pitch[0];
|
|
const unsigned int dst_stride = pic->p[0].i_pitch;
|
|
uint8_t *dst = pic->p[0].p_pixels + sys->slice.line * dst_stride;
|
|
const uint8_t *src = buf->data + buf->type->video.offset[0];
|
|
|
|
if (src_stride == dst_stride) {
|
|
if (copy_n != 0)
|
|
memcpy(dst, src, src_stride * copy_n);
|
|
}
|
|
else {
|
|
unsigned int i;
|
|
for (i = 0; i != copy_n; ++i) {
|
|
memcpy(dst, src, __MIN(dst_stride, src_stride));
|
|
dst += dst_stride;
|
|
src += src_stride;
|
|
}
|
|
}
|
|
sys->slice.line += scale_n;
|
|
}
|
|
|
|
if ((buf->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) != 0 || sys->slice.line >= scale_lines) {
|
|
|
|
if ((buf->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) == 0 || sys->slice.line != scale_lines) {
|
|
// Stuff doesn't add up...
|
|
msg_Err(p_filter, "Line count (%d/%d) & EOF disagree (flags=%#x)", sys->slice.line, scale_lines, buf->flags);
|
|
goto fail;
|
|
}
|
|
else {
|
|
sys->slice.line = 0;
|
|
|
|
vlc_mutex_lock(&sys->lock);
|
|
pic = pic_fifo_get(&sys->slice.pics); // Remove head from Q
|
|
vlc_mutex_unlock(&sys->lock);
|
|
|
|
buf_to_pic_copy_props(pic, buf);
|
|
conv_out_q_pic(sys, pic);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Put back
|
|
buf->user_data = NULL; // Zap here to make sure we can't reuse later
|
|
mmal_buffer_header_reset(buf);
|
|
|
|
if (mmal_port_send_buffer(sys->output, buf) != MMAL_SUCCESS) {
|
|
mmal_buffer_header_release(buf);
|
|
}
|
|
return;
|
|
|
|
fail:
|
|
sys->err_stream = MMAL_EIO;
|
|
vlc_sem_post(&sys->sem); // If we were waiting then break us out - the flush should fix sem values
|
|
}
|
|
|
|
|
|
static void conv_flush(filter_t * p_filter)
|
|
{
|
|
converter_sys_t * const sys = p_filter->p_sys;
|
|
unsigned int i;
|
|
|
|
if (sys->resizer_type == FILTER_RESIZER_HVS)
|
|
{
|
|
for (i = 0; i != SUBS_MAX; ++i) {
|
|
hw_mmal_subpic_flush(sys->subs + i);
|
|
}
|
|
}
|
|
|
|
if (sys->input != NULL && sys->input->is_enabled)
|
|
mmal_port_disable(sys->input);
|
|
|
|
if (sys->output != NULL && sys->output->is_enabled)
|
|
mmal_port_disable(sys->output);
|
|
|
|
// cma_buf_pool_deletez(&sys->cma_out_pool);
|
|
|
|
// Free up anything we may have already lying around
|
|
// Don't need lock as the above disables should have prevented anything
|
|
// happening in the background
|
|
|
|
for (i = 0; i != ARRAY_SIZE(sys->stash); ++i) {
|
|
conv_frame_stash_t *const stash = sys->stash + i;
|
|
unsigned int sub_no;
|
|
|
|
stash->pts = MMAL_TIME_UNKNOWN;
|
|
for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) {
|
|
if (stash->sub_bufs[sub_no] != NULL) {
|
|
mmal_buffer_header_release(stash->sub_bufs[sub_no]);
|
|
stash->sub_bufs[sub_no] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
pic_fifo_release_all(&sys->slice.pics);
|
|
pic_fifo_release_all(&sys->ret_pics);
|
|
|
|
// Reset sem values - easiest & most reliable way is to just kill & re-init
|
|
vlc_sem_init(&sys->sem, 0);
|
|
sys->pic_n = 0;
|
|
|
|
// Reset error status
|
|
sys->err_stream = MMAL_SUCCESS;
|
|
}
|
|
|
|
static void conv_stash_fixup(filter_t * const p_filter, converter_sys_t * const sys, picture_t * const p_pic)
|
|
{
|
|
conv_frame_stash_t * const stash = sys->stash + (p_pic->date & 0xf);
|
|
unsigned int sub_no;
|
|
VLC_UNUSED(p_filter);
|
|
|
|
p_pic->date = stash->pts;
|
|
for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) {
|
|
if (stash->sub_bufs[sub_no] != NULL) {
|
|
// **** Do stashed blend
|
|
// **** Aaargh, bother... need to rescale subs too
|
|
|
|
mmal_buffer_header_release(stash->sub_bufs[sub_no]);
|
|
stash->sub_bufs[sub_no] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Output buffers may contain a pic ref on error or flush
|
|
// Free it
|
|
static MMAL_BOOL_T out_buffer_pre_release_cb(MMAL_BUFFER_HEADER_T *header, void *userdata)
|
|
{
|
|
VLC_UNUSED(userdata);
|
|
|
|
picture_t * const pic = header->user_data;
|
|
header->user_data = NULL;
|
|
|
|
if (pic != NULL)
|
|
picture_Release(pic);
|
|
|
|
return MMAL_FALSE;
|
|
}
|
|
|
|
static MMAL_STATUS_T conv_set_output(filter_t * const p_filter, converter_sys_t * const sys, picture_t * const pic)
|
|
{
|
|
MMAL_STATUS_T status;
|
|
|
|
sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter;
|
|
sys->output->format->type = MMAL_ES_TYPE_VIDEO;
|
|
sys->output->format->encoding = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video);
|
|
sys->output->format->encoding_variant = 0;
|
|
hw_mmal_vlc_fmt_to_mmal_fmt(sys->output->format, &p_filter->fmt_out.video);
|
|
|
|
if (pic != NULL)
|
|
{
|
|
// Override default format width/height if we have a pic we need to match
|
|
if ((status = pic_to_format(sys->output->format, pic)) != MMAL_SUCCESS)
|
|
{
|
|
msg_Err(p_filter, "Bad format desc: %4.4s, pic=%p, bits=%d", (const char*)&pic->format.i_chroma, pic, pic->format.i_bits_per_pixel);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
if (sys->is_sliced) {
|
|
// Override height for slice
|
|
sys->output->format->es->video.height = MMAL_SLICE_HEIGHT;
|
|
}
|
|
|
|
mmal_log_dump_format(sys->output->format);
|
|
|
|
status = mmal_port_format_commit(sys->output);
|
|
if (status != MMAL_SUCCESS) {
|
|
msg_Err(p_filter, "Failed to commit format for output port %s (status=%"PRIx32" %s)",
|
|
sys->output->name, status, mmal_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
sys->output->buffer_num = __MAX(sys->is_sliced ? 16 : 2, sys->output->buffer_num_recommended);
|
|
sys->output->buffer_size = sys->output->buffer_size_recommended;
|
|
|
|
if ((status = conv_enable_out(p_filter, sys)) != MMAL_SUCCESS)
|
|
return status;
|
|
|
|
return MMAL_SUCCESS;
|
|
}
|
|
|
|
static picture_t *conv_filter(filter_t *p_filter, picture_t *p_pic)
|
|
{
|
|
converter_sys_t * const sys = p_filter->p_sys;
|
|
picture_t * ret_pics;
|
|
MMAL_STATUS_T err;
|
|
const uint64_t frame_seq = ++sys->frame_seq;
|
|
conv_frame_stash_t * const stash = sys->stash + (frame_seq & 0xf);
|
|
MMAL_BUFFER_HEADER_T * out_buf = NULL;
|
|
|
|
if (sys->err_stream != MMAL_SUCCESS) {
|
|
goto stream_fail;
|
|
}
|
|
|
|
// Check pic fmt corresponds to what we have set up
|
|
// ??? ISP may require flush (disable) but actually seems quite happy
|
|
// without
|
|
if (hw_mmal_vlc_pic_to_mmal_fmt_update(sys->input->format, p_pic))
|
|
{
|
|
msg_Dbg(p_filter, "Reset input port format");
|
|
mmal_port_format_commit(sys->input);
|
|
}
|
|
|
|
if (p_pic->context == NULL) {
|
|
// Can't have stashed subpics if not one of our pics
|
|
if (!sys->needs_copy_in)
|
|
msg_Dbg(p_filter, "No context");
|
|
}
|
|
else if (sys->resizer_type == FILTER_RESIZER_HVS)
|
|
{
|
|
unsigned int sub_no = 0;
|
|
|
|
for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) {
|
|
int rv;
|
|
if ((rv = hw_mmal_subpic_update(VLC_OBJECT(p_filter),
|
|
hw_mmal_pic_sub_buf_get(p_pic, sub_no),
|
|
sys->subs + sub_no,
|
|
&p_pic->format,
|
|
&sys->output->format->es->video.crop,
|
|
frame_seq)) == 0)
|
|
break;
|
|
else if (rv < 0)
|
|
goto fail;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsigned int sub_no = 0;
|
|
for (sub_no = 0; sub_no != SUBS_MAX; ++sub_no) {
|
|
if ((stash->sub_bufs[sub_no] = hw_mmal_pic_sub_buf_get(p_pic, sub_no)) != NULL) {
|
|
mmal_buffer_header_acquire(stash->sub_bufs[sub_no]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!sys->out_fmt_set) {
|
|
sys->out_fmt_set = true;
|
|
|
|
if (sys->is_sliced) {
|
|
// If zc then we will do stride conversion when we copy to arm side
|
|
// so no need to worry about actual pic dimensions here
|
|
if ((err = conv_set_output(p_filter, sys, NULL)) != MMAL_SUCCESS)
|
|
goto fail;
|
|
|
|
sys->out_pool = mmal_port_pool_create(sys->output, sys->output->buffer_num, sys->output->buffer_size);
|
|
}
|
|
else {
|
|
picture_t *pic = filter_NewPicture(p_filter);
|
|
err = conv_set_output(p_filter, sys, pic);
|
|
picture_Release(pic);
|
|
if (err != MMAL_SUCCESS)
|
|
goto fail;
|
|
|
|
sys->out_pool = mmal_pool_create(sys->output->buffer_num, 0);
|
|
}
|
|
|
|
if (sys->out_pool == NULL) {
|
|
msg_Err(p_filter, "Failed to create output pool");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
// Re-enable stuff if the last thing we did was flush
|
|
if ((err = conv_enable_out(p_filter, sys)) != MMAL_SUCCESS ||
|
|
(err = conv_enable_in(p_filter, sys)) != MMAL_SUCCESS)
|
|
goto fail;
|
|
|
|
// We attach pic to buf before stuffing the output port
|
|
// We could attach the pic on output for cma, but it is a lot easier to keep
|
|
// the code common.
|
|
{
|
|
picture_t * const out_pic = filter_NewPicture(p_filter);
|
|
|
|
if (out_pic == NULL)
|
|
{
|
|
msg_Err(p_filter, "Failed to alloc required filter output pic");
|
|
goto fail;
|
|
}
|
|
|
|
out_pic->format.i_sar_den = p_filter->fmt_out.video.i_sar_den;
|
|
out_pic->format.i_sar_num = p_filter->fmt_out.video.i_sar_num;
|
|
|
|
if (sys->is_sliced) {
|
|
vlc_mutex_lock(&sys->lock);
|
|
pic_fifo_put(&sys->slice.pics, out_pic);
|
|
vlc_mutex_unlock(&sys->lock);
|
|
|
|
// Poke any returned pic buffers into output
|
|
// In general this should only happen immediately after enable
|
|
while ((out_buf = mmal_queue_get(sys->out_pool->queue)) != NULL)
|
|
mmal_port_send_buffer(sys->output, out_buf);
|
|
}
|
|
else
|
|
{
|
|
// 1 in - 1 out
|
|
if ((out_buf = mmal_queue_wait(sys->out_pool->queue)) == NULL)
|
|
{
|
|
msg_Err(p_filter, "Failed to get output buffer");
|
|
picture_Release(out_pic);
|
|
goto fail;
|
|
}
|
|
mmal_buffer_header_reset(out_buf);
|
|
|
|
// Attach out_pic to the buffer & ensure it is freed when the buffer is released
|
|
// On a good send callback the pic will be extracted to avoid this
|
|
out_buf->user_data = out_pic;
|
|
mmal_buffer_header_pre_release_cb_set(out_buf, out_buffer_pre_release_cb, NULL);
|
|
|
|
{
|
|
out_buf->data = out_pic->p[0].p_pixels;
|
|
out_buf->alloc_size = out_pic->p[0].i_pitch * out_pic->p[0].i_lines;
|
|
//**** stride ????
|
|
}
|
|
|
|
if ((err = mmal_port_send_buffer(sys->output, out_buf)) != MMAL_SUCCESS)
|
|
{
|
|
msg_Err(p_filter, "Send buffer to output failed");
|
|
goto fail;
|
|
}
|
|
out_buf = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// Stuff into input
|
|
// We assume the BH is already set up with values reflecting pic date etc.
|
|
stash->pts = p_pic->date;
|
|
{
|
|
mmal_decoder_device_t *devsys = GetMMALDeviceOpaque(sys->dec_dev);
|
|
MMAL_BUFFER_HEADER_T *const pic_buf = sys->needs_copy_in ?
|
|
hw_mmal_pic_buf_copied(p_pic, sys->in_pool, sys->input, sys->cma_in_pool,
|
|
devsys->is_cma) :
|
|
hw_mmal_pic_buf_replicated(p_pic, sys->in_pool);
|
|
|
|
// Whether or not we extracted the pic_buf we are done with the picture
|
|
picture_Release(p_pic);
|
|
p_pic = NULL;
|
|
|
|
if (pic_buf == NULL) {
|
|
msg_Err(p_filter, "Pic has no attached buffer");
|
|
goto fail;
|
|
}
|
|
|
|
pic_buf->pts = frame_seq;
|
|
|
|
if ((err = mmal_port_send_buffer(sys->input, pic_buf)) != MMAL_SUCCESS)
|
|
{
|
|
msg_Err(p_filter, "Send buffer to input failed");
|
|
mmal_buffer_header_release(pic_buf);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
// We have a 1 pic latency for everything except the 1st pic which we
|
|
// wait for.
|
|
// This means we get a single static pic out
|
|
if (sys->pic_n++ == 1) {
|
|
return NULL;
|
|
}
|
|
vlc_sem_wait(&sys->sem);
|
|
|
|
// Return a single pending buffer
|
|
vlc_mutex_lock(&sys->lock);
|
|
ret_pics = pic_fifo_get(&sys->ret_pics);
|
|
vlc_mutex_unlock(&sys->lock);
|
|
|
|
if (sys->err_stream != MMAL_SUCCESS)
|
|
goto stream_fail;
|
|
|
|
conv_stash_fixup(p_filter, sys, ret_pics);
|
|
|
|
return ret_pics;
|
|
|
|
stream_fail:
|
|
msg_Err(p_filter, "MMAL error reported by callback");
|
|
fail:
|
|
if (out_buf != NULL)
|
|
mmal_buffer_header_release(out_buf);
|
|
if (p_pic != NULL)
|
|
picture_Release(p_pic);
|
|
filter_Flush(p_filter);
|
|
return NULL;
|
|
}
|
|
|
|
static void CloseConverter(filter_t *p_filter)
|
|
{
|
|
converter_sys_t * const sys = p_filter->p_sys;
|
|
|
|
if (sys == NULL)
|
|
return;
|
|
|
|
// Disables input & output ports
|
|
conv_flush(p_filter);
|
|
|
|
cma_buf_pool_deletez(&sys->cma_in_pool);
|
|
cma_buf_pool_deletez(&sys->cma_out_pool);
|
|
|
|
if (sys->component && sys->component->control->is_enabled)
|
|
mmal_port_disable(sys->component->control);
|
|
|
|
if (sys->component && sys->component->is_enabled)
|
|
mmal_component_disable(sys->component);
|
|
|
|
if (sys->resizer_type == FILTER_RESIZER_HVS)
|
|
{
|
|
for (size_t i = 0; i != SUBS_MAX; ++i) {
|
|
hw_mmal_subpic_close(sys->subs + i);
|
|
}
|
|
}
|
|
|
|
if (sys->out_pool)
|
|
{
|
|
if (sys->is_sliced)
|
|
mmal_port_pool_destroy(sys->output, sys->out_pool);
|
|
else
|
|
mmal_pool_destroy(sys->out_pool);
|
|
}
|
|
|
|
if (sys->in_pool != NULL)
|
|
mmal_pool_destroy(sys->in_pool);
|
|
|
|
if (sys->component)
|
|
mmal_component_release(sys->component);
|
|
|
|
if (p_filter->vctx_out)
|
|
vlc_video_context_Release(p_filter->vctx_out);
|
|
|
|
if (sys->dec_dev)
|
|
vlc_decoder_device_Release(sys->dec_dev);
|
|
|
|
p_filter->p_sys = NULL;
|
|
free(sys);
|
|
}
|
|
|
|
|
|
static MMAL_FOURCC_T filter_enc_in(const video_format_t * const fmt)
|
|
{
|
|
if (hw_mmal_chroma_is_mmal(fmt->i_chroma) || fmt->i_chroma == VLC_CODEC_I420)
|
|
return vlc_to_mmal_video_fourcc(fmt);
|
|
|
|
if (fmt->i_chroma == VLC_CODEC_I420_10L)
|
|
return MMAL_ENCODING_I420;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct vlc_filter_operations filter_ops = {
|
|
.filter_video = conv_filter, .flush = conv_flush, .close = CloseConverter,
|
|
};
|
|
|
|
static int OpenConverter(filter_t *p_filter)
|
|
{
|
|
int ret = VLC_EGENERIC;
|
|
converter_sys_t *sys;
|
|
MMAL_STATUS_T status;
|
|
if (p_filter->fmt_out.video.i_chroma == VLC_CODEC_I420)
|
|
return VLC_EGENERIC;
|
|
const MMAL_FOURCC_T enc_out = vlc_to_mmal_video_fourcc(&p_filter->fmt_out.video);
|
|
const MMAL_FOURCC_T enc_in = filter_enc_in(&p_filter->fmt_in.video);
|
|
|
|
// At least in principle we should deal with any mmal format as input
|
|
if (enc_in == 0 || enc_out == 0)
|
|
return VLC_EGENERIC;
|
|
|
|
filter_resizer_t resizer_type =
|
|
#ifdef NDEBUG
|
|
FILTER_RESIZER_HVS;
|
|
#else // !NDEBUG
|
|
var_GetInteger(p_filter, MMAL_CONVERTER_TYPE_NAME);
|
|
#endif // !NDEBUG
|
|
|
|
retry:
|
|
// ** Make more generic by checking supported encs
|
|
//
|
|
// Must use ISP - HVS can't do this, nor can resizer
|
|
if (enc_in == MMAL_ENCODING_YUVUV64_10) {
|
|
// If resizer selected then just give up
|
|
if (resizer_type == FILTER_RESIZER_RESIZER)
|
|
return VLC_EGENERIC;
|
|
// otherwise downgrade HVS to ISP
|
|
resizer_type = FILTER_RESIZER_ISP;
|
|
}
|
|
// HVS can't do I420
|
|
if (enc_out == MMAL_ENCODING_I420 && resizer_type == FILTER_RESIZER_HVS) {
|
|
resizer_type = FILTER_RESIZER_ISP;
|
|
}
|
|
// Only HVS can deal with SAND30
|
|
if (enc_in == MMAL_ENCODING_YUV10_COL) {
|
|
if (resizer_type != FILTER_RESIZER_HVS)
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
// Check we have a sliced version of the fourcc if we want the resizer
|
|
if (resizer_type == FILTER_RESIZER_RESIZER && pic_to_slice_mmal_fourcc(enc_out) == 0) {
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
sys = calloc(1, sizeof(converter_sys_t));
|
|
if (!sys) {
|
|
ret = VLC_ENOMEM;
|
|
goto fail;
|
|
}
|
|
p_filter->p_sys = sys;
|
|
|
|
// Init stuff the we destroy unconditionally in Close first
|
|
vlc_mutex_init(&sys->lock);
|
|
vlc_sem_init(&sys->sem, 0);
|
|
sys->err_stream = MMAL_SUCCESS;
|
|
pic_fifo_init(&sys->ret_pics);
|
|
pic_fifo_init(&sys->slice.pics);
|
|
|
|
sys->needs_copy_in = !hw_mmal_chroma_is_mmal(p_filter->fmt_in.video.i_chroma);
|
|
sys->in_port_cb_fn = conv_input_port_cb;
|
|
|
|
if (hw_mmal_chroma_is_mmal(p_filter->fmt_in.video.i_chroma))
|
|
{
|
|
assert(p_filter->vctx_in);
|
|
sys->dec_dev = vlc_video_context_HoldDevice(p_filter->vctx_in);
|
|
assert(sys->dec_dev->type == VLC_DECODER_DEVICE_MMAL);
|
|
}
|
|
else
|
|
sys->dec_dev = filter_HoldDecoderDeviceType(p_filter, VLC_DECODER_DEVICE_MMAL);
|
|
if (sys->dec_dev == NULL) {
|
|
msg_Err(p_filter, "missing MMAL decoder device");
|
|
goto fail;
|
|
}
|
|
|
|
if (hw_mmal_chroma_is_mmal(p_filter->fmt_out.video.i_chroma))
|
|
{
|
|
if (hw_mmal_chroma_is_mmal(p_filter->fmt_in.video.i_chroma))
|
|
p_filter->vctx_out = vlc_video_context_Hold(p_filter->vctx_in);
|
|
else {
|
|
p_filter->vctx_out = vlc_video_context_Create(sys->dec_dev, VLC_VIDEO_CONTEXT_MMAL, 0, NULL);
|
|
if (unlikely(p_filter->vctx_out == NULL)) {
|
|
msg_Err(p_filter, "failed to create the output video context");
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *component_name;
|
|
if (resizer_type == FILTER_RESIZER_RESIZER) {
|
|
sys->is_sliced = true;
|
|
sys->out_port_cb_fn = slice_output_port_cb;
|
|
component_name = MMAL_COMPONENT_DEFAULT_RESIZER;
|
|
}
|
|
else {
|
|
sys->is_sliced = false; // Copy directly into filter picture
|
|
sys->out_port_cb_fn = conv_output_port_cb;
|
|
if (resizer_type == FILTER_RESIZER_ISP)
|
|
component_name = MMAL_COMPONENT_ISP_RESIZER;
|
|
else
|
|
component_name = MMAL_COMPONENT_HVS;
|
|
}
|
|
sys->resizer_type = resizer_type;
|
|
|
|
status = mmal_component_create(component_name, &sys->component);
|
|
if (status != MMAL_SUCCESS) {
|
|
if (resizer_type == FILTER_RESIZER_HVS) {
|
|
msg_Warn(p_filter, "Failed to create HVS resizer - retrying with ISP");
|
|
CloseConverter(p_filter);
|
|
resizer_type = FILTER_RESIZER_ISP;
|
|
goto retry;
|
|
}
|
|
msg_Err(p_filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
|
|
component_name, status, mmal_status_to_string(status));
|
|
goto fail;
|
|
}
|
|
sys->output = sys->component->output[0];
|
|
sys->input = sys->component->input[0];
|
|
|
|
sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter;
|
|
status = mmal_port_enable(sys->component->control, conv_control_port_cb);
|
|
if (status != MMAL_SUCCESS) {
|
|
msg_Err(p_filter, "Failed to enable control port %s (status=%"PRIx32" %s)",
|
|
sys->component->control->name, status, mmal_status_to_string(status));
|
|
goto fail;
|
|
}
|
|
|
|
mmal_decoder_device_t *devsys = GetMMALDeviceOpaque(sys->dec_dev);
|
|
assert(devsys != NULL);
|
|
|
|
if (sys->needs_copy_in &&
|
|
(sys->cma_in_pool = cma_buf_pool_new(2, 2, devsys->is_cma, "conv-copy-in")) == NULL)
|
|
{
|
|
msg_Err(p_filter, "Failed to allocate input CMA pool");
|
|
goto fail;
|
|
}
|
|
|
|
sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)p_filter;
|
|
sys->input->format->type = MMAL_ES_TYPE_VIDEO;
|
|
sys->input->format->encoding = enc_in;
|
|
sys->input->format->encoding_variant = MMAL_ENCODING_I420;
|
|
hw_mmal_vlc_fmt_to_mmal_fmt(sys->input->format, &p_filter->fmt_in.video);
|
|
port_parameter_set_bool(sys->input, MMAL_PARAMETER_ZERO_COPY, 1);
|
|
|
|
mmal_log_dump_format(sys->input->format);
|
|
|
|
status = mmal_port_format_commit(sys->input);
|
|
if (status != MMAL_SUCCESS) {
|
|
msg_Err(p_filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
|
|
sys->input->name, status, mmal_status_to_string(status));
|
|
goto fail;
|
|
}
|
|
sys->input->buffer_size = sys->input->buffer_size_recommended;
|
|
sys->input->buffer_num = NUM_DECODER_BUFFER_HEADERS;
|
|
|
|
if ((status = conv_enable_in(p_filter, sys)) != MMAL_SUCCESS)
|
|
goto fail;
|
|
|
|
port_parameter_set_bool(sys->output, MMAL_PARAMETER_ZERO_COPY, sys->is_sliced);
|
|
|
|
status = mmal_component_enable(sys->component);
|
|
if (status != MMAL_SUCCESS) {
|
|
msg_Err(p_filter, "Failed to enable component %s (status=%"PRIx32" %s)",
|
|
sys->component->name, status, mmal_status_to_string(status));
|
|
goto fail;
|
|
}
|
|
|
|
if ((sys->in_pool = mmal_pool_create(sys->input->buffer_num, 0)) == NULL)
|
|
{
|
|
msg_Err(p_filter, "Failed to create input pool");
|
|
goto fail;
|
|
}
|
|
|
|
if (sys->resizer_type == FILTER_RESIZER_HVS)
|
|
{
|
|
unsigned int i;
|
|
for (i = 0; i != SUBS_MAX; ++i) {
|
|
if (hw_mmal_subpic_open(VLC_OBJECT(p_filter), sys->subs + i, sys->component->input[i + 1], -1, i + 1) != MMAL_SUCCESS)
|
|
{
|
|
msg_Err(p_filter, "Failed to open subpic %d", i);
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
p_filter->ops = &filter_ops;
|
|
// video_drain NIF in filter structure
|
|
|
|
return VLC_SUCCESS;
|
|
|
|
fail:
|
|
CloseConverter(p_filter);
|
|
|
|
if (resizer_type != FILTER_RESIZER_RESIZER && status == MMAL_ENOMEM) {
|
|
resizer_type = FILTER_RESIZER_RESIZER;
|
|
msg_Warn(p_filter, "Lack of memory to use HVS/ISP: trying resizer");
|
|
goto retry;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|