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