ffmpeg 音视频流信息分析延时

ffmpeg的两个接口avformat_open_input和avformat_find_stream_info分别用于打开一个流和分析流信息。在初始信息不足的情况下,avformat_find_stream_info接口需要在内部调用read_frame_internal接口读取流数据,然后再分析后,设置核心数据结构AVFormatContext。由于需要读取数据包,avformat_find_stream_info接口会带来很大的延迟,可通过以下几种方案降低该接口的延迟,具体如下:

通过设置AVFormatContext的probesize成员,来限制avformat_find_stream_info接口内部读取的最大数据量,代码如下:

AVFormatContext *fmt_ctx =NULL;

ret = avformat_open_input(&fmt_ctx, url, input_fmt,NULL);

fmt_ctx->probesize =4096;ret = avformat_find_stream_info(fmt_ctx,NULL);

这种方法其实会带来弊端,因为预读长度设置的过小时,在avformat_find_stream_info内部至多只会读取一帧数据,有些情况下,会导致这些数据不足以分析这个流的信息。

通过设置AVFormatContext的flags成员,来设置将avformat_find_stream_info内部读取的数据包不放入AVFormatContext的缓冲区packet_buffer中,代码如下:

AVFormatContext *fmt_ctx =NULL;

ret = avformat_open_input(&fmt_ctx, url, input_fmt,NULL);

fmt_ctx->flags |= AVFMT_FLAG_NOBUFFER;ret = avformat_find_stream_info(fmt_ctx,NULL);

深入avformat_find_stream_info接口内部就可以发现,当设置了AVFMT_FLAG_NOBUFFER选项后,数据包不入缓冲区,相当于在avformat_find_stream_info接口内部读取的每一帧数据只用于分析,不显示,摘avformat_find_stream_info接口中的一段代码即可理解:

if(ic->flags & AVFMT_FLAG_NOBUFFER) {  pkt = &pkt1;}

else{  pkt = add_to_pktbuf(&ic->packet_buffer, &pkt1,                      &ic->packet_buffer_end);

if((ret = av_dup_packet(pkt)) <0)gotofind_stream_info_err;}

当读取的数据包很多时,实际avformat_find_stream_info接口内部尝试解码以及分析的过程也是耗时的(具体没有测试),所以想到一种极端的解决方案,直接跳过avformat_find_stream_info接口,自定义初始化解码环境。

0x02 解决方法

前提条件:发送端的流信息可知。

比如环境的流信息为:

audio: AAC 44100Hz 2 channel 16bit 

video: H264 640*480 30fps

直播流

调用avformat_open_input接口后,不继续调用avformat_find_stream_info接口,具体代码如下:

AVFormatContext *fmt_ctx =NULL;

ret = avformat_open_input(&fmt_ctx, url, input_fmt,NULL);fmt_ctx->probesize =4096;

ret = init_decode(fmt_ctx);

init_decode为自己实现的接口:接口及详细代码如下:

