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

Consider the following test H.264 input, created using:

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这种不一致的行为吗?

(Using Python 3.8.12 on macOS Monterey 12.3.1, ffmpeg 4.4.1, pyAV 9.0.2)

推荐答案

这是一种预期的PyAV行为.不仅如此,这也是底层libav的预期行为.一个数据包不能保证一个帧,在生成一个帧之前可能需要多个数据包.这一点在FFmpeg's video decoder example中很明显:

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

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

[编辑]

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

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

[编辑]

pyav's 100

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

PyAV使用av_parser_parse2()解析输入数据,如下所示:


        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

因此,它会一直读取,直到输入数据被av_parser_parse2%消耗,并注意它不会在缓冲区末尾调用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()刷新最后一个数据包.

Python-3.x相关问答推荐

math. gcd背后的算法是什么,为什么它是更快的欧几里得算法?

我在创建Pandas DataFrame时感到困惑

替换Pandas中组下的列值

我不能使用拆分来分隔数据

我没有';无法理解此TemplateDoesNotExist错误

Select 作为 MultiIndex 一部分的两个 DatetimeIndex 之间的行

ValueError at /register/ 视图authenticate.views.register_user 未返回HttpResponse 对象.它返回 None 相反

在Pandas中,根据另一列中的重复值将数据分组为一列

在 groupby 之后,Pandas 在特定类别中获得最常见和最后的值

在 Python 中实现 COM 接口

如何将列表和字典逐行组合在一起

Python 3 - 给定未知数量的类别动态地将字典嵌套到列表中

逗号分隔列表的 argparse 操作或类型

Python3四舍五入到最接近的偶数

tensorflow 中 numpy.newaxis 的替代方案是什么?

通过多个键对字典列表进行分组和聚合

同步调用协程

首次使用后 zip 变量为空

在 Meta 中创建具有动态模型的通用序列化程序

在 PostgreSQL 上使用 SQLAlchemy 创建全文搜索索引