]> git.sesse.net Git - vlc/blob - src/misc/filter_chain.c
0805d40289005f0e5439ec94e85c8e7594a9aed9
[vlc] / src / misc / filter_chain.c
1 /*****************************************************************************
2  * filter_chain.c : Handle chains of filter_t objects.
3  *****************************************************************************
4  * Copyright (C) 2008 the VideoLAN team
5  * $Id$
6  *
7  * Author: Antoine Cellerier <dionoea at videolan dot org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 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 General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <vlc_filter.h>
29 #include <vlc_arrays.h>
30 #include <vlc_osd.h>
31 #include <libvlc.h>
32
33 typedef struct
34 {
35     int (*pf_init)( filter_t *, void *p_data ); /* Callback called once filter allocation has succeeded to initialize the filter's buffer allocation callbacks. This function is responsible for setting p_owner if needed. */
36     void (* pf_clean)( filter_t * ); /* Callback called on filter removal from chain to clean up buffer allocation callbacks data (ie p_owner) */
37     void *p_data; /* Data for pf_buffer_allocation_init */
38
39 } filter_chain_allocator_t;
40
41 static int  AllocatorInit( const filter_chain_allocator_t *, filter_t * );
42 static void AllocatorClean( const filter_chain_allocator_t *, filter_t * );
43
44 static bool IsInternalVideoAllocator( filter_t * );
45
46 static int  InternalVideoInit( filter_t *, void * );
47 static void InternalVideoClean( filter_t * );
48
49 static const filter_chain_allocator_t internal_video_allocator = {
50     .pf_init = InternalVideoInit,
51     .pf_clean = InternalVideoClean,
52     .p_data = NULL,
53 };
54
55 /* */
56 struct filter_chain_t
57 {
58     /* Parent object */
59     vlc_object_t *p_this;
60
61     /* List of filters */
62     vlc_array_t filters;
63
64     /* Capability of all the filters in the chain */
65     char *psz_capability;
66
67     /* Input format (read only) */
68     es_format_t fmt_in;
69
70     /* allow changing fmt_out if true */
71     bool b_allow_fmt_out_change;
72
73     /* Output format (writable depending on ... */
74     es_format_t fmt_out;
75
76     /* User provided allocator */
77     filter_chain_allocator_t allocator;
78 };
79
80 /**
81  * Local prototypes
82  */
83 static filter_t *filter_chain_AppendFilterInternal( filter_chain_t *,
84                                                     const char *, config_chain_t *,
85                                                     const es_format_t *, const es_format_t * );
86
87 static int filter_chain_AppendFromStringInternal( filter_chain_t *, const char * );
88
89 static int filter_chain_DeleteFilterInternal( filter_chain_t *, filter_t * );
90
91 static int UpdateBufferFunctions( filter_chain_t * );
92
93 /**
94  * Filter chain initialisation
95  */
96 filter_chain_t *__filter_chain_New( vlc_object_t *p_this,
97                                     const char *psz_capability,
98                                     bool b_allow_fmt_out_change,
99                                     int  (*pf_buffer_allocation_init)( filter_t *, void * ),
100                                     void (*pf_buffer_allocation_clean)( filter_t * ),
101                                     void *p_buffer_allocation_data )
102 {
103     filter_chain_t *p_chain = malloc( sizeof( *p_chain ) );
104     if( !p_chain )
105         return NULL;
106
107     p_chain->p_this = p_this;
108     vlc_array_init( &p_chain->filters );
109     p_chain->psz_capability = strdup( psz_capability );
110     if( !p_chain->psz_capability )
111     {
112         vlc_array_clear( &p_chain->filters );
113         free( p_chain );
114         return NULL;
115     }
116     es_format_Init( &p_chain->fmt_in, UNKNOWN_ES, 0 );
117     es_format_Init( &p_chain->fmt_out, UNKNOWN_ES, 0 );
118     p_chain->b_allow_fmt_out_change = b_allow_fmt_out_change;
119
120     p_chain->allocator.pf_init = pf_buffer_allocation_init;
121     p_chain->allocator.pf_clean = pf_buffer_allocation_clean;
122     p_chain->allocator.p_data = p_buffer_allocation_data;
123
124     return p_chain;
125 }
126
127 /**
128  * Filter chain destruction
129  */
130 void filter_chain_Delete( filter_chain_t *p_chain )
131 {
132     filter_chain_Reset( p_chain, NULL, NULL );
133
134     es_format_Clean( &p_chain->fmt_in );
135     es_format_Clean( &p_chain->fmt_out );
136
137     free( p_chain->psz_capability );
138     vlc_array_clear( &p_chain->filters );
139     free( p_chain );
140 }
141 /**
142  * Filter chain reinitialisation
143  */
144 void filter_chain_Reset( filter_chain_t *p_chain, const es_format_t *p_fmt_in,
145                          const es_format_t *p_fmt_out )
146 {
147     while( vlc_array_count( &p_chain->filters ) > 0 )
148     {
149         filter_t *p_filter = vlc_array_item_at_index( &p_chain->filters, 0 );
150
151         filter_chain_DeleteFilterInternal( p_chain, p_filter );
152     }
153     if( p_fmt_in )
154     {
155         es_format_Clean( &p_chain->fmt_in );
156         es_format_Copy( &p_chain->fmt_in, p_fmt_in );
157     }
158     if( p_fmt_out )
159     {
160         es_format_Clean( &p_chain->fmt_out );
161         es_format_Copy( &p_chain->fmt_out, p_fmt_out );
162     }
163 }
164
165 filter_t *filter_chain_AppendFilter( filter_chain_t *p_chain,
166                                      const char *psz_name,
167                                      config_chain_t *p_cfg,
168                                      const es_format_t *p_fmt_in,
169                                      const es_format_t *p_fmt_out )
170 {
171     filter_t *p_filter = filter_chain_AppendFilterInternal( p_chain, psz_name,
172                                                             p_cfg, p_fmt_in,
173                                                             p_fmt_out );
174     if( UpdateBufferFunctions( p_chain ) < 0 )
175         msg_Err( p_filter, "Woah! This doesn't look good." );
176     return p_filter;
177 }
178
179 int filter_chain_AppendFromString( filter_chain_t *p_chain,
180                                    const char *psz_string )
181 {
182     const int i_ret = filter_chain_AppendFromStringInternal( p_chain, psz_string );
183     if( i_ret < 0 )
184         return i_ret;
185
186     /* FIXME That one seems bad if a error is returned */
187     return UpdateBufferFunctions( p_chain );
188 }
189
190 int filter_chain_DeleteFilter( filter_chain_t *p_chain, filter_t *p_filter )
191 {
192     const int i_ret = filter_chain_DeleteFilterInternal( p_chain, p_filter );
193     if( i_ret < 0 )
194         return i_ret;
195
196     /* FIXME That one seems bad if a error is returned */
197     return UpdateBufferFunctions( p_chain );
198 }
199
200 /**
201  * Reading from the filter chain
202  */
203 filter_t *filter_chain_GetFilter( filter_chain_t *p_chain, int i_position,
204                                   const char *psz_name )
205 {
206     if( psz_name )
207     {
208         if( i_position < 0 )
209             return NULL;
210
211         for( int i = 0; i < vlc_array_count( &p_chain->filters ); i++ )
212         {
213             filter_t *p_filter = vlc_array_item_at_index( &p_chain->filters, i );
214             if( !strcmp( p_filter->psz_object_name, psz_name ) )
215             {
216                 if( i_position <= 0 )
217                     return p_filter;
218                 i_position--;
219             }
220        }
221         return NULL;
222     }
223     else
224     {
225         if( i_position < 0 || i_position >= vlc_array_count( &p_chain->filters ) )
226             return NULL;
227         return vlc_array_item_at_index( &p_chain->filters, i_position );
228     }
229 }
230
231 int filter_chain_GetLength( filter_chain_t *p_chain )
232 {
233     return vlc_array_count( &p_chain->filters );
234 }
235
236 const es_format_t *filter_chain_GetFmtOut( filter_chain_t *p_chain )
237 {
238
239     if( p_chain->b_allow_fmt_out_change )
240         return &p_chain->fmt_out;
241
242     const int i_count = vlc_array_count( &p_chain->filters );
243     if( i_count > 0 )
244     {
245         filter_t *p_last = vlc_array_item_at_index( &p_chain->filters, i_count - 1 );
246         return &p_last->fmt_out;
247     }
248
249     /* Unless filter_chain_Reset has been called we are doomed */
250     return &p_chain->fmt_out;
251 }
252
253 picture_t *filter_chain_VideoFilter( filter_chain_t *p_chain, picture_t *p_pic )
254 {
255     for( int i = 0; i < vlc_array_count( &p_chain->filters ); i++ )
256     {
257         filter_t *p_filter = vlc_array_item_at_index( &p_chain->filters, i );
258
259         p_pic = p_filter->pf_video_filter( p_filter, p_pic );
260         if( !p_pic )
261             break;
262     }
263     return p_pic;
264 }
265
266 block_t *filter_chain_AudioFilter( filter_chain_t *p_chain, block_t *p_block )
267 {
268     for( int i = 0; i < vlc_array_count( &p_chain->filters ); i++ )
269     {
270         filter_t *p_filter = vlc_array_item_at_index( &p_chain->filters, i );
271
272         p_block = p_filter->pf_audio_filter( p_filter, p_block );
273         if( !p_block )
274             break;
275     }
276     return p_block;
277 }
278
279 void filter_chain_SubFilter( filter_chain_t *p_chain,
280                              mtime_t display_date )
281 {
282     for( int i = 0; i < vlc_array_count( &p_chain->filters ); i++ )
283     {
284         filter_t *p_filter = vlc_array_item_at_index( &p_chain->filters, i );
285
286         subpicture_t *p_subpic = p_filter->pf_sub_filter( p_filter, display_date );
287         /* XXX I find that spu_t cast ugly */
288         if( p_subpic )
289             spu_DisplaySubpicture( (spu_t*)p_chain->p_this, p_subpic );
290     }
291 }
292
293
294 /* Helpers */
295 static filter_t *filter_chain_AppendFilterInternal( filter_chain_t *p_chain,
296                                                     const char *psz_name,
297                                                     config_chain_t *p_cfg,
298                                                     const es_format_t *p_fmt_in,
299                                                     const es_format_t *p_fmt_out )
300 {
301     filter_t *p_filter = vlc_custom_create( p_chain->p_this, sizeof(*p_filter),
302                                             VLC_OBJECT_GENERIC, "filter" );
303     if( !p_filter )
304         return NULL;
305     vlc_object_attach( p_filter, p_chain->p_this );
306
307     if( !p_fmt_in )
308     {
309         p_fmt_in = &p_chain->fmt_in;
310
311         const int i_count = vlc_array_count( &p_chain->filters );
312         if( i_count > 0 )
313         {
314             filter_t *p_last = vlc_array_item_at_index( &p_chain->filters, i_count - 1 );
315             p_fmt_in = &p_last->fmt_out;
316         }
317     }
318
319     if( !p_fmt_out )
320     {
321         p_fmt_out = &p_chain->fmt_out;
322     }
323
324     es_format_Copy( &p_filter->fmt_in, p_fmt_in );
325     es_format_Copy( &p_filter->fmt_out, p_fmt_out );
326     p_filter->p_cfg = p_cfg;
327     p_filter->b_allow_fmt_out_change = p_chain->b_allow_fmt_out_change;
328
329     p_filter->p_module = module_need( p_filter, p_chain->psz_capability,
330                                       psz_name, psz_name != NULL );
331
332     if( !p_filter->p_module )
333         goto error;
334
335     if( p_filter->b_allow_fmt_out_change )
336     {
337         es_format_Clean( &p_chain->fmt_out );
338         es_format_Copy( &p_chain->fmt_out, &p_filter->fmt_out );
339     }
340
341     if( AllocatorInit( &p_chain->allocator, p_filter ) )
342         goto error;
343
344     vlc_array_append( &p_chain->filters, p_filter );
345
346     msg_Dbg( p_chain->p_this, "Filter '%s' (%p) appended to chain",
347              psz_name?:p_filter->psz_object_name, p_filter );
348
349     return p_filter;
350
351 error:
352     if( psz_name )
353         msg_Err( p_chain->p_this, "Failed to create %s '%s'",
354                  p_chain->psz_capability, psz_name );
355     else
356         msg_Err( p_chain->p_this, "Failed to create %s",
357                  p_chain->psz_capability );
358     if( p_filter->p_module )
359         module_unneed( p_filter, p_filter->p_module );
360     es_format_Clean( &p_filter->fmt_in );
361     es_format_Clean( &p_filter->fmt_out );
362     vlc_object_detach( p_filter );
363     vlc_object_release( p_filter );
364     return NULL;
365 }
366
367
368 static int filter_chain_AppendFromStringInternal( filter_chain_t *p_chain,
369                                                   const char *psz_string )
370 {
371     config_chain_t *p_cfg = NULL;
372     char *psz_name = NULL;
373     char* psz_new_string;
374
375     if( !psz_string || !*psz_string )
376         return 0;
377
378     psz_new_string = config_ChainCreate( &psz_name, &p_cfg, psz_string );
379
380     filter_t *p_filter = filter_chain_AppendFilterInternal( p_chain, psz_name,
381                                                             p_cfg, NULL, NULL );
382     if( !p_filter )
383     {
384         msg_Err( p_chain->p_this, "Failed while trying to append '%s' "
385                  "to filter chain", psz_name );
386         free( psz_name );
387         free( p_cfg );
388         free( psz_new_string );
389         return -1;
390     }
391     free( psz_name );
392
393     const int i_ret = filter_chain_AppendFromStringInternal( p_chain, psz_new_string );
394     free( psz_new_string );
395     if( i_ret < 0 )
396     {
397         filter_chain_DeleteFilterInternal( p_chain, p_filter );
398         return i_ret;
399     }
400     return 1 + i_ret;
401 }
402
403 static int filter_chain_DeleteFilterInternal( filter_chain_t *p_chain,
404                                               filter_t *p_filter )
405 {
406     const int i_filter_idx = vlc_array_index_of_item( &p_chain->filters, p_filter );
407     if( i_filter_idx < 0 )
408     {
409         /* Oops, filter wasn't found
410          * FIXME shoulnd't it be an assert instead ? */
411         msg_Err( p_chain->p_this,
412                  "Couldn't find filter '%s' (%p) when trying to remove it from chain",
413                  p_filter->psz_object_name, p_filter );
414         return VLC_EGENERIC;
415     }
416
417     /* Remove it from the chain */
418     vlc_array_remove( &p_chain->filters, i_filter_idx );
419     msg_Dbg( p_chain->p_this, "Filter '%s' (%p) removed from chain",
420              p_filter->psz_object_name, p_filter );
421
422     /* Destroy the filter object */
423     AllocatorClean( &p_chain->allocator, p_filter );
424
425     vlc_object_detach( p_filter );
426     if( p_filter->p_module )
427         module_unneed( p_filter, p_filter->p_module );
428     vlc_object_release( p_filter );
429
430     /* FIXME: check fmt_in/fmt_out consitency */
431     return VLC_SUCCESS;
432 }
433
434 /**
435  * Internal chain buffer handling
436  */
437
438 static int UpdateVideoBufferFunctions( filter_chain_t *p_chain )
439 {
440     /**
441      * Last filter uses the filter chain's parent buffer allocation
442      * functions. All the other filters use internal functions.
443      * This makes it possible to have format changes between each
444      * filter without having to worry about the parent's picture
445      * heap format.
446      */
447     const int i_count = vlc_array_count( &p_chain->filters );
448     for( int i = 0; i < i_count - 1; i++ )
449     {
450         filter_t *p_filter = vlc_array_item_at_index( &p_chain->filters, i );
451
452         if( !IsInternalVideoAllocator( p_filter ) )
453         {
454             AllocatorClean( &p_chain->allocator, p_filter );
455
456             AllocatorInit( &internal_video_allocator, p_filter );
457         }
458     }
459     if( i_count >= 1 )
460     {
461         filter_t * p_filter = vlc_array_item_at_index( &p_chain->filters, i_count - 1 );
462         if( IsInternalVideoAllocator( p_filter ) )
463         {
464             AllocatorClean( &internal_video_allocator, p_filter );
465
466             if( AllocatorInit( &p_chain->allocator, p_filter ) )
467                 return VLC_EGENERIC;
468         }
469     }
470     return VLC_SUCCESS;
471 }
472 /**
473  * This function should be called after every filter chain change
474  */
475 static int UpdateBufferFunctions( filter_chain_t *p_chain )
476 {
477     if( !strcmp( p_chain->psz_capability, "video filter2" ) )
478         return UpdateVideoBufferFunctions( p_chain );
479
480     return VLC_SUCCESS;
481 }
482
483 /* Internal video allocator functions */
484 static picture_t *VideoBufferNew( filter_t *p_filter )
485 {
486     const video_format_t *p_fmt = &p_filter->fmt_out.video;
487
488     picture_t *p_picture = picture_New( p_fmt->i_chroma,
489                                         p_fmt->i_width, p_fmt->i_height,
490                                         p_fmt->i_aspect );
491     if( !p_picture )
492         msg_Err( p_filter, "Failed to allocate picture" );
493     return p_picture;
494 }
495 static void VideoBufferDelete( filter_t *p_filter, picture_t *p_picture )
496 {
497     VLC_UNUSED( p_filter );
498     picture_Release( p_picture );
499 }
500 static int InternalVideoInit( filter_t *p_filter, void *p_data )
501 {
502     VLC_UNUSED(p_data);
503
504     p_filter->pf_vout_buffer_new = VideoBufferNew;
505     p_filter->pf_vout_buffer_del = VideoBufferDelete;
506
507     return VLC_SUCCESS;
508 }
509 static void InternalVideoClean( filter_t *p_filter )
510 {
511     p_filter->pf_vout_buffer_new = NULL;
512     p_filter->pf_vout_buffer_del = NULL;
513 }
514 static bool IsInternalVideoAllocator( filter_t *p_filter )
515 {
516     return p_filter->pf_vout_buffer_new == VideoBufferNew;
517 }
518
519 /* */
520 static int AllocatorInit( const filter_chain_allocator_t *p_alloc, filter_t *p_filter )
521 {
522     if( p_alloc->pf_init )
523         return p_alloc->pf_init( p_filter, p_alloc->p_data );
524     return VLC_SUCCESS;
525 }
526 static void AllocatorClean( const filter_chain_allocator_t *p_alloc, filter_t *p_filter )
527 {
528     if( p_alloc->pf_clean )
529         p_alloc->pf_clean( p_filter );
530 }
531