FFmpeg学习 avformat_open_input()函数分析

前言

本文分析avformat_open_input函数,该函数在libavformat包下。

/**
 * Open an input stream and read the header. 
 * The codecs are not opened.
 * The stream must be closed with avformat_close_input().
 *
 * @param ps Pointer to user-supplied AVFormatContext.
 * @param options  A dictionary filled with AVFormatContext and demuxer-private options.
 * 
 * @param url URL of the stream to open.
 * 
 * @param fmt If non-NULL, this parameter forces a specific input format.
 *            Otherwise the format is autodetected.
 *
 * @return 0 on success, a negative AVERROR on failure.
 * @note If you want to use custom IO, preallocate the format context and set its pb field.
 */
int avformat_open_input(AVFormatContext **ps, const char *url, 
						ff_const59 AVInputFormat *fmt, AVDictionary **options);												

通读avformat_open_input,主要功能是:
根据传入的url确定了要使用的协议URLProtocol,比如http的或是file类型的协议;
然后按该协议打开文件或建立连接,循环从2048byte大小2的幂次递增开始读取数据,
然后再遍历所有的AVInputFormat,确定iformat,即确定是个什么格式的数据,flv啊还是mp4啥的;
调用AVInputFormat#read_header读取该格式下的头部数据,在此可能会创建AVStream,否则会在调用avformat_find_stream_info时候通过read_frame_internal创建流;

FFmpeg学习 avformat_open_input()函数分析_第1张图片

avformat_open_input函数流程图如下:
在这里插入图片描述

下面是各个分析各个函数。

utils.c#avformat_open_input

主要工作:
Open an input stream and read the header. The codecs are not opened. The stream must be closed with avformat_close_input().
return 0 on success, a negative AVERROR on failure.
If you want to use custom IO, preallocate the format context and set its pb field.
打开流并根据格式信息读取数据头创建相应的AVStream,但不打开解码器,返回0表示成功。

int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options)
{
    AVFormatContext *s = *ps;
    int i, ret = 0;
    AVDictionary *tmp = NULL;

    // 没赋值就赋值
    if (!s && !(s = avformat_alloc_context()))
        return AVERROR(ENOMEM);

    // 有format就设置
    if (fmt)
        s->iformat = fmt;

   // 在堆上分配,赋值s->url
    if (!(s->url = av_strdup(filename ? filename : ""))) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }

    // init_input找到文件格式format
    if ((ret = init_input(s, filename, &tmp)) < 0)
        goto fail;
    s->probe_score = ret;

    s->duration = s->start_time = AV_NOPTS_VALUE;

    // 读取多媒体数据文件头,根据音视频频流创建相应的AVStream。
    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
        if ((ret = s->iformat->read_header(s)) < 0)
            goto fail;

    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);
    avformat_free_context(s);
    *ps = NULL;
    return ret;
}

utils.c#init_input

Open input file and probe the format if necessary.

如果没有指定iformat的话,则打开文件并探测格式。

主要工作:

调用其它函数,找到iformat;

先根据filename探测一波iformat;

没找到的话打开文件读取数据进行探测;

还没找到的话再探测一波;

在任意一步找到iformat即返回给init_input,init_input会调用iformat的read_head创建相应的AVStream。

#define AVPROBE_SCORE_MAX       100
#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)

static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    int ret;
    // 只将filename传入
    AVProbeData pd = { filename, NULL, 0 }; 

    // 初始化25分的得分
    int score = AVPROBE_SCORE_RETRY;

    /* 
     * 自定义了IO的话
     *    如果没指定iformat,就调用av_probe_input_buffer2推测iformat
     *    如果指定了iformat,直接返回
     */    
    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;
    }

    /*
     * 1. 如果指定了iformat,并且该格式是不需要读取数据的,就直接返回
     *    如果没指定iformat,则通过av_probe_input_format2来推测iformat,推测成功则返回
     */
    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
        return score;
        
    /*
     * 2. 第一步失败,则通过io_open打开文件;返回0则表示打开成功,<0则失败,open_input流程结束
     */
    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
        return ret;

    // 第二步根据URLProtocol打开了文件,此处如果外部指定了iformat则返回;
    // 即iformat即使指定了,也要打开文件去读写;也说明了文件格式iformat和传输协议urlProtocol是两码事
    if (s->iformat)
        return 0;

    
    /*
     * 3. 第二步没找到iformat的话,再调用av_probe_input_buffer2推测
     */
    return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                 s, 0, s->format_probesize);
}

