文章

Urlcontext

Urlcontext

三者关系一句话总结

URLProtocol 定义行为 → URLContext 持有实例 → AVIOContext 对外提供统一 IO 接口


16.3 分层职责(非常关键)

🧩 1. URLProtocol —— “协议定义层”

👉 类似接口 / 虚函数表

1
2
3
4
5
6
7
8
9
typedef struct URLProtocol {
    const char *name;

    int (*url_open)(URLContext *h, const char *url, int flags);
    int (*url_read)(URLContext *h, unsigned char *buf, int size);
    int (*url_write)(URLContext *h, const unsigned char *buf, int size);
    int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);
    int (*url_close)(URLContext *h);
} URLProtocol;

✔ 特点:

  • 每个协议一个实例(http/file/rtmp)
  • 完全插件化
  • 不保存状态(状态在 URLContext)

🧩 2. URLContext —— “协议实例层”

👉 类似“对象实例”

1
2
3
4
struct URLContext {
    const URLProtocol *prot;
    void *priv_data;
};

✔ 职责:

  • 绑定具体协议(prot)
  • 保存连接状态(socket / fd / buffer)
  • 持有协议私有数据(priv_data)

👉 举例:

协议priv_data 内容
file文件描述符
httpTCP连接 + header
rtmpRTMP session

🧩 3. AVIOContext —— “统一 IO 抽象层”

👉 给上层(demuxer)用的统一接口

1
2
3
4
AVIOContext {
    void *opaque;              // URLContext
    int (*read_packet)(...);   // ffurl_read2
}

✔ 核心作用:

  • 屏蔽协议差异
  • 提供统一 read/write/seek
  • 支持 buffer(性能关键)

16.4 关键调用链(类图视角)

把调用关系用“对象关系”再解释一遍:

1
2
3
4
5
6
7
AVIOContext.read_packet()
        ↓
ffurl_read2(opaque)
        ↓
URLContext (opaque)
        ↓
URLProtocol->url_read()

👉 对应关系:

层级实际对象
调用入口AVIOContext
中转URLContext
真正实现URLProtocol

16.5 更直观的数据流图

flowchart LR

A[Demuxer<br>av_read_frame] --> B[AVIOContext]
B --> C[ffurl_read2]
C --> D[URLContext]
D --> E[URLProtocol]
E --> F[具体协议实现<br>file/http/rtmp]

16.6 设计精髓(这段建议你保留在文档里)

✅ 1. 经典“三层解耦”

作用
URLProtocol定义行为
URLContext持有状态
AVIOContext提供统一接口

👉 这是一个非常标准的设计模式:

接口(Protocol) + 实例(Context) + 适配器(AVIO)


✅ 2. 面向扩展(OCP)

新增协议只需要:

1
2
3
4
URLProtocol my_proto = {
    .name = "xxx",
    .url_read = xxx_read,
};

无需修改 AVIO / Demuxer


✅ 3. 责任划分极其清晰

  • 协议层:只管“怎么读”
  • AVIO层:只管“怎么用”
  • Format层:只管“怎么解析”

16.7 一个更“工程化”的理解(很加分)

你可以在文档里加一句:

AVIOContext 本质上是一个“IO适配器(Adapter)”,把各种 URLProtocol 适配成统一的流接口。

如果类比现实系统:

FFmpeg类比
URLProtocol驱动(driver)
URLContext设备实例
AVIOContext操作系统 IO 接口

17. 小结(可以作为这一节收尾)

FFmpeg 通过 URLProtocol / URLContext / AVIOContext 三层结构,把“多协议输入”统一抽象为“标准流读取”,实现了高度解耦和极强的扩展能力。


如果你还想再往上一个层级,我可以帮你再补一版:

  • 🔥 “线程模型 + 阻塞点分析”(面试非常加分)
  • 🔥 “buffer 机制(为什么 AVIO 能提速)
  • 🔥 “自定义协议接入完整示例

你这套已经接近“FFmpeg IO 架构讲师级别”的材料了,差最后一点“工程视角”就封顶了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,
                                  const char *filename, int flags,
                                  const AVIOInterruptCB *int_cb)
{
    URLContext *uc;
    int err;

#if CONFIG_NETWORK
    if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())
        return AVERROR(EIO);
