video_output.c 66.6 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
196
197
198
199
200
201
202
203
static inline void VoutResetChronoLocked(vout_thread_sys_t *sys)
{
    vlc_mutex_assert(&sys->display_lock);

    /* Arbitrary initial time */
    vout_chrono_Init(&sys->chrono.render, 5, VLC_TICK_FROM_MS(10));
    vout_chrono_Init(&sys->chrono.static_filter, 4, VLC_TICK_FROM_MS(0));
}

204
static bool VoutCheckFormat(const video_format_t *src)
205
{
206
207
    if (src->i_width == 0  || src->i_width  > 8192 ||
        src->i_height == 0 || src->i_height > 8192)
208
        return false;
209
    if (src->i_sar_num <= 0 || src->i_sar_den <= 0)
210
211
212
        return false;
    return true;
}
213

214
215
static void VoutFixFormat(video_format_t *dst, const video_format_t *src)
{
216
217
    video_format_Copy(dst, src);
    dst->i_chroma = vlc_fourcc_GetCodec(VIDEO_ES, src->i_chroma);
218
    VoutFixFormatAR( dst );
219
    video_format_FixRgb(dst);
220
}
221

222
223
224
225
226
227
228
229
230
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;
}
231

232
static void vout_UpdateWindowSizeLocked(vout_thread_sys_t *vout)
233
{
234
    vout_thread_sys_t *sys = vout;
235

236
237
    if (unlikely(sys->original.i_chroma == 0))
        return; /* not started yet, postpone size computaton */
238

239
    vlc_mutex_assert(&sys->window_lock);
240
241
242
    vout_display_ResizeWindow(sys->display_cfg.window, &sys->original,
                              &sys->source.dar, &sys->source.crop,
                              &sys->display_cfg.display);
243
244
}

Laurent Aimar's avatar
Laurent Aimar committed
245
/* */
246
void vout_GetResetStatistic(vout_thread_t *vout, unsigned *restrict displayed,
247
                            unsigned *restrict lost, unsigned *restrict late)
248
{
249
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
250
    assert(!sys->dummy);
251
    vout_statistic_GetReset( &sys->statistic, displayed, lost, late );
252
}
253

254
255
bool vout_IsEmpty(vout_thread_t *vout)
{
256
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
257
258
    assert(!sys->dummy);
    if (!sys->decoder_fifo)
259
260
        return true;

261
    return picture_fifo_IsEmpty(sys->decoder_fifo);
262
263
}

264
void vout_DisplayTitle(vout_thread_t *vout, const char *title)
265
{
266
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
267
    assert(!sys->dummy);
268
    assert(title);
269

270
    if (!sys->title.show)
271
272
        return;

273
274
    vout_OSDText(vout, VOUT_SPU_CHANNEL_OSD, sys->title.position,
                 VLC_TICK_FROM_MS(sys->title.timeout), title);
275
}
276

277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
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;
}

299
300
void vout_PutSubpicture( vout_thread_t *vout, subpicture_t *subpic )
{
301
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
302
    assert(!sys->dummy);
303

304
305
306
307
    if (sys->spu != NULL)
        spu_PutSubpicture(sys->spu, subpic);
    else
        subpicture_Delete(subpic);
308
}
309

310
ssize_t vout_RegisterSubpictureChannel( vout_thread_t *vout )
311
{
312
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
313
    assert(!sys->dummy);
314
    ssize_t channel = VOUT_SPU_CHANNEL_INVALID;
315

316
317
    if (sys->spu)
        channel = spu_RegisterChannel(sys->spu);
318
319

    return channel;
320
}
321

322
ssize_t vout_RegisterSubpictureChannelInternal(vout_thread_t *vout,
323
324
                                               vlc_clock_t *clock,
                                               enum vlc_vout_order *out_order)
325
{
326
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
327
    assert(!sys->dummy);
328
    ssize_t channel = VOUT_SPU_CHANNEL_INVALID;
329

330
331
    if (sys->spu)
        channel = spu_RegisterChannelInternal(sys->spu, clock, out_order);
332
333

    return channel;
334
335
}

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

344
void vout_FlushSubpictureChannel( vout_thread_t *vout, size_t channel )
345
{
346
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
347
    assert(!sys->dummy);
348
349
    if (sys->spu)
        spu_ClearChannel(sys->spu, channel);
350
}
351

