从应用调用vivi驱动分析v4l2 -- 应用代码编写

Linux v4l2架构学习总链接

v4l2测试代码

V4L2支持多种接口:capture(捕获)、output(输出)、overlay(预览)等等

这里主要分析capture

 

step 1 : 打开设备节点

int fd = open("/dev/video0", flag);

step 2 : 查询设备功能

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;
}

其中capabilities 字段标记着v4l2设备的功能

V4L2_CAP_VIDEO_CAPTURE    设备支持捕获功能

V4L2_CAP_VIDEO_OUTPUT      设备支持输出功能

V4L2_CAP_VIDEO_OVERLAY    设备支持预览功能

V4L2_CAP_STREAMING             设备支持流读写

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

 

step 3 : 设置输入设备

一个设备可能有多个输入,比如摄像头控制器和摄像头接口是分离的,需要选择。

1,枚举输入设备

struct v4l2_input input;

input.index = 0;
while (!ioctl(fd, VIDIOC_ENUMINPUT, &input))
{
    printf("input:%s\n", input.name);
    ++input.index;
}

2,设置输入设备

struct v4l2_input input;

input.index = index; //指定输入设备

if (ioctl(fd, VIDIOC_S_INPUT, &input) < 0)
{
    printf("ERR(%s):VIDIOC_S_INPUT failed\n", __func__);
    return -1;
}

step 4 : 设置图像格式

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++;
}

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; //像素格式
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;
}

step 5 : 设置缓存

v4l2设备读取数据的方式有2种,一种是read方式,一种是streaming方式。

read方式很容易理解,就是通过read函数读取

对于streaming就是在内核空间中维护一个缓存队列,然后将内存映射到用户空间,应用读取图像数据就是一个不断的出队列和入队列的过程

如何去申请和映射缓存?

1,申请缓存

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;
}

2,映射缓存

为什么要映射缓存?

如果使用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);
}

3,将所有的缓存放入队列

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;
    }
}

step 6 : 运行设备

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;
}

step 7 : 读取数据

获取图像数据其实就是一个不断地入队列出队列的过程,在出队列前要调用poll等到数据完成

1,poll或者select

2,出队列

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;
}

出队列后得到了缓存下标buffer.index,然后找到对应的缓存,通过映射过会的地址进行数据的读取

3,入队列

在数据读取完成后,要将buf重新放入队列

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;
}

读取数据就是在上面的这3步中一直不断的循环

step 8 : 关闭设备

1,停止设备

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;
}

2,取消映射

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

3,关闭设备

close(fd);

这里把应用编写的一个主要流程摘抄了出来,后面的文章会逐个分析ioctl,深入到驱动去学习v4l2。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dianlong_lee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值