enum{    FLV_TAG_TYPE_AUDIO =0x08,    FLV_TAG_TYPE_VIDEO =0x09,    FLV_TAG_TYPE_META  =0x12,};staticAVStream *create_stream(AVFormatContext *s,intcodec_type){    AVStream *st = avformat_new_stream(s,NULL);if(!st)returnNULL;    st->codec->codec_type = codec_type;returnst;}staticintget_video_extradata(AVFormatContext *s,intvideo_index){inttype, size, flags, pos, stream_type;intret = -1;int64_tdts;boolgot_extradata =false;if(!s || video_index <0|| video_index >2)returnret;for(;; avio_skip(s->pb,4)) {      pos  = avio_tell(s->pb);      type = avio_r8(s->pb);      size = avio_rb24(s->pb);      dts  = avio_rb24(s->pb);      dts |= avio_r8(s->pb) <<24;      avio_skip(s->pb,3);if(0== size)break;if(FLV_TAG_TYPE_AUDIO == type || FLV_TAG_TYPE_META == type) {/*if audio or meta tags, skip them.*/avio_seek(s->pb, size, SEEK_CUR);      }elseif(type == FLV_TAG_TYPE_VIDEO) {/*if the first video tag, read the sps/pps info from it. then break.*/size -=5;          s->streams[video_index]->codec->extradata = xmalloc(size + FF_INPUT_BUFFER_PADDING_SIZE);if(NULL== s->streams[video_index]->codec->extradata)break;memset(s->streams[video_index]->codec->extradata,0, size + FF_INPUT_BUFFER_PADDING_SIZE);memcpy(s->streams[video_index]->codec->extradata, s->pb->buf_ptr +5, size);          s->streams[video_index]->codec->extradata_size = size;          ret =0;          got_extradata =true;      }else{/*The type unknown,something wrong.*/break;      }if(got_extradata)break;  }returnret;}staticintinit_decode(AVFormatContext *s){intvideo_index = -1;intaudio_index = -1;intret = -1;if(!s)returnret;/*

    Get video stream index, if no video stream then create it.

    And audio so on.

    */if(0== s->nb_streams) {        create_stream(s, AVMEDIA_TYPE_VIDEO);        create_stream(s, AVMEDIA_TYPE_AUDIO);        video_index =0;        audio_index =1;    }elseif(1== s->nb_streams) {if(AVMEDIA_TYPE_VIDEO == s->streams[0]->codec->codec_type) {            create_stream(s, AVMEDIA_TYPE_AUDIO);            video_index =0;            audio_index =1;        }elseif(AVMEDIA_TYPE_AUDIO == s->streams[0]->codec->codec_type) {          create_stream(s, AVMEDIA_TYPE_VIDEO);          video_index =1;          audio_index =0;        }    }elseif(2== s->nb_streams) {if(AVMEDIA_TYPE_VIDEO == s->streams[0]->codec->codec_type) {          video_index =0;          audio_index =1;        }elseif(AVMEDIA_TYPE_VIDEO == s->streams[1]->codec->codec_type) {          video_index =1;          audio_index =0;        }    }/*Error. I can't find video stream.*/if(video_index !=0&& video_index !=1)returnret;//Init the audio codec(AAC).s->streams[audio_index]->codec->codec_id = AV_CODEC_ID_AAC;    s->streams[audio_index]->codec->sample_rate =44100;    s->streams[audio_index]->codec->time_base.den =44100;    s->streams[audio_index]->codec->time_base.num =1;    s->streams[audio_index]->codec->bits_per_coded_sample =16;    s->streams[audio_index]->codec->channels =2;    s->streams[audio_index]->codec->channel_layout =3;    s->streams[audio_index]->pts_wrap_bits =32;    s->streams[audio_index]->time_base.den =1000;    s->streams[audio_index]->time_base.num =1;//Init the video codec(H264).s->streams[video_index]->codec->codec_id = AV_CODEC_ID_H264;    s->streams[video_index]->codec->width =640;    s->streams[video_index]->codec->height =480;    s->streams[video_index]->codec->ticks_per_frame =2;    s->streams[video_index]->codec->pix_fmt =0;    s->streams[video_index]->pts_wrap_bits =32;    s->streams[video_index]->time_base.den =1000;    s->streams[video_index]->time_base.num =1;    s->streams[video_index]->avg_frame_rate.den =90;    s->streams[video_index]->avg_frame_rate.num =3;/*Need to change, different condition has different frame_rate. 'r_frame_rate' is new in ffmepg2.3.3*/s->streams[video_index]->r_frame_rate.den =60;    s->streams[video_index]->r_frame_rate.num =2;/* H264 need sps/pps for decoding, so read it from the first video tag.*/ret = get_video_extradata(s, video_index);/*Update the AVFormatContext Info*/s->nb_streams =2;/*empty the buffer.*/s->pb->buf_ptr = s->pb->buf_end;/*    something wrong.TODO:find out the 'pos' means what.    then set it.    */s->pb->pos = s->pb->buf_end;returnret;}

分析:

在init_decode接口执行的操作如下:

经过avformat_open_input接口的调用,AVFormatContext内部有几个流其实是无法预知的,所以需要判断,没有的流需调用create_stream接口创建,并分别设置video_index和audio_index。

根据已知信息,初始化audio和video的流信息。

因为H264解码时需要sps/pps信息,这个信息在接收到的第一个video tag中,通过get_video_extradata接口获取:

3.1avio_*系列接口读到的数据已经是flv格式的,所以判断读到的tag如果是audiotag或者metadatatag时,跳过这个tag数据,继续读。3.2如果是videotag,读取其中的数据至s->streams[video_index]->codec->extradata中,跳出循环。

更新AVFormatContext信息。

将缓冲区置空。

如果video tag是H263编码的,在init_decode接口内部,无需调用get_video_extradata接口即可成功初始化解码环境(需将codec_id设置为AV_CODEC_ID_FLV1)。

对于大多数情况,都可以通过自定义的接口init_decode替代avformat_find_stream_info接口来降低延迟,当然会有很多限制,就看具体项目需求了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容