Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include <common/qgstreamervideosink_p.h>
++ #include <common/qgstvideorenderersink_p.h>
++ #include <common/qgst_debug_p.h>
++ #include <common/qgstutils_p.h>
++ #include <rhi/qrhi.h>
++
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qloggingcategory.h>
++
++ #include <optional>
++ #include <variant>
++
++ #if QT_CONFIG(gstreamer_gl)
++ # include <QtGui/qguiapplication.h>
++ # include <QtGui/qguiapplication_platform.h>
++ # include <QtGui/qopenglcontext.h>
++ # include <QtGui/qwindow.h>
++ # include <gst/gl/gstglconfig.h>
++ # include <gst/gl/gstgldisplay.h>
++
++ # if QT_CONFIG(gstreamer_gl_x11)
++ # include <gst/gl/x11/gstgldisplay_x11.h>
++ # endif
++ # if QT_CONFIG(gstreamer_gl_egl)
++ # include <common/qgstreameregldisplay_p.h>
++ # include <QtMultimedia/private/qeglimagefunctions_p.h>
++ # include <QtMultimedia/private/qmultimedia_gl_support_p.h>
++ # include <gst/gl/egl/gstgldisplay_egl.h>
++ # include <EGL/egl.h>
++ # include <EGL/eglext.h>
++ # endif
++ # if QT_CONFIG(gstreamer_gl_wayland)
++ # include <gst/gl/wayland/gstgldisplay_wayland.h>
++ # endif
++ #endif // #if QT_CONFIG(gstreamer_gl)
++
++ QT_BEGIN_NAMESPACE
++
++ Q_STATIC_LOGGING_CATEGORY(qLcGstVideoSink, "qt.multimedia.gstvideosink");
++
++ QGstreamerPluggableVideoSink::QGstreamerPluggableVideoSink(QVideoSink *parent)
++ : QPlatformVideoSink{ parent }
++ {
++ }
++
++ void QGstreamerPluggableVideoSink::setRhi(QRhi *rhi)
++ {
++ if (m_rhi == rhi)
++ return;
++
++ m_rhi = rhi;
++ Q_EMIT rhiChanged();
++ }
++
++ QRhi* QGstreamerPluggableVideoSink::rhi() const
++ {
++ return m_rhi;
++ }
++
++ QGstreamerRelayVideoSink::QGstreamerRelayVideoSink(QObject *parent)
++ : QObject{
++ parent,
++ },
++ m_sinkBin{
++ QGstBin::create("videoSinkBin"),
++ }
++ {
++ // This is a hack for some iMX and NVidia platforms. These require the use of a special video
++ // conversion element in the pipeline before the video sink, as they unfortunately
++ // output some proprietary format from the decoder even though it's sometimes marked as
++ // a regular supported video/x-raw format.
++ //
++ // To fix this, simply insert the element into the pipeline if it's available. Otherwise
++ // we simply use an identity element.
++ QGstElementFactoryHandle factory;
++
++ // QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT allows users to override the
++ // conversion element. Ideally we construct the element programatically, though.
++ QByteArray preprocessOverride = qgetenv("QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT");
++ if (!preprocessOverride.isEmpty()) {
++ qCDebug(qLcGstVideoSink) << "requesting conversion element from environment:"
++ << preprocessOverride;
++
++ m_gstPreprocess = QGstBin::createFromPipelineDescription(preprocessOverride, nullptr,
++ /*ghostUnlinkedPads=*/true);
++ if (!m_gstPreprocess)
++ qCWarning(qLcGstVideoSink) << "Cannot create conversion element:" << preprocessOverride;
++ }
++
++ if (!m_gstPreprocess) {
++ // This is a hack for some iMX and NVidia platforms. These require the use of a special
++ // video conversion element in the pipeline before the video sink, as they unfortunately
++ // output some proprietary format from the decoder even though it's sometimes marked as
++ // a regular supported video/x-raw format.
++ static constexpr auto decodersToTest = {
++ "imxvideoconvert_g2d",
++ "nvvidconv",
++ };
++
++ for (const char *decoder : decodersToTest) {
++ factory = QGstElement::findFactory(decoder);
++ if (factory)
++ break;
++ }
++
++ if (factory) {
++ qCDebug(qLcGstVideoSink)
++ << "instantiating conversion element:"
++ << g_type_name(gst_element_factory_get_element_type(factory.get()));
++
++ m_gstPreprocess = QGstElement::createFromFactory(factory, "preprocess");
++ }
++ }
++
++ bool disablePixelAspectRatio =
++ qEnvironmentVariableIntValue("QT_GSTREAMER_DISABLE_PIXEL_ASPECT_RATIO") != 0;
++ if (disablePixelAspectRatio) {
++ // Enabling the pixel aspect ratio may expose a gstreamer bug on cameras that don't expose a
++ // pixel-aspect-ratio via `VIDIOC_CROPCAP`. This can cause the caps negotiation to fail.
++ // Using the QT_GSTREAMER_DISABLE_PIXEL_ASPECT_RATIO environment variable, one can disable
++ // pixel-aspect-ratio handling
++ //
++ m_gstCapsFilter =
++ QGstElement::createFromFactory("identity", "nullPixelAspectRatioCapsFilter");
++ } else {
++ m_gstCapsFilter =
++ QGstElement::createFromFactory("capsfilter", "pixelAspectRatioCapsFilter");
++ QGstCaps capsFilterCaps{
++ gst_caps_new_simple("video/x-raw", "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL),
++ QGstCaps::HasRef,
++ };
++ g_object_set(m_gstCapsFilter.element(), "caps", capsFilterCaps.caps(), NULL);
++ }
++
++ if (m_gstPreprocess) {
++ m_sinkBin.add(m_gstPreprocess, m_gstCapsFilter);
++ qLinkGstElements(m_gstPreprocess, m_gstCapsFilter);
++ m_sinkBin.addGhostPad(m_gstPreprocess, "sink");
++ } else {
++ m_sinkBin.add(m_gstCapsFilter);
++ m_sinkBin.addGhostPad(m_gstCapsFilter, "sink");
++ }
++
++ updateGstContexts();
++ }
++
++ QGstreamerRelayVideoSink::~QGstreamerRelayVideoSink()
++ {
++ Q_EMIT aboutToBeDestroyed();
++
++ unrefGstContexts();
++ }
++
++ QGstElement QGstreamerRelayVideoSink::gstSink()
++ {
++ if (!m_gstVideoSink) {
++ if (!m_gstQtSink)
++ createQtSink();
++
++ updateSinkElement(m_gstQtSink);
++ }
++
++ return m_sinkBin;
++ }
++
++ void QGstreamerRelayVideoSink::setActive(bool isActive)
++ {
++ if (m_isActive == isActive)
++ return;
++ m_isActive = isActive;
++
++ if (m_gstQtSink)
++ m_gstQtSink.setActive(isActive);
++ }
++
++ void QGstreamerRelayVideoSink::setAsync(bool isAsync)
++ {
++ m_sinkIsAsync = isAsync;
++ }
++
++ void QGstreamerRelayVideoSink::connectPluggableVideoSink(QGstreamerPluggableVideoSink *pluggableSink)
++ {
++ Q_ASSERT(pluggableSink);
++ m_pluggableVideoSink = pluggableSink;
++ m_pluggableVideoSink->setVideoFrame(m_currentVideoFrame);
++ m_pluggableVideoSink->setSubtitleText(m_currentSubtitleText);
++ m_pluggableVideoSink->setNativeSize(m_currentNativeSize);
++ connect(this, &QGstreamerRelayVideoSink::videoFrameChanged,
++ m_pluggableVideoSink, &QPlatformVideoSink::setVideoFrame);
++ connect(this, &QGstreamerRelayVideoSink::subtitleTextChanged,
++ m_pluggableVideoSink, &QPlatformVideoSink::setSubtitleText);
++ connect(this, &QGstreamerRelayVideoSink::nativeSizeChanged,
++ m_pluggableVideoSink, &QPlatformVideoSink::setNativeSize);
++
++ // Update pipeline contexts using the rendering rhi
++ renderingRhiChanged(m_pluggableVideoSink->rhi());
++ m_rhiConnection = connect(m_pluggableVideoSink, &QPlatformVideoSink::rhiChanged, this, [this](){
++ renderingRhiChanged(m_pluggableVideoSink->rhi());
++ });
++ }
++
++ void QGstreamerRelayVideoSink::disconnectPluggableVideoSink()
++ {
++ if (m_pluggableVideoSink) {
++ disconnect(this, &QGstreamerRelayVideoSink::videoFrameChanged,
++ m_pluggableVideoSink, &QPlatformVideoSink::setVideoFrame);
++ disconnect(this, &QGstreamerRelayVideoSink::subtitleTextChanged,
++ m_pluggableVideoSink, &QPlatformVideoSink::setSubtitleText);
++ disconnect(this, &QGstreamerRelayVideoSink::nativeSizeChanged,
++ m_pluggableVideoSink, &QPlatformVideoSink::setNativeSize);
++ disconnect(m_rhiConnection);
++ m_pluggableVideoSink = nullptr;
++ }
++ }
++
++ void QGstreamerRelayVideoSink::setVideoFrame(const QVideoFrame &frame)
++ {
++ Q_EMIT videoFrameChanged(frame);
++ m_currentVideoFrame = frame;
++ }
++
++ void QGstreamerRelayVideoSink::setSubtitleText(const QString &subtitleText)
++ {
++ Q_EMIT subtitleTextChanged(subtitleText);
++ m_currentSubtitleText = subtitleText;
++ }
++
++ void QGstreamerRelayVideoSink::setNativeSize(QSize size)
++ {
++ Q_EMIT nativeSizeChanged(size);
++ m_currentNativeSize = size;
++ }
++
++ void QGstreamerRelayVideoSink::createQtSink()
++ {
++ Q_ASSERT(!m_gstQtSink);
++
++ m_gstQtSink = QGstVideoRendererSink::createSink(this);
++ if (!m_sinkIsAsync)
++ m_gstQtSink.set("async", false);
++ m_gstQtSink.setActive(m_isActive);
++ }
++
++ void QGstreamerRelayVideoSink::updateSinkElement(QGstVideoRendererSinkElement newSink)
++ {
++ if (newSink == m_gstVideoSink)
++ return;
++
++ m_gstCapsFilter.src().modifyPipelineInIdleProbe([&] {
++ if (m_gstVideoSink)
++ m_sinkBin.stopAndRemoveElements(m_gstVideoSink);
++
++ m_gstVideoSink = std::move(newSink);
++ m_sinkBin.add(m_gstVideoSink);
++ qLinkGstElements(m_gstCapsFilter, m_gstVideoSink);
++ GstEvent *event = gst_event_new_reconfigure();
++ gst_element_send_event(m_gstVideoSink.element(), event);
++ m_gstVideoSink.syncStateWithParent();
++ });
++
++ m_sinkBin.dumpPipelineGraph("updateSinkElement");
++ }
++
++ void QGstreamerRelayVideoSink::unrefGstContexts()
++ {
++ m_gstGlDisplayContext.reset();
++ m_gstGlLocalContext.reset();
++ }
++
++ #if QT_CONFIG(gstreamer_gl)
++
++ namespace {
++
++ using AnyNativeGLContextType = std::variant<
++ # if QT_CONFIG(gstreamer_gl_egl) && QT_CONFIG(gstreamer_gl_x11) && QT_CONFIG(xcb_glx_plugin)
++ EGLContext, GLXContext
++ # elif QT_CONFIG(gstreamer_gl_egl)
++ EGLContext
++ # elif QT_CONFIG(gstreamer_gl_x11) && QT_CONFIG(xcb_glx_plugin)
++ GLXContext
++ # else
++ void *
++ # endif
++ >;
++
++ std::optional<AnyNativeGLContextType>
++ resolveNativeGlContext([[maybe_unused]] QOpenGLContext *glContext, GstGLPlatform glPlatform)
++ {
++ switch (glPlatform) {
++ # if QT_CONFIG(gstreamer_gl_egl)
++ case GST_GL_PLATFORM_EGL:
++ if (auto *eglContext = glContext->nativeInterface<QNativeInterface::QEGLContext>())
++ return eglContext->nativeContext();
++ return std::nullopt;
++ # endif
++ # if QT_CONFIG(gstreamer_gl_x11) && QT_CONFIG(xcb_glx_plugin)
++ case GST_GL_PLATFORM_GLX:
++ if (auto *glxContext = glContext->nativeInterface<QNativeInterface::QGLXContext>())
++ return glxContext->nativeContext();
++ return std::nullopt;
++ # endif
++ default:
++ return std::nullopt;
++ }
++ }
++
++ struct ResolveGstGlDisplayResult
++ {
++ QGstGLDisplayHandle display;
++ GstGLPlatform platform;
++ };
++
++ std::optional<ResolveGstGlDisplayResult> resolveGstGlDisplay([[maybe_unused]] QRhi &rhi)
++ {
++ // use the egl display if we have one
++ # if QT_CONFIG(gstreamer_gl_egl)
++ if (EGLDisplay eglDisplay = QtMultimediaPrivate::resolveEglDisplay(rhi)) {
++ QGstGLDisplayHandle gstGlDisplay{
++ GST_GL_DISPLAY_CAST(gst_gl_display_egl_new_with_egl_display(eglDisplay)),
++ QGstGLDisplayHandle::HasRef,
++ };
++ if (gstGlDisplay)
++ return ResolveGstGlDisplayResult{
++ std::move(gstGlDisplay),
++ GST_GL_PLATFORM_EGL,
++ };
++ }
++ # endif
++
++ # if QT_CONFIG(gstreamer_gl_wayland)
++ if (auto *waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()) {
++ QGstGLDisplayHandle gstGlDisplay{
++ GST_GL_DISPLAY_CAST(gst_gl_display_wayland_new_with_display(waylandApp->display())),
++ QGstGLDisplayHandle::HasRef,
++ };
++ if (gstGlDisplay)
++ return ResolveGstGlDisplayResult{
++ std::move(gstGlDisplay),
++ GST_GL_PLATFORM_EGL,
++ };
++ }
++ # endif
++
++ # if QT_CONFIG(gstreamer_gl_x11) && QT_CONFIG(xcb_glx_plugin)
++ if (auto *x11App = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()) {
++ QGstGLDisplayHandle gstGlDisplay{
++ GST_GL_DISPLAY_CAST(gst_gl_display_x11_new_with_display(x11App->display())),
++ QGstGLDisplayHandle::HasRef,
++ };
++ if (gstGlDisplay)
++ return ResolveGstGlDisplayResult{
++ std::move(gstGlDisplay),
++ GST_GL_PLATFORM_GLX,
++ };
++ }
++ # endif
++
++ return std::nullopt;
++ }
++
++ QGstGLContextHandle resolveGstGLContextHandle(const QGstGLDisplayHandle &gstGlDisplay,
++ AnyNativeGLContextType nativeContext,
++ GstGLPlatform glPlatform)
++ {
++ GstGLAPI glApi = QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL ? GST_GL_API_OPENGL
++ : GST_GL_API_GLES2;
++
++ guintptr nativeContextArgument = std::visit([](auto nativeContext) {
++ return guintptr(nativeContext);
++ }, nativeContext);
++
++ QGstGLContextHandle appContext{
++ gst_gl_context_new_wrapped(gstGlDisplay.get(), nativeContextArgument, glPlatform, glApi),
++ QGstGLContextHandle::HasRef,
++ };
++
++ return appContext;
++ }
++
++ } // namespace
++
++ # endif // #if QT_CONFIG(gstreamer_gl)
++
++ void QGstreamerRelayVideoSink::updateGstContexts()
++ {
++ QRhi *currentRhi = rhi();
++
++ unrefGstContexts();
++
++ #if QT_CONFIG(gstreamer_gl)
++ if (!currentRhi || currentRhi->backend() != QRhi::OpenGLES2)
++ return;
++ QOpenGLContext *glContext =
++ static_cast<const QRhiGles2NativeHandles *>(currentRhi->nativeHandles())->context;
++ Q_ASSERT(glContext);
++
++ std::optional resolvedDisplay = resolveGstGlDisplay(*currentRhi);
++ if (!resolvedDisplay) {
++ qWarning() << "Could not resolve GstGLDisplay";
++ return;
++ }
++
++ std::optional nativeContext = resolveNativeGlContext(glContext, resolvedDisplay->platform);
++ if (!nativeContext) {
++ qWarning() << "Could not find native gl context";
++ return;
++ }
++
++ QGstGLContextHandle appContext = resolveGstGLContextHandle(
++ resolvedDisplay->display, *nativeContext, resolvedDisplay->platform);
++ if (!appContext) {
++ qWarning() << "Could not create wrappped context for platform:"
++ << resolvedDisplay->platform;
++ return;
++ }
++
++ gst_gl_context_activate(appContext.get(), true);
++
++ QUniqueGErrorHandle error;
++ gst_gl_context_fill_info(appContext.get(), &error);
++ if (error) {
++ qWarning() << "Could not fill context info:" << error;
++ error = {};
++ }
++
++ QGstGLContextHandle displayContext;
++ gst_gl_display_create_context(resolvedDisplay->display.get(), appContext.get(), &displayContext,
++ &error);
++ if (error)
++ qWarning() << "Could not create display context:" << error;
++
++ appContext.reset();
++
++ m_gstGlDisplayContext = QGstContextHandle{
++ gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, false),
++ QGstContextHandle::HasRef,
++ };
++ gst_context_set_gl_display(m_gstGlDisplayContext.get(), resolvedDisplay->display.get());
++
++ m_gstGlLocalContext = QGstContextHandle{
++ gst_context_new("gst.gl.local_context", false),
++ QGstContextHandle::HasRef,
++ };
++
++ GstStructure *structure = gst_context_writable_structure(m_gstGlLocalContext.get());
++ gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, displayContext.get(), nullptr);
++ displayContext.reset();
++
++ // Note: after updating the context, we switch the sink and send gst_event_new_reconfigure()
++ // upstream. this will cause the context to be queried again.
++ #endif // #if QT_CONFIG(gstreamer_gl)
++ }
++
++ void QGstreamerRelayVideoSink::renderingRhiChanged(QRhi *rhi)
++ {
++ m_rhi = rhi;
++ updateGstContexts();
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qgstreamervideosink_p.cpp"