代码改变世界

FFMpeg笔记(十四)继续说说FFmpeg 升级6.1 遇到的那些坑

2024-06-08 16:02  jiayayao  阅读(28)  评论(0编辑  收藏  举报

一、mp4 秒播率下降

    灰度阶段发现秒播率略低0.x%,以为是灰度的数据抖动。上线后短视频业务方找过来,说秒播率明显下降。一起分析,发现业务方不止关心1秒秒播率,也关心首次播放vv 的200ms 秒播率,筛选出来发现数据大降。。然后我就开始分析。思路是将起播分为多个阶段,查数据看哪个阶段的耗时有明显增加,最终将问题局限在mp4 视频avformat_find_stream_info 阶段耗时大涨,普通hls 视频不存在问题。

    接下来重点分析avformat_find_stream_info 流程,重点是try_decode_frame 尝试解码获取解码器必要信息,和为了探测读取了多少数据两个步骤。try_decode_frame 对于mp4 格式一般是不需要执行的,本地验证也是如此。读取的字节数FFmpeg也有记录,对比了下新老版本,字节数一致。会不会是新老版本的代码优化上不一致,导致新版本的执行效率低于老版本?看了一眼数据,再执行效率低下也不至于增加几百ms。再苦思冥想了3天还是没解决,弹尽粮绝了,已经准备掏出手机,BOSS直聘,启动投递附件简历!LeetCode 也练习了一波接雨水,恢复了点自信。

    转机出现在线上拉取的一份日志,和网络团队的同事一起分析的时候,发现起播阶段底层在读取网络数据的时候,经常要读取几百KB,这个数字超过了业务方预下载数据的字节数,导致进行了网络请求(耗时点),同时我们设置的probesize 也就几十KB。网络的同事提示我,会不会是新版本起播时读取的字节数多于老版本,导致秒播率下降?这个问题我也不是没想过,但只是局限在avformat_find_stream_info 内部的readsize统计,底层avio 读取网络数据数据的字节数没有统计过,对比发现,新版本在起播阶段会多读取几百KB字节,问题大概率就是这了,然后断点向上反查,最终定位。

    FFmpeg 6.1 在读取mp4 视频时,会读取一部分字节数来获取流信息。新版本有两个commit为了解决mp4 视频有多个轨道时,一些特殊情况导致的必须获取更多数据才能拿到全部视频流信息:

void ff_configure_buffers_for_index(AVFormatContext *s, int64_t time_tolerance)
{
   ......
                    int64_t cur_delta;
                    if (e2_pts < e1_pts || e2_pts - (uint64_t)e1_pts < time_tolerance)
                        continue;
                    cur_delta = FFABS(e1->pos - e2->pos); // ABS 导致一个比较大的负值变成了正值
                    if (cur_delta < (1 << 23))
                        pos_delta = FFMAX(pos_delta, cur_delta);
                    break;
                }
            }
        }
    }

    pos_delta *= 2;
    ctx = ffiocontext(s->pb);
    /* XXX This could be adjusted depending on protocol*/
    if (s->pb->buffer_size < pos_delta) {
        av_log(s, AV_LOG_VERBOSE, "Reconfiguring buffers to size %"PRId64"\n", pos_delta);

        /* realloc the buffer and the original data will be retained */
        if (ffio_realloc_buf(s->pb, pos_delta)) { // 使用比较大的正值创建了大的buffer,在填充buffer 时,就需要读取更多的网络数据,如果没有,就需要进行网络请求
            av_log(s, AV_LOG_ERROR, "Realloc buffer fail.\n");
            return;
        }

        ctx->short_seek_threshold = FFMAX(ctx->short_seek_threshold, pos_delta/2);
    }

    ctx->short_seek_threshold = FFMAX(ctx->short_seek_threshold, skip);
}

  看commit message,是为了解决特定情况下的问题。但不得不说,我认为这是新版本的一个瑕疵。为了解决一个特定的问题,让普通的mp4 文件在起播的关键阶段多读取了几百KB 的网络数据,导致起播变慢,这是不应该的。不清楚最新版本解决了没有,看提交记录,是6.0 -> 6.1版本才新增的。这样看来,盲目升级最新版本也不是什么好事。ε(┬┬﹏┬┬)3

二、iOS 包大小异常

    在灰度阶段,发现iOS 包大小异常增加15M!!平时增加几百k 都要求爷爷告奶奶,现在这个数字,感觉我又要打开BOSS直聘了。检查每个文件的大小后发现,tx_template.c 文件定义了几个超大数组,这些超大数组被linkmap 识别为占用包大小。

    分析后发现,未初始化的超大数组,实际是以common段的方式存在在二进制中,最终是不占用包大小的,实际验证后也是如此。linkmap的计算方式没有考虑BSS 段中的common 类别,是linkmap 计算错误。

三、自研数据协议导致的EOF 读取不到

    在FFmpeg 4.x的版本,av_read_frame 增加了这样一个逻辑,当avio 返回EOF的时候,av_read_frame 会检查pb->error 是否为0,如果不为0的时候,返回具体的错误,而不返回EOF。在我们自研的数据协议里,当发生seek 打断的时候,返回AVERROR_EXIT ,导致了线上偶先seek 后读不到EOF,始终读到的是AVERROR_EXIT,无法结束视频。

static int read_frame_internal(AVFormatContext *s, AVPacket *pkt)
{
     ......
    /* A demuxer might have returned EOF because of an IO error, let's
     * propagate this back to the user. */
    if (ret == AVERROR_EOF && s->pb && s->pb->error < 0 && s->pb->error != AVERROR(EAGAIN))
        ret = s->pb->error;

    return ret;
}

  比较奇怪的是,在网络请求慢时,被seek 打断一般都返回AVERROR_EOF,可能其他软件兼容了这个错误,增加兼容逻辑后解决。