惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
J
Java Code Geeks
I
InfoQ
V
Visual Studio Blog
M
MIT News - Artificial intelligence
H
Help Net Security
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
人人都是产品经理
人人都是产品经理
G
Google Developers Blog
A
About on SuperTechFans
腾讯CDC
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
云风的 BLOG
云风的 BLOG
S
SegmentFault 最新的问题
WordPress大学
WordPress大学

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率
Seek策略以及在有B帧情况下的处理
音视频开发进阶 · 2022-03-18 · via 音视频开发进阶

一个专注音视频领域的小圈子

最近在做 Seek 相关功能时遇到的问题排查,顺便也学到了一些新的东西,和大家分享下。 在视频播放时执行 Seek 到任意点的操作,一般都是 Seek 到任意点往前最近的 I 帧,然后再逐帧解码到指定时间点。

这里可以优化,假设当前时间和指定时间在一个 GOP 内,就可以不用 seek ,直接顺序向下解码就好。

而正是这个优化出现了一点问题,现象如下:

已经判断播放点 A 和 Seek 点 B 不在一个 GOP 内,然后执行 av_seek_frame 方法还是把时间点 A 所在 GOP 全部解码了,导致播放上出现了卡顿。

这里就很奇怪了,明明判断不在一个 GOP ,那 Seek 时就应该从时间点 B 所在 GOP 的 I 帧开始解码, 但执行时还是解码了上一个 GOP 的内容。

到底是判断是否同一个 GOP 的函数出问题了还是 Seek 方法有问题呢?

带着疑问开始深入源码探索。

FFmpeg 没有直接提供判断两帧是否同一个 GOP 的方法,所以通过 av_index_search_timestamp 方法得到传入时间点最近的 I 帧的 index 索引,如果两个时间点的索引相同则表示为同一个 GOP 内,因为最近的 I 帧相同。

然而 av_index_search_timestamp 方法是通过 AVIndexEntry 中的 timestamp 来判断的,它是一个 DTS 值,通过二分查找得到最近的索引。

在没有 B 帧的情况下,I 帧的 PTS 等于 DTS ,所以判断不会出问题。然而正是有了 B 帧,如果 I 帧的 PTS 和 DTS 不相等的话,那么上面的判断相当于是拿一个 PTS 值和 I 帧的 DTS 比较是否同一个 GOP 了。

如果将错就错,判断 GOP 时得到结论是非同一个 GOP ,那么 Seek 也应该是非同一个 GOP ,但现实恰恰相反,Seek 当做了同一个 GOP ,这里面肯定有计算出问题了,继续深入源码。

通过在 mov.c 源码中看到了如下的操作:

static int mov_seek_stream(AVFormatContext *s, AVStream *st, int64_t timestamp, int flags)
{
    MOVStreamContext *sc = st->priv_data;
    FFStream *const sti = ffstream(st);
    int sample, time_sample, ret;
    unsigned int i;
    // Here we consider timestamp to be PTS, hence try to offset it so that we
    // can search over the DTS timeline.
    timestamp -= (sc->min_corrected_pts + sc->dts_shift);
    ret = mov_seek_fragment(s, st, timestamp);
    if (ret < 0)
        return ret;
    sample = av_index_search_timestamp(st, timestamp, flags);
    av_log(s, AV_LOG_TRACE, "stream %d, timestamp %"PRId64", sample %d\n", st->index, timestamp, sample);
    // 省略部分代码

注意到如下一行代码:

timestamp -= (sc->min_corrected_pts + sc->dts_shift);

也就是说我们传入的时间都会被减上一个值,然后再执行 av_index_search_timestamp 方法,而这个值导致判断 GOP 和 Seek 之间的关键帧索引出问题了。

正如代码中的注释所示,假设传入的时间是 PTS 值,然后给它减去偏移以得到 DTS 值,因为 av_index_search_timestamp 方法就通过 DTS 进行比较的嘛。

出现问题的原因就是 seek 的时间点正好在 I 帧的 PTS 和 DTS 范围之间了,执行 seek 时减去偏差值就小于 DTS 了,所以变成了同一个 GOP 。

现在要解决问题就是如何得到 sc->min_corrected_pts + sc->dts_shif 之和,然后判断 GOP 时减去它以修正得到 DTS 值。

还好通过遍历源码发现它的值是不会运行时改变的,一旦决定了就定下来了。另外我们可以用第一个 I 帧的 DTS 值作为偏移值。

  auto indexEntry = avStream->index_entries;
  auto nbIndexEntry = avStream->nb_index_entries;
  for (int i = 0; i < nbIndexEntry; ++i) {
    if (indexEntry[i].flags == AVINDEX_KEYFRAME) {
      DTSOffset = indexEntry[i].timestamp;
      return;
    }
  }

如果没有 B 帧,DTS 值为 0 ,有 B 帧,那么首帧的 DTS 值就可以用来做偏差值进行计算了。

原创文章,转载请注明来源:    Seek策略以及在有B帧情况下的处理