ffmpeg源码分析 (四)

io_open

    承接上一篇,对于avformat_open_input的分析还差其中非常重要的一步,就是io_open,该函数用于打开FFmpeg的输入输出文件。

    在init_input中有这么一句

if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
        return ret;

    调用了AVFormatContext的成员函数指针io_open,定义如下

/**
     * A callback for opening new IO streams.
     *
     * Whenever a muxer or a demuxer needs to open an IO stream (typically from
     * avformat_open_input() for demuxers, but for certain formats can happen at
     * other times as well), it will call this callback to obtain an IO context.
     *
     * @param s the format context 
     * @param pb on success, the newly opened IO context should be returned here
     *        如果调用成功会返回一个AVIOContext,就填入这里。
     * @param url the url to open 需要打开的url
     * @param flags a combination of AVIO_FLAG_*
     * 打开地址的方式。可以选择只读,只写,或者读写。取值如下。
     *  AVIO_FLAG_READ:只读。
     * AVIO_FLAG_WRITE:只写。
     * AVIO_FLAG_READ_WRITE:读写。
     * @param options a dictionary of additional options, with the same
     *                semantics as in avio_open2()
     * @return 0 on success, a negative AVERROR code on failure
     *
     * @note Certain muxers and demuxers do nesting, i.e. they open one or more
     * additional internal format contexts. Thus the AVFormatContext pointer
     * passed to this callback may be different from the one facing the caller.
     * It will, however, have the same 'opaque' field.
     */
    int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
                   int flags, AVDictionary **options);

    该值会在初始化AVFormatContext的时候被赋值:

static void avformat_get_context_defaults(AVFormatContext *s)
{
    ......

    s->io_open  = io_open_default;
    s->io_close = io_close_default;

    .....
}

    io_open_default最终会调用 ffio_open_whitelist方法。实际上以前版本很重要的avio_open2现在也是直接调用该方法而已。

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;
    int err;

    err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);
    if (err < 0)
        return err;
    err = ffio_fdopen(s, h);
    if (err < 0) {
        ffurl_close(h);
        return err;
    }
    return 0;
}

    其中whitelist和blacklist分别是白名单和黑名单,在这个过程下,值是从AVFormatContext中 s->protocol_whitelist, s->protocol_blacklist  读取的,如果用户没有设置,那么就是空。

    在进一步分析之前,需要了解两个结构体,一个是URLContext,还有一个是URLProtocol。具体内容可以参考 https://my.oschina.net/zzxzzg/blog/1806996 。

 ffurl_open_whitelist

    ffio_open_whitelist的第一步就是调用ffurl_open_whitelist,该方法主要用于初始化URLContext。

 ffurl_alloc

    ffio_open_whitelist进一步会调用ffurl_alloc

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)
       return url_alloc_for_protocol(puc, p, filename, flags, int_cb);

    *puc = NULL;
    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 AVERROR_PROTOCOL_NOT_FOUND;
}

    又到了“原来如此”环节,ffmpeg中一个神奇的地方就在于自动找到对应的muxer,codec..等,这里就是根据filename去寻找相应的URLProtocol。

url_find_protocol

    使用文件名寻找对应的URLProtocol.

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);
    int i;
    //格式化filename,如果是文件路径,就会变成file:开头的类型。
    if (filename[proto_len] != ':' &&
        (strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||
        is_dos_path(filename))
        strcpy(proto_str, "file");
    else
        av_strlcpy(proto_str, filename,
                   FFMIN(proto_len + 1, sizeof(proto_str)));

    av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
    if ((ptr = strchr(proto_nested, '+')))
        *ptr = '\0';
    //获取所有protocol类型数组
    protocols = ffurl_get_protocols(NULL, NULL);
    if (!protocols)
        return NULL;
    //根据该protocol定义的name和flag对和当前的文件名进行匹配,如果匹配成功,那么直接返回该protocol
    for (i = 0; protocols[i]; i++) {
            const URLProtocol *up = protocols[i];
        if (!strcmp(proto_str, up->name)) {
            av_freep(&protocols);
            return up;
        }
        if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&
            !strcmp(proto_nested, up->name)) {
            av_freep(&protocols);
            return up;
        }
    }
    av_freep(&protocols);

    return NULL;
}

 

url_alloc_for_protocol

    回到ffurl_alloc,在找到URLProtocol之后,就会调用url_alloc_for_protocol方法对URLContext进行初始化,首先,检查输入的URLProtocol是否支持指定的flag。比如flag中如果指定了AVIO_FLAG_READ,则URLProtocol中必须包含url_read();如果指定了AVIO_FLAG_WRITE,则URLProtocol中必须包含url_write()。在检查无误之后,接着就可以调用av_mallocz()为即将创建的URLContext分配内存了。接下来基本上就是各种赋值工作。

 

    至此,我们已经初始化了URLContext,接下去ffurl_open_whitelist会继续调用方法ffurl_connect

ffurl_connect

    核心代码为

 err =
        uc->prot->url_open2 ? uc->prot->url_open2(uc,
                                                  uc->filename,
                                                  uc->flags,
                                                  options) :
        uc->prot->url_open(uc, uc->filename, uc->flags);

    实际上就是调用URLProtocol的url_open2如果没有定义就调用url_open。这个地方根据不同的协议调用的url_open()具体实现函数也是不一样的,例如file协议的url_open()对应的是file_open(),而file_open()最终调用了_wsopen(),_sopen()(Windows下)或者open()(Linux下,类似于fopen())这样的系统中打开文件的API函数;而libRTMP的url_open()对应的是rtmp_open(),而rtmp_open()最终调用了libRTMP的API函数RTMP_Init(),RTMP_SetupURL(),RTMP_Connect() 以及RTMP_ConnectStream()。

