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
qffmpegdarwinhwframehelpers.mm
Go to the documentation of this file.
1// Copyright (C) 2026 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 <QtCore/qscopeguard.h>
7
8#define AVMediaType XAVMediaType
9#include <QtFFmpegMediaPluginImpl/private/qffmpegvideobuffer_p.h>
10#include <QtFFmpegMediaPluginImpl/private/qffmpeghwaccel_p.h>
11#undef AVMediaType
12
13#include <QtMultimedia/private/qvideoframe_p.h>
14
15#include <CoreVideo/CVPixelBuffer.h>
16#include <VideoToolbox/VTPixelTransferSession.h>
17
19
20using namespace Qt::StringLiterals;
21
22namespace QFFmpeg {
23
24namespace {
25
26// Helper function to allocate a FFmpeg HwFrame, from a given CVPixelBufferRef.
27// The FFmpeg HwFrame becomes a owning reference for this CVPixelBufferRef.
28// Make sure this is compatible with the layout used in ffmpeg's hwcontext_videotoolbox
29[[nodiscard]] q23::expected<AVFrameUPtr, QString> allocHWFrame(
30 AVBufferRef *hwContext,
31 QAVFHelpers::QSharedCVPixelBuffer sharedPixBuf)
32{
33 Q_ASSERT(sharedPixBuf);
34
35 AVHWFramesContext *ctx = (AVHWFramesContext *)hwContext->data;
36
37 if (ctx->width != (int)CVPixelBufferGetWidth(sharedPixBuf.get())
38 || ctx->height != (int)CVPixelBufferGetHeight(sharedPixBuf.get()))
39 return q23::unexpected{
40 u"Size of given CVPixelBufferRef does not match the FFmpeg hw frame context"_s };
41
42 auto frame = QFFmpeg::makeAVFrame();
43 if (!frame)
44 return q23::unexpected{ u"Failed to allocate FFmpeg AVFrame"_s };
45
46 frame->hw_frames_ctx = av_buffer_ref(hwContext);
47 frame->extended_data = frame->data;
48
49 CVPixelBufferRef pixbuf = sharedPixBuf.release();
50 auto releasePixBufFn = [](void* opaquePtr, uint8_t *) {
51 CVPixelBufferRelease(static_cast<CVPixelBufferRef>(opaquePtr));
52 };
53 frame->buf[0] = av_buffer_create(nullptr, 0, releasePixBufFn, pixbuf, 0);
54
55 // It is convention to use 4th data plane for hardware frames.
56 frame->data[3] = (uint8_t *)pixbuf;
57 frame->width = ctx->width;
58 frame->height = ctx->height;
59 frame->format = AV_PIX_FMT_VIDEOTOOLBOX;
60 return frame;
61}
62
63} // Anonymous namespace end
64
66 CVPixelBufferRef source)
67{
68 Q_ASSERT(source);
69 Q_ASSERT(CVPixelBufferGetWidth(source) != 0);
70 Q_ASSERT(CVPixelBufferGetHeight(source) != 0);
71 Q_ASSERT(CVPixelBufferGetPixelFormatType(source) != CvPixelFormatInvalid);
72
73 // We request an IOSurface backing (and Metal compatibility) so the
74 // transfer happens on the GPU and the buffer stays usable as a
75 // hardware video frame downstream.
76 NSDictionary *attributes = @{
77 (id)kCVPixelBufferIOSurfacePropertiesKey : @{},
78 (id)kCVPixelBufferMetalCompatibilityKey : @YES,
79 };
80
81 CVPixelBufferRef destination = nullptr;
82 const CVReturn createResult = CVPixelBufferCreate(
83 kCFAllocatorDefault,
84 CVPixelBufferGetWidth(source),
85 CVPixelBufferGetHeight(source),
86 CVPixelBufferGetPixelFormatType(source),
87 (__bridge CFDictionaryRef)attributes,
88 &destination);
89 if (createResult != kCVReturnSuccess || !destination)
90 return q23::unexpected{
91 u"Failed to allocate destination CVPixelBuffer (CVReturn %1)"_s.arg(createResult) };
92
93 // Adopt ownership immediately so the buffer is released on every error path.
94 QAVFHelpers::QSharedCVPixelBuffer destinationBuffer {
95 destination,
96 QAVFHelpers::QSharedCVPixelBuffer::RefMode::HasRef };
97
98 // Creating a transfer session per frame is acceptable but not free; if this
99 // shows up in profiles it can be hoisted into the per-stream object and
100 // reused across frames (a single session handles varying sizes/formats).
101 VTPixelTransferSessionRef transferSession = nullptr;
102 const OSStatus sessionResult = VTPixelTransferSessionCreate(
103 kCFAllocatorDefault,
104 &transferSession);
105 if (sessionResult != noErr || !transferSession)
106 return q23::unexpected{
107 u"Failed to create VTPixelTransferSession (OSStatus %1)"_s.arg(sessionResult) };
108 auto sessionGuard = qScopeGuard([&] {
109 VTPixelTransferSessionInvalidate(transferSession);
110 CFRelease(transferSession);
111 });
112
113 const OSStatus transferResult =
114 VTPixelTransferSessionTransferImage(transferSession, source, destinationBuffer.get());
115 if (transferResult != noErr)
116 return q23::unexpected{
117 u"VTPixelTransferSessionTransferImage failed (OSStatus %1)"_s.arg(transferResult) };
118
119 // The freshly created destination has no attachments; carry over the
120 // source's colorimetry (YCbCr matrix, transfer function, ...) so that
121 // QAVFHelpers::videoFormatForImageBuffer reports the same format as for the
122 // original buffer.
123 CVBufferPropagateAttachments(source, destinationBuffer.get());
124
125 return destinationBuffer;
126}
127
129 const QFFmpeg::HWAccel &hwAccel,
130 std::chrono::microseconds presentationTimeStamp,
131 const QAVFHelpers::QSharedCVPixelBuffer &imageBuffer,
132 QVideoFrameFormat format)
133{
134 q23::expected<AVFrameUPtr, QString> avFrameResult = allocHWFrame(
135 hwAccel.hwFramesContextAsBuffer(),
136 imageBuffer);
137 if (!avFrameResult)
138 return q23::unexpected{ u"Failed to allocate FFmpeg HwFrame"_s };
139 AVFrameUPtr &avFrame = *avFrameResult;
140
141 avFrame->pts = presentationTimeStamp.count();
142
143 return QVideoFramePrivate::createFrame(
144 std::make_unique<QFFmpegVideoBuffer>(std::move(avFrame)),
145 format);
146}
147
148} // namespace QFFmpeg
149
150QT_END_NAMESPACE
q23::expected< QVideoFrame, QString > qVideoFrameFromCvPixelBuffer(const QFFmpeg::HWAccel &hwAccel, std::chrono::microseconds presentationTimeStamp, const QAVFHelpers::QSharedCVPixelBuffer &imageBuffer, QVideoFrameFormat format)
q23::expected< QAVFHelpers::QSharedCVPixelBuffer, QString > deepCopyCvPixelBuffer(CVPixelBufferRef source)
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
Combined button and popup list for selecting options.