需要读取 ffmpeg 解码的输出来做一个进度条。
用 CreateProcess 来创建子进程,匿名管道重定向了 stdout 和 stderr,但是间歇性地会出现 strstr 找到了“Duration:”,但是此时 ReadBuff 里面就只有“Duration:”的情况。这种情况下再 ReadFile 也读不出来数据。
有什么头绪吗?
我的核心代码如下
bRet = CreateProcess(NULL, (LPSTR)"ffmpeg.exe -i test.mkv output.mp4 -y", NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi);
SetStdHandle(STD_OUTPUT_HANDLE, hTemp);
CloseHandle(hWrite);
while (ReadFile(hRead, ReadBuff, 1024, &ReadNum, NULL))
{
ReadBuff[ReadNum] = '\0';
if (strstr(ReadBuff, "Duration:"))
{
for(int i=0;i<=10;i++)
{
putchar(*(strstr(ReadBuff, "Duration:")+10+i));
}
putchar('\n');
}
}
1
lpts007 2021-01-30 13:57:06 +08:00
不懂,先问一下,if 没有 else 是认真的吗
|
2
lpts007 2021-01-30 13:59:05 +08:00
是不是 Duration 被分到前边,剩下内容被读走进入并不存在的 else 分支 导致的?
|
3
v2yllhwa OP @lpts007 ReadFile 是在 while 上面做的,相当于读一次处理一次。
我的缓存已经开得很大了,应该不是被刚好切开的问题。 |
4
linux40 2021-01-30 14:13:53 +08:00
Duration 后面的内容应该是随时间变化的,或许不是单纯的字符,你最好先确认一下。
|
5
alazysun 2021-01-30 14:16:26 +08:00
检查下 strlen(ReadBuff)和 ReadNum 长度区别?
|
6
Mohanson 2021-01-30 14:26:46 +08:00 via Android
Duration 并不一定会和它后面的数据被你一次读到 buf 里,甚至第一次 read 你只能读到 Durat, 第二次才会读到 ion
要明白流这个概念(虽迟但到)。 |
8
v2yllhwa OP @Mohanson 也就是说读的时候子进程可能还没有写完吗?但是我在后面加入 cout << ReadBuff << endl;后发现每次出现异常 ReadBuff 里面都是“ Duration:”。
|
9
Mohanson 2021-01-30 14:44:02 +08:00
@v2yllhwa 原因不是写没写完的问题, 而是为什么你会认为你 read() 一次正好能读取出 "Duration: xxxxxxx" 这一行数据? 为什么不会一次 read() 读出两行 "Duration: xxxxxxx", 为什么不会一次 read() 读出 "Durat"? 只是因为 xxxxxxx 后面有换行符吗?
"流"就像水龙头, 它是连续不断的流出数据, 而不是每次流正好一杯子(一行输出)的数据. |
10
Mohanson 2021-01-30 14:48:41 +08:00
当然从我的猜测讲, ffmpeg 在打印 "Duration: xxxxxxx" 的时候用了两次系统调用, 第一次打印 Duration, 第二次才打印 xxxxxxxx, 能不能一次 read() 同时读到这部分数据全看运气.
|
11
v2yllhwa OP @Mohanson 大概明白了,谢谢~ 决定改用 system 调用“start ffmpeg xxx 2>>test.txt”再从文件里面读数据了。但是多进程访问文件又是个坑 hhh
|
12
yolee599 2021-01-30 15:41:02 +08:00
输出的数据不是一下子全部出来的,数据不符合要求不要丢,把它和新的数据拼接到一起。最好用环形缓冲区,一个进程负责执行转换程序并读取管道数据放到环形缓冲区里。另一个进程负责读取环形缓冲区数据,并实现字符串的拼接处理
|
13
ipwx 2021-01-30 17:41:16 +08:00
其实,这个问题的本质,和所谓的 TCP 黏包是一回事。
|
15
GuuJiang 2021-01-30 17:56:28 +08:00
@v2yllhwa 你以为你明白了,实际上并没有明白,如果你是等命令执行完才读的文件,那么做进度条就没有意义了,如果你是进程执行中去读的文件,那么读管道会面临的问题读文件一样会面临,甚至有可能更复杂
|
16
GuuJiang 2021-01-30 17:57:18 +08:00
忘了说了,上面这条回复是针对你在 11 楼的补充
|
17
no1xsyzy 2021-01-31 00:48:13 +08:00
|
18
v2yllhwa OP 可能我还没有理解完全,但是实际操作起来的时候,发现总是能完整地读取到 ffmpeg 在标准错误流中最后一行(如果允许我用这个单位的话)的数据(也就是那一行'xxxxxx\r'的信息)。没有出现只读到一部分的情况,这是巧合吗?
|
19
v2yllhwa OP 貌似之前想错了,应该有某种特殊的保护模式可以保证每次读到的'包'是完整的(子进程一次写入的数据),但是就像
@Mohanson #10 说的,大概打印 "Duration: xxxxxxx" 的时候用了两次系统调用, 也就是两个包,这两个包有可能读到第一个第二个还没进来 |
20
whi147 2021-01-31 13:46:37 +08:00 via iPhone
环形缓冲区方案会好很多,专门开一个线程 read 。然后 ui 线程每秒去读环形缓冲区数据就行了
|
21
whi147 2021-01-31 13:47:58 +08:00 via iPhone
还有你这个创建子进程方案还会遇到兼容性问题,最好用后缀是 ex 的函数
|
22
ysc3839 2021-01-31 19:37:02 +08:00 via Android
@whi147 CreateProcess 没有带 Ex 的版本,没有特殊需求的话用这个是没问题的。
|
23
no1xsyzy 2021-01-31 21:23:56 +08:00
@v2yllhwa 看了一下 man 7 pipe
POSIX 规范中要求短于 PIPE_BUF 的 write 操作是原子的。(这个值要求至少 512,Linux 上是 4096 ) |
24
v2yllhwa OP 感谢诸位的帮助。因为是在 qt 里面写,本来想先把核心代码搞通顺再封装进去来着,今天晚上晕头转向用 QProcess 来实现居然完美解决了?!有时间我再深入研究下各位说的问题。
现在的代码~ (比起之前 CreateProcess 既方便还更兼容) ```c++ process.start(ffmpegPath,params); waitResult = process.waitForStarted(); process.setReadChannel(QProcess::StandardError); ...... bufferLength=process.readLine(buffer,1024); if(output.indexOf("Duration:")!=-1) { videoSeconds = output.mid(12,2).toInt()*60*60+output.mid(15,2).toInt()*60+output.mid(18,2).toInt(); } ``` |