fmpeg 中 avio_check() 代码分析


author: hjjdebug
date: 2025年 02月 28日 星期五 15:03:10 CST
descripton ffmpeg 中 avio_check() 代码分析


文章目录

    • 1. 测试程序
      • 程序执行结果
    • 2. 源码注释:
    • 3. 分析程序
      • 3.1. ff_file_protocol, 是URLProtocol的一个实例
      • 3.2. FileContext 是file protocol 下使用的一个私有对象结构
      • 3.3.URLContext, 跟用户接口的对象,它包含URLProtocol 实例,还包含URLProtocol中定义的私有数据,是更上层的结构对象
    • 4. 小结:

鉴于avio_check 百度搜索的结果不对,
而且其使用很简单,内涵却很丰富,所以花点时间写了该博文.
分析方法: 先写一个简单的测试程序,然后再跟踪调试代码.

1. 测试程序

$ cat main.c 
#include 
int main(int argc, char *argv[])
{
	if(argc<2)
	{
		printf("Usage %s \n", argv[0]);
		printf("Example %s main.c\n",argv[0]);
		return 0;
	}
	char *file =argv[1];
	int flags = AVIO_FLAG_READ|AVIO_FLAG_WRITE;
	//权限检查函数
	//深刻理解需要跟踪代码,它的代码一层一层的.
	int result = avio_check(file, flags); 
	//关键是返回值是什么意思?返回的是权限集合
	printf("result:%d\n",result);
	if(result>0) //<0 为失败, =0为啥权限都没有
	{
		if (result & AVIO_FLAG_READ) {
			printf("<%s>文件可读\n",file);
		} else {
			printf("<%s>文件不可读\n",file);
		}
		if (result & AVIO_FLAG_WRITE) {
			printf("<%s>文件可写\n",file);
		}
		else
		{
			printf("<%s>文件不可写\n",file);
		}
	}
	return 0;
}

程序执行结果

可以正确显示文件的读写属性,不存在的文件会返回错误.
$ ./avio_check main.c
result:3
文件可读
文件可写
hjj@hjj-7090:~/test/avio_check$ ./avio_check haha.txt
result:-2
hjj@hjj-7090:~/test/avio_check$ touch haha.txt
hjj@hjj-7090:~/test/avio_check$ ./avio_check haha.txt
result:3
文件可读
文件可写
hjj@hjj-7090:~/test/avio_check$ chmod -w haha.txt
hjj@hjj-7090:~/test/avio_check$ ls -l haha.txt
-r–r–r-- 1 hjj hjj 0 2月 28 14:58 haha.txt
hjj@hjj-7090:~/test/avio_check$ ./avio_check haha.txt
result:1
文件可读
文件不可写

2. 源码注释:

有点面向对象的意思,先创建一个对象,再调用对象的方法

int avio_check(const char *url, int flags)
{
    URLContext *h;
//先分配一个URLContext 对象h, 有后面一大堆描述做铺垫,跟踪该代码应该就能看懂了.
    int ret = ffurl_alloc(&h, url, flags, NULL); 
    if (ret < 0)
        return ret;

	//2层楼的好处是接口简单,功能强大. 不过给阅读理解确实增加了点困难.
    if (h->prot->url_check) { //由于协议下url_check非空,所以执行url_check函数
        ret = h->prot->url_check(h, flags); //这个函数实际指向file_check
    } else {
        ret = ffurl_connect(h, NULL); //这些代码未走不用分析.
        if (ret >= 0)
            ret = flags;
    }

    ffurl_close(h);
    return ret;
}

//可见传来的mask是检测的集合,返回值也是权限的集合,
//这里的集合并没有多少,就2种权限,
//返回值为负数是访问错误
//实际干活还是在底层,上层都是框架.
//注意,传来的是上层URLContext *, 这样的好处是它能够访问到更多的数据
static int file_check(URLContext *h, int mask)
{
	int ret=0;
	 const char *filename = h->filename;
    if (access(filename, F_OK) < 0)
        return AVERROR(errno);
    if (mask&AVIO_FLAG_READ)
        if (access(filename, R_OK) >= 0)
            ret |= AVIO_FLAG_READ;
    if (mask&AVIO_FLAG_WRITE)
        if (access(filename, W_OK) >= 0)
            ret |= AVIO_FLAG_WRITE;
	return ret
}

3. 分析程序

分析一下为协议而分配UrlContext的过程
0 in url_alloc_for_protocol of libavformat/avio.c:110
1 in ffurl_alloc of libavformat/avio.c:303
2 in avio_check of libavformat/avio.c:477
3 in main of main.c:14

首先要明白一个URLProtocol的概念, URLProtocol结构的定义这里就不copy了,
直接看它的一个实例.

3.1. ff_file_protocol, 是URLProtocol的一个实例

它包话一个私有类priv_data_class,指向file_class, 私有数据大小是40bytes
其主要功能是管理对文件的所有操作.

gdb 下观察这个实例
(gdb) p up
$1 = (const URLProtocol *) 0x7ffff7f735c0
(gdb) p *up
$2 = {
name = 0x7ffff7f02e33 “file”,
url_open = 0x7ffff7d322c1 ,
url_open2 = 0x0,
url_accept = 0x0,
url_handshake = 0x0,
url_read = 0x7ffff7d31f9e ,
url_write = 0x7ffff7d3202b ,
url_seek = 0x7ffff7d324ae ,
url_close = 0x7ffff7d325b4 ,
url_read_pause = 0x0,
url_read_seek = 0x0,
url_get_file_handle = 0x7ffff7d32093 ,
url_get_multi_file_handle = 0x0,
url_get_short_seek = 0x0,
url_shutdown = 0x0,
priv_data_class = 0x7ffff7f8c420 ,
priv_data_size = 40,
flags = 0,
url_check = 0x7ffff7d320b4 ,
url_open_dir = 0x7ffff7d325e0 ,
url_read_dir = 0x7ffff7d32633 ,
url_close_dir = 0x7ffff7d3299f ,
url_delete = 0x7ffff7d3217f ,
url_move = 0x7ffff7d3221b ,
default_whitelist = 0x7ffff7f02e48 “file,crypto,data”
}

