/* GStreamer * Copyright (C) <1999> Erik Walthinsen * * 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 #include #include #include #include #include #include /*#define GST_DEBUG_FORCE_DISABLE*/ #include "gstglsink.h" /* elementfactory information */ static GstElementDetails gst_glsink_details = { "OpenGL Sink/GLX", "Sink/GLVideo", "LGPL", "An OpenGL based video sink - uses OpenGL and GLX to draw video, utilizing different acceleration options", VERSION, "Gernot Ziegler ", "(C) 2002", }; /* glsink signals and args */ enum { LAST_SIGNAL }; enum { ARG_0, ARG_WIDTH, ARG_HEIGHT, ARG_FRAMES_DISPLAYED, ARG_FRAME_TIME, ARG_HOOK, ARG_MUTE, ARG_REPAINT }; /* GLsink class */ #define GST_TYPE_GLSINK (gst_glsink_get_type()) #define GST_GLSINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_GLSINK,GstGLSink)) #define GST_GLSINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_GLSINK,GstGLSink)) #define GST_IS_GLSINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_GLSINK)) #define GST_IS_GLSINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_GLSINK)) typedef struct _GstGLSink GstGLSink; typedef struct _GstGLSinkClass GstGLSinkClass; struct _GstGLSink { GstElement element; GstPad *sinkpad; gint frames_displayed; guint64 frame_time; gint width, height; gboolean muted; GstBuffer *last_image; /* not thread safe ? */ GstClock *clock; /* bufferpool stuff */ GstBufferPool *bufferpool; GMutex *cache_lock; GList *cache; /* plugins */ GstImagePlugin* plugin; GstImageConnection *conn; /* allow anybody to hook in here */ GstImageInfo *hook; }; struct _GstGLSinkClass { GstElementClass parent_class; /* plugins */ GList *plugins; }; static GType gst_glsink_get_type (void); static void gst_glsink_class_init (GstGLSinkClass *klass); static void gst_glsink_init (GstGLSink *sink); /* static void gst_glsink_dispose (GObject *object); */ static void gst_glsink_chain (GstPad *pad, GstBuffer *buf); static void gst_glsink_set_clock (GstElement *element, GstClock *clock); static GstElementStateReturn gst_glsink_change_state (GstElement *element); static GstPadLinkReturn gst_glsink_sinkconnect (GstPad *pad, GstCaps *caps); static GstCaps * gst_glsink_getcaps (GstPad *pad, GstCaps *caps); static GstBufferPool* gst_glsink_get_bufferpool (GstPad *pad); static void gst_glsink_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gst_glsink_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gst_glsink_release_conn (GstGLSink *sink); static void gst_glsink_append_cache (GstGLSink *sink, GstImageData *image); static gboolean gst_glsink_set_caps (GstGLSink *sink, GstCaps *caps); /* bufferpool stuff */ static GstBuffer* gst_glsink_buffer_new (GstBufferPool *pool, gint64 location, guint size, gpointer user_data); static void gst_glsink_buffer_free (GstBufferPool *pool, GstBuffer *buffer, gpointer user_data); /* prototypes from plugins */ extern GstImagePlugin* get_gl_rgbimage_plugin (void); extern GstImagePlugin* get_gl_nvimage_plugin (void); /* default output */ extern void gst_glxwindow_new (GstGLSink *sink); static GstPadTemplate *sink_template; static GstElementClass *parent_class = NULL; /* static guint gst_glsink_signals[LAST_SIGNAL] = { 0 }; */ static GType gst_glsink_get_type (void) { static GType videosink_type = 0; if (!videosink_type) { static const GTypeInfo videosink_info = { sizeof(GstGLSinkClass), NULL, NULL, (GClassInitFunc) gst_glsink_class_init, NULL, NULL, sizeof(GstGLSink), 0, (GInstanceInitFunc) gst_glsink_init, }; videosink_type = g_type_register_static(GST_TYPE_ELEMENT, "GstGLSink", &videosink_info, 0); } return videosink_type; } static void gst_glsink_class_init (GstGLSinkClass *klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; parent_class = g_type_class_ref (GST_TYPE_ELEMENT); gobject_class->set_property = gst_glsink_set_property; gobject_class->get_property = gst_glsink_get_property; g_object_class_install_property (gobject_class, ARG_WIDTH, g_param_spec_int ("width", "Width", "The video width", G_MININT, G_MAXINT, 0, G_PARAM_READABLE)); /* CHECKME */ g_object_class_install_property (gobject_class, ARG_HEIGHT, g_param_spec_int ("height", "Height", "The video height", G_MININT, G_MAXINT, 0, G_PARAM_READABLE)); /* CHECKME */ g_object_class_install_property (gobject_class, ARG_FRAMES_DISPLAYED, g_param_spec_int ("frames_displayed", "Frames Displayed", "The number of frames displayed so far", G_MININT,G_MAXINT, 0, G_PARAM_READWRITE)); /* CHECKME */ g_object_class_install_property (gobject_class, ARG_FRAME_TIME, g_param_spec_int ("frame_time", "Frame time", "The interval between frames", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE)); /* CHECKME */ g_object_class_install_property (gobject_class, ARG_HOOK, g_param_spec_pointer ("hook", "Hook", "The object receiving the output", G_PARAM_WRITABLE)); g_object_class_install_property (gobject_class, ARG_MUTE, g_param_spec_boolean ("mute", "Mute", "mute the output ?", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, ARG_REPAINT, g_param_spec_boolean ("repaint", "Repaint", "repaint the current frame", FALSE, G_PARAM_WRITABLE)); /* gobject_class->dispose = gst_glsink_dispose; */ gstelement_class->change_state = gst_glsink_change_state; gstelement_class->set_clock = gst_glsink_set_clock; /* plugins */ klass->plugins = NULL; //klass->plugins = g_list_append (klass->plugins, get_xvimage_plugin ()); klass->plugins = g_list_append (klass->plugins, get_gl_rgbimage_plugin ()); } /* GLSink has its own Buffer management - this allows special plugins to create special memory areas for buffer upload */ static void gst_glsink_init (GstGLSink *sink) { sink->sinkpad = gst_pad_new_from_template (sink_template, "sink"); gst_element_add_pad (GST_ELEMENT (sink), sink->sinkpad); gst_pad_set_chain_function (sink->sinkpad, gst_glsink_chain); gst_pad_set_link_function (sink->sinkpad, gst_glsink_sinkconnect); gst_pad_set_getcaps_function (sink->sinkpad, gst_glsink_getcaps); gst_pad_set_bufferpool_function (sink->sinkpad, gst_glsink_get_bufferpool); sink->last_image = NULL; sink->width = 0; sink->height = 0; sink->muted = FALSE; sink->clock = NULL; GST_FLAG_SET(sink, GST_ELEMENT_THREAD_SUGGESTED); GST_FLAG_SET (sink, GST_ELEMENT_EVENT_AWARE); /* create bufferpool and image cache */ GST_DEBUG (0, "glsink: creating bufferpool"); sink->bufferpool = gst_buffer_pool_new ( NULL, NULL, (GstBufferPoolBufferNewFunction)gst_glsink_buffer_new, NULL, (GstBufferPoolBufferFreeFunction)gst_glsink_buffer_free, sink); sink->cache_lock = g_mutex_new(); sink->cache = NULL; /* plugins */ sink->plugin = NULL; sink->conn = NULL; /* do initialization of default hook here */ gst_glxwindow_new (sink); } static void gst_glsink_release_conn (GstGLSink *sink) { if (sink->conn == NULL) return; /* free last image if any */ if (sink->last_image != NULL) { gst_buffer_unref (sink->last_image); sink->last_image = NULL; } /* free cache */ g_mutex_lock (sink->cache_lock); while (sink->cache) { sink->plugin->free_image ((GstImageData *) sink->cache->data); sink->cache = g_list_delete_link (sink->cache, sink->cache); } g_mutex_unlock (sink->cache_lock); /* release connection */ sink->conn->free_conn (sink->conn); sink->conn = NULL; } static void gst_glsink_append_cache (GstGLSink *sink, GstImageData *image) { g_mutex_lock (sink->cache_lock); sink->cache = g_list_prepend (sink->cache, image); g_mutex_unlock (sink->cache_lock); } /* Create a new buffer to hand up the chain. This allows the plugins to make its own decoding buffers */ static GstBuffer* gst_glsink_buffer_new (GstBufferPool *pool, gint64 location, guint size, gpointer user_data) { GstGLSink *sink; GstBuffer *buffer; GstImageData *image; sink = GST_GLSINK (user_data); if (sink->cache != NULL) { g_mutex_lock (sink->cache_lock); image = (GstImageData *) sink->cache->data; sink->cache = g_list_delete_link (sink->cache, sink->cache); g_mutex_unlock (sink->cache_lock); } else { image = sink->plugin->get_image (sink->hook, sink->conn); } buffer = gst_buffer_new (); GST_BUFFER_DATA (buffer) = image->data; GST_BUFFER_SIZE (buffer) = image->size; GST_BUFFER_POOL_PRIVATE (buffer) = image; return buffer; } /* Free a buffer that the chain doesn't need anymore. */ static void gst_glsink_buffer_free (GstBufferPool *pool, GstBuffer *buffer, gpointer user_data) { GstGLSink *sink = GST_GLSINK (gst_buffer_pool_get_user_data (GST_BUFFER_BUFFERPOOL (buffer))); gst_glsink_append_cache (sink, (GstImageData *) GST_BUFFER_POOL_PRIVATE (buffer)); /* set to NULL so the data is not freed */ GST_BUFFER_DATA (buffer) = NULL; gst_buffer_default_free (buffer); } static GstBufferPool* gst_glsink_get_bufferpool (GstPad *pad) { GstGLSink *sink = GST_GLSINK (gst_pad_get_parent (pad)); return sink->bufferpool; } /* Set the caps that the application desires. Go through the plugin list, finding the plugin that first fits the given parameters */ static gboolean gst_glsink_set_caps (GstGLSink *sink, GstCaps *caps) { GList *list = ((GstGLSinkClass *) G_OBJECT_GET_CLASS (sink))->plugins; GstImageConnection *conn = NULL; while (list) { GstImagePlugin *plugin = (GstImagePlugin *) list->data; if ((conn = plugin->set_caps (sink->hook, caps)) != NULL) { gst_glsink_release_conn (sink); sink->conn = conn; sink->plugin = plugin; sink->conn->open_conn (sink->conn, sink->hook); return TRUE; } list = g_list_next (list); } return FALSE; } /** Link the input video sink internally. */ static GstPadLinkReturn gst_glsink_sinkconnect (GstPad *pad, GstCaps *caps) { GstGLSink *sink; guint32 fourcc, print_format; sink = GST_GLSINK (gst_pad_get_parent (pad)); /* we are not going to act on variable caps */ if (!GST_CAPS_IS_FIXED (caps)) return GST_PAD_LINK_DELAYED; /* try to set the caps on the output */ if (gst_glsink_set_caps (sink, caps) == FALSE) { return GST_PAD_LINK_REFUSED; } /* remember width & height */ gst_caps_get_int (caps, "width", &sink->width); gst_caps_get_int (caps, "height", &sink->height); gst_caps_get_fourcc_int (caps, "format", &fourcc); print_format = GULONG_FROM_LE (fourcc); GST_DEBUG (0, "glsink: setting %08x (%4.4s) %dx%d\n", fourcc, (gchar*)&print_format, sink->width, sink->height); /* emit signal */ g_object_freeze_notify (G_OBJECT (sink)); g_object_notify (G_OBJECT (sink), "width"); g_object_notify (G_OBJECT (sink), "height"); g_object_thaw_notify (G_OBJECT (sink)); return GST_PAD_LINK_OK; } static GstCaps * gst_glsink_getcaps (GstPad *pad, GstCaps *caps) { /* what is the "caps" parameter good for? */ GstGLSink *sink = GST_GLSINK (gst_pad_get_parent (pad)); GstCaps *ret = NULL; GList *list = ((GstGLSinkClass *) G_OBJECT_GET_CLASS (sink))->plugins; while (list) { ret = gst_caps_append (ret, ((GstImagePlugin *) list->data)->get_caps (sink->hook)); list = g_list_next (list); } return ret; } static void gst_glsink_set_clock (GstElement *element, GstClock *clock) { GstGLSink *sink = GST_GLSINK (element); sink->clock = clock; } static void gst_glsink_chain (GstPad *pad, GstBuffer *buf) { GstGLSink *sink; GstClockTime time = GST_BUFFER_TIMESTAMP (buf); GstBuffer *buffer; static int frame_drops = 0; g_return_if_fail (pad != NULL); g_return_if_fail (GST_IS_PAD (pad)); g_return_if_fail (buf != NULL); sink = GST_GLSINK (gst_pad_get_parent (pad)); if (GST_IS_EVENT (buf)) { GstEvent *event = GST_EVENT (buf); switch (GST_EVENT_TYPE (event)) { default: gst_pad_event_default (pad, event); } return; } GST_DEBUG (0,"glsink: clock wait: %llu %u", GST_BUFFER_TIMESTAMP (buf), GST_BUFFER_SIZE (buf)); #if 0 if (sink->clock && time != -1) { if (time < gst_clock_get_time(sink->clock)) { g_warning("Frame drop (%d consecutive) !!", frame_drops); /* we are going to drop late buffers */ gst_buffer_unref (buf); frame_drops++; return; } frame_drops = 0; // we made it - reset time GstClockReturn ret; GstClockID id = gst_clock_new_single_shot_id (sink->clock, GST_BUFFER_TIMESTAMP (buf)); ret = gst_element_clock_wait (GST_ELEMENT (sink), id, NULL); gst_clock_id_free (id); /* we are going to drop early buffers */ if (ret == GST_CLOCK_EARLY) { gst_buffer_unref (buf); return; } } #endif /* call the notify _before_ displaying so the handlers can react */ sink->frames_displayed++; g_object_notify (G_OBJECT (sink), "frames_displayed"); if (!sink->muted) { /* free last_image, if any */ if (sink->last_image != NULL) gst_buffer_unref (sink->last_image); if (sink->bufferpool && GST_BUFFER_BUFFERPOOL (buf) == sink->bufferpool) { sink->plugin->put_image (sink->hook, (GstImageData *) GST_BUFFER_POOL_PRIVATE (buf)); sink->last_image = buf; } else { buffer = gst_buffer_new_from_pool (gst_glsink_get_bufferpool (sink->sinkpad), 0, GST_BUFFER_SIZE (buf)); memcpy (GST_BUFFER_DATA (buffer), GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf) > GST_BUFFER_SIZE (buffer) ? GST_BUFFER_SIZE (buffer) : GST_BUFFER_SIZE (buf)); sink->plugin->put_image (sink->hook, (GstImageData *) GST_BUFFER_POOL_PRIVATE (buffer)); sink->last_image = buffer; gst_buffer_unref (buf); } } } static void gst_glsink_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GstGLSink *sink; /* it's not null if we got it, but it might not be ours */ g_return_if_fail (GST_IS_GLSINK (object)); sink = GST_GLSINK (object); switch (prop_id) { case ARG_FRAMES_DISPLAYED: sink->frames_displayed = g_value_get_int (value); g_object_notify (object, "frames_displayed"); break; case ARG_FRAME_TIME: sink->frame_time = g_value_get_int (value); break; case ARG_HOOK: if (sink->hook) { sink->hook->free_info (sink->hook); } sink->hook = g_value_get_pointer (value); break; case ARG_MUTE: sink->muted = g_value_get_boolean (value); g_object_notify (object, "mute"); break; case ARG_REPAINT: if (sink->last_image != NULL) { sink->plugin->put_image (sink->hook, (GstImageData *) GST_BUFFER_POOL_PRIVATE (sink->last_image)); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_glsink_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GstGLSink *sink; /* it's not null if we got it, but it might not be ours */ sink = GST_GLSINK(object); switch (prop_id) { case ARG_WIDTH: g_value_set_int (value, sink->width); break; case ARG_HEIGHT: g_value_set_int (value, sink->height); break; case ARG_FRAMES_DISPLAYED: g_value_set_int (value, sink->frames_displayed); break; case ARG_FRAME_TIME: g_value_set_int (value, sink->frame_time/1000000); break; case ARG_MUTE: g_value_set_boolean (value, sink->muted); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstElementStateReturn gst_glsink_change_state (GstElement *element) { GstGLSink *sink; sink = GST_GLSINK (element); switch (GST_STATE_TRANSITION (element)) { case GST_STATE_NULL_TO_READY: break; case GST_STATE_READY_TO_PAUSED: { //g_warning("Going GST_STATE_READY_TO_PAUSED: %p", sink->conn); } break; case GST_STATE_PAUSED_TO_PLAYING: { //g_warning("Going GST_STATE_PAUSED_TO_PLAYING: %p", sink->conn); } break; case GST_STATE_PLAYING_TO_PAUSED: break; case GST_STATE_PAUSED_TO_READY: if (sink->conn) sink->conn->close_conn (sink->conn, sink->hook); if (sink->last_image) { gst_buffer_unref (sink->last_image); sink->last_image = NULL; } break; case GST_STATE_READY_TO_NULL: gst_glsink_release_conn (sink); break; } parent_class->change_state (element); return GST_STATE_SUCCESS; } static gboolean plugin_init (GModule *module, GstPlugin *plugin) { GstElementFactory *factory; /* create an elementfactory for the xvideosink element */ factory = gst_element_factory_new("glsink",GST_TYPE_GLSINK, &gst_glsink_details); g_return_val_if_fail(factory != NULL, FALSE); sink_template = gst_pad_template_new ( "sink", GST_PAD_SINK, GST_PAD_ALWAYS, NULL); gst_element_factory_add_pad_template (factory, sink_template); gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory)); return TRUE; } GstPluginDesc plugin_desc = { GST_VERSION_MAJOR, GST_VERSION_MINOR, "glsink", plugin_init };