4.5.2. 内存到内存的有状态视频编码器接口

有状态视频编码器按显示顺序获取原始视频帧,并将其编码为字节流。它生成完整的字节流块,包括所有元数据、标头等。生成的字节流不需要客户端进行任何进一步的后处理。

强烈建议不要在驱动程序中执行软件流处理、标头生成等操作以支持此接口。如果需要此类操作,强烈建议使用无状态视频编码器接口(正在开发中)。

4.5.2.1. 本文档中使用的约定和符号

  1. 如果本文档中未另行规定,则适用通用的 V4L2 API 规则。

  2. “必须”、“可以”、“应该”等词的含义与 RFC 2119 中的规定相同。

  3. 所有未标记为“可选”的步骤都是必需的。

  4. VIDIOC_G_EXT_CTRLS()VIDIOC_S_EXT_CTRLS() 可以与 VIDIOC_G_CTRL()VIDIOC_S_CTRL() 互换使用,除非另有说明。

  5. 单平面 API(请参阅 单平面和多平面 API)和适用的结构可以与多平面 API 互换使用,除非另有说明,具体取决于编码器功能并遵循通用的 V4L2 指南。

  6. i = [a..b]:从 a 到 b 的整数序列(包括 a 和 b),例如 i = [0..2]:i = 0, 1, 2。

  7. 给定一个 OUTPUT 缓冲区 A,则 A’ 表示 CAPTURE 队列上的缓冲区,其中包含处理缓冲区 A 产生的数据。

4.5.2.2. 术语表

请参阅 术语表

4.5.2.3. 状态机

DOT digraph of encoder state machine

编码器状态机

4.5.2.4. 查询功能

  1. 要枚举编码器支持的编码格式集,客户端可以在 CAPTURE 上调用 VIDIOC_ENUM_FMT()

    • 将返回完整的支持格式集,而与在 OUTPUT 上设置的格式无关。

  2. 要枚举支持的原始格式集,客户端可以在 OUTPUT 上调用 VIDIOC_ENUM_FMT()

    • 只会返回当前在 CAPTURE 上激活的格式支持的格式。

    • 为了枚举给定编码格式支持的原始格式,客户端必须首先在 CAPTURE 上设置该编码格式,然后在 OUTPUT 上枚举格式。

  3. 客户端可以使用 VIDIOC_ENUM_FRAMESIZES() 来检测给定格式支持的分辨率,并在 v4l2_frmsizeenum pixel_format 中传递所需的像素格式。

    • VIDIOC_ENUM_FRAMESIZES() 返回的编码像素格式的值将包括编码器对于给定的编码像素格式支持的所有可能的编码分辨率。

    • VIDIOC_ENUM_FRAMESIZES() 返回的原始像素格式的值将包括编码器对于给定的原始像素格式以及当前在 CAPTURE 上设置的编码格式支持的所有可能的帧缓冲区分辨率。

  4. 客户端可以使用 VIDIOC_ENUM_FRAMEINTERVALS() 来检测给定格式和分辨率支持的帧间隔,并在 v4l2_frmivalenum pixel_format 中传递所需的像素格式,并在 v4l2_frmivalenum widthv4l2_frmivalenum height 中传递分辨率。

    • VIDIOC_ENUM_FRAMEINTERVALS() 返回的编码像素格式和编码分辨率的值将包括编码器对于给定的编码像素格式和分辨率支持的所有可能的帧间隔。

    • VIDIOC_ENUM_FRAMEINTERVALS() 返回的原始像素格式和分辨率的值将包括编码器对于给定的原始像素格式和分辨率以及当前在 CAPTURE 上设置的编码格式、编码分辨率和编码帧间隔所支持的所有可能的帧间隔。

    • VIDIOC_ENUM_FRAMEINTERVALS() 的支持是可选的。如果未实现,则除了编解码器本身的限制之外,没有其他特殊限制。

  5. 可以使用各自的控件通过 VIDIOC_QUERYCTRL() 查询当前在 CAPTURE 上设置的编码格式(如果适用)支持的配置文件和级别。

  6. 可以通过查询其各自的控件来发现任何其他编码器功能。

