🎬 第三章 FFmpeg三大常用工具及应用选项详解
📄 一句话概括

协议管「怎么把字节送出去」,和「字节长什么样」是两件事。 这一节最危险的地方在于:输出容器「打开成功」根本不能证明协议能用。

📝 本节和前面几节的关系

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 秒实验

  1. 列协议:ffmpeg -protocols,这是唯一可靠的协议清单来源。
  2. 查某个协议在不在:ffmpeg -protocols | grep rtmp,有输出才算支持。
  3. 推一个真流:ffmpeg -re -i 片子.mp4 -f flv rtmp://127.0.0.1/live/x,没有服务必失败。

常见误区

  1. 用「能打开」判断协议可用。输出容器的 open 只做本地初始化,连接发生在写第一个包时。
  2. 把「协议」和「封装」当成一回事。-f flv 是封装、rtmp:// 是协议,两者可以自由组合。
  3. 以为推流失败一定是命令写错。很多时候是端口没开、服务没起,命令本身完全正确。
自测三问(点开对答案) 1. 为什么 `av.open(url, "w")` 对不存在的服务也返回成功? —— 因为输出容器的 open 只做本地初始化,真正的网络连接要等到写第一个包时才建立。 2. 要判断这份构建支不支持 rtmp,最可靠的办法是什么? —— `ffmpeg -protocols | grep rtmp`,读的是编译进去的协议清单,不涉及网络动作。 3. 「打开成功」这个结果能排除掉哪些错误? —— 只能排除参数格式明显写错这一类,连接、鉴权、对端是否存在都测不出来。

相关笔记