第一步:format.c#av_probe_input_format2

Guess the file format.

推测文件格式;只有推测出来的得分值 > score_max,才将该format返回。

主要工作:

调用av_probe_input_format3找到format,大于score_max则探测成功,否则探测失败。

在init_input调用中

score_max为初始值25,is_opened为false,pd该结构体只有filename。

因为该文件没有被打开过,所以只有filename这一个有效信息。

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;

ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max)
{
    int score_ret;
    ff_const59 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;
}

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

format.c#av_probe_input_format3

Guess the file format,推荐iformat,是av_probe_input_format2的具体实现。
参数score_ret是推测的值,是要和score_max对比的。

主要工作:
第一次进来时,文件没有打开,pd.buf是NULL,赋值为zerobuffer,空转?
第二遍io_open填充buf后,走到这里遍历所有格式,根据read_probe、扩展名、mime_type进行探测,如果有得分值 > 传入的score_max的话,则找到了该format; 如果没大于,则返回null。

#define AVPROBE_SCORE_EXTENSION  50 ///< score for file extension
#define AVPROBE_SCORE_MIME       75 ///< score for file mime type
#define AVPROBE_SCORE_MAX       100 ///< maximum score

ff_const59 AVInputFormat *av_probe_input_format3(ff_const59 AVProbeData *pd, int is_opened, int *score_ret)
{
    AVProbeData lpd = *pd;
    const AVInputFormat *fmt1 = NULL;
    ff_const59 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) {
        // 初始化buf,32字节,内容全是0,局部静态呀
        lpd.buf = (unsigned char *) zerobuffer;
    }

    // 判断是否是ID3v2格式,ID3是一种metadata容器,多应用于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;
    }

    /* 
     * demuxer_list是编译出来的;
     * 遍历所有格式,根据read_probe、扩展名、mime_type进行探测,如果有得分值 > 传入的score_max的话,则找到了该format; 如果没大于,则返回null。
     * 
     * 1. 如果有read_probe方法,调用read_probe方法进行判断,返回该format的得分,如果文件扩展名是该格式的话,再进行switch判断;
     *    没有read_probe方法,扩展名匹配的话则得分是AVPROBE_SCORE_EXTENSION 50
     * 2. mime_type匹配的话,则得分max(AVPROBE_SCORE_MIME75, score)
     * 3. 比较score_max得分值,超过则找到,没超过则返回null。
     */
    while ((fmt1 = av_demuxer_iterate(&i))) {
        // 如果一个文件格式如果是AVFMT_NOFILE,对应init_input中的第一次探测的时候才能向下走,第二次文件打开了则continue;
        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) {
            if (av_match_ext(lpd.filename, fmt1->extensions))
                score = AVPROBE_SCORE_EXTENSION;
        }
        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;
}

read_probe

遍历demuxer_list,调用该格式的read_probe。
只有在打开文件后调用这才有意义,即调过io_open后才给pb.buf赋了值,否则在空转。

FFmpeg学习 avformat_open_input()函数分析_第2张图片

可以参考文件格式进行分析。

第二步:s->io_open,options.c#io_open_default

通过av_probe_input_format3遍历所有格式依然没有找到iformat时,则打开该文件读取数据再次进行探测。
AVFormatContext->io_open函数指针,赋值是在初始化时options.c#avformat_alloc_context中。

