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
为了使此序列正确运行,排队的缓冲区需要足够大,以适应新的格式或控件。如果当前排队的缓冲区对于新格式而言太小,则驱动程序应返回 ENOSPC
错误以响应格式更改 (VIDIOC_S_FMT()
) 或控件更改 (VIDIOC_S_CTRL()
或 VIDIOC_S_EXT_CTRLS()
)。作为一种简化,如果当前已排队任何缓冲区,则允许驱动程序从这些 ioctl 返回 EBUSY
错误,而无需检查排队缓冲区的大小。
此外,如果正在排队的缓冲区对于当前格式或控件而言太小,则驱动程序应从 VIDIOC_QBUF()
ioctl 返回 EINVAL
错误。这些要求共同确保排队的缓冲区始终足够大,以适应配置的格式和控件。
用户空间应用程序可以通过首先设置所需的控件值,然后尝试所需的格式,来查询给定格式和控件所需的缓冲区大小。VIDIOC_TRY_FMT()
ioctl 将返回所需的缓冲区大小。
VIDIOC_S_EXT_CTRLS(x)
VIDIOC_S_EXT_CTRLS(y)
然后可以使用 VIDIOC_CREATE_BUFS()
ioctl 根据查询的大小分配缓冲区(例如,通过分配一组足够大的缓冲区以适应所有所需的格式和控件,或者通过为每个用例分配单独的一组大小合适的缓冲区)。
-
类型 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,应用程序不应设置 如果设备不支持请求,则将返回 |
-
类型 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 |
仅当设置了结构体 |
|
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 或类似的时间码。(timeval
时间戳存储在结构 v4l2_buffer
timestamp
字段中。)
-
type 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 字符。 |