歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> avformat_find_stream_info接口延遲降低

avformat_find_stream_info接口延遲降低

日期:2017/3/3 14:05:24   编辑:Linux技術
0x00 前置信息
版本:ffmpeg2.2.0
文件:vlc src/module/demux/avformat/demux.c
函數:OpenDemux

0x01 研究背景

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接口內部讀取的最大數據量,代碼如下:
[code]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中,代碼如下:
[code]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接口中的一段代碼即可理解:
[code]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)
      goto find_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接口,具體代碼如下:
[code]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為自己實現的接口:接口及詳細代碼如下:
[code]enum {
    FLV_TAG_TYPE_AUDIO = 0x08,
    FLV_TAG_TYPE_VIDEO = 0x09,
    FLV_TAG_TYPE_META  = 0x12,
};

static AVStream *create_stream(AVFormatContext *s, int codec_type)
{
    AVStream *st = avformat_new_stream(s, NULL);
    if (!st)
        return NULL;
    st->codec->codec_type = codec_type;
    return st;
}

static int get_video_extradata(AVFormatContext *s, int video_index)
{
   int  type, size, flags, pos, stream_type;
   int ret = -1;
   int64_t dts;
   bool got_extradata = false;

   if (!s || video_index < 0 || video_index > 2)
      return ret;

   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);
       } else if (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;
   }

   return ret;
}

static int init_decode(AVFormatContext *s)
{
     int video_index = -1;
     int audio_index = -1;
     int ret = -1;

     if (!s)
          return ret;

     /*
     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;
     } else if (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;
        } else if (AVMEDIA_TYPE_AUDIO == s->streams[0]->codec->codec_type) {
           create_stream(s, AVMEDIA_TYPE_VIDEO);
           video_index = 1;
           audio_index = 0;
        }
     } else if (2 == s->nb_streams) {
        if (AVMEDIA_TYPE_VIDEO == s->streams[0]->codec->codec_type) {
           video_index = 0;
           audio_index = 1;
        } else if (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)
          return ret;

     //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;

     return ret;
}

分析:
在init_decode接口執行的操作如下:
經過avformat_open_input接口的調用,AVFormatContext內部有幾個流其實是無法預知的,所以需要判斷,沒有的流需調用create_stream接口創建,並分別設置video_index和audio_index。
根據已知信息,初始化audio和video的流信息。
因為H264解碼時需要sps/pps信息,這個信息在接收到的第一個video tag中,通過get_video_extradata接口獲取:
3.1 avio_*系列接口讀到的數據已經是flv格式的,所以判斷讀到的tag如果是audio tag或者metadata tag時,跳過這個tag數據,繼續讀。
3.2 如果是video tag,讀取其中的數據至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接口來降低延遲,當然會有很多限制,就看具體項目需求了。

0x03 總結

init_decode接口能夠適配多種設備發出的流,當然還有很多細節沒有關注到,需要在後續研究中跟進。
zz:https://jiya.io/archives/vlc_optimize_1.html
Copyright © Linux教程網 All Rights Reserved