filter_chain.c 15.8 KB
Newer Older
1
2
3
/*****************************************************************************
 * filter_chain.c : Handle chains of filter_t objects.
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2008 VLC authors and VideoLAN
5
 * Copyright (C) 2008-2014 Rémi Denis-Courmont
6
7
8
 *
 * Author: Antoine Cellerier <dionoea at videolan dot org>
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
9
10
11
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
12
13
14
15
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
16
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
18
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
19
20
21
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22
23
 *****************************************************************************/

24
25
26
27
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

28
#include <vlc_filter.h>
29
#include <vlc_modules.h>
30
#include <vlc_mouse.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
31
#include <vlc_spu.h>
32
#include <libvlc.h>
33
#include <assert.h>
34

35
36
37
38
39
40
41
typedef struct chained_filter_t
{
    /* Public part of the filter structure */
    filter_t filter;
    /* Private filter chain data (shhhh!) */
    struct chained_filter_t *prev, *next;
    vlc_mouse_t *mouse;
42
    vlc_picture_chain_t pending;
43
44
} chained_filter_t;

45
/* */
46
47
struct filter_chain_t
{
48
    vlc_object_t *obj;
49
    filter_owner_t parent_video_owner; /**< Owner (downstream) callbacks */
50

51
    chained_filter_t *first, *last; /**< List of filters */
52

53
    es_format_t fmt_in; /**< Chain input format (constant) */
54
    vlc_video_context *vctx_in; /**< Chain input video context (set on Reset) */
55
    es_format_t fmt_out; /**< Chain output format (constant) */
56
    bool b_allow_fmt_out_change; /**< Each filter can change the output */
57
    const char *filter_cap; /**< Filter modules capability */
58
    const char *conv_cap; /**< Converter modules capability */
59
60
};

61
62
63
/**
 * Local prototypes
 */
64
static void FilterDeletePictures( vlc_picture_chain_t * );
65

66
static filter_chain_t *filter_chain_NewInner( vlc_object_t *obj,
67
    const char *cap, const char *conv_cap, bool fmt_out_change,
68
    enum es_format_category_e cat )
69
{
70
    assert( obj != NULL );
71
72
    assert( cap != NULL );

73
    filter_chain_t *chain = malloc( sizeof (*chain) );
74
75
76
    if( unlikely(chain == NULL) )
        return NULL;

77
    chain->obj = obj;
78
79
    chain->first = NULL;
    chain->last = NULL;
80
    es_format_Init( &chain->fmt_in, cat, 0 );
81
    chain->vctx_in = NULL;
82
    es_format_Init( &chain->fmt_out, cat, 0 );
83
    chain->b_allow_fmt_out_change = fmt_out_change;
84
    chain->filter_cap = cap;
85
    chain->conv_cap = conv_cap;
86
87
88
    return chain;
}

89
#undef filter_chain_NewSPU
90
91
92
/**
 * Filter chain initialisation
 */
93
filter_chain_t *filter_chain_NewSPU( vlc_object_t *obj, const char *cap )
94
{
95
    return filter_chain_NewInner( obj, cap, NULL, false, SPU_ES );
96
}
97

98
99
100
/** Chained filter picture allocator function */
static picture_t *filter_chain_VideoBufferNew( filter_t *filter )
{
101
102
103
    picture_t *pic;
    chained_filter_t *chained = container_of(filter, chained_filter_t, filter);
    if( chained->next != NULL )
104
    {
105
106
        // HACK as intermediate filters may not have the same video format as
        // the last one handled by the owner
107
108
109
110
        filter_owner_t saved_owner = filter->owner;
        filter->owner = (filter_owner_t) {};
        pic = filter_NewPicture( filter );
        filter->owner = saved_owner;
111
112
113
114
115
116
117
        if( pic == NULL )
            msg_Err( filter, "Failed to allocate picture" );
    }
    else
    {
        filter_chain_t *chain = filter->owner.sys;

118
        // the owner of the chain requires pictures from the last filter to be grabbed from its callback
119
        /* XXX ugly */
120
121
122
123
        filter_owner_t saved_owner = filter->owner;
        filter->owner = chain->parent_video_owner;
        pic = filter_NewPicture( filter );
        filter->owner = saved_owner;
124
    }
125
    return pic;
126
127
}

