V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
v2yllhwa
V2EX  ›  C++

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

  •  
  •   v2yllhwa ·
    yllhwa · 2021-01-30 13:50:45 +08:00 · 2714 次点击
    这是一个创建于 1404 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    "流"就像水龙头, 它是连续不断的流出数据, 而不是每次流正好一杯子(一行输出)的数据.
    Mohanson
        10
    Mohanson  
       2021-01-30 14:48:41 +08:00
    当然从我的猜测讲, ffmpeg 在打印 "Duration: xxxxxxx" 的时候用了两次系统调用, 第一次打印 Duration, 第二次才打印 xxxxxxxx, 能不能一次 read() 同时读到这部分数据全看运气.
    v2yllhwa
        11
    v2yllhwa  
    OP
       2021-01-30 14:54:30 +08:00
    @Mohanson 大概明白了,谢谢~ 决定改用 system 调用“start ffmpeg xxx 2>>test.txt”再从文件里面读数据了。但是多进程访问文件又是个坑 hhh
    yolee599
        12
    yolee599  
       2021-01-30 15:41:02 +08:00
    输出的数据不是一下子全部出来的,数据不符合要求不要丢,把它和新的数据拼接到一起。最好用环形缓冲区,一个进程负责执行转换程序并读取管道数据放到环形缓冲区里。另一个进程负责读取环形缓冲区数据,并实现字符串的拼接处理
    ipwx
        13
    ipwx  
       2021-01-30 17:41:16 +08:00
    其实,这个问题的本质,和所谓的 TCP 黏包是一回事。
    ipwx
        14
    ipwx  
       2021-01-30 17:42:45 +08:00
    @v2yllhwa 楼主你要不去查一下 TCP 黏包他们都是怎么处理的,你这边也就会怎么处理了。。。
    GuuJiang
        15
    GuuJiang  
       2021-01-30 17:56:28 +08:00
    @v2yllhwa 你以为你明白了,实际上并没有明白,如果你是等命令执行完才读的文件,那么做进度条就没有意义了,如果你是进程执行中去读的文件,那么读管道会面临的问题读文件一样会面临,甚至有可能更复杂
    GuuJiang
        16
    GuuJiang  
       2021-01-30 17:57:18 +08:00
    忘了说了,上面这条回复是针对你在 11 楼的补充
    no1xsyzy
        17
    no1xsyzy  
       2021-01-31 00:48:13 +08:00
    @Jirajine 关于你 /t/747735 #21 说的
    > 你读写文件、打 log 的时候怎么没遇到过“粘包”?
    如果 #6 的猜测正确的话,这不就来了么(
    v2yllhwa
        18
    v2yllhwa  
    OP
       2021-01-31 07:14:34 +08:00 via Android
    可能我还没有理解完全,但是实际操作起来的时候,发现总是能完整地读取到 ffmpeg 在标准错误流中最后一行(如果允许我用这个单位的话)的数据(也就是那一行'xxxxxx\r'的信息)。没有出现只读到一部分的情况,这是巧合吗?
    v2yllhwa
        19
    v2yllhwa  
    OP
       2021-01-31 07:27:57 +08:00 via Android
    貌似之前想错了,应该有某种特殊的保护模式可以保证每次读到的'包'是完整的(子进程一次写入的数据),但是就像
    @Mohanson #10 说的,大概打印 "Duration: xxxxxxx" 的时候用了两次系统调用, 也就是两个包,这两个包有可能读到第一个第二个还没进来
    whi147
        20
    whi147  
       2021-01-31 13:46:37 +08:00 via iPhone
    环形缓冲区方案会好很多,专门开一个线程 read 。然后 ui 线程每秒去读环形缓冲区数据就行了
    whi147
        21
    whi147  
       2021-01-31 13:47:58 +08:00 via iPhone
    还有你这个创建子进程方案还会遇到兼容性问题,最好用后缀是 ex 的函数
    ysc3839
        22
    ysc3839  
       2021-01-31 19:37:02 +08:00 via Android
    @whi147 CreateProcess 没有带 Ex 的版本,没有特殊需求的话用这个是没问题的。
    no1xsyzy
        23
    no1xsyzy  
       2021-01-31 21:23:56 +08:00
    @v2yllhwa 看了一下 man 7 pipe
    POSIX 规范中要求短于 PIPE_BUF 的 write 操作是原子的。(这个值要求至少 512,Linux 上是 4096 )
    v2yllhwa
        24
    v2yllhwa  
    OP
       2021-02-01 00:18:55 +08:00
    感谢诸位的帮助。因为是在 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();
    }
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5940 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 02:30 · PVG 10:30 · LAX 18:30 · JFK 21:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.