Coverage Report

Created: 2025-12-14 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/src/video_output/video_output.c
Line
Count
Source
1
/*****************************************************************************
2
 * video_output.c : video output thread
3
 *
4
 * This module describes the programming interface for video output threads.
5
 * It includes functions allowing to open a new thread, send pictures to a
6
 * thread, and destroy a previously oppened video output thread.
7
 *****************************************************************************
8
 * Copyright (C) 2000-2019 VLC authors, VideoLAN and Videolabs SAS
9
 *
10
 * Authors: Vincent Seguin <seguin@via.ecp.fr>
11
 *          Gildas Bazin <gbazin@videolan.org>
12
 *          Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
13
 *
14
 * This program is free software; you can redistribute it and/or modify it
15
 * under the terms of the GNU Lesser General Public License as published by
16
 * the Free Software Foundation; either version 2.1 of the License, or
17
 * (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Lesser General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Lesser General Public License
25
 * along with this program; if not, write to the Free Software Foundation,
26
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27
 *****************************************************************************/
28
29
/*****************************************************************************
30
 * Preamble
31
 *****************************************************************************/
32
#ifdef HAVE_CONFIG_H
33
# include "config.h"
34
#endif
35
36
#include <vlc_common.h>
37
#include <vlc_arrays.h>
38
#include <vlc_configuration.h>
39
40
#include <math.h>
41
#include <stdlib.h>                                                /* free() */
42
#include <string.h>
43
#include <assert.h>
44
45
#include <vlc_vout.h>
46
47
#include <vlc_filter.h>
48
#include <vlc_spu.h>
49
#include <vlc_vout_osd.h>
50
#include <vlc_image.h>
51
#include <vlc_plugin.h>
52
#include <vlc_codec.h>
53
#include <vlc_tracer.h>
54
#include <vlc_atomic.h>
55
56
#include "../libvlc.h"
57
#include "vout_private.h"
58
#include "vout_internal.h"
59
#include "display.h"
60
#include "snapshot.h"
61
#include "video_window.h"
62
#include "../misc/variables.h"
63
#include "../misc/threads.h"
64
#include "../clock/clock.h"
65
#include "statistic.h"
66
#include "chrono.h"
67
#include "control.h"
68
69
typedef struct vout_thread_sys_t
70
{
71
    struct vout_thread_t obj;
72
73
    vout_interlacing_state_t interlacing;
74
75
    bool dummy;
76
77
    /* Splitter module if used */
78
    char            *splitter_name;
79
80
    const char      *str_id;
81
82
    vlc_mutex_t clock_lock;
83
    bool clock_nowait; /* protected by vlc_clock_Lock()/vlc_clock_Unlock() */
84
    bool wait_interrupted;
85
    bool first_picture;
86
87
    vlc_clock_t     *clock;
88
    vlc_clock_listener_id *clock_listener_id;
89
    uint32_t clock_id;
90
    float           rate;
91
    vlc_tick_t      delay;
92
93
    video_format_t  original;   /* Original format ie coming from the decoder */
94
95
    /* */
96
    struct {
97
        vlc_rational_t dar;
98
        struct vout_crop crop;
99
    } source;
100
101
    /* Snapshot interface */
102
    struct vout_snapshot *snapshot;
103
104
    /* Statistics */
105
    vout_statistic_t statistic;
106
107
    /* Subpicture unit */
108
    spu_t           *spu;
109
    vlc_blender_t   *spu_blend;
110
111
    /* Thread & synchronization */
112
    vout_control_t  control;
113
    atomic_bool     control_is_terminated; // shutdown the vout thread
114
    vlc_thread_t    thread;
115
116
    struct {
117
        vlc_tick_t  date;
118
        vlc_tick_t  timestamp;
119
        bool        is_interlaced;
120
        picture_t   *decoded; // decoded picture before passed through chain_static
121
        picture_t   *current;
122
        video_projection_mode_t projection;
123
    } displayed;
124
125
    struct {
126
        bool        is_on;
127
        vlc_tick_t  date;
128
    } pause;
129
130
    /* OSD title configuration */
131
    struct {
132
        bool        show;
133
        int         timeout;
134
        int         position;
135
    } title;
136
137
    /* */
138
    bool            is_late_dropped;
139
140
    /* */
141
    vlc_mouse_t     mouse;
142
143
    /* Video output window */
144
    bool            window_enabled;
145
    unsigned        window_width; /* protected by display_lock */
146
    unsigned        window_height; /* protected by display_lock */
147
    vlc_mutex_t     window_lock;
148
    vlc_decoder_device *dec_device;
149
150
    /* Video output display */
151
    vout_display_cfg_t display_cfg;
152
    vout_display_t *display;
153
    vlc_queuedmutex_t display_lock;
154
155
    /* Video filter2 chain */
156
    struct {
157
        vlc_mutex_t     lock;
158
        bool            changed;
159
        bool            new_interlaced;
160
        char            *configuration;
161
        video_format_t    src_fmt;
162
        vlc_video_context *src_vctx;
163
        struct filter_chain_t *chain_static;
164
        struct filter_chain_t *chain_interactive;
165
    } filter;
166
167
    picture_fifo_t  *decoder_fifo;
168
    struct {
169
        vout_chrono_t static_filter;
170
        vout_chrono_t render;         /**< picture render time estimator */
171
    } chrono;
172
173
    unsigned frame_next_count;
174
175
    vlc_atomic_rc_t rc;
176
177
    picture_pool_t  *private_pool; // interactive + static filters & blending
178
} vout_thread_sys_t;
179
180
#define VOUT_THREAD_TO_SYS(vout) \
181
0
    container_of(vout, vout_thread_sys_t, obj.obj)
182
183
184
/* Amount of pictures in the private pool:
185
 * 3 for interactive+static filters, 1 for SPU blending, 1 for currently displayed */
186
0
#define FILTER_POOL_SIZE  (3+1+1)
187
188
/* Maximum delay between 2 displayed pictures.
189
 * XXX it is needed for now but should be removed in the long term.
190
 */
191
0
#define VOUT_REDISPLAY_DELAY VLC_TICK_FROM_MS(80)
192
193
/**
194
 * Late pictures having a delay higher than this value are thrashed.
195
 */
196
0
#define VOUT_DISPLAY_LATE_THRESHOLD VLC_TICK_FROM_MS(20)
197
198
/* Better be in advance when awakening than late... */
199
0
#define VOUT_MWAIT_TOLERANCE VLC_TICK_FROM_MS(4)
200
201
/* */
202
static inline struct vlc_tracer *GetTracer(vout_thread_sys_t *sys)
203
0
{
204
0
    return sys->str_id == NULL ? NULL :
205
0
        vlc_object_get_tracer(VLC_OBJECT(&sys->obj));
206
0
}
207
208
static inline void VoutResetChronoLocked(vout_thread_sys_t *sys)
209
0
{
210
0
    vlc_queuedmutex_assert(&sys->display_lock);
211
212
    /* Arbitrary initial time */
213
0
    vout_chrono_Init(&sys->chrono.render, 5, VLC_TICK_FROM_MS(10));
214
0
    vout_chrono_Init(&sys->chrono.static_filter, 4, VLC_TICK_FROM_MS(0));
215
0
}
216
217
static bool VoutCheckFormat(const video_format_t *src)
218
0
{
219
0
    if (src->i_width == 0  || src->i_width  > 8192 ||
220
0
        src->i_height == 0 || src->i_height > 8192)
221
0
        return false;
222
0
    if (src->i_sar_num <= 0 || src->i_sar_den <= 0)
223
0
        return false;
224
0
    return true;
225
0
}
226
227
static void VoutFixFormat(video_format_t *dst, const video_format_t *src)
228
0
{
229
0
    video_format_Copy(dst, src);
230
0
    dst->i_chroma = vlc_fourcc_GetCodec(VIDEO_ES, src->i_chroma);
231
0
    VoutFixFormatAR( dst );
232
0
    vlc_viewpoint_clip( &dst->pose );
233
0
}
234
235
static void VoutRenderWakeUpUrgent(vout_thread_sys_t *sys)
236
0
{
237
    /* The assignment to sys->clock is protected by sys->lock */
238
0
    vlc_mutex_lock(&sys->clock_lock);
239
0
    if (sys->clock)
240
0
    {
241
        /* Wake up the clock-wait between prepare() and display() */
242
0
        vlc_clock_Lock(sys->clock);
243
0
        sys->clock_nowait = true;
244
0
        vlc_clock_Wake(sys->clock);
245
0
        vlc_clock_Unlock(sys->clock);
246
0
    }
247
0
    vlc_mutex_unlock(&sys->clock_lock);
248
0
}
249
250
static bool VideoFormatIsCropArEqual(video_format_t *dst,
251
                                     const video_format_t *src)
252
0
{
253
0
    return dst->i_sar_num * src->i_sar_den == dst->i_sar_den * src->i_sar_num &&
254
0
           dst->i_x_offset       == src->i_x_offset &&
255
0
           dst->i_y_offset       == src->i_y_offset &&
256
0
           dst->i_visible_width  == src->i_visible_width &&
257
0
           dst->i_visible_height == src->i_visible_height;
258
0
}
259
260
static void vout_UpdateWindowSizeLocked(vout_thread_sys_t *vout)
261
0
{
262
0
    vout_thread_sys_t *sys = vout;
263
264
0
    if (unlikely(sys->original.i_chroma == 0))
265
0
        return; /* not started yet, postpone size computaton */
266
267
0
    vlc_mutex_assert(&sys->window_lock);
268
0
    vout_display_ResizeWindow(sys->display_cfg.window, &sys->original,
269
0
                              &sys->source.dar, &sys->source.crop,
270
0
                              &sys->display_cfg.display);
271
0
}
272
273
/* */
274
void vout_GetResetStatistic(vout_thread_t *vout, unsigned *restrict displayed,
275
                            unsigned *restrict lost, unsigned *restrict late)
276
0
{
277
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
278
0
    assert(!sys->dummy);
279
0
    vout_statistic_GetReset( &sys->statistic, displayed, lost, late );
280
0
}
281
282
bool vout_IsEmpty(vout_thread_t *vout)
283
0
{
284
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
285
0
    assert(!sys->dummy);
286
0
    assert(sys->decoder_fifo);
287
288
0
    return picture_fifo_IsEmpty(sys->decoder_fifo);
289
0
}
290
291
void vout_DisplayTitle(vout_thread_t *vout, const char *title)
292
0
{
293
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
294
0
    assert(!sys->dummy);
295
0
    assert(title);
296
297
0
    if (!sys->title.show)
298
0
        return;
299
300
0
    vout_OSDText(vout, VOUT_SPU_CHANNEL_OSD, sys->title.position,
301
0
                 VLC_TICK_FROM_MS(sys->title.timeout), title);
302
0
}
303
304
bool vout_FilterMouse(vout_thread_t *vout, vlc_mouse_t *mouse)
305
0
{
306
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
307
0
    vlc_mouse_t tmp[2], *m = mouse;
308
0
    bool event_consumed = false;
309
310
    /* Pass mouse events through the filter chains. */
311
0
    vlc_mutex_lock(&sys->filter.lock);
312
0
    if (sys->filter.chain_static != NULL
313
0
     && sys->filter.chain_interactive != NULL) {
314
0
        if (!filter_chain_MouseFilter(sys->filter.chain_interactive,
315
0
                                      &tmp[0], m))
316
0
            m = &tmp[0];
317
0
        else
318
0
            event_consumed = true;
319
0
        if (!filter_chain_MouseFilter(sys->filter.chain_static,
320
0
                                      &tmp[1], m))
321
0
            m = &tmp[1];
322
0
        else
323
0
            event_consumed = true;
324
0
    }
325
0
    vlc_mutex_unlock(&sys->filter.lock);
326
327
0
    if (mouse != m)
328
0
        *mouse = *m;
329
330
0
    return event_consumed;
331
0
}
332
333
void vout_PutSubpicture( vout_thread_t *vout, subpicture_t *subpic )
334
0
{
335
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
336
0
    assert(!sys->dummy);
337
338
0
    if (sys->spu != NULL)
339
0
        spu_PutSubpicture(sys->spu, subpic);
340
0
    else
341
0
        subpicture_Delete(subpic);
342
0
}
343
344
ssize_t vout_RegisterSubpictureChannel( vout_thread_t *vout )
345
0
{
346
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
347
0
    assert(!sys->dummy);
348
0
    ssize_t channel = VOUT_SPU_CHANNEL_INVALID;
349
350
0
    if (sys->spu)
351
0
        channel = spu_RegisterChannel(sys->spu);
352
353
0
    return channel;
354
0
}
355
356
ssize_t vout_RegisterSubpictureChannelInternal(vout_thread_t *vout,
357
                                               vlc_clock_t *clock,
358
                                               enum vlc_vout_order *out_order)
