ijkplayer播放rtsp延时越来越高处理方案

准备工作

ijkplayer的编译环境
一个局域网网络的rtsp实时流(比如wifi摄像头)
使用软解码,ff_play.c有完整控制软解码流程
ffmpeg使用ijk默认的就行,需要配置rtsp支持

注意事项

  • 网络本身有问题的情况下,此处理办法会导致频繁丢帧卡顿,不适用于真实互联网络的流。建议调试之前先用其他播放器看一下流是否有问题
  • 对于终端本身编码有延迟递增的情况下,此方案并不会降低延迟

场景

可以确保网络没有问题,并且频段也不会有影响。使用vlc播放器发现不会出现以下两个问题:

  • rtsp实时流播放随着时间变长,延时越来越高。
  • 某些设备开屏花屏。

原因

延时变高
  • 播放器会进行基准同步(包括视频,音频和系统时钟3个维度)。如果外部设置取消视频音频同步,那还会用系统时钟。
花屏
  • 某些设备开屏时流编码没有处理好,ijk这边通过配置进行丢掉指定数量的帧数再进行解码

修改方案

  • 对于实时预览,取消任何时间同步来应对此问题

修改函数

新增 previewMode, 新增preview_drop_first , 新增 low_pic

关于ijk配置修改

  //java调用设置, 开屏丢弃30帧
    mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER,"preview_drop_firstframe",30);
    //降低分辨率处理降低解码压力延迟
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER,"low_pic",1);//0:不降分辨率,正常解码。默认为0
       // 1:分辨率降低到原始的 1/2。
    //    2:分辨率降低到原始的 1/4。
       // 3:分辨率降低到原始的 1/8。
    mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER,"previewMode",1);//直播模式 ,1开启 0关闭  回放视频使用0

c++位置涉及文件:ff_play_option.h,ff_play_def.h

//在ff_play_def.h中新加入
typedef struct FFPlayer {
//其他
    int lowres;
    int previewMode;
    int preview_drop_first;
} FFPlayer;
//在ff_play.def.h里的关闭播放器时的伴生函数里重置启状态
inline static void ffp_reset_internal(FFPlayer *ffp)
{
  //其他
  //新增以下
    ffp->lowres                 = 0;
    ffp->previewMode            = 0;
    ffp->preview_drop_first      =0;
}



//ff_play_option.h中新增暴露配置值
static const AVOption ffp_context_options[] = {
     OPTION_OFFSET(infinite_buffer), OPTION_INT(0, 0, 1) },
        { "low_pic",                         "lowres",
      { "previewMode",                      "previewMode",
        OPTION_OFFSET(previewMode),       OPTION_INT(0, 0, 1) },
     {"preview_drop_firstframe",            "preview_drop_firstframe",
     OPTION_OFFSET(preview_drop_first),    OPTION_INT(0,0,INT_MAX)},       
  
}

以上配置完成。可以在java层进行使用了。

配置在native层进行应用

  • 打开ff_play.c

修改函数stream_component_open

在获取解码配置时加入

    if (!av_dict_get(opts, "threads", NULL, 0))
        if (ffp->previewMode==1)
        {
            av_log(NULL,AV_LOG_VERBOSE,"实时解码降低开销,使用线程数%d",2);
            av_dict_set(&opts, "threads", "2", 0);
        }else{
             av_dict_set(&opts, "threads", "auto", 0);
        }
       
    if (ffp->lowres>0)
        av_log(NULL,AV_LOG_VERBOSE,"实时解码降低开销,使用分辨率%d",ffp->lowres);
        av_dict_set_int(&opts, "lowres", ffp->lowres, 0);

修改函数get_video_frame,获取视频帧时,加入判断跳过为pts准备的时间偏移逻辑

              double diff = dpts;
              if (ffp->previewMode==0)
              {
                     diff=dpts - get_master_clock(is);
            }else{
                    av_log(NULL,AV_LOG_VERBOSE,"当前为直播模式,不进行%s","时间同步,完全根据pts渲染");
          }

修改函数video_refresh,每一帧获取时,进行跳帧处理

  //帧间隔时间处理
   if (lastvp->serial != vp->serial){
                if (ffp->previewMode==1)
                {
                    is->frame_timer = 0;
                }
                else{
                     is->frame_timer = av_gettime_relative() / 1000000.0;
                }
           }
  //跳帧处理
  if (opaque->preview_drop_first > 0) {
                 av_log(NULL,AV_LOG_VERBOSE,"drop first income frame count down %d",opaque->preview_drop_first);
                 opaque->preview_drop_first -= 1; 
                 frame_queue_next(&is->pictq); 
                 goto retry; 
            }

