Android / 视频 / 直播 / C/C++ 多媒体技术知识库(自学版)

30 阅读41分钟

Android / 视频 / 直播 / C/C++ 多媒体技术知识库

适用背景:高级 Android 开发、已有视频/直播业务经验,希望系统补齐播放器底层、C/C++、流媒体、性能与平台化能力。
目标方向:Mobile Architecture + Mobile Performance + Multimedia Platform → P7 / Staff Engineer
学习原则:中文资料建立理解 → 官方文档校准 → 源码验证 → Demo 实验 → 线上问题 → 平台设计


目录

  1. 先建立完整的 Multimedia 心智模型
  2. C / C++ / NDK:只学播放器真正需要的部分
  3. 数字音视频基础:RGB、YUV、PCM、采样率与时间
  4. 视频编码:H.264 / H.265 / AV1
  5. 封装、解封装:MP4 / fMP4 / TS / FLV / MKV
  6. PTS / DTS / Timebase / Clock / A-V Sync
  7. FFmpeg:从 API 到播放器源码
  8. Android Media3 / ExoPlayer:完整架构
  9. MediaCodec / Surface / Codec2 / Android Media Framework
  10. 音频链路:AAC / Opus / PCM / AudioTrack / 重采样
  11. 流媒体协议:HLS / DASH / RTMP / SRT
  12. WebRTC:实时音视频真正需要理解什么
  13. 直播系统:从 Camera 到观众屏幕
  14. Player Architecture:如何设计工业级播放器
  15. Multimedia Performance / QoE
  16. 播放器线上问题排障手册
  17. Platform Engineering:从播放器能力到多团队平台
  18. 实战项目与学习任务
  19. 推荐中文资料、课程、官方文档与源码索引
  20. 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,可以和底层播放器工程师讨论同一套问题。

重点只抓四块:

  1. 内存
  2. Ownership
  3. 并发
  4. 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>

不要只知道“这是智能指针”。

要问:

  1. 为什么这里必须共享所有权?
  2. 谁是逻辑 Owner?
  3. 是否可能形成环?
  4. 生命周期跨了哪些线程?

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 是两种运行时之间的桥梁。

你需要理解:

  • JNIEnv
  • jobject
  • jclass
  • 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 可用于捕捉生产环境罕见堆错误。

官方


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

所以必须:

  1. 使用更适合视频的人眼模型表示。
  2. 再进行空间/时间压缩。

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

编码器核心不是“把每一帧压缩一次”,而是利用:

  1. 空间冗余
  2. 时间冗余
  3. 人眼感知冗余

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 官方标准

截至 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

官方仓库:

github.com/FFmpeg/FFmp…

官方文档:

ffmpeg.org/documentati…


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

必须掌握:

  • AnalyticsListener
  • PlaybackStatsListener
  • PlaybackStats
  • EventLogger

不要自己只监听:

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

官方


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(...)

获得:

  • onInputBufferAvailable
  • onOutputBufferAvailable
  • onOutputFormatChanged
  • onError

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

它提供针对丢包/抖动的恢复能力,同时保持较低延迟。

官方源码:

github.com/Haivision/s…


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

官方:

webrtc.org/

RFC:

中文:

webrtc.org.cn/


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 腾讯云低延迟直播案例值得学什么

腾讯云公开实践中,值得你关注的不是某个私有参数,而是思路:

  1. 以开播成功率、首帧、卡顿等指标驱动。
  2. 建联/信令本身会拉长首帧。
  3. 低延迟协议需要为“直播观看”重新设计播放策略。
  4. 对数据按重要性分层。
  5. 根据 RTT、重传、带宽调整恢复策略。
  6. 利用预加载降低首帧。
  7. 网络差时牺牲部分延迟换流畅。

文章:

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 中文第一梯队:基础原理

雷霄骅音视频资料

主页:

blog.csdn.net/leixiaohua1…

推荐主题:

视音频数据处理入门
FFmpeg 源码简单分析
最简单 FFmpeg Player
H264 NALU
YUV/RGB
PCM
FLV
RTP

一个可读镜像/整理:

www.cnblogs.com/scotth/p/79…

使用方式:

原理与思路重点学习;FFmpeg API 已发生变化,旧代码不要直接复制到新工程。


19.2 中文:Android / OpenGL / 音视频

音视频开发进阶

glumes.com/

内容覆盖:

  • 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 中文网:

webrtc.org.cn/

适合:

  • 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 系列:

www.bilibili.com/video/BV1XY…

重点选择:

  • ffplay 架构
  • 音视频同步
  • H264
  • WebRTC
  • 直播延迟
  • 黑屏/绿屏/花屏

原则:

视频负责“有人给你讲一遍”,结论必须用官方文档和代码验证。


19.7 FFmpeg 官方

Documentation:

ffmpeg.org/documentati…

Source:

github.com/FFmpeg/FFmp…

Doxygen:

ffmpeg.org/doxygen/tru…

推荐源码:

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:

www.itu.int/rec/T-REC-H…

H.265:

www.itu.int/rec/T-REC-H…

Apple HLS:

developer.apple.com/streaming/

DASH-IF:

dashif.org/

SRT:

github.com/Haivision/s…


19.11 WebRTC / RTP 官方

WebRTC:

webrtc.org/

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、怎样把这一类问题变成平台能力。