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