From e56c174f8d5fe8b9680cde97cc0b74b085284c9f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 23 Jun 2004 18:08:26 +0000 Subject: [PATCH] ext/alsa/: Add clock to alsasrc. Take new capture timestamp when restarting after an overrun. Split up some functions... Original commit message from CVS: * ext/alsa/gstalsa.c: (gst_alsa_change_state), (gst_alsa_start), (gst_alsa_xrun_recovery): * ext/alsa/gstalsa.h: * ext/alsa/gstalsasink.c: (gst_alsa_sink_check_event), (gst_alsa_sink_loop), (gst_alsa_sink_get_time): * ext/alsa/gstalsasrc.c: (gst_alsa_src_init), (gst_alsa_src_get_time), (gst_alsa_src_update_avail), (gst_alsa_src_loop): Add clock to alsasrc. Take new capture timestamp when restarting after an overrun. Split up some functions between alsasrc ans alsasink. --- ChangeLog | 14 ++++++++++ ext/alsa/gstalsa.c | 42 ++++++++++++++++++++---------- ext/alsa/gstalsa.h | 4 ++- ext/alsa/gstalsasink.c | 14 +++++----- ext/alsa/gstalsasrc.c | 58 ++++++++++++++++++++++++++++++++++-------- 5 files changed, 101 insertions(+), 31 deletions(-) diff --git a/ChangeLog b/ChangeLog index f0fc6f10e4..1421a162a1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2004-06-23 Wim Taymans + + * ext/alsa/gstalsa.c: (gst_alsa_change_state), (gst_alsa_start), + (gst_alsa_xrun_recovery): + * ext/alsa/gstalsa.h: + * ext/alsa/gstalsasink.c: (gst_alsa_sink_check_event), + (gst_alsa_sink_loop), (gst_alsa_sink_get_time): + * ext/alsa/gstalsasrc.c: (gst_alsa_src_init), + (gst_alsa_src_get_time), (gst_alsa_src_update_avail), + (gst_alsa_src_loop): + Add clock to alsasrc. Take new capture timestamp when + restarting after an overrun. Split up some functions between + alsasrc ans alsasink. + 2004-06-23 Thomas Vander Stichele * ext/alsa/gstalsa.c: (gst_alsa_init), (gst_alsa_dispose), diff --git a/ext/alsa/gstalsa.c b/ext/alsa/gstalsa.c index e9c21388e3..c28ac09c76 100644 --- a/ext/alsa/gstalsa.c +++ b/ext/alsa/gstalsa.c @@ -1085,7 +1085,8 @@ gst_alsa_change_state (GstElement * element) if (!(GST_FLAG_IS_SET (element, GST_ALSA_RUNNING) || gst_alsa_start_audio (this))) return GST_STATE_FAILURE; - this->transmitted = 0; + this->played = 0; + this->captured = 0; break; case GST_STATE_PAUSED_TO_PLAYING: if (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED) { @@ -1202,6 +1203,8 @@ gst_alsa_pcm_wait (GstAlsa * this) inline gboolean gst_alsa_start (GstAlsa * this) { + GstClockTime elemnow; + GST_DEBUG ("Setting state to RUNNING"); switch (snd_pcm_state (this->handle)) { @@ -1212,6 +1215,13 @@ gst_alsa_start (GstAlsa * this) ERROR_CHECK (snd_pcm_prepare (this->handle), "error preparing: %s"); case SND_PCM_STATE_SUSPENDED: case SND_PCM_STATE_PREPARED: + /* The strategy to recover the timestamps from the xrun is to take the + * current element time and pretend we just sent all the samples up to + * that time. This will result in an offset discontinuity in the next + * buffer along with the correct timestamp on that buffer, we only + * update the capture timestamps */ + elemnow = gst_element_get_time (GST_ELEMENT (this)); + this->captured = gst_alsa_timestamp_to_samples (this, elemnow); ERROR_CHECK (snd_pcm_start (this->handle), "error starting playback: %s"); break; case SND_PCM_STATE_PAUSED: @@ -1244,13 +1254,6 @@ gst_alsa_xrun_recovery (GstAlsa * this) GST_ERROR_OBJECT (this, "status error: %s", snd_strerror (err)); if (snd_pcm_status_get_state (status) == SND_PCM_STATE_XRUN) { - struct timeval now, diff, tstamp; - - gettimeofday (&now, 0); - snd_pcm_status_get_trigger_tstamp (status, &tstamp); - timersub (&now, &tstamp, &diff); - GST_INFO_OBJECT (this, "alsa: xrun of at least %.3f msecs", - diff.tv_sec * 1000 + diff.tv_usec / 1000.0); /* if we're allowed to recover, ... */ if (this->autorecover) { @@ -1264,12 +1267,25 @@ gst_alsa_xrun_recovery (GstAlsa * this) this->period_count *= 2; } } - } - if (!(gst_alsa_stop_audio (this) && gst_alsa_start_audio (this))) { - GST_ELEMENT_ERROR (this, RESOURCE, FAILED, (NULL), - ("Error restarting audio after xrun")); - return FALSE; + /* prepare the device again */ + if ((err = snd_pcm_prepare (this->handle)) < 0) { + GST_ERROR_OBJECT (this, "prepare error: %s", snd_strerror (err)); + return FALSE; + } + if (!gst_alsa_start (this)) { + GST_ELEMENT_ERROR (this, RESOURCE, FAILED, (NULL), + ("Error starting audio after xrun")); + return FALSE; + } + GST_DEBUG_OBJECT (this, "XRun!!!! pretending we captured %lld samples", + this->captured); + } else { + if (!(gst_alsa_stop_audio (this) && gst_alsa_start_audio (this))) { + GST_ELEMENT_ERROR (this, RESOURCE, FAILED, (NULL), + ("Error restarting audio after xrun")); + return FALSE; + } } return TRUE; diff --git a/ext/alsa/gstalsa.h b/ext/alsa/gstalsa.h index b499b668df..89036313f4 100644 --- a/ext/alsa/gstalsa.h +++ b/ext/alsa/gstalsa.h @@ -156,11 +156,13 @@ struct _GstAlsa { /* clocking */ GstAlsaClock * clock; /* our provided clock */ - snd_pcm_uframes_t transmitted; /* samples transmitted since last sync + GstClockTime clock_base; + snd_pcm_uframes_t played; /* samples transmitted since last sync This thing actually is our master clock. We will event insert silent samples or drop some to sync to incoming timestamps. */ + snd_pcm_uframes_t captured; GstClockTime max_discont; /* max difference between current playback timestamp and buffers timestamps */ diff --git a/ext/alsa/gstalsasink.c b/ext/alsa/gstalsasink.c index 5959b4691e..3277f4e0cd 100644 --- a/ext/alsa/gstalsasink.c +++ b/ext/alsa/gstalsasink.c @@ -217,7 +217,7 @@ gst_alsa_sink_check_event (GstAlsaSink * sink, gint pad_nr) break; } delay = (this->format == NULL) ? 0 : - GST_SECOND * this->transmitted / this->format->rate - + GST_SECOND * this->played / this->format->rate - gst_alsa_sink_get_time (this); if (gst_event_discont_get_value (event, GST_FORMAT_TIME, &value)) { gst_element_set_time_delay (GST_ELEMENT (this), MIN (value, delay), @@ -386,11 +386,11 @@ sink_restart: * better way to get this info */ if (element->base_time > this->clock->start_time) { expected = - this->transmitted - gst_alsa_timestamp_to_samples (this, + this->played - gst_alsa_timestamp_to_samples (this, element->base_time - this->clock->start_time); } else { expected = - this->transmitted + gst_alsa_timestamp_to_samples (this, + this->played + gst_alsa_timestamp_to_samples (this, this->clock->start_time - element->base_time); } } else { @@ -485,7 +485,7 @@ sink_restart: if ((copied = this->transmit (this, &avail)) < 0) return; /* update our clock */ - this->transmitted += copied; + this->played += copied; /* flush the data */ bytes = gst_alsa_samples_to_bytes (this, copied); for (i = 0; i < element->numpads; i++) { @@ -543,11 +543,11 @@ gst_alsa_sink_get_time (GstAlsa * this) if (!this->format) return 0; if (snd_pcm_delay (this->handle, &delay) != 0) { - return this->transmitted / this->format->rate; + return this->played / this->format->rate; } - if (this->transmitted <= delay) { + if (this->played <= delay) { return 0; } - return GST_SECOND * (this->transmitted - delay) / this->format->rate; + return GST_SECOND * (this->played - delay) / this->format->rate; } diff --git a/ext/alsa/gstalsasrc.c b/ext/alsa/gstalsasrc.c index 16b33be9a7..9fce460269 100644 --- a/ext/alsa/gstalsasrc.c +++ b/ext/alsa/gstalsasrc.c @@ -43,6 +43,7 @@ static int gst_alsa_src_read (GstAlsa * this, snd_pcm_sframes_t * avail); static void gst_alsa_src_loop (GstElement * element); static void gst_alsa_src_flush (GstAlsaSrc * src); static GstElementStateReturn gst_alsa_src_change_state (GstElement * element); +static GstClockTime gst_alsa_src_get_time (GstAlsa * this); static GstAlsa *src_parent_class = NULL; @@ -127,9 +128,25 @@ gst_alsa_src_init (GstAlsaSrc * src) gst_pad_set_getcaps_function (this->pad[0], gst_alsa_get_caps); gst_element_add_pad (GST_ELEMENT (this), this->pad[0]); + this->clock = + gst_alsa_clock_new ("alsasrcclock", gst_alsa_src_get_time, this); + /* we hold a ref to our clock until we're disposed */ + gst_object_ref (GST_OBJECT (this->clock)); + gst_object_sink (GST_OBJECT (this->clock)); + gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_src_loop); } +static GstClockTime +gst_alsa_src_get_time (GstAlsa * this) +{ + GTimeVal now; + + g_get_current_time (&now); + + return GST_TIMEVAL_TO_TIME (now); +} + static int gst_alsa_src_mmap (GstAlsa * this, snd_pcm_sframes_t * avail) { @@ -305,6 +322,30 @@ gst_alsa_src_set_caps (GstAlsaSrc * src, gboolean aggressive) return FALSE; } +inline snd_pcm_sframes_t +gst_alsa_src_update_avail (GstAlsa * this) +{ + snd_pcm_sframes_t avail = -1; + + while (avail < 0) { + avail = snd_pcm_avail_update (this->handle); + if (avail < 0) { + if (avail == -EPIPE) { + gst_alsa_xrun_recovery (this); + } else { + GST_WARNING_OBJECT (this, "unknown ALSA avail_update return value (%d)", + (int) avail); + } + } + if (snd_pcm_state (this->handle) != SND_PCM_STATE_RUNNING) { + if (!gst_alsa_start (this)) { + return 0; + } + } + } + return avail; +} + /* we transmit buffers of period_size frames */ static void gst_alsa_src_loop (GstElement * element) @@ -326,14 +367,13 @@ gst_alsa_src_loop (GstElement * element) GstClockTime now; now = gst_element_get_time (element); - this->clock_base = gst_alsa_get_time (this); - this->transmitted = gst_alsa_timestamp_to_samples (this, now); + this->clock_base = gst_alsa_src_get_time (this); + this->captured = gst_alsa_timestamp_to_samples (this, now); } /* the cast to long is explicitly needed; * with avail = -32 and period_size = 100, avail < period_size is false */ - while ((long) (avail = - gst_alsa_update_avail (this)) < (long) this->period_size) { + while ((avail = gst_alsa_src_update_avail (this)) < this->period_size) { /* wait */ if (gst_alsa_pcm_wait (this) == FALSE) return; @@ -363,7 +403,7 @@ gst_alsa_src_loop (GstElement * element) * what is now in the buffer */ outreal = gst_element_get_time (GST_ELEMENT (this)) - outdur; /* ideal time is counting samples */ - outideal = gst_alsa_samples_to_timestamp (this, this->transmitted); + outideal = gst_alsa_samples_to_timestamp (this, this->captured); outsize = gst_alsa_samples_to_bytes (this, copied); outtime = GST_CLOCK_TIME_NONE; @@ -371,11 +411,9 @@ gst_alsa_src_loop (GstElement * element) if (GST_ELEMENT_CLOCK (this)) { if (GST_CLOCK (GST_ALSA (this)->clock) == GST_ELEMENT_CLOCK (this)) { outtime = outideal; - diff = outideal - outreal; GST_DEBUG_OBJECT (this, "ideal %lld, real %lld, diff %lld\n", outideal, outreal, diff); - gst_alsa_clock_update (this, outideal); } else { outtime = outreal; } @@ -392,14 +430,14 @@ gst_alsa_src_loop (GstElement * element) GST_BUFFER_TIMESTAMP (src->buf[i]) = outtime; GST_BUFFER_DURATION (src->buf[i]) = outdur; - GST_BUFFER_OFFSET (src->buf[i]) = this->transmitted; - GST_BUFFER_OFFSET_END (src->buf[i]) = this->transmitted + copied; + GST_BUFFER_OFFSET (src->buf[i]) = this->captured; + GST_BUFFER_OFFSET_END (src->buf[i]) = this->captured + copied; buf = src->buf[i]; src->buf[i] = NULL; gst_pad_push (this->pad[i], GST_DATA (buf)); } - this->transmitted += copied; + this->captured += copied; } }