深耕ffmpeg系列之avio

280 阅读14分钟

Don't ever let somebody tell you you can't do something. You got a dream, you gotta protect it.

在ffmpeg中I/O操作即读写数据,如:读取本地文件,读取网络文件,此功能是由avio这个模块来实现的。下面看一个简单读取本地文件的demo,先感受下avio的基本使用方法,再来深入探究下它的实现原理。

#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    // 输入文件路径
    const char *filename = "input.mp4";

    AVIOContext *avio_ctx = NULL;
    uint8_t *buffer = NULL;
    int buffer_size = 4096;
    int ret = 0;

    // 创建并初始化 AVIOContext
    ret = avio_open2(&avio_ctx, filename, AVIO_FLAG_READ, NULL, NULL);
    if (ret < 0) {
        fprintf(stderr, "无法打开文件: %s\n", filename);
        return ret;
    }

    // 获取文件大小
    int64_t file_size = avio_size(avio_ctx);
    printf("文件大小: %" PRId64 " 字节\n", file_size);

    // 读取文件内容示例
    uint8_t read_buffer[1024];
    int bytes_read = avio_read(avio_ctx, read_buffer, sizeof(read_buffer));
    if (bytes_read > 0) {
        printf("已读取 %d 字节数据\n", bytes_read);
        // 这里可以处理读取的数据
    }

    // 关闭 AVIOContext
    avio_close(avio_ctx);
    return 0;
}

从上面的demo可以看出用到了avio的四个接口就完成了文件的读取:

  • avio_open2:传入uri并分配初始化一个AVIOContext
  • avio_size:获取文件的总size
  • avio_read:取出文件数据到buffer中
  • avio_close:销毁avio_ctx

看着操作是不是很简单,但有个问题是,这个avio模块是支持所有类型的IO的,它是怎么支持的?如果是open一个网络的url,它的内部又是如何处理的呢?

AVIOContext

先来看看AVIOContext中有什么,逐步来揭秘下。

