| 1 | /* |
|---|
| 2 | * Copyright (C) 2007 OpenedHand |
|---|
| 3 | * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
|---|
| 4 | * |
|---|
| 5 | * This library is free software; you can redistribute it and/or |
|---|
| 6 | * modify it under the terms of the GNU Lesser General Public |
|---|
| 7 | * License as published by the Free Software Foundation; either |
|---|
| 8 | * version 2 of the License, or (at your option) any later version. |
|---|
| 9 | * |
|---|
| 10 | * This library is distributed in the hope that it will be useful, |
|---|
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 13 | * Lesser General Public License for more details. |
|---|
| 14 | * |
|---|
| 15 | * You should have received a copy of the GNU Lesser General Public |
|---|
| 16 | * License along with this library; if not, write to the Free Software |
|---|
| 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|---|
| 18 | */ |
|---|
| 19 | |
|---|
| 20 | /** |
|---|
| 21 | * SECTION:webkit-video-sink |
|---|
| 22 | * @short_description: GStreamer video sink |
|---|
| 23 | * |
|---|
| 24 | * #WebKitVideoSink is a GStreamer sink element that sends |
|---|
| 25 | * data to a #cairo_surface_t. |
|---|
| 26 | */ |
|---|
| 27 | |
|---|
| 28 | #include "config.h" |
|---|
| 29 | #include "VideoSinkGStreamer.h" |
|---|
| 30 | |
|---|
| 31 | #include <glib.h> |
|---|
| 32 | #include <gst/gst.h> |
|---|
| 33 | #include <gst/video/video.h> |
|---|
| 34 | |
|---|
| 35 | static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE("sink", |
|---|
| 36 | GST_PAD_SINK, GST_PAD_ALWAYS, |
|---|
| 37 | GST_STATIC_CAPS(GST_VIDEO_CAPS_RGBx ";" GST_VIDEO_CAPS_BGRx)); |
|---|
| 38 | |
|---|
| 39 | GST_DEBUG_CATEGORY_STATIC(webkit_video_sink_debug); |
|---|
| 40 | #define GST_CAT_DEFAULT webkit_video_sink_debug |
|---|
| 41 | |
|---|
| 42 | static GstElementDetails webkit_video_sink_details = |
|---|
| 43 | GST_ELEMENT_DETAILS((gchar*) "WebKit video sink", |
|---|
| 44 | (gchar*) "Sink/Video", |
|---|
| 45 | (gchar*) "Sends video data from a GStreamer pipeline to a Cairo surface", |
|---|
| 46 | (gchar*) "Alp Toker <alp@atoker.com>"); |
|---|
| 47 | |
|---|
| 48 | enum { |
|---|
| 49 | PROP_0, |
|---|
| 50 | PROP_SURFACE |
|---|
| 51 | }; |
|---|
| 52 | |
|---|
| 53 | struct _WebKitVideoSinkPrivate { |
|---|
| 54 | cairo_surface_t* surface; |
|---|
| 55 | GAsyncQueue* async_queue; |
|---|
| 56 | gboolean rgb_ordering; |
|---|
| 57 | int width; |
|---|
| 58 | int height; |
|---|
| 59 | int fps_n; |
|---|
| 60 | int fps_d; |
|---|
| 61 | int par_n; |
|---|
| 62 | int par_d; |
|---|
| 63 | }; |
|---|
| 64 | |
|---|
| 65 | #define _do_init(bla) \ |
|---|
| 66 | GST_DEBUG_CATEGORY_INIT (webkit_video_sink_debug, \ |
|---|
| 67 | "webkitsink", \ |
|---|
| 68 | 0, \ |
|---|
| 69 | "webkit video sink") |
|---|
| 70 | |
|---|
| 71 | GST_BOILERPLATE_FULL(WebKitVideoSink, |
|---|
| 72 | webkit_video_sink, |
|---|
| 73 | GstBaseSink, |
|---|
| 74 | GST_TYPE_BASE_SINK, |
|---|
| 75 | _do_init); |
|---|
| 76 | |
|---|
| 77 | static void |
|---|
| 78 | webkit_video_sink_base_init(gpointer g_class) |
|---|
| 79 | { |
|---|
| 80 | GstElementClass* element_class = GST_ELEMENT_CLASS(g_class); |
|---|
| 81 | |
|---|
| 82 | gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&sinktemplate)); |
|---|
| 83 | gst_element_class_set_details(element_class, &webkit_video_sink_details); |
|---|
| 84 | } |
|---|
| 85 | |
|---|
| 86 | static void |
|---|
| 87 | webkit_video_sink_init(WebKitVideoSink* sink, WebKitVideoSinkClass* klass) |
|---|
| 88 | { |
|---|
| 89 | WebKitVideoSinkPrivate* priv; |
|---|
| 90 | |
|---|
| 91 | sink->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE(sink, WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkPrivate); |
|---|
| 92 | priv->async_queue = g_async_queue_new(); |
|---|
| 93 | } |
|---|
| 94 | |
|---|
| 95 | static gboolean |
|---|
| 96 | webkit_video_sink_idle_func(gpointer data) |
|---|
| 97 | { |
|---|
| 98 | WebKitVideoSinkPrivate* priv; |
|---|
| 99 | GstBuffer* buffer; |
|---|
| 100 | |
|---|
| 101 | priv = (WebKitVideoSinkPrivate*)data; |
|---|
| 102 | |
|---|
| 103 | if (!priv->async_queue) |
|---|
| 104 | return FALSE; |
|---|
| 105 | |
|---|
| 106 | buffer = (GstBuffer*)g_async_queue_try_pop(priv->async_queue); |
|---|
| 107 | if (buffer == NULL || G_UNLIKELY(!GST_IS_BUFFER(buffer))) |
|---|
| 108 | return FALSE; |
|---|
| 109 | |
|---|
| 110 | // TODO: consider priv->rgb_ordering? |
|---|
| 111 | 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); |
|---|
| 112 | |
|---|
| 113 | // TODO: We copy the data twice right now. This could be easily improved. |
|---|
| 114 | cairo_t* cr = cairo_create(priv->surface); |
|---|
| 115 | cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); |
|---|
| 116 | cairo_set_source_surface(cr, src, 0, 0); |
|---|
| 117 | cairo_surface_destroy(src); |
|---|
| 118 | cairo_rectangle(cr, 0, 0, priv->width, priv->height); |
|---|
| 119 | cairo_fill(cr); |
|---|
| 120 | cairo_destroy(cr); |
|---|
| 121 | |
|---|
| 122 | gst_buffer_unref(buffer); |
|---|
| 123 | |
|---|
| 124 | return FALSE; |
|---|
| 125 | } |
|---|
| 126 | |
|---|
| 127 | static GstFlowReturn |
|---|
| 128 | webkit_video_sink_render(GstBaseSink* bsink, GstBuffer* buffer) |
|---|
| 129 | { |
|---|
| 130 | WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(bsink); |
|---|
| 131 | WebKitVideoSinkPrivate* priv = sink->priv; |
|---|
| 132 | |
|---|
| 133 | g_async_queue_push(priv->async_queue, gst_buffer_ref(buffer)); |
|---|
| 134 | g_idle_add_full(G_PRIORITY_HIGH_IDLE, webkit_video_sink_idle_func, priv, NULL); |
|---|
| 135 | |
|---|
| 136 | return GST_FLOW_OK; |
|---|
| 137 | } |
|---|
| 138 | |
|---|
| 139 | static gboolean |
|---|
| 140 | webkit_video_sink_set_caps(GstBaseSink* bsink, GstCaps* caps) |
|---|
| 141 | { |
|---|
| 142 | WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(bsink); |
|---|
| 143 | WebKitVideoSinkPrivate* priv = sink->priv; |
|---|
| 144 | GstStructure* structure; |
|---|
| 145 | gboolean ret; |
|---|
| 146 | const GValue* fps; |
|---|
| 147 | const GValue* par; |
|---|
| 148 | gint width, height; |
|---|
| 149 | int red_mask; |
|---|
| 150 | |
|---|
| 151 | GstCaps* intersection = gst_caps_intersect(gst_static_pad_template_get_caps(&sinktemplate), caps); |
|---|
| 152 | |
|---|
| 153 | if (gst_caps_is_empty(intersection)) |
|---|
| 154 | return FALSE; |
|---|
| 155 | |
|---|
| 156 | gst_caps_unref(intersection); |
|---|
| 157 | |
|---|
| 158 | structure = gst_caps_get_structure(caps, 0); |
|---|
| 159 | |
|---|
| 160 | ret = gst_structure_get_int(structure, "width", &width); |
|---|
| 161 | ret &= gst_structure_get_int(structure, "height", &height); |
|---|
| 162 | fps = gst_structure_get_value(structure, "framerate"); |
|---|
| 163 | ret &= (fps != NULL); |
|---|
| 164 | |
|---|
| 165 | par = gst_structure_get_value(structure, "pixel-aspect-ratio"); |
|---|
| 166 | |
|---|
| 167 | if (!ret) |
|---|
| 168 | return FALSE; |
|---|
| 169 | |
|---|
| 170 | priv->width = width; |
|---|
| 171 | priv->height = height; |
|---|
| 172 | |
|---|
| 173 | /* We dont yet use fps or pixel aspect into but handy to have */ |
|---|
| 174 | priv->fps_n = gst_value_get_fraction_numerator(fps); |
|---|
| 175 | priv->fps_d = gst_value_get_fraction_denominator(fps); |
|---|
| 176 | |
|---|
| 177 | if (par) { |
|---|
| 178 | priv->par_n = gst_value_get_fraction_numerator(par); |
|---|
| 179 | priv->par_d = gst_value_get_fraction_denominator(par); |
|---|
| 180 | } else |
|---|
| 181 | priv->par_n = priv->par_d = 1; |
|---|
| 182 | |
|---|
| 183 | gst_structure_get_int(structure, "red_mask", &red_mask); |
|---|
| 184 | priv->rgb_ordering = (red_mask == static_cast<int>(0xff000000)); |
|---|
| 185 | |
|---|
| 186 | return TRUE; |
|---|
| 187 | } |
|---|
| 188 | |
|---|
| 189 | static void |
|---|
| 190 | webkit_video_sink_dispose(GObject* object) |
|---|
| 191 | { |
|---|
| 192 | WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); |
|---|
| 193 | WebKitVideoSinkPrivate* priv = sink->priv; |
|---|
| 194 | |
|---|
| 195 | if (priv->surface) { |
|---|
| 196 | cairo_surface_destroy(priv->surface); |
|---|
| 197 | priv->surface = NULL; |
|---|
| 198 | } |
|---|
| 199 | |
|---|
| 200 | if (priv->async_queue) { |
|---|
| 201 | g_async_queue_unref(priv->async_queue); |
|---|
| 202 | priv->async_queue = NULL; |
|---|
| 203 | } |
|---|
| 204 | |
|---|
| 205 | G_OBJECT_CLASS(parent_class)->dispose(object); |
|---|
| 206 | } |
|---|
| 207 | |
|---|
| 208 | static void |
|---|
| 209 | webkit_video_sink_finalize(GObject* object) |
|---|
| 210 | { |
|---|
| 211 | G_OBJECT_CLASS(parent_class)->finalize(object); |
|---|
| 212 | } |
|---|
| 213 | |
|---|
| 214 | static void |
|---|
| 215 | webkit_video_sink_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) |
|---|
| 216 | { |
|---|
| 217 | WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); |
|---|
| 218 | WebKitVideoSinkPrivate* priv = sink->priv; |
|---|
| 219 | |
|---|
| 220 | switch (prop_id) { |
|---|
| 221 | case PROP_SURFACE: |
|---|
| 222 | if (priv->surface) |
|---|
| 223 | cairo_surface_destroy(priv->surface); |
|---|
| 224 | priv->surface = cairo_surface_reference((cairo_surface_t*)g_value_get_pointer(value)); |
|---|
| 225 | break; |
|---|
| 226 | default: |
|---|
| 227 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
|---|
| 228 | break; |
|---|
| 229 | } |
|---|
| 230 | } |
|---|
| 231 | |
|---|
| 232 | static void |
|---|
| 233 | webkit_video_sink_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) |
|---|
| 234 | { |
|---|
| 235 | WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); |
|---|
| 236 | |
|---|
| 237 | switch (prop_id) { |
|---|
| 238 | case PROP_SURFACE: |
|---|
| 239 | g_value_set_pointer(value, sink->priv->surface); |
|---|
| 240 | break; |
|---|
| 241 | default: |
|---|
| 242 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
|---|
| 243 | break; |
|---|
| 244 | } |
|---|
| 245 | } |
|---|
| 246 | |
|---|
| 247 | static gboolean |
|---|
| 248 | webkit_video_sink_stop(GstBaseSink* base_sink) |
|---|
| 249 | { |
|---|
| 250 | WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(base_sink)->priv; |
|---|
| 251 | |
|---|
| 252 | g_async_queue_lock(priv->async_queue); |
|---|
| 253 | |
|---|
| 254 | /* Remove all remaining objects from the queue */ |
|---|
| 255 | while(GstBuffer* buffer = (GstBuffer*)g_async_queue_try_pop_unlocked(priv->async_queue)) |
|---|
| 256 | gst_buffer_unref(buffer); |
|---|
| 257 | |
|---|
| 258 | g_async_queue_unlock(priv->async_queue); |
|---|
| 259 | |
|---|
| 260 | return TRUE; |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | static void |
|---|
| 264 | webkit_video_sink_class_init(WebKitVideoSinkClass* klass) |
|---|
| 265 | { |
|---|
| 266 | GObjectClass* gobject_class = G_OBJECT_CLASS(klass); |
|---|
| 267 | GstBaseSinkClass* gstbase_sink_class = GST_BASE_SINK_CLASS(klass); |
|---|
| 268 | |
|---|
| 269 | g_type_class_add_private(klass, sizeof(WebKitVideoSinkPrivate)); |
|---|
| 270 | |
|---|
| 271 | gobject_class->set_property = webkit_video_sink_set_property; |
|---|
| 272 | gobject_class->get_property = webkit_video_sink_get_property; |
|---|
| 273 | |
|---|
| 274 | gobject_class->dispose = webkit_video_sink_dispose; |
|---|
| 275 | gobject_class->finalize = webkit_video_sink_finalize; |
|---|
| 276 | |
|---|
| 277 | gstbase_sink_class->render = webkit_video_sink_render; |
|---|
| 278 | gstbase_sink_class->preroll = webkit_video_sink_render; |
|---|
| 279 | gstbase_sink_class->stop = webkit_video_sink_stop; |
|---|
| 280 | gstbase_sink_class->set_caps = webkit_video_sink_set_caps; |
|---|
| 281 | |
|---|
| 282 | g_object_class_install_property( |
|---|
| 283 | gobject_class, PROP_SURFACE, |
|---|
| 284 | g_param_spec_pointer("surface", "surface", "Target cairo_surface_t*", |
|---|
| 285 | (GParamFlags)(G_PARAM_READWRITE))); |
|---|
| 286 | } |
|---|
| 287 | |
|---|
| 288 | /** |
|---|
| 289 | * webkit_video_sink_new: |
|---|
| 290 | * @surface: a #cairo_surface_t |
|---|
| 291 | * |
|---|
| 292 | * Creates a new GStreamer video sink which uses @surface as the target |
|---|
| 293 | * for sinking a video stream from GStreamer. |
|---|
| 294 | * |
|---|
| 295 | * Return value: a #GstElement for the newly created video sink |
|---|
| 296 | */ |
|---|
| 297 | GstElement* |
|---|
| 298 | webkit_video_sink_new(cairo_surface_t* surface) |
|---|
| 299 | { |
|---|
| 300 | return (GstElement*)g_object_new(WEBKIT_TYPE_VIDEO_SINK, "surface", surface, NULL); |
|---|
| 301 | } |
|---|
| 302 | |
|---|
| 303 | void |
|---|
| 304 | webkit_video_sink_set_surface(WebKitVideoSink* sink, cairo_surface_t* surface) |
|---|
| 305 | { |
|---|
| 306 | WebKitVideoSinkPrivate* priv; |
|---|
| 307 | |
|---|
| 308 | sink->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE(sink, WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkPrivate); |
|---|
| 309 | if (priv->surface) |
|---|
| 310 | cairo_surface_destroy(priv->surface); |
|---|
| 311 | priv->surface = cairo_surface_reference(surface); |
|---|
| 312 | } |
|---|