当前位置: 首页 > news >正文

QGC 视频图传与流媒体开发

QGC 视频图传与流媒体开发

6.0 总体架构

QGC 4.0 的视频子系统在编译宏QGC_GST_STREAMING开启时,以GStreamer 1.x为底层解码引擎,采用Manager → Receiver → Pipeline → QML Sink四层结构,将网络/RTP/RTSP 码流解码后渲染到 OpenGL 纹理,再嵌入 QML 界面。

┌─────────────────────────────────────────────────────────────┐ │ View:FlightDisplayViewVideo.qml / QGCVideoBackground.qml │ │ (GstGLVideoItem OpenGL 纹理 + 网格线/全屏/热成像 PIP) │ └──────────────────────────┬──────────────────────────────────┘ │ Q_PROPERTY / 信号槽 ┌──────────────────────────▼──────────────────────────────────┐ │ Manager:VideoManager(QGCTool) │ │ URI 配置、双路流(可见光+热成像)、录制、SubtitleWriter │ └──────────────────────────┬──────────────────────────────────┘ │ start/stop/setUri/setVideoSink ┌──────────────────────────▼──────────────────────────────────┐ │ Receiver:VideoReceiver │ │ GStreamer pipeline 构建、tee 分支、watchdog、自动重连 │ └──────────────────────────┬──────────────────────────────────┘ │ udpsrc/rtspsrc → parsebin → decodebin ┌──────────────────────────▼──────────────────────────────────┐ │ Sink:qgcvideosinkbin(glupload → qmlglsink) │ │ 绑定 QML GstGLVideoItem widget │ └─────────────────────────────────────────────────────────────┘

涉及的设计模式:

模式体现
QGCTool 模式VideoManagerQGCToolbox两阶段初始化中注册
Factory 模式QGCCorePlugin::createVideoReceiver()/createVideoManager()
Tee 分支模式显示与录制共用tee,动态挂接 filesink 分支
Observer(信号槽)videoRunningChangedgotFirstRecordingKeyFrame、Fact 变更触发restartVideo()
Strategy 模式按 URI 前缀选择不同 GStreamer source 元素
Watchdog 模式_frameTimer+_videoSinkProbe检测帧活性
延迟初始化_initVideo()在 QQuickWindow 渲染阶段绑定 widget

涉及语法与技术:

  • C++:QObjectQ_PROPERTY#if defined(QGC_GST_STREAMING)条件编译
  • GStreamer C API:gst_element_factory_makeg_object_set、pad probe
  • QML:GstGLVideoItemorg.freedesktop.gstreamer.GLVideoItem
  • Fact 系统:VideoSettings持久化配置
  • MAVLink:VIDEO_STREAM_INFORMATION自动发现流地址

6.1 相机视频流接收与解码

6.1.1 模块与文件索引

文件职责
VideoStreaming/VideoManager.h/.cc视频总控,双 Receiver,设置同步
VideoStreaming/VideoReceiver.h/.ccGStreamer 管道生命周期
VideoStreaming/VideoStreaming.ccGStreamer 初始化、插件注册
VideoStreaming/gstqgcvideosinkbin.c自定义 sink bin
VideoStreaming/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_PATH(Windows/macOS 内置 GStreamer)
  • 移动端静态注册插件:coreelementslibavrtprtspudpqmlglqgc
  • 注册 QML 类型GstGLVideoItem(无 GStreamer 时用GLVideoItemStub空壳)

(2)Toolbox 初始化:VideoManager::setToolbox()

