vout_subpictures.c 45.4 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 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

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

62 63
static int SubFilterAllocationInit( filter_t *, void * );
static void SubFilterAllocationClean( filter_t * );
64 65 66 67 68 69
struct filter_owner_sys_t
{
    spu_t *p_spu;
    int i_channel;
};

Laurent Aimar's avatar
Laurent Aimar committed
70 71
#define SCALE_UNIT (1000)

72 73 74 75
/* */
static void SpuRenderCreateAndLoadText( spu_t *p_spu );
static void SpuRenderCreateAndLoadScale( spu_t *p_spu );
static void FilterRelease( filter_t *p_filter );
76

77
/**
78
 * Creates the subpicture unit
79
 *
80
 * \param p_this the parent object which creates the subpicture unit
81
 */
82
spu_t *__spu_Create( vlc_object_t *p_this )
83 84
{
    int i_index;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
85 86
    spu_t *p_spu = vlc_custom_create( p_this, sizeof( spu_t ),
                                      VLC_OBJECT_GENERIC, "subpicture" );
87 88 89

    for( i_index = 0; i_index < VOUT_MAX_SUBPICTURES; i_index++)
    {
90
        p_spu->p_subpicture[i_index].i_status = FREE_SUBPICTURE;
91 92
    }

93 94 95
    p_spu->p_blend = NULL;
    p_spu->p_text = NULL;
    p_spu->p_scale = NULL;
96
    p_spu->p_scale_yuvp = NULL;
97
    p_spu->pf_control = spu_vaControlDefault;
98 99

    /* Register the default subpicture channel */
100
    p_spu->i_channel = 2;
101

102
    vlc_mutex_init( &p_spu->subpicture_lock );
103

104 105
    vlc_object_attach( p_spu, p_this );

106
    p_spu->p_chain = filter_chain_New( p_spu, "sub filter", false,
107 108
                                       SubFilterAllocationInit,
                                       SubFilterAllocationClean,
109
                                       p_spu );
110 111 112 113 114

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

115 116 117 118 119 120 121 122 123 124 125 126
    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;

127 128 129
    /* 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 );
130 131 132
    p_spu->i_margin = val.i_int;

    var_Create( p_spu, "sub-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
133 134 135 136
    var_AddCallback( p_spu, "sub-filter", SubFilterCallback, p_spu );

    spu_ParseChain( p_spu );

137
    return VLC_SUCCESS;
138
}
Antoine Cellerier's avatar
 
Antoine Cellerier committed
139

140 141
int spu_ParseChain( spu_t *p_spu )
{
142 143
    char *psz_parser = var_GetString( p_spu, "sub-filter" );
    if( filter_chain_AppendFromString( p_spu->p_chain, psz_parser ) < 0 )
144
    {
145 146
        free( psz_parser );
        return VLC_EGENERIC;
147
    }
148

149
    free( psz_parser );
150
    return VLC_SUCCESS;
151 152 153
}

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

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

171
    if( p_spu->p_blend )
172
        FilterRelease( p_spu->p_blend );
173

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

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

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

183
    filter_chain_Delete( p_spu->p_chain );
184 185

    vlc_mutex_destroy( &p_spu->subpicture_lock );
186
    vlc_object_release( p_spu );
187 188
}

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

199
    p_input = vlc_object_find( p_this, VLC_OBJECT_INPUT, FIND_PARENT );
200 201 202
    if( !p_input ) return;

    if( b_attach )
203
    {
204 205
        UpdateSPU( p_spu, VLC_OBJECT(p_input) );
        var_AddCallback( p_input, "highlight", CropCallback, p_spu );
206 207 208 209 210
        vlc_object_release( p_input );
    }
    else
    {
        /* Delete callback */
211
        var_DelCallback( p_input, "highlight", CropCallback, p_spu );
212 213 214 215
        vlc_object_release( p_input );
    }
}

216 217

/* */
218
static void RegionPictureRelease( picture_t *p_picture )
219
{
220 221 222 223 224 225 226
    if( --p_picture->i_refcount > 0 )
        return;

    assert( p_picture->i_refcount == 0 );
    free( p_picture->p_q );
    free( p_picture->p_data_orig );
    free( p_picture->p_sys );
227
}
228

