一、前言
我这里使用的是RK3588的主板,摄像头使用的型号是IMX415。
二、V4L2框架配置
1、首先是使用open命令打开设备,然后设置格式参数,代码如下:
int fd = open(device, O_RDWR);
if (fd < 0) {
perror("打开设备失败");
return -1;
}
// 设置格式:多平面 NV12(你也可以用 NV16、NV21)
struct v4l2_format fmt;
CLEAR(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix_mp.width = WIDTH;
fmt.fmt.pix_mp.height = HEIGHT;
fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;
fmt.fmt.pix_mp.num_planes = 2;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
perror("设置格式失败");
return -1;
}
对于如何设置上面参数,可以通过命令v4l2-ctl -d /dev/videoX --all 查询:
看输出中:
-
Type: Video Capture就是V4L2_BUF_TYPE_VIDEO_CAPTURE -
Type: Video Capture Multiplanar就是V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE -
Pixel Format: SRGGB10等类似字段 说明你是 RAW Sensor,输出 Bayer 数据
我这里是如下信息显示:

2、接下来就是数据缓冲区的设置,我这里使用mmp的方式进行内存的映射,这里需要主要的一点是,缓冲区的声明周期,最好是整个声明周期就在,假如被系统回收了,会发生内存问题导致程序崩溃,特别是在多线程间操作,代码如下:
// 请求缓冲区
struct v4l2_requestbuffers req;
CLEAR(req);
req.count = BUFFER_COUNT;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
perror("请求缓冲区失败");
return -1;
}
//映射缓冲区
for (int i = 0; i < BUFFER_COUNT; ++i) {
struct v4l2_buffer buf;
struct v4l2_plane planes[1];
CLEAR(buf);
memset(planes, 0, sizeof(planes));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
buf.length = 1;
buf.m.planes = planes;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
perror("查询缓冲区失败");
return -1;
}
req_buffers[i].length = buf.m.planes[0].length;
req_buffers[i].start = mmap(NULL, buf.m.planes[0].length,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd, buf.m.planes[0].m.mem_offset);
}
// 放入队列
for (int i = 0; i < BUFFER_COUNT; ++i) {
struct v4l2_buffer buf;
struct v4l2_plane planes[1];
CLEAR(buf);
memset(planes, 0, sizeof(planes));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
buf.length = 1;
buf.m.planes = planes;
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("放入缓冲区失败");
return -1;
}
}
3、接下来就是视频流的获取了,我这边对于原始数据使用的是opencv进行的处理,方便显示到屏幕进行测试,其实更原始的操作,可以用/dev/fb* 直接操作显存地址进行显示,效率更高,我这边只做测试,最终我们需要完成对视频流的编码,然后进行推流的
// 启动视频流
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
perror("启动失败");
return ;
}
// 主循环采集并显示
try{
while (true) {
//等待视频设备(fd)变得可读(即有新的图像帧可获取)
fd_set fds;
FD_ZERO(&fds);// 初始化 fd 集合
FD_SET(fd, &fds); // 将视频设备 fd 加入监控集合
struct timeval tv = {2, 0};// 设置超时时间:2 秒
int r = select(fd + 1, &fds, NULL, NULL, &tv);
/*r > 0:表示设备可读(即有视频帧到来),你可以安全地调用 VIDIOC_DQBUF 来取出一帧
r == 0:表示超时(2 秒内没有帧来)
r == -1:系统调用失败,比如信号中断或其他错误(需要 perror() 查看)*/
if (r == -1) {
perror("select");
break;
}
struct v4l2_buffer buf;
struct v4l2_plane planes[1];
CLEAR(buf);
memset(planes, 0, sizeof(planes));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.length = 1;
buf.m.planes = planes;
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
perror("出队失败");
break;
}
if (buffers[buf.index].start == MAP_FAILED) {
std::cerr << "[producer] mmap 失败" << std::endl;
break;
}
if (buf.index >= BUFFER_COUNT || !buffers[buf.index].start) {
std::cerr << "[producer] 非法的 buf.index 或空指针 index:" <<buf.index <<std::endl;
continue;
}
// 将 NV12 数据转换为 BGR(Y 平面 + UV 平面)
cv::Mat y(HEIGHT, WIDTH, CV_8UC1, buffers[buf.index].start);
cv::Mat uv(HEIGHT / 2, WIDTH / 2, CV_8UC2,
(unsigned char*)buffers[buf.index].start + WIDTH * HEIGHT);
cv::Mat bgr;
cv::cvtColorTwoPlane(y, uv, bgr, cv::COLOR_YUV2BGR_NV12);
consumer_queue.push(bgr);
Video_frame_context temp_Data={0};
memcpy(temp_Data.frame_data, buffers[buf.index].start,WIDTH * HEIGHT*3/2);
consumer_queue.push_cam_data(temp_Data);
// 放回队列
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("重新入队失败");
break;
}
}
}
catch(cv::Exception& e){
std::cerr << "[producer] 异常: " << e.what() << std::endl;
}catch (...) {
std::cerr << "[producer] 未知异常!" << std::endl;
}
ioctl(fd, VIDIOC_STREAMOFF, &type);
for (int i = 0; i < BUFFER_COUNT; ++i) {
munmap(buffers[i].start, buffers[i].length);
}
三、运行结果
验证效果如下:画面测试目测没有感受到延迟。

下一节,我将获取的的视频通过mpp组件压缩为H265编码格式,然后分别保存到本地和使用之前我发布的zlmediakit组件进行RTSP推流。


421

被折叠的 条评论
为什么被折叠?



