文章目录
- 一、V4L2应用开发
- 1、识别摄像头
- 2、查看摄像头设备的能力
- 3、查看支持视频格式
- 4、设置视频格式
- 5、申请帧缓冲
- 6、启动采集
- 7、出队取一帧图像
- 8、入队归还帧缓冲
- 9、停止视频采集
- 10、退出释放资源
- 二、V4L2框架源码分析
- 1、struct video_device
- 2、struct v4l2_device *v4l2_dev
- 3、struct v4l2_subdev
- 4、V4L2框架
- 三、基于NVIDIA平台的CSI摄像头驱动源码分析
- 1、tegracam_device平台设备
- 2、定义imx219摄像头设备类
- 3、设备树匹配检测
- 4、探测函数probe初始化
- 5、基于英伟达平台摄像头的设备框架图
一、V4L2应用开发
1、识别摄像头
(1)开发过程中的第一步要先确定硬件是否正常工作,方便进行后续开发。
插入USB摄像头,查看摄像头是否生成,注意会生成2个设备,其中一个可以捕获图像。至于为什么会有2个,自行百度了解。
ls -l /dev/video*
(2)查看USB摄像头的设备ID
lsusb
(3)通过ID号 查看设备厂商等信息
sudo cat /sys/kernel/debug/usb/devices |grep 1908 -A 5
(4)下载v4l2-utils工具,v4l2-utils 是一个包含一系列与 Video4Linux2 (V4L2) 框架相关的实用程序和库的集合。它们的作用是帮助开发者、系统管理员和用户进行 V4L2 设备的管理、测试、配置和诊断。
sudo apt install v4l2-utils
主要用到的是v4l2-ctl指令,可以查看指令帮助集
v4l2-ctl -h
通过v4l2工具,查看摄像头 参数
v4l2-ctl -d /dev/video0 --all
(5)安装guvcview
工具,进行视频显示,实现件监控效果
sudo apt-get install guvcview
开始视频显示,设备节点是video0 还是video1需要都尝试下,错误的节点会直接报弹窗报错
guvcview -d /dev/video1
弹窗报错图如下,需要重新切换正确节点
视频采集出现如下错误
,解决方法
V4L2_CORE: Could not grab image (select timeout): Resource temporarily unavailable
虚拟机–>设置–>usb控制器—>usb兼容性,选择3.0以上即可
显示结果如下
视频显示工具2,可自行安装测试
sudo apt-get install cheese
cheese -d /dev/video1
2、查看摄像头设备的能力
现给出部分源码,后续会给出整个源码
结构体:struct v4l2_capability
内核源码路径include\uapi\linux\videodev2.h
/* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */
#define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */
#define V4L2_CAP_READWRITE 0x01000000 /* read/write systemcalls */
/*查看 摄像头设备的能力*/
int get_capability(int fd){int ret=0;struct v4l2_capability cap; memset(&cap, 0, sizeof(struct v4l2_capability)); ret = ioctl(fd, VIDIOC_QUERYCAP, &cap); /*查看设备能力信息*/if (ret < 0) {printf("VIDIOC_QUERYCAP failed (%d)\n", ret);return ret;}printf("Driver Info: \n Driver Name:%s \n Card Name:%s \n Bus info:%s \n",cap.driver,cap.card,cap.bus_info);printf("Device capabilities: \n"); if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) { /*支持视频捕获(截取一帧图像保存)*/printf(" support video capture \n");}if (cap.capabilities & V4L2_CAP_STREAMING) { /*支持视频流操作(mmap映射到同一缓冲区队列后的入队出队 即流入流出)*/printf(" support streaming i/o\n");}if(cap.capabilities & V4L2_CAP_READWRITE) { /*支持读写(需内核到应用空间拷贝 慢)*/printf(" support read i/o\n");}return ret;
}
编译后输出,如果需要放到开发板上,需使用交叉编译工具链
3、查看支持视频格式
结构体:struct v4l2_fmtdesc
int get_suppurt_video_format(int fd){int ret=0;struct v4l2_fmtdesc fmtdesc;printf("List device support video format: \n");memset(&fmtdesc, 0, sizeof(fmtdesc));fmtdesc.index = 0;fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;while ((ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) == 0) /*枚举出支持的视频格式*/{fmtdesc.index++;printf(" { pixelformat = ''%c%c%c%c'', description = ''%s'' }\n",fmtdesc.pixelformat & 0xFF, (fmtdesc.pixelformat >> 8) & 0xFF, (fmtdesc.pixelformat >> 16) & 0xFF, (fmtdesc.pixelformat >> 24) & 0xFF, fmtdesc.description);} return ret;
}
4、设置视频格式
结构体:v4l2_format
、v4l2_pix_format
#define VIDEO_WIDTH 320 //采集图像的宽度
#define VIDEO_HEIGHT 240 //采集图像的高度 nt set_video_format(int fd){ int ret = 0;struct v4l2_format fmt;memset(&fmt, 0, sizeof(fmt));fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;fmt.fmt.pix.width = VIDEO_WIDTH; fmt.fmt.pix.height = VIDEO_HEIGHT;fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; /*支持mipg的摄像头最好*//*普通摄像头会默认设置V4L2_PIX_FMT_YUYV格式 要用到jpeg库*/fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; /*视频帧的扫描方式*//*设置视频格式 */ret = ioctl(fd, VIDIOC_S_FMT, &fmt);if (ret < 0) {printf("VIDIOC_S_FMT failed (%d)\n", ret);return ret;}/*获取视频格式*/ ret = ioctl(fd, VIDIOC_G_FMT, &fmt);if (ret < 0) {printf("VIDIOC_G_FMT failed (%d)\n", ret);return ret;}printf("Stream Format Informations:\n");printf(" type: %d\n", fmt.type);printf(" width: %d\n", fmt.fmt.pix.width);printf(" height: %d\n", fmt.fmt.pix.height);char fmtstr[8];memset(fmtstr, 0, 8);memcpy(fmtstr, &fmt.fmt.pix.pixelformat, 4);printf(" pixelformat: %s\n", fmtstr);printf(" field: %d\n", fmt.fmt.pix.field);printf(" bytesperline: %d\n", fmt.fmt.pix.bytesperline);printf(" sizeimage: %d\n", fmt.fmt.pix.sizeimage);return ret;
}
5、申请帧缓冲
结构体:v4l2_requestbuffers
#define REQBUFS_COUNT 4 /*缓存区个数*/
struct v4l2_requestbuffers reqbufs; /*定义缓冲区*/
struct cam_buf {void *start;size_t length;
};
struct cam_buf bufs[REQBUFS_COUNT]; /*映射后指向的同一片帧缓冲区*/int request_buf(int fd){int ret=0;int i;struct v4l2_buffer vbuf;memset(&reqbufs, 0, sizeof(struct v4l2_requestbuffers));reqbufs.count = REQBUFS_COUNT; /*缓存区个数*/reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbufs.memory = V4L2_MEMORY_MMAP; /*设置操作申请缓存的方式:映射 MMAP*/ret = ioctl(fd, VIDIOC_REQBUFS, &reqbufs); /*向驱动申请缓存 */if (ret == -1) { printf("VIDIOC_REQBUFS fail %s %d\n",__FUNCTION__,__LINE__);return ret;}/*循环映射并入队 -> 让内核 和 应用的虚拟地址空间 指向同一片物理内存*/for (i = 0; i < reqbufs.count; i++){/*真正获取缓存的地址大小 注:你申请的多少个不一定返回那么多,原理需知内核底层代码,后续会有讲解*/memset(&vbuf, 0, sizeof(struct v4l2_buffer));vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;vbuf.memory = V4L2_MEMORY_MMAP;vbuf.index = i;/*获取视频缓冲区的详细信息*/ret = ioctl(fd, VIDIOC_QUERYBUF, &vbuf);if (ret == -1) {printf("VIDIOC_QUERYBUF fail %s %d\n",__FUNCTION__,__LINE__);return ret;}/*映射缓存到用户空间,通过mmap讲内核的缓存地址映射到用户空间,并且和文件描述符fd相关联*/bufs[i].length = vbuf.length;bufs[i].start = mmap(NULL, vbuf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, vbuf.m.offset);if (bufs[i].start == MAP_FAILED) {printf("mmap fail %s %d\n",__FUNCTION__,__LINE__);return ret;}/*每次映射都会入队,放入缓冲队列*/vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;vbuf.memory = V4L2_MEMORY_MMAP;ret = ioctl(fd, VIDIOC_QBUF, &vbuf);if (ret == -1) {printf("VIDIOC_QBUF err %s %d\n",__FUNCTION__,__LINE__);return ret;}}return ret;
}
6、启动采集
int start_camera(int fd)
{int ret;enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_STREAMON, &type); /*ioctl控制摄像头开始采集*/if (ret == -1) {perror("start_camera");return -1;}fprintf(stdout, "camera->start: start capture\n");return 0;
}
7、出队取一帧图像
int camera_dqbuf(int fd, void **buf, unsigned int *size, unsigned int *index){int ret=0;struct v4l2_buffer vbuf;vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;vbuf.memory = V4L2_MEMORY_MMAP;ret = ioctl(fd, VIDIOC_DQBUF, &vbuf); /*出队,也就是从设备中取出图片*/if (ret == -1) {perror("camera dqbuf ");return -1;} *buf = bufs[vbuf.index].start;*size = vbuf.bytesused;*index = vbuf.index; return ret;
}
8、入队归还帧缓冲
int camera_eqbuf(int fd, unsigned int index)
{int ret;struct v4l2_buffer vbuf;vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;vbuf.memory = V4L2_MEMORY_MMAP;vbuf.index = index;ret = ioctl(fd, VIDIOC_QBUF, &vbuf); /*入队*/if (ret == -1) {perror("camera->eqbuf");return -1;}return 0;
}
9、停止视频采集
int camera_stop(int fd)
{int ret;enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_STREAMOFF, &type);if (ret == -1) {perror("camera->stop");return -1;}fprintf(stdout, "camera->stop: stop capture\n");return 0;
}
10、退出释放资源
int camera_exit(int fd)
{int i;int ret=0;struct v4l2_buffer vbuf;vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;vbuf.memory = V4L2_MEMORY_MMAP;/*出队所有帧缓冲*/for (i = 0; i < reqbufs.count; i++) {ret = ioctl(fd, VIDIOC_DQBUF, &vbuf);if (ret == -1)break;}/*取消所有帧缓冲映射*/for (i = 0; i < reqbufs.count; i++)munmap(bufs[i].start, bufs[i].length);fprintf(stdout, "camera->exit: camera exit\n");return ret;
}
后续会详解ioctl()函数如何调用底层,完成上述一系列操作。
二、V4L2框架源码分析
作用:管理V4L2设备,向应用暴露控制接口。
内核源码:include/media/v4l2-dev.h
1、struct video_device
主要需要知道的是这几个结构体。
const struct v4l2_file_operations *fops;
const struct v4l2_ioctl_ops *ioctl_ops;
struct v4l2_device *v4l2_dev;
struct vb2_queue *queue;
2、struct v4l2_device *v4l2_dev
总之,v4l2_device 表示整个 V4L2 子系统的顶层对象,用于管理和操作 V4L2 子系统,而 video_device 表示单个视频设备节点,用于表示和操作具体的视频设备实例。
3、struct v4l2_subdev
V4l2的子设备提供函数,可由底层驱动进行注册勾连实现。这是Linux驱动内核源码经常有这种。
**
**某一底层驱动实现:
4、V4L2框架
三、基于NVIDIA平台的CSI摄像头驱动源码分析
摄像头框架最难的在于每个公司的平台不一样,华为海思有自己独立编写的摄像头框架平台,瑞芯微也有自己独立的摄像头框架平台,每个产家都不同,只是编写出来的摄像头框架平台最后都会勾连到上层内核V4L2框架
,提供方案给底层驱动
实现具体的函数。
1、tegracam_device平台设备
(1)定义的基于英伟达摄像头设备的类
(2)对应的传感器操作函数
(3)对应的控制函数
(4)寄存器配置函数
总结:而imx219.c的驱动程序会基于tegracam_device框架进行选配实现一系列的操作函数
2、定义imx219摄像头设备类
以imx219.c源码为例