video_output.c 66 KB
Newer Older
1
/*****************************************************************************
Michel Kaempf's avatar
Michel Kaempf committed
2
 * video_output.c : video output thread
3
 *
Michel Kaempf's avatar
Michel Kaempf committed
4
5
 * This module describes the programming interface for video output threads.
 * It includes functions allowing to open a new thread, send pictures to a
6
7
 * thread, and destroy a previously oppened video output thread.
 *****************************************************************************
Thomas Guillem's avatar
Thomas Guillem committed
8
 * Copyright (C) 2000-2019 VLC authors, VideoLAN and Videolabs SAS
9
 *
Sam Hocevar's avatar
   
Sam Hocevar committed
10
 * Authors: Vincent Seguin <seguin@via.ecp.fr>
11
 *          Gildas Bazin <gbazin@videolan.org>
12
 *          Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
13
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
14
15
16
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
17
 * (at your option) any later version.
Sam Hocevar's avatar
Sam Hocevar committed
18
 *
19
20
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
21
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
23
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
24
25
26
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27
 *****************************************************************************/
Michel Kaempf's avatar
Michel Kaempf committed
28

29
/*****************************************************************************
Michel Kaempf's avatar
Michel Kaempf committed
30
 * Preamble
31
 *****************************************************************************/
32
33
34
35
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

36
#include <vlc_common.h>
37

38
#include <math.h>
39
#include <stdlib.h>                                                /* free() */
40
#include <string.h>
41
#include <assert.h>
Vincent Seguin's avatar
Vincent Seguin committed
42

zorglub's avatar
zorglub committed
43
#include <vlc_vout.h>
44

zorglub's avatar
zorglub committed
45
#include <vlc_filter.h>
46
#include <vlc_spu.h>
47
#include <vlc_vout_osd.h>
48
#include <vlc_image.h>
49
#include <vlc_plugin.h>
50
#include <vlc_codec.h>
Thomas Guillem's avatar
Thomas Guillem committed
51
#include <vlc_tracer.h>
52
#include <vlc_atomic.h>
zorglub's avatar
zorglub committed
53

54
#include <libvlc.h>
55
#include "vout_private.h"
Laurent Aimar's avatar
Laurent Aimar committed
56
#include "vout_internal.h"
57
#include "display.h"
58
#include "snapshot.h"
59
#include "video_window.h"
60
#include "../misc/variables.h"
Thomas Guillem's avatar
Thomas Guillem committed
61
#include "../clock/clock.h"
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include "statistic.h"
#include "chrono.h"
#include "control.h"

typedef struct vout_thread_sys_t
{
    struct vout_thread_t obj;

    vout_thread_private_t private;

    bool dummy;

    /* Splitter module if used */
    char            *splitter_name;

Thomas Guillem's avatar
Thomas Guillem committed
77
    const char      *str_id;
78
79
80
81
82
83
84
85
    vlc_clock_t     *clock;
    float           rate;
    vlc_tick_t      delay;

    video_format_t  original;   /* Original format ie coming from the decoder */

    /* */
    struct {
86
        vlc_rational_t dar;
87
        struct vout_crop crop;
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
    } source;

    /* Snapshot interface */
    struct vout_snapshot *snapshot;

    /* Statistics */
    vout_statistic_t statistic;

    /* Subpicture unit */
    spu_t           *spu;
    vlc_fourcc_t    spu_blend_chroma;
    vlc_blender_t   *spu_blend;

    /* Thread & synchronization */
    vout_control_t  control;
103
    atomic_bool     control_is_terminated; // shutdown the vout thread
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
    vlc_thread_t    thread;

    struct {
        vlc_tick_t  date;
        vlc_tick_t  timestamp;
        bool        is_interlaced;
        picture_t   *decoded; // decoded picture before passed through chain_static
        picture_t   *current;
    } displayed;

    struct {
        vlc_tick_t  last;
        vlc_tick_t  timestamp;
    } step;

    struct {
        bool        is_on;
        vlc_tick_t  date;
    } pause;

    /* OSD title configuration */
    struct {
        bool        show;
        int         timeout;
        int         position;
    } title;

    /* */
    bool            is_late_dropped;

    /* */
    vlc_mouse_t     mouse;

    /* Video output window */
    bool            window_enabled;
    unsigned        window_width; /* protected by display_lock */
    unsigned        window_height; /* protected by display_lock */
    vlc_mutex_t     window_lock;
    vlc_decoder_device *dec_device;

    /* Video output display */
    vout_display_cfg_t display_cfg;
    vout_display_t *display;
    vlc_mutex_t     display_lock;

    /* Video filter2 chain */
    struct {
        vlc_mutex_t     lock;
152
        bool            changed;
153
        bool            new_interlaced;
154
155
156
157
158
159
160
161
        char            *configuration;
        video_format_t    src_fmt;
        vlc_video_context *src_vctx;
        struct filter_chain_t *chain_static;
        struct filter_chain_t *chain_interactive;
    } filter;

    picture_fifo_t  *decoder_fifo;
162
163
164
165
    struct {
        vout_chrono_t static_filter;
        vout_chrono_t render;         /**< picture render time estimator */
    } chrono;
166
167
168
169
170
171
172
173

    vlc_atomic_rc_t rc;

} vout_thread_sys_t;

