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 the VideoLAN team
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
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 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 General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, 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>
46 #include <vlc_vout_osd.h>
49 #include "vout_internal.h"
50 #include "interlacing.h"
51 #include "postprocessing.h"
54 /*****************************************************************************
56 *****************************************************************************/
57 static void *Thread(void *);
58 static void VoutDestructor(vlc_object_t
*);
60 /* Maximum delay between 2 displayed pictures.
61 * XXX it is needed for now but should be removed in the long term.
63 #define VOUT_REDISPLAY_DELAY (INT64_C(80000))
66 * Late pictures having a delay higher than this value are thrashed.
68 #define VOUT_DISPLAY_LATE_THRESHOLD (INT64_C(20000))
70 /* Better be in advance when awakening than late... */
71 #define VOUT_MWAIT_TOLERANCE (INT64_C(4000))
74 static int VoutValidateFormat(video_format_t
*dst
,
75 const video_format_t
*src
)
77 if (src
->i_width
<= 0 || src
->i_height
<= 0)
79 if (src
->i_sar_num
<= 0 || src
->i_sar_den
<= 0)
83 video_format_Copy(dst
, src
);
84 dst
->i_chroma
= vlc_fourcc_GetCodec(VIDEO_ES
, src
->i_chroma
);
85 vlc_ureduce( &dst
->i_sar_num
, &dst
->i_sar_den
,
86 src
->i_sar_num
, src
->i_sar_den
, 50000 );
87 if (dst
->i_sar_num
<= 0 || dst
->i_sar_den
<= 0) {
91 video_format_FixRgb(dst
);
95 static vout_thread_t
*VoutCreate(vlc_object_t
*object
,
96 const vout_configuration_t
*cfg
)
98 video_format_t original
;
99 if (VoutValidateFormat(&original
, cfg
->fmt
))
102 /* Allocate descriptor */
103 vout_thread_t
*vout
= vlc_custom_create(object
,
104 sizeof(*vout
) + sizeof(*vout
->p
),
105 VLC_OBJECT_VOUT
, "video output");
107 video_format_Clean(&original
);
112 vout
->p
= (vout_thread_sys_t
*)&vout
[1];
114 vout
->p
->original
= original
;
115 vout
->p
->dpb_size
= cfg
->dpb_size
;
117 vout_control_Init(&vout
->p
->control
);
118 vout_control_PushVoid(&vout
->p
->control
, VOUT_CONTROL_INIT
);
120 vout_statistic_Init(&vout
->p
->statistic
);
122 vout
->p
->i_par_den
= 1;
124 vout_snapshot_Init(&vout
->p
->snapshot
);
126 /* Initialize locks */
127 vlc_mutex_init(&vout
->p
->picture_lock
);
128 vlc_mutex_init(&vout
->p
->filter
.lock
);
129 vlc_mutex_init(&vout
->p
->spu_lock
);
131 /* Attach the new object now so we can use var inheritance below */
132 vlc_object_attach(vout
, object
);
134 /* Initialize subpicture unit */
135 vout
->p
->p_spu
= spu_Create(vout
);
137 /* Take care of some "interface/control" related initialisations */
140 /* Get splitter name if present */
141 char *splitter_name
= var_GetNonEmptyString(vout
, "vout-filter");
143 if (asprintf(&vout
->p
->splitter_name
, "%s,none", splitter_name
) < 0)
144 vout
->p
->splitter_name
= NULL
;
147 vout
->p
->splitter_name
= NULL
;
151 vout_InitInterlacingSupport(vout
, vout
->p
->displayed
.is_interlaced
);
154 vlc_object_set_destructor(vout
, VoutDestructor
);
157 if (vlc_clone(&vout
->p
->thread
, Thread
, vout
,
158 VLC_THREAD_PRIORITY_OUTPUT
)) {
159 spu_Destroy(vout
->p
->p_spu
);
160 vlc_object_release(vout
);
164 vout_control_WaitEmpty(&vout
->p
->control
);
167 msg_Err(vout
, "video output creation failed");
168 vout_CloseAndRelease(vout
);
172 vout
->p
->input
= cfg
->input
;
174 spu_Attach(vout
->p
->p_spu
, vout
->p
->input
, true);
179 vout_thread_t
*(vout_Request
)(vlc_object_t
*object
,
180 const vout_configuration_t
*cfg
)
182 vout_thread_t
*vout
= cfg
->vout
;
183 if (cfg
->change_fmt
&& !cfg
->fmt
) {
185 vout_CloseAndRelease(vout
);
189 /* If a vout is provided, try reusing it */
191 if (vout
->p
->input
!= cfg
->input
) {
193 spu_Attach(vout
->p
->p_spu
, vout
->p
->input
, false);
194 vout
->p
->input
= cfg
->input
;
196 spu_Attach(vout
->p
->p_spu
, vout
->p
->input
, true);
199 if (cfg
->change_fmt
) {
200 vout_control_cmd_t cmd
;
201 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_REINIT
);
204 vout_control_Push(&vout
->p
->control
, &cmd
);
205 vout_control_WaitEmpty(&vout
->p
->control
);
208 if (!vout
->p
->dead
) {
209 msg_Dbg(object
, "reusing provided vout");
212 vout_CloseAndRelease(vout
);
214 msg_Warn(object
, "cannot reuse provided vout");
216 return VoutCreate(object
, cfg
);
219 void vout_Close(vout_thread_t
*vout
)
224 spu_Attach(vout
->p
->p_spu
, vout
->p
->input
, false);
226 vout_snapshot_End(&vout
->p
->snapshot
);
228 vout_control_PushVoid(&vout
->p
->control
, VOUT_CONTROL_CLEAN
);
229 vlc_join(vout
->p
->thread
, NULL
);
231 vlc_mutex_lock(&vout
->p
->spu_lock
);
232 spu_Destroy(vout
->p
->p_spu
);
233 vout
->p
->p_spu
= NULL
;
234 vlc_mutex_unlock(&vout
->p
->spu_lock
);
238 static void VoutDestructor(vlc_object_t
*object
)
240 vout_thread_t
*vout
= (vout_thread_t
*)object
;
242 /* Make sure the vout was stopped first */
243 //assert(!vout->p_module);
245 free(vout
->p
->splitter_name
);
247 /* Destroy the locks */
248 vlc_mutex_destroy(&vout
->p
->spu_lock
);
249 vlc_mutex_destroy(&vout
->p
->picture_lock
);
250 vlc_mutex_destroy(&vout
->p
->filter
.lock
);
251 vout_control_Clean(&vout
->p
->control
);
254 vout_statistic_Clean(&vout
->p
->statistic
);
257 vout_snapshot_Clean(&vout
->p
->snapshot
);
259 video_format_Clean(&vout
->p
->original
);
263 void vout_ChangePause(vout_thread_t
*vout
, bool is_paused
, mtime_t date
)
265 vout_control_cmd_t cmd
;
266 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_PAUSE
);
267 cmd
.u
.pause
.is_on
= is_paused
;
268 cmd
.u
.pause
.date
= date
;
269 vout_control_Push(&vout
->p
->control
, &cmd
);
271 vout_control_WaitEmpty(&vout
->p
->control
);
274 void vout_GetResetStatistic(vout_thread_t
*vout
, int *displayed
, int *lost
)
276 vout_statistic_GetReset( &vout
->p
->statistic
, displayed
, lost
);
279 void vout_Flush(vout_thread_t
*vout
, mtime_t date
)
281 vout_control_PushTime(&vout
->p
->control
, VOUT_CONTROL_FLUSH
, date
);
282 vout_control_WaitEmpty(&vout
->p
->control
);
285 void vout_Reset(vout_thread_t
*vout
)
287 vout_control_PushVoid(&vout
->p
->control
, VOUT_CONTROL_RESET
);
288 vout_control_WaitEmpty(&vout
->p
->control
);
291 bool vout_IsEmpty(vout_thread_t
*vout
)
293 vlc_mutex_lock(&vout
->p
->picture_lock
);
295 picture_t
*picture
= picture_fifo_Peek(vout
->p
->decoder_fifo
);
297 picture_Release(picture
);
299 vlc_mutex_unlock(&vout
->p
->picture_lock
);
304 void vout_FixLeaks( vout_thread_t
*vout
)
306 vlc_mutex_lock(&vout
->p
->picture_lock
);
308 picture_t
*picture
= picture_fifo_Peek(vout
->p
->decoder_fifo
);
310 picture
= picture_pool_Get(vout
->p
->decoder_pool
);
314 picture_Release(picture
);
315 /* Not all pictures has been displayed yet or some are
317 vlc_mutex_unlock(&vout
->p
->picture_lock
);
321 /* There is no reason that no pictures are available, force one
322 * from the pool, becarefull with it though */
323 msg_Err(vout
, "pictures leaked, trying to workaround");
326 picture_pool_NonEmpty(vout
->p
->decoder_pool
, false);
328 vlc_mutex_unlock(&vout
->p
->picture_lock
);
330 void vout_NextPicture(vout_thread_t
*vout
, mtime_t
*duration
)
332 vout_control_cmd_t cmd
;
333 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_STEP
);
334 cmd
.u
.time_ptr
= duration
;
336 vout_control_Push(&vout
->p
->control
, &cmd
);
337 vout_control_WaitEmpty(&vout
->p
->control
);
340 void vout_DisplayTitle(vout_thread_t
*vout
, const char *title
)
343 vout_control_PushString(&vout
->p
->control
, VOUT_CONTROL_OSD_TITLE
, title
);
346 void vout_PutSubpicture( vout_thread_t
*vout
, subpicture_t
*subpic
)
348 vout_control_cmd_t cmd
;
349 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_SUBPICTURE
);
350 cmd
.u
.subpicture
= subpic
;
352 vout_control_Push(&vout
->p
->control
, &cmd
);
354 int vout_RegisterSubpictureChannel( vout_thread_t
*vout
)
356 int channel
= SPU_DEFAULT_CHANNEL
;
358 vlc_mutex_lock(&vout
->p
->spu_lock
);
360 channel
= spu_RegisterChannel(vout
->p
->p_spu
);
361 vlc_mutex_unlock(&vout
->p
->spu_lock
);
365 void vout_FlushSubpictureChannel( vout_thread_t
*vout
, int channel
)
367 vout_control_PushInteger(&vout
->p
->control
, VOUT_CONTROL_FLUSH_SUBPICTURE
,
371 /* vout_Control* are usable by anyone at anytime */
372 void vout_ControlChangeFullscreen(vout_thread_t
*vout
, bool fullscreen
)
374 vout_control_PushBool(&vout
->p
->control
, VOUT_CONTROL_FULLSCREEN
,
377 void vout_ControlChangeOnTop(vout_thread_t
*vout
, bool is_on_top
)
379 vout_control_PushBool(&vout
->p
->control
, VOUT_CONTROL_ON_TOP
,
382 void vout_ControlChangeDisplayFilled(vout_thread_t
*vout
, bool is_filled
)
384 vout_control_PushBool(&vout
->p
->control
, VOUT_CONTROL_DISPLAY_FILLED
,
387 void vout_ControlChangeZoom(vout_thread_t
*vout
, int num
, int den
)
389 vout_control_PushPair(&vout
->p
->control
, VOUT_CONTROL_ZOOM
,
392 void vout_ControlChangeSampleAspectRatio(vout_thread_t
*vout
,
393 unsigned num
, unsigned den
)
395 vout_control_PushPair(&vout
->p
->control
, VOUT_CONTROL_ASPECT_RATIO
,
398 void vout_ControlChangeCropRatio(vout_thread_t
*vout
,
399 unsigned num
, unsigned den
)
401 vout_control_PushPair(&vout
->p
->control
, VOUT_CONTROL_CROP_RATIO
,
404 void vout_ControlChangeCropWindow(vout_thread_t
*vout
,
405 int x
, int y
, int width
, int height
)
407 vout_control_cmd_t cmd
;
408 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_CROP_WINDOW
);
411 cmd
.u
.window
.width
= width
;
412 cmd
.u
.window
.height
= height
;
414 vout_control_Push(&vout
->p
->control
, &cmd
);
416 void vout_ControlChangeCropBorder(vout_thread_t
*vout
,
417 int left
, int top
, int right
, int bottom
)
419 vout_control_cmd_t cmd
;
420 vout_control_cmd_Init(&cmd
, VOUT_CONTROL_CROP_BORDER
);
421 cmd
.u
.border
.left
= left
;
422 cmd
.u
.border
.top
= top
;
423 cmd
.u
.border
.right
= right
;
424 cmd
.u
.border
.bottom
= bottom
;
426 vout_control_Push(&vout
->p
->control
, &cmd
);
428 void vout_ControlChangeFilters(vout_thread_t
*vout
, const char *filters
)
430 vout_control_PushString(&vout
->p
->control
, VOUT_CONTROL_CHANGE_FILTERS
,
433 void vout_ControlChangeSubFilters(vout_thread_t
*vout
, const char *filters
)
435 vout_control_PushString(&vout
->p
->control
, VOUT_CONTROL_CHANGE_SUB_FILTERS
,
438 void vout_ControlChangeSubMargin(vout_thread_t
*vout
, int margin
)
440 vout_control_PushInteger(&vout
->p
->control
, VOUT_CONTROL_CHANGE_SUB_MARGIN
,
445 static void VoutGetDisplayCfg(vout_thread_t
*vout
, vout_display_cfg_t
*cfg
, const char *title
)
447 /* Load configuration */
448 cfg
->is_fullscreen
= var_CreateGetBool(vout
, "fullscreen");
449 cfg
->display
.title
= title
;
450 const int display_width
= var_CreateGetInteger(vout
, "width");
451 const int display_height
= var_CreateGetInteger(vout
, "height");
452 cfg
->display
.width
= display_width
> 0 ? display_width
: 0;
453 cfg
->display
.height
= display_height
> 0 ? display_height
: 0;
454 cfg
->is_display_filled
= var_CreateGetBool(vout
, "autoscale");
455 cfg
->display
.sar
.num
= 1; /* TODO monitor AR */
456 cfg
->display
.sar
.den
= 1;
457 unsigned zoom_den
= 1000;
458 unsigned zoom_num
= zoom_den
* var_CreateGetFloat(vout
, "scale");
459 vlc_ureduce(&zoom_num
, &zoom_den
, zoom_num
, zoom_den
, 0);
460 cfg
->zoom
.num
= zoom_num
;
461 cfg
->zoom
.den
= zoom_den
;
462 cfg
->align
.vertical
= VOUT_DISPLAY_ALIGN_CENTER
;
463 cfg
->align
.horizontal
= VOUT_DISPLAY_ALIGN_CENTER
;
464 const int align_mask
= var_CreateGetInteger(vout
, "align");
465 if (align_mask
& 0x1)
466 cfg
->align
.horizontal
= VOUT_DISPLAY_ALIGN_LEFT
;
467 else if (align_mask
& 0x2)
468 cfg
->align
.horizontal
= VOUT_DISPLAY_ALIGN_RIGHT
;
469 if (align_mask
& 0x4)
470 cfg
->align
.vertical
= VOUT_DISPLAY_ALIGN_TOP
;
471 else if (align_mask
& 0x8)
472 cfg
->align
.vertical
= VOUT_DISPLAY_ALIGN_BOTTOM
;
475 vout_window_t
* vout_NewDisplayWindow(vout_thread_t
*vout
, vout_display_t
*vd
,
476 const vout_window_cfg_t
*cfg
)
479 vout_window_cfg_t cfg_override
= *cfg
;
481 if (!var_InheritBool( vout
, "embedded-video"))
482 cfg_override
.is_standalone
= true;
484 if (vout
->p
->window
.is_unused
&& vout
->p
->window
.object
) {
485 assert(!vout
->p
->splitter_name
);
486 if (!cfg_override
.is_standalone
== !vout
->p
->window
.cfg
.is_standalone
&&
487 cfg_override
.type
== vout
->p
->window
.cfg
.type
) {
488 /* Reuse the stored window */
489 msg_Dbg(vout
, "Reusing previous vout window");
490 vout_window_t
*window
= vout
->p
->window
.object
;
491 if (cfg_override
.width
!= vout
->p
->window
.cfg
.width
||
492 cfg_override
.height
!= vout
->p
->window
.cfg
.height
)
493 vout_window_SetSize(window
,
494 cfg_override
.width
, cfg_override
.height
);
495 vout
->p
->window
.is_unused
= false;
496 vout
->p
->window
.cfg
= cfg_override
;
500 vout_window_Delete(vout
->p
->window
.object
);
501 vout
->p
->window
.is_unused
= true;
502 vout
->p
->window
.object
= NULL
;
505 vout_window_t
*window
= vout_window_New(VLC_OBJECT(vout
), "$window",
509 if (!vout
->p
->splitter_name
) {
510 vout
->p
->window
.is_unused
= false;
511 vout
->p
->window
.cfg
= cfg_override
;
512 vout
->p
->window
.object
= window
;
517 void vout_DeleteDisplayWindow(vout_thread_t
*vout
, vout_display_t
*vd
,
518 vout_window_t
*window
)
521 if (!vout
->p
->window
.is_unused
&& vout
->p
->window
.object
== window
) {
522 vout
->p
->window
.is_unused
= true;
523 } else if (vout
->p
->window
.is_unused
&& vout
->p
->window
.object
&& !window
) {
524 vout_window_Delete(vout
->p
->window
.object
);
525 vout
->p
->window
.is_unused
= true;
526 vout
->p
->window
.object
= NULL
;
528 vout_window_Delete(window
);
534 static picture_t
*VoutVideoFilterInteractiveNewPicture(filter_t
*filter
)
536 vout_thread_t
*vout
= (vout_thread_t
*)filter
->p_owner
;
538 return picture_pool_Get(vout
->p
->private_pool
);
540 static picture_t
*VoutVideoFilterStaticNewPicture(filter_t
*filter
)
542 vout_thread_t
*vout
= (vout_thread_t
*)filter
->p_owner
;
544 vlc_assert_locked(&vout
->p
->filter
.lock
);
545 if (filter_chain_GetLength(vout
->p
->filter
.chain_interactive
) == 0)
546 return VoutVideoFilterInteractiveNewPicture(filter
);
547 return picture_NewFromFormat(&filter
->fmt_out
.video
);
549 static void VoutVideoFilterDelPicture(filter_t
*filter
, picture_t
*picture
)
552 picture_Release(picture
);
554 static int VoutVideoFilterStaticAllocationSetup(filter_t
*filter
, void *data
)
556 filter
->pf_video_buffer_new
= VoutVideoFilterStaticNewPicture
;
557 filter
->pf_video_buffer_del
= VoutVideoFilterDelPicture
;
558 filter
->p_owner
= data
; /* vout */
561 static int VoutVideoFilterInteractiveAllocationSetup(filter_t
*filter
, void *data
)
563 filter
->pf_video_buffer_new
= VoutVideoFilterInteractiveNewPicture
;
564 filter
->pf_video_buffer_del
= VoutVideoFilterDelPicture
;
565 filter
->p_owner
= data
; /* vout */
570 static int ThreadDisplayPreparePicture(vout_thread_t
*vout
, bool reuse
, bool is_late_dropped
)
574 vlc_mutex_lock(&vout
->p
->filter
.lock
);
576 picture_t
*picture
= filter_chain_VideoFilter(vout
->p
->filter
.chain_static
, NULL
);
577 assert(!reuse
|| !picture
);
581 if (reuse
&& vout
->p
->displayed
.decoded
) {
582 decoded
= picture_Hold(vout
->p
->displayed
.decoded
);
584 decoded
= picture_fifo_Pop(vout
->p
->decoder_fifo
);
585 if (is_late_dropped
&& decoded
&& !decoded
->b_force
) {
586 const mtime_t predicted
= mdate() + 0; /* TODO improve */
587 const mtime_t late
= predicted
- decoded
->date
;
588 if (late
> VOUT_DISPLAY_LATE_THRESHOLD
) {
589 msg_Warn(vout
, "picture is too late to be displayed (missing %d ms)", (int)(late
/1000));
590 picture_Release(decoded
);
593 } else if (late
> 0) {
594 msg_Dbg(vout
, "picture might be displayed late (missing %d ms)", (int)(late
/1000));
602 if (vout
->p
->displayed
.decoded
)
603 picture_Release(vout
->p
->displayed
.decoded
);
605 vout
->p
->displayed
.decoded
= picture_Hold(decoded
);
606 vout
->p
->displayed
.timestamp
= decoded
->date
;
607 vout
->p
->displayed
.is_interlaced
= !decoded
->b_progressive
;
608 vout
->p
->displayed
.qtype
= decoded
->i_qtype
;
610 picture
= filter_chain_VideoFilter(vout
->p
->filter
.chain_static
, decoded
);
613 vlc_mutex_unlock(&vout
->p
->filter
.lock
);
615 vout_statistic_Update(&vout
->p
->statistic
, 0, lost_count
);
619 assert(!vout
->p
->displayed
.next
);
620 if (!vout
->p
->displayed
.current
)
621 vout
->p
->displayed
.current
= picture
;
623 vout
->p
->displayed
.next
= picture
;
627 static int ThreadDisplayRenderPicture(vout_thread_t
*vout
, bool is_forced
)
629 vout_display_t
*vd
= vout
->p
->display
.vd
;
631 picture_t
*torender
= picture_Hold(vout
->p
->displayed
.current
);
633 vout_chrono_Start(&vout
->p
->render
);
635 vlc_mutex_lock(&vout
->p
->filter
.lock
);
636 picture_t
*filtered
= filter_chain_VideoFilter(vout
->p
->filter
.chain_interactive
, torender
);
637 vlc_mutex_unlock(&vout
->p
->filter
.lock
);
642 if (filtered
->date
!= vout
->p
->displayed
.current
->date
)
643 msg_Warn(vout
, "Unsupported timestamp modifications done by chain_interactive");
646 * Check for subpictures to display
648 const bool do_snapshot
= vout_snapshot_IsRequested(&vout
->p
->snapshot
);
649 mtime_t spu_render_time
= is_forced
? mdate() : filtered
->date
;
650 if (vout
->p
->pause
.is_on
)
651 spu_render_time
= vout
->p
->pause
.date
;
653 spu_render_time
= filtered
->date
> 1 ? filtered
->date
: mdate();
655 subpicture_t
*subpic
= spu_SortSubpictures(vout
->p
->p_spu
,
662 * - be sure to end up with a direct buffer.
663 * - blend subtitles, and in a fast access buffer
665 picture_t
*direct
= NULL
;
667 (vout
->p
->decoder_pool
!= vout
->p
->display_pool
|| subpic
)) {
669 if (vout
->p
->is_decoder_pool_slow
)
670 render
= picture_NewFromFormat(&vd
->source
);
671 else if (vout
->p
->decoder_pool
!= vout
->p
->display_pool
)
672 render
= picture_pool_Get(vout
->p
->display_pool
);
674 render
= picture_pool_Get(vout
->p
->private_pool
);
677 picture_Copy(render
, filtered
);
679 spu_RenderSubpictures(vout
->p
->p_spu
,
681 subpic
, &vd
->source
, spu_render_time
);
683 if (vout
->p
->is_decoder_pool_slow
) {
684 direct
= picture_pool_Get(vout
->p
->display_pool
);
686 picture_Copy(direct
, render
);
687 picture_Release(render
);
692 picture_Release(filtered
);
702 * Take a snapshot if requested
705 vout_snapshot_Set(&vout
->p
->snapshot
, &vd
->source
, direct
);
707 /* Render the direct buffer returned by vout_RenderPicture */
708 vout_RenderWrapper(vout
, direct
);
710 vout_chrono_Stop(&vout
->p
->render
);
715 msg_Info(vout
, "render: avg %d ms var %d ms",
716 (int)(vout
->p
->render
.avg
/1000), (int)(vout
->p
->render
.var
/1000));
720 /* Wait the real date (for rendering jitter) */
722 mtime_t delay
= direct
->date
- mdate();
724 msg_Warn(vout
, "picture is late (%lld ms)", delay
/ 1000);
729 /* Display the direct buffer returned by vout_RenderPicture */
730 vout
->p
->displayed
.date
= mdate();
732 vout_DisplayWrapper(vout
, direct
);
734 vout_statistic_Update(&vout
->p
->statistic
, 1, 0);
739 static int ThreadDisplayPicture(vout_thread_t
*vout
,
740 bool now
, mtime_t
*deadline
)
742 bool is_late_dropped
= vout
->p
->is_late_dropped
&& !vout
->p
->pause
.is_on
&& !now
;
743 bool first
= !vout
->p
->displayed
.current
;
744 if (first
&& ThreadDisplayPreparePicture(vout
, true, is_late_dropped
)) /* FIXME not sure it is ok */
746 if (!vout
->p
->pause
.is_on
|| now
) {
747 while (!vout
->p
->displayed
.next
) {
748 if (ThreadDisplayPreparePicture(vout
, false, is_late_dropped
)) {
754 const mtime_t date
= mdate();
755 const mtime_t render_delay
= vout_chrono_GetHigh(&vout
->p
->render
) + VOUT_MWAIT_TOLERANCE
;
757 mtime_t date_next
= VLC_TS_INVALID
;
758 if (!vout
->p
->pause
.is_on
&& vout
->p
->displayed
.next
)
759 date_next
= vout
->p
->displayed
.next
->date
- render_delay
;
761 /* FIXME/XXX we must redisplay the last decoded picture (because
762 * of potential vout updated, or filters update or SPU update)
763 * For now a high update period is needed but it coulmd be removed
765 * - vout module emits events from theselves.
766 * - *and* SPU is modified to emit an event or a deadline when needed.
768 * So it will be done latter.
770 mtime_t date_refresh
= VLC_TS_INVALID
;
771 if (vout
->p
->displayed
.date
> VLC_TS_INVALID
)
772 date_refresh
= vout
->p
->displayed
.date
+ VOUT_REDISPLAY_DELAY
- render_delay
;
775 if (date_next
!= VLC_TS_INVALID
)
776 drop
|= date_next
+ 0 <= date
;
778 bool refresh
= false;
779 if (date_refresh
> VLC_TS_INVALID
)
780 refresh
= date_refresh
<= date
;
782 if (!first
&& !refresh
&& !drop
) {
783 if (date_next
!= VLC_TS_INVALID
&& date_refresh
!= VLC_TS_INVALID
)
784 *deadline
= __MIN(date_next
, date_refresh
);
785 else if (date_next
!= VLC_TS_INVALID
)
786 *deadline
= date_next
;
787 else if (date_refresh
!= VLC_TS_INVALID
)
788 *deadline
= date_refresh
;
793 picture_Release(vout
->p
->displayed
.current
);
794 vout
->p
->displayed
.current
= vout
->p
->displayed
.next
;
795 vout
->p
->displayed
.next
= NULL
;
797 if (!vout
->p
->displayed
.current
)
800 bool is_forced
= now
|| (!drop
&& refresh
) || vout
->p
->displayed
.current
->b_force
;
801 return ThreadDisplayRenderPicture(vout
, is_forced
);
804 static void ThreadManage(vout_thread_t
*vout
,
806 vout_interlacing_support_t
*interlacing
,
807 vout_postprocessing_support_t
*postprocessing
)
809 vlc_mutex_lock(&vout
->p
->picture_lock
);
811 *deadline
= VLC_TS_INVALID
;
813 if (ThreadDisplayPicture(vout
, false, deadline
))
817 const int picture_qtype
= vout
->p
->displayed
.qtype
;
818 const bool picture_interlaced
= vout
->p
->displayed
.is_interlaced
;
820 vlc_mutex_unlock(&vout
->p
->picture_lock
);
822 /* Post processing */
823 vout_SetPostProcessingState(vout
, postprocessing
, picture_qtype
);
826 vout_SetInterlacingState(vout
, interlacing
, picture_interlaced
);
828 vout_ManageWrapper(vout
);
831 static void ThreadDisplaySubpicture(vout_thread_t
*vout
,
832 subpicture_t
*subpicture
)
834 spu_DisplaySubpicture(vout
->p
->p_spu
, subpicture
);
837 static void ThreadFlushSubpicture(vout_thread_t
*vout
, int channel
)
839 spu_ClearChannel(vout
->p
->p_spu
, channel
);
842 static void ThreadDisplayOsdTitle(vout_thread_t
*vout
, const char *string
)
844 if (!vout
->p
->title
.show
)
847 vout_OSDText(vout
, SPU_DEFAULT_CHANNEL
,
848 vout
->p
->title
.position
, INT64_C(1000) * vout
->p
->title
.timeout
,
852 static void ThreadFilterFlush(vout_thread_t
*vout
)
854 if (vout
->p
->displayed
.current
)
855 picture_Release( vout
->p
->displayed
.current
);
856 vout
->p
->displayed
.current
= NULL
;
858 if (vout
->p
->displayed
.next
)
859 picture_Release( vout
->p
->displayed
.next
);
860 vout
->p
->displayed
.next
= NULL
;
862 vlc_mutex_lock(&vout
->p
->filter
.lock
);
863 filter_chain_VideoFlush(vout
->p
->filter
.chain_static
);
864 filter_chain_VideoFlush(vout
->p
->filter
.chain_interactive
);
865 vlc_mutex_unlock(&vout
->p
->filter
.lock
);
873 static void ThreadChangeFilters(vout_thread_t
*vout
, const char *filters
)
875 ThreadFilterFlush(vout
);
877 vlc_array_t array_static
;
878 vlc_array_t array_interactive
;
880 vlc_array_init(&array_static
);
881 vlc_array_init(&array_interactive
);
882 char *current
= filters
? strdup(filters
) : NULL
;
886 char *next
= config_ChainCreate(&name
, &cfg
, current
);
889 vout_filter_t
*e
= xmalloc(sizeof(*e
));
892 if (!strcmp(e
->name
, "deinterlace") ||
893 !strcmp(e
->name
, "postproc")) {
894 vlc_array_append(&array_static
, e
);
896 vlc_array_append(&array_interactive
, e
);
900 config_ChainDestroy(cfg
);
908 es_format_Init(&fmt
, VIDEO_ES
, vout
->p
->original
.i_chroma
);
909 fmt
.video
= vout
->p
->original
;
911 vlc_mutex_lock(&vout
->p
->filter
.lock
);
913 filter_chain_Reset(vout
->p
->filter
.chain_static
, &fmt
, &fmt
);
914 filter_chain_Reset(vout
->p
->filter
.chain_interactive
, &fmt
, &fmt
);
916 for (int a
= 0; a
< 2; a
++) {
917 vlc_array_t
*array
= a
== 0 ? &array_static
:
919 filter_chain_t
*chain
= a
== 0 ? vout
->p
->filter
.chain_static
:
920 vout
->p
->filter
.chain_interactive
;
922 for (int i
= 0; i
< vlc_array_count(array
); i
++) {
923 vout_filter_t
*e
= vlc_array_item_at_index(array
, i
);
924 msg_Dbg(vout
, "Adding '%s' as %s", e
->name
, a
== 0 ? "static" : "interactive");
925 if (!filter_chain_AppendFilter(chain
, e
->name
, e
->cfg
, NULL
, NULL
)) {
926 msg_Err(vout
, "Failed to add filter '%s'", e
->name
);
927 config_ChainDestroy(e
->cfg
);
932 vlc_array_clear(array
);
935 vlc_mutex_unlock(&vout
->p
->filter
.lock
);
938 static void ThreadChangeSubFilters(vout_thread_t
*vout
, const char *filters
)
940 spu_ChangeFilters(vout
->p
->p_spu
, filters
);
942 static void ThreadChangeSubMargin(vout_thread_t
*vout
, int margin
)
944 spu_ChangeMargin(vout
->p
->p_spu
, margin
);
947 static void ThreadChangePause(vout_thread_t
*vout
, bool is_paused
, mtime_t date
)
949 assert(!vout
->p
->pause
.is_on
|| !is_paused
);
951 if (vout
->p
->pause
.is_on
) {
952 const mtime_t duration
= date
- vout
->p
->pause
.date
;
954 if (vout
->p
->step
.timestamp
> VLC_TS_INVALID
)
955 vout
->p
->step
.timestamp
+= duration
;
956 if (vout
->p
->step
.last
> VLC_TS_INVALID
)
957 vout
->p
->step
.last
+= duration
;
958 picture_fifo_OffsetDate(vout
->p
->decoder_fifo
, duration
);
959 if (vout
->p
->displayed
.decoded
)
960 vout
->p
->displayed
.decoded
->date
+= duration
;
961 spu_OffsetSubtitleDate(vout
->p
->p_spu
, duration
);
963 ThreadFilterFlush(vout
);
965 vout
->p
->step
.timestamp
= VLC_TS_INVALID
;
966 vout
->p
->step
.last
= VLC_TS_INVALID
;
968 vout
->p
->pause
.is_on
= is_paused
;
969 vout
->p
->pause
.date
= date
;
972 static void ThreadFlush(vout_thread_t
*vout
, bool below
, mtime_t date
)
974 vout
->p
->step
.timestamp
= VLC_TS_INVALID
;
975 vout
->p
->step
.last
= VLC_TS_INVALID
;
977 ThreadFilterFlush(vout
); /* FIXME too much */
979 picture_t
*last
= vout
->p
->displayed
.decoded
;
981 if (( below
&& last
->date
<= date
) ||
982 (!below
&& last
->date
>= date
)) {
983 picture_Release(last
);
985 vout
->p
->displayed
.decoded
= NULL
;
986 vout
->p
->displayed
.date
= VLC_TS_INVALID
;
987 vout
->p
->displayed
.timestamp
= VLC_TS_INVALID
;
991 picture_fifo_Flush(vout
->p
->decoder_fifo
, date
, below
);
994 static void ThreadReset(vout_thread_t
*vout
)
996 ThreadFlush(vout
, true, INT64_MAX
);
997 if (vout
->p
->decoder_pool
)
998 picture_pool_NonEmpty(vout
->p
->decoder_pool
, true);
999 vout
->p
->pause
.is_on
= false;
1000 vout
->p
->pause
.date
= mdate();
1003 static void ThreadStep(vout_thread_t
*vout
, mtime_t
*duration
)
1007 if (vout
->p
->step
.last
<= VLC_TS_INVALID
)
1008 vout
->p
->step
.last
= vout
->p
->displayed
.timestamp
;
1011 if (ThreadDisplayPicture(vout
, true, &dummy
))
1014 vout
->p
->step
.timestamp
= vout
->p
->displayed
.timestamp
;
1016 if (vout
->p
->step
.last
> VLC_TS_INVALID
&&
1017 vout
->p
->step
.timestamp
> vout
->p
->step
.last
) {
1018 *duration
= vout
->p
->step
.timestamp
- vout
->p
->step
.last
;
1019 vout
->p
->step
.last
= vout
->p
->step
.timestamp
;
1020 /* TODO advance subpicture by the duration ... */
1024 static void ThreadChangeFullscreen(vout_thread_t
*vout
, bool fullscreen
)
1026 /* FIXME not sure setting "fullscreen" is good ... */
1027 var_SetBool(vout
, "fullscreen", fullscreen
);
1028 vout_SetDisplayFullscreen(vout
->p
->display
.vd
, fullscreen
);
1031 static void ThreadChangeOnTop(vout_thread_t
*vout
, bool is_on_top
)
1033 vout_SetWindowState(vout
->p
->display
.vd
,
1034 is_on_top
? VOUT_WINDOW_STATE_ABOVE
:
1035 VOUT_WINDOW_STATE_NORMAL
);
1038 static void ThreadChangeDisplayFilled(vout_thread_t
*vout
, bool is_filled
)
1040 vout_SetDisplayFilled(vout
->p
->display
.vd
, is_filled
);
1043 static void ThreadChangeZoom(vout_thread_t
*vout
, int num
, int den
)
1045 if (num
* 10 < den
) {
1048 } else if (num
> den
* 10) {
1052 vout_SetDisplayZoom(vout
->p
->display
.vd
, num
, den
);
1055 static void ThreadChangeAspectRatio(vout_thread_t
*vout
,
1056 unsigned num
, unsigned den
)
1058 const video_format_t
*source
= &vout
->p
->original
;
1060 if (num
> 0 && den
> 0) {
1061 num
*= source
->i_visible_height
;
1062 den
*= source
->i_visible_width
;
1063 vlc_ureduce(&num
, &den
, num
, den
, 0);
1065 vout_SetDisplayAspect(vout
->p
->display
.vd
, num
, den
);
1069 static void ThreadExecuteCropWindow(vout_thread_t
*vout
,
1070 unsigned crop_num
, unsigned crop_den
,
1071 unsigned x
, unsigned y
,
1072 unsigned width
, unsigned height
)
1074 const video_format_t
*source
= &vout
->p
->original
;
1076 vout_SetDisplayCrop(vout
->p
->display
.vd
,
1078 source
->i_x_offset
+ x
,
1079 source
->i_y_offset
+ y
,
1082 static void ThreadExecuteCropBorder(vout_thread_t
*vout
,
1083 unsigned left
, unsigned top
,
1084 unsigned right
, unsigned bottom
)
1086 const video_format_t
*source
= &vout
->p
->original
;
1087 ThreadExecuteCropWindow(vout
, 0, 0,
1090 /* At worst, it becomes < 0 (but unsigned) and will be rejected */
1091 source
->i_visible_width
- (left
+ right
),
1092 source
->i_visible_height
- (top
+ bottom
));
1095 static void ThreadExecuteCropRatio(vout_thread_t
*vout
,
1096 unsigned num
, unsigned den
)
1098 const video_format_t
*source
= &vout
->p
->original
;
1099 ThreadExecuteCropWindow(vout
, num
, den
,
1101 source
->i_visible_width
,
1102 source
->i_visible_height
);
1105 static int ThreadStart(vout_thread_t
*vout
, const vout_display_state_t
*state
)
1107 vlc_mouse_Init(&vout
->p
->mouse
);
1108 vout
->p
->decoder_fifo
= picture_fifo_New();
1109 vout
->p
->decoder_pool
= NULL
;
1110 vout
->p
->display_pool
= NULL
;
1111 vout
->p
->private_pool
= NULL
;
1113 vout
->p
->filter
.chain_static
=
1114 filter_chain_New( vout
, "video filter2", false,
1115 VoutVideoFilterStaticAllocationSetup
, NULL
, vout
);
1116 vout
->p
->filter
.chain_interactive
=
1117 filter_chain_New( vout
, "video filter2", false,
1118 VoutVideoFilterInteractiveAllocationSetup
, NULL
, vout
);
1120 vout_display_state_t state_default
;
1122 VoutGetDisplayCfg(vout
, &state_default
.cfg
, vout
->p
->display
.title
);
1123 state_default
.wm_state
= var_CreateGetBool(vout
, "video-on-top") ? VOUT_WINDOW_STATE_ABOVE
:
1124 VOUT_WINDOW_STATE_NORMAL
;
1125 state_default
.sar
.num
= 0;
1126 state_default
.sar
.den
= 0;
1128 state
= &state_default
;
1131 if (vout_OpenWrapper(vout
, vout
->p
->splitter_name
, state
))
1132 return VLC_EGENERIC
;
1133 if (vout_InitWrapper(vout
))
1134 return VLC_EGENERIC
;
1135 assert(vout
->p
->decoder_pool
);
1137 vout
->p
->displayed
.current
= NULL
;
1138 vout
->p
->displayed
.next
= NULL
;
1139 vout
->p
->displayed
.decoded
= NULL
;
1140 vout
->p
->displayed
.date
= VLC_TS_INVALID
;
1141 vout
->p
->displayed
.timestamp
= VLC_TS_INVALID
;
1142 vout
->p
->displayed
.qtype
= QTYPE_NONE
;
1143 vout
->p
->displayed
.is_interlaced
= false;
1145 vout
->p
->step
.last
= VLC_TS_INVALID
;
1146 vout
->p
->step
.timestamp
= VLC_TS_INVALID
;
1148 video_format_Print(VLC_OBJECT(vout
), "original format", &vout
->p
->original
);
1152 static void ThreadStop(vout_thread_t
*vout
, vout_display_state_t
*state
)
1154 /* Destroy translation tables */
1155 if (vout
->p
->display
.vd
) {
1156 if (vout
->p
->decoder_pool
) {
1157 ThreadFlush(vout
, true, INT64_MAX
);
1158 vout_EndWrapper(vout
);
1160 vout_CloseWrapper(vout
, state
);
1163 /* Destroy the video filters2 */
1164 filter_chain_Delete(vout
->p
->filter
.chain_interactive
);
1165 filter_chain_Delete(vout
->p
->filter
.chain_static
);
1167 if (vout
->p
->decoder_fifo
)
1168 picture_fifo_Delete(vout
->p
->decoder_fifo
);
1169 assert(!vout
->p
->decoder_pool
);
1172 static void ThreadInit(vout_thread_t
*vout
)
1174 vout
->p
->window
.is_unused
= true;
1175 vout
->p
->window
.object
= NULL
;
1176 vout
->p
->dead
= false;
1177 vout
->p
->is_late_dropped
= var_InheritBool(vout
, "drop-late-frames");
1178 vout
->p
->pause
.is_on
= false;
1179 vout
->p
->pause
.date
= VLC_TS_INVALID
;
1181 vout_chrono_Init(&vout
->p
->render
, 5, 10000); /* Arbitrary initial time */
1184 static void ThreadClean(vout_thread_t
*vout
)
1186 if (vout
->p
->window
.object
) {
1187 assert(vout
->p
->window
.is_unused
);
1188 vout_window_Delete(vout
->p
->window
.object
);
1190 vout_chrono_Clean(&vout
->p
->render
);
1191 vout
->p
->dead
= true;
1192 vout_control_Dead(&vout
->p
->control
);
1195 static int ThreadReinit(vout_thread_t
*vout
,
1196 const vout_configuration_t
*cfg
)
1198 video_format_t original
;
1199 if (VoutValidateFormat(&original
, cfg
->fmt
)) {
1200 ThreadStop(vout
, NULL
);
1202 return VLC_EGENERIC
;
1204 if (video_format_IsSimilar(&original
, &vout
->p
->original
)) {
1205 if (cfg
->dpb_size
<= vout
->p
->dpb_size
)
1207 msg_Warn(vout
, "DPB need to be increased");
1210 vout_display_state_t state
;
1211 memset(&state
, 0, sizeof(state
));
1213 ThreadStop(vout
, &state
);
1215 if (!state
.cfg
.is_fullscreen
) {
1216 state
.cfg
.display
.width
= 0;
1217 state
.cfg
.display
.height
= 0;
1221 /* FIXME current vout "variables" are not in sync here anymore
1222 * and I am not sure what to do */
1224 vout
->p
->original
= original
;
1225 vout
->p
->dpb_size
= cfg
->dpb_size
;
1226 if (ThreadStart(vout
, &state
)) {
1228 return VLC_EGENERIC
;
1233 /*****************************************************************************
1234 * Thread: video output thread
1235 *****************************************************************************
1236 * Video output thread. This function does only returns when the thread is
1237 * terminated. It handles the pictures arriving in the video heap and the
1238 * display device events.
1239 *****************************************************************************/
1240 static void *Thread(void *object
)
1242 vout_thread_t
*vout
= object
;
1244 vout_interlacing_support_t interlacing
= {
1245 .is_interlaced
= false,
1248 vout_postprocessing_support_t postprocessing
= {
1249 .qtype
= QTYPE_NONE
,
1252 mtime_t deadline
= VLC_TS_INVALID
;
1254 vout_control_cmd_t cmd
;
1256 /* FIXME remove thoses ugly timeouts
1258 while (!vout_control_Pop(&vout
->p
->control
, &cmd
, deadline
, 100000)) {
1260 case VOUT_CONTROL_INIT
:
1262 if (ThreadStart(vout
, NULL
)) {
1263 ThreadStop(vout
, NULL
);
1268 case VOUT_CONTROL_CLEAN
:
1269 ThreadStop(vout
, NULL
);
1272 case VOUT_CONTROL_REINIT
:
1273 if (ThreadReinit(vout
, cmd
.u
.cfg
))
1276 case VOUT_CONTROL_SUBPICTURE
:
1277 ThreadDisplaySubpicture(vout
, cmd
.u
.subpicture
);
1278 cmd
.u
.subpicture
= NULL
;
1280 case VOUT_CONTROL_FLUSH_SUBPICTURE
:
1281 ThreadFlushSubpicture(vout
, cmd
.u
.integer
);
1283 case VOUT_CONTROL_OSD_TITLE
:
1284 ThreadDisplayOsdTitle(vout
, cmd
.u
.string
);
1286 case VOUT_CONTROL_CHANGE_FILTERS
:
1287 ThreadChangeFilters(vout
, cmd
.u
.string
);
1289 case VOUT_CONTROL_CHANGE_SUB_FILTERS
:
1290 ThreadChangeSubFilters(vout
, cmd
.u
.string
);
1292 case VOUT_CONTROL_CHANGE_SUB_MARGIN
:
1293 ThreadChangeSubMargin(vout
, cmd
.u
.integer
);
1295 case VOUT_CONTROL_PAUSE
:
1296 ThreadChangePause(vout
, cmd
.u
.pause
.is_on
, cmd
.u
.pause
.date
);
1298 case VOUT_CONTROL_FLUSH
:
1299 ThreadFlush(vout
, false, cmd
.u
.time
);
1301 case VOUT_CONTROL_RESET
:
1304 case VOUT_CONTROL_STEP
:
1305 ThreadStep(vout
, cmd
.u
.time_ptr
);
1307 case VOUT_CONTROL_FULLSCREEN
:
1308 ThreadChangeFullscreen(vout
, cmd
.u
.boolean
);
1310 case VOUT_CONTROL_ON_TOP
:
1311 ThreadChangeOnTop(vout
, cmd
.u
.boolean
);
1313 case VOUT_CONTROL_DISPLAY_FILLED
:
1314 ThreadChangeDisplayFilled(vout
, cmd
.u
.boolean
);
1316 case VOUT_CONTROL_ZOOM
:
1317 ThreadChangeZoom(vout
, cmd
.u
.pair
.a
, cmd
.u
.pair
.b
);
1319 case VOUT_CONTROL_ASPECT_RATIO
:
1320 ThreadChangeAspectRatio(vout
, cmd
.u
.pair
.a
, cmd
.u
.pair
.b
);
1322 case VOUT_CONTROL_CROP_RATIO
:
1323 ThreadExecuteCropRatio(vout
, cmd
.u
.pair
.a
, cmd
.u
.pair
.b
);
1325 case VOUT_CONTROL_CROP_WINDOW
:
1326 ThreadExecuteCropWindow(vout
, 0, 0,
1327 cmd
.u
.window
.x
, cmd
.u
.window
.y
,
1328 cmd
.u
.window
.width
, cmd
.u
.window
.height
);
1330 case VOUT_CONTROL_CROP_BORDER
:
1331 ThreadExecuteCropBorder(vout
,
1332 cmd
.u
.border
.left
, cmd
.u
.border
.top
,
1333 cmd
.u
.border
.right
, cmd
.u
.border
.bottom
);
1338 vout_control_cmd_Clean(&cmd
);
1341 ThreadManage(vout
, &deadline
, &interlacing
, &postprocessing
);