From 5a3941c762b5fa0cfc4b077fc5e56aa3e32b414b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 20 Apr 2005 10:19:54 +0000 Subject: [PATCH] An attempt at a set of audio base classes together with some design docs. Original commit message from CVS: * 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. --- ChangeLog | 38 ++ docs/design-audiosinks.txt | 137 ++++++ gst-libs/gst/audio/Makefile.am | 19 +- gst-libs/gst/audio/TODO | 13 + gst-libs/gst/audio/gstaudiosink.c | 404 ++++++++++++++++ gst-libs/gst/audio/gstaudiosink.h | 85 ++++ gst-libs/gst/audio/gstbaseaudiosink.c | 267 +++++++++++ gst-libs/gst/audio/gstbaseaudiosink.h | 95 ++++ gst-libs/gst/audio/gstringbuffer.c | 656 ++++++++++++++++++++++++++ gst-libs/gst/audio/gstringbuffer.h | 176 +++++++ 10 files changed, 1885 insertions(+), 5 deletions(-) create mode 100644 docs/design-audiosinks.txt create mode 100644 gst-libs/gst/audio/TODO create mode 100644 gst-libs/gst/audio/gstaudiosink.c create mode 100644 gst-libs/gst/audio/gstaudiosink.h create mode 100644 gst-libs/gst/audio/gstbaseaudiosink.c create mode 100644 gst-libs/gst/audio/gstbaseaudiosink.h create mode 100644 gst-libs/gst/audio/gstringbuffer.c create mode 100644 gst-libs/gst/audio/gstringbuffer.h diff --git a/ChangeLog b/ChangeLog index 3deab71c70..8102d3485b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,41 @@ +2005-04-20 Wim Taymans + + * 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 * gst/audioconvert/Makefile.am: diff --git a/docs/design-audiosinks.txt b/docs/design-audiosinks.txt new file mode 100644 index 0000000000..bc66c6de52 --- /dev/null +++ b/docs/design-audiosinks.txt @@ -0,0 +1,137 @@ +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. + + + diff --git a/gst-libs/gst/audio/Makefile.am b/gst-libs/gst/audio/Makefile.am index 54208713fd..58d9c219fb 100644 --- a/gst-libs/gst/audio/Makefile.am +++ b/gst-libs/gst/audio/Makefile.am @@ -17,7 +17,10 @@ CLEANFILES = gstaudiofilterexample.c \ $(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 @@ -25,14 +28,15 @@ libgstaudioinclude_HEADERS = \ 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) @@ -45,4 +49,9 @@ libgstaudiofilterexample_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) 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 diff --git a/gst-libs/gst/audio/TODO b/gst-libs/gst/audio/TODO new file mode 100644 index 0000000000..c3492581f6 --- /dev/null +++ b/gst-libs/gst/audio/TODO @@ -0,0 +1,13 @@ +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 + diff --git a/gst-libs/gst/audio/gstaudiosink.c b/gst-libs/gst/audio/gstaudiosink.c new file mode 100644 index 0000000000..039471887d --- /dev/null +++ b/gst-libs/gst/audio/gstaudiosink.c @@ -0,0 +1,404 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen + * 2005 Wim Taymans + * + * 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 + +#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; +} diff --git a/gst-libs/gst/audio/gstaudiosink.h b/gst-libs/gst/audio/gstaudiosink.h new file mode 100644 index 0000000000..fba15d68e3 --- /dev/null +++ b/gst-libs/gst/audio/gstaudiosink.h @@ -0,0 +1,85 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen + * 2005 Wim Taymans + * + * 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 +#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__ */ diff --git a/gst-libs/gst/audio/gstbaseaudiosink.c b/gst-libs/gst/audio/gstbaseaudiosink.c new file mode 100644 index 0000000000..4d77608805 --- /dev/null +++ b/gst-libs/gst/audio/gstbaseaudiosink.c @@ -0,0 +1,267 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen + * 2005 Wim Taymans + * + * 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; +} diff --git a/gst-libs/gst/audio/gstbaseaudiosink.h b/gst-libs/gst/audio/gstbaseaudiosink.h new file mode 100644 index 0000000000..1c72055986 --- /dev/null +++ b/gst-libs/gst/audio/gstbaseaudiosink.h @@ -0,0 +1,95 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen + * 2005 Wim Taymans + * + * 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 +#include +#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__ */ diff --git a/gst-libs/gst/audio/gstringbuffer.c b/gst-libs/gst/audio/gstringbuffer.c new file mode 100644 index 0000000000..959e2e7a4c --- /dev/null +++ b/gst-libs/gst/audio/gstringbuffer.c @@ -0,0 +1,656 @@ +/* GStreamer + * Copyright (C) 2005 Wim Taymans + * + * 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 + +#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); +} diff --git a/gst-libs/gst/audio/gstringbuffer.h b/gst-libs/gst/audio/gstringbuffer.h new file mode 100644 index 0000000000..6d5f5f55f0 --- /dev/null +++ b/gst-libs/gst/audio/gstringbuffer.h @@ -0,0 +1,176 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen + * 2005 Wim Taymans + * + * 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 + +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__ */