ffmpeg源代码分析之avformat_open_input

运行avformat_open_input函数是ffmpeg初始化之后做的第一件事。从大面意思上来讲,函数作用是打开输入流,但是流具体是如何打开的,具体做了哪些事情,我们慢慢来分析研究下:

<pre name="code" class="cpp">int avformat_open_input(AVFormatContext **ps, const char *filename,
                        AVInputFormat *fmt, AVDictionary **options)
{
    AVFormatContext *s = *ps;
    int ret = 0;
    AVDictionary *tmp = NULL;
    ID3v2ExtraMeta *id3v2_extra_meta = NULL;//保存MP3的一些信息,比如作者,风格,年代什么的

    //如果s没有初始化,则创建一个
    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);
    }
    //如果指定了fmt,直接进行赋值
    if (fmt)
        s->iformat = fmt;

    if (options)
        av_dict_copy(&tmp, *options, 0);

    if ((ret = av_opt_set_dict(s, &tmp)) < 0)//没有设置option参数,该步可以跳过
        goto fail;
   
    //探测输入流的格式是此步实现的。
    if ((ret = init_input(s, filename, &tmp)) < 0)
        goto fail;
.....
}

 
 
/* 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 };//使用AVProbeData进行探测,里面的参数是文件名
    int score = AVPROBE_SCORE_RETRY;

    //调用者已经指定了Pb(一般不会这么做),除非自己去实现让ffmpeg去内存中获取数据
    if (s->pb) {                                          //AVFormatContext中的AVIOContext *pb是否已经初始化,里面保存着对底层数据的读写操作。
        s->flags |= AVFMT_FLAG_CUSTOM_IO;                 //如果我们自己指定了pb,会执行到这,此处代表自己的IO
        if (!s->iformat)                                  //如果iformat没有指定,需要进行iformat探测和文件格式探测
            //指定了pb但是没指定iformat,以pb读取媒体数据进行探测     
            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;
    }

    /*常规来讲会执行到这儿,首先如果指定了iformat,并且不需要文件,直接就返回
    *如果没有指定iformat,就根据文件后缀猜测iformat
    */
    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||                   
        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
        return score;

    //如果从文件名中也猜测不错媒体格式,只能进行文件探测了,先打开文件
    if ((ret = avio_open2(&s->pb, filename, AVIO_FLAG_READ | s->avio_flags,
                          &s->interrupt_callback, options)) < 0)
        return ret;
    if (s->iformat)
        return 0;
    return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                 s, 0, s->format_probesize);
}


ffmpeg根据文件后缀探测媒体格式的步骤如下:

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 *av_probe_input_format3(AVProbeData *pd, int is_opened,
                                      int *score_ret)
{
    AVProbeData lpd = *pd;
    AVInputFormat *fmt1 = NULL, *fmt;
    int score, nodat = 0, score_max = 0;
    const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];//32

    if (!lpd.buf)
        lpd.buf = 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) {
            lpd.buf      += id3len;
            lpd.buf_size -= id3len;
        } else if (id3len >= PROBE_BUF_MAX) {
            nodat = 2;
        } else
            nodat = 1;
    }

    fmt = NULL;
    while ((fmt1 = av_iformat_next(fmt1))) {//循环获取所有的注册格式,格式是在初始化的时候注册的
        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE))
            continue;
        score = 0;
        if (fmt1->read_probe) {
            score = fmt1->read_probe(&lpd);
            if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
                if      (nodat == 0) score = FFMAX(score, 1);
                else if (nodat == 1) score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
                else                 score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
            }
        } else if (fmt1->extensions) {
            if (av_match_ext(lpd.filename, fmt1->extensions))
                score = AVPROBE_SCORE_EXTENSION;
        }
        if (score > score_max) {
            score_max = score;
            fmt       = fmt1;
        } else if (score == score_max)
            fmt = NULL;
    }
    if (nodat == 1)
        score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
    *score_ret = score_max;

    return fmt;
}

读文件获取文件格式信息:

