ffmpeg 从avio_write 到 udp_write

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);


架构明显把整体搞复杂了, 它把一个整体强制划分为不同的层,层与层之间靠接口或架构来衔接
但对于调用者来说又是把事情搞简单了. 调用者只关心本层代码就可以了.

架构一般都采用对象,所以对象的初始化就会很关键,搞清数据的来源,函数指针的来源.
 

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值