Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qmultimediautils.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtMultimedia/qvideoframe.h>
7#include <QtMultimedia/qvideoframeformat.h>
8
9#include <QtCore/qdir.h>
10#include <QtCore/qfileinfo.h>
11#include <QtCore/qloggingcategory.h>
12#include <QtCore/qoperatingsystemversion.h>
13#include <QtCore/qtemporaryfile.h>
14#include <QtCore/quuid.h>
15
16#include <cmath>
17
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::Literals;
21
22Q_STATIC_LOGGING_CATEGORY(qLcMultimediaUtils, "qt.multimedia.utils");
23
25{
26 int integral = int(floor(value));
27 value -= qreal(integral);
28 if (value == 0.)
29 return {integral, 1};
30
31 const int dMax = 1000;
32 int n1 = 0, d1 = 1, n2 = 1, d2 = 1;
33 qreal mid = 0.;
34 while (d1 <= dMax && d2 <= dMax) {
35 mid = qreal(n1 + n2) / (d1 + d2);
36
37 if (qAbs(value - mid) < 0.000001) {
38 break;
39 } else if (value > mid) {
40 n1 = n1 + n2;
41 d1 = d1 + d2;
42 } else {
43 n2 = n1 + n2;
44 d2 = d1 + d2;
45 }
46 }
47
48 if (d1 + d2 <= dMax)
49 return {n1 + n2 + integral * (d1 + d2), d1 + d2};
50 else if (d2 < d1)
51 return { n2 + integral * d2, d2 };
52 else
53 return { n1 + integral * d1, d1 };
54}
55
56QSize qCalculateFrameSize(QSize resolution, Fraction par)
57{
58 if (par.numerator == par.denominator || par.numerator < 1 || par.denominator < 1)
59 return resolution;
60
61 if (par.numerator > par.denominator)
62 return { resolution.width() * par.numerator / par.denominator, resolution.height() };
63
64 return { resolution.width(), resolution.height() * par.denominator / par.numerator };
65}
66
67QSize qRotatedFrameSize(QSize size, int rotation)
68{
69 Q_ASSERT(rotation % 90 == 0);
70 return rotation % 180 ? size.transposed() : size;
71}
72
73QSize qRotatedFramePresentationSize(const QVideoFrame &frame)
74{
75 // For mirrored frames the rotation can be +/- 180 degrees,
76 // but this inaccuracy doesn't impact on the result.
77 const int rotation = qToUnderlying(frame.rotation()) + qToUnderlying(frame.surfaceFormat().rotation());
78 return qRotatedFrameSize(frame.size(), rotation);
79}
80
81QUrl qMediaFromUserInput(const QUrl &url)
82{
83 return QUrl::fromUserInput(url.toString(), QDir::currentPath(), QUrl::AssumeLocalFile);
84}
85
87{
88 static const bool autoHdrEnabled = qEnvironmentVariableIntValue("QT_MEDIA_AUTO_HDR");
89
90 return autoHdrEnabled;
91}
92
93QRhiSwapChain::Format qGetRequiredSwapChainFormat(const QVideoFrameFormat &format)
94{
95 constexpr auto sdrMaxLuminance = 100.0f;
96 const auto formatMaxLuminance = format.maxLuminance();
97
98 return formatMaxLuminance > sdrMaxLuminance ? QRhiSwapChain::HDRExtendedSrgbLinear
99 : QRhiSwapChain::SDR;
100}
101
102bool qShouldUpdateSwapChainFormat(QRhiSwapChain *swapChain,
103 QRhiSwapChain::Format requiredSwapChainFormat)
104{
105 if (!swapChain)
106 return false;
107
108 return qIsAutoHdrEnabled() && swapChain->format() != requiredSwapChainFormat
109 && swapChain->isFormatSupported(requiredSwapChainFormat);
110}
111
112Q_MULTIMEDIA_EXPORT VideoTransformation
113qNormalizedSurfaceTransformation(const QVideoFrameFormat &format)
114{
115 VideoTransformation result;
116 result.mirrorVertically(format.scanLineDirection() == QVideoFrameFormat::BottomToTop);
117 result.rotate(format.rotation());
118 result.mirrorHorizontally(format.isMirrored());
119 return result;
120}
121
123 VideoTransformation videoOutputTransformation)
124{
125 VideoTransformation result = qNormalizedSurfaceTransformation(frame.surfaceFormat());
126 result.rotate(frame.rotation());
127 result.mirrorHorizontally(frame.mirrored());
128 result.rotate(videoOutputTransformation.rotation);
129 result.mirrorHorizontally(videoOutputTransformation.mirroredHorizontallyAfterRotation);
130 return result;
131}
132
133// Only accepts inputs divisible by 90.
134// Invalid input returns no rotation.
136{
137 if (clockwiseDegrees % 90 != 0) {
138 qCWarning(qLcMultimediaUtils) << "qVideoRotationFromAngle(int) received "
139 "input not divisible by 90. Input was: "
140 << clockwiseDegrees;
141 return QtVideo::Rotation::None;
142 }
143
144 int newDegrees = clockwiseDegrees % 360;
145 // Adjust negative rotations into positive ones.
146 if (newDegrees < 0)
147 newDegrees += 360;
148 return static_cast<QtVideo::Rotation>(newDegrees);
149}
150
152{
153 const qreal absScaleX = std::hypot(matrix.m11(), matrix.m12());
154 const qreal absScaleY = std::hypot(matrix.m21(), matrix.m22());
155
156 if (qFuzzyIsNull(absScaleX) || qFuzzyIsNull(absScaleY))
157 return {}; // the matrix is malformed
158
159 qreal cos1 = matrix.m11() / absScaleX;
160 qreal sin1 = matrix.m12() / absScaleX;
161
162 // const: consider yScale > 0, as negative yScale can be compensated via negative xScale
163 const qreal sin2 = -matrix.m21() / absScaleY;
164 const qreal cos2 = matrix.m22() / absScaleY;
165
166 VideoTransformation result;
167
168 // try detecting the best pair option to detect mirroring
169
170 if (std::abs(cos1) + std::abs(cos2) > std::abs(sin1) + std::abs(sin2))
171 result.mirroredHorizontallyAfterRotation = std::signbit(cos1) != std::signbit(cos2);
172 else
173 result.mirroredHorizontallyAfterRotation = std::signbit(sin1) != std::signbit(sin2);
174
175 if (result.mirroredHorizontallyAfterRotation) {
176 cos1 *= -1;
177 sin1 *= -1;
178 }
179
180 const qreal maxDiscrepancy = 0.2;
181
182 if (std::abs(cos1 - cos2) > maxDiscrepancy || std::abs(sin1 - sin2) > maxDiscrepancy)
183 return {}; // the matrix is sheared too much, this is not supported currently
184
185 const qreal angle = atan2(sin1 + sin2, cos1 + cos2);
186 Q_ASSERT(!std::isnan(angle)); // checked upon scale validation
187
188 result.rotation = qVideoRotationFromDegrees(qRound(angle / M_PI_2) * 90);
189 return result;
190}
191
192namespace QtMultimediaPrivate {
194 [[maybe_unused]] const QUrl &qrcUrl)
195{
196#if QT_CONFIG(temporaryfile)
197 std::unique_ptr<QTemporaryFile> tempFile;
198
199 if constexpr (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Android) {
200 tempFile = std::unique_ptr<QTemporaryFile>(QTemporaryFile::createNativeFile(qrcFile));
201 if (!tempFile)
202 return q23::unexpected(u"Failed to establish temporary file during playback"_s);
203
204 // Use a temp path derived from the original resource path
205 const QFileInfo mediaInfo(qrcUrl.path());
206 const QString targetDirPath = QDir::tempPath() + mediaInfo.path();
207 if (!QDir().mkpath(targetDirPath))
208 return q23::unexpected(
209 u"Could not create a temporary directory: %1"_s.arg(targetDirPath));
210
211 // Add a random suffix to avoid collisions
212 const QString baseName = mediaInfo.completeBaseName() + QLatin1Char('_')
213 + QUuid::createUuid().toString(QUuid::WithoutBraces);
214
215 // Keep extension suffix
216 const QString suffix = mediaInfo.suffix();
217
218 const QString newName = targetDirPath + QLatin1Char('/') + baseName
219 + (suffix.isEmpty() ? QString() : QLatin1Char('.') + suffix);
220
221 if (!tempFile->rename(newName))
222 return q23::unexpected(u"Could not rename temporary file to: %1"_s.arg(newName));
223 } else {
224 tempFile = std::make_unique<QTemporaryFile>();
225
226 // Preserve original file extension, some back ends might not load the file if it doesn't
227 // have an extension.
228 const QString suffix = QFileInfo(qrcFile).suffix();
229 if (!suffix.isEmpty())
230 tempFile->setFileTemplate(tempFile->fileTemplate() + QLatin1Char('.') + suffix);
231
232 if (!tempFile->open())
233 return q23::unexpected(tempFile->errorString());
234
235 // Copy the qrc data into the temporary file
236 char buffer[4096];
237 while (true) {
238 qint64 len = qrcFile.read(buffer, sizeof(buffer));
239 if (len < 1)
240 break;
241 tempFile->write(buffer, len);
242 }
243 tempFile->close();
244 }
245
246 QUrl url = QUrl::fromLocalFile(tempFile->fileName());
247 return QrcMedia{
248 std::move(url),
249 std::move(tempFile),
250 };
251#else
252 return q23::unexpected(u"Qt was built with -no-feature-temporaryfile: playback from "
253 "resource file is not supported!"_s);
254#endif
255}
256
257} // namespace QtMultimediaPrivate
258
259QT_END_NAMESPACE
q23::expected< QrcMedia, QString > qCopyQrcToTemporaryFile(QFile &qrcFile, const QUrl &qrcUrl)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
#define M_PI_2
Definition qmath.h:205
QRhiSwapChain::Format qGetRequiredSwapChainFormat(const QVideoFrameFormat &format)
QUrl qMediaFromUserInput(const QUrl &url)
QSize qRotatedFrameSize(QSize size, int rotation)
Fraction qRealToFraction(qreal value)
bool qIsAutoHdrEnabled()
QSize qRotatedFramePresentationSize(const QVideoFrame &frame)
QtVideo::Rotation qVideoRotationFromDegrees(int clockwiseDegrees)
VideoTransformationOpt qVideoTransformationFromMatrix(const QTransform &matrix)
VideoTransformation qNormalizedFrameTransformation(const QVideoFrame &frame, VideoTransformation videoOutputTransformation)
QSize qCalculateFrameSize(QSize resolution, Fraction par)
bool qShouldUpdateSwapChainFormat(QRhiSwapChain *swapChain, QRhiSwapChain::Format requiredSwapChainFormat)