Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/FlyView/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ qt_add_qml_module(FlyViewModule
FlightDisplayViewDummy.qml
FlightDisplayViewGStreamer.qml
FlightDisplayViewGStreamerD3D11.qml
FlightDisplayViewMetal.qml
FlightDisplayViewQtMultimedia.qml
FlightDisplayViewUVC.qml
FlightDisplayViewVideo.qml
Expand Down
22 changes: 22 additions & 0 deletions src/FlyView/FlightDisplayViewMetal.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import QtQuick
import QtMultimedia

import QGroundControl

// macOS Metal rendering path for GStreamer video.
// Frames are pushed from the C++ GstAppSinkAdapter to this VideoOutput's QVideoSink.
VideoOutput {
objectName: "videoContent"
fillMode: VideoOutput.PreserveAspectFit

Connections {
target: QGroundControl.videoManager
function onImageFileChanged(filename) {
grabToImage(function(result) {
if (!result.saveToFile(filename)) {
console.error('Error capturing video frame');
}
});
}
}
}
18 changes: 16 additions & 2 deletions src/FlyView/FlightDisplayViewVideo.qml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ Item {
}
}
}
Component {
id: videoBackgroundMetalComponent
FlightDisplayViewMetal {
}
}
Loader {
// GStreamer is causing crashes on Lenovo laptop OpenGL Intel drivers. In order to workaround this
// we don't load a QGCVideoBackground object when video is disabled. This prevents any video rendering
Expand All @@ -146,7 +151,9 @@ Item {
visible: _showStreamLoader
sourceComponent: QGroundControl.videoManager.gstreamerD3D11Sink
? videoBackgroundD3D11Component
: videoBackgroundGLComponent
: QGroundControl.videoManager.gstreamerAppleSink
? videoBackgroundMetalComponent
: videoBackgroundGLComponent
Comment on lines 152 to +156
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component selection is driven by videoManager.gstreamerAppleSink, which is currently compile-time and always true on macOS. If the GStreamer sinkbin can't initialize the appsink path (or falls back to GL), this will still load FlightDisplayViewMetal (VideoOutput) instead of the GL-backed QGCVideoBackground, and video rendering will break. Consider selecting based on a runtime backend indicator (or ensure the appsink path cannot fail without disabling streaming).

Copilot uses AI. Check for mistakes.

property bool videoDisabled: QGroundControl.settingsManager.videoSettings.videoSource.rawValue === QGroundControl.settingsManager.videoSettings.disabledVideoSource
}
Expand Down Expand Up @@ -233,7 +240,10 @@ Item {
anchors.fill: parent
opacity: _camera ? (_camera.thermalMode === MavlinkCameraControlInterface.THERMAL_BLEND ? _camera.thermalOpacity / 100 : 1.0) : 0
sourceComponent: QGroundControl.videoManager.gstreamerD3D11Sink
? thermalBackgroundD3D11 : thermalBackgroundGL
? thermalBackgroundD3D11
: QGroundControl.videoManager.gstreamerAppleSink
? thermalBackgroundMetal
: thermalBackgroundGL
onLoaded: { if (item) item.objectName = "thermalVideo" }

Component {
Expand All @@ -244,6 +254,10 @@ Item {
id: thermalBackgroundD3D11
QGCVideoBackgroundD3D11 {}
}
Component {
id: thermalBackgroundMetal
FlightDisplayViewMetal {}
}
}
}
//-- Zoom
Expand Down
29 changes: 16 additions & 13 deletions src/QGCApplication.cc
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,11 @@ void QGCApplication::init()
bool QGCApplication::_initVideo()
{
#ifdef QGC_GST_STREAMING
// GStreamer video playback requires OpenGL. On platforms where OpenGL
// is unavailable (e.g. recent macOS with only Metal), fall back to the
// default graphics API — video streaming won't work but the rest of
// QGC remains functional.
// GStreamer video rendering backend selection:
// - Windows D3D11: native RHI, no OpenGL needed.
// - macOS: appsink → QVideoSink → Metal RHI VideoOutput, no OpenGL needed.
// - Linux/other: qml6glsink requires OpenGL. Probe for a working GL context
// and fall back to the default graphics API if unavailable.
//
// The offscreen platform (used in CI boot tests) never provides a real
// GL context, so skip the probe there — just set OpenGL API to exercise
Expand All @@ -255,15 +256,17 @@ bool QGCApplication::_initVideo()
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
}
qCDebug(QGCApplicationLog) << "D3D11 video sink available, using default graphics API";
#elif defined(Q_OS_MACOS)
// macOS Metal rendering path: appsink → QVideoSink → VideoOutput.
// Do NOT force OpenGL — let Qt use the default Metal RHI backend.
// The appsink path in qgcvideosinkbin avoids the GL-dependent qml6glsink.
if (isOffscreen) {
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
} else {
qCDebug(QGCApplicationLog) << "macOS: using default RHI backend (Metal) for appsink video path";
}
Comment on lines +260 to +267
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The surrounding comment block still states that "GStreamer video playback requires OpenGL". With the new macOS appsink → QVideoSink path, that’s no longer true on macOS (and is now explicitly avoided here). Consider updating/re-scoping the comment so it doesn’t mislead future maintainers (e.g. clarify it applies to the GL sink path only).

Copilot uses AI. Check for mistakes.
#else
const bool skipGLProbe = isOffscreen
#if defined(Q_OS_MACOS)
// macOS still provides OpenGL (deprecated but functional). The
// QOpenGLContext::create() probe is unreliable without a native
// surface, so skip it and force OpenGL for qml6glsink.
|| true
#endif
;
const bool skipGLProbe = isOffscreen;

if (skipGLProbe) {
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
Expand All @@ -276,7 +279,7 @@ bool QGCApplication::_initVideo()
<< "Using default graphics API (Metal/Vulkan).";
}
}
#endif // QGC_GST_D3D11_SINK
#endif // QGC_GST_D3D11_SINK / Q_OS_MACOS
#endif

