范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

ijkplayer基于rtsp直播延时的深度优化

  现在ijkPlayer是许多播放器、直播平台的首选,相信很多开发者都接触过ijkPlayer,无论是Android工程师还是iOS工程师。我曾经在Github上的ijkPlayer开源项目上提问过:视频流为1080P、30fps,如何优化RTSP直播的延时为大约100ms呢?发现大家对RTSP直播延时优化非常感兴趣,纷纷提问或者给出自己的观点。本文主要是总结,也是与大家探讨RTSP直播的延时优化。
  目录
  一、修改编译脚本支持RTSP
  二、修改播放器的option参数
  三、网络抖动的丢包
  四、解码器设为零延时
  五、减少FFmpeg拆帧等待延时
  1、找到当前帧结束符
  2、去掉parse_packet的while循环
  3、 修改av_parser_parse2的帧偏移量
  4、去掉parser_parse的寻找帧起始码
  5、修改parser.c的组帧方法 一、修改编译脚本支持RTSP
  ijkPlayer默认是没有把RTSP协议编译进去,所以我们得修改编译脚本,原来的disable改为enable: export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-protocol=rtp" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-protocol=tcp" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=rtsp" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=sdp" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=rtp"
  二、修改播放器的option参数 //丢帧阈值 mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 30); //视频帧率 mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fps", 30); //环路滤波 mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48); //设置无packet缓存 mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0); mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer"); //不限制拉流缓存大小 mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1); //设置最大缓存数量 mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "max-buffer-size", 1024); //设置最小解码帧数 mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 3); //启动预加载 mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1); //设置探测包数量 mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probsize", "4096"); //设置分析流时长 mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", "2000000");
  值得注意的是,ijkPlayer默认使用udp拉流,因为速度比较快。如果需要可靠且减少丢包,可以改为tcp协议: mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp");
  另外,可以这样开启硬解码,如果打开硬解码失败,再自动切换到软解码: mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0); mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0); mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0);
  三、网络抖动的丢包
  在拉流时,音频流、视频流是单独保存到缓冲队列的。如果发生网络抖动,就会引起缓冲抖动(JitBuffer),可以总结为网络卡顿导致音视频缓冲队列增大,从而导致解码滞后、播放滞后。此时,我们需要主动丢包来跟进当前时间戳。因为音视频同步一般以音频时钟为基准,人们对音频更加敏感,所以我们优先丢掉视频队列的包。但是,丢视频数据包时,需要丢掉整个GOP的数据包,因为B帧、P帧依赖I帧来解码,否则会引起花屏。有一位开发者叫做暴走大牙,他的一篇关于ijkPlayer直播延时的文章写得很好:ijkplay播放直播流延时控制小结
  四、解码器设为零延时
  大家应该听过编码器的零延时(zerolatency),但可能没听过解码器零延时。其实解码器内部默认会缓存几帧数据,用于后续关联帧的解码,大概是3-5帧。经过反复测试,发现解码器的缓存帧会带来100多ms延时。也就是说,假如能够去掉缓存帧,就可以减少100多ms的延时。而在avcodec.h文件的AVCodecContext结构体有一个参数(flags)用来设置解码器延时: typedef struct AVCodecContext { ...... int flags; ...... }
  为了去掉解码器缓存帧,我们可以把flags设置为CODEC_FLAG_LOW_DELAY。在初始化解码器时进行设置: //set decoder as low deday codec_ctx->flags |= CODEC_FLAG_LOW_DELAY;
  【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以后台私信扣1免费领取~
  五、减少FFmpeg拆帧等待延时
  FFmpeg拆帧是根据下一帧的起始码来作为当前帧结束符,起始码一般是:0x00 0x00 0x00 0x01或者0x00 0x00 0x01。这样就会带来一帧的延时,这一帧延时能不能去掉呢?如果有帧结束符,我们以帧结束符来拆帧,这样做就能解决一帧延时。现在,问题变成找到帧结束符,然后替换成下一帧起始码来拆帧。整个调用流程是:read_frame—>read_frame_internal—>parse_packet—>av_parser_parse2—>parser_parse—>ff_combine_frame. 流程图如下:
  1、找到当前帧结束符
  在rtpdec.c文件的rtp_parse_packet_internal方法里,有获取帧结束符,也就是mark标志位,我们在这里设一个全局变量: static int rtp_parse_packet_internal(RTPDemuxContext *s, AVPacket *pkt,                                      const uint8_t *buf, int len) {     ......       if (buf[1] & 0x80)         flags |= RTP_FLAG_MARKER;     //the end of a frame     mark_flag = flags;       ...... }
  2、去掉parse_packet的while循环
  我们在外部调用libavformat模块的utils.c文件的read_frame读取一帧数据,而read_frame调用内部方法read_frame_internal,read_frame_internal接着调用parse_packet方法,在该方法里有一个while循环体。现在把循环体去掉,并且释放申请的内存: static int parse_packet(AVFormatContext *s, AVPacket *pkt, int stream_index) {     ......   //    while (size > 0 || (pkt == &flush_pkt && got_output)) {         int len;         int64_t next_pts = pkt->pts;         int64_t next_dts = pkt->dts;           av_init_packet(&out_pkt);         len = av_parser_parse2(st->parser, st->internal->avctx,                                &out_pkt.data, &out_pkt.size, data, size,                                pkt->pts, pkt->dts, pkt->pos);         pkt->pts = pkt->dts = AV_NOPTS_VALUE;         pkt->pos = -1;         /* increment read pointer */         data += len;         size -= len;           got_output = !!out_pkt.size;           if (!out_pkt.size){             av_packet_unref(&out_pkt);//release current packet             av_packet_unref(pkt);//release current packet             return 0; //            continue;         }     ......                     ret = add_to_pktbuf(&s->internal->parse_queue, &out_pkt,                             &s->internal->parse_queue_end, 1);         av_packet_unref(&out_pkt);         if (ret < 0)             goto fail; //    }       /* end of the stream => close and free the parser */     if (pkt == &flush_pkt) {         av_parser_close(st->parser);         st->parser = NULL;     }   fail:     av_packet_unref(pkt);     return ret; }
  3、 修改av_parser_parse2的帧偏移量
  在libavcodec模块的parser.c文件中,parse_packet调用到av_parser_parse2来解释数据包,该方法内部有记录帧偏移量。原先是等待下一帧的起始码,现在改为当前帧结束符,所以要把下一帧起始码这个偏移量长度去掉: int av_parser_parse2(AVCodecParserContext *s, AVCodecContext *avctx,                      uint8_t **poutbuf, int *poutbuf_size,                      const uint8_t *buf, int buf_size,                      int64_t pts, int64_t dts, int64_t pos) {     ......       /* WARNING: the returned index can be negative */     index = s->parser->parser_parse(s, avctx, (const uint8_t **) poutbuf,                                     poutbuf_size, buf, buf_size);     av_assert0(index > -0x20000000); // The API does not allow returning AVERROR codes #define FILL(name) if(s->name > 0 && avctx->name <= 0) avctx->name = s->name     if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {         FILL(field_order);     }       /* update the file pointer */     if (*poutbuf_size) {         /* fill the data for the current frame */         s->frame_offset = s->next_frame_offset;           /* offset of the next frame */ //        s->next_frame_offset = s->cur_offset + index;         //video frame don"t plus index         if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {             s->next_frame_offset = s->cur_offset;         }else{             s->next_frame_offset = s->cur_offset + index;         }         s->fetch_timestamp   = 1;     }     if (index < 0)         index = 0;     s->cur_offset += index;     return index; }
  4、去掉parser_parse的寻找帧起始码
  av_parser_parse2调用到parser_parse方法,而我们这里使用的是h264解码,所以在libavcodec模块的h264_parser.c有一个结构体ff_h264_parser,把h264_parse赋值给parser_parse: AVCodecParser ff_h264_parser = {     .codec_ids      = { AV_CODEC_ID_H264 },     .priv_data_size = sizeof(H264ParseContext),     .parser_init    = init,     .parser_parse   = h264_parse,     .parser_close   = h264_close,     .split          = h264_split, };
  现在我们需要h264_parser.c文件的h264_parse方法,去掉寻找下一帧起始码作为当前帧结束符的过程: static int h264_parse(AVCodecParserContext *s,                       AVCodecContext *avctx,                       const uint8_t **poutbuf, int *poutbuf_size,                       const uint8_t *buf, int buf_size) {     ......       if (s->flags & PARSER_FLAG_COMPLETE_FRAMES) {         next = buf_size;     } else { //TODO:don"t use next frame start code, modify by xufulong //        next = h264_find_frame_end(p, buf, buf_size, avctx);           if (ff_combine_frame(pc, next, &buf, &buf_size) < 0) {             *poutbuf      = NULL;             *poutbuf_size = 0;             return buf_size;         }   /*        if (next < 0 && next != END_NOT_FOUND) {             av_assert1(pc->last_index + next >= 0);             h264_find_frame_end(p, &pc->buffer[pc->last_index + next], -next, avctx); // update state         }*/     }       ...... }5、修改parser.c的组帧方法
  h264_parse又调用parser.c的ff_combine_frame组帧方法,我们在这里把mark替换起始码作为帧结束符: external int mark_flag;//引用全局变量   int ff_combine_frame(ParseContext *pc, int next,const uint8_t **buf, int *buf_size) {     ......       /* copy into buffer end return */ //    if (next == END_NOT_FOUND) {         void *new_buffer = av_fast_realloc(pc->buffer, &pc->buffer_size,                                            *buf_size + pc->index +                                            AV_INPUT_BUFFER_PADDING_SIZE);           if (!new_buffer) {                        pc->index = 0;             return AVERROR(ENOMEM);         }         pc->buffer = new_buffer;         memcpy(&pc->buffer[pc->index], *buf, *buf_size);         pc->index += *buf_size; //        return -1;           if(!mark_flag)             return -1;         next = 0; //    }       ......   }
  经过以上修改,局域网用电脑推送1080P、30fps的视频流,Android设备拉流解码播放,整体延时可优化至130ms左右。而手机推流,延时可达到86ms。
  作者:徐福记456
  原文链接:https://blog.csdn.net/u011686167/article/details/85256101?spm=1001.2014.3001.5502

原神3。0复刻池再爆料,胡桃或换为心海?一众新手强力角色登场前段时间,某个战绩不错的预言家突然曝出了3。0版本将有三期复刻池的消息,而且有提到达达利亚与胡桃的复刻几率最高。紧随其后,又有玩家在论坛中说可以准备抽护摩之杖。然而就在不久前,这位VRAwards2022入围名单公布!你喜欢的VR游戏上榜了吗?(VRPinea8月8日讯)8月3日,VR大奖(VRAwards)的评委会公布了2022年度的决赛入围者名单。今年已是该活动举办的第六个年头,前两年由于疫情的影响,颁奖仪式都改为了十大Steam生存游戏节奶茶价游戏,方舟生存进化29元历史最低最近Steam游戏平台所开启的生存游戏节活动已经接近末了,不过这一次参与活动的游戏太多,玩家们难免有些遗漏,下面我来为大家推荐10款好游戏,也算是再给大家一次查缺补漏的机会。方舟生华为办公宝IdeaHubS2系列发布首搭鸿蒙系统8月8日下午,华为与央视联合召开2022华为智能协作新品发布会,正式发布首款搭载鸿蒙系统的华为新一代办公宝IdeaHubS2系列。目前,华为IdeaHubS2系列已在华为商城上架,可折叠平板电脑?除了三星的flip和fold外,可折叠新设备可能发布三星的下一代可折叠智能手机距离该公司Unpacked发布会亮相还有几天的时间。但是,一旦GalaxyZFlip4和GalaxyZFold4正式发布,接下来会发生什么呢?三星会继续研三星GalaxyA235G发布搭载骁龙695处理器近日,根据多家科技媒体的消息,三星在海外官网推出了全新入门5G手机GalaxyA235G。按照三星这家智能手机厂商的介绍,这款手机采用了高通695处理器,同样配备了低价手机常有的5电脑C盘又满了,今天教大家如何清理C盘垃圾很多小伙伴,电脑用的时间久了,慢慢的就会觉得电脑卡顿,C盘变满了。那么如何解决呢,今天给大家分享几个小技巧。一输入代码技巧同时按住Windows键和字母R键,按下之后会弹出运行框在恭喜刘国梁!恭喜国乒!国家体育总局官宣,马龙最新身份曝光北京时间8月8日,国乒正在积极备战成都世乒赛团体赛的比赛,队伍在结束了7月份的布达佩斯密集的比赛任务后,主力队员第一时间返回国内进行备战,随后年轻队员们则是前往突尼斯参加了WTT支最新一周世界排名出炉!张本智和未能超过梁靖崑,雨果重回前5名随着突尼斯挑战赛的结束,国际乒联也更新了最新一周的世界排名。而在男子单打排名当中,由于樊振东马龙和梁靖崑这三位现世界排名前三位的球员没有报名本次突尼斯挑战赛的比赛,所以三个人的世界江湖永远都有新鲜事,九阴真经便是最努力的播报员很难搞清楚江湖的具体定义,笼统地来说就是形色各异的人混在一起产生的蝴蝶效应,没有固定的剧本,一切都是权衡利弊的产物,而几百年的江湖世界,又怎是一部剧情能说清的?连金庸和古龙这种武侠大电池加特小米MIX5新机曝光,小米MIX4沦为让路机跌至白菜价虽然说研发费用的投入和研发成果的展出很多都不能立竿见影,但从这两年小米手机身上依然能看出一些阶段性的成果,比如在屏幕性能影像快充等多个模块,小米旗舰的表现都已稳居整个行业的第一梯队
朝鲜的豪华套餐,一顿240元,妥妥贵族食物说起旅游食品,其实很多都大有水分,很多普通的食材经特殊的当地烹饪手法一烹制,马上就会飞上枝头变凤凰,在不是那么商业化的朝鲜也是如此。就比如朝游爱好者圈内传说的豪华套餐,一份就得两百朝鲜的豪华套餐,一顿240元,妥妥贵族食物说起旅游食品,其实很多都大有水分,很多普通的食材经特殊的当地烹饪手法一烹制,马上就会飞上枝头变凤凰,在不是那么商业化的朝鲜也是如此。就比如朝游爱好者圈内传说的豪华套餐,一份就得两百听说这个丛林飞跃游戏很火,29大人小孩都可玩,关键是便宜又好玩与家人们洞察自然,享受快乐今天要和大家推荐一个户外运动项目丛林飞跃。惊险!刺激!好玩!的探险乐园。N多游乐项目,再也不用愁没游戏玩。假期溜娃,家庭出游随时开趴走起!PART01惊险听说这个丛林飞跃游戏很火,29大人小孩都可玩,关键是便宜又好玩与家人们洞察自然,享受快乐今天要和大家推荐一个户外运动项目丛林飞跃。惊险!刺激!好玩!的探险乐园。N多游乐项目,再也不用愁没游戏玩。假期溜娃,家庭出游随时开趴走起!PART01惊险盘点国内最迷人的6条水上公路水天一色见之难忘,你走过几条?生活总是需要惊喜来给平淡的日子添加一抹色彩,世界之大,一定会有地方值得你停下脚步,静静欣赏沿途的风景。今天就来和大家盘点一下如同人间仙境的国内最美6条水上公路吧。1天空之镜之美青海盘点国内最迷人的6条水上公路水天一色见之难忘,你走过几条?生活总是需要惊喜来给平淡的日子添加一抹色彩,世界之大,一定会有地方值得你停下脚步,静静欣赏沿途的风景。今天就来和大家盘点一下如同人间仙境的国内最美6条水上公路吧。1天空之镜之美青海冬奥村,美就美在半山腰从延庆去冬奥村,有人说走G7可免高速费,于是舍弃京礼高速走了一遭,结果真是好奇害惨聪明人。在松山上夜穿了十几公里的盘山路,油钱远超那八块钱的过路费。幸亏急弯处都有智能的声控灯,减去冬奥村,美就美在半山腰从延庆去冬奥村,有人说走G7可免高速费,于是舍弃京礼高速走了一遭,结果真是好奇害惨聪明人。在松山上夜穿了十几公里的盘山路,油钱远超那八块钱的过路费。幸亏急弯处都有智能的声控灯,减去你还在空腹吃这4种食物吗?生病了可别怪我,空腹进食也有顺序日常生活中我们总能听到许多空腹的禁忌空腹不能喝茶喝咖啡喝酒?空腹不能吃香蕉?空腹不能吃柿子?真相究竟是什么呢?快跟小圈一起来了解柴宁莉中国人民解放军总医院第一医学中心消化内科医学部你还在空腹吃这4种食物吗?生病了可别怪我,空腹进食也有顺序日常生活中我们总能听到许多空腹的禁忌空腹不能喝茶喝咖啡喝酒?空腹不能吃香蕉?空腹不能吃柿子?真相究竟是什么呢?快跟小圈一起来了解柴宁莉中国人民解放军总医院第一医学中心消化内科医学部糖尿病患者测血糖时要注意这五点,不然测出的血糖可能不准了众所周知,在糖尿病的诊断和检查中,血糖的检查是一个非常重要的环节,血糖值的争取测量,有利于对患者的病情做出正确的判断,不然会影响到糖尿病的及时和正确的治疗。在测血糖的时候会有许多的