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);
 架构明显把整体搞复杂了, 它把一个整体强制划分为不同的层,层与层之间靠接口或架构来衔接
 但对于调用者来说又是把事情搞简单了. 调用者只关心本层代码就可以了.
架构一般都采用对象,所以对象的初始化就会很关键,搞清数据的来源,函数指针的来源.