e90be2b9e3f8ff578775a07a509c1dd87252e910
[WebKit-https.git] / WebCore / platform / graphics / gtk / VideoSinkGStreamer.cpp
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("WebKit video sink",
44                         "Sink/Video",
45                         "Sends video data from a GStreamer pipeline to a Cairo surface",
46                         "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 == 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     WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object);
212
213     G_OBJECT_CLASS(parent_class)->finalize(object);
214 }
215
216 static void
217 webkit_video_sink_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
218 {
219     WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object);
220     WebKitVideoSinkPrivate* priv = sink->priv;
221
222     switch (prop_id) {
223     case PROP_SURFACE:
224         if (priv->surface)
225             cairo_surface_destroy(priv->surface);
226         priv->surface = cairo_surface_reference((cairo_surface_t*)g_value_get_pointer(value));
227         break;
228     default:
229         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
230         break;
231     }
232 }
233
234 static void
235 webkit_video_sink_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
236 {
237     WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object);
238
239     switch (prop_id) {
240     case PROP_SURFACE:
241         g_value_set_pointer(value, sink->priv->surface);
242         break;
243     default:
244         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
245         break;
246     }
247 }
248
249 static gboolean
250 webkit_video_sink_stop(GstBaseSink* base_sink)
251 {
252     WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(base_sink)->priv;
253
254     g_async_queue_lock(priv->async_queue);
255
256     /* Remove all remaining objects from the queue */
257     while(GstBuffer* buffer = (GstBuffer*)g_async_queue_try_pop_unlocked(priv->async_queue))
258         gst_buffer_unref(buffer);
259
260     g_async_queue_unlock(priv->async_queue);
261
262     return TRUE;
263 }
264
265 static void
266 webkit_video_sink_class_init(WebKitVideoSinkClass* klass)
267 {
268     GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
269     GstBaseSinkClass* gstbase_sink_class = GST_BASE_SINK_CLASS(klass);
270
271     g_type_class_add_private(klass, sizeof(WebKitVideoSinkPrivate));
272
273     gobject_class->set_property = webkit_video_sink_set_property;
274     gobject_class->get_property = webkit_video_sink_get_property;
275
276     gobject_class->dispose = webkit_video_sink_dispose;
277     gobject_class->finalize = webkit_video_sink_finalize;
278
279     gstbase_sink_class->render = webkit_video_sink_render;
280     gstbase_sink_class->preroll = webkit_video_sink_render;
281     gstbase_sink_class->stop = webkit_video_sink_stop;
282     gstbase_sink_class->set_caps = webkit_video_sink_set_caps;
283
284     g_object_class_install_property(
285         gobject_class, PROP_SURFACE,
286         g_param_spec_pointer("surface", "surface", "Target cairo_surface_t*",
287                              (GParamFlags)(G_PARAM_READWRITE)));
288 }
289
290 /**
291  * webkit_video_sink_new:
292  * @surface: a #cairo_surface_t
293  *
294  * Creates a new GStreamer video sink which uses @surface as the target
295  * for sinking a video stream from GStreamer.
296  *
297  * Return value: a #GstElement for the newly created video sink
298  */
299 GstElement*
300 webkit_video_sink_new(cairo_surface_t* surface)
301 {
302     return (GstElement*)g_object_new(WEBKIT_TYPE_VIDEO_SINK, "surface", surface, NULL);
303 }
304
305 void
306 webkit_video_sink_set_surface(WebKitVideoSink* sink, cairo_surface_t* surface)
307 {
308     WebKitVideoSinkPrivate* priv;
309
310     sink->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE(sink, WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkPrivate);
311     if (priv->surface)
312         cairo_surface_destroy(priv->surface);
313     priv->surface = cairo_surface_reference(surface);
314 }
315
316 static gboolean
317 plugin_init(GstPlugin* plugin)
318 {
319     return gst_element_register(plugin, "webkitsink", GST_RANK_PRIMARY, WEBKIT_TYPE_VIDEO_SINK);
320 }
321
322 #define VERSION "0.1"
323 #define PACKAGE "webkit"
324
325 GST_PLUGIN_DEFINE_STATIC(
326     GST_VERSION_MAJOR,
327     GST_VERSION_MINOR,
328     "webkitsink",
329     "Element to render to WebKit Cairo surfaces",
330     plugin_init,
331     VERSION,
332     "LGPL", /* license */
333     PACKAGE,
334     ""
335 );