美文网首页
ffmpeg源码分析1-avformat_open_input

ffmpeg源码分析1-avformat_open_input

作者: PuiKei | 来源:发表于2018-10-15 21:57 被阅读0次

    本文参考雷神博文: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()中的主要代码分析完毕。

    相关文章

      网友评论

          本文标题:ffmpeg源码分析1-avformat_open_input

          本文链接:https://www.haomeiwen.com/subject/llyqzftx.html