vout_subpictures.c 55.8 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
Antoine Cellerier's avatar
Antoine Cellerier 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>
Clément Stenac's avatar
Clément Stenac 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
#include <assert.h>
41
#include <limits.h>
42

43 44 45
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
#define VLC_FOURCC_YUVP VLC_FOURCC('Y','U','V','P')
#define VLC_FOURCC_YUVA VLC_FOURCC('Y','U','V','A')
#define VLC_FOURCC_RGBA VLC_FOURCC('R','G','B','A')
#define VLC_FOURCC_TEXT VLC_FOURCC('T','E','X','T')

typedef struct
{
    subpicture_t *p_subpicture;
    bool          b_reject;
} spu_heap_entry_t;

typedef struct
{
    spu_heap_entry_t p_entry[VOUT_MAX_SUBPICTURES];

} spu_heap_t;

63 64
struct spu_private_t
{
65 66 67 68
    vlc_mutex_t lock;   /* lock to protect all followings fields */

    spu_heap_t heap;

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
    int i_channel;             /**< number of subpicture channels registered */
    int64_t i_subpicture_order; /**< number of created subpicture since spu creation */

    filter_t *p_blend;                            /**< alpha blending module */
    filter_t *p_text;                              /**< text renderer module */
    filter_t *p_scale_yuvp;                     /**< scaling module for YUVP */
    filter_t *p_scale;                    /**< scaling module (all but YUVP) */
    bool b_force_crop;                     /**< force cropping of subpicture */
    int i_crop_x, i_crop_y, i_crop_width, i_crop_height;       /**< cropping */

    int i_margin;                        /**< force position of a subpicture */
    bool b_force_palette;             /**< force palette of subpicture */
    uint8_t palette[4][4];                               /**< forced palette */

    /* Supciture filters */
    filter_chain_t *p_chain;
};

87 88 89 90 91 92 93 94 95
/* */
struct subpicture_region_private_t
{
    video_format_t fmt;
    picture_t      *p_picture;
};

static void SpuRegionPrivateDestroy( subpicture_region_private_t *p_private );

96 97 98
static void UpdateSPU   ( spu_t *, vlc_object_t * );
static int  CropCallback( vlc_object_t *, char const *,
                          vlc_value_t, vlc_value_t, void * );
99

100
static int SpuControl( spu_t *, int, va_list );
101

102 103 104
static void SpuClearChannel( spu_t *p_spu, int i_channel, bool b_locked );

/* Buffer allocation for SPU filter (blend, scale, ...) */
105 106
static subpicture_t *spu_new_buffer( filter_t * );
static void spu_del_buffer( filter_t *, subpicture_t * );
107 108
static picture_t *spu_new_video_buffer( filter_t * );
static void spu_del_video_buffer( filter_t *, picture_t * );
109

110
static int spu_ParseChain( spu_t * );
111 112

/* Buffer aloccation fir SUB filter */
113 114 115
static int SubFilterCallback( vlc_object_t *, char const *,
                              vlc_value_t, vlc_value_t, void * );

116 117
static int SubFilterAllocationInit( filter_t *, void * );
static void SubFilterAllocationClean( filter_t * );
118

Laurent Aimar's avatar
Laurent Aimar committed
119 120
#define SCALE_UNIT (1000)

121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
/* */
subpicture_region_t *subpicture_region_New( const video_format_t *p_fmt )
{
    subpicture_region_t *p_region = calloc( 1, sizeof(*p_region ) );
    if( !p_region )
        return NULL;

    p_region->fmt = *p_fmt;
    p_region->fmt.p_palette = NULL;
    if( p_fmt->i_chroma == VLC_FOURCC_YUVP )
    {
        p_region->fmt.p_palette = calloc( 1, sizeof(*p_region->fmt.p_palette) );
        if( p_fmt->p_palette )
            *p_region->fmt.p_palette = *p_fmt->p_palette;
    }
    p_region->i_alpha = 0xff;
    p_region->p_next = NULL;
    p_region->p_private = NULL;
    p_region->psz_text = NULL;
    p_region->p_style = NULL;
    p_region->p_picture = NULL;

    if( p_fmt->i_chroma == VLC_FOURCC_TEXT )
        return p_region;

    p_region->p_picture = picture_New( p_fmt->i_chroma, p_fmt->i_width, p_fmt->i_height,
                                       p_fmt->i_aspect );
    if( !p_region->p_picture )
    {
        free( p_fmt->p_palette );
        free( p_region );
        return NULL;
    }

    return p_region;
}
157

158
/* */
159
void subpicture_region_Delete( subpicture_region_t *p_region )
160
{
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
    if( !p_region )
        return;

    if( p_region->p_private )
        SpuRegionPrivateDestroy( p_region->p_private );

    if( p_region->p_picture )
        picture_Release( p_region->p_picture );

    free( p_region->fmt.p_palette );

    free( p_region->psz_text );
    free( p_region->psz_html );
    //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
    free( p_region );
}