_videoReceiver = toolbox->corePlugin()->createVideoReceiver(this); _thermalVideoReceiver = toolbox->corePlugin()->createVideoReceiver(this); _updateSettings(); if(isGStreamer()) { startVideo(); _subtitleWriter.setVideoReceiver(_videoReceiver);

(3)QML 绑定:VideoManager::_initVideo()在渲染同步阶段查找videoContent/thermalVideo控件,创建qgcvideosinkbinsetVideoSink()

6.1.3 视频源 URI 与协议映射

VideoManager::_updateSettings()负责将配置或 MAVLink 自动发现转为 URI:

配置/MAVLink 类型URI 格式GStreamer Source
UDP H.264udp://0.0.0.0:5600udpsrc+ RTP H264 caps
UDP H.265udp265://0.0.0.0:5600udpsrc+ RTP H265 caps
RTSPrtsp://host:554/livertspsrc
TCP MPEG-TStcp://host:porttcpclientsrc+tsdemux
MPEG-TS UDPmpegts://0.0.0.0:portudpsrc+tsdemux
Taisync 移动端tsusb://0.0.0.0:port专用 udpsrc
UVC 摄像头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 使用VideoSettingsudpPort(默认5600)、rtspUrltcpUrl

6.1.4 GStreamer 管道结构

逻辑拓扑(注释描述):

datasource(sourcebin) → tee → queue → decodebin → qgcvideosinkbin └→ [录制分支: teepad → queue → mux → filesink]

sourcebin 内部:

udpsrc/rtspsrc/tcpclientsrc → [rtpjitterbuffer] → parsebin → [ghost pad] 或 tsdemux → parsebin(MPEG-TS)

sink bin(gstqgcvideosinkbin.c):

glupload → glcolorconvert → qmlglsink(绑定 QML GstGLVideoItem)

qmlglsinkwidget属性指向 QML 中的GstGLVideoItem,实现OpenGL 纹理零拷贝渲染到 Qt Quick 场景图。

6.1.5_makeSource()协议细节

RTSP:

g_object_set(source, "location", qPrintable(uri), "latency", 17, "udp-reconnect", 1, "timeout", _udpReconnect_us, NULL);
  • latency=17:RTSP 内部 jitter 缓冲 17ms
  • udp-reconnect=1:RTP over UDP 断线重连
  • timeout=5000000μs(5s):UDP 重连超时

UDP H.264 RTP caps:

application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264

parsebin + decodebin:使用 GStreamer 自动插件选择(autoplug),自动匹配rtph264depayh264parseavdec_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极简包装,仅声明GstGLVideoItem+receiver属性。

FlightDisplayViewVideo.qml

  • 绑定QGroundControl.videoManager.videoReceiver
  • 根据aspectRatiovideoFit(Fit Width / Fit Height / Stretch)计算显示尺寸
  • Loader延迟加载QGCVideoBackground(规避部分 Intel 驱动 OpenGL 崩溃)
  • 无视频时显示 “WAITING FOR VIDEO” / “VIDEO DISABLED”
  • 双击切换全屏(videoManager.fullScreen
  • 支持 MAVLink 相机变焦(PinchArea →QGCCameraControl

6.1.9 双路视频(可见光 + 热成像)

VideoManager维护两个独立VideoReceiver

  • _videoReceivervideoContentwidget
  • _thermalVideoReceiverthermalVideowidget

MAVLinkdynamicCameras()分别提供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 窗口
  • 全屏/变焦 UI

6.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×1080):

stream << 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 列nRows=3
  • 每列显示若干 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/largeValuesWidget/small(与ValuePageWidget.qml共享配置),默认包括相对高度、地速、飞行时间等。

采样率:_sampleRate = 1Hz(注释说明 >1Hz 时多数播放器显示异常)。

6.2.3 实时 QML 叠加层

三分网格线(FlightDisplayViewVideo.qml):

Rectangle { 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,扩展路径:

  1. FlightDisplayViewVideo.qmlQGCVideoBackground上层叠加 QMLColumn/Repeater,绑定activeVehicle的 Fact(类似 Fly 视图仪表板)
  2. 或修改 GStreamer 管道,在 decode 后插入textoverlay/cairooverlay元素(需改 C++VideoReceiver
  3. Custom 插件可参考custom-example/src/CustomVideoManager.cc

6.3 图传卡顿、延时优化

6.3.1 延迟来源分析

端到端视频延迟 ≈ 发送端编码缓冲 + 网络传输 + 接收端 jitterbuffer + decode + sink sync + 渲染。

QGC 可控的接收端延迟主要来自:

环节默认行为延迟影响
rtpjitterbuffer默认启用(RTP 流)~100-200ms
rtspsrc latency17ms
queue默认无限缓冲可变
qmlglsink syncsync=true(跟随 clock)1-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)跳过 rtpjitterbuffer:

if (probeRes & 2 && !_videoSettings->lowLatencyMode()->rawValue().toBool()) { buffer = gst_element_factory_make("rtpjitterbuffer", nullptr); // source → buffer → parser } else { // 低延迟:source → parser 直连 }

RTP pad 检测(_padProbe)识别 RTP 流后,非低延迟模式插入 jitterbuffer 重排序/缓冲。

(2)Video sink 异步模式:

g_object_set(_videoSink, "sync", !_videoSettings->lowLatencyMode()->rawValue().toBool(), NULL);

sync=falseqgcvideosinkbin转发到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

超时重启(_updateTimer,1Hz):

if(_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_timer(1389ms 单次)→VideoManager::restartVideo()

RTSP 预连接失败 →_tcp_timeout()→ 5s 后重试QTcpSocket连接

EOS 消息 →_handleEOS()→ 重启管道

6.3.5 其他优化机制

机制位置说明
queue默认参数start()TODO:建议queue2 max-size-buffers=1进一步降延迟
ArduSub 自动低延迟Vehicle.cc:521-524水下 ROV 默认 UDP H.264 +lowLatencyMode=true
disableWhenDisarmedVehicle.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 videotestsrcpattern=ball!video/x-raw,width=640,height=480!\x264enc!rtph264pay!udpsinkhost=127.0.0.1port=5600

接收端(低延迟对照):

gst-launch-1.0 udpsrcport=5600\caps='application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264'!\rtph264depay!h264parse!avdec_h264!autovideosinksync=false

QGC 实际使用parsebin+decodebin+qgcvideosinkbin,比固定 depay 链更通用但可能多一层缓冲。

6.3.7 卡顿排查建议

  1. 开启 lowLatencyMode(Settings → General → Video)
  2. 确认 UDP 端口无冲突(默认 5600,与 MAVROS/其他工具隔离)
  3. 减小发送端 GOP/关键帧间隔(I 帧间隔过大导致 decode 等待)
  4. 检查_lastFrameTimewatchdog 日志(频繁 restart 说明链路不稳定)
  5. RTSP 场景确认预连接成功_serverPresent标志)
  6. 移动端优先 UDP 而非 RTSP(RTSP 握手+TCP 开销更大)
  7. 关闭不必要的第二路流(热成像_thermalVideoReceiver
  8. CPU/GPU 解码能力decodebin自动选择软解/硬解,弱设备可强制硬件解码插件

6.4 VideoSettings 配置项完整表

Fact 名类型默认值作用
videoSourcestring“”RTSP/UDP/TCP/MPEG-TS/UVC/Disabled
udpPortuint165600UDP 绑定端口
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 widget
VideoReceiverstart()/stop()管道 PLAYING/NULL
VideoReceiver_makeSource()按 URI 建 source bin
VideoReceiverstartRecording()/stopRecording()Tee 分支录制
VideoReceiver_updateTimer()帧超时 watchdog
VideoReceiver_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 数据(1Hz,1920×1080 三列布局),回放时可显示;实时仅有网格线等 QML 叠加。

延迟优化lowLatencyMode为核心(移除 jitterbuffer + sink sync=false,约减 200ms),配合帧活性 watchdog(rtspTimeout)、错误自动重启(1389ms)、RTSP 预连接探测、ArduSub 默认低延迟等机制,在稳定性与实时性之间取得平衡。

http://www.jsqmd.com/news/894574/

相关文章:

  • 5步掌握BepInEx:从游戏新手到模组大师的完整指南
  • 构建内容生成服务时利用Taotoken实现模型降级与容灾
  • 从UE5 Nanite到CIM项目:聊聊LOD技术的前世今生与实战避坑
  • 给51单片机智能小车的避障程序‘瘦身’:优化定时器与中断资源分配(附完整代码对比)
  • 基于文本挖掘的教学评价分析:从情感分析与主题建模到实践应用
  • 荣品RV1126 SDK编译避坑指南:从分区表修改到rkmedia自定义编译
  • 基于AWS Bedrock与Step Functions构建智能DevOps Agent实战指南
  • STM32寄存器点灯避坑指南:CRL和CRH寄存器配置详解(附Keil工程)
  • 嵌入式系统中看门狗定时器与SD卡文件系统的冲突与优化
  • LVGL在STM32内存紧张?F103上优化触摸移植的3个实战技巧(附Level3优化配置)
  • 量子增强与大语言模型结合的数据填补技术
  • OK3588开发板多屏显示实战:如何用Uboot菜单灵活切换HDMI和eDP屏幕
  • Grid++Report实战:如何用一款老牌国产报表工具,搞定医院HIS和建筑工程里的复杂表格?
  • Win10文件属性丢了数字签名和安全选项卡?别慌,一个注册表文件就能救回来
  • CARE Loop:以人为本的本地大模型开发框架与实践指南
  • C语言跨平台桌面UI突围!libui-ng实战对比Win32、GTK老牌方案
  • 别再只看衰减了!手把手教你读懂USB3.0线束测试报告(以AVT相机线为例)
  • 别再死记硬背了!用Python画个动图,5分钟搞懂Moore和Mealy状态机的区别
  • 从工厂到你家:Matter设备里的DAC、PAI、CD证书到底是怎么烧录和工作的?
  • RK3588开发板触摸屏调试实录:搞定GT9XX驱动编译与DTS配置的那些坑
  • 从《Real-Time Rendering》到UE5:一文读懂LOD技术演进史(附Tessellation与几何形变LOD实战解析)
  • AI记忆引擎核心:指数衰减公式R=e^(-t/S)的原理与调优实践
  • QGC 固件升级与硬件适配
  • AI编程助手延迟优化:提升开发者心流与代码质量的智能交互设计
  • 【最新v2.7.5 版本安装包】零代码搭建智能助手,OpenClaw 零基础无需命令快速部署教程
  • 别再只读数据了!深入解析DHT11和MQ2的底层通信协议与51单片机精准驱动(附示波器波形分析)
  • 深入理解AURIX TC3xx中断路由(IR):对比ARM Cortex-M,聊聊SRN和ICU的设计哲学
  • 避坑指南:在VMware虚拟机Ubuntu22.04上搞定CH340串口驱动,连接ROS2机械臂
  • Java开发高手秘籍:性能优化与调试技巧全解析
  • 光电融合ViT加速:硅光子技术突破视觉Transformer瓶颈