X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fvideo_output%2Fvideo_output.c;h=915363c5fac5fdf1df5f78b33401e73a94332319;hb=12ade3e3bc975d5426ba4af155b7372c31093b31;hp=5b06f51b5add848718bfe0ededa329d66ec43d7a;hpb=5a6ee7961b6485a3b6b7d4713ab4e050f572d0be;p=vlc diff --git a/src/video_output/video_output.c b/src/video_output/video_output.c index 5b06f51b5a..915363c5fa 100644 --- a/src/video_output/video_output.c +++ b/src/video_output/video_output.c @@ -68,7 +68,7 @@ static void VoutDestructor(vlc_object_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)) /* */ static int VoutValidateFormat(video_format_t *dst, @@ -92,56 +92,11 @@ static int VoutValidateFormat(video_format_t *dst, return VLC_SUCCESS; } -/***************************************************************************** - * 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 *object, vout_thread_t *vout, - const video_format_t *fmt) -{ - if (!fmt) { - if (vout) - vout_CloseAndRelease(vout); - return NULL; - } - - /* If a vout is provided, try reusing it */ - if (vout) { - spu_Attach(vout->p->p_spu, VLC_OBJECT(vout), false); - vlc_object_detach(vout); - - vout_control_cmd_t cmd; - vout_control_cmd_Init(&cmd, VOUT_CONTROL_REINIT); - cmd.u.reinit.fmt = fmt; - - vout_control_Push(&vout->p->control, &cmd); - vout_control_WaitEmpty(&vout->p->control); - if (!vout->p->dead) { - vlc_object_attach(vout, object); - spu_Attach(vout->p->p_spu, VLC_OBJECT(vout), true); - - msg_Dbg(object, "reusing provided vout"); - return vout; - } - vout_CloseAndRelease(vout); - - msg_Warn(object, "cannot reuse provided vout"); - } - return vout_Create(object, fmt); -} - -/***************************************************************************** - * 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 *object, const video_format_t *fmt) +static vout_thread_t *VoutCreate(vlc_object_t *object, + const vout_configuration_t *cfg) { video_format_t original; - if (VoutValidateFormat(&original, fmt)) + if (VoutValidateFormat(&original, cfg->fmt)) return NULL; /* Allocate descriptor */ @@ -157,6 +112,7 @@ vout_thread_t *(vout_Create)(vlc_object_t *object, const video_format_t *fmt) vout->p = (vout_thread_sys_t*)&vout[1]; vout->p->original = original; + vout->p->dpb_size = cfg->dpb_size; vout_control_Init(&vout->p->control); vout_control_PushVoid(&vout->p->control, VOUT_CONTROL_INIT); @@ -169,7 +125,8 @@ vout_thread_t *(vout_Create)(vlc_object_t *object, const video_format_t *fmt) /* Initialize locks */ vlc_mutex_init(&vout->p->picture_lock); - vlc_mutex_init(&vout->p->vfilter_lock); + vlc_mutex_init(&vout->p->filter.lock); + vlc_mutex_init(&vout->p->spu_lock); /* Attach the new object now so we can use var inheritance below */ vlc_object_attach(vout, object); @@ -199,10 +156,10 @@ vout_thread_t *(vout_Create)(vlc_object_t *object, const video_format_t *fmt) /* */ if (vlc_clone(&vout->p->thread, Thread, vout, VLC_THREAD_PRIORITY_OUTPUT)) { + spu_Destroy(vout->p->p_spu); vlc_object_release(vout); return NULL; } - spu_Attach(vout->p->p_spu, VLC_OBJECT(vout), true); vout_control_WaitEmpty(&vout->p->control); @@ -212,27 +169,69 @@ vout_thread_t *(vout_Create)(vlc_object_t *object, const video_format_t *fmt) return NULL; } + vout->p->input = cfg->input; + if (vout->p->input) + spu_Attach(vout->p->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. - *****************************************************************************/ +vout_thread_t *(vout_Request)(vlc_object_t *object, + const vout_configuration_t *cfg) +{ + 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->p_spu, vout->p->input, false); + vout->p->input = cfg->input; + if (vout->p->input) + spu_Attach(vout->p->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; + + vout_control_Push(&vout->p->control, &cmd); + vout_control_WaitEmpty(&vout->p->control); + } + + if (!vout->p->dead) { + msg_Dbg(object, "reusing provided vout"); + return vout; + } + vout_CloseAndRelease(vout); + + msg_Warn(object, "cannot reuse provided vout"); + } + return VoutCreate(object, cfg); +} + void vout_Close(vout_thread_t *vout) { assert(vout); - spu_Attach(vout->p->p_spu, VLC_OBJECT(vout), false); + if (vout->p->input) + spu_Attach(vout->p->p_spu, vout->p->input, false); vout_snapshot_End(&vout->p->snapshot); vout_control_PushVoid(&vout->p->control, VOUT_CONTROL_CLEAN); vlc_join(vout->p->thread, NULL); + + vlc_mutex_lock(&vout->p->spu_lock); + spu_Destroy(vout->p->p_spu); + vout->p->p_spu = NULL; + vlc_mutex_unlock(&vout->p->spu_lock); } /* */ @@ -245,12 +244,10 @@ static void VoutDestructor(vlc_object_t *object) free(vout->p->splitter_name); - /* */ - spu_Destroy(vout->p->p_spu); - /* Destroy the locks */ + vlc_mutex_destroy(&vout->p->spu_lock); vlc_mutex_destroy(&vout->p->picture_lock); - vlc_mutex_destroy(&vout->p->vfilter_lock); + vlc_mutex_destroy(&vout->p->filter.lock); vout_control_Clean(&vout->p->control); /* */ @@ -348,15 +345,27 @@ void vout_DisplayTitle(vout_thread_t *vout, const char *title) void vout_PutSubpicture( vout_thread_t *vout, subpicture_t *subpic ) { - spu_DisplaySubpicture(vout->p->p_spu, 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 ) { - return spu_RegisterChannel(vout->p->p_spu); + int channel = SPU_DEFAULT_CHANNEL; + + vlc_mutex_lock(&vout->p->spu_lock); + if (vout->p->p_spu) + channel = spu_RegisterChannel(vout->p->p_spu); + vlc_mutex_unlock(&vout->p->spu_lock); + + return channel; } void vout_FlushSubpictureChannel( vout_thread_t *vout, int channel ) { - spu_ClearChannel(vout->p->p_spu, channel); + vout_control_PushInteger(&vout->p->control, VOUT_CONTROL_FLUSH_SUBPICTURE, + channel); } /* vout_Control* are usable by anyone at anytime */ @@ -426,214 +435,370 @@ 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); +} + +/* */ +static void VoutGetDisplayCfg(vout_thread_t *vout, vout_display_cfg_t *cfg, const char *title) +{ + /* 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"); + cfg->display.sar.num = 1; /* TODO monitor AR */ + cfg->display.sar.den = 1; + 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_window_Delete(vout->p->window.object); + vout->p->window.is_unused = true; + vout->p->window.object = NULL; + } + + 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; +} + +void vout_DeleteDisplayWindow(vout_thread_t *vout, vout_display_t *vd, + vout_window_t *window) +{ + 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); + } +} /* */ -static picture_t *VoutVideoFilterNewPicture(filter_t *filter) + +static picture_t *VoutVideoFilterInteractiveNewPicture(filter_t *filter) { vout_thread_t *vout = (vout_thread_t*)filter->p_owner; + return picture_pool_Get(vout->p->private_pool); } +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 VoutVideoFilterAllocationSetup(filter_t *filter, void *data) +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 = VoutVideoFilterNewPicture; + filter->pf_video_buffer_new = VoutVideoFilterInteractiveNewPicture; filter->pf_video_buffer_del = VoutVideoFilterDelPicture; filter->p_owner = data; /* vout */ return VLC_SUCCESS; } /* */ -static int ThreadDisplayPicture(vout_thread_t *vout, - bool now, mtime_t *deadline) +static int ThreadDisplayPreparePicture(vout_thread_t *vout, bool reuse, bool is_late_dropped) { - vout_display_t *vd = vout->p->display.vd; - int displayed_count = 0; int lost_count = 0; - 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); - } else { - redisplay = true; - } - } - 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; - } - /* */ - is_forced = true; - *deadline = date - vout_chrono_GetHigh(&vout->p->render); - } - if (*deadline > VOUT_MWAIT_TOLERANCE) - *deadline -= VOUT_MWAIT_TOLERANCE; + vlc_mutex_lock(&vout->p->filter.lock); - /* If we are too early and can wait, do it */ - if (date < *deadline && !now) - break; + 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); + if (is_late_dropped && decoded && !decoded->b_force) { + const mtime_t predicted = mdate() + 0; /* TODO improve */ const mtime_t late = predicted - decoded->date; - if (late > 0) { + if (late > VOUT_DISPLAY_LATE_THRESHOLD) { + msg_Warn(vout, "picture is too late to be displayed (missing %d ms)", (int)(late/1000)); + picture_Release(decoded); + lost_count++; + continue; + } else if (late > 0) { msg_Dbg(vout, "picture might be displayed late (missing %d ms)", (int)(late/1000)); - if (late > VOUT_DISPLAY_LATE_THRESHOLD) { - msg_Warn(vout, "rejected picture because of render time"); - /* TODO */ - picture_Release(decoded); - lost_count++; - break; - } } } - - 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; + vout->p->displayed.decoded = picture_Hold(decoded); + vout->p->displayed.timestamp = decoded->date; + vout->p->displayed.is_interlaced = !decoded->b_progressive; + vout->p->displayed.qtype = decoded->i_qtype; + + picture = filter_chain_VideoFilter(vout->p->filter.chain_static, decoded); + } + + vlc_mutex_unlock(&vout->p->filter.lock); + + vout_statistic_Update(&vout->p->statistic, 0, lost_count); + 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_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"); + + /* + * 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 - 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); + 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); - } else { - direct = render; - } - picture_Release(filtered); - filtered = NULL; } else { - direct = filtered; + direct = render; } + picture_Release(filtered); + filtered = NULL; + } else { + direct = filtered; + } - /* - * Take a snapshot if requested - */ - if (direct && do_snapshot) - vout_snapshot_Set(&vout->p->snapshot, &vd->source, direct); + if (!direct) + return VLC_EGENERIC; + + /* + * Take a snapshot if requested + */ + if (do_snapshot) + vout_snapshot_Set(&vout->p->snapshot, &vd->source, direct); - /* Render the direct buffer returned by vout_RenderPicture */ - if (direct) { - vout_RenderWrapper(vout, direct); + /* Render the direct buffer returned by vout_RenderPicture */ + vout_RenderWrapper(vout, direct); - vout_chrono_Stop(&vout->p->render); + 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 + { + 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(direct->date); + + /* Display the direct buffer returned by vout_RenderPicture */ + vout->p->displayed.date = mdate(); + + vout_DisplayWrapper(vout, direct); + + vout_statistic_Update(&vout->p->statistic, 1, 0); - /* Wait the real date (for rendering jitter) */ - if (!is_forced) - mwait(decoded->date); + return VLC_SUCCESS; +} - /* Display the direct buffer returned by vout_RenderPicture */ - vout->p->displayed.date = mdate(); - if (direct) - vout_DisplayWrapper(vout, direct); +static int ThreadDisplayPicture(vout_thread_t *vout, + bool now, mtime_t *deadline) +{ + bool is_late_dropped = vout->p->is_late_dropped && !vout->p->pause.is_on && !now; + bool first = !vout->p->displayed.current; + if (first && ThreadDisplayPreparePicture(vout, true, is_late_dropped)) /* FIXME not sure it is ok */ + return VLC_EGENERIC; + if (!vout->p->pause.is_on || now) { + while (!vout->p->displayed.next) { + if (ThreadDisplayPreparePicture(vout, false, is_late_dropped)) { + break; + } + } + } - displayed_count++; - break; + const mtime_t date = mdate(); + const mtime_t render_delay = vout_chrono_GetHigh(&vout->p->render) + VOUT_MWAIT_TOLERANCE; + + mtime_t date_next = VLC_TS_INVALID; + if (!vout->p->pause.is_on && vout->p->displayed.next) + date_next = vout->p->displayed.next->date - render_delay; + + /* 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. + */ + 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; + + bool drop = now; + if (date_next != VLC_TS_INVALID) + drop |= date_next + 0 <= date; + + bool refresh = false; + if (date_refresh > VLC_TS_INVALID) + refresh = date_refresh <= date; + + if (!first && !refresh && !drop) { + if (date_next != VLC_TS_INVALID && date_refresh != VLC_TS_INVALID) + *deadline = __MIN(date_next, date_refresh); + else if (date_next != VLC_TS_INVALID) + *deadline = date_next; + else if (date_refresh != VLC_TS_INVALID) + *deadline = date_refresh; + return VLC_EGENERIC; } - vout_statistic_Update(&vout->p->statistic, displayed_count, lost_count); - if (displayed_count <= 0) + if (drop) { + picture_Release(vout->p->displayed.current); + vout->p->displayed.current = vout->p->displayed.next; + vout->p->displayed.next = NULL; + } + if (!vout->p->displayed.current) return VLC_EGENERIC; - return VLC_SUCCESS; + + bool is_forced = now || (!drop && refresh) || vout->p->displayed.current->b_force; + return ThreadDisplayRenderPicture(vout, is_forced); } static void ThreadManage(vout_thread_t *vout, @@ -644,7 +809,10 @@ static void ThreadManage(vout_thread_t *vout, vlc_mutex_lock(&vout->p->picture_lock); *deadline = VLC_TS_INVALID; - ThreadDisplayPicture(vout, false, deadline); + for (;;) { + if (ThreadDisplayPicture(vout, false, deadline)) + break; + } const int picture_qtype = vout->p->displayed.qtype; const bool picture_interlaced = vout->p->displayed.is_interlaced; @@ -660,6 +828,17 @@ static void ThreadManage(vout_thread_t *vout, vout_ManageWrapper(vout); } +static void ThreadDisplaySubpicture(vout_thread_t *vout, + subpicture_t *subpicture) +{ + spu_DisplaySubpicture(vout->p->p_spu, subpicture); +} + +static void ThreadFlushSubpicture(vout_thread_t *vout, int channel) +{ + spu_ClearChannel(vout->p->p_spu, channel); +} + static void ThreadDisplayOsdTitle(vout_thread_t *vout, const char *string) { if (!vout->p->title.show) @@ -670,26 +849,100 @@ static void ThreadDisplayOsdTitle(vout_thread_t *vout, const char *string) string); } +static void ThreadFilterFlush(vout_thread_t *vout) +{ + 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; + + vlc_mutex_lock(&vout->p->filter.lock); + filter_chain_VideoFlush(vout->p->filter.chain_static); + filter_chain_VideoFlush(vout->p->filter.chain_interactive); + 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 char *filters) { + ThreadFilterFlush(vout); + + 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 { + vlc_array_append(&array_interactive, e); + } + } else { + if (cfg) + config_ChainDestroy(cfg); + free(name); + } + free(current); + current = next; + } + 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); + vlc_mutex_lock(&vout->p->filter.lock); - 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"); + filter_chain_Reset(vout->p->filter.chain_static, &fmt, &fmt); + filter_chain_Reset(vout->p->filter.chain_interactive, &fmt, &fmt); - vlc_mutex_unlock(&vout->p->vfilter_lock); + 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; + + 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); + } + free(e->name); + free(e); + } + vlc_array_clear(array); + } + + vlc_mutex_unlock(&vout->p->filter.lock); } static void ThreadChangeSubFilters(vout_thread_t *vout, const char *filters) { spu_ChangeFilters(vout->p->p_spu, filters); } +static void ThreadChangeSubMargin(vout_thread_t *vout, int margin) +{ + spu_ChangeMargin(vout->p->p_spu, margin); +} static void ThreadChangePause(vout_thread_t *vout, bool is_paused, mtime_t date) { @@ -705,8 +958,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->p_spu, duration); + + ThreadFilterFlush(vout); } else { vout->p->step.timestamp = VLC_TS_INVALID; vout->p->step.last = VLC_TS_INVALID; @@ -720,6 +974,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); /* FIXME too much */ + picture_t *last = vout->p->displayed.decoded; if (last) { if (( below && last->date <= date) || @@ -731,6 +987,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); } @@ -839,60 +1096,48 @@ 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; - } - } - ThreadExecuteCropWindow(vout, num, den, x, y, width, height); + ThreadExecuteCropWindow(vout, num, den, + 0, 0, + source->i_visible_width, + source->i_visible_height); } -static int ThreadInit(vout_thread_t *vout) +static int ThreadStart(vout_thread_t *vout, const vout_display_state_t *state) { - vout->p->dead = false; - vout->p->is_late_dropped = var_InheritBool(vout, "drop-late-frames"); 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->vfilter_chain = + vout->p->filter.chain_static = + filter_chain_New( vout, "video filter2", false, + VoutVideoFilterStaticAllocationSetup, NULL, vout); + vout->p->filter.chain_interactive = filter_chain_New( vout, "video filter2", false, - VoutVideoFilterAllocationSetup, NULL, vout); + VoutVideoFilterInteractiveAllocationSetup, NULL, vout); - if (vout_OpenWrapper(vout, vout->p->splitter_name)) + 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_chrono_Init(&vout->p->render, 5, 10000); /* Arbitrary initial time */ - + 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.decoded = NULL; vout->p->displayed.timestamp = VLC_TS_INVALID; vout->p->displayed.qtype = QTYPE_NONE; vout->p->displayed.is_interlaced = false; @@ -900,49 +1145,89 @@ static int ThreadInit(vout_thread_t *vout) vout->p->step.last = VLC_TS_INVALID; vout->p->step.timestamp = VLC_TS_INVALID; - vout->p->pause.is_on = false; - vout->p->pause.date = VLC_TS_INVALID; - video_format_Print(VLC_OBJECT(vout), "original format", &vout->p->original); return VLC_SUCCESS; } -static void ThreadClean(vout_thread_t *vout) +static void ThreadStop(vout_thread_t *vout, vout_display_state_t *state) { - /* Destroy the video filters2 */ - filter_chain_Delete(vout->p->vfilter_chain); - /* Destroy translation tables */ if (vout->p->display.vd) { if (vout->p->decoder_pool) { ThreadFlush(vout, true, INT64_MAX); vout_EndWrapper(vout); } - vout_CloseWrapper(vout); + vout_CloseWrapper(vout, state); } - /* Detach subpicture unit from vout */ - vlc_object_detach(vout->p->p_spu); + /* Destroy the video filters2 */ + filter_chain_Delete(vout->p->filter.chain_interactive); + filter_chain_Delete(vout->p->filter.chain_static); if (vout->p->decoder_fifo) picture_fifo_Delete(vout->p->decoder_fifo); assert(!vout->p->decoder_pool); - vout_chrono_Clean(&vout->p->render); +} +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) +{ + 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 video_format_t *fmt) + const vout_configuration_t *cfg) { video_format_t original; - if (VoutValidateFormat(&original, fmt)) + if (VoutValidateFormat(&original, cfg->fmt)) { + ThreadStop(vout, NULL); + ThreadClean(vout); return VLC_EGENERIC; - if (video_format_IsSimilar(&original, &vout->p->original)) - return VLC_SUCCESS; + } + 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)); - /* TODO */ - return VLC_EGENERIC; + 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; } /***************************************************************************** @@ -973,19 +1258,27 @@ static void *Thread(void *object) while (!vout_control_Pop(&vout->p->control, &cmd, deadline, 100000)) { switch(cmd.type) { case VOUT_CONTROL_INIT: - if (ThreadInit(vout)) { + ThreadInit(vout); + if (ThreadStart(vout, NULL)) { + ThreadStop(vout, NULL); ThreadClean(vout); return NULL; } break; case VOUT_CONTROL_CLEAN: + ThreadStop(vout, NULL); ThreadClean(vout); return NULL; case VOUT_CONTROL_REINIT: - if (ThreadReinit(vout, cmd.u.reinit.fmt)) { - ThreadClean(vout); + if (ThreadReinit(vout, cmd.u.cfg)) return NULL; - } + 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); @@ -996,6 +1289,9 @@ static void *Thread(void *object) 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;