Video4Linux2:

该应用使用的是USB摄像头,在Linux中内核驱动框架多采Video4Linux2(V4L2),V4L2为上层的访问底层的视频设备提供了统一的接口,提取出公共代码避免底层硬件差异。 V4L2支持三类设备:视频输入输出设备、VBI设备和radio设备及更多。下图V4L2在Linux系统中的结构图:

S3连接python_usb


USB摄像头初始化:

1.打开设备文件:

fd =open("dev/video", O_RDWR);

2.查询设备支持的能力:

ioctl(fd, VIDIOC_QUERYCAP, &tV4l2Cap);

3.获取当前视频设备支持的视频格式:
摄像头的输出格式多种多样,有可能是JPEG、RGB、YUYV等等,而LCD显示屏需要输入RGB格式数据。因此,此时需获取当前摄像头支持的视频格式,以为后续数据格式转换作准备。

ioctl(fd, VIDIOC_ENUM_FMT, &tFmtDesc)

4.查看驱动程序是否支持当前LCD分辨率,若不支持,将调整参数,传回应用层:

ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);

5.请求V4L2驱动分配视频缓冲区,V4L2是视频设备的驱动层,位于内核空间,所以通过VIDIOC_REQBUFS控制命令申请的内存位于内核空间,应用程序不能直接访问,需要通过调用mmap内存映射函数把内核空间内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间:

ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);

6. 使用控制命令VIDIOC_QUERYBUF获得已经分配的V4L2的视频缓冲区的使用状态、在内核空间的偏移地址、缓冲区长度等相关信息。然后调用函数mmap把内核空间地址映射到用户空间,以便应用程序访问位于内核空间的视频缓冲区:

ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);

mmap(0,tV4l2Buf.length, PROT_READ, MAP_SHARED, fd,tV4l2Buf.m.offset);

7. 数据流IO:
当开始数据流IO时,数据(摄像头获取的单帧画面数据)以v4l2_buffer的格式在应用和驱动之间传输。一个缓冲区可以有三种状态:

a.在驱动的传入队列中,驱动程序将会对此队列中的缓冲区进行处理,用户空间通过VIDIOC_QBUF把缓冲区放入到队列。对于一个视频捕获设备,传入队列中的缓冲区是空的,驱动会往其中填充数据;

b.在驱动的传出队列中,这些缓冲区已由驱动处理过,对于一个视频捕获设备,缓存区已经填充了视频数据,正等用户空间来认领;

c.用户空间状态的队列,已经通过VIDIOC_DQBUF传出到用户空间的缓冲区,此时缓冲区由用户空间拥有,驱动无法访问。

S3连接python_V4L2_02

此时应将申请并且mmap的多个视频缓冲区v4l2_buffer依次放入队列,等待驱动填充数据:

ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf)

8.至此就完成了摄像头的初始化

9.参考代码

#define NB_BUFFER 4  //视频缓冲区个数(自定义)

typedef struct videoDeviceParam
{
    int iFd;
    int iWidth;
    int iHeight;
    int iBpp;
    int pixelformat;
    int iVideoBufCnt;
    int iVideoBufCurIndex;

    int iVideoBufMaxLen;
    unsigned char *pucVideBuf[NB_BUFFER];

}T_videoDeviceParam,*PT_videoDeviceParam;


int V4l2InitDevice(char * videoDevName , PT_videoDeviceParam pt_videoDeviceParam)
{
    int i;
    int iFd;
    int iError;

    struct v4l2_capability tV4l2Cap;
    struct v4l2_fmtdesc tFmtDesc;
    struct v4l2_format  tV4l2Fmt;
    struct v4l2_requestbuffers tV4l2ReqBuffs;
    struct v4l2_buffer tV4l2Buf;

    unsigned int iLcdWidth;
    unsigned int iLcdHeigt;
    unsigned int iLcdBpp;   

     iFd =open(videoDevName, O_RDWR);  //打开设备文件
     if(iFd < 0 )
        printf("Video OPEN Failed.\n");

     pt_videoDeviceParam->iFd = iFd;


    memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));
    iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);//查询设备支持的能力
    if (iError)
    {
            printf("unable to query device.\n");
            goto err_exit;
    }   

    /* 获取当前视频设备支持的视频格式 */
    memset(&tFmtDesc, 0, sizeof(tFmtDesc));
    tFmtDesc.index = 0;
    tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0)
    {
        if (tFmtDesc.pixelformat == V4L2_PIX_FMT_YUYV)
        {
            pt_videoDeviceParam->pixelformat = tFmtDesc.pixelformat;
            break;
        }
        tFmtDesc.index++;
    }

    if (!pt_videoDeviceParam->pixelformat)
    {
        printf("can not support the format of this device\n");
        goto  err_exit;       
    }

    LcdGetResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);
    memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
    tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2Fmt.fmt.pix.pixelformat = pt_videoDeviceParam->pixelformat;
    tV4l2Fmt.fmt.pix.width       = iLcdWidth;
    tV4l2Fmt.fmt.pix.height      = iLcdHeigt;
    tV4l2Fmt.fmt.pix.field         = V4L2_FIELD_ANY;

    /* 如果驱动程序发现无法某些参数(比如分辨率),
     * 它会调整这些参数, 并且返回给应用程序
     */
     iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt); 
     if (iError) 
      {
            printf("Unable to set format\n");
             goto err_exit;        
     }



    pt_videoDeviceParam->iWidth  = tV4l2Fmt.fmt.pix.width;
    pt_videoDeviceParam->iHeight = tV4l2Fmt.fmt.pix.height;


    /* 申请视频缓冲区 */
    memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
    tV4l2ReqBuffs.count = NB_BUFFER;
    tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;

    iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
        if (iError) 
        {
            printf("Unable to allocate buffers.\n");
             goto err_exit;        
     }

    pt_videoDeviceParam->iVideoBufCnt = tV4l2ReqBuffs.count;

     if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING)
     {
             /* 映射至应用程序空间 */
             for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) 
             {
                memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
                tV4l2Buf.index = i;
                tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                tV4l2Buf.memory = V4L2_MEMORY_MMAP;
                iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
                if (iError) 
                 {
                      printf("Unable to query buffer.\n");
                     goto err_exit;
                }



                pt_videoDeviceParam->iVideoBufMaxLen = tV4l2Buf.length;
                printf("tV4l2Buf.length = %d\n",tV4l2Buf.length );
                 pt_videoDeviceParam->pucVideBuf[i] = mmap(0,
                                     tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,
                                     tV4l2Buf.m.offset);
                        if (pt_videoDeviceParam->pucVideBuf[i] == MAP_FAILED) 
                             {
                                  printf("Unable to map buffer\n");
                                 goto err_exit;
                             }
                 }
                /* 将分配空间放入队列*/
                for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) 
                {
                    memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
                    tV4l2Buf.index = i;
                    tV4l2Buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                    tV4l2Buf.memory = V4L2_MEMORY_MMAP;
                    iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);
                    if (iError)
                    {
                        printf("Unable to queue buffer.\n");
                        goto err_exit;
                    }           
                }

        }        
    return 0;

err_exit:
    printf("err_exit.\n");
    close(iFd);
    return -1;
}