🎬 第十二章 各种开发语言调用FFmpeg命令行
📄 一句话概括

VS 里有三条路:system()、CreateProcess、ShellExecute。 要控制输出就只剩 CreateProcess 一条,而它要你自己拼引号。

📝 本节和前面几节的关系

上一节把三件事立住了,这一节落到 Windows 的具体 API 上。 重点只有两个:system() 多出来的那层 cmd,和 CreateProcess 的引号规则。

一、三条路,能做的事差得远

VS 里调外部程序,常见三种写法。别按「哪个简单」选,按「要不要拿输出」选。

写法 能拿输出吗 能拿退出码吗


system() 不能,直接打屏 能,但要解 wait status CreateProcess 能,自己接管道 能,GetExitCodeProcess ShellExecute 不能 不能(它只返回成败)

ShellExecute 最省事也最没用:它连「跑完了没有」都不告诉你, 只知道「启动成功了没有」。 system() 居中,能等、能拿码,但拿不到输出。 要日志、要进度、要判断具体错在哪,只有 CreateProcess。

二、system() 多出来的那层 cmd,代价在哪

system() 内部是 cmd.exe /c 你的命令行。 所以你写的那一行会先被 cmd 解析一遍,剩下的才交给 ffmpeg。 两遍解析,就多出两类问题。

多出来的问题 具体表现


多一个进程 任务管理器里能看到 cmd.exe cmd 自己的特殊字符 &、|、>、^ 被 cmd 吃掉 引号被剥一层 路径带空格时莫名报错 退出码是 cmd 的 ffmpeg 的码被 cmd 转手

最典型的现象是「同一行命令,命令行窗口里跑得通,VS 里跑不通」。 因为窗口里那层 cmd 是你自己开的,剥引号的规则你已经习惯了; 而 system() 里那层 cmd 是隐式的,多剥的那一层你看不见。

三、CreateProcess 的引号规则,六条就说完

这套规则是 C 运行库定的,所有 Windows 程序都照它解析:

规则 例子


有空格就整体加引号 "a b.mp4" 引号内的引号要转义 a\"b → a"b 引号前的反斜杠翻倍 "C:\my dir\" 非引号前的反斜杠不动 C:\dir\a.mp4 空参数写成两个引号 "" 没空格就别加引号 out.mp4

手写这六条不算难,难在【想不到要写】。 所以实践里的做法是:先写成 vector<string>,再交给一个「拼串函数」, 由它统一处理引号,业务代码里永远只碰数组。 上一节那段封装里的 list2cmdline 就是这个函数。

四、那个一闪而过的黑框

ffmpeg 是控制台程序。CreateProcess 起它的时候,系统会按默认给它配一个控制台窗口。 在 GUI 程序里,这个窗口会闪一下——用户看着像出了 bug。

解法是给 dwCreationFlags 加上 CREATE_NO_WINDOW(本机实测值 134217728)。 注意它只影响「有没有窗口」,不影响输出: 加了之后 stdout 仍然要你自己接管道,否则 ffmpeg 写出去的东西就没人收了。

另一种写法是 DETACHED_PROCESS(本机实测值 8),连控制台都不分配。 两者在「只要不弹窗」这个需求上效果一样,区别在子进程还能不能开控制台—— 日常用 CREATE_NO_WINDOW 就够了。

五、字符集:只用 W 版,别碰 A 版

Windows 的 API 大多有 A/W 两个版本:A 收 ANSI 字符串,W 收宽字符。 CreateProcessA 在中文路径下会按当前代码页转换,转不了就是乱码或失败。

VS 新建项目默认是 Unicode 字符集,CreateProcess 宏会自动展开成 CreateProcessW, 所以新代码不用操心。 真正会踩的是从别处拷来的老代码:作者机器上路径全英文,跑得好好的; 到你这里路径带一个中文字,报的就是「系统找不到指定的文件」—— 而文件明明在那儿。

判断方法很简单:看代码里写的是 CreateProcessA 还是 CreateProcessW。 只要是 A,先改成 W 再排查别的。

六、VS 项目里那三处设置

如果不止调命令行,还要链接 FFmpeg 的库,项目属性里要填三处:

位置 填什么


C/C++ → 附加包含目录 ffmpeg\include 链接器 → 附加库目录 ffmpeg\lib 链接器 → 附加依赖项 avformat.lib 等四个

只调命令行的话,这三处一处都不用填——因为命令行方式不链接任何 FFmpeg 库, 你的程序只是启动了一个 exe。 这也是「调命令行」最实在的好处:换个 ffmpeg 版本,你的程序一行都不用重编。

七、动手 Demo:拼引号的四条规则,逐个看

下面把四组真实会遇到的参数,交给「按 MSVC 规则拼串」的函数。 第二组和第三组是重点:反斜杠什么时候翻倍、什么时候不翻。

import subprocess

cases = [
    ["ffmpeg", "-i", "out.mp4"],
    ["ffmpeg", "-i", "C:\\my dir\\a.mp4"],
    ["ffmpeg", "-i", "C:\\my dir\\"],
    ["ffmpeg", "-vf", "drawtext=text='hi there'"],
]
for c in cases:
    print(subprocess.list2cmdline(c))

