vout_subpictures.c 56 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
/* */
struct subpicture_region_private_t
{
    video_format_t fmt;
    picture_t      *p_picture;
};

94 95 96 97 98 99 100
/* Subpicture rendered flag
 * FIXME ? it could be moved to private ? */
#define SUBPICTURE_RENDERED  (0x1000)
#ifdef SUBPICTURE_RENDERED < SUBPICTURE_ALIGN_MASK
#   error SUBPICTURE_RENDERED too low
#endif

101 102
static void SpuRegionPrivateDestroy( subpicture_region_private_t *p_private );

103 104 105
static void UpdateSPU   ( spu_t *, vlc_object_t * );
static int  CropCallback( vlc_object_t *, char const *,
                          vlc_value_t, vlc_value_t, void * );
106

107
static int SpuControl( spu_t *, int, va_list );
108

109 110 111
static void SpuClearChannel( spu_t *p_spu, int i_channel, bool b_locked );

/* Buffer allocation for SPU filter (blend, scale, ...) */
112 113
static subpicture_t *spu_new_buffer( filter_t * );
static void spu_del_buffer( filter_t *, subpicture_t * );
114 115
static picture_t *spu_new_video_buffer( filter_t * );
static void spu_del_video_buffer( filter_t *, picture_t * );
116

117
static int spu_ParseChain( spu_t * );
118 119

/* Buffer aloccation fir SUB filter */
120 121 122
static int SubFilterCallback( vlc_object_t *, char const *,
                              vlc_value_t, vlc_value_t, void * );

123 124
static int SubFilterAllocationInit( filter_t *, void * );
static void SubFilterAllocationClean( filter_t * );
125

Laurent Aimar's avatar
Laurent Aimar committed
126 127
#define SCALE_UNIT (1000)

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 157 158 159 160 161 162 163
/* */
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;
}
164

165
/* */
166
void subpicture_region_Delete( subpicture_region_t *p_region )
167
{
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
    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;
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 287 288 289 290 291 292 293
        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 );
    }
}
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
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 );
}

320 321 322 323
/* */
static void SpuRenderCreateAndLoadText( spu_t *p_spu );
static void SpuRenderCreateAndLoadScale( spu_t *p_spu );
static void FilterRelease( filter_t *p_filter );
324

325
/**
326
 * Creates the subpicture unit
327
 *
328
 * \param p_this the parent object which creates the subpicture unit
329
 */
330
spu_t *__spu_Create( vlc_object_t *p_this )
331
{
332 333 334 335 336 337 338 339 340 341 342 343 344 345
    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 */
346 347 348 349
    vlc_mutex_init( &p_sys->lock );

    SpuHeapInit( &p_sys->heap );

350
    p_sys->i_subpicture_order = 1;
351

352 353 354 355
    p_sys->p_blend = NULL;
    p_sys->p_text = NULL;
    p_sys->p_scale = NULL;
    p_sys->p_scale_yuvp = NULL;
356 357

    /* Register the default subpicture channel */
358
    p_sys->i_channel = 2;
359 360 361

    vlc_object_attach( p_spu, p_this );

362
    p_sys->p_chain = filter_chain_New( p_spu, "sub filter", false,
363 364
                                       SubFilterAllocationInit,
                                       SubFilterAllocationClean,
365
                                       p_spu );
366 367 368 369 370

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

371 372 373 374 375 376 377 378 379 380
    return p_spu;
}

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

383
    /* If the user requested a sub margin, we force the position. */
384
    p_sys->i_margin = var_CreateGetInteger( p_spu, "sub-margin" );
385 386

    var_Create( p_spu, "sub-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
387 388 389 390
    var_AddCallback( p_spu, "sub-filter", SubFilterCallback, p_spu );

    spu_ParseChain( p_spu );

391
    return VLC_SUCCESS;
392
}
Antoine Cellerier's avatar
 
Antoine Cellerier committed
393

394 395
int spu_ParseChain( spu_t *p_spu )
{
396
    char *psz_parser = var_GetString( p_spu, "sub-filter" );
397 398 399
    int i_ret;

    if( !psz_parser )
400
        return VLC_EGENERIC;
401 402

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

404
    free( psz_parser );
405 406

    return i_ret;
407 408 409
}

/**
410
 * Destroy the subpicture unit
411
 *
412
 * \param p_this the parent object which destroys the subpicture unit
413
 */