主要工作:
根据filename确定要使用的协议,然后初始化URLContext;
url_open2/url_open打开文件或建立连接,准备读取数据;
然后初始化AVIOContext;

// options.c
static int io_open_default(AVFormatContext *s, AVIOContext **pb,  const char *url, int flags, AVDictionary **options)
{
    av_log(s, loglevel, "Opening \'%s\' for %s\n", url, flags & AVIO_FLAG_WRITE ? "writing" : "reading");
    return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
}

// aviobuf.c
int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char *blacklist ) {

    URLContext *h;
    *s = NULL;

    // 初始化URLContext
    ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);
 
    // 初始化AVIOContext,会初始化读取的buffer大小,默认32768字节长
    ffio_fdopen(s, h);
    return 0;
}

avio.c#ffio_open_whitelist

  • 主要工作:
    确定该输入文件是什么协议的,并初始化URLContext;
    然后按该协议打开该文件;
  • int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char* blacklist, URLContext *parent) {
        ffurl_alloc(puc, filename, flags, int_cb);
        ffurl_connect(*puc, options);
        return 0;
    }
    
    int ffurl_alloc(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb) {
        const URLProtocol *p = NULL;
        p = url_find_protocol(filename);
        if (p)
           // 初始化URLContext,进行赋值
           return url_alloc_for_protocol(puc, p, filename, flags, int_cb);
    }
    
    /*
     * 根据相应的协议,调用协议的open函数,创建连接或者打开文件;
     * 可以以http.c为例梳理,其中会绕会到tcp.c在打开socket后读取http response
     */
    int ffurl_connect(URLContext *uc, AVDictionary **options) {
        int err =
            uc->prot->url_open2 ? uc->prot->url_open2(uc, uc->filename, uc->flags, options) :
            uc->prot->url_open(uc, uc->filename, uc->flags);
    
        if (err)
            return err;
        uc->is_connected = 1;
    
        // 无法seek则设置is_streamed为true,表示no seek possible
        if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file"))
            if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)
                uc->is_streamed = 1;
    
        return 0;
    }
    

    avio.c#url_find_protocol

  • 主要工作:
    根据传入的filename,遍历所有协议,确定传入的文件是什么协议的,
  • static const struct URLProtocol *url_find_protocol(const char *filename)
    {
        const URLProtocol **protocols;
        char proto_str[128], proto_nested[128], *ptr;
    
        // 查找第一个非字母数字的字符下标
        size_t proto_len = strspn(filename, URL_SCHEME_CHARS);
        
    
        if (filename[proto_len] != ':' &&
            (strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||
            is_dos_path(filename)){
            /* 
             * 文件路径,比如/storage/emulated/0/Android/data/com.baiiu.example/cache/test.mp4
             * 则proto_str是file
             */
            strcpy(proto_str, "file");
        } else {
            /* 
             * http://,rtmp:// 这样的,则proto_str是http、rtmp
             */
            av_strlcpy(proto_str, filename,
                       FFMIN(proto_len + 1, sizeof(proto_str)));
        }
    
        // 把proto_str赋值给proto_nested,嵌套的如hls+http:// m3u8格式的
        av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
        if ((ptr = strchr(proto_nested, '+')))
            *ptr = '\0';
    
        // 获取所有支持的protocols
        protocols = ffurl_get_protocols(NULL, NULL);
        if (!protocols)
            return NULL;
        
        int i;
        for (i = 0; protocols[i]; i++) {
            const URLProtocol *up = protocols[i];
            // 协议相同
            if (!strcmp(proto_str, up->name)) {
                av_freep(&protocols);
                return up;
            }
    
            // nested协议相同
            if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&
                !strcmp(proto_nested, up->name)) {
                av_freep(&protocols);
                return up;
            }
        }
        av_freep(&protocols);
        if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL))
            av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
                                         "openssl, gnutls or securetransport enabled.\n");
    
        return NULL;
    }
    

