| /* |
| * Copyright (C) 2007 OpenedHand |
| * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser 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 |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| /** |
| * SECTION:webkit-video-sink |
| * @short_description: GStreamer video sink |
| * |
| * #WebKitVideoSink is a GStreamer sink element that sends |
| * data to a #cairo_surface_t. |
| */ |
| |
| #include "config.h" |
| #include "VideoSinkGStreamer.h" |
| |
| #include <glib.h> |
| #include <gst/gst.h> |
| #include <gst/video/video.h> |
| |
| static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE("sink", |
| GST_PAD_SINK, GST_PAD_ALWAYS, |
| GST_STATIC_CAPS(GST_VIDEO_CAPS_RGBx ";" GST_VIDEO_CAPS_BGRx)); |
| |
| GST_DEBUG_CATEGORY_STATIC(webkit_video_sink_debug); |
| #define GST_CAT_DEFAULT webkit_video_sink_debug |
| |
| static GstElementDetails webkit_video_sink_details = |
| GST_ELEMENT_DETAILS("WebKit video sink", |
| "Sink/Video", |
| "Sends video data from a GStreamer pipeline to a Cairo surface", |
| "Alp Toker <alp@atoker.com>"); |
| |
| enum { |
| PROP_0, |
| PROP_SURFACE |
| }; |
| |
| struct _WebKitVideoSinkPrivate { |
| cairo_surface_t* surface; |
| GAsyncQueue* async_queue; |
| gboolean rgb_ordering; |
| int width; |
| int height; |
| int fps_n; |
| int fps_d; |
| int par_n; |
| int par_d; |
| }; |
| |
| #define _do_init(bla) \ |
| GST_DEBUG_CATEGORY_INIT (webkit_video_sink_debug, \ |
| "webkitsink", \ |
| 0, \ |
| "webkit video sink") |
| |
| GST_BOILERPLATE_FULL(WebKitVideoSink, |
| webkit_video_sink, |
| GstBaseSink, |
| GST_TYPE_BASE_SINK, |
| _do_init); |
| |
| static void |
| webkit_video_sink_base_init(gpointer g_class) |
| { |
| GstElementClass* element_class = GST_ELEMENT_CLASS(g_class); |
| |
| gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&sinktemplate)); |
| gst_element_class_set_details(element_class, &webkit_video_sink_details); |
| } |
| |
| static void |
| webkit_video_sink_init(WebKitVideoSink* sink, WebKitVideoSinkClass* klass) |
| { |
| WebKitVideoSinkPrivate* priv; |
| |
| sink->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE(sink, WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkPrivate); |
| priv->async_queue = g_async_queue_new(); |
| } |
| |
| static gboolean |
| webkit_video_sink_idle_func(gpointer data) |
| { |
| WebKitVideoSinkPrivate* priv; |
| GstBuffer* buffer; |
| |
| priv = (WebKitVideoSinkPrivate*)data; |
| |
| if (!priv->async_queue) |
| return FALSE; |
| |
| buffer = (GstBuffer*)g_async_queue_try_pop(priv->async_queue); |
| if (buffer == NULL || G_UNLIKELY(!GST_IS_BUFFER(buffer))) |
| return FALSE; |
| |
| // TODO: consider priv->rgb_ordering? |
| cairo_surface_t* src = cairo_image_surface_create_for_data(GST_BUFFER_DATA(buffer), CAIRO_FORMAT_RGB24, priv->width, priv->height, (4 * priv->width + 3) & ~ 3); |
| |
| // TODO: We copy the data twice right now. This could be easily improved. |
| cairo_t* cr = cairo_create(priv->surface); |
| cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); |
| cairo_set_source_surface(cr, src, 0, 0); |
| cairo_surface_destroy(src); |
| cairo_rectangle(cr, 0, 0, priv->width, priv->height); |
| cairo_fill(cr); |
| cairo_destroy(cr); |
| |
| gst_buffer_unref(buffer); |
| |
| return FALSE; |
| } |
| |
| static GstFlowReturn |
| webkit_video_sink_render(GstBaseSink* bsink, GstBuffer* buffer) |
| { |
| WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(bsink); |
| WebKitVideoSinkPrivate* priv = sink->priv; |
| |
| g_async_queue_push(priv->async_queue, gst_buffer_ref(buffer)); |
| g_idle_add_full(G_PRIORITY_HIGH_IDLE, webkit_video_sink_idle_func, priv, NULL); |
| |
| return GST_FLOW_OK; |
| } |
| |
| static gboolean |
| webkit_video_sink_set_caps(GstBaseSink* bsink, GstCaps* caps) |
| { |
| WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(bsink); |
| WebKitVideoSinkPrivate* priv = sink->priv; |
| GstStructure* structure; |
| gboolean ret; |
| const GValue* fps; |
| const GValue* par; |
| gint width, height; |
| int red_mask; |
| |
| GstCaps* intersection = gst_caps_intersect(gst_static_pad_template_get_caps(&sinktemplate), caps); |
| |
| if (gst_caps_is_empty(intersection)) |
| return FALSE; |
| |
| gst_caps_unref(intersection); |
| |
| structure = gst_caps_get_structure(caps, 0); |
| |
| ret = gst_structure_get_int(structure, "width", &width); |
| ret &= gst_structure_get_int(structure, "height", &height); |
| fps = gst_structure_get_value(structure, "framerate"); |
| ret &= (fps != NULL); |
| |
| par = gst_structure_get_value(structure, "pixel-aspect-ratio"); |
| |
| if (!ret) |
| return FALSE; |
| |
| priv->width = width; |
| priv->height = height; |
| |
| /* We dont yet use fps or pixel aspect into but handy to have */ |
| priv->fps_n = gst_value_get_fraction_numerator(fps); |
| priv->fps_d = gst_value_get_fraction_denominator(fps); |
| |
| if (par) { |
| priv->par_n = gst_value_get_fraction_numerator(par); |
| priv->par_d = gst_value_get_fraction_denominator(par); |
| } else |
| priv->par_n = priv->par_d = 1; |
| |
| gst_structure_get_int(structure, "red_mask", &red_mask); |
| priv->rgb_ordering = (red_mask == 0xff000000); |
| |
| return TRUE; |
| } |
| |
| static void |
| webkit_video_sink_dispose(GObject* object) |
| { |
| WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); |
| WebKitVideoSinkPrivate* priv = sink->priv; |
| |
| if (priv->surface) { |
| cairo_surface_destroy(priv->surface); |
| priv->surface = NULL; |
| } |
| |
| if (priv->async_queue) { |
| g_async_queue_unref(priv->async_queue); |
| priv->async_queue = NULL; |
| } |
| |
| G_OBJECT_CLASS(parent_class)->dispose(object); |
| } |
| |
| static void |
| webkit_video_sink_finalize(GObject* object) |
| { |
| WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); |
| |
| G_OBJECT_CLASS(parent_class)->finalize(object); |
| } |
| |
| static void |
| webkit_video_sink_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) |
| { |
| WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); |
| WebKitVideoSinkPrivate* priv = sink->priv; |
| |
| switch (prop_id) { |
| case PROP_SURFACE: |
| if (priv->surface) |
| cairo_surface_destroy(priv->surface); |
| priv->surface = cairo_surface_reference((cairo_surface_t*)g_value_get_pointer(value)); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| webkit_video_sink_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) |
| { |
| WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); |
| |
| switch (prop_id) { |
| case PROP_SURFACE: |
| g_value_set_pointer(value, sink->priv->surface); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static gboolean |
| webkit_video_sink_stop(GstBaseSink* base_sink) |
| { |
| WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(base_sink)->priv; |
| |
| g_async_queue_lock(priv->async_queue); |
| |
| /* Remove all remaining objects from the queue */ |
| while(GstBuffer* buffer = (GstBuffer*)g_async_queue_try_pop_unlocked(priv->async_queue)) |
| gst_buffer_unref(buffer); |
| |
| g_async_queue_unlock(priv->async_queue); |
| |
| return TRUE; |
| } |
| |
| static void |
| webkit_video_sink_class_init(WebKitVideoSinkClass* klass) |
| { |
| GObjectClass* gobject_class = G_OBJECT_CLASS(klass); |
| GstBaseSinkClass* gstbase_sink_class = GST_BASE_SINK_CLASS(klass); |
| |
| g_type_class_add_private(klass, sizeof(WebKitVideoSinkPrivate)); |
| |
| gobject_class->set_property = webkit_video_sink_set_property; |
| gobject_class->get_property = webkit_video_sink_get_property; |
| |
| gobject_class->dispose = webkit_video_sink_dispose; |
| gobject_class->finalize = webkit_video_sink_finalize; |
| |
| gstbase_sink_class->render = webkit_video_sink_render; |
| gstbase_sink_class->preroll = webkit_video_sink_render; |
| gstbase_sink_class->stop = webkit_video_sink_stop; |
| gstbase_sink_class->set_caps = webkit_video_sink_set_caps; |
| |
| g_object_class_install_property( |
| gobject_class, PROP_SURFACE, |
| g_param_spec_pointer("surface", "surface", "Target cairo_surface_t*", |
| (GParamFlags)(G_PARAM_READWRITE))); |
| } |
| |
| /** |
| * webkit_video_sink_new: |
| * @surface: a #cairo_surface_t |
| * |
| * Creates a new GStreamer video sink which uses @surface as the target |
| * for sinking a video stream from GStreamer. |
| * |
| * Return value: a #GstElement for the newly created video sink |
| */ |
| GstElement* |
| webkit_video_sink_new(cairo_surface_t* surface) |
| { |
| return (GstElement*)g_object_new(WEBKIT_TYPE_VIDEO_SINK, "surface", surface, NULL); |
| } |
| |
| void |
| webkit_video_sink_set_surface(WebKitVideoSink* sink, cairo_surface_t* surface) |
| { |
| WebKitVideoSinkPrivate* priv; |
| |
| sink->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE(sink, WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkPrivate); |
| if (priv->surface) |
| cairo_surface_destroy(priv->surface); |
| priv->surface = cairo_surface_reference(surface); |
| } |