2006-09-15 Wim Taymans <wim@fluendo.com>
+ * 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 <wim@fluendo.com>
+
* gst/playback/gstplaybasebin.c: (gst_play_base_bin_class_init),
(gst_play_base_bin_init), (fill_buffer), (check_queue),
(queue_threshold_reached), (gst_play_base_bin_set_property),
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)
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;
}
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 */
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)
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;
}
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);
}
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;
}
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)
/* 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 */
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
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)));
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)
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);
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);
return res;
+ /* ERRORS */
flushing:
{
GST_OBJECT_UNLOCK (buf);
* 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.
* @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.
*
/* 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;
}
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. */
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);
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);
/* 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);
-
- /* 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);
+ ("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, 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;
/* 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;
}
}