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. 查询能力¶
要枚举解码器支持的编码格式集,客户端在
OUTPUT
队列上调用VIDIOC_ENUM_FMT()
。驱动程序必须始终返回完整的受支持
OUTPUT
格式集,无论CAPTURE
队列上当前设置的格式如何。同时,驱动程序必须将编解码器特定能力控制(例如 H.264 配置文件)返回的值集限制为硬件实际支持的集。
要枚举支持的原始格式集,客户端在
CAPTURE
队列上调用VIDIOC_ENUM_FMT()
。驱动程序必须只返回
OUTPUT
队列上当前活动格式所支持的格式。根据当前设置的
OUTPUT
格式,支持的原始格式集可能取决于某些编解码器相关控件的值。客户端负责确保在查询CAPTURE
队列之前设置这些控件。否则将使用这些控件的默认值,并且返回的格式集可能无法用于客户端尝试解码的媒体。
客户端可以使用
VIDIOC_ENUM_FRAMESIZES()
检测给定格式支持的分辨率,在v4l2_frmsizeenum
的pixel_format
中传入所需的像素格式。如果适用,可以通过
VIDIOC_QUERYCTRL()
使用各自的控件查询当前OUTPUT
格式的支持的配置文件和级别。
4.5.3.2. 初始化¶
通过
VIDIOC_S_FMT()
在OUTPUT
队列上设置编码格式。所需字段
类型
适用于
OUTPUT
的V4L2_BUF_TYPE_*
枚举。像素格式
编码像素格式。
width
,height
从流中解析出的编码宽度和高度。
- 其他字段
遵循标准语义。
注意
更改
OUTPUT
格式可能会更改当前设置的CAPTURE
格式。驱动程序将从正在设置的OUTPUT
格式派生一个新的CAPTURE
格式,包括分辨率、色度参数等。如果客户端需要特定的CAPTURE
格式,它必须在之后进行调整。调用
VIDIOC_S_EXT_CTRLS()
设置OUTPUT
格式所需的所有控件(解析的头文件等),以枚举CAPTURE
格式。为
CAPTURE
队列调用VIDIOC_G_FMT()
以获取从字节流解析/解码的目标缓冲区的格式。所需字段
类型
适用于
CAPTURE
的V4L2_BUF_TYPE_*
枚举。
返回字段
width
,height
解码帧的帧缓冲区分辨率。
像素格式
解码帧的像素格式。
num_planes
(仅适用于 _MPLANEtype
)像素格式的平面数。
sizeimage
,bytesperline
按照标准语义;匹配帧缓冲区格式。
注意
pixelformat
的值可以是OUTPUT
格式支持的任何像素格式,基于硬件能力。建议驱动程序为当前配置选择首选/最优格式。例如,如果 RGB 需要额外的转换步骤,则 YUV 格式可能优于 RGB 格式。[可选] 通过
VIDIOC_ENUM_FMT()
在CAPTURE
队列上枚举CAPTURE
格式。客户端可以使用此 ioctl 发现当前OUTPUT
格式支持哪些替代原始格式,并通过VIDIOC_S_FMT()
选择其中之一。注意
驱动程序将只返回当前选定的
OUTPUT
格式和当前设置的控件所支持的格式,即使解码器通常可能支持更多格式。例如,解码器可能支持分辨率为 1920x1088 及更低分辨率的 YUV 和 RGB 格式,但对于更高分辨率仅支持 YUV(由于硬件限制)。将 1920x1088 或更低分辨率设置为
OUTPUT
格式后,VIDIOC_ENUM_FMT()
可能会返回一组 YUV 和 RGB 像素格式,但将分辨率设置为高于 1920x1088 后,驱动程序将不再返回 RGB 像素格式,因为此分辨率不支持它们。[可选] 在
CAPTURE
队列上通过VIDIOC_S_FMT()
选择与建议的CAPTURE
格式不同的格式。客户端可以选择与驱动程序在VIDIOC_G_FMT()
中选择/建议的格式不同的格式。所需字段
类型
适用于
CAPTURE
的V4L2_BUF_TYPE_*
枚举。像素格式
原始像素格式。
width
,height
解码流的帧缓冲区分辨率;通常与
VIDIOC_G_FMT()
返回的相同,但如果硬件支持合成和/或缩放,则可能不同。
执行此步骤后,客户端必须再次执行步骤 3,以获取有关缓冲区大小和布局的最新信息。
通过
VIDIOC_REQBUFS()
在OUTPUT
队列上分配源(字节流)缓冲区。所需字段
计数
请求分配的缓冲区数量;大于零。
类型
适用于
OUTPUT
的V4L2_BUF_TYPE_*
枚举。内存
遵循标准语义。
返回字段
计数
实际分配的缓冲区数量。
如果需要,驱动程序将调整
count
,使其等于或大于给定格式所需OUTPUT
缓冲区的最小数量和请求的计数。客户端在 ioctl 返回后必须检查此值以获取实际分配的缓冲区数量。
通过
VIDIOC_REQBUFS()
在CAPTURE
队列上分配目标(原始格式)缓冲区。所需字段
计数
请求分配的缓冲区数量;大于零。客户端负责推断流正确解码所需的最小缓冲区数量(例如,考虑参考帧)并传入等于或更大的数量。
类型
适用于
CAPTURE
的V4L2_BUF_TYPE_*
枚举。内存
遵循标准语义。
V4L2_MEMORY_USERPTR
不支持CAPTURE
缓冲区。
返回字段
计数
调整为已分配的缓冲区数量,以防编解码器需要比请求更多的缓冲区。
驱动程序必须调整 count 以适应当前格式、流配置和请求计数所需的
CAPTURE
缓冲区最小数量。客户端在 ioctl 返回后必须检查此值以获取已分配的缓冲区数量。
- 通过在媒体设备上调用
MEDIA_IOC_REQUEST_ALLOC()
分配请求(可能每个OUTPUT
缓冲区一个)。 通过在媒体设备上调用
MEDIA_IOC_REQUEST_ALLOC()
分配请求(可能每个OUTPUT
缓冲区一个)。
- 通过在媒体设备上调用
- 通过
VIDIOC_STREAMON()
在OUTPUT
和CAPTURE
队列上启动流式传输 VIDIOC_STREAMON()
.
- 通过
4.5.3.3. 解码¶
对于每个帧,客户端负责提交至少一个请求,其中附加以下内容:
编解码器当前配置所需的编码数据量,作为提交到
OUTPUT
队列的缓冲区。通常,这对应于一帧的编码数据,但某些格式可能允许(或要求)每单位不同的量。解码提交的编码数据所需的所有元数据,以与正在解码的格式相关的控件形式。
源OUTPUT
缓冲区的数据量和内容,以及必须在请求上设置的控件,取决于活动编码像素格式,并可能受编解码器特定扩展控件的影响,如每种格式的文档所述。
如果解码后的帧可能需要在当前请求之后一个或多个解码请求才能生成,则客户端必须在OUTPUT
缓冲区上设置V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF
标志。这将导致(可能部分)解码的CAPTURE
缓冲区不被提供用于出队,如果下一个OUTPUT
缓冲区的时间戳没有改变,则将其重复用于下一个解码请求。
一个典型的帧将使用以下序列进行解码
使用
VIDIOC_QBUF()
将一个包含一单位编码字节流数据的OUTPUT
缓冲区排队,用于解码请求。所需字段
索引
正在排队的缓冲区的索引。
类型
缓冲区的类型。
已用字节数
缓冲区中编码数据帧占用的字节数。
标志
必须设置
V4L2_BUF_FLAG_REQUEST_FD
标志。此外,如果我们不确定当前解码请求是生成完全解码帧所需的最后一个请求,那么V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF
也必须设置。请求_fd
必须设置为解码请求的文件描述符。
时间戳
每帧必须设置为唯一值。此值将传播到解码帧的缓冲区中,也可用于将此帧用作另一个帧的参考。如果每帧使用多个解码请求,则给定帧的所有
OUTPUT
缓冲区的时间戳必须相同。如果时间戳改变,则当前持有的CAPTURE
缓冲区将可用于出队,当前请求将处理一个新的CAPTURE
缓冲区。
使用
VIDIOC_S_EXT_CTRLS()
设置解码请求的编解码器特定控件。所需字段
哪个
必须是
V4L2_CTRL_WHICH_REQUEST_VAL
。请求_fd
必须设置为解码请求的文件描述符。
- 其他字段
设置控件时,其他字段照常设置。
controls
数组必须包含解码帧所需的所有编解码器特定控件。
注意
可以在不同次调用
VIDIOC_S_EXT_CTRLS()
中指定控件,或者覆盖先前设置的控件,只要request_fd
和which
设置正确。提交请求时的控件状态将被考虑。注意
步骤 1 和 2 的发生顺序可以互换。
通过在请求 FD 上调用
MEDIA_REQUEST_IOC_QUEUE()
提交请求。如果请求在没有
OUTPUT
缓冲区的情况下提交,或者请求中缺少某些必需的控件,则MEDIA_REQUEST_IOC_QUEUE()
将返回-ENOENT
。如果排队了多个OUTPUT
缓冲区,则将返回-EINVAL
。MEDIA_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
队列的缓冲区按排队顺序处理,那么用户空间可以利用此保证并在满足以下条件时将参考缓冲区排队:
所有受参考帧影响的帧的请求已排队,并且
已排队足够数量的
CAPTURE
缓冲区以覆盖所有解码的引用帧。
当排队解码请求时,驱动程序将增加与参考帧关联的所有资源的引用计数。这意味着客户端可以(例如)在之后不需要时关闭参考帧缓冲区的 DMABUF 文件描述符。
4.5.3.5. 搜索¶
为了进行搜索,客户端只需使用与新流位置对应的输入缓冲区提交请求。但是,它必须意识到分辨率可能已更改,并在这种情况下遵循动态分辨率更改序列。此外,根据所使用的编解码器,图片参数(例如 H.264 的 SPS/PPS)可能已更改,客户端负责确保将有效状态发送到解码器。
然后,客户端可以自由忽略来自搜索前位置的任何返回的CAPTURE
缓冲区。
4.5.3.6. 暂停¶
为了暂停,客户端只需停止将缓冲区排队到OUTPUT
队列。没有源字节流数据,就没有要处理的数据,编解码器将保持空闲。
4.5.3.7. 动态分辨率更改¶
如果客户端检测到流中的分辨率变化,它将需要使用新分辨率再次执行初始化序列
如果最后提交的请求由于使用
V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF
标志而导致CAPTURE
缓冲区被持有,则最后一帧在CAPTURE
队列中不可用。在这种情况下,应发送V4L2_DEC_CMD_FLUSH
命令。这将使驱动程序出队被持有的CAPTURE
缓冲区。等待所有提交的请求完成并出队相应的输出缓冲区。
在
OUTPUT
和CAPTURE
队列上调用VIDIOC_STREAMOFF()
。通过在
CAPTURE
队列上调用VIDIOC_REQBUFS()
并将缓冲区计数设置为零来释放所有CAPTURE
缓冲区。在
OUTPUT
队列上设置新分辨率后,再次执行初始化序列(减去OUTPUT
缓冲区的分配)。请注意,由于分辨率限制,可能需要在CAPTURE
队列上选择不同的格式。
4.5.3.8. 排空¶
如果最后提交的请求由于使用V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF
标志而导致CAPTURE
缓冲区被持有,则最后一帧在CAPTURE
队列中不可用。在这种情况下,应发送V4L2_DEC_CMD_FLUSH
命令。这将使驱动程序出队被持有的CAPTURE
缓冲区。
之后,为了排空无状态解码器上的流,客户端只需等待所有提交的请求完成。