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
4#include "qvideoframe.h"
5
6#include "qvideotexturehelper_p.h"
7#include "qmemoryvideobuffer_p.h"
8#include "qvideoframeconverter_p.h"
9#include "qvideoframeformat.h"
10#include "qpainter.h"
11#include <qtextlayout.h>
12
13#include <qimage.h>
14#include <qmutex.h>
15#include <qpair.h>
16#include <qsize.h>
17#include <qvariant.h>
18#include <rhi/qrhi.h>
19
20#include <mutex>
21
22#include <QDebug>
23
24QT_BEGIN_NAMESPACE
25
26class QVideoFramePrivate : public QSharedData
27{
28public:
29 QVideoFramePrivate() = default;
30 QVideoFramePrivate(const QVideoFrameFormat &format)
31 : format(format)
32 {
33 }
34
35 ~QVideoFramePrivate()
36 {
37 delete buffer;
38 }
39
40 qint64 startTime = -1;
41 qint64 endTime = -1;
42 QAbstractVideoBuffer::MapData mapData;
43 QVideoFrameFormat format;
44 QAbstractVideoBuffer *buffer = nullptr;
45 int mappedCount = 0;
46 QMutex mapMutex;
47 QString subtitleText;
48 QVideoFrame::RotationAngle rotationAngle = QVideoFrame::Rotation0;
49 bool mirrored = false;
50 QImage image;
51 std::once_flag imageOnceFlag;
52
53private:
54 Q_DISABLE_COPY(QVideoFramePrivate)
55};
56
57QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QVideoFramePrivate);
58
59/*!
60 \class QVideoFrame
61 \brief The QVideoFrame class represents a frame of video data.
62 \inmodule QtMultimedia
63
64 \ingroup multimedia
65 \ingroup multimedia_video
66
67 A QVideoFrame encapsulates the pixel data of a video frame, and information about the frame.
68
69 Video frames can come from several places - decoded \l {QMediaPlayer}{media}, a
70 \l {QCamera}{camera}, or generated programmatically. The way pixels are described in these
71 frames can vary greatly, and some pixel formats offer greater compression opportunities at
72 the expense of ease of use.
73
74 The pixel contents of a video frame can be mapped to memory using the map() function. After
75 a successful call to map(), the video data can be accessed through various functions. Some of
76 the YUV pixel formats provide the data in several planes. The planeCount() method will return
77 the amount of planes that being used.
78
79 While mapped, the video data of each plane can accessed using the bits() function, which
80 returns a pointer to a buffer. The size of this buffer is given by the mappedBytes() function,
81 and the size of each line is given by bytesPerLine(). The return value of the handle()
82 function may also be used to access frame data using the internal buffer's native APIs
83 (for example - an OpenGL texture handle).
84
85 A video frame can also have timestamp information associated with it. These timestamps can be
86 used to determine when to start and stop displaying the frame.
87
88 QVideoFrame objects can consume a significant amount of memory or system resources and
89 should not be held for longer than required by the application.
90
91 \note Since video frames can be expensive to copy, QVideoFrame is explicitly shared, so any
92 change made to a video frame will also apply to any copies.
93*/
94
95
96/*!
97 Constructs a null video frame.
98*/
99QVideoFrame::QVideoFrame()
100{
101}
102
103/*!
104 \internal
105 Constructs a video frame from a \a buffer with the given pixel \a format and \a size in pixels.
106
107 \note This doesn't increment the reference count of the video buffer.
108*/
109QVideoFrame::QVideoFrame(QAbstractVideoBuffer *buffer, const QVideoFrameFormat &format)
110 : d(new QVideoFramePrivate(format))
111{
112 d->buffer = buffer;
113}
114
115/*!
116 \internal
117*/
118QAbstractVideoBuffer *QVideoFrame::videoBuffer() const
119{
120 return d ? d->buffer : nullptr;
121}
122
123/*!
124 Constructs a video frame of the given pixel \a format.
125
126*/
127QVideoFrame::QVideoFrame(const QVideoFrameFormat &format)
128 : d(new QVideoFramePrivate(format))
129{
130 auto *textureDescription = QVideoTextureHelper::textureDescription(format: format.pixelFormat());
131 qsizetype bytes = textureDescription->bytesForSize(s: format.frameSize());
132 if (bytes > 0) {
133 QByteArray data;
134 data.resize(size: bytes);
135
136 // Check the memory was successfully allocated.
137 if (!data.isEmpty())
138 d->buffer = new QMemoryVideoBuffer(data, textureDescription->strideForWidth(width: format.frameWidth()));
139 }
140}
141
142/*!
143 Constructs a shallow copy of \a other. Since QVideoFrame is
144 explicitly shared, these two instances will reflect the same frame.
145
146*/
147QVideoFrame::QVideoFrame(const QVideoFrame &other) = default;
148
149/*!
150 \fn QVideoFrame::QVideoFrame(QVideoFrame &&other)
151
152 Constructs a QVideoFrame by moving from \a other.
153*/
154
155/*!
156 \fn void QVideoFrame::swap(QVideoFrame &other) noexcept
157
158 Swaps the current video frame with \a other.
159*/
160
161/*!
162 \fn QVideoFrame &QVideoFrame::operator=(QVideoFrame &&other)
163
164 Moves \a other into this QVideoFrame.
165*/
166
167/*!
168 Assigns the contents of \a other to this video frame. Since QVideoFrame is
169 explicitly shared, these two instances will reflect the same frame.
170
171*/
172QVideoFrame &QVideoFrame::operator =(const QVideoFrame &other) = default;
173
174/*!
175 \return \c true if this QVideoFrame and \a other reflect the same frame.
176 */
177bool QVideoFrame::operator==(const QVideoFrame &other) const
178{
179 // Due to explicit sharing we just compare the QSharedData which in turn compares the pointers.
180 return d == other.d;
181}
182
183/*!
184 \return \c true if this QVideoFrame and \a other do not reflect the same frame.
185 */
186bool QVideoFrame::operator!=(const QVideoFrame &other) const
187{
188 return d != other.d;
189}
190
191/*!
192 Destroys a video frame.
193*/
194QVideoFrame::~QVideoFrame() = default;
195
196/*!
197 Identifies whether a video frame is valid.
198
199 An invalid frame has no video buffer associated with it.
200
201 Returns true if the frame is valid, and false if it is not.
202*/
203bool QVideoFrame::isValid() const
204{
205 return (d && d->buffer) && d->format.pixelFormat() != QVideoFrameFormat::Format_Invalid;
206}
207
208/*!
209 Returns the pixel format of this video frame.
210*/
211QVideoFrameFormat::PixelFormat QVideoFrame::pixelFormat() const
212{
213 return d ? d->format.pixelFormat() : QVideoFrameFormat::Format_Invalid;
214}
215
216/*!
217 Returns the surface format of this video frame.
218*/
219QVideoFrameFormat QVideoFrame::surfaceFormat() const
220{
221 return d ? d->format : QVideoFrameFormat{};
222}
223
224/*!
225 Returns the type of a video frame's handle.
226
227 The handle type could either be NoHandle, meaning that the frame is memory
228 based, or a RHI texture.
229*/
230QVideoFrame::HandleType QVideoFrame::handleType() const
231{
232 return (d && d->buffer) ? d->buffer->handleType() : QVideoFrame::NoHandle;
233}
234
235/*!
236 Returns the dimensions of a video frame.
237*/
238QSize QVideoFrame::size() const
239{
240 return d ? d->format.frameSize() : QSize();
241}
242
243/*!
244 Returns the width of a video frame.
245*/
246int QVideoFrame::width() const
247{
248 return size().width();
249}
250
251/*!
252 Returns the height of a video frame.
253*/
254int QVideoFrame::height() const
255{
256 return size().height();
257}
258
259/*!
260 Identifies if a video frame's contents are currently mapped to system memory.
261
262 This is a convenience function which checks that the \l {QVideoFrame::MapMode}{MapMode}
263 of the frame is not equal to QVideoFrame::NotMapped.
264
265 Returns true if the contents of the video frame are mapped to system memory, and false
266 otherwise.
267
268 \sa mapMode(), QVideoFrame::MapMode
269*/
270
271bool QVideoFrame::isMapped() const
272{
273 return d && d->buffer && d->buffer->mapMode() != QVideoFrame::NotMapped;
274}
275
276/*!
277 Identifies if the mapped contents of a video frame will be persisted when the frame is unmapped.
278
279 This is a convenience function which checks if the \l {QVideoFrame::MapMode}{MapMode}
280 contains the QVideoFrame::WriteOnly flag.
281
282 Returns true if the video frame will be updated when unmapped, and false otherwise.
283
284 \note The result of altering the data of a frame that is mapped in read-only mode is undefined.
285 Depending on the buffer implementation the changes may be persisted, or worse alter a shared
286 buffer.
287
288 \sa mapMode(), QVideoFrame::MapMode
289*/
290bool QVideoFrame::isWritable() const
291{
292 return d && d->buffer && (d->buffer->mapMode() & QVideoFrame::WriteOnly);
293}
294
295/*!
296 Identifies if the mapped contents of a video frame were read from the frame when it was mapped.
297
298 This is a convenience function which checks if the \l {QVideoFrame::MapMode}{MapMode}
299 contains the QVideoFrame::WriteOnly flag.
300
301 Returns true if the contents of the mapped memory were read from the video frame, and false
302 otherwise.
303
304 \sa mapMode(), QVideoFrame::MapMode
305*/
306bool QVideoFrame::isReadable() const
307{
308 return d && d->buffer && (d->buffer->mapMode() & QVideoFrame::ReadOnly);
309}
310
311/*!
312 Returns the mode a video frame was mapped to system memory in.
313
314 \sa map(), QVideoFrame::MapMode
315*/
316QVideoFrame::MapMode QVideoFrame::mapMode() const
317{
318 return (d && d->buffer) ? d->buffer->mapMode() : QVideoFrame::NotMapped;
319}
320
321/*!
322 Maps the contents of a video frame to system (CPU addressable) memory.
323
324 In some cases the video frame data might be stored in video memory or otherwise inaccessible
325 memory, so it is necessary to map a frame before accessing the pixel data. This may involve
326 copying the contents around, so avoid mapping and unmapping unless required.
327
328 The map \a mode indicates whether the contents of the mapped memory should be read from and/or
329 written to the frame. If the map mode includes the \c QVideoFrame::ReadOnly flag the
330 mapped memory will be populated with the content of the video frame when initially mapped. If the map
331 mode includes the \c QVideoFrame::WriteOnly flag the content of the possibly modified
332 mapped memory will be written back to the frame when unmapped.
333
334 While mapped the contents of a video frame can be accessed directly through the pointer returned
335 by the bits() function.
336
337 When access to the data is no longer needed, be sure to call the unmap() function to release the
338 mapped memory and possibly update the video frame contents.
339
340 If the video frame has been mapped in read only mode, it is permissible to map it
341 multiple times in read only mode (and unmap it a corresponding number of times). In all
342 other cases it is necessary to unmap the frame first before mapping a second time.
343
344 \note Writing to memory that is mapped as read-only is undefined, and may result in changes
345 to shared data or crashes.
346
347 Returns true if the frame was mapped to memory in the given \a mode and false otherwise.
348
349 \sa unmap(), mapMode(), bits()
350*/
351bool QVideoFrame::map(QVideoFrame::MapMode mode)
352{
353
354 if (!d || !d->buffer)
355 return false;
356
357 QMutexLocker lock(&d->mapMutex);
358 if (mode == QVideoFrame::NotMapped)
359 return false;
360
361 if (d->mappedCount > 0) {
362 //it's allowed to map the video frame multiple times in read only mode
363 if (d->buffer->mapMode() == QVideoFrame::ReadOnly
364 && mode == QVideoFrame::ReadOnly) {
365 d->mappedCount++;
366 return true;
367 }
368
369 return false;
370 }
371
372 Q_ASSERT(d->mapData.data[0] == nullptr);
373 Q_ASSERT(d->mapData.bytesPerLine[0] == 0);
374 Q_ASSERT(d->mapData.nPlanes == 0);
375 Q_ASSERT(d->mapData.size[0] == 0);
376
377 d->mapData = d->buffer->map(mode);
378 if (d->mapData.nPlanes == 0)
379 return false;
380
381 if (d->mapData.nPlanes == 1) {
382 auto pixelFmt = d->format.pixelFormat();
383 // If the plane count is 1 derive the additional planes for planar formats.
384 switch (pixelFmt) {
385 case QVideoFrameFormat::Format_Invalid:
386 case QVideoFrameFormat::Format_ARGB8888:
387 case QVideoFrameFormat::Format_ARGB8888_Premultiplied:
388 case QVideoFrameFormat::Format_XRGB8888:
389 case QVideoFrameFormat::Format_BGRA8888:
390 case QVideoFrameFormat::Format_BGRA8888_Premultiplied:
391 case QVideoFrameFormat::Format_BGRX8888:
392 case QVideoFrameFormat::Format_ABGR8888:
393 case QVideoFrameFormat::Format_XBGR8888:
394 case QVideoFrameFormat::Format_RGBA8888:
395 case QVideoFrameFormat::Format_RGBX8888:
396 case QVideoFrameFormat::Format_AYUV:
397 case QVideoFrameFormat::Format_AYUV_Premultiplied:
398 case QVideoFrameFormat::Format_UYVY:
399 case QVideoFrameFormat::Format_YUYV:
400 case QVideoFrameFormat::Format_Y8:
401 case QVideoFrameFormat::Format_Y16:
402 case QVideoFrameFormat::Format_Jpeg:
403 case QVideoFrameFormat::Format_SamplerExternalOES:
404 case QVideoFrameFormat::Format_SamplerRect:
405 // Single plane or opaque format.
406 break;
407 case QVideoFrameFormat::Format_YUV420P:
408 case QVideoFrameFormat::Format_YUV420P10:
409 case QVideoFrameFormat::Format_YUV422P:
410 case QVideoFrameFormat::Format_YV12: {
411 // The UV stride is usually half the Y stride and is 32-bit aligned.
412 // However it's not always the case, at least on Windows where the
413 // UV planes are sometimes not aligned.
414 // We calculate the stride using the UV byte count to always
415 // have a correct stride.
416 const int height = this->height();
417 const int yStride = d->mapData.bytesPerLine[0];
418 const int uvHeight = pixelFmt == QVideoFrameFormat::Format_YUV422P ? height : height / 2;
419 const int uvStride = (d->mapData.size[0] - (yStride * height)) / uvHeight / 2;
420
421 // Three planes, the second and third vertically (and horizontally for other than Format_YUV422P formats) subsampled.
422 d->mapData.nPlanes = 3;
423 d->mapData.bytesPerLine[2] = d->mapData.bytesPerLine[1] = uvStride;
424 d->mapData.size[0] = yStride * height;
425 d->mapData.size[1] = uvStride * uvHeight;
426 d->mapData.size[2] = uvStride * uvHeight;
427 d->mapData.data[1] = d->mapData.data[0] + d->mapData.size[0];
428 d->mapData.data[2] = d->mapData.data[1] + d->mapData.size[1];
429 break;
430 }
431 case QVideoFrameFormat::Format_NV12:
432 case QVideoFrameFormat::Format_NV21:
433 case QVideoFrameFormat::Format_IMC2:
434 case QVideoFrameFormat::Format_IMC4:
435 case QVideoFrameFormat::Format_P010:
436 case QVideoFrameFormat::Format_P016: {
437 // Semi planar, Full resolution Y plane with interleaved subsampled U and V planes.
438 d->mapData.nPlanes = 2;
439 d->mapData.bytesPerLine[1] = d->mapData.bytesPerLine[0];
440 int size = d->mapData.size[0];
441 d->mapData.size[0] = (d->mapData.bytesPerLine[0] * height());
442 d->mapData.size[1] = size - d->mapData.size[0];
443 d->mapData.data[1] = d->mapData.data[0] + d->mapData.size[0];
444 break;
445 }
446 case QVideoFrameFormat::Format_IMC1:
447 case QVideoFrameFormat::Format_IMC3: {
448 // Three planes, the second and third vertically and horizontally subsumpled,
449 // but with lines padded to the width of the first plane.
450 d->mapData.nPlanes = 3;
451 d->mapData.bytesPerLine[2] = d->mapData.bytesPerLine[1] = d->mapData.bytesPerLine[0];
452 d->mapData.size[0] = (d->mapData.bytesPerLine[0] * height());
453 d->mapData.size[1] = (d->mapData.bytesPerLine[0] * height() / 2);
454 d->mapData.size[2] = (d->mapData.bytesPerLine[0] * height() / 2);
455 d->mapData.data[1] = d->mapData.data[0] + d->mapData.size[0];
456 d->mapData.data[2] = d->mapData.data[1] + d->mapData.size[1];
457 break;
458 }
459 }
460 }
461
462 d->mappedCount++;
463 return true;
464}
465
466/*!
467 Releases the memory mapped by the map() function.
468
469 If the \l {QVideoFrame::MapMode}{MapMode} included the QVideoFrame::WriteOnly
470 flag this will persist the current content of the mapped memory to the video frame.
471
472 unmap() should not be called if map() function failed.
473
474 \sa map()
475*/
476void QVideoFrame::unmap()
477{
478 if (!d || !d->buffer)
479 return;
480
481 QMutexLocker lock(&d->mapMutex);
482
483 if (d->mappedCount == 0) {
484 qWarning() << "QVideoFrame::unmap() was called more times then QVideoFrame::map()";
485 return;
486 }
487
488 d->mappedCount--;
489
490 if (d->mappedCount == 0) {
491 d->mapData = {};
492 d->buffer->unmap();
493 }
494}
495
496/*!
497 Returns the number of bytes in a scan line of a \a plane.
498
499 This value is only valid while the frame data is \l {map()}{mapped}.
500
501 \sa bits(), map(), mappedBytes(), planeCount()
502 \since 5.4
503*/
504
505int QVideoFrame::bytesPerLine(int plane) const
506{
507 if (!d)
508 return 0;
509 return plane >= 0 && plane < d->mapData.nPlanes ? d->mapData.bytesPerLine[plane] : 0;
510}
511
512/*!
513 Returns a pointer to the start of the frame data buffer for a \a plane.
514
515 This value is only valid while the frame data is \l {map()}{mapped}.
516
517 Changes made to data accessed via this pointer (when mapped with write access)
518 are only guaranteed to have been persisted when unmap() is called and when the
519 buffer has been mapped for writing.
520
521 \sa map(), mappedBytes(), bytesPerLine(), planeCount()
522 \since 5.4
523*/
524uchar *QVideoFrame::bits(int plane)
525{
526 if (!d)
527 return nullptr;
528 return plane >= 0 && plane < d->mapData.nPlanes ? d->mapData.data[plane] : nullptr;
529}
530
531/*!
532 Returns a pointer to the start of the frame data buffer for a \a plane.
533
534 This value is only valid while the frame data is \l {map()}{mapped}.
535
536 If the buffer was not mapped with read access, the contents of this
537 buffer will initially be uninitialized.
538
539 \sa map(), mappedBytes(), bytesPerLine(), planeCount()
540 \since 5.4
541*/
542const uchar *QVideoFrame::bits(int plane) const
543{
544 if (!d)
545 return nullptr;
546 return plane >= 0 && plane < d->mapData.nPlanes ? d->mapData.data[plane] : nullptr;
547}
548
549/*!
550 Returns the number of bytes occupied by plane \a plane of the mapped frame data.
551
552 This value is only valid while the frame data is \l {map()}{mapped}.
553
554 \sa map()
555*/
556int QVideoFrame::mappedBytes(int plane) const
557{
558 if (!d)
559 return 0;
560 return plane >= 0 && plane < d->mapData.nPlanes ? d->mapData.size[plane] : 0;
561}
562
563/*!
564 Returns the number of planes in the video frame.
565
566 \sa map()
567 \since 5.4
568*/
569
570int QVideoFrame::planeCount() const
571{
572 if (!d)
573 return 0;
574 return d->format.planeCount();
575}
576
577/*!
578 Returns the presentation time (in microseconds) when the frame should be displayed.
579
580 An invalid time is represented as -1.
581
582*/
583qint64 QVideoFrame::startTime() const
584{
585 if (!d)
586 return -1;
587 return d->startTime;
588}
589
590/*!
591 Sets the presentation \a time (in microseconds) when the frame should initially be displayed.
592
593 An invalid time is represented as -1.
594
595*/
596void QVideoFrame::setStartTime(qint64 time)
597{
598 if (!d)
599 return;
600 d->startTime = time;
601}
602
603/*!
604 Returns the presentation time (in microseconds) when a frame should stop being displayed.
605
606 An invalid time is represented as -1.
607
608*/
609qint64 QVideoFrame::endTime() const
610{
611 if (!d)
612 return -1;
613 return d->endTime;
614}
615
616/*!
617 Sets the presentation \a time (in microseconds) when a frame should stop being displayed.
618
619 An invalid time is represented as -1.
620
621*/
622void QVideoFrame::setEndTime(qint64 time)
623{
624 if (!d)
625 return;
626 d->endTime = time;
627}
628
629/*!
630 \enum QVideoFrame::RotationAngle
631
632 The angle of the clockwise rotation that should be applied to a video
633 frame before displaying.
634
635 \value Rotation0 No rotation required, the frame has correct orientation
636 \value Rotation90 The frame should be rotated by 90 degrees
637 \value Rotation180 The frame should be rotated by 180 degrees
638 \value Rotation270 The frame should be rotated by 270 degrees
639*/
640
641/*!
642 Sets the \a angle the frame should be rotated clockwise before displaying.
643*/
644void QVideoFrame::setRotationAngle(QVideoFrame::RotationAngle angle)
645{
646 if (d)
647 d->rotationAngle = angle;
648}
649
650/*!
651 Returns the angle the frame should be rotated clockwise before displaying.
652 */
653QVideoFrame::RotationAngle QVideoFrame::rotationAngle() const
654{
655 return d ? d->rotationAngle : Rotation0;
656}
657
658/*!
659 Sets the \a mirrored flag for the frame.
660*/
661void QVideoFrame::setMirrored(bool mirrored)
662{
663 if (d)
664 d->mirrored = mirrored;
665}
666
667/*!
668 Returns whether the frame should be mirrored before displaying.
669*/
670bool QVideoFrame::mirrored() const
671{
672 return d && d->mirrored;
673}
674
675/*!
676 Based on the pixel format converts current video frame to image.
677 \since 5.15
678*/
679QImage QVideoFrame::toImage() const
680{
681 if (!isValid())
682 return {};
683
684 std::call_once(once&: d->imageOnceFlag, f: [this]() {
685 const bool mirrorY = surfaceFormat().scanLineDirection() != QVideoFrameFormat::TopToBottom;
686 d->image = qImageFromVideoFrame(frame: *this, rotation: rotationAngle(), mirrorX: mirrored(), mirrorY);
687 });
688
689 return d->image;
690}
691
692/*!
693 Returns the subtitle text that should be rendered together with this video frame.
694*/
695QString QVideoFrame::subtitleText() const
696{
697 return d ? d->subtitleText : QString();
698}
699
700/*!
701 Sets the subtitle text that should be rendered together with this video frame to \a text.
702*/
703void QVideoFrame::setSubtitleText(const QString &text)
704{
705 if (!d)
706 return;
707 d->subtitleText = text;
708}
709
710/*!
711 Uses a QPainter, \a{painter}, to render this QVideoFrame to \a rect.
712 The PaintOptions \a options can be used to specify a background color and
713 how \a rect should be filled with the video.
714
715 \note that rendering will usually happen without hardware acceleration when
716 using this method.
717*/
718void QVideoFrame::paint(QPainter *painter, const QRectF &rect, const PaintOptions &options)
719{
720 if (!isValid()) {
721 painter->fillRect(rect, color: options.backgroundColor);
722 return;
723 }
724
725 QRectF targetRect = rect;
726 QSizeF size = this->size();
727 if (rotationAngle() % 180)
728 size.transpose();
729
730 size.scale(s: targetRect.size(), mode: options.aspectRatioMode);
731
732 if (options.aspectRatioMode == Qt::KeepAspectRatio) {
733 targetRect = QRect(0, 0, size.width(), size.height());
734 targetRect.moveCenter(p: rect.center());
735 // we might not be drawing every pixel, fill the leftovers black
736 if (options.backgroundColor != Qt::transparent && rect != targetRect) {
737 if (targetRect.top() > rect.top()) {
738 QRectF top(rect.left(), rect.top(), rect.width(), targetRect.top() - rect.top());
739 painter->fillRect(r: top, c: Qt::black);
740 }
741 if (targetRect.left() > rect.left()) {
742 QRectF top(rect.left(), targetRect.top(), targetRect.left() - rect.left(), targetRect.height());
743 painter->fillRect(r: top, c: Qt::black);
744 }
745 if (targetRect.right() < rect.right()) {
746 QRectF top(targetRect.right(), targetRect.top(), rect.right() - targetRect.right(), targetRect.height());
747 painter->fillRect(r: top, c: Qt::black);
748 }
749 if (targetRect.bottom() < rect.bottom()) {
750 QRectF top(rect.left(), targetRect.bottom(), rect.width(), rect.bottom() - targetRect.bottom());
751 painter->fillRect(r: top, c: Qt::black);
752 }
753 }
754 }
755
756 if (map(mode: QVideoFrame::ReadOnly)) {
757 const QTransform oldTransform = painter->transform();
758 QTransform transform = oldTransform;
759 transform.translate(dx: targetRect.center().x() - size.width()/2,
760 dy: targetRect.center().y() - size.height()/2);
761 painter->setTransform(transform);
762 QImage image = toImage();
763 painter->drawImage(targetRect: {{}, size}, image, sourceRect: {{},image.size()});
764 painter->setTransform(transform: oldTransform);
765
766 unmap();
767 } else if (isValid()) {
768 // #### error handling
769 } else {
770 painter->fillRect(r: rect, c: Qt::black);
771 }
772
773 if ((options.paintFlags & PaintOptions::DontDrawSubtitles) || d->subtitleText.isEmpty())
774 return;
775
776 // draw subtitles
777 auto text = d->subtitleText;
778 text.replace(before: QLatin1Char('\n'), after: QChar::LineSeparator);
779
780 QVideoTextureHelper::SubtitleLayout layout;
781 layout.update(frameSize: targetRect.size().toSize(), text: this->subtitleText());
782 layout.draw(painter, translate: targetRect.topLeft());
783}
784
785#ifndef QT_NO_DEBUG_STREAM
786static QString qFormatTimeStamps(qint64 start, qint64 end)
787{
788 // Early out for invalid.
789 if (start < 0)
790 return QLatin1String("[no timestamp]");
791
792 bool onlyOne = (start == end);
793
794 // [hh:]mm:ss.ms
795 const int s_millis = start % 1000000;
796 start /= 1000000;
797 const int s_seconds = start % 60;
798 start /= 60;
799 const int s_minutes = start % 60;
800 start /= 60;
801
802 if (onlyOne) {
803 if (start > 0)
804 return QString::fromLatin1(ba: "@%1:%2:%3.%4")
805 .arg(a: start, fieldwidth: 1, base: 10, fillChar: QLatin1Char('0'))
806 .arg(a: s_minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
807 .arg(a: s_seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
808 .arg(a: s_millis, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
809 return QString::fromLatin1(ba: "@%1:%2.%3")
810 .arg(a: s_minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
811 .arg(a: s_seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
812 .arg(a: s_millis, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
813 }
814
815 if (end == -1) {
816 // Similar to start-start, except it means keep displaying it?
817 if (start > 0)
818 return QString::fromLatin1(ba: "%1:%2:%3.%4 - forever")
819 .arg(a: start, fieldwidth: 1, base: 10, fillChar: QLatin1Char('0'))
820 .arg(a: s_minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
821 .arg(a: s_seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
822 .arg(a: s_millis, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
823 return QString::fromLatin1(ba: "%1:%2.%3 - forever")
824 .arg(a: s_minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
825 .arg(a: s_seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
826 .arg(a: s_millis, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
827 }
828
829 const int e_millis = end % 1000000;
830 end /= 1000000;
831 const int e_seconds = end % 60;
832 end /= 60;
833 const int e_minutes = end % 60;
834 end /= 60;
835
836 if (start > 0 || end > 0)
837 return QString::fromLatin1(ba: "%1:%2:%3.%4 - %5:%6:%7.%8")
838 .arg(a: start, fieldwidth: 1, base: 10, fillChar: QLatin1Char('0'))
839 .arg(a: s_minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
840 .arg(a: s_seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
841 .arg(a: s_millis, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
842 .arg(a: end, fieldwidth: 1, base: 10, fillChar: QLatin1Char('0'))
843 .arg(a: e_minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
844 .arg(a: e_seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
845 .arg(a: e_millis, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
846 return QString::fromLatin1(ba: "%1:%2.%3 - %4:%5.%6")
847 .arg(a: s_minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
848 .arg(a: s_seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
849 .arg(a: s_millis, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
850 .arg(a: e_minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
851 .arg(a: e_seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
852 .arg(a: e_millis, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
853}
854
855QDebug operator<<(QDebug dbg, QVideoFrame::HandleType type)
856{
857 QDebugStateSaver saver(dbg);
858 dbg.nospace();
859 switch (type) {
860 case QVideoFrame::NoHandle:
861 return dbg << "NoHandle";
862 case QVideoFrame::RhiTextureHandle:
863 return dbg << "RhiTextureHandle";
864 }
865 return dbg;
866}
867
868QDebug operator<<(QDebug dbg, const QVideoFrame& f)
869{
870 QDebugStateSaver saver(dbg);
871 dbg.nospace();
872 dbg << "QVideoFrame(" << f.size() << ", "
873 << f.pixelFormat() << ", "
874 << f.handleType() << ", "
875 << f.mapMode() << ", "
876 << qFormatTimeStamps(start: f.startTime(), end: f.endTime()).toLatin1().constData();
877 dbg << ')';
878 return dbg;
879}
880#endif
881
882QT_END_NAMESPACE
883
884

source code of qtmultimedia/src/multimedia/video/qvideoframe.cpp