mosaic.c 33.1 KB
Newer Older
dionoea's avatar
dionoea committed
1
/*****************************************************************************
2
3
 * mosaic.c : Mosaic video plugin for vlc
 *****************************************************************************
4
 * Copyright (C) 2004-2008 the VideoLAN team
5
6
 * $Id$
 *
7
 * Authors: Antoine Cellerier <dionoea at videolan dot org>
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *          Christophe Massiot <massiot@via.ecp.fr>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (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
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
dionoea's avatar
dionoea committed
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
dionoea's avatar
dionoea committed
23
24
25
*****************************************************************************/

/*****************************************************************************
26
27
 * Preamble
 *****************************************************************************/
28
29
30
31
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

32
#include <vlc_common.h>
33
#include <vlc_plugin.h>
dionoea's avatar
dionoea committed
34

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
35
#include <math.h>
36
#include <limits.h> /* INT_MAX */
37

ivoire's avatar
ivoire committed
38
39
#include <vlc_filter.h>
#include <vlc_image.h>
dionoea's avatar
dionoea committed
40

41
42
#include "mosaic.h"

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
43
#define BLANK_DELAY INT64_C(1000000)
dionoea's avatar
dionoea committed
44

45
46
47
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
dionoea's avatar
dionoea committed
48
49
static int  CreateFilter    ( vlc_object_t * );
static void DestroyFilter   ( vlc_object_t * );
50
static subpicture_t *Filter ( filter_t *, mtime_t );
dionoea's avatar
dionoea committed
51

52
53
static int MosaicCallback   ( vlc_object_t *, char const *, vlc_value_t,
                              vlc_value_t, void * );
54

dionoea's avatar
dionoea committed
55
/*****************************************************************************
56
57
 * filter_sys_t : filter descriptor
 *****************************************************************************/
dionoea's avatar
dionoea committed
58
59
struct filter_sys_t
{
60
61
    vlc_mutex_t lock;         /* Internal filter lock */
    vlc_mutex_t *p_lock;      /* Pointer to mosaic bridge lock */
62

dionoea's avatar
dionoea committed
63
    image_handler_t *p_image;
64
65

    int i_position;           /* Mosaic positioning method */
66
67
    bool b_ar;          /* Do we keep the aspect ratio ? */
    bool b_keep;        /* Do we keep the original picture format ? */
68
69
70
71
72
73
74
75
    int i_width, i_height;    /* Mosaic height and width */
    int i_cols, i_rows;       /* Mosaic rows and cols */
    int i_align;              /* Mosaic alignment in background video */
    int i_xoffset, i_yoffset; /* Top left corner offset */
    int i_borderw, i_borderh; /* Border width/height between miniatures */
    int i_alpha;              /* Subfilter alpha blending */

    char **ppsz_order;        /* List of picture-ids */
76
77
    int i_order_length;

78
79
    int *pi_x_offsets;        /* List of substreams x offsets */
    int *pi_y_offsets;        /* List of substreams y offsets */
80
81
    int i_offsets_length;

82
    mtime_t i_delay;
dionoea's avatar
dionoea committed
83
84
85
};

/*****************************************************************************
86
87
 * Module descriptor
 *****************************************************************************/
88
#define ALPHA_TEXT N_("Transparency")
dionoea's avatar
Mosaic:    
dionoea committed
89
90
#define ALPHA_LONGTEXT N_( \
        "Transparency of the mosaic foreground pictures. " \
91
92
93
94
95
96
97
98
        "0 means transparent, 255 opaque (default)." )

#define HEIGHT_TEXT N_("Height")
#define HEIGHT_LONGTEXT N_( "Total height of the mosaic, in pixels." )
#define WIDTH_TEXT N_("Width")
#define WIDTH_LONGTEXT N_( "Total width of the mosaic, in pixels." )

#define XOFFSET_TEXT N_("Top left corner X coordinate")
dionoea's avatar
Mosaic:    
dionoea committed
99
100
#define XOFFSET_LONGTEXT N_( \
        "X Coordinate of the top-left corner of the mosaic.")
101
#define YOFFSET_TEXT N_("Top left corner Y coordinate")
dionoea's avatar
Mosaic:    
dionoea committed
102
103
104
#define YOFFSET_LONGTEXT N_( \
        "Y Coordinate of the top-left corner of the mosaic.")

105
#define BORDERW_TEXT N_("Border width")
dionoea's avatar
Mosaic:    
dionoea committed
106
107
#define BORDERW_LONGTEXT N_( \
        "Width in pixels of the border between miniatures." )
108
#define BORDERH_TEXT N_("Border height")
dionoea's avatar
Mosaic:    
dionoea committed
109
110
#define BORDERH_LONGTEXT N_( \
        "Height in pixels of the border between miniatures." )
111
112
113

#define ALIGN_TEXT N_("Mosaic alignment" )
#define ALIGN_LONGTEXT N_( \
dionoea's avatar
Mosaic:    
dionoea committed
114
115
116
        "You can enforce the mosaic alignment on the video " \
        "(0=center, 1=left, 2=right, 4=top, 8=bottom, you can " \
        "also use combinations of these values, eg 6 = top-right).")
117
118

#define POS_TEXT N_("Positioning method")
dionoea's avatar
Mosaic:    
dionoea committed
119
120
#define POS_LONGTEXT N_( \
        "Positioning method for the mosaic. auto: " \
