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 | |
24 | QT_BEGIN_NAMESPACE |
25 | |
26 | class QVideoFramePrivate : public QSharedData |
27 | { |
28 | public: |
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 | |
53 | private: |
54 | Q_DISABLE_COPY(QVideoFramePrivate) |
55 | }; |
56 | |
57 | QT_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 | */ |
99 | QVideoFrame::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 | */ |
109 | QVideoFrame::QVideoFrame(QAbstractVideoBuffer *buffer, const QVideoFrameFormat &format) |
110 | : d(new QVideoFramePrivate(format)) |
111 | { |
112 | d->buffer = buffer; |
113 | } |
114 | |
115 | /*! |
116 | \internal |
117 | */ |
118 | QAbstractVideoBuffer *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 | */ |
127 | QVideoFrame::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 | */ |
147 | QVideoFrame::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 | */ |
172 | QVideoFrame &QVideoFrame::operator =(const QVideoFrame &other) = default; |
173 | |
174 | /*! |
175 | \return \c true if this QVideoFrame and \a other reflect the same frame. |
176 | */ |
177 | bool 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 | */ |
186 | bool QVideoFrame::operator!=(const QVideoFrame &other) const |
187 | { |
188 | return d != other.d; |
189 | } |
190 | |
191 | /*! |
192 | Destroys a video frame. |
193 | */ |
194 | QVideoFrame::~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 | */ |
203 | bool 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 | */ |
211 | QVideoFrameFormat::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 | */ |
219 | QVideoFrameFormat 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 | */ |
230 | QVideoFrame::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 | */ |
238 | QSize QVideoFrame::size() const |
239 | { |
240 | return d ? d->format.frameSize() : QSize(); |
241 | } |
242 | |
243 | /*! |
244 | Returns the width of a video frame. |
245 | */ |
246 | int QVideoFrame::width() const |
247 | { |
248 | return size().width(); |
249 | } |
250 | |
251 | /*! |
252 | Returns the height of a video frame. |
253 | */ |
254 | int 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 | |
271 | bool 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 | */ |
290 | bool 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 | */ |
306 | bool 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 | */ |
316 | QVideoFrame::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 | */ |
351 | bool 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 | */ |
476 | void 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 | |
505 | int 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 | */ |
524 | uchar *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 | */ |
542 | const 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 | */ |
556 | int 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 | |
570 | int 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 | */ |
583 | qint64 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 | */ |
596 | void 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 | */ |
609 | qint64 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 | */ |
622 | void 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 | */ |
644 | void 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 | */ |
653 | QVideoFrame::RotationAngle QVideoFrame::rotationAngle() const |
654 | { |
655 | return d ? d->rotationAngle : Rotation0; |
656 | } |
657 | |
658 | /*! |
659 | Sets the \a mirrored flag for the frame. |
660 | */ |
661 | void 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 | */ |
670 | bool 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 | */ |
679 | QImage 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 | */ |
695 | QString 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 | */ |
703 | void 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 | */ |
718 | void 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 |
786 | static 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 | |
855 | QDebug 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 | |
868 | QDebug 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 | |
882 | QT_END_NAMESPACE |
883 | |
884 | |