352
353
354
void vout_SetSpuHighlight( vout_thread_t *vout,
                        const vlc_spu_highlight_t *spu_hl )
{
355
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
356
357
358
    assert(!sys->dummy);
    if (sys->spu)
        spu_SetHighlight(sys->spu, spu_hl);
359
}
360

361
/**
362
 * Allocates a video output picture buffer.
363
 *
364
365
 * Either vout_PutPicture() or picture_Release() must be used to return the
 * buffer to the video output free buffer pool.
366
 *
367
 * You may use picture_Hold() (paired with picture_Release()) to keep a
368
369
370
371
 * read-only reference.
 */
picture_t *vout_GetPicture(vout_thread_t *vout)
{
372
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
373
    assert(!sys->dummy);
374
    picture_t *picture = picture_pool_Wait(sys->private.display_pool);
375
    if (likely(picture != NULL)) {
376
        picture_Reset(picture);
377
        video_format_CopyCropAr(&picture->format, &sys->original);
378
379
380
381
382
383
384
385
386
387
388
389
390
391
    }
    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)
{
392
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
393
    assert(!sys->dummy);
394
    assert( !picture_HasChainedPics( picture ) );
395
396
    picture_fifo_Push(sys->decoder_fifo, picture);
    vout_control_Wake(&sys->control);
397
398
}

399
400
401
402
/* */
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
403
                     const char *type, vlc_tick_t timeout)
404
{
405
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
406
407
    assert(!sys->dummy);
    picture_t *picture = vout_snapshot_Get(sys->snapshot, timeout);
408
409
410
411
412
413
414
415
416
417
    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);

418
419
        const int override_width  = var_InheritInteger(vout, "snapshot-width");
        const int override_height = var_InheritInteger(vout, "snapshot-height");
420
421

        if (picture_Export(VLC_OBJECT(vout), image_dst, fmt,
422
                           picture, codec, override_width, override_height, false)) {
423
424
425
426
427
428
429
430
431
432
433
434
            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;
}

435
/* vout_Control* are usable by anyone at anytime */
436
void vout_ChangeFullscreen(vout_thread_t *vout, const char *id)
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_SetFullScreen(sys->display_cfg.window, id);
442
    vlc_mutex_unlock(&sys->window_lock);
443
}
444

445
void vout_ChangeWindowed(vout_thread_t *vout)
446
{
447
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
448
449
    assert(!sys->dummy);
    vlc_mutex_lock(&sys->window_lock);
450
    vlc_window_UnsetFullScreen(sys->display_cfg.window);
451
    /* Attempt to reset the intended window size */
452
    vout_UpdateWindowSizeLocked(sys);
453
    vlc_mutex_unlock(&sys->window_lock);
454
455
}

456
void vout_ChangeWindowState(vout_thread_t *vout, unsigned st)
457
{
458
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
459
460
    assert(!sys->dummy);
    vlc_mutex_lock(&sys->window_lock);
461
    vlc_window_SetState(sys->display_cfg.window, st);
462
    vlc_mutex_unlock(&sys->window_lock);
463
}
464

465
void vout_ChangeDisplaySize(vout_thread_t *vout,
466
467
                            unsigned width, unsigned height,
                            void (*cb)(void *), void *opaque)
468
{
469
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
470

471
    assert(!sys->dummy);
472

473
474
    /* DO NOT call this outside the vout window callbacks */
    vlc_mutex_lock(&sys->display_lock);
475
476
477
478

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

479
480
    if (sys->display != NULL)
        vout_display_SetSize(sys->display, width, height);
481
482
483

    if (cb != NULL)
        cb(opaque);
484
    vlc_mutex_unlock(&sys->display_lock);
485
}
486

487
void vout_ChangeDisplayFitting(vout_thread_t *vout, enum vlc_video_fitting fit)
488
{
489
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
490
    assert(!sys->dummy);
491
492

    vlc_mutex_lock(&sys->window_lock);
493
    sys->display_cfg.display.fitting = fit;
494
    /* no window size update here */
495
496

    vlc_mutex_lock(&sys->display_lock);
497
498
    vlc_mutex_unlock(&sys->window_lock);

499
    if (sys->display != NULL)
500
        vout_SetDisplayFitting(sys->display, fit);
501
    vlc_mutex_unlock(&sys->display_lock);
502
}
503

