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.
This commit is contained in:
Wim Taymans 2004-06-23 18:08:26 +00:00
parent 96c2a15318
commit e56c174f8d
5 changed files with 101 additions and 31 deletions

View File

@ -1,3 +1,17 @@
2004-06-23 Wim Taymans <wim@fluendo.com>
* 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 <thomas at apestaart dot org> 2004-06-23 Thomas Vander Stichele <thomas at apestaart dot org>
* ext/alsa/gstalsa.c: (gst_alsa_init), (gst_alsa_dispose), * ext/alsa/gstalsa.c: (gst_alsa_init), (gst_alsa_dispose),

View File

@ -1085,7 +1085,8 @@ gst_alsa_change_state (GstElement * element)
if (!(GST_FLAG_IS_SET (element, GST_ALSA_RUNNING) || if (!(GST_FLAG_IS_SET (element, GST_ALSA_RUNNING) ||
gst_alsa_start_audio (this))) gst_alsa_start_audio (this)))
return GST_STATE_FAILURE; return GST_STATE_FAILURE;
this->transmitted = 0; this->played = 0;
this->captured = 0;
break; break;
case GST_STATE_PAUSED_TO_PLAYING: case GST_STATE_PAUSED_TO_PLAYING:
if (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED) { if (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED) {
@ -1202,6 +1203,8 @@ gst_alsa_pcm_wait (GstAlsa * this)
inline gboolean inline gboolean
gst_alsa_start (GstAlsa * this) gst_alsa_start (GstAlsa * this)
{ {
GstClockTime elemnow;
GST_DEBUG ("Setting state to RUNNING"); GST_DEBUG ("Setting state to RUNNING");
switch (snd_pcm_state (this->handle)) { 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"); ERROR_CHECK (snd_pcm_prepare (this->handle), "error preparing: %s");
case SND_PCM_STATE_SUSPENDED: case SND_PCM_STATE_SUSPENDED:
case SND_PCM_STATE_PREPARED: 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"); ERROR_CHECK (snd_pcm_start (this->handle), "error starting playback: %s");
break; break;
case SND_PCM_STATE_PAUSED: 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)); GST_ERROR_OBJECT (this, "status error: %s", snd_strerror (err));
if (snd_pcm_status_get_state (status) == SND_PCM_STATE_XRUN) { 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 we're allowed to recover, ... */
if (this->autorecover) { if (this->autorecover) {
@ -1264,12 +1267,25 @@ gst_alsa_xrun_recovery (GstAlsa * this)
this->period_count *= 2; this->period_count *= 2;
} }
} }
}
if (!(gst_alsa_stop_audio (this) && gst_alsa_start_audio (this))) { /* prepare the device again */
GST_ELEMENT_ERROR (this, RESOURCE, FAILED, (NULL), if ((err = snd_pcm_prepare (this->handle)) < 0) {
("Error restarting audio after xrun")); GST_ERROR_OBJECT (this, "prepare error: %s", snd_strerror (err));
return FALSE; 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; return TRUE;

View File

@ -156,11 +156,13 @@ struct _GstAlsa {
/* clocking */ /* clocking */
GstAlsaClock * clock; /* our provided clock */ 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. This thing actually is our master clock.
We will event insert silent samples or We will event insert silent samples or
drop some to sync to incoming timestamps. drop some to sync to incoming timestamps.
*/ */
snd_pcm_uframes_t captured;
GstClockTime max_discont; /* max difference between current GstClockTime max_discont; /* max difference between current
playback timestamp and buffers timestamps playback timestamp and buffers timestamps
*/ */

View File

@ -217,7 +217,7 @@ gst_alsa_sink_check_event (GstAlsaSink * sink, gint pad_nr)
break; break;
} }
delay = (this->format == NULL) ? 0 : 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); gst_alsa_sink_get_time (this);
if (gst_event_discont_get_value (event, GST_FORMAT_TIME, &value)) { if (gst_event_discont_get_value (event, GST_FORMAT_TIME, &value)) {
gst_element_set_time_delay (GST_ELEMENT (this), MIN (value, delay), gst_element_set_time_delay (GST_ELEMENT (this), MIN (value, delay),
@ -386,11 +386,11 @@ sink_restart:
* better way to get this info */ * better way to get this info */
if (element->base_time > this->clock->start_time) { if (element->base_time > this->clock->start_time) {
expected = expected =
this->transmitted - gst_alsa_timestamp_to_samples (this, this->played - gst_alsa_timestamp_to_samples (this,
element->base_time - this->clock->start_time); element->base_time - this->clock->start_time);
} else { } else {
expected = expected =
this->transmitted + gst_alsa_timestamp_to_samples (this, this->played + gst_alsa_timestamp_to_samples (this,
this->clock->start_time - element->base_time); this->clock->start_time - element->base_time);
} }
} else { } else {
@ -485,7 +485,7 @@ sink_restart:
if ((copied = this->transmit (this, &avail)) < 0) if ((copied = this->transmit (this, &avail)) < 0)
return; return;
/* update our clock */ /* update our clock */
this->transmitted += copied; this->played += copied;
/* flush the data */ /* flush the data */
bytes = gst_alsa_samples_to_bytes (this, copied); bytes = gst_alsa_samples_to_bytes (this, copied);
for (i = 0; i < element->numpads; i++) { for (i = 0; i < element->numpads; i++) {
@ -543,11 +543,11 @@ gst_alsa_sink_get_time (GstAlsa * this)
if (!this->format) if (!this->format)
return 0; return 0;
if (snd_pcm_delay (this->handle, &delay) != 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 0;
} }
return GST_SECOND * (this->transmitted - delay) / this->format->rate; return GST_SECOND * (this->played - delay) / this->format->rate;
} }

View File

@ -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_loop (GstElement * element);
static void gst_alsa_src_flush (GstAlsaSrc * src); static void gst_alsa_src_flush (GstAlsaSrc * src);
static GstElementStateReturn gst_alsa_src_change_state (GstElement * element); static GstElementStateReturn gst_alsa_src_change_state (GstElement * element);
static GstClockTime gst_alsa_src_get_time (GstAlsa * this);
static GstAlsa *src_parent_class = NULL; 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_pad_set_getcaps_function (this->pad[0], gst_alsa_get_caps);
gst_element_add_pad (GST_ELEMENT (this), this->pad[0]); 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); 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 static int
gst_alsa_src_mmap (GstAlsa * this, snd_pcm_sframes_t * avail) 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; 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 */ /* we transmit buffers of period_size frames */
static void static void
gst_alsa_src_loop (GstElement * element) gst_alsa_src_loop (GstElement * element)
@ -326,14 +367,13 @@ gst_alsa_src_loop (GstElement * element)
GstClockTime now; GstClockTime now;
now = gst_element_get_time (element); now = gst_element_get_time (element);
this->clock_base = gst_alsa_get_time (this); this->clock_base = gst_alsa_src_get_time (this);
this->transmitted = gst_alsa_timestamp_to_samples (this, now); this->captured = gst_alsa_timestamp_to_samples (this, now);
} }
/* the cast to long is explicitly needed; /* the cast to long is explicitly needed;
* with avail = -32 and period_size = 100, avail < period_size is false */ * with avail = -32 and period_size = 100, avail < period_size is false */
while ((long) (avail = while ((avail = gst_alsa_src_update_avail (this)) < this->period_size) {
gst_alsa_update_avail (this)) < (long) this->period_size) {
/* wait */ /* wait */
if (gst_alsa_pcm_wait (this) == FALSE) if (gst_alsa_pcm_wait (this) == FALSE)
return; return;
@ -363,7 +403,7 @@ gst_alsa_src_loop (GstElement * element)
* what is now in the buffer */ * what is now in the buffer */
outreal = gst_element_get_time (GST_ELEMENT (this)) - outdur; outreal = gst_element_get_time (GST_ELEMENT (this)) - outdur;
/* ideal time is counting samples */ /* 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); outsize = gst_alsa_samples_to_bytes (this, copied);
outtime = GST_CLOCK_TIME_NONE; outtime = GST_CLOCK_TIME_NONE;
@ -371,11 +411,9 @@ gst_alsa_src_loop (GstElement * element)
if (GST_ELEMENT_CLOCK (this)) { if (GST_ELEMENT_CLOCK (this)) {
if (GST_CLOCK (GST_ALSA (this)->clock) == GST_ELEMENT_CLOCK (this)) { if (GST_CLOCK (GST_ALSA (this)->clock) == GST_ELEMENT_CLOCK (this)) {
outtime = outideal; outtime = outideal;
diff = outideal - outreal; diff = outideal - outreal;
GST_DEBUG_OBJECT (this, "ideal %lld, real %lld, diff %lld\n", outideal, GST_DEBUG_OBJECT (this, "ideal %lld, real %lld, diff %lld\n", outideal,
outreal, diff); outreal, diff);
gst_alsa_clock_update (this, outideal);
} else { } else {
outtime = outreal; outtime = outreal;
} }
@ -392,14 +430,14 @@ gst_alsa_src_loop (GstElement * element)
GST_BUFFER_TIMESTAMP (src->buf[i]) = outtime; GST_BUFFER_TIMESTAMP (src->buf[i]) = outtime;
GST_BUFFER_DURATION (src->buf[i]) = outdur; GST_BUFFER_DURATION (src->buf[i]) = outdur;
GST_BUFFER_OFFSET (src->buf[i]) = this->transmitted; GST_BUFFER_OFFSET (src->buf[i]) = this->captured;
GST_BUFFER_OFFSET_END (src->buf[i]) = this->transmitted + copied; GST_BUFFER_OFFSET_END (src->buf[i]) = this->captured + copied;
buf = src->buf[i]; buf = src->buf[i];
src->buf[i] = NULL; src->buf[i] = NULL;
gst_pad_push (this->pad[i], GST_DATA (buf)); gst_pad_push (this->pad[i], GST_DATA (buf));
} }
this->transmitted += copied; this->captured += copied;
} }
} }