[vlc-devel] [PATCH 4/4] filters: add an operations structure to set the callbacks

Steve Lhomme robux4 at ycbcr.xyz
Mon Oct 5 17:03:04 CEST 2020


From: Alexandre Janniaux <ajanni at videolabs.io>

Generate a simple operations structure for filters generating their own filter
callback via VIDEO_FILTER_WRAPPER().

The filter chain sets a mouse handler on video filters that don't have one,
just as before, by using a local version of the ops structure of the filter and
adding the local mouse callback.

Co-authored-by: Steve Lhomme <robux4 at ycbcr.xyz>
---
 include/vlc_filter.h                          | 124 ++++++++++--------
 modules/access/screen/screen.c                |   2 +-
 modules/arm_neon/chroma_yuv.c                 |  44 +++----
 modules/arm_neon/yuv_rgb.c                    |  10 +-
 modules/audio_filter/audiobargraph_a.c        |   7 +-
 modules/audio_filter/center.c                 |   7 +-
 modules/audio_filter/channel_mixer/dolby.c    |   6 +-
 .../audio_filter/channel_mixer/headphone.c    |   7 +-
 modules/audio_filter/channel_mixer/mono.c     |   7 +-
 modules/audio_filter/channel_mixer/remap.c    |   6 +-
 modules/audio_filter/channel_mixer/simple.c   |   8 +-
 .../channel_mixer/spatialaudio.cpp            |  18 ++-
 modules/audio_filter/channel_mixer/trivial.c  |  24 +++-
 modules/audio_filter/chorus_flanger.c         |  11 +-
 modules/audio_filter/compressor.c             |   7 +-
 modules/audio_filter/converter/format.c       |  14 +-
 modules/audio_filter/converter/tospdif.c      |   8 +-
 modules/audio_filter/equalizer.c              |   7 +-
 modules/audio_filter/gain.c                   |   6 +-
 modules/audio_filter/karaoke.c                |   7 +-
 modules/audio_filter/libebur128.c             |   7 +-
 modules/audio_filter/normvol.c                |   6 +-
 modules/audio_filter/param_eq.c               |  20 +--
 modules/audio_filter/resampler/bandlimited.c  |   6 +-
 modules/audio_filter/resampler/soxr.c         |  10 +-
 modules/audio_filter/resampler/speex.c        |   6 +-
 modules/audio_filter/resampler/src.c          |   7 +-
 modules/audio_filter/resampler/ugly.c         |   6 +-
 modules/audio_filter/scaletempo.c             |  14 +-
 .../audio_filter/spatializer/spatializer.cpp  |  12 +-
 modules/audio_filter/stereo_widen.c           |   6 +-
 modules/hw/d3d11/d3d11_deinterlace.c          |   7 +-
 modules/hw/d3d11/d3d11_filters.c              |   6 +-
 modules/hw/d3d11/d3d11_surface.c              |  20 +--
 modules/hw/d3d9/d3d9_filters.c                |   6 +-
 modules/hw/d3d9/dxa9.c                        |  16 ++-
 modules/hw/d3d9/dxva2_deinterlace.c           |   7 +-
 modules/hw/mmal/converter.c                   |   7 +-
 modules/hw/mmal/deinterlace.c                 |  14 +-
 modules/hw/nvdec/chroma.c                     |   6 +-
 modules/hw/vaapi/chroma.c                     |  10 +-
 modules/hw/vaapi/filters.c                    |  24 +++-
 modules/hw/vdpau/adjust.c                     |   6 +-
 modules/hw/vdpau/chroma.c                     |  23 +++-
 modules/hw/vdpau/deinterlace.c                |   6 +-
 modules/hw/vdpau/sharpen.c                    |   6 +-
 modules/spu/audiobargraph_v.c                 |  12 +-
 modules/spu/dynamicoverlay/dynamicoverlay.c   |   6 +-
 modules/spu/logo.c                            |  18 ++-
 modules/spu/marq.c                            |   6 +-
 modules/spu/mosaic.c                          |   6 +-
 modules/spu/rss.c                             |   6 +-
 modules/spu/subsdelay.c                       |   6 +-
 modules/text_renderer/freetype/freetype.c     |   7 +-
 modules/text_renderer/nsspeechsynthesizer.m   |   6 +-
 modules/text_renderer/sapi.cpp                |  10 +-
 modules/text_renderer/svg.c                   |   6 +-
 modules/text_renderer/tdummy.c                |   6 +-
 modules/video_chroma/chain.c                  |  15 ++-
 modules/video_chroma/cvpx.c                   |  28 +++-
 modules/video_chroma/grey_yuv.c               |  29 ++--
 modules/video_chroma/i420_nv12.c              |  12 +-
 modules/video_chroma/i420_rgb.c               |  18 +--
 modules/video_chroma/i420_yuy2.c              |  59 +++++----
 modules/video_chroma/i422_i420.c              |   6 +-
 modules/video_chroma/i422_yuy2.c              |  69 +++++-----
 modules/video_chroma/omxdl.c                  |  52 ++++----
 modules/video_chroma/rv32.c                   |   6 +-
 modules/video_chroma/swscale.c                |   6 +-
 modules/video_chroma/yuvp.c                   |   2 +-
 modules/video_chroma/yuy2_i420.c              |   6 +-
 modules/video_chroma/yuy2_i422.c              |   6 +-
 modules/video_filter/adjust.c                 |  16 ++-
 modules/video_filter/alphamask.c              |   7 +-
 modules/video_filter/anaglyph.c               |   8 +-
 modules/video_filter/antiflicker.c            |   7 +-
 modules/video_filter/ball.c                   |   6 +-
 modules/video_filter/blend.cpp                |  13 +-
 modules/video_filter/blendbench.c             |  12 +-
 modules/video_filter/bluescreen.c             |   7 +-
 modules/video_filter/canvas.c                 |   7 +-
 modules/video_filter/ci_filters.m             |  14 +-
 modules/video_filter/colorthres.c             |  14 +-
 modules/video_filter/croppadd.c               |   7 +-
 .../video_filter/deinterlace/deinterlace.c    |  12 +-
 modules/video_filter/edgedetection.c          |   7 +-
 modules/video_filter/erase.c                  |   7 +-
 modules/video_filter/extract.c                |   7 +-
 modules/video_filter/fps.c                    |   7 +-
 modules/video_filter/freeze.c                 |   9 +-
 modules/video_filter/gaussianblur.c           |   7 +-
 modules/video_filter/gradfun.c                |  10 +-
 modules/video_filter/gradient.c               |   7 +-
 modules/video_filter/grain.c                  |  10 +-
 modules/video_filter/hqdn3d.c                 |   7 +-
 modules/video_filter/invert.c                 |   6 +-
 modules/video_filter/magnify.c                |   9 +-
 modules/video_filter/mirror.c                 |   6 +-
 modules/video_filter/motionblur.c             |   6 +-
 modules/video_filter/motiondetect.c           |   6 +-
 modules/video_filter/oldmovie.c               |   6 +-
 modules/video_filter/opencv_example.cpp       |  12 +-
 modules/video_filter/opencv_wrapper.c         |   9 +-
 modules/video_filter/posterize.c              |   6 +-
 modules/video_filter/postproc.c               |   6 +-
 modules/video_filter/psychedelic.c            |   6 +-
 modules/video_filter/puzzle.c                 |   8 +-
 modules/video_filter/ripple.c                 |   7 +-
 modules/video_filter/rotate.c                 |  14 +-
 modules/video_filter/scale.c                  |   7 +-
 modules/video_filter/scene.c                  |   8 +-
 modules/video_filter/sepia.c                  |   6 +-
 modules/video_filter/sharpen.c                |   6 +-
 modules/video_filter/transform.c              |   8 +-
 modules/video_filter/vhs.c                    |   6 +-
 modules/video_filter/wave.c                   |   6 +-
 modules/visualization/glspectrum.c            |   6 +-
 modules/visualization/goom.c                  |   8 +-
 modules/visualization/projectm.cpp            |  10 +-
 modules/visualization/visual/visual.c         |   8 +-
 modules/visualization/vsxu.cpp                |  10 +-
 src/audio_output/filters.c                    |   6 +-
 src/audio_output/meter.c                      |   8 +-
 src/misc/filter.c                             |   2 +-
 src/misc/filter_chain.c                       |  10 +-
 src/misc/image.c                              |   6 +-
 src/video_output/vout_subpictures.c           |   6 +-
 127 files changed, 1007 insertions(+), 438 deletions(-)

diff --git a/include/vlc_filter.h b/include/vlc_filter.h
index d9c274a645a..0ced8ea03d6 100644
--- a/include/vlc_filter.h
+++ b/include/vlc_filter.h
@@ -79,68 +79,44 @@ typedef struct filter_owner_t
 
 struct vlc_mouse_t;
 