229 230 231 232 233 234 235 236 237
/**
 * Create a subpicture region
 *
 * \param p_this vlc_object_t
 * \param p_fmt the format that this subpicture region should have
 */
subpicture_region_t *__spu_CreateRegion( vlc_object_t *p_this,
                                         video_format_t *p_fmt )
{
238
    subpicture_region_t *p_region = calloc( 1, sizeof(*p_region ) );
239 240
    if( !p_region )
        return NULL;
241

242 243 244 245 246 247 248 249 250 251 252 253 254
    /* FIXME is that *really* wanted? */
    if( p_fmt->i_chroma == VLC_FOURCC('Y','U','V','P') )
        p_fmt->p_palette = calloc( 1, sizeof(video_palette_t) );
    else
        p_fmt->p_palette = NULL;    /* XXX and that above all? */

    p_region->fmt = *p_fmt;
    p_region->i_alpha = 0xff;
    p_region->p_next = NULL;
    p_region->p_cache = NULL;
    p_region->psz_text = NULL;
    p_region->p_style = NULL;

255 256
    if( p_fmt->i_chroma == VLC_FOURCC('T','E','X','T') )
        return p_region;
257 258 259 260 261 262

    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 )
    {
263
        free( p_fmt->p_palette );
264
        free( p_region );
265 266 267
        return NULL;
    }

268
    p_region->picture.i_refcount = 1;
269 270
    p_region->picture.pf_release = RegionPictureRelease;

271 272 273 274 275 276 277 278 279 280 281
    return p_region;
}

/**
 * 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 )
{
282 283 284 285 286
    if( !p_region )
        return;

    picture_Release( &p_region->picture );

287
    free( p_region->fmt.p_palette );
288 289
    if( p_region->p_cache )
        __spu_DestroyRegion( p_this, p_region->p_cache );
290

291 292
    free( p_region->psz_text );
    free( p_region->psz_html );
293
    //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
294 295
    free( p_region );
}
Sam Hocevar's avatar
 
Sam Hocevar committed
296

297
/**
298
 * Display a subpicture
299
 *
Sam Hocevar's avatar
 
Sam Hocevar committed
300 301
 * Remove the reservation flag of a subpicture, which will cause it to be
 * ready for display.
302
 * \param p_spu the subpicture unit object
303 304
 * \param p_subpic the subpicture to display
 */
305
void spu_DisplaySubpicture( spu_t *p_spu, subpicture_t *p_subpic )
Sam Hocevar's avatar
 
Sam Hocevar committed
306 307 308 309
{
    /* Check if status is valid */
    if( p_subpic->i_status != RESERVED_SUBPICTURE )
    {
310 311
        msg_Err( p_spu, "subpicture %p has invalid status #%d",
                 p_subpic, p_subpic->i_status );
Sam Hocevar's avatar
 
Sam Hocevar committed
312 313
    }

314 315
    /* Remove reservation flag */
    p_subpic->i_status = READY_SUBPICTURE;
Sam Hocevar's avatar
 
Sam Hocevar committed
316

317
    if( p_subpic->i_channel == DEFAULT_CHAN )
Sam Hocevar's avatar
 
Sam Hocevar committed
318
    {
319 320 321
        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
322
    }
Sam Hocevar's avatar
 
Sam Hocevar committed
323 324
}

325
/**
326
 * Allocate a subpicture in the spu heap.
327
 *
328
 * This function create a reserved subpicture in the spu heap.
Sam Hocevar's avatar
 
Sam Hocevar committed
329 330 331
 * 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.
332
 * \param p_spu the subpicture unit in which to create the subpicture
333 334
 * \return NULL on error, a reserved subpicture otherwise
 */
335
subpicture_t *spu_CreateSubpicture( spu_t *p_spu )
Sam Hocevar's avatar
 
