4、多路视频源基础:多路视频源的概念、常见的视频源(v4l2src、filesrc、videotestsrc)、多路源的启动与停止控制

4.1 多路视频源到底是个啥?

好,咱们直接切入正题。多路视频源,说白了就是同时从多个地方拉视频流进来。

你想想看,一个监控大屏上同时显示16个摄像头画面,那就是16路视频源。一个直播导播台同时切4个机位,那就是4路视频源。我刚开始接触GStreamer时,总觉得“多路”就是多开几个进程完事。后来才发现,真正的多路同步处理,远比想象中复杂。

多路视频源的核心挑战在于:各路源的启动时序、停止控制、以及时钟同步。这三件事没做好,画面就会错位、卡顿、甚至直接崩掉。

我个人习惯把视频源分成三类:

  • 硬件源:比如摄像头、采集卡,走v4l2src
  • 文件源:本地视频文件,走filesrc
  • 测试源:纯软件生成的测试画面,走videotestsrc

嗯,这里要注意:不同类型的源,启动方式差别很大。硬件源需要等待设备就绪,文件源需要预加载,测试源则几乎零延迟。

4.2 常见的视频源详解

4.2.1 v4l2src:摄像头和采集卡

v4l2src是Linux下最常用的视频采集源。它基于Video4Linux2框架,支持USB摄像头、HDMI采集卡、MIPI摄像头等。

我在项目中遇到过一个问题:某款工业相机用v4l2src死活打不开,后来发现是设备节点权限没配好。嗯,这个坑我踩过,后面会讲。

基本用法:

gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! autovideosink

关键参数:

参数 说明 我的建议
device 设备节点路径,默认/dev/video0 多路时务必显式指定
num-buffers 采集帧数,-1表示持续采集 测试时设个有限值,避免内存泄漏
io-mode I/O模式:0=mmap, 1=read, 2=userptr mmap性能最好,我常用
避坑指南:我曾经在树莓派上同时开4路USB摄像头,结果只有2路能正常工作。后来发现是USB带宽不够。解决办法:要么换USB3.0,要么降低分辨率。

4.2.2 filesrc:文件源

filesrc用于读取本地视频文件。它本身只负责读数据,不关心文件格式。格式解析需要搭配demuxer使用。

基本用法:

gst-launch-1.0 filesrc location=test.mp4 ! qtdemux ! h264parse ! avdec_h264 ! videoconvert ! autovideosink

多路文件源时,我建议注意两点:

  • 磁盘I/O瓶颈:同时读多个大文件,磁盘可能会扛不住。SSD会好很多。
  • 文件预加载:用filesrc的do-timestamp属性,可以让文件流带上时间戳,方便后续同步。
小技巧:如果你需要循环播放文件,可以用multifilesrc配合loop=True。我在做数字标牌项目时经常这么用。

4.2.3 videotestsrc:测试源

videotestsrc是GStreamer自带的测试视频源。它能生成各种测试图案,比如彩条、雪花、棋盘格等。调试管道时特别好用。

基本用法:

gst-launch-1.0 videotestsrc pattern=0 ! videoconvert ! autovideosink

常用pattern值:

图案 适用场景
0 彩条(SMPTE) 色彩校准测试
1 随机雪花 噪声测试
2 棋盘格 对齐测试
18 圆形渐变 色彩过渡测试

我个人习惯用videotestsrc做多路测试。因为它不依赖硬件,启动快,而且可以精确控制帧率和分辨率。你想想看,调试同步逻辑时,用真实摄像头多麻烦?测试源一键搞定。

4.3 多路源的启动与停止控制

4.3.1 启动策略:顺序启动 vs 并行启动

多路源启动时,有两种策略:

  • 顺序启动:一路启动完成,再启动下一路。优点是稳定,缺点是慢。
  • 并行启动:所有路同时启动。优点是快,缺点是可能资源竞争。

我在项目中遇到过:并行启动4路4K摄像头,结果系统直接OOM(内存溢出)。后来改成顺序启动,每路间隔200ms,问题解决。

代码示例(Python + GStreamer):

import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib

Gst.init(None)

# 创建4路测试源
pipelines = []
for i in range(4):
    pipeline_str = f"videotestsrc pattern={i} ! videoconvert ! autovideosink"
    pipeline = Gst.parse_launch(pipeline_str)
    pipelines.append(pipeline)

# 顺序启动,每路间隔500ms
def start_pipeline(index):
    if index < len(pipelines):
        pipelines[index].set_state(Gst.State.PLAYING)
        print(f"第{index+1}路已启动")
        GLib.timeout_add(500, start_pipeline, index + 1)

start_pipeline(0)

# 启动主循环
loop = GLib.MainLoop()
loop.run()
核心要点:启动时务必检查每个pipeline的状态。我曾经因为一路启动失败,导致后续所有路都卡住。加个超时机制会安全很多。

4.3.2 停止控制:优雅退出

停止多路源时,最忌讳的就是直接kill进程。正确的做法是:

  1. 发送EOS(End of Stream)事件
  2. 等待管道状态变为NULL
  3. 释放资源

代码示例:

def stop_all_pipelines(pipelines):
    for i, pipeline in enumerate(pipelines):
        # 发送EOS事件
        pipeline.send_event(Gst.Event.new_eos())
        # 等待状态变更
        pipeline.get_state(Gst.CLOCK_TIME_NONE)
        # 设置为NULL状态
        pipeline.set_state(Gst.State.NULL)
        print(f"第{i+1}路已停止")
    print("所有路已停止")
避坑指南:我曾经在停止时忘记等待EOS,直接set_state(NULL)。结果导致文件源的数据没写完,下次启动时文件损坏。嗯,从那以后我再也不敢偷懒了。

4.3.3 动态增减源

实际项目中,经常需要动态添加或移除视频源。比如监控系统里,某个摄像头掉线了,需要自动移除并重新连接。

GStreamer里可以用GstBin的add/remove方法动态操作。但要注意:

  • 动态添加源时,需要重新协商caps
  • 动态移除源时,要处理好pending的buffer
  • 建议用pad的block信号来控制动态切换

我个人习惯的做法是:预先创建好所有源,但初始状态设为PAUSED。需要哪路,就把它切换到PLAYING。这样避免了动态创建/销毁的开销。

4.4 本章小结

多路视频源是GStreamer同步处理的基础。说白了,你得先能稳定地拿到多路数据,才能谈后面的同步、混合、编码。

记住三个关键点:

  • 源的类型决定启动方式:硬件源要等设备,文件源要预加载,测试源最省心
  • 启动要稳:顺序启动加间隔,避免资源竞争
  • 停止要优雅:EOS事件不能省,状态转换要完整

下一章,我们会深入讲解各路源的时钟同步问题。你想想看,如果两路视频的时钟不同步,画面会是什么样子?嗯,到时候你就知道了。