ffmpeg 从avio_write 到 udp_write
---------------------------------------------
author: hjjdebug
date: 2024年 03月 11日 星期一 14:16:44 CST
description: ffmpeg 从avio_write 到 udp_write
---------------------------------------------
文章目录:
1. main 调用avio_write
2. avio_write 调用flush_buffer
3. flush_buffer 调用的writeout函数
3.1, 挖掘一下为什么h->max_packet_size 是1472
3.2, 可见1472又由s->pkt_size决定,而s是h->priv_data,所以要看h是如何构建的!! 还要关心h->priv_data是如何构建的.
3.3, 协议的发现, 其中filename="udp://239.1.1.51:8001"
4. writeout 函数调用了ffurl_write 函数,
5. ffurl_write 直接调用了retry_transfer_wrapper
6. udp_write 函数参数, 有一个内存handle, 数据指针和大小, 是实际传递数据的地方.
int nRet = avio_open(&pWriteCtx, "udp://239.1.1.51:8001), AVIO_FLAG_WRITE);
只分析一句话.
avio_write(pWriteCtx, buf, sizeof(buf));
在gdb中, 中断在udp_write 函数处, 打bt 命令显示调用栈如小.
#0 udp_write (h=0x5555555622c0, buf=0x5555555729c0 <incomplete sequence \370\200>, size=1472) at libavformat/udp.c:1204
#1 0x00007ffff7cf7bed in retry_transfer_wrapper (h=0x5555555622c0, buf=0x5555555729c0 <incomplete sequence \370\200>, size=1472, size_min=1472, transfer_func=0x7ffff7eb3f2b <udp_write>) at libavformat/avio.c:370
#2 0x00007ffff7cf7de7 in ffurl_write (h=0x5555555622c0, buf=0x5555555729c0 <incomplete sequence \370\200>, size=1472) at libavformat/avio.c:423
#3 0x00007ffff7cf8cd9 in writeout (s=0x555555573000, data=0x5555555729c0 <incomplete sequence \370\200>, len=1472) at libavformat/aviobuf.c:170
#4 0x00007ffff7cf8e2e in flush_buffer (s=0x555555573000) at libavformat/aviobuf.c:191
#5 0x00007ffff7cf909e in avio_write (s=0x555555573000, buf=0x7fffffffc610 <incomplete sequence \370\200>, size=5824) at libavformat/aviobuf.c:238
#6 0x0000555555555571 in main () at main.cpp:54
调用层次分析: 我们需要重点关心哪些内容?
1. main 调用avio_write, 要把buf地址开始,size=5824的数据发送出去, 同时还传了一个地址s=0x555555573000.
这个s 是什么呢? 是一个内存handle, 实际就是对象地址, 有什么用途,从这个地址可以找到很多有用的信息.
2. avio_write 调用flush_buffer, 只给了内存handle, 还是给它起个名吧,它叫AVIOContext. 要求把它的缓存刷新出去,
要想把5824个数据都刷出去,也许需要刷新好几次缓存吧.
3. flush_buffer 调用的writeout函数,
writeout的参数仍然是AVIOContext, 包括data,len, 这个data应该是缓存的地址,长度1472是缓存的长度.
现在来确认.
这个缓存指针和长度是在哪里赋值的? 应该在初始化时赋值的. 具体位置:
int ffio_fdopen(AVIOContext **s, URLContext *h) //只需要认识URLContext 就可以了.
{
uint8_t *buffer = NULL;
int buffer_size, max_packet_size;
max_packet_size = h->max_packet_size; // h->max_packet_size 是1472
if (max_packet_size) {
buffer_size = max_packet_size; // buffer_size 由 max_packet_size 决定, max_packet_size 由h->max_packet_size 决定
} else {
buffer_size = IO_BUFFER_SIZE;
}
if (!(h->flags & AVIO_FLAG_WRITE) && h->is_streamed) {
if (buffer_size > INT_MAX/2)
return AVERROR(EINVAL);
buffer_size *= 2;
}
buffer = av_malloc(buffer_size); // buffer 地址和大小由该语句确定.
if (!buffer)
return AVERROR(ENOMEM);
*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)->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;
return 0;
}
3.1, 挖掘一下为什么h->max_packet_size 是1472
0 in udp_open of libavformat/udp.c:830
1 in ffurl_connect of libavformat/avio.c:213
2 in ffurl_open_whitelist of libavformat/avio.c:347
3 in ffio_open_whitelist of libavformat/aviobuf.c:1152
4 in avio_open2 of libavformat/aviobuf.c:1166
5 in avio_open of libavformat/aviobuf.c:1139
6 in main of main.cpp:45
UDPContext *s = h->priv_data; // 其中s 是UDPContext
if (s->pkt_size > 0)
h->max_packet_size = s->pkt_size; // 1472
3.2, 可见1472又由s->pkt_size决定,而s是h->priv_data,所以要看h是如何构建的!! 还要关心h->priv_data是如何构建的.
这里h指的是URLContext, h->priv_data是UDPContext
0 in url_alloc_for_protocol of libavformat/avio.c:120
1 in ffurl_alloc of libavformat/avio.c:303
2 in ffurl_open_whitelist of libavformat/avio.c:316
3 in ffio_open_whitelist of libavformat/aviobuf.c:1152
4 in avio_open2 of libavformat/aviobuf.c:1166
5 in avio_open of libavformat/aviobuf.c:1139
6 in main of main.cpp:44
其中协议(up, urlprotocal)的私有类赋值给私有数据变量,然后设置给类的默认数据
*(const AVClass **)uc->priv_data = up->priv_data_class;
av_opt_set_defaults(uc->priv_data); // 这个私有数据uc->priv_data就是UDPContext
其中up->priv_data_class 就是 udp_class, up 是UrlProtocol 指针
3.3, 协议的发现, 其中filename="udp://239.1.1.51:8001"
p = url_find_protocol(filename);
根据名称,找到的是下面这个协议
const URLProtocol ff_udp_protocol = {
.name = "udp",
.url_open = udp_open,
.url_read = udp_read,
.url_write = udp_write,
.url_close = udp_close,
.url_get_file_handle = udp_get_file_handle,
.priv_data_size = sizeof(UDPContext),
.priv_data_class = &udp_class, // 这个是私有数据类
.flags = URL_PROTOCOL_FLAG_NETWORK,
};
下面是udp_class
static const AVClass udp_class = { //UDPContext 第一个成员变量就是udp_class
.class_name = "udp",
.item_name = av_default_item_name,
.option = options, // 该options 描述了UDPContext 的默认的成员变量的值
.version = LIBAVUTIL_VERSION_INT,
};
其option 的默认选项是该文件udp.c 下定义的options 选项
其中有一项为pkt_size, 默认1472, 刨根刨到底了.
{ "pkt_size", "Maximum UDP packet size", OFFSET(pkt_size), AV_OPT_TYPE_INT, { .i64 = 1472 }, -1, INT_MAX,
.flags = D|E },
4. writeout 函数调用了ffurl_write 函数,
ffurl_write 函数的调用参数与writeout 的调用参数数据没有改变,但内存handle 变了,从s 变成了h.
经查,h 是URLContext, h=s->opaque
所以要关注一下s->opaque 是怎样赋值的. 请参考avio_alloc_context函数, 它保留了h,并用
ffurl_read, ffurl_write, ffurl_seek 给s的函数指针赋值.有点多此一举吗? 非也,
ffurl_write 函数在avio.c中, 并不在aviobuf.c中,不是一个文件, 当写aviobuf.c时,avio.c文件已经存在,所以可以调用
但直接调用会显得耦合太紧, 所以通过函数指针调用的. 只需要在创建对象时,将地址付给函数指针即可.
5. ffurl_write 直接调用了retry_transfer_wrapper
retry_transfer_wrapper 内存handle 没有变是URLContext, 数据没有变,但多了一个transfer_func,
transfer_func 的地址是h->prot->url_write, 实际指向是udp_write 地址
6. udp_write 函数参数, 有一个内存handle, 数据指针和大小, 是实际传递数据的地方.
为什么一直传递这个URLContext 指针? 就是可以从它那里拿到UDPContext, 然后才实际发送数据.
例如udp_write 的实现
UDPContext *s=h->priv_data; //要关注一下h->priv_data是怎样赋值的.
ret = sendto (s->udp_fd, buf, size, 0,
(struct sockaddr *) &s->dest_addr,
s->dest_addr_len);
架构明显把整体搞复杂了, 它把一个整体强制划分为不同的层,层与层之间靠接口或架构来衔接
但对于调用者来说又是把事情搞简单了. 调用者只关心本层代码就可以了.
架构一般都采用对象,所以对象的初始化就会很关键,搞清数据的来源,函数指针的来源.