ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

QGC视频流二次开发:GStreamer架构与RTSP实战解析

QGC视频流二次开发:GStreamer架构与RTSP实战解析 做QGC二次开发的人十有八九会被视频流这块卡上一阵子。项目本身是开源的文档也不少但真正涉及到“视频流能不能显示出来”“画面为什么黑屏”“RTSP地址明明写了怎么没反应”这类问题时很多帖子都只给结论不给过程翻了半天代码还是一头雾水。这篇文章我就把QGC的视频流模块从头到尾捋一遍拆开讲清楚它到底是怎么接收视频、怎么解码、怎么显示的以及二次开发时你大概率要动的那些关键点。内容会偏向源码分析和实操适合正在做QGC定制开发、或者打算把无人机视频流接进QGC的开发者参考。1. 视频流模块整体架构搞清楚QGC到底怎么“看”视频1.1 GStreamer在前端扮演的角色QGC的地面站软件是基于Qt开发的视频显示部分没有自己造轮子而是集成了一套开源的多媒体框架叫GStreamer。很多刚接触QGC二次开发的人会问为什么是GStreamer而不是FFmpeg或者干脆用Qt自带的QMediaPlayer答案藏在QGC实际运行的平台和需求里。GStreamer强在跨平台和Pipeline式的媒体处理。你可以把它理解成一条流水线视频流的接收、解封装、解码、格式转换、渲染每一段都是一个单独的“插件”通过管道串联起来。QGC需要同时支持Windows、Linux、macOS还要支持RTSP、UDP裸流、本地文件等多种视频源GStreamer的插件机制天然适合这种需求。FFmpeg虽然编码能力强但它的架构更偏命令行工具库要在Qt界面里做实时渲染GStreamer的videoconvert和gtk/glimagesink等元素反而更顺手。QGC在启动时会根据编译选项决定是否启用视频支持。默认情况下的release版是带GStreamer的。如果你自己从源码编译需要注意一个开关QGC_GST_STREAMING。没打开这个宏所有视频相关的代码编译后都不会生效界面上也不会出现视频悬浮窗。很多新手编译完发现没有视频功能多半是这个宏没开启。1.2 三条视频接收路径UDP、RTSP、TCPQGC的VideoReceiver并不是一个只支持单一协议的工具它内部根据不同的视频源格式开启了不同的GStreamer Pipeline分支。我梳理一下比较核心的三条路径UDP裸流常见于数传链路里直接传H264或H265裸流的情况QGC使用udpsrc作为源头然后直接喂给解码器。由于没有RTSP协商的过程延迟可以压得很低但丢包恢复能力比较弱。RTSP拉流这是目前最主流的接入方式。QGC通过rtspsrc插件连接到IPC或者机载电脑上的RTSP服务端自动协商编码格式和传输方式UDP/TCP。大多数网络摄像头走这条路径。TCP自定义流适合有特殊封装格式的场景。QGC允许你把视频源指定为TCP地址用tcpclientsrc来接收数据。这种路径用得少但某些私有协议远程传输时非常实用。了解这三条路径最大的意义在于当你遇到“画面出不来”的问题时先确认你的视频源到底是什么类型。是RTSP还是裸的H264流决定了你要改的代码位置完全不一样。1.3 为什么选GStreamer不选FFmpeg我见过有人尝试把QGC的视频模块替换成FFmpeg方案最后基本都绕回GStreamer了。这不是说FFmpeg不好而是改造代价太大。QGC视频模块和GStreamer的绑定很深从VideoReceiver到VideoManager几乎每个类都直接操作GStreamer的Element和Bus。替换成FFmpeg意味着这些类全部要推翻重写还要自己处理渲染和窗口嵌入。另外GStreamer的Pipeline描述文件就是那段字符串比如rtspsrc location... ! rtph264depay ! avdec_h264 ! videoconvert ! qglimagesink是可以直接在配置里改的。这给二次开发带来了非常大的灵活性。你不需要重新编译整个QGC仅仅修改QGCSetting里的视频管道字符串就能换一种解码方式或者插入一个自定义的滤镜。这种“配置即代码”的风格很对我胃口调试效率很高。2. 源码级梳理视频从网口到屏幕的完整旅程2.1 关键代码位置动工之前必须知道的几个类二次开发最怕的就是一头扎进源码里找不到北。QGC的视频流代码量不大但涉及类不少我先列一份“地图”把关键类和它的职责分清楚。第一个必须要认识的是VideoManager。它是整个视频模块的入口负责管理接收器实例的创建和销毁。QGC主程序通过单例模式持有它当MAVLink消息里的MAV_LINK或者用户手动打开视频时VideoManager会被激活。第二个是VideoReceiver这是视频流的营养中枢。它负责解析用户配置的URL、设置GStreamer Pipeline、接收GStreamer Bus上报的错误和状态消息。你要对视频流做任何编程式控制开关、切换源、读取状态都绕不开这个类。第三个是VideoSurface和VideoRenderer负责把解码后的视频帧渲染到QML界面上。QGC是QML写的UI视频窗口其实是一个通过QQuickItem暴露出来的OpenGL纹理对象GStreamer的qglimagesink插件把每一帧视频推进这个纹理里显示。除了这三个核心类还有AirframeComponent和MAVLinkVideoStream这类辅助类负责从MAVLink消息里解析视频流URL、在UI上生成视频按钮。排查“连接无人机后视频不自动打开”这类问题时重点查它在不在工作。2.2 视频启停的钥匙——QSettings怎么读QGC的很多行为都是用QSettings配置的。视频模块的配置主要集中在UI设置界面的“General”页里底层对应的是VideoSettings这个类。它把一系列用户可调的参数映射成QGCSetting的键值对。日常二次开发最常碰到的几个键VideoSource视频源类型可选值有RTSP、UDP、TCP等。VideoURL视频源地址比如rtsp://192.168.1.100:554/stream1。VideoPipelineGStreamer的Pipeline描述字符串。UDPVideoPortUDP模式下的监听端口。LowLatencyMode低延迟模式开关打开后会调整GStreamer内部缓冲参数。很多人忽略了一个细节QGC在读取VideoPipeline时并不是把它当作一条需要完全按照用户写法执行的命令而是经过了一系列字符串解析。它会根据视频源类型在管道字符串里自动替换掉占位符。比如你在设置里写了UDP端口号VideoReceiver会把端口号的宏替换进Pipeline里再交给GStreamer去解析。如果直接改Pipeline字符串格式错了轻则视频打不开重则整个视频子模块崩溃因为QGC对GStreamer返回的空管道指针处理得不算健壮空指针解引用直接会崩。2.3 渲染链路解读QML里的那个“视频悬浮窗”在QGC界面上视频是叠加在飞行地图右上角的一个小悬浮窗里的。这个控件在QML里的层次大概是这样的最底层是FlightDisplayViewMap地图上面叠一个VideoSurface视频纹理再上面是各种数据面板控件。这个悬浮窗的位置不固定用户可以拖动。当用户拖拽时QML层会更新窗口坐标属性底层的VideoSurface根据坐标调整OpenGL纹理的显示区域。这个机制本身不算复杂但如果二次开发时你在同一个界面上增加了多个视频窗口就得注意了QGC内部的VideoSurface大体上只支持单个主视频源实例除非你有意做多路复用否则叠加多个Surface会导致视频纹理在反初始化时互相干扰最典型的症状是切换视频源时窗口变成白块。3. 实操5分钟跑通RTSP视频流3.1 准备一个稳定的视频源不要一开始就拿飞控的机载摄像头调试那样问题变量太多了。我建议先用本地电脑起一个RTSP服务来测试QGC的视频链路是否正常。最省事的工具是mediamtx原rtsp-simple-server它在Windows、Linux、macOS上都能跑。把摄像头的RTSP地址或者本地视频文件推给它它会对外提供一个新的RTSP地址。比如你在mediamtx的配置里把输入源指向本地一个MP4文件然后对外发布rtsp://127.0.0.1:8554/camera。这样QGC的VideoURL可以直接填这个地址链路和真实场景完全一致但排除了编码器、网络带宽、认证等干扰因素。3.2 修改QSettings参数的详细流程跑通视频流的操作路径其实很短但我还是建议把每一步都看清楚免得后续排查摸不着头脑。第一步打开QGC的“设置”界面进入“General”页面。找到“Video”栏把Video Source设置为RTSP Video Stream。这个选项决定了QGC走rtspsrc分支。第二步在Video URL里填上你的RTSP地址比如rtsp://127.0.0.1:8554/camera。注意格式必须是rtsp://开头的完整URL不能漏掉端口号否则GStreamer连接会报超时。第三步确认Video Pipeline的值是能用的。默认值一般是rtspsrc locationrtsp://%URL% ! decodebin ! videoconvert ! qglimagesink或者类似写法。注意有个%URL%占位符QGC启动时会用VideoURL的实际值去替换它。decodebin会动态检测编码格式并选择合适的解码器这是最省心的方式但延迟会略高。追求低延迟的话可以显式指定解码器比如rtph264depay ! avdec_h264前提是你的视频源确定是H264编码。第四步保证RTSP服务端允许匿名拉流或者已经配置好了用户名密码。QGC的VideoURL支持rtsp://user:passip:port/path这种带认证的写法。配置完后重启QGC进入主飞行界面正常情况下右上角的视频窗口就会开始显示画面。如果在“General”里勾选了“Auto Stream”那么QGC只要有MAVLink连接就会自动尝试拉流如果没勾选你需要点击飞控设置里的视频按钮手动触发。3.3 验证与掉坑点我在测试里面遇到过不少次“填了地址但黑屏”的情况这里把最常见的三个坑先放到前面Video Pipeline字符串末尾的分号!不能漏也不能多。GStreamer对管道的语法检查不友好少一个元素整条Pipeline直接跑不起来而且QGC日志里只会出现一句笼统的Failed to set pipeline to playing state。防火墙拦截。Windows上跑RTSP拉流如果Windows Defender防火墙没有放行QGC进程TCP连接往往能建立但UDP数据包进不来表现就是画面一直转圈。可以先在防火墙里把QGC加入允许列表或者临时关闭防火墙测试一分钟。视频分辨率太大解码性能跟不上。有些4K摄像头输出4K30帧但你的地面站电脑解码软解吃力画面就一卡一卡甚至花屏。可以先把摄像头输出降到1080P再测。4. 二次开发落地三个最常见的改造方向4.1 按需启停视频流用代码控制VideoReceiver官方原版的QGC视频流基本上是跟随MAVLink连接状态走的。但在二次开发里很多客户要求视频流不能一直挂着得由操作员手动按键打开或者地面站软件启动后自动连接预设的RTSP地址不需要飞控参与。实现按需启停核心是直接操作VideoManager和VideoReceiver两个单例。如果你要从QML里调用可以先把VideoManager注册成QML单例对象暴露一个startStream()方法和一个stopStream()方法。两个方法的底层代码逻辑很简单前者调_videoManager-startVideo()后者调_videoManager-stopVideo()。但注意如果重复调用startVideo()QGC内部的引用计数会增加而stopVideo()只会把引用计数减到0才真正关停。写代码时如果没注意这个机制会导致你调了两次停止视频却还在后台运行CPU占用居高不下。4.2 适配私有RTSP URL格式我在实际项目里碰到过一种情况客户的IPC的RTSP地址很特殊包含了动态的会话ID每过一段时间就会变。传统做法是让用户每次修改VideoURL但这显然不够智能。更好的办法是在QGC里增加一个自定义的URL拼接逻辑。具体思路是在VideoReceiver层增加一个urlOverride属性。当它被赋值时QGC优先使用这个值作为拉流地址而忽略配置里的VideoURL。在业务逻辑层次你可以用定时器去请求一台HTTP服务获取当前有效的RTSP地址然后更新urlOverride。这样即使后端地址变了QGC也能无缝切换到新的流不需要人工干预。要注意的是切换地址前最好先做一个短暂暂停把旧Pipeline停掉再启动新Pipeline。直接在播放状态里改locationGStreamer的rtspsrc有时候会报错导致新的流起不来。我习惯的做法是先停止视频流等200ms再设置新URL最后重新启动这样最稳定。4.3 拉流叠加OSD信息把飞行数据画在视频上很多客户希望在视频画面里看到实时高度、速度、经纬度等飞行数据。这个需求很常见实现方式也有好几种。最偷懒的是在QML层叠加文本覆盖层但这样实现简单屏幕采集截图的时候如果没把覆盖层一起截进去效果就达不到客户要求。更专业的是在视频进入UI之前用GStreamer的textoverlay插件把OSD信息烧录进视频流里。在QGC的VideoPipeline字符串里加一个textoverlay元素再通过GStreamer的属性接口更新文字内容。比如Pipeline可以是rtspsrc locationrtsp://%URL% ! decodebin ! videoconvert ! textoverlay nameosd ! videoconvert ! qglimagesink然后获取osd这个Element的指针用g_object_set()修改它的text属性。这样OSD信息就真正压进了视频像素里不管是保存截图还是录屏都能看到相关信息。但这里有个注意点textoverlay只能处理编码前的原始视频帧如果你的Pipeline里在它前面接了硬解出来的NV12格式后面又没有videoconvert转到RGBA渲染是正常的但一旦插入textoverlay你必须确保它的上游格式是它能接受的颜色空间。否则GStreamer会在运行时报格式协商错误。解决办法是在textoverlay前后各加一个videoconvert确保格式兼容。5. 常见问题与排查技巧实录5.1 高频问题速查表现象可能原因处理思路视频窗口黑屏无任何状态提示Pipeline没有正确启动或解码器缺少在终端用gst-launch手动测试同样的Pipeline确认GStreamer环境没问题视频一直转圈加载RTSP连接超时或网络不通在电脑上用VLC或ffplay测试RTSP地址是否能正常播放有画面但花屏/绿屏解码器选错或视频源编码格式与Pipeline不匹配用decodebin自动选择解码器避免硬编码avdec_h264打开视频后QGC崩溃Pipeline字符串语法错误或GStreamer插件缺失检查最后的! videoconvert ! qglimagesink是否完整用gst-inspect-1.0确认插件存在视频延迟太高默认Pipeline为了兼容性使用了较大的缓冲开启低延迟模式或者网络条件好的时候改用UDP传输切换视频源后窗口白屏VideoSurface没有正确更新纹理手动调用videoReceiver-stop()后延迟一段时间再start()5.2 排查VideoReceiver状态的技巧QGC的VideoReceiver内部维护了一个状态机包含Stopped、Starting、Running、Error等状态。排查问题最直接的办法是打日志看状态迁移。QGC是Qt的qDebug体系在VideoReceiver.cc里有一堆qCDebug(VideoManagerLog)输出编译时如果把VideoManagerLog这个logging category的级别调到Debug日志会输出详细的状态变化。最典型的排查流程先看日志里有没有gst pipeline state change这一行。如果有但画面没出来说明Pipeline已经动起来了问题在渲染环节如果没有说明Pipeline起都没起来问题在网络或者地址解析。这个判断能帮你快速把问题范围缩小一半。5.3 使用GStreamer调试工具辅助开发QGC内部的GStreamer Pipeline可能需要加一些环境变量才能看到调试信息。最有用的是GST_DEBUG。在启动QGC之前在终端里设置export GST_DEBUG3这样会把WARNING以上的GStreamer日志输出到控制台。如果打开到5会输出非常详细的日志包括每个Element的状态变化和缓冲区信息但也会非常刷屏。实际排查时从3开始不够再升。另外一个非常管用的工具是gst-launch-1.0你可以先把QGC里的Pipeline原封不动在终端里跑一遍确认单独运行没问题再回QGC里找问题。比如我的Pipeline是gst-launch-1.0 rtspsrc locationrtsp://127.0.0.1:8554/camera ! decodebin ! videoconvert ! autovideosink如果这条命令能出图像那QGC的问题大概率出在它内部的渲染通道而不是拉流解码环节。从我自己的开发经验来看QGC视频流的二次开发真正难的并不是视频编解码本身而是把视频模块和原有架构融洽地接在一起。QGC的视频管道设计得其实已经很方便了大部分需求都可以通过调整Pipeline字符串和参数解决只有在多路复用、自定义封装协议、跨平台摄像头集成这些特殊场景下才需要深入改代码。做二次开发之前强烈建议先在设置界面里把默认的RTSP链路完整跑一遍再动手改代码。基础链路不通后面改再多代码都是白搭。最后分享一个小经验在QGC里改了视频相关代码重新编译之后如果发现变化没有生效先检查一下是不是没有删掉旧的构建缓存尤其是QML相关文件。QGC的QML缓存有时候会比较顽固不清理干净你会以为改的是没用的代码实际上是因为缓存里还在跑旧逻辑。视频流这种问题尤其明显界面和数据都是实时更新的但缓存会把你“骗”过去好一会儿。
返回列表