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#include <optional>
14#include <variant>
15
16#if QT_CONFIG(gstreamer_gl)
17# include <QtGui/qguiapplication.h>
18# include <QtGui/qguiapplication_platform.h>
19# include <QtGui/qopenglcontext.h>
20# include <QtGui/qwindow.h>
21# include <gst/gl/gstglconfig.h>
22# include <gst/gl/gstgldisplay.h>
23
24# if QT_CONFIG(gstreamer_gl_x11)
25# include <gst/gl/x11/gstgldisplay_x11.h>
26# endif
27# if QT_CONFIG(gstreamer_gl_egl)
28# include <common/qgstreameregldisplay_p.h>
29# include <QtMultimedia/private/qeglimagefunctions_p.h>
30# include <QtMultimedia/private/qmultimedia_gl_support_p.h>
31# include <gst/gl/egl/gstgldisplay_egl.h>
32# include <EGL/egl.h>
33# include <EGL/eglext.h>
34# endif
35# if QT_CONFIG(gstreamer_gl_wayland)
36# include <gst/gl/wayland/gstgldisplay_wayland.h>
37# endif
38#endif // #if QT_CONFIG(gstreamer_gl)
39
41
42Q_STATIC_LOGGING_CATEGORY(qLcGstVideoSink, "qt.multimedia.gstvideosink");
43
45 : QObject{
46 parent,
47 },
49 QGstBin::create("videoSinkBin"),
50 }
51{
52 // This is a hack for some iMX and NVidia platforms. These require the use of a special video
53 // conversion element in the pipeline before the video sink, as they unfortunately
54 // output some proprietary format from the decoder even though it's sometimes marked as
55 // a regular supported video/x-raw format.
56 //
57 // To fix this, simply insert the element into the pipeline if it's available. Otherwise
58 // we simply use an identity element.
59 QGstElementFactoryHandle factory;
60
61 // QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT allows users to override the
62 // conversion element. Ideally we construct the element programatically, though.
63 QByteArray preprocessOverride = qgetenv("QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT");
64 if (!preprocessOverride.isEmpty()) {
65 qCDebug(qLcGstVideoSink) << "requesting conversion element from environment:"
66 << preprocessOverride;
67
68 m_gstPreprocess = QGstBin::createFromPipelineDescription(preprocessOverride, nullptr,
69 /*ghostUnlinkedPads=*/true);
70 if (!m_gstPreprocess)
71 qCWarning(qLcGstVideoSink) << "Cannot create conversion element:" << preprocessOverride;
72 }
73
74 if (!m_gstPreprocess) {
75 // This is a hack for some iMX and NVidia platforms. These require the use of a special
76 // video conversion element in the pipeline before the video sink, as they unfortunately
77 // output some proprietary format from the decoder even though it's sometimes marked as
78 // a regular supported video/x-raw format.
79 static constexpr auto decodersToTest = {
80 "imxvideoconvert_g2d",
81 "nvvidconv",
82 };
83
84 for (const char *decoder : decodersToTest) {
85 factory = QGstElement::findFactory(decoder);
86 if (factory)
87 break;
88 }
89
90 if (factory) {
91 qCDebug(qLcGstVideoSink)
92 << "instantiating conversion element:"
93 << g_type_name(gst_element_factory_get_element_type(factory.get()));
94
95 m_gstPreprocess = QGstElement::createFromFactory(factory, "preprocess");
96 }
97 }
98
99 bool disablePixelAspectRatio =
100 qEnvironmentVariableIntValue("QT_GSTREAMER_DISABLE_PIXEL_ASPECT_RATIO") != 0;
101 if (disablePixelAspectRatio) {
102 // Enabling the pixel aspect ratio may expose a gstreamer bug on cameras that don't expose a
103 // pixel-aspect-ratio via `VIDIOC_CROPCAP`. This can cause the caps negotiation to fail.
104 // Using the QT_GSTREAMER_DISABLE_PIXEL_ASPECT_RATIO environment variable, one can disable
105 // pixel-aspect-ratio handling
106 //
107 // compare: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6242
108 m_gstCapsFilter =
109 QGstElement::createFromFactory("identity", "nullPixelAspectRatioCapsFilter");
110 } else {
111 m_gstCapsFilter =
112 QGstElement::createFromFactory("capsfilter", "pixelAspectRatioCapsFilter");
113 QGstCaps capsFilterCaps{
114 gst_caps_new_simple("video/x-raw", "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL),
115 QGstCaps::HasRef,
116 };
117 g_object_set(m_gstCapsFilter.element(), "caps", capsFilterCaps.caps(), NULL);
118 }
119
120 if (m_gstPreprocess) {
121 m_sinkBin.add(m_gstPreprocess, m_gstCapsFilter);
122 qLinkGstElements(m_gstPreprocess, m_gstCapsFilter);
123 m_sinkBin.addGhostPad(m_gstPreprocess, "sink");
124 } else {
125 m_sinkBin.add(m_gstCapsFilter);
126 m_sinkBin.addGhostPad(m_gstCapsFilter, "sink");
127 }
128
129 updateGstContexts();
130}
131
133{
134 Q_EMIT aboutToBeDestroyed();
135
136 unrefGstContexts();
137}
138
140{
141 if (!m_gstVideoSink) {
142 if (!m_gstQtSink)
143 createQtSink();
144
145 updateSinkElement(m_gstQtSink);
146 }
147
148 return m_sinkBin;
149}
150
152{
153 if (m_isActive == isActive)
154 return;
155 m_isActive = isActive;
156
157 if (m_gstQtSink)
158 m_gstQtSink.setActive(isActive);
159}
160
162{
163 m_sinkIsAsync = isAsync;
164}
165
166void QGstreamerRelayVideoSink::connectPluggableVideoSink(QGstreamerPluggableVideoSink *pluggableSink)
167{
168 Q_ASSERT(pluggableSink);
169 m_pluggableVideoSink = pluggableSink;
170 m_pluggableVideoSink->setVideoFrame(m_currentVideoFrame);
171 m_pluggableVideoSink->setSubtitleText(m_currentSubtitleText);
172 m_pluggableVideoSink->setNativeSize(m_currentNativeSize);
173 connect(this, &QGstreamerRelayVideoSink::videoFrameChanged,
174 m_pluggableVideoSink, &QPlatformVideoSink::setVideoFrame);
175 connect(this, &QGstreamerRelayVideoSink::subtitleTextChanged,
176 m_pluggableVideoSink, &QPlatformVideoSink::setSubtitleText);
178 m_pluggableVideoSink, &QPlatformVideoSink::setNativeSize);
179
180 // Update pipeline contexts using the rendering rhi
181
182 m_pluggableVideoSink->withRhi([&](QRhi *rhi) {
183 renderingRhiChanged(rhi);
184 });
185
186 m_rhiConnection =
187 connect(m_pluggableVideoSink, &QRhiVideoSink::rhiChanged, this, [this](QRhi *rhi) {
188 renderingRhiChanged(rhi);
189 });
190}
191
193{
194 if (m_pluggableVideoSink) {
195 disconnect(this, &QGstreamerRelayVideoSink::videoFrameChanged,
196 m_pluggableVideoSink, &QPlatformVideoSink::setVideoFrame);
197 disconnect(this, &QGstreamerRelayVideoSink::subtitleTextChanged,
198 m_pluggableVideoSink, &QPlatformVideoSink::setSubtitleText);
200 m_pluggableVideoSink, &QPlatformVideoSink::setNativeSize);
201 disconnect(m_rhiConnection);
202 m_pluggableVideoSink = nullptr;
203 }
204}
205
206void QGstreamerRelayVideoSink::setVideoFrame(const QVideoFrame &frame)
207{
208 Q_EMIT videoFrameChanged(frame);
209 m_currentVideoFrame = frame;
210}
211
212void QGstreamerRelayVideoSink::setSubtitleText(const QString &subtitleText)
213{
214 Q_EMIT subtitleTextChanged(subtitleText);
215 m_currentSubtitleText = subtitleText;
216}
217
219{
220 Q_EMIT nativeSizeChanged(size);
221 m_currentNativeSize = size;
222}
223
224void QGstreamerRelayVideoSink::createQtSink()
225{
226 Q_ASSERT(!m_gstQtSink);
227
228 m_gstQtSink = QGstVideoRendererSink::createSink(this);
229 if (!m_sinkIsAsync)
230 m_gstQtSink.set("async", false);
231 m_gstQtSink.setActive(m_isActive);
232}
233
234void QGstreamerRelayVideoSink::updateSinkElement(QGstVideoRendererSinkElement newSink)
235{
236 if (newSink == m_gstVideoSink)
237 return;
238
239 m_gstCapsFilter.src().modifyPipelineInIdleProbe([&] {
240 if (m_gstVideoSink)
241 m_sinkBin.stopAndRemoveElements(m_gstVideoSink);
242
243 m_gstVideoSink = std::move(newSink);
244 m_sinkBin.add(m_gstVideoSink);
245 qLinkGstElements(m_gstCapsFilter, m_gstVideoSink);
246 GstEvent *event = gst_event_new_reconfigure();
247 gst_element_send_event(m_gstVideoSink.element(), event);
248 m_gstVideoSink.syncStateWithParent();
249 });
250
251 m_sinkBin.dumpPipelineGraph("updateSinkElement");
252}
253
254void QGstreamerRelayVideoSink::unrefGstContexts()
255{
256 m_gstGlDisplayContext.reset();
257 m_gstGlLocalContext.reset();
258}
259
260#if QT_CONFIG(gstreamer_gl)
261
262namespace {
263
264using AnyNativeGLContextType = std::variant<
265# if QT_CONFIG(gstreamer_gl_egl) && QT_CONFIG(gstreamer_gl_x11) && QT_CONFIG(xcb_glx_plugin)
266 EGLContext, GLXContext
267# elif QT_CONFIG(gstreamer_gl_egl)
268 EGLContext
269# elif QT_CONFIG(gstreamer_gl_x11) && QT_CONFIG(xcb_glx_plugin)
270 GLXContext
271# else
272 void *
273# endif
274 >;
275
276std::optional<AnyNativeGLContextType>
277resolveNativeGlContext([[maybe_unused]] QOpenGLContext *glContext, GstGLPlatform glPlatform)
278{
279 switch (glPlatform) {
280# if QT_CONFIG(gstreamer_gl_egl)
281 case GST_GL_PLATFORM_EGL:
282 if (auto *eglContext = glContext->nativeInterface<QNativeInterface::QEGLContext>())
283 return eglContext->nativeContext();
284 return std::nullopt;
285# endif
286# if QT_CONFIG(gstreamer_gl_x11) && QT_CONFIG(xcb_glx_plugin)
287 case GST_GL_PLATFORM_GLX:
288 if (auto *glxContext = glContext->nativeInterface<QNativeInterface::QGLXContext>())
289 return glxContext->nativeContext();
290 return std::nullopt;
291# endif
292 default:
293 return std::nullopt;
294 }
295}
296
297struct ResolveGstGlDisplayResult
298{
299 QGstGLDisplayHandle display;
300 GstGLPlatform platform;
301};
302
303std::optional<ResolveGstGlDisplayResult> resolveGstGlDisplay([[maybe_unused]] QRhi &rhi)
304{
305 // use the egl display if we have one
306# if QT_CONFIG(gstreamer_gl_egl)
307 if (EGLDisplay eglDisplay = QtMultimediaPrivate::resolveEglDisplay(rhi)) {
308 QGstGLDisplayHandle gstGlDisplay{
309 GST_GL_DISPLAY_CAST(gst_gl_display_egl_new_with_egl_display(eglDisplay)),
310 QGstGLDisplayHandle::HasRef,
311 };
312 if (gstGlDisplay)
313 return ResolveGstGlDisplayResult{
314 std::move(gstGlDisplay),
315 GST_GL_PLATFORM_EGL,
316 };
317 }
318# endif
319
320# if QT_CONFIG(gstreamer_gl_wayland)
321 if (auto *waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()) {
322 QGstGLDisplayHandle gstGlDisplay{
323 GST_GL_DISPLAY_CAST(gst_gl_display_wayland_new_with_display(waylandApp->display())),
324 QGstGLDisplayHandle::HasRef,
325 };
326 if (gstGlDisplay)
327 return ResolveGstGlDisplayResult{
328 std::move(gstGlDisplay),
329 GST_GL_PLATFORM_EGL,
330 };
331 }
332# endif
333
334# if QT_CONFIG(gstreamer_gl_x11) && QT_CONFIG(xcb_glx_plugin)
335 if (auto *x11App = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()) {
336 QGstGLDisplayHandle gstGlDisplay{
337 GST_GL_DISPLAY_CAST(gst_gl_display_x11_new_with_display(x11App->display())),
338 QGstGLDisplayHandle::HasRef,
339 };
340 if (gstGlDisplay)
341 return ResolveGstGlDisplayResult{
342 std::move(gstGlDisplay),
343 GST_GL_PLATFORM_GLX,
344 };
345 }
346# endif
347
348 return std::nullopt;
349}
350
351QGstGLContextHandle resolveGstGLContextHandle(const QGstGLDisplayHandle &gstGlDisplay,
352 AnyNativeGLContextType nativeContext,
353 GstGLPlatform glPlatform)
354{
355 GstGLAPI glApi = QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL ? GST_GL_API_OPENGL
356 : GST_GL_API_GLES2;
357
358 guintptr nativeContextArgument = std::visit([](auto nativeContext) {
359 return guintptr(nativeContext);
360 }, nativeContext);
361
362 QGstGLContextHandle appContext{
363 gst_gl_context_new_wrapped(gstGlDisplay.get(), nativeContextArgument, glPlatform, glApi),
364 QGstGLContextHandle::HasRef,
365 };
366
367 return appContext;
368}
369
370} // namespace
371
372# endif // #if QT_CONFIG(gstreamer_gl)
373
374void QGstreamerRelayVideoSink::updateGstContexts()
375{
376 QRhi *currentRhi = rhi();
377
378 unrefGstContexts();
379
380#if QT_CONFIG(gstreamer_gl)
381 if (!currentRhi || currentRhi->backend() != QRhi::OpenGLES2)
382 return;
383 QOpenGLContext *glContext =
384 static_cast<const QRhiGles2NativeHandles *>(currentRhi->nativeHandles())->context;
385 Q_ASSERT(glContext);
386
387 std::optional resolvedDisplay = resolveGstGlDisplay(*currentRhi);
388 if (!resolvedDisplay) {
389 qWarning() << "Could not resolve GstGLDisplay";
390 return;
391 }
392
393 std::optional nativeContext = resolveNativeGlContext(glContext, resolvedDisplay->platform);
394 if (!nativeContext) {
395 qWarning() << "Could not find native gl context";
396 return;
397 }
398
399 QGstGLContextHandle appContext = resolveGstGLContextHandle(
400 resolvedDisplay->display, *nativeContext, resolvedDisplay->platform);
401 if (!appContext) {
402 qWarning() << "Could not create wrappped context for platform:"
403 << resolvedDisplay->platform;
404 return;
405 }
406
407 gst_gl_context_activate(appContext.get(), true);
408
409 QUniqueGErrorHandle error;
410 gst_gl_context_fill_info(appContext.get(), &error);
411 if (error) {
412 qWarning() << "Could not fill context info:" << error;
413 error = {};
414 }
415
416 QGstGLContextHandle displayContext;
417 gst_gl_display_create_context(resolvedDisplay->display.get(), appContext.get(), &displayContext,
418 &error);
419 if (error)
420 qWarning() << "Could not create display context:" << error;
421
422 appContext.reset();
423
424 m_gstGlDisplayContext = QGstContextHandle{
425 gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, false),
426 QGstContextHandle::HasRef,
427 };
428 gst_context_set_gl_display(m_gstGlDisplayContext.get(), resolvedDisplay->display.get());
429
430 m_gstGlLocalContext = QGstContextHandle{
431 gst_context_new("gst.gl.local_context", false),
432 QGstContextHandle::HasRef,
433 };
434
435 GstStructure *structure = gst_context_writable_structure(m_gstGlLocalContext.get());
436 gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, displayContext.get(), nullptr);
437 displayContext.reset();
438
439 // Note: after updating the context, we switch the sink and send gst_event_new_reconfigure()
440 // upstream. this will cause the context to be queried again.
441#endif // #if QT_CONFIG(gstreamer_gl)
442}
443
444void QGstreamerRelayVideoSink::renderingRhiChanged(QRhi *rhi)
445{
446 m_rhi = rhi;
447 updateGstContexts();
448}
449
450QT_END_NAMESPACE
451
452#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.