4.5.1. 内存到内存的有状态视频解码器接口

有状态视频解码器接收完整的字节流块(例如,Annex-B H.264/HEVC 流,原始 VP8/9 流),并将它们解码为按显示顺序排列的原始视频帧。解码器不应要求客户端提供任何额外信息来处理这些缓冲区。

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

4.5.1.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.1.2. 词汇表

CAPTURE

目标缓冲区队列;对于解码器,是包含解码帧的缓冲区队列;对于编码器,是包含编码字节流的缓冲区队列;V4L2_BUF_TYPE_VIDEO_CAPTUREV4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;数据从硬件捕获到 CAPTURE 缓冲区中。

客户端

与实现此接口的解码器或编码器通信的应用程序。

编码格式

编码/压缩的视频字节流格式(例如,H.264、VP8 等);另请参阅:原始格式。

编码高度

给定编码分辨率的高度。

编码分辨率

像素流分辨率与编解码器和硬件要求对齐;通常,可见分辨率向上舍入到完整的宏块;另请参阅:可见分辨率。

编码宽度

给定编码分辨率的宽度。

编码树单元

HEVC 编解码器的处理单元(对应于 H.264、VP8、VP9 中的宏块单元),可以使用高达 64×64 像素的块结构。擅长将图片细分为可变大小的结构。

解码顺序

帧被解码的顺序;如果编码格式包含帧重新排序的功能,则可能与显示顺序不同;对于解码器,客户端必须按解码顺序对 OUTPUT 缓冲区进行排队;对于编码器,编码器必须按解码顺序返回 CAPTURE 缓冲区。

目标

解码过程产生的数据;请参见 CAPTURE

显示顺序

必须显示帧的顺序;对于编码器,客户端必须按显示顺序对 OUTPUT 缓冲区进行排队;对于解码器,解码器必须按显示顺序返回 CAPTURE 缓冲区。

DPB

解码图像缓冲区;H.264/HEVC 术语,用于存储在进一步解码步骤中可用于参考的已解码原始帧的缓冲区。

EOS

流结束。

IDR

即时解码器刷新;H.264/HEVC 编码流中的一种关键帧类型,它会清除较早参考帧(DPB)的列表。

关键帧

一个编码帧,不引用之前解码的帧,即可以完全独立解码。

宏块

基于线性块变换的图像和视频压缩格式(例如 H.264、VP8、VP9)中的处理单元;特定于编解码器,但对于大多数流行的编解码器,大小为 16x16 个样本(像素)。HEVC 编解码器使用一种稍微灵活的处理单元,称为编码树单元 (CTU)。

OUTPUT

源缓冲区队列;对于解码器,是包含编码字节流的缓冲区队列;对于编码器,是包含原始帧的缓冲区队列;V4L2_BUF_TYPE_VIDEO_OUTPUTV4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;硬件从 OUTPUT 缓冲区获取数据。

PPS

图片参数集;H.264/HEVC 字节流中的一种元数据实体。

原始格式

包含原始像素数据(例如,YUV、RGB 格式)的未压缩格式。

恢复点

字节流中可以开始/继续解码的点,无需任何之前的状态/数据存在,例如:关键帧 (VP8/VP9) 或 SPS/PPS/IDR 序列 (H.264/HEVC);需要恢复点才能开始解码新流,或者在寻道后恢复解码。

馈送到解码器或编码器的数据;请参见 OUTPUT

源高度

给定源分辨率的像素高度;仅与编码器相关。

源分辨率

作为编码器源的源帧的像素分辨率,并进一步裁剪到可见分辨率的边界;仅与编码器相关。

源宽度

给定源分辨率的像素宽度;仅与编码器相关。

SPS

序列参数集;H.264/HEVC 字节流中的一种元数据实体。

流元数据

编码字节流中包含的额外(非视觉)信息;例如:编码分辨率、可见分辨率、编解码器配置文件。

可见高度

给定可见分辨率的高度;显示高度。

可见分辨率

用于显示目的的可见图片的流分辨率(以像素为单位);必须小于或等于编码分辨率;显示分辨率。

可见宽度

给定可见分辨率的宽度;显示宽度。

4.5.1.3. 状态机

DOT digraph of decoder state machine

解码器状态机

