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函数流程总结:
- 当ffmpeg config中打开了一些protocol之后,在config时会自动创建ffmpeg/libavformat/protocol_list.c文件,里面定义了一个全局数组url_protocols,其定义了当前配置了哪些protocol。 ffurl_open_whitelist根据uri中的协议前缀找到对应的protocol,然后将其封装在URLContext.prot中
- 通过白名单和黑名单列表检查是否找错了protocol
- 调用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_SET | 0 | 从文件开头(起始位置)计算偏移量。 例如:offset=100 表示绝对位置 100。 |
| SEEK_CUR | 1 | 从当前文件位置计算偏移量。 例如:offset=50 表示从当前位置向后移动 50 字节。 |
| SEEK_END | 2 | 从文件末尾计算偏移量(通常 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;
}