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 秒实验
- 把第三组改成
C:\my dir(不带结尾反斜杠):看还翻倍吗。 - 在参数里塞一个双引号:看它转义成
\"之后 ffmpeg 收到的是什么。 - 给
CreateProcess的 flags 换成DETACHED_PROCESS:对比窗口行为。
常见误区
- 用
system()又想要日志。它的输出直接进终端,程序里拿不到。 - 自己拼引号时只处理空格。结尾反斜杠、内嵌引号这两条最常漏。
- 以为黑框是 ffmpeg 弹的。是系统给控制台程序配的,加个标志就没有了。
自测三问(点开对答案)
1. 为什么 `system()` 里的命令在窗口里能跑、在程序里不能跑? —— 因为隐式多了一层 cmd,多剥一次引号。 2. `"C:\my dir\\"` 里为什么是两个反斜杠? —— 结尾反斜杠紧挨收尾引号,必须翻倍,否则会转义掉那个引号。 3. 只调命令行需要链接 FFmpeg 的库吗? —— 不需要。你只是启动一个 exe,不链接任何 FFmpeg 库。- ☐ 12.1.2 完——下一节 12.1.3 Qt 调用 FFmpeg 命令行