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