为何QProcess无法通过readyRead信号捕获Python子进程的stdout?
问题描述
执行以下C++代码调用Whisper工具:
QProcess p_Whisper; p_Whisper.setArguments( QStringList() << "--model" << "medium.en" << "--output_dir" << d_Hidden.absolutePath() << "--task" << "transcribe" << "--language" << "en" << "--condition_on_previous_text" << "False" << "--no_speech_threshold" << "0.5" << "--logprob_threshold" << "None" << "--threads" << "8" << "--fp16" << "False" << "--verbose" << "True" << f.fileName() ); p_Whisper.setProgram("/home/anon/.local/bin/whisper");
在Shell中执行对应命令时,能正常输出转录内容:
/home/anon/.local/bin/whisper --model tiny.en --output_dir /home/anon/Music/AF/.transcripts --task transcribe --language en --condition_on_previous_text False --no_speech_threshold 0.5 --logprob_threshold None --threads 8 "/home/anon/Music/AF/CORONAVIRUS PANDEMIC - New Cases in France, Chicago- Virus Spreading RAPIDLY _ America First Ep. 53..-OoFCDcxBbvQ.m4a" /home/anon/.local/lib/python3.10/site-packages/whisper/transcribe.py:78: UserWarning: FP16 is not supported on CPU; using FP32 instead warnings.warn("FP16 is not supported on CPU; using FP32 instead") [00:00.000 --> 00:02.000] For the king of the nation, [00:02.000 --> 00:04.000] for the king of the nation. [00:04.000 --> 00:06.000] Go be on the wheel! [00:34.000 --> 00:40.120] Not interested. I'm sorry. I'm sorry for the eventy, but I just can't do it. You're an e-girl. You know the world [00:40.240 --> 00:48.080] No e-girls who's got to clip don't eat girls never hashtag never e-girls not even once [00:48.080 --> 00:55.080] Yeah, I don't have to ever heard of it, but what is this that? [01:48.080 --> 02:03.080] Yeah, I've never heard of bigfoots, who's that?
但在Qt中通过以下代码捕获输出时,始终无法获取上述的转录内容:
connect( &p_Whisper, &QProcess::readyReadStandardError,[this, &p_Whisper](){ ct_Error( p_Whisper.readAllStandardError() ); }); connect( &p_Whisper, &QProcess::readyReadStandardOutput,[this, &p_Whisper](){ QString s = p_Whisper.readAllStandardOutput(); ct_Process(s); ui->textBrowser->append(s); });
用户尝试过channelReadyRead信号,但未触发:
connect( &p_Whisper, &QProcess::channelReadyRead,[this, &p_Whisper](int channel){ p_Whisper.setCurrentReadChannel(channel); QString s = p_Whisper.readAllStandardOutput(); ct_Process(s); ui->textBrowser->append(s); });
也尝试过设置通道模式为ForwardedChannels,此时输出会转发到运行程序的Shell,但仍无法通过信号捕获。
补充信息:Whisper是Python封装的C子进程集合;Shell中将stdout/stderr重定向到文件时,stdout能成功捕获转录内容,说明Whisper不会因终端环境改变行为。
用户提出三个问题:
- 为何QProcess的readyRead信号无法捕获该stdout?
- 子进程的输出是否在QProcess的不同通道中?
- 是否可通过QProcess捕获目标stdout?是否有环境变量可辅助实现?
问题解答
1. 为何QProcess的readyRead信号无法捕获该stdout?
核心原因是Python的缓冲机制:当Python程序的stdout不是直接连接到终端(比如被QProcess管道捕获)时,会启用块缓冲模式,而非终端环境下的行缓冲。这意味着Whisper的转录输出会被暂时缓存,直到缓冲区填满或进程结束才会一次性输出,导致QProcess的readyReadStandardOutput信号无法实时触发,甚至可能在进程结束前都收不到数据。
另外,Whisper的Python层调用的C子进程,其输出同样可能因为非终端环境触发块缓冲,进一步延迟输出。
2. 子进程的输出是否在QProcess的不同通道中?
不是。根据Shell重定向测试结果,转录内容明确输出到stdout。QProcess启动的whisper进程作为父进程,其所有子进程的stdout默认会继承父进程的stdout通道,也就是QProcess监听的stdout管道。问题出在缓冲机制而非通道分配。
3. 是否可通过QProcess捕获目标stdout?是否有环境变量可辅助实现?
可以捕获,有两种可行方案:
- 强制Python使用行缓冲:修改启动命令,让Python强制启用行缓冲。将
/home/anon/.local/bin/whisper替换为python3 -u /home/anon/.local/bin/whisper(-u参数禁用缓冲,强制stdout/stderr使用行缓冲)。修改后的QProcess代码:p_Whisper.setProgram("python3"); p_Whisper.setArguments( QStringList() << "-u" << "/home/anon/.local/bin/whisper" << "--model" << "medium.en" << "--output_dir" << d_Hidden.absolutePath() << "--task" << "transcribe" << "--language" << "en" << "--condition_on_previous_text" << "False" << "--no_speech_threshold" << "0.5" << "--logprob_threshold" << "None" << "--threads" << "8" << "--fp16" << "False" << "--verbose" << "True" << f.fileName() ); - 设置环境变量:通过
PYTHONUNBUFFERED环境变量强制Python无缓冲输出。在启动QProcess前添加:
这个变量的作用和QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("PYTHONUNBUFFERED", "1"); p_Whisper.setProcessEnvironment(env);-u参数一致,无需修改启动程序路径,直接作用于Whisper的Python运行环境。
两种方案都能让Whisper的转录内容实时输出到stdout,触发QProcess的readyReadStandardOutput信号。
内容的提问来源于stack exchange,提问作者Jannies - They do it for free