#define VOUT_THREAD_TO_SYS(vout) \
    container_of(vout, vout_thread_sys_t, obj.obj)

174

Laurent Aimar's avatar
Laurent Aimar committed
175
176
177
/* Maximum delay between 2 displayed pictures.
 * XXX it is needed for now but should be removed in the long term.
 */
178
#define VOUT_REDISPLAY_DELAY VLC_TICK_FROM_MS(80)
179

Laurent Aimar's avatar
Laurent Aimar committed
180
181
182
/**
 * Late pictures having a delay higher than this value are thrashed.
 */
183
#define VOUT_DISPLAY_LATE_THRESHOLD VLC_TICK_FROM_MS(20)
184
185

/* Better be in advance when awakening than late... */
186
#define VOUT_MWAIT_TOLERANCE VLC_TICK_FROM_MS(4)
187

188
/* */
Thomas Guillem's avatar
Thomas Guillem committed
189
190
191
192
193
194
static inline struct vlc_tracer *GetTracer(vout_thread_sys_t *sys)
{
    return sys->str_id == NULL ? NULL :
        vlc_object_get_tracer(VLC_OBJECT(&sys->obj));
}

195
static bool VoutCheckFormat(const video_format_t *src)
196
{
197
198
    if (src->i_width == 0  || src->i_width  > 8192 ||
        src->i_height == 0 || src->i_height > 8192)
199
        return false;
200
    if (src->i_sar_num <= 0 || src->i_sar_den <= 0)
201
202
203
        return false;
    return true;
}
204

205
206
static void VoutFixFormat(video_format_t *dst, const video_format_t *src)
{
207
208
    video_format_Copy(dst, src);
    dst->i_chroma = vlc_fourcc_GetCodec(VIDEO_ES, src->i_chroma);
209
    VoutFixFormatAR( dst );
210
    video_format_FixRgb(dst);
211
}
212

213
214
215
216
217
218
219
220
221
static bool VideoFormatIsCropArEqual(video_format_t *dst,
                                     const video_format_t *src)
{
    return dst->i_sar_num * src->i_sar_den == dst->i_sar_den * src->i_sar_num &&
           dst->i_x_offset       == src->i_x_offset &&
           dst->i_y_offset       == src->i_y_offset &&
           dst->i_visible_width  == src->i_visible_width &&
           dst->i_visible_height == src->i_visible_height;
}
222

223
static void vout_UpdateWindowSizeLocked(vout_thread_sys_t *vout)
224
{
225
    vout_thread_sys_t *sys = vout;
226

227
228
    if (unlikely(sys->original.i_chroma == 0))
        return; /* not started yet, postpone size computaton */
229

230
    vlc_mutex_assert(&sys->window_lock);
231
232
233
    vout_display_ResizeWindow(sys->display_cfg.window, &sys->original,
                              &sys->source.dar, &sys->source.crop,
                              &sys->display_cfg.display);
234
235
}

