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.
This commit is contained in:
Wim Taymans 2005-04-20 10:19:54 +00:00
parent 468d6d4326
commit 5a3941c762
10 changed files with 1885 additions and 5 deletions

View file

@ -1,3 +1,41 @@
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> 2005-04-20 Wim Taymans <wim@fluendo.com>
* gst/audioconvert/Makefile.am: * gst/audioconvert/Makefile.am:

137
docs/design-audiosinks.txt Normal file
View file

@ -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.

View file

@ -17,7 +17,10 @@ CLEANFILES = gstaudiofilterexample.c \
$(BUILT_SOURCES) $(BUILT_SOURCES)
libgstaudio_la_SOURCES = audio.c audioclock.c \ libgstaudio_la_SOURCES = audio.c audioclock.c \
multichannel.c multichannel.c \
gstaudiosink.c \
gstbaseaudiosink.c \
gstringbuffer.c
nodist_libgstaudio_la_SOURCES = $(built_sources) nodist_libgstaudio_la_SOURCES = $(built_sources)
libgstaudioincludedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/audio libgstaudioincludedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/audio
@ -25,14 +28,15 @@ libgstaudioinclude_HEADERS = \
audio.h \ audio.h \
audioclock.h \ audioclock.h \
gstaudiofilter.h \ gstaudiofilter.h \
multichannel.h gstaudiosink.h \
gstbaseaudiosink.h \
nodist_libgstaudioinclude_HEADERS = \ gstringbuffer.h \
multichannel.h \
multichannel-enumtypes.h multichannel-enumtypes.h
libgstaudio_la_LIBADD = libgstaudio_la_LIBADD =
libgstaudio_la_CFLAGS = $(GST_CFLAGS) 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_SOURCES = gstaudiofilter.c gstaudiofilter.h
libgstaudiofilter_la_CFLAGS = $(GST_CFLAGS) libgstaudiofilter_la_CFLAGS = $(GST_CFLAGS)
@ -45,4 +49,9 @@ libgstaudiofilterexample_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
gstaudiofilterexample.c: $(srcdir)/make_filter $(srcdir)/gstaudiofiltertemplate.c gstaudiofilterexample.c: $(srcdir)/make_filter $(srcdir)/gstaudiofiltertemplate.c
$(srcdir)/make_filter AudiofilterExample $(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 include $(top_srcdir)/common/glib-gen.mak

13
gst-libs/gst/audio/TODO Normal file
View file

@ -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

View file

@ -0,0 +1,404 @@
/* 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;
}

View file

@ -0,0 +1,85 @@
/* 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__ */

View file

@ -0,0 +1,267 @@
/* 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;
}

View file

@ -0,0 +1,95 @@
/* 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__ */

View file

@ -0,0 +1,656 @@
/* 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);
}

View file

@ -0,0 +1,176 @@
/* 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__ */