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 | |
34 | QT_BEGIN_NAMESPACE |
35 | |
36 | static Q_LOGGING_CATEGORY(qLcMediaVideoSink, "qt.multimedia.videosink" ) |
37 | |
38 | QGstreamerVideoSink::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 | |
65 | QGstreamerVideoSink::~QGstreamerVideoSink() |
66 | { |
67 | unrefGstContexts(); |
68 | |
69 | setPipeline(QGstPipeline()); |
70 | } |
71 | |
72 | QGstElement QGstreamerVideoSink::gstSink() |
73 | { |
74 | updateSinkElement(); |
75 | return sinkBin; |
76 | } |
77 | |
78 | void QGstreamerVideoSink::setPipeline(QGstPipeline pipeline) |
79 | { |
80 | gstPipeline = pipeline; |
81 | } |
82 | |
83 | bool QGstreamerVideoSink::inStoppedState() const |
84 | { |
85 | if (gstPipeline.isNull()) |
86 | return true; |
87 | return gstPipeline.inStoppedState(); |
88 | } |
89 | |
90 | void 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 | |
106 | void QGstreamerVideoSink::createQtSink() |
107 | { |
108 | gstQtSink = QGstElement(reinterpret_cast<GstElement *>(QGstVideoRendererSink::createSink(surface: this))); |
109 | } |
110 | |
111 | void 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 | |
138 | void 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 | |
150 | void 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 | |
236 | QT_END_NAMESPACE |
237 | |
238 | #include "moc_qgstreamervideosink_p.cpp" |
239 | |