414
void spu_Destroy( spu_t *p_spu )
415
{
416
    spu_private_t *p_sys = p_spu->p;
417

418 419 420 421 422
    if( p_sys->p_blend )
        FilterRelease( p_sys->p_blend );

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

424 425
    if( p_sys->p_scale_yuvp )
        FilterRelease( p_sys->p_scale_yuvp );
426

427 428
    if( p_sys->p_scale )
        FilterRelease( p_sys->p_scale );
429

430
    filter_chain_Delete( p_sys->p_chain );
431

432 433 434 435
    /* Destroy all remaining subpictures */
    SpuHeapClean( &p_sys->heap );

    vlc_mutex_destroy( &p_sys->lock );
436

437
    vlc_object_release( p_spu );
438 439
}

440 441 442
/**
 * Attach/Detach the SPU from any input
 *
443
 * \param p_this the object in which to destroy the subpicture unit
444 445
 * \param b_attach to select attach or detach
 */
446
void spu_Attach( spu_t *p_spu, vlc_object_t *p_this, bool b_attach )
447 448 449
{
    vlc_object_t *p_input;

450
    p_input = vlc_object_find( p_this, VLC_OBJECT_INPUT, FIND_PARENT );
451 452 453
    if( !p_input ) return;

    if( b_attach )
454
    {
455 456
        UpdateSPU( p_spu, VLC_OBJECT(p_input) );
        var_AddCallback( p_input, "highlight", CropCallback, p_spu );
457 458 459 460 461
        vlc_object_release( p_input );
    }
    else
    {
        /* Delete callback */
462
        var_DelCallback( p_input, "highlight", CropCallback, p_spu );
463 464 465 466
        vlc_object_release( p_input );
    }
}

467
/**
468
 * Display a subpicture
469
 *
Sam Hocevar's avatar
 
Sam Hocevar committed
470 471
 * Remove the reservation flag of a subpicture, which will cause it to be
 * ready for display.
472
 * \param p_spu the subpicture unit object
473 474
 * \param p_subpic the subpicture to display
 */
475
void spu_DisplaySubpicture( spu_t *p_spu, subpicture_t *p_subpic )
Sam Hocevar's avatar
 
Sam Hocevar committed
476
{
477
    spu_private_t *p_sys = p_spu->p;
Sam Hocevar's avatar
 
Sam Hocevar committed
478

479
    /* DEFAULT_CHAN always reset itself */
480
    if( p_subpic->i_channel == DEFAULT_CHAN )
481 482 483 484 485 486 487 488 489
        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
490
    {
491 492 493 494
        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
495
    }
496
    vlc_mutex_unlock( &p_sys->lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
497 498
}

499
/**
500
 * Allocate a subpicture in the spu heap.
501
 *
502
 * This function create a reserved subpicture in the spu heap.
Sam Hocevar's avatar
 
Sam Hocevar committed
503 504 505
 * 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.
506
 * \param p_spu the subpicture unit in which to create the subpicture
507 508
 * \return NULL on error, a reserved subpicture otherwise
 */
509
subpicture_t *spu_CreateSubpicture( spu_t *p_spu )
Sam Hocevar's avatar
 
Sam Hocevar committed
510
{
511
    VLC_UNUSED(p_spu);
Sam Hocevar's avatar
 
Sam Hocevar committed
512

513
    return subpicture_New();
Sam Hocevar's avatar
 
Sam Hocevar committed
514 515
}

516 517 518
/**
 * Remove a subpicture from the heap
 *
Sam Hocevar's avatar
 
Sam Hocevar committed
519 520 521
 * 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
522
 * by the spu.
523
 */
524
void spu_DestroySubpicture( spu_t *p_spu, subpicture_t *p_subpic )
Sam Hocevar's avatar
 
Sam Hocevar committed
525
{
526
    VLC_UNUSED(p_spu);
527

528
    subpicture_Delete( p_subpic );
Sam Hocevar's avatar
 
Sam Hocevar committed
529 530
}

531 532 533
static void FilterRelease( filter_t *p_filter )
{
    if( p_filter->p_module )
534
        module_unneed( p_filter, p_filter->p_module );
535 536 537 538 539

    vlc_object_detach( p_filter );
    vlc_object_release( p_filter );
}

540 541 542 543
static void SpuRenderCreateBlend( spu_t *p_spu, vlc_fourcc_t i_chroma, int i_aspect )
{
    filter_t *p_blend;

544
    assert( !p_spu->p->p_blend );
545

546
    p_spu->p->p_blend =
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
    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 );
}
567 568
static void SpuRenderUpdateBlend( spu_t *p_spu, int i_out_width, int i_out_height,
                                  const video_format_t *p_in_fmt )
