⑫tiny4412 Linux驱动开发之V4L2(usb camera)示例程序

本来这一次准备写SPI的,但是因为读取SPI Flash的数值不正确,还在继续努力,所以就先不写SPI了,回头再写.这次先来说一下V4L2.

V4L2全称video for Linux 2,是Linux的新一代视频架构,刚买了一个USB摄像头,所以,这里试一下效果.买的这个摄像头只支持YUYV格式,也就是YUV422,我之前有一套代码是测试MJPEG的,我笔记本的摄像头是支持这种格式的,这里因为只支持YUYV,所以,代码稍微修改了一下,但是效果不是很好,代码后边有空了,再优化.

首先,在Ubuntu PC环境下,先来测试一下效果,代码如下:

#include 
#include 
#include 
#include 

#include            

#include             
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include         
#include 

#define CLEAR(x) memset (&(x), 0, sizeof (x))

struct buffer {
    void *                  start;
    size_t                  length;
};

static char *dev_name = "/dev/video1";//摄像头设备名
static int fd = -1;
struct buffer *buffers = NULL;
static unsigned int n_buffers = 0;

#define VIDEO_WIDTH     640
#define VIDEO_HEIGHT    480    // 分辨率600 * 480(VGA格式)

// 录制格式,笔记本摄像头可以选择V4L2_PIX_FMT_MJPEG,这样可以直接保存成jpg,直观
#define VIDEO_FORMAT V4L2_PIX_FMT_YUYV  //V4L2_PIX_FMT_MJPEG  
//V4L2_PIX_FMT_MJPEG
//V4L2_PIX_FMT_YUYV
//V4L2_PIX_FMT_YVU420
//V4L2_PIX_FMT_RGB32


FILE *file_fd;
#define CAPTURE_FILE "test.yuv"//"test.jpg"     // 笔记本摄像头选择"test.jpg"
static unsigned long file_length;

//////////////////////////////////////////////////////
//获取一帧数据
//从视频缓冲区的输出队列中取得一个已经保存有一帧视频数据的视频缓冲区
//////////////////////////////////////////////////////
static int read_frame (void)
{
    struct v4l2_buffer buf;
    CLEAR (buf);
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    
    if(ioctl (fd, VIDIOC_DQBUF, &buf) == -1)
    {
        printf("VIDIOC_DQBUF failture\n"); //出列采集的帧缓冲
        exit(1);
    }

    assert (buf.index < n_buffers);
    //  printf ("buf.index dq is %d,\n",buf.index);

    //将其写入文件中
    fwrite(buffers[buf.index].start, buffers[buf.index].length, 1, file_fd);
    printf("Capture one frame saved in %s\n", CAPTURE_FILE);
    
    //再将其入列
    if(ioctl (fd, VIDIOC_QBUF, &buf)<0)
	    printf("failture VIDIOC_QBUF\n");
    
    return 1;
}

