4.5.3. 内存到内存无状态视频解码器接口

无状态解码器是一种在处理帧之间不保留任何状态的解码器。这意味着每一帧的解码都独立于任何先前和未来的帧,并且客户端负责维护解码状态,并在每次解码请求时将其提供给解码器。这与有状态视频解码器接口形成对比,在有状态解码器接口中,硬件和驱动程序维护解码状态,客户端所要做的就是提供原始编码流并按显示顺序将解码后的帧出队。

本节介绍用户空间(“客户端”)如何与无状态解码器通信,以便成功解码编码流。与有状态编解码器相比,解码器/客户端序列更简单,但这种简单性的代价是客户端的额外复杂性,客户端负责维护一致的解码状态。

无状态解码器使用 请求 API。当调用 VIDIOC_REQBUFS()VIDIOC_CREATE_BUFS() 时,无状态解码器必须在其 OUTPUT 队列上公开 V4L2_BUF_CAP_SUPPORTS_REQUESTS 功能。

根据解码器支持的编码格式,单个解码帧可能是多个解码请求的结果(例如,每帧具有多个切片的 H.264 流)。支持此类格式的解码器还必须在其 OUTPUT 队列上公开 V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF 功能。

4.5.3.1. 查询功能

  1. 要枚举解码器支持的编码格式集,客户端在 OUTPUT 队列上调用 VIDIOC_ENUM_FMT()

    • 驱动程序必须始终返回支持的完整 OUTPUT 格式集,而不管当前在 CAPTURE 队列上设置的格式如何。

    • 同时,驱动程序必须将编解码器特定功能控件(例如 H.264 配置文件)返回的值集限制为硬件实际支持的集。

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

    • 驱动程序必须仅返回当前在 OUTPUT 队列上活动的格式支持的格式。

    • 根据当前设置的 OUTPUT 格式,支持的原始格式集可能取决于某些编解码器相关控件的值。客户端负责确保在查询 CAPTURE 队列之前设置这些控件。如果未能这样做,将导致使用这些控件的默认值,并且返回的格式集可能无法用于客户端尝试解码的媒体。

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

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