4.5.1.4. 查询功能

  1. 为了枚举解码器支持的编码格式集,客户端可以调用 VIDIOC_ENUM_FMT()OUTPUT 上。

    • 将返回所有支持的格式,而与在 CAPTURE 上设置的格式无关。

    • 检查 v4l2_fmtdesc 的 flags 字段,以获取有关解码器在每种编码格式方面的功能的更多信息。特别是解码器是否具有成熟的字节流解析器,以及解码器是否支持动态分辨率更改。

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

    • 将仅返回当前在 OUTPUT 上处于活动状态的格式支持的格式。

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

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

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

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

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

4.5.1.5. 初始化

  1. 通过 VIDIOC_S_FMT()OUTPUT 上设置编码格式。

    • 必需字段

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

      pixelformat

      编码的像素格式。

      widthheight

      流的编码分辨率;只有当无法从给定编码格式的流中解析时才需要;否则,解码器将使用此分辨率作为占位符分辨率,一旦它可以从流中解析实际的编码分辨率,该分辨率很可能会更改。

      sizeimage

      OUTPUT 缓冲区所需的大小;解码器可能会调整它以匹配硬件要求。

      其他字段

      遵循标准语义。

    • 返回字段

      sizeimage

      OUTPUT 缓冲区调整后的大小。

    • CAPTURE 格式将基于 VIDIOC_S_FMT() 返回的宽度和高度,立即更新为适当的帧缓冲区分辨率。然而,对于包含流分辨率信息的编码格式,在解码器完成从流中解析信息后,它将使用新值更新 CAPTURE 格式,并发出源更改事件,无论它们是否与客户端设置的值匹配。

    重要提示

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

  2. 通过 VIDIOC_REQBUFS()OUTPUT 上分配源(字节流)缓冲区。

    • 必需字段

      count

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

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

      memory

      遵循标准语义。

    • 返回字段

      count

      实际分配的缓冲区数量。

    警告

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

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

    • 必需字段

      count

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

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

      memory

      遵循标准语义。

      format

      遵循标准语义。

    • 返回字段

      count

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

    警告

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

  3. 通过 VIDIOC_STREAMON()OUTPUT 队列上开始流式传输。

  4. 此步骤仅适用于流中包含分辨率信息的编码格式。 通过 VIDIOC_QBUF()VIDIOC_DQBUF() 继续将字节流缓冲区排队/出队到/从 OUTPUT 队列。将处理这些缓冲区并按顺序返回给客户端,直到找到配置 CAPTURE 队列所需的元数据。这通过解码器发送 V4L2_EVENT_SOURCE_CHANGE 事件且 changes 设置为 V4L2_EVENT_SRC_CH_RESOLUTION 来指示。

    • 如果第一个缓冲区不包含足够的数据来发生这种情况,则不是错误。只要需要更多数据,缓冲区的处理将继续。

    • 如果触发事件的缓冲区中的数据需要解码第一帧,则在初始化序列完成并解码帧之前,该数据不会返回给客户端。

    • 如果客户端没有自行设置流的编码分辨率,则在 CAPTURE 队列上调用 VIDIOC_G_FMT()VIDIOC_S_FMT()VIDIOC_TRY_FMT()VIDIOC_REQBUFS() 不会返回流的真实值,直到发出 V4L2_EVENT_SOURCE_CHANGE 事件,且 changes 设置为 V4L2_EVENT_SRC_CH_RESOLUTION

    重要提示

    解码器将事件排队后发出的任何客户端查询都将返回适用于刚刚解析的流的值,包括队列格式、选择矩形和控件。

    注意

    能够自行从字节流获取流参数的客户端可以尝试将 OUTPUT 格式的宽度和高度设置为与流的编码大小匹配的非零值,跳过此步骤并继续执行“捕获设置”序列。但是,它不得依赖任何关于流参数的驱动程序查询,例如选择矩形和控件,因为解码器尚未从流中解析它们。如果客户端配置的值与解码器解析的值不匹配,将触发“动态分辨率更改”以重新配置它们。

    注意

    在此阶段不会生成解码帧。

  5. 继续执行“捕获设置”序列。

