[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