guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_alsasrc_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
-
+static GstStateChangeReturn gst_alsasrc_change_state (GstElement * element,
+ GstStateChange transition);
static GstCaps *gst_alsasrc_getcaps (GstBaseSrc * bsrc, GstCaps * filter);
static gboolean gst_alsasrc_open (GstAudioSrc * asrc);
GstAudioRingBufferSpec * spec);
static gboolean gst_alsasrc_unprepare (GstAudioSrc * asrc);
static gboolean gst_alsasrc_close (GstAudioSrc * asrc);
-static guint gst_alsasrc_read (GstAudioSrc * asrc, gpointer data, guint length);
+static guint gst_alsasrc_read
+ (GstAudioSrc * asrc, gpointer data, guint length, GstClockTime * timestamp);
static guint gst_alsasrc_delay (GstAudioSrc * asrc);
static void gst_alsasrc_reset (GstAudioSrc * asrc);
gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_alsasrc_read);
gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_alsasrc_delay);
gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_alsasrc_reset);
+ gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_alsasrc_change_state);
g_object_class_install_property (gobject_class, PROP_DEVICE,
g_param_spec_string ("device", "Device",
}
}
+static GstStateChangeReturn
+gst_alsasrc_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+ GstAudioBaseSrc *src = GST_AUDIO_BASE_SRC (element);
+ GstAlsaSrc *alsa = GST_ALSA_SRC (element);
+ GstClock *clk;
+
+ switch (transition) {
+ /* show the compiler that we care */
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ break;
+
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ clk = src->clock;
+ alsa->driver_timestamps = FALSE;
+ if (GST_IS_SYSTEM_CLOCK (clk)) {
+ gint clocktype;
+ g_object_get (clk, "clock-type", &clocktype, NULL);
+ if (clocktype == GST_CLOCK_TYPE_MONOTONIC) {
+ GST_INFO ("Using driver timestamps !");
+ alsa->driver_timestamps = TRUE;
+ }
+ }
+ break;
+ }
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ return ret;
+}
+
static void
gst_alsasrc_init (GstAlsaSrc * alsasrc)
{
alsasrc->device = g_strdup (DEFAULT_PROP_DEVICE);
alsasrc->cached_caps = NULL;
+ alsasrc->driver_timestamps = FALSE;
g_mutex_init (&alsasrc->alsa_lock);
}
/* start the transfer on first read */
CHECK (snd_pcm_sw_params_set_start_threshold (alsa->handle, params,
0), start_threshold);
+ /* use monotonic timestamping */
+ CHECK (snd_pcm_sw_params_set_tstamp_mode (alsa->handle, params,
+ SND_PCM_TSTAMP_MMAP), tstamp_mode);
#if GST_CHECK_ALSA_VERSION(1,0,16)
/* snd_pcm_sw_params_set_xfer_align() is deprecated, alignment is always 1 */
snd_pcm_sw_params_free (params);
return err;
}
+tstamp_mode:
+ {
+ GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL),
+ ("Unable to set tstamp mode for playback: %s", snd_strerror (err)));
+ snd_pcm_sw_params_free (params);
+ return err;
+ }
#if !GST_CHECK_ALSA_VERSION(1,0,16)
set_align:
{
alsa = GST_ALSA_SRC (asrc);
CHECK (snd_pcm_open (&alsa->handle, alsa->device, SND_PCM_STREAM_CAPTURE,
- SND_PCM_NONBLOCK), open_error);
+ (alsa->driver_timestamps == TRUE) ? 0 : SND_PCM_NONBLOCK),
+ open_error);
return TRUE;
return err;
}
+static GstClockTime
+gst_alsasrc_get_timestamp (GstAlsaSrc * asrc)
+{
+ snd_pcm_status_t *status;
+ snd_htimestamp_t tstamp;
+ GstClockTime timestamp;
+ snd_pcm_uframes_t avail;
+ gint err = -EPIPE;
+
+ if (G_UNLIKELY (!asrc)) {
+ GST_ERROR_OBJECT (asrc, "No alsa handle created yet !");
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ if (G_UNLIKELY (snd_pcm_status_malloc (&status) != 0)) {
+ GST_ERROR_OBJECT (asrc, "snd_pcm_status_malloc failed");
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ if (G_UNLIKELY (snd_pcm_status (asrc->handle, status) != 0)) {
+ GST_ERROR_OBJECT (asrc, "snd_pcm_status failed");
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ /* in case an xrun condition has occured we need to handle this */
+ if (snd_pcm_status_get_state (status) != SND_PCM_STATE_RUNNING) {
+ if (xrun_recovery (asrc, asrc->handle, err) < 0) {
+ GST_WARNING_OBJECT (asrc, "Could not recover from xrun condition !");
+ }
+ /* reload the status alsa status object, since recovery made it invalid */
+ if (G_UNLIKELY (snd_pcm_status (asrc->handle, status) != 0)) {
+ GST_ERROR_OBJECT (asrc, "snd_pcm_status failed");
+ }
+ }
+
+ /* get high resolution time stamp from driver */
+ snd_pcm_status_get_htstamp (status, &tstamp);
+ timestamp = GST_TIMESPEC_TO_TIME (tstamp);
+
+ /* max available frames sets the depth of the buffer */
+ avail = snd_pcm_status_get_avail (status);
+
+ /* calculate the timestamp of the next sample to be read */
+ timestamp -= gst_util_uint64_scale_int (avail, GST_SECOND, asrc->rate);
+
+ /* compensate for the fact that we really need the timestamp of the
+ * previously read data segment */
+ timestamp -= asrc->period_time * 1000;
+
+ snd_pcm_status_free (status);
+
+ GST_LOG_OBJECT (asrc, "ALSA timestamp : %" GST_TIME_FORMAT
+ ", delay %lu", GST_TIME_ARGS (timestamp), avail);
+
+ return timestamp;
+}
+
static guint
-gst_alsasrc_read (GstAudioSrc * asrc, gpointer data, guint length)
+gst_alsasrc_read (GstAudioSrc * asrc, gpointer data, guint length,
+ GstClockTime * timestamp)
{
GstAlsaSrc *alsa;
gint err;
}
GST_ALSA_SRC_UNLOCK (asrc);
+ /* if driver timestamps are enabled we need to return this here */
+ if (alsa->driver_timestamps && timestamp)
+ *timestamp = gst_alsasrc_get_timestamp (alsa);
+
return length - (cptr * alsa->bpf);
read_error:
guint channels;
gint bpf;
gboolean driver_timestamps;
- GstClockTime first_alsa_ts;
guint buffer_time;
guint period_time;
GstAudioRingBufferSpec *spec;
guint read;
GstClockTime timestamp, duration;
+ GstClockTime rb_timestamp = GST_CLOCK_TIME_NONE;
GstClock *clock;
+ gboolean first;
ringbuffer = src->ringbuffer;
spec = &ringbuffer->spec;
gst_buffer_map (buf, &info, GST_MAP_WRITE);
ptr = info.data;
+ first = TRUE;
do {
- read = gst_audio_ring_buffer_read (ringbuffer, sample, ptr, samples);
+ GstClockTime tmp_ts;
+
+ read =
+ gst_audio_ring_buffer_read (ringbuffer, sample, ptr, samples, &tmp_ts);
+ if (first && GST_CLOCK_TIME_IS_VALID (tmp_ts)) {
+ first = FALSE;
+ rb_timestamp = tmp_ts;
+ }
GST_DEBUG_OBJECT (src, "read %u of %u", read, samples);
/* if we read all, we're done */
if (read == samples)
} else {
GstClockTime base_time;
- /* to get the timestamp against the clock we also need to add our offset */
- timestamp = gst_audio_clock_adjust (clock, timestamp);
+ if (GST_CLOCK_TIME_IS_VALID (rb_timestamp)) {
+ /* the read method returned a timestamp so we use this instead */
+ timestamp = rb_timestamp;
+ } else {
+ /* to get the timestamp against the clock we also need to add our offset */
+ timestamp = gst_audio_clock_adjust (clock, timestamp);
+ }
/* we are not slaved, subtract base_time */
base_time = GST_ELEMENT_CAST (src)->base_time;
*outbuf = buf;
+ GST_LOG_OBJECT (src, "Pushed buffer timestamp %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
+
return GST_FLOW_OK;
/* ERRORS */
{
gboolean res = FALSE;
GstAudioRingBufferClass *rclass;
- gint segsize, bpf;
+ gint segsize, bpf, i;
g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE);
if (G_UNLIKELY (!res))
goto acquire_failed;
+ GST_INFO_OBJECT (buf, "Allocating an array for %d timestamps",
+ spec->segtotal);
+ buf->timestamps = g_slice_alloc0 (sizeof (GstClockTime) * spec->segtotal);
+ /* initialize array with invalid timestamps */
+ for (i = 0; i < spec->segtotal; i++) {
+ buf->timestamps[i] = GST_CLOCK_TIME_NONE;
+ }
+
if (G_UNLIKELY ((bpf = buf->spec.info.bpf) == 0))
goto invalid_bpf;
gst_audio_ring_buffer_stop (buf);
GST_OBJECT_LOCK (buf);
+
+ if (G_LIKELY (buf->timestamps)) {
+ GST_INFO_OBJECT (buf, "Freeing timestamp buffer, %d entries",
+ buf->spec.segtotal);
+ g_slice_free1 (sizeof (GstClockTime) * buf->spec.segtotal, buf->timestamps);
+ buf->timestamps = NULL;
+ }
+
if (G_UNLIKELY (!buf->acquired))
goto was_released;
* @sample: the sample position of the data
* @data: where the data should be read
* @len: the number of samples in data to read
+ * @timestamp: where the timestamp is returned
*
* Read @len samples from the ringbuffer into the memory pointed
* to by @data.
* @len should not be a multiple of the segment size of the ringbuffer
* although it is recommended.
*
+ * @timestamp will return the timestamp associated with the data returned.
+ *
* Returns: The number of samples read from the ringbuffer or -1 on
* error.
*
*/
guint
gst_audio_ring_buffer_read (GstAudioRingBuffer * buf, guint64 sample,
- guint8 * data, guint len)
+ guint8 * data, guint len, GstClockTime * timestamp)
{
gint segdone;
- gint segsize, segtotal, channels, bps, bpf, sps;
+ gint segsize, segtotal, channels, bps, bpf, sps, readseg = 0;
guint8 *dest;
guint to_read;
gboolean need_reorder;
/* read enough samples */
while (to_read > 0) {
gint sampleslen;
- gint readseg, sampleoff;
+ gint sampleoff;
/* figure out the segment and the offset inside the segment where
* the sample should be read from. */
data += sampleslen * bpf;
}
+ if (buf->timestamps && timestamp) {
+ *timestamp = buf->timestamps[readseg % segtotal];
+ GST_INFO_OBJECT (buf, "Retrieved timestamp %" GST_TIME_FORMAT
+ " @ %d", GST_TIME_ARGS (*timestamp), readseg % segtotal);
+ }
+
return len - to_read;
/* ERRORS */
}
}
}
+
+/**
+ * gst_ring_buffer_set_timestamp:
+ * @buf: the #GstRingBuffer
+ * @readseg: the current data segment
+ * @timestamp: The new timestamp of the buffer.
+ *
+ * Set a new timestamp on the buffer.
+ *
+ * MT safe.
+ *
+ * Since:
+ */
+void
+gst_audio_ring_buffer_set_timestamp (GstAudioRingBuffer * buf, gint readseg,
+ GstClockTime timestamp)
+{
+ g_return_if_fail (GST_IS_AUDIO_RING_BUFFER (buf));
+
+ GST_INFO_OBJECT (buf, "Storing timestamp %" GST_TIME_FORMAT
+ " @ %d", GST_TIME_ARGS (timestamp), readseg);
+ if (buf->timestamps) {
+ buf->timestamps[readseg] = timestamp;
+ } else {
+ GST_ERROR_OBJECT (buf, "Could not store timestamp, no timestamps buffer");
+ }
+}
gboolean acquired;
guint8 *memory;
gsize size;
+ GstClockTime *timestamps;
GstAudioRingBufferSpec spec;
gint samples_per_seg;
guint8 *empty_seg;
/* read samples */
guint gst_audio_ring_buffer_read (GstAudioRingBuffer *buf, guint64 sample,
- guint8 *data, guint len);
+ guint8 *data, guint len, GstClockTime *timestamp);
+
+/* Set timestamp on buffer */
+void gst_audio_ring_buffer_set_timestamp (GstAudioRingBuffer * buf, gint readseg, GstClockTime
+ timestamp);
/* mostly protected */
/* not yet implemented
GST_DEBUG_FUNCPTR (gst_audio_src_ring_buffer_delay);
}
-typedef guint (*ReadFunc) (GstAudioSrc * src, gpointer data, guint length);
+typedef guint (*ReadFunc)
+ (GstAudioSrc * src, gpointer data, guint length, GstClockTime * timestamp);
/* this internal thread does nothing else but read samples from the audio device.
* It will read each segment in the ringbuffer and will update the play
GST_DEBUG_OBJECT (src, "enter thread");
- readfunc = csrc->read;
- if (readfunc == NULL)
+ if ((readfunc = csrc->read) == NULL)
goto no_function;
/* FIXME: maybe we should at least use a custom pointer type here? */
gint left, len;
guint8 *readptr;
gint readseg;
+ GstClockTime timestamp = GST_CLOCK_TIME_NONE;
if (gst_audio_ring_buffer_prepare_read (buf, &readseg, &readptr, &len)) {
gint read;
left = len;
do {
- read = readfunc (src, readptr, left);
+ read = readfunc (src, readptr, left, ×tamp);
GST_LOG_OBJECT (src, "transfered %d bytes of %d to segment %d", read,
left, readseg);
if (read < 0 || read > left) {
readptr += read;
} while (left > 0);
+ /* Update timestamp on buffer if required */
+ gst_audio_ring_buffer_set_timestamp (buf, readseg, timestamp);
+
/* we read one segment */
gst_audio_ring_buffer_advance (buf, 1);
} else {
/* close the device */
gboolean (*close) (GstAudioSrc *src);
/* read samples from the device */
- guint (*read) (GstAudioSrc *src, gpointer data, guint length);
+ guint (*read) (GstAudioSrc *src, gpointer data, guint length,
+ GstClockTime *timestamp);
/* get number of samples queued in the device */
guint (*delay) (GstAudioSrc *src);
/* reset the audio device, unblock from a write */