带有 AVSEEK_FLAG_ANY 的 ffmpeg av_seek_frame 导致灰屏

Posted

技术标签:

【中文标题】带有 AVSEEK_FLAG_ANY 的 ffmpeg av_seek_frame 导致灰屏【英文标题】:ffmpeg av_seek_frame with AVSEEK_FLAG_ANY causes grey screen 【发布时间】:2014-01-11 03:45:59 【问题描述】:

问题: omxplayer 的源代码使用AVSEEK_FLAG_BACKWARD 标志调用ffmpeg av_seek_frame() 方法。虽然不是 100% 肯定,但我相信这会寻找最接近的 i-frame。相反,我想寻找确切的位置,所以我修改了源代码,使av_seek_frame() 方法现在使用AVSEEK_FLAG_ANY 标志。现在,当电影加载时,我得到一个灰屏,通常为 1 秒钟,在此期间我可以听到音频。我已经在多台计算机上尝试过这个(我实际上正在同步它们,因此也是同时)所以这不是一个孤立的事件。我的猜测是寻找非 i 帧的计算成本更高,导致初始灰屏。

问题:如何使用 ffmpeg 指示音频等到视频准备好后再继续。

【问题讨论】:

【参考方案1】:

实际上,AVSEEK_FLAG_BACKWARD 表示您希望找到最近的关键帧,其时间戳小于您正在寻找的那个

通过使用AVSEEK_FLAG_ANY,您可以获得与您要求的时间戳完全对应的帧。但是这一帧可能不是关键帧,这意味着它不能被完全解码。这就解释了你的“灰屏”,它会一直出现,直到到达下一个关键帧。

因此,解决方案是使用AVSEEK_FLAG_BACKWARD向后搜索,并从此关键帧读取下一帧(例如使用av_read_frame()),直到找到与您的时间戳对应的帧。此时,您的帧将被完全解码,不再显示为“灰屏”。

注意:看来,由于某种原因,av_seek_frame() 使用 AVSEEK_FLAG_BACKWARD 返回 next 关键帧,而我正在寻找的帧是前一个关键帧这个关键帧。否则它会返回前一个关键帧(这是我想要的)。我的解决方案是更改我给av_seek_frame() 的时间戳,以确保它将在我正在寻找的帧之前返回关键帧

【讨论】:

我认为你的最后一段有错误 "可能不是关键帧,也就是说无法完全解码" 什么意思,只有关键帧可以完全解码? 我不是专家,但我是这样看的:视频是使用“关键帧”编码的,它们是独立的图像,以及“帧" 依赖于关键帧。换句话说,当你读取一个关键帧时,你可以解码相应的图像。但是一帧并不包含解码它所需的所有信息,因此您需要解码之前的帧(而不是关键帧)。因此,如果您“跳转”到一个帧而之前没有读取关键帧,那么您就会错过有关该帧的信息。是不是更清楚了? 我认为你在这里错了。一些帧需要解码其他帧。 “keyframe”实际上是一个I-frame(参见here)。 寻找第一个出现在你想要的之前的关键帧(或者另一个离它不太远的关键帧,只要它出现在之前一个你想要的)。然后,一次读一帧,直到达到目标(这实际上是我答案的第三段=))。【参考方案2】:

用一些代码完成 JonesV 的回答:

void seekFrame(unsigned frameIndex)

    // Seek is done on packet dts
    int64_t target_dts_usecs = (int64_t)round(frameIndex
            * (double)m_video_stream->r_frame_rate.den
            / m_video_stream->r_frame_rate.num * AV_TIME_BASE);
    // Remove first dts: when non zero seek should be more accurate
    auto first_dts_usecs = (int64_t)round(m_video_stream->first_dts
        * (double)m_video_stream->time_base.num
        / m_video_stream->time_base.den * AV_TIME_BASE);
    target_dts_usecs += first_dts_usecs;
    int rv = av_seek_frame(
        m_format_ctx, -1, target_dts_usecs, AVSEEK_FLAG_BACKWARD);
    if (rv < 0)
        throw exception("Failed to seek");

    avcodec_flush_buffers(m_codec_ctx);

然后您可以开始解码检查 AVPacket.dts 与原始目标 dts,在 AVStream.time_base 上计算。到达目标 dts 后,下一个解码帧应该是所需的帧。

【讨论】:

auto first_dts_usecs = (int64_t)round(m_video_stream-&gt;first_dts * (double)m_video_stream-&gt;time_base.num / m_video_stream-&gt;time_base.den * AV_TIME_BASE) 的行是什么? @csguy 据我记得,第一个 dts 可能不为零,在这种情况下,它应该被考虑并作为偏移量。我不知道av_seek_frame 时间戳是否仍应被视为微秒,但可以肯定的是,很久以前它就在我的测试中。针对新版本的 ffmpeg 仔细验证此代码的正确性。

以上是关于带有 AVSEEK_FLAG_ANY 的 ffmpeg av_seek_frame 导致灰屏的主要内容,如果未能解决你的问题,请参考以下文章

AndroidStudio 中使用FFMPEG

centos7 ffmpeg安装

流媒体开发4环境搭建

Android ijkplayer详解使用教程

iOS 上视频的动画叠加

php+ffmpeg判断视频编码格式是否h264?