ffmpeg系列-解复用流程解析

发布于:2022-12-17 ⋅ 阅读:(569) ⋅ 点赞:(0)

本文主要来分析demuxer的流程。

1.结构流程图

从上面的结构图中我们可以看到AVFormatContext的iformat指向AVInputFormat。

2.实现流程图
3.avformat_open_input函数作用
首先看函数的声明

int avformat_open_input(AVFormatContext **ps, const char *filename,
                        AVInputFormat *fmt, AVDictionary **options)

这个函数的作用是打开文件的链接,如果是网络连接,还会发起网络请求,并一直等待网络数据的返回,然后读取视频流的数据(协议操作)。

AVFormatContext **ps

 

该函数的主要作用是填充好AVFormatContext **ps这个结构体。AVFormatContext这个结构体里面的参数比较多,这里就不一一列举了,详细可以参考avformat.h这个头文件,具体用到到时再详细说明。

const char *filename

文件的全部路径,比如

http://videocdn.eebbk.net/7abbdb39fd2aa71efa64f9897f4f5616.mp4
AVInputFormat *fmt

 AVInputFormat 的结构体也比较复杂,主要是封装媒体数据封装类型的结构体,比如flv,mpegts,mp4等。在这里可以传入空,如果为空,后面就会从网络数据中读出。当然如果我们知道文件的类型,先用av_find_input_format(“mp4”)初始化出对应的结构体,这里我们用的是mp4,先初始化好这个结构体,比如对于直播来说,会比较节约时间。
 

AVDictionary **options

struct AVDictionary {
  int count;
  AVDictionaryEntry *elems;
};
typedef struct AVDictionaryEntry {
  char *key;
  char *value;
} AVDictionaryEntry;

字典类型的可选参数,可以向ffmpeg中传入指定的参数的值。比如我们这里传入了

//number of frames used to probe fps
 av_dict_set_int(&ffp->format_opts, "fpsprobesize", 0, 0);

表示fpsprobesize对应的参数值为0,当然还可以传入更多值,具体可以参考options_table.h/libavformat这个头文件。

文章最后扫码进qun,可免费领取音视频学习资料,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等

 

函数使用sample:

    AVFormatContext *ic = NULL;//初始化ic变量
	//申请AVFormatContext内存
    ic = avformat_alloc_context();
    if (!ic) {//内存申请失败
        av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n");
        ret = AVERROR(ENOMEM);
        goto fail;
    }
    ic->interrupt_callback.callback = decode_interrupt_cb;//IO层错误回调
    ic->interrupt_callback.opaque = is;
    if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
        av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
        scan_all_pmts_set = 1;
    }
    err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
    if (err < 0) {
        print_error(is->filename, err);
        ret = -1;
        goto fail;
    }

4.流程实现分析

讲解解复用流程,我们还是需要从avformat_open_input/utils.c/libavformat函数开始分析整个解析流程。

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;
	//如果s为空,且没有申请内存的话,申请AVFormatContext内存
    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;//AVInputFormat赋值给AVFormatContext的AVInputFormat

    if (options)//获取option,拷贝给tmp
        av_dict_copy(&tmp, *options, 0);

    if (s->pb) // must be before any goto fail
        s->flags |= AVFMT_FLAG_CUSTOM_IO;
    //把获取的option设置给AVFormatContext
    if ((ret = av_opt_set_dict(s, &tmp)) < 0)
        goto fail;
	//filename赋值给s->filename
    av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
	//初始化协议与初始化解封装(文件探测等)
    if ((ret = init_input(s, filename, &tmp)) < 0)
        goto fail;
    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;
    }
	//打开流的时候跳过最初的字节
    avio_skip(s->pb, s->skip_initial_bytes);

    /* 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;
        }
    }
	//把AVFormatContext的duration和start_time置为NOPTS
    s->duration = s->start_time = AV_NOPTS_VALUE;

    /* Allocate private data. */
    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 */
    if (s->pb)
        ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);


    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
		//在调用完init_input()完成基本的初始化并且推测得到相应的AVInputFormat之后,
		//avformat_open_input()会调用AVInputFormat的read_header()方法读取媒体文件的文件头并且完成相关的初始化工作
        if ((ret = s->iformat->read_header(s)) < 0)
            goto fail;
	//id3v2信息的处理
	//如果metadata是空的话
    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) {//如果文件名是mp3 aac 或者 tta
        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;
        } 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;

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

