Linux--V4L2应用程序开发(一)数据采集及问题

一、V4L2介绍

Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。(Video4Linux2)是 Linux 内核中用于捕获视频数据的框架和 API。

它提供了一套标准化的接口,方便开发者与各种视频硬件设备进行交互,并支持丰富的视频格式和功能。通过使用 V4L2,开发者可以构建高效的视频捕获和处理应用程序。

V4L2支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少。

V4L2 的主要功能和特性

  1. 设备抽象:V4L2 提供了一种统一的接口来抽象不同的硬件设备,使得应用程序无需关心具体的硬件实现细节。

  2. 视频捕获:支持从视频捕获设备(如摄像头)中获取视频数据。V4L2 提供了多种视频格式和帧率的支持。

  3. 视频输出:支持将视频数据发送到视频输出设备(如显示器或其他显示设备)。

  4. 控制接口:允许应用程序控制视频设备的各种参数,如亮度、对比度、饱和度等。

  5. 内存映射和用户指针:提供了高效的数据传输机制,包括内存映射(mmap)和用户指针(user pointer)方式,使得视频数据的捕获和处理更加高效。

二、 buffer的管理

使用摄像头时,核心是"获得数据"。所以先讲如何获取数据,即如何得到buffer。

摄像头采集数据时,是一帧又一帧地连续采集。所以需要申请若干个buffer,驱动程序把数据放入buffer,APP从buffer得到数据。这些buffer可以使用链表来管理。

驱动程序周而复始地做如下事情:

  • 从硬件采集到数据

  • 把"空闲链表"取出buffer,把数据存入buffer

  • 把含有数据的buffer放入"完成链表"

APP也会周而复始地做如下事情:

  • 监测"完成链表",等待它含有buffer

  • 从"完成链表"中取出buffer

  • 处理数据

  • 把buffer放入"空闲链表"

链表操作示意图如下:

三、 完整的使用流程

参考mjpg-streamer和video2lcd,总结了摄像头的使用流程,如下:、

列出帧细节

  • open:打开设备节点/dev/videoX

  • ioctl VIDIOC_QUERYCAP:Query Capbility,查询能力,比如

    • 确认它是否是"捕获设备",因为有些节点是输出设备

    • 确认它是否支持mmap操作,还是仅支持read/write操作

  • ioctl VIDIOC_ENUM_FMT:枚举它支持的格式

  • ioctl VIDIOC_S_FMT:在上面枚举出来的格式里,选择一个来设置格式

申请buffer用来放置摄像头数据

  • ioctl VIDIOC_REQBUFS:申请buffer,APP可以申请很多个buffer,但是驱动程序不一定能申请到

  • ioctl VIDIOC_QUERYBUF和mmap:查询buffer信息、映射

    • 如果申请到了N个buffer,这个ioctl就应该执行N次

    • 执行mmap后,APP就可以直接读写这些buffer

  • ioctl VIDIOC_QBUF:把buffer放入"空闲链表"

    • 如果申请到了N个buffer,这个ioctl就应该执行N次

获取数据

  • ioctl VIDIOC_STREAMON:启动摄像头

存储数据

  • 这里是一个循环:使用poll/select监测buffer,然后从"完成链表"中取出buffer,处理后再放入"空闲链表"

    • poll/select

    • ioctl VIDIOC_DQBUF:从"完成链表"中取出buffer

    • 处理:前面使用mmap映射了每个buffer的地址,处理时就可以直接使用地址来访问buffer

    • ioclt VIDIOC_QBUF:把buffer放入"空闲链表"

  • ioctl VIDIOC_STREAMOFF:停止摄像头

四、列出帧数据遇到的问题:

问题一、linux/videodev.h:没有那个文件或目录

解决:

sudo apt-get install libv4l-dev
cd /usr/include/linux
sudo ln -s ../libv4l1-videodev.h videodev.h

五、帧数据代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h>          /* for videodev2.h */
#include <linux/videodev2.h>

/* ./video_test </dev/video0> */

