所有操作在 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 平台上一样会出各种各样的问题导致软件无法获取采集卡的视频流(黑屏)。
没有硬件加速的话,显示采集卡的内容很慢,并且毫无意义。