4.5.2. 内存到内存的有状态视频编码器接口¶
有状态视频编码器按显示顺序获取原始视频帧,并将其编码为字节流。它生成完整的字节流块,包括所有元数据、标头等。生成的字节流不需要客户端进行任何进一步的后处理。
强烈建议不要在驱动程序中执行软件流处理、标头生成等操作以支持此接口。如果需要此类操作,强烈建议使用无状态视频编码器接口(正在开发中)。
4.5.2.1. 本文档中使用的约定和符号¶
如果本文档中未另行规定,则适用通用的 V4L2 API 规则。
“必须”、“可以”、“应该”等词的含义与 RFC 2119 中的规定相同。
所有未标记为“可选”的步骤都是必需的。
VIDIOC_G_EXT_CTRLS()
和VIDIOC_S_EXT_CTRLS()
可以与VIDIOC_G_CTRL()
和VIDIOC_S_CTRL()
互换使用,除非另有说明。单平面 API(请参阅 单平面和多平面 API)和适用的结构可以与多平面 API 互换使用,除非另有说明,具体取决于编码器功能并遵循通用的 V4L2 指南。
i = [a..b]:从 a 到 b 的整数序列(包括 a 和 b),例如 i = [0..2]:i = 0, 1, 2。
给定一个
OUTPUT
缓冲区 A,则 A’ 表示CAPTURE
队列上的缓冲区,其中包含处理缓冲区 A 产生的数据。
4.5.2.2. 术语表¶
请参阅 术语表。
4.5.2.3. 状态机¶
4.5.2.4. 查询功能¶
要枚举编码器支持的编码格式集,客户端可以在
CAPTURE
上调用VIDIOC_ENUM_FMT()
。将返回完整的支持格式集,而与在
OUTPUT
上设置的格式无关。
要枚举支持的原始格式集,客户端可以在
OUTPUT
上调用VIDIOC_ENUM_FMT()
。只会返回当前在
CAPTURE
上激活的格式支持的格式。为了枚举给定编码格式支持的原始格式,客户端必须首先在
CAPTURE
上设置该编码格式,然后在OUTPUT
上枚举格式。
客户端可以使用
VIDIOC_ENUM_FRAMESIZES()
来检测给定格式支持的分辨率,并在v4l2_frmsizeenum
pixel_format
中传递所需的像素格式。由
VIDIOC_ENUM_FRAMESIZES()
返回的编码像素格式的值将包括编码器对于给定的编码像素格式支持的所有可能的编码分辨率。由
VIDIOC_ENUM_FRAMESIZES()
返回的原始像素格式的值将包括编码器对于给定的原始像素格式以及当前在CAPTURE
上设置的编码格式支持的所有可能的帧缓冲区分辨率。
客户端可以使用
VIDIOC_ENUM_FRAMEINTERVALS()
来检测给定格式和分辨率支持的帧间隔,并在v4l2_frmivalenum
pixel_format
中传递所需的像素格式,并在v4l2_frmivalenum
width
和v4l2_frmivalenum
height
中传递分辨率。由
VIDIOC_ENUM_FRAMEINTERVALS()
返回的编码像素格式和编码分辨率的值将包括编码器对于给定的编码像素格式和分辨率支持的所有可能的帧间隔。由
VIDIOC_ENUM_FRAMEINTERVALS()
返回的原始像素格式和分辨率的值将包括编码器对于给定的原始像素格式和分辨率以及当前在CAPTURE
上设置的编码格式、编码分辨率和编码帧间隔所支持的所有可能的帧间隔。对
VIDIOC_ENUM_FRAMEINTERVALS()
的支持是可选的。如果未实现,则除了编解码器本身的限制之外,没有其他特殊限制。
可以使用各自的控件通过
VIDIOC_QUERYCTRL()
查询当前在CAPTURE
上设置的编码格式(如果适用)支持的配置文件和级别。可以通过查询其各自的控件来发现任何其他编码器功能。
4.5.2.5. 初始化¶
通过
VIDIOC_S_FMT()
在CAPTURE
队列上设置编码格式。必填字段
type
适用于
CAPTURE
的V4L2_BUF_TYPE_*
枚举。pixelformat
要生成的编码格式。
sizeimage
CAPTURE
缓冲区的所需大小;编码器可以调整它以匹配硬件要求。width
,height
忽略(只读)。
- 其他字段
遵循标准语义。
返回字段
sizeimage
已调整的
CAPTURE
缓冲区的大小。width
,height
编码器基于当前状态(例如
OUTPUT
格式、选择矩形等)选择的编码大小(只读)。
重要提示
更改
CAPTURE
格式可能会更改当前设置的OUTPUT
格式。如何确定新的OUTPUT
格式取决于编码器,客户端必须确保它之后满足其需求。可选。 通过
VIDIOC_ENUM_FMT()
枚举所选编码格式支持的OUTPUT
格式(源的原始格式)。必填字段
type
适用于
OUTPUT
的V4L2_BUF_TYPE_*
枚举。- 其他字段
遵循标准语义。
返回字段
pixelformat
当前在
CAPTURE
队列上选择的编码格式支持的原始格式。- 其他字段
遵循标准语义。
通过
VIDIOC_S_FMT()
在OUTPUT
队列上设置原始源格式。必填字段
type
适用于
OUTPUT
的V4L2_BUF_TYPE_*
枚举。pixelformat
源的原始格式。
width
,height
源分辨率。
- 其他字段
遵循标准语义。
返回字段
width
,height
可以根据当前选择的格式的要求进行调整,以匹配编码器的最小值、最大值和对齐要求,如
VIDIOC_ENUM_FRAMESIZES()
所报告。- 其他字段
遵循标准语义。
设置
OUTPUT
格式会将选择矩形重置为其默认值,该默认值基于新的分辨率,如下一步中所述。
通过
VIDIOC_S_PARM()
设置OUTPUT
队列上的原始帧间隔。这也会将CAPTURE
队列上的编码帧间隔设置为相同的值。必填字段
type
适用于
OUTPUT
的V4L2_BUF_TYPE_*
枚举。parm.output
将除
parm.output.timeperframe
之外的所有字段设置为 0。parm.output.timeperframe
所需的帧间隔;编码器可能会调整它以匹配硬件要求。
返回字段
parm.output.timeperframe
调整后的帧间隔。
重要提示
更改
OUTPUT
帧间隔也会设置编码器用来编码视频的帧率。因此,将帧间隔设置为 1/24(或每秒 24 帧)将生成一个可以按该速度回放的编码视频流。OUTPUT
队列的帧间隔只是一个提示,应用程序可能会以不同的速率提供原始帧。驱动程序可以使用它来帮助调度并行运行的多个编码器。在下一步中,可以选择将
CAPTURE
帧间隔更改为不同的值。这对于离线编码非常有用,在离线编码中,编码帧间隔可能与提供原始帧的速率不同。重要提示
timeperframe
处理的是帧,而不是场。因此,对于隔行扫描格式,这是每两个场的时间,因为一帧由顶部场和底部场组成。注意
由于历史原因,更改
OUTPUT
帧间隔也会更改CAPTURE
队列上的编码帧间隔。理想情况下,这些应该是独立的设置,但这会破坏现有的 API。可选 通过
VIDIOC_S_PARM()
设置CAPTURE
队列上的编码帧间隔。仅当编码帧间隔与原始帧间隔不同时才需要这样做,离线编码通常是这种情况。此功能的支持由 V4L2_FMT_FLAG_ENC_CAP_FRAME_INTERVAL 格式标志指示。必填字段
type
适用于
CAPTURE
的V4L2_BUF_TYPE_*
枚举。parm.capture
将除
parm.capture.timeperframe
之外的所有字段设置为 0。parm.capture.timeperframe
所需的编码帧间隔;编码器可能会调整它以匹配硬件要求。
返回字段
parm.capture.timeperframe
调整后的帧间隔。
重要提示
更改
CAPTURE
帧间隔会设置编码视频的帧率。它不设置缓冲区到达CAPTURE
队列的速率,这取决于编码器的速度以及OUTPUT
队列上排队原始帧的速度。重要提示
timeperframe
处理的是帧,而不是场。因此,对于隔行扫描格式,这是每两个场的时间,因为一帧由顶部场和底部场组成。注意
并非所有驱动程序都支持此功能,在这种情况下,只需为
OUTPUT
队列设置所需的编码帧间隔。但是,可以基于
OUTPUT
帧间隔调度多个编码器的驱动程序必须支持此可选功能。可选。 如果希望与完整的 OUTPUT 分辨率不同,则通过
VIDIOC_S_SELECTION()
在OUTPUT
队列上设置流元数据的可见分辨率。必填字段
type
适用于
OUTPUT
的V4L2_BUF_TYPE_*
枚举。target
设置为
V4L2_SEL_TGT_CROP
。r.left
、r.top
、r.width
、r.height
可见矩形;此矩形必须适合 V4L2_SEL_TGT_CROP_BOUNDS 矩形,并且可能会进行调整以匹配编解码器和硬件约束。
返回字段
r.left
、r.top
、r.width
、r.height
编码器调整后的可见矩形。
以下选择目标在
OUTPUT
上受支持V4L2_SEL_TGT_CROP_BOUNDS
等于完整源帧,与活动的
OUTPUT
格式匹配。V4L2_SEL_TGT_CROP_DEFAULT
等于
V4L2_SEL_TGT_CROP_BOUNDS
。V4L2_SEL_TGT_CROP
源缓冲区中要编码到
CAPTURE
流中的矩形;默认为V4L2_SEL_TGT_CROP_DEFAULT
。注意
此选择目标的常见用例是对分辨率不是宏块倍数的源视频进行编码,例如,常见的 1920x1080 分辨率可能需要源缓冲区对于具有 16x16 宏块大小的编解码器对齐到 1920x1088。为了避免编码填充,客户端需要将此选择目标显式配置为 1920x1080。
警告
编码器可能会将裁剪/合成矩形调整为最接近的受支持矩形,以满足编解码器和硬件要求。客户端需要检查
VIDIOC_S_SELECTION()
返回的调整后的矩形。通过
VIDIOC_REQBUFS()
为OUTPUT
和CAPTURE
分配缓冲区。这可以按任何顺序执行。必填字段
count
要分配的请求缓冲区数;大于零。
type
适合
OUTPUT
或CAPTURE
的V4L2_BUF_TYPE_*
枚举。- 其他字段
遵循标准语义。
返回字段
count
实际分配的缓冲区数。
警告
实际分配的缓冲区数可能与给定的
count
不同。客户端必须在调用返回后检查count
的更新值。注意
要分配多于最小数量的 OUTPUT 缓冲区(用于管道深度),客户端可以查询
V4L2_CID_MIN_BUFFERS_FOR_OUTPUT
控件以获取所需的最少缓冲区数,并将获得的值加上count
字段中需要的额外缓冲区数传递给VIDIOC_REQBUFS()
。或者,可以使用
VIDIOC_CREATE_BUFS()
来更好地控制缓冲区分配。必填字段
count
要分配的请求缓冲区数;大于零。
type
适用于
OUTPUT
的V4L2_BUF_TYPE_*
枚举。- 其他字段
遵循标准语义。
返回字段
count
调整为已分配的缓冲区数。
通过
VIDIOC_STREAMON()
开始在OUTPUT
和CAPTURE
队列上进行流式传输。这可以按任何顺序执行。当两个队列都开始流式传输时,实际的编码过程开始。
注意
如果客户端在编码过程中停止 CAPTURE
队列,然后再次重新启动它,则编码器将开始生成一个独立于停止之前生成的流的流。确切的约束取决于编码格式,但可能包括以下含义
重新启动后生成的编码帧不得引用停止之前生成的任何帧,例如,H.264/HEVC 没有长期参考,
必须包含在独立流中的任何标头都必须再次生成,例如,H.264/HEVC 的 SPS 和 PPS。
4.5.2.6. 编码¶
在 初始化 序列成功完成后达到此状态。在此状态下,客户端通过 VIDIOC_QBUF()
和 VIDIOC_DQBUF()
将缓冲区排队和出队到两个队列,遵循标准语义。
编码的 CAPTURE
缓冲区的内容取决于活动的编码像素格式,并且可能受到编解码器特定的扩展控件的影响,如每个格式的文档中所述。
两个队列独立运行,遵循 V4L2 缓冲区队列和内存到内存设备的标准行为。此外,由于所选编码格式的属性(例如帧重排序),从 CAPTURE
队列出队的编码帧的顺序可能与将原始帧排队到 OUTPUT
队列的顺序不同。
客户端不得假设 CAPTURE
和 OUTPUT
缓冲区之间存在任何直接关系,以及任何特定的缓冲区可用于出队的时间。具体来说
排队到
OUTPUT
的缓冲区可能会导致在CAPTURE
上生成多个缓冲区(例如,如果返回编码帧允许编码器返回在显示中位于其前面,但在解码顺序中位于其后面的帧;但是,也可能有其他原因导致这种情况),排队到
OUTPUT
的缓冲区可能会导致在编码过程中稍后在CAPTURE
上生成缓冲区,和/或在处理进一步的OUTPUT
缓冲区之后,或以乱序返回,例如,如果使用显示重排序,即使没有额外的缓冲区排队到
OUTPUT
,缓冲区也可能在CAPTURE
队列上变得可用(例如,在耗尽或EOS
期间),这是因为过去排队的OUTPUT
缓冲区的编码结果仅在稍后时间可用,这是由于编码过程的特殊性,排队到
OUTPUT
的缓冲区在被编码到相应的CAPTURE
缓冲区后可能不会立即变得可用于出队,例如,如果编码器需要使用该帧作为编码进一步帧的参考。
注意
为了使编码后的 CAPTURE
缓冲区能够与其来源的 OUTPUT
缓冲区匹配,客户端可以在将 OUTPUT
缓冲区入队时设置 v4l2_buffer
结构的 timestamp
字段。由该 OUTPUT
缓冲区编码产生的 CAPTURE
缓冲区在出队时,其 timestamp
字段将被设置为相同的值。
除了一个 OUTPUT
缓冲区生成一个 CAPTURE
缓冲区的简单情况外,还定义了以下情况:
一个
OUTPUT
缓冲区生成多个CAPTURE
缓冲区:同一个OUTPUT
时间戳将被复制到多个CAPTURE
缓冲区中。编码顺序与呈现顺序不同(即,与
OUTPUT
缓冲区相比,CAPTURE
缓冲区是乱序的):CAPTURE
时间戳将不会保留OUTPUT
时间戳的顺序。
注意
为了让客户端区分帧类型(关键帧、中间帧;确切的类型列表取决于编码格式),CAPTURE
缓冲区在出队时,会在其 v4l2_buffer
结构中设置相应的标志位。有关标志的确切列表及其含义,请参阅 v4l2_buffer
和每种编码像素格式的文档。
如果发生编码错误,将会根据编码器的能力将错误报告给客户端。具体来说:
包含失败编码操作结果的
CAPTURE
缓冲区(如果有)将会被返回,并设置V4L2_BUF_FLAG_ERROR
标志。如果编码器能够准确报告触发错误的
OUTPUT
缓冲区,则这些缓冲区将被返回,并设置V4L2_BUF_FLAG_ERROR
标志。
注意
如果 CAPTURE
缓冲区太小,它只会被返回,并设置 V4L2_BUF_FLAG_ERROR
标志。还需要更多的工作来检测这种错误的发生,是因为缓冲区太小,并提供支持来释放现有的过小缓冲区。
如果发生导致编码无法继续的致命故障,则对相应的编码器文件句柄的任何进一步操作将返回 -EIO 错误代码。客户端可以关闭文件句柄并打开新的文件句柄,或者通过停止两个队列上的流、释放所有缓冲区并再次执行初始化序列来重新初始化实例。
4.5.2.7. 编码参数更改¶
允许客户端随时使用 VIDIOC_S_CTRL()
来更改编码器参数。参数的可用性取决于特定的编码器,客户端必须查询编码器以查找可用控件的集合。
根据 V4L2 控制接口的标准语义,在编码期间更改每个参数的能力取决于特定的编码器。客户端可以在编码期间尝试设置控件,如果操作失败并返回 -EBUSY 错误代码,则需要停止 CAPTURE
队列才能允许进行配置更改。为此,它可以遵循 Drain 序列,以避免丢失已排队/编码的帧。
根据 V4L2 控制接口的标准语义,参数更新的时序取决于特定的编码器。如果客户端需要在特定帧精确应用参数,则应考虑使用请求 API (请求 API),如果编码器支持。
4.5.2.8. 排空¶
为了确保所有已排队的 OUTPUT
缓冲区都已处理,并且相关的 CAPTURE
缓冲区已提供给客户端,客户端必须遵循下面描述的排空序列。排空序列结束后,客户端已接收到所有在序列开始之前排队的所有 OUTPUT
缓冲区的编码帧。
通过发出
VIDIOC_ENCODER_CMD()
来开始排空序列。必填字段
cmd
设置为
V4L2_ENC_CMD_STOP
。flags
设置为 0。
pts
设置为 0。
警告
只有在
OUTPUT
和CAPTURE
队列都在流式传输时,才能启动该序列。出于兼容性原因,即使任何队列未在流式传输,调用VIDIOC_ENCODER_CMD()
也不会失败,但同时它也不会启动 Drain 序列,因此下面描述的步骤将不适用。客户端在发出
VIDIOC_ENCODER_CMD()
之前排队的任何OUTPUT
缓冲区都将像正常情况一样进行处理和编码。客户端必须继续独立处理两个队列,类似于正常的编码操作。这包括:对
CAPTURE
缓冲区进行入队和出队操作,直到出队带有V4L2_BUF_FLAG_LAST
标志的缓冲区。警告
最后一个缓冲区可能为空(
v4l2_buffer
的bytesused
= 0),在这种情况下,客户端必须忽略它,因为它不包含编码帧。注意
任何尝试在标记有
V4L2_BUF_FLAG_LAST
的缓冲区之外出队更多CAPTURE
缓冲区都将导致从VIDIOC_DQBUF()
返回 -EPIPE 错误。对已处理的
OUTPUT
缓冲区进行出队操作,直到所有在V4L2_ENC_CMD_STOP
命令之前入队的缓冲区都被出队。出队
V4L2_EVENT_EOS
事件(如果客户端订阅了该事件)。
注意
为了向后兼容,编码器将在最后一个帧被编码并且所有帧都准备好出队时发出
V4L2_EVENT_EOS
事件。这是一种不推荐使用的行为,客户端不得依赖它。应改为使用V4L2_BUF_FLAG_LAST
缓冲区标志。一旦所有在
V4L2_ENC_CMD_STOP
调用之前入队的OUTPUT
缓冲区被出队,并且最后一个CAPTURE
缓冲区被出队,编码器就会停止工作,并且它将接受但不处理任何新入队的OUTPUT
缓冲区,直到客户端发出以下任何操作:V4L2_ENC_CMD_START
- 编码器不会被重置,并将正常恢复操作,包括排空之前的所有状态。在
CAPTURE
队列上成对执行VIDIOC_STREAMOFF()
和VIDIOC_STREAMON()
操作 - 编码器将被重置(参见 重置 序列),然后恢复编码。在
OUTPUT
队列上成对执行VIDIOC_STREAMOFF()
和VIDIOC_STREAMON()
操作 - 编码器将正常恢复操作,但是,在V4L2_ENC_CMD_STOP
和VIDIOC_STREAMOFF()
之间排队到OUTPUT
队列的任何源帧都将被丢弃。
注意
一旦启动排空序列,客户端需要将其驱动完成,如上述步骤所述,除非客户端通过在任何 OUTPUT
或 CAPTURE
队列上发出 VIDIOC_STREAMOFF()
来中止该过程。在排空序列进行时,不允许客户端再次发出 V4L2_ENC_CMD_START
或 V4L2_ENC_CMD_STOP
,如果尝试发出,它们将失败并返回 -EBUSY 错误代码。
为了方便参考,下面描述了各种边界情况的处理:
如果在发出
V4L2_ENC_CMD_STOP
命令时,OUTPUT
队列中没有缓冲区,则排空序列会立即完成,并且编码器会返回一个设置了V4L2_BUF_FLAG_LAST
标志的空CAPTURE
缓冲区。如果在排空序列完成时,
CAPTURE
队列中没有缓冲区,则客户端下次对CAPTURE
缓冲区进行入队时,它将立即作为设置了V4L2_BUF_FLAG_LAST
标志的空缓冲区返回。如果在清空序列的中间对
CAPTURE
队列调用VIDIOC_STREAMOFF()
,则清空序列将被取消,并且所有CAPTURE
缓冲区都将隐式返回给客户端。如果在清空序列的中间对
OUTPUT
队列调用VIDIOC_STREAMOFF()
,则清空序列将立即完成,并且下一个CAPTURE
缓冲区将返回为空,并设置V4L2_BUF_FLAG_LAST
标志。
虽然不是强制性的,但可以使用 VIDIOC_TRY_ENCODER_CMD()
查询编码器命令的可用性。
4.5.2.9. 重置¶
客户端可能希望请求编码器重新初始化编码,以便后续的流数据与之前生成的流数据相互独立。根据编码格式,这可能意味着
重新启动后生成的编码帧不得引用停止之前生成的任何帧,例如,H.264/HEVC 没有长期参考,
必须包含在独立流中的任何标头都必须再次生成,例如,H.264/HEVC 的 SPS 和 PPS。
这可以通过执行重置序列来实现。
执行 清空 序列,以确保所有正在进行的编码完成,并且相应的缓冲区被出队。
通过
VIDIOC_STREAMOFF()
停止CAPTURE
队列上的流传输。这将返回所有当前排队的CAPTURE
缓冲区给客户端,但不包含有效的帧数据。通过
VIDIOC_STREAMON()
启动CAPTURE
队列上的流传输,并继续进行常规编码序列。从现在开始,生成到CAPTURE
缓冲区中的编码帧将包含一个独立的流,该流可以被解码,而无需在重置序列之前编码的帧,从发出 清空 序列的 V4L2_ENC_CMD_STOP 后排队的第一个OUTPUT
缓冲区开始。
此序列也可以用于更改无法动态更改参数的编码器的编码参数。
4.5.2.10. 提交点¶
设置格式和分配缓冲区会触发编码器行为的更改。
在
CAPTURE
队列上设置格式可能会更改OUTPUT
队列上支持/通告的格式集。特别是,这也意味着OUTPUT
格式可能会被重置,并且客户端不得依赖于先前设置的格式被保留。枚举
OUTPUT
队列上的格式始终仅返回当前CAPTURE
格式支持的格式。在
OUTPUT
队列上设置格式不会更改CAPTURE
队列上可用的格式列表。尝试设置当前选择的CAPTURE
格式不支持的OUTPUT
格式将导致编码器将请求的OUTPUT
格式调整为支持的格式。枚举
CAPTURE
队列上的格式始终返回所有支持的编码格式,而与当前的OUTPUT
格式无关。当在任何
OUTPUT
或CAPTURE
队列上分配缓冲区时,客户端不得更改CAPTURE
队列上的格式。驱动程序将为任何此类格式更改尝试返回 -EBUSY 错误代码。
总而言之,设置格式和分配必须始终从 CAPTURE
队列开始,并且 CAPTURE
队列是控制 OUTPUT
队列支持的格式集的主控。