修改函数compute_target_delay,获取下一帧处理要延时多久处理时,进行延时归0处理,仅仅依赖系统时钟

 if (ffp->previewMode==0)
           {
             diff = get_clock(&is->vidclk) - get_master_clock(is);
              /* skip or repeat frame. We take into account the
           delay to compute the threshold. I still don't know
           if it is the best guess */
            sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
            /* -- by bbcallen: replace is->max_frame_duration with AV_NOSYNC_THRESHOLD */
            if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) {
                if (diff <= -sync_threshold)
                    delay = FFMAX(0, delay + diff);
                else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD)
                    delay = delay + diff;
                else if (diff >= sync_threshold)
                    delay = 2 * delay;
            }
           }else{
             av_log(NULL,AV_LOG_VERBOSE,"当前为直播模式,不延迟计算同步%s","直播模式1");
           }

修改函数stream_open,加入如果是直播模式则不初始化音频解码和字幕解码的判断

 if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
        goto fail;
    if (ffp->previewMode==0)
    {
     
        if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
            goto fail;
        if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
            goto fail;
    }else{
           av_log(NULL,AV_LOG_VERBOSE,"当前为直播模式禁用音视频解码队列初始化:%s","模式为1");
    }

修改函数stream_component_open,将解码线程默认定死,这个值外部也能自行修改,也应用lowres设置分辨率

 if (ffp->previewMode==1)
        {
            av_log(NULL,AV_LOG_VERBOSE,"实时解码降低开销,使用线程数%d",2);
            av_dict_set(&opts, "threads", "2", 0);
        }else{
             av_dict_set(&opts, "threads", "auto", 0);
        }
   if (ffp->lowres>0)
        av_log(NULL,AV_LOG_VERBOSE,"实时解码降低开销,使用分辨率%d",ffp->lowres);
        av_dict_set_int(&opts, "lowres", ffp->lowres, 0);      

参照文件修改,对照复制修改

观察设备master平台,延迟300-400ms 持续1小时,分辨率720p

观察设备羚羊平台,延迟300-400ms 持续1小时,分辨率640p

软解码

ijk总体配置

        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-hevc", 0);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0);//软解码
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 3000000);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fast", 1); // 开启快速解码模式
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "max_delay", 0); // 禁用缓冲延迟
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER,"previewMode",1);//直播模式
     /*   mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "video-pictq-size", 3);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "video-pictq-size-min", 1);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "video-pictq-size-max", 1);//编码关键帧间隔不大时,可以使用此配置,有效降低延迟,网络波动必卡*/
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "video-pictq-size", 9);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "video-pictq-size-min", 3);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "video-pictq-size-max", 6);
        //丢首次开屏帧
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER,"preview_drop_firstframe",30);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT,"dns_cache_clear",1);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT,"find_stream_info",0);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", rtspTransport);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzemaxduration", 100L);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1L);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "max-buffer-size", 2097152);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER,"overlay-format","fcc-_es2");
        //此配置可以有效控制所有延迟,不能设置为0,单位为ms
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 10); //300
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER,"keyframe_start",1);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 1048576);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "reconnect", 5);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
        //mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_frame", 5);

        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 60);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 30);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER,"low_pic",0);//0:不降分辨率,正常解码。
       // 1:分辨率降低到原始的 1/2。
    //    2:分辨率降低到原始的 1/4。
       // 3:分辨率降低到原始的 1/8。
     //   mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_fps", 30);
        mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "an", 1);//静音

总结

一次简单的对着问题调整的方案,没有什么技术含量。

后续补充

  • 在函数video_refresh里,有一个控制帧数渲染间隔的位置。那里可以更加直观的在延迟和播放之间进行平衡
  • 在对delay赋值的时候,获取maxfps来确定帧数间隔
  • 测试在delay为0的时候,局域网wifi摄像头可以达到200ms左右延迟。已经很低了,如果再继续降低,需要从网络链路下手了,缺点是网络波动时,会有卡顿掉帧很明显。
  • 测试在delay根据maxfps计算的间隔时,局域网wifi摄像头可以达到延迟在350ms左右,但是播放平稳,除了受到干扰(系统资源不够,或者网络波动)的情况下,也会明显的出现卡顿掉帧。
//在java层可以通过设置最大帧数来减少间隔
 mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_fps", 30);
//在native层里进行获取
    time= av_gettime_relative()/1000000.0;
            if (ffp->previewMode==1)
            {
                    double fps = ffp->max_fps;  // 目标帧率(例如 30 FPS)
                    delay = 1.0 / fps;  // 每帧的延迟时间(秒)
                    av_log(NULL,AV_LOG_VERBOSE,"直播模式,非系统控制,当前帧数间隔,%0.6f",delay);
                    //delay可以根据帧数来确定间隔,忽略网络延迟等其他因素,在播放平稳和延迟之间取得平衡。
                    //delay如果设置为0,系统会根据自己的情况来调度,会优先保证平稳播放,延迟就会递增。
            }
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值