分享

FFMPEG源码----结构体AVIOContext

 开花结果 2020-03-27

typedef struct {

    /**

     * A class for private options.

     *

     * If this AVIOContext is created by avio_open2(), av_class is set and

     * passes the options down to protocols.

     *

     * If this AVIOContext is manually allocated, then av_class may be set by

     * the caller.

     *

     * warning -- this field can be NULL, be sure to not pass this AVIOContext

     * to any av_opt_* functions in that case.

     */

    AVClass *av_class;

    unsigned char *buffer;  /**< Start of the buffer. */

    int buffer_size;        /**< Maximum buffer size */

    unsigned char *buf_ptr; /**< Current position in the buffer */

    unsigned char *buf_end; /**< End of the data, may be less than

                                 buffer+buffer_size if the read function returned

                                 less data than requested, e.g. for streams where

                                 no more data has been received yet. */

    void *opaque;           /**< A private pointer, passed to the read/write/seek/...

                                 functions. */

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

    int64_t pos;            /**< position in the file of the current buffer */

    int must_flush;         /**< true if the next seek should flush */

    int eof_reached;        /**< true if eof reached */

    int write_flag;         /**< true if open for writing */

    int max_packet_size;

    unsigned long checksum;

    unsigned char *checksum_ptr;

    unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);

    int error;              /**< contains the error code or 0 if no error happened */

    /**

     * Pause or resume playback for network streaming protocols - e.g. MMS.

     */

    int (*read_pause)(void *opaque, int pause);

    /**

     * Seek to a given timestamp in stream with the specified stream_index.

     * Needed for some network streaming protocols which don't support seeking

     * to byte position.

     */

    int64_t (*read_seek)(void *opaque, int stream_index,

                         int64_t timestamp, int flags);

    /**

     * A combination of AVIO_SEEKABLE_ flags or 0 when the stream is not seekable.

     */

    int seekable;

    /**

     * max filesize, used to limit allocations

     * This field is internal to libavformat and access from outside is not allowed.

     */

     int64_t maxsize;

} AVIOContext;

该结构体可以通过avio_open2函数来分配初始化,也可以手动分配初始化。下面看看avio_open2源码中在做什么。

在此之前,为了看源码时理解更顺畅一些,首先明确几个结构体间的关系,

AVIOContext中包含有一个URLContext 结构体,而在URLContext 中包含有一个URLProtocol结构体。

源码分析

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

    if (err < 0) {

        ffurl_close(h);

        return err;

    }

    return 0;

}

可以看到,该函数比较简单,其中两个主要的函数ffurl_open和ffio_fdopen,而需要生成的结构体AVIOContext **s,主要就是初始化了一个URLContext结构体给它。
一 ffurl_open函数

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);                     //--------------------(1)

    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);                                        //------------------(2)

    if (!ret)

        return 0;

fail:

    ffurl_close(*puc);

    *puc = NULL;

    return ret;

}

先看第(1)个函数:

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(filename, "https:", NULL))

        av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "

                                     "openssl, gnutls,\n"

                                     "or securetransport enabled.\n");

    return AVERROR_PROTOCOL_NOT_FOUND;

}

这里出现了URLProtocol 这个结构体,用于解析路径的协议,比如file、udp、rtmp等等。

static struct URLProtocol *url_find_protocol(const char *filename)

{

    URLProtocol *up = NULL;

    char proto_str[128], proto_nested[128], *ptr;

    size_t proto_len = strspn(filename, URL_SCHEME_CHARS);

    if (filename[proto_len] != ':' &&

        (filename[proto_len] != ',' || !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)));

    if ((ptr = strchr(proto_str, ',')))

        *ptr = '\0';

    av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));

    if ((ptr = strchr(proto_nested, '+')))

        *ptr = '\0';

/* 这里proto_str已经储存为protocol的名字,比如"file"、“rtmp”、“http”等等,然后会循环获取当前注册了的协议结构体URLProtocol,

 * 比较其中的名字up->name是否 一样,如果一样就匹配成功,返回该URLProtocol

 */

    while (up = ffurl_protocol_next(up)) {

        if (!strcmp(proto_str, up->name))

            break;

        if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&

            !strcmp(proto_nested, up->name))

            break;

    }

    return up;

}