-/** Structure describing a filter
- * @warning BIG FAT WARNING : the code relies on the first 4 members of
- * filter_t and decoder_t to be the same, so if you have anything to add,
- * do it at the end of the structure.
- */
-struct filter_t
+struct vlc_filter_operations
 {
-    struct vlc_object_t obj;
-
-    /* Module properties */
-    module_t *          p_module;
-    void               *p_sys;
-
-    /* Input format */
-    es_format_t         fmt_in;
-    vlc_video_context   *vctx_in;  // video filter, set by owner
-
-    /* Output format of filter */
-    es_format_t         fmt_out;
-    vlc_video_context   *vctx_out; // video filter, handled by the filter
-    bool                b_allow_fmt_out_change;
-
-    /* Name of the "video filter" shortcut that is requested, can be NULL */
-    const char *        psz_name;
-    /* Filter configuration */
-    config_chain_t *    p_cfg;
-
+    /* Operation depending on the type of filter. */
     union
     {
         /** Filter a picture (video filter) */
-        picture_t * (*pf_video_filter)( filter_t *, picture_t * );
+        picture_t * (*filter_video)(filter_t *, picture_t *);
 
         /** Filter an audio block (audio filter) */
-        block_t * (*pf_audio_filter)( filter_t *, block_t * );
+        block_t * (*filter_audio)(filter_t *, block_t *);
 
         /** Blend a subpicture onto a picture (blend) */
-        void (*pf_video_blend)( filter_t *,  picture_t *, const picture_t *,
-                                 int, int, int );
+        void (*blend_video)(filter_t *,  picture_t *, const picture_t *,
+                            int, int, int);
 
         /** Generate a subpicture (sub source) */
-        subpicture_t *(*pf_sub_source)( filter_t *, vlc_tick_t );
+        subpicture_t *(*source_sub)(filter_t *, vlc_tick_t);
 
         /** Filter a subpicture (sub filter) */
-        subpicture_t *(*pf_sub_filter)( filter_t *, subpicture_t * );
+        subpicture_t *(*filter_sub)(filter_t *, subpicture_t *);
 
         /** Render text (text render) */
-        int (*pf_render)( filter_t *, subpicture_region_t *,
-                          subpicture_region_t *, const vlc_fourcc_t * );
+        int (*render)(filter_t *, subpicture_region_t *,
+                      subpicture_region_t *, const vlc_fourcc_t *);
     };
 
     union
     {
         /* TODO: video filter drain */
         /** Drain (audio filter) */
-        block_t *(*pf_audio_drain) ( filter_t * );
+        block_t *(*drain_audio)(filter_t *);
     };
 
     /** Flush
      *
      * Flush (i.e. discard) any internal buffer in a video or audio filter.
      */
-    void (*pf_flush)( filter_t * );
+    void (*flush)(filter_t *);
 
     /** Change viewpoint
      *
@@ -148,20 +124,49 @@ struct filter_t
      * used for Ambisonics rendering will change its output according to this
      * viewpoint.
      */
-    void (*pf_change_viewpoint)( filter_t *, const vlc_viewpoint_t * );
+    void (*change_viewpoint)(filter_t *, const vlc_viewpoint_t *);
 
-    union
-    {
-        /** Filter mouse state (video filter).
-         *
-         * If non-NULL, you must convert from output to input formats:
-         * - If VLC_SUCCESS is returned, the mouse state is propagated.
-         * - Otherwise, the mouse change is not propagated.
-         * If NULL, the mouse state is considered unchanged and will be
-         * propagated. */
-        int (*pf_video_mouse)( filter_t *, struct vlc_mouse_t *,
-                               const struct vlc_mouse_t *p_old);
-    };
+    /** Filter mouse state (video filter).
+     *
+     * If non-NULL, you must convert from output to input formats:
+     * - If VLC_SUCCESS is returned, the mouse state is propagated.
+     * - Otherwise, the mouse change is not propagated.
+     * If NULL, the mouse state is considered unchanged and will be
+     * propagated. */
+    int (*video_mouse)(filter_t *, struct vlc_mouse_t *,
+                       const struct vlc_mouse_t *p_old);
+
+};
+
+/** Structure describing a filter
+ * @warning BIG FAT WARNING : the code relies on the first 4 members of
+ * filter_t and decoder_t to be the same, so if you have anything to add,
+ * do it at the end of the structure.
+ */
+struct filter_t
+{
+    struct vlc_object_t obj;
+
+    /* Module properties */
+    module_t *          p_module;
+    void               *p_sys;
+
+    /* Input format */
+    es_format_t         fmt_in;
+    vlc_video_context   *vctx_in;  // video filter, set by owner
+
+    /* Output format of filter */
+    es_format_t         fmt_out;
+    vlc_video_context   *vctx_out; // video filter, handled by the filter
+    bool                b_allow_fmt_out_change;
+
+    /* Name of the "video filter" shortcut that is requested, can be NULL */
+    const char *        psz_name;
+    /* Filter configuration */
+    config_chain_t *    p_cfg;
+
+    /* Implementation of filter API */
+    const struct vlc_filter_operations *ops;
 
     /** Private structure for the owner of the filter */
     filter_owner_t      owner;
@@ -170,7 +175,7 @@ struct filter_t
 /**
  * This function will return a new picture usable by p_filter as an output
  * buffer. You have to release it using picture_Release or by returning
- * it to the caller as a pf_video_filter return value.
+ * it to the caller as a ops->filter_video return value.
  * Provided for convenience.
  *
  * \param p_filter filter_t object
@@ -198,15 +203,15 @@ static inline picture_t *filter_NewPicture( filter_t *p_filter )
  */
 static inline void filter_Flush( filter_t *p_filter )
 {
-    if( p_filter->pf_flush != NULL )
-        p_filter->pf_flush( p_filter );
+    if( p_filter->ops && p_filter->ops->flush != NULL )
+        p_filter->ops->flush( p_filter );
 }
 
 static inline void filter_ChangeViewpoint( filter_t *p_filter,
                                            const vlc_viewpoint_t *vp)
 {
-    if( p_filter->pf_change_viewpoint != NULL )
-        p_filter->pf_change_viewpoint( p_filter, vp );
+    if( p_filter->ops && p_filter->ops->change_viewpoint != NULL )
+        p_filter->ops->change_viewpoint( p_filter, vp );
 }
 
 static inline vlc_decoder_device * filter_HoldDecoderDevice( filter_t *p_filter )
@@ -239,8 +244,8 @@ static inline vlc_decoder_device * filter_HoldDecoderDeviceType( filter_t *p_fil
  */
 static inline block_t *filter_DrainAudio( filter_t *p_filter )
 {
-    if( p_filter->pf_audio_drain )
-        return p_filter->pf_audio_drain( p_filter );
+    if( p_filter->ops && p_filter->ops->drain_audio )
+        return p_filter->ops->drain_audio( p_filter );
     else
         return NULL;
 }
@@ -255,7 +260,7 @@ static inline void filter_SendAudioLoudness(filter_t *filter,
 /**
  * This function will return a new subpicture usable by p_filter as an output
  * buffer. You have to release it using subpicture_Delete or by returning it to
- * the caller as a pf_sub_source return value.
+ * the caller as a ops->sub_source return value.
  * Provided for convenience.
  *
  * \param p_filter filter_t object
@@ -353,7 +358,10 @@ VLC_API void filter_DeleteBlend( vlc_blender_t * );
         }                                                               \
         picture_Release( p_pic );                                       \
         return p_outpic;                                                \
-    }
+    }                                                                   \
+    static const struct vlc_filter_operations name ## _ops = {          \
+        .filter_video = name ## _Filter,                                \
+    };
 
 /**
  * Filter chain management API
diff --git a/modules/access/screen/screen.c b/modules/access/screen/screen.c
index 0c472deaa8f..fda1417eb0c 100644
--- a/modules/access/screen/screen.c
+++ b/modules/access/screen/screen.c
@@ -390,7 +390,7 @@ void RenderCursor( demux_t *p_demux, int i_x, int i_y,
     if( p_sys->p_blend )
     {
         p_sys->dst.p->p_pixels = p_dst;
-        p_sys->p_blend->pf_video_blend( p_sys->p_blend,
+        p_sys->p_blend->ops->blend_video( p_sys->p_blend,
                                         &p_sys->dst,
                                         p_sys->p_mouse,
 #ifdef SCREEN_SUBSCREEN
diff --git a/modules/arm_neon/chroma_yuv.c b/modules/arm_neon/chroma_yuv.c
index 53fe0f86f51..6715f228a97 100644
--- a/modules/arm_neon/chroma_yuv.c
+++ b/modules/arm_neon/chroma_yuv.c
@@ -231,16 +231,16 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_YUYV:
-                    filter->pf_video_filter = I420_YUYV_Filter;
+                    filter->ops = &I420_YUYV_ops;
                     break;
                 case VLC_CODEC_UYVY:
-                    filter->pf_video_filter = I420_UYVY_Filter;
+                    filter->ops = &I420_UYVY_ops;
                     break;
                 case VLC_CODEC_YVYU:
-                    filter->pf_video_filter = I420_YVYU_Filter;
+                    filter->ops = &I420_YVYU_ops;
                     break;
                 case VLC_CODEC_VYUY:
-                    filter->pf_video_filter = I420_VYUY_Filter;
+                    filter->ops = &I420_VYUY_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -251,16 +251,16 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_YUYV:
-                    filter->pf_video_filter = I420_YVYU_Filter;
+                    filter->ops = &I420_YVYU_ops;
                     break;
                 case VLC_CODEC_UYVY:
-                    filter->pf_video_filter = I420_VYUY_Filter;
+                    filter->ops = &I420_VYUY_ops;
                     break;
                 case VLC_CODEC_YVYU:
-                    filter->pf_video_filter = I420_YUYV_Filter;
+                    filter->ops = &I420_YUYV_ops;
                     break;
                 case VLC_CODEC_VYUY:
-                    filter->pf_video_filter = I420_UYVY_Filter;
+                    filter->ops = &I420_UYVY_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -271,16 +271,16 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_YUYV:
-                    filter->pf_video_filter = I422_YUYV_Filter;
+                    filter->ops = &I422_YUYV_ops;
                     break;
                 case VLC_CODEC_UYVY:
-                    filter->pf_video_filter = I422_UYVY_Filter;
+                    filter->ops = &I422_UYVY_ops;
                     break;
                 case VLC_CODEC_YVYU:
-                    filter->pf_video_filter = I422_YVYU_Filter;
+                    filter->ops = &I422_YVYU_ops;
                     break;
                 case VLC_CODEC_VYUY:
-                    filter->pf_video_filter = I422_VYUY_Filter;
+                    filter->ops = &I422_VYUY_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -292,10 +292,10 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I420:
-                    filter->pf_video_filter = Semiplanar_Planar_420_Filter;
+                    filter->ops = &Semiplanar_Planar_420_ops;
                     break;
                 case VLC_CODEC_YV12:
-                    filter->pf_video_filter = Semiplanar_Planar_420_Swap_Filter;
+                    filter->ops = &Semiplanar_Planar_420_Swap_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -306,10 +306,10 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I420:
-                    filter->pf_video_filter = Semiplanar_Planar_420_Swap_Filter;
+                    filter->ops = &Semiplanar_Planar_420_Swap_ops;
                     break;
                 case VLC_CODEC_YV12:
-                    filter->pf_video_filter = Semiplanar_Planar_420_Filter;
+                    filter->ops = &Semiplanar_Planar_420_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -320,7 +320,7 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I422:
-                    filter->pf_video_filter = Semiplanar_Planar_422_Filter;
+                    filter->ops = &Semiplanar_Planar_422_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -331,7 +331,7 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I444:
-                    filter->pf_video_filter = Semiplanar_Planar_444_Filter;
+                    filter->ops = &Semiplanar_Planar_444_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -343,7 +343,7 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I422:
-                    filter->pf_video_filter = YUYV_I422_Filter;
+                    filter->ops = &YUYV_I422_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -353,7 +353,7 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I422:
-                    filter->pf_video_filter = UYVY_I422_Filter;
+                    filter->ops = &UYVY_I422_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -363,7 +363,7 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I422:
-                    filter->pf_video_filter = YVYU_I422_Filter;
+                    filter->ops = &YVYU_I422_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -374,7 +374,7 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I422:
-                    filter->pf_video_filter = VYUY_I422_Filter;
+                    filter->ops = &VYUY_I422_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
diff --git a/modules/arm_neon/yuv_rgb.c b/modules/arm_neon/yuv_rgb.c
index 84b5328d768..a654a72f50d 100644
--- a/modules/arm_neon/yuv_rgb.c
+++ b/modules/arm_neon/yuv_rgb.c
@@ -150,7 +150,7 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_in.video.i_chroma)
             {
                 case VLC_CODEC_I420:
-                    filter->pf_video_filter = I420_RV16_Filter;
+                    filter->ops = &I420_RV16_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
@@ -166,16 +166,16 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_in.video.i_chroma)
             {
                 case VLC_CODEC_I420:
-                    filter->pf_video_filter = I420_RGBA_Filter;
+                    filter->ops = &I420_RGBA_ops;
                     break;
                 case VLC_CODEC_YV12:
-                    filter->pf_video_filter = YV12_RGBA_Filter;
+                    filter->ops = &YV12_RGBA_ops;
                     break;
                 case VLC_CODEC_NV21:
-                    filter->pf_video_filter = NV21_RGBA_Filter;
+                    filter->ops = &NV21_RGBA_ops;
                     break;
                 case VLC_CODEC_NV12:
-                    filter->pf_video_filter = NV12_RGBA_Filter;
+                    filter->ops = &NV12_RGBA_ops;
                     break;
                 default:
                     return VLC_EGENERIC;
diff --git a/modules/audio_filter/audiobargraph_a.c b/modules/audio_filter/audiobargraph_a.c
index e744b52f243..90a212c2901 100644
--- a/modules/audio_filter/audiobargraph_a.c
+++ b/modules/audio_filter/audiobargraph_a.c
@@ -137,7 +137,12 @@ static int Open( vlc_object_t *p_this )
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     aout_FormatPrepare(&p_filter->fmt_in.audio);
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoWork,
+    };
+    p_filter->ops = &filter_ops;
 
     vlc_object_t *vlc = VLC_OBJECT(vlc_object_instance(p_filter));
 
diff --git a/modules/audio_filter/center.c b/modules/audio_filter/center.c
index fe9301cfdbf..14a41e156c3 100644
--- a/modules/audio_filter/center.c
+++ b/modules/audio_filter/center.c
@@ -75,7 +75,12 @@ static int Open (vlc_object_t *in)
     filter->fmt_out.audio.i_rate = filter->fmt_in.audio.i_rate;
     aout_FormatPrepare(&filter->fmt_in.audio);
     aout_FormatPrepare(&filter->fmt_out.audio);
-    filter->pf_audio_filter = Process;
+
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = Process,
+    };
+    filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
diff --git a/modules/audio_filter/channel_mixer/dolby.c b/modules/audio_filter/channel_mixer/dolby.c
index 8d5c63c2567..de05e3eb7ff 100644
--- a/modules/audio_filter/channel_mixer/dolby.c
+++ b/modules/audio_filter/channel_mixer/dolby.c
@@ -138,7 +138,11 @@ static int Create( vlc_object_t *p_this )
         ++i;
     }
 
-    p_filter->pf_audio_filter = DoWork;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoWork,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/audio_filter/channel_mixer/headphone.c b/modules/audio_filter/channel_mixer/headphone.c
index 0c91a158926..77aa32411ba 100644
--- a/modules/audio_filter/channel_mixer/headphone.c
+++ b/modules/audio_filter/channel_mixer/headphone.c
@@ -478,7 +478,12 @@ static int OpenFilter( vlc_object_t *p_this )
     {
         p_filter->fmt_in.audio.i_physical_channels = AOUT_CHANS_5_0;
     }
-    p_filter->pf_audio_filter = Convert;
+
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = Convert,
+    };
+    p_filter->ops = &filter_ops;
 
     aout_FormatPrepare(&p_filter->fmt_in.audio);
     aout_FormatPrepare(&p_filter->fmt_out.audio);
diff --git a/modules/audio_filter/channel_mixer/mono.c b/modules/audio_filter/channel_mixer/mono.c
index 0c383e761ec..74f8ccb736f 100644
--- a/modules/audio_filter/channel_mixer/mono.c
+++ b/modules/audio_filter/channel_mixer/mono.c
@@ -384,7 +384,12 @@ static int OpenFilter( vlc_object_t *p_this )
         p_filter->fmt_out.audio.i_channels = 2;
     }
     p_filter->fmt_out.audio.i_rate = p_filter->fmt_in.audio.i_rate;
-    p_filter->pf_audio_filter = Convert;
+
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = Convert,
+    };
+    p_filter->ops = &filter_ops;
 
     msg_Dbg( p_this, "%4.4s->%4.4s, channels %d->%d, bits per sample: %i->%i",
              (char *)&p_filter->fmt_in.i_codec,
diff --git a/modules/audio_filter/channel_mixer/remap.c b/modules/audio_filter/channel_mixer/remap.c
index 8c49a5f1ae3..e6299f8cb8f 100644
--- a/modules/audio_filter/channel_mixer/remap.c
+++ b/modules/audio_filter/channel_mixer/remap.c
@@ -366,7 +366,11 @@ static int OpenFilter( vlc_object_t *p_this )
              aout_FormatPrintChannels( audio_in ),
              aout_FormatPrintChannels( audio_out ) );
 
-    p_filter->pf_audio_filter = Remap;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = Remap,
+    };
+    p_filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
diff --git a/modules/audio_filter/channel_mixer/simple.c b/modules/audio_filter/channel_mixer/simple.c
index 567269a6bff..06f17e3fbfc 100644
--- a/modules/audio_filter/channel_mixer/simple.c
+++ b/modules/audio_filter/channel_mixer/simple.c
@@ -345,8 +345,11 @@ static int OpenFilter( vlc_object_t *p_this )
     if( do_work == NULL )
         return VLC_EGENERIC;
 
-    p_filter->pf_audio_filter = Filter;
-    p_filter->p_sys = (void *)do_work;
+    static const struct vlc_filter_operations filter_ops =
+        { .filter_audio = Filter };
+
+    p_filter->ops = &filter_ops;
+    p_filter->p_sys = do_work;
     return VLC_SUCCESS;
 }
 
@@ -392,4 +395,3 @@ static block_t *Filter( filter_t *p_filter, block_t *p_block )
 
     return p_out;
 }
-
diff --git a/modules/audio_filter/channel_mixer/spatialaudio.cpp b/modules/audio_filter/channel_mixer/spatialaudio.cpp
index acb9ed0b75b..803c03bbaa2 100644
--- a/modules/audio_filter/channel_mixer/spatialaudio.cpp
+++ b/modules/audio_filter/channel_mixer/spatialaudio.cpp
@@ -318,6 +318,16 @@ static int allocateBuffers(filter_spatialaudio *p_sys)
     return VLC_SUCCESS;
 }
 
+static const struct FilterOperationInitializer {
+    struct vlc_filter_operations ops {};
+    FilterOperationInitializer()
+    {
+        ops.filter_audio = Mix;
+        ops.flush = Flush;
+        ops.change_viewpoint = ChangeViewpoint;
+    };
+} filter_ops;
+
 static int OpenBinauralizer(vlc_object_t *p_this)
 {
     filter_t *p_filter = (filter_t *)p_this;
@@ -391,9 +401,7 @@ static int OpenBinauralizer(vlc_object_t *p_this)
     aout_FormatPrepare(outfmt);
 
     p_filter->p_sys = p_sys;
-    p_filter->pf_audio_filter = Mix;
-    p_filter->pf_flush = Flush;
-    p_filter->pf_change_viewpoint = ChangeViewpoint;
+    p_filter->ops = &filter_ops.ops;
 
     return VLC_SUCCESS;
 }
@@ -541,9 +549,7 @@ static int Open(vlc_object_t *p_this)
     }
 
     p_filter->p_sys = p_sys;
-    p_filter->pf_audio_filter = Mix;
-    p_filter->pf_flush = Flush;
-    p_filter->pf_change_viewpoint = ChangeViewpoint;
+    p_filter->ops = &filter_ops.ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/audio_filter/channel_mixer/trivial.c b/modules/audio_filter/channel_mixer/trivial.c
index 6bb58e34da6..faa142e6488 100644
--- a/modules/audio_filter/channel_mixer/trivial.c
+++ b/modules/audio_filter/channel_mixer/trivial.c
@@ -174,6 +174,18 @@ static int Create( vlc_object_t *p_this )
     const audio_format_t *infmt = &p_filter->fmt_in.audio;
     const audio_format_t *outfmt = &p_filter->fmt_out.audio;
 
+    static const struct vlc_filter_operations equal_filter_ops =
+        { .filter_audio = Equals };
+
+    static const struct vlc_filter_operations extract_filter_ops =
+        { .filter_audio = Extract };
+
+    static const struct vlc_filter_operations upmix_filter_ops =
+        { .filter_audio = Upmix };
+
+    static const struct vlc_filter_operations downmix_filter_ops =
+        { .filter_audio = Downmix };
+
     if( infmt->i_physical_channels == 0 )
     {
         assert( infmt->i_channels > 0 );
@@ -181,7 +193,7 @@ static int Create( vlc_object_t *p_this )
             return VLC_EGENERIC;
         if( aout_FormatNbChannels( outfmt ) == infmt->i_channels )
         {
-            p_filter->pf_audio_filter = Equals;
+            p_filter->ops = &equal_filter_ops;
             return VLC_SUCCESS;
         }
         else
@@ -189,7 +201,7 @@ static int Create( vlc_object_t *p_this )
             if( infmt->i_channels > AOUT_CHAN_MAX )
                 msg_Info(p_filter, "%d channels will be dropped.",
                          infmt->i_channels - AOUT_CHAN_MAX);
-            p_filter->pf_audio_filter = Extract;
+            p_filter->ops = &extract_filter_ops;
             return VLC_SUCCESS;
         }
     }
@@ -211,7 +223,7 @@ static int Create( vlc_object_t *p_this )
     if ( aout_FormatNbChannels( outfmt ) == 1
       && aout_FormatNbChannels( infmt ) == 1 )
     {
-        p_filter->pf_audio_filter = Equals;
+        p_filter->ops = &equal_filter_ops;
         return VLC_SUCCESS;
     }
 
@@ -293,7 +305,7 @@ static int Create( vlc_object_t *p_this )
             }
         if( b_equals )
         {
-            p_filter->pf_audio_filter = Equals;
+            p_filter->ops = &equal_filter_ops;
             return VLC_SUCCESS;
         }
     }
@@ -305,9 +317,9 @@ static int Create( vlc_object_t *p_this )
     memcpy( p_sys->channel_map, channel_map, sizeof(channel_map) );
 
     if( aout_FormatNbChannels( outfmt ) > aout_FormatNbChannels( infmt ) )
-        p_filter->pf_audio_filter = Upmix;
+        p_filter->ops = &upmix_filter_ops;
     else
-        p_filter->pf_audio_filter = Downmix;
+        p_filter->ops = &downmix_filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/audio_filter/chorus_flanger.c b/modules/audio_filter/chorus_flanger.c
index 8643c502f8e..6732fd412c2 100644
--- a/modules/audio_filter/chorus_flanger.c
+++ b/modules/audio_filter/chorus_flanger.c
@@ -196,7 +196,12 @@ static int Open( vlc_object_t *p_this )
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     aout_FormatPrepare(&p_filter->fmt_in.audio);
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoWork,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
@@ -344,7 +349,7 @@ static int paramCallback( vlc_object_t *p_this, char const *psz_var,
         {
             p_sys->f_delayTime = oldval.f_float;
             p_sys->i_bufferLength = p_sys->i_channels * ( (int)
-                            ( ( p_sys->f_delayTime + p_sys->f_sweepDepth ) * 
+                            ( ( p_sys->f_delayTime + p_sys->f_sweepDepth ) *
                               p_filter->fmt_in.audio.i_rate/1000 ) + 1 );
         }
     }
@@ -357,7 +362,7 @@ static int paramCallback( vlc_object_t *p_this, char const *psz_var,
         {
             p_sys->f_sweepDepth = oldval.f_float;
             p_sys->i_bufferLength = p_sys->i_channels * ( (int)
-                            ( ( p_sys->f_delayTime + p_sys->f_sweepDepth ) * 
+                            ( ( p_sys->f_delayTime + p_sys->f_sweepDepth ) *
                               p_filter->fmt_in.audio.i_rate/1000 ) + 1 );
         }
     }
diff --git a/modules/audio_filter/compressor.c b/modules/audio_filter/compressor.c
index 5aab1ceaaca..5f25865d55d 100644
--- a/modules/audio_filter/compressor.c
+++ b/modules/audio_filter/compressor.c
@@ -256,7 +256,12 @@ static int Open( vlc_object_t *p_this )
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     aout_FormatPrepare(&p_filter->fmt_in.audio);
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoWork,
+    };
+    p_filter->ops = &filter_ops;
 
     /* At this stage, we are ready! */
     msg_Dbg( p_filter, "compressor successfully initialized" );
diff --git a/modules/audio_filter/converter/format.c b/modules/audio_filter/converter/format.c
index b7801998d98..e9098f24fe0 100644
--- a/modules/audio_filter/converter/format.c
+++ b/modules/audio_filter/converter/format.c
@@ -71,10 +71,20 @@ static int Open(vlc_object_t *object)
     if (src->i_codec == dst->i_codec)
         return VLC_EGENERIC;
 
-    filter->pf_audio_filter = FindConversion(src->i_codec, dst->i_codec);
-    if (filter->pf_audio_filter == NULL)
+    cvt_t conv_func = FindConversion(src->i_codec, dst->i_codec);
+    if (conv_func == NULL)
         return VLC_EGENERIC;
 
+    struct vlc_filter_operations *filter_ops = vlc_obj_malloc(object, sizeof *filter_ops);
+    if (filter_ops == NULL)
+        return VLC_ENOMEM;
+
+    (*filter_ops) = (const struct vlc_filter_operations) {
+        .filter_audio = conv_func,
+    };
+
+    filter->ops = filter_ops;
+
     msg_Dbg(filter, "%4.4s->%4.4s, bits per sample: %i->%i",
             (char *)&src->i_codec, (char *)&dst->i_codec,
             src->audio.i_bitspersample, dst->audio.i_bitspersample);
diff --git a/modules/audio_filter/converter/tospdif.c b/modules/audio_filter/converter/tospdif.c
index c9d7e08adcf..e513e7aa288 100644
--- a/modules/audio_filter/converter/tospdif.c
+++ b/modules/audio_filter/converter/tospdif.c
@@ -616,8 +616,12 @@ static int Open( vlc_object_t *p_this )
     if( unlikely( p_sys == NULL ) )
         return VLC_ENOMEM;
 
-    p_filter->pf_audio_filter = DoWork;
-    p_filter->pf_flush = Flush;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoWork,
+        .flush = Flush,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/audio_filter/equalizer.c b/modules/audio_filter/equalizer.c
index 92ccaf85b2d..b99beb7ccd3 100644
--- a/modules/audio_filter/equalizer.c
+++ b/modules/audio_filter/equalizer.c
@@ -164,7 +164,11 @@ static int Open( vlc_object_t *p_this )
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     aout_FormatPrepare(&p_filter->fmt_in.audio);
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoWork,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
@@ -571,4 +575,3 @@ static int TwoPassCallback( vlc_object_t *p_this, char const *psz_cmd,
     vlc_mutex_unlock( &p_sys->lock );
     return VLC_SUCCESS;
 }
-
diff --git a/modules/audio_filter/gain.c b/modules/audio_filter/gain.c
index 655d9443049..049677396f0 100644
--- a/modules/audio_filter/gain.c
+++ b/modules/audio_filter/gain.c
@@ -98,7 +98,11 @@ static int Open( vlc_object_t *p_this )
     msg_Dbg( p_filter, "gain multiplier sets to %.2fx", p_sys->f_gain );
 
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = Process;
+
+    static const struct vlc_filter_operations filter_ops =
+        { .filter_audio = Process };
+    p_filter->ops = &filter_ops;
+
     return VLC_SUCCESS;
 }
 
diff --git a/modules/audio_filter/karaoke.c b/modules/audio_filter/karaoke.c
index 62967e5cec0..2ec3d6e37c2 100644
--- a/modules/audio_filter/karaoke.c
+++ b/modules/audio_filter/karaoke.c
@@ -54,7 +54,12 @@ static int Open (vlc_object_t *obj)
     filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     aout_FormatPrepare(&filter->fmt_in.audio);
     filter->fmt_out.audio = filter->fmt_in.audio;
-    filter->pf_audio_filter = Process;
+
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = Process,
+    };
+    filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
diff --git a/modules/audio_filter/libebur128.c b/modules/audio_filter/libebur128.c
index 2a1623741f0..368016eb7b6 100644
--- a/modules/audio_filter/libebur128.c
+++ b/modules/audio_filter/libebur128.c
@@ -230,6 +230,10 @@ Flush(filter_t *filter)
     }
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_audio = Process, .flush = Flush,
+};
+
 static int Open(vlc_object_t *this)
 {
     filter_t *filter = (filter_t *) this;
@@ -279,8 +283,7 @@ static int Open(vlc_object_t *this)
 
     filter->p_sys = sys;
     filter->fmt_out.audio = filter->fmt_in.audio;
-    filter->pf_audio_filter = Process;
-    filter->pf_flush = Flush;
+    filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
diff --git a/modules/audio_filter/normvol.c b/modules/audio_filter/normvol.c
index a9aa2b9cfb3..0fc01194d11 100644
--- a/modules/audio_filter/normvol.c
+++ b/modules/audio_filter/normvol.c
@@ -119,7 +119,11 @@ static int Open( vlc_object_t *p_this )
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     aout_FormatPrepare(&p_filter->fmt_in.audio);
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoWork,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/audio_filter/param_eq.c b/modules/audio_filter/param_eq.c
index f498ffe08f0..57efac83d56 100644
--- a/modules/audio_filter/param_eq.c
+++ b/modules/audio_filter/param_eq.c
@@ -114,17 +114,22 @@ static int Open( vlc_object_t *p_this )
 
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoWork,
+    };
+    p_filter->ops = &filter_ops;
 
     p_sys->f_lowf = var_InheritFloat( p_this, "param-eq-lowf");
     p_sys->f_lowgain = var_InheritFloat( p_this, "param-eq-lowgain");
     p_sys->f_highf = var_InheritFloat( p_this, "param-eq-highf");
     p_sys->f_highgain = var_InheritFloat( p_this, "param-eq-highgain");
- 
+
     p_sys->f_f1 = var_InheritFloat( p_this, "param-eq-f1");
     p_sys->f_Q1 = var_InheritFloat( p_this, "param-eq-q1");
     p_sys->f_gain1 = var_InheritFloat( p_this, "param-eq-gain1");
- 
+
     p_sys->f_f2 = var_InheritFloat( p_this, "param-eq-f2");
     p_sys->f_Q2 = var_InheritFloat( p_this, "param-eq-q2");
     p_sys->f_gain2 = var_InheritFloat( p_this, "param-eq-gain2");