typedef struct AVIOContext {
    //用于进行私有option机制,后续单独出一篇文章进行讲解其实现原理
    const AVClass *av_class;

    /*
     * The following shows the relationship between buffer, buf_ptr,
     * buf_ptr_max, buf_end, buf_size, and pos, when reading and when writing
     * (since AVIOContext is used for both):
     *
     **********************************************************************************
     *                                   READING
     **********************************************************************************
     *
     *                            |              buffer_size              |
     *                            |---------------------------------------|
     *                            |                                       |
     *
     *                         buffer          buf_ptr       buf_end
     *                            +---------------+-----------------------+
     *                            |/ / / / / / / /|/ / / / / / /|         |
     *  read buffer:              |/ / consumed / | to be read /|         |
     *                            |/ / / / / / / /|/ / / / / / /|         |
     *                            +---------------+-----------------------+
     *
     *                                                         pos
     *              +-------------------------------------------+-----------------+
     *  input file: |                                           |                 |
     *              +-------------------------------------------+-----------------+
     *
     *
     **********************************************************************************
     *                                   WRITING
     **********************************************************************************
     *
     *                             |          buffer_size                 |
     *                             |--------------------------------------|
     *                             |                                      |
     *
     *                                                buf_ptr_max
     *                          buffer                 (buf_ptr)       buf_end
     *                             +-----------------------+--------------+
     *                             |/ / / / / / / / / / / /|              |
     *  write buffer:              | / / to be flushed / / |              |
     *                             |/ / / / / / / / / / / /|              |
     *                             +-----------------------+--------------+
     *                               buf_ptr can be in this
     *                               due to a backward seek
     *
     *                            pos
     *               +-------------+----------------------------------------------+
     *  output file: |             |                                              |
     *               +-------------+----------------------------------------------+
     *
     */
     
    unsigned char *buffer;  /* buffer的起始地址 */
    int buffer_size;        /* buffer大小 */
    unsigned char *buf_ptr; /* buffer当前位置 */
    unsigned char *buf_end; /* 数据的结束位置,可能小于buffer + buffer_size */
    unsigned char *buf_ptr_max; /* 记录之前写入到达的最远位置,确保`avio_flush()` 时,  */
                                /*  会将buffer到buf_ptr_max之间的所有数据刷新到输出设备 */
    int64_t bytes_read; /* 统计此AVIOContext读了多少字节 */
    int64_t bytes_written; /* 统计此AVIOContext写了多少字节. */
    int64_t pos;            /* 当前缓存buffer中缓存的数据在文件中的位置 */
    
    // 读写操作函数,可由调用者传入或者使用默认函数
    void *opaque;  /*私有指针传递给 read/write/seek/...等函数*/
    int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
    int (*write_packet)(void *opaque, const uint8_t *buf, int buf_size);
    int64_t (*seek)(void *opaque, int64_t offset, int whence);

    int (*read_pause)(void *opaque, int pause); /* 网络protocol时,pause或者resume播放 */
    /* 当某些网络protocol不能进行byte position seek时,需要用时间戳进行seek */
    int64_t (*read_seek)(void *opaque, int stream_index,
                         int64_t timestamp, int flags);
    
    int eof_reached;        /* 当出错或者eof时为true,表示不能读数据了 */
    int error;              /* 出错时包含错误码 */
    int write_flag;         /* 此context为写时,此flag为true */
    int max_packet_size;
    int min_packet_size;    /**< Try to buffer at least this amount of data
                                 before flushing it. */
    unsigned long checksum;
    unsigned char *checksum_ptr;
    unsigned long (*update_checksum)(unsigned long checksum, 
                                     const uint8_t *buf, 
                                     unsigned int size);
    int seekable; /* AVIO_SEEKABLE_ flags的组合,不能seek时为0 */

    /* 直接读写:当 `direct` 为真时,avio_read()和avio_write()会尽可能直接访问底层 I/O 设备,*/
    /*   而不是通过 `AVIOContext` 的内部缓冲区。*/
    /* 直接定位:avio_seek()会始终调用底层的seek函数,跳过任何缓冲逻辑。*/
    int direct;

    //允许使用的protocol
    const char *protocol_whitelist;

    //不允许使用的protocol
    const char *protocol_blacklist;

    // 用于给用户调AVIO_DATA_MARKER_
    int (*write_data_type)(void *opaque, const uint8_t *buf, int buf_size,
                           enum AVIODataMarkerType type, int64_t time);

    //如果设置为true,则不回调write_data_type notify AVIO_DATA_MARKER_BOUNDARY_POINT
    int ignore_boundary_point;

} AVIOContext;

avio_open

从上面struct AVIOContext中的定义来看,并没有定义protocol相关的对象,那avio模块是如何区分不同type的I/O的呢? 下面来阅读下avio_open2的源码来寻找这个问题的答案。

int avio_open2(AVIOContext **s, //出参
               const char *filename, //输入url
               int flags,
               const AVIOInterruptCB *int_cb, 
               AVDictionary **options)
{
    return ffio_open_whitelist(s, filename, flags, int_cb, options, NULL, NULL);
}

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;

    *s = NULL;
    //此函数分配URLContext
    err = ffurl_open_whitelist(&h, filename, flags, int_cb, options,
                               whitelist, blacklist, NULL);
    ...............;
    //传入URLContext并分配初始化AVIOContext,那AVIOContext与URLContext如何关联起来的呢?
    err = ffio_fdopen(s, h);
    ...............;
    return 0;
}

ffurl_open_whitelist

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)
{
    AVDictionary *tmp_opts = NULL;
    AVDictionaryEntry *e;
    // 看看如何通过filename找到URLContext
    int ret = ffurl_alloc(puc, filename, flags, int_cb);
    ............................;
    //为URLContext设置白名单和黑名单option
    if ((ret = av_opt_set_dict(*puc, options)) < 0)
        goto fail;
    //下面细看
    ret = ffurl_connect(*puc, options);

    if (!ret)
        return 0;
fail:
    ffurl_closep(puc);
    return ret;
}