569
{
570
    filter_t *p_blend = p_spu->p->p_blend;
571 572 573 574

    assert( p_blend );

    /* */
Laurent Aimar's avatar
Laurent Aimar committed
575
    if( p_blend->p_module && p_blend->fmt_in.video.i_chroma != p_in_fmt->i_chroma )
576 577 578
    {
        /* 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 */
579
        module_unneed( p_blend, p_blend->p_module );
580 581 582 583
        p_blend->p_module = NULL;
    }

    /* */
Laurent Aimar's avatar
Laurent Aimar committed
584 585 586 587 588 589 590
    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;
591 592 593

    /* */
    if( !p_blend->p_module )
594
        p_blend->p_module = module_need( p_blend, "video blending", 0, 0 );
595
}
596
static void SpuRenderCreateAndLoadText( spu_t *p_spu )
597 598 599
{
    filter_t *p_text;

600
    assert( !p_spu->p->p_text );
601

602
    p_spu->p->p_text =
603 604 605 606 607 608 609 610 611
    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 =
612
    p_text->fmt_out.video.i_visible_width = 32;
613
    p_text->fmt_out.video.i_height =
614
    p_text->fmt_out.video.i_visible_height = 32;
615 616 617 618 619 620

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

621
    /* FIXME TOCHECK shouldn't module_need( , , psz_modulename, false ) do the
622 623 624 625
     * same than these 2 calls ? */
    char *psz_modulename = var_CreateGetString( p_spu, "text-renderer" );
    if( psz_modulename && *psz_modulename )
    {
626
        p_text->p_module = module_need( p_text, "text renderer",
627 628 629 630 631
                                        psz_modulename, true );
    }
    free( psz_modulename );

    if( !p_text->p_module )
632
        p_text->p_module = module_need( p_text, "text renderer", NULL, false );
633 634 635 636 637 638

    /* 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 );
639 640
}

641 642 643
static filter_t *CreateAndLoadScale( vlc_object_t *p_obj,
                                     vlc_fourcc_t i_src_chroma, vlc_fourcc_t i_dst_chroma,
                                     bool b_resize )
644 645 646
{
    filter_t *p_scale;

647 648
    p_scale = vlc_custom_create( p_obj, sizeof(filter_t),
                                 VLC_OBJECT_GENERIC, "scale" );
649
    if( !p_scale )
650
        return NULL;
651 652

    es_format_Init( &p_scale->fmt_in, VIDEO_ES, 0 );
653
    p_scale->fmt_in.video.i_chroma = i_src_chroma;
654 655 656 657
    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 );
658
    p_scale->fmt_out.video.i_chroma = i_dst_chroma;
659
    p_scale->fmt_out.video.i_width =
660
    p_scale->fmt_out.video.i_height = b_resize ? 16 : 32;
661 662 663 664

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

665
    vlc_object_attach( p_scale, p_obj );
666
    p_scale->p_module = module_need( p_scale, "video filter2", 0, 0 );
667 668 669 670 671

    return p_scale;
}
static void SpuRenderCreateAndLoadScale( spu_t *p_spu )
{
672 673
    assert( !p_spu->p->p_scale );
    assert( !p_spu->p->p_scale_yuvp );
674 675
    /* XXX p_spu->p_scale is used for all conversion/scaling except yuvp to
     * yuva/rgba */
676 677
    p_spu->p->p_scale = CreateAndLoadScale( VLC_OBJECT(p_spu),
                                            VLC_FOURCC_YUVA, VLC_FOURCC_YUVA, true );
678 679
    /* This one is used for YUVP to YUVA/RGBA without scaling
     * FIXME rename it */
680 681
    p_spu->p->p_scale_yuvp = CreateAndLoadScale( VLC_OBJECT(p_spu),
                                                 VLC_FOURCC_YUVP, VLC_FOURCC_YUVA, false );
682 683
}

Laurent Aimar's avatar
Laurent Aimar committed
684
static void SpuRenderText( spu_t *p_spu, bool *pb_rerender_text,
685 686
                           subpicture_t *p_subpic, subpicture_region_t *p_region,
                           int i_min_scale_ratio )
Laurent Aimar's avatar
Laurent Aimar committed
687
{
688 689
    filter_t *p_text = p_spu->p->p_text;

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

692
    if( !p_text || !p_text->p_module )
Laurent Aimar's avatar
Laurent Aimar committed
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
        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.
     */
714 715 716 717
    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
718

719
    if( p_text->pf_render_html && p_region->psz_html )
Laurent Aimar's avatar
Laurent Aimar committed
720
    {
721
        p_text->pf_render_html( p_text, p_region, p_region );
Laurent Aimar's avatar
Laurent Aimar committed
722
    }
723
    else if( p_text->pf_render_text )
Laurent Aimar's avatar
Laurent Aimar committed
724
    {
725
        p_text->pf_render_text( p_text, p_region, p_region );
Laurent Aimar's avatar
Laurent Aimar committed
726
    }
727
    *pb_rerender_text = var_GetBool( p_text, "text-rerender" );
Laurent Aimar's avatar
Laurent Aimar committed
728 729 730 731 732

exit:
    p_region->i_align |= SUBPICTURE_RENDERED;
}

