[vlc-devel] [PATCH 6/6] Add mmal-based deinterlace filter

Julian Scheel julian at jusst.de
Thu Jun 19 11:22:21 CEST 2014


Add a video_filter module which uses the Broadcom Multi Media Abstraction
Layer (mmal) to deinterlace video in hardware.

As of now it is only working in opaque mode, due to a firmware limitation.
Once this is fixed it can be easily extended to support I420 encoding and use
it as simple chroma filter.

Signed-off-by: Julian Scheel <julian at jusst.de>
---
 configure.ac                            |  27 ++
 modules/video_filter/Modules.am         |   7 +
 modules/video_filter/deinterlace/mmal.c | 474 ++++++++++++++++++++++++++++++++
 3 files changed, 508 insertions(+)
 create mode 100644 modules/video_filter/deinterlace/mmal.c

diff --git a/configure.ac b/configure.ac
index 2611a11..781dd4d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2174,6 +2174,33 @@ if test "${enable_mmal_codec}" != "no"; then
 fi
 
 dnl
+dnl MMAL deinterlace filter plugin
+dnl
+AC_ARG_ENABLE(mmal-deinterlace,
+  AS_HELP_STRING([--enable-mmal-deinterlace],
+    [Multi-Media Abstraction Layer (MMAL) based deinterlace plugin for Raspberry Pi (default enable)]))
+if test "${enable_mmal_deinterlace}" != "no"; then
+  VLC_SAVE_FLAGS
+  LDFLAGS="${LDFLAGS} -L/opt/vc/lib"
+  CPPFLAGS="${CPPFLAGS} -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vcos/linux"
+  AC_CHECK_HEADERS(interface/mmal/mmal.h,
+    [ AC_CHECK_LIB(bcm_host, bcm_host_init, [
+        VLC_ADD_PLUGIN([mmal_deinterlace])
+        VLC_ADD_LDFLAGS([mmal_deinterlace],[ -L/opt/vc/lib ])
+        VLC_ADD_CFLAGS([mmal_deinterlace],[ -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vcos/linux ])
+        VLC_ADD_LIBS([mmal_deinterlace],[ -lbcm_host -lmmal ]) ], [
+          AS_IF([test "${enable_mmal_deinterlace}" = "yes"],
+            [ AC_MSG_ERROR([Cannot find bcm library...]) ],
+            [ AC_MSG_WARN([Cannot find bcm library...]) ])
+          ],
+        [])
+    ] , [ AS_IF([test "${enable_mmal_deinterlace}" = "yes"],
+      [ AC_MSG_ERROR([Cannot find development headers for mmal...]) ],
+      [ AC_MSG_WARN([Cannot find development headers for mmal...]) ]) ])
+  VLC_RESTORE_FLAGS
+fi
+
+dnl
 dnl CrystalHD codec plugin
 dnl
 AC_ARG_ENABLE(crystalhd,
diff --git a/modules/video_filter/Modules.am b/modules/video_filter/Modules.am
index 3bb8cdb..42e10e8 100644
--- a/modules/video_filter/Modules.am
+++ b/modules/video_filter/Modules.am
@@ -31,6 +31,13 @@ libdeinterlace_plugin_la_CFLAGS += -DCAN_COMPILE_ARM
 endif
 video_filter_LTLIBRARIES += libdeinterlace_plugin.la
 
+libmmal_deinterlace_plugin_la_SOURCES = \
+	deinterlace/mmal.c
+libmmal_deinterlace_plugin_la_CFLAGS = $(AM_CFLAGS) $(CFLAGS_mmal_deinterlace)
+libmmal_deinterlace_plugin_la_LDFLAGS = $(AM_LDFLAGS) $(LDFLAGS_mmal_deinterlace)
+libmmal_deinterlace_plugin_la_LIBADD = $(LIBS_mmal_deinterlace)
+video_filter_LTLIBRARIES += libmmal_deinterlace_plugin.la
+
 libdynamicoverlay_plugin_la_SOURCES = \
 	dynamicoverlay/dynamicoverlay_buffer.c \
 	dynamicoverlay/dynamicoverlay_queue.c \
diff --git a/modules/video_filter/deinterlace/mmal.c b/modules/video_filter/deinterlace/mmal.c
new file mode 100644
index 0000000..65eb39c
--- /dev/null
+++ b/modules/video_filter/deinterlace/mmal.c
@@ -0,0 +1,474 @@
+/*****************************************************************************
+ * mmal.c: MMAL-based deinterlace plugin for Raspberry Pi
+ *****************************************************************************
+ * Copyright © 2014 jusst technologies GmbH
+ * $Id$
+ *
+ * Authors: Julian Scheel <julian at jusst.de>
+ *          Dennis Hamester <dennis.hamester at gmail.com>
+ *
+ * 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 <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_filter.h>
+
+#include <bcm_host.h>
+#include <interface/mmal/mmal.h>
+#include <interface/mmal/util/mmal_util.h>
+#include <interface/mmal/util/mmal_default_components.h>
+
+#define NUM_ACTUAL_OPAQUE_BUFFERS 22
+#define NUM_EXTRA_BUFFERS 5
+#define MIN_NUM_BUFFERS_IN_TRANSIT 2
+
+#define MAX_ALLOWED_FPS 30
+
+static int Open(filter_t *filter);
+static void Close(filter_t *filter);
+
+vlc_module_begin()
+    set_shortname(N_("MMAL deinterlace"))
+    set_description(N_("MMAL-based deinterlace filter plugin"))
+    set_capability("video filter2", 10)
+    set_category(CAT_VIDEO)
+    set_subcategory(SUBCAT_VIDEO_VFILTER)
+    set_callbacks(Open, Close)
+    add_shortcut("deinterlace")
+vlc_module_end()
+
+struct filter_sys_t {
+    MMAL_COMPONENT_T *component;
+    MMAL_PORT_T *input;
+    MMAL_POOL_T *input_pool;
+    MMAL_PORT_T *output;
+    MMAL_POOL_T *output_pool;
+
+    video_format_t fmt_out;
+
+    MMAL_QUEUE_T *filtered_pictures;
+    vlc_mutex_t mutex;
+};
+
+static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
+static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
+static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
+static picture_t *deinterlace(filter_t *filter, picture_t *picture);
+
+#define MMAL_COMPONENT_DEFAULT_DEINTERLACE "vc.ril.image_fx"
+
+static int Open(filter_t *filter)
+{
+    int32_t frame_duration = 1000000 /
+        ((double)filter->fmt_in.video.i_frame_rate /
+        filter->fmt_in.video.i_frame_rate_base);
+    MMAL_PARAMETER_IMAGEFX_PARAMETERS_T imfx_param = {
+            { MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS, sizeof(imfx_param) },
+            MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV,
+            2,
+            { 3, frame_duration }
+    };
+    MMAL_PARAMETER_UINT32_T extra_buffers;
+
+    int ret = VLC_SUCCESS;
+    MMAL_STATUS_T status;
+    filter_sys_t *sys;
+
+    msg_Dbg(filter, "Try to open mmal_deinterlace filter. frame_duration: %d!", frame_duration);
+
+    if (filter->fmt_in.video.i_chroma != VLC_CODEC_MMAL_OPAQUE)
+        return VLC_EGENERIC;
+
+    if (filter->fmt_out.video.i_chroma != VLC_CODEC_MMAL_OPAQUE)
+        return VLC_EGENERIC;
+
+    sys = calloc(1, sizeof(filter_sys_t));
+    if (!sys)
+        return VLC_ENOMEM;
+    filter->p_sys = sys;
+
+    bcm_host_init();
+
+    status = mmal_component_create(MMAL_COMPONENT_DEFAULT_DEINTERLACE, &sys->component);
+    if (status != MMAL_SUCCESS) {
+        msg_Err(filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
+                MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status));
+        ret = VLC_EGENERIC;
+        goto out;
+    }
+
+    status = mmal_port_parameter_set(sys->component->output[0], &imfx_param.hdr);
+    if (status != MMAL_SUCCESS) {
+        msg_Err(filter, "Failed to configure MMAL component %s (status=%"PRIx32" %s)",
+                MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status));
+        ret = VLC_EGENERIC;
+        goto out;
+    }
+
+    sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
+    status = mmal_port_enable(sys->component->control, control_port_cb);
+    if (status != MMAL_SUCCESS) {
+        msg_Err(filter, "Failed to enable control port %s (status=%"PRIx32" %s)",
+                sys->component->control->name, status, mmal_status_to_string(status));
+        ret = VLC_EGENERIC;
+        goto out;
+    }
+
+    sys->input = sys->component->input[0];
+    sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
+    if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE)
+        sys->input->format->encoding = MMAL_ENCODING_OPAQUE;
+    sys->input->format->es->video.width = filter->fmt_in.video.i_width;
+    sys->input->format->es->video.height = filter->fmt_in.video.i_height;
+    sys->input->format->es->video.crop.x = 0;
+    sys->input->format->es->video.crop.y = 0;
+    sys->input->format->es->video.crop.width = filter->fmt_in.video.i_width;
+    sys->input->format->es->video.crop.height = filter->fmt_in.video.i_height;
+    sys->input->format->es->video.par.num = filter->fmt_in.video.i_sar_num;
+    sys->input->format->es->video.par.den = filter->fmt_in.video.i_sar_den;
+
+    es_format_Copy(&filter->fmt_out, &filter->fmt_in);
+
+    status = mmal_port_format_commit(sys->input);
+    if (status != MMAL_SUCCESS) {
+        msg_Err(filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
+                        sys->input->name, status, mmal_status_to_string(status));
+        ret = VLC_EGENERIC;
+        goto out;
+    }
+    sys->input->buffer_size = sys->input->buffer_size_recommended;
+
+    sys->input->buffer_num = NUM_ACTUAL_OPAQUE_BUFFERS;
+    status = mmal_port_enable(sys->input, input_port_cb);
+    if (status != MMAL_SUCCESS) {
+        msg_Err(filter, "Failed to enable input port %s (status=%"PRIx32" %s)",
+                sys->input->name, status, mmal_status_to_string(status));
+        ret = VLC_EGENERIC;
+        goto out;
+    }
+
+    sys->output = sys->component->output[0];
+    sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
+    mmal_format_full_copy(sys->output->format, sys->input->format);
+
+#if 1
+    if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) {
+        extra_buffers.hdr.id = MMAL_PARAMETER_EXTRA_BUFFERS;
+        extra_buffers.hdr.size = sizeof(MMAL_PARAMETER_UINT32_T);
+        extra_buffers.value = NUM_EXTRA_BUFFERS;
+        status = mmal_port_parameter_set(sys->output, &extra_buffers.hdr);
+        if (status != MMAL_SUCCESS) {
+            msg_Err(filter, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)",
+                    status, mmal_status_to_string(status));
+            ret = VLC_EGENERIC;
+            goto out;
+        }
+    }
+#endif
+
+    status = mmal_port_format_commit(sys->output);
+    if (status != MMAL_SUCCESS) {
+        msg_Err(filter, "Failed to commit format for output port %s (status=%"PRIx32" %s)",
+                        sys->input->name, status, mmal_status_to_string(status));
+        ret = VLC_EGENERIC;
+        goto out;
+    }
+
+    sys->output->buffer_num = NUM_ACTUAL_OPAQUE_BUFFERS;
+    status = mmal_port_enable(sys->output, output_port_cb);
+    if (status != MMAL_SUCCESS) {
+        msg_Err(filter, "Failed to enable output port %s (status=%"PRIx32" %s)",
+                sys->output->name, status, mmal_status_to_string(status));
+        ret = VLC_EGENERIC;
+        goto out;
+    }
+
+    status = mmal_component_enable(sys->component);
+    if (status != MMAL_SUCCESS) {
+        msg_Err(filter, "Failed to enable component %s (status=%"PRIx32" %s)",
+                sys->component->name, status, mmal_status_to_string(status));
+        ret = VLC_EGENERIC;
+        goto out;
+    }
+
+    sys->output_pool = mmal_pool_create(sys->output->buffer_num_recommended, 0);
+    sys->input_pool = mmal_pool_create(sys->input->buffer_num, 0);
+    sys->filtered_pictures = mmal_queue_create();
+
+    filter->pf_video_filter = deinterlace;
+    vlc_mutex_init_recursive(&sys->mutex);
+
+out:
+    if (ret != VLC_SUCCESS)
+        Close(filter);
+
+    return ret;
+}
+
+static void Close(filter_t *filter)
+{
+    filter_sys_t *sys = filter->p_sys;
+    MMAL_BUFFER_HEADER_T *buffer;
+
+    if (!sys)
+        return;
+
+    if (sys->component && sys->component->control->is_enabled)
+        mmal_port_disable(sys->component->control);
+
+    if (sys->input && sys->input->is_enabled)
+        mmal_port_disable(sys->input);
+
+    if (sys->output && sys->output->is_enabled)
+        mmal_port_disable(sys->output);
+
+    if (sys->component && sys->component->is_enabled)
+        mmal_component_disable(sys->component);
+
+    if (sys->input_pool)
+        mmal_pool_destroy(sys->input_pool);
+
+    while ((buffer = mmal_queue_get(sys->filtered_pictures))) {
+        picture_t *pic = (picture_t *)buffer->user_data;
+        picture_Release(pic);
+
+        buffer->user_data = NULL;
+        buffer->alloc_size = 0;
+        buffer->data = NULL;
+        mmal_buffer_header_release(buffer);
+    }
+
+    if (sys->filtered_pictures)
+        mmal_queue_destroy(sys->filtered_pictures);
+
+    if (sys->output_pool)
+        mmal_pool_destroy(sys->output_pool);
+
+    if (sys->component)
+        mmal_component_release(sys->component);
+
+    free(sys);
+
+    bcm_host_deinit();
+}
+
+static int send_output_buffer(filter_t *filter)
+{
+    filter_sys_t *sys = filter->p_sys;
+    MMAL_BUFFER_HEADER_T *buffer;
+    MMAL_STATUS_T status;
+    picture_t *picture;
+    int ret = 0;
+
+    buffer = mmal_queue_get(sys->output_pool->queue);
+    if (!buffer) {
+        msg_Warn(filter, "Failed to get new buffer");
+        ret = -1;
+        goto out;
+    }
+
+    picture = filter_NewPicture(filter);
+    if (!picture) {
+        msg_Warn(filter, "Failed to get new picture");
+        mmal_buffer_header_release(buffer);
+        ret = -1;
+        goto out;
+    }
+
+    mmal_buffer_header_reset(buffer);
+    buffer->user_data = picture;
+    buffer->cmd = 0;
+    buffer->alloc_size = sys->output->buffer_size;
+    buffer->data = picture->p[0].p_pixels;
+
+    status = mmal_port_send_buffer(sys->output, buffer);
+    if (status != MMAL_SUCCESS) {
+        msg_Err(filter, "Failed to send buffer to output port (status=%"PRIx32" %s)",
+                status, mmal_status_to_string(status));
+        mmal_buffer_header_release(buffer);
+        filter_DeletePicture(filter, picture);
+        ret = -1;
+        goto out;
+    }
+
+out:
+    return ret;
+}
+
+static void fill_output_port(filter_t *filter)
+{
+    filter_sys_t *sys = filter->p_sys;
+    /* allow at least 2 buffers in transit */
+    unsigned max_buffers_in_transit = __MAX(sys->output->buffer_num_recommended,
+            MIN_NUM_BUFFERS_IN_TRANSIT);
+    unsigned buffers_available = mmal_queue_length(sys->output_pool->queue);
+    unsigned buffers_in_transit = sys->output_pool->headers_num - buffers_available -
+        mmal_queue_length(sys->filtered_pictures);
+    unsigned buffers_to_send = max_buffers_in_transit - buffers_in_transit;
+    unsigned i;
+
+    if (buffers_to_send > buffers_available)
+        buffers_to_send = buffers_available;
+
+#ifndef NDEBUG
+    msg_Dbg(filter, "Send %d buffers to output port (available: %d, in_transit: %d, buffer_num: %d)",
+                    buffers_to_send, buffers_available, buffers_in_transit,
+                    sys->output->buffer_num);
+#endif
+    for (i = 0; i < buffers_to_send; ++i) {
+        if (send_output_buffer(filter) < 0)
+            break;
+    }
+}
+
+static picture_t *deinterlace(filter_t *filter, picture_t *picture)
+{
+    filter_sys_t *sys = filter->p_sys;
+    MMAL_BUFFER_HEADER_T *buffer;
+    picture_t *out_picture = NULL;
+    picture_t *ret = NULL;
+    MMAL_STATUS_T status;
+
+    /*
+     * Send output buffers
+     */
+    if (sys->output_pool) {
+        int i = 0;
+        vlc_mutex_lock(&sys->mutex);
+        while((buffer = mmal_queue_get(sys->filtered_pictures))) {
+            i++;
+            if (!out_picture) {
+                out_picture = (picture_t *)buffer->user_data;
+                ret = out_picture;
+            } else {
+                out_picture->p_next = (picture_t *)buffer->user_data;
+                out_picture = out_picture->p_next;
+            }
+            out_picture->date = buffer->pts;
+
+            buffer->user_data = NULL;
+            buffer->alloc_size = 0;
+            buffer->data = NULL;
+            mmal_buffer_header_release(buffer);
+        }
+        if (out_picture)
+            out_picture->p_next = NULL;
+        fill_output_port(filter);
+        vlc_mutex_unlock(&sys->mutex);
+    }
+
+    /*
+     * Process input
+     */
+    if (!picture)
+        goto out;
+
+    /*
+     * Do not deinterlace if framerate is > 30fps. This is unlikely to work
+     * well in any case.
+     */
+    if (picture) {
+        double fps = (double)picture->format.i_frame_rate / picture->format.i_frame_rate_base;
+        if (fps > MAX_ALLOWED_FPS)
+            return picture;
+    }
+
+    vlc_mutex_lock(&sys->mutex);
+    buffer = mmal_queue_timedwait(sys->input_pool->queue, 2);
+    if (!buffer) {
+        msg_Err(filter, "Failed to retrieve buffer header for input picture");
+        goto out;
+    }
+
+    mmal_buffer_header_reset(buffer);
+    buffer->user_data = picture;
+    buffer->pts = picture->date;
+    buffer->cmd = 0;
+    buffer->alloc_size = sys->input->buffer_size;
+    buffer->length = sys->input->buffer_size;
+    buffer->data = picture->p[0].p_pixels;
+
+    status = mmal_port_send_buffer(sys->input, buffer);
+    if (status != MMAL_SUCCESS) {
+        msg_Err(filter, "Failed to send buffer to input port (status=%"PRIx32" %s)",
+                status, mmal_status_to_string(status));
+        goto out;
+    }
+    vlc_mutex_unlock(&sys->mutex);
+
+out:
+    return ret;
+}
+
+static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+{
+    filter_t *filter = (filter_t *)port->userdata;
+    MMAL_STATUS_T status;
+
+    if (buffer->cmd == MMAL_EVENT_ERROR) {
+        status = *(uint32_t *)buffer->data;
+        msg_Err(filter, "MMAL error %"PRIx32" \"%s\"", status,
+                mmal_status_to_string(status));
+    }
+
+    mmal_buffer_header_release(buffer);
+}
+
+static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+{
+    picture_t *picture = (picture_t *)buffer->user_data;
+    filter_t *filter = (filter_t *)port->userdata;
+    filter_sys_t *sys = filter->p_sys;
+
+    buffer->user_data = NULL;
+    mmal_buffer_header_release(buffer);
+    vlc_mutex_lock(&sys->mutex);
+    if (picture)
+        picture_Release(picture);
+    vlc_mutex_unlock(&sys->mutex);
+}
+
+static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+{
+    filter_t *filter = (filter_t *)port->userdata;
+    filter_sys_t *sys = filter->p_sys;
+    picture_t *picture;
+
+    vlc_mutex_lock(&sys->mutex);
+    if (buffer->cmd == 0) {
+        if (buffer->length > 0) {
+            mmal_queue_put(sys->filtered_pictures, buffer);
+            fill_output_port(filter);
+        } else {
+            picture = (picture_t *)buffer->user_data;
+            filter_DeletePicture(filter, picture);
+            buffer->user_data = NULL;
+            buffer->alloc_size = 0;
+            buffer->data = NULL;
+            mmal_buffer_header_release(buffer);
+        }
+    } else if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) {
+        msg_Warn(filter, "MMAL_EVENT_FORMAT_CHANGED seen but not handled");
+        mmal_buffer_header_release(buffer);
+    } else {
+        mmal_buffer_header_release(buffer);
+    }
+    vlc_mutex_unlock(&sys->mutex);
+}
-- 
2.0.0




More information about the vlc-devel mailing list