FFMPEG4.1源码分析之 av_opt_copy()

1 av_opt_copy()


av_opt_copy() 声明:

  • 所属库:libavutil(lavu),注意是lavu库,这个是ffmpeg中的功能库,代表的是一个功能
  • 头文件:libavutil/opt.h
  • 声明:从src对象中拷贝选项到dest对象中。
               1)要点一:注意理解“拷贝选项”的意思是将src对象中与src->av_class->option能对应上的那些成员,拷贝一份值到目的dest对象对应的成员。
               2)要点二:
    正如声明上的说明,dest对象的原有字段所占用的内存将被释放掉,然后重新申请内存,并拷贝src对象对应成员的值到新申请的内存上——除非src和des的成员都指向同样的内存,这种情况下不会申请新的内存。
    /**
     * Copy options from src object into dest object.
     *
     * Options that require memory allocation (e.g. string or binary) are malloc'ed in dest object.
     * Original memory allocated for such options is freed unless both src and dest options points to the same memory.
     *
     * @param dest Object to copy from
     * @param src  Object to copy into
     * @return 0 on success, negative on error
     */
    int av_opt_copy(void *dest, const void *src);

     

 av_opt_copy() 源码:

  • 源文件:libutil/opt.c
    int av_opt_copy(void *dst, const void *src)
    {
        const AVOption *o = NULL;
        const AVClass *c;
        int ret = 0;
    
        // 验证参数有效性
        if (!src)
            return AVERROR(EINVAL);
    
        c = *(AVClass **)src;
        if (!c || c != *(AVClass **)dst)
            return AVERROR(EINVAL);
    
        // 循环获取src的选项,然后将选项对应的src成员值拷贝到dest成员
        while ((o = av_opt_next(src, o))) {
            void *field_dst = (uint8_t *)dst + o->offset;  // 指向目标成员的指针
            void *field_src = (uint8_t *)src + o->offset;  // 指向源成员的指针
            uint8_t **field_dst8 = (uint8_t **)field_dst;  // 指向目标成员的指针(注意类型不同)
            uint8_t **field_src8 = (uint8_t **)field_src;  // 指向原成员的指针(注意类型不同)
    
            // 字符串类型
            if (o->type == AV_OPT_TYPE_STRING) {       
                // 判断源串与目的串是否是同一个,如果不是,则释放目的串空间  
                // 如果是的话就不能释放空间了,否则源串也没了,源串指针就变成悬空指针了
                if (*field_dst8 != *field_src8)
                    av_freep(field_dst8);
                // 分配空间并拷贝源串到目的串
                *field_dst8 = av_strdup(*field_src8);
                // 
                if (*field_src8 && !*field_dst8)
                    ret = AVERROR(ENOMEM);
    
            // 二进制数
            } else if (o->type == AV_OPT_TYPE_BINARY) {  
                // 求取二进制块的大小
                int len = *(int *)(field_src8 + 1);
                // 判断源二进制块与目的二进制块是否是同一块
                // 若不是则释放当前目的块的空间,若是则不能释放,否则源块也跟着没了。
                if (*field_dst8 != *field_src8)
                    av_freep(field_dst8);
                // 复制原块到目的块
                *field_dst8 = av_memdup(*field_src8, len);
                // 判断目的二进制块是否分配并拷贝成功,
                // 若不成功注意需要使得len也为0,保证二者的一致性
                if (len && !*field_dst8) {
                    ret = AVERROR(ENOMEM);
                    len = 0;
                }
                // 最后设置二进制块的大小值
                *(int *)(field_dst8 + 1) = len;
    
            // const常量
            } else if (o->type == AV_OPT_TYPE_CONST) { 
                // do nothing
    
            // 字典类型AVDictionary*
            } else if (o->type == AV_OPT_TYPE_DICT) {  
                // 转换地址类型
                AVDictionary **sdict = (AVDictionary **) field_src;
                AVDictionary **ddict = (AVDictionary **) field_dst;
                // 判断源AVDictionary与目的AVDictionary是否是同一个
                // 若是同一个则不能释放空间,否则源AVDictionary也没了
                // 若是不是同一个,则使用av_dict_free()来释放当前目的AVDictionary的空间
                if (*sdict != *ddict)
                    av_dict_free(ddict);
                *ddict = NULL;
                // 进行字典拷贝
                av_dict_copy(ddict, *sdict, 0);
                // 若源AVDictionary的条目数不等于目的AVDictionary的条目数
                // 内存空间分配失败
                if (av_dict_count(*sdict) != av_dict_count(*ddict))
                    ret = AVERROR(ENOMEM);
            } else {                                  // 其他类型
                int size = opt_size(o->type);
                if (size < 0)
                    ret = size;
                else
                    memcpy(field_dst, field_src, size);
            }
        }
        return ret;
    }
  1.  验证入参的有效性:确保3点,
           1) 源对象src不为空;
           2) 源对象src的第一个参数为AVClass*;
           3) 源对象src和目的对象dest的第一个参数AVClass*指向的是同一个对象AVClass (如此进行AVOption拷贝才是有效的)

  2.  循环调用av_opt_next()取出src的选项,并将选型所对应的src成员值赋给dest的对应的成员:av_opt_next()函数的详解见文章FFMPEG4.1源码分析之 av_opt_next()注意本函数对于不同类型的成员,赋值的方式是不一样的,在数据拷贝的时候对字符串类型,二进制类型,常量类型,字典类型进行特例处理,剩余的其他类型统一处理。在跟随下面分析过程中,认真思考下,为什么这几个类型会作为特例处理,他们的共性是什么?首先看看有哪些类型定义:

    enum AVOptionType{
        AV_OPT_TYPE_FLAGS,
        AV_OPT_TYPE_INT,
        AV_OPT_TYPE_INT64,
        AV_OPT_TYPE_DOUBLE,
        AV_OPT_TYPE_FLOAT,
        AV_OPT_TYPE_STRING,
        AV_OPT_TYPE_RATIONAL,
        AV_OPT_TYPE_BINARY,  ///< offset must point to a pointer immediately followed by an int for the length
        AV_OPT_TYPE_DICT,
        AV_OPT_TYPE_UINT64,
        AV_OPT_TYPE_CONST,
        AV_OPT_TYPE_IMAGE_SIZE, ///< offset must point to two consecutive integers
        AV_OPT_TYPE_PIXEL_FMT,
        AV_OPT_TYPE_SAMPLE_FMT,
        AV_OPT_TYPE_VIDEO_RATE, ///< offset must point to AVRational
        AV_OPT_TYPE_DURATION,
        AV_OPT_TYPE_COLOR,
        AV_OPT_TYPE_CHANNEL_LAYOUT,
        AV_OPT_TYPE_BOOL,
    };
    

    1)字符串类型(AV_OPT_TYPE_STRING):字符串类型成员是char*类型,此处假设成员名为m_str。非常重要的一点是弄清楚field_dst8 和 field_src8在此种条件下指代的是什么。
               1.1)首先,field_dst变量是一个指针,其指向了当前要处理的目标成员地址m_str,即field_dst指向了m_str;field_dst8是个double point(双重指针),是对field_dst进行强制转换的结果,也就是说field_dst8变量的值其实和field_dst变量的值是一样的,field_dst8存储的是目标成员的地址!!!不过由于类型不同,所指代的意义不一样。
              1.2)其次,if (*field_dst8 != *field_src8) 这个判断代表的含义需要重点理解下,field_dst8为目标成员地址,那么*field_dst8当然是取出目标成员值咯,由于目标成员m_str也是一个指针,其值是指向一个字符串的,那么*field_dst8内容的值是个指向字符串的地址,并且需要按照uint8_t *的类型来解释这个值(field_dst8是uint8_t **类型,*field_dst8类型当然是uint8_t *了)。结论:*field_dst8就是一个指针,指向目标字符串成员变量所指向的同一个字符串;同理,*field_src8指向源字符串成员指向的同一个字符串;上述if语句的含义就是看源成员和目的成员是否是指向同一个字符串。

  3.           1.3)再次,若二者不指向同一个字符串,那么使用av_freep()释放掉目的成员所占用的字符串内存。相等则不能释放空间,否则源串也跟着没了,并且源串指针变成了悬空指针。av_freep()函数的具体解析见 FFMPEG4.1源码分析之 内存管理APIs av_freep() && av_free()
              1.4)最后,使用av_strdup()从源成员拷贝一份到目的成员地址。av_strdup()解释详见 FFMPEG4.1源码分析之 内存管理APIs av_strdup() && av_strndup()
              1.5)做最后的验证,若源串不为空,而av_strdup()得到的目的串为空,那么返回错误码AVERROR(ENOMEM),告知内存空间不足。
    2)二进制数据块类型(AV_OPT_TYPE_BINARY):二进制成员由指向二进制块uint8*类型的成员变量(假设为uint8* m_pBinary)+ 块长度成员变量(假设为int m_nBinary)构成。
              2.1) 首先:int len = *(int *)(field_src8 + 1);计算二进制数据块的大小,field_src8指向二进制成员变量的首地址,也即指向m_pBinary,而field_src8 + 1则指向了m_nBinary,此时做类型强制转换成int*,然后取值,显然得到的就是m_nBinary的值,即二进制块的大小;
              2.2) 其次:if (*field_dst8 != *field_src8);基于字符串类型的分析,此处就只说结论了,*field_dst8是当前目标二进制块地址,*field_src8是当前源二进制块的地址,显然此处比较的是二者是否是同一个二进制块。如果不等则不是,那么先使用av_freep()来释放目标二进制块的内存空间,如果相等,则不能释放空间,否则源二进制块也没了。
              2.3)接着,使用av_memdup()进行内存分配和拷贝,从源二进制块拷贝一份到新分配的目的二进制块空间。
              2.4)验证len不为0,2.3)操作是否成功,若不成功,那么记得将len置为0,保证m_pBinary和m_nBinary保持一致性
              2.5)最后记得设置m_nBinary为最终的真实的长度len。
    3)常量类型(AV_OPT_TYPE_CONST):这个就不多说了,常量不可变,无法赋值。
    4)字典类型(AV_OPT_TYPE_DICT):基于字符串类型和二进制块类型已经进行了消息的解析,字典类型其实也是类似的,源码上已经进行了注释,这儿就不再多赘述,关于源码中对AVDictionary类型进行的相关操作av_dict_free(),av_dict_copy(),av_dict_count()详见 FFMPEG4.1源码分析之 字典类型APIs av_dict_free() && av_dict_copy() && av_dict_count()
    5)其他类型:opt_size求出类型的大小之后,直接进行内存拷贝来覆盖目的地址的原值。