121
        "automatically choose the best number of rows and columns. " \
122
123
        "fixed: use the user-defined number of rows and columns. " \
        "offsets: use the user-defined offsets for each image." )
124

125
#define ROWS_TEXT N_("Number of rows")
dionoea's avatar
Mosaic:    
dionoea committed
126
127
#define ROWS_LONGTEXT N_( \
        "Number of image rows in the mosaic (only used if " \
Christophe Mutricy's avatar
Christophe Mutricy committed
128
        "positionning method is set to \"fixed\")." )
dionoea's avatar
Mosaic:    
dionoea committed
129

130
#define COLS_TEXT N_("Number of columns")
dionoea's avatar
Mosaic:    
dionoea committed
131
132
#define COLS_LONGTEXT N_( \
        "Number of image columns in the mosaic (only used if " \
133
134
135
        "positionning method is set to \"fixed\"." )

#define AR_TEXT N_("Keep aspect ratio")
dionoea's avatar
Mosaic:    
dionoea committed
136
137
#define AR_LONGTEXT N_( \
        "Keep the original aspect ratio when resizing " \
138
        "mosaic elements." )
139
#define KEEP_TEXT N_("Keep original size")
dionoea's avatar
Mosaic:    
dionoea committed
140
141
#define KEEP_LONGTEXT N_( \
        "Keep the original size of mosaic elements." )
dionoea's avatar
dionoea committed
142

143
#define ORDER_TEXT N_("Elements order" )
dionoea's avatar
Mosaic:    
dionoea committed
144
145
#define ORDER_LONGTEXT N_( \
        "You can enforce the order of the elements on " \
146
147
        "the mosaic. You must give a comma-separated list of picture ID(s)." \
        "These IDs are assigned in the \"mosaic-bridge\" module." )
148

149
#define OFFSETS_TEXT N_("Offsets in order" )
dionoea's avatar
Mosaic:    
dionoea committed
150
151
152
#define OFFSETS_LONGTEXT N_( \
        "You can enforce the (x,y) offsets of the elements on the mosaic " \
        "(only used if positioning method is set to \"offsets\"). You " \
153
154
        "must give a comma-separated list of coordinates (eg: 10,10,150,10)." )

155
#define DELAY_TEXT N_("Delay")
dionoea's avatar
Mosaic:    
dionoea committed
156
157
158
#define DELAY_LONGTEXT N_( \
        "Pictures coming from the mosaic elements will be delayed " \
        "according to this value (in milliseconds). For high " \
159
160
        "values you will need to raise caching at input.")

161
162
163
164
enum
{
    position_auto = 0, position_fixed = 1, position_offsets = 2
};
165
166
static const int pi_pos_values[] = { 0, 1, 2 };
static const char *const ppsz_pos_descriptions[] =
167
    { N_("auto"), N_("fixed"), N_("offsets") };
dionoea's avatar
dionoea committed
168

169
170
static const int pi_align_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
static const char *const ppsz_align_descriptions[] =
171
172
173
     { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
     N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };

174
#define CFG_PREFIX "mosaic-"
dionoea's avatar
dionoea committed
175

176
177
178
179
180
181
182
vlc_module_begin ()
    set_description( N_("Mosaic video sub filter") )
    set_shortname( N_("Mosaic") )
    set_category( CAT_VIDEO )
    set_subcategory( SUBCAT_VIDEO_SUBPIC)
    set_capability( "sub filter", 0 )
    set_callbacks( CreateFilter, DestroyFilter )
dionoea's avatar
dionoea committed
183

184
    add_integer_with_range( CFG_PREFIX "alpha", 255, 0, 255, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
185
                            ALPHA_TEXT, ALPHA_LONGTEXT, false )
dionoea's avatar
Mosaic:    
dionoea committed
186
187

    add_integer( CFG_PREFIX "height", 100, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
188
                 HEIGHT_TEXT, HEIGHT_LONGTEXT, false )
dionoea's avatar
Mosaic:    
dionoea committed
189
    add_integer( CFG_PREFIX "width", 100, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
190
                 WIDTH_TEXT, WIDTH_LONGTEXT, false )
dionoea's avatar
Mosaic:    
dionoea committed
191
192

    add_integer( CFG_PREFIX "align", 5, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
193
194
                 ALIGN_TEXT, ALIGN_LONGTEXT, true)
        change_integer_list( pi_align_values, ppsz_align_descriptions, NULL )
dionoea's avatar
Mosaic:    
dionoea committed
195
196

    add_integer( CFG_PREFIX "xoffset", 0, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
197
                 XOFFSET_TEXT, XOFFSET_LONGTEXT, true )
dionoea's avatar
Mosaic:    
dionoea committed
198
    add_integer( CFG_PREFIX "yoffset", 0, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
199
                 YOFFSET_TEXT, YOFFSET_LONGTEXT, true )
dionoea's avatar
Mosaic:    
dionoea committed
200
201

    add_integer( CFG_PREFIX "borderw", 0, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
202
                 BORDERW_TEXT, BORDERW_LONGTEXT, true )
203
        add_deprecated_alias( CFG_PREFIX "vborder" )
dionoea's avatar
Mosaic:    
dionoea committed
204
    add_integer( CFG_PREFIX "borderh", 0, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
205
                 BORDERH_TEXT, BORDERH_LONGTEXT, true )