int main(int argc, char **argv)
{
    int fd;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_frmsizeenum fsenum;
    int fmt_index = 0;
    int frame_index = 0;

    if (argc != 2)
    {
        printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]);
        return -1;
    }

    /* open */
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("can not open %s\n", argv[1]);
        return -1;
    }

    while (1)
    {
        /* 枚举格式 */
        fmtdesc.index = fmt_index;  // 比如从0开始
        fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 指定type为"捕获"
        if (0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
            break;

        frame_index = 0;
        while (1)
        {
            /* 枚举这种格式所支持的帧大小 */
            memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
            fsenum.pixel_format = fmtdesc.pixelformat;
            fsenum.index = frame_index;

            if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
            {
                printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, frame_index, fsenum.discrete.width, fsenum.discrete.height);
            }
            else
            {
                break;
            }

            frame_index++;
        }

        fmt_index++;
    }

    return 0;
}

调用ioctl VIDIOC_ENUM_FMT可以枚举摄像头支持的格式,但是无法获得更多细节(比如支持哪些分辨率),

调用ioctl VIDIOC_G_FMT可以获得"当前的格式",包括分辨率等细节,但是无法获得其他格式的细节。

需要结合VIDIOC_ENUM_FMT、VIDIOC_ENUM_FRAMESIZES这2个ioctl来获得这些细节:

  • VIDIOC_ENUM_FMT:枚举格式

  • VIDIOC_ENUM_FRAMESIZES:枚举指定格式的帧大小(即分辨率)

1、重要结构体

用于描述视频设备支持的格式信息的结构体。

struct v4l2_fmtdesc {
    __u32            index;             /* Format number      */
    __u32            type;              /* enum v4l2_buf_type */
    __u32               flags;
    __u8            description[32];   /* Description string */
    __u32            pixelformat;       /* Format fourcc      */
    __u32            reserved[4];
};

字段解释

  1. index (__u32 index)

    • 这个字段用于指定格式的索引号。用户可以通过设置不同的索引号来查询设备支持的不同格式。索引号从0开始,每次递增1,直到查询不到更多的格式为止。
  2. type (__u32 type)

    • 这个字段指定了缓冲区类型,是一个枚举类型 enum v4l2_buf_type。它表示查询的格式属于哪种类型的缓冲区,常见的值包括 V4L2_BUF_TYPE_VIDEO_CAPTURE(视频捕获)和 V4L2_BUF_TYPE_VIDEO_OUTPUT(视频输出)。
  3. flags (__u32 flags)

    • 这个字段包含一些标志,用于描述格式的特性。目前这个字段的用途不多,通常设置为0。
  4. description (__u8 description[32])

    • 这个字段是一个描述字符串,提供了格式的文本描述信息。比如它可能会包含 "YUV 4:2:2" 或 "MJPEG" 等描述信息。最多可以包含32个字符,包括终止符。
  5. pixelformat (__u32 pixelformat)

    • 这个字段是一个四字符代码 (FourCC),用于标识像素格式。例如,V4L2_PIX_FMT_YUYV 表示 YUYV 格式,V4L2_PIX_FMT_MJPEG 表示 MJPEG 格式。FourCC 是一种常用的编码方式,使用四个字节(通常是ASCII字符)来唯一标识视频数据格式。
  6. reserved (__u32 reserved[4])

    • 这个字段是保留字段,通常设置为0。这些字段的存在是为了将来可能的扩展,以确保结构体的大小和对齐方式在不同的 V4L2 版本中保持一致。

用途:struct v4l2_fmtdesc 通常与 VIDIOC_ENUM_FMT ioctl 操作一起使用。应用程序使用这个结构体来枚举视频设备支持的所有格式。

struct v4l2_frmsizeenum {
    __u32            index;        /* Frame size number */
    __u32            pixel_format;    /* Pixel format */
    __u32            type;        /* Frame size type the device supports. */

    union {                    /* Frame size */
        struct v4l2_frmsize_discrete    discrete;
        struct v4l2_frmsize_stepwise    stepwise;
    };

    __u32   reserved[2];            /* Reserved space for future use */
};