128
129
130
static vlc_decoder_device * filter_chain_HoldDecoderDevice(vlc_object_t *o, void *sys)
{
    filter_chain_t *chain = sys;
131
132
133

    if (chain->parent_video_owner.video == NULL ||
        chain->parent_video_owner.video->hold_device == NULL)
134
135
136
137
138
        return NULL;

    return chain->parent_video_owner.video->hold_device(o, chain->parent_video_owner.sys);
}

139
140
static const struct filter_video_callbacks filter_chain_video_cbs =
{
141
    filter_chain_VideoBufferNew, filter_chain_HoldDecoderDevice,
142
143
};

144
145
146
147
#undef filter_chain_NewVideo
filter_chain_t *filter_chain_NewVideo( vlc_object_t *obj, bool allow_change,
                                       const filter_owner_t *restrict owner )
{
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
    filter_chain_t *chain =
        filter_chain_NewInner( obj, "video filter",
                                  "video converter", allow_change, VIDEO_ES );
    if (unlikely(chain == NULL))
        return NULL;

    if( owner != NULL && owner->video != NULL )
    {
        // keep this to get pictures for the last filter in the chain
        assert( owner->video->buffer_new != NULL );
        chain->parent_video_owner = *owner;
    }
    else
        chain->parent_video_owner = (filter_owner_t){};
    return chain;
163
164
}

165
166
167
168
169
170
void filter_chain_Clear( filter_chain_t *p_chain )
{
    while( p_chain->first != NULL )
        filter_chain_DeleteFilter( p_chain, &p_chain->first->filter );
}

171
172
173
174
175
/**
 * Filter chain destruction
 */
void filter_chain_Delete( filter_chain_t *p_chain )
{
176
    filter_chain_Clear( p_chain );
177

178
    es_format_Clean( &p_chain->fmt_in );
179
180
    if ( p_chain->vctx_in )
        vlc_video_context_Release( p_chain->vctx_in );
181
    es_format_Clean( &p_chain->fmt_out );
182

183
184
185
186
187
    free( p_chain );
}
/**
 * Filter chain reinitialisation
 */
188
189
void filter_chain_Reset( filter_chain_t *p_chain,
                         const es_format_t *p_fmt_in, vlc_video_context *vctx_in,
190
191
                         const es_format_t *p_fmt_out )
{
192
    filter_chain_Clear( p_chain );
193

194
195
196
    assert(p_fmt_in != NULL);
    es_format_Clean( &p_chain->fmt_in );
    es_format_Copy( &p_chain->fmt_in, p_fmt_in );
197
198
199
    if ( p_chain->vctx_in )
        vlc_video_context_Release( p_chain->vctx_in );
    p_chain->vctx_in = vctx_in ? vlc_video_context_Hold(vctx_in) : NULL;
200
201
202
203

    assert(p_fmt_out != NULL);
    es_format_Clean( &p_chain->fmt_out );
    es_format_Copy( &p_chain->fmt_out, p_fmt_out );
204
205
}

206
207
static filter_t *filter_chain_AppendInner( filter_chain_t *chain,
    const char *name, const char *capability, config_chain_t *cfg,
208
    const es_format_t *fmt_out )
