Skip to content

Video: GStreamer source-factory extraction, thread-safety, iOS xcframework-only#14350

Open
HTRamsey wants to merge 1 commit into
mavlink:masterfrom
HTRamsey:video/gst-source-factory
Open

Video: GStreamer source-factory extraction, thread-safety, iOS xcframework-only#14350
HTRamsey wants to merge 1 commit into
mavlink:masterfrom
HTRamsey:video/gst-source-factory

Conversation

@HTRamsey
Copy link
Copy Markdown
Collaborator

@HTRamsey HTRamsey commented May 9, 2026

Summary

Single-commit PR consolidating GStreamer-side cleanup on video/gst-source-factory.

Code (src/VideoManager/VideoReceiver/GStreamer/)

  • Extract GstSourceFactory from GstVideoReceiver — RTSP/UDP/TCP/file source construction now lives in its own translation unit (GstSourceFactory.{h,cc}).
  • Pipeline thread-safety: add _pipelineMutex + _acquirePipelineRef() so bus sync-message handlers and worker-thread callbacks can't race against stop().
  • Bounded EOS wait: gst_bus_timed_pop_filtered now caps at 3 s instead of GST_CLOCK_TIME_NONE.
  • QRhi cross-thread fix: populate QGCRhiCapture::DeviceSnapshot (atomic backend / device pointers / adapter LUID) on the render thread; D3D11/D3D12 context bridges read the snapshot instead of dereferencing QRhi* from the GStreamer streaming thread.
  • GstContextBridgeRegistry: handle-based registration with pointer-dedup and freed-slot reuse; supports unregister*.
  • DMA_DRM caps: gstqgcvideosinkbin advertises video/x-raw(ANY); QGC_GST_OFFER_DMA_DRM_LINEAR opt-in for explicit LINEAR DMA_DRM caps.
  • DMA-buf perf: QGC_GST_DMABUF_NO_MMAP_FENCE opt-out skips the per-frame mmap fence on linear buffers.
  • Diagnostics: GstAppSinkAdapter exposes negotiated allocator/format/features/resolution as Q_PROPERTYs; auto DOT-dump on pipeline ERROR (capped at 10 files in QStandardPaths::CacheLocation/qgc-pipeline-dot/).

Build (cmake/find-modules/)

  • iOS = xcframework only. GStreamer 1.28 SDK ships GStreamer.xcframework; the legacy .framework layout is gone. Removed:
    • _qgc_discover_ios_sdk .framework walk + fallback (~145 LOC)
    • "1.28+ pretend-framework-is-xcframework" rescue block
    • Legacy /Library/Developer/GStreamer/iPhone.sdk/GStreamer.framework system path
    • iOS branch from find_package(GStreamerMobile) gate
    • gst_ios_init.m.in shim (101 LOC)
  • Added hard floor: iOS requires GStreamer ≥ 1.28.0.
  • Net: FindQGCGStreamer.cmake 1243 → 1112 LOC.

Test plan

  • Linux Release configure + full build clean
  • macOS desktop build (framework path unchanged)
  • iOS device build against bundled 1.28.1 xcframework
  • iOS simulator build (ios-arm64_x86_64-simulator slice)
  • Windows + Android builds (no CMake changes for those platforms)
  • Live RTSP stream: verify no regressions in start/stop, EOS, reconnect
  • D3D11 + D3D12 hw-decoder paths on Windows
  • DMA-buf zero-copy on Linux with VAAPI source

Copilot AI review requested due to automatic review settings May 9, 2026 22:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors and hardens QGroundControl’s GStreamer video receiver stack by extracting source-bin construction into a dedicated factory, improving cross-thread safety around pipeline teardown and GPU context bridging, adding diagnostics/perf toggles, and simplifying iOS GStreamer SDK discovery to xcframework-only (≥ 1.28).

Changes:

  • Extracted GstSourceFactory to centralize RTSP/UDP/TCP source-bin construction and reduce GstVideoReceiver complexity.
  • Improved safety and observability: pipeline DOT dumps on ERROR, bounded EOS waits on stop, appsink negotiation diagnostics, and safer D3D bridge priming via a render-thread QRhi device snapshot.
  • Simplified iOS SDK discovery in CMake to require GStreamer.xcframework (GStreamer ≥ 1.28) and removed the legacy framework/shim paths.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/VideoManager/VideoReceiver/GStreamer/HwBuffers/QGCRhiCapture.h Adds a thread-safe native device snapshot API for cross-thread GPU bridging.
