dd49ff715822d81cd96d62d6832ac27b70d85624
[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  * ## Example launch lines
34  *
35  * ### Show the GStreamer website homepage
36  *
37  * ```
38  * gst-launch-1.0 -v wpesrc location="https://gstreamer.freedesktop.org" ! queue ! glimagesink
39  * ```
40  *
41  * ### Save the first 50 video frames generated for the GStreamer website as PNG files in /tmp
42  *
43  * ```
44  * LIBGL_ALWAYS_SOFTWARE=true gst-launch-1.0 -v wpesrc num-buffers=50 location="https://gstreamer.freedesktop.org" ! videoconvert ! pngenc ! multifilesink location=/tmp/snapshot-%05d.png
45  * ```
46  *
47  *
48  * ### Show the GStreamer website homepage as played with GstPlayer in a GTK+ window
49  *
50  * ```
51  * gst-play-1.0 --videosink gtkglsink wpe://https://gstreamer.freedesktop.org
52  * ```
53  *
54  * ### Composite WPE with a video stream in a single OpenGL scene
55  *
56  * ```
57  * gst-launch-1.0  glvideomixer name=m sink_1::zorder=0 ! glimagesink wpesrc location="file:///home/phil/Downloads/plunk/index.html" draw-background=0 ! m. videotestsrc ! queue ! glupload ! glcolorconvert ! m.
58  * ```
59  *
60  *
61  * ### Composite WPE with a video stream, sink_0 pad properties have to match the video dimensions
62  *
63  * ```
64  * gst-launch-1.0 glvideomixer name=m sink_1::zorder=0 sink_0::height=818 sink_0::width=1920 ! gtkglsink wpesrc location="file:///home/phil/Downloads/plunk/index.html" draw-background=0 ! m. uridecodebin uri="http://192.168.1.44/Sintel.2010.1080p.mkv" name=d d. ! queue ! glupload ! glcolorconvert ! m.
65  * ```
66  *
67  * Additionally, any audio stream created by WPE is exposed as "sometimes" audio
68  * source pads.
69  *
70  * This source also relays GStreamer bus messages from the GStreamer pipelines
71  * running inside the web pages  as [element custom](gst_message_new_custom)
72  * messages which structure is called `WpeForwarded` and has the following
73  * fields:
74  *
75  * * `message`: The original #GstMessage
76  * * `wpesrc-original-src-name`: Name of the original element posting the
77  *   message
78  * * `wpesrc-original-src-type`: Name of the GType of the original element
79  *   posting the message
80  * * `wpesrc-original-src-path`: [Path](gst_object_get_path_string) of the
81  *   original element positing the message
82  *
83  * Note: This feature will be disabled if you disable the tracer subsystem.
84  */
85
86 #include "gstwpesrcbin.h"
87 #include "gstwpevideosrc.h"
88 #include "gstwpe.h"
89 #include "WPEThreadedView.h"
90
91 #include <gst/allocators/allocators.h>
92 #include <gst/base/gstflowcombiner.h>
93 #include <wpe/extensions/audio.h>
94
95 #include <sys/mman.h>
96 #include <unistd.h>
97
98 G_DEFINE_TYPE (GstWpeAudioPad, gst_wpe_audio_pad, GST_TYPE_GHOST_PAD);
99
100 static void
101 gst_wpe_audio_pad_class_init (GstWpeAudioPadClass * klass)
102 {
103 }
104
105 static void
106 gst_wpe_audio_pad_init (GstWpeAudioPad * pad)
107 {
108   gst_audio_info_init (&pad->info);
109   pad->discont_pending = FALSE;
110   pad->buffer_time = GST_CLOCK_TIME_NONE;
111 }
112
113 static GstWpeAudioPad *
114 gst_wpe_audio_pad_new (const gchar * name)
115 {
116   GstWpeAudioPad *pad = GST_WPE_AUDIO_PAD (g_object_new (gst_wpe_audio_pad_get_type (),
117     "name", name, "direction", GST_PAD_SRC, NULL));
118
119   if (!gst_ghost_pad_construct (GST_GHOST_PAD (pad))) {
120     gst_object_unref (pad);
121     return NULL;
122   }
123
124   return pad;
125 }
126
127 struct _GstWpeSrc
128 {
129   GstBin parent;
130
131   GstAllocator *fd_allocator;
132   GstElement *video_src;
133   GHashTable *audio_src_pads;
134   GstFlowCombiner *flow_combiner;
135 };
136
137 enum
138 {
139  PROP_0,
140  PROP_LOCATION,
141  PROP_DRAW_BACKGROUND
142 };
143
144 enum
145 {
146  SIGNAL_LOAD_BYTES,
147  LAST_SIGNAL
148 };
149
150 static guint gst_wpe_video_src_signals[LAST_SIGNAL] = { 0 };
151
152 static void gst_wpe_src_uri_handler_init (gpointer iface, gpointer data);
153
154 GST_DEBUG_CATEGORY_EXTERN (wpe_src_debug);
155 #define GST_CAT_DEFAULT wpe_src_debug
156
157 #define gst_wpe_src_parent_class parent_class
158 G_DEFINE_TYPE_WITH_CODE (GstWpeSrc, gst_wpe_src, GST_TYPE_BIN,
159     G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_wpe_src_uri_handler_init));
160
161 /**
162  * GstWpeSrc!video
163  *
164  * Since: 1.20
165  */
166 static GstStaticPadTemplate video_src_factory =
167 GST_STATIC_PAD_TEMPLATE ("video", GST_PAD_SRC, GST_PAD_ALWAYS,
168     GST_STATIC_CAPS ("video/x-raw(memory:GLMemory), "
169                      "format = (string) RGBA, "
170                      "width = " GST_VIDEO_SIZE_RANGE ", "
171                      "height = " GST_VIDEO_SIZE_RANGE ", "
172                      "framerate = " GST_VIDEO_FPS_RANGE ", "
173                      "pixel-aspect-ratio = (fraction)1/1,"
174                      "texture-target = (string)2D"
175                      "; video/x-raw, "
176                      "format = (string) BGRA, "
177                      "width = " GST_VIDEO_SIZE_RANGE ", "
178                      "height = " GST_VIDEO_SIZE_RANGE ", "
179                      "framerate = " GST_VIDEO_FPS_RANGE ", "
180                      "pixel-aspect-ratio = (fraction)1/1"
181                      ));
182
183 /**
184  * GstWpeSrc!audio_%u
185  *
186  * Each audio stream in the renderer web page will expose the and `audio_%u`
187  * #GstPad.
188  *
189  * Since: 1.20
190  */
191 static GstStaticPadTemplate audio_src_factory =
192 GST_STATIC_PAD_TEMPLATE ("audio_%u", GST_PAD_SRC, GST_PAD_SOMETIMES,
193     GST_STATIC_CAPS ( \
194         GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F32)) ", layout=(string)interleaved; " \
195         GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F64)) ", layout=(string)interleaved; " \
196         GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (S16)) ", layout=(string)interleaved" \
197 ));
198
199 static GstFlowReturn
200 gst_wpe_src_chain_buffer (GstPad * pad, GstObject * parent, GstBuffer * buffer)
201 {
202   GstWpeSrc *src = GST_WPE_SRC (gst_object_get_parent (parent));
203   GstFlowReturn result, chain_result;
204
205   chain_result = gst_proxy_pad_chain_default (pad, GST_OBJECT_CAST (src), buffer);
206   result = gst_flow_combiner_update_pad_flow (src->flow_combiner, pad, chain_result);
207   gst_object_unref (src);
208
209   if (result == GST_FLOW_FLUSHING)
210     return chain_result;
211
212   return result;
213 }
214
215 void
216 gst_wpe_src_new_audio_stream (GstWpeSrc *src, guint32 id, GstCaps *caps, const gchar *stream_id)
217 {
218   GstWpeAudioPad *audio_pad;
219   GstPad *pad;
220   gchar *name;
221   GstEvent *stream_start;
222   GstSegment segment;
223
224   name = g_strdup_printf ("audio_%u", id);
225   audio_pad = gst_wpe_audio_pad_new (name);
226   pad = GST_PAD_CAST (audio_pad);
227   g_free (name);
228
229   gst_pad_set_active (pad, TRUE);
230   gst_element_add_pad (GST_ELEMENT_CAST (src), pad);
231   gst_flow_combiner_add_pad (src->flow_combiner, pad);
232
233   GST_DEBUG_OBJECT (src, "Adding pad: %" GST_PTR_FORMAT, pad);
234
235   stream_start = gst_event_new_stream_start (stream_id);
236   gst_pad_push_event (pad, stream_start);
237
238   gst_audio_info_from_caps (&audio_pad->info, caps);
239   gst_pad_push_event (pad, gst_event_new_caps (caps));
240
241   gst_segment_init (&segment, GST_FORMAT_TIME);
242   gst_pad_push_event (pad, gst_event_new_segment (&segment));
243
244   g_hash_table_insert (src->audio_src_pads, GUINT_TO_POINTER (id), audio_pad);
245 }
246
247 void
248 gst_wpe_src_set_audio_shm (GstWpeSrc* src, GUnixFDList *fds, guint32 id)
249 {
250   gint fd;
251   GstWpeAudioPad *audio_pad = GST_WPE_AUDIO_PAD (g_hash_table_lookup (src->audio_src_pads, GUINT_TO_POINTER (id)));
252
253   g_return_if_fail (GST_IS_WPE_SRC (src));
254   g_return_if_fail (fds);
255   g_return_if_fail (g_unix_fd_list_get_length (fds) == 1);
256   g_return_if_fail (audio_pad->fd <= 0);
257
258   fd = g_unix_fd_list_get (fds, 0, NULL);
259   g_return_if_fail (fd >= 0);
260
261   if (audio_pad->fd > 0)
262     close(audio_pad->fd);
263
264   audio_pad->fd = dup(fd);
265 }
266
267 void
268 gst_wpe_src_push_audio_buffer (GstWpeSrc* src, guint32 id, guint64 size)
269 {
270   GstWpeAudioPad *audio_pad = GST_WPE_AUDIO_PAD (g_hash_table_lookup (src->audio_src_pads, GUINT_TO_POINTER (id)));
271   GstBuffer *buffer;
272   GstClock *clock;
273
274   g_return_if_fail (audio_pad->fd > 0);
275
276   GST_TRACE_OBJECT (audio_pad, "Handling incoming audio packet");
277
278   gpointer data = mmap (0, size, PROT_READ, MAP_PRIVATE, audio_pad->fd, 0);
279   buffer = gst_buffer_new_wrapped (g_memdup(data, size), size);
280   munmap (data, size);
281   gst_buffer_add_audio_meta (buffer, &audio_pad->info, size, NULL);
282
283   clock = gst_element_get_clock (GST_ELEMENT_CAST (src));
284   if (clock) {
285     GstClockTime now;
286     GstClockTime base_time = gst_element_get_base_time (GST_ELEMENT_CAST (src));
287
288     now = gst_clock_get_time (clock);
289     if (now > base_time)
290       now -= base_time;
291     else
292       now = 0;
293     gst_object_unref (clock);
294
295     audio_pad->buffer_time = now;
296     GST_BUFFER_DTS (buffer) = audio_pad->buffer_time;
297   }
298
299   GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT);
300   if (audio_pad->discont_pending) {
301     GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
302     audio_pad->discont_pending = FALSE;
303   }
304
305   gst_flow_combiner_update_pad_flow (src->flow_combiner, GST_PAD (audio_pad),
306     gst_pad_push (GST_PAD_CAST (audio_pad), buffer));
307 }
308
309 static void
310 gst_wpe_src_remove_audio_pad (GstWpeSrc *src, GstPad *pad)
311 {
312   GST_DEBUG_OBJECT (src, "Removing pad: %" GST_PTR_FORMAT, pad);
313   gst_element_remove_pad (GST_ELEMENT_CAST (src), pad);
314   gst_flow_combiner_remove_pad (src->flow_combiner, pad);
315 }
316
317 void
318 gst_wpe_src_stop_audio_stream(GstWpeSrc* src, guint32 id)
319 {
320   GstPad *pad = GST_PAD (g_hash_table_lookup (src->audio_src_pads, GUINT_TO_POINTER (id)));
321   g_return_if_fail (GST_IS_PAD (pad));
322
323   GST_INFO_OBJECT(pad, "Stopping");
324   gst_pad_push_event (pad, gst_event_new_eos ());
325   gst_wpe_src_remove_audio_pad (src, pad);
326   g_hash_table_remove (src->audio_src_pads, GUINT_TO_POINTER (id));
327 }
328
329 void
330 gst_wpe_src_pause_audio_stream(GstWpeSrc* src, guint32 id)
331 {
332   GstWpeAudioPad *audio_pad = GST_WPE_AUDIO_PAD (g_hash_table_lookup (src->audio_src_pads, GUINT_TO_POINTER (id)));
333   GstPad *pad = GST_PAD_CAST (audio_pad);
334   g_return_if_fail (GST_IS_PAD (pad));
335
336   GST_INFO_OBJECT(pad, "Pausing");
337   gst_pad_push_event (pad, gst_event_new_gap (audio_pad->buffer_time, GST_CLOCK_TIME_NONE));
338
339   audio_pad->discont_pending = TRUE;
340 }
341
342 static void
343 gst_wpe_src_load_bytes (GstWpeVideoSrc * src, GBytes * bytes)
344 {
345   GstWpeSrc *self = GST_WPE_SRC (src);
346
347   if (self->video_src)
348     g_signal_emit_by_name (self->video_src, "load-bytes", bytes, NULL);
349 }
350
351 static void
352 gst_wpe_src_set_location (GstWpeSrc * src, const gchar * location)
353 {
354   g_object_set (src->video_src, "location", location, NULL);
355 }
356
357 static void
358 gst_wpe_src_get_property (GObject * object, guint prop_id,
359     GValue * value, GParamSpec * pspec)
360 {
361   GstWpeSrc *self = GST_WPE_SRC (object);
362
363   if (self->video_src)
364     g_object_get_property (G_OBJECT (self->video_src), pspec->name, value);
365 }
366
367 static void
368 gst_wpe_src_set_property (GObject * object, guint prop_id,
369     const GValue * value, GParamSpec * pspec)
370 {
371   GstWpeSrc *self = GST_WPE_SRC (object);
372
373   if (self->video_src) {
374     if (prop_id == PROP_LOCATION)
375       gst_wpe_src_set_location (self, g_value_get_string (value));
376     else
377       g_object_set_property (G_OBJECT (self->video_src), pspec->name, value);
378   }
379 }
380
381 static GstURIType
382 gst_wpe_src_uri_get_type (GType)
383 {
384   return GST_URI_SRC;
385 }
386
387 static const gchar *const *
388 gst_wpe_src_get_protocols (GType)
389 {
390   static const char *protocols[] = { "wpe", NULL };
391   return protocols;
392 }
393
394 static gchar *
395 gst_wpe_src_get_uri (GstURIHandler * handler)
396 {
397   GstWpeSrc *src = GST_WPE_SRC (handler);
398   gchar *location;
399   gchar *res;
400
401   g_object_get (src->video_src, "location", &location, NULL);
402   res = g_strdup_printf ("wpe://%s", location);
403   g_free (location);
404
405   return res;
406 }
407
408 static gboolean
409 gst_wpe_src_set_uri (GstURIHandler * handler, const gchar * uri,
410     GError ** error)
411 {
412   GstWpeSrc *src = GST_WPE_SRC (handler);
413
414   gst_wpe_src_set_location(src, uri + 6);
415   return TRUE;
416 }
417
418 static void
419 gst_wpe_src_uri_handler_init (gpointer iface_ptr, gpointer data)
420 {
421   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) iface_ptr;
422
423   iface->get_type = gst_wpe_src_uri_get_type;
424   iface->get_protocols = gst_wpe_src_get_protocols;
425   iface->get_uri = gst_wpe_src_get_uri;
426   iface->set_uri = gst_wpe_src_set_uri;
427 }
428
429 static void
430 gst_wpe_src_init (GstWpeSrc * src)
431 {
432   GstPad *pad;
433   GstPad *ghost_pad;
434   GstProxyPad *proxy_pad;
435
436   gst_bin_set_suppressed_flags (GST_BIN_CAST (src),
437       static_cast<GstElementFlags>(GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK));
438   GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_SOURCE);
439
440   src->fd_allocator = gst_fd_allocator_new ();
441   src->audio_src_pads = g_hash_table_new (g_direct_hash, g_direct_equal);
442   src->flow_combiner = gst_flow_combiner_new ();
443   src->video_src = gst_element_factory_make ("wpevideosrc", NULL);
444
445   gst_bin_add (GST_BIN_CAST (src), src->video_src);
446
447   pad = gst_element_get_static_pad (GST_ELEMENT_CAST (src->video_src), "src");
448   ghost_pad = gst_ghost_pad_new_from_template ("video", pad,
449     gst_static_pad_template_get (&video_src_factory));
450   proxy_pad = gst_proxy_pad_get_internal (GST_PROXY_PAD (ghost_pad));
451   gst_pad_set_active (GST_PAD_CAST (proxy_pad), TRUE);
452
453   gst_element_add_pad (GST_ELEMENT_CAST (src), GST_PAD_CAST (ghost_pad));
454   gst_flow_combiner_add_pad (src->flow_combiner, GST_PAD_CAST (ghost_pad));
455   gst_pad_set_chain_function (GST_PAD_CAST (proxy_pad), gst_wpe_src_chain_buffer);
456
457   gst_object_unref (proxy_pad);
458   gst_object_unref (pad);
459 }
460
461 static gboolean
462 gst_wpe_audio_remove_audio_pad  (gint32  *id, GstPad *pad, GstWpeSrc  *self)
463 {
464   gst_wpe_src_remove_audio_pad (self, pad);
465
466   return TRUE;
467 }
468
469 static GstStateChangeReturn
470 gst_wpe_src_change_state (GstElement * element, GstStateChange transition)
471 {
472   GstStateChangeReturn result;
473   GstWpeSrc *src = GST_WPE_SRC (element);
474
475   GST_DEBUG_OBJECT (src, "%s", gst_state_change_get_name (transition));
476   result = GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS , change_state, (element, transition), GST_STATE_CHANGE_FAILURE);
477
478   switch (transition) {
479   case GST_STATE_CHANGE_PAUSED_TO_READY:{
480     g_hash_table_foreach_remove (src->audio_src_pads, (GHRFunc) gst_wpe_audio_remove_audio_pad, src);
481     gst_flow_combiner_reset (src->flow_combiner);
482     break;
483   }
484   default:
485     break;
486   }
487
488   return result;
489 }
490
491 static void
492 gst_wpe_src_dispose (GObject *object)
493 {
494     GstWpeSrc *src = GST_WPE_SRC (object);
495
496     g_hash_table_unref (src->audio_src_pads);
497     gst_flow_combiner_free (src->flow_combiner);
498     gst_object_unref (src->fd_allocator);
499
500     GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
501 }
502
503 static void
504 gst_wpe_src_class_init (GstWpeSrcClass * klass)
505 {
506   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
507   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
508
509   gobject_class->set_property = gst_wpe_src_set_property;
510   gobject_class->get_property = gst_wpe_src_get_property;
511   gobject_class->dispose = gst_wpe_src_dispose;
512
513   g_object_class_install_property (gobject_class, PROP_LOCATION,
514       g_param_spec_string ("location", "location", "The URL to display", "",
515           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
516   g_object_class_install_property (gobject_class, PROP_DRAW_BACKGROUND,
517       g_param_spec_boolean ("draw-background", "Draws the background",
518           "Whether to draw the WebView background", TRUE,
519           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
520
521   gst_element_class_set_static_metadata (element_class, "WPE source",
522       "Source/Video/Audio", "Creates Audio/Video streams from a web"
523       " page using WPE web engine",
524       "Philippe Normand <philn@igalia.com>, Žan Doberšek "
525       "<zdobersek@igalia.com>");
526
527   /**
528    * GstWpeSrc::load-bytes:
529    * @src: the object which received the signal
530    * @bytes: the GBytes data to load
531    *
532    * Load the specified bytes into the internal webView.
533    */
534   gst_wpe_video_src_signals[SIGNAL_LOAD_BYTES] =
535       g_signal_new_class_handler ("load-bytes", G_TYPE_FROM_CLASS (klass),
536       static_cast < GSignalFlags > (G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
537       G_CALLBACK (gst_wpe_src_load_bytes), NULL, NULL, NULL, G_TYPE_NONE, 1,
538       G_TYPE_BYTES);
539
540   element_class->change_state = GST_DEBUG_FUNCPTR (gst_wpe_src_change_state);
541
542   gst_element_class_add_static_pad_template (element_class, &video_src_factory);
543   gst_element_class_add_static_pad_template (element_class, &audio_src_factory);
544 }