209
{
210
    chained_filter_t *chained =
211
        vlc_custom_create( chain->obj, sizeof(*chained), "filter" );
212
213
214
215
216
    if( unlikely(chained == NULL) )
        return NULL;

    filter_t *filter = &chained->filter;

217
    const es_format_t *fmt_in;
218
    vlc_video_context *vctx_in;
219
    if( chain->last != NULL )
220
    {
221
        fmt_in = &chain->last->filter.fmt_out;
222
223
        vctx_in = chain->last->filter.vctx_out;
    }
224
    else
225
    {
226
        fmt_in = &chain->fmt_in;
227
228
        vctx_in = chain->vctx_in;
    }
229
230
231
232
233

    if( fmt_out == NULL )
        fmt_out = &chain->fmt_out;

    es_format_Copy( &filter->fmt_in, fmt_in );
234
    filter->vctx_in = vctx_in;
235
236
237
    es_format_Copy( &filter->fmt_out, fmt_out );
    filter->b_allow_fmt_out_change = chain->b_allow_fmt_out_change;
    filter->p_cfg = cfg;
Thomas Guillem's avatar
Thomas Guillem committed
238
    filter->psz_name = name;
239

240
241
242
243
244
245
246
    if (fmt_in->i_cat == VIDEO_ES)
    {
        filter->owner.video = &filter_chain_video_cbs;
        filter->owner.sys = chain;
    }
    else
        filter->owner.sub = NULL;
247

248
    assert( capability != NULL );
249
    if( name != NULL && chain->b_allow_fmt_out_change )
250
251
252
253
254
255
256
257
258
259
    {
        /* Append the "chain" video filter to the current list.
         * This filter will be used if the requested filter fails to load.
         * It will then try to add a video converter before. */
        char name_chained[strlen(name) + sizeof(",chain")];
        sprintf( name_chained, "%s,chain", name );
        filter->p_module = module_need( filter, capability, name_chained, true );
    }
    else
        filter->p_module = module_need( filter, capability, name, name != NULL );
260

261
262
    if( filter->p_module == NULL )
        goto error;
263
    assert( filter->ops != NULL );
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279

    if( chain->last == NULL )
    {
        assert( chain->first == NULL );
        chain->first = chained;
    }
    else
        chain->last->next = chained;
    chained->prev = chain->last;
    chain->last = chained;
    chained->next = NULL;

    vlc_mouse_t *mouse = malloc( sizeof(*mouse) );
    if( likely(mouse != NULL) )
        vlc_mouse_Init( mouse );
    chained->mouse = mouse;
280
    vlc_picture_chain_Init( &chained->pending );
281

282
    msg_Dbg( chain->obj, "Filter '%s' (%p) appended to chain",
283
             (name != NULL) ? name : module_get_name(filter->p_module, false),
284
             (void *)filter );
285
286
287
288
    return filter;

error:
    if( name != NULL )
289
        msg_Err( chain->obj, "Failed to create %s '%s'", capability, name );
290
    else
291
        msg_Err( chain->obj, "Failed to create %s", capability );
292
293
    es_format_Clean( &filter->fmt_out );
    es_format_Clean( &filter->fmt_in );
294
    vlc_object_delete(filter);
295
    return NULL;
296
297
}

298
299
filter_t *filter_chain_AppendFilter( filter_chain_t *chain,
    const char *name, config_chain_t *cfg,
300
    const es_format_t *fmt_out )
301
302
{
    return filter_chain_AppendInner( chain, name, chain->filter_cap, cfg,
303
                                     fmt_out );
304
305
}

306
int filter_chain_AppendConverter( filter_chain_t *chain,
307
    const es_format_t *fmt_out )
308
{
309
    return filter_chain_AppendInner( chain, NULL, chain->conv_cap, NULL,
310
                                     fmt_out ) != NULL ? 0 : -1;
311
312
}