@@ -132,7 +137,7 @@ static int Open( vlc_object_t *p_this )
     p_sys->f_f3 = var_InheritFloat( p_this, "param-eq-f3");
     p_sys->f_Q3 = var_InheritFloat( p_this, "param-eq-q3");
     p_sys->f_gain3 = var_InheritFloat( p_this, "param-eq-gain3");
- 
+
 
     i_samplerate = p_filter->fmt_in.audio.i_rate;
     CalcPeakEQCoeffs(p_sys->f_f1, p_sys->f_Q1, p_sys->f_gain1,
@@ -200,18 +205,18 @@ static void CalcPeakEQCoeffs( float f0, float Q, float gainDB, float Fs,
     if (f0 > Fs/2*0.95f) f0 = Fs/2*0.95f;
     if (gainDB < -40) gainDB = -40;
     if (gainDB > 40) gainDB = 40;
- 
+
     A = powf(10, gainDB/40);
     w0 = 2*((float)M_PI)*f0/Fs;
     alpha = sinf(w0)/(2*Q);
- 
+
     b0 = 1 + alpha*A;
     b1 = -2*cosf(w0);
     b2 = 1 - alpha*A;
     a0 = 1 + alpha/A;
     a1 = -2*cosf(w0);
     a2 = 1 - alpha/A;
- 
+
     // Store values to coeffs and normalize by 1/a0
     coeffs[0] = b0/a0;
     coeffs[1] = b1/a0;
@@ -320,4 +325,3 @@ void ProcessEQ( const float *src, float *dest, float *state,
         }
     }
 }
-
diff --git a/modules/audio_filter/resampler/bandlimited.c b/modules/audio_filter/resampler/bandlimited.c
index 2d544315af3..1256f71afd6 100644
--- a/modules/audio_filter/resampler/bandlimited.c
+++ b/modules/audio_filter/resampler/bandlimited.c
@@ -279,6 +279,10 @@ static block_t *Resample( filter_t * p_filter, block_t * p_in_buf )
     return p_out_buf;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_audio = Resample,
+};
+
 /*****************************************************************************
  * OpenFilter:
  *****************************************************************************/
@@ -306,7 +310,7 @@ static int OpenFilter( vlc_object_t *p_this )
 
     p_sys->i_old_wing = 0;
     p_sys->b_first = true;
-    p_filter->pf_audio_filter = Resample;
+    p_filter->ops = &filter_ops;
 
     msg_Dbg( p_this, "%4.4s/%iKHz/%i->%4.4s/%iKHz/%i",
              (char *)&p_filter->fmt_in.i_codec,
diff --git a/modules/audio_filter/resampler/soxr.c b/modules/audio_filter/resampler/soxr.c
index 1490d30c63e..b073e3e3a34 100644
--- a/modules/audio_filter/resampler/soxr.c
+++ b/modules/audio_filter/resampler/soxr.c
@@ -188,10 +188,14 @@ Open( vlc_object_t *p_obj, bool b_change_ratio )
              (const char *)&p_filter->fmt_out.audio.i_format,
              p_filter->fmt_out.audio.i_rate );
 
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = Resample,
+        .drain_audio = Drain,
+        .flush = Flush,
+    };
+    p_filter->ops = &filter_ops;
     p_filter->p_sys = p_sys;
-    p_filter->pf_audio_filter = Resample;
-    p_filter->pf_flush = Flush;
-    p_filter->pf_audio_drain = Drain;
     return VLC_SUCCESS;
 }
 
diff --git a/modules/audio_filter/resampler/speex.c b/modules/audio_filter/resampler/speex.c
index eeefc4f4c43..6c339489c2a 100644
--- a/modules/audio_filter/resampler/speex.c
+++ b/modules/audio_filter/resampler/speex.c
@@ -92,8 +92,12 @@ static int OpenResampler (vlc_object_t *obj)
         return VLC_ENOMEM;
     }
 
+    static const struct vlc_filter_operations filter_ops =
+        { .filter_audio = Resample };
+
     filter->p_sys = st;
-    filter->pf_audio_filter = Resample;
+    filter->ops = &filter_ops;
+
     return VLC_SUCCESS;
 }
 
diff --git a/modules/audio_filter/resampler/src.c b/modules/audio_filter/resampler/src.c
index 1254314b137..5e069e76bc6 100644
--- a/modules/audio_filter/resampler/src.c
+++ b/modules/audio_filter/resampler/src.c
@@ -103,8 +103,13 @@ static int OpenResampler (vlc_object_t *obj)
         return VLC_EGENERIC;
     }
 
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = Resample,
+    };
+    filter->ops = &filter_ops;
     filter->p_sys = s;
-    filter->pf_audio_filter = Resample;
+
     return VLC_SUCCESS;
 }
 
diff --git a/modules/audio_filter/resampler/ugly.c b/modules/audio_filter/resampler/ugly.c
index 63bba803c77..da13a598dd0 100644
--- a/modules/audio_filter/resampler/ugly.c
+++ b/modules/audio_filter/resampler/ugly.c
@@ -77,7 +77,11 @@ static int CreateResampler( vlc_object_t *p_this )
      || !AOUT_FMT_LINEAR( &p_filter->fmt_in.audio ) )
         return VLC_EGENERIC;
 
-    p_filter->pf_audio_filter = DoWork;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoWork,
+    };
+    p_filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
diff --git a/modules/audio_filter/scaletempo.c b/modules/audio_filter/scaletempo.c
index c67e01a50fc..18f8a30d106 100644
--- a/modules/audio_filter/scaletempo.c
+++ b/modules/audio_filter/scaletempo.c
@@ -444,7 +444,11 @@ static int Open( vlc_object_t *p_this )
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     aout_FormatPrepare(&p_filter->fmt_in.audio);
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoWork,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
@@ -510,7 +514,11 @@ static int OpenPitch( vlc_object_t *p_this )
     if( !p_sys->resampler )
         return VLC_EGENERIC;
 
-    p_filter->pf_audio_filter = DoPitchWork;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = DoPitchWork,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
@@ -609,7 +617,7 @@ static block_t *DoPitchWork( filter_t * p_filter, block_t * p_in_buf )
     p_filter->fmt_in.audio.i_rate = rate_shift;
 
     /* Change rate, thus changing pitch */
-    p_in_buf = p->resampler->pf_audio_filter( p->resampler, p_in_buf );
+    p_in_buf = p->resampler->ops->filter_audio( p->resampler, p_in_buf );
 
     /* Change tempo while preserving shifted pitch */
     return DoWork( p_filter, p_in_buf );
diff --git a/modules/audio_filter/spatializer/spatializer.cpp b/modules/audio_filter/spatializer/spatializer.cpp
index 4f9c19b1330..a1f78d30af9 100644
--- a/modules/audio_filter/spatializer/spatializer.cpp
+++ b/modules/audio_filter/spatializer/spatializer.cpp
@@ -171,7 +171,16 @@ static int Open( vlc_object_t *p_this )
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     aout_FormatPrepare(&p_filter->fmt_in.audio);
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+
+    static const struct FilterOperationInitializer {
+        struct vlc_filter_operations ops {};
+        FilterOperationInitializer()
+        {
+            ops.filter_audio = DoWork;
+        };
+    } filter_ops;
+
+    p_filter->ops = &filter_ops.ops;
     return VLC_SUCCESS;
 }
 
@@ -286,4 +295,3 @@ static int DampCallback( vlc_object_t *p_this, char const *,
     msg_Dbg( p_this, "'damp' value is now %3.1f", newval.f_float );
     return VLC_SUCCESS;
 }
-
diff --git a/modules/audio_filter/stereo_widen.c b/modules/audio_filter/stereo_widen.c
index 1ec29bfffd0..ba19de6d464 100644
--- a/modules/audio_filter/stereo_widen.c
+++ b/modules/audio_filter/stereo_widen.c
@@ -153,7 +153,11 @@ static int Open( vlc_object_t *obj )
         return VLC_ENOMEM;
     }
 
-    p_filter->pf_audio_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_audio = Filter,
+    };
+    p_filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
diff --git a/modules/hw/d3d11/d3d11_deinterlace.c b/modules/hw/d3d11/d3d11_deinterlace.c
index b2c8d99447a..3eb8e9bf081 100644
--- a/modules/hw/d3d11/d3d11_deinterlace.c
+++ b/modules/hw/d3d11/d3d11_deinterlace.c
@@ -232,6 +232,10 @@ picture_t *AllocPicture( filter_t *p_filter )
     return pic;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Deinterlace, .flush = Flush,
+};
+
 int D3D11OpenDeinterlace(vlc_object_t *obj)
 {
     filter_t *filter = (filter_t *)obj;
@@ -350,8 +354,7 @@ int D3D11OpenDeinterlace(vlc_object_t *obj)
 
     filter->fmt_out.video   = out_fmt;
     filter->vctx_out        = vlc_video_context_Hold(filter->vctx_in);
-    filter->pf_video_filter = Deinterlace;
-    filter->pf_flush        = Flush;
+    filter->ops             = &filter_ops;
     filter->p_sys = sys;
 
     return VLC_SUCCESS;
diff --git a/modules/hw/d3d11/d3d11_filters.c b/modules/hw/d3d11/d3d11_filters.c
index 868ce7ac31f..36e3351543e 100644
--- a/modules/hw/d3d11/d3d11_filters.c
+++ b/modules/hw/d3d11/d3d11_filters.c
@@ -335,6 +335,10 @@ static int AdjustCallback( vlc_object_t *p_this, char const *psz_var,
     return VLC_SUCCESS;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Filter,
+};
+
 static int D3D11OpenAdjust(vlc_object_t *obj)
 {
     filter_t *filter = (filter_t *)obj;
@@ -507,7 +511,7 @@ static int D3D11OpenAdjust(vlc_object_t *obj)
         }
     }
 
-    filter->pf_video_filter = Filter;
+    filter->ops = &filter_ops;
     filter->p_sys = sys;
     filter->vctx_out = vlc_video_context_Hold(filter->vctx_in);
     d3d11_device_unlock(sys->d3d_dev);
diff --git a/modules/hw/d3d11/d3d11_surface.c b/modules/hw/d3d11/d3d11_surface.c
index d6bc9ba0267..f3fe4ddbd44 100644
--- a/modules/hw/d3d11/d3d11_surface.c
+++ b/modules/hw/d3d11/d3d11_surface.c
@@ -566,7 +566,7 @@ static void NV12_D3D11(filter_t *p_filter, picture_t *src, picture_t *dst)
         sys->staging_pic->context = NULL; // some CPU filters won't like the mix of CPU/GPU
 
         picture_Hold( src );
-        sys->filter->pf_video_filter(sys->filter, src);
+        sys->filter->ops->filter_video(sys->filter, src);
 
         sys->staging_pic->context = staging_pic_ctx;
         ID3D11DeviceContext_Unmap(sys->d3d_dev->d3dcontext, p_staging_sys->resource[KNOWN_DXGI_INDEX], 0);
@@ -660,6 +660,10 @@ static picture_t *NV12_D3D11_Filter( filter_t *p_filter, picture_t *p_pic )
     return p_outpic;
 }
 
+static const struct vlc_filter_operations NV12_D3D11_ops = {
+    .filter_video = NV12_D3D11_Filter,
+};
+
 int D3D11OpenConverter( vlc_object_t *obj )
 {
     filter_t *p_filter = (filter_t *)obj;
@@ -679,34 +683,34 @@ int D3D11OpenConverter( vlc_object_t *obj )
     case VLC_CODEC_YV12:
         if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D11_OPAQUE )
             return VLC_EGENERIC;
-        p_filter->pf_video_filter = D3D11_YUY2_Filter;
+        p_filter->ops = &D3D11_YUY2_ops;
         break;
     case VLC_CODEC_I420_10L:
         if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D11_OPAQUE_10B )
             return VLC_EGENERIC;
-        p_filter->pf_video_filter = D3D11_YUY2_Filter;
+        p_filter->ops = &D3D11_YUY2_ops;
         pixel_bytes = 2;
         break;
     case VLC_CODEC_NV12:
         if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D11_OPAQUE )
             return VLC_EGENERIC;
-        p_filter->pf_video_filter = D3D11_NV12_Filter;
+        p_filter->ops = &D3D11_NV12_ops;
         break;
     case VLC_CODEC_P010:
         if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D11_OPAQUE_10B )
             return VLC_EGENERIC;
-        p_filter->pf_video_filter = D3D11_NV12_Filter;
+        p_filter->ops = &D3D11_NV12_ops;
         pixel_bytes = 2;
         break;
     case VLC_CODEC_RGBA:
         if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D11_OPAQUE_RGBA )
             return VLC_EGENERIC;
-        p_filter->pf_video_filter = D3D11_RGBA_Filter;
+        p_filter->ops = &D3D11_RGBA_ops;
         break;
     case VLC_CODEC_BGRA:
         if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D11_OPAQUE_BGRA )
             return VLC_EGENERIC;
-        p_filter->pf_video_filter = D3D11_RGBA_Filter;
+        p_filter->ops = &D3D11_RGBA_ops;
         break;
     default:
         return VLC_EGENERIC;
@@ -753,7 +757,7 @@ int D3D11OpenCPUConverter( vlc_object_t *obj )
     case VLC_CODEC_YV12:
     case VLC_CODEC_NV12:
     case VLC_CODEC_P010:
-        p_filter->pf_video_filter = NV12_D3D11_Filter;
+        p_filter->ops = &NV12_D3D11_ops;
         break;
     default:
         return VLC_EGENERIC;
diff --git a/modules/hw/d3d9/d3d9_filters.c b/modules/hw/d3d9/d3d9_filters.c
index 80106bce5b2..07eaa133a99 100644
--- a/modules/hw/d3d9/d3d9_filters.c
+++ b/modules/hw/d3d9/d3d9_filters.c
@@ -259,6 +259,10 @@ static int AdjustCallback( vlc_object_t *p_this, char const *psz_var,
     return VLC_SUCCESS;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Filter,
+};
+
 static int D3D9OpenAdjust(vlc_object_t *obj)
 {
     filter_t *filter = (filter_t *)obj;
@@ -450,7 +454,7 @@ static int D3D9OpenAdjust(vlc_object_t *obj)
 
     sys->hdecoder_dll = hdecoder_dll;
 
-    filter->pf_video_filter = Filter;
+    filter->ops = &filter_ops;
     filter->p_sys = sys;
     filter->vctx_out = vlc_video_context_Hold(filter->vctx_in);
 
diff --git a/modules/hw/d3d9/dxa9.c b/modules/hw/d3d9/dxa9.c
index 563818dc708..afb484895d1 100644
--- a/modules/hw/d3d9/dxa9.c
+++ b/modules/hw/d3d9/dxa9.c
@@ -258,7 +258,7 @@ static void YV12_D3D9(filter_t *p_filter, picture_t *src, picture_t *dst)
 
         picture_Hold( src );
 
-        sys->filter->pf_video_filter(sys->filter, src);
+        sys->filter->ops->filter_video(sys->filter, src);
 
         sys->staging->context = staging_pic_ctx;
 
@@ -396,23 +396,23 @@ int D3D9OpenConverter( vlc_object_t *obj )
     case VLC_CODEC_YV12:
         if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D9_OPAQUE )
             return VLC_EGENERIC;
-        p_filter->pf_video_filter = DXA9_YV12_Filter;
+        p_filter->ops = &DXA9_YV12_ops;
         break;
     case VLC_CODEC_I420_10L:
         if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D9_OPAQUE_10B )
             return VLC_EGENERIC;
-        p_filter->pf_video_filter = DXA9_YV12_Filter;
+        p_filter->ops = &DXA9_YV12_ops;
         pixel_bytes = 2;
         break;
     case VLC_CODEC_NV12:
         if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D9_OPAQUE )
             return VLC_EGENERIC;
-        p_filter->pf_video_filter = DXA9_NV12_Filter;
+        p_filter->ops = &DXA9_NV12_ops;
         break;
     case VLC_CODEC_P010:
         if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D9_OPAQUE_10B )
             return VLC_EGENERIC;
-        p_filter->pf_video_filter = DXA9_NV12_Filter;
+        p_filter->ops = &DXA9_NV12_ops;
         pixel_bytes = 2;
         break;
     default:
@@ -433,6 +433,10 @@ int D3D9OpenConverter( vlc_object_t *obj )
     return VLC_SUCCESS;
 }
 
+static const struct vlc_filter_operations YV12_D3D9_ops = {
+    .filter_video = YV12_D3D9_Filter,
+};
+
 int D3D9OpenCPUConverter( vlc_object_t *obj )
 {
     filter_t *p_filter = (filter_t *)obj;
@@ -452,7 +456,7 @@ int D3D9OpenCPUConverter( vlc_object_t *obj )
     case VLC_CODEC_YV12:
     case VLC_CODEC_I420_10L:
     case VLC_CODEC_P010:
-        p_filter->pf_video_filter = YV12_D3D9_Filter;
+        p_filter->ops = &YV12_D3D9_ops;
         break;
     default:
         return VLC_EGENERIC;
diff --git a/modules/hw/d3d9/dxva2_deinterlace.c b/modules/hw/d3d9/dxva2_deinterlace.c
index 7c0c4ec6920..0d5f5825500 100644
--- a/modules/hw/d3d9/dxva2_deinterlace.c
+++ b/modules/hw/d3d9/dxva2_deinterlace.c
@@ -321,6 +321,10 @@ picture_t *AllocPicture( filter_t *p_filter )
     return pic;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Deinterlace, .flush = Flush,
+};
+
 int D3D9OpenDeinterlace(vlc_object_t *obj)
 {
     filter_t *filter = (filter_t *)obj;
@@ -501,8 +505,7 @@ int D3D9OpenDeinterlace(vlc_object_t *obj)
 
     filter->fmt_out.video   = out_fmt;
     filter->vctx_out        = vlc_video_context_Hold(filter->vctx_in);
-    filter->pf_video_filter = Deinterlace;
-    filter->pf_flush        = Flush;
+    filter->ops             = &filter_ops;
     filter->p_sys = sys;
 
     return VLC_SUCCESS;
diff --git a/modules/hw/mmal/converter.c b/modules/hw/mmal/converter.c
index f62f21e0876..eecf36eac00 100644
--- a/modules/hw/mmal/converter.c
+++ b/modules/hw/mmal/converter.c
@@ -787,6 +787,10 @@ static MMAL_FOURCC_T filter_enc_in(const video_format_t * const fmt)
 }
 
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = conv_filter, .flush = conv_flush,
+};
+
 int OpenConverter(vlc_object_t * obj)
 {
     filter_t * const p_filter = (filter_t *)obj;
@@ -975,8 +979,7 @@ retry:
         }
     }
 
-    p_filter->pf_video_filter = conv_filter;
-    p_filter->pf_flush = conv_flush;
+    p_filter->ops = &filter_ops;
     // video_drain NIF in filter structure
 
     return VLC_SUCCESS;
diff --git a/modules/hw/mmal/deinterlace.c b/modules/hw/mmal/deinterlace.c
index 2709a1f1730..97c11a20c81 100644
--- a/modules/hw/mmal/deinterlace.c
+++ b/modules/hw/mmal/deinterlace.c
@@ -417,6 +417,14 @@ static bool is_fmt_valid_in(const vlc_fourcc_t fmt)
     return fmt == VLC_CODEC_MMAL_OPAQUE;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = deinterlace, .flush = di_flush,
+};
+
+static const struct vlc_filter_operations filter_pass_ops = {
+    .filter_video = pass_deinterlace, .flush = pass_flush,
+};
+
 static int OpenMmalDeinterlace(vlc_object_t *p_this)
 {
     filter_t *filter = (filter_t*)p_this;
@@ -499,8 +507,7 @@ static int OpenMmalDeinterlace(vlc_object_t *p_this)
 
     if (sys->use_passthrough)
     {
-        filter->pf_video_filter = pass_deinterlace;
-        filter->pf_flush = pass_flush;
+        filter->ops = &filter_pass_ops;
         return VLC_SUCCESS;
     }
 
@@ -600,8 +607,7 @@ static int OpenMmalDeinterlace(vlc_object_t *p_this)
         goto fail;
     }
 
