diff --git a/vfxpipe/vfxpipe.cpp b/vfxpipe/vfxpipe.cpp index 75f31f5..6746295 100644 --- a/vfxpipe/vfxpipe.cpp +++ b/vfxpipe/vfxpipe.cpp @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "vfxpipe.h" +#include #include // for dladdr, Dl_info #include // for path #include // for function @@ -10,18 +11,19 @@ #include // for signal, SIGCHLD, SIG_IGN #include // for exit, realpath #include // for allocator, operator+, char_traits, string +#include #include // for close, dup2, pipe, write, execv, fork, read, STDIN_FILENO, STDOUT_FILENO namespace VfxPipe { -int spawnProcess(int* pipeRead, int* pipeWrite, const std::string& url, ErrorHandler errorHandler) +std::tuple spawnProcess(int* pipeRead, int* pipeWrite, const std::string& url, ErrorHandler errorHandler) { int fdsToChild[2]; int fdsFromChild[2]; if (pipe(fdsToChild) == -1 || pipe(fdsFromChild) == -1) { errorHandler(std::string("vfxpipe pipe failed: ") + strerror(errno)); - return -1; + return { -1, 0 }; } // Ignore child exit so we don't have to waitpid, and to avoid zombie processes @@ -30,7 +32,7 @@ int spawnProcess(int* pipeRead, int* pipeWrite, const std::string& url, ErrorHan int pid = fork(); if (pid == -1) { errorHandler(std::string("vfxpipe fork failed: ") + strerror(errno)); - return -1; + return { -1, 0 }; } // In the child if (pid == 0) { @@ -74,7 +76,13 @@ int spawnProcess(int* pipeRead, int* pipeWrite, const std::string& url, ErrorHan close(fdsFromChild[1]); close(fdsToChild[0]); - return pid; + + uint32_t sinkCount = 0; + if (!dataIO(*pipeRead, reinterpret_cast(&sinkCount), sizeof(sinkCount), read, errorHandler)) { + return { -1, 0 }; + } + + return { pid, sinkCount }; } FrameServer::FrameServer(const std::string& url) @@ -93,14 +101,26 @@ FrameServer::~FrameServer() close(pipeWrite); } -bool FrameServer::renderFrame(double time, const std::vector& sourceFrames, RenderedVideoFrame& outputFrame, ErrorHandler errorHandler) +bool FrameServer::initialize(ErrorHandler errorHandler) { if (!pid) { - pid = spawnProcess(&pipeRead, &pipeWrite, url, errorHandler); + std::tie(pid, sinkCount) = spawnProcess(&pipeRead, &pipeWrite, url, errorHandler); + if (pid == -1) { + errorHandler("vfxpipe failed to spawn process"); + return false; + } + return true; } - if (pid == -1) { - errorHandler("vfxpipe failed to spawn process"); + return pid != -1; +} + +bool FrameServer::renderFrame(double time, const std::vector& sourceFrames, RenderedVideoFrame& outputFrame, ErrorHandler errorHandler) +{ + if (pid == -1) return false; + if (!pid) { + if (!initialize(errorHandler)) + return false; } auto ioErrorHandler = [this, errorHandler](std::string msg) { @@ -121,11 +141,15 @@ bool FrameServer::renderFrame(double time, const std::vector& return false; } - uint32_t frameCount = sourceFrames.size(); + uint32_t frameCount = std::min(sinkCount, static_cast(sourceFrames.size())); if (!dataIO(pipeWrite, reinterpret_cast(&frameCount), sizeof(frameCount), write, ioErrorHandler)) { return false; } + int index = 0; for (const auto& sourceFrame : sourceFrames) { + ++index; + if (index > frameCount) + break; if (!writeVideoFrame(pipeWrite, sourceFrame, ioErrorHandler)) { return false; } diff --git a/vfxpipe/vfxpipe.h b/vfxpipe/vfxpipe.h index 8855a5c..372e0fd 100644 --- a/vfxpipe/vfxpipe.h +++ b/vfxpipe/vfxpipe.h @@ -98,14 +98,17 @@ class FrameServer { public: FrameServer(const std::string& url); ~FrameServer(); + bool initialize(ErrorHandler errorHandler); bool renderFrame(double time, const std::vector& sourceFrames, RenderedVideoFrame& outputFrame, ErrorHandler errorHandler); std::string& getUrl() { return url; } + uint32_t getSinkCount() { return sinkCount; } private: std::string url; int pid; int pipeWrite; int pipeRead; + uint32_t sinkCount; }; template diff --git a/viewer/viewer.cpp b/viewer/viewer.cpp index 7c88cde..e7979a0 100644 --- a/viewer/viewer.cpp +++ b/viewer/viewer.cpp @@ -34,6 +34,11 @@ #include // for string #include // for vector +void errorHandler(std::string msg) +{ + qCritical() << msg; +}; + Viewer::Viewer() : QMainWindow(0) , sizeLabel(0) @@ -111,10 +116,6 @@ void Viewer::renderContent() } } - auto errorHandler = [](std::string msg) { - qCritical() << msg; - }; - if (renderImage.size() != scrollArea->widget()->size()) renderImage = QImage(scrollArea->widget()->size(), QImage::Format_RGBA8888); VfxPipe::VideoFrame outputImage(VfxPipe::VideoFrameFormat::PixelFormat::RGBA32, renderImage.width(), renderImage.height(), reinterpret_cast(renderImage.bits())); @@ -221,15 +222,15 @@ void Viewer::createContent(const QString& fileName) logTextEdit->clear(); - setContentUIEnabled(true); // XXX need UI enabled all the time now? + if (frameServer->initialize(errorHandler)) + setContentUIEnabled(true); - setupImages(scrollArea->widget()->size()); + setupImages(frameServer->getSinkCount(), scrollArea->widget()->size()); renderContent(); } -void Viewer::setupImages(const QSize& size) +void Viewer::setupImages(uint32_t imageCount, const QSize& size) { - int imageCount = 0; // XXX need a count, make frameserver return count imagesTable->setRowCount(imageCount); for (qsizetype row = 0; row < imageCount; ++row) { imagesTable->insertRow(row); diff --git a/viewer/viewer.h b/viewer/viewer.h index 867a021..ce02e4a 100644 --- a/viewer/viewer.h +++ b/viewer/viewer.h @@ -40,7 +40,7 @@ private slots: void renderContent(); void setContentUIEnabled(bool enable); void handleResize(); - void setupImages(const QSize& size); + void setupImages(uint32_t imageCount, const QSize& size); double sliderTimeValue(int value); QLabel* sizeLabel; QDoubleSpinBox* timeSpinBox; diff --git a/webvfx/frameserver.cpp b/webvfx/frameserver.cpp index 22aabd1..9daeb6e 100644 --- a/webvfx/frameserver.cpp +++ b/webvfx/frameserver.cpp @@ -82,6 +82,17 @@ double parseDuration(QString durationValue) QEvent::Type FrameServer::renderEventType = static_cast(QEvent::registerEventType()); +void ioErrorHandler(std::string msg) +{ + // EOF + if (msg.empty()) { + QCoreApplication::exit(0); + } else { + qCritical() << "WebVfx frameserver: " << msg.c_str(); + QCoreApplication::exit(1); + } +}; + FrameServer::FrameServer(QUrl& qmlUrl, QObject* parent) : QObject(parent) , duration(0) @@ -110,9 +121,13 @@ void FrameServer::onContentLoadFinished(bool result) if (result) { auto size = content->getContentSize(); auto videoSinks = content->getVideoSinks(); - for (qsizetype i = 0; i < videoSinks.size(); ++i) { - frameSinks.append(FrameSink(videoSinks.at(i))); + for (const auto videoSink : videoSinks) { + frameSinks.append(FrameSink(videoSink)); } + // Write sink count + uint32_t sinkCount = videoSinks.size(); + if (!VfxPipe::dataIO(STDOUT_FILENO, reinterpret_cast(&sinkCount), sizeof(sinkCount), write, ioErrorHandler)) + return; QCoreApplication::postEvent(this, new QEvent(renderEventType)); } else { qCritical() << "QML content failed to load."; @@ -132,19 +147,8 @@ bool FrameServer::event(QEvent* event) void FrameServer::readFrames() { - auto f = __FUNCTION__; - auto ioErrorHandler = [f](std::string msg) { - // EOF - if (msg.empty()) { - QCoreApplication::exit(0); - } else { - qCritical() << f << ": WebVfx frameserver: " << msg.c_str(); - QCoreApplication::exit(1); - } - }; - double time; - if (!VfxPipe::dataIO(STDIN_FILENO, reinterpret_cast(&time), sizeof(time), read, ioErrorHandler)) + if (!VfxPipe::dataIO(STDIN_FILENO, reinterpret_cast(&time), sizeof(time), read, ioErrorHandler)) return; if (initialTime == -1) { @@ -162,6 +166,7 @@ void FrameServer::readFrames() uint32_t frameCount; if (!VfxPipe::dataIO(STDIN_FILENO, reinterpret_cast(&frameCount), sizeof(frameCount), read, ioErrorHandler)) return; + if (frameCount > frameSinks.size()) { qCritical() << "Frame count " << frameCount << " does not match video sink count " << frameSinks.size(); QCoreApplication::exit(1);