src/VideoManager/VideoReceiver/GStreamer/HwBuffers/QGCRhiCapture.cc Populates/clears the snapshot on scene graph init/invalidate using atomics.
src/VideoManager/VideoReceiver/GStreamer/HwBuffers/GstDmaBufVideoBuffer.cc Adds an env-var opt-out to skip the linear-buffer mmap “fence” stall.
src/VideoManager/VideoReceiver/GStreamer/HwBuffers/GstD3DContextBridgeCommon.h Switches backend gating to snapshot-based checks (no QRhi cross-thread reads).
src/VideoManager/VideoReceiver/GStreamer/HwBuffers/GstD3DContextBridgeCommon.cc Implements snapshot backend checks and adjusts adapter-match logging.
src/VideoManager/VideoReceiver/GStreamer/HwBuffers/GstD3D12ContextBridge.cc Primes D3D12 using snapshot LUID (render-thread captured).
src/VideoManager/VideoReceiver/GStreamer/HwBuffers/GstD3D11ContextBridge.cc Primes D3D11 using snapshot device pointer + LUID (render-thread captured).
src/VideoManager/VideoReceiver/GStreamer/HwBuffers/GstContextBridgeRegistry.h Adds handle-based registration/unregistration + dedup semantics.
src/VideoManager/VideoReceiver/GStreamer/HwBuffers/GstContextBridgeRegistry.cc Implements dedup + freed-slot reuse and unregister APIs.
src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.h Adds _pipelineMutex + _acquirePipelineRef() to reduce stop/bus callback races.
src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc Uses GstSourceFactory, adds bounded EOS wait and DOT dump on ERROR with fallback path.
src/VideoManager/VideoReceiver/GStreamer/GstSourceFactory.h New source-bin factory interface and jitter-buffer policy enum.
src/VideoManager/VideoReceiver/GStreamer/GstSourceFactory.cc New implementation for RTSP/UDP/TCP source-bin building.
src/VideoManager/VideoReceiver/GStreamer/gstqgc/gstqgcvideosinkbin.cc Narrows sink template to video/x-raw(ANY) and adds optional DMA_DRM LINEAR offer.
src/VideoManager/VideoReceiver/GStreamer/GstAppSinkAdapter.h Exposes negotiated allocator/format/features/resolution as Q_PROPERTY diagnostics.
src/VideoManager/VideoReceiver/GStreamer/GstAppSinkAdapter.cc Captures caps negotiation details on caps changes and emits negotiationChanged.
src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt Adds GstSourceFactory.{h,cc} to the build.
cmake/find-modules/GStreamer/gst_ios_init.m.in Removes the legacy iOS GStreamer init shim template.
cmake/find-modules/FindQGCGStreamer.cmake Makes iOS discovery xcframework-only (≥1.28) and removes legacy framework paths.
Comments suppressed due to low confidence (1)

cmake/find-modules/FindQGCGStreamer.cmake:468

  • _qgc_discover_ios_sdk now ignores a user-supplied GStreamer_ROOT_DIR: if GStreamer_ROOT_DIR is set (and valid) but /Library/Developer/.../GStreamer.xcframework is not present, the code still enters the auto-download branch because the condition is only “if(NOT DEFINED _gst_ios_system_xcfw)”. This breaks the documented ability to point at a custom/bundled iOS SDK. Consider restoring the previous guard (only download when GStreamer_ROOT_DIR is not defined or doesn’t exist) and, when GStreamer_ROOT_DIR is provided, derive _gst_ios_system_xcfw from it instead of forcing a download.
    # ── System install ────────────────────────────────────────────────────────
    if(NOT DEFINED GStreamer_ROOT_DIR
       AND EXISTS "/Library/Developer/GStreamer/iPhone.sdk/GStreamer.xcframework")
        set(_gst_ios_system_xcfw "/Library/Developer/GStreamer/iPhone.sdk/GStreamer.xcframework")
    endif()

    # ── Auto-download / expand ────────────────────────────────────────────────
    if(NOT DEFINED _gst_ios_system_xcfw)
        if(CPM_SOURCE_CACHE)
            set(_gst_ios_cache_dir "${CPM_SOURCE_CACHE}/gstreamer-ios-${GStreamer_FIND_VERSION}")
        else()
            set(_gst_ios_cache_dir "${CMAKE_BINARY_DIR}/_deps/gstreamer-ios-${GStreamer_FIND_VERSION}")
        endif()
        set(_gst_ios_expanded "${_gst_ios_cache_dir}/expanded")

