[vlc-commits] [Git][videolan/vlc][master] 14 commits: picture_fifo: expose the lock
Jean-Baptiste Kempf (@jbk)
gitlab at videolan.org
Tue Dec 16 22:38:29 UTC 2025
Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC
Commits:
a254772e by Thomas Guillem at 2025-12-16T23:05:21+01:00
picture_fifo: expose the lock
To allow more controls and to expose the count in the next commit.
- - - - -
bf175e13 by Thomas Guillem at 2025-12-16T23:05:21+01:00
picture_fifo: add picture_fifo_GetCount()
- - - - -
03260c91 by Thomas Guillem at 2025-12-16T23:05:21+01:00
vout: rework vout_NextPicture() args and return value
It is now possible to request more than one picture to be displayed next.
This function now returns the required number of pictures to be sent via
vout_PutPicture() to fulfil the request.
- - - - -
62b65354 by Thomas Guillem at 2025-12-16T23:05:21+01:00
decoder: move next-frame check
Handle it after checking the vout
- - - - -
ee452dee by Thomas Guillem at 2025-12-16T23:05:21+01:00
decoder: accurately count frames_countdown
This will allow the frames_countdown variable to match with the actual
number of frame-next requests.
Indeed, this variable was incremented, regardless of the number of
pictures ready to be displayed by the vout (just decoded).
This was resulting in a buffering gap: If there were 20 frames on the
paused vout, the same amount of pictures was kept ahead for all future
frame-next requests because of the frames_countdown increase. Because of
that, it was also impossible to know when we reached EOF on next-frame.
Furthermore, this commit is needed for the new and future next-frame
buffering workaround as it allows finer buffering (ask buffering only
when needed, and not 20 frames in advance).
Refs #29487
- - - - -
95d6210a by Thomas Guillem at 2025-12-16T23:05:21+01:00
decoder: save when the video is drained
- - - - -
26d3ca6e by Thomas Guillem at 2025-12-16T23:05:21+01:00
decoder: notify when next-frame reaches EOF
- - - - -
05d0a927 by Thomas Guillem at 2025-12-16T23:05:21+01:00
decoder: fix possible prev-frame miss near EOF
If the seek-step was not enough, it was not increased because there was
no next frames to trigger it.
- - - - -
00d1b391 by Thomas Guillem at 2025-12-16T23:05:21+01:00
es_out: use the buffering flag when doing next-frame
This will avoid RESET_PCR or discontinuity handling when the rebuffering
is forced after few next-frame requests.
- - - - -
9dd9d811 by Thomas Guillem at 2025-12-16T23:05:21+01:00
input: rework next-frame buffering workaround
ea93b2c847880581b55c433b3aa0f315c6449cf3 is working by luck.
vlc_input_decoder_IsEmpty() should not be used to know if the input need
more buffering, it might return false when the fifo is empty and when
the vout has few pictures. In the unlikely case where the vout has fewer
pictures than the number of frame-next request (only when sending
requests in a burst via API, and not by hotkeys), it will keep returning
false, not triggering the buffering when it needed.
Furthermore, when paused, if ES_OUT_PRIV_GET_BUFFERING return false, the
input_thread_t will wait undefinitely (only wake by a new request).
This commit introduces a workaround, that is not cleaner than the
previous one, reinventing buffering when we decided to postpone it, but
it is only done for the next-frame usage and works reliably (cf.
unit-tests).
Really fixes #28145
Fixes #29487
- - - - -
23e0cea2 by Thomas Guillem at 2025-12-16T23:05:21+01:00
mock: fix possible miss of last video/input blocks
"mock://length=20000000;video_track_count=1video_frame_rate=1"
Was not sending the last frame (fps == 19).
- - - - -
1154701c by Thomas Guillem at 2025-12-16T23:05:21+01:00
test: player: next-prev: reduce test duration
Don't do extras check on 1fps video. Cf. comment.
And do failing scenarios at 30fps instead of 1fps.
cf. !5176
- - - - -
4b6bff1c by Thomas Guillem at 2025-12-16T23:05:21+01:00
test: player: next_prev: check next-frame until EOF
- - - - -
afee5055 by Thomas Guillem at 2025-12-16T23:05:21+01:00
test: player: next_prev: check small burst
Current scenarios are waiting after the first next/prev requests in order to
get deterministic video timecodes. So add a scenario that won't wait and
send few requests (6) in a burst.
- - - - -
16 changed files:
- include/vlc_picture_fifo.h
- modules/demux/mock.c
- modules/stream_out/transcode/encoder/encoder.c
- modules/stream_out/transcode/encoder/video.c
- src/input/decoder.c
- src/input/decoder.h
- src/input/decoder_prevframe.c
- src/input/es_out.c
- src/input/input.c
- src/input/input_internal.h
- src/libvlccore.sym
- src/misc/image.c
- src/misc/picture_fifo.c
- src/video_output/video_output.c
- src/video_output/vout_internal.h
- test/src/player/next_prev.c
Changes:
=====================================
include/vlc_picture_fifo.h
=====================================
@@ -49,6 +49,19 @@ VLC_API picture_fifo_t * picture_fifo_New( void ) VLC_USED;
*/
VLC_API void picture_fifo_Delete( picture_fifo_t * );
+/**
+ * Lock the picture_fifo
+ *
+ * All picture_fifo_t functions, except picture_fifo_Delete(), need to be
+ * called with the lock held.
+ */
+VLC_API void picture_fifo_Lock( picture_fifo_t * );
+
+/**
+ * Unlock the picture_fifo
+ */
+VLC_API void picture_fifo_Unlock( picture_fifo_t * );
+
/**
* It retrieves a picture_t from the fifo.
*
@@ -61,6 +74,11 @@ VLC_API picture_t * picture_fifo_Pop( picture_fifo_t * ) VLC_USED;
*/
VLC_API bool picture_fifo_IsEmpty( picture_fifo_t * );
+/**
+ * It returns the number of pictures queued
+ */
+VLC_API size_t picture_fifo_GetCount( picture_fifo_t * );
+
/**
* It saves a picture_t into the fifo.
*/
=====================================
modules/demux/mock.c
=====================================
@@ -1350,7 +1350,7 @@ Demux(demux_t *demux)
{
ret = DemuxVideo(demux,
__MIN(step_length + sys->clock - VLC_TICK_0 - sys->pts_offset, sys->length));
- if (date_Get(&sys->video_date) - VLC_TICK_0 - sys->pts_offset + video_step_length < sys->length)
+ if (date_Get(&sys->video_date) - VLC_TICK_0 - sys->pts_offset + video_step_length <= sys->length)
eof = false;
}
@@ -1358,7 +1358,7 @@ Demux(demux_t *demux)
if (step_length == 0)
{
sys->clock += sys->input_sample_length;
- if (sys->clock - VLC_TICK_0 - sys->pts_offset + sys->input_sample_length < sys->length)
+ if (sys->clock - VLC_TICK_0 - sys->pts_offset + sys->input_sample_length <= sys->length)
eof = false;
}
=====================================
modules/stream_out/transcode/encoder/encoder.c
=====================================
@@ -51,6 +51,7 @@ void transcode_encoder_delete( transcode_encoder_t *p_enc )
{
transcode_encoder_video_stop( p_enc );
block_ChainRelease( p_enc->p_buffers );
+
picture_fifo_Delete( p_enc->pp_pics );
}
=====================================
modules/stream_out/transcode/encoder/video.c
=====================================
@@ -292,6 +292,14 @@ void transcode_encoder_video_configure( vlc_object_t *p_obj,
(const char *)&p_enc_in->i_chroma);
}
+static picture_t *picture_fifo_LockPop( picture_fifo_t *fifo )
+{
+ picture_fifo_Lock( fifo );
+ picture_t *pic = picture_fifo_Pop( fifo );
+ picture_fifo_Unlock( fifo );
+ return pic;
+}
+
static void* EncoderThread( void *obj )
{
vlc_thread_set_name("vlc-encoder");
@@ -306,7 +314,7 @@ static void* EncoderThread( void *obj )
for( ;; )
{
while( !p_enc->b_abort &&
- (p_pic = picture_fifo_Pop( p_enc->pp_pics )) == NULL )
+ (p_pic = picture_fifo_LockPop( p_enc->pp_pics )) == NULL )
vlc_cond_wait( &p_enc->cond, &p_enc->lock_out );
vlc_sem_post( &p_enc->picture_pool_has_room );
@@ -326,7 +334,7 @@ static void* EncoderThread( void *obj )
}
/*Encode what we have in the buffer on closing*/
- while( (p_pic = picture_fifo_Pop( p_enc->pp_pics )) != NULL )
+ while( (p_pic = picture_fifo_LockPop( p_enc->pp_pics )) != NULL )
{
vlc_sem_post( &p_enc->picture_pool_has_room );
p_block = vlc_encoder_EncodeVideo( p_enc->p_encoder, p_pic );
@@ -435,7 +443,9 @@ block_t * transcode_encoder_video_encode( transcode_encoder_t *p_enc, picture_t
vlc_sem_wait( &p_enc->picture_pool_has_room );
vlc_mutex_lock( &p_enc->lock_out );
picture_Hold( p_pic );
+ picture_fifo_Lock( p_enc->pp_pics );
picture_fifo_Push( p_enc->pp_pics, p_pic );
+ picture_fifo_Unlock( p_enc->pp_pics );
vlc_cond_signal( &p_enc->cond );
vlc_mutex_unlock( &p_enc->lock_out );
return NULL;
=====================================
src/input/decoder.c
=====================================
@@ -120,6 +120,7 @@ struct decoder_video
vout_thread_t *vout;
enum vlc_vout_order vout_order;
bool started;
+ bool drained;
/* pool to use when the decoder doesn't use its own */
struct picture_pool_t *out_pool;
@@ -323,6 +324,29 @@ static void Decoder_SeekPreviousFrame(vlc_input_decoder_t *owner, int steps,
}
}
+static void Decoder_PausedForNextFrame(vlc_input_decoder_t *owner)
+{
+ vlc_fifo_Assert(owner->p_fifo);
+ assert(owner->cat == VIDEO_ES);
+ assert(owner->output_paused);
+
+ if (owner->video.vout == NULL)
+ return;
+
+ if (likely(owner->frames_countdown <= 0))
+ return;
+
+ /* Handle all next-frame requests that were sent while the video was
+ * pausing */
+ int next_request_count = owner->frames_countdown;
+ owner->frames_countdown = vout_NextPicture(owner->video.vout, next_request_count);
+
+ assert(next_request_count >= owner->frames_countdown);
+ /* Notify for pictures that are processes by the vout */
+ for (int i = 0; i < next_request_count - owner->frames_countdown; ++i)
+ decoder_Notify(owner, frame_next_status, 0);
+}
+
static void Decoder_DisplayPreviousFrame(vlc_input_decoder_t *owner, picture_t *pic)
{
vlc_fifo_Assert(owner->p_fifo);
@@ -334,7 +358,7 @@ static void Decoder_DisplayPreviousFrame(vlc_input_decoder_t *owner, picture_t *
}
vout_PutPicture(owner->video.vout, pic);
- vout_NextPicture(owner->video.vout);
+ vout_NextPicture(owner->video.vout, 1);
decoder_Notify(owner, frame_previous_status, 0);
}
@@ -1491,12 +1515,8 @@ static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_pi
picture_Release(p_picture);
return ret;
}
-
}
- if( unlikely(p_owner->paused) && likely(p_owner->frames_countdown > 0) )
- p_owner->frames_countdown--;
-
/* */
if( p_vout == NULL )
{
@@ -1504,6 +1524,14 @@ static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_pi
return VLC_EGENERIC;
}
+ if (unlikely(p_owner->output_paused && p_owner->frames_countdown > 0))
+ {
+ p_owner->frames_countdown--;
+ vout_PutPicture(p_vout, p_picture);
+ decoder_Notify(p_owner, frame_next_status, 0);
+ return VLC_SUCCESS;
+ }
+
if( p_picture->b_still )
{
/* Ensure no earlier higher pts breaks still state */
@@ -1895,6 +1923,33 @@ static void DecoderThread_Flush( vlc_input_decoder_t *p_owner )
p_owner->error = false;
}
+static void Decoder_VideoDrained(vlc_input_decoder_t *owner)
+{
+ owner->video.drained = true;
+ if (owner->frames_countdown > 0)
+ {
+ if (unlikely(vout_IsEmpty(owner->video.vout)))
+ {
+ /* Unlikely case where all pictures are already sent to the vout
+ * next queue while draining. This could happen with a burst of
+ * next-frame request near EOF. */
+ owner->frames_countdown = 0;
+ decoder_Notify(owner, frame_next_status, -EAGAIN);
+ }
+ }
+ else if(owner->frames_countdown == -1)
+ {
+ /* Also check if we need to increase seek steps near EOF */
+ int seek_steps;
+ picture_t *nullpic =
+ decoder_prevframe_AddPic(&owner->video.pf, NULL,
+ &owner->video.pf_pts, &seek_steps);
+ assert(nullpic == NULL); (void) nullpic;
+ if (seek_steps != DEC_PF_SEEK_STEPS_NONE)
+ Decoder_SeekPreviousFrame(owner, seek_steps, true);
+ }
+}
+
/**
* The decoding main loop
*
@@ -1945,6 +2000,9 @@ static void *DecoderThread( void *p_data )
Decoder_ChangeOutputPause( p_owner, p_owner->paused, p_owner->pause_date );
decoder_Notify(p_owner, on_output_paused, p_owner->paused,
p_owner->pause_date);
+ if (unlikely(p_owner->paused && p_owner->cat == VIDEO_ES
+ && p_owner->frames_countdown != 0))
+ Decoder_PausedForNextFrame(p_owner);
continue;
}
@@ -1978,6 +2036,13 @@ static void *DecoderThread( void *p_data )
{ /* Wait for a block to decode (or a request to drain) */
p_owner->b_idle = true;
vlc_cond_signal( &p_owner->wait_acknowledge );
+
+ if (p_owner->frames_countdown > 0)
+ {
+ /* next-frames are requested but the FIFO is empty, ask for
+ * more buffering */
+ decoder_Notify( p_owner, frame_next_need_data, true );
+ }
vlc_fifo_Wait( p_owner->p_fifo );
p_owner->b_idle = false;
continue;
@@ -1995,11 +2060,22 @@ static void *DecoderThread( void *p_data )
{
p_owner->b_draining = false;
- if( p_owner->cat == AUDIO_ES
- && p_owner->audio.stream != NULL )
- { /* Draining: the decoder is drained and all decoded buffers are
- * queued to the output at this point. Now drain the output. */
- vlc_aout_stream_Drain( p_owner->audio.stream );
+ switch (p_owner->cat)
+ {
+ case AUDIO_ES:
+ if( p_owner->audio.stream != NULL )
+ {
+ /* Draining: the decoder is drained and all decoded
+ * buffers are queued to the output at this point.
+ * Now drain the output. */
+ vlc_aout_stream_Drain( p_owner->audio.stream );
+ }
+ break;
+ case VIDEO_ES:
+ Decoder_VideoDrained(p_owner);
+ break;
+ default:
+ break;
}
}
@@ -2146,6 +2222,7 @@ CreateDecoder( vlc_object_t *p_parent, const struct vlc_input_decoder_cfg *cfg )
case VIDEO_ES:
p_owner->video.vout = NULL;
p_owner->video.started = false;
+ p_owner->video.drained = false;
vlc_mutex_init( &p_owner->video.mouse_lock );
p_owner->video.mouse_event = NULL;
p_owner->video.mouse_opaque = NULL;
@@ -2575,6 +2652,9 @@ void vlc_input_decoder_DecodeWithStatus(vlc_input_decoder_t *p_owner, vlc_frame_
vlc_fifo_WaitCond( p_owner->p_fifo, &p_owner->wait_fifo );
}
+ if (vlc_fifo_IsEmpty(p_owner->p_fifo) && p_owner->frames_countdown > 0)
+ decoder_Notify(p_owner, frame_next_need_data, false);
+
vlc_fifo_QueueUnlocked( p_owner->p_fifo, frame );
if (status != NULL)
GetStatusLocked(p_owner, status);
@@ -2692,6 +2772,7 @@ void vlc_input_decoder_Flush( vlc_input_decoder_t *p_owner )
}
else if( cat == VIDEO_ES )
{
+ p_owner->video.drained = false;
if( p_owner->video.vout && p_owner->video.started
&& p_owner->frames_countdown != -1 )
{
@@ -2889,13 +2970,32 @@ void vlc_input_decoder_FrameNext( vlc_input_decoder_t *p_owner )
return;
}
+ if( p_owner->video.drained && vout_IsEmpty( p_owner->video.vout ) )
+ {
+ p_owner->frames_countdown = 0;
+ decoder_Notify( p_owner, frame_next_status, -EAGAIN );
+ vlc_fifo_Unlock( p_owner->p_fifo );
+ return;
+ }
+
StopFrameNextLocked( p_owner );
- p_owner->frames_countdown++;
- vlc_fifo_Signal( p_owner->p_fifo );
- vout_NextPicture( p_owner->video.vout );
- /* TODO: it should be notified from the vout */
- decoder_Notify( p_owner, frame_next_status, 0 );
+ if (!p_owner->output_paused)
+ {
+ /* Request will be handled when paused, from
+ * Decoder_PausedForNextFrame() */
+ p_owner->frames_countdown++;
+ vlc_fifo_Unlock( p_owner->p_fifo );
+ return;
+ }
+
+ size_t needed_count = vout_NextPicture(p_owner->video.vout, 1);
+ assert(p_owner->frames_countdown >= 0);
+ if (needed_count > (unsigned) p_owner->frames_countdown)
+ p_owner->frames_countdown++;
+ else
+ decoder_Notify(p_owner, frame_next_status, 0);
+
vlc_fifo_Unlock( p_owner->p_fifo );
}
=====================================
src/input/decoder.h
=====================================
@@ -54,6 +54,8 @@ struct vlc_input_decoder_callbacks {
unsigned lost, unsigned played, void *userdata);
void (*frame_next_status)(vlc_input_decoder_t *decoder, int status,
void *userdata);
+ void (*frame_next_need_data)(vlc_input_decoder_t *decoder, bool need_data,
+ void *userdata);
void (*frame_previous_status)(vlc_input_decoder_t *decoder, int status,
void *userdata);
void (*frame_previous_seek)(vlc_input_decoder_t *decoder, vlc_tick_t pts,
=====================================
src/input/decoder_prevframe.c
=====================================
@@ -76,11 +76,12 @@ decoder_prevframe_AddPic(struct decoder_prevframe *pf, picture_t *pic,
if (pf->flushing)
{
- picture_Release(pic);
+ if (pic != NULL)
+ picture_Release(pic);
return NULL;
}
- if (pts <= pic->date && pf->pic != NULL)
+ if (pic != NULL && pts <= pic->date && pf->pic != NULL)
{
/* Reached the previous frame */
picture_t *resume_pic = pic;
@@ -105,7 +106,7 @@ decoder_prevframe_AddPic(struct decoder_prevframe *pf, picture_t *pic,
return pic;
}
- if (pic->date >= pts)
+ if (pic == NULL || pic->date >= pts)
{
if (pf->pic == NULL && !pf->failed && pf->req_count > 0)
{
@@ -116,7 +117,8 @@ decoder_prevframe_AddPic(struct decoder_prevframe *pf, picture_t *pic,
*seek_steps = pf->seek_steps;
}
- picture_Release(pic);
+ if (pic != NULL)
+ picture_Release(pic);
return NULL;
}
=====================================
src/input/es_out.c
=====================================
@@ -526,6 +526,24 @@ decoder_frame_next_status(vlc_input_decoder_t *decoder, int status,
input_SendEvent(p_sys->p_input, &event);
}
+static void
+decoder_frame_next_need_data(vlc_input_decoder_t *decoder, bool need_data,
+ void *userdata)
+{
+ (void) decoder;
+
+ es_out_id_t *id = userdata;
+ struct vlc_input_es_out *out = id->out;
+ es_out_sys_t *p_sys = container_of(out, es_out_sys_t, out);
+
+ if (!p_sys->p_input)
+ return;
+
+ vlc_value_t val = { .b_bool = need_data };
+ input_ControlPushHelper(p_sys->p_input, INPUT_CONTROL_NEED_DATA_FRAME_NEXT,
+ &val);
+}
+
static void
decoder_frame_previous_status(vlc_input_decoder_t *decoder, int status,
void *userdata)
@@ -598,6 +616,7 @@ static const struct vlc_input_decoder_callbacks decoder_cbs = {
.on_new_video_stats = decoder_on_new_video_stats,
.on_new_audio_stats = decoder_on_new_audio_stats,
.frame_next_status = decoder_frame_next_status,
+ .frame_next_need_data = decoder_frame_next_need_data,
.frame_previous_status = decoder_frame_previous_status,
.frame_previous_seek = decoder_frame_previous_seek,
.get_attachments = decoder_get_attachments,
@@ -3570,9 +3589,10 @@ static int EsOutVaControlLocked(es_out_sys_t *p_sys, input_source_t *source,
/* TODO do not use vlc_tick_now() but proper stream acquisition date */
const bool b_low_delay = priv->b_low_delay;
bool b_extra_buffering_allowed = !b_low_delay && EsOutIsExtraBufferingAllowed(p_sys);
+ bool buffering = p_sys->b_buffering || p_sys->p_next_frame_es != NULL;
vlc_tick_t i_late = input_clock_Update(
p_pgrm->p_input_clock,
- input_CanPaceControl(p_sys->p_input), p_sys->b_buffering,
+ input_CanPaceControl(p_sys->p_input), buffering,
b_extra_buffering_allowed,
i_pcr, vlc_tick_now() );
@@ -4018,15 +4038,6 @@ static int EsOutVaPrivControlLocked(es_out_sys_t *p_sys, input_source_t *source,
bool *pb = va_arg( args, bool* );
if( p_sys->b_buffering )
*pb = true;
- else if( p_sys->p_next_frame_es != NULL )
- {
- /* The input thread will continue to call demux() if this control
- * returns true. In case of next-frame, ask the input thread to
- * continue to demux() until the vout has a picture to display. */
- assert( p_sys->b_paused );
- *pb = p_sys->p_next_frame_es->p_dec != NULL
- && vlc_input_decoder_IsEmpty( p_sys->p_next_frame_es->p_dec );
- }
else
*pb = false;
return VLC_SUCCESS;
=====================================
src/input/input.c
=====================================
@@ -283,6 +283,7 @@ input_thread_t * input_Create( vlc_object_t *p_parent, input_item_t *p_item,
vlc_renderer_item_hold( cfg->renderer ) : NULL;
priv->prev_frame.enabled = priv->prev_frame.end = false;
priv->prev_frame.last_pts = VLC_TICK_INVALID;
+ priv->next_frame_need_data = false;
priv->viewpoint_changed = false;
/* Fetch the viewpoint from the mediaplayer or the playlist if any */
@@ -660,8 +661,12 @@ static void MainLoop( input_thread_t *p_input, bool b_interactive )
* is paused -> this may cause problem with some of them
* The same problem can be seen when seeking while paused */
if( b_paused )
+ {
b_paused = !es_out_GetBuffering( input_priv(p_input)->p_es_out )
|| input_priv(p_input)->master->b_eof;
+ if( b_paused && input_priv(p_input)->next_frame_need_data )
+ b_paused = false;
+ }
if( !b_paused )
{
@@ -751,8 +756,12 @@ static void MainLoop( input_thread_t *p_input, bool b_interactive )
}
/* Update the wakeup time */
- if( i_wakeup != 0 )
+ if( input_priv(p_input)->next_frame_need_data )
+ i_wakeup = 0;
+ else if( i_wakeup != 0 )
+ {
i_wakeup = es_out_GetWakeup( input_priv(p_input)->p_es_out );
+ }
}
}
}
@@ -2061,6 +2070,7 @@ static bool Control( input_thread_t *p_input,
/* Reset the decoders states and clock sync (before calling the demuxer */
es_out_Control(&priv->p_es_out->out, ES_OUT_RESET_PCR);
ResetFramePrevious( p_input );
+ priv->next_frame_need_data = false;
int i_ret = ControlSetPosition( p_input, param.pos.f_val,
param.pos.b_fast_seek );
@@ -2084,6 +2094,7 @@ static bool Control( input_thread_t *p_input,
/* Reset the decoders states and clock sync (before calling the demuxer */
es_out_Control(&priv->p_es_out->out, ES_OUT_RESET_PCR);
ResetFramePrevious( p_input );
+ priv->next_frame_need_data = false;
int i_ret = ControlSetTime( p_input, param.time.i_val,
param.time.b_fast_seek );
@@ -2103,6 +2114,7 @@ static bool Control( input_thread_t *p_input,
if( priv->i_state == PAUSE_S )
{
ResetFramePrevious( p_input );
+ priv->next_frame_need_data = false;
ControlUnpause( p_input, i_control_date );
b_force_update = true;
}
@@ -2306,6 +2318,7 @@ static bool Control( input_thread_t *p_input,
es_out_Control(&priv->p_es_out->out, ES_OUT_RESET_PCR);
ResetFramePrevious( p_input );
+ priv->next_frame_need_data = false;
demux_Control(priv->master->p_demux,
DEMUX_SET_TITLE, i_title);
break;
@@ -2349,6 +2362,7 @@ static bool Control( input_thread_t *p_input,
es_out_Control(&priv->p_es_out->out, ES_OUT_RESET_PCR);
ResetFramePrevious( p_input );
+ priv->next_frame_need_data = false;
demux_Control( priv->master->p_demux,
DEMUX_SET_SEEKPOINT, i_seekpoint );
input_SendEventSeekpoint( p_input, i_title, i_seekpoint );
@@ -2484,6 +2498,9 @@ static bool Control( input_thread_t *p_input,
}
b_force_update = true;
break;
+ case INPUT_CONTROL_NEED_DATA_FRAME_NEXT:
+ priv->next_frame_need_data = param.val.b_bool;
+ break;
case INPUT_CONTROL_SEEK_FRAME_PREVIOUS:
SeekFramePrevious(p_input, param.frame_previous_seek.pts,
param.frame_previous_seek.frame_rate,
=====================================
src/input/input_internal.h
=====================================
@@ -565,6 +565,8 @@ typedef struct input_thread_private_t
bool enabled;
bool end;
} prev_frame;
+
+ bool next_frame_need_data;
} input_thread_private_t;
static inline input_thread_private_t *input_priv(input_thread_t *input)
@@ -627,6 +629,7 @@ enum input_control_e
INPUT_CONTROL_SET_FRAME_NEXT,
INPUT_CONTROL_SET_FRAME_PREVIOUS,
+ INPUT_CONTROL_NEED_DATA_FRAME_NEXT,
INPUT_CONTROL_SEEK_FRAME_PREVIOUS,
INPUT_CONTROL_SET_RENDERER,
=====================================
src/libvlccore.sym
=====================================
@@ -328,10 +328,13 @@ picture_Destroy
picture_CopyProperties
picture_Copy
picture_Export
+picture_fifo_Lock
+picture_fifo_Unlock
picture_fifo_Delete
picture_fifo_Flush
picture_fifo_New
picture_fifo_IsEmpty
+picture_fifo_GetCount
picture_fifo_Pop
picture_fifo_Push
picture_GetAncillary
=====================================
src/misc/image.c
=====================================
@@ -144,7 +144,9 @@ void image_HandlerDelete( image_handler_t *p_image )
static void ImageQueueVideo( decoder_t *p_dec, picture_t *p_pic )
{
struct decoder_owner *p_owner = dec_get_owner( p_dec );
+ picture_fifo_Lock( p_owner->p_image->outfifo );
picture_fifo_Push( p_owner->p_image->outfifo, p_pic );
+ picture_fifo_Unlock( p_owner->p_image->outfifo );
}
static picture_t *ImageRead( image_handler_t *p_image, block_t *p_block,
@@ -196,6 +198,7 @@ static picture_t *ImageRead( image_handler_t *p_image, block_t *p_block,
/* Drain */
p_image->p_dec->pf_decode( p_image->p_dec, NULL );
+ picture_fifo_Lock( p_image->outfifo );
p_pic = picture_fifo_Pop( p_image->outfifo );
unsigned lostcount = 0;
@@ -205,6 +208,8 @@ static picture_t *ImageRead( image_handler_t *p_image, block_t *p_block,
picture_Release( lostpic );
lostcount++;
}
+ picture_fifo_Unlock( p_image->outfifo );
+
if( lostcount > 0 )
msg_Warn( p_image->p_parent, "Image decoder output more than one "
"picture (%u)", lostcount );
=====================================
src/misc/picture_fifo.c
=====================================
@@ -40,20 +40,19 @@
struct picture_fifo_t {
vlc_mutex_t lock;
vlc_picture_chain_t pics;
+ size_t count;
};
static void PictureFifoReset(picture_fifo_t *fifo)
{
vlc_picture_chain_Init( &fifo->pics );
+ fifo->count = 0;
}
static void PictureFifoPush(picture_fifo_t *fifo, picture_t *picture)
{
assert(!picture_HasChainedPics(picture));
vlc_picture_chain_Append( &fifo->pics, picture );
-}
-static picture_t *PictureFifoPop(picture_fifo_t *fifo)
-{
- return vlc_picture_chain_PopFront( &fifo->pics );
+ fifo->count++;
}
picture_fifo_t *picture_fifo_New(void)
@@ -69,35 +68,37 @@ picture_fifo_t *picture_fifo_New(void)
void picture_fifo_Push(picture_fifo_t *fifo, picture_t *picture)
{
- vlc_mutex_lock(&fifo->lock);
+ vlc_mutex_assert(&fifo->lock);
PictureFifoPush(fifo, picture);
- vlc_mutex_unlock(&fifo->lock);
}
picture_t *picture_fifo_Pop(picture_fifo_t *fifo)
{
- vlc_mutex_lock(&fifo->lock);
- picture_t *picture = PictureFifoPop(fifo);
- vlc_mutex_unlock(&fifo->lock);
-
- return picture;
+ vlc_mutex_assert(&fifo->lock);
+ picture_t *pic = vlc_picture_chain_PopFront( &fifo->pics );
+ if (pic != NULL)
+ fifo->count--;
+ return pic;
}
bool picture_fifo_IsEmpty(picture_fifo_t *fifo)
{
- vlc_mutex_lock(&fifo->lock);
- bool empty = vlc_picture_chain_IsEmpty( &fifo->pics );
- vlc_mutex_unlock(&fifo->lock);
-
- return empty;
+ vlc_mutex_assert(&fifo->lock);
+ return vlc_picture_chain_IsEmpty( &fifo->pics );
+}
+VLC_API size_t picture_fifo_GetCount(picture_fifo_t *fifo)
+{
+ vlc_mutex_assert(&fifo->lock);
+ return fifo->count;
}
void picture_fifo_Flush(picture_fifo_t *fifo, vlc_tick_t date, bool flush_before)
{
+ vlc_mutex_assert(&fifo->lock);
picture_t *picture;
vlc_picture_chain_t flush_chain;
vlc_picture_chain_Init(&flush_chain);
+ fifo->count = 0;
- vlc_mutex_lock(&fifo->lock);
if (date == VLC_TICK_INVALID)
vlc_picture_chain_GetAndClear(&fifo->pics, &flush_chain);
else {
@@ -114,13 +115,24 @@ void picture_fifo_Flush(picture_fifo_t *fifo, vlc_tick_t date, bool flush_before
PictureFifoPush(fifo, picture);
}
}
- vlc_mutex_unlock(&fifo->lock);
while ((picture = vlc_picture_chain_PopFront(&flush_chain)) != NULL)
picture_Release(picture);
}
void picture_fifo_Delete(picture_fifo_t *fifo)
{
+ vlc_mutex_lock(&fifo->lock);
picture_fifo_Flush(fifo, VLC_TICK_INVALID, true);
+ vlc_mutex_unlock(&fifo->lock);
free(fifo);
}
+
+void picture_fifo_Lock(picture_fifo_t *fifo)
+{
+ vlc_mutex_lock(&fifo->lock);
+}
+
+void picture_fifo_Unlock(picture_fifo_t *fifo)
+{
+ vlc_mutex_unlock(&fifo->lock);
+}
\ No newline at end of file
=====================================
src/video_output/video_output.c
=====================================
@@ -285,7 +285,10 @@ bool vout_IsEmpty(vout_thread_t *vout)
assert(!sys->dummy);
assert(sys->decoder_fifo);
- return picture_fifo_IsEmpty(sys->decoder_fifo);
+ picture_fifo_Lock(sys->decoder_fifo);
+ bool empty = picture_fifo_IsEmpty(sys->decoder_fifo);
+ picture_fifo_Unlock(sys->decoder_fifo);
+ return empty;
}
void vout_DisplayTitle(vout_thread_t *vout, const char *title)
@@ -403,7 +406,9 @@ void vout_PutPicture(vout_thread_t *vout, picture_t *picture)
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
assert( !picture_HasChainedPics( picture ) );
+ picture_fifo_Lock(sys->decoder_fifo);
picture_fifo_Push(sys->decoder_fifo, picture);
+ picture_fifo_Unlock(sys->decoder_fifo);
vout_control_Wake(&sys->control);
}
@@ -1069,7 +1074,9 @@ static picture_t *PreparePicture(vout_thread_sys_t *vout, bool reuse_decoded,
if (decoded == NULL)
break;
} else {
+ picture_fifo_Lock(sys->decoder_fifo);
decoded = picture_fifo_Pop(sys->decoder_fifo);
+ picture_fifo_Unlock(sys->decoder_fifo);
if (decoded == NULL)
break;
@@ -1608,7 +1615,9 @@ static bool UpdateCurrentPicture(vout_thread_sys_t *sys)
* when the clock is configured. */
if (sys->first_picture)
{
+ picture_fifo_Lock(sys->decoder_fifo);
bool has_next_pic = !picture_fifo_IsEmpty(sys->decoder_fifo);
+ picture_fifo_Unlock(sys->decoder_fifo);
if (!has_next_pic)
return false;
@@ -1742,7 +1751,9 @@ static void vout_FlushUnlocked(vout_thread_sys_t *vout, bool below,
}
}
+ picture_fifo_Lock(sys->decoder_fifo);
picture_fifo_Flush(sys->decoder_fifo, date, below);
+ picture_fifo_Unlock(sys->decoder_fifo);
vlc_queuedmutex_lock(&sys->display_lock);
if (sys->display != NULL)
@@ -1778,16 +1789,24 @@ vlc_tick_t vout_Flush(vout_thread_t *vout, vlc_tick_t date)
return displayed_pts;
}
-void vout_NextPicture(vout_thread_t *vout)
+size_t vout_NextPicture(vout_thread_t *vout, size_t request_frame_count)
{
vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);
assert(!sys->dummy);
vout_control_Hold(&sys->control);
- sys->frame_next_count++;
+ sys->frame_next_count += request_frame_count;
+
+ picture_fifo_Lock(sys->decoder_fifo);
+ size_t pics_count = picture_fifo_GetCount(sys->decoder_fifo);
+ size_t needed_count = sys->frame_next_count <= pics_count ? 0
+ : sys->frame_next_count - pics_count;
+ picture_fifo_Unlock(sys->decoder_fifo);
vout_control_ReleaseAndWake(&sys->control);
+
+ return needed_count;
}
void vout_ChangeDelay(vout_thread_t *vout, vlc_tick_t delay)
=====================================
src/video_output/vout_internal.h
=====================================
@@ -237,9 +237,13 @@ void vout_GetResetStatistic( vout_thread_t *p_vout, unsigned *pi_displayed,
unsigned *pi_lost, unsigned *pi_late );
/**
- * This function will force to display the next picture while paused
+ * This function will force to display next pictures while paused
+ *
+ * @param request_frame_count number of pictures to display next
+ * @return Number of picture needed to fulfill the request (to send via
+ * vout_PutPicture())
*/
-void vout_NextPicture( vout_thread_t *p_vout );
+size_t vout_NextPicture( vout_thread_t *p_vout, size_t request_frame_count );
/**
* This function will ask the display of the input title
=====================================
test/src/player/next_prev.c
=====================================
@@ -140,7 +140,6 @@ wait_next_tc(struct np_ctx *np_ctx)
}
}
-
static void
check_resumed_timer(struct np_ctx *np_ctx)
{
@@ -343,7 +342,158 @@ go_start(struct np_ctx *np_ctx, bool extra_checks)
}
static void
-test_prev(struct ctx *ctx, const struct media_params *params)
+go_eof(struct np_ctx *np_ctx)
+{
+ struct ctx *ctx = np_ctx->ctx;
+ struct timer_state *timer = &np_ctx->timers[0];
+ vlc_player_t *player = ctx->player;
+ struct vlc_player_timer_smpte_timecode *tc = &np_ctx->tc;
+ struct report_timer *r = NULL;
+
+ unsigned fps = get_fps(ctx);
+
+ increase_tc(np_ctx);
+
+ size_t current_frame_count = tc->seconds * fps + tc->frames;
+ size_t end_frame_count = ctx->params.length * fps / CLOCK_FREQ;
+ assert(end_frame_count > current_frame_count);
+ size_t frame_next_count_total = end_frame_count - current_frame_count;
+ unsigned burst = 100;
+
+ /* Send prev-frame requests in burst until we reach start of file */
+ while (frame_next_count_total > 0)
+ {
+ size_t frame_next_count = frame_next_count_total > burst ? burst
+ : frame_next_count_total;
+
+ frame_next_count_total -= frame_next_count;
+
+ /* Send burst */
+ for (size_t i = 0; i < frame_next_count; ++i)
+ vlc_player_NextVideoFrame(player);
+
+ /* Wait for all next-frame status */
+ wait_next_frame_status(np_ctx, frame_next_count, 0);
+
+ /* Check that all video timecodes are increasing */
+ player_lock_timer(player, timer);
+ for (size_t i = 0; i < frame_next_count; ++i)
+ {
+ r = timer_state_wait_next_report(timer);
+ assert(r->type == REPORT_TIMER_TC);
+ assert(r->tc.seconds == tc->seconds);
+ assert(r->tc.frames == tc->frames);
+
+ increase_tc(np_ctx);
+ }
+ player_unlock_timer(player, timer);
+
+ /* Ensure the normal timer is moving */
+ check_normal_timer(np_ctx, frame_next_count, true);
+ }
+
+ vlc_player_NextVideoFrame(player);
+ wait_next_frame_status(np_ctx, 1, -EAGAIN);
+
+ decrease_tc(np_ctx);
+}
+
+static void
+resume(struct np_ctx *np_ctx)
+{
+ struct ctx *ctx = np_ctx->ctx;
+ vlc_player_t *player = ctx->player;
+
+ /* Resume player */
+ vlc_player_Resume(player);
+ wait_state(ctx, VLC_PLAYER_STATE_PLAYING);
+
+ /* Wait for the forced frame after resume and next/prev frame */
+ wait_next_tc(np_ctx);
+
+ /* Wait for the normal frame after resume */
+ wait_next_tc(np_ctx);
+}
+
+static void
+burst_unpaused(struct np_ctx *np_ctx)
+{
+ struct ctx *ctx = np_ctx->ctx;
+ vlc_player_t *player = ctx->player;
+ struct timer_state *timer = &np_ctx->timers[0];
+ struct vlc_player_timer_smpte_timecode *tc = &np_ctx->tc;
+ struct report_timer *r = NULL;
+ const unsigned burst = 6;
+
+ assert_state(ctx, VLC_PLAYER_STATE_PLAYING);
+
+ for (size_t i = 0; i < burst + 1 /* pause */; i++)
+ vlc_player_NextVideoFrame(player);
+
+ /* First request is pausing the video */
+ wait_next_frame_status(np_ctx, 1, -EAGAIN);
+ assert_state(ctx, VLC_PLAYER_STATE_PAUSED);
+ wait_type_timer(np_ctx, REPORT_TIMER_PAUSED);
+
+ /* Wait all status events */
+ wait_next_frame_status(np_ctx, burst, 0);
+
+ /* Check updated timecodes */
+ increase_tc(np_ctx);
+ player_lock_timer(player, timer);
+ for (size_t i = 0; i < burst; ++i)
+ {
+ r = timer_state_wait_next_report(timer);
+
+ assert(r->type == REPORT_TIMER_TC);
+ assert(r->tc.seconds == tc->seconds);
+ assert(r->tc.frames == tc->frames);
+
+ increase_tc(np_ctx);
+ }
+ player_unlock_timer(player, timer);
+
+ /* Ensure the normal timer is moving */
+ check_normal_timer(np_ctx, burst, true);
+
+ /* Resume player */
+ resume(np_ctx);
+
+ for (size_t i = 0; i < burst + 1 /* pause */; i++)
+ vlc_player_PreviousVideoFrame(player);
+
+ /* First request is pausing the video */
+ wait_prev_frame_status(np_ctx, 1, -EAGAIN);
+ assert_state(ctx, VLC_PLAYER_STATE_PAUSED);
+ wait_type_timer(np_ctx, REPORT_TIMER_PAUSED);
+
+ /* Wait all status events */
+ wait_prev_frame_status(np_ctx, burst, 0);
+
+ /* Check updated timecodes */
+ decrease_tc(np_ctx);
+ player_lock_timer(player, timer);
+ for (size_t i = 0; i < burst; ++i)
+ {
+ r = timer_state_wait_next_report(timer);
+
+ assert(r->type == REPORT_TIMER_TC);
+ assert(r->tc.seconds == tc->seconds);
+ assert(r->tc.frames == tc->frames);
+
+ decrease_tc(np_ctx);
+ }
+ player_unlock_timer(player, timer);
+
+ /* Ensure the normal timer is moving */
+ check_normal_timer(np_ctx, burst, false);
+
+ /* Resume player */
+ resume(np_ctx);
+}
+
+static void
+test_prev(struct ctx *ctx, const struct media_params *params, bool extra_checks)
{
test_log("prev-frame (fps: %u/%u pts-delay: %"PRId64" with_audio: %zu)\n",
params->video_frame_rate, params->video_frame_rate_base,
@@ -381,6 +531,10 @@ test_prev(struct ctx *ctx, const struct media_params *params)
/* Wait for a first frame */
wait_next_tc(&np_ctx);
+ /* Check that it behaves correctly when sending 1st request and next ones
+ * in a burst */
+ burst_unpaused(&np_ctx);
+
vlc_player_SetTime(player, params->length / 2);
/* Wait for a first frame */
@@ -389,34 +543,37 @@ test_prev(struct ctx *ctx, const struct media_params *params)
/* Wait for a first frame after seek */
wait_next_tc(&np_ctx);
- vlc_player_PreviousVideoFrame(player);
+ vlc_player_NextVideoFrame(player);
/* First request is pausing the video */
- wait_prev_frame_status(&np_ctx, 1, -EAGAIN);
+ wait_next_frame_status(&np_ctx, 1, -EAGAIN);
assert_state(ctx, VLC_PLAYER_STATE_PAUSED);
-
- /* Also wait the timer is paused */
wait_type_timer(&np_ctx, REPORT_TIMER_PAUSED);
- go_start(&np_ctx, true);
+ /* Go EOF, via next-frame */
+ go_eof(&np_ctx);
+
+ /* Go start via prev-frame */
+ go_start(&np_ctx, extra_checks);
/* Check start of file a second time */
vlc_player_PreviousVideoFrame(player);
wait_prev_frame_status(&np_ctx, 1, -EAGAIN);
- /* Check playback can be resumed */
- check_resumed_timer(&np_ctx);
-
- /* Go back to start */
- go_start(&np_ctx, false);
+ if (extra_checks)
+ {
+ /* Check playback can be resumed */
+ check_resumed_timer(&np_ctx);
- /* Check we can seek again */
- check_seek_timer(&np_ctx);
+ /* Go back to start */
+ go_start(&np_ctx, false);
- /* Go back to start */
- go_start(&np_ctx, false);
+ /* Check we can seek again */
+ check_seek_timer(&np_ctx);
- /* XXX check next-frame UNTIL EOF (currently NextFrame burst is not working) */
+ /* Go back to start */
+ go_start(&np_ctx, false);
+ }
test_end(ctx);
player_remove_timer(player, &np_ctx.timers[0]);
@@ -517,13 +674,13 @@ main(void)
/* 25 fps + pts-delay lower than frame duration */
params.pts_delay = VLC_TICK_FROM_MS(20);
params.video_frame_rate = 25;
- test_prev(&ctx, ¶ms);
+ test_prev(&ctx, ¶ms, true);
params.pts_delay = DEFAULT_PTS_DELAY;
/* 29.97fps: Playing less than a minute so no drop frames */
params.video_frame_rate = 30000;
params.video_frame_rate_base = 1001;
- test_prev(&ctx, ¶ms);
+ test_prev(&ctx, ¶ms, true);
params.video_frame_rate_base = 1;
/* Now, disable audio to also test normal timer */
@@ -532,14 +689,18 @@ main(void)
/* 60 fps */
params.video_frame_rate = 60;
- test_prev(&ctx, ¶ms);
+ test_prev(&ctx, ¶ms, true);
/* 1 fps */
params.length = VLC_TICK_FROM_SEC(20);
params.video_frame_rate = 1;
- test_prev(&ctx, ¶ms);
+ /* Don't do extra checks for 1fps, as the check_resumed_timer() might take
+ * a longer time than expected due to a bug on the clock: resuming a 1fps
+ * video after prev/frame next might result on using bad reference points. */
+ test_prev(&ctx, ¶ms, false);
/* Fail because can't seek */
+ params.video_frame_rate = 30;
params.can_seek = false;
test_fail(&ctx, ¶ms, -ENOTSUP);
params.can_seek = true;
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/5aebff101357c14f659c9647d03d9e6a41a8ccc2...afee505518f578ad306c46763083233808937f05
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/5aebff101357c14f659c9647d03d9e6a41a8ccc2...afee505518f578ad306c46763083233808937f05
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list