393 lines
13 KiB
C++
393 lines
13 KiB
C++
// 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"
|