protocols.c#ffurl_get_protocols

  • 主要工作:
    获取所有协议,返回进行遍历,找到filename对应的协议;
    url_protocols变量在protocol_list.c文件中声明和定义,动态生成;
// 差不多这些协议吧,通过extern定义,是在别的文件内定义的

// https协议相关,从http->tls->tcp
extern const URLProtocol ff_http_protocol;
extern const URLProtocol ff_https_protocol;
extern const URLProtocol ff_tls_openssl_protocol;
extern const URLProtocol ff_tcp_protocol;

extern const URLProtocol ff_file_protocol;
extern const URLProtocol ff_ftp_protocol;
extern const URLProtocol ff_hls_protocol;
extern const URLProtocol ff_httpproxy_protocol;
extern const URLProtocol ff_rtp_protocol;
extern const URLProtocol ff_tls_protocol;
extern const URLProtocol ff_udp_protocol;
extern const URLProtocol ff_udplite_protocol;
extern const URLProtocol ff_librtmp_protocol;
extern const URLProtocol ff_librtmpe_protocol;
extern const URLProtocol ff_librtmps_protocol;
extern const URLProtocol ff_librtmpt_protocol;
extern const URLProtocol ff_librtmpte_protocol;

const URLProtocol **ffurl_get_protocols(const char *whitelist, const char *blacklist) {
    const URLProtocol **ret;
    int i, ret_idx = 0;

    ret = av_mallocz_array(FF_ARRAY_ELEMS(url_protocols), sizeof(*ret));
    if (!ret)
        return NULL;

    for (i = 0; url_protocols[i]; i++) {
        const URLProtocol *up = url_protocols[i];

        // 黑白名单过滤...

        ret[ret_idx++] = up;
    }

    return ret;
}

aviobuf.c#ffio_fdopen

初始化AVIOContext。
retry_transfer_wrapper是对ffurl_read、ffurl_write、ffurl_seek方法的封装,读取size长度的流数据。

int ffio_fdopen(AVIOContext **s, URLContext *h) {
    *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,
                            (int (*)(void *, uint8_t *, int))  ffurl_read,
                            (int (*)(void *, uint8_t *, int))  ffurl_write,
                            (int64_t (*)(void *, int64_t, int))ffurl_seek);

    return 0;
}


// avio.c
int ffurl_read(URLContext *h, unsigned char *buf, int size)
{
    if (!(h->flags & AVIO_FLAG_READ))
        return AVERROR(EIO);
    return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read);
}

int ffurl_read_complete(URLContext *h, unsigned char *buf, int size)
{
    if (!(h->flags & AVIO_FLAG_READ))
        return AVERROR(EIO);
    return retry_transfer_wrapper(h, buf, size, size, h->prot->url_read);
}

int ffurl_write(URLContext *h, const unsigned char *buf, int size)
{
    if (!(h->flags & AVIO_FLAG_WRITE))
        return AVERROR(EIO);
    /* avoid sending too big packets */
    if (h->max_packet_size && size > h->max_packet_size)
        return AVERROR(EIO);

    return retry_transfer_wrapper(h, (unsigned char *)buf, size, size,
                                  (int (*)(struct URLContext *, uint8_t *, int))
                                  h->prot->url_write);
}

static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
                                         int size, int size_min,
                                         int (*transfer_func)(URLContext *h,
                                                              uint8_t *buf,
                                                              int size))
{
    int ret, len;
    int fast_retries = 5;
    int64_t wait_since = 0;

    len = 0;
    while (len < size_min) {
        if (ff_check_interrupt(&h->interrupt_callback))
            return AVERROR_EXIT;
        ret = transfer_func(h, buf + len, size - len);

        if (ret == AVERROR(EINTR))
            continue;

        if (h->flags & AVIO_FLAG_NONBLOCK)
            return ret;

        if (ret == AVERROR(EAGAIN)) {
            ret = 0;
            if (fast_retries) {
                fast_retries--;
            } else {
                if (h->rw_timeout) {
                    if (!wait_since)
                        wait_since = av_gettime_relative();
                    else if (av_gettime_relative() > wait_since + h->rw_timeout)
                        return AVERROR(EIO);
                }
                av_usleep(1000);
            }
        } else if (ret == AVERROR_EOF)
            return (len > 0) ? len : AVERROR_EOF;
        else if (ret < 0)
            return ret;
        if (ret) {
            fast_retries = FFMAX(fast_retries, 2);
            wait_since = 0;
        }
        len += ret;
    }
    return len;
}

