gst-libs/gst/audio/gstbaseaudiosink.c: Change the way in which the ringbuffer is started when dealing with a slaved c...

Original commit message from CVS:
* gst-libs/gst/audio/gstbaseaudiosink.c:
(gst_base_audio_sink_resample_slaving),
(gst_base_audio_sink_skew_slaving),
(gst_base_audio_sink_sync_latency), (gst_base_audio_sink_render),
(gst_base_audio_sink_async_play),
(gst_base_audio_sink_change_state):
Change the way in which the ringbuffer is started when dealing with a
slaved clock and latency. We now sync to the clock until we reach
upstream latency before starting the ringbuffer. This has the effect
that we can accurately align the master and slave clocks and let the
rate correction code take care of the initial drift or rounding errors
instead of leaving them uncorrected with the old approach.
This commit is contained in:
Wim Taymans 2008-05-20 11:09:06 +00:00
parent b5a5d64713
commit 95d162fb71
2 changed files with 192 additions and 52 deletions

View file

@ -1,3 +1,18 @@
2008-05-20 Wim Taymans <wim.taymans@collabora.co.uk>
* gst-libs/gst/audio/gstbaseaudiosink.c:
(gst_base_audio_sink_resample_slaving),
(gst_base_audio_sink_skew_slaving),
(gst_base_audio_sink_sync_latency), (gst_base_audio_sink_render),
(gst_base_audio_sink_async_play),
(gst_base_audio_sink_change_state):
Change the way in which the ringbuffer is started when dealing with a
slaved clock and latency. We now sync to the clock until we reach
upstream latency before starting the ringbuffer. This has the effect
that we can accurately align the master and slave clocks and let the
rate correction code take care of the initial drift or rounding errors
instead of leaving them uncorrected with the old approach.
2008-05-20 Sebastian Dröge <slomo@circular-chaos.org> 2008-05-20 Sebastian Dröge <slomo@circular-chaos.org>
* gst/audioconvert/gstaudioconvert.c: * gst/audioconvert/gstaudioconvert.c:

View file

