• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer GdkPixbuf sink
2  * Copyright (C) 2006-2008 Tim-Philipp Müller <tim centricular net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is free software; you can redistribute it and/or
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /**
21  * SECTION:element-gdkpixbufsink
22  * @title: gdkpixbufsink
23  *
24  * This sink element takes RGB or RGBA images as input and wraps them into
25  * #GdkPixbuf objects, for easy saving to file via the
26  * GdkPixbuf library API or displaying in Gtk+ applications (e.g. using
27  * the #GtkImage widget).
28  *
29  * There are two ways to use this element and obtain the #GdkPixbuf objects
30  * created:
31  *
32  * * Watching for element messages named `preroll-pixbuf` or `pixbuf` on the bus, which
33  * will be posted whenever an image would usually be rendered. See below for
34  * more details on these messages and how to extract the pixbuf object
35  * contained in them.
36  *
37  * * Retrieving the current pixbuf via the #GstGdkPixbufSink:last-pixbuf property
38  * when needed. This is the easiest way to get at pixbufs for snapshotting
39  * purposes - just wait until the pipeline is prerolled (ASYNC_DONE message
40  * on the bus), then read the property. If you use this method, you may want
41  * to disable message posting by setting the #GstGdkPixbufSink:post-messages
42  * property to %FALSE. This avoids unnecessary memory overhead.
43  *
44  * The primary purpose of this element is to abstract away the #GstBuffer to
45  * #GdkPixbuf conversion. Other than that it's very similar to the fakesink
46  * element.
47  *
48  * This element is meant for easy no-hassle video snapshotting. It is not
49  * suitable for video playback or video display at high framerates. Use
50  * ximagesink, xvimagesink or some other suitable video sink in connection
51  * with the #GstVideoOverlay interface instead if you want to do video playback.
52  *
53  * ## Message details
54  *
55  * As mentioned above, this element will by default post element messages
56  * containing structures named `preroll-pixbuf`
57  * ` or `pixbuf` on the bus (this
58  * can be disabled by setting the #GstGdkPixbufSink:post-messages property
59  * to %FALSE though). The element message structure has the following fields:
60  *
61  * * `pixbuf`: the #GdkPixbuf object
62  * * `pixel-aspect-ratio`: the pixel aspect ratio (PAR) of the input image
63  *   (this field contains a value of type #GST_TYPE_FRACTION); the
64  *   PAR is usually 1:1 for images, but is often something non-1:1 in the case
65  *   of video input. In this case the image may be distorted and you may need
66  *   to rescale it accordingly before saving it to file or displaying it. This
67  *   can easily be done using gdk_pixbuf_scale() (the reason this is not done
68  *   automatically is that the application will often scale the image anyway
69  *   according to the size of the output window, in which case it is much more
70  *   efficient to only scale once rather than twice). You can put a videoscale
71  *   element and a capsfilter element with
72  *   `video/x-raw-rgb,pixel-aspect-ratio=(fraction)1/1` caps
73  *   in front of this element to make sure the pixbufs always have a 1:1 PAR.
74  *
75  * ## Example pipeline
76  * |[
77  * gst-launch-1.0 -m -v videotestsrc num-buffers=1 ! gdkpixbufsink
78  * ]| Process one single test image as pixbuf (note that the output you see will
79  * be slightly misleading. The message structure does contain a valid pixbuf
80  * object even if the structure string says &apos;(NULL)&apos;).
81  */
82 
83 #ifdef HAVE_CONFIG_H
84 #include "config.h"
85 #endif
86 
87 #include "gstgdkpixbufelements.h"
88 #include "gstgdkpixbufsink.h"
89 
90 #include <gst/video/video.h>
91 
92 #define DEFAULT_SEND_MESSAGES TRUE
93 #define DEFAULT_POST_MESSAGES TRUE
94 
95 enum
96 {
97   PROP_0,
98   PROP_POST_MESSAGES,
99   PROP_LAST_PIXBUF,
100   PROP_LAST
101 };
102 
103 
104 G_DEFINE_TYPE (GstGdkPixbufSink, gst_gdk_pixbuf_sink, GST_TYPE_VIDEO_SINK);
105 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (gdkpixbufsink, "gdkpixbufsink",
106     GST_RANK_NONE, GST_TYPE_GDK_PIXBUF_SINK, gdk_pixbuf_element_init (plugin));
107 
108 static void gst_gdk_pixbuf_sink_set_property (GObject * object, guint prop_id,
109     const GValue * value, GParamSpec * pspec);
110 static void gst_gdk_pixbuf_sink_get_property (GObject * object, guint prop_id,
111     GValue * value, GParamSpec * pspec);
112 
113 static gboolean gst_gdk_pixbuf_sink_start (GstBaseSink * basesink);
114 static gboolean gst_gdk_pixbuf_sink_stop (GstBaseSink * basesink);
115 static gboolean gst_gdk_pixbuf_sink_set_caps (GstBaseSink * basesink,
116     GstCaps * caps);
117 static GstFlowReturn gst_gdk_pixbuf_sink_render (GstBaseSink * bsink,
118     GstBuffer * buf);
119 static GstFlowReturn gst_gdk_pixbuf_sink_preroll (GstBaseSink * bsink,
120     GstBuffer * buf);
121 static GdkPixbuf *gst_gdk_pixbuf_sink_get_pixbuf_from_buffer (GstGdkPixbufSink *
122     sink, GstBuffer * buf);
123 
124 static GstStaticPadTemplate pixbufsink_sink_factory =
125     GST_STATIC_PAD_TEMPLATE ("sink",
126     GST_PAD_SINK,
127     GST_PAD_ALWAYS,
128     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB") ";"
129         GST_VIDEO_CAPS_MAKE ("RGBA"))
130     );
131 
132 static void
gst_gdk_pixbuf_sink_class_init(GstGdkPixbufSinkClass * klass)133 gst_gdk_pixbuf_sink_class_init (GstGdkPixbufSinkClass * klass)
134 {
135   GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass);
136   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
137   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
138 
139   gst_element_class_set_static_metadata (element_class, "GdkPixbuf sink",
140       "Sink/Video", "Output images as GdkPixbuf objects in bus messages",
141       "Tim-Philipp Müller <tim centricular net>");
142 
143   gst_element_class_add_static_pad_template (element_class,
144       &pixbufsink_sink_factory);
145 
146   gobject_class->set_property = gst_gdk_pixbuf_sink_set_property;
147   gobject_class->get_property = gst_gdk_pixbuf_sink_get_property;
148 
149   /**
150    * GstGdkPixbuf:post-messages:
151    *
152    * Post messages on the bus containing pixbufs.
153    */
154   g_object_class_install_property (gobject_class, PROP_POST_MESSAGES,
155       g_param_spec_boolean ("post-messages", "Post Messages",
156           "Whether to post messages containing pixbufs on the bus",
157           DEFAULT_POST_MESSAGES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
158 
159   g_object_class_install_property (gobject_class, PROP_LAST_PIXBUF,
160       g_param_spec_object ("last-pixbuf", "Last Pixbuf",
161           "Last GdkPixbuf object rendered", GDK_TYPE_PIXBUF,
162           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
163 
164   basesink_class->start = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_start);
165   basesink_class->stop = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_stop);
166   basesink_class->render = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_render);
167   basesink_class->preroll = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_preroll);
168   basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_set_caps);
169 }
170 
171 static void
gst_gdk_pixbuf_sink_init(GstGdkPixbufSink * sink)172 gst_gdk_pixbuf_sink_init (GstGdkPixbufSink * sink)
173 {
174   sink->par_n = 0;
175   sink->par_d = 0;
176   sink->has_alpha = FALSE;
177   sink->last_pixbuf = NULL;
178   sink->post_messages = DEFAULT_POST_MESSAGES;
179 
180   /* we're not a real video sink, we just derive from GstVideoSink in case
181    * anything interesting is added to it in future */
182   gst_base_sink_set_max_lateness (GST_BASE_SINK (sink), -1);
183   gst_base_sink_set_qos_enabled (GST_BASE_SINK (sink), FALSE);
184 }
185 
186 
187 static gboolean
gst_gdk_pixbuf_sink_start(GstBaseSink * basesink)188 gst_gdk_pixbuf_sink_start (GstBaseSink * basesink)
189 {
190   GST_LOG_OBJECT (basesink, "start");
191 
192   return TRUE;
193 }
194 
195 static gboolean
gst_gdk_pixbuf_sink_stop(GstBaseSink * basesink)196 gst_gdk_pixbuf_sink_stop (GstBaseSink * basesink)
197 {
198   GstGdkPixbufSink *sink = GST_GDK_PIXBUF_SINK (basesink);
199 
200   GST_VIDEO_SINK_WIDTH (sink) = 0;
201   GST_VIDEO_SINK_HEIGHT (sink) = 0;
202 
203   sink->par_n = 0;
204   sink->par_d = 0;
205   sink->has_alpha = FALSE;
206 
207   if (sink->last_pixbuf) {
208     g_object_unref (sink->last_pixbuf);
209     sink->last_pixbuf = NULL;
210   }
211 
212   GST_LOG_OBJECT (sink, "stop");
213 
214   return TRUE;
215 }
216 
217 static gboolean
gst_gdk_pixbuf_sink_set_caps(GstBaseSink * basesink,GstCaps * caps)218 gst_gdk_pixbuf_sink_set_caps (GstBaseSink * basesink, GstCaps * caps)
219 {
220   GstGdkPixbufSink *sink = GST_GDK_PIXBUF_SINK (basesink);
221   GstVideoInfo info;
222   GstVideoFormat fmt;
223   gint w, h, par_n, par_d;
224 
225   GST_LOG_OBJECT (sink, "caps: %" GST_PTR_FORMAT, caps);
226 
227   if (!gst_video_info_from_caps (&info, caps)) {
228     GST_WARNING_OBJECT (sink, "parse_caps failed");
229     return FALSE;
230   }
231 
232   fmt = GST_VIDEO_INFO_FORMAT (&info);
233   w = GST_VIDEO_INFO_WIDTH (&info);
234   h = GST_VIDEO_INFO_HEIGHT (&info);
235   par_n = GST_VIDEO_INFO_PAR_N (&info);
236   par_d = GST_VIDEO_INFO_PAR_N (&info);
237 
238 #ifndef G_DISABLE_ASSERT
239   {
240     gint s;
241     s = GST_VIDEO_INFO_COMP_PSTRIDE (&info, 0);
242     g_assert ((fmt == GST_VIDEO_FORMAT_RGB && s == 3) ||
243         (fmt == GST_VIDEO_FORMAT_RGBA && s == 4));
244   }
245 #endif
246 
247   GST_VIDEO_SINK_WIDTH (sink) = w;
248   GST_VIDEO_SINK_HEIGHT (sink) = h;
249 
250   sink->par_n = par_n;
251   sink->par_d = par_d;
252 
253   sink->has_alpha = GST_VIDEO_INFO_HAS_ALPHA (&info);
254 
255   GST_INFO_OBJECT (sink, "format             : %d", fmt);
256   GST_INFO_OBJECT (sink, "width x height     : %d x %d", w, h);
257   GST_INFO_OBJECT (sink, "pixel-aspect-ratio : %d/%d", par_n, par_d);
258 
259   sink->info = info;
260 
261   return TRUE;
262 }
263 
264 static void
gst_gdk_pixbuf_sink_pixbuf_destroy_notify(guchar * pixels,GstVideoFrame * frame)265 gst_gdk_pixbuf_sink_pixbuf_destroy_notify (guchar * pixels,
266     GstVideoFrame * frame)
267 {
268   gst_video_frame_unmap (frame);
269   gst_buffer_unref (frame->buffer);
270   g_slice_free (GstVideoFrame, frame);
271 }
272 
273 static GdkPixbuf *
gst_gdk_pixbuf_sink_get_pixbuf_from_buffer(GstGdkPixbufSink * sink,GstBuffer * buf)274 gst_gdk_pixbuf_sink_get_pixbuf_from_buffer (GstGdkPixbufSink * sink,
275     GstBuffer * buf)
276 {
277   GdkPixbuf *pix = NULL;
278   GstVideoFrame *frame;
279   gint minsize, bytes_per_pixel;
280 
281   g_return_val_if_fail (GST_VIDEO_SINK_WIDTH (sink) > 0, NULL);
282   g_return_val_if_fail (GST_VIDEO_SINK_HEIGHT (sink) > 0, NULL);
283 
284   frame = g_slice_new0 (GstVideoFrame);
285   gst_video_frame_map (frame, &sink->info, buf, GST_MAP_READ);
286 
287   bytes_per_pixel = (sink->has_alpha) ? 4 : 3;
288 
289   /* last row needn't have row padding */
290   minsize = (GST_VIDEO_FRAME_COMP_STRIDE (frame, 0) *
291       (GST_VIDEO_SINK_HEIGHT (sink) - 1)) +
292       (bytes_per_pixel * GST_VIDEO_SINK_WIDTH (sink));
293 
294   g_return_val_if_fail (gst_buffer_get_size (buf) >= minsize, NULL);
295 
296   gst_buffer_ref (buf);
297   pix = gdk_pixbuf_new_from_data (GST_VIDEO_FRAME_COMP_DATA (frame, 0),
298       GDK_COLORSPACE_RGB, sink->has_alpha, 8, GST_VIDEO_SINK_WIDTH (sink),
299       GST_VIDEO_SINK_HEIGHT (sink), GST_VIDEO_FRAME_COMP_STRIDE (frame, 0),
300       (GdkPixbufDestroyNotify) gst_gdk_pixbuf_sink_pixbuf_destroy_notify,
301       frame);
302 
303   return pix;
304 }
305 
306 static GstFlowReturn
gst_gdk_pixbuf_sink_handle_buffer(GstBaseSink * basesink,GstBuffer * buf,const gchar * msg_name)307 gst_gdk_pixbuf_sink_handle_buffer (GstBaseSink * basesink, GstBuffer * buf,
308     const gchar * msg_name)
309 {
310   GstGdkPixbufSink *sink;
311   GdkPixbuf *pixbuf;
312   gboolean do_post;
313 
314   sink = GST_GDK_PIXBUF_SINK (basesink);
315 
316   pixbuf = gst_gdk_pixbuf_sink_get_pixbuf_from_buffer (sink, buf);
317 
318   GST_OBJECT_LOCK (sink);
319 
320   do_post = sink->post_messages;
321 
322   if (sink->last_pixbuf)
323     g_object_unref (sink->last_pixbuf);
324 
325   sink->last_pixbuf = pixbuf;   /* take ownership */
326 
327   GST_OBJECT_UNLOCK (sink);
328 
329   if (G_UNLIKELY (pixbuf == NULL))
330     goto error;
331 
332   if (do_post) {
333     GstStructure *s;
334     GstMessage *msg;
335     GstFormat format;
336     GstClockTime timestamp;
337     GstClockTime running_time, stream_time;
338 
339     GstSegment *segment = &basesink->segment;
340     format = segment->format;
341 
342     timestamp = GST_BUFFER_PTS (buf);
343     running_time = gst_segment_to_running_time (segment, format, timestamp);
344     stream_time = gst_segment_to_stream_time (segment, format, timestamp);
345 
346     /* it's okay to keep using pixbuf here, we can be sure no one is going to
347      * unref or change sink->last_pixbuf before we return from this function.
348      * The structure will take its own ref to the pixbuf. */
349     s = gst_structure_new (msg_name,
350         "pixbuf", GDK_TYPE_PIXBUF, pixbuf,
351         "pixel-aspect-ratio", GST_TYPE_FRACTION, sink->par_n, sink->par_d,
352         "timestamp", G_TYPE_UINT64, timestamp,
353         "stream-time", G_TYPE_UINT64, stream_time,
354         "running-time", G_TYPE_UINT64, running_time, NULL);
355 
356     msg = gst_message_new_element (GST_OBJECT_CAST (sink), s);
357     gst_element_post_message (GST_ELEMENT_CAST (sink), msg);
358   }
359 
360   g_object_notify (G_OBJECT (sink), "last-pixbuf");
361 
362   return GST_FLOW_OK;
363 
364 /* ERRORS */
365 error:
366   {
367     /* This shouldn't really happen */
368     GST_ELEMENT_ERROR (sink, LIBRARY, FAILED,
369         ("Couldn't create pixbuf from RGB image."),
370         ("Probably not enough free memory"));
371     return GST_FLOW_ERROR;
372   }
373 }
374 
375 static GstFlowReturn
gst_gdk_pixbuf_sink_preroll(GstBaseSink * basesink,GstBuffer * buf)376 gst_gdk_pixbuf_sink_preroll (GstBaseSink * basesink, GstBuffer * buf)
377 {
378   return gst_gdk_pixbuf_sink_handle_buffer (basesink, buf, "preroll-pixbuf");
379 }
380 
381 static GstFlowReturn
gst_gdk_pixbuf_sink_render(GstBaseSink * basesink,GstBuffer * buf)382 gst_gdk_pixbuf_sink_render (GstBaseSink * basesink, GstBuffer * buf)
383 {
384   return gst_gdk_pixbuf_sink_handle_buffer (basesink, buf, "pixbuf");
385 }
386 
387 static void
gst_gdk_pixbuf_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)388 gst_gdk_pixbuf_sink_set_property (GObject * object, guint prop_id,
389     const GValue * value, GParamSpec * pspec)
390 {
391   GstGdkPixbufSink *sink;
392 
393   sink = GST_GDK_PIXBUF_SINK (object);
394 
395   switch (prop_id) {
396     case PROP_POST_MESSAGES:
397       GST_OBJECT_LOCK (sink);
398       sink->post_messages = g_value_get_boolean (value);
399       GST_OBJECT_UNLOCK (sink);
400       break;
401     default:
402       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
403       break;
404   }
405 }
406 
407 static void
gst_gdk_pixbuf_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)408 gst_gdk_pixbuf_sink_get_property (GObject * object, guint prop_id,
409     GValue * value, GParamSpec * pspec)
410 {
411   GstGdkPixbufSink *sink;
412 
413   sink = GST_GDK_PIXBUF_SINK (object);
414 
415   switch (prop_id) {
416     case PROP_POST_MESSAGES:
417       GST_OBJECT_LOCK (sink);
418       g_value_set_boolean (value, sink->post_messages);
419       GST_OBJECT_UNLOCK (sink);
420       break;
421     case PROP_LAST_PIXBUF:
422       GST_OBJECT_LOCK (sink);
423       g_value_set_object (value, sink->last_pixbuf);
424       GST_OBJECT_UNLOCK (sink);
425       break;
426     default:
427       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
428       break;
429   }
430 }
431