diff options
-rw-r--r-- | ChangeLog | 27 | ||||
-rw-r--r-- | gst/audiobuffer/Makefile.am | 10 | ||||
-rw-r--r-- | gst/audiobuffer/gstaudioringbuffer.c | 1186 |
3 files changed, 1223 insertions, 0 deletions
@@ -1,3 +1,30 @@ +2008-10-30 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/audiobuffer/Makefile.am: + * gst/audiobuffer/gstaudioringbuffer.c: + (gst_int_ring_buffer_acquire), (gst_int_ring_buffer_release), + (gst_int_ring_buffer_start), (gst_int_ring_buffer_base_init), + (gst_int_ring_buffer_class_init), (gst_int_ring_buffer_init), + (gst_int_ring_buffer_new), (gst_audio_ringbuffer_get_type), + (gst_audio_ringbuffer_class_init), (gst_audio_ringbuffer_init), + (gst_audio_ringbuffer_finalize), (gst_audio_ringbuffer_getcaps), + (gst_audio_ringbuffer_setcaps), (gst_audio_ringbuffer_bufferalloc), + (gst_audio_ringbuffer_handle_sink_event), + (gst_audio_ringbuffer_render), (gst_audio_ringbuffer_chain), + (gst_audio_ringbuffer_handle_src_event), + (gst_audio_ringbuffer_handle_src_query), + (gst_audio_ringbuffer_get_range), + (gst_audio_ringbuffer_src_checkgetrange_function), + (gst_audio_ringbuffer_sink_activate_push), + (gst_audio_ringbuffer_src_activate_push), + (gst_audio_ringbuffer_src_activate_pull), + (gst_audio_ringbuffer_change_state), + (gst_audio_ringbuffer_set_property), + (gst_audio_ringbuffer_get_property), (plugin_init): + Add first version of an audioringbuffer element that can be inserted in + the pipeline to convert push-based upstream into a pull-based + downstream. + 2008-10-29 Michael Smith <msmith@songbirdnest.com> * gst/aiffparse/aiffparse.c: diff --git a/gst/audiobuffer/Makefile.am b/gst/audiobuffer/Makefile.am new file mode 100644 index 00000000..6f2a3828 --- /dev/null +++ b/gst/audiobuffer/Makefile.am @@ -0,0 +1,10 @@ +plugin_LTLIBRARIES = libgstaudiobuffer.la + +libgstaudiobuffer_la_SOURCES = gstaudioringbuffer.c +libgstaudiobuffer_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) +libgstaudiobuffer_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) \ + -lgstaudio-$(GST_MAJORMINOR) + +libgstaudiobuffer_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + diff --git a/gst/audiobuffer/gstaudioringbuffer.c b/gst/audiobuffer/gstaudioringbuffer.c new file mode 100644 index 00000000..19a9c48b --- /dev/null +++ b/gst/audiobuffer/gstaudioringbuffer.c @@ -0,0 +1,1186 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans@gmail.com> + * + * gstaudioringbuffer.c: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-audioringbuffer + * @short_description: Asynchronous audio ringbuffer. + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include <glib/gstdio.h> + +#include <gst/gst.h> +#include <gst/gst-i18n-plugin.h> + +#include <gst/audio/gstringbuffer.h> + +static const GstElementDetails gst_audio_ringbuffer_details = +GST_ELEMENT_DETAILS ("AudioRingbuffer", + "Generic", + "Asynchronous Audio ringbuffer", + "Wim Taymans <wim.taymans@gmail.com>"); + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (audioringbuffer_debug); +#define GST_CAT_DEFAULT (audioringbuffer_debug) + +enum +{ + LAST_SIGNAL +}; + +#define DEFAULT_BUFFER_TIME ((200 * GST_MSECOND) / GST_USECOND) +#define DEFAULT_SEGMENT_TIME ((10 * GST_MSECOND) / GST_USECOND) + + +enum +{ + PROP_0, + PROP_BUFFER_TIME, + PROP_SEGMENT_TIME, + PROP_LAST +}; + +#define GST_TYPE_AUDIO_RINGBUFFER \ + (gst_audio_ringbuffer_get_type()) +#define GST_AUDIO_RINGBUFFER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIO_RINGBUFFER,GstAudioRingbuffer)) +#define GST_AUDIO_RINGBUFFER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUDIO_RINGBUFFER,GstAudioRingbufferClass)) +#define GST_IS_AUDIO_RINGBUFFER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIO_RINGBUFFER)) +#define GST_IS_AUDIO_RINGBUFFER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUDIO_RINGBUFFER)) +#define GST_AUDIO_RINGBUFFER_CAST(obj) \ + ((GstAudioRingbuffer *)(obj)) + +static GType gst_audio_ringbuffer_get_type (void); + +typedef struct _GstAudioRingbuffer GstAudioRingbuffer; +typedef struct _GstAudioRingbufferClass GstAudioRingbufferClass; + +typedef struct _GstIntRingBuffer GstIntRingBuffer; +typedef struct _GstIntRingBufferClass GstIntRingBufferClass; + +struct _GstAudioRingbuffer +{ + GstElement element; + + /*< private > */ + GstPad *sinkpad; + GstPad *srcpad; + + gboolean pushing; + gboolean pulling; + + /* segments to keep track of timestamps */ + GstSegment sink_segment; + GstSegment src_segment; + + /* flowreturn when srcpad is paused */ + gboolean is_eos; + gboolean flushing; + gboolean waiting; + + GCond *cond; + + GstRingBuffer *buffer; + + GstClockTime buffer_time; + GstClockTime segment_time; + + guint64 next_sample; + guint64 last_align; +}; + +struct _GstAudioRingbufferClass +{ + GstElementClass parent_class; +}; + + +#define GST_TYPE_INT_RING_BUFFER (gst_int_ring_buffer_get_type()) +#define GST_INT_RING_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_INT_RING_BUFFER,GstIntRingBuffer)) +#define GST_INT_RING_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_INT_RING_BUFFER,GstIntRingBufferClass)) +#define GST_INT_RING_BUFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_INT_RING_BUFFER, GstIntRingBufferClass)) +#define GST_INT_RING_BUFFER_CAST(obj) ((GstIntRingBuffer *)obj) +#define GST_IS_INT_RING_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_INT_RING_BUFFER)) +#define GST_IS_INT_RING_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_INT_RING_BUFFER)) + + +struct _GstIntRingBuffer +{ + GstRingBuffer object; +}; + +struct _GstIntRingBufferClass +{ + GstRingBufferClass parent_class; +}; + +GST_BOILERPLATE (GstIntRingBuffer, gst_int_ring_buffer, GstRingBuffer, + GST_TYPE_RING_BUFFER); + +static gboolean +gst_int_ring_buffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec) +{ + spec->seglatency = spec->segtotal; + + buf->data = gst_buffer_new_and_alloc (spec->segtotal * spec->segsize); + memset (GST_BUFFER_DATA (buf->data), 0, GST_BUFFER_SIZE (buf->data)); + + return TRUE; +} + +static gboolean +gst_int_ring_buffer_release (GstRingBuffer * buf) +{ + gst_buffer_unref (buf->data); + buf->data = NULL; + + return TRUE; +} + +static gboolean +gst_int_ring_buffer_start (GstRingBuffer * buf) +{ + GstAudioRingbuffer *ringbuffer; + + ringbuffer = GST_AUDIO_RINGBUFFER (GST_OBJECT_PARENT (buf)); + + GST_OBJECT_LOCK (ringbuffer); + if (G_UNLIKELY (ringbuffer->waiting)) { + ringbuffer->waiting = FALSE; + GST_DEBUG_OBJECT (ringbuffer, "start, sending signal"); + g_cond_broadcast (ringbuffer->cond); + } + GST_OBJECT_UNLOCK (ringbuffer); + + return TRUE; +} + + +static void +gst_int_ring_buffer_base_init (gpointer klass) +{ +} + +static void +gst_int_ring_buffer_class_init (GstIntRingBufferClass * klass) +{ + GstRingBufferClass *gstringbuffer_class; + + gstringbuffer_class = (GstRingBufferClass *) klass; + + gstringbuffer_class->acquire = + GST_DEBUG_FUNCPTR (gst_int_ring_buffer_acquire); + gstringbuffer_class->release = + GST_DEBUG_FUNCPTR (gst_int_ring_buffer_release); + gstringbuffer_class->start = GST_DEBUG_FUNCPTR (gst_int_ring_buffer_start); +} + +static void +gst_int_ring_buffer_init (GstIntRingBuffer * buff, + GstIntRingBufferClass * g_class) +{ +} + +static GstRingBuffer * +gst_int_ring_buffer_new (void) +{ + GstRingBuffer *res; + + res = g_object_new (GST_TYPE_INT_RING_BUFFER, NULL); + + return res; +} + +/* can't use boilerplate as we need to register with Queue2 to avoid conflicts + * with ringbuffer in core elements */ +static void gst_audio_ringbuffer_class_init (GstAudioRingbufferClass * klass); +static void gst_audio_ringbuffer_init (GstAudioRingbuffer * ringbuffer, + GstAudioRingbufferClass * g_class); +static GstElementClass *elem_parent_class; + +static GType +gst_audio_ringbuffer_get_type (void) +{ + static GType gst_audio_ringbuffer_type = 0; + + if (!gst_audio_ringbuffer_type) { + static const GTypeInfo gst_audio_ringbuffer_info = { + sizeof (GstAudioRingbufferClass), + NULL, + NULL, + (GClassInitFunc) gst_audio_ringbuffer_class_init, + NULL, + NULL, + sizeof (GstAudioRingbuffer), + 0, + (GInstanceInitFunc) gst_audio_ringbuffer_init, + NULL + }; + + gst_audio_ringbuffer_type = + g_type_register_static (GST_TYPE_ELEMENT, "GstAudioRingbuffer", + &gst_audio_ringbuffer_info, 0); + } + return gst_audio_ringbuffer_type; +} + +static void gst_audio_ringbuffer_finalize (GObject * object); + +static void gst_audio_ringbuffer_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_audio_ringbuffer_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstFlowReturn gst_audio_ringbuffer_chain (GstPad * pad, + GstBuffer * buffer); +static GstFlowReturn gst_audio_ringbuffer_bufferalloc (GstPad * pad, + guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf); + +static gboolean gst_audio_ringbuffer_handle_sink_event (GstPad * pad, + GstEvent * event); + +static gboolean gst_audio_ringbuffer_handle_src_event (GstPad * pad, + GstEvent * event); +static gboolean gst_audio_ringbuffer_handle_src_query (GstPad * pad, + GstQuery * query); + +static GstCaps *gst_audio_ringbuffer_getcaps (GstPad * pad); +static gboolean gst_audio_ringbuffer_setcaps (GstPad * pad, GstCaps * caps); + +static GstFlowReturn gst_audio_ringbuffer_get_range (GstPad * pad, + guint64 offset, guint length, GstBuffer ** buffer); +static gboolean gst_audio_ringbuffer_src_checkgetrange_function (GstPad * pad); + +static gboolean gst_audio_ringbuffer_src_activate_pull (GstPad * pad, + gboolean active); +static gboolean gst_audio_ringbuffer_src_activate_push (GstPad * pad, + gboolean active); +static gboolean gst_audio_ringbuffer_sink_activate_push (GstPad * pad, + gboolean active); + +static GstStateChangeReturn gst_audio_ringbuffer_change_state (GstElement * + element, GstStateChange transition); + +/* static guint gst_audio_ringbuffer_signals[LAST_SIGNAL] = { 0 }; */ + +static void +gst_audio_ringbuffer_class_init (GstAudioRingbufferClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + elem_parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_get_property); + + g_object_class_install_property (gobject_class, PROP_BUFFER_TIME, + g_param_spec_int64 ("buffer-time", "Buffer Time", + "Size of audio buffer in nanoseconds", 1, + G_MAXINT64, DEFAULT_BUFFER_TIME, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SEGMENT_TIME, + g_param_spec_int64 ("segment-time", "Segment Time", + "Audio segment duration in nanoseconds", 1, + G_MAXINT64, DEFAULT_SEGMENT_TIME, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&srctemplate)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sinktemplate)); + + gst_element_class_set_details (gstelement_class, + &gst_audio_ringbuffer_details); + + /* set several parent class virtual functions */ + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_finalize); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_change_state); +} + +static void +gst_audio_ringbuffer_init (GstAudioRingbuffer * ringbuffer, + GstAudioRingbufferClass * g_class) +{ + ringbuffer->sinkpad = + gst_pad_new_from_static_template (&sinktemplate, "sink"); + + gst_pad_set_chain_function (ringbuffer->sinkpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_chain)); + gst_pad_set_activatepush_function (ringbuffer->sinkpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_sink_activate_push)); + gst_pad_set_event_function (ringbuffer->sinkpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_handle_sink_event)); + gst_pad_set_getcaps_function (ringbuffer->sinkpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_getcaps)); + gst_pad_set_setcaps_function (ringbuffer->sinkpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_setcaps)); + gst_pad_set_bufferalloc_function (ringbuffer->sinkpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_bufferalloc)); + gst_element_add_pad (GST_ELEMENT (ringbuffer), ringbuffer->sinkpad); + + ringbuffer->srcpad = gst_pad_new_from_static_template (&srctemplate, "src"); + + gst_pad_set_activatepull_function (ringbuffer->srcpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_src_activate_pull)); + gst_pad_set_activatepush_function (ringbuffer->srcpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_src_activate_push)); + gst_pad_set_getrange_function (ringbuffer->srcpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_get_range)); + gst_pad_set_checkgetrange_function (ringbuffer->srcpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_src_checkgetrange_function)); + gst_pad_set_getcaps_function (ringbuffer->srcpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_getcaps)); + gst_pad_set_event_function (ringbuffer->srcpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_handle_src_event)); + gst_pad_set_query_function (ringbuffer->srcpad, + GST_DEBUG_FUNCPTR (gst_audio_ringbuffer_handle_src_query)); + gst_element_add_pad (GST_ELEMENT (ringbuffer), ringbuffer->srcpad); + + gst_segment_init (&ringbuffer->sink_segment, GST_FORMAT_TIME); + + ringbuffer->cond = g_cond_new (); + + ringbuffer->is_eos = FALSE; + + ringbuffer->buffer_time = DEFAULT_BUFFER_TIME; + ringbuffer->segment_time = DEFAULT_SEGMENT_TIME; + + GST_DEBUG_OBJECT (ringbuffer, + "initialized ringbuffer's not_empty & not_full conditions"); +} + +/* called only once, as opposed to dispose */ +static void +gst_audio_ringbuffer_finalize (GObject * object) +{ + GstAudioRingbuffer *ringbuffer = GST_AUDIO_RINGBUFFER (object); + + GST_DEBUG_OBJECT (ringbuffer, "finalizing ringbuffer"); + + g_cond_free (ringbuffer->cond); + + G_OBJECT_CLASS (elem_parent_class)->finalize (object); +} + +static GstCaps * +gst_audio_ringbuffer_getcaps (GstPad * pad) +{ + GstAudioRingbuffer *ringbuffer; + GstPad *otherpad; + GstCaps *result; + + ringbuffer = GST_AUDIO_RINGBUFFER (GST_PAD_PARENT (pad)); + + otherpad = + (pad == ringbuffer->srcpad ? ringbuffer->sinkpad : ringbuffer->srcpad); + result = gst_pad_peer_get_caps (otherpad); + if (result == NULL) + result = gst_caps_new_any (); + + return result; +} + +static gboolean +gst_audio_ringbuffer_setcaps (GstPad * pad, GstCaps * caps) +{ + GstAudioRingbuffer *ringbuffer; + GstRingBufferSpec *spec; + + ringbuffer = GST_AUDIO_RINGBUFFER (GST_PAD_PARENT (pad)); + + if (!ringbuffer->buffer) + return FALSE; + + spec = &ringbuffer->buffer->spec; + + GST_DEBUG_OBJECT (ringbuffer, "release old ringbuffer"); + + /* release old ringbuffer */ + gst_ring_buffer_activate (ringbuffer->buffer, FALSE); + gst_ring_buffer_release (ringbuffer->buffer); + + GST_DEBUG_OBJECT (ringbuffer, "parse caps"); + + spec->buffer_time = ringbuffer->buffer_time; + spec->latency_time = ringbuffer->segment_time; + + /* parse new caps */ + if (!gst_ring_buffer_parse_caps (spec, caps)) + goto parse_error; + + gst_ring_buffer_debug_spec_buff (spec); + + GST_DEBUG_OBJECT (ringbuffer, "acquire ringbuffer"); + if (!gst_ring_buffer_acquire (ringbuffer->buffer, spec)) + goto acquire_error; + + GST_DEBUG_OBJECT (ringbuffer, "activate ringbuffer"); + gst_ring_buffer_activate (ringbuffer->buffer, TRUE); + + /* calculate actual latency and buffer times. + * FIXME: In 0.11, store the latency_time internally in ns */ + spec->latency_time = gst_util_uint64_scale (spec->segsize, + (GST_SECOND / GST_USECOND), spec->rate * spec->bytes_per_sample); + + spec->buffer_time = spec->segtotal * spec->latency_time; + + gst_ring_buffer_debug_spec_buff (spec); + + return TRUE; + + /* ERRORS */ +parse_error: + { + GST_DEBUG_OBJECT (ringbuffer, "could not parse caps"); + GST_ELEMENT_ERROR (ringbuffer, STREAM, FORMAT, + (NULL), ("cannot parse audio format.")); + return FALSE; + } +acquire_error: + { + GST_DEBUG_OBJECT (ringbuffer, "could not acquire ringbuffer"); + return FALSE; + } +} + +static GstFlowReturn +gst_audio_ringbuffer_bufferalloc (GstPad * pad, guint64 offset, guint size, + GstCaps * caps, GstBuffer ** buf) +{ + GstAudioRingbuffer *ringbuffer; + GstFlowReturn result; + + ringbuffer = GST_AUDIO_RINGBUFFER (GST_PAD_PARENT (pad)); + + /* Forward to src pad, without setting caps on the src pad */ + result = gst_pad_alloc_buffer (ringbuffer->srcpad, offset, size, caps, buf); + + return result; +} + +static gboolean +gst_audio_ringbuffer_handle_sink_event (GstPad * pad, GstEvent * event) +{ + GstAudioRingbuffer *ringbuffer; + gboolean forward; + + ringbuffer = GST_AUDIO_RINGBUFFER (GST_OBJECT_PARENT (pad)); + + forward = ringbuffer->pushing || ringbuffer->pulling; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + { + GST_LOG_OBJECT (ringbuffer, "received flush start event"); + break; + } + case GST_EVENT_FLUSH_STOP: + { + ringbuffer->is_eos = FALSE; + GST_LOG_OBJECT (ringbuffer, "received flush stop event"); + break; + } + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + gdouble rate, arate; + GstFormat format; + gint64 start, stop, time; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + gst_segment_set_newsegment_full (&ringbuffer->sink_segment, update, rate, + arate, format, start, stop, time); + break; + } + case GST_EVENT_EOS: + ringbuffer->is_eos = TRUE; + break; + default: + break; + } + if (forward) { + gst_pad_push_event (ringbuffer->srcpad, event); + } else { + if (event) + gst_event_unref (event); + } + return TRUE; +} + +#define DIFF_TOLERANCE 2 + +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_audio_ringbuffer_chain (GstPad * pad, GstBuffer * buffer) +{ + GstFlowReturn res; + GstAudioRingbuffer *ringbuffer; + + ringbuffer = GST_AUDIO_RINGBUFFER (GST_OBJECT_PARENT (pad)); + + if (ringbuffer->pushing) { + GST_DEBUG_OBJECT (ringbuffer, "proxy pushing buffer"); + res = gst_pad_push (ringbuffer->srcpad, buffer); + } else { + GST_DEBUG_OBJECT (ringbuffer, "render buffer in ringbuffer"); + res = gst_audio_ringbuffer_render (ringbuffer, buffer); + } + + return res; +} + +static gboolean +gst_audio_ringbuffer_handle_src_event (GstPad * pad, GstEvent * event) +{ + gboolean res = TRUE; + GstAudioRingbuffer *ringbuffer = GST_AUDIO_RINGBUFFER (GST_PAD_PARENT (pad)); + + /* just forward upstream */ + res = gst_pad_push_event (ringbuffer->sinkpad, event); + + return res; +} + +static gboolean +gst_audio_ringbuffer_handle_src_query (GstPad * pad, GstQuery * query) +{ + GstAudioRingbuffer *ringbuffer; + + ringbuffer = GST_AUDIO_RINGBUFFER (GST_PAD_PARENT (pad)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + break; + case GST_QUERY_DURATION: + break; + case GST_QUERY_BUFFERING: + break; + default: + break; + } + + return TRUE; +} + +static GstFlowReturn +gst_audio_ringbuffer_get_range (GstPad * pad, guint64 offset, guint length, + GstBuffer ** buffer) +{ + GstAudioRingbuffer *ringbuffer; + GstRingBuffer *rbuf; + GstFlowReturn ret; + + ringbuffer = GST_AUDIO_RINGBUFFER_CAST (gst_pad_get_parent (pad)); + + rbuf = ringbuffer->buffer; + + if (ringbuffer->pulling) { + GST_DEBUG_OBJECT (ringbuffer, "proxy pulling range"); + ret = gst_pad_pull_range (ringbuffer->sinkpad, offset, length, buffer); + } else { + guint8 *data; + guint len; + guint64 sample; + gint bps, segsize, segtotal, sps; + gint sampleslen, segdone; + gint readseg, sampleoff; + guint8 *dest; + + GST_DEBUG_OBJECT (ringbuffer, + "pulling data at %" G_GUINT64_FORMAT ", length %u", offset, length); + + if (offset != ringbuffer->src_segment.last_stop) { + GST_DEBUG_OBJECT (ringbuffer, "expected offset %" G_GINT64_FORMAT, + ringbuffer->src_segment.last_stop); + } + + /* first wait till we have something in the ringbuffer and it + * is running */ + GST_OBJECT_LOCK (ringbuffer); + if (ringbuffer->flushing) + goto flushing; + + while (ringbuffer->waiting) { + GST_DEBUG_OBJECT (ringbuffer, "waiting for unlock"); + g_cond_wait (ringbuffer->cond, GST_OBJECT_GET_LOCK (ringbuffer)); + GST_DEBUG_OBJECT (ringbuffer, "unlocked"); + + if (ringbuffer->flushing) + goto flushing; + } + GST_OBJECT_UNLOCK (ringbuffer); + + bps = rbuf->spec.bytes_per_sample; + + if (G_UNLIKELY (length % bps) != 0) + goto wrong_size; + + segsize = rbuf->spec.segsize; + segtotal = rbuf->spec.segtotal; + sps = rbuf->samples_per_seg; + dest = GST_BUFFER_DATA (rbuf->data); + + sample = offset / bps; + len = length / bps; + + *buffer = gst_buffer_new_and_alloc (length); + data = GST_BUFFER_DATA (*buffer); + + while (len) { + gint diff; + + /* figure out the segment and the offset inside the segment where + * the sample should be read from. */ + readseg = sample / sps; + sampleoff = (sample % sps); + + segdone = g_atomic_int_get (&rbuf->segdone) - rbuf->segbase; + + diff = readseg - segdone; + + /* we can read now */ + readseg = readseg % segtotal; + sampleslen = MIN (sps - sampleoff, len); + + GST_DEBUG_OBJECT (ringbuffer, + "read @%p seg %d, off %d, sampleslen %d, diff %d", + dest + readseg * segsize, readseg, sampleoff, sampleslen, diff); + + memcpy (data, dest + (readseg * segsize) + (sampleoff * bps), + (sampleslen * bps)); + + if (diff > 0) + gst_ring_buffer_advance (rbuf, diff); + + len -= sampleslen; + sample += sampleslen; + data += sampleslen * bps; + } + + ringbuffer->src_segment.last_stop += length; + + ret = GST_FLOW_OK; + } + + gst_object_unref (ringbuffer); + + return ret; + + /* ERRORS */ +flushing: + { + GST_DEBUG_OBJECT (ringbuffer, "we are flushing"); + GST_OBJECT_UNLOCK (ringbuffer); + gst_object_unref (ringbuffer); + return GST_FLOW_WRONG_STATE; + } +wrong_size: + { + GST_DEBUG_OBJECT (ringbuffer, "wrong size"); + GST_ELEMENT_ERROR (ringbuffer, STREAM, WRONG_TYPE, + (NULL), ("asked to pull buffer of wrong size.")); + return GST_FLOW_ERROR; + } +} + +static gboolean +gst_audio_ringbuffer_src_checkgetrange_function (GstPad * pad) +{ + gboolean ret; + + /* we can always operate in pull mode */ + ret = TRUE; + + return ret; +} + +/* sink currently only operates in push mode */ +static gboolean +gst_audio_ringbuffer_sink_activate_push (GstPad * pad, gboolean active) +{ + gboolean result = TRUE; + GstAudioRingbuffer *ringbuffer; + + ringbuffer = GST_AUDIO_RINGBUFFER (gst_pad_get_parent (pad)); + + if (active) { + GST_DEBUG_OBJECT (ringbuffer, "activating push mode"); + ringbuffer->is_eos = FALSE; + ringbuffer->pulling = FALSE; + } else { + /* unblock chain function */ + GST_DEBUG_OBJECT (ringbuffer, "deactivating push mode"); + ringbuffer->pulling = FALSE; + } + + gst_object_unref (ringbuffer); + + return result; +} + +/* src operating in push mode, we will proxy the push from upstream, basically + * acting as a passthrough element. */ +static gboolean +gst_audio_ringbuffer_src_activate_push (GstPad * pad, gboolean active) +{ + gboolean result = FALSE; + GstAudioRingbuffer *ringbuffer; + + ringbuffer = GST_AUDIO_RINGBUFFER (gst_pad_get_parent (pad)); + + if (active) { + GST_DEBUG_OBJECT (ringbuffer, "activating push mode"); + ringbuffer->is_eos = FALSE; + ringbuffer->pushing = TRUE; + ringbuffer->pulling = FALSE; + result = TRUE; + } else { + GST_DEBUG_OBJECT (ringbuffer, "deactivating push mode"); + ringbuffer->pushing = FALSE; + ringbuffer->pulling = FALSE; + result = TRUE; + } + + gst_object_unref (ringbuffer); + + return result; +} + +/* pull mode, downstream will call our getrange function */ +static gboolean +gst_audio_ringbuffer_src_activate_pull (GstPad * pad, gboolean active) +{ + gboolean result; + GstAudioRingbuffer *ringbuffer; + + ringbuffer = GST_AUDIO_RINGBUFFER (gst_pad_get_parent (pad)); + + if (active) { + GST_DEBUG_OBJECT (ringbuffer, "activating pull mode"); + + /* try to activate upstream in pull mode as well. If it fails, no problems, + * we'll be activated in push mode. Remember that we are pulling-through */ + ringbuffer->pulling = gst_pad_activate_pull (ringbuffer->sinkpad, active); + + ringbuffer->is_eos = FALSE; + ringbuffer->waiting = TRUE; + ringbuffer->flushing = FALSE; + gst_segment_init (&ringbuffer->src_segment, GST_FORMAT_BYTES); + result = TRUE; + } else { + GST_DEBUG_OBJECT (ringbuffer, "deactivating pull mode"); + + if (ringbuffer->pulling) + gst_pad_activate_pull (ringbuffer->sinkpad, active); + + ringbuffer->pulling = FALSE; + ringbuffer->waiting = FALSE; + ringbuffer->flushing = TRUE; + result = TRUE; + } + gst_object_unref (ringbuffer); + + return result; +} + +static GstStateChangeReturn +gst_audio_ringbuffer_change_state (GstElement * element, + GstStateChange transition) +{ + GstAudioRingbuffer *ringbuffer; + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + ringbuffer = GST_AUDIO_RINGBUFFER (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (ringbuffer->buffer == NULL) { + ringbuffer->buffer = gst_int_ring_buffer_new (); + gst_object_set_parent (GST_OBJECT (ringbuffer->buffer), + GST_OBJECT (ringbuffer)); + gst_ring_buffer_open_device (ringbuffer->buffer); + } + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + ringbuffer->next_sample = -1; + ringbuffer->last_align = -1; + gst_ring_buffer_set_flushing (ringbuffer->buffer, FALSE); + gst_ring_buffer_may_start (ringbuffer->buffer, TRUE); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_OBJECT_LOCK (ringbuffer); + ringbuffer->flushing = TRUE; + ringbuffer->waiting = FALSE; + g_cond_broadcast (ringbuffer->cond); + GST_OBJECT_UNLOCK (ringbuffer); + + gst_ring_buffer_set_flushing (ringbuffer->buffer, TRUE); + gst_ring_buffer_may_start (ringbuffer->buffer, FALSE); + break; + default: + break; + } + + ret = + GST_ELEMENT_CLASS (elem_parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_ring_buffer_activate (ringbuffer->buffer, FALSE); + gst_ring_buffer_release (ringbuffer->buffer); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + if (ringbuffer->buffer != NULL) { + gst_ring_buffer_close_device (ringbuffer->buffer); + gst_object_unparent (GST_OBJECT (ringbuffer->buffer)); + ringbuffer->buffer = NULL; + } + break; + default: + break; + } + + return ret; +} + +static void +gst_audio_ringbuffer_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstAudioRingbuffer *ringbuffer; + + ringbuffer = GST_AUDIO_RINGBUFFER (object); + + switch (prop_id) { + case PROP_BUFFER_TIME: + ringbuffer->buffer_time = g_value_get_int64 (value); + break; + case PROP_SEGMENT_TIME: + ringbuffer->segment_time = g_value_get_int64 (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_audio_ringbuffer_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstAudioRingbuffer *ringbuffer; + + ringbuffer = GST_AUDIO_RINGBUFFER (object); + + switch (prop_id) { + case PROP_BUFFER_TIME: + g_value_set_int64 (value, ringbuffer->buffer_time); + break; + case PROP_SEGMENT_TIME: + g_value_set_int64 (value, ringbuffer->segment_time); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (audioringbuffer_debug, "audioringbuffer", 0, + "Audio ringbuffer element"); + +#ifdef ENABLE_NLS + GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, + LOCALEDIR); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +#endif /* ENABLE_NLS */ + + return gst_element_register (plugin, "audioringbuffer", GST_RANK_NONE, + GST_TYPE_AUDIO_RINGBUFFER); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "audioringbuffer", + "An audio ringbuffer", plugin_init, VERSION, GST_LICENSE, + GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |