| /* GStreamer |
| * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> |
| * |
| * 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. |
| */ |
| |
| /* |
| <ds-work> your element belongs to a scheduler, which calls some functions from the same thread |
| <ds-work> all the other functions could be called from any random thread |
| <gernot> ds-work: which are the "some" function in that case ? |
| <gernot> It is quite costly to do glXGetCurrentContext for every function call. |
| <ds-work> _chain, -get, _loop |
| */ |
| |
| #include <config.h> |
| #include <string.h> |
| |
| #include <X11/Xlib.h> |
| #include <X11/Xutil.h> |
| #include <GL/glx.h> |
| #include <GL/gl.h> |
| #include <GL/glu.h> |
| #include <sys/time.h> |
| |
| /*#define GST_DEBUG_FORCE_DISABLE*/ |
| |
| #include "gstglsink.h" |
| |
| /* elementfactory information */ |
| static GstElementDetails gst_glsink_details = { |
| "OpenGL Sink/GLX", |
| "Sink/GLVideo", |
| "An OpenGL based video sink - uses OpenGL and GLX to draw video, utilizing different acceleration options", |
| "Gernot Ziegler <gz@lysator.liu.se>" |
| }; |
| |
| /* default template - initiated with class struct to allow gst-register to work |
| with X running */ |
| GST_PAD_TEMPLATE_FACTORY (gst_glsink_sink_template_factory, |
| "sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_CAPS_NEW ( "glsink_rgbsink", "video/x-raw-rgb", |
| "framerate", GST_PROPS_FLOAT_RANGE(0, G_MAXFLOAT), |
| "width", GST_PROPS_INT_RANGE(0, G_MAXINT), |
| "height", GST_PROPS_INT_RANGE(0, G_MAXINT)), |
| GST_CAPS_NEW ( "glsink_yuvsink", "video/x-raw-yuv", |
| "framerate", GST_PROPS_FLOAT_RANGE(0, G_MAXFLOAT), |
| "width", GST_PROPS_INT_RANGE(0, G_MAXINT), |
| "height", GST_PROPS_INT_RANGE(0, G_MAXINT)) |
| ) |
| |
| /* 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, |
| ARG_DEMO, |
| ARG_DUMP |
| }; |
| |
| /* 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; |
| gint demo; // some kind of fun demo mode to let GL show its 3D capabilities |
| gboolean dumpvideo; // dump the video down to .ppm:s |
| 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_base_init (gpointer g_class); |
| 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, GstData *_data); |
| 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); |
| extern void gst_glxwindow_hook_context (GstImageInfo *info); |
| extern void gst_glxwindow_unhook_context (GstImageInfo *info); |
| |
| |
| 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), |
| gst_glsink_base_init, |
| 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_base_init (gpointer g_class) |
| { |
| GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); |
| |
| gst_element_class_set_details (element_class, &gst_glsink_details); |
| |
| gst_element_class_add_pad_template ( |
| element_class, |
| GST_PAD_TEMPLATE_GET (gst_glsink_sink_template_factory)); |
| } |
| |
| 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); |
| |
| 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)); |
| g_object_class_install_property (gobject_class, ARG_DEMO, |
| g_param_spec_int ("demo", "Demo", "demo mode (shows 3D capabilities)",0, 1, 0, G_PARAM_READWRITE)); |
| g_object_class_install_property (gobject_class, ARG_DUMP, |
| g_param_spec_boolean ("dump", "Dump", "stores sequence of frames in .ppm files", FALSE, G_PARAM_READWRITE)); |
| |
| gobject_class->set_property = gst_glsink_set_property; |
| gobject_class->get_property = gst_glsink_get_property; |
| |
| /*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_gl_rgbimage_plugin ()); |
| klass->plugins = g_list_append (klass->plugins, get_gl_nvimage_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 ("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); |
| //printf("GLSink_init: Current context %p\n", glXGetCurrentContext()); |
| gst_glxwindow_unhook_context(sink->hook); |
| } |
| |
| /** frees the temporary connection that tests the window system capabilities */ |
| 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 cache is non-empty, get buffer from there */ |
| 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 { |
| /* otherwise, get one from the plugin */ |
| 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) |
| { |
| g_warning("in glsink set caps!\n"); |
| printf("Getting GLstring, context is %p\n", glXGetCurrentContext()); |
| |
| GList *list = ((GstGLSinkClass *) G_OBJECT_GET_CLASS (sink))->plugins; |
| GstImageConnection *conn = NULL; |
| while (list) |
| { |
| printf("AGetting GLstring, context is %p\n", glXGetCurrentContext()); |
| GstImagePlugin *plugin = (GstImagePlugin *) list->data; |
| if ((conn = plugin->set_caps (sink->hook, caps)) != NULL) |
| { |
| //gst_glsink_release_conn (sink); |
| printf("BGetting GLstring, context is %p\n", glXGetCurrentContext()); |
| sink->conn = conn; |
| printf("CGetting GLstring, context is %p\n", glXGetCurrentContext()); |
| 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) |
| { |
| g_warning("in glsink sinkconnect!\n"); |
| GstGLSink *sink; |
| guint32 fourcc, print_format, result; |
| |
| 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; |
| |
| gst_glxwindow_hook_context(sink->hook); |
| /* try to set the caps on the output */ |
| result = gst_glsink_set_caps (sink, caps); |
| gst_glxwindow_unhook_context(sink->hook); |
| |
| if (result == 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 ("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) |
| { |
| g_warning("in glsink get caps!\n"); |
| /* 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; |
| |
| gst_glxwindow_hook_context(sink->hook); |
| while (list) |
| { |
| ret = gst_caps_append (ret, ((GstImagePlugin *) list->data)->get_caps (sink->hook)); |
| list = g_list_next (list); |
| } |
| |
| gst_glxwindow_unhook_context(sink->hook); |
| 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, GstData *_data) |
| { |
| //g_warning("in glsink_chain!\n"); |
| GstBuffer *buf = GST_BUFFER (_data); |
| GstGLSink *sink; |
| |
| GstBuffer *buffer; |
| |
| 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 ("glsink: clock wait: %llu %u", |
| GST_BUFFER_TIMESTAMP (buf), GST_BUFFER_SIZE (buf)); |
| |
| #if 0 |
| GstClockTime time = GST_BUFFER_TIMESTAMP (buf); |
| static int frame_drops = 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) |
| { |
| if (glXGetCurrentContext() == NULL) |
| { |
| printf("Rehooking window !\n"); |
| gst_glxwindow_hook_context(sink->hook); |
| |
| #if 1 |
| GST_DEBUG("Initializing OpenGL parameters\n"); |
| /* initialize OpenGL drawing */ |
| glEnable(GL_DEPTH_TEST); |
| glEnable(GL_TEXTURE_2D); |
| glClearDepth(1.0f); |
| glClearColor(0, 0, 0, 0); |
| |
| glEnable(GL_AUTO_NORMAL); // let OpenGL generate the Normals |
| |
| glDisable(GL_BLEND); |
| glDisable(GL_CULL_FACE); |
| glPolygonMode(GL_FRONT, GL_FILL); |
| glPolygonMode(GL_BACK, GL_FILL); |
| |
| glShadeModel(GL_SMOOTH); |
| glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST); |
| |
| GstGLImageInfo *window = (GstGLImageInfo *)sink->hook; |
| int w=window->width, h = window->height; |
| |
| glViewport(0, 0, (GLint) w, (GLint) h); |
| glMatrixMode(GL_PROJECTION); |
| glLoadIdentity(); |
| |
| GLfloat aspect = (GLfloat) w / (GLfloat) h; |
| glFrustum(-aspect, aspect, -1.0, 1.0, 5.0, 500.0); |
| |
| #endif |
| gst_glxwindow_unhook_context(sink->hook); |
| gst_glxwindow_hook_context(sink->hook); |
| glMatrixMode(GL_MODELVIEW); |
| #if 0 |
| sink->hook->free_info(sink->hook); |
| printf("Reallocating window brutally !\n"); |
| gst_glxwindow_new(sink); |
| #endif |
| } |
| |
| /* 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) { |
| |
| // awful hack ! But I currently have no other solution without changing the API |
| sink->hook->demo = sink->demo; |
| sink->hook->dumpvideo = sink->dumpvideo; |
| |
| 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); |
| } |
| |
| //gst_glxwindow_unhook_context(sink->hook); |
| } |
| |
| } |
| |
| |
| static void |
| gst_glsink_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) |
| { |
| //g_warning("in set_property!\n"); |
| 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_DEMO: |
| sink->demo = g_value_get_int (value); |
| g_object_notify (object, "demo"); |
| break; |
| case ARG_DUMP: |
| sink->dumpvideo = g_value_get_boolean (value); |
| g_object_notify (object, "dump"); |
| 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) |
| { |
| //g_warning("in get_property!\n"); |
| 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; |
| case ARG_DEMO: |
| g_value_set_int (value, sink->demo); |
| break; |
| case ARG_DUMP: |
| g_value_set_boolean (value, sink->dumpvideo); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| |
| static GstElementStateReturn |
| gst_glsink_change_state (GstElement *element) |
| { |
| //g_warning("in change_state!\n"); |
| 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 (GstPlugin *plugin) |
| { |
| /* Loading the library containing GstVideoSink, our parent object */ |
| if (!gst_library_load ("gstvideo")) |
| return FALSE; |
| |
| /* this is needed later on in the _real_ init (during a gst-launch) */ |
| sink_template = gst_pad_template_new ( |
| "sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| NULL); |
| |
| if (!gst_element_register (plugin, "glsink", GST_RANK_NONE, GST_TYPE_GLSINK)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| GST_PLUGIN_DEFINE ( |
| GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| "glsink", |
| "An OpenGL based video sink - uses OpenGL and GLX to draw video, utilizing different acceleration options", |
| plugin_init, |
| VERSION, |
| GST_LICENSE, |
| GST_PACKAGE, |
| GST_ORIGIN |
| ); |