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#include <QtMultimedia/private/qmultimedia_ranges_p.h>
9
10#include <QtCore/qdir.h>
11#include <QtCore/qfileinfo.h>
12#include <QtCore/qloggingcategory.h>
13#include <QtCore/qoperatingsystemversion.h>
14#include <QtCore/qtemporaryfile.h>
15#include <QtCore/quuid.h>
16
17#include <cmath>
18#ifdef Q_OS_UNIX
19# include <cerrno>
20# include <sys/ioctl.h>
21#endif
22
23QT_BEGIN_NAMESPACE
24
25using namespace Qt::Literals;
26
27Q_STATIC_LOGGING_CATEGORY(qLcMultimediaUtils, "qt.multimedia.utils");
28
30{
31 int integral = int(floor(value));
32 value -= qreal(integral);
33 if (value == 0.)
34 return {integral, 1};
35
36 const int dMax = 1000;
37 int n1 = 0, d1 = 1, n2 = 1, d2 = 1;
38 qreal mid = 0.;
39 while (d1 <= dMax && d2 <= dMax) {
40 mid = qreal(n1 + n2) / (d1 + d2);
41
42 if (qAbs(value - mid) < 0.000001) {
43 break;
44 } else if (value > mid) {
45 n1 = n1 + n2;
46 d1 = d1 + d2;
47 } else {
48 n2 = n1 + n2;
49 d2 = d1 + d2;
50 }
51 }
52
53 if (d1 + d2 <= dMax)
54 return {n1 + n2 + integral * (d1 + d2), d1 + d2};
55 else if (d2 < d1)
56 return { n2 + integral * d2, d2 };
57 else
58 return { n1 + integral * d1, d1 };
59}
60
61QSize qCalculateFrameSize(QSize resolution, Fraction par)
62{
63 if (par.numerator == par.denominator || par.numerator < 1 || par.denominator < 1)
64 return resolution;
65
66 if (par.numerator > par.denominator)
67 return { resolution.width() * par.numerator / par.denominator, resolution.height() };
68
69 return { resolution.width(), resolution.height() * par.denominator / par.numerator };
70}
71
72QSize qRotatedFrameSize(QSize size, int rotation)
73{
74 Q_ASSERT(rotation % 90 == 0);
75 return rotation % 180 ? size.transposed() : size;
76}
77
78QSize qRotatedFramePresentationSize(const QVideoFrame &frame)
79{
80 // For mirrored frames the rotation can be +/- 180 degrees,
81 // but this inaccuracy doesn't impact on the result.
82 const int rotation = qToUnderlying(frame.rotation()) + qToUnderlying(frame.surfaceFormat().rotation());
83 return qRotatedFrameSize(frame.size(), rotation);
84}
85
86QSize qClosestSupportedResolution(QSize requested, QSpan<const QSize> supportedResolutions)
87{
88 if (requested.isEmpty())
89 return {};
90
91 namespace ranges = QtMultimediaPrivate::ranges;
92
93 if (ranges::contains(supportedResolutions, requested))
94 return requested;
95
96 const qreal requestedAspectRatio =
97 static_cast<qreal>(requested.width()) / static_cast<qreal>(requested.height());
98 const auto aspectRatioDiff = [&](QSize size) {
99 return qAbs((static_cast<qreal>(size.width()) / static_cast<qreal>(size.height()))
100 - requestedAspectRatio);
101 };
102 const auto pixelCountDiff = [&](QSize size) {
103 return qAbs((static_cast<qint64>(size.width()) * size.height())
104 - (static_cast<qint64>(requested.width()) * requested.height()));
105 };
106
107 const auto *const bestAspectRatio =
108 ranges::min_element(supportedResolutions, [&](QSize lhs, QSize rhs) {
109 return aspectRatioDiff(lhs) < aspectRatioDiff(rhs);
110 });
111
112 if (bestAspectRatio == supportedResolutions.end())
113 return {}; // No valid candidate
114
115 // Allow a small tolerance
116 constexpr qreal aspectRatioTolerance = 0.01;
117 const qreal maxAspectRatioDiff = aspectRatioDiff(*bestAspectRatio) + aspectRatioTolerance;
118
119 QSize closestResolution;
120 for (const QSize candidate : supportedResolutions) {
121 if (candidate.isEmpty())
122 continue;
123 if (aspectRatioDiff(candidate) > maxAspectRatioDiff)
124 continue;
125 if (!closestResolution.isValid()
126 || pixelCountDiff(candidate) < pixelCountDiff(closestResolution)) {
127 closestResolution = candidate;
128 }
129 }
130
131 return closestResolution;
132}
133
134QUrl qMediaFromUserInput(const QUrl &url)
135{
136 return QUrl::fromUserInput(url.toString(), QDir::currentPath(), QUrl::AssumeLocalFile);
137}
138
140{
141 static const bool autoHdrEnabled = qEnvironmentVariableIntValue("QT_MEDIA_AUTO_HDR");
142
143 return autoHdrEnabled;
144}
145
146QRhiSwapChain::Format qGetRequiredSwapChainFormat(const QVideoFrameFormat &format)
147{
148 constexpr auto sdrMaxLuminance = 100.0f;
149 const auto formatMaxLuminance = format.maxLuminance();
150
151 return formatMaxLuminance > sdrMaxLuminance ? QRhiSwapChain::HDRExtendedSrgbLinear
152 : QRhiSwapChain::SDR;
153}
154
155bool qShouldUpdateSwapChainFormat(QRhiSwapChain *swapChain,
156 QRhiSwapChain::Format requiredSwapChainFormat)
157{
158 if (!swapChain)
159 return false;
160
161 return qIsAutoHdrEnabled() && swapChain->format() != requiredSwapChainFormat
162 && swapChain->isFormatSupported(requiredSwapChainFormat);
163}
164
165Q_MULTIMEDIA_EXPORT VideoTransformation
166qNormalizedSurfaceTransformation(const QVideoFrameFormat &format)
167{
168 VideoTransformation result;
169 result.mirrorVertically(format.scanLineDirection() == QVideoFrameFormat::BottomToTop);
170 result.rotate(format.rotation());
171 result.mirrorHorizontally(format.isMirrored());
172 return result;
173}
174
176 VideoTransformation videoOutputTransformation)
177{
178 VideoTransformation result = qNormalizedSurfaceTransformation(frame.surfaceFormat());
179 result.rotate(frame.rotation());
180 result.mirrorHorizontally(frame.mirrored());
181 result.rotate(videoOutputTransformation.rotation);
182 result.mirrorHorizontally(videoOutputTransformation.mirroredHorizontallyAfterRotation);
183 return result;
184}
185
186// Only accepts inputs divisible by 90.
187// Invalid input returns no rotation.
189{
190 if (clockwiseDegrees % 90 != 0) {
191 qCWarning(qLcMultimediaUtils) << "qVideoRotationFromAngle(int) received "
192 "input not divisible by 90. Input was: "
193 << clockwiseDegrees;
194 return QtVideo::Rotation::None;
195 }
196
197 int newDegrees = clockwiseDegrees % 360;
198 // Adjust negative rotations into positive ones.
199 if (newDegrees < 0)
200 newDegrees += 360;
201 return static_cast<QtVideo::Rotation>(newDegrees);
202}
203
205{
206 const qreal absScaleX = std::hypot(matrix.m11(), matrix.m12());
207 const qreal absScaleY = std::hypot(matrix.m21(), matrix.m22());
208
209 if (qFuzzyIsNull(absScaleX) || qFuzzyIsNull(absScaleY))
210 return {}; // the matrix is malformed
211
212 qreal cos1 = matrix.m11() / absScaleX;
213 qreal sin1 = matrix.m12() / absScaleX;
214
215 // const: consider yScale > 0, as negative yScale can be compensated via negative xScale
216 const qreal sin2 = -matrix.m21() / absScaleY;
217 const qreal cos2 = matrix.m22() / absScaleY;
218
219 VideoTransformation result;
220
221 // try detecting the best pair option to detect mirroring
222
223 if (std::abs(cos1) + std::abs(cos2) > std::abs(sin1) + std::abs(sin2))
224 result.mirroredHorizontallyAfterRotation = std::signbit(cos1) != std::signbit(cos2);
225 else
226 result.mirroredHorizontallyAfterRotation = std::signbit(sin1) != std::signbit(sin2);
227
228 if (result.mirroredHorizontallyAfterRotation) {
229 cos1 *= -1;
230 sin1 *= -1;
231 }
232
233 const qreal maxDiscrepancy = 0.2;
234
235 if (std::abs(cos1 - cos2) > maxDiscrepancy || std::abs(sin1 - sin2) > maxDiscrepancy)
236 return {}; // the matrix is sheared too much, this is not supported currently
237
238 const qreal angle = atan2(sin1 + sin2, cos1 + cos2);
239 Q_ASSERT(!std::isnan(angle)); // checked upon scale validation
240
241 result.rotation = qVideoRotationFromDegrees(qRound(angle / M_PI_2) * 90);
242 return result;
243}
244
245namespace QtMultimediaPrivate {
246
247#ifdef Q_OS_UNIX
248int qt_safe_ioctl(int fd, int request, void *arg)
249{
250 int res;
251
252 do {
253 res = ::ioctl(fd, request, arg);
254 } while (res == -1 && EINTR == errno);
255
256 return res;
257}
258#endif
259
261 [[maybe_unused]] const QUrl &qrcUrl)
262{
263#if QT_CONFIG(temporaryfile)
264 std::unique_ptr<QTemporaryFile> tempFile;
265
266 if constexpr (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Android) {
267 tempFile = std::unique_ptr<QTemporaryFile>(QTemporaryFile::createNativeFile(qrcFile));
268 if (!tempFile)
269 return q23::unexpected(u"Failed to establish temporary file during playback"_s);
270
271 // Use a temp path derived from the original resource path
272 const QFileInfo mediaInfo(qrcUrl.path());
273 const QString targetDirPath = QDir::tempPath() + mediaInfo.path();
274 if (!QDir().mkpath(targetDirPath))
275 return q23::unexpected(
276 u"Could not create a temporary directory: %1"_s.arg(targetDirPath));
277
278 // Add a random suffix to avoid collisions
279 const QString baseName = mediaInfo.completeBaseName() + QLatin1Char('_')
280 + QUuid::createUuid().toString(QUuid::WithoutBraces);
281
282 // Keep extension suffix
283 const QString suffix = mediaInfo.suffix();
284
285 const QString newName = targetDirPath + QLatin1Char('/') + baseName
286 + (suffix.isEmpty() ? QString() : QLatin1Char('.') + suffix);
287
288 if (!tempFile->rename(newName))
289 return q23::unexpected(u"Could not rename temporary file to: %1"_s.arg(newName));
290 } else {
291 tempFile = std::make_unique<QTemporaryFile>();
292
293 // Preserve original file extension, some back ends might not load the file if it doesn't
294 // have an extension.
295 const QString suffix = QFileInfo(qrcFile).suffix();
296 if (!suffix.isEmpty())
297 tempFile->setFileTemplate(tempFile->fileTemplate() + QLatin1Char('.') + suffix);
298
299 if (!tempFile->open())
300 return q23::unexpected(tempFile->errorString());
301
302 // Copy the qrc data into the temporary file
303 char buffer[4096];
304 while (true) {
305 qint64 len = qrcFile.read(buffer, sizeof(buffer));
306 if (len < 1)
307 break;
308 tempFile->write(buffer, len);
309 }
310 tempFile->close();
311 }
312
313 QUrl url = QUrl::fromLocalFile(tempFile->fileName());
314 return QrcMedia{
315 std::move(url),
316 std::move(tempFile),
317 };
318#else
319 return q23::unexpected(u"Qt was built with -no-feature-temporaryfile: playback from "
320 "resource file is not supported!"_s);
321#endif
322}
323
324} // namespace QtMultimediaPrivate
325
326QT_END_NAMESPACE
\inmodule QtCore
Definition qsize.h:27
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)
QSize qClosestSupportedResolution(QSize requested, QSpan< const QSize > supportedResolutions)
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)