vout_subpictures.c 51.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 34 35
#include <assert.h>
#include <limits.h>

36
#include <vlc_common.h>
37 38
#include <vlc_modules.h>
#include <vlc_input.h>
Clément Stenac's avatar
Clément Stenac committed
39 40
#include <vlc_vout.h>
#include <vlc_filter.h>
41
#include <vlc_spu.h>
42

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
43
#include "../libvlc.h"
Laurent Aimar's avatar
Laurent Aimar committed
44
#include "vout_internal.h"
45
#include "../misc/subpicture.h"
46

47 48 49
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
50

51
/* Number of simultaneous subpictures */
52
#define VOUT_MAX_SUBPICTURES (__MAX(VOUT_MAX_PICTURES, SPU_MAX_PREPARE_TIME/5000))
53

Laurent Aimar's avatar
Laurent Aimar committed
54
/* */
Laurent Aimar's avatar
Laurent Aimar committed
55 56 57
typedef struct {
    subpicture_t *subpicture;
    bool          reject;
58 59
} spu_heap_entry_t;

Laurent Aimar's avatar
Laurent Aimar committed
60 61
typedef struct {
    spu_heap_entry_t entry[VOUT_MAX_SUBPICTURES];
62 63
} spu_heap_t;

Laurent Aimar's avatar
Laurent Aimar committed
64 65 66
struct spu_private_t {
    vlc_mutex_t  lock;            /* lock to protect all followings fields */
    vlc_object_t *input;
67

Laurent Aimar's avatar
Laurent Aimar committed
68
    spu_heap_t   heap;
69

Laurent Aimar's avatar
Laurent Aimar committed
70 71 72 73 74 75 76 77 78 79 80
    int channel;             /**< number of subpicture channels registered */
    filter_t *text;                              /**< text renderer module */
    filter_t *scale_yuvp;                     /**< scaling module for YUVP */
    filter_t *scale;                    /**< scaling module (all but YUVP) */
    bool force_crop;                     /**< force cropping of subpicture */
    struct {
        int x;
        int y;
        int width;
        int height;
    } crop;                                                  /**< cropping */
81

Laurent Aimar's avatar
Laurent Aimar committed
82 83 84
    int     margin;                    /**< force position of a subpicture */
    bool    force_palette;                /**< force palette of subpicture */
    uint8_t palette[4][4];                             /**< forced palette */
85

86
    /* Subpiture filters */
Laurent Aimar's avatar
Laurent Aimar committed
87
    char           *source_chain_update;
88
    vlc_mutex_t    source_chain_lock;
Laurent Aimar's avatar
Laurent Aimar committed
89 90
    filter_chain_t *source_chain;
    char           *filter_chain_update;
91
    vlc_mutex_t    filter_chain_lock;
Laurent Aimar's avatar
Laurent Aimar committed
92
    filter_chain_t *filter_chain;
93 94

    /* */
Laurent Aimar's avatar
Laurent Aimar committed
95
    mtime_t last_sort_date;
96 97
};

