直白解读linux下V4L2拍照

来源:互联网 发布:linux informix 目录 编辑:程序博客网 时间:2024/06/05 20:57

声明:

环境:linux或ubunt下

编辑器:vim

编译工具:gcc

设备:USB接口的摄像头

难点理解:

1.对于缓冲帧的解释:假设我们申请5个缓冲帧,那么这5个缓冲帧就相当于5个盘子,操作系统在内存中开辟5个缓存区队列来存放这5个盘子,当相机拍照后将数据放入盘子中,我们通过VIDIOC_DQBUF取数据可以理解为将盘子取出来,然后对数据操作,操作完成之后,要通过VIDIOC_QBUF将盘子放回去,以保证可以循环接收数据。

2.对于映射的解释:对于映射,可以理解为cpu在处理时会给你分配几个空间,然后把这几个空间的首地址返回给你,如果你有数据,那么他就会把数据放到这个空间内。

程序源码:(包含详细注释)

/* @sun : 2017-8-18 email: 13045892482@163.com csdn : Mr_sunp abstract :        under linux,Get an image with v4l2. */#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <linux/videodev2.h>#include <sys/ioctl.h>#include <sys/mman.h>#define NB_BUFFER 5#define DBG_DIR "jpg/"#define VIDEO_L_MASK 0x01#define VIDEO_R_MASK 0x02#if 1/* 头文件 :<linux/videodev2.h> 函数   :int ioctl(intfd, int request, struct v4l2_capability *argp); struct v4l2_capability     设备的功能 {      __u8 driver[16];   // 驱动名字      __u8 card[32];     // 设备名字      __u8 bus_info[32]; // 设备在系统中的位置      __u32 version;     // 驱动版本号      __u32 capabilities;// 设备支持的操作      __u32 reserved[4]; // 保留字段   };  VIDIOC_QUERYCAP    //查询驱动功能 *//*  fd:文件描述符  */int showCapability(int fd){    printf("-----------------------设备信息---------------------\n");struct v4l2_capability cap;    if(ioctl(fd,VIDIOC_QUERYCAP,&cap) < 0) {                perror("error ioctl VIDIOC_QUERYCAP !");         return -1;    }    printf("DriverName   :%s\nCard Name    :%s\nBus info     :%s\nDriverVersion:%u.%u.%u\n"            ,cap.driver            ,cap.card            ,cap.bus_info            ,(cap.version>>16)&0xff            ,(cap.version>>8)&0xff            ,cap.version&0xff);    return 0;}#endif#if 1/* 函数   :int ioctl(intfd, int request, struct v4l2_fmtdesc *argp); struct v4l2_fmtdesc   {      __u32 index;               // 要查询的格式序号,应用程序设置      enum v4l2_buf_type type;   // 帧类型,应用程序设置      __u32 flags;               // 是否为压缩格式      __u8 description[32];      // 格式名称      __u32 pixelformat;         // 格式      __u32 reserved[4];         // 保留   };  VIDIOC_ENUM_FMT    //指令含义:获取当前驱动支持的视频格式 *//*  fd:文件描述符  */int showFmtdesc(int fd){    printf("-----------------------支持格式---------------------\n");        struct v4l2_fmtdesc dis_fmtdesc;      dis_fmtdesc.index=0;      dis_fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;      printf("Supportformat:\n");      while(ioctl(fd,VIDIOC_ENUM_FMT,&dis_fmtdesc)!=-1)      {          printf("\t%d.%s\n",dis_fmtdesc.index+1,dis_fmtdesc.description);          dis_fmtdesc.index++;      }    return 0;}#endif#if 1/* 函数   :int ioctl(intfd, int request, struct v4l2_format *argp); struct v4l2_format     帧的格式  {      enum v4l2_buf_type type;        //帧类型,应用程序设置     union fmt      {          structv4l2_pix_format pix;  //视频设备使用          structv4l2_window win;          structv4l2_vbi_format vbi;          structv4l2_sliced_vbi_format sliced;          __u8raw_data[200];      };   };  VIDIOC_G_FMT:      //指令含义:读取当前驱动的捕获格式 VIDIOC_S_FMT:      //指令含义:设置当前驱动的捕获格式 VIDIOC_ENUM_FMT:   //指令含义:获取当前驱动支持的视频格式 *//*  fd:文件描述符  */int showFormat(int fd){    printf("------------------------帧信息----------------------\n");    struct v4l2_format dis_fmt;      dis_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;      ioctl(fd,VIDIOC_G_FMT,&dis_fmt);      printf("Currentdata format information:\n\twidth:%d\n\theight:%d\n"            ,dis_fmt.fmt.pix.width            ,dis_fmt.fmt.pix.height);    struct v4l2_fmtdesc fmtdesc;      fmtdesc.index=0;      fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;      while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)    {          if(fmtdesc.pixelformat & dis_fmt.fmt.pix.pixelformat)          {              printf("\tformat:%s\n",fmtdesc.description);              break;          }          fmtdesc.index++;      }    return 0;}#endif#if 1/* V4L2_FIELD_ANY         //指令含义: V4L2_PIX_FMT_MJPEG     //指令含义:MJPEG格式 V4L2_PIX_FMT_UYVY      //指令含义:YUYV格式 VIDIOC_S_PARM          //指令格式:检查格式和设置 *//* fd : 文件描述符   w :宽  h:高  */int setFormat(int fd ,int w,int h){    printf("--------------------设置当前格式--------------------\n");    struct v4l2_format fmt = {                .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,                .fmt = {            .pix = {                                .width = w,                         //帧宽                                .height = h,                        //帧高                                .pixelformat = V4L2_PIX_FMT_MJPEG,  //帧格式                                .field = V4L2_FIELD_ANY,                        }                },    };    //检查格式 和 设置是否成功    if(ioctl(fd,VIDIOC_S_PARM,&fmt) < 0){                perror("error ioctl VIDIOC_S_PARM !");         return -1;    }else{        printf("set image size:\n\twidth = %d\n\theight = %d\n",w,h);    }    return 0;}#endif#if 1/* fd :文件描述符 fps:帧率 numerator:这里固定为1 */int setFps(int fd ,int fps){    printf("--------------------设置当前帧率--------------------\n");    struct v4l2_streamparm setfps = {        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,        .parm = {             .capture = {                     .timeperframe = {                        .numerator=1,                        .denominator = fps,   //帧率时间长度为 = numerator/denominator                    }            }        },    };if (ioctl(fd, VIDIOC_S_PARM, &setfps) < 0) {        perror("error SETFPS failed !");        return -1;    }else{        printf("\tset fps : %d\n",fps);    }    return 0;}#endif#if 1/*  fd :文件描述符 val:曝光值 */int setExposure(int fd,int val){    printf("--------------------设置曝光模式--------------------\n");    struct v4l2_control con_setup = {   //设置手动曝光        .id = V4L2_CID_EXPOSURE_AUTO,        .value = V4L2_EXPOSURE_MANUAL,    };    if(ioctl(fd,VIDIOC_S_CTRL,&con_setup)<0){        perror("set exposure failed !");    }        struct v4l2_control con_set = {     //设置曝光绝对值        .id = V4L2_CID_EXPOSURE_ABSOLUTE,        .value = val,    };    if(ioctl(fd,VIDIOC_S_CTRL,&con_set)<0){        perror("set exposure failed !");    }else{        printf("set exposure\n\tmode : 手动\n\tval : %d\n",val);    }    return 0;}#endif#if 1/*  fd :文件描述符 val:焦距 */int setFocus(int fd ,int val){    printf("--------------------设置对焦模式--------------------\n");    struct v4l2_control con_setup = {   //关闭自动对焦        .id = V4L2_CID_FOCUS_AUTO,        .value = 0,    };    if(ioctl(fd,VIDIOC_S_CTRL,&con_setup) < 0){        perror("focus set failed !");    }else{        printf("focus off;\n");    }    struct v4l2_control con_set = {     //设置焦点值        .id = V4L2_CID_FOCUS_ABSOLUTE,        .value = val,    };    if(ioctl(fd,VIDIOC_S_CTRL,&con_set) < 0){        perror("focus set failed !");    }else{        printf("focus on;\n\tval : %d\n",val);    }    return 0;}#endif#if 1/* 申请和管理缓冲区,应用程序和设备有三种交换数据的方法,直接read/write ,内存映射(memorymapping) ,用户指针。这里只讨论 memorymapping. 函数   :int ioctl(intfd, int request, struct v4l2_requestbuffers *argp); struct v4l2_requestbuffers  申请帧缓冲 {      __u32 count;            // 缓冲区内缓冲帧的数目      enum v4l2_buf_type type;// 缓冲帧数据格式      enum v4l2_memorymemory; // 区别是内存映射还是用户指针方式      __u32 reserved[2];   }; VIDIOC_REQBUFS         //指令含义:分配内存 *//*  fd :文件描述符 */int requestBuffers(int fd){    printf("---------------------申请缓冲区---------------------\n");        struct v4l2_requestbuffers rb = {                .count = NB_BUFFER,                     //缓冲帧个数                .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,//数据类型:流类型,永远都是                .memory = V4L2_MEMORY_MMAP,         //存储类型:V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR        };        if (ioctl(fd, VIDIOC_REQBUFS, &rb) < 0){                perror("error VIDIOC_REQBUFS !");                return -1;        }    printf("count : %d\n",NB_BUFFER);    return 0;}#endif#if 1/* 函数   :int ioctl(intfd, int request, struct v4l2_buffer *argp); struct v4l2_buffer   {      __u32 index;                //buffer 序号      enum v4l2_buf_type type;    //buffer 类型      __u32 byteused;             //buffer 中已使用的字节数      __u32 flags;                //区分是MMAP 还是USERPTR      enum v4l2_fieldfield;      struct timeval timestamp;   //获取第一个字节时的系统时间      struct v4l2_timecode timecode;      __u32 sequence;             //队列中的序号      enum v4l2_memory memory;    //IO 方式,被应用程序设置      union m      {          __u32 offset;           //缓冲帧地址,只对MMAP 有效          unsigned longuserptr;      };      __u32 length;               //缓冲帧长度      __u32 input;      __u32 reserved;   }; VIDIOC_QUERYBUF        //把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址 *//* MMAP:定义一个结构体来映射每个缓冲帧 */struct buffer{    void *start;    unsigned int length;} *buffers;/* #include<sys/mman.h> void *mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset); //addr 映射起始地址,一般为NULL ,让内核自动选择 //length 被映射内存块的长度 //prot 标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE //flags 确定此内存映射能否被其他进程共享,MAP_SHARED,MAP_PRIVATE //fd,offset, 确定被映射的内存地址 成功:返回成功映射后的地址,不成功:返回MAP_FAILED ((void*)-1);  int munmap(void*addr, size_t length);// 断开映射 //addr 为映射后的地址,length 为映射后的内存长度 *//*  fd :文件描述符 addr:二级指针,用来存放开辟的buffers地址 */int getBufLenAndAddr(int fd,struct buffer **addr){    printf("-----------------获取缓冲区地址,长度---------------\n");        buffers = (struct buffer*)calloc(NB_BUFFER,sizeof(*buffers));    *addr = buffers; //记录开辟的空间首地址,便于free    if(!buffers){   //将n_buffers个以申请到的缓冲帧映射到引用程序,用buffers指针记录        perror("error calloc failed !");        return -1;    }    unsigned int n_buffers;    for(n_buffers=0;n_buffers<NB_BUFFER;++n_buffers){    //循环映射        struct v4l2_buffer buf;          memset(&buf,0,sizeof(buf));                  buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;          buf.memory = V4L2_MEMORY_MMAP;          buf.index = n_buffers;          // 查询序号为n_buffers 的缓冲区,得到其起始物理地址和大小        if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf)){            perror("error VIDIOC_QUERYBUF failed !");            return -1;          }              buffers[n_buffers].length = buf.length;         //映射内存        buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ,MAP_SHARED,fd,buf.m.offset);          if (MAP_FAILED == buffers[n_buffers].start){    //MAP_FAILED判断返回是否错误            perror("error mmap failed !");            return -1;        }    }    return 0;}#endif#if 1/* VIDIOC_STREAMON    指令含义:开始采集 VIDIOC_STREAMOFF    指令含义:结束采集 *//*  fd :文件描述符 */int putQueue(int fd){    printf("-----------------将申请到的缓冲帧放入队-------------\n");    unsigned int i;    for(i=0;i<NB_BUFFER;i++){        struct v4l2_buffer buf;        buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;          buf.memory =V4L2_MEMORY_MMAP;          buf.index = i;          if(ioctl(fd,VIDIOC_QBUF, &buf) < 0) {            perror("error VIDIOC_QBUF failed !");            return -1;        };    }    return 0;}#endif#if 1/*  fd :文件描述符 */int videoOn(int fd){    printf("--------------------开始采集数据-------------------\n");    enum v4l2_buf_type type;    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;    int temp = ioctl(fd,VIDIOC_STREAMON,&type);    if(temp < 0){        perror("video on failed !\n");    }    return 0;}#endif#if 1/*  fd :文件描述符 */int handleDate(int fd){    printf("--------------------获取一帧并处里------------------\n");    struct v4l2_buffer buf;      memset(&buf,0,sizeof(buf));          buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;      buf.memory = V4L2_MEMORY_MMAP;          ioctl (fd,VIDIOC_DQBUF, &buf);   // 从缓冲区取出一个缓冲帧char jpgName[32];    sprintf(jpgName,DBG_DIR "%03d.jpg",buf.index);    FILE *fSource = fopen(jpgName,"wb");    fwrite(buffers[buf.index].start,1,(size_t)buf.bytesused,fSource);    fclose(fSource);    ioctl (fd, VIDIOC_QBUF,&buf);  // 将取出的缓冲帧放回缓冲区 }#endif#if 1/*  fd :文件描述符 */int videoOff(int fd){    printf("----------------------结束采集数据------------------\n");    int type_off = V4L2_BUF_TYPE_VIDEO_CAPTURE;    if(ioctl(fd,VIDIOC_STREAMOFF,&type_off)){        perror("error STREAMOFF failed");        return -1;    }    return 0;}#endifint main(int argc, char *argv[]){    int fd;    struct buffer *ptr;    int i;        if((fd = open(argv[1], O_RDWR)) == -1) {                perror("error open !");         return -1;    }    mkdir(DBG_DIR,0777);    showCapability(fd);    showFmtdesc(fd);    showFormat(fd);    setFormat(fd,800,600);    setFps(fd,60);    setExposure(fd,12);//    setFocus(fd,12); 焦距的设置因摄像头而定,如果不支持这去掉这个为默认状态    requestBuffers(fd);    getBufLenAndAddr(fd,&ptr);    putQueue(fd);    videoOn(fd);    handleDate(fd);    videoOff(fd);    free(ptr);    close(fd);    return 0;}