现在ijkPlayer是许多播放器、直播平台的首选,相信很多开发者都接触过ijkPlayer,无论是Android工程师还是iOS工程师。我曾经在Github上的ijkPlayer开源项目上提问过:视频流为1080P、30fps,如何优化RTSP直播的延时为大约100ms呢?发现大家对RTSP直播延时优化非常感兴趣,纷纷提问或者给出自己的观点。本文主要是总结,也是与大家探讨RTSP直播的延时优化。 目录 一、修改编译脚本支持RTSP 二、修改播放器的option参数 三、网络抖动的丢包 四、解码器设为零延时 五、减少FFmpeg拆帧等待延时 1、找到当前帧结束符 2、去掉parsepacket的while循环 3、修改avparserparse2的帧偏移量 4、去掉parserparse的寻找帧起始码 5、修改parser。c的组帧方法一、修改编译脚本支持RTSP ijkPlayer默认是没有把RTSP协议编译进去,所以我们得修改编译脚本,原来的disable改为enable:exportCOMMONFFCFGFLAGSCOMMONFFCFGFLAGSenableprotocolrtpexportCOMMONFFCFGFLAGSCOMMONFFCFGFLAGSenableprotocoltcpexportCOMMONFFCFGFLAGSCOMMONFFCFGFLAGSenabledemuxerrtspexportCOMMONFFCFGFLAGSCOMMONFFCFGFLAGSenabledemuxersdpexportCOMMONFFCFGFLAGSCOMMONFFCFGFLAGSenabledemuxerrtp 二、修改播放器的option参数丢帧阈值mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYPLAYER,framedrop,30);视频帧率mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYPLAYER,fps,30);环路滤波mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYCODEC,skiploopfilter,48);设置无packet缓存mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYPLAYER,packetbuffering,0);mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYFORMAT,fflags,nobuffer);不限制拉流缓存大小mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYPLAYER,infbuf,1);设置最大缓存数量mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYFORMAT,maxbuffersize,1024);设置最小解码帧数mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYPLAYER,minframes,3);启动预加载mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYPLAYER,startonprepared,1);设置探测包数量mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYFORMAT,probsize,4096);设置分析流时长mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYFORMAT,analyzeduration,2000000); 值得注意的是,ijkPlayer默认使用udp拉流,因为速度比较快。如果需要可靠且减少丢包,可以改为tcp协议:mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYFORMAT,rtsptransport,tcp); 另外,可以这样开启硬解码,如果打开硬解码失败,再自动切换到软解码:mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYPLAYER,mediacodec,0);mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYPLAYER,mediacodecautorotate,0);mediaPlayer。setOption(IjkMediaPlayer。OPTCATEGORYPLAYER,mediacodechandleresolutionchange,0); 三、网络抖动的丢包 在拉流时,音频流、视频流是单独保存到缓冲队列的。如果发生网络抖动,就会引起缓冲抖动(JitBuffer),可以总结为网络卡顿导致音视频缓冲队列增大,从而导致解码滞后、播放滞后。此时,我们需要主动丢包来跟进当前时间戳。因为音视频同步一般以音频时钟为基准,人们对音频更加敏感,所以我们优先丢掉视频队列的包。但是,丢视频数据包时,需要丢掉整个GOP的数据包,因为B帧、P帧依赖I帧来解码,否则会引起花屏。有一位开发者叫做暴走大牙,他的一篇关于ijkPlayer直播延时的文章写得很好:ijkplay播放直播流延时控制小结 四、解码器设为零延时 大家应该听过编码器的零延时(zerolatency),但可能没听过解码器零延时。其实解码器内部默认会缓存几帧数据,用于后续关联帧的解码,大概是35帧。经过反复测试,发现解码器的缓存帧会带来100多ms延时。也就是说,假如能够去掉缓存帧,就可以减少100多ms的延时。而在avcodec。h文件的AVCodecContext结构体有一个参数(flags)用来设置解码器延时:typedefstructAVCodecContext{。。。。。。。。。。。。} 为了去掉解码器缓存帧,我们可以把flags设置为CODECFLAGLOWDELAY。在初始化解码器时进行设置:setdecoderaslowdedaycodecctxflagsCODECFLAGLOWDELAY; 【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(CC,Linux,FFmpegwebRTCrtmphlsrtspffplaysrs等等)有需要的可以后台私信扣1免费领取 五、减少FFmpeg拆帧等待延时 FFmpeg拆帧是根据下一帧的起始码来作为当前帧结束符,起始码一般是:0x000x000x000x01或者0x000x000x01。这样就会带来一帧的延时,这一帧延时能不能去掉呢?如果有帧结束符,我们以帧结束符来拆帧,这样做就能解决一帧延时。现在,问题变成找到帧结束符,然后替换成下一帧起始码来拆帧。整个调用流程是:readframereadframeinternalparsepacketavparserparse2parserparseffcombineframe。流程图如下: 1、找到当前帧结束符 在rtpdec。c文件的rtpparsepacketinternal方法里,有获取帧结束符,也就是mark标志位,我们在这里设一个全局变量:staticintrtpparsepacketinternal(RTPDemuxContexts,AVPacketpkt,constuint8tbuf,intlen){。。。。。。if(buf〔1〕0x80)flagsRTPFLAGMARKER;。。。。。。} 2、去掉parsepacket的while循环 我们在外部调用libavformat模块的utils。c文件的readframe读取一帧数据,而readframe调用内部方法readframeinternal,readframeinternal接着调用parsepacket方法,在该方法里有一个while循环体。现在把循环体去掉,并且释放申请的内存:staticintparsepacket(AVFormatContexts,AVPacketpkt,intstreamindex){。。。。。。while(size0(pktflushpktgotoutput)){int64int64avinitpacket(outpkt);lenavparserparse2(stparser,stinternalavctx,outpkt。data,outpkt。size,data,size,pktpts,pktdts,pktpos);pktptspktdtsAVNOPTSVALUE;pktpos1;gotoutput!!outpkt。if(!outpkt。size){avpacketunref(outpkt);releasecurrentpacketavpacketunref(pkt);releasecurrentpacketreturn0;}。。。。。。retaddtopktbuf(sinternalparsequeue,outpkt,sinternalparsequeueend,1);avpacketunref(outpkt);if(ret0)}endofthestreamcloseandfreetheparserif(pktflushpkt){avparserclose(stparser);stparserNULL;}fail:avpacketunref(pkt);} 3、修改avparserparse2的帧偏移量 在libavcodec模块的parser。c文件中,parsepacket调用到avparserparse2来解释数据包,该方法内部有记录帧偏移量。原先是等待下一帧的起始码,现在改为当前帧结束符,所以要把下一帧起始码这个偏移量长度去掉:intavparserparse2(AVCodecParserContexts,AVCodecContextavctx,uint8tpoutbuf,intpoutbufsize,constuint8tbuf,intbufsize,int64tpts,int64tdts,int64tpos){。。。。。。WARNING:thereturnedindexcanbenegativeindexsparserparserparse(s,avctx,(constuint8t)poutbuf,poutbufsize,buf,bufsize);avassert0(index0x20000000);TheAPIdoesnotallowreturningAVERRORcodesdefineFILL(name)if(sname0avctxname0)avctxnamesnameif(avctxcodectypeAVMEDIATYPEVIDEO){FILL(fieldorder);}updatethefilepointerif(poutbufsize){videoframedontplusindexif(avctxcodectypeAVMEDIATYPEVIDEO){}else{}sfetchtimestamp1;}if(index0)index0;} 4、去掉parserparse的寻找帧起始码 avparserparse2调用到parserparse方法,而我们这里使用的是h264解码,所以在libavcodec模块的h264parser。c有一个结构体ffh264parser,把h264parse赋值给parserparse:AVCodecParserffh264parser{。codecids{AVCODECIDH264},。privdatasizesizeof(H264ParseContext),。parserinitinit,。parserparseh264parse,。parsercloseh264close,。splith264split,}; 现在我们需要h264parser。c文件的h264parse方法,去掉寻找下一帧起始码作为当前帧结束符的过程:staticinth264parse(AVCodecParserContexts,AVCodecContextavctx,constuint8tpoutbuf,intpoutbufsize,constuint8tbuf,intbufsize){。。。。。。if(sflagsPARSERFLAGCOMPLETEFRAMES){}else{TODO:dontusenextframestartcode,modifybyxufulongnexth264findframeend(p,buf,bufsize,avctx);if(ffcombineframe(pc,next,buf,bufsize)0){poutbufNULL;poutbufsize0;}if(next0next!ENDNOTFOUND){avassert1(pclastindexnext0);h264findframeend(p,pcbuffer〔pclastindexnext〕,next,avctx);updatestate}}。。。。。。}5、修改parser。c的组帧方法 h264parse又调用parser。c的ffcombineframe组帧方法,我们在这里把mark替换起始码作为帧结束符:引用全局变量intffcombineframe(ParseContextpc,intnext,constuint8tbuf,intbufsize){。。。。。。copyintobufferendreturnif(nextENDNOTFOUND){voidnewbufferavfastrealloc(pcbuffer,pcbuffersize,bufsizepcindexAVINPUTBUFFERPADDINGSIZE);if(!newbuffer){pcindex0;returnAVERROR(ENOMEM);}memcpy(pcbuffer〔pcindex〕,buf,bufsize);return1;if(!markflag)return1;next0;}。。。。。。} 经过以上修改,局域网用电脑推送1080P、30fps的视频流,Android设备拉流解码播放,整体延时可优化至130ms左右。而手机推流,延时可达到86ms。 作者:徐福记456 原文链接:https:blog。csdn。netu011686167articledetails85256101?spm1001。2014。3001。5502