359
0
{
360
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
361
0
    assert(!sys->dummy);
362
0
    ssize_t channel = VOUT_SPU_CHANNEL_INVALID;
363
364
0
    if (sys->spu)
365
0
        channel = spu_RegisterChannelInternal(sys->spu, clock, out_order);
366
367
0
    return channel;
368
0
}
369
370
void vout_UnregisterSubpictureChannel( vout_thread_t *vout, size_t channel )
371
0
{
372
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
373
0
    assert(!sys->dummy);
374
0
    assert(sys->spu);
375
0
    spu_UnregisterChannel(sys->spu, channel);
376
0
}
377
378
void vout_FlushSubpictureChannel( vout_thread_t *vout, size_t channel )
379
0
{
380
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
381
0
    assert(!sys->dummy);
382
0
    if (sys->spu)
383
0
        spu_ClearChannel(sys->spu, channel);
384
0
}
385
386
void vout_SetSpuHighlight( vout_thread_t *vout,
387
                        const vlc_spu_highlight_t *spu_hl )
388
0
{
389
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
390
0
    assert(!sys->dummy);
391
0
    if (sys->spu)
392
0
        spu_SetHighlight(sys->spu, spu_hl);
393
0
}
394
395
/**
396
 * It gives to the vout a picture to be displayed.
397
 *
398
 * Becareful, after vout_PutPicture is called, picture_t::p_next cannot be
399
 * read/used.
400
 */
401
void vout_PutPicture(vout_thread_t *vout, picture_t *picture)
402
0
{
403
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
404
0
    assert(!sys->dummy);
405
0
    assert( !picture_HasChainedPics( picture ) );
406
0
    picture_fifo_Push(sys->decoder_fifo, picture);
407
0
    vout_control_Wake(&sys->control);
408
0
}
409
410
/* */
411
int vout_GetSnapshot(vout_thread_t *vout,
412
                     block_t **image_dst, picture_t **picture_dst,
413
                     video_format_t *fmt,
414
                     const char *type, vlc_tick_t timeout)
415
0
{
416
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
417
0
    assert(!sys->dummy);
418
0
    picture_t *picture = vout_snapshot_Get(sys->snapshot, timeout);
419
0
    if (!picture) {
420
0
        msg_Err(vout, "Failed to grab a snapshot");
421
0
        return VLC_EGENERIC;
422
0
    }
423
424
0
    if (image_dst) {
425
0
        vlc_fourcc_t codec = VLC_CODEC_PNG;
426
0
        if (type && image_Type2Fourcc(type))
427
0
            codec = image_Type2Fourcc(type);
428
429
0
        const int override_width  = var_InheritInteger(vout, "snapshot-width");
430
0
        const int override_height = var_InheritInteger(vout, "snapshot-height");
431
432
0
        if (picture_Export(VLC_OBJECT(vout), image_dst, fmt,
433
0
                           picture, codec, override_width, override_height, false)) {
434
0
            msg_Err(vout, "Failed to convert image for snapshot");
435
0
            picture_Release(picture);
436
0
            return VLC_EGENERIC;
437
0
        }
438
0
    }
439
0
    if (picture_dst)
440
0
        *picture_dst = picture;
441
0
    else
442
0
        picture_Release(picture);
443
0
    return VLC_SUCCESS;
444
0
}
445
446
/* vout_Control* are usable by anyone at anytime */
447
void vout_ChangeFullscreen(vout_thread_t *vout, const char *id)
448
0
{
449
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
450
0
    assert(!sys->dummy);
451
0
    vlc_mutex_lock(&sys->window_lock);
452
0
    vlc_window_SetFullScreen(sys->display_cfg.window, id);
453
0
    vlc_mutex_unlock(&sys->window_lock);
454
0
}
455
456
void vout_ChangeWindowed(vout_thread_t *vout)
457
0
{
458
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
459
0
    assert(!sys->dummy);
460
0
    vlc_mutex_lock(&sys->window_lock);
461
0
    vlc_window_UnsetFullScreen(sys->display_cfg.window);
462
    /* Attempt to reset the intended window size */
463
0
    vout_UpdateWindowSizeLocked(sys);
464
0
    vlc_mutex_unlock(&sys->window_lock);
465
0
}
466
467
void vout_ChangeWindowState(vout_thread_t *vout, unsigned st)
468
0
{
469
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
470
0
    assert(!sys->dummy);
471
0
    vlc_mutex_lock(&sys->window_lock);
472
0
    vlc_window_SetState(sys->display_cfg.window, st);
473
0
    vlc_mutex_unlock(&sys->window_lock);
474
0
}
475
476
void vout_ChangeDisplaySize(vout_thread_t *vout,
477
                            unsigned width, unsigned height,
478
                            void (*cb)(void *), void *opaque)
479
0
{
480
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
481
482
0
    assert(!sys->dummy);
483
484
0
    VoutRenderWakeUpUrgent(sys);
485
486
    /* DO NOT call this outside the vout window callbacks */
487
0
    vlc_queuedmutex_lock(&sys->display_lock);
488
489
0
    sys->window_width = width;
490
0
    sys->window_height = height;
491
492
0
    if (sys->display != NULL)
493
0
        vout_display_SetSize(sys->display, width, height);
494
495
0
    if (cb != NULL)
496
0
        cb(opaque);
497
0
    vlc_queuedmutex_unlock(&sys->display_lock);
498
0
}
499
500
void vout_ChangeDisplayFitting(vout_thread_t *vout, enum vlc_video_fitting fit)
501
0
{
502
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
503
0
    assert(!sys->dummy);
504
505
0
    vlc_mutex_lock(&sys->window_lock);
506
0
    sys->display_cfg.display.fitting = fit;
507
    /* no window size update here */
508
509
0
    vlc_queuedmutex_lock(&sys->display_lock);
510
0
    vlc_mutex_unlock(&sys->window_lock);
511
512
0
    if (sys->display != NULL)
513
0
        vout_SetDisplayFitting(sys->display, fit);
514
0
    vlc_queuedmutex_unlock(&sys->display_lock);
515
0
}
516
517
void vout_ChangeZoom(vout_thread_t *vout, unsigned num, unsigned den)
518
0
{
519
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
520
0
    assert(!sys->dummy);
521
522
0
    if (num != 0 && den != 0) {
523
0
        vlc_ureduce(&num, &den, num, den, 0);
524
0
    } else {
525
0
        num = 1;
526
0
        den = 1;
527
0
    }
528
529
0
    if (num * 10 < den) {
530
0
        num = 1;
531
0
        den = 10;
532
0
    } else if (num > den * 10) {
533
0
        num = 10;
534
0
        den = 1;
535
0
    }
536
537
0
    vlc_mutex_lock(&sys->window_lock);
538
0
    sys->display_cfg.display.zoom.num = num;
539
0
    sys->display_cfg.display.zoom.den = den;
540
541
0
    vout_UpdateWindowSizeLocked(sys);
542
543
0
    vlc_queuedmutex_lock(&sys->display_lock);
544
0
    vlc_mutex_unlock(&sys->window_lock);
545
546
0
    if (sys->display != NULL)
547
0
        vout_SetDisplayZoom(sys->display, num, den);
548
0
    vlc_queuedmutex_unlock(&sys->display_lock);
549
0
}
550
551
void vout_ChangeDisplayAspectRatio(vout_thread_t *vout, vlc_rational_t dar)
552
0
{
553
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
554
0
    assert(!sys->dummy);
555
556
0
    vlc_mutex_lock(&sys->window_lock);
557
0
    sys->source.dar = dar;
558
559
0
    vout_UpdateWindowSizeLocked(sys);
560
561
0
    vlc_queuedmutex_lock(&sys->display_lock);
562
0
    vlc_mutex_unlock(&sys->window_lock);
563
564
0
    if (sys->display != NULL)
565
0
        vout_SetDisplayAspect(sys->display, dar);
566
0
    vlc_queuedmutex_unlock(&sys->display_lock);
567
0
}
568
569
void vout_ChangeCrop(vout_thread_t *vout,
570
                     const struct vout_crop *restrict crop)
571
0
{
572
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
573
0
    assert(!sys->dummy);
574
575
0
    vlc_mutex_lock(&sys->window_lock);
576
0
    sys->source.crop = *crop;
577
0
    vout_UpdateWindowSizeLocked(sys);
578
579
0
    vlc_queuedmutex_lock(&sys->display_lock);
580
0
    vlc_mutex_unlock(&sys->window_lock);
581
582
0
    if (sys->display != NULL)
583
0
        vout_SetDisplayCrop(sys->display, crop);
584
0
    vlc_queuedmutex_unlock(&sys->display_lock);
585
0
}
586
587
void vout_ControlChangeFilters(vout_thread_t *vout, const char *filters)
588
0
{
589
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
590
0
    assert(!sys->dummy);
591
0
    vlc_mutex_lock(&sys->filter.lock);
592
0
    if (sys->filter.configuration)
593
0
    {
594
0
        if (filters == NULL || strcmp(sys->filter.configuration, filters))
595
0
        {
596
0
            free(sys->filter.configuration);
597
0
            sys->filter.configuration = filters ? strdup(filters) : NULL;
598
0
            sys->filter.changed = true;
599
0
        }
600
0
    }
601
0
    else if (filters != NULL)
602
0
    {
603
0
        sys->filter.configuration = strdup(filters);
604
0
        sys->filter.changed = true;
605
0
    }
606
0
    vlc_mutex_unlock(&sys->filter.lock);
607
0
}
608
609
void vout_ControlChangeInterlacing(vout_thread_t *vout, bool set)
610
0
{
611
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
612
0
    assert(!sys->dummy);
613
0
    vlc_mutex_lock(&sys->filter.lock);
614
0
    sys->filter.new_interlaced = set;
615
0
    vlc_mutex_unlock(&sys->filter.lock);
616
0
}
617
618
void vout_ControlChangeSubSources(vout_thread_t *vout, const char *filters)
619
0
{
620
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
621
0
    assert(!sys->dummy);
622
0
    if (likely(sys->spu != NULL))
623
0
        spu_ChangeSources(sys->spu, filters);
624
0
}
625
626
void vout_ControlChangeSubFilters(vout_thread_t *vout, const char *filters)
627
0
{
628
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
629
0
    assert(!sys->dummy);
630
0
    if (likely(sys->spu != NULL))
631
0
        spu_ChangeFilters(sys->spu, filters);
632
0
}
633
634
void vout_ChangeSpuChannelMargin(vout_thread_t *vout,
635
                                 enum vlc_vout_order order, int margin)
636
0
{
637
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
638
0
    assert(!sys->dummy);
639
0
    if (likely(sys->spu != NULL))
640
0
        spu_ChangeChannelOrderMargin(sys->spu, order, margin);
641
0
}
642
643
void vout_ChangeViewpoint(vout_thread_t *vout,
644
                          const vlc_viewpoint_t *p_viewpoint)
645
0
{
646
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
647
0
    assert(!sys->dummy);
648
649
0
    vlc_mutex_lock(&sys->window_lock);
650
0
    sys->display_cfg.viewpoint = *p_viewpoint;
651
    /* no window size update here */
652
0
    vlc_mutex_unlock(&sys->window_lock);
653
654
0
    VoutRenderWakeUpUrgent(sys);
655
0
    vlc_queuedmutex_lock(&sys->display_lock);
656
0
    if (sys->display != NULL)
657
0
        vout_SetDisplayViewpoint(sys->display, p_viewpoint);
658
0
    vlc_queuedmutex_unlock(&sys->display_lock);
659
0
}
660
661
void vout_ChangeIccProfile(vout_thread_t *vout,
662
                           vlc_icc_profile_t *profile)
663
0
{
664
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
665
0
    assert(!sys->dummy);
666
667
0
    vlc_queuedmutex_lock(&sys->display_lock);
668
0
    free(sys->display_cfg.icc_profile);
669
0
    sys->display_cfg.icc_profile = profile;
670
0
    if (sys->display != NULL)
671
0
        vout_SetDisplayIccProfile(sys->display, profile);
672
0
    vlc_queuedmutex_unlock(&sys->display_lock);
673
0
}
674
675
void vout_ToggleProjection(vout_thread_t *vout, bool enabled)
676
0
{
677
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
678
0
    assert(!sys->dummy);
679
680
0
    video_projection_mode_t projection;
681
0
    if (sys->displayed.projection != PROJECTION_MODE_RECTANGULAR && !enabled)
682
0
        projection = PROJECTION_MODE_RECTANGULAR;
683
0
    else if (sys->original.projection_mode != PROJECTION_MODE_RECTANGULAR && enabled)
684
0
        projection = sys->original.projection_mode;
685
0
    else return;
686
687
0
    vlc_queuedmutex_lock(&sys->display_lock);
688
0
    if (sys->display != NULL)
689
0
        vout_SetDisplayProjection(sys->display, projection);
690
0
    vlc_queuedmutex_unlock(&sys->display_lock);
691
692
0
}
693
694
void vout_ResetProjection(vout_thread_t *vout)
695
0
{
696
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
697
0
    assert(!sys->dummy);
698
699
0
    msg_Dbg(vout, "resetting projection_mode to %d", sys->original.projection_mode);
700
0
    vout_ChangeProjection(vout, sys->original.projection_mode);
701
0
}
702
703
void vout_ChangeProjection(vout_thread_t *vout,
704
                           video_projection_mode_t projection)