206
        add_deprecated_alias( CFG_PREFIX "hborder" )
dionoea's avatar
dionoea committed
207

dionoea's avatar
Mosaic:    
dionoea committed
208
    add_integer( CFG_PREFIX "position", 0, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
209
                 POS_TEXT, POS_LONGTEXT, false )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
210
        change_integer_list( pi_pos_values, ppsz_pos_descriptions, NULL )
dionoea's avatar
Mosaic:    
dionoea committed
211
    add_integer( CFG_PREFIX "rows", 2, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
212
                 ROWS_TEXT, ROWS_LONGTEXT, false )
dionoea's avatar
Mosaic:    
dionoea committed
213
    add_integer( CFG_PREFIX "cols", 2, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
214
                 COLS_TEXT, COLS_LONGTEXT, false )
dionoea's avatar
Mosaic:    
dionoea committed
215
216

    add_bool( CFG_PREFIX "keep-aspect-ratio", 0, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
217
              AR_TEXT, AR_LONGTEXT, false )
dionoea's avatar
Mosaic:    
dionoea committed
218
    add_bool( CFG_PREFIX "keep-picture", 0, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
219
              KEEP_TEXT, KEEP_LONGTEXT, false )
dionoea's avatar
Mosaic:    
dionoea committed
220
221

    add_string( CFG_PREFIX "order", "", NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
222
                ORDER_TEXT, ORDER_LONGTEXT, false )
dionoea's avatar
Mosaic:    
dionoea committed
223
224

    add_string( CFG_PREFIX "offsets", "", NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
225
                OFFSETS_TEXT, OFFSETS_LONGTEXT, false )
226
227

    add_integer( CFG_PREFIX "delay", 0, NULL, DELAY_TEXT, DELAY_LONGTEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
228
                 false )
229
vlc_module_end ()
dionoea's avatar
dionoea committed
230

231
static const char *const ppsz_filter_options[] = {
232
    "alpha", "height", "width", "align", "xoffset", "yoffset",
233
    "borderw", "borderh", "position", "rows", "cols",
234
    "keep-aspect-ratio", "keep-picture", "order", "offsets",
235
    "delay", NULL
236
};
dionoea's avatar
dionoea committed
237

238
239
240
241
242
/*****************************************************************************
 * mosaic_ParseSetOffsets:
 * parse the "--mosaic-offsets x1,y1,x2,y2,x3,y3" parameter
 * and set the corresponding struct filter_sys_t entries.
 *****************************************************************************/
243
244
245
246
247
#define mosaic_ParseSetOffsets( a, b, c ) \
      __mosaic_ParseSetOffsets( VLC_OBJECT( a ), b, c )
static void __mosaic_ParseSetOffsets( vlc_object_t *p_this,
                                      filter_sys_t *p_sys,
                                      char *psz_offsets )
248
{
249
    if( *psz_offsets )
250
251
252
253
254
255
256
    {
        char *psz_end = NULL;
        int i_index = 0;
        do
        {
            i_index++;

257
258
            p_sys->pi_x_offsets =
                realloc( p_sys->pi_x_offsets, i_index * sizeof(int) );
259
260
261
262
            p_sys->pi_x_offsets[i_index - 1] = atoi( psz_offsets );
            psz_end = strchr( psz_offsets, ',' );
            psz_offsets = psz_end + 1;

263
264
            p_sys->pi_y_offsets =
                realloc( p_sys->pi_y_offsets, i_index * sizeof(int) );
265
266
267
268
            p_sys->pi_y_offsets[i_index - 1] = atoi( psz_offsets );
            psz_end = strchr( psz_offsets, ',' );
            psz_offsets = psz_end + 1;

269
270
271
            msg_Dbg( p_this, CFG_PREFIX "offset: id %d, x=%d, y=%d",
                     i_index, p_sys->pi_x_offsets[i_index - 1],
                              p_sys->pi_y_offsets[i_index - 1]  );
272

273
        } while( psz_end );
274
275
276
277
        p_sys->i_offsets_length = i_index;
    }
}

dionoea's avatar
dionoea committed
278
/*****************************************************************************
279
280
 * CreateFiler: allocate mosaic video filter
 *****************************************************************************/
dionoea's avatar
dionoea committed
281
282
283
284
static int CreateFilter( vlc_object_t *p_this )
{
    filter_t *p_filter = (filter_t *)p_this;
    filter_sys_t *p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
285
    vlc_object_t *p_libvlc = VLC_OBJECT( p_filter->p_libvlc );
286
    char *psz_order, *_psz_order;
287
    char *psz_offsets;
288
    int i_index;
289
    vlc_value_t val;
290
    int i_command;
dionoea's avatar
dionoea committed
291

292
293
294
    /* The mosaic thread is more important than the decoder threads */
    vlc_thread_set_priority( p_this, VLC_THREAD_PRIORITY_OUTPUT );

dionoea's avatar
dionoea committed
295
296
297
298
299
300
301
    /* Allocate structure */
    p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
    if( p_sys == NULL )
        return VLC_ENOMEM;

    p_filter->pf_sub_filter = Filter;

302
    vlc_mutex_init( &p_sys->lock );
303
304
    vlc_mutex_lock( &p_sys->lock );

305
306
    var_Create( p_libvlc, "mosaic-lock", VLC_VAR_MUTEX );
    var_Get( p_libvlc, "mosaic-lock", &val );
307
308
    p_sys->p_lock = val.p_address;

309
310
311
    config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
                       p_filter->p_cfg );