这个函数内部主要是对路径的字符串做一些处理,从而获取到该路径对应的URLProtocol。如果成功获取,那么会 return url_alloc_for_protocol(puc, p, filename, flags, int_cb)

static int url_alloc_for_protocol(URLContext **puc, struct URLProtocol *up,

                                  const char *filename, int flags,

                                  const AVIOInterruptCB *int_cb)

{

    URLContext *uc;

    int err;

#if CONFIG_NETWORK

    if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())

        return AVERROR(EIO);

#endif

    if ((flags & AVIO_FLAG_READ) && !up->url_read) {

        av_log(NULL, AV_LOG_ERROR,

               "Impossible to open the '%s' protocol for reading\n", up->name);

        return AVERROR(EIO);

    }

    if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {

        av_log(NULL, AV_LOG_ERROR,

               "Impossible to open the '%s' protocol for writing\n", up->name);

        return AVERROR(EIO);

    }

    uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);

    if (!uc) {

        err = AVERROR(ENOMEM);

        goto fail;

    }

    uc->av_class = &ffurl_context_class;

    uc->filename = (char *)&uc[1];

    strcpy(uc->filename, filename);

    uc->prot            = up;

    uc->flags           = flags;

    uc->is_streamed     = 0; /* default = not streamed */

    uc->max_packet_size = 0; /* default: stream file */

    if (up->priv_data_size) {

        uc->priv_data = av_mallocz(up->priv_data_size);

        if (!uc->priv_data) {

            err = AVERROR(ENOMEM);

            goto fail;

        }

        if (up->priv_data_class) {

            int proto_len= strlen(up->name);

            char *start = strchr(uc->filename, ',');

            *(const AVClass **)uc->priv_data = up->priv_data_class;

            av_opt_set_defaults(uc->priv_data);

            if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == start){

                int ret= 0;

                char *p= start;

                char sep= *++p;

                char *key, *val;

                p++;

                while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){

                    *val= *key= 0;

                    ret= av_opt_set(uc->priv_data, p, key+1, 0);

                    if (ret == AVERROR_OPTION_NOT_FOUND)

                        av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p);

                    *val= *key= sep;

                    p= val+1;

                }

                if(ret<0 || p!=key){

                    av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start);

                    av_freep(&uc->priv_data);

                    av_freep(&uc);

                    err = AVERROR(EINVAL);

                    goto fail;

                }

                memmove(start, key+1, strlen(key));

            }

        }

    }

    if (int_cb)

        uc->interrupt_callback = *int_cb;

    *puc = uc;

    return 0;

fail:

    *puc = NULL;

    if (uc)

        av_freep(&uc->priv_data);

    av_freep(&uc);

#if CONFIG_NETWORK

    if (up->flags & URL_PROTOCOL_FLAG_NETWORK)

        ff_network_close();

#endif

    return err;

}

这个函数中主要就是malloc分配内存,并初始化URLContext 中一些变量的值。
至此,ffurl_alloc函数已经初始化一个URLContext结构体,并分配好空间。

第(2)个函数

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;

    /* We must be careful here as ffurl_seek() could be slow,

     * for example for http */

    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;

}

在该函数中会查看结构体URLContext 中是否已经赋值了url_open2,如果有则通过url_open2 打开文件,否则使用url_open函数来打开文件。这个函数根据不同的协议会传入不同的回调,比如file类型其实就是调用open函数等。

二 ffio_fdopen函数

int ffio_fdopen(AVIOContext **s, URLContext *h)