从avformat_open_input中我们可以知道函数先调用init_input来实现解协议和解封装,在解协议和解封装结束后调用iformat->read_header来读取文件头,根据视音频流创建相应的AVStream。还有一些逻辑判断,如黑白协议表以及id3v2等的处理。

下面我们看下init_input/utils.c/libavformat函数
 

/* 
*	打开输入文件,探测文件格式,返回的是探测文件所得的分数
*	Open input file and probe the format if necessary. 
*/
static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    int ret;
    AVProbeData pd = { filename, NULL, 0 };
    int score = AVPROBE_SCORE_RETRY;//默认score 25分
	//如果已经有了AVIOContext,也就是文件(协议)已经打开了
    if (s->pb) {
        s->flags |= AVFMT_FLAG_CUSTOM_IO;
        if (!s->iformat)//如果AVFormatContext中没有指定AVInputformat,指定就调用av_probe_input_buffer2()推测AVInputFormat
            return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                         s, 0, s->format_probesize);
        else if (s->iformat->flags & AVFMT_NOFILE)//如果有AVinputFormat,且flag是AVFMT_NOFILE
            av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "
                                      "will be ignored with AVFMT_NOFILE format.\n");
        return 0;
    }
	//如果已经指定了AVInputformat,且flag是AVFMT_NOFILE;
	//或者如果没有指定AVInputFormat,调用av_probe_input_format2(NULL,…)根据文件路径判断文件格式,来获取AVInputFormat
	//(注意)这里没有获取到AVIOContext  没有获取到AVIOContext就直接返回了?这里这样做是为了强调没有给函数提供输入数据,
	//此时函数仅仅通过文件路径来推测AVInputFormat
    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
        return score;
 	//如果AVIOContext和AVInputFormat都没有指定的话
	//调aviobuf.c中的avio_open函数来打开协议(打开文件),获取AVIOContext,如果获取失败了,直接返回
    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
        return ret;

    if (s->iformat)//如果协议打开有了AVInputforamt,则直接返回
        return 0;
	//解完协议,创建打开一个AVIOContext后
	//调用foramt.c中的av_probe_input_buffer2来探测获取AVInputFormat并返回探测分数
    return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                 s, 0, s->format_probesize);
}

init_input函数主要用来打开输入文件(协议),探测文件格式,返回的是探测文件所得的分数。

函数中做了一下判断:

1.如果已经有了AVIOContext,也就是协议已经打开了,但没有AVInputFormat,则使用av_probe_input_buffer2来推测AVInputFormat。

2.如果已经有了AVInputFormat,直接返回;如果没有指定AVInputFormat,调用av_probe_input_format2(NULL,…)根据文件路径判断文件格式(这里是没有获取到AVIOContext的,所以是根据路径来探测文件格式)。

3.如果AVIOContext和AVInputFormat都没有指定的话,调avio_open/aviobuf.函数来打开协议(打开文件),打开文件后(也就是获取一个AVIOContext后),调用av_probe_input_buffer2/foramt.c来探测获取AVInputFormat,av_probe_input_buffer2内部不断调用avio_read来读取数据,然后调用av_probe_input_format2来获取AVInputFormat,并返回探测分数。

接下来我们围绕三个判断来分别看一下流程实现。

