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. 如果适用,可以通过VIDIOC_QUERYCTRL()使用各自的控件查询当前OUTPUT格式的支持的配置文件和级别。

4.5.3.2. 初始化

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

    • 所需字段

      类型

      适用于OUTPUTV4L2_BUF_TYPE_*枚举。

      像素格式

      编码像素格式。

      width, height

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

      其他字段

      遵循标准语义。

    注意

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

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

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

    • 所需字段

      类型

      适用于CAPTUREV4L2_BUF_TYPE_*枚举。

    • 返回字段

      width, height

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

      像素格式

      解码帧的像素格式。

      num_planes (仅适用于 _MPLANE type)

      像素格式的平面数。

      sizeimage, bytesperline

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

    注意

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

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

    注意

    驱动程序将只返回当前选定的OUTPUT格式和当前设置的控件所支持的格式,即使解码器通常可能支持更多格式。

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

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

    • 所需字段

      类型

      适用于CAPTUREV4L2_BUF_TYPE_*枚举。

      像素格式

      原始像素格式。

      width, height

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

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

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

    • 所需字段

      计数

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

      类型

      适用于OUTPUTV4L2_BUF_TYPE_*枚举。

      内存

      遵循标准语义。

    • 返回字段

      计数

      实际分配的缓冲区数量。

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

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

    • 所需字段

      计数

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

      类型

      适用于CAPTUREV4L2_BUF_TYPE_*枚举。

      内存

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

    • 返回字段

      计数

      调整为已分配的缓冲区数量,以防编解码器需要比请求更多的缓冲区。

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

  8. 通过在媒体设备上调用MEDIA_IOC_REQUEST_ALLOC()分配请求(可能每个OUTPUT缓冲区一个)。

    通过在媒体设备上调用MEDIA_IOC_REQUEST_ALLOC()分配请求(可能每个OUTPUT缓冲区一个)。

  9. 通过VIDIOC_STREAMON()OUTPUTCAPTURE队列上启动流式传输

    VIDIOC_STREAMON().

4.5.3.3. 解码

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

  • 编解码器当前配置所需的编码数据量,作为提交到OUTPUT队列的缓冲区。通常,这对应于一帧的编码数据,但某些格式可能允许(或要求)每单位不同的量。

  • 解码提交的编码数据所需的所有元数据,以与正在解码的格式相关的控件形式。

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

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

一个典型的帧将使用以下序列进行解码

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

    • 所需字段

      索引

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

      类型

      缓冲区的类型。

      已用字节数

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

      标志

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

      请求_fd

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

      时间戳

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

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

    • 所需字段

      哪个

      必须是V4L2_CTRL_WHICH_REQUEST_VAL

      请求_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. 如果最后提交的请求由于使用V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF标志而导致CAPTURE缓冲区被持有,则最后一帧在CAPTURE队列中不可用。在这种情况下,应发送V4L2_DEC_CMD_FLUSH命令。这将使驱动程序出队被持有的CAPTURE缓冲区。

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

  3. OUTPUTCAPTURE队列上调用VIDIOC_STREAMOFF()

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

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

4.5.3.8. 排空

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

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