ffmpeg源码分析1-avformat_open_input

本文参考雷神博文:https://blog.csdn.net/leixiaohua1020

FFMPEG打开媒体的的过程开始于avformat_open_input,因此该函数的重要性不可忽视
//参数ps统领整个上下文,
//会返回一个AVFormatContext的实例.
//参数filename是媒体文件名或URL.
//参数fmt是要打开的媒体格式的操作结构,因为是读,所以是inputFormat.此处可以
//传入一个使用者定义的inputFormat,对应命令行中的 -f xxx段,如果指定了它,
//在打开文件中就不会探测文件的实际格式了,以它为准了.
//参数options是对某种格式的一些操作,是为了在命令行中可以对不同的格式传入
//特殊的操作参数而建的, 为了了解流程,完全可以无视它.

ps:函数调用成功之后处理过的AVFormatContext结构体。
file:打开的视音频流的URL。
fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat。
dictionay:附加的一些选项,一般情况下可以设置为NULL

在该函数中,FFMPEG完成了:
输入输出结构体AVIOContext的初始化;
输入数据的协议(例如RTMP,或者file)的识别(通过一套评分机制):1判断文件名的后缀 2读取文件头的数据进行比对;
使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与FFMPEG连接(非专业用词);
剩下的就是调用该URLProtocol的函数进行open,read等操作了

int avformat_open_input(AVFormatContext **ps, const char *filename,
                      AVInputFormat *fmt, AVDictionary **options)
{
  AVFormatContext *s = *ps;
  int i, ret = 0;
  AVDictionary *tmp = NULL;
  ID3v2ExtraMeta *id3v2_extra_meta = NULL;
//预防之前没有申请空间这里再申请一次,如果还是失败就直接return
  if (!s && !(s = avformat_alloc_context()))
      return AVERROR(ENOMEM);
  if (!s->av_class) {
      av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");
      return AVERROR(EINVAL);
  }
//如果指定了输入的格式,这样直接使用
  if (fmt)
      s->iformat = fmt;

  if (options)
      av_dict_copy(&tmp, *options, 0);

  if (s->pb) // must be before any goto fail
      s->flags |= AVFMT_FLAG_CUSTOM_IO;

  if ((ret = av_opt_set_dict(s, &tmp)) < 0)
      goto fail;

//复制文件名或者流的url给url指针,strdup申请了空间放置字符串并返回
  if (!(s->url = av_strdup(filename ? filename : ""))) {
      ret = AVERROR(ENOMEM);
      goto fail;
  }

#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGS
//标准库函数strlcpy,是更加安全版本的strcpy函数,把filename复制到s->filename
  av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif

//该函数主要是初始化AVInputFormat结构体,主要是调用av_probe_input_buffer2函数探测码流格式    这个函数对里面的函数指针seek跟close等赋值
//执行完此函数后,s->pb和s->iformat都已经指向了有效实例.pb是用于读写数据的,它  
  //把媒体数据当做流来读写,不管是什么媒体格式,而iformat把pb读出来的流按某种媒体格  
  //式进行分析,也就是说pb在底层,iformat在上层.
//绝大部分初始化工作都是在这里做的
  if ((ret = init_input(s, filename, &tmp)) < 0)
      goto fail;
/**
   * format probing score.
   * The maximal score is AVPROBE_SCORE_MAX, its set when the demuxer probes
   * the format.
   * - encoding: unused
   * - decoding: set by avformat, read by user
   */
  s->probe_score = ret;

  if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {
      s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);
      if (!s->protocol_whitelist) {
          ret = AVERROR(ENOMEM);
          goto fail;
      }
  }

  if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {
      s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);
      if (!s->protocol_blacklist) {
          ret = AVERROR(ENOMEM);
          goto fail;
      }
  }

  if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {
      av_log(s, AV_LOG_ERROR, "Format not on whitelist \'%s\'\n", s->format_whitelist);
      ret = AVERROR(EINVAL);
      goto fail;
  }
//直接将文件指针指向 s->skip_initial_bytes位置
  avio_skip(s->pb, s->skip_initial_bytes);