/* */
void subpicture_region_ChainDelete( subpicture_region_t *p_head )
{
    while( p_head )
    {
        subpicture_region_t *p_next = p_head->p_next;
184

185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
        subpicture_region_Delete( p_head );

        p_head = p_next;
    }
}

/**
 * This function create a new empty subpicture.
 */
static subpicture_t *subpicture_New( void )
{
    subpicture_t *p_subpic = calloc( 1, sizeof(*p_subpic) );
    if( !p_subpic )
        return NULL;

    p_subpic->i_order    = 0;
    p_subpic->b_absolute = true;
    p_subpic->b_fade     = false;
    p_subpic->b_subtitle = false;
    p_subpic->i_alpha    = 0xFF;
    p_subpic->p_region   = NULL;
    p_subpic->pf_render  = NULL;
    p_subpic->pf_destroy = NULL;
    p_subpic->p_sys      = NULL;

    return p_subpic;
}

static void subpicture_Delete( subpicture_t *p_subpic )
{
    subpicture_region_ChainDelete( p_subpic->p_region );
    p_subpic->p_region = NULL;

    if( p_subpic->pf_destroy )
    {
        p_subpic->pf_destroy( p_subpic );
    }
    free( p_subpic );
}


static void SpuHeapInit( spu_heap_t *p_heap )
{
    for( int i = 0; i < VOUT_MAX_SUBPICTURES; i++ )
    {
        spu_heap_entry_t *e = &p_heap->p_entry[i];

        e->p_subpicture = NULL;
        e->b_reject = false;
    }
}

static int SpuHeapPush( spu_heap_t *p_heap, subpicture_t *p_subpic )
{
    for( int i = 0; i < VOUT_MAX_SUBPICTURES; i++ )
    {
        spu_heap_entry_t *e = &p_heap->p_entry[i];

        if( e->p_subpicture )
            continue;

        e->p_subpicture = p_subpic;
        e->b_reject = false;
        return VLC_SUCCESS;
    }
    return VLC_EGENERIC;
}

static void SpuHeapDeleteAt( spu_heap_t *p_heap, int i_index )
{
    spu_heap_entry_t *e = &p_heap->p_entry[i_index];

    if( e->p_subpicture )
        subpicture_Delete( e->p_subpicture );

    e->p_subpicture = NULL;
}

static int SpuHeapDeleteSubpicture( spu_heap_t *p_heap, subpicture_t *p_subpic )
{
    for( int i = 0; i < VOUT_MAX_SUBPICTURES; i++ )
    {
        spu_heap_entry_t *e = &p_heap->p_entry[i];

        if( e->p_subpicture != p_subpic )
            continue;

        SpuHeapDeleteAt( p_heap, i );
        return VLC_SUCCESS;
    }
    return VLC_EGENERIC;
}

static void SpuHeapClean( spu_heap_t *p_heap )
{
    for( int i = 0; i < VOUT_MAX_SUBPICTURES; i++ )
    {
        spu_heap_entry_t *e = &p_heap->p_entry[i];
        if( e->p_subpicture )
            subpicture_Delete( e->p_subpicture );
    }
}
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
static subpicture_region_private_t *SpuRegionPrivateCreate( video_format_t *p_fmt )
{
    subpicture_region_private_t *p_private = malloc( sizeof(*p_private) );

    if( !p_private )
        return NULL;

    p_private->fmt = *p_fmt;
    if( p_fmt->p_palette )
    {
        p_private->fmt.p_palette = malloc( sizeof(*p_private->fmt.p_palette) );
        if( p_private->fmt.p_palette )
            *p_private->fmt.p_palette = *p_fmt->p_palette;
    }
    p_private->p_picture = NULL;

    return p_private;
}
static void SpuRegionPrivateDestroy( subpicture_region_private_t *p_private )
{
    if( p_private->p_picture )
        picture_Release( p_private->p_picture );
    free( p_private->fmt.p_palette );
    free( p_private );
}

313 314 315 316
/* */
static void SpuRenderCreateAndLoadText( spu_t *p_spu );
static void SpuRenderCreateAndLoadScale( spu_t *p_spu );
static void FilterRelease( filter_t *p_filter );
317

318
/**
319
 * Creates the subpicture unit
320
 *
321
 * \param p_this the parent object which creates the subpicture unit
322
 */
323
spu_t *__spu_Create( vlc_object_t *p_this )
324
{
325 326 327 328 329 330 331 332 333 334 335 336 337 338
    spu_t *p_spu;
    spu_private_t *p_sys;
    
    p_spu = vlc_custom_create( p_this, sizeof(spu_t) + sizeof(spu_private_t),
                               VLC_OBJECT_GENERIC, "subpicture" );

    if( !p_spu )
        return NULL;

    /* Initialize spu fields */
    p_spu->pf_control = SpuControl;
    p_spu->p = p_sys = (spu_private_t*)&p_spu[1];

    /* Initialize private fields */
339 340 341 342
    vlc_mutex_init( &p_sys->lock );

    SpuHeapInit( &p_sys->heap );

343
    p_sys->i_subpicture_order = 1;
344

345 346 347 348
    p_sys->p_blend = NULL;
    p_sys->p_text = NULL;
    p_sys->p_scale = NULL;
    p_sys->p_scale_yuvp = NULL;
349 350

    /* Register the default subpicture channel */
351
    p_sys->i_channel = 2;
352 353 354

    vlc_object_attach( p_spu, p_this );

355
    p_sys->p_chain = filter_chain_New( p_spu, "sub filter", false,
356 357
                                       SubFilterAllocationInit,
                                       SubFilterAllocationClean,
358
                                       p_spu );
359 360 361 362 363

    /* Load text and scale module */
    SpuRenderCreateAndLoadText( p_spu );
    SpuRenderCreateAndLoadScale( p_spu );

364 365 366 367 368 369 370 371 372 373
    return p_spu;
}

