[vlc-devel] [PATCH 03/14] video_filter: add a video filter converting between different 3D stereo formats

Rémi Denis-Courmont remi at remlab.net
Tue Sep 9 20:03:33 CEST 2014


Le mardi 9 septembre 2014, 19:08:22 Felix Abecassis a écrit :
> ---
>  modules/video_filter/Modules.am         |   2 +
>  modules/video_filter/stereo3d_convert.c | 408
> ++++++++++++++++++++++++++++++++ 2 files changed, 410 insertions(+)
>  create mode 100644 modules/video_filter/stereo3d_convert.c
> 
> diff --git a/modules/video_filter/Modules.am
> b/modules/video_filter/Modules.am index 3bb8cdb..e1eb973 100644
> --- a/modules/video_filter/Modules.am
> +++ b/modules/video_filter/Modules.am
> @@ -89,6 +89,7 @@ SOURCES_blendbench = blendbench.c
>  SOURCES_postproc = postproc.c
>  SOURCES_scene = scene.c
>  SOURCES_sepia = sepia.c
> +SOURCES_stereo3d_convert = stereo3d_convert.c
>  SOURCES_yuvp = yuvp.c
>  SOURCES_antiflicker = antiflicker.c
>  SOURCES_atmo = atmo/atmo.cpp \
> @@ -154,6 +155,7 @@ video_filter_LTLIBRARIES += \
>  	libsepia_plugin.la \
>  	libsharpen_plugin.la \
>  	libsubsdelay_plugin.la \
> +	libstereo3d_convert_plugin.la \
>  	libtransform_plugin.la \
>  	libwave_plugin.la \
>  	libgradfun_plugin.la \
> diff --git a/modules/video_filter/stereo3d_convert.c
> b/modules/video_filter/stereo3d_convert.c new file mode 100644
> index 0000000..9f9dd61
> --- /dev/null
> +++ b/modules/video_filter/stereo3d_convert.c
> @@ -0,0 +1,408 @@
> +/**************************************************************************
> *** + * stereo3d_convert.c : conversion filter between different 3D stereo
> types +
> ***************************************************************************
> ** + * Copyright (C) 2014 VLC authors and VideoLAN
> + *
> + * Author: Felix Abecassis <felix.abecassis 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_filter.h>
> +#include <vlc_modules.h>
> +#include <vlc_plugin.h>
> +
> +#include <math.h>
> +
> +static int Open(vlc_object_t *);
> +static void Close(vlc_object_t *);
> +static picture_t *Filter(filter_t *, picture_t *);
> +
> +#define CFG_PREFIX "stereo3dconvert-"
> +
> +#define CONVERT2D_TEXT N_("Stereo 3D to 2D conversion mode")
> +static const char * const convert2d_list[] = { "unchanged", "left", "right"
> }; +static const char * const convert2d_list_text[] = { N_("Both eyes
> (unchanged)"), +    N_("Left eye only"), N_("Right eye only") };
> +
> +typedef enum
> +{
> +    UNCHANGED = 0,
> +    LEFT_EYE,
> +    RIGHT_EYE
> +} convert2d_mode_t;
> +
> +vlc_module_begin()
> +    set_description(N_("Stereo 3D conversion filter"))
> +    set_shortname(N_( "Stereo 3D conversion"))
> +    set_capability("video filter2", 100)
> +    set_category(CAT_VIDEO)
> +    set_subcategory(SUBCAT_VIDEO_VFILTER)
> +
> +    add_string(CFG_PREFIX "2d", "unchanged", CONVERT2D_TEXT,
> CONVERT2D_TEXT, false) +        change_string_list(convert2d_list,
> convert2d_list_text)
> +
> +    add_shortcut("stereo3d_convert")
> +    set_callbacks(Open, Close)
> +vlc_module_end()
> +
> +struct filter_sys_t
> +{
> +    filter_t *scale_filter;
> +    convert2d_mode_t convert2d_mode;
> +};
> +
> +static picture_t *BufferNew(filter_t *filter)
> +{
> +    picture_t *pic = (picture_t*)filter->owner.sys;
> +    return pic;

This looks very fishy. What if the scaler allocates two pictures in a row?

> +}
> +
> +static void BufferDelete(filter_t *filter, picture_t *p_picture)
> +{
> +    VLC_UNUSED(filter);
> +    picture_Release(p_picture);
> +}
> +
> +static int Open(vlc_object_t *p_this)
> +{
> +    filter_t *filter = (filter_t *)p_this;
> +
> +    const video_format_t *fmt_in = &filter->fmt_in.video;
> +    video_format_t *fmt_out = &filter->fmt_out.video;
> +    if (fmt_in->stereo.mode == fmt_out->stereo.mode)
> +        return VLC_EGENERIC;
> +
> +    filter_sys_t *sys = calloc(1, sizeof(*sys));
> +    if (!sys)
> +        return VLC_ENOMEM;
> +    filter->p_sys = sys;
> +
> +    const vlc_chroma_description_t *chroma_desc =
> vlc_fourcc_GetChromaDescription(fmt_in->i_chroma); +    if
> (chroma_desc->plane_count == 0)
> +        goto error;
> +
> +    /*
> +     * A video filter is needed to convert from the source
> +     * stereoscopic type to the target stereoscopic type.
> +     *
> +     * For instance when converting from a SBS_LR picture of dimension
> +     * (w1, h1) to a TB_LR picture of dimension (w2, h2). The left
> +     * rectangle of size (w1/2, h1) of the input picture is stretched
> +     * to the top rectangle of size (w2, h2/2) of the output picture.
> +     */
> +    filter_t *scale_filter = (filter_t *)vlc_object_create(filter,
> sizeof(filter_t)); +    if (!scale_filter)
> +        goto error;
> +    sys->scale_filter = scale_filter;
> +
> +    es_format_Init(&scale_filter->fmt_in, VIDEO_ES, 0);
> +    scale_filter->fmt_in.video = filter->fmt_in.video;
> +    es_format_Init(&scale_filter->fmt_out, VIDEO_ES, 0);
> +    scale_filter->fmt_out.video = filter->fmt_out.video;
> +    scale_filter->fmt_in.video.stereo.mode = VLC_STEREO3D_2D;
> +    scale_filter->fmt_out.video.stereo.mode = VLC_STEREO3D_2D;
> +
> +    /*
> +     * Overload the picture allocation function of the internal filter
> +     * in order to pass the picture to render to.
> +     */
> +    scale_filter->owner.video.buffer_new = BufferNew;
> +    scale_filter->owner.video.buffer_del = BufferDelete;
> +    scale_filter->p_module = module_need(sys->scale_filter, "video
> filter2", NULL, false); +    if (!scale_filter->p_module)
> +        goto error;
> +
> +    char *convert2d_mode = var_InheritString(filter, CFG_PREFIX "2d");
> +    if (convert2d_mode)
> +    {
> +        if (!strcmp(convert2d_mode, "left"))
> +            sys->convert2d_mode = LEFT_EYE;
> +        else if (!strcmp(convert2d_mode, "right"))
> +            sys->convert2d_mode = RIGHT_EYE;
> +        else
> +            sys->convert2d_mode = UNCHANGED;
> +        free(convert2d_mode);
> +    }
> +
> +    filter->pf_video_filter = Filter;
> +
> +    return VLC_SUCCESS;
> +
> +error:
> +    Close(p_this);
> +    return VLC_EGENERIC;
> +}
> +
> +static void Close(vlc_object_t *p_this)
> +{
> +    filter_t *filter = (filter_t *)p_this;
> +    filter_sys_t *sys = filter->p_sys;
> +
> +    if (sys)
> +    {
> +        if (sys->scale_filter)
> +        {
> +            if (sys->scale_filter->p_module)
> +                module_unneed(sys->scale_filter,
> sys->scale_filter->p_module); +           
> vlc_object_release(sys->scale_filter);
> +        }
> +    }
> +
> +    free(sys);
> +}
> +
> +/*
> + * Create two pictures storing the left and right eyes of src
> + * according to the stereoscopic type. The created pictures simply
> + * wrap the original pictures therefore they should not be freed.
> + *
> + * For instance, with an TB_LR picture, *res_left will point to the
> + * top half of src and *res_right will point to the bottom half of
> + * src. The height of each picture is half the height of the original
> + * picture.
> + */
> +static void ExtractPictures(filter_t *filter,
> +                            picture_t *src, const video_format_t
> *src_format, +                            picture_t **res_left, picture_t
> **res_right) +{
> +    filter_sys_t *sys = filter->p_sys;
> +
> +    video_format_t res_format = src->format;
> +    res_format.stereo.mode = VLC_STEREO3D_2D;
> +    res_format.stereo.flags = 0;
> +
> +    *res_left = NULL;
> +    *res_right = NULL;
> +
> +    const stereo3d_format_t *stereo = &src_format->stereo;
> +
> +    picture_resource_t src_resource = { .p_sys = src->p_sys };
> +    for (int i = 0; i < src->i_planes; ++i)
> +    {
> +        src_resource.p[i].p_pixels = src->p[i].p_pixels;
> +        src_resource.p[i].i_lines  = src->p[i].i_lines;
> +        src_resource.p[i].i_pitch  = src->p[i].i_pitch;
> +    }
> +    picture_resource_t left_resource = src_resource;
> +    picture_resource_t right_resource = src_resource;
> +
> +    /*
> +     * Depending on the stereoscopic type, we have to tweak the pitch
> +     * and/or the pointer in order to wrap the original picture.
> +     *
> +     * Computations are made assuming left-right (LR) ordering,
> +     * right-left ordering is supported by swapping pictures at the
> +     * end.
> +     */
> +    if (stereo->mode == VLC_STEREO3D_SBS)
> +    {
> +        res_format.i_visible_width /= 2;
> +        res_format.i_width = res_format.i_visible_width;
> +
> +        /* Advance to the start of the right picture. */
> +        for (int i = 0; i < src->i_planes; ++i)
> +            right_resource.p[i].p_pixels += src->p[i].i_visible_pitch / 2;
> +    }
> +    else if (stereo->mode == VLC_STEREO3D_TB)
> +    {
> +        res_format.i_visible_height /= 2;
> +        res_format.i_height = res_format.i_visible_height;
> +
> +        for (int i = 0; i < src->i_planes; ++i)
> +        {
> +            left_resource.p[i].i_lines = src->p[i].i_visible_lines / 2;
> +            right_resource.p[i].i_lines = src->p[i].i_visible_lines / 2;
> +
> +            /* Skip the left picture (i.e. the first half of the buffer) */
> +            right_resource.p[i].p_pixels += src->p[i].i_pitch *
> left_resource.p[i].i_lines; +        }
> +    }
> +    else if (stereo->mode == VLC_STEREO3D_ROW)
> +    {
> +        res_format.i_visible_height /= 2;
> +        res_format.i_height = res_format.i_visible_height;
> +
> +        for (int i = 0; i < src->i_planes; ++i)
> +        {
> +            left_resource.p[i].i_lines = src->p[i].i_visible_lines / 2;
> +            right_resource.p[i].i_lines = src->p[i].i_visible_lines / 2;
> +
> +            /*
> +             * Double the pitch of left and right pictures so only one
> +             * line over two is used.
> +             */
> +            right_resource.p[i].i_pitch *= 2;
> +            left_resource.p[i].i_pitch *= 2;
> +
> +            /* Advance to second line. */
> +            right_resource.p[i].p_pixels += src->p[i].i_pitch;
> +        }
> +    }
> +    else if (stereo->mode == VLC_STEREO3D_COL)
> +    {
> +        res_format.i_visible_width /= 2;
> +        res_format.i_width = res_format.i_visible_width;
> +
> +        /* Advance to the start of the right picture. */
> +        for (int i = 0; i < src->i_planes; ++i)
> +        {
> +            right_resource.p[i].p_pixels += src->p[i].i_pixel_pitch;
> +        }
> +    }
> +
> +    picture_t *left = picture_NewFromResource(&res_format, &left_resource);
> +    if (!left)
> +        return;
> +
> +    picture_t *right = picture_NewFromResource(&res_format,
> &right_resource); +    if (!right)
> +    {
> +        picture_Release(left);
> +        return;
> +    }
> +
> +    if (stereo->mode == VLC_STEREO3D_COL)
> +    {
> +        for (int i = 0; i < left->i_planes; ++i)
> +        {
> +            left->p[i].i_pixel_pitch *= 2;
> +            right->p[i].i_pixel_pitch *= 2;
> +        }
> +    }
> +
> +    if (stereo->mode == VLC_STEREO3D_2D)
> +    {
> +        if (sys->convert2d_mode == LEFT_EYE)
> +            *res_left = left;
> +        else
> +            *res_right = right;
> +        return;
> +    }
> +
> +    /* Swap pictures pointers in case of right-left ordering */
> +    bool is_swapped = stereo->flags & VLC_STEREO3D_SWAP_EYES;
> +    if (is_swapped)
> +    {
> +        *res_left = right;
> +        *res_right = left;
> +    }
> +    else
> +    {
> +        *res_left = left;
> +        *res_right = right;
> +    }
> +}
> +
> +static void GenericRescale(picture_t *dst, const picture_t *src)
> +{
> +    for (int i = 0; i < src->i_planes; ++i)
> +    {
> +        const uint8_t *s = src->p[i].p_pixels;
> +        uint8_t *d = dst->p[i].p_pixels;
> +
> +        uint32_t src_pitch = src->p[i].i_pitch;
> +        uint32_t dst_pitch = dst->p[i].i_pitch;
> +        uint32_t src_pixel_pitch = src->p[i].i_pixel_pitch;
> +        uint32_t dst_pixel_pitch = dst->p[i].i_pixel_pitch;
> +
> +        float step_r = src->p[i].i_visible_lines /
> (float)dst->p[i].i_visible_lines; +        float step_c =
> src->p[i].i_visible_pitch / (float)dst->p[i].i_visible_pitch; +       
> float pos_r = 0.f;
> +        for (int r = 0; r < dst->p[i].i_visible_lines; r++, pos_r +=
> step_r) +        {
> +            float pos_c = 0.f;
> +            for (int c = 0; c < dst->p[i].i_visible_pitch; ++c, pos_c +=
> step_c) +            {
> +                int src_r = __MIN(roundf(pos_r),
> src->p[i].i_visible_lines); +                int src_c =
> __MIN(roundf(pos_c), src->p[i].i_visible_pitch); +
> +                d[r * dst_pitch + c * dst_pixel_pitch] = s[src_r *
> src_pitch + src_c * src_pixel_pitch]; +            }
> +        }
> +    }
> +}
> +
> +static void Rescale(filter_t *filter, picture_t *dst, picture_t *src)
> +{
> +    filter_sys_t *sys = filter->p_sys;
> +    filter_t *scale_filter = sys->scale_filter;
> +
> +    /* Increase refcount to avoid picture src being released in the
> internal filter */ +    picture_Hold(src);
> +    scale_filter->fmt_in.video = src->format;
> +    scale_filter->fmt_out.video = dst->format;
> +
> +    if (filter->fmt_in.video.stereo.mode == VLC_STEREO3D_COL
> +        || filter->fmt_out.video.stereo.mode == VLC_STEREO3D_COL)
> +    {
> +        GenericRescale(dst, src);
> +    }
> +    else
> +    {
> +        scale_filter->owner.sys = dst;
> +        picture_t *res = scale_filter->pf_video_filter(scale_filter, src);
> +        if (!res)
> +        {
> +            /* fallback to generic conversion routine */
> +            GenericRescale(dst, src);
> +        }
> +    }
> +}
> +
> +static picture_t *Filter(filter_t *filter, picture_t *src)
> +{
> +    filter_sys_t *sys = filter->p_sys;
> +    const video_format_t fmt_in = filter->fmt_in.video;
> +    video_format_t fmt_out = filter->fmt_out.video;
> +
> +    if (fmt_in.stereo.mode == VLC_STEREO3D_2D)
> +        return src;
> +
> +    if (fmt_out.stereo.mode == VLC_STEREO3D_2D
> +        && sys->convert2d_mode == UNCHANGED)
> +        fmt_out.stereo = fmt_in.stereo;
> +
> +    /*
> +     * Extract left/right pictures from src and dst and use the
> +     * internal filter to copy src_left to dst_left and src_right to
> +     * dst_right
> +     */
> +    picture_t *src_left;
> +    picture_t *src_right;
> +    ExtractPictures(filter, src, &fmt_in, &src_left, &src_right);
> +
> +    picture_t *dst = filter_NewPicture(filter);
> +    if (!dst)
> +        return src;
> +    picture_t *dst_left;
> +    picture_t *dst_right;
> +    ExtractPictures(filter, dst, &fmt_out, &dst_left, &dst_right);
> +
> +    if (src_left && dst_left)
> +        Rescale(filter, dst_left, src_left);
> +    if (src_right && dst_right)
> +        Rescale(filter, dst_right, src_right);
> +
> +    dst->date = src->date;
> +    picture_Release(src);
> +
> +    return dst;
> +}

-- 
Rémi Denis-Courmont
http://www.remlab.net/




More information about the vlc-devel mailing list