demux: heif: refactor pic setup
[vlc.git] / src / video_output / video_output.c
blobcc95c4d8741014dee7f5ff1574026f50e43d6ad2
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
9 * $Id$
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 /*****************************************************************************
31 * Preamble
32 *****************************************************************************/
33 #ifdef HAVE_CONFIG_H
34 # include "config.h"
35 #endif
37 #include <vlc_common.h>
39 #include <stdlib.h> /* free() */
40 #include <string.h>
41 #include <assert.h>
43 #include <vlc_vout.h>
45 #include <vlc_filter.h>
46 #include <vlc_spu.h>
47 #include <vlc_vout_osd.h>
48 #include <vlc_image.h>
49 #include <vlc_plugin.h>
51 #include <libvlc.h>
52 #include "vout_internal.h"
53 #include "interlacing.h"
54 #include "display.h"
55 #include "window.h"
56 #include "../misc/variables.h"
58 /*****************************************************************************
59 * Local prototypes
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)
69 /**
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)
77 /* */
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)
83 return VLC_EGENERIC;
84 if (src->i_sar_num <= 0 || src->i_sar_den <= 0)
85 return VLC_EGENERIC;
87 /* */
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) {
93 dst->i_sar_num = 1;
94 dst->i_sar_den = 1;
96 video_format_FixRgb(dst);
97 return VLC_SUCCESS;
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))
122 return NULL;
124 /* Allocate descriptor */
125 vout_thread_t *vout = vlc_custom_create(object,
126 sizeof(*vout) + sizeof(*vout->p),
127 "video output");
128 if (!vout) {
129 video_format_Clean(&original);
130 return NULL;
133 /* */
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 */
156 vout_IntfInit(vout);
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");
168 /* */
169 vout_InitInterlacingSupport(vout, vout->p->displayed.is_interlaced);
171 /* Window */
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
178 #ifdef __APPLE__
179 .x = var_InheritInteger(vout, "video-x"),
180 .y = var_InheritInteger(vout, "video-y"),
181 #endif
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);
190 return NULL;
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;
199 } else
200 vout->p->window = NULL;
202 /* */
203 vlc_object_set_destructor(vout, VoutDestructor);
205 /* */
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);
212 return NULL;
215 vout_control_WaitEmpty(&vout->p->control);
217 if (vout->p->dead) {
218 msg_Err(vout, "video output creation failed");
219 vout_CloseAndRelease(vout);
220 return NULL;
223 vout->p->input = input;
224 if (vout->p->input)
225 spu_Attach(vout->p->spu, input, true);
227 return vout;
230 #undef vout_Request
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 */
238 if (vout) {
239 if (vout->p->input != input) {
240 if (vout->p->input)
241 spu_Attach(vout->p->spu, vout->p->input, false);
242 vout->p->input = input;
243 if (vout->p->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);
249 cmd.cfg = cfg;
250 vout_control_Push(&vout->p->control, &cmd);
251 vout_control_WaitEmpty(&vout->p->control);
253 if (cfg->fmt)
254 vout_IntfReinit(vout);
256 if (!vout->p->dead) {
257 msg_Dbg(object, "reusing provided vout");
258 return 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)
269 assert(vout);
271 if (vout->p->input)
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);
290 vout->p->spu = NULL;
291 vlc_mutex_unlock(&vout->p->spu_lock);
294 /* */
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);
310 /* */
311 vout_statistic_Clean(&vout->p->statistic);
313 /* */
314 vout_snapshot_Clean(&vout->p->snapshot);
316 video_format_Clean(&vout->p->original);
319 /* */
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);
352 if (picture)
353 picture_Release(picture);
355 return !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)
370 assert(title);
372 if (!vout->p->title.show)
373 return;
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)
381 assert(mouse);
382 vout_control_cmd_t cmd;
383 vout_control_cmd_Init(&cmd, VOUT_CONTROL_MOUSE_STATE);
384 cmd.mouse = *mouse;
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);
402 if (vout->p->spu)
403 channel = spu_RegisterChannel(vout->p->spu);
404 vlc_mutex_unlock(&vout->p->spu_lock);
406 return channel;
408 void vout_FlushSubpictureChannel( vout_thread_t *vout, int channel )
410 vout_control_PushInteger(&vout->p->control, VOUT_CONTROL_FLUSH_SUBPICTURE,
411 channel);
413 void vout_SetSpuHighlight( vout_thread_t *vout,
414 const vlc_spu_highlight_t *spu_hl )
416 vlc_mutex_lock(&vout->p->spu_lock);
417 if (vout->p->spu)
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);
438 return picture;
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
447 * read/used.
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);
458 else
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);
467 /* */
468 int vout_GetSnapshot(vout_thread_t *vout,
469 block_t **image_dst, picture_t **picture_dst,
470 video_format_t *fmt,
471 const char *type, vlc_tick_t timeout)
473 picture_t *picture = vout_snapshot_Get(&vout->p->snapshot, timeout);
474 if (!picture) {
475 msg_Err(vout, "Failed to grab a snapshot");
476 return VLC_EGENERIC;
479 if (image_dst) {
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);
491 return VLC_EGENERIC;
494 if (picture_dst)
495 *picture_dst = picture;
496 else
497 picture_Release(picture);
498 return VLC_SUCCESS;
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.
517 if (window != NULL)
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;
528 if (window != NULL)
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;
539 if (window != NULL)
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);
558 cmd.window.x = 0;
559 cmd.window.y = 0;
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,
567 is_filled);
570 void vout_ControlChangeZoom(vout_thread_t *vout, int num, int den)
572 if (num * 10 < den) {
573 num = den;
574 den *= 10;
575 } else if (num > den * 10) {
576 num = den * 10;
579 vout_ControlUpdateWindowSize(vout);
580 vout_control_PushPair(&vout->p->control, VOUT_CONTROL_ZOOM,
581 num, den);
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,
589 num, den);
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,
597 num, den);
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,
633 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);
669 /* */
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");
677 #endif
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) {
688 msar_num = 1;
689 msar_den = 1;
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;
711 /* */
712 static int FilterRestartCallback(vlc_object_t *p_this, char const *psz_var,
713 vlc_value_t oldval, vlc_value_t newval,
714 void *p_data)
716 (void) p_this; (void) psz_var; (void) oldval; (void) newval;
717 vout_ControlChangeFilters((vout_thread_t *)p_data, NULL);
718 return 0;
721 static int ThreadDelFilterCallbacks(filter_t *filter, void *opaque)
723 filter_DelProxyCallbacks((vlc_object_t *)opaque, filter,
724 FilterRestartCallback);
725 return VLC_SUCCESS;
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);
740 if (picture) {
741 picture_Reset(picture);
742 VideoFormatCopyCropAr(&picture->format, &filter->fmt_out.video);
744 return picture;
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;
768 if (!is_locked)
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);
772 if (!is_locked)
773 vlc_mutex_unlock(&vout->p->filter.lock);
776 typedef struct {
777 char *name;
778 config_chain_t *cfg;
779 } vout_filter_t;
781 static void ThreadChangeFilters(vout_thread_t *vout,
782 const video_format_t *source,
783 const char *filters,
784 int deinterlace,
785 bool is_locked)
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));
803 if (likely(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;
811 while (current) {
812 config_chain_t *cfg;
813 char *name;
814 char *next = config_ChainCreate(&name, &cfg, current);
816 if (name && *name) {
817 vout_filter_t *e = malloc(sizeof(*e));
819 if (likely(e)) {
820 e->name = name;
821 e->cfg = cfg;
822 if (!strcmp(e->name, "postproc"))
823 vlc_array_append_or_abort(&array_static, e);
824 else
825 vlc_array_append_or_abort(&array_interactive, e);
827 else {
828 if (cfg)
829 config_ChainDestroy(cfg);
830 free(name);
832 } else {
833 if (cfg)
834 config_ChainDestroy(cfg);
835 free(name);
837 free(current);
838 current = next;
841 if (!is_locked)
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 :
851 &array_interactive;
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,
860 NULL, NULL);
861 if (!filter)
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);
869 free(e->name);
870 free(e);
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;
893 if (source) {
894 video_format_Clean(&vout->p->filter.format);
895 video_format_Copy(&vout->p->filter.format, source);
898 if (!is_locked)
899 vlc_mutex_unlock(&vout->p->filter.lock);
903 /* */
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);
913 while (!picture) {
914 picture_t *decoded;
915 if (reuse && vout->p->displayed.decoded) {
916 decoded = picture_Hold(vout->p->displayed.decoded);
917 } else {
918 decoded = picture_fifo_Pop(vout->p->decoder_fifo);
919 if (decoded) {
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;
924 else
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);
932 continue;
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);
942 if (!decoded)
943 break;
944 reuse = false;
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);
958 if (!picture)
959 return VLC_EGENERIC;
961 assert(!vout->p->displayed.next);
962 if (!vout->p->displayed.current)
963 vout->p->displayed.current = picture;
964 else
965 vout->p->displayed.next = picture;
966 return VLC_SUCCESS;
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.
980 * */
982 assert(vout->p->spu_blend);
984 static const struct filter_video_callbacks cbs = {
985 .buffer_new = ConvertRGB32AndBlendBufferNew,
987 filter_owner_t owner = {
988 .video = &cbs,
990 filter_chain_t *filterc = filter_chain_NewVideo(vout, false, &owner);
991 if (!filterc)
992 return NULL;
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);
1002 return NULL;
1005 picture_Hold(pic);
1006 pic = filter_chain_VideoFilter(filterc, pic);
1007 filter_chain_Delete(filterc);
1009 if (pic)
1011 filter_t *swblend = filter_NewBlend(VLC_OBJECT(vout), &dst.video);
1012 if (swblend)
1014 bool success = picture_BlendSubpicture(pic, swblend, subpic) > 0;
1015 filter_DeleteBlend(swblend);
1016 if (success)
1017 return pic;
1019 picture_Release(pic);
1021 return NULL;
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);
1037 if (!filtered)
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;
1050 else
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 ||
1069 do_snapshot ||
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;
1074 if (do_dr_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;
1082 fmt_spu.i_width =
1083 fmt_spu.i_visible_width = place.width;
1084 fmt_spu.i_height =
1085 fmt_spu.i_visible_height = place.height;
1087 subpicture_chromas = vd->info.subpicture_chromas;
1088 } else {
1089 if (do_early_spu) {
1090 fmt_spu = vd->source;
1091 } else {
1092 fmt_spu = vd->fmt;
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,
1116 &vd->source,
1117 render_subtitle_date, render_osd_date,
1118 do_snapshot);
1120 * Perform rendering
1122 * We have to:
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);
1132 if (blent) {
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;
1138 } else
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. */
1143 if (do_snapshot)
1145 picture_t *copy = ConvertRGB32AndBlend(vout, blent, subpic);
1146 if (copy)
1147 snap_pic = copy;
1149 picture_Release(blent);
1153 subpicture_Delete(subpic);
1154 subpic = NULL;
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);
1162 if (!direct) {
1163 picture_Release(todisplay);
1164 if (subpic)
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
1182 if (do_snapshot)
1184 assert(snap_pic);
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) {
1195 if (subpic != NULL)
1196 subpicture_Delete(subpic);
1197 return VLC_EGENERIC;
1200 if (sys->display.use_dr) {
1201 vout_display_Prepare(vd, todisplay, subpic, todisplay->date);
1202 } else {
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,
1206 todisplay->date);
1208 if (!do_dr_spu && subpic)
1210 subpicture_Delete(subpic);
1211 subpic = NULL;
1215 vout_chrono_Stop(&sys->render);
1216 #if 0
1218 static int i = 0;
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));
1223 #endif
1225 /* Wait the real date (for rendering jitter) */
1226 #if 0
1227 vlc_tick_t delay = todisplay->date - vlc_tick_now();
1228 if (delay < 1000)
1229 msg_Warn(vout, "picture is late (%lld ms)", delay / 1000);
1230 #endif
1231 if (!is_forced)
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);
1237 if (subpic)
1238 subpicture_Delete(subpic);
1240 vout_statistic_AddDisplayed(&sys->statistic, 1);
1242 return VLC_SUCCESS;
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;
1252 if (first)
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
1274 * if and only if:
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);
1342 } else {
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;
1350 if (window != NULL)
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;
1362 if (last) {
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)
1380 *duration = 0;
1382 if (vout->p->step.last == VLC_TICK_INVALID)
1383 vout->p->step.last = vout->p->displayed.timestamp;
1385 if (ThreadDisplayPicture(vout, NULL))
1386 return;
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)
1409 return;
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,
1444 .sys = vout,
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;
1454 if (!state) {
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;
1464 #endif
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))
1472 goto error;
1473 if (vout_InitWrapper(vout))
1475 vout_CloseWrapper(vout, state);
1476 goto error;
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);
1494 return VLC_SUCCESS;
1495 error:
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;
1543 if (!cfg->fmt)
1545 vout->p->mouse_event = NULL;
1546 vout->p->opaque = NULL;
1547 return VLC_SUCCESS;
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);
1569 return VLC_SUCCESS;
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)
1583 #endif
1585 state.cfg.display.width = 0;
1586 state.cfg.display.height = 0;
1588 state.sar.num = 0;
1589 state.sar.den = 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;
1607 return VLC_SUCCESS;
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)
1617 switch(cmd.type) {
1618 case VOUT_CONTROL_CLEAN:
1619 ThreadStop(vout, NULL);
1620 return 1;
1621 case VOUT_CONTROL_REINIT:
1622 if (ThreadReinit(vout, cmd.cfg))
1623 return 1;
1624 break;
1625 case VOUT_CONTROL_CANCEL:
1626 ThreadCancel(vout, cmd.boolean);
1627 break;
1628 case VOUT_CONTROL_SUBPICTURE:
1629 ThreadDisplaySubpicture(vout, cmd.subpicture);
1630 cmd.subpicture = NULL;
1631 break;
1632 case VOUT_CONTROL_FLUSH_SUBPICTURE:
1633 ThreadFlushSubpicture(vout, cmd.integer);
1634 break;
1635 case VOUT_CONTROL_CHANGE_FILTERS:
1636 ThreadChangeFilters(vout, NULL,
1637 cmd.string != NULL ?
1638 cmd.string : vout->p->filter.configuration,
1639 -1, false);
1640 break;
1641 case VOUT_CONTROL_CHANGE_INTERLACE:
1642 ThreadChangeFilters(vout, NULL, vout->p->filter.configuration,
1643 cmd.boolean ? 1 : 0, false);
1644 break;
1645 case VOUT_CONTROL_PAUSE:
1646 ThreadChangePause(vout, cmd.pause.is_on, cmd.pause.date);
1647 break;
1648 case VOUT_CONTROL_FLUSH:
1649 ThreadFlush(vout, false, cmd.time);
1650 break;
1651 case VOUT_CONTROL_STEP:
1652 ThreadStep(vout, cmd.time_ptr);
1653 break;
1654 case VOUT_CONTROL_MOUSE_STATE:
1655 ThreadTranslateMouseState(vout, &cmd.mouse);
1656 break;
1657 case VOUT_CONTROL_DISPLAY_SIZE:
1658 vout_SetDisplaySize(vout->p->display.vd,
1659 cmd.window.width, cmd.window.height);
1660 break;
1661 case VOUT_CONTROL_DISPLAY_FILLED:
1662 vout_SetDisplayFilled(vout->p->display.vd, cmd.boolean);
1663 break;
1664 case VOUT_CONTROL_ZOOM:
1665 vout_SetDisplayZoom(vout->p->display.vd, cmd.pair.a, cmd.pair.b);
1666 break;
1667 case VOUT_CONTROL_ASPECT_RATIO:
1668 vout_SetDisplayAspect(vout->p->display.vd, cmd.pair.a, cmd.pair.b);
1669 break;
1670 case VOUT_CONTROL_CROP_RATIO:
1671 vout_SetDisplayCrop(vout->p->display.vd, cmd.pair.a, cmd.pair.b,
1672 0, 0, 0, 0);
1673 break;
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);
1678 break;
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);
1683 break;
1684 case VOUT_CONTROL_VIEWPOINT:
1685 vout_SetDisplayViewpoint(vout->p->display.vd, &cmd.viewpoint);
1686 break;
1687 default:
1688 break;
1690 vout_control_cmd_Clean(&cmd);
1691 return 0;
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;
1707 bool wait = false;
1709 if (ThreadStart(vout, NULL))
1710 goto out;
1712 for (;;) {
1713 vout_control_cmd_t cmd;
1715 if (wait)
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);
1719 } else {
1720 deadline = VLC_TICK_INVALID;
1722 while (!vout_control_Pop(&sys->control, &cmd, deadline))
1723 if (ThreadControl(vout, cmd))
1724 goto out;
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);
1735 out:
1736 vout->p->dead = true;
1737 vout_control_Dead(&vout->p->control);
1738 return NULL;