/**
 * Initialise the subpicture unit
 *
 * \param p_spu the subpicture unit object
 */
int spu_Init( spu_t *p_spu )
{
374
    spu_private_t *p_sys = p_spu->p;
375

376
    /* If the user requested a sub margin, we force the position. */
377
    p_sys->i_margin = var_CreateGetInteger( p_spu, "sub-margin" );
378 379

    var_Create( p_spu, "sub-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
380 381 382 383
    var_AddCallback( p_spu, "sub-filter", SubFilterCallback, p_spu );

    spu_ParseChain( p_spu );

384
    return VLC_SUCCESS;
385
}
Antoine Cellerier's avatar
 
Antoine Cellerier committed
386

387 388
int spu_ParseChain( spu_t *p_spu )
{
389
    char *psz_parser = var_GetString( p_spu, "sub-filter" );
390 391 392
    int i_ret;

    if( !psz_parser )
393
        return VLC_EGENERIC;
394 395

    i_ret = filter_chain_AppendFromString( p_spu->p->p_chain, psz_parser );
396

397
    free( psz_parser );
398 399

    return i_ret;
400 401 402
}

/**
403
 * Destroy the subpicture unit
404
 *
405
 * \param p_this the parent object which destroys the subpicture unit
406
 */
407
void spu_Destroy( spu_t *p_spu )
408
{
409
    spu_private_t *p_sys = p_spu->p;
410

411 412 413 414 415
    if( p_sys->p_blend )
        FilterRelease( p_sys->p_blend );

    if( p_sys->p_text )
        FilterRelease( p_sys->p_text );
416

417 418
    if( p_sys->p_scale_yuvp )
        FilterRelease( p_sys->p_scale_yuvp );
419

420 421
    if( p_sys->p_scale )
        FilterRelease( p_sys->p_scale );
422

423
    filter_chain_Delete( p_sys->p_chain );
424

425 426 427 428
    /* Destroy all remaining subpictures */
    SpuHeapClean( &p_sys->heap );

    vlc_mutex_destroy( &p_sys->lock );
429

430
    vlc_object_release( p_spu );
431 432
}

433 434 435
/**
 * Attach/Detach the SPU from any input
 *
436
 * \param p_this the object in which to destroy the subpicture unit
437 438
 * \param b_attach to select attach or detach
 */
439
void spu_Attach( spu_t *p_spu, vlc_object_t *p_this, bool b_attach )
440 441 442
{
    vlc_object_t *p_input;

443
    p_input = vlc_object_find( p_this, VLC_OBJECT_INPUT, FIND_PARENT );
444 445 446
    if( !p_input ) return;

    if( b_attach )
447
    {
448 449
        UpdateSPU( p_spu, VLC_OBJECT(p_input) );
        var_AddCallback( p_input, "highlight", CropCallback, p_spu );
450 451 452 453 454
        vlc_object_release( p_input );
    }
    else
    {
        /* Delete callback */
455
        var_DelCallback( p_input, "highlight", CropCallback, p_spu );
456 457 458 459
        vlc_object_release( p_input );
    }
}

460
/**
461
 * Display a subpicture
462
 *
Sam Hocevar's avatar
 
Sam Hocevar committed
463 464
 * Remove the reservation flag of a subpicture, which will cause it to be
 * ready for display.
465
 * \param p_spu the subpicture unit object
466 467
 * \param p_subpic the subpicture to display
 */
468
void spu_DisplaySubpicture( spu_t *p_spu, subpicture_t *p_subpic )
Sam Hocevar's avatar
 
Sam Hocevar committed
469
{
470
    spu_private_t *p_sys = p_spu->p;
Sam Hocevar's avatar
 
Sam Hocevar committed
471

472
    /* DEFAULT_CHAN always reset itself */
473
    if( p_subpic->i_channel == DEFAULT_CHAN )
474 475 476 477 478 479 480 481 482
        SpuClearChannel( p_spu, DEFAULT_CHAN, false );

    /* p_private is for spu only and cannot be non NULL here */
    for( subpicture_region_t *r = p_subpic->p_region; r != NULL; r = r->p_next )
        assert( r->p_private == NULL );

    /* */
    vlc_mutex_lock( &p_sys->lock );
    if( SpuHeapPush( &p_sys->heap, p_subpic ) )
Sam Hocevar's avatar
 
Sam Hocevar committed
483
    {
484 485 486 487
        vlc_mutex_unlock( &p_sys->lock );
        msg_Err( p_spu, "subpicture heap full" );
        subpicture_Delete( p_subpic );
        return;
Sam Hocevar's avatar
 
Sam Hocevar committed
488
    }
489
    vlc_mutex_unlock( &p_sys->lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
490 491
}

492
/**
493
 * Allocate a subpicture in the spu heap.
494
 *
495
 * This function create a reserved subpicture in the spu heap.
Sam Hocevar's avatar
 
Sam Hocevar committed
496 497 498
 * 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.
499
 * \param p_spu the subpicture unit in which to create the subpicture
500 501
 * \return NULL on error, a reserved subpicture otherwise
 */
502
subpicture_t *spu_CreateSubpicture( spu_t *p_spu )
Sam Hocevar's avatar
 
Sam Hocevar committed
503
{
504
    VLC_UNUSED(p_spu);
Sam Hocevar's avatar
 
Sam Hocevar committed
505

506
    return subpicture_New();
Sam Hocevar's avatar
 
Sam Hocevar committed
507 508
}

509 510 511
/**
 * Remove a subpicture from the heap
 *
Sam Hocevar's avatar
 
Sam Hocevar committed
512 513 514
 * 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
515
 * by the spu.
516
 */
517
void spu_DestroySubpicture( spu_t *p_spu, subpicture_t *p_subpic )
Sam Hocevar's avatar
 
Sam Hocevar committed
518
{
519
    VLC_UNUSED(p_spu);
520

521
    subpicture_Delete( p_subpic );
Sam Hocevar's avatar
 
Sam Hocevar committed
522 523
}

524 525 526
static void FilterRelease( filter_t *p_filter )
{
    if( p_filter->p_module )
527
        module_unneed( p_filter, p_filter->p_module );
528 529 530 531 532

    vlc_object_detach( p_filter );
    vlc_object_release( p_filter );
}

533 534 535 536
static void SpuRenderCreateBlend( spu_t *p_spu, vlc_fourcc_t i_chroma, int i_aspect )
{
    filter_t *p_blend;

537
    assert( !p_spu->p->p_blend );
538

539
    p_spu->p->p_blend =
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
    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 );
}
560 561
static void SpuRenderUpdateBlend( spu_t *p_spu, int i_out_width, int i_out_height,
                                  const video_format_t *p_in_fmt )
562
{
563
    filter_t *p_blend = p_spu->p->p_blend;
564 565 566 567

    assert( p_blend );

    /* */
Laurent Aimar's avatar
Laurent Aimar committed
568
    if( p_blend->p_module && p_blend->fmt_in.video.i_chroma != p_in_fmt->i_chroma )
569 570 571
    {
        /* 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 */
572
        module_unneed( p_blend, p_blend->p_module );
573 574 575 576
        p_blend->p_module = NULL;
    }

    /* */
Laurent Aimar's avatar
Laurent Aimar committed
577 578 579 580 581 582 583
    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;
584 585 586

    /* */
    if( !p_blend->p_module )
587
        p_blend->p_module = module_need( p_blend, "video blending", 0, 0 );
588
}
589
static void SpuRenderCreateAndLoadText( spu_t *p_spu )
590 591 592
{
    filter_t *p_text;

593
    assert( !p_spu->p->p_text );
594

595
    p_spu->p->p_text =
596 597 598 599 600 601 602 603 604
    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 =
605
    p_text->fmt_out.video.i_visible_width = 32;
606
    p_text->fmt_out.video.i_height =
607
    p_text->fmt_out.video.i_visible_height = 32;
608 609 610 611 612 613

    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 );

614
    /* FIXME TOCHECK shouldn't module_need( , , psz_modulename, false ) do the
615 616 617 618
     * same than these 2 calls ? */
    char *psz_modulename = var_CreateGetString( p_spu, "text-renderer" );
    if( psz_modulename && *psz_modulename )
    {
619
        p_text->p_module = module_need( p_text, "text renderer",
620 621 622 623 624
                                        psz_modulename, true );
    }
    free( psz_modulename );

    if( !p_text->p_module )
625
        p_text->p_module = module_need( p_text, "text renderer", NULL, false );
626 627 628 629 630 631

    /* Create a few variables used for enhanced text rendering */
    var_Create( p_text, "spu-duration", VLC_VAR_TIME );
    var_Create( p_text, "spu-elapsed", VLC_VAR_TIME );
    var_Create( p_text, "text-rerender", VLC_VAR_BOOL );
    var_Create( p_text, "scale", VLC_VAR_INTEGER );
632 633
}

