7#include <QtCore/qloggingcategory.h>
8#include <QtCore/qrunnable.h>
9#include <QtCore/private/qfactoryloader_p.h>
11#include <QtMultimedia/qmediaplayer.h>
12#include <QtMultimedia/qmediacapturesession.h>
13#include <QtMultimedia/private/qmultimediautils_p.h>
14#include <QtMultimedia/private/qvideoframetexturepool_p.h>
15#include <QtMultimedia/private/qvideooutputorientationhandler_p.h>
17#include <QtMultimediaQuick/private/qsgvideonode_p.h>
19#include <QtQuick/qquickwindow.h>
20#include <QtQuick/private/qquickwindow_p.h>
24Q_STATIC_LOGGING_CATEGORY(qLcVideo,
"qt.multimedia.video")
30 return (o % 180) == 0;
35 return qIsDefaultAspect(qToUnderlying(rotation));
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
94
95
96
97
99QQuickVideoOutput::QQuickVideoOutput(QQuickItem *parent) :
102 setFlag(ItemHasContents,
true);
104 m_sink =
new QQuickVideoSink(
this);
105 qRegisterMetaType<QVideoFrameFormat>();
108 connect(m_sink, &QVideoSink::videoFrameChanged,
this,
109 makeGuardedCall([
this](
const QVideoFrame &frame) {
110 if (frame.isValid() || m_endOfStreamPolicy == ClearOutput)
113 Qt::DirectConnection);
118QQuickVideoOutput::~QQuickVideoOutput()
121 QMutexLocker lock(&m_destructorGuard->m_mutex);
122 m_destructorGuard->m_isAlive =
false;
126 disconnectWindowConnections();
130
131
132
133
134
135
136
138QVideoSink *QQuickVideoOutput::videoSink()
const
144
145
146
147
148
149
150
151
152
153
154
155
157QQuickVideoOutput::FillMode QQuickVideoOutput::fillMode()
const
159 return FillMode(m_aspectRatioMode);
162void QQuickVideoOutput::setFillMode(FillMode mode)
164 if (mode == fillMode())
167 m_aspectRatioMode = Qt::AspectRatioMode(mode);
169 m_geometryDirty =
true;
172 Q_EMIT fillModeChanged(mode);
175void QQuickVideoOutput::_q_newFrame(QSize size)
179 size = qRotatedFrameSize(size, m_frameDisplayingRotation);
181 if (m_nativeSize != size) {
184 m_geometryDirty =
true;
186 setImplicitWidth(size.width());
187 setImplicitHeight(size.height());
189 Q_EMIT sourceRectChanged();
194void QQuickVideoOutput::_q_updateGeometry()
196 const QRectF rect(0, 0, width(), height());
197 const QRectF absoluteRect(x(), y(), width(), height());
199 if (!m_geometryDirty && m_lastRect == absoluteRect)
202 QRectF oldContentRect(m_contentRect);
204 m_geometryDirty =
false;
205 m_lastRect = absoluteRect;
207 const auto fill = m_aspectRatioMode;
208 if (m_nativeSize.isEmpty()) {
211 m_contentRect = rect;
212 }
else if (fill == Qt::IgnoreAspectRatio) {
213 m_contentRect = rect;
215 QSizeF scaled = m_nativeSize;
216 scaled.scale(rect.size(), fill);
218 m_contentRect = QRectF(QPointF(), scaled);
219 m_contentRect.moveCenter(rect.center());
224 if (m_contentRect != oldContentRect)
225 Q_EMIT contentRectChanged();
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253int QQuickVideoOutput::orientation()
const
255 return m_orientation;
258void QQuickVideoOutput::setOrientation(
int orientation)
261 if (orientation % 90)
265 if (m_orientation == orientation)
270 if (qVideoRotationFromDegrees(orientation - m_orientation) == QtVideo::Rotation::None) {
271 m_orientation = orientation;
272 Q_EMIT orientationChanged();
276 m_geometryDirty =
true;
280 bool oldAspect = qIsDefaultAspect(m_orientation);
281 bool newAspect = qIsDefaultAspect(orientation);
283 m_orientation = orientation;
286 QMutexLocker lock(&m_frameMutex);
287 m_frameDisplayingRotation = qNormalizedFrameTransformation(m_frame, m_orientation).rotation;
290 if (oldAspect != newAspect) {
291 m_nativeSize.transpose();
293 setImplicitWidth(m_nativeSize.width());
294 setImplicitHeight(m_nativeSize.height());
300 Q_EMIT orientationChanged();
304
305
306
307
308
309
310
311
312
313
314
315
316bool QQuickVideoOutput::mirrored()
const
321void QQuickVideoOutput::setMirrored(
bool mirrored)
323 if (m_mirrored == mirrored)
325 m_mirrored = mirrored;
328 Q_EMIT mirroredChanged();
332
333
334
335
336
337
338
339
340
341
342
343
344
345QRectF QQuickVideoOutput::contentRect()
const
347 return m_contentRect;
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366QQuickVideoOutput::EndOfStreamPolicy QQuickVideoOutput::endOfStreamPolicy()
const
368 return m_endOfStreamPolicy;
371void QQuickVideoOutput::setEndOfStreamPolicy(EndOfStreamPolicy policy)
373 if (m_endOfStreamPolicy == policy)
376 m_endOfStreamPolicy = policy;
377 Q_EMIT endOfStreamPolicyChanged(policy);
381
382
383
384
385
386
387
388
389void QQuickVideoOutput::clearOutput()
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411QRectF QQuickVideoOutput::sourceRect()
const
414 QSizeF size = m_nativeSize;
418 if (!qIsDefaultAspect(m_frameDisplayingRotation))
425 const QRectF viewport = adjustedViewport();
426 Q_ASSERT(viewport.size() == size);
427 return QRectF(viewport.topLeft(), size);
430void QQuickVideoOutput::geometryChange(
const QRectF &newGeometry,
const QRectF &oldGeometry)
432 Q_UNUSED(newGeometry);
433 Q_UNUSED(oldGeometry);
435 QQuickItem::geometryChange(newGeometry, oldGeometry);
444void QQuickVideoOutput::releaseResources()
448 QQuickItem::releaseResources();
451void QQuickVideoOutput::initRhiForSink()
453 QRhi *rhi = m_window ? QQuickWindowPrivate::get(m_window)->rhi :
nullptr;
457void QQuickVideoOutput::itemChange(QQuickItem::ItemChange change,
458 const QQuickItem::ItemChangeData &changeData)
460 if (change != QQuickItem::ItemSceneChange)
463 if (changeData.window == m_window)
466 disconnectWindowConnections();
467 m_window = changeData.window;
470 auto connectToWindow = [&](
auto signal,
auto function) {
471 connect(m_window, signal,
this, makeGuardedCall(std::move(function)),
472 Qt::DirectConnection);
476 connectToWindow(&QQuickWindow::sceneGraphInitialized, [
this] {
480 connectToWindow(&QQuickWindow::sceneGraphInvalidated, [
this] {
481 if (
auto texturePool = m_texturePool.lock())
482 texturePool->clearTextures();
483 m_sink->setRhi(
nullptr);
486 connectToWindow(&QQuickWindow::afterFrameEnd, [
this] {
487 if (
auto texturePool = m_texturePool.lock())
488 texturePool->onFrameEndInvoked();
494QSize QQuickVideoOutput::nativeSize()
const
496 return m_videoFormat.viewport().size();
499void QQuickVideoOutput::updateGeometry()
501 const QRectF viewport = m_videoFormat.viewport();
502 const QSizeF frameSize = m_videoFormat.frameSize();
503 const QRectF normalizedViewport(viewport.x() / frameSize.width(),
504 viewport.y() / frameSize.height(),
505 viewport.width() / frameSize.width(),
506 viewport.height() / frameSize.height());
507 const QRectF rect(0, 0, width(), height());
508 if (nativeSize().isEmpty()) {
509 m_renderedRect = rect;
510 m_sourceTextureRect = normalizedViewport;
511 }
else if (m_aspectRatioMode == Qt::IgnoreAspectRatio) {
512 m_renderedRect = rect;
513 m_sourceTextureRect = normalizedViewport;
514 }
else if (m_aspectRatioMode == Qt::KeepAspectRatio) {
515 m_sourceTextureRect = normalizedViewport;
516 m_renderedRect = contentRect();
517 }
else if (m_aspectRatioMode == Qt::KeepAspectRatioByExpanding) {
518 m_renderedRect = rect;
519 const qreal contentHeight = contentRect().height();
520 const qreal contentWidth = contentRect().width();
523 const qreal relativeOffsetLeft = -contentRect().left() / contentWidth;
524 const qreal relativeOffsetTop = -contentRect().top() / contentHeight;
525 const qreal relativeWidth = rect.width() / contentWidth;
526 const qreal relativeHeight = rect.height() / contentHeight;
529 const qreal totalOffsetLeft = normalizedViewport.x() + relativeOffsetLeft * normalizedViewport.width();
530 const qreal totalOffsetTop = normalizedViewport.y() + relativeOffsetTop * normalizedViewport.height();
531 const qreal totalWidth = normalizedViewport.width() * relativeWidth;
532 const qreal totalHeight = normalizedViewport.height() * relativeHeight;
534 if (qIsDefaultAspect(m_frameDisplayingRotation)) {
535 m_sourceTextureRect = QRectF(totalOffsetLeft, totalOffsetTop,
536 totalWidth, totalHeight);
538 m_sourceTextureRect = QRectF(totalOffsetTop, totalOffsetLeft,
539 totalHeight, totalWidth);
544QSGNode *QQuickVideoOutput::updatePaintNode(QSGNode *oldNode,
545 QQuickItem::UpdatePaintNodeData *data)
550 QSGVideoNode *videoNode =
static_cast<QSGVideoNode *>(oldNode);
552 QMutexLocker lock(&m_frameMutex);
554 if (m_frameChanged) {
555 if (videoNode && videoNode->pixelFormat() != m_frame.pixelFormat()) {
556 qCDebug(qLcVideo) <<
"updatePaintNode: deleting old video node because frame format changed";
561 if (!m_frame.isValid()) {
562 qCDebug(qLcVideo) <<
"updatePaintNode: no frames yet";
563 m_frameChanged =
false;
571 QRhi *rhi = m_window ? QQuickWindowPrivate::get(m_window)->rhi :
nullptr;
572 videoNode =
new QSGVideoNode(
this, m_videoFormat, rhi);
573 m_texturePool = videoNode->texturePool();
574 qCDebug(qLcVideo) <<
"updatePaintNode: Video node created.";
579 m_frameChanged =
false;
580 m_frame = QVideoFrame();
584 if (m_frameChanged) {
585 videoNode->setCurrentFrame(m_frame);
587 updateHdr(videoNode);
590 m_frameChanged =
false;
591 m_frame = QVideoFrame();
594 videoNode->setTexturedRectGeometry(
595 m_renderedRect, m_sourceTextureRect,
596 VideoTransformation{ qVideoRotationFromDegrees(orientation()), m_mirrored });
601void QQuickVideoOutput::updateHdr(QSGVideoNode *videoNode)
603 auto *videoOutputWindow = window();
604 if (!videoOutputWindow)
607 auto *swapChain = videoOutputWindow->swapChain();
611 const auto requiredSwapChainFormat = qGetRequiredSwapChainFormat(m_frame.surfaceFormat());
612 if (qShouldUpdateSwapChainFormat(swapChain, requiredSwapChainFormat)) {
613 auto *recreateSwapChainJob = QRunnable::create([swapChain, requiredSwapChainFormat]() {
614 swapChain->destroy();
615 swapChain->setFormat(requiredSwapChainFormat);
616 swapChain->createOrResize();
622 videoOutputWindow->scheduleRenderJob(recreateSwapChainJob, QQuickWindow::AfterSwapStage);
625 videoNode->setSurfaceFormat(swapChain->format());
626 videoNode->setHdrInfo(swapChain->hdrInfo());
629void QQuickVideoOutput::disconnectWindowConnections()
634 m_window->disconnect(
this);
637QRectF QQuickVideoOutput::adjustedViewport()
const
639 return m_videoFormat.viewport();
642void QQuickVideoOutput::setFrame(
const QVideoFrame &frame)
645 QMutexLocker lock(&m_frameMutex);
647 m_videoFormat = frame.surfaceFormat();
649 m_frameDisplayingRotation = qNormalizedFrameTransformation(frame, m_orientation).rotation;
650 m_frameChanged =
true;
653 QMetaObject::invokeMethod(
this, &QQuickVideoOutput::_q_newFrame, frame.size());
656std::optional<std::chrono::nanoseconds> QQuickVideoOutput::g_signalBackoff;
657void QQuickVideoOutput::setSignalBackoff(std::optional<std::chrono::nanoseconds> ns)
659 g_signalBackoff = ns;
664#include "moc_qquickvideooutput_p.cpp"
Combined button and popup list for selecting options.
bool qIsDefaultAspect(int o)