1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qgstreamervideosink_p.h"
5#include "qgstvideorenderersink_p.h"
6#include "qgstsubtitlesink_p.h"
7#include <qgstutils_p.h>
8#include <rhi/qrhi.h>
9
10#if QT_CONFIG(gstreamer_gl)
11#include <QGuiApplication>
12#include <QtGui/qopenglcontext.h>
13#include <QWindow>
14#include <qpa/qplatformnativeinterface.h>
15#include <gst/gl/gstglconfig.h>
16
17#if GST_GL_HAVE_WINDOW_X11 && __has_include("X11/Xlib-xcb.h")
18# include <gst/gl/x11/gstgldisplay_x11.h>
19#endif
20#if GST_GL_HAVE_PLATFORM_EGL
21# include <gst/gl/egl/gstgldisplay_egl.h>
22# include <EGL/egl.h>
23# include <EGL/eglext.h>
24#endif
25#if GST_GL_HAVE_WINDOW_WAYLAND && __has_include("wayland-client.h")
26# include <gst/gl/wayland/gstgldisplay_wayland.h>
27#endif
28#endif // #if QT_CONFIG(gstreamer_gl)
29
30#include <QtCore/qdebug.h>
31
32#include <QtCore/qloggingcategory.h>
33
34QT_BEGIN_NAMESPACE
35
36static Q_LOGGING_CATEGORY(qLcMediaVideoSink, "qt.multimedia.videosink")
37
38QGstreamerVideoSink::QGstreamerVideoSink(QVideoSink *parent)
39 : QPlatformVideoSink(parent)
40{
41 sinkBin = QGstBin("videoSinkBin");
42 // This is a hack for some iMX and NVidia platforms. These require the use of a special video
43 // conversion element in the pipeline before the video sink, as they unfortunately
44 // output some proprietary format from the decoder even though it's sometimes marked as
45 // a regular supported video/x-raw format.
46 //
47 // To fix this, simply insert the element into the pipeline if it's available. Otherwise
48 // we simply use an identity element.
49 gstQueue = QGstElement("queue");
50 auto imxVideoConvert = QGstElement("imxvideoconvert_g2d");
51 auto nvidiaVideoConvert = QGstElement("nvvidconv");
52 if (!imxVideoConvert.isNull())
53 gstPreprocess = imxVideoConvert;
54 else if (!nvidiaVideoConvert.isNull())
55 gstPreprocess = nvidiaVideoConvert;
56 else
57 gstPreprocess = QGstElement("identity");
58 sinkBin.add(e1: gstQueue, e2: gstPreprocess);
59 gstQueue.link(next: gstPreprocess);
60 sinkBin.addGhostPad(child: gstQueue, name: "sink");
61
62 gstSubtitleSink = GST_ELEMENT(QGstSubtitleSink::createSink(this));
63}
64
65QGstreamerVideoSink::~QGstreamerVideoSink()
66{
67 unrefGstContexts();
68
69 setPipeline(QGstPipeline());
70}
71
72QGstElement QGstreamerVideoSink::gstSink()
73{
74 updateSinkElement();
75 return sinkBin;
76}
77
78void QGstreamerVideoSink::setPipeline(QGstPipeline pipeline)
79{
80 gstPipeline = pipeline;
81}
82
83bool QGstreamerVideoSink::inStoppedState() const
84{
85 if (gstPipeline.isNull())
86 return true;
87 return gstPipeline.inStoppedState();
88}
89
90void QGstreamerVideoSink::setRhi(QRhi *rhi)
91{
92 if (rhi && rhi->backend() != QRhi::OpenGLES2)
93 rhi = nullptr;
94 if (m_rhi == rhi)
95 return;
96
97 m_rhi = rhi;
98 updateGstContexts();
99 if (!gstQtSink.isNull()) {
100 // force creation of a new sink with proper caps
101 createQtSink();
102 updateSinkElement();
103 }
104}
105
106void QGstreamerVideoSink::createQtSink()
107{
108 gstQtSink = QGstElement(reinterpret_cast<GstElement *>(QGstVideoRendererSink::createSink(surface: this)));
109}
110
111void QGstreamerVideoSink::updateSinkElement()
112{
113 QGstElement newSink;
114 if (gstQtSink.isNull())
115 createQtSink();
116 newSink = gstQtSink;
117
118 if (newSink == gstVideoSink)
119 return;
120
121 gstPipeline.beginConfig();
122
123 if (!gstVideoSink.isNull()) {
124 gstVideoSink.setStateSync(GST_STATE_NULL);
125 sinkBin.remove(element: gstVideoSink);
126 }
127
128 gstVideoSink = newSink;
129 sinkBin.add(element: gstVideoSink);
130 if (!gstPreprocess.link(next: gstVideoSink))
131 qCDebug(qLcMediaVideoSink) << "couldn't link preprocess and sink";
132 gstVideoSink.setState(GST_STATE_PAUSED);
133
134 gstPipeline.endConfig();
135 gstPipeline.dumpGraph(fileName: "updateVideoSink");
136}
137
138void QGstreamerVideoSink::unrefGstContexts()
139{
140 if (m_gstGlDisplayContext)
141 gst_context_unref(context: m_gstGlDisplayContext);
142 m_gstGlDisplayContext = nullptr;
143 if (m_gstGlLocalContext)
144 gst_context_unref(context: m_gstGlLocalContext);
145 m_gstGlLocalContext = nullptr;
146 m_eglDisplay = nullptr;
147 m_eglImageTargetTexture2D = nullptr;
148}
149
150void QGstreamerVideoSink::updateGstContexts()
151{
152 unrefGstContexts();
153
154#if QT_CONFIG(gstreamer_gl)
155 if (!m_rhi || m_rhi->backend() != QRhi::OpenGLES2)
156 return;
157
158 auto *nativeHandles = static_cast<const QRhiGles2NativeHandles *>(m_rhi->nativeHandles());
159 auto glContext = nativeHandles->context;
160 Q_ASSERT(glContext);
161
162 const QString platform = QGuiApplication::platformName();
163 QPlatformNativeInterface *pni = QGuiApplication::platformNativeInterface();
164 m_eglDisplay = pni->nativeResourceForIntegration(resource: "egldisplay");
165// qDebug() << "platform is" << platform << m_eglDisplay;
166
167 GstGLDisplay *gstGlDisplay = nullptr;
168 const char *contextName = "eglcontext";
169 GstGLPlatform glPlatform = GST_GL_PLATFORM_EGL;
170 // use the egl display if we have one
171 if (m_eglDisplay) {
172#if GST_GL_HAVE_PLATFORM_EGL
173 gstGlDisplay = (GstGLDisplay *)gst_gl_display_egl_new_with_egl_display(display: m_eglDisplay);
174 m_eglImageTargetTexture2D = eglGetProcAddress(procname: "glEGLImageTargetTexture2DOES");
175#endif
176 } else {
177 auto display = pni->nativeResourceForIntegration(resource: "display");
178
179 if (display) {
180#if GST_GL_HAVE_WINDOW_X11 && __has_include("X11/Xlib-xcb.h")
181 if (platform == QLatin1String("xcb")) {
182 contextName = "glxcontext";
183 glPlatform = GST_GL_PLATFORM_GLX;
184
185 gstGlDisplay = (GstGLDisplay *)gst_gl_display_x11_new_with_display(display: (Display *)display);
186 }
187#endif
188#if GST_GL_HAVE_WINDOW_WAYLAND && __has_include("wayland-client.h")
189 if (platform.startsWith(s: QLatin1String("wayland"))) {
190 Q_ASSERT(!gstGlDisplay);
191 gstGlDisplay = (GstGLDisplay *)gst_gl_display_wayland_new_with_display(display: (struct wl_display *)display);
192 }
193#endif
194 }
195 }
196
197 if (!gstGlDisplay) {
198 qWarning() << "Could not create GstGLDisplay";
199 return;
200 }
201
202 void *nativeContext = pni->nativeResourceForContext(resource: contextName, context: glContext);
203 if (!nativeContext)
204 qWarning() << "Could not find resource for" << contextName;
205
206 GstGLAPI glApi = QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL ? GST_GL_API_OPENGL : GST_GL_API_GLES2;
207 GstGLContext *appContext = gst_gl_context_new_wrapped(display: gstGlDisplay, handle: (guintptr)nativeContext, context_type: glPlatform, available_apis: glApi);
208 if (!appContext)
209 qWarning() << "Could not create wrappped context for platform:" << glPlatform;
210
211 GstGLContext *displayContext = nullptr;
212 GError *error = nullptr;
213 gst_gl_display_create_context(display: gstGlDisplay, other_context: appContext, p_context: &displayContext, error: &error);
214 if (error) {
215 qWarning() << "Could not create display context:" << error->message;
216 g_clear_error(err: &error);
217 }
218
219 if (appContext)
220 gst_object_unref(object: appContext);
221
222 m_gstGlDisplayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, persistent: false);
223 gst_context_set_gl_display(context: m_gstGlDisplayContext, display: gstGlDisplay);
224 gst_object_unref(object: gstGlDisplay);
225
226 m_gstGlLocalContext = gst_context_new(context_type: "gst.gl.local_context", persistent: false);
227 GstStructure *structure = gst_context_writable_structure(context: m_gstGlLocalContext);
228 gst_structure_set(structure, fieldname: "context", GST_TYPE_GL_CONTEXT, displayContext, nullptr);
229 gst_object_unref(object: displayContext);
230
231 if (!gstPipeline.isNull())
232 gst_element_set_context(element: gstPipeline.element(), context: m_gstGlLocalContext);
233#endif // #if QT_CONFIG(gstreamer_gl)
234}
235
236QT_END_NAMESPACE
237
238#include "moc_qgstreamervideosink_p.cpp"
239

source code of qtmultimedia/src/plugins/multimedia/gstreamer/common/qgstreamervideosink.cpp