4.5.2.5. 初始化

  1. 通过 VIDIOC_S_FMT()CAPTURE 队列上设置编码格式。

    • 必填字段

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

      pixelformat

      要生成的编码格式。

      sizeimage

      CAPTURE 缓冲区的所需大小;编码器可以调整它以匹配硬件要求。

      width, height

      忽略(只读)。

      其他字段

      遵循标准语义。

    • 返回字段

      sizeimage

      已调整的 CAPTURE 缓冲区的大小。

      width, height

      编码器基于当前状态(例如 OUTPUT 格式、选择矩形等)选择的编码大小(只读)。

    重要提示

    更改 CAPTURE 格式可能会更改当前设置的 OUTPUT 格式。如何确定新的 OUTPUT 格式取决于编码器,客户端必须确保它之后满足其需求。

  2. 可选。 通过 VIDIOC_ENUM_FMT() 枚举所选编码格式支持的 OUTPUT 格式(源的原始格式)。

    • 必填字段

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

      其他字段

      遵循标准语义。

    • 返回字段

      pixelformat

      当前在 CAPTURE 队列上选择的编码格式支持的原始格式。

      其他字段

      遵循标准语义。

  3. 通过 VIDIOC_S_FMT()OUTPUT 队列上设置原始源格式。

    • 必填字段

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

      pixelformat

      源的原始格式。

      width, height

      源分辨率。

      其他字段

      遵循标准语义。

    • 返回字段

      width, height

      可以根据当前选择的格式的要求进行调整,以匹配编码器的最小值、最大值和对齐要求,如 VIDIOC_ENUM_FRAMESIZES() 所报告。

      其他字段

      遵循标准语义。

    • 设置 OUTPUT 格式会将选择矩形重置为其默认值,该默认值基于新的分辨率,如下一步中所述。

  4. 通过 VIDIOC_S_PARM() 设置 OUTPUT 队列上的原始帧间隔。这也会将 CAPTURE 队列上的编码帧间隔设置为相同的值。

    • 必填字段

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

      parm.output

      将除 parm.output.timeperframe 之外的所有字段设置为 0。

      parm.output.timeperframe

      所需的帧间隔;编码器可能会调整它以匹配硬件要求。

    • 返回字段

      parm.output.timeperframe

      调整后的帧间隔。

    重要提示

    更改 OUTPUT 帧间隔也会设置编码器用来编码视频的帧率。因此,将帧间隔设置为 1/24(或每秒 24 帧)将生成一个可以按该速度回放的编码视频流。OUTPUT 队列的帧间隔只是一个提示,应用程序可能会以不同的速率提供原始帧。驱动程序可以使用它来帮助调度并行运行的多个编码器。

    在下一步中,可以选择将 CAPTURE 帧间隔更改为不同的值。这对于离线编码非常有用,在离线编码中,编码帧间隔可能与提供原始帧的速率不同。

    重要提示

    timeperframe 处理的是,而不是场。因此,对于隔行扫描格式,这是每两个场的时间,因为一帧由顶部场和底部场组成。

    注意

    由于历史原因,更改 OUTPUT 帧间隔也会更改 CAPTURE 队列上的编码帧间隔。理想情况下,这些应该是独立的设置,但这会破坏现有的 API。

  5. 可选 通过 VIDIOC_S_PARM() 设置 CAPTURE 队列上的编码帧间隔。仅当编码帧间隔与原始帧间隔不同时才需要这样做,离线编码通常是这种情况。此功能的支持由 V4L2_FMT_FLAG_ENC_CAP_FRAME_INTERVAL 格式标志指示。

    • 必填字段

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

      parm.capture

      将除 parm.capture.timeperframe 之外的所有字段设置为 0。

      parm.capture.timeperframe

      所需的编码帧间隔;编码器可能会调整它以匹配硬件要求。

    • 返回字段

      parm.capture.timeperframe

      调整后的帧间隔。

    重要提示

    更改 CAPTURE 帧间隔会设置编码视频的帧率。它设置缓冲区到达 CAPTURE 队列的速率,这取决于编码器的速度以及 OUTPUT 队列上排队原始帧的速度。

    重要提示

    timeperframe 处理的是,而不是场。因此,对于隔行扫描格式,这是每两个场的时间,因为一帧由顶部场和底部场组成。

    注意

    并非所有驱动程序都支持此功能,在这种情况下,只需为 OUTPUT 队列设置所需的编码帧间隔。

    但是,可以基于 OUTPUT 帧间隔调度多个编码器的驱动程序必须支持此可选功能。

  6. 可选。 如果希望与完整的 OUTPUT 分辨率不同,则通过 VIDIOC_S_SELECTION()OUTPUT 队列上设置流元数据的可见分辨率。

    • 必填字段

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

      target

      设置为 V4L2_SEL_TGT_CROP

      r.leftr.topr.widthr.height

      可见矩形;此矩形必须适合 V4L2_SEL_TGT_CROP_BOUNDS 矩形,并且可能会进行调整以匹配编解码器和硬件约束。

    • 返回字段

      r.leftr.topr.widthr.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() 返回的调整后的矩形。

  7. 通过 VIDIOC_REQBUFS()OUTPUTCAPTURE 分配缓冲区。这可以按任何顺序执行。

    • 必填字段

      count

      要分配的请求缓冲区数;大于零。

      type

      适合 OUTPUTCAPTUREV4L2_BUF_TYPE_* 枚举。

      其他字段

      遵循标准语义。

    • 返回字段

      count

      实际分配的缓冲区数。

    警告

    实际分配的缓冲区数可能与给定的 count 不同。客户端必须在调用返回后检查 count 的更新值。

    注意

    要分配多于最小数量的 OUTPUT 缓冲区(用于管道深度),客户端可以查询 V4L2_CID_MIN_BUFFERS_FOR_OUTPUT 控件以获取所需的最少缓冲区数,并将获得的值加上 count 字段中需要的额外缓冲区数传递给 VIDIOC_REQBUFS()

    或者,可以使用 VIDIOC_CREATE_BUFS() 来更好地控制缓冲区分配。

    • 必填字段

      count

      要分配的请求缓冲区数;大于零。

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

      其他字段

      遵循标准语义。

    • 返回字段

      count

      调整为已分配的缓冲区数。

  8. 通过 VIDIOC_STREAMON() 开始在 OUTPUTCAPTURE 队列上进行流式传输。这可以按任何顺序执行。当两个队列都开始流式传输时,实际的编码过程开始。

