1 /*****************************************************************************
2 * chain.c : chain multiple video filter modules as a last resort solution
3 *****************************************************************************
4 * Copyright (C) 2007-2017 VLC authors and VideoLAN
6 * Authors: Antoine Cellerier <dionoea at videolan dot org>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
23 /*****************************************************************************
25 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_filter.h>
34 #include <vlc_mouse.h>
35 #include <vlc_picture.h>
37 /*****************************************************************************
39 *****************************************************************************/
40 static int ActivateConverter ( filter_t
* );
41 static int ActivateFilter ( filter_t
* );
42 static void Destroy ( filter_t
* );
45 set_description( N_("Video filtering using a chain of video filter modules") )
46 set_callback_video_converter( ActivateConverter
, 1 )
48 set_callback_video_filter( ActivateFilter
)
51 /*****************************************************************************
53 *****************************************************************************/
54 static picture_t
*Chain ( filter_t
*, picture_t
* );
55 static void Flush ( filter_t
* );
57 static int BuildTransformChain( filter_t
*p_filter
);
58 static int BuildChromaResize( filter_t
* );
59 static int BuildChromaChain( filter_t
*p_filter
);
60 static int BuildFilterChain( filter_t
*p_filter
);
62 static int CreateChain( filter_t
*p_filter
, const es_format_t
*p_fmt_mid
);
63 static int CreateResizeChromaChain( filter_t
*p_filter
, const es_format_t
*p_fmt_mid
);
64 static filter_t
* AppendTransform( filter_chain_t
*p_chain
, const es_format_t
*p_fmt_in
,
65 const es_format_t
*p_fmt_out
);
66 static void EsFormatMergeSize( es_format_t
*p_dst
,
67 const es_format_t
*p_base
,
68 const es_format_t
*p_size
);
70 #define ALLOWED_CHROMAS_YUV10 \
75 static const vlc_fourcc_t pi_allowed_chromas_yuv[] = {
78 ALLOWED_CHROMAS_YUV10
,
85 static const vlc_fourcc_t pi_allowed_chromas_yuv10
[] = {
86 ALLOWED_CHROMAS_YUV10
,
95 static const vlc_fourcc_t
*get_allowed_chromas( filter_t
*p_filter
)
97 switch (p_filter
->fmt_out
.video
.i_chroma
)
99 case VLC_CODEC_I420_10L
:
100 case VLC_CODEC_I420_10B
:
101 case VLC_CODEC_I420_16L
:
102 case VLC_CODEC_CVPX_P010
:
103 case VLC_CODEC_D3D9_OPAQUE_10B
:
104 case VLC_CODEC_D3D11_OPAQUE_10B
:
105 case VLC_CODEC_VAAPI_420_10BPP
:
106 return pi_allowed_chromas_yuv10
;
108 return pi_allowed_chromas_yuv
;
114 filter_chain_t
*p_chain
;
115 filter_t
*p_video_filter
;
116 struct vlc_filter_operations custom_ops
;
119 /* Restart filter callback */
120 static int RestartFilterCallback( vlc_object_t
*obj
, char const *psz_name
,
121 vlc_value_t oldval
, vlc_value_t newval
,
123 { VLC_UNUSED(obj
); VLC_UNUSED(psz_name
); VLC_UNUSED(oldval
);
126 var_TriggerCallback( (vlc_object_t
*)p_data
, "video-filter" );
130 /*****************************************************************************
132 *****************************************************************************/
133 static picture_t
*BufferChainNew( filter_t
*p_filter
)
135 filter_t
*p_chain_parent
= p_filter
->owner
.sys
;
136 // the last filter of the internal chain gets its pictures from the original
138 return filter_NewPicture( p_chain_parent
);
141 #define CHAIN_LEVEL_MAX 2
143 static vlc_decoder_device
* HoldChainDecoderDevice(vlc_object_t
*o
, void *sys
)
146 filter_t
*p_chain_parent
= sys
;
147 return filter_HoldDecoderDevice( p_chain_parent
);
150 static const struct filter_video_callbacks filter_video_chain_cbs
=
152 BufferChainNew
, HoldChainDecoderDevice
,
155 static const struct vlc_filter_operations filter_ops
= {
156 .filter_video
= Chain
, .flush
= Flush
, .close
= Destroy
,
159 /*****************************************************************************
160 * Activate: allocate a chroma function
161 *****************************************************************************
162 * This function allocates and initializes a chroma function
163 *****************************************************************************/
164 static int Activate( filter_t
*p_filter
, int (*pf_build
)(filter_t
*) )
167 int i_ret
= VLC_EGENERIC
;
169 p_sys
= p_filter
->p_sys
= calloc( 1, sizeof( *p_sys
) );
173 filter_owner_t owner
= {
174 .video
= &filter_video_chain_cbs
,
178 p_sys
->p_chain
= filter_chain_NewVideo( p_filter
, p_filter
->b_allow_fmt_out_change
, &owner
);
179 if( !p_sys
->p_chain
)
185 int type
= VLC_VAR_INTEGER
;
186 if( var_Type( vlc_object_parent(p_filter
), "chain-level" ) != 0 )
187 type
|= VLC_VAR_DOINHERIT
;
189 var_Create( p_filter
, "chain-level", type
);
190 /* Note: atomicity is not actually needed here. */
191 var_IncInteger( p_filter
, "chain-level" );
193 int level
= var_GetInteger( p_filter
, "chain-level" );
194 if( level
< 0 || level
> CHAIN_LEVEL_MAX
)
195 msg_Err( p_filter
, "Too high level of recursion (%d)", level
);
197 i_ret
= pf_build( p_filter
);
199 var_Destroy( p_filter
, "chain-level" );
203 /* Hum ... looks like this really isn't going to work. Too bad. */
204 if (p_sys
->p_video_filter
)
205 filter_DelProxyCallbacks( p_filter
, p_sys
->p_video_filter
,
206 RestartFilterCallback
);
207 filter_chain_Delete( p_sys
->p_chain
);
211 if( p_filter
->b_allow_fmt_out_change
)
213 es_format_Clean( &p_filter
->fmt_out
);
214 es_format_Copy( &p_filter
->fmt_out
,
215 filter_chain_GetFmtOut( p_sys
->p_chain
) );
216 p_filter
->vctx_out
= filter_chain_GetVideoCtxOut( p_sys
->p_chain
);
218 assert(p_filter
->vctx_out
== filter_chain_GetVideoCtxOut( p_sys
->p_chain
));
220 p_filter
->ops
= &filter_ops
;
224 static int ActivateConverter( filter_t
*p_filter
)
226 const bool b_chroma
= p_filter
->fmt_in
.video
.i_chroma
!= p_filter
->fmt_out
.video
.i_chroma
;
227 const bool b_resize
= p_filter
->fmt_in
.video
.i_width
!= p_filter
->fmt_out
.video
.i_width
||
228 p_filter
->fmt_in
.video
.i_height
!= p_filter
->fmt_out
.video
.i_height
;
230 const bool b_chroma_resize
= b_chroma
&& b_resize
;
231 const bool b_transform
= p_filter
->fmt_in
.video
.orientation
!= p_filter
->fmt_out
.video
.orientation
;
233 if( !b_chroma
&& !b_chroma_resize
&& !b_transform
)
236 return Activate( p_filter
, b_transform
? BuildTransformChain
:
237 b_chroma_resize
? BuildChromaResize
:
241 static int ActivateFilter( filter_t
*p_filter
)
243 if( !p_filter
->b_allow_fmt_out_change
|| p_filter
->psz_name
== NULL
)
246 if( var_Type( vlc_object_parent(p_filter
), "chain-filter-level" ) != 0 )
249 var_Create( p_filter
, "chain-filter-level", VLC_VAR_INTEGER
);
250 int i_ret
= Activate( p_filter
, BuildFilterChain
);
251 var_Destroy( p_filter
, "chain-filter-level" );
256 static void Destroy( filter_t
*p_filter
)
258 filter_sys_t
*p_sys
= p_filter
->p_sys
;
260 if (p_sys
->p_video_filter
)
261 filter_DelProxyCallbacks( p_filter
, p_sys
->p_video_filter
,
262 RestartFilterCallback
);
263 filter_chain_Delete( p_sys
->p_chain
);
267 /*****************************************************************************
269 *****************************************************************************/
270 static picture_t
*Chain( filter_t
*p_filter
, picture_t
*p_pic
)
272 filter_sys_t
*p_sys
= p_filter
->p_sys
;
273 return filter_chain_VideoFilter( p_sys
->p_chain
, p_pic
);
276 static void Flush( filter_t
*p_filter
)
278 filter_sys_t
*p_sys
= p_filter
->p_sys
;
279 filter_chain_VideoFlush( p_sys
->p_chain
);
282 /*****************************************************************************
284 *****************************************************************************/
286 static int BuildTransformChain( filter_t
*p_filter
)
292 /* Lets try transform first, then (potentially) resize+chroma */
293 msg_Dbg( p_filter
, "Trying to build transform, then chroma+resize" );
294 es_format_Copy( &fmt_mid
, &p_filter
->fmt_in
);
295 video_format_TransformTo(&fmt_mid
.video
, p_filter
->fmt_out
.video
.orientation
);
296 i_ret
= CreateChain( p_filter
, &fmt_mid
);
297 es_format_Clean( &fmt_mid
);
298 if( i_ret
== VLC_SUCCESS
)
301 /* Lets try resize+chroma first, then transform */
302 msg_Dbg( p_filter
, "Trying to build chroma+resize" );
303 EsFormatMergeSize( &fmt_mid
, &p_filter
->fmt_out
, &p_filter
->fmt_in
);
304 i_ret
= CreateChain( p_filter
, &fmt_mid
);
305 es_format_Clean( &fmt_mid
);
309 static int BuildChromaResize( filter_t
*p_filter
)
314 /* Lets try resizing and then doing the chroma conversion */
315 msg_Dbg( p_filter
, "Trying to build resize+chroma" );
316 EsFormatMergeSize( &fmt_mid
, &p_filter
->fmt_in
, &p_filter
->fmt_out
);
317 i_ret
= CreateResizeChromaChain( p_filter
, &fmt_mid
);
318 es_format_Clean( &fmt_mid
);
320 if( i_ret
== VLC_SUCCESS
)
323 /* Lets try it the other way arround (chroma and then resize) */
324 msg_Dbg( p_filter
, "Trying to build chroma+resize" );
325 EsFormatMergeSize( &fmt_mid
, &p_filter
->fmt_out
, &p_filter
->fmt_in
);
326 i_ret
= CreateChain( p_filter
, &fmt_mid
);
327 es_format_Clean( &fmt_mid
);
328 if( i_ret
== VLC_SUCCESS
)
334 static int BuildChromaChain( filter_t
*p_filter
)
337 int i_ret
= VLC_EGENERIC
;
339 /* Now try chroma format list */
340 const vlc_fourcc_t
*pi_allowed_chromas
= get_allowed_chromas( p_filter
);
341 for( int i
= 0; pi_allowed_chromas
[i
]; i
++ )
343 const vlc_fourcc_t i_chroma
= pi_allowed_chromas
[i
];
344 if( i_chroma
== p_filter
->fmt_in
.i_codec
||
345 i_chroma
== p_filter
->fmt_out
.i_codec
)
348 msg_Dbg( p_filter
, "Trying to use chroma %4.4s as middle man",
351 es_format_Copy( &fmt_mid
, &p_filter
->fmt_in
);
353 fmt_mid
.video
.i_chroma
= i_chroma
;
354 fmt_mid
.video
.i_rmask
= 0;
355 fmt_mid
.video
.i_gmask
= 0;
356 fmt_mid
.video
.i_bmask
= 0;
357 video_format_FixRgb(&fmt_mid
.video
);
359 i_ret
= CreateChain( p_filter
, &fmt_mid
);
360 es_format_Clean( &fmt_mid
);
362 if( i_ret
== VLC_SUCCESS
)
369 static int ChainMouse( filter_t
*p_filter
, vlc_mouse_t
*p_mouse
,
370 const vlc_mouse_t
*p_old
)
372 filter_sys_t
*p_sys
= p_filter
->p_sys
;
373 return filter_chain_MouseFilter( p_sys
->p_chain
, p_mouse
, p_old
);
376 static int BuildFilterChain( filter_t
*p_filter
)
379 int i_ret
= VLC_EGENERIC
;
381 filter_sys_t
*p_sys
= p_filter
->p_sys
;
383 /* Now try chroma format list */
384 const vlc_fourcc_t
*pi_allowed_chromas
= get_allowed_chromas( p_filter
);
385 for( int i
= 0; pi_allowed_chromas
[i
]; i
++ )
387 filter_chain_Reset( p_sys
->p_chain
, &p_filter
->fmt_in
, p_filter
->vctx_in
, &p_filter
->fmt_out
);
389 const vlc_fourcc_t i_chroma
= pi_allowed_chromas
[i
];
390 if( i_chroma
== p_filter
->fmt_in
.i_codec
||
391 i_chroma
== p_filter
->fmt_out
.i_codec
)
394 msg_Dbg( p_filter
, "Trying to use chroma %4.4s as middle man",
397 es_format_Copy( &fmt_mid
, &p_filter
->fmt_in
);
399 fmt_mid
.video
.i_chroma
= i_chroma
;
400 fmt_mid
.video
.i_rmask
= 0;
401 fmt_mid
.video
.i_gmask
= 0;
402 fmt_mid
.video
.i_bmask
= 0;
403 video_format_FixRgb(&fmt_mid
.video
);
405 if( filter_chain_AppendConverter( p_sys
->p_chain
,
406 &fmt_mid
) == VLC_SUCCESS
)
408 p_sys
->p_video_filter
=
409 filter_chain_AppendFilter( p_sys
->p_chain
,
410 p_filter
->psz_name
, p_filter
->p_cfg
,
412 if( p_sys
->p_video_filter
)
414 filter_AddProxyCallbacks( p_filter
,
415 p_sys
->p_video_filter
,
416 RestartFilterCallback
);
417 if (p_sys
->p_video_filter
->ops
->video_mouse
!= NULL
)
419 p_sys
->custom_ops
= *p_sys
->p_video_filter
->ops
;
420 p_sys
->p_video_filter
->ops
= &p_sys
->custom_ops
;
421 p_sys
->custom_ops
.video_mouse
= ChainMouse
;
423 es_format_Clean( &fmt_mid
);
425 p_filter
->vctx_out
= filter_chain_GetVideoCtxOut( p_sys
->p_chain
);
429 es_format_Clean( &fmt_mid
);
431 if( i_ret
!= VLC_SUCCESS
)
432 filter_chain_Reset( p_sys
->p_chain
, &p_filter
->fmt_in
, p_filter
->vctx_in
, &p_filter
->fmt_out
);
437 /*****************************************************************************
439 *****************************************************************************/
440 static int CreateChain( filter_t
*p_filter
, const es_format_t
*p_fmt_mid
)
442 filter_sys_t
*p_sys
= p_filter
->p_sys
;
443 filter_chain_Reset( p_sys
->p_chain
, &p_filter
->fmt_in
, p_filter
->vctx_in
, &p_filter
->fmt_out
);
445 if( p_filter
->fmt_in
.video
.orientation
!= p_fmt_mid
->video
.orientation
)
447 filter_t
*p_transform
= AppendTransform( p_sys
->p_chain
, &p_filter
->fmt_in
, p_fmt_mid
);
448 // Check if filter was enough:
449 if( p_transform
== NULL
)
451 if( es_format_IsSimilar(&p_transform
->fmt_out
, &p_filter
->fmt_out
) )
453 p_filter
->vctx_out
= p_transform
->vctx_out
;
459 if( filter_chain_AppendConverter( p_sys
->p_chain
, p_fmt_mid
) )
463 if( p_fmt_mid
->video
.orientation
!= p_filter
->fmt_out
.video
.orientation
)
465 if( AppendTransform( p_sys
->p_chain
, p_fmt_mid
,
466 &p_filter
->fmt_out
) == NULL
)
471 if( filter_chain_AppendConverter( p_sys
->p_chain
, &p_filter
->fmt_out
) )
474 p_filter
->vctx_out
= filter_chain_GetVideoCtxOut( p_sys
->p_chain
);
478 filter_chain_Clear( p_sys
->p_chain
);
482 static int CreateResizeChromaChain( filter_t
*p_filter
, const es_format_t
*p_fmt_mid
)
484 filter_sys_t
*p_sys
= p_filter
->p_sys
;
485 filter_chain_Reset( p_sys
->p_chain
, &p_filter
->fmt_in
, p_filter
->vctx_in
, &p_filter
->fmt_out
);
487 int i_ret
= filter_chain_AppendConverter( p_sys
->p_chain
, p_fmt_mid
);
488 if( i_ret
!= VLC_SUCCESS
)
491 if( p_filter
->b_allow_fmt_out_change
)
493 /* XXX: Update i_sar_num/i_sar_den from last converter. Cf.
494 * p_filter->b_allow_fmt_out_change comment in video_chroma/swscale.c.
498 es_format_Copy( &fmt_out
,
499 filter_chain_GetFmtOut( p_sys
->p_chain
) );
500 fmt_out
.video
.i_chroma
= p_filter
->fmt_out
.video
.i_chroma
;
502 i_ret
= filter_chain_AppendConverter( p_sys
->p_chain
, &fmt_out
);
503 es_format_Clean( &fmt_out
);
506 i_ret
= filter_chain_AppendConverter( p_sys
->p_chain
, &p_filter
->fmt_out
);
508 if( i_ret
!= VLC_SUCCESS
)
509 filter_chain_Clear( p_sys
->p_chain
);
511 p_filter
->vctx_out
= filter_chain_GetVideoCtxOut( p_sys
->p_chain
);
516 static filter_t
* AppendTransform( filter_chain_t
*p_chain
, const es_format_t
*p_fmt1
,
517 const es_format_t
*p_fmt2
)
519 video_transform_t transform
= video_format_GetTransform(p_fmt1
->video
.orientation
, p_fmt2
->video
.orientation
);
523 switch ( transform
) {
534 case TRANSFORM_HFLIP
:
537 case TRANSFORM_VFLIP
:
540 case TRANSFORM_TRANSPOSE
:
543 case TRANSFORM_ANTI_TRANSPOSE
:
544 type
= "antitranspose";
557 snprintf( config
, 100, "transform{type=%s}", type
);
558 char *next
= config_ChainCreate( &name
, &cfg
, config
);
560 filter_t
*p_filter
= filter_chain_AppendFilter( p_chain
, name
, cfg
, p_fmt2
);
562 config_ChainDestroy(cfg
);
569 static void EsFormatMergeSize( es_format_t
*p_dst
,
570 const es_format_t
*p_base
,
571 const es_format_t
*p_size
)
573 es_format_Copy( p_dst
, p_base
);
575 p_dst
->video
.i_width
= p_size
->video
.i_width
;
576 p_dst
->video
.i_height
= p_size
->video
.i_height
;
578 p_dst
->video
.i_visible_width
= p_size
->video
.i_visible_width
;
579 p_dst
->video
.i_visible_height
= p_size
->video
.i_visible_height
;
581 p_dst
->video
.i_x_offset
= p_size
->video
.i_x_offset
;
582 p_dst
->video
.i_y_offset
= p_size
->video
.i_y_offset
;
584 p_dst
->video
.orientation
= p_size
->video
.orientation
;
585 p_dst
->video
.i_sar_num
= p_size
->video
.i_sar_num
;
586 p_dst
->video
.i_sar_den
= p_size
->video
.i_sar_den
;