{

    uint8_t *buffer;

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

//分配AVIOContext 结构体内存空间,赋值一些参数和回调函数

    *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);

    if (!*s) {

        av_free(buffer);

        return AVERROR(ENOMEM);

    }

    (*s)->direct = h->flags & AVIO_FLAG_DIRECT;

    (*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;

    (*s)->max_packet_size = max_packet_size;

    if(h->prot) {

        (*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause;

        (*s)->read_seek  = (int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek;

    }

    (*s)->av_class = &ff_avio_class;

    return 0;

}

该函数首先初始化AVIOContext中的Buffer。如果URLContext中设置了max_packet_size,则将Buffer的大小设置为max_packet_size。如果没有设置的话(似乎大部分URLContext都没有设置该值),则会分配IO_BUFFER_SIZE个字节给Buffer。IO_BUFFER_SIZE取值为32768。后面就是一些属性的赋值操作。

其中有几个函数注意一下,ffurl_read、ffurl_write和ffurl_seek。

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

}

该函数首先判断一下URLContext 结构体是否支持读操作,如果支持就会调用retry_transfer_wrapper函数。

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 < 1)

            return (ret < 0 && ret != AVERROR_EOF) ? ret : len;

        if (ret)

            fast_retries = FFMAX(fast_retries, 2);

        len += ret;

    }

    return len;

}

可以看到核心就是 ret = transfer_func(h, buf + len, size - len),而该函数是传入的回调函数,在read中就是h->prot->url_read,这个函数会根据不同的协议而不同,也就是在进行读文件的实际操作,比如file协议中会调用read函数去实际读取数据。读完以后会进行一些容错处理。

下面给大家看一下file协议的函数就明白上面的意思了。文件协议中代码如下(file.c):

URLProtocol ff_file_protocol = {

    .name                = "file",

    .url_open            = file_open,

    .url_read            = file_read,

    .url_write           = file_write,

    .url_seek            = file_seek,

    .url_close           = file_close,

    .url_get_file_handle = file_get_handle,

    .url_check           = file_check,

};

其中的所有file_xxx函数如下:

static int file_read(URLContext *h, unsigned char *buf, int size)

{

    int fd = (intptr_t) h->priv_data;

    int r = read(fd, buf, size);

    return (-1 == r)?AVERROR(errno):r;

}

static int file_write(URLContext *h, const unsigned char *buf, int size)

{

    int fd = (intptr_t) h->priv_data;

    int r = write(fd, buf, size);

    return (-1 == r)?AVERROR(errno):r;

}

static int file_get_handle(URLContext *h)

{

    return (intptr_t) h->priv_data;

}

static int file_check(URLContext *h, int mask)

{

    struct stat st;

    int ret = stat(h->filename, &st);

    if (ret < 0)

        return AVERROR(errno);

    ret |= st.st_mode&S_IRUSR ? mask&AVIO_FLAG_READ  : 0;

    ret |= st.st_mode&S_IWUSR ? mask&AVIO_FLAG_WRITE : 0;

    return ret;

}

#if CONFIG_FILE_PROTOCOL

static int file_open(URLContext *h, const char *filename, int flags)

{

    int access;

    int fd;

    av_strstart(filename, "file:", &filename);

    if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) {

        access = O_CREAT | O_TRUNC | O_RDWR;

    } else if (flags & AVIO_FLAG_WRITE) {

        access = O_CREAT | O_TRUNC | O_WRONLY;

    } else {

        access = O_RDONLY;

    }

#ifdef O_BINARY

    access |= O_BINARY;

#endif

    fd = open(filename, access, 0666);

    if (fd == -1)

        return AVERROR(errno);

    h->priv_data = (void *) (intptr_t) fd;

    return 0;

}

/* XXX: use llseek */

static int64_t file_seek(URLContext *h, int64_t pos, int whence)

{

    int fd = (intptr_t) h->priv_data;

    if (whence == AVSEEK_SIZE) {

        struct stat st;

        int ret = fstat(fd, &st);

        return ret < 0 ? AVERROR(errno) : st.st_size;

    }

    return lseek(fd, pos, whence);

}

static int file_close(URLContext *h)

{

    int fd = (intptr_t) h->priv_data;

    return close(fd);

}

到此,结构体AVIOContext就被初始化完成。


参考资料:https://blog.csdn.net/leixiaohua1020/article/details/14215369

原文链接:https://blog.csdn.net/guxinkobe/article/details/100514689

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约