Fix build with gstreamer 1.12
[WebKit-https.git] / Source / WebCore / platform / graphics / gstreamer / GLVideoSinkGStreamer.cpp
1 /*
2  * Copyright (C) 2019 Igalia S.L
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 License
15  * aint with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21 #include "GLVideoSinkGStreamer.h"
22
23 #if ENABLE(VIDEO) && USE(GSTREAMER_GL)
24
25 #include "GStreamerCommon.h"
26 #include "MediaPlayerPrivateGStreamer.h"
27 #include "PlatformDisplay.h"
28
29 #include <gst/app/gstappsink.h>
30 #include <wtf/glib/WTFGType.h>
31
32 // gstglapi.h may include eglplatform.h and it includes X.h, which
33 // defines None, breaking MediaPlayer::None enum
34 #if PLATFORM(X11) && GST_GL_HAVE_PLATFORM_EGL
35 #undef None
36 #endif // PLATFORM(X11) && GST_GL_HAVE_PLATFORM_EGL
37
38 using namespace WebCore;
39
40 enum {
41     PROP_0,
42     PROP_STATS,
43     PROP_LAST
44 };
45
46 struct _WebKitGLVideoSinkPrivate {
47     GRefPtr<GstElement> appSink;
48     GRefPtr<GstContext> glDisplayElementContext;
49     GRefPtr<GstContext> glAppElementContext;
50     MediaPlayerPrivateGStreamer* mediaPlayerPrivate;
51 };
52
53 GST_DEBUG_CATEGORY_STATIC(webkit_gl_video_sink_debug);
54 #define GST_CAT_DEFAULT webkit_gl_video_sink_debug
55
56 #define GST_GL_CAPS_FORMAT "{ RGBx, RGBA, I420, Y444, YV12, Y41B, Y42B, NV12, NV21, VUYA }"
57 static GstStaticPadTemplate sinkTemplate = GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
58
59 #define webkit_gl_video_sink_parent_class parent_class
60 WEBKIT_DEFINE_TYPE_WITH_CODE(WebKitGLVideoSink, webkit_gl_video_sink, GST_TYPE_BIN,
61     GST_DEBUG_CATEGORY_INIT(webkit_gl_video_sink_debug, "webkitglvideosink", 0, "GL video sink element"))
62
63 static void webKitGLVideoSinkConstructed(GObject* object)
64 {
65     GST_CALL_PARENT(G_OBJECT_CLASS, constructed, (object));
66
67     WebKitGLVideoSink* sink = WEBKIT_GL_VIDEO_SINK(object);
68
69     sink->priv->appSink = gst_element_factory_make("appsink", "webkit-gl-video-appsink");
70     ASSERT(sink->priv->appSink);
71     g_object_set(sink->priv->appSink.get(), "enable-last-sample", FALSE, "emit-signals", TRUE, "max-buffers", 1, nullptr);
72
73     GstElement* upload = gst_element_factory_make("glupload", nullptr);
74     GstElement* colorconvert = gst_element_factory_make("glcolorconvert", nullptr);
75     ASSERT(upload);
76     ASSERT(colorconvert);
77     gst_bin_add_many(GST_BIN_CAST(sink), upload, colorconvert, sink->priv->appSink.get(), nullptr);
78
79     // Workaround until we can depend on GStreamer 1.16.2.
80     // https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/commit/8d32de090554cf29fe359f83aa46000ba658a693
81     // Forcing a color conversion to RGBA here allows glupload to internally use
82     // an uploader that adds a VideoMeta, through the TextureUploadMeta caps
83     // feature, without needing the patch above. However this specific caps
84     // feature is going to be removed from GStreamer so it is considered a
85     // short-term workaround. This code path most likely will have a negative
86     // performance impact on embedded platforms as well. Downstream embedders
87     // are highly encouraged to cherry-pick the patch linked above in their BSP
88     // and set the WEBKIT_GST_NO_RGBA_CONVERSION environment variable until
89     // GStreamer 1.16.2 is released.
90     // See also https://bugs.webkit.org/show_bug.cgi?id=201422
91     GRefPtr<GstCaps> caps;
92     if (webkitGstCheckVersion(1, 16, 2) || getenv("WEBKIT_GST_NO_RGBA_CONVERSION"))
93         caps = adoptGRef(gst_caps_from_string("video/x-raw, format = (string) " GST_GL_CAPS_FORMAT));
94     else {
95         GST_INFO_OBJECT(sink, "Forcing RGBA as GStreamer is not new enough.");
96         caps = adoptGRef(gst_caps_from_string("video/x-raw, format = (string) RGBA"));
97     }
98     gst_caps_set_features(caps.get(), 0, gst_caps_features_new(GST_CAPS_FEATURE_MEMORY_GL_MEMORY, nullptr));
99     g_object_set(sink->priv->appSink.get(), "caps", caps.get(), nullptr);
100
101     gst_element_link_many(upload, colorconvert, sink->priv->appSink.get(), nullptr);
102
103     GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(upload, "sink"));
104     gst_element_add_pad(GST_ELEMENT_CAST(sink), gst_ghost_pad_new("sink", pad.get()));
105 }
106
107 void webKitGLVideoSinkFinalize(GObject* object)
108 {
109     ASSERT(isMainThread());
110
111     WebKitGLVideoSink* sink = WEBKIT_GL_VIDEO_SINK(object);
112     WebKitGLVideoSinkPrivate* priv = sink->priv;
113
114     if (priv->mediaPlayerPrivate)
115         g_signal_handlers_disconnect_by_data(priv->appSink.get(), priv->mediaPlayerPrivate);
116
117     GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
118 }
119
120 GRefPtr<GstContext> requestGLContext(const char* contextType)
121 {
122     auto& sharedDisplay = PlatformDisplay::sharedDisplayForCompositing();
123     auto* gstGLDisplay = sharedDisplay.gstGLDisplay();
124     auto* gstGLContext = sharedDisplay.gstGLContext();
125     ASSERT(gstGLDisplay && gstGLContext);
126
127     if (!g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE)) {
128         GstContext* displayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
129         gst_context_set_gl_display(displayContext, gstGLDisplay);
130         return adoptGRef(displayContext);
131     }
132
133     if (!g_strcmp0(contextType, "gst.gl.app_context")) {
134         GstContext* appContext = gst_context_new("gst.gl.app_context", TRUE);
135         GstStructure* structure = gst_context_writable_structure(appContext);
136 #if GST_CHECK_VERSION(1, 12, 0)
137         gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, gstGLContext, nullptr);
138 #else
139         gst_structure_set(structure, "context", GST_GL_TYPE_CONTEXT, gstGLContext, nullptr);
140 #endif
141         return adoptGRef(appContext);
142     }
143
144     return nullptr;
145 }
146
147 static GstStateChangeReturn webKitGLVideoSinkChangeState(GstElement* element, GstStateChange transition)
148 {
149     WebKitGLVideoSink* sink = WEBKIT_GL_VIDEO_SINK(element);
150     WebKitGLVideoSinkPrivate* priv = sink->priv;
151
152 #if GST_CHECK_VERSION(1, 14, 0)
153     GST_DEBUG_OBJECT(element, "%s", gst_state_change_get_name(transition));
154 #endif
155
156     switch (transition) {
157     case GST_STATE_CHANGE_NULL_TO_READY:
158 #if GST_CHECK_VERSION(1, 14, 0)
159     case GST_STATE_CHANGE_READY_TO_READY:
160 #endif
161     case GST_STATE_CHANGE_READY_TO_PAUSED: {
162         if (!priv->glDisplayElementContext)
163             priv->glDisplayElementContext = requestGLContext(GST_GL_DISPLAY_CONTEXT_TYPE);
164
165         if (priv->glDisplayElementContext)
166             gst_element_set_context(GST_ELEMENT_CAST(sink), priv->glDisplayElementContext.get());
167
168         if (!priv->glAppElementContext)
169             priv->glAppElementContext = requestGLContext("gst.gl.app_context");
170
171         if (priv->glAppElementContext)
172             gst_element_set_context(GST_ELEMENT_CAST(sink), priv->glAppElementContext.get());
173         break;
174     }
175     default:
176         break;
177     }
178
179     return GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
180 }
181
182 static void webKitGLVideoSinkGetProperty(GObject* object, guint propertyId, GValue* value, GParamSpec* paramSpec)
183 {
184     WebKitGLVideoSink* sink = WEBKIT_GL_VIDEO_SINK(object);
185
186     switch (propertyId) {
187     case PROP_STATS:
188         if (webkitGstCheckVersion(1, 17, 0)) {
189             GUniqueOutPtr<GstStructure> stats;
190             g_object_get(sink->priv->appSink.get(), "stats", &stats.outPtr(), nullptr);
191             gst_value_set_structure(value, stats.get());
192         }
193         break;
194     default:
195         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, paramSpec);
196         RELEASE_ASSERT_NOT_REACHED();
197         break;
198     }
199 }
200
201 static void webkit_gl_video_sink_class_init(WebKitGLVideoSinkClass* klass)
202 {
203     GObjectClass* objectClass = G_OBJECT_CLASS(klass);
204     GstElementClass* elementClass = GST_ELEMENT_CLASS(klass);
205
206     objectClass->constructed = webKitGLVideoSinkConstructed;
207     objectClass->finalize = webKitGLVideoSinkFinalize;
208     objectClass->get_property = webKitGLVideoSinkGetProperty;
209
210     gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&sinkTemplate));
211     gst_element_class_set_static_metadata(elementClass, "WebKit GL video sink", "Sink/Video", "Renders video", "Philippe Normand <philn@igalia.com>");
212
213     g_object_class_install_property(objectClass, PROP_STATS, g_param_spec_boxed("stats", "Statistics",
214         "Sink Statistics", GST_TYPE_STRUCTURE, static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
215
216     elementClass->change_state = GST_DEBUG_FUNCPTR(webKitGLVideoSinkChangeState);
217 }
218
219 void webKitGLVideoSinkSetMediaPlayerPrivate(WebKitGLVideoSink* sink, MediaPlayerPrivateGStreamer* player)
220 {
221     WebKitGLVideoSinkPrivate* priv = sink->priv;
222
223     priv->mediaPlayerPrivate = player;
224     g_signal_connect(priv->appSink.get(), "new-sample", G_CALLBACK(+[](GstElement* sink, MediaPlayerPrivateGStreamer* player) -> GstFlowReturn {
225         GRefPtr<GstSample> sample = adoptGRef(gst_app_sink_pull_sample(GST_APP_SINK(sink)));
226         player->triggerRepaint(sample.get());
227         return GST_FLOW_OK;
228     }), player);
229     g_signal_connect(priv->appSink.get(), "new-preroll", G_CALLBACK(+[](GstElement* sink, MediaPlayerPrivateGStreamer* player) -> GstFlowReturn {
230         GRefPtr<GstSample> sample = adoptGRef(gst_app_sink_pull_preroll(GST_APP_SINK(sink)));
231         player->triggerRepaint(sample.get());
232         return GST_FLOW_OK;
233     }), player);
234
235     GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(priv->appSink.get(), "sink"));
236     gst_pad_add_probe(pad.get(), static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_PUSH | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), [] (GstPad*, GstPadProbeInfo* info,  gpointer userData) -> GstPadProbeReturn {
237         // In some platforms (e.g. OpenMAX on the Raspberry Pi) when a resolution change occurs the
238         // pipeline has to be drained before a frame with the new resolution can be decoded.
239         // In this context, it's important that we don't hold references to any previous frame
240         // (e.g. m_sample) so that decoding can continue.
241         // We are also not supposed to keep the original frame after a flush.
242         if (info->type & GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM) {
243             if (GST_QUERY_TYPE(GST_PAD_PROBE_INFO_QUERY(info)) != GST_QUERY_DRAIN)
244                 return GST_PAD_PROBE_OK;
245             GST_DEBUG("Acting upon DRAIN query");
246         }
247         if (info->type & GST_PAD_PROBE_TYPE_EVENT_FLUSH) {
248             if (GST_EVENT_TYPE(GST_PAD_PROBE_INFO_EVENT(info)) != GST_EVENT_FLUSH_START)
249                 return GST_PAD_PROBE_OK;
250             GST_DEBUG("Acting upon flush-start event");
251         }
252
253         auto* player = static_cast<MediaPlayerPrivateGStreamer*>(userData);
254         player->flushCurrentBuffer();
255         return GST_PAD_PROBE_OK;
256     }, player, nullptr);
257 }
258
259 bool webKitGLVideoSinkProbePlatform()
260 {
261     if (!PlatformDisplay::sharedDisplayForCompositing().gstGLContext()) {
262         GST_WARNING("WebKit shared GL context is not available.");
263         return false;
264     }
265
266     return isGStreamerPluginAvailable("app") && isGStreamerPluginAvailable("opengl");
267 }
268
269 #endif