From 148c90439ac8cedbfb22d150fde84beb8cb1a53d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jun 2004 12:01:33 +0000 Subject: [PATCH] ext/alsa/: Cleanups, take queued samples into account when reporting the time. Original commit message from CVS: * ext/alsa/gstalsa.c: (gst_alsa_get_time), (gst_alsa_clock_update), (gst_alsa_change_state), (gst_alsa_update_avail), (gst_alsa_xrun_recovery): * ext/alsa/gstalsa.h: * ext/alsa/gstalsasrc.c: (gst_alsa_src_loop): Cleanups, take queued samples into account when reporting the time. --- ChangeLog | 10 +++++ ext/alsa/gstalsa.c | 94 +++++++++++++++++++++++++++++++------------ ext/alsa/gstalsa.h | 3 ++ ext/alsa/gstalsasrc.c | 47 +++++++++++++--------- 4 files changed, 109 insertions(+), 45 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1bb0fb0522..6426679ae2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2004-06-22 Wim Taymans + + * ext/alsa/gstalsa.c: (gst_alsa_get_time), (gst_alsa_clock_update), + (gst_alsa_change_state), (gst_alsa_update_avail), + (gst_alsa_xrun_recovery): + * ext/alsa/gstalsa.h: + * ext/alsa/gstalsasrc.c: (gst_alsa_src_loop): + Cleanups, take queued samples into account when reporting + the time. + 2004-06-22 Wim Taymans * gst/videorate/gstvideorate.c: (gst_videorate_class_init), diff --git a/ext/alsa/gstalsa.c b/ext/alsa/gstalsa.c index ef151b793a..3915598dc3 100644 --- a/ext/alsa/gstalsa.c +++ b/ext/alsa/gstalsa.c @@ -323,22 +323,46 @@ gst_alsa_get_property (GObject * object, guint prop_id, GValue * value, * ask ALSA for current time using htstamp * FIXME: This is not very accurate, should use alsa timers instead. * htstamp seems to use the system clock instead of the hw clock. + * We also use an ugly hack for now to substract the number of queued + * samples in the device from the system time, this makes sure that other + * plugins timestamp their samples with the right time but makes the + * clock a little unstable. A good way to fix this is to get the exact amount + * of samples that were produced by ALSA but I can't find a way to get that + * information.. oh well.. ALSA has enough methods, there is bound to be at + * least 1 way of getting the info... */ GstClockTime gst_alsa_get_time (GstAlsa * this) { int err; snd_htimestamp_t timestamp; - GstClockTime time; + GstClockTime time, ideal; + GstClockTime availtime; + snd_pcm_sframes_t avail; if ((err = snd_pcm_status (this->handle, this->status)) < 0) { GST_WARNING_OBJECT (this, "could not get snd_pcm_status"); } + /* see how many samples are still in the buffer */ + avail = snd_pcm_status_get_avail (this->status); + availtime = gst_alsa_samples_to_timestamp (this, avail); + + /* get the clock time */ snd_pcm_status_get_htstamp (this->status, ×tamp); /* time = GST_TIMESPEC_TO_TIME (timestamp); */ - time = timestamp.tv_sec * GST_SECOND + timestamp.tv_nsec * GST_NSECOND; + /* we have to compensate the time for the number of queued samples + * in the buffer */ + time = + timestamp.tv_sec * GST_SECOND + timestamp.tv_nsec * GST_NSECOND - + availtime; + ideal = + this->clock_base + gst_alsa_samples_to_timestamp (this, + this->transmitted) - availtime; + + GST_DEBUG_OBJECT (this, "clock time %lld, diff to ideal %lld\n", time, + time - ideal); GST_LOG_OBJECT (this, "ALSA reports time of %" GST_TIME_FORMAT, GST_TIME_ARGS (time)); @@ -346,6 +370,12 @@ gst_alsa_get_time (GstAlsa * this) return time; } +void +gst_alsa_clock_update (GstAlsa * this, GstClockTime ideal) +{ + +} + static const GList * gst_alsa_probe_get_properties (GstPropertyProbe * probe) { @@ -1122,6 +1152,7 @@ gst_alsa_change_state (GstElement * element) gst_alsa_start_audio (this))) return GST_STATE_FAILURE; this->transmitted = 0; + this->clock_base = GST_CLOCK_TIME_NONE; break; case GST_STATE_PAUSED_TO_PLAYING: if (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED) { @@ -1191,14 +1222,22 @@ gst_alsa_set_clock (GstElement * element, GstClock * clock) inline snd_pcm_sframes_t gst_alsa_update_avail (GstAlsa * this) { - snd_pcm_sframes_t avail = snd_pcm_avail_update (this->handle); + snd_pcm_sframes_t avail = -1; - 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); + 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; @@ -1280,17 +1319,7 @@ 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); - - /* start new timestamps from the current time */ - this->transmitted = gst_alsa_timestamp_to_samples (this, - gst_element_get_time (GST_ELEMENT (this))); + GstClockTime elemnow; /* if we're allowed to recover, ... */ if (this->autorecover) { @@ -1304,12 +1333,27 @@ 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; + if ((err = snd_pcm_prepare (this->handle)) < 0) { + GST_ERROR_OBJECT (this, "prepare error: %s", snd_strerror (err)); + return FALSE; + } + /* 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 */ + elemnow = gst_element_get_time (GST_ELEMENT (this)); + this->transmitted = gst_alsa_timestamp_to_samples (this, elemnow); + GST_DEBUG_OBJECT (this, "XRun!!!! pretending we transmitted %lld samples", + this->transmitted); + + } else { + /* something else happened, reset the device */ + 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 be7ae1bf10..9fac58d8fa 100644 --- a/ext/alsa/gstalsa.h +++ b/ext/alsa/gstalsa.h @@ -156,6 +156,7 @@ struct _GstAlsa { /* clocking */ GstAlsaClock * clock; /* our provided clock */ + GstClockTime clock_base; /* adjusted clock base time */ snd_pcm_uframes_t transmitted; /* samples transmitted since last sync This thing actually is our master clock. We will event insert silent samples or @@ -193,6 +194,8 @@ GstCaps * gst_alsa_caps (snd_pcm_format_t format, gint channels); GstClockTime gst_alsa_get_time (GstAlsa * this); +void gst_alsa_clock_update (GstAlsa * this, GstClockTime ideal); + /* audio processing functions */ inline snd_pcm_sframes_t gst_alsa_update_avail (GstAlsa * this); diff --git a/ext/alsa/gstalsasrc.c b/ext/alsa/gstalsasrc.c index 05703d2232..16b33be9a7 100644 --- a/ext/alsa/gstalsasrc.c +++ b/ext/alsa/gstalsasrc.c @@ -322,25 +322,18 @@ gst_alsa_src_loop (GstElement * element) return; } } + if (this->clock_base == GST_CLOCK_TIME_NONE) { + 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); + } /* the cast to long is explicitly needed; * with avail = -32 and period_size = 100, avail < period_size is false */ - while ((avail = gst_alsa_update_avail (this)) < (long) this->period_size) { - if (avail == -EPIPE) { - GST_DEBUG_OBJECT (this, "got EPIPE when checking for available bytes"); - continue; - } - if (avail < 0) { - GST_DEBUG_OBJECT (this, - "got error %s (%d) when checking for available bytes", - snd_strerror (avail)); - return; - } - if (snd_pcm_state (this->handle) != SND_PCM_STATE_RUNNING) { - if (!gst_alsa_start (this)) - return; - continue; - }; + while ((long) (avail = + gst_alsa_update_avail (this)) < (long) this->period_size) { /* wait */ if (gst_alsa_pcm_wait (this) == FALSE) return; @@ -360,17 +353,31 @@ gst_alsa_src_loop (GstElement * element) { gint outsize; - GstClockTime outtime, outdur; + GstClockTime outtime, outdur, outreal, outideal; + gint64 diff; + + /* duration of buffer is just the time of the samples */ + outdur = gst_alsa_samples_to_timestamp (this, copied); + + /* The real capture time is the time of the clock minus the duration and + * 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); outsize = gst_alsa_samples_to_bytes (this, copied); - outdur = gst_alsa_samples_to_timestamp (this, copied); outtime = GST_CLOCK_TIME_NONE; if (GST_ELEMENT_CLOCK (this)) { if (GST_CLOCK (GST_ALSA (this)->clock) == GST_ELEMENT_CLOCK (this)) { - outtime = gst_alsa_samples_to_timestamp (this, this->transmitted); + 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 = gst_element_get_time (GST_ELEMENT (this)); + outtime = outreal; } }