本文鏈接:https://blog.csdn.net/coroutines/article/details/70141086
可參考:http://www.it610.com/article/4522348.htm //v4l2官方翻譯
基於V4L2的應用,通常面臨着大塊數據的讀取與拷貝等問題。尤其在嵌入式系統中,對於實時性能要求較高的應用,拷貝會花上幾十個ms的時間,這通常輕則造成用戶體驗差,重則導致產品質量不達標。V4L2 Framework定義了幾種不同的方式,用於從設備中讀取數據,這篇文章簡要介紹下在Streaming I/O模式下,如何使用這幾種數據的獲取與使用方法。Streaming I/O設計的目的就是爲了減少在數據處理的各個環節中,拷貝的次數,從而實現各階段硬件的無縫配合。
本文針對的是USB Camera (Capture)設備。
1. 設備支持
對於設備特性來說,需要設備支持Streaming能力,這個需要通過V4L2的Capability來判斷,方式如下:
struct v4l2_capability _cap;
ioctl( _fd, VIDIOC_QUERYCAP, &_cap );
其中,_fd是打開的V4L2設備的描述符,通過:
if( (_cap.capabilities & V4L2_CAP_STREAMING) != 0 ) {
printf( " V4L2_CAP_STREAMING" );
}
判斷是否支持Streaming方式訪問。
2 Memory Map
通過Memory Map訪問V4L2設備驅動中分配的內存。設備收到的數據存在驅動內的Buffer中,通過Map方式,將內存Map到用戶空間。使用這種方式只有指向這段內存的用戶空間指針在各個處理環節中傳遞,不會發生真實的數據拷貝。
struct v4l2_requestbuffers reqbuf;
struct {
void *start;
size_t length;
} *buffers;
unsigned int i;
memset(&reqbuf, 0, sizeof(reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP;
reqbuf.count = 20;
if (-1 == ioctl (fd, VIDIOC_REQBUFS, &reqbuf)) {
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;
memset(&buffer, 0, sizeof(buffer));
buffer.type = reqbuf.type;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = i;
if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buffer)) {
perror("VIDIOC_QUERYBUF");
exit(EXIT_FAILURE);
}
buffers[i].length = buffer.length; /* remember for munmap() */
buffers[i].start = mmap(NULL, buffer.length,
PROT_READ | PROT_WRITE, /* recommended */
MAP_SHARED, /* recommended */
fd, buffer.m.offset);
if (MAP_FAILED == buffers[i].start) {
/* If you do not exit here you should unmap() and free()
the buffers mapped so far. */
perror("mmap");
exit(EXIT_FAILURE);
}
}
/* Cleanup. */
for (i = 0; i < reqbuf.count; i++)
munmap(buffers[i].start, buffers[i].length);
用戶程序首先需要通過VIDIOC_REQBUFS,通知驅動進行驅動內內存分配;之後通過VIDIOC_QUERYBUF取得驅動中各內存塊的基本信息,主要是Buffer長度和Offset;取得這些信息後,通過mmap,將內存map到User Space中使用。在驅動中,V4L2對於Buffer的使用是隊列形式,Buffer出隊後,再次入隊之前,驅動無法再使用這個Buffer,因此申請的Buffer個數通常是多個,避免數據丟失。
在使用過程中,需要通過poll操作,判斷是否有數據到達,然後通過VIDIOC_DQBUF,取得當前有數據的Buffer,通過Buffer的Index屬性,找到對應的User Space指針,交由下個環節處理;處理完成後,通過VIDIOC_QBUF,將Buffer重新入隊。
3 User Pointers
User Pointers方式會將用戶空間分配的內存指針及長度傳遞給V4L2驅動(雖然是用戶空間分配,但不一定是在堆空間上分配的內存,可以是通過其它方式映射出來的內存,比如從另一個設備驅動中),這樣數據到達後,可以直接傳遞這個指針到下個環節中處理。初始化方式如下:
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);
}
4 DMA buffer importing
DMA方式的初始化方式如下:
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);
}
DMA方式的傳輸基於文件描述符進行,fd的傳遞是通過VIDIOC_QBUF中的描述符設置:
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;
}
poll操作返回後,可以通過這個dmafd進行下一步處理。