int ffurl_alloc(URLContext **puc, const char *filename, int flags,
                const AVIOInterruptCB *int_cb)
{
    const URLProtocol *p = NULL;
    // 查找protocol
    p = url_find_protocol(filename);
    if (p)
       return url_alloc_for_protocol(puc, p, filename, flags, int_cb);

    *puc = NULL;
    return AVERROR_PROTOCOL_NOT_FOUND;
}

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中获取协议前缀,如:http, file, rtsp
    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';
    //从全局变量url_protocols获取配置打开的所有protocol,然后通过协议前缀proto_str找到对应的protocol
    protocols = ffurl_get_protocols(NULL, NULL);
    if (!protocols)
        return NULL;
    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);
    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;
}

//当找到protocol之后,再调用ffurl_connect是干啥呢?
int ffurl_connect(URLContext *uc, AVDictionary **options)
{
    int err;
    AVDictionary *tmp_opts = NULL;
    AVDictionaryEntry *e;

    if (!options)
        options = &tmp_opts;

    // Check that URLContext was initialized correctly and lists are matching if set
    av_assert0(!(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||
               (uc->protocol_whitelist && !strcmp(uc->protocol_whitelist, e->value)));
    av_assert0(!(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||
               (uc->protocol_blacklist && !strcmp(uc->protocol_blacklist, e->value)));

    if (uc->protocol_whitelist && av_match_list(uc->prot->name, uc->protocol_whitelist, ',') <= 0) {
        av_log(uc, AV_LOG_ERROR, "Protocol '%s' not on whitelist '%s'!\n", uc->prot->name, uc->protocol_whitelist);
        return AVERROR(EINVAL);
    }
    // 通过黑名单和白名单检测是否为合法的protocol
    ..........................................;
    if ((err = av_dict_set(options, "protocol_whitelist", uc->protocol_whitelist, 0)) < 0)
        return err;
    if ((err = av_dict_set(options, "protocol_blacklist", uc->protocol_blacklist, 0)) < 0)
        return err;
    
    //调用具体protocol的open函数
    err =
        uc->prot->url_open2 ? uc->prot->url_open2(uc,
                                                  uc->filename,
                                                  uc->flags,
                                                  options) :
        uc->prot->url_open(uc, uc->filename, uc->flags);

    av_dict_set(options, "protocol_whitelist", NULL, 0);
    av_dict_set(options, "protocol_blacklist", NULL, 0);

   ..........................;
    return 0;
}

ffurl_open_whitelist函数流程总结:

  1. 当ffmpeg config中打开了一些protocol之后,在config时会自动创建ffmpeg/libavformat/protocol_list.c文件,里面定义了一个全局数组url_protocols,其定义了当前配置了哪些protocol。 ffurl_open_whitelist根据uri中的协议前缀找到对应的protocol,然后将其封装在URLContext.prot中
  2. 通过白名单和黑名单列表检查是否找错了protocol
  3. 调用protocol的open函数
/ * ffmpeg/libavformat/protocol_list.c */
static const URLProtocol * const url_protocols[] = {
    &ff_cache_protocol,
    &ff_concat_protocol,
    &ff_file_protocol,
    &ff_http_protocol,
    &ff_https_protocol,
    &ff_rpmsg_protocol,
    &ff_tcp_protocol,
    &ff_tls_protocol,
    &ff_unix_protocol,
    NULL };

ffio_fdopen

int ffio_fdopen(AVIOContext **sp, URLContext *h)
{
    AVIOContext *s;
    uint8_t *buffer = NULL;
    int buffer_size, max_packet_size;
    ......................................;
    //buffer_size默认值为IO_BUFFER_SIZE为32K
    buffer = av_malloc(buffer_size);
    if (!buffer)
        return AVERROR(ENOMEM);
    //分配AVIOContext, 传入buffer, ffurl_read2, ffurl_write2, ffurl_seek回调函数
    *sp = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,
                             ffurl_read2, ffurl_write2, ffurl_seek2);

    s = *sp;
    //设置是否为direct 模式
    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) {
        //赋值pause和seek接口
        s->read_pause = h->prot->url_read_pause;
        s->read_seek  = h->prot->url_read_seek;

        if (h->prot->url_read_seek)
            s->seekable |= AVIO_SEEKABLE_TIME;
    }
    ((FFIOContext*)s)->short_seek_get = ffurl_get_short_seek;
    s->av_class = &ff_avio_class;
    return 0;
}

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, const uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
    FFIOContext *s = av_malloc(sizeof(*s));
    if (!s)
        return NULL;
    ffio_init_context(s, buffer, buffer_size, write_flag, opaque,
                  read_packet, write_packet, seek);
    return &s->pub;
}