705
0
{
706
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
707
0
    assert(!sys->dummy);
708
709
    /* Use vout_ResetProjection instead. */
710
0
    assert((int)projection != -1);
711
0
    msg_Dbg(vout, "setting projection_mode to %d", projection);
712
713
0
    vlc_queuedmutex_lock(&sys->display_lock);
714
0
    if (sys->display != NULL)
715
0
        vout_SetDisplayProjection(sys->display, projection);
716
0
    vlc_queuedmutex_unlock(&sys->display_lock);
717
0
}
718
719
void vout_ControlChangeStereo(vout_thread_t *vout, vlc_stereoscopic_mode_t mode)
720
0
{
721
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
722
0
    assert(!sys->dummy);
723
724
0
    vlc_queuedmutex_lock(&sys->display_lock);
725
0
    sys->display_cfg.stereo_mode = mode;
726
0
    if (sys->display != NULL)
727
0
        vout_SetDisplayStereo(sys->display, mode);
728
0
    vlc_queuedmutex_unlock(&sys->display_lock);
729
0
}
730
731
/* */
732
static void VoutGetDisplayCfg(vout_thread_sys_t *p_vout, const video_format_t *fmt, vout_display_cfg_t *cfg)
733
0
{
734
0
    vout_thread_t *vout = &p_vout->obj;
735
    /* Load configuration */
736
0
    cfg->viewpoint = fmt->pose;
737
738
0
    const int display_width = var_GetInteger(vout, "width");
739
0
    const int display_height = var_GetInteger(vout, "height");
740
0
    cfg->display.width   = display_width > 0  ? display_width  : 0;
741
0
    cfg->display.height  = display_height > 0 ? display_height : 0;
742
0
    cfg->display.full_fill = var_GetBool(vout, "spu-fill");
743
0
    cfg->display.fitting = var_GetBool(vout, "autoscale")
744
0
        ? var_InheritFit(VLC_OBJECT(vout)) : VLC_VIDEO_FIT_NONE;
745
0
    unsigned msar_num, msar_den;
746
0
    if (var_InheritURational(vout, &msar_num, &msar_den, "monitor-par") ||
747
0
        msar_num <= 0 || msar_den <= 0) {
748
0
        msar_num = 1;
749
0
        msar_den = 1;
750
0
    }
751
0
    cfg->display.sar.num = msar_num;
752
0
    cfg->display.sar.den = msar_den;
753
0
    unsigned zoom_den = 1000;
754
0
    unsigned zoom_num = zoom_den * var_GetFloat(vout, "zoom");
755
0
    vlc_ureduce(&zoom_num, &zoom_den, zoom_num, zoom_den, 0);
756
0
    cfg->display.zoom.num = zoom_num;
757
0
    cfg->display.zoom.den = zoom_den;
758
0
    cfg->display.align.vertical = VLC_VIDEO_ALIGN_CENTER;
759
0
    cfg->display.align.horizontal = VLC_VIDEO_ALIGN_CENTER;
760
0
    const int align_mask = var_GetInteger(vout, "align");
761
0
    if (align_mask & VOUT_ALIGN_LEFT)
762
0
        cfg->display.align.horizontal = VLC_VIDEO_ALIGN_LEFT;
763
0
    else if (align_mask & VOUT_ALIGN_RIGHT)
764
0
        cfg->display.align.horizontal = VLC_VIDEO_ALIGN_RIGHT;
765
0
    if (align_mask & VOUT_ALIGN_TOP)
766
0
        cfg->display.align.vertical = VLC_VIDEO_ALIGN_TOP;
767
0
    else if (align_mask & VOUT_ALIGN_BOTTOM)
768
0
        cfg->display.align.vertical = VLC_VIDEO_ALIGN_BOTTOM;
769
0
    cfg->stereo_mode = var_GetInteger(vout, "video-stereo-mode");
770
0
}
771
772
/* */
773
static int FilterRestartCallback(vlc_object_t *p_this, char const *psz_var,
774
                                 vlc_value_t oldval, vlc_value_t newval,
775
                                 void *p_data)
776
0
{
777
0
    (void) p_this; (void) psz_var; (void) oldval; (void) newval;
778
0
    vout_ControlChangeFilters((vout_thread_t *)p_data, NULL);
779
0
    return 0;
780
0
}
781
782
static int DelFilterCallbacks(filter_t *filter, void *opaque)
783
0
{
784
0
    vout_thread_sys_t *sys = opaque;
785
0
    filter_DelProxyCallbacks(VLC_OBJECT(&sys->obj), filter,
786
0
                             FilterRestartCallback);
787
0
    return VLC_SUCCESS;
788
0
}
789
790
static void DelAllFilterCallbacks(vout_thread_sys_t *vout)
791
0
{
792
0
    vout_thread_sys_t *sys = vout;
793
0
    assert(sys->filter.chain_interactive != NULL);
794
0
    filter_chain_ForEach(sys->filter.chain_interactive,
795
0
                         DelFilterCallbacks, vout);
796
0
}
797
798
static picture_t *VoutVideoFilterInteractiveNewPicture(filter_t *filter)
799
0
{
800
0
    vout_thread_sys_t *sys = filter->owner.sys;
801
802
0
    picture_t *picture = picture_pool_Get(sys->private_pool);
803
0
    if (picture) {
804
0
        picture_Reset(picture);
805
0
        video_format_CopyCropAr(&picture->format, &filter->fmt_out.video);
806
0
    }
807
0
    return picture;
808
0
}
809
810
static picture_t *VoutVideoFilterStaticNewPicture(filter_t *filter)
811
0
{
812
0
    vout_thread_sys_t *sys = filter->owner.sys;
813
814
0
    vlc_mutex_assert(&sys->filter.lock);
815
0
    if (filter_chain_IsEmpty(sys->filter.chain_interactive))
816
        // we may be using the last filter of both chains, so we get the picture
817
        // from the display module pool, just like for the last interactive filter.
818
0
        return VoutVideoFilterInteractiveNewPicture(filter);
819
820
0
    return picture_NewFromFormat(&filter->fmt_out.video);
821
0
}
822
823
static void FilterFlush(vout_thread_sys_t *sys, bool is_locked)
824
0
{
825
0
    if (sys->displayed.current)
826
0
    {
827
0
        picture_Release( sys->displayed.current );
828
0
        sys->displayed.current = NULL;
829
0
        sys->displayed.date = VLC_TICK_INVALID;
830
0
    }
831
832
0
    if (!is_locked)
833
0
        vlc_mutex_lock(&sys->filter.lock);
834
0
    filter_chain_VideoFlush(sys->filter.chain_static);
835
0
    filter_chain_VideoFlush(sys->filter.chain_interactive);
836
0
    if (!is_locked)
837
0
        vlc_mutex_unlock(&sys->filter.lock);
838
0
}
839
840
typedef struct {
841
    char           *name;
842
    config_chain_t *cfg;
843
} vout_filter_t;
844
845
static int strcmp_void(const void *a, const void *b)
846
0
{
847
0
    const char *const *entry = b;
848
0
    return strcmp(a, *entry);
849
0
}
850
851
static void ChangeFilters(vout_thread_sys_t *vout)
852
0
{
853
    /* bsearch: must be sorted alphabetically */
854
0
    static const char *const static_filters[] = {
855
0
        "amf_frc",
856
0
        "fps",
857
0
        "postproc",
858
0
    };
859
0
    vout_thread_sys_t *sys = vout;
860
0
    FilterFlush(vout, true);
861
0
    DelAllFilterCallbacks(vout);
862
863
0
    vlc_array_t array_static;
864
0
    vlc_array_t array_interactive;
865
866
0
    vlc_array_init(&array_static);
867
0
    vlc_array_init(&array_interactive);
868
869
0
    if (sys->interlacing.has_deint)
870
0
    {
871
0
        vout_filter_t *e = malloc(sizeof(*e));
872
873
0
        if (likely(e))
874
0
        {
875
0
            char *filter = var_InheritString(&vout->obj, "deinterlace-filter");
876
0
            free(config_ChainCreate(&e->name, &e->cfg, filter));
877
0
            free(filter);
878
0
            vlc_array_append_or_abort(&array_static, e);
879
0
        }
880
0
    }
881
882
0
    char *current = sys->filter.configuration ? strdup(sys->filter.configuration) : NULL;
883
0
    while (current) {
884
0
        config_chain_t *cfg;
885
0
        char *name;
886
0
        char *next = config_ChainCreate(&name, &cfg, current);
887
888
0
        if (name && *name) {
889
0
            vout_filter_t *e = malloc(sizeof(*e));
890
891
0
            if (likely(e)) {
892
0
                e->name = name;
893
0
                e->cfg  = cfg;
894
0
                bool is_static_filter =
895
0
                    bsearch(e->name, static_filters, ARRAY_SIZE(static_filters),
896
0
                            sizeof(const char *), strcmp_void) != NULL;
897
0
                if (is_static_filter)
898
0
                    vlc_array_append_or_abort(&array_static, e);
899
0
                else
900
0
                    vlc_array_append_or_abort(&array_interactive, e);
901
0
            }
902
0
            else {
903
0
                if (cfg)
904
0
                    config_ChainDestroy(cfg);
905
0
                free(name);
906
0
            }
907
0
        } else {
908
0
            if (cfg)
909
0
                config_ChainDestroy(cfg);
910
0
            free(name);
911
0
        }
912
0
        free(current);
913
0
        current = next;
914
0
    }
915
916
0
    es_format_t fmt_target;
917
0
    es_format_InitFromVideo(&fmt_target, &sys->filter.src_fmt);
918
0
    vlc_video_context *vctx_target  = sys->filter.src_vctx;
919
920
0
    const es_format_t *p_fmt_current = &fmt_target;
921
0
    vlc_video_context *vctx_current = vctx_target;
922
923
0
    for (int a = 0; a < 2; a++) {
924
0
        vlc_array_t    *array = a == 0 ? &array_static :
925
0
                                         &array_interactive;
926
0
        filter_chain_t *chain = a == 0 ? sys->filter.chain_static :
927
0
                                         sys->filter.chain_interactive;
928
929
0
        filter_chain_Reset(chain, p_fmt_current, vctx_current, p_fmt_current);
930
0
        for (size_t i = 0; i < vlc_array_count(array); i++) {
931
0
            vout_filter_t *e = vlc_array_item_at_index(array, i);
932
0
            msg_Dbg(&vout->obj, "Adding '%s' as %s", e->name, a == 0 ? "static" : "interactive");
933
0
            filter_t *filter = filter_chain_AppendFilter(chain, e->name, e->cfg,
934
0
                               NULL);
935
0
            if (!filter)
936
0
                msg_Err(&vout->obj, "Failed to add filter '%s'", e->name);
937
0
            else if (a == 1) /* Add callbacks for interactive filters */
938
0
                filter_AddProxyCallbacks(&vout->obj, filter, FilterRestartCallback);
939
940
0
            config_ChainDestroy(e->cfg);
941
0
            free(e->name);
942
0
            free(e);
943
0
        }
944
0
        if (!filter_chain_IsEmpty(chain))
945
0
        {
946
0
            p_fmt_current = filter_chain_GetFmtOut(chain);
947
0
            vctx_current = filter_chain_GetVideoCtxOut(chain);
948
0
        }
949
0
        vlc_array_clear(array);
950
0
    }
951
952
0
    if (!es_format_IsSimilar(p_fmt_current, &fmt_target)) {
953
0
        es_format_LogDifferences(vlc_object_logger(&vout->obj),
954
0
                "current", p_fmt_current, "new", &fmt_target);
955
956
        /* Shallow local copy */
957
0
        es_format_t tmp = *p_fmt_current;
958
        /* Assign the same chroma to compare everything except the chroma */
959
0
        tmp.i_codec = fmt_target.i_codec;
960
0
        tmp.video.i_chroma = fmt_target.video.i_chroma;
961
962
0
        int ret = VLC_EGENERIC;
963
964
0
        bool only_chroma_changed = es_format_IsSimilar(&tmp, &fmt_target);
965
0
        if (only_chroma_changed)
966
0
        {
967
0
            picture_pool_t *new_private_pool =
968
0
                    picture_pool_NewFromFormat(&p_fmt_current->video,
969
0
                                               FILTER_POOL_SIZE);
970
0
            if (new_private_pool != NULL)
971
0
            {
972
0
                msg_Dbg(&vout->obj, "Changing vout format to %4.4s",
973
0
                                    (const char *) &p_fmt_current->video.i_chroma);
974
                /* Only the chroma changed, request the vout to update the format */
975
0
                ret = vout_SetDisplayFormat(sys->display, &p_fmt_current->video,
976
0
                                            vctx_current);
977
0
                if (ret != VLC_SUCCESS)
978
0
                {
979
0
                    picture_pool_Release(new_private_pool);
980
0
                    msg_Dbg(&vout->obj, "Changing vout format to %4.4s failed",
981
0
                            (const char *) &p_fmt_current->video.i_chroma);
982
0
                }
983
0
                else
984
0
                {
985
                    // update the pool
986
0
                    picture_pool_Release(sys->private_pool);
987
0
                    sys->private_pool = new_private_pool;
988
0
                }
989
0
            }
990
0
        }
991
992
0
        if (ret != VLC_SUCCESS)
993
0
        {
994
0
            msg_Dbg(&vout->obj, "Adding a filter to compensate for format changes in interactive chain (%p)",
995
0
                    (void*)sys->filter.chain_interactive);
996
0
            if (filter_chain_AppendConverter(sys->filter.chain_interactive,
997
0
                                             &fmt_target) != VLC_SUCCESS) {
998
0
                msg_Err(&vout->obj, "Failed to compensate for the format changes, removing all filters");
999
0
                DelAllFilterCallbacks(vout);
1000
0
                filter_chain_Reset(sys->filter.chain_static,      &fmt_target, vctx_target, &fmt_target);
1001
0
                filter_chain_Reset(sys->filter.chain_interactive, &fmt_target, vctx_target, &fmt_target);
1002
0
            }
1003
0
        }
1004
0
    }
1005
1006
0
    es_format_Clean(&fmt_target);
1007
1008
0
    sys->filter.changed = false;
1009
0
}
1010
1011
static bool IsPictureLateToProcess(vout_thread_sys_t *vout, const video_format_t *fmt,
1012
                          vlc_tick_t time_until_display,
1013
                          vlc_tick_t process_duration)
