diff --git a/demo/examples/filter-banner.qml b/demo/examples/filter-banner.qml index 6f1995b..9baf635 100644 --- a/demo/examples/filter-banner.qml +++ b/demo/examples/filter-banner.qml @@ -2,9 +2,6 @@ import QtQuick import QtMultimedia Rectangle { - width: webvfx.videoSize.width - height: webvfx.videoSize.height - VideoOutput { id: video anchors.fill: parent diff --git a/demo/examples/filter-demo.qml b/demo/examples/filter-demo.qml index daa3942..d2a97a8 100644 --- a/demo/examples/filter-demo.qml +++ b/demo/examples/filter-demo.qml @@ -2,8 +2,6 @@ import QtQuick import QtMultimedia Rectangle { - width: webvfx.videoSize.width - height: webvfx.videoSize.height color: "lightgray" VideoOutput { diff --git a/demo/examples/filter-quad.qml b/demo/examples/filter-quad.qml new file mode 100644 index 0000000..cded9f1 --- /dev/null +++ b/demo/examples/filter-quad.qml @@ -0,0 +1,44 @@ +import QtQuick +import QtQuick.Layouts +import QtMultimedia + +GridLayout { + id: grid + columns: 2 + + VideoOutput { + id: tlVideo + Layout.column: 0 + Layout.row: 0 + Layout.fillWidth: true + Layout.fillHeight: true + } + VideoOutput { + id: trVideo + Layout.column: 1 + Layout.row: 0 + Layout.fillWidth: true + Layout.fillHeight: true + } + VideoOutput { + id: blVideo + Layout.column: 0 + Layout.row: 1 + Layout.fillWidth: true + Layout.fillHeight: true + } + VideoOutput { + id: brVideo + Layout.column: 1 + Layout.row: 1 + Layout.fillWidth: true + Layout.fillHeight: true + } + Component.onCompleted: { + var videoSource = webvfx.addVideoSource(); + webvfx.appendVideoSink(videoSource, tlVideo.videoSink); + webvfx.appendVideoSink(videoSource, trVideo.videoSink); + webvfx.appendVideoSink(videoSource, blVideo.videoSink); + webvfx.appendVideoSink(videoSource, brVideo.videoSink); + } +} \ No newline at end of file diff --git a/demo/examples/producer-demo.qml b/demo/examples/producer-demo.qml index eaa7896..719ed3f 100644 --- a/demo/examples/producer-demo.qml +++ b/demo/examples/producer-demo.qml @@ -3,8 +3,6 @@ import QtQuick.Shapes 1.0 Shape { id: shape - width: webvfx.videoSize.width - height: webvfx.videoSize.height anchors.centerIn: parent ShapePath { fillGradient: RadialGradient { diff --git a/demo/examples/transition-demo3d.qml b/demo/examples/transition-demo3d.qml index c272418..a048a3c 100644 --- a/demo/examples/transition-demo3d.qml +++ b/demo/examples/transition-demo3d.qml @@ -6,8 +6,6 @@ import stream.webvfx.WebVfx View3D { id: view - width: webvfx.videoSize.width - height: webvfx.videoSize.height environment: SceneEnvironment { clearColor: "skyblue" diff --git a/demo/examples/transition-shader-crosszoom.qml b/demo/examples/transition-shader-crosszoom.qml index fb2fc19..7863c8c 100644 --- a/demo/examples/transition-shader-crosszoom.qml +++ b/demo/examples/transition-shader-crosszoom.qml @@ -2,9 +2,6 @@ import QtQuick 2.0 import QtMultimedia Rectangle { - width: webvfx.videoSize.width - height: webvfx.videoSize.height - VideoOutput { id: sourceVideo anchors.fill: parent diff --git a/demo/examples/transition-shader-pagecurl.qml b/demo/examples/transition-shader-pagecurl.qml index 65c3da1..e133258 100644 --- a/demo/examples/transition-shader-pagecurl.qml +++ b/demo/examples/transition-shader-pagecurl.qml @@ -2,16 +2,13 @@ import QtQuick 2.0 import QtMultimedia Rectangle { - width: webvfx.videoSize.width - height: webvfx.videoSize.height - - VideoOutput { + VideoOutput { id: sourceVideo anchors.fill: parent visible: false layer.enabled: true - } - VideoOutput { + } + VideoOutput { id: targetVideo anchors.fill: parent visible: false diff --git a/demo/examples/transition-simple.qml b/demo/examples/transition-simple.qml index 59b9c60..3239cb5 100644 --- a/demo/examples/transition-simple.qml +++ b/demo/examples/transition-simple.qml @@ -2,8 +2,7 @@ import QtQuick import QtMultimedia Rectangle { - width: webvfx.videoSize.width - height: webvfx.videoSize.height + id: root color: "lightgray" VideoOutput { @@ -37,6 +36,15 @@ Rectangle { id: animationController animation: animation } + Connections { + target: root + function onWidthChanged(w) { + animationController.reload(); + } + function onHeightChanged(h) { + animationController.reload(); + } + } Component.onCompleted: { webvfx.appendVideoSink(webvfx.addVideoSource(), sourceVideo.videoSink); webvfx.appendVideoSink(webvfx.addVideoSource(), targetVideo.videoSink); @@ -46,8 +54,5 @@ Rectangle { function onRenderRequested(time) { animationController.progress = time; } - function onVideoSizeChanged(size) { - animationController.reload(); - } } } diff --git a/demo/mlt/mlt_filter_quad b/demo/mlt/mlt_filter_quad new file mode 100755 index 0000000..c95f1d4 --- /dev/null +++ b/demo/mlt/mlt_filter_quad @@ -0,0 +1,16 @@ +#!/bin/bash + +BASEDIR=$(dirname "${BASH_SOURCE[0]}") +export DISPLAY=${DISPLAY:-:0} +case ${PLUGIN:-frei0r} in +frei0r) + FILTER=("frei0r.webvfx_filter" "0=\"\"${BASEDIR}/../examples/filter-quad.qml?webvfx_duration=300/30\"\"") + ;; +mlt) + FILTER=(mltwebvfx:"${BASEDIR}/../examples/filter-quad.qml") + ;; +esac +REDTESTSRC=lavfi:$(COLOR=red ${BASEDIR}/tests/testsrc) +melt -verbose "${VFX_SOURCE:-${REDTESTSRC}}" out=299 \ + -filter "${FILTER[@]}" out=299 \ + $(eval echo $(< "${VFX_CONSUMER:-${BASEDIR}/consumer_sdl}")) mlt_profile=${BASEDIR}/webvfx_profile diff --git a/demo/mlt/tests/extract_frames b/demo/mlt/tests/extract_frames index 78c9c32..dee2d0f 100755 --- a/demo/mlt/tests/extract_frames +++ b/demo/mlt/tests/extract_frames @@ -7,6 +7,7 @@ OUTPUT_DIR=${1:?Usage: $(basename $0) OUTPUT_DIR} ${BASEDIR}/extract_frame -s 00:00:04 "${BASEDIR}/../mlt_filter_banner" "${OUTPUT_DIR}/filter_banner.png" ${BASEDIR}/extract_frame -s 00:00:04 "${BASEDIR}/../mlt_filter_demo" "${OUTPUT_DIR}/filter_demo.png" +${BASEDIR}/extract_frame -s 00:00:04 "${BASEDIR}/../mlt_filter_quad" "${OUTPUT_DIR}/filter_quad.png" ${BASEDIR}/extract_frame -s 00:00:04 "${BASEDIR}/../mlt_producer_demo" "${OUTPUT_DIR}/producer_demo.png" ${BASEDIR}/extract_frame -s 00:00:09 "${BASEDIR}/../mlt_transition_demo3d" "${OUTPUT_DIR}/transition_demo3d.png" ${BASEDIR}/extract_frame -s 00:00:09 "${BASEDIR}/../mlt_transition_shader_crosszoom_extra" "${OUTPUT_DIR}/transition_shader_crosszoom.png" diff --git a/demo/mlt/tests/fixtures/linux/frei0r/filter_quad.png b/demo/mlt/tests/fixtures/linux/frei0r/filter_quad.png new file mode 100644 index 0000000..a435940 Binary files /dev/null and b/demo/mlt/tests/fixtures/linux/frei0r/filter_quad.png differ diff --git a/demo/mlt/tests/fixtures/linux/mlt/filter_quad.png b/demo/mlt/tests/fixtures/linux/mlt/filter_quad.png new file mode 100644 index 0000000..a435940 Binary files /dev/null and b/demo/mlt/tests/fixtures/linux/mlt/filter_quad.png differ diff --git a/demo/mlt/tests/fixtures/macos/frei0r/filter_quad.png b/demo/mlt/tests/fixtures/macos/frei0r/filter_quad.png new file mode 100644 index 0000000..0d9813f Binary files /dev/null and b/demo/mlt/tests/fixtures/macos/frei0r/filter_quad.png differ diff --git a/demo/mlt/tests/fixtures/macos/mlt/filter_quad.png b/demo/mlt/tests/fixtures/macos/mlt/filter_quad.png new file mode 100644 index 0000000..0d9813f Binary files /dev/null and b/demo/mlt/tests/fixtures/macos/mlt/filter_quad.png differ diff --git a/vfxpipe/vfxpipe.cpp b/vfxpipe/vfxpipe.cpp index 9d1b635..6e8e6e8 100644 --- a/vfxpipe/vfxpipe.cpp +++ b/vfxpipe/vfxpipe.cpp @@ -16,7 +16,7 @@ namespace VfxPipe { -std::tuple spawnProcess(int* pipeReadStdout, int* pipeWriteStdin, int* pipeReadStderr, const std::string& url, ErrorHandler errorHandler) +std::tuple spawnProcess(int* pipeReadStdout, int* pipeWriteStdin, int* pipeReadStderr, const std::string& url, uint32_t width, uint32_t height, ErrorHandler errorHandler) { int fdsToChildStdin[2]; int fdsFromChildStdout[2]; @@ -70,7 +70,7 @@ std::tuple spawnProcess(int* pipeReadStdout, int* pipeWriteStdin, exit(1); } } else { - errorHandler(std::string("vfxpipe unable to determine library path")); + errorHandler("vfxpipe unable to determine library path"); exit(1); } } @@ -86,8 +86,19 @@ std::tuple spawnProcess(int* pipeReadStdout, int* pipeWriteStdin, close(fdsFromChildStderr[1]); } + VideoFrameFormat format(VideoFrameFormat::RGBA32, width, height); uint32_t sinkCount = 0; - if (!dataIO(*pipeReadStdout, reinterpret_cast(&sinkCount), sizeof(sinkCount), read, errorHandler)) { + if (!(writeVideoFrameFormat(*pipeWriteStdin, format, errorHandler) + && dataIO(*pipeReadStdout, reinterpret_cast(&sinkCount), sizeof(sinkCount), read, errorHandler))) { + errorHandler("vfxpipe failed to read sink count"); + close(*pipeReadStdout); + *pipeReadStdout = -1; + close(*pipeWriteStdin); + *pipeWriteStdin = -1; + if (pipeReadStderr) { + close(*pipeReadStderr); + *pipeReadStderr = -1; + } return { -1, 0 }; } @@ -110,10 +121,10 @@ FrameServer::~FrameServer() close(pipeWriteStdin); } -bool FrameServer::initialize(ErrorHandler errorHandler, int* pipeReadStderr) +bool FrameServer::initialize(ErrorHandler errorHandler, uint32_t width, uint32_t height, int* pipeReadStderr) { if (!pid) { - std::tie(pid, sinkCount) = spawnProcess(&pipeReadStdout, &pipeWriteStdin, pipeReadStderr, url, errorHandler); + std::tie(pid, sinkCount) = spawnProcess(&pipeReadStdout, &pipeWriteStdin, pipeReadStderr, url, width, height, errorHandler); if (pid == -1) { errorHandler("vfxpipe failed to spawn process"); return false; @@ -128,7 +139,7 @@ bool FrameServer::renderFrame(double time, const std::vector& if (pid == -1) return false; if (!pid) { - if (!initialize(errorHandler)) + if (!initialize(errorHandler, outputFrame.format.width, outputFrame.format.height)) return false; } diff --git a/vfxpipe/vfxpipe.h b/vfxpipe/vfxpipe.h index b0f53b8..4fe5428 100644 --- a/vfxpipe/vfxpipe.h +++ b/vfxpipe/vfxpipe.h @@ -98,7 +98,7 @@ class FrameServer { public: FrameServer(const std::string& url); ~FrameServer(); - bool initialize(ErrorHandler errorHandler, int* pipeReadStderr = nullptr); + bool initialize(ErrorHandler errorHandler, uint32_t width, uint32_t height, int* pipeReadStderr = nullptr); bool renderFrame(double time, const std::vector& sourceFrames, RenderedVideoFrame& outputFrame, ErrorHandler errorHandler); std::string& getUrl() { return url; } uint32_t getSinkCount() { return sinkCount; } diff --git a/viewer/viewer.cpp b/viewer/viewer.cpp index 2597940..a6b162e 100644 --- a/viewer/viewer.cpp +++ b/viewer/viewer.cpp @@ -240,7 +240,7 @@ void Viewer::createContent(const QString& fileName) delete errorNotifier; int pipeReadStderr = 0; - if (frameServer->initialize(errorHandler, &pipeReadStderr)) { + if (frameServer->initialize(errorHandler, size.width(), size.height(), &pipeReadStderr)) { errorNotifier = new QSocketNotifier(pipeReadStderr, QSocketNotifier::Read, this); connect(errorNotifier, &QSocketNotifier::activated, this, &Viewer::onErrorReadyRead); setContentUIEnabled(true); diff --git a/webvfx/content_context.cpp b/webvfx/content_context.cpp index e2e8bdb..6469c5b 100644 --- a/webvfx/content_context.cpp +++ b/webvfx/content_context.cpp @@ -39,14 +39,6 @@ QString ContentContext::getStringParameter(const QString& name) return QString(); } -void ContentContext::setVideoSize(QSize size) -{ - if (videoSize == size) - return; - videoSize = size; - emit videoSizeChanged(size); -} - qsizetype ContentContext::addVideoSource() { videoSinks.resize(videoSinks.size() + 1); diff --git a/webvfx/content_context.h b/webvfx/content_context.h index b2c3aa2..35df3ce 100644 --- a/webvfx/content_context.h +++ b/webvfx/content_context.h @@ -24,7 +24,6 @@ namespace WebVfx { class ContentContext : public QObject { Q_OBJECT - Q_PROPERTY(QSize videoSize READ getVideoSize WRITE setVideoSize NOTIFY videoSizeChanged) // QML contents can set this if it requires async rendering. // It should invoke emitAsyncRenderComplete when ready Q_PROPERTY(bool asyncRenderRequired READ isAsyncRenderRequired WRITE setAsyncRenderRequired) @@ -57,8 +56,6 @@ class ContentContext : public QObject { // webvfx.emitAsyncRenderComplete() Q_INVOKABLE void emitAsyncRenderComplete(); - QSize getVideoSize() const { return videoSize; }; - void setVideoSize(QSize size); bool isAsyncRenderRequired() { return asyncRenderRequired; }; void setAsyncRenderRequired(bool asyncRender) { asyncRenderRequired = asyncRender; }; const QList>& getVideoSinks() { return videoSinks; } @@ -68,14 +65,12 @@ class ContentContext : public QObject { // time is normalized 0..1.0 // JS: webvfx.renderRequested.connect(function (time) { doSomething(); }) void renderRequested(double time); - void videoSizeChanged(QSize size); void asyncRenderComplete(); private: Parameters* parameters; QList> videoSinks; - QSize videoSize; bool asyncRenderRequired; }; diff --git a/webvfx/frameserver.cpp b/webvfx/frameserver.cpp index 9daeb6e..1e11214 100644 --- a/webvfx/frameserver.cpp +++ b/webvfx/frameserver.cpp @@ -104,6 +104,11 @@ FrameServer::FrameServer(QUrl& qmlUrl, QObject* parent) urlQuery.removeQueryItem("webvfx_duration"); content = new WebVfx::QmlContent(new FrameServerParameters(urlQuery)); + if (!VfxPipe::readVideoFrameFormat(STDIN_FILENO, outputFrame.format, ioErrorHandler)) { + return; + } + content->resize(outputFrame.format.width, outputFrame.format.height); + qmlUrl.setQuery(QString()); connect(content, &WebVfx::QmlContent::contentLoadFinished, this, &FrameServer::onContentLoadFinished); @@ -119,7 +124,6 @@ FrameServer::~FrameServer() void FrameServer::onContentLoadFinished(bool result) { if (result) { - auto size = content->getContentSize(); auto videoSinks = content->getVideoSinks(); for (const auto videoSink : videoSinks) { frameSinks.append(FrameSink(videoSink)); @@ -161,7 +165,7 @@ void FrameServer::readFrames() if (!VfxPipe::readVideoFrameFormat(STDIN_FILENO, outputFrame.format, ioErrorHandler)) return; - content->setContentSize(QSize(outputFrame.format.width, outputFrame.format.height)); + content->resize(outputFrame.format.width, outputFrame.format.height); uint32_t frameCount; if (!VfxPipe::dataIO(STDIN_FILENO, reinterpret_cast(&frameCount), sizeof(frameCount), read, ioErrorHandler)) diff --git a/webvfx/qml_content.cpp b/webvfx/qml_content.cpp index 256a74d..bbbdf84 100644 --- a/webvfx/qml_content.cpp +++ b/webvfx/qml_content.cpp @@ -20,7 +20,7 @@ QmlContent::QmlContent(RenderControl* renderControl, Parameters* parameters) , renderControl(renderControl) , renderExpected(false) { - setResizeMode(ResizeMode::SizeViewToRootObject); + setResizeMode(ResizeMode::SizeRootObjectToView); // Expose context to the QML rootContext()->setContextProperty("webvfx", contentContext); @@ -60,13 +60,6 @@ void QmlContent::loadContent(const QUrl& url) setSource(url); } -void QmlContent::setContentSize(const QSize& size) -{ - if (contentContext->getVideoSize() != size) { - contentContext->setVideoSize(size); - } -} - void QmlContent::contextAsyncRenderComplete() { if (!renderExpected) { @@ -83,7 +76,7 @@ void QmlContent::contextAsyncRenderComplete() void QmlContent::renderContent(double time) { - if (!renderControl->install(this, contentContext->getVideoSize())) { + if (!renderControl->install(this, size())) { emit renderComplete(QImage()); } diff --git a/webvfx/qml_content.h b/webvfx/qml_content.h index f24875b..2ac8557 100644 --- a/webvfx/qml_content.h +++ b/webvfx/qml_content.h @@ -30,8 +30,6 @@ class QmlContent : public QQuickView { ~QmlContent() override; void loadContent(const QUrl& url); - void setContentSize(const QSize& size); - QSize getContentSize() { return contentContext->getVideoSize(); } const QList>& getVideoSinks() { return contentContext->getVideoSinks(); } void renderContent(double time);