先分析第二种情况,如果已经有了AVInputFormat,直接返回;如果没有指定AVInputFormat,调用av_probe_input_format2(NULL,…)根据文件路径判断文件格式(这里是没有获取到AVIOContext的,pb也是null,所以是根据路径来探测文件格式)。看下av_probe_input_format2(在第三种情况时内部也调用到了av_probe_input_format2函数)
 

/***
*将得到的匹配分数与要求的匹配值相比较,
*如果匹配分数>匹配值,这返回得到的解复用器,否则返回NULL
*pd:存储输入数据信息的AVProbeData结构体。
*is_opened:文件是否打开。
*score_max:判决AVInputFormat的门限值。只有某格式判决分数大于该门限值的时候,函数才会返回该封装格式,否则返回NULL。
**/
AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max)
{
    int score_ret;
    AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);
	//当av_probe_input_format3()返回的分数大于score_max的时候,
	//才会返回AVInputFormat,否则返回NULL。
    if (score_ret > *score_max) {
        *score_max = score_ret;
        return fmt;
    } else
        return NULL;
}

当av_probe_input_format3()返回的分数大于score_max的时候,才会返回AVInputFormat,否则返回NULL。下面跟踪函数跳转到av_probe_input_format3/format.c函数,再看这个函数前,我们先看下AVProbeData结构体,这个结构体主要是包含探测文件的data数据:
 

/**
 * This structure contains the data a format has to probe a file.
 * 这个结构体包含探测文件的data数据
 */
typedef struct AVProbeData {
    const char *filename;//文件路径
    unsigned char *buf; /**< buf存储用于推测AVInputFormat的媒体数据,其中buf可以为空,但是其后面无论如何都需要填充AVPROBE_PADDING_SIZE个0(AVPROBE_PADDING_SIZE取值为32,即32个0) Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
    int buf_size;       /**< 推测AVInputFormat的媒体数据的大小Size of buf except extra allocated bytes */
    const char *mime_type; /**<保存媒体的类型 mime_type, when known. */
} AVProbeData;

继续分析av_probe_input_format3/format.c函数,在第二种情况中,是没有获取到AVIOContext的,所以也没有获取到探测AVInputformat的媒体数据。