QGCCorePlugin::instance(); // CorePlugin must be initialized before VideoManager for Video Cleanup
Expand Down
30 changes: 30 additions & 0 deletions src/VideoManager/VideoManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
#include "UVCReceiver.h"
#ifdef QGC_GST_STREAMING
#include "GStreamerHelpers.h"
#include "GStreamer.h"
#endif
#if defined(QGC_GST_STREAMING) && defined(Q_OS_MACOS)
#include <QtMultimedia/QVideoSink>
#include <QtMultimediaQuick/private/qquickvideooutput_p.h>
#endif

#include <QtConcurrent/QtConcurrent>
Expand Down Expand Up @@ -501,6 +506,15 @@ bool VideoManager::gstreamerD3D11Sink()
#endif
}

bool VideoManager::gstreamerAppleSink()
{
#if defined(QGC_GST_STREAMING) && defined(Q_OS_MACOS) && !defined(QGC_GST_D3D11_SINK)
return true;
#else
return false;
#endif
}
Comment on lines +509 to +516
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gstreamerAppleSink() is a compile-time constant that always returns true on macOS when GStreamer streaming is enabled. That means QML will always load the Metal VideoOutput component, even if qgcvideosinkbin ends up using (or falling back to) the GL sink path at runtime. Since the GL path expects a GstGLQt6VideoItem-based widget, this mismatch would prevent video rendering. Consider exposing the actual sinkbin backend selection (appsink vs GL) as a runtime property and drive QML selection from that, or remove the GL fallback if it can't be supported from the current UI wiring.

Copilot uses AI. Check for mistakes.

