基于 FFmpeg 与 V4L2 的多路摄像头视频采集,图像处理处理与 RTMP 推流项目(开源)
在直播、远程监控、视频会议等场景中,实时视频推流是核心需求之一。本文将分享一个基于 Linux 平台的解决方案,通过 V4L2 采集摄像头数据,结合 OpenCV 进行图像处理,最终使用 FFmpeg 编码并通过 RTMP 协议推流至服务器。项目代码模块化程度高,可扩展性强。
(本项目已完成pc平台及rk3566平台测试。)
项目代码已开源,链接:https://github.com/OHMYGODJONY/MultiCam-Realtime-Capture-and-Streaming.
对单个视频流灰度转换后效果:
扩展目标检测算法后检测效果:
一、项目背景与技术选型
1.1 需求场景
- 实时采集摄像头视频数据(支持多设备)
- 对视频帧进行自定义处理(如美颜、目标检测)
- 将处理后的视频编码为 H.264 并通过 RTMP 推流
1.2 技术栈选择
- V4L2:Linux 下标准的视频设备接口,直接操作硬件设备,支持 DMA 零拷贝,效率高于普通 USB 摄像头库。
v4l2教程:V4L2 使用教程_v4l2-compliance-CSDN博客
- FFmpeg:强大的音视频处理库,提供编码、格式转换、协议推流等一站式功能。
- 多线程 + 线程安全队列:实现采集、处理、推流的异步解耦,提升系统吞吐量。
二、整体架构设计
项目采用模块化设计,分为 4 个核心模块,数据流向清晰:
摄像头设备 → CameraCapture(采集) → ThreadSafeQueue(数据传递) → EncoderStreamer(编码推流)↑ImageProcessor(图像处理)
- 采集模块(CameraCapture):基于 V4L2 实现摄像头初始化、参数配置、帧数据捕获。
- 处理模块接口(ImageProcessor):提供可扩展的图像处理接口(提供BGR24DE cv::Mat格式数据),支持自定义算法。
- 编码推流模块(EncoderStreamer):通过 FFmpeg 将视频编码为 H.264 并推流至 RTMP 服务器。
- 线程安全队列(ThreadSafeQueue):实现采集线程与编码线程的解耦,避免阻塞。
三、开发环境与依赖安装
3.1 环境要求
- 操作系统:Ubuntu 20.04+
- 依赖库:
- FFmpeg(libavformat、libavcodec、libswscale 等)
- V4L2 开发库(libv4l-dev)
- OpenCV(libopencv-dev)
- pthread(线程库)
# 安装FFmpeg
sudo apt-get install libavutil-dev libavcodec-dev libavformat-dev libswscale-dev# 安装V4L2
sudo apt-get install libv4l-dev v4l-utils# 安装OpenCV
sudo apt-get install libopencv-dev# 安装线程库(通常系统已预装)
sudo apt-get install libpthread-stubs0-dev
四、核心模块实现详解
4.1 摄像头采集模块(CameraCapture)
基于 V4L2 实现,核心是通过 IOCTL 命令与设备交互,流程如下:
-
打开设备与检查能力
通过
open()
打开摄像头设备(如/dev/video0
),使用VIDIOC_QUERYCAP
查询设备是否支持视频捕获和流式 IO。
// 关键代码示例:打开设备并检查能力
fd_ = open(device_path_.c_str(), O_RDWR | O_NONBLOCK, 0);
v4l2_capability cap;
IOCTL_RETRY(fd_, VIDIOC_QUERYCAP, &cap);
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {// 设备不支持视频捕获
}
-
设置采集参数
通过
VIDIOC_S_FMT
设置分辨率、像素格式(默认 YUYV422),通过VIDIOC_S_PARM
设置帧率。 -
缓冲区管理
申请 4 个 DMA 缓冲区(
VIDIOC_REQBUFS
),通过mmap()
映射到用户空间,实现零拷贝数据访问。缓冲区通过VIDIOC_QBUF
(入队)和VIDIOC_DQBUF
(出队)循环使用。 -
帧采集线程
启动独立线程循环调用
get_frame()
,通过select()
等待缓冲区就绪,获取帧数据后通过回调函数推送至处理队列。
4.2 图像处理模块(ImageProcessor)
设计为可扩展接口,默认提供空实现,用户可通过继承重写processFrame()
实现自定义逻辑:
class ImageProcessor {
public:virtual void processFrame(cv::Mat& mat) {} // 基类空实现
};// 示例:自定义添加水印
class WatermarkProcessor : public ImageProcessor {void processFrame(cv::Mat& mat) override {cv::putText(mat, "Live Stream", cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2);}
};
数据流转:摄像头原始帧(YUYV422)→ 转换为 BGR24(OpenCV 格式)→ 封装成cv::Mat → 调用processFrame()
处理 → 转换回 YUV420P(编码所需格式)。
4.3 编码与 RTMP 推流模块(EncoderStreamer)
基于 FFmpeg 实现,核心流程:
-
初始化 FFmpeg
- 调用
avformat_network_init()
初始化网络模块。 - 创建输出格式上下文(
AVFormatContext
),指定 RTMP 地址和格式(flv)。 - 查找 H.264 编码器(
avcodec_find_encoder(AV_CODEC_ID_H264)
),配置编码器参数(比特率、帧率、GOP 大小等)。
- 调用
-
格式转换
使用sws_scale()
将图像处理后的 BGR24 格式转换为编码器所需的 YUV420P 格式。 -
编码与推流
- 将转换后的帧通过
avcodec_send_frame()
送入编码器。 - 通过
avcodec_receive_packet()
获取编码后的数据包(AVPacket)。 - 调整时间戳(PTS)后,通过
av_interleaved_write_frame()
推流至 RTMP 服务器。
- 将转换后的帧通过
-
关键参数优化
- 编码器参数:
preset=ultrafast
(快速编码,牺牲部分压缩率)、crf=23
(质量控制)。 - 推流参数:
max_delay=0
(减少延迟)、stimeout=2000000
(2 秒超时)。
- 编码器参数:
4.4 线程安全队列(ThreadSafeQueue)
基于std::mutex
和std::condition_variable
实现,支持超时机制,用于采集线程与编码线程的异步数据传递:
// 入队(生产者)
bool push(const T& item, int timeout_ms = -1);// 出队(消费者)
bool pop(T& item, int timeout_ms = 50);
通过队列解耦,避免采集线程因编码阻塞而丢帧,同时防止编码线程因无数据而空转。
五、常见问题与解决方案
-
摄像头无法打开
- 检查设备权限:
sudo chmod 666 /dev/video0
。 - 确认设备存在:
v4l2-ctl --list-devices
。
- 检查设备权限:
-
格式不支持
- 用
v4l2-ctl --list-formats-ext
查询设备支持的分辨率和格式,调整初始化参数。
- 用
-
推流延迟高
- 减少
max_delay
,降低编码器preset
等级(如ultrafast
)。 - 缩小队列大小,避免帧堆积。
- 减少
-
编码失败
- 检查 FFmpeg 是否编译了 H.264 编码器:
ffmpeg -encoders | grep h264
。 - 确保输入帧格式为 YUV420P(编码器要求)。
- 检查 FFmpeg 是否编译了 H.264 编码器:
总结
本文实现了从摄像头采集到 RTMP 推流的完整链路,核心在于通过模块化设计和多线程异步处理提升系统效率。V4L2 保证了采集性能,FFmpeg 简化了编码推流流程,而可扩展的图像处理接口为业务定制提供了便利。