1014
0
{
1015
0
    vout_thread_sys_t *sys = vout;
1016
1017
0
    vlc_tick_t late = process_duration - time_until_display;
1018
1019
0
    vlc_tick_t late_threshold;
1020
0
    if (fmt->i_frame_rate && fmt->i_frame_rate_base) {
1021
0
        late_threshold = vlc_tick_from_samples(fmt->i_frame_rate_base, fmt->i_frame_rate);
1022
0
    }
1023
0
    else
1024
0
        late_threshold = VOUT_DISPLAY_LATE_THRESHOLD;
1025
0
    if (late > late_threshold) {
1026
0
        struct vlc_tracer *tracer = GetTracer(vout);
1027
0
        if (tracer != NULL)
1028
0
            vlc_tracer_TraceEvent(tracer, "RENDER", sys->str_id, "toolate");
1029
1030
0
        msg_Warn(&vout->obj, "picture is too late to be displayed (missing %"PRId64" ms)", MS_FROM_VLC_TICK(late));
1031
0
        return true;
1032
0
    }
1033
0
    return false;
1034
0
}
1035
1036
static inline vlc_tick_t GetRenderDelay(vout_thread_sys_t *sys)
1037
0
{
1038
0
    return vout_chrono_GetHigh(&sys->chrono.render) + VOUT_MWAIT_TOLERANCE;
1039
0
}
1040
1041
static bool IsPictureLateToStaticFilter(vout_thread_sys_t *vout,
1042
                                        vlc_tick_t time_until_display)
1043
0
{
1044
0
    vout_thread_sys_t *sys = vout;
1045
0
    const es_format_t *static_es = filter_chain_GetFmtOut(sys->filter.chain_static);
1046
0
    const vlc_tick_t prepare_decoded_duration =
1047
0
        vout_chrono_GetHigh(&sys->chrono.render) +
1048
0
        vout_chrono_GetHigh(&sys->chrono.static_filter);
1049
0
    return IsPictureLateToProcess(vout, &static_es->video, time_until_display, prepare_decoded_duration);
1050
0
}
1051
1052
/* */
1053
VLC_USED
1054
static picture_t *PreparePicture(vout_thread_sys_t *vout, bool reuse_decoded,
1055
                                 bool frame_by_frame)
1056
0
{
1057
0
    vout_thread_sys_t *sys = vout;
1058
0
    bool is_late_dropped = sys->is_late_dropped && !frame_by_frame;
1059
1060
0
    vlc_mutex_lock(&sys->filter.lock);
1061
1062
0
    picture_t *picture = filter_chain_VideoFilter(sys->filter.chain_static, NULL);
1063
0
    assert(!reuse_decoded || !picture);
1064
1065
0
    while (!picture) {
1066
0
        picture_t *decoded;
1067
0
        if (unlikely(reuse_decoded && sys->displayed.decoded)) {
1068
0
            decoded = picture_Hold(sys->displayed.decoded);
1069
0
            if (decoded == NULL)
1070
0
                break;
1071
0
        } else {
1072
0
            decoded = picture_fifo_Pop(sys->decoder_fifo);
1073
0
            if (decoded == NULL)
1074
0
                break;
1075
1076
0
            if (!decoded->b_force)
1077
0
            {
1078
0
                const vlc_tick_t system_now = vlc_tick_now();
1079
0
                uint32_t clock_id;
1080
0
                vlc_clock_Lock(sys->clock);
1081
0
                const vlc_tick_t system_pts =
1082
0
                    vlc_clock_ConvertToSystem(sys->clock, system_now,
1083
0
                                              decoded->date, sys->rate, &clock_id);
1084
0
                vlc_clock_Unlock(sys->clock);
1085
0
                if (clock_id != sys->clock_id)
1086
0
                {
1087
0
                    sys->clock_id = clock_id;
1088
0
                    msg_Dbg(&vout->obj, "Using a new clock context (%u), "
1089
0
                            "flusing static filters", clock_id);
1090
1091
                    /* Most deinterlace modules can't handle a PTS
1092
                     * discontinuity, so flush them.
1093
                     *
1094
                     * FIXME: Pass a discontinuity flag and handle it in
1095
                     * deinterlace modules. */
1096
0
                    filter_chain_VideoFlush(sys->filter.chain_static);
1097
0
                }
1098
1099
0
                if (is_late_dropped
1100
0
                 && IsPictureLateToStaticFilter(vout, system_pts - system_now))
1101
0
                {
1102
0
                    picture_Release(decoded);
1103
0
                    vout_statistic_AddLost(&sys->statistic, 1);
1104
1105
                    /* A picture dropped means discontinuity for the
1106
                     * filters and we need to notify eg. deinterlacer. */
1107
0
                    filter_chain_VideoFlush(sys->filter.chain_static);
1108
0
                    continue;
1109
0
                }
1110
0
            }
1111
1112
0
            if (!VideoFormatIsCropArEqual(&decoded->format, &sys->filter.src_fmt))
1113
0
            {
1114
                // we received an aspect ratio change
1115
                // Update the filters with the filter source format with the new aspect ratio
1116
0
                video_format_Clean(&sys->filter.src_fmt);
1117
0
                video_format_Copy(&sys->filter.src_fmt, &decoded->format);
1118
0
                if (sys->filter.src_vctx)
1119
0
                    vlc_video_context_Release(sys->filter.src_vctx);
1120
0
                vlc_video_context *pic_vctx = picture_GetVideoContext(decoded);
1121
0
                sys->filter.src_vctx = pic_vctx ? vlc_video_context_Hold(pic_vctx) : NULL;
1122
1123
0
                ChangeFilters(vout);
1124
0
            }
1125
0
        }
1126
1127
0
        reuse_decoded = false;
1128
1129
0
        if (sys->displayed.decoded)
1130
0
            picture_Release(sys->displayed.decoded);
1131
1132
0
        sys->displayed.decoded       = picture_Hold(decoded);
1133
0
        sys->displayed.timestamp     = decoded->date;
1134
0
        sys->displayed.is_interlaced = !decoded->b_progressive;
1135
1136
0
        vout_chrono_Start(&sys->chrono.static_filter);
1137
0
        picture = filter_chain_VideoFilter(sys->filter.chain_static, sys->displayed.decoded);
1138
0
        vout_chrono_Stop(&sys->chrono.static_filter);
1139
0
    }
1140
1141
0
    vlc_mutex_unlock(&sys->filter.lock);
1142
1143
0
    return picture;
1144
0
}
1145
1146
static vlc_decoder_device * VoutHoldDecoderDevice(vlc_object_t *o, void *opaque)
1147
0
{
1148
0
    VLC_UNUSED(o);
1149
0
    vout_thread_sys_t *sys = opaque;
1150
0
    return sys->dec_device ? vlc_decoder_device_Hold( sys->dec_device ) : NULL;
1151
0
}
1152
1153
static const struct filter_video_callbacks vout_video_cbs = {
1154
    NULL, VoutHoldDecoderDevice,
1155
};
1156
1157
static picture_t *ConvertRGBAAndBlend(vout_thread_sys_t *vout, picture_t *pic,
1158
                                      vlc_render_subpicture *subpic)
1159
0
{
1160
0
    vout_thread_sys_t *sys = vout;
1161
    /* This function will convert the pic to RGBA and blend the subpic to it.
1162
     * The returned pic can't be used to display since the chroma will be
1163
     * different than the "vout display" one, but it can be used for snapshots.
1164
     * */
1165
1166
0
    assert(sys->spu_blend);
1167
1168
0
    filter_owner_t owner = {
1169
0
        .video = &vout_video_cbs,
1170
0
        .sys = vout,
1171
0
    };
1172
0
    filter_chain_t *filterc = filter_chain_NewVideo(&vout->obj, false, &owner);
1173
0
    if (!filterc)
1174
0
        return NULL;
1175
1176
0
    es_format_t src = sys->spu_blend->fmt_out;
1177
0
    es_format_t dst = src;
1178
0
    dst.video.i_chroma = VLC_CODEC_RGBA;
1179
1180
0
    filter_chain_Reset(filterc, &src,
1181
0
                       NULL /* TODO output video context of blender */,
1182
0
                       &dst);
1183
1184
0
    if (filter_chain_AppendConverter(filterc, &dst) != VLC_SUCCESS)
1185
0
    {
1186
0
        filter_chain_Delete(filterc);
1187
0
        return NULL;
1188
0
    }
1189
1190
0
    picture_Hold(pic);
1191
0
    pic = filter_chain_VideoFilter(filterc, pic);
1192
0
    filter_chain_Delete(filterc);
1193
1194
0
    if (pic)
1195
0
    {
1196
0
        vlc_blender_t *swblend = filter_NewBlend(VLC_OBJECT(&vout->obj), &dst.video);
1197
0
        if (swblend)
1198
0
        {
1199
0
            bool success = picture_BlendSubpicture(pic, swblend, subpic) > 0;
1200
0
            filter_DeleteBlend(swblend);
1201
0
            if (success)
1202
0
                return pic;
1203
0
        }
1204
0
        picture_Release(pic);
1205
0
    }
1206
0
    return NULL;
1207
0
}
1208
1209
static picture_t *FilterPictureInteractive(vout_thread_sys_t *sys)
1210
0
{
1211
    // hold it as the filter chain will release it or return it and we release it
1212
0
    picture_Hold(sys->displayed.current);
1213
1214
0
    vlc_mutex_lock(&sys->filter.lock);
1215
0
    picture_t *filtered = filter_chain_VideoFilter(sys->filter.chain_interactive, sys->displayed.current);
1216
0
    vlc_mutex_unlock(&sys->filter.lock);
1217
1218
0
    if (filtered && filtered->date != sys->displayed.current->date)
1219
0
        msg_Warn(&sys->obj, "Unsupported timestamp modifications done by chain_interactive");
1220
1221
0
    return filtered;
1222
0
}
1223
1224
static vlc_render_subpicture *RenderSPUs(vout_thread_sys_t *sys,
1225
                                const vlc_fourcc_t *subpicture_chromas,
1226
                                const video_format_t *spu_frame,
1227
                                vlc_tick_t system_now, vlc_tick_t render_subtitle_date,
1228
                                bool ignore_osd, bool spu_in_full_window,
1229
                                const vout_display_place_t *video_position)
1230
0
{
1231
0
    if (unlikely(sys->spu == NULL))
1232
0
        return NULL;
1233
0
    return spu_Render(sys->spu,
1234
0
                      subpicture_chromas, spu_frame,
1235
0
                      sys->display->source, spu_in_full_window, video_position,
1236
0
                      system_now, render_subtitle_date,
1237
0
                      ignore_osd);
1238
0
}
1239
1240
static int PrerenderPicture(vout_thread_sys_t *sys, picture_t *filtered,
1241
                            picture_t **out_pic,
1242
                            vlc_render_subpicture **out_subpic)