Laurent Aimar's avatar
Laurent Aimar committed
98 99 100
/*****************************************************************************
 * heap managment
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
101
static void SpuHeapInit(spu_heap_t *heap)
Laurent Aimar's avatar
Laurent Aimar committed
102
{
Laurent Aimar's avatar
Laurent Aimar committed
103 104
    for (int i = 0; i < VOUT_MAX_SUBPICTURES; i++) {
        spu_heap_entry_t *e = &heap->entry[i];
Laurent Aimar's avatar
Laurent Aimar committed
105

Laurent Aimar's avatar
Laurent Aimar committed
106 107
        e->subpicture = NULL;
        e->reject     = false;
Laurent Aimar's avatar
Laurent Aimar committed
108 109 110
    }
}

Laurent Aimar's avatar
Laurent Aimar committed
111
static int SpuHeapPush(spu_heap_t *heap, subpicture_t *subpic)
Laurent Aimar's avatar
Laurent Aimar committed
112
{
Laurent Aimar's avatar
Laurent Aimar committed
113 114
    for (int i = 0; i < VOUT_MAX_SUBPICTURES; i++) {
        spu_heap_entry_t *e = &heap->entry[i];
Laurent Aimar's avatar
Laurent Aimar committed
115

Laurent Aimar's avatar
Laurent Aimar committed
116
        if (e->subpicture)
Laurent Aimar's avatar
Laurent Aimar committed
117
            continue;
Laurent Aimar's avatar
Laurent Aimar committed
118

Laurent Aimar's avatar
Laurent Aimar committed
119 120
        e->subpicture = subpic;
        e->reject     = false;
Laurent Aimar's avatar
Laurent Aimar committed
121 122 123 124
        return VLC_SUCCESS;
    }
    return VLC_EGENERIC;
}
Laurent Aimar's avatar
Laurent Aimar committed
125

Laurent Aimar's avatar
Laurent Aimar committed
126
static void SpuHeapDeleteAt(spu_heap_t *heap, int index)
Laurent Aimar's avatar
Laurent Aimar committed
127
{
Laurent Aimar's avatar
Laurent Aimar committed
128
    spu_heap_entry_t *e = &heap->entry[index];
129

Laurent Aimar's avatar
Laurent Aimar committed
130 131
    if (e->subpicture)
        subpicture_Delete(e->subpicture);
132

Laurent Aimar's avatar
Laurent Aimar committed
133
    e->subpicture = NULL;
Laurent Aimar's avatar
Laurent Aimar committed
134
}
Laurent Aimar's avatar
Laurent Aimar committed
135

Laurent Aimar's avatar
Laurent Aimar committed
136
static int SpuHeapDeleteSubpicture(spu_heap_t *heap, subpicture_t *subpic)
Laurent Aimar's avatar
Laurent Aimar committed
137
{
Laurent Aimar's avatar
Laurent Aimar committed
138 139
    for (int i = 0; i < VOUT_MAX_SUBPICTURES; i++) {
        spu_heap_entry_t *e = &heap->entry[i];
Laurent Aimar's avatar
Laurent Aimar committed
140

Laurent Aimar's avatar
Laurent Aimar committed
141
        if (e->subpicture != subpic)
Laurent Aimar's avatar
Laurent Aimar committed
142 143
            continue;

Laurent Aimar's avatar
Laurent Aimar committed
144
        SpuHeapDeleteAt(heap, i);
Laurent Aimar's avatar
Laurent Aimar committed
145 146 147 148
        return VLC_SUCCESS;
    }
    return VLC_EGENERIC;
}
149

Laurent Aimar's avatar
Laurent Aimar committed
150
static void SpuHeapClean(spu_heap_t *heap)
Laurent Aimar's avatar
Laurent Aimar committed
151
{
Laurent Aimar's avatar
Laurent Aimar committed
152 153 154 155
    for (int i = 0; i < VOUT_MAX_SUBPICTURES; i++) {
        spu_heap_entry_t *e = &heap->entry[i];
        if (e->subpicture)
            subpicture_Delete(e->subpicture);
Laurent Aimar's avatar
Laurent Aimar committed
156 157
    }
}
158

Laurent Aimar's avatar
Laurent Aimar committed
159 160 161
struct filter_owner_sys_t {
    spu_t *spu;
    int   channel;
162
};
163

Laurent Aimar's avatar
Laurent Aimar committed
164
static void FilterRelease(filter_t *filter)
Laurent Aimar's avatar
Laurent Aimar committed
165
{
Laurent Aimar's avatar
Laurent Aimar committed
166 167 168 169
    if (filter->p_module)
        module_unneed(filter, filter->p_module);
    if (filter->p_owner)
        free(filter->p_owner);
170

Laurent Aimar's avatar
Laurent Aimar committed
171
    vlc_object_release(filter);
Laurent Aimar's avatar
Laurent Aimar committed
172
}
173

Laurent Aimar's avatar
Laurent Aimar committed
174
static picture_t *spu_new_video_buffer(filter_t *filter)
Laurent Aimar's avatar
Laurent Aimar committed
175
{
Laurent Aimar's avatar
Laurent Aimar committed
176
    const video_format_t *fmt = &filter->fmt_out.video;
177

Laurent Aimar's avatar
Laurent Aimar committed
178 179
    VLC_UNUSED(filter);
    return picture_NewFromFormat(fmt);
Laurent Aimar's avatar
Laurent Aimar committed
180
}
Laurent Aimar's avatar
Laurent Aimar committed
181
static void spu_del_video_buffer(filter_t *filter, picture_t *picture)
182
{
Laurent Aimar's avatar
Laurent Aimar committed
183 184
    VLC_UNUSED(filter);
    picture_Release(picture);
Laurent Aimar's avatar
Laurent Aimar committed
185
}
186

Laurent Aimar's avatar
Laurent Aimar committed
187 188 189
static int spu_get_attachments(filter_t *filter,
                               input_attachment_t ***attachment_ptr,
                               int *attachment_count)
Laurent Aimar's avatar
Laurent Aimar committed
190
{
Laurent Aimar's avatar
Laurent Aimar committed
191 192 193 194 195 196 197 198
    spu_t *spu = filter->p_owner->spu;

    int ret = VLC_EGENERIC;
    if (spu->p->input)
        ret = input_Control((input_thread_t*)spu->p->input,
                            INPUT_GET_ATTACHMENTS,
                            attachment_ptr, attachment_count);
    return ret;
Laurent Aimar's avatar
Laurent Aimar committed
199
}
200

Laurent Aimar's avatar
Laurent Aimar committed
201
static filter_t *SpuRenderCreateAndLoadText(spu_t *spu)
Laurent Aimar's avatar
Laurent Aimar committed
202
{
Laurent Aimar's avatar
Laurent Aimar committed
203 204 205
    filter_t *text = vlc_custom_create(spu, sizeof(*text),
                                       VLC_OBJECT_GENERIC, "spu text");
    if (!text)
206
        return NULL;
207

Laurent Aimar's avatar
Laurent Aimar committed
208 209
    text->p_owner = xmalloc(sizeof(*text->p_owner));
    text->p_owner->spu = spu;
210

Laurent Aimar's avatar
Laurent Aimar committed
211
    es_format_Init(&text->fmt_in, VIDEO_ES, 0);
212

Laurent Aimar's avatar
Laurent Aimar committed
213 214 215 216 217
    es_format_Init(&text->fmt_out, VIDEO_ES, 0);
    text->fmt_out.video.i_width          =
    text->fmt_out.video.i_visible_width  = 32;
    text->fmt_out.video.i_height         =
    text->fmt_out.video.i_visible_height = 32;
218

Laurent Aimar's avatar
Laurent Aimar committed
219
    text->pf_get_attachments = spu_get_attachments;
220

Laurent Aimar's avatar
Laurent Aimar committed
221
    text->p_module = module_need(text, "text renderer", "$text-renderer", false);
Laurent Aimar's avatar
Laurent Aimar committed
222 223

    /* Create a few variables used for enhanced text rendering */
Laurent Aimar's avatar
Laurent Aimar committed
224 225
    var_Create(text, "spu-elapsed",   VLC_VAR_TIME);
    var_Create(text, "text-rerender", VLC_VAR_BOOL);
226

Laurent Aimar's avatar
Laurent Aimar committed
227
    return text;
228 229
}

Laurent Aimar's avatar
Laurent Aimar committed
230 231 232 233
static filter_t *SpuRenderCreateAndLoadScale(vlc_object_t *object,
                                             vlc_fourcc_t src_chroma,
                                             vlc_fourcc_t dst_chroma,
                                             bool require_resize)
