# 零拷贝缓冲 · Buffer 与 CopyOnWriteBuffer

> 一个 RTP 包在系统里要流经打包、加密、pacing、发送好几站，每站都复制一份负载会很浪费。这一章读 buffer.h / copy_on_write_buffer.h，讲清 WebRTC 怎么用引用计数实现「写时复制」缓冲，让数据在多个环节间零拷贝地共享。

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/buffers
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 零拷贝缓冲 · Buffer 与 CopyOnWriteBuffer

一个 [RTP 包](https://daiw.org/manual/webrtc/rtp-packet)从生成到发出，要经过[打包](https://daiw.org/manual/webrtc/video-packetization) → [加密](https://daiw.org/manual/webrtc/srtp-encryption) → [pacing](https://daiw.org/manual/webrtc/pacing) → [发送](https://daiw.org/manual/webrtc/rtp-sender)好几站，还可能被[缓存起来等重传](https://daiw.org/manual/webrtc/nack-rtx)。如果每站都把负载（可能上千字节）复制一份，开销会很可观。WebRTC 用**写时复制缓冲**解决——这一章看它，它正好是[上一章引用计数](https://daiw.org/manual/webrtc/scoped-refptr)的一个漂亮应用。

## Buffer：WebRTC 的字节缓冲

先看最基础的 `Buffer`（`buffer.h`）——一个可增长的字节数组，类似 `std::vector<uint8_t>`，但为[媒体数据](https://daiw.org/manual/webrtc/rtp-packet)的用法调过：管理一块连续内存、能 append、能 resize、避免不必要的清零。WebRTC 里[包、帧、编码数据](https://daiw.org/manual/webrtc/video-frame)的原始字节，底层多是它。这没什么特别，特别的是下一个。

## CopyOnWriteBuffer：共享，直到有人要改

真正体现设计的是 `CopyOnWriteBuffer`。看它的成员（真实源码 `copy_on_write_buffer.h`）：

```cpp
// 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>`**——底层字节被[引用计数](https://daiw.org/manual/webrtc/scoped-refptr)的对象持有。

<Callout type="info">
  **`CopyOnWriteBuffer` 复制时**只复制一个[引用计数指针](https://daiw.org/manual/webrtc/scoped-refptr)，不复制底层字节**——多个 `CopyOnWriteBuffer` 可以共享同一块数据。只有当某个持有者**要修改**内容时，才真正复制一份（`UnshareAndEnsureCapacity`），这就是“写时复制”（Copy-on-Write）。** 工作机制：

  - **拷贝 = 共享** —— 把一个 `CopyOnWriteBuffer` 传给下一站，只是 `scoped_refptr` [计数 +1](https://daiw.org/manual/webrtc/scoped-refptr)，两者指向**同一块字节**，零拷贝。
  - **只读 = 一直共享** —— 只要没人改，大家一直共用一份。[加密前只读地传](https://daiw.org/manual/webrtc/srtp-encryption)、[缓存等重传只读地存](https://daiw.org/manual/webrtc/nack-rtx)，全程不复制。
  - **写 = 这时才复制** —— 某站要改内容时，先检查“是不是还有别人共享这块”（[计数 > 1](https://daiw.org/manual/webrtc/scoped-refptr)？），是就**复制出私有的一份**再改（`Unshare`），不影响别人。

  于是**常见的“只读传递”路径完全零拷贝，只有真正要改的地方才付复制代价**。这对[一个包流经多站](https://daiw.org/manual/webrtc/rtp-packet)的场景是巨大的节省——数据在 [pacer 队列](https://daiw.org/manual/webrtc/pacing)、[重传缓存](https://daiw.org/manual/webrtc/nack-rtx)、[发送路径](https://daiw.org/manual/webrtc/rtp-sender)间流转，大多是“[传引用、不动内容](https://daiw.org/manual/webrtc/scoped-refptr)”，COW 让这些传递几乎免费。
</Callout>

## 为什么这对 WebRTC 特别重要

写时复制不是 WebRTC 独有的概念（很多字符串库也这么干），但它在 WebRTC 里**特别值**：

```mermaid
flowchart LR
    ENC["编码数据(一份字节)"] --> P1["RTP 打包<br/>CowBuffer 引用"]
    P1 --> P2["FEC / 重传缓存<br/>同一份,+1"]
    P1 --> P3["加密前只读传<br/>同一份,+1"]
    P3 -->|"要改:加密写入"| COPY["这时复制一份私有的"]
    style COPY fill:#fbbf24,color:#000
```

[一个媒体包](https://daiw.org/manual/webrtc/rtp-packet)天然会被**多方同时引用**——[发出去一份](https://daiw.org/manual/webrtc/rtp-sender)、[重传缓存留一份](https://daiw.org/manual/webrtc/nack-rtx)、[统计看一眼](https://daiw.org/manual/webrtc/stats-collector)。若都深拷贝，[高码率视频](https://daiw.org/manual/webrtc/simulcast-svc)下每秒成百上千个包、每个上千字节，复制开销会吃掉可观的 CPU 和内存带宽。COW 把这些“[同一份数据的多个引用](https://daiw.org/manual/webrtc/scoped-refptr)”变成真正的共享，是实时媒体处理的**性能关键**之一。这也再次说明[上一章的引用计数](https://daiw.org/manual/webrtc/scoped-refptr)不只管“对象生死”，还能拿来做“[数据共享](https://daiw.org/manual/webrtc/buffers)”——`CopyOnWriteBuffer` = [引用计数](https://daiw.org/manual/webrtc/scoped-refptr) + 写时复制。

## 小结与下一章

**`Buffer`**（`buffer.h`）是 WebRTC 的基础字节缓冲（类 `vector<uint8_t>`，为媒体调优）。**`CopyOnWriteBuffer`** 是重点：内部持 **`scoped_refptr<RefCountedBuffer>`**（[引用计数](https://daiw.org/manual/webrtc/scoped-refptr)的底层字节），**拷贝只复制指针 + 计数、共享同一份数据，只有要修改时才真复制**（`UnshareAndEnsureCapacity`）——写时复制。这让[一个包流经打包 / 加密 / pacing / 重传缓存](https://daiw.org/manual/webrtc/rtp-packet)的“[只读传递](https://daiw.org/manual/webrtc/rtp-sender)”路径**零拷贝**，只在真要改处付代价，是[高码率媒体](https://daiw.org/manual/webrtc/simulcast-svc)处理的性能关键。它是[引用计数](https://daiw.org/manual/webrtc/scoped-refptr)用于“数据共享”的漂亮范例。

零拷贝解决了“数据怎么高效流动”，但多线程访问共享数据还得**同步**。下一章看 WebRTC 的锁与线程注解。👉 [同步与线程注解](https://daiw.org/manual/webrtc/synchronization)。

## 参考资料

- [1] libwebrtc 源码.`rtc_base/buffer.h`、`rtc_base/copy_on_write_buffer.h`（`CopyOnWriteBuffer`、`UnshareAndEnsureCapacity`、`scoped_refptr<RefCountedBuffer>`）。
- [2] 关联[引用计数](https://daiw.org/manual/webrtc/scoped-refptr)、[RTP 包](https://daiw.org/manual/webrtc/rtp-packet)、[NACK 重传缓存](https://daiw.org/manual/webrtc/nack-rtx)。
- [3] libwebrtc 源码.`rtc_base/zero_memory.h` 等配套。
