------------------------------------------------------------
author: hjjdebug
date: 2024年 08月 14日 星期三 13:26:15 CST
description: avformat_open_input 打开URL的流程
------------------------------------------------------------
0 in __libc_open64 of ../sysdeps/unix/sysv/linux/open64.c:37
1 in avpriv_open of libavutil/file_open.c:84
2 in file_open of libavformat/file.c:231
3 in ffurl_connect of libavformat/avio.c:213
4 in ffurl_open_whitelist of libavformat/avio.c:347
5 in ffio_open_whitelist of libavformat/aviobuf.c:1152
6 in io_open_default of libavformat/options.c:191
7 in init_input of libavformat/utils.c:445
8 in avformat_open_input of libavformat/utils.c:548
9 in open_input_file of filtering_video.c:39
10 in main of filtering_video.c:211
第9层,10层是用户代码,就不用分析了,
测试代码是ffmpeg自带的Doc/example/filtering_video.c
第8层也不用分析了,请参考
分析 avformat_open_input 数据读取过程-CSDN博客
avformat_open_input 函数打开一个本地文件必然要做的三件事情是
1. 打开文件
2. 读取数据
3. 分析数据.
本博客针对的是 1. 打开文件,
分析到init_input为止.
所附代码是精简化后的代码,帮助理解.
首先说明一下,设置 open 断点中断不了,应该设置open64.
因为在64位机器上, 被编译器的编译选项重新将open改名为open64了
第1层: avpriv_open
----------------------------------------
输入参数: URLContext *h
输出参数: buf, size
返回值: 读取的字节数
功能: 从URLContext 中读取数据到buf
----------------------------------------
int avpriv_open(const char *filename, int flags, ...)
{
unsigned int mode = 0;
//处理可变参数
va_list ap;
va_start(ap, flags);
if (flags & O_CREAT)
mode = va_arg(ap, unsigned int);
va_end(ap);
#ifdef O_CLOEXEC
flags |= O_CLOEXEC;
#endif
#ifdef O_NOINHERIT
flags |= O_NOINHERIT;
#endif
int fd = open(filename, flags, mode); // 调用libc接口, 实际是open64
#if HAVE_FCNTL
if (fd != -1) {
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
av_log(NULL, AV_LOG_DEBUG, "Failed to set close on exec\n");
}
#endif
return fd;
}
第2层: file_open
------------------------------------------------
输入参数: URLContext *h, filename,flags
输出参数: h->priv_data->fd
返回值: 读取的字节数
功能: 打开文件, 把描述符保存到URLContext 中
------------------------------------------------
static int file_open(URLContext *h, const char *filename, int flags)
{
av_strstart(filename, "file:", &filename);
FileContext *c = h->priv_data;
int access; //组织访问标志, 可读,可写,可创建,可破环
if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) {
access = O_CREAT | O_RDWR;
if (c->trunc) access |= O_TRUNC;
}
else if (flags & AVIO_FLAG_WRITE) {
access = O_CREAT | O_WRONLY;
if (c->trunc) access |= O_TRUNC;
}
else {
access = O_RDONLY;
}
#ifdef O_BINARY
access |= O_BINARY;
#endif
int fd = avpriv_open(filename, access, 0666); //打开文件,保存到FileContext
c->fd = fd;
//获取文件属性
struct stat st;
h->is_streamed = !fstat(fd, &st) && S_ISFIFO(st.st_mode);
//不是流又想写,把包尺寸增大. 别用32k 提高性能
if (!h->is_streamed && flags & AVIO_FLAG_WRITE)
h->min_packet_size = h->max_packet_size = 262144;
if (c->seekable >= 0)
h->is_streamed = !c->seekable; // 能seek 就不是流
return 0;
}
第3层: ffurl_connect
------------------------------------------------
输入参数: URLContext *h, AVDictionary **options
输出参数: h
返回值:
功能: 打开文件, 把描述符保存到URLContext 中
------------------------------------------------
int ffurl_connect(URLContext *uc, AVDictionary **options)
{
int err;
err =
uc->prot->url_open2 ? uc->prot->url_open2(uc,
uc->filename,
uc->flags,
options) :
uc->prot->url_open(uc, uc->filename, uc->flags); //走的是改函数
uc->is_connected = 1;
return 0;
}
从URLContext 中取到prot 对象, 这个对象是一个在全局变量中定义的对象.
调用它的url_open 函数, 本例中它调用到了file_open, 因为它找到的prot 对象是ff_file_protocol
它是libavformat/file.c中定义的一个协议
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"
};
第4层: ffurl_open_whitelist
------------------------------------------------
输入参数: filename,flag,int_cb,ooptions,whitelist,blacklist,parent
输出参数: URLContext **puc
返回值:
功能: 打开文件, 把描述符保存到URLContext 中
------------------------------------------------
该函数虽然参数众多,但一般下面这些项都是空的
ooptions,whitelist,blacklist,parent
为简单起见把无用代码都删掉,简化如下
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)
{
int ret = ffurl_alloc(puc, filename, flags, int_cb); //根据文件名称分配puc URLContext对象
ret = ffurl_connect(*puc, options); //上面的分析
return ret;
}
//其再向下继续调用,限于篇幅就不再分析了,到此也容易理解了.
int ffurl_alloc(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb)
{
const URLProtocol *p = url_find_protocol(filename); //查找协议
return url_alloc_for_protocol(puc, p, filename, flags, int_cb); //分配URLContext 对象
}
第5层: ffio_open_whitelist
------------------------------------------------
输入参数: AVIOContext **s,filename,flag,int_cb,ooptions,whitelist,blacklist
输出参数: AVIOContext **s
返回值:
功能: 打开文件, 把描述符保存到AVIOContext 中
------------------------------------------------
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 = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);
if (err < 0) return err;
*s = NULL;
err = ffio_fdopen(s, h); //参考下面分析,创建s, 把h中参数复制到s
if (err < 0) { ffurl_close(h); return err; }
return 0;
}
//创建 AVIOContext 对象
int ffio_fdopen(AVIOContext **s, URLContext *h)
{
int max_packet_size = h->max_packet_size;
int buffer_size; // 计算buffer_size
if (max_packet_size) {
buffer_size = max_packet_size; /* no need to buffersize more than one packet */
} else {
buffer_size = IO_BUFFER_SIZE;
}
uint8_t *buffer = av_malloc(buffer_size); // 给AVIOContext 分配buffer
//创建AVIOContext 对象,并赋值ffurl_read,rrurl_write,ffurl_seek 函数
*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);
(*s)->direct = h->flags & AVIO_FLAG_DIRECT;
//继续为AVIOContext 对象赋值
(*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 = (int (*)(void *, int))h->prot->url_read_pause;
(*s)->read_seek = (int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek;
if (h->prot->url_read_seek) (*s)->seekable |= AVIO_SEEKABLE_TIME;
}
(*s)->short_seek_get = (int (*)(void *))ffurl_get_short_seek;
(*s)->av_class = &ff_avio_class; // 这是一个标准ffmpeg 类
return 0;
}
第6层: io_open_default
------------------------------------------------
输入参数: AVFormatContext *s, url,flags,options
输出参数: AVIOContext **pb
返回值:
功能: 打开文件, 把描述符保存到AVIOContext 中
------------------------------------------------
static int io_open_default(AVFormatContext *s, AVIOContext **pb,
const char *url, int flags, AVDictionary **options)
{
return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, \
options, s->protocol_whitelist, s->protocol_blacklist); //参考上面分析
}
第7层: init_input
------------------------------------------------------------------
输入参数: AVFormatContext *s, filename,AVDictionary**options
输出参数: AVFormatContext s->iformat
返回值:
功能: 打开文件,创建s->pb, 探测文件格式,赋值给s->iformat
------------------------------------------------------------------
/* Open input file and probe the format if necessary. */
//打开文件,探测文件格式
static int init_input(AVFormatContext *s, const char *filename,
AVDictionary **options)
{
int ret;
//s->io_open 调用了io_open_default, 就是上面的分析过程,创建了AVIOContext s->pb
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
return ret;
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize); // 参考
}
第8层: 忽略,与参考重复,请参考
分析 avformat_open_input 数据读取过程-CSDN博客
小结: 文件的打开过程并不是直接的open, 因为传来的字符串是URL,所以它要先分析
解析字符串,从查协议开始,发现是文件才转而走打开文件的流程. 沿途创建的几个对象.
其中AVIOContext 是带缓冲的读写对象
URLContext 是统一的资源读写对象
FileContext 是针对本地文件的读写对象
分割成几个对象是为了分割功能和接口简化(单一功能原则和接口简化原则)
最后它把打开的文件描述符放在了FileContext c对象的 c->fd下
无所谓它放到哪里,因为对上层来说, 下面是被隐藏的,上层调用无需关心.
可以想象打开udp流也是这样的流程,直到最后实现时打开的是udp流。