234
{
Laurent Aimar's avatar
Laurent Aimar committed
235 236 237
    filter_t *scale = vlc_custom_create(object, sizeof(*scale),
                                        VLC_OBJECT_GENERIC, "scale");
    if (!scale)
Laurent Aimar's avatar
Laurent Aimar committed
238
        return NULL;
239

Laurent Aimar's avatar
Laurent Aimar committed
240 241 242 243
    es_format_Init(&scale->fmt_in, VIDEO_ES, 0);
    scale->fmt_in.video.i_chroma = src_chroma;
    scale->fmt_in.video.i_width =
    scale->fmt_in.video.i_height = 32;
244

Laurent Aimar's avatar
Laurent Aimar committed
245 246 247 248
    es_format_Init(&scale->fmt_out, VIDEO_ES, 0);
    scale->fmt_out.video.i_chroma = dst_chroma;
    scale->fmt_out.video.i_width =
    scale->fmt_out.video.i_height = require_resize ? 16 : 32;
249

Laurent Aimar's avatar
Laurent Aimar committed
250 251
    scale->pf_video_buffer_new = spu_new_video_buffer;
    scale->pf_video_buffer_del = spu_del_video_buffer;
252

Laurent Aimar's avatar
Laurent Aimar committed
253
    scale->p_module = module_need(scale, "video filter2", NULL, false);
254

Laurent Aimar's avatar
Laurent Aimar committed
255
    return scale;
Laurent Aimar's avatar
Laurent Aimar committed
256
}
257

Laurent Aimar's avatar
Laurent Aimar committed
258 259
static void SpuRenderText(spu_t *spu, bool *rerender_text,
                          subpicture_region_t *region,
260
                          const vlc_fourcc_t *chroma_list,
261
                          mtime_t elapsed_time)
262
{
Laurent Aimar's avatar
Laurent Aimar committed
263
    filter_t *text = spu->p->text;
264

Laurent Aimar's avatar
Laurent Aimar committed
265
    assert(region->fmt.i_chroma == VLC_CODEC_TEXT);
266

Laurent Aimar's avatar
Laurent Aimar committed
267
    if (!text || !text->p_module)
268
        return;
269

Laurent Aimar's avatar
Laurent Aimar committed
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    /* 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.
     */
289
    var_SetTime(text, "spu-elapsed", elapsed_time);
Laurent Aimar's avatar
Laurent Aimar committed
290 291 292
    var_SetBool(text, "text-rerender", false);

    if (text->pf_render_html && region->psz_html)
293
        text->pf_render_html(text, region, region, chroma_list);
Laurent Aimar's avatar
Laurent Aimar committed
294
    else if (text->pf_render_text)
295
        text->pf_render_text(text, region, region, chroma_list);
Laurent Aimar's avatar
Laurent Aimar committed
296
    *rerender_text = var_GetBool(text, "text-rerender");
297 298
}

299
/**
Laurent Aimar's avatar
Laurent Aimar committed
300
 * A few scale functions helpers.
301 302
 */

Laurent Aimar's avatar
Laurent Aimar committed
303
#define SCALE_UNIT (1000)
Laurent Aimar's avatar
Laurent Aimar committed
304
typedef struct {
Laurent Aimar's avatar
Laurent Aimar committed
305 306 307
    int w;
    int h;
} spu_scale_t;
308

Laurent Aimar's avatar
Laurent Aimar committed
309
static spu_scale_t spu_scale_create(int w, int h)
Laurent Aimar's avatar
Laurent Aimar committed
310 311
{
    spu_scale_t s = { .w = w, .h = h };
Laurent Aimar's avatar
Laurent Aimar committed
312
    if (s.w <= 0)
Laurent Aimar's avatar
Laurent Aimar committed
313
        s.w = SCALE_UNIT;
Laurent Aimar's avatar
Laurent Aimar committed
314
    if (s.h <= 0)
Laurent Aimar's avatar
Laurent Aimar committed
315 316 317
        s.h = SCALE_UNIT;
    return s;
}
Laurent Aimar's avatar
Laurent Aimar committed
318
static spu_scale_t spu_scale_unit(void)
Laurent Aimar's avatar
Laurent Aimar committed
319
{
Laurent Aimar's avatar
Laurent Aimar committed
320
    return spu_scale_create(SCALE_UNIT, SCALE_UNIT);
Laurent Aimar's avatar
Laurent Aimar committed
321
}
Laurent Aimar's avatar
Laurent Aimar committed
322
static spu_scale_t spu_scale_createq(int64_t wn, int64_t wd, int64_t hn, int64_t hd)
Laurent Aimar's avatar
Laurent Aimar committed
323
{
Laurent Aimar's avatar
Laurent Aimar committed
324 325
    return spu_scale_create(wn * SCALE_UNIT / wd,
                            hn * SCALE_UNIT / hd);
Laurent Aimar's avatar
Laurent Aimar committed
326
}
Laurent Aimar's avatar
Laurent Aimar committed
327
static int spu_scale_w(int v, const spu_scale_t s)
Laurent Aimar's avatar
Laurent Aimar committed
328 329 330
{
    return v * s.w / SCALE_UNIT;
}
Laurent Aimar's avatar
Laurent Aimar committed
331
static int spu_scale_h(int v, const spu_scale_t s)
Laurent Aimar's avatar
Laurent Aimar committed
332 333 334
{
    return v * s.h / SCALE_UNIT;
}
Laurent Aimar's avatar
Laurent Aimar committed
335
static int spu_invscale_w(int v, const spu_scale_t s)
Laurent Aimar's avatar
Laurent Aimar committed
336 337 338
{
    return v * SCALE_UNIT / s.w;
}
Laurent Aimar's avatar
Laurent Aimar committed
339
static int spu_invscale_h(int v, const spu_scale_t s)
Laurent Aimar's avatar
Laurent Aimar committed
340 341
{
    return v * SCALE_UNIT / s.h;
342 343
}

344
/**
Laurent Aimar's avatar
Laurent Aimar committed
345
 * A few area functions helpers
346
 */