634 635 636
static filter_t *CreateAndLoadScale( vlc_object_t *p_obj,
                                     vlc_fourcc_t i_src_chroma, vlc_fourcc_t i_dst_chroma,
                                     bool b_resize )
637 638 639
{
    filter_t *p_scale;

640 641
    p_scale = vlc_custom_create( p_obj, sizeof(filter_t),
                                 VLC_OBJECT_GENERIC, "scale" );
642
    if( !p_scale )
643
        return NULL;
644 645

    es_format_Init( &p_scale->fmt_in, VIDEO_ES, 0 );
646
    p_scale->fmt_in.video.i_chroma = i_src_chroma;
647 648 649 650
    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 );
651
    p_scale->fmt_out.video.i_chroma = i_dst_chroma;
652
    p_scale->fmt_out.video.i_width =
653
    p_scale->fmt_out.video.i_height = b_resize ? 16 : 32;
654 655 656 657

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

658
    vlc_object_attach( p_scale, p_obj );
659
    p_scale->p_module = module_need( p_scale, "video filter2", 0, 0 );
660 661 662 663 664

    return p_scale;
}
static void SpuRenderCreateAndLoadScale( spu_t *p_spu )
{
665 666
    assert( !p_spu->p->p_scale );
    assert( !p_spu->p->p_scale_yuvp );
667 668
    /* XXX p_spu->p_scale is used for all conversion/scaling except yuvp to
     * yuva/rgba */
669 670
    p_spu->p->p_scale = CreateAndLoadScale( VLC_OBJECT(p_spu),
                                            VLC_FOURCC_YUVA, VLC_FOURCC_YUVA, true );
671 672
    /* This one is used for YUVP to YUVA/RGBA without scaling
     * FIXME rename it */
673 674
    p_spu->p->p_scale_yuvp = CreateAndLoadScale( VLC_OBJECT(p_spu),
                                                 VLC_FOURCC_YUVP, VLC_FOURCC_YUVA, false );
675 676
}

