1 /*****************************************************************************
2 * chain.c : chain multiple video filter modules as a last resort solution
3 *****************************************************************************
4 * Copyright (C) 2007-2017 VLC authors and VideoLAN
7 * Authors: Antoine Cellerier <dionoea at videolan dot org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_filter.h>
35 #include <vlc_mouse.h>
36 #include <vlc_picture.h>
38 /*****************************************************************************
40 *****************************************************************************/
41 static int ActivateConverter ( vlc_object_t
* );
42 static int ActivateFilter ( vlc_object_t
* );
43 static void Destroy ( vlc_object_t
* );
46 set_description( N_("Video filtering using a chain of video filter modules") )
47 set_capability( "video converter", 1 )
48 set_callbacks( ActivateConverter
, Destroy
)
50 set_capability( "video filter", 0 )
51 set_callbacks( ActivateFilter
, Destroy
)
54 /*****************************************************************************
56 *****************************************************************************/
57 static picture_t
*Chain ( filter_t
*, picture_t
* );
59 static int BuildTransformChain( filter_t
*p_filter
);
60 static int BuildChromaResize( filter_t
* );
61 static int BuildChromaChain( filter_t
*p_filter
);
62 static int BuildFilterChain( filter_t
*p_filter
);
64 static int CreateChain( filter_t
*p_parent
, const es_format_t
*p_fmt_mid
);
65 static int CreateResizeChromaChain( filter_t
*p_parent
, const es_format_t
*p_fmt_mid
);
66 static filter_t
* AppendTransform( filter_chain_t
*p_chain
, const es_format_t
*p_fmt_in
,
67 const es_format_t
*p_fmt_out
);
68 static void EsFormatMergeSize( es_format_t
*p_dst
,
69 const es_format_t
*p_base
,
70 const es_format_t
*p_size
);
72 #define ALLOWED_CHROMAS_YUV10 \
77 static const vlc_fourcc_t pi_allowed_chromas_yuv[] = {
80 ALLOWED_CHROMAS_YUV10
,
87 static const vlc_fourcc_t pi_allowed_chromas_yuv10
[] = {
88 ALLOWED_CHROMAS_YUV10
,
97 static const vlc_fourcc_t
*get_allowed_chromas( filter_t
*p_filter
)
99 switch (p_filter
->fmt_out
.video
.i_chroma
)
101 case VLC_CODEC_I420_10L
:
102 case VLC_CODEC_I420_10B
:
103 case VLC_CODEC_I420_16L
:
104 case VLC_CODEC_CVPX_P010
:
105 case VLC_CODEC_D3D9_OPAQUE_10B
:
106 case VLC_CODEC_D3D11_OPAQUE_10B
:
107 case VLC_CODEC_VAAPI_420_10BPP
:
108 return pi_allowed_chromas_yuv10
;
110 return pi_allowed_chromas_yuv
;
116 filter_chain_t
*p_chain
;
117 filter_t
*p_video_filter
;
120 /* Restart filter callback */
121 static int RestartFilterCallback( vlc_object_t
*obj
, char const *psz_name
,
122 vlc_value_t oldval
, vlc_value_t newval
,
124 { VLC_UNUSED(obj
); VLC_UNUSED(psz_name
); VLC_UNUSED(oldval
);
127 var_TriggerCallback( (vlc_object_t
*)p_data
, "video-filter" );
131 /*****************************************************************************
133 *****************************************************************************/
134 static picture_t
*BufferNew( filter_t
*p_filter
)
136 filter_t
*p_parent
= p_filter
->owner
.sys
;
138 return filter_NewPicture( p_parent
);
141 #define CHAIN_LEVEL_MAX 2
143 /*****************************************************************************
144 * Activate: allocate a chroma function
145 *****************************************************************************
146 * This function allocates and initializes a chroma function
147 *****************************************************************************/
148 static int Activate( filter_t
*p_filter
, int (*pf_build
)(filter_t
*) )
151 int i_ret
= VLC_EGENERIC
;
153 p_sys
= p_filter
->p_sys
= calloc( 1, sizeof( *p_sys
) );
157 filter_owner_t owner
= {
160 .buffer_new
= BufferNew
,
164 p_sys
->p_chain
= filter_chain_NewVideo( p_filter
, p_filter
->b_allow_fmt_out_change
, &owner
);
165 if( !p_sys
->p_chain
)
171 int type
= VLC_VAR_INTEGER
;
172 if( var_Type( p_filter
->obj
.parent
, "chain-level" ) != 0 )
173 type
|= VLC_VAR_DOINHERIT
;
175 var_Create( p_filter
, "chain-level", type
);
176 /* Note: atomicity is not actually needed here. */
177 var_IncInteger( p_filter
, "chain-level" );
179 int level
= var_GetInteger( p_filter
, "chain-level" );
180 if( level
< 0 || level
> CHAIN_LEVEL_MAX
)
181 msg_Err( p_filter
, "Too high level of recursion (%d)", level
);
183 i_ret
= pf_build( p_filter
);
185 var_Destroy( p_filter
, "chain-level" );
189 /* Hum ... looks like this really isn't going to work. Too bad. */
190 if (p_sys
->p_video_filter
)
191 filter_DelProxyCallbacks( p_filter
, p_sys
->p_video_filter
,
192 RestartFilterCallback
);
193 filter_chain_Delete( p_sys
->p_chain
);
197 else if( p_filter
->b_allow_fmt_out_change
)
199 es_format_Clean( &p_filter
->fmt_out
);
200 es_format_Copy( &p_filter
->fmt_out
,
201 filter_chain_GetFmtOut( p_filter
->p_sys
->p_chain
) );
204 p_filter
->pf_video_filter
= Chain
;
208 static int ActivateConverter( vlc_object_t
*p_this
)
210 filter_t
*p_filter
= (filter_t
*)p_this
;
212 const bool b_chroma
= p_filter
->fmt_in
.video
.i_chroma
!= p_filter
->fmt_out
.video
.i_chroma
;
213 const bool b_resize
= p_filter
->fmt_in
.video
.i_width
!= p_filter
->fmt_out
.video
.i_width
||
214 p_filter
->fmt_in
.video
.i_height
!= p_filter
->fmt_out
.video
.i_height
;
216 const bool b_chroma_resize
= b_chroma
&& b_resize
;
217 const bool b_transform
= p_filter
->fmt_in
.video
.orientation
!= p_filter
->fmt_out
.video
.orientation
;
219 if( !b_chroma
&& !b_chroma_resize
&& !b_transform
)
222 return Activate( p_filter
, b_transform
? BuildTransformChain
:
223 b_chroma_resize
? BuildChromaResize
:
227 static int ActivateFilter( vlc_object_t
*p_this
)
229 filter_t
*p_filter
= (filter_t
*)p_this
;
231 if( !p_filter
->b_allow_fmt_out_change
|| p_filter
->psz_name
== NULL
)
234 if( var_Type( p_filter
->obj
.parent
, "chain-filter-level" ) != 0 )
237 var_Create( p_filter
, "chain-filter-level", VLC_VAR_INTEGER
);
238 int i_ret
= Activate( p_filter
, BuildFilterChain
);
239 var_Destroy( p_filter
, "chain-filter-level" );
244 static void Destroy( vlc_object_t
*p_this
)
246 filter_t
*p_filter
= (filter_t
*)p_this
;
248 if (p_filter
->p_sys
->p_video_filter
)
249 filter_DelProxyCallbacks( p_filter
, p_filter
->p_sys
->p_video_filter
,
250 RestartFilterCallback
);
251 filter_chain_Delete( p_filter
->p_sys
->p_chain
);
252 free( p_filter
->p_sys
);
255 /*****************************************************************************
257 *****************************************************************************/
258 static picture_t
*Chain( filter_t
*p_filter
, picture_t
*p_pic
)
260 return filter_chain_VideoFilter( p_filter
->p_sys
->p_chain
, p_pic
);
263 /*****************************************************************************
265 *****************************************************************************/
267 static int BuildTransformChain( filter_t
*p_filter
)
273 /* Lets try transform first, then (potentially) resize+chroma */
274 msg_Dbg( p_filter
, "Trying to build transform, then chroma+resize" );
275 es_format_Copy( &fmt_mid
, &p_filter
->fmt_in
);
276 video_format_TransformTo(&fmt_mid
.video
, p_filter
->fmt_out
.video
.orientation
);
277 i_ret
= CreateChain( p_filter
, &fmt_mid
);
278 es_format_Clean( &fmt_mid
);
279 if( i_ret
== VLC_SUCCESS
)
282 /* Lets try resize+chroma first, then transform */
283 msg_Dbg( p_filter
, "Trying to build chroma+resize" );
284 EsFormatMergeSize( &fmt_mid
, &p_filter
->fmt_out
, &p_filter
->fmt_in
);
285 i_ret
= CreateChain( p_filter
, &fmt_mid
);
286 es_format_Clean( &fmt_mid
);
287 if( i_ret
== VLC_SUCCESS
)
293 static int BuildChromaResize( filter_t
*p_filter
)
298 /* Lets try resizing and then doing the chroma conversion */
299 msg_Dbg( p_filter
, "Trying to build resize+chroma" );
300 EsFormatMergeSize( &fmt_mid
, &p_filter
->fmt_in
, &p_filter
->fmt_out
);
301 i_ret
= CreateResizeChromaChain( p_filter
, &fmt_mid
);
302 es_format_Clean( &fmt_mid
);
304 if( i_ret
== VLC_SUCCESS
)
307 /* Lets try it the other way arround (chroma and then resize) */
308 msg_Dbg( p_filter
, "Trying to build chroma+resize" );
309 EsFormatMergeSize( &fmt_mid
, &p_filter
->fmt_out
, &p_filter
->fmt_in
);
310 i_ret
= CreateChain( p_filter
, &fmt_mid
);
311 es_format_Clean( &fmt_mid
);
312 if( i_ret
== VLC_SUCCESS
)
318 static int BuildChromaChain( filter_t
*p_filter
)
321 int i_ret
= VLC_EGENERIC
;
323 /* Now try chroma format list */
324 const vlc_fourcc_t
*pi_allowed_chromas
= get_allowed_chromas( p_filter
);
325 for( int i
= 0; pi_allowed_chromas
[i
]; i
++ )
327 const vlc_fourcc_t i_chroma
= pi_allowed_chromas
[i
];
328 if( i_chroma
== p_filter
->fmt_in
.i_codec
||
329 i_chroma
== p_filter
->fmt_out
.i_codec
)
332 msg_Dbg( p_filter
, "Trying to use chroma %4.4s as middle man",
335 es_format_Copy( &fmt_mid
, &p_filter
->fmt_in
);
337 fmt_mid
.video
.i_chroma
= i_chroma
;
338 fmt_mid
.video
.i_rmask
= 0;
339 fmt_mid
.video
.i_gmask
= 0;
340 fmt_mid
.video
.i_bmask
= 0;
341 video_format_FixRgb(&fmt_mid
.video
);
343 i_ret
= CreateChain( p_filter
, &fmt_mid
);
344 es_format_Clean( &fmt_mid
);
346 if( i_ret
== VLC_SUCCESS
)
353 static int ChainMouse( filter_t
*p_filter
, vlc_mouse_t
*p_mouse
,
354 const vlc_mouse_t
*p_old
, const vlc_mouse_t
*p_new
)
357 return filter_chain_MouseFilter( p_filter
->p_sys
->p_chain
, p_mouse
, p_new
);
360 static int BuildFilterChain( filter_t
*p_filter
)
363 int i_ret
= VLC_EGENERIC
;
365 /* Now try chroma format list */
366 const vlc_fourcc_t
*pi_allowed_chromas
= get_allowed_chromas( p_filter
);
367 for( int i
= 0; pi_allowed_chromas
[i
]; i
++ )
369 filter_chain_Reset( p_filter
->p_sys
->p_chain
, &p_filter
->fmt_in
, &p_filter
->fmt_out
);
371 const vlc_fourcc_t i_chroma
= pi_allowed_chromas
[i
];
372 if( i_chroma
== p_filter
->fmt_in
.i_codec
||
373 i_chroma
== p_filter
->fmt_out
.i_codec
)
376 msg_Dbg( p_filter
, "Trying to use chroma %4.4s as middle man",
379 es_format_Copy( &fmt_mid
, &p_filter
->fmt_in
);
381 fmt_mid
.video
.i_chroma
= i_chroma
;
382 fmt_mid
.video
.i_rmask
= 0;
383 fmt_mid
.video
.i_gmask
= 0;
384 fmt_mid
.video
.i_bmask
= 0;
385 video_format_FixRgb(&fmt_mid
.video
);
387 if( filter_chain_AppendConverter( p_filter
->p_sys
->p_chain
,
388 NULL
, &fmt_mid
) == VLC_SUCCESS
)
390 p_filter
->p_sys
->p_video_filter
=
391 filter_chain_AppendFilter( p_filter
->p_sys
->p_chain
,
392 p_filter
->psz_name
, p_filter
->p_cfg
,
393 &fmt_mid
, &fmt_mid
);
394 if( p_filter
->p_sys
->p_video_filter
)
396 filter_AddProxyCallbacks( p_filter
,
397 p_filter
->p_sys
->p_video_filter
,
398 RestartFilterCallback
);
399 if (p_filter
->p_sys
->p_video_filter
->pf_video_mouse
!= NULL
)
400 p_filter
->pf_video_mouse
= ChainMouse
;
401 es_format_Clean( &fmt_mid
);
406 es_format_Clean( &fmt_mid
);
408 if( i_ret
!= VLC_SUCCESS
)
409 filter_chain_Reset( p_filter
->p_sys
->p_chain
, &p_filter
->fmt_in
, &p_filter
->fmt_out
);
414 /*****************************************************************************
416 *****************************************************************************/
417 static int CreateChain( filter_t
*p_parent
, const es_format_t
*p_fmt_mid
)
419 filter_chain_Reset( p_parent
->p_sys
->p_chain
, &p_parent
->fmt_in
, &p_parent
->fmt_out
);
423 if( p_parent
->fmt_in
.video
.orientation
!= p_fmt_mid
->video
.orientation
)
425 p_filter
= AppendTransform( p_parent
->p_sys
->p_chain
, &p_parent
->fmt_in
, p_fmt_mid
);
426 // Check if filter was enough:
427 if( p_filter
== NULL
)
429 if( es_format_IsSimilar(&p_filter
->fmt_out
, &p_parent
->fmt_out
))
434 if( filter_chain_AppendConverter( p_parent
->p_sys
->p_chain
,
439 if( p_fmt_mid
->video
.orientation
!= p_parent
->fmt_out
.video
.orientation
)
441 if( AppendTransform( p_parent
->p_sys
->p_chain
, p_fmt_mid
,
442 &p_parent
->fmt_out
) == NULL
)
447 if( filter_chain_AppendConverter( p_parent
->p_sys
->p_chain
,
454 filter_chain_Reset( p_parent
->p_sys
->p_chain
, NULL
, NULL
);
458 static int CreateResizeChromaChain( filter_t
*p_parent
, const es_format_t
*p_fmt_mid
)
460 filter_chain_Reset( p_parent
->p_sys
->p_chain
, &p_parent
->fmt_in
, &p_parent
->fmt_out
);
462 int i_ret
= filter_chain_AppendConverter( p_parent
->p_sys
->p_chain
,
464 if( i_ret
!= VLC_SUCCESS
)
467 if( p_parent
->b_allow_fmt_out_change
)
469 /* XXX: Update i_sar_num/i_sar_den from last converter. Cf.
470 * p_filter->b_allow_fmt_out_change comment in video_chroma/swscale.c.
474 es_format_Copy( &fmt_out
,
475 filter_chain_GetFmtOut( p_parent
->p_sys
->p_chain
) );
476 fmt_out
.video
.i_chroma
= p_parent
->fmt_out
.video
.i_chroma
;
478 i_ret
= filter_chain_AppendConverter( p_parent
->p_sys
->p_chain
,
480 es_format_Clean( &fmt_out
);
483 i_ret
= filter_chain_AppendConverter( p_parent
->p_sys
->p_chain
,
484 NULL
, &p_parent
->fmt_out
);
486 if( i_ret
!= VLC_SUCCESS
)
487 filter_chain_Reset( p_parent
->p_sys
->p_chain
, NULL
, NULL
);
491 static filter_t
* AppendTransform( filter_chain_t
*p_chain
, const es_format_t
*p_fmt1
,
492 const es_format_t
*p_fmt2
)
494 video_transform_t transform
= video_format_GetTransform(p_fmt1
->video
.orientation
, p_fmt2
->video
.orientation
);
498 switch ( transform
) {
509 case TRANSFORM_HFLIP
:
512 case TRANSFORM_VFLIP
:
515 case TRANSFORM_TRANSPOSE
:
518 case TRANSFORM_ANTI_TRANSPOSE
:
519 type
= "antitranspose";
532 snprintf( config
, 100, "transform{type=%s}", type
);
533 char *next
= config_ChainCreate( &name
, &cfg
, config
);
535 filter_t
*p_filter
= filter_chain_AppendFilter( p_chain
, name
, cfg
, p_fmt1
, p_fmt2
);
537 config_ChainDestroy(cfg
);
544 static void EsFormatMergeSize( es_format_t
*p_dst
,
545 const es_format_t
*p_base
,
546 const es_format_t
*p_size
)
548 es_format_Copy( p_dst
, p_base
);
550 p_dst
->video
.i_width
= p_size
->video
.i_width
;
551 p_dst
->video
.i_height
= p_size
->video
.i_height
;
553 p_dst
->video
.i_visible_width
= p_size
->video
.i_visible_width
;
554 p_dst
->video
.i_visible_height
= p_size
->video
.i_visible_height
;
556 p_dst
->video
.i_x_offset
= p_size
->video
.i_x_offset
;
557 p_dst
->video
.i_y_offset
= p_size
->video
.i_y_offset
;
559 p_dst
->video
.orientation
= p_size
->video
.orientation
;
560 p_dst
->video
.i_sar_num
= p_size
->video
.i_sar_num
;
561 p_dst
->video
.i_sar_den
= p_size
->video
.i_sar_den
;