Qt 6.x
The Qt SDK
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 <qsgvideonode_p.h>
15
17
18static Q_LOGGING_CATEGORY(qLcVideo, "qt.multimedia.video")
19
20namespace {
21
22inline bool qIsDefaultAspect(int o)
23{
24 return (o % 180) == 0;
25}
26
27/*
28 * Return the orientation normalized to 0-359
29 */
30inline int qNormalizedOrientation(int o)
31{
32 // Negative orientations give negative results
33 int o2 = o % 360;
34 if (o2 < 0)
35 o2 += 360;
36 return o2;
37}
38
39}
40
44
93// TODO: Restore Qt System Info docs when the module is released
94
103{
105
106 m_sink = new QVideoSink(this);
107 qRegisterMetaType<QVideoFrameFormat>();
109 [&](const QVideoFrame &frame) {
110 setFrame(frame);
112 });
113
115 this, &QQuickVideoOutput::_q_newFrame);
116
117 initRhiForSink();
118}
119
121{
122}
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 if (!qIsDefaultAspect(m_orientation + m_frameOrientation)) {
175 size.transpose();
176 }
177
178 if (m_nativeSize != size) {
179 m_nativeSize = size;
180
181 m_geometryDirty = true;
182
183 setImplicitWidth(size.width());
184 setImplicitHeight(size.height());
185
187 }
188}
189
190/* Based on fill mode and our size, figure out the source/dest rects */
191void QQuickVideoOutput::_q_updateGeometry()
192{
193 const QRectF rect(0, 0, width(), height());
194 const QRectF absoluteRect(x(), y(), width(), height());
195
196 if (!m_geometryDirty && m_lastRect == absoluteRect)
197 return;
198
199 QRectF oldContentRect(m_contentRect);
200
201 m_geometryDirty = false;
202 m_lastRect = absoluteRect;
203
204 const auto fill = m_aspectRatioMode;
205 if (m_nativeSize.isEmpty()) {
206 //this is necessary for item to receive the
207 //first paint event and configure video surface.
208 m_contentRect = rect;
209 } else if (fill == Qt::IgnoreAspectRatio) {
210 m_contentRect = rect;
211 } else {
212 QSizeF scaled = m_nativeSize;
213 scaled.scale(rect.size(), fill);
214
215 m_contentRect = QRectF(QPointF(), scaled);
216 m_contentRect.moveCenter(rect.center());
217 }
218
219 updateGeometry();
220
221 if (m_contentRect != oldContentRect)
223}
224
242{
243 return m_orientation;
244}
245
247{
248 // Make sure it's a multiple of 90.
249 if (orientation % 90)
250 return;
251
252 // If there's no actual change, return
253 if (m_orientation == orientation)
254 return;
255
256 // If the new orientation is the same effect
257 // as the old one, don't update the video node stuff
258 if ((m_orientation % 360) == (orientation % 360)) {
259 m_orientation = orientation;
261 return;
262 }
263
264 m_geometryDirty = true;
265
266 // Otherwise, a new orientation
267 // See if we need to change aspect ratio orientation too
268 bool oldAspect = qIsDefaultAspect(m_orientation);
269 bool newAspect = qIsDefaultAspect(orientation);
270
271 m_orientation = orientation;
272
273 if (oldAspect != newAspect) {
274 m_nativeSize.transpose();
275
276 setImplicitWidth(m_nativeSize.width());
277 setImplicitHeight(m_nativeSize.height());
278
279 // Source rectangle does not change for orientation
280 }
281
282 update();
284}
285
301{
302 return m_contentRect;
303}
304
323{
324 // We might have to transpose back
325 QSizeF size = m_nativeSize;
326 if (!size.isValid())
327 return {};
328
329 if (!qIsDefaultAspect(m_orientation + m_frameOrientation))
330 size.transpose();
331
332
333 // Take the viewport into account for the top left position.
334 // m_nativeSize is already adjusted to the viewport, as it originates
335 // from QVideoFrameFormat::viewport(), which includes pixel aspect ratio
336 const QRectF viewport = adjustedViewport();
337 Q_ASSERT(viewport.size() == size);
338 return QRectF(viewport.topLeft(), size);
339}
340
341void QQuickVideoOutput::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
342{
343 Q_UNUSED(newGeometry);
344 Q_UNUSED(oldGeometry);
345
346 QQuickItem::geometryChange(newGeometry, oldGeometry);
347
348 // Explicitly listen to geometry changes here. This is needed since changing the position does
349 // not trigger a call to updatePaintNode().
350 // We need to react to position changes though, as the window backened's display rect gets
351 // changed in that situation.
352 _q_updateGeometry();
353}
354
355void QQuickVideoOutput::_q_invalidateSceneGraph()
356{
357 invalidateSceneGraph();
358}
359
360void QQuickVideoOutput::_q_sceneGraphInitialized()
361{
362 initRhiForSink();
363}
364
366{
367 // Called on the gui thread when the window is closed or changed.
368 invalidateSceneGraph();
369}
370
371void QQuickVideoOutput::invalidateSceneGraph()
372{
373 // Called on the render thread, e.g. when the context is lost.
374 // QMutexLocker lock(&m_frameMutex);
375 initRhiForSink();
376}
377
378void QQuickVideoOutput::initRhiForSink()
379{
380 QRhi *rhi = m_window ? QQuickWindowPrivate::get(m_window)->rhi : nullptr;
381 m_sink->setRhi(rhi);
382}
383
385 const QQuickItem::ItemChangeData &changeData)
386{
387 if (change != QQuickItem::ItemSceneChange)
388 return;
389
390 if (changeData.window == m_window)
391 return;
392 if (m_window)
393 disconnect(m_window);
394 m_window = changeData.window;
395
396 if (m_window) {
397 // We want to receive the signals in the render thread
398 QObject::connect(m_window, &QQuickWindow::sceneGraphInitialized, this, &QQuickVideoOutput::_q_sceneGraphInitialized,
401 this, &QQuickVideoOutput::_q_invalidateSceneGraph, Qt::DirectConnection);
402 }
403 initRhiForSink();
404}
405
406QSize QQuickVideoOutput::nativeSize() const
407{
408 return m_surfaceFormat.viewport().size();
409}
410
411void QQuickVideoOutput::updateGeometry()
412{
413 const QRectF viewport = m_surfaceFormat.viewport();
414 const QSizeF frameSize = m_surfaceFormat.frameSize();
415 const QRectF normalizedViewport(viewport.x() / frameSize.width(),
416 viewport.y() / frameSize.height(),
417 viewport.width() / frameSize.width(),
418 viewport.height() / frameSize.height());
419 const QRectF rect(0, 0, width(), height());
420 if (nativeSize().isEmpty()) {
421 m_renderedRect = rect;
422 m_sourceTextureRect = normalizedViewport;
423 } else if (m_aspectRatioMode == Qt::IgnoreAspectRatio) {
424 m_renderedRect = rect;
425 m_sourceTextureRect = normalizedViewport;
426 } else if (m_aspectRatioMode == Qt::KeepAspectRatio) {
427 m_sourceTextureRect = normalizedViewport;
428 m_renderedRect = contentRect();
429 } else if (m_aspectRatioMode == Qt::KeepAspectRatioByExpanding) {
430 m_renderedRect = rect;
431 const qreal contentHeight = contentRect().height();
432 const qreal contentWidth = contentRect().width();
433
434 // Calculate the size of the source rectangle without taking the viewport into account
435 const qreal relativeOffsetLeft = -contentRect().left() / contentWidth;
436 const qreal relativeOffsetTop = -contentRect().top() / contentHeight;
437 const qreal relativeWidth = rect.width() / contentWidth;
438 const qreal relativeHeight = rect.height() / contentHeight;
439
440 // Now take the viewport size into account
441 const qreal totalOffsetLeft = normalizedViewport.x() + relativeOffsetLeft * normalizedViewport.width();
442 const qreal totalOffsetTop = normalizedViewport.y() + relativeOffsetTop * normalizedViewport.height();
443 const qreal totalWidth = normalizedViewport.width() * relativeWidth;
444 const qreal totalHeight = normalizedViewport.height() * relativeHeight;
445
446 if (qIsDefaultAspect(orientation() + m_frameOrientation)) {
447 m_sourceTextureRect = QRectF(totalOffsetLeft, totalOffsetTop,
448 totalWidth, totalHeight);
449 } else {
450 m_sourceTextureRect = QRectF(totalOffsetTop, totalOffsetLeft,
451 totalHeight, totalWidth);
452 }
453 }
454
455 if (m_surfaceFormat.scanLineDirection() == QVideoFrameFormat::BottomToTop) {
456 qreal top = m_sourceTextureRect.top();
457 m_sourceTextureRect.setTop(m_sourceTextureRect.bottom());
458 m_sourceTextureRect.setBottom(top);
459 }
460
461 if (m_surfaceFormat.isMirrored()) {
462 qreal left = m_sourceTextureRect.left();
463 m_sourceTextureRect.setLeft(m_sourceTextureRect.right());
464 m_sourceTextureRect.setRight(left);
465 }
466}
467
470{
471 Q_UNUSED(data);
472 _q_updateGeometry();
473
474 QSGVideoNode *videoNode = static_cast<QSGVideoNode *>(oldNode);
475
476 QMutexLocker lock(&m_frameMutex);
477
478 if (m_frameChanged) {
479 if (videoNode && videoNode->pixelFormat() != m_frame.pixelFormat()) {
480 qCDebug(qLcVideo) << "updatePaintNode: deleting old video node because frame format changed";
481 delete videoNode;
482 videoNode = nullptr;
483 }
484
485 if (!m_frame.isValid()) {
486 qCDebug(qLcVideo) << "updatePaintNode: no frames yet";
487 m_frameChanged = false;
488 return nullptr;
489 }
490
491 if (!videoNode) {
492 // Get a node that supports our frame. The surface is irrelevant, our
493 // QSGVideoItemSurface supports (logically) anything.
494 updateGeometry();
495 videoNode = new QSGVideoNode(this, m_surfaceFormat);
496 qCDebug(qLcVideo) << "updatePaintNode: Video node created. Handle type:" << m_frame.handleType();
497 }
498 }
499
500 if (!videoNode) {
501 m_frameChanged = false;
502 m_frame = QVideoFrame();
503 return nullptr;
504 }
505
506 if (m_frameChanged) {
507 videoNode->setCurrentFrame(m_frame);
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
521QRectF QQuickVideoOutput::adjustedViewport() const
522{
523 return m_surfaceFormat.viewport();
524}
525
526void QQuickVideoOutput::setFrame(const QVideoFrame &frame)
527{
528 m_frameMutex.lock();
529 m_surfaceFormat = frame.surfaceFormat();
530 m_frame = frame;
531 m_frameOrientation = frame.rotationAngle();
532 m_frameChanged = true;
533 m_frameMutex.unlock();
534}
535
536void QQuickVideoOutput::stop()
537{
538 setFrame({});
539 update();
540}
541
543
544#include "moc_qquickvideooutput_p.cpp"
\inmodule QtCore
Definition qmutex.h:317
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:293
void lock() noexcept
Locks the mutex.
Definition qmutex.h:290
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
\inmodule QtCore\reentrant
Definition qpoint.h:214
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:64
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:73
qreal y
Defines the item's y position relative to its parent.
Definition qquickitem.h:74
QSizeF size() const
qreal width
This property holds the width of this item.
Definition qquickitem.h:76
void setImplicitHeight(qreal)
qreal height
This property holds the height of this item.
Definition qquickitem.h:77
ItemChange
Used in conjunction with QQuickItem::itemChange() to notify the item about certain types of changes.
Definition qquickitem.h:143
void setImplicitWidth(qreal)
void update()
Schedules a call to updatePaintNode() for this item.
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override
void frameUpdated(QSize)
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:483
constexpr void moveCenter(const QPointF &p) noexcept
Moves the rectangle, leaving the center point at the given position.
Definition qrect.h:712
constexpr qreal bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:499
constexpr void setBottom(qreal pos) noexcept
Sets the bottom edge of the rectangle to the given finite y coordinate.
Definition qrect.h:670
constexpr void setRight(qreal pos) noexcept
Sets the right edge of the rectangle to the given finite x coordinate.
Definition qrect.h:664
constexpr void setLeft(qreal pos) noexcept
Sets the left edge of the rectangle to the given finite x coordinate.
Definition qrect.h:661
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:718
constexpr qreal width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:715
constexpr void setTop(qreal pos) noexcept
Sets the top edge of the rectangle to the given finite y coordinate.
Definition qrect.h:667
constexpr qreal left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:496
constexpr qreal top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:497
constexpr qreal right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:498
constexpr QSize size() const noexcept
Returns the size of the rectangle.
Definition qrect.h:241
\inmodule QtGui
Definition qrhi.h:1767
\group qtquick-scenegraph-nodes \title Qt Quick Scene Graph Node classes
Definition qsgnode.h:37
void setTexturedRectGeometry(const QRectF &boundingRect, const QRectF &textureRect, int orientation)
QVideoFrameFormat::PixelFormat pixelFormat() const
void setCurrentFrame(const QVideoFrame &frame)
\inmodule QtCore
Definition qsize.h:207
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:132
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:129
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:123
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:26
QVideoFrame::HandleType handleType() const
Returns the type of a video frame's handle.
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,...)
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLdouble GLdouble GLdouble GLdouble top
GLint left
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
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:92
QImage scaled(const QImage &image)
[0]
myObject disconnect()
[26]
QReadWriteLock lock
[0]
ba fill(true)
view viewport() -> scroll(dx, dy, deviceRect)
QFrame frame
[0]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent
\inmodule QtQuick
Definition qquickitem.h:158