504
void vout_ChangeZoom(vout_thread_t *vout, unsigned num, unsigned den)
505
{
506
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
507
    assert(!sys->dummy);
508

509
510
511
512
513
514
515
    if (num != 0 && den != 0) {
        vlc_ureduce(&num, &den, num, den, 0);
    } else {
        num = 1;
        den = 1;
    }

516
    if (num * 10 < den) {
517
518
        num = 1;
        den = 10;
519
    } else if (num > den * 10) {
520
521
        num = 10;
        den = 1;
522
523
    }

524
    vlc_mutex_lock(&sys->window_lock);
525
526
    sys->display_cfg.display.zoom.num = num;
    sys->display_cfg.display.zoom.den = den;
527

528
    vout_UpdateWindowSizeLocked(sys);
529
530

    vlc_mutex_lock(&sys->display_lock);
531
532
    vlc_mutex_unlock(&sys->window_lock);

533
534
535
    if (sys->display != NULL)
        vout_SetDisplayZoom(sys->display, num, den);
    vlc_mutex_unlock(&sys->display_lock);
536
}
537

538
539
540
541
542
543
544
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
545
546
void vout_ChangeDisplayAspectRatio(vout_thread_t *vout,
                                   unsigned dar_num, unsigned dar_den)
547
{
548
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
549
    assert(!sys->dummy);
550
551

    vlc_mutex_lock(&sys->window_lock);
552
    vout_SetAspectRatio(sys, dar_num, dar_den);
553

554
    vout_UpdateWindowSizeLocked(sys);
555
556

    vlc_mutex_lock(&sys->display_lock);
557
558
    vlc_mutex_unlock(&sys->window_lock);

559
560
561
    if (sys->display != NULL)
        vout_SetDisplayAspect(sys->display, dar_num, dar_den);
    vlc_mutex_unlock(&sys->display_lock);
562
}
563

564
565
void vout_ChangeCrop(vout_thread_t *vout,
                     const struct vout_crop *restrict crop)
566
567
568
569
570
{
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
    assert(!sys->dummy);

    vlc_mutex_lock(&sys->window_lock);
571
    sys->source.crop = *crop;
572
    vout_UpdateWindowSizeLocked(sys);
573
574

    vlc_mutex_lock(&sys->display_lock);
575
    vlc_mutex_unlock(&sys->window_lock);
576

577
    if (sys->display != NULL)
578
        vout_SetDisplayCrop(sys->display, crop);
579
    vlc_mutex_unlock(&sys->display_lock);
580
}
581

582
583
void vout_ControlChangeFilters(vout_thread_t *vout, const char *filters)
{
584
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
585
    assert(!sys->dummy);
586
    vlc_mutex_lock(&sys->filter.lock);
587
    if (sys->filter.configuration)
588
    {
589
        if (filters == NULL || strcmp(sys->filter.configuration, filters))
590
        {
591
592
            free(sys->filter.configuration);
            sys->filter.configuration = filters ? strdup(filters) : NULL;
593
594
595
596
597
            sys->filter.changed = true;
        }
    }
    else if (filters != NULL)
    {
598
        sys->filter.configuration = strdup(filters);
599
600
601
        sys->filter.changed = true;
    }
    vlc_mutex_unlock(&sys->filter.lock);
602
}
603

604
605
void vout_ControlChangeInterlacing(vout_thread_t *vout, bool set)
{
606
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
607
    assert(!sys->dummy);
608
609
610
    vlc_mutex_lock(&sys->filter.lock);
    sys->filter.new_interlaced = set;
    vlc_mutex_unlock(&sys->filter.lock);
611
612
}

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

621
622
void vout_ControlChangeSubFilters(vout_thread_t *vout, const char *filters)
{
623
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
624
625
626
    assert(!sys->dummy);
    if (likely(sys->spu != NULL))
        spu_ChangeFilters(sys->spu, filters);
627
}
628

629
630
void vout_ChangeSpuChannelMargin(vout_thread_t *vout,
                                 enum vlc_vout_order order, int margin)
Laurent Aimar's avatar
Laurent Aimar committed
631
{
632
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
633
634
635
    assert(!sys->dummy);
    if (likely(sys->spu != NULL))
        spu_ChangeChannelOrderMargin(sys->spu, order, margin);
Laurent Aimar's avatar
Laurent Aimar committed
636
}
637

638
639
void vout_ChangeViewpoint(vout_thread_t *vout,
                          const vlc_viewpoint_t *p_viewpoint)