Laurent Aimar's avatar
Laurent Aimar committed
236
/* */
237
void vout_GetResetStatistic(vout_thread_t *vout, unsigned *restrict displayed,
238
                            unsigned *restrict lost, unsigned *restrict late)
239
{
240
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
241
    assert(!sys->dummy);
242
    vout_statistic_GetReset( &sys->statistic, displayed, lost, late );
243
}
244

245
246
bool vout_IsEmpty(vout_thread_t *vout)
{
247
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
248
249
    assert(!sys->dummy);
    if (!sys->decoder_fifo)
250
251
        return true;

252
    return picture_fifo_IsEmpty(sys->decoder_fifo);
253
254
}

255
void vout_DisplayTitle(vout_thread_t *vout, const char *title)
256
{
257
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
258
    assert(!sys->dummy);
259
    assert(title);
260

261
    if (!sys->title.show)
262
263
        return;

264
265
    vout_OSDText(vout, VOUT_SPU_CHANNEL_OSD, sys->title.position,
                 VLC_TICK_FROM_MS(sys->title.timeout), title);
266
}
267

268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
void vout_FilterMouse(vout_thread_t *vout, vlc_mouse_t *mouse)
{
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
    vlc_mouse_t tmp[2], *m = mouse;

    /* Pass mouse events through the filter chains. */
    vlc_mutex_lock(&sys->filter.lock);
    if (sys->filter.chain_static != NULL
     && sys->filter.chain_interactive != NULL) {
        if (!filter_chain_MouseFilter(sys->filter.chain_interactive,
                                      &tmp[0], m))
            m = &tmp[0];
        if (!filter_chain_MouseFilter(sys->filter.chain_static,
                                      &tmp[1], m))
            m = &tmp[1];
    }
    vlc_mutex_unlock(&sys->filter.lock);

    if (mouse != m)
        *mouse = *m;
}

290
291
void vout_PutSubpicture( vout_thread_t *vout, subpicture_t *subpic )
{
292
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
293
    assert(!sys->dummy);
294

295
296
297
298
    if (sys->spu != NULL)
        spu_PutSubpicture(sys->spu, subpic);
    else
        subpicture_Delete(subpic);
299
}
300

301
ssize_t vout_RegisterSubpictureChannel( vout_thread_t *vout )
302
{
303
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
304
    assert(!sys->dummy);
305
    ssize_t channel = VOUT_SPU_CHANNEL_INVALID;
306

307
308
    if (sys->spu)
        channel = spu_RegisterChannel(sys->spu);
309
310

    return channel;
311
}
312

313
ssize_t vout_RegisterSubpictureChannelInternal(vout_thread_t *vout,
314
315
                                               vlc_clock_t *clock,
                                               enum vlc_vout_order *out_order)
316
{
317
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
318
    assert(!sys->dummy);
319
    ssize_t channel = VOUT_SPU_CHANNEL_INVALID;
320

321
322
    if (sys->spu)
        channel = spu_RegisterChannelInternal(sys->spu, clock, out_order);
323
324

    return channel;
325
326
}

327
void vout_UnregisterSubpictureChannel( vout_thread_t *vout, size_t channel )
Thomas Guillem's avatar
Thomas Guillem committed
328
{
329
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
330
331
332
    assert(!sys->dummy);
    assert(sys->spu);
    spu_UnregisterChannel(sys->spu, channel);
Thomas Guillem's avatar
Thomas Guillem committed
333
334
}

335
void vout_FlushSubpictureChannel( vout_thread_t *vout, size_t channel )
336
{
337
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
338
    assert(!sys->dummy);
339
340
    if (sys->spu)
        spu_ClearChannel(sys->spu, channel);
341
}
342

343
344
345
void vout_SetSpuHighlight( vout_thread_t *vout,
                        const vlc_spu_highlight_t *spu_hl )
{
346
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
347
348
349
    assert(!sys->dummy);
    if (sys->spu)
        spu_SetHighlight(sys->spu, spu_hl);
350
}
351

352
/**
353
 * Allocates a video output picture buffer.
354
 *
355
356
 * Either vout_PutPicture() or picture_Release() must be used to return the
 * buffer to the video output free buffer pool.
357
 *
358
 * You may use picture_Hold() (paired with picture_Release()) to keep a
359
360
361
362
 * read-only reference.
 */
