ffmpeg源码分析(1)

/**
 * 打开文件流,读取头,codecs此时并没有打开。
 * 这个流必须调用avformat_close_input().
 * @param ps AVFormatContext *ps = NULL ,该参数应该用(&ps)
 * @param url 要打开的URL.
 * @param fmt 如果不是NULL,则将打开指定的格式的媒体文件,
               否则将进行自动探测文件封装格式。
 * @param options 一个字典,内容填充为AVFormatContext
                  和demuxer-private 选项。是一个输出参数                 
 *
 * @return 成功返回0,失败返回一个负数错误码( AVERROR)
 *
 * @note 如果你想要自定义IO, 请预先分配 format context 同时设置 pb field
 */
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);

该函数第一步是调用avformat_alloc_context创建一个AVFormatContext对象,同时为这个对象进行部分初始化。

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;

    //1.创建一个`AVFormatContext`对象,同时进行部分初始化
    if (!s && !(s = avformat_alloc_context()))
        return AVERROR(ENOMEM);
    
    //在1步对象创建的时候,已经进行赋值了。
    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;
    //如果指定了附加参数,则将参数复制到tmp中
    if (options)
        av_dict_copy(&tmp, *options, 0);

    //pb是AVIOContext对象,这里如果不是null,则表示使用的是自定义IO的方式
    //该值不为null,则表示*ps不是一个null对象,是提前赋值好在传递进来的。
    if (s->pb)
        s->flags |= AVFMT_FLAG_CUSTOM_IO;
    //使用tmp里的参数对AVFormatContext *s这个对象进行赋值
    if ((ret = av_opt_set_dict(s, &tmp)) < 0)
        goto fail;
    
    //赋值filename里的内容到一个新的内存中,同时返回这片内存的地址
    //说白了就是赋值s->url
    if (!(s->url = av_strdup(filename ? filename : ""))) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }
    
    //如果定义了FF_API_FORMAT_FILENAME,则赋值filename
    #if FF_API_FORMAT_FILENAME,则赋值
    FF_DISABLE_DEPRECATION_WARNINGS
    av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
    FF_ENABLE_DEPRECATION_WARNINGS
#endif

上面的代码是对AVFormatContext *s这个对象进行部分初始化 ,紧接着调用init_input函数打开文件:

在探究init_input之前,我们需要先了解几个准备的数据结构和准备函数:

//探测文件格式数据的结构体
typedef struct AVProbeData {
    //文件名
    const char *filename;
    //数据域,存放用于探测的数据,这个buffer必须要额外分配32个被零填充的字节
    unsigned char *buf; 
    //除去额外分配的字节后的buf大小
    int buf_size;
    //mimeType ,当时位置文件格式时值为"*/"
    const char *mime_type; 
}
/**
* 探测输入文件的格式信息
*/
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);
    //如果探测结果大于输入的分数,则返回探测分数,同时返回探测结果
    //否则,返回NULL
    if (score_ret > *score_max) {
        *score_max = score_ret;
        return fmt;
    } else
        return NULL;
}
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,
                                      int *score_ret)
{
    //探测数据结构体
    AVProbeData lpd = *pd;
    const AVInputFormat *fmt1 = NULL;
    AVInputFormat *fmt = NULL;
    int score, score_max = 0;
    void *i = 0;
    
    //分配的额外32个字节的数据
    //AVPROBE_PADDING_SIZE探测填充数据大小:32
    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;

    //如果lpd.buf为null,赋值额外的空数据
    if (!lpd.buf)
        lpd.buf = (unsigned char *) zerobuffer;

    //因为Id3v2在文件的首部顺序记录10个字节的ID3V2.3的头部。
    //因此当探测数据满足10字节时,判断是否满足ID3V2
    if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
        //id3的总长度:包含了10个字节的头
        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;
            //实际数据大小减去id3标签长度
            lpd.buf_size -= id3len;
        } else if (id3len >= PROBE_BUF_MAX) { 
            //id3的长度大于设定的最大探测缓冲大小
            nodat = ID3_GREATER_MAX_PROBE;
        } else
            nodat = ID3_GREATER_PROBE;
    }

    //遍历查找封装格式
    while ((fmt1 = av_demuxer_iterate(&i))) {
        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
            continue;
        
        score = 0;
        //探测函数回调不为空
        if (fmt1->read_probe) {
            //开始探测数据,并返回得分
            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);
            //匹配扩展名,计算得分
            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) { //回调为NULL
            //直接依据扩展名计算得分
            if (av_match_ext(lpd.filename, fmt1->extensions))
                score = AVPROBE_SCORE_EXTENSION;
        }
        //依据mime匹配程度计算得分
        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;
            }
        }
        
        //赋值最佳匹配结果
        if (score > score_max) {
            score_max = score;
            fmt       = (AVInputFormat*)fmt1;
        } else if (score == score_max)//出现多次相同的最佳匹配,无法确定返回NULL
            fmt = NULL;
    } //循环结束
    
    if (nodat == ID3_GREATER_PROBE)
        score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
    *score_ret = score_max;

    return fmt;
}
//打开文件,同时同时探测文件格式
static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    int ret;
    //初始化探测数据结构体
    AVProbeData pd = { filename, NULL, 0 };
    
    //默认探测得分:该数值为100/4 = 25;
    int score = AVPROBE_SCORE_RETRY;
    
    //判断是否是自定义IO的方式,这里我们暂时不讨论
    if (s->pb) {
        s->flags |= AVFMT_FLAG_CUSTOM_IO;
        if (!s->iformat)
            return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                         s, 0, s->format_probesize);
        else if (s->iformat->flags & AVFMT_NOFILE)
            av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "
                                      "will be ignored with AVFMT_NOFILE format.\n");
        return 0;
    }

    //如果之前指定过文件格式,并且改格式是非文件格式,则直接返回score
    //如果没有指定文件格式,则自动探测文件格式。
    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
        return score;
    
    //没有发现文件格式,则打开文件
    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
        return ret;
    //找到封装格式,直接返回成功
    if (s->iformat)
        return 0;
    //没有找到封装格式,则根据打开的文件流里的探测数据,重新进行探测
    return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                 s, 0, s->format_probesize);
}

init_input函数还有两个很重要的没有分析:io_open和av_probe_input_buffer2。

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