Laurent Aimar's avatar
Laurent Aimar committed
677
static void SpuRenderText( spu_t *p_spu, bool *pb_rerender_text,
678 679
                           subpicture_t *p_subpic, subpicture_region_t *p_region,
                           int i_min_scale_ratio )
Laurent Aimar's avatar
Laurent Aimar committed
680
{
681 682
    filter_t *p_text = p_spu->p->p_text;

683
    assert( p_region->fmt.i_chroma == VLC_FOURCC_TEXT );
Laurent Aimar's avatar
Laurent Aimar committed
684

685
    if( !p_text || !p_text->p_module )
Laurent Aimar's avatar
Laurent Aimar committed
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
        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.
     */
707 708 709 710
    var_SetTime( p_text, "spu-duration", p_subpic->i_stop - p_subpic->i_start );
    var_SetTime( p_text, "spu-elapsed", mdate() - p_subpic->i_start );
    var_SetBool( p_text, "text-rerender", false );
    var_SetInteger( p_text, "scale", i_min_scale_ratio );
Laurent Aimar's avatar
Laurent Aimar committed
711

712
    if( p_text->pf_render_html && p_region->psz_html )
Laurent Aimar's avatar
Laurent Aimar committed
713
    {
714
        p_text->pf_render_html( p_text, p_region, p_region );
Laurent Aimar's avatar
Laurent Aimar committed
715
    }
716
    else if( p_text->pf_render_text )
Laurent Aimar's avatar
Laurent Aimar committed
717
    {
718
        p_text->pf_render_text( p_text, p_region, p_region );
Laurent Aimar's avatar
Laurent Aimar committed
719
    }
720
    *pb_rerender_text = var_GetBool( p_text, "text-rerender" );
Laurent Aimar's avatar
Laurent Aimar committed
721 722 723 724 725

exit:
    p_region->i_align |= SUBPICTURE_RENDERED;
}

726 727 728
/**
 * A few scale functions helpers.
 */
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
typedef struct
{
    int w;
    int h;
} spu_scale_t;

static spu_scale_t spu_scale_create( int w, int h )
{
    spu_scale_t s = { .w = w, .h = h };
    if( s.w <= 0 )
        s.w = SCALE_UNIT;
    if( s.h <= 0 )
        s.h = SCALE_UNIT;
    return s;
}
744 745 746 747
static spu_scale_t spu_scale_unit(void )
{
    return spu_scale_create( SCALE_UNIT, SCALE_UNIT );
}
748 749 750 751 752 753 754 755 756 757 758 759 760
static spu_scale_t spu_scale_createq( int wn, int wd, int hn, int hd )
{
    return spu_scale_create( wn * SCALE_UNIT / wd,
                             hn * SCALE_UNIT / hd );
}
static int spu_scale_w( int v, const spu_scale_t s )
{
    return v * s.w / SCALE_UNIT;
}
static int spu_scale_h( int v, const spu_scale_t s )
{
    return v * s.h / SCALE_UNIT;
}
761 762 763 764
static int spu_invscale_w( int v, const spu_scale_t s )
{
    return v * SCALE_UNIT / s.w;
}
765 766 767 768
static int spu_invscale_h( int v, const spu_scale_t s )
{
    return v * SCALE_UNIT / s.h;
}
769

770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 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 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890
/**
 * A few area functions helpers
 */

typedef struct
{
    int i_x;
    int i_y;
    int i_width;
    int i_height;

    spu_scale_t scale;
} spu_area_t;

