• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     REPAINT_REQUESTED,
50     LAST_SIGNAL
51 };
52 
53 enum {
54     PROP_0,
55     PROP_SURFACE
56 };
57 
58 static guint webkit_video_sink_signals[LAST_SIGNAL] = { 0, };
59 
60 struct _WebKitVideoSinkPrivate {
61     cairo_surface_t* surface;
62     GAsyncQueue* async_queue;
63     gboolean rgb_ordering;
64     int width;
65     int height;
66     int fps_n;
67     int fps_d;
68     int par_n;
69     int par_d;
70 };
71 
72 #define _do_init(bla) \
73     GST_DEBUG_CATEGORY_INIT(webkit_video_sink_debug, \
74                             "webkitsink", \
75                             0, \
76                             "webkit video sink")
77 
78 GST_BOILERPLATE_FULL(WebKitVideoSink,
79                      webkit_video_sink,
80                      GstBaseSink,
81                      GST_TYPE_BASE_SINK,
82                      _do_init);
83 
84 static void
webkit_video_sink_base_init(gpointer g_class)85 webkit_video_sink_base_init(gpointer g_class)
86 {
87     GstElementClass* element_class = GST_ELEMENT_CLASS(g_class);
88 
89     gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&sinktemplate));
90     gst_element_class_set_details(element_class, &webkit_video_sink_details);
91 }
92 
93 static void
webkit_video_sink_init(WebKitVideoSink * sink,WebKitVideoSinkClass * klass)94 webkit_video_sink_init(WebKitVideoSink* sink, WebKitVideoSinkClass* klass)
95 {
96     WebKitVideoSinkPrivate* priv;
97 
98     sink->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE(sink, WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkPrivate);
99     priv->async_queue = g_async_queue_new();
100 }
101 
102 static gboolean
webkit_video_sink_idle_func(gpointer data)103 webkit_video_sink_idle_func(gpointer data)
104 {
105     WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(data);
106     WebKitVideoSinkPrivate* priv = sink->priv;
107     GstBuffer* buffer;
108 
109     if (!priv->async_queue)
110         return FALSE;
111 
112     buffer = (GstBuffer*)g_async_queue_try_pop(priv->async_queue);
113     if (!buffer || G_UNLIKELY(!GST_IS_BUFFER(buffer)))
114         return FALSE;
115 
116     // TODO: consider priv->rgb_ordering?
117     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);
118 
119     // TODO: We copy the data twice right now. This could be easily improved.
120     cairo_t* cr = cairo_create(priv->surface);
121     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
122     cairo_set_source_surface(cr, src, 0, 0);
123     cairo_surface_destroy(src);
124     cairo_rectangle(cr, 0, 0, priv->width, priv->height);
125     cairo_fill(cr);
126     cairo_destroy(cr);
127 
128     gst_buffer_unref(buffer);
129 
130     g_signal_emit(sink, webkit_video_sink_signals[REPAINT_REQUESTED], 0);
131 
132     return FALSE;
133 }
134 
135 static GstFlowReturn
webkit_video_sink_render(GstBaseSink * bsink,GstBuffer * buffer)136 webkit_video_sink_render(GstBaseSink* bsink, GstBuffer* buffer)
137 {
138     WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(bsink);
139     WebKitVideoSinkPrivate* priv = sink->priv;
140 
141     g_async_queue_push(priv->async_queue, gst_buffer_ref(buffer));
142     g_idle_add_full(G_PRIORITY_HIGH_IDLE, webkit_video_sink_idle_func, sink, 0);
143 
144     return GST_FLOW_OK;
145 }
146 
147 static gboolean
webkit_video_sink_set_caps(GstBaseSink * bsink,GstCaps * caps)148 webkit_video_sink_set_caps(GstBaseSink* bsink, GstCaps* caps)
149 {
150     WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(bsink);
151     WebKitVideoSinkPrivate* priv = sink->priv;
152     GstStructure* structure;
153     gboolean ret;
154     const GValue* fps;
155     const GValue* par;
156     gint width, height;
157     int red_mask;
158 
159     GstCaps* intersection = gst_caps_intersect(gst_static_pad_template_get_caps(&sinktemplate), caps);
160 
161     if (gst_caps_is_empty(intersection))
162         return FALSE;
163 
164     gst_caps_unref(intersection);
165 
166     structure = gst_caps_get_structure(caps, 0);
167 
168     ret = gst_structure_get_int(structure, "width", &width);
169     ret &= gst_structure_get_int(structure, "height", &height);
170     fps = gst_structure_get_value(structure, "framerate");
171     ret &= (fps != 0);
172 
173     par = gst_structure_get_value(structure, "pixel-aspect-ratio");
174 
175     if (!ret)
176         return FALSE;
177 
178     priv->width = width;
179     priv->height = height;
180 
181     /* We dont yet use fps or pixel aspect into but handy to have */
182     priv->fps_n = gst_value_get_fraction_numerator(fps);
183     priv->fps_d = gst_value_get_fraction_denominator(fps);
184 
185     if (par) {
186         priv->par_n = gst_value_get_fraction_numerator(par);
187         priv->par_d = gst_value_get_fraction_denominator(par);
188     } else
189         priv->par_n = priv->par_d = 1;
190 
191     gst_structure_get_int(structure, "red_mask", &red_mask);
192     priv->rgb_ordering = (red_mask == static_cast<int>(0xff000000));
193 
194     return TRUE;
195 }
196 
197 static void
webkit_video_sink_dispose(GObject * object)198 webkit_video_sink_dispose(GObject* object)
199 {
200     WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object);
201     WebKitVideoSinkPrivate* priv = sink->priv;
202 
203     if (priv->surface) {
204         cairo_surface_destroy(priv->surface);
205         priv->surface = 0;
206     }
207 
208     if (priv->async_queue) {
209         g_async_queue_unref(priv->async_queue);
210         priv->async_queue = 0;
211     }
212 
213     G_OBJECT_CLASS(parent_class)->dispose(object);
214 }
215 
216 static void
webkit_video_sink_finalize(GObject * object)217 webkit_video_sink_finalize(GObject* object)
218 {
219     G_OBJECT_CLASS(parent_class)->finalize(object);
220 }
221 
222 static void
webkit_video_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)223 webkit_video_sink_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
224 {
225     WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object);
226     WebKitVideoSinkPrivate* priv = sink->priv;
227 
228     switch (prop_id) {
229     case PROP_SURFACE:
230         if (priv->surface)
231             cairo_surface_destroy(priv->surface);
232         priv->surface = cairo_surface_reference((cairo_surface_t*)g_value_get_pointer(value));
233         break;
234     default:
235         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
236         break;
237     }
238 }
239 
240 static void
webkit_video_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)241 webkit_video_sink_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
242 {
243     WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(object);
244 
245     switch (prop_id) {
246     case PROP_SURFACE:
247         g_value_set_pointer(value, sink->priv->surface);
248         break;
249     default:
250         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
251         break;
252     }
253 }
254 
255 static gboolean
webkit_video_sink_stop(GstBaseSink * base_sink)256 webkit_video_sink_stop(GstBaseSink* base_sink)
257 {
258     WebKitVideoSinkPrivate* priv = WEBKIT_VIDEO_SINK(base_sink)->priv;
259 
260     g_async_queue_lock(priv->async_queue);
261 
262     /* Remove all remaining objects from the queue */
263     while (GstBuffer* buffer = (GstBuffer*)g_async_queue_try_pop_unlocked(priv->async_queue))
264         gst_buffer_unref(buffer);
265 
266     g_async_queue_unlock(priv->async_queue);
267 
268     return TRUE;
269 }
270 
271 static void
webkit_video_sink_class_init(WebKitVideoSinkClass * klass)272 webkit_video_sink_class_init(WebKitVideoSinkClass* klass)
273 {
274     GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
275     GstBaseSinkClass* gstbase_sink_class = GST_BASE_SINK_CLASS(klass);
276 
277     g_type_class_add_private(klass, sizeof(WebKitVideoSinkPrivate));
278 
279     gobject_class->set_property = webkit_video_sink_set_property;
280     gobject_class->get_property = webkit_video_sink_get_property;
281 
282     gobject_class->dispose = webkit_video_sink_dispose;
283     gobject_class->finalize = webkit_video_sink_finalize;
284 
285     gstbase_sink_class->render = webkit_video_sink_render;
286     gstbase_sink_class->preroll = webkit_video_sink_render;
287     gstbase_sink_class->stop = webkit_video_sink_stop;
288     gstbase_sink_class->set_caps = webkit_video_sink_set_caps;
289 
290     webkit_video_sink_signals[REPAINT_REQUESTED] = g_signal_new("repaint-requested",
291             G_TYPE_FROM_CLASS(klass),
292             (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
293             0,
294             0,
295             0,
296             g_cclosure_marshal_VOID__VOID,
297             G_TYPE_NONE, 0);
298 
299     g_object_class_install_property(
300         gobject_class, PROP_SURFACE,
301         g_param_spec_pointer("surface", "surface", "Target cairo_surface_t*",
302                              (GParamFlags)(G_PARAM_READWRITE)));
303 }
304 
305 /**
306  * webkit_video_sink_new:
307  * @surface: a #cairo_surface_t
308  *
309  * Creates a new GStreamer video sink which uses @surface as the target
310  * for sinking a video stream from GStreamer.
311  *
312  * Return value: a #GstElement for the newly created video sink
313  */
314 GstElement*
webkit_video_sink_new(cairo_surface_t * surface)315 webkit_video_sink_new(cairo_surface_t* surface)
316 {
317     return (GstElement*)g_object_new(WEBKIT_TYPE_VIDEO_SINK, "surface", surface, 0);
318 }
319 
320 void
webkit_video_sink_set_surface(WebKitVideoSink * sink,cairo_surface_t * surface)321 webkit_video_sink_set_surface(WebKitVideoSink* sink, cairo_surface_t* surface)
322 {
323     WebKitVideoSinkPrivate* priv;
324 
325     sink->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE(sink, WEBKIT_TYPE_VIDEO_SINK, WebKitVideoSinkPrivate);
326     if (priv->surface)
327         cairo_surface_destroy(priv->surface);
328     priv->surface = cairo_surface_reference(surface);
329 }
330