From 65b1938b38c8a6705a72da554836cd7effa40a11 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 15 Sep 2006 14:53:44 +0000 Subject: [PATCH] gst-libs/gst/audio/: Do the delay calculation in the source/sink base classes as this is specific for the capture/pla... Original commit message from CVS: * gst-libs/gst/audio/gstbaseaudiosink.c: (gst_base_audio_sink_get_time), (gst_base_audio_sink_callback): * gst-libs/gst/audio/gstbaseaudiosrc.c: (gst_base_audio_src_get_time), (gst_base_audio_src_fixate), (gst_base_audio_src_get_times), (gst_base_audio_src_get_offset), (gst_base_audio_src_create), (gst_base_audio_src_change_state): Do the delay calculation in the source/sink base classes as this is specific for the capture/playback mode. Try to fixate a bit better, like round depth up to a multiple of 8 bigger than width. Handle underruns correctly by marking DISCONT on buffers and adjusting timestamps to handle the gap. Set offset/offset_end correctly on buffers. * gst-libs/gst/audio/gstringbuffer.c: (gst_ring_buffer_pause), (gst_ring_buffer_samples_done), (gst_ring_buffer_commit), (gst_ring_buffer_read): Remove resync and underrun recovery from the ringbuffer. Fix ringbuffer read code on under/overrun. --- ChangeLog | 22 +++++ gst-libs/gst/audio/gstbaseaudiosink.c | 22 ++++- gst-libs/gst/audio/gstbaseaudiosrc.c | 114 ++++++++++++++++++++------ gst-libs/gst/audio/gstringbuffer.c | 70 ++++++++-------- 4 files changed, 163 insertions(+), 65 deletions(-) diff --git a/ChangeLog b/ChangeLog index fc9465b075..1030f59d09 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,25 @@ +2006-09-15 Wim Taymans + + * gst-libs/gst/audio/gstbaseaudiosink.c: + (gst_base_audio_sink_get_time), (gst_base_audio_sink_callback): + * gst-libs/gst/audio/gstbaseaudiosrc.c: + (gst_base_audio_src_get_time), (gst_base_audio_src_fixate), + (gst_base_audio_src_get_times), (gst_base_audio_src_get_offset), + (gst_base_audio_src_create), (gst_base_audio_src_change_state): + Do the delay calculation in the source/sink base classes as this is + specific for the capture/playback mode. + Try to fixate a bit better, like round depth up to a multiple of 8 + bigger than width. + Handle underruns correctly by marking DISCONT on buffers and adjusting + timestamps to handle the gap. + Set offset/offset_end correctly on buffers. + + * gst-libs/gst/audio/gstringbuffer.c: (gst_ring_buffer_pause), + (gst_ring_buffer_samples_done), (gst_ring_buffer_commit), + (gst_ring_buffer_read): + Remove resync and underrun recovery from the ringbuffer. + Fix ringbuffer read code on under/overrun. + 2006-09-15 Wim Taymans * gst/playback/gstplaybasebin.c: (gst_play_base_bin_class_init), diff --git a/gst-libs/gst/audio/gstbaseaudiosink.c b/gst-libs/gst/audio/gstbaseaudiosink.c index c946310579..d20ccb6e31 100644 --- a/gst-libs/gst/audio/gstbaseaudiosink.c +++ b/gst-libs/gst/audio/gstbaseaudiosink.c @@ -91,7 +91,7 @@ static void gst_base_audio_sink_get_times (GstBaseSink * bsink, static gboolean gst_base_audio_sink_setcaps (GstBaseSink * bsink, GstCaps * caps); -//static guint gst_base_audio_sink_signals[LAST_SIGNAL] = { 0 }; +/* static guint gst_base_audio_sink_signals[LAST_SIGNAL] = { 0 }; */ static void gst_base_audio_sink_base_init (gpointer g_class) @@ -217,18 +217,32 @@ clock_disabled: static GstClockTime gst_base_audio_sink_get_time (GstClock * clock, GstBaseAudioSink * sink) { - guint64 samples; + guint64 raw, samples; + guint delay; GstClockTime result; if (sink->ringbuffer == NULL || sink->ringbuffer->spec.rate == 0) return GST_CLOCK_TIME_NONE; /* our processed samples are always increasing */ - samples = gst_ring_buffer_samples_done (sink->ringbuffer); + raw = samples = gst_ring_buffer_samples_done (sink->ringbuffer); + + /* the number of samples not yet processed, this is still queued in the + * device (not played for playback). */ + delay = gst_ring_buffer_delay (sink->ringbuffer); + + if (G_LIKELY (samples >= delay)) + samples -= delay; + else + samples = 0; result = gst_util_uint64_scale_int (samples, GST_SECOND, sink->ringbuffer->spec.rate); + GST_DEBUG_OBJECT (sink, + "processed samples: raw %llu, delay %u, real %llu, time %" + GST_TIME_FORMAT, raw, delay, samples, GST_TIME_ARGS (result)); + return result; } @@ -707,7 +721,7 @@ static void gst_base_audio_sink_callback (GstRingBuffer * rbuf, guint8 * data, guint len, gpointer user_data) { - //GstBaseAudioSink *sink = GST_BASE_AUDIO_SINK (data); + /* GstBaseAudioSink *sink = GST_BASE_AUDIO_SINK (data); */ } /* should be called with the LOCK */ diff --git a/gst-libs/gst/audio/gstbaseaudiosrc.c b/gst-libs/gst/audio/gstbaseaudiosrc.c index d4275664a3..3e68901bdb 100644 --- a/gst-libs/gst/audio/gstbaseaudiosrc.c +++ b/gst-libs/gst/audio/gstbaseaudiosrc.c @@ -75,7 +75,7 @@ static void gst_base_audio_src_get_times (GstBaseSrc * bsrc, GstBuffer * buffer, GstClockTime * start, GstClockTime * end); static gboolean gst_base_audio_src_setcaps (GstBaseSrc * bsrc, GstCaps * caps); -//static guint gst_base_audio_src_signals[LAST_SIGNAL] = { 0 }; +/* static guint gst_base_audio_src_signals[LAST_SIGNAL] = { 0 }; */ static void gst_base_audio_src_base_init (gpointer g_class) @@ -201,17 +201,28 @@ wrong_state: static GstClockTime gst_base_audio_src_get_time (GstClock * clock, GstBaseAudioSrc * src) { - guint64 samples; + guint64 raw, samples; + guint delay; GstClockTime result; if (G_UNLIKELY (src->ringbuffer == NULL || src->ringbuffer->spec.rate == 0)) return GST_CLOCK_TIME_NONE; - samples = gst_ring_buffer_samples_done (src->ringbuffer); + raw = samples = gst_ring_buffer_samples_done (src->ringbuffer); + + /* the number of samples not yet processed, this is still queued in the + * device (not yet read for capture). */ + delay = gst_ring_buffer_delay (src->ringbuffer); + + samples += delay; result = gst_util_uint64_scale_int (samples, GST_SECOND, src->ringbuffer->spec.rate); + GST_DEBUG_OBJECT (src, + "processed samples: raw %llu, delay %u, real %llu, time %" + GST_TIME_FORMAT, raw, delay, samples, GST_TIME_ARGS (result)); + return result; } @@ -271,14 +282,24 @@ static void gst_base_audio_src_fixate (GstPad * pad, GstCaps * caps) { GstStructure *s; + gint width, depth; s = gst_caps_get_structure (caps, 0); + /* fields for all formats */ gst_structure_fixate_field_nearest_int (s, "rate", 44100); gst_structure_fixate_field_nearest_int (s, "channels", 2); - gst_structure_fixate_field_nearest_int (s, "depth", 16); gst_structure_fixate_field_nearest_int (s, "width", 16); - gst_structure_set (s, "signed", G_TYPE_BOOLEAN, TRUE, NULL); + + /* fields for int */ + if (gst_structure_has_field (s, "depth")) { + gst_structure_get_int (s, "width", &width); + /* round width to nearest multiple of 8 for the depth */ + depth = GST_ROUND_UP_8 (width); + gst_structure_fixate_field_nearest_int (s, "depth", depth); + } + if (gst_structure_has_field (s, "signed")) + gst_structure_fixate_field_boolean (s, "signed", TRUE); if (gst_structure_has_field (s, "endianness")) gst_structure_fixate_field_nearest_int (s, "endianness", G_BYTE_ORDER); } @@ -341,9 +362,8 @@ static void gst_base_audio_src_get_times (GstBaseSrc * bsrc, GstBuffer * buffer, GstClockTime * start, GstClockTime * end) { - /* ne need to sync to a clock here, we schedule the samples based - * on our own clock for the moment. FIXME, implement this when - * we are not using our own clock */ + /* no need to sync to a clock here, we schedule the samples based + * on our own clock for the moment. */ *start = GST_CLOCK_TIME_NONE; *end = GST_CLOCK_TIME_NONE; } @@ -369,6 +389,45 @@ gst_base_audio_src_event (GstBaseSrc * bsrc, GstEvent * event) return TRUE; } +/* get the next offset in the ringbuffer for reading samples. + * If the next sample is too far away, this function will position itself to the + * next most recent sample, creating discontinuity */ +static guint64 +gst_base_audio_src_get_offset (GstBaseAudioSrc * src) +{ + guint64 sample; + gint readseg, segdone, segtotal, sps; + gint diff; + + /* assume we can append to the previous sample */ + sample = src->next_sample; + /* no previous sample, try to read from position 0 */ + if (sample == -1) + sample = 0; + + sps = src->ringbuffer->samples_per_seg; + segtotal = src->ringbuffer->spec.segtotal; + + /* figure out the segment and the offset inside the segment where + * the sample should be read from. */ + readseg = sample / sps; + + /* get the currently processed segment */ + segdone = g_atomic_int_get (&src->ringbuffer->segdone) + - src->ringbuffer->segbase; + + /* see how far away it is from the read segment, normally segdone (where new + * data is written in the ringbuffer) is bigger than readseg (where we are + * reading). */ + diff = segdone - readseg; + if (diff >= segtotal) { + /* sample would be dropped, position to next playable position */ + sample = (segdone - segtotal + 1) * sps; + } + + return sample; +} + static GstFlowReturn gst_base_audio_src_create (GstBaseSrc * bsrc, guint64 offset, guint length, GstBuffer ** outbuf) @@ -396,14 +455,17 @@ gst_base_audio_src_create (GstBaseSrc * bsrc, guint64 offset, guint length, /* make sure we round down to an integral number of samples */ length -= length % bps; - /* calculate the sequentially next sample we need to read */ - sample = (src->next_sample != -1 ? src->next_sample : 0); - + /* figure out the offset in the ringbuffer */ if (G_UNLIKELY (offset != -1)) { - /* if a specific offset was given it must be the next - * sequential offset we expect or we fail. */ - if (offset / bps != sample) + sample = offset / bps; + /* if a specific offset was given it must be the next sequential + * offset we expect or we fail for now. */ + if (src->next_sample != -1 && sample != src->next_sample) goto wrong_offset; + } else { + /* calculate the sequentially next sample we need to read. This can jump and + * create a DISCONT. */ + sample = gst_base_audio_src_get_offset (src); } /* get the number of samples to read */ @@ -413,10 +475,19 @@ gst_base_audio_src_create (GstBaseSrc * bsrc, guint64 offset, guint length, buf = gst_buffer_new_and_alloc (length); data = GST_BUFFER_DATA (buf); + /* read the sample */ res = gst_ring_buffer_read (ringbuffer, sample, data, samples); if (G_UNLIKELY (res == -1)) goto stopped; + /* mark discontinuity if needed */ + if (G_UNLIKELY (sample != src->next_sample) && src->next_sample != -1) { + GST_WARNING_OBJECT (src, + "create DISCONT of %" G_GUINT64_FORMAT " samples at sample %" + G_GUINT64_FORMAT, sample - src->next_sample, sample); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + } + /* FIXME, we timestamp against our own clock, also handle the case * where we are slaved to another clock. We currently refuse to accept * any other clock than the one we provide, so this code is fine for @@ -426,6 +497,8 @@ gst_base_audio_src_create (GstBaseSrc * bsrc, guint64 offset, guint length, src->next_sample = sample + samples; GST_BUFFER_DURATION (buf) = gst_util_uint64_scale_int (src->next_sample, GST_SECOND, ringbuffer->spec.rate) - GST_BUFFER_TIMESTAMP (buf); + GST_BUFFER_OFFSET (buf) = sample; + GST_BUFFER_OFFSET_END (buf) = sample + samples; gst_buffer_set_caps (buf, GST_PAD_CAPS (GST_BASE_SRC_PAD (bsrc))); @@ -470,13 +543,6 @@ gst_base_audio_src_create_ringbuffer (GstBaseAudioSrc * src) return buffer; } -void -gst_base_audio_src_callback (GstRingBuffer * rbuf, guint8 * data, guint len, - gpointer user_data) -{ - //GstBaseAudioSrc *src = GST_BASE_AUDIO_SRC (data); -} - static GstStateChangeReturn gst_base_audio_src_change_state (GstElement * element, GstStateChange transition) @@ -488,12 +554,10 @@ gst_base_audio_src_change_state (GstElement * element, case GST_STATE_CHANGE_NULL_TO_READY: if (src->ringbuffer == NULL) { src->ringbuffer = gst_base_audio_src_create_ringbuffer (src); - gst_ring_buffer_set_callback (src->ringbuffer, - gst_base_audio_src_callback, src); } if (!gst_ring_buffer_open_device (src->ringbuffer)) return GST_STATE_CHANGE_FAILURE; - src->next_sample = 0; + src->next_sample = -1; break; case GST_STATE_CHANGE_READY_TO_PAUSED: gst_ring_buffer_set_flushing (src->ringbuffer, FALSE); @@ -515,7 +579,7 @@ gst_base_audio_src_change_state (GstElement * element, case GST_STATE_CHANGE_PAUSED_TO_READY: gst_ring_buffer_set_flushing (src->ringbuffer, TRUE); gst_ring_buffer_release (src->ringbuffer); - src->next_sample = 0; + src->next_sample = -1; break; case GST_STATE_CHANGE_READY_TO_NULL: gst_ring_buffer_close_device (src->ringbuffer); diff --git a/gst-libs/gst/audio/gstringbuffer.c b/gst-libs/gst/audio/gstringbuffer.c index ee5ad71efa..11ee3201f0 100644 --- a/gst-libs/gst/audio/gstringbuffer.c +++ b/gst-libs/gst/audio/gstringbuffer.c @@ -920,6 +920,7 @@ gst_ring_buffer_pause (GstRingBuffer * buf) return res; + /* ERRORS */ flushing: { GST_OBJECT_UNLOCK (buf); @@ -989,6 +990,12 @@ done: * implementation uses another internal buffer between the audio * device. * + * For playback ringbuffers this is the amount of samples transfered from the + * ringbuffer to the device but still not played. + * + * For capture ringbuffers this is the amount of samples in the device that are + * not yet transfered to the ringbuffer. + * * Returns: The number of samples queued in the audio device. * * MT safe. @@ -1020,7 +1027,8 @@ done: * @buf: the #GstRingBuffer to query * * Get the number of samples that were processed by the ringbuffer - * since it was last started. + * since it was last started. This does not include the number of samples not + * yet processed (see gst_ring_buffer_delay()). * * Returns: The number of samples processed by the ringbuffer. * @@ -1038,18 +1046,8 @@ gst_ring_buffer_samples_done (GstRingBuffer * buf) /* get the amount of segments we processed */ segdone = g_atomic_int_get (&buf->segdone); - /* and the number of samples not yet processed */ - delay = gst_ring_buffer_delay (buf); - - raw = samples = ((guint64) segdone) * buf->samples_per_seg; - - if (G_LIKELY (samples >= delay)) - samples -= delay; - else - samples = 0; - - GST_DEBUG_OBJECT (buf, "processed samples: raw %llu, delay %u, real %llu", - raw, delay, samples); + /* convert to samples */ + samples = ((guint64) segdone) * buf->samples_per_seg; return samples; } @@ -1237,9 +1235,10 @@ gst_ring_buffer_commit (GstRingBuffer * buf, guint64 sample, guchar * data, GST_DEBUG ("pointer at %d, sample %llu, write to %d-%d, to_write %d, diff %d, segtotal %d, segsize %d", - segdone, sample, writeseg, sampleoff, to_write, diff, segtotal, sps); + segdone, sample, writeseg, sampleoff, to_write, diff, segtotal, + segsize); - /* segment too far ahead, we need to drop, hopefully UNLIKELY */ + /* segment too far ahead, writer too slow, we need to drop, hopefully UNLIKELY */ if (G_UNLIKELY (diff < 0)) { /* we need to drop one segment at a time, pretend we wrote a * segment. */ @@ -1310,6 +1309,7 @@ gst_ring_buffer_read (GstRingBuffer * buf, guint64 sample, guchar * data, gint segdone; gint segsize, segtotal, bps, sps; guint8 *dest; + guint to_read; g_return_val_if_fail (GST_IS_RING_BUFFER (buf), -1); g_return_val_if_fail (buf->data != NULL, -1); @@ -1321,13 +1321,14 @@ gst_ring_buffer_read (GstRingBuffer * buf, guint64 sample, guchar * data, bps = buf->spec.bytes_per_sample; sps = buf->samples_per_seg; + to_read = len; /* read enough samples */ - while (len > 0) { + while (to_read > 0) { gint sampleslen; gint readseg, sampleoff; /* figure out the segment and the offset inside the segment where - * the sample should be written. */ + * the sample should be read from. */ readseg = sample / sps; sampleoff = (sample % sps); @@ -1337,33 +1338,29 @@ gst_ring_buffer_read (GstRingBuffer * buf, guint64 sample, guchar * data, /* get the currently processed segment */ segdone = g_atomic_int_get (&buf->segdone) - buf->segbase; - /* see how far away it is from the read segment */ + /* see how far away it is from the read segment, normally segdone (where + * the hardware is writing) is bigger than readseg (where software is + * reading) */ diff = segdone - readseg; GST_DEBUG - ("pointer at %d, sample %llu, read from %d-%d, len %d, diff %d, segtotal %d, segsize %d", - segdone, sample, readseg, sampleoff, len, diff, segtotal, segsize); + ("pointer at %d, sample %llu, read from %d-%d, to_read %d, diff %d, segtotal %d, segsize %d", + segdone, sample, readseg, sampleoff, to_read, diff, segtotal, + segsize); - /* segment too far ahead, we need to drop */ - if (diff < 0) { - /* we need to drop one segment at a time, pretend we read an - * empty segment. */ - sampleslen = MIN (sps, len); + /* segment too far ahead, reader too slow */ + if (G_UNLIKELY (diff >= segtotal)) { + /* pretend we read an empty segment. */ + sampleslen = MIN (sps, to_read); memcpy (data, buf->empty_seg, sampleslen * bps); goto next; } /* read segment is within readable range, we can break the loop and * start reading the data. */ - if (diff > 0 && diff < segtotal) + if (diff > 0) break; - /* flush if diff has grown bigger than ringbuffer */ - if (diff >= segtotal) { - gst_ring_buffer_clear_all (buf); - buf->segdone = readseg; - } - /* else we need to wait for the segment to become readable. */ if (!wait_segment (buf)) goto not_started; @@ -1371,26 +1368,27 @@ gst_ring_buffer_read (GstRingBuffer * buf, guint64 sample, guchar * data, /* we can read now */ readseg = readseg % segtotal; - sampleslen = MIN (sps - sampleoff, len); + sampleslen = MIN (sps - sampleoff, to_read); - GST_DEBUG_OBJECT (buf, "read @%p seg %d, off %d, len %d", + GST_DEBUG_OBJECT (buf, "read @%p seg %d, off %d, sampleslen %d", dest + readseg * segsize, readseg, sampleoff, sampleslen); memcpy (data, dest + (readseg * segsize) + (sampleoff * bps), (sampleslen * bps)); next: - len -= sampleslen; + to_read -= sampleslen; sample += sampleslen; data += sampleslen * bps; } - return len; + return len - to_read; /* ERRORS */ not_started: { GST_DEBUG_OBJECT (buf, "stopped processing"); + /* FIXME, return len - to_read after fixing caller */ return -1; } }