void ffio_init_context(FFIOContext *ctx,
                  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, const uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
    AVIOContext *const s = &ctx->pub;

    memset(ctx, 0, sizeof(*ctx));

    s->buffer      = buffer;
    ctx->orig_buffer_size =
    s->buffer_size = buffer_size;
    s->buf_ptr     = buffer;
    s->buf_ptr_max = buffer;
    s->opaque      = opaque;//opaque为URLContext
    s->direct      = 0;
    ...................; 
  }

AVIOContext,URLContext与URLProtocol关系图

从上述最终的代码可以看出AVIOContext,URLContext与URLProtocol之间的关系:

graph TD
FFIOContext.pub --> AVIOContext --> AVIOContext.opaque --> URLContext -->URLContext.prot --> URLProtocol
AVIOContext --> AVIOContext.read_packet --> ffurl_read2
AVIOContext --> AVIOContext.write_packet --> ffurl_write2
AVIOContext --> AVIOContext.seek --> ffurl_seek

avio_read

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

    size1 = size;
    while (size > 0) {
        //计算缓存中的数据量与想要读取的size的小的值
        len = FFMIN(s->buf_end - s->buf_ptr, size);
        if (len == 0 || s->write_flag) { //如果len为0,表示要么缓存中没有数据,要么读取的数据量为0
            //如果为direct模式或者读取的size大于缓存的总size,就直接读取数据,不用缓存
            if((s->direct || size > s->buffer_size) && !s->update_checksum && s->read_packet) {
                // bypass the buffer and read data directly into buf
                len = read_packet_wrapper(s, buf, size);
                if (len == AVERROR_EOF) {
                    /* do not modify buffer if EOF reached so that a seek back can
                    be done without rereading data */
                    s->eof_reached = 1;
                    break;
                } else if (len < 0) {
                    s->eof_reached = 1;
                    s->error= len;
                    break;
                } else {
                    s->pos += len;
                    ffiocontext(s)->bytes_read += len;
                    s->bytes_read = ffiocontext(s)->bytes_read;
                    size -= len;
                    buf += len;
                    // reset the buffer
                    s->buf_ptr = s->buffer;
                    s->buf_end = s->buffer/* + len*/;
                }
            } else { // 否则就走fill buffers,先把数据读到缓存中
                fill_buffer(s);
                len = s->buf_end - s->buf_ptr;
                if (len == 0)
                    break;
            }
        } else { //从缓存中将数据拷贝到外部buf中
            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;
}

fill_buffer

调用read_packet_wrapper往缓存中读max_buffer_size字节个buffer

static void fill_buffer(AVIOContext *s)
{
    FFIOContext *const ctx = (FFIOContext *)s;
    int max_buffer_size = s->max_packet_size ?
                          s->max_packet_size : IO_BUFFER_SIZE;
    uint8_t *dst        = s->buf_end - s->buffer + max_buffer_size <= s->buffer_size ?
                          s->buf_end : s->buffer;
    int len             = s->buffer_size - (dst - s->buffer);

    ............................;

    /* make buffer smaller in case it ended up large after probing */
    if (s->read_packet && ctx->orig_buffer_size &&
        s->buffer_size > ctx->orig_buffer_size  && len >= ctx->orig_buffer_size) {
        if (dst == s->buffer && s->buf_ptr != dst) {
            int ret = set_buf_size(s, ctx->orig_buffer_size);
            if (ret < 0)
                av_log(s, AV_LOG_WARNING, "Failed to decrease buffer size\n");

            s->checksum_ptr = dst = s->buffer;
        }
        len = ctx->orig_buffer_size;
    }
   
     /*                            |              buffer_size              | */
     /*                            |---------------------------------------| */
     /*                            |                                       | */
     /*                                                                       */
     /*                         buffer          buf_ptr       buf_end         */
     /*                            +---------------+-----------------------+  */
     /*                            |/ / / / / / / /|/ / / / / / /|         |  */
     /*  read buffer:              |/ / consumed / | to be read /|         |  */
     /*                            |/ / / / / / / /|/ / / / / / /|         |  */
     /*                            +---------------+-----------------------+  */
     /*                                                                       */
     /*                                                         pos           */
     /*              +-------------------------------------------+-----------------+ */
     /*  input file: |                                           |                 | */
     /*              +-------------------------------------------+-----------------+ */
    //每次读取max_buffer_sizedata到缓存中
    len = read_packet_wrapper(s, dst, len);
    if (len == AVERROR_EOF) {
        /* do not modify buffer if EOF reached so that a seek back can
           be done without rereading data */
        s->eof_reached = 1;
    } else if (len < 0) {
        s->eof_reached = 1;
        s->error= len;
    } else {
        s->pos += len;//更新文件读取位置
        s->buf_ptr = dst; //更新buffer缓存位置
        s->buf_end = dst + len;
        ffiocontext(s)->bytes_read += len;
        s->bytes_read = ffiocontext(s)->bytes_read;
    }
}

read_packet_wrapper

static int read_packet_wrapper(AVIOContext *s, uint8_t *buf, int size)
{
    int ret;

    if (!s->read_packet)
        return AVERROR(EINVAL);
    //此函数指针为ffurl_read2
    ret = s->read_packet(s->opaque, buf, size);
    av_assert2(ret || s->max_packet_size);
    return ret;
}

int ffurl_read2(void *urlcontext, uint8_t *buf, int size)
{
    URLContext *h = urlcontext;

    if (!(h->flags & AVIO_FLAG_READ))
        return AVERROR(EIO);
    return retry_transfer_wrapper(h, buf, NULL, size, 1, 1);
}
//深入看下retry_transfer_wrapper
static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
                                         const uint8_t *cbuf,
                                         int size, int size_min,
                                         int read)
{
    int ret, len;
    int fast_retries = 5;
    int64_t wait_since = 0;

    len = 0;
    while (len < size_min) {
        // demux模块可以设置interrupt钩子函数,主动中断读取数据
        if (ff_check_interrupt(&h->interrupt_callback))
            return AVERROR_EXIT;
        //调用protocol的read函数读取数据
        ret = read ? h->prot->url_read (h, buf + len, size - len):
                     h->prot->url_write(h, cbuf + len, size - len);
        // 如果被系统调度中断,继续读
        if (ret == AVERROR(EINTR))
            continue;
        //如果为非阻塞模式,无论是否读取到了足量的数据,直接返回
        if (h->flags & AVIO_FLAG_NONBLOCK)
            return ret;
        //如果返回EAGAIN,需要重试
        if (ret == AVERROR(EAGAIN)) {
            ret = 0;
            //先连续读5次,快速重试
            if (fast_retries) {
                fast_retries--;
            } else {
                //快速重试搞完了,还是EAGAIN,就睡眠1000微秒,继续读,直到timeout
                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;
}

ff_file_protocol的url_read

const 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,
    .url_delete          = file_delete,
    .url_move            = file_move,
    .priv_data_size      = sizeof(FileContext),
    .priv_data_class     = &file_class,
    .url_open_dir        = file_open_dir,
    .url_read_dir        = file_read_dir,
    .url_close_dir       = file_close_dir,
    .default_whitelist   = "file,crypto,data"
};
static int file_read(URLContext *h, unsigned char *buf, int size)
{
    FileContext *c = h->priv_data;
    int ret;
    size = FFMIN(size, c->blocksize);
    ret = read(c->fd, buf, size); //直接调用系统调用read读取文件buffer
    if (ret == 0 && c->follow)
        return AVERROR(EAGAIN);
    if (ret == 0)
        return AVERROR_EOF;
    return (ret == -1) ? AVERROR(errno) : ret;
}

avio_seek

int64_t avio_seek(AVIOContext *s, int64_t offset, int whence)

whence 参数的取值与含义

whence 支持以下三种主要模式(定义在 libavutil/avio.h 中):

常量含义
SEEK_SET0从文件开头(起始位置)计算偏移量。 例如:offset=100 表示绝对位置 100。
SEEK_CUR1从当前文件位置计算偏移量。 例如:offset=50 表示从当前位置向后移动 50 字节。
SEEK_END2从文件末尾计算偏移量(通常 offset 为负数)。 例如:offset=-10 表示倒数第 10 字节。
AVSEEK_FORCE与其他标志按位或联合使用,强制执行查找操作
AVSEEK_SIZE与其他标志按位或联合使用,表示则返回文件大小(此时 offset 被忽略)
int64_t avio_seek(AVIOContext *s, int64_t offset, int whence)
{
    FFIOContext *const ctx = ffiocontext(s);
    int64_t offset1;
    int64_t pos;
    int force = whence & AVSEEK_FORCE;
    int buffer_size;
    int short_seek;
    whence &= ~AVSEEK_FORCE;

    if(!s)
        return AVERROR(EINVAL);

    if ((whence & AVSEEK_SIZE))
        return s->seek ? s->seek(s->opaque, offset, AVSEEK_SIZE) : AVERROR(ENOSYS);

    buffer_size = s->buf_end - s->buffer;
    // pos is the absolute position that the beginning of s->buffer corresponds to in the file
    pos = s->pos - (s->write_flag ? 0 : buffer_size);

    .........................................;

    offset1 = offset - pos; // "offset1" is the relative offset from the beginning of s->buffer
    s->buf_ptr_max = FFMAX(s->buf_ptr_max, s->buf_ptr);
    if ((!s->direct || !s->seek) &&
        offset1 >= 0 && 
        offset1 <= (s->write_flag ? s->buf_ptr_max - s->buffer : buffer_size)) {
        //当seek的返回在缓存缓冲区内部,就无需调用底层seek,直接更新buff_ptr即可
        s->buf_ptr = s->buffer + offset1;
    } else if ((!(s->seekable & AVIO_SEEKABLE_NORMAL) ||
               offset1 <= buffer_size + short_seek) &&
               !s->write_flag && offset1 >= 0 &&
               (!s->direct || !s->seek) &&
              (whence != SEEK_END || force)) {
        //如果不支持正常seek,并且是小范围seek就需要将数据先读到缓存中,而不是直接调用seek
        while(s->pos < offset && !s->eof_reached)
            fill_buffer(s);
        if (s->eof_reached)
            return AVERROR_EOF;
        s->buf_ptr = s->buf_end - (s->pos - offset);
    } else if(!s->write_flag && offset1 < 0 
              && -offset1 < buffer_size>>1 
              && s->seek && offset > 0) {
        int64_t res;
        // 如果是小范围向文件头seek,就先seek,然后再缓存一部分数据到缓存中
        pos -= FFMIN(buffer_size>>1, pos);
        if ((res = s->seek(s->opaque, pos, SEEK_SET)) < 0)
            return res;
        s->buf_end =
        s->buf_ptr = s->buffer;
        s->pos = pos;
        s->eof_reached = 0;
        fill_buffer(s);
        return avio_seek(s, offset, SEEK_SET | force);
    } else {
        //如果是向文件头大范围seek,或者是向文件尾部seek,就直接调用seek并清空缓存。
        int64_t res;
        if (s->write_flag) {
            flush_buffer(s);
        }
        if (!s->seek)
            return AVERROR(EPIPE);
        if ((res = s->seek(s->opaque, offset, SEEK_SET)) < 0)
            return res;
        ctx->seek_count++;
        if (!s->write_flag)
            s->buf_end = s->buffer;
        s->buf_ptr = s->buf_ptr_max = s->buffer;
        s->pos = offset;
    }
    s->eof_reached = 0;
    return offset;
}