c++ 使用管道读取子进程的输出 不完整

2021-01-30 13:50:45 +08:00
 v2yllhwa

需要读取 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');
        }
    }

2695 次点击
所在节点    C++
24 条回复
lpts007
2021-01-30 13:57:06 +08:00
不懂,先问一下,if 没有 else 是认真的吗
lpts007
2021-01-30 13:59:05 +08:00
是不是 Duration 被分到前边,剩下内容被读走进入并不存在的 else 分支 导致的?
v2yllhwa
2021-01-30 14:03:02 +08:00
@lpts007 ReadFile 是在 while 上面做的,相当于读一次处理一次。
我的缓存已经开得很大了,应该不是被刚好切开的问题。
linux40
2021-01-30 14:13:53 +08:00
Duration 后面的内容应该是随时间变化的,或许不是单纯的字符,你最好先确认一下。
alazysun
2021-01-30 14:16:26 +08:00
检查下 strlen(ReadBuff)和 ReadNum 长度区别?
Mohanson
2021-01-30 14:26:46 +08:00
Duration 并不一定会和它后面的数据被你一次读到 buf 里,甚至第一次 read 你只能读到 Durat, 第二次才会读到 ion

要明白流这个概念(虽迟但到)。
v2yllhwa
2021-01-30 14:29:32 +08:00
@linux40 可以确定 Duration 只会出现在开头一次
v2yllhwa
2021-01-30 14:34:05 +08:00
@Mohanson 也就是说读的时候子进程可能还没有写完吗?但是我在后面加入 cout << ReadBuff << endl;后发现每次出现异常 ReadBuff 里面都是“ Duration:”。
Mohanson
2021-01-30 14:44:02 +08:00
@v2yllhwa 原因不是写没写完的问题, 而是为什么你会认为你 read() 一次正好能读取出 "Duration: xxxxxxx" 这一行数据? 为什么不会一次 read() 读出两行 "Duration: xxxxxxx", 为什么不会一次 read() 读出 "Durat"? 只是因为 xxxxxxx 后面有换行符吗?

"流"就像水龙头, 它是连续不断的流出数据, 而不是每次流正好一杯子(一行输出)的数据.
Mohanson
2021-01-30 14:48:41 +08:00
当然从我的猜测讲, ffmpeg 在打印 "Duration: xxxxxxx" 的时候用了两次系统调用, 第一次打印 Duration, 第二次才打印 xxxxxxxx, 能不能一次 read() 同时读到这部分数据全看运气.
v2yllhwa
2021-01-30 14:54:30 +08:00
@Mohanson 大概明白了,谢谢~ 决定改用 system 调用“start ffmpeg xxx 2>>test.txt”再从文件里面读数据了。但是多进程访问文件又是个坑 hhh
yolee599
2021-01-30 15:41:02 +08:00
输出的数据不是一下子全部出来的,数据不符合要求不要丢,把它和新的数据拼接到一起。最好用环形缓冲区,一个进程负责执行转换程序并读取管道数据放到环形缓冲区里。另一个进程负责读取环形缓冲区数据,并实现字符串的拼接处理
ipwx
2021-01-30 17:41:16 +08:00
其实,这个问题的本质,和所谓的 TCP 黏包是一回事。
ipwx
2021-01-30 17:42:45 +08:00
@v2yllhwa 楼主你要不去查一下 TCP 黏包他们都是怎么处理的,你这边也就会怎么处理了。。。
GuuJiang
2021-01-30 17:56:28 +08:00
@v2yllhwa 你以为你明白了,实际上并没有明白,如果你是等命令执行完才读的文件,那么做进度条就没有意义了,如果你是进程执行中去读的文件,那么读管道会面临的问题读文件一样会面临,甚至有可能更复杂
GuuJiang
2021-01-30 17:57:18 +08:00
忘了说了,上面这条回复是针对你在 11 楼的补充
no1xsyzy
2021-01-31 00:48:13 +08:00
@Jirajine 关于你 /t/747735 #21 说的
> 你读写文件、打 log 的时候怎么没遇到过“粘包”?
如果 #6 的猜测正确的话,这不就来了么(
v2yllhwa
2021-01-31 07:14:34 +08:00
可能我还没有理解完全,但是实际操作起来的时候,发现总是能完整地读取到 ffmpeg 在标准错误流中最后一行(如果允许我用这个单位的话)的数据(也就是那一行'xxxxxx\r'的信息)。没有出现只读到一部分的情况,这是巧合吗?
v2yllhwa
2021-01-31 07:27:57 +08:00
貌似之前想错了,应该有某种特殊的保护模式可以保证每次读到的'包'是完整的(子进程一次写入的数据),但是就像
@Mohanson #10 说的,大概打印 "Duration: xxxxxxx" 的时候用了两次系统调用, 也就是两个包,这两个包有可能读到第一个第二个还没进来
whi147
2021-01-31 13:46:37 +08:00
环形缓冲区方案会好很多,专门开一个线程 read 。然后 ui 线程每秒去读环形缓冲区数据就行了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/749817

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX