2

在编写一个看似简单的 Qt 应用程序部分,该部分将运行一个子进程并从其标准输出中读取数据时,我偶然发现了一个让我非常困惑的问题。应用程序应该从子进程中读取数据块(原始视频帧)并在它们到达时对其进行处理:

  1. 启动一个 QProcess
  2. 收集数据直到有足够的一帧
  3. 处理帧
  4. 返回步骤 2

这个想法是使用信号和槽来实现处理循环——这在我下面提供的简单、精简的示例中可能看起来很愚蠢,但在原始应用程序的框架内似乎完全合理。所以我们开始:

app::app() {
  process.start("cat /dev/zero");
  buffer = new char[frameLength];
  connect(this, SIGNAL(wantNewFrame()), SLOT(readFrame()), Qt::QueuedConnection);
  connect(this, SIGNAL(frameReady()), SLOT(frameHandler()), Qt::QueuedConnection);
  emit wantNewFrame();
}

我从这里开始一个简单的过程 ( cat /dev/zero),这样我们就可以确信它不会耗尽数据。我还做了两个连接:一个在需要帧时开始读取,另一个在帧到达时调用数据处理函数。请注意,这个简单的示例在单个线程中运行,因此将连接设为排队类型以避免无限递归。该wantNewFrame()信号启动第一帧的采集;它在控件返回事件循环时得到处理。

bool app::readFrame() {
  qint64 bytesNeeded = frameLength;
  qint64 bytesRead = 0;
  char* ptr = buffer;
  while (bytesNeeded > 0) {
    process.waitForReadyRead();
    bytesRead = process.read(ptr, bytesNeeded);
    if (bytesRead == -1) {
      qDebug() << "process state" << process.state();
      qDebug() << "process error" << process.error();
      qDebug() << "QIODevice error" << process.errorString();
      QCoreApplication::quit();
      break;
    }
    ptr += bytesRead;
    bytesNeeded -= bytesRead;
  }
  if (bytesNeeded == 0) {
    emit frameReady();
    return true;
  } else
    return false;
}

读取帧:基本上,我只是在数据到达时将其填充到缓冲区中。最后的frameReady()信号表明帧已准备好,进而导致数据处理功能运行。

void app::frameHandler() {
  static qint64 frameno = 0;
  qDebug() << "frame" << frameno++;
  emit wantNewFrame();
}

一个简单的数据处理器:它只计算帧数。完成后,它发出wantNewFrame()重新开始读取周期。

就是这个。为了完整起见,我还将在此处发布头文件和 main()。

应用程序.h:

#include <QDebug>
#include <QCoreApplication>
#include <QProcess>

class app : public QObject
{
Q_OBJECT
public:
  app();
  ~app() { delete[] buffer; }

signals:
  void wantNewFrame();
  void frameReady();

public slots:
  bool readFrame();
  void frameHandler();

private:
  static const quint64 frameLength = 614400;
  QProcess process;
  char* buffer;
};

主.cpp:

#include "app.h"

int main(int argc, char** argv)
{
    QCoreApplication coreapp(argc, argv);
    app foo;
    return coreapp.exec();
}

现在是奇怪的部分。该程序可以很好地处理随机数量的帧(我见过从 15 到 1000 多个),但最终停止并抱怨 QProcess 崩溃了:

$ ./app
frame 1
...
frame 245 
frame 246 
frame 247 
process state 0 
process error 1 
QIODevice error "Process crashed" 

进程状态 0 表示“未运行”,进程错误 1 ​​表示“崩溃”。我对其进行了调查,发现子进程收到了一个 SIGPIPE——即父进程已经关闭了它的管道。但我完全不知道发生这种情况的地点和原因。还有人吗?

4

1 回答 1

0

代码看起来有点奇怪(不使用readyRead信号,而是依赖延迟的信号/插槽)。正如您在讨论中指出的那样,您已经在 qt-interest ML 上看到了我询问类似问题的线程。我刚刚意识到我QueuedConnection当时也使用过。我无法解释为什么它是错误的——在我看来,排队的信号“应该起作用”。一个盲点是invokeMethodQt 的实现所使用的 以某种方式与您的信号传递竞争,以便您在 Qt 有机会处理数据之前清空您的读取缓冲区。这意味着 Qt 最终将读取零字节并(正确地)将其解释为EOF,关闭管道。

我再也找不到引用的“Qt 任务 217111”了,但是在他们的 Jira 中有一些关于waitForReadyRead无法按用户期望工作的报告,例如QTBUG-9529

我会把它带到 Qt 的“兴趣”邮件列表中,并远离waitFor...方法系列。我同意他们的文档值得更新。

于 2013-04-08T09:59:16.560 回答