]> git.sesse.net Git - vlc/blob - src/misc/filter_chain.c
Remove CPP contradiction from [5a005c66]
[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_osd.h>
30 #include <vlc_modules.h>
31 #include <libvlc.h>
32 #include <assert.h>
33
34 typedef struct
35 {
36     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. */
37     void (* pf_clean)( filter_t * ); /* Callback called on filter removal from chain to clean up buffer allocation callbacks data (ie p_owner) */
38     void *p_data; /* Data for pf_buffer_allocation_init */
39
40 } filter_chain_allocator_t;
41
42 typedef struct chained_filter_t
43 {
44     /* Public part of the filter structure */
45     filter_t filter;
46     /* Private filter chain data (shhhh!) */
47     struct chained_filter_t *prev, *next;
48     vlc_mouse_t *mouse;
49 } chained_filter_t;
50
51 /* Only use this with filter objects from _this_ C module */
52 static inline chained_filter_t *chained (filter_t *filter)
53 {
54     return (chained_filter_t *)filter;
55 }
56
57 static int  AllocatorInit( const filter_chain_allocator_t *,
58                            chained_filter_t * );
59 static void AllocatorClean( const filter_chain_allocator_t *,
60                             chained_filter_t * );
61
62 static bool IsInternalVideoAllocator( chained_filter_t * );
63
64 static int  InternalVideoInit( filter_t *, void * );
65 static void InternalVideoClean( filter_t * );
66
67 static const filter_chain_allocator_t internal_video_allocator = {
68     .pf_init = InternalVideoInit,
69     .pf_clean = InternalVideoClean,
70     .p_data = NULL,
71 };
72
73 /* */
74 struct filter_chain_t
75 {
76     vlc_object_t *p_this; /**< Owner object */
77     filter_chain_allocator_t allocator; /**< Owner allocation callbacks */
78
79     chained_filter_t *first, *last; /**< List of filters */
80
81     es_format_t fmt_in; /**< Chain input format (constant) */
82     es_format_t fmt_out; /**< Chain current output format */
83     unsigned length; /**< Number of filters */
84     bool b_allow_fmt_out_change; /**< Can the output format be changed? */
85     char psz_capability[1]; /**< Module capability for all chained filters */
86 };
87
88 /**
89  * Local prototypes
90  */
91 static filter_t *filter_chain_AppendFilterInternal( filter_chain_t *,
92                                                     const char *, config_chain_t *,
93                                                     const es_format_t *, const es_format_t * );
94
95 static int filter_chain_AppendFromStringInternal( filter_chain_t *, const char * );
96
97 static int filter_chain_DeleteFilterInternal( filter_chain_t *, filter_t * );
98
99 static int UpdateBufferFunctions( filter_chain_t * );
100
101 #undef filter_chain_New
102 /**
103  * Filter chain initialisation
104  */
105 filter_chain_t *filter_chain_New( vlc_object_t *p_this,
106                                   const char *psz_capability,
107                                   bool b_allow_fmt_out_change,
108                                   int  (*pf_buffer_allocation_init)( filter_t *, void * ),
109                                   void (*pf_buffer_allocation_clean)( filter_t * ),
110                                   void *p_buffer_allocation_data )
111 {
112     assert( p_this );
113     assert( psz_capability );
114
115     size_t size = sizeof(filter_chain_t) + strlen(psz_capability);
116     filter_chain_t *p_chain = malloc( size );
117     if( !p_chain )
118         return NULL;
119
120     p_chain->p_this = p_this;
121     p_chain->last = p_chain->first = NULL;
122     p_chain->length = 0;
123     strcpy( p_chain->psz_capability, psz_capability );
124
125     es_format_Init( &p_chain->fmt_in, UNKNOWN_ES, 0 );
126     es_format_Init( &p_chain->fmt_out, UNKNOWN_ES, 0 );
127     p_chain->b_allow_fmt_out_change = b_allow_fmt_out_change;
128
129     p_chain->allocator.pf_init = pf_buffer_allocation_init;
130     p_chain->allocator.pf_clean = pf_buffer_allocation_clean;
131     p_chain->allocator.p_data = p_buffer_allocation_data;
132
133     return p_chain;
134 }
135
136 /**
137  * Filter chain destruction
138  */
139 void filter_chain_Delete( filter_chain_t *p_chain )
140 {
141     filter_chain_Reset( p_chain, NULL, NULL );
142
143     es_format_Clean( &p_chain->fmt_in );
144     es_format_Clean( &p_chain->fmt_out );
145
146     free( p_chain );
147 }
148 /**
149  * Filter chain reinitialisation
150  */
151 void filter_chain_Reset( filter_chain_t *p_chain, const es_format_t *p_fmt_in,
152                          const es_format_t *p_fmt_out )
153 {
154     filter_t *p_filter;
155
156     while( (p_filter = &p_chain->first->filter) != NULL )
157         filter_chain_DeleteFilterInternal( p_chain, p_filter );
158
159     if( p_fmt_in )
160     {
161         es_format_Clean( &p_chain->fmt_in );
162         es_format_Copy( &p_chain->fmt_in, p_fmt_in );
163     }
164     if( p_fmt_out )
165     {
166         es_format_Clean( &p_chain->fmt_out );
167         es_format_Copy( &p_chain->fmt_out, p_fmt_out );
168     }
169 }
170
171 filter_t *filter_chain_AppendFilter( filter_chain_t *p_chain,
172                                      const char *psz_name,
173                                      config_chain_t *p_cfg,
174                                      const es_format_t *p_fmt_in,
175                                      const es_format_t *p_fmt_out )
176 {
177     filter_t *p_filter = filter_chain_AppendFilterInternal( p_chain, psz_name,
178                                                             p_cfg, p_fmt_in,
179                                                             p_fmt_out );
180     if( UpdateBufferFunctions( p_chain ) < 0 )
181         msg_Err( p_filter, "Woah! This doesn't look good." );
182     return p_filter;
183 }
184
185 int filter_chain_AppendFromString( filter_chain_t *p_chain,
186                                    const char *psz_string )
187 {
188     const int i_ret = filter_chain_AppendFromStringInternal( p_chain, psz_string );
189     if( i_ret < 0 )
190         return i_ret;
191
192     /* FIXME That one seems bad if a error is returned */
193     return UpdateBufferFunctions( p_chain );
194 }
195
196 int filter_chain_DeleteFilter( filter_chain_t *p_chain, filter_t *p_filter )
197 {
198     const int i_ret = filter_chain_DeleteFilterInternal( p_chain, p_filter );
199     if( i_ret < 0 )
200         return i_ret;
201
202     /* FIXME That one seems bad if a error is returned */
203     return UpdateBufferFunctions( p_chain );
204 }
205
206 int filter_chain_GetLength( filter_chain_t *p_chain )
207 {
208     return p_chain->length;
209 }
210
211 const es_format_t *filter_chain_GetFmtOut( filter_chain_t *p_chain )
212 {
213
214     if( p_chain->b_allow_fmt_out_change )
215         return &p_chain->fmt_out;
216
217     if( p_chain->last != NULL )
218         return &p_chain->last->filter.fmt_out;
219
220     /* Unless filter_chain_Reset has been called we are doomed */
221     return &p_chain->fmt_out;
222 }
223
224 picture_t *filter_chain_VideoFilter( filter_chain_t *p_chain, picture_t *p_pic )
225 {
226     for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
227     {
228         filter_t *p_filter = &f->filter;
229
230         p_pic = p_filter->pf_video_filter( p_filter, p_pic );
231         if( !p_pic )
232             break;
233     }
234     return p_pic;
235 }
236
237 void filter_chain_VideoFlush( filter_chain_t *p_chain )
238 {
239     for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
240     {
241         filter_t *p_filter = &f->filter;
242
243         filter_FlushPictures( p_filter );
244     }
245 }
246
247
248 block_t *filter_chain_AudioFilter( filter_chain_t *p_chain, block_t *p_block )
249 {
250     for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
251     {
252         filter_t *p_filter = &f->filter;
253
254         p_block = p_filter->pf_audio_filter( p_filter, p_block );
255         if( !p_block )
256             break;
257     }
258     return p_block;
259 }
260
261 void filter_chain_SubFilter( filter_chain_t *p_chain,
262                              mtime_t display_date )
263 {
264     for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
265     {
266         filter_t *p_filter = &f->filter;
267         subpicture_t *p_subpic = p_filter->pf_sub_filter( p_filter, display_date );
268         /* XXX I find that spu_t cast ugly */
269         if( p_subpic )
270             spu_DisplaySubpicture( (spu_t*)p_chain->p_this, p_subpic );
271     }
272 }
273
274 int filter_chain_MouseFilter( filter_chain_t *p_chain, vlc_mouse_t *p_dst, const vlc_mouse_t *p_src )
275 {
276     vlc_mouse_t current = *p_src;
277
278     for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
279     {
280         filter_t *p_filter = &f->filter;
281         vlc_mouse_t *p_mouse = f->mouse;
282
283         if( p_filter->pf_video_mouse && p_mouse )
284         {
285             vlc_mouse_t old = *p_mouse;
286             vlc_mouse_t filtered;
287
288             *p_mouse = current;
289             if( p_filter->pf_video_mouse( p_filter, &filtered, &old, &current ) )
290                 return VLC_EGENERIC;
291             current = filtered;
292         }
293     }
294
295     *p_dst = current;
296     return VLC_SUCCESS;
297 }
298
299 int filter_chain_MouseEvent( filter_chain_t *p_chain,
300                              const vlc_mouse_t *p_mouse,
301                              const video_format_t *p_fmt )
302 {
303     for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
304     {
305         filter_t *p_filter = &f->filter;
306
307         if( p_filter->pf_sub_mouse )
308         {
309             vlc_mouse_t old = *f->mouse;
310             *f->mouse = *p_mouse;
311             if( p_filter->pf_sub_mouse( p_filter, &old, p_mouse, p_fmt ) )
312                 return VLC_EGENERIC;
313         }
314     }
315
316     return VLC_SUCCESS;
317 }
318
319 /* Helpers */
320 static filter_t *filter_chain_AppendFilterInternal( filter_chain_t *p_chain,
321                                                     const char *psz_name,
322                                                     config_chain_t *p_cfg,
323                                                     const es_format_t *p_fmt_in,
324                                                     const es_format_t *p_fmt_out )
325 {
326     chained_filter_t *p_chained =
327         vlc_custom_create( p_chain->p_this, sizeof(*p_chained),
328                            VLC_OBJECT_GENERIC, "filter" );
329     filter_t *p_filter = &p_chained->filter;
330     if( !p_filter )
331         return NULL;
332     vlc_object_attach( p_filter, p_chain->p_this );
333
334     if( !p_fmt_in )
335     {
336         if( p_chain->last != NULL )
337             p_fmt_in = &p_chain->last->filter.fmt_out;
338         else
339             p_fmt_in = &p_chain->fmt_in;
340     }
341
342     if( !p_fmt_out )
343     {
344         p_fmt_out = &p_chain->fmt_out;
345     }
346
347     es_format_Copy( &p_filter->fmt_in, p_fmt_in );
348     es_format_Copy( &p_filter->fmt_out, p_fmt_out );
349     p_filter->p_cfg = p_cfg;
350     p_filter->b_allow_fmt_out_change = p_chain->b_allow_fmt_out_change;
351
352     p_filter->p_module = module_need( p_filter, p_chain->psz_capability,
353                                       psz_name, psz_name != NULL );
354
355     if( !p_filter->p_module )
356         goto error;
357
358     if( p_filter->b_allow_fmt_out_change )
359     {
360         es_format_Clean( &p_chain->fmt_out );
361         es_format_Copy( &p_chain->fmt_out, &p_filter->fmt_out );
362     }
363
364     if( AllocatorInit( &p_chain->allocator, p_chained ) )
365         goto error;
366
367     if( p_chain->last == NULL )
368     {
369         assert( p_chain->first == NULL );
370         p_chain->first = p_chained;
371     }
372     else
373         p_chain->last->next = p_chained;
374     p_chained->prev = p_chain->last;
375     p_chain->last = p_chained;
376     p_chained->next = NULL;
377     p_chain->length++;
378
379     vlc_mouse_t *p_mouse = malloc( sizeof(*p_mouse) );
380     if( p_mouse )
381         vlc_mouse_Init( p_mouse );
382     p_chained->mouse = p_mouse;
383
384     msg_Dbg( p_chain->p_this, "Filter '%s' (%p) appended to chain",
385              psz_name ? psz_name : module_get_name(p_filter->p_module, false),
386              p_filter );
387
388     return p_filter;
389
390 error:
391     if( psz_name )
392         msg_Err( p_chain->p_this, "Failed to create %s '%s'",
393                  p_chain->psz_capability, psz_name );
394     else
395         msg_Err( p_chain->p_this, "Failed to create %s",
396                  p_chain->psz_capability );
397     if( p_filter->p_module )
398         module_unneed( p_filter, p_filter->p_module );
399     es_format_Clean( &p_filter->fmt_in );
400     es_format_Clean( &p_filter->fmt_out );
401     vlc_object_release( p_filter );
402     return NULL;
403 }
404
405
406 static int filter_chain_AppendFromStringInternal( filter_chain_t *p_chain,
407                                                   const char *psz_string )
408 {
409     config_chain_t *p_cfg = NULL;
410     char *psz_name = NULL;
411     char* psz_new_string;
412
413     if( !psz_string || !*psz_string )
414         return 0;
415
416     psz_new_string = config_ChainCreate( &psz_name, &p_cfg, psz_string );
417
418     filter_t *p_filter = filter_chain_AppendFilterInternal( p_chain, psz_name,
419                                                             p_cfg, NULL, NULL );
420     if( !p_filter )
421     {
422         msg_Err( p_chain->p_this, "Failed while trying to append '%s' "
423                  "to filter chain", psz_name );
424         free( psz_name );
425         free( p_cfg );
426         free( psz_new_string );
427         return -1;
428     }
429     free( psz_name );
430
431     const int i_ret = filter_chain_AppendFromStringInternal( p_chain, psz_new_string );
432     free( psz_new_string );
433     if( i_ret < 0 )
434     {
435         filter_chain_DeleteFilterInternal( p_chain, p_filter );
436         return i_ret;
437     }
438     return 1 + i_ret;
439 }
440
441 static int filter_chain_DeleteFilterInternal( filter_chain_t *p_chain,
442                                               filter_t *p_filter )
443 {
444     chained_filter_t *p_chained = chained( p_filter );
445
446     /* Remove it from the chain */
447     if( p_chained->prev != NULL )
448         p_chained->prev->next = p_chained->next;
449     else
450     {
451         assert( p_chained == p_chain->first );
452         p_chain->first = p_chained->next;
453     }
454
455     if( p_chained->next != NULL )
456         p_chained->next->prev = p_chained->prev;
457     else
458     {
459         assert( p_chained == p_chain->last );
460         p_chain->last = p_chained->prev;
461     }
462     p_chain->length--;
463
464     msg_Dbg( p_chain->p_this, "Filter %p removed from chain", p_filter );
465
466     /* Destroy the filter object */
467     if( IsInternalVideoAllocator( p_chained ) )
468         AllocatorClean( &internal_video_allocator, p_chained );
469     else
470         AllocatorClean( &p_chain->allocator, p_chained );
471
472     if( p_filter->p_module )
473         module_unneed( p_filter, p_filter->p_module );
474     free( p_chained->mouse );
475     vlc_object_release( p_filter );
476
477
478     /* FIXME: check fmt_in/fmt_out consitency */
479     return VLC_SUCCESS;
480 }
481
482 /**
483  * Internal chain buffer handling
484  */
485
486 static int UpdateVideoBufferFunctions( filter_chain_t *p_chain )
487 {
488     /**
489      * Last filter uses the filter chain's parent buffer allocation
490      * functions. All the other filters use internal functions.
491      * This makes it possible to have format changes between each
492      * filter without having to worry about the parent's picture
493      * heap format.
494      */
495     /* FIXME: we should only update the last and penultimate filters */
496     chained_filter_t *f;
497
498     for( f = p_chain->first; f != p_chain->last; f = f->next )
499     {
500         if( !IsInternalVideoAllocator( f ) )
501         {
502             AllocatorClean( &p_chain->allocator, f );
503
504             AllocatorInit( &internal_video_allocator, f );
505         }
506     }
507
508     if( f != NULL )
509     {
510         if( IsInternalVideoAllocator( f ) )
511         {
512             AllocatorClean( &internal_video_allocator, f );
513
514             if( AllocatorInit( &p_chain->allocator, f ) )
515                 return VLC_EGENERIC;
516         }
517     }
518     return VLC_SUCCESS;
519 }
520
521 /**
522  * This function should be called after every filter chain change
523  */
524 static int UpdateBufferFunctions( filter_chain_t *p_chain )
525 {
526     if( !strcmp( p_chain->psz_capability, "video filter2" ) )
527         return UpdateVideoBufferFunctions( p_chain );
528
529     return VLC_SUCCESS;
530 }
531
532 /* Internal video allocator functions */
533 static picture_t *VideoBufferNew( filter_t *p_filter )
534 {
535     const video_format_t *p_fmt = &p_filter->fmt_out.video;
536
537     picture_t *p_picture = picture_NewFromFormat( p_fmt );
538     if( !p_picture )
539         msg_Err( p_filter, "Failed to allocate picture" );
540     return p_picture;
541 }
542 static void VideoBufferDelete( filter_t *p_filter, picture_t *p_picture )
543 {
544     VLC_UNUSED( p_filter );
545     picture_Release( p_picture );
546 }
547 static int InternalVideoInit( filter_t *p_filter, void *p_data )
548 {
549     VLC_UNUSED(p_data);
550
551     p_filter->pf_video_buffer_new = VideoBufferNew;
552     p_filter->pf_video_buffer_del = VideoBufferDelete;
553
554     return VLC_SUCCESS;
555 }
556 static void InternalVideoClean( filter_t *p_filter )
557 {
558     p_filter->pf_video_buffer_new = NULL;
559     p_filter->pf_video_buffer_del = NULL;
560 }
561
562 static bool IsInternalVideoAllocator( chained_filter_t *p_filter )
563 {
564     return p_filter->filter.pf_video_buffer_new == VideoBufferNew;
565 }
566
567 /* */
568 static int AllocatorInit( const filter_chain_allocator_t *p_alloc,
569                           chained_filter_t *p_filter )
570 {
571     if( p_alloc->pf_init )
572         return p_alloc->pf_init( &p_filter->filter, p_alloc->p_data );
573     return VLC_SUCCESS;
574 }
575
576 static void AllocatorClean( const filter_chain_allocator_t *p_alloc,
577                             chained_filter_t *p_filter )
578 {
579     if( p_alloc->pf_clean )
580         p_alloc->pf_clean( &p_filter->filter );
581 }
582