2a39a811248283d9db9d04f49d3aa09a368ef34d
[platform/upstream/gstreamer.git] / ext / wpe / gstwpesrcbin.cpp
1 /* Copyright (C) <2018, 2019> Philippe Normand <philn@igalia.com>
2  * Copyright (C) <2018, 2019> Žan Doberšek <zdobersek@igalia.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /**
21  * SECTION:element-wpesrc
22  * @title: wpesrc
23  *
24  * The wpesrc element is used to produce a video texture representing a web page
25  * rendered off-screen by WPE.
26  *
27  * Starting from WPEBackend-FDO 1.6.x, software rendering support is available.
28  * This features allows wpesrc to be used on machines without GPU, and/or for
29  * testing purpose. To enable it, set the `LIBGL_ALWAYS_SOFTWARE=true`
30  * environment variable and make sure `video/x-raw, format=BGRA` caps are
31  * negotiated by the wpesrc element.
32  *
33  * Additionally, any audio stream created by WPE is exposed as "sometimes" audio
34  * source pads.
35  */
36
37 #include "gstwpesrcbin.h"
38 #include "gstwpevideosrc.h"
39 #include "gstwpe-private.h"
40 #include "WPEThreadedView.h"
41
42 #include <gst/allocators/allocators.h>
43 #include <gst/base/gstflowcombiner.h>
44 #include <wpe/extensions/audio.h>
45
46 G_DEFINE_TYPE (GstWpeAudioPad, gst_wpe_audio_pad, GST_TYPE_GHOST_PAD);
47
48 static void
49 gst_wpe_audio_pad_class_init (GstWpeAudioPadClass * klass)
50 {
51 }
52
53 static void
54 gst_wpe_audio_pad_init (GstWpeAudioPad * pad)
55 {
56   gst_audio_info_init (&pad->info);
57   pad->discont_pending = FALSE;
58   pad->buffer_time = GST_CLOCK_TIME_NONE;
59 }
60
61 static GstWpeAudioPad *
62 gst_wpe_audio_pad_new (const gchar * name)
63 {
64   GstWpeAudioPad *pad = GST_WPE_AUDIO_PAD (g_object_new (gst_wpe_audio_pad_get_type (),
65     "name", name, "direction", GST_PAD_SRC, NULL));
66
67   if (!gst_ghost_pad_construct (GST_GHOST_PAD (pad))) {
68     gst_object_unref (pad);
69     return NULL;
70   }
71
72   return pad;
73 }
74
75 struct _GstWpeSrc
76 {
77   GstBin parent;
78
79   GstAllocator *fd_allocator;
80   GstElement *video_src;
81   GHashTable *audio_src_pads;
82   GstFlowCombiner *flow_combiner;
83 };
84
85 enum
86 {
87  PROP_0,
88  PROP_LOCATION,
89  PROP_DRAW_BACKGROUND
90 };
91
92 enum
93 {
94  SIGNAL_LOAD_BYTES,
95  LAST_SIGNAL
96 };
97
98 static guint gst_wpe_video_src_signals[LAST_SIGNAL] = { 0 };
99
100 static void gst_wpe_src_uri_handler_init (gpointer iface, gpointer data);
101
102 GST_DEBUG_CATEGORY_EXTERN (wpe_src_debug);
103 #define GST_CAT_DEFAULT wpe_src_debug
104
105 #define gst_wpe_src_parent_class parent_class
106 G_DEFINE_TYPE_WITH_CODE (GstWpeSrc, gst_wpe_src, GST_TYPE_BIN,
107     G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_wpe_src_uri_handler_init));
108
109 static GstStaticPadTemplate video_src_factory =
110 GST_STATIC_PAD_TEMPLATE ("video", GST_PAD_SRC, GST_PAD_SOMETIMES,
111     GST_STATIC_CAPS ("video/x-raw(memory:GLMemory), "
112                      "format = (string) RGBA, "
113                      "width = " GST_VIDEO_SIZE_RANGE ", "
114                      "height = " GST_VIDEO_SIZE_RANGE ", "
115                      "framerate = " GST_VIDEO_FPS_RANGE ", "
116                      "pixel-aspect-ratio = (fraction)1/1,"
117                      "texture-target = (string)2D"
118 #if ENABLE_SHM_BUFFER_SUPPORT
119                      "; video/x-raw, "
120                      "format = (string) BGRA, "
121                      "width = " GST_VIDEO_SIZE_RANGE ", "
122                      "height = " GST_VIDEO_SIZE_RANGE ", "
123                      "framerate = " GST_VIDEO_FPS_RANGE ", "
124                      "pixel-aspect-ratio = (fraction)1/1"
125 #endif
126                      ));
127
128 static GstStaticPadTemplate audio_src_factory =
129 GST_STATIC_PAD_TEMPLATE ("audio_%u", GST_PAD_SRC, GST_PAD_SOMETIMES,
130     GST_STATIC_CAPS ( \
131         GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F32)) ", layout=(string)interleaved; " \
132         GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F64)) ", layout=(string)interleaved; " \
133         GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (S16)) ", layout=(string)interleaved" \
134 ));
135
136 static GstFlowReturn
137 gst_wpe_src_chain_buffer (GstPad * pad, GstObject * parent, GstBuffer * buffer)
138 {
139   GstWpeSrc *src = GST_WPE_SRC (gst_object_get_parent (parent));
140   GstFlowReturn result, chain_result;
141
142   chain_result = gst_proxy_pad_chain_default (pad, GST_OBJECT_CAST (src), buffer);
143   result = gst_flow_combiner_update_pad_flow (src->flow_combiner, pad, chain_result);
144   gst_object_unref (src);
145
146   if (result == GST_FLOW_FLUSHING)
147     return chain_result;
148
149   return result;
150 }
151
152 static void
153 on_audio_receiver_handle_start(void* data, uint32_t id, int32_t channels, const char* format, int32_t sampleRate)
154 {
155   GstWpeSrc* src = GST_WPE_SRC (data);
156   GstWpeAudioPad *audio_pad;
157   GstPad *pad;
158   gchar *name;
159   GstEvent *stream_start;
160   GstSegment segment;
161   GstCaps *caps;
162
163   GST_DEBUG_OBJECT (src, "Exposing audio pad for stream %u", id);
164   name = g_strdup_printf ("audio_%u", id);
165   audio_pad = gst_wpe_audio_pad_new (name);
166   pad = GST_PAD_CAST (audio_pad);
167   g_free (name);
168
169   gst_pad_set_active (pad, TRUE);
170   gst_element_add_pad (GST_ELEMENT_CAST (src), pad);
171   gst_flow_combiner_add_pad (src->flow_combiner, pad);
172
173   name = gst_pad_create_stream_id_printf(pad, GST_ELEMENT_CAST (src), "%03u", id);
174   stream_start = gst_event_new_stream_start (name);
175   gst_pad_push_event (pad, stream_start);
176   g_free (name);
177
178   caps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, format,
179     "rate", G_TYPE_INT, sampleRate,
180     "channels", G_TYPE_INT, channels,
181     "channel-mask", GST_TYPE_BITMASK, gst_audio_channel_get_fallback_mask (channels),
182     "layout", G_TYPE_STRING, "interleaved", NULL);
183   gst_audio_info_from_caps (&audio_pad->info, caps);
184   gst_pad_push_event (pad, gst_event_new_caps (caps));
185   gst_caps_unref (caps);
186
187   gst_segment_init (&segment, GST_FORMAT_TIME);
188   gst_pad_push_event (pad, gst_event_new_segment (&segment));
189
190   g_hash_table_insert (src->audio_src_pads, GUINT_TO_POINTER (id), audio_pad);
191 }
192
193 static void
194 on_audio_receiver_handle_packet(void* data, struct wpe_audio_packet_export* packet_export, uint32_t id, int32_t fd, uint32_t size)
195 {
196   GstWpeSrc* src = GST_WPE_SRC (data);
197   GstWpeAudioPad *audio_pad = GST_WPE_AUDIO_PAD (g_hash_table_lookup (src->audio_src_pads, GUINT_TO_POINTER (id)));
198   GstPad *pad = GST_PAD_CAST (audio_pad);
199   GstBuffer *buffer;
200   GstClock *clock;
201
202   g_return_if_fail (GST_IS_PAD (pad));
203   g_return_if_fail (fd >= 0);
204
205   GST_TRACE_OBJECT (pad, "Handling incoming audio packet");
206   buffer = gst_buffer_new ();
207
208   GstMemory *mem = gst_fd_allocator_alloc (src->fd_allocator, dup (fd), size, GST_FD_MEMORY_FLAG_KEEP_MAPPED);
209   gst_buffer_append_memory (buffer, mem);
210   gst_buffer_add_audio_meta (buffer, &audio_pad->info, size, NULL);
211
212   clock = gst_element_get_clock (GST_ELEMENT_CAST (src));
213   if (clock) {
214     GstClockTime now;
215     GstClockTime base_time = gst_element_get_base_time (GST_ELEMENT_CAST (src));
216
217     now = gst_clock_get_time (clock);
218     if (now > base_time)
219       now -= base_time;
220     else
221       now = 0;
222     gst_object_unref (clock);
223
224     audio_pad->buffer_time = now;
225     GST_BUFFER_DTS (buffer) = audio_pad->buffer_time;
226   }
227
228   GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT);
229   if (audio_pad->discont_pending) {
230     GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
231     audio_pad->discont_pending = FALSE;
232   }
233
234   gst_flow_combiner_update_pad_flow (src->flow_combiner, pad, gst_pad_push (pad, buffer));
235   wpe_audio_packet_export_release (packet_export);
236   close (fd);
237 }
238
239 static void
240 on_audio_receiver_handle_stop(void* data, uint32_t id)
241 {
242   GstWpeSrc* src = GST_WPE_SRC (data);
243   GstWpeAudioPad *audio_pad = GST_WPE_AUDIO_PAD (g_hash_table_lookup (src->audio_src_pads, GUINT_TO_POINTER (id)));
244   GstPad *pad = GST_PAD_CAST (audio_pad);
245   g_return_if_fail (GST_IS_PAD (pad));
246
247   GST_INFO_OBJECT(pad, "Stopping");
248   gst_pad_push_event (pad, gst_event_new_eos ());
249   gst_element_remove_pad (GST_ELEMENT_CAST (src), pad);
250   gst_flow_combiner_remove_pad (src->flow_combiner, pad);
251   g_hash_table_remove (src->audio_src_pads, GUINT_TO_POINTER (id));
252 }
253
254 static void
255 on_audio_receiver_handle_pause(void* data, uint32_t id)
256 {
257   GstWpeSrc* src = GST_WPE_SRC (data);
258   GstWpeAudioPad *audio_pad = GST_WPE_AUDIO_PAD (g_hash_table_lookup (src->audio_src_pads, GUINT_TO_POINTER (id)));
259   GstPad *pad = GST_PAD_CAST (audio_pad);
260   g_return_if_fail (GST_IS_PAD (pad));
261
262   GST_INFO_OBJECT(pad, "Pausing");
263   gst_pad_push_event (pad, gst_event_new_gap (audio_pad->buffer_time, GST_CLOCK_TIME_NONE));
264
265   audio_pad->discont_pending = TRUE;
266 }
267
268 static void
269 on_audio_receiver_handle_resume(void* data, uint32_t id)
270 {
271   GstWpeSrc* src = GST_WPE_SRC (data);
272   GstWpeAudioPad *audio_pad = GST_WPE_AUDIO_PAD (g_hash_table_lookup (src->audio_src_pads, GUINT_TO_POINTER (id)));
273   GstPad *pad = GST_PAD_CAST (audio_pad);
274   g_return_if_fail (GST_IS_PAD (pad));
275
276   GST_INFO_OBJECT(pad, "Resuming");
277 }
278
279
280 static const struct wpe_audio_receiver audio_receiver = {
281   .handle_start = on_audio_receiver_handle_start,
282   .handle_packet = on_audio_receiver_handle_packet,
283   .handle_stop = on_audio_receiver_handle_stop,
284   .handle_pause = on_audio_receiver_handle_pause,
285   .handle_resume = on_audio_receiver_handle_resume
286 };
287
288 static void
289 gst_wpe_src_load_bytes (GstWpeVideoSrc * src, GBytes * bytes)
290 {
291   GstWpeSrc *self = GST_WPE_SRC (src);
292
293   if (self->video_src)
294     g_signal_emit_by_name (self->video_src, "load-bytes", bytes, NULL);
295 }
296
297 static void
298 gst_wpe_src_set_location (GstWpeSrc * src, const gchar * location)
299 {
300   GstPad *pad;
301   GstPad *ghost_pad;
302   GstProxyPad *proxy_pad;
303
304   g_object_set (src->video_src, "location", location, NULL);
305
306   ghost_pad = gst_element_get_static_pad (GST_ELEMENT_CAST (src), "video");
307   if (GST_IS_PAD (ghost_pad)) {
308     gst_object_unref (ghost_pad);
309     return;
310   }
311
312   gst_bin_add (GST_BIN_CAST (src), src->video_src);
313
314   pad = gst_element_get_static_pad (GST_ELEMENT_CAST (src->video_src), "src");
315   ghost_pad = gst_ghost_pad_new_from_template ("video", pad,
316     gst_static_pad_template_get (&video_src_factory));
317   proxy_pad = gst_proxy_pad_get_internal (GST_PROXY_PAD (ghost_pad));
318   gst_pad_set_active (GST_PAD_CAST (proxy_pad), TRUE);
319
320   gst_element_add_pad (GST_ELEMENT_CAST (src), GST_PAD_CAST (ghost_pad));
321   gst_flow_combiner_add_pad (src->flow_combiner, GST_PAD_CAST (ghost_pad));
322   gst_pad_set_chain_function (GST_PAD_CAST (proxy_pad), gst_wpe_src_chain_buffer);
323
324   gst_object_unref (proxy_pad);
325   gst_object_unref (pad);
326 }
327
328 static void
329 gst_wpe_src_get_property (GObject * object, guint prop_id,
330     GValue * value, GParamSpec * pspec)
331 {
332   GstWpeSrc *self = GST_WPE_SRC (object);
333
334   if (self->video_src)
335     g_object_get_property (G_OBJECT (self->video_src), pspec->name, value);
336 }
337
338 static void
339 gst_wpe_src_set_property (GObject * object, guint prop_id,
340     const GValue * value, GParamSpec * pspec)
341 {
342   GstWpeSrc *self = GST_WPE_SRC (object);
343
344   if (self->video_src) {
345     if (prop_id == PROP_LOCATION)
346       gst_wpe_src_set_location (self, g_value_get_string (value));
347     else
348       g_object_set_property (G_OBJECT (self->video_src), pspec->name, value);
349   }
350 }
351
352 static GstURIType
353 gst_wpe_src_uri_get_type (GType)
354 {
355   return GST_URI_SRC;
356 }
357
358 static const gchar *const *
359 gst_wpe_src_get_protocols (GType)
360 {
361   static const char *protocols[] = { "wpe", NULL };
362   return protocols;
363 }
364
365 static gchar *
366 gst_wpe_src_get_uri (GstURIHandler * handler)
367 {
368   GstWpeSrc *src = GST_WPE_SRC (handler);
369   const gchar *location;
370   g_object_get (src->video_src, "location", &location, NULL);
371   return g_strdup_printf ("wpe://%s", location);
372 }
373
374 static gboolean
375 gst_wpe_src_set_uri (GstURIHandler * handler, const gchar * uri,
376     GError ** error)
377 {
378   GstWpeSrc *src = GST_WPE_SRC (handler);
379
380   gst_wpe_src_set_location(src, uri + 6);
381   return TRUE;
382 }
383
384 static void
385 gst_wpe_src_uri_handler_init (gpointer iface_ptr, gpointer data)
386 {
387   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) iface_ptr;
388
389   iface->get_type = gst_wpe_src_uri_get_type;
390   iface->get_protocols = gst_wpe_src_get_protocols;
391   iface->get_uri = gst_wpe_src_get_uri;
392   iface->set_uri = gst_wpe_src_set_uri;
393 }
394
395 static void
396 gst_wpe_src_init (GstWpeSrc * src)
397 {
398   gst_bin_set_suppressed_flags (GST_BIN_CAST (src),
399       static_cast<GstElementFlags>(GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK));
400   GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_SOURCE);
401
402   src->fd_allocator = gst_fd_allocator_new ();
403   src->audio_src_pads = g_hash_table_new (g_direct_hash, g_direct_equal);
404   src->flow_combiner = gst_flow_combiner_new ();
405   src->video_src = gst_element_factory_make ("wpevideosrc", NULL);
406
407   gst_wpe_video_src_register_audio_receiver (src->video_src, &audio_receiver, src);
408 }
409
410 static GstStateChangeReturn
411 gst_wpe_src_change_state (GstElement * element, GstStateChange transition)
412 {
413   GstStateChangeReturn result;
414   GstWpeSrc *src = GST_WPE_SRC (element);
415
416   GST_DEBUG_OBJECT (src, "%s", gst_state_change_get_name (transition));
417   result = GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS , change_state, (element, transition), GST_STATE_CHANGE_FAILURE);
418
419   switch (transition) {
420   case GST_STATE_CHANGE_PAUSED_TO_READY:{
421     gst_flow_combiner_reset (src->flow_combiner);
422     break;
423   }
424   default:
425     break;
426   }
427
428   return result;
429 }
430
431 static void
432 gst_wpe_src_dispose (GObject *object)
433 {
434     GstWpeSrc *src = GST_WPE_SRC (object);
435
436     g_hash_table_unref (src->audio_src_pads);
437     gst_flow_combiner_free (src->flow_combiner);
438     gst_object_unref (src->fd_allocator);
439
440     GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
441 }
442
443 static void
444 gst_wpe_src_class_init (GstWpeSrcClass * klass)
445 {
446   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
447   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
448
449   gobject_class->set_property = gst_wpe_src_set_property;
450   gobject_class->get_property = gst_wpe_src_get_property;
451   gobject_class->dispose = gst_wpe_src_dispose;
452
453   g_object_class_install_property (gobject_class, PROP_LOCATION,
454       g_param_spec_string ("location", "location", "The URL to display", "",
455           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
456   g_object_class_install_property (gobject_class, PROP_DRAW_BACKGROUND,
457       g_param_spec_boolean ("draw-background", "Draws the background",
458           "Whether to draw the WebView background", TRUE,
459           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
460
461   gst_element_class_set_static_metadata (element_class, "WPE source",
462       "Source/Video/Audio", "Creates a video stream from a WPE browser",
463       "Philippe Normand <philn@igalia.com>, Žan Doberšek "
464       "<zdobersek@igalia.com>");
465
466   /**
467    * GstWpeSrc::load-bytes:
468    * @src: the object which received the signal
469    * @bytes: the GBytes data to load
470    *
471    * Load the specified bytes into the internal webView.
472    */
473   gst_wpe_video_src_signals[SIGNAL_LOAD_BYTES] =
474       g_signal_new_class_handler ("load-bytes", G_TYPE_FROM_CLASS (klass),
475       static_cast < GSignalFlags > (G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
476       G_CALLBACK (gst_wpe_src_load_bytes), NULL, NULL, NULL, G_TYPE_NONE, 1,
477       G_TYPE_BYTES);
478
479   element_class->change_state = GST_DEBUG_FUNCPTR (gst_wpe_src_change_state);
480
481   gst_element_class_add_static_pad_template (element_class, &video_src_factory);
482   gst_element_class_add_static_pad_template (element_class, &audio_src_factory);
483 }