Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgstreamervideosink.cpp
Go to the documentation of this file.
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 <common/qgstreameregldisplay_p.h>
26# include <gst/gl/egl/gstgldisplay_egl.h>
27# include <EGL/egl.h>
28# include <EGL/eglext.h>
29# endif
30# if QT_CONFIG(gstreamer_gl_wayland)
31# include <gst/gl/wayland/gstgldisplay_wayland.h>
32# endif
33#endif // #if QT_CONFIG(gstreamer_gl)
34
36
37Q_STATIC_LOGGING_CATEGORY(qLcGstVideoSink, "qt.multimedia.gstvideosink");
38
39QGstreamerPluggableVideoSink::QGstreamerPluggableVideoSink(QVideoSink *parent)
40 : QPlatformVideoSink{ parent }
41{
42}
43
44void QGstreamerPluggableVideoSink::setRhi(QRhi *rhi)
45{
46 if (m_rhi == rhi)
47 return;
48
49 m_rhi = rhi;
50 emit rhiChanged();
51}
52
54{
55 return m_rhi;
56}
57
59 : QObject{
60 parent,
61 },
63 QGstBin::create("videoSinkBin"),
64 }
65{
66 // This is a hack for some iMX and NVidia platforms. These require the use of a special video
67 // conversion element in the pipeline before the video sink, as they unfortunately
68 // output some proprietary format from the decoder even though it's sometimes marked as
69 // a regular supported video/x-raw format.
70 //
71 // To fix this, simply insert the element into the pipeline if it's available. Otherwise
72 // we simply use an identity element.
73 QGstElementFactoryHandle factory;
74
75 // QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT allows users to override the
76 // conversion element. Ideally we construct the element programatically, though.
77 QByteArray preprocessOverride = qgetenv("QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT");
78 if (!preprocessOverride.isEmpty()) {
79 qCDebug(qLcGstVideoSink) << "requesting conversion element from environment:"
80 << preprocessOverride;
81
82 m_gstPreprocess = QGstBin::createFromPipelineDescription(preprocessOverride, nullptr,
83 /*ghostUnlinkedPads=*/true);
84 if (!m_gstPreprocess)
85 qCWarning(qLcGstVideoSink) << "Cannot create conversion element:" << preprocessOverride;
86 }
87
88 if (!m_gstPreprocess) {
89 // This is a hack for some iMX and NVidia platforms. These require the use of a special
90 // video conversion element in the pipeline before the video sink, as they unfortunately
91 // output some proprietary format from the decoder even though it's sometimes marked as
92 // a regular supported video/x-raw format.
93 static constexpr auto decodersToTest = {
94 "imxvideoconvert_g2d",
95 "nvvidconv",
96 };
97
98 for (const char *decoder : decodersToTest) {
99 factory = QGstElement::findFactory(decoder);
100 if (factory)
101 break;
102 }
103
104 if (factory) {
105 qCDebug(qLcGstVideoSink)
106 << "instantiating conversion element:"
107 << g_type_name(gst_element_factory_get_element_type(factory.get()));
108
109 m_gstPreprocess = QGstElement::createFromFactory(factory, "preprocess");
110 }
111 }
112
113 bool disablePixelAspectRatio =
114 qEnvironmentVariableIntValue("QT_GSTREAMER_DISABLE_PIXEL_ASPECT_RATIO") != 0;
115 if (disablePixelAspectRatio) {
116 // Enabling the pixel aspect ratio may expose a gstreamer bug on cameras that don't expose a
117 // pixel-aspect-ratio via `VIDIOC_CROPCAP`. This can cause the caps negotiation to fail.
118 // Using the QT_GSTREAMER_DISABLE_PIXEL_ASPECT_RATIO environment variable, one can disable
119 // pixel-aspect-ratio handling
120 //
121 // compare: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6242
122 m_gstCapsFilter =
123 QGstElement::createFromFactory("identity", "nullPixelAspectRatioCapsFilter");
124 } else {
125 m_gstCapsFilter =
126 QGstElement::createFromFactory("capsfilter", "pixelAspectRatioCapsFilter");
127 QGstCaps capsFilterCaps{
128 gst_caps_new_simple("video/x-raw", "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL),
129 QGstCaps::HasRef,
130 };
131 g_object_set(m_gstCapsFilter.element(), "caps", capsFilterCaps.caps(), NULL);
132 }
133
134 if (m_gstPreprocess) {
135 m_sinkBin.add(m_gstPreprocess, m_gstCapsFilter);
136 qLinkGstElements(m_gstPreprocess, m_gstCapsFilter);
137 m_sinkBin.addGhostPad(m_gstPreprocess, "sink");
138 } else {
139 m_sinkBin.add(m_gstCapsFilter);
140 m_sinkBin.addGhostPad(m_gstCapsFilter, "sink");
141 }
142
143 updateGstContexts();
144}
145
147{
148 emit aboutToBeDestroyed();
149
150 unrefGstContexts();
151}
152
154{
155 if (!m_gstVideoSink) {
156 if (!m_gstQtSink)
157 createQtSink();
158
159 updateSinkElement(m_gstQtSink);
160 }
161
162 return m_sinkBin;
163}
164
166{
167 if (m_isActive == isActive)
168 return;
169 m_isActive = isActive;
170
171 if (m_gstQtSink)
172 m_gstQtSink.setActive(isActive);
173}
174
176{
177 m_sinkIsAsync = isAsync;
178}
179
181{
182 Q_ASSERT(pluggableSink);
183 m_pluggableVideoSink = pluggableSink;
184 m_pluggableVideoSink->setVideoFrame(m_currentVideoFrame);
185 m_pluggableVideoSink->setSubtitleText(m_currentSubtitleText);
186 m_pluggableVideoSink->setNativeSize(m_currentNativeSize);
187 connect(this, &QGstreamerRelayVideoSink::videoFrameChanged,
188 m_pluggableVideoSink, &QPlatformVideoSink::setVideoFrame);
189 connect(this, &QGstreamerRelayVideoSink::subtitleTextChanged,
190 m_pluggableVideoSink, &QPlatformVideoSink::setSubtitleText);
192 m_pluggableVideoSink, &QPlatformVideoSink::setNativeSize);
193
194 // Update pipeline contexts using the rendering rhi
195 renderingRhiChanged(m_pluggableVideoSink->rhi());
196 m_rhiConnection = connect(m_pluggableVideoSink, &QPlatformVideoSink::rhiChanged, this, [this](){
197 renderingRhiChanged(m_pluggableVideoSink->rhi());
198 });
199}
200
202{
203 if (m_pluggableVideoSink) {
204 disconnect(this, &QGstreamerRelayVideoSink::videoFrameChanged,
205 m_pluggableVideoSink, &QPlatformVideoSink::setVideoFrame);
206 disconnect(this, &QGstreamerRelayVideoSink::subtitleTextChanged,
207 m_pluggableVideoSink, &QPlatformVideoSink::setSubtitleText);
209 m_pluggableVideoSink, &QPlatformVideoSink::setNativeSize);
210 disconnect(m_rhiConnection);
211 m_pluggableVideoSink = nullptr;
212 }
213}
214
215void QGstreamerRelayVideoSink::setVideoFrame(const QVideoFrame &frame)
216{
217 emit videoFrameChanged(frame);
218 m_currentVideoFrame = frame;
219}
220
221void QGstreamerRelayVideoSink::setSubtitleText(const QString &subtitleText)
222{
223 emit subtitleTextChanged(subtitleText);
224 m_currentSubtitleText = subtitleText;
225}
226
228{
229 emit nativeSizeChanged(size);
230 m_currentNativeSize = size;
231}
232
233void QGstreamerRelayVideoSink::createQtSink()
234{
235 Q_ASSERT(!m_gstQtSink);
236
237 m_gstQtSink = QGstVideoRendererSink::createSink(this);
238 if (!m_sinkIsAsync)
239 m_gstQtSink.set("async", false);
240 m_gstQtSink.setActive(m_isActive);
241}
242
243void QGstreamerRelayVideoSink::updateSinkElement(QGstVideoRendererSinkElement newSink)
244{
245 if (newSink == m_gstVideoSink)
246 return;
247
248 m_gstCapsFilter.src().modifyPipelineInIdleProbe([&] {
249 if (m_gstVideoSink)
250 m_sinkBin.stopAndRemoveElements(m_gstVideoSink);
251
252 m_gstVideoSink = std::move(newSink);
253 m_sinkBin.add(m_gstVideoSink);
254 qLinkGstElements(m_gstCapsFilter, m_gstVideoSink);
255 GstEvent *event = gst_event_new_reconfigure();
256 gst_element_send_event(m_gstVideoSink.element(), event);
257 m_gstVideoSink.syncStateWithParent();
258 });
259
260 m_sinkBin.dumpPipelineGraph("updateSinkElement");
261}
262
263void QGstreamerRelayVideoSink::unrefGstContexts()
264{
265 m_gstGlDisplayContext.reset();
266 m_gstGlLocalContext.reset();
267}
268
269void QGstreamerRelayVideoSink::updateGstContexts()
270{
271 QRhi *currentRhi = rhi();
272
273 using namespace Qt::Literals;
274
275 unrefGstContexts();
276
277#if QT_CONFIG(gstreamer_gl)
278 if (!currentRhi || currentRhi->backend() != QRhi::OpenGLES2)
279 return;
280 auto *nativeHandles = static_cast<const QRhiGles2NativeHandles *>(currentRhi->nativeHandles());
281 auto glContext = nativeHandles->context;
282 Q_ASSERT(glContext);
283
284 const QString platform = QGuiApplication::platformName();
285 QPlatformNativeInterface *pni = QGuiApplication::platformNativeInterface();
286
287 QGstGLDisplayHandle gstGlDisplay;
288
289 QByteArray contextName = "eglcontext"_ba;
290 GstGLPlatform glPlatform = GST_GL_PLATFORM_EGL;
291 // use the egl display if we have one
292# if QT_CONFIG(gstreamer_gl_egl)
293 if (auto eglDisplay = qGstEglDisplay()) {
294 gstGlDisplay.reset(GST_GL_DISPLAY_CAST(gst_gl_display_egl_new_with_egl_display(eglDisplay)),
295 QGstGLDisplayHandle::HasRef);
296 } else
297# endif
298 {
299 auto display = pni->nativeResourceForIntegration("display"_ba);
300
301 if (display) {
302# if QT_CONFIG(gstreamer_gl_x11)
303 if (platform == QLatin1String("xcb")) {
304 contextName = "glxcontext"_ba;
305 glPlatform = GST_GL_PLATFORM_GLX;
306
307 gstGlDisplay.reset(GST_GL_DISPLAY_CAST(gst_gl_display_x11_new_with_display(
308 reinterpret_cast<Display *>(display))),
309 QGstGLDisplayHandle::HasRef);
310 }
311# endif
312# if QT_CONFIG(gstreamer_gl_wayland)
313 if (platform.startsWith(QLatin1String("wayland"))) {
314 Q_ASSERT(!gstGlDisplay);
315 gstGlDisplay.reset(GST_GL_DISPLAY_CAST(gst_gl_display_wayland_new_with_display(
316 reinterpret_cast<struct wl_display *>(display))),
317 QGstGLDisplayHandle::HasRef);
318 }
319#endif
320 }
321 }
322
323 if (!gstGlDisplay) {
324 qWarning() << "Could not create GstGLDisplay";
325 return;
326 }
327
328 void *nativeContext = pni->nativeResourceForContext(contextName, glContext);
329 if (!nativeContext)
330 qWarning() << "Could not find resource for" << contextName;
331
332 GstGLAPI glApi = QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL ? GST_GL_API_OPENGL : GST_GL_API_GLES2;
333 QGstGLContextHandle appContext{
334 gst_gl_context_new_wrapped(gstGlDisplay.get(), guintptr(nativeContext), glPlatform, glApi),
335 QGstGLContextHandle::HasRef,
336 };
337 if (!appContext)
338 qWarning() << "Could not create wrappped context for platform:" << glPlatform;
339
340 gst_gl_context_activate(appContext.get(), true);
341
342 QUniqueGErrorHandle error;
343 gst_gl_context_fill_info(appContext.get(), &error);
344 if (error) {
345 qWarning() << "Could not fill context info:" << error;
346 error = {};
347 }
348
349 QGstGLContextHandle displayContext;
350 gst_gl_display_create_context(gstGlDisplay.get(), appContext.get(), &displayContext, &error);
351 if (error)
352 qWarning() << "Could not create display context:" << error;
353
354 appContext.reset();
355
356 m_gstGlDisplayContext.reset(gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, false),
357 QGstContextHandle::HasRef);
358 gst_context_set_gl_display(m_gstGlDisplayContext.get(), gstGlDisplay.get());
359
360 m_gstGlLocalContext.reset(gst_context_new("gst.gl.local_context", false),
361 QGstContextHandle::HasRef);
362 GstStructure *structure = gst_context_writable_structure(m_gstGlLocalContext.get());
363 gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, displayContext.get(), nullptr);
364 displayContext.reset();
365
366 // Note: after updating the context, we switch the sink and send gst_event_new_reconfigure()
367 // upstream. this will cause the context to be queried again.
368#endif // #if QT_CONFIG(gstreamer_gl)
369}
370
371void QGstreamerRelayVideoSink::renderingRhiChanged(QRhi *rhi)
372{
373 m_rhi = rhi;
374 updateGstContexts();
375}
376
377QT_END_NAMESPACE
378
379#include "moc_qgstreamervideosink_p.cpp"
QGstreamerRelayVideoSink(QObject *parent=nullptr)
void setSubtitleText(const QString &subtitleText)
void connectPluggableVideoSink(QGstreamerPluggableVideoSink *pluggableSink)
void nativeSizeChanged(QSize size)
Combined button and popup list for selecting options.