| /* 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 |
| }; |
| |