ffmpeg 从avio_write 到 udp_write

news/2024/7/10 18:44:36 标签: ffmpeg, avio_write, udp_write

ffmpegavio_writeudp_write
---------------------------------------------
author: hjjdebug
date: 2024年 03月 11日 星期一 14:16:44 CST
description: ffmpegavio_writeudp_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);


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

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


http://www.niftyadmin.cn/n/5422271.html

相关文章

单片机灭火避障小车设计

目录 目录 II 摘要 III 1 智能小车概述 5 1.1 国内外研究动态 5 1.2 课题的目的和意义 5 2 系统设计概述 6 2.1 系统设计要求 6 2 总体方案设计 7 2.1 硬件设计 7 2.1.1 车体设计 7 2.1.2 主控制器模块 8 2.1.3 电源模块 8 2.1.4 电机驱动模块 9 2.2 火源检测模块 10 2.3 避障…

JavaScript 设计模式之访问者模式

访问者模式 将一些必要的方法统一封装在一个对象中管理起来 假使我们有以下对象&#xff0c;提供了 splice、push、pop 三个方法&#xff0c;都基于 Array 来实现 const Visitor (() > {return {splice() {let args Array.prototype.splice.call(arguments, 1);return …

Python机器学习预测+回归全家桶,新增TCN,BiTCN,TCN-GRU,BiTCN-BiGRU等组合模型预测...

截止到本期&#xff0c;一共发了4篇关于机器学习预测全家桶Python代码的文章。参考往期文章如下&#xff1a; 1.机器学习预测全家桶-Python&#xff0c;一次性搞定多/单特征输入&#xff0c;多/单步预测&#xff01;最强模板&#xff01; 2.机器学习预测全家桶-Python&#xff…

Flink创建TableEnvironment

在官网上&#xff0c;Flink创建TableEnvironment有两种方式&#xff1a;1.通过静态方法 TableEnvironment.create() 创建&#xff1b;2.从现有的 StreamExecutionEnvironment 创建一个 StreamTableEnvironment 与 DataStream API 互操作 import org.apache.flink.table.api.En…

Android开发 Activity启动模式、ViewModel与LiveData,及Kotlin Coroutines

目录 Activity启动模式 onNewIntent解释 Activity启动模式的考虑时机 Service启动模式 ContentProvider的作用 Broadcast的注册方式 AsyncTask的作用 ViewModel LiveData Kotlin Coroutines 结合使用 Activity启动模式 Android中Activity的启动模式有四种&#xff0…

驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接问题解决

om.microsoft.sqlserver.jdbc.SQLServerException:驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server建立安全连接。错误:“The server selected protocol version TLS10 is not accepted by client preferences [TLS13, TLS12]”你的项目要连接sqlserver数据库&#xf…

面向对象设计之里氏替换原则

设计模式专栏&#xff1a;http://t.csdnimg.cn/4Mt4u 思考&#xff1a;什么样的代码才算违反里氏替换原则&#xff1f; 目录 1.里氏替换原则的定义 2.里氏替换原则与多态的区别 3.违反里氏替换原则的反模式 4.总结 1.里氏替换原则的定义 里氏替换原则&#xff08;Liskov S…

在C#中使用NModbus4通信库执行【读】操作

在工业自动化和控制系统中&#xff0c;Modbus协议是一种广泛使用的通信协议。它是一种基于主从结构的串行通信协议&#xff0c;简单、可靠且易于实现。在C#中&#xff0c;可以使用NModbus4库来执行Modbus通信。本文将详细介绍如何使用NModbus4库在C#中执行Modbus读操作。 1. N…