-    filter->pf_video_filter = deinterlace;
-    filter->pf_flush = di_flush;
+    filter->ops = &filter_ops;
     return 0;
 
 fail:
diff --git a/modules/hw/nvdec/chroma.c b/modules/hw/nvdec/chroma.c
index 88353f5cbed..2e09fb18cef 100644
--- a/modules/hw/nvdec/chroma.c
+++ b/modules/hw/nvdec/chroma.c
@@ -117,6 +117,10 @@ done:
     return dst;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = FilterCUDAToCPU,
+};
+
 static int OpenCUDAToCPU( vlc_object_t *p_this )
 {
     filter_t *p_filter = (filter_t *)p_this;
@@ -137,7 +141,7 @@ static int OpenCUDAToCPU( vlc_object_t *p_this )
            ) )
         return VLC_EGENERIC;
 
-    p_filter->pf_video_filter = FilterCUDAToCPU;
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/hw/vaapi/chroma.c b/modules/hw/vaapi/chroma.c
index 38b904ed203..7ea95254d40 100644
--- a/modules/hw/vaapi/chroma.c
+++ b/modules/hw/vaapi/chroma.c
@@ -316,6 +316,14 @@ static int CheckFmt(const video_format_t *in, const video_format_t *out,
     return VLC_EGENERIC;
 }
 
+static const struct vlc_filter_operations filter_upload_ops = {
+    .filter_video = UploadSurface,
+};
+
+static const struct vlc_filter_operations filter_download_ops = {
+    .filter_video = DownloadSurface,
+};
+
 int
 vlc_vaapi_OpenChroma(vlc_object_t *obj)
 {
@@ -333,7 +341,7 @@ vlc_vaapi_OpenChroma(vlc_object_t *obj)
                  &pixel_bytes))
         return VLC_EGENERIC;
 
-    filter->pf_video_filter = is_upload ? UploadSurface : DownloadSurface;
+    filter->ops = is_upload ? &filter_upload_ops : &filter_download_ops;
 
     if (!(filter_sys = calloc(1, sizeof(filter_sys_t))))
     {
diff --git a/modules/hw/vaapi/filters.c b/modules/hw/vaapi/filters.c
index debe2b507b1..ac96b16c81c 100644
--- a/modules/hw/vaapi/filters.c
+++ b/modules/hw/vaapi/filters.c
@@ -567,6 +567,10 @@ OpenAdjust_InitFilterParams(filter_t * filter, void * p_data,
     return VLC_SUCCESS;
 }
 
+static const struct vlc_filter_operations Adjust_ops = {
+    .filter_video = Adjust,
+};
+
 static int
 OpenAdjust(vlc_object_t * obj)
 {
@@ -587,7 +591,7 @@ OpenAdjust(vlc_object_t * obj)
     for (unsigned int i = 0; i < NUM_ADJUST_MODES; ++i)
         var_AddCallback(obj, adjust_params_names[i], FilterCallback, p_data);
 
-    filter->pf_video_filter = Adjust;
+    filter->ops = &Adjust_ops;
 
     return VLC_SUCCESS;
 
@@ -684,6 +688,10 @@ OpenBasicFilter_InitFilterParams(filter_t * filter, void * p_data,
     return VLC_SUCCESS;
 }
 
+static const struct vlc_filter_operations BasicFilter_ops = {
+    .filter_video = BasicFilter,
+};
+
 static int
 OpenBasicFilter(vlc_object_t * obj, VAProcFilterType filter_type,
                 const char *psz_sigma_name, struct range const *p_vlc_range)
@@ -708,7 +716,7 @@ OpenBasicFilter(vlc_object_t * obj, VAProcFilterType filter_type,
 
     var_AddCallback(obj, p_data->sigma.psz_name, FilterCallback, p_data);
 
-    filter->pf_video_filter = BasicFilter;
+    filter->ops = &BasicFilter_ops;
 
     return VLC_SUCCESS;
 
@@ -1065,6 +1073,13 @@ OpenDeinterlace_InitHistory(void * p_data, VAProcPipelineCaps const * pipeline_c
     return VLC_SUCCESS;
 }
 
+static const struct vlc_filter_operations DeinterlaceX2_ops = {
+    .filter_video = DeinterlaceX2, .flush = Deinterlace_Flush,
+};
+static const struct vlc_filter_operations Deinterlace_ops = {
+    .filter_video = Deinterlace,   .flush = Deinterlace_Flush,
+};
+
 static int
 OpenDeinterlace(vlc_object_t * obj)
 {
@@ -1079,10 +1094,9 @@ OpenDeinterlace(vlc_object_t * obj)
         goto error;
 
     if (p_data->b_double_rate)
-        filter->pf_video_filter = DeinterlaceX2;
+        filter->ops = &DeinterlaceX2_ops;
     else
-        filter->pf_video_filter = Deinterlace;
-    filter->pf_flush = Deinterlace_Flush;
+        filter->ops = &Deinterlace_ops;
 
     for (unsigned int i = 0; i < METADATA_SIZE; ++i)
     {
diff --git a/modules/hw/vdpau/adjust.c b/modules/hw/vdpau/adjust.c
index 27c8b4bfa7d..3a4d5a5387a 100644
--- a/modules/hw/vdpau/adjust.c
+++ b/modules/hw/vdpau/adjust.c
@@ -139,6 +139,10 @@ static const char *const options[] = {
     "brightness", "contrast", "saturation", "hue", NULL
 };
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Adjust,
+};
+
 static int Open(vlc_object_t *obj)
 {
     filter_t *filter = (filter_t *)obj;
@@ -157,7 +161,7 @@ static int Open(vlc_object_t *obj)
     if (unlikely(sys == NULL))
         return VLC_ENOMEM;
 
-    filter->pf_video_filter = Adjust;
+    filter->ops = &filter_ops;
     filter->p_sys = sys;
 
     config_ChainParse(filter, "", options, filter->p_cfg);
diff --git a/modules/hw/vdpau/chroma.c b/modules/hw/vdpau/chroma.c
index fa4b1e8b62c..06c1a633d89 100644
--- a/modules/hw/vdpau/chroma.c
+++ b/modules/hw/vdpau/chroma.c
@@ -743,6 +743,14 @@ const struct vlc_video_context_operations vdpau_vctx_ops = {
     NULL,
 };
 
+static const struct vlc_filter_operations filter_output_opaque_ops = {
+    .filter_video = VideoRender, .flush = Flush,
+};
+
+static const struct vlc_filter_operations filter_output_ycbcr_ops = {
+    .filter_video = YCbCrRender, .flush = Flush,
+};
+
 static int OutputOpen(vlc_object_t *obj)
 {
     filter_t *filter = (filter_t *)obj;
@@ -766,7 +774,7 @@ static int OutputOpen(vlc_object_t *obj)
 
     filter->p_sys = sys;
 
-    picture_t *(*video_filter)(filter_t *, picture_t *) = VideoRender;
+    const struct vlc_filter_operations *ops = &filter_output_opaque_ops;
 
     if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_444)
     {
@@ -789,7 +797,7 @@ static int OutputOpen(vlc_object_t *obj)
     else
     if (vlc_fourcc_to_vdp_ycc(filter->fmt_in.video.i_chroma,
                               &sys->chroma, &sys->format))
-        video_filter = YCbCrRender;
+        ops = &filter_output_ycbcr_ops;
     else
     {
         vlc_decoder_device_Release(dec_device);
@@ -817,7 +825,7 @@ static int OutputOpen(vlc_object_t *obj)
     }
 
     /* Create the video-to-output mixer */
-    sys->mixer = MixerCreate(filter, video_filter == YCbCrRender);
+    sys->mixer = MixerCreate(filter, ops == &filter_output_ycbcr_ops);
     if (sys->mixer == VDP_INVALID_HANDLE)
     {
         picture_pool_Release(sys->pool);
@@ -834,8 +842,7 @@ static int OutputOpen(vlc_object_t *obj)
     sys->procamp.saturation = 1.f;
     sys->procamp.hue = 0.f;
 
-    filter->pf_video_filter = video_filter;
-    filter->pf_flush = Flush;
+    filter->ops = ops;
     return VLC_SUCCESS;
 }
 
@@ -888,6 +895,10 @@ static bool ChromaMatches(VdpChromaType vdp_type, vlc_fourcc_t vlc_chroma)
     }
 }
 
+static const struct vlc_filter_operations filter_ycbcr_ops = {
+    .filter_video = VideoExport_Filter,
+};
+
 static int YCbCrOpen(vlc_object_t *obj)
 {
     filter_t *filter = (filter_t *)obj;
@@ -913,7 +924,7 @@ static int YCbCrOpen(vlc_object_t *obj)
         return VLC_ENOMEM;
     sys->format = format;
 
-    filter->pf_video_filter = VideoExport_Filter;
+    filter->ops = &filter_ycbcr_ops;
     filter->p_sys = sys;
     return VLC_SUCCESS;
 }
diff --git a/modules/hw/vdpau/deinterlace.c b/modules/hw/vdpau/deinterlace.c
index bd66a61d1b0..2a5a996f384 100644
--- a/modules/hw/vdpau/deinterlace.c
+++ b/modules/hw/vdpau/deinterlace.c
@@ -96,6 +96,10 @@ static picture_t *Deinterlace(filter_t *filter, picture_t *src)
     return src;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Deinterlace,
+};
+
 static int Open(vlc_object_t *obj)
 {
     filter_t *filter = (filter_t *)obj;
@@ -119,7 +123,7 @@ static int Open(vlc_object_t *obj)
 
     sys->last_pts = VLC_TICK_INVALID;
 
-    filter->pf_video_filter = Deinterlace;
+    filter->ops = &filter_ops;
     filter->p_sys = sys;
     filter->fmt_out.video.i_frame_rate *= 2;
     filter->vctx_out = vlc_video_context_Hold(filter->vctx_in);
diff --git a/modules/hw/vdpau/sharpen.c b/modules/hw/vdpau/sharpen.c
index 4aee7e3c406..718eedddb1f 100644
--- a/modules/hw/vdpau/sharpen.c
+++ b/modules/hw/vdpau/sharpen.c
@@ -79,6 +79,10 @@ static picture_t *Sharpen(filter_t *filter, picture_t *pic)
 
 static const char *const options[] = { "sigma", NULL };
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Sharpen,
+};
+
 static int Open(vlc_object_t *obj)
 {
     filter_t *filter = (filter_t *)obj;
@@ -125,7 +129,7 @@ static int Open(vlc_object_t *obj)
     if (unlikely(sys == NULL))
         return VLC_ENOMEM;
 
-    filter->pf_video_filter = Sharpen;
+    filter->ops = &filter_ops;
     filter->p_sys = sys;
 
     config_ChainParse(filter, "sharpen-", options, filter->p_cfg);
diff --git a/modules/spu/audiobargraph_v.c b/modules/spu/audiobargraph_v.c
index de3f9627a6f..9fdc78c7e4d 100644
--- a/modules/spu/audiobargraph_v.c
+++ b/modules/spu/audiobargraph_v.c
@@ -487,6 +487,14 @@ out:
     return p_dst;
 }
 
+static const struct vlc_filter_operations filter_sub_ops = {
+    .source_sub = FilterSub,
+};
+
+static const struct vlc_filter_operations filter_video_ops = {
+    .filter_video = FilterVideo,
+};
+
 /**
  * Common open function
  */
@@ -558,9 +566,9 @@ static int OpenCommon(vlc_object_t *p_this, bool b_sub)
                          BarGraphCallback, p_sys);
 
     if (b_sub)
-        p_filter->pf_sub_source = FilterSub;
+        p_filter->ops = &filter_sub_ops;
     else
-        p_filter->pf_video_filter = FilterVideo;
+        p_filter->ops = &filter_video_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/spu/dynamicoverlay/dynamicoverlay.c b/modules/spu/dynamicoverlay/dynamicoverlay.c
index b038a2d66ac..37cdf7936da 100644
--- a/modules/spu/dynamicoverlay/dynamicoverlay.c
+++ b/modules/spu/dynamicoverlay/dynamicoverlay.c
@@ -81,6 +81,10 @@ static const char *const ppsz_filter_options[] = {
     "input", "output", NULL
 };
 
+static const struct vlc_filter_operations filter_ops = {
+    .source_sub = Filter,
+};
+
 /*****************************************************************************
  * Create: allocates adjust video thread output method
  *****************************************************************************
@@ -110,7 +114,7 @@ static int Create( vlc_object_t *p_this )
     p_sys->b_atomic = false;
     vlc_mutex_init( &p_sys->lock );
 
-    p_filter->pf_sub_source = Filter;
+    p_filter->ops = &filter_ops;
 
     config_ChainParse( p_filter, "overlay-", ppsz_filter_options,
                        p_filter->p_cfg );
diff --git a/modules/spu/logo.c b/modules/spu/logo.c
index c8d1b02445b..9a713cb2407 100644
--- a/modules/spu/logo.c
+++ b/modules/spu/logo.c
@@ -215,6 +215,15 @@ static int OpenVideo( vlc_object_t *p_this )
     return OpenCommon( p_this, false );
 }
 
+static const struct vlc_filter_operations filter_sub_ops = {
+    .source_sub = FilterSub,
+};
+
+static const struct vlc_filter_operations filter_video_ops = {
+    .filter_video = FilterVideo,
+    .video_mouse = Mouse,
+};
+
 /**
  * Common open function
  */
@@ -293,14 +302,9 @@ static int OpenCommon( vlc_object_t *p_this, bool b_sub )
 
     /* Misc init */
     if( b_sub )
-    {
-        p_filter->pf_sub_source = FilterSub;
-    }
+        p_filter->ops = &filter_sub_ops;
     else
-    {
-        p_filter->pf_video_filter = FilterVideo;
-        p_filter->pf_video_mouse = Mouse;
-    }
+        p_filter->ops = &filter_video_ops;
 
     free( psz_filename );
     return VLC_SUCCESS;
diff --git a/modules/spu/marq.c b/modules/spu/marq.c
index c370c5322dc..31657aa0478 100644
--- a/modules/spu/marq.c
+++ b/modules/spu/marq.c
@@ -178,6 +178,10 @@ static const char *const ppsz_filter_options[] = {
     NULL
 };
 
+static const struct vlc_filter_operations filter_ops = {
+    .source_sub = Filter,
+};
+
 /*****************************************************************************
  * CreateFilter: allocates marquee video filter
  *****************************************************************************/
@@ -228,7 +232,7 @@ static int CreateFilter( vlc_object_t *p_this )
     CREATE_VAR( p_style->i_font_size, Integer, "marq-size" );
 
     /* Misc init */
-    p_filter->pf_sub_source = Filter;
+    p_filter->ops = &filter_ops;
     p_sys->last_time = 0;
 
     return VLC_SUCCESS;
diff --git a/modules/spu/mosaic.c b/modules/spu/mosaic.c
index deecc74e73c..c8544950014 100644
--- a/modules/spu/mosaic.c
+++ b/modules/spu/mosaic.c
@@ -271,6 +271,10 @@ static void mosaic_ParseSetOffsets( vlc_object_t *p_this,
 #define mosaic_ParseSetOffsets( a, b, c ) \
             mosaic_ParseSetOffsets( VLC_OBJECT( a ), b, c )
 
+static const struct vlc_filter_operations filter_ops = {
+    .source_sub = Filter,
+};
+
 /*****************************************************************************
  * CreateFiler: allocate mosaic video filter
  *****************************************************************************/
@@ -288,7 +292,7 @@ static int CreateFilter( vlc_object_t *p_this )
     if( p_sys == NULL )
         return VLC_ENOMEM;
 
-    p_filter->pf_sub_source = Filter;
+    p_filter->ops = &filter_ops;
 
     vlc_mutex_init( &p_sys->lock );
     vlc_mutex_lock( &p_sys->lock );
diff --git a/modules/spu/rss.c b/modules/spu/rss.c
index 95f55e34a4b..08c5ea59577 100644
--- a/modules/spu/rss.c
+++ b/modules/spu/rss.c
@@ -240,6 +240,10 @@ static void InitCurrentContext(filter_sys_t *p_sys)
     p_sys->i_cur_char = -1;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .source_sub = Filter,
+};
+
 /*****************************************************************************
  * CreateFilter: allocates RSS video filter
  *****************************************************************************/
@@ -311,7 +315,7 @@ static int CreateFilter( vlc_object_t *p_this )
 
     /* Misc init */
     vlc_mutex_init( &p_sys->lock );
-    p_filter->pf_sub_source = Filter;
+    p_filter->ops = &filter_ops;
     p_sys->last_date = (vlc_tick_t)0;
     p_sys->b_fetched = false;
 
diff --git a/modules/spu/subsdelay.c b/modules/spu/subsdelay.c
index 70b2e9a332f..b17a4325655 100644
--- a/modules/spu/subsdelay.c
+++ b/modules/spu/subsdelay.c
@@ -293,6 +293,10 @@ vlc_module_begin()
 
 static const char * const ppsz_filter_options[] = { "mode", "factor", "overlap", NULL };
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_sub = SubsdelayFilter,
+};
+
 /*****************************************************************************
  * SubsdelayCreate: Create subsdelay filter
  *****************************************************************************/
@@ -336,7 +340,7 @@ static int SubsdelayCreate( vlc_object_t *p_this )
     var_AddCallback( p_filter, CFG_MIN_START_STOP_INTERVAL, SubsdelayCallback, p_sys );
 
     p_filter->p_sys = p_sys;
-    p_filter->pf_sub_filter = SubsdelayFilter;
+    p_filter->ops = &filter_ops;
 
     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options, p_filter->p_cfg );
 
diff --git a/modules/text_renderer/freetype/freetype.c b/modules/text_renderer/freetype/freetype.c
index 5b5fc2d08f2..fe78056212d 100644
--- a/modules/text_renderer/freetype/freetype.c
+++ b/modules/text_renderer/freetype/freetype.c
@@ -1159,6 +1159,11 @@ static int Render( filter_t *p_filter, subpicture_region_t *p_region_out,
     return rv;
 }
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .render = Render,
+};
+
 /*****************************************************************************
  * Create: allocates osd-text video thread output method
  *****************************************************************************
@@ -1236,7 +1241,7 @@ static int Create( vlc_object_t *p_this )
     if( LoadFontsFromAttachments( p_filter ) == VLC_ENOMEM )
         goto error;
 
-    p_filter->pf_render = Render;
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 
diff --git a/modules/text_renderer/nsspeechsynthesizer.m b/modules/text_renderer/nsspeechsynthesizer.m
index 1b71c779ec6..5e6774f09af 100644
--- a/modules/text_renderer/nsspeechsynthesizer.m
+++ b/modules/text_renderer/nsspeechsynthesizer.m
@@ -58,6 +58,10 @@ typedef struct filter_sys_t
     NSString *lastString;
 } filter_sys_t;
 
+static const struct vlc_filter_operations filter_ops = {
+    .render = RenderText,
+};
+
 static int  Create (vlc_object_t *p_this)
 {
     filter_t *p_filter = (filter_t *)p_this;
@@ -70,7 +74,7 @@ static int  Create (vlc_object_t *p_this)
     p_sys->currentLocale = p_sys->lastString = @"";
     p_sys->speechSynthesizer = [[NSSpeechSynthesizer alloc] init];
 
-    p_filter->pf_render = RenderText;
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/text_renderer/sapi.cpp b/modules/text_renderer/sapi.cpp
index 874f5c83143..ad5109ef05c 100644
--- a/modules/text_renderer/sapi.cpp
+++ b/modules/text_renderer/sapi.cpp
@@ -93,6 +93,14 @@ static void LeaveMTA(void)
     CoUninitialize();
 }
 
+static const struct FilterOperationInitializer {
+    struct vlc_filter_operations ops {};
+    FilterOperationInitializer()
+    {
+        ops.render = RenderText;
+    };
+} filter_ops;
+
 static int Create (vlc_object_t *p_this)
 {
     filter_t *p_filter = (filter_t *)p_this;
@@ -157,7 +165,7 @@ static int Create (vlc_object_t *p_this)
 
     LeaveMTA();
 
-    p_filter->pf_render = RenderText;
+    p_filter->ops = &filter_ops.ops;
 
     return VLC_SUCCESS;
 
diff --git a/modules/text_renderer/svg.c b/modules/text_renderer/svg.c
index f97c5fa3bf6..5e710d15eec 100644
--- a/modules/text_renderer/svg.c
+++ b/modules/text_renderer/svg.c
@@ -169,6 +169,10 @@ static char *svg_GetDocument( filter_t *p_filter, int i_width, int i_height, con
     return psz_result;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .render = RenderText,
+};
+
 /*****************************************************************************
  * Create: allocates svg video thread output method
  *****************************************************************************
@@ -184,7 +188,7 @@ static int Create( vlc_object_t *p_this )
         return VLC_ENOMEM;
     p_filter->p_sys = p_sys;
 
-    p_filter->pf_render = RenderText;
+    p_filter->ops = &filter_ops;
     svg_LoadTemplate( p_filter );
 
 #if (GLIB_MAJOR_VERSION < 2 || GLIB_MINOR_VERSION < 36)
diff --git a/modules/text_renderer/tdummy.c b/modules/text_renderer/tdummy.c
index 3451c1068c9..4fd996674c1 100644
--- a/modules/text_renderer/tdummy.c
+++ b/modules/text_renderer/tdummy.c
@@ -47,9 +47,13 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
     return VLC_EGENERIC;
 }
 
+static const struct vlc_filter_operations filter_ops = {
+    .render = RenderText,
+};
+
 static int OpenRenderer( vlc_object_t *p_this )
 {
     filter_t *p_filter = (filter_t *)p_this;
-    p_filter->pf_render = RenderText;
+    p_filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
diff --git a/modules/video_chroma/chain.c b/modules/video_chroma/chain.c
index 7ec2798cc3c..c25711c7d32 100644
--- a/modules/video_chroma/chain.c
+++ b/modules/video_chroma/chain.c
@@ -114,6 +114,7 @@ typedef struct
 {
     filter_chain_t *p_chain;
     filter_t *p_video_filter;
+    struct vlc_filter_operations custom_ops;
 } filter_sys_t;
 
 /* Restart filter callback */