picture_t *vout_GetPicture(vout_thread_t *vout)
{
363
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
364
    assert(!sys->dummy);
365
    picture_t *picture = picture_pool_Wait(sys->private.display_pool);
366
    if (likely(picture != NULL)) {
367
        picture_Reset(picture);
368
        video_format_CopyCropAr(&picture->format, &sys->original);
369
370
371
372
373
374
375
376
377
378
379
380
381
382
    }
    return picture;
}

/**
 * It gives to the vout a picture to be displayed.
 *
 * The given picture MUST comes from vout_GetPicture.
 *
 * Becareful, after vout_PutPicture is called, picture_t::p_next cannot be
 * read/used.
 */
void vout_PutPicture(vout_thread_t *vout, picture_t *picture)
{
383
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
384
    assert(!sys->dummy);
385
    assert( !picture_HasChainedPics( picture ) );
386
387
    picture_fifo_Push(sys->decoder_fifo, picture);
    vout_control_Wake(&sys->control);
388
389
}

390
391
392
393
/* */
int vout_GetSnapshot(vout_thread_t *vout,
                     block_t **image_dst, picture_t **picture_dst,
                     video_format_t *fmt,
Steve Lhomme's avatar
Steve Lhomme committed
394
                     const char *type, vlc_tick_t timeout)
395
{
396
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
397
398
    assert(!sys->dummy);
    picture_t *picture = vout_snapshot_Get(sys->snapshot, timeout);
399
400
401
402
403
404
405
406
407
408
    if (!picture) {
        msg_Err(vout, "Failed to grab a snapshot");
        return VLC_EGENERIC;
    }

    if (image_dst) {
        vlc_fourcc_t codec = VLC_CODEC_PNG;
        if (type && image_Type2Fourcc(type))
            codec = image_Type2Fourcc(type);

409
410
        const int override_width  = var_InheritInteger(vout, "snapshot-width");
        const int override_height = var_InheritInteger(vout, "snapshot-height");
411
412

        if (picture_Export(VLC_OBJECT(vout), image_dst, fmt,
413
                           picture, codec, override_width, override_height, false)) {
414
415
416
417
418
419
420
421
422
423
424
425
            msg_Err(vout, "Failed to convert image for snapshot");
            picture_Release(picture);
            return VLC_EGENERIC;
        }
    }
    if (picture_dst)
        *picture_dst = picture;
    else
        picture_Release(picture);
    return VLC_SUCCESS;
}

426
/* vout_Control* are usable by anyone at anytime */
427
void vout_ChangeFullscreen(vout_thread_t *vout, const char *id)
428
{
429
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
430
431
    assert(!sys->dummy);
    vlc_mutex_lock(&sys->window_lock);
432
    vlc_window_SetFullScreen(sys->display_cfg.window, id);
433
    vlc_mutex_unlock(&sys->window_lock);
434
}
435

436
void vout_ChangeWindowed(vout_thread_t *vout)
437
{
438
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
439
440
    assert(!sys->dummy);
    vlc_mutex_lock(&sys->window_lock);
441
    vlc_window_UnsetFullScreen(sys->display_cfg.window);
442
    /* Attempt to reset the intended window size */
443
    vout_UpdateWindowSizeLocked(sys);
444
    vlc_mutex_unlock(&sys->window_lock);
445
446
}

447
void vout_ChangeWindowState(vout_thread_t *vout, unsigned st)
448
{
449
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
450
451
    assert(!sys->dummy);
    vlc_mutex_lock(&sys->window_lock);
452
    vlc_window_SetState(sys->display_cfg.window, st);
453
    vlc_mutex_unlock(&sys->window_lock);
454
}
455

456
void vout_ChangeDisplaySize(vout_thread_t *vout,
457
458
                            unsigned width, unsigned height,
                            void (*cb)(void *), void *opaque)