/**
*该函数遍历所有的解复用器,调用它们的read_probe()函数计算匹配得分,
*如果解复用器定义了文件扩展名,还会比较输匹配数据跟扩展名的匹配得分。
*函数最终返回计算找到的最匹配的解复用器,并将匹配分数也返回。
*根据输入数据查找合适的AVInputFormat。输入的数据位于AVProbeData中
**/
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,
                                      int *score_ret)
{
    AVProbeData lpd = *pd;//把输入数据赋值给lpd
    AVInputFormat *fmt1 = NULL, *fmt;
    int score, score_max = 0;
    const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];
    enum nodat {
        NO_ID3,
        ID3_ALMOST_GREATER_PROBE,
        ID3_GREATER_PROBE,
        ID3_GREATER_MAX_PROBE,
    } nodat = NO_ID3;

    if (!lpd.buf)
        lpd.buf = (unsigned char *) zerobuffer;

    if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
        int id3len = ff_id3v2_tag_len(lpd.buf);
        if (lpd.buf_size > id3len + 16) {
            if (lpd.buf_size < 2LL*id3len + 16)
                nodat = ID3_ALMOST_GREATER_PROBE;
            lpd.buf      += id3len;
            lpd.buf_size -= id3len;
        } else if (id3len >= PROBE_BUF_MAX) {
            nodat = ID3_GREATER_MAX_PROBE;
        } else
            nodat = ID3_GREATER_PROBE;
    }

    fmt = NULL;
	//该循环调用av_iformat_next()遍历FFmpeg中所有的AVInputFormat
    while ((fmt1 = av_iformat_next(fmt1))) {
        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
            continue;
        score = 0;
        if (fmt1->read_probe) {
			//调用demuxer中的read_probe来探测文件,获取匹配分数(这一方法如果结果匹配的话,一般会获得AVPROBE_SCORE_MAX的分值,即100分)
            score = fmt1->read_probe(&lpd);
            if (score)
                av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);
			//如果没有read_probe函数来探测文件,也就是不包含这个函数的话,
			//就使用av_match_ext()函数比较输入媒体的扩展名和AVInputFormat的扩展名是否匹配,
			//如果匹配的话,设定匹配分数为AVPROBE_SCORE_EXTENSION(AVPROBE_SCORE_EXTENSION取值为50,即50分)。
            if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
                switch (nodat) {
                case NO_ID3:
                    score = FFMAX(score, 1);
                    break;
                case ID3_GREATER_PROBE:
                case ID3_ALMOST_GREATER_PROBE:
                    score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
                    break;
                case ID3_GREATER_MAX_PROBE:
                    score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
                    break;
                }
            }
        } else if (fmt1->extensions) {
            if (av_match_ext(lpd.filename, fmt1->extensions))
                score = AVPROBE_SCORE_EXTENSION;
        }
		//使用av_match_name()比较输入媒体的mime_type和AVInputFormat的mime_type,
		//如果匹配的话,设定匹配分数为AVPROBE_SCORE_MIME(AVPROBE_SCORE_MIME取值为75,即75分)。
        if (av_match_name(lpd.mime_type, fmt1->mime_type)) {
            if (AVPROBE_SCORE_MIME > score) {
                av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME);
                score = AVPROBE_SCORE_MIME;
            }
        }
		//如果该AVInputFormat的匹配分数大于此前的最大匹配分数,
		//则记录当前的匹配分数为最大匹配分数,
		//并且记录当前的AVInputFormat为最佳匹配的AVInputFormat。
        if (score > score_max) {
            score_max = score;
            fmt       = fmt1;
        } else if (score == score_max)
            fmt = NULL;
    }
    if (nodat == ID3_GREATER_PROBE)
        score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
    *score_ret = score_max;

    return fmt;//返回获取最大分数值的AVInputFormat
}

根据输入数据来查找获取合适的AVInputFormat。输入的数据在AVProbeData中,在while循环中调用av_iformat_next来遍历FFMpeg中所有的AVInputFormat,并根据下面的规则来确定AVInputFormat和输入媒体数据的匹配分数。

1.如果AVInputFormat中包含read_probe(),就调用read_probe()函数获取匹配分数(这一方法如果结果匹配的话,一般会获得AVPROBE_SCORE_MAX的分值,即100分)。如果不包含该函数,就使用av_match_ext()函数比较输入媒体的扩展名和AVInputFormat的扩展名是否匹配,如果匹配的话,设定匹配分数为AVPROBE_SCORE_EXTENSION(AVPROBE_SCORE_EXTENSION取值为50,即50分)。

2.使用av_match_name()比较输入媒体的mime_type和AVInputFormat的mime_type,如果匹配的话,设定匹配分数为AVPROBE_SCORE_MIME(AVPROBE_SCORE_MIME取值为75,即75分)。

3.如果该AVInputFormat的匹配分数大于此前的最大匹配分数,则记录当前的匹配分数为最大匹配分数,并且记录当前的AVInputFormat为最佳匹配的AVInputFormat。

我们看下mov中read_probe/mov.c/libavformat函数的实现
 