@@ -152,6 +153,10 @@ static const struct filter_video_callbacks filter_video_chain_cbs =
     BufferChainNew, HoldChainDecoderDevice,
 };
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Chain,
+};
+
 /*****************************************************************************
  * Activate: allocate a chroma function
  *****************************************************************************
@@ -213,7 +218,7 @@ static int Activate( filter_t *p_filter, int (*pf_build)(filter_t *) )
     }
     assert(p_filter->vctx_out == filter_chain_GetVideoCtxOut( p_sys->p_chain ));
     /* */
-    p_filter->pf_video_filter = Chain;
+    p_filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
@@ -409,8 +414,12 @@ static int BuildFilterChain( filter_t *p_filter )
                 filter_AddProxyCallbacks( p_filter,
                                           p_sys->p_video_filter,
                                           RestartFilterCallback );
-                if (p_sys->p_video_filter->pf_video_mouse != NULL)
-                    p_filter->pf_video_mouse = ChainMouse;
+                if (p_sys->p_video_filter->ops->video_mouse != NULL)
+                {
+                    p_sys->custom_ops = *p_sys->p_video_filter->ops;
+                    p_sys->p_video_filter->ops = &p_sys->custom_ops;
+                    p_sys->custom_ops.video_mouse = ChainMouse;
+                }
                 es_format_Clean( &fmt_mid );
                 i_ret = VLC_SUCCESS;
                 p_filter->vctx_out = filter_chain_GetVideoCtxOut( p_sys->p_chain );
diff --git a/modules/video_chroma/cvpx.c b/modules/video_chroma/cvpx.c
index f3ece816328..062b1ef0f9a 100644
--- a/modules/video_chroma/cvpx.c
+++ b/modules/video_chroma/cvpx.c
@@ -248,6 +248,13 @@ static void Close(vlc_object_t *obj)
     free(p_sys);
 }
 
+static const struct vlc_filter_operations CVPX_TO_SW_ops = {
+    .filter_video = CVPX_TO_SW_Filter,
+};
+static const struct vlc_filter_operations SW_TO_CVPX_ops = {
+    .filter_video = SW_TO_CVPX_Filter,
+};
+
 static int Open(vlc_object_t *obj)
 {
     filter_t *p_filter = (filter_t *)obj;
@@ -261,9 +268,9 @@ static int Open(vlc_object_t *obj)
     case VLC_CODEC_CVPX_##x: \
         sw_fmt = p_filter->fmt_out.video; \
         if (p_filter->fmt_out.video.i_chroma == VLC_CODEC_##x) \
-            p_filter->pf_video_filter = CVPX_TO_SW_Filter; \
+            p_filter->ops = &CVPX_TO_SW_ops; \
         else if (i420_fcc != 0 && p_filter->fmt_out.video.i_chroma == i420_fcc) { \
-            p_filter->pf_video_filter = CVPX_TO_SW_Filter; \
+            p_filter->ops = &CVPX_TO_SW_ops; \
             sw_fmt.i_chroma = VLC_CODEC_##x; \
         } else return VLC_EGENERIC; \
 
@@ -271,10 +278,10 @@ static int Open(vlc_object_t *obj)
     case VLC_CODEC_CVPX_##x: \
         sw_fmt = p_filter->fmt_in.video; \
         if (p_filter->fmt_in.video.i_chroma == VLC_CODEC_##x) { \
-            p_filter->pf_video_filter = SW_TO_CVPX_Filter; \
+            p_filter->ops = &SW_TO_CVPX_ops; \
         } \
         else if (i420_fcc != 0 && p_filter->fmt_in.video.i_chroma == i420_fcc) { \
-            p_filter->pf_video_filter = SW_TO_CVPX_Filter; \
+            p_filter->ops = &SW_TO_CVPX_ops; \
             sw_fmt.i_chroma = VLC_CODEC_##x; \
         } else return VLC_EGENERIC; \
         b_need_pool = true;
@@ -432,6 +439,10 @@ static vlc_fourcc_t const supported_chromas[] = { VLC_CODEC_CVPX_BGRA,
                                                   VLC_CODEC_CVPX_P010,
                                                   VLC_CODEC_CVPX_UYVY };
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Filter,
+};
+
 static int
 Open_CVPX_to_CVPX(vlc_object_t *obj)
 {
@@ -480,7 +491,7 @@ Open_CVPX_to_CVPX(vlc_object_t *obj)
     }
 
     filter->p_sys = p_sys;
-    filter->pf_video_filter = Filter;
+    filter->ops = &filter_ops;
     filter->vctx_out = vlc_video_context_Hold(filter->vctx_in);
     filter->fmt_out.i_codec = filter->fmt_out.video.i_chroma;
     return VLC_SUCCESS;
@@ -553,6 +564,10 @@ static const vlc_fourcc_t supported_sw_chromas[] = {
     VLC_CODEC_UYVY, VLC_CODEC_P010,
 };
 
+static const struct vlc_filter_operations chain_CVPX_ops = {
+    .filter_video = chain_CVPX_Filter, .flush = chain_CVPX_Flush,
+};
+
 static int
 Open_chain_CVPX(vlc_object_t *obj)
 {
@@ -656,8 +671,7 @@ Open_chain_CVPX(vlc_object_t *obj)
 
     filter->vctx_out = vctx_out;
     filter->p_sys = chain;
-    filter->pf_flush = chain_CVPX_Flush;
-    filter->pf_video_filter = chain_CVPX_Filter;
+    filter->ops = &chain_CVPX_ops;
 
     /* Display the current conversion chain in the logs. */
     msg_Dbg(filter, "CVPX conversion chain:");
diff --git a/modules/video_chroma/grey_yuv.c b/modules/video_chroma/grey_yuv.c
index ea09775c9cb..308bd5c24b0 100644
--- a/modules/video_chroma/grey_yuv.c
+++ b/modules/video_chroma/grey_yuv.c
@@ -56,6 +56,20 @@ vlc_module_end ()
 VIDEO_FILTER_WRAPPER( GREY_I420 )
 VIDEO_FILTER_WRAPPER( GREY_YUY2 )
 
+static const struct vlc_filter_operations *
+GetFilterOperations( filter_t *filter )
+{
+    switch( filter->fmt_out.video.i_chroma )
+    {
+        case VLC_CODEC_I420:
+            return &GREY_I420_ops;
+        case VLC_CODEC_YUYV:
+            return &GREY_YUY2_ops;
+        default:
+            return NULL;
+    }
+}
+
 /*****************************************************************************
  * Activate: allocate a chroma function
  *****************************************************************************
@@ -78,18 +92,9 @@ static int Activate( vlc_object_t *p_this )
 
     if ( p_filter->fmt_in.video.i_chroma != VLC_CODEC_GREY )
         return VLC_EGENERIC;
-
-    switch( p_filter->fmt_out.video.i_chroma )
-    {
-        case VLC_CODEC_I420:
-            p_filter->pf_video_filter = GREY_I420_Filter;
-            break;
-        case VLC_CODEC_YUYV:
-            p_filter->pf_video_filter = GREY_YUY2_Filter;
-            break;
-        default:
-            return VLC_EGENERIC;
-    }
+    p_filter->ops = GetFilterOperations(p_filter);
+    if ( p_filter->ops == NULL )
+        return VLC_EGENERIC;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_chroma/i420_nv12.c b/modules/video_chroma/i420_nv12.c
index 53e1364adfc..fe27022517d 100644
--- a/modules/video_chroma/i420_nv12.c
+++ b/modules/video_chroma/i420_nv12.c
@@ -168,23 +168,23 @@ static int Create( vlc_object_t *p_this )
         case VLC_CODEC_J420:
             if( outfcc != VLC_CODEC_NV12 )
                 return -1;
-            p_filter->pf_video_filter = I420_NV12_Filter;
+            p_filter->ops = &I420_NV12_ops;
             break;
 
         case VLC_CODEC_YV12:
             if( outfcc != VLC_CODEC_NV12 )
                 return -1;
-            p_filter->pf_video_filter = YV12_NV12_Filter;
+            p_filter->ops = &YV12_NV12_ops;
             break;
         case VLC_CODEC_NV12:
             switch( outfcc )
             {
                 case VLC_CODEC_I420:
                 case VLC_CODEC_J420:
-                    p_filter->pf_video_filter = NV12_I420_Filter;
+                    p_filter->ops = &NV12_I420_ops;
                     break;
                 case VLC_CODEC_YV12:
-                    p_filter->pf_video_filter = NV12_YV12_Filter;
+                    p_filter->ops = &NV12_YV12_ops;
                     break;
                 default:
                     return -1;
@@ -195,14 +195,14 @@ static int Create( vlc_object_t *p_this )
             if( outfcc != VLC_CODEC_P010 )
                 return -1;
             pixel_bytes = 2;
-            p_filter->pf_video_filter = I42010B_P010_Filter;
+            p_filter->ops = &I42010B_P010_ops;
             break;
 
         case VLC_CODEC_P010:
             if( outfcc != VLC_CODEC_I420_10L )
                 return -1;
             pixel_bytes = 2;
-            p_filter->pf_video_filter = P010_I42010B_Filter;
+            p_filter->ops = &P010_I42010B_ops;
             break;
 
         default:
diff --git a/modules/video_chroma/i420_rgb.c b/modules/video_chroma/i420_rgb.c
index fbb8f90e42b..20bb2eadbe9 100644
--- a/modules/video_chroma/i420_rgb.c
+++ b/modules/video_chroma/i420_rgb.c
@@ -131,7 +131,7 @@ static int Activate( vlc_object_t *p_this )
                     {
                         /* R5G5B6 pixel format */
                         msg_Dbg(p_this, "RGB pixel format is R5G5B5");
-                        p_filter->pf_video_filter = I420_R5G5B5_Filter;
+                        p_filter->ops = &I420_R5G5B5_ops;
                     }
                     else if( ( p_filter->fmt_out.video.i_rmask == 0xf800
                             && p_filter->fmt_out.video.i_gmask == 0x07e0
@@ -139,7 +139,7 @@ static int Activate( vlc_object_t *p_this )
                     {
                         /* R5G6B5 pixel format */
                         msg_Dbg(p_this, "RGB pixel format is R5G6B5");
-                        p_filter->pf_video_filter = I420_R5G6B5_Filter;
+                        p_filter->ops = &I420_R5G6B5_ops;
                     }
                     else
                         return VLC_EGENERIC;
@@ -152,7 +152,7 @@ static int Activate( vlc_object_t *p_this )
                     {
                         /* A8R8G8B8 pixel format */
                         msg_Dbg(p_this, "RGB pixel format is A8R8G8B8");
-                        p_filter->pf_video_filter = I420_A8R8G8B8_Filter;
+                        p_filter->ops = &I420_A8R8G8B8_ops;
                     }
                     else if( p_filter->fmt_out.video.i_rmask == 0xff000000
                           && p_filter->fmt_out.video.i_gmask == 0x00ff0000
@@ -160,7 +160,7 @@ static int Activate( vlc_object_t *p_this )
                     {
                         /* R8G8B8A8 pixel format */
                         msg_Dbg(p_this, "RGB pixel format is R8G8B8A8");
-                        p_filter->pf_video_filter = I420_R8G8B8A8_Filter;
+                        p_filter->ops = &I420_R8G8B8A8_ops;
                     }
                     else if( p_filter->fmt_out.video.i_rmask == 0x0000ff00
                           && p_filter->fmt_out.video.i_gmask == 0x00ff0000
@@ -168,7 +168,7 @@ static int Activate( vlc_object_t *p_this )
                     {
                         /* B8G8R8A8 pixel format */
                         msg_Dbg(p_this, "RGB pixel format is B8G8R8A8");
-                        p_filter->pf_video_filter = I420_B8G8R8A8_Filter;
+                        p_filter->ops = &I420_B8G8R8A8_ops;
                     }
                     else if( p_filter->fmt_out.video.i_rmask == 0x000000ff
                           && p_filter->fmt_out.video.i_gmask == 0x0000ff00
@@ -176,21 +176,21 @@ static int Activate( vlc_object_t *p_this )
                     {
                         /* A8B8G8R8 pixel format */
                         msg_Dbg(p_this, "RGB pixel format is A8B8G8R8");
-                        p_filter->pf_video_filter = I420_A8B8G8R8_Filter;
+                        p_filter->ops = &I420_A8B8G8R8_ops;
                     }
                     else
                         return VLC_EGENERIC;
                     break;
 #else
                 case VLC_CODEC_RGB8:
-                    p_filter->pf_video_filter = I420_RGB8_Filter;
+                    p_filter->ops = &I420_RGB8_ops;
                     break;
                 case VLC_CODEC_RGB15:
                 case VLC_CODEC_RGB16:
-                    p_filter->pf_video_filter = I420_RGB16_Filter;
+                    p_filter->ops = &I420_RGB16_ops;
                     break;
                 case VLC_CODEC_RGB32:
-                    p_filter->pf_video_filter = I420_RGB32_Filter;
+                    p_filter->ops = &I420_RGB32_ops;
                     break;
 #endif
                 default:
diff --git a/modules/video_chroma/i420_yuy2.c b/modules/video_chroma/i420_yuy2.c
index b18fea3c31f..fb7a787f957 100644
--- a/modules/video_chroma/i420_yuy2.c
+++ b/modules/video_chroma/i420_yuy2.c
@@ -107,6 +107,34 @@ VIDEO_FILTER_WRAPPER( I420_IUYV )
 VIDEO_FILTER_WRAPPER( I420_Y211 )
 #endif
 
+static const struct vlc_filter_operations *
+GetFilterOperations( filter_t *p_filter )
+{
+    switch( p_filter->fmt_out.video.i_chroma )
+    {
+        case VLC_CODEC_YUYV:
+            return &I420_YUY2_ops;
+
+        case VLC_CODEC_YVYU:
+            return &I420_YVYU_ops;
+
+        case VLC_CODEC_UYVY:
+            return &I420_UYVY_ops;
+
+#if !defined (MODULE_NAME_IS_i420_yuy2_altivec)
+        case VLC_FOURCC('I','U','Y','V'):
+            return &I420_IUYV_ops;
+#endif
+
+#if defined (MODULE_NAME_IS_i420_yuy2)
+        case VLC_CODEC_Y211:
+            return &I420_Y211_ops;
+#endif
+        default:
+            return NULL;
+    }
+}
+
 /*****************************************************************************
  * Activate: allocate a chroma function
  *****************************************************************************
@@ -134,33 +162,10 @@ static int Activate( vlc_object_t *p_this )
     if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_I420)
         return VLC_EGENERIC;
 
-    switch( p_filter->fmt_out.video.i_chroma )
-    {
-        case VLC_CODEC_YUYV:
-            p_filter->pf_video_filter = I420_YUY2_Filter;
-            break;
-
-        case VLC_CODEC_YVYU:
-            p_filter->pf_video_filter = I420_YVYU_Filter;
-            break;
-
-        case VLC_CODEC_UYVY:
-            p_filter->pf_video_filter = I420_UYVY_Filter;
-            break;
-#if !defined (MODULE_NAME_IS_i420_yuy2_altivec)
-        case VLC_FOURCC('I','U','Y','V'):
-            p_filter->pf_video_filter = I420_IUYV_Filter;
-            break;
-#endif
-
-#if defined (MODULE_NAME_IS_i420_yuy2)
-        case VLC_CODEC_Y211:
-            p_filter->pf_video_filter = I420_Y211_Filter;
-            break;
-#endif
-        default:
-            return VLC_EGENERIC;
-    }
+    /* Find the adequate filter function depending on the output format. */
+    p_filter->ops = GetFilterOperations( p_filter );
+    if( p_filter->ops == NULL )
+        return VLC_EGENERIC;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_chroma/i422_i420.c b/modules/video_chroma/i422_i420.c
index 5ac6918f853..72315a8d943 100644
--- a/modules/video_chroma/i422_i420.c
+++ b/modules/video_chroma/i422_i420.c
@@ -87,15 +87,15 @@ static int Activate( vlc_object_t *p_this )
             {
                 case VLC_CODEC_I420:
                 case VLC_CODEC_J420:
-                    p_filter->pf_video_filter = I422_I420_Filter;
+                    p_filter->ops = &I422_I420_ops;
                     break;
 
                 case VLC_CODEC_YV12:
-                    p_filter->pf_video_filter = I422_YV12_Filter;
+                    p_filter->ops = &I422_YV12_ops;
                     break;
 
                 case VLC_CODEC_YUV420A:
-                    p_filter->pf_video_filter = I422_YUVA_Filter;
+                    p_filter->ops = &I422_YUVA_ops;
                     break;
 
                 default:
diff --git a/modules/video_chroma/i422_yuy2.c b/modules/video_chroma/i422_yuy2.c
index 416dbfff02c..eb29dc965da 100644
--- a/modules/video_chroma/i422_yuy2.c
+++ b/modules/video_chroma/i422_yuy2.c
@@ -89,6 +89,35 @@ VIDEO_FILTER_WRAPPER( I422_IUYV )
 VIDEO_FILTER_WRAPPER( I422_Y211 )
 #endif
 
+
+static const struct vlc_filter_operations*
+GetFilterOperations(filter_t *filter)
+{
+    switch( filter->fmt_out.video.i_chroma )
+    {
+        case VLC_CODEC_YUYV:
+            return &I422_YUY2_ops;
+
+        case VLC_CODEC_YVYU:
+            return &I422_YVYU_ops;
+
+        case VLC_CODEC_UYVY:
+            return &I422_UYVY_ops;
+
+        case VLC_FOURCC('I','U','Y','V'):
+            return &I422_IUYV_ops;
+
+#if defined (MODULE_NAME_IS_i422_yuy2)
+        case VLC_CODEC_Y211:
+            return &I422_Y211_ops;
+#endif
+
+        default:
+            return NULL;
+    }
+
+}
+
 /*****************************************************************************
  * Activate: allocate a chroma function
  *****************************************************************************
@@ -111,42 +140,16 @@ static int Activate( vlc_object_t *p_this )
         return VLC_EGENERIC;
     }
 
-    switch( p_filter->fmt_in.video.i_chroma )
-    {
-        case VLC_CODEC_I422:
-            switch( p_filter->fmt_out.video.i_chroma )
-            {
-                case VLC_CODEC_YUYV:
-                    p_filter->pf_video_filter = I422_YUY2_Filter;
-                    break;
-
-                case VLC_CODEC_YVYU:
-                    p_filter->pf_video_filter = I422_YVYU_Filter;
-                    break;
-
-                case VLC_CODEC_UYVY:
-                    p_filter->pf_video_filter = I422_UYVY_Filter;
-                    break;
+    /* This is a i422 -> * converter. */
+    if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_I422 )
+        return VLC_EGENERIC;
 
-                case VLC_FOURCC('I','U','Y','V'):
-                    p_filter->pf_video_filter = I422_IUYV_Filter;
-                    break;
 
-#if defined (MODULE_NAME_IS_i422_yuy2)
-                case VLC_CODEC_Y211:
-                    p_filter->pf_video_filter = I422_Y211_Filter;
-                    break;
-#endif
-
-                default:
-                    return -1;
-            }
-            break;
+    p_filter->ops = GetFilterOperations( p_filter );
+    if( p_filter->ops == NULL)
+        return VLC_EGENERIC;
 
-        default:
-            return -1;
-    }
-    return 0;
+    return VLC_SUCCESS;
 }
 
 /* Following functions are local */
diff --git a/modules/video_chroma/omxdl.c b/modules/video_chroma/omxdl.c
index 292fe950eab..e643f0735d5 100644
--- a/modules/video_chroma/omxdl.c
+++ b/modules/video_chroma/omxdl.c
@@ -265,7 +265,7 @@ static int Open (vlc_object_t *obj)
                 case VLC_CODEC_RGB16:
                     if (FixRV16 (&filter->fmt_out.video))
                         return VLC_EGENERIC;
-                    filter->pf_video_filter = I420_RV16_Filter;
+                    filter->ops = &I420_RV16_ops;
                     return VLC_SUCCESS;
             }
             break;
@@ -276,7 +276,7 @@ static int Open (vlc_object_t *obj)
                 case VLC_CODEC_RGB16:
                     if (FixRV16 (&filter->fmt_out.video))
                         return VLC_EGENERIC;
-                    filter->pf_video_filter = YV12_RV16_Filter;
+                    filter->ops = &YV12_RV16_ops;
                     return VLC_SUCCESS;
             }
             break;
@@ -285,10 +285,10 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I420:
-                    filter->pf_video_filter = I422_I420_Filter;
+                    filter->ops = &I422_I420_ops;
                     return VLC_SUCCESS;
                 case VLC_CODEC_YV12:
-                    filter->pf_video_filter = I422_YV12_Filter;
+                    filter->ops = &I422_YV12_ops;
                     return VLC_SUCCESS;
             }
             break;