312
#define GET_VAR( name, min, max )                                           \
313
314
    i_command = var_CreateGetIntegerCommand( p_filter, CFG_PREFIX #name );  \
    p_sys->i_##name = __MIN( max, __MAX( min, i_command ) );                \
315
    var_AddCallback( p_filter, CFG_PREFIX #name, MosaicCallback, p_sys );
316

317
318
319
320
    GET_VAR( width, 0, INT_MAX );
    GET_VAR( height, 0, INT_MAX );
    GET_VAR( xoffset, 0, INT_MAX );
    GET_VAR( yoffset, 0, INT_MAX );
321

322
    GET_VAR( align, 0, 10 );
323
    if( p_sys->i_align == 3 || p_sys->i_align == 7 )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
324
        p_sys->i_align = 5; /* FIXME: NOT THREAD SAFE w.r.t. callback */
325

326
327
    GET_VAR( borderw, 0, INT_MAX );
    GET_VAR( borderh, 0, INT_MAX );
328
329
    GET_VAR( rows, 1, INT_MAX );
    GET_VAR( cols, 1, INT_MAX );
330
    GET_VAR( alpha, 0, 255 );
331
    GET_VAR( position, 0, 2 );
332
    GET_VAR( delay, 100, INT_MAX );
333
#undef GET_VAR
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
334
    p_sys->i_delay *= 1000; /* FIXME: NOT THREAD SAFE w.r.t. callback */
335

336
337
338
    p_sys->b_ar = var_CreateGetBoolCommand( p_filter,
                                            CFG_PREFIX "keep-aspect-ratio" );
    var_AddCallback( p_filter, CFG_PREFIX "keep-aspect-ratio", MosaicCallback,
339
340
                     p_sys );

341
342
    p_sys->b_keep = var_CreateGetBoolCommand( p_filter,
                                              CFG_PREFIX "keep-picture" );
343
344
345
346
    if ( !p_sys->b_keep )
    {
        p_sys->p_image = image_HandlerCreate( p_filter );
    }
347

348
349
    p_sys->i_order_length = 0;
    p_sys->ppsz_order = NULL;
350
    psz_order = var_CreateGetStringCommand( p_filter, CFG_PREFIX "order" );
351
    _psz_order = psz_order;
352
    var_AddCallback( p_filter, CFG_PREFIX "order", MosaicCallback, p_sys );
353

354
    if( *psz_order )
355
    {
356
        char *psz_end = NULL;
357
        i_index = 0;
358
        do
359
        {
360
361
362
363
364
365
366
            psz_end = strchr( psz_order, ',' );
            i_index++;
            p_sys->ppsz_order = realloc( p_sys->ppsz_order,
                                         i_index * sizeof(char *) );
            p_sys->ppsz_order[i_index - 1] = strndup( psz_order,
                                           psz_end - psz_order );
            psz_order = psz_end+1;
367
        } while( psz_end );
368
369
370
        p_sys->i_order_length = i_index;
    }

371
372
    free( _psz_order );

373
    /* Manage specific offsets for substreams */
374
    psz_offsets = var_CreateGetStringCommand( p_filter, CFG_PREFIX "offsets" );
375
376
377
    p_sys->i_offsets_length = 0;
    p_sys->pi_x_offsets = NULL;
    p_sys->pi_y_offsets = NULL;
378
    mosaic_ParseSetOffsets( p_filter, p_sys, psz_offsets );
379
    free( psz_offsets );
380
    var_AddCallback( p_filter, CFG_PREFIX "offsets", MosaicCallback, p_sys );
381

382
383
    vlc_mutex_unlock( &p_sys->lock );

dionoea's avatar
dionoea committed
384
385
386
387
    return VLC_SUCCESS;
}

/*****************************************************************************
388
389
 * DestroyFilter: destroy mosaic video filter
 *****************************************************************************/
dionoea's avatar
dionoea committed
390
391
392
393
394
static void DestroyFilter( vlc_object_t *p_this )
{
    filter_t *p_filter = (filter_t*)p_this;
    filter_sys_t *p_sys = p_filter->p_sys;

ivoire's avatar
ivoire committed
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
#define DEL_CB( name ) \
    var_DelCallback( p_filter, CFG_PREFIX #name, MosaicCallback, p_sys )
    DEL_CB( width );
    DEL_CB( height );
    DEL_CB( xoffset );
    DEL_CB( yoffset );

    DEL_CB( align );

    DEL_CB( borderw );
    DEL_CB( borderh );
    DEL_CB( rows );
    DEL_CB( cols );
    DEL_CB( alpha );
    DEL_CB( position );
    DEL_CB( delay );

    DEL_CB( keep-aspect-ratio );
    DEL_CB( order );
#undef DEL_CB
415

416
417
418
419
    if( !p_sys->b_keep )
    {
        image_HandlerDelete( p_sys->p_image );
    }
dionoea's avatar
dionoea committed
420

421
422
    if( p_sys->i_order_length )
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
423
        for( int i_index = 0; i_index < p_sys->i_order_length; i_index++ )
424
425
426
427
428
        {
            free( p_sys->ppsz_order[i_index] );
        }
        free( p_sys->ppsz_order );
    }
429
430
431
432
433
434
    if( p_sys->i_offsets_length )
    {
        free( p_sys->pi_x_offsets );
        free( p_sys->pi_y_offsets );
        p_sys->i_offsets_length = 0;
    }
435

436
    vlc_mutex_destroy( &p_sys->lock );
dionoea's avatar
dionoea committed
437
438
439
440
    free( p_sys );
}

/*****************************************************************************
441
442
443
444
445
446
 * MosaicReleasePicture : Hack to avoid picture duplication
 *****************************************************************************/
static void MosaicReleasePicture( picture_t *p_picture )
{
    picture_t *p_original_pic = (picture_t *)p_picture->p_sys;

447
    picture_Release( p_original_pic );
448
}
dionoea's avatar
dionoea committed
449

450
451
452
/*****************************************************************************
 * Filter
 *****************************************************************************/
dionoea's avatar
dionoea committed
453
454
455
static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
{
    filter_sys_t *p_sys = p_filter->p_sys;
456
    bridge_t *p_bridge;
457

dionoea's avatar
dionoea committed
458
459
    subpicture_t *p_spu;

460
    int i_index, i_real_index, i_row, i_col;
461
    int i_greatest_real_index_used = p_sys->i_order_length - 1;
dionoea's avatar
dionoea committed
462

463
    unsigned int col_inner_width, row_inner_height;
464

dionoea's avatar
dionoea committed
465
466
467
468
    subpicture_region_t *p_region;
    subpicture_region_t *p_region_prev = NULL;

    /* Allocate the subpicture internal data. */
Laurent Aimar's avatar
Laurent Aimar committed
469
    p_spu = filter_NewSubpicture( p_filter );
dionoea's avatar
dionoea committed
470
471
472
    if( !p_spu )
        return NULL;

473
474
475
476
    /* Initialize subpicture */
    p_spu->i_channel = 0;
    p_spu->i_start  = date;
    p_spu->i_stop = 0;
477
    p_spu->b_ephemer = true;
478
    p_spu->i_alpha = p_sys->i_alpha;
479
    p_spu->b_absolute = false;
dionoea's avatar
dionoea committed
480

481
    vlc_mutex_lock( &p_sys->lock );
482
483
484
485
486
487
488
489
490
    vlc_mutex_lock( p_sys->p_lock );

    p_bridge = GetBridge( p_filter );
    if ( p_bridge == NULL )
    {
        vlc_mutex_unlock( p_sys->p_lock );
        vlc_mutex_unlock( &p_sys->lock );
        return p_spu;
    }
491

492
    if ( p_sys->i_position == position_offsets )
493
494
495
496
497
    {
        /* If we have either too much or not enough offsets, fall-back
         * to automatic positioning. */
        if ( p_sys->i_offsets_length != p_sys->i_order_length )
        {
498
499
500
501
502
503
            msg_Err( p_filter,
                     "Number of specified offsets (%d) does not match number "
                     "of input substreams in mosaic-order (%d), falling back "
                     "to mosaic-position=0",
                     p_sys->i_offsets_length, p_sys->i_order_length );
            p_sys->i_position = position_auto;
504
505
506
        }
    }

507
    if ( p_sys->i_position == position_auto )
dionoea's avatar
dionoea committed
508
    {
509
        int i_numpics = p_sys->i_order_length; /* keep slots and all */
510
        for ( i_index = 0; i_index < p_bridge->i_es_num; i_index++ )
dionoea's avatar
dionoea committed
511
        {
512
513
            bridged_es_t *p_es = p_bridge->pp_es[i_index];
            if ( !p_es->b_empty )
514
            {
dionoea's avatar
dionoea committed
515
                i_numpics ++;
516
                if( p_sys->i_order_length && p_es->psz_id != NULL )
517
518
519
                {
                    /* We also want to leave slots for images given in
                     * mosaic-order that are not available in p_vout_picture */
520
521
522
                    int i;
                    for( i = 0; i < p_sys->i_order_length ; i++ )
                    {
523
                        if( !strcmp( p_sys->ppsz_order[i], p_es->psz_id ) )
524
                        {
525
                            i_numpics--;
526
527
528
529
530
                            break;
                        }
                    }

                }
dionoea's avatar
dionoea committed
531
532
            }
        }
dionoea's avatar
Mosaic:    
dionoea committed
533
        p_sys->i_rows = ceil(sqrt( (double)i_numpics ));
534
535
536
        p_sys->i_cols = ( i_numpics % p_sys->i_rows == 0 ?
                            i_numpics / p_sys->i_rows :
                            i_numpics / p_sys->i_rows + 1 );
dionoea's avatar
dionoea committed
537
538
    }

539
    col_inner_width  = ( ( p_sys->i_width - ( p_sys->i_cols - 1 )
540
                       * p_sys->i_borderw ) / p_sys->i_cols );
541
    row_inner_height = ( ( p_sys->i_height - ( p_sys->i_rows - 1 )
542
                       * p_sys->i_borderh ) / p_sys->i_rows );
543

544
545
    i_real_index = 0;

546
    for ( i_index = 0; i_index < p_bridge->i_es_num; i_index++ )
dionoea's avatar
dionoea committed
547
    {
548
        bridged_es_t *p_es = p_bridge->pp_es[i_index];
dionoea's avatar
Mosaic:    
dionoea committed
549
        video_format_t fmt_in, fmt_out;
550
551
        picture_t *p_converted;

dionoea's avatar
Mosaic:    
dionoea committed
552
553
554
        memset( &fmt_in, 0, sizeof( video_format_t ) );
        memset( &fmt_out, 0, sizeof( video_format_t ) );

555
        if ( p_es->b_empty )
556
            continue;
557

558
559
        while ( p_es->p_picture != NULL
                 && p_es->p_picture->date + p_sys->i_delay < date )
560
561
562
563
        {
            if ( p_es->p_picture->p_next != NULL )
            {
                picture_t *p_next = p_es->p_picture->p_next;
Laurent Aimar's avatar
Laurent Aimar committed
564
                picture_Release( p_es->p_picture );
565
566
567
568
569
570
                p_es->p_picture = p_next;
            }
            else if ( p_es->p_picture->date + p_sys->i_delay + BLANK_DELAY <
                        date )
            {
                /* Display blank */
571
                picture_Release( p_es->p_picture );
572
573
                p_es->p_picture = NULL;
                p_es->pp_last = &p_es->p_picture;
574
                break;
575
576
            }
            else
577
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
578
                msg_Dbg( p_filter, "too late picture for %s (%"PRId64 ")",
579
580
                         p_es->psz_id,
                         date - p_es->p_picture->date - p_sys->i_delay );
581
582
                break;
            }
dionoea's avatar
dionoea committed
583
584
        }

585
586
587
        if ( p_es->p_picture == NULL )
            continue;

588
        if ( p_sys->i_order_length == 0 )
dionoea's avatar
dionoea committed
589
        {
590
            i_real_index++;
dionoea's avatar
dionoea committed
591
        }
592
593
594
        else
        {
            int i;
595
            for ( i = 0; i <= p_sys->i_order_length; i++ )
596
            {
597
598
                if ( i == p_sys->i_order_length ) break;
                if ( strcmp( p_es->psz_id, p_sys->ppsz_order[i] ) == 0 )
599
600
601
602
603
                {
                    i_real_index = i;
                    break;
                }
            }
604
            if ( i == p_sys->i_order_length )
605
606
                i_real_index = ++i_greatest_real_index_used;
        }
607
        i_row = ( i_real_index / p_sys->i_cols ) % p_sys->i_rows;
608
609
        i_col = i_real_index % p_sys->i_cols ;

610
        if ( !p_sys->b_keep )
611
        {
612
            /* Convert the images */
613
614
615
            fmt_in.i_chroma = p_es->p_picture->format.i_chroma;
            fmt_in.i_height = p_es->p_picture->format.i_height;
            fmt_in.i_width = p_es->p_picture->format.i_width;
616

617
618
619
            if( fmt_in.i_chroma == VLC_CODEC_YUVA ||
                fmt_in.i_chroma == VLC_CODEC_RGBA )
                fmt_out.i_chroma = VLC_CODEC_YUVA;
620
            else
621
                fmt_out.i_chroma = VLC_CODEC_I420;
622
623
624
            fmt_out.i_width = col_inner_width;
            fmt_out.i_height = row_inner_height;

625
            if( p_sys->b_ar ) /* keep aspect ratio */
626
            {
627
628
629
630
                if( (float)fmt_out.i_width / (float)fmt_out.i_height
                      > (float)fmt_in.i_width / (float)fmt_in.i_height )
                {
                    fmt_out.i_width = ( fmt_out.i_height * fmt_in.i_width )
631
                                         / fmt_in.i_height;
632
633
634
635
                }
                else
                {
                    fmt_out.i_height = ( fmt_out.i_width * fmt_in.i_height )
636
                                        / fmt_in.i_width;
637
638
                }
             }
639

640
641
            fmt_out.i_visible_width = fmt_out.i_width;
            fmt_out.i_visible_height = fmt_out.i_height;
dionoea's avatar
dionoea committed
642

643
            p_converted = image_Convert( p_sys->p_image, p_es->p_picture,
644
645
646
                                         &fmt_in, &fmt_out );
            if( !p_converted )
            {
647
648
                msg_Warn( p_filter,
                           "image resizing and chroma conversion failed" );
649
650
651
652
                continue;
            }
        }
        else
dionoea's avatar
dionoea committed
653
        {
654
            p_converted = p_es->p_picture;
655
656
657
658
659
            fmt_in.i_width = fmt_out.i_width = p_converted->format.i_width;
            fmt_in.i_height = fmt_out.i_height = p_converted->format.i_height;
            fmt_in.i_chroma = fmt_out.i_chroma = p_converted->format.i_chroma;
            fmt_out.i_visible_width = fmt_out.i_width;
            fmt_out.i_visible_height = fmt_out.i_height;
dionoea's avatar
dionoea committed
660
661
        }

662
        p_region = subpicture_region_New( &fmt_out );
663
        /* FIXME the copy is probably not needed anymore */
664
        if( p_region )
665
            picture_Copy( p_region->p_picture, p_converted );
666
667
668
        if( !p_sys->b_keep )
            picture_Release( p_converted );

dionoea's avatar
dionoea committed
669
670
671
672
        if( !p_region )
        {
            msg_Err( p_filter, "cannot allocate SPU region" );
            p_filter->pf_sub_buffer_del( p_filter, p_spu );
673
            vlc_mutex_unlock( p_sys->p_lock );
674
            vlc_mutex_unlock( &p_sys->lock );
675
            return p_spu;
dionoea's avatar
dionoea committed
676
677
        }

678
        if( p_es->i_x >= 0 && p_es->i_y >= 0 )
679
        {
680
681
            p_region->i_x = p_es->i_x;
            p_region->i_y = p_es->i_y;
682
        }
683
        else if( p_sys->i_position == position_offsets )
684
        {
685
            p_region->i_x = p_sys->pi_x_offsets[i_real_index];
686
687
            p_region->i_y = p_sys->pi_y_offsets[i_real_index];
        }
688
689
        else
        {
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
            if( fmt_out.i_width > col_inner_width ||
                p_sys->b_ar || p_sys->b_keep )
            {
                /* we don't have to center the video since it takes the
                whole rectangle area or it's larger than the rectangle */
                p_region->i_x = p_sys->i_xoffset
                            + i_col * ( p_sys->i_width / p_sys->i_cols )
                            + ( i_col * p_sys->i_borderw ) / p_sys->i_cols;
            }
            else
            {
                /* center the video in the dedicated rectangle */
                p_region->i_x = p_sys->i_xoffset
                        + i_col * ( p_sys->i_width / p_sys->i_cols )
                        + ( i_col * p_sys->i_borderw ) / p_sys->i_cols
                        + ( col_inner_width - fmt_out.i_width ) / 2;
            }

            if( fmt_out.i_height > row_inner_height
                || p_sys->b_ar || p_sys->b_keep )
            {
                /* we don't have to center the video since it takes the
                whole rectangle area or it's taller than the rectangle */
                p_region->i_y = p_sys->i_yoffset
                        + i_row * ( p_sys->i_height / p_sys->i_rows )
                        + ( i_row * p_sys->i_borderh ) / p_sys->i_rows;
            }
            else
            {
                /* center the video in the dedicated rectangle */
                p_region->i_y = p_sys->i_yoffset
                        + i_row * ( p_sys->i_height / p_sys->i_rows )
                        + ( i_row * p_sys->i_borderh ) / p_sys->i_rows
                        + ( row_inner_height - fmt_out.i_height ) / 2;
            }
725
        }
Laurent Aimar's avatar
Laurent Aimar committed
726
        p_region->i_align = p_sys->i_align;
727
        p_region->i_alpha = p_es->i_alpha;
728

729
730
        if( p_region_prev == NULL )
        {
dionoea's avatar
dionoea committed
731
            p_spu->p_region = p_region;
732
733
734
        }
        else
        {
dionoea's avatar
dionoea committed
735
736
737
738
739
740
            p_region_prev->p_next = p_region;
        }

        p_region_prev = p_region;
    }

741
    vlc_mutex_unlock( p_sys->p_lock );
742
    vlc_mutex_unlock( &p_sys->lock );
dionoea's avatar
dionoea committed
743
744
745

    return p_spu;
}
746
747
748
749
750
751
752
753

