diff --git a/include/vlc_aout.h b/include/vlc_aout.h index ac5610b891f41d8ed9200144c148c25f383de7f5..a4ef1800605af38f1a8c8fac3fedfa56108bd317 100644 --- a/include/vlc_aout.h +++ b/include/vlc_aout.h @@ -200,7 +200,8 @@ struct audio_output * handle the first play() date, cf. play() documentation. * * \warning It is recommended to report the audio delay via - * aout_TimingReport(). In that case, time_get should not be implemented. + * aout_TimingReport() or by implementing timing_get. In that case, + * time_get should not be implemented. * * \param delay pointer to the delay until the next sample to be written * to the playback buffer is rendered [OUT] @@ -209,6 +210,23 @@ struct audio_output * \note This callback cannot be called in stopped state. */ + int (*timing_get)(audio_output_t *, vlc_tick_t *system_ts, vlc_tick_t *audio_ts); + /**< Returns a timing point to estimate the audio latency. + * + * system_ts doesn't have to be close to vlc_tick_now(). Any valid { + * system_ts, audio_ts } points in the past are sufficient to update the + * clock. + * + * \note audio_ts starts at 0 and should not take the block PTS into + * account. + * + * \param system_ts pointer to the system time + * \param audio_ts pointer to audio time + * \return 0 on success, non-zero on failure or lack of data + * + * \note This callback cannot be called in stopped state. + */ + void (*play)(audio_output_t *, block_t *block, vlc_tick_t date); /**< Queues a block of samples for playback (mandatory, cannot be NULL). * diff --git a/include/vlc_frame.h b/include/vlc_frame.h index 5ac9deb694a4441751d0a68d7c5f537026273645..bb27b3de5e651150579cd1108196b6b17fdec02d 100644 --- a/include/vlc_frame.h +++ b/include/vlc_frame.h @@ -671,6 +671,11 @@ static inline void vlc_fifo_WaitCond(vlc_fifo_t *fifo, vlc_cond_t *condvar) vlc_cond_wait(condvar, &q->lock); } +static inline void vlc_fifo_TimedWait(vlc_fifo_t *fifo, vlc_tick_t deadline) +{ + vlc_queue_TimedWait(vlc_fifo_queue(fifo), deadline); +} + /** * Queues a linked-list of blocks into a locked FIFO. * diff --git a/include/vlc_queue.h b/include/vlc_queue.h index 9d1d4b106549f57312ac2f7faf3796f350b713b0..59137f1e6462180a4d2a45d898510bf6fa882913 100644 --- a/include/vlc_queue.h +++ b/include/vlc_queue.h @@ -122,6 +122,11 @@ static inline void vlc_queue_Wait(vlc_queue_t *q) vlc_cond_wait(&q->wait, &q->lock); } +static inline void vlc_queue_TimedWait(vlc_queue_t *q, vlc_tick_t deadline) +{ + vlc_cond_timedwait(&q->wait, &q->lock, deadline); +} + /** * Queues an entry (without locking). * diff --git a/modules/audio_output/mmdevice.c b/modules/audio_output/mmdevice.c index a0ab8cfcc91a43f0bb12fe6256b3b541a49fd53f..bac16fc25c6fd712e1deeddc458d5ec8963ff937 100644 --- a/modules/audio_output/mmdevice.c +++ b/modules/audio_output/mmdevice.c @@ -135,6 +135,19 @@ static int TimeGet(audio_output_t *aout, vlc_tick_t *restrict delay) return SUCCEEDED(hr) ? 0 : -1; } +static int TimingGet(audio_output_t *aout, vlc_tick_t *restrict system_ts, + vlc_tick_t *restrict audio_ts) +{ + aout_sys_t *sys = aout->sys; + HRESULT hr; + + EnterMTA(); + hr = aout_stream_TimingGet(sys->stream, system_ts, audio_ts); + LeaveMTA(); + + return SUCCEEDED(hr) ? 0 : -1; +} + static void Play(audio_output_t *aout, block_t *block, vlc_tick_t date) { aout_sys_t *sys = aout->sys; @@ -1217,6 +1230,11 @@ static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt) return -1; } + if (s->timing_get != NULL) + aout->timing_get = TimingGet; + else + aout->time_get = TimeGet; + assert (sys->stream == NULL); sys->stream = s; aout_GainRequest(aout, sys->gain); @@ -1316,7 +1334,6 @@ static int Open(vlc_object_t *obj) aout->start = Start; aout->stop = Stop; - aout->time_get = TimeGet; aout->play = Play; aout->pause = Pause; aout->flush = Flush; diff --git a/modules/audio_output/mmdevice.h b/modules/audio_output/mmdevice.h index 65e41de4d40912e1fcb41d23d7addc5800b0ce45..2c2570a660086d021502ef72c3a4f90cafedd9cd 100644 --- a/modules/audio_output/mmdevice.h +++ b/modules/audio_output/mmdevice.h @@ -38,6 +38,7 @@ struct aout_stream void (*stop)(aout_stream_t *); HRESULT (*time_get)(aout_stream_t *, vlc_tick_t *); + HRESULT (*timing_get)(aout_stream_t *, vlc_tick_t *, vlc_tick_t *); HRESULT (*play)(aout_stream_t *, block_t *, vlc_tick_t); HRESULT (*pause)(aout_stream_t *, bool); HRESULT (*flush)(aout_stream_t *); @@ -71,6 +72,12 @@ static inline HRESULT aout_stream_TimeGet(aout_stream_t *s, vlc_tick_t *delay) return (s->time_get)(s, delay); } +static inline HRESULT aout_stream_TimingGet(aout_stream_t *s, vlc_tick_t *system_ts, + vlc_tick_t *audio_ts) +{ + return (s->timing_get)(s, system_ts, audio_ts); +} + static inline HRESULT aout_stream_Play(aout_stream_t *s, block_t *block, vlc_tick_t date) { diff --git a/modules/audio_output/wasapi.c b/modules/audio_output/wasapi.c index c8045ff022cdf55f296a368256419ce45562fd2d..c0d41311ebb4fe5db840f64fa40813a1d7000a49 100644 --- a/modules/audio_output/wasapi.c +++ b/modules/audio_output/wasapi.c @@ -132,7 +132,8 @@ static void ResetTimer(aout_stream_t *s) } /*** VLC audio output callbacks ***/ -static HRESULT TimeGet(aout_stream_t *s, vlc_tick_t *restrict delay) +static HRESULT TimingGet(aout_stream_t *s, vlc_tick_t *restrict system_ts, + vlc_tick_t *restrict audio_ts) { aout_stream_sys_t *sys = s->sys; void *pv; @@ -161,14 +162,11 @@ static HRESULT TimeGet(aout_stream_t *s, vlc_tick_t *restrict delay) return hr; } - vlc_tick_t written = vlc_tick_from_frac(sys->written, sys->rate); - vlc_tick_t tick_pos = vlc_tick_from_frac(pos, clock_freq); + *audio_ts = vlc_tick_from_frac(pos, clock_freq); + *system_ts = VLC_TICK_FROM_MSFTIME(qpcpos); static_assert((10000000 % CLOCK_FREQ) == 0, "Frequency conversion broken"); - *delay = written - tick_pos - - VLC_TICK_FROM_MSFTIME(get_qpc() - qpcpos); - return hr; } @@ -923,7 +921,7 @@ static HRESULT Start(aout_stream_t *s, audio_sample_format_t *restrict pfmt, *pfmt = fmt; sys->written = 0; s->sys = sys; - s->time_get = TimeGet; + s->timing_get = TimingGet; s->play = Play; s->pause = Pause; s->flush = Flush; diff --git a/src/audio_output/aout_internal.h b/src/audio_output/aout_internal.h index 957f62a3656d00ddb22468588625a61efad1e3ff..f17c7b6c00206a498998779b2f919960d8a0a4a5 100644 --- a/src/audio_output/aout_internal.h +++ b/src/audio_output/aout_internal.h @@ -153,7 +153,7 @@ void vlc_aout_stream_ChangeRate(vlc_aout_stream *stream, float rate); void vlc_aout_stream_ChangeDelay(vlc_aout_stream *stream, vlc_tick_t delay); void vlc_aout_stream_Flush(vlc_aout_stream *stream); void vlc_aout_stream_Drain(vlc_aout_stream *stream); -void vlc_aout_stream_UpdateLatency(vlc_aout_stream *stream); +void vlc_aout_stream_UpdateLatency(vlc_aout_stream *stream, vlc_tick_t *next_deadline); /* Contrary to other vlc_aout_stream_*() functions, this function can be called from * any threads */ bool vlc_aout_stream_IsDrained(vlc_aout_stream *stream); diff --git a/src/audio_output/dec.c b/src/audio_output/dec.c index 1a3643d7090b8d668424e61c588f77b25f4cd39c..e8d12eb35f1e1782775408f21aab6013b97ce474 100644 --- a/src/audio_output/dec.c +++ b/src/audio_output/dec.c @@ -46,6 +46,11 @@ struct timing_point vlc_tick_t audio_ts; }; +/* aout->timing is called every TIMING_GET_DELAY */ +#define TIMING_GET_DELAY VLC_TICK_FROM_MS(1000) +/* In case of failure, call aout->timing more often until success. */ +#define TIMING_GET_FAIL_DELAY VLC_TICK_FROM_MS(20) + struct vlc_aout_stream { aout_instance_t *instance; @@ -65,6 +70,8 @@ struct vlc_aout_stream bool discontinuity; vlc_tick_t request_delay; vlc_tick_t delay; + + vlc_tick_t last_timing_date; } sync; vlc_tick_t original_pts; @@ -79,8 +86,6 @@ struct vlc_aout_stream /* Index of the next point to write */ size_t head; - bool running; - vlc_tick_t first_pts; vlc_tick_t last_pts; /* Used for stream_TimeGet() emulation */ @@ -91,6 +96,8 @@ struct vlc_aout_stream void *notify_latency_data; } timing_points; + bool playing; + const char *str_id; /* Original input format and profile, won't change for the lifetime of a @@ -163,7 +170,8 @@ static void stream_Discontinuity(vlc_aout_stream *stream) stream->timing_points.first_pts = stream->timing_points.last_pts = VLC_TICK_INVALID; - stream->timing_points.running = false; + stream->playing = false; + stream->sync.last_timing_date = 0; } static void stream_Reset(vlc_aout_stream *stream) @@ -581,8 +589,8 @@ static void stream_HandleDrift(vlc_aout_stream *stream, vlc_tick_t drift, } } -static void stream_Synchronize(vlc_aout_stream *stream, vlc_tick_t system_now, - vlc_tick_t dec_pts) +static vlc_tick_t +stream_UpdateTime(vlc_aout_stream *stream, vlc_tick_t system_now, vlc_tick_t dec_pts) { /** * Depending on the drift between the actual and intended playback times, @@ -603,7 +611,7 @@ static void stream_Synchronize(vlc_aout_stream *stream, vlc_tick_t system_now, vlc_tick_t delay; if (stream_TimeGet(stream, &delay) != 0) - return; /* nothing can be done if timing is unknown */ + return 0; /* nothing can be done if timing is unknown */ if (stream->sync.discontinuity) { @@ -624,14 +632,12 @@ static void stream_Synchronize(vlc_aout_stream *stream, vlc_tick_t system_now, { stream_Silence(stream, jitter, dec_pts - delay); if (stream_TimeGet(stream, &delay) != 0) - return; + return 0; } } - vlc_tick_t drift = vlc_clock_Update(stream->sync.clock, system_now + delay, - dec_pts, stream->sync.rate); - - stream_HandleDrift(stream, drift, dec_pts); + return vlc_clock_Update(stream->sync.clock, system_now + delay, + dec_pts, stream->sync.rate); } void vlc_aout_stream_NotifyTiming(vlc_aout_stream *stream, vlc_tick_t system_ts, @@ -726,10 +732,53 @@ stream_ReadTimingPoints(vlc_aout_stream *stream) return drift; } -void vlc_aout_stream_UpdateLatency(vlc_aout_stream *stream) +static vlc_tick_t +stream_UpdateTiming(vlc_aout_stream *stream, vlc_tick_t system_now, + vlc_tick_t *next_deadline) { - if (stream->timing_points.running) - stream_ReadTimingPoints(stream); + audio_output_t *aout = aout_stream_aout(stream); + vlc_tick_t drift = 0; + + if (aout->timing_get != NULL) + { + if (system_now - stream->sync.last_timing_date >= TIMING_GET_DELAY) + { + vlc_tick_t system_ts, audio_ts, delay; + int ret = aout->timing_get(aout, &system_ts, &audio_ts); + if (ret == 0) + { + drift = vlc_clock_Update(stream->sync.clock, system_ts, + audio_ts, stream->sync.rate); + stream->sync.last_timing_date = system_now; + delay = TIMING_GET_DELAY; + } + else + delay = TIMING_GET_FAIL_DELAY; + + if (next_deadline != NULL) + *next_deadline = delay + system_now; + } + else if (next_deadline != NULL) + *next_deadline = TIMING_GET_DELAY + stream->sync.last_timing_date; + } + else + { + assert(aout->time_get == NULL); + + drift = stream_ReadTimingPoints(stream); + + if (next_deadline != NULL) + *next_deadline = VLC_TICK_MAX; + } + + return drift; +} + +void vlc_aout_stream_UpdateLatency(vlc_aout_stream *stream, + vlc_tick_t *next_deadline) +{ + if (stream->playing) + stream_UpdateTiming(stream, vlc_tick_now(), next_deadline); } /***************************************************************************** @@ -796,13 +845,12 @@ int vlc_aout_stream_Play(vlc_aout_stream *stream, block_t *block) /* Drift correction */ vlc_tick_t system_now = vlc_tick_now(); + vlc_tick_t drift; if (aout->time_get != NULL) - stream_Synchronize(stream, system_now, original_pts); + drift = stream_UpdateTime(stream, system_now, original_pts); else - { - vlc_tick_t drift = stream_ReadTimingPoints(stream); - stream_HandleDrift(stream, drift, original_pts); - } + drift = stream_UpdateTiming(stream, system_now, NULL); + stream_HandleDrift(stream, drift, original_pts); vlc_tick_t play_date = vlc_clock_ConvertToSystem(stream->sync.clock, system_now, original_pts, @@ -817,9 +865,9 @@ int vlc_aout_stream_Play(vlc_aout_stream *stream, block_t *block) vlc_audio_meter_Process(&owner->meter, block, play_date); - if (!stream->timing_points.running) + if (!stream->playing) { - stream->timing_points.running = true; + stream->playing = true; /* Assert that no timing points are updated between flush and play */ #ifndef NDEBUG diff --git a/src/audio_output/output.c b/src/audio_output/output.c index e7f6c05b198ad819acf1578c0f4c689c1cce1e1f..db88ef73e5d0a352d7f4f604f741b2b06e69c0ef 100644 --- a/src/audio_output/output.c +++ b/src/audio_output/output.c @@ -287,6 +287,9 @@ audio_output_t *aout_New (vlc_object_t *parent) return NULL; } assert(aout->start && aout->stop); + /* Both NULL or only one valid */ + assert(aout->time_get == NULL || + (aout->time_get == NULL) != (aout->timing_get == NULL)); /* * Persistent audio output variables diff --git a/src/input/decoder.c b/src/input/decoder.c index 9e05a23556dddd0b09d257c96d0c85609c24ce79..14e030a0b5afd5b56692e0b69581478e04424532 100644 --- a/src/input/decoder.c +++ b/src/input/decoder.c @@ -1665,6 +1665,7 @@ static void *DecoderThread( void *p_data ) vlc_input_decoder_t *p_owner = (vlc_input_decoder_t *)p_data; float rate = 1.f; vlc_tick_t delay = 0; + vlc_tick_t audio_latency_deadline = VLC_TICK_MAX; bool paused = false; const char *thread_name; @@ -1766,13 +1767,14 @@ 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 ); - vlc_fifo_Wait( p_owner->p_fifo ); + vlc_fifo_TimedWait( p_owner->p_fifo, audio_latency_deadline ); /* Update the audio latency after a possible long wait in order * to update the audio clock as soon as possible. */ if( p_owner->dec.fmt_in.i_cat == AUDIO_ES && p_owner->p_astream != NULL ) - vlc_aout_stream_UpdateLatency( p_owner->p_astream ); + vlc_aout_stream_UpdateLatency( p_owner->p_astream, + &audio_latency_deadline ); p_owner->b_idle = false; continue;