V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
v2yllhwa
0.01D
V2EX  ›  C++

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

  •  
  •   v2yllhwa ·
    yllhwa · Jan 30, 2021 · 3331 views
    This topic created in 1925 days ago, the information mentioned may be changed or developed.

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

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

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