Sam Hocevar committed
336 337
{
    int                 i_subpic;                        /* subpicture index */
338
    subpicture_t *      p_subpic = NULL;            /* first free subpicture */
Sam Hocevar's avatar
 
Sam Hocevar committed
339

340
    /* Get lock */
341
    vlc_mutex_lock( &p_spu->subpicture_lock );
342

Sam Hocevar's avatar
 
Sam Hocevar committed
343 344 345
    /*
     * Look for an empty place
     */
346
    p_subpic = NULL;
Sam Hocevar's avatar
 
Sam Hocevar committed
347 348
    for( i_subpic = 0; i_subpic < VOUT_MAX_SUBPICTURES; i_subpic++ )
    {
349
        if( p_spu->p_subpicture[i_subpic].i_status == FREE_SUBPICTURE )
Sam Hocevar's avatar
 
Sam Hocevar committed
350 351
        {
            /* Subpicture is empty and ready for allocation */
352 353
            p_subpic = &p_spu->p_subpicture[i_subpic];
            p_spu->p_subpicture[i_subpic].i_status = RESERVED_SUBPICTURE;
354
            break;
Sam Hocevar's avatar
 
Sam Hocevar committed
355 356 357
        }
    }

358 359
    /* If no free subpicture could be found */
    if( p_subpic == NULL )
Sam Hocevar's avatar
 
Sam Hocevar committed
360
    {
361 362
        msg_Err( p_spu, "subpicture heap is full" );
        vlc_mutex_unlock( &p_spu->subpicture_lock );
363
        return NULL;
Sam Hocevar's avatar
 
Sam Hocevar committed
364
    }
Sam Hocevar's avatar
 
Sam Hocevar committed
365

366
    /* Copy subpicture information, set some default values */
367 368
    memset( p_subpic, 0, sizeof(subpicture_t) );
    p_subpic->i_status   = RESERVED_SUBPICTURE;
369 370
    p_subpic->b_absolute = true;
    p_subpic->b_fade     = false;
371
    p_subpic->b_subtitle = false;
372
    p_subpic->i_alpha    = 0xFF;
373 374 375 376
    p_subpic->p_region   = NULL;
    p_subpic->pf_render  = NULL;
    p_subpic->pf_destroy = NULL;
    p_subpic->p_sys      = NULL;
377
    vlc_mutex_unlock( &p_spu->subpicture_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
378

379 380
    p_subpic->pf_create_region = __spu_CreateRegion;
    p_subpic->pf_destroy_region = __spu_DestroyRegion;
381

382
    return p_subpic;
Sam Hocevar's avatar
 
Sam Hocevar committed
383 384
}

385 386 387
/**
 * Remove a subpicture from the heap
 *
Sam Hocevar's avatar
 
Sam Hocevar committed
388 389 390
 * 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
391
 * by the spu.
392
 */
393
void spu_DestroySubpicture( spu_t *p_spu, subpicture_t *p_subpic )
Sam Hocevar's avatar
 
Sam Hocevar committed
394
{
Gildas Bazin's avatar
 
Gildas Bazin committed
395
    /* Get lock */
396
    vlc_mutex_lock( &p_spu->subpicture_lock );
Gildas Bazin's avatar
 
Gildas Bazin committed
397 398 399 400

    /* There can be race conditions so we need to check the status */
    if( p_subpic->i_status == FREE_SUBPICTURE )
    {
401
        vlc_mutex_unlock( &p_spu->subpicture_lock );
Gildas Bazin's avatar
 
Gildas Bazin committed
402 403 404
        return;
    }

405 406 407 408
    /* Check if status is valid */
    if( ( p_subpic->i_status != RESERVED_SUBPICTURE )
           && ( p_subpic->i_status != READY_SUBPICTURE ) )
    {
409
        msg_Err( p_spu, "subpicture %p has invalid status %d",
410 411 412
                         p_subpic, p_subpic->i_status );
    }

413 414 415 416
    while( p_subpic->p_region )
    {
        subpicture_region_t *p_region = p_subpic->p_region;
        p_subpic->p_region = p_region->p_next;
417
        spu_DestroyRegion( p_spu, p_region );
418 419
    }

420 421 422 423 424 425
    if( p_subpic->pf_destroy )
    {
        p_subpic->pf_destroy( p_subpic );
    }

    p_subpic->i_status = FREE_SUBPICTURE;
Gildas Bazin's avatar
 
Gildas Bazin committed
426

427
    vlc_mutex_unlock( &p_spu->subpicture_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
428 429 430
}

/*****************************************************************************
431
 * spu_RenderSubpictures: render a subpicture list
Sam Hocevar's avatar
 
Sam Hocevar committed
432
 *****************************************************************************
Sam Hocevar's avatar
 
Sam Hocevar committed
433
 * This function renders all sub picture units in the list.
Sam Hocevar's avatar
 
Sam Hocevar committed
434
 *****************************************************************************/
435 436 437 438 439 440 441 442 443
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 );
}

