通过V4L2 API获取Camera数据

通过V4l2 API获取Camera数据
Views: 365
1 0
Read Time:3 Minute, 42 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的buffer空间内,可以进行后续使用。

步骤十一:停止推流

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

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

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

极客,文学爱好者。如果你也喜欢我,那你大可不必害羞。

Articles: 85

Leave a Reply

Your email address will not be published. Required fields are marked *

en_USEN