9#include <QtMultimedia/private/qavfcameradebug_p.h>
10#include <qdarwinformatsinfo_p.h>
11#include <avfmetadata_p.h>
13#include <QtCore/qmetaobject.h>
14#include <QtCore/qatomic.h>
15#include <QtCore/private/qcore_mac_p.h>
27 if (!session->captureSession())
30 if (!session->videoInput() && !session->audioInput())
44using AVFAtomicInt64 = QAtomicInteger<qint64>;
48@interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) (PrivateAPI)
49- (
bool)addWriterInputs;
51- (
void)updateDuration:(CMTime)newTimeStamp;
52- (QCFType<CMSampleBufferRef>)adjustTime:(
const QCFType<CMSampleBufferRef> &)sample
56@implementation QT_MANGLE_NAMESPACE(AVFMediaAssetWriter)
59 AVFCameraService *m_service;
61 AVFScopedPointer<AVAssetWriterInput> m_cameraWriterInput;
62 AVFScopedPointer<AVAssetWriterInput> m_audioWriterInput;
65 AVFScopedPointer<dispatch_queue_t> m_writerQueue;
67 AVFScopedPointer<dispatch_queue_t> m_videoQueue;
69 AVFScopedPointer<dispatch_queue_t> m_audioQueue;
71 AVFScopedPointer<AVAssetWriter> m_assetWriter;
73 AVFMediaEncoder *m_delegate;
79 bool m_writeFirstAudioBuffer;
82 CMTime m_lastTimeStamp;
83 CMTime m_lastVideoTimestamp;
84 CMTime m_lastAudioTimestamp;
88 NSDictionary *m_audioSettings;
89 NSDictionary *m_videoSettings;
91 AVFAtomicInt64 m_durationInMs;
94- (
id)initWithDelegate:(AVFMediaEncoder *)delegate
98 if (self = [super init]) {
99 m_delegate = delegate;
100 m_setStartTime =
true;
101 m_state.storeRelaxed(WriterStateIdle);
107- (
bool)setupWithFileURL:(NSURL *)fileURL
108 cameraService:(AVFCameraService *)service
109 audioSettings:(NSDictionary *)audioSettings
110 videoSettings:(NSDictionary *)videoSettings
111 fileFormat:(QMediaFormat::FileFormat)fileFormat
112 transform:(CGAffineTransform)transform
116 if (!qt_capture_session_isValid(service)) {
117 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"invalid capture session";
122 m_audioSettings = audioSettings;
123 m_videoSettings = videoSettings;
125 AVFCameraSession *session = m_service->session();
127 m_writerQueue.reset(dispatch_queue_create(
"asset-writer-queue", DISPATCH_QUEUE_SERIAL));
128 if (!m_writerQueue) {
129 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"failed to create an asset writer's queue";
133 m_videoQueue.reset();
134 if (session->videoInput() && session->videoOutput() && session->videoOutput()->videoDataOutput()) {
135 m_videoQueue.reset(dispatch_queue_create(
"video-output-queue", DISPATCH_QUEUE_SERIAL));
137 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"failed to create video queue";
140 dispatch_set_target_queue(m_videoQueue, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0));
143 m_audioQueue.reset();
144 if (session->audioInput() && session->audioOutput()) {
145 m_audioQueue.reset(dispatch_queue_create(
"audio-output-queue", DISPATCH_QUEUE_SERIAL));
147 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"failed to create audio queue";
154 auto fileType = QDarwinFormatInfo::avFileTypeForContainerFormat(fileFormat);
155 m_assetWriter.reset([[AVAssetWriter alloc] initWithURL:fileURL
158 if (!m_assetWriter) {
159 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"failed to create asset writer";
164 m_writeFirstAudioBuffer =
true;
166 if (![self addWriterInputs]) {
167 m_assetWriter.reset();
171 if (m_cameraWriterInput)
172 m_cameraWriterInput.data().transform = transform;
174 [self setMetaData:fileType];
180- (
void)setMetaData:(AVFileType)fileType
182 m_assetWriter.data().metadata = AVFMetaData::toAVMetadataForFormat(m_delegate->metaData(), fileType);
189 m_setStartTime =
true;
191 m_state.storeRelease(WriterStateActive);
193 [m_assetWriter startWriting];
194 AVCaptureSession *session = m_service->session()->captureSession();
195 if (!session.running)
196 [session startRunning];
201 if (m_state.loadAcquire() != WriterStateActive && m_state.loadAcquire() != WriterStatePaused)
204 if ([m_assetWriter status] != AVAssetWriterStatusWriting
205 && [m_assetWriter status] != AVAssetWriterStatusFailed)
212 m_state.storeRelease(WriterStateIdle);
217 dispatch_sync(m_writerQueue, ^{});
221 dispatch_sync(m_videoQueue, ^{});
224 [m_assetWriter finishWritingWithCompletionHandler:^{
227 if (m_state.loadAcquire() == WriterStateAborted)
230 AVCaptureSession *session = m_service->session()->captureSession();
232 [session stopRunning];
233 QMetaObject::invokeMethod(m_delegate,
"assetWriterFinished", Qt::QueuedConnection);
241 if (m_state.fetchAndStoreRelease(WriterStateAborted) != WriterStateActive) {
253 dispatch_sync(m_writerQueue, ^{});
257 dispatch_sync(m_videoQueue, ^{});
261 [m_assetWriter finishWritingWithCompletionHandler:^{
267 if (m_state.loadAcquire() != WriterStateActive)
269 if ([m_assetWriter status] != AVAssetWriterStatusWriting)
272 m_state.storeRelease(WriterStatePaused);
278 if (m_state.loadAcquire() != WriterStatePaused)
280 if ([m_assetWriter status] != AVAssetWriterStatusWriting)
283 m_state.storeRelease(WriterStateActive);
286- (
void)setStartTimeFrom:(CMSampleBufferRef)sampleBuffer
289 Q_ASSERT(m_setStartTime);
290 Q_ASSERT(sampleBuffer);
292 if (m_state.loadAcquire() != WriterStateActive)
295 QMetaObject::invokeMethod(m_delegate,
"assetWriterStarted", Qt::QueuedConnection);
297 m_durationInMs.storeRelease(0);
298 m_startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
299 m_lastTimeStamp = m_startTime;
300 [m_assetWriter startSessionAtSourceTime:m_startTime];
301 m_setStartTime =
false;
304- (QCFType<CMSampleBufferRef>)adjustTime:(
const QCFType<CMSampleBufferRef> &)sample
308 CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count);
309 CMSampleTimingInfo* timingInfo = (CMSampleTimingInfo*) malloc(
sizeof(CMSampleTimingInfo) * count);
310 CMSampleBufferGetSampleTimingInfoArray(sample, count, timingInfo, &count);
311 for (CMItemCount i = 0; i < count; i++)
313 timingInfo[i].decodeTimeStamp = CMTimeSubtract(timingInfo[i].decodeTimeStamp, offset);
314 timingInfo[i].presentationTimeStamp = CMTimeSubtract(timingInfo[i].presentationTimeStamp, offset);
316 CMSampleBufferRef updatedBuffer;
317 CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, sample, count, timingInfo, &updatedBuffer);
319 return updatedBuffer;
322- (
void)writeVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
325 Q_ASSERT(sampleBuffer);
327 if (m_state.loadAcquire() == WriterStateActive) {
329 [self setStartTimeFrom:sampleBuffer];
331 if (m_cameraWriterInput.data().readyForMoreMediaData) {
332 [self updateDuration:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
333 [m_cameraWriterInput appendSampleBuffer:sampleBuffer];
338- (
void)writeAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer
340 Q_ASSERT(sampleBuffer);
343 if (m_state.loadAcquire() == WriterStateActive) {
345 [self setStartTimeFrom:sampleBuffer];
347 if (m_audioWriterInput.data().readyForMoreMediaData) {
348 [self updateDuration:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
349 [m_audioWriterInput appendSampleBuffer:sampleBuffer];
354- (
void)captureOutput:(AVCaptureOutput *)captureOutput
355 didOutputSampleBuffer:(CMSampleBufferRef)buffer
356 fromConnection:(AVCaptureConnection *)connection
358 Q_UNUSED(connection);
359 Q_ASSERT(m_service && m_service->session());
361 if (m_state.loadAcquire() != WriterStateActive && m_state.loadAcquire() != WriterStatePaused)
364 if ([m_assetWriter status] != AVAssetWriterStatusWriting) {
365 if ([m_assetWriter status] == AVAssetWriterStatusFailed) {
366 NSError *error = [m_assetWriter error];
367 NSString *failureReason = error.localizedFailureReason;
368 NSString *suggestion = error.localizedRecoverySuggestion;
369 NSString *errorString = suggestion ? [failureReason stringByAppendingString:suggestion] : failureReason;
370 QMetaObject::invokeMethod(m_delegate,
"assetWriterError",
371 Qt::QueuedConnection,
372 Q_ARG(QString, QString::fromNSString(errorString)));
377 if (!CMSampleBufferDataIsReady(buffer)) {
378 qWarning() << Q_FUNC_INFO <<
"sample buffer is not ready, skipping.";
383 auto sampleBuffer = QCFType<CMSampleBufferRef>::constructFromGet(buffer);
385 bool isVideoBuffer =
true;
386 isVideoBuffer = (captureOutput != m_service->session()->audioOutput());
390 if (m_service->session()->videoOutput()) {
391 NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> *vfDelegate =
392 (NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> *)m_service->session()->videoOutput()->captureDelegate();
394 AVCaptureOutput *output = nil;
395 AVCaptureConnection *connection = nil;
396 [vfDelegate captureOutput:output didOutputSampleBuffer:sampleBuffer fromConnection:connection];
400 if (m_service->session()->audioOutput()) {
401 NSObject<AVCaptureAudioDataOutputSampleBufferDelegate> *audioPreviewDelegate =
402 (NSObject<AVCaptureAudioDataOutputSampleBufferDelegate> *)m_service->session()->audioPreviewDelegate();
403 if (audioPreviewDelegate) {
404 AVCaptureOutput *output = nil;
405 AVCaptureConnection *connection = nil;
406 [audioPreviewDelegate captureOutput:output didOutputSampleBuffer:sampleBuffer fromConnection:connection];
411 if (m_state.loadAcquire() != WriterStateActive)
415 CMTime currentTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
416 CMTime lastTimestamp = isVideoBuffer ? m_lastVideoTimestamp : m_lastAudioTimestamp;
418 if (!CMTIME_IS_INVALID(lastTimestamp)) {
419 if (!CMTIME_IS_INVALID(m_timeOffset))
420 currentTimestamp = CMTimeSubtract(currentTimestamp, m_timeOffset);
422 CMTime pauseDuration = CMTimeSubtract(currentTimestamp, lastTimestamp);
424 if (m_timeOffset.value == 0)
425 m_timeOffset = pauseDuration;
427 m_timeOffset = CMTimeAdd(m_timeOffset, pauseDuration);
429 m_lastVideoTimestamp = kCMTimeInvalid;
430 m_adjustTime =
false;
433 if (m_timeOffset.value > 0) {
434 sampleBuffer = [self adjustTime:sampleBuffer by:m_timeOffset];
437 CMTime currentTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
438 CMTime currentDuration = CMSampleBufferGetDuration(sampleBuffer);
439 if (currentDuration.value > 0)
440 currentTimestamp = CMTimeAdd(currentTimestamp, currentDuration);
444 m_lastVideoTimestamp = currentTimestamp;
445 dispatch_async(m_writerQueue, ^{
446 [self writeVideoSampleBuffer:sampleBuffer];
447 m_writeFirstAudioBuffer =
true;
449 }
else if (m_writeFirstAudioBuffer) {
450 m_lastAudioTimestamp = currentTimestamp;
451 dispatch_async(m_writerQueue, ^{
452 [self writeAudioSampleBuffer:sampleBuffer];
457- (
bool)addWriterInputs
459 Q_ASSERT(m_service && m_service->session());
460 Q_ASSERT(m_assetWriter.data());
462 AVFCameraSession *session = m_service->session();
464 m_cameraWriterInput.reset();
467 Q_ASSERT(session->videoCaptureDevice() && session->videoOutput() && session->videoOutput()->videoDataOutput());
469 m_cameraWriterInput.reset([[AVAssetWriterInput alloc]
470 initWithMediaType:AVMediaTypeVideo
471 outputSettings:m_videoSettings
472 sourceFormatHint:session->videoCaptureDevice()
473 .activeFormat.formatDescription]);
474 } @
catch (NSException *exception) {
475 qCWarning(qLcCamera) << Q_FUNC_INFO <<
"Failed to create video writer input:"
476 << QString::fromNSString(exception.reason);
477 m_cameraWriterInput.reset();
482 if (m_cameraWriterInput && [m_assetWriter canAddInput:m_cameraWriterInput]) {
483 [m_assetWriter addInput:m_cameraWriterInput];
485 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"failed to add camera writer input";
486 m_cameraWriterInput.reset();
489 } @
catch (NSException *exception) {
490 qCWarning(qLcCamera) << Q_FUNC_INFO <<
"Failed to add video input:"
491 << QString::fromNSString(exception.reason);
492 m_cameraWriterInput.reset();
496 m_cameraWriterInput.data().expectsMediaDataInRealTime = YES;
499 m_audioWriterInput.reset();
502 m_audioWriterInput.reset([[AVAssetWriterInput alloc]
503 initWithMediaType:AVMediaTypeAudio
504 outputSettings:m_audioSettings]);
505 } @
catch (NSException *exception) {
506 qCWarning(qLcCamera) << Q_FUNC_INFO <<
"Failed to create audio writer input:"
507 << QString::fromNSString(exception.reason);
508 m_audioWriterInput.reset();
510 if (!m_cameraWriterInput)
513 if (!m_audioWriterInput) {
514 qWarning() << Q_FUNC_INFO <<
"failed to create audio writer input";
516 if (!m_cameraWriterInput)
520 if ([m_assetWriter canAddInput:m_audioWriterInput]) {
521 [m_assetWriter addInput:m_audioWriterInput];
522 m_audioWriterInput.data().expectsMediaDataInRealTime = YES;
524 qWarning() << Q_FUNC_INFO <<
"failed to add audio writer input";
525 m_audioWriterInput.reset();
526 if (!m_cameraWriterInput)
530 } @
catch (NSException *exception) {
533 <<
"Failed to add audio input:" << QString::fromNSString(exception.reason);
534 m_audioWriterInput.reset();
535 if (!m_cameraWriterInput)
547 Q_ASSERT(m_service && m_service->session());
548 AVFCameraSession *session = m_service->session();
551 Q_ASSERT(session->videoOutput() && session->videoOutput()->videoDataOutput());
552 [session->videoOutput()->videoDataOutput() setSampleBufferDelegate:self queue:m_videoQueue];
556 Q_ASSERT(session->audioOutput());
557 [session->audioOutput() setSampleBufferDelegate:self queue:m_audioQueue];
561- (
void)updateDuration:(CMTime)newTimeStamp
563 Q_ASSERT(CMTIME_IS_VALID(m_startTime));
564 Q_ASSERT(CMTIME_IS_VALID(m_lastTimeStamp));
565 if (CMTimeCompare(newTimeStamp, m_lastTimeStamp) > 0) {
567 const CMTime duration = CMTimeSubtract(newTimeStamp, m_startTime);
568 if (CMTIME_IS_INVALID(duration))
571 m_durationInMs.storeRelease(CMTimeGetSeconds(duration) * 1000);
572 m_lastTimeStamp = newTimeStamp;
574 m_delegate->updateDuration([self durationInMs]);
578- (qint64)durationInMs
580 return m_durationInMs.loadAcquire();
AVFCameraSession * session() const
bool qt_capture_session_isValid(AVFCameraService *service)