444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
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
471
static void SpuRenderUpdateBlend( spu_t *p_spu, int i_out_width, int i_out_height, const video_format_t *p_in_fmt )
472 473 474 475 476 477
{
    filter_t *p_blend = p_spu->p_blend;

    assert( p_blend );

    /* */
Laurent Aimar's avatar
Laurent Aimar committed
478
    if( p_blend->p_module && p_blend->fmt_in.video.i_chroma != p_in_fmt->i_chroma )
479 480 481 482 483 484 485 486
    {
        /* 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
487 488 489 490 491 492 493
    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;
494 495 496 497 498

    /* */
    if( !p_blend->p_module )
        p_blend->p_module = module_Need( p_blend, "video blending", 0, 0 );
}
499
static void SpuRenderCreateAndLoadText( spu_t *p_spu )
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
{
    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 =
515
    p_text->fmt_out.video.i_visible_width = 32;
516
    p_text->fmt_out.video.i_height =
517
    p_text->fmt_out.video.i_visible_height = 32;
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537

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

538
static filter_t *CreateAndLoadScale( vlc_object_t *p_obj, vlc_fourcc_t i_chroma )
539 540 541
{
    filter_t *p_scale;

542 543
    p_scale = vlc_custom_create( p_obj, sizeof(filter_t),
                                 VLC_OBJECT_GENERIC, "scale" );
544
    if( !p_scale )
545
        return NULL;
546 547

    es_format_Init( &p_scale->fmt_in, VIDEO_ES, 0 );
548
    p_scale->fmt_in.video.i_chroma = i_chroma;
549 550 551 552
    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 );
553
    p_scale->fmt_out.video.i_chroma = i_chroma;
554 555 556 557 558 559
    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;

560 561 562 563 564 565 566 567 568 569 570 571 572
    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') );
573 574
}

Laurent Aimar's avatar
Laurent Aimar committed
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 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
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;
}

636 637 638
/**
 * A few scale functions helpers.
 */
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
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;
}
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;
}

Laurent Aimar's avatar
Laurent Aimar committed
668 669 670 671 672 673
/**
 * Place a region
 */
static void SpuRegionPlace( int *pi_x, int *pi_y,
                            const video_format_t *p_fmt,
                            const subpicture_t *p_subpic,
674
                            const subpicture_region_t *p_region )
Laurent Aimar's avatar
Laurent Aimar committed
675
{
676 677
    int i_delta_x = p_region->i_x;
    int i_delta_y = p_region->i_y;
Laurent Aimar's avatar
Laurent Aimar committed
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
    int i_x, i_y;

    if( p_region->i_align & SUBPICTURE_ALIGN_TOP )
    {
        i_y = i_delta_y;
    }
    else if( p_region->i_align & SUBPICTURE_ALIGN_BOTTOM )
    {
        i_y = p_fmt->i_height - p_region->fmt.i_height - i_delta_y;
    }
    else
    {
        i_y = p_fmt->i_height / 2 - p_region->fmt.i_height / 2;
    }

    if( p_region->i_align & SUBPICTURE_ALIGN_LEFT )
    {
        i_x = i_delta_x;
    }
    else if( p_region->i_align & SUBPICTURE_ALIGN_RIGHT )
    {
        i_x = p_fmt->i_width - p_region->fmt.i_width - i_delta_x;
    }
    else
    {
        i_x = p_fmt->i_width / 2 - p_region->fmt.i_width / 2;
    }

    if( p_subpic->b_absolute )
    {
708 709
        i_x = i_delta_x;
        i_y = i_delta_y;
Laurent Aimar's avatar
Laurent Aimar committed
710
    }
711 712 713 714
    if( i_x < 0 )
        i_x = 0;
    if( i_y < 0 )
        i_y = 0;
Laurent Aimar's avatar
Laurent Aimar committed
715

716 717
    *pi_x = i_x;
    *pi_y = i_y;
Laurent Aimar's avatar
Laurent Aimar committed
718 719
}