733 734 735
/**
 * A few scale functions helpers.
 */
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
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;
}
751 752 753 754
static spu_scale_t spu_scale_unit(void )
{
    return spu_scale_create( SCALE_UNIT, SCALE_UNIT );
}
755 756 757 758 759 760 761 762 763 764 765 766 767
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;
}
768 769 770 771
static int spu_invscale_w( int v, const spu_scale_t s )
{
    return v * SCALE_UNIT / s.w;
}
772 773 774 775
static int spu_invscale_h( int v, const spu_scale_t s )
{
    return v * SCALE_UNIT / s.h;
}
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 891 892 893 894 895 896 897
/**
 * 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
898 899 900 901 902
/**
 * Place a region
 */
static void SpuRegionPlace( int *pi_x, int *pi_y,
                            const subpicture_t *p_subpic,
903 904
                            const subpicture_region_t *p_region,
                            int i_margin_y )
Laurent Aimar's avatar
Laurent Aimar committed
905
{
906 907
    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
908 909
    int i_x, i_y;

910
    assert( p_region->i_x != INT_MAX && p_region->i_y != INT_MAX );
Laurent Aimar's avatar
Laurent Aimar committed
911 912 913 914 915 916
    if( p_region->i_align & SUBPICTURE_ALIGN_TOP )
    {
        i_y = i_delta_y;
    }
    else if( p_region->i_align & SUBPICTURE_ALIGN_BOTTOM )
    {
917
        i_y = p_subpic->i_original_picture_height - p_region->fmt.i_height - i_delta_y;
Laurent Aimar's avatar
Laurent Aimar committed
918 919 920
    }
    else
    {
921
        i_y = p_subpic->i_original_picture_height / 2 - p_region->fmt.i_height / 2;
Laurent Aimar's avatar
Laurent Aimar committed
922 923 924 925 926 927 928 929
    }

    if( p_region->i_align & SUBPICTURE_ALIGN_LEFT )
    {
        i_x = i_delta_x;
    }
    else if( p_region->i_align & SUBPICTURE_ALIGN_RIGHT )
    {
930
        i_x = p_subpic->i_original_picture_width - p_region->fmt.i_width - i_delta_x;
Laurent Aimar's avatar
Laurent Aimar committed
931 932 933
    }
    else
    {
934
        i_x = p_subpic->i_original_picture_width / 2 - p_region->fmt.i_width / 2;
Laurent Aimar's avatar
Laurent Aimar committed
935 936 937 938
    }

    if( p_subpic->b_absolute )
    {
939 940
        i_x = i_delta_x;
        i_y = i_delta_y;
Laurent Aimar's avatar
Laurent Aimar committed
941
    }
942

943
    /* Margin shifts all subpictures */
944
    if( i_margin_y != 0 )
945
        i_y -= i_margin_y;
946

947 948 949 950
    /* 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;
951 952
    if( i_x < 0 )
        i_x = 0;
953 954 955 956

    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;
957 958
    if( i_y < 0 )
        i_y = 0;
Laurent Aimar's avatar
Laurent Aimar committed
959

960 961
    *pi_x = i_x;
    *pi_y = i_y;
Laurent Aimar's avatar
Laurent Aimar committed
962 963
}

964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985
/**
 * 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;
}

986 987 988 989
/**
 * It will render the provided region onto p_pic_dst.
 */

Laurent Aimar's avatar
Laurent Aimar committed
990
static void SpuRenderRegion( spu_t *p_spu,
991
                             picture_t *p_pic_dst, spu_area_t *p_area,
Laurent Aimar's avatar
Laurent Aimar committed
992
                             subpicture_t *p_subpic, subpicture_region_t *p_region,
993
                             const spu_scale_t scale_size,
994 995
                             const video_format_t *p_fmt,
                             const spu_area_t *p_subtitle_area, int i_subtitle_area )
Laurent Aimar's avatar
Laurent Aimar committed
996
{
997 998
    spu_private_t *p_sys = p_spu->p;

999 1000
    video_format_t fmt_original = p_region->fmt;
    bool b_rerender_text = false;
1001
    bool b_restore_format = false;
Laurent Aimar's avatar
Laurent Aimar committed
1002 1003 1004
    int i_x_offset;
    int i_y_offset;

1005
    video_format_t region_fmt;
1006 1007
    picture_t *p_region_picture;

1008 1009 1010 1011
    /* Invalidate area by default */
    *p_area = spu_area_create( 0,0, 0,0, scale_size );

    /* Render text region */
1012
    if( p_region->fmt.i_chroma == VLC_FOURCC_TEXT )
Laurent Aimar's avatar
Laurent Aimar committed
1013
    {
1014
        const int i_min_scale_ratio = SCALE_UNIT; /* FIXME what is the right value? (scale_size is not) */
1015
        SpuRenderText( p_spu, &b_rerender_text, p_subpic, p_region, i_min_scale_ratio );
1016
        b_restore_format = b_rerender_text;
Laurent Aimar's avatar
Laurent Aimar committed
1017

Laurent Aimar's avatar
Laurent Aimar committed
1018
        /* Check if the rendering has failed ... */
1019
        if( p_region->fmt.i_chroma == VLC_FOURCC_TEXT )
Laurent Aimar's avatar
Laurent Aimar committed
1020 1021
            goto exit;
    }
Laurent Aimar's avatar
Laurent Aimar committed
1022

1023 1024 1025 1026
    /* 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).
     */
1027
    const bool b_using_palette = p_region->fmt.i_chroma == VLC_FOURCC_YUVP;
1028 1029
    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;
1030
    bool b_changed_palette     = false;
1031

1032

1033 1034 1035 1036 1037
    /* 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 )
1038
        i_margin_y = spu_invscale_h( p_sys->i_margin, scale_size );
1039

1040 1041 1042
    /* Place the picture
     * We compute the position in the rendered size */
    SpuRegionPlace( &i_x_offset, &i_y_offset,
1043 1044
                    p_subpic, p_region, i_margin_y );

1045 1046 1047 1048 1049 1050 1051 1052 1053
    /* 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 )
    {
1054 1055
        spu_area_t display = spu_area_create( 0, 0, p_fmt->i_width, p_fmt->i_height,
                                              spu_scale_unit() );
1056

1057 1058
        SpuAreaFixOverlap( p_area, &display, p_subtitle_area, i_subtitle_area,
                           p_region->i_align );
1059
    }
1060 1061

    /* Fix the position for the current scale_size */
1062 1063
    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 );
1064

1065
    /* */
1066
    if( b_force_palette )
Laurent Aimar's avatar
Laurent Aimar committed
1067
    {
1068 1069 1070 1071 1072 1073 1074
        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++ )
1075
                palette.palette[i][j] = p_sys->palette[i][j];
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087

        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
1088 1089
    }

