vout_subpictures.c 51.6 KB
Newer Older
Sam Hocevar's avatar
 
Sam Hocevar committed
1 2 3
/*****************************************************************************
 * vout_subpictures.c : subpicture management functions
 *****************************************************************************
4
 * Copyright (C) 2000-2007 the VideoLAN team
5
 * $Id$
Sam Hocevar's avatar
 
Sam Hocevar committed
6 7 8
 *
 * Authors: Vincent Seguin <seguin@via.ecp.fr>
 *          Samuel Hocevar <sam@zoy.org>
9
 *          Gildas Bazin <gbazin@videolan.org>
Sam Hocevar's avatar
 
Sam Hocevar committed
10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * 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
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
Sam Hocevar's avatar
 
Sam Hocevar committed
24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33
#include <vlc_common.h>
zorglub's avatar
zorglub committed
34 35 36 37
#include <vlc_vout.h>
#include <vlc_block.h>
#include <vlc_filter.h>
#include <vlc_osd.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
38
#include "../libvlc.h"
39

40 41
#include <assert.h>

42 43 44 45 46 47
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static void UpdateSPU   ( spu_t *, vlc_object_t * );
static int  CropCallback( vlc_object_t *, char const *,
                          vlc_value_t, vlc_value_t, void * );
48

gbazin's avatar
gbazin committed
49 50
static int spu_vaControlDefault( spu_t *, int, va_list );

51 52
static subpicture_t *sub_new_buffer( filter_t * );
static void sub_del_buffer( filter_t *, subpicture_t * );
53 54
static subpicture_t *spu_new_buffer( filter_t * );
static void spu_del_buffer( filter_t *, subpicture_t * );
55 56
static picture_t *spu_new_video_buffer( filter_t * );
static void spu_del_video_buffer( filter_t *, picture_t * );
57

58 59 60 61
static int spu_ParseChain( spu_t * );
static int SubFilterCallback( vlc_object_t *, char const *,
                              vlc_value_t, vlc_value_t, void * );

dionoea's avatar
dionoea committed
62 63
static int sub_filter_allocation_init( filter_t *, void * );
static void sub_filter_allocation_clear( filter_t * );
gbazin's avatar
gbazin committed
64 65 66 67 68 69
struct filter_owner_sys_t
{
    spu_t *p_spu;
    int i_channel;
};

70 71 72 73 74
enum {
    SCALE_DEFAULT,
    SCALE_TEXT,
    SCALE_SIZE
};
dionoea's avatar
dionoea committed
75

76 77 78 79 80 81 82 83 84
static void FilterRelease( filter_t *p_filter )
{
    if( p_filter->p_module )
        module_Unneed( p_filter, p_filter->p_module );

    vlc_object_detach( p_filter );
    vlc_object_release( p_filter );
}

85
/**
86
 * Creates the subpicture unit
87
 *
88
 * \param p_this the parent object which creates the subpicture unit
89
 */
90
spu_t *__spu_Create( vlc_object_t *p_this )
91 92
{
    int i_index;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
93 94
    spu_t *p_spu = vlc_custom_create( p_this, sizeof( spu_t ),
                                      VLC_OBJECT_GENERIC, "subpicture" );
95 96 97

    for( i_index = 0; i_index < VOUT_MAX_SUBPICTURES; i_index++)
    {
98
        p_spu->p_subpicture[i_index].i_status = FREE_SUBPICTURE;
99 100
    }

101 102 103
    p_spu->p_blend = NULL;
    p_spu->p_text = NULL;
    p_spu->p_scale = NULL;
104
    p_spu->p_scale_yuvp = NULL;
gbazin's avatar
gbazin committed
105
    p_spu->pf_control = spu_vaControlDefault;
106 107

    /* Register the default subpicture channel */
gbazin's avatar
gbazin committed
108
    p_spu->i_channel = 2;
109

110
    vlc_mutex_init( &p_spu->subpicture_lock );
111

112 113
    vlc_object_attach( p_spu, p_this );

dionoea's avatar
dionoea committed
114 115 116 117
    p_spu->p_chain = filter_chain_New( p_spu, "sub filter", false,
                                       sub_filter_allocation_init,
                                       sub_filter_allocation_clear,
                                       p_spu );
118 119 120 121 122 123 124 125 126 127 128 129
    return p_spu;
}

/**
 * Initialise the subpicture unit
 *
 * \param p_spu the subpicture unit object
 */
