美文网首页Android 播放器ijkplayer秘籍
Android 播放器首屏时间问题

Android 播放器首屏时间问题

作者: TinyTina | 来源:发表于2017-05-09 22:37 被阅读314次

    因看到卢_俊 ——直播疑难杂症排查(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 播放器首屏时间问题

      本文链接:https://www.haomeiwen.com/subject/euoitxtx.html