Laurent Aimar's avatar
Laurent Aimar committed
347 348 349 350 351
typedef struct {
    int x;
    int y;
    int width;
    int height;
352

Laurent Aimar's avatar
Laurent Aimar committed
353 354
    spu_scale_t scale;
} spu_area_t;
355

Laurent Aimar's avatar
Laurent Aimar committed
356
static spu_area_t spu_area_create(int x, int y, int w, int h, spu_scale_t s)
Laurent Aimar's avatar
Laurent Aimar committed
357
{
Laurent Aimar's avatar
Laurent Aimar committed
358
    spu_area_t a = { .x = x, .y = y, .width = w, .height = h, .scale = s };
Laurent Aimar's avatar
Laurent Aimar committed
359
    return a;
Sam Hocevar's avatar
 
Sam Hocevar committed
360
}
Laurent Aimar's avatar
Laurent Aimar committed
361
static spu_area_t spu_area_scaled(spu_area_t a)
362
{
Laurent Aimar's avatar
Laurent Aimar committed
363
    if (a.scale.w == SCALE_UNIT && a.scale.h == SCALE_UNIT)
Laurent Aimar's avatar
Laurent Aimar committed
364
        return a;
365

Laurent Aimar's avatar
Laurent Aimar committed
366 367 368 369
    a.x      = spu_scale_w(a.x,      a.scale);
    a.y      = spu_scale_h(a.y,      a.scale);
    a.width  = spu_scale_w(a.width,  a.scale);
    a.height = spu_scale_h(a.height, a.scale);
370

Laurent Aimar's avatar
Laurent Aimar committed
371 372 373
    a.scale = spu_scale_unit();
    return a;
}
Laurent Aimar's avatar
Laurent Aimar committed
374
static spu_area_t spu_area_unscaled(spu_area_t a, spu_scale_t s)
Laurent Aimar's avatar
Laurent Aimar committed
375
{
Laurent Aimar's avatar
Laurent Aimar committed
376
    if (a.scale.w == s.w && a.scale.h == s.h)
Laurent Aimar's avatar
Laurent Aimar committed
377
        return a;
378

Laurent Aimar's avatar
Laurent Aimar committed
379
    a = spu_area_scaled(a);
380

Laurent Aimar's avatar
Laurent Aimar committed
381 382 383 384
    a.x      = spu_invscale_w(a.x,      s);
    a.y      = spu_invscale_h(a.y,      s);
    a.width  = spu_invscale_w(a.width,  s);
    a.height = spu_invscale_h(a.height, s);
385

Laurent Aimar's avatar
Laurent Aimar committed
386 387 388
    a.scale = s;
    return a;
}
Laurent Aimar's avatar
Laurent Aimar committed
389
static bool spu_area_overlap(spu_area_t a, spu_area_t b)
Laurent Aimar's avatar
Laurent Aimar committed
390
{
Laurent Aimar's avatar
Laurent Aimar committed
391 392
    const int dx = 0;
    const int dy = 0;
393

Laurent Aimar's avatar
Laurent Aimar committed
394 395
    a = spu_area_scaled(a);
    b = spu_area_scaled(b);
396

Laurent Aimar's avatar
Laurent Aimar committed
397 398
    return __MAX(a.x - dx, b.x) < __MIN(a.x + a.width  + dx, b.x + b.width ) &&
           __MAX(a.y - dy, b.y) < __MIN(a.y + a.height + dy, b.y + b.height);
Laurent Aimar's avatar
Laurent Aimar committed
399
}
Laurent Aimar's avatar
Laurent Aimar committed
400

Laurent Aimar's avatar
Laurent Aimar committed
401 402 403
/**
 * Avoid area overlapping
 */
Laurent Aimar's avatar
Laurent Aimar committed
404 405
static void SpuAreaFixOverlap(spu_area_t *dst,
                              const spu_area_t *sub_array, int sub_count, int align)
Laurent Aimar's avatar
Laurent Aimar committed
406
{
Laurent Aimar's avatar
Laurent Aimar committed
407 408 409
    spu_area_t a = spu_area_scaled(*dst);
    bool is_moved = false;
    bool is_ok;
410

Laurent Aimar's avatar
Laurent Aimar committed
411 412
    /* Check for overlap
     * XXX It is not fast O(n^2) but we should not have a lot of region */
Laurent Aimar's avatar
Laurent Aimar committed
413 414 415 416 417 418
    do {
        is_ok = true;
        for (int i = 0; i < sub_count; i++) {
            spu_area_t sub = spu_area_scaled(sub_array[i]);

            if (!spu_area_overlap(a, sub))
Laurent Aimar's avatar
Laurent Aimar committed
419
                continue;
420

Laurent Aimar's avatar
Laurent Aimar committed
421
            if (align & SUBPICTURE_ALIGN_TOP) {
Laurent Aimar's avatar
Laurent Aimar committed
422
                /* We go down */
Laurent Aimar's avatar
Laurent Aimar committed
423 424 425 426
                int i_y = sub.y + sub.height;
                a.y = i_y;
                is_moved = true;
            } else if (align & SUBPICTURE_ALIGN_BOTTOM) {
Laurent Aimar's avatar
Laurent Aimar committed
427
                /* We go up */
Laurent Aimar's avatar
Laurent Aimar committed
428 429 430 431
                int i_y = sub.y - a.height;
                a.y = i_y;
                is_moved = true;
            } else {
Laurent Aimar's avatar
Laurent Aimar committed
432
                /* TODO what to do in this case? */
Laurent Aimar's avatar
Laurent Aimar committed
433
                //fprintf(stderr, "Overlap with unsupported alignment\n");
Laurent Aimar's avatar
Laurent Aimar committed
434 435
                break;
            }
436

Laurent Aimar's avatar
Laurent Aimar committed
437
            is_ok = false;
Laurent Aimar's avatar
Laurent Aimar committed
438
            break;
Laurent Aimar's avatar
Laurent Aimar committed
439
        }
Laurent Aimar's avatar
Laurent Aimar committed
440
    } while (!is_ok);
441

Laurent Aimar's avatar
Laurent Aimar committed
442 443
    if (is_moved)
        *dst = spu_area_unscaled(a, dst->scale);
Laurent Aimar's avatar
Laurent Aimar committed
444
}
445