1.1 opt_size()


opt_size() 声明:

  • 所属库:libavutil(lavu
  • 头文件:无,静态函数
  • 源文件:libavutil/opt.c
                  需要注意的以下几点:
                   1)从二进制块类型的成员求取大小方式:可以确认该成员由两部分组成uint8_t*指向二进制块的指针 + int二进制块占字节数;二进制块类型成员size是指针所占字节数(一般是4)+ int类型所占字节数(一般也是4) = 一般是8;

                   2)AVOptionType有19种类型,而opt_size只求取了17种类型的值,对于const类型和AVDictionary*类型的成员没有求值。
                        2.1) const类型具体可以是任意数据类型,所以无法求取size;
                        2.2) AVDictionary*类型为什么不像字符串类型char* 以及 二进制块类型一样,当作指针处理来求取size呢?这个是我非常疑惑的一点
    static int opt_size(enum AVOptionType type)
    {
        switch(type) {
        case AV_OPT_TYPE_BOOL:
        case AV_OPT_TYPE_INT:
        case AV_OPT_TYPE_FLAGS:
            return sizeof(int);
    
        case AV_OPT_TYPE_DURATION:
        case AV_OPT_TYPE_CHANNEL_LAYOUT:
        case AV_OPT_TYPE_INT64:
        case AV_OPT_TYPE_UINT64:
            return sizeof(int64_t);
    
        case AV_OPT_TYPE_DOUBLE:
            return sizeof(double);
    
        case AV_OPT_TYPE_FLOAT:
            return sizeof(float);
    
        case AV_OPT_TYPE_STRING:
            return sizeof(uint8_t*);
    
        case AV_OPT_TYPE_VIDEO_RATE:
        case AV_OPT_TYPE_RATIONAL:
            return sizeof(AVRational);
    
        case AV_OPT_TYPE_BINARY:                   
            return sizeof(uint8_t*) + sizeof(int);
    
        case AV_OPT_TYPE_IMAGE_SIZE:
            return sizeof(int[2]);
    
        case AV_OPT_TYPE_PIXEL_FMT:
            return sizeof(enum AVPixelFormat);
    
        case AV_OPT_TYPE_SAMPLE_FMT:
            return sizeof(enum AVSampleFormat);
    
        case AV_OPT_TYPE_COLOR:
            return 4;
        }
        return AVERROR(EINVAL);
    }

           

3 总结


由上述源码的分析,可以看出特例处理中的4个类型除了常量类型不需要进行操作之外,其他三个特例的处理基本使相似的,其实也是因为这三类成员变量具体同一个特征:即均是指针类型的变量或含有指针,使得数据的内存并非都和结构体对象在一起,造成拷贝的时候需要进行深拷贝。就如同c++类中实现复制拷贝函数一样,必须进行深拷贝。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值