4.5.1.6. 捕获设置

  1. CAPTURE 队列上调用 VIDIOC_G_FMT(),以获取从字节流解析/解码的目标缓冲区的格式。

    • 必需字段

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

    • 返回字段

      widthheight

      解码帧的帧缓冲区分辨率。

      pixelformat

      解码帧的像素格式。

      num_planes (仅适用于 _MPLANE type

      像素格式的平面数。

      sizeimagebytesperline

      按照标准语义;匹配帧缓冲区格式。

    注意

    pixelformat 的值可以是解码器当前支持的任何像素格式。解码器应为默认配置选择首选/最佳格式。例如,如果后者需要额外的转换步骤,则 YUV 格式可能比 RGB 格式更受欢迎。

  2. 可选。通过 VIDIOC_G_SELECTION() 获取可见分辨率。

    • 必需字段

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

      target

      设置为 V4L2_SEL_TGT_COMPOSE

    • 返回字段

      r.leftr.topr.widthr.height

      可见矩形;它必须适合 CAPTUREVIDIOC_G_FMT() 返回的帧缓冲区分辨率。

    • 以下选择目标在 CAPTURE 上受支持

      V4L2_SEL_TGT_CROP_BOUNDS

      对应于流的编码分辨率。

      V4L2_SEL_TGT_CROP_DEFAULT

      覆盖 CAPTURE 缓冲区中包含有意义的图片数据(可见区域)的部分的矩形;宽度和高度将等于流的可见分辨率。

      V4L2_SEL_TGT_CROP

      要输出到 CAPTURE 的编码分辨率内的矩形;默认为 V4L2_SEL_TGT_CROP_DEFAULT;在没有其他合成/缩放功能的硬件上为只读。

      V4L2_SEL_TGT_COMPOSE_BOUNDS

      CAPTURE 缓冲区中的最大矩形,裁剪后的帧可以合成到该矩形中;如果硬件不支持合成/缩放,则等于 V4L2_SEL_TGT_CROP

      V4L2_SEL_TGT_COMPOSE_DEFAULT

      等于 V4L2_SEL_TGT_CROP

      V4L2_SEL_TGT_COMPOSE

      写入裁剪后的帧的 CAPTURE 缓冲区内的矩形;默认为 V4L2_SEL_TGT_COMPOSE_DEFAULT;在没有其他合成/缩放功能的硬件上为只读。

      V4L2_SEL_TGT_COMPOSE_PADDED

      硬件覆盖的 CAPTURE 缓冲区内的矩形;如果硬件不写入填充像素,则等于 V4L2_SEL_TGT_COMPOSE

    警告

    只有在解码器成功解析流元数据后,这些值才能保证有意义。在此之前,客户端不得依赖于查询。

  3. 可选。 通过 CAPTURE 队列上的 VIDIOC_ENUM_FMT() 枚举 CAPTURE 格式。一旦解析并知道流信息,客户端可以使用此 ioctl 来发现给定流支持哪些原始格式,并通过 VIDIOC_S_FMT() 选择其中一种。

    重要提示

    解码器将仅返回当前已建立的编码格式支持的格式,这遵循OUTPUT格式和/或在此初始化序列中解析的流元数据,即使解码器通常可能支持更多格式。换句话说,返回的集合将是查询功能部分中提到的初始查询的子集。

    例如,解码器可能支持分辨率为 1920x1088 及以下的 YUV 和 RGB 格式,但对于更高的分辨率仅支持 YUV(由于硬件限制)。在解析 1920x1088 或更低的分辨率后,VIDIOC_ENUM_FMT() 可能会返回一组 YUV 和 RGB 像素格式,但在解析高于 1920x1088 的分辨率后,解码器将不会返回 RGB,因为此分辨率不支持 RGB。

    但是,在同一流中发现分辨率更改后触发的后续分辨率更改事件可能会将流切换到较低的分辨率,在这种情况下,VIDIOC_ENUM_FMT() 将再次返回 RGB 格式。

  4. 可选。 通过 VIDIOC_S_FMT()CAPTURE 队列上设置 CAPTURE 格式。客户端可以选择与解码器在 VIDIOC_G_FMT() 中选择/建议的格式不同的格式。

    • 必需字段

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

      pixelformat

      原始像素格式。

      widthheight

      解码流的帧缓冲区分辨率;通常与 VIDIOC_G_FMT() 返回的分辨率相同,但如果硬件支持合成和/或缩放,则可能会有所不同。

  • 设置 CAPTURE 格式会将合成选择矩形重置为基于新分辨率的默认值,如上一步所述。

  1. 可选。 如果需要并且解码器具有合成和/或缩放功能,则通过 VIDIOC_S_SELECTION()CAPTURE 队列上设置合成矩形。

    • 必需字段

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

      target

      设置为 V4L2_SEL_TGT_COMPOSE

      r.leftr.topr.widthr.height

      写入裁剪后的帧的 CAPTURE 缓冲区内的矩形;默认为 V4L2_SEL_TGT_COMPOSE_DEFAULT;在没有其他合成/缩放功能的硬件上为只读。

    • 返回字段

      r.leftr.topr.widthr.height

      可见矩形;它必须适合 CAPTUREVIDIOC_G_FMT() 返回的帧缓冲区分辨率。

    警告

    解码器可能会将合成矩形调整为最接近的支持矩形,以满足编解码器和硬件要求。客户端需要检查 VIDIOC_S_SELECTION() 返回的调整后的矩形。

  2. 如果满足以下所有条件,客户端可以立即恢复解码

    • 新格式的 sizeimage(在上一步中确定)小于或等于当前已分配缓冲区的大小,

    • 当前分配的缓冲区数量大于或等于在前几步中获取的最小缓冲区数量。为了满足此要求,客户端可以使用 VIDIOC_CREATE_BUFS() 添加新缓冲区。

    在这种情况下,其余步骤不适用,客户端可以通过以下操作之一恢复解码

    • 如果 CAPTURE 队列正在流式传输,请使用 V4L2_DEC_CMD_START 命令调用 VIDIOC_DECODER_CMD()

    • 如果 CAPTURE 队列未在流式传输,请在 CAPTURE 队列上调用 VIDIOC_STREAMON()

    但是,如果客户端打算更改缓冲区集,以降低内存使用量或出于任何其他原因,则可以通过执行以下步骤来实现。

  3. 如果 CAPTURE 队列正在流式传输, 请继续在 CAPTURE 队列上排队和出队缓冲区,直到出队带有 V4L2_BUF_FLAG_LAST 标志的缓冲区。

  4. 如果 CAPTURE 队列正在流式传输, 请在 CAPTURE 队列上调用 VIDIOC_STREAMOFF() 以停止流式传输。

    警告

    OUTPUT 队列必须保持流式传输。在其上调用 VIDIOC_STREAMOFF() 将中止该序列并触发查找。

  5. 如果 CAPTURE 队列已分配缓冲区, 请使用 VIDIOC_REQBUFS() 释放 CAPTURE 缓冲区。

    • 必需字段

      count

      设置为 0。

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

      memory

      遵循标准语义。

  6. 通过 VIDIOC_REQBUFS()CAPTURE 队列上分配 CAPTURE 缓冲区。

    • 必需字段

      count

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

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

      memory

      遵循标准语义。

    • 返回字段

      count

      实际分配的缓冲区数量。

    警告

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

    注意

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

    或者,可以使用 CAPTURE 队列上的 VIDIOC_CREATE_BUFS() 来更好地控制缓冲区分配。例如,通过分配大于当前 CAPTURE 格式的缓冲区,可以适应未来的分辨率变化。

    • 必需字段

      count

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

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

      memory

      遵循标准语义。

      format

      表示新分配的缓冲区要适应的最大帧缓冲区分辨率的格式。

    • 返回字段

      count

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

    警告

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

    注意

    要为与从流元数据解析的格式不同的格式分配缓冲区,客户端必须在启动元数据解析之前按如下方式进行

    • OUTPUT 格式的宽度和高度设置为所需的编码分辨率,以使解码器适当地配置 CAPTURE 格式,

    • 使用 VIDIOC_G_FMT() 查询 CAPTURE 格式并将其保存到此步骤。

    然后可以将查询中获得的格式在此步骤中与 VIDIOC_CREATE_BUFS() 一起使用来分配缓冲区。

  7. CAPTURE 队列上调用 VIDIOC_STREAMON() 以开始解码帧。

4.5.1.7. 解码

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

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

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

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

  • 排队到 OUTPUT 的缓冲区可能不会导致 CAPTURE 上产生任何缓冲区(例如,如果它不包含编码数据,或者如果其中仅存在元数据语法结构),

  • 排队到 OUTPUT 的缓冲区可能会导致 CAPTURE 上产生多个缓冲区(如果编码数据包含多个帧,或者如果返回解码帧允许解码器返回在解码中先于它但在显示顺序中在其后面的帧),

  • 排队到 OUTPUT 的缓冲区可能会导致在解码过程的稍后阶段和/或处理进一步的 OUTPUT 缓冲区后在 CAPTURE 上产生缓冲区,或者以无序的方式返回,例如,如果使用显示重新排序,

  • 缓冲区可能会在没有其他缓冲区排队到 OUTPUT 的情况下在 CAPTURE 队列上变为可用(例如,在耗尽或 EOS 期间),因为过去排队的 OUTPUT 缓冲区的解码结果仅在稍后时间可用,这是由于解码过程的特殊性。

注意

为了允许将解码后的 CAPTURE 缓冲区与它们来自的 OUTPUT 缓冲区进行匹配,客户端可以在将 OUTPUT 缓冲区入队时设置 v4l2_buffer 结构的 timestamp 字段。由该 OUTPUT 缓冲区解码产生的 CAPTURE 缓冲区在出队时,它们的 timestamp 字段将被设置为相同的值。

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

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

  • 多个 OUTPUT 缓冲区生成一个 CAPTURE 缓冲区:将复制第一个入队的 OUTPUT 缓冲区的时间戳。

  • 解码顺序与显示顺序不同(即,与 OUTPUT 缓冲区相比,CAPTURE 缓冲区是乱序的):CAPTURE 时间戳将不会保留 OUTPUT 时间戳的顺序。

注意

即使 CAPTURE 缓冲区出队后,流使用的作为参考帧的 CAPTURE 缓冲区的后备内存也可能会被硬件读取。因此,客户端应避免在 CAPTURE 队列正在流式传输时写入此内存。不遵守此规则可能会导致解码帧损坏。

同样,当使用除 V4L2_MEMORY_MMAP 以外的内存类型时,客户端应确保每个 CAPTURE 缓冲区在 CAPTURE 队列流式传输期间始终以相同的后备内存入队。原因是驱动程序可以使用 V4L2 缓冲区索引来识别帧。因此,如果参考帧的后备内存以不同的缓冲区 ID 提交,则驱动程序可能会错误地识别它,并在它仍在使用的同时将新帧解码到其中,从而导致以下帧损坏。

在解码过程中,解码器可能会启动以下列出的特殊序列之一。这些序列将导致解码器返回来自序列开始之前处理的所有 OUTPUT 缓冲区的所有 CAPTURE 缓冲区。最后一个缓冲区将设置 V4L2_BUF_FLAG_LAST 标志。要确定要遵循的序列,客户端必须检查是否有任何待处理的事件,并且

  • 如果 V4L2_EVENT_SOURCE_CHANGE 事件的 changes 设置为 V4L2_EVENT_SRC_CH_RESOLUTION,则需要遵循“动态分辨率更改”序列。

  • 如果 V4L2_EVENT_EOS 事件处于待处理状态,则需要遵循“流结束”序列。

某些序列可以相互混合,并且需要按照它们发生的方式进行处理。每个序列的具体操作都有文档记录。

如果发生解码错误,将向客户端报告,错误详细程度取决于解码器的功能。具体来说:

  • 包含解码失败操作结果的 CAPTURE 缓冲区将返回,并设置 V4L2_BUF_FLAG_ERROR 标志。

  • 如果解码器能够精确报告触发错误的 OUTPUT 缓冲区,则将返回该缓冲区并设置 V4L2_BUF_FLAG_ERROR 标志。

如果发生不允许解码继续的致命故障,则对相应解码器文件句柄的任何进一步操作都将返回 -EIO 错误代码。客户端可以关闭文件句柄并打开新的文件句柄,或者通过停止两个队列的流式传输、释放所有缓冲区并再次执行初始化序列来重新初始化实例。

4.5.1.8. Seek

Seek 由 OUTPUT 队列控制,因为它是编码数据的来源。Seek 不需要对 CAPTURE 队列执行任何特定操作,但可能会根据正常的解码器操作受到影响。

  1. 通过 VIDIOC_STREAMOFF() 停止 OUTPUT 队列以开始 seek 序列。

    • 必需字段

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

    • 解码器将丢弃所有待处理的 OUTPUT 缓冲区,并且必须将其视为已返回给客户端(遵循标准语义)。

  2. 通过 VIDIOC_STREAMON() 重新启动 OUTPUT 队列。

    • 必需字段

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

    • 调用返回后,解码器将开始接受新的源字节流缓冲区。

  3. 在 seek 后,开始将包含编码数据的缓冲区入队到 OUTPUT 队列,直到找到合适的恢复点。

    注意

    没有要求从恢复点(例如 SPS 或关键帧)开始精确地入队编码数据。任何入队的 OUTPUT 缓冲区都将被处理并返回给客户端,直到找到合适的恢复点。在寻找恢复点的同时,解码器不应将任何解码帧生成到 CAPTURE 缓冲区中。

    已知某些硬件会错误地处理 seek 到非恢复点的情况。此类操作可能会导致在 CAPTURE 队列上提供不确定数量的损坏的解码帧。驱动程序必须确保不会发生致命的解码错误或崩溃,并针对与 seek 操作相关的硬件问题实施任何必要的处理和解决方法。

    警告

    对于 H.264/HEVC 编解码器,客户端必须注意不要在 SPS/PPS 更改时执行 seek 操作。即使目标帧可以是关键帧,解码器状态内的过时 SPS/PPS 也会在解码时导致未定义的结果。尽管解码器必须在没有崩溃或致命解码错误的情况下处理这种情况,但客户端不应期望获得合理的解码输出。

    如果硬件可以检测到此类损坏的解码帧,则会将相应的缓冲区返回给客户端,并设置 V4L2_BUF_FLAG_ERROR。有关解码错误报告的进一步描述,请参见“解码”部分。

  4. 找到恢复点后,解码器将开始返回包含解码帧的 CAPTURE 缓冲区。

重要提示

Seek 可能会导致启动“动态分辨率更改”序列,因为 seek 目标具有与 seek 之前解码的流部分不同的解码参数。必须按照正常的解码器操作来处理此序列。

警告

未指定 CAPTURE 队列何时开始生成包含来自 seek 后入队的 OUTPUT 缓冲区的解码数据的缓冲区,因为它独立于 OUTPUT 队列运行。

解码器可能会返回多个剩余的 CAPTURE 缓冲区,其中包含来自在执行 seek 序列之前入队的 OUTPUT 缓冲区的解码帧。

VIDIOC_STREAMOFF 操作会丢弃任何剩余的入队 OUTPUT 缓冲区,这意味着并非所有在 seek 序列之前入队的 OUTPUT 缓冲区都可能会生成匹配的 CAPTURE 缓冲区。例如,给定 OUTPUT 队列上的操作顺序:

QBUF(A), QBUF(B), STREAMOFF(), STREAMON(), QBUF(G), QBUF(H),

允许在 CAPTURE 队列上出现以下任何结果:

{A’, B’, G’, H’}, {A’, G’, H’}, {G’, H’}。

要确定包含 seek 后第一个解码帧的 CAPTURE 缓冲区,客户端可以观察时间戳以匹配 CAPTURE 和 OUTPUT 缓冲区,或使用 V4L2_DEC_CMD_STOP 和 V4L2_DEC_CMD_START 来清空解码器。

注意

要实现瞬时 seek,客户端也可以重新启动 CAPTURE 队列上的流式传输,以丢弃已解码但尚未出队的缓冲区。

4.5.1.9. 动态分辨率更改

字节流中包含分辨率元数据的流可能需要在解码期间切换到不同的分辨率。

注意

并非所有解码器都能检测到分辨率更改。那些可以检测到的解码器会在调用 VIDIOC_ENUM_FMT() 时为编码格式设置 V4L2_FMT_FLAG_DYN_RESOLUTION 标志。

当解码器检测到编码帧的一个或多个参数与先前建立的参数(以及相应的查询所反映的参数)不同时,序列开始:

  • 编码分辨率(OUTPUT 宽度和高度),

  • 可见分辨率(选择矩形),

  • 解码所需的最小缓冲区数量,

  • 比特流的位深度已更改。

每当发生这种情况时,解码器必须按如下方式进行:

  1. 在流中遇到分辨率更改后,解码器会发送一个 V4L2_EVENT_SOURCE_CHANGE 事件,并将 changes 设置为 V4L2_EVENT_SRC_CH_RESOLUTION

    重要提示

    解码器对事件进行排队后发出的任何客户端查询都将返回应用于分辨率更改后流的值,包括队列格式、选择矩形和控件。

  2. 然后,解码器将处理并解码分辨率更改点之前的所有剩余缓冲区。

    • 更改之前的最后一个缓冲区必须标记 V4L2_BUF_FLAG_LAST 标志,类似于上面的排空序列。

    警告

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

    注意

    任何尝试出队超过标记 V4L2_BUF_FLAG_LAST 标志的 CAPTURE 缓冲区的操作,都会导致 VIDIOC_DQBUF() 返回 -EPIPE 错误。

客户端必须按照下面描述的步骤继续该序列,以继续解码过程。

  1. 将源更改事件出队。

    重要提示

    源更改会触发隐式的解码器排空,类似于显式的排空序列。解码器在完成后停止。必须通过以下两种方式之一恢复解码过程:在 CAPTURE 队列上调用一对 VIDIOC_STREAMOFF()VIDIOC_STREAMON(),或者使用 V4L2_DEC_CMD_START 命令调用 VIDIOC_DECODER_CMD()

  2. 继续执行“捕获设置”序列。

注意

在分辨率更改序列期间,OUTPUT 队列必须保持流式传输状态。在 OUTPUT 队列上调用 VIDIOC_STREAMOFF() 将中止该序列并启动查找。

原则上,OUTPUT 队列与 CAPTURE 队列分开操作,这在整个分辨率更改序列期间也保持不变。

为了获得最佳性能和简单性,即使在处理此序列时,客户端也应继续向/从 OUTPUT 队列排队/出队缓冲区。

4.5.1.10. 排空

为确保所有排队的 OUTPUT 缓冲区都已处理,并且相关的 CAPTURE 缓冲区已提供给客户端,客户端必须遵循下面描述的排空序列。排空序列结束后,客户端已收到在序列开始之前排队的所有 OUTPUT 缓冲区的所有解码帧。

  1. 通过发出 VIDIOC_DECODER_CMD() 开始排空。

    • 必需字段

      cmd

      设置为 V4L2_DEC_CMD_STOP

      flags

      设置为 0。

      pts

      设置为 0。

    警告

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

  2. 客户端在发出 VIDIOC_DECODER_CMD() 之前排队的任何 OUTPUT 缓冲区都将照常处理和解码。客户端必须继续独立处理这两个队列,类似于正常的解码操作。这包括

    • 在继续排空序列之前,处理由处理这些缓冲区触发的任何操作,例如动态分辨率更改序列,

    • 排队和出队 CAPTURE 缓冲区,直到出队标记 V4L2_BUF_FLAG_LAST 标志的缓冲区,

      警告

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

      注意

      任何尝试出队超过标记 V4L2_BUF_FLAG_LAST 标志的 CAPTURE 缓冲区的操作,都会导致 VIDIOC_DQBUF() 返回 -EPIPE 错误。

    • 出队已处理的 OUTPUT 缓冲区,直到出队在 V4L2_DEC_CMD_STOP 命令之前排队的所有缓冲区,

    • 如果客户端订阅了 V4L2_EVENT_EOS 事件,则将其出队。

    注意

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

  3. 一旦出队所有在调用 V4L2_DEC_CMD_STOP 之前排队的 OUTPUT 缓冲区,并且出队最后一个 CAPTURE 缓冲区,解码器将停止,并且它将接受(但不会处理)任何新排队的 OUTPUT 缓冲区,直到客户端发出以下任何操作为止

    • V4L2_DEC_CMD_START - 解码器将不会重置,并将正常恢复操作,并保留排空之前的所有状态,

    • CAPTURE 队列上调用一对 VIDIOC_STREAMOFF()VIDIOC_STREAMON() - 解码器将正常恢复操作,但是队列中仍然存在的任何 CAPTURE 缓冲区都将返回给客户端,

    • OUTPUT 队列上调用一对 VIDIOC_STREAMOFF()VIDIOC_STREAMON() - 任何挂起的源缓冲区都将返回给客户端,并且将触发查找序列。

注意

一旦启动排空序列,客户端需要按照上述步骤将其驱动完成,除非它通过在任何 OUTPUTCAPTURE 队列上发出 VIDIOC_STREAMOFF() 来中止该过程。在排空序列正在进行时,不允许客户端再次发出 V4L2_DEC_CMD_STARTV4L2_DEC_CMD_STOP,如果尝试,它们将因 -EBUSY 错误代码而失败。

尽管不是强制性的,但可以使用 VIDIOC_TRY_DECODER_CMD() 查询解码器命令的可用性。

4.5.1.11. 流结束

如果解码器在流中遇到流结束标记,则解码器将启动排空序列,客户端必须如上所述处理该序列,跳过初始的 VIDIOC_DECODER_CMD()

4.5.1.12. 提交点

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

  1. OUTPUT 队列上设置格式可能会更改 CAPTURE 队列上支持/公布的格式集。特别是,这也意味着 CAPTURE 格式可能会重置,客户端不得依赖之前设置的格式是否保留。

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

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

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

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

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