linux v4l2架构分析之用户层应用编程

        camera驱动和设备注册完毕后,会在/dev目录下生成相应的设备节点,用户层可以通过这些设备节点对设备进行配置、查询、数据获取等一系列的操作。

v4l2图像采集的基本流程:

1、打开视频设备文件

//videoX根据摄像头设备选择实际设备节点文件
int fd = open("/dev/videoX", flag);

可以使用阻塞或非阻塞的模式打开设备节点,在非阻塞的情况下,即使未采集到视频数据,驱动任然会将缓存返回给应用程序。

2、查询视频设备属性、功能

v4l2可以通过ioctl接口查询、配置设备属性和功能,常用的ioctl命令标志符:

     VIDIOC_REQBUFS:分配内存
     VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
     VIDIOC_QUERYCAP:查询驱动功能
     VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
     VIDIOC_S_FMT:设置当前驱动的频捕获格式
     VIDIOC_G_FMT:读取当前驱动的频捕获格式
     VIDIOC_TRY_FMT:验证当前驱动的显示格式
     VIDIOC_CROPCAP:查询驱动的修剪能力
     VIDIOC_S_CROP:设置视频信号的边框
     VIDIOC_G_CROP:读取视频信号的边框
     VIDIOC_QBUF:把数据从缓存中读取出来
     VIDIOC_DQBUF:把数据放回缓存队列
     VIDIOC_STREAMON:开始视频显示函数
     VIDIOC_STREAMOFF:结束视频显示函数
     VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。

对于 VIDIOC_QUERYCAP,使用如下:

/*
    capabilities中的比特位表示是否支持设备的相应功能,

    V4L2_CAP_VIDEO_CAPTURE    设备支持捕获功能

    V4L2_CAP_VIDEO_OUTPUT     设备支持输出功能

    V4L2_CAP_VIDEO_OVERLAY    设备支持预览功能

    V4L2_CAP_STREAMING        设备支持流读写

    V4L2_CAP_READWRITE        设备支持read、write方式读写

*/
struct v4l2_capability {
	__u8	driver[16];	/* i.e. "bttv" */
	__u8	card[32];	/* i.e. "Hauppauge WinTV" */
	__u8	bus_info[32];	/* "PCI:" + pci_name(pci_dev) */
	__u32   version;        /* should use KERNEL_VERSION() */
	__u32	capabilities;	/* Device capabilities */
	__u32	reserved[4];
};
 
struct v4l2_capability cap;
 
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0)
{
    printf("ERR(%s):VIDIOC_QUERYCAP failed\n", __func__);
    return -1;
}

3、设置视频设备参数,如图像格式

        3.1 枚举支持的像素格式:

struct v4l2_fmtdesc fmtdesc;
 
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc.index = 0;
 
while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
{
    printf("fmt:%s\n", fmtdesc.description);
 
    fmtdesc.index++;
}

        3.2 设置图像格式

struct v4l2_format v4l2_fmt;
 
memset(&v4l2_fmt, 0, sizeof(struct v4l2_format));
v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //数据流类型
v4l2_fmt.fmt.pix.width = width; //宽度
v4l2_fmt.fmt.pix.height = height; //高度
v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //像素格式为yuyv格式
v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY;
 
if (ioctl(fd, VIDIOC_S_FMT, &v4l2_fmt) < 0)
{
    printf("ERR(%s):VIDIOC_S_FMT failed\n", __func__);
    return -1;
}

4、申请帧缓冲

        通过流的方式获取视频数据,需要申请缓冲,这些缓冲会被视频采集输入队列和视频采集输出队列所维护,在采集视频流时,视频采集输入队列取出缓冲存储视频流数据,然后将缓冲放入视频采集输出队列中等待应用程序的获取。应用程序从视频采集输出队列中获取缓冲并处理完数据后,会将缓冲放回视频采集输入队列,等待下一次的视频流获取。

        缓冲申请如下

struct v4l2_requestbuffers req;
 
req.count = nr_bufs; //缓存数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
 
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
{
    printf("ERR(%s):VIDIOC_REQBUFS failed\n", __func__);
    return -1;
}

5、内存地址映射,将第4步骤中申请的缓冲地址映射到用户空间

        通过read方式读取数据,需要将数据从内核空间拷贝到用户空间,效率较低,而是用内存映射的方式则能够将内核空间的内存映射到用户空间,不需要再进行拷贝。

        以下是缓冲映射,映射前需要检查一下待映射的缓冲是否正常。

struct v4l2_buffer v4l2_buffer;
void* addr;
 
for(i = 0; i < nr_bufs; i++)
{
    memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
    v4l2_buffer.index = i; //想要查询的缓存
    v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buffer.memory = V4L2_MEMORY_MMAP;
 
    /* 查询缓存信息 */
    ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2_buffer);
    if(ret < 0)
    {
        printf("Unable to query buffer.\n");
        return -1;
    }
 
    /* 映射 */
    addr = mmap(NULL /* start anywhere */ ,
            v4l2_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
            fd, v4l2_buffer.m.offset);
}

6、将申请的缓冲放入视频采集输入队列

struct v4l2_buffer v4l2_buffer;
 
for(i = 0; i < nr_bufs; i++)
{
    memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
    /* 想要放入队列的缓存 */
    v4l2_buffer.index = i; 
    v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buffer.memory = V4L2_MEMORY_MMAP;	
 
    ret = ioctl(fd, VIDIOC_QBUF, &v4l2_buffer);
    if(ret < 0)
    {
        printf("Unable to queue buffer.\n");
        return -1;
    }
}

7、开启视频流采集

enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
{
    printf("ERR(%s):VIDIOC_STREAMON failed\n", __func__);
    return -1;
}

8、从视频采集输出队列中取出已采集好帧数据的缓冲

        在采集视频流时,v4l2会从视频采集输入队列取出缓冲存储视频流数据,然后将缓冲放入视频采集输出队列中等待应用程序的获取。

        以下是通过poll等待缓冲放入输出队列并从输出队列获取缓冲的过程:

struct v4l2_buffer buffer;
 
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
 
if (ioctl(fd, VIDIOC_DQBUF, &buffer) < 0)
{
    printf("ERR(%s):VIDIOC_DQBUF failed, dropped frame\n", __func__);
    return -1;
}

9、数据处理完上一步骤取出的缓存后,将缓冲重新放入视频采集输入队列

        将缓冲重新放入视频采集输入队列,等待用于新到来的视频数据的存储。应用代码过程如下:

struct v4l2_buffer v4l2_buf;
 
v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf.memory = V4L2_MEMORY_MMAP;
v4l2_buf.index = i; //指定buf
 
if (ioctl(fd, VIDIOC_QBUF, &v4l2_buf) < 0)
{
    printf("ERR(%s):VIDIOC_QBUF failed\n", __func__);
    return -1;
}

10、停止视频流采集

enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
{
    printf("ERR(%s):VIDIOC_STREAMOFF failed\n", __func__);
    return -1;
}

11、关闭设备

        关闭设备前要释放资源,比如取消映射,如下:

for(i = 0; i < nr_bufs; ++i)
    munmap(buf[i].addr, buf[i]->length);

        关闭设备

close(fd);

参考:

从应用调用vivi驱动分析v4l2 -- 应用代码编写_dianlong_lee的博客-CSDN博客

V4L2应用程序框架_คิดถึง643的博客-CSDN博客

V4L2 摄像头应用__十年饮冰难凉热血的博客-CSDN博客_v4l2 摄像头

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值