//主函数
int main (int argc,char ** argv)
{
    
/************************************初始化Start****************************************************/
    file_fd = fopen(CAPTURE_FILE, "w");//图片文件名

    //打开设备
    fd = open (dev_name, O_RDWR | O_NONBLOCK, 0);
    if(fd < 0)
    {
        printf("open %s failed\n",dev_name);
        exit(1);
    }

    //获取驱动信息//获取摄像头参数//查询驱动功能并打印
    struct v4l2_capability cap;

    if(ioctl (fd, VIDIOC_QUERYCAP, &cap) < 0)
    {
        printf("get vidieo capability error,error code: %d \n", errno);
        exit(1);
    }
    
    // Print capability infomations
    printf("\nCapability Informations:\n");
    printf("Driver Name:%s\nCard Name:%s\nBus info:%s\nDriver Version:%u.%u.%u\nCapabilities: X\n",
	    cap.driver,cap.card,cap.bus_info,
	    (cap.version>>16)&0XFF, (cap.version>>8)&0XFF,cap.version&0XFF,
	    cap.capabilities );

    //获取设备支持的视频格式
    struct v4l2_fmtdesc fmtdesc;     
    CLEAR (fmtdesc);
    fmtdesc.index = 0;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("\nSupport format:\n");
    
    while ((ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) == 0)
    {
        printf("/t%d.\n{\npixelformat = '%c%c%c%c',\ndescription = '%s'\n }\n",
            fmtdesc.index+1,
            fmtdesc.pixelformat & 0xFF,
            (fmtdesc.pixelformat >> 8) & 0xFF,
            (fmtdesc.pixelformat >> 16) & 0xFF,
            (fmtdesc.pixelformat >> 24) & 0xFF,
            fmtdesc.description);  
        fmtdesc.index++;
    }

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){
        fprintf (stderr, "%s is no video capture device\n", dev_name);
        exit (EXIT_FAILURE);
    }
    ///////////取景窗口缩放/有的摄像头不支持/////////////////////////////////////////////////////////////////////////



    //检查是否支持某种帧格式   
    struct v4l2_format fmt2;
    fmt2.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;   
    fmt2.fmt.pix.pixelformat = VIDEO_FORMAT;

    if(ioctl(fd,VIDIOC_TRY_FMT,&fmt2)==-1)  
        if(errno==EINVAL)
            printf("not support format %s!\n",VIDEO_FORMAT);   

    //设置视频捕获格式
    struct v4l2_format fmt;
    CLEAR (fmt);
    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = VIDEO_WIDTH;//320;
    fmt.fmt.pix.height      = VIDEO_HEIGHT;//240;
    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;
    fmt.fmt.pix.pixelformat = VIDEO_FORMAT;
    
    //设置图像格式
    if(ioctl (fd, VIDIOC_S_FMT, &fmt) < 0)
    {
        printf("failture VIDIOC_S_FMT\n");
        exit(1);
    }

    ///////////////////////////////////////////////////////////////////////
    // 显示当前帧的相关信息
    struct v4l2_format fmt3;
    fmt3.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd,VIDIOC_G_FMT,&fmt3); 
    printf("\nCurrent data format information:\n twidth:%d\n theight:%d\n",
	    fmt3.fmt.pix.width,fmt3.fmt.pix.height);

    struct v4l2_fmtdesc fmtdes;
    fmtdes.index=0;
    fmtdes.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    
    while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdes)!=-1)
    {
        if(fmtdes.pixelformat & fmt3.fmt.pix.pixelformat)
        {
            printf(" tformat:%s\n",fmtdes.description);
            break;
        }
        fmtdes.index++;
    }

    ///////////////////////////////////////////////////////////////////////
    //视频分配捕获内存
    struct v4l2_requestbuffers req;
    CLEAR (req);
    req.count               = 4;
    req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory              = V4L2_MEMORY_MMAP;

    //申请缓冲,count是申请的数量
    if(ioctl (fd, VIDIOC_REQBUFS, &req) < 0)
    {
        printf("failture VIDIOC_REQBUFS\n");
        exit(1);
    }
    
    if (req.count < 2)
	    printf("Insufficient buffer memory\n");

    //内存中建立对应空间
    //获取缓冲帧的地址、长度
    buffers = calloc (req.count, sizeof (*buffers));//在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针
    if (!buffers)
    {
        fprintf (stderr, "Out of memory/n");
        exit (EXIT_FAILURE);
    }

    for (n_buffers = 0; n_buffers < req.count; ++n_buffers)
    {
        struct v4l2_buffer buf;   //驱动中的一帧
        CLEAR (buf);
        buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory      = V4L2_MEMORY_MMAP;
        buf.index       = n_buffers;// 要获取内核视频缓冲区的信息编号

        if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf)) //映射用户空间
        {
            printf ("VIDIOC_QUERYBUF error\n");
            exit(-1);
        }
        buffers[n_buffers].length = buf.length; 

        // 把内核空间缓冲区映射到用户空间缓冲区
        buffers[n_buffers].start = mmap (NULL ,    //通过mmap建立映射关系
            buf.length,
            PROT_READ | PROT_WRITE ,
            MAP_SHARED ,
            fd,
            buf.m.offset);

        if (MAP_FAILED == buffers[n_buffers].start)
        {
            printf ("mmap failed\n");
            exit(1);
        }
    }

    //投放一个空的视频缓冲区到视频缓冲区输入队列中
    //把四个缓冲帧放入队列,并启动数据流
    unsigned int i;

    // 将缓冲帧放入队列
    enum v4l2_buf_type type;
    for (i = 0; i < n_buffers; ++i)
    {
        struct v4l2_buffer buf;
        CLEAR (buf);
        buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory      = V4L2_MEMORY_MMAP;
        buf.index       = i; //指定要投放到视频输入队列中的内核空间视频缓冲区的编号;

        if (-1 == ioctl (fd, VIDIOC_QBUF, &buf))//申请到的缓冲进入列队
            printf ("VIDIOC_QBUF failed\n");
    }

    //开始捕捉图像数据  
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == ioctl (fd, VIDIOC_STREAMON, &type))
    {
        printf ("VIDIOC_STREAMON failed\n");
        exit(1);
    }
