Physical Address:
ChongQing,China.
WebSite:
当我们在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。一般需要这两个配置都满足才认为是有效的Camera设备。
其中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)