Dec 02, 2025
3 min read
Rust,

折腾记:如何在 Rust 中获取采集卡视频流并播放

所有操作在 linux 平台上。

来源于一个小需求,为了能和电脑共用屏幕,在电脑上播放来着游戏机的游戏画面,我一般是借助 obs 软件来获取采集卡的视频流。当然,这也是游戏主播直接推流到直播平台的一种方式。

linux 平台上一般使用 v4l2-ctl 获取视频流,包括摄像头设备和采集卡设备。

比如列出所有可用的设备:

 v4l2-ctl --list-devices             
Hagibis: Hagibis (usb-0000:0e:00.0-2):
        /dev/video0
        /dev/video1
        /dev/media0

可以使用下面的命令获取一些信息:

v4l2-ctl --device=/dev/video0 --info

列出设备支持的格式:

v4l2-ctl --device=/dev/video0 --list-formats-ext

我的采集卡设备是海备思,支持 MJPG 和 YUYV 格式。可以使用 ffplay 来播放看看:

ffplay -f v4l2 -input_format yuyv422 -video_size 1280x720 -i /dev/video0

其实 ffplay 就已经能满足了我的功能基本需求了。如果自己实现播放效果的话,可以使用 GStreamer,有官方的 rust 绑定。

GStreamer 是一个跨平台的媒体处理框架,它提供了一套完整的 API,用于处理音频、视频、图像等。

GStreamer 是基于 pipeline 来实现整个媒体处理流程的。比如最简单的一个播放 mp4 文件的例子:

{filesrc} - {videoconvert} - {autovideosink}

回到需要,我们需要获取采集卡的视频流,并播放出来,大概的流程如下:

{v4l2src} - {videoconvert} - {autovideosink}

更精细复杂的可能是:

{v4l2src} - {capsfilter} - {videoconvert} - {autovideosink}

v4l2src

从采集卡/dev/video0获取视频流,使用 v4l2src 。

let source = gst::ElementFactory::make("v4l2src")
        .property("device", "/dev/video0")
        .property("do-timestamp", true)
        .build()
        .expect("Failed to create v4l2src");

capsfilter

可能我们需要指定设备的输出格式、分辨率、帧率等信息,可以使用 capsfilter

let input_caps_str = "video/x-raw,format=YUY2,width=640,height=480,framerate=30/1";
let input_caps = gst::Caps::from_str(input_caps_str).expect("Failed to parse input caps");

let input_caps_filter = gst::ElementFactory::make("capsfilter")
    .property("caps", input_caps)
    .build()
    .expect("Failed to create input capsfilter");

如果你指定的是MJPG格式,那么你需要使用jpegdec解码器,管道流程可能就变成这样了:

{v4l2src} - {capsfilter} - {jpegdec} - {videoconvert} - {autovideosink}
let decoder = gst::ElementFactory::make("jpegdec").build().unwrap();

但是,是否能指定输出格式,还得看硬件的支持。以及是否允许切换。

caps 是否配置正确可以使用gst-launch-1.0 来测试,比如我测试我的采集卡:

gst-launch-1.0 v4l2src device=/dev/video0 ! \
                       video/x-raw,format=YUY2,width=640,height=480,framerate=30/1 ! \
                       videoconvert ! \
                       appsink name=video_sink
设置暂停管道 ...
管道正在使用且不需要 PREROLL ...
管道被 PREROLLED ...
设置播放管道 ...
New clock: GstSystemClock
重新分配延迟时间...
0:01:08.9 / 99:99:99.

如果你的测试参数有问题,会有一些错误,比如下面的配置错误的,不受支持的分辨率和帧率,会返回如下错误:

 gst-launch-1.0 v4l2src device=/dev/video0 ! \
                       video/x-raw,format=YUY2,width=320,height=240,framerate=15/1 ! \
                       videoconvert ! \
                       appsink name=video_sink
设置暂停管道 ...
管道正在使用且不需要 PREROLL ...
管道被 PREROLLED ...
设置播放管道 ...
New clock: GstSystemClock
错误:来自组件 /GstPipeline:pipeline0/GstV4l2Src:v4l2src0:Internal data stream error.
额外的调试信息:
../libs/gst/base/gstbasesrc.c(3177): gst_base_src_loop (): /GstPipeline:pipeline0/GstV4l2Src:v4l2src0:
streaming stopped, reason not-negotiated (-4)
Execution ended after 0:00:00.000457309
设置 NULL 管道 ...
释放管道资源 ...

使用 lib 也可以解析 gst-launch-1.0 的参数:

let pipeline = gst::parse::launch(
        "v4l2src device=/dev/video0 ! \
                       video/x-raw,format=YUY2,width=640,height=480,framerate=30/1 ! \
                       videoconvert ! \
                       appsink name=video_sink",
    )
    .unwrap();

转换器

videoconvert 是最基础的转换器,它默认使用的是CPU,这也是最简单支持最好的转换器。它的缺点就是: 慢。

let conv = gst::ElementFactory::make("videoconvert")
            .build()
            .expect("Failed to create videoconvert");

如果有可能,最好使用有 GPU 加速的转换器,比如 vaapipostproc。 由于gstreamer 是基于pipeline 来实现流水线的,流水线里的元素被称为Element,我们可以通过下面的方式来查看当前电脑支持的所有 element:

gst-inspect-1.0  --print-plugin-auto-install-info |grep 'element-*'

vaapipostproc 是基于 VAAPI 的转换器,VAAPI 是 Intel 的硬件加速接口。我的硬件是 AMD CPU

  • Nvidia GPU 组合。经过测试,在我的电脑上 使用vaapipostproc可以稳定实现 640x480 fps 30 的播放,但是无法做到 fps 60 的播放。

不过, Gstreamer 是有基于 CUDA 的 Element 的:

element-nvcudah264enc
element-nvcudah265enc
element-cudaupload
element-cudadownload
element-cudaconvert
element-cudascale
element-cudaconvertscale
element-cudaipcsink
element-cudaipcsrc

真有那么顺利吗?

Linux 平台总体而言,就是不稳定,无论是硬件还是软件。我的电脑 GPU 硬件有时候在断电情况下启动会偶发地无法加载进来,这时候是无法调用CUDA的。

vaapipostproc 是一个神奇的模块,只有最开始调用的时候它是正常的。后面不知道是什么原因,再也无法成功调用,GStreamer 显示未挂载,但是 vaapi 相关模块正常。

/dev/video0 只能被一个进程占用,如果开发测试过程中出现进程有残留,需要使用 sudo fuser -v /dev/video0 命令查看进程,并使用 sudo kill -9 <PID> 命令杀死进程。

事实上,哪怕 obs 这样的高质量软件,在 linux 平台上一样会出各种各样的问题导致软件无法获取采集卡的视频流(黑屏)。

没有硬件加速的话,显示采集卡的内容很慢,并且毫无意义。