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