print("CREATE_NO_WINDOW =", subprocess.CREATE_NO_WINDOW)

实跑输出:

--------------------------------------------------------------------
12.1.2 Visual Studio 调用 FFmpeg 命令行
--------------------------------------------------------------------
**① `system()` 会把命令交给 cmd.exe 再解析一遍**

  走的是哪条路            中间多出什么              引号被谁处理
  --------------------------------------------------------------------------------
  system("ffmpeg -i …")   cmd.exe /c 一个进程       cmd 处理一次,程序再处理一次
  CreateProcess(…)        没有中间层                只有你的拼串规则

**多这一层,后果是「同一行命令,命令行窗口里能跑,VS 里跑不了」。**
**因为 cmd 先按它自己的规矩剥一遍引号,剩下的才交给 ffmpeg。**
**路径里有空格、有中文、有 `&` 或 `^`,都会在这层出问题。**

**② 拼引号的规则不是「有空格就加引号」这么简单**

**Windows 的 C 运行库在解析命令行时有一套固定规则,Python 的 `list2cmdline` 就是它的实现。**
**拿四种真实会遇到的参数试一遍:**

  参数原样                      list2cmdline 拼出来的一整条
  ----------------------------------------------------------------------------------
  C:\my dir\a.mp4               ffmpeg -i "C:\my dir\a.mp4"
  C:\my dir\                    ffmpeg -i "C:\my dir\\"
  drawtext=text='hi there'      ffmpeg -vf "drawtext=text='hi there'"
  a[0]scale=w=2,h=2[b];[b]null  ffmpeg -filter_complex a[0]scale=w=2,h=2[b];[b]null

**第二行最值得看:`C:\my dir\` 拼出来是 `"C:\my dir\\"`——**
**结尾那个反斜杠被【翻倍】了,因为它的下一个字符正好是收尾的双引号,**
**不翻倍就变成「转义引号」,整个参数会连着后面几个一起吞掉。**
**这是手写拼串最常翻车的一处,也是「为什么明明显式加了引号还是不对」的答案。**

**第三行说明另一件事:引号包住的是【整串】,里面那个单引号原样留着。**
**而第四行一个引号都没有——`[`、`]`、`;`、`,` 在 Windows 命令行里不是特殊字符,**
**真正特殊的是 `&`、`|`、`>`、`^`(那是 cmd 管的事,不是 CreateProcess 管的)。**
**所以「滤镜串要不要加引号」,答案跟你想的相反:**
**不是因为有逗号才加,而是因为里面有没有空格。**

**③ 那个一闪而过的黑框,就是一个常量**

  常量                      本机的值        作用
  ------------------------------------------------------------------------
  CREATE_NO_WINDOW          134217728       不弹控制台窗口
  DETACHED_PROCESS          8               连控制台都不给它

**VS 调试时那个黑框,是 ffmpeg 作为控制台程序被启动时系统自动配的窗口。**
**给 `CreateProcess` 的 `dwCreationFlags` 加上 `CREATE_NO_WINDOW`,它就不出来了。**
**注意这个标志只影响窗口,不影响输出——加了之后 stdout 仍然要你自己接管道,否则直接丢掉。**

**④ 字符集只有一个正确答案**

  用的哪个函数          中文路径会怎样                结论
  ------------------------------------------------------------------------------
  CreateProcessA        按 ANSI 代码页转,路径乱码    不要用
  CreateProcessW        原样传宽字符                  一律用它
  system()              看当前代码页,多半出错        同上,能用不用

**VS 新建项目默认就是 Unicode 字符集,所以只要你调的是 `CreateProcessW`,这一条不用操心。**
**真正会踩的是从别人那里拷来的老代码——里面写的是 `CreateProcessA`,**
**在他机器上路径全英文,跑得好好的;到你这里路径一带中文,就是「文件不存在」。**

前两行没有空格,一个引号都不加;第三行有空格,整段被包起来。 第四行最要紧:C:\my dir\ 拼出来是 "C:\my dir\\"—— 结尾那个反斜杠翻了一倍,因为下一个字符是收尾引号。

30 秒实验

  1. 把第三组改成 C:\my dir(不带结尾反斜杠):看还翻倍吗。
  2. 在参数里塞一个双引号:看它转义成 \" 之后 ffmpeg 收到的是什么。
  3. 给 CreateProcess 的 flags 换成 DETACHED_PROCESS:对比窗口行为。

常见误区

  1. 用 system() 又想要日志。它的输出直接进终端,程序里拿不到。
  2. 自己拼引号时只处理空格。结尾反斜杠、内嵌引号这两条最常漏。
  3. 以为黑框是 ffmpeg 弹的。是系统给控制台程序配的,加个标志就没有了。
自测三问(点开对答案) 1. 为什么 `system()` 里的命令在窗口里能跑、在程序里不能跑? —— 因为隐式多了一层 cmd,多剥一次引号。 2. `"C:\my dir\\"` 里为什么是两个反斜杠? —— 结尾反斜杠紧挨收尾引号,必须翻倍,否则会转义掉那个引号。 3. 只调命令行需要链接 FFmpeg 的库吗? —— 不需要。你只是启动一个 exe,不链接任何 FFmpeg 库。

相关笔记