@@ -299,7 +299,7 @@ static int Open (vlc_object_t *obj)
                 case VLC_CODEC_RGB16:
                     if (FixRV16 (&filter->fmt_out.video))
                         return VLC_EGENERIC;
-                    filter->pf_video_filter = I444_RV16_Filter;
+                    filter->ops = &I444_RV16_ops;
                     return VLC_SUCCESS;
             }
             return VLC_EGENERIC;
@@ -310,12 +310,12 @@ static int Open (vlc_object_t *obj)
                 case VLC_CODEC_RGB24:
                     if (FixRV24 (&filter->fmt_out.video))
                         return VLC_EGENERIC;
-                    filter->pf_video_filter = YUYV_RV24_Filter;
+                    filter->ops = &YUYV_RV24_ops;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB16:
                     if (FixRV16 (&filter->fmt_out.video))
                         return VLC_EGENERIC;
-                    filter->pf_video_filter = YUYV_RV16_Filter;
+                    filter->ops = &YUYV_RV16_ops;
                     return VLC_SUCCESS;
             }
             return VLC_EGENERIC;
@@ -324,10 +324,10 @@ static int Open (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I420:
-                    filter->pf_video_filter = UYVY_I420_Filter;
+                    filter->ops = &UYVY_I420_ops;
                     return VLC_SUCCESS;
                 case VLC_CODEC_YV12:
-                    filter->pf_video_filter = UYVY_YV12_Filter;
+                    filter->ops = &UYVY_YV12_ops;
                     return VLC_SUCCESS;
             }
             return VLC_EGENERIC;
@@ -484,33 +484,33 @@ static int OpenScaler (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I420:
-                    filter->pf_video_filter = I420_I420_Scale_Filter;
+                    filter->ops = &I420_I420_Scale_ops;
                     return VLC_SUCCESS;
                 case VLC_CODEC_YV12:
-                    filter->pf_video_filter = I420_YV12_Scale_Filter;
+                    filter->ops = &I420_YV12_Scale_ops;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB16:
                     if (FixRV16 (&filter->fmt_out.video))
                         return VLC_EGENERIC;
-                    filter->pf_video_filter = I420_RGB_Scale_Filter;
+                    filter->ops = &I420_RGB_Scale_ops;
                     *conv = OMX_IP_BGR565;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB15:
                     if (FixRV15 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = I420_RGB_Scale_Filter;
+                    filter->ops = &I420_RGB_Scale_ops;
                     *conv = OMX_IP_BGR555;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB12:
                     if (FixRV12 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = I420_RGB_Scale_Filter;
+                    filter->ops = &I420_RGB_Scale_ops;
                     *conv = OMX_IP_BGR444;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB24:
                     if (FixRV24 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = I420_RGB_Scale_Filter;
+                    filter->ops = &I420_RGB_Scale_ops;
                     *conv = OMX_IP_BGR888;
                     return VLC_SUCCESS;
             }
@@ -520,33 +520,33 @@ static int OpenScaler (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I420:
-                    filter->pf_video_filter = YV12_I420_Scale_Filter;
+                    filter->ops = &YV12_I420_Scale_ops;
                     return VLC_SUCCESS;
                 case VLC_CODEC_YV12:
-                    filter->pf_video_filter = YV12_YV12_Scale_Filter;
+                    filter->ops = &YV12_YV12_Scale_ops;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB16:
                     if (FixRV16 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = YV12_RGB_Scale_Filter;
+                    filter->ops = &YV12_RGB_Scale_ops;
                     *conv = OMX_IP_BGR565;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB15:
                     if (FixRV15 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = YV12_RGB_Scale_Filter;
+                    filter->ops = &YV12_RGB_Scale_ops;
                     *conv = OMX_IP_BGR555;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB12:
                     if (FixRV12 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = YV12_RGB_Scale_Filter;
+                    filter->ops = &YV12_RGB_Scale_ops;
                     *conv = OMX_IP_BGR444;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB24:
                     if (FixRV24 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = YV12_RGB_Scale_Filter;
+                    filter->ops = &YV12_RGB_Scale_ops;
                     *conv = OMX_IP_BGR888;
                     return VLC_SUCCESS;
             }
@@ -556,30 +556,30 @@ static int OpenScaler (vlc_object_t *obj)
             switch (filter->fmt_out.video.i_chroma)
             {
                 case VLC_CODEC_I422:
-                    filter->pf_video_filter = I422_I422_Scale_Filter;
+                    filter->ops = &I422_I422_Scale_ops;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB16:
                     if (FixRV16 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = I422_RGB_Scale_Filter;
+                    filter->ops = &I422_RGB_Scale_ops;
                     *conv = OMX_IP_BGR565;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB15:
                     if (FixRV15 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = I422_RGB_Scale_Filter;
+                    filter->ops = &I422_RGB_Scale_ops;
                     *conv = OMX_IP_BGR555;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB12:
                     if (FixRV12 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = I422_RGB_Scale_Filter;
+                    filter->ops = &I422_RGB_Scale_ops;
                     *conv = OMX_IP_BGR444;
                     return VLC_SUCCESS;
                 case VLC_CODEC_RGB24:
                     if (FixRV24 (&filter->fmt_out.video))
                         break;
-                    filter->pf_video_filter = I422_RGB_Scale_Filter;
+                    filter->ops = &I422_RGB_Scale_ops;
                     *conv = OMX_IP_BGR888;
                     return VLC_SUCCESS;
             }
diff --git a/modules/video_chroma/rv32.c b/modules/video_chroma/rv32.c
index 9546872722b..835b658531d 100644
--- a/modules/video_chroma/rv32.c
+++ b/modules/video_chroma/rv32.c
@@ -47,6 +47,10 @@ vlc_module_begin ()
     set_callback( OpenFilter )
 vlc_module_end ()
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Filter,
+};
+
 /*****************************************************************************
  * OpenFilter: probe the filter and return score
  *****************************************************************************/
@@ -67,7 +71,7 @@ static int OpenFilter( vlc_object_t *p_this )
      || p_filter->fmt_in.video.orientation != p_filter->fmt_out.video.orientation)
         return -1;
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_chroma/swscale.c b/modules/video_chroma/swscale.c
index 3c7bdf78ea3..8d5106ffd61 100644
--- a/modules/video_chroma/swscale.c
+++ b/modules/video_chroma/swscale.c
@@ -139,6 +139,10 @@ static int GetSwsCpuMask(void);
 /* XXX is it always 3 even for BIG_ENDIAN (blend.c seems to think so) ? */
 #define OFFSET_A (3)
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Filter,
+};
+
 /*****************************************************************************
  * OpenScaler: probe the filter and return score
  *****************************************************************************/
@@ -192,7 +196,7 @@ static int OpenScaler( vlc_object_t *p_this )
     }
 
     /* */
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     msg_Dbg( p_filter, "%ix%i (%ix%i) chroma: %4.4s -> %ix%i (%ix%i) chroma: %4.4s with scaling using %s",
              p_filter->fmt_in.video.i_visible_width, p_filter->fmt_in.video.i_visible_height,
diff --git a/modules/video_chroma/yuvp.c b/modules/video_chroma/yuvp.c
index b6149ce1654..8fe086637bd 100644
--- a/modules/video_chroma/yuvp.c
+++ b/modules/video_chroma/yuvp.c
@@ -78,7 +78,7 @@ static int Open( vlc_object_t *p_this )
         return VLC_EGENERIC;
     }
 
-    p_filter->pf_video_filter = Convert_Filter;
+    p_filter->ops = &Convert_ops;
 
     msg_Dbg( p_filter, "YUVP to %4.4s converter",
              (const char*)&p_filter->fmt_out.video.i_chroma );
diff --git a/modules/video_chroma/yuy2_i420.c b/modules/video_chroma/yuy2_i420.c
index cfcb2c3717b..fa0c3cfd570 100644
--- a/modules/video_chroma/yuy2_i420.c
+++ b/modules/video_chroma/yuy2_i420.c
@@ -84,15 +84,15 @@ static int Activate( vlc_object_t *p_this )
             switch( p_filter->fmt_in.video.i_chroma )
             {
                 case VLC_CODEC_YUYV:
-                    p_filter->pf_video_filter = YUY2_I420_Filter;
+                    p_filter->ops = &YUY2_I420_ops;
                     break;
 
                 case VLC_CODEC_YVYU:
-                    p_filter->pf_video_filter = YVYU_I420_Filter;
+                    p_filter->ops = &YVYU_I420_ops;
                     break;
 
                 case VLC_CODEC_UYVY:
-                    p_filter->pf_video_filter = UYVY_I420_Filter;
+                    p_filter->ops = &UYVY_I420_ops;
                     break;
 
                 default:
diff --git a/modules/video_chroma/yuy2_i422.c b/modules/video_chroma/yuy2_i422.c
index f8aa0b9b040..b35c62f84dc 100644
--- a/modules/video_chroma/yuy2_i422.c
+++ b/modules/video_chroma/yuy2_i422.c
@@ -84,15 +84,15 @@ static int Activate( vlc_object_t *p_this )
             switch( p_filter->fmt_in.video.i_chroma )
             {
                 case VLC_CODEC_YUYV:
-                    p_filter->pf_video_filter = YUY2_I422_Filter;
+                    p_filter->ops = &YUY2_I422_ops;
                     break;
 
                 case VLC_CODEC_YVYU:
-                    p_filter->pf_video_filter = YVYU_I422_Filter;
+                    p_filter->ops = &YVYU_I422_ops;
                     break;
 
                 case VLC_CODEC_UYVY:
-                    p_filter->pf_video_filter = UYVY_I422_Filter;
+                    p_filter->ops = &UYVY_I422_ops;
                     break;
 
                 default:
diff --git a/modules/video_filter/adjust.c b/modules/video_filter/adjust.c
index 063af77f22a..85cf2bbb165 100644
--- a/modules/video_filter/adjust.c
+++ b/modules/video_filter/adjust.c
@@ -141,6 +141,16 @@ static int BoolCallback( vlc_object_t *obj, char const *varname,
     return VLC_SUCCESS;
 }
 
+static const struct vlc_filter_operations planar_filter_ops =
+{
+    .filter_video = FilterPlanar,
+};
+
+static const struct vlc_filter_operations packed_filter_ops =
+{
+    .filter_video = FilterPacked,
+};
+
 /*****************************************************************************
  * Create: allocates adjust video filter
  *****************************************************************************/
@@ -166,7 +176,7 @@ static int Create( vlc_object_t *p_this )
     {
         CASE_PLANAR_YUV
             /* Planar YUV */
-            p_filter->pf_video_filter = FilterPlanar;
+            p_filter->ops = &planar_filter_ops;
             p_sys->pf_process_sat_hue_clip = planar_sat_hue_clip_C;
             p_sys->pf_process_sat_hue = planar_sat_hue_C;
             break;
@@ -174,14 +184,14 @@ static int Create( vlc_object_t *p_this )
         CASE_PLANAR_YUV10
         CASE_PLANAR_YUV9
             /* Planar YUV 9-bit or 10-bit */
-            p_filter->pf_video_filter = FilterPlanar;
+            p_filter->ops = &planar_filter_ops;
             p_sys->pf_process_sat_hue_clip = planar_sat_hue_clip_C_16;
             p_sys->pf_process_sat_hue = planar_sat_hue_C_16;
             break;
 
         CASE_PACKED_YUV_422
             /* Packed YUV 4:2:2 */
-            p_filter->pf_video_filter = FilterPacked;
+            p_filter->ops = &packed_filter_ops;
             p_sys->pf_process_sat_hue_clip = packed_sat_hue_clip_C;
             p_sys->pf_process_sat_hue = packed_sat_hue_C;
             break;
diff --git a/modules/video_filter/alphamask.c b/modules/video_filter/alphamask.c
index 001f37185c7..9b894f9f760 100644
--- a/modules/video_filter/alphamask.c
+++ b/modules/video_filter/alphamask.c
@@ -82,6 +82,11 @@ typedef struct
     vlc_mutex_t mask_lock;
 } filter_sys_t;
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 static int Create( vlc_object_t *p_this )
 {
     filter_t *p_filter = (filter_t *)p_this;
@@ -122,7 +127,7 @@ static int Create( vlc_object_t *p_this )
     vlc_mutex_init( &p_sys->mask_lock );
     var_AddCallback( p_filter, CFG_PREFIX "mask", MaskCallback,
                      p_filter );
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/anaglyph.c b/modules/video_filter/anaglyph.c
index b17bf7a5256..0f1b16689f4 100644
--- a/modules/video_filter/anaglyph.c
+++ b/modules/video_filter/anaglyph.c
@@ -86,6 +86,11 @@ typedef struct
     int left, right;
 } filter_sys_t;
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = &Filter,
+};
+
 
 static int Create(vlc_object_t *p_this)
 {
@@ -156,7 +161,7 @@ static int Create(vlc_object_t *p_this)
             break;
     }
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
@@ -301,4 +306,3 @@ static void combine_side_by_side_yuv420(picture_t *p_inpic, picture_t *p_outpic,
         vout += p_outpic->p[V_PLANE].i_pitch - uv_visible_pitch;
     }
 }
-
diff --git a/modules/video_filter/antiflicker.c b/modules/video_filter/antiflicker.c
index 568a84ae615..4f360ee0027 100644
--- a/modules/video_filter/antiflicker.c
+++ b/modules/video_filter/antiflicker.c
@@ -94,6 +94,11 @@ typedef struct
     uint8_t *p_old_data;
 } filter_sys_t;
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 /*****************************************************************************
  * Create: allocates Distort video thread output method
  *****************************************************************************
@@ -121,7 +126,7 @@ static int Create( vlc_object_t *p_this )
         return VLC_ENOMEM;
     p_filter->p_sys = p_sys;
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     /* Initialize the arguments */
     atomic_init( &p_sys->i_window_size,
diff --git a/modules/video_filter/ball.c b/modules/video_filter/ball.c
index 563ff8bedc0..15086833117 100644
--- a/modules/video_filter/ball.c
+++ b/modules/video_filter/ball.c
@@ -215,6 +215,10 @@ struct filter_sys_t
     } colorList[4];
 };
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
 
 /*****************************************************************************
 * Create: allocates Distort video thread output method
@@ -261,7 +265,7 @@ static int Create( vlc_object_t *p_this )
     if( p_sys->p_image == NULL )
         return VLC_EGENERIC;
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     config_ChainParse( p_filter, FILTER_PREFIX, ppsz_filter_options,
                        p_filter->p_cfg );
diff --git a/modules/video_filter/blend.cpp b/modules/video_filter/blend.cpp
index b33aa084f38..faf49cf482f 100644
--- a/modules/video_filter/blend.cpp
+++ b/modules/video_filter/blend.cpp
@@ -649,7 +649,7 @@ struct filter_sys_t {
 /**
  * It blends 2 picture together.
  */
-static void Blend(filter_t *filter,
+static void DoBlend(filter_t *filter,
                   picture_t *dst, const picture_t *src,
                   int x_offset, int y_offset, int alpha)
 {
@@ -680,6 +680,14 @@ static void Blend(filter_t *filter,
                width, height, alpha);
 }
 
+static const struct FilterOperationInitializer {
+    struct vlc_filter_operations ops {};
+    FilterOperationInitializer()
+    {
+        ops.blend_video = DoBlend;
+    };
+} filter_ops;
+
 static int Open(vlc_object_t *object)
 {
     filter_t *filter = (filter_t *)object;
@@ -699,7 +707,7 @@ static int Open(vlc_object_t *object)
         return VLC_EGENERIC;
     }
 
-    filter->pf_video_blend = Blend;
+    filter->ops = &filter_ops.ops;
     filter->p_sys          = sys;
     return VLC_SUCCESS;
 }
@@ -710,4 +718,3 @@ static void Close(vlc_object_t *object)
     filter_sys_t *p_sys = reinterpret_cast<filter_sys_t *>( filter->p_sys );
     delete p_sys;
 }
-
diff --git a/modules/video_filter/blendbench.c b/modules/video_filter/blendbench.c
index f2886385311..0a6c17ad22b 100644
--- a/modules/video_filter/blendbench.c
+++ b/modules/video_filter/blendbench.c
@@ -144,6 +144,11 @@ static int blendbench_LoadImage( vlc_object_t *p_this, picture_t **pp_pic,
     return VLC_SUCCESS;
 }
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 /*****************************************************************************
  * Create: allocates video thread output method
  *****************************************************************************/
@@ -162,7 +167,7 @@ static int Create( vlc_object_t *p_this )
     p_sys = p_filter->p_sys;
     p_sys->b_done = false;
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     /* needed to get options passed in transcode using the
      * adjust{name=value} syntax */
@@ -252,9 +257,8 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
     vlc_tick_t time = vlc_tick_now();
     for( int i_iter = 0; i_iter < p_sys->i_loops; ++i_iter )
     {
-        p_blend->pf_video_blend( p_blend,
-                                 p_sys->p_base_image, p_sys->p_blend_image,
-                                 0, 0, p_sys->i_alpha );
+        filter_Blend( p_blend, p_sys->p_base_image, p_sys->p_blend_image,
+                      0, 0, p_sys->i_alpha );
     }
     time = vlc_tick_now() - time;
 
diff --git a/modules/video_filter/bluescreen.c b/modules/video_filter/bluescreen.c
index 3fe83a1a5df..d624ddf2df9 100644
--- a/modules/video_filter/bluescreen.c
+++ b/modules/video_filter/bluescreen.c
@@ -106,6 +106,11 @@ typedef struct
     uint8_t *p_at;
 } filter_sys_t;
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 static int Create( vlc_object_t *p_this )
 {
     filter_t *p_filter = (filter_t *)p_this;
@@ -143,7 +148,7 @@ static int Create( vlc_object_t *p_this )
     p_sys->p_at = NULL;
 #undef GET_VAR
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/canvas.c b/modules/video_filter/canvas.c
index 80010eecc17..580948f719c 100644
--- a/modules/video_filter/canvas.c
+++ b/modules/video_filter/canvas.c
@@ -144,6 +144,11 @@ static const struct filter_video_callbacks canvas_cbs =
     video_chain_new, NULL,
 };
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 /*****************************************************************************
  *
  *****************************************************************************/
@@ -373,7 +378,7 @@ static int Activate( vlc_object_t *p_this )
                   i_canvas_width, i_canvas_height );
     }
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/ci_filters.m b/modules/video_filter/ci_filters.m
index 154fc44012c..4bc46be3460 100644
--- a/modules/video_filter/ci_filters.m
+++ b/modules/video_filter/ci_filters.m
@@ -376,7 +376,8 @@ Filter(filter_t *filter, picture_t *src)
 
     if (ctx->src_converter)
     {
-        src = ctx->dst_converter->pf_video_filter(ctx->src_converter, src);
+        // TODO
+        src = ctx->dst_converter->ops->filter_video(ctx->src_converter, src);
         if (!src)
             return NULL;
     }