从文件定义中观察这个对象的定义
file.c 中的定义了一个URl协议对象 ff_file_protocol
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), //大小是Context大小
.priv_data_class = &file_class, //关注这个,定义了一个AVClass 类
.url_open_dir = file_open_dir,
.url_read_dir = file_read_dir,
.url_close_dir = file_close_dir,
.default_whitelist = “file,crypto,data”
};
注意,这些protocol 协议中定义的函数,例如file_open,file_read,file_check 其传递的参数都是URLContext *h
而不是那私有对象的指针,传递上层指针的好处是可以访问到更多的数据.
关注它包含的对象file_class, 它是一个AVClass 对象, 定义该对象就是为了初始化一个FileContext对象
static const AVClass file_class = {
.class_name = “file”,
.item_name = av_default_item_name,
.option = file_options,
.version = LIBAVUTIL_VERSION_INT,
};
该私有对象FileContext 是干什么的? 就是当你打开一个文件时,你肯定要保留文件描述符, 你可能也要保留这个文件的其它信息,
所以就定义了该私有结构FileContext.

顺便也看看file_options 的定义,这实际上是FileContext 某些成员变量的描述信息.
static const AVOption file_options[] = {
{ “truncate”, “truncate existing files on write”, offsetof(FileContext, trunc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
{ “blocksize”, “set I/O operation maximum block size”, offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
{ “follow”, “Follow a file as it is being written”, offsetof(FileContext, follow), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
{ “seekable”, “Sets if the file is seekable”, offsetof(FileContext, seekable), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 0, AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_ENCODING_PARAM },
{ NULL }
};

3.2. FileContext 是file protocol 下使用的一个私有对象结构

由file_class对象可以创建一个FileContext对象,FileContext大小由协议的priv_data_size指定
FileContext 对象的创见方法如下: p指针实际上就是FileContext指针
up 是url-protocal的意思
void *p = av_mallocz(up->priv_data_size); //对文件协议来说它是40
*(const AVClass **)p = up->priv_data_class; //对文件协议来说它是file_class地址
av_opt_set_defaults§; //对context初始化
我们把这个分配的指针放到哪里呢?
放到它的上层对象中保管. 上层对象要保管ff_file_protocol 协议地址,也要保管FileContext对象地址
这个上层对象就是UrlContext
分层管理的好处是功能强大,它可以管理很多协议,例如可以管理http协议,udp协议,rtp协议等
但接口很简单. 用户只需要跟上层打交道.
FileContext 我们已经说了很多了,顺便把FileContext结构也copy一下吧,看看它会保留哪些数据.
typedef struct FileContext {
const AVClass *class;
int fd;
int trunc;
int blocksize;
int follow;
int seekable;
#if HAVE_DIRENT_H
DIR *dir;
#endif
} FileContext;

3.3.URLContext, 跟用户接口的对象,它包含URLProtocol 实例,还包含URLProtocol中定义的私有数据,是更上层的结构对象

typedef struct URLContext {
//首先,它是一个AVClass,叫ffurl_context_class 其option会对后面的几个成员变量进行说明
const AVClass *av_class;
const struct URLProtocol *prot; //协议是要保留的
void *priv_data; //私有数据,就是上面讲的FileContext 是要保留的
char *filename; 其它的变量意义都比较简单,一目了然.就不用注释了.
int flags;
int max_packet_size;
int is_streamed;
int is_connected;
AVIOInterruptCB interrupt_callback;
int64_t rw_timeout;
const char *protocol_whitelist;
const char *protocol_blacklist;
int min_packet_size;
} URLContext;

既然URLContext 是一个AVClass 类, 我们也应该看看它的描述对象ffurl_context_class.
以及它所对应的options.
const AVClass ffurl_context_class = {
.class_name = “URLContext”,
.item_name = urlcontext_to_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
.child_next = urlcontext_child_next,
#if FF_API_CHILD_CLASS_NEXT
.child_class_next = ff_urlcontext_child_class_next,
#endif
.child_class_iterate = ff_urlcontext_child_class_iterate,
};

#define OFFSET(x) offsetof(URLContext,x)
static const AVOption options[] = {
{“protocol_whitelist”, “List of protocols that are allowed to be used”, OFFSET(protocol_whitelist), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },
{“protocol_blacklist”, “List of protocols that are not allowed to be used”, OFFSET(protocol_blacklist), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },
{“rw_timeout”, “Timeout for IO operations (in microseconds)”, offsetof(URLContext, rw_timeout), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D|E },
{ NULL }
};
URLContext 对象是new出来的,并顺便还开辟了一段空间来保存文件名称.
uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
uc->av_class = &ffurl_context_class;

用户分配一个URLContext h,然后就可以拿h来说事了.
再去阅读第2部分的代码注释,再跟踪调试代码,表示可以理解了.

4. 小结:

本来如果不用架构, 对文件而言直接3个access 就能够判别出文件的属性, 但是加了架构,平添了很多代码.
带来的好处是,它的功能更强大,它不仅支持文件协议,还支持其它协议.而用户的上层调用接口是一样的.
但它的执行效率比无架构的执行了更多代码.这个代价是必需付出的.

你可能感兴趣的:(ffmpeg,avio_check)