459
{
460
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
461

462
    assert(!sys->dummy);
463

464
465
    /* DO NOT call this outside the vout window callbacks */
    vlc_mutex_lock(&sys->display_lock);
466
467
468
469

    sys->window_width = width;
    sys->window_height = height;

470
471
    if (sys->display != NULL)
        vout_display_SetSize(sys->display, width, height);
472
473
474

    if (cb != NULL)
        cb(opaque);
475
    vlc_mutex_unlock(&sys->display_lock);
476
}
477

478
void vout_ChangeDisplayFilled(vout_thread_t *vout, bool is_filled)
479
{
480
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
481
    assert(!sys->dummy);
482
483

    vlc_mutex_lock(&sys->window_lock);
484
    sys->display_cfg.display.autoscale = is_filled;
485
    /* no window size update here */
486
487

    vlc_mutex_lock(&sys->display_lock);
488
489
    vlc_mutex_unlock(&sys->window_lock);

490
491
492
    if (sys->display != NULL)
        vout_SetDisplayFilled(sys->display, is_filled);
    vlc_mutex_unlock(&sys->display_lock);
493
}
494

495
void vout_ChangeZoom(vout_thread_t *vout, unsigned num, unsigned den)
496
{
497
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
498
    assert(!sys->dummy);
499

500
501
502
503
504
505
506
    if (num != 0 && den != 0) {
        vlc_ureduce(&num, &den, num, den, 0);
    } else {
        num = 1;
        den = 1;
    }

507
    if (num * 10 < den) {
508
509
        num = 1;
        den = 10;
510
    } else if (num > den * 10) {
511
512
        num = 10;
        den = 1;
513
514
    }

515
    vlc_mutex_lock(&sys->window_lock);
516
517
    sys->display_cfg.display.zoom.num = num;
    sys->display_cfg.display.zoom.den = den;
518

519
    vout_UpdateWindowSizeLocked(sys);
520
521

    vlc_mutex_lock(&sys->display_lock);
522
523
    vlc_mutex_unlock(&sys->window_lock);

524
525
526
    if (sys->display != NULL)
        vout_SetDisplayZoom(sys->display, num, den);
    vlc_mutex_unlock(&sys->display_lock);
527
}
528

