wall.c 14 KB
Newer Older
1
2
3
/*****************************************************************************
 * wall.c : Wall video plugin for vlc
 *****************************************************************************
4
 * Copyright (C) 2000-2009 the VideoLAN team
5
 * $Id$
6
7
8
9
10
11
12
 *
 * Authors: Samuel Hocevar <sam@zoy.org>
 *
 * 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.
13
 *
14
15
16
17
18
19
20
 * 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
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22
23
24
25
26
27
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

28
29
30
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
31
#include <assert.h>
32

33
#include <vlc_common.h>
34
#include <vlc_plugin.h>
35
#include <vlc_video_splitter.h>
36

37
38
/* FIXME it is needed for VOUT_ALIGN_* only */
#include <vlc_vout.h>
39

40
41
42
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
Christophe Massiot's avatar
Christophe Massiot committed
43
#define COLS_TEXT N_("Number of columns")
44
#define COLS_LONGTEXT N_("Number of horizontal windows in " \
45
    "which to split the video.")
46

Christophe Massiot's avatar
Christophe Massiot committed
47
#define ROWS_TEXT N_("Number of rows")
48
#define ROWS_LONGTEXT N_("Number of vertical windows in " \
49
    "which to split the video.")
50

Christophe Massiot's avatar
Christophe Massiot committed
51
#define ACTIVE_TEXT N_("Active windows")
52
#define ACTIVE_LONGTEXT N_("Comma-separated list of active windows, " \
53
54
    "defaults to all")

55
#define ASPECT_TEXT N_("Element aspect ratio")
56
57
#define ASPECT_LONGTEXT N_("Aspect ratio of the individual displays " \
   "building the wall.")
58

59
60
#define CFG_PREFIX "wall-"

61
62
63
64
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );

vlc_module_begin()
65
66
    set_description( N_("Wall video filter") )
    set_shortname( N_("Image wall" ))
67
    set_capability( "video splitter", 0 )
68
69
70
71
72
    set_category( CAT_VIDEO )
    set_subcategory( SUBCAT_VIDEO_VFILTER )

    add_integer( CFG_PREFIX "cols", 3, NULL, COLS_TEXT, COLS_LONGTEXT, false )
    add_integer( CFG_PREFIX "rows", 3, NULL, ROWS_TEXT, ROWS_LONGTEXT, false )
73
    add_string( CFG_PREFIX "active", NULL, NULL, ACTIVE_TEXT, ACTIVE_LONGTEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
74
                 true )
75
    add_string( CFG_PREFIX "element-aspect", "4:3", NULL, ASPECT_TEXT, ASPECT_LONGTEXT, false )
hartman's avatar
hartman committed
76

77
    add_shortcut( "wall" )
78
79
    set_callbacks( Open, Close )
vlc_module_end()
80

81
82
83
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
84
static const char *const ppsz_filter_options[] = {
85
86
87
    "cols", "rows", "active", "element-aspect", NULL
};

88
89
/* */
typedef struct
90
{
91
92
93
94
95
96
97
98
99
100
101
102
    bool b_active;
    int  i_output;
    int  i_width;
    int  i_height;
    int  i_align;
    int  i_left;
    int  i_top;
} wall_output_t;

#define ROW_MAX (15)
#define COL_MAX (15)
struct video_splitter_sys_t
103
{
104
105
106
    int           i_col;
    int           i_row;
    int           i_output;
Laurent Aimar's avatar
Laurent Aimar committed
107
    wall_output_t pp_output[COL_MAX][ROW_MAX]; /* [x][y] */
108
};
109

110
111
112
113
static int Filter( video_splitter_t *, picture_t *pp_dst[], picture_t * );
static int Mouse( video_splitter_t *, vlc_mouse_t *,
                  int i_index,
                  const vlc_mouse_t *p_old, const vlc_mouse_t *p_new );
114

115
116
117
118
/**
 * This function allocates and initializes a Wall splitter module.
 */
