]> git.sesse.net Git - vlc/blobdiff - src/video_output/video_output.c
Use gettext() as per gettext standard for non-constant parameters
[vlc] / src / video_output / video_output.c
index 6ffe6ae9a85937f804346c6e5bb34730c868593d..02ba01048dd62ca39fdf1fa1ce8319ce8503e38a 100644 (file)
@@ -79,13 +79,20 @@ static void PictureHeapFixRgb( picture_heap_t * );
 static void     vout_Destructor   ( vlc_object_t * p_this );
 
 /* Object variables callbacks */
-static int DeinterlaceCallback( vlc_object_t *, char const *,
-                                vlc_value_t, vlc_value_t, void * );
 static int FilterCallback( vlc_object_t *, char const *,
                            vlc_value_t, vlc_value_t, void * );
 static int VideoFilter2Callback( vlc_object_t *, char const *,
                                  vlc_value_t, vlc_value_t, void * );
 
+/* */
+static void PostProcessEnable( vout_thread_t * );
+static void PostProcessDisable( vout_thread_t * );
+static void PostProcessSetFilterQuality( vout_thread_t *p_vout );
+static int  PostProcessCallback( vlc_object_t *, char const *,
+                                 vlc_value_t, vlc_value_t, void * );
+/* */
+static void DeinterlaceEnable( vout_thread_t * );
+
 /* From vout_intf.c */
 int vout_Snapshot( vout_thread_t *, picture_t * );
 
