122 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,
false);
123 if (!attachments || CFArrayGetCount(attachments) == 0)
124 return q23::unexpected{ u"CMSampleBuffer has no attachments array"_s };
126 CFDictionaryRef attachment = (CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
127 NSDictionary *dict = (__bridge NSDictionary *)attachment;
129 NSNumber *statusNumber = dict[(id)SCStreamFrameInfoStatus];
131 return q23::unexpected{ u"CMSampleBuffer has no frame status"_s };
134 info.status =
static_cast<SCFrameStatus>(statusNumber.intValue);
136 CGRect contentRect = CGRectZero;
137 NSDictionary *frameInfo = dict[(id)SCStreamFrameInfoContentRect];
139 contentRect = CGRectMakeWithDictionaryRepresentation(
140 (__bridge CFDictionaryRef)frameInfo, &contentRect)
145 NSNumber *scaleNumber = dict[(id)SCStreamFrameInfoScaleFactor];
146 CGFloat scaleFactor = scaleNumber ? scaleNumber.doubleValue : 1.0;
148 NSNumber *contentScaleNumber = dict[(id)SCStreamFrameInfoContentScale];
149 CGFloat contentScale = contentScaleNumber ? contentScaleNumber.doubleValue : 1.0;
150 if (contentScale <= 0.0)
153 info.contentRect = QSize{
154 static_cast<
int>(std::lround(contentRect.size.width * scaleFactor / contentScale)),
155 static_cast<
int>(std::lround(contentRect.size.height * scaleFactor / contentScale)), };
163 QMacScreenCaptureStreamOutput &scStreamOutput,
164 CMSampleBufferRef sampleBufferRef)
166 CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBufferRef);
168 return q23::unexpected(u"Cannot get CVImageBufferRef from CMSampleBufferRef"_s);
169 if (CFGetTypeID(imageBufferRef) != CVPixelBufferGetTypeID())
170 return q23::unexpected(u"Grabbed CVImageBufferRef that is not of type CVPixelBuffer"_s);
172 auto pixelBuffer = QAVFHelpers::QSharedCVPixelBuffer(
174 QAVFHelpers::QSharedCVPixelBuffer::RefMode::NeedsRef);
179 q23::expected<QAVFHelpers::QSharedCVPixelBuffer, QString> copyResult = deepCopyCvPixelBuffer(
182 return q23::unexpected(u"Failed to copy incoming pixel buffer: "_s + copyResult.error());
183 pixelBuffer =
std::move(*copyResult);
186 QSize incomingFrameSize {
187 static_cast<
int>(CVPixelBufferGetWidth(pixelBuffer.get())),
188 static_cast<
int>(CVPixelBufferGetHeight(pixelBuffer.get())) };
189 Q_ASSERT(!incomingFrameSize.isEmpty());
190 CvPixelFormat incomingCvPixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer.get());
191 Q_ASSERT(scStreamOutput.m_hwAccel);
192 scStreamOutput.m_hwAccel->updateFramesContext(
193 av_map_videotoolbox_format_to_pixfmt(incomingCvPixelFormat),
197 std::chrono::microseconds frameTime =
198 QAVFHelpers::CMTimeToMicroseconds(CMSampleBufferGetPresentationTimeStamp(sampleBufferRef));
199 if (!scStreamOutput.m_baseTime) {
200 scStreamOutput.m_baseTime = frameTime;
201 scStreamOutput.m_startTime = frameTime;
204 QVideoFrameFormat format = QAVFHelpers::videoFormatForImageBuffer(pixelBuffer.get());
205 if (!format.isValid())
206 return q23::unexpected(u"Cannot get get video format for image buffer"_s);
208 format.setColorSpace(QMacScreenCaptureKit::colorSpace);
209 format.setColorRange(QMacScreenCaptureKit::colorRange);
210 format.setColorTransfer(QMacScreenCaptureKit::colorTransfer);
212 Q_ASSERT(scStreamOutput.m_hwAccel);
214 q23::expected<QVideoFrame, QString> frameResult = QFFmpeg::qVideoFrameFromCvPixelBuffer(
215 *scStreamOutput.m_hwAccel,
216 scStreamOutput.m_startTime - *scStreamOutput.m_baseTime,
220 qCWarning(qLcMacScreenCapture) << frameResult.error();
222 frame = *frameResult;
224 if (!frame.isValid()) {
225 frame = QVideoFramePrivate::createFrame(
226 std::make_unique<QFFmpeg::CVImageVideoBuffer>(std::move(pixelBuffer)),
230 frame.setStartTime((scStreamOutput.m_startTime - *scStreamOutput.m_baseTime).count());
231 frame.setEndTime((frameTime - *scStreamOutput.m_baseTime).count());
232 scStreamOutput.m_startTime = frameTime;
240 QFFmpeg::QMacScreenCaptureStreamOutput &streamOutput,
241 CMSampleBufferRef sampleBufferRef)
243 Q_ASSERT(streamOutput.m_qScreenCaptureKit);
245 q23::expected<FrameInfo, QString> frameInfoResult = readFrameInfo(sampleBufferRef);
246 if (!frameInfoResult) {
247 qCDebug(qLcMacScreenCapture)
248 <<
"Error while reading frame info of CMSampleBufferRef:"
249 << frameInfoResult.error();
253 const FrameInfo &frameInfo = *frameInfoResult;
257 if (frameInfo.status != SCFrameStatusComplete)
267 if (!frameInfo.contentRect.isEmpty()) {
268 bool newContentRectIsDifferent =
269 streamOutput.m_previousFrameContentRect
270 && *streamOutput.m_previousFrameContentRect != frameInfo.contentRect;
271 if (newContentRectIsDifferent)
272 streamOutput.m_qScreenCaptureKit->updateStream(frameInfo.contentRect);
274 streamOutput.m_previousFrameContentRect = frameInfo.contentRect;
277 q23::expected<QVideoFrame, QString> videoFrameResult = createQVideoFrame(
280 if (!videoFrameResult) {
281 qCWarning(qLcMacScreenCapture)
282 <<
"Failed to create qVideoFrame from CMSampleBufferRef:"
283 << videoFrameResult.error();
287 emit streamOutput.m_qScreenCaptureKit->newVideoFrameGenerated(
288 streamOutput.m_qScreenCaptureKit->streamId(),
289 std::move(*videoFrameResult));
293 QMacScreenCaptureStreamDelegate &streamDelegate,
294 QMacScreenCaptureKit::StreamId streamId,
297 streamDelegate.m_streamId = streamId;
299 &streamDelegate.m_helper,
300 &QMacScreenCaptureStreamDelegateHelper::didStopWithError,
301 &macScreenCaptureKit,
302 &QMacScreenCaptureKit::streamStoppedWithError);
308 uint32_t cvPixelFormat,
311 auto streamOutput = AVFScopedPointer{ [[QMacScreenCaptureStreamOutput alloc] init] };
313 streamOutput.data()->m_qScreenCaptureKit = &macScreenCaptureKit;
315 streamOutput.data()->m_hwAccel = HWAccel::create(AV_HWDEVICE_TYPE_VIDEOTOOLBOX);
316 if (!streamOutput.data()->m_hwAccel)
317 return q23::unexpected(
318 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
320 streamOutput.data()->m_hwAccel->createFramesContext(
321 av_map_videotoolbox_format_to_pixfmt(cvPixelFormat),
324 if (!streamOutput.data()->m_hwAccel->hwFramesContextAsBuffer())
325 return q23::unexpected(
326 u"Unable to create FFmpeg HW context when starting ScreenCaptureKit stream"_s);
338 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
339 [m_stream.data() stopCaptureWithCompletionHandler:[semaphore](NSError *error) {
341 qCWarning(qLcMacScreenCapture)
342 <<
"Error while stopping ScreenCaptureKit stream during teardown:"
343 << QString::fromNSString(error.localizedDescription);
345 dispatch_semaphore_signal(semaphore);
347 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
348 dispatch_release(semaphore);
352 dispatch_sync(m_dispatchQueue.data(), []{});
540 std::optional<qreal> frameRate)
543 Q_ASSERT(!resolutionPx.isEmpty());
547 auto scStreamConfig = AVFScopedPointer{ [[SCStreamConfiguration alloc] init] };
548 scStreamConfig.data().width = resolutionPx.width();
549 scStreamConfig.data().height = resolutionPx.height();
552 scStreamConfig.data().scalesToFit =
false;
554 scStreamConfig.data().pixelFormat = QMacScreenCaptureKit::cvPixelFormat;
555 scStreamConfig.data().colorSpaceName = QMacScreenCaptureKit::cgColorSpace();
556 scStreamConfig.data().captureResolution = SCCaptureResolutionBest;
557 if (@available(macOS 15.0, *))
558 scStreamConfig.data().captureDynamicRange = SCCaptureDynamicRangeSDR;
561 Q_ASSERT(frameRate > 0);
562 scStreamConfig.data().minimumFrameInterval =
563 CMTimeMake(1,
static_cast<int32_t>(std::round(*frameRate)));
565 scStreamConfig.data().minimumFrameInterval = kCMTimeZero;
568 return scStreamConfig;