1243
0
{
1244
0
    vout_display_t *vd = sys->display;
1245
1246
    /*
1247
     * Get the rendering date for the current subpicture to be displayed.
1248
     */
1249
0
    vlc_tick_t system_now = vlc_tick_now();
1250
0
    vlc_tick_t render_subtitle_date;
1251
0
    if (sys->pause.is_on)
1252
0
        render_subtitle_date = sys->pause.date;
1253
0
    else if (filtered->b_force)
1254
0
        render_subtitle_date = system_now;
1255
0
    else
1256
0
    {
1257
0
        vlc_clock_Lock(sys->clock);
1258
0
        render_subtitle_date = filtered->date <= VLC_TICK_0 ? system_now :
1259
0
            vlc_clock_ConvertToSystem(sys->clock, system_now, filtered->date,
1260
0
                                      sys->rate, NULL);
1261
0
        vlc_clock_Unlock(sys->clock);
1262
0
    }
1263
1264
    /*
1265
     * Check whether we let the display draw the subpicture itself (when
1266
     * vd_does_blending=true), and if we can fallback to blending the subpicture
1267
     * ourselves (blending_before_converter=true).
1268
     */
1269
0
    const bool do_snapshot = vout_snapshot_IsRequested(sys->snapshot);
1270
0
    const bool vd_does_blending = !do_snapshot &&
1271
0
                                   vd->info.subpicture_chromas &&
1272
0
                                   *vd->info.subpicture_chromas != 0;
1273
0
    const bool spu_in_full_window = vd->cfg->display.full_fill &&
1274
0
                                    vd_does_blending;
1275
1276
    //FIXME: Denying blending_before_converter if vd->source->orientation != ORIENT_NORMAL
1277
    //will have the effect that snapshots miss the subpictures. We do this
1278
    //because there is currently no way to transform subpictures to match
1279
    //the source format.
1280
    // In early SPU blending the blending is done into the source chroma,
1281
    // otherwise it's done in the display chroma
1282
0
    const bool blending_before_converter = vd->source->orientation == ORIENT_NORMAL;
1283
1284
0
    const vout_display_place_t *video_place = NULL; // default to fit the video
1285
0
    video_format_t fmt_spu;
1286
0
    if (vd_does_blending) {
1287
0
        video_place = vd->place;
1288
1289
0
        fmt_spu = *vd->source;
1290
0
        fmt_spu.i_sar_num = vd->cfg->display.sar.num;
1291
0
        fmt_spu.i_sar_den = vd->cfg->display.sar.den;
1292
0
        fmt_spu.i_x_offset       = 0;
1293
0
        fmt_spu.i_y_offset       = 0;
1294
0
        fmt_spu.i_width          =
1295
0
        fmt_spu.i_visible_width  = vd->cfg->display.width;
1296
0
        fmt_spu.i_height         =
1297
0
        fmt_spu.i_visible_height = vd->cfg->display.height;
1298
0
    } else {
1299
0
        if (blending_before_converter) {
1300
0
            fmt_spu = *vd->source;
1301
0
        } else {
1302
0
            fmt_spu = *vd->fmt;
1303
0
            fmt_spu.i_sar_num = vd->cfg->display.sar.num;
1304
0
            fmt_spu.i_sar_den = vd->cfg->display.sar.den;
1305
0
        }
1306
1307
0
        if (sys->spu_blend &&
1308
0
            !video_format_IsSameChroma(&sys->spu_blend->fmt_out.video, &fmt_spu)) {
1309
0
            filter_DeleteBlend(sys->spu_blend);
1310
0
            sys->spu_blend = NULL;
1311
0
        }
1312
0
        if (!sys->spu_blend) {
1313
0
            sys->spu_blend = filter_NewBlend(VLC_OBJECT(&sys->obj), &fmt_spu);
1314
0
            if (unlikely(sys->spu_blend == NULL))
1315
0
                msg_Err(&sys->obj, "Failed to create blending filter, OSD/Subtitles will not work");
1316
0
        }
1317
0
    }
1318
1319
    /* Get the subpicture to be displayed. */
1320
0
    video_format_t fmt_spu_rot;
1321
0
    video_format_ApplyRotation(&fmt_spu_rot, &fmt_spu);
1322
    /*
1323
     * Perform rendering
1324
     *
1325
     * We have to:
1326
     * - be sure to end up with a direct buffer.
1327
     * - blend subtitles, and in a fast access buffer
1328
     */
1329
0
    picture_t *todisplay = filtered;
1330
0
    picture_t *snap_pic = todisplay;
1331
0
    if (!vd_does_blending && blending_before_converter && sys->spu_blend) {
1332
0
        vlc_render_subpicture *subpic = RenderSPUs(sys, NULL, &fmt_spu_rot,
1333
0
                                          system_now, render_subtitle_date,
1334
0
                                          do_snapshot, spu_in_full_window, video_place);
1335
0
        if (subpic) {
1336
0
            picture_t *blent = picture_pool_Get(sys->private_pool);
1337
0
            if (blent) {
1338
0
                video_format_CopyCropAr(&blent->format, &filtered->format);
1339
0
                picture_Copy(blent, filtered);
1340
0
                if (picture_BlendSubpicture(blent, sys->spu_blend, subpic)) {
1341
0
                    picture_Release(todisplay);
1342
0
                    snap_pic = todisplay = blent;
1343
0
                } else
1344
0
                {
1345
                    /* Blending failed, likely because the picture is opaque or
1346
                     * read-only. Try to convert the opaque picture to a
1347
                     * software RGB32 to generate a snapshot. */
1348
0
                    if (do_snapshot)
1349
0
                    {
1350
0
                        picture_t *copy = ConvertRGBAAndBlend(sys, blent, subpic);
1351
0
                        if (copy)
1352
0
                            snap_pic = copy;
1353
0
                    }
1354
0
                    picture_Release(blent);
1355
0
                }
1356
0
            }
1357
0
            vlc_render_subpicture_Delete(subpic);
1358
0
        }
1359
0
    }
1360
1361
    /*
1362
     * Take a snapshot if requested
1363
     */
1364
0
    if (do_snapshot)
1365
0
    {
1366
0
        assert(snap_pic);
1367
0
        vout_snapshot_Set(sys->snapshot, vd->source, snap_pic);
1368
0
        if (snap_pic != todisplay)
1369
0
            picture_Release(snap_pic);
1370
0
    }
1371
1372
    /* Render the direct buffer */
1373
0
    vout_UpdateDisplaySourceProperties(vd, &todisplay->format);
1374
1375
0
    todisplay = vout_ConvertForDisplay(vd, todisplay);
1376
0
    if (todisplay == NULL) {
1377
0
        return VLC_EGENERIC;
1378
0
    }
1379
1380
0
    if (!vd_does_blending && !blending_before_converter && sys->spu_blend)
1381
0
    {
1382
0
        vlc_render_subpicture *subpic = RenderSPUs(sys, NULL, &fmt_spu_rot,
1383
0
                                          system_now, render_subtitle_date,
1384
0
                                          do_snapshot, spu_in_full_window, video_place);
1385
0
        if (subpic)
1386
0
        {
1387
0
            picture_BlendSubpicture(todisplay, sys->spu_blend, subpic);
1388
0
            vlc_render_subpicture_Delete(subpic);
1389
0
        }
1390
0
    }
1391
1392
0
    *out_pic = todisplay;
1393
0
    if (vd_does_blending)
1394
0
        *out_subpic = RenderSPUs(sys, vd->info.subpicture_chromas, &fmt_spu_rot,
1395
0
                                 system_now, render_subtitle_date,
1396
0
                                 false, spu_in_full_window, video_place);
1397
0
    else
1398
0
        *out_subpic = NULL;
1399
1400
0
    return VLC_SUCCESS;
1401
0
}
1402
1403
enum render_picture_type
1404
{
1405
    RENDER_PICTURE_NORMAL,
1406
    RENDER_PICTURE_FORCED,
1407
    RENDER_PICTURE_NEXT,
1408
};
1409
1410
static int RenderPicture(vout_thread_sys_t *sys,
1411
                         enum render_picture_type render_type)