446

Laurent Aimar's avatar
Laurent Aimar committed
447
static void SpuAreaFitInside(spu_area_t *area, const spu_area_t *boundary)
Laurent Aimar's avatar
Laurent Aimar committed
448
{
Laurent Aimar's avatar
Laurent Aimar committed
449
  spu_area_t a = spu_area_scaled(*area);
450

Laurent Aimar's avatar
Laurent Aimar committed
451 452 453 454 455
  const int i_error_x = (a.x + a.width) - boundary->width;
  if (i_error_x > 0)
      a.x -= i_error_x;
  if (a.x < 0)
      a.x = 0;
456

Laurent Aimar's avatar
Laurent Aimar committed
457 458 459 460 461
  const int i_error_y = (a.y + a.height) - boundary->height;
  if (i_error_y > 0)
      a.y -= i_error_y;
  if (a.y < 0)
      a.y = 0;
462

Laurent Aimar's avatar
Laurent Aimar committed
463
  *area = spu_area_unscaled(a, area->scale);
Laurent Aimar's avatar
Laurent Aimar committed
464
}
465

Laurent Aimar's avatar
Laurent Aimar committed
466 467 468
/**
 * Place a region
 */
Laurent Aimar's avatar
Laurent Aimar committed
469 470 471
static void SpuRegionPlace(int *x, int *y,
                           const subpicture_t *subpic,
                           const subpicture_region_t *region)
Laurent Aimar's avatar
Laurent Aimar committed
472
{
Laurent Aimar's avatar
Laurent Aimar committed
473 474 475 476 477 478 479 480 481
    assert(region->i_x != INT_MAX && region->i_y != INT_MAX);
    if (subpic->b_absolute) {
        *x = region->i_x;
        *y = region->i_y;
    } else {
        if (region->i_align & SUBPICTURE_ALIGN_TOP)
            *y = region->i_y;
        else if (region->i_align & SUBPICTURE_ALIGN_BOTTOM)
            *y = subpic->i_original_picture_height - region->fmt.i_height - region->i_y;
482
        else
Laurent Aimar's avatar
Laurent Aimar committed
483
            *y = subpic->i_original_picture_height / 2 - region->fmt.i_height / 2;
484

Laurent Aimar's avatar
Laurent Aimar committed
485 486 487 488
        if (region->i_align & SUBPICTURE_ALIGN_LEFT)
            *x = region->i_x;
        else if (region->i_align & SUBPICTURE_ALIGN_RIGHT)
            *x = subpic->i_original_picture_width - region->fmt.i_width - region->i_x;
489
        else
Laurent Aimar's avatar
Laurent Aimar committed
490
            *x = subpic->i_original_picture_width / 2 - region->fmt.i_width / 2;
Laurent Aimar's avatar
Laurent Aimar committed
491 492 493 494 495 496 497
    }
}

/**
 * This function compares two 64 bits integers.
 * It can be used by qsort.
 */
Laurent Aimar's avatar
Laurent Aimar committed
498
static int IntegerCmp(int64_t i0, int64_t i1)
Laurent Aimar's avatar
Laurent Aimar committed
499 500 501 502 503 504 505 506 507 508 509 510 511 512
{
    return i0 < i1 ? -1 : i0 > i1 ? 1 : 0;
}
/**
 * This function compares 2 subpictures using the following properties
 * (ordered by priority)
 * 1. absolute positionning
 * 2. start time
 * 3. creation order (per channel)
 *
 * It can be used by qsort.
 *
 * XXX spu_RenderSubpictures depends heavily on this order.
 */
Laurent Aimar's avatar
Laurent Aimar committed
513
static int SubpictureCmp(const void *s0, const void *s1)
Laurent Aimar's avatar
Laurent Aimar committed
514
{
Laurent Aimar's avatar
Laurent Aimar committed
515 516
    subpicture_t *subpic0 = *(subpicture_t**)s0;
    subpicture_t *subpic1 = *(subpicture_t**)s1;
Laurent Aimar's avatar
Laurent Aimar committed
517 518
    int r;

Laurent Aimar's avatar
Laurent Aimar committed
519 520 521 522 523 524 525
    r = IntegerCmp(!subpic0->b_absolute, !subpic1->b_absolute);
    if (!r)
        r = IntegerCmp(subpic0->i_start, subpic1->i_start);
    if (!r)
        r = IntegerCmp(subpic0->i_channel, subpic1->i_channel);
    if (!r)
        r = IntegerCmp(subpic0->i_order, subpic1->i_order);
Laurent Aimar's avatar
Laurent Aimar committed
526 527 528
    return r;
}

Laurent Aimar's avatar
Laurent Aimar committed
529
/*****************************************************************************
530
 * SpuSelectSubpictures: find the subpictures to display
Laurent Aimar's avatar
Laurent Aimar committed
531 532 533 534 535 536 537 538
 *****************************************************************************
 * This function parses all subpictures and decides which ones need to be
 * displayed. If no picture has been selected, display_date will depend on
 * the subpicture.
 * We also check for ephemer DVD subpictures (subpictures that have
 * to be removed if a newer one is available), which makes it a lot
 * more difficult to guess if a subpicture has to be rendered or not.
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
539 540 541 542 543 544
static void SpuSelectSubpictures(spu_t *spu,
                                 unsigned int *subpicture_count,
                                 subpicture_t **subpicture_array,
                                 mtime_t render_subtitle_date,
                                 mtime_t render_osd_date,
                                 bool ignore_osd)
Laurent Aimar's avatar
Laurent Aimar committed
545
{
Laurent Aimar's avatar
Laurent Aimar committed
546
    spu_private_t *sys = spu->p;
547 548

    /* */