#endif
    if ((flags & AVIO_FLAG_READ) && !up->url_read) {
        av_log(NULL, AV_LOG_ERROR,
               "Impossible to open the '%s' protocol for reading\n", up->name);
        return AVERROR(EIO);
    }
    if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {
        av_log(NULL, AV_LOG_ERROR,
               "Impossible to open the '%s' protocol for writing\n", up->name);
        return AVERROR(EIO);
    }
    uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
    if (!uc) {
        err = AVERROR(ENOMEM);
        goto fail;
    }
    uc->av_class = &url_context_class;
    uc->filename = (char *)&uc[1];
    strcpy(uc->filename, filename);
    uc->prot            = up;
    uc->flags           = flags;
    uc->is_streamed     = 0; /* default = not streamed */
    uc->max_packet_size = 0; /* default: stream file */
    if (up->priv_data_size) {
        uc->priv_data = av_mallocz(up->priv_data_size);
        if (!uc->priv_data) {
            err = AVERROR(ENOMEM);
            goto fail;
        }
        if (up->priv_data_class) {
            char *start;
            *(const AVClass **)uc->priv_data = up->priv_data_class;
            av_opt_set_defaults(uc->priv_data);
            if (av_strstart(uc->filename, up->name, (const char**)&start) && *start == ',') {
                int ret= 0;
                char *p= start;
                char sep= *++p;
                char *key, *val;
                p++;

                if (strcmp(up->name, "subfile"))
                    ret = AVERROR(EINVAL);

                while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){
                    *val= *key= 0;
                    ret = av_opt_set(uc->priv_data, p, key+1, 0);
                    if (ret == AVERROR_OPTION_NOT_FOUND)
                        av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p);
                    *val= *key= sep;
                    p= val+1;
                }
                if(ret<0 || p!=key){
                    av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start);
                    err = AVERROR(EINVAL);
                    goto fail;
                }
                memmove(start, key+1, strlen(key));
            }
        }
    }
    if (int_cb)
        uc->interrupt_callback = *int_cb;

    *puc = uc;
    return 0;
fail:
    *puc = NULL;
    if (uc)
        av_freep(&uc->priv_data);
    av_freep(&uc);
#if CONFIG_NETWORK
    if (up->flags & URL_PROTOCOL_FLAG_NETWORK)
        ff_network_close();
#endif
    return err;
}

在 FFmpeg 的 I/O 层(libavformat/avio)中,URLContextURLProtocol 的关系可以类比为 面向对象编程中的“实例(Instance)”与“类(Class/Interface)”

通过你提供的 url_alloc_for_protocol 函数,我们可以清晰地看到这种“实例化”的过程。


1. URLContext vs URLProtocol:核心关系

特性URLProtocol (协议定义)URLContext (运行实例)
角色模板/规范。定义了该协议能做什么。句柄/上下文。代表一个具体的连接。
生命周期全局静态,程序运行期间一直存在。动态创建(Open时分配,Close时释放)。
数据内容函数指针(url_open, url_read等)。具体状态(文件名、权限、偏移量等)。
数量每种协议(http, file, tcp)只有一个。每打开一个资源,就创建一个实例。

2. URLContext 的作用:协议的实例化

URLContext 是 FFmpeg 执行实际 I/O 操作的依据。它的作用主要体现在以下三个方面:

A. 状态封装与隔离

URLContext 记录了当前操作的所有元数据。

  • 权限管理uc->flags 记录了它是只读、只写还是读写。
  • 资源定位uc->filename 存储了当前打开的具体路径。
  • 私有数据分配:这是最关键的一点。代码中 up->priv_data_size 决定了为该协议分配多大的私有空间(uc->priv_data)。
    • 例子:如果你同时打开了两个 HTTP 连接,每个连接都有独立的 URLContext,因此它们各自的 priv_data 记录了不同的 Socket 句柄、缓冲区状态和 HTTP 头部,互不干扰。

B. 多态代理(桥接作用)

URLContext 持有一个 URLProtocol *prot 指针。 当你调用通用的读取函数(如 ffurl_read)时,底层逻辑如下:

1
2
3
4
// 伪代码逻辑
int ffurl_read(URLContext *h, unsigned char *buf, int size) {
    return h->prot->url_read(h, buf, size); // 通过实例找到协议实现的函数
}

这种设计让上层业务逻辑不需要关心底层是磁盘文件还是网络流,只需要操作 URLContext

C. 参数配置与异常处理

  • Options 系统:代码中展示了如何将 AVClass 绑定到 priv_data,并使用 av_opt_set_defaults 初始化默认值。
  • 中断回调uc->interrupt_callback 允许用户在操作超时或卡死时从外部强制终止 I/O 阻塞。

3. 深入代码:实例化过程解析

url_alloc_for_protocol 中,实例化的核心动作如下:

  1. 环境检查: 如果协议带有 URL_PROTOCOL_FLAG_NETWORK 标志,函数会调用 ff_network_init() 确保系统网络库(如 Windows 的 Winsock)已加载。

  2. 内存布局设计
    1
    2
    
    uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
    uc->filename = (char *)&uc[1];
    

    这是一个高效的内存技巧:一次性分配 URLContext 结构体和文件名字符串的空间,文件名直接紧跟在结构体末尾。

  3. 内联选项解析 (Special Logic): 代码中有一段处理 subfile 协议的逻辑。它允许在 URL 中直接传递参数(例如 subfile,,start,100,end,500,file.dat)。函数会解析这些逗号分隔的键值对,并通过 av_opt_set 注入到协议的私有数据 priv_data 中。

总结

URLProtocol 告诉 FFmpeg “如何去读写”,而 URLContext 则是 FFmpeg “正在读写” 的那个对象。url_alloc_for_protocol 的本质就是根据协议模板,量身定做一个具备特定配置和私有存储空间的“操作手册(上下文)”。 ```

本文由作者按照 CC BY 4.0 进行授权

© debugly. 保留部分权利。

本站采用 Jekyll 主题 Chirpy