filter_chain: introduce dedicated filter_chain_NewVideo() for video filters
[vlc.git] / modules / video_chroma / chain.c
1 /*****************************************************************************
2  * chain.c : chain multiple video filter modules as a last resort solution
3  *****************************************************************************
4  * Copyright (C) 2007-2008 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea at videolan dot org>
8  *
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.
13  *
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.
18  *
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  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_filter.h>
35
36 /*****************************************************************************
37  * Module descriptor
38  *****************************************************************************/
39 static int       Activate   ( vlc_object_t * );
40 static void      Destroy    ( vlc_object_t * );
41
42 vlc_module_begin ()
43     set_description( N_("Video filtering using a chain of video filter modules") )
44     set_capability( "video filter2", 1 )
45     set_callbacks( Activate, Destroy )
46 vlc_module_end ()
47
48 /*****************************************************************************
49  * Local prototypes.
50  *****************************************************************************/
51 static picture_t *Chain         ( filter_t *, picture_t * );
52
53 static int BuildTransformChain( filter_t *p_filter );
54 static int BuildChromaResize( filter_t * );
55 static int BuildChromaChain( filter_t *p_filter );
56
57 static int CreateChain( filter_t *p_parent, es_format_t *p_fmt_mid, config_chain_t * );
58 static filter_t * AppendTransform( filter_chain_t *p_chain, es_format_t *p_fmt_in, es_format_t *p_fmt_out );
59 static void EsFormatMergeSize( es_format_t *p_dst,
60                                const es_format_t *p_base,
61                                const es_format_t *p_size );
62
63 static const vlc_fourcc_t pi_allowed_chromas[] = {
64     VLC_CODEC_I420,
65     VLC_CODEC_I422,
66     VLC_CODEC_RGB32,
67     VLC_CODEC_RGB24,
68     0
69 };
70
71 struct filter_sys_t
72 {
73     filter_chain_t *p_chain;
74 };
75
76 /*****************************************************************************
77  * Buffer management
78  *****************************************************************************/
79 static picture_t *BufferNew( filter_t *p_filter )
80 {
81     filter_t *p_parent = p_filter->owner.sys;
82
83     return filter_NewPicture( p_parent );
84 }
85
86 static void BufferDel( filter_t *p_filter, picture_t *p_pic )
87 {
88     filter_t *p_parent = p_filter->owner.sys;
89
90     filter_DeletePicture( p_parent, p_pic );
91 }
92
93 #define CHAIN_LEVEL_MAX 1
94
95 /*****************************************************************************
96  * Activate: allocate a chroma function
97  *****************************************************************************
98  * This function allocates and initializes a chroma function
99  *****************************************************************************/
100 static int Activate( vlc_object_t *p_this )
101 {
102     filter_t *p_filter = (filter_t *)p_this;
103     filter_sys_t *p_sys;
104     int i_ret;
105
106     const bool b_chroma = p_filter->fmt_in.video.i_chroma != p_filter->fmt_out.video.i_chroma;
107     const bool b_resize = p_filter->fmt_in.video.i_width  != p_filter->fmt_out.video.i_width ||
108                           p_filter->fmt_in.video.i_height != p_filter->fmt_out.video.i_height;
109     const bool b_transform = p_filter->fmt_in.video.orientation != p_filter->fmt_out.video.orientation;
110
111     if( !b_chroma && !b_resize && !b_transform)
112         return VLC_EGENERIC;
113
114     p_sys = p_filter->p_sys = calloc( 1, sizeof( *p_sys ) );
115     if( !p_sys )
116         return VLC_ENOMEM;
117
118     filter_owner_t owner = {
119         .sys = p_filter,
120         .video = {
121             .buffer_new = BufferNew,
122             .buffer_del = BufferDel,
123         },
124     };
125
126     p_sys->p_chain = filter_chain_NewVideo( p_filter, false, &owner );
127     if( !p_sys->p_chain )
128     {
129         free( p_sys );
130         return VLC_EGENERIC;
131     }
132
133     if( b_transform )
134         i_ret = BuildTransformChain( p_filter );
135     else if( b_chroma && b_resize )
136         i_ret = BuildChromaResize( p_filter );
137     else if( b_chroma )
138         i_ret = BuildChromaChain( p_filter );
139     else
140         i_ret = VLC_EGENERIC;
141
142     if( i_ret )
143     {
144         /* Hum ... looks like this really isn't going to work. Too bad. */
145         filter_chain_Delete( p_sys->p_chain );
146         free( p_sys );
147         return VLC_EGENERIC;
148     }
149     /* */
150     p_filter->pf_video_filter = Chain;
151     return VLC_SUCCESS;
152 }
153
154 static void Destroy( vlc_object_t *p_this )
155 {
156     filter_t *p_filter = (filter_t *)p_this;
157     filter_chain_Delete( p_filter->p_sys->p_chain );
158     free( p_filter->p_sys );
159 }
160
161 /*****************************************************************************
162  * Chain
163  *****************************************************************************/
164 static picture_t *Chain( filter_t *p_filter, picture_t *p_pic )
165 {
166     return filter_chain_VideoFilter( p_filter->p_sys->p_chain, p_pic );
167 }
168
169 /*****************************************************************************
170  * Builders
171  *****************************************************************************/
172
173 static int BuildTransformChain( filter_t *p_filter )
174 {
175
176     es_format_t fmt_mid;
177     int i_ret;
178
179     /* Lets try transform first, then (potentially) resize+chroma */
180     msg_Dbg( p_filter, "Trying to build transform, then chroma+resize" );
181     es_format_Copy( &fmt_mid, &p_filter->fmt_in );
182     video_format_TransformTo(&fmt_mid.video, p_filter->fmt_out.video.orientation);
183     i_ret = CreateChain( p_filter, &fmt_mid, NULL );
184     es_format_Clean( &fmt_mid );
185     if( i_ret == VLC_SUCCESS )
186         return VLC_SUCCESS;
187
188     /* Lets try resize+chroma first, then transform */
189     msg_Dbg( p_filter, "Trying to build chroma+resize" );
190     EsFormatMergeSize( &fmt_mid, &p_filter->fmt_out, &p_filter->fmt_in );
191     i_ret = CreateChain( p_filter, &fmt_mid, NULL );
192     es_format_Clean( &fmt_mid );
193     if( i_ret == VLC_SUCCESS )
194         return VLC_SUCCESS;
195
196     return VLC_EGENERIC;
197 }
198
199 static int BuildChromaResize( filter_t *p_filter )
200 {
201     es_format_t fmt_mid;
202     int i_ret;
203
204     /* Lets try resizing and then doing the chroma conversion */
205     msg_Dbg( p_filter, "Trying to build resize+chroma" );
206     EsFormatMergeSize( &fmt_mid, &p_filter->fmt_in, &p_filter->fmt_out );
207     i_ret = CreateChain( p_filter, &fmt_mid, NULL );
208     es_format_Clean( &fmt_mid );
209     if( i_ret == VLC_SUCCESS )
210         return VLC_SUCCESS;
211
212     /* Lets try it the other way arround (chroma and then resize) */
213     msg_Dbg( p_filter, "Trying to build chroma+resize" );
214     EsFormatMergeSize( &fmt_mid, &p_filter->fmt_out, &p_filter->fmt_in );
215     i_ret = CreateChain( p_filter, &fmt_mid, NULL );
216     es_format_Clean( &fmt_mid );
217     if( i_ret == VLC_SUCCESS )
218         return VLC_SUCCESS;
219
220     return VLC_EGENERIC;
221 }
222
223 static int BuildChromaChain( filter_t *p_filter )
224 {
225     es_format_t fmt_mid;
226
227     /* We have to protect ourself against a too high recursion */
228     const char *psz_option = MODULE_STRING"-level";
229     int i_level = 0;
230     for( const config_chain_t *c = p_filter->p_cfg; c != NULL; c = c->p_next)
231     {
232         if( c->psz_name && c->psz_value && !strcmp(c->psz_name, psz_option) )
233         {
234             i_level = atoi(c->psz_value);
235             if( i_level < 0 || i_level > CHAIN_LEVEL_MAX )
236             {
237                 msg_Err( p_filter, "Too high level of recursion (%d)", i_level );
238                 return VLC_EGENERIC;
239             }
240             break;
241         }
242     }
243
244     /* */
245     int i_ret = VLC_EGENERIC;
246
247     /* */
248     config_chain_t cfg_level;
249     memset(&cfg_level, 0, sizeof(cfg_level));
250     cfg_level.psz_name = strdup(psz_option);
251     if( asprintf( &cfg_level.psz_value, "%d", i_level + 1) < 0 )
252         cfg_level.psz_value = NULL;
253     if( !cfg_level.psz_name || !cfg_level.psz_value )
254         goto exit;
255
256     /* Now try chroma format list */
257     for( int i = 0; pi_allowed_chromas[i]; i++ )
258     {
259         const vlc_fourcc_t i_chroma = pi_allowed_chromas[i];
260         if( i_chroma == p_filter->fmt_in.i_codec ||
261             i_chroma == p_filter->fmt_out.i_codec )
262             continue;
263
264         msg_Dbg( p_filter, "Trying to use chroma %4.4s as middle man",
265                  (char*)&i_chroma );
266
267         es_format_Copy( &fmt_mid, &p_filter->fmt_in );
268         fmt_mid.i_codec        =
269         fmt_mid.video.i_chroma = i_chroma;
270         fmt_mid.video.i_rmask  = 0;
271         fmt_mid.video.i_gmask  = 0;
272         fmt_mid.video.i_bmask  = 0;
273         video_format_FixRgb(&fmt_mid.video);
274
275         i_ret = CreateChain( p_filter, &fmt_mid, &cfg_level );
276         es_format_Clean( &fmt_mid );
277
278         if( i_ret == VLC_SUCCESS )
279             break;
280     }
281
282 exit:
283     free( cfg_level.psz_name );
284     free( cfg_level.psz_value );
285     return i_ret;
286 }
287
288 /*****************************************************************************
289  *
290  *****************************************************************************/
291 static int CreateChain( filter_t *p_parent, es_format_t *p_fmt_mid, config_chain_t *p_cfg )
292 {
293     filter_chain_Reset( p_parent->p_sys->p_chain, &p_parent->fmt_in, &p_parent->fmt_out );
294
295     filter_t *p_filter;
296
297     if( p_parent->fmt_in.video.orientation != p_fmt_mid->video.orientation)
298     {
299         p_filter = AppendTransform( p_parent->p_sys->p_chain, &p_parent->fmt_in, p_fmt_mid );
300     }
301     else
302     {
303         p_filter = filter_chain_AppendFilter( p_parent->p_sys->p_chain, NULL, p_cfg, NULL, p_fmt_mid );
304     }
305
306     if( !p_filter )
307         return VLC_EGENERIC;
308
309     //Check if first filter was enough (transform filter most likely):
310     if( es_format_IsSimilar(&p_filter->fmt_out, &p_parent->fmt_out ))
311        return VLC_SUCCESS;
312
313     if( p_fmt_mid->video.orientation != p_parent->fmt_out.video.orientation)
314     {
315         p_filter = AppendTransform( p_parent->p_sys->p_chain, p_fmt_mid, &p_parent->fmt_out );
316     }
317     else
318     {
319         p_filter = filter_chain_AppendFilter( p_parent->p_sys->p_chain, NULL, p_cfg, p_fmt_mid, NULL );
320     }
321
322     if( !p_filter )
323     {
324         //Clean up.
325         filter_chain_Reset( p_parent->p_sys->p_chain, NULL, NULL );
326         return VLC_EGENERIC;
327     }
328
329     return VLC_SUCCESS;
330 }
331
332 static filter_t * AppendTransform( filter_chain_t *p_chain, es_format_t *p_fmt1, es_format_t *p_fmt2 )
333 {
334     video_transform_t transform = video_format_GetTransform(p_fmt1->video.orientation, p_fmt2->video.orientation);
335
336     const char *type;
337
338     switch ( transform ) {
339
340         case TRANSFORM_R90:
341             type = "90";
342             break;
343         case TRANSFORM_R180:
344             type = "180";
345             break;
346         case TRANSFORM_R270:
347             type = "270";
348             break;
349         case TRANSFORM_HFLIP:
350             type = "hflip";
351             break;
352         case TRANSFORM_VFLIP:
353             type = "vflip";
354             break;
355         case TRANSFORM_TRANSPOSE:
356             type = "transpose";
357             break;
358         case TRANSFORM_ANTI_TRANSPOSE:
359             type = "antitranspose";
360             break;
361         default:
362             type = NULL;
363             break;
364     }
365
366     if( !type )
367         return NULL;
368
369     config_chain_t *cfg;
370     char *name;
371     char config[100];
372     snprintf( config, 100, "transform{type=%s}", type );
373     char *next = config_ChainCreate( &name, &cfg, config );
374
375     filter_t *p_filter = filter_chain_AppendFilter( p_chain, name, cfg, p_fmt1, p_fmt2 );
376
377     config_ChainDestroy(cfg);
378     free(name);
379     free(next);
380
381     return p_filter;
382 }
383
384 static void EsFormatMergeSize( es_format_t *p_dst,
385                                const es_format_t *p_base,
386                                const es_format_t *p_size )
387 {
388     es_format_Copy( p_dst, p_base );
389
390     p_dst->video.i_width  = p_size->video.i_width;
391     p_dst->video.i_height = p_size->video.i_height;
392
393     p_dst->video.i_visible_width  = p_size->video.i_visible_width;
394     p_dst->video.i_visible_height = p_size->video.i_visible_height;
395
396     p_dst->video.i_x_offset = p_size->video.i_x_offset;
397     p_dst->video.i_y_offset = p_size->video.i_y_offset;
398
399     p_dst->video.orientation = p_size->video.orientation;
400 }
401