bool VideoManager::uvcEnabled()
{
return UVCReceiver::enabled();
Expand Down Expand Up @@ -888,6 +902,22 @@ void VideoManager::_initVideoReceiver(VideoReceiver *receiver, QQuickWindow *win
}
receiver->setSink(sink);

#if defined(QGC_GST_STREAMING) && defined(Q_OS_MACOS) && !defined(QGC_GST_D3D11_SINK)
// macOS Metal path: connect appsink inside the sinkbin to the QVideoSink
// belonging to the QML VideoOutput widget.
if (sink && widget) {
auto *videoOutput = qobject_cast<QQuickVideoOutput *>(widget);
if (videoOutput) {
QVideoSink *videoSink = videoOutput->videoSink();
if (!GStreamer::setupAppleSinkAdapter(sink, videoSink, receiver)) {
qCWarning(VideoManagerLog) << "setupAppleSinkAdapter failed" << receiver->name();
}
} else {
qCWarning(VideoManagerLog) << "Widget is not a VideoOutput, cannot connect appsink" << receiver->name();
}
}
#endif

(void) connect(receiver, &VideoReceiver::onStartComplete, this, [this, receiver](VideoReceiver::STATUS status) {
qCDebug(VideoManagerLog) << "Video" << receiver->name() << "Start complete, status:" << status;
switch (status) {
Expand Down
2 changes: 2 additions & 0 deletions src/VideoManager/VideoManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class VideoManager : public QObject

Q_PROPERTY(bool gstreamerEnabled READ gstreamerEnabled CONSTANT)
Q_PROPERTY(bool gstreamerD3D11Sink READ gstreamerD3D11Sink CONSTANT)
Q_PROPERTY(bool gstreamerAppleSink READ gstreamerAppleSink CONSTANT)
Q_PROPERTY(bool qtmultimediaEnabled READ qtmultimediaEnabled CONSTANT)
Q_PROPERTY(bool uvcEnabled READ uvcEnabled CONSTANT)
Q_PROPERTY(bool autoStreamConfigured READ autoStreamConfigured NOTIFY autoStreamConfiguredChanged)
Expand Down Expand Up @@ -83,6 +84,7 @@ class VideoManager : public QObject
void setfullScreen(bool on);
static bool gstreamerEnabled();
static bool gstreamerD3D11Sink();
static bool gstreamerAppleSink();
static bool qtmultimediaEnabled();
static bool uvcEnabled();

Expand Down
15 changes: 14 additions & 1 deletion src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE VideoItemStub.h)
if(QGC_ENABLE_GST_VIDEOSTREAMING)
find_package(QGCGStreamer
REQUIRED
COMPONENTS Core Base Video Gl GlPrototypes Rtsp
COMPONENTS Core Base Video Gl GlPrototypes Rtsp App
OPTIONAL_COMPONENTS GlEgl GlWayland GlX11
Comment on lines 5 to 8
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_package(QGCGStreamer ... COMPONENTS ... App) makes the App (gstapp) API a hard requirement on all platforms, but the new appsink adapter sources are APPLE-only. Consider making App conditional on APPLE (or an OPTIONAL_COMPONENT) so non-macOS builds don’t gain an unnecessary dependency.

Copilot uses AI. Check for mistakes.
)

Comment on lines +7 to 10
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_package(QGCGStreamer ... COMPONENTS ... App) makes the App (gst-app) component a required dependency for all platforms whenever GStreamer streaming is enabled, even though appsink usage is Apple-only in this PR. This can break Linux/Windows builds that previously satisfied the dependency set but don't have the App dev package. Consider making App required only under if(APPLE) (or moving it to OPTIONAL_COMPONENTS and validating it only on Apple).

Suggested change
COMPONENTS Core Base Video Gl GlPrototypes Rtsp App
OPTIONAL_COMPONENTS GlEgl GlWayland GlX11
)
COMPONENTS Core Base Video Gl GlPrototypes Rtsp
OPTIONAL_COMPONENTS App GlEgl GlWayland GlX11
)
if(APPLE AND NOT QGCGStreamer_App_FOUND)
message(FATAL_ERROR "QGCGStreamer App component (gst-app) is required on Apple platforms")
endif()

Copilot uses AI. Check for mistakes.
Expand All @@ -20,6 +20,19 @@ if(QGC_ENABLE_GST_VIDEOSTREAMING)
GstVideoReceiver.h
)

if(APPLE)
target_sources(${CMAKE_PROJECT_NAME}
PRIVATE
GstAppSinkAdapter.cc
GstAppSinkAdapter.h
)
target_link_libraries(${CMAKE_PROJECT_NAME}
PRIVATE
Qt6::Multimedia
Qt6::MultimediaQuickPrivate
)
endif()

add_subdirectory(gstqgc)
endif()

Expand Down
24 changes: 24 additions & 0 deletions src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
#include "AppSettings.h"
#include "GstVideoReceiver.h"

#ifdef Q_OS_MACOS
#include "GstAppSinkAdapter.h"
#endif

#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
Expand Down Expand Up @@ -870,4 +874,24 @@ VideoReceiver *createVideoReceiver(QObject *parent)
return new GstVideoReceiver(parent);
}

bool setupAppleSinkAdapter(void *sinkBin, QVideoSink *videoSink, QObject *adapterParent)
{
#ifdef Q_OS_MACOS
if (!sinkBin || !videoSink) {
return false;
}

auto *adapter = new GstAppSinkAdapter(adapterParent);
if (!adapter->setup(GST_ELEMENT(sinkBin), videoSink)) {
qCCritical(GStreamerLog) << "GstAppSinkAdapter::setup() failed";
adapter->deleteLater();
return false;
}
return true;
#else
Q_UNUSED(sinkBin); Q_UNUSED(videoSink); Q_UNUSED(adapterParent);
return false;
#endif
}

} // namespace GStreamer
5 changes: 5 additions & 0 deletions src/VideoManager/VideoReceiver/GStreamer/GStreamer.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Q_DECLARE_LOGGING_CATEGORY(GStreamerAPILog)
Q_DECLARE_LOGGING_CATEGORY(GStreamerDecoderRanksLog)

class QQuickItem;
class QVideoSink;
class VideoReceiver;

namespace GStreamer
Expand All @@ -31,4 +32,8 @@ void *createVideoSink(QQuickItem *widget, QObject *parent = nullptr);
void releaseVideoSink(void *sink);
VideoReceiver *createVideoReceiver(QObject *parent = nullptr);

/// On macOS: connect the appsink inside the sinkbin to a QVideoSink.
/// Returns true on success. No-op (returns false) on other platforms.
bool setupAppleSinkAdapter(void *sinkBin, QVideoSink *videoSink, QObject *adapterParent);

}
Loading
Loading