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 '(NULL)').
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