/*****************************************************************************
* Callback to update params on the fly
*****************************************************************************/
static int MosaicCallback( vlc_object_t *p_this, char const *psz_var,
                            vlc_value_t oldval, vlc_value_t newval,
                            void *p_data )
{
Rafaël Carré's avatar
Rafaël Carré committed
754
    VLC_UNUSED(oldval);
755
    filter_sys_t *p_sys = (filter_sys_t *) p_data;
dionoea's avatar
Mosaic:    
dionoea committed
756
757
758

#define VAR_IS( a ) !strcmp( psz_var, CFG_PREFIX a )
    if( VAR_IS( "alpha" ) )
759
    {
760
        vlc_mutex_lock( &p_sys->lock );
761
        msg_Dbg( p_this, "changing alpha from %d/255 to %d/255",
762
763
                         p_sys->i_alpha, newval.i_int);
        p_sys->i_alpha = __MIN( __MAX( newval.i_int, 0 ), 255 );
764
        vlc_mutex_unlock( &p_sys->lock );
765
    }
dionoea's avatar
Mosaic:    
dionoea committed
766
    else if( VAR_IS( "height" ) )
767
    {
768
        vlc_mutex_lock( &p_sys->lock );
769
        msg_Dbg( p_this, "changing height from %dpx to %dpx",
770
771
                          p_sys->i_height, newval.i_int );
        p_sys->i_height = __MAX( newval.i_int, 0 );
772
        vlc_mutex_unlock( &p_sys->lock );
773
    }
dionoea's avatar
Mosaic:    
dionoea committed
774
    else if( VAR_IS( "width" ) )
775
    {
776
        vlc_mutex_lock( &p_sys->lock );
777
        msg_Dbg( p_this, "changing width from %dpx to %dpx",
778
779
                         p_sys->i_width, newval.i_int );
        p_sys->i_width = __MAX( newval.i_int, 0 );
780
        vlc_mutex_unlock( &p_sys->lock );
781
    }
dionoea's avatar
Mosaic:    
dionoea committed
782
    else if( VAR_IS( "xoffset" ) )
783
    {
784
        vlc_mutex_lock( &p_sys->lock );
785
        msg_Dbg( p_this, "changing x offset from %dpx to %dpx",
786
787
                         p_sys->i_xoffset, newval.i_int );
        p_sys->i_xoffset = __MAX( newval.i_int, 0 );
788
        vlc_mutex_unlock( &p_sys->lock );
789
    }
dionoea's avatar
Mosaic:    
dionoea committed
790
    else if( VAR_IS( "yoffset" ) )
791
    {
792
        vlc_mutex_lock( &p_sys->lock );
793
        msg_Dbg( p_this, "changing y offset from %dpx to %dpx",
794
795
                         p_sys->i_yoffset, newval.i_int );
        p_sys->i_yoffset = __MAX( newval.i_int, 0 );
796
        vlc_mutex_unlock( &p_sys->lock );
797
    }
dionoea's avatar
Mosaic:    
dionoea committed
798
    else if( VAR_IS( "align" ) )
799
800
801
802
803
804
805
806
    {
        int i_old = 0, i_new = 0;
        vlc_mutex_lock( &p_sys->lock );
        newval.i_int = __MIN( __MAX( newval.i_int, 0 ), 10 );
        if( newval.i_int == 3 || newval.i_int == 7 )
            newval.i_int = 5;
        while( pi_align_values[i_old] != p_sys->i_align ) i_old++;
        while( pi_align_values[i_new] != newval.i_int ) i_new++;
807
        msg_Dbg( p_this, "changing alignment from %d (%s) to %d (%s)",
808
809
810
811
812
                     p_sys->i_align, ppsz_align_descriptions[i_old],
                     newval.i_int, ppsz_align_descriptions[i_new] );
        p_sys->i_align = newval.i_int;
        vlc_mutex_unlock( &p_sys->lock );
    }
dionoea's avatar
Mosaic:    
dionoea committed
813
    else if( VAR_IS( "borderw" ) )
814
    {
815
        vlc_mutex_lock( &p_sys->lock );
816
817
818
        msg_Dbg( p_this, "changing border width from %dpx to %dpx",
                         p_sys->i_borderw, newval.i_int );
        p_sys->i_borderw = __MAX( newval.i_int, 0 );
819
        vlc_mutex_unlock( &p_sys->lock );
820
    }
dionoea's avatar
Mosaic:    
dionoea committed
821
    else if( VAR_IS( "borderh" ) )
822
    {
823
        vlc_mutex_lock( &p_sys->lock );
824
825
826
        msg_Dbg( p_this, "changing border height from %dpx to %dpx",
                         p_sys->i_borderh, newval.i_int );
        p_sys->i_borderh = __MAX( newval.i_int, 0 );
827
        vlc_mutex_unlock( &p_sys->lock );
828
    }
dionoea's avatar
Mosaic:    
dionoea committed
829
    else if( VAR_IS( "position" ) )
830
    {
831
        if( newval.i_int > 2 || newval.i_int < 0 )
832
        {
833
834
835
836
837
            msg_Err( p_this,
                     "Position is either 0 (%s), 1 (%s) or 2 (%s)",
                     ppsz_pos_descriptions[0],
                     ppsz_pos_descriptions[1],
                     ppsz_pos_descriptions[2] );
838
839
840
        }
        else
        {
841
            vlc_mutex_lock( &p_sys->lock );
842
            msg_Dbg( p_this, "changing position method from %d (%s) to %d (%s)",
dionoea's avatar
Mosaic:    
dionoea committed
843
844
                    p_sys->i_position, ppsz_pos_descriptions[p_sys->i_position],
                    newval.i_int, ppsz_pos_descriptions[newval.i_int]);
845
            p_sys->i_position = newval.i_int;
846
            vlc_mutex_unlock( &p_sys->lock );
847
848
        }
    }
dionoea's avatar
Mosaic:    
dionoea committed
849
    else if( VAR_IS( "rows" ) )
850
    {
851
        vlc_mutex_lock( &p_sys->lock );
852
        msg_Dbg( p_this, "changing number of rows from %d to %d",
853
854
                         p_sys->i_rows, newval.i_int );
        p_sys->i_rows = __MAX( newval.i_int, 1 );
855
        vlc_mutex_unlock( &p_sys->lock );
856
    }
dionoea's avatar
Mosaic:    
dionoea committed
857
    else if( VAR_IS( "cols" ) )
858
    {
859
        vlc_mutex_lock( &p_sys->lock );
860
        msg_Dbg( p_this, "changing number of columns from %d to %d",
861
862
                         p_sys->i_cols, newval.i_int );
        p_sys->i_cols = __MAX( newval.i_int, 1 );
863
        vlc_mutex_unlock( &p_sys->lock );
864
    }
dionoea's avatar
Mosaic:    
dionoea committed
865
    else if( VAR_IS( "order" ) )
866
    {
zorglub's avatar
zorglub committed
867
868
        char *psz_order;
        int i_index;
869
870
871
872
873
874
875
        vlc_mutex_lock( &p_sys->lock );
        msg_Dbg( p_this, "Changing mosaic order to %s", newval.psz_string );

        psz_order = newval.psz_string;

        while( p_sys->i_order_length-- )
        {
876
            free( p_sys->ppsz_order[p_sys->i_order_length] );
877
        }
878
879
880
881
        free( p_sys->ppsz_order );
        p_sys->ppsz_order = NULL;

        if( *psz_order )
882
883
884
885
886
887
888
889
890
891
892
893
        {
            char *psz_end = NULL;
            i_index = 0;
            do
            {
                psz_end = strchr( psz_order, ',' );
                i_index++;
                p_sys->ppsz_order = realloc( p_sys->ppsz_order,
                                    i_index * sizeof(char *) );
                p_sys->ppsz_order[i_index - 1] =