V4L2 (video For Linux Two) 是 linux 内核提供给应用程序访问音、视频驱动的统一接口。其相关定义包含在头文件中。
工作流程
- 打开设备
- 检查设备属性
- 设置帧格式
- 设置输入输出方法
- 循环获取数据
- 关闭设备
设备打开和关闭
相关接口和数据结构:
- 接口:
#include<fcntl.h> #include<unistd.h> // 打开 int open(constchar*device_name, int flags); // 关闭 int close(fd);
Step1: 打开设备
/*Step1: open /dev/video0 :uvc 摄像头 插入后自动生成 /dev/video0*/ int fd = -1; fd = open(dev_name, O_RDWR | O_NONBLOCK, 0); if(fd < 0) { printf("ERROR(%s): can not open %s \n", MODULE_TAG, dev_name); return -1; }
查询设备属性:VIDIOC_QUERYCAP
相关接口和数据结构:
- 接口:
int ioctl(int fd, int request, struct v4l2_capability *argp);
- 数据结构
struct v4l2_capability { u8 driver[16]; // 驱动名字 u8 card[32]; // 设备名字 u8 bus_info[32]; // 设备在系统中的位置 u32 version; // 驱动版本号 u32 capabilities; // 设备支持的操作.常用值V4L2_CAP_VIDEO_CAPTURE:是否支持图像获取 u32 reserved[4]; // 保留字段 };
Step2: get capabilities
int ret = -1; struct v4l2_capability cap; memset(&cap, 0, sizeof(cap)); ret = ioctl(fd, VIDIOC_QUERYCAP, &cap); if( ret < 0) { printf("ERROR(%s): ioctl VIDIOC_QUERYCAP error \n", MODULE_TAG); return -1; } else{ // Print capability infomations printf("Capability Informations:\n"); printf("\tdriver: %s\n", cap.driver); printf("\tcard: %s\n", cap.card); printf("\tbus_info: %s\n", cap.bus_info); printf("\tversion: %08X\n", cap.version); printf("\tcapabilities: %08X\n", cap.capabilities); }
查询所有支持的格式:VIDIOC_ENUM_FMT
相关接口和数据结构:
- 接口
int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);
- 数据结构
struct v4l2_fmtdesc { u32 index; // 要查询的格式序号,应用程序设置 enum v4l2_buf_type type; // 帧类型,应用程序设置 u32 flags; // 是否为压缩格式 u8 description[32]; // 格式名称:YUYV/MJPEG u32 pixelformat; // 格式 u32 reserved[4]; // 保留 };
Step3: get support fmt
struct v4l2_fmtdesc fmtdest; memset(&fmtdest, 0, sizeof(fmtdest)); fmtdest.index = 0; fmtdest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; printf("\nSupport FMT: \n"); while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdest) != -1) { printf("\t%d.%s\n", fmtdest.index + 1, fmtdest.description); fmtdest.index++; }
查询或设置当前帧信息:
VIDIOC_G_FMT/VIDIOC_S_FMT:
相关接口和数据结构:
- 接口:
int ioctl(int fd, int request, struct v4l2_format *argp);
- 数据结构:
struct v4l2_format { enum v4l2_buf_type type; // 帧类型,应用程序设置 union fmt { struct v4l2_pix_format pix; // 视频设备使用 struct v4l2_window win; struct v4l2_vbi_format vbi; struct v4l2_sliced_vbi_format sliced; u8 raw_data[200]; }; }; struct v4l2_pix_format { u32 width; // 帧宽,单位像素 u32 height; // 帧高,单位像素 u32 pixelformat; // 帧格式 enum v4l2_field field; u32 bytesperline; u32 sizeimage; enum v4l2_colorspace colorspace; u32 priv; };
Step4: set fmt:
int ret = -1; struct v4l2_format fmt; CLEAR(fmt); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = FRAME_WIDTH; fmt.fmt.pix.height = FRAME_HEIGHT; #ifdef CAPTURE_YUV fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; #else fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; #endif /* fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; */ fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; ret = ioctl(fd, VIDIOC_S_FMT, &fmt); if( ret < 0) { printf("ERROR(%s): ioctl VIDIOC_S_FMT error \n", MODULE_TAG); return -1; }
申请和管理缓冲区
相关接口和数据结构
- 接口:
// 向设备申请缓冲区:VIDIOC_REQBUFS int ioctl(int fd, int request, struct v4l2_requestbuffers *argp); // 获取缓冲区地址长度:VIDIOC_QUERYBUF int ioctl(int fd, int request, struct v4l2_buffer *argp); //内存映射 #include<sys/mman.h> void*mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void*addr, size_t length);// 断开映射
- 数据结构:
//1.1 struct v4l2_requestbuffers { u32 count; // 缓冲区内缓冲帧的数目 enum v4l2_buf_type type; // 缓冲帧数据格式 enum v4l2_memory memory; // 区别是内存映射还是用户指针方式.V4L2_MEMORY_MMAP, V4L2_MEMORY_USERPTR u32 reserved[2]; }; //1.2 struct v4l2_buffer { u32 index; //buffer 序号 enum v4l2_buf_type type; //buffer 类型 u32 byteused; //buffer 中已使用的字节数 u32 flags; // 区分是MMAP 还是USERPTR enum v4l2_field field; struct timeval timestamp; // 获取第一个字节时的系统时间 struct v4l2_timecode timecode; u32 sequence; // 队列中的序号 enum v4l2_memory memory; //IO 方式,被应用程序设置 union m { u32 offset; // 缓冲帧地址,只对MMAP 有效 unsignedlong userptr; }; u32 length; // 缓冲帧长度 u32 input; u32 reserved; }; //1.3 struct buffer { void* start; unsignedint length; }*buffers;
Step5: 申请缓冲
struct v4l2_requestbuffers req; CLEAR(req); req.count = 4;//1 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd, VIDIOC_REQBUFS, &req); // 申请4个缓冲帧 if( ret < 0) { printf("ERROR(%s): ioctl VIDIOC_REQBUFS error \n", MODULE_TAG); goto err_exit; } if(req.count < 1) { printf("Insufficient buffer memory\n"); } /* 将四个申请的缓冲帧映射到应用程序*/ uvc_ctx.buffers = (struct buffer*)calloc(req.count, sizeof(*(uvc_ctx.buffers))); struct v4l2_buffer buf; for(uvc_ctx.n_buffers = 0; uvc_ctx.n_buffers < req.count; ++uvc_ctx.n_buffers) { CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = uvc_ctx.n_buffers; /* 查询序号为n_buffers 的缓冲区, 得到起始物理地址和大小*/ if(ioctl(fd, VIDIOC_QUERYBUF, &buf)) { printf("ERROR(%s) : ioctl VIDIOC_QUERYBUF error\n", MODULE_TAG); goto err_free; } uvc_ctx.buffers[uvc_ctx.n_buffers].length = buf.length; /* 映射内存 */ uvc_ctx.buffers[uvc_ctx.n_buffers].start = mmap(NULL ,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED ,fd, buf.m.offset); if(MAP_FAILED == uvc_ctx.buffers[uvc_ctx.n_buffers].start) { printf("ERROR(%s): mmap failed\n", MODULE_TAG); goto err_free; } /* 将4个缓冲区放入队列 */ if(ioctl(fd, VIDIOC_QBUF, &buf) < 0) { printf("ERROR(%s) : ioctl VIDIOC_QBUF failed\n", MODULE_TAG); goto err_unmap; } }
开始获取数据流:
VIDIOCSTREAMON:启动 VIDIOCSTREAMOFF:停止
相关接口和数据结构:
- 接口:
// 在开始之前应把缓冲帧放入缓冲队列 //VIDIOC_QBUF:把帧放入队列 //VIDIOC_DQBUF:从队列中取出帧 int ioctl(int fd, int request, constint*argp);
Step6:开启视频流
enum v4l2_buf_type type; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if(ioctl(fd, VIDIOC_STREAMON, &type) < 0) { printf("ERROR(%s): ioctl VIDIOC_STREAMON failed\n", MODULE_TAG); goto err_unmap; }
帧处理
// 循环处理 while(!uvc_ctx.stop) { fd_set fds; struct timeval tv; int r; FD_ZERO (&fds); FD_SET (fd, &fds); tv.tv_sec = 2; tv.tv_usec = 0; r = select (fd + 1, &fds, NULL, NULL, &tv); if(-1== r) { if(EINTR == errno) { printf ("select err\n"); continue; } } if(0== r) { fprintf (stderr, "select timeout\n"); exit (EXIT_FAILURE); } /* 取出一帧 */ read_frame(uvc_ctx.fd); } //单帧处理 static int read_frame (int fd) { struct v4l2_buffer buf; CLEAR (buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; /* 取出一帧 : VIDIOC_DQBUF*/ int ret = ioctl(fd, VIDIOC_DQBUF, &buf); if( ret < 0) { printf("ERROR(%s): ioctl VIDIOC_DQBUF error \n", __func__); return -1; } assert (buf.index < uvc_ctx.n_buffers); #if 1 FILE*file_fd; #ifdef CAPTURE_YUV file_fd = fopen("frame.yuv", "w"); if(file_fd < 0) { printf("ERROR(%s): fopen frame.yuv error \n", __func__); return -1; } #else file_fd = fopen("frame.jpg", "w"); if(file_fd < 0) { printf("ERROR(%s): fopen frame.jpg error \n", __func__); return-1; } #endif fwrite(uvc_ctx.buffers[buf.index].start, uvc_ctx.buffers[buf.index].length, 1, file_fd); fclose(file_fd); #endif //save image or display_lcd usleep(500); ret = ioctl (fd, VIDIOC_QBUF, &buf); if( ret < 0) { printf("ERROR(%s)ioctl VIDIOC_QBUF\n", __func__); return -1; } return 0 ; }
总结
本文主要介绍了V4L2 应用编程基本流程,基本操作就是从usb 摄像头获取指定的视频帧 MPEG 或者 YUYV,保存成对应的图片。读者也可以根据需要,转换格式放到 lcd 屏幕或其他设备上显示。