概述
Video4Linux2是Linux系统下进行音影图像开发的应用编程接口,他比Video4Linux具有更好的扩张性和灵活性,支持更多的硬件设备。该框架遵循驱动框架设计理念:它具有用于表示设备数据实例的struct v4l2_device,用于引用子设备实例的struct v4l2_subdev,用于存储v4l2设备节点信息的struct video_device,以及用于描述v4l2文件句柄的struct v4l2_fh。v4l2框架可以选择与media framework结合变得更加灵活,方便运行时控制。linux源码中有提供一个基于v4l2框架的编程实例v4l2-pci-skeleton.c,通过这个实例可以去学会使用v4l2 driver api。
Stream I/O
v4l2有三个Stream I/O 方式:Memory Mapping、User Pointers、DMA buffer importing。这三种方式都可以在应用层获取到driver填充的camera图像数据。
1 、Memory Mapping
内存映射的方式有两种情况,一种是单平面的内存映射,一种是多平面的内存映射。
在多平面映射的时候,需要定义一个struct v4l2_plane数组然后把首地址赋值给m.planes,并且把plane的数量告知给v4l2_buffer。在VIDIOC_QUERYBUF成功后调用mmap映射每一个buffer、每一个plane,映射次数为请求的buffer数量*plane数量。从概念上来说,流驱动程序需要维护两个队列,一个是传入队列,一个是传出队列。这两个队列都以FIFO的形式运作。其实v4l2 core层已经帮你实现了出队入队的工作,在你VIDIOC_QBUF的时候帮你把buffer放进vb2_queue的queued_list中 ,在你VIDIOC_DQBUF的时候帮你从vb2_queue的done_list中取出已经填充好数据的buffer。需要注意的是,当4412接受完图像后会产生一个硬件中断,在中断处理函数中,需要调用vb2_buffer_done把填充完的buffer放进vb2_queue的done_wq中并唤醒等待队列。调用vb2_buffer_done的这个操作一般是平台驱动完成的,比如三星平台,会在fimc驱动程序中完成。
关于多平面的映射方法如下:
struct v4l2_requestbuffers reqbuf;
/* Our current format uses 3 planes per buffer */
#define FMT_NUM_PLANES = 3
struct {
void *start[FMT_NUM_PLANES];
size_t length[FMT_NUM_PLANES];
} *buffers;
unsigned int i, j;
memset(&reqbuf, 0, sizeof(reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
reqbuf.memory = V4L2_MEMORY_MMAP;
reqbuf.count = 20;
if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
if (errno == EINVAL)
printf("Video capturing or mmap-streaming is not supported\\n");
else
perror("VIDIOC_REQBUFS");
exit(EXIT_FAILURE);
}
/* We want at least five buffers. */
if (reqbuf.count < 5) {
/* You may need to free the buffers here. */
printf("Not enough buffer memory\\n");
exit(EXIT_FAILURE);
}
buffers = calloc(reqbuf.count, sizeof(*buffers));
assert(buffers != NULL);
for (i = 0; i < reqbuf.count; i++) {
struct v4l2_buffer buffer;
struct v4l2_plane planes[FMT_NUM_PLANES];
memset(&buffer, 0, sizeof(buffer));
buffer.type = reqbuf.type;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = i;
/* length in struct v4l2_buffer in multi-planar API stores the size
* of planes array. */
buffer.length = FMT_NUM_PLANES;
buffer.m.planes = planes;
if (ioctl(fd, VIDIOC_QUERYBUF, &buffer) < 0) {
perror("VIDIOC_QUERYBUF");
exit(EXIT_FAILURE);
}
/* Every plane has to be mapped separately */
for (j = 0; j < FMT_NUM_PLANES; j++) {
buffers[i].length[j] = buffer.m.planes[j].length; /* remember for munmap() */
buffers[i].start[j] = mmap(NULL, buffer.m.planes[j].length,
PROT_READ | PROT_WRITE, /* recommended */
MAP_SHARED, /* recommended */
fd, buffer.m.planes[j].m.offset);
if (MAP_FAILED == buffers[i].start[j]) {
/* If you do not exit here you should unmap() and free()
the buffers and planes mapped so far. */
perror("mmap");
exit(EXIT_FAILURE);
}
}
}
/* Cleanup. */
for (i = 0; i < reqbuf.count; i++)
for (j = 0; j < FMT_NUM_PLANES; j++)
munmap(buffers[i].start[j], buffers[i].length[j]);
2 、User Pointers
顾名思义,这种方式就是用户空间指针的意思,由应用层去分配连续的地址空间,然后以指针的形式传递给v4l2驱动程序,v4l2驱动程序会把获取到的图像数据保存到这个地址上面。在调用VIDIOC_REQBUFS的时候指定内存方式为V4L2_MEMORY_USERPTR。
struct v4l2_requestbuffers reqbuf;
memset (&reqbuf, 0, sizeof (reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_USERPTR;
if (ioctl (fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
if (errno == EINVAL)
printf ("Video capturing or user pointer streaming is not supported\\n");
else
perror ("VIDIOC_REQBUFS");
exit (EXIT_FAILURE);
}
struct v4l2_buffer结构体中有一个union,里面有个userptr成员,通过设置这个成员可以把用户层分配的地址空间的首地址给v4l2驱动程序。
union {
__u32 offset;
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
3 、DMA buffer importing
DMABUF框架提供了一种用于多个设备间共享缓冲区的通用方法。支持DMABUF的设备驱动程序可以把DMA缓冲区以文件描述符的形式导出到用户空间(导出者角色);也可以从用户空间把从不同设备或者同一个设备导出来的文件描述符导入到DMA缓冲区(导入者角色)。这里讲下导入者这种方式的应用。
在VIDIOC_REQBUFS的时候告知v4l2要使用DMA buffer importing。
struct v4l2_requestbuffers reqbuf;
memset(&reqbuf, 0, sizeof (reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_DMABUF;
reqbuf.count = 1;
if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
if (errno == EINVAL)
printf("Video capturing or DMABUF streaming is not supported\\n");
else
perror("VIDIOC_REQBUFS");
exit(EXIT_FAILURE);
}
使用单平面的api入队dma buffer
int buffer_queue(int v4lfd, int index, int dmafd)
{
struct v4l2_buffer buf;
memset(&buf, 0, sizeof buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_DMABUF;
buf.index = index;
buf.m.fd = dmafd;
if (ioctl(v4lfd, VIDIOC_QBUF, &buf) == -1) {
perror("VIDIOC_QBUF");
return -1;
}
return 0;
}
使用多平面的api入队dma buffer
int buffer_queue_mp(int v4lfd, int index, int dmafd[], int n_planes)
{
struct v4l2_buffer buf;
struct v4l2_plane planes[VIDEO_MAX_PLANES];
int i;
memset(&buf, 0, sizeof buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_DMABUF;
buf.index = index;
buf.m.planes = planes;
buf.length = n_planes;
memset(&planes, 0, sizeof planes);
for (i = 0; i < n_planes; ++i)
buf.m.planes[i].m.fd = dmafd[i];
if (ioctl(v4lfd, VIDIOC_QBUF, &buf) == -1) {
perror("VIDIOC_QBUF");
return -1;
}
return 0