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