Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquickvideooutput.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// Copyright (C) 2016 Research In Motion
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6#include <private/qvideooutputorientationhandler_p.h>
7#include <QtMultimedia/qmediaplayer.h>
8#include <QtMultimedia/qmediacapturesession.h>
9#include <private/qfactoryloader_p.h>
10#include <QtCore/qloggingcategory.h>
11#include <qvideosink.h>
12#include <QtQuick/QQuickWindow>
13#include <private/qquickwindow_p.h>
14#include <private/qmultimediautils_p.h>
15#include <qsgvideonode_p.h>
16#include <QtCore/qrunnable.h>
17
19
20static Q_LOGGING_CATEGORY(qLcVideo, "qt.multimedia.video")
21
22namespace {
23
24inline bool qIsDefaultAspect(int o)
25{
26 return (o % 180) == 0;
27}
28
29/*
30 * Return the orientation normalized to 0-359
31 */
32inline int qNormalizedOrientation(int o)
33{
34 // Negative orientations give negative results
35 int o2 = o % 360;
36 if (o2 < 0)
37 o2 += 360;
38 return o2;
39}
40
41}
42
46
95// TODO: Restore Qt System Info docs when the module is released
96
104 QQuickItem(parent)
105{
107
108 m_sink = new QVideoSink(this);
109 qRegisterMetaType<QVideoFrameFormat>();
111 [this](const QVideoFrame &frame) {
112 setFrame(frame);
113 QMetaObject::invokeMethod(this, &QQuickVideoOutput::_q_newFrame, frame.size());
114 },
116
117 initRhiForSink();
118}
119
123
134{
135 return m_sink;
136}
137
153{
154 return FillMode(m_aspectRatioMode);
155}
156
158{
159 if (mode == fillMode())
160 return;
161
162 m_aspectRatioMode = Qt::AspectRatioMode(mode);
163
164 m_geometryDirty = true;
165 update();
166
168}
169
170void QQuickVideoOutput::_q_newFrame(QSize size)
171{
172 update();
173
174 size = qRotatedFrameSize(size, m_orientation + m_frameOrientation);
175
176 if (m_nativeSize != size) {
177 m_nativeSize = size;
178
179 m_geometryDirty = true;
180
181 setImplicitWidth(size.width());
182 setImplicitHeight(size.height());
183
185 }
186}
187
188/* Based on fill mode and our size, figure out the source/dest rects */
189void QQuickVideoOutput::_q_updateGeometry()
190{
191 const QRectF rect(0, 0, width(), height());
192 const QRectF absoluteRect(x(), y(), width(), height());
193
194 if (!m_geometryDirty && m_lastRect == absoluteRect)
195 return;
196
197 QRectF oldContentRect(m_contentRect);
198
199 m_geometryDirty = false;
200 m_lastRect = absoluteRect;
201
202 const auto fill = m_aspectRatioMode;
203 if (m_nativeSize.isEmpty()) {
204 //this is necessary for item to receive the
205 //first paint event and configure video surface.
206 m_contentRect = rect;
207 } else if (fill == Qt::IgnoreAspectRatio) {
208 m_contentRect = rect;
209 } else {
210 QSizeF scaled = m_nativeSize;
211 scaled.scale(rect.size(), fill);
212
213 m_contentRect = QRectF(QPointF(), scaled);
214 m_contentRect.moveCenter(rect.center());
215 }
216
217 updateGeometry();
218
219 if (m_contentRect != oldContentRect)
221}
222
240{
241 return m_orientation;
242}
243
245{
246 // Make sure it's a multiple of 90.
247 if (orientation % 90)
248 return;
249
250 // If there's no actual change, return
251 if (m_orientation == orientation)
252 return;
253
254 // If the new orientation is the same effect
255 // as the old one, don't update the video node stuff
256 if ((m_orientation % 360) == (orientation % 360)) {
257 m_orientation = orientation;
259 return;
260 }
261
262 m_geometryDirty = true;
263
264 // Otherwise, a new orientation
265 // See if we need to change aspect ratio orientation too
266 bool oldAspect = qIsDefaultAspect(m_orientation);
267 bool newAspect = qIsDefaultAspect(orientation);
268
269 m_orientation = orientation;
270
271 if (oldAspect != newAspect) {
272 m_nativeSize.transpose();
273
274 setImplicitWidth(m_nativeSize.width());
275 setImplicitHeight(m_nativeSize.height());
276
277 // Source rectangle does not change for orientation
278 }
279
280 update();
282}
283
299{
300 return m_contentRect;
301}
302
321{
322 // We might have to transpose back
323 QSizeF size = m_nativeSize;
324 if (!size.isValid())
325 return {};
326
327 if (!qIsDefaultAspect(m_orientation + m_frameOrientation))
328 size.transpose();
329
330
331 // Take the viewport into account for the top left position.
332 // m_nativeSize is already adjusted to the viewport, as it originates
333 // from QVideoFrameFormat::viewport(), which includes pixel aspect ratio
334 const QRectF viewport = adjustedViewport();
335 Q_ASSERT(viewport.size() == size);
336 return QRectF(viewport.topLeft(), size);
337}
338
339void QQuickVideoOutput::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
340{
341 Q_UNUSED(newGeometry);
342 Q_UNUSED(oldGeometry);
343
344 QQuickItem::geometryChange(newGeometry, oldGeometry);
345
346 // Explicitly listen to geometry changes here. This is needed since changing the position does
347 // not trigger a call to updatePaintNode().
348 // We need to react to position changes though, as the window backened's display rect gets
349 // changed in that situation.
350 _q_updateGeometry();
351}
352
353void QQuickVideoOutput::_q_invalidateSceneGraph()
354{
355 invalidateSceneGraph();
356}
357
358void QQuickVideoOutput::_q_sceneGraphInitialized()
359{
360 initRhiForSink();
361}
362
364{
365 // Called on the gui thread when the window is closed or changed.
366 invalidateSceneGraph();
367}
368
369void QQuickVideoOutput::invalidateSceneGraph()
370{
371 // Called on the render thread, e.g. when the context is lost.
372 // QMutexLocker lock(&m_frameMutex);
373 initRhiForSink();
374}
375
376void QQuickVideoOutput::initRhiForSink()
377{
378 QRhi *rhi = m_window ? QQuickWindowPrivate::get(m_window)->rhi : nullptr;
379 m_sink->setRhi(rhi);
380}
381
383 const QQuickItem::ItemChangeData &changeData)
384{
385 if (change != QQuickItem::ItemSceneChange)
386 return;
387
388 if (changeData.window == m_window)
389 return;
390 if (m_window)
391 disconnect(m_window);
392 m_window = changeData.window;
393
394 if (m_window) {
395 // We want to receive the signals in the render thread
397 &QQuickVideoOutput::_q_sceneGraphInitialized, Qt::DirectConnection);
399 &QQuickVideoOutput::_q_invalidateSceneGraph, Qt::DirectConnection);
400 }
401 initRhiForSink();
402}
403
404QSize QQuickVideoOutput::nativeSize() const
405{
406 return m_videoFormat.viewport().size();
407}
408
409void QQuickVideoOutput::updateGeometry()
410{
411 const QRectF viewport = m_videoFormat.viewport();
412 const QSizeF frameSize = m_videoFormat.frameSize();
413 const QRectF normalizedViewport(viewport.x() / frameSize.width(),
414 viewport.y() / frameSize.height(),
415 viewport.width() / frameSize.width(),
416 viewport.height() / frameSize.height());
417 const QRectF rect(0, 0, width(), height());
418 if (nativeSize().isEmpty()) {
419 m_renderedRect = rect;
420 m_sourceTextureRect = normalizedViewport;
421 } else if (m_aspectRatioMode == Qt::IgnoreAspectRatio) {
422 m_renderedRect = rect;
423 m_sourceTextureRect = normalizedViewport;
424 } else if (m_aspectRatioMode == Qt::KeepAspectRatio) {
425 m_sourceTextureRect = normalizedViewport;
426 m_renderedRect = contentRect();
427 } else if (m_aspectRatioMode == Qt::KeepAspectRatioByExpanding) {
428 m_renderedRect = rect;
429 const qreal contentHeight = contentRect().height();
430 const qreal contentWidth = contentRect().width();
431
432 // Calculate the size of the source rectangle without taking the viewport into account
433 const qreal relativeOffsetLeft = -contentRect().left() / contentWidth;
434 const qreal relativeOffsetTop = -contentRect().top() / contentHeight;
435 const qreal relativeWidth = rect.width() / contentWidth;
436 const qreal relativeHeight = rect.height() / contentHeight;
437
438 // Now take the viewport size into account
439 const qreal totalOffsetLeft = normalizedViewport.x() + relativeOffsetLeft * normalizedViewport.width();
440 const qreal totalOffsetTop = normalizedViewport.y() + relativeOffsetTop * normalizedViewport.height();
441 const qreal totalWidth = normalizedViewport.width() * relativeWidth;
442 const qreal totalHeight = normalizedViewport.height() * relativeHeight;
443
444 if (qIsDefaultAspect(orientation() + m_frameOrientation)) {
445 m_sourceTextureRect = QRectF(totalOffsetLeft, totalOffsetTop,
446 totalWidth, totalHeight);
447 } else {
448 m_sourceTextureRect = QRectF(totalOffsetTop, totalOffsetLeft,
449 totalHeight, totalWidth);
450 }
451 }
452
453 if (m_videoFormat.scanLineDirection() == QVideoFrameFormat::BottomToTop) {
454 qreal top = m_sourceTextureRect.top();
455 m_sourceTextureRect.setTop(m_sourceTextureRect.bottom());
456 m_sourceTextureRect.setBottom(top);
457 }
458
459 if (m_videoFormat.isMirrored()) {
460 qreal left = m_sourceTextureRect.left();
461 m_sourceTextureRect.setLeft(m_sourceTextureRect.right());
462 m_sourceTextureRect.setRight(left);
463 }
464}
465
468{
469 Q_UNUSED(data);
470 _q_updateGeometry();
471
472 QSGVideoNode *videoNode = static_cast<QSGVideoNode *>(oldNode);
473
474 QMutexLocker lock(&m_frameMutex);
475
476 if (m_frameChanged) {
477 if (videoNode && videoNode->pixelFormat() != m_frame.pixelFormat()) {
478 qCDebug(qLcVideo) << "updatePaintNode: deleting old video node because frame format changed";
479 delete videoNode;
480 videoNode = nullptr;
481 }
482
483 if (!m_frame.isValid()) {
484 qCDebug(qLcVideo) << "updatePaintNode: no frames yet";
485 m_frameChanged = false;
486 return nullptr;
487 }
488
489 if (!videoNode) {
490 // Get a node that supports our frame. The surface is irrelevant, our
491 // QSGVideoItemSurface supports (logically) anything.
492 updateGeometry();
493 videoNode = new QSGVideoNode(this, m_videoFormat);
494 qCDebug(qLcVideo) << "updatePaintNode: Video node created. Handle type:" << m_frame.handleType();
495 }
496 }
497
498 if (!videoNode) {
499 m_frameChanged = false;
500 m_frame = QVideoFrame();
501 return nullptr;
502 }
503
504 if (m_frameChanged) {
505 videoNode->setCurrentFrame(m_frame);
506
507 updateHdr(videoNode);
508
509 //don't keep the frame for more than really necessary
510 m_frameChanged = false;
511 m_frame = QVideoFrame();
512 }
513
514 // Negative rotations need lots of %360
515 videoNode->setTexturedRectGeometry(m_renderedRect, m_sourceTextureRect,
516 qNormalizedOrientation(orientation()));
517
518 return videoNode;
519}
520
521void QQuickVideoOutput::updateHdr(QSGVideoNode *videoNode)
522{
523 auto *videoOutputWindow = window();
524 if (!videoOutputWindow)
525 return;
526
527 auto *swapChain = videoOutputWindow->swapChain();
528 if (!swapChain)
529 return;
530
531 const auto requiredSwapChainFormat = qGetRequiredSwapChainFormat(m_frame.surfaceFormat());
532 if (qShouldUpdateSwapChainFormat(swapChain, requiredSwapChainFormat)) {
533 auto *recreateSwapChainJob = QRunnable::create([swapChain, requiredSwapChainFormat]() {
534 swapChain->destroy();
535 swapChain->setFormat(requiredSwapChainFormat);
536 swapChain->createOrResize();
537 });
538
539 // Even though the 'recreate swap chain' job is scheduled for the current frame the
540 // effect will be visible only starting from the next frame since the recreation would
541 // happen after the actual swap.
542 videoOutputWindow->scheduleRenderJob(recreateSwapChainJob, QQuickWindow::AfterSwapStage);
543 }
544
545 videoNode->setSurfaceFormat(swapChain->format());
546 videoNode->setHdrInfo(swapChain->hdrInfo());
547}
548
549QRectF QQuickVideoOutput::adjustedViewport() const
550{
551 return m_videoFormat.viewport();
552}
553
554void QQuickVideoOutput::setFrame(const QVideoFrame &frame)
555{
556 QMutexLocker lock(&m_frameMutex);
557
558 m_videoFormat = frame.surfaceFormat();
559 m_frame = frame;
560 m_frameOrientation = static_cast<int>(frame.rotation());
561 m_frameChanged = true;
562}
563
565
566#include "moc_qquickvideooutput_p.cpp"
\inmodule QtCore
Definition qmutex.h:313
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
\inmodule QtCore\reentrant
Definition qpoint.h:217
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
void setFlag(Flag flag, bool enabled=true)
Enables the specified flag for this item if enabled is true; if enabled is false, the flag is disable...
virtual void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
qreal x
\qmlproperty real QtQuick::Item::x \qmlproperty real QtQuick::Item::y \qmlproperty real QtQuick::Item...
Definition qquickitem.h:72
qreal y
Defines the item's y position relative to its parent.
Definition qquickitem.h:73
QSizeF size() const
QQuickWindow * window() const
Returns the window in which this item is rendered.
qreal width
This property holds the width of this item.
Definition qquickitem.h:75
void setImplicitHeight(qreal)
qreal height
This property holds the height of this item.
Definition qquickitem.h:76
ItemChange
Used in conjunction with QQuickItem::itemChange() to notify the item about certain types of changes.
Definition qquickitem.h:144
void setImplicitWidth(qreal)
void update()
Schedules a call to updatePaintNode() for this item.
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override
void fillModeChanged(QQuickVideoOutput::FillMode)
void contentRectChanged()
void orientationChanged()
void itemChange(ItemChange change, const ItemChangeData &changeData) override
Called when change occurs for this item.
QQuickVideoOutput(QQuickItem *parent=0)
void releaseResources() override
This function is called when an item should release graphics resources which are not already managed ...
QSGNode * updatePaintNode(QSGNode *, UpdatePaintNodeData *) override
Called on the render thread when it is time to sync the state of the item with the scene graph.
void setFillMode(FillMode mode)
void sourceRectChanged()
static QQuickWindowPrivate * get(QQuickWindow *c)
void sceneGraphInitialized()
\qmlsignal QtQuick::Window::frameSwapped()
void sceneGraphInvalidated()
\qmlsignal QtQuick::Window::sceneGraphInitialized()
\inmodule QtCore\reentrant
Definition qrect.h:484
constexpr void moveCenter(const QPointF &p) noexcept
Moves the rectangle, leaving the center point at the given position.
Definition qrect.h:726
constexpr qreal bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:500
constexpr void setBottom(qreal pos) noexcept
Sets the bottom edge of the rectangle to the given finite y coordinate.
Definition qrect.h:684
constexpr void setRight(qreal pos) noexcept
Sets the right edge of the rectangle to the given finite x coordinate.
Definition qrect.h:678
constexpr void setLeft(qreal pos) noexcept
Sets the left edge of the rectangle to the given finite x coordinate.
Definition qrect.h:675
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:732
constexpr qreal width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:729
constexpr void setTop(qreal pos) noexcept
Sets the top edge of the rectangle to the given finite y coordinate.
Definition qrect.h:681
constexpr qreal left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:497
constexpr qreal top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:498
constexpr qreal right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:499
constexpr QSize size() const noexcept
Returns the size of the rectangle.
Definition qrect.h:242
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1804
static QRunnable * create(Callable &&functionToRun)
Definition qrunnable.h:114
\group qtquick-scenegraph-nodes \title Qt Quick Scene Graph Node classes
Definition qsgnode.h:37
\inmodule QtCore
Definition qsize.h:208
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:133
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:130
constexpr bool isEmpty() const noexcept
Returns true if either of the width and height is less than or equal to 0; otherwise returns false.
Definition qsize.h:124
void transpose() noexcept
Swaps the width and height values.
Definition qsize.cpp:130
bool isMirrored() const
Returns true if the surface is mirrored around its vertical axis.
QRect viewport() const
Returns the viewport of a video stream.
Direction scanLineDirection() const
Returns the direction of scan lines.
QSize frameSize() const
Returns the dimensions of frames in a video stream.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:27
QVideoFrame::HandleType handleType() const
Returns the type of a video frame's handle.
QVideoFrameFormat surfaceFormat() const
Returns the surface format of this video frame.
QVideoFrameFormat::PixelFormat pixelFormat() const
Returns the pixel format of this video frame.
bool isValid() const
Identifies whether a video frame is valid.
The QVideoSink class represents a generic sink for video data.
Definition qvideosink.h:22
void videoFrameChanged(const QVideoFrame &frame) QT6_ONLY(const)
Signals when the video frame changes.
void setRhi(QRhi *rhi)
QSize size
the size of the widget excluding any window frame
Definition qwidget.h:113
rect
[4]
Combined button and popup list for selecting options.
AspectRatioMode
@ KeepAspectRatioByExpanding
@ KeepAspectRatio
@ IgnoreAspectRatio
@ DirectConnection
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
QRhiSwapChain::Format qGetRequiredSwapChainFormat(const QVideoFrameFormat &format)
QSize qRotatedFrameSize(QSize size, int rotation)
bool qShouldUpdateSwapChainFormat(QRhiSwapChain *swapChain, QRhiSwapChain::Format requiredSwapChainFormat)
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLdouble GLdouble GLdouble GLdouble top
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLint left
static constexpr QSize frameSize(const T &frame)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
#define Q_UNUSED(x)
double qreal
Definition qtypes.h:187
QImage scaled(const QImage &image)
[0]
myObject disconnect()
[26]
QReadWriteLock lock
[0]
ba fill(true)
view viewport() -> scroll(dx, dy, deviceRect)
QFrame frame
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...
\inmodule QtQuick
Definition qquickitem.h:159