解析h264帧中的数据包时PyAV不一致



当生成H.264帧并使用pyAV对其进行解码时,只有在两次调用parse方法时,才会从帧中解析数据包。

考虑使用以下测试H.264输入创建:

ffmpeg -f lavfi -i testsrc=duration=10:size=1280x720:rate=30 -f image2 -vcodec libx264 -bsf h264_mp4toannexb -force_key_frames source -x264-params keyint=1:scenecut=0 "frame-%4d.h264"

现在,使用pyAV解析第一帧:

import av
codec = av.CodecContext.create('h264', 'r')
with open('/path/to/frame-0001.h264', 'rb') as file_handler:
chunk = file_handler.read()
packets = codec.parse(chunk) # This line needs to be invoked twice to parse packets

除非再次调用最后一行(packets = codec.parse(chunk)(,否则数据包保持为空

此外,对于我无法描述的不同现实生活示例,从数据包中解码帧似乎也需要几个解码调用:

packet = packets[0]
frames = codec.decode(packet) # This line needs to be invoked 2-3 times to actually receive frames.

有人知道pyAV这种不协调的行为吗?

(在macOS Monterey 12.3.1、ffmpeg 4.4.1、pyAV 9.0.2上使用Python 3.8.12(

这是预期的PyAV行为。这不仅是底层libav的预期行为。一个数据包不能保证一个帧,在产生一个帧之前可能需要多个数据包。这在FFmpeg的视频解码器示例中很明显

while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;

如果它需要更多的数据包来形成一个帧,它就会抛出EAGAIN错误。

[编辑]

实际上,上面的例子不是一个好例子,因为它只是在EAGAIN上存在。要检索帧,它应该是EAGAIN:上的continue

while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
if (AVERROR(EAGAIN))
continue;
if (ret == AVERROR_EOF)
return;

[编辑]

pyav的codec.parse()

解码有时需要额外的调用是一个众所周知的事实,但需要刷新的解析器并不常见。以下是PyAV和FFmpeg:之间的区别

PyAV用av_parser_parse2()解析输入数据,如下[ref]:


while True:
with nogil:
consumed = lib.av_parser_parse2(
self.parser,
self.ptr,
&out_data, &out_size,
in_data, in_size,
lib.AV_NOPTS_VALUE, lib.AV_NOPTS_VALUE,
0
)
err_check(consumed)
# ...snip...
if not in_size:
# This was a flush. Only one packet should ever be returned.
break
in_data += consumed
in_size -= consumed
if not in_size:
# Aaaand now we're done.
break

因此,它读取直到输入数据被100%消耗,并注意到它不会在缓冲区末尾调用av_parser_parse2(这是有意义的,因为输入数据可能只是流数据的一部分

相比之下,FFmpeg不直接调用av_parser_parse2,而是使用parse_packet,您可以看到它是如何处理类似情况的:

while (size > 0 || (flush && got_output)) {
int64_t next_pts = pkt->pts;
int64_t next_dts = pkt->dts;
int len;
len = av_parser_parse2(sti->parser, sti->avctx,
&out_pkt->data, &out_pkt->size, data, size,
pkt->pts, pkt->dts, pkt->pos);

它还调用av_parser_parse2,以在输入数据流用完后刷新该流。因此,您需要在PyAV中执行同样的操作:在读取所有帧之后,最后一次调用codec.parse()来刷新最后一个数据包。

相关内容

  • 没有找到相关文章

最新更新