//很多静态图像文件格式,都被当作一个格式处理,比如要打开.jpeg文件,需要的格式  
  //名为image2
  /* Check filename in case an image number is expected. */
//检查文件名,在期望的图像编号的情况下检查文件名,后续再了解此处细节
  if (s->iformat->flags & AVFMT_NEEDNUMBER) {
      if (!av_filename_number_test(filename)) {
          ret = AVERROR(EINVAL);
          goto fail;
      }
  }

  s->duration = s->start_time = AV_NOPTS_VALUE;

  /* Allocate private data. */
//分配私有数据,此结构的size在定义AVInputFormat时已指定了
  if (s->iformat->priv_data_size > 0) {
      if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
          ret = AVERROR(ENOMEM);
          goto fail;
      }
//这里的用处还没细看,先不管
      if (s->iformat->priv_class) {
          *(const AVClass **) s->priv_data = s->iformat->priv_class;
          av_opt_set_defaults(s->priv_data);
          if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
              goto fail;
      }
  }

  /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
//从MP3文件读取id3
  if (s->pb)
      ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);

//读一下媒体的头部,根据流的数量分配流结构并初始化,把文件指针指向数据区开始处等.
read_header函数指针在init_input函数里面赋值
//读取多媒体数据文件头,根据视音频流创建相应的AVStream。
  if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
      if ((ret = s->iformat->read_header(s)) < 0)
          goto fail;

  if (!s->metadata) {
      s->metadata = s->internal->id3v2_meta;
      s->internal->id3v2_meta = NULL;
  } else if (s->internal->id3v2_meta) {
      int level = AV_LOG_WARNING;
      if (s->error_recognition & AV_EF_COMPLIANT)
          level = AV_LOG_ERROR;
      av_log(s, level, "Discarding ID3 tags because more suitable tags were found.\n");
      av_dict_free(&s->internal->id3v2_meta);
      if (s->error_recognition & AV_EF_EXPLODE)
          return AVERROR_INVALIDDATA;
  }

  if (id3v2_extra_meta) {
      if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||
          !strcmp(s->iformat->name, "tta")) {
          if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0)
              goto fail;
          if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0)
              goto fail;
          if ((ret = ff_id3v2_parse_priv(s, &id3v2_extra_meta)) < 0)
              goto fail;
      } else
          av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");
  }
  ff_id3v2_free_extra_meta(&id3v2_extra_meta);
//附属照片??
  if ((ret = avformat_queue_attached_pictures(s)) < 0)
      goto fail;

// s->internal->data_offset保存数据区开始的位置 
  if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
      s->internal->data_offset = avio_tell(s->pb);

  s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
//更新一下结构体参数
  update_stream_avctx(s);

  for (i = 0; i < s->nb_streams; i++)
      s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id;

  if (options) {
      av_dict_free(options);
      *options = tmp;
  }
  *ps = s;
  return 0;

fail:
  ff_id3v2_free_extra_meta(&id3v2_extra_meta);
  av_dict_free(&tmp);
  if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
      avio_closep(&s->pb);
  avformat_free_context(s);
  *ps = NULL;
  return ret;
}

下面是这个函数的调用结构图:


image.png


init_input()

此函数分析在ffmpeg源码分析3-init_input



AVInputFormat-> read_header()

在调用完init_input()完成基本的初始化并且推测得到相应的AVInputFormat之后,avformat_open_input()会调用AVInputFormat的read_header()方法读取媒体文件的文件头并且完成相关的初始化工作。read_header()是一个位于AVInputFormat结构体中的一个函数指针,对于不同的封装格式,会调用不同的read_header()的实现函数。举个例子,当输入视频的封装格式为MOV的时候,会调用mov的AVInputFormat中的read_header()。mov的AVInputFormat定义位于libavformat\mov.c文件中,如下所示。

