+2005-04-20 Wim Taymans <wim@fluendo.com>
+
+ * docs/design-audiosinks.txt:
+ * gst-libs/gst/audio/Makefile.am:
+ * gst-libs/gst/audio/TODO:
+ * gst-libs/gst/audio/gstaudiosink.c:
+ (gst_audioringbuffer_get_type), (gst_audioringbuffer_class_init),
+ (audioringbuffer_thread_func), (gst_audioringbuffer_init),
+ (gst_audioringbuffer_dispose), (gst_audioringbuffer_finalize),
+ (gst_audioringbuffer_acquire), (gst_audioringbuffer_release),
+ (gst_audioringbuffer_play), (gst_audioringbuffer_stop),
+ (gst_audioringbuffer_delay), (gst_audiosink_base_init),
+ (gst_audiosink_class_init), (gst_audiosink_init),
+ (gst_audiosink_create_ringbuffer):
+ * gst-libs/gst/audio/gstaudiosink.h:
+ * gst-libs/gst/audio/gstbaseaudiosink.c:
+ (gst_baseaudiosink_base_init), (gst_baseaudiosink_class_init),
+ (gst_baseaudiosink_init), (gst_baseaudiosink_set_property),
+ (gst_baseaudiosink_get_property), (gst_baseaudiosink_setcaps),
+ (gst_baseaudiosink_get_times), (gst_baseaudiosink_event),
+ (gst_baseaudiosink_preroll), (gst_baseaudiosink_render),
+ (gst_baseaudiosink_create_ringbuffer),
+ (gst_baseaudiosink_callback), (gst_baseaudiosink_change_state):
+ * gst-libs/gst/audio/gstbaseaudiosink.h:
+ * gst-libs/gst/audio/gstringbuffer.c: (gst_ringbuffer_get_type),
+ (gst_ringbuffer_class_init), (gst_ringbuffer_init),
+ (gst_ringbuffer_dispose), (gst_ringbuffer_finalize),
+ (gst_ringbuffer_set_callback), (gst_ringbuffer_acquire),
+ (gst_ringbuffer_release), (gst_ringbuffer_play_unlocked),
+ (gst_ringbuffer_play), (gst_ringbuffer_pause),
+ (gst_ringbuffer_resume), (gst_ringbuffer_stop),
+ (gst_ringbuffer_callback), (gst_ringbuffer_delay),
+ (gst_ringbuffer_played_samples), (gst_ringbuffer_commit),
+ (gst_ringbuffer_prepare_read), (gst_ringbuffer_clear):
+ * gst-libs/gst/audio/gstringbuffer.h:
+ An attempt at a set of audio base classes together with some
+ design docs.
+
2005-04-20 Wim Taymans <wim@fluendo.com>
* gst/audioconvert/Makefile.am:
--- /dev/null
+Audiosink design
+----------------
+
+Requirements:
+
+ - must operate chain based.
+ Most simple playback pipelines will push audio from the decoders
+ into the audio sink.
+
+ - must operate getrange based
+ Most professional audio applications will operate in a mode where
+ the audio sink pulls samples from the pipeline. This is typically
+ done in a callback from the audiosink requesting N samples. The
+ callback is either scheduled from a thread or from an interrupt
+ from the audio hardware device.
+
+ - Exact sample accurate clocks.
+ the audiosink must be able to provide a clock that is sample
+ accurate even if samples are dropped or when discontinuities are
+ found in the stream.
+
+ - Exact timing of playback.
+ The audiosink must be able to play samples at their exact times.
+
+ - use DMA access when possible.
+ When the hardware can do DMA we should use it. This should also
+ work over bufferpools to avoid data copying to/from kernel space.
+
+
+Design:
+
+ The design is based on a set of base classes and the concept of a
+ ringbuffer of samples.
+
+ +-----------+ - provide preroll, rendering, timing
+ + basesink + - caps nego
+ +-----+-----+
+ |
+ +-----V----------+ - manages ringbuffer
+ + baseaudiosink + - manages scheduling (push/pull)
+ +-----+----------+ - manages clock/query/seek
+ | - manages scheduling of samples in the ringbuffer
+ | - manages caps parsing
+ |
+ +-----V------+ - default ringbuffer implementation with a GThread
+ + audiosink + - subclasses provide open/read/close methods
+ +------------+
+
+ The ringbuffer is a contiguous piece of memory divided into segtotal
+ pieces of segments. Each segment has segsize bytes.
+
+ play position write position
+ v v
+ +---+---+---+-------------------------------------+----------+
+ + 0 | 1 | 2 | .... | segtotal |
+ +---+---+---+-------------------------------------+----------+
+ <--->
+ segsize bytes = N samples * bytes_per_sample.
+
+
+ The ringbuffer has a play and write position, which is expressed in
+ segments. The play position is where the device is currently reading
+ samples and the write position is where new samples can be written
+ into the buffer.
+
+ The latency of the ringbuffer is the distance between the play and
+ write position. The lowest latency is the size of a segment, thus
+ smaller segment sizes allow for lower latency.
+
+ The ringbuffer can be put to the PLAYING or STOPPED state.
+
+ In the STOPPED state no samples are played to the device and the play
+ pointer does not advance.
+
+ In the PLAYING state samples are written to the device and the ringbuffer
+ should call a configurable callback after each segment is written to the
+ device. In this state the play pointer is advanced after each segment is
+ written.
+
+ A write operation to the ringbuffer will put new samples in the ringbuffer.
+ If there is not enough space in the ringbuffer, the write operation will
+ block. The playback of the buffer never stops, even if the buffer is
+ empty. When the buffer is empty, silence is played by the device.
+
+ The ringbuffer is implemented with lockfree atomic operations, especially
+ on the reading side so that low-latency operations are possible.
+
+
+Scheduling:
+
+ - chain based mode:
+
+ In chain based mode, bytes are written into the ringbuffer. This operation
+ will eventually block when the ringbuffer is filled.
+
+ When no samples arrive in time, the ringbuffer will play silence. Each
+ buffer that arrives will be placed into the ringbuffer at the correct
+ times. This means that dropping samples or inserting silence is done
+ automatically and very accurate and independend of the play pointer.
+
+ In this mode, the ringbuffer is usually kept as full as possible. When
+ using a small buffer (small segsize and segtotal), the latency for audio
+ to start from the sink to when it is played can be kept low but at least
+ one context switch has to be made between read and write.
+
+ - getrange based mode
+
+ In getrange based mode, the baseaudiosink will use the callback function
+ of the ringbuffer to get a segsize samples from the peer element. These
+ samples will then be placed in the ringbuffer at the next play position.
+ It is assumed that the getrange function returns fast enough to fill the
+ ringbuffer before the play pointer reaches the write pointer.
+
+ In this mode, the ringbuffer is usually kept as empty as possible. There
+ is no context switch needed between the elements that create the samples
+ and the actual writing of the samples to the device.
+
+
+DMA mode:
+
+ - Elements that can do DMA based access to the audio device have to subclass
+ from the GstBaseAudioSink class and wrap the DMA ringbuffer in a subclass
+ of GstRingBuffer.
+
+ The ringbuffer subclass should trigger a callback after writing or playing
+ each sample to the device. This callback can be triggered from a thread or
+ from a signal from the audio device.
+
+
+Clocks:
+
+ The GstBaseAudioSink class will use the ringbuffer to act as a clock provider.
+ It can do this by using the play pointer and the delay to calculate the
+ clock time.
+
+
+
$(BUILT_SOURCES)
libgstaudio_la_SOURCES = audio.c audioclock.c \
- multichannel.c
+ multichannel.c \
+ gstaudiosink.c \
+ gstbaseaudiosink.c \
+ gstringbuffer.c
nodist_libgstaudio_la_SOURCES = $(built_sources)
libgstaudioincludedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/audio
audio.h \
audioclock.h \
gstaudiofilter.h \
- multichannel.h
-
-nodist_libgstaudioinclude_HEADERS = \
+ gstaudiosink.h \
+ gstbaseaudiosink.h \
+ gstringbuffer.h \
+ multichannel.h \
multichannel-enumtypes.h
libgstaudio_la_LIBADD =
libgstaudio_la_CFLAGS = $(GST_CFLAGS)
-libgstaudio_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstaudio_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(GST_BASE_LIBS)
libgstaudiofilter_la_SOURCES = gstaudiofilter.c gstaudiofilter.h
libgstaudiofilter_la_CFLAGS = $(GST_CFLAGS)
gstaudiofilterexample.c: $(srcdir)/make_filter $(srcdir)/gstaudiofiltertemplate.c
$(srcdir)/make_filter AudiofilterExample $(srcdir)/gstaudiofiltertemplate.c
+noinst_PROGRAMS = testchannels
+testchannels_SOURCES = testchannels.c
+testchannels_CFLAGS = $(GST_CFLAGS)
+testchannels_LDFLAGS = $(GST_LIBS)
+
include $(top_srcdir)/common/glib-gen.mak
--- /dev/null
+TODO
+----
+
+- audio base classes:
+ - GstBaseAudioSink
+ - parse caps into rinbuffer spec, also mase sure surround sound
+ is parsed correctly.
+ - implement seek/query/convert
+ - implement clocks
+ - implement getrange scheduling
+ - GstRingBuffer
+ - copy samples to right position in ringbuffer
+
--- /dev/null
+/* GStreamer
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2005 Wim Taymans <wim@fluendo.com>
+ *
+ * gstaudiosink.c: simple audio sink base class
+ *
+ * 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.
+ */
+
+#include <string.h>
+
+#include "gstaudiosink.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_audiosink_debug);
+#define GST_CAT_DEFAULT gst_audiosink_debug
+
+#define GST_TYPE_AUDIORINGBUFFER \
+ (gst_audioringbuffer_get_type())
+#define GST_AUDIORINGBUFFER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIORINGBUFFER,GstAudioRingBuffer))
+#define GST_AUDIORINGBUFFER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUDIORINGBUFFER,GstAudioRingBufferClass))
+#define GST_AUDIORINGBUFFER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_AUDIORINGBUFFER, GstAudioRingBufferClass))
+#define GST_IS_AUDIORINGBUFFER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIORINGBUFFER))
+#define GST_IS_AUDIORINGBUFFER_CLASS(obj)\
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUDIORINGBUFFER))
+
+typedef struct _GstAudioRingBuffer GstAudioRingBuffer;
+typedef struct _GstAudioRingBufferClass GstAudioRingBufferClass;
+
+#define GST_AUDIORINGBUFFER_GET_COND(buf) (((GstAudioRingBuffer *)buf)->cond)
+#define GST_AUDIORINGBUFFER_WAIT(buf) (g_cond_wait (GST_AUDIORINGBUFFER_GET_COND (buf), GST_GET_LOCK (buf)))
+#define GST_AUDIORINGBUFFER_SIGNAL(buf) (g_cond_signal (GST_AUDIORINGBUFFER_GET_COND (buf)))
+#define GST_AUDIORINGBUFFER_BROADCAST(buf)(g_cond_broadcast (GST_AUDIORINGBUFFER_GET_COND (buf)))
+
+struct _GstAudioRingBuffer
+{
+ GstRingBuffer object;
+
+ gboolean running;
+ gint queuedseg;
+
+ GCond *cond;
+};
+
+struct _GstAudioRingBufferClass
+{
+ GstRingBufferClass parent_class;
+};
+
+static void gst_audioringbuffer_class_init (GstAudioRingBufferClass * klass);
+static void gst_audioringbuffer_init (GstAudioRingBuffer * ringbuffer);
+static void gst_audioringbuffer_dispose (GObject * object);
+static void gst_audioringbuffer_finalize (GObject * object);
+
+static GstRingBufferClass *ring_parent_class = NULL;
+
+static gboolean gst_audioringbuffer_acquire (GstRingBuffer * buf,
+ GstRingBufferSpec * spec);
+static gboolean gst_audioringbuffer_release (GstRingBuffer * buf);
+static gboolean gst_audioringbuffer_play (GstRingBuffer * buf);
+static gboolean gst_audioringbuffer_stop (GstRingBuffer * buf);
+static guint gst_audioringbuffer_delay (GstRingBuffer * buf);
+
+/* ringbuffer abstract base class */
+GType
+gst_audioringbuffer_get_type (void)
+{
+ static GType ringbuffer_type = 0;
+
+ if (!ringbuffer_type) {
+ static const GTypeInfo ringbuffer_info = {
+ sizeof (GstAudioRingBufferClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gst_audioringbuffer_class_init,
+ NULL,
+ NULL,
+ sizeof (GstAudioRingBuffer),
+ 0,
+ (GInstanceInitFunc) gst_audioringbuffer_init,
+ NULL
+ };
+
+ ringbuffer_type =
+ g_type_register_static (GST_TYPE_RINGBUFFER, "GstAudioRingBuffer",
+ &ringbuffer_info, 0);
+ }
+ return ringbuffer_type;
+}
+
+static void
+gst_audioringbuffer_class_init (GstAudioRingBufferClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstObjectClass *gstobject_class;
+ GstRingBufferClass *gstringbuffer_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstobject_class = (GstObjectClass *) klass;
+ gstringbuffer_class = (GstRingBufferClass *) klass;
+
+ ring_parent_class = g_type_class_ref (GST_TYPE_RINGBUFFER);
+
+ gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_audioringbuffer_dispose);
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_audioringbuffer_finalize);
+
+ gstringbuffer_class->acquire =
+ GST_DEBUG_FUNCPTR (gst_audioringbuffer_acquire);
+ gstringbuffer_class->release =
+ GST_DEBUG_FUNCPTR (gst_audioringbuffer_release);
+ gstringbuffer_class->play = GST_DEBUG_FUNCPTR (gst_audioringbuffer_play);
+ gstringbuffer_class->stop = GST_DEBUG_FUNCPTR (gst_audioringbuffer_stop);
+
+ gstringbuffer_class->delay = GST_DEBUG_FUNCPTR (gst_audioringbuffer_delay);
+}
+
+typedef guint (*WriteFunc) (GstAudioSink * sink, gpointer data, guint length);
+
+/* this internal thread does nothing else but write samples to the audio device.
+ * It will write each segment in the ringbuffer and will update the play
+ * pointer.
+ * The play/stop methods control the thread.
+ */
+static void
+audioringbuffer_thread_func (GstRingBuffer * buf)
+{
+ GstAudioSink *sink;
+ GstAudioSinkClass *csink;
+ GstAudioRingBuffer *abuf = GST_AUDIORINGBUFFER (buf);
+ WriteFunc writefunc;
+ gint segsize, segtotal;
+
+ sink = GST_AUDIOSINK (GST_OBJECT_PARENT (buf));
+ csink = GST_AUDIOSINK_GET_CLASS (sink);
+
+ GST_DEBUG ("enter thread");
+
+ writefunc = csink->write;
+ if (writefunc == NULL)
+ goto no_function;
+
+ segsize = buf->spec.segsize;
+ segtotal = buf->spec.segtotal;
+
+ while (TRUE) {
+ if (g_atomic_int_get (&buf->state) == GST_RINGBUFFER_STATE_PLAYING) {
+ gint to_write, written;
+ guint8 *readptr;
+ gint readseg;
+
+ /* we write one segment */
+ to_write = segsize;
+ written = 0;
+ /* need to read and write the next segment */
+ readseg = (buf->playseg + 1) % segtotal;
+ /* get a pointer in the buffer to this segment */
+ readptr = gst_ringbuffer_prepare_read (buf, readseg);
+
+ do {
+ written = writefunc (sink, readptr + written, to_write);
+ if (written < 0 || written > to_write) {
+ perror ("error writing data\n");
+ break;
+ }
+ to_write -= written;
+ } while (to_write > 0);
+
+ /* clear written samples */
+ gst_ringbuffer_clear (buf, readseg);
+
+ /* we wrote one segment */
+ gst_ringbuffer_callback (buf, 1);
+ } else {
+ GST_LOCK (abuf);
+ GST_DEBUG ("signal wait");
+ GST_AUDIORINGBUFFER_SIGNAL (buf);
+ GST_DEBUG ("wait for play");
+ GST_AUDIORINGBUFFER_WAIT (buf);
+ GST_DEBUG ("got signal");
+ if (!abuf->running) {
+ GST_UNLOCK (abuf);
+ GST_DEBUG ("stop running");
+ goto done;
+ }
+ GST_UNLOCK (abuf);
+ }
+ }
+done:
+ GST_DEBUG ("exit thread");
+
+ return;
+
+ /* ERROR */
+no_function:
+ {
+ GST_DEBUG ("no write function, exit thread");
+ return;
+ }
+}
+
+static void
+gst_audioringbuffer_init (GstAudioRingBuffer * ringbuffer)
+{
+ ringbuffer->running = TRUE;
+ ringbuffer->queuedseg = 0;
+
+ ringbuffer->cond = g_cond_new ();
+}
+
+static void
+gst_audioringbuffer_dispose (GObject * object)
+{
+ G_OBJECT_CLASS (ring_parent_class)->dispose (object);
+}
+
+static void
+gst_audioringbuffer_finalize (GObject * object)
+{
+ G_OBJECT_CLASS (ring_parent_class)->finalize (object);
+}
+
+static gboolean
+gst_audioringbuffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec)
+{
+ GstAudioSink *sink;
+ GstAudioSinkClass *csink;
+ gboolean result = FALSE;
+
+ sink = GST_AUDIOSINK (GST_OBJECT_PARENT (buf));
+ csink = GST_AUDIOSINK_GET_CLASS (sink);
+
+ if (csink->open)
+ result = csink->open (sink, spec);
+
+ if (!result)
+ goto could_not_open;
+
+ /* allocate one more segment as we need some headroom */
+ spec->segtotal++;
+
+ buf->data = gst_buffer_new_and_alloc (spec->segtotal * spec->segsize);
+ memset (GST_BUFFER_DATA (buf), 0, GST_BUFFER_SIZE (buf));
+
+ sink->thread =
+ g_thread_create ((GThreadFunc) audioringbuffer_thread_func, buf, TRUE,
+ NULL);
+ GST_AUDIORINGBUFFER_WAIT (buf);
+
+ return result;
+
+could_not_open:
+ {
+ return FALSE;
+ }
+}
+
+/* function is called with LOCK */
+static gboolean
+gst_audioringbuffer_release (GstRingBuffer * buf)
+{
+ GstAudioSink *sink;
+ GstAudioSinkClass *csink;
+ GstAudioRingBuffer *abuf;
+ gboolean result = FALSE;
+
+ sink = GST_AUDIOSINK (GST_OBJECT_PARENT (buf));
+ csink = GST_AUDIOSINK_GET_CLASS (sink);
+ abuf = GST_AUDIORINGBUFFER (buf);
+
+ abuf->running = FALSE;
+ GST_AUDIORINGBUFFER_SIGNAL (buf);
+ GST_UNLOCK (buf);
+
+ /* join the thread */
+ g_thread_join (sink->thread);
+
+ GST_LOCK (buf);
+
+ if (csink->close)
+ result = csink->close (sink);
+
+ return result;
+}
+
+static gboolean
+gst_audioringbuffer_play (GstRingBuffer * buf)
+{
+ GstAudioSink *sink;
+
+ sink = GST_AUDIOSINK (GST_OBJECT_PARENT (buf));
+
+ GST_DEBUG ("play");
+ GST_AUDIORINGBUFFER_SIGNAL (buf);
+
+ return TRUE;
+}
+
+static gboolean
+gst_audioringbuffer_stop (GstRingBuffer * buf)
+{
+ GstAudioSink *sink;
+ GstAudioSinkClass *csink;
+
+ sink = GST_AUDIOSINK (GST_OBJECT_PARENT (buf));
+ csink = GST_AUDIOSINK_GET_CLASS (sink);
+
+ /* unblock any pending writes to the audio device */
+ if (csink->reset)
+ csink->reset (sink);
+
+ GST_DEBUG ("stop");
+ GST_AUDIORINGBUFFER_WAIT (buf);
+
+ return TRUE;
+}
+
+static guint
+gst_audioringbuffer_delay (GstRingBuffer * buf)
+{
+ GstAudioSink *sink;
+ GstAudioSinkClass *csink;
+ guint res = 0;
+
+ sink = GST_AUDIOSINK (GST_OBJECT_PARENT (buf));
+ csink = GST_AUDIOSINK_GET_CLASS (sink);
+
+ if (csink->delay)
+ res = csink->delay (sink);
+
+ return res;
+}
+
+/* AudioSink signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ ARG_0,
+};
+
+#define _do_init(bla) \
+ GST_DEBUG_CATEGORY_INIT (gst_audiosink_debug, "audiosink", 0, "audiosink element");
+
+GST_BOILERPLATE_FULL (GstAudioSink, gst_audiosink, GstBaseAudioSink,
+ GST_TYPE_BASEAUDIOSINK, _do_init);
+
+static GstRingBuffer *gst_audiosink_create_ringbuffer (GstBaseAudioSink * sink);
+
+static void
+gst_audiosink_base_init (gpointer g_class)
+{
+}
+
+static void
+gst_audiosink_class_init (GstAudioSinkClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSinkClass *gstbasesink_class;
+ GstBaseAudioSinkClass *gstbaseaudiosink_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesink_class = (GstBaseSinkClass *) klass;
+ gstbaseaudiosink_class = (GstBaseAudioSinkClass *) klass;
+
+ gstbaseaudiosink_class->create_ringbuffer =
+ GST_DEBUG_FUNCPTR (gst_audiosink_create_ringbuffer);
+}
+
+static void
+gst_audiosink_init (GstAudioSink * audiosink)
+{
+}
+
+static GstRingBuffer *
+gst_audiosink_create_ringbuffer (GstBaseAudioSink * sink)
+{
+ GstRingBuffer *buffer;
+
+ buffer = g_object_new (GST_TYPE_AUDIORINGBUFFER, NULL);
+
+ return buffer;
+}
--- /dev/null
+/* GStreamer
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2005 Wim Taymans <wim@fluendo.com>
+ *
+ * gstaudiosink.h:
+ *
+ * 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.
+ */
+
+/* a base class for simple audio sinks.
+ *
+ * This base class only requires subclasses to implement a set
+ * of simple functions.
+ *
+ * - open: open the device with the specified caps
+ * - write: write the samples to the audio device
+ * - close: close the device
+ * - delay: the number of samples queued in the device
+ * - reset: unblock a write to the device and reset.
+ *
+ * All scheduling of samples and timestamps is done in this
+ * base class together with the GstBaseAudioSink using a
+ * default implementation of a ringbuffer that uses threads.
+ */
+
+#ifndef __GST_AUDIOSINK_H__
+#define __GST_AUDIOSINK_H__
+
+#include <gst/gst.h>
+#include "gstbaseaudiosink.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_AUDIOSINK (gst_audiosink_get_type())
+#define GST_AUDIOSINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIOSINK,GstAudioSink))
+#define GST_AUDIOSINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUDIOSINK,GstAudioSinkClass))
+#define GST_AUDIOSINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),GST_TYPE_AUDIOSINK,GstAudioSinkClass))
+#define GST_IS_AUDIOSINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIOSINK))
+#define GST_IS_AUDIOSINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUDIOSINK))
+
+typedef struct _GstAudioSink GstAudioSink;
+typedef struct _GstAudioSinkClass GstAudioSinkClass;
+
+struct _GstAudioSink {
+ GstBaseAudioSink element;
+
+ /*< private >*/ /* with LOCK */
+ GThread *thread;
+};
+
+struct _GstAudioSinkClass {
+ GstBaseAudioSinkClass parent_class;
+
+ /* vtable */
+
+ /* open the device with given specs */
+ gboolean (*open) (GstAudioSink *sink, GstRingBufferSpec *spec);
+ /* close the device */
+ gboolean (*close) (GstAudioSink *sink);
+ /* write samples to the device */
+ guint (*write) (GstAudioSink *sink, gpointer data, guint length);
+ /* get number of samples queued in the device */
+ guint (*delay) (GstAudioSink *sink);
+ /* reset the audio device, unblock from a write */
+ void (*reset) (GstAudioSink *sink);
+};
+
+GType gst_audiosink_get_type(void);
+
+G_END_DECLS
+
+#endif /* __GST_AUDIOSINK_H__ */
--- /dev/null
+/* GStreamer
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2005 Wim Taymans <wim@fluendo.com>
+ *
+ * gstbaseaudiosink.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.
+ */
+
+#include "gstbaseaudiosink.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_baseaudiosink_debug);
+#define GST_CAT_DEFAULT gst_baseaudiosink_debug
+
+/* BaseAudioSink signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+#define DEFAULT_BUFFER -1
+#define DEFAULT_LATENCY -1
+enum
+{
+ PROP_0,
+ PROP_BUFFER,
+ PROP_LATENCY,
+};
+
+#define _do_init(bla) \
+ GST_DEBUG_CATEGORY_INIT (gst_baseaudiosink_debug, "baseaudiosink", 0, "baseaudiosink element");
+
+GST_BOILERPLATE_FULL (GstBaseAudioSink, gst_baseaudiosink, GstBaseSink,
+ GST_TYPE_BASESINK, _do_init);
+
+static void gst_baseaudiosink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_baseaudiosink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static GstElementStateReturn gst_baseaudiosink_change_state (GstElement *
+ element);
+
+static GstFlowReturn gst_baseaudiosink_preroll (GstBaseSink * bsink,
+ GstBuffer * buffer);
+static GstFlowReturn gst_baseaudiosink_render (GstBaseSink * bsink,
+ GstBuffer * buffer);
+static void gst_baseaudiosink_event (GstBaseSink * bsink, GstEvent * event);
+static void gst_baseaudiosink_get_times (GstBaseSink * bsink,
+ GstBuffer * buffer, GstClockTime * start, GstClockTime * end);
+static gboolean gst_baseaudiosink_setcaps (GstBaseSink * bsink, GstCaps * caps);
+
+//static guint gst_baseaudiosink_signals[LAST_SIGNAL] = { 0 };
+
+static void
+gst_baseaudiosink_base_init (gpointer g_class)
+{
+}
+
+static void
+gst_baseaudiosink_class_init (GstBaseAudioSinkClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSinkClass *gstbasesink_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesink_class = (GstBaseSinkClass *) klass;
+
+ gobject_class->set_property =
+ GST_DEBUG_FUNCPTR (gst_baseaudiosink_set_property);
+ gobject_class->get_property =
+ GST_DEBUG_FUNCPTR (gst_baseaudiosink_get_property);
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BUFFER,
+ g_param_spec_uint64 ("buffer", "Buffer",
+ "Size of audio buffer in nanoseconds (-1 = default)",
+ 0, G_MAXUINT64, DEFAULT_BUFFER, G_PARAM_READWRITE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LATENCY,
+ g_param_spec_uint64 ("latency", "Latency",
+ "Audio latency in nanoseconds (-1 = default)",
+ 0, G_MAXUINT64, DEFAULT_LATENCY, G_PARAM_READWRITE));
+
+ gstelement_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_baseaudiosink_change_state);
+
+ gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_baseaudiosink_event);
+ gstbasesink_class->preroll = GST_DEBUG_FUNCPTR (gst_baseaudiosink_preroll);
+ gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_baseaudiosink_render);
+ gstbasesink_class->get_times =
+ GST_DEBUG_FUNCPTR (gst_baseaudiosink_get_times);
+ gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_baseaudiosink_setcaps);
+}
+
+static void
+gst_baseaudiosink_init (GstBaseAudioSink * baseaudiosink)
+{
+ baseaudiosink->buffer = DEFAULT_BUFFER;
+ baseaudiosink->latency = DEFAULT_LATENCY;
+}
+
+static void
+gst_baseaudiosink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstBaseAudioSink *sink;
+
+ sink = GST_BASEAUDIOSINK (object);
+
+ switch (prop_id) {
+ case PROP_BUFFER:
+ break;
+ case PROP_LATENCY:
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_baseaudiosink_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstBaseAudioSink *sink;
+
+ sink = GST_BASEAUDIOSINK (object);
+
+ switch (prop_id) {
+ case PROP_BUFFER:
+ break;
+ case PROP_LATENCY:
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_baseaudiosink_setcaps (GstBaseSink * bsink, GstCaps * caps)
+{
+ GstBaseAudioSink *sink = GST_BASEAUDIOSINK (bsink);
+ GstRingBufferSpec *spec;
+
+ spec = &sink->ringbuffer->spec;
+
+ gst_caps_replace (&spec->caps, caps);
+ spec->buffersize = sink->buffer;
+ spec->latency = sink->latency;
+
+ spec->segtotal = 0x7fff;
+ spec->segsize = 0x2048;
+
+ gst_ringbuffer_release (sink->ringbuffer);
+ gst_ringbuffer_acquire (sink->ringbuffer, spec);
+
+ return TRUE;
+}
+
+static void
+gst_baseaudiosink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
+ GstClockTime * start, GstClockTime * end)
+{
+ *start = GST_CLOCK_TIME_NONE;
+ *end = GST_CLOCK_TIME_NONE;
+}
+
+static void
+gst_baseaudiosink_event (GstBaseSink * bsink, GstEvent * event)
+{
+}
+
+static GstFlowReturn
+gst_baseaudiosink_preroll (GstBaseSink * bsink, GstBuffer * buffer)
+{
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_baseaudiosink_render (GstBaseSink * bsink, GstBuffer * buf)
+{
+ GstBaseAudioSink *sink = GST_BASEAUDIOSINK (bsink);
+
+ gst_ringbuffer_commit (sink->ringbuffer, 0,
+ GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
+
+ return GST_FLOW_OK;
+}
+
+GstRingBuffer *
+gst_baseaudiosink_create_ringbuffer (GstBaseAudioSink * sink)
+{
+ GstBaseAudioSinkClass *bclass;
+ GstRingBuffer *buffer = NULL;
+
+ bclass = GST_BASEAUDIOSINK_GET_CLASS (sink);
+ if (bclass->create_ringbuffer)
+ buffer = bclass->create_ringbuffer (sink);
+
+ if (buffer) {
+ gst_object_set_parent (GST_OBJECT (buffer), GST_OBJECT (sink));
+ }
+
+ return buffer;
+}
+
+void
+gst_baseaudiosink_callback (GstRingBuffer * rbuf, guint advance, gpointer data)
+{
+ //GstBaseAudioSink *sink = GST_BASEAUDIOSINK (data);
+}
+
+static GstElementStateReturn
+gst_baseaudiosink_change_state (GstElement * element)
+{
+ GstElementStateReturn ret = GST_STATE_SUCCESS;
+ GstBaseAudioSink *sink = GST_BASEAUDIOSINK (element);
+ GstElementState transition = GST_STATE_TRANSITION (element);
+
+ switch (transition) {
+ case GST_STATE_NULL_TO_READY:
+ break;
+ case GST_STATE_READY_TO_PAUSED:
+ sink->ringbuffer = gst_baseaudiosink_create_ringbuffer (sink);
+ gst_ringbuffer_set_callback (sink->ringbuffer, gst_baseaudiosink_callback,
+ sink);
+ break;
+ case GST_STATE_PAUSED_TO_PLAYING:
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element);
+
+ switch (transition) {
+ case GST_STATE_PLAYING_TO_PAUSED:
+ gst_ringbuffer_stop (sink->ringbuffer);
+ break;
+ case GST_STATE_PAUSED_TO_READY:
+ gst_ringbuffer_release (sink->ringbuffer);
+ gst_object_unref (GST_OBJECT (sink->ringbuffer));
+ break;
+ case GST_STATE_READY_TO_NULL:
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
--- /dev/null
+/* GStreamer
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2005 Wim Taymans <wim@fluendo.com>
+ *
+ * gstbaseaudiosink.h:
+ *
+ * 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.
+ */
+
+/* a base class for audio sinks.
+ *
+ * It uses a ringbuffer to schedule playback of samples. This makes
+ * it very easy to drop or insert samples to align incomming
+ * buffers to the exact playback timestamp.
+ *
+ * Subclasses must provide a ringbuffer pointing to either DMA
+ * memory or regular memory. A subclass should also call a callback
+ * function when it has played N segments in the buffer. The subclass
+ * is free to use a thread to signal this callback, use EIO or any
+ * other mechanism.
+ *
+ * The base class is able to operate in push or pull mode. The chain
+ * mode will queue the samples in the ringbuffer as much as possible.
+ * The available space is calculated in the callback function.
+ *
+ * The pull mode will pull_range() a new buffer of N samples with a
+ * configurable latency. This allows for high-end real time
+ * audio processing pipelines driven by the audiosink. The callback
+ * function will be used to perform a pull_range() on the sinkpad.
+ * The thread scheduling the callback can be a real-time thread.
+ *
+ * Subclasses must implement a GstRingBuffer in addition to overriding
+ * the methods in GstBaseSink and this class.
+ */
+
+#ifndef __GST_BASEAUDIOSINK_H__
+#define __GST_BASEAUDIOSINK_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstbasesink.h>
+#include "gstringbuffer.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_BASEAUDIOSINK (gst_baseaudiosink_get_type())
+#define GST_BASEAUDIOSINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASEAUDIOSINK,GstBaseAudioSink))
+#define GST_BASEAUDIOSINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASEAUDIOSINK,GstBaseAudioSinkClass))
+#define GST_BASEAUDIOSINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_BASEAUDIOSINK, GstBaseAudioSinkClass))
+#define GST_IS_BASEAUDIOSINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASEAUDIOSINK))
+#define GST_IS_BASEAUDIOSINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASEAUDIOSINK))
+
+#define GST_BASEAUDIOSINK_CLOCK(obj) (GST_BASEAUDIOSINK (obj)->clock)
+#define GST_BASEAUDIOSINK_PAD(obj) (GST_BASEAUDIOSINK (obj)->sinkpad)
+
+typedef struct _GstBaseAudioSink GstBaseAudioSink;
+typedef struct _GstBaseAudioSinkClass GstBaseAudioSinkClass;
+
+struct _GstBaseAudioSink {
+ GstBaseSink element;
+
+ /* our ringbuffer */
+ GstRingBuffer *ringbuffer;
+
+ /* required buffer and latency */
+ GstClockTime buffer;
+ GstClockTime latency;
+};
+
+struct _GstBaseAudioSinkClass {
+ GstBaseSinkClass parent_class;
+
+ /* subclass ringbuffer allocation */
+ GstRingBuffer* (*create_ringbuffer) (GstBaseAudioSink *sink);
+};
+
+GType gst_baseaudiosink_get_type(void);
+
+GstRingBuffer *gst_baseaudiosink_create_ringbuffer (GstBaseAudioSink *sink);
+
+G_END_DECLS
+
+#endif /* __GST_BASEAUDIOSINK_H__ */
--- /dev/null
+/* GStreamer
+ * Copyright (C) 2005 Wim Taymans <wim@fluendo.com>
+ *
+ * gstringbuffer.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.
+ */
+
+#include <string.h>
+
+#include "gstringbuffer.h"
+
+static void gst_ringbuffer_class_init (GstRingBufferClass * klass);
+static void gst_ringbuffer_init (GstRingBuffer * ringbuffer);
+static void gst_ringbuffer_dispose (GObject * object);
+static void gst_ringbuffer_finalize (GObject * object);
+
+static GstObjectClass *parent_class = NULL;
+
+/* ringbuffer abstract base class */
+GType
+gst_ringbuffer_get_type (void)
+{
+ static GType ringbuffer_type = 0;
+
+ if (!ringbuffer_type) {
+ static const GTypeInfo ringbuffer_info = {
+ sizeof (GstRingBufferClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gst_ringbuffer_class_init,
+ NULL,
+ NULL,
+ sizeof (GstRingBuffer),
+ 0,
+ (GInstanceInitFunc) gst_ringbuffer_init,
+ NULL
+ };
+
+ ringbuffer_type = g_type_register_static (GST_TYPE_OBJECT, "GstRingBuffer",
+ &ringbuffer_info, G_TYPE_FLAG_ABSTRACT);
+ }
+ return ringbuffer_type;
+}
+
+static void
+gst_ringbuffer_class_init (GstRingBufferClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstObjectClass *gstobject_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstobject_class = (GstObjectClass *) klass;
+
+ parent_class = g_type_class_ref (GST_TYPE_OBJECT);
+
+ gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_ringbuffer_dispose);
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_ringbuffer_finalize);
+}
+
+static void
+gst_ringbuffer_init (GstRingBuffer * ringbuffer)
+{
+ ringbuffer->acquired = FALSE;
+ ringbuffer->state = GST_RINGBUFFER_STATE_STOPPED;
+ ringbuffer->playseg = -1;
+ ringbuffer->writeseg = -1;
+ ringbuffer->segfilled = 0;
+ ringbuffer->freeseg = -1;
+ ringbuffer->segplayed = 0;
+ ringbuffer->cond = g_cond_new ();
+}
+
+static void
+gst_ringbuffer_dispose (GObject * object)
+{
+ GstRingBuffer *ringbuffer = GST_RINGBUFFER (object);
+
+ G_OBJECT_CLASS (parent_class)->dispose (G_OBJECT (ringbuffer));
+}
+
+static void
+gst_ringbuffer_finalize (GObject * object)
+{
+ GstRingBuffer *ringbuffer = GST_RINGBUFFER (object);
+
+ g_cond_free (ringbuffer->cond);
+
+ G_OBJECT_CLASS (parent_class)->finalize (G_OBJECT (ringbuffer));
+}
+
+/**
+ * gst_ringbuffer_set_callback:
+ * @buf: the #GstRingBuffer to set the callback on
+ * @cb: the callback to set
+ * @data: use data passed to the callback
+ *
+ * Sets the given callback function on the buffer. This function
+ * will be called every time a segment has been written to a device.
+ *
+ * MT safe.
+ */
+void
+gst_ringbuffer_set_callback (GstRingBuffer * buf, GstRingBufferCallback cb,
+ gpointer data)
+{
+ GST_LOCK (buf);
+ buf->callback = cb;
+ buf->cb_data = data;
+ GST_UNLOCK (buf);
+}
+
+/**
+ * gst_ringbuffer_acquire:
+ * @buf: the #GstRingBuffer to acquire
+ * @spec: the specs of the buffer
+ *
+ * Allocate the resources for the ringbuffer. This function fills
+ * in the data pointer of the ring buffer with a valid #GstBuffer
+ * to which samples can be written.
+ *
+ * Returns: TRUE if the device could be acquired, FALSE on error.
+ *
+ * MT safe.
+ */
+gboolean
+gst_ringbuffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec)
+{
+ gboolean res = FALSE;
+ GstRingBufferClass *rclass;
+
+ GST_LOCK (buf);
+ if (buf->acquired) {
+ res = TRUE;
+ goto done;
+ }
+ buf->acquired = TRUE;
+
+ rclass = GST_RINGBUFFER_GET_CLASS (buf);
+ if (rclass->acquire)
+ res = rclass->acquire (buf, spec);
+
+ if (!res) {
+ buf->acquired = FALSE;
+ } else {
+ buf->freeseg = spec->segtotal;
+ if (buf->spec.bytes_per_sample != 0) {
+ buf->samples_per_seg = buf->spec.segsize / buf->spec.bytes_per_sample;
+ } else {
+ g_warning ("invalid bytes_per_sample from acquire ringbuffer");
+ buf->samples_per_seg = buf->spec.segsize;
+ }
+ }
+done:
+ GST_UNLOCK (buf);
+
+ return res;
+}
+
+/**
+ * gst_ringbuffer_release:
+ * @buf: the #GstRingBuffer to release
+ *
+ * Free the resources of the ringbuffer.
+ *
+ * Returns: TRUE if the device could be released, FALSE on error.
+ *
+ * MT safe.
+ */
+gboolean
+gst_ringbuffer_release (GstRingBuffer * buf)
+{
+ gboolean res = FALSE;
+ GstRingBufferClass *rclass;
+
+ gst_ringbuffer_stop (buf);
+
+ GST_LOCK (buf);
+ if (!buf->acquired) {
+ res = TRUE;
+ goto done;
+ }
+ buf->acquired = FALSE;
+
+ rclass = GST_RINGBUFFER_GET_CLASS (buf);
+ if (rclass->release)
+ res = rclass->release (buf);
+
+ if (!res) {
+ buf->acquired = TRUE;
+ }
+
+done:
+ GST_UNLOCK (buf);
+
+ return res;
+}
+
+static gboolean
+gst_ringbuffer_play_unlocked (GstRingBuffer * buf)
+{
+ gboolean res = FALSE;
+ GstRingBufferClass *rclass;
+
+ /* if paused, set to playing */
+ res = g_atomic_int_compare_and_exchange (&buf->state,
+ GST_RINGBUFFER_STATE_STOPPED, GST_RINGBUFFER_STATE_PLAYING);
+
+ if (!res) {
+ /* was not stopped */
+ res = TRUE;
+ goto done;
+ }
+
+ rclass = GST_RINGBUFFER_GET_CLASS (buf);
+ if (rclass->play)
+ res = rclass->play (buf);
+
+ if (!res) {
+ buf->state = GST_RINGBUFFER_STATE_PAUSED;
+ }
+done:
+ return res;
+}
+
+/**
+ * gst_ringbuffer_play:
+ * @buf: the #GstRingBuffer to play
+ *
+ * Start playing samples from the ringbuffer.
+ *
+ * Returns: TRUE if the device could be started, FALSE on error.
+ *
+ * MT safe.
+ */
+gboolean
+gst_ringbuffer_play (GstRingBuffer * buf)
+{
+ gboolean res = FALSE;
+
+ GST_LOCK (buf);
+ res = gst_ringbuffer_play_unlocked (buf);
+ GST_UNLOCK (buf);
+
+ return res;
+}
+
+/**
+ * gst_ringbuffer_pause:
+ * @buf: the #GstRingBuffer to pause
+ *
+ * Pause playing samples from the ringbuffer.
+ *
+ * Returns: TRUE if the device could be paused, FALSE on error.
+ *
+ * MT safe.
+ */
+gboolean
+gst_ringbuffer_pause (GstRingBuffer * buf)
+{
+ gboolean res = FALSE;
+ GstRingBufferClass *rclass;
+
+ GST_LOCK (buf);
+ /* if playing, set to paused */
+ res = g_atomic_int_compare_and_exchange (&buf->state,
+ GST_RINGBUFFER_STATE_PLAYING, GST_RINGBUFFER_STATE_PAUSED);
+
+ if (!res) {
+ /* was not playing */
+ res = TRUE;
+ goto done;
+ }
+
+ /* signal any waiters */
+ GST_RINGBUFFER_SIGNAL (buf);
+
+ rclass = GST_RINGBUFFER_GET_CLASS (buf);
+ if (rclass->pause)
+ res = rclass->pause (buf);
+
+ if (!res) {
+ buf->state = GST_RINGBUFFER_STATE_PLAYING;
+ }
+done:
+ GST_UNLOCK (buf);
+
+ return res;
+}
+
+/**
+ * gst_ringbuffer_resume:
+ * @buf: the #GstRingBuffer to resume
+ *
+ * Resume playing samples from the ringbuffer in the paused state.
+ *
+ * Returns: TRUE if the device could be paused, FALSE on error.
+ *
+ * MT safe.
+ */
+gboolean
+gst_ringbuffer_resume (GstRingBuffer * buf)
+{
+ gboolean res = FALSE;
+ GstRingBufferClass *rclass;
+
+ GST_LOCK (buf);
+ /* if playing, set to paused */
+ res = g_atomic_int_compare_and_exchange (&buf->state,
+ GST_RINGBUFFER_STATE_PAUSED, GST_RINGBUFFER_STATE_PLAYING);
+
+ if (!res) {
+ /* was not paused */
+ res = TRUE;
+ goto done;
+ }
+
+ /* signal any waiters */
+ GST_RINGBUFFER_SIGNAL (buf);
+
+ rclass = GST_RINGBUFFER_GET_CLASS (buf);
+ if (rclass->resume)
+ res = rclass->resume (buf);
+
+ if (!res) {
+ buf->state = GST_RINGBUFFER_STATE_PAUSED;
+ }
+done:
+ GST_UNLOCK (buf);
+
+ return res;
+}
+
+
+/**
+ * gst_ringbuffer_stop:
+ * @buf: the #GstRingBuffer to stop
+ *
+ * Stop playing samples from the ringbuffer.
+ *
+ * Returns: TRUE if the device could be stopped, FALSE on error.
+ *
+ * MT safe.
+ */
+gboolean
+gst_ringbuffer_stop (GstRingBuffer * buf)
+{
+ gboolean res = FALSE;
+ GstRingBufferClass *rclass;
+
+ GST_LOCK (buf);
+ /* if playing, set to stopped */
+ res = g_atomic_int_compare_and_exchange (&buf->state,
+ GST_RINGBUFFER_STATE_PLAYING, GST_RINGBUFFER_STATE_STOPPED);
+
+ if (!res) {
+ /* was not playing, must be stopped then */
+ res = TRUE;
+ goto done;
+ }
+
+ /* signal any waiters */
+ GST_RINGBUFFER_SIGNAL (buf);
+
+ rclass = GST_RINGBUFFER_GET_CLASS (buf);
+ if (rclass->stop)
+ res = rclass->stop (buf);
+
+ if (!res) {
+ buf->state = GST_RINGBUFFER_STATE_PLAYING;
+ } else {
+ buf->segfilled = 0;
+ buf->playseg = -1;
+ buf->writeseg = -1;
+ buf->freeseg = buf->spec.segtotal;
+ buf->segplayed = 0;
+ }
+done:
+ GST_UNLOCK (buf);
+
+ return res;
+}
+
+/**
+ * gst_ringbuffer_callback:
+ * @buf: the #GstRingBuffer to callback
+ * @advance: the number of segments written
+ *
+ * Subclasses should call this function to notify the fact that
+ * @advance segments are now played by the device.
+ *
+ * MT safe.
+ */
+void
+gst_ringbuffer_callback (GstRingBuffer * buf, guint advance)
+{
+ gint prevfree;
+ gint segtotal;
+
+ if (advance == 0)
+ return;
+
+ segtotal = buf->spec.segtotal;
+
+ /* update counter */
+ g_atomic_int_add (&buf->segplayed, advance);
+
+ /* update free segments counter */
+ prevfree = g_atomic_int_exchange_and_add (&buf->freeseg, advance);
+ if (prevfree + advance > segtotal) {
+ g_warning ("underrun!! read %d, write %d, advance %d, free %d, prevfree %d",
+ buf->playseg, buf->writeseg, advance, buf->freeseg, prevfree);
+ buf->freeseg = segtotal;
+ buf->writeseg = buf->playseg;
+ /* make sure to signal */
+ prevfree = -1;
+ }
+
+ buf->playseg = (buf->playseg + advance) % segtotal;
+
+ if (prevfree == -1) {
+ /* we need to take the lock to make sure the other thread is
+ * blocking in the wait */
+ GST_LOCK (buf);
+ GST_RINGBUFFER_SIGNAL (buf);
+ GST_UNLOCK (buf);
+ }
+
+ if (buf->callback)
+ buf->callback (buf, advance, buf->cb_data);
+}
+
+/**
+ * gst_ringbuffer_delay:
+ * @buf: the #GstRingBuffer to query
+ *
+ * Get the number of samples queued in the audio device. This is
+ * usually less than the segment size but can be bigger when the
+ * implementation uses another internal buffer between the audio
+ * device.
+ *
+ * Returns: The number of samples queued in the audio device.
+ *
+ * MT safe.
+ */
+guint
+gst_ringbuffer_delay (GstRingBuffer * buf)
+{
+ GstRingBufferClass *rclass;
+ guint res = 0;
+
+ rclass = GST_RINGBUFFER_GET_CLASS (buf);
+ if (rclass->delay)
+ res = rclass->delay (buf);
+
+ return res;
+}
+
+/**
+ * gst_ringbuffer_played_samples:
+ * @buf: the #GstRingBuffer to query
+ *
+ * Get the number of samples that were played by the ringbuffer
+ * since it was last started.
+ *
+ * Returns: The number of samples played by the ringbuffer.
+ *
+ * MT safe.
+ */
+guint64
+gst_ringbuffer_played_samples (GstRingBuffer * buf)
+{
+ gint segplayed;
+ guint64 samples;
+ guint delay;
+
+ /* get the amount of segments we played */
+ segplayed = g_atomic_int_get (&buf->segplayed);
+ /* and the number of samples not yet played */
+ delay = gst_ringbuffer_delay (buf);
+
+ samples = (segplayed * buf->samples_per_seg) - delay;
+
+ return samples;
+}
+
+/**
+ * gst_ringbuffer_commit:
+ * @buf: the #GstRingBuffer to commit
+ * @sample: the sample position of the data
+ * @data: the data to commit
+ * @len: the length of the data to commit
+ *
+ * Commit @length samples pointed to by @data to the ringbuffer
+ * @buf. The first sample should be written at position @sample in
+ * the ringbuffer.
+ *
+ * @len should not be a multiple of the segment size of the ringbuffer
+ * although it is recommended.
+ *
+ * Returns: The number of samples written to the ringbuffer.
+ *
+ * MT safe.
+ */
+/* FIXME, write the samples into the right position in the ringbuffer based
+ * on the sample position argument
+ */
+guint
+gst_ringbuffer_commit (GstRingBuffer * buf, guint64 sample, guchar * data,
+ guint len)
+{
+ guint towrite = len;
+ gint segsize, segtotal;
+ guint8 *dest;
+
+ if (buf->data == NULL)
+ goto no_buffer;
+
+ dest = GST_BUFFER_DATA (buf->data);
+ segsize = buf->spec.segsize;
+ segtotal = buf->spec.segtotal;
+
+ /* we write the complete buffer in chunks of segsize so that we can check for
+ * a filled buffer after each segment. */
+ while (towrite > 0) {
+ gint segavail;
+ gint segwrite;
+ gint writeseg;
+ gint segfilled;
+
+ segfilled = buf->segfilled;
+
+ /* check for partial buffer */
+ if (G_LIKELY (segfilled == 0)) {
+ gint prevfree;
+ gint newseg;
+
+ /* no partial buffer to fill up, allocate a new one */
+ prevfree = g_atomic_int_exchange_and_add (&buf->freeseg, -1);
+ if (prevfree == 0) {
+ /* nothing was free */
+ GST_DEBUG ("filled %d %d", buf->writeseg, buf->playseg);
+
+ GST_LOCK (buf);
+ /* buffer must be playing now or we deadlock since nobody is reading */
+ if (g_atomic_int_get (&buf->state) != GST_RINGBUFFER_STATE_PLAYING)
+ gst_ringbuffer_play_unlocked (buf);
+
+ GST_RINGBUFFER_WAIT (buf);
+ if (g_atomic_int_get (&buf->state) != GST_RINGBUFFER_STATE_PLAYING)
+ goto not_playing;
+ GST_UNLOCK (buf);
+ }
+
+ /* need to do this atomic as the reader updates the write pointer on
+ * overruns */
+ do {
+ writeseg = g_atomic_int_get (&buf->writeseg);
+ newseg = (writeseg + 1) % segtotal;
+ } while (!g_atomic_int_compare_and_exchange (&buf->writeseg, writeseg,
+ newseg));
+ writeseg = newseg;
+ } else {
+ /* this is the segment we should write to */
+ writeseg = g_atomic_int_get (&buf->writeseg);
+ }
+ if (writeseg < 0 || writeseg > segtotal) {
+ g_warning ("invalid segment %d", writeseg);
+ writeseg = 0;
+ }
+
+ /* this is the available size now in the current segment */
+ segavail = segsize - segfilled;
+
+ /* we write up to the available space */
+ segwrite = MIN (segavail, towrite);
+
+ memcpy (dest + writeseg * segsize + segfilled, data, segwrite);
+
+ towrite -= segwrite;
+ data += segwrite;
+
+ if (segfilled + segwrite == segsize) {
+ buf->segfilled = 0;
+ } else {
+ buf->segfilled = segfilled + segwrite;
+ }
+ }
+ return len - towrite;
+
+no_buffer:
+ {
+ GST_DEBUG ("no buffer");
+ return -1;
+ }
+not_playing:
+ {
+ GST_UNLOCK (buf);
+ GST_DEBUG ("stopped playing");
+ return len - towrite;
+ }
+}
+
+/**
+ * gst_ringbuffer_prepare_read:
+ * @buf: the #GstRingBuffer to read from
+ * @segment: the segment to read
+ *
+ * Returns a pointer to memory where the data from segment @segment
+ * can be found.
+ *
+ * MT safe.
+ */
+guint8 *
+gst_ringbuffer_prepare_read (GstRingBuffer * buf, gint segment)
+{
+ guint8 *data;
+
+ data = GST_BUFFER_DATA (buf->data);
+
+ return data + (segment % buf->spec.segtotal) * buf->spec.segsize;
+}
+
+/**
+ * gst_ringbuffer_clear:
+ * @buf: the #GstRingBuffer to clear
+ * @segment: the segment to clear
+ *
+ * Clear the given segment of the buffer with silence samples.
+ * This function is used by subclasses.
+ *
+ * MT safe.
+ */
+void
+gst_ringbuffer_clear (GstRingBuffer * buf, gint segment)
+{
+ guint8 *data;
+
+ data = GST_BUFFER_DATA (buf->data);
+
+ memset (data + (segment % buf->spec.segtotal) * buf->spec.segsize, 0,
+ buf->spec.segsize);
+}
--- /dev/null
+/* GStreamer
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2005 Wim Taymans <wim@fluendo.com>
+ *
+ * gstringbuffer.h:
+ *
+ * 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.
+ */
+
+#ifndef __GST_RINGBUFFER_H__
+#define __GST_RINGBUFFER_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_RINGBUFFER (gst_ringbuffer_get_type())
+#define GST_RINGBUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RINGBUFFER,GstRingBuffer))
+#define GST_RINGBUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RINGBUFFER,GstRingBufferClass))
+#define GST_RINGBUFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RINGBUFFER, GstRingBufferClass))
+#define GST_IS_RINGBUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RINGBUFFER))
+#define GST_IS_RINGBUFFER_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RINGBUFFER))
+
+typedef struct _GstRingBuffer GstRingBuffer;
+typedef struct _GstRingBufferClass GstRingBufferClass;
+typedef struct _GstRingBufferSpec GstRingBufferSpec;
+
+typedef void (*GstRingBufferCallback) (GstRingBuffer *rbuf, guint advance, gpointer data);
+
+typedef enum {
+ GST_RINGBUFFER_STATE_STOPPED,
+ GST_RINGBUFFER_STATE_PAUSED,
+ GST_RINGBUFFER_STATE_PLAYING,
+} GstRingBufferState;
+
+typedef enum {
+ GST_SEGSTATE_INVALID,
+ GST_SEGSTATE_EMPTY,
+ GST_SEGSTATE_FILLED,
+ GST_SEGSTATE_PARTIAL,
+} GstRingBufferSegState;
+
+typedef enum
+{
+ GST_U8,
+ GST_S8,
+
+ GST_U16_LE,
+ GST_S16_LE,
+ GST_U16_BE,
+ GST_S16_BE,
+
+ GST_U24_LE,
+ GST_S24_LE,
+ GST_U24_BE,
+ GST_S24_BE,
+
+ GST_FLOAT_LE,
+ GST_FLOAT_BE,
+} GstBufferFormat;
+
+struct _GstRingBufferSpec
+{
+ /* in */
+ GstCaps *caps; /* the caps of the buffer */
+
+ /* in/out */
+ GstBufferFormat format;
+ gint rate;
+ gint channels;
+
+ GstClockTime latency; /* the required/actual latency */
+ GstClockTime buffersize; /* the required/actual size of the buffer */
+ gint segsize; /* size of one buffer segement */
+ gint segtotal; /* total number of segments */
+
+ /* out */
+ gint bytes_per_sample; /* number of bytes of one sample */
+ guint8 silence_sample[32]; /* bytes representing silence */
+};
+
+#define GST_RINGBUFFER_GET_COND(buf) (((GstRingBuffer *)buf)->cond)
+#define GST_RINGBUFFER_WAIT(buf) (g_cond_wait (GST_RINGBUFFER_GET_COND (buf), GST_GET_LOCK (buf)))
+#define GST_RINGBUFFER_SIGNAL(buf) (g_cond_signal (GST_RINGBUFFER_GET_COND (buf)))
+#define GST_RINGBUFFER_BROADCAST(buf)(g_cond_broadcast (GST_RINGBUFFER_GET_COND (buf)))
+
+struct _GstRingBuffer {
+ GstObject object;
+
+ /*< public >*/ /* with LOCK */
+ GCond *cond;
+ gboolean acquired;
+ GstBuffer *data;
+ GstRingBufferSpec spec;
+ GstRingBufferSegState *segstate;
+ gint samples_per_seg; /* number of samples per segment */
+
+ /*< public >*/ /* ATOMIC */
+ gint state; /* state of the buffer */
+ gint freeseg; /* number of free segments */
+ gint segplayed; /* number of segments played since last start */
+
+ /*< protected >*/
+ gint playseg; /* segment currently playing */
+ gint writeseg; /* segment currently written */
+ gint segfilled; /* bytes used in current write segment */
+
+ /*< private >*/
+ GstRingBufferCallback callback;
+ gpointer cb_data;
+};
+
+struct _GstRingBufferClass {
+ GstObjectClass parent_class;
+
+ /*< public >*/
+ /* allocate the resources for the ringbuffer using the given specs */
+ gboolean (*acquire) (GstRingBuffer *buf, GstRingBufferSpec *spec);
+ /* free resources of the ringbuffer */
+ gboolean (*release) (GstRingBuffer *buf);
+
+ /* playback control */
+ gboolean (*play) (GstRingBuffer *buf);
+ gboolean (*pause) (GstRingBuffer *buf);
+ gboolean (*resume) (GstRingBuffer *buf);
+ gboolean (*stop) (GstRingBuffer *buf);
+
+ /* number of samples queued in device */
+ guint (*delay) (GstRingBuffer *buf);
+};
+
+GType gst_ringbuffer_get_type(void);
+
+/* callback stuff */
+void gst_ringbuffer_set_callback (GstRingBuffer *buf, GstRingBufferCallback cb,
+ gpointer data);
+void gst_ringbuffer_callback (GstRingBuffer *buf, guint advance);
+
+/* allocate resources */
+gboolean gst_ringbuffer_acquire (GstRingBuffer *buf, GstRingBufferSpec *spec);
+gboolean gst_ringbuffer_release (GstRingBuffer *buf);
+
+/* playback/pause */
+gboolean gst_ringbuffer_play (GstRingBuffer *buf);
+gboolean gst_ringbuffer_pause (GstRingBuffer *buf);
+gboolean gst_ringbuffer_resume (GstRingBuffer *buf);
+gboolean gst_ringbuffer_stop (GstRingBuffer *buf);
+
+/* get status */
+guint gst_ringbuffer_delay (GstRingBuffer *buf);
+guint64 gst_ringbuffer_played_samples (GstRingBuffer *buf);
+
+/* commit samples */
+guint gst_ringbuffer_commit (GstRingBuffer *buf, guint64 sample,
+ guchar *data, guint len);
+
+/* mostly protected */
+guint8* gst_ringbuffer_prepare_read (GstRingBuffer *buf, gint segment);
+void gst_ringbuffer_clear (GstRingBuffer *buf, gint segment);
+
+G_END_DECLS
+
+#endif /* __GST_RINGBUFFER_H__ */