/*
*文件探测函数
*/
static int mov_probe(AVProbeData *p)
{
    int64_t offset;
    uint32_t tag;
    int score = 0;
    int moov_offset = -1;

    /* check file header */
    offset = 0;
    for (;;) {
        /* ignore invalid offset */
        if ((offset + 8) > (unsigned int)p->buf_size)//如果探测数据buf大小小于8的话,直接退出,第一次探测传进来的默认是2048,可以看av_probe_input_buffer2/format.c
            break;
        tag = AV_RL32(p->buf + offset + 4);//第一次取的时候偏移为0,则从buf指针开始偏移4个字节,后面再次偏移时加上offset
        //如果第一次的话,则获得第5个数据到第8个数据,正常的mp4,也就是ftyp头了        
        switch(tag) {
        /* 检查tag,check for obvious tags */
        case MKTAG('m','o','o','v'):
            moov_offset = offset + 4;
        case MKTAG('m','d','a','t'):
        case MKTAG('p','n','o','t'): /* detect movs with preview pics like ew.mov and april.mov */
        case MKTAG('u','d','t','a'): /* Packet Video PVAuthor adds this and a lot of more junk */
        case MKTAG('f','t','y','p'):
            if (AV_RB32(p->buf+offset) < 8 &&
                (AV_RB32(p->buf+offset) != 1 ||
                 offset + 12 > (unsigned int)p->buf_size ||
                 AV_RB64(p->buf+offset + 8) == 0)) {
                score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
            } else if (tag == MKTAG('f','t','y','p') &&
                       (   AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ')//如果第8个字节到第12个字节是jp2_或jpx_,正常的mp4是isom
                        || AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ')
                    )) {
                score = FFMAX(score, 5);//打5分
            } else {//正常的mp4视频直接进入这里检测
				//hxk 添加测试
				if(AV_RL32(p->buf + offset + 8) == MKTAG('i','s','o','m')){//获取buf的第8个字节到第12个字节(offset是0)
					av_log(NULL, AV_LOG_ERROR, "hxk>>>>isom!\n");	  
				}
				//添加结束
                score = AVPROBE_SCORE_MAX;//100分
            }
            offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset;
            break;
        /* those are more common words, so rate then a bit less */
        case MKTAG('e','d','i','w'): /* xdcam files have reverted first tags */
        case MKTAG('w','i','d','e'):
        case MKTAG('f','r','e','e'):
        case MKTAG('j','u','n','k'):
        case MKTAG('p','i','c','t'):
            score  = FFMAX(score, AVPROBE_SCORE_MAX - 5);
            offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset;
            break;
        case MKTAG(0x82,0x82,0x7f,0x7d):
        case MKTAG('s','k','i','p'):
        case MKTAG('u','u','i','d'):
        case MKTAG('p','r','f','l'):
            /* if we only find those cause probedata is too small at least rate them */
            score  = FFMAX(score, AVPROBE_SCORE_EXTENSION);
            offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset;
            break;
        default:
            offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset;
        }
    }
    if(score > AVPROBE_SCORE_MAX - 50 && moov_offset != -1) {
        /* moov atom in the header - we should make sure that this is not a
         * MOV-packed MPEG-PS */
        offset = moov_offset;
        while(offset < (p->buf_size - 16)){ /* Sufficient space */
               /* We found an actual hdlr atom */
            if(AV_RL32(p->buf + offset     ) == MKTAG('h','d','l','r') &&
               AV_RL32(p->buf + offset +  8) == MKTAG('m','h','l','r') &&
               AV_RL32(p->buf + offset + 12) == MKTAG('M','P','E','G')){
                av_log(NULL, AV_LOG_WARNING, "Found media data tag MPEG indicating this is a MOV-packed MPEG-PS.\n");
                /* We found a media handler reference atom describing an
                 * MPEG-PS-in-MOV, return a
                 * low score to force expanding the probe window until
                 * mpegps_probe finds what it needs */
                return 5;
            }else
                /* Keep looking */
                offset+=2;
        }
    }

    return score;
}

从上面可以知道,mov的探测文件主要是从AVProbeData的探测数据buf中前4个字节开始,获取ftyp box,当查找到ftyp box后再处理一些其他的判断,然后直接返回AVPROBE_SCORE_MAX(100分)。

这里的AVProbeData 第一次探测传进来的默认是2048个字节,pb->size = 2048,具体实现逻辑在av_probe_input_buffer2/format.c中,不断地读取数据,知道读取最大的探测数据(1M大小这样).

