blob: 213632698ac3c4e163f16da2ab44a50200596fe0 [file] [log] [blame]
/* GStreamer
* Copyright (C) 2001 RidgeRun, Inc. (www.ridgerun.com)
*
* gstautoplugcache.c: Data cache for the dynamic autoplugger
*
* 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 <gst/gst.h>
GstElementDetails gst_autoplugcache_details = {
"AutoplugCache",
"Connection",
"Data cache for the dynamic autoplugger",
VERSION,
"Erik Walthinsen <omega@temple-baptist.com>",
"(C) 2001 RidgeRun, Inc. (www.ridgerun.com)",
};
#define GST_TYPE_AUTOPLUGCACHE \
(gst_autoplugcache_get_type())
#define GST_AUTOPLUGCACHE(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUTOPLUGCACHE,GstAutoplugCache))
#define GST_AUTOPLUGCACHE_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUTOPLUGCACHE,GstAutoplugCacheClass))
#define GST_IS_AUTOPLUGCACHE(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUTOPLUGCACHE))
#define GST_IS_AUTOPLUGCACHE_CLASS(obj) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUTOPLUGCACHE))
typedef struct _GstAutoplugCache GstAutoplugCache;
typedef struct _GstAutoplugCacheClass GstAutoplugCacheClass;
struct _GstAutoplugCache {
GstElement element;
GstPad *sinkpad, *srcpad;
gboolean caps_proxy;
GList *cache;
GList *cache_start;
gint buffer_count;
GList *current_playout;
gboolean fire_empty;
gboolean fire_first;
};
struct _GstAutoplugCacheClass {
GstElementClass parent_class;
void (*first_buffer) (GstElement *element, GstBuffer *buf);
void (*cache_empty) (GstElement *element);
};
/* Cache signals and args */
enum {
FIRST_BUFFER,
CACHE_EMPTY,
LAST_SIGNAL
};
enum {
ARG_0,
ARG_BUFFER_COUNT,
ARG_CAPS_PROXY,
ARG_RESET
};
static void gst_autoplugcache_class_init (GstAutoplugCacheClass *klass);
static void gst_autoplugcache_init (GstAutoplugCache *cache);
static void gst_autoplugcache_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec);
static void gst_autoplugcache_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec);
static void gst_autoplugcache_loop (GstElement *element);
static GstPadNegotiateReturn gst_autoplugcache_nego_src (GstPad *pad, GstCaps **caps, gpointer *data);
static GstPadNegotiateReturn gst_autoplugcache_nego_sink (GstPad *pad, GstCaps **caps, gpointer *data);
static GstElementStateReturn gst_autoplugcache_change_state (GstElement *element);
static GstElementClass *parent_class = NULL;
static guint gst_autoplugcache_signals[LAST_SIGNAL] = { 0 };
GType
gst_autoplugcache_get_type(void) {
static GType autoplugcache_type = 0;
if (!autoplugcache_type) {
static const GTypeInfo autoplugcache_info = {
sizeof(GstAutoplugCacheClass),
NULL,
NULL,
(GClassInitFunc)gst_autoplugcache_class_init,
NULL,
NULL,
sizeof(GstAutoplugCache),
0,
(GInstanceInitFunc)gst_autoplugcache_init,
};
autoplugcache_type = g_type_register_static (GST_TYPE_ELEMENT, "GstAutoplugCache", &autoplugcache_info, 0);
}
return autoplugcache_type;
}
static void
gst_autoplugcache_class_init (GstAutoplugCacheClass *klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = (GObjectClass*)klass;
gstelement_class = (GstElementClass*)klass;
parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_BUFFER_COUNT,
g_param_spec_int("buffer_count","buffer_count","buffer_count",
0,G_MAXINT,0,G_PARAM_READABLE)); // CHECKME!
g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_CAPS_PROXY,
g_param_spec_boolean("caps_proxy","caps_proxy","caps_proxy",
FALSE,G_PARAM_READWRITE)); // CHECKME!
g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_RESET,
g_param_spec_boolean("reset","reset","reset",
FALSE,G_PARAM_WRITABLE)); // CHECKME!
gst_autoplugcache_signals[FIRST_BUFFER] =
g_signal_newc ("first_buffer", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GstAutoplugCacheClass, first_buffer), NULL, NULL,
g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1,
G_TYPE_POINTER);
gst_autoplugcache_signals[CACHE_EMPTY] =
g_signal_newc ("cache_empty", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GstAutoplugCacheClass, cache_empty), NULL, NULL,
g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
gobject_class->set_property = gst_autoplugcache_set_property;
gobject_class->get_property = gst_autoplugcache_get_property;
gstelement_class->change_state = gst_autoplugcache_change_state;
}
static void
gst_autoplugcache_init (GstAutoplugCache *cache)
{
gst_element_set_loop_function(GST_ELEMENT(cache), GST_DEBUG_FUNCPTR(gst_autoplugcache_loop));
cache->sinkpad = gst_pad_new ("sink", GST_PAD_SINK);
// gst_pad_set_negotiate_function (cache->sinkpad, gst_autoplugcache_nego_sink);
gst_element_add_pad (GST_ELEMENT(cache), cache->sinkpad);
cache->srcpad = gst_pad_new ("src", GST_PAD_SRC);
// gst_pad_set_negotiate_function (cache->srcpad, gst_autoplugcache_nego_src);
gst_element_add_pad (GST_ELEMENT(cache), cache->srcpad);
cache->caps_proxy = FALSE;
// provide a zero basis for the cache
cache->cache = g_list_prepend(NULL, NULL);
cache->cache_start = cache->cache;
cache->buffer_count = 0;
cache->current_playout = 0;
cache->fire_empty = FALSE;
cache->fire_first = FALSE;
}
static void
gst_autoplugcache_loop (GstElement *element)
{
GstAutoplugCache *cache;
GstBuffer *buf = NULL;
cache = GST_AUTOPLUGCACHE (element);
/* Theory:
* The cache is a doubly-linked list. The front of the list is the most recent
* buffer, the end of the list is the first buffer. The playout pointer always
* points to the latest buffer sent out the end. cache points to the front
* (most reccent) of the list at all times. cache_start points to the first
* buffer, i.e. the end of the list.
* If the playout pointer does not have a prev (towards the most recent) buffer
* (== NULL), a buffer must be pulled from the sink pad and added to the cache.
* When the playout pointer gets reset (as in a set_property), the cache is walked
* without problems, because the playout pointer has a non-NULL next. When
* the playout pointer hits the end of cache again it has to start pulling.
*/
do {
// the first time through, the current_playout pointer is going to be NULL
if (cache->current_playout == NULL) {
// get a buffer
buf = gst_pad_pull (cache->sinkpad);
// add it to the cache, though cache == NULL
gst_buffer_ref (buf);
cache->cache = g_list_prepend (cache->cache, buf);
cache->buffer_count++;
// set the current_playout pointer
cache->current_playout = cache->cache;
g_signal_emit (G_OBJECT(cache), gst_autoplugcache_signals[FIRST_BUFFER], 0, buf);
// send the buffer on its way
gst_pad_push (cache->srcpad, buf);
}
// the steady state is where the playout is at the front of the cache
else if (g_list_previous(cache->current_playout) == NULL) {
// if we've been told to fire an empty signal (after a reset)
if (cache->fire_empty) {
int oldstate = GST_STATE(cache);
GST_DEBUG(0,"at front of cache, about to pull, but firing signal\n");
gst_object_ref (GST_OBJECT (cache));
g_signal_emit (G_OBJECT(cache), gst_autoplugcache_signals[CACHE_EMPTY], 0, NULL);
if (GST_STATE(cache) != oldstate) {
gst_object_ref (GST_OBJECT (cache));
GST_DEBUG(GST_CAT_AUTOPLUG, "state changed during signal, aborting\n");
cothread_switch(cothread_current_main());
}
gst_object_unref (GST_OBJECT (cache));
}
// get a buffer
buf = gst_pad_pull (cache->sinkpad);
// add it to the front of the cache
gst_buffer_ref (buf);
cache->cache = g_list_prepend (cache->cache, buf);
cache->buffer_count++;
// set the current_playout pointer
cache->current_playout = cache->cache;
// send the buffer on its way
gst_pad_push (cache->srcpad, buf);
}
// otherwise we're trundling through existing cached buffers
else {
// move the current_playout pointer
cache->current_playout = g_list_previous (cache->current_playout);
if (cache->fire_first) {
g_signal_emit (G_OBJECT(cache), gst_autoplugcache_signals[FIRST_BUFFER], 0, buf);
cache->fire_first = FALSE;
}
// push that buffer
gst_pad_push (cache->srcpad, GST_BUFFER(cache->current_playout->data));
}
} while (!GST_FLAG_IS_SET (element, GST_ELEMENT_COTHREAD_STOPPING));
}
static GstPadNegotiateReturn
gst_autoplugcache_nego_src (GstPad *pad, GstCaps **caps, gpointer *data)
{
GstAutoplugCache *cache = GST_AUTOPLUGCACHE (GST_PAD_PARENT (pad));
return gst_pad_negotiate_proxy (pad, cache->sinkpad, caps);
}
static GstPadNegotiateReturn
gst_autoplugcache_nego_sink (GstPad *pad, GstCaps **caps, gpointer *data)
{
GstAutoplugCache *cache = GST_AUTOPLUGCACHE (GST_PAD_PARENT (pad));
return gst_pad_negotiate_proxy (pad, cache->srcpad, caps);
}
static GstElementStateReturn
gst_autoplugcache_change_state (GstElement *element)
{
// FIXME this should do something like free the cache on ->NULL
if (GST_ELEMENT_CLASS (parent_class)->change_state)
return GST_ELEMENT_CLASS (parent_class)->change_state (element);
return GST_STATE_SUCCESS;
}
static void
gst_autoplugcache_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
GstAutoplugCache *cache;
cache = GST_AUTOPLUGCACHE (object);
switch (prop_id) {
case ARG_CAPS_PROXY:
cache->caps_proxy = g_value_get_boolean (value);
GST_DEBUG(0,"caps_proxy is %d\n",cache->caps_proxy);
if (cache->caps_proxy) {
gst_pad_set_negotiate_function (cache->sinkpad, GST_DEBUG_FUNCPTR(gst_autoplugcache_nego_sink));
gst_pad_set_negotiate_function (cache->srcpad, GST_DEBUG_FUNCPTR(gst_autoplugcache_nego_src));
} else {
gst_pad_set_negotiate_function (cache->sinkpad, NULL);
gst_pad_set_negotiate_function (cache->srcpad, NULL);
}
break;
case ARG_RESET:
// no idea why anyone would set this to FALSE, but just in case ;-)
if (g_value_get_boolean (value)) {
GST_DEBUG(0,"resetting playout pointer\n");
// reset the playout pointer to the begining again
cache->current_playout = cache->cache_start;
// now we can fire a signal when the cache runs dry
cache->fire_empty = TRUE;
// also set it up to fire the first_buffer signal again
cache->fire_first = TRUE;
}
break;
default:
break;
}
}
static void
gst_autoplugcache_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
GstAutoplugCache *cache;
cache = GST_AUTOPLUGCACHE (object);
switch (prop_id) {
case ARG_BUFFER_COUNT:
g_value_set_int (value, cache->buffer_count);
break;
case ARG_CAPS_PROXY:
g_value_set_boolean (value, cache->caps_proxy);
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
plugin_init (GModule *module, GstPlugin *plugin)
{
GstElementFactory *factory;
factory = gst_elementfactory_new ("autoplugcache", GST_TYPE_AUTOPLUGCACHE,
&gst_autoplugcache_details);
g_return_val_if_fail (factory != NULL, FALSE);
gst_plugin_add_factory (plugin, factory);
return TRUE;
}
GstPluginDesc plugin_desc = {
GST_VERSION_MAJOR,
GST_VERSION_MINOR,
"autoplugcache",
plugin_init
};