static int Open( vlc_object_t *p_this )
119
{
120
121
    video_splitter_t *p_splitter = (video_splitter_t*)p_this;
    video_splitter_sys_t *p_sys;
122

123
124
    p_splitter->p_sys = p_sys = malloc( sizeof(*p_sys) );
    if( !p_sys )
125
        return VLC_ENOMEM;
126

127
128
    config_ChainParse( p_splitter, CFG_PREFIX, ppsz_filter_options,
                       p_splitter->p_cfg );
129

130
131
132
    /* */
    p_sys->i_col = var_CreateGetInteger( p_splitter, CFG_PREFIX "cols" );
    p_sys->i_col = __MAX( 1, __MIN( COL_MAX, p_sys->i_col ) );
133

134
135
    p_sys->i_row = var_CreateGetInteger( p_splitter, CFG_PREFIX "rows" );
    p_sys->i_row = __MAX( 1, __MIN( ROW_MAX, p_sys->i_row ) );
136

137
138
    msg_Dbg( p_splitter, "opening a %i x %i wall",
             p_sys->i_col, p_sys->i_row );
139

140
141
    /* */
    char *psz_state = var_CreateGetNonEmptyString( p_splitter, CFG_PREFIX "active" );
142

143
144
145
146
    /* */
    bool pb_active[COL_MAX*ROW_MAX];
    for( int i = 0; i < COL_MAX*ROW_MAX; i++ )
        pb_active[i] = psz_state == NULL;
147

148
149
150
    /* Parse active list if provided */
    char *psz_tmp = psz_state;
    while( psz_tmp && *psz_tmp )
151
    {
152
153
154
        char *psz_next = strchr( psz_tmp, ',' );
        if( psz_next )
            *psz_next++ = '\0';
155

156
157
158
        const int i_index = atoi( psz_tmp );
        if( i_index >= 0 && i_index < COL_MAX*ROW_MAX )
            pb_active[i_index] = true;
Laurent Aimar's avatar
Laurent Aimar committed
159
160

        psz_tmp = psz_next;
161
    }
162
    free( psz_state );
163

164
165
166
167
168
    /* Parse aspect ratio if provided */
    int i_aspect = 0;
    char *psz_aspect = var_CreateGetNonEmptyString( p_splitter,
                                                    CFG_PREFIX "element-aspect" );
    if( psz_aspect )
169
    {
170
171
172
        int i_ar_num, i_ar_den;
        if( sscanf( psz_aspect, "%d:%d", &i_ar_num, &i_ar_den ) == 2 &&
            i_ar_num > 0 && i_ar_den > 0 )
173
        {
174
            i_aspect = i_ar_num * VOUT_ASPECT_FACTOR / i_ar_den;
175
176
177
        }
        else
        {
178
            msg_Warn( p_splitter, "invalid aspect ratio specification" );
179
        }
180
        free( psz_aspect );
181
    }
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    if( i_aspect <= 0 )
        i_aspect = 4 * VOUT_ASPECT_FACTOR / 3;

    /* Compute placements/size of the windows */
    const unsigned w1 = ( p_splitter->fmt.i_width / p_sys->i_col ) & ~1;
    const unsigned h1 = ( w1 * VOUT_ASPECT_FACTOR / i_aspect ) & ~1;

    const unsigned h2 = ( p_splitter->fmt.i_height / p_sys->i_row ) & ~1;
    const unsigned w2 = ( h2 * i_aspect / VOUT_ASPECT_FACTOR ) & ~1;

    unsigned i_target_width;
    unsigned i_target_height;
    unsigned i_hstart, i_hend;
    unsigned i_vstart, i_vend;
    bool b_vstart_rounded;
    bool b_hstart_rounded;

    if( h1 * p_sys->i_row < p_splitter->fmt.i_height )
200
    {
201
        i_target_width = w2;
202
        i_target_height = h2;
203

204
        i_vstart = 0;
205
206
207
208
209
210
211
212
213
214
215
        b_vstart_rounded = false;
        i_vend = p_splitter->fmt.i_height;

        unsigned i_tmp = i_target_width * p_sys->i_col;
        while( i_tmp < p_splitter->fmt.i_width )
            i_tmp += p_sys->i_col;

        i_hstart = (( i_tmp - p_splitter->fmt.i_width ) / 2)&~1;
        b_hstart_rounded  = ( ( i_tmp - p_splitter->fmt.i_width ) % 2 ) ||
            ( ( ( i_tmp - p_splitter->fmt.i_width ) / 2 ) & 1 );
        i_hend = i_hstart + p_splitter->fmt.i_width;
216
217
218
219
220
    }
    else
    {
        i_target_height = h1;
        i_target_width = w1;
221

222
223
224
        i_hstart = 0;
        b_hstart_rounded = false;
        i_hend = p_splitter->fmt.i_width;
225

226
227
228
        unsigned i_tmp = i_target_height * p_sys->i_row;
        while( i_tmp < p_splitter->fmt.i_height )
            i_tmp += p_sys->i_row;
229

230
231
232
233
234
235
236
        i_vstart = ( ( i_tmp - p_splitter->fmt.i_height ) / 2 ) & ~1;
        b_vstart_rounded  = ( ( i_tmp - p_splitter->fmt.i_height ) % 2 ) ||
            ( ( ( i_tmp - p_splitter->fmt.i_height ) / 2 ) & 1 );
        i_vend = i_vstart + p_splitter->fmt.i_height;
    }
    msg_Dbg( p_splitter, "target resolution %dx%d", i_target_width, i_target_height );
    msg_Dbg( p_splitter, "target window (%d,%d)-(%d,%d)", i_hstart,i_vstart,i_hend,i_vend );
237

238
239
    int i_active = 0;
    for( int y = 0, i_top = 0; y < p_sys->i_row; y++ )
240
    {
241
242
243
244
245
        /* */
        int i_height = 0;
        int i_halign = 0;
        if( y * i_target_height >= i_vstart &&
            ( y + 1 ) * i_target_height <= i_vend )
246
        {
247
248
249
250
251
252
253
254
255
256
257
258
            i_height = i_target_height;
        }
        else if( ( y + 1 ) * i_target_height < i_vstart ||
                 ( y * i_target_height ) > i_vend )
        {
            i_height = 0;
        }
        else
        {
            i_height = ( i_target_height -
                         i_vstart%i_target_height );
            if(  y >= ( p_sys->i_row / 2 ) )
259
            {
260
261
                i_halign = VOUT_ALIGN_TOP;
                i_height -= b_vstart_rounded ? 2: 0;
262
263
264
            }
            else
            {
265
                i_halign = VOUT_ALIGN_BOTTOM;
266
            }
267
        }
268

269
270
271
272
273
274
275
276
277
278
        /* */
        for( int x = 0, i_left = 0; x < p_sys->i_col; x++ )
        {
            wall_output_t *p_output = &p_sys->pp_output[x][y];

            /* */
            int i_width;
            int i_valign = 0;
            if( x*i_target_width >= i_hstart &&
                (x+1)*i_target_width <= i_hend )
279
            {
280
                i_width = i_target_width;
281
            }
282
283
            else if( ( x + 1 ) * i_target_width < i_hstart ||
                     ( x * i_target_width ) > i_hend )
284
            {
285
                i_width = 0;
286
287
288
            }
            else
            {
289
290
                i_width = ( i_target_width - i_hstart % i_target_width );
                if( x >= ( p_sys->i_col / 2 ) )
291
                {
292
293
                    i_valign = VOUT_ALIGN_LEFT;
                    i_width -= b_hstart_rounded ? 2: 0;
294
295
296
                }
                else
                {
297
                    i_valign = VOUT_ALIGN_RIGHT;
298
                }
299
            }
300

301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
            /* */
            p_output->b_active = pb_active[y * p_sys->i_col + x] &&
                                 i_height > 0 && i_width > 0;
            p_output->i_output = -1;
            p_output->i_align = i_valign | i_halign;
            p_output->i_width = i_width;
            p_output->i_height = i_height;
            p_output->i_left = i_left;
            p_output->i_top = i_top;

            msg_Dbg( p_splitter, "window %dx%d at %d:%d size %dx%d", 
                     x, y, i_left, i_top, i_width, i_height );

            if( p_output->b_active )
                i_active++;

317
            i_left += i_width;
318
319
320
321
322
323
324
325
326
        }
        i_top += i_height;
    }
    if( i_active <= 0 )
    {
        msg_Err( p_splitter, "No active video output" );
        free( p_sys );
        return VLC_EGENERIC;
    }
327

328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
    /* Setup output configuration */
    p_splitter->i_output = i_active;
    p_splitter->p_output = calloc( p_splitter->i_output,
                                   sizeof(*p_splitter->p_output) );
    if( !p_splitter->p_output )
    {
        free( p_sys );
        return VLC_ENOMEM;
    }
    for( int y = 0, i_output = 0; y < p_sys->i_row; y++ )
    {
        for( int x = 0; x < p_sys->i_col; x++ )
        {
            wall_output_t *p_output = &p_sys->pp_output[x][y];
            if( !p_output->b_active )
343
                continue;
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359

            p_output->i_output = i_output++;

            video_splitter_output_t *p_cfg = &p_splitter->p_output[p_output->i_output];

            video_format_Copy( &p_cfg->fmt, &p_splitter->fmt );
            p_cfg->fmt.i_visible_width  =
            p_cfg->fmt.i_width          = p_output->i_width;
            p_cfg->fmt.i_visible_height =
            p_cfg->fmt.i_height         = p_output->i_height;
            p_cfg->fmt.i_aspect         = (int64_t)i_aspect * i_target_height * p_output->i_width / i_target_width / p_output->i_height;

            p_cfg->window.i_x     = p_output->i_left; /* FIXME relative to video-x/y (TODO in wrapper.c) ? */
            p_cfg->window.i_y     = p_output->i_top;
            p_cfg->window.i_align = p_output->i_align;
            p_cfg->psz_module = NULL;
360
361
362
        }
    }

363
364
365
    /* */
    p_splitter->pf_filter = Filter;
    p_splitter->pf_mouse = Mouse;
gbazin's avatar
   
gbazin committed
366

367
    return VLC_SUCCESS;
368
369
}