1412
0
{
1413
0
    vout_display_t *vd = sys->display;
1414
1415
0
    vout_chrono_Start(&sys->chrono.render);
1416
1417
0
    picture_t *filtered = FilterPictureInteractive(sys);
1418
0
    if (!filtered)
1419
0
        return VLC_EGENERIC;
1420
1421
0
    vlc_clock_Lock(sys->clock);
1422
0
    sys->clock_nowait = false;
1423
0
    vlc_clock_Unlock(sys->clock);
1424
0
    vlc_queuedmutex_lock(&sys->display_lock);
1425
1426
0
    picture_t *todisplay;
1427
0
    vlc_render_subpicture *subpic;
1428
0
    int ret = PrerenderPicture(sys, filtered, &todisplay, &subpic);
1429
0
    if (ret != VLC_SUCCESS)
1430
0
    {
1431
0
        vlc_queuedmutex_unlock(&sys->display_lock);
1432
0
        return ret;
1433
0
    }
1434
1435
0
    bool render_now = render_type != RENDER_PICTURE_NORMAL;
1436
1437
0
    vlc_tick_t system_now = vlc_tick_now();
1438
0
    const vlc_tick_t pts = todisplay->date;
1439
0
    vlc_tick_t system_pts;
1440
0
    if (render_now)
1441
0
        system_pts = system_now;
1442
0
    else
1443
0
    {
1444
0
        vlc_clock_Lock(sys->clock);
1445
0
        assert(!sys->displayed.current->b_force);
1446
0
        system_pts = vlc_clock_ConvertToSystem(sys->clock, system_now, pts,
1447
0
                                               sys->rate, NULL);
1448
0
        vlc_clock_Unlock(sys->clock);
1449
0
    }
1450
1451
0
    const unsigned frame_rate = todisplay->format.i_frame_rate;
1452
0
    const unsigned frame_rate_base = todisplay->format.i_frame_rate_base;
1453
1454
0
    if (vd->ops->prepare != NULL)
1455
0
        vd->ops->prepare(vd, todisplay, subpic, system_pts);
1456
1457
0
    vout_chrono_Stop(&sys->chrono.render);
1458
1459
0
    struct vlc_tracer *tracer = GetTracer(sys);
1460
0
    system_now = vlc_tick_now();
1461
0
    if (!render_now)
1462
0
    {
1463
0
        const vlc_tick_t late = system_now - system_pts;
1464
0
        if (unlikely(late > 0))
1465
0
        {
1466
0
            if (tracer != NULL)
1467
0
                vlc_tracer_TraceEvent(tracer, "RENDER", sys->str_id, "late");
1468
0
            msg_Dbg(vd, "picture displayed late (missing %"PRId64" ms)", MS_FROM_VLC_TICK(late));
1469
0
            vout_statistic_AddLate(&sys->statistic, 1);
1470
1471
            /* vd->prepare took too much time. Tell the clock that the pts was
1472
             * rendered late. */
1473
0
            system_pts = system_now;
1474
0
        }
1475
0
        else if (vd->ops->display != NULL)
1476
0
        {
1477
0
            vlc_tick_t max_deadline = system_now + VOUT_REDISPLAY_DELAY;
1478
1479
            /* Wait to reach system_pts if the plugin doesn't handle
1480
             * asynchronous display */
1481
0
            vlc_clock_Lock(sys->clock);
1482
1483
0
            bool timed_out = false;
1484
0
            sys->wait_interrupted = false;
1485
0
            while (!timed_out)
1486
0
            {
1487
0
                vlc_tick_t deadline;
1488
0
                if (vlc_clock_IsPaused(sys->clock))
1489
0
                    deadline = max_deadline;
1490
0
                else
1491
0
                {
1492
0
                    assert(!sys->displayed.current->b_force);
1493
0
                    deadline = vlc_clock_ConvertToSystem(sys->clock,
1494
0
                                                         vlc_tick_now(), pts,
1495
0
                                                         sys->rate, NULL);
1496
0
                    if (deadline > max_deadline)
1497
0
                        deadline = max_deadline;
1498
0
                }
1499
1500
0
                if (sys->clock_nowait)
1501
0
                {
1502
                    /* A caller (the UI thread) awaits for the rendering to
1503
                     * complete urgently, do not wait. */
1504
0
                    sys->wait_interrupted = true;
1505
0
                    break;
1506
0
                }
1507
1508
0
                system_pts = deadline;
1509
0
                timed_out = vlc_clock_Wait(sys->clock, deadline);
1510
0
            }
1511
0
            vlc_clock_Unlock(sys->clock);
1512
0
        }
1513
0
        sys->displayed.date = system_pts;
1514
0
    }
1515
0
    else
1516
0
    {
1517
0
        sys->displayed.date = system_now;
1518
0
    }
1519
1520
    /* Next frames should be updated as forced points */
1521
0
    if (render_type == RENDER_PICTURE_NEXT)
1522
0
        system_now = VLC_TICK_MAX;
1523
0
    else
1524
0
        system_now = vlc_tick_now();
1525
1526
    /* Display the direct buffer returned by vout_RenderPicture */
1527
0
    vout_display_Display(vd, todisplay);
1528
0
    vlc_clock_Lock(sys->clock);
1529
0
    vlc_tick_t drift = vlc_clock_UpdateVideo(sys->clock,
1530
0
                                             system_now,
1531
0
                                             pts, sys->rate,
1532
0
                                             frame_rate, frame_rate_base);
1533
0
    vlc_clock_Unlock(sys->clock);
1534
1535
0
    vlc_queuedmutex_unlock(&sys->display_lock);
1536
1537
0
    picture_Release(todisplay);
1538
1539
0
    if (subpic)
1540
0
        vlc_render_subpicture_Delete(subpic);
1541
1542
0
    vout_statistic_AddDisplayed(&sys->statistic, 1);
1543
1544
0
    if (tracer != NULL && system_pts != VLC_TICK_MAX)
1545
0
        vlc_tracer_TraceWithTs(tracer, system_pts,
1546
0
                               VLC_TRACE("type", "RENDER"),
1547
0
                               VLC_TRACE("id", sys->str_id),
1548
0
                               VLC_TRACE_TICK_NS("drift", drift),
1549
0
                               VLC_TRACE_END);
1550
1551
0
    return VLC_SUCCESS;
1552
0
}
1553
1554
static void UpdateDeinterlaceFilter(vout_thread_sys_t *sys)
1555
0
{
1556
0
    vlc_mutex_lock(&sys->filter.lock);
1557
0
    if (sys->filter.changed ||
1558
0
        sys->interlacing.has_deint != sys->filter.new_interlaced)
1559
0
    {
1560
0
        sys->interlacing.has_deint = sys->filter.new_interlaced;
1561
0
        ChangeFilters(sys);
1562
0
    }
1563
0
    vlc_mutex_unlock(&sys->filter.lock);
1564
0
}
1565
1566
static int DisplayNextFrame(vout_thread_sys_t *sys)
1567
0
{
1568
0
    UpdateDeinterlaceFilter(sys);
1569
1570
0
    picture_t *next = PreparePicture(sys, !sys->displayed.current, true);
1571
1572
0
    if (next)
1573
0
    {
1574
0
        if (likely(sys->displayed.current != NULL))
1575
0
            picture_Release(sys->displayed.current);
1576
0
        sys->displayed.current = next;
1577
0
    }
1578
1579
0
    if (!next)
1580
0
        return VLC_EGENERIC;
1581
1582
0
    return RenderPicture(sys, RENDER_PICTURE_NEXT);
1583
0
}
1584
1585
static bool UpdateCurrentPicture(vout_thread_sys_t *sys)
1586
0
{
1587
0
    assert(sys->clock);
1588
1589
0
    if (sys->frame_next_count > 0)
1590
0
    {
1591
0
        if (DisplayNextFrame(sys) == VLC_SUCCESS)
1592
0
            --sys->frame_next_count;
1593
0
        return false;
1594
0
    }
1595
1596
0
    if (sys->displayed.current == NULL)
1597
0
    {
1598
0
        sys->displayed.current = PreparePicture(sys, true, false);
1599
0
        return sys->displayed.current != NULL;
1600
0
    }
1601
1602
0
    if (sys->pause.is_on || sys->wait_interrupted)
1603
0
        return false;
1604
1605
    /* Prevent to query the clock if we know that there are no next pictures.
1606
     * Since the clock is likely no properly setup at that stage. Indeed, the
1607
     * input/decoder.c send a first forced picture quickly, then a next one
1608
     * when the clock is configured. */
1609
0
    if (sys->first_picture)
1610
0
    {
1611
0
        bool has_next_pic = !picture_fifo_IsEmpty(sys->decoder_fifo);
1612
0
        if (!has_next_pic)
1613
0
            return false;
1614
1615
0
        sys->first_picture = false;
1616
0
    }
1617
1618
0
    const vlc_tick_t system_now = vlc_tick_now();
1619
0
    vlc_clock_Lock(sys->clock);
1620
0
    const vlc_tick_t system_swap_current =
1621
0
        vlc_clock_ConvertToSystem(sys->clock, system_now,
1622
0
                                  sys->displayed.current->date, sys->rate, NULL);
1623
0
    vlc_clock_Unlock(sys->clock);
1624
1625
0
    vlc_tick_t system_prepare_current = system_swap_current - GetRenderDelay(sys);
1626
0
    if (unlikely(system_prepare_current > system_now))
1627
        // the current frame is not late, we still have time to display it
1628
        // no need to get a new picture
1629
0
        return true;
1630
1631
    // the current frame will be late, look for the next not late one
1632
0
    picture_t *next = PreparePicture(sys, false, false);
1633
0
    if (next == NULL)
1634
0
        return false;
1635
    /* We might have reset the current picture when preparing the next one,
1636
     * because filters had to be changed. In this case, avoid releasing the
1637
     * picture since it will lead to null pointer dereference errors. */
1638
0
    if (sys->displayed.current != NULL)
1639
0
        picture_Release(sys->displayed.current);
1640
1641
0
    sys->displayed.current = next;
1642
1643
0
    return true;
1644
0
}
1645
1646
static vlc_tick_t DisplayPicture(vout_thread_sys_t *vout)
1647
0
{
1648
0
    vout_thread_sys_t *sys = vout;
1649
1650
0
    assert(sys->clock);
1651
1652
0
    UpdateDeinterlaceFilter(sys);
1653
1654
0
    bool current_changed = UpdateCurrentPicture(sys);
1655
0
    if (current_changed)
1656
0
    {
1657
        // next frame will still need some waiting before display, we don't need
1658
        // to render now
1659
        // display forced picture immediately
1660
0
        bool render_now = sys->displayed.current->b_force;
1661
1662
0
        RenderPicture(vout, render_now ? RENDER_PICTURE_FORCED
1663
0
                                       : RENDER_PICTURE_NORMAL);
1664
0
        if (!render_now)
1665
            /* Prepare the next picture immediately without waiting */
1666
0
            return VLC_TICK_INVALID;
1667
0
    }
1668
0
    else if (sys->wait_interrupted)
1669
0
    {
1670
0
        sys->wait_interrupted = false;
1671
0
        if (likely(sys->displayed.current != NULL))
1672
0
            RenderPicture(vout, RENDER_PICTURE_FORCED);
1673
0
        return VLC_TICK_INVALID;
1674
0
    }
1675
0
    else if (likely(sys->displayed.date != VLC_TICK_INVALID))
1676
0
    {
1677
        // next date we need to display again the current picture
1678
0
        vlc_tick_t date_refresh = sys->displayed.date + VOUT_REDISPLAY_DELAY - GetRenderDelay(sys);
1679
0
        const vlc_tick_t system_now = vlc_tick_now();
1680
        /* FIXME/XXX we must redisplay the last decoded picture (because
1681
        * of potential vout updated, or filters update or SPU update)
1682
        * For now a high update period is needed but it could be removed
1683
        * if and only if:
1684
        * - vout module emits events from themselves.
1685
        * - *and* SPU is modified to emit an event or a deadline when needed.
1686
        *
1687
        * So it will be done later.
1688
        */
1689
0
        if (date_refresh > system_now) {
1690
            // nothing changed, wait until the next deadline or a control
1691
0
            vlc_tick_t max_deadline = system_now + VOUT_REDISPLAY_DELAY;
1692
0
            return __MIN(date_refresh, max_deadline);
1693
0
        }
1694
0
        RenderPicture(vout, RENDER_PICTURE_FORCED);
1695
0
    }
1696
1697
    // wait until the next deadline or a control
1698
0
    return vlc_tick_now() + VOUT_REDISPLAY_DELAY;
1699
0
}
1700
1701
void vout_ChangePause(vout_thread_t *vout, bool is_paused, vlc_tick_t date)
1702
0
{
1703
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
1704
0
    assert(!sys->dummy);
1705
1706
0
    vout_control_Hold(&sys->control);
1707
0
    assert(!sys->pause.is_on || !is_paused);
1708
1709
0
    if (sys->pause.is_on)
1710
0
        FilterFlush(sys, false);
1711
1712
0
    sys->pause.is_on = is_paused;
1713
0
    sys->pause.date  = date;
1714
0
    vout_control_Release(&sys->control);
1715
1716
0
    struct vlc_tracer *tracer = GetTracer(sys);
1717
0
    if (tracer != NULL)
1718
0
        vlc_tracer_TraceEvent(tracer, "RENDER", sys->str_id,
1719
0
                              is_paused ? "paused" : "resumed");
1720
1721
0
    vlc_mutex_lock(&sys->window_lock);
1722
0
    vlc_window_SetInhibition(sys->display_cfg.window, !is_paused);
1723
0
    vlc_mutex_unlock(&sys->window_lock);
1724
0
}
1725
1726
static void vout_FlushUnlocked(vout_thread_sys_t *vout, bool below,
1727
                               vlc_tick_t date)
1728
0
{
1729
0
    vout_thread_sys_t *sys = vout;
1730
1731
0
    FilterFlush(vout, false); /* FIXME too much */
1732
1733
0
    picture_t *last = sys->displayed.decoded;
1734
0
    if (last) {
1735
0
        if ((date == VLC_TICK_INVALID) ||
1736
0
            ( below && last->date <= date) ||
1737
0
            (!below && last->date >= date)) {
1738
0
            picture_Release(last);
1739
1740
0
            sys->displayed.decoded   = NULL;
1741
0
            sys->displayed.date      = VLC_TICK_INVALID;
1742
0
        }
1743
0
    }
1744
1745
0
    picture_fifo_Flush(sys->decoder_fifo, date, below);
1746
1747
0
    vlc_queuedmutex_lock(&sys->display_lock);
1748
0
    if (sys->display != NULL)
1749
0
        vout_FilterFlush(sys->display);
1750
    /* Reinitialize chrono to ensure we re-compute any new render timing. */
1751
0
    VoutResetChronoLocked(sys);
1752
0
    vlc_queuedmutex_unlock(&sys->display_lock);
1753
1754
0
    if (sys->clock != NULL)
1755
0
    {
1756
0
        vlc_clock_Lock(sys->clock);
1757
0
        vlc_clock_Reset(sys->clock);
1758
0
        vlc_clock_SetDelay(sys->clock, sys->delay);
1759
0
        vlc_clock_Unlock(sys->clock);
1760
0
    }
1761
0
    sys->first_picture = true;
1762
0
}
1763
1764
vlc_tick_t vout_Flush(vout_thread_t *vout, vlc_tick_t date)
1765
0
{
1766
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
1767
0
    assert(!sys->dummy);
1768
1769
0
    vout_control_Hold(&sys->control);
1770
0
    vout_FlushUnlocked(sys, false, date);
1771
0
    vlc_tick_t displayed_pts = sys->displayed.timestamp;
1772
0
    vout_control_Release(&sys->control);
1773
1774
0
    struct vlc_tracer *tracer = GetTracer(sys);
1775
0
    if (tracer != NULL)
1776
0
        vlc_tracer_TraceEvent(tracer, "RENDER", sys->str_id, "flushed");
1777
1778
0
    return displayed_pts;
1779
0
}
1780
1781
void vout_NextPicture(vout_thread_t *vout)
1782
0
{
1783
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
1784
0
    assert(!sys->dummy);
1785
1786
0
    vout_control_Hold(&sys->control);
1787
1788
0
    sys->frame_next_count++;
1789
1790
0
    vout_control_ReleaseAndWake(&sys->control);
1791
0
}
1792
1793
void vout_ChangeDelay(vout_thread_t *vout, vlc_tick_t delay)
1794
0
{
1795
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
1796
0
    assert(!sys->dummy);
1797
0
    assert(sys->display);
1798
1799
0
    vout_control_Hold(&sys->control);
1800
0
    vlc_clock_Lock(sys->clock);
1801
0
    vlc_clock_SetDelay(sys->clock, delay);
1802
0
    vlc_clock_Unlock(sys->clock);
1803
0
    sys->delay = delay;
1804
0
    vout_control_Release(&sys->control);
1805
0
}
1806
1807
void vout_ChangeRate(vout_thread_t *vout, float rate)
1808
0
{
1809
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
1810
0
    assert(!sys->dummy);
1811
1812
0
    vout_control_Hold(&sys->control);
1813
0
    sys->rate = rate;
1814
0
    vout_control_Release(&sys->control);
1815
0
}
1816
1817
void vout_ChangeSpuDelay(vout_thread_t *vout, size_t channel_id,
1818
                         vlc_tick_t delay)