int avio_open2(AVIOContext **s, const char *filename, int flags,
               const AVIOInterruptCB *int_cb, AVDictionary **options)//第三个参数是中断回调机制,以后再讲,此处可以不用理解其意义
{
    URLContext *h;
    int err;

    err = ffurl_open(&h, filename, flags, int_cb, options);//根据文件名称查找对应的协议
    if (err < 0)
        return err;
    err = ffio_fdopen(s, h);//调用ffio_init_context将url中的ffurl_read,ffurl_write,ffurl_seek注册到AVIOContext中
    if (err < 0) {
        ffurl_close(h);
        return err;
    }
    return 0;
}


//下面函数的作用是根据url/filename名称,找到对应的URLProtocol操作集,比如该文件是本地文件或者是网络流等等
int ffurl_open(URLContext **puc, const char *filename, int flags,
               const AVIOInterruptCB *int_cb, AVDictionary **options)
{
    int ret = ffurl_alloc(puc, filename, flags, int_cb);//根据文件名判断协议,如果是文件的话,那就是file协议
    if (ret < 0)
        return ret;
    if (options && (*puc)->prot->priv_data_class &&
        (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)
        goto fail;
    if ((ret = av_opt_set_dict(*puc, options)) < 0)
        goto fail;
    ret = ffurl_connect(*puc, options);//打开具体的协议,比如file
    if (!ret)
        return 0;
fail:
    ffurl_close(*puc);
    *puc = NULL;
    return ret;
}


int ffurl_alloc(URLContext **puc, const char *filename, int flags,
                const AVIOInterruptCB *int_cb)
{
    URLProtocol *p = NULL;

    if (!first_protocol) {//注册协议链表
        av_log(NULL, AV_LOG_WARNING, "No URL Protocols are registered. "
                                     "Missing call to av_register_all()?\n");
    }

    p = url_find_protocol(filename);//根据文件名称判断
    if (p)
       return url_alloc_for_protocol(puc, p, filename, flags, int_cb);

    *puc = NULL;
    if (av_strstart("https:", filename, NULL))
        av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile with openssl or gnutls enabled.\n");
    return AVERROR_PROTOCOL_NOT_FOUND;
}




//探测流格式,在AVIOContext *pb已经指定的情况下调用!
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;
    uint8_t *mime_type;
    int ret = 0, probe_size, buf_offset = 0;
    int score = 0;

    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 (!*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);
    }

    //循环探测
    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)//为buf分配内存空间
            return ret;

	//使用avio_read()读取一段数据
        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) {
                av_free(buf);
                return ret;
            }
            score = 0;
            ret   = 0;          /* error was end of file, nothing read */
        }
        buf_offset += ret;//偏移量指向数据的结尾
        if (buf_offset < 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) {
        av_free(buf);
        return AVERROR_INVALIDDATA;
    }

    /* Rewind. Reuse probe buffer to avoid seeking. */
    ret = ffio_rewind_with_probe_data(pb, &buf, buf_offset);

    return ret < 0 ? ret : score;
}

/*可以看出,上面探测的步骤为:
 1.分配内存
 2.读取数据
 3.探测格式
下面是如何读取数据
*/
int avio_read(AVIOContext *s, unsigned char *buf, int size)
{
    int len, size1;

    size1 = size;
    while (size > 0) {
        len = s->buf_end - s->buf_ptr;//AVIOContext的剩余buf的长度
        if (len > size)
            len = size;
        if (len == 0 || s->write_flag) {//若没有剩余的数据
            if((s->direct || size > s->buffer_size) && !s->update_checksum){
                if(s->read_packet)
                    len = s->read_packet(s->opaque, buf, size);
                if (len <= 0) {
                    /* do not modify buffer if EOF reached so that a seek back can
                    be done without rereading data */
                    s->eof_reached = 1;
                    if(len<0)
                        s->error= len;
                    break;
                } else {
                    s->pos += len;
                    s->bytes_read += len;
                    size -= len;
                    buf += len;
                    s->buf_ptr = s->buffer;
                    s->buf_end = s->buffer/* + len*/;
                }
            } else {
                fill_buffer(s);
                len = s->buf_end - s->buf_ptr;
                if (len == 0)
                    break;
            }
        } else {
            memcpy(buf, s->buf_ptr, len);
            buf += len;
            s->buf_ptr += len;
            size -= len;
        }
    }
    if (size1 == size) {
        if (s->error)      return s->error;
        if (url_feof(s))   return AVERROR_EOF;
    }
    return size1 - size;
}



你可能感兴趣的:(ffmpeg)