Laurent Aimar's avatar
Laurent Aimar committed
549
    *subpicture_count = 0;
Laurent Aimar's avatar
Laurent Aimar committed
550

Laurent Aimar's avatar
Laurent Aimar committed
551
    /* Create a list of channels */
Laurent Aimar's avatar
Laurent Aimar committed
552 553
    int channel[VOUT_MAX_SUBPICTURES];
    int channel_count = 0;
Laurent Aimar's avatar
Laurent Aimar committed
554

Laurent Aimar's avatar
Laurent Aimar committed
555 556 557
    for (int index = 0; index < VOUT_MAX_SUBPICTURES; index++) {
        spu_heap_entry_t *entry = &sys->heap.entry[index];
        if (!entry->subpicture || entry->reject)
Laurent Aimar's avatar
Laurent Aimar committed
558
            continue;
Laurent Aimar's avatar
Laurent Aimar committed
559
        const int i_channel = entry->subpicture->i_channel;
Laurent Aimar's avatar
Laurent Aimar committed
560
        int i;
Laurent Aimar's avatar
Laurent Aimar committed
561 562
        for (i = 0; i < channel_count; i++) {
            if (channel[i] == i_channel)
Laurent Aimar's avatar
Laurent Aimar committed
563 564
                break;
        }
Laurent Aimar's avatar
Laurent Aimar committed
565 566
        if (channel_count <= i)
            channel[channel_count++] = i_channel;
Laurent Aimar's avatar
Laurent Aimar committed
567 568
    }

Laurent Aimar's avatar
Laurent Aimar committed
569 570 571 572 573
    /* Fill up the subpicture_array arrays with relevent pictures */
    for (int i = 0; i < channel_count; i++) {
        subpicture_t *available_subpic[VOUT_MAX_SUBPICTURES];
        bool         is_available_late[VOUT_MAX_SUBPICTURES];
        int          available_count = 0;
574

575
        mtime_t      start_date = render_subtitle_date;
576
        mtime_t      ephemer_subtitle_date = 0;
577
        mtime_t      ephemer_osd_date = 0;
Laurent Aimar's avatar
Laurent Aimar committed
578 579
        int64_t      ephemer_subtitle_order = INT64_MIN;
        int64_t      ephemer_system_order = INT64_MIN;
Laurent Aimar's avatar
Laurent Aimar committed
580

581
        /* Select available pictures */
Laurent Aimar's avatar
Laurent Aimar committed
582 583 584 585 586 587 588 589 590
        for (int index = 0; index < VOUT_MAX_SUBPICTURES; index++) {
            spu_heap_entry_t *entry = &sys->heap.entry[index];
            subpicture_t *current = entry->subpicture;
            bool is_stop_valid;
            bool is_late;

            if (!current || entry->reject) {
                if (entry->reject)
                    SpuHeapDeleteAt(&sys->heap, index);
Laurent Aimar's avatar
Laurent Aimar committed
591 592 593
                continue;
            }

Laurent Aimar's avatar
Laurent Aimar committed
594 595
            if (current->i_channel != channel[i] ||
               (ignore_osd && !current->b_subtitle))
Laurent Aimar's avatar
Laurent Aimar committed
596
                continue;
Laurent Aimar's avatar
Laurent Aimar committed
597 598 599 600

            const mtime_t render_date = current->b_subtitle ? render_subtitle_date : render_osd_date;
            if (render_date &&
                render_date < current->i_start) {
Laurent Aimar's avatar
Laurent Aimar committed
601 602 603 604
                /* Too early, come back next monday */
                continue;
            }

Laurent Aimar's avatar
Laurent Aimar committed
605 606 607 608 609 610
            mtime_t *ephemer_date_ptr  = current->b_subtitle ? &ephemer_subtitle_date  : &ephemer_osd_date;
            int64_t *ephemer_order_ptr = current->b_subtitle ? &ephemer_subtitle_order : &ephemer_system_order;
            if (current->i_start >= *ephemer_date_ptr) {
                *ephemer_date_ptr = current->i_start;
                if (current->i_order > *ephemer_order_ptr)
                    *ephemer_order_ptr = current->i_order;
611
            }
Laurent Aimar's avatar
Laurent Aimar committed
612

Laurent Aimar's avatar
Laurent Aimar committed
613
            is_stop_valid = !current->b_ephemer || current->i_stop > current->i_start;
614

Laurent Aimar's avatar
Laurent Aimar committed
615
            is_late = is_stop_valid && current->i_stop <= render_date;
616 617

            /* start_date will be used for correct automatic overlap support
Laurent Aimar's avatar
Laurent Aimar committed
618
             * in case picture that should not be displayed anymore (display_time)
Laurent Aimar's avatar
Laurent Aimar committed
619 620 621
             * overlap with a picture to be displayed (current->i_start)  */
            if (current->b_subtitle && !is_late && !current->b_ephemer)
                start_date = current->i_start;
622 623

            /* */
Laurent Aimar's avatar
Laurent Aimar committed
624 625 626
            available_subpic[available_count] = current;
            is_available_late[available_count] = is_late;
            available_count++;
Laurent Aimar's avatar
Laurent Aimar committed
627 628
        }

629
        /* Only forced old picture display at the transition */
Laurent Aimar's avatar
Laurent Aimar committed
630 631 632
        if (start_date < sys->last_sort_date)
            start_date = sys->last_sort_date;
        if (start_date <= 0)
633 634 635
            start_date = INT64_MAX;

        /* Select pictures to be displayed */
Laurent Aimar's avatar
Laurent Aimar committed
636 637 638
        for (int index = 0; index < available_count; index++) {
            subpicture_t *current = available_subpic[index];
            bool is_late = is_available_late[index];
Laurent Aimar's avatar
Laurent Aimar committed
639

Laurent Aimar's avatar
Laurent Aimar committed
640 641 642
            const mtime_t stop_date = current->b_subtitle ? __MAX(start_date, sys->last_sort_date) : render_osd_date;
            const mtime_t ephemer_date  = current->b_subtitle ? ephemer_subtitle_date  : ephemer_osd_date;
            const int64_t ephemer_order = current->b_subtitle ? ephemer_subtitle_order : ephemer_system_order;
643

644
            /* Destroy late and obsolete ephemer subpictures */
Laurent Aimar's avatar
Laurent Aimar committed
645 646 647 648 649 650 651
            bool is_rejeted = is_late && current->i_stop <= stop_date;
            if (current->b_ephemer) {
                if (current->i_start < ephemer_date)
                    is_rejeted = true;
                else if (current->i_start == ephemer_date &&
                         current->i_order < ephemer_order)
                    is_rejeted = true;
Laurent Aimar's avatar
Laurent Aimar committed
652
            }
653

Laurent Aimar's avatar
Laurent Aimar committed
654 655
            if (is_rejeted)
                SpuHeapDeleteSubpicture(&sys->heap, current);
Laurent Aimar's avatar
Laurent Aimar committed
656
            else
Laurent Aimar's avatar
Laurent Aimar committed
657
                subpicture_array[(*subpicture_count)++] = current;
Laurent Aimar's avatar
Laurent Aimar committed
658
        }
Laurent Aimar's avatar
Laurent Aimar committed
659
    }
