视频帧 · VideoFrame 与像素缓冲
一帧视频在内存里是什么样?这一章讲清 VideoFrame 的结构、I420(YUV)像素格式为什么是视频的通用货币、VideoFrameBuffer 的引用计数与零拷贝,以及帧上的时间戳、旋转等元数据。
视频帧 · VideoFrame 与像素缓冲
视频管线传的是帧。在深入编码、传输之前,先看清「一帧视频在 WebRTC 内存里到底长什么样」——VideoFrame(api/video/video_frame.h)。
VideoFrame:像素 + 元数据
一个 VideoFrame = 一块像素缓冲 + 一组元数据:
- 像素缓冲(
VideoFrameBuffer) —— 真正的图像数据,几十万到几百万个像素。 - 元数据 —— 宽高、时间戳(决定播放时刻与同步)、旋转角度(手机横竖屏)、以及颜色空间等。
它是采集、自适应、编码之间流转的通用容器——摄像头产出它,编码器消费它,解码后又产出它交给渲染。
I420:视频的通用货币
像素怎么存?WebRTC 内部的「标准格式」是 I420(一种 YUV 4:2:0),这是视频世界的通用货币:
I420 把图像存成「亮度 Y + 两个色度 U/V」三个平面,且色度降采样**——这利用了人眼「对亮度敏感、对颜色迟钝」的生理特点省一半数据。** 具体:Y(亮度)每个像素一个值(全分辨率),U、V(色度)每 2×2 个像素才共享一个值(四分之一分辨率)。于是每像素平均 1.5 字节(而非 RGB 的 3 字节),天然省一半,还几乎看不出差别。
为什么统一用 I420?因为编解码器几乎都吃 YUV(编码前要从摄像头的各种格式转成 YUV),而 I420 是最通用的一种。WebRTC 把它定为内部标准,各环节就有了共同语言:摄像头的 NV12、RGB 等格式先转成 I420,再进编码器;解码出来也是 I420,再转成显示要的格式。一个统一的中间格式,把「五花八门的输入 / 输出」和「编解码器」解耦了——又是 WebRTC 常见的抽象解耦手法。VideoFrameBuffer 也支持原生纹理(GPU 上的帧)等其他形态,按需转换。
引用计数与零拷贝
视频帧很大(一帧 1080p 的 I420 约 3 MB),到处拷贝会要命。所以 VideoFrameBuffer 是引用计数的(scoped_refptr):
一帧的像素数据只存一份,多个环节通过引用计数共享它,而不是各拷一份——这是视频管线性能的关键。 想象一帧图像同时要给编码器、要给本地预览、要给统计,如果每处都 memcpy 一份 3 MB,一秒 30 帧就是每秒几百 MB 的无谓拷贝,CPU 和内存带宽都扛不住。引用计数让它们共享同一块缓冲:传递帧就是传递一个智能指针(几字节),真正的像素纹丝不动。只有当某个环节要修改帧时,才按需拷贝(copy-on-write 思想)。大对象共享、避免拷贝,是所有高性能媒体系统的铁律,WebRTC 用引用计数把它落地。这也是为什么像素缓冲和帧元数据分开——元数据轻,可以随意拷;像素重,必须共享。
时间戳:一帧的身份证
帧上的时间戳特别重要,贯穿后续多章:
一帧从采集到渲染,时间戳是它一路的身份证——音视频同步、抖动缓冲、渲染时机全靠它。
小结与下一章
VideoFrame = I420 像素缓冲 + 元数据(宽高、时间戳、旋转)。I420(YUV 4:2:0)是视频通用货币——利用人眼特点把色度降采样,每像素 1.5 字节省一半,且是编解码器的通用输入。VideoFrameBuffer 引用计数共享,避免大帧到处拷贝(零拷贝、copy-on-write),是性能关键。时间戳是帧从采集到渲染一路的身份证。
帧从摄像头来,怎么喂给编码器?这中间还有个 source→sink 的连接、以及第一道自适应。下一章看采集到编码器的入口。👉 采集与编码器输入。
参考资料
- [1] libwebrtc 源码.
api/video/video_frame.h、api/video/video_frame_buffer.h、api/video/i420_buffer.h。 - [2] libwebrtc 源码.
common_video/(帧处理工具)。 - [3] YUV / I420 格式 —— 视频编码的基础表示。