1 /* 2 * Copyright (C) 2007 OpenedHand 3 * Copyright (C) 2007 Alp Toker <alp (at) 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 triggers 25 * repaints in the WebKit GStreamer media player for the 26 * current video buffer. 27 */ 28 29 #include "config.h" 30 #include "VideoSinkGStreamer.h" 31 32 #include <glib.h> 33 #include <gst/gst.h> 34 #include <gst/video/video.h> 35 36 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE("sink", 37 GST_PAD_SINK, GST_PAD_ALWAYS, 38 // CAIRO_FORMAT_RGB24 used to render the video buffers is little/big endian dependant. 39 #if G_BYTE_ORDER == G_LITTLE_ENDIAN 40 GST_STATIC_CAPS(GST_VIDEO_CAPS_BGRx ";" GST_VIDEO_CAPS_BGRA) 41 #else 42 GST_STATIC_CAPS(GST_VIDEO_CAPS_xRGB ";" GST_VIDEO_CAPS_ARGB) 43 #endif 44 ); 45 46 GST_DEBUG_CATEGORY_STATIC(webkit_video_sink_debug); 47 #define GST_CAT_DEFAULT webkit_video_sink_debug 48 49 enum { 50 REPAINT_REQUESTED, 51 LAST_SIGNAL 52 }; 53 54 enum { 55 PROP_0 56 }; 57 58 static guint webkit_video_sink_signals[LAST_SIGNAL] = { 0, }; 59 60 struct _WebKitVideoSinkPrivate { 61 GstBuffer* buffer; 62 guint timeout_id; 63 GMutex* buffer_mutex; 64 GCond* data_cond; 65 66 // If this is TRUE all processing should finish ASAP 67 // This is necessary because there could be a race between 68 // unlock() and render(), where unlock() wins, signals the 69 // GCond, then render() tries to render a frame although 70 // everything else isn't running anymore. This will lead 71 // to deadlocks because render() holds the stream lock. 72 // 73 // Protected by the buffer mutex 74 gboolean unlocked; 75 }; 76 77 #define _do_init(bla) \ 78 GST_DEBUG_CATEGORY_INIT(webkit_video_sink_debug, \ 79 "webkitsink", \ 80 0, \ 81 "webkit video sink") 82 83 GST_BOILERPLATE_FULL(WebKitVideoSink, 84 webkit_video_sink, 85 GstVideoSink, 86 GST_TYPE_VIDEO_SINK, 87 _do_init); 88 89 static void 90 webkit_video_sink_base_init(gpointer g_class) 91 { 92 GstElementClass* element_class = GST_ELEMENT_CLASS(g_class); 93 94 gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&sinktemplate)); 95 gst_element_class_set_details_simple(element_class, "WebKit video sink", 96 "Sink/Video", "Sends video data from a GStreamer pipeline to a Cairo surface", 97 "Alp Toker <alp (at) atoker.com>"); 98 } 99 100 static void 101 webkit_video_sink_init(WebKitVideoSink* sink, WebKitVideoSinkClass* klass) 102 { 103 WebKitVideoSinkPrivate* priv; 104 105 sink->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE(sink, WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkPrivate); 106 priv->data_cond = g_cond_new(); 107 priv->buffer_mutex = g_mutex_new(); 108 } 109 110 static gboolean 111 webkit_video_sink_timeout_func(gpointer data) 112 { 113 WebKitVideoSink* sink = reinterpret_cast<WebKitVideoSink*>(data); 114 WebKitVideoSinkPrivate* priv = sink->priv; 115 GstBuffer* buffer; 116 117 g_mutex_lock(priv->buffer_mutex); 118 buffer = priv->buffer; 119 priv->buffer = 0; 120 priv->timeout_id = 0; 121 122 if (!buffer || priv->unlocked || G_UNLIKELY(!GST_IS_BUFFER(buffer))) { 123 g_cond_signal(priv->data_cond); 124 g_mutex_unlock(priv->buffer_mutex); 125 return FALSE; 126 } 127 128 g_signal_emit(sink, webkit_video_sink_signals[REPAINT_REQUESTED], 0, buffer); 129 gst_buffer_unref(buffer); 130 g_cond_signal(priv->data_cond); 131 g_mutex_unlock(priv->buffer_mutex); 132 133 return FALSE; 134 } 135 136 static GstFlowReturn 137 webkit_video_sink_render(GstBaseSink* bsink, GstBuffer* buffer) 138 { 139 WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(bsink); 140 WebKitVideoSinkPrivate* priv = sink->priv; 141 142 g_mutex_lock(priv->buffer_mutex); 143 144 if (priv->unlocked) { 145 g_mutex_unlock(priv->buffer_mutex); 146 return GST_FLOW_OK; 147 } 148 149 priv->buffer = gst_buffer_ref(buffer); 150 151 // For the unlikely case where the buffer has no caps, the caps 152 // are implicitely the caps of the pad. This shouldn't happen. 153 if (G_UNLIKELY(!GST_BUFFER_CAPS(buffer))) { 154 buffer = priv->buffer = gst_buffer_make_metadata_writable(priv->buffer); 155 gst_buffer_set_caps(priv->buffer, GST_PAD_CAPS(GST_BASE_SINK_PAD(bsink))); 156 } 157 158 GstCaps *caps = GST_BUFFER_CAPS(buffer); 159 GstVideoFormat format; 160 int width, height; 161 if (G_UNLIKELY(!gst_video_format_parse_caps(caps, &format, &width, &height))) { 162 gst_buffer_unref(buffer); 163 g_mutex_unlock(priv->buffer_mutex); 164 return GST_FLOW_ERROR; 165 } 166 167 // Cairo's ARGB has pre-multiplied alpha while GStreamer's doesn't. 168 // Here we convert to Cairo's ARGB. 169 if (format == GST_VIDEO_FORMAT_ARGB || format == GST_VIDEO_FORMAT_BGRA) { 170 // Because GstBaseSink::render() only owns the buffer reference in the 171 // method scope we can't use gst_buffer_make_writable() here. Also 172 // The buffer content should not be changed here because the same buffer 173 // could be passed multiple times to this method (in theory) 174 GstBuffer *newBuffer = gst_buffer_try_new_and_alloc(GST_BUFFER_SIZE(buffer)); 175 176 // Check if allocation failed 177 if (G_UNLIKELY(!newBuffer)) { 178 gst_buffer_unref(buffer); 179 g_mutex_unlock(priv->buffer_mutex); 180 return GST_FLOW_ERROR; 181 } 182 183 gst_buffer_copy_metadata(newBuffer, buffer, (GstBufferCopyFlags) GST_BUFFER_COPY_ALL); 184 185 // We don't use Color::premultipliedARGBFromColor() here because 186 // one function call per video pixel is just too expensive: 187 // For 720p/PAL for example this means 1280*720*25=23040000 188 // function calls per second! 189 unsigned short alpha; 190 const guint8 *source = GST_BUFFER_DATA(buffer); 191 guint8 *destination = GST_BUFFER_DATA(newBuffer); 192 193 for (int x = 0; x < height; x++) { 194 for (int y = 0; y < width; y++) { 195 #if G_BYTE_ORDER == G_LITTLE_ENDIAN 196 alpha = source[3]; 197 destination[0] = (source[0] * alpha + 128) / 255; 198 destination[1] = (source[1] * alpha + 128) / 255; 199 destination[2] = (source[2] * alpha + 128) / 255; 200 destination[3] = alpha; 201 #else 202 alpha = source[0]; 203 destination[0] = alpha; 204 destination[1] = (source[1] * alpha + 128) / 255; 205 destination[2] = (source[2] * alpha + 128) / 255; 206 destination[3] = (source[3] * alpha + 128) / 255; 207 #endif 208 source += 4; 209 destination += 4; 210 } 211 } 212 gst_buffer_unref(buffer); 213 buffer = priv->buffer = newBuffer; 214 } 215 216 // Use HIGH_IDLE+20 priority, like Gtk+ for redrawing operations. 217 priv->timeout_id = g_timeout_add_full(G_PRIORITY_HIGH_IDLE + 20, 0, 218 webkit_video_sink_timeout_func, 219 gst_object_ref(sink), 220 (GDestroyNotify)gst_object_unref); 221 222 g_cond_wait(priv->data_cond, priv->buffer_mutex); 223 g_mutex_unlock(priv->buffer_mutex); 224 return GST_FLOW_OK; 225 } 226 227 static void 228 webkit_video_sink_dispose(GObject* object) 229 { 230 WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); 231 WebKitVideoSinkPrivate* priv = sink->priv; 232 233 if (priv->data_cond) { 234 g_cond_free(priv->data_cond); 235 priv->data_cond = 0; 236 } 237 238 if (priv->buffer_mutex) { 239 g_mutex_free(priv->buffer_mutex); 240 priv->buffer_mutex = 0; 241 } 242 243 G_OBJECT_CLASS(parent_class)->dispose(object); 244 } 245 246 static void 247 unlock_buffer_mutex(WebKitVideoSinkPrivate* priv) 248 { 249 g_mutex_lock(priv->buffer_mutex); 250 251 if (priv->buffer) { 252 gst_buffer_unref(priv->buffer); 253 priv->buffer = 0; 254 } 255 256 priv->unlocked = TRUE; 257 258 g_cond_signal(priv->data_cond); 259 g_mutex_unlock(priv->buffer_mutex); 260 } 261 262 static gboolean 263 webkit_video_sink_unlock(GstBaseSink* object) 264 { 265 WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); 266 267 unlock_buffer_mutex(sink->priv); 268 269 return GST_CALL_PARENT_WITH_DEFAULT(GST_BASE_SINK_CLASS, unlock, 270 (object), TRUE); 271 } 272 273 static gboolean 274 webkit_video_sink_unlock_stop(GstBaseSink* object) 275 { 276 WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object); 277 WebKitVideoSinkPrivate* priv = sink->priv; 278 279 g_mutex_lock(priv->buffer_mutex); 280 priv->unlocked = FALSE; 281 g_mutex_unlock(priv->buffer_mutex); 282 283 return GST_CALL_PARENT_WITH_DEFAULT(GST_BASE_SINK_CLASS, unlock_stop, 284 (object), TRUE); 285 } 286 287 static gboolean 288 webkit_video_sink_stop(GstBaseSink* base_sink) 289 { 290 WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(base_sink)->priv; 291 292 unlock_buffer_mutex(priv); 293 return TRUE; 294 } 295 296 static gboolean 297 webkit_video_sink_start(GstBaseSink* base_sink) 298 { 299 WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(base_sink)->priv; 300 301 g_mutex_lock(priv->buffer_mutex); 302 priv->unlocked = FALSE; 303 g_mutex_unlock(priv->buffer_mutex); 304 return TRUE; 305 } 306 307 static void 308 marshal_VOID__MINIOBJECT(GClosure * closure, GValue * return_value, 309 guint n_param_values, const GValue * param_values, 310 gpointer invocation_hint, gpointer marshal_data) 311 { 312 typedef void (*marshalfunc_VOID__MINIOBJECT) (gpointer obj, gpointer arg1, gpointer data2); 313 marshalfunc_VOID__MINIOBJECT callback; 314 GCClosure *cc = (GCClosure *) closure; 315 gpointer data1, data2; 316 317 g_return_if_fail(n_param_values == 2); 318 319 if (G_CCLOSURE_SWAP_DATA(closure)) { 320 data1 = closure->data; 321 data2 = g_value_peek_pointer(param_values + 0); 322 } else { 323 data1 = g_value_peek_pointer(param_values + 0); 324 data2 = closure->data; 325 } 326 callback = (marshalfunc_VOID__MINIOBJECT) (marshal_data ? marshal_data : cc->callback); 327 328 callback(data1, gst_value_get_mini_object(param_values + 1), data2); 329 } 330 331 static void 332 webkit_video_sink_class_init(WebKitVideoSinkClass* klass) 333 { 334 GObjectClass* gobject_class = G_OBJECT_CLASS(klass); 335 GstBaseSinkClass* gstbase_sink_class = GST_BASE_SINK_CLASS(klass); 336 337 g_type_class_add_private(klass, sizeof(WebKitVideoSinkPrivate)); 338 339 gobject_class->dispose = webkit_video_sink_dispose; 340 341 gstbase_sink_class->unlock = webkit_video_sink_unlock; 342 gstbase_sink_class->unlock_stop = webkit_video_sink_unlock_stop; 343 gstbase_sink_class->render = webkit_video_sink_render; 344 gstbase_sink_class->preroll = webkit_video_sink_render; 345 gstbase_sink_class->stop = webkit_video_sink_stop; 346 gstbase_sink_class->start = webkit_video_sink_start; 347 348 webkit_video_sink_signals[REPAINT_REQUESTED] = g_signal_new("repaint-requested", 349 G_TYPE_FROM_CLASS(klass), 350 (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION), 351 0, 352 0, 353 0, 354 marshal_VOID__MINIOBJECT, 355 G_TYPE_NONE, 1, GST_TYPE_BUFFER); 356 } 357 358 /** 359 * webkit_video_sink_new: 360 * 361 * Creates a new GStreamer video sink. 362 * 363 * Return value: a #GstElement for the newly created video sink 364 */ 365 GstElement* 366 webkit_video_sink_new(void) 367 { 368 return (GstElement*)g_object_new(WEBKIT_TYPE_VIDEO_SINK, 0); 369 } 370 371