1819
0
{
1820
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
1821
0
    assert(!sys->dummy);
1822
0
    assert(sys->spu);
1823
0
    spu_SetClockDelay(sys->spu, channel_id, delay);
1824
0
}
1825
1826
void vout_ChangeSpuRate(vout_thread_t *vout, size_t channel_id, float rate)
1827
0
{
1828
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
1829
0
    assert(!sys->dummy);
1830
0
    assert(sys->spu);
1831
0
    spu_SetClockRate(sys->spu, channel_id, rate);
1832
0
}
1833
1834
static int vout_Start(vout_thread_sys_t *vout, vlc_video_context *vctx, const vout_configuration_t *cfg)
1835
0
{
1836
0
    vout_thread_sys_t *sys = vout;
1837
0
    filter_chain_t *cs, *ci;
1838
1839
0
    assert(!sys->dummy);
1840
1841
0
    vlc_mutex_lock(&sys->window_lock);
1842
0
    vout_display_window_SetMouseHandler(sys->display_cfg.window,
1843
0
                                        cfg->mouse_event, cfg->mouse_opaque);
1844
0
    vlc_mutex_unlock(&sys->window_lock);
1845
1846
0
    sys->private_pool = NULL;
1847
1848
0
    sys->filter.configuration = NULL;
1849
0
    video_format_Copy(&sys->filter.src_fmt, &sys->original);
1850
0
    sys->filter.src_vctx = vctx ? vlc_video_context_Hold(vctx) : NULL;
1851
1852
0
    static const struct filter_video_callbacks static_cbs = {
1853
0
        VoutVideoFilterStaticNewPicture, VoutHoldDecoderDevice,
1854
0
    };
1855
0
    static const struct filter_video_callbacks interactive_cbs = {
1856
0
        VoutVideoFilterInteractiveNewPicture, VoutHoldDecoderDevice,
1857
0
    };
1858
0
    filter_owner_t owner = {
1859
0
        .video = &static_cbs,
1860
0
        .sys = vout,
1861
0
    };
1862
1863
0
    cs = filter_chain_NewVideo(&vout->obj, true, &owner);
1864
1865
0
    owner.video = &interactive_cbs;
1866
0
    ci = filter_chain_NewVideo(&vout->obj, true, &owner);
1867
1868
0
    vlc_mutex_lock(&sys->filter.lock);
1869
0
    sys->filter.chain_static = cs;
1870
0
    sys->filter.chain_interactive = ci;
1871
0
    vlc_mutex_unlock(&sys->filter.lock);
1872
1873
0
    vout_display_cfg_t dcfg;
1874
0
    struct vout_crop crop;
1875
0
    vlc_rational_t dar;
1876
1877
0
    vlc_mutex_lock(&sys->window_lock);
1878
0
#ifndef NDEBUG
1879
0
    if (vctx)
1880
0
    {
1881
        // make sure the decoder device we receive matches the one we have cached
1882
0
        vlc_decoder_device *dec_device = vlc_video_context_HoldDevice(vctx);
1883
0
        assert(dec_device && dec_device == sys->dec_device);
1884
0
        vlc_decoder_device_Release(dec_device);
1885
0
    }
1886
0
#endif
1887
1888
0
    dcfg = sys->display_cfg;
1889
0
    crop = sys->source.crop;
1890
0
    dar = sys->source.dar;
1891
0
    vlc_queuedmutex_lock(&sys->display_lock);
1892
0
    vlc_mutex_unlock(&sys->window_lock);
1893
1894
    /* Reinitialize chrono to ensure we re-compute any new render timing. */
1895
0
    VoutResetChronoLocked(sys);
1896
1897
    /* Setup the window size, protected by the display_lock */
1898
0
    dcfg.display.width = sys->window_width;
1899
0
    dcfg.display.height = sys->window_height;
1900
1901
0
    int projection = var_InheritInteger(&vout->obj, "projection-mode");
1902
0
    if (projection == -1)
1903
0
        dcfg.projection = sys->original.projection_mode;
1904
0
    else if (projection >= 0)
1905
0
        dcfg.projection = (video_projection_mode_t)projection;
1906
1907
0
    sys->private_pool =
1908
0
        picture_pool_NewFromFormat(&sys->original, FILTER_POOL_SIZE);
1909
0
    if (sys->private_pool == NULL) {
1910
0
        vlc_queuedmutex_unlock(&sys->display_lock);
1911
0
        goto error;
1912
0
    }
1913
1914
0
    sys->display = vout_OpenWrapper(&vout->obj, sys->splitter_name, &dcfg,
1915
0
                                    &sys->original, vctx);
1916
0
    if (sys->display == NULL) {
1917
0
        vlc_queuedmutex_unlock(&sys->display_lock);
1918
0
        goto error;
1919
0
    }
1920
1921
0
    vout_SetDisplayCrop(sys->display, &crop);
1922
1923
0
    vout_SetDisplayAspect(sys->display, dar);
1924
0
    vlc_queuedmutex_unlock(&sys->display_lock);
1925
1926
0
    sys->displayed.current       = NULL;
1927
0
    sys->displayed.decoded       = NULL;
1928
0
    sys->displayed.date          = VLC_TICK_INVALID;
1929
0
    sys->displayed.timestamp     = VLC_TICK_INVALID;
1930
0
    sys->displayed.is_interlaced = false;
1931
1932
0
    sys->pause.is_on = false;
1933
0
    sys->pause.date  = VLC_TICK_INVALID;
1934
1935
0
    sys->spu_blend               = NULL;
1936
1937
0
    video_format_Print(VLC_OBJECT(&vout->obj), "original format", &sys->original);
1938
0
    return VLC_SUCCESS;
1939
0
error:
1940
0
    if (sys->filter.chain_interactive != NULL)
1941
0
        DelAllFilterCallbacks(vout);
1942
0
    vlc_mutex_lock(&sys->filter.lock);
1943
0
    ci = sys->filter.chain_interactive;
1944
0
    cs = sys->filter.chain_static;
1945
0
    sys->filter.chain_interactive = NULL;
1946
0
    sys->filter.chain_static = NULL;
1947
0
    vlc_mutex_unlock(&sys->filter.lock);
1948
0
    if (ci != NULL)
1949
0
        filter_chain_Delete(ci);
1950
0
    if (cs != NULL)
1951
0
        filter_chain_Delete(cs);
1952
0
    if (sys->private_pool)
1953
0
    {
1954
0
        picture_pool_Release(sys->private_pool);
1955
0
        sys->private_pool = NULL;
1956
0
    }
1957
0
    video_format_Clean(&sys->filter.src_fmt);
1958
0
    if (sys->filter.src_vctx)
1959
0
    {
1960
0
        vlc_video_context_Release(sys->filter.src_vctx);
1961
0
        sys->filter.src_vctx = NULL;
1962
0
    }
1963
0
    vlc_mutex_lock(&sys->window_lock);
1964
0
    vout_display_window_SetMouseHandler(sys->display_cfg.window, NULL, NULL);
1965
0
    vlc_mutex_unlock(&sys->window_lock);
1966
0
    return VLC_EGENERIC;
1967
0
}
1968
1969
/*****************************************************************************
1970
 * Thread: video output thread
1971
 *****************************************************************************
1972
 * Video output thread. This function does only returns when the thread is
1973
 * terminated. It handles the pictures arriving in the video heap and the
1974
 * display device events.
1975
 *****************************************************************************/
1976
static void *Thread(void *object)
1977
0
{
1978
0
    vout_thread_sys_t *vout = object;
1979
0
    vout_thread_sys_t *sys = vout;
1980
1981
0
    vlc_thread_set_name("vlc-vout");
1982
1983
0
    vlc_tick_t deadline = VLC_TICK_INVALID;
1984
1985
0
    for (;;) {
1986
0
        vout_control_Wait(&sys->control, deadline);
1987
1988
0
        if (atomic_load(&sys->control_is_terminated))
1989
0
            break;
1990
1991
        /* A deadline of VLC_TICK_INVALID means "immediately" */
1992
0
        deadline = DisplayPicture(vout);
1993
1994
0
        assert(deadline == VLC_TICK_INVALID ||
1995
0
               deadline <= vlc_tick_now() + VOUT_REDISPLAY_DELAY);
1996
1997
0
        if (atomic_load(&sys->control_is_terminated))
1998
0
            break;
1999
2000
0
        const bool picture_interlaced = sys->displayed.is_interlaced;
2001
2002
0
        vout_SetInterlacingState(&vout->obj, &sys->interlacing, picture_interlaced);
2003
0
    }
2004
0
    return NULL;
2005
0
}
2006
2007
static void vout_ReleaseDisplay(vout_thread_sys_t *vout)
2008
0
{
2009
0
    vout_thread_sys_t *sys = vout;
2010
0
    filter_chain_t *ci, *cs;
2011
2012
0
    assert(sys->display != NULL);
2013
2014
0
    if (sys->spu_blend != NULL)
2015
0
        filter_DeleteBlend(sys->spu_blend);
2016
2017
    /* Destroy the rendering display */
2018
0
    if (sys->private_pool != NULL)
2019
0
    {
2020
0
        vout_FlushUnlocked(vout, true, VLC_TICK_MAX);
2021
2022
0
        picture_pool_Release(sys->private_pool);
2023
0
        sys->private_pool = NULL;
2024
0
    }
2025
2026
0
    vlc_queuedmutex_lock(&sys->display_lock);
2027
0
    vout_CloseWrapper(&vout->obj, sys->display);
2028
0
    sys->display = NULL;
2029
0
    vlc_queuedmutex_unlock(&sys->display_lock);
2030
2031
    /* Destroy the video filters */
2032
0
    DelAllFilterCallbacks(vout);
2033
0
    vlc_mutex_lock(&sys->filter.lock);
2034
0
    ci = sys->filter.chain_interactive;
2035
0
    cs = sys->filter.chain_static;
2036
0
    sys->filter.chain_interactive = NULL;
2037
0
    sys->filter.chain_static = NULL;
2038
0
    vlc_mutex_unlock(&sys->filter.lock);
2039
0
    filter_chain_Delete(ci);
2040
0
    filter_chain_Delete(cs);
2041
0
    video_format_Clean(&sys->filter.src_fmt);
2042
0
    if (sys->filter.src_vctx)
2043
0
    {
2044
0
        vlc_video_context_Release(sys->filter.src_vctx);
2045
0
        sys->filter.src_vctx = NULL;
2046
0
    }
2047
0
    free(sys->filter.configuration);
2048
2049
0
    assert(sys->private_pool == NULL);
2050
2051
0
    vlc_mutex_lock(&sys->window_lock);
2052
0
    vout_display_window_SetMouseHandler(sys->display_cfg.window, NULL, NULL);
2053
0
    vlc_mutex_unlock(&sys->window_lock);
2054
2055
0
    if (sys->spu)
2056
0
        spu_Detach(sys->spu);
2057
2058
0
    if (sys->clock_listener_id != NULL)
2059
0
    {
2060
0
        vlc_clock_Lock(sys->clock);
2061
0
        vlc_clock_RemoveListener(sys->clock, sys->clock_listener_id);
2062
0
        vlc_clock_Unlock(sys->clock);
2063
0
        sys->clock_listener_id = NULL;
2064
0
    }
2065
2066
0
    vlc_mutex_lock(&sys->clock_lock);
2067
0
    sys->clock = NULL;
2068
0
    vlc_mutex_unlock(&sys->clock_lock);
2069
0
    sys->str_id = NULL;
2070
0
    sys->clock_id = 0;
2071
0
    sys->first_picture = true;
2072
0
}
2073
2074
void vout_StopDisplay(vout_thread_t *vout)
2075
0
{
2076
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
2077
2078
0
    atomic_store(&sys->control_is_terminated, true);
2079
    // wake up so it goes back to the loop that will detect the terminated state
2080
0
    vout_control_Wake(&sys->control);
2081
0
    vlc_join(sys->thread, NULL);
2082
2083
0
    vout_ReleaseDisplay(sys);
2084
0
}
2085
2086
static void vout_DisableWindow(vout_thread_sys_t *sys)
2087
0
{
2088
0
    vlc_mutex_lock(&sys->window_lock);
2089
0
    if (sys->window_enabled) {
2090
0
        vlc_window_Disable(sys->display_cfg.window);
2091
0
        sys->window_enabled = false;
2092
0
    }
2093
0
    vlc_mutex_unlock(&sys->window_lock);
2094
0
}
2095
2096
void vout_Stop(vout_thread_t *vout)
2097
0
{
2098
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
2099
0
    assert(!sys->dummy);
2100
2101
0
    if (sys->display != NULL)
2102
0
        vout_StopDisplay(vout);
2103
2104
0
    vout_DisableWindow(sys);
2105
0
}
2106
2107
void vout_Close(vout_thread_t *vout)
2108
0
{
2109
0
    assert(vout);
2110
2111
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
2112
0
    assert(!sys->dummy);
2113
2114
0
    vout_Stop(vout);
2115
2116
0
    vout_IntfDeinit(VLC_OBJECT(vout));
2117
0
    vout_snapshot_End(sys->snapshot);
2118
2119
0
    if (sys->spu)
2120
0
        spu_Destroy(sys->spu);
2121
2122
0
    vout_Release(vout);
2123
0
}
2124
2125
void vout_Release(vout_thread_t *vout)
2126
0
{
2127
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
2128
2129
0
    if (!vlc_atomic_rc_dec(&sys->rc))
2130
0
        return;
2131
2132
0
    if (sys->dummy)
2133
0
    {
2134
0
        vlc_object_delete(VLC_OBJECT(vout));
2135
0
        return;
2136
0
    }
2137
2138
0
    picture_fifo_Delete(sys->decoder_fifo);
2139
2140
0
    free(sys->splitter_name);
2141
0
    free(sys->display_cfg.icc_profile);
2142
2143
0
    if (sys->dec_device)
2144
0
        vlc_decoder_device_Release(sys->dec_device);
2145
2146
0
    assert(!sys->window_enabled);
2147
0
    vout_display_window_Delete(sys->display_cfg.window);
2148
2149
    /* */
2150
0
    vout_statistic_Clean(&sys->statistic);
2151
2152
    /* */
2153
0
    vout_snapshot_Destroy(sys->snapshot);
2154
0
    video_format_Clean(&sys->original);
2155
0
    vlc_object_delete(VLC_OBJECT(vout));
2156
0
}
2157
2158
static vout_thread_sys_t *vout_CreateCommon(vlc_object_t *object)
2159
0
{
2160
    /* Allocate descriptor */
2161
0
    vout_thread_sys_t *vout = vlc_custom_create(object,
2162
0
                                            sizeof(*vout),
2163
0
                                            "video output");
2164
0
    if (!vout)
2165
0
        return NULL;
2166
2167
0
    vout_CreateVars(&vout->obj);
2168
2169
0
    vout_thread_sys_t *sys = vout;
2170
0
    vlc_atomic_rc_init(&sys->rc);
2171
0
    vlc_mouse_Init(&sys->mouse);
2172
0
    return vout;
2173
0
}
2174
2175
vout_thread_t *vout_CreateDummy(vlc_object_t *object)
2176
0
{
2177
0
    vout_thread_sys_t *vout = vout_CreateCommon(object);
2178
0
    if (!vout)
2179
0
        return NULL;
2180
2181
0
    vout_thread_sys_t *sys = vout;
2182
0
    sys->dummy = true;
2183
0
    return &vout->obj;
2184
0
}
2185
2186
vout_thread_t *vout_Create(vlc_object_t *object)
2187
0
{
2188
0
    vout_thread_sys_t *p_vout = vout_CreateCommon(object);
2189
0
    if (!p_vout)
2190
0
        return NULL;
2191
0
    vout_thread_t *vout = &p_vout->obj;
2192
0
    vout_thread_sys_t *sys = p_vout;
2193
0
    sys->dummy = false;
2194
2195
0
    sys->decoder_fifo = picture_fifo_New();
2196
0
    if (sys->decoder_fifo == NULL)
2197
0
    {
2198
0
        vlc_object_delete(vout);
2199
0
        return NULL;
2200
0
    }
2201
2202
    /* Register the VLC variable and callbacks. On the one hand, the variables
2203
     * must be ready early on because further initializations below depend on
2204
     * some of them. On the other hand, the callbacks depend on said
2205
     * initializations. In practice, this works because the object is not
2206
     * visible and callbacks not triggerable before this function returns the
2207
     * fully initialized object to its caller.
2208
     */
2209
0
    vout_IntfInit(vout);
2210
2211
    /* Get splitter name if present */
2212
0
    sys->splitter_name = NULL;
2213
2214
0
    if (config_GetType("video-splitter")) {
2215
0
        char *splitter_name = var_InheritString(vout, "video-splitter");
2216
0
        if (unlikely(splitter_name == NULL)) {
2217
0
            picture_fifo_Delete(sys->decoder_fifo);
2218
0
            vlc_object_delete(vout);
2219
0
            return NULL;
2220
0
        }
2221
2222
0
        if (strcmp(splitter_name, "none") != 0) {
2223
0
            var_Create(vout, "window", VLC_VAR_STRING);
2224
0
            var_SetString(vout, "window", "wdummy");
2225
0
            sys->splitter_name = splitter_name;
2226
0
        } else
2227
0
            free(splitter_name);
2228
0
    }
2229
2230
0
    video_format_Init(&sys->original, 0);
2231
0
    sys->source.dar = VLC_DAR_FROM_SOURCE;
2232
0
    sys->source.crop.mode = VOUT_CROP_NONE;
2233
0
    sys->snapshot = vout_snapshot_New();
2234
0
    vout_statistic_Init(&sys->statistic);
2235
2236
    /* Initialize subpicture unit */
2237
0
    sys->spu = var_InheritBool(vout, "spu") || var_InheritBool(vout, "osd") ?
2238
0
               spu_Create(vout, vout) : NULL;
2239
2240
0
    vout_control_Init(&sys->control);
2241
0
    atomic_init(&sys->control_is_terminated, false);
2242
2243
0
    sys->title.show     = var_InheritBool(vout, "video-title-show");
2244
0
    sys->title.timeout  = var_InheritInteger(vout, "video-title-timeout");
2245
0
    sys->title.position = var_InheritInteger(vout, "video-title-position");
2246
2247
0
    sys->private_pool = NULL;
2248
2249
0
    vout_InitInterlacingSupport(vout, &sys->interlacing);
2250
2251
0
    sys->is_late_dropped = var_InheritBool(vout, "drop-late-frames");
2252
2253
0
    vlc_mutex_init(&sys->filter.lock);
2254
2255
0
    vlc_mutex_init(&sys->clock_lock);
2256
0
    sys->clock_nowait = false;
2257
0
    sys->wait_interrupted = false;
2258
0
    sys->first_picture = true;
2259
2260
    /* Display */
2261
0
    sys->display = NULL;
2262
0
    sys->display_cfg.icc_profile = NULL;
2263
0
    vlc_queuedmutex_init(&sys->display_lock);
2264
2265
    /* Window */
2266
0
    sys->window_width = sys->window_height = 0;
2267
0
    sys->display_cfg.window = vout_display_window_New(vout);
2268
0
    if (sys->display_cfg.window == NULL) {
2269
0
        if (sys->spu)
2270
0
            spu_Destroy(sys->spu);
2271
0
        picture_fifo_Delete(sys->decoder_fifo);
2272
0
        vlc_object_delete(vout);
2273
0
        return NULL;
2274
0
    }
2275
2276
0
    if (sys->splitter_name != NULL)
2277
0
        var_Destroy(vout, "window");
2278
0
    sys->window_enabled = false;
2279
0
    sys->frame_next_count = 0;
2280
0
    vlc_mutex_init(&sys->window_lock);
2281
2282
0
    if (var_InheritBool(vout, "video-wallpaper"))
2283
0
        vlc_window_SetState(sys->display_cfg.window, VLC_WINDOW_STATE_BELOW);
2284
0
    else if (var_InheritBool(vout, "video-on-top"))
2285
0
        vlc_window_SetState(sys->display_cfg.window, VLC_WINDOW_STATE_ABOVE);
2286
2287
0
    return vout;
2288
0
}
2289
2290
vout_thread_t *vout_Hold( vout_thread_t *vout)
2291
0
{
2292
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
2293
2294
0
    vlc_atomic_rc_inc(&sys->rc);
2295
0
    return vout;
2296
0
}
2297
2298
int vout_ChangeSource( vout_thread_t *vout, const video_format_t *original,
2299
                       const vlc_video_context *vctx )
