因看到卢_俊 ——直播疑难杂症排查(3)— 首开慢的博文。故描述一下我在工作中遇到这方面问题的解决方式。
首先声明一下,因为是基于ijkplayer而封装的播放器,所以我的方案是在此基础上实现的。
在这篇直播疑难杂症排查(3)— 首开慢博文中总结了很全面,我读了收益很大。
2.1 点击播放后才从服务器取播放地址
2.2 DNS 解析慢
2.3 播放策略原因
2.4 播放参数配置
2.5 服务端线路原因
想必大家都对上述问题都做过相关研究且在实际的项目中优化过,所以我这里就描述一下不一样的地方。
主要探讨的是
2.4 播放参数配置
其实一开始的方案和文章所描述的一样,设置这2个参数来减少 avformat_find_stream_info
函数的调用时间。但由于需要解决下述问题:
1、很容易出现参数设置过大无法有效的减少时间调用,设置过小出现无法解析全部码流信息
因为需要提供通用的播放器SDK以支持多种流格式多种码率以及点播直播。所以决定试一试其它方案解决这个问题。
替换掉 avformat_find_stream_info
函数
这个想法是看到一篇博文VLC优化(1) avformat_find_stream_info接口延迟降低 中描述所启发。但实际操作中发现,此方案局限性太大。
avformat_find_stream_info
处理
比如都熟悉的RTMP协议,正常情况下RTMP流的视频元数据就在最前面.理论上打开速度应该非常快才对。不应该那么慢。参考对比rtmp web 播放。
想必avformat_find_stream_info
函数内部实行有些冗余的操作,导致耗时较长。
函数位于 libavformat/utils.c 文件里。日志分析,函数的大部分时间耗费在以下代码域中(相关文件代码)
以下是简易代码块。
for (;;)
{
....
}
其实里面就是死循环读取码流信息,正常退出条件:
直到读取到相关需要的信息
read_size >= probesize
读取的音频视频流的时长 >= max_analyze_duration
为何已经读取到了相关解码信息还不退出呢?查看一下循环内有一段是检查是否读取到了必须的信息
for (i = 0; i < ic->nb_streams; i++) {
int fps_analyze_framecount = 20;
st = ic->streams[i];
if (!has_codec_parameters(st, NULL))
break;
/* If the timebase is coarse (like the usual millisecond precision
* of mkv), we need to analyze more frames to reliably arrive at
* the correct fps. */
if (av_q2d(st->time_base) > 0.0005)
fps_analyze_framecount *= 2;
if (!tb_unreliable(st->internal->avctx))
fps_analyze_framecount = 0;
if (ic->fps_probe_size >= 0)
fps_analyze_framecount = ic->fps_probe_size;
if (st->disposition & AV_DISPOSITION_ATTACHED_PIC)
fps_analyze_framecount = 0;
/* variable fps and no guess at the real fps */
if (!(st->r_frame_rate.num && st->avg_frame_rate.num) &&
st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
int count = (ic->iformat->flags & AVFMT_NOTIMESTAMPS) ?
st->info->codec_info_duration_fields/2 :
st->info->duration_count;
if (count < fps_analyze_framecount)
break;
}
if (st->parser && st->parser->parser->split &&
!st->internal->avctx->extradata)
break;
if (st->first_dts == AV_NOPTS_VALUE &&
!(ic->iformat->flags & AVFMT_NOTIMESTAMPS) &&
st->codec_info_nb_frames < ((st->disposition & AV_DISPOSITION_ATTACHED_PIC) ? 1 : ic->max_ts_probe) &&
(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO))
break;
}
我们看到为何已经有了必须要的解码信息if (!has_codec_parameters(st, NULL))
还需要读取那些信息才能退出呢?其实主要还是为了 fps 的信息获取操作。其实这个信息对视频打开播放没有影响。所以我在死循环内添加了以下逻辑代码:
for (int j = 0; j < ic->nb_streams; j++){
st = ic->streams[j];
if (has_codec_parameters(st, NULL)){
if (st->parser && st->parser->parser->split && !st->codecpar->extradata){
av_log(ic, AV_LOG_DEBUG, "%d has_codec_parameters but no extradata \n",j);
}else {
if(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
prob_video_ok = 1;
av_log(ic, AV_LOG_DEBUG, "%d video has_codec_parameters \n",j);
}else if(st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
prob_audio_ok = 1;
av_log(ic, AV_LOG_DEBUG, "%d audio has_codec_parameters \n",j);
}
}
}
}
当获取到了相关解码信息后直接就退出这个死循环。
经过测试确实减少了avformat_find_stream_info
函数调用时间,然后打开流的速度快了很多。
测试中发现有时候显示画面快,有时候显示慢。这是因为服务器没有设置缓存GOP或者I帧的策略导致的,因为第一帧画面需要I帧。
try_decode_frame
处理
因为播放业务集中在机顶盒上且是高码率的流,而机顶盒硬件性能太差。分析发现 try_decode_frame
这个函数多次调用消耗很多时间。看一下下面对这个函数的描述:
/* If still no information, we try to open the codec and to
* decompress the frame. We try to avoid that in most cases as
* it takes longer and uses more memory. For MPEG-4, we need to
* decompress for QuickTime.
*
* If AV_CODEC_CAP_CHANNEL_CONF is set this will force decoding of at
* least one frame of codec data, this makes sure the codec initializes
* the channel configuration and does not only trust the values from
* the container. */
函数调用的地方描述了,要避免调用这个函数。因为这会消耗更长以及更多的内存。从上面描述来看其实不调用这个函数也不会有问题。
if(decodec_state[pkt->stream_index] == 0)
{
int result = try_decode_frame(ic, st, pkt,(options && i < orig_nb_streams) ? &options[i] : NULL);
if(result > 0){
decodec_state[pkt->stream_index] = 1;
}
}
如果流的当前Track 已经成功解码,则下次就不用重复调用这个函数了。这个是因为的流很可能会出现前几帧一直都是视频帧,然后才是音频帧。导致多次调用视频帧进行try_decode_frame
软解浪费时间,尤其在低端设备上面以及H265编码。
简单总结
其实没有什么需要总结的。直接看直播疑难杂症排查(3)— 首开慢这篇文章就好,已经总结的很好。不过最后强调一下
2.3 播放策略原因
这个优化也非常重要,不亚于avformat_find_stream_info
的优化。
最后,本来打算做一个测试对比。因为发现没有大家都能访问的合适的测试流作为对比。而公司内部的流外部无法访问,也就作罢了。声明一下:本人水平有限。如有错误,敬请指教。谢谢!