| /* GStreamer GdkPixbuf-based image decoder |
| * Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu> |
| * Copyright (C) 2003 David A. Schleef <ds@schleef.org> |
| * |
| * 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <gst/gst.h> |
| #include <gst/video/video.h> |
| #include <gdk-pixbuf/gdk-pixbuf.h> |
| #include <string.h> |
| |
| #include "gstgdkpixbufdec.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (gdkpixbufdec_debug); |
| #define GST_CAT_DEFAULT gdkpixbufdec_debug |
| |
| static GstStaticPadTemplate gst_gdk_pixbuf_dec_sink_template = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("image/png; " |
| /* "image/jpeg; " disabled because we can't handle MJPEG */ |
| /*"image/gif; " disabled because we can't handle animated gifs */ |
| "image/x-icon; " |
| "application/x-navi-animation; " |
| "image/x-cmu-raster; " |
| "image/x-sun-raster; " |
| "image/x-pixmap; " |
| "image/tiff; " |
| "image/x-portable-anymap; " |
| "image/x-portable-bitmap; " |
| "image/x-portable-graymap; " |
| "image/x-portable-pixmap; " |
| "image/bmp; " |
| "image/x-bmp; " |
| "image/x-MS-bmp; " |
| "image/vnd.wap.wbmp; " "image/x-bitmap; " "image/x-tga; " |
| "image/x-pcx; image/svg; image/svg+xml") |
| ); |
| |
| static GstStaticPadTemplate gst_gdk_pixbuf_dec_src_template = |
| GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB") "; " |
| GST_VIDEO_CAPS_MAKE ("RGBA")) |
| ); |
| |
| static GstStateChangeReturn |
| gst_gdk_pixbuf_dec_change_state (GstElement * element, |
| GstStateChange transition); |
| static GstFlowReturn gst_gdk_pixbuf_dec_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buffer); |
| static gboolean gst_gdk_pixbuf_dec_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| |
| #define gst_gdk_pixbuf_dec_parent_class parent_class |
| G_DEFINE_TYPE (GstGdkPixbufDec, gst_gdk_pixbuf_dec, GST_TYPE_ELEMENT); |
| |
| static gboolean |
| gst_gdk_pixbuf_dec_sink_setcaps (GstGdkPixbufDec * filter, GstCaps * caps) |
| { |
| const GValue *framerate; |
| GstStructure *s; |
| |
| s = gst_caps_get_structure (caps, 0); |
| |
| if ((framerate = gst_structure_get_value (s, "framerate")) != NULL) { |
| filter->in_fps_n = gst_value_get_fraction_numerator (framerate); |
| filter->in_fps_d = gst_value_get_fraction_denominator (framerate); |
| GST_DEBUG_OBJECT (filter, "got framerate of %d/%d fps => packetized mode", |
| filter->in_fps_n, filter->in_fps_d); |
| } else { |
| filter->in_fps_n = 0; |
| filter->in_fps_d = 1; |
| GST_DEBUG_OBJECT (filter, "no framerate, assuming single image"); |
| } |
| |
| return TRUE; |
| } |
| |
| static GstCaps * |
| gst_gdk_pixbuf_dec_get_capslist (GstCaps * filter) |
| { |
| GSList *slist; |
| GSList *slist0; |
| GstCaps *capslist = NULL; |
| GstCaps *return_caps = NULL; |
| GstCaps *tmpl_caps; |
| |
| capslist = gst_caps_new_empty (); |
| slist0 = gdk_pixbuf_get_formats (); |
| |
| for (slist = slist0; slist; slist = g_slist_next (slist)) { |
| GdkPixbufFormat *pixbuf_format; |
| char **mimetypes; |
| char **mimetype; |
| |
| pixbuf_format = slist->data; |
| mimetypes = gdk_pixbuf_format_get_mime_types (pixbuf_format); |
| |
| for (mimetype = mimetypes; *mimetype; mimetype++) { |
| gst_caps_append_structure (capslist, gst_structure_new_empty (*mimetype)); |
| } |
| g_strfreev (mimetypes); |
| } |
| g_slist_free (slist0); |
| |
| tmpl_caps = |
| gst_static_caps_get (&gst_gdk_pixbuf_dec_sink_template.static_caps); |
| return_caps = gst_caps_intersect (capslist, tmpl_caps); |
| |
| gst_caps_unref (tmpl_caps); |
| gst_caps_unref (capslist); |
| |
| if (filter && return_caps) { |
| GstCaps *temp; |
| |
| temp = gst_caps_intersect (return_caps, filter); |
| gst_caps_unref (return_caps); |
| return_caps = temp; |
| } |
| |
| return return_caps; |
| } |
| |
| static gboolean |
| gst_gdk_pixbuf_dec_sink_query (GstPad * pad, GstObject * parent, |
| GstQuery * query) |
| { |
| gboolean res; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_CAPS: |
| { |
| GstCaps *filter, *caps; |
| |
| gst_query_parse_caps (query, &filter); |
| caps = gst_gdk_pixbuf_dec_get_capslist (filter); |
| gst_query_set_caps_result (query, caps); |
| gst_caps_unref (caps); |
| |
| res = TRUE; |
| break; |
| } |
| default: |
| res = gst_pad_query_default (pad, parent, query); |
| break; |
| } |
| return res; |
| } |
| |
| |
| /* initialize the plugin's class */ |
| static void |
| gst_gdk_pixbuf_dec_class_init (GstGdkPixbufDecClass * klass) |
| { |
| GstElementClass *gstelement_class; |
| |
| gstelement_class = (GstElementClass *) klass; |
| |
| gstelement_class->change_state = |
| GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_dec_change_state); |
| |
| gst_element_class_add_static_pad_template (gstelement_class, |
| &gst_gdk_pixbuf_dec_src_template); |
| gst_element_class_add_static_pad_template (gstelement_class, |
| &gst_gdk_pixbuf_dec_sink_template); |
| gst_element_class_set_static_metadata (gstelement_class, |
| "GdkPixbuf image decoder", "Codec/Decoder/Image", |
| "Decodes images in a video stream using GdkPixbuf", |
| "David A. Schleef <ds@schleef.org>, Renato Filho <renato.filho@indt.org.br>"); |
| |
| GST_DEBUG_CATEGORY_INIT (gdkpixbufdec_debug, "gdkpixbuf", 0, |
| "GdkPixbuf image decoder"); |
| } |
| |
| static void |
| gst_gdk_pixbuf_dec_init (GstGdkPixbufDec * filter) |
| { |
| filter->sinkpad = |
| gst_pad_new_from_static_template (&gst_gdk_pixbuf_dec_sink_template, |
| "sink"); |
| gst_pad_set_query_function (filter->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_dec_sink_query)); |
| gst_pad_set_chain_function (filter->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_dec_chain)); |
| gst_pad_set_event_function (filter->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_dec_sink_event)); |
| gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad); |
| |
| filter->srcpad = |
| gst_pad_new_from_static_template (&gst_gdk_pixbuf_dec_src_template, |
| "src"); |
| gst_pad_use_fixed_caps (filter->srcpad); |
| gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad); |
| |
| filter->last_timestamp = GST_CLOCK_TIME_NONE; |
| filter->pixbuf_loader = NULL; |
| filter->packetized = FALSE; |
| } |
| |
| static gboolean |
| gst_gdk_pixbuf_dec_setup_pool (GstGdkPixbufDec * filter, GstVideoInfo * info) |
| { |
| GstCaps *target; |
| GstQuery *query; |
| GstBufferPool *pool; |
| GstStructure *config; |
| guint size, min, max; |
| |
| target = gst_pad_get_current_caps (filter->srcpad); |
| if (!target) |
| return FALSE; |
| |
| /* try to get a bufferpool now */ |
| /* find a pool for the negotiated caps now */ |
| query = gst_query_new_allocation (target, TRUE); |
| |
| if (!gst_pad_peer_query (filter->srcpad, query)) { |
| /* not a problem, we use the query defaults */ |
| GST_DEBUG_OBJECT (filter, "ALLOCATION query failed"); |
| } |
| |
| if (gst_query_get_n_allocation_pools (query) > 0) { |
| /* we got configuration from our peer, parse them */ |
| gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); |
| } else { |
| pool = NULL; |
| size = info->size; |
| min = max = 0; |
| } |
| |
| gst_query_unref (query); |
| |
| if (pool == NULL) { |
| /* we did not get a pool, make one ourselves then */ |
| pool = gst_buffer_pool_new (); |
| } |
| |
| /* and configure */ |
| config = gst_buffer_pool_get_config (pool); |
| gst_buffer_pool_config_set_params (config, target, size, min, max); |
| gst_buffer_pool_set_config (pool, config); |
| |
| if (filter->pool) { |
| gst_buffer_pool_set_active (filter->pool, FALSE); |
| gst_object_unref (filter->pool); |
| } |
| filter->pool = pool; |
| |
| /* and activate */ |
| gst_buffer_pool_set_active (filter->pool, TRUE); |
| |
| gst_caps_unref (target); |
| |
| return TRUE; |
| } |
| |
| static GstFlowReturn |
| gst_gdk_pixbuf_dec_flush (GstGdkPixbufDec * filter) |
| { |
| GstBuffer *outbuf; |
| GdkPixbuf *pixbuf; |
| int y; |
| guint8 *out_pix; |
| guint8 *in_pix; |
| int in_rowstride, out_rowstride; |
| GstFlowReturn ret; |
| GstCaps *caps = NULL; |
| gint width, height; |
| gint n_channels; |
| GstVideoFrame frame; |
| |
| pixbuf = gdk_pixbuf_loader_get_pixbuf (filter->pixbuf_loader); |
| if (pixbuf == NULL) |
| goto no_pixbuf; |
| |
| width = gdk_pixbuf_get_width (pixbuf); |
| height = gdk_pixbuf_get_height (pixbuf); |
| |
| if (GST_VIDEO_INFO_FORMAT (&filter->info) == GST_VIDEO_FORMAT_UNKNOWN) { |
| GstVideoInfo info; |
| GstVideoFormat fmt; |
| GList *l; |
| |
| GST_DEBUG ("Set size to %dx%d", width, height); |
| |
| n_channels = gdk_pixbuf_get_n_channels (pixbuf); |
| switch (n_channels) { |
| case 3: |
| fmt = GST_VIDEO_FORMAT_RGB; |
| break; |
| case 4: |
| fmt = GST_VIDEO_FORMAT_RGBA; |
| break; |
| default: |
| goto channels_not_supported; |
| } |
| |
| |
| gst_video_info_init (&info); |
| gst_video_info_set_format (&info, fmt, width, height); |
| info.fps_n = filter->in_fps_n; |
| info.fps_d = filter->in_fps_d; |
| caps = gst_video_info_to_caps (&info); |
| |
| filter->info = info; |
| |
| gst_pad_set_caps (filter->srcpad, caps); |
| gst_caps_unref (caps); |
| |
| gst_gdk_pixbuf_dec_setup_pool (filter, &info); |
| |
| for (l = filter->pending_events; l; l = l->next) |
| gst_pad_push_event (filter->srcpad, l->data); |
| g_list_free (filter->pending_events); |
| filter->pending_events = NULL; |
| } |
| |
| ret = gst_buffer_pool_acquire_buffer (filter->pool, &outbuf, NULL); |
| if (ret != GST_FLOW_OK) |
| goto no_buffer; |
| |
| GST_BUFFER_TIMESTAMP (outbuf) = filter->last_timestamp; |
| GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE; |
| |
| in_pix = gdk_pixbuf_get_pixels (pixbuf); |
| in_rowstride = gdk_pixbuf_get_rowstride (pixbuf); |
| |
| gst_video_frame_map (&frame, &filter->info, outbuf, GST_MAP_WRITE); |
| out_pix = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0); |
| out_rowstride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0); |
| |
| for (y = 0; y < height; y++) { |
| memcpy (out_pix, in_pix, width * GST_VIDEO_FRAME_COMP_PSTRIDE (&frame, 0)); |
| in_pix += in_rowstride; |
| out_pix += out_rowstride; |
| } |
| |
| gst_video_frame_unmap (&frame); |
| |
| GST_DEBUG ("pushing... %" G_GSIZE_FORMAT " bytes", |
| gst_buffer_get_size (outbuf)); |
| ret = gst_pad_push (filter->srcpad, outbuf); |
| |
| if (ret != GST_FLOW_OK) |
| GST_DEBUG_OBJECT (filter, "flow: %s", gst_flow_get_name (ret)); |
| |
| return ret; |
| |
| /* ERRORS */ |
| no_pixbuf: |
| { |
| GST_ELEMENT_ERROR (filter, STREAM, DECODE, (NULL), ("error geting pixbuf")); |
| return GST_FLOW_ERROR; |
| } |
| channels_not_supported: |
| { |
| GST_ELEMENT_ERROR (filter, STREAM, DECODE, (NULL), |
| ("%d channels not supported", n_channels)); |
| return GST_FLOW_ERROR; |
| } |
| no_buffer: |
| { |
| GST_DEBUG ("Failed to create outbuffer - %s", gst_flow_get_name (ret)); |
| return ret; |
| } |
| } |
| |
| static gboolean |
| gst_gdk_pixbuf_dec_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event) |
| { |
| GstFlowReturn res = GST_FLOW_OK; |
| gboolean ret = TRUE, forward = TRUE; |
| GstGdkPixbufDec *pixbuf; |
| |
| pixbuf = GST_GDK_PIXBUF_DEC (parent); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_CAPS: |
| { |
| GstCaps *caps; |
| |
| gst_event_parse_caps (event, &caps); |
| ret = gst_gdk_pixbuf_dec_sink_setcaps (pixbuf, caps); |
| forward = FALSE; |
| break; |
| } |
| case GST_EVENT_EOS: |
| if (pixbuf->pixbuf_loader != NULL) { |
| gdk_pixbuf_loader_close (pixbuf->pixbuf_loader, NULL); |
| res = gst_gdk_pixbuf_dec_flush (pixbuf); |
| g_object_unref (G_OBJECT (pixbuf->pixbuf_loader)); |
| pixbuf->pixbuf_loader = NULL; |
| /* as long as we don't have flow returns for event functions we need |
| * to post an error here, or the application might never know that |
| * things failed */ |
| if (res != GST_FLOW_OK && res != GST_FLOW_FLUSHING |
| && res != GST_FLOW_EOS && res != GST_FLOW_NOT_LINKED) { |
| GST_ELEMENT_FLOW_ERROR (pixbuf, res); |
| forward = FALSE; |
| ret = FALSE; |
| } |
| } |
| break; |
| case GST_EVENT_FLUSH_STOP: |
| g_list_free_full (pixbuf->pending_events, |
| (GDestroyNotify) gst_event_unref); |
| pixbuf->pending_events = NULL; |
| /* Fall through */ |
| case GST_EVENT_SEGMENT: |
| { |
| const GstSegment *segment; |
| gst_event_parse_segment (event, &segment); |
| if (segment->format == GST_FORMAT_BYTES) |
| pixbuf->packetized = FALSE; |
| else |
| pixbuf->packetized = TRUE; |
| if (pixbuf->pixbuf_loader != NULL) { |
| gdk_pixbuf_loader_close (pixbuf->pixbuf_loader, NULL); |
| g_object_unref (G_OBJECT (pixbuf->pixbuf_loader)); |
| pixbuf->pixbuf_loader = NULL; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| if (forward) { |
| if (!gst_pad_has_current_caps (pixbuf->srcpad) && |
| GST_EVENT_IS_SERIALIZED (event) |
| && GST_EVENT_TYPE (event) > GST_EVENT_CAPS |
| && GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_STOP |
| && GST_EVENT_TYPE (event) != GST_EVENT_EOS) { |
| ret = TRUE; |
| pixbuf->pending_events = g_list_prepend (pixbuf->pending_events, event); |
| } else { |
| ret = gst_pad_event_default (pad, parent, event); |
| } |
| } else { |
| gst_event_unref (event); |
| } |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_gdk_pixbuf_dec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) |
| { |
| GstGdkPixbufDec *filter; |
| GstFlowReturn ret = GST_FLOW_OK; |
| GError *error = NULL; |
| GstClockTime timestamp; |
| GstMapInfo map; |
| |
| filter = GST_GDK_PIXBUF_DEC (parent); |
| |
| timestamp = GST_BUFFER_TIMESTAMP (buf); |
| |
| if (GST_CLOCK_TIME_IS_VALID (timestamp)) |
| filter->last_timestamp = timestamp; |
| |
| GST_LOG_OBJECT (filter, "buffer with ts: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (timestamp)); |
| |
| if (filter->pixbuf_loader == NULL) |
| filter->pixbuf_loader = gdk_pixbuf_loader_new (); |
| |
| gst_buffer_map (buf, &map, GST_MAP_READ); |
| |
| GST_LOG_OBJECT (filter, "Writing buffer size %d", (gint) map.size); |
| if (!gdk_pixbuf_loader_write (filter->pixbuf_loader, map.data, map.size, |
| &error)) |
| goto error; |
| |
| if (filter->packetized == TRUE) { |
| gdk_pixbuf_loader_close (filter->pixbuf_loader, NULL); |
| ret = gst_gdk_pixbuf_dec_flush (filter); |
| g_object_unref (filter->pixbuf_loader); |
| filter->pixbuf_loader = NULL; |
| } |
| |
| gst_buffer_unmap (buf, &map); |
| gst_buffer_unref (buf); |
| |
| return ret; |
| |
| /* ERRORS */ |
| error: |
| { |
| GST_ELEMENT_ERROR (filter, STREAM, DECODE, (NULL), |
| ("gdk_pixbuf_loader_write error: %s", error->message)); |
| g_error_free (error); |
| gst_buffer_unmap (buf, &map); |
| gst_buffer_unref (buf); |
| return GST_FLOW_ERROR; |
| } |
| } |
| |
| static GstStateChangeReturn |
| gst_gdk_pixbuf_dec_change_state (GstElement * element, |
| GstStateChange transition) |
| { |
| GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; |
| GstGdkPixbufDec *dec = GST_GDK_PIXBUF_DEC (element); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| /* default to single image mode, setcaps function might not be called */ |
| dec->in_fps_n = 0; |
| dec->in_fps_d = 1; |
| gst_video_info_init (&dec->info); |
| break; |
| default: |
| break; |
| } |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| if (ret == GST_STATE_CHANGE_FAILURE) |
| return ret; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| dec->in_fps_n = 0; |
| dec->in_fps_d = 0; |
| if (dec->pool) { |
| gst_buffer_pool_set_active (dec->pool, FALSE); |
| gst_object_replace ((GstObject **) & dec->pool, NULL); |
| } |
| g_list_free_full (dec->pending_events, (GDestroyNotify) gst_event_unref); |
| dec->pending_events = NULL; |
| if (dec->pixbuf_loader != NULL) { |
| gdk_pixbuf_loader_close (dec->pixbuf_loader, NULL); |
| g_object_unref (G_OBJECT (dec->pixbuf_loader)); |
| dec->pixbuf_loader = NULL; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |