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 (QAVFHelpers::CVPixelBufferSize(sharedPixBuf.get()) != QSize(ctx->width, ctx->height))
38 return q23::unexpected{
39 u"Size of given CVPixelBufferRef does not match the FFmpeg hw frame context"_s };
40
41 auto frame = QFFmpeg::makeAVFrame();
42 if (!frame)
43 return q23::unexpected{ u"Failed to allocate FFmpeg AVFrame"_s };
44
45 frame->hw_frames_ctx = av_buffer_ref(hwContext);
46 frame->extended_data = frame->data;
47
48 CVPixelBufferRef pixbuf = sharedPixBuf.release();
49 auto releasePixBufFn = [](void* opaquePtr, uint8_t *) {
50 CVPixelBufferRelease(static_cast<CVPixelBufferRef>(opaquePtr));
51 };
52 frame->buf[0] = av_buffer_create(nullptr, 0, releasePixBufFn, pixbuf, 0);
53
54 // It is convention to use 4th data plane for hardware frames.
55 frame->data[3] = (uint8_t *)pixbuf;
56 frame->width = ctx->width;
57 frame->height = ctx->height;
58 frame->format = AV_PIX_FMT_VIDEOTOOLBOX;
59 return frame;
60}
61
62} // Anonymous namespace end
63
65 CVPixelBufferRef source)
66{
67 Q_ASSERT(source);
68 const QSize sourceSize = QAVFHelpers::CVPixelBufferSize(source);
69 Q_ASSERT(!sourceSize.isEmpty());
70 Q_ASSERT(CVPixelBufferGetPixelFormatType(source) != CvPixelFormatInvalid);
71
72 // We request an IOSurface backing (and Metal compatibility) so the
73 // transfer happens on the GPU and the buffer stays usable as a
74 // hardware video frame downstream.
75 NSDictionary *attributes = @{
76 (id)kCVPixelBufferIOSurfacePropertiesKey : @{},
77 (id)kCVPixelBufferMetalCompatibilityKey : @YES,
78 };
79
80 CVPixelBufferRef destination = nullptr;
81 const CVReturn createResult = CVPixelBufferCreate(
82 kCFAllocatorDefault,
83 sourceSize.width(),
84 sourceSize.height(),
85 CVPixelBufferGetPixelFormatType(source),
86 (__bridge CFDictionaryRef)attributes,
87 &destination);
88 if (createResult != kCVReturnSuccess || !destination)
89 return q23::unexpected{
90 u"Failed to allocate destination CVPixelBuffer (CVReturn %1)"_s.arg(createResult) };
91
92 // Adopt ownership immediately so the buffer is released on every error path.
93 QAVFHelpers::QSharedCVPixelBuffer destinationBuffer {
94 destination,
95 QAVFHelpers::QSharedCVPixelBuffer::RefMode::HasRef };
96
97 // Creating a transfer session per frame is acceptable but not free; if this
98 // shows up in profiles it can be hoisted into the per-stream object and
99 // reused across frames (a single session handles varying sizes/formats).
100 VTPixelTransferSessionRef transferSession = nullptr;
101 const OSStatus sessionResult = VTPixelTransferSessionCreate(
102 kCFAllocatorDefault,
103 &transferSession);
104 if (sessionResult != noErr || !transferSession)
105 return q23::unexpected{
106 u"Failed to create VTPixelTransferSession (OSStatus %1)"_s.arg(sessionResult) };
107 auto sessionGuard = qScopeGuard([&] {
108 VTPixelTransferSessionInvalidate(transferSession);
109 CFRelease(transferSession);
110 });
111
112 const OSStatus transferResult =
113 VTPixelTransferSessionTransferImage(transferSession, source, destinationBuffer.get());
114 if (transferResult != noErr)
115 return q23::unexpected{
116 u"VTPixelTransferSessionTransferImage failed (OSStatus %1)"_s.arg(transferResult) };
117
118 // The freshly created destination has no attachments; carry over the
119 // source's colorimetry (YCbCr matrix, transfer function, ...) so that
120 // QAVFHelpers::videoFormatForImageBuffer reports the same format as for the
121 // original buffer.
122 CVBufferPropagateAttachments(source, destinationBuffer.get());
123
124 return destinationBuffer;
125}
126
128 const QFFmpeg::HWAccel &hwAccel,
129 std::chrono::microseconds presentationTimeStamp,
130 const QAVFHelpers::QSharedCVPixelBuffer &imageBuffer,
131 QVideoFrameFormat format)
132{
133 q23::expected<AVFrameUPtr, QString> avFrameResult = allocHWFrame(
134 hwAccel.hwFramesContextAsBuffer(),
135 imageBuffer);
136 if (!avFrameResult)
137 return q23::unexpected{ u"Failed to allocate FFmpeg HwFrame"_s };
138 AVFrameUPtr &avFrame = *avFrameResult;
139
140 avFrame->pts = presentationTimeStamp.count();
141
142 return QVideoFramePrivate::createFrame(
143 std::make_unique<QFFmpegVideoBuffer>(std::move(avFrame)),
144 format);
145}
146
147} // namespace QFFmpeg
148
149QT_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)
Combined button and popup list for selecting options.