2300
0
{
2301
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
2302
2303
0
    if (sys->display == NULL)
2304
0
        return -1;
2305
0
    if (sys->filter.src_vctx != vctx)
2306
0
        return -1;
2307
2308
     /* TODO: If dimensions are equal or slightly smaller, update the aspect
2309
     * ratio and crop settings, instead of recreating a display.
2310
     */
2311
0
    if (!video_format_IsSimilar(original, &sys->original))
2312
0
    {
2313
0
        msg_Dbg(&vout->obj, "vout format changed");
2314
0
        video_format_LogDifferences(vlc_object_logger(&vout->obj), "current", &sys->original, "new", original);
2315
0
        return -1;
2316
0
    }
2317
2318
    /* It is assumed that the SPU input matches input already. */
2319
0
    return 0;
2320
0
}
2321
2322
static int EnableWindowLocked(vout_thread_sys_t *vout, const video_format_t *original)
2323
0
{
2324
0
    assert(vout != NULL);
2325
0
    vout_thread_sys_t *sys = vout;
2326
2327
0
    assert(!sys->dummy);
2328
0
    vlc_mutex_assert(&sys->window_lock);
2329
0
    VoutGetDisplayCfg(vout, original, &sys->display_cfg);
2330
0
    vout_UpdateWindowSizeLocked(vout);
2331
2332
0
    if (!sys->window_enabled) {
2333
0
        if (vlc_window_Enable(sys->display_cfg.window)) {
2334
0
            msg_Err(&vout->obj, "failed to enable window");
2335
0
            return -1;
2336
0
        }
2337
0
        sys->window_enabled = true;
2338
0
    }
2339
0
    return 0;
2340
0
}
2341
2342
static void vout_InitSource(vout_thread_sys_t *vout)
2343
0
{
2344
0
    char *psz_ar = var_InheritString(&vout->obj, "aspect-ratio");
2345
0
    if (psz_ar) {
2346
0
        vlc_rational_t ar;
2347
0
        if (vout_ParseDisplayAspectRatio(&ar, psz_ar))
2348
0
            vout->source.dar = ar;
2349
0
        free(psz_ar);
2350
0
    }
2351
2352
0
    char *psz_crop = var_InheritString(&vout->obj, "crop");
2353
0
    if (psz_crop) {
2354
0
        if (!vout_ParseCrop(&vout->source.crop, psz_crop))
2355
0
            vout->source.crop.mode = VOUT_CROP_NONE;
2356
0
        free(psz_crop);
2357
0
    }
2358
0
}
2359
2360
static void clock_event_OnDiscontinuity(void *data)
2361
0
{
2362
0
    vout_thread_sys_t *vout = data;
2363
0
    vout_thread_sys_t *sys = vout;
2364
2365
    /* The Render thread wait for a deadline that is either:
2366
     *  - VOUT_REDISPLAY_DELAY
2367
     *  - calculated from the clock
2368
     * In case of a clock discontinuity, we need to wake up the Render thread,
2369
     * in order to trigger the rendering of the next picture, if new timings
2370
     * require it. */
2371
0
    vout_control_Wake(&sys->control);
2372
0
}
2373
2374
int vout_Request(const vout_configuration_t *cfg, vlc_video_context *vctx, input_thread_t *input)
2375
0
{
2376
0
    vout_thread_sys_t *vout = VOUT_THREAD_TO_SYS(cfg->vout);
2377
0
    vout_thread_sys_t *sys = vout;
2378
2379
0
    assert(cfg->fmt != NULL);
2380
0
    assert(cfg->clock != NULL);
2381
2382
0
    if (!VoutCheckFormat(cfg->fmt)) {
2383
0
        if (sys->display != NULL)
2384
0
            vout_StopDisplay(cfg->vout);
2385
0
        return -1;
2386
0
    }
2387
2388
0
    video_format_t original;
2389
0
    VoutFixFormat(&original, cfg->fmt);
2390
2391
0
    if (vout_ChangeSource(cfg->vout, &original, vctx) == 0)
2392
0
    {
2393
0
        video_format_Clean(&original);
2394
0
        return 0;
2395
0
    }
2396
2397
0
    vlc_mutex_lock(&sys->window_lock);
2398
0
    video_format_Clean(&sys->original);
2399
0
    sys->original = original;
2400
0
    sys->displayed.projection = original.projection_mode;
2401
0
    vout_InitSource(vout);
2402
2403
0
    if (EnableWindowLocked(vout, &original) != 0)
2404
0
    {
2405
        /* the window was not enabled, nor the display started */
2406
0
        msg_Err(cfg->vout, "failed to enable window");
2407
0
        vlc_mutex_unlock(&sys->window_lock);
2408
0
        assert(sys->display == NULL);
2409
0
        return -1;
2410
0
    }
2411
0
    vlc_mutex_unlock(&sys->window_lock);
2412
2413
0
    if (sys->display != NULL)
2414
0
        vout_StopDisplay(cfg->vout);
2415
2416
0
    vout_ReinitInterlacingSupport(cfg->vout, &sys->interlacing);
2417
2418
0
    sys->delay = 0;
2419
0
    sys->rate = 1.f;
2420
0
    sys->str_id = cfg->str_id;
2421
0
    sys->clock_id = 0;
2422
2423
0
    vlc_mutex_lock(&sys->clock_lock);
2424
0
    sys->clock = cfg->clock;
2425
0
    vlc_mutex_unlock(&sys->clock_lock);
2426
2427
0
    static const struct vlc_clock_event_cbs clock_event_cbs = {
2428
0
        .on_discontinuity = clock_event_OnDiscontinuity,
2429
0
    };
2430
0
    vlc_clock_Lock(sys->clock);
2431
0
    sys->clock_listener_id =
2432
0
        vlc_clock_AddListener(sys->clock, &clock_event_cbs, vout);
2433
0
    vlc_clock_Unlock(sys->clock);
2434
2435
0
    sys->delay = 0;
2436
2437
0
    if (vout_Start(vout, vctx, cfg))
2438
0
    {
2439
0
        msg_Err(cfg->vout, "video output display creation failed");
2440
0
        goto error_display;
2441
0
    }
2442
0
    atomic_store(&sys->control_is_terminated, false);
2443
0
    if (vlc_clone(&sys->thread, Thread, vout))
2444
0
        goto error_thread;
2445
2446
0
    if (input != NULL && sys->spu)
2447
0
        spu_Attach(sys->spu, input);
2448
0
    vout_IntfReinit(cfg->vout);
2449
0
    return 0;
2450
2451
0
error_thread:
2452
0
    vout_ReleaseDisplay(vout);
2453
0
error_display:
2454
0
    vout_DisableWindow(vout);
2455
0
    if (sys->clock_listener_id != NULL)
2456
0
    {
2457
0
        vlc_clock_Lock(sys->clock);
2458
0
        vlc_clock_RemoveListener(sys->clock, sys->clock_listener_id);
2459
0
        vlc_clock_Unlock(sys->clock);
2460
0
    }
2461
0
    sys->clock_listener_id = NULL;
2462
0
    vlc_mutex_lock(&sys->clock_lock);
2463
0
    sys->clock = NULL;
2464
0
    vlc_mutex_unlock(&sys->clock_lock);
2465
0
    return -1;
2466
0
}
2467
2468
vlc_decoder_device *vout_GetDevice(vout_thread_t *vout)
2469
0
{
2470
0
    vlc_decoder_device *dec_device = NULL;
2471
2472
0
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
2473
2474
0
    vlc_mutex_lock(&sys->window_lock);
2475
0
    if (sys->dec_device == NULL)
2476
0
        sys->dec_device = vlc_decoder_device_Create(&vout->obj, sys->display_cfg.window);
2477
0
    dec_device = sys->dec_device ? vlc_decoder_device_Hold( sys->dec_device ) : NULL;
2478
0
    vlc_mutex_unlock(&sys->window_lock);
2479
0
    return dec_device;
2480
0
}