blob: 6c4cf3dc9a2df5f901dc9c390a7c527d0ac9350c [file] [log] [blame]
/* GStreamer GdkPixbuf sink
* Copyright (C) 2006-2008 Tim-Philipp Müller <tim centricular net>
*
* 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 free software; you can redistribute it and/or
* 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.
*/
/**
* SECTION:element-gdkpixbufsink
*
* This sink element takes RGB or RGBA images as input and wraps them into
* #GdkPixbuf objects, for easy saving to file via the
* GdkPixbuf library API or displaying in Gtk+ applications (e.g. using
* the #GtkImage widget).
*
* There are two ways to use this element and obtain the #GdkPixbuf objects
* created:
* <itemizedlist>
* <listitem>
* Watching for element messages named <classname>&quot;preroll-pixbuf&quot;
* </classname> or <classname>&quot;pixbuf&quot;</classname> on the bus, which
* will be posted whenever an image would usually be rendered. See below for
* more details on these messages and how to extract the pixbuf object
* contained in them.
* </listitem>
* <listitem>
* Retrieving the current pixbuf via the #GstGdkPixbufSink:last-pixbuf property
* when needed. This is the easiest way to get at pixbufs for snapshotting
* purposes - just wait until the pipeline is prerolled (ASYNC_DONE message
* on the bus), then read the property. If you use this method, you may want
* to disable message posting by setting the #GstGdkPixbufSink:post-messages
* property to %FALSE. This avoids unnecessary memory overhead.
* </listitem>
* </itemizedlist>
*
* The primary purpose of this element is to abstract away the #GstBuffer to
* #GdkPixbuf conversion. Other than that it's very similar to the fakesink
* element.
*
* This element is meant for easy no-hassle video snapshotting. It is not
* suitable for video playback or video display at high framerates. Use
* ximagesink, xvimagesink or some other suitable video sink in connection
* with the #GstXOverlay interface instead if you want to do video playback.
*
* <refsect2>
* <title>Message details</title>
* As mentioned above, this element will by default post element messages
* containing structures named <classname>&quot;preroll-pixbuf&quot;
* </classname> or <classname>&quot;pixbuf&quot;</classname> on the bus (this
* can be disabled by setting the #GstGdkPixbufSink:post-messages property
* to %FALSE though). The element message structure has the following fields:
* <itemizedlist>
* <listitem>
* <classname>&quot;pixbuf&quot;</classname>: the #GdkPixbuf object
* </listitem>
* <listitem>
* <classname>&quot;pixel-aspect-ratio&quot;</classname>: the pixel aspect
* ratio (PAR) of the input image (this field contains a #GstFraction); the
* PAR is usually 1:1 for images, but is often something non-1:1 in the case
* of video input. In this case the image may be distorted and you may need
* to rescale it accordingly before saving it to file or displaying it. This
* can easily be done using gdk_pixbuf_scale() (the reason this is not done
* automatically is that the application will often scale the image anyway
* according to the size of the output window, in which case it is much more
* efficient to only scale once rather than twice). You can put a videoscale
* element and a capsfilter element with
* <literal>video/x-raw-rgb,pixel-aspect-ratio=(fraction)1/1</literal> caps
* in front of this element to make sure the pixbufs always have a 1:1 PAR.
* </listitem>
* </itemizedlist>
* </refsect2>
*
* <refsect2>
* <title>Example pipeline</title>
* |[
* gst-launch-1.0 -m -v videotestsrc num-buffers=1 ! gdkpixbufsink
* ]| Process one single test image as pixbuf (note that the output you see will
* be slightly misleading. The message structure does contain a valid pixbuf
* object even if the structure string says &apos;(NULL)&apos;).
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstgdkpixbufsink.h"
#include <gst/video/video.h>
#define DEFAULT_SEND_MESSAGES TRUE
#define DEFAULT_POST_MESSAGES TRUE
enum
{
PROP_0,
PROP_POST_MESSAGES,
PROP_LAST_PIXBUF,
PROP_LAST
};
G_DEFINE_TYPE (GstGdkPixbufSink, gst_gdk_pixbuf_sink, GST_TYPE_VIDEO_SINK);
static void gst_gdk_pixbuf_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_gdk_pixbuf_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static gboolean gst_gdk_pixbuf_sink_start (GstBaseSink * basesink);
static gboolean gst_gdk_pixbuf_sink_stop (GstBaseSink * basesink);
static gboolean gst_gdk_pixbuf_sink_set_caps (GstBaseSink * basesink,
GstCaps * caps);
static GstFlowReturn gst_gdk_pixbuf_sink_render (GstBaseSink * bsink,
GstBuffer * buf);
static GstFlowReturn gst_gdk_pixbuf_sink_preroll (GstBaseSink * bsink,
GstBuffer * buf);
static GdkPixbuf *gst_gdk_pixbuf_sink_get_pixbuf_from_buffer (GstGdkPixbufSink *
sink, GstBuffer * buf);
static GstStaticPadTemplate pixbufsink_sink_factory =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB") ";"
GST_VIDEO_CAPS_MAKE ("RGBA"))
);
static void
gst_gdk_pixbuf_sink_class_init (GstGdkPixbufSinkClass * klass)
{
GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gst_element_class_set_static_metadata (element_class, "GdkPixbuf sink",
"Sink/Video", "Output images as GdkPixbuf objects in bus messages",
"Tim-Philipp Müller <tim centricular net>");
gst_element_class_add_static_pad_template (element_class,
&pixbufsink_sink_factory);
gobject_class->set_property = gst_gdk_pixbuf_sink_set_property;
gobject_class->get_property = gst_gdk_pixbuf_sink_get_property;
/**
* GstGdkPixbuf:post-messages:
*
* Post messages on the bus containing pixbufs.
*/
g_object_class_install_property (gobject_class, PROP_POST_MESSAGES,
g_param_spec_boolean ("post-messages", "Post Messages",
"Whether to post messages containing pixbufs on the bus",
DEFAULT_POST_MESSAGES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_LAST_PIXBUF,
g_param_spec_object ("last-pixbuf", "Last Pixbuf",
"Last GdkPixbuf object rendered", GDK_TYPE_PIXBUF,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
basesink_class->start = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_start);
basesink_class->stop = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_stop);
basesink_class->render = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_render);
basesink_class->preroll = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_preroll);
basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_set_caps);
}
static void
gst_gdk_pixbuf_sink_init (GstGdkPixbufSink * sink)
{
sink->par_n = 0;
sink->par_d = 0;
sink->has_alpha = FALSE;
sink->last_pixbuf = NULL;
sink->post_messages = DEFAULT_POST_MESSAGES;
/* we're not a real video sink, we just derive from GstVideoSink in case
* anything interesting is added to it in future */
gst_base_sink_set_max_lateness (GST_BASE_SINK (sink), -1);
gst_base_sink_set_qos_enabled (GST_BASE_SINK (sink), FALSE);
}
static gboolean
gst_gdk_pixbuf_sink_start (GstBaseSink * basesink)
{
GST_LOG_OBJECT (basesink, "start");
return TRUE;
}
static gboolean
gst_gdk_pixbuf_sink_stop (GstBaseSink * basesink)
{
GstGdkPixbufSink *sink = GST_GDK_PIXBUF_SINK (basesink);
GST_VIDEO_SINK_WIDTH (sink) = 0;
GST_VIDEO_SINK_HEIGHT (sink) = 0;
sink->par_n = 0;
sink->par_d = 0;
sink->has_alpha = FALSE;
if (sink->last_pixbuf) {
g_object_unref (sink->last_pixbuf);
sink->last_pixbuf = NULL;
}
GST_LOG_OBJECT (sink, "stop");
return TRUE;
}
static gboolean
gst_gdk_pixbuf_sink_set_caps (GstBaseSink * basesink, GstCaps * caps)
{
GstGdkPixbufSink *sink = GST_GDK_PIXBUF_SINK (basesink);
GstVideoInfo info;
GstVideoFormat fmt;
gint w, h, par_n, par_d;
GST_LOG_OBJECT (sink, "caps: %" GST_PTR_FORMAT, caps);
if (!gst_video_info_from_caps (&info, caps)) {
GST_WARNING_OBJECT (sink, "parse_caps failed");
return FALSE;
}
fmt = GST_VIDEO_INFO_FORMAT (&info);
w = GST_VIDEO_INFO_WIDTH (&info);
h = GST_VIDEO_INFO_HEIGHT (&info);
par_n = GST_VIDEO_INFO_PAR_N (&info);
par_d = GST_VIDEO_INFO_PAR_N (&info);
#ifndef G_DISABLE_ASSERT
{
gint s;
s = GST_VIDEO_INFO_COMP_PSTRIDE (&info, 0);
g_assert ((fmt == GST_VIDEO_FORMAT_RGB && s == 3) ||
(fmt == GST_VIDEO_FORMAT_RGBA && s == 4));
}
#endif
GST_VIDEO_SINK_WIDTH (sink) = w;
GST_VIDEO_SINK_HEIGHT (sink) = h;
sink->par_n = par_n;
sink->par_d = par_d;
sink->has_alpha = GST_VIDEO_INFO_HAS_ALPHA (&info);
GST_INFO_OBJECT (sink, "format : %d", fmt);
GST_INFO_OBJECT (sink, "width x height : %d x %d", w, h);
GST_INFO_OBJECT (sink, "pixel-aspect-ratio : %d/%d", par_n, par_d);
sink->info = info;
return TRUE;
}
static void
gst_gdk_pixbuf_sink_pixbuf_destroy_notify (guchar * pixels,
GstVideoFrame * frame)
{
gst_video_frame_unmap (frame);
gst_buffer_unref (frame->buffer);
g_slice_free (GstVideoFrame, frame);
}
static GdkPixbuf *
gst_gdk_pixbuf_sink_get_pixbuf_from_buffer (GstGdkPixbufSink * sink,
GstBuffer * buf)
{
GdkPixbuf *pix = NULL;
GstVideoFrame *frame;
gint minsize, bytes_per_pixel;
g_return_val_if_fail (GST_VIDEO_SINK_WIDTH (sink) > 0, NULL);
g_return_val_if_fail (GST_VIDEO_SINK_HEIGHT (sink) > 0, NULL);
frame = g_slice_new0 (GstVideoFrame);
gst_video_frame_map (frame, &sink->info, buf, GST_MAP_READ);
bytes_per_pixel = (sink->has_alpha) ? 4 : 3;
/* last row needn't have row padding */
minsize = (GST_VIDEO_FRAME_COMP_STRIDE (frame, 0) *
(GST_VIDEO_SINK_HEIGHT (sink) - 1)) +
(bytes_per_pixel * GST_VIDEO_SINK_WIDTH (sink));
g_return_val_if_fail (gst_buffer_get_size (buf) >= minsize, NULL);
gst_buffer_ref (buf);
pix = gdk_pixbuf_new_from_data (GST_VIDEO_FRAME_COMP_DATA (frame, 0),
GDK_COLORSPACE_RGB, sink->has_alpha, 8, GST_VIDEO_SINK_WIDTH (sink),
GST_VIDEO_SINK_HEIGHT (sink), GST_VIDEO_FRAME_COMP_STRIDE (frame, 0),
(GdkPixbufDestroyNotify) gst_gdk_pixbuf_sink_pixbuf_destroy_notify,
frame);
return pix;
}
static GstFlowReturn
gst_gdk_pixbuf_sink_handle_buffer (GstBaseSink * basesink, GstBuffer * buf,
const gchar * msg_name)
{
GstGdkPixbufSink *sink;
GdkPixbuf *pixbuf;
gboolean do_post;
sink = GST_GDK_PIXBUF_SINK (basesink);
pixbuf = gst_gdk_pixbuf_sink_get_pixbuf_from_buffer (sink, buf);
GST_OBJECT_LOCK (sink);
do_post = sink->post_messages;
if (sink->last_pixbuf)
g_object_unref (sink->last_pixbuf);
sink->last_pixbuf = pixbuf; /* take ownership */
GST_OBJECT_UNLOCK (sink);
if (G_UNLIKELY (pixbuf == NULL))
goto error;
if (do_post) {
GstStructure *s;
GstMessage *msg;
GstFormat format;
GstClockTime timestamp;
GstClockTime running_time, stream_time;
GstSegment *segment = &basesink->segment;
format = segment->format;
timestamp = GST_BUFFER_PTS (buf);
running_time = gst_segment_to_running_time (segment, format, timestamp);
stream_time = gst_segment_to_stream_time (segment, format, timestamp);
/* it's okay to keep using pixbuf here, we can be sure no one is going to
* unref or change sink->last_pixbuf before we return from this function.
* The structure will take its own ref to the pixbuf. */
s = gst_structure_new (msg_name,
"pixbuf", GDK_TYPE_PIXBUF, pixbuf,
"pixel-aspect-ratio", GST_TYPE_FRACTION, sink->par_n, sink->par_d,
"timestamp", G_TYPE_UINT64, timestamp,
"stream-time", G_TYPE_UINT64, stream_time,
"running-time", G_TYPE_UINT64, running_time, NULL);
msg = gst_message_new_element (GST_OBJECT_CAST (sink), s);
gst_element_post_message (GST_ELEMENT_CAST (sink), msg);
}
g_object_notify (G_OBJECT (sink), "last-pixbuf");
return GST_FLOW_OK;
/* ERRORS */
error:
{
/* This shouldn't really happen */
GST_ELEMENT_ERROR (sink, LIBRARY, FAILED,
("Couldn't create pixbuf from RGB image."),
("Probably not enough free memory"));
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
gst_gdk_pixbuf_sink_preroll (GstBaseSink * basesink, GstBuffer * buf)
{
return gst_gdk_pixbuf_sink_handle_buffer (basesink, buf, "preroll-pixbuf");
}
static GstFlowReturn
gst_gdk_pixbuf_sink_render (GstBaseSink * basesink, GstBuffer * buf)
{
return gst_gdk_pixbuf_sink_handle_buffer (basesink, buf, "pixbuf");
}
static void
gst_gdk_pixbuf_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstGdkPixbufSink *sink;
sink = GST_GDK_PIXBUF_SINK (object);
switch (prop_id) {
case PROP_POST_MESSAGES:
GST_OBJECT_LOCK (sink);
sink->post_messages = g_value_get_boolean (value);
GST_OBJECT_UNLOCK (sink);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_gdk_pixbuf_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstGdkPixbufSink *sink;
sink = GST_GDK_PIXBUF_SINK (object);
switch (prop_id) {
case PROP_POST_MESSAGES:
GST_OBJECT_LOCK (sink);
g_value_set_boolean (value, sink->post_messages);
GST_OBJECT_UNLOCK (sink);
break;
case PROP_LAST_PIXBUF:
GST_OBJECT_LOCK (sink);
g_value_set_object (value, sink->last_pixbuf);
GST_OBJECT_UNLOCK (sink);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}