V4L2文档翻译(十二)
2014-05-05 10:54
330 查看
http://linuxtv.org/downloads/v4l-dvb-apis/io.html
在打开一个V4L2设备后会自动选择使用经典的I/O方法read()和write(),当驱动不支持写或读时会失败。
其他的方法必须通过协商。应用程序通过VIDIOC_REQBUFS ioctl来选择内存映射或用户缓存的I/O流,I/O同步方法未定义。
尽管应用程序不直接接受图片数据,视频overlay也可被考虑做其他I/O方法。初始化视频overlay是通过VIDIOC_S_FMT ioctl,其他信息见“视频Overlay接口”章节。
通常一个I/O方法,包括overlay,是与每一个文件描述符关联的。只是以下情况是例外,若应用程序不与驱动进行数据交换(表盘程序,见“打开关闭设备”章节),以及驱动准许使用同一个文件描述符进行视频捕捉和overlay,这是为了对V4L和早期版本的V4L2兼容。
VIDIOC_S_FMT和VIDIOC_REQBUFS会在某方面允许这种情况,但是对于关闭和重打开设备来说简单驱动不需要支持I/O方法切换(第一次切换read/write后)。之后的小节描述了各种I/O方法的细节。
驱动可能需要CPU进行数据拷贝操作,但是他们也可以通过支持DMA来处理用户内存,所以此I/O方法并不一定比其他只是交换缓冲区指针的方法效率低。因为像帧计数器或时间戳传输的无元数据的一些类型,所以它不优先考虑,此信息对于识别掉帧和与其他数据流同步来说很有必要。无论怎样,这是个简单的IO方法,需要很少甚至不需要任何的设置来交换数据。它允许在命令行中使用(vidvtrl工具的虚构的):
应用程序使用read()函数从设备中读取,使用write()函数对设备进行写入,若驱动与应用程序交换数据则必须声明一个I/O方法,但是不一定像是这样。若读取或写入支持了,那么驱动还必须支持select()和poll()函数。
在驱动等级select()和poll()是一样的,select()是非常重要的选项。
应用程序和驱动只交换缓存指针,而数据本身并不被拷贝的I/O方法叫做“流”。内存映射即是将设备内存中的缓存映射到应用程序的地址空间去,设备内存可以是比如做视频捕捉等时显卡上的视频内存。无论如何,作为长久以来效率最高的I/O方法,非常多的驱动支持流,他们将在可DMA操作的主内存中申请缓存。
一个驱动可支持许多套缓存,每一套通过一个独一无二的缓存类型值定义。他们都是互相独立的,而且每一套缓存处理不同的数据类型。要同时访问不同的缓存必须通过使用不同的文件描述符。
应用程序通过VIDIOC_REQBUFS ioctl申请设备缓存,同时要带入需求的缓存数量和缓存类型,比如V4L2_BUF_TYPE_VIDEO_CAPTURE。这个ioctl也可以用来改变缓存数量或释放已申请的内存,对已映射的缓存无效。
应用程序在操作缓存前必须要通过mmap()函数将他们映射到应用程序地址空间中,而所映射的缓存在设备内存中具体哪个位置是由VIDIOC_QUERYBUF ioctl决定的。在单一平面API中,返回的struct v4l2_buffer中m.offset和length成员用作mmap()函数的第六个和第二个参数。多平面API中,struct v4l2_buffer结构体包含了struct v4l2_plane结构体集合,每一个都包含了各自的,m.offset和length。当使用多平面API时,每个缓存冲的每个平面都需要分别映射,所以调用mmap()的次数就等于缓存数乘以每个缓存内的平面数量。offset和length的值必须不能修改。切记,缓存被分配在物理内存中,不同于虚拟内存的是,可以与硬盘做交换。应用程序在执行了munmap()函数后要尽快释放缓存。
例3.1 单平面API中的缓存映
例3.2 多平面API中的缓存映射
概念上讲,流驱动包含两个缓存序列,一个传入序列、一个传出序列。他们使同步捕捉或输出操作从应用程序分开来锁定到视频时钟上,因为应用程序可能受限于随机的硬盘或网络延迟,还有其他进程的抢占,因此减少了数据丢失的可能性。这些序列由FIFO管道组成,缓存可以在输入管道上进行输出,可以在输出管道上进行捕捉。
驱动可能会一直需要一个队列缓存的最小数,在没有缓存限制的情况下应用程序可以预先装配、解除和处理。他们还可以在缓存解除前以不同的顺序进行队列装配,驱动可以用任何顺序对空缓存进行填充。缓存的索引号并没有任何规定,只要是唯一的就好。
初始化所有映射缓存要在其没有进入队列的时候进行,驱动很难做到这一点。对于捕捉应用来说,通常是先装配所有已映射缓存,然后开始捕捉并进入读循环中。应用程序会一直等到一个被填充的缓存能被解除(移除队列),然后若不再需要其数据了就重新将其放入队列。输出程序填充缓存,然后将其放入队列中,当累计了足够的缓存后开始VIDIOC_STREAMON。在写循环中,若应用程序把空闲缓存用完了,那么必须等待有空的buffer可以移除队列和再利用。
应用程序通过VIDIOC_QBUF和VIDIOC_DQBUF来使一个缓存入列和出列。缓存的状态是已映射、已入列、满还是空可以在任何时候通过VIDIOC_QUERYBUF ioctl进行查询。有两个方法使应用程序的执行挂起来等待可出列的缓存。VIDIOC_DQBUF会在传出序列中没有缓存时自动阻塞,若打开设备的时候设置了O_NONBLOCK标志则VIDIOC_DQBUF会在没有可用缓存时返回EAGAIN。select()或poll()函数总是可用的。
应用程序通过调用VIDIOC_STREAMON和VIDIOC_STREAMOFF ioctl来开始、停止捕捉或输出。注意VIDIOC_STREAMOFF会移除掉所有的缓存。因为在多任务操作系统中并没有“当前”的概念,所以如果应用程序需要与其他项目进行同步,那么它应该检查struct v4l2_buffer中捕捉的或输出的缓存timestamp。
声明了内存映射I/O的驱动必须支持VIDIOC_REQBUFS, VIDIOC_QUERYBUF, VIDIOC_QBUF, VIDIOC_DQBUF, VIDIOC_STREAMON和VIDIOC_STREAMOFF ioctl,和mmap()、munmap()、select()及poll()函数。
此I/O方法结合了高级读写以及内存映射方法。缓存(平面)通过应用程序本身进行申请,可以贮存在虚拟或共享内存中,需要交换的只是数据指针。这些指针和元数据在struct v4l2_buffer(对多平面API来说是struct v4l2_plane)中。若调用VIDIOC_REQBUFS且带有相应缓存类型,则驱动必须切换到用户指针I/O模式。不需要预先分配缓存(平面),因此他们没有索引,也不能像映射缓存那样通过VIDIOC_QUERYBUF ioctl进行查询。
例3.3 初始化用户指针I/O流
缓存地址和大小通过VIDIOC_QBUF ioctl快速写入,尽管缓存通常是循环的,应用程序也可以在每个VIDIOC_QBUF调用中使用不同的地址和大小进行区分。如果硬件需要,驱动将在物理内存中进行内存页交换来创建一个连续的内存区域,这对处于内核虚拟内存子系统中的应用来说很显然就有必要。当缓存页被交换到了磁盘上,他们会被拿回来并锁定到物理内存中为了DMA使用。
填充的或显示的缓存通过VIDIOC_DQBUF ioctl出列。驱动可以在DMA完成和这个ioctl间的任何时候解锁内存页。当VIDIOC_STREAMOFF、VIDIOC_REQBUFS调用或设备关闭时内存也会被解锁。应用程序必须要注意,不要把未出列的缓存释放掉。那样的话,这些缓存一直是锁定的,浪费着物理内存。还有一点需要注意的是,当内存被应用程序释放然后利用为其他目的的时候,驱动并不会被通知,比如完成了请求的DMA以及更新了有效数据。
对于捕捉应用程序来说,将一定数量的空缓存入列是很正常的,他们是为了开启捕捉以及进入读循环。应用程序会等待一个填充后的缓存何时能被出列,然后当其数据不再需要的时候将此缓存重新入列。输出应用程序填充缓存,然后将缓存入列,当累积了足够的缓存输出动作就开始了。在写循环中,若应用程序用光了空闲缓存,那么他必须等到某个空缓存可以被出列,然后重新利用它。应用程序通过挂起的方式等待缓存有两种途径,默认的是当传出序列中没有缓存时VIDIOC_DQBUF会阻塞住。而如果在打开设备时候open()设定了O_NONBLOCK参数,那么VIDIOC_DQBUF在这种情况下会直接返回EAGAIN错误代码。这select()和poll()往往都是有效的。
可以通过VIDIOC_STREAMON和VIDIOC_STREAMOFF来控制捕捉或输出应用程序的开始和停止。需要注意的是,VIDIOC_STREAMOFF存在着一定的副作用,就是它会将所有序列冲的缓存移除,并将他们解锁。由于在多任务系统中并没有“当前”的概念,所以如果一个应用程序需要同其他事件进行同步,应该通过检查捕捉或输出的缓存struct v4l2_buffer中的timestamp成员来达成同步的目的。
实现用户指针I/O方法的驱动必须支持VIDIOC_REQBUFS,VIDIOC_QBUF,VIDIOC_DQBUF,VIDIOC_STREAMON及VIDIOC_STREAMOFF ioctl,还有select()和poll()函数。
第三节:输入和输出
V4L2 API定义了一些不同的方法来从设备读取或写入,所有需要与应用程序交换数据的驱动最少必须支持其中之一。在打开一个V4L2设备后会自动选择使用经典的I/O方法read()和write(),当驱动不支持写或读时会失败。
其他的方法必须通过协商。应用程序通过VIDIOC_REQBUFS ioctl来选择内存映射或用户缓存的I/O流,I/O同步方法未定义。
尽管应用程序不直接接受图片数据,视频overlay也可被考虑做其他I/O方法。初始化视频overlay是通过VIDIOC_S_FMT ioctl,其他信息见“视频Overlay接口”章节。
通常一个I/O方法,包括overlay,是与每一个文件描述符关联的。只是以下情况是例外,若应用程序不与驱动进行数据交换(表盘程序,见“打开关闭设备”章节),以及驱动准许使用同一个文件描述符进行视频捕捉和overlay,这是为了对V4L和早期版本的V4L2兼容。
VIDIOC_S_FMT和VIDIOC_REQBUFS会在某方面允许这种情况,但是对于关闭和重打开设备来说简单驱动不需要支持I/O方法切换(第一次切换read/write后)。之后的小节描述了各种I/O方法的细节。
读/写
若VIDIOC_QUERYCAP ioctl返回的struct v4l2_capability中capabilities包含了V4L2_CAP_READWRITE时,输入和输出设备需要分别支持read()和write()函数。驱动可能需要CPU进行数据拷贝操作,但是他们也可以通过支持DMA来处理用户内存,所以此I/O方法并不一定比其他只是交换缓冲区指针的方法效率低。因为像帧计数器或时间戳传输的无元数据的一些类型,所以它不优先考虑,此信息对于识别掉帧和与其他数据流同步来说很有必要。无论怎样,这是个简单的IO方法,需要很少甚至不需要任何的设置来交换数据。它允许在命令行中使用(vidvtrl工具的虚构的):
> vidctrl /dev/video --input=0 --format=YUYV --size=352x288 > dd if=/dev/video of=myimage.422 bs=202752 count=1
应用程序使用read()函数从设备中读取,使用write()函数对设备进行写入,若驱动与应用程序交换数据则必须声明一个I/O方法,但是不一定像是这样。若读取或写入支持了,那么驱动还必须支持select()和poll()函数。
在驱动等级select()和poll()是一样的,select()是非常重要的选项。
I/O流 (内存映射)
若VIDIOC_QUERYCAP ioctl后返回的struct v4l2_capability中capabilities包含了V4L2_CAP_STREAMING标志则输入、输出设备要支持这个I/O方法。有两种流方法,应用程序通过VIDIOC_REQBUFS ioctl决定内存映射特性是否支持。应用程序和驱动只交换缓存指针,而数据本身并不被拷贝的I/O方法叫做“流”。内存映射即是将设备内存中的缓存映射到应用程序的地址空间去,设备内存可以是比如做视频捕捉等时显卡上的视频内存。无论如何,作为长久以来效率最高的I/O方法,非常多的驱动支持流,他们将在可DMA操作的主内存中申请缓存。
一个驱动可支持许多套缓存,每一套通过一个独一无二的缓存类型值定义。他们都是互相独立的,而且每一套缓存处理不同的数据类型。要同时访问不同的缓存必须通过使用不同的文件描述符。
应用程序通过VIDIOC_REQBUFS ioctl申请设备缓存,同时要带入需求的缓存数量和缓存类型,比如V4L2_BUF_TYPE_VIDEO_CAPTURE。这个ioctl也可以用来改变缓存数量或释放已申请的内存,对已映射的缓存无效。
应用程序在操作缓存前必须要通过mmap()函数将他们映射到应用程序地址空间中,而所映射的缓存在设备内存中具体哪个位置是由VIDIOC_QUERYBUF ioctl决定的。在单一平面API中,返回的struct v4l2_buffer中m.offset和length成员用作mmap()函数的第六个和第二个参数。多平面API中,struct v4l2_buffer结构体包含了struct v4l2_plane结构体集合,每一个都包含了各自的,m.offset和length。当使用多平面API时,每个缓存冲的每个平面都需要分别映射,所以调用mmap()的次数就等于缓存数乘以每个缓存内的平面数量。offset和length的值必须不能修改。切记,缓存被分配在物理内存中,不同于虚拟内存的是,可以与硬盘做交换。应用程序在执行了munmap()函数后要尽快释放缓存。
例3.1 单平面API中的缓存映
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);
例3.2 多平面API中的缓存映射
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]);
概念上讲,流驱动包含两个缓存序列,一个传入序列、一个传出序列。他们使同步捕捉或输出操作从应用程序分开来锁定到视频时钟上,因为应用程序可能受限于随机的硬盘或网络延迟,还有其他进程的抢占,因此减少了数据丢失的可能性。这些序列由FIFO管道组成,缓存可以在输入管道上进行输出,可以在输出管道上进行捕捉。
驱动可能会一直需要一个队列缓存的最小数,在没有缓存限制的情况下应用程序可以预先装配、解除和处理。他们还可以在缓存解除前以不同的顺序进行队列装配,驱动可以用任何顺序对空缓存进行填充。缓存的索引号并没有任何规定,只要是唯一的就好。
初始化所有映射缓存要在其没有进入队列的时候进行,驱动很难做到这一点。对于捕捉应用来说,通常是先装配所有已映射缓存,然后开始捕捉并进入读循环中。应用程序会一直等到一个被填充的缓存能被解除(移除队列),然后若不再需要其数据了就重新将其放入队列。输出程序填充缓存,然后将其放入队列中,当累计了足够的缓存后开始VIDIOC_STREAMON。在写循环中,若应用程序把空闲缓存用完了,那么必须等待有空的buffer可以移除队列和再利用。
应用程序通过VIDIOC_QBUF和VIDIOC_DQBUF来使一个缓存入列和出列。缓存的状态是已映射、已入列、满还是空可以在任何时候通过VIDIOC_QUERYBUF ioctl进行查询。有两个方法使应用程序的执行挂起来等待可出列的缓存。VIDIOC_DQBUF会在传出序列中没有缓存时自动阻塞,若打开设备的时候设置了O_NONBLOCK标志则VIDIOC_DQBUF会在没有可用缓存时返回EAGAIN。select()或poll()函数总是可用的。
应用程序通过调用VIDIOC_STREAMON和VIDIOC_STREAMOFF ioctl来开始、停止捕捉或输出。注意VIDIOC_STREAMOFF会移除掉所有的缓存。因为在多任务操作系统中并没有“当前”的概念,所以如果应用程序需要与其他项目进行同步,那么它应该检查struct v4l2_buffer中捕捉的或输出的缓存timestamp。
声明了内存映射I/O的驱动必须支持VIDIOC_REQBUFS, VIDIOC_QUERYBUF, VIDIOC_QBUF, VIDIOC_DQBUF, VIDIOC_STREAMON和VIDIOC_STREAMOFF ioctl,和mmap()、munmap()、select()及poll()函数。
I/O流 (用户指针)
若VIDIOC_QUERYCAP ioctl后返回的struct v4l2_capability中capabilities包含了V4L2_CAP_STREAMING标志则输入、输出设备要支持这个I/O方法。是否需要支持用户指针方法由VIDIOC_REQBUFS ioctl决定。此I/O方法结合了高级读写以及内存映射方法。缓存(平面)通过应用程序本身进行申请,可以贮存在虚拟或共享内存中,需要交换的只是数据指针。这些指针和元数据在struct v4l2_buffer(对多平面API来说是struct v4l2_plane)中。若调用VIDIOC_REQBUFS且带有相应缓存类型,则驱动必须切换到用户指针I/O模式。不需要预先分配缓存(平面),因此他们没有索引,也不能像映射缓存那样通过VIDIOC_QUERYBUF ioctl进行查询。
例3.3 初始化用户指针I/O流
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); }
缓存地址和大小通过VIDIOC_QBUF ioctl快速写入,尽管缓存通常是循环的,应用程序也可以在每个VIDIOC_QBUF调用中使用不同的地址和大小进行区分。如果硬件需要,驱动将在物理内存中进行内存页交换来创建一个连续的内存区域,这对处于内核虚拟内存子系统中的应用来说很显然就有必要。当缓存页被交换到了磁盘上,他们会被拿回来并锁定到物理内存中为了DMA使用。
填充的或显示的缓存通过VIDIOC_DQBUF ioctl出列。驱动可以在DMA完成和这个ioctl间的任何时候解锁内存页。当VIDIOC_STREAMOFF、VIDIOC_REQBUFS调用或设备关闭时内存也会被解锁。应用程序必须要注意,不要把未出列的缓存释放掉。那样的话,这些缓存一直是锁定的,浪费着物理内存。还有一点需要注意的是,当内存被应用程序释放然后利用为其他目的的时候,驱动并不会被通知,比如完成了请求的DMA以及更新了有效数据。
对于捕捉应用程序来说,将一定数量的空缓存入列是很正常的,他们是为了开启捕捉以及进入读循环。应用程序会等待一个填充后的缓存何时能被出列,然后当其数据不再需要的时候将此缓存重新入列。输出应用程序填充缓存,然后将缓存入列,当累积了足够的缓存输出动作就开始了。在写循环中,若应用程序用光了空闲缓存,那么他必须等到某个空缓存可以被出列,然后重新利用它。应用程序通过挂起的方式等待缓存有两种途径,默认的是当传出序列中没有缓存时VIDIOC_DQBUF会阻塞住。而如果在打开设备时候open()设定了O_NONBLOCK参数,那么VIDIOC_DQBUF在这种情况下会直接返回EAGAIN错误代码。这select()和poll()往往都是有效的。
可以通过VIDIOC_STREAMON和VIDIOC_STREAMOFF来控制捕捉或输出应用程序的开始和停止。需要注意的是,VIDIOC_STREAMOFF存在着一定的副作用,就是它会将所有序列冲的缓存移除,并将他们解锁。由于在多任务系统中并没有“当前”的概念,所以如果一个应用程序需要同其他事件进行同步,应该通过检查捕捉或输出的缓存struct v4l2_buffer中的timestamp成员来达成同步的目的。
实现用户指针I/O方法的驱动必须支持VIDIOC_REQBUFS,VIDIOC_QBUF,VIDIOC_DQBUF,VIDIOC_STREAMON及VIDIOC_STREAMOFF ioctl,还有select()和poll()函数。
相关文章推荐
- V4L2文档翻译(十)
- V4L2文档翻译(十一)
- V4L2文档翻译(八)
- V4L2文档翻译
- linux内核文档翻译—V4L2-framework.txt
- V4L2文档翻译(一)
- V4L2文档翻译(二)
- V4L2文档翻译(六)
- Flume官方文档翻译之(十二)
- V4L2文档翻译(三)
- V4L2文档翻译(十三)
- linux内核文档翻译之——V4L2-framework.txt
- V4L2文档翻译(四)
- linux内核文档翻译之——V4L2-framework.txt
- Android官方文档翻译 十二 3.Supporting Different Devices
- V4L2文档翻译(一)
- V4L2文档翻译(五)
- V4L2文档翻译(三)
- V4L2文档翻译(六)
- V4L2文档翻译(二)