static spu_area_t spu_area_create( int x, int y, int w, int h, spu_scale_t s )
{
    spu_area_t a = { .i_x = x, .i_y = y, .i_width = w, .i_height = h, .scale = s };
    return a;
}
static spu_area_t spu_area_scaled( spu_area_t a )
{
    if( a.scale.w == SCALE_UNIT && a.scale.h == SCALE_UNIT )
        return a;

    a.i_x = spu_scale_w( a.i_x, a.scale );
    a.i_y = spu_scale_h( a.i_y, a.scale );

    a.i_width  = spu_scale_w( a.i_width,  a.scale );
    a.i_height = spu_scale_h( a.i_height, a.scale );

    a.scale = spu_scale_unit();
    return a;
}
static spu_area_t spu_area_unscaled( spu_area_t a, spu_scale_t s )
{
    if( a.scale.w == s.w && a.scale.h == s.h )
        return a;

    a = spu_area_scaled( a );

    a.i_x = spu_invscale_w( a.i_x, s );
    a.i_y = spu_invscale_h( a.i_y, s );

    a.i_width  = spu_invscale_w( a.i_width, s );
    a.i_height = spu_invscale_h( a.i_height, s );

    a.scale = s;
    return a;
}
static bool spu_area_overlap( spu_area_t a, spu_area_t b )
{
    const int i_dx = 0;
    const int i_dy = 0;

    a = spu_area_scaled( a );
    b = spu_area_scaled( b );

    return  __MAX( a.i_x-i_dx, b.i_x ) < __MIN( a.i_x+a.i_width +i_dx, b.i_x+b.i_width  ) &&
            __MAX( a.i_y-i_dy, b.i_y ) < __MIN( a.i_y+a.i_height+i_dy, b.i_y+b.i_height );
}

/**
 * Avoid area overlapping
 */
static void SpuAreaFixOverlap( spu_area_t *p_dst,
                               const spu_area_t *p_master,
                               const spu_area_t *p_sub, int i_sub, int i_align )
{
    spu_area_t a = spu_area_scaled( *p_dst );
    bool b_moved = false;
    bool b_ok;

    assert( p_master->i_x == 0 && p_master->i_y == 0 );

    /* Check for overlap
     * XXX It is not fast O(n^2) but we should not have a lot of region */
    do
    {
        b_ok = true;
        for( int i = 0; i < i_sub; i++ )
        {
            spu_area_t sub = spu_area_scaled( p_sub[i] );

            if( !spu_area_overlap( a, sub ) )
                continue;

            if( i_align & SUBPICTURE_ALIGN_TOP )
            {
                /* We go down */
                int i_y = sub.i_y + sub.i_height;
                if( i_y + a.i_height > p_master->i_height )
                    break;
                a.i_y = i_y;
                b_moved = true;
            }
            else if( i_align & SUBPICTURE_ALIGN_BOTTOM )
            {
                /* We go up */
                int i_y = sub.i_y - a.i_height;
                if( i_y < 0 )
                    break;
                a.i_y = i_y;
                b_moved = true;
            }
            else
            {
                /* TODO what to do in this case? */
                //fprintf( stderr, "Overlap with unsupported alignment\n" );
                break;
            }

            b_ok = false;
            break;
        }
    } while( !b_ok );

    if( b_moved )
        *p_dst = spu_area_unscaled( a, p_dst->scale );
}


Laurent Aimar's avatar
Laurent Aimar committed
891 892 893 894 895
/**
 * Place a region
 */
static void SpuRegionPlace( int *pi_x, int *pi_y,
                            const subpicture_t *p_subpic,
896 897
                            const subpicture_region_t *p_region,
                            int i_margin_y )
