Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
9#include <rhi/qrhi.h>
10
11#if QT_CONFIG(gstreamer_gl)
12#include <QGuiApplication>
13#include <QtGui/qopenglcontext.h>
14#include <QWindow>
15#include <qpa/qplatformnativeinterface.h>
16#include <gst/gl/gstglconfig.h>
17
18#if GST_GL_HAVE_WINDOW_X11 && __has_include("X11/Xlib-xcb.h")
19# include <gst/gl/x11/gstgldisplay_x11.h>
20#endif
21#if GST_GL_HAVE_PLATFORM_EGL
22# include <gst/gl/egl/gstgldisplay_egl.h>
23# include <EGL/egl.h>
24# include <EGL/eglext.h>
25#endif
26#if GST_GL_HAVE_WINDOW_WAYLAND && __has_include("wayland-client.h")
27# include <gst/gl/wayland/gstgldisplay_wayland.h>
28#endif
29#endif // #if QT_CONFIG(gstreamer_gl)
30
31#include <QtCore/qdebug.h>
32
33#include <QtCore/qloggingcategory.h>
34
36
37static Q_LOGGING_CATEGORY(qLcGstVideoSink, "qt.multimedia.gstvideosink");
38
40 : QPlatformVideoSink(parent)
41{
42 sinkBin = QGstBin::create("videoSinkBin");
43
44 // This is a hack for some iMX and NVidia platforms. These require the use of a special video
45 // conversion element in the pipeline before the video sink, as they unfortunately
46 // output some proprietary format from the decoder even though it's sometimes marked as
47 // a regular supported video/x-raw format.
48 //
49 // To fix this, simply insert the element into the pipeline if it's available. Otherwise
50 // we simply use an identity element.
51 gstQueue = QGstElement::createFromFactory("queue", "videoSinkQueue");
52
54
55 // QT_MULTIMEDIA_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT allows users to override the
56 // conversion element. Ideally we construct the element programatically, though.
57 QByteArray preprocessOverride =
58 qgetenv("QT_MULTIMEDIA_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT");
59 if (!preprocessOverride.isEmpty()) {
60 qCDebug(qLcGstVideoSink) << "requesting conversion element from environment: "
61 << preprocessOverride;
63 gst_element_factory_find(preprocessOverride.constData()),
64 };
65 }
66
67 if (!factory)
69 gst_element_factory_find("imxvideoconvert_g2d"),
70 };
71
72 if (!factory)
74 gst_element_factory_find("nvvidconv"),
75 };
76
77 if (factory) {
78 qCDebug(qLcGstVideoSink) << "instantiating conversion element: "
79 << g_type_name(
80 gst_element_factory_get_element_type(factory.get()));
81
82 gstPreprocess = QGstElement::createFromFactory(factory, "preprocess");
83 }
84
85 bool disablePixelAspectRatio =
86 qEnvironmentVariableIsSet("QT_GSTREAMER_DISABLE_PIXEL_ASPECT_RATIO");
87 if (disablePixelAspectRatio) {
88 // Enabling the pixel aspect ratio may expose a gstreamer bug on cameras that don't expose a
89 // pixel-aspect-ratio via `VIDIOC_CROPCAP`. This can cause the caps negotiation to fail.
90 // Using the QT_GSTREAMER_DISABLE_PIXEL_ASPECT_RATIO environment variable, one can disable
91 // pixel-aspect-ratio handling
92 //
93 // compare: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6242
94 gstCapsFilter =
95 QGstElement::createFromFactory("identity", "nullPixelAspectRatioCapsFilter");
96 } else {
97 gstCapsFilter = QGstElement::createFromFactory("capsfilter", "pixelAspectRatioCapsFilter");
98 QGstCaps capsFilterCaps{
99 gst_caps_new_simple("video/x-raw", "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL),
101 };
102 g_object_set(gstCapsFilter.element(), "caps", capsFilterCaps.caps(), NULL);
103 }
104
105 if (gstPreprocess) {
106 sinkBin.add(gstQueue, gstPreprocess, gstCapsFilter);
107 qLinkGstElements(gstQueue, gstPreprocess, gstCapsFilter);
108 } else {
109 sinkBin.add(gstQueue, gstCapsFilter);
110 qLinkGstElements(gstQueue, gstCapsFilter);
111 }
112 sinkBin.addGhostPad(gstQueue, "sink");
113
114 gstSubtitleSink =
116}
117
119{
121
122 unrefGstContexts();
123
125}
126
128{
129 updateSinkElement();
130 return sinkBin;
131}
132
134{
135 gstPipeline = std::move(pipeline);
136}
137
139{
140 if (gstPipeline.isNull())
141 return true;
142 return gstPipeline.inStoppedState();
143}
144
146{
147 if (rhi && rhi->backend() != QRhi::OpenGLES2)
148 rhi = nullptr;
149 if (m_rhi == rhi)
150 return;
151
152 m_rhi = rhi;
153 updateGstContexts();
154 if (!gstQtSink.isNull()) {
155 // force creation of a new sink with proper caps
156 createQtSink();
157 updateSinkElement();
158 }
159}
160
161void QGstreamerVideoSink::createQtSink()
162{
163 if (gstQtSink)
164 gstQtSink.setStateSync(GST_STATE_NULL);
165
166 gstQtSink = QGstElement(reinterpret_cast<GstElement *>(QGstVideoRendererSink::createSink(this)),
168}
169
170void QGstreamerVideoSink::updateSinkElement()
171{
172 QGstElement newSink;
173 if (gstQtSink.isNull())
174 createQtSink();
175 newSink = gstQtSink;
176
177 if (newSink == gstVideoSink)
178 return;
179
180 gstPipeline.modifyPipelineWhileNotRunning([&] {
181 if (!gstVideoSink.isNull())
182 sinkBin.stopAndRemoveElements(gstVideoSink);
183
184 newSink.set("async", false); // no asynchronous state changes
185
186 gstVideoSink = newSink;
187 sinkBin.add(gstVideoSink);
188 qLinkGstElements(gstCapsFilter, gstVideoSink);
189 gstVideoSink.setState(GST_STATE_PAUSED);
190 });
191
192 gstPipeline.dumpGraph("updateVideoSink");
193}
194
195void QGstreamerVideoSink::unrefGstContexts()
196{
197 m_gstGlDisplayContext.close();
198 m_gstGlLocalContext.close();
199 m_eglDisplay = nullptr;
200 m_eglImageTargetTexture2D = nullptr;
201}
202
203void QGstreamerVideoSink::updateGstContexts()
204{
205 unrefGstContexts();
206
207#if QT_CONFIG(gstreamer_gl)
208 if (!m_rhi || m_rhi->backend() != QRhi::OpenGLES2)
209 return;
210
211 auto *nativeHandles = static_cast<const QRhiGles2NativeHandles *>(m_rhi->nativeHandles());
212 auto glContext = nativeHandles->context;
213 Q_ASSERT(glContext);
214
217 m_eglDisplay = pni->nativeResourceForIntegration("egldisplay");
218// qDebug() << "platform is" << platform << m_eglDisplay;
219
220 QGstGLDisplayHandle gstGlDisplay;
221
222 const char *contextName = "eglcontext";
223 GstGLPlatform glPlatform = GST_GL_PLATFORM_EGL;
224 // use the egl display if we have one
225 if (m_eglDisplay) {
226#if GST_GL_HAVE_PLATFORM_EGL
227 gstGlDisplay.reset(
228 GST_GL_DISPLAY_CAST(gst_gl_display_egl_new_with_egl_display(m_eglDisplay)));
229 m_eglImageTargetTexture2D = eglGetProcAddress("glEGLImageTargetTexture2DOES");
230#endif
231 } else {
232 auto display = pni->nativeResourceForIntegration("display");
233
234 if (display) {
235#if GST_GL_HAVE_WINDOW_X11 && __has_include("X11/Xlib-xcb.h")
236 if (platform == QLatin1String("xcb")) {
237 contextName = "glxcontext";
238 glPlatform = GST_GL_PLATFORM_GLX;
239
240 gstGlDisplay.reset(GST_GL_DISPLAY_CAST(
241 gst_gl_display_x11_new_with_display(reinterpret_cast<Display *>(display))));
242 }
243#endif
244#if GST_GL_HAVE_WINDOW_WAYLAND && __has_include("wayland-client.h")
245 if (platform.startsWith(QLatin1String("wayland"))) {
246 Q_ASSERT(!gstGlDisplay);
247 gstGlDisplay.reset(GST_GL_DISPLAY_CAST(gst_gl_display_wayland_new_with_display(
248 reinterpret_cast<struct wl_display *>(display))));
249 }
250#endif
251 }
252 }
253
254 if (!gstGlDisplay) {
255 qWarning() << "Could not create GstGLDisplay";
256 return;
257 }
258
259 void *nativeContext = pni->nativeResourceForContext(contextName, glContext);
260 if (!nativeContext)
261 qWarning() << "Could not find resource for" << contextName;
262
263 GstGLAPI glApi = QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL ? GST_GL_API_OPENGL : GST_GL_API_GLES2;
264 QGstGLContextHandle appContext{
265 gst_gl_context_new_wrapped(gstGlDisplay.get(), guintptr(nativeContext), glPlatform, glApi),
266 };
267 if (!appContext)
268 qWarning() << "Could not create wrappped context for platform:" << glPlatform;
269
270 gst_gl_context_activate(appContext.get(), true);
271
273 gst_gl_context_fill_info(appContext.get(), &error);
274 if (error) {
275 qWarning() << "Could not fill context info:" << error;
276 error = {};
277 }
278
279 QGstGLContextHandle displayContext;
280 gst_gl_display_create_context(gstGlDisplay.get(), appContext.get(), &displayContext, &error);
281 if (error)
282 qWarning() << "Could not create display context:" << error;
283
284 appContext.close();
285
286 m_gstGlDisplayContext.reset(gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, false));
287 gst_context_set_gl_display(m_gstGlDisplayContext.get(), gstGlDisplay.get());
288
289 m_gstGlLocalContext.reset(gst_context_new("gst.gl.local_context", false));
290 GstStructure *structure = gst_context_writable_structure(m_gstGlLocalContext.get());
291 gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, displayContext.get(), nullptr);
292 displayContext.close();
293
294 if (!gstPipeline.isNull())
295 gst_element_set_context(gstPipeline.element(), m_gstGlLocalContext.get());
296#endif // #if QT_CONFIG(gstreamer_gl)
297}
298
300
301#include "moc_qgstreamervideosink_p.cpp"
\inmodule QtCore
Definition qbytearray.h:57
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void add)(const Ts &...ts)
Definition qgst_p.h:691
static QGstBin create(const char *name)
Definition qgst.cpp:1060
void addGhostPad(const QGstElement &child, const char *name)
Definition qgst.cpp:1118
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void stopAndRemoveElements)(Ts... ts)
Definition qgst_p.h:710
GstStateChangeReturn setState(GstState state)
Definition qgst.cpp:942
GstElement * element() const
Definition qgst.cpp:1018
bool setStateSync(GstState state, std::chrono::nanoseconds timeout=std::chrono::seconds(1))
Definition qgst.cpp:947
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
Definition qgst.cpp:835
bool inStoppedState() const
void dumpGraph(const char *fileName)
void modifyPipelineWhileNotRunning(Functor &&fn)
static QGstSubtitleSink * createSink(QGstreamerVideoSink *sink)
static QGstVideoRendererSink * createSink(QGstreamerVideoSink *surface)
QGstreamerVideoSink(QVideoSink *parent=nullptr)
void setPipeline(QGstPipeline pipeline)
void setRhi(QRhi *rhi) override
static QPlatformNativeInterface * platformNativeInterface()
QString platformName
The name of the underlying platform plugin.
static OpenGLModuleType openGLModuleType()
Returns the underlying OpenGL implementation type.
The QPlatformNativeInterface class provides an abstraction for retrieving native resource handles.
\variable QRhiGles2InitParams::format
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1804
Implementation backend() const
Definition qrhi.cpp:8651
@ OpenGLES2
Definition qrhi.h:1809
const QRhiNativeHandles * nativeHandles()
Definition qrhi.cpp:10137
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
The QVideoSink class represents a generic sink for video data.
Definition qvideosink.h:22
struct wl_display * display
Definition linuxdmabuf.h:41
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void qLinkGstElements)(const Ts &...ts)
Definition qgst_p.h:644
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
#define emit
struct _XDisplay Display
QT_BEGIN_NAMESPACE Platform platform()
QItemEditorFactory * factory