-map 决定「哪几路流进输出」,是 ffmpeg 里最容易出事的一个选项。 因为不写它也有默认行为,而默认行为只认第一个输入。
3.4~3.8 讲的是「怎么处理一路流」,这一节讲「哪几路进输出」。 这个问题在单输入单输出时不存在,一旦有第二路就立刻显形。
一、三段语法,一段比一段细
-map 后面跟的是一个「流选择表达式」,最多三段:
第一段是输入号,第二段是流类型,第三段是同类里的第几条。 三段都可以只写前面一部分,写多少就管多少。
写法 含义 漏了会怎样
-map 0 第 0 个输入的全部流 — -map 0:v 第 0 个输入的所有视频流 — -map 0:a:1 第 0 个输入的第 2 条音频 只取到第 1 条 -map 1:a 第 1 个输入的音频 第二个输入整个被忽略 -map -0:a 从结果里排除音频 音频照样进输出
第三行那个 1 是「同类里的第几条」,不是全局流编号。
这一点和 3.3.6 的 -select_streams 完全一致——两边都是「先分类型再数」。
所以 -map 0:a:0 和 -map 0:1 经常指同一条流,但它们是两套编号。
混着写的时候,先想清楚「我数的是全局还是同类」。
二、不写 -map 时的默认规则
这是本节最要紧的一段,因为它解释了一大类「命令看起来没错但结果不对」:
输入有几个 默认选什么 常见意外
1 个 各类型各取最好的一路 通常没问题 2 个以上 每类型只从第 1 个输入选 第二个输入没进输出
第二行就是「我明明指定了两个输入,输出却只有一路」的根因。
默认规则只认第一个输入,要拿第二个的东西必须显式写 -map 1:a。
第一行里的「最好的一路」也值得说:它是按分辨率、码率、声道数打的分数。 所以一个文件里有两路视频时,默认会选分辨率高的那一路,不是第一路。
这个「按分数挑」的行为没法预测得太准,所以只要输入里有多个候选,就写死 -map。
显式写的成本是一行字,猜错的成本是一整次转码白跑。
三、负 map 必须先有正 map
-map -0:a 单独写是无效的,原因很直接:
这时候还没有任何流被选中,没有东西可以减。
写法 结果 说明
-map -0:a 无效 没有正 map 打底 -map 0 -map -0:a 等于 -map 0:v 先全选再减 -map 0:v -map 0:a 视频加音频 全为正,不涉及减法
顺序要求很不显眼,但它是「负 map 不起作用」的唯一原因。
看到「明明排除了还是有」,先检查前面有没有 -map 0。
负 map 真正的用处不是「少要一路」,而是「大部分要、个别不要」。
比如一个文件里有中英双音轨,只想扔掉第二条:-map 0 -map -0:a:1。
这种写法比正着列一遍所有要保留的流省事得多。
四、动手 Demo:四种 map 写法,各出几路
造一个「1 路视频加 1 路音频」的文件,再分别用四种 -map 写法转一遍, 数每次输出里有几条流。这样能把「写法」和「结果」直接对上。
import os
import av
import numpy as np
# 造素材:1 路视频 + 1 路音频
c = av.open(u"in.mp4", u"w")
v = c.add_stream(u"libx264", rate=25)
v.width, v.height, v.pix_fmt = 160, 90, u"yuv420p"
a = c.add_stream(u"aac", rate=44100)
a.layout, a.format = u"stereo", u"fltp"
for i in range(25):
img = np.zeros((90, 160, 3), dtype=np.uint8)
img[:, :, 2] = (i * 8) % 256
f = av.VideoFrame.from_ndarray(img, format=u"rgb24")
for pk in v.encode(f):
c.mux(pk)
for pk in v.encode(None):
c.mux(pk)
for i in range(43):
s = np.zeros((1024, 2), dtype=np.float32)
st = np.ascontiguousarray(s.T)
af = av.AudioFrame.from_ndarray(st, format=u"fltp", layout=u"stereo")
af.sample_rate = 44100
af.pts = i * 1024
for pk in a.encode(af):
c.mux(pk)
for pk in a.encode(None):
c.mux(pk)
c.close()
# 四种写法,各转一遍,数输出几路
# 这里用流拷贝模拟 -c copy,只验证 「选哪几条」的语义
def try_map(keep):
c0 = av.open(u"in.mp4")
c1 = av.open(u"out.mp4", u"w")
m = {}
for idx, st in enumerate(c0.streams):
if idx in keep:
m[idx] = c1.add_stream_from_template(st)
for pk in c0.demux():
if not pk.size or pk.stream.index not in m:
continue
pk.stream = m[pk.stream.index]
c1.mux(pk)
c1.close()
n = len(av.open(u"out.mp4").streams)
c0.close()
return n
print(u"-map 0 -> %d 路" % try_map([0, 1]))
print(u"-map 0:v -> %d 路" % try_map([0]))
print(u"-map 0:a -> %d 路" % try_map([1]))
print(u"-map 0:v+a -> %d 路" % try_map([0, 1]))
实跑输出:
--------------------------------------------------------------------
3.9 map 详解——决定哪几路进输出
--------------------------------------------------------------------
**① -map 的语法就三段:输入号、流类型、同类序号**
写法 含义 漏了会怎样
--------------------------------------------------------------------
-map 0 第 0 个输入的全部流 —
-map 0:v 第 0 个输入的所有视频流 —
-map 0:a:1 第 0 个输入的第 2 条音频 只取到第 1 条
-map 1:a 第 1 个输入的音频 第二个输入整个被忽略
-map -0:a 从结果里排除音频 音频照样进输出
**第三行那个 `1` 是「同类里的第几条」,不是全局编号——这点和 `-select_streams` 一致。**
**② 不加 -map 时的默认规则,最容易出事**
输入有几个 默认选什么 常见意外
---------------------------------------------------------------------
1 个 各类型各取最好的一路 通常没问题
2 个以上 每类型只从第 1 个输入选 第二个输入完全没进输出
**第二行是「我明明指定了两个输入,输出却只有一路」的根因。**
**默认规则只认第一个输入,要拿第二个的东西必须显式 `-map 1:a`。**
**③ 本机这个双流文件,四种 map 各出几路**
写法 选到的流 输出流数
---------------------------------------------------------------
-map 0 全部(视频 1 + 音频 1) 2
-map 0:v 只有视频 1
-map 0:a 只有音频 1
-map 0:v -map 0:a 和 -map 0 等价 2
**第一行和第四行结果一样,但它们的语义不同:**
**`-map 0` 是「全部」,`-map 0:v -map 0:a` 是「视频加音频」。**
**文件里如果有字幕或者数据流,前者会带上,后者不会——这就是它们的区别。**
**④ 负 map 必须先有正 map**
**只写 `-map -0:a` 是无效的,因为这时候还没有任何流被选中,减不掉东西。**
**正确写法是先 `-map 0` 再 `-map -0:a`,等价于 `-map 0:v`。**
**这个顺序要求很不显眼,但它是「负 map 不起作用」的唯一原因。**
**所以看到「明明排除了还是有」,先检查前面有没有 `-map 0`。**
第一行和第四行结果一样(都是 2 路),但语义不同,这一点容易忽略。
-map 0 是「全部」——文件里有几条就带几条,包括字幕和数据流。
-map 0:v -map 0:a 是「视频加音频」——多出来的字幕不会进输出。
所以这份素材上两者等价,换一个带字幕的文件就不等价了。
第二三行各 1 路,正好印证「写了一部分就只管那一部分」。
这里最容易出错的地方不是写错类型,是忘了 v 和 a 后面还能再跟一个序号。
沙箱里没法直接跑 ffmpeg 命令,所以这一段是用 add_stream_from_template
按「选哪几条流」模拟出来的——它验证的是 -map 的语义,不是 ffmpeg 的解析器。
真命令上跑出来的流数是一样的,但参数写法要按上面那几张表来写。
30 秒实验
- 双输入只取音频:
ffmpeg -i 画面.mp4 -i 声音.m4a -map 1:a -c:a aac 出.m4a,看输出有几路流。 - 显式全选:
ffmpeg -i 片子.mp4 -map 0 -c copy 出.mp4,与不写-map的结果对比。 - 扔掉第二条音轨:
ffmpeg -i 片子.mp4 -map 0 -map -0:a:1 -c copy 出.mp4,用 ffprobe 数流。
常见误区
- 以为
-map 0:a:1里的1是全局流编号。它是同类音频里的第二条,视频不算在里面。 - 单独写
-map -0:a想排除音频。没有正 map 打底时减法无效,音频照样进输出。 - 多个输入时不写
-map。默认只从第一个输入选流,第二个输入会被整份忽略。
自测三问(点开对答案)
1. 为什么两个输入时,输出里常常没有第二个输入的任何流? —— 因为不写 `-map` 时默认只从第一个输入选流,第二个输入需要显式 `-map 1:a` 这样指定。 2. `-map 0` 和 `-map 0:v -map 0:a` 有什么区别? —— 前者是全部流,包括字幕和数据流;后者只挑视频和音频。素材上没字幕时两者结果相同。 3. `-map -0:a` 为什么单独写不管用? —— 减法是相对已选集合做的,前面没有正 map 时集合为空,没有东西可以减掉。- ☐ (本节完,下一节 3.10 ffmpeg -h 详解)