接下来我们看下av_probe_input_buffer2/format.c函数,这里是通过io_open打开文件,获取到AVIOContext后再调用av_probe_input_buffer2函数来探测AVInputFormat(这里的AVProbeData是有数据的)。
 

/**
*	
*文件探测
*pb:用于读取数据的AVIOContext。
*fmt:输出推测出来的AVInputFormat。
*filename:输入媒体的路径。
*logctx:日志(没有研究过)。
*offset:开始推测AVInputFormat的偏移量。
*max_probe_size:用于推测格式的媒体数据的最大值。
av_probe_input_buffer2()首先需要确定用于推测格式的媒体数据的最大值max_probe_size。
max_probe_size默认为PROBE_BUF_MAX(PROBE_BUF_MAX取值为1 << 20,即1048576Byte,大约1MB)

在确定了max_probe_size之后,函数就会进入到一个循环中,
调用avio_read()读取数据并且使用av_probe_input_format2()(该函数前文已经记录过)
推测文件格式。
肯定有人会奇怪这里为什么要使用一个循环,
而不是只运行一次?其实这个循环是一个逐渐增加输入媒体数据量的过程。
av_probe_input_buffer2()并不是一次性读取max_probe_size字节的媒体数据,
我个人感觉可能是因为这样做不是很经济,
因为推测大部分媒体格式根本用不到1MB这么多的媒体数据。
因此函数中使用一个probe_size存储需要读取的字节数,
并且随着循环次数的增加逐渐增加这个值。
函数首先从PROBE_BUF_MIN(取值为2048)个字节开始读取,
如果通过这些数据已经可以推测出AVInputFormat,
那么就可以直接退出循环了(参考for循环的判断条件“!*fmt”);
如果没有推测出来,就增加probe_size的量为过去的2倍(参考for循环的表达式“probe_size << 1”),
继续推测AVInputFormat;如果一直读取到max_probe_size字节的数据依然没能确定AVInputFormat,
则会退出循环并且返回错误信息

**/
int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt,
                          const char *filename, void *logctx,
                          unsigned int offset, unsigned int max_probe_size)
{
    AVProbeData pd = { filename ? filename : "" };
    uint8_t *buf = NULL;
    int ret = 0, probe_size, buf_offset = 0;
    int score = 0;
    int ret2;
	//探测文件的字节数,取自AVFormatContext
    if (!max_probe_size)//如果没有探测字节数
        max_probe_size = PROBE_BUF_MAX;//设置最大值到max_probe_size中,大约1M
    else if (max_probe_size < PROBE_BUF_MIN) {//如果最大的探测字节数小于2048个,直接返回error
        av_log(logctx, AV_LOG_ERROR,
               "Specified probe size value %u cannot be < %u\n", max_probe_size, PROBE_BUF_MIN);
        return AVERROR(EINVAL);
    }

    if (offset >= max_probe_size)//如果偏移大于最大探测字节数,刚传进来的offset是0
        return AVERROR(EINVAL);

    if (pb->av_class) {
        uint8_t *mime_type_opt = NULL;
        char *semi;
        av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);
        pd.mime_type = (const char *)mime_type_opt;//赋值mime_type到probedata中
        av_log(NULL, AV_LOG_ERROR,"hxk>>>>mime_type:%s\n",pd.mime_type);
        semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL;
        if (semi) {
            *semi = '\0';
        }
    }
#if 0
    if (!*fmt && pb->av_class && av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type) >= 0 && mime_type) {
        if (!av_strcasecmp(mime_type, "audio/aacp")) {
            *fmt = av_find_input_format("aac");
        }
        av_freep(&mime_type);
    }