Laurent Aimar's avatar
Laurent Aimar committed
720
static void SpuRenderRegion( spu_t *p_spu,
721
                             picture_t *p_pic_dst,
Laurent Aimar's avatar
Laurent Aimar committed
722
                             subpicture_t *p_subpic, subpicture_region_t *p_region,
723
                             const spu_scale_t scale_size,
Laurent Aimar's avatar
Laurent Aimar committed
724 725
                             const video_format_t *p_fmt )
{
726
    video_format_t fmt_original;
Laurent Aimar's avatar
Laurent Aimar committed
727
    bool b_rerender_text;
728
    bool b_restore_format = false;
Laurent Aimar's avatar
Laurent Aimar committed
729
    int i_fade_alpha;
Laurent Aimar's avatar
Laurent Aimar committed
730 731
    int i_x_offset;
    int i_y_offset;
732
    filter_t *p_scale;
Laurent Aimar's avatar
Laurent Aimar committed
733

734 735 736
    vlc_assert_locked( &p_spu->subpicture_lock );

    fmt_original = p_region->fmt;
Laurent Aimar's avatar
Laurent Aimar committed
737
    b_rerender_text = false;
Laurent Aimar's avatar
Laurent Aimar committed
738
    if( p_region->fmt.i_chroma == VLC_FOURCC('T','E','X','T') )
Laurent Aimar's avatar
Laurent Aimar committed
739
    {
740
        SpuRenderText( p_spu, &b_rerender_text, p_subpic, p_region, SCALE_UNIT );
741
        b_restore_format = b_rerender_text;
Laurent Aimar's avatar
Laurent Aimar committed
742

Laurent Aimar's avatar
Laurent Aimar committed
743 744 745 746
        /* 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
747

748 749 750 751 752 753 754 755 756
    /* 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
757
    {
Laurent Aimar's avatar
Laurent Aimar committed
758
        /* It looks so wrong I won't comment
Laurent Aimar's avatar
Laurent Aimar committed
759 760 761
         * p_palette->palette is [256][4] with a int i_entries
         * p_spu->palette is [4][4]
         * */
762 763
        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
764 765
    }

766 767 768 769 770 771
    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
772
        ( scale_size.w != SCALE_UNIT || scale_size.h != SCALE_UNIT || b_force_palette ) )
