X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fvideo_output%2Fvideo_output.c;h=f9e52cdb377c9b3ca9c06accb068b2e27cc47199;hb=681bf48f3c246640a193b009f5b86010495c4849;hp=8467ca88fe7cf3995fac19ac0921e340c4a017a2;hpb=a3988310d257c5eee3b372a6fa7e3251e64c72c7;p=vlc diff --git a/src/video_output/video_output.c b/src/video_output/video_output.c index 8467ca88fe..f9e52cdb37 100644 --- a/src/video_output/video_output.c +++ b/src/video_output/video_output.c @@ -5,26 +5,26 @@ * 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-2007 the VideoLAN team + * Copyright (C) 2000-2007 VLC authors and VideoLAN * $Id$ * * Authors: Vincent Seguin * Gildas Bazin * Laurent Aimar * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 General Public License for more details. + * 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 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. + * 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. *****************************************************************************/ /***************************************************************************** @@ -44,28 +44,18 @@ #include #include +#include #include -#include #include "vout_internal.h" #include "interlacing.h" -#include "postprocessing.h" #include "display.h" /***************************************************************************** * Local prototypes *****************************************************************************/ -static void *Thread(void *); -static void vout_Destructor(vlc_object_t *); - -/* Object variables callbacks */ -static int FilterCallback( vlc_object_t *, char const *, - vlc_value_t, vlc_value_t, void * ); -static int VideoFilter2Callback( vlc_object_t *, char const *, - vlc_value_t, vlc_value_t, void * ); - -/* */ -static void PrintVideoFormat(vout_thread_t *, const char *, const video_format_t *); +static void *Thread(void *); +static void VoutDestructor(vlc_object_t *); /* Maximum delay between 2 displayed pictures. * XXX it is needed for now but should be removed in the long term. @@ -78,347 +68,210 @@ static void PrintVideoFormat(vout_thread_t *, const char *, const video_format_t #define VOUT_DISPLAY_LATE_THRESHOLD (INT64_C(20000)) /* Better be in advance when awakening than late... */ -#define VOUT_MWAIT_TOLERANCE (INT64_C(1000)) +#define VOUT_MWAIT_TOLERANCE (INT64_C(4000)) -/***************************************************************************** - * Video Filter2 functions - *****************************************************************************/ -static picture_t *video_new_buffer_filter( filter_t *p_filter ) +/* */ +static int VoutValidateFormat(video_format_t *dst, + const video_format_t *src) { - vout_thread_t *p_vout = (vout_thread_t*)p_filter->p_owner; - return picture_pool_Get(p_vout->p->private_pool); -} + if (src->i_width <= 0 || src->i_width > 8192 || + src->i_height <= 0 || src->i_height > 8192) + return VLC_EGENERIC; + if (src->i_sar_num <= 0 || src->i_sar_den <= 0) + return VLC_EGENERIC; -static void video_del_buffer_filter( filter_t *p_filter, picture_t *p_pic ) -{ - VLC_UNUSED(p_filter); - picture_Release(p_pic); + /* */ + video_format_Copy(dst, src); + dst->i_chroma = vlc_fourcc_GetCodec(VIDEO_ES, src->i_chroma); + vlc_ureduce( &dst->i_sar_num, &dst->i_sar_den, + src->i_sar_num, src->i_sar_den, 50000 ); + if (dst->i_sar_num <= 0 || dst->i_sar_den <= 0) { + dst->i_sar_num = 1; + dst->i_sar_den = 1; + } + video_format_FixRgb(dst); + return VLC_SUCCESS; } - -static int video_filter_buffer_allocation_init( filter_t *p_filter, void *p_data ) +static void VideoFormatCopyCropAr(video_format_t *dst, + const video_format_t *src) { - p_filter->pf_video_buffer_new = video_new_buffer_filter; - p_filter->pf_video_buffer_del = video_del_buffer_filter; - p_filter->p_owner = p_data; /* p_vout */ - return VLC_SUCCESS; + video_format_CopyCrop(dst, src); + dst->i_sar_num = src->i_sar_num; + dst->i_sar_den = src->i_sar_den; } - -#undef vout_Request -/***************************************************************************** - * vout_Request: find a video output thread, create one, or destroy one. - ***************************************************************************** - * This function looks for a video output thread matching the current - * properties. If not found, it spawns a new one. - *****************************************************************************/ -vout_thread_t *vout_Request( vlc_object_t *p_this, vout_thread_t *p_vout, - video_format_t *p_fmt ) -{ - if( !p_fmt ) - { - /* Video output is no longer used. - * TODO: support for reusing video outputs with proper _thread-safe_ - * reference handling. */ - if( p_vout ) - vout_CloseAndRelease( p_vout ); - return NULL; - } - - /* If a video output was provided, lock it, otherwise look for one. */ - if( p_vout ) - { - vlc_object_hold( p_vout ); - } - - /* TODO: find a suitable unused video output */ - - /* If we now have a video output, check it has the right properties */ - if( p_vout ) - { - vlc_mutex_lock( &p_vout->p->change_lock ); - - if( !video_format_IsSimilar( &p_vout->p->original, p_fmt ) ) - { - vlc_mutex_unlock( &p_vout->p->change_lock ); - - /* We are not interested in this format, close this vout */ - vout_CloseAndRelease( p_vout ); - vlc_object_release( p_vout ); - p_vout = NULL; - } - else - { - /* This video output is cool! Hijack it. */ - vlc_mutex_unlock( &p_vout->p->change_lock ); - - vlc_object_release( p_vout ); - } - - if( p_vout ) - { - msg_Dbg( p_this, "reusing provided vout" ); - - spu_Attach( p_vout->p->p_spu, VLC_OBJECT(p_vout), false ); - vlc_object_detach( p_vout ); - - vlc_object_attach( p_vout, p_this ); - spu_Attach( p_vout->p->p_spu, VLC_OBJECT(p_vout), true ); - } - } - - if( !p_vout ) - { - msg_Dbg( p_this, "no usable vout present, spawning one" ); - - p_vout = vout_Create( p_this, p_fmt ); - } - - return p_vout; +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; } -/***************************************************************************** - * vout_Create: creates a new video output thread - ***************************************************************************** - * This function creates a new video output thread, and returns a pointer - * to its description. On error, it returns NULL. - *****************************************************************************/ -vout_thread_t * (vout_Create)( vlc_object_t *p_parent, video_format_t *p_fmt ) +static vout_thread_t *VoutCreate(vlc_object_t *object, + const vout_configuration_t *cfg) { - vout_thread_t *p_vout; /* thread descriptor */ - vlc_value_t text; - - - config_chain_t *p_cfg; - char *psz_parser; - char *psz_name; - - if( p_fmt->i_width <= 0 || p_fmt->i_height <= 0 ) - return NULL; - const vlc_fourcc_t i_chroma = vlc_fourcc_GetCodec( VIDEO_ES, p_fmt->i_chroma ); - - vlc_ureduce( &p_fmt->i_sar_num, &p_fmt->i_sar_den, - p_fmt->i_sar_num, p_fmt->i_sar_den, 50000 ); - if( p_fmt->i_sar_num <= 0 || p_fmt->i_sar_den <= 0 ) + video_format_t original; + if (VoutValidateFormat(&original, cfg->fmt)) return NULL; /* Allocate descriptor */ - static const char typename[] = "video output"; - p_vout = vlc_custom_create( p_parent, sizeof( *p_vout ), VLC_OBJECT_VOUT, - typename ); - if( p_vout == NULL ) - return NULL; - - /* */ - p_vout->p = calloc( 1, sizeof(*p_vout->p) ); - if( !p_vout->p ) - { - vlc_object_release( p_vout ); + vout_thread_t *vout = vlc_custom_create(object, + sizeof(*vout) + sizeof(*vout->p), + "video output"); + if (!vout) { + video_format_Clean(&original); return NULL; } /* */ - p_vout->p->original = *p_fmt; /* FIXME palette */ - p_vout->p->original.i_chroma = i_chroma; - video_format_FixRgb( &p_vout->p->original ); - - /* Initialize misc stuff */ - vout_control_Init( &p_vout->p->control ); - vout_chrono_Init( &p_vout->p->render, 5, 10000 ); /* Arbitrary initial time */ - vout_statistic_Init( &p_vout->p->statistic ); - p_vout->p->i_par_num = - p_vout->p->i_par_den = 1; - p_vout->p->displayed.date = VLC_TS_INVALID; - p_vout->p->displayed.decoded = NULL; - p_vout->p->displayed.timestamp = VLC_TS_INVALID; - p_vout->p->displayed.qtype = QTYPE_NONE; - p_vout->p->displayed.is_interlaced = false; - - p_vout->p->step.last = VLC_TS_INVALID; - p_vout->p->step.timestamp = VLC_TS_INVALID; - - p_vout->p->pause.is_on = false; - p_vout->p->pause.date = VLC_TS_INVALID; + vout->p = (vout_thread_sys_t*)&vout[1]; - p_vout->p->decoder_fifo = picture_fifo_New(); - p_vout->p->decoder_pool = NULL; + vout->p->original = original; + vout->p->dpb_size = cfg->dpb_size; - vlc_mouse_Init( &p_vout->p->mouse ); + vout_control_Init(&vout->p->control); + vout_control_PushVoid(&vout->p->control, VOUT_CONTROL_INIT); - vout_snapshot_Init( &p_vout->p->snapshot ); + vout_statistic_Init(&vout->p->statistic); - p_vout->p->vfilter_chain = - filter_chain_New( p_vout, "video filter2", false, - video_filter_buffer_allocation_init, NULL, p_vout ); + vout_snapshot_Init(&vout->p->snapshot); /* Initialize locks */ - vlc_mutex_init( &p_vout->p->picture_lock ); - vlc_mutex_init( &p_vout->p->change_lock ); - vlc_mutex_init( &p_vout->p->vfilter_lock ); - - /* Mouse coordinates */ - var_Create( p_vout, "mouse-button-down", VLC_VAR_INTEGER ); - var_Create( p_vout, "mouse-moved", VLC_VAR_COORDS ); - var_Create( p_vout, "mouse-clicked", VLC_VAR_COORDS ); - /* Mouse object (area of interest in a video filter) */ - var_Create( p_vout, "mouse-object", VLC_VAR_BOOL ); - - /* Attach the new object now so we can use var inheritance below */ - vlc_object_attach( p_vout, p_parent ); + vlc_mutex_init(&vout->p->picture_lock); + vlc_mutex_init(&vout->p->filter.lock); + vlc_mutex_init(&vout->p->spu_lock); /* Initialize subpicture unit */ - p_vout->p->p_spu = spu_Create( p_vout ); - - /* */ - spu_Init( p_vout->p->p_spu ); - - spu_Attach( p_vout->p->p_spu, VLC_OBJECT(p_vout), true ); - - p_vout->p->is_late_dropped = var_InheritBool( p_vout, "drop-late-frames" ); + vout->p->spu = spu_Create(vout); /* Take care of some "interface/control" related initialisations */ - vout_IntfInit( p_vout ); - - /* Look for the default filter configuration */ - p_vout->p->psz_filter_chain = - var_CreateGetStringCommand( p_vout, "vout-filter" ); - - /* Apply video filter2 objects on the first vout */ - var_Create( p_vout, "video-filter", - VLC_VAR_STRING | VLC_VAR_DOINHERIT | VLC_VAR_ISCOMMAND ); - var_AddCallback( p_vout, "video-filter", VideoFilter2Callback, NULL ); - var_TriggerCallback( p_vout, "video-filter" ); - - /* Choose the video output module */ - if( !p_vout->p->psz_filter_chain || !*p_vout->p->psz_filter_chain ) - { - psz_parser = NULL; - } - else - { - psz_parser = strdup( p_vout->p->psz_filter_chain ); - p_vout->p->title.show = false; - } + vout_IntfInit(vout); - /* Create the vout thread */ - char *psz_tmp = config_ChainCreate( &psz_name, &p_cfg, psz_parser ); - free( psz_parser ); - free( psz_tmp ); - p_vout->p->p_cfg = p_cfg; + vout->p->title.show = var_InheritBool(vout, "video-title-show"); + vout->p->title.timeout = var_InheritInteger(vout, "video-title-timeout"); + vout->p->title.position = var_InheritInteger(vout, "video-title-position"); - /* Create a few object variables for interface interaction */ - var_Create( p_vout, "vout-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT ); - text.psz_string = _("Filters"); - var_Change( p_vout, "vout-filter", VLC_VAR_SETTEXT, &text, NULL ); - var_AddCallback( p_vout, "vout-filter", FilterCallback, NULL ); + /* Get splitter name if present */ + char *splitter_name = var_InheritString(vout, "video-splitter"); + if (splitter_name && *splitter_name) { + vout->p->splitter_name = splitter_name; + } else { + free(splitter_name); + } /* */ - vout_InitInterlacingSupport( p_vout, p_vout->p->displayed.is_interlaced ); - - if( p_vout->p->psz_filter_chain && *p_vout->p->psz_filter_chain ) - { - char *psz_tmp; - if( asprintf( &psz_tmp, "%s,none", psz_name ) < 0 ) - psz_tmp = strdup( "" ); - free( psz_name ); - psz_name = psz_tmp; - } - p_vout->p->psz_module_name = psz_name; + vout_InitInterlacingSupport(vout, vout->p->displayed.is_interlaced); /* */ - vlc_object_set_destructor( p_vout, vout_Destructor ); + vlc_object_set_destructor(vout, VoutDestructor); /* */ - vlc_cond_init( &p_vout->p->change_wait ); - if( vlc_clone( &p_vout->p->thread, Thread, p_vout, - VLC_THREAD_PRIORITY_OUTPUT ) ) - { - spu_Attach( p_vout->p->p_spu, VLC_OBJECT(p_vout), false ); - spu_Destroy( p_vout->p->p_spu ); - p_vout->p->p_spu = NULL; - vlc_object_release( p_vout ); + if (vlc_clone(&vout->p->thread, Thread, vout, + VLC_THREAD_PRIORITY_OUTPUT)) { + spu_Destroy(vout->p->spu); + vlc_object_release(vout); return NULL; } - vlc_mutex_lock( &p_vout->p->change_lock ); - while( !p_vout->p->b_ready ) - { /* We are (ab)using the same condition in opposite directions for - * b_ready and b_done. This works because of the strict ordering. */ - assert( !p_vout->p->b_done ); - vlc_cond_wait( &p_vout->p->change_wait, &p_vout->p->change_lock ); - } - vlc_mutex_unlock( &p_vout->p->change_lock ); + vout_control_WaitEmpty(&vout->p->control); - if( p_vout->p->b_error ) - { - msg_Err( p_vout, "video output creation failed" ); - vout_CloseAndRelease( p_vout ); + if (vout->p->dead) { + msg_Err(vout, "video output creation failed"); + vout_CloseAndRelease(vout); return NULL; } - return p_vout; + vout->p->input = cfg->input; + if (vout->p->input) + spu_Attach(vout->p->spu, vout->p->input, true); + + return vout; } -/***************************************************************************** - * vout_Close: Close a vout created by vout_Create. - ***************************************************************************** - * You HAVE to call it on vout created by vout_Create before vlc_object_release. - * You should NEVER call it on vout not obtained through vout_Create - * (like with vout_Request or vlc_object_find.) - * You can use vout_CloseAndRelease() as a convenience method. - *****************************************************************************/ -void vout_Close( vout_thread_t *p_vout ) +vout_thread_t *(vout_Request)(vlc_object_t *object, + const vout_configuration_t *cfg) { - assert( p_vout ); + vout_thread_t *vout = cfg->vout; + if (cfg->change_fmt && !cfg->fmt) { + if (vout) + vout_CloseAndRelease(vout); + return NULL; + } + + /* If a vout is provided, try reusing it */ + if (vout) { + if (vout->p->input != cfg->input) { + if (vout->p->input) + spu_Attach(vout->p->spu, vout->p->input, false); + vout->p->input = cfg->input; + if (vout->p->input) + spu_Attach(vout->p->spu, vout->p->input, true); + } + + if (cfg->change_fmt) { + vout_control_cmd_t cmd; + vout_control_cmd_Init(&cmd, VOUT_CONTROL_REINIT); + cmd.u.cfg = cfg; - vlc_mutex_lock( &p_vout->p->change_lock ); - p_vout->p->b_done = true; - vlc_cond_signal( &p_vout->p->change_wait ); - vlc_mutex_unlock( &p_vout->p->change_lock ); + vout_control_Push(&vout->p->control, &cmd); + vout_control_WaitEmpty(&vout->p->control); + } - vout_snapshot_End( &p_vout->p->snapshot ); + if (!vout->p->dead) { + msg_Dbg(object, "reusing provided vout"); + vout_IntfReinit(vout); + return vout; + } + vout_CloseAndRelease(vout); - vlc_join( p_vout->p->thread, NULL ); + msg_Warn(object, "cannot reuse provided vout"); + } + return VoutCreate(object, cfg); } -/* */ -static void vout_Destructor( vlc_object_t * p_this ) +void vout_Close(vout_thread_t *vout) { - vout_thread_t *p_vout = (vout_thread_t *)p_this; + assert(vout); - /* Make sure the vout was stopped first */ - //assert( !p_vout->p_module ); + if (vout->p->input) + spu_Attach(vout->p->spu, vout->p->input, false); - free( p_vout->p->psz_module_name ); + vout_snapshot_End(&vout->p->snapshot); - /* */ - if( p_vout->p->p_spu ) - spu_Destroy( p_vout->p->p_spu ); + vout_control_PushVoid(&vout->p->control, VOUT_CONTROL_CLEAN); + vlc_join(vout->p->thread, NULL); - vout_chrono_Clean( &p_vout->p->render ); + vlc_mutex_lock(&vout->p->spu_lock); + spu_Destroy(vout->p->spu); + vout->p->spu = NULL; + vlc_mutex_unlock(&vout->p->spu_lock); +} - if( p_vout->p->decoder_fifo ) - picture_fifo_Delete( p_vout->p->decoder_fifo ); - assert( !p_vout->p->decoder_pool ); +/* */ +static void VoutDestructor(vlc_object_t *object) +{ + vout_thread_t *vout = (vout_thread_t *)object; - /* Destroy the locks */ - vlc_cond_destroy( &p_vout->p->change_wait ); - vlc_mutex_destroy( &p_vout->p->picture_lock ); - vlc_mutex_destroy( &p_vout->p->change_lock ); - vlc_mutex_destroy( &p_vout->p->vfilter_lock ); - vout_control_Clean( &p_vout->p->control ); + /* Make sure the vout was stopped first */ + //assert(!vout->p_module); - /* */ - vout_statistic_Clean( &p_vout->p->statistic ); + free(vout->p->splitter_name); - /* */ - vout_snapshot_Clean( &p_vout->p->snapshot ); + /* Destroy the locks */ + vlc_mutex_destroy(&vout->p->spu_lock); + vlc_mutex_destroy(&vout->p->picture_lock); + vlc_mutex_destroy(&vout->p->filter.lock); + vout_control_Clean(&vout->p->control); /* */ - free( p_vout->p->psz_filter_chain ); - - config_ChainDestroy( p_vout->p->p_cfg ); + vout_statistic_Clean(&vout->p->statistic); - free( p_vout->p ); + /* */ + vout_snapshot_Clean(&vout->p->snapshot); + video_format_Clean(&vout->p->original); } /* */ @@ -433,10 +286,9 @@ void vout_ChangePause(vout_thread_t *vout, bool is_paused, mtime_t date) vout_control_WaitEmpty(&vout->p->control); } -void vout_GetResetStatistic( vout_thread_t *p_vout, int *pi_displayed, int *pi_lost ) +void vout_GetResetStatistic(vout_thread_t *vout, int *displayed, int *lost) { - vout_statistic_GetReset( &p_vout->p->statistic, - pi_displayed, pi_lost ); + vout_statistic_GetReset( &vout->p->statistic, displayed, lost ); } void vout_Flush(vout_thread_t *vout, mtime_t date) @@ -506,9 +358,133 @@ void vout_DisplayTitle(vout_thread_t *vout, const char *title) vout_control_PushString(&vout->p->control, VOUT_CONTROL_OSD_TITLE, title); } -spu_t *vout_GetSpu( vout_thread_t *p_vout ) +void vout_PutSubpicture( vout_thread_t *vout, subpicture_t *subpic ) +{ + vout_control_cmd_t cmd; + vout_control_cmd_Init(&cmd, VOUT_CONTROL_SUBPICTURE); + cmd.u.subpicture = subpic; + + vout_control_Push(&vout->p->control, &cmd); +} +int vout_RegisterSubpictureChannel( vout_thread_t *vout ) +{ + int channel = SPU_DEFAULT_CHANNEL; + + vlc_mutex_lock(&vout->p->spu_lock); + if (vout->p->spu) + channel = spu_RegisterChannel(vout->p->spu); + vlc_mutex_unlock(&vout->p->spu_lock); + + return channel; +} +void vout_FlushSubpictureChannel( vout_thread_t *vout, int channel ) { - return p_vout->p->p_spu; + vout_control_PushInteger(&vout->p->control, VOUT_CONTROL_FLUSH_SUBPICTURE, + channel); +} + +/** + * It retreives a picture from the vout or NULL if no pictures are + * available yet. + * + * You MUST call vout_PutPicture or vout_ReleasePicture on it. + * + * You may use vout_HoldPicture(paired with vout_ReleasePicture) to keep a + * read-only reference. + */ +picture_t *vout_GetPicture(vout_thread_t *vout) +{ + /* Get lock */ + vlc_mutex_lock(&vout->p->picture_lock); + picture_t *picture = picture_pool_Get(vout->p->decoder_pool); + if (picture) { + picture_Reset(picture); + VideoFormatCopyCropAr(&picture->format, &vout->p->original); + } + vlc_mutex_unlock(&vout->p->picture_lock); + + return picture; +} + +/** + * It gives to the vout a picture to be displayed. + * + * The given picture MUST comes from vout_GetPicture. + * + * Becareful, after vout_PutPicture is called, picture_t::p_next cannot be + * read/used. + */ +void vout_PutPicture(vout_thread_t *vout, picture_t *picture) +{ + vlc_mutex_lock(&vout->p->picture_lock); + + picture->p_next = NULL; + picture_fifo_Push(vout->p->decoder_fifo, picture); + + vlc_mutex_unlock(&vout->p->picture_lock); + + vout_control_Wake(&vout->p->control); +} + +/** + * It releases a picture retreived by vout_GetPicture. + */ +void vout_ReleasePicture(vout_thread_t *vout, picture_t *picture) +{ + vlc_mutex_lock(&vout->p->picture_lock); + + picture_Release(picture); + + vlc_mutex_unlock(&vout->p->picture_lock); + + vout_control_Wake(&vout->p->control); +} + +/** + * It increment the reference counter of a picture retreived by + * vout_GetPicture. + */ +void vout_HoldPicture(vout_thread_t *vout, picture_t *picture) +{ + vlc_mutex_lock(&vout->p->picture_lock); + + picture_Hold(picture); + + vlc_mutex_unlock(&vout->p->picture_lock); +} + +/* */ +int vout_GetSnapshot(vout_thread_t *vout, + block_t **image_dst, picture_t **picture_dst, + video_format_t *fmt, + const char *type, mtime_t timeout) +{ + picture_t *picture = vout_snapshot_Get(&vout->p->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)) { + 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 */ @@ -549,10 +525,10 @@ void vout_ControlChangeCropWindow(vout_thread_t *vout, { vout_control_cmd_t cmd; vout_control_cmd_Init(&cmd, VOUT_CONTROL_CROP_WINDOW); - cmd.u.window.x = x; - cmd.u.window.y = y; - cmd.u.window.width = width; - cmd.u.window.height = height; + cmd.u.window.x = __MAX(x, 0); + cmd.u.window.y = __MAX(y, 0); + cmd.u.window.width = __MAX(width, 0); + cmd.u.window.height = __MAX(height, 0); vout_control_Push(&vout->p->control, &cmd); } @@ -561,255 +537,609 @@ void vout_ControlChangeCropBorder(vout_thread_t *vout, { vout_control_cmd_t cmd; vout_control_cmd_Init(&cmd, VOUT_CONTROL_CROP_BORDER); - cmd.u.border.left = left; - cmd.u.border.top = top; - cmd.u.border.right = right; - cmd.u.border.bottom = bottom; + cmd.u.border.left = __MAX(left, 0); + cmd.u.border.top = __MAX(top, 0); + cmd.u.border.right = __MAX(right, 0); + cmd.u.border.bottom = __MAX(bottom, 0); vout_control_Push(&vout->p->control, &cmd); } +void vout_ControlChangeFilters(vout_thread_t *vout, const char *filters) +{ + vout_control_PushString(&vout->p->control, VOUT_CONTROL_CHANGE_FILTERS, + filters); +} +void vout_ControlChangeSubSources(vout_thread_t *vout, const char *filters) +{ + vout_control_PushString(&vout->p->control, VOUT_CONTROL_CHANGE_SUB_SOURCES, + filters); +} +void vout_ControlChangeSubFilters(vout_thread_t *vout, const char *filters) +{ + vout_control_PushString(&vout->p->control, VOUT_CONTROL_CHANGE_SUB_FILTERS, + filters); +} +void vout_ControlChangeSubMargin(vout_thread_t *vout, int margin) +{ + vout_control_PushInteger(&vout->p->control, VOUT_CONTROL_CHANGE_SUB_MARGIN, + margin); +} -/***************************************************************************** - * InitThread: initialize video output thread - ***************************************************************************** - * This function is called from Thread and performs the second step of the - * initialization. It returns 0 on success. Note that the thread's flag are not - * modified inside this function. - * XXX You have to enter it with change_lock taken. - *****************************************************************************/ -static int ThreadInit(vout_thread_t *vout) +/* */ +static void VoutGetDisplayCfg(vout_thread_t *vout, vout_display_cfg_t *cfg, const char *title) { - /* Initialize output method, it allocates direct buffers for us */ - if (vout_InitWrapper(vout)) - return VLC_EGENERIC; - assert(vout->p->decoder_pool); + /* Load configuration */ + cfg->is_fullscreen = var_CreateGetBool(vout, "fullscreen"); + cfg->display.title = title; + const int display_width = var_CreateGetInteger(vout, "width"); + const int display_height = var_CreateGetInteger(vout, "height"); + cfg->display.width = display_width > 0 ? display_width : 0; + cfg->display.height = display_height > 0 ? display_height : 0; + cfg->is_display_filled = var_CreateGetBool(vout, "autoscale"); + 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_CreateGetFloat(vout, "scale"); + vlc_ureduce(&zoom_num, &zoom_den, zoom_num, zoom_den, 0); + cfg->zoom.num = zoom_num; + cfg->zoom.den = zoom_den; + cfg->align.vertical = VOUT_DISPLAY_ALIGN_CENTER; + cfg->align.horizontal = VOUT_DISPLAY_ALIGN_CENTER; + const int align_mask = var_CreateGetInteger(vout, "align"); + if (align_mask & 0x1) + cfg->align.horizontal = VOUT_DISPLAY_ALIGN_LEFT; + else if (align_mask & 0x2) + cfg->align.horizontal = VOUT_DISPLAY_ALIGN_RIGHT; + if (align_mask & 0x4) + cfg->align.vertical = VOUT_DISPLAY_ALIGN_TOP; + else if (align_mask & 0x8) + cfg->align.vertical = VOUT_DISPLAY_ALIGN_BOTTOM; +} + +vout_window_t * vout_NewDisplayWindow(vout_thread_t *vout, vout_display_t *vd, + const vout_window_cfg_t *cfg) +{ + VLC_UNUSED(vd); + vout_window_cfg_t cfg_override = *cfg; + + if (!var_InheritBool( vout, "embedded-video")) + cfg_override.is_standalone = true; + + if (vout->p->window.is_unused && vout->p->window.object) { + assert(!vout->p->splitter_name); + if (!cfg_override.is_standalone == !vout->p->window.cfg.is_standalone && + cfg_override.type == vout->p->window.cfg.type) { + /* Reuse the stored window */ + msg_Dbg(vout, "Reusing previous vout window"); + vout_window_t *window = vout->p->window.object; + if (cfg_override.width != vout->p->window.cfg.width || + cfg_override.height != vout->p->window.cfg.height) + vout_window_SetSize(window, + cfg_override.width, cfg_override.height); + vout->p->window.is_unused = false; + vout->p->window.cfg = cfg_override; + return window; + } - vout->p->displayed.decoded = NULL; + vout_window_Delete(vout->p->window.object); + vout->p->window.is_unused = true; + vout->p->window.object = NULL; + } - /* print some usefull debug info about different vout formats - */ - PrintVideoFormat(vout, "pic render", &vout->p->original); - return VLC_SUCCESS; + vout_window_t *window = vout_window_New(VLC_OBJECT(vout), "$window", + &cfg_override); + if (!window) + return NULL; + if (!vout->p->splitter_name) { + vout->p->window.is_unused = false; + vout->p->window.cfg = cfg_override; + vout->p->window.object = window; + } + return window; } -static int ThreadDisplayPicture(vout_thread_t *vout, - bool now, mtime_t *deadline) +void vout_DeleteDisplayWindow(vout_thread_t *vout, vout_display_t *vd, + vout_window_t *window) { - vout_display_t *vd = vout->p->display.vd; - int displayed_count = 0; - int lost_count = 0; + VLC_UNUSED(vd); + if (!vout->p->window.is_unused && vout->p->window.object == window) { + vout->p->window.is_unused = true; + } else if (vout->p->window.is_unused && vout->p->window.object && !window) { + vout_window_Delete(vout->p->window.object); + vout->p->window.is_unused = true; + vout->p->window.object = NULL; + } else if (window) { + vout_window_Delete(window); + } +} - for (;;) { - const mtime_t date = mdate(); - const bool is_paused = vout->p->pause.is_on; - bool redisplay = is_paused && !now && vout->p->displayed.decoded; - bool is_forced; - - /* 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 coulmd be removed - * if and only if: - * - vout module emits events from theselves. - * - *and* SPU is modified to emit an event or a deadline when needed. - * - * So it will be done latter. - */ - if (!redisplay) { - picture_t *peek = picture_fifo_Peek(vout->p->decoder_fifo); - if (peek) { - is_forced = peek->b_force || is_paused || now; - *deadline = (is_forced ? date : peek->date) - vout_chrono_GetHigh(&vout->p->render); - picture_Release(peek); +/* */ +static picture_t *VoutVideoFilterInteractiveNewPicture(filter_t *filter) +{ + vout_thread_t *vout = (vout_thread_t*)filter->p_owner; + + picture_t *picture = picture_pool_Get(vout->p->private_pool); + if (picture) { + picture_Reset(picture); + VideoFormatCopyCropAr(&picture->format, &filter->fmt_out.video); + } + return picture; +} +static picture_t *VoutVideoFilterStaticNewPicture(filter_t *filter) +{ + vout_thread_t *vout = (vout_thread_t*)filter->p_owner; + + vlc_assert_locked(&vout->p->filter.lock); + if (filter_chain_GetLength(vout->p->filter.chain_interactive) == 0) + return VoutVideoFilterInteractiveNewPicture(filter); + + return picture_NewFromFormat(&filter->fmt_out.video); +} +static void VoutVideoFilterDelPicture(filter_t *filter, picture_t *picture) +{ + VLC_UNUSED(filter); + picture_Release(picture); +} +static int VoutVideoFilterStaticAllocationSetup(filter_t *filter, void *data) +{ + filter->pf_video_buffer_new = VoutVideoFilterStaticNewPicture; + filter->pf_video_buffer_del = VoutVideoFilterDelPicture; + filter->p_owner = data; /* vout */ + return VLC_SUCCESS; +} +static int VoutVideoFilterInteractiveAllocationSetup(filter_t *filter, void *data) +{ + filter->pf_video_buffer_new = VoutVideoFilterInteractiveNewPicture; + filter->pf_video_buffer_del = VoutVideoFilterDelPicture; + filter->p_owner = data; /* vout */ + return VLC_SUCCESS; +} +static void ThreadFilterFlush(vout_thread_t *vout, bool is_locked) +{ + if (vout->p->displayed.current) + picture_Release( vout->p->displayed.current ); + vout->p->displayed.current = NULL; + + if (vout->p->displayed.next) + picture_Release( vout->p->displayed.next ); + vout->p->displayed.next = NULL; + + if (!is_locked) + vlc_mutex_lock(&vout->p->filter.lock); + filter_chain_VideoFlush(vout->p->filter.chain_static); + filter_chain_VideoFlush(vout->p->filter.chain_interactive); + if (!is_locked) + vlc_mutex_unlock(&vout->p->filter.lock); +} + +typedef struct { + char *name; + config_chain_t *cfg; +} vout_filter_t; + +static void ThreadChangeFilters(vout_thread_t *vout, + const video_format_t *source, + const char *filters, + bool is_locked) +{ + ThreadFilterFlush(vout, is_locked); + + vlc_array_t array_static; + vlc_array_t array_interactive; + + vlc_array_init(&array_static); + vlc_array_init(&array_interactive); + char *current = filters ? strdup(filters) : NULL; + while (current) { + config_chain_t *cfg; + char *name; + char *next = config_ChainCreate(&name, &cfg, current); + + if (name && *name) { + vout_filter_t *e = xmalloc(sizeof(*e)); + e->name = name; + e->cfg = cfg; + if (!strcmp(e->name, "deinterlace") || + !strcmp(e->name, "postproc")) { + vlc_array_append(&array_static, e); } else { - redisplay = true; + vlc_array_append(&array_interactive, e); } + } else { + if (cfg) + config_ChainDestroy(cfg); + free(name); } - if (redisplay) { - /* FIXME a better way for this delay is needed */ - const mtime_t date_update = vout->p->displayed.date + VOUT_REDISPLAY_DELAY; - if (date_update > date || !vout->p->displayed.decoded) { - *deadline = vout->p->displayed.decoded ? date_update : VLC_TS_INVALID; - break; + free(current); + current = next; + } + + if (!is_locked) + vlc_mutex_lock(&vout->p->filter.lock); + + es_format_t fmt_target; + es_format_InitFromVideo(&fmt_target, source ? source : &vout->p->filter.format); + + es_format_t fmt_current = fmt_target; + + for (int a = 0; a < 2; a++) { + vlc_array_t *array = a == 0 ? &array_static : + &array_interactive; + filter_chain_t *chain = a == 0 ? vout->p->filter.chain_static : + vout->p->filter.chain_interactive; + + filter_chain_Reset(chain, &fmt_current, &fmt_current); + for (int i = 0; i < vlc_array_count(array); i++) { + vout_filter_t *e = vlc_array_item_at_index(array, i); + msg_Dbg(vout, "Adding '%s' as %s", e->name, a == 0 ? "static" : "interactive"); + if (!filter_chain_AppendFilter(chain, e->name, e->cfg, NULL, NULL)) { + msg_Err(vout, "Failed to add filter '%s'", e->name); + config_ChainDestroy(e->cfg); } - /* */ - is_forced = true; - *deadline = date - vout_chrono_GetHigh(&vout->p->render); + free(e->name); + free(e); } - if (*deadline > VOUT_MWAIT_TOLERANCE) - *deadline -= VOUT_MWAIT_TOLERANCE; + fmt_current = *filter_chain_GetFmtOut(chain); + vlc_array_clear(array); + } + VideoFormatCopyCropAr(&fmt_target.video, &fmt_current.video); + if (!es_format_IsSimilar(&fmt_current, &fmt_target)) { + msg_Dbg(vout, "Adding a filter to compensate for format changes"); + if (!filter_chain_AppendFilter(vout->p->filter.chain_interactive, NULL, NULL, + &fmt_current, &fmt_target)) { + msg_Err(vout, "Failed to compensate for the format changes, removing all filters"); + filter_chain_Reset(vout->p->filter.chain_static, &fmt_target, &fmt_target); + filter_chain_Reset(vout->p->filter.chain_interactive, &fmt_target, &fmt_target); + } + } - /* If we are too early and can wait, do it */ - if (date < *deadline && !now) - break; + if (vout->p->filter.configuration != filters) { + free(vout->p->filter.configuration); + vout->p->filter.configuration = filters ? strdup(filters) : NULL; + } + if (source) { + video_format_Clean(&vout->p->filter.format); + video_format_Copy(&vout->p->filter.format, source); + } + + if (!is_locked) + vlc_mutex_unlock(&vout->p->filter.lock); +} + +/* */ +static int ThreadDisplayPreparePicture(vout_thread_t *vout, bool reuse, bool frame_by_frame) +{ + bool is_late_dropped = vout->p->is_late_dropped && !vout->p->pause.is_on && !frame_by_frame; + + vlc_mutex_lock(&vout->p->filter.lock); + + picture_t *picture = filter_chain_VideoFilter(vout->p->filter.chain_static, NULL); + assert(!reuse || !picture); + + while (!picture) { picture_t *decoded; - if (redisplay) { - decoded = vout->p->displayed.decoded; - vout->p->displayed.decoded = NULL; + if (reuse && vout->p->displayed.decoded) { + decoded = picture_Hold(vout->p->displayed.decoded); } else { decoded = picture_fifo_Pop(vout->p->decoder_fifo); - assert(decoded); - if (!is_forced && !vout->p->is_late_dropped) { - const mtime_t predicted = date + vout_chrono_GetLow(&vout->p->render); - const mtime_t late = predicted - decoded->date; - if (late > 0) { - msg_Dbg(vout, "picture might be displayed late (missing %d ms)", (int)(late/1000)); + if (decoded) { + if (is_late_dropped && !decoded->b_force) { + const mtime_t predicted = mdate() + 0; /* TODO improve */ + const mtime_t late = predicted - decoded->date; if (late > VOUT_DISPLAY_LATE_THRESHOLD) { - msg_Warn(vout, "rejected picture because of render time"); - /* TODO */ + msg_Warn(vout, "picture is too late to be displayed (missing %"PRId64" ms)", late/1000); picture_Release(decoded); - lost_count++; - break; + vout_statistic_AddLost(&vout->p->statistic, 1); + continue; + } else if (late > 0) { + msg_Dbg(vout, "picture might be displayed late (missing %"PRId64" ms)", late/1000); } } + if (!VideoFormatIsCropArEqual(&decoded->format, &vout->p->filter.format)) + ThreadChangeFilters(vout, &decoded->format, vout->p->filter.configuration, true); } - - vout->p->displayed.is_interlaced = !decoded->b_progressive; - vout->p->displayed.qtype = decoded->i_qtype; } - vout->p->displayed.timestamp = decoded->date; - /* */ + if (!decoded) + break; + reuse = false; + if (vout->p->displayed.decoded) picture_Release(vout->p->displayed.decoded); - picture_Hold(decoded); - vout->p->displayed.decoded = decoded; - - /* */ - vout_chrono_Start(&vout->p->render); - - picture_t *filtered = NULL; - if (decoded) { - vlc_mutex_lock(&vout->p->vfilter_lock); - filtered = filter_chain_VideoFilter(vout->p->vfilter_chain, decoded); - //assert(filtered == decoded); // TODO implement - vlc_mutex_unlock(&vout->p->vfilter_lock); - if (!filtered) - continue; - } - /* - * Check for subpictures to display - */ - const bool do_snapshot = vout_snapshot_IsRequested(&vout->p->snapshot); - mtime_t spu_render_time = is_forced ? mdate() : filtered->date; - if (vout->p->pause.is_on) - spu_render_time = vout->p->pause.date; - else - spu_render_time = filtered->date > 1 ? filtered->date : mdate(); - - subpicture_t *subpic = spu_SortSubpictures(vout->p->p_spu, - spu_render_time, - do_snapshot); - /* - * Perform rendering - * - * We have to: - * - be sure to end up with a direct buffer. - * - blend subtitles, and in a fast access buffer - */ - picture_t *direct = NULL; - if (filtered && - (vout->p->decoder_pool != vout->p->display_pool || subpic)) { - picture_t *render; - if (vout->p->is_decoder_pool_slow) - render = picture_NewFromFormat(&vd->source); - else if (vout->p->decoder_pool != vout->p->display_pool) - render = picture_pool_Get(vout->p->display_pool); - else - render = picture_pool_Get(vout->p->private_pool); - - if (render) { - picture_Copy(render, filtered); - - spu_RenderSubpictures(vout->p->p_spu, - render, &vd->source, - subpic, &vd->source, spu_render_time); - } - if (vout->p->is_decoder_pool_slow) { - direct = picture_pool_Get(vout->p->display_pool); - if (direct) - picture_Copy(direct, render); - picture_Release(render); + vout->p->displayed.decoded = picture_Hold(decoded); + vout->p->displayed.timestamp = decoded->date; + vout->p->displayed.is_interlaced = !decoded->b_progressive; - } else { - direct = render; - } - picture_Release(filtered); - filtered = NULL; + picture = filter_chain_VideoFilter(vout->p->filter.chain_static, decoded); + } + + vlc_mutex_unlock(&vout->p->filter.lock); + + if (!picture) + return VLC_EGENERIC; + + assert(!vout->p->displayed.next); + if (!vout->p->displayed.current) + vout->p->displayed.current = picture; + else + vout->p->displayed.next = picture; + return VLC_SUCCESS; +} + +static int ThreadDisplayRenderPicture(vout_thread_t *vout, bool is_forced) +{ + vout_thread_sys_t *sys = vout->p; + vout_display_t *vd = vout->p->display.vd; + + picture_t *torender = picture_Hold(vout->p->displayed.current); + + vout_chrono_Start(&vout->p->render); + + vlc_mutex_lock(&vout->p->filter.lock); + picture_t *filtered = filter_chain_VideoFilter(vout->p->filter.chain_interactive, torender); + vlc_mutex_unlock(&vout->p->filter.lock); + + if (!filtered) + return VLC_EGENERIC; + + if (filtered->date != vout->p->displayed.current->date) + msg_Warn(vout, "Unsupported timestamp modifications done by chain_interactive"); + + /* + * Get the subpicture to be displayed + */ + const bool do_snapshot = vout_snapshot_IsRequested(&vout->p->snapshot); + mtime_t render_subtitle_date; + if (vout->p->pause.is_on) + render_subtitle_date = vout->p->pause.date; + else + render_subtitle_date = filtered->date > 1 ? filtered->date : mdate(); + mtime_t render_osd_date = mdate(); /* FIXME wrong */ + + /* + * Get the subpicture to be displayed + */ + const bool do_dr_spu = !do_snapshot && + vd->info.subpicture_chromas && + *vd->info.subpicture_chromas != 0; + const bool do_early_spu = !do_dr_spu && + (vd->info.is_slow || + sys->display.use_dr || + do_snapshot || + !vout_IsDisplayFiltered(vd) || + vd->fmt.i_width * vd->fmt.i_height <= vd->source.i_width * vd->source.i_height); + + const vlc_fourcc_t *subpicture_chromas; + video_format_t fmt_spu; + if (do_dr_spu) { + vout_display_place_t place; + vout_display_PlacePicture(&place, &vd->source, vd->cfg, false); + + fmt_spu = vd->source; + if (fmt_spu.i_width * fmt_spu.i_height < place.width * place.height) { + 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; + } + subpicture_chromas = vd->info.subpicture_chromas; + } else { + if (do_early_spu) { + fmt_spu = vd->source; } else { - direct = filtered; + fmt_spu = vd->fmt; + fmt_spu.i_sar_num = vd->cfg->display.sar.num; + fmt_spu.i_sar_den = vd->cfg->display.sar.den; } + subpicture_chromas = NULL; - /* - * Take a snapshot if requested - */ - if (direct && do_snapshot) - vout_snapshot_Set(&vout->p->snapshot, &vd->source, direct); + if (vout->p->spu_blend && + vout->p->spu_blend->fmt_out.video.i_chroma != fmt_spu.i_chroma) { + filter_DeleteBlend(vout->p->spu_blend); + vout->p->spu_blend = NULL; + vout->p->spu_blend_chroma = 0; + } + if (!vout->p->spu_blend && vout->p->spu_blend_chroma != fmt_spu.i_chroma) { + vout->p->spu_blend_chroma = fmt_spu.i_chroma; + vout->p->spu_blend = filter_NewBlend(VLC_OBJECT(vout), &fmt_spu); + if (!vout->p->spu_blend) + msg_Err(vout, "Failed to create blending filter, OSD/Subtitles will not work"); + } + } - /* Render the direct buffer returned by vout_RenderPicture */ - if (direct) { - vout_RenderWrapper(vout, direct); + subpicture_t *subpic = spu_Render(vout->p->spu, + subpicture_chromas, &fmt_spu, + &vd->source, + render_subtitle_date, render_osd_date, + do_snapshot); + /* + * Perform rendering + * + * We have to: + * - be sure to end up with a direct buffer. + * - blend subtitles, and in a fast access buffer + */ + bool is_direct = vout->p->decoder_pool == vout->p->display_pool; + picture_t *todisplay = filtered; + if (do_early_spu && subpic) { + picture_t *blent = picture_pool_Get(vout->p->private_pool); + if (blent) { + VideoFormatCopyCropAr(&blent->format, &filtered->format); + picture_Copy(blent, filtered); + if (vout->p->spu_blend + && picture_BlendSubpicture(blent, vout->p->spu_blend, subpic)) { + picture_Release(todisplay); + todisplay = blent; + } else + picture_Release(blent); + } + subpicture_Delete(subpic); + subpic = NULL; + } - vout_chrono_Stop(&vout->p->render); -#if 0 - { - static int i = 0; - if (((i++)%10) == 0) - msg_Info(vout, "render: avg %d ms var %d ms", - (int)(vout->p->render.avg/1000), (int)(vout->p->render.var/1000)); - } -#endif + assert(vout_IsDisplayFiltered(vd) == !sys->display.use_dr); + if (sys->display.use_dr && !is_direct) { + picture_t *direct = picture_pool_Get(vout->p->display_pool); + if (!direct) { + picture_Release(todisplay); + if (subpic) + subpicture_Delete(subpic); + return VLC_EGENERIC; } - /* Wait the real date (for rendering jitter) */ - if (!is_forced) - mwait(decoded->date); + /* The display uses direct rendering (no conversion), but its pool of + * pictures is not usable by the decoder (too few, too slow or + * subject to invalidation...). Since there are no filters, copying + * pictures from the decoder to the output is unavoidable. */ + VideoFormatCopyCropAr(&direct->format, &todisplay->format); + picture_Copy(direct, todisplay); + picture_Release(todisplay); + todisplay = direct; + } - /* Display the direct buffer returned by vout_RenderPicture */ - vout->p->displayed.date = mdate(); - if (direct) - vout_DisplayWrapper(vout, direct); + /* + * Take a snapshot if requested + */ + if (do_snapshot) + vout_snapshot_Set(&vout->p->snapshot, &vd->source, todisplay); - displayed_count++; - break; + /* Render the direct buffer */ + vout_UpdateDisplaySourceProperties(vd, &todisplay->format); + if (sys->display.use_dr) { + vout_display_Prepare(vd, todisplay, subpic); + } else { + sys->display.filtered = vout_FilterDisplay(vd, todisplay); + if (sys->display.filtered) { + if (!do_dr_spu && !do_early_spu && vout->p->spu_blend && subpic) + picture_BlendSubpicture(sys->display.filtered, vout->p->spu_blend, subpic); + vout_display_Prepare(vd, sys->display.filtered, do_dr_spu ? subpic : NULL); + } + if (!do_dr_spu && subpic) + { + subpicture_Delete(subpic); + subpic = NULL; + } + if (!sys->display.filtered) + return VLC_EGENERIC; } - vout_statistic_Update(&vout->p->statistic, displayed_count, lost_count); - if (displayed_count <= 0) - return VLC_EGENERIC; + vout_chrono_Stop(&vout->p->render); +#if 0 + { + static int i = 0; + if (((i++)%10) == 0) + msg_Info(vout, "render: avg %d ms var %d ms", + (int)(vout->p->render.avg/1000), (int)(vout->p->render.var/1000)); + } +#endif + + /* Wait the real date (for rendering jitter) */ +#if 0 + mtime_t delay = direct->date - mdate(); + if (delay < 1000) + msg_Warn(vout, "picture is late (%lld ms)", delay / 1000); +#endif + if (!is_forced) + mwait(todisplay->date); + + /* Display the direct buffer returned by vout_RenderPicture */ + vout->p->displayed.date = mdate(); + vout_display_Display(vd, + sys->display.filtered ? sys->display.filtered + : todisplay, + subpic); + sys->display.filtered = NULL; + + vout_statistic_AddDisplayed(&vout->p->statistic, 1); + return VLC_SUCCESS; } -static int ThreadManage(vout_thread_t *vout, - mtime_t *deadline, - vout_interlacing_support_t *interlacing, - vout_postprocessing_support_t *postprocessing) +static int ThreadDisplayPicture(vout_thread_t *vout, mtime_t *deadline) { - vlc_mutex_lock(&vout->p->picture_lock); - - *deadline = VLC_TS_INVALID; - ThreadDisplayPicture(vout, false, deadline); + bool frame_by_frame = !deadline; + bool paused = vout->p->pause.is_on; + bool first = !vout->p->displayed.current; + + if (first) + if (ThreadDisplayPreparePicture(vout, true, frame_by_frame)) /* FIXME not sure it is ok */ + return VLC_EGENERIC; + + if (!paused || frame_by_frame) + while (!vout->p->displayed.next && !ThreadDisplayPreparePicture(vout, false, frame_by_frame)) + ; + + const mtime_t date = mdate(); + const mtime_t render_delay = vout_chrono_GetHigh(&vout->p->render) + VOUT_MWAIT_TOLERANCE; + + bool drop_next_frame = frame_by_frame; + mtime_t date_next = VLC_TS_INVALID; + if (!paused && vout->p->displayed.next) { + date_next = vout->p->displayed.next->date - render_delay; + if (date_next /* + 0 FIXME */ <= date) + drop_next_frame = true; + } - const int picture_qtype = vout->p->displayed.qtype; - const bool picture_interlaced = vout->p->displayed.is_interlaced; + /* 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 theselves. + * - *and* SPU is modified to emit an event or a deadline when needed. + * + * So it will be done later. + */ + bool refresh = false; - vlc_mutex_unlock(&vout->p->picture_lock); + mtime_t date_refresh = VLC_TS_INVALID; + if (vout->p->displayed.date > VLC_TS_INVALID) { + date_refresh = vout->p->displayed.date + VOUT_REDISPLAY_DELAY - render_delay; + refresh = date_refresh <= date; + } - /* Post processing */ - vout_SetPostProcessingState(vout, postprocessing, picture_qtype); + if (!first && !refresh && !drop_next_frame) { + if (!frame_by_frame) { + if (date_refresh != VLC_TS_INVALID) + *deadline = date_refresh; + if (date_next != VLC_TS_INVALID && date_next < *deadline) + *deadline = date_next; + } + return VLC_EGENERIC; + } - /* Deinterlacing */ - vout_SetInterlacingState(vout, interlacing, picture_interlaced); + if (drop_next_frame) { + picture_Release(vout->p->displayed.current); + vout->p->displayed.current = vout->p->displayed.next; + vout->p->displayed.next = NULL; + } - if (vout_ManageWrapper(vout)) { - /* A fatal error occurred, and the thread must terminate - * immediately, without displaying anything - setting b_error to 1 - * causes the immediate end of the main while() loop. */ - // FIXME pf_end + if (!vout->p->displayed.current) return VLC_EGENERIC; - } - return VLC_SUCCESS; + + /* display the picture immediately */ + bool is_forced = frame_by_frame || (!drop_next_frame && refresh) || vout->p->displayed.current->b_force; + return ThreadDisplayRenderPicture(vout, is_forced); +} + +static void ThreadDisplaySubpicture(vout_thread_t *vout, + subpicture_t *subpicture) +{ + spu_PutSubpicture(vout->p->spu, subpicture); +} + +static void ThreadFlushSubpicture(vout_thread_t *vout, int channel) +{ + spu_ClearChannel(vout->p->spu, channel); } static void ThreadDisplayOsdTitle(vout_thread_t *vout, const char *string) @@ -817,32 +1147,28 @@ static void ThreadDisplayOsdTitle(vout_thread_t *vout, const char *string) if (!vout->p->title.show) return; - vlc_assert_locked(&vout->p->change_lock); - vout_OSDText(vout, SPU_DEFAULT_CHANNEL, vout->p->title.position, INT64_C(1000) * vout->p->title.timeout, string); } -static void ThreadChangeFilters(vout_thread_t *vout, const char *filters) +static void ThreadChangeSubSources(vout_thread_t *vout, const char *filters) { - es_format_t fmt; - es_format_Init(&fmt, VIDEO_ES, vout->p->original.i_chroma); - fmt.video = vout->p->original; - - vlc_mutex_lock(&vout->p->vfilter_lock); + spu_ChangeSources(vout->p->spu, filters); +} - filter_chain_Reset(vout->p->vfilter_chain, &fmt, &fmt); - if (filter_chain_AppendFromString(vout->p->vfilter_chain, - filters) < 0) - msg_Err(vout, "Video filter chain creation failed"); +static void ThreadChangeSubFilters(vout_thread_t *vout, const char *filters) +{ + spu_ChangeFilters(vout->p->spu, filters); +} - vlc_mutex_unlock(&vout->p->vfilter_lock); +static void ThreadChangeSubMargin(vout_thread_t *vout, int margin) +{ + spu_ChangeMargin(vout->p->spu, margin); } static void ThreadChangePause(vout_thread_t *vout, bool is_paused, mtime_t date) { - vlc_assert_locked(&vout->p->change_lock); assert(!vout->p->pause.is_on || !is_paused); if (vout->p->pause.is_on) { @@ -855,8 +1181,9 @@ static void ThreadChangePause(vout_thread_t *vout, bool is_paused, mtime_t date) picture_fifo_OffsetDate(vout->p->decoder_fifo, duration); if (vout->p->displayed.decoded) vout->p->displayed.decoded->date += duration; + spu_OffsetSubtitleDate(vout->p->spu, duration); - spu_OffsetSubtitleDate(vout->p->p_spu, duration); + ThreadFilterFlush(vout, false); } else { vout->p->step.timestamp = VLC_TS_INVALID; vout->p->step.last = VLC_TS_INVALID; @@ -870,6 +1197,8 @@ static void ThreadFlush(vout_thread_t *vout, bool below, mtime_t date) vout->p->step.timestamp = VLC_TS_INVALID; vout->p->step.last = VLC_TS_INVALID; + ThreadFilterFlush(vout, false); /* FIXME too much */ + picture_t *last = vout->p->displayed.decoded; if (last) { if (( below && last->date <= date) || @@ -881,6 +1210,7 @@ static void ThreadFlush(vout_thread_t *vout, bool below, mtime_t date) vout->p->displayed.timestamp = VLC_TS_INVALID; } } + picture_fifo_Flush(vout->p->decoder_fifo, date, below); } @@ -900,8 +1230,7 @@ static void ThreadStep(vout_thread_t *vout, mtime_t *duration) if (vout->p->step.last <= VLC_TS_INVALID) vout->p->step.last = vout->p->displayed.timestamp; - mtime_t dummy; - if (ThreadDisplayPicture(vout, true, &dummy)) + if (ThreadDisplayPicture(vout, NULL)) return; vout->p->step.timestamp = vout->p->displayed.timestamp; @@ -916,8 +1245,6 @@ static void ThreadStep(vout_thread_t *vout, mtime_t *duration) static void ThreadChangeFullscreen(vout_thread_t *vout, bool fullscreen) { - /* FIXME not sure setting "fullscreen" is good ... */ - var_SetBool(vout, "fullscreen", fullscreen); vout_SetDisplayFullscreen(vout->p->display.vd, fullscreen); } @@ -948,83 +1275,254 @@ static void ThreadChangeZoom(vout_thread_t *vout, int num, int den) static void ThreadChangeAspectRatio(vout_thread_t *vout, unsigned num, unsigned den) { - const video_format_t *source = &vout->p->original; - - if (num > 0 && den > 0) { - num *= source->i_visible_height; - den *= source->i_visible_width; - vlc_ureduce(&num, &den, num, den, 0); - } vout_SetDisplayAspect(vout->p->display.vd, num, den); } static void ThreadExecuteCropWindow(vout_thread_t *vout, - unsigned crop_num, unsigned crop_den, unsigned x, unsigned y, unsigned width, unsigned height) { - const video_format_t *source = &vout->p->original; - - vout_SetDisplayCrop(vout->p->display.vd, - crop_num, crop_den, - source->i_x_offset + x, - source->i_y_offset + y, - width, height); + vout_SetDisplayCrop(vout->p->display.vd, 0, 0, + x, y, width, height); } static void ThreadExecuteCropBorder(vout_thread_t *vout, unsigned left, unsigned top, unsigned right, unsigned bottom) { - const video_format_t *source = &vout->p->original; - ThreadExecuteCropWindow(vout, 0, 0, - left, - top, - /* At worst, it becomes < 0 (but unsigned) and will be rejected */ - source->i_visible_width - (left + right), - source->i_visible_height - (top + bottom)); + msg_Err(vout, "ThreadExecuteCropBorder %d.%d %dx%d", left, top, right, bottom); + vout_SetDisplayCrop(vout->p->display.vd, 0, 0, + left, top, -(int)right, -(int)bottom); } static void ThreadExecuteCropRatio(vout_thread_t *vout, unsigned num, unsigned den) { - const video_format_t *source = &vout->p->original; - - int x, y; - int width, height; - if (num <= 0 || den <= 0) { - num = 0; - den = 0; - x = 0; - y = 0; - width = source->i_visible_width; - height = source->i_visible_height; - } else { - unsigned scaled_width = (uint64_t)source->i_visible_height * num * source->i_sar_den / den / source->i_sar_num; - unsigned scaled_height = (uint64_t)source->i_visible_width * den * source->i_sar_num / num / source->i_sar_den; - - if (scaled_width < source->i_visible_width) { - x = (source->i_visible_width - scaled_width) / 2; - y = 0; - width = scaled_width; - height = source->i_visible_height; - } else { - x = 0; - y = (source->i_visible_height - scaled_height) / 2; - width = source->i_visible_width; - height = scaled_height; + vout_SetDisplayCrop(vout->p->display.vd, num, den, + 0, 0, 0, 0); +} + +static int ThreadStart(vout_thread_t *vout, const vout_display_state_t *state) +{ + vlc_mouse_Init(&vout->p->mouse); + vout->p->decoder_fifo = picture_fifo_New(); + vout->p->decoder_pool = NULL; + vout->p->display_pool = NULL; + vout->p->private_pool = NULL; + + vout->p->filter.configuration = NULL; + video_format_Copy(&vout->p->filter.format, &vout->p->original); + vout->p->filter.chain_static = + filter_chain_New( vout, "video filter2", true, + VoutVideoFilterStaticAllocationSetup, NULL, vout); + vout->p->filter.chain_interactive = + filter_chain_New( vout, "video filter2", true, + VoutVideoFilterInteractiveAllocationSetup, NULL, vout); + + vout_display_state_t state_default; + if (!state) { + VoutGetDisplayCfg(vout, &state_default.cfg, vout->p->display.title); + state_default.wm_state = var_CreateGetBool(vout, "video-on-top") ? VOUT_WINDOW_STATE_ABOVE : + VOUT_WINDOW_STATE_NORMAL; + state_default.sar.num = 0; + state_default.sar.den = 0; + + state = &state_default; + } + + if (vout_OpenWrapper(vout, vout->p->splitter_name, state)) + return VLC_EGENERIC; + if (vout_InitWrapper(vout)) + return VLC_EGENERIC; + assert(vout->p->decoder_pool); + + vout->p->displayed.current = NULL; + vout->p->displayed.next = NULL; + vout->p->displayed.decoded = NULL; + vout->p->displayed.date = VLC_TS_INVALID; + vout->p->displayed.timestamp = VLC_TS_INVALID; + vout->p->displayed.is_interlaced = false; + + vout->p->step.last = VLC_TS_INVALID; + vout->p->step.timestamp = VLC_TS_INVALID; + + vout->p->spu_blend_chroma = 0; + vout->p->spu_blend = NULL; + + video_format_Print(VLC_OBJECT(vout), "original format", &vout->p->original); + return VLC_SUCCESS; +} + +static void ThreadStop(vout_thread_t *vout, vout_display_state_t *state) +{ + if (vout->p->spu_blend) + filter_DeleteBlend(vout->p->spu_blend); + + /* Destroy translation tables */ + if (vout->p->display.vd) { + if (vout->p->decoder_pool) { + ThreadFlush(vout, true, INT64_MAX); + vout_EndWrapper(vout); } + vout_CloseWrapper(vout, state); } - ThreadExecuteCropWindow(vout, num, den, x, y, width, height); + + /* Destroy the video filters2 */ + filter_chain_Delete(vout->p->filter.chain_interactive); + filter_chain_Delete(vout->p->filter.chain_static); + video_format_Clean(&vout->p->filter.format); + free(vout->p->filter.configuration); + + if (vout->p->decoder_fifo) + picture_fifo_Delete(vout->p->decoder_fifo); + assert(!vout->p->decoder_pool); +} + +static void ThreadInit(vout_thread_t *vout) +{ + vout->p->window.is_unused = true; + vout->p->window.object = NULL; + vout->p->dead = false; + vout->p->is_late_dropped = var_InheritBool(vout, "drop-late-frames"); + vout->p->pause.is_on = false; + vout->p->pause.date = VLC_TS_INVALID; + + vout_chrono_Init(&vout->p->render, 5, 10000); /* Arbitrary initial time */ } static void ThreadClean(vout_thread_t *vout) { - /* Destroy translation tables */ - if (!vout->p->b_error) { - ThreadFlush(vout, true, INT64_MAX); - vout_EndWrapper(vout); + if (vout->p->window.object) { + assert(vout->p->window.is_unused); + vout_window_Delete(vout->p->window.object); + } + vout_chrono_Clean(&vout->p->render); + vout->p->dead = true; + vout_control_Dead(&vout->p->control); +} + +static int ThreadReinit(vout_thread_t *vout, + const vout_configuration_t *cfg) +{ + video_format_t original; + if (VoutValidateFormat(&original, cfg->fmt)) { + ThreadStop(vout, NULL); + ThreadClean(vout); + return VLC_EGENERIC; + } + /* We ignore crop/ar changes at this point, they are dynamically supported */ + VideoFormatCopyCropAr(&vout->p->original, &original); + if (video_format_IsSimilar(&original, &vout->p->original)) { + if (cfg->dpb_size <= vout->p->dpb_size) + return VLC_SUCCESS; + msg_Warn(vout, "DPB need to be increased"); + } + + vout_display_state_t state; + memset(&state, 0, sizeof(state)); + + ThreadStop(vout, &state); + + if (!state.cfg.is_fullscreen) { + state.cfg.display.width = 0; + state.cfg.display.height = 0; + } + state.sar.num = 0; + state.sar.den = 0; + /* FIXME current vout "variables" are not in sync here anymore + * and I am not sure what to do */ + + vout->p->original = original; + vout->p->dpb_size = cfg->dpb_size; + if (ThreadStart(vout, &state)) { + ThreadClean(vout); + return VLC_EGENERIC; + } + return VLC_SUCCESS; +} + +static int ThreadControl(vout_thread_t *vout, vout_control_cmd_t cmd) +{ + switch(cmd.type) { + case VOUT_CONTROL_INIT: + ThreadInit(vout); + if (!ThreadStart(vout, NULL)) + break; + case VOUT_CONTROL_CLEAN: + ThreadStop(vout, NULL); + ThreadClean(vout); + return 1; + case VOUT_CONTROL_REINIT: + if (ThreadReinit(vout, cmd.u.cfg)) + return 1; + break; + case VOUT_CONTROL_SUBPICTURE: + ThreadDisplaySubpicture(vout, cmd.u.subpicture); + cmd.u.subpicture = NULL; + break; + case VOUT_CONTROL_FLUSH_SUBPICTURE: + ThreadFlushSubpicture(vout, cmd.u.integer); + break; + case VOUT_CONTROL_OSD_TITLE: + ThreadDisplayOsdTitle(vout, cmd.u.string); + break; + case VOUT_CONTROL_CHANGE_FILTERS: + ThreadChangeFilters(vout, NULL, cmd.u.string, false); + break; + case VOUT_CONTROL_CHANGE_SUB_SOURCES: + ThreadChangeSubSources(vout, cmd.u.string); + break; + case VOUT_CONTROL_CHANGE_SUB_FILTERS: + ThreadChangeSubFilters(vout, cmd.u.string); + break; + case VOUT_CONTROL_CHANGE_SUB_MARGIN: + ThreadChangeSubMargin(vout, cmd.u.integer); + break; + case VOUT_CONTROL_PAUSE: + ThreadChangePause(vout, cmd.u.pause.is_on, cmd.u.pause.date); + break; + case VOUT_CONTROL_FLUSH: + ThreadFlush(vout, false, cmd.u.time); + break; + case VOUT_CONTROL_RESET: + ThreadReset(vout); + break; + case VOUT_CONTROL_STEP: + ThreadStep(vout, cmd.u.time_ptr); + break; + case VOUT_CONTROL_FULLSCREEN: + ThreadChangeFullscreen(vout, cmd.u.boolean); + break; + case VOUT_CONTROL_ON_TOP: + ThreadChangeOnTop(vout, cmd.u.boolean); + break; + case VOUT_CONTROL_DISPLAY_FILLED: + ThreadChangeDisplayFilled(vout, cmd.u.boolean); + break; + case VOUT_CONTROL_ZOOM: + ThreadChangeZoom(vout, cmd.u.pair.a, cmd.u.pair.b); + break; + case VOUT_CONTROL_ASPECT_RATIO: + ThreadChangeAspectRatio(vout, cmd.u.pair.a, cmd.u.pair.b); + break; + case VOUT_CONTROL_CROP_RATIO: + ThreadExecuteCropRatio(vout, cmd.u.pair.a, cmd.u.pair.b); + break; + case VOUT_CONTROL_CROP_WINDOW: + ThreadExecuteCropWindow(vout, + cmd.u.window.x, cmd.u.window.y, + cmd.u.window.width, cmd.u.window.height); + break; + case VOUT_CONTROL_CROP_BORDER: + ThreadExecuteCropBorder(vout, + cmd.u.border.left, cmd.u.border.top, + cmd.u.border.right, cmd.u.border.bottom); + break; + default: + break; } + vout_control_cmd_Clean(&cmd); + return 0; } /***************************************************************************** @@ -1037,199 +1535,32 @@ static void ThreadClean(vout_thread_t *vout) static void *Thread(void *object) { vout_thread_t *vout = object; - bool has_wrapper; - - /* - * Initialize thread - */ - has_wrapper = !vout_OpenWrapper(vout, vout->p->psz_module_name); - - vlc_mutex_lock(&vout->p->change_lock); - - if (has_wrapper) - vout->p->b_error = ThreadInit(vout); - else - vout->p->b_error = true; - - /* signal the creation of the vout */ - vout->p->b_ready = true; - vlc_cond_signal(&vout->p->change_wait); - - if (vout->p->b_error) - goto exit_thread; + vout_thread_sys_t *sys = vout->p; - /* */ vout_interlacing_support_t interlacing = { .is_interlaced = false, .date = mdate(), }; - vout_postprocessing_support_t postprocessing = { - .qtype = QTYPE_NONE, - }; - /* - * Main loop - it is not executed if an error occurred during - * initialization - */ mtime_t deadline = VLC_TS_INVALID; - while (!vout->p->b_done && !vout->p->b_error) { + for (;;) { vout_control_cmd_t cmd; + /* FIXME remove thoses ugly timeouts */ + while (!vout_control_Pop(&sys->control, &cmd, deadline, 100000)) + if (ThreadControl(vout, cmd)) + return NULL; - vlc_mutex_unlock(&vout->p->change_lock); - /* FIXME remove thoses ugly timeouts - */ - while (!vout_control_Pop(&vout->p->control, &cmd, deadline, 100000)) { - /* TODO remove the lock when possible (ie when - * vout->p->fmt_* are not protected by it anymore) */ - vlc_mutex_lock(&vout->p->change_lock); - switch(cmd.type) { - case VOUT_CONTROL_OSD_TITLE: - ThreadDisplayOsdTitle(vout, cmd.u.string); - break; - case VOUT_CONTROL_CHANGE_FILTERS: - ThreadChangeFilters(vout, cmd.u.string); - break; - case VOUT_CONTROL_PAUSE: - ThreadChangePause(vout, cmd.u.pause.is_on, cmd.u.pause.date); - break; - case VOUT_CONTROL_FLUSH: - ThreadFlush(vout, false, cmd.u.time); - break; - case VOUT_CONTROL_RESET: - ThreadReset(vout); - break; - case VOUT_CONTROL_STEP: - ThreadStep(vout, cmd.u.time_ptr); - break; - case VOUT_CONTROL_FULLSCREEN: - ThreadChangeFullscreen(vout, cmd.u.boolean); - break; - case VOUT_CONTROL_ON_TOP: - ThreadChangeOnTop(vout, cmd.u.boolean); - break; - case VOUT_CONTROL_DISPLAY_FILLED: - ThreadChangeDisplayFilled(vout, cmd.u.boolean); - break; - case VOUT_CONTROL_ZOOM: - ThreadChangeZoom(vout, cmd.u.pair.a, cmd.u.pair.b); - break; - case VOUT_CONTROL_ASPECT_RATIO: - ThreadChangeAspectRatio(vout, cmd.u.pair.a, cmd.u.pair.b); - break; - case VOUT_CONTROL_CROP_RATIO: - ThreadExecuteCropRatio(vout, cmd.u.pair.a, cmd.u.pair.b); - break; - case VOUT_CONTROL_CROP_WINDOW: - ThreadExecuteCropWindow(vout, 0, 0, - cmd.u.window.x, cmd.u.window.y, - cmd.u.window.width, cmd.u.window.height); - break; - case VOUT_CONTROL_CROP_BORDER: - ThreadExecuteCropBorder(vout, - cmd.u.border.left, cmd.u.border.top, - cmd.u.border.right, cmd.u.border.bottom); - break; - default: - break; - } - vlc_mutex_unlock(&vout->p->change_lock); - vout_control_cmd_Clean(&cmd); - } - vlc_mutex_lock(&vout->p->change_lock); - - /* */ - if (ThreadManage(vout, &deadline, - &interlacing, &postprocessing)) { - vout->p->b_error = true; - break; - } - } - - /* - * Error loop - wait until the thread destruction is requested - * - * XXX I wonder if we should periodically clean the decoder_fifo - * or have a way to prevent it filling up. - */ - while (vout->p->b_error && !vout->p->b_done) - vlc_cond_wait(&vout->p->change_wait, &vout->p->change_lock); - - /* Clean thread */ - ThreadClean(vout); + vlc_mutex_lock(&sys->picture_lock); -exit_thread: - /* Detach subpicture unit from both input and vout */ - spu_Attach(vout->p->p_spu, VLC_OBJECT(vout), false); - vlc_object_detach(vout->p->p_spu); + deadline = VLC_TS_INVALID; + while (!ThreadDisplayPicture(vout, &deadline)) + ; - vlc_mutex_unlock(&vout->p->change_lock); - - if (has_wrapper) - vout_CloseWrapper(vout); - vout_control_Dead(&vout->p->control); + const bool picture_interlaced = sys->displayed.is_interlaced; - /* Destroy the video filters2 */ - filter_chain_Delete(vout->p->vfilter_chain); - - return NULL; -} + vlc_mutex_unlock(&sys->picture_lock); -/***************************************************************************** - * object variables callbacks: a bunch of object variables are used by the - * interfaces to interact with the vout. - *****************************************************************************/ -static int FilterCallback( vlc_object_t *p_this, char const *psz_cmd, - vlc_value_t oldval, vlc_value_t newval, void *p_data ) -{ - vout_thread_t *p_vout = (vout_thread_t *)p_this; - input_thread_t *p_input; - (void)psz_cmd; (void)oldval; (void)p_data; - - p_input = (input_thread_t *)vlc_object_find( p_this, VLC_OBJECT_INPUT, - FIND_PARENT ); - if (!p_input) - { - msg_Err( p_vout, "Input not found" ); - return VLC_EGENERIC; + vout_SetInterlacingState(vout, &interlacing, picture_interlaced); + vout_ManageWrapper(vout); } - - /* Modify input as well because the vout might have to be restarted */ - var_Create( p_input, "vout-filter", VLC_VAR_STRING ); - var_SetString( p_input, "vout-filter", newval.psz_string ); - - /* Now restart current video stream */ - input_Control( p_input, INPUT_RESTART_ES, -VIDEO_ES ); - vlc_object_release( p_input ); - - return VLC_SUCCESS; } - -/***************************************************************************** - * Video Filter2 stuff - *****************************************************************************/ -static int VideoFilter2Callback(vlc_object_t *object, char const *cmd, - vlc_value_t oldval, vlc_value_t newval, - void *data) -{ - vout_thread_t *vout = (vout_thread_t *)object; - (void)cmd; (void)oldval; (void)data; - - vout_control_PushString(&vout->p->control, VOUT_CONTROL_CHANGE_FILTERS, - newval.psz_string); - return VLC_SUCCESS; -} - -/* */ -static void PrintVideoFormat(vout_thread_t *vout, - const char *description, - const video_format_t *fmt) -{ - msg_Dbg(vout, "%s sz %ix%i, of (%i,%i), vsz %ix%i, 4cc %4.4s, sar %i:%i, msk r0x%x g0x%x b0x%x", - description, - fmt->i_width, fmt->i_height, fmt->i_x_offset, fmt->i_y_offset, - fmt->i_visible_width, fmt->i_visible_height, - (char*)&fmt->i_chroma, - fmt->i_sar_num, fmt->i_sar_den, - fmt->i_rmask, fmt->i_gmask, fmt->i_bmask); -} -