/************************************初始化END****************************************************/

    /////////////////////////////////////////////////////////////////////////
    for (;;) //这一段涉及到异步IO
    {
        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);//判断是否可读(即摄像头是否准备好),tv是定时

        if (-1 == r){
            if (EINTR == errno)
            continue;
            printf ("select err\n");
        }

        if (0 == r){
            fprintf (stderr, "select timeout\n");
            exit (EXIT_FAILURE);
        }

        if (read_frame ())//如果可读,执行read_frame ()函数,并跳出循环
            break;
    }
    /////// Release the resource////////////////////////////////////////////////////  
    unsigned int ii;
    for (ii = 0; ii < n_buffers; ++ii)
        if (-1 == munmap (buffers[ii].start, buffers[ii].length))   
            free (buffers);

    /////////////////////////////////////////////////////////

    close (fd);
    printf("Camera test Done.\n");
    fclose (file_fd);
    //exit (EXIT_SUCCESS);
    return 0;
}

测试代码运行之后,会在同级目录下生成一张照片,如果我们设置的是保存成jpg格式,那么可以直接查看这张图片,但是,因为买的这个USB免驱摄像头只支持YUYV,所以,这里选择保存为.yuv格式,这种格式需要借助查看工具才能看到效果,我这里用的是pYUV,大家可以百度自己下载这个,然后用pYUV打开我们刚刚运行上面那段代码生成的test.yuv,可以看到如下效果:

⑫tiny4412 Linux驱动开发之V4L2(usb camera)示例程序_第1张图片

⑫tiny4412 Linux驱动开发之V4L2(usb camera)示例程序_第2张图片

软件问题,效果不是特别明显,但是仔细看还是可以看到天花板是很清晰的,后边有空了,再优化软件,现在没时间了,明天就要开始在新部门的工作了,起码今天还是看到了效果,对吧,如果用笔记本摄像头,并且使用MJPEG格式,保存为jpg格式,就是那种正常的照片了.如果买的免驱摄像头也支持这种格式,则也可以达到同样的效果,以前在S5PV210的板子上也做过,是完全OK的,反正先不说了,这套代码,有空了再优化吧.

然后这套代码,用交叉编译生成ARM开发板可用的可执行文件,再去试试,友善直接移植好了USB摄像头相关的驱动,我们只需要调用应用程序测试即可,一开始在/dev目录就有15个video设备名(video0~video),这时插入USB摄像头,会多出一个video15,这个就是USB摄像头的设备名,我们把代码里的设备名改为/dev/video15,然后重新交叉编译,在Exynos 4412上运行,拍到了一张图片,效果如下:

⑫tiny4412 Linux驱动开发之V4L2(usb camera)示例程序_第3张图片

⑫tiny4412 Linux驱动开发之V4L2(usb camera)示例程序_第4张图片

可以看到,拍出了我的座机,然后图像和我的显示器有一部分重叠在一块了,这就就先这样吧,后边的话,有空解决一下软件问题,或者买个比较好的摄像头也是可以解决的,哈哈.

测试程序有一些问题,现在是找到了替代方案,效果上截了一张图,如下:

⑫tiny4412 Linux驱动开发之V4L2(usb camera)示例程序_第5张图片

用的软件是xawtv,通过如下命令安装: sudo apt-get install xawtv

安装好之后,执行 xawtv /dev/videoX  // X代表camera的ID,这里我用的是/dev/video1,执行之后就是这个效果,后边看情况吧,有机会的话,就改一下测试程序,使其也能达到这种效果.

有一个博客写的不错,可以参考一下:https://blog.csdn.net/qq_15718789/article/details/72637659

你可能感兴趣的:(Linux)