Android 播放器首屏时间问题

因看到卢_俊 ——直播疑难杂症排查(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的优化。

最后,本来打算做一个测试对比。因为发现没有大家都能访问的合适的测试流作为对比。而公司内部的流外部无法访问,也就作罢了。声明一下:本人水平有限。如有错误,敬请指教。谢谢!

你可能感兴趣的:(Android 播放器首屏时间问题)