]> git.sesse.net Git - vlc/blob - modules/video_splitter/wall.c
decoder: drain the audio output properly
[vlc] / modules / video_splitter / wall.c
1 /*****************************************************************************
2  * wall.c : Wall video plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2000-2009 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Samuel Hocevar <sam@zoy.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31 #include <assert.h>
32
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_video_splitter.h>
36
37 /* FIXME it is needed for VOUT_ALIGN_* only */
38 #include <vlc_vout.h>
39
40 #define ROW_MAX (15)
41 #define COL_MAX (15)
42
43 /*****************************************************************************
44  * Module descriptor
45  *****************************************************************************/
46 #define COLS_TEXT N_("Number of columns")
47 #define COLS_LONGTEXT N_("Number of horizontal windows in " \
48     "which to split the video.")
49
50 #define ROWS_TEXT N_("Number of rows")
51 #define ROWS_LONGTEXT N_("Number of vertical windows in " \
52     "which to split the video.")
53
54 #define ACTIVE_TEXT N_("Active windows")
55 #define ACTIVE_LONGTEXT N_("Comma-separated list of active windows, " \
56     "defaults to all")
57
58 #define ASPECT_TEXT N_("Element aspect ratio")
59 #define ASPECT_LONGTEXT N_("Aspect ratio of the individual displays " \
60    "building the wall.")
61
62 #define CFG_PREFIX "wall-"
63
64 static int  Open ( vlc_object_t * );
65 static void Close( vlc_object_t * );
66
67 vlc_module_begin()
68     set_description( N_("Wall video filter") )
69     set_shortname( N_("Image wall" ))
70     set_capability( "video splitter", 0 )
71     set_category( CAT_VIDEO )
72     set_subcategory( SUBCAT_VIDEO_VFILTER )
73
74     add_integer( CFG_PREFIX "cols", 3, COLS_TEXT, COLS_LONGTEXT, false )
75     change_integer_range( 1, COL_MAX )
76     add_integer( CFG_PREFIX "rows", 3, ROWS_TEXT, ROWS_LONGTEXT, false )
77     change_integer_range( 1, ROW_MAX )
78     add_string( CFG_PREFIX "active", NULL, ACTIVE_TEXT, ACTIVE_LONGTEXT,
79                  true )
80     add_string( CFG_PREFIX "element-aspect", "16:9", ASPECT_TEXT, ASPECT_LONGTEXT, false )
81
82     add_shortcut( "wall" )
83     set_callbacks( Open, Close )
84 vlc_module_end()
85
86 /*****************************************************************************
87  * Local prototypes
88  *****************************************************************************/
89 static const char *const ppsz_filter_options[] = {
90     "cols", "rows", "active", "element-aspect", NULL
91 };
92
93 /* */
94 typedef struct
95 {
96     bool b_active;
97     int  i_output;
98     int  i_width;
99     int  i_height;
100     int  i_align;
101     int  i_left;
102     int  i_top;
103 } wall_output_t;
104
105 struct video_splitter_sys_t
106 {
107     int           i_col;
108     int           i_row;
109     int           i_output;
110     wall_output_t pp_output[COL_MAX][ROW_MAX]; /* [x][y] */
111 };
112
113 static int Filter( video_splitter_t *, picture_t *pp_dst[], picture_t * );
114 static int Mouse( video_splitter_t *, vlc_mouse_t *,
115                   int i_index,
116                   const vlc_mouse_t *p_old, const vlc_mouse_t *p_new );
117
118 /**
119  * This function allocates and initializes a Wall splitter module.
120  */
121 static int Open( vlc_object_t *p_this )
122 {
123     video_splitter_t *p_splitter = (video_splitter_t*)p_this;
124     video_splitter_sys_t *p_sys;
125
126     const vlc_chroma_description_t *p_chroma =
127         vlc_fourcc_GetChromaDescription( p_splitter->fmt.i_chroma );
128     if( p_chroma == NULL || p_chroma->plane_count == 0 )
129         return VLC_EGENERIC;
130
131     p_splitter->p_sys = p_sys = malloc( sizeof(*p_sys) );
132     if( !p_sys )
133         return VLC_ENOMEM;
134
135     config_ChainParse( p_splitter, CFG_PREFIX, ppsz_filter_options,
136                        p_splitter->p_cfg );
137
138     /* */
139     p_sys->i_col = var_CreateGetInteger( p_splitter, CFG_PREFIX "cols" );
140     p_sys->i_col = VLC_CLIP( p_sys->i_col, 1, COL_MAX );
141
142     p_sys->i_row = var_CreateGetInteger( p_splitter, CFG_PREFIX "rows" );
143     p_sys->i_row = VLC_CLIP( p_sys->i_row, 1, ROW_MAX );
144
145     msg_Dbg( p_splitter, "opening a %i x %i wall",
146              p_sys->i_col, p_sys->i_row );
147
148     /* */
149     char *psz_state = var_CreateGetNonEmptyString( p_splitter, CFG_PREFIX "active" );
150
151     /* */
152     bool pb_active[COL_MAX*ROW_MAX];
153     for( int i = 0; i < COL_MAX*ROW_MAX; i++ )
154         pb_active[i] = psz_state == NULL;
155
156     /* Parse active list if provided */
157     char *psz_tmp = psz_state;
158     while( psz_tmp && *psz_tmp )
159     {
160         char *psz_next = strchr( psz_tmp, ',' );
161         if( psz_next )
162             *psz_next++ = '\0';
163
164         const int i_index = atoi( psz_tmp );
165         if( i_index >= 0 && i_index < COL_MAX*ROW_MAX )
166             pb_active[i_index] = true;
167
168         psz_tmp = psz_next;
169     }
170     free( psz_state );
171
172     /* Parse aspect ratio if provided */
173     int i_aspect = 0;
174     char *psz_aspect = var_CreateGetNonEmptyString( p_splitter,
175                                                     CFG_PREFIX "element-aspect" );
176     if( psz_aspect )
177     {
178         int i_ar_num, i_ar_den;
179         if( sscanf( psz_aspect, "%d:%d", &i_ar_num, &i_ar_den ) == 2 &&
180             i_ar_num > 0 && i_ar_den > 0 )
181         {
182             i_aspect = i_ar_num * VOUT_ASPECT_FACTOR / i_ar_den;
183         }
184         else
185         {
186             msg_Warn( p_splitter, "invalid aspect ratio specification" );
187         }
188         free( psz_aspect );
189     }
190     if( i_aspect <= 0 )
191         i_aspect = 4 * VOUT_ASPECT_FACTOR / 3;
192
193     /* Compute placements/size of the windows */
194     const unsigned w1 = ( p_splitter->fmt.i_width / p_sys->i_col ) & ~1;
195     const unsigned h1 = ( w1 * VOUT_ASPECT_FACTOR / i_aspect ) & ~1;
196
197     const unsigned h2 = ( p_splitter->fmt.i_height / p_sys->i_row ) & ~1;
198     const unsigned w2 = ( h2 * i_aspect / VOUT_ASPECT_FACTOR ) & ~1;
199
200     unsigned i_target_width;
201     unsigned i_target_height;
202     unsigned i_hstart, i_hend;
203     unsigned i_vstart, i_vend;
204     bool b_vstart_rounded;
205     bool b_hstart_rounded;
206
207     if( h1 * p_sys->i_row < p_splitter->fmt.i_height )
208     {
209         i_target_width = w2;
210         i_target_height = h2;
211
212         i_vstart = 0;
213         b_vstart_rounded = false;
214         i_vend = p_splitter->fmt.i_height;
215
216         unsigned i_tmp = i_target_width * p_sys->i_col;
217         while( i_tmp < p_splitter->fmt.i_width )
218             i_tmp += p_sys->i_col;
219
220         i_hstart = (( i_tmp - p_splitter->fmt.i_width ) / 2)&~1;
221         b_hstart_rounded  = ( ( i_tmp - p_splitter->fmt.i_width ) % 2 ) ||
222             ( ( ( i_tmp - p_splitter->fmt.i_width ) / 2 ) & 1 );
223         i_hend = i_hstart + p_splitter->fmt.i_width;
224     }
225     else
226     {
227         i_target_height = h1;
228         i_target_width = w1;
229
230         i_hstart = 0;
231         b_hstart_rounded = false;
232         i_hend = p_splitter->fmt.i_width;
233
234         unsigned i_tmp = i_target_height * p_sys->i_row;
235         while( i_tmp < p_splitter->fmt.i_height )
236             i_tmp += p_sys->i_row;
237
238         i_vstart = ( ( i_tmp - p_splitter->fmt.i_height ) / 2 ) & ~1;
239         b_vstart_rounded  = ( ( i_tmp - p_splitter->fmt.i_height ) % 2 ) ||
240             ( ( ( i_tmp - p_splitter->fmt.i_height ) / 2 ) & 1 );
241         i_vend = i_vstart + p_splitter->fmt.i_height;
242     }
243     msg_Dbg( p_splitter, "target resolution %dx%d", i_target_width, i_target_height );
244     msg_Dbg( p_splitter, "target window (%d,%d)-(%d,%d)", i_hstart,i_vstart,i_hend,i_vend );
245
246     int i_active = 0;
247     for( int y = 0, i_top = 0; y < p_sys->i_row; y++ )
248     {
249         /* */
250         int i_height = 0;
251         int i_halign = 0;
252         if( y * i_target_height >= i_vstart &&
253             ( y + 1 ) * i_target_height <= i_vend )
254         {
255             i_height = i_target_height;
256         }
257         else if( ( y + 1 ) * i_target_height < i_vstart ||
258                  ( y * i_target_height ) > i_vend )
259         {
260             i_height = 0;
261         }
262         else
263         {
264             i_height = ( i_target_height -
265                          i_vstart%i_target_height );
266             if(  y >= ( p_sys->i_row / 2 ) )
267             {
268                 i_halign = VOUT_ALIGN_TOP;
269                 i_height -= b_vstart_rounded ? 2: 0;
270             }
271             else
272             {
273                 i_halign = VOUT_ALIGN_BOTTOM;
274             }
275         }
276
277         /* */
278         for( int x = 0, i_left = 0; x < p_sys->i_col; x++ )
279         {
280             wall_output_t *p_output = &p_sys->pp_output[x][y];
281
282             /* */
283             int i_width;
284             int i_valign = 0;
285             if( x*i_target_width >= i_hstart &&
286                 (x+1)*i_target_width <= i_hend )
287             {
288                 i_width = i_target_width;
289             }
290             else if( ( x + 1 ) * i_target_width < i_hstart ||
291                      ( x * i_target_width ) > i_hend )
292             {
293                 i_width = 0;
294             }
295             else
296             {
297                 i_width = ( i_target_width - i_hstart % i_target_width );
298                 if( x >= ( p_sys->i_col / 2 ) )
299                 {
300                     i_valign = VOUT_ALIGN_LEFT;
301                     i_width -= b_hstart_rounded ? 2: 0;
302                 }
303                 else
304                 {
305                     i_valign = VOUT_ALIGN_RIGHT;
306                 }
307             }
308
309             /* */
310             p_output->b_active = pb_active[y * p_sys->i_col + x] &&
311                                  i_height > 0 && i_width > 0;
312             p_output->i_output = -1;
313             p_output->i_align = i_valign | i_halign;
314             p_output->i_width = i_width;
315             p_output->i_height = i_height;
316             p_output->i_left = i_left;
317             p_output->i_top = i_top;
318
319             msg_Dbg( p_splitter, "window %dx%d at %d:%d size %dx%d", 
320                      x, y, i_left, i_top, i_width, i_height );
321
322             if( p_output->b_active )
323                 i_active++;
324
325             i_left += i_width;
326         }
327         i_top += i_height;
328     }
329     if( i_active <= 0 )
330     {
331         msg_Err( p_splitter, "No active video output" );
332         free( p_sys );
333         return VLC_EGENERIC;
334     }
335
336     /* Setup output configuration */
337     p_splitter->i_output = i_active;
338     p_splitter->p_output = calloc( p_splitter->i_output,
339                                    sizeof(*p_splitter->p_output) );
340     if( !p_splitter->p_output )
341     {
342         free( p_sys );
343         return VLC_ENOMEM;
344     }
345     for( int y = 0, i_output = 0; y < p_sys->i_row; y++ )
346     {
347         for( int x = 0; x < p_sys->i_col; x++ )
348         {
349             wall_output_t *p_output = &p_sys->pp_output[x][y];
350             if( !p_output->b_active )
351                 continue;
352
353             p_output->i_output = i_output++;
354
355             video_splitter_output_t *p_cfg = &p_splitter->p_output[p_output->i_output];
356
357             video_format_Copy( &p_cfg->fmt, &p_splitter->fmt );
358             p_cfg->fmt.i_visible_width  =
359             p_cfg->fmt.i_width          = p_output->i_width;
360             p_cfg->fmt.i_visible_height =
361             p_cfg->fmt.i_height         = p_output->i_height;
362             p_cfg->fmt.i_sar_num        = (int64_t)i_aspect * i_target_height;
363             p_cfg->fmt.i_sar_den        = VOUT_ASPECT_FACTOR * i_target_width;
364             p_cfg->window.i_x     = p_output->i_left;
365             p_cfg->window.i_y     = p_output->i_top;
366             p_cfg->window.i_align = p_output->i_align;
367             p_cfg->psz_module = NULL;
368         }
369     }
370
371     /* */
372     p_splitter->pf_filter = Filter;
373     p_splitter->pf_mouse = Mouse;
374
375     return VLC_SUCCESS;
376 }
377
378 /**
379  * Terminate a splitter module.
380  */
381 static void Close( vlc_object_t *p_this )
382 {
383     video_splitter_t *p_splitter = (video_splitter_t*)p_this;
384     video_splitter_sys_t *p_sys = p_splitter->p_sys;
385
386     free( p_splitter->p_output );
387     free( p_sys );
388 }
389
390 static int Filter( video_splitter_t *p_splitter, picture_t *pp_dst[], picture_t *p_src )
391 {
392     video_splitter_sys_t *p_sys = p_splitter->p_sys;
393
394     if( video_splitter_NewPicture( p_splitter, pp_dst ) )
395     {
396         picture_Release( p_src );
397         return VLC_EGENERIC;
398     }
399
400     for( int y = 0; y < p_sys->i_row; y++ )
401     {
402         for( int x = 0; x < p_sys->i_col; x++ )
403         {
404             wall_output_t *p_output = &p_sys->pp_output[x][y];
405             if( !p_output->b_active )
406                 continue;
407
408             picture_t *p_dst = pp_dst[p_output->i_output];
409
410             /* */
411             picture_t tmp = *p_src;
412             for( int i = 0; i < tmp.i_planes; i++ )
413             {
414                 plane_t *p0 = &tmp.p[0];
415                 plane_t *p = &tmp.p[i];
416                 const int i_y = p_output->i_top  * p->i_visible_pitch / p0->i_visible_pitch;
417                 const int i_x = p_output->i_left * p->i_visible_lines / p0->i_visible_lines;
418
419                 p->p_pixels += i_y * p->i_pitch + ( i_x - (i_x % p->i_pixel_pitch));
420             }
421             picture_Copy( p_dst, &tmp );
422         }
423     }
424
425     picture_Release( p_src );
426     return VLC_SUCCESS;
427 }
428 static int Mouse( video_splitter_t *p_splitter, vlc_mouse_t *p_mouse,
429                   int i_index,
430                   const vlc_mouse_t *p_old, const vlc_mouse_t *p_new )
431 {
432     VLC_UNUSED(p_old);
433     video_splitter_sys_t *p_sys = p_splitter->p_sys;
434
435     for( int y = 0; y < p_sys->i_row; y++ )
436     {
437         for( int x = 0; x < p_sys->i_col; x++ )
438         {
439             wall_output_t *p_output = &p_sys->pp_output[x][y];
440             if( p_output->b_active && p_output->i_output == i_index )
441             {
442                 *p_mouse = *p_new;
443                 p_mouse->i_x += p_output->i_left;
444                 p_mouse->i_y += p_output->i_top;
445                 return VLC_SUCCESS;
446             }
447         }
448     }
449     vlc_assert_unreachable();
450     return VLC_EGENERIC;
451 }
452