
QGC 视频图传与流媒体开发6.0 总体架构QGC 4.0 的视频子系统在编译宏QGC_GST_STREAMING开启时以GStreamer 1.x为底层解码引擎采用Manager → Receiver → Pipeline → QML Sink四层结构将网络/RTP/RTSP 码流解码后渲染到 OpenGL 纹理再嵌入 QML 界面。┌─────────────────────────────────────────────────────────────┐ │ ViewFlightDisplayViewVideo.qml / QGCVideoBackground.qml │ │ GstGLVideoItem OpenGL 纹理 网格线/全屏/热成像 PIP │ └──────────────────────────┬──────────────────────────────────┘ │ Q_PROPERTY / 信号槽 ┌──────────────────────────▼──────────────────────────────────┐ │ ManagerVideoManagerQGCTool │ │ URI 配置、双路流可见光热成像、录制、SubtitleWriter │ └──────────────────────────┬──────────────────────────────────┘ │ start/stop/setUri/setVideoSink ┌──────────────────────────▼──────────────────────────────────┐ │ ReceiverVideoReceiver │ │ GStreamer pipeline 构建、tee 分支、watchdog、自动重连 │ └──────────────────────────┬──────────────────────────────────┘ │ udpsrc/rtspsrc → parsebin → decodebin ┌──────────────────────────▼──────────────────────────────────┐ │ Sinkqgcvideosinkbinglupload → qmlglsink │ │ 绑定 QML GstGLVideoItem widget │ └─────────────────────────────────────────────────────────────┘涉及的设计模式模式体现QGCTool 模式VideoManager在QGCToolbox两阶段初始化中注册Factory 模式QGCCorePlugin::createVideoReceiver()/createVideoManager()Tee 分支模式显示与录制共用tee动态挂接 filesink 分支Observer信号槽videoRunningChanged、gotFirstRecordingKeyFrame、Fact 变更触发restartVideo()Strategy 模式按 URI 前缀选择不同 GStreamer source 元素Watchdog 模式_frameTimer_videoSinkProbe检测帧活性延迟初始化_initVideo()在 QQuickWindow 渲染阶段绑定 widget涉及语法与技术CQObject、Q_PROPERTY、#if defined(QGC_GST_STREAMING)条件编译GStreamer C APIgst_element_factory_make、g_object_set、pad probeQMLGstGLVideoItemorg.freedesktop.gstreamer.GLVideoItemFact 系统VideoSettings持久化配置MAVLinkVIDEO_STREAM_INFORMATION自动发现流地址6.1 相机视频流接收与解码6.1.1 模块与文件索引文件职责VideoStreaming/VideoManager.h/.cc视频总控双 Receiver设置同步VideoStreaming/VideoReceiver.h/.ccGStreamer 管道生命周期VideoStreaming/VideoStreaming.ccGStreamer 初始化、插件注册VideoStreaming/gstqgcvideosinkbin.c自定义 sink binVideoStreaming/gstqgc.cQGC GStreamer 插件注册Settings/VideoSettings.h/.cc视频配置 Fact 组Settings/Video.SettingsGroup.json默认值与枚举FlightMap/QGCVideoBackground.qmlGstGLVideoItem包装FlightDisplay/FlightDisplayViewVideo.qmlFly 视图视频区域Camera/QGCCameraManager.hMAVLink 相机/流信息6.1.2 初始化流程1应用启动QGCApplication调用initializeVideoStreaming(argc, argv, gstDebugLevel)设置GST_PLUGIN_PATHWindows/macOS 内置 GStreamer移动端静态注册插件coreelements、libav、rtp、rtsp、udp、qmlgl、qgc等注册 QML 类型GstGLVideoItem无 GStreamer 时用GLVideoItemStub空壳2Toolbox 初始化VideoManager::setToolbox()_videoReceiver toolbox-corePlugin()-createVideoReceiver(this); _thermalVideoReceiver toolbox-corePlugin()-createVideoReceiver(this); _updateSettings(); if(isGStreamer()) { startVideo(); _subtitleWriter.setVideoReceiver(_videoReceiver);3QML 绑定VideoManager::_initVideo()在渲染同步阶段查找videoContent/thermalVideo控件创建qgcvideosinkbin并setVideoSink()。6.1.3 视频源 URI 与协议映射VideoManager::_updateSettings()负责将配置或 MAVLink 自动发现转为 URI配置/MAVLink 类型URI 格式GStreamer SourceUDP H.264udp://0.0.0.0:5600udpsrc RTP H264 capsUDP H.265udp265://0.0.0.0:5600udpsrc RTP H265 capsRTSPrtsp://host:554/livertspsrcTCP MPEG-TStcp://host:porttcpclientsrctsdemuxMPEG-TS UDPmpegts://0.0.0.0:portudpsrctsdemuxTaisync 移动端tsusb://0.0.0.0:port专用 udpsrcUVC 摄像头QtQCamera非 GStreamer 路径MAVLink 自动发现QGCVideoStreamInfo示例switch(pInfo-type()) { case VIDEO_STREAM_TYPE_RTSP: _videoReceiver-setUri(pInfo-uri()); break; case VIDEO_STREAM_TYPE_RTPUDP: _videoReceiver-setUri(QStringLiteral(udp://0.0.0.0:%1).arg(pInfo-uri())); break; case VIDEO_STREAM_TYPE_MPEG_TS_H264: _videoReceiver-setUri(QStringLiteral(mpegts://0.0.0.0:%1).arg(pInfo-uri())); break;手动配置 fallback 使用VideoSettings的udpPort默认5600、rtspUrl、tcpUrl。6.1.4 GStreamer 管道结构逻辑拓扑注释描述datasource(sourcebin) → tee → queue → decodebin → qgcvideosinkbin └→ [录制分支: teepad → queue → mux → filesink]sourcebin 内部udpsrc/rtspsrc/tcpclientsrc → [rtpjitterbuffer] → parsebin → [ghost pad] 或 tsdemux → parsebinMPEG-TSsink bingstqgcvideosinkbin.cglupload → glcolorconvert → qmlglsink绑定 QML GstGLVideoItemqmlglsink的widget属性指向 QML 中的GstGLVideoItem实现OpenGL 纹理零拷贝渲染到 Qt Quick 场景图。6.1.5_makeSource()协议细节RTSPg_object_set(source, location, qPrintable(uri), latency, 17, udp-reconnect, 1, timeout, _udpReconnect_us, NULL);latency17RTSP 内部 jitter 缓冲 17msudp-reconnect1RTP over UDP 断线重连timeout5000000μs5sUDP 重连超时UDP H.264 RTP capsapplication/x-rtp, media(string)video, clock-rate(int)90000, encoding-name(string)H264parsebin decodebin使用 GStreamer 自动插件选择autoplug自动匹配rtph264depay→h264parse→avdec_h264等无需硬编码解码链。6.1.6start()管道构建gst_bin_add_many(GST_BIN(_pipeline), source, _tee, queue, decoder, _videoSink, nullptr); g_signal_connect(source, pad-added, G_CALLBACK(newPadCB), _tee); gst_element_link_many(_tee, queue, decoder, nullptr); g_signal_connect(decoder, pad-added, G_CALLBACK(newPadCB), _videoSink); running gst_element_set_state(_pipeline, GST_STATE_PLAYING) ! GST_STATE_CHANGE_FAILURE;pad-added 回调source 元素尤其rtspsrc在协商完成后才产生 pad通过newPadCB动态链接到tee。Bus 消息_onBusMessage处理 ERROR/EOS/STATE_CHANGED触发_handleError自动重启。6.1.7 RTSP/TCP 预连接探测rtspsrc若首次连接失败不会自动重试。QGC 用QTcpSocket轮询5s 间隔检测服务器可达成功后才start()管道if(!_serverPresent useTcpConnection) { _tcp_timer.start(100); return; }6.1.8 QML 显示层QGCVideoBackground.qml极简包装仅声明GstGLVideoItemreceiver属性。FlightDisplayViewVideo.qml绑定QGroundControl.videoManager.videoReceiver根据aspectRatio和videoFitFit Width / Fit Height / Stretch计算显示尺寸Loader延迟加载QGCVideoBackground规避部分 Intel 驱动 OpenGL 崩溃无视频时显示 “WAITING FOR VIDEO” / “VIDEO DISABLED”双击切换全屏videoManager.fullScreen支持 MAVLink 相机变焦PinchArea →QGCCameraControl6.1.9 双路视频可见光 热成像VideoManager维护两个独立VideoReceiver_videoReceiver→videoContentwidget_thermalVideoReceiver→thermalVideowidgetMAVLinkdynamicCameras()分别提供currentStreamInstance()和thermalStreamInstance()支持 PIP 混合显示模式。6.1.10 视频录制分支startRecording()从tee请求新 pad挂接录制分支tee → [teepad] → queue → [probe: 等待 I 帧] → mux → filesink关键帧等待_keyframeWatch在收到第一个非 DELTA 帧I 帧前丢弃 buffer设置 PTS offset 为 0再挂接 filesink保证录制文件可立即解码播放。录制格式mkv/mov/mp4VideoSettings.recordingFormat。6.2 画面叠加 OSD 飞行信息6.2.1 重要结论实时 OSD vs 录制字幕QGC 4.0不在实时视频画面上叠加 MAVLink 遥测 OSD。飞行信息叠加仅发生在视频录制时通过ASS 字幕文件.ass写入回放时可显示。实时视频上仅有QML 层叠加非遥测三分构图网格线gridLines设置等待/禁用提示文字热成像 PIP 窗口全屏/变焦 UI6.2.2 SubtitleWriter — 录制 OSD 实现文件VideoStreaming/SubtitleWriter.h/.cc工作流程VideoReceiver.startRecording() → 管道运行等待 I 帧 → gotFirstRecordingKeyFrame 信号 → SubtitleWriter._startCapturingTelemetry() → 读取 QSettings ValuesWidget/large small仪表板字段列表 → 创建 与视频同名的 .ass 文件 → 1Hz QTimer 启动 → _captureTelemetry() 每秒执行 → 从 activeVehicle 读取 Fact 值 → 写入 ASS Dialogue 行1920×1080 坐标系 → recordingChanged(false) → 停止写入ASS 文件头固定 1920×1080stream QStringLiteral( [Script Info]\n ... PlayResX: 1920\n PlayResY: 1080\n ... [Events]\n Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n );布局算法屏幕分为3 列nRows3每列显示若干 Fact 的名称右对齐和数值单位左对齐使用 ASS 定位标签\pos(x,1075)和\an3右对齐左上角显示当前日期\pos(10,35)数据来源绑定for (const auto i : _values) { valuesStrings QStringLiteral(%2 %3).arg(vehicle-getFact(i)-cookedValueString()) .arg(vehicle-getFact(i)-cookedUnits()); namesStrings QStringLiteral(%1:).arg(vehicle-getFact(i)-shortDescription()); }字段列表来自用户在Values 仪表板中配置的ValuesWidget/large和ValuesWidget/small与ValuePageWidget.qml共享配置默认包括相对高度、地速、飞行时间等。采样率_sampleRate 1Hz注释说明 1Hz 时多数播放器显示异常。6.2.3 实时 QML 叠加层三分网格线FlightDisplayViewVideo.qmlRectangle { color: Qt.rgba(1,1,1,0.5) x: parent.width * 0.33 // 竖线 1/3、2/3 visible: _showGrid !QGroundControl.videoManager.fullScreen } Rectangle { y: parent.height * 0.33 // 横线 1/3、2/3 }由VideoSettings.gridLines控制enum: Hide/Show。若需实现实时 OSD扩展路径在FlightDisplayViewVideo.qml的QGCVideoBackground上层叠加 QMLColumn/Repeater绑定activeVehicle的 Fact类似 Fly 视图仪表板或修改 GStreamer 管道在 decode 后插入textoverlay/cairooverlay元素需改 CVideoReceiverCustom 插件可参考custom-example/src/CustomVideoManager.cc6.3 图传卡顿、延时优化6.3.1 延迟来源分析端到端视频延迟 ≈ 发送端编码缓冲 网络传输 接收端 jitterbuffer decode sink sync 渲染。QGC 可控的接收端延迟主要来自环节默认行为延迟影响rtpjitterbuffer默认启用RTP 流~100-200msrtspsrc latency17ms小queue默认无限缓冲可变qmlglsink syncsynctrue跟随 clock1-2 帧decodebin 内部缓冲自动可变6.3.2 lowLatencyMode — 核心低延迟开关设置定义name: lowLatencyMode, longDescription: If this option is enabled, the rtpjitterbuffer is removed and the video sink is set to assynchronous mode, reducing the latency by about 200 ms., defaultValue: false三处生效1跳过 rtpjitterbufferif (probeRes 2 !_videoSettings-lowLatencyMode()-rawValue().toBool()) { buffer gst_element_factory_make(rtpjitterbuffer, nullptr); // source → buffer → parser } else { // 低延迟source → parser 直连 }RTP pad 检测_padProbe识别 RTP 流后非低延迟模式插入 jitterbuffer 重排序/缓冲。2Video sink 异步模式g_object_set(_videoSink, sync, !_videoSettings-lowLatencyMode()-rawValue().toBool(), NULL);syncfalse经qgcvideosinkbin转发到qmlglsink不等待系统 clock收到帧即显示牺牲帧率稳定性换取低延迟。3设置变更自动重启void VideoManager::_lowLatencyModeChanged() { restartVideo(); }6.3.3 帧活性 Watchdog探测机制gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, _videoSinkProbe, this, nullptr);每个 buffer 到达 sink 时更新_lastFrameTime。超时重启_updateTimer1Hzif(_videoRunning) { uint32_t timeout _videoSettings-rtspTimeout()-rawValue().toUInt(); // 默认 2s if(now - _lastFrameTime timeout) { stop(); _stop false; // 允许 _updateTimer 自动 restart } } else { if(!_stop !_running _uri.isEmpty() false streamEnabled) { start(); // 自动重连 } }这解决了 RTSP 断流后管道僵死、画面冻结但不报错的问题。6.3.4 错误自动重启管道 ERROR 消息 →_handleError()→_restart_timer1389ms 单次→VideoManager::restartVideo()RTSP 预连接失败 →_tcp_timeout()→ 5s 后重试QTcpSocket连接EOS 消息 →_handleEOS()→ 重启管道6.3.5 其他优化机制机制位置说明queue默认参数start()TODO建议queue2 max-size-buffers1进一步降延迟ArduSub 自动低延迟Vehicle.cc:521-524水下 ROV 默认 UDP H.264 lowLatencyModetruedisableWhenDisarmedVehicle.cc:1704-1707上锁后停流减少无效解码 CPU 占用streamEnabledVideoSettings总开关关闭则完全不建管道双路流独立 ReceiverVideoManager主/热成像互不影响Loader 延迟加载FlightDisplayViewVideo.qml规避 Intel OpenGL 驱动崩溃录制 I 帧等待_keyframeWatch避免录制文件开头花屏磁盘空间管理_cleanupOldVideos()超maxVideoSize自动删旧文件6.3.6 命令行对照测试README 提供的 GStreamer 测试命令VideoStreaming/README.md发送端gst-launch-1.0 videotestsrcpatternball!video/x-raw,width640,height480!\x264enc!rtph264pay!udpsinkhost127.0.0.1port5600接收端低延迟对照gst-launch-1.0 udpsrcport5600\capsapplication/x-rtp, media(string)video, clock-rate(int)90000, encoding-name(string)H264!\rtph264depay!h264parse!avdec_h264!autovideosinksyncfalseQGC 实际使用parsebindecodebinqgcvideosinkbin比固定 depay 链更通用但可能多一层缓冲。6.3.7 卡顿排查建议开启 lowLatencyModeSettings → General → Video确认 UDP 端口无冲突默认 5600与 MAVROS/其他工具隔离减小发送端 GOP/关键帧间隔I 帧间隔过大导致 decode 等待检查_lastFrameTimewatchdog 日志频繁 restart 说明链路不稳定RTSP 场景确认预连接成功_serverPresent标志移动端优先 UDP 而非 RTSPRTSP 握手TCP 开销更大关闭不必要的第二路流热成像_thermalVideoReceiverCPU/GPU 解码能力decodebin自动选择软解/硬解弱设备可强制硬件解码插件6.4 VideoSettings 配置项完整表Fact 名类型默认值作用videoSourcestring“”RTSP/UDP/TCP/MPEG-TS/UVC/DisabledudpPortuint165600UDP 绑定端口rtspUrlstring“”RTSP 地址tcpUrlstring“”TCP 地址aspectRatiofloat1.77777716:9 宽高比videoFitenumFit Height显示缩放模式gridLinesenumHide三分网格线streamEnabledbooltrue流总开关disableWhenDisarmedboolfalse上锁后停流lowLatencyModeboolfalse低延迟模式rtspTimeoutuint322s无帧超时recordingFormatenummkv录制容器maxVideoSizeuint3210240MB录制空间上限enableStorageLimitboolfalse自动清理旧录制6.6 关键方法速查类方法作用VideoManagerstartVideo()/stopVideo()/restartVideo()启停/重启VideoManager_updateSettings()URI 协议映射VideoManager_initVideo()/_makeVideoSink()绑定 QML widgetVideoReceiverstart()/stop()管道 PLAYING/NULLVideoReceiver_makeSource()按 URI 建 source binVideoReceiverstartRecording()/stopRecording()Tee 分支录制VideoReceiver_updateTimer()帧超时 watchdogVideoReceiver_videoSinkProbe()帧到达探测SubtitleWriter_captureTelemetry()写 ASS 遥测字幕VideoSettingsstreamConfigured()配置完整性检查6.7 本章小结QGroundControl 4.0 的视频图传子系统以GStreamer 管道为核心通过VideoManager统一管理配置与生命周期VideoReceiver按 URI 协议动态构建 source→tee→decode→sink 链路最终经自定义qgcvideosinkbin渲染到 QMLGstGLVideoItem。OSD 方面QGC 不在实时画面叠加遥测而是通过SubtitleWriter在录制时以ASS 字幕写入 Fact 数据1Hz1920×1080 三列布局回放时可显示实时仅有网格线等 QML 叠加。延迟优化以lowLatencyMode为核心移除 jitterbuffer sink syncfalse约减 200ms配合帧活性 watchdogrtspTimeout、错误自动重启1389ms、RTSP 预连接探测、ArduSub 默认低延迟等机制在稳定性与实时性之间取得平衡。