avformat_open_input 打开URL的流程

------------------------------------------------------------
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流。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值