Laurent Aimar's avatar
Laurent Aimar committed
773
    {
774 775
        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
776 777 778 779 780

        /* Destroy if cache is unusable */
        if( p_region->p_cache )
        {
            if( p_region->p_cache->fmt.i_width  != i_dst_width ||
781
                p_region->p_cache->fmt.i_height != i_dst_height ||
782
                b_force_palette )
Laurent Aimar's avatar
Laurent Aimar committed
783 784 785 786 787 788 789 790
            {
                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
791
        if( !p_region->p_cache )
Laurent Aimar's avatar
Laurent Aimar committed
792
        {
Laurent Aimar's avatar
Laurent Aimar committed
793 794
            picture_t *p_pic;

795 796
            p_scale->fmt_in.video = p_region->fmt;
            p_scale->fmt_out.video = p_region->fmt;
Laurent Aimar's avatar
Laurent Aimar committed
797

798 799
            if( p_scale->fmt_out.video.p_palette )
                *p_scale->fmt_out.video.p_palette =
Laurent Aimar's avatar
Laurent Aimar committed
800 801
                    *p_region->fmt.p_palette;

802 803
            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
804

805
            p_scale->fmt_out.video.i_visible_width =
806
                spu_scale_w( p_region->fmt.i_visible_width, scale_size );
807
            p_scale->fmt_out.video.i_visible_height =
808
                spu_scale_h( p_region->fmt.i_visible_height, scale_size );
Laurent Aimar's avatar
Laurent Aimar committed
809

Laurent Aimar's avatar
Laurent Aimar committed
810 811 812
            p_region->p_cache =
                p_subpic->pf_create_region( VLC_OBJECT(p_spu),
                                            &p_scale->fmt_out.video );
Laurent Aimar's avatar
Laurent Aimar committed
813

814 815
            p_pic = NULL;
            if( p_scale->p_module )
Laurent Aimar's avatar
Laurent Aimar committed
816
            {
817 818
                picture_Yield( &p_region->picture );
                p_pic = p_scale->pf_video_filter( p_scale, &p_region->picture );
Laurent Aimar's avatar
Laurent Aimar committed
819
            }
Laurent Aimar's avatar
Laurent Aimar committed
820 821
            if( p_pic )
            {
Laurent Aimar's avatar
Laurent Aimar committed
822 823 824 825 826 827 828 829
                picture_Copy( &p_region->p_cache->picture, p_pic );
                picture_Release( p_pic );

                p_region->p_cache->fmt = p_scale->fmt_out.video;
                p_region->p_cache->i_x = spu_scale_w( p_region->i_x, scale_size );
                p_region->p_cache->i_y = spu_scale_h( p_region->i_y, scale_size );
                p_region->p_cache->i_align = p_region->i_align;
                p_region->p_cache->i_alpha = p_region->i_alpha;
Laurent Aimar's avatar
Laurent Aimar committed
830
            }
831 832
            else
            {
Laurent Aimar's avatar
Laurent Aimar committed
833
                msg_Err( p_spu, "scaling failed (module not loaded)" );
834 835 836 837
                p_subpic->pf_destroy_region( VLC_OBJECT(p_spu),
                                             p_region->p_cache );
                p_region->p_cache = NULL;
            }
Laurent Aimar's avatar
Laurent Aimar committed
838

Laurent Aimar's avatar
Laurent Aimar committed
839 840
        }

841
        /* And use the scaled picture */
Laurent Aimar's avatar
Laurent Aimar committed
842
        if( p_region->p_cache )
843
        {
Laurent Aimar's avatar
Laurent Aimar committed
844
            p_region = p_region->p_cache;
845 846
            fmt_original = p_region->fmt;
        }
Laurent Aimar's avatar
Laurent Aimar committed
847 848
    }

Laurent Aimar's avatar
Laurent Aimar committed
849 850
    /* */
    SpuRegionPlace( &i_x_offset, &i_y_offset,
851
                    p_fmt, p_subpic, p_region );
Laurent Aimar's avatar
Laurent Aimar committed
852

853
    if( p_spu->i_margin != 0 && !b_force_crop )
Laurent Aimar's avatar
Laurent Aimar committed
854 855
    {
        int i_diff = 0;
856
        int i_low = i_y_offset - p_spu->i_margin;
Laurent Aimar's avatar
Laurent Aimar committed
857 858 859 860 861 862 863
        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;
864
        i_y_offset -= p_spu->i_margin + i_diff;
Laurent Aimar's avatar
Laurent Aimar committed
865 866
    }

Laurent Aimar's avatar
Laurent Aimar committed
867
    /* Force cropping if requested */
868
    if( b_force_crop )
Laurent Aimar's avatar
Laurent Aimar committed
869
    {
Laurent Aimar's avatar
Laurent Aimar committed
870
        video_format_t *p_fmt = &p_region->fmt;
871 872 873 874
        int i_crop_x = spu_scale_w( p_spu->i_crop_x, scale_size );
        int i_crop_y = spu_scale_h( p_spu->i_crop_y, scale_size );
        int i_crop_width = spu_scale_w( p_spu->i_crop_width, scale_size );
        int i_crop_height= spu_scale_h( p_spu->i_crop_height,scale_size );
Laurent Aimar's avatar
Laurent Aimar committed
875 876 877 878 879 880

        /* 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
881
        {
Laurent Aimar's avatar
Laurent Aimar committed
882 883
            /* No intersection */
            p_fmt->i_visible_width = p_fmt->i_visible_height = 0;
Laurent Aimar's avatar
Laurent Aimar committed
884 885 886
        }
        else
        {
Laurent Aimar's avatar
Laurent Aimar committed
887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
            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
902
        }
903
        b_restore_format = true;
Laurent Aimar's avatar
Laurent Aimar committed
904 905
    }

Laurent Aimar's avatar
Laurent Aimar committed
906 907 908
    i_x_offset = __MAX( i_x_offset, 0 );
    i_y_offset = __MAX( i_y_offset, 0 );

Laurent Aimar's avatar
Laurent Aimar committed
909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924
    /* 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
925 926 927 928

    if( p_spu->p_blend->p_module )
    {
        p_spu->p_blend->pf_video_blend( p_spu->p_blend, p_pic_dst,
929
            &p_region->picture, i_x_offset, i_y_offset,
Laurent Aimar's avatar
Laurent Aimar committed
930 931 932 933 934 935 936 937 938 939
            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
940 941 942 943 944 945 946 947 948 949 950
    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;
    }
951
    if( b_restore_format )
952
        p_region->fmt = fmt_original;
Laurent Aimar's avatar
Laurent Aimar committed
953
}
954

955
void spu_RenderSubpictures( spu_t *p_spu,
956
                            picture_t *p_pic_dst, const video_format_t *p_fmt_dst,
957
                            subpicture_t *p_subpic_list,
958
                            const video_format_t *p_fmt_src )
Sam Hocevar's avatar
 
Sam Hocevar committed
959
{
960 961
    const int i_source_video_width  = p_fmt_src->i_width;
    const int i_source_video_height = p_fmt_src->i_height;
962
    const mtime_t i_current_date = mdate();
963

964
    subpicture_t *p_subpic;
965

Gildas Bazin's avatar
 
Gildas Bazin committed
966
    /* Get lock */
967
    vlc_mutex_lock( &p_spu->subpicture_lock );
Gildas Bazin's avatar
 
Gildas Bazin committed
968

969 970 971 972 973 974 975 976
    /* Be sure we have at least 1 picture to process */
    if( !p_subpic_list || p_subpic_list->i_status == FREE_SUBPICTURE )
    {
        vlc_mutex_unlock( &p_spu->subpicture_lock );
        return;
    }

    /* */
977
    for( p_subpic = p_subpic_list;
978
            p_subpic != NULL && p_subpic->i_status != FREE_SUBPICTURE; /* Check again status (as we where unlocked) */
979
                p_subpic = p_subpic->p_next )
Sam Hocevar's avatar
 
Sam Hocevar committed
980
    {
981 982
        /* */
        if( p_subpic->pf_pre_render )
983
            p_subpic->pf_pre_render( p_spu, p_subpic, p_fmt_dst );
984

985 986
        if( p_subpic->pf_update_regions )
        {
987
            video_format_t fmt_org = *p_fmt_dst;
988 989 990 991 992
            fmt_org.i_width =
            fmt_org.i_visible_width = i_source_video_width;
            fmt_org.i_height =
            fmt_org.i_visible_height = i_source_video_height;

993
            p_subpic->pf_update_regions( p_spu, p_subpic, &fmt_org, i_current_date );
994
        }
995
    }
996

997 998
    /* Create the blending module */
    if( !p_spu->p_blend )
999
        SpuRenderCreateBlend( p_spu, p_fmt_dst->i_chroma, p_fmt_dst->i_aspect );
1000

1001 1002
    /* */
    for( p_subpic = p_subpic_list; ; p_subpic = p_subpic->p_next )
1003 1004
    {
        subpicture_region_t *p_region;
1005 1006 1007

        if( !p_subpic || p_subpic->i_status == FREE_SUBPICTURE )
            break;
1008