第三步:utils.c#av_probe_input_buffer2

主要工作:
通过io_open打开文件后,循环按该协议进行读取数据,从最小2048byte开始读取,不断扩充buf,直到探测到格式或达到1M为止。

填充pb.buf后,调用av_probe_input_format2继续探测iformat。此时buf的数据已经有值,便可以按照各个Inputformat确定score。

int av_probe_input_buffer2(AVIOContext *pb, ff_const59 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;

    // 默认的PROBE_BUF_MAX,1<<20 byte,1M
    if (!max_probe_size) {
        max_probe_size = PROBE_BUF_MAX;
    } else if (max_probe_size < PROBE_BUF_MIN) {
        return AVERROR(EINVAL);
    }

    if (offset >= max_probe_size)
        return AVERROR(EINVAL);

    /*
     * 从PROBE_BUF_MIN 2048 byte开始,逐渐 *2扩大到max_probe_size,最大到1M+1 byte,
     * 直到探测出iformat为止、或者直到最大也没探测出来,即探测失败;
     */
    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.
         * 1. 重新分配buf大小,当前的probe_size + AVPROBE_PADDING_SIZE
         * 2. 根据io_open确定的protocol协议进行读取数据
         *      用offset来控制读了多少,从哪开始读,不用重新读取
         */
        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) {
            if (ret != AVERROR_EOF)
                goto fail;

            // 重置
            score = 0;
            ret   = 0;          /* error was end of file, nothing read */
        }
        buf_offset += ret; // 增加offset
        if (buf_offset < offset)
            continue;

        pd.buf_size = buf_offset - offset;
        pd.buf = &buf[offset]; // 赋值pb.buf

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

        /*
         * Guess file format. 再次探测格式,is_opened为1,文件已经打开了
         */
        *fmt = av_probe_input_format2(&pd, 1, &score);
    }

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

aviobuf.c#avio_read

主要工作
avio_read通过AVIOContext#read_packet读取数据,循环读取到size大小后返回。

AVIOContext#read_packet,是在ffio_fdopen函数中对AVIOContext进行初始化。
实际调用在avio.c#ffurl_read。
ffurl_read方法实际上在调用相应prototol的url_read。

参考上面ffurl_read解释,最终会调到protocol里的url_read调用相应的protocol进行数据读取。

// aviobuf.c
int avio_read(AVIOContext *s, unsigned char *buf, int size)
{
    int len, size1;

    size1 = size;
    while (size > 0) {
        len = FFMIN(s->buf_end - s->buf_ptr, size);
        if (len == 0 || s->write_flag) {
            if((s->direct || size > s->buffer_size) && !s->update_checksum) {
                len = read_packet_wrapper(s, buf, size);
                if (len == AVERROR_EOF) {
                    s->eof_reached = 1;
                    break;
                } else if (len < 0) {
                    s->eof_reached = 1;
                    s->error= len;
                    break;
                } else {
                    s->pos += len;
                    s->bytes_read += len;
                    size -= len;
                    buf += len;
                    // reset the buffer
                    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 (avio_feof(s))  return AVERROR_EOF;
    }
    return size1 - size;
}

static int read_packet_wrapper(AVIOContext *s, uint8_t *buf, int size) {
    return s->read_packet(s->opaque, buf, size);
}

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓ 

你可能感兴趣的:(音视频开发进阶,音视频,webrtc,视频编解码,实时音视频,websocket)