Article directory
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.
CopyOnWriteBuffer
There is a class in webrtc , which is based on Buffer
a 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 CopyOnWriteBuffer
the core feature of . It also has the following features:
- 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
) - The object supports copy semantics, move semantics (
Buffer
object-based) - Copy semantics is to allow multiple
CopyOnwrite
objects to share memory (Buffer
object) - When the interface is changed, a copy of the buffer will be generated (a new
Buffer
object 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
CopyOnWriteBuffer
is very suitable to use. Writing media data toCopyOnWirteBuffer
objects will be transferred with value semantics, and the internal onesBuffer
will not be copied. This is more convenient than using naked pointers, becauseCopyOnWriteBuffer
it 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
CopyOnWriteBuffer
in, and automatically copy an rtp package when changing. This is more convenient than using raw pointers directly.
accomplish
The internal buffer is a Buffer
type shared_ptr
pointer, expressing its semantics that it can be shared _buffer
.
//是一个Buffer对象
std::shared_ptr<Buffer> _buffer;
copy semantics
CopyOnWriteBuffer
Supports 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 CopyOnWriteBuffer
objects 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
CopyOnWriteBuffer
Defines a move constructor and a **move assignment function. **By std::move
passing _buffer
the ownership of the object to another CopyOnWriteBuffer
object.
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
CopyOnWriteBuffer
The core feature is copy-on-write. Multiple CopyOnWriteBuffer
objects 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 UnshareAndEnsureCapacity
the method to determine whether to generate a new Buffer.
The UnshareAndEnsureCapacity method
As the function name indicates, it has two logics: if _buffer
it is shared, a new _buffer
object will be generated; if it is not shared, it will be confirmed whether a new _buffer
object 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 AppendData
an 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 CopyOnWriteBuffer
to fragment the object, and a new object of size is generated from the original object , which isoffset
shared with the original object .length
CopyOnWriteBuffer
_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
CopyOnWriteBuffer
The implementation is based on Buffer
, which is stronger in function than Buffer
. Buffer
We can also encapsulate our own Buffer class based on business needs .