static GstClock * gst_base_audio_src_provide_clock (GstElement * elem) { GstBaseAudioSrc *src; GstClock *clock; src = GST_BASE_AUDIO_SRC (elem); /* we have no ringbuffer (must be NULL state) */ if (src->ringbuffer == NULL) goto wrong_state; if (!gst_ring_buffer_is_acquired (src->ringbuffer)) goto wrong_state; GST_OBJECT_LOCK (src); if (!src->priv->provide_clock) goto clock_disabled; clock = GST_CLOCK_CAST (gst_object_ref (src->clock)); GST_OBJECT_UNLOCK (src); return clock; /* ERRORS */ wrong_state: { GST_DEBUG_OBJECT (src, "ringbuffer not acquired"); return NULL; } clock_disabled: { GST_DEBUG_OBJECT (src, "clock provide disabled"); GST_OBJECT_UNLOCK (src); return NULL; } }
static GstFlowReturn gst_audio_ringbuffer_render (GstAudioRingbuffer * ringbuffer, GstBuffer * buf) { GstRingBuffer *rbuf; gint bps, accum; guint size; guint samples, written, out_samples; gint64 diff, align, ctime, cstop; guint8 *data; guint64 in_offset; GstClockTime time, stop, render_start, render_stop, sample_offset; gboolean align_next; rbuf = ringbuffer->buffer; /* can't do anything when we don't have the device */ if (G_UNLIKELY (!gst_ring_buffer_is_acquired (rbuf))) goto wrong_state; bps = rbuf->spec.bytes_per_sample; size = GST_BUFFER_SIZE (buf); if (G_UNLIKELY (size % bps) != 0) goto wrong_size; samples = size / bps; out_samples = samples; in_offset = GST_BUFFER_OFFSET (buf); time = GST_BUFFER_TIMESTAMP (buf); GST_DEBUG_OBJECT (ringbuffer, "time %" GST_TIME_FORMAT ", offset %llu, start %" GST_TIME_FORMAT ", samples %u", GST_TIME_ARGS (time), in_offset, GST_TIME_ARGS (ringbuffer->sink_segment.start), samples); data = GST_BUFFER_DATA (buf); stop = time + gst_util_uint64_scale_int (samples, GST_SECOND, rbuf->spec.rate); if (!gst_segment_clip (&ringbuffer->sink_segment, GST_FORMAT_TIME, time, stop, &ctime, &cstop)) goto out_of_segment; /* see if some clipping happened */ diff = ctime - time; if (diff > 0) { /* bring clipped time to samples */ diff = gst_util_uint64_scale_int (diff, rbuf->spec.rate, GST_SECOND); GST_DEBUG_OBJECT (ringbuffer, "clipping start to %" GST_TIME_FORMAT " %" G_GUINT64_FORMAT " samples", GST_TIME_ARGS (ctime), diff); samples -= diff; data += diff * bps; time = ctime; } diff = stop - cstop; if (diff > 0) { /* bring clipped time to samples */ diff = gst_util_uint64_scale_int (diff, rbuf->spec.rate, GST_SECOND); GST_DEBUG_OBJECT (ringbuffer, "clipping stop to %" GST_TIME_FORMAT " %" G_GUINT64_FORMAT " samples", GST_TIME_ARGS (cstop), diff); samples -= diff; stop = cstop; } /* bring buffer start and stop times to running time */ render_start = gst_segment_to_running_time (&ringbuffer->sink_segment, GST_FORMAT_TIME, time); render_stop = gst_segment_to_running_time (&ringbuffer->sink_segment, GST_FORMAT_TIME, stop); GST_DEBUG_OBJECT (ringbuffer, "running: start %" GST_TIME_FORMAT " - stop %" GST_TIME_FORMAT, GST_TIME_ARGS (render_start), GST_TIME_ARGS (render_stop)); /* and bring the time to the rate corrected offset in the buffer */ render_start = gst_util_uint64_scale_int (render_start, rbuf->spec.rate, GST_SECOND); render_stop = gst_util_uint64_scale_int (render_stop, rbuf->spec.rate, GST_SECOND); /* positive playback rate, first sample is render_start, negative rate, first * sample is render_stop. When no rate conversion is active, render exactly * the amount of input samples to avoid aligning to rounding errors. */ if (ringbuffer->sink_segment.rate >= 0.0) { sample_offset = render_start; if (ringbuffer->sink_segment.rate == 1.0) render_stop = sample_offset + samples; } else { sample_offset = render_stop; if (ringbuffer->sink_segment.rate == -1.0) render_start = sample_offset + samples; } /* always resync after a discont */ if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT))) { GST_DEBUG_OBJECT (ringbuffer, "resync after discont"); goto no_align; } /* resync when we don't know what to align the sample with */ if (G_UNLIKELY (ringbuffer->next_sample == -1)) { GST_DEBUG_OBJECT (ringbuffer, "no align possible: no previous sample position known"); goto no_align; } /* now try to align the sample to the previous one, first see how big the * difference is. */ if (sample_offset >= ringbuffer->next_sample) diff = sample_offset - ringbuffer->next_sample; else diff = ringbuffer->next_sample - sample_offset; /* we tollerate half a second diff before we start resyncing. This * should be enough to compensate for various rounding errors in the timestamp * and sample offset position. We always resync if we got a discont anyway and * non-discont should be aligned by definition. */ if (G_LIKELY (diff < rbuf->spec.rate / DIFF_TOLERANCE)) { /* calc align with previous sample */ align = ringbuffer->next_sample - sample_offset; GST_DEBUG_OBJECT (ringbuffer, "align with prev sample, ABS (%" G_GINT64_FORMAT ") < %d", align, rbuf->spec.rate / DIFF_TOLERANCE); } else { /* bring sample diff to seconds for error message */ diff = gst_util_uint64_scale_int (diff, GST_SECOND, rbuf->spec.rate); /* timestamps drifted apart from previous samples too much, we need to * resync. We log this as an element warning. */ GST_ELEMENT_WARNING (ringbuffer, CORE, CLOCK, ("Compensating for audio synchronisation problems"), ("Unexpected discontinuity in audio timestamps of more " "than half a second (%" GST_TIME_FORMAT "), resyncing", GST_TIME_ARGS (diff))); align = 0; } ringbuffer->last_align = align; /* apply alignment */ render_start += align; render_stop += align; no_align: /* number of target samples is difference between start and stop */ out_samples = render_stop - render_start; /* we render the first or last sample first, depending on the rate */ if (ringbuffer->sink_segment.rate >= 0.0) sample_offset = render_start; else sample_offset = render_stop; GST_DEBUG_OBJECT (ringbuffer, "rendering at %" G_GUINT64_FORMAT " %d/%d", sample_offset, samples, out_samples); /* we need to accumulate over different runs for when we get interrupted */ accum = 0; align_next = TRUE; do { written = gst_ring_buffer_commit_full (rbuf, &sample_offset, data, samples, out_samples, &accum); GST_DEBUG_OBJECT (ringbuffer, "wrote %u of %u", written, samples); /* if we wrote all, we're done */ if (written == samples) break; GST_OBJECT_LOCK (ringbuffer); if (ringbuffer->flushing) goto flushing; GST_OBJECT_UNLOCK (ringbuffer); /* if we got interrupted, we cannot assume that the next sample should * be aligned to this one */ align_next = FALSE; samples -= written; data += written * bps; } while (TRUE); if (align_next) ringbuffer->next_sample = sample_offset; else ringbuffer->next_sample = -1; GST_DEBUG_OBJECT (ringbuffer, "next sample expected at %" G_GUINT64_FORMAT, ringbuffer->next_sample); if (GST_CLOCK_TIME_IS_VALID (stop) && stop >= ringbuffer->sink_segment.stop) { GST_DEBUG_OBJECT (ringbuffer, "start playback because we are at the end of segment"); gst_ring_buffer_start (rbuf); } return GST_FLOW_OK; /* SPECIAL cases */ out_of_segment: { GST_DEBUG_OBJECT (ringbuffer, "dropping sample out of segment time %" GST_TIME_FORMAT ", start %" GST_TIME_FORMAT, GST_TIME_ARGS (time), GST_TIME_ARGS (ringbuffer->sink_segment.start)); return GST_FLOW_OK; } /* ERRORS */ wrong_state: { GST_DEBUG_OBJECT (ringbuffer, "ringbuffer not negotiated"); GST_ELEMENT_ERROR (ringbuffer, STREAM, FORMAT, (NULL), ("ringbuffer not negotiated.")); return GST_FLOW_NOT_NEGOTIATED; } wrong_size: { GST_DEBUG_OBJECT (ringbuffer, "wrong size"); GST_ELEMENT_ERROR (ringbuffer, STREAM, WRONG_TYPE, (NULL), ("ringbuffer received buffer of wrong size.")); return GST_FLOW_ERROR; } flushing: { GST_DEBUG_OBJECT (ringbuffer, "ringbuffer is flushing"); GST_OBJECT_UNLOCK (ringbuffer); return GST_FLOW_WRONG_STATE; } }
static GstFlowReturn gst_base_audio_src_create (GstBaseSrc * bsrc, guint64 offset, guint length, GstBuffer ** outbuf) { GstBaseAudioSrc *src = GST_BASE_AUDIO_SRC (bsrc); GstBuffer *buf; guchar *data; guint samples, total_samples; guint64 sample; gint bps; GstRingBuffer *ringbuffer; GstRingBufferSpec *spec; guint read; GstClockTime timestamp, duration; GstClock *clock; ringbuffer = src->ringbuffer; spec = &ringbuffer->spec; if (G_UNLIKELY (!gst_ring_buffer_is_acquired (ringbuffer))) goto wrong_state; bps = spec->bytes_per_sample; if ((length == 0 && bsrc->blocksize == 0) || length == -1) /* no length given, use the default segment size */ length = spec->segsize; else /* make sure we round down to an integral number of samples */ length -= length % bps; /* figure out the offset in the ringbuffer */ if (G_UNLIKELY (offset != -1)) { 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); } GST_DEBUG_OBJECT (src, "reading from sample %" G_GUINT64_FORMAT, sample); /* get the number of samples to read */ total_samples = samples = length / bps; /* FIXME, using a bufferpool would be nice here */ buf = gst_buffer_new_and_alloc (length); data = GST_BUFFER_DATA (buf); do { read = gst_ring_buffer_read (ringbuffer, sample, data, samples); GST_DEBUG_OBJECT (src, "read %u of %u", read, samples); /* if we read all, we're done */ if (read == samples) break; /* else something interrupted us and we wait for playing again. */ GST_DEBUG_OBJECT (src, "wait playing"); if (gst_base_src_wait_playing (bsrc) != GST_FLOW_OK) goto stopped; GST_DEBUG_OBJECT (src, "continue playing"); /* read next samples */ sample += read; samples -= read; data += read * bps; } while (TRUE); /* 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_ELEMENT_WARNING (src, CORE, CLOCK, (_("Can't record audio fast enough")), ("dropped %" G_GUINT64_FORMAT " samples", sample - src->next_sample)); GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); } src->next_sample = sample + samples; /* get the normal timestamp to get the duration. */ timestamp = gst_util_uint64_scale_int (sample, GST_SECOND, spec->rate); duration = gst_util_uint64_scale_int (src->next_sample, GST_SECOND, spec->rate) - timestamp; GST_OBJECT_LOCK (src); if (!(clock = GST_ELEMENT_CLOCK (src))) goto no_sync; if (clock != src->clock) { /* we are slaved, check how to handle this */ switch (src->priv->slave_method) { case GST_BASE_AUDIO_SRC_SLAVE_RESAMPLE: /* not implemented, use skew algorithm. This algorithm should * work on the readout pointer and produces more or less samples based * on the clock drift */ case GST_BASE_AUDIO_SRC_SLAVE_SKEW: { GstClockTime running_time; GstClockTime base_time; GstClockTime current_time; guint64 running_time_sample; gint running_time_segment; gint current_segment; gint segment_skew; gint sps; /* samples per segment */ sps = ringbuffer->samples_per_seg; /* get the current time */ current_time = gst_clock_get_time (clock); /* get the basetime */ base_time = GST_ELEMENT_CAST (src)->base_time; /* get the running_time */ running_time = current_time - base_time; /* the running_time converted to a sample (relative to the ringbuffer) */ running_time_sample = gst_util_uint64_scale_int (running_time, spec->rate, GST_SECOND); /* the segmentnr corrensponding to running_time, round down */ running_time_segment = running_time_sample / sps; /* the segment currently read from the ringbuffer */ current_segment = sample / sps; /* the skew we have between running_time and the ringbuffertime */ segment_skew = running_time_segment - current_segment; GST_DEBUG_OBJECT (bsrc, "\n running_time = %" GST_TIME_FORMAT "\n timestamp = %" GST_TIME_FORMAT "\n running_time_segment = %d" "\n current_segment = %d" "\n segment_skew = %d", GST_TIME_ARGS (running_time), GST_TIME_ARGS (timestamp), running_time_segment, current_segment, segment_skew); /* Resync the ringbuffer if: * 1. We get one segment into the future. * This is clearly a lie, because we can't * possibly have a buffer with timestamp 1 at * time 0. (unless it has time-travelled...) * * 2. We are more than the length of the ringbuffer behind. * The length of the ringbuffer then gets to dictate * the threshold for what is concidered "too late" * * 3. If this is our first buffer. * We know that we should catch up to running_time * the first time we are ran. */ if ((segment_skew < 0) || (segment_skew >= ringbuffer->spec.segtotal) || (current_segment == 0)) { gint segments_written; gint first_segment; gint last_segment; gint new_last_segment; gint segment_diff; gint new_first_segment; guint64 new_sample; /* we are going to say that the last segment was captured at the current time (running_time), minus one segment of creation-latency in the ringbuffer. This can be thought of as: The segment arrived in the ringbuffer at time X, and that means it was created at time X - (one segment). */ new_last_segment = running_time_segment - 1; /* for better readablity */ first_segment = current_segment; /* get the amount of segments written from the device by now */ segments_written = g_atomic_int_get (&ringbuffer->segdone); /* subtract the base to segments_written to get the number of the last written segment in the ringbuffer (one segment written = segment 0) */ last_segment = segments_written - ringbuffer->segbase - 1; /* we see how many segments the ringbuffer was timeshifted */ segment_diff = new_last_segment - last_segment; /* we move the first segment an equal amount */ new_first_segment = first_segment + segment_diff; /* and we also move the segmentbase the same amount */ ringbuffer->segbase -= segment_diff; /* we calculate the new sample value */ new_sample = ((guint64) new_first_segment) * sps; /* and get the relative time to this -> our new timestamp */ timestamp = gst_util_uint64_scale_int (new_sample, GST_SECOND, spec->rate); /* we update the next sample accordingly */ src->next_sample = new_sample + samples; GST_DEBUG_OBJECT (bsrc, "Timeshifted the ringbuffer with %d segments: " "Updating the timestamp to %" GST_TIME_FORMAT ", " "and src->next_sample to %" G_GUINT64_FORMAT, segment_diff, GST_TIME_ARGS (timestamp), src->next_sample); } break; } case GST_BASE_AUDIO_SRC_SLAVE_RETIMESTAMP: { GstClockTime base_time, latency; /* We are slaved to another clock, take running time of the pipeline clock and * timestamp against it. Somebody else in the pipeline should figure out the * clock drift. We keep the duration we calculated above. */ timestamp = gst_clock_get_time (clock); base_time = GST_ELEMENT_CAST (src)->base_time; if (timestamp > base_time) timestamp -= base_time; else timestamp = 0; /* subtract latency */ latency = gst_util_uint64_scale_int (total_samples, GST_SECOND, spec->rate); if (timestamp > latency) timestamp -= latency; else timestamp = 0; } case GST_BASE_AUDIO_SRC_SLAVE_NONE: break; } } else { GstClockTime base_time; /* we are not slaved, subtract base_time */ base_time = GST_ELEMENT_CAST (src)->base_time; if (timestamp > base_time) timestamp -= base_time; else timestamp = 0; } no_sync: GST_OBJECT_UNLOCK (src); GST_BUFFER_TIMESTAMP (buf) = timestamp; GST_BUFFER_DURATION (buf) = duration; GST_BUFFER_OFFSET (buf) = sample; GST_BUFFER_OFFSET_END (buf) = sample + samples; *outbuf = buf; return GST_FLOW_OK; /* ERRORS */ wrong_state: { GST_DEBUG_OBJECT (src, "ringbuffer in wrong state"); return GST_FLOW_WRONG_STATE; } wrong_offset: { GST_ELEMENT_ERROR (src, RESOURCE, SEEK, (NULL), ("resource can only be operated on sequentially but offset %" G_GUINT64_FORMAT " was given", offset)); return GST_FLOW_ERROR; } stopped: { gst_buffer_unref (buf); GST_DEBUG_OBJECT (src, "ringbuffer stopped"); return GST_FLOW_WRONG_STATE; } }