Laurent Aimar's avatar
Laurent Aimar committed
898
{
899 900
    const int i_delta_x = p_region->i_x;
    const int i_delta_y = p_region->i_y;
Laurent Aimar's avatar
Laurent Aimar committed
901 902
    int i_x, i_y;

903
    assert( p_region->i_x != INT_MAX && p_region->i_y != INT_MAX );
Laurent Aimar's avatar
Laurent Aimar committed
904 905 906 907 908 909
    if( p_region->i_align & SUBPICTURE_ALIGN_TOP )
    {
        i_y = i_delta_y;
    }
    else if( p_region->i_align & SUBPICTURE_ALIGN_BOTTOM )
    {
910
        i_y = p_subpic->i_original_picture_height - p_region->fmt.i_height - i_delta_y;
Laurent Aimar's avatar
Laurent Aimar committed
911 912 913
    }
    else
    {
914
        i_y = p_subpic->i_original_picture_height / 2 - p_region->fmt.i_height / 2;
Laurent Aimar's avatar
Laurent Aimar committed
915 916 917 918 919 920 921 922
    }

    if( p_region->i_align & SUBPICTURE_ALIGN_LEFT )
    {
        i_x = i_delta_x;
    }
    else if( p_region->i_align & SUBPICTURE_ALIGN_RIGHT )
    {
923
        i_x = p_subpic->i_original_picture_width - p_region->fmt.i_width - i_delta_x;
Laurent Aimar's avatar
Laurent Aimar committed
924 925 926
    }
    else
    {
927
        i_x = p_subpic->i_original_picture_width / 2 - p_region->fmt.i_width / 2;
Laurent Aimar's avatar
Laurent Aimar committed
928 929 930 931
    }

    if( p_subpic->b_absolute )
    {
932 933
        i_x = i_delta_x;
        i_y = i_delta_y;
Laurent Aimar's avatar
Laurent Aimar committed
934
    }
935

936
    /* Margin shifts all subpictures */
937
    if( i_margin_y != 0 )
938
        i_y -= i_margin_y;
939

940 941 942 943
    /* Clamp offset to not go out of the screen (when possible) */
    const int i_error_x = (i_x + p_region->fmt.i_width) - p_subpic->i_original_picture_width;
    if( i_error_x > 0 )
        i_x -= i_error_x;
944 945
    if( i_x < 0 )
        i_x = 0;
946 947 948 949

    const int i_error_y = (i_y + p_region->fmt.i_height) - p_subpic->i_original_picture_height;
    if( i_error_y > 0 )
        i_y -= i_error_y;
950 951
    if( i_y < 0 )
        i_y = 0;
Laurent Aimar's avatar
Laurent Aimar committed
952

953 954
    *pi_x = i_x;
    *pi_y = i_y;
Laurent Aimar's avatar
Laurent Aimar committed
955 956
}

957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978
/**
 * This function computes the current alpha value for a given region.
 */
static int SpuRegionAlpha( subpicture_t *p_subpic, subpicture_region_t *p_region )
{
    /* Compute alpha blend value */
    int 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 );
        }
    }
    return i_fade_alpha * p_subpic->i_alpha * p_region->i_alpha / 65025;
}

979 980 981 982
/**
 * It will render the provided region onto p_pic_dst.
 */

Laurent Aimar's avatar
Laurent Aimar committed
983
static void SpuRenderRegion( spu_t *p_spu,
984
                             picture_t *p_pic_dst, spu_area_t *p_area,
Laurent Aimar's avatar
Laurent Aimar committed
985
                             subpicture_t *p_subpic, subpicture_region_t *p_region,
986
                             const spu_scale_t scale_size,
987 988
                             const video_format_t *p_fmt,
                             const spu_area_t *p_subtitle_area, int i_subtitle_area )
