diff options
Diffstat (limited to 'tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp')
-rw-r--r-- | tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp b/tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp new file mode 100644 index 000000000..a7b6f286c --- /dev/null +++ b/tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp @@ -0,0 +1,392 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +//TESTED_COMPONENT=plugins/declarative/multimedia + +#include <QtTest/QtTest> + +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> +#include <QQuickView> +#include <QVideoSink> +#include <QMediaPlayer> + +#include "private/qquickvideooutput_p.h" + +#include <qobject.h> +#include <qvideoframeformat.h> +#include <qvideoframe.h> + +void presentDummyFrame(QVideoSink *sink, const QSize &size) +{ + if (sink) { + QVideoFrameFormat format(size, QVideoFrameFormat::Format_ARGB8888_Premultiplied); + QVideoFrame frame(format); + + sink->setVideoFrame(frame); + + // Have to spin an event loop or two for the surfaceFormatChanged() signal + qApp->processEvents(); + } +} + +class tst_QQuickVideoOutput : public QObject +{ + Q_OBJECT +public: + tst_QQuickVideoOutput(); + + ~tst_QQuickVideoOutput() override + { + delete m_mappingOutput; + delete m_mappingComponent; + } + +public slots: + void initTestCase(); + +private slots: + void fillMode(); + void orientation(); + void surfaceSource(); + void paintSurface(); + void sourceRect(); + + void contentRect(); + void contentRect_data(); + +private: + QQmlEngine m_engine; + + // Variables used for the mapping test + QQmlComponent *m_mappingComponent = nullptr; + QQuickVideoOutput *m_mappingOutput = nullptr; + + void updateOutputGeometry(QObject *output); +}; + +void tst_QQuickVideoOutput::initTestCase() +{ + // We initialize the mapping vars here + m_mappingComponent = new QQmlComponent(&m_engine); + m_mappingComponent->loadUrl(QUrl("qrc:/main.qml")); + + auto *component = m_mappingComponent->create(); + QVERIFY(component != nullptr); + + m_mappingOutput = qobject_cast<QQuickVideoOutput *>(component); + QVERIFY(m_mappingOutput); + + presentDummyFrame(m_mappingOutput->videoSink(), QSize(200,100)); + updateOutputGeometry(m_mappingOutput); + // First make sure the component has processed the frame + QCOMPARE(m_mappingOutput->sourceRect(), QRectF(0, 0, 200,100)); +} + +tst_QQuickVideoOutput::tst_QQuickVideoOutput() +{ +} + +void tst_QQuickVideoOutput::fillMode() +{ + QQmlComponent component(&m_engine); + component.loadUrl(QUrl("qrc:/main.qml")); + + QObject *videoOutput = component.create(); + QVERIFY(videoOutput != nullptr); + + QSignalSpy propSpy(videoOutput, SIGNAL(fillModeChanged(QQuickVideoOutput::FillMode))); + + // Default is preserveaspectfit + QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::PreserveAspectFit); + QCOMPARE(propSpy.size(), 0); + + videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::PreserveAspectCrop))); + QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::PreserveAspectCrop); + QCOMPARE(propSpy.size(), 1); + + videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::Stretch))); + QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::Stretch); + QCOMPARE(propSpy.size(), 2); + + videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::Stretch))); + QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::Stretch); + QCOMPARE(propSpy.size(), 2); + + delete videoOutput; +} + +void tst_QQuickVideoOutput::orientation() +{ + QQmlComponent component(&m_engine); + component.loadUrl(QUrl("qrc:/main.qml")); + + QObject *videoOutput = component.create(); + QVERIFY(videoOutput != nullptr); + + QSignalSpy propSpy(videoOutput, SIGNAL(orientationChanged())); + + // Default orientation is 0 + QCOMPARE(videoOutput->property("orientation").toInt(), 0); + QCOMPARE(propSpy.size(), 0); + + videoOutput->setProperty("orientation", QVariant(90)); + QCOMPARE(videoOutput->property("orientation").toInt(), 90); + QCOMPARE(propSpy.size(), 1); + + videoOutput->setProperty("orientation", QVariant(180)); + QCOMPARE(videoOutput->property("orientation").toInt(), 180); + QCOMPARE(propSpy.size(), 2); + + videoOutput->setProperty("orientation", QVariant(270)); + QCOMPARE(videoOutput->property("orientation").toInt(), 270); + QCOMPARE(propSpy.size(), 3); + + videoOutput->setProperty("orientation", QVariant(360)); + QCOMPARE(videoOutput->property("orientation").toInt(), 360); + QCOMPARE(propSpy.size(), 4); + + // More than 360 should be fine + videoOutput->setProperty("orientation", QVariant(540)); + QCOMPARE(videoOutput->property("orientation").toInt(), 540); + QCOMPARE(propSpy.size(), 5); + + // Negative should be fine + videoOutput->setProperty("orientation", QVariant(-180)); + QCOMPARE(videoOutput->property("orientation").toInt(), -180); + QCOMPARE(propSpy.size(), 6); + + // Same value should not reemit + videoOutput->setProperty("orientation", QVariant(-180)); + QCOMPARE(videoOutput->property("orientation").toInt(), -180); + QCOMPARE(propSpy.size(), 6); + + // Non multiples of 90 should not work + videoOutput->setProperty("orientation", QVariant(-1)); + QCOMPARE(videoOutput->property("orientation").toInt(), -180); + QCOMPARE(propSpy.size(), 6); + + delete videoOutput; +} + +void tst_QQuickVideoOutput::surfaceSource() +{ + QQmlComponent component(&m_engine); + component.loadUrl(QUrl("qrc:/main.qml")); + + QObject *videoOutput = component.create(); + QVERIFY(videoOutput != nullptr); + + QMediaPlayer holder(this); + + QCOMPARE(holder.videoOutput(), nullptr); + + holder.setVideoOutput(videoOutput); + + QVERIFY(holder.videoOutput() != nullptr); + QVERIFY(holder.videoSink() != nullptr); + + delete videoOutput; + + // This should clear the surface + QVERIFY(holder.videoOutput() == nullptr); + QVERIFY(holder.videoSink() == nullptr); + + // Also, creating two sources, setting them in order, and destroying the first + // should not zero holder.videoSink() + videoOutput = component.create(); + holder.setVideoOutput(videoOutput); + + QObject *surface = holder.videoOutput(); + QVERIFY(surface != nullptr); + + QObject *videoOutput2 = component.create(); + QVERIFY(videoOutput2); + holder.setVideoOutput(videoOutput2); + QVERIFY(holder.videoOutput() != nullptr); + QVERIFY(holder.videoOutput() != surface); // Surface should have changed + surface = holder.videoOutput(); + QVERIFY(surface == videoOutput2); + + // Now delete first one + delete videoOutput; + QVERIFY(holder.videoOutput() == surface); // Should not have changed surface + + // Now create a second surface and assign it as the source + // The old surface holder should be zeroed + QMediaPlayer holder2(this); + holder2.setVideoOutput(videoOutput2); + + QVERIFY(holder.videoOutput() == nullptr); + QVERIFY(holder2.videoOutput() != nullptr); + + // Finally a combination - set the same source to two things, then assign a new source + // to the first output - should not reset the first source + videoOutput = component.create(); + holder2.setVideoOutput(videoOutput); + + // Both vo and vo2 were pointed to holder2 - setting vo2 should not clear holder2 + QVERIFY(holder2.videoOutput() != nullptr); + QVERIFY(holder.videoOutput() == nullptr); + holder.setVideoOutput(videoOutput2); + QVERIFY(holder2.videoOutput() != nullptr); + QVERIFY(holder.videoOutput() != nullptr); + + // They should also be independent + QVERIFY(holder.videoOutput() != holder2.videoOutput()); + + delete videoOutput; + delete videoOutput2; +} + +static const uchar rgb32ImageData[] = +{// B G R A + 0x00, 0x01, 0x02, 0xff, 0x03, 0x04, 0x05, 0xff, + 0x06, 0x07, 0x08, 0xff, 0x09, 0x0a, 0x0b, 0xff, + 0x00, 0x01, 0x02, 0xff, 0x03, 0x04, 0x05, 0xff, + 0x06, 0x07, 0x08, 0xff, 0x09, 0x0a, 0x0b, 0xff, + 0x00, 0x01, 0x02, 0xff, 0x03, 0x04, 0x05, 0xff, + 0x06, 0x07, 0x08, 0xff, 0x09, 0x0a, 0x0b, 0xff, + 0x00, 0x01, 0x02, 0xff, 0x03, 0x04, 0x05, 0xff, + 0x06, 0x07, 0x08, 0xff, 0x09, 0x0a, 0x0b, 0xff +}; + +void tst_QQuickVideoOutput::paintSurface() +{ + QQuickView window; + window.setSource(QUrl("qrc:/main.qml")); + window.show(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + + auto videoOutput = qobject_cast<QQuickVideoOutput *>(window.rootObject()); + QVERIFY(videoOutput); + + auto surface = videoOutput->videoSink(); + QVERIFY(surface); + videoOutput->setSize(QSize(2, 2)); + + QVideoFrame frame(QVideoFrameFormat(QSize(4, 4), QVideoFrameFormat::Format_ARGB8888)); + frame.map(QVideoFrame::ReadWrite); + QCOMPARE(frame.mappedBytes(0), 64); + memcpy(frame.bits(0), rgb32ImageData, 64); + frame.unmap(); + surface->setVideoFrame(frame); +} + +void tst_QQuickVideoOutput::sourceRect() +{ + QQmlComponent component(&m_engine); + component.loadUrl(QUrl("qrc:/main.qml")); + + QObject *videoOutput = component.create(); + QVERIFY(videoOutput != nullptr); + + QMediaPlayer holder(this); + + QSignalSpy propSpy(videoOutput, SIGNAL(sourceRectChanged())); + + holder.setVideoOutput(videoOutput); + + QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF()); + + presentDummyFrame(holder.videoSink(), QSize(200,100)); + + QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); + QCOMPARE(propSpy.size(), 1); + + // Another frame shouldn't cause a source rect change + presentDummyFrame(holder.videoSink(), QSize(200,100)); + QCOMPARE(propSpy.size(), 1); + QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); + + // Changing orientation and stretch modes should not affect this + videoOutput->setProperty("orientation", QVariant(90)); + updateOutputGeometry(videoOutput); + QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); + + videoOutput->setProperty("orientation", QVariant(180)); + updateOutputGeometry(videoOutput); + QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); + + videoOutput->setProperty("orientation", QVariant(270)); + updateOutputGeometry(videoOutput); + QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); + + videoOutput->setProperty("orientation", QVariant(-90)); + updateOutputGeometry(videoOutput); + QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); + + videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::PreserveAspectCrop))); + updateOutputGeometry(videoOutput); + QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); + + videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::Stretch))); + updateOutputGeometry(videoOutput); + QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); + + videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::Stretch))); + updateOutputGeometry(videoOutput); + QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); + + delete videoOutput; +} + +void tst_QQuickVideoOutput::updateOutputGeometry(QObject *output) +{ + // Since the object isn't visible, update() doesn't do anything + // so we manually force this + QMetaObject::invokeMethod(output, "_q_updateGeometry"); +} + +void tst_QQuickVideoOutput::contentRect() +{ + QFETCH(int, orientation); + QFETCH(QQuickVideoOutput::FillMode, fillMode); + QFETCH(QRectF, expected); + + QVERIFY(m_mappingOutput); + m_mappingOutput->setProperty("orientation", QVariant(orientation)); + m_mappingOutput->setProperty("fillMode", QVariant::fromValue(fillMode)); + + updateOutputGeometry(m_mappingOutput); + + QRectF output = m_mappingOutput->property("contentRect").toRectF(); + QCOMPARE(output, expected); +} + +void tst_QQuickVideoOutput::contentRect_data() +{ + QTest::addColumn<int>("orientation"); + QTest::addColumn<QQuickVideoOutput::FillMode>("fillMode"); + QTest::addColumn<QRectF>("expected"); + + QQuickVideoOutput::FillMode stretch = QQuickVideoOutput::Stretch; + QQuickVideoOutput::FillMode fit = QQuickVideoOutput::PreserveAspectFit; + QQuickVideoOutput::FillMode crop = QQuickVideoOutput::PreserveAspectCrop; + + // Stretch just keeps the full render rect regardless of orientation + QTest::newRow("s0") << 0 << stretch << QRectF(0,0,150,100); + QTest::newRow("s90") << 90 << stretch << QRectF(0,0,150,100); + QTest::newRow("s180") << 180 << stretch << QRectF(0,0,150,100); + QTest::newRow("s270") << 270 << stretch << QRectF(0,0,150,100); + + // Fit depends on orientation + // Source is 200x100, fitting in 150x100 -> 150x75 + // or 100x200 -> 50x100 + QTest::newRow("f0") << 0 << fit << QRectF(0,12.5f,150,75); + QTest::newRow("f90") << 90 << fit << QRectF(50,0,50,100); + QTest::newRow("f180") << 180 << fit << QRectF(0,12.5,150,75); + QTest::newRow("f270") << 270 << fit << QRectF(50,0,50,100); + + // Crop also depends on orientation, may go outside render rect + // 200x100 -> -25,0 200x100 + // 100x200 -> 0,-100 150x300 + QTest::newRow("c0") << 0 << crop << QRectF(-25,0,200,100); + QTest::newRow("c90") << 90 << crop << QRectF(0,-100,150,300); + QTest::newRow("c180") << 180 << crop << QRectF(-25,0,200,100); + QTest::newRow("c270") << 270 << crop << QRectF(0,-100,150,300); +} + +QTEST_MAIN(tst_QQuickVideoOutput) + +#include "tst_qquickvideooutput.moc" |