@@ -416,7 +417,7 @@ Filter(filter_t *filter, picture_t *src)
 
     if (ctx->dst_converter)
     {
-        dst = ctx->dst_converter->pf_video_filter(ctx->dst_converter, dst);
+        dst = ctx->dst_converter->ops->filter_video(ctx->dst_converter, dst);
         if (!dst)
             return NULL;
     }
@@ -568,6 +569,12 @@ CVPX_to_CVPX_converter_Create(filter_t *filter, bool to_rgba)
     return converter;
 }
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+    .video_mouse = Mouse,
+};
+
 static int
 Open(vlc_object_t *obj, char const *psz_filter)
 {
@@ -677,8 +684,7 @@ Open(vlc_object_t *obj, char const *psz_filter)
     p_sys->psz_filter = psz_filter;
     p_sys->ctx = ctx;
 
-    filter->pf_video_filter = Filter;
-    filter->pf_video_mouse = Mouse;
+    filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 
diff --git a/modules/video_filter/colorthres.c b/modules/video_filter/colorthres.c
index 3011b94f99d..5f7e483b117 100644
--- a/modules/video_filter/colorthres.c
+++ b/modules/video_filter/colorthres.c
@@ -102,6 +102,16 @@ typedef struct
     atomic_int i_color;
 } filter_sys_t;
 
+static const struct vlc_filter_operations planar_filter_ops =
+{
+    .filter_video = Filter,
+};
+
+static const struct vlc_filter_operations packed_filter_ops =
+{
+    .filter_video = FilterPacked,
+};
+
 /*****************************************************************************
  * Create: allocates adjust video thread output method
  *****************************************************************************
@@ -115,11 +125,11 @@ static int Create( vlc_object_t *p_this )
     switch( p_filter->fmt_in.video.i_chroma )
     {
         CASE_PLANAR_YUV
-            p_filter->pf_video_filter = Filter;
+            p_filter->ops = &planar_filter_ops;
             break;
 
         CASE_PACKED_YUV_422
-            p_filter->pf_video_filter = FilterPacked;
+            p_filter->ops = &packed_filter_ops;
             break;
 
         default:
diff --git a/modules/video_filter/croppadd.c b/modules/video_filter/croppadd.c
index 1d2f27fbe45..4471e859a7b 100644
--- a/modules/video_filter/croppadd.c
+++ b/modules/video_filter/croppadd.c
@@ -122,6 +122,11 @@ typedef struct
     int i_paddright;
 } filter_sys_t;
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 /*****************************************************************************
  * OpenFilter: probe the filter and return score
  *****************************************************************************/
@@ -185,7 +190,7 @@ static int OpenFilter( vlc_object_t *p_this )
         - p_sys->i_cropleft - p_sys->i_cropright
         + p_sys->i_paddleft + p_sys->i_paddright;
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     msg_Dbg( p_filter, "Crop: Top: %d, Bottom: %d, Left: %d, Right: %d",
              p_sys->i_croptop, p_sys->i_cropbottom, p_sys->i_cropleft,
diff --git a/modules/video_filter/deinterlace/deinterlace.c b/modules/video_filter/deinterlace/deinterlace.c
index 1676a07f8ca..714066c21c3 100644
--- a/modules/video_filter/deinterlace/deinterlace.c
+++ b/modules/video_filter/deinterlace/deinterlace.c
@@ -53,7 +53,7 @@
 /**
  * Top-level filtering method.
  *
- * Open() sets this up as the processing method (pf_video_filter)
+ * Open() sets this up as the processing method (filter_video)
  * in the filter structure.
  *
  * Note that there is no guarantee that the returned picture directly
@@ -480,6 +480,12 @@ int Mouse( filter_t *p_filter,
 }
 
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_video = Deinterlace,
+    .flush = Flush,
+    .video_mouse = Mouse,
+};
+
 /*****************************************************************************
  * Open
  *****************************************************************************/
@@ -643,9 +649,7 @@ notsupp:
     }
     p_filter->fmt_out.video = fmt;
     p_filter->fmt_out.i_codec = fmt.i_chroma;
-    p_filter->pf_video_filter = Deinterlace;
-    p_filter->pf_flush = Flush;
-    p_filter->pf_video_mouse  = Mouse;
+    p_filter->ops = &filter_ops;
 
     msg_Dbg( p_filter, "deinterlacing" );
 
diff --git a/modules/video_filter/edgedetection.c b/modules/video_filter/edgedetection.c
index 935189122f1..d425473704f 100644
--- a/modules/video_filter/edgedetection.c
+++ b/modules/video_filter/edgedetection.c
@@ -82,6 +82,11 @@ static const struct filter_video_callbacks filter_video_edge_cbs =
     new_frame, NULL,
 };
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 /*****************************************************************************
  * Opens the filter.
  * Allocates and initializes data needed by the filter. The image needs to
@@ -123,7 +128,7 @@ static int Open( vlc_object_t *p_this )
         return VLC_EGENERIC;
     }
     /* Set callback function */
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
     p_filter->p_sys = sys;
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/erase.c b/modules/video_filter/erase.c
index 7e6683b14e4..0ec8df77d50 100644
--- a/modules/video_filter/erase.c
+++ b/modules/video_filter/erase.c
@@ -122,6 +122,11 @@ static void LoadMask( filter_t *p_filter, const char *psz_filename )
     image_HandlerDelete( p_image );
 }
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 /*****************************************************************************
  * Create
  *****************************************************************************/
@@ -153,7 +158,7 @@ static int Create( vlc_object_t *p_this )
         return VLC_ENOMEM;
     p_sys = p_filter->p_sys;
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
                        p_filter->p_cfg );
diff --git a/modules/video_filter/extract.c b/modules/video_filter/extract.c
index 0e849642a95..ca5708792c3 100644
--- a/modules/video_filter/extract.c
+++ b/modules/video_filter/extract.c
@@ -89,6 +89,11 @@ typedef struct
     uint32_t i_color;
 } filter_sys_t;
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 /*****************************************************************************
  * Create
  *****************************************************************************/
@@ -142,7 +147,7 @@ static int Create( vlc_object_t *p_this )
     var_AddCallback( p_filter, FILTER_PREFIX "component",
                      ExtractCallback, p_sys );
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/fps.c b/modules/video_filter/fps.c
index 4e6a59dfd66..a4ea6fd671a 100644
--- a/modules/video_filter/fps.c
+++ b/modules/video_filter/fps.c
@@ -131,6 +131,11 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_picture)
     return last_pic;
 }
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 static int Open( vlc_object_t *p_this)
 {
     filter_t *p_filter = (filter_t*)p_this;
@@ -180,7 +185,7 @@ static int Open( vlc_object_t *p_this)
 
     p_sys->p_previous_pic = NULL;
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     /* We don't change neither the format nor the picture */
     if ( p_filter->vctx_in )
diff --git a/modules/video_filter/freeze.c b/modules/video_filter/freeze.c
index be5fde8e9d9..719a762619f 100644
--- a/modules/video_filter/freeze.c
+++ b/modules/video_filter/freeze.c
@@ -88,6 +88,12 @@ vlc_module_end()
  * Local prototypes
  *****************************************************************************/
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+    .video_mouse = freeze_mouse,
+};
+
 /**
  * Open the filter
  */
@@ -121,8 +127,7 @@ static int Open( vlc_object_t *p_this )
 
     /* init data */
 
-    p_filter->pf_video_filter = Filter;
-    p_filter->pf_video_mouse  = freeze_mouse;
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/gaussianblur.c b/modules/video_filter/gaussianblur.c
index 3a97dbcd34e..ea0aa41167e 100644
--- a/modules/video_filter/gaussianblur.c
+++ b/modules/video_filter/gaussianblur.c
@@ -123,6 +123,11 @@ static void gaussianblur_InitDistribution( filter_sys_t *p_sys )
     p_sys->pt_distribution = pt_distribution;
 }
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 static int Create( vlc_object_t *p_this )
 {
     filter_t *p_filter = (filter_t *)p_this;
@@ -155,7 +160,7 @@ static int Create( vlc_object_t *p_this )
     config_ChainParse( p_filter, FILTER_PREFIX, ppsz_filter_options,
                        p_filter->p_cfg );
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     p_sys->f_sigma =
         var_CreateGetFloat( p_filter, FILTER_PREFIX "sigma" );
diff --git a/modules/video_filter/gradfun.c b/modules/video_filter/gradfun.c
index 093d207523e..01f0042486a 100644
--- a/modules/video_filter/gradfun.c
+++ b/modules/video_filter/gradfun.c
@@ -110,6 +110,11 @@ typedef struct
     struct vf_priv_s cfg;
 } filter_sys_t;
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 static int Open(vlc_object_t *object)
 {
     filter_t *filter = (filter_t *)object;
@@ -156,8 +161,8 @@ static int Open(vlc_object_t *object)
 #endif
         cfg->filter_line = filter_line_c;
 
-    filter->p_sys           = sys;
-    filter->pf_video_filter = Filter;
+    filter->p_sys = sys;
+    filter->ops   = &filter_ops;
     return VLC_SUCCESS;
 }
 
@@ -236,4 +241,3 @@ static int Callback(vlc_object_t *object, char const *cmd,
 
     return VLC_SUCCESS;
 }
-
diff --git a/modules/video_filter/gradient.c b/modules/video_filter/gradient.c
index 22675305f5e..b7c599d2266 100644
--- a/modules/video_filter/gradient.c
+++ b/modules/video_filter/gradient.c
@@ -124,6 +124,11 @@ typedef struct
     int *p_pre_hough;
 } filter_sys_t;
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 /*****************************************************************************
  * Create: allocates Distort video thread output method
  *****************************************************************************
@@ -151,7 +156,7 @@ static int Create( vlc_object_t *p_this )
         return VLC_ENOMEM;
     p_filter->p_sys = p_sys;
 
-    p_filter->pf_video_filter = Filter;
+    p_filter->ops = &filter_ops;
 
     p_sys->p_pre_hough = NULL;
 
diff --git a/modules/video_filter/grain.c b/modules/video_filter/grain.c
index 174de0a822a..b63f836c6eb 100644
--- a/modules/video_filter/grain.c
+++ b/modules/video_filter/grain.c
@@ -378,6 +378,11 @@ static int Callback(vlc_object_t *object, char const *cmd,
     return VLC_SUCCESS;
 }
 
+static const struct vlc_filter_operations filter_ops =
+{
+    .filter_video = Filter,
+};
+
 static int Open(vlc_object_t *object)
 {
     filter_t *filter = (filter_t *)object;
@@ -419,8 +424,8 @@ static int Open(vlc_object_t *object)
     sys->cfg.variance = var_CreateGetFloatCommand(filter, CFG_PREFIX "variance");
     var_AddCallback(filter, CFG_PREFIX "variance", Callback, NULL);
 
-    filter->p_sys           = sys;
-    filter->pf_video_filter = Filter;
+    filter->p_sys = sys;
+    filter->ops   = &filter_ops;
     return VLC_SUCCESS;
 }
 
@@ -432,4 +437,3 @@ static void Close(vlc_object_t *object)
     var_DelCallback(filter, CFG_PREFIX "variance", Callback, NULL);
     free(sys);
 }
-
diff --git a/modules/video_filter/hqdn3d.c b/modules/video_filter/hqdn3d.c
index beb57974d8e..0dcbca718e9 100644
--- a/modules/video_filter/hqdn3d.c
+++ b/modules/video_filter/hqdn3d.c
@@ -154,8 +154,13 @@ static int Open(vlc_object_t *this)
     sys->luma_temp = var_CreateGetFloatCommand(filter, FILTER_PREFIX "luma-temp");
     sys->chroma_temp = var_CreateGetFloatCommand(filter, FILTER_PREFIX "chroma-temp");
 
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+
     filter->p_sys = sys;
-    filter->pf_video_filter = Filter;
+    filter->ops = &filter_ops;
 
     var_AddCallback( filter, FILTER_PREFIX "luma-spat", DenoiseCallback, sys );
     var_AddCallback( filter, FILTER_PREFIX "chroma-spat", DenoiseCallback, sys );
diff --git a/modules/video_filter/invert.c b/modules/video_filter/invert.c
index 67f96977c78..632635ce588 100644
--- a/modules/video_filter/invert.c
+++ b/modules/video_filter/invert.c
@@ -76,7 +76,11 @@ static int Create( vlc_object_t *p_this )
      || p_chroma->pixel_size * 8 != p_chroma->pixel_bits )
         return VLC_EGENERIC;
 
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
diff --git a/modules/video_filter/magnify.c b/modules/video_filter/magnify.c
index e5582bbc0ff..b5d2fe042c0 100644
--- a/modules/video_filter/magnify.c
+++ b/modules/video_filter/magnify.c
@@ -129,8 +129,12 @@ static int Create( vlc_object_t *p_this )
     p_sys->i_hide_timeout = VLC_TICK_FROM_MS( var_InheritInteger( p_filter, "mouse-hide-timeout" ) );
 
     /* */
-    p_filter->pf_video_filter = Filter;
-    p_filter->pf_video_mouse = Mouse;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+        .video_mouse = Mouse,
+    };
+    p_filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
@@ -419,4 +423,3 @@ static int Mouse( filter_t *p_filter, vlc_mouse_t *p_new, const vlc_mouse_t *p_o
     p_new->i_y = p_sys->i_y + p_new->i_y * ZOOM_FACTOR / p_sys->i_zoom;
     return VLC_SUCCESS;
 }
-
diff --git a/modules/video_filter/mirror.c b/modules/video_filter/mirror.c
index ab8f9dcdd04..57a1dea39c4 100644
--- a/modules/video_filter/mirror.c
+++ b/modules/video_filter/mirror.c
@@ -155,7 +155,11 @@ static int Create( vlc_object_t *p_this )
     var_AddCallback( p_filter, CFG_PREFIX "split", FilterCallback, p_sys );
     var_AddCallback( p_filter, CFG_PREFIX "direction", FilterCallback, p_sys );
 
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/motionblur.c b/modules/video_filter/motionblur.c
index 4bd504598c8..b536c0a4520 100644
--- a/modules/video_filter/motionblur.c
+++ b/modules/video_filter/motionblur.c
@@ -113,7 +113,11 @@ static int Create( vlc_object_t *p_this )
     }
     p_sys->b_first = true;
 
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     config_ChainParse( p_filter, FILTER_PREFIX, ppsz_filter_options,
                        p_filter->p_cfg );
diff --git a/modules/video_filter/motiondetect.c b/modules/video_filter/motiondetect.c
index 9906132a616..4787f5580ca 100644
--- a/modules/video_filter/motiondetect.c
+++ b/modules/video_filter/motiondetect.c
@@ -106,7 +106,11 @@ static int Create( vlc_object_t *p_this )
                      (char*)&(p_fmt->i_chroma) );
             return VLC_EGENERIC;
     }
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     /* Allocate structure */
     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
diff --git a/modules/video_filter/oldmovie.c b/modules/video_filter/oldmovie.c
index f7c6a710fc0..c2d4fc7cb54 100644
--- a/modules/video_filter/oldmovie.c
+++ b/modules/video_filter/oldmovie.c
@@ -216,7 +216,11 @@ static int Open( vlc_object_t *p_this ) {
         return VLC_ENOMEM;
 
     /* init data */
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
     p_sys->i_start_time = p_sys->i_cur_time = p_sys->i_last_time = vlc_tick_now();
 
     return VLC_SUCCESS;
diff --git a/modules/video_filter/opencv_example.cpp b/modules/video_filter/opencv_example.cpp
index 1334cd4c366..ed4e8cba266 100644
--- a/modules/video_filter/opencv_example.cpp
+++ b/modules/video_filter/opencv_example.cpp
@@ -108,7 +108,14 @@ static int OpenFilter( vlc_object_t *p_this )
     p_sys->event_info.p_region = NULL;
     p_sys->i_id = 0;
 
-    p_filter->pf_video_filter = Filter;
+    static const struct FilterOperationInitializer {
+        struct vlc_filter_operations ops {};
+        FilterOperationInitializer()
+        {
+            ops.filter_video = Filter;
+        };
+    } filter_ops;
+    p_filter->ops = &filter_ops.ops;
 
     //create the VIDEO_FILTER_EVENT_VARIABLE
     vlc_value_t val;
@@ -157,7 +164,7 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
     CvPoint pt1, pt2;
     int scale = 1;
     filter_sys_t *p_sys = static_cast<filter_sys_t *>(p_filter->p_sys);
- 
+
     if ((!p_pic) )
     {
         msg_Err( p_filter, "no image array" );
@@ -217,4 +224,3 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
 
     return p_pic;
 }
-
diff --git a/modules/video_filter/opencv_wrapper.c b/modules/video_filter/opencv_wrapper.c
index 2ce695913f9..d103cc7edc8 100644
--- a/modules/video_filter/opencv_wrapper.c
+++ b/modules/video_filter/opencv_wrapper.c
@@ -249,8 +249,12 @@ static int Create( vlc_object_t *p_this )
     msg_Dbg( p_filter, "opencv_wrapper successfully started" );
 #endif
 
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
     p_filter->p_sys = p_sys;
-    p_filter->pf_video_filter = Filter;
 
     return VLC_SUCCESS;
 }
@@ -414,7 +418,7 @@ static picture_t* Filter( filter_t* p_filter, picture_t* p_pic )
     VlcPictureToIplImage( p_filter, p_pic );
     // Pass the image (as a pointer to the first IplImage*) to the
     // internal OpenCV filter for processing.
-    p_sys->p_opencv->pf_video_filter( p_sys->p_opencv, (picture_t*)&(p_sys->p_cv_image[0]) );
+    p_sys->p_opencv->ops->filter_video( p_sys->p_opencv, (picture_t*)&(p_sys->p_cv_image[0]) );
 
     if(p_sys->i_wrapper_output == PROCESSED) {
         // Processed video
@@ -459,4 +463,3 @@ static picture_t* Filter( filter_t* p_filter, picture_t* p_pic )
         return NULL;
     }
 }
-
diff --git a/modules/video_filter/posterize.c b/modules/video_filter/posterize.c
index 91762a8591c..99d69cac17f 100644
--- a/modules/video_filter/posterize.c
+++ b/modules/video_filter/posterize.c
@@ -134,7 +134,11 @@ static int Create( vlc_object_t *p_this )
 
     var_AddCallback( p_filter, CFG_PREFIX "level", FilterCallback, p_sys );
 
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/postproc.c b/modules/video_filter/postproc.c
index bf0192de2b0..24fa4bc9ba7 100644
--- a/modules/video_filter/postproc.c
+++ b/modules/video_filter/postproc.c
@@ -252,7 +252,11 @@ static int OpenPostproc( vlc_object_t *p_this )
     var_AddCallback( p_filter, FILTER_PREFIX "q", PPQCallback, NULL );
     var_AddCallback( p_filter, FILTER_PREFIX "name", PPNameCallback, NULL );
 