注意

如果客户端在编码过程中停止 CAPTURE 队列,然后再次重新启动它,则编码器将开始生成一个独立于停止之前生成的流的流。确切的约束取决于编码格式,但可能包括以下含义

  • 重新启动后生成的编码帧不得引用停止之前生成的任何帧,例如,H.264/HEVC 没有长期参考,

  • 必须包含在独立流中的任何标头都必须再次生成,例如,H.264/HEVC 的 SPS 和 PPS。

4.5.2.6. 编码

初始化 序列成功完成后达到此状态。在此状态下,客户端通过 VIDIOC_QBUF()VIDIOC_DQBUF() 将缓冲区排队和出队到两个队列,遵循标准语义。

编码的 CAPTURE 缓冲区的内容取决于活动的编码像素格式,并且可能受到编解码器特定的扩展控件的影响,如每个格式的文档中所述。

两个队列独立运行,遵循 V4L2 缓冲区队列和内存到内存设备的标准行为。此外,由于所选编码格式的属性(例如帧重排序),从 CAPTURE 队列出队的编码帧的顺序可能与将原始帧排队到 OUTPUT 队列的顺序不同。

客户端不得假设 CAPTUREOUTPUT 缓冲区之间存在任何直接关系,以及任何特定的缓冲区可用于出队的时间。具体来说

  • 排队到 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 缓冲区的编码帧。

  1. 通过发出 VIDIOC_ENCODER_CMD() 来开始排空序列。

    • 必填字段

      cmd

      设置为 V4L2_ENC_CMD_STOP

      flags

      设置为 0。

      pts

      设置为 0。

    警告

    只有在 OUTPUTCAPTURE 队列都在流式传输时,才能启动该序列。出于兼容性原因,即使任何队列未在流式传输,调用 VIDIOC_ENCODER_CMD() 也不会失败,但同时它也不会启动 Drain 序列,因此下面描述的步骤将不适用。

  2. 客户端在发出 VIDIOC_ENCODER_CMD() 之前排队的任何 OUTPUT 缓冲区都将像正常情况一样进行处理和编码。客户端必须继续独立处理两个队列,类似于正常的编码操作。这包括:

    • CAPTURE 缓冲区进行入队和出队操作,直到出队带有 V4L2_BUF_FLAG_LAST 标志的缓冲区。

      警告

      最后一个缓冲区可能为空(v4l2_bufferbytesused = 0),在这种情况下,客户端必须忽略它,因为它不包含编码帧。

      注意

      任何尝试在标记有 V4L2_BUF_FLAG_LAST 的缓冲区之外出队更多 CAPTURE 缓冲区都将导致从 VIDIOC_DQBUF() 返回 -EPIPE 错误。

    • 对已处理的 OUTPUT 缓冲区进行出队操作,直到所有在 V4L2_ENC_CMD_STOP 命令之前入队的缓冲区都被出队。

    • 出队 V4L2_EVENT_EOS 事件(如果客户端订阅了该事件)。

    注意

    为了向后兼容,编码器将在最后一个帧被编码并且所有帧都准备好出队时发出 V4L2_EVENT_EOS 事件。这是一种不推荐使用的行为,客户端不得依赖它。应改为使用 V4L2_BUF_FLAG_LAST 缓冲区标志。

  3. 一旦所有在 V4L2_ENC_CMD_STOP 调用之前入队的 OUTPUT 缓冲区被出队,并且最后一个 CAPTURE 缓冲区被出队,编码器就会停止工作,并且它将接受但不处理任何新入队的 OUTPUT 缓冲区,直到客户端发出以下任何操作:

    • V4L2_ENC_CMD_START - 编码器不会被重置,并将正常恢复操作,包括排空之前的所有状态。

    • CAPTURE 队列上成对执行 VIDIOC_STREAMOFF()VIDIOC_STREAMON() 操作 - 编码器将被重置(参见 重置 序列),然后恢复编码。

    • OUTPUT 队列上成对执行 VIDIOC_STREAMOFF()VIDIOC_STREAMON() 操作 - 编码器将正常恢复操作,但是,在 V4L2_ENC_CMD_STOPVIDIOC_STREAMOFF() 之间排队到 OUTPUT 队列的任何源帧都将被丢弃。