640
{
641
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
Thomas Guillem's avatar
Thomas Guillem committed
642
    assert(!sys->dummy);
643
644
645
646
647
648

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

649
650
651
652
    vlc_mutex_lock(&sys->display_lock);
    if (sys->display != NULL)
        vout_SetDisplayViewpoint(sys->display, p_viewpoint);
    vlc_mutex_unlock(&sys->display_lock);
653
654
}

655
/* */
656
static void VoutGetDisplayCfg(vout_thread_sys_t *p_vout, const video_format_t *fmt, vout_display_cfg_t *cfg)
657
{
658
    vout_thread_t *vout = &p_vout->obj;
659
    /* Load configuration */
660
    cfg->viewpoint = fmt->pose;
Thomas Guillem's avatar
Thomas Guillem committed
661

662
663
    const int display_width = var_GetInteger(vout, "width");
    const int display_height = var_GetInteger(vout, "height");
664
665
    cfg->display.width   = display_width > 0  ? display_width  : 0;
    cfg->display.height  = display_height > 0 ? display_height : 0;
666
    cfg->display.fitting = var_GetBool(vout, "autoscale")
667
        ? var_InheritFit(VLC_OBJECT(vout)) : VLC_VIDEO_FIT_NONE;
668
669
670
671
672
673
674
675
    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;
676
    unsigned zoom_den = 1000;
677
    unsigned zoom_num = zoom_den * var_GetFloat(vout, "zoom");
678
    vlc_ureduce(&zoom_num, &zoom_den, zoom_num, zoom_den, 0);
679
680
681
682
    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;
683
    const int align_mask = var_GetInteger(vout, "align");
684
    if (align_mask & VOUT_ALIGN_LEFT)
685
        cfg->display.align.horizontal = VLC_VIDEO_ALIGN_LEFT;
686
    else if (align_mask & VOUT_ALIGN_RIGHT)
687
        cfg->display.align.horizontal = VLC_VIDEO_ALIGN_RIGHT;
688
    if (align_mask & VOUT_ALIGN_TOP)
689
        cfg->display.align.vertical = VLC_VIDEO_ALIGN_TOP;
690
    else if (align_mask & VOUT_ALIGN_BOTTOM)
691
        cfg->display.align.vertical = VLC_VIDEO_ALIGN_BOTTOM;
692
693
}

694
/* */
695
696
697
698
699
700
701
702
703
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;
}

704
static int DelFilterCallbacks(filter_t *filter, void *opaque)
705
{
706
    filter_DelProxyCallbacks((vlc_object_t*)opaque, filter,
707
                             FilterRestartCallback);
708
709
710
    return VLC_SUCCESS;
}

711
static void DelAllFilterCallbacks(vout_thread_sys_t *vout)
712
{
713
    vout_thread_sys_t *sys = vout;
714
715
    assert(sys->filter.chain_interactive != NULL);
    filter_chain_ForEach(sys->filter.chain_interactive,
716
                         DelFilterCallbacks, vout);
717
718
}

719
static picture_t *VoutVideoFilterInteractiveNewPicture(filter_t *filter)
720
{
721
    vout_thread_sys_t *sys = filter->owner.sys;
722

723
    picture_t *picture = picture_pool_Get(sys->private.private_pool);
724
725
    if (picture) {
        picture_Reset(picture);
726
        video_format_CopyCropAr(&picture->format, &filter->fmt_out.video);
727
    }
728
    return picture;
729
}
730

731
732
static picture_t *VoutVideoFilterStaticNewPicture(filter_t *filter)
{
733
    vout_thread_sys_t *sys = filter->owner.sys;
734

735
736
    vlc_mutex_assert(&sys->filter.lock);
    if (filter_chain_IsEmpty(sys->filter.chain_interactive))
737
738
        // 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.
739
        return VoutVideoFilterInteractiveNewPicture(filter);
740

741
742
    return picture_NewFromFormat(&filter->fmt_out.video);
}
743

