V4L2应用程序开发(2)
参考资料:韦东山第三期
V4L2获取数据的操作流程如下:
- 打开设备
- ioctl VIDIOC_QUERYCAP:Query Capbility,查询能力
- 枚举格式、设置格式
- ioctl VIDIOC_REQBUFS:申请buffer
- ioctl VIDIOC_QUERYBUF和mmap:查询buffer信息、映射
- ioctl VIDIOC_QBUF:把buffer放入"空闲链表"
- ioctl VIDIOC_STREAMON:启动摄像头
- 这里是一个循环:使用poll/select监测buffer,然后从"完成链表"中取出buffer,处理后再放入"空闲链表"
- poll/select
- ioctl VIDIOC_DQBUF:从"完成链表"中取出buffer
- 处理:前面使用mmap映射了每个buffer的地址,把这个buffer的数据存为文件
- ioclt VIDIOC_QBUF:把buffer放入"空闲链表"
- ioctl VIDIOC_STREAMOFF:停止摄像头
获取数据demo如下:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <unistd.h> #include <sys/time.h> #include <stdint.h> #include <pthread.h> #include <linux/videodev2.h> int stop = 1; int* thread(void *arg) { if(getchar() == 'x') stop = 0; return NULL; } int main(int argc, char* argv[]) { //获取当前时间,将其保存到cur_time中去 struct timeval tv; gettimeofday(&tv,NULL); //获取1970-1-1到现在的时间结果保存到tv中 uint64_t sec=tv.tv_sec; uint64_t min=tv.tv_sec/60; struct tm cur_tm; //保存转换后的时间结果 localtime_r((time_t*)&sec,&cur_tm); char cur_time[24]; //cur_time为本次生成图片的名字 snprintf(cur_time,24,"%d%02d%02d-%02d:%02d:%02d.avi",cur_tm.tm_year+1900,cur_tm.tm_mon+1,cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min,cur_tm.tm_sec); int type; int pfd; pthread_t thid; pthread_create(&thid, NULL, (void *)thread, NULL); //1、打开设备 int fd = open("/dev/video0", O_RDWR); if(-1 == fd) { perror("打开摄像头失败"); } //2、获取采集格式 struct v4l2_fmtdesc v4fmt; v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; v4fmt.index = 0; int re = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt); if(re < 0) { perror("获取失败"); goto over; } printf("description=%s\n",v4fmt.description); unsigned char *p = (unsigned char *)&v4fmt.pixelformat; printf("pixelformat=%c%c%c%c\n",p[0],p[1],p[2],p[3]); //printf("reserved=%d\n",v4fmt.res将帧缓冲区出队erved[0]); //3、设置采集格式 struct v4l2_format vfmt; vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //摄像头采集功能 vfmt.fmt.pix.width = 640; //采集的长度 vfmt.fmt.pix.height = 480; //采集的宽度 vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; //设置视频采集格式 int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt); if(ret < 0) { perror("设置采集格式失败"); goto over; } /* memset(&vfmt, 0, sizeof(vfmt)); vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_G_FMT, &vfmt); if(ret < 0) { perror("获取失败格式"); } unsigned char *p = (unsigned char *)&vfmt.pixelformat; printf("pixelformat=%c%c%c%c\n",p[0],p[1],p[2],p[3]); */ //4、申请帧缓冲队列 struct v4l2_requestbuffers reqbuf; reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.count = 4; //申请4个缓冲区 reqbuf.memory = V4L2_MEMORY_MMAP; //映射方式为内存映射 ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuf); //申请buffer if(ret < 0) { perror("申请内存缓冲区失败"); goto over; } //5、映射缓存区到用户空间 unsigned char *mptr[4]; //保存映射后用户空间的首地址 unsigned int size[4]; struct v4l2_buffer mapbuf; mapbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; for(int i = 0; i < 4; i++) { mapbuf.index = i; ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuf); //从内核空间查询一个做映射 if(ret < 0) { perror("查询内核空间队列失败"); goto over; } mptr[i] = (unsigned char *)mmap(NULL, mapbuf.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mapbuf.m.offset); printf("offset size is %d\n",mapbuf.m.offset); size[i] = mapbuf.length; //释放映射的时候要知道其缓存区的大小 //通知内核映射完,buffer放到空闲链表 ret = ioctl(fd, VIDIOC_QBUF, &mapbuf); if(ret < 0) { perror("放回失败"); goto over; } } pfd = open(cur_time,O_WRONLY|O_CREAT|O_APPEND,0644); if(-1 == pfd) { perror("打开图片失败"); goto over; } //6、开始采集数据 type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_STREAMON, &type); if(ret < 0) { perror("开始失败"); goto over; } while(stop == 1) { //从队列中提取一帧数据 struct v4l2_buffer readbuf; readbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_DQBUF, &readbuf); if(ret < 0) { perror("读取数据失败"); goto over; } //将读取到的数据写入文件中 ret = write(pfd,mptr[readbuf.index], readbuf.length); printf("readbuf.index = %d\n",readbuf.index); if(-1 == ret) { perror("写入图片失败"); goto over; } //告诉内核采集完毕 ret = ioctl(fd, VIDIOC_QBUF, &readbuf); if(ret < 0) { perror("通知失败"); goto over; } usleep(30000); } //8、停止采集 ret = ioctl(fd, VIDIOC_STREAMOFF, &type); if(ret < 0) { perror("停止采集失败"); goto over; } // 9、做结束工作 释放关闭等 over: close(fd); close(pfd); for(int i = 0; i < 4; i++) { printf("%d\n",size[i]); munmap(mptr[i],size[i]); } return 0; }
亮度控制demo:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <linux/types.h> /* for videodev2.h */ #include <linux/videodev2.h> #include <poll.h> #include <sys/mman.h> #include <pthread.h> /* ./video_test </dev/video0> */ static void *thread_brightness_control (void *args) // 创建线程控制亮度,在./video_test /dev/video0的时候终端输入字符,控制亮度信息 { int fd = (int)args; unsigned char c; int brightness; int delta; struct v4l2_queryctrl qctrl; memset(&qctrl, 0, sizeof(qctrl)); qctrl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0; if (0 != ioctl(fd, VIDIOC_QUERYCTRL, &qctrl)) { printf("can not query brightness\n"); return NULL; } printf("brightness min = %d, max = %d\n", qctrl.minimum, qctrl.maximum); delta = (qctrl.maximum - qctrl.minimum) / 10; struct v4l2_control ctl; ctl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0; ioctl(fd, VIDIOC_G_CTRL, &ctl); while (1) { c = getchar(); if (c == 'u' || c == 'U') { ctl.value += delta; } else if (c == 'd' || c == 'D') { ctl.value -= delta; } if (ctl.value > qctrl.maximum) ctl.value = qctrl.maximum; if (ctl.value < qctrl.minimum) ctl.value = qctrl.minimum; ioctl(fd, VIDIOC_S_CTRL, &ctl); } return NULL; } int main(int argc, char **argv) { int fd; struct v4l2_fmtdesc fmtdesc; struct v4l2_frmsizeenum fsenum; int fmt_index = 0; int frame_index = 0; int i; void *bufs[32]; int buf_cnt; int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; struct pollfd fds[1]; char filename[32]; int file_cnt = 0; if (argc != 2) { printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]); return -1; } /* open */ fd = open(argv[1], O_RDWR); if (fd < 0) { printf("can not open %s\n", argv[1]); return -1; } /* 查询能力 */ struct v4l2_capability cap; memset(&cap, 0, sizeof(struct v4l2_capability)); if (0 == ioctl(fd, VIDIOC_QUERYCAP, &cap)) { if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) { fprintf(stderr, "Error opening device %s: video capture not supported.\n", argv[1]); return -1; } if(!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "%s does not support streaming i/o\n", argv[1]); return -1; } } else { printf("can not get capability\n"); return -1; } while (1) { /* 枚举格式 */ fmtdesc.index = fmt_index; // 比如从0开始 fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定type为"捕获" if (0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) break; frame_index = 0; while (1) { /* 枚举这种格式所支持的帧大小 */ memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum)); fsenum.pixel_format = fmtdesc.pixelformat; fsenum.index = frame_index; if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0) { printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, frame_index, fsenum.discrete.width, fsenum.discrete.height); } else { break; } frame_index++; } fmt_index++; } /* 设置格式 */ struct v4l2_format fmt; memset(&fmt, 0, sizeof(struct v4l2_format)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = 1024; fmt.fmt.pix.height = 768; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; fmt.fmt.pix.field = V4L2_FIELD_ANY; if (0 == ioctl(fd, VIDIOC_S_FMT, &fmt)) { printf("set format ok: %d x %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height); } else { printf("can not set format\n"); return -1; } /* * 申请buffer */ struct v4l2_requestbuffers rb; memset(&rb, 0, sizeof(struct v4l2_requestbuffers)); rb.count = 32; rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; rb.memory = V4L2_MEMORY_MMAP; if (0 == ioctl(fd, VIDIOC_REQBUFS, &rb)) { /* 申请成功后, mmap这些buffer */ buf_cnt = rb.count; for(i = 0; i < rb.count; i++) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (0 == ioctl(fd, VIDIOC_QUERYBUF, &buf)) { /* mmap */ bufs[i] = mmap(0 /* start anywhere */ , buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if(bufs[i] == MAP_FAILED) { perror("Unable to map buffer"); return -1; } } else { printf("can not query buffer\n"); return -1; } } printf("map %d buffers ok\n", buf_cnt); } else { printf("can not request buffers\n"); return -1; } /* 把所有buffer放入"空闲链表" */ for(i = 0; i < buf_cnt; ++i) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (0 != ioctl(fd, VIDIOC_QBUF, &buf)) { perror("Unable to queue buffer"); return -1; } } printf("queue buffers ok\n"); /* 启动摄像头 */ if (0 != ioctl(fd, VIDIOC_STREAMON, &type)) { perror("Unable to start capture"); return -1; } printf("start capture ok\n"); /* 创建线程用来控制亮度 */ pthread_t thread; pthread_create(&thread, NULL, thread_brightness_control, (void *)fd); while (1) { /* poll */ memset(fds, 0, sizeof(fds)); fds[0].fd = fd; fds[0].events = POLLIN; if (1 == poll(fds, 1, -1)) { /* 把buffer取出队列 */ struct v4l2_buffer buf; memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (0 != ioctl(fd, VIDIOC_DQBUF, &buf)) { perror("Unable to dequeue buffer"); return -1; } /* 把buffer的数据存为文件 */ sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++); int fd_file = open(filename, O_RDWR | O_CREAT, 0666); if (fd_file < 0) { printf("can not create file : %s\n", filename); } printf("capture to %s\n", filename); write(fd_file, bufs[buf.index], buf.bytesused); close(fd_file); /* 把buffer放入队列 */ if (0 != ioctl(fd, VIDIOC_QBUF, &buf)) { perror("Unable to queue buffer"); return -1; } } } if (0 != ioctl(fd, VIDIOC_STREAMOFF, &type)) { perror("Unable to stop capture"); return -1; } printf("stop capture ok\n"); close(fd); return 0; }