[vlc-devel] [PATCH 10/10] video_filter:deinterlace: move some structures in common.h

Rémi Denis-Courmont remi at remlab.net
Mon Jun 26 19:41:17 CEST 2017


Le 26 juin 2017 19:20:19 GMT+02:00, Steve Lhomme <robux4 at videolabs.io> a écrit :
>So they can be reused by other deinterlacing implementations
>---
> modules/video_filter/Makefile.am                 |   3 +-
> modules/video_filter/deinterlace/algo_ivtc.c     |  34 +--
> modules/video_filter/deinterlace/algo_phosphor.c |   4 +-
> modules/video_filter/deinterlace/algo_yadif.c    |  10 +-
>modules/video_filter/deinterlace/common.c        | 317
>+++++++++++++++++++++
> modules/video_filter/deinterlace/common.h        |  62 ++++-
>modules/video_filter/deinterlace/deinterlace.c   | 334
>+++--------------------
> modules/video_filter/deinterlace/deinterlace.h   |  37 +--
> 8 files changed, 445 insertions(+), 356 deletions(-)
> create mode 100644 modules/video_filter/deinterlace/common.c
>
>diff --git a/modules/video_filter/Makefile.am
>b/modules/video_filter/Makefile.am
>index 8fcc93c4db..a46eb3ce89 100644
>--- a/modules/video_filter/Makefile.am
>+++ b/modules/video_filter/Makefile.am
>@@ -109,7 +109,8 @@ video_filter_LTLIBRARIES = \
> 
> libdeinterlace_plugin_la_SOURCES = \
>	video_filter/deinterlace/deinterlace.c
>video_filter/deinterlace/deinterlace.h \
>-	video_filter/deinterlace/mmx.h video_filter/deinterlace/common.h \
>+        video_filter/deinterlace/mmx.h \
>+        video_filter/deinterlace/common.c
>video_filter/deinterlace/common.h \
> 	video_filter/deinterlace/merge.c video_filter/deinterlace/merge.h \
>	video_filter/deinterlace/helpers.c video_filter/deinterlace/helpers.h
>\
>	video_filter/deinterlace/algo_basic.c
>video_filter/deinterlace/algo_basic.h \
>diff --git a/modules/video_filter/deinterlace/algo_ivtc.c
>b/modules/video_filter/deinterlace/algo_ivtc.c
>index 5603828f0e..2fb378aecb 100644
>--- a/modules/video_filter/deinterlace/algo_ivtc.c
>+++ b/modules/video_filter/deinterlace/algo_ivtc.c
>@@ -406,8 +406,8 @@ static void IVTCLowLevelDetect( filter_t *p_filter
>)
> 
>     filter_sys_t *p_sys = p_filter->p_sys;
>     ivtc_sys_t *p_ivtc  = &p_sys->ivtc;
>-    picture_t *p_curr = p_sys->pp_history[1];
>-    picture_t *p_next = p_sys->pp_history[2];
>+    picture_t *p_curr = p_sys->context.pp_history[1];
>+    picture_t *p_next = p_sys->context.pp_history[2];
> 
>     assert( p_next != NULL );
>     assert( p_curr != NULL );
>@@ -458,7 +458,7 @@ static void IVTCCadenceDetectAlgoScores( filter_t
>*p_filter )
> 
>     filter_sys_t *p_sys = p_filter->p_sys;
>     ivtc_sys_t *p_ivtc  = &p_sys->ivtc;
>-    picture_t *p_next = p_sys->pp_history[2];
>+    picture_t *p_next = p_sys->context.pp_history[2];
> 
>     assert( p_next != NULL );
> 
>@@ -632,7 +632,7 @@ static void IVTCCadenceDetectAlgoVektor( filter_t
>*p_filter )
>     filter_sys_t *p_sys = p_filter->p_sys;
>     ivtc_sys_t *p_ivtc  = &p_sys->ivtc;
> 
>-    picture_t *p_next = p_sys->pp_history[2];
>+    picture_t *p_next = p_sys->context.pp_history[2];
> 
>     assert( p_next != NULL );
> 
>@@ -860,9 +860,9 @@ static void IVTCSoftTelecineDetect( filter_t
>*p_filter )
> 
>     filter_sys_t *p_sys = p_filter->p_sys;
>     ivtc_sys_t *p_ivtc  = &p_sys->ivtc;
>-    picture_t *p_prev = p_sys->pp_history[0];
>-    picture_t *p_curr = p_sys->pp_history[1];
>-    picture_t *p_next = p_sys->pp_history[2];
>+    picture_t *p_prev = p_sys->context.pp_history[0];
>+    picture_t *p_curr = p_sys->context.pp_history[1];
>+    picture_t *p_next = p_sys->context.pp_history[2];
> 
>     assert( p_next != NULL );
>     assert( p_curr != NULL );
>@@ -977,9 +977,9 @@ static void IVTCCadenceAnalyze( filter_t *p_filter
>)
> 
>     filter_sys_t *p_sys = p_filter->p_sys;
>     ivtc_sys_t *p_ivtc  = &p_sys->ivtc;
>-    picture_t *p_prev = p_sys->pp_history[0];
>-    picture_t *p_curr = p_sys->pp_history[1];
>-    picture_t *p_next = p_sys->pp_history[2];
>+    picture_t *p_prev = p_sys->context.pp_history[0];
>+    picture_t *p_curr = p_sys->context.pp_history[1];
>+    picture_t *p_next = p_sys->context.pp_history[2];
> 
>     assert( p_next != NULL );
>     assert( p_curr != NULL );
>@@ -1216,8 +1216,8 @@ static bool IVTCOutputOrDropFrame( filter_t
>*p_filter, picture_t *p_dst )
>     ivtc_sys_t *p_ivtc  = &p_sys->ivtc;
>  mtime_t t_final = VLC_TS_INVALID; /* for custom timestamp mangling */
> 
>-    picture_t *p_curr = p_sys->pp_history[1];
>-    picture_t *p_next = p_sys->pp_history[2];
>+    picture_t *p_curr = p_sys->context.pp_history[1];
>+    picture_t *p_next = p_sys->context.pp_history[2];
> 
>     assert( p_next != NULL );
>     assert( p_curr != NULL );
>@@ -1479,9 +1479,9 @@ int RenderIVTC( filter_t *p_filter, picture_t
>*p_dst, picture_t *p_pic,
>     filter_sys_t *p_sys = p_filter->p_sys;
>     ivtc_sys_t *p_ivtc  = &p_sys->ivtc;
> 
>-    picture_t *p_prev = p_sys->pp_history[0];
>-    picture_t *p_curr = p_sys->pp_history[1];
>-    picture_t *p_next = p_sys->pp_history[2];
>+    picture_t *p_prev = p_sys->context.pp_history[0];
>+    picture_t *p_curr = p_sys->context.pp_history[1];
>+    picture_t *p_next = p_sys->context.pp_history[2];
> 
>     /* If the history mechanism has failed, we have nothing to do. */
>     if( !p_next )
>@@ -1523,7 +1523,7 @@ int RenderIVTC( filter_t *p_filter, picture_t
>*p_dst, picture_t *p_pic,
>   bool b_have_output_frame = IVTCOutputOrDropFrame( p_filter, p_dst );
> 
>         /* The next frame will get a custom timestamp, too. */
>-        p_sys->i_frame_offset = CUSTOM_PTS;
>+        p_sys->context.i_frame_offset = CUSTOM_PTS;
> 
>         if( b_have_output_frame )
>             return VLC_SUCCESS;
>@@ -1572,7 +1572,7 @@ int RenderIVTC( filter_t *p_filter, picture_t
>*p_dst, picture_t *p_pic,
> 
>       /* At the next frame, the filter starts. The next frame will get
>            a custom timestamp. */
>-        p_sys->i_frame_offset = CUSTOM_PTS;
>+        p_sys->context.i_frame_offset = CUSTOM_PTS;
> 
>         picture_Copy( p_dst, p_next );
>         return VLC_SUCCESS;
>diff --git a/modules/video_filter/deinterlace/algo_phosphor.c
>b/modules/video_filter/deinterlace/algo_phosphor.c
>index cc90ec13a6..cd954c631d 100644
>--- a/modules/video_filter/deinterlace/algo_phosphor.c
>+++ b/modules/video_filter/deinterlace/algo_phosphor.c
>@@ -290,8 +290,8 @@ int RenderPhosphor( filter_t *p_filter,
>     filter_sys_t *p_sys = p_filter->p_sys;
> 
>     /* Last two input frames */
>-    picture_t *p_in  = p_sys->pp_history[HISTORY_SIZE-1];
>-    picture_t *p_old = p_sys->pp_history[HISTORY_SIZE-2];
>+    picture_t *p_in  = p_sys->context.pp_history[HISTORY_SIZE-1];
>+    picture_t *p_old = p_sys->context.pp_history[HISTORY_SIZE-2];
> 
>/* Use the same input picture as "old" at the first frame after startup
>*/
>     if( !p_old )
>diff --git a/modules/video_filter/deinterlace/algo_yadif.c
>b/modules/video_filter/deinterlace/algo_yadif.c
>index b326a17736..a3ccb1108b 100644
>--- a/modules/video_filter/deinterlace/algo_yadif.c
>+++ b/modules/video_filter/deinterlace/algo_yadif.c
>@@ -59,9 +59,9 @@ int RenderYadif( filter_t *p_filter, picture_t
>*p_dst, picture_t *p_src,
>     assert( i_field == 0 || i_field == 1 );
> 
>/* As the pitches must match, use ONLY pictures coming from
>picture_New()! */
>-    picture_t *p_prev = p_sys->pp_history[0];
>-    picture_t *p_cur  = p_sys->pp_history[1];
>-    picture_t *p_next = p_sys->pp_history[2];
>+    picture_t *p_prev = p_sys->context.pp_history[0];
>+    picture_t *p_cur  = p_sys->context.pp_history[1];
>+    picture_t *p_next = p_sys->context.pp_history[2];
> 
>     /* Account for soft field repeat.
> 
>@@ -172,7 +172,7 @@ int RenderYadif( filter_t *p_filter, picture_t
>*p_dst, picture_t *p_src,
>             }
>         }
> 
>-        p_sys->i_frame_offset = 1; /* p_cur will be rendered at next
>frame, too */
>+        p_sys->context.i_frame_offset = 1; /* p_cur will be rendered
>at next frame, too */
> 
>         return VLC_SUCCESS;
>     }
>@@ -187,7 +187,7 @@ int RenderYadif( filter_t *p_filter, picture_t
>*p_dst, picture_t *p_src,
>     }
>     else
>     {
>-        p_sys->i_frame_offset = 1; /* p_cur will be rendered at next
>frame */
>+        p_sys->context.i_frame_offset = 1; /* p_cur will be rendered
>at next frame */
> 
>         return VLC_EGENERIC;
>     }
>diff --git a/modules/video_filter/deinterlace/common.c
>b/modules/video_filter/deinterlace/common.c
>new file mode 100644
>index 0000000000..e36cb1d08f
>--- /dev/null
>+++ b/modules/video_filter/deinterlace/common.c
>@@ -0,0 +1,317 @@
>+/*****************************************************************************
>+ * common.c : Common helper function for the VLC deinterlacer
>+
>*****************************************************************************
>+ * Copyright (C) 2000-2017 VLC authors and VideoLAN
>+ * $Id$
>+ *
>+ * Author: Sam Hocevar <sam at zoy.org>
>+ *         Christophe Massiot <massiot at via.ecp.fr>
>+ *         Laurent Aimar <fenrir at videolan.org>
>+ *         Juha Jeronen <juha.jeronen at jyu.fi>
>+ *         Steve Lhomme <robux4 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_picture.h>
>+
>+#include "common.h"
>+
>+void FlushDeinterlacing(struct deinterlace_ctx *p_context)
>+{
>+    p_context->meta[0].pi_date = VLC_TS_INVALID;
>+    p_context->meta[0].pi_nb_fields = 2;
>+    p_context->meta[0].pb_top_field_first = true;
>+    for( int i = 1; i < METADATA_SIZE; i++ )
>+        p_context->meta[i] = p_context->meta[i-1];
>+
>+    p_context->i_frame_offset = 0; /* reset to default value (first
>frame after
>+                                      flush cannot have offset) */
>+    for( int i = 0; i < HISTORY_SIZE; i++ )
>+    {
>+        if( p_context->pp_history[i] )
>+            picture_Release( p_context->pp_history[i] );
>+        p_context->pp_history[i] = NULL;
>+    }
>+}
>+
>+mtime_t GetFieldDuration(const struct deinterlace_ctx *p_context,
>+                         const video_format_t *fmt, picture_t *p_pic )
>+{
>+    mtime_t i_field_dur = 0;
>+
>+    /* Calculate one field duration. */
>+    int i = 0;
>+    int iend = METADATA_SIZE-1;
>+    /* Find oldest valid logged date.
>+       The current input frame doesn't count. */
>+    for( ; i < iend; i++ )
>+        if( p_context->meta[i].pi_date > VLC_TS_INVALID )
>+            break;
>+    if( i < iend )
>+    {
>+        /* Count how many fields the valid history entries
>+           (except the new frame) represent. */
>+        int i_fields_total = 0;
>+        for( int j = i ; j < iend; j++ )
>+            i_fields_total += p_context->meta[j].pi_nb_fields;
>+        /* One field took this long. */
>+        i_field_dur = (p_pic->date - p_context->meta[i].pi_date) /
>i_fields_total;
>+    }
>+    else if (fmt->i_frame_rate_base)
>+        i_field_dur = 10000 * fmt->i_frame_rate /
>fmt->i_frame_rate_base;
>+
>+    /* Note that we default to field duration 0 if it could not be
>+       determined. This behaves the same as the old code - leaving the
>+       extra output frame dates the same as p_pic->date if the last
>cached
>+       date was not valid.
>+    */
>+    return i_field_dur;
>+}
>+
>+void GetDeinterlacingOutput( const struct deinterlace_ctx *p_context,
>+                             video_format_t *p_dst, const
>video_format_t *p_src )
>+{
>+    *p_dst = *p_src;
>+
>+    if( p_context->b_half_height )
>+    {
>+        p_dst->i_height /= 2;
>+        p_dst->i_visible_height /= 2;
>+        p_dst->i_y_offset /= 2;
>+        p_dst->i_sar_den *= 2;
>+    }
>+
>+    if( p_context->b_double_rate )
>+    {
>+        p_dst->i_frame_rate *= 2;
>+    }
>+}
>+
>+picture_t *DoDeinterlacing( filter_t *p_filter,
>+                            struct deinterlace_ctx *p_context,
>picture_t *p_pic )
>+{
>+    picture_t *p_dst[DEINTERLACE_DST_SIZE];
>+    int i_double_rate_alloc_end;
>+    /* Remember the frame offset that we should use for this frame.
>+       The value in p_sys will be updated to reflect the correct value
>+       for the *next* frame when we call the renderer. */
>+    int i_frame_offset;
>+    int i_meta_idx;
>+
>+    bool b_top_field_first;
>+
>+    /* Request output picture */
>+    p_dst[0] = filter_NewPicture( p_filter );
>+    if( p_dst[0] == NULL )
>+    {
>+        picture_Release( p_pic );
>+        return NULL;
>+    }
>+    picture_CopyProperties( p_dst[0], p_pic );
>+
>+    /* Any unused p_dst pointers must be NULL, because they are used
>to
>+       check how many output frames we have. */
>+    for( int i = 1; i < DEINTERLACE_DST_SIZE; ++i )
>+        p_dst[i] = NULL;
>+
>+    /* Update the input frame history, if the currently active
>algorithm
>+       needs it. */
>+    if( p_context->b_use_frame_history )
>+    {
>+        /* Keep reference for the picture */
>+        picture_t *p_dup = picture_Hold( p_pic );
>+
>+        /* Slide the history */
>+        if( p_context->pp_history[0] )
>+            picture_Release( p_context->pp_history[0] );
>+        for( int i = 1; i < HISTORY_SIZE; i++ )
>+            p_context->pp_history[i-1] = p_context->pp_history[i];
>+        p_context->pp_history[HISTORY_SIZE-1] = p_dup;
>+    }
>+
>+    /* Slide the metadata history. */
>+    for( int i = 1; i < METADATA_SIZE; i++ )
>+        p_context->meta[i-1] = p_context->meta[i];
>+    /* The last element corresponds to the current input frame. */
>+    p_context->meta[METADATA_SIZE-1].pi_date            = p_pic->date;
>+    p_context->meta[METADATA_SIZE-1].pi_nb_fields       =
>p_pic->i_nb_fields;
>+    p_context->meta[METADATA_SIZE-1].pb_top_field_first =
>p_pic->b_top_field_first;
>+
>+    /* Remember the frame offset that we should use for this frame.
>+       The value in p_sys will be updated to reflect the correct value
>+       for the *next* frame when we call the renderer. */
>+    i_frame_offset = p_context->i_frame_offset;
>+    i_meta_idx     = (METADATA_SIZE-1) - i_frame_offset;
>+
>+    int i_nb_fields;
>+
>+    /* These correspond to the current *outgoing* frame. */
>+    if( i_frame_offset != CUSTOM_PTS )
>+    {
>+        /* Pick the correct values from the history. */
>+        b_top_field_first =
>p_context->meta[i_meta_idx].pb_top_field_first;
>+        i_nb_fields       = p_context->meta[i_meta_idx].pi_nb_fields;
>+    }
>+    else
>+    {
>+        /* Framerate doublers must not request CUSTOM_PTS, as they
>need the
>+           original field timings, and need Deinterlace() to allocate
>the
>+           correct number of output frames. */
>+        assert( !p_context->b_double_rate );
>+
>+        /* NOTE: i_nb_fields is only used for framerate doublers, so
>it is
>+                 unused in this case. b_top_field_first is only passed
>to the
>+                 algorithm. We assume that algorithms that request
>CUSTOM_PTS
>+                 will, if necessary, extract the TFF/BFF information
>themselves.
>+        */
>+        b_top_field_first = p_pic->b_top_field_first; /* this is not
>guaranteed
>+                                                         to be
>meaningful */
>+        i_nb_fields       = p_pic->i_nb_fields;       /* unused */
>+    }
>+
>+    /* For framerate doublers, determine field duration and allocate
>+       output frames. */
>+    i_double_rate_alloc_end = 0; /* One past last for allocated output
>+                                        frames in p_dst[]. Used only
>for
>+                                        framerate doublers. Will be
>inited
>+                                        below. Declared here because
>the
>+                                        PTS logic needs the result. */
>+    if( p_context->b_double_rate )
>+    {
>+        i_double_rate_alloc_end = i_nb_fields;
>+        if( i_nb_fields > DEINTERLACE_DST_SIZE )
>+        {
>+            /* Note that the effective buffer size depends also on the
>constant
>+               private_picture in vout_wrapper.c, since that
>determines the
>+               maximum number of output pictures filter_NewPicture()
>will
>+               successfully allocate for one input frame.
>+            */
>+            msg_Err( p_filter, "Framerate doubler: output buffer too
>small; "\
>+                               "fields = %d, buffer size = %d.
>Dropping the "\
>+                               "remaining fields.",
>+                               i_nb_fields, DEINTERLACE_DST_SIZE );
>+            i_double_rate_alloc_end = DEINTERLACE_DST_SIZE;
>+        }
>+
>+        /* Allocate output frames. */
>+        for( int i = 1; i < i_double_rate_alloc_end ; ++i )
>+        {
>+            p_dst[i-1]->p_next =
>+            p_dst[i]           = filter_NewPicture( p_filter );
>+            if( p_dst[i] )
>+            {
>+                picture_CopyProperties( p_dst[i], p_pic );
>+            }
>+            else
>+            {
>+                msg_Err( p_filter, "Framerate doubler: could not
>allocate "\
>+                                   "output frame %d", i+1 );
>+                i_double_rate_alloc_end = i; /* Inform the PTS logic
>about the
>+                                                correct end position.
>*/
>+                break; /* If this happens, the rest of the allocations
>+                          aren't likely to work, either... */
>+            }
>+        }
>+        /* Now we have allocated *up to* the correct number of frames;
>+           normally, exactly the correct number. Upon alloc failure,
>+           we may have succeeded in allocating *some* output frames,
>+           but fewer than were desired. In such a case, as many will
>+           be rendered as were successfully allocated.
>+
>+           Note that now p_dst[i] != NULL
>+           for 0 <= i < i_double_rate_alloc_end. */
>+    }
>+    assert( p_context->b_double_rate  ||  p_dst[1] == NULL );
>+    assert( i_nb_fields > 2  ||  p_dst[2] == NULL );
>+
>+    /* Render */
>+    if ( p_context->b_single_field )
>+    {
>+        if ( p_context->pf_render_pic( p_filter, p_dst[0], p_pic,
>+                                       0, 0 ) )
>+            goto drop;
>+    }
>+    else
>+    {
>+        /* Note: RenderIVTC will automatically drop the duplicate
>frames
>+                 produced by IVTC. This is part of normal operation.
>*/
>+        if ( p_context->pf_render_pic( p_filter, p_dst[0], p_pic,
>+                                       0, !b_top_field_first ) )
>+            goto drop;
>+        if ( p_dst[1] )
>+            p_context->pf_render_pic( p_filter, p_dst[1], p_pic,
>+                                      1, !b_top_field_first );
>+        if ( p_dst[2] )
>+            p_context->pf_render_pic( p_filter, p_dst[1], p_pic,
>+                                      2, !b_top_field_first );
>+    }
>+
>+    /* Set output timestamps, if the algorithm didn't request
>CUSTOM_PTS
>+       for this frame. */
>+    assert( i_frame_offset <= METADATA_SIZE ||
>+            i_frame_offset == CUSTOM_PTS );
>+    if( i_frame_offset != CUSTOM_PTS )
>+    {
>+        mtime_t i_base_pts = p_context->meta[i_meta_idx].pi_date;
>+
>+        /* Note: in the usual case (i_frame_offset = 0  and
>+                 b_double_rate = false), this effectively does
>nothing.
>+                 This is needed to correct the timestamp
>+                 when i_frame_offset > 0. */
>+        p_dst[0]->date = i_base_pts;
>+
>+        if( p_context->b_double_rate )
>+        {
>+            mtime_t i_field_dur = GetFieldDuration( p_context,
>&p_pic->format, p_pic );
>+            /* Processing all actually allocated output frames. */
>+            for( int i = 1; i < i_double_rate_alloc_end; ++i )
>+            {
>+                /* XXX it's not really good especially for the first
>picture, but
>+                 * I don't think that delaying by one frame is worth
>it */
>+                if( i_base_pts > VLC_TS_INVALID )
>+                    p_dst[i]->date = i_base_pts + i * i_field_dur;
>+                else
>+                    p_dst[i]->date = VLC_TS_INVALID;
>+            }
>+        }
>+    }
>+
>+    for( int i = 0; i < DEINTERLACE_DST_SIZE; ++i )
>+    {
>+        if( p_dst[i] )
>+        {
>+            p_dst[i]->b_progressive = true;
>+            p_dst[i]->i_nb_fields = 2;
>+        }
>+    }
>+
>+    picture_Release( p_pic );
>+    return p_dst[0];
>+
>+drop:
>+    picture_Release( p_dst[0] );
>+    for( int i = 1; i < DEINTERLACE_DST_SIZE; ++i )
>+    {
>+        if( p_dst[i] )
>+            picture_Release( p_dst[i] );
>+    }
>+    picture_Release( p_pic );
>+    return NULL;
>+}
>diff --git a/modules/video_filter/deinterlace/common.h
>b/modules/video_filter/deinterlace/common.h
>index dcd7e63c73..739398cdb9 100644
>--- a/modules/video_filter/deinterlace/common.h
>+++ b/modules/video_filter/deinterlace/common.h
>@@ -1,10 +1,11 @@
>/*****************************************************************************
>  * common.h : Common macros for the VLC deinterlacer
>*****************************************************************************
>- * Copyright (C) 2000-2011 VLC authors and VideoLAN
>+ * Copyright (C) 2000-2017 VLC authors and VideoLAN
>  * $Id$
>  *
>  * Author: Sam Hocevar <sam at zoy.org>
>+ *         Steve Lhomme <robux4 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
>@@ -24,6 +25,11 @@
> #ifndef VLC_DEINTERLACE_COMMON_H
> #define VLC_DEINTERLACE_COMMON_H 1
> 
>+#include <vlc_common.h>
>+#include <vlc_filter.h>
>+
>+#include <assert.h>
>+
> /**
>  * \file
>  * Common macros for the VLC deinterlacer.
>@@ -35,4 +41,58 @@
> #define FFMIN(a,b)      __MIN(a,b)
> #define FFMIN3(a,b,c)   FFMIN(FFMIN(a,b),c)
> 
>+/**
>+ * Metadata history structure, used for framerate doublers.
>+ * This is used for computing field duration in Deinterlace().
>+ * @see Deinterlace()
>+ */
>+typedef struct {
>+    mtime_t pi_date;
>+    int     pi_nb_fields;
>+    bool    pb_top_field_first;
>+} metadata_history_t;
>+
>+#define METADATA_SIZE (3)
>+#define HISTORY_SIZE (3)
>+#define CUSTOM_PTS -1
>+
>+struct deinterlace_ctx
>+{
>+    /* Algorithm behaviour flags */
>+    bool b_double_rate;       /**< Shall we double the framerate? */
>+    bool b_half_height;       /**< Shall be divide the height by 2 */
>+    bool b_use_frame_history; /**< Use the input frame history buffer?
>*/
>+    bool b_single_field;      /**< The filter doesn't handle the field
>info */
>+
>+    /**
>+     * Metadata history (PTS, nb_fields, TFF). Used for framerate
>doublers.
>+     * @see metadata_history_t
>+     */
>+    metadata_history_t meta[METADATA_SIZE];
>+
>+    /** Output frame timing / framerate doubler control
>+        (see extra documentation in deinterlace.h) */
>+    int i_frame_offset;
>+
>+    /** Input frame history buffer for algorithms with temporal
>filtering. */
>+    picture_t *pp_history[HISTORY_SIZE];
>+
>+    int (*pf_render_pic)(filter_t *p_filter, picture_t *p_dst,
>picture_t *p_pic,
>+                         int order, int i_field);
>+};
>+
>+#define DEINTERLACE_DST_SIZE 3
>+
>+mtime_t GetFieldDuration( const struct deinterlace_ctx *,
>+                          const video_format_t *fmt, picture_t *p_pic
>);
>+
>+void GetDeinterlacingOutput( const struct deinterlace_ctx *,
>+                             video_format_t *p_dst, const
>video_format_t *p_src );
>+
>+picture_t *DoDeinterlacing( filter_t *,
>+                            struct deinterlace_ctx *, picture_t * );
>+
>+void FlushDeinterlacing( struct deinterlace_ctx * );
>+
>+
> #endif
>diff --git a/modules/video_filter/deinterlace/deinterlace.c
>b/modules/video_filter/deinterlace/deinterlace.c
>index a1efcf6b8e..392e9c9d7b 100644
>--- a/modules/video_filter/deinterlace/deinterlace.c
>+++ b/modules/video_filter/deinterlace/deinterlace.c
>@@ -151,43 +151,43 @@ static void SetFilterMethod( filter_t *p_filter,
>const char *mode, bool pack )
>     if ( mode == NULL )
>         mode = "auto";
> 
>-    p_sys->b_double_rate = false;
>-    p_sys->b_half_height = false;
>-    p_sys->b_use_frame_history = false;
>-    p_sys->b_single_field = false;
>+    p_sys->context.b_double_rate = false;
>+    p_sys->context.b_half_height = false;
>+    p_sys->context.b_use_frame_history = false;
>+    p_sys->context.b_single_field = false;
>     p_sys->b_phosphor_mode = false;
> 
>     if ( !strcmp( mode, "auto" ) || !strcmp( mode, "x" ) )
>     {
>-        p_sys->pf_render_pic = RenderX;
>-        p_sys->b_single_field = true;
>+        p_sys->context.pf_render_pic = RenderX;
>+        p_sys->context.b_single_field = true;
>     }
>     else if( !strcmp( mode, "discard" ) )
>     {
>-        p_sys->pf_render_pic = RenderDiscard;
>-        p_sys->b_single_field = true;
>-        p_sys->b_half_height = true;
>+        p_sys->context.pf_render_pic = RenderDiscard;
>+        p_sys->context.b_single_field = true;
>+        p_sys->context.b_half_height = true;
>     }
>else if( !strcmp( mode, "bob" ) || !strcmp( mode, "progressive-scan" )
>)
>     {
>-        p_sys->pf_render_pic = RenderBob;
>-        p_sys->b_double_rate = true;
>+        p_sys->context.pf_render_pic = RenderBob;
>+        p_sys->context.b_double_rate = true;
>     }
>     else if( !strcmp( mode, "linear" ) )
>     {
>-        p_sys->pf_render_pic = RenderLinear;
>-        p_sys->b_double_rate = true;
>+        p_sys->context.pf_render_pic = RenderLinear;
>+        p_sys->context.b_double_rate = true;
>     }
>     else if( !strcmp( mode, "mean" ) )
>     {
>-        p_sys->pf_render_pic = RenderMean;
>-        p_sys->b_single_field = true;
>-        p_sys->b_half_height = true;
>+        p_sys->context.pf_render_pic = RenderMean;
>+        p_sys->context.b_single_field = true;
>+        p_sys->context.b_half_height = true;
>     }
>     else if( !strcmp( mode, "blend" ) )
>     {
>-        p_sys->pf_render_pic = RenderBlend;
>-        p_sys->b_single_field = true;
>+        p_sys->context.pf_render_pic = RenderBlend;
>+        p_sys->context.b_single_field = true;
>     }
>     else if( pack )
>     {
>@@ -197,15 +197,15 @@ static void SetFilterMethod( filter_t *p_filter,
>const char *mode, bool pack )
>     }
>     else if( !strcmp( mode, "yadif" ) )
>     {
>-        p_sys->pf_render_pic = RenderYadif;
>-        p_sys->b_single_field = true;
>-        p_sys->b_use_frame_history = true;
>+        p_sys->context.pf_render_pic = RenderYadif;
>+        p_sys->context.b_single_field = true;
>+        p_sys->context.b_use_frame_history = true;
>     }
>     else if( !strcmp( mode, "yadif2x" ) )
>     {
>-        p_sys->pf_render_pic = RenderYadif;
>-        p_sys->b_double_rate = true;
>-        p_sys->b_use_frame_history = true;
>+        p_sys->context.pf_render_pic = RenderYadif;
>+        p_sys->context.b_double_rate = true;
>+        p_sys->context.b_use_frame_history = true;
>     }
>     else if( p_sys->chroma->pixel_size > 1 )
>     {
>@@ -215,16 +215,16 @@ static void SetFilterMethod( filter_t *p_filter,
>const char *mode, bool pack )
>     }
>     else if( !strcmp( mode, "phosphor" ) )
>     {
>-        p_sys->pf_render_pic = RenderPhosphor;
>+        p_sys->context.pf_render_pic = RenderPhosphor;
>         p_sys->b_phosphor_mode = true;
>-        p_sys->b_double_rate = true;
>-        p_sys->b_use_frame_history = true;
>+        p_sys->context.b_double_rate = true;
>+        p_sys->context.b_use_frame_history = true;
>     }
>     else if( !strcmp( mode, "ivtc" ) )
>     {
>-        p_sys->pf_render_pic = RenderIVTC;
>-        p_sys->b_single_field = true;
>-        p_sys->b_use_frame_history = true;
>+        p_sys->context.pf_render_pic = RenderIVTC;
>+        p_sys->context.b_single_field = true;
>+        p_sys->context.b_use_frame_history = true;
>     }
>     else
>         msg_Err( p_filter, "unknown deinterlace mode \"%s\"", mode );
>@@ -248,20 +248,8 @@ static void GetOutputFormat( filter_t *p_filter,
>                   video_format_t *p_dst, const video_format_t *p_src )
> {
>     filter_sys_t *p_sys = p_filter->p_sys;
>-    *p_dst = *p_src;
> 
>-    if( p_sys->b_half_height )
>-    {
>-        p_dst->i_height /= 2;
>-        p_dst->i_visible_height /= 2;
>-        p_dst->i_y_offset /= 2;
>-        p_dst->i_sar_den *= 2;
>-    }
>-
>-    if( p_sys->b_double_rate )
>-    {
>-        p_dst->i_frame_rate *= 2;
>-    }
>+    GetDeinterlacingOutput(&p_sys->context, p_dst, p_src);
> 
>     if( p_sys->b_phosphor_mode &&
>         2 * p_sys->chroma->p[1].h.num == p_sys->chroma->p[1].h.den &&
>@@ -282,240 +270,10 @@ static void GetOutputFormat( filter_t *p_filter,
>  * video filter functions
>*****************************************************************************/
> 
>-#define DEINTERLACE_DST_SIZE 3
>-
>-static mtime_t GetFieldDuration( metadata_history_t meta[static
>METADATA_SIZE],
>-                                 picture_t *p_pic )
>-{
>-    mtime_t i_field_dur = 0;
>-
>-    /* Calculate one field duration. */
>-    int i = 0;
>-    int iend = METADATA_SIZE-1;
>-    /* Find oldest valid logged date.
>-       The current input frame doesn't count. */
>-    for( ; i < iend; i++ )
>-        if( meta[i].pi_date > VLC_TS_INVALID )
>-            break;
>-    if( i < iend )
>-    {
>-        /* Count how many fields the valid history entries
>-           (except the new frame) represent. */
>-        int i_fields_total = 0;
>-        for( int j = i ; j < iend; j++ )
>-            i_fields_total += meta[j].pi_nb_fields;
>-        /* One field took this long. */
>-        i_field_dur = (p_pic->date - meta[i].pi_date) /
>i_fields_total;
>-    }
>-    /* Note that we default to field duration 0 if it could not be
>-       determined. This behaves the same as the old code - leaving the
>-       extra output frame dates the same as p_pic->date if the last
>cached
>-       date was not valid.
>-    */
>-    return i_field_dur;
>-}
>-
> /* This is the filter function. See Open(). */
> picture_t *Deinterlace( filter_t *p_filter, picture_t *p_pic )
> {
>-    filter_sys_t *p_sys = p_filter->p_sys;
>-    picture_t *p_dst[DEINTERLACE_DST_SIZE];
>-
>-    /* Request output picture */
>-    p_dst[0] = filter_NewPicture( p_filter );
>-    if( p_dst[0] == NULL )
>-    {
>-        picture_Release( p_pic );
>-        return NULL;
>-    }
>-    picture_CopyProperties( p_dst[0], p_pic );
>-
>-    /* Any unused p_dst pointers must be NULL, because they are used
>to
>-       check how many output frames we have. */
>-    for( int i = 1; i < DEINTERLACE_DST_SIZE; ++i )
>-        p_dst[i] = NULL;
>-
>-    /* Update the input frame history, if the currently active
>algorithm
>-       needs it. */
>-    if( p_sys->b_use_frame_history )
>-    {
>-        /* Keep reference for the picture */
>-        picture_t *p_dup = picture_Hold( p_pic );
>-
>-        /* Slide the history */
>-        if( p_sys->pp_history[0] )
>-            picture_Release( p_sys->pp_history[0] );
>-        for( int i = 1; i < HISTORY_SIZE; i++ )
>-            p_sys->pp_history[i-1] = p_sys->pp_history[i];
>-        p_sys->pp_history[HISTORY_SIZE-1] = p_dup;
>-    }
>-
>-    /* Slide the metadata history. */
>-    for( int i = 1; i < METADATA_SIZE; i++ )
>-        p_sys->meta[i-1] = p_sys->meta[i];
>-    /* The last element corresponds to the current input frame. */
>-    p_sys->meta[METADATA_SIZE-1].pi_date            = p_pic->date;
>-    p_sys->meta[METADATA_SIZE-1].pi_nb_fields       =
>p_pic->i_nb_fields;
>-    p_sys->meta[METADATA_SIZE-1].pb_top_field_first =
>p_pic->b_top_field_first;
>-
>-    /* Remember the frame offset that we should use for this frame.
>-       The value in p_sys will be updated to reflect the correct value
>-       for the *next* frame when we call the renderer. */
>-    int i_frame_offset = p_sys->i_frame_offset;
>-    int i_meta_idx     = (METADATA_SIZE-1) - i_frame_offset;
>-
>-    /* These correspond to the current *outgoing* frame. */
>-    bool b_top_field_first;
>-    int i_nb_fields;
>-    if( i_frame_offset != CUSTOM_PTS )
>-    {
>-        /* Pick the correct values from the history. */
>-        b_top_field_first =
>p_sys->meta[i_meta_idx].pb_top_field_first;
>-        i_nb_fields       = p_sys->meta[i_meta_idx].pi_nb_fields;
>-    }
>-    else
>-    {
>-        /* Framerate doublers must not request CUSTOM_PTS, as they
>need the
>-           original field timings, and need Deinterlace() to allocate
>the
>-           correct number of output frames. */
>-        assert( !p_sys->b_double_rate );
>-
>-        /* NOTE: i_nb_fields is only used for framerate doublers, so
>it is
>-                 unused in this case. b_top_field_first is only passed
>to the
>-                 algorithm. We assume that algorithms that request
>CUSTOM_PTS
>-                 will, if necessary, extract the TFF/BFF information
>themselves.
>-        */
>-        b_top_field_first = p_pic->b_top_field_first; /* this is not
>guaranteed
>-                                                         to be
>meaningful */
>-        i_nb_fields       = p_pic->i_nb_fields;       /* unused */
>-    }
>-
>-    /* For framerate doublers, determine field duration and allocate
>-       output frames. */
>-    int i_double_rate_alloc_end = 0; /* One past last for allocated
>output
>-                                        frames in p_dst[]. Used only
>for
>-                                        framerate doublers. Will be
>inited
>-                                        below. Declared here because
>the
>-                                        PTS logic needs the result. */
>-    if( p_sys->b_double_rate )
>-    {
>-        i_double_rate_alloc_end = i_nb_fields;
>-        if( i_nb_fields > DEINTERLACE_DST_SIZE )
>-        {
>-            /* Note that the effective buffer size depends also on the
>constant
>-               private_picture in vout_wrapper.c, since that
>determines the
>-               maximum number of output pictures filter_NewPicture()
>will
>-               successfully allocate for one input frame.
>-            */
>-            msg_Err( p_filter, "Framerate doubler: output buffer too
>small; "\
>-                               "fields = %d, buffer size = %d.
>Dropping the "\
>-                               "remaining fields.",
>-                               i_nb_fields, DEINTERLACE_DST_SIZE );
>-            i_double_rate_alloc_end = DEINTERLACE_DST_SIZE;
>-        }
>-
>-        /* Allocate output frames. */
>-        for( int i = 1; i < i_double_rate_alloc_end ; ++i )
>-        {
>-            p_dst[i-1]->p_next =
>-            p_dst[i]           = filter_NewPicture( p_filter );
>-            if( p_dst[i] )
>-            {
>-                picture_CopyProperties( p_dst[i], p_pic );
>-            }
>-            else
>-            {
>-                msg_Err( p_filter, "Framerate doubler: could not
>allocate "\
>-                                   "output frame %d", i+1 );
>-                i_double_rate_alloc_end = i; /* Inform the PTS logic
>about the
>-                                                correct end position.
>*/
>-                break; /* If this happens, the rest of the allocations
>-                          aren't likely to work, either... */
>-            }
>-        }
>-        /* Now we have allocated *up to* the correct number of frames;
>-           normally, exactly the correct number. Upon alloc failure,
>-           we may have succeeded in allocating *some* output frames,
>-           but fewer than were desired. In such a case, as many will
>-           be rendered as were successfully allocated.
>-
>-           Note that now p_dst[i] != NULL
>-           for 0 <= i < i_double_rate_alloc_end. */
>-    }
>-    assert( p_sys->b_double_rate  ||  p_dst[1] == NULL );
>-    assert( i_nb_fields > 2  ||  p_dst[2] == NULL );
>-
>-    /* Render */
>-    if ( p_sys->b_single_field )
>-    {
>-        if ( p_sys->pf_render_pic( p_filter, p_dst[0], p_pic, 0, 0 ) )
>-            goto drop;
>-    }
>-    else
>-    {
>-        /* Note: RenderIVTC will automatically drop the duplicate
>frames
>-                 produced by IVTC. This is part of normal operation.
>*/
>-        if ( p_sys->pf_render_pic( p_filter, p_dst[0], p_pic,
>-                                   0, !b_top_field_first ) )
>-            goto drop;
>-        if ( p_dst[1] )
>-            p_sys->pf_render_pic( p_filter, p_dst[1], p_pic,
>-                                  1, !b_top_field_first );
>-        if ( p_dst[2] )
>-            p_sys->pf_render_pic( p_filter, p_dst[1], p_pic,
>-                                  2, !b_top_field_first );
>-    }
>-
>-    /* Set output timestamps, if the algorithm didn't request
>CUSTOM_PTS
>-       for this frame. */
>-    assert( i_frame_offset <= METADATA_SIZE  ||  i_frame_offset ==
>CUSTOM_PTS );
>-    if( i_frame_offset != CUSTOM_PTS )
>-    {
>-        mtime_t i_base_pts = p_sys->meta[i_meta_idx].pi_date;
>-
>-        /* Note: in the usual case (i_frame_offset = 0  and
>-                 b_double_rate = false), this effectively does
>nothing.
>-                 This is needed to correct the timestamp
>-                 when i_frame_offset > 0. */
>-        p_dst[0]->date = i_base_pts;
>-
>-        if( p_sys->b_double_rate )
>-        {
>-            mtime_t i_field_dur = GetFieldDuration( p_sys->meta, p_pic
>);
>-            /* Processing all actually allocated output frames. */
>-            for( int i = 1; i < i_double_rate_alloc_end; ++i )
>-            {
>-                /* XXX it's not really good especially for the first
>picture, but
>-                 * I don't think that delaying by one frame is worth
>it */
>-                if( i_base_pts > VLC_TS_INVALID )
>-                    p_dst[i]->date = i_base_pts + i * i_field_dur;
>-                else
>-                    p_dst[i]->date = VLC_TS_INVALID;
>-            }
>-        }
>-    }
>-
>-    for( int i = 0; i < DEINTERLACE_DST_SIZE; ++i )
>-    {
>-        if( p_dst[i] )
>-        {
>-            p_dst[i]->b_progressive = true;
>-            p_dst[i]->i_nb_fields = 2;
>-        }
>-    }
>-
>-    picture_Release( p_pic );
>-    return p_dst[0];
>-
>-drop:
>-    picture_Release( p_dst[0] );
>-    for( int i = 1; i < DEINTERLACE_DST_SIZE; ++i )
>-    {
>-        if( p_dst[i] )
>-            picture_Release( p_dst[i] );
>-    }
>-    picture_Release( p_pic );
>-    return NULL;
>+    return DoDeinterlacing( p_filter, &p_filter->p_sys->context, p_pic
>);
> }
> 
>/*****************************************************************************
>@@ -524,22 +282,8 @@ drop:
> 
> void Flush( filter_t *p_filter )
> {
>-    filter_sys_t *p_sys = p_filter->p_sys;
>+    FlushDeinterlacing(&p_filter->p_sys->context);
> 
>-    for( int i = 0; i < METADATA_SIZE; i++ )
>-    {
>-        p_sys->meta[i].pi_date = VLC_TS_INVALID;
>-        p_sys->meta[i].pi_nb_fields = 2;
>-        p_sys->meta[i].pb_top_field_first = true;
>-    }
>-    p_sys->i_frame_offset = 0; /* reset to default value (first frame
>after
>-                                  flush cannot have offset) */
>-    for( int i = 0; i < HISTORY_SIZE; i++ )
>-    {
>-        if( p_sys->pp_history[i] )
>-            picture_Release( p_sys->pp_history[i] );
>-        p_sys->pp_history[i] = NULL;
>-    }
>     IVTCClearState( p_filter );
> }
> 
>@@ -553,7 +297,7 @@ int Mouse( filter_t *p_filter,
> {
>     VLC_UNUSED(p_old);
>     *p_mouse = *p_new;
>-    if( p_filter->p_sys->b_half_height )
>+    if( p_filter->p_sys->context.b_half_height )
>         p_mouse->i_y *= 2;
>     return VLC_SUCCESS;
> }
>@@ -613,14 +357,14 @@ notsupp:
> 
>     for( int i = 0; i < METADATA_SIZE; i++ )
>     {
>-        p_sys->meta[i].pi_date = VLC_TS_INVALID;
>-        p_sys->meta[i].pi_nb_fields = 2;
>-        p_sys->meta[i].pb_top_field_first = true;
>+        p_sys->context.meta[i].pi_date = VLC_TS_INVALID;
>+        p_sys->context.meta[i].pi_nb_fields = 2;
>+        p_sys->context.meta[i].pb_top_field_first = true;
>     }
>-    p_sys->i_frame_offset = 0; /* start with default value (first-ever
>frame
>+    p_sys->context.i_frame_offset = 0; /* start with default value
>(first-ever frame
>                                   cannot have offset) */
>     for( int i = 0; i < HISTORY_SIZE; i++ )
>-        p_sys->pp_history[i] = NULL;
>+        p_sys->context.pp_history[i] = NULL;
> 
>     IVTCClearState( p_filter );
> 
>diff --git a/modules/video_filter/deinterlace/deinterlace.h
>b/modules/video_filter/deinterlace/deinterlace.h
>index 728a7ff5fc..9d951b9881 100644
>--- a/modules/video_filter/deinterlace/deinterlace.h
>+++ b/modules/video_filter/deinterlace/deinterlace.h
>@@ -42,6 +42,7 @@ struct vlc_object_t;
> #include "algo_yadif.h"
> #include "algo_phosphor.h"
> #include "algo_ivtc.h"
>+#include "common.h"
> 
>/*****************************************************************************
>  * Local data
>@@ -61,20 +62,6 @@ static const char *const mode_list_text[] = {
>  * Data structures
>*****************************************************************************/
> 
>-#define METADATA_SIZE (3)
>-/**
>- * Metadata history structure, used for framerate doublers.
>- * This is used for computing field duration in Deinterlace().
>- * @see Deinterlace()
>- */
>-typedef struct {
>-    mtime_t pi_date;
>-    int     pi_nb_fields;
>-    bool    pb_top_field_first;
>-} metadata_history_t;
>-
>-#define HISTORY_SIZE (3)
>-#define CUSTOM_PTS -1
> /**
>  * Top-level deinterlace subsystem state.
>  */
>@@ -82,12 +69,6 @@ struct filter_sys_t
> {
>     const vlc_chroma_description_t *chroma;
> 
>-    /* Algorithm behaviour flags */
>-    bool b_double_rate;       /**< Shall we double the framerate? */
>-    bool b_half_height;       /**< Shall be divide the height by 2 */
>-    bool b_use_frame_history; /**< Use the input frame history buffer?
>*/
>-    bool b_single_field;      /**< The filter doesn't handle the field
>info */
>-
>     /** Merge routine: C, MMX, SSE, ALTIVEC, NEON, ... */
>     void (*pf_merge) ( void *, const void *, const void *, size_t );
> #if defined (__i386__) || defined (__x86_64__)
>@@ -95,21 +76,7 @@ struct filter_sys_t
>     void (*pf_end_merge) ( void );
> #endif
> 
>-    int (*pf_render_pic)(filter_t *p_filter, picture_t *p_dst,
>picture_t *p_pic,
>-                         int order, int i_field);
>-
>-    /**
>-     * Metadata history (PTS, nb_fields, TFF). Used for framerate
>doublers.
>-     * @see metadata_history_t
>-     */
>-    metadata_history_t meta[METADATA_SIZE];
>-
>-    /** Output frame timing / framerate doubler control
>-        (see extra documentation in deinterlace.h) */
>-    int i_frame_offset;
>-
>-    /** Input frame history buffer for algorithms with temporal
>filtering. */
>-    picture_t *pp_history[HISTORY_SIZE];
>+    struct deinterlace_ctx   context;
> 
>     /* Algorithm-specific substructures */
>     bool b_phosphor_mode;     /**< The filter mode is "phosphor" */
>-- 
>2.12.1
>
>
>_______________________________________________
>vlc-devel mailing list
>To unsubscribe or modify your subscription options:
>https://mailman.videolan.org/listinfo/vlc-devel

For empirical reasons, I fear that this ends up worse rather than better. Sure, in theory, this is reusable... But look at the decoder synch stuff that was also supposed to be reusable...

Case in point, VDPAU has its own history implementation, and it is very compact. Using this code would probably involve reverse abstraction antipattern, and make the code overall more intricate...
-- 
Rémi Denis-Courmont
Typed on an inconvenient virtual keyboard
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20170626/94cc31f4/attachment-0001.html>


More information about the vlc-devel mailing list