370
371
372
373
/**
 * Terminate a splitter module.
 */
static void Close( vlc_object_t *p_this )
374
{
375
376
    video_splitter_t *p_splitter = (video_splitter_t*)p_this;
    video_splitter_sys_t *p_sys = p_splitter->p_sys;
377

378
379
    free( p_splitter->p_output );
    free( p_sys );
380
381
}

382
static int Filter( video_splitter_t *p_splitter, picture_t *pp_dst[], picture_t *p_src )
383
{
384
    video_splitter_sys_t *p_sys = p_splitter->p_sys;
385

386
    if( video_splitter_NewPicture( p_splitter, pp_dst ) )
387
388
    {
        picture_Release( p_src );
389
        return VLC_EGENERIC;
390
    }
391

392
    for( int y = 0; y < p_sys->i_row; y++ )
393
    {
394
        for( int x = 0; x < p_sys->i_col; x++ )
395
        {
396
397
            wall_output_t *p_output = &p_sys->pp_output[x][y];
            if( !p_output->b_active )
398
399
                continue;

400
401
            video_splitter_output_t *p_cfg = &p_splitter->p_output[p_output->i_output];
            picture_t *p_dst = pp_dst[p_output->i_output];
402

403
404
405
            /* */
            picture_t tmp = *p_src;
            for( int i = 0; i < tmp.i_planes; i++ )
406
            {
407
408
409
410
                plane_t *p0 = &tmp.p[0];
                plane_t *p = &tmp.p[i];
                const int i_y = p_output->i_top  * p->i_visible_pitch / p0->i_visible_pitch;
                const int i_x = p_output->i_left * p->i_visible_lines / p0->i_visible_lines;
411

412
                p->p_pixels += i_y * p->i_pitch + ( i_x - (i_x % p->i_pixel_pitch));
413
            }
414
            picture_Copy( p_dst, &tmp );
415
416
        }
    }
417

418
    picture_Release( p_src );
419
420
    return VLC_SUCCESS;
}
421
422
423
static int Mouse( video_splitter_t *p_splitter, vlc_mouse_t *p_mouse,
                  int i_index,
                  const vlc_mouse_t *p_old, const vlc_mouse_t *p_new )
gbazin's avatar
   
gbazin committed
424
{
425
    video_splitter_sys_t *p_sys = p_splitter->p_sys;
gbazin's avatar
   
gbazin committed
426

427
    for( int y = 0; y < p_sys->i_row; y++ )
gbazin's avatar
   
gbazin committed
428
    {
429
        for( int x = 0; x < p_sys->i_col; x++ )
gbazin's avatar
   
gbazin committed
430
        {
ivoire's avatar
ivoire committed
431
            wall_output_t *p_output = &p_sys->pp_output[x][y];
432
            if( p_output->b_active && p_output->i_output == i_index )
433
            {
434
435
436
437
                *p_mouse = *p_new;
                p_mouse->i_x += p_output->i_left;
                p_mouse->i_y += p_output->i_top;
                return VLC_SUCCESS;
438
            }
gbazin's avatar
   
gbazin committed
439
440
        }
    }
441
442
    assert(0);
    return VLC_EGENERIC;
gbazin's avatar
   
gbazin committed
443
}
444