vlc/src/video_output/video_output.c

2323 lines
72 KiB
C

/*****************************************************************************
* video_output.c : video output thread
*
* This module describes the programming interface for video output threads.
* It includes functions allowing to open a new thread, send pictures to a
* thread, and destroy a previously oppened video output thread.
*****************************************************************************
* Copyright (C) 2000-2019 VLC authors, VideoLAN and Videolabs SAS
*
* Authors: Vincent Seguin <seguin@via.ecp.fr>
* Gildas Bazin <gbazin@videolan.org>
* Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_arrays.h>
#include <vlc_configuration.h>
#include <math.h>
#include <stdlib.h> /* free() */
#include <string.h>
#include <assert.h>
#include <vlc_vout.h>
#include <vlc_filter.h>
#include <vlc_spu.h>
#include <vlc_vout_osd.h>
#include <vlc_image.h>
#include <vlc_plugin.h>
#include <vlc_codec.h>
#include <vlc_tracer.h>
#include <vlc_atomic.h>
#include <libvlc.h>
#include "vout_private.h"
#include "vout_internal.h"
#include "display.h"
#include "snapshot.h"
#include "video_window.h"
#include "../misc/variables.h"
#include "../misc/threads.h"
#include "../clock/clock.h"
#include "statistic.h"
#include "chrono.h"
#include "control.h"
typedef struct vout_thread_sys_t
{
struct vout_thread_t obj;
vout_interlacing_state_t interlacing;
bool dummy;
/* Splitter module if used */
char *splitter_name;
const char *str_id;
vlc_mutex_t clock_lock;
bool clock_nowait; /* protected by vlc_clock_Lock()/vlc_clock_Unlock() */
bool wait_interrupted;
vlc_clock_t *clock;
vlc_clock_listener_id *clock_listener_id;
float rate;
vlc_tick_t delay;
video_format_t original; /* Original format ie coming from the decoder */
/* */
struct {
vlc_rational_t dar;
struct vout_crop crop;
} source;
/* Snapshot interface */
struct vout_snapshot *snapshot;
/* Statistics */
vout_statistic_t statistic;
/* Subpicture unit */
spu_t *spu;
vlc_blender_t *spu_blend;
/* Thread & synchronization */
vout_control_t control;
atomic_bool control_is_terminated; // shutdown the vout thread
vlc_thread_t thread;
struct {
vlc_tick_t date;
vlc_tick_t timestamp;
bool is_interlaced;
picture_t *decoded; // decoded picture before passed through chain_static
picture_t *current;
} displayed;
struct {
bool is_on;
vlc_tick_t date;
} pause;
/* OSD title configuration */
struct {
bool show;
int timeout;
int position;
} title;
/* */
bool is_late_dropped;
/* */
vlc_mouse_t mouse;
/* Video output window */
bool window_enabled;
unsigned window_width; /* protected by display_lock */
unsigned window_height; /* protected by display_lock */
vlc_mutex_t window_lock;
vlc_decoder_device *dec_device;
/* Video output display */
vout_display_cfg_t display_cfg;
vout_display_t *display;
vlc_queuedmutex_t display_lock;
/* Video filter2 chain */
struct {
vlc_mutex_t lock;
bool changed;
bool new_interlaced;
char *configuration;
video_format_t src_fmt;
vlc_video_context *src_vctx;
struct filter_chain_t *chain_static;
struct filter_chain_t *chain_interactive;
} filter;
picture_fifo_t *decoder_fifo;
struct {
vout_chrono_t static_filter;
vout_chrono_t render; /**< picture render time estimator */
} chrono;
unsigned frame_next_count;
vlc_atomic_rc_t rc;
picture_pool_t *private_pool; // interactive + static filters & blending
} vout_thread_sys_t;
#define VOUT_THREAD_TO_SYS(vout) \
container_of(vout, vout_thread_sys_t, obj.obj)
/* Amount of pictures in the private pool:
* 3 for interactive+static filters, 1 for SPU blending, 1 for currently displayed */
#define FILTER_POOL_SIZE (3+1+1)
/* Maximum delay between 2 displayed pictures.
* XXX it is needed for now but should be removed in the long term.
*/
#define VOUT_REDISPLAY_DELAY VLC_TICK_FROM_MS(80)
/**
* Late pictures having a delay higher than this value are thrashed.
*/
#define VOUT_DISPLAY_LATE_THRESHOLD VLC_TICK_FROM_MS(20)
/* Better be in advance when awakening than late... */
#define VOUT_MWAIT_TOLERANCE VLC_TICK_FROM_MS(4)
/* */
static inline struct vlc_tracer *GetTracer(vout_thread_sys_t *sys)
{
return sys->str_id == NULL ? NULL :
vlc_object_get_tracer(VLC_OBJECT(&sys->obj));
}
static inline void VoutResetChronoLocked(vout_thread_sys_t *sys)
{
vlc_queuedmutex_assert(&sys->display_lock);
/* Arbitrary initial time */
vout_chrono_Init(&sys->chrono.render, 5, VLC_TICK_FROM_MS(10));
vout_chrono_Init(&sys->chrono.static_filter, 4, VLC_TICK_FROM_MS(0));
}
static bool VoutCheckFormat(const video_format_t *src)
{
if (src->i_width == 0 || src->i_width > 8192 ||
src->i_height == 0 || src->i_height > 8192)
return false;
if (src->i_sar_num <= 0 || src->i_sar_den <= 0)
return false;
return true;
}
static void VoutFixFormat(video_format_t *dst, const video_format_t *src)
{
video_format_Copy(dst, src);
dst->i_chroma = vlc_fourcc_GetCodec(VIDEO_ES, src->i_chroma);
VoutFixFormatAR( dst );
vlc_viewpoint_clip( &dst->pose );
}
static void VoutRenderWakeUpUrgent(vout_thread_sys_t *sys)
{
/* The assignment to sys->clock is protected by sys->lock */
vlc_mutex_lock(&sys->clock_lock);
if (sys->clock)
{
/* Wake up the clock-wait between prepare() and display() */
vlc_clock_Lock(sys->clock);
sys->clock_nowait = true;
vlc_clock_Wake(sys->clock);
vlc_clock_Unlock(sys->clock);
}
vlc_mutex_unlock(&sys->clock_lock);
}
static bool VideoFormatIsCropArEqual(video_format_t *dst,
const video_format_t *src)
{
return dst->i_sar_num * src->i_sar_den == dst->i_sar_den * src->i_sar_num &&
dst->i_x_offset == src->i_x_offset &&
dst->i_y_offset == src->i_y_offset &&
dst->i_visible_width == src->i_visible_width &&
dst->i_visible_height == src->i_visible_height;
}
static void vout_UpdateWindowSizeLocked(vout_thread_sys_t *vout)
{
vout_thread_sys_t *sys = vout;
if (unlikely(sys->original.i_chroma == 0))
return; /* not started yet, postpone size computaton */
vlc_mutex_assert(&sys->window_lock);
vout_display_ResizeWindow(sys->display_cfg.window, &sys->original,
&sys->source.dar, &sys->source.crop,
&sys->display_cfg.display);
}
/* */
void vout_GetResetStatistic(vout_thread_t *vout, unsigned *restrict displayed,
unsigned *restrict lost, unsigned *restrict late)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vout_statistic_GetReset( &sys->statistic, displayed, lost, late );
}
bool vout_IsEmpty(vout_thread_t *vout)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
if (!sys->decoder_fifo)
return true;
return picture_fifo_IsEmpty(sys->decoder_fifo);
}
void vout_DisplayTitle(vout_thread_t *vout, const char *title)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
assert(title);
if (!sys->title.show)
return;
vout_OSDText(vout, VOUT_SPU_CHANNEL_OSD, sys->title.position,
VLC_TICK_FROM_MS(sys->title.timeout), title);
}
void vout_FilterMouse(vout_thread_t *vout, vlc_mouse_t *mouse)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
vlc_mouse_t tmp[2], *m = mouse;
/* Pass mouse events through the filter chains. */
vlc_mutex_lock(&sys->filter.lock);
if (sys->filter.chain_static != NULL
&& sys->filter.chain_interactive != NULL) {
if (!filter_chain_MouseFilter(sys->filter.chain_interactive,
&tmp[0], m))
m = &tmp[0];
if (!filter_chain_MouseFilter(sys->filter.chain_static,
&tmp[1], m))
m = &tmp[1];
}
vlc_mutex_unlock(&sys->filter.lock);
if (mouse != m)
*mouse = *m;
}
void vout_PutSubpicture( vout_thread_t *vout, subpicture_t *subpic )
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
if (sys->spu != NULL)
spu_PutSubpicture(sys->spu, subpic);
else
subpicture_Delete(subpic);
}
ssize_t vout_RegisterSubpictureChannel( vout_thread_t *vout )
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
ssize_t channel = VOUT_SPU_CHANNEL_INVALID;
if (sys->spu)
channel = spu_RegisterChannel(sys->spu);
return channel;
}
ssize_t vout_RegisterSubpictureChannelInternal(vout_thread_t *vout,
vlc_clock_t *clock,
enum vlc_vout_order *out_order)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
ssize_t channel = VOUT_SPU_CHANNEL_INVALID;
if (sys->spu)
channel = spu_RegisterChannelInternal(sys->spu, clock, out_order);
return channel;
}
void vout_UnregisterSubpictureChannel( vout_thread_t *vout, size_t channel )
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
assert(sys->spu);
spu_UnregisterChannel(sys->spu, channel);
}
void vout_FlushSubpictureChannel( vout_thread_t *vout, size_t channel )
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
if (sys->spu)
spu_ClearChannel(sys->spu, channel);
}
void vout_SetSpuHighlight( vout_thread_t *vout,
const vlc_spu_highlight_t *spu_hl )
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
if (sys->spu)
spu_SetHighlight(sys->spu, spu_hl);
}
/**
* It gives to the vout a picture to be displayed.
*
* Becareful, after vout_PutPicture is called, picture_t::p_next cannot be
* read/used.
*/
void vout_PutPicture(vout_thread_t *vout, picture_t *picture)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
assert( !picture_HasChainedPics( picture ) );
picture_fifo_Push(sys->decoder_fifo, picture);
vout_control_Wake(&sys->control);
}
/* */
int vout_GetSnapshot(vout_thread_t *vout,
block_t **image_dst, picture_t **picture_dst,
video_format_t *fmt,
const char *type, vlc_tick_t timeout)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
picture_t *picture = vout_snapshot_Get(sys->snapshot, timeout);
if (!picture) {
msg_Err(vout, "Failed to grab a snapshot");
return VLC_EGENERIC;
}
if (image_dst) {
vlc_fourcc_t codec = VLC_CODEC_PNG;
if (type && image_Type2Fourcc(type))
codec = image_Type2Fourcc(type);
const int override_width = var_InheritInteger(vout, "snapshot-width");
const int override_height = var_InheritInteger(vout, "snapshot-height");
if (picture_Export(VLC_OBJECT(vout), image_dst, fmt,
picture, codec, override_width, override_height, false)) {
msg_Err(vout, "Failed to convert image for snapshot");
picture_Release(picture);
return VLC_EGENERIC;
}
}
if (picture_dst)
*picture_dst = picture;
else
picture_Release(picture);
return VLC_SUCCESS;
}
/* vout_Control* are usable by anyone at anytime */
void vout_ChangeFullscreen(vout_thread_t *vout, const char *id)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vlc_mutex_lock(&sys->window_lock);
vlc_window_SetFullScreen(sys->display_cfg.window, id);
vlc_mutex_unlock(&sys->window_lock);
}
void vout_ChangeWindowed(vout_thread_t *vout)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vlc_mutex_lock(&sys->window_lock);
vlc_window_UnsetFullScreen(sys->display_cfg.window);
/* Attempt to reset the intended window size */
vout_UpdateWindowSizeLocked(sys);
vlc_mutex_unlock(&sys->window_lock);
}
void vout_ChangeWindowState(vout_thread_t *vout, unsigned st)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vlc_mutex_lock(&sys->window_lock);
vlc_window_SetState(sys->display_cfg.window, st);
vlc_mutex_unlock(&sys->window_lock);
}
void vout_ChangeDisplaySize(vout_thread_t *vout,
unsigned width, unsigned height,
void (*cb)(void *), void *opaque)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
VoutRenderWakeUpUrgent(sys);
/* DO NOT call this outside the vout window callbacks */
vlc_queuedmutex_lock(&sys->display_lock);
sys->window_width = width;
sys->window_height = height;
if (sys->display != NULL)
vout_display_SetSize(sys->display, width, height);
if (cb != NULL)
cb(opaque);
vlc_queuedmutex_unlock(&sys->display_lock);
}
void vout_ChangeDisplayFitting(vout_thread_t *vout, enum vlc_video_fitting fit)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vlc_mutex_lock(&sys->window_lock);
sys->display_cfg.display.fitting = fit;
/* no window size update here */
vlc_queuedmutex_lock(&sys->display_lock);
vlc_mutex_unlock(&sys->window_lock);
if (sys->display != NULL)
vout_SetDisplayFitting(sys->display, fit);
vlc_queuedmutex_unlock(&sys->display_lock);
}
void vout_ChangeZoom(vout_thread_t *vout, unsigned num, unsigned den)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
if (num != 0 && den != 0) {
vlc_ureduce(&num, &den, num, den, 0);
} else {
num = 1;
den = 1;
}
if (num * 10 < den) {
num = 1;
den = 10;
} else if (num > den * 10) {
num = 10;
den = 1;
}
vlc_mutex_lock(&sys->window_lock);
sys->display_cfg.display.zoom.num = num;
sys->display_cfg.display.zoom.den = den;
vout_UpdateWindowSizeLocked(sys);
vlc_queuedmutex_lock(&sys->display_lock);
vlc_mutex_unlock(&sys->window_lock);
if (sys->display != NULL)
vout_SetDisplayZoom(sys->display, num, den);
vlc_queuedmutex_unlock(&sys->display_lock);
}
static void vout_SetAspectRatio(vout_thread_sys_t *sys,
unsigned dar_num, unsigned dar_den)
{
sys->source.dar.num = dar_num;
sys->source.dar.den = dar_den;
}
void vout_ChangeDisplayAspectRatio(vout_thread_t *vout,
unsigned dar_num, unsigned dar_den)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vlc_mutex_lock(&sys->window_lock);
vout_SetAspectRatio(sys, dar_num, dar_den);
vout_UpdateWindowSizeLocked(sys);
vlc_queuedmutex_lock(&sys->display_lock);
vlc_mutex_unlock(&sys->window_lock);
if (sys->display != NULL)
vout_SetDisplayAspect(sys->display, dar_num, dar_den);
vlc_queuedmutex_unlock(&sys->display_lock);
}
void vout_ChangeCrop(vout_thread_t *vout,
const struct vout_crop *restrict crop)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vlc_mutex_lock(&sys->window_lock);
sys->source.crop = *crop;
vout_UpdateWindowSizeLocked(sys);
vlc_queuedmutex_lock(&sys->display_lock);
vlc_mutex_unlock(&sys->window_lock);
if (sys->display != NULL)
vout_SetDisplayCrop(sys->display, crop);
vlc_queuedmutex_unlock(&sys->display_lock);
}
void vout_ControlChangeFilters(vout_thread_t *vout, const char *filters)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vlc_mutex_lock(&sys->filter.lock);
if (sys->filter.configuration)
{
if (filters == NULL || strcmp(sys->filter.configuration, filters))
{
free(sys->filter.configuration);
sys->filter.configuration = filters ? strdup(filters) : NULL;
sys->filter.changed = true;
}
}
else if (filters != NULL)
{
sys->filter.configuration = strdup(filters);
sys->filter.changed = true;
}
vlc_mutex_unlock(&sys->filter.lock);
}
void vout_ControlChangeInterlacing(vout_thread_t *vout, bool set)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vlc_mutex_lock(&sys->filter.lock);
sys->filter.new_interlaced = set;
vlc_mutex_unlock(&sys->filter.lock);
}
void vout_ControlChangeSubSources(vout_thread_t *vout, const char *filters)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
if (likely(sys->spu != NULL))
spu_ChangeSources(sys->spu, filters);
}
void vout_ControlChangeSubFilters(vout_thread_t *vout, const char *filters)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
if (likely(sys->spu != NULL))
spu_ChangeFilters(sys->spu, filters);
}
void vout_ChangeSpuChannelMargin(vout_thread_t *vout,
enum vlc_vout_order order, int margin)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
if (likely(sys->spu != NULL))
spu_ChangeChannelOrderMargin(sys->spu, order, margin);
}
void vout_ChangeViewpoint(vout_thread_t *vout,
const vlc_viewpoint_t *p_viewpoint)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vlc_mutex_lock(&sys->window_lock);
sys->display_cfg.viewpoint = *p_viewpoint;
/* no window size update here */
vlc_mutex_unlock(&sys->window_lock);
VoutRenderWakeUpUrgent(sys);
vlc_queuedmutex_lock(&sys->display_lock);
if (sys->display != NULL)
vout_SetDisplayViewpoint(sys->display, p_viewpoint);
vlc_queuedmutex_unlock(&sys->display_lock);
}
void vout_ChangeIccProfile(vout_thread_t *vout,
vlc_icc_profile_t *profile)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vlc_queuedmutex_lock(&sys->display_lock);
free(sys->display_cfg.icc_profile);
sys->display_cfg.icc_profile = profile;
if (sys->display != NULL)
vout_SetDisplayIccProfile(sys->display, profile);
vlc_queuedmutex_unlock(&sys->display_lock);
}
/* */
static void VoutGetDisplayCfg(vout_thread_sys_t *p_vout, const video_format_t *fmt, vout_display_cfg_t *cfg)
{
vout_thread_t *vout = &p_vout->obj;
/* Load configuration */
cfg->viewpoint = fmt->pose;
const int display_width = var_GetInteger(vout, "width");
const int display_height = var_GetInteger(vout, "height");
cfg->display.width = display_width > 0 ? display_width : 0;
cfg->display.height = display_height > 0 ? display_height : 0;
cfg->display.fitting = var_GetBool(vout, "autoscale")
? var_InheritFit(VLC_OBJECT(vout)) : VLC_VIDEO_FIT_NONE;
unsigned msar_num, msar_den;
if (var_InheritURational(vout, &msar_num, &msar_den, "monitor-par") ||
msar_num <= 0 || msar_den <= 0) {
msar_num = 1;
msar_den = 1;
}
cfg->display.sar.num = msar_num;
cfg->display.sar.den = msar_den;
unsigned zoom_den = 1000;
unsigned zoom_num = zoom_den * var_GetFloat(vout, "zoom");
vlc_ureduce(&zoom_num, &zoom_den, zoom_num, zoom_den, 0);
cfg->display.zoom.num = zoom_num;
cfg->display.zoom.den = zoom_den;
cfg->display.align.vertical = VLC_VIDEO_ALIGN_CENTER;
cfg->display.align.horizontal = VLC_VIDEO_ALIGN_CENTER;
const int align_mask = var_GetInteger(vout, "align");
if (align_mask & VOUT_ALIGN_LEFT)
cfg->display.align.horizontal = VLC_VIDEO_ALIGN_LEFT;
else if (align_mask & VOUT_ALIGN_RIGHT)
cfg->display.align.horizontal = VLC_VIDEO_ALIGN_RIGHT;
if (align_mask & VOUT_ALIGN_TOP)
cfg->display.align.vertical = VLC_VIDEO_ALIGN_TOP;
else if (align_mask & VOUT_ALIGN_BOTTOM)
cfg->display.align.vertical = VLC_VIDEO_ALIGN_BOTTOM;
}
/* */
static int FilterRestartCallback(vlc_object_t *p_this, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval,
void *p_data)
{
(void) p_this; (void) psz_var; (void) oldval; (void) newval;
vout_ControlChangeFilters((vout_thread_t *)p_data, NULL);
return 0;
}
static int DelFilterCallbacks(filter_t *filter, void *opaque)
{
vout_thread_sys_t *sys = opaque;
filter_DelProxyCallbacks(VLC_OBJECT(&sys->obj), filter,
FilterRestartCallback);
return VLC_SUCCESS;
}
static void DelAllFilterCallbacks(vout_thread_sys_t *vout)
{
vout_thread_sys_t *sys = vout;
assert(sys->filter.chain_interactive != NULL);
filter_chain_ForEach(sys->filter.chain_interactive,
DelFilterCallbacks, vout);
}
static picture_t *VoutVideoFilterInteractiveNewPicture(filter_t *filter)
{
vout_thread_sys_t *sys = filter->owner.sys;
picture_t *picture = picture_pool_Get(sys->private_pool);
if (picture) {
picture_Reset(picture);
video_format_CopyCropAr(&picture->format, &filter->fmt_out.video);
}
return picture;
}
static picture_t *VoutVideoFilterStaticNewPicture(filter_t *filter)
{
vout_thread_sys_t *sys = filter->owner.sys;
vlc_mutex_assert(&sys->filter.lock);
if (filter_chain_IsEmpty(sys->filter.chain_interactive))
// we may be using the last filter of both chains, so we get the picture
// from the display module pool, just like for the last interactive filter.
return VoutVideoFilterInteractiveNewPicture(filter);
return picture_NewFromFormat(&filter->fmt_out.video);
}
static void FilterFlush(vout_thread_sys_t *sys, bool is_locked)
{
if (sys->displayed.current)
{
picture_Release( sys->displayed.current );
sys->displayed.current = NULL;
sys->displayed.date = VLC_TICK_INVALID;
}
if (!is_locked)
vlc_mutex_lock(&sys->filter.lock);
filter_chain_VideoFlush(sys->filter.chain_static);
filter_chain_VideoFlush(sys->filter.chain_interactive);
if (!is_locked)
vlc_mutex_unlock(&sys->filter.lock);
}
typedef struct {
char *name;
config_chain_t *cfg;
} vout_filter_t;
static void ChangeFilters(vout_thread_sys_t *vout)
{
vout_thread_sys_t *sys = vout;
FilterFlush(vout, true);
DelAllFilterCallbacks(vout);
vlc_array_t array_static;
vlc_array_t array_interactive;
vlc_array_init(&array_static);
vlc_array_init(&array_interactive);
if (sys->interlacing.has_deint)
{
vout_filter_t *e = malloc(sizeof(*e));
if (likely(e))
{
char *filter = var_InheritString(&vout->obj, "deinterlace-filter");
free(config_ChainCreate(&e->name, &e->cfg, filter));
free(filter);
vlc_array_append_or_abort(&array_static, e);
}
}
char *current = sys->filter.configuration ? strdup(sys->filter.configuration) : NULL;
while (current) {
config_chain_t *cfg;
char *name;
char *next = config_ChainCreate(&name, &cfg, current);
if (name && *name) {
vout_filter_t *e = malloc(sizeof(*e));
if (likely(e)) {
e->name = name;
e->cfg = cfg;
if (!strcmp(e->name, "postproc"))
vlc_array_append_or_abort(&array_static, e);
else
vlc_array_append_or_abort(&array_interactive, e);
}
else {
if (cfg)
config_ChainDestroy(cfg);
free(name);
}
} else {
if (cfg)
config_ChainDestroy(cfg);
free(name);
}
free(current);
current = next;
}
es_format_t fmt_target;
es_format_InitFromVideo(&fmt_target, &sys->filter.src_fmt);
vlc_video_context *vctx_target = sys->filter.src_vctx;
const es_format_t *p_fmt_current = &fmt_target;
vlc_video_context *vctx_current = vctx_target;
for (int a = 0; a < 2; a++) {
vlc_array_t *array = a == 0 ? &array_static :
&array_interactive;
filter_chain_t *chain = a == 0 ? sys->filter.chain_static :
sys->filter.chain_interactive;
filter_chain_Reset(chain, p_fmt_current, vctx_current, p_fmt_current);
for (size_t i = 0; i < vlc_array_count(array); i++) {
vout_filter_t *e = vlc_array_item_at_index(array, i);
msg_Dbg(&vout->obj, "Adding '%s' as %s", e->name, a == 0 ? "static" : "interactive");
filter_t *filter = filter_chain_AppendFilter(chain, e->name, e->cfg,
NULL);
if (!filter)
msg_Err(&vout->obj, "Failed to add filter '%s'", e->name);
else if (a == 1) /* Add callbacks for interactive filters */
filter_AddProxyCallbacks(&vout->obj, filter, FilterRestartCallback);
config_ChainDestroy(e->cfg);
free(e->name);
free(e);
}
if (!filter_chain_IsEmpty(chain))
{
p_fmt_current = filter_chain_GetFmtOut(chain);
vctx_current = filter_chain_GetVideoCtxOut(chain);
}
vlc_array_clear(array);
}
if (!es_format_IsSimilar(p_fmt_current, &fmt_target)) {
es_format_LogDifferences(vlc_object_logger(&vout->obj),
"current", p_fmt_current, "new", &fmt_target);
/* Shallow local copy */
es_format_t tmp = *p_fmt_current;
/* Assign the same chroma to compare everything except the chroma */
tmp.i_codec = fmt_target.i_codec;
tmp.video.i_chroma = fmt_target.video.i_chroma;
int ret = VLC_EGENERIC;
bool only_chroma_changed = es_format_IsSimilar(&tmp, &fmt_target);
if (only_chroma_changed)
{
picture_pool_t *new_private_pool =
picture_pool_NewFromFormat(&p_fmt_current->video,
FILTER_POOL_SIZE);
if (new_private_pool != NULL)
{
msg_Dbg(&vout->obj, "Changing vout format to %4.4s",
(const char *) &p_fmt_current->video.i_chroma);
/* Only the chroma changed, request the vout to update the format */
ret = vout_SetDisplayFormat(sys->display, &p_fmt_current->video,
vctx_current);
if (ret != VLC_SUCCESS)
{
picture_pool_Release(new_private_pool);
msg_Dbg(&vout->obj, "Changing vout format to %4.4s failed",
(const char *) &p_fmt_current->video.i_chroma);
}
else
{
// update the pool
picture_pool_Release(sys->private_pool);
sys->private_pool = new_private_pool;
}
}
}
if (ret != VLC_SUCCESS)
{
msg_Dbg(&vout->obj, "Adding a filter to compensate for format changes in interactive chain (%p)",
(void*)sys->filter.chain_interactive);
if (filter_chain_AppendConverter(sys->filter.chain_interactive,
&fmt_target) != VLC_SUCCESS) {
msg_Err(&vout->obj, "Failed to compensate for the format changes, removing all filters");
DelAllFilterCallbacks(vout);
filter_chain_Reset(sys->filter.chain_static, &fmt_target, vctx_target, &fmt_target);
filter_chain_Reset(sys->filter.chain_interactive, &fmt_target, vctx_target, &fmt_target);
}
}
}
es_format_Clean(&fmt_target);
sys->filter.changed = false;
}
static bool IsPictureLate(vout_thread_sys_t *vout, picture_t *decoded,
vlc_tick_t system_now, vlc_tick_t system_pts)
{
vout_thread_sys_t *sys = vout;
const vlc_tick_t prepare_decoded_duration = vout_chrono_GetHigh(&sys->chrono.render) +
vout_chrono_GetHigh(&sys->chrono.static_filter);
vlc_tick_t late = system_now + prepare_decoded_duration - system_pts;
vlc_tick_t late_threshold;
if (decoded->format.i_frame_rate && decoded->format.i_frame_rate_base) {
late_threshold = vlc_tick_from_samples(decoded->format.i_frame_rate_base, decoded->format.i_frame_rate);
}
else
late_threshold = VOUT_DISPLAY_LATE_THRESHOLD;
if (late > late_threshold) {
struct vlc_tracer *tracer = GetTracer(vout);
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", sys->str_id, "toolate");
msg_Warn(&vout->obj, "picture is too late to be displayed (missing %"PRId64" ms)", MS_FROM_VLC_TICK(late));
return true;
}
return false;
}
/* */
VLC_USED
static picture_t *PreparePicture(vout_thread_sys_t *vout, bool reuse_decoded,
bool frame_by_frame)
{
vout_thread_sys_t *sys = vout;
bool is_late_dropped = sys->is_late_dropped && !frame_by_frame;
vlc_mutex_lock(&sys->filter.lock);
picture_t *picture = filter_chain_VideoFilter(sys->filter.chain_static, NULL);
assert(!reuse_decoded || !picture);
while (!picture) {
picture_t *decoded;
if (unlikely(reuse_decoded && sys->displayed.decoded)) {
decoded = picture_Hold(sys->displayed.decoded);
} else {
decoded = picture_fifo_Pop(sys->decoder_fifo);
if (decoded) {
if (is_late_dropped && !decoded->b_force)
{
const vlc_tick_t system_now = vlc_tick_now();
vlc_clock_Lock(sys->clock);
const vlc_tick_t system_pts =
vlc_clock_ConvertToSystem(sys->clock, system_now,
decoded->date, sys->rate);
vlc_clock_Unlock(sys->clock);
if (IsPictureLate(vout, decoded, system_now, system_pts))
{
picture_Release(decoded);
vout_statistic_AddLost(&sys->statistic, 1);
/* A picture dropped means discontinuity for the
* filters and we need to notify eg. deinterlacer. */
filter_chain_VideoFlush(sys->filter.chain_static);
continue;
}
}
if (!VideoFormatIsCropArEqual(&decoded->format, &sys->filter.src_fmt))
{
// we received an aspect ratio change
// Update the filters with the filter source format with the new aspect ratio
video_format_Clean(&sys->filter.src_fmt);
video_format_Copy(&sys->filter.src_fmt, &decoded->format);
if (sys->filter.src_vctx)
vlc_video_context_Release(sys->filter.src_vctx);
vlc_video_context *pic_vctx = picture_GetVideoContext(decoded);
sys->filter.src_vctx = pic_vctx ? vlc_video_context_Hold(pic_vctx) : NULL;
ChangeFilters(vout);
}
}
}
if (!decoded)
break;
reuse_decoded = false;
if (sys->displayed.decoded)
picture_Release(sys->displayed.decoded);
sys->displayed.decoded = picture_Hold(decoded);
sys->displayed.timestamp = decoded->date;
sys->displayed.is_interlaced = !decoded->b_progressive;
vout_chrono_Start(&sys->chrono.static_filter);
picture = filter_chain_VideoFilter(sys->filter.chain_static, sys->displayed.decoded);
vout_chrono_Stop(&sys->chrono.static_filter);
}
vlc_mutex_unlock(&sys->filter.lock);
return picture;
}
static vlc_decoder_device * VoutHoldDecoderDevice(vlc_object_t *o, void *opaque)
{
VLC_UNUSED(o);
vout_thread_sys_t *sys = opaque;
return sys->dec_device ? vlc_decoder_device_Hold( sys->dec_device ) : NULL;
}
static const struct filter_video_callbacks vout_video_cbs = {
NULL, VoutHoldDecoderDevice,
};
static picture_t *ConvertRGBAAndBlend(vout_thread_sys_t *vout, picture_t *pic,
vlc_render_subpicture *subpic)
{
vout_thread_sys_t *sys = vout;
/* This function will convert the pic to RGBA and blend the subpic to it.
* The returned pic can't be used to display since the chroma will be
* different than the "vout display" one, but it can be used for snapshots.
* */
assert(sys->spu_blend);
filter_owner_t owner = {
.video = &vout_video_cbs,
.sys = vout,
};
filter_chain_t *filterc = filter_chain_NewVideo(&vout->obj, false, &owner);
if (!filterc)
return NULL;
es_format_t src = sys->spu_blend->fmt_out;
es_format_t dst = src;
dst.video.i_chroma = VLC_CODEC_RGBA;
filter_chain_Reset(filterc, &src,
NULL /* TODO output video context of blender */,
&dst);
if (filter_chain_AppendConverter(filterc, &dst) != VLC_SUCCESS)
{
filter_chain_Delete(filterc);
return NULL;
}
picture_Hold(pic);
pic = filter_chain_VideoFilter(filterc, pic);
filter_chain_Delete(filterc);
if (pic)
{
vlc_blender_t *swblend = filter_NewBlend(VLC_OBJECT(&vout->obj), &dst.video);
if (swblend)
{
bool success = picture_BlendSubpicture(pic, swblend, subpic) > 0;
filter_DeleteBlend(swblend);
if (success)
return pic;
}
picture_Release(pic);
}
return NULL;
}
static picture_t *FilterPictureInteractive(vout_thread_sys_t *sys)
{
// hold it as the filter chain will release it or return it and we release it
picture_Hold(sys->displayed.current);
vlc_mutex_lock(&sys->filter.lock);
picture_t *filtered = filter_chain_VideoFilter(sys->filter.chain_interactive, sys->displayed.current);
vlc_mutex_unlock(&sys->filter.lock);
if (filtered && filtered->date != sys->displayed.current->date)
msg_Warn(&sys->obj, "Unsupported timestamp modifications done by chain_interactive");
return filtered;
}
static vlc_render_subpicture *RenderSPUs(vout_thread_sys_t *sys,
const vlc_fourcc_t *subpicture_chromas,
const video_format_t *spu_frame,
vlc_tick_t system_now, vlc_tick_t render_subtitle_date,
bool ignore_osd)
{
if (unlikely(sys->spu == NULL))
return NULL;
return spu_Render(sys->spu,
subpicture_chromas, spu_frame,
sys->display->source,
system_now, render_subtitle_date,
ignore_osd);
}
static int PrerenderPicture(vout_thread_sys_t *sys, picture_t *filtered,
picture_t **out_pic,
vlc_render_subpicture **out_subpic)
{
vout_display_t *vd = sys->display;
/*
* Get the rendering date for the current subpicture to be displayed.
*/
vlc_tick_t system_now = vlc_tick_now();
vlc_tick_t render_subtitle_date;
if (sys->pause.is_on)
render_subtitle_date = sys->pause.date;
else
{
vlc_clock_Lock(sys->clock);
render_subtitle_date = filtered->date <= VLC_TICK_0 ? system_now :
vlc_clock_ConvertToSystem(sys->clock, system_now, filtered->date,
sys->rate);
vlc_clock_Unlock(sys->clock);
}
/*
* Check whether we let the display draw the subpicture itself (when
* vd_does_blending=true), and if we can fallback to blending the subpicture
* ourselves (blending_before_converter=true).
*/
const bool do_snapshot = vout_snapshot_IsRequested(sys->snapshot);
const bool vd_does_blending = !do_snapshot &&
vd->info.subpicture_chromas &&
*vd->info.subpicture_chromas != 0;
//FIXME: Denying blending_before_converter if vd->source->orientation != ORIENT_NORMAL
//will have the effect that snapshots miss the subpictures. We do this
//because there is currently no way to transform subpictures to match
//the source format.
// In early SPU blending the blending is done into the source chroma,
// otherwise it's done in the display chroma
const bool blending_before_converter = vd->source->orientation == ORIENT_NORMAL;
video_format_t fmt_spu;
if (vd_does_blending) {
vout_display_place_t place;
vout_display_PlacePicture(&place, vd->source, &vd->cfg->display);
fmt_spu = *vd->source;
fmt_spu.i_sar_num = vd->cfg->display.sar.num;
fmt_spu.i_sar_den = vd->cfg->display.sar.den;
fmt_spu.i_width =
fmt_spu.i_visible_width = place.width;
fmt_spu.i_height =
fmt_spu.i_visible_height = place.height;
} else {
if (blending_before_converter) {
fmt_spu = *vd->source;
} else {
fmt_spu = *vd->fmt;
fmt_spu.i_sar_num = vd->cfg->display.sar.num;
fmt_spu.i_sar_den = vd->cfg->display.sar.den;
}
if (sys->spu_blend &&
!video_format_IsSameChroma(&sys->spu_blend->fmt_out.video, &fmt_spu)) {
filter_DeleteBlend(sys->spu_blend);
sys->spu_blend = NULL;
}
if (!sys->spu_blend) {
sys->spu_blend = filter_NewBlend(VLC_OBJECT(&sys->obj), &fmt_spu);
if (unlikely(sys->spu_blend == NULL))
msg_Err(&sys->obj, "Failed to create blending filter, OSD/Subtitles will not work");
}
}
/* Get the subpicture to be displayed. */
video_format_t fmt_spu_rot;
video_format_ApplyRotation(&fmt_spu_rot, &fmt_spu);
/*
* Perform rendering
*
* We have to:
* - be sure to end up with a direct buffer.
* - blend subtitles, and in a fast access buffer
*/
picture_t *todisplay = filtered;
picture_t *snap_pic = todisplay;
if (!vd_does_blending && blending_before_converter && sys->spu_blend) {
vlc_render_subpicture *subpic = RenderSPUs(sys, NULL, &fmt_spu_rot,
system_now, render_subtitle_date,
do_snapshot);
if (subpic) {
picture_t *blent = picture_pool_Get(sys->private_pool);
if (blent) {
video_format_CopyCropAr(&blent->format, &filtered->format);
picture_Copy(blent, filtered);
if (picture_BlendSubpicture(blent, sys->spu_blend, subpic)) {
picture_Release(todisplay);
snap_pic = todisplay = blent;
} else
{
/* Blending failed, likely because the picture is opaque or
* read-only. Try to convert the opaque picture to a
* software RGB32 to generate a snapshot. */
if (do_snapshot)
{
picture_t *copy = ConvertRGBAAndBlend(sys, blent, subpic);
if (copy)
snap_pic = copy;
}
picture_Release(blent);
}
}
vlc_render_subpicture_Delete(subpic);
}
}
/*
* Take a snapshot if requested
*/
if (do_snapshot)
{
assert(snap_pic);
vout_snapshot_Set(sys->snapshot, vd->source, snap_pic);
if (snap_pic != todisplay)
picture_Release(snap_pic);
}
/* Render the direct buffer */
vout_UpdateDisplaySourceProperties(vd, &todisplay->format, &sys->source.dar);
todisplay = vout_ConvertForDisplay(vd, todisplay);
if (todisplay == NULL) {
return VLC_EGENERIC;
}
if (!vd_does_blending && !blending_before_converter && sys->spu_blend)
{
vlc_render_subpicture *subpic = RenderSPUs(sys, NULL, &fmt_spu_rot,
system_now, render_subtitle_date,
do_snapshot);
if (subpic)
{
picture_BlendSubpicture(todisplay, sys->spu_blend, subpic);
vlc_render_subpicture_Delete(subpic);
}
}
*out_pic = todisplay;
if (vd_does_blending)
*out_subpic = RenderSPUs(sys, vd->info.subpicture_chromas, &fmt_spu_rot,
system_now, render_subtitle_date,
false);
else
*out_subpic = NULL;
return VLC_SUCCESS;
}
static int RenderPicture(vout_thread_sys_t *sys, bool render_now)
{
vout_display_t *vd = sys->display;
vout_chrono_Start(&sys->chrono.render);
picture_t *filtered = FilterPictureInteractive(sys);
if (!filtered)
return VLC_EGENERIC;
vlc_clock_Lock(sys->clock);
sys->clock_nowait = false;
vlc_clock_Unlock(sys->clock);
vlc_queuedmutex_lock(&sys->display_lock);
picture_t *todisplay;
vlc_render_subpicture *subpic;
int ret = PrerenderPicture(sys, filtered, &todisplay, &subpic);
if (ret != VLC_SUCCESS)
{
vlc_queuedmutex_unlock(&sys->display_lock);
return ret;
}
vlc_tick_t system_now = vlc_tick_now();
const vlc_tick_t pts = todisplay->date;
vlc_clock_Lock(sys->clock);
vlc_tick_t system_pts = render_now ? system_now :
vlc_clock_ConvertToSystem(sys->clock, system_now, pts, sys->rate);
vlc_clock_Unlock(sys->clock);
const unsigned frame_rate = todisplay->format.i_frame_rate;
const unsigned frame_rate_base = todisplay->format.i_frame_rate_base;
if (vd->ops->prepare != NULL)
vd->ops->prepare(vd, todisplay, subpic, system_pts);
vout_chrono_Stop(&sys->chrono.render);
struct vlc_tracer *tracer = GetTracer(sys);
system_now = vlc_tick_now();
if (!render_now)
{
const vlc_tick_t late = system_now - system_pts;
if (unlikely(late > 0))
{
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", sys->str_id, "late");
msg_Dbg(vd, "picture displayed late (missing %"PRId64" ms)", MS_FROM_VLC_TICK(late));
vout_statistic_AddLate(&sys->statistic, 1);
/* vd->prepare took too much time. Tell the clock that the pts was
* rendered late. */
system_pts = system_now;
}
else if (vd->ops->display != NULL)
{
vlc_tick_t max_deadline = system_now + VOUT_REDISPLAY_DELAY;
/* Wait to reach system_pts if the plugin doesn't handle
* asynchronous display */
vlc_clock_Lock(sys->clock);
bool timed_out = false;
sys->wait_interrupted = false;
while (!timed_out)
{
vlc_tick_t deadline;
if (vlc_clock_IsPaused(sys->clock))
deadline = max_deadline;
else
{
deadline = vlc_clock_ConvertToSystem(sys->clock,
vlc_tick_now(), pts,
sys->rate);
if (deadline > max_deadline)
deadline = max_deadline;
}
if (sys->clock_nowait)
{
/* A caller (the UI thread) awaits for the rendering to
* complete urgently, do not wait. */
sys->wait_interrupted = true;
break;
}
system_pts = deadline;
timed_out = vlc_clock_Wait(sys->clock, deadline);
}
vlc_clock_Unlock(sys->clock);
}
sys->displayed.date = system_pts;
}
else
{
sys->displayed.date = system_now;
}
/* Display the direct buffer returned by vout_RenderPicture */
vout_display_Display(vd, todisplay);
vlc_clock_Lock(sys->clock);
vlc_tick_t drift = vlc_clock_UpdateVideo(sys->clock,
vlc_tick_now(),
pts, sys->rate,
frame_rate, frame_rate_base);
vlc_clock_Unlock(sys->clock);
vlc_queuedmutex_unlock(&sys->display_lock);
picture_Release(todisplay);
if (subpic)
vlc_render_subpicture_Delete(subpic);
vout_statistic_AddDisplayed(&sys->statistic, 1);
if (tracer != NULL && system_pts != VLC_TICK_MAX)
vlc_tracer_TraceWithTs(tracer, system_pts,
VLC_TRACE("type", "RENDER"),
VLC_TRACE("id", sys->str_id),
VLC_TRACE_TICK_NS("drift", drift),
VLC_TRACE_END);
return VLC_SUCCESS;
}
static void UpdateDeinterlaceFilter(vout_thread_sys_t *sys)
{
vlc_mutex_lock(&sys->filter.lock);
if (sys->filter.changed ||
sys->interlacing.has_deint != sys->filter.new_interlaced)
{
sys->interlacing.has_deint = sys->filter.new_interlaced;
ChangeFilters(sys);
}
vlc_mutex_unlock(&sys->filter.lock);
}
static int DisplayNextFrame(vout_thread_sys_t *sys)
{
UpdateDeinterlaceFilter(sys);
picture_t *next = PreparePicture(sys, !sys->displayed.current, true);
if (next)
{
if (likely(sys->displayed.current != NULL))
picture_Release(sys->displayed.current);
sys->displayed.current = next;
}
if (!next)
return VLC_EGENERIC;
return RenderPicture(sys, true);
}
static bool UpdateCurrentPicture(vout_thread_sys_t *sys)
{
assert(sys->clock);
if (sys->frame_next_count > 0)
{
if (DisplayNextFrame(sys) == VLC_SUCCESS)
--sys->frame_next_count;
return false;
}
if (sys->displayed.current == NULL)
{
sys->displayed.current = PreparePicture(sys, true, false);
return sys->displayed.current != NULL;
}
if (sys->pause.is_on || sys->wait_interrupted)
return false;
const vlc_tick_t system_now = vlc_tick_now();
vlc_clock_Lock(sys->clock);
const vlc_tick_t system_swap_current =
vlc_clock_ConvertToSystem(sys->clock, system_now,
sys->displayed.current->date, sys->rate);
vlc_clock_Unlock(sys->clock);
const vlc_tick_t render_delay = vout_chrono_GetHigh(&sys->chrono.render) + VOUT_MWAIT_TOLERANCE;
vlc_tick_t system_prepare_current = system_swap_current - render_delay;
if (unlikely(system_prepare_current > system_now))
// the current frame is not late, we still have time to display it
// no need to get a new picture
return true;
// the current frame will be late, look for the next not late one
picture_t *next = PreparePicture(sys, false, false);
if (next == NULL)
return false;
/* We might have reset the current picture when preparing the next one,
* because filters had to be changed. In this case, avoid releasing the
* picture since it will lead to null pointer dereference errors. */
if (sys->displayed.current != NULL)
picture_Release(sys->displayed.current);
sys->displayed.current = next;
return true;
}
static vlc_tick_t DisplayPicture(vout_thread_sys_t *vout)
{
vout_thread_sys_t *sys = vout;
assert(sys->clock);
UpdateDeinterlaceFilter(sys);
bool current_changed = UpdateCurrentPicture(sys);
if (current_changed)
{
// next frame will still need some waiting before display, we don't need
// to render now
// display forced picture immediately
bool render_now = sys->displayed.current->b_force;
RenderPicture(vout, render_now);
if (!render_now)
/* Prepare the next picture immediately without waiting */
return VLC_TICK_INVALID;
}
else if (sys->wait_interrupted)
{
sys->wait_interrupted = false;
if (likely(sys->displayed.current != NULL))
RenderPicture(vout, true);
return VLC_TICK_INVALID;
}
else if (likely(sys->displayed.date != VLC_TICK_INVALID))
{
const vlc_tick_t render_delay = vout_chrono_GetHigh(&sys->chrono.render) + VOUT_MWAIT_TOLERANCE;
// next date we need to display again the current picture
vlc_tick_t date_refresh = sys->displayed.date + VOUT_REDISPLAY_DELAY - render_delay;
const vlc_tick_t system_now = vlc_tick_now();
/* FIXME/XXX we must redisplay the last decoded picture (because
* of potential vout updated, or filters update or SPU update)
* For now a high update period is needed but it could be removed
* if and only if:
* - vout module emits events from themselves.
* - *and* SPU is modified to emit an event or a deadline when needed.
*
* So it will be done later.
*/
if (date_refresh > system_now) {
// nothing changed, wait until the next deadline or a control
vlc_tick_t max_deadline = system_now + VOUT_REDISPLAY_DELAY;
return __MIN(date_refresh, max_deadline);
}
RenderPicture(vout, true);
}
// wait until the next deadline or a control
return vlc_tick_now() + VOUT_REDISPLAY_DELAY;
}
void vout_ChangePause(vout_thread_t *vout, bool is_paused, vlc_tick_t date)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vout_control_Hold(&sys->control);
assert(!sys->pause.is_on || !is_paused);
if (sys->pause.is_on)
FilterFlush(sys, false);
sys->pause.is_on = is_paused;
sys->pause.date = date;
vout_control_Release(&sys->control);
struct vlc_tracer *tracer = GetTracer(sys);
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", sys->str_id,
is_paused ? "paused" : "resumed");
vlc_mutex_lock(&sys->window_lock);
vlc_window_SetInhibition(sys->display_cfg.window, !is_paused);
vlc_mutex_unlock(&sys->window_lock);
}
static void vout_FlushUnlocked(vout_thread_sys_t *vout, bool below,
vlc_tick_t date)
{
vout_thread_sys_t *sys = vout;
FilterFlush(vout, false); /* FIXME too much */
picture_t *last = sys->displayed.decoded;
if (last) {
if ((date == VLC_TICK_INVALID) ||
( below && last->date <= date) ||
(!below && last->date >= date)) {
picture_Release(last);
sys->displayed.decoded = NULL;
sys->displayed.date = VLC_TICK_INVALID;
sys->displayed.timestamp = VLC_TICK_INVALID;
}
}
picture_fifo_Flush(sys->decoder_fifo, date, below);
vlc_queuedmutex_lock(&sys->display_lock);
if (sys->display != NULL)
vout_FilterFlush(sys->display);
/* Reinitialize chrono to ensure we re-compute any new render timing. */
VoutResetChronoLocked(sys);
vlc_queuedmutex_unlock(&sys->display_lock);
if (sys->clock != NULL)
{
vlc_clock_Lock(sys->clock);
vlc_clock_Reset(sys->clock);
vlc_clock_SetDelay(sys->clock, sys->delay);
vlc_clock_Unlock(sys->clock);
}
}
void vout_Flush(vout_thread_t *vout, vlc_tick_t date)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vout_control_Hold(&sys->control);
vout_FlushUnlocked(sys, false, date);
vout_control_Release(&sys->control);
struct vlc_tracer *tracer = GetTracer(sys);
if (tracer != NULL)
vlc_tracer_TraceEvent(tracer, "RENDER", sys->str_id, "flushed");
}
void vout_NextPicture(vout_thread_t *vout)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vout_control_Hold(&sys->control);
sys->frame_next_count++;
vout_control_ReleaseAndWake(&sys->control);
}
void vout_ChangeDelay(vout_thread_t *vout, vlc_tick_t delay)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
assert(sys->display);
vout_control_Hold(&sys->control);
vlc_clock_Lock(sys->clock);
vlc_clock_SetDelay(sys->clock, delay);
vlc_clock_Unlock(sys->clock);
sys->delay = delay;
vout_control_Release(&sys->control);
}
void vout_ChangeRate(vout_thread_t *vout, float rate)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vout_control_Hold(&sys->control);
sys->rate = rate;
vout_control_Release(&sys->control);
}
void vout_ChangeSpuDelay(vout_thread_t *vout, size_t channel_id,
vlc_tick_t delay)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
assert(sys->spu);
spu_SetClockDelay(sys->spu, channel_id, delay);
}
void vout_ChangeSpuRate(vout_thread_t *vout, size_t channel_id, float rate)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
assert(sys->spu);
spu_SetClockRate(sys->spu, channel_id, rate);
}
static int vout_Start(vout_thread_sys_t *vout, vlc_video_context *vctx, const vout_configuration_t *cfg)
{
vout_thread_sys_t *sys = vout;
filter_chain_t *cs, *ci;
assert(!sys->dummy);
vlc_mutex_lock(&sys->window_lock);
vout_display_window_SetMouseHandler(sys->display_cfg.window,
cfg->mouse_event, cfg->mouse_opaque);
vlc_mutex_unlock(&sys->window_lock);
sys->decoder_fifo = picture_fifo_New();
sys->private_pool = NULL;
sys->filter.configuration = NULL;
video_format_Copy(&sys->filter.src_fmt, &sys->original);
sys->filter.src_vctx = vctx ? vlc_video_context_Hold(vctx) : NULL;
static const struct filter_video_callbacks static_cbs = {
VoutVideoFilterStaticNewPicture, VoutHoldDecoderDevice,
};
static const struct filter_video_callbacks interactive_cbs = {
VoutVideoFilterInteractiveNewPicture, VoutHoldDecoderDevice,
};
filter_owner_t owner = {
.video = &static_cbs,
.sys = vout,
};
cs = filter_chain_NewVideo(&vout->obj, true, &owner);
owner.video = &interactive_cbs;
ci = filter_chain_NewVideo(&vout->obj, true, &owner);
vlc_mutex_lock(&sys->filter.lock);
sys->filter.chain_static = cs;
sys->filter.chain_interactive = ci;
vlc_mutex_unlock(&sys->filter.lock);
vout_display_cfg_t dcfg;
struct vout_crop crop;
unsigned num, den;
vlc_mutex_lock(&sys->window_lock);
#ifndef NDEBUG
if (vctx)
{
// make sure the decoder device we receive matches the one we have cached
vlc_decoder_device *dec_device = vlc_video_context_HoldDevice(vctx);
assert(dec_device && dec_device == sys->dec_device);
vlc_decoder_device_Release(dec_device);
}
#endif
dcfg = sys->display_cfg;
crop = sys->source.crop;
num = sys->source.dar.num;
den = sys->source.dar.den;
vlc_queuedmutex_lock(&sys->display_lock);
vlc_mutex_unlock(&sys->window_lock);
/* Reinitialize chrono to ensure we re-compute any new render timing. */
VoutResetChronoLocked(sys);
/* Setup the window size, protected by the display_lock */
dcfg.display.width = sys->window_width;
dcfg.display.height = sys->window_height;
sys->private_pool =
picture_pool_NewFromFormat(&sys->original, FILTER_POOL_SIZE);
if (sys->private_pool == NULL) {
vlc_queuedmutex_unlock(&sys->display_lock);
goto error;
}
sys->display = vout_OpenWrapper(&vout->obj, sys->splitter_name, &dcfg,
&sys->original, vctx);
if (sys->display == NULL) {
vlc_queuedmutex_unlock(&sys->display_lock);
goto error;
}
vout_SetDisplayCrop(sys->display, &crop);
if (num != 0 && den != 0)
vout_SetDisplayAspect(sys->display, num, den);
vlc_queuedmutex_unlock(&sys->display_lock);
sys->displayed.current = NULL;
sys->displayed.decoded = NULL;
sys->displayed.date = VLC_TICK_INVALID;
sys->displayed.timestamp = VLC_TICK_INVALID;
sys->displayed.is_interlaced = false;
sys->pause.is_on = false;
sys->pause.date = VLC_TICK_INVALID;
sys->spu_blend = NULL;
video_format_Print(VLC_OBJECT(&vout->obj), "original format", &sys->original);
return VLC_SUCCESS;
error:
if (sys->filter.chain_interactive != NULL)
DelAllFilterCallbacks(vout);
vlc_mutex_lock(&sys->filter.lock);
ci = sys->filter.chain_interactive;
cs = sys->filter.chain_static;
sys->filter.chain_interactive = NULL;
sys->filter.chain_static = NULL;
vlc_mutex_unlock(&sys->filter.lock);
if (ci != NULL)
filter_chain_Delete(ci);
if (cs != NULL)
filter_chain_Delete(cs);
if (sys->private_pool)
{
picture_pool_Release(sys->private_pool);
sys->private_pool = NULL;
}
video_format_Clean(&sys->filter.src_fmt);
if (sys->filter.src_vctx)
{
vlc_video_context_Release(sys->filter.src_vctx);
sys->filter.src_vctx = NULL;
}
if (sys->decoder_fifo != NULL)
{
picture_fifo_Delete(sys->decoder_fifo);
sys->decoder_fifo = NULL;
}
vlc_mutex_lock(&sys->window_lock);
vout_display_window_SetMouseHandler(sys->display_cfg.window, NULL, NULL);
vlc_mutex_unlock(&sys->window_lock);
return VLC_EGENERIC;
}
/*****************************************************************************
* Thread: video output thread
*****************************************************************************
* Video output thread. This function does only returns when the thread is
* terminated. It handles the pictures arriving in the video heap and the
* display device events.
*****************************************************************************/
static void *Thread(void *object)
{
vout_thread_sys_t *vout = object;
vout_thread_sys_t *sys = vout;
vlc_thread_set_name("vlc-vout");
vlc_tick_t deadline = VLC_TICK_INVALID;
for (;;) {
vout_control_Wait(&sys->control, deadline);
if (atomic_load(&sys->control_is_terminated))
break;
/* A deadline of VLC_TICK_INVALID means "immediately" */
deadline = DisplayPicture(vout);
assert(deadline == VLC_TICK_INVALID ||
deadline <= vlc_tick_now() + VOUT_REDISPLAY_DELAY);
if (atomic_load(&sys->control_is_terminated))
break;
const bool picture_interlaced = sys->displayed.is_interlaced;
vout_SetInterlacingState(&vout->obj, &sys->interlacing, picture_interlaced);
}
return NULL;
}
static void vout_ReleaseDisplay(vout_thread_sys_t *vout)
{
vout_thread_sys_t *sys = vout;
filter_chain_t *ci, *cs;
assert(sys->display != NULL);
if (sys->spu_blend != NULL)
filter_DeleteBlend(sys->spu_blend);
/* Destroy the rendering display */
if (sys->private_pool != NULL)
{
vout_FlushUnlocked(vout, true, VLC_TICK_MAX);
picture_pool_Release(sys->private_pool);
sys->private_pool = NULL;
}
vlc_queuedmutex_lock(&sys->display_lock);
vout_CloseWrapper(&vout->obj, sys->display);
sys->display = NULL;
vlc_queuedmutex_unlock(&sys->display_lock);
/* Destroy the video filters */
DelAllFilterCallbacks(vout);
vlc_mutex_lock(&sys->filter.lock);
ci = sys->filter.chain_interactive;
cs = sys->filter.chain_static;
sys->filter.chain_interactive = NULL;
sys->filter.chain_static = NULL;
vlc_mutex_unlock(&sys->filter.lock);
filter_chain_Delete(ci);
filter_chain_Delete(cs);
video_format_Clean(&sys->filter.src_fmt);
if (sys->filter.src_vctx)
{
vlc_video_context_Release(sys->filter.src_vctx);
sys->filter.src_vctx = NULL;
}
free(sys->filter.configuration);
if (sys->decoder_fifo != NULL)
{
picture_fifo_Delete(sys->decoder_fifo);
sys->decoder_fifo = NULL;
}
assert(sys->private_pool == NULL);
vlc_mutex_lock(&sys->window_lock);
vout_display_window_SetMouseHandler(sys->display_cfg.window, NULL, NULL);
vlc_mutex_unlock(&sys->window_lock);
if (sys->spu)
spu_Detach(sys->spu);
if (sys->clock_listener_id != NULL)
{
vlc_clock_Lock(sys->clock);
vlc_clock_RemoveListener(sys->clock, sys->clock_listener_id);
vlc_clock_Unlock(sys->clock);
sys->clock_listener_id = NULL;
}
vlc_mutex_lock(&sys->clock_lock);
sys->clock = NULL;
vlc_mutex_unlock(&sys->clock_lock);
sys->str_id = NULL;
}
void vout_StopDisplay(vout_thread_t *vout)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
atomic_store(&sys->control_is_terminated, true);
// wake up so it goes back to the loop that will detect the terminated state
vout_control_Wake(&sys->control);
vlc_join(sys->thread, NULL);
vout_ReleaseDisplay(sys);
}
static void vout_DisableWindow(vout_thread_sys_t *sys)
{
vlc_mutex_lock(&sys->window_lock);
if (sys->window_enabled) {
vlc_window_Disable(sys->display_cfg.window);
sys->window_enabled = false;
}
vlc_mutex_unlock(&sys->window_lock);
}
void vout_Stop(vout_thread_t *vout)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
if (sys->display != NULL)
vout_StopDisplay(vout);
vout_DisableWindow(sys);
}
void vout_Close(vout_thread_t *vout)
{
assert(vout);
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vout_Stop(vout);
vout_IntfDeinit(VLC_OBJECT(vout));
vout_snapshot_End(sys->snapshot);
if (sys->spu)
spu_Destroy(sys->spu);
vout_Release(vout);
}
void vout_Release(vout_thread_t *vout)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
if (!vlc_atomic_rc_dec(&sys->rc))
return;
if (sys->dummy)
{
vlc_object_delete(VLC_OBJECT(vout));
return;
}
free(sys->splitter_name);
free(sys->display_cfg.icc_profile);
if (sys->dec_device)
vlc_decoder_device_Release(sys->dec_device);
assert(!sys->window_enabled);
vout_display_window_Delete(sys->display_cfg.window);
/* */
vout_statistic_Clean(&sys->statistic);
/* */
vout_snapshot_Destroy(sys->snapshot);
video_format_Clean(&sys->original);
vlc_object_delete(VLC_OBJECT(vout));
}
static vout_thread_sys_t *vout_CreateCommon(vlc_object_t *object)
{
/* Allocate descriptor */
vout_thread_sys_t *vout = vlc_custom_create(object,
sizeof(*vout),
"video output");
if (!vout)
return NULL;
vout_CreateVars(&vout->obj);
vout_thread_sys_t *sys = vout;
vlc_atomic_rc_init(&sys->rc);
vlc_mouse_Init(&sys->mouse);
return vout;
}
vout_thread_t *vout_CreateDummy(vlc_object_t *object)
{
vout_thread_sys_t *vout = vout_CreateCommon(object);
if (!vout)
return NULL;
vout_thread_sys_t *sys = vout;
sys->dummy = true;
return &vout->obj;
}
vout_thread_t *vout_Create(vlc_object_t *object)
{
vout_thread_sys_t *p_vout = vout_CreateCommon(object);
if (!p_vout)
return NULL;
vout_thread_t *vout = &p_vout->obj;
vout_thread_sys_t *sys = p_vout;
sys->dummy = false;
/* Register the VLC variable and callbacks. On the one hand, the variables
* must be ready early on because further initializations below depend on
* some of them. On the other hand, the callbacks depend on said
* initializations. In practice, this works because the object is not
* visible and callbacks not triggerable before this function returns the
* fully initialized object to its caller.
*/
vout_IntfInit(vout);
/* Get splitter name if present */
sys->splitter_name = NULL;
if (config_GetType("video-splitter")) {
char *splitter_name = var_InheritString(vout, "video-splitter");
if (unlikely(splitter_name == NULL)) {
vlc_object_delete(vout);
return NULL;
}
if (strcmp(splitter_name, "none") != 0) {
var_Create(vout, "window", VLC_VAR_STRING);
var_SetString(vout, "window", "wdummy");
sys->splitter_name = splitter_name;
} else
free(splitter_name);
}
video_format_Init(&sys->original, 0);
sys->source.dar.num = 0;
sys->source.dar.den = 0;
sys->source.crop.mode = VOUT_CROP_NONE;
sys->snapshot = vout_snapshot_New();
vout_statistic_Init(&sys->statistic);
/* Initialize subpicture unit */
sys->spu = var_InheritBool(vout, "spu") || var_InheritBool(vout, "osd") ?
spu_Create(vout, vout) : NULL;
vout_control_Init(&sys->control);
atomic_init(&sys->control_is_terminated, false);
sys->title.show = var_InheritBool(vout, "video-title-show");
sys->title.timeout = var_InheritInteger(vout, "video-title-timeout");
sys->title.position = var_InheritInteger(vout, "video-title-position");
sys->private_pool = NULL;
vout_InitInterlacingSupport(vout, &sys->interlacing);
sys->is_late_dropped = var_InheritBool(vout, "drop-late-frames");
vlc_mutex_init(&sys->filter.lock);
vlc_mutex_init(&sys->clock_lock);
sys->clock_nowait = false;
sys->wait_interrupted = false;
/* Display */
sys->display = NULL;
sys->display_cfg.icc_profile = NULL;
vlc_queuedmutex_init(&sys->display_lock);
/* Window */
sys->window_width = sys->window_height = 0;
sys->display_cfg.window = vout_display_window_New(vout);
if (sys->display_cfg.window == NULL) {
if (sys->spu)
spu_Destroy(sys->spu);
vlc_object_delete(vout);
return NULL;
}
if (sys->splitter_name != NULL)
var_Destroy(vout, "window");
sys->window_enabled = false;
sys->frame_next_count = 0;
vlc_mutex_init(&sys->window_lock);
if (var_InheritBool(vout, "video-wallpaper"))
vlc_window_SetState(sys->display_cfg.window, VLC_WINDOW_STATE_BELOW);
else if (var_InheritBool(vout, "video-on-top"))
vlc_window_SetState(sys->display_cfg.window, VLC_WINDOW_STATE_ABOVE);
return vout;
}
vout_thread_t *vout_Hold( vout_thread_t *vout)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
vlc_atomic_rc_inc(&sys->rc);
return vout;
}
int vout_ChangeSource( vout_thread_t *vout, const video_format_t *original,
const vlc_video_context *vctx )
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
if (sys->display == NULL)
return -1;
if (sys->filter.src_vctx != vctx)
return -1;
/* TODO: If dimensions are equal or slightly smaller, update the aspect
* ratio and crop settings, instead of recreating a display.
*/
if (!video_format_IsSimilar(original, &sys->original))
{
msg_Dbg(&vout->obj, "vout format changed");
video_format_LogDifferences(vlc_object_logger(&vout->obj), "current", &sys->original, "new", original);
return -1;
}
/* It is assumed that the SPU input matches input already. */
return 0;
}
static int EnableWindowLocked(vout_thread_sys_t *vout, const video_format_t *original)
{
assert(vout != NULL);
vout_thread_sys_t *sys = vout;
assert(!sys->dummy);
vlc_mutex_assert(&sys->window_lock);
VoutGetDisplayCfg(vout, original, &sys->display_cfg);
vout_UpdateWindowSizeLocked(vout);
if (!sys->window_enabled) {
if (vlc_window_Enable(sys->display_cfg.window)) {
msg_Err(&vout->obj, "failed to enable window");
return -1;
}
sys->window_enabled = true;
}
return 0;
}
static void vout_InitSource(vout_thread_sys_t *vout)
{
char *psz_ar = var_InheritString(&vout->obj, "aspect-ratio");
if (psz_ar) {
unsigned num, den;
if (!GetAspectRatio(psz_ar, &num, &den))
vout_SetAspectRatio(vout, num, den);
free(psz_ar);
}
char *psz_crop = var_InheritString(&vout->obj, "crop");
if (psz_crop) {
if (!vout_ParseCrop(&vout->source.crop, psz_crop))
vout->source.crop.mode = VOUT_CROP_NONE;
free(psz_crop);
}
}
static void clock_event_OnDiscontinuity(void *data)
{
vout_thread_sys_t *vout = data;
vout_thread_sys_t *sys = vout;
/* The Render thread wait for a deadline that is either:
* - VOUT_REDISPLAY_DELAY
* - calculated from the clock
* In case of a clock discontinuity, we need to wake up the Render thread,
* in order to trigger the rendering of the next picture, if new timings
* require it. */
vout_control_Wake(&sys->control);
}
int vout_Request(const vout_configuration_t *cfg, vlc_video_context *vctx, input_thread_t *input)
{
vout_thread_sys_t *vout = VOUT_THREAD_TO_SYS(cfg->vout);
vout_thread_sys_t *sys = vout;
assert(cfg->fmt != NULL);
assert(cfg->clock != NULL);
if (!VoutCheckFormat(cfg->fmt)) {
if (sys->display != NULL)
vout_StopDisplay(cfg->vout);
return -1;
}
video_format_t original;
VoutFixFormat(&original, cfg->fmt);
if (vout_ChangeSource(cfg->vout, &original, vctx) == 0)
{
video_format_Clean(&original);
return 0;
}
vlc_mutex_lock(&sys->window_lock);
video_format_Clean(&sys->original);
sys->original = original;
vout_InitSource(vout);
if (EnableWindowLocked(vout, &original) != 0)
{
/* the window was not enabled, nor the display started */
msg_Err(cfg->vout, "failed to enable window");
vlc_mutex_unlock(&sys->window_lock);
assert(sys->display == NULL);
return -1;
}
vlc_mutex_unlock(&sys->window_lock);
if (sys->display != NULL)
vout_StopDisplay(cfg->vout);
vout_ReinitInterlacingSupport(cfg->vout, &sys->interlacing);
sys->delay = 0;
sys->rate = 1.f;
sys->str_id = cfg->str_id;
vlc_mutex_lock(&sys->clock_lock);
sys->clock = cfg->clock;
vlc_mutex_unlock(&sys->clock_lock);
static const struct vlc_clock_event_cbs clock_event_cbs = {
.on_discontinuity = clock_event_OnDiscontinuity,
};
vlc_clock_Lock(sys->clock);
sys->clock_listener_id =
vlc_clock_AddListener(sys->clock, &clock_event_cbs, vout);
vlc_clock_Unlock(sys->clock);
sys->delay = 0;
if (vout_Start(vout, vctx, cfg))
{
msg_Err(cfg->vout, "video output display creation failed");
goto error_display;
}
atomic_store(&sys->control_is_terminated, false);
if (vlc_clone(&sys->thread, Thread, vout))
goto error_thread;
if (input != NULL && sys->spu)
spu_Attach(sys->spu, input);
vout_IntfReinit(cfg->vout);
return 0;
error_thread:
vout_ReleaseDisplay(vout);
error_display:
vout_DisableWindow(vout);
if (sys->clock_listener_id != NULL)
{
vlc_clock_Lock(sys->clock);
vlc_clock_RemoveListener(sys->clock, sys->clock_listener_id);
vlc_clock_Unlock(sys->clock);
}
sys->clock_listener_id = NULL;
vlc_mutex_lock(&sys->clock_lock);
sys->clock = NULL;
vlc_mutex_unlock(&sys->clock_lock);
return -1;
}
vlc_decoder_device *vout_GetDevice(vout_thread_t *vout)
{
vlc_decoder_device *dec_device = NULL;
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
vlc_mutex_lock(&sys->window_lock);
if (sys->dec_device == NULL)
sys->dec_device = vlc_decoder_device_Create(&vout->obj, sys->display_cfg.window);
dec_device = sys->dec_device ? vlc_decoder_device_Hold( sys->dec_device ) : NULL;
vlc_mutex_unlock(&sys->window_lock);
return dec_device;
}