3.5. 缓冲区¶
缓冲区包含应用程序和驱动程序使用流式 I/O 方法之一交换的数据。在多平面 API 中,数据保存在平面中,而缓冲区结构充当平面的容器。仅交换指向缓冲区(平面)的指针,数据本身不进行复制。这些指针以及时间戳或场奇偶校验等元信息存储在 v4l2_buffer
结构中,该结构是 ioctl VIDIOC_QUERYBUF、VIDIOC_QBUF 和 VIDIOC_DQBUF ioctl 的参数。在多平面 API 中,v4l2_buffer
结构的一些特定于平面的成员,例如每个平面的指针和大小,存储在 v4l2_plane
结构中。在这种情况下,v4l2_buffer
包含一个平面结构数组。
出队的视频缓冲区带有时间戳。驱动程序决定在帧的哪个部分以及使用哪个时钟获取时间戳。请参阅 缓冲区标志 中的 V4L2_BUF_FLAG_TIMESTAMP_MASK
和 V4L2_BUF_FLAG_TSTAMP_SRC_MASK
掩码中的标志。这些标志在整个视频流期间始终有效且恒定。这些标志的更改可能会作为 VIDIOC_S_INPUT 或 VIDIOC_S_OUTPUT 的副作用发生。例如,mem-to-mem 设备使用的 V4L2_BUF_FLAG_TIMESTAMP_COPY
时间戳类型是该规则的例外:时间戳源标志从 OUTPUT 视频缓冲区复制到 CAPTURE 视频缓冲区。
3.5.1. 格式、控件和缓冲区之间的交互¶
V4L2 公开了影响缓冲区大小或数据在缓冲区中布局方式的参数。这些参数通过格式和控件公开。这种控件的一个示例是 V4L2_CID_ROTATE
控件,该控件会修改像素在缓冲区中存储的方向,以及当所选格式在行尾包含填充时缓冲区的尺寸。
解释缓冲区内容所需的信息集(例如,像素格式、行步幅、平铺方向或旋转)在本节的其余部分中统称为缓冲区布局。
可以修改缓冲区布局的控件应设置 V4L2_CTRL_FLAG_MODIFY_LAYOUT
标志。
修改影响缓冲区大小或布局的格式或控件需要停止流。任何在流活动时尝试进行此类修改的操作都应导致设置格式或控件的 ioctl 返回 EBUSY
错误代码。在这种情况下,当流活动时,驱动程序还应在调用 VIDIOC_QUERYCTRL()
或 VIDIOC_QUERY_EXT_CTRL()
时,为此类控件设置 V4L2_CTRL_FLAG_GRABBED
标志。
注意
VIDIOC_S_SELECTION()
ioctl 可以根据硬件(例如,如果设备不包含缩放器)修改格式以及选择矩形。类似地,VIDIOC_S_INPUT()
、VIDIOC_S_OUTPUT()
、VIDIOC_S_STD()
和 VIDIOC_S_DV_TIMINGS()
ioctl 也可以修改格式和选择矩形。当这些 ioctl 导致缓冲区大小或布局更改时,驱动程序应像处理 VIDIOC_S_FMT()
ioctl 中所述的所有情况一样处理该条件。
仅影响缓冲区布局的控件可以在流停止时随时修改。由于它们不影响缓冲区大小,因此无需进行特殊处理来将这些控件与缓冲区分配同步,并且一旦流停止,V4L2_CTRL_FLAG_GRABBED
标志将被清除。
影响缓冲区大小的格式和控件与缓冲区分配交互。处理此问题的最简单方法是驱动程序始终要求重新分配缓冲区,以便更改这些格式或控件。在这种情况下,要执行此类更改,用户空间应用程序应首先使用 VIDIOC_STREAMOFF()
ioctl 停止正在运行的视频流,如果分配了所有缓冲区,则使用 VIDIOC_REQBUFS()
ioctl 释放所有缓冲区。释放所有缓冲区后,控件的 V4L2_CTRL_FLAG_GRABBED
标志将被清除。然后可以修改格式或控件,然后应重新分配缓冲区并重新启动流。典型的 ioctl 序列是
VIDIOC_STREAMOFF
VIDIOC_REQBUFS(0)
VIDIOC_S_EXT_CTRLS
VIDIOC_S_FMT
VIDIOC_REQBUFS(n)
VIDIOC_QBUF
VIDIOC_STREAMON
第二个 VIDIOC_REQBUFS()
调用将考虑新的格式和控制值,以计算要分配的缓冲区大小。如果需要,应用程序还可以通过调用 VIDIOC_G_FMT()
ioctl 来检索大小。
注意
API 没有强制要求控件 (3.) 和格式 (4.) 更改的上述顺序。可以根据设备和用例以不同的顺序设置格式和控件,甚至可以交错设置。例如,某些控件对于不同的像素格式的行为可能不同,在这种情况下,可能需要首先设置格式。
当需要重新分配时,任何在分配缓冲区时尝试修改影响缓冲区大小的格式或控件的操作都应导致格式或控件设置 ioctl 返回 EBUSY
错误。任何尝试排队对于当前格式或控件而言太小的缓冲区的操作都应导致 VIDIOC_QBUF()
ioctl 返回 EINVAL
错误。
缓冲区重新分配是一项昂贵的操作。为了避免这种开销,驱动程序可以(并鼓励)允许在分配缓冲区的情况下更改影响缓冲区大小的格式或控件。在这种情况下,修改格式和控件的典型 ioctl 序列是
VIDIOC_STREAMOFF
VIDIOC_S_EXT_CTRLS
VIDIOC_S_FMT
VIDIOC_QBUF
VIDIOC_STREAMON
为了使此序列正常工作,排队的缓冲区需要足够大,才能容纳新的格式或控件。如果当前排队了对于新格式而言太小的缓冲区,则驱动程序应在响应格式更改 (VIDIOC_S_FMT()
) 或控件更改 (VIDIOC_S_CTRL()
或 VIDIOC_S_EXT_CTRLS()
) 时返回 ENOSPC
错误。为简化起见,如果当前排队了任何缓冲区,则允许驱动程序从这些 ioctl 返回 EBUSY
错误,而无需检查排队的缓冲区大小。
此外,如果排队的缓冲区对于当前格式或控件而言太小,则驱动程序应从 VIDIOC_QBUF()
ioctl 返回 EINVAL
错误。这些要求共同确保排队的缓冲区始终足够大,以满足配置的格式和控件。
用户空间应用程序可以通过首先设置所需的控件值,然后尝试所需的格式来查询给定格式和控件所需的缓冲区大小。VIDIOC_TRY_FMT()
ioctl 将返回所需的缓冲区大小。
VIDIOC_S_EXT_CTRLS(x)
VIDIOC_S_EXT_CTRLS(y)
然后可以使用 VIDIOC_CREATE_BUFS()
ioctl 根据查询的大小分配缓冲区(例如,通过分配一组足够大的缓冲区来容纳所有所需的格式和控件,或者通过为每个用例分配单独的一组大小合适的缓冲区)。
-
type v4l2_buffer¶
3.5.2. struct v4l2_buffer¶
__u32 |
|
缓冲区编号,由应用程序设置,除非调用 VIDIOC_DQBUF,此时由驱动程序设置。此字段的范围从零到使用 ioctl VIDIOC_REQBUFS ioctl (struct |
__u32 |
|
缓冲区类型,与 struct |
__u32 |
|
缓冲区中数据占用的字节数。它取决于协商的数据格式,并且对于诸如 JPEG 图像之类的压缩可变大小数据,每个缓冲区可能会发生变化。当 |
__u32 |
|
应用程序或驱动程序设置的标志,请参阅 缓冲区标志。 |
__u32 |
|
指示缓冲区中图像的场序,请参阅 |
struct timeval |
|
对于捕获流,这是捕获第一个数据字节的时间,由相关时钟 ID 的 |
struct |
|
当 |
__u32 |
|
由驱动程序设置,按顺序计算帧(不是场!)。此字段针对输入和输出设备设置。 |
在 注意 这可以计算例如通过 USB 接收的帧,而不考虑由于有限的压缩吞吐量或总线带宽而被远程硬件丢弃的帧。这些设备通过不枚举任何视频标准来识别,请参阅 视频标准。 |
||
__u32 |
|
此字段必须由应用程序和/或驱动程序根据选择的 I/O 方法设置。请参阅 |
union { |
|
|
__u32 |
|
对于单平面 API 并且当 |
unsigned long |
|
对于单平面 API 并且当 |
|
当使用多平面 API 时,包含指向 struct |
|
int |
|
对于单平面 API 并且当 |
} |
||
__u32 |
|
对于单平面 API,以字节为单位的缓冲区大小(不是有效负载)。这是由驱动程序根据对 ioctl VIDIOC_REQBUFS 和/或 ioctl VIDIOC_CREATE_BUFS 的调用设置的。对于多平面 API,应用程序将此设置为 |
__u32 |
|
用于未来扩展的占位符。驱动程序和应用程序必须将其设置为 0。 |
__u32 |
|
要将缓冲区排队的请求的文件描述符。如果设置了标志
对于 VIDIOC_QBUF 以外的任何 ioctl,应用程序不应设置 如果设备不支持请求,则将返回 |
-
type v4l2_plane¶
3.5.3. struct v4l2_plane¶
__u32 |
|
平面中数据占用的字节数(其有效负载)。当 注意 请注意,实际的图像数据从 |
__u32 |
|
平面的字节大小(不是其有效负载)。这是由驱动程序根据对 ioctl VIDIOC_REQBUFS 和/或 ioctl VIDIOC_CREATE_BUFS 的调用设置的。 |
union { |
|
|
__u32 |
|
当包含结构体 |
unsigned long |
|
当包含结构体 |
int |
|
当包含结构体 |
} |
||
__u32 |
|
平面中视频数据的字节偏移量。当 注意 该 data_offset 包含在 |
__u32 |
|
保留供将来使用。驱动程序和应用程序应将其置零。 |
-
type v4l2_buf_type¶
3.5.4. enum v4l2_buf_type¶
|
1 |
单平面视频捕获流的缓冲区,请参阅 视频捕获接口。 |
|
9 |
多平面视频捕获流的缓冲区,请参阅 视频捕获接口。 |
|
2 |
单平面视频输出流的缓冲区,请参阅 视频输出接口。 |
|
10 |
多平面视频输出流的缓冲区,请参阅 视频输出接口。 |
|
3 |
用于视频叠加的缓冲区,请参阅 视频叠加接口。 |
|
4 |
原始 VBI 捕获流的缓冲区,请参阅 原始 VBI 数据接口。 |
|
5 |
原始 VBI 输出流的缓冲区,请参阅 原始 VBI 数据接口。 |
|
6 |
切片 VBI 捕获流的缓冲区,请参阅 切片 VBI 数据接口。 |
|
7 |
切片 VBI 输出流的缓冲区,请参阅 切片 VBI 数据接口。 |
|
8 |
用于视频输出叠加 (OSD) 的缓冲区,请参阅 视频输出叠加接口。 |
|
11 |
用于软件定义无线电 (SDR) 捕获流的缓冲区,请参阅 软件定义无线电接口 (SDR)。 |
|
12 |
用于软件定义无线电 (SDR) 输出流的缓冲区,请参阅 软件定义无线电接口 (SDR)。 |
|
13 |
用于元数据捕获的缓冲区,请参阅 元数据接口。 |
|
14 |
用于元数据输出的缓冲区,请参阅 元数据接口。 |
3.5.5. 缓冲区标志¶
|
0x00000001 |
缓冲区驻留在设备内存中,并且已映射到应用程序的地址空间中,有关详细信息,请参阅 流式 I/O(内存映射)。当调用 ioctl VIDIOC_QUERYBUF、ioctl VIDIOC_QBUF, VIDIOC_DQBUF 或 VIDIOC_DQBUF ioctl 时,驱动程序会设置或清除此标志。由驱动程序设置。 |
|
0x00000002 |
在内部,驱动程序维护两个缓冲区队列,一个传入队列和一个传出队列。当设置此标志时,该缓冲区当前位于传入队列上。在缓冲区被填充(捕获设备)或显示(输出设备)后,它会自动移动到传出队列。当调用 |
|
0x00000004 |
当设置此标志时,该缓冲区当前位于传出队列上,准备好从驱动程序中出队。当调用 |
|
0x00000040 |
当设置此标志时,该缓冲区已成功出队,但数据可能已损坏。这是可恢复的,流式传输可以正常继续,并且可以正常重复使用该缓冲区。当调用 |
|
0x00000080 |
此缓冲区是尚未入队的请求的一部分。 |
|
0x00000008 |
当调用 |
|
0x00000010 |
类似于 |
|
0x00000020 |
类似于 |
|
0x00000100 |
|
|
0x00000400 |
缓冲区已准备好进行 I/O,并且可以由应用程序入队。当调用 VIDIOC_QUERYBUF、VIDIOC_PREPARE_BUF、VIDIOC_QBUF 或 VIDIOC_DQBUF ioctl 时,驱动程序会设置或清除此标志。 |
|
0x00000800 |
无需使此缓冲区的缓存失效。通常,如果 CPU 不会接触缓冲区中捕获的数据,则应用程序应使用此标志,相反,该缓冲区可能会传递给支持 DMA 的硬件单元以进行进一步处理或输出。除非队列用于 内存映射 流式 I/O 并报告 V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS 功能,否则将忽略此标志。 |
|
0x00001000 |
此缓冲区不需要清除缓存。通常,如果此缓冲区中的数据不是由 CPU 创建,而是由某些具有 DMA 功能的单元创建的,则应用程序应将此标志用于输出缓冲区,在这种情况下,未使用缓存。除非队列用于 内存映射 流式 I/O 并报告 V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS 功能,否则将忽略此标志。 |
|
0x00000200 |
仅当设置了 struct |
|
0x00100000 |
硬件产生的最后一个缓冲区。当调用 ioctl VIDIOC_QUERYBUF 或 VIDIOC_DQBUF ioctl 时,mem2mem 编解码器驱动程序会在捕获队列上设置此标志,用于最后一个缓冲区。由于硬件限制,最后一个缓冲区可能为空。在这种情况下,无论格式如何,驱动程序都会将 |
|
0x00800000 |
|
|
0x0000e000 |
下面时间戳类型的掩码。要测试时间戳类型,请通过使用缓冲区标志和时间戳掩码执行逻辑与运算来屏蔽掉不属于时间戳类型的位。 |
|
0x00000000 |
未知的时间戳类型。此类型由 Linux 3.9 之前的驱动程序使用,可以是单调的(见下文)或实时的(挂钟时间)。单调时钟在嵌入式系统中更受欢迎,而大多数驱动程序使用实时时钟。用户空间可以通过使用时钟 ID |
|
0x00002000 |
缓冲区时间戳取自 |
|
0x00004000 |
CAPTURE 缓冲区时间戳取自相应的 OUTPUT 缓冲区。此标志仅适用于 mem2mem 设备。 |
|
0x00070000 |
下面时间戳源的掩码。时间戳源定义了时间戳相对于帧的获取时间点。 |
|
0x00000000 |
帧结束。当接收到帧的最后一个像素或已传输帧的最后一个像素时,会获取缓冲区时间戳。在实践中,软件生成的时间戳通常会在接收或传输最后一个像素后的一小段时间从时钟读取,具体取决于系统及其中的其他活动。 |
|
0x00010000 |
曝光开始。当帧的曝光开始时会获取缓冲区时间戳。这仅对 |
3.5.6. enum v4l2_memory¶
|
1 |
该缓冲区用于 内存映射 I/O。 |
|
2 |
该缓冲区用于 用户指针 I/O。 |
|
3 |
[待办] |
|
4 |
该缓冲区用于 DMA 共享缓冲区 I/O。 |
3.5.7. 时间码¶
v4l2_buffer_timecode
结构旨在保存 SMPTE 12M 或类似的时间码。(struct timeval
时间戳存储在 struct v4l2_buffer
timestamp
字段中。)
-
类型 v4l2_timecode¶
3.5.7.1. struct v4l2_timecode¶
__u32 |
|
时间码所基于的帧速率,请参阅 时间码类型。 |
__u32 |
|
时间码标志,请参阅 时间码标志。 |
__u8 |
|
帧计数,0 ... 23/24/29/49/59,具体取决于时间码的类型。 |
__u8 |
|
秒计数,0 ... 59。这是一个二进制数,而不是 BCD 数。 |
__u8 |
|
分钟计数,0 ... 59。这是一个二进制数,而不是 BCD 数。 |
__u8 |
|
小时计数,0 ... 29。这是一个二进制数,而不是 BCD 数。 |
__u8 |
|
时间码中的“用户组”位。 |
3.5.7.2. 时间码类型¶
|
1 |
每秒 24 帧,即电影。 |
|
2 |
每秒 25 帧,即 PAL 或 SECAM 视频。 |
|
3 |
每秒 30 帧,即 NTSC 视频。 |
|
4 |
|
|
5 |
3.5.7.3. 时间码标志¶
|
0x0001 |
指示以 29.97 fps 材料计数帧的“丢帧”语义。设置后,每分钟开始的帧号 0 和 1 将从计数中省略,但分钟 0、10、20、30、40、50 除外。 |
|
0x0002 |
“彩色帧”标志。 |
|
0x000C |
“二进制组标志”的字段掩码。 |
|
0x0000 |
未指定的格式。 |
|
0x0008 |
8 位 ISO 字符。 |