一、引言:为什么音视频技术如此重要
如果说 HTTP 和 TCP/IP 是互联网的骨架,那么音视频技术就是互联网的血肉。2025 年,视频流占互联网总流量的 82% 以上。从抖音短视频到 Zoom 视频会议,从 Netflix 4K 流媒体到微信视频通话,音视频技术无处不在。
对于 Android 开发者而言,音视频技术更是绕不开的领域。无论是实现一个自定义相机预览,还是开发一套直播推流 SDK,或者优化视频播放的起播速度,都绕不开对底层音视频原理的深入理解。MediaCodec、MediaMuxer、OpenGL ES、FFmpeg 这些 API 和工具都只是表面——真正的内力来自于对 PCM、YUV、H.264、容器格式、流媒体协议的透彻理解。
本文将从最基础的音频采样和色彩空间讲起,深入到 H.264 编码管线,再到容器格式和流媒体协议,最后落地到 Android MediaCodec 的实战用法。这是一条从底层原理到工程实践的学习路径。
二、PCM 音频:数字声音的根基
2.1 声音是如何变成数字的
声音的本质是空气中传播的机械振动——声波。声波是连续的模拟信号,要让计算机处理它,必须经过脉冲编码调制(PCM, Pulse Code Modulation)过程,将模拟信号转换为数字信号。
PCM 的三步转换:
第 1 步:采样(Sampling)
以固定的时间间隔测量模拟信号的幅度。采样率(Sample Rate)决定了每秒采样的次数。根据奈奎斯特-香农采样定理,要无损地表示频率为 f 的信号,采样率必须至少为 2f。人耳能听到的最高频率约 20kHz,因此 CD 音质的采样率设定为 44.1kHz(略高于 2 × 20kHz)。
第 2 步:量化(Quantization)
将每个采样的连续幅度值映射到离散的数值等级。量化精度由位深度(Bit Depth)决定,位深度越高,量化噪声越低,动态范围越大。CD 使用 16 位量化。
第 3 步:编码(Encoding)
将量化后的数值表示为二进制数据。PCM 是最简单的编码方式,直接将量化值按顺序排列。有损压缩编码(如 MP3、AAC、Opus)则在此基础上进行心理声学建模和数据压缩。
模拟声波 (连续的电压信号) |
2.2 采样率:44.1kHz vs 48kHz
44.1kHz —— 音频 CD 标准:
- 由 Sony 和 Philips 在 1979 年联合制定(红皮书标准)
- 选择 44.1kHz 的历史原因:早期数字音频使用录像带存储,44.1kHz 可以同时兼容 PAL(588 行/帧)和 NTSC(490 行/帧)制式的录像带
- 今天仍是音乐制作和消费音频的主流采样率(MP3、AAC、流媒体音乐)
48kHz —— 专业视频制作标准:
- 由电影和电视工业推动
- 与视频帧率(24/25/30fps)的整数倍关系更好,便于音视频同步
- 48kHz × 1024 采样/帧 ÷ 48000 = 与 24fps 视频每帧 2000 个采样,整数对齐
- 广泛用于 DVD、蓝光、数字电视、YouTube
其他常见采样率:
- 8kHz: 电话语音(VoIP、PSTN),足够覆盖 300-3400Hz 的语音频段
- 16kHz: 宽带语音(VoLTE、WebRTC 默认),覆盖到 8kHz
- 22.05kHz: 44.1kHz 的一半,低质量音乐
- 32kHz: 专业广播和某些 VoIP 编解码器
- 96kHz / 192kHz: 高解析度音频(Hi-Res Audio),主要用于录音棚制作和发烧友市场
Android 平台支持的采样率:
大多数 Android 设备的原生采样率是 48kHz。使用 AudioTrack 或 AAudio 时,如果输入数据采样率与硬件不匹配,Android 的 AudioFlinger 会进行重采样(Resampling),这会引入额外的延迟和 CPU 开销。因此最优实践是始终使用 AudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE) 获取设备原生采样率。
2.3 位深度:16-bit vs 24-bit vs 32-bit
位深度决定了每个采样值用多少比特表示,直接影响音频的动态范围和量化噪声。
16-bit(CD 标准):
- 数值范围:-32768 ~ 32767(2^16 = 65536 个级别)
- 理论动态范围:20 × log10(65536) ≈ 96.3 dB
- 量化噪声的理论信噪比(SNR):约 96 dB
- 适用场景:消费级音频播放、流媒体、游戏音效
24-bit(录音棚标准):
- 数值范围:-8388608 ~ 8388607(2^24 = 16777216 个级别)
- 理论动态范围:20 × log10(16777216) ≈ 144.5 dB
- 远超人类听觉的动态范围(约 120 dB 从听阈到痛阈)
- 适用场景:专业录音、混音、母带制作
32-bit(浮点):
- IEEE 754 单精度浮点数:1 位符号 + 8 位指数 + 23 位尾数
- 动态范围:约 1528 dB(因为浮点格式的动态范围不取决于位宽,而是指数量级范围)
- 永远不会”数字削波”——超过 0 dBFS 的值仍能精确表示(值 > 1.0 或 < -1.0)
- 适用场景:DAW(数字音频工作站)内部处理、音频插件链、FFmpeg 内部处理(
flt采样格式)
位深度的实际影响:
- 更高的位深度**不意味着更好的”音质”**——一旦超过视频播放环境的噪声底噪(通常 40-60 dB SPL),额外的动态范围对听感没有贡献
- 但更高的位深度对后期处理至关重要:在混音中多次调整增益、EQ、混响等操作会在每个步骤累积量化误差。使用 24-bit 或 32-bit 浮点作为工作格式,可以保持后期处理中的精度
- 交付格式(MP3、AAC、Opus)不需要高位深:有损编解码器在编码过程中已经引入了远大于量化噪声的压缩失真
2.4 声道布局
单声道(Mono): 1 个声道。所有声音来自同一位置(或中心)。PCM 数据布局:[L]
立体声(Stereo): 2 个声道。最常见的消费音频格式,通过左右声道差异营造声场。PCM 数据布局(交错存储):[L0, R0, L1, R1, L2, R2, …]
5.1 环绕声: 6 个声道:
- 前置左(FL)、前置右(FR)
- 中置(FC)——主要用于对话
- 低频效果(LFE / Subwoofer)——“.1” 表示只处理低频
- 后置左(BL / SL)、后置右(BR / SR)
7.1 环绕声: 8 个声道:在 5.1 基础上增加侧环绕左(SL)和侧环绕右(SR)
Android 的声道掩码(Channel Mask):
// AudioFormat 中的声道常量 |
声道布局 vs 声道数量:
声道数量(channel count)只是一个整数,声道布局(channel mask)描述了每个声道的空间位置。在 Android 的 AudioTrack 初始化时,如果只传声道数量而不传声道掩码,系统会用默认的布局映射。
2.5 WAV 文件格式
WAV(Waveform Audio File Format)是最简单的 PCM 音频容器。它基于 RIFF(Resource Interchange File Format)结构,由一个个”块”(chunk)级联而成。
WAV 文件的二进制结构:
┌─────────────────────────────────────────────────────────────────────┐ |
一个具体的 44.1kHz/16-bit/立体声 WAV 文件(前 44 字节头部):
偏移 内容 说明 |
WAV 的局限性:
- 文件最大约 4GB(RIFF 的 ChunkSize 是 32 位无符号整数)
- 不支持流式传输(必须在文件头部已知总数据大小)
- 元数据支持非常有限(没有内建的标题、艺术家、专辑等字段)
- 扩展格式 RF64 解决了 4GB 限制,但兼容性差
三、YUV 色彩空间:视频的图像表示
3.1 为什么视频使用 YUV 而不是 RGB
人眼对亮度(明暗)变化的敏感度远高于对颜色(色度)变化的敏感度。这一生物学事实是视频压缩的生理学基础。
YUV 的三个分量:
- Y(Luma,亮度): 表示像素的明暗程度。黑白电视只显示这一分量
- U(Cb,蓝色色度分量): 蓝色与亮度的差值
- V(Cr,红色色度分量): 红色与亮度的差值
色度子采样(Chroma Subsampling):
利用人眼对色度不敏感的特性,视频编码使用色度子采样来减少数据量——保持每个像素的 Y 分量不变,但对 U 和 V 分量进行降采样。
常见的 YUV 格式标识(以 4:x:y 格式描述):
4:4:4: 无子采样。每个像素有独立的 Y、U、V 值。质量最高,数据量最大(每 4 像素 12 个采样值)。用于专业后期制作和数字中间片。
4:2:2: 水平方向每 2 个像素共用一对 UV。每 4 像素 8 个采样值。用于广播级制作(如广电录像机)和部分专业视频接口(SDI)。
4:2:0: 水平方向和垂直方向都 2:1 子采样。每 2×2 的 4 个像素块共用一对 UV。数据量仅为 4:4:4 的 50%。这是消费级视频的绝对主流格式,H.264/H.265/VP9/AV1 几乎都使用 4:2:0。
4:2:0 色度子采样的像素布局 (每 2×2 = 4 个像素块): |
3.2 YUV 的平面存储格式与半平面存储格式
YUV 数据在内存中的排列方式分为两大类:平面格式(Planar)和半平面格式(Semi-Planar / Interleaved)。
平面格式 —— I420(YUV420P):
三个分量分别存储为独立的平面(plane)。先存储所有像素的 Y,再存储所有像素的 U,最后存储所有像素的 V。
I420 内存布局 (4×4 像素为例): |
I420 是最常用的标准 YUV 格式,也是 Android MediaCodec 输出 COLOR_FormatYUV420Planar 的默认格式之一。
半平面格式 —— NV12:
Y 分量独占一个平面,U 和 V 分量交错存储在第二个平面中(UV 交错,即 UVUVUV…)。
NV12 内存布局 (4×4 像素为例): |
NV12 是 Android 平台最广泛使用的 YUV 格式。Android Camera2 API 的 ImageFormat.NV21(见下文)和 MediaCodec 的 COLOR_FormatYUV420SemiPlanar 都使用类似的半平面布局。
半平面格式 —— NV21:
与 NV12 类似,但 U 和 V 的次序相反:V 在前,U 在后(即 VUVUVU…)。
NV21 内存布局 (4×4 像素为例): |
NV21 是 Android 的默认相机预览格式。Camera1 API 的 onPreviewFrame() 回调默认返回 NV21 数据。如果你需要将 Camera 帧送入 MediaCodec 编码器,通常需要在 NV21(相机输出)和 NV12(编码器输入)之间做转换——或者更简单地,使用 Surface 输入模式避免这个转换。
三大 YUV 格式对比:
| 特性 | I420 (YUV420P) | NV12 | NV21 |
|---|---|---|---|
| 存储方式 | 三平面 | 双平面 (UV交错) | 双平面 (VU交错) |
| Y 平面大小 | W × H | W × H | W × H |
| UV 平面大小 | W/2 × H/2 × 2 | W × H/2 | W × H/2 |
| UV 序 | U plane + V plane | U+V 交错 | V+U 交错 |
| 主流用途 | 编解码通用格式 | Android MediaCodec 编码 | Android Camera 预览 |
3.3 YUV ↔ RGB 转换
颜色空间转换是视频处理中最频繁的操作之一。以下是 BT.601 标准(SD 视频)的转换公式,也是 Android 平台最通用的转换。
RGB → YUV(BT.601,用于编码前的颜色空间转换):
Y = 0.299 × R + 0.587 × G + 0.114 × B |
注意 Y 的权重分配(0.299 / 0.587 / 0.114)反映了人眼对绿色最敏感、对红色次之、对蓝色最不敏感的特性。这就是为什么绿色通道携带了最多的亮度信息。
YUV → RGB(BT.601,用于显示时的转换):
R = Y + 1.402 × (Cr - 128) |
整型优化(避免浮点运算):
在移动设备和嵌入式平台上,浮点运算代价高昂。实际实现通常使用定点运算 + 移位来加速。例如,通过将系数乘以 2^n 并四舍五入为整数,转换后右移 n 位:
// 快速 YUV420 (NV21) → RGB888 转换 (定点优化) |
YUV 范围:TV Range vs Full Range(JPEG Range):
- TV Range(Limited Range / BT.601/BT.709 标准): Y ∈ [16, 235], UV ∈ [16, 240]。这是广播电视的传统范围,为超调和下冲留了余量。
- Full Range(JPEG Range): YUV ∈ [0, 255]。不需要映射偏移,数据更简单。
Android MediaCodec 默认使用 TV Range(除非编码器配置了特定 profile),解码后如果需要送入 OpenGL 纹理(期望 0-255 范围),需要注意范围映射。
四、H.264 编码管线:逐步骤深入
H.264(MPEG-4 AVC, Advanced Video Coding)至今仍是世界上最广泛使用的视频编解码标准。从 YouTube 视频到蓝光光盘,从安防监控到视频会议,H.264 无处不在。
H.264 编码器的工作不是”压缩像素”,而是消除冗余。视频中存在三类冗余:
- 空间冗余(Spatial Redundancy): 同一帧内相邻像素通常相似 → 帧内预测
- 时间冗余(Temporal Redundancy): 相邻帧之间通常变化很小 → 帧间预测
- 统计冗余(Statistical Redundancy): 数据中的统计模式 → 熵编码
4.1 H.264 编码器总体架构
输入视频帧 (YUV) |
重建回路(Decoder Loop)为什么重要?
编码器的预测需要解码后的参考帧,而不是原始帧。如果编码器拿原始帧做预测而解码器拿重建帧做预测,误差会累积,最终导致漂移(Drift)——画质越来越差。因此每个 H.264 编码器内部都包含一个完整的解码器,确保预测基础一致。
4.2 第一步:帧分割 —— 从帧到宏块
H.264 将一帧图像分割为宏块(Macroblock, MB)作为基本处理单元:
- 每个宏块包含一个 16×16 的亮度(Y)区域
- 对应的两个 8×8 的色度区域(U 和 V,4:2:0 格式下)
对于 1080p(1920×1080)视频:
- Y 层宏块数量:1920/16 × 1080/16 = 120 × 67.5 = 120 × 68 = 8160 个宏块
- 每行最后一个宏块需要填充(padding)到 16 的整数倍
在 H.264 中,每个 16×16 宏块还可以进一步分割为更小的子宏块(Sub-macroblock Partition)用于运动补偿:
- 16×16, 16×8, 8×16, 8×8
- 8×8 还可以再分割为 8×4, 4×8, 4×4
更小的分割能更精确地匹配物体运动,但需要更多的运动矢量编码开销。
4.3 第二步:预测(Prediction)
预测是视频压缩的核心。H.264 提供了两大类预测模式:
4.3.1 帧内预测(Intra Prediction)—— 消除空间冗余
帧内预测利用同一帧内已编码的相邻像素来预测当前块,只传输预测模式编号和残差。
H.264 定义了 3 种亮度帧内预测类型(基于块大小):
- Intra 4×4: 9 种预测模式。适合纹理细节丰富的区域
- Intra 8×8: 9 种预测模式(High Profile 新增)。适合较平滑的渐变区域
- Intra 16×16: 4 种预测模式。适合平坦区域
Intra 4×4 的 9 种预测模式:
当前 4×4 块依赖的参考像素 (已编码的相邻块): |
编码器会对所有 9 种模式计算率失真代价(Rate-Distortion Cost):RDcost = Distortion + λ × Rate,选择代价最小的模式。Distortion 是该模式预测产生的残差能量(SSD/SAD),Rate 是编码该模式所需的总比特数,λ 是拉格朗日乘子(取决于 QP)。
Intra 16×16 的 4 种预测模式:
- Mode 0: Vertical
- Mode 1: Horizontal
- Mode 2: DC
- Mode 3: Plane(平面):使用线性模型对平滑亮度渐变建模,适合天空、渐变背景等区域
色度帧内预测:
色度块(8×8,针对 4:2:0)也有自己的 4 种预测模式,类似于 Intra 16×16 的模式。
4.3.2 帧间预测(Inter Prediction)—— 消除时间冗余
帧间预测利用已编码的参考帧来预测当前块,只需传输运动矢量(指示参考块的位置)和残差。
运动估计(Motion Estimation, ME):
编码器在参考帧的搜索窗口中寻找与当前块最匹配的区域。
当前帧 (时间 t) 参考帧 (时间 t-1) |
运动估计算法:
- 全搜索(Full Search): 搜索窗口内每个可能的位置,绝对最优但计算量巨大
- 菱形搜索(Diamond Search): 从中心开始,按菱形模式向外搜索
- 六边形搜索(Hexagon Search): 类似菱形搜索但用六边形模式,收敛更快
- UMH(Uneven Multi-Hexagon): x264 的默认搜索算法,结合了多种搜索模式
- TESA(Transformed Exhaustive Search): x264 最慢但质量最高的算法
运动估计的匹配准则:
- SAD(Sum of Absolute Differences):
Σ|当前像素 - 参考像素|,硬件实现简单 - SATD(Sum of Absolute Transformed Differences): 对残差先做 Hadamard 变换再求绝对和,码率估计更准确(x264 默认)
- SSD(Sum of Squared Differences): 均方误差
子像素运动估计(Sub-pixel ME):
H.264 支持 1/4 像素精度(亮度)的运动估计。在搜索整像素最佳匹配后,编码器通过6 抽头 Wiener 滤波器插值出 1/2 像素位置,再通过双线性插值得到 1/4 像素位置,在这些子像素位置进一步搜索。这显著提高了运动补偿精度——特别是在缓慢运动的场景中,整像素精度可能导致”抖动”。
运动补偿(Motion Compensation, MC):
一旦确定运动矢量,编码器从参考帧中取出对应块,与当前块做差,得到残差。残差经过 DCT、量化和熵编码发送给解码器。解码器只做”运动补偿”(不需要”运动估计”,因为运动矢量已经在码流中)。
4.3.3 预测模式决策
编码器对每个宏块都要做复杂的模式决策:
- SKIP 模式: 无残差,无运动矢量(直接沿用预测的运动矢量)。码率最小,用于静止帧或全局平移场景
- Intra 模式: 帧内预测。用于场景切换、新出现的物体,或 I 帧中的宏块
- Inter P 模式: 单个参考帧的帧间预测(前向预测)
- Inter B 模式: 最多两个参考帧的帧间预测(前向+后向或双前向)
模式决策的遍历顺序(x264 的实现,从低复杂度到高复杂度):
- 首先检查 SKIP 模式(如代价足够低,直接选择)
- 测试 P 模式的各个分割
- 测试 Intra 模式(通常只在场景切换附近才会胜出)
- 对于 B 帧,还需要测试双向预测
4.4 第三步:变换与量化
4.4.1 DCT 变换(整数 DCT)
H.264 使用整数 DCT(Discrete Cosine Transform)代替传统的浮点 DCT。整数 DCT 的好处:
- 无浮点运算,编解码结果在所有平台上 100% 一致(没有浮点舍入差异)
- 硬件实现简单(只需加减和移位)
- 与真正的 DCT 的近似误差极低,不影响压缩效率
4×4 整数 DCT 变换矩阵(H.264 标准定义):
正向变换: |
DCT 变换的核心效果:将残差数据的能量集中到左上角(低频系数)。对于大多数自然视频的残差,变换后大部分高频系数接近零,只需编码少数非零系数。
DCT 系数的空间分布:
4×4 DCT 系数矩阵: |
H.264 中还有额外的 Hadamard 变换:
对于 Intra 16×16 模式,亮度块的 16 个 DC 系数(每个 4×4 块的 DC 系数)先被组合成一个 4×4 DC 矩阵,再进行一次 Hadamard 变换。色度块的 DC 系数也是类似处理。这种分层变换进一步增强了能量集中。
4.4.2 量化(Quantization)—— 有损压缩的核心
量化是 H.264 编码中唯一的必然有损环节(预测中的子像素插值和去块滤波也是潜在的精度损失源)。它把连续的 DCT 系数映射为离散的整数级别:
量化公式: Z_ij = round( Y_ij / Qstep ) |
QP(Quantization Parameter)与 Qstep 的关系:
- QP 取值范围:0 ~ 51
- Qstep 随 QP 指数增长:QP 每增加 6,Qstep 翻倍
- QP 增加 1,Qstep 增加约 12.5%,码率约降低 12.5%
QP 与 Qstep 对应关系: |
H.264 的量化矩阵(前 6 个 QP 值,4×4 块):
H.264 标准定义了量化矩阵 MF(Multiplication Factor),将除法和舍入合并为乘法+移位操作,以便于硬件实现。对于给定的 QP:
量化: |Z_ij| = (|Y_ij| × MF[q][i][j] + f) >> qbits |
量化引入了”死区(Dead Zone)”:
量化后,绝对值小于阈值的系数被归零。这个”死区”的大小由 f 参数控制。更大的死区清零更多系数,降低码率但损失更多细节。
量化对视觉质量的影响:
- 低频系数量化误差 → 块效应(Blocking Artifacts),去块滤波可缓解
- 高频系数被清零 → 细节丢失、模糊、ringing artifacts(振铃效应)
- 色度分量过度量化 → 颜色失真和色块(Color Banding)
这就是为什么编码器通常对色度分量使用比亮度更高的 QP(”色度 QP 偏移”,通常 +6),因为人眼对色彩细节不敏感。
4.5 第四步:熵编码
熵编码(Entropy Coding)是无损压缩——它不损失任何信息,只是用更紧凑的方式表示量化后的系数。
H.264 提供了两种熵编码方案:
CAVLC(Context-Adaptive Variable Length Coding)
CAVLC 使用上下文自适应的可变长度编码表。它的核心思路:
- 统计上,量化后的大多数系数都是零,非零系数通常集中在低频区域
- 非零系数中,±1 出现的概率远高于其他值
- 相邻块的非零系数数量高度相关
CAVLC 编码一个 4×4 块的全过程:
将 4×4 系数按 Zig-Zag 扫描为一维数组
Zig-Zag 扫描顺序 (将 2D 系数矩阵 → 1D 序列):
┌────┬────┬────┬────┐
│ 0 │ 1 │ 5 │ 6 │ 扫描: (0,0)→(0,1)→(1,0)→(2,0)→(1,1)→(0,2)→(0,3)→(1,2)→...
├────┼────┼────┼────┤
│ 2 │ 4 │ 7 │ 12 │ 效果:低频系数在前,高频系数(多为零)集中在后面
├────┼────┼────┼────┤
│ 3 │ 8 │ 11 │ 13 │
├────┼────┼────┼────┤
│ 9 │ 10 │ 14 │ 15 │
└────┴────┴────┴────┘编码非零系数的个数(TotalCoeff)和 TrailingOnes(尾部连续的 ±1 个数,最多 3 个): 使用联合码表,根据相邻块预测当前块可能的 coeff 范围
编码每个 TrailingOne 的符号(+ 或 -): 每个 1 bit
编码剩余非零系数的值(Levels): 使用指数哥伦布编码或查找表
编码最后一个非零系数之前零的总数(TotalZeros): 使用上下文自适应的查找表
编码每个非零系数之前的零的个数(RunBefore): 从高频向低频方向,编码每个非零系数前面的连续零的个数
CABAC(Context-Adaptive Binary Arithmetic Coding)
CABAC 是 H.264 Main 和 High Profile 的默认熵编码方式,相比 CAVLC 有 9-14% 的码率节省。代价是计算复杂度显著升高。
CABAC 的三步框架:
第 1 步:二值化(Binarization)
将非二进制的语法元素(如运动矢量差值、系数 Level)映射为二进制串。常用方法:
- 一元码(Unary): N 用 N 个 “1” + 一个 “0” 表示(如 3 → “1110”)
- 截断一元码(Truncated Unary): 有限最大值版本
- k 阶指数哥伦布码(Exp-Golomb): 适用于无上限的值
- 定长码(Fixed Length): 如二进制表示的系数符号位
第 2 步:上下文建模(Context Modeling)
为每个比特(bin)动态选择概率模型。CABAC 定义了 460+ 个上下文模型,每个模型维护一个 6 位概率状态索引(共 64 个状态)和当前大概率符号(MPS, Most Probable Symbol)的取值。
上下文的选取取决于:
- 当前语法元素的类型(coeff_flag, last_coeff, mvd 等)
- 相邻块同类型语法元素的值(空间相关性)
- 当前块在帧内的位置
第 3 步:二进制算术编码(Binary Arithmetic Coding)
对每个 bin,使用所选上下文模型的概率进行算术编码:
- 编码器维护一个区间(Range, [0, 2^9-1])和一个偏移
- 根据当前 bin 的概率将区间重新划分为两个子区间(MPS 区间 + LPS 区间)
- 选择对应子区间,输出比特
- 更新概率模型状态(如果编码的是 MPS,概率增大;如果是 LPS,概率减小并可能翻转 MPS)
- 如果 Range 低于阈值(小于 256),执行重归一化:Range 倍增并输出一个比特
CABAC 是高度串行的——每个 bin 的编码依赖上一个 bin 更新后的状态。这让 CABAC 难以并行化,也是硬件解码器的设计瓶颈之一。H.265/HEVC 引入的波前并行处理(Wavefront Parallel Processing, WPP)和Tiles以及在 H.266/VVC 中的进一步改进,都是为了绕开 CABAC 的串行瓶颈。
4.6 第五步:去块滤波(Deblocking Filter)
去块滤波(Deblocking Filter)是 H.264 编码环内的最后一步,也是解码端的必要环节。它对块边界附近的像素进行平滑处理,减少因分块编码产生的”块效应”(Blocking Artifacts)。
为什么会有块效应?
- 相邻宏块使用不同的预测模式、参考帧、运动矢量
- 量化对每个块的精度影响不同
- 块边界处的像素可能来自完全不同的参考区域
去块滤波如何工作:
滤波在每个 4×4 块边界上进行(水平和垂直边界,先水平后垂直)。对于每条边界,滤波器根据以下因素决定滤波强度(Bs, Boundary Strength):
| 条件 | Bs 值 |
|---|---|
| 边界是宏块边界,且至少一边是 Intra 预测 | 4(最强) |
| 边界是宏块边界,且至少一边有编码残差 | 3 |
| 两边的运动矢量差 >= 1 个整像素 | 2 |
| 两边使用不同的参考帧,或运动矢量差 >= 1 个整像素 | 1 |
| 其他情况 | 0(不滤波) |
BS = 4 的强滤波(针对平坦区域的宏块边界):
同时对边界两侧各 3 个像素(共 6 个像素)进行修改。
BS = 1/2/3 的弱滤波:
只可能修改边界两侧各 1 个像素(最多 2 个),且只在像素值差小于阈值时触发。
阈值控制(α 和 β):
去块滤波还使用两个与 QP 相关的阈值 α(块边缘阈值)和 β(块内阈值)。这些阈值与 QP 成正比:
- QP 高 → α, β 大 → 更多滤波(因为量化噪声大,块效应更严重)
- QP 低 → α, β 小 → 少滤波或不滤波(避免模糊保留的真实细节)
- 可以通过
filterOffsetA和filterOffsetB参数(在 Slice Header 中)偏移
去块滤波的贡献约占总解码时间的 1/3,但它对主观视觉质量的提升至关重要——同样的编码比特数下,有去块滤波的 H.264 视频显著优于没有该功能的视频格式(如 MPEG-4 Part 2)。
五、I/P/B 帧与 GOP 结构
5.1 三种帧类型
视频编码中的帧可分为三种基本类型,它们的根本区别在于预测参考的来源:
I 帧(Intra-coded Frame,帧内编码帧 / 关键帧):
- 只使用帧内预测(不依赖其他帧)
- 压缩率最低(约 7:1 到 20:1)——因为没有利用时间冗余
- 是随机访问点(Random Access Point)——解码可以从任何一个 I 帧开始
- 是错误恢复点——传输丢包或解码错误后,通过下一个 I 帧重新同步
- I 帧通常以 IDR(Instantaneous Decoder Refresh) 形式出现,它告诉解码器:”忘记之前所有的参考帧,从这里开始重新构建”
P 帧(Predicted Frame,前向预测帧):
- 可以引用之前的一个或多个帧(前向参考列表 List0)进行帧间预测
- 也可以包含帧内编码的宏块(当帧间预测找不到好的匹配时)
- 压缩率约为 I 帧的 3-5 倍
- 解码 P 帧需要所有依赖的参考帧已经解码
B 帧(Bi-directional Predicted Frame,双向预测帧):
- 可以引用之前的帧和之后的帧(双向参考,List0 + List1)
- 拥有最高的压缩率(约为 I 帧的 10-20 倍)
- 两种特殊的预测模式:
- 双向预测(Bi-prediction): 前向预测块和后向预测块的加权平均
- 直接模式(Direct Mode): 不传输运动矢量,从相邻块推算
- B 帧不被其他帧引用——它可以被丢弃而不影响后续帧的解码(在码流稀疏化时非常有用)
B 帧的显示顺序 vs 编码顺序:
因为 B 帧引用了”未来”的帧,编码器必须调整顺序。例如:
显示顺序: I₀ B₁ B₂ P₃ B₄ B₅ P₆ |
这就是为什么视频容器中有一个 “PTS”(Presentation Time Stamp)和 “DTS”(Decode Time Stamp)的区别——DTS 决定解码时机,PTS 决定显示时机。
B 帧的参考列表:
H.264 允许 B 帧维护两个参考帧列表:
- List0: 主要是过去帧(前向参考)
- List1: 主要是未来帧(后向参考)
一个 B 帧的宏块可以使用:
- 来自 List0 的单个参考
- 来自 List1 的单个参考
- 来自 List0 和 List1 的加权双向参考
5.2 GOP(Group of Pictures)
GOP 是一组连续的图像序列,从 IDR 帧开始,到下一个 IDR 帧之前结束。
GOP 结构的两个关键参数:
- GOP 长度(GOP Size / Keyframe Interval): 两个 I 帧之间的帧数。例如 GOP=30 意味着每 30 帧就有一个 IDR 帧
- B 帧数量(B-frame Count): 连续 P 帧之间的 B 帧数量
典型的 GOP 结构:
GOP Size = 30, B-frames = 2 (常见于 HLS 直播): |
GOP 设计的权衡:
| 参数 | 更小/更少 | 更大/更多 |
|---|---|---|
| GOP 大小 | 更多关键帧 → 更大文件,但 seek 更快,错误恢复更快 | 更少关键帧 → 更高压缩率,但 seek 慢 |
| B 帧数量 | 编码更快,延迟更低(实时场景) | 更高压缩率,但延迟更高(VOD 场景) |
分层 B 帧(Hierarchical B-frames):
现代编码器(H.264 High Profile, H.265, VP9, AV1)使用分层 B 帧结构进一步提高压缩效率:
显示顺序: I₀ B₁ B₂ B₃ P₄ B₅ B₆ B₇ P₈ |
分层 B 帧的优点:
- 时间可伸缩性:丢弃高层级帧,可以降低帧率而不需要重新编码
- 更高压缩率:每个层级的参考帧在时间上更近,预测更准确
- 实现帧率自适应:网络拥塞时服务器可以丢弃高层时间层级的帧来降低码率,而客户端仍能播放(以较低帧率)
六、容器格式
编码后的视频流(H.264/H.265)和音频流(AAC/Opus)是裸流(raw elementary stream)——它们缺少时间戳、索引、元数据等关键信息。容器格式就是将这些裸流”打包”成可播放的文件或流。
6.1 MP4 —— 最通用的视频容器
MP4 基于 ISO Base Media File Format(ISOBMFF, ISO 14496-12),使用面向对象的”原子”(atom/box)结构。每个原子由 4 字节长度 + 4 字节类型 + 数据组成。
MP4 文件结构概述:
典型 MP4 文件(非 fragmented): |
关键原子详解:
ftyp(File Type Box):
标识文件的品牌(Brand)和兼容性(Compatible Brands)。例如:
mp42: 标准 MP4 文件isom: 基于 ISOBMFF 的文件avc1: 包含 AVC/H.264 视频M4A: iTunes 音频 (.m4a)
moov(Movie Box):
MP4 的核心元数据容器。对于非 fragmented MP4(普通 mp4 文件),moov 必须包含所有帧的索引信息(stts, stsz, stco/stco64)。这意味着:
- moov 通常写在 mdat 的后面(因为编码结束后才知道所有帧的大小和偏移)
- 对于流式播放(HTTP Progressive Download),播放器需要先获取 moov 才能定位到具体的帧
- 这就是为什么
ffmpeg有一个-movflags +faststart参数——它将 moov 移动到文件开头,让播放器可以在文件完整下载前就开始播放
stbl(Sample Table Box):stbl 下的子原子构成了帧索引表。理解这些原子之间的关系是理解 MP4 播放的关键:
时间 → 字节偏移的完整映射链: |
fMP4(Fragmented MP4):
传统的非 fragmented MP4 有一个致命缺陷:必须在编码开始时就知道所有帧的信息——这不适用于直播、录制、或分片传输。Fragmented MP4(fMP4)解决了这个问题。
fMP4 将媒体数据分割为多个fragment(片段),每个 fragment 可以独立解码和播放:
Fragmented MP4 结构: |
fMP4 是 DASH(Dynamic Adaptive Streaming)和 CMAF(Common Media Application Format)的核心容器格式,也是 HLS 从 TS 迁移到 fMP4 的目标格式。
6.2 MKV / WebM —— 开放标准的容器
MKV(Matroska Video):
- 基于 EBML(Extensible Binary Meta Language),类似 XML 的二进制格式
- 完全开源,无专利限制(专利风险已通过专利池覆盖)
- 几乎可以封装任何编解码格式(H.264, H.265, VP9, AV1, AAC, Opus, FLAC, ASS 字幕等)
- 支持章节、多音轨、多字幕轨、附件(嵌入字体)、菜单
- 适合本地存储和归档,不适合流媒体(不支持 fragment 式随机访问)
WebM:
WebM 是 Matroska 的子集,专门为 Web 设计,由 Google 推动:
- 视频:VP8 / VP9 / AV1
- 音频:Vorbis / Opus
- 不支持字幕、章节、多音轨等高级功能
- 是 HTML5
<video>标签的主要支持容器
EBML 结构:
EBML 元素: |
6.3 FLV —— 直播时代的遗产
FLV(Flash Video)是 Adobe Flash Player 使用的视频容器,虽然 Flash 本身已于 2020 年退役,但 FLV 格式在直播推流领域仍然广泛使用,尤其是在中国国内的直播平台中。
FLV 文件结构:
FLV 文件: |
FLV 的 tag 结构简单直接,每个 tag 有明确的时间戳。这使得 FLV 在直播场景中有两个优势:
- 可以边录制边推流——每个 tag 自成一体,不需要全局索引
- 解析器极其简单,适合低延迟场景
FLV 在 RTMP 中的应用:
在 RTMP 直播推流中,上行(推流)和下行(拉流)都使用 FLV 格式承载音视频数据。RTMP 的 VideoData 和 AudioData 消息体实际上就是 FLV tag 的数据部分(去掉 tag header)。
七、流媒体协议
7.1 RTMP —— 实时消息传输协议
RTMP(Real-Time Messaging Protocol)是 Adobe 开发的用于 Flash Player 和服务器之间传输音视频和数据的协议。虽然 Flash 已死,RTMP 在直播推流领域依然是主力。
RTMP 握手(Handshake):
RTMP 使用一个独特的三次握手来验证连接和交换随机数:
Client Server |
这个握手过程看起来繁琐,但它实现了:
- 协议版本协商
- 确定连接的延迟(通过回显时间戳)
- 确保双方都能正确处理随机数据(防御某些代理/缓存设备的错误行为)
RTMP 消息类型:
| 消息类型 ID | 名称 | 用途 |
|---|---|---|
| 1 | Set Chunk Size | 调整分块大小(默认 128 字节) |
| 2 | Abort Message | 取消一个消息的传输 |
| 3 | Acknowledgement | 流量控制:收到一定字节后回确认 |
| 4 | User Control Message | 流事件(Stream Begin, Stream EOF, Buffer Empty 等) |
| 5 | Window Ack Size | 设置确认窗口大小 |
| 6 | Set Peer Bandwidth | 设置对端带宽限制 |
| 8 | Audio Message | 音频数据 |
| 9 | Video Message | 视频数据 |
| 15 | AMF3 Data Message | 元数据/数据消息(ActionScript 3 格式) |
| 18 | AMF0 Data Message | 元数据/数据消息(ActionScript 1.0 格式) |
| 20 | Command Message (AMF0) | RPC 命令:connect, createStream, publish, play 等 |
| 22 | Aggregate Message | 聚合消息(将多个子消息打包为一个,减少开销) |
RTMP 分块(Chunking)机制:
RTMP 将大消息拆分为固定大小的块(chunk),在多路复用的连接上交错发送。默认块大小 128 字节——即消息超过 128 字节就会被拆分。块越小,音频帧可以更快地插入到视频帧之间,减少音频延迟;块越大,头部开销越小。
RTMP 消息分块示例 (一个 350 字节的视频消息, chunk size = 128): |
四种 Chunk Header 类型:
- Type 0(12 字节): 完整头部。包含 Stream ID、Message Type、Timestamp 等全部信息
- Type 1(8 字节): 省略了 Message Stream ID(与上一个相同),但包含 Timestamp Delta 和 Message Length
- Type 2(4 字节): 只包含 Timestamp Delta。消息流 ID、消息类型、消息长度都与上一个相同
- Type 3(1 字节): 最精简。Timestamps 也沿用之前的值(或使用 Timestamp Delta 扩展)
这个设计巧妙地利用了消息流的时序局部性——同一消息类型在同一条流中往往连续出现。
RTMP 的典型推/拉流交互流程:
推流 (Publishing): |
7.2 HLS —— HTTP Live Streaming
HLS(HTTP Live Streaming)是 Apple 开发的基于 HTTP 的自适应流媒体协议。它是最广泛支持的流媒体协议——iOS 和 macOS 原生支持,Android 从 3.1 开始支持,所有主流浏览器通过 MSE(Media Source Extensions)支持。
HLS 的核心概念:
HLS 将视频切成一系列短小的媒体段(通常 2-10 秒),每个段是一个独立的文件(.ts 或 .mp4),然后通过播放列表(.m3u8)串联起来。
主播放列表(Master Playlist / Multivariant Playlist):
#EXTM3U |
主播放列表列出了不同码率的变体(Variant)。播放器根据当前网络带宽自动选择并切换——这就是自适应码率(ABR, Adaptive Bitrate)。
媒体播放列表(Media Playlist):
#EXTM3U |
EXT-X-TARGETDURATION:最大分片时长(播放器据此计算缓冲策略)EXT-X-MEDIA-SEQUENCE:第一个分片的序号(用于直播的滑动窗口)EXTINF:每个分片的具体时长
直播 HLS 的滑动窗口:
在直播中,服务器不断添加新的分片并移除旧的分片,播放列表始终保持一个固定长度的窗口(通常是 3-5 个分片)。播放器定期重新请求播放列表来获取最新分片。
Live HLS 播放列表随时间的变化: |
延迟来源(HLS 延迟通常 15-30 秒):
- 分片时长(如 6 秒)——一个分片必须先完全生成才能被客户端请求
- 分片缓冲——播放器通常缓冲 2-3 个分片以应对网络抖动
- 播放列表更新间隔——播放器每隔一定时间(通常是一个分片时长)请求更新
- 总延迟 ≈ 3 × 分片时长 ≈ 18 秒(基于 6 秒分片)
低延迟 HLS(LL-HLS / Apple Low-Latency HLS):
通过以下技术将 HLS 延迟降低到 2-5 秒:
- 分片化 MP4(fMP4): 代替 .ts 文件
- Partial Segments: 分片在完整生成前就可以部分交付
- Blocking Playlist Reload: 服务器保持 HTTP 连接打开,直到有新分片再返回
- Playlist Delta Updates: 只传输播放列表的变化部分
EXT-X-PRELOAD-HINT: 预告下一个即将可用的分片,让播放器提前发起请求
7.3 DASH —— Dynamic Adaptive Streaming over HTTP
DASH(MPEG-DASH, ISO 23009-1)是国际标准化的自适���流媒体协议,与 HLS 类似但更灵活(也更复杂)。
DASH 的核心文件:MPD(Media Presentation Description)
|
DASH 的分段(Segment)格式:
- 初始化分片(Initialization Segment): 包含 ftyp + moov,只需加载一次
- 媒体分片(Media Segment): 每个分片是一个独立的 fragmented MP4(包含 moof + mdat)
DASH vs HLS 对比:
| 维度 | HLS | DASH |
|---|---|---|
| 播放列表格式 | m3u8 (文本) | MPD (XML) |
| 分片格式 | TS 或 fMP4 (CMAF) | fMP4 或 CMAF |
| 编解码限制 | H.264/H.265 + AAC (规范限制) | 无限制 (任意编解码) |
| DRM | FairPlay (Apple 独占) | Widevine/PlayReady/FairPlay 均可 |
| 延迟 | 传统 15-30s, LL-HLS 2-5s | 传统 15-30s, LL-DASH 2-5s |
| 平台支持 | iOS/macOS 原生, Android 原生 | Android 原生, Web (dash.js) |
CMAF(Common Media Application Format):
CMAF 是一种标准化的分片 MP4 格式,同时被 HLS 和 DASH 支持。使用 CMAF 意味着同一组媒体分片可以同时用于 HLS 和 DASH,只需要两份不同的播放列表文件(.m3u8 和 .mpd)和一份初始化分片。这大大简化了媒体源站的基础设施。
7.4 WebRTC —— 实时通信的革命
WebRTC(Web Real-Time Communication)是一个开源项目,为浏览器和移动应用提供实时、点对点(P2P)的音视频和数据通信。
WebRTC 的三大核心 API:
- MediaStream / getUserMedia: 访问本地摄像头和麦克风
- RTCPeerConnection: 建立点对点连接,传输音视频流
- RTCDataChannel: 传输任意数据(低延迟,基于 SCTP)
WebRTC 的协议栈:
┌─────────────────────────────────────────────────┐ |
WebRTC 连接建立全流程:
第一步:SDP(Session Description Protocol)交换(Offer/Answer 模型)
SDP 描述媒体能力:支持的编解码器、码率、分辨率、传输协议等。
呼叫方 (Offer) → 信令服务器 → 被叫方 (Answer): |
第二步:ICE(Interactive Connectivity Establishment)—— NAT 穿透
ICE 是 WebRTC 最精妙的部分。它尝试所有可能的方法在两个位于不同 NAT 后面的设备之间建立直接连接。
ICE 候选(Candidates)类型:
Host Candidate(主机候选): 设备自身的 IP 地址
- 如
192.168.1.10 - 只在同一局域网内可用
- 如
Server Reflexive Candidate(srflx / STUN 候选): 通过 STUN 服务器获取的公网 IP:Port
- 过程:客户端向 STUN 服务器发送请求,STUN 服务器回复”你的公网地址是 203.0.113.5:45678”
- 这是在 NAT 上自动创建的端口映射
Relay Candidate(中继候选 / TURN 候选): 通过 TURN 服务器中继的所有流量
- 当 P2P 直接连接失败(如对称 NAT)时使用
- 服务器成本高(需要承载所有媒体的带宽)
ICE 的候选收集和连接检查:
1. 双方收集本地候选 (host candidates) |
第三步:DTLS-SRTP 密钥交换
连接建立后(ICE 成功后),WebRTC 使用 DTLS(Datagram Transport Layer Security)进行密钥交换。DTLS 本质上是在 UDP 上运行的 TLS,解决了”如何在不可靠的数据报传输上提供 TLS 的安全保证”的问题。
DTLS 握手完成后,从中派生出SRTP(Secure RTP)密钥,用于加密后续的媒体数据。这就是为什么叫 “DTLS-SRTP”——DTLS 负责密钥协商,SRTP 负责媒体加密。
SRTP 的关键特性:
- 加密 RTP 载荷(使用 AES-CTR 或 AES-GCM)
- 认证 RTP 数据包(使用 HMAC-SHA1)
- 防重放攻击(通过序列号检查)
- 开销极小(每包约 10 字节的认证标签)
WebRTC 的媒体传输特点:
- UDP 优先: 实时通信不能等待 TCP 重传,丢几帧优于增加延迟
- FEC(Forward Error Correction)和 NACK: 通过冗余数据或选择性重传来恢复丢包
- 带宽估计(BWE, Bandwidth Estimation): 基于延迟梯度(delay-based, GCC 算法)和丢包率(loss-based)动态调整编码码率
- RTCP(RTP Control Protocol): 定期发送 SR(Sender Report)和 RR(Receiver Report),传递丢包率、抖动、RTT 等统计信息
八、Android MediaCodec 实战
MediaCodec 是 Android 提供的底层音视频编解码 API。它直接使用设备的硬件编解码器(DSP, GPU, 或专用硬件加速模块),提供了远低于软件编解码的功耗和延迟。
8.1 MediaCodec 的生命周期
MediaCodec 状态机: |
8.2 同步模式(Synchronous Mode)
在同步模式下,编解码器使用一组固定的输入/输出缓冲区数组(从 API 21 之后已废弃 getInputBuffers()/getOutputBuffers(),改为在 dequeue 时获取 Buffer 索引):
视频解码示例(同步模式):
// 创建 H.264 解码器 |
BufferInfo 详解:
public final class MediaCodec.BufferInfo { |
8.3 异步模式(Asynchronous Mode)
API 21 引入了异步模式,通过回调接收事件,避免了手动管理缓冲区队列。
MediaCodec codec = MediaCodec.createDecoderByType("video/avc"); |
同步 vs 异步模式对比:
| 特性 | 同步模式 | 异步模式 |
|---|---|---|
| API 级别 | 16+ | 21+ |
| 线程模型 | 手动管理, 在调用线程上阻塞 | 回调在内部线程上执行 |
| 复杂度 | 需要处理超时和重试逻辑 | 事件驱动, 代码更简洁 |
| 性能 | 理论上略高 (无回调开销) | 几乎相同 |
| 适用场景 | 已有自己的线程模型 | 新代码, 简单场景 |
8.4 Surface 输入 —— 零拷贝编码
从 API 18 开始,MediaCodec 支持将 Surface 作为编码器的输入。这是 Android 视频录制中最常用的模式——相机预览直接渲染到 Surface,编码器从 Surface 中取帧。
Surface 输入的工作流程:
// 创建 H.264 编码器 |
Surface 输入的底层原理(零拷贝路径):
传统的 Buffer 输入模式:
App 内存 (YUV Buffer) |
Surface 输入模式:
GPU 内存 (GraphicBuffer, 来自 Camera 或 OpenGL) |
这就是 Surface 输入模式的核心优势:避免了从 GPU 到 CPU、再从 CPU 到编解码器的两次内存拷贝,对于 1080p@60fps 这样的大数据量场景,节省的带宽和时间非常可观。
8.5 编解码器选择
Android 设备上通常有多个 H.264 编解码器可供选择(硬件、软件、不同厂商实现)。
// 列出所有 H.264 编解码器 |
编解码器选择的经验法则:
硬件编解码器优先(
isHardwareAccelerated() == true):功耗低、延迟低、吞吐量高,但可能有厂商 bug。同一型号设备下,硬件解码器产生的输出格式(如 YUV 对齐方式)是稳定的。Google 软件编解码器作为备选:
c2.android.avc.encoder/c2.android.avc.decoder。行为一致、无厂商 bug、但功耗高、延迟高、4K 以上吃力。永远不要硬编码解码器名称:使用
createEncoderByType("video/avc")/createDecoderByType("video/avc")让系统选择默认编解码器。除非你有特定需求(如必须支持 Baseline Profile),才通过遍历选择特定编解码器。H.264 的 Profile / Level 选择:
- Baseline Profile: 最简单,无 B 帧,无 CABAC。兼容性最好,压缩率最低。Android 相机的默认录制 profile。大部分硬件编码器只支持 Baseline。
- Main Profile: 支持 B 帧和 CABAC。兼容性好,压缩率优于 Baseline。
- High Profile: 支持 8×8 变换和自定义量化矩阵。最好的压缩率,但兼容性稍差(非常老旧的设备可能不支持)。YouTube 1080p+ 只用 High Profile。
- 对于大多数 Android 应用,Baseline(兼容优先)或 Main(平衡)是安全的选择。
// 通过 ContentResolver 查询设备支持的编码器 (Android 5.0+) |
8.6 常见问题与最佳实践
问题 1:编码器输出绿屏/花屏
最常见的调试方向是检查 YUV 格式。Android 相机输出通常是 NV21,而许多编码器的硬件实现期望 NV12 或 I420。使用 Surface 输入模式可以完全规避这一问题——OpenGL 纹理没有格式问题。
问题 2:解码器丢帧
确保 dequeueOutputBuffer 的超时时间不要太长(5000-10000 微秒为宜),并且及时调用 releaseOutputBuffer。如果渲染到 Surface 的 render=true,系统会在下一个 VSync 时将帧送达 SurfaceFlinger。
问题 3:Start-up 延迟
MediaCodec 的 start() 调用会有几十到上百毫秒的初始化延迟(硬件编解码器加载固件)。在实时通信场景中,可以预初始化一个”温”的编解码器池来消除这个延迟。
问题 4:Surface 输入下的时间戳控制
每个帧在 Surface 中输入时的时间戳由 eglPresentationTimeANDROID / setPresentationTime 控制。如果时间戳不连续或跳跃,编码器可能跳过帧或产生异常大的 GOP。
问题 5:Codec 2.0 vs OMX
Android 10 引入了 Codec 2.0 框架,正在逐步取代 OpenMAX(OMX)。Codec 2.0 使用更简单的 C API、更好的并发模型和更少的厂商定制空间。对于应用层开发者,MediaCodec 的 API 保持不变,但底层驱动逻辑有显著改善。
九、总结与学习路径
音视频技术是一个庞大而精密的领域,但它有着清晰的层次结构。以下是从基础到高级的学习建议:
第一层:理论基础(本文覆盖)
- PCM 音频原理(采样、量化、WAV 格式)
- YUV 色彩空间与色度子采样
- H.264 编码管线(预测、变换、量化、熵编码、去块滤波)
- 帧类型与 GOP 结构
- 容器格式(MP4、MKV、FLV)
- 流媒体协议(RTMP、HLS、DASH、WebRTC)
第二层:Android 媒体栈
- MediaCodec(解码/编码)→ 本文覆盖
- MediaExtractor + MediaMuxer(解封装/封装)
- AudioTrack + AudioRecord(PCM 播放/录制)
- Camera2 API + Surface(视频采集)
- OpenGL ES 2.0+(渲染、滤镜、特效)
第三层:高级主题
- FFmpeg(跨平台多媒体处理)
- H.265/HEVC、VP9、AV1 编码技术演进
- 视频超分辨率、插帧(AI/ML 辅助)
- 实时通信优化(Jitter Buffer、FEC、Simulcast、SVC)
- 视频质量评估(PSNR、SSIM、VMAF)
音视频开发的独特挑战在于:它跨越了数学、信号处理、计算机体系结构、操作系统、网络协议等多个领域。但它的回报也是丰厚的——你写下的每一条代码优化,都能让用户看到更清晰的画面、体验到更低延迟的通话、消耗更少的流量和电量。这正是系统级开发的魅力所在。