660

Laurent Aimar's avatar
Laurent Aimar committed
661
    sys->last_sort_date = render_subtitle_date;
Laurent Aimar's avatar
Laurent Aimar committed
662 663
}

Laurent Aimar's avatar
Laurent Aimar committed
664 665 666 667 668


/**
 * It will transform the provided region into another region suitable for rendering.
 */
Laurent Aimar's avatar
Laurent Aimar committed
669 670 671 672 673 674 675 676
static void SpuRenderRegion(spu_t *spu,
                            subpicture_region_t **dst_ptr, spu_area_t *dst_area,
                            subpicture_t *subpic, subpicture_region_t *region,
                            const spu_scale_t scale_size,
                            const vlc_fourcc_t *chroma_list,
                            const video_format_t *fmt,
                            const spu_area_t *subtitle_area, int subtitle_area_count,
                            mtime_t render_date)
677
{
Laurent Aimar's avatar
Laurent Aimar committed
678
    spu_private_t *sys = spu->p;
679

Laurent Aimar's avatar
Laurent Aimar committed
680 681 682 683
    video_format_t fmt_original = region->fmt;
    bool restore_text = false;
    int x_offset;
    int y_offset;
684

Laurent Aimar's avatar
Laurent Aimar committed
685
    video_format_t region_fmt;
Laurent Aimar's avatar
Laurent Aimar committed
686
    picture_t *region_picture;
687

Laurent Aimar's avatar
Laurent Aimar committed
688
    /* Invalidate area by default */
Laurent Aimar's avatar
Laurent Aimar committed
689 690
    *dst_area = spu_area_create(0,0, 0,0, scale_size);
    *dst_ptr  = NULL;
691

Laurent Aimar's avatar
Laurent Aimar committed
692
    /* Render text region */
Laurent Aimar's avatar
Laurent Aimar committed
693 694
    if (region->fmt.i_chroma == VLC_CODEC_TEXT) {
        SpuRenderText(spu, &restore_text, region,
695
                      chroma_list,
696
                      render_date - subpic->i_start);
Laurent Aimar's avatar
Laurent Aimar committed
697 698

        /* Check if the rendering has failed ... */
Laurent Aimar's avatar
Laurent Aimar committed
699
        if (region->fmt.i_chroma == VLC_CODEC_TEXT)
Laurent Aimar's avatar
Laurent Aimar committed
700
            goto exit;
701 702
    }

Laurent Aimar's avatar
Laurent Aimar committed
703
    /* Force palette if requested
Laurent Aimar's avatar
Laurent Aimar committed
704
     * FIXME b_force_palette and force_crop are applied to all subpictures using palette
Laurent Aimar's avatar
Laurent Aimar committed
705 706
     * instead of only the right one (being the dvd spu).
     */
Laurent Aimar's avatar
Laurent Aimar committed
707 708 709 710
    const bool using_palette = region->fmt.i_chroma == VLC_CODEC_YUVP;
    const bool force_palette = using_palette && sys->force_palette;
    const bool force_crop    = force_palette && sys->force_crop;
    bool changed_palette     = false;
711

Laurent Aimar's avatar
Laurent Aimar committed
712 713 714
    /* 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) */
Laurent Aimar's avatar
Laurent Aimar committed
715 716 717
    int y_margin = 0;
    if (!force_crop && subpic->b_subtitle)
        y_margin = spu_invscale_h(sys->margin, scale_size);
Laurent Aimar's avatar
Laurent Aimar committed
718 719 720

    /* Place the picture
     * We compute the position in the rendered size */
Laurent Aimar's avatar
Laurent Aimar committed
721 722
    SpuRegionPlace(&x_offset, &y_offset,
                   subpic, region);
Laurent Aimar's avatar
Laurent Aimar committed
723 724 725

    /* Save this position for subtitle overlap support
     * it is really important that there are given without scale_size applied */
Laurent Aimar's avatar
Laurent Aimar committed
726 727 728
    *dst_area = spu_area_create(x_offset, y_offset,
                                region->fmt.i_width, region->fmt.i_height,
                                scale_size);
Laurent Aimar's avatar
Laurent Aimar committed
729 730

    /* Handle overlapping subtitles when possible */
Laurent Aimar's avatar
Laurent Aimar committed
731 732 733
    if (subpic->b_subtitle && !subpic->b_absolute)
        SpuAreaFixOverlap(dst_area, subtitle_area, subtitle_area_count,
                          region->i_align);
734

Laurent Aimar's avatar
Laurent Aimar committed
735 736
    /* we copy the area: for the subtitle overlap support we want
     * to only save the area without margin applied */
Laurent Aimar's avatar
Laurent Aimar committed
737
    spu_area_t restrained = *dst_area;
738

Laurent Aimar's avatar
Laurent Aimar committed
739
    /* apply margin to subtitles and correct if they go over the picture edge */
Laurent Aimar's avatar
Laurent Aimar committed
740 741
    if (subpic->b_subtitle)
        restrained.y -= y_margin;
742

Laurent Aimar's avatar
Laurent Aimar committed
743 744 745
    spu_area_t display = spu_area_create(0, 0, fmt->i_width, fmt->i_height,
                                         spu_scale_unit());
    SpuAreaFitInside(&restrained, &display);
Laurent Aimar's avatar
Laurent Aimar committed
746

Laurent Aimar's avatar
Laurent Aimar committed
747
    /* Fix the position for the current scale_size */
Laurent Aimar's avatar
Laurent Aimar committed
748 749
    x_offset = spu_scale_w(restrained.x, restrained.scale);
    y_offset = spu_scale_h(restrained.y, restrained.scale);
Laurent Aimar's avatar
Laurent Aimar committed
750 751

    /* */
Laurent Aimar's avatar
Laurent Aimar committed
752 753 754
    if (force_palette) {
        video_palette_t *old_palette = region->fmt.p_palette;
        video_palette_t new_palette;
Laurent Aimar's avatar
Laurent Aimar committed
755

Laurent Aimar's avatar
Laurent Aimar committed
756
        /* We suppose DVD palette here */
Laurent Aimar's avatar
Laurent Aimar committed
757 758 759 760 761 762 763 764 765 766 767
        new_palette.i_entries = 4;
        for (int i = 0; i < 4; i++)
            for (int j = 0; j < 4; j++)
                new_palette.palette[i][j] = sys->palette[i][j];

        if (old_palette->i_entries == new_palette.i_entries) {
            for (int i = 0; i < old_palette->i_entries; i++)
                for (int j = 0; j < 4; j++)
                    changed_palette |= old_palette->palette[i][j] != new_palette.palette[i][j];
        } else {
            changed_palette = true;
Laurent Aimar's avatar
Laurent Aimar committed
768
        }
Laurent Aimar's avatar
Laurent Aimar committed
769
        *old_palette = new_palette;
Laurent Aimar's avatar
Laurent Aimar committed
770 771
    }

Laurent Aimar's avatar
Laurent Aimar committed
772
    /* */
Laurent Aimar's avatar
Laurent Aimar committed
773 774 775 776 777 778 779
    region_fmt = region->fmt;
    region_picture = region->p_picture;

    bool convert_chroma = true;
    for (int i = 0; chroma_list[i] && convert_chroma; i++) {
        if (region_fmt.i_chroma == chroma_list[i])
            convert_chroma = false;
780
    }
Laurent Aimar's avatar
Laurent Aimar committed
781

Laurent Aimar's avatar
Laurent Aimar committed
782
    /* Scale from rendered size to destination size */
Laurent Aimar's avatar
Laurent Aimar committed
783 784 785 786 787 788
    if (sys->scale && sys->scale->p_module &&
        (!using_palette || (sys->scale_yuvp && sys->scale_yuvp->p_module)) &&
        (scale_size.w != SCALE_UNIT || scale_size.h != SCALE_UNIT ||
        using_palette || convert_chroma)) {
        const unsigned dst_width  = spu_scale_w(region->fmt.i_width,  scale_size);
        const unsigned dst_height = spu_scale_h(region->fmt.i_height, scale_size);
Laurent Aimar's avatar
Laurent Aimar committed
789

Laurent Aimar's avatar
Laurent Aimar committed
790
        /* Destroy the cache if unusable */
Laurent Aimar's avatar
Laurent Aimar committed
791 792 793
        if (region->p_private) {
            subpicture_region_private_t *private = region->p_private;
            bool is_changed = false;
Laurent Aimar's avatar
Laurent Aimar committed
794

Laurent Aimar's avatar
Laurent Aimar committed
795
            /* Check resize changes */
Laurent Aimar's avatar
Laurent Aimar committed
796 797 798
            if (dst_width  != private->fmt.i_width ||
                dst_height != private->fmt.i_height)
                is_changed = true;
Laurent Aimar's avatar
Laurent Aimar committed
799

Laurent Aimar's avatar
Laurent Aimar committed
800
            /* Check forced palette changes */
Laurent Aimar's avatar
Laurent Aimar committed
801 802
            if (changed_palette)
                is_changed = true;
Laurent Aimar's avatar
Laurent Aimar committed
803

Laurent Aimar's avatar
Laurent Aimar committed
804 805
            if (convert_chroma && private->fmt.i_chroma != chroma_list[0])
                is_changed = true;
806

Laurent Aimar's avatar
Laurent Aimar committed
807 808 809
            if (is_changed) {
                subpicture_region_private_Delete(private);
                region->p_private = NULL;
Laurent Aimar's avatar
Laurent Aimar committed
810 811
            }
        }
Laurent Aimar's avatar
Laurent Aimar committed
812

Laurent Aimar's avatar
Laurent Aimar committed
813
        /* Scale if needed into cache */
Laurent Aimar's avatar
Laurent Aimar committed
814 815
        if (!region->p_private && dst_width > 0 && dst_height > 0) {
            filter_t *scale = sys->scale;
Laurent Aimar's avatar
Laurent Aimar committed
816

Laurent Aimar's avatar
Laurent Aimar committed
817 818
            picture_t *picture = region->p_picture;
            picture_Hold(picture);
Laurent Aimar's avatar
Laurent Aimar committed
819

Laurent Aimar's avatar
Laurent Aimar committed
820
            /* Convert YUVP to YUVA/RGBA first for better scaling quality */
Laurent Aimar's avatar
Laurent Aimar committed
821 822
            if (using_palette) {
                filter_t *scale_yuvp = sys->scale_yuvp;
823

Laurent Aimar's avatar