Teach you how to implement buffer (4) - CopyOnWriteBuffer in webrtc


The previous three articles introduced Buffer the implementation principle. Simply put, Buffer it is like one vector<uint8_t> , which automatically manages memory and automatically expands capacity. Compared with vector<uint8_t> it, it provides an easier-to-use interface for writing and reading data. It can be used as a base Buffer on which to build more functional buffer classes.

CopyOnWriteBufferThere is a class in webrtc , which is based on Buffera buffer that implements a copy-on-write function.

Function

Copy-on-write means that when the content is changed, a copy of the buffer is generated, and the content is changed on the copy without affecting the content of the original buffer. This is CopyOnWriteBufferthe core feature of . It also has the following features:

  1. It is a dynamic buffer with the concept of size and capacity, and the capacity is greater than size. After the capacity becomes larger, it will not decrease (based on Buffer)
  2. The object supports copy semantics, move semantics ( Bufferobject-based)
  3. Copy semantics is to allow multiple CopyOnwriteobjects to share memory ( Bufferobject)
  4. When the interface is changed, a copy of the buffer will be generated (a new Bufferobject will be generated)

effect

In streaming media, real-time audio and video applications, a basic process is to receive media streams, process them, and then send media streams:

  • When receiving streams, all media data are read operations, so it CopyOnWriteBufferis very suitable to use. Writing media data to CopyOnWirteBufferobjects will be transferred with value semantics, and the internal ones Bufferwill not be copied. This is more convenient than using naked pointers, because CopyOnWriteBufferit provides A variety of interfaces for operating/reading data.
  • When processing, such as media stream forwarding applications, usually only need to change some fields in the rtp packet, and then forward it. Then put the rtp package CopyOnWriteBufferin, and automatically copy an rtp package when changing. This is more convenient than using raw pointers directly.

accomplish

The internal buffer is a Buffertype shared_ptrpointer, expressing its semantics that it can be shared _buffer.

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

copy semantics

CopyOnWriteBufferSupports copy semantics, so there are copy constructors and assignment operators.

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

The role of copy semantics is to allow multiple CopyOnWriteBufferobjects to share _buffer.

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

The assignment operator, too:

CopyOnWriteBuffer& operator=(const CopyOnWriteBuffer& buf) {
    
    
        assert(IsConsistent());
        assert(buf.IsConsistent());
        if (&buf != this) {
    
    
            _buffer = buf._buffer;
            _offset = buf._offset;
            _size = buf._size;
        }

        return *this;
    }

move semantics

CopyOnWriteBufferDefines a move constructor and a **move assignment function. **By std::movepassing _bufferthe ownership of the object to another CopyOnWriteBufferobject.

CopyOnWriteBuffer::CopyOnWriteBuffer(CopyOnWriteBuffer&& buf)
    : _buffer(std::move(buf._buffer)), _offset(buf._offset), _size(buf._size) {
    
    
    buf._offset = 0;
    buf._size = 0;
    assert(IsConsistent());
}

The same goes for the move assignment operator:

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

copy-on-write

CopyOnWriteBufferThe core feature is copy-on-write. Multiple CopyOnWriteBufferobjects share a Buffer. When an object changes the Buffer, a new Buffer will be generated for the object. The interfaces involved in modifying Buffer are as follows:

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

The first three methods will call UnshareAndEnsureCapacitythe method to determine whether to generate a new Buffer.

The UnshareAndEnsureCapacity method

As the function name indicates, it has two logics: if _bufferit is shared, a new _bufferobject will be generated; if it is not shared, it will be confirmed whether a new _bufferobject needs to be generated through capacity.

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

An example of copy-on-write

The following example is AppendDataan example of using

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;

slice method

It is used CopyOnWriteBufferto fragment the object, and a new object of size is generated from the original object , which isoffset shared with the original object .lengthCopyOnWriteBuffer_buffer

CopyOnWriteBuffer Slice(size_t offset, size_t length) const {
    
    
    CopyOnWriteBuffer slice(*this);
    slice._offset += offset;
    slice._size = length;
    return slice;
}

sample code

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;

Summarize

CopyOnWriteBufferThe implementation is based on Buffer, which is stronger in function than Buffer. BufferWe can also encapsulate our own Buffer class based on business needs .

おすすめ

転載: blog.csdn.net/mo4776/article/details/127646579
おすすめ