编解码、封装、传输、会话是四层叠加,不是四个并列的选项。画质由编码器决定,协议只管怎么送——所以「哪个协议画质好」这个问题本身是错的。
1.2.1 讲了连接怎么建。但连接只是「路」,路上跑什么还得定义。这一节先把「跑的内容」分成四层,后面第 2~6 章再一层一层拆开。
一、四层叠加,不是并列
很多资料把 RTMP、HLS、H.264 并列成「几种视频格式」,这是把不同层次的东西混在一起了。
① 编码层:把画面压成码流。H.264、H.265、AV1。 ② 封装层:把码流切成包,加时间戳、加索引。TS、FLV、MP4、MKV。 ③ 传输层:包怎么在网络上走。TCP、UDP、QUIC。 ④ 会话层:怎么开始、暂停、停止、定位。RTSP、RTMP、HLS、HTTP-FLV。
四层是自由组合的:同一条 H.264 码流,装进 TS 走 RTSP 也行,装进 FLV 走 RTMP 也行。甚至同一个 HLS 里,也可以同时塞 H.264 和 H.265 两份。
理解这个分层,后面看协议就不会乱: RTMP 规定的只是第 ③④ 层,它完全不关心你编的是 H.264 还是 H.265。
二、I 帧、P 帧、B 帧:压缩靠什么
视频能压到几百分之一,靠的是「大部分画面没变」。
I 帧(帧内编码):只用自己的像素压缩,像一张 JPEG。它可以独立解码,所以是关键帧。 P 帧(前向预测):只存「和前一个参考帧的差」,加上运动矢量(这块内容往左移了 3 个像素)。 B 帧(双向预测):同时参考前后两帧,压得最狠,但要多等一帧才能解。
关键点:P 帧和 B 帧都依赖别的帧。 所以一旦丢了 I 帧,后面一串全废——这叫「花屏」。 流媒体的每个片段都必须从 I 帧开始,就是为了这个。
GOP(Group of Pictures)是两个 I 帧之间的间隔。 GOP 越短,越容易从丢包里恢复,但码率越高; GOP 越长,压缩率越好,但一丢就是一大片。 直播通常设 1~2 秒一个 I 帧,正好和 HLS 的切片对齐。
三、码率、质量、GOP 三者的拉扯
码率 = 每秒多少比特。 它是「花的钱」,画质是「买到的东西」。
同样码率下,画面越复杂(运动的树叶、雨滴、噪点)质量越差——因为编码器要把有限的比特分给更多「新信息」。 这就是为什么风景片能压得很小,体育直播压不动。
质量由三个旋钮决定: ① CRF/QP:恒定质量,码率浮动。质量优先时用它。 ② 目标码率(ABR):恒定码率,质量浮动。网络带宽固定时用它。 ③ preset:编码速度换压缩率。veryfast 比 medium 快两倍,但同画质码率高 20%。
直播必须用 ABR 或 CBR——因为你的上行带宽是固定的,码率一飙就卡。 点播才用 CRF,让码率随画面复杂度浮动。
四、协议不决定画质
经常听到「RTMP 画质不如 HLS」——这是错觉。 同一条码流,装进 RTMP 送和装进 HLS 送,解出来是一模一样的帧。
真实的差别来自别处: ① 码率上限:RTMP 常用于上行推流,主播的上行带宽决定了码率天花板;HLS 用于下行分发,天花板在服务端。 ② 转码:服务端经常把 RTMP 流转成多档 HLS,转码过程中降了码率——你看到的「差」,差在转码这一步。 ③ 丢包处理:UDP 丢了就花屏,TCP 丢了会等重传、卡一下。两种「差」不一样,但都不是编码器的问题。
所以判断画质问题,要沿着链路找: 编码器设的码率是多少 → 中间转码了吗 → 传输丢包了吗。 把「协议」当嫌疑人,基本是冤枉的。
五、动手 Demo:同一个画面,三种 CRF,码率与质量各是多少
用 libx264 真编 60 帧,CRF 取 18/28/38,再解回来和原始帧逐像素比,算 PSNR。 画面里特意加了会移动的细纹理——纯渐变的画面压得太狠,三档分不出差别。
import io, math, numpy as np, av
W, H, N, RNG = 640, 360, 60, np.random.default_rng(7)
NOISE = RNG.integers(-7, 8, size=(H, W)).astype(np.int16)
def frame(i):
base = np.linspace(16, 190, W)[None, :] * np.ones((H, 1))
gx = (np.arange(W)[None, :] + i * 3) // 4
gy = np.arange(H)[:, None] // 4
y = base + ((gx + gy) % 2) * 42 + NOISE
cx = 40 + (i * 9) % (W - 80)
y[H // 2 - 40:H // 2 + 40, cx:cx + 60] = 235
f = av.VideoFrame(W, H, "yuv420p")
f.planes[0].update(np.clip(y, 16, 235).astype(np.uint8).tobytes())
for p in (1, 2):
f.planes[p].update(bytes([128]) * f.planes[p].buffer_size)
f.pts = i
return f
src = [frame(i) for i in range(N)]
for crf in (18, 28, 38):
buf = io.BytesIO()
out = av.open(buf, "w", format="mpegts")
st = out.add_stream("libx264", rate=25)
st.width, st.height, st.pix_fmt = W, H, "yuv420p"
st.options = {"crf": str(crf), "preset": "veryfast", "g": "25"}
for fr in src:
for pk in st.encode(fr):
out.mux(pk)
for pk in st.encode(None):
out.mux(pk)
out.close()
blob = buf.getvalue()
rin, se, cnt, k = av.open(io.BytesIO(blob), format="mpegts"), 0.0, 0, 0
for fr in rin.decode(rin.streams.video[0]):
if k >= N:
break
a = fr.to_ndarray(format="yuv420p")[:H].astype(float)
b = src[k].to_ndarray(format="yuv420p")[:H].astype(float)
se += ((a - b) ** 2).sum()
cnt += a.size
k += 1
rin.close()
psnr = 10 * math.log10(255 * 255 / (se / cnt))
print("CRF %2d %5.0f kbps %5.1f dB %4d KB"
% (crf, len(blob) * 8 / 1000 / (N / 25), psnr, len(blob) // 1024))
实跑输出:
--------------------------------------------------------------------
编解码器——同一个画面,三种码率,质量差多少
--------------------------------------------------------------------
① 真编三种质量,看码率与 PSNR
**CRF 是 x264 的「恒定质量」旋钮**:数字小 = 质量高 = 码率高。
**PSNR 是拿解码后的画面和原始画面逐像素比,越高越像,40 dB 以上肉眼基本看不出。**
CRF 码率 PSNR 60 帧字节 编码耗时
------------------------------------------------------------
18 3594 kbps 35.7 dB 1052 KB 203 毫秒
28 419 kbps 32.3 dB 122 KB 111 毫秒
38 381 kbps 22.1 dB 111 KB 160 毫秒
**CRF 从 18 放到 38,码率降到 11%,PSNR 掉 13.6 dB。**
**但真正值得看的是 28 到 38 这一段:码率只降了 9%,PSNR 却掉了 10.2 dB。**
**说明「降码率」与「掉质量」不是一条直线——38 那档是拿画质换来的数字好看。**
**实际工程里直播用 CRF 23~28,点播才用 18~20。**
② 「视频流协议」到底约定了什么
层 管什么 谁负责
--------------------------------------------------------------
编码器 把画面压成码流(H.264/H.265) libx264 等
封装 把码流切成包、加上时间戳与索引 TS/FLV/MP4
传输 包怎么在网络上走、怎么重传 TCP/UDP
会话 怎么开始、怎么暂停、怎么停 RTSP/RTMP
**四层是叠加的,不是并列的。**
**同一条 H.264 码流,装进 TS 走 RTSP 也行,装进 FLV 走 RTMP 也行。**
**所以「RTMP 与 HLS 哪个画质好」这个问题本身是错的——画质由编码器决定,协议只管怎么送。**
三档的码率差了几倍,PSNR 却没差那么多——这就是「边际收益递减」。 最有意思的是 28 到 38 这一段:码率几乎没降,PSNR 掉了一大截。 说明 38 那档是拿画质换的数字好看。 注意最高那档的 PSNR 也只有 35 dB 左右——因为画面里的细噪声本来就压不掉,这不是编码器不行。
30 秒实验
- 把
preset从veryfast换成medium,看码率降多少、耗时涨多少。这就是「用时间换码率」。 - 把
g从 25 改成 250,看码率掉多少,再想想要是丢一个包会怎样。 - 把画面里的
NOISE去掉再跑,你会发现三档差距突然变小——说明「画面复杂度」比「CRF 数字」更能决定码率。
常见误区
- 「H.265 一定比 H.264 好」——同画质省码率,但编码慢、老设备解不了。选型看终端,不看参数表。
- 「码率越高画质越好」——同一条码流,CRF 18 编出来 3.6 Mbps,换成 CRF 38 可能只有 0.4 Mbps,但把 3.6 Mbps 的再转一遍码,画质并不会变好。
- 「B 帧越多越好」——B 帧要等后面的帧才能解,直播用多了会增加编码延迟,通常只在前瞻里用 0~3 个。
自测三问(点开对答案)
1. RTMP 和 H.264 是同一层的东西吗? —— **不是。**H.264 是编码层,RTMP 是会话层,**RTMP 里可以装 H.264,也可以装 H.265。** 2. 为什么直播的 GOP 通常设 1~2 秒? —— **为了和切片对齐,也为了丢包后能快速恢复。**GOP 太长,一个新观众要等很久才能等到关键帧。 3. 同样的 CRF,为什么体育直播比风景片码率高很多? —— **因为画面变化大,可预测的部分少,编码器要花更多比特描述新信息。**- ☐ (本节完,下一节 1.2.3 常见的流媒体协议)