int spu_Init( spu_t *p_spu )
{
    vlc_value_t val;

130 131 132
    /* If the user requested a sub margin, we force the position. */
    var_Create( p_spu, "sub-margin", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
    var_Get( p_spu, "sub-margin", &val );
133 134 135
    p_spu->i_margin = val.i_int;

    var_Create( p_spu, "sub-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
136 137 138 139
    var_AddCallback( p_spu, "sub-filter", SubFilterCallback, p_spu );

    spu_ParseChain( p_spu );

140
    return VLC_SUCCESS;
141
}
dionoea's avatar
 
dionoea committed
142

143 144
int spu_ParseChain( spu_t *p_spu )
{
dionoea's avatar
dionoea committed
145 146
    char *psz_parser = var_GetString( p_spu, "sub-filter" );
    if( filter_chain_AppendFromString( p_spu->p_chain, psz_parser ) < 0 )
147
    {
dionoea's avatar
dionoea committed
148 149
        free( psz_parser );
        return VLC_EGENERIC;
150
    }
151

dionoea's avatar
dionoea committed
152
    free( psz_parser );
153
    return VLC_SUCCESS;
154 155 156
}

/**
157
 * Destroy the subpicture unit
158
 *
159
 * \param p_this the parent object which destroys the subpicture unit
160
 */
161
void spu_Destroy( spu_t *p_spu )
162 163 164 165 166 167
{
    int i_index;

    /* Destroy all remaining subpictures */
    for( i_index = 0; i_index < VOUT_MAX_SUBPICTURES; i_index++ )
    {
168
        if( p_spu->p_subpicture[i_index].i_status != FREE_SUBPICTURE )
169
        {
170
            spu_DestroySubpicture( p_spu, &p_spu->p_subpicture[i_index] );
171 172 173
        }
    }

174
    if( p_spu->p_blend )
175
        FilterRelease( p_spu->p_blend );
176

177
    if( p_spu->p_text )
178
        FilterRelease( p_spu->p_text );
179

180 181
    if( p_spu->p_scale_yuvp )
        FilterRelease( p_spu->p_scale_yuvp );
182

183
    if( p_spu->p_scale )
184
        FilterRelease( p_spu->p_scale );
185

186
    filter_chain_Delete( p_spu->p_chain );
187 188

    vlc_mutex_destroy( &p_spu->subpicture_lock );
189
    vlc_object_release( p_spu );
190 191
}

192 193 194
/**
 * Attach/Detach the SPU from any input
 *
195
 * \param p_this the object in which to destroy the subpicture unit
196 197
 * \param b_attach to select attach or detach
 */
198
void spu_Attach( spu_t *p_spu, vlc_object_t *p_this, bool b_attach )
199 200 201
{
    vlc_object_t *p_input;

202
    p_input = vlc_object_find( p_this, VLC_OBJECT_INPUT, FIND_PARENT );
203 204 205
    if( !p_input ) return;

    if( b_attach )
206
    {
207 208
        UpdateSPU( p_spu, VLC_OBJECT(p_input) );
        var_AddCallback( p_input, "highlight", CropCallback, p_spu );
209 210 211 212 213
        vlc_object_release( p_input );
    }
    else
    {
        /* Delete callback */
214
        var_DelCallback( p_input, "highlight", CropCallback, p_spu );
215 216 217 218 219 220 221 222 223 224
        vlc_object_release( p_input );
    }
}

/**
 * Create a subpicture region
 *
 * \param p_this vlc_object_t
 * \param p_fmt the format that this subpicture region should have
 */
225 226
static void RegionPictureRelease( picture_t *p_pic )
{
227
    free( p_pic->p_data_orig );
228
}
229 230 231 232
subpicture_region_t *__spu_CreateRegion( vlc_object_t *p_this,
                                         video_format_t *p_fmt )
{
    subpicture_region_t *p_region = malloc( sizeof(subpicture_region_t) );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
233 234
    if( !p_region ) return NULL;

235
    memset( p_region, 0, sizeof(subpicture_region_t) );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
236
    p_region->i_alpha = 0xff;
237 238
    p_region->p_next = NULL;
    p_region->p_cache = NULL;
239
    p_region->fmt = *p_fmt;
240
    p_region->psz_text = NULL;
hartman's avatar
hartman committed
241
    p_region->p_style = NULL;
242 243 244 245 246 247

    if( p_fmt->i_chroma == VLC_FOURCC('Y','U','V','P') )
        p_fmt->p_palette = p_region->fmt.p_palette =
            malloc( sizeof(video_palette_t) );
    else p_fmt->p_palette = p_region->fmt.p_palette = NULL;

248
    p_region->picture.p_data_orig = NULL;
249 250

    if( p_fmt->i_chroma == VLC_FOURCC('T','E','X','T') ) return p_region;
251 252 253 254 255 256 257

    vout_AllocatePicture( p_this, &p_region->picture, p_fmt->i_chroma,
                          p_fmt->i_width, p_fmt->i_height, p_fmt->i_aspect );

    if( !p_region->picture.i_planes )
    {
        free( p_region );
258
        free( p_fmt->p_palette );
259 260 261
        return NULL;
    }

262 263
    p_region->picture.pf_release = RegionPictureRelease;

264 265 266
    return p_region;
}

267 268 269 270 271 272 273 274 275 276 277 278
/**
 * Make a subpicture region from an existing picture_t
 *
 * \param p_this vlc_object_t
 * \param p_fmt the format that this subpicture region should have
 * \param p_pic a pointer to the picture creating the region (not freed)
 */
subpicture_region_t *__spu_MakeRegion( vlc_object_t *p_this,
                                       video_format_t *p_fmt,
                                       picture_t *p_pic )
{
    subpicture_region_t *p_region = malloc( sizeof(subpicture_region_t) );
279
    (void)p_this;
280
    if( !p_region ) return NULL;
281
    memset( p_region, 0, sizeof(subpicture_region_t) );
282
    p_region->i_alpha = 0xff;
283 284 285 286
    p_region->p_next = 0;
    p_region->p_cache = 0;
    p_region->fmt = *p_fmt;
    p_region->psz_text = 0;
hartman's avatar
hartman committed
287
    p_region->p_style = NULL;
288 289 290 291 292 293 294 295 296 297 298 299

    if( p_fmt->i_chroma == VLC_FOURCC('Y','U','V','P') )
        p_fmt->p_palette = p_region->fmt.p_palette =
            malloc( sizeof(video_palette_t) );
    else p_fmt->p_palette = p_region->fmt.p_palette = NULL;

    memcpy( &p_region->picture, p_pic, sizeof(picture_t) );
    p_region->picture.pf_release = RegionPictureRelease;

    return p_region;
}

300 301 302 303 304 305 306 307 308
/**
 * Destroy a subpicture region
 *
 * \param p_this vlc_object_t
 * \param p_region the subpicture region to destroy
 */
void __spu_DestroyRegion( vlc_object_t *p_this, subpicture_region_t *p_region )
{
    if( !p_region ) return;
309 310
    if( p_region->picture.pf_release )
        p_region->picture.pf_release( &p_region->picture );
311
    free( p_region->fmt.p_palette );
312
    if( p_region->p_cache ) __spu_DestroyRegion( p_this, p_region->p_cache );
313

314 315
    free( p_region->psz_text );
    free( p_region->psz_html );
316
    //free( p_region->p_style ); FIXME --fenrir plugin does not allocate the memory for it. I think it might lead to segfault, video renderer can live longer than the decoder
317 318
    free( p_region );
}
Sam Hocevar's avatar
 
Sam Hocevar committed
319

320
/**
321
 * Display a subpicture
322
 *
Sam Hocevar's avatar
 
Sam Hocevar committed
323 324
 * Remove the reservation flag of a subpicture, which will cause it to be
 * ready for display.
325
 * \param p_spu the subpicture unit object
326 327
 * \param p_subpic the subpicture to display
 */
328
void spu_DisplaySubpicture( spu_t *p_spu, subpicture_t *p_subpic )
Sam Hocevar's avatar
 
Sam Hocevar committed
329 330 331 332
{
    /* Check if status is valid */
    if( p_subpic->i_status != RESERVED_SUBPICTURE )
    {
333 334
        msg_Err( p_spu, "subpicture %p has invalid status #%d",
                 p_subpic, p_subpic->i_status );
Sam Hocevar's avatar
 
Sam Hocevar committed
335 336
    }

337 338
    /* Remove reservation flag */
    p_subpic->i_status = READY_SUBPICTURE;
Sam Hocevar's avatar
 
Sam Hocevar committed
339

340
    if( p_subpic->i_channel == DEFAULT_CHAN )
Sam Hocevar's avatar
 
Sam Hocevar committed
341
    {
342 343 344
        p_subpic->i_channel = 0xFFFF;
        spu_Control( p_spu, SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
        p_subpic->i_channel = DEFAULT_CHAN;
Sam Hocevar's avatar
 
Sam Hocevar committed
345
    }
Sam Hocevar's avatar
 
Sam Hocevar committed
346 347
}

348
/**
349
 * Allocate a subpicture in the spu heap.
350
 *
351
 * This function create a reserved subpicture in the spu heap.
Sam Hocevar's avatar
 
Sam Hocevar committed
352 353 354
 * A null pointer is returned if the function fails. This method provides an
 * already allocated zone of memory in the spu data fields. It needs locking
 * since several pictures can be created by several producers threads.
355
 * \param p_spu the subpicture unit in which to create the subpicture
356 357
 * \return NULL on error, a reserved subpicture otherwise
 */
358
subpicture_t *spu_CreateSubpicture( spu_t *p_spu )
Sam Hocevar's avatar
 
Sam Hocevar committed
359 360
{
    int                 i_subpic;                        /* subpicture index */
361
    subpicture_t *      p_subpic = NULL;            /* first free subpicture */
Sam Hocevar's avatar
 
Sam Hocevar committed
362

363
    /* Get lock */
364
    vlc_mutex_lock( &p_spu->subpicture_lock );
365

Sam Hocevar's avatar
 
Sam Hocevar committed
366 367 368
    /*
     * Look for an empty place
     */
yoann's avatar
yoann committed
369
    p_subpic = NULL;
Sam Hocevar's avatar
 
Sam Hocevar committed
370 371
    for( i_subpic = 0; i_subpic < VOUT_MAX_SUBPICTURES; i_subpic++ )
    {
372
        if( p_spu->p_subpicture[i_subpic].i_status == FREE_SUBPICTURE )
Sam Hocevar's avatar
 
Sam Hocevar committed
373 374
        {
            /* Subpicture is empty and ready for allocation */
375 376
            p_subpic = &p_spu->p_subpicture[i_subpic];
            p_spu->p_subpicture[i_subpic].i_status = RESERVED_SUBPICTURE;
377
            break;
Sam Hocevar's avatar
 
Sam Hocevar committed
378 379 380
        }
    }

381 382
    /* If no free subpicture could be found */
    if( p_subpic == NULL )
Sam Hocevar's avatar
 
Sam Hocevar committed
383
    {
384 385
        msg_Err( p_spu, "subpicture heap is full" );
        vlc_mutex_unlock( &p_spu->subpicture_lock );
386
        return NULL;
Sam Hocevar's avatar
 
Sam Hocevar committed
387
    }
Sam Hocevar's avatar
 
Sam Hocevar committed
388

389
    /* Copy subpicture information, set some default values */
390 391
    memset( p_subpic, 0, sizeof(subpicture_t) );
    p_subpic->i_status   = RESERVED_SUBPICTURE;
392 393 394
    p_subpic->b_absolute = true;
    p_subpic->b_pausable = false;
    p_subpic->b_fade     = false;
395
    p_subpic->i_alpha    = 0xFF;
396 397 398 399
    p_subpic->p_region   = NULL;
    p_subpic->pf_render  = NULL;
    p_subpic->pf_destroy = NULL;
    p_subpic->p_sys      = NULL;
400
    vlc_mutex_unlock( &p_spu->subpicture_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
401

402
    p_subpic->pf_create_region = __spu_CreateRegion;
403
    p_subpic->pf_make_region = __spu_MakeRegion;
404
    p_subpic->pf_destroy_region = __spu_DestroyRegion;
405

406
    return p_subpic;
Sam Hocevar's avatar
 
Sam Hocevar committed
407 408
}

409 410 411
/**
 * Remove a subpicture from the heap
 *
Sam Hocevar's avatar
 
Sam Hocevar committed
412 413 414
 * This function frees a previously reserved subpicture.
 * It is meant to be used when the construction of a picture aborted.
 * This function does not need locking since reserved subpictures are ignored
415
 * by the spu.
416
 */
417
void spu_DestroySubpicture( spu_t *p_spu, subpicture_t *p_subpic )
Sam Hocevar's avatar
 
Sam Hocevar committed
418
{
gbazin's avatar
 
gbazin committed
419
    /* Get lock */
420
    vlc_mutex_lock( &p_spu->subpicture_lock );
gbazin's avatar
 
gbazin committed
421 422 423 424

    /* There can be race conditions so we need to check the status */
    if( p_subpic->i_status == FREE_SUBPICTURE )
    {
425
        vlc_mutex_unlock( &p_spu->subpicture_lock );
gbazin's avatar
 
gbazin committed
426 427 428
        return;
    }

429 430 431 432
    /* Check if status is valid */
    if( ( p_subpic->i_status != RESERVED_SUBPICTURE )
           && ( p_subpic->i_status != READY_SUBPICTURE ) )
    {
433
        msg_Err( p_spu, "subpicture %p has invalid status %d",
434 435 436
                         p_subpic, p_subpic->i_status );
    }

437 438 439 440
    while( p_subpic->p_region )
    {
        subpicture_region_t *p_region = p_subpic->p_region;
        p_subpic->p_region = p_region->p_next;
441
        spu_DestroyRegion( p_spu, p_region );
442 443
    }

444 445 446 447 448 449
    if( p_subpic->pf_destroy )
    {
        p_subpic->pf_destroy( p_subpic );
    }

    p_subpic->i_status = FREE_SUBPICTURE;
gbazin's avatar
 
gbazin committed
450

451
    vlc_mutex_unlock( &p_spu->subpicture_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
452 453 454
}

/*****************************************************************************
455
 * spu_RenderSubpictures: render a subpicture list
Sam Hocevar's avatar
 
Sam Hocevar committed
456
 *****************************************************************************
Sam Hocevar's avatar
 
Sam Hocevar committed
457
 * This function renders all sub picture units in the list.
Sam Hocevar's avatar
 
Sam Hocevar committed
458
 *****************************************************************************/
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
static void SpuRenderCreateBlend( spu_t *p_spu, vlc_fourcc_t i_chroma, int i_aspect )
{
    filter_t *p_blend;

    assert( !p_spu->p_blend );

    p_spu->p_blend =
    p_blend        = vlc_custom_create( p_spu, sizeof(filter_t),
                                        VLC_OBJECT_GENERIC, "blend" );
    if( !p_blend )
        return;

    es_format_Init( &p_blend->fmt_in, VIDEO_ES, 0 );

    es_format_Init( &p_blend->fmt_out, VIDEO_ES, 0 );
    p_blend->fmt_out.video.i_x_offset = 0;
    p_blend->fmt_out.video.i_y_offset = 0;
    p_blend->fmt_out.video.i_chroma = i_chroma;
    p_blend->fmt_out.video.i_aspect = i_aspect;

    /* The blend module will be loaded when needed with the real
    * input format */
    p_blend->p_module = NULL;

    /* */
    vlc_object_attach( p_blend, p_spu );
}
Laurent Aimar's avatar
Laurent Aimar committed
486
static void SpuRenderUpdateBlend( spu_t *p_spu, int i_out_width, int i_out_height, const video_format_t *p_in_fmt )
487 488 489 490 491 492
{
    filter_t *p_blend = p_spu->p_blend;

    assert( p_blend );

    /* */
Laurent Aimar's avatar
Laurent Aimar committed
493
    if( p_blend->p_module && p_blend->fmt_in.video.i_chroma != p_in_fmt->i_chroma )
494 495 496 497 498 499 500 501
    {
        /* The chroma is not the same, we need to reload the blend module
         * XXX to match the old behaviour just test !p_blend->fmt_in.video.i_chroma */
        module_Unneed( p_blend, p_blend->p_module );
        p_blend->p_module = NULL;
    }

    /* */
Laurent Aimar's avatar
Laurent Aimar committed
502 503 504 505 506 507 508
    p_blend->fmt_in.video = *p_in_fmt;

    /* */
    p_blend->fmt_out.video.i_width =
    p_blend->fmt_out.video.i_visible_width = i_out_width;
    p_blend->fmt_out.video.i_height =
    p_blend->fmt_out.video.i_visible_height = i_out_height;
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552

    /* */
    if( !p_blend->p_module )
        p_blend->p_module = module_Need( p_blend, "video blending", 0, 0 );
}
static void SpuRenderCreateAndLoadText( spu_t *p_spu, int i_width, int i_height )
{
    filter_t *p_text;

    assert( !p_spu->p_text );

    p_spu->p_text =
    p_text        = vlc_custom_create( p_spu, sizeof(filter_t),
                                       VLC_OBJECT_GENERIC, "spu text" );
    if( !p_text )
        return;

    es_format_Init( &p_text->fmt_in, VIDEO_ES, 0 );

    es_format_Init( &p_text->fmt_out, VIDEO_ES, 0 );
    p_text->fmt_out.video.i_width =
    p_text->fmt_out.video.i_visible_width = i_width;
    p_text->fmt_out.video.i_height =
    p_text->fmt_out.video.i_visible_height = i_height;

    p_text->pf_sub_buffer_new = spu_new_buffer;
    p_text->pf_sub_buffer_del = spu_del_buffer;

    vlc_object_attach( p_text, p_spu );

    /* FIXME TOCHECK shouldn't module_Need( , , psz_modulename, false ) do the
     * same than these 2 calls ? */
    char *psz_modulename = var_CreateGetString( p_spu, "text-renderer" );
    if( psz_modulename && *psz_modulename )
    {
        p_text->p_module = module_Need( p_text, "text renderer",
                                        psz_modulename, true );
    }
    free( psz_modulename );

    if( !p_text->p_module )
        p_text->p_module = module_Need( p_text, "text renderer", NULL, false );
}

553
static filter_t *CreateAndLoadScale( vlc_object_t *p_obj, vlc_fourcc_t i_chroma )
554 555 556
{
    filter_t *p_scale;

557 558
    p_scale = vlc_custom_create( p_obj, sizeof(filter_t),
                                 VLC_OBJECT_GENERIC, "scale" );
559
    if( !p_scale )
560
        return NULL;
561 562

    es_format_Init( &p_scale->fmt_in, VIDEO_ES, 0 );
563
    p_scale->fmt_in.video.i_chroma = i_chroma;
564 565 566 567
    p_scale->fmt_in.video.i_width =
    p_scale->fmt_in.video.i_height = 32;

    es_format_Init( &p_scale->fmt_out, VIDEO_ES, 0 );
568
    p_scale->fmt_out.video.i_chroma = i_chroma;
569 570 571 572 573 574
    p_scale->fmt_out.video.i_width =
    p_scale->fmt_out.video.i_height = 16;

    p_scale->pf_vout_buffer_new = spu_new_video_buffer;
    p_scale->pf_vout_buffer_del = spu_del_video_buffer;

575 576 577 578 579 580 581 582 583 584 585 586 587 588
    vlc_object_attach( p_scale, p_obj );
    p_scale->p_module = module_Need( p_scale, "video filter2", 0, 0 );

    return p_scale;
}

static void SpuRenderCreateAndLoadScale( spu_t *p_spu )
{
    /* FIXME: We'll also be using it for YUVA and RGBA blending ... */

    assert( !p_spu->p_scale );
    assert( !p_spu->p_scale_yuvp );
    p_spu->p_scale = CreateAndLoadScale( VLC_OBJECT(p_spu), VLC_FOURCC('Y','U','V','A') );
    p_spu->p_scale_yuvp = p_spu->p_scale_yuvp = CreateAndLoadScale( VLC_OBJECT(p_spu), VLC_FOURCC('Y','U','V','P') );
589 590
}

Laurent Aimar's avatar
Laurent Aimar committed
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
static void SpuRenderText( spu_t *p_spu, bool *pb_rerender_text,
                           subpicture_t *p_subpic, subpicture_region_t *p_region, int i_min_scale_ratio )
{
    assert( p_region->fmt.i_chroma == VLC_FOURCC('T','E','X','T') );

    if( !p_spu->p_text || !p_spu->p_text->p_module )
        goto exit;

    /* Setup 3 variables which can be used to render
     * time-dependent text (and effects). The first indicates
     * the total amount of time the text will be on screen,
     * the second the amount of time it has already been on
     * screen (can be a negative value as text is layed out
     * before it is rendered) and the third is a feedback
     * variable from the renderer - if the renderer sets it
     * then this particular text is time-dependent, eg. the
     * visual progress bar inside the text in karaoke and the
     * text needs to be rendered multiple times in order for
     * the effect to work - we therefore need to return the
     * region to its original state at the end of the loop,
     * instead of leaving it in YUVA or YUVP.
     * Any renderer which is unaware of how to render
     * time-dependent text can happily ignore the variables
     * and render the text the same as usual - it should at
     * least show up on screen, but the effect won't change
     * the text over time.
     */

    /* FIXME why these variables are recreated every time and not
     * when text renderer module was created ? */
    var_Create( p_spu->p_text, "spu-duration", VLC_VAR_TIME );
    var_Create( p_spu->p_text, "spu-elapsed", VLC_VAR_TIME );
    var_Create( p_spu->p_text, "text-rerender", VLC_VAR_BOOL );
    var_Create( p_spu->p_text, "scale", VLC_VAR_INTEGER );

    var_SetTime( p_spu->p_text, "spu-duration", p_subpic->i_stop - p_subpic->i_start );
    var_SetTime( p_spu->p_text, "spu-elapsed", mdate() - p_subpic->i_start );
    var_SetBool( p_spu->p_text, "text-rerender", false );
    var_SetInteger( p_spu->p_text, "scale", i_min_scale_ratio );

    if( p_spu->p_text->pf_render_html && p_region->psz_html )
    {
        p_spu->p_text->pf_render_html( p_spu->p_text,
                                       p_region, p_region );
    }
    else if( p_spu->p_text->pf_render_text )
    {
        p_spu->p_text->pf_render_text( p_spu->p_text,
                                       p_region, p_region );
    }
    *pb_rerender_text = var_GetBool( p_spu->p_text, "text-rerender" );

    var_Destroy( p_spu->p_text, "spu-duration" );
    var_Destroy( p_spu->p_text, "spu-elapsed" );
    var_Destroy( p_spu->p_text, "text-rerender" );
    var_Destroy( p_spu->p_text, "scale" );

exit:
    p_region->i_align |= SUBPICTURE_RENDERED;
}

Laurent Aimar's avatar
Laurent Aimar committed
652 653 654 655 656 657 658 659 660
static void SpuRenderRegion( spu_t *p_spu,
                             picture_t *p_pic_dst, picture_t *p_pic_src,
                             subpicture_t *p_subpic, subpicture_region_t *p_region,
                             const int i_scale_width_orig, const int i_scale_height_orig,
                             const int pi_subpic_x[SCALE_SIZE],
                             const int pi_scale_width[SCALE_SIZE],
                             const int pi_scale_height[SCALE_SIZE],
                             const video_format_t *p_fmt )
{
661
    video_format_t fmt_original;
Laurent Aimar's avatar
Laurent Aimar committed
662
    bool b_rerender_text;
663
    bool b_restore_format = false;
Laurent Aimar's avatar
Laurent Aimar committed
664
    int i_fade_alpha;
Laurent Aimar's avatar
Laurent Aimar committed
665 666
    int i_x_offset;
    int i_y_offset;
Laurent Aimar's avatar
Laurent Aimar committed
667 668 669
    int i_scale_idx;
    int i_inv_scale_x;
    int i_inv_scale_y;
670
    filter_t *p_scale;
Laurent Aimar's avatar
Laurent Aimar committed
671

672 673 674
    vlc_assert_locked( &p_spu->subpicture_lock );

    fmt_original = p_region->fmt;
Laurent Aimar's avatar
Laurent Aimar committed
675
    b_rerender_text = false;
Laurent Aimar's avatar
Laurent Aimar committed
676
    if( p_region->fmt.i_chroma == VLC_FOURCC('T','E','X','T') )
Laurent Aimar's avatar
Laurent Aimar committed
677
    {
Laurent Aimar's avatar
Laurent Aimar committed
678
        SpuRenderText( p_spu, &b_rerender_text, p_subpic, p_region, __MIN(i_scale_width_orig, i_scale_height_orig) );
679
        b_restore_format = b_rerender_text;
Laurent Aimar's avatar
Laurent Aimar committed
680

Laurent Aimar's avatar
Laurent Aimar committed
681 682 683 684
        /* Check if the rendering has failed ... */
        if( p_region->fmt.i_chroma == VLC_FOURCC('T','E','X','T') )
            goto exit;
    }
Laurent Aimar's avatar
Laurent Aimar committed
685

Laurent Aimar's avatar
Laurent Aimar committed
686 687
    if( p_region->i_align & SUBPICTURE_RENDERED )
    {
Laurent Aimar's avatar
Laurent Aimar committed
688
        /* We are using a region which come from rendered text */
Laurent Aimar's avatar
Laurent Aimar committed
689 690 691 692
        i_scale_idx   = SCALE_TEXT;
        i_inv_scale_x = i_scale_width_orig;
        i_inv_scale_y = i_scale_height_orig;
    }
Laurent Aimar's avatar
Laurent Aimar committed
693 694 695 696 697 698
    else
    {
        i_scale_idx   = SCALE_DEFAULT;
        i_inv_scale_x = 1000;
        i_inv_scale_y = 1000;
    }
Laurent Aimar's avatar
Laurent Aimar committed
699 700 701 702

    i_x_offset = (p_region->i_x + pi_subpic_x[ i_scale_idx ]) * i_inv_scale_x / 1000;
    i_y_offset = (p_region->i_y + p_subpic->i_y) * i_inv_scale_y / 1000;

703 704 705 706 707 708 709 710 711
    /* Force palette if requested
     * FIXME b_force_palette and b_force_crop are applied to all subpictures using palette
     * instead of only the right one (being the dvd spu).
     */
    const bool b_using_palette = p_region->fmt.i_chroma == VLC_FOURCC('Y','U','V','P');
    const bool b_force_palette = b_using_palette && p_spu->b_force_palette;
    const bool b_force_crop    = b_force_palette && p_spu->b_force_crop;

    if( b_force_palette )
Laurent Aimar's avatar
Laurent Aimar committed
712
    {
Laurent Aimar's avatar
Laurent Aimar committed
713
        /* It looks so wrong I won't comment
Laurent Aimar's avatar
Laurent Aimar committed
714 715 716
         * p_palette->palette is [256][4] with a int i_entries
         * p_spu->palette is [4][4]
         * */
717 718
        p_region->fmt.p_palette->i_entries = 4;
        memcpy( p_region->fmt.p_palette->palette, p_spu->palette, 4*sizeof(uint32_t) );
Laurent Aimar's avatar
Laurent Aimar committed
719 720
    }

721 722 723 724 725 726
    if( b_using_palette )
        p_scale = p_spu->p_scale_yuvp;
    else
        p_scale = p_spu->p_scale;

    if( p_scale &&
Laurent Aimar's avatar
Laurent Aimar committed
727
        ( ( pi_scale_width[i_scale_idx]  > 0 && pi_scale_width[i_scale_idx]  != 1000 ) ||
728
          ( pi_scale_height[i_scale_idx] > 0 && pi_scale_height[i_scale_idx] != 1000 ) ||
729
          ( b_force_palette ) ) )
Laurent Aimar's avatar
Laurent Aimar committed
730
    {
731 732
        const unsigned i_dst_width  = p_region->fmt.i_width  * pi_scale_width[i_scale_idx] / 1000;
        const unsigned i_dst_height = p_region->fmt.i_height * pi_scale_height[i_scale_idx] / 1000;
Laurent Aimar's avatar
Laurent Aimar committed
733 734 735 736 737

        /* Destroy if cache is unusable */
        if( p_region->p_cache )
        {
            if( p_region->p_cache->fmt.i_width  != i_dst_width ||
738
                p_region->p_cache->fmt.i_height != i_dst_height ||
739
                b_force_palette )
Laurent Aimar's avatar
Laurent Aimar committed
740 741 742 743 744 745 746 747
            {
                p_subpic->pf_destroy_region( VLC_OBJECT(p_spu),
                                             p_region->p_cache );
                p_region->p_cache = NULL;
            }
        }

        /* Scale if needed into cache */
Laurent Aimar's avatar
Laurent Aimar committed
748
        if( !p_region->p_cache )
Laurent Aimar's avatar
Laurent Aimar committed
749
        {
Laurent Aimar's avatar
Laurent Aimar committed
750 751
            picture_t *p_pic;

752 753
            p_scale->fmt_in.video = p_region->fmt;
            p_scale->fmt_out.video = p_region->fmt;
Laurent Aimar's avatar
Laurent Aimar committed
754 755 756

            p_region->p_cache =
                p_subpic->pf_create_region( VLC_OBJECT(p_spu),
757
                                            &p_scale->fmt_out.video );
Laurent Aimar's avatar
Laurent Aimar committed
758 759
            p_region->p_cache->p_next = p_region->p_next;

760 761
            if( p_scale->fmt_out.video.p_palette )
                *p_scale->fmt_out.video.p_palette =
Laurent Aimar's avatar
Laurent Aimar committed
762 763 764 765 766
                    *p_region->fmt.p_palette;

            vout_CopyPicture( p_spu, &p_region->p_cache->picture,
                              &p_region->picture );

767 768
            p_scale->fmt_out.video.i_width = i_dst_width;
            p_scale->fmt_out.video.i_height = i_dst_height;
Laurent Aimar's avatar
Laurent Aimar committed
769

770
            p_scale->fmt_out.video.i_visible_width =
Laurent Aimar's avatar
Laurent Aimar committed
771
                p_region->fmt.i_visible_width * pi_scale_width[ i_scale_idx ] / 1000;
772
            p_scale->fmt_out.video.i_visible_height =
Laurent Aimar's avatar
Laurent Aimar committed
773
                p_region->fmt.i_visible_height * pi_scale_height[ i_scale_idx ] / 1000;
Laurent Aimar's avatar
Laurent Aimar committed
774

775
            p_region->p_cache->fmt = p_scale->fmt_out.video;
Laurent Aimar's avatar
Laurent Aimar committed
776 777 778 779 780
            p_region->p_cache->i_x = p_region->i_x * pi_scale_width[ i_scale_idx ] / 1000;
            p_region->p_cache->i_y = p_region->i_y * pi_scale_height[ i_scale_idx ] / 1000;
            p_region->p_cache->i_align = p_region->i_align;
            p_region->p_cache->i_alpha = p_region->i_alpha;

781 782 783 784 785
            p_pic = NULL;
            if( p_scale->p_module )
                p_pic = p_scale->pf_video_filter( p_scale, &p_region->p_cache->picture );
            else
                msg_Err( p_spu, "scaling failed (module not loaded)" );
Laurent Aimar's avatar
Laurent Aimar committed
786 787 788 789 790
            if( p_pic )
            {
                p_region->p_cache->picture = *p_pic;
                free( p_pic );
            }
Laurent Aimar's avatar
Laurent Aimar committed
791 792
        }

793
        /* And use the scaled picture */
Laurent Aimar's avatar
Laurent Aimar committed
794
        if( p_region->p_cache )
795
        {
Laurent Aimar's avatar
Laurent Aimar committed
796
            p_region = p_region->p_cache;
797 798
            fmt_original = p_region->fmt;
        }
Laurent Aimar's avatar
Laurent Aimar committed
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836
    }

    if( p_region->i_align & SUBPICTURE_ALIGN_BOTTOM )
    {
        i_y_offset = p_fmt->i_height - p_region->fmt.i_height -
            (p_subpic->i_y + p_region->i_y) * i_inv_scale_y / 1000;
    }
    else if ( !(p_region->i_align & SUBPICTURE_ALIGN_TOP) )
    {
        i_y_offset = p_fmt->i_height / 2 - p_region->fmt.i_height / 2;
    }

    if( p_region->i_align & SUBPICTURE_ALIGN_RIGHT )
    {
        i_x_offset = p_fmt->i_width - p_region->fmt.i_width -
            (pi_subpic_x[ i_scale_idx ] + p_region->i_x)
            * i_inv_scale_x / 1000;
    }
    else if ( !(p_region->i_align & SUBPICTURE_ALIGN_LEFT) )
    {
        i_x_offset = p_fmt->i_width / 2 - p_region->fmt.i_width / 2;
    }

    if( p_subpic->b_absolute )
    {
        i_x_offset = (p_region->i_x +
            pi_subpic_x[ i_scale_idx ] *
                             pi_scale_width[ i_scale_idx ] / 1000)
            * i_inv_scale_x / 1000;
        i_y_offset = (p_region->i_y +
            p_subpic->i_y * pi_scale_height[ i_scale_idx ] / 1000)
            * i_inv_scale_y / 1000;

    }

    i_x_offset = __MAX( i_x_offset, 0 );
    i_y_offset = __MAX( i_y_offset, 0 );

837
    if( p_spu->i_margin != 0 && !b_force_crop )
Laurent Aimar's avatar
Laurent Aimar committed
838 839 840 841 842 843 844 845 846 847 848 849 850
    {
        int i_diff = 0;
        int i_low = (i_y_offset - p_spu->i_margin) * i_inv_scale_y / 1000;
        int i_high = i_low + p_region->fmt.i_height;

        /* crop extra margin to keep within bounds */
        if( i_low < 0 )
            i_diff = i_low;
        if( i_high > (int)p_fmt->i_height )
            i_diff = i_high - p_fmt->i_height;
        i_y_offset -= ( p_spu->i_margin * i_inv_scale_y / 1000 + i_diff );
    }

Laurent Aimar's avatar
Laurent Aimar committed
851
    /* Force cropping if requested */
852
    if( b_force_crop )
Laurent Aimar's avatar
Laurent Aimar committed
853
    {
Laurent Aimar's avatar
Laurent Aimar committed
854
        video_format_t *p_fmt = &p_region->fmt;
Laurent Aimar's avatar
Laurent Aimar committed
855 856 857 858 859 860 861 862 863 864 865 866 867 868
        int i_crop_x = p_spu->i_crop_x * pi_scale_width[ i_scale_idx ] / 1000
                            * i_inv_scale_x / 1000;
        int i_crop_y = p_spu->i_crop_y * pi_scale_height[ i_scale_idx ] / 1000
                            * i_inv_scale_y / 1000;
        int i_crop_width = p_spu->i_crop_width * pi_scale_width[ i_scale_idx ] / 1000
                            * i_inv_scale_x / 1000;
        int i_crop_height = p_spu->i_crop_height * pi_scale_height[ i_scale_idx ] / 1000
                            * i_inv_scale_y / 1000;

        /* Find the intersection */
        if( i_crop_x + i_crop_width <= i_x_offset ||
            i_x_offset + (int)p_fmt->i_visible_width < i_crop_x ||
            i_crop_y + i_crop_height <= i_y_offset ||
            i_y_offset + (int)p_fmt->i_visible_height < i_crop_y )
Laurent Aimar's avatar
Laurent Aimar committed
869
        {
Laurent Aimar's avatar
Laurent Aimar committed
870 871
            /* No intersection */
            p_fmt->i_visible_width = p_fmt->i_visible_height = 0;
Laurent Aimar's avatar
Laurent Aimar committed
872 873 874
        }
        else
        {
Laurent Aimar's avatar
Laurent Aimar committed
875 876 877 878 879 880 881 882 883 884 885 886 887 888 889
            int i_x, i_y, i_x_end, i_y_end;
            i_x = __MAX( i_crop_x, i_x_offset );
            i_y = __MAX( i_crop_y, i_y_offset );
            i_x_end = __MIN( i_crop_x + i_crop_width,
                           i_x_offset + (int)p_fmt->i_visible_width );
            i_y_end = __MIN( i_crop_y + i_crop_height,
                           i_y_offset + (int)p_fmt->i_visible_height );

            p_fmt->i_x_offset = i_x - i_x_offset;
            p_fmt->i_y_offset = i_y - i_y_offset;
            p_fmt->i_visible_width = i_x_end - i_x;
            p_fmt->i_visible_height = i_y_end - i_y;

            i_x_offset = i_x;
            i_y_offset = i_y;
Laurent Aimar's avatar
Laurent Aimar committed
890
        }
891
        b_restore_format = true;
Laurent Aimar's avatar
Laurent Aimar committed
892 893
    }

Laurent Aimar's avatar
Laurent Aimar committed
894 895 896
    i_x_offset = __MAX( i_x_offset, 0 );
    i_y_offset = __MAX( i_y_offset, 0 );

Laurent Aimar's avatar
Laurent Aimar committed
897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
    /* Compute alpha blend value */
    i_fade_alpha = 255;
    if( p_subpic->b_fade )
    {
        mtime_t i_fade_start = ( p_subpic->i_stop +
                                 p_subpic->i_start ) / 2;
        mtime_t i_now = mdate();
        if( i_now >= i_fade_start && p_subpic->i_stop > i_fade_start )
        {
            i_fade_alpha = 255 * ( p_subpic->i_stop - i_now ) /
                           ( p_subpic->i_stop - i_fade_start );
        }
    }

    /* Update the blender */
    SpuRenderUpdateBlend( p_spu, p_fmt->i_width, p_fmt->i_height, &p_region->fmt );
Laurent Aimar's avatar
Laurent Aimar committed
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927

    if( p_spu->p_blend->p_module )
    {
        p_spu->p_blend->pf_video_blend( p_spu->p_blend, p_pic_dst,
            p_pic_src, &p_region->picture, i_x_offset, i_y_offset,
            i_fade_alpha * p_subpic->i_alpha * p_region->i_alpha / 65025 );
    }
    else
    {
        msg_Err( p_spu, "blending %4.4s to %4.4s failed",
                 (char *)&p_spu->p_blend->fmt_out.video.i_chroma,
                 (char *)&p_spu->p_blend->fmt_out.video.i_chroma );
    }

exit:
Laurent Aimar's avatar
Laurent Aimar committed
928 929 930 931 932 933 934 935 936 937 938
    if( b_rerender_text )
    {
        /* Some forms of subtitles need to be re-rendered more than
         * once, eg. karaoke. We therefore restore the region to its
         * pre-rendered state, so the next time through everything is
         * calculated again.
         */
        p_region->picture.pf_release( &p_region->picture );
        memset( &p_region->picture, 0, sizeof( picture_t ) );
        p_region->i_align &= ~SUBPICTURE_RENDERED;
    }
939
    if( b_restore_format )
940
        p_region->fmt = fmt_original;
Laurent Aimar's avatar
Laurent Aimar committed
941
}
942

943 944 945
void spu_RenderSubpictures( spu_t *p_spu, video_format_t *p_fmt,
                            picture_t *p_pic_dst, picture_t *p_pic_src,
                            subpicture_t *p_subpic,
946
                            int i_scale_width_orig, int i_scale_height_orig )
Sam Hocevar's avatar
 
Sam Hocevar committed
947
{
948 949
    int i_source_video_width;
    int i_source_video_height;
Laurent Aimar's avatar
Laurent Aimar committed
950
    subpicture_t *p_subpic_v;
951

gbazin's avatar
 
gbazin committed
952
    /* Get lock */
953
    vlc_mutex_lock( &p_spu->subpicture_lock );
gbazin's avatar
 
gbazin committed
954

955 956 957 958 959 960 961 962 963 964
    for( p_subpic_v = p_subpic;
            p_subpic_v != NULL && p_subpic_v->i_status != FREE_SUBPICTURE;
            p_subpic_v = p_subpic_v->p_next )
    {
        if( p_subpic_v->pf_pre_render )
        {
            p_subpic_v->pf_pre_render( p_fmt, p_spu, p_subpic_v, mdate() );
        }
    }

965
    if( i_scale_width_orig <= 0 )
Laurent Aimar's avatar
Laurent Aimar committed
966
        i_scale_width_orig = 1000;
967
    if( i_scale_height_orig <= 0 )
Laurent Aimar's avatar
Laurent Aimar committed
968
        i_scale_height_orig = 1000;
969 970 971 972

    i_source_video_width  = p_fmt->i_width  * 1000 / i_scale_width_orig;
    i_source_video_height = p_fmt->i_height * 1000 / i_scale_height_orig;

gbazin's avatar
 
gbazin committed
973
    /* Check i_status again to make sure spudec hasn't destroyed the subpic */
Laurent Aimar's avatar
Laurent Aimar committed
974
    for( ; ( p_subpic != NULL ) && ( p_subpic->i_status != FREE_SUBPICTURE ); p_subpic = p_subpic->p_next )
Sam Hocevar's avatar
 
Sam Hocevar committed
975
    {
976
        subpicture_region_t *p_region;
977 978 979 980 981
        int pi_scale_width[ SCALE_SIZE ];
        int pi_scale_height[ SCALE_SIZE ];
        int pi_subpic_x[ SCALE_SIZE ];
        int k;

982 983 984 985 986 987 988
        /* If the source video and subtitles stream agree on the size of
         * the video then disregard all further references to the subtitle
         * stream.
         */
        if( ( i_source_video_height == p_subpic->i_original_picture_height ) &&
            ( i_source_video_width  == p_subpic->i_original_picture_width ) )
        {
989
            /* FIXME this looks wrong */