114 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,
false);
116 return q23::unexpected{ u"CMSampleBuffer has no attachments array"_s };
118 CFDictionaryRef attachment = (CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
120 NSDictionary *dict = (__bridge NSDictionary *)attachment;
122 CGRect contentRect = CGRectZero;
123 NSDictionary *frameInfo = dict[(id)SCStreamFrameInfoContentRect];
126 contentRect = CGRectMakeWithDictionaryRepresentation(
127 (__bridge CFDictionaryRef)frameInfo, &contentRect)
132 NSNumber *scaleNumber = dict[(id)SCStreamFrameInfoScaleFactor];
133 CGFloat scaleFactor = scaleNumber ? scaleNumber.doubleValue : 1.0;
135 NSNumber *contentScaleNumber = dict[(id)SCStreamFrameInfoContentScale];
136 CGFloat contentScale = contentScaleNumber ? contentScaleNumber.doubleValue : 1.0;
137 if (contentScale <= 0.0)
141 static_cast<
int>(std::round(contentRect.size.width * scaleFactor / contentScale)),
142 static_cast<
int>(std::round(contentRect.size.height * scaleFactor / contentScale)), };
148 QMacScreenCaptureStreamOutput &scStreamOutput,
149 CMSampleBufferRef sampleBufferRef)
151 CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBufferRef);
153 return q23::unexpected(u"Cannot get CVImageBufferRef from CMSampleBufferRef"_s);
154 if (CFGetTypeID(imageBufferRef) != CVPixelBufferGetTypeID())
155 return q23::unexpected(u"Grabbed CVImageBufferRef that is not of type CVPixelBuffer"_s);
157 auto pixelBuffer = QAVFHelpers::QSharedCVPixelBuffer(
159 QAVFHelpers::QSharedCVPixelBuffer::RefMode::NeedsRef);
164 q23::expected<QAVFHelpers::QSharedCVPixelBuffer, QString> copyResult = deepCopyCvPixelBuffer(
167 return q23::unexpected(u"Failed to copy incoming pixel buffer: "_s + copyResult.error());
168 pixelBuffer =
std::move(*copyResult);
171 QSize incomingFrameSize {
172 static_cast<
int>(CVPixelBufferGetWidth(pixelBuffer.get())),
173 static_cast<
int>(CVPixelBufferGetHeight(pixelBuffer.get())) };
174 Q_ASSERT(!incomingFrameSize.isEmpty());
175 CvPixelFormat incomingCvPixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer.get());
176 Q_ASSERT(scStreamOutput.m_hwAccel);
177 scStreamOutput.m_hwAccel->updateFramesContext(
178 av_map_videotoolbox_format_to_pixfmt(incomingCvPixelFormat),
182 std::chrono::microseconds frameTime =
183 QAVFHelpers::CMTimeToMicroseconds(CMSampleBufferGetPresentationTimeStamp(sampleBufferRef));
184 if (!scStreamOutput.m_baseTime) {
185 scStreamOutput.m_baseTime = frameTime;
186 scStreamOutput.m_startTime = frameTime;
189 QVideoFrameFormat format = QAVFHelpers::videoFormatForImageBuffer(pixelBuffer.get());
190 if (!format.isValid())
191 return q23::unexpected(u"Cannot get get video format for image buffer"_s);
193 format.setColorSpace(QMacScreenCaptureKit::colorSpace);
194 format.setColorRange(QMacScreenCaptureKit::colorRange);
195 format.setColorTransfer(QMacScreenCaptureKit::colorTransfer);
197 Q_ASSERT(scStreamOutput.m_hwAccel);
199 q23::expected<QVideoFrame, QString> frameResult = QFFmpeg::qVideoFrameFromCvPixelBuffer(
200 *scStreamOutput.m_hwAccel,
201 scStreamOutput.m_startTime - *scStreamOutput.m_baseTime,
205 qCWarning(qLcMacScreenCapture) << frameResult.error();
207 frame = *frameResult;
209 if (!frame.isValid()) {
210 frame = QVideoFramePrivate::createFrame(
211 std::make_unique<QFFmpeg::CVImageVideoBuffer>(std::move(pixelBuffer)),
215 frame.setStartTime((scStreamOutput.m_startTime - *scStreamOutput.m_baseTime).count());
216 frame.setEndTime((frameTime - *scStreamOutput.m_baseTime).count());
217 scStreamOutput.m_startTime = frameTime;
225 QFFmpeg::QMacScreenCaptureStreamOutput &streamOutput,
226 CMSampleBufferRef sampleBufferRef)
230 Q_ASSERT(streamOutput.m_qScreenCaptureKit);
237 q23::expected<QSize, QString> readContentRectResult = ReadContentRect(sampleBufferRef);
238 if (readContentRectResult) {
239 QSize contentRect = *readContentRectResult;
244 if (!contentRect.isEmpty()) {
245 bool newContentRectIsDifferent =
246 streamOutput.m_previousFrameContentRect
247 && *streamOutput.m_previousFrameContentRect != contentRect;
248 if (newContentRectIsDifferent)
249 streamOutput.m_qScreenCaptureKit->updateStream(contentRect);
251 streamOutput.m_previousFrameContentRect = contentRect;
255 streamOutput.m_previousFrameContentRect =
std::nullopt;
256 qCDebug(qLcMacScreenCapture)
257 <<
"Unable to read window content rect from CMSampleBUffer: "
258 << readContentRectResult.error();
261 q23::expected<QVideoFrame, QString> videoFrameResult = createQVideoFrame(
264 if (!videoFrameResult) {
265 qCWarning(qLcMacScreenCapture)
266 <<
"Failed to create qVideoFrame from CMSampleBufferRef: "
267 << videoFrameResult.error();
271 emit streamOutput.m_qScreenCaptureKit->newVideoFrameGenerated(
272 streamOutput.m_qScreenCaptureKit->streamId(),
273 std::move(*videoFrameResult));
292 uint32_t cvPixelFormat,
295 auto streamOutput = AVFScopedPointer{ [[QMacScreenCaptureStreamOutput alloc] init] };
297 streamOutput.data()->m_qScreenCaptureKit = &macScreenCaptureKit;
299 streamOutput.data()->m_hwAccel = HWAccel::create(AV_HWDEVICE_TYPE_VIDEOTOOLBOX);
300 if (!streamOutput.data()->m_hwAccel)
301 return q23::unexpected(
302 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
304 streamOutput.data()->m_hwAccel->createFramesContext(
305 av_map_videotoolbox_format_to_pixfmt(cvPixelFormat),
308 if (!streamOutput.data()->m_hwAccel->hwFramesContextAsBuffer())
309 return q23::unexpected(
310 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
322 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
323 [m_stream.data() stopCaptureWithCompletionHandler:[semaphore](NSError *error) {
325 qCWarning(qLcMacScreenCapture)
326 <<
"Error while stopping ScreenCaptureKit stream during teardown: "
327 << QString::fromNSString(error.localizedDescription);
329 dispatch_semaphore_signal(semaphore);
331 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
332 dispatch_release(semaphore);
336 dispatch_sync(m_dispatchQueue.data(), []{});
487 std::optional<qreal> frameRate)
490 Q_ASSERT(!resolutionPx.isEmpty());
494 auto scStreamConfig = AVFScopedPointer{ [[SCStreamConfiguration alloc] init] };
495 scStreamConfig.data().width = resolutionPx.width();
496 scStreamConfig.data().height = resolutionPx.height();
499 scStreamConfig.data().scalesToFit =
false;
501 scStreamConfig.data().pixelFormat = QMacScreenCaptureKit::cvPixelFormat;
502 scStreamConfig.data().colorSpaceName = QMacScreenCaptureKit::cgColorSpace();
503 scStreamConfig.data().captureResolution = SCCaptureResolutionBest;
504 if (@available(macOS 15.0, *))
505 scStreamConfig.data().captureDynamicRange = SCCaptureDynamicRangeSDR;
508 Q_ASSERT(frameRate > 0);
509 scStreamConfig.data().minimumFrameInterval =
510 CMTimeMake(1,
static_cast<int32_t>(std::round(*frameRate)));
512 scStreamConfig.data().minimumFrameInterval = kCMTimeZero;
515 return scStreamConfig;