美文网首页音视频开发音视频专题
ffmpeg源码分析3-init_input

ffmpeg源码分析3-init_input

作者: PuiKei | 来源:发表于2018-10-17 19:55 被阅读261次

    本文参考雷神博文整理:https://blog.csdn.net/leixiaohua1020
    init_input它的主要工作就是打开输入的视频数据并且探测视频的格式。该函数的定义位于libavformat\utils.c,整体调用结构如下(注:目前分析的版本是4.0.2,图中函数调用名称可能跟旧版本不对应)。

    1425470637_9894.jpg

    init_input()

    /* 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;
    //当使用了自定义的AVIOContext的时候(AVFormatContext中的AVIOContext不为空,即
    //s->pb!=NULL),如果指定了AVInputFormat就直接返回,如果没有指定就
    //调用av_probe_input_buffer2()推测AVInputFormat。这一情况出现的不算很多,但是当我们
    //从内存中读取数据的时候(需要初始化自定义的AVIOContext),就会执行这一步骤。
        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;
        }
    //在更一般的情况下,如果已经指定了AVInputFormat,就直接返回;如果没有
    //指定AVInputFormat,就调用av_probe_input_format(NULL,…)根据文件路径判断文件格式。
    //这里特意把av_probe_input_format()的第1个参数写成“NULL”,是为了强调这个时候实际上
    //并没有给函数提供输入数据,此时仅仅通过文件路径推测AVInputFormat。
    
        if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
            (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
            return score;
    //如果发现通过文件路径判断不出来文件格式,那么就需要打开文件探测文件格式了,这个
    //时候会首先调用io_open打开文件,io_open在avformat_alloc_context()----->avformat_get_context_defaults(ic)----->s->io_open  = io_open_default赋值。
    //然后调用av_probe_input_buffer2()推测AVInputFormat。
        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);
    }
    

    下面分析一下av_probe_input_format2(),s->io_open(),av_probe_input_buffer2()这几个函数

    av_probe_input_format2()

    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);
        if (score_ret > *score_max) {
            *score_max = score_ret;
            return fmt;
        } else
            return NULL;
    }
    

    该函数用于根据输入数据查找合适的AVInputFormat。参数含义如下所示:
    pd:存储输入数据信息的AVProbeData结构体。
    is_opened:文件是否打开。
    score_max:判决AVInputFormat的门限值。只有某格式判决分数大于该门限值的时候,函数才会返回该封装格式,否则返回NULL。

    该函数中涉及到一个结构体AVProbeData,用于存储输入文件的一些信息,它的定义如下所示。

    /**
     * This structure contains the data a format has to probe a file.
     */
    typedef struct AVProbeData {
        const char *filename;
        unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
        int buf_size;       /**< Size of buf except extra allocated bytes */
        const char *mime_type; /**< mime_type, when known. */
    } AVProbeData;
    

    av_probe_input_format3()

    av_probe_input_format3()是一个API函数,声明位于libavformat\avformat.h,如下所示。

    /**
     * Guess the file format.
     *
     * @param is_opened Whether the file is already opened; determines whether
     *                  demuxers with or without AVFMT_NOFILE are probed.
     * @param score_ret The score of the best detection.
     */
    AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret);
    

    从函数声明中可以看出,av_probe_input_format3()和av_probe_input_format2()的区别是函数的第3个参数不同:av_probe_input_format2()是一个分数的门限值,而av_probe_input_format3()是一个探测后的最匹配的格式的分数值。

    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;
        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;
    //id3相关的,这里先不管。MP3文件才有的
        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;
        }
    
        while ((fmt1 = av_demuxer_iterate(&i))) {
            if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
                continue;
            score = 0;
    //如果AVInputFormat中包含read_probe(),就调用read_probe()函数获取匹配分数(这一方法
    //如果结果匹配的话,一般会获得AVPROBE_SCORE_MAX的分值,即100分)
            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) {
    //如果不包含该函数,就使用av_match_ext()函数比较输入媒体的扩展名和AVInputFormat的
    //扩展名是否匹配,如果匹配的话,设定匹配分数
    //为AVPROBE_SCORE_EXTENSION(AVPROBE_SCORE_EXTENSION取值为50,即50分)。
                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;
                }
            }
            if (score > score_max) {
                score_max = score;
                fmt       = (AVInputFormat*)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;
    }
    

    av_probe_input_format3()根据输入数据查找合适的AVInputFormat。输入的数据位于AVProbeData中。前文已经提到过,其中filename是文件路径, buf存储用于推测AVInputFormat的媒体数据,最后还有个mime_type保存媒体的类型。其中buf可以为空,但是其后面无论如何都需要填充AVPROBE_PADDING_SIZE个0(AVPROBE_PADDING_SIZE取值为32,即32个0)。

    typedef struct AVProbeData {
        const char *filename;
        unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
        int buf_size;       /**< Size of buf except extra allocated bytes */
        const char *mime_type; /**< mime_type, when known. */
    } AVProbeData;
    

    该函数最主要的部分是一个循环。该循环调用av_demuxer_iterate()遍历FFmpeg中所有的AVInputFormat,并根据以下规则确定AVInputFormat和输入媒体数据的匹配分数(score,反应匹配程度)


    (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。


    上述过程中涉及到以下几个知识点:

    AVInputFormat->read_probe()

    AVInputFormat中包含read_probe()是用于获得匹配函数的函数指针,不同的封装格式包含不同的实现函数。位于libavformat\目录下的flvdec.c,movdec.c等。
    此函数作用一般是从文件中获取文件头的数据来匹配。例如文件头包含FLV或者MOOV这些这段表明是flv文件或者mov文件等。

    av_match_ext()

    int av_match_ext(const char *filename, const char *extensions)
    {
        const char *ext;
     
        if (!filename)
            return 0;
     //该函数返回 str 中最后一次出现字符 c 的位置。如果未找到该值,则函数返回一个空指针。
    //例如返回“.mov”
        ext = strrchr(filename, '.');
    //av_match_name用于比较两个格式的名称。简单地说就是比较字符串。注意该函数的字符串是
    //不区分大小写的,字符都转换为小写进行比较。
        if (ext)
            return av_match_name(ext + 1, extensions);
        return 0;
    }
    
    av_match_name()
    int av_match_name(const char *name, const char *names)
    {
        const char *p;
        int len, namelen;
    
        if (!name || !names)
            return 0;
    //strlen()用来计算指定的字符串s 的长度,不包括结束字符"\0"
        namelen = strlen(name);
        while (*names) {
            int negate = '-' == *names;
    //查找字符串names中首次出现','字符的位置
            p = strchr(names, ',');
            if (!p)
                p = names + strlen(names);
            names += negate;
            len = FFMAX(p - names, namelen);
    //比较name跟names的len长度,先转成小写来比较,相等的话返回0
    //整个函数return !negate;
            if (!av_strncasecmp(name, names, len) || !strncmp("ALL", names, FFMAX(3, p - names)))
                return !negate;
            names = p + (*p == ',');
        }
        return 0;
    }
    

    上述函数还有一点需要注意,其中使用了一个while()循环,用于搜索“,”。这是因为FFmpeg中有些格式是对应多种格式名称的,例如MKV格式的解复用器(Demuxer)的定义如下。

    AVInputFormat ff_matroska_demuxer = {
        .name           = "matroska,webm",
        .long_name      = NULL_IF_CONFIG_SMALL("Matroska / WebM"),
        .extensions     = "mkv,mk3d,mka,mks",
        .priv_data_size = sizeof(MatroskaDemuxContext),
        .read_probe     = matroska_probe,
        .read_header    = matroska_read_header,
        .read_packet    = matroska_read_packet,
        .read_close     = matroska_read_close,
        .read_seek      = matroska_read_seek,
        .mime_type      = "audio/webm,audio/x-matroska,video/webm,video/x-matroska"
    };
    

    从代码可以看出,ff_matroska_demuxer中的name字段对应“matroska,webm”,mime_type字段对应“audio/webm,audio/x-matroska,video/webm,video/x-matroska”。av_match_name()函数对于这样的字符串,会把它按照“,”截断成一个个的名称,然后一一进行比较。



    io_open->io_open_default

    在avformat_alloc_context()----->avformat_get_context_defaults(ic)----->s->io_open = io_open_default赋值.此函数后续再看,打开本地文件或者URL基本都是进入这里识别出AVInputFormat 跟AVIOContext。
    ffmpeg源码分析4-io_open_default



    av_probe_input_buffer2()

    av_probe_input_buffer2()是一个API函数,它根据输入的媒体数据推测该媒体数据的AVInputFormat,声明位于libavformat\avformat.h,如下所示。

    /**
     * Probe a bytestream to determine the input format. Each time a probe returns
     * with a score that is too low, the probe buffer size is increased and another
     * attempt is made. When the maximum probe size is reached, the input format
     * with the highest score is returned.
     *
     * @param pb the bytestream to probe
     * @param fmt the input format is put here
     * @param filename the filename of the stream
     * @param logctx the log context
     * @param offset the offset within the bytestream to probe from
     * @param max_probe_size the maximum probe buffer size (zero for default)
     * @return the score in case of success, a negative value corresponding to an
     *         the maximal score is AVPROBE_SCORE_MAX
     * AVERROR code otherwise
     */
    int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt,
                               const char *filename, void *logctx,
                               unsigned int offset, unsigned int max_probe_size);
    

    av_probe_input_buffer2()参数的含义如下所示:
    pb:用于读取数据的AVIOContext。
    fmt:输出推测出来的AVInputFormat。
    filename:输入媒体的路径。
    logctx:日志(没有研究过)。
    offset:开始推测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;
    
        if (!max_probe_size)
            max_probe_size = PROBE_BUF_MAX;
        else if (max_probe_size < PROBE_BUF_MIN) {
            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)
            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;
            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_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt;
             probe_size = FFMIN(probe_size << 1,
                                FFMAX(max_probe_size, probe_size + 1))) {
            score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;
    
            /* Read probe data. */
            if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)
                goto fail;
            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);
    
            /* Guess file format. */
            *fmt = av_probe_input_format2(&pd, 1, &score);
            if (*fmt) {
                /* This can only be true in the last iteration. */
                if (score <= AVPROBE_SCORE_RETRY) {
                    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)
            ret = AVERROR_INVALIDDATA;
    
    fail:
        /* 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;
    }
    

    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,则会退出循环并且返回错误信息。
    这个函数的用法是从内存中读取数据并侦测数据的格式,例如Winpcap抓取网络上的RTP包,打算直接送给ffmpeg进行解码。一直没能找到合适的方法。因为抓取的数据包是存在内存中的,所以无法传递给avformat_open_input()函数其路径(根本没有路径= =)。当然也可以将抓取的数据报存成文件,然后用ffmpeg打开这个文件,但是这样的话,程序的就太难控制了。

    后来经过分析ffmpeg的源代码,发现其竟然是可以从内存中读取数据的,代码很简单,如下所示:

    FILE *fp_open;
     
    //读取数据的回调函数-------------------------
    //AVIOContext使用的回调函数!
    //注意:返回值是读取的字节数
    //手动初始化AVIOContext只需要两个东西:内容来源的buffer,和读取这个Buffer到FFmpeg中的函数
    //回调函数,功能就是:把buf_size字节数据送入buf即可
    //第一个参数(void *opaque)一般情况下可以不用
    int fill_iobuffer(void * opaque,uint8_t *buf, int bufsize){
        if(!feof(fp_open)){
            int true_size=fread(buf,1,buf_size,fp_open);
            return true_size;
        }else{
            return -1;
        }
    }
    int main(){
        ...
        fp_open=fopen("test.h264","rb+");
        AVFormatContext *ic = NULL;
        ic = avformat_alloc_context();
        unsigned char * iobuffer=(unsigned char *)av_malloc(32768);
        AVIOContext *avio =avio_alloc_context(iobuffer, 32768,0,NULL,fill_iobuffer,NULL,NULL);
        ic->pb=avio;
        err = avformat_open_input(&ic, "nothing", NULL, NULL);
        ...//解码
    }
    

    关键要在avformat_open_input()之前初始化一个AVIOContext,而且将原本的AVFormatContext的指针pb(AVIOContext类型)指向这个自行初始化AVIOContext。当自行指定了AVIOContext之后,avformat_open_input()里面的URL参数就不起作用了。示例代码开辟了一块空间iobuffer作为AVIOContext的缓存。
    fill_iobuffer则是将数据读取至iobuffer的回调函数。fill_iobuffer()形式(参数,返回值)是固定的,是一个回调函数,如下所示(只是个例子,具体怎么读取数据可以自行设计)。示例中回调函数将文件中的内容通过fread()读入内存。



    相关文章

      网友评论

        本文标题:ffmpeg源码分析3-init_input

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