313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
void filter_chain_DeleteFilter( filter_chain_t *chain, filter_t *filter )
{
    chained_filter_t *chained = (chained_filter_t *)filter;

    /* Remove it from the chain */
    if( chained->prev != NULL )
        chained->prev->next = chained->next;
    else
    {
        assert( chained == chain->first );
        chain->first = chained->next;
    }

    if( chained->next != NULL )
        chained->next->prev = chained->prev;
    else
    {
        assert( chained == chain->last );
        chain->last = chained->prev;
    }

334
    filter_Close( filter );
335
336
    module_unneed( filter, filter->p_module );

337
    msg_Dbg( chain->obj, "Filter %p removed from chain", (void *)filter );
338
    FilterDeletePictures( &chained->pending );
339
340

    free( chained->mouse );
ssbssa's avatar
ssbssa committed
341
342
    es_format_Clean( &filter->fmt_out );
    es_format_Clean( &filter->fmt_in );
343

344
    vlc_object_delete(filter);
345
346
347
348
    /* FIXME: check fmt_in/fmt_out consitency */
}


349
int filter_chain_AppendFromString( filter_chain_t *chain, const char *str )
350
{
351
352
    char *buf = NULL;
    int ret = 0;
353

354
355
356
357
358
359
360
361
362
363
364
    while( str != NULL && str[0] != '\0' )
    {
        config_chain_t *cfg;
        char *name;

        char *next = config_ChainCreate( &name, &cfg, str );

        str = next;
        free( buf );
        buf = next;

365
        filter_t *filter = filter_chain_AppendFilter( chain, name, cfg, NULL );
366
367
368
        if( cfg )
            config_ChainDestroy( cfg );

369
370
        if( filter == NULL )
        {
371
            msg_Err( chain->obj, "Failed to append '%s' to chain", name );
372
373
374
375
376
377
378
379
380
381
382
383
384
385
            free( name );
            goto error;
        }

        free( name );
        ret++;
    }

    free( buf );
    return ret;

error:
    while( ret > 0 ) /* Unwind */
    {
386
        filter_chain_DeleteFilter( chain, &chain->last->filter );
387
388
389
        ret--;
    }
    free( buf );
390
    return VLC_EGENERIC;
391
392
}

393
394
395
396
397
398
399
400
401
402
403
404
int filter_chain_ForEach( filter_chain_t *chain,
                          int (*cb)( filter_t *, void * ), void *opaque )
{
    for( chained_filter_t *f = chain->first; f != NULL; f = f->next )
    {
        int ret = cb( &f->filter, opaque );
        if( ret )
            return ret;
    }
    return VLC_SUCCESS;
}

405
bool filter_chain_IsEmpty(const filter_chain_t *chain)
406
{
407
    return chain->first == NULL;
408
409
}

410
const es_format_t *filter_chain_GetFmtOut( const filter_chain_t *p_chain )
411
{
412
413
    if( p_chain->last != NULL )
        return &p_chain->last->filter.fmt_out;
414
415
416
417
418

    /* Unless filter_chain_Reset has been called we are doomed */
    return &p_chain->fmt_out;
}

419
420
421
422
423
424
425
426
427
428
vlc_video_context *filter_chain_GetVideoCtxOut(const filter_chain_t *p_chain)
{
    if( p_chain->last != NULL )
        return p_chain->last->filter.vctx_out;

    /* No filter was added, the filter chain has no effect, make sure the chromas are compatible */
    assert(p_chain->fmt_in.video.i_chroma == p_chain->fmt_out.video.i_chroma);
    return p_chain->vctx_in;
}

429
static picture_t *FilterChainVideoFilter( chained_filter_t *f, picture_t *p_pic )
430
{
431
    for( ; f != NULL; f = f->next )
432
    {
433
        filter_t *p_filter = &f->filter;
434
        p_pic = p_filter->ops->filter_video( p_filter, p_pic );
435
436
        if( !p_pic )
            break;
437
        if( !vlc_picture_chain_IsEmpty( &f->pending ) )
438
439
        {
            msg_Warn( p_filter, "dropping pictures" );
440
            FilterDeletePictures( &f->pending );
441
        }
442
        f->pending = picture_GetAndResetChain( p_pic );
443
444
445
446
    }
    return p_pic;
}