Laurent Aimar's avatar
Laurent Aimar committed
989
{
990 991
    spu_private_t *p_sys = p_spu->p;

992 993
    video_format_t fmt_original = p_region->fmt;
    bool b_rerender_text = false;
994
    bool b_restore_format = false;
Laurent Aimar's avatar
Laurent Aimar committed
995 996 997
    int i_x_offset;
    int i_y_offset;

998
    video_format_t region_fmt;
999 1000
    picture_t *p_region_picture;

1001 1002 1003 1004
    /* Invalidate area by default */
    *p_area = spu_area_create( 0,0, 0,0, scale_size );

    /* Render text region */
1005
    if( p_region->fmt.i_chroma == VLC_FOURCC_TEXT )
Laurent Aimar's avatar
Laurent Aimar committed
1006
    {
1007
        const int i_min_scale_ratio = SCALE_UNIT; /* FIXME what is the right value? (scale_size is not) */
1008
        SpuRenderText( p_spu, &b_rerender_text, p_subpic, p_region, i_min_scale_ratio );
1009
        b_restore_format = b_rerender_text;
Laurent Aimar's avatar
Laurent Aimar committed
1010

Laurent Aimar's avatar
Laurent Aimar committed
1011
        /* Check if the rendering has failed ... */
1012
        if( p_region->fmt.i_chroma == VLC_FOURCC_TEXT )
Laurent Aimar's avatar
Laurent Aimar committed
1013 1014
            goto exit;
    }
Laurent Aimar's avatar
Laurent Aimar committed
1015

1016 1017 1018 1019
    /* 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).
     */
1020
    const bool b_using_palette = p_region->fmt.i_chroma == VLC_FOURCC_YUVP;
1021 1022
    const bool b_force_palette = b_using_palette && p_sys->b_force_palette;
    const bool b_force_crop    = b_force_palette && p_sys->b_force_crop;
1023
    bool b_changed_palette     = false;
1024

1025

1026 1027 1028 1029 1030
    /* Compute the margin which is expressed in destination pixel unit
     * The margin is applied only to subtitle and when no forced crop is
     * requested (dvd menu) */
    int i_margin_y = 0;
    if( !b_force_crop && p_subpic->b_subtitle )
1031
        i_margin_y = spu_invscale_h( p_sys->i_margin, scale_size );
1032

1033 1034 1035
    /* Place the picture
     * We compute the position in the rendered size */
    SpuRegionPlace( &i_x_offset, &i_y_offset,
1036 1037
                    p_subpic, p_region, i_margin_y );

1038 1039 1040 1041 1042 1043 1044 1045 1046
    /* Save this position for subtitle overlap support
     * it is really important that there are given without scale_size applied */
    *p_area = spu_area_create( i_x_offset, i_y_offset,
                               p_region->fmt.i_width, p_region->fmt.i_height,
                               scale_size );

    /* Handle overlapping subtitles when possible */
    if( p_subpic->b_subtitle && !p_subpic->b_absolute )
    {
1047 1048
        spu_area_t display = spu_area_create( 0, 0, p_fmt->i_width, p_fmt->i_height,
                                              spu_scale_unit() );
1049

1050 1051
        SpuAreaFixOverlap( p_area, &display, p_subtitle_area, i_subtitle_area,
                           p_region->i_align );
1052
    }
1053 1054

    /* Fix the position for the current scale_size */
1055 1056
    i_x_offset = spu_scale_w( p_area->i_x, p_area->scale );
    i_y_offset = spu_scale_h( p_area->i_y, p_area->scale );
1057

1058
    /* */
1059
    if( b_force_palette )
Laurent Aimar's avatar
Laurent Aimar committed
1060
    {
1061 1062 1063 1064 1065 1066 1067
        video_palette_t *p_palette = p_region->fmt.p_palette;
        video_palette_t palette;

        /* We suppose DVD palette here */
        palette.i_entries = 4;
        for( int i = 0; i < 4; i++ )
            for( int j = 0; j < 4; j++ )
1068
                palette.palette[i][j] = p_sys->palette[i][j];
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080

        if( p_palette->i_entries == palette.i_entries )
        {
            for( int i = 0; i < p_palette->i_entries; i++ )
                for( int j = 0; j < 4; j++ )
                    b_changed_palette |= p_palette->palette[i][j] != palette.palette[i][j];
        }
        else
        {
            b_changed_palette = true;
        }
        *p_palette = palette;
Laurent Aimar's avatar
Laurent Aimar committed
1081 1082
    }

1083
    /* */
1084
    region_fmt = p_region->fmt;
1085 1086
    p_region_picture = p_region->p_picture;

1087

1088
    /* Scale from rendered size to destination size */
1089 1090
    if( p_sys->p_scale && p_sys->p_scale->p_module &&
        ( !b_using_palette || ( p_sys->p_scale_yuvp && p_sys->p_scale_yuvp->p_module ) ) &&
1091
        ( scale_size.w != SCALE_UNIT || scale_size.h != SCALE_UNIT || b_using_palette ) )
Laurent Aimar's avatar
Laurent Aimar committed
1092
    {
1093 1094
        const unsigned i_dst_width  = spu_scale_w( p_region->fmt.i_width, scale_size );
        const unsigned i_dst_height = spu_scale_h( p_region->fmt.i_height, scale_size );
Laurent Aimar's avatar
Laurent Aimar committed
1095

1096
        /* Destroy the cache if unusable */
1097
        if( p_region->p_private )
Laurent Aimar's avatar
Laurent Aimar committed
1098
        {
1099 1100 1101 1102 1103 1104 1105 1106
            subpicture_region_private_t *p_private = p_region->p_private;
            bool b_changed = false;

            /* Check resize changes */
            if( i_dst_width  != p_private->fmt.i_width ||
                i_dst_height != p_private->fmt.i_height )
                b_changed = true;

1107 1108 1109
            /* Check forced palette changes */
            if( b_changed_palette )
                b_changed = true;
1110 1111 1112 1113 1114 1115

            if( b_changed )
            {
                SpuRegionPrivateDestroy( p_private );
                p_region->p_private = NULL;
            }
Laurent Aimar's avatar
Laurent Aimar committed
1116 1117 1118
        }

        /* Scale if needed into cache */
1119
        if( !p_region->p_private )
Laurent Aimar's avatar
Laurent Aimar committed
1120
        {
1121
            filter_t *p_scale = p_sys->p_scale;
1122 1123

            picture_t *p_picture = p_region->p_picture;
1124
            picture_Hold( p_picture );
1125 1126 1127 1128

            /* Convert YUVP to YUVA/RGBA first for better scaling quality */
            if( b_using_palette )
            {
1129
                filter_t *p_scale_yuvp = p_sys->p_scale_yuvp;
1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141

                p_scale_yuvp->fmt_in.video = p_region->fmt;

                /* TODO converting to RGBA for RGB video output is better */
                p_scale_yuvp->fmt_out.video = p_region->fmt;
                p_scale_yuvp->fmt_out.video.i_chroma = VLC_FOURCC_YUVA;

                p_picture = p_scale_yuvp->pf_video_filter( p_scale_yuvp, p_picture );
                if( !p_picture )
                {
                    /* Well we will try conversion+scaling */
                    msg_Warn( p_spu, "%4.4s to %4.4s conversion failed",
Laurent Aimar's avatar
Laurent Aimar committed
1142 1143
                             (const char*)&p_scale_yuvp->fmt_in.video.i_chroma,
                             (const char*)&p_scale_yuvp->fmt_out.video.i_chroma );
1144 1145
                }
            }
Laurent Aimar's avatar
Laurent Aimar committed
1146

1147 1148