一、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,可能其他软件兼容了这个错误,增加兼容逻辑后解决。
标签:seek,读取,FFmpeg,6.1,pb,版本,delta,EOF,FFMpeg From: https://www.cnblogs.com/jiayayao/p/18238651