529
530
531
532
533
534
535
static void vout_SetAspectRatio(vout_thread_sys_t *sys,
                                     unsigned dar_num, unsigned dar_den)
{
    sys->source.dar.num = dar_num;
    sys->source.dar.den = dar_den;
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
536
537
void vout_ChangeDisplayAspectRatio(vout_thread_t *vout,
                                   unsigned dar_num, unsigned dar_den)
538
{
539
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
540
    assert(!sys->dummy);
541
542

    vlc_mutex_lock(&sys->window_lock);
543
    vout_SetAspectRatio(sys, dar_num, dar_den);
544

545
    vout_UpdateWindowSizeLocked(sys);
546
547

    vlc_mutex_lock(&sys->display_lock);
548
549
    vlc_mutex_unlock(&sys->window_lock);

550
551
552
    if (sys->display != NULL)
        vout_SetDisplayAspect(sys->display, dar_num, dar_den);
    vlc_mutex_unlock(&sys->display_lock);
553
}
554

555
556
void vout_ChangeCrop(vout_thread_t *vout,
                     const struct vout_crop *restrict crop)
557
558
559
560
561
{
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
    assert(!sys->dummy);

    vlc_mutex_lock(&sys->window_lock);
562
    sys->source.crop = *crop;
563
    vout_UpdateWindowSizeLocked(sys);
564
565

    vlc_mutex_lock(&sys->display_lock);
566
    vlc_mutex_unlock(&sys->window_lock);
567

568
    if (sys->display != NULL)
569
        vout_SetDisplayCrop(sys->display, crop);
570
    vlc_mutex_unlock(&sys->display_lock);
571
}
572

573
574
void vout_ControlChangeFilters(vout_thread_t *vout, const char *filters)
{
575
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
576
    assert(!sys->dummy);
577
    vlc_mutex_lock(&sys->filter.lock);
578
    if (sys->filter.configuration)
579
    {
580
        if (filters == NULL || strcmp(sys->filter.configuration, filters))
581
        {
582
583
            free(sys->filter.configuration);
            sys->filter.configuration = filters ? strdup(filters) : NULL;
584
585
586
587
588
            sys->filter.changed = true;
        }
    }
    else if (filters != NULL)
    {
589
        sys->filter.configuration = strdup(filters);
590
591
592
        sys->filter.changed = true;
    }
    vlc_mutex_unlock(&sys->filter.lock);
593
}
594

595
596
void vout_ControlChangeInterlacing(vout_thread_t *vout, bool set)
{
597
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
598
    assert(!sys->dummy);
599
600
601
    vlc_mutex_lock(&sys->filter.lock);
    sys->filter.new_interlaced = set;
    vlc_mutex_unlock(&sys->filter.lock);
602
603
}

604
void vout_ControlChangeSubSources(vout_thread_t *vout, const char *filters)
605
{
606
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
607
608
609
    assert(!sys->dummy);
    if (likely(sys->spu != NULL))
        spu_ChangeSources(sys->spu, filters);
610
}
611

612
613
void vout_ControlChangeSubFilters(vout_thread_t *vout, const char *filters)
{
614
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
615
616
617
    assert(!sys->dummy);
    if (likely(sys->spu != NULL))
        spu_ChangeFilters(sys->spu, filters);
618
}
619

620
621
void vout_ChangeSpuChannelMargin(vout_thread_t *vout,
                                 enum vlc_vout_order order, int margin)
Laurent Aimar's avatar
Laurent Aimar committed
622
{
623
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
624
625
626
    assert(!sys->dummy);
    if (likely(sys->spu != NULL))
        spu_ChangeChannelOrderMargin(sys->spu, order, margin);
Laurent Aimar's avatar
Laurent Aimar committed
627
}
628

629
630
void vout_ChangeViewpoint(vout_thread_t *vout,
                          const vlc_viewpoint_t *p_viewpoint)
631
{
632
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
633
    assert(!sys->dummy);
634
635
636
637
638
639

    vlc_mutex_lock(&sys->window_lock);
    sys->display_cfg.viewpoint = *p_viewpoint;
    /* no window size update here */
    vlc_mutex_unlock(&sys->window_lock);

640
641
642
643
    vlc_mutex_lock(&sys->display_lock);
    if (sys->display != NULL)
        vout_SetDisplayViewpoint(sys->display, p_viewpoint);
    vlc_mutex_unlock(&sys->display_lock);
644
645
}

646
/* */
647
static void VoutGetDisplayCfg(vout_thread_sys_t *p_vout, const video_format_t *fmt, vout_display_cfg_t *cfg)
648
{
649
    vout_thread_t *vout = &p_vout->obj;
650
    /* Load configuration */
651
    cfg->viewpoint = fmt->pose;
Thomas Guillem's avatar
Thomas Guillem committed
652

653
654
    const int display_width = var_GetInteger(vout, "width");
    const int display_height = var_GetInteger(vout, "height");
655
656
    cfg->display.width   = display_width > 0  ? display_width  : 0;
    cfg->display.height  = display_height > 0 ? display_height : 0;
657
    cfg->display.autoscale = var_GetBool(vout, "autoscale");
658
659
660
661
662
663
664
665
    unsigned msar_num, msar_den;
    if (var_InheritURational(vout, &msar_num, &msar_den, "monitor-par") ||
        msar_num <= 0 || msar_den <= 0) {
        msar_num = 1;
        msar_den = 1;
    }
    cfg->display.sar.num = msar_num;
    cfg->display.sar.den = msar_den;
666
    unsigned zoom_den = 1000;
667
    unsigned zoom_num = zoom_den * var_GetFloat(vout, "zoom");
668
    vlc_ureduce(&zoom_num, &zoom_den, zoom_num, zoom_den, 0);
669
670
671
672
    cfg->display.zoom.num = zoom_num;
    cfg->display.zoom.den = zoom_den;
    cfg->display.align.vertical = VLC_VIDEO_ALIGN_CENTER;
    cfg->display.align.horizontal = VLC_VIDEO_ALIGN_CENTER;
673
    const int align_mask = var_GetInteger(vout, "align");
674
    if (align_mask & VOUT_ALIGN_LEFT)
675
        cfg->display.align.horizontal = VLC_VIDEO_ALIGN_LEFT;
676
    else if (align_mask & VOUT_ALIGN_RIGHT)
677
        cfg->display.align.horizontal = VLC_VIDEO_ALIGN_RIGHT;
678
    if (align_mask & VOUT_ALIGN_TOP)
679
        cfg->display.align.vertical = VLC_VIDEO_ALIGN_TOP;
680
    else if (align_mask & VOUT_ALIGN_BOTTOM)
681
        cfg->display.align.vertical = VLC_VIDEO_ALIGN_BOTTOM;
682
683
}

684
/* */
685
686
687
688
689
690
691
692
693
static int FilterRestartCallback(vlc_object_t *p_this, char const *psz_var,
                                 vlc_value_t oldval, vlc_value_t newval,
                                 void *p_data)
{
    (void) p_this; (void) psz_var; (void) oldval; (void) newval;
    vout_ControlChangeFilters((vout_thread_t *)p_data, NULL);
    return 0;
}

694
static int DelFilterCallbacks(filter_t *filter, void *opaque)
695
{
696
    filter_DelProxyCallbacks((vlc_object_t*)opaque, filter,
697
                             FilterRestartCallback);
698
699
700
    return VLC_SUCCESS;
}

701
static void DelAllFilterCallbacks(vout_thread_sys_t *vout)
702
{
703
    vout_thread_sys_t *sys = vout;
704
705
    assert(sys->filter.chain_interactive != NULL);
    filter_chain_ForEach(sys->filter.chain_interactive,
706
                         DelFilterCallbacks, vout);
707
708
}

709
static picture_t *VoutVideoFilterInteractiveNewPicture(filter_t *filter)
710
{
711
    vout_thread_sys_t *sys = filter->owner.sys;
712

713
    picture_t *picture = picture_pool_Get(sys->private.private_pool);
714
715
    if (picture) {
        picture_Reset(picture);
716
        video_format_CopyCropAr(&picture->format, &filter->fmt_out.video);
717
    }
718
    return picture;
719
}
720

721
722
static picture_t *VoutVideoFilterStaticNewPicture(filter_t *filter)
{
723
    vout_thread_sys_t *sys = filter->owner.sys;
724

725
726
    vlc_mutex_assert(&sys->filter.lock);
    if (filter_chain_IsEmpty(sys->filter.chain_interactive))
727
728
        // we may be using the last filter of both chains, so we get the picture
        // from the display module pool, just like for the last interactive filter.
729
        return VoutVideoFilterInteractiveNewPicture(filter);
730

731
732
    return picture_NewFromFormat(&filter->fmt_out.video);
}
733

734
static void FilterFlush(vout_thread_sys_t *sys, bool is_locked)
Laurent Aimar's avatar
Laurent Aimar committed
735
{
736
    if (sys->displayed.current)
Steve Lhomme's avatar
Steve Lhomme committed
737
    {
738
739
        picture_Release( sys->displayed.current );
        sys->displayed.current = NULL;
Steve Lhomme's avatar
Steve Lhomme committed
740
    }
Laurent Aimar's avatar
Laurent Aimar committed
741

742
    if (!is_locked)
743
744
745
        vlc_mutex_lock(&sys->filter.lock);
    filter_chain_VideoFlush(sys->filter.chain_static);
    filter_chain_VideoFlush(sys->filter.chain_interactive);
746
    if (!is_locked)
747
        vlc_mutex_unlock(&sys->filter.lock);
Laurent Aimar's avatar
Laurent Aimar committed
748
749
750
751
752
753
754
}

typedef struct {
    char           *name;
    config_chain_t *cfg;
} vout_filter_t;

755
static void ChangeFilters(vout_thread_sys_t *vout)
Laurent Aimar's avatar
Laurent Aimar committed
756
{
757
    vout_thread_sys_t *sys = vout;
758
759
    FilterFlush(vout, true);
    DelAllFilterCallbacks(vout);
Laurent Aimar's avatar
Laurent Aimar committed
760
761
762
763
764
765

    vlc_array_t array_static;
    vlc_array_t array_interactive;

    vlc_array_init(&array_static);
    vlc_array_init(&array_interactive);
766

767
    if (sys->private.interlacing.has_deint)
768
769
770
    {
        vout_filter_t *e = malloc(sizeof(*e));

771
        if (likely(e))
772
        {
773
774
775
            char *filter = var_InheritString(&vout->obj, "deinterlace-filter");
            free(config_ChainCreate(&e->name, &e->cfg, filter));
            free(filter);
776
            vlc_array_append_or_abort(&array_static, e);
777
778
779
        }
    }

780
    char *current = sys->filter.configuration ? strdup(sys->filter.configuration) : NULL;
Laurent Aimar's avatar
Laurent Aimar committed
781
782
783
784
785
786
    while (current) {
        config_chain_t *cfg;
        char *name;
        char *next = config_ChainCreate(&name, &cfg, current);

        if (name && *name) {
787
788
            vout_filter_t *e = malloc(sizeof(*e));

789
            if (likely(e)) {
790
791
                e->name = name;
                e->cfg  = cfg;
792
                if (!strcmp(e->name, "postproc"))
793
                    vlc_array_append_or_abort(&array_static, e);
794
                else
795
                    vlc_array_append_or_abort(&array_interactive, e);
796
797
798
799
800
            }
            else {
                if (cfg)
                    config_ChainDestroy(cfg);
                free(name);
Laurent Aimar's avatar
Laurent Aimar committed
801
802
803
804
805
806
807
808
809
810
811
            }
        } else {
            if (cfg)
                config_ChainDestroy(cfg);
            free(name);
        }
        free(current);
        current = next;
    }

    es_format_t fmt_target;
812
813
    es_format_InitFromVideo(&fmt_target, &sys->filter.src_fmt);
    vlc_video_context *vctx_target  = sys->filter.src_vctx;
Laurent Aimar's avatar
Laurent Aimar committed
814

815
    const es_format_t *p_fmt_current = &fmt_target;
816
    vlc_video_context *vctx_current = vctx_target;
Laurent Aimar's avatar
Laurent Aimar committed
817
818
819
820

    for (int a = 0; a < 2; a++) {
        vlc_array_t    *array = a == 0 ? &array_static :
                                         &array_interactive;
821
822
        filter_chain_t *chain = a == 0 ? sys->filter.chain_static :
                                         sys->filter.chain_interactive;
Laurent Aimar's avatar
Laurent Aimar committed
823

824
        filter_chain_Reset(chain, p_fmt_current, vctx_current, p_fmt_current);
Thomas Guillem's avatar
Thomas Guillem committed
825
        for (size_t i = 0; i < vlc_array_count(array); i++) {
Laurent Aimar's avatar
Laurent Aimar committed
826
            vout_filter_t *e = vlc_array_item_at_index(array, i);
827
            msg_Dbg(&vout->obj, "Adding '%s' as %s", e->name, a == 0 ? "static" : "interactive");
828
            filter_t *filter = filter_chain_AppendFilter(chain, e->name, e->cfg,
829
                               NULL);
830
            if (!filter)
831
                msg_Err(&vout->obj, "Failed to add filter '%s'", e->name);
832
            else if (a == 1) /* Add callbacks for interactive filters */
833
                filter_AddProxyCallbacks(&vout->obj, filter, FilterRestartCallback);
834

835
            config_ChainDestroy(e->cfg);
Laurent Aimar's avatar
Laurent Aimar committed
836
837
838
            free(e->name);
            free(e);
        }
839
840
841
        if (!filter_chain_IsEmpty(chain))
        {
            p_fmt_current = filter_chain_GetFmtOut(chain);
842
            vctx_current = filter_chain_GetVideoCtxOut(chain);
843
        }
Laurent Aimar's avatar
Laurent Aimar committed
844
845
        vlc_array_clear(array);
    }
846

847
    if (!es_format_IsSimilar(p_fmt_current, &fmt_target)) {
848
        msg_Dbg(&vout->obj, "Adding a filter to compensate for format changes");
849
        if (filter_chain_AppendConverter(sys->filter.chain_interactive,
850
                                         &fmt_target) !=