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(含)的整数序列,即 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.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

      注意

      此选择目标的常见用例是编码分辨率不是宏块倍数的源视频,例如,对于具有 16x16 宏块大小的编解码器,常见的 1920x1080 分辨率可能要求源缓冲区对齐到 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 的缓冲区可能会导致在编码过程后期和/或处理更多 OUTPUT 缓冲区后才在 CAPTURE 上产生一个缓冲区,或者无序返回,例如在使用显示重新排序时;

  • 缓冲区可能会在 CAPTURE 队列上可用,而无需额外缓冲区入队到 OUTPUT(例如在排空或 EOS 期间),这是因为过去入队的 OUTPUT 缓冲区,其编码结果由于编码过程的特殊性,仅在稍后可用;

  • 入队到 OUTPUT 的缓冲区可能不会在编码到相应的 CAPTURE 缓冲区后立即变得可出队,例如,如果编码器需要将该帧用作编码后续帧的参考。

注意

为了允许将编码后的 CAPTURE 缓冲区与其源自的 OUTPUT 缓冲区进行匹配,客户端可以在 OUTPUT 缓冲区入队时设置 timestamp 字段的 v4l2_buffer 结构体。从该 OUTPUT 缓冲区编码而来的 CAPTURE 缓冲区,在出队时其 timestamp 字段将被设置为相同的值。

除了一个 OUTPUT 缓冲区产生一个 CAPTURE 缓冲区的直接情况外,还定义了以下情况:

  • 一个 OUTPUT 缓冲区生成多个 CAPTURE 缓冲区:相同的 OUTPUT 时间戳将复制到多个 CAPTURE 缓冲区;

  • 编码顺序与呈现顺序不同(即 CAPTURE 缓冲区相对于 OUTPUT 缓冲区是无序的):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 队列才能允许配置更改。为此,它可以遵循 排空 序列,以避免丢失已入队/编码的帧。

参数更新的时序取决于编码器,并遵循 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() 的调用也不会失败,但同时它也不会启动 排空 序列,因此下面描述的步骤将不适用。

  2. 客户端在发出 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 缓冲区标志。

  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 标志的空缓冲区返回。

  • 如果在排空序列进行中调用 VIDIOC_STREAMOFF()CAPTURE 队列上,则排空序列被取消,所有 CAPTURE 缓冲区都隐式返回给客户端。

  • 如果在排空序列进行中调用 VIDIOC_STREAMOFF()OUTPUT 队列上,则排空序列立即完成,下一个 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 队列支持格式集合的主体。