IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    通过V4L2 API获取Camera数据

    FranzKafka95发表于 2023-10-16 09:44:29
    love 0
    Read Time:3 Minute, 41 Second

    当我们在Linux或者Android平台开发Camera相关应用时,都需要从Camera驱动中获取到Camera数据,用于后续的Preview、Capture或者Snapshot等,此时我们往往会借助V4L2框架来实现。

    V4L2的全称为Video For Linux Version Two;是当前Linux或者类Linux平台中广泛使用的多媒体框架,为上层软件提供多种硬件能力,如codec encoder、decoder,camera等。V4L2在软件层次上属于内核框架层,用户态程序在与V4L2框架进行交互时,往往都需要通过ioctl的系统调用来实现。本篇文章将根据实际应用中的流程,介绍如何通过V4L2 API获取Camera数据。

    步骤一:通过open系统调用获取对应Camera设备的文件句柄

    在通过v4l2接口获取Camera数据前,我们需要知道Camera设备的设备节点;接着我们就可以通过open函数的系统调用来打开相应的设备节点,Camera的设备节点都位于/dev/video*下。

    //flag O_RDWR表明可读写
    mDeviceFd = open(deviceName, O_RDWR, 0);

    步骤二:通过ioctl系统调用判定是否是有效的Camera设备及其支持的功能

    v4l2_capability caps;
    int result = ioctl(mDeviceFd, VIDIOC_QUERYCAP, &caps);

    返回值小于0则表明查询失败,反之则查询成功;在查询成功的情况下判断其配置位值是否为1,这里我们查询的是两个配置:V4L2_CAP_VIDEO_CAPTURE、V4L2_CAP_STREAMING。

    其中v4l2_capability结构体的定义如下:

    /**
      * struct v4l2_capability - Describes V4L2 device caps returned by VIDIOC_QUERYCAP
      *
      * @driver:           name of the driver module (e.g. "bttv")
      * @card:     name of the card (e.g. "Hauppauge WinTV")
      * @bus_info:         name of the bus (e.g. "PCI:" + pci_name(pci_dev) )
      * @version:          KERNEL_VERSION
      * @capabilities: capabilities of the physical device as a whole
      * @device_caps:  capabilities accessed via this particular device (node)
      * @reserved:         reserved fields for future extensions
      */
    struct v4l2_capability {
        __u8    driver[16];
        __u8    card[32];
        __u8    bus_info[32];
        __u32   version;
        __u32   capabilities;
        __u32   device_caps;
        __u32   reserved[3];
    };

    如果我们是在Linux系统中,可以使用v4l2-ctl命令来查看某个Camera设备支持的具体的配置信息:

    marcus@goliat:~$ v4l2-ctl -d /dev/video4  --info
    Driver Info:
        Driver name      : uvcvideo
        Card type        : USB 2.0 Camera: USB Camera
        Bus info         : usb-0000:00:14.0-8.3.1.1
        Driver version   : 6.0.8
        Capabilities     : 0x84a00001
            Video Capture
            Metadata Capture
            Streaming
            Extended Pix Format
            Device Capabilities
        Device Caps      : 0x04200001
            Video Capture
            Streaming
            Extended Pix Format

    步骤三:通过ioctl系统调用查询当前Camera设备支持的数据格式,为下一步做准备

    v4l2_fmtdesc formatDescription;
    formatDescription.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int result=ioctl(mDeviceFd, VIDIOC_ENUM_FMT, &formatDescription);

    其中v4l2_fmtdesc结构体的定义如下:

    struct v4l2_fmtdesc {
        __u32   index;
        __u32   type;
        __u32   flags;
        __u32   description;
        __u32   pixelformat;
    }

    返回值小于0则查询失败,反之则查询成功;在查询成功时我们可以通过formatDescription.pixelformat值判断其支持的格式类型,常见的数据类型包括:

    V4L2_PIX_FMT_YUYV
    V4L2_PIX_FMT_NV21
    V4L2_PIX_FMT_NV16
    V4L2_PIX_FMT_YVU420
    V4L2_PIX_FMT_RGB32
    V4L2_PIX_FMT_ARGB32

    步骤四:设定Camera设备的输出格式,该输出格式必须是步骤三中所支持的;一般我们会根据实际需要结合Camera设备自身的能力设定合适的输出格式,输出格式的不同会影响到数据量的大小。

    v4l2_format format;
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    format.fmt.pix.width = width;
    format.fmt.pix.height = height;
    //设置数据格式
    ioctl(mDeviceFd, VIDIOC_S_FMT, &format)

    在这一步中,所涉及到的v4l2_format结构体的定义如下:

    /**
     * struct v4l2_format - stream data format
     * @type:   enum v4l2_buf_type; type of the data stream
     * @pix:    definition of an image format
     * @pix_mp: definition of a multiplanar image format
     * @win:    definition of an overlaid image
     * @vbi:    raw VBI capture or output parameters
     * @sliced: sliced VBI capture or output parameters
     * @raw_data:       placeholder for future extensions and custom formats
     * @fmt:    union of @pix, @pix_mp, @win, @vbi, @sliced, @sdr, @meta
     *          and @raw_data
     */
    struct v4l2_format {
        __u32    type;
        union {
            struct v4l2_pix_format              pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
            struct v4l2_pix_format_mplane       pix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
            struct v4l2_window          win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
            struct v4l2_vbi_format              vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */
            struct v4l2_sliced_vbi_format       sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
            struct v4l2_sdr_format              sdr;     /* V4L2_BUF_TYPE_SDR_CAPTURE */
            struct v4l2_meta_format             meta;    /* V4L2_BUF_TYPE_META_CAPTURE */
            __u8        raw_data[200];                   /* user-defined */
        } fmt;
    };

    一般我们还需要进行二次校验,确保输出数据符合要求:

    //获取数据格式
    ioctl(mDeviceFd, VIDIOC_G_FMT, &format)

    步骤五:申请Buffer区域用于推流

    v4l2_requestbuffers bufrequest;
    bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    bufrequest.memory = V4L2_MEMORY_MMAP;//使用mmap进行映射
    bufrequest.count = 1;//buffer数量,一般我们申请一个即可
    ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest)

    返回值小于0则申请失败,反之则成功,接下来我们就可以开始构造v4l2_buffer对象用于接收Camera数据了。

    其中所涉及到的结构体v4l2_requestbuffers定义如下:

    struct v4l2_requestbuffers {
        __u32                   count;
        __u32                   type;           /* enum v4l2_buf_type */
        __u32                   memory;         /* enum v4l2_memory */
        __u32                   capabilities;
        __u8                    flags;
        __u8                    reserved[3];
    };

    这里解读一下几个比较重要的参数:

    count:用于表征buffer的数量,我们需要合适的数量;如果数量设定过小,可能会导致driver在填充Camera数据时没有可用的buffer,数据设定过大则会消费额外的内存空间。

    type:buffer所存储的数据类型,针对Camera设备,一般我们将此项设定为V4L2_BUF_TYPE_VIDEO_CAPTURE。

    memory:设定推流数据的更新方式,可选值包括:V4L2_MEMORY_MMAP、V4L2_MEMORY_USERPTR 、V4L2_MEMORY_DMABUF。

    步骤六:查询v4l2为我们创建的buffer信息

    v4l2_buffer buf = {0};
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = 0;//index表明其次序
    int res = ioctl(mDeviceFd, VIDIOC_QUERYBUF, &buf);

    返回值小于0则表明查询失败,反之则成功,我们可以得到如下信息:

    buf.m.offset:偏移量
    buf.length:长度
    buf.flags:标志位

    步骤七:通过mmap虚拟内存映射,对应步骤六中的设定的memory为 V4L2_MEMORY_MMAP ,从而拿到Camera数据对应本地进程空间内的指针:

    buffer = (u_int8_t*)mmap (NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, mDeviceFd, buf.m.offset);
    if(buffer == MAP_FAILED)
    {
    	return false;
    }
    //memset清0,避免杂数据
    memset(buffer, 0, buf.length);

    这一步的目的是为了提高效率,通过虚拟内存映射可以直接拿到Camera数据;

    步骤八:通知Camera驱动,buffer入列

    ioctl(mDeviceFd, VIDIOC_QBUF, &buf)

    步骤九:通知Camera驱动,开始推流

    const int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(mDeviceFd, VIDIOC_STREAMON, &type)

    步骤十:通过Camea驱动,buffer出列

    struct v4l2_buffer buf = {
                    .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
                    .memory = V4L2_MEMORY_MMAP
    };
    ioctl(mDeviceFd, VIDIOC_DQBUF, &buf);

    自此我们就将camera数据存放值了类型为v4l2_buffer的buf空间内,可以进行后续使用。

    步骤十一:停止推流

    当我们需要关闭Camera设备或者不再需要Camera数据时,我们需要停止推流:

    const int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(mDeviceFd, VIDIOC_STREAMOFF, &type)

    Happy
    Happy
    0 0 %
    Sad
    Sad
    0 0 %
    Excited
    Excited
    0 0 %
    Sleepy
    Sleepy
    0 0 %
    Angry
    Angry
    0 0 %
    Surprise
    Surprise
    0 0 %

    The post 通过V4L2 API获取Camera数据 first appeared on FranzKafka Blog.



沪ICP备19023445号-2号
友情链接