注意

一旦启动排空序列,客户端需要将其驱动完成,如上述步骤所述,除非客户端通过在任何 OUTPUTCAPTURE 队列上发出 VIDIOC_STREAMOFF() 来中止该过程。在排空序列进行时,不允许客户端再次发出 V4L2_ENC_CMD_STARTV4L2_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。

这可以通过执行重置序列来实现。

  1. 执行 清空 序列,以确保所有正在进行的编码完成,并且相应的缓冲区被出队。

  2. 通过 VIDIOC_STREAMOFF() 停止 CAPTURE 队列上的流传输。这将返回所有当前排队的 CAPTURE 缓冲区给客户端,但不包含有效的帧数据。

  3. 通过 VIDIOC_STREAMON() 启动 CAPTURE 队列上的流传输,并继续进行常规编码序列。从现在开始,生成到 CAPTURE 缓冲区中的编码帧将包含一个独立的流,该流可以被解码,而无需在重置序列之前编码的帧,从发出 清空 序列的 V4L2_ENC_CMD_STOP 后排队的第一个 OUTPUT 缓冲区开始。

此序列也可以用于更改无法动态更改参数的编码器的编码参数。

4.5.2.10. 提交点

设置格式和分配缓冲区会触发编码器行为的更改。

  1. CAPTURE 队列上设置格式可能会更改 OUTPUT 队列上支持/通告的格式集。特别是,这也意味着 OUTPUT 格式可能会被重置,并且客户端不得依赖于先前设置的格式被保留。

  2. 枚举 OUTPUT 队列上的格式始终仅返回当前 CAPTURE 格式支持的格式。

  3. OUTPUT 队列上设置格式不会更改 CAPTURE 队列上可用的格式列表。尝试设置当前选择的 CAPTURE 格式不支持的 OUTPUT 格式将导致编码器将请求的 OUTPUT 格式调整为支持的格式。

  4. 枚举 CAPTURE 队列上的格式始终返回所有支持的编码格式,而与当前的 OUTPUT 格式无关。

  5. 当在任何 OUTPUTCAPTURE 队列上分配缓冲区时,客户端不得更改 CAPTURE 队列上的格式。驱动程序将为任何此类格式更改尝试返回 -EBUSY 错误代码。

总而言之,设置格式和分配必须始终从 CAPTURE 队列开始,并且 CAPTURE 队列是控制 OUTPUT 队列支持的格式集的主控。