Comment on lines +18 to +25
/// Opaque slot handle returned by register*; pass to unregister* to clear that slot.
/// kInvalidHandle indicates the registry was full or the handler was already registered.
using RegistrationHandle = int;
constexpr RegistrationHandle kInvalidHandle = -1;

/// Register a bridge handler. Called from static initializers before any pipeline starts.
void registerBridgeHandler(BridgeHandler handler);
/// Duplicate registrations of the same handler pointer are ignored (returns the existing slot).
RegistrationHandle registerBridgeHandler(BridgeHandler handler);
Comment on lines +33 to +41
/// True if QGCRhiCapture's atomic snapshot has been populated and matches @p expectedBackend.
/// Reads atomic fields only — safe from the bus-sync thread. @p backendName is purely for
/// logging ("D3D11" / "D3D12"). Returns false if the snapshot isn't ready yet (caller should
/// retry on next NEED_CONTEXT) or if the backend is wrong (caller logs once via
/// @p state.warnedWrongBackend).
bool checkSnapshotBackend(BridgeState &state,
const QLoggingCategory &cat,
int expectedBackend,
const char *backendName);
unsigned(info.vendorId), unsigned(info.deviceId),
int(info.deviceType), static_cast<long long>(expectedLuid));
<< apiName << "bridge adapter LUID="
<< QString::asprintf("0x%llx", static_cast<long long>(expectedLuid));
Comment on lines +43 to +44
/// Returns the global snapshot. Atomic fields make individual reads thread-safe.
DeviceSnapshot &deviceSnapshot() noexcept;

