
1、H264 的 NALU 是什么H264 视频流并不是一整块原始图像而是一个个 NALUNetwork Abstraction Layer Unit组成的。例如SPS PPS IDR P Frame SEI ...每个 NALU 都长这样[start code] [NALU Header] [NALU Payload]例如00 00 00 01 67 xx xx xx这里67就是NALU Header2、NALU Header 结构H264 规定NALU Header 1字节结构-------------- | F | NRI | TYPE | -------------- 1 2 5 bit即位含义Fforbidden_bitNRI重要性TYPENALU 类型3. 三种帧类型的核心定义I 帧 (Intra-coded picture - 关键帧)定义它是完整的图像类似于一张独立的 JPEG 图片。特点不依赖于其他任何帧就能解码。它是视频序列的“基准”。作用用于视频跳转拖动进度条、断线后画面恢复以及场景切换。体积最大因为它包含了全部像素信息。P 帧 (Predictive-coded picture - 前向预测帧)定义它不存储完整画面只存储与前一个 I 帧或 P 帧的差异运动矢量。特点解码时必须参考前面的帧。体积约为 I 帧的一半甚至更小。B 帧 (Bi-predictive picture - 双向预测帧)定义它不仅参考前面的帧还参考后面的帧来计算差异。特点压缩率最高但解码最复杂。在实时通信如 WSS 视频流中为了降低延迟有时会禁用 B 帧因为解码它需要等待后续帧到达。体积最小通常只有 I 帧的 1/10。4. 关键概念GOP (Group of Pictures)视频流由多个 GOP 组成。一个 GOP 通常以 I 帧开始后面跟着一系列 P 帧和 B 帧。DTS (Decoding Time Stamp)解码时间戳告诉解码器什么时候该解码这帧。PTS (Presentation Time Stamp)显示时间戳告诉播放器什么时候该显示这帧。注意由于 B 帧需要参考后面的帧所以解码顺序和显示顺序是不一样的。PTS 与 DTS 的区别在 Linux 多媒体开发中你可能还会遇到另一个词DTS (Decoding Time Stamp)。既然你之前问到了 BIP 帧这里就非常关键了DTS解码时间戳告诉解码器什么时候该解码这一帧。PTS显示时间戳告诉显示器什么时候该渲染这一帧。关键点对于I 帧和P 帧DTS 和 PTS 通常是一样的。但对于B 帧由于它依赖后面的帧解码顺序和显示顺序会发生错乱。解码顺序I P B显示顺序I B P5. 开发中的实际应用在处理类似 Linux 下的 FFmpeg 编解码项目时你会经常遇到以下场景推流延迟如果你在做实时监控或视频通话通常会将 B 帧数量设为 0b-frames0以换取更低的端到端延迟。Seek 操作在播放器开发中如果你想实现快速跳转必须定位到最近的I 帧。如果定位到 P 或 B 帧画面会出现花屏因为缺少参考数据。带宽控制如果网络环境差丢弃 B 帧对画质影响最小且不会导致后续帧无法解码但如果丢了 I 帧整个 GOP 都会失效。6.Seek在视频播放器开发如你正在研究的 FFmpeg 框架中Seek 操作简单来说就是**“跳进度”**——即用户拖动进度条或通过代码指令将播放位置跳转到视频的某个特定时间点。1. Seek 的底层执行流程当你调用类似av_seek_frame的函数时底层发生了以下操作文件定位解复用器根据你提供的时间戳在媒体文件中寻找对应的Offset偏移量。寻找 I 帧由于视频压缩存在 B/P 帧依赖Seek不能随便跳到任何一帧。它必须找到目标时间点之前的最近一个I 帧关键帧。刷新缓冲区 (Flush)清空 Packet 队列旧的待处理数据包必须全部扔掉av_packet_unref。重置解码器调用avcodec_flush_buffers告诉解码器“之前的参考帧都没用了从头开始解。”重新填充与显示从 I 帧开始连续解码直到到达用户指定的精确 PTS 时间点。2. Seek 的两种主要模式在编程实现时你会面临两种选择快速 Seek (Fast Seek)直接跳转到最近的关键帧I 帧并立即显示。优点速度极快CPU 消耗低。缺点跳转的位置不精确可能会比你点击的位置早几秒。精准 Seek (Precise Seek)先跳到目标点之前的 I 帧然后解码器后台快速解码中间的 P/B 帧但不显示直到到达你点击的那一帧才开始渲染。优点指哪打哪用户体验好。缺点如果 GOP两个 I 帧之间的间隔很长跳转会有明显的卡顿感。7.帧内预测水平预测观察左侧边缘左侧块的最右一列像素值分别是212, 170, 128, 96。水平填充编码器假设“画面是横向连续的”。于是它把左边的212直接横着向右填满第一行把170填满第二行依此类推。结果对比看看黄色块的第一行预测值是212而实际像素是212, 212, 213, 214。预测非常精准这样第一行只需要记录0, 0, 1, 2这样极小的差值极大地节省了存储空间。8.帧间预测这是视频压缩中最强大、贡献最高的技术能压掉 90% 以上的数据量。它的核心逻辑是视频中相邻的两帧画面通常非常相似只是物体发生了位移。1. 核心原理运动补偿 (Motion Compensation)帧间预测不再看当前这一张图里的邻居而是去**参考帧Reference Frame**里寻找最像自己的块。搜索 (Motion Estimation)编码器在之前的 I 帧或 P 帧里反复比对寻找和当前黄色块最接近的一块区域。运动矢量 (Motion Vector, MV)找到了它不在原来的坐标而是往左平移了 10 个像素往上移动了 2 个像素。于是编码器只记录一个坐标差(-10, -2)。残差 (Residual)即使找到了最像的块可能还有微小差别比如灯光变了。编码器把两块相减只存剩下的那点“渣渣”。2. 帧间预测的三个“借钱”对象还记得你之前问的BIP 帧吗它们在帧间预测里的表现完全不同P 帧 (Predictive)单向预测。只能向“过去”的帧借数据。B 帧 (Bi-predictive)双向预测。既可以向“过去”借也可以向“未来”借甚至把过去和未来的块取个平均值。这就是为什么 B 帧压缩率最高。I 帧它是骨气十足的从不使用帧间预测只用帧内预测。3. 为什么会有“花屏”和“卡顿”你在写 C 播放器或处理网络流时一定遇到过画面变成一堆乱七八糟色块的情况。这通常是帧间预测“翻车”了参考帧丢失如果网络丢了一个 P 帧后面的 P 帧还要参考它。因为“过去的记忆”丢了解码器只能在错误的参考基础上继续加残差画面就糊了。解药Seek 操作当你 Seek 时必须跳到 I 帧。因为 I 帧不需要参考过去它能重置所有的预测状态让画面重新变清晰。9.DCT 变换离散余弦变换Discrete Cosine Transform1. 为什么需要 DCT核心直觉通过帧内或帧间预测后我们得到了残差Residual。虽然残差里的数字已经很小了但它们在空间上还是很乱。空间域原始的像素点或残差点。频率域DCT 把这些点转化成“频率”。低频代表图像中大面积平坦、变化缓慢的部分比如皮肤、背景。这是图像的“骨架”。高频代表图像中细节丰富、边缘尖锐的部分比如发丝、草地纹理。这是图像的“毛发”。人的眼睛对低频非常敏感但对高频细节的微小损失几乎察觉不到。DCT 的目的就是把这两者分开。2. DCT 做了什么4x4 或 8x8 块想象一个 8x8 的残差块。通过 DCT 公式计算后它会变成同样大小的 8x8 矩阵但分布发生了巨变能量集中左上角的数值DC 系数变得非常大它代表了整个块的平均亮度。低频趋向于零右下角的数值AC 系数代表高频细节通常变得非常小甚至接近于 0。高频3. 它是如何实现压缩的配合量化DCT 本身是无损的如果你用高精度浮点数还原回去画面是一样的。真正的压缩发生在接下来的量化Quantization阶段编码器会给高频部分除以一个很大的数。由于 DCT 已经让高频系数变得很小了除以大数后它们直接变成了0。结果原本复杂的 8x8 矩阵变成了左上角有几个数剩下全是 0。在传输时这些 0 几乎不占空间。4. 在 C/Linux 开发中的体现在 FFmpeg 源码或高性能多媒体处理中DCT 是计算量最大的部分之一。硬件加速现代 CPU如 Intel 的 AVX 或 ARM 的 NEON都有专门的指令集来并行处理 DCT 矩阵运算。蝶形算法为了提高速度代码里通常不会直接套用原始的三角函数公式而是使用一种叫“蝶形算法”的快速实现将复杂度从 $O(N^2)$ 降低到 $O(N \log N)$。整数 DCT在 H.264 之后为了避免浮点数运算在不同平台比如你的 Linux 客户端和 Kunpeng 服务器产生的微小舍入误差导致花屏标准改用了整数 DCT保证了完全的数学一致性。QPQuantization Parameter量化参数文件名中的qp10、qp28、qp50代表了不同的压缩强度。它们与 DCT 的关系可以总结为一句话QP 越大被抹成 0 的 DCT 系数就越多。1. QP 是如何“粗暴”对待 DCT 系数的在视频编码流程中DCT 变换之后紧接着就是量化 (Quantization)。量化的数学本质其实就是除法qp10 (低量化高保真)除数很小。这意味着 DCT 矩阵中绝大多数的数字包括右下角的高频细节都能在除法后保留下来。结果文件体积很大5,505 KB因为要记录很多非零数字。画面细节极其丰富。qp50 (高量化极高压缩)除数非常大。除了左上角那个最大的能量块DC 系数其他大部分 DCT 系数在除完并取整后全都变成了0。结果文件体积变得极小只有 80 KB因为只需要存几个数字和一大堆“0”。画面会变得非常模糊甚至出现严重的块效应。2. 项目中如何应用当网络带宽充足时你会动态调整编码器把 QP 降到 20 左右保证用户看得很爽。当网络拥塞丢包严重时你会迅速调高 QP比如 40虽然画面变糊了但因为生成的比特流变小了像 qp50 只有 80KB视频依然能勉强跑通而不会彻底卡死。3. 量化步长它决定了你还原数值时跳转的“步伐”如果步长是16你存了一个数字1。解码器还原时会把它乘回步长$1 \times 16 16$。你存了一个数字2。解码器还原时$2 \times 16 32$。中间的数去哪了在步长为 16 的情况下解码器永远无法还原出 17, 18, 19 这些数字。这些丢失的细节就是“量化误差”。量化步长正是 DCT 变换后的系数也就是频域化的残差需要除以的那个值。总结视频压缩的“三部曲”预测 (Prediction)借邻居或历史的数据算出“残差”。变换 (DCT)把残差从空间点变成频率让能量集中在左上角。量化 (Quantization)把右下角不重要的高频数据抹成 0。