零拷贝缓冲 · Buffer 与 CopyOnWriteBuffer
一个 RTP 包在系统里要流经打包、加密、pacing、发送好几站,每站都复制一份负载会很浪费。这一章读 buffer.h / copy_on_write_buffer.h,讲清 WebRTC 怎么用引用计数实现「写时复制」缓冲,让数据在多个环节间零拷贝地共享。
零拷贝缓冲 · Buffer 与 CopyOnWriteBuffer
一个 RTP 包从生成到发出,要经过打包 → 加密 → pacing → 发送好几站,还可能被缓存起来等重传。如果每站都把负载(可能上千字节)复制一份,开销会很可观。WebRTC 用写时复制缓冲解决——这一章看它,它正好是上一章引用计数的一个漂亮应用。
Buffer:WebRTC 的字节缓冲
先看最基础的 Buffer(buffer.h)——一个可增长的字节数组,类似 std::vector<uint8_t>,但为媒体数据的用法调过:管理一块连续内存、能 append、能 resize、避免不必要的清零。WebRTC 里包、帧、编码数据的原始字节,底层多是它。这没什么特别,特别的是下一个。
CopyOnWriteBuffer:共享,直到有人要改
真正体现设计的是 CopyOnWriteBuffer。看它的成员(真实源码 copy_on_write_buffer.h):
// copy_on_write_buffer.h(节选,真实源码)
class CopyOnWriteBuffer {
// ...
void EnsureCapacity(size_t capacity);
void UnshareAndEnsureCapacity(size_t new_capacity); // 写前:若共享则先复制
private:
scoped_refptr<RefCountedBuffer> buffer_; // 注意:引用计数的缓冲!
};关键就在 buffer_ 的类型:scoped_refptr<RefCountedBuffer>——底层字节被引用计数的对象持有。
CopyOnWriteBuffer 复制时只复制一个引用计数指针,不复制底层字节**——多个 CopyOnWriteBuffer 可以共享同一块数据。只有当某个持有者要修改内容时,才真正复制一份(UnshareAndEnsureCapacity),这就是“写时复制”(Copy-on-Write)。** 工作机制:
- 拷贝 = 共享 —— 把一个
CopyOnWriteBuffer传给下一站,只是scoped_refptr计数 +1,两者指向同一块字节,零拷贝。 - 只读 = 一直共享 —— 只要没人改,大家一直共用一份。加密前只读地传、缓存等重传只读地存,全程不复制。
- 写 = 这时才复制 —— 某站要改内容时,先检查“是不是还有别人共享这块”(计数 > 1?),是就复制出私有的一份再改(
Unshare),不影响别人。
于是常见的“只读传递”路径完全零拷贝,只有真正要改的地方才付复制代价。这对一个包流经多站的场景是巨大的节省——数据在 pacer 队列、重传缓存、发送路径间流转,大多是“传引用、不动内容”,COW 让这些传递几乎免费。
为什么这对 WebRTC 特别重要
写时复制不是 WebRTC 独有的概念(很多字符串库也这么干),但它在 WebRTC 里特别值:
一个媒体包天然会被多方同时引用——发出去一份、重传缓存留一份、统计看一眼。若都深拷贝,高码率视频下每秒成百上千个包、每个上千字节,复制开销会吃掉可观的 CPU 和内存带宽。COW 把这些“同一份数据的多个引用”变成真正的共享,是实时媒体处理的性能关键之一。这也再次说明上一章的引用计数不只管“对象生死”,还能拿来做“数据共享”——CopyOnWriteBuffer = 引用计数 + 写时复制。
小结与下一章
Buffer(buffer.h)是 WebRTC 的基础字节缓冲(类 vector<uint8_t>,为媒体调优)。CopyOnWriteBuffer 是重点:内部持 scoped_refptr<RefCountedBuffer>(引用计数的底层字节),拷贝只复制指针 + 计数、共享同一份数据,只有要修改时才真复制(UnshareAndEnsureCapacity)——写时复制。这让一个包流经打包 / 加密 / pacing / 重传缓存的“只读传递”路径零拷贝,只在真要改处付代价,是高码率媒体处理的性能关键。它是引用计数用于“数据共享”的漂亮范例。
零拷贝解决了“数据怎么高效流动”,但多线程访问共享数据还得同步。下一章看 WebRTC 的锁与线程注解。👉 同步与线程注解。