字段解释

  1. __u32 index

    • 意义:帧尺寸编号(索引)。
    • 说明:用于枚举多个帧尺寸。调用者应从 index = 0 开始,并递增 index 来查询设备支持的所有帧尺寸。
  2. __u32 pixel_format

    • 意义:像素格式。
    • 说明:表示要查询的帧尺寸对应的像素格式。像素格式通常使用 V4L2 的四字符代码(fourcc)表示,如 V4L2_PIX_FMT_YUYV
  3. __u32 type

    • 意义:帧尺寸类型。
    • 说明:表示设备支持的帧尺寸类型。可能的值包括:
      • V4L2_FRMSIZE_TYPE_DISCRETE:设备支持的帧尺寸是离散的(单个特定的值)。
      • V4L2_FRMSIZE_TYPE_CONTINUOUS:设备支持连续的帧尺寸范围。
      • V4L2_FRMSIZE_TYPE_STEPWISE:设备支持的帧尺寸在一个范围内,并且可以按特定步长进行调整。
  4. union

    • 意义:包含帧尺寸信息的联合体。
    • 说明:根据 type 字段的值,联合体包含具体的帧尺寸信息。
      • struct v4l2_frmsize_discrete discrete:表示离散的帧尺寸。
      • struct v4l2_frmsize_stepwise stepwise:表示按步长调整的帧尺寸范围。
  5. __u32 reserved[2]

    • 意义:保留字段。
    • 说明:用于将来扩展使用,调用者应将其设置为 0。

2、重要函数

ioctl():

ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现,也可以用来读写数据。

3、应用程序ioctl()获得硬件信息的流程

  • 应用程序调用 ioctl 操作

    • 应用程序调用 ioctl 函数,向设备文件(如 /dev/video0)发送 VIDIOC_ENUM_FMT 请求,例如查询视频设备支持的格式,并传递一个 struct v4l2_fmtdesc 结构体来接收格式信息。
  • 设备驱动程序处理请求

    • 内核中的视频设备驱动程序接收到这个 ioctl 请求。系统内核会将这个请求传递给与 /dev/video0 关联的设备驱动程序。设备驱动程序中会有相应的 ioctl 处理函数来接收和处理这个请求。设备驱动程序接收到 ioctl 请求后,会执行相应的处理逻辑。以 V4L2 驱动程序为例,它会有一个 vidioc_enum_fmt 函数来处理 VIDIOC_ENUM_FMT 请求。
    • 关于内核如何找到设备文件匹配的驱动程序:内核通过设备文件的主设备号找到对应的设备驱动程序,次设备号用于区分不同的设备实例。设备驱动程序在加载时会注册主设备号,并提供处理设备文件操作的函数,这样内核就能将对设备文件的操作请求传递给合适的设备驱动程序进行处理。
  • 硬件设备返回信息

    • 设备驱动程序通过与硬件设备的通信(通常是通过内存映射 I/O 或总线接口,如 PCI、I2C、SPI 等)获取硬件设备的格式信息(信息其实就储存在硬件的各种寄存器里)。驱动程序根据硬件设备返回的数据,填充 v4l2_fmtdesc 结构体。
  • 驱动程序返回结果

    • 驱动程序将填充好的 struct v4l2_fmtdesc 结构体返回给应用程序,应用程序就可以读取其中的信息。

输出结果:

常见的视频格式

  1. RGB

    • 描述:红、绿、蓝三原色组成的图像格式。
    • 应用:广泛用于显示器、图像处理。
  2. YUV

    • 描述:一种颜色空间,将图像数据分为亮度(Y)和色度(U 和 V)分量。
    • 应用:视频压缩、传输和处理。
  3. MJPEG(Motion JPEG)

    • 描述:每一帧都使用 JPEG 压缩的格式。
    • 应用:简单视频编码,不需要太高压缩率的场景。
  4. H.264 / H.265

    • 描述:现代视频压缩标准,提供高压缩率和良好的视频质量。
    • 应用:流媒体、存储、视频会议等。

参考资料:百问网嵌入式专家-韦东山嵌入式专注于嵌入式课程及硬件研发

  • 34
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值