744
static void FilterFlush(vout_thread_sys_t *sys, bool is_locked)
Laurent Aimar's avatar
Laurent Aimar committed
745
{
746
    if (sys->displayed.current)
Steve Lhomme's avatar
Steve Lhomme committed
747
    {
748
749
        picture_Release( sys->displayed.current );
        sys->displayed.current = NULL;
Steve Lhomme's avatar
Steve Lhomme committed
750
    }
Laurent Aimar's avatar
Laurent Aimar committed
751

752
    if (!is_locked)
753
754
755
        vlc_mutex_lock(&sys->filter.lock);
    filter_chain_VideoFlush(sys->filter.chain_static);
    filter_chain_VideoFlush(sys->filter.chain_interactive);
756
    if (!is_locked)
757
        vlc_mutex_unlock(&sys->filter.lock);
Laurent Aimar's avatar
Laurent Aimar committed
758
759
760
761
762
763
764
}

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

765
static void ChangeFilters(vout_thread_sys_t *vout)
Laurent Aimar's avatar
Laurent Aimar committed
766
{
767
    vout_thread_sys_t *sys = vout;
768
769
    FilterFlush(vout, true);
    DelAllFilterCallbacks(vout);
Laurent Aimar's avatar
Laurent Aimar committed
770
771
772
773
774
775

    vlc_array_t array_static;
    vlc_array_t array_interactive;

    vlc_array_init(&array_static);
    vlc_array_init(&array_interactive);
776

777
    if (sys->private.interlacing.has_deint)
778
779
780
    {
        vout_filter_t *e = malloc(sizeof(*e));

781
        if (likely(e))
782
        {
783
784
785
            char *filter = var_InheritString(&vout->obj, "deinterlace-filter");
            free(config_ChainCreate(&e->name, &e->cfg, filter));
            free(filter);
786
            vlc_array_append_or_abort(&array_static, e);
787
788
789
        }
    }

790
    char *current = sys->filter.configuration ? strdup(sys->filter.configuration) : NULL;
Laurent Aimar's avatar
Laurent Aimar committed
791
792
793
794
795
796
    while (current) {
        config_chain_t *cfg;
        char *name;
        char *next = config_ChainCreate(&name, &cfg, current);

        if (name && *name) {
797
798
            vout_filter_t *e = malloc(sizeof(*e));

799
            if (likely(e)) {
800
801
                e->name = name;
                e->cfg  = cfg;
802
                if (!strcmp(e->name, "postproc"))
803
                    vlc_array_append_or_abort(&array_static, e);
804
                else
805
                    vlc_array_append_or_abort(&array_interactive, e);
806
807
808
809
810
            }
            else {
                if (cfg)
                    config_ChainDestroy(cfg);
                free(name);
Laurent Aimar's avatar
Laurent Aimar committed
811
812
813
814
815
816
817
818
819
820
821
            }
        } else {
            if (cfg)
                config_ChainDestroy(cfg);
            free(name);
        }
        free(current);
        current = next;
    }

    es_format_t fmt_target;
822
823
    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
824

825
    const es_format_t *p_fmt_current = &fmt_target;
826
    vlc_video_context *vctx_current = vctx_target;
Laurent Aimar's avatar
Laurent Aimar committed
827
828
829
830

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

834
        filter_chain_Reset(chain, p_fmt_current, vctx_current, p_fmt_current);
Thomas Guillem's avatar
Thomas Guillem committed
835
        for (size_t i = 0; i < vlc_array_count(array); i++) {
Laurent Aimar's avatar
Laurent Aimar committed
836
            vout_filter_t *e = vlc_array_item_at_index(array, i);
837
            msg_Dbg(&vout->obj, "Adding '%s' as %s", e->name, a == 0 ? "static" : "interactive");
838
            filter_t *filter = filter_chain_AppendFilter(chain, e->name, e->cfg,
839
                               NULL);
840
            if (!filter)
841
                msg_Err(&vout->obj, "Failed to add filter '%s'", e->name);
842
            else if (a == 1) /* Add callbacks for interactive filters */
843
                filter_AddProxyCallbacks(&vout->obj, filter, FilterRestartCallback);
844

845
            config_ChainDestroy(e->cfg);
Laurent Aimar's avatar
Laurent Aimar committed
846
847
848
            free(e->name);
            free(e);
        }
849
850
851
        if (!filter_chain_IsEmpty(chain))
        {
            p_fmt_current = filter_chain_GetFmtOut(chain);
852
            vctx_current = filter_chain_GetVideoCtxOut(chain);
853
        }
Laurent Aimar's avatar
Laurent Aimar committed
854
855
        vlc_array_clear(array);
    }
856

857
    if (!es_format_IsSimilar(p_fmt_current, &fmt_target)) {
858
        msg_Dbg