协议管「怎么把字节送出去」,和「字节长什么样」是两件事。 这一节最危险的地方在于:输出容器「打开成功」根本不能证明协议能用。
3.11.6 讲完了解码器,这一节讲最外层——字节怎么出机器。 这一层在 30 课推流时踩过坑,这里把那个坑讲透。
一、三层是独立的,可以自由组合
一个输出地址里其实叠了三层东西,各管各的:
层 管什么 例子
协议层 怎么送 file/http/rtmp/rtsp/srt 封装层 送的内容怎么排 flv/mpegts/mp4 编码层 画面怎么压 h264/aac
这三层独立到什么程度:-f flv rtmp://… 里 flv 是封装、rtmp 是协议。
换成 -f mpegts udp://… 就是另外两种组合,编码层一个字都不用改。
所以「换个协议」不等于「换个封装」,两者可以自由组合——只要对端认。 反过来说,对端不认的时候要分清是协议不认还是封装不认。
二、本机四个地址,全部「打开成功」
这一段是本节的地雷区,先看现象:
协议 结果 说明
RTMP 打开成功 不等于能工作 UDP 打开成功 不等于能工作 TCP 打开成功 不等于能工作 HTTP 打开成功 不等于能工作
这四个地址指向的都是不存在的服务,却全部返回「打开成功」。 看起来皆大欢喜,实际上一个字节都没发出去。
原因在于输出容器的 open 只做了本地初始化,真正的连接发生在写第一个包的时候。 这个「延迟连接」的设计本身没错,错在把它当成「协议可用」的判据。
三、能打开、能连接、能工作,是三件事
把检查深度拆成三级,就明白为什么上面那张表不可信:
检查深度 做到哪一步 能发现什么
open 成功 本地初始化完 什么都发现不了 写一个包 真的发一次 连接失败、协议不支持 对端收到 服务端确认 这才叫能用
第一级看着最省事,信息量却是零——这就是「试打开」这种做法的根本问题。 它最多能排除「参数格式明显写错」这一类,别的都测不出来。
判断协议支不支持,唯一可靠的办法是 ffmpeg -protocols | grep 名字。
这一步读的是编译进去的协议清单,不涉及任何网络动作,所以准。
四、Python 这一层没有协议清单接口
这是一个具体的能力边界,值得写清楚:
想查什么 靠什么 可靠吗
协议清单 ffmpeg -protocols 准 某个协议在不在 试打开 不可靠 协议参数 ffmpeg -h protocol=名字 准
av.protocols_available 这个东西不存在,libavformat 也没把它暴露给 Python。
所以本节的能力表只能靠「逐个试」,而试本身有上面那个陷阱。
这也是为什么这一节的自测题全是「判断对错」而不是「填数字」。 数字在这台机器上根本拿不到,硬编一个就是编造。
五、动手 Demo:四个地址全「打开成功」的真相
分别用 rtmp、udp、tcp、http 四种协议去打开一个不存在的输出地址。 这四个地址都没有对应的服务在跑,看它们会不会报错。 结果是四个全部成功——这一段的意义就在于把「为什么」说清楚。
import av
URL = [u"rtmp://127.0.0.1:1935/live/x",
u"udp://127.0.0.1:9999",
u"tcp://127.0.0.1:9998",
u"http://127.0.0.1:9997/x"]
FMT = [u"flv", u"mpegts", u"mpegts", u"mp4"]
for url, fmt in zip(URL, FMT):
try:
c = av.open(url, u"w", format=fmt)
c.close()
print(u"打开成功 %s" % url.split(u":")[0])
except Exception as e:
print(u"报错 %s %s" % (url.split(u":")[0], e))
print(u"protocols_available 存在吗:%s"
% hasattr(av, u"protocols_available"))
实跑输出:
--------------------------------------------------------------------
3.11.7 -protocols——协议这一层最容易被误判
--------------------------------------------------------------------
**① 协议是「怎么把字节送出去」,和「字节长什么样」是两件事**
层 管什么 例子
-------------------------------------------------------------
协议层 怎么送 file/http/rtmp/rtsp/srt
封装层 送的内容怎么排 flv/mpegts/mp4
编码层 画面怎么压 h264/aac
**这三层是独立的:`-f flv rtmp://…` 里,flv 是封装、rtmp 是协议。**
**所以「换个协议」不等于「换个封装」,两者可以自由组合——只要对方认。**
**② 本机这四个地址,能不能打开**
协议 结果 说明
------------------------------------------
RTMP 打开成功 (不等于能工作)
UDP 打开成功 (不等于能工作)
TCP 打开成功 (不等于能工作)
HTTP 打开成功 (不等于能工作)
**这一列全是「打开成功」,看起来皆大欢喜——但这恰恰是本节最危险的一处。**
**下面第四段解释为什么。**
**③ PyAV 没有「列出所有协议」的接口**
**`av.protocols_available` 这个东西不存在,libavformat 也没把它暴露给 Python。**
**所以这一节的能力表只能靠「逐个试」,而试本身有陷阱。**
想查什么 靠什么 可靠吗
-----------------------------------------------------------------
协议清单 ffmpeg -protocols 准,但要真 exe
某个协议在不在 试打开 不可靠,见下
协议参数 ffmpeg -h protocol=名字 准,但要真 exe
**④ 「能打开」为什么不能当作「能用」**
**第一段那四个地址全都「打开成功」,可它们指向的是不存在的服务。**
**原因在于输出容器的 open 只做了本地初始化,真正的连接发生在写第一个包的时候。**
**这个坑在 30 课里踩过一次:`av.open("rtmp://…", "w")` 立刻返回成功,**
**一个字节都没发出去。真正推流时才崩,而且崩的原因和「协议不存在」长得完全不一样。**
检查深度 做到哪一步 能发现什么
-------------------------------------------------------
open 成功 本地初始化完 什么都发现不了
写一个包 真的发一次 连接失败、协议不支持
对端收到 服务端确认 这才叫能用
**所以判断「协议支不支持」,唯一可靠的办法是 `ffmpeg -protocols | grep 名字`。**
**试打开这种做法,最多只能排除「明显报错」的那一类。**
四行全是「打开成功」,而 127.0.0.1 上那几个端口根本没有服务在听。 如果把这一段当成「协议可用」的证据,就会写出注定失败的推流脚本。
注意代码里那个 format=fmt 不能省:不写它,av.open 会报找不到输出格式。
这个报错反而比「打开成功」有用——它至少说明这一步确实在问封装的事。
最后一行打的是 False:Python 这一层确实没有协议清单接口。
所以在沙箱里没法回答「这份构建支持哪些协议」,只能回答「哪些协议打不开时不会立刻报错」。
这两句话差别很大,文案不能混着写。
30 秒实验
- 列协议:
ffmpeg -protocols,这是唯一可靠的协议清单来源。 - 查某个协议在不在:
ffmpeg -protocols | grep rtmp,有输出才算支持。 - 推一个真流:
ffmpeg -re -i 片子.mp4 -f flv rtmp://127.0.0.1/live/x,没有服务必失败。
常见误区
- 用「能打开」判断协议可用。输出容器的 open 只做本地初始化,连接发生在写第一个包时。
- 把「协议」和「封装」当成一回事。
-f flv是封装、rtmp://是协议,两者可以自由组合。 - 以为推流失败一定是命令写错。很多时候是端口没开、服务没起,命令本身完全正确。
自测三问(点开对答案)
1. 为什么 `av.open(url, "w")` 对不存在的服务也返回成功? —— 因为输出容器的 open 只做本地初始化,真正的网络连接要等到写第一个包时才建立。 2. 要判断这份构建支不支持 rtmp,最可靠的办法是什么? —— `ffmpeg -protocols | grep rtmp`,读的是编译进去的协议清单,不涉及网络动作。 3. 「打开成功」这个结果能排除掉哪些错误? —— 只能排除参数格式明显写错这一类,连接、鉴权、对端是否存在都测不出来。- ☐ (本节完,下一节 3.11.8 -hwaccels)