1090
    /* */
1091
    region_fmt = p_region->fmt;
1092 1093
    p_region_picture = p_region->p_picture;

1094

1095
    /* Scale from rendered size to destination size */
1096 1097
    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 ) ) &&
1098
        ( scale_size.w != SCALE_UNIT || scale_size.h != SCALE_UNIT || b_using_palette ) )
Laurent Aimar's avatar
Laurent Aimar committed
1099
    {
1100 1101
        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
1102

1103
        /* Destroy the cache if unusable */
1104
        if( p_region->p_private )
Laurent Aimar's avatar
Laurent Aimar committed
1105
        {
1106 1107 1108 1109 1110 1111 1112 1113
            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;

1114 1115 1116
            /* Check forced palette changes */
            if( b_changed_palette )
                b_changed = true;
1117 1118 1119 1120 1121 1122

            if( b_changed )
            {
                SpuRegionPrivateDestroy( p_private );
                p_region->p_private = NULL;
            }
Laurent Aimar's avatar
Laurent Aimar committed
1123 1124 1125
        }

        /* Scale if needed into cache */
1126
        if( !p_region->p_private )
Laurent Aimar's avatar
Laurent Aimar committed
1127
        {
1128
            filter_t *p_scale = p_sys->p_scale;
1129 1130

            picture_t *p_picture = p_region->p_picture;
1131
            picture_Hold( p_picture );
1132 1133 1134 1135

            /* Convert YUVP to YUVA/RGBA first for better scaling quality */
            if( b_using_palette )
            {
1136
                filter_t *p_scale_yuvp = p_sys->p_scale_yuvp;
1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148

                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
1149 1150
                             (const char*)&p_scale_yuvp->fmt_in.video.i_chroma,
                             (const char*)&p_scale_yuvp->fmt_out.video.i_chroma );
Laurent Aimar's avatar