1 /*****************************************************************************
2 * video_output.c : video output thread
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-2007 VLC authors and VideoLAN
11 * Authors: Vincent Seguin <seguin@via.ecp.fr>
12 * Gildas Bazin <gbazin@videolan.org>
13 * Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
15 * This program is free software; you can redistribute it and/or modify it
16 * under the terms of the GNU Lesser General Public License as published by
17 * the Free Software Foundation; either version 2.1 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU Lesser General Public License for more details.
25 * You should have received a copy of the GNU Lesser General Public License
26 * along with this program; if not, write to the Free Software Foundation,
27 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
28 *****************************************************************************/
30 /*****************************************************************************
32 *****************************************************************************/
37 #include <vlc_common.h>
39 #include <stdlib.h> /* free() */
45 #include <vlc_filter.h>
47 #include <vlc_vout_osd.h>
48 #include <vlc_image.h>
49 #include <vlc_plugin.h>
52 #include "vout_internal.h"
53 #include "interlacing.h"
56 #include "../misc/variables.h"
58 /*****************************************************************************
60 *****************************************************************************/
61 static void *Thread(void *);
62 static void VoutDestructor(vlc_object_t
*);
64 /* Maximum delay between 2 displayed pictures.
65 * XXX it is needed for now but should be removed in the long term.
67 #define VOUT_REDISPLAY_DELAY VLC_TICK_FROM_MS(80)
70 * Late pictures having a delay higher than this value are thrashed.
72 #define VOUT_DISPLAY_LATE_THRESHOLD VLC_TICK_FROM_MS(20)
74 /* Better be in advance when awakening than late... */
75 #define VOUT_MWAIT_TOLERANCE VLC_TICK_FROM_MS(4)
78 static int VoutValidateFormat(video_format_t
*dst
,
79 const video_format_t
*src
)
81 if (src
->i_width
== 0 || src
->i_width
> 8192 ||
82 src
->i_height
== 0 || src
->i_height
> 8192)
84 if (src
->i_sar_num
<= 0 || src
->i_sar_den
<= 0)
88 video_format_Copy(dst
, src
);
89 dst
->i_chroma
= vlc_fourcc_GetCodec(VIDEO_ES
, src
->i_chroma
);
90 vlc_ureduce( &dst
->i_sar_num
, &dst
->i_sar_den
,
91 src
->i_sar_num
, src
->i_sar_den
, 50000 );
92 if (dst
->i_sar_num
<= 0 || dst
->i_sar_den
<= 0) {
96 video_format_FixRgb(dst
);
99 static void VideoFormatCopyCropAr(video_format_t
*dst
,
100 const video_format_t
*src
)
102 video_format_CopyCrop(dst
, src
);
103 dst
->i_sar_num
= src
->i_sar_num
;
104 dst
->i_sar_den
= src
->i_sar_den
;
106 static bool VideoFormatIsCropArEqual(video_format_t
*dst
,
107 const video_format_t
*src
)
109 return dst
->i_sar_num
* src
->i_sar_den
== dst
->i_sar_den
* src
->i_sar_num
&&
110 dst
->i_x_offset
== src
->i_x_offset
&&
111 dst
->i_y_offset
== src
->i_y_offset
&&
112 dst
->i_visible_width
== src
->i_visible_width
&&
113 dst
->i_visible_height
== src
->i_visible_height
;
116 static vout_thread_t
*VoutCreate(vlc_object_t
*object
,
117 const vout_configuration_t
*cfg
,
118 input_thread_t
*input
)
120 video_format_t original
;
121 if (VoutValidateFormat(&original
, cfg
->fmt
))
124 /* Allocate descriptor */
125 vout_thread_t
*vout
= vlc_custom_create(object
,
126 sizeof(*vout
) + sizeof(*vout
->p
),
129 video_format_Clean(&original
);
134 vout
->p
= (vout_thread_sys_t
*)&vout
[1];
136 vout
->p
->original
= original
;
137 vout
->p
->dpb_size
= cfg
->dpb_size
;
138 vout
->p
->mouse_event
= cfg
->mouse_event
;
139 vout
->p
->opaque
= cfg
->opaque
;
140 vout
->p
->dead
= false;
141 vout
->p
->is_late_dropped
= var_InheritBool(vout
, "drop-late-frames");
142 vout
->p
->pause
.is_on
= false;
143 vout
->p
->pause
.date
= VLC_TICK_INVALID
;
145 vout_control_Init(&vout
->p
->control
);
146 vout_statistic_Init(&vout
->p
->statistic
);
147 vout_snapshot_Init(&vout
->p
->snapshot
);
148 vout_chrono_Init(&vout
->p
->render
, 5, VLC_TICK_FROM_MS(10)); /* Arbitrary initial time */
150 /* Initialize locks */
151 vlc_mutex_init(&vout
->p
->filter
.lock
);
152 vlc_mutex_init(&vout
->p
->spu_lock
);
153 vlc_mutex_init(&vout
->p
->window_lock
);
155 /* Take care of some "interface/control" related initialisations */
158 /* Initialize subpicture unit */
159 vout
->p
->spu
= spu_Create(vout
, vout
);
161 vout
->p
->title
.show
= var_InheritBool(vout
, "video-title-show");
162 vout
->p
->title
.timeout
= var_InheritInteger(vout
, "video-title-timeout");
163 vout
->p
->title
.position
= var_InheritInteger(vout
, "video-title-position");
165 /* Get splitter name if present */
166 vout
->p
->splitter_name
= var_InheritString(vout
, "video-splitter");
169 vout_InitInterlacingSupport(vout
, vout
->p
->displayed
.is_interlaced
);
172 if (vout
->p
->splitter_name
== NULL
) {
173 vout_window_cfg_t wcfg
= {
174 .is_standalone
= !var_InheritBool(vout
, "embedded-video"),
175 .is_fullscreen
= var_GetBool(vout
, "fullscreen"),
176 .is_decorated
= var_InheritBool(vout
, "video-deco"),
177 // TODO: take pixel A/R, crop and zoom into account
179 .x
= var_InheritInteger(vout
, "video-x"),
180 .y
= var_InheritInteger(vout
, "video-y"),
182 .width
= cfg
->fmt
->i_visible_width
,
183 .height
= cfg
->fmt
->i_visible_height
,
186 vout_window_t
*window
= vout_display_window_New(vout
, &wcfg
);
187 if (unlikely(window
== NULL
)) {
188 spu_Destroy(vout
->p
->spu
);
189 vlc_object_release(vout
);
193 if (var_InheritBool(vout
, "video-wallpaper"))
194 vout_window_SetState(window
, VOUT_WINDOW_STATE_BELOW
);
195 else if (var_InheritBool(vout
, "video-on-top"))
196 vout_window_SetState(window
, VOUT_WINDOW_STATE_ABOVE
);
198 vout
->p
->window
= window
;
200 vout
->p
->window
= NULL
;
203 vlc_object_set_destructor(vout
, VoutDestructor
);
206 if (vlc_clone(&vout
->p
->thread
, Thread
, vout
,
207 VLC_THREAD_PRIORITY_OUTPUT
)) {
208 if (vout
->p
->window
!= NULL
)
209 vout_display_window_Delete(vout
->p
->window
);
210 spu_Destroy(vout
->p
->spu
);
211 vlc_object_release(vout
);
215 vout_control_WaitEmpty(&vout
->p
->control
);
218 msg_Err(vout
, "video output creation failed");
219 vout_CloseAndRelease(vout
);
223 vout
->p
->input
= input
;
225 spu_Attach(vout
->p
->spu
, input
, true);
231 vout_thread_t
*vout_Request(vlc_object_t
*object
,
232 const vout_configuration_t
*cfg
,
233 input_thread_t
*input
)
235 vout_thread_t
*vout
= cfg
->vout
;
237 /* If a vout is provided, try reusing it */
239 if (vout
->p
->input
!= input
) {
241 spu_Attach(vout
->p
->spu
, vout
->p
->input
, false);
242 vout
->p
->input
= input
;
244 spu_Attach(vout
->p
->spu
, vout
->p
->input
, true);
247 vout_control_cmd_t cmd
;
248 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_REINIT
);
250 vout_control_Push(&vout
->p
->control
, &cmd
);
251 vout_control_WaitEmpty(&vout
->p
->control
);
254 vout_IntfReinit(vout
);
256 if (!vout
->p
->dead
) {
257 msg_Dbg(object
, "reusing provided vout");
260 vout_CloseAndRelease(vout
);
262 msg_Warn(object
, "cannot reuse provided vout");
264 return VoutCreate(object
, cfg
, input
);
267 void vout_Close(vout_thread_t
*vout
)
272 spu_Attach(vout
->p
->spu
, vout
->p
->input
, false);
274 vout_snapshot_End(&vout
->p
->snapshot
);
276 vout_control_PushVoid(&vout
->p
->control
, VOUT_CONTROL_CLEAN
);
277 vlc_join(vout
->p
->thread
, NULL
);
279 vout_chrono_Clean(&vout
->p
->render
);
281 vlc_mutex_lock(&vout
->p
->window_lock
);
282 if (vout
->p
->window
!= NULL
) {
283 vout_display_window_Delete(vout
->p
->window
);
284 vout
->p
->window
= NULL
;
286 vlc_mutex_unlock(&vout
->p
->window_lock
);
288 vlc_mutex_lock(&vout
->p
->spu_lock
);
289 spu_Destroy(vout
->p
->spu
);
291 vlc_mutex_unlock(&vout
->p
->spu_lock
);
295 static void VoutDestructor(vlc_object_t
*object
)
297 vout_thread_t
*vout
= (vout_thread_t
*)object
;
299 /* Make sure the vout was stopped first */
300 //assert(!vout->p_module);
302 free(vout
->p
->splitter_name
);
304 /* Destroy the locks */
305 vlc_mutex_destroy(&vout
->p
->window_lock
);
306 vlc_mutex_destroy(&vout
->p
->spu_lock
);
307 vlc_mutex_destroy(&vout
->p
->filter
.lock
);
308 vout_control_Clean(&vout
->p
->control
);
311 vout_statistic_Clean(&vout
->p
->statistic
);
314 vout_snapshot_Clean(&vout
->p
->snapshot
);
316 video_format_Clean(&vout
->p
->original
);
320 void vout_Cancel(vout_thread_t
*vout
, bool canceled
)
322 vout_control_PushBool(&vout
->p
->control
, VOUT_CONTROL_CANCEL
, canceled
);
323 vout_control_WaitEmpty(&vout
->p
->control
);
326 void vout_ChangePause(vout_thread_t
*vout
, bool is_paused
, vlc_tick_t date
)
328 vout_control_cmd_t cmd
;
329 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_PAUSE
);
330 cmd
.pause
.is_on
= is_paused
;
331 cmd
.pause
.date
= date
;
332 vout_control_Push(&vout
->p
->control
, &cmd
);
334 vout_control_WaitEmpty(&vout
->p
->control
);
337 void vout_GetResetStatistic(vout_thread_t
*vout
, unsigned *restrict displayed
,
338 unsigned *restrict lost
)
340 vout_statistic_GetReset( &vout
->p
->statistic
, displayed
, lost
);
343 void vout_Flush(vout_thread_t
*vout
, vlc_tick_t date
)
345 vout_control_PushTime(&vout
->p
->control
, VOUT_CONTROL_FLUSH
, date
);
346 vout_control_WaitEmpty(&vout
->p
->control
);
349 bool vout_IsEmpty(vout_thread_t
*vout
)
351 picture_t
*picture
= picture_fifo_Peek(vout
->p
->decoder_fifo
);
353 picture_Release(picture
);
358 void vout_NextPicture(vout_thread_t
*vout
, vlc_tick_t
*duration
)
360 vout_control_cmd_t cmd
;
361 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_STEP
);
362 cmd
.time_ptr
= duration
;
364 vout_control_Push(&vout
->p
->control
, &cmd
);
365 vout_control_WaitEmpty(&vout
->p
->control
);
368 void vout_DisplayTitle(vout_thread_t
*vout
, const char *title
)
372 if (!vout
->p
->title
.show
)
375 vout_OSDText(vout
, VOUT_SPU_CHANNEL_OSD
, vout
->p
->title
.position
,
376 VLC_TICK_FROM_MS(vout
->p
->title
.timeout
), title
);
379 void vout_MouseState(vout_thread_t
*vout
, const vlc_mouse_t
*mouse
)
382 vout_control_cmd_t cmd
;
383 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_MOUSE_STATE
);
386 vout_control_Push(&vout
->p
->control
, &cmd
);
389 void vout_PutSubpicture( vout_thread_t
*vout
, subpicture_t
*subpic
)
391 vout_control_cmd_t cmd
;
392 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_SUBPICTURE
);
393 cmd
.subpicture
= subpic
;
395 vout_control_Push(&vout
->p
->control
, &cmd
);
397 int vout_RegisterSubpictureChannel( vout_thread_t
*vout
)
399 int channel
= VOUT_SPU_CHANNEL_AVAIL_FIRST
;
401 vlc_mutex_lock(&vout
->p
->spu_lock
);
403 channel
= spu_RegisterChannel(vout
->p
->spu
);
404 vlc_mutex_unlock(&vout
->p
->spu_lock
);
408 void vout_FlushSubpictureChannel( vout_thread_t
*vout
, int channel
)
410 vout_control_PushInteger(&vout
->p
->control
, VOUT_CONTROL_FLUSH_SUBPICTURE
,
413 void vout_SetSpuHighlight( vout_thread_t
*vout
,
414 const vlc_spu_highlight_t
*spu_hl
)
416 vlc_mutex_lock(&vout
->p
->spu_lock
);
418 spu_SetHighlight(vout
->p
->spu
, spu_hl
);
419 vlc_mutex_unlock(&vout
->p
->spu_lock
);
423 * Allocates a video output picture buffer.
425 * Either vout_PutPicture() or picture_Release() must be used to return the
426 * buffer to the video output free buffer pool.
428 * You may use picture_Hold() (paired with picture_Release()) to keep a
429 * read-only reference.
431 picture_t
*vout_GetPicture(vout_thread_t
*vout
)
433 picture_t
*picture
= picture_pool_Wait(vout
->p
->decoder_pool
);
434 if (likely(picture
!= NULL
)) {
435 picture_Reset(picture
);
436 VideoFormatCopyCropAr(&picture
->format
, &vout
->p
->original
);
442 * It gives to the vout a picture to be displayed.
444 * The given picture MUST comes from vout_GetPicture.
446 * Becareful, after vout_PutPicture is called, picture_t::p_next cannot be
449 void vout_PutPicture(vout_thread_t
*vout
, picture_t
*picture
)
451 picture
->p_next
= NULL
;
452 if (picture_pool_OwnsPic(vout
->p
->decoder_pool
, picture
))
454 picture_fifo_Push(vout
->p
->decoder_fifo
, picture
);
456 vout_control_Wake(&vout
->p
->control
);
460 /* FIXME: HACK: Drop this picture because the vout changed. The old
461 * picture pool need to be kept by the new vout. This requires a major
462 * "vout display" API change. */
463 picture_Release(picture
);
468 int vout_GetSnapshot(vout_thread_t
*vout
,
469 block_t
**image_dst
, picture_t
**picture_dst
,
471 const char *type
, vlc_tick_t timeout
)
473 picture_t
*picture
= vout_snapshot_Get(&vout
->p
->snapshot
, timeout
);
475 msg_Err(vout
, "Failed to grab a snapshot");
480 vlc_fourcc_t codec
= VLC_CODEC_PNG
;
481 if (type
&& image_Type2Fourcc(type
))
482 codec
= image_Type2Fourcc(type
);
484 const int override_width
= var_InheritInteger(vout
, "snapshot-width");
485 const int override_height
= var_InheritInteger(vout
, "snapshot-height");
487 if (picture_Export(VLC_OBJECT(vout
), image_dst
, fmt
,
488 picture
, codec
, override_width
, override_height
)) {
489 msg_Err(vout
, "Failed to convert image for snapshot");
490 picture_Release(picture
);
495 *picture_dst
= picture
;
497 picture_Release(picture
);
501 void vout_ChangeAspectRatio( vout_thread_t
*p_vout
,
502 unsigned int i_num
, unsigned int i_den
)
504 vout_ControlChangeSampleAspectRatio( p_vout
, i_num
, i_den
);
507 /* vout_Control* are usable by anyone at anytime */
508 void vout_ControlChangeFullscreen(vout_thread_t
*vout
, const char *id
)
510 vout_window_t
*window
;
512 vlc_mutex_lock(&vout
->p
->window_lock
);
513 window
= vout
->p
->window
;
514 /* Window is NULL if the output is a splitter,
515 * or if the output was already closed by its owner.
518 vout_window_SetFullScreen(window
, id
);
519 vlc_mutex_unlock(&vout
->p
->window_lock
);
522 void vout_ControlChangeWindowed(vout_thread_t
*vout
)
524 vout_window_t
*window
;
526 vlc_mutex_lock(&vout
->p
->window_lock
);
527 window
= vout
->p
->window
;
529 vout_window_UnsetFullScreen(window
);
530 vlc_mutex_unlock(&vout
->p
->window_lock
);
533 void vout_ControlChangeWindowState(vout_thread_t
*vout
, unsigned st
)
535 vout_window_t
*window
;
537 vlc_mutex_lock(&vout
->p
->window_lock
);
538 window
= vout
->p
->window
;
540 vout_window_SetState(window
, st
);
541 vlc_mutex_unlock(&vout
->p
->window_lock
);
544 static void vout_ControlUpdateWindowSize(vout_thread_t
*vout
)
546 vlc_mutex_lock(&vout
->p
->window_lock
);
547 if (vout
->p
->window
!= NULL
)
548 vout_display_window_UpdateSize(vout
->p
->window
, &vout
->p
->original
);
549 vlc_mutex_unlock(&vout
->p
->window_lock
);
552 void vout_ControlChangeDisplaySize(vout_thread_t
*vout
,
553 unsigned width
, unsigned height
)
555 vout_control_cmd_t cmd
;
557 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_DISPLAY_SIZE
);
560 cmd
.window
.width
= width
;
561 cmd
.window
.height
= height
;
562 vout_control_Push(&vout
->p
->control
, &cmd
);
564 void vout_ControlChangeDisplayFilled(vout_thread_t
*vout
, bool is_filled
)
566 vout_control_PushBool(&vout
->p
->control
, VOUT_CONTROL_DISPLAY_FILLED
,
570 void vout_ControlChangeZoom(vout_thread_t
*vout
, int num
, int den
)
572 if (num
* 10 < den
) {
575 } else if (num
> den
* 10) {
579 vout_ControlUpdateWindowSize(vout
);
580 vout_control_PushPair(&vout
->p
->control
, VOUT_CONTROL_ZOOM
,
584 void vout_ControlChangeSampleAspectRatio(vout_thread_t
*vout
,
585 unsigned num
, unsigned den
)
587 vout_ControlUpdateWindowSize(vout
);
588 vout_control_PushPair(&vout
->p
->control
, VOUT_CONTROL_ASPECT_RATIO
,
592 void vout_ControlChangeCropRatio(vout_thread_t
*vout
,
593 unsigned num
, unsigned den
)
595 vout_ControlUpdateWindowSize(vout
);
596 vout_control_PushPair(&vout
->p
->control
, VOUT_CONTROL_CROP_RATIO
,
600 void vout_ControlChangeCropWindow(vout_thread_t
*vout
,
601 int x
, int y
, int width
, int height
)
603 vout_control_cmd_t cmd
;
605 vout_ControlUpdateWindowSize(vout
);
607 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_CROP_WINDOW
);
608 cmd
.window
.x
= __MAX(x
, 0);
609 cmd
.window
.y
= __MAX(y
, 0);
610 cmd
.window
.width
= __MAX(width
, 0);
611 cmd
.window
.height
= __MAX(height
, 0);
612 vout_control_Push(&vout
->p
->control
, &cmd
);
615 void vout_ControlChangeCropBorder(vout_thread_t
*vout
,
616 int left
, int top
, int right
, int bottom
)
618 vout_control_cmd_t cmd
;
620 vout_ControlUpdateWindowSize(vout
);
622 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_CROP_BORDER
);
623 cmd
.border
.left
= __MAX(left
, 0);
624 cmd
.border
.top
= __MAX(top
, 0);
625 cmd
.border
.right
= __MAX(right
, 0);
626 cmd
.border
.bottom
= __MAX(bottom
, 0);
627 vout_control_Push(&vout
->p
->control
, &cmd
);
630 void vout_ControlChangeFilters(vout_thread_t
*vout
, const char *filters
)
632 vout_control_PushString(&vout
->p
->control
, VOUT_CONTROL_CHANGE_FILTERS
,
636 void vout_ControlChangeSubSources(vout_thread_t
*vout
, const char *filters
)
638 vlc_mutex_lock(&vout
->p
->spu_lock
);
639 if (likely(vout
->p
->spu
!= NULL
))
640 spu_ChangeSources(vout
->p
->spu
, filters
);
641 vlc_mutex_unlock(&vout
->p
->spu_lock
);
644 void vout_ControlChangeSubFilters(vout_thread_t
*vout
, const char *filters
)
646 vlc_mutex_lock(&vout
->p
->spu_lock
);
647 if (likely(vout
->p
->spu
!= NULL
))
648 spu_ChangeFilters(vout
->p
->spu
, filters
);
649 vlc_mutex_unlock(&vout
->p
->spu_lock
);
652 void vout_ControlChangeSubMargin(vout_thread_t
*vout
, int margin
)
654 vlc_mutex_lock(&vout
->p
->spu_lock
);
655 if (likely(vout
->p
->spu
!= NULL
))
656 spu_ChangeMargin(vout
->p
->spu
, margin
);
657 vlc_mutex_unlock(&vout
->p
->spu_lock
);
660 void vout_ControlChangeViewpoint(vout_thread_t
*vout
,
661 const vlc_viewpoint_t
*p_viewpoint
)
663 vout_control_cmd_t cmd
;
664 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_VIEWPOINT
);
665 cmd
.viewpoint
= *p_viewpoint
;
666 vout_control_Push(&vout
->p
->control
, &cmd
);
670 static void VoutGetDisplayCfg(vout_thread_t
*vout
, vout_display_cfg_t
*cfg
)
672 /* Load configuration */
673 cfg
->window
= vout
->p
->window
;
674 #if defined(_WIN32) || defined(__OS2__)
675 cfg
->is_fullscreen
= var_GetBool(vout
, "fullscreen")
676 || var_GetBool(vout
, "video-wallpaper");
678 cfg
->viewpoint
= vout
->p
->original
.pose
;
680 const int display_width
= var_GetInteger(vout
, "width");
681 const int display_height
= var_GetInteger(vout
, "height");
682 cfg
->display
.width
= display_width
> 0 ? display_width
: 0;
683 cfg
->display
.height
= display_height
> 0 ? display_height
: 0;
684 cfg
->is_display_filled
= var_GetBool(vout
, "autoscale");
685 unsigned msar_num
, msar_den
;
686 if (var_InheritURational(vout
, &msar_num
, &msar_den
, "monitor-par") ||
687 msar_num
<= 0 || msar_den
<= 0) {
691 cfg
->display
.sar
.num
= msar_num
;
692 cfg
->display
.sar
.den
= msar_den
;
693 unsigned zoom_den
= 1000;
694 unsigned zoom_num
= zoom_den
* var_GetFloat(vout
, "zoom");
695 vlc_ureduce(&zoom_num
, &zoom_den
, zoom_num
, zoom_den
, 0);
696 cfg
->zoom
.num
= zoom_num
;
697 cfg
->zoom
.den
= zoom_den
;
698 cfg
->align
.vertical
= VOUT_DISPLAY_ALIGN_CENTER
;
699 cfg
->align
.horizontal
= VOUT_DISPLAY_ALIGN_CENTER
;
700 const int align_mask
= var_GetInteger(vout
, "align");
701 if (align_mask
& 0x1)
702 cfg
->align
.horizontal
= VOUT_DISPLAY_ALIGN_LEFT
;
703 else if (align_mask
& 0x2)
704 cfg
->align
.horizontal
= VOUT_DISPLAY_ALIGN_RIGHT
;
705 if (align_mask
& 0x4)
706 cfg
->align
.vertical
= VOUT_DISPLAY_ALIGN_TOP
;
707 else if (align_mask
& 0x8)
708 cfg
->align
.vertical
= VOUT_DISPLAY_ALIGN_BOTTOM
;
712 static int FilterRestartCallback(vlc_object_t
*p_this
, char const *psz_var
,
713 vlc_value_t oldval
, vlc_value_t newval
,
716 (void) p_this
; (void) psz_var
; (void) oldval
; (void) newval
;
717 vout_ControlChangeFilters((vout_thread_t
*)p_data
, NULL
);
721 static int ThreadDelFilterCallbacks(filter_t
*filter
, void *opaque
)
723 filter_DelProxyCallbacks((vlc_object_t
*)opaque
, filter
,
724 FilterRestartCallback
);
728 static void ThreadDelAllFilterCallbacks(vout_thread_t
*vout
)
730 assert(vout
->p
->filter
.chain_interactive
!= NULL
);
731 filter_chain_ForEach(vout
->p
->filter
.chain_interactive
,
732 ThreadDelFilterCallbacks
, vout
);
735 static picture_t
*VoutVideoFilterInteractiveNewPicture(filter_t
*filter
)
737 vout_thread_t
*vout
= filter
->owner
.sys
;
739 picture_t
*picture
= picture_pool_Get(vout
->p
->private_pool
);
741 picture_Reset(picture
);
742 VideoFormatCopyCropAr(&picture
->format
, &filter
->fmt_out
.video
);
747 static picture_t
*VoutVideoFilterStaticNewPicture(filter_t
*filter
)
749 vout_thread_t
*vout
= filter
->owner
.sys
;
751 vlc_mutex_assert(&vout
->p
->filter
.lock
);
752 if (filter_chain_IsEmpty(vout
->p
->filter
.chain_interactive
))
753 return VoutVideoFilterInteractiveNewPicture(filter
);
755 return picture_NewFromFormat(&filter
->fmt_out
.video
);
758 static void ThreadFilterFlush(vout_thread_t
*vout
, bool is_locked
)
760 if (vout
->p
->displayed
.current
)
761 picture_Release( vout
->p
->displayed
.current
);
762 vout
->p
->displayed
.current
= NULL
;
764 if (vout
->p
->displayed
.next
)
765 picture_Release( vout
->p
->displayed
.next
);
766 vout
->p
->displayed
.next
= NULL
;
769 vlc_mutex_lock(&vout
->p
->filter
.lock
);
770 filter_chain_VideoFlush(vout
->p
->filter
.chain_static
);
771 filter_chain_VideoFlush(vout
->p
->filter
.chain_interactive
);
773 vlc_mutex_unlock(&vout
->p
->filter
.lock
);
781 static void ThreadChangeFilters(vout_thread_t
*vout
,
782 const video_format_t
*source
,
787 ThreadFilterFlush(vout
, is_locked
);
788 ThreadDelAllFilterCallbacks(vout
);
790 vlc_array_t array_static
;
791 vlc_array_t array_interactive
;
793 vlc_array_init(&array_static
);
794 vlc_array_init(&array_interactive
);
796 vout
->p
->filter
.has_deint
=
797 deinterlace
== 1 || (deinterlace
== -1 && vout
->p
->filter
.has_deint
);
799 if (vout
->p
->filter
.has_deint
)
801 vout_filter_t
*e
= malloc(sizeof(*e
));
805 free(config_ChainCreate(&e
->name
, &e
->cfg
, "deinterlace"));
806 vlc_array_append_or_abort(&array_static
, e
);
810 char *current
= filters
? strdup(filters
) : NULL
;
814 char *next
= config_ChainCreate(&name
, &cfg
, current
);
817 vout_filter_t
*e
= malloc(sizeof(*e
));
822 if (!strcmp(e
->name
, "postproc"))
823 vlc_array_append_or_abort(&array_static
, e
);
825 vlc_array_append_or_abort(&array_interactive
, e
);
829 config_ChainDestroy(cfg
);
834 config_ChainDestroy(cfg
);
842 vlc_mutex_lock(&vout
->p
->filter
.lock
);
844 es_format_t fmt_target
;
845 es_format_InitFromVideo(&fmt_target
, source
? source
: &vout
->p
->filter
.format
);
847 const es_format_t
*p_fmt_current
= &fmt_target
;
849 for (int a
= 0; a
< 2; a
++) {
850 vlc_array_t
*array
= a
== 0 ? &array_static
:
852 filter_chain_t
*chain
= a
== 0 ? vout
->p
->filter
.chain_static
:
853 vout
->p
->filter
.chain_interactive
;
855 filter_chain_Reset(chain
, p_fmt_current
, p_fmt_current
);
856 for (size_t i
= 0; i
< vlc_array_count(array
); i
++) {
857 vout_filter_t
*e
= vlc_array_item_at_index(array
, i
);
858 msg_Dbg(vout
, "Adding '%s' as %s", e
->name
, a
== 0 ? "static" : "interactive");
859 filter_t
*filter
= filter_chain_AppendFilter(chain
, e
->name
, e
->cfg
,
863 msg_Err(vout
, "Failed to add filter '%s'", e
->name
);
864 config_ChainDestroy(e
->cfg
);
866 else if (a
== 1) /* Add callbacks for interactive filters */
867 filter_AddProxyCallbacks(vout
, filter
, FilterRestartCallback
);
872 p_fmt_current
= filter_chain_GetFmtOut(chain
);
873 vlc_array_clear(array
);
876 if (!es_format_IsSimilar(p_fmt_current
, &fmt_target
)) {
877 msg_Dbg(vout
, "Adding a filter to compensate for format changes");
878 if (filter_chain_AppendConverter(vout
->p
->filter
.chain_interactive
,
879 p_fmt_current
, &fmt_target
) != 0) {
880 msg_Err(vout
, "Failed to compensate for the format changes, removing all filters");
881 ThreadDelAllFilterCallbacks(vout
);
882 filter_chain_Reset(vout
->p
->filter
.chain_static
, &fmt_target
, &fmt_target
);
883 filter_chain_Reset(vout
->p
->filter
.chain_interactive
, &fmt_target
, &fmt_target
);
887 es_format_Clean(&fmt_target
);
889 if (vout
->p
->filter
.configuration
!= filters
) {
890 free(vout
->p
->filter
.configuration
);
891 vout
->p
->filter
.configuration
= filters
? strdup(filters
) : NULL
;
894 video_format_Clean(&vout
->p
->filter
.format
);
895 video_format_Copy(&vout
->p
->filter
.format
, source
);
899 vlc_mutex_unlock(&vout
->p
->filter
.lock
);
904 static int ThreadDisplayPreparePicture(vout_thread_t
*vout
, bool reuse
, bool frame_by_frame
)
906 bool is_late_dropped
= vout
->p
->is_late_dropped
&& !vout
->p
->pause
.is_on
&& !frame_by_frame
;
908 vlc_mutex_lock(&vout
->p
->filter
.lock
);
910 picture_t
*picture
= filter_chain_VideoFilter(vout
->p
->filter
.chain_static
, NULL
);
911 assert(!reuse
|| !picture
);
915 if (reuse
&& vout
->p
->displayed
.decoded
) {
916 decoded
= picture_Hold(vout
->p
->displayed
.decoded
);
918 decoded
= picture_fifo_Pop(vout
->p
->decoder_fifo
);
920 if (is_late_dropped
&& !decoded
->b_force
) {
921 vlc_tick_t late_threshold
;
922 if (decoded
->format
.i_frame_rate
&& decoded
->format
.i_frame_rate_base
)
923 late_threshold
= VLC_TICK_FROM_MS(500) * decoded
->format
.i_frame_rate_base
/ decoded
->format
.i_frame_rate
;
925 late_threshold
= VOUT_DISPLAY_LATE_THRESHOLD
;
926 const vlc_tick_t predicted
= vlc_tick_now() + 0; /* TODO improve */
927 const vlc_tick_t late
= predicted
- decoded
->date
;
928 if (late
> late_threshold
) {
929 msg_Warn(vout
, "picture is too late to be displayed (missing %"PRId64
" ms)", MS_FROM_VLC_TICK(late
));
930 picture_Release(decoded
);
931 vout_statistic_AddLost(&vout
->p
->statistic
, 1);
933 } else if (late
> 0) {
934 msg_Dbg(vout
, "picture might be displayed late (missing %"PRId64
" ms)", MS_FROM_VLC_TICK(late
));
937 if (!VideoFormatIsCropArEqual(&decoded
->format
, &vout
->p
->filter
.format
))
938 ThreadChangeFilters(vout
, &decoded
->format
, vout
->p
->filter
.configuration
, -1, true);
946 if (vout
->p
->displayed
.decoded
)
947 picture_Release(vout
->p
->displayed
.decoded
);
949 vout
->p
->displayed
.decoded
= picture_Hold(decoded
);
950 vout
->p
->displayed
.timestamp
= decoded
->date
;
951 vout
->p
->displayed
.is_interlaced
= !decoded
->b_progressive
;
953 picture
= filter_chain_VideoFilter(vout
->p
->filter
.chain_static
, decoded
);
956 vlc_mutex_unlock(&vout
->p
->filter
.lock
);
961 assert(!vout
->p
->displayed
.next
);
962 if (!vout
->p
->displayed
.current
)
963 vout
->p
->displayed
.current
= picture
;
965 vout
->p
->displayed
.next
= picture
;
969 static picture_t
*ConvertRGB32AndBlendBufferNew(filter_t
*filter
)
971 return picture_NewFromFormat(&filter
->fmt_out
.video
);
974 static picture_t
*ConvertRGB32AndBlend(vout_thread_t
*vout
, picture_t
*pic
,
975 subpicture_t
*subpic
)
977 /* This function will convert the pic to RGB32 and blend the subpic to it.
978 * The returned pic can't be used to display since the chroma will be
979 * different than the "vout display" one, but it can be used for snapshots.
982 assert(vout
->p
->spu_blend
);
984 static const struct filter_video_callbacks cbs
= {
985 .buffer_new
= ConvertRGB32AndBlendBufferNew
,
987 filter_owner_t owner
= {
990 filter_chain_t
*filterc
= filter_chain_NewVideo(vout
, false, &owner
);
994 es_format_t src
= vout
->p
->spu_blend
->fmt_out
;
995 es_format_t dst
= src
;
996 dst
.video
.i_chroma
= VLC_CODEC_RGB32
;
997 video_format_FixRgb(&dst
.video
);
999 if (filter_chain_AppendConverter(filterc
, &src
, &dst
) != 0)
1001 filter_chain_Delete(filterc
);
1006 pic
= filter_chain_VideoFilter(filterc
, pic
);
1007 filter_chain_Delete(filterc
);
1011 filter_t
*swblend
= filter_NewBlend(VLC_OBJECT(vout
), &dst
.video
);
1014 bool success
= picture_BlendSubpicture(pic
, swblend
, subpic
) > 0;
1015 filter_DeleteBlend(swblend
);
1019 picture_Release(pic
);
1024 static int ThreadDisplayRenderPicture(vout_thread_t
*vout
, bool is_forced
)
1026 vout_thread_sys_t
*sys
= vout
->p
;
1027 vout_display_t
*vd
= sys
->display
.vd
;
1029 picture_t
*torender
= picture_Hold(sys
->displayed
.current
);
1031 vout_chrono_Start(&sys
->render
);
1033 vlc_mutex_lock(&sys
->filter
.lock
);
1034 picture_t
*filtered
= filter_chain_VideoFilter(sys
->filter
.chain_interactive
, torender
);
1035 vlc_mutex_unlock(&sys
->filter
.lock
);
1038 return VLC_EGENERIC
;
1040 if (filtered
->date
!= sys
->displayed
.current
->date
)
1041 msg_Warn(vout
, "Unsupported timestamp modifications done by chain_interactive");
1044 * Get the subpicture to be displayed
1046 const bool do_snapshot
= vout_snapshot_IsRequested(&sys
->snapshot
);
1047 vlc_tick_t render_subtitle_date
;
1048 if (sys
->pause
.is_on
)
1049 render_subtitle_date
= sys
->pause
.date
;
1051 render_subtitle_date
= filtered
->date
> 1 ? filtered
->date
: vlc_tick_now();
1052 vlc_tick_t render_osd_date
= vlc_tick_now(); /* FIXME wrong */
1055 * Get the subpicture to be displayed
1057 const bool do_dr_spu
= !do_snapshot
&&
1058 vd
->info
.subpicture_chromas
&&
1059 *vd
->info
.subpicture_chromas
!= 0;
1061 //FIXME: Denying do_early_spu if vd->source.orientation != ORIENT_NORMAL
1062 //will have the effect that snapshots miss the subpictures. We do this
1063 //because there is currently no way to transform subpictures to match
1064 //the source format.
1065 const bool do_early_spu
= !do_dr_spu
&&
1066 vd
->source
.orientation
== ORIENT_NORMAL
&&
1067 (vd
->info
.is_slow
||
1068 sys
->display
.use_dr
||
1070 vd
->fmt
.i_width
* vd
->fmt
.i_height
<= vd
->source
.i_width
* vd
->source
.i_height
);
1072 const vlc_fourcc_t
*subpicture_chromas
;
1073 video_format_t fmt_spu
;
1075 vout_display_place_t place
;
1076 vout_display_PlacePicture(&place
, &vd
->source
, vd
->cfg
, false);
1078 fmt_spu
= vd
->source
;
1079 if (fmt_spu
.i_width
* fmt_spu
.i_height
< place
.width
* place
.height
) {
1080 fmt_spu
.i_sar_num
= vd
->cfg
->display
.sar
.num
;
1081 fmt_spu
.i_sar_den
= vd
->cfg
->display
.sar
.den
;
1083 fmt_spu
.i_visible_width
= place
.width
;
1085 fmt_spu
.i_visible_height
= place
.height
;
1087 subpicture_chromas
= vd
->info
.subpicture_chromas
;
1090 fmt_spu
= vd
->source
;
1093 fmt_spu
.i_sar_num
= vd
->cfg
->display
.sar
.num
;
1094 fmt_spu
.i_sar_den
= vd
->cfg
->display
.sar
.den
;
1096 subpicture_chromas
= NULL
;
1098 if (sys
->spu_blend
&&
1099 sys
->spu_blend
->fmt_out
.video
.i_chroma
!= fmt_spu
.i_chroma
) {
1100 filter_DeleteBlend(sys
->spu_blend
);
1101 sys
->spu_blend
= NULL
;
1102 sys
->spu_blend_chroma
= 0;
1104 if (!sys
->spu_blend
&& sys
->spu_blend_chroma
!= fmt_spu
.i_chroma
) {
1105 sys
->spu_blend_chroma
= fmt_spu
.i_chroma
;
1106 sys
->spu_blend
= filter_NewBlend(VLC_OBJECT(vout
), &fmt_spu
);
1107 if (!sys
->spu_blend
)
1108 msg_Err(vout
, "Failed to create blending filter, OSD/Subtitles will not work");
1112 video_format_t fmt_spu_rot
;
1113 video_format_ApplyRotation(&fmt_spu_rot
, &fmt_spu
);
1114 subpicture_t
*subpic
= spu_Render(sys
->spu
,
1115 subpicture_chromas
, &fmt_spu_rot
,
1117 render_subtitle_date
, render_osd_date
,
1123 * - be sure to end up with a direct buffer.
1124 * - blend subtitles, and in a fast access buffer
1126 bool is_direct
= sys
->decoder_pool
== sys
->display_pool
;
1127 picture_t
*todisplay
= filtered
;
1128 picture_t
*snap_pic
= todisplay
;
1129 if (do_early_spu
&& subpic
) {
1130 if (sys
->spu_blend
) {
1131 picture_t
*blent
= picture_pool_Get(sys
->private_pool
);
1133 VideoFormatCopyCropAr(&blent
->format
, &filtered
->format
);
1134 picture_Copy(blent
, filtered
);
1135 if (picture_BlendSubpicture(blent
, sys
->spu_blend
, subpic
)) {
1136 picture_Release(todisplay
);
1137 snap_pic
= todisplay
= blent
;
1140 /* Blending failed, likely because the picture is opaque or
1141 * read-only. Try to convert the opaque picture to a
1142 * software RGB32 one before blending it. */
1145 picture_t
*copy
= ConvertRGB32AndBlend(vout
, blent
, subpic
);
1149 picture_Release(blent
);
1153 subpicture_Delete(subpic
);
1157 assert(vout_IsDisplayFiltered(vd
) == !sys
->display
.use_dr
);
1158 if (sys
->display
.use_dr
&& !is_direct
) {
1159 picture_t
*direct
= NULL
;
1160 if (likely(sys
->display_pool
!= NULL
))
1161 direct
= picture_pool_Get(sys
->display_pool
);
1163 picture_Release(todisplay
);
1165 subpicture_Delete(subpic
);
1166 return VLC_EGENERIC
;
1169 /* The display uses direct rendering (no conversion), but its pool of
1170 * pictures is not usable by the decoder (too few, too slow or
1171 * subject to invalidation...). Since there are no filters, copying
1172 * pictures from the decoder to the output is unavoidable. */
1173 VideoFormatCopyCropAr(&direct
->format
, &todisplay
->format
);
1174 picture_Copy(direct
, todisplay
);
1175 picture_Release(todisplay
);
1176 snap_pic
= todisplay
= direct
;
1180 * Take a snapshot if requested
1185 vout_snapshot_Set(&sys
->snapshot
, &vd
->source
, snap_pic
);
1186 if (snap_pic
!= todisplay
)
1187 picture_Release(snap_pic
);
1190 /* Render the direct buffer */
1191 vout_UpdateDisplaySourceProperties(vd
, &todisplay
->format
);
1193 todisplay
= vout_FilterDisplay(vd
, todisplay
);
1194 if (todisplay
== NULL
) {
1196 subpicture_Delete(subpic
);
1197 return VLC_EGENERIC
;
1200 if (sys
->display
.use_dr
) {
1201 vout_display_Prepare(vd
, todisplay
, subpic
, todisplay
->date
);
1203 if (!do_dr_spu
&& !do_early_spu
&& sys
->spu_blend
&& subpic
)
1204 picture_BlendSubpicture(todisplay
, sys
->spu_blend
, subpic
);
1205 vout_display_Prepare(vd
, todisplay
, do_dr_spu
? subpic
: NULL
,
1208 if (!do_dr_spu
&& subpic
)
1210 subpicture_Delete(subpic
);
1215 vout_chrono_Stop(&sys
->render
);
1219 if (((i
++)%10) == 0)
1220 msg_Info(vout
, "render: avg %d ms var %d ms",
1221 (int)(sys
->render
.avg
/1000), (int)(sys
->render
.var
/1000));
1225 /* Wait the real date (for rendering jitter) */
1227 vlc_tick_t delay
= todisplay
->date
- vlc_tick_now();
1229 msg_Warn(vout
, "picture is late (%lld ms)", delay
/ 1000);
1232 vlc_tick_wait(todisplay
->date
);
1234 /* Display the direct buffer returned by vout_RenderPicture */
1235 sys
->displayed
.date
= vlc_tick_now();
1236 vout_display_Display(vd
, todisplay
);
1238 subpicture_Delete(subpic
);
1240 vout_statistic_AddDisplayed(&sys
->statistic
, 1);
1245 static int ThreadDisplayPicture(vout_thread_t
*vout
, vlc_tick_t
*deadline
)
1247 vout_thread_sys_t
*sys
= vout
->p
;
1248 bool frame_by_frame
= !deadline
;
1249 bool paused
= sys
->pause
.is_on
;
1250 bool first
= !sys
->displayed
.current
;
1253 if (ThreadDisplayPreparePicture(vout
, true, frame_by_frame
)) /* FIXME not sure it is ok */
1254 return VLC_EGENERIC
;
1256 if (!paused
|| frame_by_frame
)
1257 while (!sys
->displayed
.next
&& !ThreadDisplayPreparePicture(vout
, false, frame_by_frame
))
1260 const vlc_tick_t date
= vlc_tick_now();
1261 const vlc_tick_t render_delay
= vout_chrono_GetHigh(&sys
->render
) + VOUT_MWAIT_TOLERANCE
;
1263 bool drop_next_frame
= frame_by_frame
;
1264 vlc_tick_t date_next
= VLC_TICK_INVALID
;
1265 if (!paused
&& sys
->displayed
.next
) {
1266 date_next
= sys
->displayed
.next
->date
- render_delay
;
1267 if (date_next
/* + 0 FIXME */ <= date
)
1268 drop_next_frame
= true;
1271 /* FIXME/XXX we must redisplay the last decoded picture (because
1272 * of potential vout updated, or filters update or SPU update)
1273 * For now a high update period is needed but it could be removed
1275 * - vout module emits events from theselves.
1276 * - *and* SPU is modified to emit an event or a deadline when needed.
1278 * So it will be done later.
1280 bool refresh
= false;
1282 vlc_tick_t date_refresh
= VLC_TICK_INVALID
;
1283 if (sys
->displayed
.date
!= VLC_TICK_INVALID
) {
1284 date_refresh
= sys
->displayed
.date
+ VOUT_REDISPLAY_DELAY
- render_delay
;
1285 refresh
= date_refresh
<= date
;
1287 bool force_refresh
= !drop_next_frame
&& refresh
;
1289 if (!first
&& !refresh
&& !drop_next_frame
) {
1290 if (!frame_by_frame
) {
1291 if (date_refresh
!= VLC_TICK_INVALID
)
1292 *deadline
= date_refresh
;
1293 if (date_next
!= VLC_TICK_INVALID
&& date_next
< *deadline
)
1294 *deadline
= date_next
;
1296 return VLC_EGENERIC
;
1299 if (drop_next_frame
) {
1300 picture_Release(sys
->displayed
.current
);
1301 sys
->displayed
.current
= sys
->displayed
.next
;
1302 sys
->displayed
.next
= NULL
;
1305 if (!sys
->displayed
.current
)
1306 return VLC_EGENERIC
;
1308 /* display the picture immediately */
1309 bool is_forced
= frame_by_frame
|| force_refresh
|| sys
->displayed
.current
->b_force
;
1310 int ret
= ThreadDisplayRenderPicture(vout
, is_forced
);
1311 return force_refresh
? VLC_EGENERIC
: ret
;
1314 static void ThreadDisplaySubpicture(vout_thread_t
*vout
,
1315 subpicture_t
*subpicture
)
1317 spu_PutSubpicture(vout
->p
->spu
, subpicture
);
1320 static void ThreadFlushSubpicture(vout_thread_t
*vout
, int channel
)
1322 spu_ClearChannel(vout
->p
->spu
, channel
);
1325 static void ThreadChangePause(vout_thread_t
*vout
, bool is_paused
, vlc_tick_t date
)
1327 assert(!vout
->p
->pause
.is_on
|| !is_paused
);
1329 if (vout
->p
->pause
.is_on
) {
1330 const vlc_tick_t duration
= date
- vout
->p
->pause
.date
;
1332 if (vout
->p
->step
.timestamp
!= VLC_TICK_INVALID
)
1333 vout
->p
->step
.timestamp
+= duration
;
1334 if (vout
->p
->step
.last
!= VLC_TICK_INVALID
)
1335 vout
->p
->step
.last
+= duration
;
1336 picture_fifo_OffsetDate(vout
->p
->decoder_fifo
, duration
);
1337 if (vout
->p
->displayed
.decoded
)
1338 vout
->p
->displayed
.decoded
->date
+= duration
;
1339 spu_OffsetSubtitleDate(vout
->p
->spu
, duration
);
1341 ThreadFilterFlush(vout
, false);
1343 vout
->p
->step
.timestamp
= VLC_TICK_INVALID
;
1344 vout
->p
->step
.last
= VLC_TICK_INVALID
;
1346 vout
->p
->pause
.is_on
= is_paused
;
1347 vout
->p
->pause
.date
= date
;
1349 vout_window_t
*window
= vout
->p
->window
;
1351 vout_window_SetInhibition(window
, !is_paused
);
1354 static void ThreadFlush(vout_thread_t
*vout
, bool below
, vlc_tick_t date
)
1356 vout
->p
->step
.timestamp
= VLC_TICK_INVALID
;
1357 vout
->p
->step
.last
= VLC_TICK_INVALID
;
1359 ThreadFilterFlush(vout
, false); /* FIXME too much */
1361 picture_t
*last
= vout
->p
->displayed
.decoded
;
1363 if ((date
== VLC_TICK_INVALID
) ||
1364 ( below
&& last
->date
<= date
) ||
1365 (!below
&& last
->date
>= date
)) {
1366 picture_Release(last
);
1368 vout
->p
->displayed
.decoded
= NULL
;
1369 vout
->p
->displayed
.date
= VLC_TICK_INVALID
;
1370 vout
->p
->displayed
.timestamp
= VLC_TICK_INVALID
;
1374 picture_fifo_Flush(vout
->p
->decoder_fifo
, date
, below
);
1375 vout_FilterFlush(vout
->p
->display
.vd
);
1378 static void ThreadStep(vout_thread_t
*vout
, vlc_tick_t
*duration
)
1382 if (vout
->p
->step
.last
== VLC_TICK_INVALID
)
1383 vout
->p
->step
.last
= vout
->p
->displayed
.timestamp
;
1385 if (ThreadDisplayPicture(vout
, NULL
))
1388 vout
->p
->step
.timestamp
= vout
->p
->displayed
.timestamp
;
1390 if (vout
->p
->step
.last
!= VLC_TICK_INVALID
&&
1391 vout
->p
->step
.timestamp
> vout
->p
->step
.last
) {
1392 *duration
= vout
->p
->step
.timestamp
- vout
->p
->step
.last
;
1393 vout
->p
->step
.last
= vout
->p
->step
.timestamp
;
1394 /* TODO advance subpicture by the duration ... */
1398 static void ThreadTranslateMouseState(vout_thread_t
*vout
,
1399 const vlc_mouse_t
*win_mouse
)
1401 vout_display_t
*vd
= vout
->p
->display
.vd
;
1402 vlc_mouse_t vid_mouse
;
1403 vout_display_place_t place
;
1405 /* Translate window coordinates to video coordinates */
1406 vout_display_PlacePicture(&place
, &vd
->source
, vd
->cfg
, false);
1408 if (place
.width
<= 0 || place
.height
<= 0)
1411 const int x
= vd
->source
.i_x_offset
1412 + (int64_t)(win_mouse
->i_x
- place
.x
)
1413 * vd
->source
.i_visible_width
/ place
.width
;
1414 const int y
= vd
->source
.i_y_offset
1415 + (int64_t)(win_mouse
->i_y
- place
.y
)
1416 * vd
->source
.i_visible_height
/ place
.height
;
1418 vid_mouse
= *win_mouse
;
1419 vlc_mouse_SetPosition(&vid_mouse
, x
, y
);
1421 /* Then pass up the filter chains. */
1422 vout_SendDisplayEventMouse(vout
, &vid_mouse
);
1425 static int ThreadStart(vout_thread_t
*vout
, vout_display_state_t
*state
)
1427 vlc_mouse_Init(&vout
->p
->mouse
);
1428 vout
->p
->decoder_fifo
= picture_fifo_New();
1429 vout
->p
->decoder_pool
= NULL
;
1430 vout
->p
->display_pool
= NULL
;
1431 vout
->p
->private_pool
= NULL
;
1433 vout
->p
->filter
.configuration
= NULL
;
1434 video_format_Copy(&vout
->p
->filter
.format
, &vout
->p
->original
);
1436 static const struct filter_video_callbacks static_cbs
= {
1437 .buffer_new
= VoutVideoFilterStaticNewPicture
,
1439 static const struct filter_video_callbacks interactive_cbs
= {
1440 .buffer_new
= VoutVideoFilterInteractiveNewPicture
,
1442 filter_owner_t owner
= {
1443 .video
= &static_cbs
,
1446 vout
->p
->filter
.chain_static
=
1447 filter_chain_NewVideo( vout
, true, &owner
);
1449 owner
.video
= &interactive_cbs
;
1450 vout
->p
->filter
.chain_interactive
=
1451 filter_chain_NewVideo( vout
, true, &owner
);
1453 vout_display_state_t state_default
;
1455 VoutGetDisplayCfg(vout
, &state_default
.cfg
);
1457 #if defined(_WIN32) || defined(__OS2__)
1458 bool below
= var_InheritBool(vout
, "video-wallpaper");
1459 bool above
= var_InheritBool(vout
, "video-on-top");
1461 state_default
.wm_state
= below
? VOUT_WINDOW_STATE_BELOW
1462 : above
? VOUT_WINDOW_STATE_ABOVE
1463 : VOUT_WINDOW_STATE_NORMAL
;
1465 state_default
.sar
.num
= 0;
1466 state_default
.sar
.den
= 0;
1468 state
= &state_default
;
1471 if (vout_OpenWrapper(vout
, vout
->p
->splitter_name
, state
))
1473 if (vout_InitWrapper(vout
))
1475 vout_CloseWrapper(vout
, state
);
1478 assert(vout
->p
->decoder_pool
&& vout
->p
->private_pool
);
1480 vout
->p
->displayed
.current
= NULL
;
1481 vout
->p
->displayed
.next
= NULL
;
1482 vout
->p
->displayed
.decoded
= NULL
;
1483 vout
->p
->displayed
.date
= VLC_TICK_INVALID
;
1484 vout
->p
->displayed
.timestamp
= VLC_TICK_INVALID
;
1485 vout
->p
->displayed
.is_interlaced
= false;
1487 vout
->p
->step
.last
= VLC_TICK_INVALID
;
1488 vout
->p
->step
.timestamp
= VLC_TICK_INVALID
;
1490 vout
->p
->spu_blend_chroma
= 0;
1491 vout
->p
->spu_blend
= NULL
;
1493 video_format_Print(VLC_OBJECT(vout
), "original format", &vout
->p
->original
);
1496 if (vout
->p
->filter
.chain_interactive
!= NULL
)
1498 ThreadDelAllFilterCallbacks(vout
);
1499 filter_chain_Delete(vout
->p
->filter
.chain_interactive
);
1501 if (vout
->p
->filter
.chain_static
!= NULL
)
1502 filter_chain_Delete(vout
->p
->filter
.chain_static
);
1503 video_format_Clean(&vout
->p
->filter
.format
);
1504 if (vout
->p
->decoder_fifo
!= NULL
)
1505 picture_fifo_Delete(vout
->p
->decoder_fifo
);
1506 return VLC_EGENERIC
;
1509 static void ThreadStop(vout_thread_t
*vout
, vout_display_state_t
*state
)
1511 if (vout
->p
->spu_blend
)
1512 filter_DeleteBlend(vout
->p
->spu_blend
);
1514 /* Destroy translation tables */
1515 if (vout
->p
->display
.vd
) {
1516 if (vout
->p
->decoder_pool
) {
1517 ThreadFlush(vout
, true, INT64_MAX
);
1518 vout_EndWrapper(vout
);
1520 vout_CloseWrapper(vout
, state
);
1523 /* Destroy the video filters */
1524 ThreadDelAllFilterCallbacks(vout
);
1525 filter_chain_Delete(vout
->p
->filter
.chain_interactive
);
1526 filter_chain_Delete(vout
->p
->filter
.chain_static
);
1527 video_format_Clean(&vout
->p
->filter
.format
);
1528 free(vout
->p
->filter
.configuration
);
1530 if (vout
->p
->decoder_fifo
)
1531 picture_fifo_Delete(vout
->p
->decoder_fifo
);
1532 assert(!vout
->p
->decoder_pool
);
1534 if (vout
->p
->mouse_event
)
1535 vout
->p
->mouse_event(NULL
, vout
->p
->opaque
);
1538 static int ThreadReinit(vout_thread_t
*vout
,
1539 const vout_configuration_t
*cfg
)
1541 video_format_t original
;
1545 vout
->p
->mouse_event
= NULL
;
1546 vout
->p
->opaque
= NULL
;
1550 vout
->p
->mouse_event
= cfg
->mouse_event
;
1551 vout
->p
->opaque
= cfg
->opaque
;
1553 vout
->p
->pause
.is_on
= false;
1554 vout
->p
->pause
.date
= VLC_TICK_INVALID
;
1556 if (VoutValidateFormat(&original
, cfg
->fmt
)) {
1557 ThreadStop(vout
, NULL
);
1558 return VLC_EGENERIC
;
1561 /* We ignore ar changes at this point, they are dynamically supported.
1562 * #19268: don't ignore crop changes (fix vouts using the crop size of the
1563 * previous format). */
1564 vout
->p
->original
.i_sar_num
= original
.i_sar_num
;
1565 vout
->p
->original
.i_sar_den
= original
.i_sar_den
;
1566 if (video_format_IsSimilar(&original
, &vout
->p
->original
)) {
1567 if (cfg
->dpb_size
<= vout
->p
->dpb_size
) {
1568 video_format_Clean(&original
);
1571 msg_Warn(vout
, "DPB need to be increased");
1574 vout_display_state_t state
;
1575 memset(&state
, 0, sizeof(state
));
1577 ThreadStop(vout
, &state
);
1579 vout_ReinitInterlacingSupport(vout
);
1581 #if defined(_WIN32) || defined(__OS2__)
1582 if (!state
.cfg
.is_fullscreen
)
1585 state
.cfg
.display
.width
= 0;
1586 state
.cfg
.display
.height
= 0;
1591 /* FIXME current vout "variables" are not in sync here anymore
1592 * and I am not sure what to do */
1593 if (state
.cfg
.display
.sar
.num
<= 0 || state
.cfg
.display
.sar
.den
<= 0) {
1594 state
.cfg
.display
.sar
.num
= 1;
1595 state
.cfg
.display
.sar
.den
= 1;
1597 if (state
.cfg
.zoom
.num
== 0 || state
.cfg
.zoom
.den
== 0) {
1598 state
.cfg
.zoom
.num
= 1;
1599 state
.cfg
.zoom
.den
= 1;
1602 vout
->p
->original
= original
;
1603 vout
->p
->dpb_size
= cfg
->dpb_size
;
1604 if (ThreadStart(vout
, &state
))
1605 return VLC_EGENERIC
;
1610 static void ThreadCancel(vout_thread_t
*vout
, bool canceled
)
1612 picture_pool_Cancel(vout
->p
->decoder_pool
, canceled
);
1615 static int ThreadControl(vout_thread_t
*vout
, vout_control_cmd_t cmd
)
1618 case VOUT_CONTROL_CLEAN
:
1619 ThreadStop(vout
, NULL
);
1621 case VOUT_CONTROL_REINIT
:
1622 if (ThreadReinit(vout
, cmd
.cfg
))
1625 case VOUT_CONTROL_CANCEL
:
1626 ThreadCancel(vout
, cmd
.boolean
);
1628 case VOUT_CONTROL_SUBPICTURE
:
1629 ThreadDisplaySubpicture(vout
, cmd
.subpicture
);
1630 cmd
.subpicture
= NULL
;
1632 case VOUT_CONTROL_FLUSH_SUBPICTURE
:
1633 ThreadFlushSubpicture(vout
, cmd
.integer
);
1635 case VOUT_CONTROL_CHANGE_FILTERS
:
1636 ThreadChangeFilters(vout
, NULL
,
1637 cmd
.string
!= NULL
?
1638 cmd
.string
: vout
->p
->filter
.configuration
,
1641 case VOUT_CONTROL_CHANGE_INTERLACE
:
1642 ThreadChangeFilters(vout
, NULL
, vout
->p
->filter
.configuration
,
1643 cmd
.boolean
? 1 : 0, false);
1645 case VOUT_CONTROL_PAUSE
:
1646 ThreadChangePause(vout
, cmd
.pause
.is_on
, cmd
.pause
.date
);
1648 case VOUT_CONTROL_FLUSH
:
1649 ThreadFlush(vout
, false, cmd
.time
);
1651 case VOUT_CONTROL_STEP
:
1652 ThreadStep(vout
, cmd
.time_ptr
);
1654 case VOUT_CONTROL_MOUSE_STATE
:
1655 ThreadTranslateMouseState(vout
, &cmd
.mouse
);
1657 case VOUT_CONTROL_DISPLAY_SIZE
:
1658 vout_SetDisplaySize(vout
->p
->display
.vd
,
1659 cmd
.window
.width
, cmd
.window
.height
);
1661 case VOUT_CONTROL_DISPLAY_FILLED
:
1662 vout_SetDisplayFilled(vout
->p
->display
.vd
, cmd
.boolean
);
1664 case VOUT_CONTROL_ZOOM
:
1665 vout_SetDisplayZoom(vout
->p
->display
.vd
, cmd
.pair
.a
, cmd
.pair
.b
);
1667 case VOUT_CONTROL_ASPECT_RATIO
:
1668 vout_SetDisplayAspect(vout
->p
->display
.vd
, cmd
.pair
.a
, cmd
.pair
.b
);
1670 case VOUT_CONTROL_CROP_RATIO
:
1671 vout_SetDisplayCrop(vout
->p
->display
.vd
, cmd
.pair
.a
, cmd
.pair
.b
,
1674 case VOUT_CONTROL_CROP_WINDOW
:
1675 vout_SetDisplayCrop(vout
->p
->display
.vd
, 0, 0,
1676 cmd
.window
.x
, cmd
.window
.y
,
1677 cmd
.window
.width
, cmd
.window
.height
);
1679 case VOUT_CONTROL_CROP_BORDER
:
1680 vout_SetDisplayCrop(vout
->p
->display
.vd
, 0, 0,
1681 cmd
.border
.left
, cmd
.border
.top
,
1682 -(int)cmd
.border
.right
, -(int)cmd
.border
.bottom
);
1684 case VOUT_CONTROL_VIEWPOINT
:
1685 vout_SetDisplayViewpoint(vout
->p
->display
.vd
, &cmd
.viewpoint
);
1690 vout_control_cmd_Clean(&cmd
);
1694 /*****************************************************************************
1695 * Thread: video output thread
1696 *****************************************************************************
1697 * Video output thread. This function does only returns when the thread is
1698 * terminated. It handles the pictures arriving in the video heap and the
1699 * display device events.
1700 *****************************************************************************/
1701 static void *Thread(void *object
)
1703 vout_thread_t
*vout
= object
;
1704 vout_thread_sys_t
*sys
= vout
->p
;
1706 vlc_tick_t deadline
= VLC_TICK_INVALID
;
1709 if (ThreadStart(vout
, NULL
))
1713 vout_control_cmd_t cmd
;
1717 const vlc_tick_t max_deadline
= vlc_tick_now() + VLC_TICK_FROM_MS(100);
1718 deadline
= deadline
== VLC_TICK_INVALID
? max_deadline
: __MIN(deadline
, max_deadline
);
1720 deadline
= VLC_TICK_INVALID
;
1722 while (!vout_control_Pop(&sys
->control
, &cmd
, deadline
))
1723 if (ThreadControl(vout
, cmd
))
1726 deadline
= VLC_TICK_INVALID
;
1727 wait
= ThreadDisplayPicture(vout
, &deadline
) != VLC_SUCCESS
;
1729 const bool picture_interlaced
= sys
->displayed
.is_interlaced
;
1731 vout_SetInterlacingState(vout
, picture_interlaced
);
1732 vout_ManageWrapper(vout
);
1736 vout
->p
->dead
= true;
1737 vout_control_Dead(&vout
->p
->control
);