直白解读linux下V4L2拍照

声明:

环境:linux或ubunt下

编辑器:vim

编译工具:gcc

设备:USB接口的摄像头

难点理解:

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

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

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

/*
 @sun : 2017-8-18
 email: [email protected]
 csdn : Mr_sunp
 abstract :
        under linux,Get an image with v4l2.
 */
#include 
#include 
#include 

#include 
#include 
#include 

#include 
#include 
#include 

#define NB_BUFFER 5
#define DBG_DIR "jpg/"

#define VIDEO_L_MASK 0x01
#define VIDEO_R_MASK 0x02

#if 1
/*
 头文件 :
 函数   :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
 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


你可能感兴趣的:(Linux)