树莓派学习专题<10>:使用V4L2驱动获取摄像头数据--申请和管理缓冲区
树莓派学习专题<10>:使用V4L2驱动获取摄像头数据--申请和管理缓冲区
- 1. 申请和管理缓冲区代码
- 2. 代码解析
- 3. 实测结果
1. 申请和管理缓冲区代码
/* 数据缓冲区 */
typedef struct tag_BufDesc
{void *pvBufPtr ;size_t szBufSize ;
} stBufDesc ;stBufDesc *g_pstBufDesc ;/*********************************************** other codes * ********************************************//*******************************************************************************
- Function : __AllocateBuffers
- Description : 本函数获取缓冲区,并将缓冲区映射到用户空间。
- Input : VOID
- Output : NULL
- Return : VOID
- Others :
*******************************************************************************/
void __AllocateBuffers(void)
{struct v4l2_requestbuffers stReqBuf ;struct v4l2_buffer stBuf ;int iLoop ;memset(&stReqBuf, 0, sizeof(stReqBuf)) ;/* 申请缓冲区 */stReqBuf.count = 4 ;stReqBuf.memory = V4L2_MEMORY_MMAP ;stReqBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;if(-1 == ioctl(g_iFDVideo, VIDIOC_REQBUFS, &stReqBuf)){printf("Request buffers failed.\n") ;exit(-1) ;}/* 申请用户空间缓冲区描述符空间 */g_pstBufDesc = (stBufDesc *)calloc(stReqBuf.count, sizeof(stBufDesc)) ;if(NULL == g_pstBufDesc){printf("Calloc buffer descriptor failed.\n") ;exit(-1) ;}/* 将申请的缓冲区映射到用户空间 */for(iLoop = 0 ; iLoop < stReqBuf.count ; iLoop++){memset(&stBuf, 0, sizeof(stBuf)) ;stBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;stBuf.index = iLoop;stBuf.memory = V4L2_MEMORY_MMAP;if(-1 == ioctl(g_iFDVideo, VIDIOC_QUERYBUF, &stBuf)){printf("Get buffer information failed.\n") ;exit(-1) ;}g_pstBufDesc[iLoop].szBufSize = stBuf.length;g_pstBufDesc[iLoop].pvBufPtr = mmap(NULL,stBuf.length,PROT_READ | PROT_WRITE,MAP_SHARED,g_iFDVideo,stBuf.m.offset);if(MAP_FAILED == g_pstBufDesc[iLoop].pvBufPtr){printf("Map V4L2 buffer to user space failed.\n") ;exit(-1) ;}if(-1 == ioctl(g_iFDVideo, VIDIOC_QBUF, &stBuf)){printf("Queue buffer failed.\n") ;exit(-1) ;}}printf("--Buffer information---------------------------------------\n") ;for(iLoop = 0 ; iLoop < stReqBuf.count ; iLoop++){printf("-- Frame buffer :%d address :0x%x size:%d\n", iLoop, g_pstBufDesc[iLoop].pvBufPtr, g_pstBufDesc[iLoop].szBufSize) ;}printf("-----------------------------------------------------------\n") ;return ;
}
2. 代码解析
命令 VIDIOC_REQBUFS 可申请视频帧缓冲。该命令的定义为:
#define VIDIOC_REQBUFS _IOWR('V', 8, struct v4l2_requestbuffers)
可见该命令需要一个 struct 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 填写需要申请多少块缓存。
- type 填写 V4L2_BUF_TYPE_VIDEO_CAPTURE 。
- memory 有如下四种选项。V4L2_MEMORY_MMAP 选项使用 memory mapping 型缓存,此时由驱动在内核空间开辟缓冲区,并将缓冲区告知应用程序;V4L2_MEMORY_USERPTR 选项,用户负责开辟缓冲区,并将缓冲区通告给驱动程序。V4L2_MEMORY_OVERLAY 尚未实现。V4L2_MEMORY_DMABUF 试用于两种设备之间做数据拷贝。MMAP 方式和 USERPTR 方式,都无需数据拷贝,由驱动直接将图像数据写入到缓冲。用户程序只需要管理和切换这些缓冲。
enum v4l2_memory {V4L2_MEMORY_MMAP = 1,V4L2_MEMORY_USERPTR = 2,V4L2_MEMORY_OVERLAY = 3,V4L2_MEMORY_DMABUF = 4,
};
VIDIOC_REQBUFS命令申请缓存,但是并没有返回申请缓存的地址。ioctl命令 VIDIOC_QUERYBUF 可以获取申请到的缓存的信息。该命令的定义如下:
#define VIDIOC_QUERYBUF _IOWR('V', 9, struct v4l2_buffer)
其中,struct v4l2_buffer 的定义如下:
struct v4l2_buffer {__u32 index;__u32 type;__u32 bytesused;__u32 flags;__u32 field;struct timeval timestamp;struct v4l2_timecode timecode;__u32 sequence;/* memory location */__u32 memory;union {__u32 offset;unsigned long userptr;struct v4l2_plane *planes;__s32 fd;} m;__u32 length;__u32 reserved2;union {__s32 request_fd;__u32 reserved;};
};
查询时,index 需要依次从0开始填写。例如上面代码中申请了4块缓存,则需要使用 VIDIOC_QUERYBUF 命令查询4次,index依次为0到3。type 字段和前述的相同,填写 V4L2_BUF_TYPE_VIDEO_CAPTURE 。memory 字段填写 V4L2_MEMORY_MMAP,和前面设定的保持一致。
VIDIOC_QUERYBUF 命令执行后,需要关注offset 字段。该字段标志了实际分配的内存的位置。稍后mmap函数需要这个参数。
length 字段表示申请了多大的缓存。因为前面已经设定了输出的帧格式(长宽等),对于非压缩的输出格式,例如YUV420,YUYV,或者RGB格式,一帧画面需要多大的空间来保存,驱动会自己计算。这里会返回申请到的缓冲块大小。
最后,使用 mmap 函数,将内核空间的缓存和用户空间事先定义的缓存指针映射起来,这样用户空间就可以用缓存指针来操作这些缓存。
3. 实测结果
材料:
- Raspberry Pi 4B计算机;
- IMX219摄像头组件。
运行上述代码,查看打印信息:
--Buffer information---------------------------------------
-- Frame buffer :0 address :0xf7b63000 size:1843200
-- Frame buffer :1 address :0xf79a1000 size:1843200
-- Frame buffer :2 address :0xf77df000 size:1843200
-- Frame buffer :3 address :0xf761d000 size:1843200
-----------------------------------------------------------
注意:因为我将摄像头输出的分辨率设定为了1280 X 720,输出格式为YUYV(每像素占用两字节),因此每帧画面需要的缓存大小为:
1280 * 720 * 2B = 1843200B