1 /* Copyright (C) <2018, 2019> Philippe Normand <philn@igalia.com>
2 * Copyright (C) <2018, 2019> Žan Doberšek <zdobersek@igalia.com>
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.
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.
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.
21 * SECTION:element-wpesrc
24 * The wpesrc element is used to produce a video texture representing a web page
25 * rendered off-screen by WPE.
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.
33 * Additionally, any audio stream created by WPE is exposed as "sometimes" audio
37 #include "gstwpesrcbin.h"
38 #include "gstwpevideosrc.h"
39 #include "gstwpe-private.h"
40 #include "WPEThreadedView.h"
42 #include <gst/allocators/allocators.h>
43 #include <gst/base/gstflowcombiner.h>
44 #include <wpe/extensions/audio.h>
46 G_DEFINE_TYPE (GstWpeAudioPad, gst_wpe_audio_pad, GST_TYPE_GHOST_PAD);
49 gst_wpe_audio_pad_class_init (GstWpeAudioPadClass * klass)
54 gst_wpe_audio_pad_init (GstWpeAudioPad * pad)
56 gst_audio_info_init (&pad->info);
57 pad->discont_pending = FALSE;
58 pad->buffer_time = GST_CLOCK_TIME_NONE;
61 static GstWpeAudioPad *
62 gst_wpe_audio_pad_new (const gchar * name)
64 GstWpeAudioPad *pad = GST_WPE_AUDIO_PAD (g_object_new (gst_wpe_audio_pad_get_type (),
65 "name", name, "direction", GST_PAD_SRC, NULL));
67 if (!gst_ghost_pad_construct (GST_GHOST_PAD (pad))) {
68 gst_object_unref (pad);
79 GstAllocator *fd_allocator;
80 GstElement *video_src;
81 GHashTable *audio_src_pads;
82 GstFlowCombiner *flow_combiner;
98 static guint gst_wpe_video_src_signals[LAST_SIGNAL] = { 0 };
100 static void gst_wpe_src_uri_handler_init (gpointer iface, gpointer data);
102 GST_DEBUG_CATEGORY_EXTERN (wpe_src_debug);
103 #define GST_CAT_DEFAULT wpe_src_debug
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));
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
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"
128 static GstStaticPadTemplate audio_src_factory =
129 GST_STATIC_PAD_TEMPLATE ("audio_%u", GST_PAD_SRC, GST_PAD_SOMETIMES,
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" \
137 gst_wpe_src_chain_buffer (GstPad * pad, GstObject * parent, GstBuffer * buffer)
139 GstWpeSrc *src = GST_WPE_SRC (gst_object_get_parent (parent));
140 GstFlowReturn result, chain_result;
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);
146 if (result == GST_FLOW_FLUSHING)
153 on_audio_receiver_handle_start(void* data, uint32_t id, int32_t channels, const char* format, int32_t sampleRate)
155 GstWpeSrc* src = GST_WPE_SRC (data);
156 GstWpeAudioPad *audio_pad;
159 GstEvent *stream_start;
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);
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);
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);
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);
187 gst_segment_init (&segment, GST_FORMAT_TIME);
188 gst_pad_push_event (pad, gst_event_new_segment (&segment));
190 g_hash_table_insert (src->audio_src_pads, GUINT_TO_POINTER (id), audio_pad);
194 on_audio_receiver_handle_packet(void* data, struct wpe_audio_packet_export* packet_export, uint32_t id, int32_t fd, uint32_t size)
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);
202 g_return_if_fail (GST_IS_PAD (pad));
203 g_return_if_fail (fd >= 0);
205 GST_TRACE_OBJECT (pad, "Handling incoming audio packet");
206 buffer = gst_buffer_new ();
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);
212 clock = gst_element_get_clock (GST_ELEMENT_CAST (src));
215 GstClockTime base_time = gst_element_get_base_time (GST_ELEMENT_CAST (src));
217 now = gst_clock_get_time (clock);
222 gst_object_unref (clock);
224 audio_pad->buffer_time = now;
225 GST_BUFFER_DTS (buffer) = audio_pad->buffer_time;
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;
234 gst_flow_combiner_update_pad_flow (src->flow_combiner, pad, gst_pad_push (pad, buffer));
235 wpe_audio_packet_export_release (packet_export);
240 on_audio_receiver_handle_stop(void* data, uint32_t id)
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));
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));
255 on_audio_receiver_handle_pause(void* data, uint32_t id)
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));
262 GST_INFO_OBJECT(pad, "Pausing");
263 gst_pad_push_event (pad, gst_event_new_gap (audio_pad->buffer_time, GST_CLOCK_TIME_NONE));
265 audio_pad->discont_pending = TRUE;
269 on_audio_receiver_handle_resume(void* data, uint32_t id)
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));
276 GST_INFO_OBJECT(pad, "Resuming");
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
289 gst_wpe_src_load_bytes (GstWpeVideoSrc * src, GBytes * bytes)
291 GstWpeSrc *self = GST_WPE_SRC (src);
294 g_signal_emit_by_name (self->video_src, "load-bytes", bytes, NULL);
298 gst_wpe_src_set_location (GstWpeSrc * src, const gchar * location)
302 GstProxyPad *proxy_pad;
304 g_object_set (src->video_src, "location", location, NULL);
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);
312 gst_bin_add (GST_BIN_CAST (src), src->video_src);
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);
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);
324 gst_object_unref (proxy_pad);
325 gst_object_unref (pad);
329 gst_wpe_src_get_property (GObject * object, guint prop_id,
330 GValue * value, GParamSpec * pspec)
332 GstWpeSrc *self = GST_WPE_SRC (object);
335 g_object_get_property (G_OBJECT (self->video_src), pspec->name, value);
339 gst_wpe_src_set_property (GObject * object, guint prop_id,
340 const GValue * value, GParamSpec * pspec)
342 GstWpeSrc *self = GST_WPE_SRC (object);
344 if (self->video_src) {
345 if (prop_id == PROP_LOCATION)
346 gst_wpe_src_set_location (self, g_value_get_string (value));
348 g_object_set_property (G_OBJECT (self->video_src), pspec->name, value);
353 gst_wpe_src_uri_get_type (GType)
358 static const gchar *const *
359 gst_wpe_src_get_protocols (GType)
361 static const char *protocols[] = { "wpe", NULL };
366 gst_wpe_src_get_uri (GstURIHandler * handler)
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);
375 gst_wpe_src_set_uri (GstURIHandler * handler, const gchar * uri,
378 GstWpeSrc *src = GST_WPE_SRC (handler);
380 gst_wpe_src_set_location(src, uri + 6);
385 gst_wpe_src_uri_handler_init (gpointer iface_ptr, gpointer data)
387 GstURIHandlerInterface *iface = (GstURIHandlerInterface *) iface_ptr;
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;
396 gst_wpe_src_init (GstWpeSrc * src)
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);
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);
407 gst_wpe_video_src_register_audio_receiver (src->video_src, &audio_receiver, src);
410 static GstStateChangeReturn
411 gst_wpe_src_change_state (GstElement * element, GstStateChange transition)
413 GstStateChangeReturn result;
414 GstWpeSrc *src = GST_WPE_SRC (element);
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);
419 switch (transition) {
420 case GST_STATE_CHANGE_PAUSED_TO_READY:{
421 gst_flow_combiner_reset (src->flow_combiner);
432 gst_wpe_src_dispose (GObject *object)
434 GstWpeSrc *src = GST_WPE_SRC (object);
436 g_hash_table_unref (src->audio_src_pads);
437 gst_flow_combiner_free (src->flow_combiner);
438 gst_object_unref (src->fd_allocator);
440 GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
444 gst_wpe_src_class_init (GstWpeSrcClass * klass)
446 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
447 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
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;
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)));
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>");
467 * GstWpeSrc::load-bytes:
468 * @src: the object which received the signal
469 * @bytes: the GBytes data to load
471 * Load the specified bytes into the internal webView.
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,
479 element_class->change_state = GST_DEBUG_FUNCPTR (gst_wpe_src_change_state);
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);