AVInputFormat ff_mov_demuxer = {
    .name           = "mov,mp4,m4a,3gp,3g2,mj2",
    .long_name      = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),
    .priv_class     = &mov_class,
    .priv_data_size = sizeof(MOVContext),
    .extensions     = "mov,mp4,m4a,3gp,3g2,mj2",
    .read_probe     = mov_probe,
    .read_header    = mov_read_header,
    .read_packet    = mov_read_packet,
    .read_close     = mov_read_close,
    .read_seek      = mov_read_seek,
    .flags          = AVFMT_NO_BYTE_SEEK,
};

可以看出read_header()指向了mov_read_header()函数。mov_read_header()的实现同样位于libavformat\mov.c文件中,如下所示。

static int mov_read_header(AVFormatContext *s)
{
    MOVContext *mov = s->priv_data;
    AVIOContext *pb = s->pb;
    int j, err;
    MOVAtom atom = { AV_RL32("root") };
    int i;

    if (mov->decryption_key_len != 0 && mov->decryption_key_len != AES_CTR_KEY_SIZE) {
        av_log(s, AV_LOG_ERROR, "Invalid decryption key len %d expected %d\n",
            mov->decryption_key_len, AES_CTR_KEY_SIZE);
        return AVERROR(EINVAL);
    }

    mov->fc = s;
    mov->trak_index = -1;
    /* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */
    if (pb->seekable & AVIO_SEEKABLE_NORMAL)
        atom.size = avio_size(pb);
    else
        atom.size = INT64_MAX;

    /* check MOV header */
    do {
        if (mov->moov_retry)
            avio_seek(pb, 0, SEEK_SET);
        if ((err = mov_read_default(mov, pb, atom)) < 0) {
            av_log(s, AV_LOG_ERROR, "error reading header\n");
            mov_read_close(s);
            return err;
        }
    } while ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mov->found_moov && !mov->moov_retry++);
    if (!mov->found_moov) {
        av_log(s, AV_LOG_ERROR, "moov atom not found\n");
        mov_read_close(s);
        return AVERROR_INVALIDDATA;
    }
    av_log(mov->fc, AV_LOG_TRACE, "on_parse_exit_offset=%"PRId64"\n", avio_tell(pb));

    if (pb->seekable & AVIO_SEEKABLE_NORMAL) {
        if (mov->nb_chapter_tracks > 0 && !mov->ignore_chapters)
            mov_read_chapters(s);
        for (i = 0; i < s->nb_streams; i++)
            if (s->streams[i]->codecpar->codec_tag == AV_RL32("tmcd")) {
                mov_read_timecode_track(s, s->streams[i]);
            } else if (s->streams[i]->codecpar->codec_tag == AV_RL32("rtmd")) {
                mov_read_rtmd_track(s, s->streams[i]);
            }
    }

    /* copy timecode metadata from tmcd tracks to the related video streams */
    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;
        if (sc->timecode_track > 0) {
            AVDictionaryEntry *tcr;
            int tmcd_st_id = -1;

            for (j = 0; j < s->nb_streams; j++)
                if (s->streams[j]->id == sc->timecode_track)
                    tmcd_st_id = j;

            if (tmcd_st_id < 0 || tmcd_st_id == i)
                continue;
            tcr = av_dict_get(s->streams[tmcd_st_id]->metadata, "timecode", NULL, 0);
            if (tcr)
                av_dict_set(&st->metadata, "timecode", tcr->value, 0);
        }
    }
    export_orphan_timecode(s);

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;
        fix_timescale(mov, sc);
        if(st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && st->codecpar->codec_id == AV_CODEC_ID_AAC) {
            st->skip_samples = sc->start_pad;
        }
        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && sc->nb_frames_for_fps > 0 && sc->duration_for_fps > 0)
            av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
                      sc->time_scale*(int64_t)sc->nb_frames_for_fps, sc->duration_for_fps, INT_MAX);
        if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
            if (st->codecpar->width <= 0 || st->codecpar->height <= 0) {
                st->codecpar->width  = sc->width;
                st->codecpar->height = sc->height;
            }
            if (st->codecpar->codec_id == AV_CODEC_ID_DVD_SUBTITLE) {
                if ((err = mov_rewrite_dvd_sub_extradata(st)) < 0)
                    return err;
            }
        }
        if (mov->handbrake_version &&
            mov->handbrake_version <= 1000000*0 + 1000*10 + 2 &&  // 0.10.2
            st->codecpar->codec_id == AV_CODEC_ID_MP3
        ) {
            av_log(s, AV_LOG_VERBOSE, "Forcing full parsing for mp3 stream\n");
            st->need_parsing = AVSTREAM_PARSE_FULL;
        }
    }

    if (mov->trex_data) {
        for (i = 0; i < s->nb_streams; i++) {
            AVStream *st = s->streams[i];
            MOVStreamContext *sc = st->priv_data;
            if (st->duration > 0) {
                if (sc->data_size > INT64_MAX / sc->time_scale / 8) {
                    av_log(s, AV_LOG_ERROR, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n",
                           sc->data_size, sc->time_scale);
                    mov_read_close(s);
                    return AVERROR_INVALIDDATA;
                }
                st->codecpar->bit_rate = sc->data_size * 8 * sc->time_scale / st->duration;
            }
        }
    }

    if (mov->use_mfra_for > 0) {
        for (i = 0; i < s->nb_streams; i++) {
            AVStream *st = s->streams[i];
            MOVStreamContext *sc = st->priv_data;
            if (sc->duration_for_fps > 0) {
                if (sc->data_size > INT64_MAX / sc->time_scale / 8) {
                    av_log(s, AV_LOG_ERROR, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n",
                           sc->data_size, sc->time_scale);
                    mov_read_close(s);
                    return AVERROR_INVALIDDATA;
                }
                st->codecpar->bit_rate = sc->data_size * 8 * sc->time_scale /
                    sc->duration_for_fps;
            }
        }
    }

    for (i = 0; i < mov->bitrates_count && i < s->nb_streams; i++) {
        if (mov->bitrates[i]) {
            s->streams[i]->codecpar->bit_rate = mov->bitrates[i];
        }
    }

    ff_rfps_calculate(s);

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;

        switch (st->codecpar->codec_type) {
        case AVMEDIA_TYPE_AUDIO:
            err = ff_replaygain_export(st, s->metadata);
            if (err < 0) {
                mov_read_close(s);
                return err;
            }
            break;
        case AVMEDIA_TYPE_VIDEO:
            if (sc->display_matrix) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, (uint8_t*)sc->display_matrix,
                                              sizeof(int32_t) * 9);
                if (err < 0)
                    return err;

                sc->display_matrix = NULL;
            }
            if (sc->stereo3d) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_STEREO3D,
                                              (uint8_t *)sc->stereo3d,
                                              sizeof(*sc->stereo3d));
                if (err < 0)
                    return err;

                sc->stereo3d = NULL;
            }
            if (sc->spherical) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_SPHERICAL,
                                              (uint8_t *)sc->spherical,
                                              sc->spherical_size);
                if (err < 0)
                    return err;

                sc->spherical = NULL;
            }
            if (sc->mastering) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
                                              (uint8_t *)sc->mastering,
                                              sizeof(*sc->mastering));
                if (err < 0)
                    return err;

                sc->mastering = NULL;
            }
            if (sc->coll) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
                                              (uint8_t *)sc->coll,
                                              sc->coll_size);
                if (err < 0)
                    return err;

                sc->coll = NULL;
            }
            break;
        }
    }
    ff_configure_buffers_for_index(s, AV_TIME_BASE);

    for (i = 0; i < mov->frag_index.nb_items; i++)
        if (mov->frag_index.item[i].moof_offset <= mov->fragment.moof_offset)
            mov->frag_index.item[i].headers_read = 1;

    return 0;
}

可以看出,函数读取了mov的文件头并且判断其中是否包含视频流和音频流,创建相应的视频流和音频流。
经过上面的步骤AVInputFormat的read_header()完成了视音频流对应的AVStream的创建。至此,avformat_open_input()中的主要代码分析完毕。

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

推荐阅读更多精彩内容