ffio_fopen

    io_open_default两个核心方法之二就是ffio_fopen, 之前我们已经初始化了URLContext,现在需要做的就是根据URLContext初始化AVIOContext

int ffio_fdopen(AVIOContext **s, URLContext *h)
{
    AVIOInternal *internal = NULL;
    uint8_t *buffer = NULL;
    int buffer_size, max_packet_size;

    max_packet_size = h->max_packet_size;
    if (max_packet_size) {
        buffer_size = max_packet_size; /* no need to bufferize more than one packet */
    } else {
        buffer_size = IO_BUFFER_SIZE;
    }
    buffer = av_malloc(buffer_size);
    if (!buffer)
        return AVERROR(ENOMEM);

    internal = av_mallocz(sizeof(*internal));
    if (!internal)
        goto fail;

    internal->h = h;

    *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE,
                            internal, io_read_packet, io_write_packet, io_seek);
    if (!*s)
        goto fail;

    (*s)->protocol_whitelist = av_strdup(h->protocol_whitelist);
    if (!(*s)->protocol_whitelist && h->protocol_whitelist) {
        avio_closep(s);
        goto fail;
    }
    (*s)->protocol_blacklist = av_strdup(h->protocol_blacklist);
    if (!(*s)->protocol_blacklist && h->protocol_blacklist) {
        avio_closep(s);
        goto fail;
    }
    (*s)->direct = h->flags & AVIO_FLAG_DIRECT;

    (*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;
    (*s)->max_packet_size = max_packet_size;
    (*s)->min_packet_size = h->min_packet_size;
    if(h->prot) {
        (*s)->read_pause = io_read_pause;
        (*s)->read_seek  = io_read_seek;

        if (h->prot->url_read_seek)
            (*s)->seekable |= AVIO_SEEKABLE_TIME;
    }
    (*s)->short_seek_get = io_short_seek;
    (*s)->av_class = &ff_avio_class;
    return 0;
fail:
    av_freep(&internal);
    av_freep(&buffer);
    return AVERROR(ENOMEM);
}

    ffio_fdopen()函数首先初始化AVIOContext中的Buffer。如果URLContext中设置了max_packet_size,则将Buffer的大小设置为max_packet_size。如果没有设置的话(似乎大部分URLContext都没有设置该值),则会分配IO_BUFFER_SIZE个字节给Buffer。IO_BUFFER_SIZE取值为32768。

    然后会调用 avio_alloc_context 创建AVIOContext。之后的代码就是在给它赋初始值。

avio_alloc_context

/**
 * Allocate and initialize an AVIOContext for buffered I/O. It must be later
 * freed with avio_context_free().
 *
 * @param buffer Memory block for input/output operations via AVIOContext.
 *        The buffer must be allocated with av_malloc() and friends.
 *        It may be freed and replaced with a new buffer by libavformat.
 *        AVIOContext.buffer holds the buffer currently in use,
 *        which must be later freed with av_free(). 
 *        AVIOContext中的Buffer。
 * @param buffer_size The buffer size is very important for performance.
 *        For protocols with fixed blocksize it should be set to this blocksize.
 *        For others a typical size is a cache page, e.g. 4kb. 
 *        AVIOContext中的Buffer的大小。
 * @param write_flag Set to 1 if the buffer should be writable, 0 otherwise.
 * @param opaque An opaque pointer to user-specific data. 
 *  用户自定义数据。
 * @param read_packet  A function for refilling the buffer, may be NULL.
 *                     For stream protocols, must never return 0 but rather
 *                     a proper AVERROR code. 
 *        读取外部数据,填充Buffer的函数。
 * @param write_packet A function for writing the buffer contents, may be NULL.
 *        The function may not change the input buffers content. 
 *      向Buffer中写入数据的函数。
 * @param seek A function for seeking to specified byte position, may be NULL. 
 *     用于Seek的函数。
 *
 * @return Allocated AVIOContext or NULL on failure.
 */
AVIOContext *avio_alloc_context(
                  unsigned char *buffer,
                  int buffer_size,
                  int write_flag,
                  void *opaque,
                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence));

    该方法的参数比较多,但是方法体很简单,无非是分配内存,初始化默认值,其中非常重要的就是以下代码了,3个函数作为该AVIOContext的读,写,跳转函数。

    s->write_packet    = write_packet;
    s->read_packet     = read_packet;
    s->seek            = seek;

    我们回到 ffio_fopen  中找到这几个函数指针实际上就是 io_read_packet, io_write_packet, io_seek。再进一步剥析就是ffurl_read,ffurl_write,ffurl_seek。

 io_seek

    这个方法是最简单的

int64_t ffurl_seek(URLContext *h, int64_t pos, int whence)
{
    int64_t ret;

    if (!h->prot->url_seek)
        return AVERROR(ENOSYS);
    ret = h->prot->url_seek(h, pos, whence & ~AVSEEK_FORCE);
    return ret;
}

    他实际上就是调用URLProtocol的url_seek方法。

ffurl_read,ffurl_write

    这两个方法大同小异,代码很简单,调用retry_transfer_wrapper方法进行真正的操作,真正的操作是什么?就是retry_transfer_wrapper这个方法的最后一个参数,分别是 h->prot->url_read 和h->prot->url_write,也就是URLProtocol中的url_read方法和url_wirte方法。

    retry_transfer_wrapper的作用是对URLProtocol方法调用进行一次封装,做一些校验和错误处理。

 

结语

    至此,我们分析完了io_open。连同上一篇,我们已经基本了解了init_input方法到底是如何打开文件,判断文件格式,并且初始化一些之后需要使用到的内容。

 

参考文献

    https://blog.csdn.net/leixiaohua1020/article/details/41199947

转载于:https://my.oschina.net/zzxzzg/blog/1857574

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值