播放器的核心不是解码,是按时间戳把帧送出去——它大部分时间在等。 解码比播放快得多,不快进就必须压着节奏走。
2.4 把图拼成了视频,这一节讲怎么把它放出来。 2.6 用 ffprobe 看信息,三个工具到此集齐。
一、ffplay 是什么
它是 FFmpeg 自带的播放器,和 ffmpeg、ffprobe 是同一个包里的三个 exe。 作用很朴素:验证一个文件能不能正常播、音画对不对得上。
特点 具体表现 意味着什么
极简 没有播放列表、没有设置界面 不是给人日常看片的 吃 SDL 要图形界面才能起窗口 纯命令行服务器上跑不起来 参数即调试 命令行能改同步、改滤镜 排障时比 GUI 播放器有用
第二行是实际会遇到的问题:在没有桌面环境的 Linux 服务器上, ffplay 起不来窗口,会报 SDL 相关的错。这不是它坏了,是它本来就要窗口。
那种场合要做的是「把流转出去」而不是「在本地看」——第八章的推流就是干这个的。
二、播放器实际在做的五件事
「播放」这个词太笼统,拆开看是五个环节,每个环节坏掉的症状都不一样:
环节 在干什么 坏了的表现
解复用 从容器里读出压缩包 打不开、一直转圈 解码 把包还原成像素 花屏、绿屏、马赛克 排队 按时间戳排好顺序 卡顿、跳帧、倒序 同步 让音视频对齐 口型对不上 等待 到点才送出去显示 (这是正常状态)
最后一行最容易被忽略:播放器大部分时间在 sleep。 因为解码只要几毫秒,而一段 5 秒的片子要放 5 秒。
下面 Demo 把这两个数量了一遍:解码耗时占时长的比例小到可以忽略。 所以「播放卡」几乎从来不是解码不够快,而是同步逻辑或者磁盘读取出了问题。
三、倍速播放为什么好做
既然本来就是在等,那把等待时间除以 2 就是 2 倍速。 解码能力足够的话,倍速几乎不增加 CPU 负担——只是少睡一会儿。
场景 倍速时发生什么 结果
解码富余 少睡一半时间 流畅,CPU 基本不变 解码刚好够 没有可压缩的等待 开始丢帧 解码不够 等待已经是零 倍速无效,照样卡
这解释了一件事:同一台机器,1080p 能 2 倍速,4K 却不行。 不是软件的问题,是解码这条腿已经跑到极限了,没有余量可挤。
反过来说,如果 4K 也能轻松 2 倍速,那说明硬件解码在起作用—— 这是判断「有没有走硬解」的一个土办法。
四、动手 Demo:播放器其实一直在等
沙箱起不了播放窗口,但可以量最关键的那一半:把整段素材解码完要多久。 拿它和素材时长一比,就能看出播放器的时间都花在哪。
import time
import av
c = av.open(u"src.mp4")
dur = float(c.duration) / 1000000 # 微秒转秒
t0 = time.perf_counter()
n = 0
for f in c.decode(video=0):
n += 1 # 只解,不做任何处理
t = (time.perf_counter() - t0) * 1000
c.close()
print(u"时长 %.0f 毫秒" % (dur * 1000))
print(u"解码 %d 帧用了 %.1f 毫秒" % (n, t))
print(u"解码占时长 %.1f%%" % (t / (dur * 1000) * 100))
实跑输出:
--------------------------------------------------------------------
ffplay 视频播放——播放器其实一直在等
--------------------------------------------------------------------
**① 先量一件事:解码到底要多久**
项 值
-----------------------------------------------------
素材时长 5000 毫秒(125 帧 ÷ 25 fps)
全部解码耗时 7.7 毫秒
解码占时长的比例 0.2%
**解码只要 7.7 毫秒,可这段片子要放 5000 毫秒。**
**中间那 4992 毫秒,播放器在干什么?在等。**
**② 播放器的核心不是「解码」,是「按时间戳把帧送出去」**
环节 干什么 出问题的表现
---------------------------------------------------
解复用 读出压缩包 打不开、一直转圈
解码 还原成像素 花屏、绿屏
排队 按 pts 排好顺序 卡顿、跳帧
同步 音视频对齐 口型对不上
等待 到点才送出去 —
**最后一行是很多人没想过的:播放器大部分时间在 sleep。**
**因为解码比播放快得多,不快进的话就必须压着节奏走。**
**③ 这也是「倍速播放」好做的原因**
**既然本来就在等,把等待时间除以 2 就是 2 倍速——**
**解码能力足够的话,倍速几乎不增加 CPU 负担。**
**反过来,如果解码已经追不上播放(4K 高码率),那倍速就会开始丢帧。**
「只解,不做任何处理」这句很重要:一旦加了缩放、转格式,量的就不是解码了。 这个实验要的正是「纯解码」这个下限。
两个数字一摆,结论就出来了:解码只占时长的很小一个零头,剩下全是等待。 播放器的工作重心因此从「算得快」变成了「等得准」。
「等得准」靠的是时间戳。每一帧带着 pts(显示时刻),到点才送出去。 pts 错了,表现就是卡顿、跳帧或者音画不同步——都不是解码的错。
这也是为什么修「播放卡」要先看时间戳,而不是先怀疑性能。
用 ffprobe 的 -show_packets 看一眼 pts 是不是从 0 开始、有没有大跳变,比猜快得多。
30 秒实验
- 放一次:
ffplay 输入.mp4,然后按s步进一帧、按p暂停,体会「一帧一帧」是什么。 - 只看视频:
ffplay -an 输入.mp4,把音频关掉,看画面节奏是不是更稳。 - 看时间戳:
ffplay -debug_ts 输入.mp4,它会打出每一帧的 pts,能直接看出同步逻辑。
常见误区
- 以为 ffplay 卡就是解码太慢。绝大多数情况是同步或读取的问题。解码通常只占时长的百分之几。
- 在无桌面的服务器上用 ffplay。它依赖 SDL,需要图形界面。那种场合该用推流而不是本地播放。
- 拿 ffplay 当日常播放器。它没有播放列表、没有设置界面,定位是「验证文件」的工具,不是给人看片的。
自测三问(点开对答案)
1. 为什么说播放器大部分时间在等? —— 因为解码一段 5 秒的片子只要几毫秒,而这段片子必须花 5 秒放完。剩下的时间只能压着节奏等,不能快进。 2. 倍速播放为什么几乎不增加 CPU 负担? —— 倍速做的是「把等待时间除以倍数」,不是「把解码加速」。本来就在等着,少睡一会儿而已。 3. 4K 片子开 2 倍速会卡,最可能是什么原因? —— 解码已经没有余量了。等待时间被压到零之后,倍速只能靠丢帧实现,看起来就是卡。- ☐ (本节完,下一节 2.6 ffprobe 获取视频信息)