4.5.3.2. 初始化

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

    • 必填字段

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

      pixelformat

      编码的像素格式。

      widthheight

      从流中解析的编码宽度和高度。

      其他字段

      遵循标准语义。

    注意

    更改 OUTPUT 格式可能会更改当前设置的 CAPTURE 格式。驱动程序将从正在设置的 OUTPUT 格式中派生新的 CAPTURE 格式,包括分辨率、色度参数等。如果客户端需要特定的 CAPTURE 格式,则必须在之后对其进行调整。

  2. 调用 VIDIOC_S_EXT_CTRLS() 以设置 OUTPUT 格式所需的所有控件(已解析的标头等),以枚举 CAPTURE 格式。

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

    • 必填字段

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

    • 返回字段

      widthheight

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

      pixelformat

      解码帧的像素格式。

      num_planes(仅适用于 _MPLANE type

      像素格式的平面数。

      sizeimagebytesperline

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

    注意

    pixelformat 的值可以是基于硬件功能的 OUTPUT 格式支持的任何像素格式。建议驱动程序为当前配置选择首选/最佳格式。例如,如果 RGB 需要额外的转换步骤,则 YUV 格式可能优先于 RGB 格式。

  4. [可选] 通过在 CAPTURE 队列上使用 VIDIOC_ENUM_FMT() 枚举 CAPTURE 格式。客户端可以使用此 ioctl 来发现当前 OUTPUT 格式支持哪些替代原始格式,并通过 VIDIOC_S_FMT() 选择其中一种。

    注意

    驱动程序将仅返回当前选择的 OUTPUT 格式和当前设置的控件支持的格式,即使解码器通常可能支持更多格式也是如此。

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

  5. [可选] 选择与 VIDIOC_S_FMT()CAPTURE 队列上建议的不同的 CAPTURE 格式。客户端可以选择与驱动程序在 VIDIOC_G_FMT() 中选择/建议的不同的格式。

    • 必填字段

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

      pixelformat

      原始像素格式。

      widthheight

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

    执行此步骤后,客户端必须再次执行步骤 3,以便获得有关缓冲区大小和布局的最新信息。

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

    • 必填字段

      count

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

      type

      适用于 OUTPUTV4L2_BUF_TYPE_* 枚举。

      memory

      遵循标准语义。

    • 返回字段

      count

      实际分配的缓冲区数。

    • 如果需要,驱动程序会将 count 调整为等于或大于给定格式所需的最小 OUTPUT 缓冲区数和请求的计数。客户端必须在 ioctl 返回后检查此值,以获取实际分配的缓冲区数。

  7. 通过 VIDIOC_REQBUFS()CAPTURE 队列上分配目标(原始格式)缓冲区。

    • 必填字段

      count

      请求分配的缓冲区数;大于零。客户端负责推断正确解码流所需的最小缓冲区数(例如,考虑参考帧),并传递相等或更大的数量。

      type

      适用于 CAPTUREV4L2_BUF_TYPE_* 枚举。

      memory

      遵循标准语义。 V4L2_MEMORY_USERPTR 不支持 CAPTURE 缓冲区。

    • 返回字段

      count

      如果编解码器需要比请求更多的缓冲区,则调整为分配的缓冲区数。

    • 驱动程序必须将 count 调整为当前格式、流配置和请求的计数所需的最小 CAPTURE 缓冲区数。客户端必须在 ioctl 返回后检查此值,以获取分配的缓冲区数。

  8. 通过以下方式分配请求(可能每个 OUTPUT 缓冲区一个):

    在媒体设备上使用 MEDIA_IOC_REQUEST_ALLOC()

  9. 通过以下方式在 OUTPUTCAPTURE 队列上启动流:

    VIDIOC_STREAMON().

4.5.3.3. 解码

对于每个帧,客户端负责提交至少一个请求,该请求附带以下内容:

  • 编解码器根据其当前配置期望的编码数据量,以缓冲区形式提交到 OUTPUT 队列。通常,这对应于一个帧的编码数据,但某些格式可能允许(或要求)每个单元不同的数据量。

  • 解码提交的编码数据所需的所有元数据,以与要解码的格式相关的控制信息的形式提供。

OUTPUT 缓冲区的数据量和内容,以及必须在请求上设置的控制信息,取决于活动的编码像素格式,并且可能会受到编解码器特定的扩展控制的影响,如每种格式的文档中所述。

如果解码后的帧可能需要在当前请求之后执行一个或多个解码请求才能生成,则客户端必须在 OUTPUT 缓冲区上设置 V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF 标志。这将导致(可能部分)解码的 CAPTURE 缓冲区不可用于出队,并且如果下一个 OUTPUT 缓冲区的的时间戳未更改,则会重复用于下一个解码请求。

因此,典型的帧将使用以下顺序解码:

  1. 使用 VIDIOC_QBUF() 将包含一个单元的编码字节流数据的 OUTPUT 缓冲区排队,用于解码请求。

    • 必填字段

      index

      正在排队的缓冲区的索引。

      type

      缓冲区的类型。

      bytesused

      缓冲区中编码数据帧占用的字节数。

      flags

      必须设置 V4L2_BUF_FLAG_REQUEST_FD 标志。此外,如果我们不确定当前解码请求是生成完全解码帧所需的最后一个请求,则还必须设置 V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF

      request_fd

      必须设置为解码请求的文件描述符。

      timestamp

      必须为每个帧设置为唯一值。该值将传播到解码帧的缓冲区中,并且也可以用于将此帧用作另一个帧的参考。如果每个帧使用多个解码请求,则给定帧的所有 OUTPUT 缓冲区的时间戳必须相同。如果时间戳发生变化,则当前持有的 CAPTURE 缓冲区将可用于出队,并且当前请求将在新的 CAPTURE 缓冲区上工作。

  2. 使用 VIDIOC_S_EXT_CTRLS() 设置解码请求的编解码器特定控制。

    • 必填字段

      which

      必须是 V4L2_CTRL_WHICH_REQUEST_VAL

      request_fd

      必须设置为解码请求的文件描述符。

      其他字段

      其他字段按照设置控件时的通常方式设置。 controls 数组必须包含解码帧所需的所有编解码器特定控制。

    注意

    可以在不同的 VIDIOC_S_EXT_CTRLS() 调用中指定控制,或者覆盖先前设置的控制,只要正确设置了 request_fdwhich 即可。请求提交时的控制状态将被考虑。

    注意

    步骤 1 和 2 的执行顺序可以互换。

  3. 通过在请求 FD 上调用 MEDIA_REQUEST_IOC_QUEUE() 来提交请求。

    如果提交的请求没有 OUTPUT 缓冲区,或者请求中缺少某些必需的控制,则 MEDIA_REQUEST_IOC_QUEUE() 将返回 -ENOENT。如果排队了多个 OUTPUT 缓冲区,则它将返回 -EINVALMEDIA_REQUEST_IOC_QUEUE() 返回非零值表示此请求不会生成 CAPTURE 缓冲区。

CAPTURE 缓冲区不能是请求的一部分,它们是独立排队的。它们以解码顺序返回(即与编码帧提交到 OUTPUT 队列的顺序相同)。

运行时解码错误通过携带 V4L2_BUF_FLAG_ERROR 标志的出队 CAPTURE 缓冲区发出信号。如果解码的参考帧有错误,则所有引用它的后续解码帧也设置了 V4L2_BUF_FLAG_ERROR 标志,尽管解码器仍会尝试生成(可能已损坏)的帧。

4.5.3.4. 解码时的缓冲区管理

与有状态解码器相反,无状态解码器不执行任何类型的缓冲区管理:它仅保证客户端可以使用出队的 CAPTURE 缓冲区,只要它们不再次排队即可。“使用”这里包括使用缓冲区进行合成或显示。

出队的捕获缓冲区也可以用作另一个缓冲区的参考帧。

通过将其时间戳转换为纳秒,并将其存储到编解码器相关的控制结构的相应成员中,可以将帧指定为参考帧。必须使用 v4l2_timeval_to_ns() 函数执行该转换。只要其所有编码数据单元都已成功提交到 OUTPUT 队列,就可以使用帧的时间戳来引用它。

包含参考帧的解码缓冲区在所有引用它的帧都被解码之前,不得重复用作解码目标。实现此目的最安全的方法是,在所有引用它的解码帧都已出队之前,避免将参考缓冲区排队。但是,如果驱动程序可以保证排队到 CAPTURE 队列的缓冲区按排队顺序处理,则用户空间可以利用此保证,并在满足以下条件时将参考缓冲区排队:

  1. 受参考帧影响的所有帧的请求都已排队,并且

  2. 已排队足够数量的 CAPTURE 缓冲区以覆盖所有解码的参考帧。

在排队解码请求时,驱动程序将增加与参考帧关联的所有资源的引用计数。这意味着客户端可以例如关闭参考帧缓冲区的 DMABUF 文件描述符,如果它之后不需要它们。

4.5.3.5. 查找

为了查找,客户端只需要使用与新流位置对应的输入缓冲区提交请求即可。但是,它必须意识到分辨率可能已更改,并且在这种情况下遵循动态分辨率更改序列。此外,根据所使用的编解码器,图片参数(例如,H.264 的 SPS/PPS)可能已更改,并且客户端负责确保将有效状态发送到解码器。

然后,客户端可以自由忽略来自查找前位置的任何返回的 CAPTURE 缓冲区。

4.5.3.6. 暂停

为了暂停,客户端可以停止将缓冲区排队到 OUTPUT 队列。如果没有源字节流数据,则没有要处理的数据,并且编解码器将保持空闲状态。

4.5.3.7. 动态分辨率更改

如果客户端检测到流中的分辨率更改,则需要使用新分辨率再次执行初始化序列:

  1. 如果上次提交的请求导致 CAPTURE 缓冲区因使用 V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF 标志而被保留,则最后一个帧在 CAPTURE 队列上不可用。在这种情况下,应发送 V4L2_DEC_CMD_FLUSH 命令。这将使驱动程序将保留的 CAPTURE 缓冲区出队。

  2. 等待直到所有提交的请求都完成并出队相应的输出缓冲区。

  3. OUTPUTCAPTURE 队列上调用 VIDIOC_STREAMOFF()

  4. 通过在 CAPTURE 队列上使用零缓冲区计数调用 VIDIOC_REQBUFS() 来释放所有 CAPTURE 缓冲区。

  5. 使用在 OUTPUT 队列上设置的新分辨率,再次执行初始化序列(减去 OUTPUT 缓冲区的分配)。请注意,由于分辨率约束,可能需要在 CAPTURE 队列上选择不同的格式。

4.5.3.8. 排空

如果上次提交的请求导致 CAPTURE 缓冲区因使用 V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF 标志而被保留,则最后一个帧在 CAPTURE 队列上不可用。在这种情况下,应发送 V4L2_DEC_CMD_FLUSH 命令。这将使驱动程序将保留的 CAPTURE 缓冲区出队。

之后,为了在无状态解码器上排空流,客户端只需要等待所有提交的请求完成即可。