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
qavfsamplebufferdelegate.mm
Go to the documentation of this file.
1// Copyright (C) 2022 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 <QtFFmpegMediaPluginImpl/private/qavfsamplebufferdelegate_p.h>
5
6#define AVMediaType XAVMediaType
7extern "C" {
8#include <libavutil/hwcontext_videotoolbox.h>
9} // extern "C"
10#undef AVMediaType
11
12#include <QtCore/qloggingcategory.h>
13#include <QtCore/qsize.h>
14
15#include <QtMultimedia/private/qavfhelpers_p.h>
16#include <QtMultimedia/private/qvideoframe_p.h>
17
18#include <QtFFmpegMediaPluginImpl/private/qcvimagevideobuffer_p.h>
19#include <QtFFmpegMediaPluginImpl/private/qffmpegdarwinhwframehelpers_p.h>
20#define AVMediaType XAVMediaType
21#include <QtFFmpegMediaPluginImpl/private/qffmpegvideobuffer_p.h>
22#include <QtFFmpegMediaPluginImpl/private/qffmpeghwaccel_p.h>
23#undef AVMediaType
24
25#include <chrono>
26#include <optional>
27
28Q_STATIC_LOGGING_CATEGORY(qLcAvfSampleBufferDelegate, "qt.multimedia.avf.samplebufferdelegate");
29
30using namespace Qt::StringLiterals;
31
32QT_USE_NAMESPACE
33
34@implementation QT_MANGLE_NAMESPACE(QAVFSampleBufferDelegate) {
35@private
36 std::function<void(const QVideoFrame &)> frameHandler;
37 QFFmpeg::QAVFSampleBufferDelegateTransformProvider transformationProvider;
38 std::unique_ptr<QFFmpeg::HWAccel> m_accel;
39 std::chrono::microseconds startTime;
40 std::optional<std::chrono::microseconds> baseTime;
41 qreal frameRate;
42}
43
44- (instancetype)initWithFrameHandler:(std::function<void(const QVideoFrame &)>)handler
45{
46 if (!(self = [super init]))
47 return nil;
48
49 Q_ASSERT(handler);
50
51 frameHandler = std::move(handler);
52 return self;
53}
54
55- (void)discardFutureSamples
56{
57 frameHandler = nullptr;
58}
59
60- (void)setTransformationProvider:
61 (const QFFmpeg::QAVFSampleBufferDelegateTransformProvider &)provider
62{
63 transformationProvider = std::move(provider);
64}
65
66- (void)captureOutput:(AVCaptureOutput *)captureOutput
67 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
68 fromConnection:(AVCaptureConnection *)connection
69{
70 Q_UNUSED(captureOutput);
71
72 if (!frameHandler)
73 return;
74
75 CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
76 if (!imageBuffer || CFGetTypeID(imageBuffer) != CVPixelBufferGetTypeID()) {
77 qWarning() << "Cannot get image buffer from sample buffer";
78 return;
79 }
80
81 auto pixelBuffer = QAVFHelpers::QSharedCVPixelBuffer(
82 imageBuffer,
83 QAVFHelpers::QSharedCVPixelBuffer::RefMode::NeedsRef);
84
85 // The incoming buffer is recycled from a pool owned by AVFoundation, so we
86 // copy it into a free-standing CVPixelBuffer that we fully own before
87 // keeping it around in a QVideoFrame.
88 q23::expected<QAVFHelpers::QSharedCVPixelBuffer, QString> copyResult =
89 QFFmpeg::deepCopyCvPixelBuffer(pixelBuffer.get());
90 if (!copyResult) {
91 qCWarning(qLcAvfSampleBufferDelegate)
92 << "Failed to copy incoming pixel buffer:" << copyResult.error();
93 return;
94 }
95 pixelBuffer = std::move(*copyResult);
96
97 QSize incomingFrameSize {
98 static_cast<int>(CVPixelBufferGetWidth(pixelBuffer.get())),
99 static_cast<int>(CVPixelBufferGetHeight(pixelBuffer.get())) };
100 Q_ASSERT(!incomingFrameSize.isEmpty());
101 CvPixelFormat incomingCvPixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer.get());
102
103 Q_ASSERT(m_accel);
104 m_accel->updateFramesContext(
105 av_map_videotoolbox_format_to_pixfmt(incomingCvPixelFormat),
106 incomingFrameSize);
107
108 std::chrono::microseconds frameTime =
109 QAVFHelpers::CMTimeToMicroseconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer));
110 if (!baseTime) {
111 baseTime = frameTime;
112 startTime = frameTime;
113 }
114
115 QVideoFrameFormat format = QAVFHelpers::videoFormatForImageBuffer(pixelBuffer.get());
116 if (!format.isValid()) {
117 qWarning() << "Cannot get get video format for image buffer"
118 << CVPixelBufferGetWidth(pixelBuffer.get()) << 'x'
119 << CVPixelBufferGetHeight(pixelBuffer.get());
120 return;
121 }
122
123 std::optional<QFFmpeg::QAVFSampleBufferDelegateTransform> transform;
124 if (transformationProvider) {
125 transform = transformationProvider(connection);
126 const VideoTransformation &surfaceTransform = transform.value().surfaceTransform;
127 format.setRotation(surfaceTransform.rotation);
128 format.setMirrored(surfaceTransform.mirroredHorizontallyAfterRotation);
129 }
130
131 format.setStreamFrameRate(frameRate);
132
133 QVideoFrame frame;
134 q23::expected<QVideoFrame, QString> frameResult = QFFmpeg::qVideoFrameFromCvPixelBuffer(
135 *m_accel,
136 startTime - *baseTime,
137 pixelBuffer,
138 format);
139 if (!frameResult)
140 qCWarning(qLcAvfSampleBufferDelegate) << frameResult.error();
141 else
142 frame = *frameResult;
143
144 if (!frame.isValid())
145 frame = QVideoFramePrivate::createFrame(
146 std::make_unique<QFFmpeg::CVImageVideoBuffer>(std::move(pixelBuffer)),
147 std::move(format));
148
149 if (transform.has_value()) {
150 const VideoTransformation &presentationTransform = transform.value().presentationTransform;
151 frame.setRotation(presentationTransform.rotation);
152 frame.setMirrored(presentationTransform.mirroredHorizontallyAfterRotation);
153 }
154
155 frame.setStartTime((startTime - *baseTime).count());
156 frame.setEndTime((frameTime - *baseTime).count());
157 startTime = frameTime;
158
159 frameHandler(frame);
160}
161
162// Sets the initial HWAccel. Should only be called once during
163// initialization.
164- (void)setHWAccel:(std::unique_ptr<QFFmpeg::HWAccel> &&)accel
165{
166 m_accel = std::move(accel);
167}
168
169- (void)setVideoFormatFrameRate:(qreal)rate
170{
171 frameRate = rate;
172}
173
174@end
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)