@@ -174,17 +181,13 @@ vout_thread_t *__vout_Request( vlc_object_t *p_this, vout_thread_t *p_vout,
     /* If we now have a video output, check it has the right properties */
     if( p_vout )
     {
-        char *psz_filter_chain;
-        vlc_value_t val;
-
         vlc_mutex_lock( &p_vout->change_lock );
 
         /* We don't directly check for the "vout-filter" variable for obvious
          * performance reasons. */
         if( p_vout->p->b_filter_change )
         {
-            var_Get( p_vout, "vout-filter", &val );
-            psz_filter_chain = val.psz_string;
+            char *psz_filter_chain = var_GetString( p_vout, "vout-filter" );
 
             if( psz_filter_chain && !*psz_filter_chain )
             {
@@ -268,10 +271,11 @@ vout_thread_t *__vout_Request( vlc_object_t *p_this, vout_thread_t *p_vout,
         {
             msg_Dbg( p_this, "reusing provided vout" );
 
-            spu_Attach( p_vout->p_spu, p_this, true );
-
+            spu_Attach( p_vout->p_spu, VLC_OBJECT(p_vout), false );
             vlc_object_detach( p_vout );
+
             vlc_object_attach( p_vout, p_this );
+            spu_Attach( p_vout->p_spu, VLC_OBJECT(p_vout), true );
         }
     }
 
@@ -295,7 +299,7 @@ vout_thread_t * __vout_Create( vlc_object_t *p_parent, video_format_t *p_fmt )
 {
     vout_thread_t  * p_vout;                            /* thread descriptor */
     int              i_index;                               /* loop variable */
-    vlc_value_t      val, text;
+    vlc_value_t      text;
 
     unsigned int i_width = p_fmt->i_width;
     unsigned int i_height = p_fmt->i_height;
@@ -374,7 +378,8 @@ vout_thread_t * __vout_Create( vlc_object_t *p_parent, video_format_t *p_fmt )
 
     /* Initialize misc stuff */
     p_vout->i_changes    = 0;
-    p_vout->b_scale      = 1;
+    p_vout->b_autoscale  = 1;
+    p_vout->i_zoom      = ZOOM_FP_FACTOR;
     p_vout->b_fullscreen = 0;
     p_vout->i_alignment  = 0;
     p_vout->p->render_time  = 10;
@@ -391,6 +396,13 @@ vout_thread_t * __vout_Create( vlc_object_t *p_parent, video_format_t *p_fmt )
     p_vout->p->i_picture_displayed_date = 0;
     p_vout->p->b_picture_displayed = false;
     p_vout->p->b_picture_empty = false;
+    p_vout->p->i_picture_qtype = QTYPE_NONE;
+
+    p_vout->p->snapshot.b_available = true;
+    p_vout->p->snapshot.i_request = 0;
+    p_vout->p->snapshot.p_picture = NULL;
+    vlc_mutex_init( &p_vout->p->snapshot.lock );
+    vlc_cond_init( &p_vout->p->snapshot.wait );
 
     /* Initialize locks */
     vlc_mutex_init( &p_vout->picture_lock );
@@ -403,17 +415,19 @@ vout_thread_t * __vout_Create( vlc_object_t *p_parent, video_format_t *p_fmt )
     var_Create( p_vout, "mouse-y", VLC_VAR_INTEGER );
     var_Create( p_vout, "mouse-button-down", VLC_VAR_INTEGER );
     var_Create( p_vout, "mouse-moved", VLC_VAR_BOOL );
-    var_Create( p_vout, "mouse-clicked", VLC_VAR_INTEGER );
+    var_Create( p_vout, "mouse-clicked", VLC_VAR_BOOL );
 
     /* Initialize subpicture unit */
     p_vout->p_spu = spu_Create( p_vout );
-    spu_Attach( p_vout->p_spu, p_parent, true );
 
     /* Attach the new object now so we can use var inheritance below */
     vlc_object_attach( p_vout, p_parent );
 
+    /* */
     spu_Init( p_vout->p_spu );
 
+    spu_Attach( p_vout->p_spu, VLC_OBJECT(p_vout), true );
+
     /* Take care of some "interface/control" related initialisations */
     vout_IntfInit( p_vout );
 
@@ -428,6 +442,8 @@ vout_thread_t * __vout_Create( vlc_object_t *p_parent, video_format_t *p_fmt )
         /* Apply video filter2 objects on the first vout */
         p_vout->p->psz_vf2 =
             var_CreateGetStringCommand( p_vout, "video-filter" );
+
+        p_vout->p->b_first_vout = true;
     }
     else
     {
@@ -446,6 +462,9 @@ vout_thread_t * __vout_Create( vlc_object_t *p_parent, video_format_t *p_fmt )
         var_Create( p_vout, "video-filter",
                     VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
         p_vout->p->psz_vf2 = var_GetString( p_vout, "video-filter" );
+
+        /* */
+        p_vout->p->b_first_vout = false;
     }
 
     var_AddCallback( p_vout, "video-filter", VideoFilter2Callback, NULL );
@@ -455,9 +474,7 @@ vout_thread_t * __vout_Create( vlc_object_t *p_parent, video_format_t *p_fmt )
     /* Choose the video output module */
     if( !p_vout->p->psz_filter_chain || !*p_vout->p->psz_filter_chain )
     {
-        var_Create( p_vout, "vout", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
-        var_Get( p_vout, "vout", &val );
-        psz_parser = val.psz_string;
+        psz_parser = var_CreateGetString( p_vout, "vout" );
     }
     else
     {
@@ -480,47 +497,32 @@ vout_thread_t * __vout_Create( vlc_object_t *p_parent, video_format_t *p_fmt )
     if( p_vout->p_module == NULL )
     {
         msg_Err( p_vout, "no suitable vout module" );
+        spu_Attach( p_vout->p_spu, VLC_OBJECT(p_vout), false );
+        spu_Destroy( p_vout->p_spu );
+        p_vout->p_spu = NULL;
         vlc_object_release( p_vout );
         return NULL;
     }
 
     /* Create a few object variables for interface interaction */
-    var_Create( p_vout, "deinterlace", VLC_VAR_STRING | VLC_VAR_HASCHOICE );
-    text.psz_string = _("Deinterlace");
-    var_Change( p_vout, "deinterlace", VLC_VAR_SETTEXT, &text, NULL );
-    val.psz_string = (char *)""; text.psz_string = _("Disable");
-    var_Change( p_vout, "deinterlace", VLC_VAR_ADDCHOICE, &val, &text );
-    val.psz_string = (char *)"discard"; text.psz_string = _("Discard");
-    var_Change( p_vout, "deinterlace", VLC_VAR_ADDCHOICE, &val, &text );
-    val.psz_string = (char *)"blend"; text.psz_string = _("Blend");
-    var_Change( p_vout, "deinterlace", VLC_VAR_ADDCHOICE, &val, &text );
-    val.psz_string = (char *)"mean"; text.psz_string = _("Mean");
-    var_Change( p_vout, "deinterlace", VLC_VAR_ADDCHOICE, &val, &text );
-    val.psz_string = (char *)"bob"; text.psz_string = _("Bob");
-    var_Change( p_vout, "deinterlace", VLC_VAR_ADDCHOICE, &val, &text );
-    val.psz_string = (char *)"linear"; text.psz_string = _("Linear");
-    var_Change( p_vout, "deinterlace", VLC_VAR_ADDCHOICE, &val, &text );
-    val.psz_string = (char *)"x"; text.psz_string = (char *)"X";
-    var_Change( p_vout, "deinterlace", VLC_VAR_ADDCHOICE, &val, &text );
-
-    if( var_Get( p_vout, "deinterlace-mode", &val ) == VLC_SUCCESS )
-    {
-        var_Set( p_vout, "deinterlace", val );
-        free( val.psz_string );
-    }
-    var_AddCallback( p_vout, "deinterlace", DeinterlaceCallback, NULL );
-
     var_Create( p_vout, "vout-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
     text.psz_string = _("Filters");
     var_Change( p_vout, "vout-filter", VLC_VAR_SETTEXT, &text, NULL );
     var_AddCallback( p_vout, "vout-filter", FilterCallback, NULL );
 
+    /* */
+    DeinterlaceEnable( p_vout );
+
+    /* */
     vlc_cond_init( &p_vout->p->change_wait );
     if( vlc_clone( &p_vout->p->thread, RunThread, p_vout,
                    VLC_THREAD_PRIORITY_OUTPUT ) )
     {
         module_unneed( p_vout, p_vout->p_module );
         p_vout->p_module = NULL;
+        spu_Attach( p_vout->p_spu, VLC_OBJECT(p_vout), false );
+        spu_Destroy( p_vout->p_spu );
+        p_vout->p_spu = NULL;
         vlc_object_release( p_vout );
         return NULL;
     }
@@ -560,6 +562,12 @@ void vout_Close( vout_thread_t *p_vout )
     p_vout->p->b_done = true;
     vlc_cond_signal( &p_vout->p->change_wait );
     vlc_mutex_unlock( &p_vout->change_lock );
+
+    vlc_mutex_lock( &p_vout->p->snapshot.lock );
+    p_vout->p->snapshot.b_available = false;
+    vlc_cond_broadcast( &p_vout->p->snapshot.wait );
+    vlc_mutex_unlock( &p_vout->p->snapshot.lock );
+
     vlc_join( p_vout->p->thread, NULL );
     module_unneed( p_vout, p_vout->p_module );
     p_vout->p_module = NULL;
@@ -574,7 +582,8 @@ static void vout_Destructor( vlc_object_t * p_this )
     assert( !p_vout->p_module );
 
     /* */
-    spu_Destroy( p_vout->p_spu );
+    if( p_vout->p_spu )
+        spu_Destroy( p_vout->p_spu );
 
     /* Destroy the locks */
     vlc_cond_destroy( &p_vout->p->change_wait );
@@ -583,6 +592,21 @@ static void vout_Destructor( vlc_object_t * p_this )
     vlc_mutex_destroy( &p_vout->change_lock );
     vlc_mutex_destroy( &p_vout->p->vfilter_lock );
 
+    /* */
+    for( ;; )
+    {
+        picture_t *p_picture = p_vout->p->snapshot.p_picture;
+        if( !p_picture )
+            break;
+
+        p_vout->p->snapshot.p_picture = p_picture->p_next;
+
+        picture_Release( p_picture );
+    }
+    vlc_cond_destroy( &p_vout->p->snapshot.wait );
+    vlc_mutex_destroy( &p_vout->p->snapshot.lock );
+
+    /* */
     free( p_vout->p->psz_filter_chain );
     free( p_vout->p->psz_title );
 
@@ -833,28 +857,8 @@ static int InitThread( vout_thread_t *p_vout )
 
     AspectRatio( p_vout->fmt_render.i_aspect, &i_aspect_x, &i_aspect_y );
 
-    msg_Dbg( p_vout, "picture in %ix%i (%i,%i,%ix%i), "
-             "chroma %4.4s, ar %i:%i, sar %i:%i",
-             p_vout->fmt_render.i_width, p_vout->fmt_render.i_height,
-             p_vout->fmt_render.i_x_offset, p_vout->fmt_render.i_y_offset,
-             p_vout->fmt_render.i_visible_width,
-             p_vout->fmt_render.i_visible_height,
-             (char*)&p_vout->fmt_render.i_chroma,
-             i_aspect_x, i_aspect_y,
-             p_vout->fmt_render.i_sar_num, p_vout->fmt_render.i_sar_den );
-
     AspectRatio( p_vout->fmt_in.i_aspect, &i_aspect_x, &i_aspect_y );
 
-    msg_Dbg( p_vout, "picture user %ix%i (%i,%i,%ix%i), "
-             "chroma %4.4s, ar %i:%i, sar %i:%i",
-             p_vout->fmt_in.i_width, p_vout->fmt_in.i_height,
-             p_vout->fmt_in.i_x_offset, p_vout->fmt_in.i_y_offset,
-             p_vout->fmt_in.i_visible_width,
-             p_vout->fmt_in.i_visible_height,
-             (char*)&p_vout->fmt_in.i_chroma,
-             i_aspect_x, i_aspect_y,
-             p_vout->fmt_in.i_sar_num, p_vout->fmt_in.i_sar_den );
-
     if( !p_vout->fmt_out.i_width || !p_vout->fmt_out.i_height )
     {
         p_vout->fmt_out.i_width = p_vout->fmt_out.i_visible_width =
@@ -879,16 +883,6 @@ static int InitThread( vout_thread_t *p_vout )
 
     AspectRatio( p_vout->fmt_out.i_aspect, &i_aspect_x, &i_aspect_y );
 
-    msg_Dbg( p_vout, "picture out %ix%i (%i,%i,%ix%i), "
-             "chroma %4.4s, ar %i:%i, sar %i:%i",
-             p_vout->fmt_out.i_width, p_vout->fmt_out.i_height,
-             p_vout->fmt_out.i_x_offset, p_vout->fmt_out.i_y_offset,
-             p_vout->fmt_out.i_visible_width,
-             p_vout->fmt_out.i_visible_height,
-             (char*)&p_vout->fmt_out.i_chroma,
-             i_aspect_x, i_aspect_y,
-             p_vout->fmt_out.i_sar_num, p_vout->fmt_out.i_sar_den );
-
     /* FIXME removed the need of both fmt_* and heap infos */
     /* Calculate shifts from system-updated masks */
     PictureHeapFixRgb( &p_vout->render );
@@ -897,6 +891,38 @@ static int InitThread( vout_thread_t *p_vout )
     PictureHeapFixRgb( &p_vout->output );
     VideoFormatImportRgb( &p_vout->fmt_out, &p_vout->output );
 
+    /* print some usefull debug info about different vout formats
+     */
+    msg_Dbg( p_vout, "pic render sz %ix%i, of (%i,%i), vsz %ix%i, 4cc %4.4s, ar %i:%i, sar %i:%i, msk r0x%x g0x%x b0x%x",
+             p_vout->fmt_render.i_width, p_vout->fmt_render.i_height,
+             p_vout->fmt_render.i_x_offset, p_vout->fmt_render.i_y_offset,
+             p_vout->fmt_render.i_visible_width,
+             p_vout->fmt_render.i_visible_height,
+             (char*)&p_vout->fmt_render.i_chroma,
+             i_aspect_x, i_aspect_y,
+             p_vout->fmt_render.i_sar_num, p_vout->fmt_render.i_sar_den,
+             p_vout->fmt_render.i_rmask, p_vout->fmt_render.i_gmask, p_vout->fmt_render.i_bmask );
+
+    msg_Dbg( p_vout, "pic in sz %ix%i, of (%i,%i), vsz %ix%i, 4cc %4.4s, ar %i:%i, sar %i:%i, msk r0x%x g0x%x b0x%x",
+             p_vout->fmt_in.i_width, p_vout->fmt_in.i_height,
+             p_vout->fmt_in.i_x_offset, p_vout->fmt_in.i_y_offset,
+             p_vout->fmt_in.i_visible_width,
+             p_vout->fmt_in.i_visible_height,
+             (char*)&p_vout->fmt_in.i_chroma,
+             i_aspect_x, i_aspect_y,
+             p_vout->fmt_in.i_sar_num, p_vout->fmt_in.i_sar_den,
+             p_vout->fmt_in.i_rmask, p_vout->fmt_in.i_gmask, p_vout->fmt_in.i_bmask );
+
+    msg_Dbg( p_vout, "pic out sz %ix%i, of (%i,%i), vsz %ix%i, 4cc %4.4s, ar %i:%i, sar %i:%i, msk r0x%x g0x%x b0x%x",
+             p_vout->fmt_out.i_width, p_vout->fmt_out.i_height,
+             p_vout->fmt_out.i_x_offset, p_vout->fmt_out.i_y_offset,
+             p_vout->fmt_out.i_visible_width,
+             p_vout->fmt_out.i_visible_height,
+             (char*)&p_vout->fmt_out.i_chroma,
+             i_aspect_x, i_aspect_y,
+             p_vout->fmt_out.i_sar_num, p_vout->fmt_out.i_sar_den,
+             p_vout->fmt_out.i_rmask, p_vout->fmt_out.i_gmask, p_vout->fmt_out.i_bmask );
+
     /* Check whether we managed to create direct buffers similar to
      * the render buffers, ie same size and chroma */
     if( ( p_vout->output.i_width == p_vout->render.i_width )
@@ -982,6 +1008,7 @@ static void* RunThread( void *p_this )
 {
     vout_thread_t *p_vout = p_this;
     int             i_idle_loops = 0;  /* loops without displaying a picture */
+    int             i_picture_qtype_last = QTYPE_NONE;
 
     bool            b_drop_late;
 
@@ -1051,8 +1078,8 @@ static void* RunThread( void *p_this )
                 vout_UsePictureLocked( p_vout, p_pic );
                 p_vout->p->i_picture_lost++;
 
-                msg_Warn( p_vout, "late picture skipped (%"PRId64")",
-                                  current_date - p_pic->date );
+                msg_Warn( p_vout, "late picture skipped (%"PRId64" > %d)",
+                                  current_date - p_pic->date, - p_vout->p->render_time );
             }
             else if( ( !p_last || p_last->date < p_pic->date ) &&
                      ( p_picture == NULL || p_pic->date < p_picture->date ) )
@@ -1130,6 +1157,11 @@ static void* RunThread( void *p_this )
                 p_vout->p->p_picture_displayed = p_picture;
             }
         }
+
+        /* */
+        const int i_postproc_type = p_vout->p->i_picture_qtype;
+        const int i_postproc_state = (p_vout->p->i_picture_qtype != QTYPE_NONE) - (i_picture_qtype_last != QTYPE_NONE);
+
         vlc_mutex_unlock( &p_vout->picture_lock );
 
         if( p_picture == NULL )
@@ -1140,9 +1172,13 @@ static void* RunThread( void *p_this )
             p_filtered_picture = filter_chain_VideoFilter( p_vout->p->p_vf2_chain,
                                                            p_picture );
 
-        /* FIXME it is ugly that b_snapshot is not locked but I do not
-         * know which lock to use (here and in the snapshot callback) */
-        const bool b_snapshot = p_vout->p->b_snapshot && p_picture != NULL;
+        bool b_snapshot = false;
+        if( vlc_mutex_trylock( &p_vout->p->snapshot.lock ) == 0 )
+        {
+             b_snapshot = p_vout->p->snapshot.i_request > 0
+                       && p_picture != NULL;
+             vlc_mutex_unlock( &p_vout->p->snapshot.lock );
+        }
 
         /*
          * Check for subpictures to display
@@ -1164,10 +1200,25 @@ static void* RunThread( void *p_this )
          */
         if( p_directbuffer && b_snapshot )
         {
-            /* FIXME lock (see b_snapshot) */
-            p_vout->p->b_snapshot = false;
-
-            vout_Snapshot( p_vout, p_directbuffer );
+            vlc_mutex_lock( &p_vout->p->snapshot.lock );
+            assert( p_vout->p->snapshot.i_request > 0 );
+            while( p_vout->p->snapshot.i_request > 0 )
+            {
+                picture_t *p_pic = picture_New( p_vout->fmt_out.i_chroma,
+                                                p_vout->fmt_out.i_width,
+                                                p_vout->fmt_out.i_height,
+                                                p_vout->fmt_out.i_aspect  );
+                if( !p_pic )
+                    break;
+
+                picture_Copy( p_pic, p_directbuffer );
+
+                p_pic->p_next = p_vout->p->snapshot.p_picture;
+                p_vout->p->snapshot.p_picture = p_pic;
+                p_vout->p->snapshot.i_request--;
+            }
+            vlc_cond_broadcast( &p_vout->p->snapshot.wait );
+            vlc_mutex_unlock( &p_vout->p->snapshot.lock );
         }
 
         /*
@@ -1195,6 +1246,9 @@ static void* RunThread( void *p_this )
                 p_vout->p->render_time += current_render_time;
                 p_vout->p->render_time >>= 2;
             }
+            else
+                msg_Dbg( p_vout, "skipped big render time %d > %d", (int) current_render_time,
+                 (int) (p_vout->p->render_time +VOUT_DISPLAY_DELAY ) ) ;
         }
 
         /* Give back change lock */
@@ -1264,6 +1318,14 @@ static void* RunThread( void *p_this )
             break;
         }
 
+        while( p_vout->i_changes & VOUT_ON_TOP_CHANGE )
+        {
+            p_vout->i_changes &= ~VOUT_ON_TOP_CHANGE;
+            vlc_mutex_unlock( &p_vout->change_lock );
+            vout_Control( p_vout, VOUT_SET_STAY_ON_TOP, p_vout->b_on_top );
+            vlc_mutex_lock( &p_vout->change_lock );
+        }
+
         if( p_vout->i_changes & VOUT_SIZE_CHANGE )
         {
             /* this must only happen when the vout plugin is incapable of
@@ -1331,7 +1393,7 @@ static void* RunThread( void *p_this )
 
             p_vout->b_error = InitThread( p_vout );
             if( p_vout->b_error )
-                msg_Err( p_vout, "InitThread after VOUT_PICTURE_BUFFERS_CHANGE failed\n" );
+                msg_Err( p_vout, "InitThread after VOUT_PICTURE_BUFFERS_CHANGE failed" );
 
             vlc_cond_signal( &p_vout->p->picture_wait );
             vlc_mutex_unlock( &p_vout->picture_lock );
@@ -1340,6 +1402,14 @@ static void* RunThread( void *p_this )
                 break;
         }
 
+        /* Post processing */
+        if( i_postproc_state == 1 )
+            PostProcessEnable( p_vout );
+        else if( i_postproc_state == -1 )
+            PostProcessDisable( p_vout );
+        if( i_postproc_state != 0 )
+            i_picture_qtype_last = i_postproc_type;
+
         /* Check for "video filter2" changes */
         vlc_mutex_lock( &p_vout->p->vfilter_lock );
         if( p_vout->p->psz_vf2 )
@@ -1356,6 +1426,9 @@ static void* RunThread( void *p_this )
 
             free( p_vout->p->psz_vf2 );
             p_vout->p->psz_vf2 = NULL;
+
+            if( i_picture_qtype_last != QTYPE_NONE )
+                PostProcessSetFilterQuality( p_vout );
         }
         vlc_mutex_unlock( &p_vout->p->vfilter_lock );
     }
@@ -1601,75 +1674,11 @@ static void PictureHeapFixRgb( picture_heap_t *p_heap )
  * object variables callbacks: a bunch of object variables are used by the
  * interfaces to interact with the vout.
  *****************************************************************************/
-static int DeinterlaceCallback( vlc_object_t *p_this, char const *psz_cmd,
-                       vlc_value_t oldval, vlc_value_t newval, void *p_data )
-{
-    vout_thread_t *p_vout = (vout_thread_t *)p_this;
-    input_thread_t *p_input;
-    vlc_value_t val;
-
-    char *psz_mode = newval.psz_string;
-    char *psz_filter, *psz_deinterlace = NULL;
-    (void)psz_cmd; (void)oldval; (void)p_data;
-
-    var_Get( p_vout, "vout-filter", &val );
-    psz_filter = val.psz_string;
-    if( psz_filter ) psz_deinterlace = strstr( psz_filter, "deinterlace" );
-
-    if( !psz_mode || !*psz_mode )
-    {
-        if( psz_deinterlace )
-        {
-            char *psz_src = psz_deinterlace + sizeof("deinterlace") - 1;
-            if( psz_src[0] == ':' ) psz_src++;
-            memmove( psz_deinterlace, psz_src, strlen(psz_src) + 1 );
-        }
-    }
-    else if( !psz_deinterlace )
-    {
-        psz_filter = realloc( psz_filter, strlen( psz_filter ) +
-                              sizeof(":deinterlace") );
-        if( psz_filter )
-        {
-            if( *psz_filter )
-                strcat( psz_filter, ":" );
-            strcat( psz_filter, "deinterlace" );
-        }
-    }
-
-    p_input = (input_thread_t *)vlc_object_find( p_this, VLC_OBJECT_INPUT,
-                                                 FIND_PARENT );
-    if( !p_input )
-    {
-        free( psz_filter );
-        return VLC_EGENERIC;
-    }
-
-    if( psz_mode && *psz_mode )
-    {
-        /* Modify input as well because the vout might have to be restarted */
-        val.psz_string = psz_mode;
-        var_Create( p_input, "deinterlace-mode", VLC_VAR_STRING );
-        var_Set( p_input, "deinterlace-mode", val );
-    }
-    vlc_object_release( p_input );
-
-    val.b_bool = true;
-    var_Set( p_vout, "intf-change", val );
-
-    val.psz_string = psz_filter;
-    var_Set( p_vout, "vout-filter", val );
-    free( psz_filter );
-
-    return VLC_SUCCESS;
-}
-
 static int FilterCallback( vlc_object_t *p_this, char const *psz_cmd,
                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
 {
     vout_thread_t *p_vout = (vout_thread_t *)p_this;
     input_thread_t *p_input;
-    vlc_value_t val;
     (void)psz_cmd; (void)oldval; (void)p_data;
 
     p_input = (input_thread_t *)vlc_object_find( p_this, VLC_OBJECT_INPUT,
@@ -1677,17 +1686,14 @@ static int FilterCallback( vlc_object_t *p_this, char const *psz_cmd,
     if (!p_input)
     {
         msg_Err( p_vout, "Input not found" );
-        return( VLC_EGENERIC );
+        return VLC_EGENERIC;
     }
 
-    val.b_bool = true;
-    var_Set( p_vout, "intf-change", val );
+    var_SetBool( p_vout, "intf-change", true );
 
     /* Modify input as well because the vout might have to be restarted */
-    val.psz_string = newval.psz_string;
     var_Create( p_input, "vout-filter", VLC_VAR_STRING );
-
-    var_Set( p_input, "vout-filter", val );
+    var_SetString( p_input, "vout-filter", newval.psz_string );
 
     /* Now restart current video stream */
     input_Control( p_input, INPUT_RESTART_ES, -VIDEO_ES );
@@ -1712,6 +1718,109 @@ static int VideoFilter2Callback( vlc_object_t *p_this, char const *psz_cmd,
     return VLC_SUCCESS;
 }
 
+/*****************************************************************************
+ * Post-processing
+ *****************************************************************************/
+static bool PostProcessIsPresent( const char *psz_filter )
+{
+    const char  *psz_pp = "postproc";
+    const size_t i_pp = strlen(psz_pp);
+    return psz_filter &&
+           !strncmp( psz_filter, psz_pp, strlen(psz_pp) ) &&
+           ( psz_filter[i_pp] == '\0' || psz_filter[i_pp] == ':' );
+}
+
+static int PostProcessCallback( vlc_object_t *p_this, char const *psz_cmd,
+                                vlc_value_t oldval, vlc_value_t newval, void *p_data )
+{
+    vout_thread_t *p_vout = (vout_thread_t *)p_this;
+    VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval); VLC_UNUSED(p_data);
+
+    static const char *psz_pp = "postproc";
+
+    char *psz_vf2 = var_GetString( p_vout, "video-filter" );
+
+    if( newval.i_int <= 0 )
+    {
+        if( PostProcessIsPresent( psz_vf2 ) )
+        {
+            strcpy( psz_vf2, &psz_vf2[strlen(psz_pp)] );
+            if( *psz_vf2 == ':' )
+                strcpy( psz_vf2, &psz_vf2[1] );
+        }
+    }
+    else
+    {
+        if( !PostProcessIsPresent( psz_vf2 ) )
+        {
+            if( psz_vf2 )
+            {
+                char *psz_tmp = psz_vf2;
+                if( asprintf( &psz_vf2, "%s:%s", psz_pp, psz_tmp ) < 0 )
+                    psz_vf2 = psz_tmp;
+                else
+                    free( psz_tmp );
+            }
+            else
+            {
+                psz_vf2 = strdup( psz_pp );
+            }
+        }
+    }
+    if( psz_vf2 )
+    {
+        var_SetString( p_vout, "video-filter", psz_vf2 );
+        free( psz_vf2 );
+    }
+
+    return VLC_SUCCESS;
+}
+static void PostProcessEnable( vout_thread_t *p_vout )
+{
+    msg_Dbg( p_vout, "Post-processing available" );
+    var_Create( p_vout, "postprocess", VLC_VAR_INTEGER | VLC_VAR_HASCHOICE );
+    for( int i = 0; i <= 6; i++ )
+    {
+        vlc_value_t val;
+        vlc_value_t text;
+        char psz_text[1+1];
+
+        val.i_int = i;
+        snprintf( psz_text, sizeof(psz_text), "%d", i );
+        if( i == 0 )
+            text.psz_string = _("Disable");
+        else
+            text.psz_string = psz_text;
+        var_Change( p_vout, "postprocess", VLC_VAR_ADDCHOICE, &val, &text );
+    }
+    var_AddCallback( p_vout, "postprocess", PostProcessCallback, NULL );
+
+    /* */
+    char *psz_filter = var_GetNonEmptyString( p_vout, "video-filter" );
+    int i_postproc_q = 0;
+    if( PostProcessIsPresent( psz_filter ) )
+        i_postproc_q = var_CreateGetInteger( p_vout, "postproc-q" );
+
+    var_SetInteger( p_vout, "postprocess", i_postproc_q );
+
+    free( psz_filter );
+}
+static void PostProcessDisable( vout_thread_t *p_vout )
+{
+    msg_Dbg( p_vout, "Post-processing no more available" );
+    var_Destroy( p_vout, "postprocess" );
+}
+static void PostProcessSetFilterQuality( vout_thread_t *p_vout )
+{
+    vlc_object_t *p_pp = vlc_object_find_name( p_vout, "postproc", FIND_CHILD );
+    if( !p_pp )
+        return;
+
+    var_SetInteger( p_pp, "postproc-q", var_GetInteger( p_vout, "postprocess" ) );
+    vlc_object_release( p_pp );
+}
+
+
 static void DisplayTitleOnOSD( vout_thread_t *p_vout )
 {
     const mtime_t i_start = mdate();
@@ -1733,3 +1842,220 @@ static void DisplayTitleOnOSD( vout_thread_t *p_vout )
     p_vout->p->psz_title = NULL;
 }
 
+/*****************************************************************************
+ * Deinterlacing
+ *****************************************************************************/
+typedef struct
+{
+    const char *psz_mode;
+    const char *psz_description;
+    bool       b_vout_filter;
+} deinterlace_mode_t;
+
+/* XXX
+ * You can use the non vout filter if and only if the video properties stay the
+ * same (width/height/chroma/fps), at least for now.
+ */
+static const deinterlace_mode_t p_deinterlace_mode[] = {
+    { "",           "Disable",  false },
+    { "discard",    "Discard",  true },
+    { "blend",      "Blend",    false },
+    { "mean",       "Mean",     true  },
+    { "bob",        "Bob",      true },
+    { "linear",     "Linear",   true },
+    { "x",          "X",        false },
+    { NULL, NULL, true }
+};
+
+static char *FilterFind( char *psz_filter_base, const char *psz_module )
+{
+    const size_t i_module = strlen( psz_module );
+    const char *psz_filter = psz_filter_base;
+
+    if( !psz_filter || i_module <= 0 )
+        return NULL;
+
+    for( ;; )
+    {
+        char *psz_find = strstr( psz_filter, psz_module );
+        if( !psz_find )
+            return NULL;
+        if( psz_find[i_module] == '\0' || psz_find[i_module] == ':' )
+            return psz_find;
+        psz_filter = &psz_find[i_module];
+    }
+}
+
+static bool DeinterlaceIsPresent( vout_thread_t *p_vout, bool b_vout_filter )
+{
+    char *psz_filter = var_GetNonEmptyString( p_vout, b_vout_filter ? "vout-filter" : "video-filter" );
+
+    bool b_found = FilterFind( psz_filter, "deinterlace" ) != NULL;
+
+    free( psz_filter );
+
+    return b_found;
+}
+
+static void DeinterlaceRemove( vout_thread_t *p_vout, bool b_vout_filter )
+{
+    const char *psz_variable = b_vout_filter ? "vout-filter" : "video-filter";
+    char *psz_filter = var_GetNonEmptyString( p_vout, psz_variable );
+
+    char *psz = FilterFind( psz_filter, "deinterlace" );
+    if( !psz )
+    {
+        free( psz_filter );
+        return;
+    }
+
+    /* */
+    strcpy( &psz[0], &psz[strlen("deinterlace")] );
+    if( *psz == ':' )
+        strcpy( &psz[0], &psz[1] );
+
+    var_SetString( p_vout, psz_variable, psz_filter );
+    free( psz_filter );
+}
+static void DeinterlaceAdd( vout_thread_t *p_vout, bool b_vout_filter )
+{
+    const char *psz_variable = b_vout_filter ? "vout-filter" : "video-filter";
+
+    char *psz_filter = var_GetNonEmptyString( p_vout, psz_variable );
+
+    if( FilterFind( psz_filter, "deinterlace" ) )
+    {
+        free( psz_filter );
+        return;
+    }
+
+    /* */
+    if( psz_filter )
+    {
+        char *psz_tmp = psz_filter;
+        if( asprintf( &psz_filter, "%s:%s", psz_tmp, "deinterlace" ) < 0 )
+            psz_filter = psz_tmp;
+        else
+            free( psz_tmp );
+    }
+    else
+    {
+        psz_filter = strdup( "deinterlace" );
+    }
+
+    if( psz_filter )
+    {
+        var_SetString( p_vout, psz_variable, psz_filter );
+        free( psz_filter );
+    }
+}
+
+static int DeinterlaceCallback( vlc_object_t *p_this, char const *psz_cmd,
+                                vlc_value_t oldval, vlc_value_t newval, void *p_data )
+{
+    vout_thread_t *p_vout = (vout_thread_t *)p_this;
+
+    /* */
+    const deinterlace_mode_t *p_mode;
+    for( p_mode = &p_deinterlace_mode[0]; p_mode->psz_mode; p_mode++ )
+    {
+        if( !strcmp( p_mode->psz_mode, newval.psz_string ?: "" ) )
+            break;
+    }
+    if( !p_mode->psz_mode )
+    {
+        msg_Err( p_this, "Invalid value (%s) ignored", newval.psz_string );
+        return VLC_EGENERIC;
+    }
+
+    /* We have to set input variable to ensure restart support
+     * XXX it is only needed because of vout-filter but must be done
+     * for non video filter anyway */
+    input_thread_t *p_input = (input_thread_t *)vlc_object_find( p_this, VLC_OBJECT_INPUT, FIND_PARENT );
+    if( p_input )
+    {
+        var_Create( p_input, "vout-deinterlace", VLC_VAR_STRING );
+        var_SetString( p_input, "vout-deinterlace", p_mode->psz_mode );
+
+        var_Create( p_input, "deinterlace-mode", VLC_VAR_STRING );
+        var_SetString( p_input, "deinterlace-mode", p_mode->psz_mode );
+
+        var_Create( p_input, "sout-deinterlace-mode", VLC_VAR_STRING );
+        var_SetString( p_input, "sout-deinterlace-mode", p_mode->psz_mode );
+
+        vlc_object_release( p_input );
+    }
+
+    char *psz_old;
+
+    if( p_mode->b_vout_filter )
+    {
+        psz_old = var_CreateGetString( p_vout, "deinterlace-mode" );
+    }
+    else
+    {
+        psz_old = var_CreateGetString( p_vout, "sout-deinterlace-mode" );
+        var_SetString( p_vout, "sout-deinterlace-mode", p_mode->psz_mode );
+    }
+
+    /* */
+    if( !strcmp( p_mode->psz_mode, "" ) )
+    {
+        DeinterlaceRemove( p_vout, false );
+        DeinterlaceRemove( p_vout, true );
+    }
+    else if( !DeinterlaceIsPresent( p_vout, p_mode->b_vout_filter ) )
+    {
+        DeinterlaceRemove( p_vout, !p_mode->b_vout_filter );
+        DeinterlaceAdd( p_vout, p_mode->b_vout_filter );
+    }
+    else
+    {
+        DeinterlaceRemove( p_vout, !p_mode->b_vout_filter );
+        if( psz_old && strcmp( psz_old, p_mode->psz_mode ) )
+            var_TriggerCallback( p_vout, p_mode->b_vout_filter ? "vout-filter" : "video-filter" );
+    }
+    free( psz_old );
+
+    (void)psz_cmd; (void) oldval; (void) p_data;
+    return VLC_SUCCESS;
+}
+
+static void DeinterlaceEnable( vout_thread_t *p_vout )
+{
+    vlc_value_t val, text;
+
+    if( !p_vout->p->b_first_vout )
+        return;
+
+    msg_Dbg( p_vout, "Deinterlacing available" );
+
+    /* Create the configuration variable */
+    var_Create( p_vout, "deinterlace", VLC_VAR_STRING | VLC_VAR_HASCHOICE );
+    text.psz_string = _("Deinterlace");
+    var_Change( p_vout, "deinterlace", VLC_VAR_SETTEXT, &text, NULL );
+
+    for( int i = 0; p_deinterlace_mode[i].psz_mode; i++ )
+    {
+        val.psz_string  = (char*)p_deinterlace_mode[i].psz_mode;
+        text.psz_string = (char*)gettext(p_deinterlace_mode[i].psz_description);
+        var_Change( p_vout, "deinterlace", VLC_VAR_ADDCHOICE, &val, &text );
+    }
+    var_AddCallback( p_vout, "deinterlace", DeinterlaceCallback, NULL );
+
+    /* */
+    char *psz_mode = NULL;
+    if( var_Type( p_vout, "vout-deinterlace" ) != 0 )
+        psz_mode = var_CreateGetNonEmptyString( p_vout, "vout-deinterlace" );
+    if( !psz_mode )
+    {
+        /* Get the initial value */
+        if( DeinterlaceIsPresent( p_vout, true ) )
+            psz_mode = var_CreateGetNonEmptyString( p_vout, "deinterlace-mode" );
+        else if( DeinterlaceIsPresent( p_vout, false ) )
+            psz_mode = var_CreateGetNonEmptyString( p_vout, "sout-deinterlace-mode" );
+    }
+    var_SetString( p_vout, "deinterlace", psz_mode ?: "" );
+    free( psz_mode );
+}
+