• 手把手教你实现buffer(四)——webrtc中的CopyOnWriteBuffer



    前面的三篇文章介绍了 Buffer实现原理,简单的来说, Buffer就像一个 vector,自动管理内存,自动扩容,相比 vector它提供了更易用的写入,读取数据的接口。它可以作为一个基本 Buffer,在它的基础上构建功能更多丰富的buffer类。

    webrtc中有一个CopyOnWriteBuffer类,它就是基于Buffer实现了一个写时复制功能的buffer。

    功能

    写时复制指在改变内容时,产生一个buffer的拷贝,在拷贝上更改内容,而不影响原buffer的内容。这是CopyOnWriteBuffer的核心特性。它还具有如下几个特性:

    1. 它是个动态buffer,有size和容量的概念,容量大于size。容量变大后就不会减小(基于Buffer)
    2. 该对象支持拷贝语义,移动语义(基于Buffer对象)
    3. 拷贝语义,是让多个CopyOnwrite对象共享内存(Buffer对象)
    4. 当调用改动接口时,则会生成buffer的拷贝(产生新的Buffer对象)

    作用

    流媒体,实时音视频这类应用中有个基本流程就是收媒体流,处理,再发送媒体流:

    • 在收流时,对媒体数据都是读操作,那么使用CopyOnWriteBuffer就很适合,将媒体数据写入CopyOnWirteBuffer对象,将以值语义传递,内部的Buffer并不会拷贝,这样比使用裸指针更方便,因为CopyOnWriteBuffer就提供了多种操作/读取数据的接口。
    • 在处理时,比如媒体流转发应用,通常只需要在rtp包中更改某些字段,再转发出去。那么将rtp包放入CopyOnWriteBuffer中,在更改时,自动copy一份rtp包。这样比直接使用裸指针更方便。

    实现

    内部buffer是一个Buffer类型shared_ptr指针,表述它的语义就是可以共享_buffer

    //是一个Buffer对象
    std::shared_ptr<Buffer> _buffer;
    
    • 1
    • 2

    复制语义

    CopyOnWriteBuffer支持复制语义,所以有复制构造函数,赋值操作符。

    //复制构造函数
    CopyOnWriteBuffer(const CopyOnWriteBuffer& buf);
    //赋值操作符
    CopyOnWriteBuffer& operator=(const CopyOnWriteBuffer& buf);
    
    • 1
    • 2
    • 3
    • 4

    复制语义的作用就是让多个CopyOnWriteBuffer对象共享_buffer

    CopyOnWriteBuffer::CopyOnWriteBuffer(const CopyOnWriteBuffer& buf)
        : _buffer(buf._buffer), _offset(buf._offset), _size(buf._size) {}
    
    • 1
    • 2

    赋值操作符,也是如此:

    CopyOnWriteBuffer& operator=(const CopyOnWriteBuffer& buf) {
            assert(IsConsistent());
            assert(buf.IsConsistent());
            if (&buf != this) {
                _buffer = buf._buffer;
                _offset = buf._offset;
                _size = buf._size;
            }
    
            return *this;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    移动语义

    CopyOnWriteBuffer定义有移动构造函数和**移动赋值函数。 **通过std::move_buffer的所有权交给另外一个CopyOnWriteBuffer对象。

    CopyOnWriteBuffer::CopyOnWriteBuffer(CopyOnWriteBuffer&& buf)
        : _buffer(std::move(buf._buffer)), _offset(buf._offset), _size(buf._size) {
        buf._offset = 0;
        buf._size = 0;
        assert(IsConsistent());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    移动赋值操作符也是如此:

    CopyOnWriteBuffer& operator=(CopyOnWriteBuffer&& buf) {
        assert(IsConsistent());
        assert(buf.IsConsistent());
        _buffer = std::move(buf._buffer);
        _offset = buf._offset;
        _size = buf._size;
        buf._offset = 0;
        buf._size = 0;
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    写时复制

    CopyOnWriteBuffer核心特性就是写时复制,多个CopyOnWriteBuffer对象共享一个 Buffer,当某个对象改动Buffer时,就会对这个对象产生一个新的Buffer。涉及到修改Buffer的接口如下:

    • void AppendData(const uint8_t*data, size_t size)
    • uint8_t& operator[](size_t index)
    • void SetSize(size_t size)
    • void SetData(const uint8_t*data, size_t size)

    前三个方法都会调用UnshareAndEnsureCapacity方法,通过它来确定是否产生新的Buffer。

    UnshareAndEnsureCapacity 方法

    如函数名表示的意思,它有两个逻辑 :如果_buffer被共享则产生一个新的_buffer对象;如果没有被共享,则通过capacity来确认是否需要产生一个新的_buffer对象。

    void CopyOnWriteBuffer::UnshareAndEnsureCapacity(size_t new_capacity) {
        if (_buffer.unique() && new_capacity <= capacity()) {
            return;
        }
    
        _buffer.reset(new Buffer(_buffer->data() + _offset, _size, new_capacity));
        _offset = 0;
        assert(IsConsistent());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    写时复制的一个例子

    下面这个例子是使用AppendData的例子

    const uint8_t kTestData[] = {0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf};
    
    //共享
    CopyOnWriteBuffer buf23(kTestData,3,10);
    CopyOnWriteBuffer buf24(buf23);
    
    const uint8_t* d1 = buf23.cdata();
    const uint8_t* d2 = buf24.cdata();
    std::cout<<"before append data d1 == d2 "<<(d1 == d2)<<",buf23 size "<<buf23.size()<<",buf24 size "<<buf24.size()<<",buf23 c "<<buf23.capacity()<<",buf24 c "<<buf24.capacity()<<std::endl;
        
    uint8_t moreData[] = {17,18,19};
    
    //buf24 调用Append时,会产生一个新的buffer
    buf24.AppendData(moreData,3);
       
    std::cout<<"after append data,buf23 size "<<buf23.size()<<",buf24 size "<<buf24.size()<<",buf23 c "<<buf23.capacity()<<",buf24 c "<<buf24.capacity()<<std::endl;
        
    const uint8_t* d3 = buf24.cdata();
    std::cout<<"d1 == d3 "<<(d1 == d3)<<std::endl;
    
    //打印buf23的内容:1,2,3
    std::cout<<"print buf23 data:";
    for (size_t i=0; i<buf23.size(); ++i) {
        std::cout<<static_cast<int>(buf23[i])<<" ";
    }
    
    std::cout<<std::endl;
    
    //打印buf24的内容:1,2,3,17,18,19
    std::cout<<"print buf24 data:";
    for (size_t i=0; i<buf24.size(); ++i) {
        std::cout<<static_cast<int>(buf24[i])<<" ";
    }
    
    std::cout<<std::endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    slice方法

    用于对CopyOnWriteBuffer对象进行分片,产生的一个从原对象offset处,sizelength,新的CopyOnWriteBuffer对象,它与原对象共享_buffer

    CopyOnWriteBuffer Slice(size_t offset, size_t length) const {
        CopyOnWriteBuffer slice(*this);
        slice._offset += offset;
        slice._size = length;
        return slice;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    示例代码
    const uint8_t kTestData[] = {0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf};
    
    CopyOnWriteBuffer ssbuf(kTestData,10,10);
    //slice并不导致产生新buffer
    CopyOnWriteBuffer sslice = ssbuf.Slice(0,3);
    //sslice[0]改变了,将产生一个新的buffer
    sslice[0] = 0xaa;
    //sslice[0]的值为170
    std::cout<<"sslice index 0 "<<static_cast<int>(*sslice.cdata())<<std::endl;
    //ssbuf[0]的值为1
    std::cout<<"ssbuf index 0 "<<static_cast<int>(*ssbuf.cdata())<<std::endl;
    std::cout<<"sslic data == ssbuf data "<<(sslice.cdata() == ssbuf.cdata())<<std::endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    总结

    CopyOnWriteBuffer的实现基于Buffer,在功能上强于Buffer。我们也可以根据业务的需求,基于Buffer封装自己的Buffer类。

  • 相关阅读:
    麒麟系统开发笔记(十四):在国产麒麟系统上编译libmodbus库、搭建基础开发环境和移植测试Demo
    Elasticsearch 和 LangChain 合作开发可用于生产的 RAG 模板
    金龙鱼半年报:增收不增利,控本依旧是头等大事
    单调栈和单调队列可以很简单
    如何实现跨窗口通讯
    数据库的三大范式
    RocketMQ源码分析(十四)之IndexFile
    Ti AM335X工控模块矩阵键盘电路的设计与驱动移植
    linux下的工具---vim
    Java注解
  • 原文地址:https://blog.csdn.net/mo4776/article/details/127646579