#endif
//for循环处理探测数据
//函数首先从PROBE_BUF_MIN(取值为2048)个字节开始读取;probe_size小于最大探测值,且没有fmt
//每一次for循环probe_size的值增加一倍
    for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt;
         probe_size = FFMIN(probe_size << 1,
                            FFMAX(max_probe_size, probe_size + 1))) {//比较probe_size+1与探测最大值的最大值,然后取探测值的2倍与最大值做比较
        //如果探测值小于最大探测值(约1m)     打分25分
        score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;

        /* 为探测数据buf申请内存空间Read probe data. */
        if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)
            goto fail;
		//从pb(AVIOContext)中读取探测数据buf
        if ((ret = avio_read(pb, buf + buf_offset,
                             probe_size - buf_offset)) < 0) {
            /* Fail if error was not end of file, otherwise, lower score. */
            if (ret != AVERROR_EOF)
                goto fail;

            score = 0;
            ret   = 0;          /* error was end of file, nothing read */
        }
        buf_offset += ret;
        if (buf_offset < offset)
            continue;
        pd.buf_size = buf_offset - offset;
        pd.buf = &buf[offset];

        memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);

        /* 将上面读取到的pd(AVProbedata)传入到av_probe_input_format2函数
           猜文件格式,探测合适的解复用器Guess file format. */
        *fmt = av_probe_input_format2(&pd, 1, &score);
        if (*fmt) {//获取fmt
            /* This can only be true in the last iteration. */
            if (score <= AVPROBE_SCORE_RETRY) {//如果分数小于25分
                av_log(logctx, AV_LOG_WARNING,
                       "Format %s detected only with low score of %d, "
                       "misdetection possible!\n", (*fmt)->name, score);
            } else
                av_log(logctx, AV_LOG_DEBUG,
                       "Format %s probed with size=%d and score=%d\n",
                       (*fmt)->name, probe_size, score);
#if 0
            FILE *f = fopen("probestat.tmp", "ab");
            fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename);
            fclose(f);
#endif
        }
    }

    if (!*fmt)//for循环没有获取到AVInputFormat,返回数据错误
        ret = AVERROR_INVALIDDATA;

fail:
    /* 将探测数据返回给AVIOContext的缓冲buffer,Rewind. Reuse probe buffer to avoid seeking. */
    ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset);
    if (ret >= 0)
        ret = ret2;

    av_freep(&pd.mime_type);
    return ret < 0 ? ret : score;
}


从上面可以看到,在确定了max_probe_size之后(第一次探测的数据大小是2048个字节),函数就会进入到一个循环中,调用avio_read()读取数据并且使用av_probe_input_format2()推测文件格式(这里传进去的AVProbeData是有数据的)。

肯定有人会奇怪这里为什么要使用一个循环,
而不是只运行一次?其实这个循环是一个逐渐增加输入媒体数据量的过程。

av_probe_input_buffer2()并不是一次性读取max_probe_size字节的媒体数据,
我个人感觉可能是因为这样做不是很经济,因为推测大部分媒体格式根本用不到1MB这么多的媒体数据。因此函数中使用一个probe_size存储需要读取的字节数,并且随着循环次数的增加逐渐增加这个值。
函数首先从PROBE_BUF_MIN(取值为2048)个字节开始读取,如果通过这些数据已经可以推测出AVInputFormat,那么就可以直接退出循环了(参考for循环的判断条件“!*fmt”);如果没有推测出来,就增加probe_size的量为过去的2倍(参考for循环的表达式“probe_size<<1”),继续推测AVInputFormat;如果一直读取到max_probe_size字节的数据依然没能确定AVInputFormat,则会退出循环并且返回错误信息。

在调用完init_input()完成基本的初始化并且推测得到相应的AVInputFormat之后,avformat_open_input()会调用AVInputFormat的read_header()方法读取媒体文件的文件头并且完成相关的初始化工作。read_header()是一个位于AVInputFormat结构体中的一个函数指针,对于不同的封装格式,会调用不同的read_header()的实现函数。这里还是以mov来举例。由于mov的read_header代码以及相关细节比较多,关于mov的read_header的具体分析见《ffmpeg-mov格式与分离器实现详解》。

本文含有隐藏内容,请 开通VIP 后查看