Android / 视频 / 直播 / C/C++ 多媒体技术知识库
适用背景:高级 Android 开发、已有视频/直播业务经验,希望系统补齐播放器底层、C/C++、流媒体、性能与平台化能力。
目标方向:Mobile Architecture + Mobile Performance + Multimedia Platform → P7 / Staff Engineer
学习原则:中文资料建立理解 → 官方文档校准 → 源码验证 → Demo 实验 → 线上问题 → 平台设计
目录
- 先建立完整的 Multimedia 心智模型
- C / C++ / NDK:只学播放器真正需要的部分
- 数字音视频基础:RGB、YUV、PCM、采样率与时间
- 视频编码:H.264 / H.265 / AV1
- 封装、解封装:MP4 / fMP4 / TS / FLV / MKV
- PTS / DTS / Timebase / Clock / A-V Sync
- FFmpeg:从 API 到播放器源码
- Android Media3 / ExoPlayer:完整架构
- MediaCodec / Surface / Codec2 / Android Media Framework
- 音频链路:AAC / Opus / PCM / AudioTrack / 重采样
- 流媒体协议:HLS / DASH / RTMP / SRT
- WebRTC:实时音视频真正需要理解什么
- 直播系统:从 Camera 到观众屏幕
- Player Architecture:如何设计工业级播放器
- Multimedia Performance / QoE
- 播放器线上问题排障手册
- Platform Engineering:从播放器能力到多团队平台
- 实战项目与学习任务
- 推荐中文资料、课程、官方文档与源码索引
- 12 个月执行路线
1. 先建立完整的 Multimedia 心智模型
1.1 一个点播视频到底怎么播放出来
先把所有库名忘掉,只看数据流:
URL / Local File
↓
Network / File I/O
↓
Container Parser / Demuxer
↓
Compressed Audio/Video Packet
↓
Decoder
↓
Raw Audio/Video Frame
↓
A/V Synchronization
↓
Video Renderer / Audio Renderer
↓
Surface / AudioTrack
↓
Display / Speaker
映射到常见技术:
URL
↓
OkHttp / Cronet / HttpDataSource
↓
MP4 / HLS / DASH Parser
↓
H264 / H265 / AV1 Packet
↓
MediaCodec / FFmpeg Decoder
↓
YUV Frame
↓
Renderer
↓
Surface
你以后看到播放器问题,不应该首先想到:
ExoPlayer 哪个 API 写错了?
而应该先判断问题属于哪一层:
数据源
Container
Codec
Timestamp
Decoder
Buffer
Render
Surface
Audio
Network
Device
1.2 一个直播链路到底发生了什么
Camera / Microphone
↓
Capture
↓
Preprocess
↓
Encode
↓
Push
↓
Ingest
↓
Transcode
↓
Package
↓
CDN
↓
Pull
↓
Jitter / Buffer
↓
Decode
↓
Sync
↓
Render
直播问题必须始终区分三端:
主播端
↓
云端
↓
观众端
例如“观众卡顿”:
- 所有人都卡:优先查主播上行、源站、转码、CDN 上游。
- 只有部分用户卡:优先查用户网络、调度节点、设备解码、播放器 Buffer。
- 某个机型卡:优先查 Codec / Surface / Vendor Implementation。
- 某个 Codec 卡:查解码能力、Profile/Level、分辨率、HDR、硬解实现。
这套“先分层再定位”的能力,是比掌握更多 API 更重要的底层能力。
2. C / C++ / NDK:只学播放器真正需要的部分
你的目标不是转型成纯 C++ 工程师,而是做到:
可以读 FFmpeg / WebRTC / Android Media Native 源码,可以写 Demo,可以定位 Native Crash,可以和底层播放器工程师讨论同一套问题。
重点只抓四块:
- 内存
- Ownership
- 并发
- JNI / NDK
2.1 Pointer 到底是什么
int value = 10;
int *p = &value;
含义:
value:真正的数据。&value:value 的内存地址。p:保存地址。*p:读取/修改地址中的内容。
*p = 20;
等价于把 value 改成 20。
为什么音视频库大量用 Pointer
1080p YUV420 一帧大约:
1920 × 1080 × 1.5 ≈ 3.1 MB
如果函数之间不断复制完整 Frame:
Decode
↓ copy 3MB
Filter
↓ copy 3MB
Render
↓ copy 3MB
30fps 时内存带宽会很可观。
因此底层系统倾向于:
Buffer + Pointer / Reference
而不是大对象复制。
这就是后面理解:
AVBufferRef- MediaCodec Buffer
- GraphicBuffer
- Surface
- Zero Copy
的基础。
2.2 Stack 与 Heap
Stack
void test() {
int value = 1;
Player player;
}
特点:
- 随作用域自动销毁。
- 分配/释放速度快。
- 生命周期容易理解。
- 空间有限。
Heap
Player* player = new Player();
delete player;
或者:
void* p = malloc(size);
free(p);
特点:
- 生命周期由程序控制。
- 可以持有大对象/跨作用域对象。
- 更容易出现生命周期错误。
播放器的:
Packet
Frame
CodecContext
Decoder
Network Buffer
Queue
Thread Context
大量是动态生命周期对象,因此 Native 播放器首先就是一个 资源生命周期系统。
2.3 三类必须认识的 Native 内存错误
Memory Leak
申请了但没释放。
长期播放时表现常见为:
播放时间 ↑
↓
Native Heap ↑
↓
系统内存压力 ↑
↓
卡顿 / Kill / OOM
Use After Free
Player* p = new Player();
delete p;
p->play(); // 错
内存已经释放但继续访问。
为什么它很难查:
- 有时立刻 Crash。
- 有时原地址还没被复用,看起来“还能跑”。
- 有时在另外一个线程过几秒才 Crash。
Double Free
同一块内存释放两次。
通常导致:
- allocator 检测到错误
- SIGABRT
- 堆损坏
2.4 RAII 是现代 C++ 最值得学的概念之一
RAII 的本质:
资源生命周期绑定对象生命周期。
{
std::unique_ptr<Decoder> decoder =
std::make_unique<Decoder>();
} // 自动析构
它不是只解决内存,还可以管理:
File
Socket
Mutex
Thread
Codec
Frame Buffer
GPU Resource
为什么播放器需要 RAII
Player 常见异常路径很多:
prepare()
↓
open network success
↓
open demuxer success
↓
decoder init failed
如果全部手写:
close network
free demuxer
free decoder
...
非常容易漏。
RAII 让:
正常路径和异常路径都依赖对象析构统一释放。
2.5 unique_ptr / shared_ptr / weak_ptr
unique_ptr
单一所有者。
Player
└── owns Decoder
最推荐默认使用的语义:
谁拥有,谁负责释放。
shared_ptr
多个所有者共享生命周期。
内部维护引用计数。
问题:
A owns B
B owns A
引用计数永远不能归零。
weak_ptr
不增加强引用计数。
主要用途:
打破 shared_ptr 循环引用。
阅读源码的正确问题
以后看到:
std::shared_ptr<X>
不要只知道“这是智能指针”。
要问:
- 为什么这里必须共享所有权?
- 谁是逻辑 Owner?
- 是否可能形成环?
- 生命周期跨了哪些线程?
2.6 Move Semantics 为什么在音视频里常见
大型 Buffer 不希望复制。
std::vector<uint8_t> buffer;
如果复制:
allocate
+
copy N bytes
Move:
auto b = std::move(buffer);
重点不是“语法炫技”,而是:
把资源所有权转移,而不是复制资源。
播放器中 Packet、Frame、Buffer Queue 的设计都经常出现类似思想。
2.7 并发:播放器一定不是单线程
典型 Player:
Main/UI Thread
Network Thread
Demux Thread
Video Decode Thread
Audio Decode Thread
Video Render Thread
Audio Output Thread
所以必须理解:
Mutex
保护共享数据。
std::mutex mutex;
Condition Variable
生产者/消费者最常见:
Demux Thread
↓ produce Packet
Packet Queue
Decode Thread
↓ consume Packet
没有数据时 Decode Thread 不应该疯狂 while 轮询,而应该等待条件变量。
Race Condition
两个线程同时操作一个状态:
Thread A: release decoder
Thread B: decode frame
如果没有同步:
偶发 Native Crash。
Deadlock
Thread A:
lock A
wait B
Thread B:
lock B
wait A
播放器“偶发卡死但不 Crash”经常需要检查:
- Mutex
- Condition Variable
- Thread join
- callback reentrancy
2.8 JNI 到底解决什么
Android:
Kotlin / Java
↓
JNI
↓
C / C++
JNI 是两种运行时之间的桥梁。
你需要理解:
JNIEnvjobjectjclass- Local Reference
- Global Reference
- Native Thread attach/detach
- Java exception ↔ Native error
- ByteBuffer / DirectBuffer
最常见 JNI 坑
线程问题
Native 自己创建的线程:
默认没有 Java
JNIEnv上下文。
需要 attach 到 JVM。
Reference 生命周期
Local Reference 只在当前 JNI 调用上下文有效。
跨线程/跨调用持有 Java 对象:
应使用 Global Reference,并正确释放。
2.9 NDK 实际学习内容
你要做到:
Android Studio
↓
Gradle
↓
CMake
↓
NDK
↓
libxxx.so
↓
JNI
↓
Kotlin
必学:
- CMakeLists.txt
- ABI:arm64-v8a / armeabi-v7a
System.loadLibrary- LLDB
- tombstone
- symbolication
- HWASan / GWP-ASan
Android 官方当前建议新 Native 项目优先使用 CMake;Native 内存问题调试中,HWASan 是测试阶段重点工具,GWP-ASan 可用于捕捉生产环境罕见堆错误。
官方
- NDK Guide
developer.android.com/ndk/guides/ - Native Memory Debug
developer.android.com/ndk/guides/… - HWASan
developer.android.com/ndk/guides/… - GWP-ASan
developer.android.com/ndk/guides/…
3. 数字音视频基础:RGB、YUV、PCM、采样率与时间
3.1 RGB 为什么不适合直接传输视频
RGB888:
一个像素 = R + G + B = 3 Byte
1080p:
1920 × 1080 × 3
≈ 6.2 MB / frame
30fps:
≈ 186 MB/s
一分钟原始数据:
≈ 11 GB
所以必须:
- 使用更适合视频的人眼模型表示。
- 再进行空间/时间压缩。
3.2 为什么视频常用 YUV
YUV:
Y = Luma,亮度
U/V = Chroma,色度
人眼对亮度变化更敏感,对高频颜色变化不那么敏感。
所以可以:
保存更多 Y,减少 U/V。
这叫:
Chroma Subsampling
3.3 YUV 4:4:4 / 4:2:2 / 4:2:0
4:4:4
每个像素都有完整色度。
质量高,数据大。
4:2:2
水平方向色度采样减少一半。
4:2:0
水平、垂直都减少色度采样。
移动视频最常见。
1080p YUV420 大致:
1920 × 1080 × 1.5
≈ 3.1 MB/frame
比 RGB888 约减半。
3.4 I420 / NV12 / NV21 为什么容易弄错
I420
Planar:
YYYYYYYY
UU
VV
NV12
Semi-planar:
YYYYYYYY
UVUVUVUV
NV21
YYYYYYYY
VUVUVUVU
NV12 和 NV21:
只差 U/V 顺序。
但顺序错了:
整体颜色严重异常。
Android 关联
Camera、ImageReader、MediaCodec、OpenGL 之间处理像素格式时,必须同时记录:
- pixel format
- width/height
- stride
- slice height
- crop
- UV order
否则“明明 YUV420,为什么还是花屏”并不少见。
3.5 Stride 为什么重要
很多人以为:
一行字节数 = width
硬件 Buffer 不一定。
为了内存对齐:
width = 1920
stride = 1920 / 1984 / 2048 ...
如果读取下一行时错误地用 width,而真实存储按 stride:
图像可能斜、错位、花屏。
3.6 Color Space / Range
常见:
- BT.601
- BT.709
- BT.2020
- Limited Range
- Full Range
粗略:
- SD 常见 BT.601
- HD 常见 BT.709
- UHD/HDR 经常涉及 BT.2020
如果 YUV→RGB 使用错误矩阵:
颜色会偏。
所以现代播放器里 Format Metadata 不能只记录 Codec:
codec
resolution
pixel format
color space
color range
transfer characteristics
HDR metadata
都可能影响最终画面。
3.7 HDR 为什么会让播放器问题更复杂
HDR 不只是“更亮”。
涉及:
- 更高位深,例如 10 bit
- 色域
- EOTF / Transfer
- HDR10 / HLG / Dolby Vision
- Tone Mapping
例如:
HEVC Main10
意味着:
10bit Profile。
某台设备“支持 HEVC”:
不代表:
支持所有 HEVC Profile + Level + HDR Path。
因此能力判断不能只写:
supportsH265 = true
应该至少考虑:
Codec
Profile
Level
Bit Depth
Resolution
Frame Rate
HDR
Secure Playback
3.8 PCM:理解音频的 AVFrame
PCM 可以理解为:
解码后的原始音频采样。
关键参数:
Sample Rate
Channel Count
Sample Format
例如:
48 kHz
Stereo
16-bit PCM
每秒数据:
48000 samples
× 2 channels
× 2 bytes
= 192 KB/s
3.9 Sample Rate 是什么
44.1 kHz:
每秒采样 44100 次。
48 kHz:
每秒 48000 次。
音视频/直播系统常见 48kHz。
如果两段音频 Sample Rate 不同:
不能直接拼接。
需要 Resample。
4. 视频编码:H.264 / H.265 / AV1
编码器核心不是“把每一帧压缩一次”,而是利用:
- 空间冗余
- 时间冗余
- 人眼感知冗余
4.1 帧内预测与帧间预测
Intra Prediction
利用同一帧邻近像素预测。
Inter Prediction
利用前后帧相似区域预测。
视频连续帧往往高度相似,因此帧间预测能显著降低数据量。
4.2 I / P / B Frame
I Frame
独立解码。
特点:
随机访问友好
恢复友好
数据量大
P Frame
参考过去 Frame。
压缩效率高于 I。
B Frame
可以参考过去和未来。
通常进一步提高压缩效率。
代价:
解码顺序和显示顺序不同。
这会直接影响:
- PTS
- DTS
- Decoder Reorder Buffer
- Low Latency
4.3 IDR 和普通 I Frame 的区别
实践中更应该理解 IDR。
IDR(Instantaneous Decoder Refresh):
解码器可以从这里重新建立干净的参考关系。
Seek / 切流 / 错误恢复经常希望落在 IDR 或随机访问点。
简单说:
I Frame = 帧内编码
IDR = 同时具有解码刷新语义的关键随机访问点
4.4 GOP 到底是什么
I B B P B B P B B I
两个关键随机访问点之间的一组帧可理解为 GOP。
GOP 长
优点:
I 帧比例 ↓
压缩效率 ↑
码率通常 ↓
缺点:
随机访问成本 ↑
首帧等待可能 ↑
直播恢复速度 ↓
错误传播风险 ↑
为什么直播常见 1~2 秒关键帧间隔
因为直播需要平衡:
压缩效率
vs
首帧
vs
切流
vs
错误恢复
腾讯云低延时直播实践也将合理 GOP 作为延迟和卡顿的重要调节项。
4.5 为什么 B Frame 对低延迟不友好
B Frame 可能需要未来帧作为参考。
因此编码/解码都可能引入:
Reorder / Lookahead Delay。
对于电影点播:
可以接受。
对于 RTC:
往往不可接受。
所以传统 WebRTC 实时路径中通常强调低重排延迟,而不是追求最高压缩率。
4.6 Profile / Level 不是装饰字段
Profile
决定:
Codec 使用了哪些编码工具。
Level
决定:
分辨率、码率、宏块处理能力等能力范围。
所以设备宣称:
H.264 supported
仍然要检查:
Profile
Level
Resolution
FPS
4.7 Bitrate 与 Quality
同一个 Codec:
Bitrate 越高
通常 Quality 越好
但不是线性。
还受:
- 内容复杂度
- FPS
- Resolution
- GOP
- Encoder Preset
- Rate Control
影响。
4.8 CBR / VBR
CBR
目标是码率更稳定。
适合对网络带宽预算严格的直播场景。
VBR
复杂画面多给码率,简单画面少给码率。
同平均码率下通常画质更优。
但网络瞬时峰值更难预测。
4.9 H.264 / HEVC / AV1 的工程选型
H.264
优势:
- 设备覆盖最好
- 硬解最成熟
- 系统兼容成本低
适合:
最强兼容性 fallback。
HEVC / H.265
优势:
相同视觉质量通常可降低码率。
代价:
- 编解码复杂度更高
- 老设备覆盖下降
- License / 生态考虑
- 10bit / HDR 能力矩阵更复杂
AV1
优势:
- 更现代的压缩效率
- 开放标准生态
代价:
- 老设备硬解覆盖有限
- 软件解码 CPU/功耗可能较高
平台层不要硬编码一种 Codec
应该做 Capability Strategy:
Content Available Formats
+
Device Codec Capability
+
Hardware Acceleration
+
Network
+
Battery / Thermal
↓
Codec Selection
4.10 官方标准
- H.264 / AVC
www.itu.int/rec/T-REC-H… - H.265 / HEVC
www.itu.int/rec/T-REC-H…
截至 2026 年,ITU 仍在维护 H.264/H.265 的当前 Recommendation,因此标准本身不是“十几年前就不再变化的老资料”。
5. 封装、解封装:MP4 / fMP4 / TS / FLV / MKV
5.1 Codec != Container
MP4 Container
├── H.264 Video
├── AAC Audio
└── Subtitle
Codec 解决:
如何压缩音视频。
Container 解决:
如何把多个 Track、时间戳、索引、Metadata 放在一起。
5.2 Demux 到底做什么
MP4
↓
Demux
├── H264 Packet
├── AAC Packet
└── Subtitle Packet
Demux 后:
数据仍然是压缩状态。
所以:
Demux != Decode
5.3 Mux 到底做什么
H264 Packet
AAC Packet
Timestamp
Metadata
↓
Mux
↓
MP4
5.4 Remux 为什么快且无损
ffmpeg -i input.mp4 -c copy output.mkv
大致流程:
MP4
↓ Demux
Packet
↓ Mux
MKV
不存在:
Decode
Encode
因此:
- 快
- CPU 成本低
- 没有重新编码画质损失
5.5 MP4 的核心 Box
MP4 基于 Box / Atom。
至少理解:
ftyp
moov
mdat
ftyp
文件类型/兼容信息。
moov
重要元数据:
- Track
- Time
- Sample Table
- Codec Description
mdat
真正媒体数据。
5.6 为什么 moov 位置会影响起播
普通 MP4 如果:
mdat 在前
moov 在文件末尾
播放器可能需要更多下载/Seek 才拿到完整索引。
Fast Start:
把 moov 移到前面。
这样网络点播可以更快获得媒体结构。
FFmpeg 常见:
-movflags +faststart
5.7 fMP4 为什么适合 Streaming
Fragmented MP4:
初始化段
+
多个 Fragment
可以边生产、边传输、边播放。
HLS / DASH 都可以使用 fMP4/CMAF。
5.8 MPEG-TS 为什么长期用于直播
TS 的特点:
- 小 Packet
- 面向流
- 容错相对友好
- 不依赖完整文件结束才能解析
代价:
Container Overhead 相对较高。
5.9 FLV 为什么在传统直播里常见
RTMP/HTTP-FLV 生态中广泛使用。
优点:
- 简单
- 连续流式传输
- 传统直播生态成熟
但现代流媒体生态越来越多使用:
- CMAF/fMP4
- HLS
- DASH
- WebRTC 等
6. PTS / DTS / Timebase / Clock / A-V Sync
这一章是播放器真正的核心之一。
6.1 DTS 是什么
DTS:
Decoding Timestamp
告诉 Decoder:
什么时候按解码顺序处理这份数据。
6.2 PTS 是什么
PTS:
Presentation Timestamp
告诉 Renderer:
什么时候向用户呈现这帧。
6.3 为什么 B Frame 会导致 PTS != DTS
显示顺序:
I B B P
B 帧需要参考未来的 P。
因此 Decoder 必须先拿到 P。
解码顺序可能变成:
I P B B
所以:
Decode Order != Presentation Order
即:
DTS != PTS
一句话记住:
DTS 面向 Decoder,PTS 面向用户时间轴。
6.4 Timebase 是什么
时间戳不是一定用毫秒。
例如:
time_base = 1 / 90000
PTS = 180000
真实时间:
180000 × 1/90000
= 2 秒
所以:
Timestamp = 整数计数
Timebase = 一个计数单位代表多长时间
6.5 为什么不同 Stream 有不同 Timebase
Video、Audio 可能使用不同时间基。
例如:
Video: 1/90000
Audio: 1/48000
比较两者时间不能直接比较:
video_pts
vs
audio_pts
必须统一换算到:
秒
微秒
统一 Clock
6.6 A/V Sync 到底是什么问题
播放器有多个时间来源:
Audio Clock
Video Clock
External Clock
核心问题:
当前应该播放到哪一时刻?
6.7 为什么常让 Audio 做 Master Clock
人耳对:
- discontinuity
- 音调变化
- 短暂停顿
非常敏感。
Video 更容易通过:
Delay
Drop
Duplicate
做小幅调整。
所以常见策略:
Audio Clock = Master
Video PTS = Follow Audio
不是绝对规则,但非常典型。
6.8 Video 快了怎么办
Audio Clock = 10.000s
Video PTS = 10.080s
Video 提前约 80ms。
策略:
Delay Render
6.9 Video 慢了怎么办
Audio Clock = 10.200s
Video PTS = 10.000s
Video 落后。
如果差距超过阈值:
Drop Frame
因为继续显示旧帧只会越来越落后。
6.10 直播为什么还需要另外一种 Clock
直播多一个概念:
Live Edge。
不仅要音画同步,还要考虑:
播放位置
vs
真实直播时间
如果:
Target Live Offset = 2s
Current Offset = 5s
播放器可能:
稍微加速播放,例如 1.02x。
如果网络差、频繁 Rebuffer:
可以把 Target Live Offset 调大,优先流畅。
Media3 当前 LivePlaybackSpeedControl 就是这种设计:根据当前 live offset 与 target live offset 调整播放速度;发生 rebuffer 后也可以增加目标直播偏移。
6.11 NTP / RTP Timestamp 在 RTC 中是什么关系
RTP Timestamp:
表示媒体采样时间,不是 Unix 时间。
不同媒体时钟率可能不同。
例如 Video:
90 kHz clock
Audio Opus:
48 kHz
RTCP Sender Report 可以建立:
RTP Timestamp
↔
NTP Time
映射。
这对多媒体同步、跨流对齐非常重要。
7. FFmpeg:从 API 到播放器源码
FFmpeg 的价值不是只会命令行。
你的目标应该是:
通过 FFmpeg 理解整个 Media Pipeline。
7.1 六个核心库
libavformat
负责:
- Container
- Demux / Mux
- 部分 Protocol / I/O
libavcodec
负责:
- Codec API
- Encoder / Decoder
libavfilter
Filter Graph:
- Scale
- Crop
- Overlay
- Audio Filter
libswscale
- Pixel Format Convert
- Image Scale
libswresample
- Audio Resample
- Sample Format Convert
- Channel Layout Convert
libavutil
通用基础:
- Frame
- Rational
- Buffer
- Time
- Dict 等
7.2 AVPacket 是什么
AVPacket:
压缩后的编码数据容器。
典型:
Demuxer
↓
AVPacket
↓
Decoder
内容可能是:
- H264
- HEVC
- AAC
- Opus
Packet 往往:
几 KB ~ 几百 KB。
7.3 AVFrame 是什么
AVFrame:
解码后的原始 Frame。
视频:
- YUV
- RGB
音频:
- PCM Samples
典型:
Decoder
↓
AVFrame
↓
Filter / Renderer
1080p YUV420 Frame 可达约 3MB。
7.4 AVPacket 与 AVFrame 最重要的工程区别
AVPacket
压缩态
尺寸相对小
网络/Container 侧
AVFrame
解码态
尺寸大
Render/Filter 侧
因此播放器 Queue 设计通常不能简单:
Packet Queue size = Frame Queue size
Frame Queue 的内存压力远高于 Packet Queue。
7.5 当前 FFmpeg 解码基本模式
现代 FFmpeg API 重点理解:
avcodec_send_packet()
↓
Decoder Internal Buffer
↓
avcodec_receive_frame()
不是简单:
一个 Packet 必然对应一个 Frame。
实际可能:
- 一个 Packet → 多 Frame
- 多 Packet → 一个 Frame
- Decoder 有内部重排
- Flush 阶段还有 Frame 输出
7.6 为什么 send_packet() 返回 EAGAIN
含义通常是:
Decoder 输出尚未被取走,内部无法继续接受新输入。
正确模式:
send packet
↓
receive frames until EAGAIN
↓
send next packet
这帮助你理解 Decoder 是:
有状态的 Pipeline,不是纯函数。
7.7 FFmpeg 最小解码流程
avformat_open_input
↓
avformat_find_stream_info
↓
find video stream
↓
avcodec_find_decoder
↓
avcodec_alloc_context3
↓
avcodec_parameters_to_context
↓
avcodec_open2
↓
av_read_frame
↓
avcodec_send_packet
↓
avcodec_receive_frame
↓
render / dump YUV
7.8 ffprobe 应该成为你的第一反应
播放器异常拿到媒体文件后:
ffprobe -hide_banner -show_streams -show_format input.mp4
重点观察:
format_name
duration
bit_rate
codec_name
profile
level
pix_fmt
width
height
r_frame_rate
avg_frame_rate
time_base
color_space
color_transfer
为什么
在没有打开 Android Studio 之前,就能排除大量:
- Codec 不支持
- Profile 超限
- HDR
- FPS
- Container
- 时间轴
问题。
7.9 ffplay 为什么比从 Codec 源码开始读更适合你
ffplay 是一个小型完整 Player。
它包含:
- Demux
- Packet Queue
- Decode Thread
- Frame Queue
- Audio
- Video
- Clock
- A/V Sync
- Seek
所以阅读顺序:
ffplay overall state
↓
PacketQueue
↓
FrameQueue
↓
decoder_decode_frame
↓
video_refresh
↓
Clock
↓
compute_target_delay
你真正应该学习的是:
模块怎么协作。
而不是一开始进入 H264 宏块数学实现。
7.10 FFmpeg 源码目录
FFmpeg/
├── fftools/
├── libavformat/
├── libavcodec/
├── libavfilter/
├── libavutil/
├── libswscale/
├── libswresample/
└── doc/examples/
重点文件:
fftools/ffplay.c
libavcodec/packet.h
libavutil/frame.h
libavformat/avformat.h
官方仓库:
官方文档:
8. Android Media3 / ExoPlayer:完整架构
Media3 的核心价值不是“比 MediaPlayer 好用”。
它是一个:
组件可注入、可替换的播放器架构。
8.1 核心数据链
MediaItem
↓
MediaSource
↓
MediaPeriod
↓
SampleStream
↓
Renderer
↓
MediaCodec
↓
Surface / AudioTrack
底层读取:
MediaSource
↓
DataSource
↓
Network / File / Cache
8.2 MediaItem
MediaItem 是:
播放内容的“业务描述”。
可以包含:
- URI
- MIME
- DRM
- Subtitle
- Metadata
- Live config
它不是网络加载器。
8.3 MediaSource
MediaSource:
定义媒体、加载媒体,并向 Player 暴露可读取媒体。
不同协议:
ProgressiveMediaSource
HlsMediaSource
DashMediaSource
都统一在这一层抽象。
这就是架构上的:
Protocol/Container Variation 被隔离在 MediaSource 层。
8.4 DataSource
DataSource:
真正读取 Byte 的低层抽象。
例如:
- HTTP
- File
- Asset
- Cache
- Custom Protocol
因此:
MediaSource = 媒体结构 / 生命周期
DataSource = 字节来源
8.5 Extractor
Progressive Media:
MP4 / MP3 / TS...
↓
Extractor
↓
Samples
Extractor 本质上就是 Container Parser / Demuxer 的角色。
8.6 MediaPeriod
可以理解为:
Timeline 中实际可以加载和提供 Samples 的一段媒体。
这对:
- Playlist
- Ad insertion
- Concatenation
- Clipping
很重要。
8.7 Renderer
Renderer 消费 Sample。
视频:
Compressed Sample
↓
MediaCodecVideoRenderer
↓
MediaCodec
↓
Surface
音频:
Compressed Sample
↓
MediaCodecAudioRenderer
↓
AudioSink
↓
AudioTrack
Renderer 是 Player Framework 和设备解码/输出的关键边界。
8.8 TrackSelector
输入:
多个视频 Representation
多个音轨
字幕
Device Capability
User Preference
输出:
选哪些 Track。
ABR 最终选择哪个 Video Track 也和它相关。
8.9 LoadControl
LoadControl 不负责“下载”。
它负责:
什么时候继续 Buffer,以及 Buffer 到什么程度才能播放。
典型概念:
minBuffer
maxBuffer
bufferForPlayback
bufferForPlaybackAfterRebuffer
8.10 为什么 Buffer 不是越大越好
Buffer 大:
Rebuffer 风险 ↓
但:
Memory ↑
Bandwidth Waste ↑
Live Latency ↑
Start Cost ↑
所以:
长视频点播
更偏向稳定。
短视频 Feed
关注:
- 秒开
- Swipe 预测
- Preload
- 流量浪费
直播
关注:
Latency
vs
Rebuffer
完全不同的策略。
8.11 Media3 Preload 的平台意义
现在 Media3 有 DefaultPreloadManager。
典型 Feed:
Current = N
Priority:
N+1 > N-1 > N+2 ...
可以定义每个 Item:
预加载到什么状态、加载多少。
真正的平台问题不是:
“加一个 preload API”。
而是定义策略:
User Swipe Probability
Network Type
Data Saver
Battery
Current Buffer
Item Distance
Content Duration
决定:
预加载谁、多少、什么时候取消。
8.12 LivePlaybackSpeedControl
低延迟直播不能只调 Buffer。
假设:
target offset = 2s
current offset = 4s
可以微加速:
1.02x
追近直播边缘。
如果频繁 Rebuffer:
说明网络承受不了当前 offset。
可以:
增加 target offset,换取稳定性。
这就是典型 Control Loop:
Measure
↓
Compare Target
↓
Adjust Speed / Offset
↓
Observe
非常适合你从 Platform / Performance 角度研究。
8.13 Media3 Analytics
必须掌握:
AnalyticsListenerPlaybackStatsListenerPlaybackStatsEventLogger
不要自己只监听:
onPlaybackStateChanged()
就声称建立了 QoE。
真正的 Analytics 要关联:
EventTime
Timeline
MediaPeriod
Format
Decoder
Load Event
Dropped Frames
Playback State
8.14 Media3 源码阅读顺序
Player
↓
ExoPlayerImpl
↓
ExoPlayerImplInternal
↓
MediaSource
↓
MediaPeriod
↓
SampleStream
↓
Renderer
↓
MediaCodecRenderer
↓
MediaCodec
然后再补:
TrackSelector
LoadControl
DataSource
Extractor
Analytics
Preload
LivePlaybackSpeedControl
官方
- ExoPlayer Customization
developer.android.com/media/media… - Preloading
developer.android.com/media/media… - Live Streaming
developer.android.com/media/media… - AnalyticsListener
developer.android.com/reference/a… - PlaybackStats
developer.android.com/reference/a…
9. MediaCodec / Surface / Codec2 / Android Media Framework
9.1 MediaCodec 是什么
MediaCodec:
Android 暴露给 App 的底层 Codec 接口。
输入:
Compressed H264 / HEVC / AV1
输出:
Raw Frame
or
Surface
9.2 同步模式
dequeueInputBuffer
↓
fill
↓
queueInputBuffer
dequeueOutputBuffer
↓
process
↓
releaseOutputBuffer
容易理解,适合 Demo。
9.3 异步模式
通过:
setCallback(...)
获得:
onInputBufferAvailableonOutputBufferAvailableonOutputFormatChangedonError
Android 官方文档把 Callback 模式作为 MediaCodec 异步处理机制。
9.4 ByteBuffer 输出模式
Decoder
↓
Raw Buffer
↓
App
优点:
- 可以拿到 Frame。
- 可以做算法处理。
- 可以转 YUV / 图像识别。
缺点:
- 更多 CPU 参与。
- 可能产生内存 Copy。
- App 管理复杂。
- 高分辨率成本高。
9.5 Surface 输出模式
MediaCodec
↓
Surface
↓
BufferQueue
↓
SurfaceFlinger / Consumer
App 不需要把几 MB 的 YUV Frame 拷贝进 Java Heap 再画。
所以通常:
视频播放优先 Surface Path。
9.6 Surface 为什么能降低 Copy
Android Graphics Pipeline 中,Surface 是 BufferQueue 的 Producer 侧抽象之一。
Media Decoder 可以:
直接向 Surface 对应的 BufferQueue 输出 Graphic Buffer。
最终 Consumer:
- SurfaceFlinger
- TextureView / GL consumer
- 其他组件
可以接管。
这就是 Android Media/Graphics 的关键交点。
9.7 为什么硬解并不等于“永远最好”
硬解优点:
- CPU 低
- Battery 通常更好
- 高分辨率性能好
但可能:
- Vendor Bug
- 特定 Stream 不兼容
- Codec Resource 不足
- Surface 状态 Bug
- Profile/Level 边界问题
软件解:
- 行为更可控
- 某些边缘 Stream 兼容更好
但:
- CPU
- Battery
- Thermal
代价大。
因此工业 Player 常有:
Hardware Decode
↓ failure
Software Decode Fallback
但不能无脑 fallback:
4K HEVC 软件解可能直接把设备拖垮。
9.8 如何判断 Codec 能力
Android 10+ 可以用:
isHardwareAccelerated()
isSoftwareOnly()
isVendor()
还可以检查:
PerformancePoint
例如:
3840 × 2160 @ 60
设备“支持 HEVC”不表示:
一定稳定支持 4K60 HEVC Main10。
9.9 Codec2 / Stagefright 你应该理解到哪
不要一开始读完整 Framework。
先建立:
App
↓
Media3
↓
MediaCodec Java API
↓
Native Media Framework
↓
Codec2 / Media Codec Service
↓
Vendor Codec
↓
Hardware
AOSP Media 仍然以 Stagefright / Media Framework 作为 Native Media 核心,并通过系统 media / swcodec 模块持续更新。
你的目标:
知道问题可能跨越 Framework、Codec Service、Vendor HAL,而不是要求自己重写 Codec2。
9.10 为什么同一视频不同机型结果不同
因为最终链路可能分别是:
Qualcomm Codec
MediaTek Codec
Samsung Codec
Google Software Codec
因此线上埋点应记录:
codecName
canonicalCodecName
isHardwareAccelerated
isVendor
mime
profile
level
width
height
fps
HDR
device
os
否则日志只有:
DecoderInitializationException
价值非常有限。
10. 音频链路:AAC / Opus / PCM / AudioTrack / 重采样
很多 Android 视频开发对音频明显弱于视频,但 A/V Sync 和直播体验离不开音频。
10.1 AAC
常见于:
- MP4
- HLS
- 直播
- 点播
目标:
高效压缩一般媒体音频。
10.2 Opus
实时通信非常常见。
特点:
- 低延迟
- 支持较宽码率范围
- 语音和音乐兼顾
- WebRTC 生态核心音频 Codec 之一
10.3 解码后是什么
AAC / Opus
↓ Decoder
PCM
↓
AudioTrack
↓
Audio HAL
↓
Speaker
10.4 重采样为什么需要
假设:
Input = 44.1 kHz
Output Device = 48 kHz
必须把时间轴上的采样点重新映射。
FFmpeg:
libswresample
解决:
- Sample Rate Conversion
- Sample Format Conversion
- Channel Layout Conversion
10.5 音频为什么常做 Master Clock
因为:
音频输出设备自己有稳定的 Sample Consumption Rate。
AudioTrack 在消费 PCM 时,天然形成一个:
真实播放时间基准。
这比“假设系统每 33ms 显示一帧视频”可靠。
10.6 音频卡顿为什么比视频掉帧更明显
视频掉一帧:
33ms
很多人不明显感知。
音频出现:
10~20ms discontinuity
就可能产生:
- 爆音
- 断裂
- click/pop
因此音频 Buffer 的策略和 Video 不完全相同。
11. 流媒体协议:HLS / DASH / RTMP / SRT
11.1 HLS 的本质
HLS:
Playlist
+
Segment
+
HTTP
例如:
master.m3u8
├── 1080p playlist
├── 720p playlist
└── 480p playlist
单码率:
playlist.m3u8
├── segment001
├── segment002
└── segment003
11.2 为什么 HLS 易于扩展 CDN
Segment 就是 HTTP Object。
CDN 很擅长:
Cache
Edge Distribution
HTTP
所以 HLS 的优势:
大规模分发简单可靠。
11.3 为什么普通 HLS 延迟较高
例如:
Segment = 6s
Player 等 2~3 个 Segment
仅 Buffer 就可能:
12~18s
再加:
- Encoder
- Packaging
- CDN
总延迟更高。
11.4 LL-HLS 怎么降低延迟
核心不是“把所有 Segment 改成 0.1 秒”。
它引入更细粒度:
Partial Segment / Part
并配合:
- blocking reload
- preload hint
- CMAF
降低发布和获取等待。
但依然保留:
HTTP/CDN 可扩展性。
Apple 官方仍持续维护 HLS 与 Low-Latency HLS 文档。
官方:
developer.apple.com/streaming/
11.5 DASH
DASH:
MPD
+
Representation
+
Segment
与 HLS 思想相似:
HTTP Adaptive Streaming。
核心抽象:
Period
AdaptationSet
Representation
Segment
11.6 ABR 的本质
假设:
1080p 5Mbps
720p 2.5Mbps
480p 1Mbps
估算网络:
1.4Mbps
如果坚持 1080p:
download rate < playback consumption
↓
buffer decreasing
↓
rebuffer
降到 480p:
清晰度下降,但播放连续。
ABR 的根本:
Quality vs Continuity。
11.7 ABR 为什么不能只看 Bandwidth
真实策略至少看:
- throughput
- buffer
- viewport
- device decode capability
- recent switches
- startup phase
- playback speed
- network volatility
否则:
1080
↓
480
↓
1080
↓
480
Quality Oscillation 本身也是坏体验。
11.8 RTMP 今天最应该怎么理解
RTMP 仍常见于:
推流 / Ingest。
例如:
OBS / Mobile Push
↓
RTMP
↓
Origin
观众大规模分发通常不一定继续使用 RTMP,而可能:
Origin
↓
Transmux / Package
↓
HLS / DASH / WebRTC
11.9 SRT
SRT 目标:
低延迟、可靠的流传输。
常见于专业视频贡献链路:
Contribution
Encoder → Cloud
Remote Production
它提供针对丢包/抖动的恢复能力,同时保持较低延迟。
官方源码:
12. WebRTC:实时音视频真正需要理解什么
WebRTC 不是“另一种播放器协议”。
它是:
实时互动音视频系统。
12.1 为什么 RTC 和普通直播的目标不同
点播:
Quality / Smoothness
直播:
Latency + Smoothness
RTC:
Interactive Latency
RTC 中:
晚到的数据有时比丢掉还没价值。
例如视频会议:
一个 2 秒前的视频包即使终于到了:
已经没有实时意义。
12.2 Signaling 不属于 WebRTC 媒体标准本身
双方需要交换:
- SDP
- ICE Candidate
但怎么交换可以由业务决定:
WebSocket
HTTP
Custom RPC
所以:
Signaling ≠ Media Transport
12.3 ICE / STUN / TURN
STUN
用于发现:
NAT 后面的外部映射地址。
ICE
收集多个 Candidate 并尝试找到最佳连接路径。
TURN
P2P 无法建立时:
通过 Relay Server 转发。
TURN 增加:
- Server Cost
- Network Path
- Potential Latency
但提高可连通性。
12.4 RTP
RTP 提供:
- Sequence Number
- Timestamp
- Payload Type
- SSRC
它不保证:
- 不丢包
- 一定顺序
- 一定及时
实时系统允许:
以“及时性”为优先设计。
12.5 RTCP
RTCP 是反馈通道的重要组成。
可用于:
- loss
- timing
- sender/receiver reports
- media feedback
- congestion feedback
IETF 的 RTCP congestion feedback 规范明确讨论了:
- packet sequence
- arrival time
- packet loss
这些信息如何用于发送端拥塞控制。
12.6 Jitter Buffer
网络包到达时间:
10ms
14ms
80ms
20ms
...
不稳定。
直接播放:
声画断裂。
Jitter Buffer:
Network Packets
↓
short buffering/reordering
↓
steady playout
但 Buffer 大:
smoothness ↑
latency ↑
仍然是 Trade-off。
12.7 NACK
丢包:
Sequence 100
101
103
发现:
102 missing
Receiver 请求:
Retransmit 102。
但实时系统中要判断:
重传回来还来不来得及。
12.8 FEC
Forward Error Correction:
发送冗余数据,使接收端在部分丢包时可以恢复。
优点:
- 不等待 RTT。
缺点:
- 占额外带宽。
NACK vs FEC:
Retransmission Cost
vs
Redundancy Cost
需要根据 RTT、Loss、Bandwidth 做权衡。
12.9 PLI / Keyframe Request
Decoder 丢失关键参考导致画面无法恢复:
Receiver 请求发送新的关键帧。
这也是为什么:
参考链和错误恢复是 RTC 编码策略的重要组成。
12.10 Congestion Control 的核心
网络容量在变化:
5 Mbps
↓
1 Mbps
↓
3 Mbps
发送端不能一直 5Mbps。
否则:
Queue Build Up
↓
Delay ↑
↓
Loss ↑
↓
Real-time UX Collapse
拥塞控制需要动态调整:
Encoder Bitrate
Frame Rate
Resolution
WebRTC/IETF 规范把“根据网络 capacity 自适应”视为实时媒体必须具备的核心能力。
12.11 WebRTC 源码阅读顺序
不建议一上来全仓库搜。
api/
↓
pc/ PeerConnection
↓
p2p/ ICE
↓
modules/rtp_rtcp
↓
modules/video_coding
↓
modules/congestion_controller
↓
audio / video
官方:
RFC:
- RTP/WebRTC Transport
www.rfc-editor.org/rfc/rfc8834… - Real-time Congestion Requirements
www.rfc-editor.org/rfc/rfc8836… - RTCP Congestion Feedback
www.rfc-editor.org/rfc/rfc8888…
中文:
13. 直播系统:从 Camera 到观众屏幕
13.1 全链路
Camera
↓
Capture
↓
Preprocess
↓
Encode
↓
Push
↓
Ingest
↓
Transcode
↓
Package
↓
Origin
↓
CDN
↓
Edge
↓
Player
↓
Decode
↓
Render
13.2 延迟不是一个值,而是一组值之和
Capture Delay
+
Encode Delay
+
Push Network Delay
+
Server Delay
+
Transcode Delay
+
Package Delay
+
CDN Delay
+
Player Buffer
+
Decode / Render
=
End-to-End Latency
所以看到:
Player 端直播落后 8 秒
不能直接把 8 秒归因于 Player。
13.3 如何做真正的 End-to-End Latency 测量
最可靠思路:
源头把真实时间编码进:
- SEI
- Metadata
- Frame overlay
- Timestamp mapping
客户端拿到后比较:
Now
-
Source Capture Time
才能知道:
真正端到端延迟。
13.4 Transcoding 为什么存在
主播可能只推:
1080p 6Mbps
但用户网络千差万别。
云端生成:
1080p 6M
720p 3M
480p 1.2M
360p 600K
Player 使用 ABR 动态选择。
代价:
- CPU/GPU 云成本
- 延迟
- 编码画质损失
13.5 为什么“所有用户卡”和“部分用户卡”诊断方向不同
所有用户卡
优先:
Source Capture
Encoder
Uplink
Ingest
Transcode
Origin
部分用户卡
优先:
Edge
ISP
User Network
Device
Decoder
Player Buffer
腾讯云的直播卡顿排查实践也使用这种“主播端 / 云端 / 播放端”链路拆解方法。
13.6 低延迟 vs 卡顿
想把直播:
Buffer 3s → 300ms
直接减少 2.7s 延迟。
但弱网时:
抗抖动空间大幅下降。
于是:
Latency ↓
Rebuffer ↑
低延迟直播最核心的问题就是:
如何做动态平衡,而不是把 Buffer 写死得很小。
13.7 一个更成熟的动态策略
网络好:
target live offset ↓
buffer ↓
playback speed ~ 1.0
网络开始抖:
temporary buffer ↑
target offset ↑
网络恢复:
1.01~1.05x
慢慢追近 live edge
不要暴力:
1.5x 长时间追帧
否则用户明显感知。
13.8 腾讯云低延迟直播案例值得学什么
腾讯云公开实践中,值得你关注的不是某个私有参数,而是思路:
- 以开播成功率、首帧、卡顿等指标驱动。
- 建联/信令本身会拉长首帧。
- 低延迟协议需要为“直播观看”重新设计播放策略。
- 对数据按重要性分层。
- 根据 RTT、重传、带宽调整恢复策略。
- 利用预加载降低首帧。
- 网络差时牺牲部分延迟换流畅。
文章:
cloud.tencent.com/developer/a…
这类文章更接近你未来 Staff/P7 要学习的:
系统 Trade-off,而不是 API。
14. Player Architecture:如何设计工业级播放器
14.1 最基础的 Player Core
Player
├── Source
├── Network
├── Demux
├── Packet Queue
├── Decoder
├── Frame Queue
├── Clock
├── Audio Render
├── Video Render
└── State Machine
14.2 为什么要拆 Queue
典型:
Network / Demux
↓
Packet Queue
↓
Decode
↓
Frame Queue
↓
Render
每一层处理速度不同。
Queue 用来:
解耦不同处理速度。
但 Queue 也会制造:
- Memory
- Latency
- stale data
所以 Queue 不能无限大。
14.3 Packet Queue 与 Frame Queue 的策略应该不同
Packet:
压缩态,单位数据小。
Frame:
解码态,单位数据大。
1080p YUV420:
约 3MB/frame
10 帧:
约 30MB
因此 Frame Queue 通常更严格控制。
14.4 Player State Machine
不要只用:
isPlaying Boolean
至少应该建:
IDLE
PREPARING
READY
PLAYING
PAUSED
BUFFERING
SEEKING
ENDED
ERROR
RELEASED
并定义:
哪些状态允许哪些 Command。
14.5 为什么状态机能减少播放器 Bug
例如:
release()
同时
seek()
如果没有清晰状态:
- race
- native use-after-free
- callback after release
都很常见。
成熟 Player 需要:
Command
↓
State Validation
↓
Serialized Execution
↓
State Transition
↓
Event
14.6 Error Model
不要只定义:
ERROR_PLAYBACK
至少分:
Network
Manifest
Demux
DRM
Decoder Init
Decoder Runtime
Audio Output
Surface
Timeout
Unsupported
Unknown
再带:
Recoverable?
Retry?
Fallback?
User Action?
14.7 Fallback Strategy
例如 Decoder:
HW HEVC
↓ fail
HW H264 alternate representation
↓ fail
SW decode
↓
Unsupported
但每一步都应有:
- capability check
- cost
- metric
- reason code
否则 fallback 会掩盖质量问题。
14.8 Player Pool
短视频 Feed 常见:
避免每次 Swipe 都重新创建完整 Player。
但 Player Pool 也不是越多越好:
Hardware Decoder Resource
Memory
Surface
Network
都有限。
平台策略要回答:
- Pool size?
- Current / Next?
- Reuse Decoder?
- When release?
- Low-memory behavior?
14.9 Cache
分层思考:
HTTP Cache
Media Segment Cache
Application File Cache
Memory Buffer
不要混成一个“缓存”。
Cache 主要解决:
- 重复播放
- Seek
- Feed 返回
- 网络成本
但要控制:
- eviction
- disk
- privacy
- DRM
- stale
15. Multimedia Performance / QoE
Performance 不是“感觉快”。
必须:
定义指标 → 分解阶段 → 采集 → 诊断 → 防回归。
15.1 First Frame Time
表面定义:
Play Request
→
First Frame Rendered
但真正可诊断的版本应拆:
T0 Play Request
T1 Player Ready
T2 Network Start
T3 First Byte
T4 Manifest/Container Ready
T5 First Video Sample
T6 Decoder Created
T7 First Frame Decoded
T8 First Frame Rendered
最终:
FirstFrame = T8 - T0
如果慢:
立即知道哪段慢。
15.2 为什么“首帧平均 800ms”不是一个好指标
平均值会掩盖尾部。
应该看:
P50
P90
P95
P99
例如:
P50 = 500ms
P95 = 2.5s
P99 = 8s
这比:
average = 780ms
更能发现真实问题。
15.3 Playback Success Rate 怎么定义才有意义
你必须先写清楚 Success。
例如:
First Frame Successfully Rendered
则:
Start Success Rate
=
FirstFrame Sessions
/
Play Requests
如果定义:
Played > 5 seconds
含义完全不同。
所以指标平台必须:
有统一 Metric Contract。
15.4 Rebuffer Ratio
Total Rebuffer Duration
/
Effective Playback Duration
例如:
100s 播放
5s Buffer
约 5%。
为什么比 Rebuffer Count 更好
1 次 × 10s
比:
2 次 × 100ms
明显更差。
Count 不能表达严重程度。
15.5 Startup Rebuffer 要单独看
大量直播/短视频:
起播附近的卡顿占比很高。
因此可以拆:
Startup Rebuffer
Mid-play Rebuffer
Seek Rebuffer
原因链不同。
15.6 Dropped Frames
掉帧可能来自:
Decoder too slow
Renderer too slow
Main Thread
GPU
Surface
Sync catching up
单独上报一个:
droppedFrames = 35
还不够。
要关联:
- decoder
- CPU load
- device
- frame rate
- resolution
- render mode
15.7 QoE 维度
最低建议:
App Version
Player Version
OS
Device
Manufacturer
SoC
Network Type
ISP
Country/Region
Protocol
CDN
Codec
Codec Name
HW/SW
Profile/Level
Resolution
FPS
Bitrate
HDR
Content ID
这样才能发现:
某 SoC
+
Android 某版本
+
HEVC Main10
+
4K
=
Decoder Fail Rate ↑
15.8 QoS 与 QoE
可以这样理解:
QoS / Technical Quality
- RTT
- Loss
- Buffer
- FPS
- Bitrate
- Decoder Error
QoE / User Experience
- Start success
- Time to first frame
- Rebuffer
- Watch duration
- Abandon
- Engagement
最终 Staff/Platform 应该能把:
Technical Metric
↓
User Metric
↓
Business Metric
建立关联。
腾讯云公开案例也将首帧、卡顿等质量指标与播放渗透率、播放时长等业务结果联系起来。
15.9 Performance Budget
举例:
P95 First Frame < X
P95 Rebuffer Ratio < Y
Decoder Init Fail < Z
Dropped Frame < N / min
然后:
CI
Dogfood
Canary
Release
每个阶段都有 Gate。
这才是:
Performance Engineering。
16. 播放器线上问题排障手册
这一章建议你反复使用。
16.1 黑屏但有声音
先判断:
Audio 正常说明 Network、Container 至少部分是通的。
排查树:
有 Video Track 吗?
↓
Video Packet 正常吗?
↓
Decoder 初始化成功吗?
↓
Decoder 有 Output Frame 吗?
↓
Frame PTS 正常吗?
↓
Surface 有效吗?
↓
releaseOutputBuffer(render=true) 吗?
↓
Surface lifecycle 是否被销毁/替换?
↓
HDR / pixel format / vendor bug?
重点日志:
codecName
input count
output count
first output pts
rendered count
surface identity
surface lifecycle
format
16.2 花屏 / 绿屏
优先:
Pixel Format
Stride
UV order
Width/Height
Crop
Bit Depth
Decoder Output
如果是 Surface 硬解:
不一定是 App YUV 解析问题,可能是 Vendor Codec / Surface path。
如果是 ByteBuffer:
高概率检查 YUV layout。
16.3 音画不同步
按层查:
Source timestamp
↓
Demux pts/dts
↓
Decoded frame pts
↓
Audio clock
↓
Video clock
↓
Renderer schedule
不要一上来:
给视频加 100ms delay。
那只是掩盖问题。
16.4 首帧慢
阶段拆解:
App preparation
DNS
Connect / TLS
Request
TTFB
Manifest
Segment
Demux
Codec init
First packet
First decode
First render
哪个阶段没有埋点:
哪个阶段就是盲区。
16.5 卡顿
判断:
Buffer 空
Network / Throughput / CDN / ABR
Buffer 有数据但画面卡
Decoder / Render / CPU / GPU / Main Thread
Audio 卡 + Video 卡
可能:
Network
Source
Global Thread Stall
Video 卡但 Audio 顺
可能:
Video Decoder
Render
Surface
Sync
16.6 Decoder Init Failed
记录:
mime
codecName
profile
level
resolution
fps
secure
hdr
surface
策略:
Check capability
↓
Try alternate HW codec
↓
Alternate representation
↓
SW fallback if safe
↓
Report unsupported
16.7 直播延迟越来越大
重点查:
Player Buffer increasing?
Network receive rate?
Decode rate?
Render rate?
Audio clock?
Frame queue?
Server source timestamp?
如果:
Receive normal
Decode normal
Buffer steadily increases
可能播放器没有:
live catch-up / drop strategy。
16.8 内存持续增长
分:
Java Heap
Native Heap
Graphic
Codec Buffer
Disk Cache
不要只看 Android Studio Java Heap。
Native Player 常见:
- Frame Queue
- Packet Queue
- AVBufferRef
- JNI Global Ref
- Surface/GraphicBuffer
17. Platform Engineering:从播放器能力到多团队平台
这是你未来 P6 → P7 / Staff 最需要升级的部分。
17.1 Platform 不是“大而全 SDK”
真正定义:
把多个团队重复、长期、需要一致治理的能力抽象出来。
例如:
Player Platform
├── Player API
├── Source
├── Codec Capability
├── Preload
├── Cache
├── ABR
├── Error
├── Retry/Fallback
├── QoE
├── Experiment
├── Debug Tool
└── Compatibility
17.2 哪些不应该进 Player Platform
Like
Comment
Follow
Shopping Cart
Specific Business Animation
Campaign Rule
因为它们是:
Business Domain。
Platform 最大的能力之一:
知道什么不应该抽象。
17.3 如何判断是否值得平台化
1. 多团队是否重复实现
重复越多:
平台收益越大。
2. 是否存在一致性要求
例如:
- QoE
- Security
- Error Code
- Logging
- Codec policy
非常适合集中治理。
3. 生命周期是否长期存在
一次性业务专项:
不一定值得。
4. 集中实现是否真的降低 TCO
如果平台团队维护复杂度:
比各团队自己做更高,
那就是失败平台。
5. 是否会妨碍业务迭代
平台过度限制:
Adoption 会自然失败。
17.4 Platform Boundary
建议明确四类:
Platform MUST
Platform SHOULD
Business MAY
Business OWNS
例如:
QoE schema MUST
Error taxonomy MUST
Player API MUST
Preload strategy SHOULD + configurable
UI animation Business owns
17.5 Governance
一个技术标准如果只是 Wiki:
不是 Governance。
成熟治理:
Standard
↓
Rule
↓
Automation
↓
Measurement
↓
Exception
↓
Enforcement
↓
Evolution
例如 Compose:
Architecture Guideline
↓
Template
↓
Lint
↓
AI Workflow
↓
CI
↓
Code Review
↓
Metrics
17.6 Adoption 是平台核心指标
不要只说:
“平台已经开发完。”
应该看:
Number of Teams
Number of Apps
Traffic Coverage
Feature Coverage
Migration Rate
Developer Satisfaction
Integration Cost
Escape Rate
17.7 TCO:Total Cost of Ownership
平台价值不能只看:
开发一次省了多少代码。
还要看:
Platform Team Cost
Maintenance
Migration
Compatibility
Incident Cost
Business Integration Cost
Training
长期:
Total Org Cost with Platform
<
Total Org Cost without Platform
平台才有意义。
17.8 你的 Compose AI Migration 应该怎么升级
你的两个 Workflow:
New Compose Page
Legacy → Compose
已经可以按 Platform 思维设计。
不要把 AI 定义成“架构决策者”
稳定规则应该固化成:
Architecture Contract
Template
Lint
Static Analysis
Test
AI 负责:
Analyze
Transform
Generate
Explain
Repair
17.9 Migration 验证矩阵
不是:
Build Success = Migration Success。
必须检查:
Behavior
Visual
Navigation
State
Analytics
Accessibility
Performance
Crash
Rollback
尤其 Legacy → Compose:
行为等价比代码相似重要得多。
17.10 AI Workflow 的 Platform 结构
Input
↓
Code / Context Analyzer
↓
Architecture Rules
↓
Migration Planner
↓
AI Transformation
↓
Static Validation
↓
Build
↓
Unit Test
↓
Screenshot / UI Test
↓
Performance Check
↓
Human Review
↓
Canary
↓
Metrics
↓
Rollback
这就不再是:
Prompt 技巧。
而是:
Engineering Migration System。
18. 实战项目与学习任务
不要做几十个无关 Demo。做 6 个能串起来的 Lab。
Lab 1:C/C++ Native Memory Lab
实现:
malloc/free
use-after-free
double-free
unique_ptr
shared_ptr cycle
weak_ptr
producer-consumer
然后:
- LLDB
- HWASan
目标:
能看 Native 生命周期与线程问题。
Lab 2:Media Inspector
输入任意媒体文件,输出:
{
"container": "mp4",
"duration": 120.0,
"video": {
"codec": "h264",
"profile": "High",
"level": 41,
"width": 1920,
"height": 1080,
"fps": 30,
"pix_fmt": "yuv420p",
"time_base": "1/90000"
},
"audio": {
"codec": "aac",
"sample_rate": 48000,
"channels": 2
}
}
使用:
ffprobe
+
FFmpeg libavformat
目标:
Container / Codec / Metadata 真正打通。
Lab 3:Packet → Frame
FFmpeg API:
av_read_frame
↓
AVPacket
↓
avcodec_send_packet
↓
avcodec_receive_frame
↓
AVFrame
打印:
packet.pts
packet.dts
packet.duration
stream.time_base
frame.pts
frame format
目标:
真正理解 PTS/DTS,而不是背定义。
Lab 4:Android Mini Player
只使用:
MediaExtractor
+
MediaCodec
+
Surface
不使用 Media3。
实现:
Play
Pause
Seek
EOS
目标:
去掉高层 Framework 后理解 Android Decode Pipeline。
Lab 5:Media3 Buffer / Preload Experiment
准备弱网模拟。
修改:
- Buffer policy
- preload target
- priority
记录:
P50/P95 First Frame
Rebuffer Ratio
Traffic Waste
Memory
Battery
目标:
形成“策略 → 指标 → Trade-off”思维。
Lab 6:QoE SDK
事件:
PLAY_REQUEST
NETWORK_START
FIRST_BYTE
SOURCE_READY
DECODER_INIT
FIRST_DECODED_FRAME
FIRST_RENDERED_FRAME
BUFFER_START
BUFFER_END
FORMAT_CHANGE
DECODER_ERROR
PLAY_END
维度:
device
os
codec
decoder
network
protocol
cdn
resolution
自动计算:
- startup
- rebuffer
- success
- error rate
目标:
从 Player Engineer 进入 Performance / Platform。
19. 推荐中文资料、课程、官方文档与源码索引
19.1 中文第一梯队:基础原理
雷霄骅音视频资料
主页:
推荐主题:
视音频数据处理入门
FFmpeg 源码简单分析
最简单 FFmpeg Player
H264 NALU
YUV/RGB
PCM
FLV
RTP
一个可读镜像/整理:
使用方式:
原理与思路重点学习;FFmpeg API 已发生变化,旧代码不要直接复制到新工程。
19.2 中文:Android / OpenGL / 音视频
音视频开发进阶
内容覆盖:
- FFmpeg
- Android
- OpenGL
- WebRTC
- Vulkan
适合作为:
中文补充解释和实验参考。
19.3 中文:真实直播工程案例
腾讯云:快直播低延时播放质量优化
cloud.tencent.com/developer/a…
重点看:
- 首帧
- 建联
- 弱网
- FEC/重传
- 预加载
- 卡顿
- 延迟 vs 流畅
腾讯云:低延时直播架构与弱网
cloud.tencent.com/developer/a…
重点看:
- GOP
- Buffer
- 延迟
- 卡顿指标
- 推流端/云端/播放端分层
腾讯云:直播卡顿问题及优化
cloud.tencent.com/developer/a…
重点看:
- QoS/QoE
- 全链路监控
- 卡顿归因
19.4 中文 WebRTC
WebRTC 中文网:
适合:
- ICE
- STUN
- TURN
- RTP/RTCP
- SDP
- WebRTC 实战
最终一定回官方/RFC 校准。
19.5 系统中文课程
极客时间:快手·音视频技术入门课
time.geekbang.org/column/intr…
作者:
快手音视频首席架构师刘歧。
适合:
- 系统补音视频基础
- FFmpeg
- 转码
- 直播
极客时间:快手·移动端音视频开发实战
time.geekbang.org/column/intr…
与你背景匹配度很高:
- Android/iOS
- Render
- Player
- Capture
- Encode
- 架构
极客时间:从 0 打造音视频直播系统
time.geekbang.org/column/intr…
重点:
- WebRTC
- RTC
- RTP/RTCP
- 直播系统
19.6 Bilibili 补充课程
可以选择性看,不建议作为唯一标准答案。
例如综合 FFmpeg / Player / WebRTC 系列:
重点选择:
- ffplay 架构
- 音视频同步
- H264
- WebRTC
- 直播延迟
- 黑屏/绿屏/花屏
原则:
视频负责“有人给你讲一遍”,结论必须用官方文档和代码验证。
19.7 FFmpeg 官方
Documentation:
Source:
Doxygen:
推荐源码:
fftools/ffplay.c
libavcodec/packet.h
libavutil/frame.h
libavformat/avformat.h
doc/examples/
19.8 Android 官方
Media3
developer.android.com/media/media…
Customization:
developer.android.com/media/media…
Preloading:
developer.android.com/media/media…
Live:
developer.android.com/media/media…
HLS:
developer.android.com/media/media…
Analytics:
developer.android.com/reference/a…
PlaybackStats:
developer.android.com/reference/a…
MediaCodec
developer.android.com/reference/a…
Codec Performance:
developer.android.com/media/optim…
Media Performance:
developer.android.com/media/optim…
Android Native Media
AOSP Media:
source.android.com/docs/core/m…
Media Modules:
source.android.com/docs/core/m…
Surface / BufferQueue:
source.android.com/docs/core/g…
19.9 NDK
NDK:
developer.android.com/ndk/guides/
Native Memory:
developer.android.com/ndk/guides/…
HWASan:
developer.android.com/ndk/guides/…
19.10 Streaming / Codec 官方
H.264:
H.265:
Apple HLS:
developer.apple.com/streaming/
DASH-IF:
SRT:
19.11 WebRTC / RTP 官方
WebRTC:
RTP in WebRTC:
www.rfc-editor.org/rfc/rfc8834…
Congestion Control Requirements:
www.rfc-editor.org/rfc/rfc8836…
RTCP Congestion Feedback:
www.rfc-editor.org/rfc/rfc8888…
20. 12 个月执行路线
你已经有多年 Android + 视频/直播经验,不应该按“小白一年课表”学。
重点补“底层解释力 + 系统设计能力”。
Month 1:C / C++ / NDK
重点:
Pointer
Memory
RAII
Smart Pointer
Thread
Condition Variable
JNI
CMake
HWASan
产出:
Native Memory Lab。
Month 2:Raw Media
重点:
RGB
YUV
Stride
Color Space
PCM
Sample Rate
产出:
YUV Viewer + PCM Player。
Month 3:Codec
重点:
H264
HEVC
AV1
I/P/B
GOP
IDR
Profile/Level
Bitrate
产出:
FFmpeg 编码参数实验报告。
Month 4:Container / Timestamp
重点:
MP4
fMP4
TS
Demux
Mux
PTS
DTS
Timebase
产出:
Media Inspector + Packet/Frame Logger。
Month 5:FFmpeg
重点:
libavformat
libavcodec
AVPacket
AVFrame
ffplay
产出:
Mini FFmpeg Player。
Month 6:A/V Sync
重点:
Clock
Audio Master
Frame Delay
Drop
Seek
产出:
可人为制造 ±100ms、±500ms 不同步的实验 Player。
Month 7:Media3
重点:
MediaSource
MediaPeriod
DataSource
Renderer
TrackSelector
LoadControl
Preload
Analytics
产出:
Media3 源码调用链文档。
Month 8:MediaCodec / AOSP
重点:
MediaCodec
Surface
BufferQueue
HW/SW Codec
Vendor
Codec2
产出:
MediaExtractor + MediaCodec Mini Player。
Month 9:Streaming
重点:
HLS
DASH
CMAF
ABR
RTMP
SRT
产出:
本地多码率 HLS + Android ABR 实验。
Month 10:WebRTC
重点:
ICE
STUN
TURN
RTP
RTCP
Jitter
NACK
FEC
Congestion
产出:
一对一 RTC + Loss/RTT 模拟记录。
Month 11:QoE / Performance
重点:
First Frame
Rebuffer
Decoder
Dropped Frame
Live Offset
Metrics
产出:
QoE SDK + Dashboard Schema。
Month 12:Platform
输出一份真正的:
Mobile Multimedia Platform Design
必须包含:
Problem
Users / Teams
Boundary
Architecture
API
Error Model
QoE
Codec Strategy
Preload
Cache
Fallback
Governance
Adoption
Migration
Rollback
TCO
Evolution
这份设计才是你从:
“有 5 年视频直播经验的 Android 高级工程师”
往:
Mobile Multimedia / Performance / Platform Staff Engineer
升级时最有价值的最终产物。
最终学习标准
完成整套知识以后,你应该能做到的不是背诵:
PTS 是展示时间。
而是遇到:
“Android 某些 HEVC 直播偶发音频正常但画面黑 2 秒,然后恢复”
你可以建立完整假设:
Network?
↓
Video packet?
↓
Keyframe / GOP?
↓
Decoder waiting reference?
↓
Vendor Codec?
↓
Surface Lifecycle?
↓
Output frame exists?
↓
PTS delayed?
↓
Live sync?
并知道:
每一层应该采什么日志、看什么源码、怎样设计 fallback、怎样把这一类问题变成平台能力。