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 <common/qgstreamervideosink_p.h> |
5 | #include <common/qgstvideorenderersink_p.h> |
6 | #include <common/qgst_debug_p.h> |
7 | #include <common/qgstutils_p.h> |
8 | #include <rhi/qrhi.h> |
9 | |
10 | #include <QtCore/qdebug.h> |
11 | #include <QtCore/qloggingcategory.h> |
12 | |
13 | #if QT_CONFIG(gstreamer_gl) |
14 | # include <QtGui/qguiapplication.h> |
15 | # include <QtGui/qopenglcontext.h> |
16 | # include <QtGui/qwindow.h> |
17 | # include <QtGui/qpa/qplatformnativeinterface.h> |
18 | # include <gst/gl/gstglconfig.h> |
19 | # include <gst/gl/gstgldisplay.h> |
20 | |
21 | # if QT_CONFIG(gstreamer_gl_x11) |
22 | # include <gst/gl/x11/gstgldisplay_x11.h> |
23 | # endif |
24 | # if QT_CONFIG(gstreamer_gl_egl) |
25 | # include <gst/gl/egl/gstgldisplay_egl.h> |
26 | # include <EGL/egl.h> |
27 | # include <EGL/eglext.h> |
28 | # endif |
29 | # if QT_CONFIG(gstreamer_gl_wayland) |
30 | # include <gst/gl/wayland/gstgldisplay_wayland.h> |
31 | # endif |
32 | #endif // #if QT_CONFIG(gstreamer_gl) |
33 | |
34 | QT_BEGIN_NAMESPACE |
35 | |
36 | static Q_LOGGING_CATEGORY(qLcGstVideoSink, "qt.multimedia.gstvideosink" ); |
37 | |
38 | QGstreamerVideoSink::QGstreamerVideoSink(QVideoSink *parent) |
39 | : QPlatformVideoSink{ |
40 | parent, |
41 | }, |
42 | m_sinkBin{ |
43 | QGstBin::create(name: "videoSinkBin" ), |
44 | } |
45 | { |
46 | // This is a hack for some iMX and NVidia platforms. These require the use of a special video |
47 | // conversion element in the pipeline before the video sink, as they unfortunately |
48 | // output some proprietary format from the decoder even though it's sometimes marked as |
49 | // a regular supported video/x-raw format. |
50 | // |
51 | // To fix this, simply insert the element into the pipeline if it's available. Otherwise |
52 | // we simply use an identity element. |
53 | QGstElementFactoryHandle factory; |
54 | |
55 | // QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT allows users to override the |
56 | // conversion element. Ideally we construct the element programatically, though. |
57 | QByteArray preprocessOverride = qgetenv(varName: "QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT" ); |
58 | if (!preprocessOverride.isEmpty()) { |
59 | qCDebug(qLcGstVideoSink) << "requesting conversion element from environment:" |
60 | << preprocessOverride; |
61 | |
62 | m_gstPreprocess = QGstBin::createFromPipelineDescription(pipelineDescription: preprocessOverride, name: nullptr, |
63 | /*ghostUnlinkedPads=*/true); |
64 | if (!m_gstPreprocess) |
65 | qCWarning(qLcGstVideoSink) << "Cannot create conversion element:" << preprocessOverride; |
66 | } |
67 | |
68 | if (!m_gstPreprocess) { |
69 | // This is a hack for some iMX and NVidia platforms. These require the use of a special |
70 | // video conversion element in the pipeline before the video sink, as they unfortunately |
71 | // output some proprietary format from the decoder even though it's sometimes marked as |
72 | // a regular supported video/x-raw format. |
73 | static constexpr auto decodersToTest = { |
74 | "imxvideoconvert_g2d" , |
75 | "nvvidconv" , |
76 | }; |
77 | |
78 | for (const char *decoder : decodersToTest) { |
79 | factory = QGstElement::findFactory(decoder); |
80 | if (factory) |
81 | break; |
82 | } |
83 | |
84 | if (factory) { |
85 | qCDebug(qLcGstVideoSink) |
86 | << "instantiating conversion element:" |
87 | << g_type_name(type: gst_element_factory_get_element_type(factory: factory.get())); |
88 | |
89 | m_gstPreprocess = QGstElement::createFromFactory(factory, name: "preprocess" ); |
90 | } |
91 | } |
92 | |
93 | bool disablePixelAspectRatio = |
94 | qEnvironmentVariableIsSet(varName: "QT_GSTREAMER_DISABLE_PIXEL_ASPECT_RATIO" ); |
95 | if (disablePixelAspectRatio) { |
96 | // Enabling the pixel aspect ratio may expose a gstreamer bug on cameras that don't expose a |
97 | // pixel-aspect-ratio via `VIDIOC_CROPCAP`. This can cause the caps negotiation to fail. |
98 | // Using the QT_GSTREAMER_DISABLE_PIXEL_ASPECT_RATIO environment variable, one can disable |
99 | // pixel-aspect-ratio handling |
100 | // |
101 | // compare: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6242 |
102 | m_gstCapsFilter = |
103 | QGstElement::createFromFactory(factory: "identity" , name: "nullPixelAspectRatioCapsFilter" ); |
104 | } else { |
105 | m_gstCapsFilter = |
106 | QGstElement::createFromFactory(factory: "capsfilter" , name: "pixelAspectRatioCapsFilter" ); |
107 | QGstCaps capsFilterCaps{ |
108 | gst_caps_new_simple(media_type: "video/x-raw" , fieldname: "pixel-aspect-ratio" , GST_TYPE_FRACTION, 1, 1, NULL), |
109 | QGstCaps::HasRef, |
110 | }; |
111 | g_object_set(object: m_gstCapsFilter.element(), first_property_name: "caps" , capsFilterCaps.caps(), NULL); |
112 | } |
113 | |
114 | if (m_gstPreprocess) { |
115 | m_sinkBin.add(ts: m_gstPreprocess, ts: m_gstCapsFilter); |
116 | qLinkGstElements(ts: m_gstPreprocess, ts: m_gstCapsFilter); |
117 | m_sinkBin.addGhostPad(child: m_gstPreprocess, name: "sink" ); |
118 | } else { |
119 | m_sinkBin.add(ts: m_gstCapsFilter); |
120 | m_sinkBin.addGhostPad(child: m_gstCapsFilter, name: "sink" ); |
121 | } |
122 | } |
123 | |
124 | QGstreamerVideoSink::~QGstreamerVideoSink() |
125 | { |
126 | emit aboutToBeDestroyed(); |
127 | |
128 | unrefGstContexts(); |
129 | } |
130 | |
131 | QGstElement QGstreamerVideoSink::gstSink() |
132 | { |
133 | if (!m_gstVideoSink) { |
134 | if (!m_gstQtSink) |
135 | createQtSink(); |
136 | |
137 | updateSinkElement(newSink: m_gstQtSink); |
138 | } |
139 | |
140 | return m_sinkBin; |
141 | } |
142 | |
143 | void QGstreamerVideoSink::setActive(bool isActive) |
144 | { |
145 | if (m_isActive == isActive) |
146 | return; |
147 | m_isActive = isActive; |
148 | |
149 | if (m_gstQtSink) |
150 | m_gstQtSink.setActive(isActive); |
151 | } |
152 | |
153 | void QGstreamerVideoSink::setAsync(bool isAsync) |
154 | { |
155 | m_sinkIsAsync = isAsync; |
156 | } |
157 | |
158 | void QGstreamerVideoSink::setRhi(QRhi *rhi) |
159 | { |
160 | if (rhi && rhi->backend() != QRhi::OpenGLES2) |
161 | rhi = nullptr; |
162 | if (m_rhi == rhi) |
163 | return; |
164 | |
165 | m_rhi = rhi; |
166 | updateGstContexts(); |
167 | if (m_gstQtSink) { |
168 | QGstVideoRendererSinkElement oldSink = std::move(m_gstQtSink); |
169 | |
170 | // force creation of a new sink with proper caps. |
171 | createQtSink(); |
172 | updateSinkElement(newSink: m_gstQtSink); |
173 | } |
174 | } |
175 | |
176 | void QGstreamerVideoSink::createQtSink() |
177 | { |
178 | Q_ASSERT(!m_gstQtSink); |
179 | |
180 | m_gstQtSink = QGstVideoRendererSink::createSink(surface: this); |
181 | if (!m_sinkIsAsync) |
182 | m_gstQtSink.set(property: "async" , b: false); |
183 | m_gstQtSink.setActive(m_isActive); |
184 | } |
185 | |
186 | void QGstreamerVideoSink::updateSinkElement(QGstVideoRendererSinkElement newSink) |
187 | { |
188 | if (newSink == m_gstVideoSink) |
189 | return; |
190 | |
191 | m_gstCapsFilter.src().modifyPipelineInIdleProbe(f: [&] { |
192 | if (m_gstVideoSink) |
193 | m_sinkBin.stopAndRemoveElements(ts&: m_gstVideoSink); |
194 | |
195 | m_gstVideoSink = std::move(newSink); |
196 | m_sinkBin.add(ts: m_gstVideoSink); |
197 | qLinkGstElements(ts: m_gstCapsFilter, ts: m_gstVideoSink); |
198 | GstEvent *event = gst_event_new_reconfigure(); |
199 | gst_element_send_event(element: m_gstVideoSink.element(), event); |
200 | m_gstVideoSink.syncStateWithParent(); |
201 | }); |
202 | |
203 | m_sinkBin.dumpPipelineGraph(filename: "updateSinkElement" ); |
204 | } |
205 | |
206 | void QGstreamerVideoSink::unrefGstContexts() |
207 | { |
208 | m_gstGlDisplayContext.close(); |
209 | m_gstGlLocalContext.close(); |
210 | m_eglDisplay = nullptr; |
211 | m_eglImageTargetTexture2D = nullptr; |
212 | } |
213 | |
214 | void QGstreamerVideoSink::updateGstContexts() |
215 | { |
216 | using namespace Qt::Literals; |
217 | |
218 | unrefGstContexts(); |
219 | |
220 | #if QT_CONFIG(gstreamer_gl) |
221 | if (!m_rhi || m_rhi->backend() != QRhi::OpenGLES2) |
222 | return; |
223 | |
224 | auto *nativeHandles = static_cast<const QRhiGles2NativeHandles *>(m_rhi->nativeHandles()); |
225 | auto glContext = nativeHandles->context; |
226 | Q_ASSERT(glContext); |
227 | |
228 | const QString platform = QGuiApplication::platformName(); |
229 | QPlatformNativeInterface *pni = QGuiApplication::platformNativeInterface(); |
230 | m_eglDisplay = pni->nativeResourceForIntegration(resource: "egldisplay"_ba ); |
231 | // qDebug() << "platform is" << platform << m_eglDisplay; |
232 | |
233 | QGstGLDisplayHandle gstGlDisplay; |
234 | |
235 | QByteArray contextName = "eglcontext"_ba ; |
236 | GstGLPlatform glPlatform = GST_GL_PLATFORM_EGL; |
237 | // use the egl display if we have one |
238 | if (m_eglDisplay) { |
239 | # if QT_CONFIG(gstreamer_gl_egl) |
240 | gstGlDisplay.reset( |
241 | GST_GL_DISPLAY_CAST(gst_gl_display_egl_new_with_egl_display(m_eglDisplay))); |
242 | m_eglImageTargetTexture2D = eglGetProcAddress(procname: "glEGLImageTargetTexture2DOES" ); |
243 | # endif |
244 | } else { |
245 | auto display = pni->nativeResourceForIntegration(resource: "display"_ba ); |
246 | |
247 | if (display) { |
248 | # if QT_CONFIG(gstreamer_gl_x11) |
249 | if (platform == QLatin1String("xcb" )) { |
250 | contextName = "glxcontext"_ba ; |
251 | glPlatform = GST_GL_PLATFORM_GLX; |
252 | |
253 | gstGlDisplay.reset(GST_GL_DISPLAY_CAST( |
254 | gst_gl_display_x11_new_with_display(reinterpret_cast<Display *>(display)))); |
255 | } |
256 | # endif |
257 | # if QT_CONFIG(gstreamer_gl_wayland) |
258 | if (platform.startsWith(s: QLatin1String("wayland" ))) { |
259 | Q_ASSERT(!gstGlDisplay); |
260 | gstGlDisplay.reset(GST_GL_DISPLAY_CAST(gst_gl_display_wayland_new_with_display( |
261 | reinterpret_cast<struct wl_display *>(display)))); |
262 | } |
263 | #endif |
264 | } |
265 | } |
266 | |
267 | if (!gstGlDisplay) { |
268 | qWarning() << "Could not create GstGLDisplay" ; |
269 | return; |
270 | } |
271 | |
272 | void *nativeContext = pni->nativeResourceForContext(resource: contextName, context: glContext); |
273 | if (!nativeContext) |
274 | qWarning() << "Could not find resource for" << contextName; |
275 | |
276 | GstGLAPI glApi = QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL ? GST_GL_API_OPENGL : GST_GL_API_GLES2; |
277 | QGstGLContextHandle appContext{ |
278 | gst_gl_context_new_wrapped(display: gstGlDisplay.get(), handle: guintptr(nativeContext), context_type: glPlatform, available_apis: glApi), |
279 | }; |
280 | if (!appContext) |
281 | qWarning() << "Could not create wrappped context for platform:" << glPlatform; |
282 | |
283 | gst_gl_context_activate(context: appContext.get(), activate: true); |
284 | |
285 | QUniqueGErrorHandle error; |
286 | gst_gl_context_fill_info(context: appContext.get(), error: &error); |
287 | if (error) { |
288 | qWarning() << "Could not fill context info:" << error; |
289 | error = {}; |
290 | } |
291 | |
292 | QGstGLContextHandle displayContext; |
293 | gst_gl_display_create_context(display: gstGlDisplay.get(), other_context: appContext.get(), p_context: &displayContext, error: &error); |
294 | if (error) |
295 | qWarning() << "Could not create display context:" << error; |
296 | |
297 | appContext.close(); |
298 | |
299 | m_gstGlDisplayContext.reset(handle: gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, persistent: false)); |
300 | gst_context_set_gl_display(context: m_gstGlDisplayContext.get(), display: gstGlDisplay.get()); |
301 | |
302 | m_gstGlLocalContext.reset(handle: gst_context_new(context_type: "gst.gl.local_context" , persistent: false)); |
303 | GstStructure *structure = gst_context_writable_structure(context: m_gstGlLocalContext.get()); |
304 | gst_structure_set(structure, fieldname: "context" , GST_TYPE_GL_CONTEXT, displayContext.get(), nullptr); |
305 | displayContext.close(); |
306 | |
307 | // Note: after updating the context, we switch the sink and send gst_event_new_reconfigure() |
308 | // upstream. this will cause the context to be queried again. |
309 | #endif // #if QT_CONFIG(gstreamer_gl) |
310 | } |
311 | |
312 | QT_END_NAMESPACE |
313 | |
314 | #include "moc_qgstreamervideosink_p.cpp" |
315 | |