447
448
449
450
451
452
453
454
455
456
457
static picture_t *filter_chained( chained_filter_t *f, picture_t *p_pic )
{
    for( ; f != NULL; f = f->next )
    {
        p_pic = f->filter.ops->filter_video( &f->filter, p_pic );
        if( unlikely(!p_pic) )
            break;
    }
    return p_pic;
}

458
459
460
461
462
463
464
465
picture_t *filter_chain_VideoFilter( filter_chain_t *p_chain, picture_t *p_pic )
{
    if( p_pic )
    {
        p_pic = FilterChainVideoFilter( p_chain->first, p_pic );
        if( p_pic )
            return p_pic;
    }
466
467
468
469
470
471
    return filter_chain_VideoDrain( p_chain );
}

picture_t *filter_chain_VideoDrain( filter_chain_t *p_chain )
{
    picture_t *p_pic;
472
473
    for( chained_filter_t *b = p_chain->last; b != NULL; b = b->prev )
    {
474
        if( vlc_picture_chain_IsEmpty( &b->pending ) )
475
            continue;
476
        p_pic = vlc_picture_chain_PopFront( &b->pending );
477
478
479
480
481
482
483
484

        p_pic = FilterChainVideoFilter( b->next, p_pic );
        if( p_pic )
            return p_pic;
    }
    return NULL;
}

485
486
487
488
489
490
void filter_chain_VideoFlush( filter_chain_t *p_chain )
{
    for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
    {
        filter_t *p_filter = &f->filter;

491
        FilterDeletePictures( &f->pending );
492

493
        filter_Flush( p_filter );
494
495
496
    }
}

497
void filter_chain_SubSource( filter_chain_t *p_chain, spu_t *spu,
Steve Lhomme's avatar
Steve Lhomme committed
498
                             vlc_tick_t display_date )
499
{
500
    for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
501
    {
502
        filter_t *p_filter = &f->filter;
503
        subpicture_t *p_subpic = p_filter->ops->source_sub( p_filter, display_date );
504
        if( p_subpic )
505
            spu_PutSubpicture( spu, p_subpic );
506
507
508
    }
}

509
510
511
512
513
514
subpicture_t *filter_chain_SubFilter( filter_chain_t *p_chain, subpicture_t *p_subpic )
{
    for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
    {
        filter_t *p_filter = &f->filter;

515
        p_subpic = p_filter->ops->filter_sub( p_filter, p_subpic );
516
517
518
519
520
521
522

        if( !p_subpic )
            break;
    }
    return p_subpic;
}

523
524
525
526
int filter_chain_MouseFilter( filter_chain_t *p_chain, vlc_mouse_t *p_dst, const vlc_mouse_t *p_src )
{
    vlc_mouse_t current = *p_src;

527
    for( chained_filter_t *f = p_chain->last; f != NULL; f = f->prev )
528
    {
529
530
        filter_t *p_filter = &f->filter;
        vlc_mouse_t *p_mouse = f->mouse;
531

532
        if( p_filter->ops->video_mouse && p_mouse )
533
534
        {
            vlc_mouse_t old = *p_mouse;
535
            vlc_mouse_t filtered = current;
536
537

            *p_mouse = current;
538
            if( p_filter->ops->video_mouse( p_filter, &filtered, &old) )
539
540
541
542
543
544
545
546
547
                return VLC_EGENERIC;
            current = filtered;
        }
    }

    *p_dst = current;
    return VLC_SUCCESS;
}

548
/* Helpers */
549
static void FilterDeletePictures( vlc_picture_chain_t *pictures )
550
{
551
    while( !vlc_picture_chain_IsEmpty( pictures ) )
552
    {
553
        picture_t *next = vlc_picture_chain_PopFront( pictures );
554
        picture_Release( next );
555
556
    }
}