#include <QtCore/qglobal.h>
#include <QtCore/QObject>
#include <QtCore/QString>
Comment on lines +400 to +406
// Lock before nulling so an in-flight _onBusMessage on the streaming thread cannot read
// a half-destroyed _pipeline. _acquirePipelineRef takes its own ref under the same lock.
{
QMutexLocker lock(&_pipelineMutex);
gst_clear_object(&_pipeline);
_pipeline = nullptr;
}
[[maybe_unused]] static const bool dotDirHinted = []() {
if (qgetenv("GST_DEBUG_DUMP_DOT_DIR").isEmpty()) {
qCInfo(GstVideoReceiverLog).noquote()
<< "Pipeline dot-graph dumps are disabled. Set GST_DEBUG_DUMP_DOT_DIR=/path/to/dir to enable.";
GstObject *parent = gst_object_get_parent(GST_OBJECT(element));
if (parent) {
if (!gst_element_add_pad(GST_ELEMENT(parent), ghostpad)) {
qCCritical(GstSourceFactoryLog) << "gst_element_add_pad() failed";
@codecov
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

Codecov Report

❌ Patch coverage is 8.68726% with 473 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (master@da67e5b). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...anager/VideoReceiver/GStreamer/GstSourceFactory.cc 0.00% 202 Missing ⚠️
...anager/VideoReceiver/GStreamer/GstVideoReceiver.cc 0.00% 65 Missing ⚠️
.../VideoManager/VideoReceiver/GStreamer/GStreamer.cc 0.00% 42 Missing and 5 partials ⚠️
...anager/VideoReceiver/GStreamer/GStreamerLogging.cc 24.00% 18 Missing and 20 partials ⚠️
src/VideoManager/VideoManager.cc 0.00% 33 Missing and 4 partials ⚠️
...nager/VideoReceiver/GStreamer/GstAppSinkAdapter.cc 18.91% 21 Missing and 9 partials ⚠️
...er/GStreamer/HwBuffers/GstContextBridgeRegistry.cc 42.85% 14 Missing and 10 partials ⚠️
...VideoReceiver/GStreamer/HwBuffers/QGCRhiCapture.cc 28.57% 15 Missing ⚠️
...VideoManager/VideoReceiver/VideoReceiverFactory.cc 0.00% 7 Missing ⚠️
...ceiver/GStreamer/HwBuffers/GstDmaBufVideoBuffer.cc 0.00% 3 Missing ⚠️
... and 3 more

❌ Your patch check has failed because the patch coverage (8.68%) is below the target coverage (30.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##             master   #14350   +/-   ##
=========================================
  Coverage          ?   25.28%           
=========================================
  Files             ?      768           
  Lines             ?    65742           
  Branches          ?    30390           
=========================================
  Hits              ?    16620           
  Misses            ?    37439           
  Partials          ?    11683           
Flag Coverage Δ
unittests 25.28% <8.68%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/VideoManager/VideoManager.h 20.00% <100.00%> (ø)
...anager/VideoReceiver/GStreamer/GStreamerHelpers.cc 36.69% <ø> (ø)
...anager/VideoReceiver/GStreamer/GstAppSinkAdapter.h 0.00% <ø> (ø)
...Manager/VideoReceiver/GStreamer/GstVideoReceiver.h 0.00% <ø> (ø)
...deoReceiver/GStreamer/gstqgc/gstqgcvideosinkbin.cc 39.09% <ø> (ø)
...VideoReceiver/QtMultimedia/QtMultimediaReceiver.cc 0.00% <ø> (ø)
...oManager/VideoReceiver/QtMultimedia/UVCReceiver.cc 6.34% <ø> (ø)
src/Settings/VideoSettings.cc 15.78% <0.00%> (ø)
src/API/QGCCorePlugin.cc 8.92% <0.00%> (ø)
src/QGCApplication.cc 21.44% <0.00%> (ø)
... and 10 more

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update da67e5b...411a6c7. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@HTRamsey HTRamsey force-pushed the video/gst-source-factory branch from 612c23f to abe7c23 Compare May 9, 2026 23:37
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 10, 2026

Build Results

Platform Status

Platform Status Details
Linux Passed View
Windows Failed View
MacOS Passed View
Android Failed View

Some builds failed.

Pre-commit

Check Status Details
pre-commit Failed (non-blocking) View

Pre-commit hooks: 4 passed, 44 failed, 7 skipped.

Test Results

linux-coverage: 98 passed, 0 skipped
Total: 98 passed, 0 skipped

Code Coverage

Coverage Baseline Change
59.0% 59.1% -0.1%

Artifact Sizes

Artifact Size Δ from master
QGroundControl 216.86 MB -4.60 MB (decrease)
QGroundControl-aarch64 176.74 MB +0.10 MB (increase)
QGroundControl-x86_64 171.94 MB -0.44 MB (decrease)
Total size decreased by 4.94 MB

Updated: 2026-05-10 22:04:17 UTC • Triggered by: Linux

@HTRamsey HTRamsey force-pushed the video/gst-source-factory branch from abe7c23 to 948418e Compare May 10, 2026 08:43
@github-actions github-actions Bot added Platform: Android github_actions Pull requests that update GitHub Actions code Tools labels May 10, 2026
Move source-bin construction (rtspsrc / tcpclientsrc / udpsrc + parsebin
wrapping, MPEG-TS demux, optional rtpjitterbuffer) and its four pad-glue
helpers (filterParserCaps, padProbe, linkPad, wrapWithGhostPad) out of
GstVideoReceiver into GStreamer::SourceFactory. The receiver now calls
SourceFactory::create(uri, jitterBuffer); the factory owns transport
dispatch, the receiver owns pipeline lifecycle.

Drive-by fixes in the extracted code:

- Use-after-free in error paths after gst_bin_add_many: locals are
  nulled and non-owning aliases (upstream / binParser / demux / jitter)
  carry through, so the unconditional gst_clear_object cleanup never
  double-unrefs bin-owned children.
- Validate host/port for tcpclientsrc and udpsrc; previously a missing
  port (-1) silently narrowed to 65535.
- udp:// URI rebuild via QUrl(setScheme/setUserInfo) + toEncoded()
  preserves query params (multicast-iface, etc.); the old format-string
  reconstruction dropped them.
- URI scheme detection via QUrl::scheme() instead of QString::contains;
  unknown schemes now log qCWarning and fail fast.
- RTSP credentials via QUrl::userName/password (FullyDecoded) instead
  of manual indexOf(':') split, so percent-encoded passwords work.
- Typed jitter-buffer policy: enum class JitterBuffer { None,
  DropOnLatency, Buffered } replaces the overloaded int (-1/0/>0).
@HTRamsey HTRamsey force-pushed the video/gst-source-factory branch from 948418e to 411a6c7 Compare May 10, 2026 21:32
@github-actions github-actions Bot added the QML label May 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants