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

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


ffmpeg源码分析1-avformat_open_input_第1张图片
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()中的主要代码分析完毕。

你可能感兴趣的:(ffmpeg源码分析1-avformat_open_input)