-    p_filter->pf_video_filter = PostprocPict;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = PostprocPict,
+    };
+    p_filter->ops = &filter_ops;
 
     msg_Warn( p_filter, "Quantification table was not set by video decoder. "
                         "Postprocessing won't look good." );
diff --git a/modules/video_filter/psychedelic.c b/modules/video_filter/psychedelic.c
index 94c57641227..5295ffce441 100644
--- a/modules/video_filter/psychedelic.c
+++ b/modules/video_filter/psychedelic.c
@@ -96,7 +96,11 @@ static int Create( vlc_object_t *p_this )
         return VLC_ENOMEM;
     p_filter->p_sys = p_sys;
 
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     p_sys->x = 10;
     p_sys->y = 10;
diff --git a/modules/video_filter/puzzle.c b/modules/video_filter/puzzle.c
index bef39368bc4..80b5775c742 100644
--- a/modules/video_filter/puzzle.c
+++ b/modules/video_filter/puzzle.c
@@ -202,8 +202,12 @@ static int Open( vlc_object_t *p_this )
     var_AddCallback( p_filter, CFG_PREFIX "rotation",     puzzle_Callback, p_sys );
     var_AddCallback( p_filter, CFG_PREFIX "mode",     puzzle_Callback, p_sys );
 
-    p_filter->pf_video_filter = Filter;
-    p_filter->pf_video_mouse = puzzle_mouse;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+        .video_mouse = puzzle_mouse,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/ripple.c b/modules/video_filter/ripple.c
index a190c350039..c6748995bbd 100644
--- a/modules/video_filter/ripple.c
+++ b/modules/video_filter/ripple.c
@@ -90,7 +90,12 @@ static int Create( vlc_object_t *p_this )
     if( p_sys == NULL )
         return VLC_ENOMEM;
     p_filter->p_sys = p_sys;
-    p_filter->pf_video_filter = Filter;
+
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     p_sys->f_angle = 0.0;
     p_sys->last_date = 0;
diff --git a/modules/video_filter/rotate.c b/modules/video_filter/rotate.c
index 005ba96ec9b..48b9d92cb3a 100644
--- a/modules/video_filter/rotate.c
+++ b/modules/video_filter/rotate.c
@@ -118,6 +118,16 @@ static void fetch_trigo( filter_sys_t *sys, int *i_sin, int *i_cos )
     *i_cos = sincos.cos;
 }
 
+static const struct vlc_filter_operations planar_filter_ops =
+{
+    .filter_video = Filter,
+};
+
+static const struct vlc_filter_operations packed_filter_ops =
+{
+    .filter_video = FilterPacked,
+};
+
 /*****************************************************************************
  * Create: allocates Distort video filter
  *****************************************************************************/
@@ -135,11 +145,11 @@ static int Create( vlc_object_t *p_this )
     switch( p_filter->fmt_in.video.i_chroma )
     {
         CASE_PLANAR_YUV
-            p_filter->pf_video_filter = Filter;
+            p_filter->ops = &planar_filter_ops;
             break;
 
         CASE_PACKED_YUV_422
-            p_filter->pf_video_filter = FilterPacked;
+            p_filter->ops = &packed_filter_ops;
             break;
 
         default:
diff --git a/modules/video_filter/scale.c b/modules/video_filter/scale.c
index ef5189e2e13..648be69e4ab 100644
--- a/modules/video_filter/scale.c
+++ b/modules/video_filter/scale.c
@@ -74,7 +74,12 @@ static int OpenFilter( vlc_object_t *p_this )
 
 #warning Converter cannot (really) change output format.
     video_format_ScaleCropAr( &p_filter->fmt_out.video, &p_filter->fmt_in.video );
-    p_filter->pf_video_filter = Filter;
+
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     msg_Dbg( p_filter, "%ix%i -> %ix%i", p_filter->fmt_in.video.i_width,
              p_filter->fmt_in.video.i_height, p_filter->fmt_out.video.i_width,
diff --git a/modules/video_filter/scene.c b/modules/video_filter/scene.c
index 48ca076dfd8..f655cb4e7a6 100644
--- a/modules/video_filter/scene.c
+++ b/modules/video_filter/scene.c
@@ -148,7 +148,7 @@ typedef struct
 } filter_sys_t;
 
 /*****************************************************************************
- * Create: initialize and set pf_video_filter()
+ * Create: initialize and set ops
  *****************************************************************************/
 static int Create( vlc_object_t *p_this )
 {
@@ -197,7 +197,11 @@ static int Create( vlc_object_t *p_this )
     if( p_sys->psz_path == NULL )
         p_sys->psz_path = config_GetUserDir( VLC_PICTURES_DIR );
 
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/sepia.c b/modules/video_filter/sepia.c
index 9ca567c614e..aa653ffdc83 100644
--- a/modules/video_filter/sepia.c
+++ b/modules/video_filter/sepia.c
@@ -143,7 +143,11 @@ static int Create( vlc_object_t *p_this )
              var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "intensity" ) );
     var_AddCallback( p_filter, CFG_PREFIX "intensity", FilterCallback, NULL );
 
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/video_filter/sharpen.c b/modules/video_filter/sharpen.c
index 76972de44af..a53ca5c5001 100644
--- a/modules/video_filter/sharpen.c
+++ b/modules/video_filter/sharpen.c
@@ -118,7 +118,11 @@ static int Create( vlc_object_t *p_this )
         return VLC_ENOMEM;
     p_filter->p_sys = p_sys;
 
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     config_ChainParse( p_filter, FILTER_PREFIX, ppsz_filter_options,
                    p_filter->p_cfg );
diff --git a/modules/video_filter/transform.c b/modules/video_filter/transform.c
index a9e8dcb1479..ab48f748b41 100644
--- a/modules/video_filter/transform.c
+++ b/modules/video_filter/transform.c
@@ -432,9 +432,13 @@ static int Open(vlc_object_t *object)
             goto error;
     }
 
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+        .video_mouse = Mouse,
+    };
+    filter->ops = &filter_ops;
     filter->p_sys           = sys;
-    filter->pf_video_filter = Filter;
-    filter->pf_video_mouse  = Mouse;
     return VLC_SUCCESS;
 error:
     free(sys);
diff --git a/modules/video_filter/vhs.c b/modules/video_filter/vhs.c
index 6d5c13db8e8..e27c9b376aa 100644
--- a/modules/video_filter/vhs.c
+++ b/modules/video_filter/vhs.c
@@ -143,7 +143,11 @@ static int Open( vlc_object_t *p_this )
         return VLC_ENOMEM;
 
     /* init data */
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
     p_sys->i_start_time = p_sys->i_cur_time = p_sys->i_last_time = vlc_tick_now();
 
     return VLC_SUCCESS;
diff --git a/modules/video_filter/wave.c b/modules/video_filter/wave.c
index eae6c961b74..e732251cacc 100644
--- a/modules/video_filter/wave.c
+++ b/modules/video_filter/wave.c
@@ -90,7 +90,11 @@ static int Create( vlc_object_t *p_this )
     if( !p_sys )
         return VLC_ENOMEM;
 
-    p_filter->pf_video_filter = Filter;
+    static const struct vlc_filter_operations filter_ops =
+    {
+        .filter_video = Filter,
+    };
+    p_filter->ops = &filter_ops;
 
     p_sys->f_angle = 0.0;
     p_sys->last_date = 0;
diff --git a/modules/visualization/glspectrum.c b/modules/visualization/glspectrum.c
index 5ca0e75dedb..3e8b878197b 100644
--- a/modules/visualization/glspectrum.c
+++ b/modules/visualization/glspectrum.c
@@ -111,6 +111,10 @@ static void *Thread(void *);
 const GLfloat lightZeroColor[] = {1.0f, 1.0f, 1.0f, 1.0f};
 const GLfloat lightZeroPosition[] = {0.0f, 3.0f, 10.0f, 0.0f};
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_audio = DoWork,
+};
+
 /**
  * Open the module.
  * @param p_this: the filter object
@@ -160,7 +164,7 @@ static int Open(vlc_object_t * p_this)
 
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+    p_filter->ops = &filter_ops;
 
     return VLC_SUCCESS;
 }
diff --git a/modules/visualization/goom.c b/modules/visualization/goom.c
index fa8f2cc197a..eeeeae90cf7 100644
--- a/modules/visualization/goom.c
+++ b/modules/visualization/goom.c
@@ -104,6 +104,11 @@ static void Flush( filter_t * );
 
 static void *Thread( void * );
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_audio = DoWork,
+    .flush = Flush,
+};
+
 /*****************************************************************************
  * Open: open a scope effect plugin
  *****************************************************************************/
@@ -154,8 +159,7 @@ static int Open( vlc_object_t *p_this )
 
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
-    p_filter->pf_flush = Flush;
+    p_filter->ops = &filter_ops;
     return VLC_SUCCESS;
 }
 
diff --git a/modules/visualization/projectm.cpp b/modules/visualization/projectm.cpp
index f4b636fb232..b0e2d0edbb0 100644
--- a/modules/visualization/projectm.cpp
+++ b/modules/visualization/projectm.cpp
@@ -157,6 +157,14 @@ struct filter_sys_t
 static block_t *DoWork( filter_t *, block_t * );
 static void *Thread( void * );
 
+static const struct FilterOperationInitializer {
+    struct vlc_filter_operations ops {};
+    FilterOperationInitializer()
+    {
+        ops.filter_audio = DoWork;
+    };
+} filter_ops;
+
 /**
  * Open the module
  * @param p_this: the filter object
@@ -200,7 +208,7 @@ static int Open( vlc_object_t * p_this )
 
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+    p_filter->ops = &filter_ops.ops;
     return VLC_SUCCESS;
 
 error:
diff --git a/modules/visualization/visual/visual.c b/modules/visualization/visual/visual.c
index db693dfbcd9..21c34ad9452 100644
--- a/modules/visualization/visual/visual.c
+++ b/modules/visualization/visual/visual.c
@@ -188,6 +188,11 @@ typedef struct
     vlc_thread_t    thread;
 } filter_sys_t;
 
+static const struct vlc_filter_operations filter_ops = {
+    .filter_audio = DoWork,
+    .flush = Flush,
+};
+
 /*****************************************************************************
  * Open: open the visualizer
  *****************************************************************************/
@@ -323,8 +328,7 @@ static int Open( vlc_object_t *p_this )
 
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
-    p_filter->pf_flush = Flush;
+    p_filter->ops = &filter_ops;
     return VLC_SUCCESS;
 
 error:
diff --git a/modules/visualization/vsxu.cpp b/modules/visualization/vsxu.cpp
index dbbb061ecf2..9abea745da8 100644
--- a/modules/visualization/vsxu.cpp
+++ b/modules/visualization/vsxu.cpp
@@ -97,6 +97,14 @@ struct filter_sys_t
 static block_t *DoWork( filter_t *, block_t * );
 static void *Thread( void * );
 
+static const struct FilterOperationInitializer {
+    struct vlc_filter_operations ops {};
+    FilterOperationInitializer()
+    {
+        ops.filter_audio = DoWork;
+    };
+} filter_ops;
+
 /**
  * Open the module
  * @param p_this: the filter object
@@ -141,7 +149,7 @@ static int Open( vlc_object_t * p_this )
 
     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
-    p_filter->pf_audio_filter = DoWork;
+    p_filter->ops = &filter_ops.ops;
 
     return VLC_SUCCESS;
 
diff --git a/src/audio_output/filters.c b/src/audio_output/filters.c
index 11a77e95b1f..d2d2b3225a6 100644
--- a/src/audio_output/filters.c
+++ b/src/audio_output/filters.c
@@ -84,7 +84,9 @@ filter_t *aout_filter_Create(vlc_object_t *obj, const filter_owner_t *restrict o
         filter = NULL;
     }
     else
-        assert (filter->pf_audio_filter != NULL);
+    {
+        assert (filter->ops != NULL && filter->ops->filter_audio != NULL);
+    }
     return filter;
 }
 
@@ -286,7 +288,7 @@ static block_t *aout_FiltersPipelinePlay(filter_t *const *filters,
 
         /* Please note that p_block->i_nb_samples & i_buffer
          * shall be set by the filter plug-in. */
-        block = filter->pf_audio_filter (filter, block);
+        block = filter->ops->filter_audio (filter, block);
     }
     return block;
 }
diff --git a/src/audio_output/meter.c b/src/audio_output/meter.c
index 3cfc1158abe..8fd570d0b10 100644
--- a/src/audio_output/meter.c
+++ b/src/audio_output/meter.c
@@ -109,7 +109,7 @@ vlc_audio_meter_AddPlugin(struct vlc_audio_meter *meter, const char *chain,
         if (plugin->filter == NULL)
             goto error;
 
-        assert(plugin->filter->pf_audio_drain == NULL); /* Not supported */
+        assert(plugin->filter->ops->drain_audio == NULL); /* Not supported */
     }
 
     vlc_mutex_lock(&meter->lock);
@@ -198,7 +198,7 @@ vlc_audio_meter_Process(struct vlc_audio_meter *meter, block_t *block, vlc_tick_
         {
             plugin->last_date = date + block->i_length;
 
-            block_t *same_block = filter->pf_audio_filter(filter, block);
+            block_t *same_block = filter->ops->filter_audio(filter, block);
             assert(same_block == block); (void) same_block;
         }
     }
@@ -215,8 +215,8 @@ vlc_audio_meter_Flush(struct vlc_audio_meter *meter)
     vlc_list_foreach(plugin, &meter->plugins, node)
     {
         filter_t *filter = plugin->filter;
-        if (filter != NULL && filter->pf_flush != NULL)
-            filter->pf_flush(filter);
+        if (filter != NULL && filter->ops->flush != NULL)
+            filter->ops->flush(filter);
     }
 
     vlc_mutex_unlock(&meter->lock);
diff --git a/src/misc/filter.c b/src/misc/filter.c
index e8c1cae82eb..ed23e1d7fe6 100644
--- a/src/misc/filter.c
+++ b/src/misc/filter.c
@@ -164,7 +164,7 @@ int filter_Blend( vlc_blender_t *p_blend,
     if( !p_blend->p_module )
         return VLC_EGENERIC;
 
-    p_blend->pf_video_blend( p_blend, p_dst, p_src, i_dst_x, i_dst_y, i_alpha );
+    p_blend->ops->blend_video( p_blend, p_dst, p_src, i_dst_x, i_dst_y, i_alpha );
     return VLC_SUCCESS;
 }
 
diff --git a/src/misc/filter_chain.c b/src/misc/filter_chain.c
index 645a27507fd..bce6980d9bb 100644
--- a/src/misc/filter_chain.c
+++ b/src/misc/filter_chain.c
@@ -429,7 +429,7 @@ static picture_t *FilterChainVideoFilter( chained_filter_t *f, picture_t *p_pic
     for( ; f != NULL; f = f->next )
     {
         filter_t *p_filter = &f->filter;
-        p_pic = p_filter->pf_video_filter( p_filter, p_pic );
+        p_pic = p_filter->ops->filter_video( p_filter, p_pic );
         if( !p_pic )
             break;
         if( f->pending )
@@ -483,7 +483,7 @@ void filter_chain_SubSource( filter_chain_t *p_chain, spu_t *spu,
     for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
     {
         filter_t *p_filter = &f->filter;
-        subpicture_t *p_subpic = p_filter->pf_sub_source( p_filter, display_date );
+        subpicture_t *p_subpic = p_filter->ops->source_sub( p_filter, display_date );
         if( p_subpic )
             spu_PutSubpicture( spu, p_subpic );
     }
@@ -495,7 +495,7 @@ subpicture_t *filter_chain_SubFilter( filter_chain_t *p_chain, subpicture_t *p_s
     {
         filter_t *p_filter = &f->filter;
 
-        p_subpic = p_filter->pf_sub_filter( p_filter, p_subpic );
+        p_subpic = p_filter->ops->filter_sub( p_filter, p_subpic );
 
         if( !p_subpic )
             break;
@@ -512,13 +512,13 @@ int filter_chain_MouseFilter( filter_chain_t *p_chain, vlc_mouse_t *p_dst, const
         filter_t *p_filter = &f->filter;
         vlc_mouse_t *p_mouse = f->mouse;
 
-        if( p_filter->pf_video_mouse && p_mouse )
+        if( p_filter->ops->video_mouse && p_mouse )
         {
             vlc_mouse_t old = *p_mouse;
             vlc_mouse_t filtered = current;
 
             *p_mouse = current;
-            if( p_filter->pf_video_mouse( p_filter, &filtered, &old ) )
+            if( p_filter->ops->video_mouse( p_filter, &filtered, &old) )
                 return VLC_EGENERIC;
             current = filtered;
         }
diff --git a/src/misc/image.c b/src/misc/image.c
index faa05d9b018..e8f508c66b3 100644
--- a/src/misc/image.c
+++ b/src/misc/image.c
@@ -270,7 +270,7 @@ static picture_t *ImageRead( image_handler_t *p_image, block_t *p_block,
             video_format_Copy( &p_image->p_converter->fmt_out.video, p_fmt_out);
         }
 
-        p_pic = p_image->p_converter->pf_video_filter( p_image->p_converter, p_pic );
+        p_pic = p_image->p_converter->ops->filter_video( p_image->p_converter, p_pic );
     }
     else
     {
@@ -434,7 +434,7 @@ static block_t *ImageWrite( image_handler_t *p_image, picture_t *p_pic,
         picture_Hold( p_pic );
 
         p_tmp_pic =
-            p_image->p_converter->pf_video_filter( p_image->p_converter, p_pic );
+            p_image->p_converter->ops->filter_video( p_image->p_converter, p_pic );
 
         if( likely(p_tmp_pic != NULL) )
         {
@@ -569,7 +569,7 @@ static picture_t *ImageConvert( image_handler_t *p_image, picture_t *p_pic,
 
     picture_Hold( p_pic );
 
-    return p_image->p_converter->pf_video_filter( p_image->p_converter, p_pic );
+    return p_image->p_converter->ops->filter_video( p_image->p_converter, p_pic );
 }
 
 /**
diff --git a/src/video_output/vout_subpictures.c b/src/video_output/vout_subpictures.c
index 8766e7b5820..fefcf21b8eb 100644
--- a/src/video_output/vout_subpictures.c
+++ b/src/video_output/vout_subpictures.c
@@ -343,7 +343,7 @@ static int SpuRenderText(spu_t *spu,
     text->fmt_out.video.i_height =
     text->fmt_out.video.i_visible_height = i_original_height;
 
-    int i_ret = text->pf_render(text, region, region, chroma_list);
+    int i_ret = text->ops->render(text, region, region, chroma_list);
 
     vlc_mutex_unlock(&sys->textlock);
     return i_ret;
@@ -996,7 +996,7 @@ static void SpuRenderRegion(spu_t *spu,
                 scale_yuvp->fmt_out.video = region->fmt;
                 scale_yuvp->fmt_out.video.i_chroma = chroma_list[0];
 
-                picture = scale_yuvp->pf_video_filter(scale_yuvp, picture);
+                picture = scale_yuvp->ops->filter_video(scale_yuvp, picture);
                 if (!picture) {
                     /* Well we will try conversion+scaling */
                     msg_Warn(spu, "%4.4s to %4.4s conversion failed",
@@ -1027,7 +1027,7 @@ static void SpuRenderRegion(spu_t *spu,
                 scale->fmt_out.video.i_visible_height =
                     spu_scale_h(region->fmt.i_visible_height, scale_size);
 
-                picture = scale->pf_video_filter(scale, picture);
+                picture = scale->ops->filter_video(scale, picture);
                 if (!picture)
                     msg_Err(spu, "scaling failed");
             }
-- 
2.26.2



More information about the vlc-devel mailing list