@ -52,6 +52,8 @@ struct _GstBaseAudioSinkPrivate
GstClockTimeDiff avg_skew; GstClockTimeDiff avg_skew;
/* the number of samples we aligned last time */ /* the number of samples we aligned last time */
gint64 last_align; gint64 last_align;
gboolean sync_latency;
}; };
/* BaseAudioSink signals and args */ /* BaseAudioSink signals and args */
@ -809,6 +811,22 @@ gst_base_audio_sink_resample_slaving (GstBaseAudioSink * sink,
GstClockTime cinternal, cexternal; GstClockTime cinternal, cexternal;
GstClockTime crate_num, crate_denom; GstClockTime crate_num, crate_denom;
/* FIXME, we can sample and add observations here or use the timeouts on the
* clock. No idea which one is better or more stable. The timeout seems more
* arbitrary but this one seems more demanding and does not work when there is
* no data comming in to the sink. */
#if 0
GstClockTime etime, itime;
gdouble r_squared;
/* sample clocks and figure out clock skew */
etime = gst_clock_get_time (GST_ELEMENT_CLOCK (sink));
itime = gst_clock_get_internal_time (sink->provided_clock);
/* add new observation */
gst_clock_add_observation (sink->provided_clock, itime, etime, &r_squared);
#endif
/* get calibration parameters to compensate for speed and offset differences /* get calibration parameters to compensate for speed and offset differences
* when we are slaved */ * when we are slaved */
gst_clock_get_calibration (sink->provided_clock, &cinternal, &cexternal, gst_clock_get_calibration (sink->provided_clock, &cinternal, &cexternal,
@ -858,6 +876,12 @@ gst_base_audio_sink_skew_slaving (GstBaseAudioSink * sink,
etime = gst_clock_get_time (GST_ELEMENT_CLOCK (sink)); etime = gst_clock_get_time (GST_ELEMENT_CLOCK (sink));
itime = gst_clock_get_internal_time (sink->provided_clock); itime = gst_clock_get_internal_time (sink->provided_clock);
GST_DEBUG_OBJECT (sink,
"internal %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT
" cinternal %" GST_TIME_FORMAT " cexternal %" GST_TIME_FORMAT,
GST_TIME_ARGS (itime), GST_TIME_ARGS (etime),
GST_TIME_ARGS (cinternal), GST_TIME_ARGS (cexternal));
/* make sure we never go below 0 */ /* make sure we never go below 0 */
etime = etime > cexternal ? etime - cexternal : 0; etime = etime > cexternal ? etime - cexternal : 0;
itime = itime > cinternal ? itime - cinternal : 0; itime = itime > cinternal ? itime - cinternal : 0;
@ -990,6 +1014,136 @@ gst_base_audio_sink_handle_slaving (GstBaseAudioSink * sink,
} }
} }
/* must be called with LOCK */
static GstFlowReturn
gst_base_audio_sink_sync_latency (GstBaseSink * bsink)
{
GstClock *clock;
GstClockReturn status;
GstClockTime time;
GstFlowReturn ret;
GstBaseAudioSink *sink;
GstClockTime itime, etime;
GstClockTime rate_num, rate_denom;
sink = GST_BASE_AUDIO_SINK (bsink);
clock = GST_ELEMENT_CLOCK (sink);
if (G_UNLIKELY (clock == NULL))
goto no_clock;
/* we provided the global clock, don't need to do anything special */
if (clock == sink->provided_clock)
goto no_slaving;
GST_OBJECT_UNLOCK (sink);
do {
GstClockTimeDiff jitter;
GST_DEBUG_OBJECT (sink, "checking preroll");
/* first wait for the playing state before we can continue */
if (G_UNLIKELY (bsink->need_preroll)) {
ret = gst_base_sink_wait_preroll (bsink);
if (ret != GST_FLOW_OK)
goto flushing;
}
GST_OBJECT_LOCK (sink);
time = sink->priv->us_latency;
GST_OBJECT_UNLOCK (sink);
/* preroll done, we can sync since we are in PLAYING now. */
GST_DEBUG_OBJECT (sink, "possibly waiting for clock to reach %"
GST_TIME_FORMAT, GST_TIME_ARGS (time));
/* wait for the clock, this can be interrupted because we got shut down or
* we PAUSED. */
status = gst_base_sink_wait_clock (bsink, time, &jitter);
GST_DEBUG_OBJECT (sink, "clock returned %d %" GST_TIME_FORMAT, status,
GST_TIME_ARGS (jitter));
/* invalid time, no clock or sync disabled, just continue then */
if (status == GST_CLOCK_BADTIME)
break;
/* waiting could have been interrupted and we can be flushing now */
if (G_UNLIKELY (bsink->flushing))
goto flushing;
/* retry if we got unscheduled, which means we did not reach the timeout
* yet. if some other error occures, we continue. */
} while (status == GST_CLOCK_UNSCHEDULED);
GST_OBJECT_LOCK (sink);
GST_DEBUG_OBJECT (sink, "latency synced");
/* if we are slaved to a clock, we need to set the initial
* calibration */
switch (sink->priv->slave_method) {
case GST_BASE_AUDIO_SINK_SLAVE_SKEW:
case GST_BASE_AUDIO_SINK_SLAVE_RESAMPLE:
case GST_BASE_AUDIO_SINK_SLAVE_NONE:
default:
/* When we are prerolled, our internal clock should exactly have been the
* latency (== the running time of the external clock) */
etime = GST_ELEMENT_CAST (sink)->base_time + time;
itime = gst_base_audio_sink_get_time (sink->provided_clock, sink);
break;
}
GST_DEBUG_OBJECT (sink,
"internal time: %" GST_TIME_FORMAT " external time: %" GST_TIME_FORMAT,
GST_TIME_ARGS (itime), GST_TIME_ARGS (etime));
/* copy the original calibrated rate but update the internal and external
* times. */
gst_clock_get_calibration (sink->provided_clock, NULL, NULL, &rate_num,
&rate_denom);
gst_clock_set_calibration (sink->provided_clock, itime, etime,
rate_num, rate_denom);
switch (sink->priv->slave_method) {
case GST_BASE_AUDIO_SINK_SLAVE_RESAMPLE:
/* only set as master when we are resampling */
GST_DEBUG_OBJECT (sink, "Setting clock as master");
gst_clock_set_master (sink->provided_clock, clock);
break;
case GST_BASE_AUDIO_SINK_SLAVE_SKEW:
case GST_BASE_AUDIO_SINK_SLAVE_NONE:
default:
break;
}
sink->priv->avg_skew = -1;
sink->next_sample = -1;
/* start ringbuffer so we can start slaving right away when we need to */
gst_ring_buffer_start (sink->ringbuffer);
return GST_FLOW_OK;
/* ERRORS */
no_clock:
{
GST_DEBUG_OBJECT (sink, "we have no clock");
return GST_FLOW_OK;
}
no_slaving:
{
GST_DEBUG_OBJECT (sink, "we are not slaved");
return GST_FLOW_OK;
}
flushing:
{
GST_DEBUG_OBJECT (sink, "we are flushing");
GST_OBJECT_LOCK (sink);
return GST_FLOW_WRONG_STATE;
}
}
static GstFlowReturn static GstFlowReturn
gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf) gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf)
{ {
@ -1007,6 +1161,7 @@ gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf)
GstClockTime base_time = GST_CLOCK_TIME_NONE, latency; GstClockTime base_time = GST_CLOCK_TIME_NONE, latency;
GstClock *clock; GstClock *clock;
gboolean sync, slaved, align_next; gboolean sync, slaved, align_next;
GstFlowReturn ret;
sink = GST_BASE_AUDIO_SINK (bsink); sink = GST_BASE_AUDIO_SINK (bsink);
@ -1016,6 +1171,21 @@ gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf)
if (G_UNLIKELY (!gst_ring_buffer_is_acquired (ringbuf))) if (G_UNLIKELY (!gst_ring_buffer_is_acquired (ringbuf)))
goto wrong_state; goto wrong_state;
/* Wait for upstream latency before starting the ringbuffer, we do this so
* that we can align the first sample of the ringbuffer to the base_time +
* latency. */
GST_OBJECT_LOCK (sink);
if (G_UNLIKELY (sink->priv->sync_latency)) {
/* only do this once until we are set back to PLAYING */
sink->priv->sync_latency = FALSE;
ret = gst_base_audio_sink_sync_latency (bsink);
GST_OBJECT_UNLOCK (sink);
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto sync_latency_failed;
} else {
GST_OBJECT_UNLOCK (sink);
}
bps = ringbuf->spec.bytes_per_sample; bps = ringbuf->spec.bytes_per_sample;
size = GST_BUFFER_SIZE (buf); size = GST_BUFFER_SIZE (buf);
@ -1293,6 +1463,11 @@ stopping:
GST_DEBUG_OBJECT (sink, "ringbuffer is stopping"); GST_DEBUG_OBJECT (sink, "ringbuffer is stopping");
return GST_FLOW_WRONG_STATE; return GST_FLOW_WRONG_STATE;
} }
sync_latency_failed:
{
GST_DEBUG_OBJECT (sink, "failed waiting for latency");
return ret;
}
} }
/** /**
@ -1399,65 +1574,14 @@ eos:
static GstStateChangeReturn static GstStateChangeReturn
gst_base_audio_sink_async_play (GstBaseSink * basesink) gst_base_audio_sink_async_play (GstBaseSink * basesink)
{ {
GstClock *clock;
GstBaseAudioSink *sink; GstBaseAudioSink *sink;
GstClockTime itime, etime;
GstClockTime rate_num, rate_denom;
sink = GST_BASE_AUDIO_SINK (basesink); sink = GST_BASE_AUDIO_SINK (basesink);
GST_DEBUG_OBJECT (sink, "ringbuffer may start now"); GST_DEBUG_OBJECT (sink, "ringbuffer may start now");
sink->priv->sync_latency = TRUE;
gst_ring_buffer_may_start (sink->ringbuffer, TRUE); gst_ring_buffer_may_start (sink->ringbuffer, TRUE);
clock = GST_ELEMENT_CLOCK (sink);
if (clock == NULL)
goto done;
/* we provided the global clock, don't need to do anything special */
if (clock == sink->provided_clock)
goto done;
/* if we are slaved to a clock, we need to set the initial
* calibration */
switch (sink->priv->slave_method) {
case GST_BASE_AUDIO_SINK_SLAVE_SKEW:
case GST_BASE_AUDIO_SINK_SLAVE_RESAMPLE:
case GST_BASE_AUDIO_SINK_SLAVE_NONE:
default:
/* When we are prerolled, our internal clock should exactly have been the time
* of the external clock */
etime = gst_clock_get_time (clock);
itime = gst_base_audio_sink_get_time (sink->provided_clock, sink);
break;
}
GST_DEBUG_OBJECT (sink,
"internal time: %" GST_TIME_FORMAT " external time: %" GST_TIME_FORMAT,
GST_TIME_ARGS (itime), GST_TIME_ARGS (etime));
gst_clock_get_calibration (sink->provided_clock, NULL, NULL, &rate_num,
&rate_denom);
gst_clock_set_calibration (sink->provided_clock, itime, etime,
rate_num, rate_denom);
switch (sink->priv->slave_method) {
case GST_BASE_AUDIO_SINK_SLAVE_RESAMPLE:
/* only set as master when we are resampling */
GST_DEBUG_OBJECT (sink, "Setting clock as master");
gst_clock_set_master (sink->provided_clock, clock);
break;
case GST_BASE_AUDIO_SINK_SLAVE_SKEW:
case GST_BASE_AUDIO_SINK_SLAVE_NONE:
default:
break;
}
sink->priv->avg_skew = -1;
sink->next_sample = -1;
/* start ringbuffer so we can start slaving right away when we need to */
gst_ring_buffer_start (sink->ringbuffer);
done:
return GST_STATE_CHANGE_SUCCESS; return GST_STATE_CHANGE_SUCCESS;
} }
@ -1504,6 +1628,7 @@ gst_base_audio_sink_change_state (GstElement * element,
/* ringbuffer cannot start anymore */ /* ringbuffer cannot start anymore */
gst_ring_buffer_may_start (sink->ringbuffer, FALSE); gst_ring_buffer_may_start (sink->ringbuffer, FALSE);
gst_ring_buffer_pause (sink->ringbuffer); gst_ring_buffer_pause (sink->ringbuffer);
sink->priv->sync_latency = FALSE;
GST_OBJECT_UNLOCK (sink); GST_OBJECT_UNLOCK (sink);
break; break;
case GST_STATE_CHANGE_PAUSED_TO_READY: case GST_STATE_CHANGE_PAUSED_TO_READY: