6#include <camera/avfcamerarenderer_p.h>
7#include <camera/avfcameraservice_p.h>
8#include <camera/avfcamerasession_p.h>
9#include <camera/avfmediaencoder_p.h>
10#include <common/avfmetadata_p.h>
11#include <qdarwinformatsinfo_p.h>
13#include <QtMultimedia/private/qavfcameradebug_p.h>
14#include <QtCore/qatomic.h>
15#include <QtCore/qmetaobject.h>
16#include <QtCore/private/qcore_mac_p.h>
28 if (!session->captureSession())
31 if (!session->videoInput() && !session->audioInput())
45using AVFAtomicInt64 = QAtomicInteger<qint64>;
49@interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) (PrivateAPI)
50- (
bool)addWriterInputs;
52- (
void)updateDuration:(CMTime)newTimeStamp;
53- (QCFType<CMSampleBufferRef>)adjustTime:(
const QCFType<CMSampleBufferRef> &)sample
57@implementation QT_MANGLE_NAMESPACE(AVFMediaAssetWriter)
60 AVFCameraService *m_service;
62 AVFScopedPointer<AVAssetWriterInput> m_cameraWriterInput;
63 AVFScopedPointer<AVAssetWriterInput> m_audioWriterInput;
66 QCFType<CMSampleBufferRef> m_pendingAudioBuffer;
67 bool m_audioFormatStabilized;
70 AVFScopedPointer<dispatch_queue_t> m_writerQueue;
72 AVFScopedPointer<dispatch_queue_t> m_videoQueue;
74 AVFScopedPointer<dispatch_queue_t> m_audioQueue;
76 AVFScopedPointer<AVAssetWriter> m_assetWriter;
78 AVFMediaEncoder *m_delegate;
84 bool m_writeFirstAudioBuffer;
87 CMTime m_lastTimeStamp;
88 CMTime m_lastVideoTimestamp;
89 CMTime m_lastAudioTimestamp;
93 NSDictionary *m_audioSettings;
94 NSDictionary *m_videoSettings;
96 AVFAtomicInt64 m_durationInMs;
99- (
id)initWithDelegate:(AVFMediaEncoder *)delegate
103 if (self = [super init]) {
104 m_delegate = delegate;
105 m_setStartTime =
true;
106 m_state.storeRelaxed(WriterStateIdle);
112- (
bool)setupWithFileURL:(NSURL *)fileURL
113 cameraService:(AVFCameraService *)service
114 audioSettings:(NSDictionary *)audioSettings
115 videoSettings:(NSDictionary *)videoSettings
116 fileFormat:(QMediaFormat::FileFormat)fileFormat
117 transform:(CGAffineTransform)transform
121 if (!qt_capture_session_isValid(service)) {
122 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"invalid capture session";
127 m_audioSettings = audioSettings;
128 m_videoSettings = videoSettings;
130 AVFCameraSession *session = m_service->session();
132 m_writerQueue.reset(dispatch_queue_create(
"asset-writer-queue", DISPATCH_QUEUE_SERIAL));
133 if (!m_writerQueue) {
134 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"failed to create an asset writer's queue";
138 m_videoQueue.reset();
139 if (session->videoInput() && session->videoOutput() && session->videoOutput()->videoDataOutput()) {
140 m_videoQueue.reset(dispatch_queue_create(
"video-output-queue", DISPATCH_QUEUE_SERIAL));
142 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"failed to create video queue";
145 dispatch_set_target_queue(m_videoQueue, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0));
148 m_audioQueue.reset();
149 if (session->audioInput() && session->audioOutput()) {
150 m_audioQueue.reset(dispatch_queue_create(
"audio-output-queue", DISPATCH_QUEUE_SERIAL));
152 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"failed to create audio queue";
159 auto fileType = QDarwinFormatInfo::avFileTypeForContainerFormat(fileFormat);
160 m_assetWriter.reset([[AVAssetWriter alloc] initWithURL:fileURL
163 if (!m_assetWriter) {
164 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"failed to create asset writer";
169 m_writeFirstAudioBuffer =
true;
171 if (![self addWriterInputs]) {
172 m_assetWriter.reset();
176 if (m_cameraWriterInput)
177 m_cameraWriterInput.data().transform = transform;
179 [self setMetaData:fileType];
185- (
void)setMetaData:(AVFileType)fileType
187 m_assetWriter.data().metadata = AVFMetaData::toAVMetadataForFormat(m_delegate->metaData(), fileType);
194 m_setStartTime =
true;
195 m_audioFormatStabilized =
false;
196 m_pendingAudioBuffer =
nullptr;
198 m_state.storeRelease(WriterStateActive);
200 [m_assetWriter startWriting];
201 AVCaptureSession *session = m_service->session()->captureSession();
202 if (!session.running)
203 [session startRunning];
208 if (m_state.loadAcquire() != WriterStateActive && m_state.loadAcquire() != WriterStatePaused)
211 if ([m_assetWriter status] != AVAssetWriterStatusWriting
212 && [m_assetWriter status] != AVAssetWriterStatusFailed)
219 m_state.storeRelease(WriterStateIdle);
224 dispatch_sync(m_writerQueue, ^{});
228 dispatch_sync(m_videoQueue, ^{});
231 [m_assetWriter finishWritingWithCompletionHandler:^{
234 if (m_state.loadAcquire() == WriterStateAborted)
237 AVCaptureSession *session = m_service->session()->captureSession();
239 [session stopRunning];
240 QMetaObject::invokeMethod(m_delegate,
"assetWriterFinished", Qt::QueuedConnection);
248 if (m_state.fetchAndStoreRelease(WriterStateAborted) != WriterStateActive) {
260 dispatch_sync(m_writerQueue, ^{});
264 dispatch_sync(m_videoQueue, ^{});
268 [m_assetWriter finishWritingWithCompletionHandler:^{
274 if (m_state.loadAcquire() != WriterStateActive)
276 if ([m_assetWriter status] != AVAssetWriterStatusWriting)
279 m_state.storeRelease(WriterStatePaused);
285 if (m_state.loadAcquire() != WriterStatePaused)
287 if ([m_assetWriter status] != AVAssetWriterStatusWriting)
290 m_state.storeRelease(WriterStateActive);
293- (
void)setStartTimeFrom:(CMSampleBufferRef)sampleBuffer
296 Q_ASSERT(m_setStartTime);
297 Q_ASSERT(sampleBuffer);
299 if (m_state.loadAcquire() != WriterStateActive)
302 QMetaObject::invokeMethod(m_delegate,
"assetWriterStarted", Qt::QueuedConnection);
304 m_durationInMs.storeRelease(0);
305 m_startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
306 m_lastTimeStamp = m_startTime;
307 [m_assetWriter startSessionAtSourceTime:m_startTime];
308 m_setStartTime =
false;
311- (QCFType<CMSampleBufferRef>)adjustTime:(
const QCFType<CMSampleBufferRef> &)sample
315 CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count);
316 CMSampleTimingInfo* timingInfo = (CMSampleTimingInfo*) malloc(
sizeof(CMSampleTimingInfo) * count);
317 CMSampleBufferGetSampleTimingInfoArray(sample, count, timingInfo, &count);
318 for (CMItemCount i = 0; i < count; i++)
320 timingInfo[i].decodeTimeStamp = CMTimeSubtract(timingInfo[i].decodeTimeStamp, offset);
321 timingInfo[i].presentationTimeStamp = CMTimeSubtract(timingInfo[i].presentationTimeStamp, offset);
323 CMSampleBufferRef updatedBuffer;
324 CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, sample, count, timingInfo, &updatedBuffer);
326 return updatedBuffer;
329- (
void)writeVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
332 Q_ASSERT(sampleBuffer);
334 if (m_state.loadAcquire() == WriterStateActive) {
336 [self setStartTimeFrom:sampleBuffer];
338 if (m_cameraWriterInput.data().readyForMoreMediaData) {
339 [self updateDuration:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
340 [m_cameraWriterInput appendSampleBuffer:sampleBuffer];
345- (
void)writeAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer
347 Q_ASSERT(sampleBuffer);
350 if (m_state.loadAcquire() == WriterStateActive) {
352 [self setStartTimeFrom:sampleBuffer];
365 if (!m_audioFormatStabilized) {
366 if (!m_pendingAudioBuffer) {
367 m_pendingAudioBuffer = QCFType<CMSampleBufferRef>::constructFromGet(sampleBuffer);
371 CMFormatDescriptionRef pendingFormat =
372 CMSampleBufferGetFormatDescription(m_pendingAudioBuffer);
373 CMFormatDescriptionRef currentFormat = CMSampleBufferGetFormatDescription(sampleBuffer);
375 if (pendingFormat && currentFormat
376 && CMFormatDescriptionEqual(pendingFormat, currentFormat)) {
377 m_audioFormatStabilized =
true;
380 if (m_audioWriterInput.data().readyForMoreMediaData) {
381 [self updateDuration:CMSampleBufferGetPresentationTimeStamp(
382 m_pendingAudioBuffer)];
383 [m_audioWriterInput appendSampleBuffer:m_pendingAudioBuffer];
385 m_pendingAudioBuffer =
nullptr;
387 qCDebug(qLcCamera) <<
"Audio format changed, discarding pending buffer";
388 m_pendingAudioBuffer = QCFType<CMSampleBufferRef>::constructFromGet(sampleBuffer);
393 if (m_audioWriterInput.data().readyForMoreMediaData) {
394 [self updateDuration:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
395 [m_audioWriterInput appendSampleBuffer:sampleBuffer];
400- (
void)captureOutput:(AVCaptureOutput *)captureOutput
401 didOutputSampleBuffer:(CMSampleBufferRef)buffer
402 fromConnection:(AVCaptureConnection *)connection
404 Q_UNUSED(connection);
405 Q_ASSERT(m_service && m_service->session());
407 if (m_state.loadAcquire() != WriterStateActive && m_state.loadAcquire() != WriterStatePaused)
410 if ([m_assetWriter status] != AVAssetWriterStatusWriting) {
411 if ([m_assetWriter status] == AVAssetWriterStatusFailed) {
412 NSError *error = [m_assetWriter error];
413 NSString *failureReason = error.localizedFailureReason;
414 NSString *suggestion = error.localizedRecoverySuggestion;
415 NSString *errorString = suggestion ? [failureReason stringByAppendingString:suggestion] : failureReason;
416 QMetaObject::invokeMethod(m_delegate,
"assetWriterError",
417 Qt::QueuedConnection,
418 Q_ARG(QString, QString::fromNSString(errorString)));
423 if (!CMSampleBufferDataIsReady(buffer)) {
424 qWarning() << Q_FUNC_INFO <<
"sample buffer is not ready, skipping.";
429 auto sampleBuffer = QCFType<CMSampleBufferRef>::constructFromGet(buffer);
431 bool isVideoBuffer =
true;
432 isVideoBuffer = (captureOutput != m_service->session()->audioOutput());
436 if (m_service->session()->videoOutput()) {
437 NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> *vfDelegate =
438 (NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> *)m_service->session()->videoOutput()->captureDelegate();
440 AVCaptureOutput *output = nil;
441 AVCaptureConnection *connection = nil;
442 [vfDelegate captureOutput:output didOutputSampleBuffer:sampleBuffer fromConnection:connection];
446 if (m_service->session()->audioOutput()) {
447 NSObject<AVCaptureAudioDataOutputSampleBufferDelegate> *audioPreviewDelegate =
448 (NSObject<AVCaptureAudioDataOutputSampleBufferDelegate> *)m_service->session()->audioPreviewDelegate();
449 if (audioPreviewDelegate) {
450 AVCaptureOutput *output = nil;
451 AVCaptureConnection *connection = nil;
452 [audioPreviewDelegate captureOutput:output didOutputSampleBuffer:sampleBuffer fromConnection:connection];
457 if (m_state.loadAcquire() != WriterStateActive)
461 CMTime currentTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
462 CMTime lastTimestamp = isVideoBuffer ? m_lastVideoTimestamp : m_lastAudioTimestamp;
464 if (!CMTIME_IS_INVALID(lastTimestamp)) {
465 if (!CMTIME_IS_INVALID(m_timeOffset))
466 currentTimestamp = CMTimeSubtract(currentTimestamp, m_timeOffset);
468 CMTime pauseDuration = CMTimeSubtract(currentTimestamp, lastTimestamp);
470 if (m_timeOffset.value == 0)
471 m_timeOffset = pauseDuration;
473 m_timeOffset = CMTimeAdd(m_timeOffset, pauseDuration);
475 m_lastVideoTimestamp = kCMTimeInvalid;
476 m_adjustTime =
false;
479 if (m_timeOffset.value > 0) {
480 sampleBuffer = [self adjustTime:sampleBuffer by:m_timeOffset];
483 CMTime currentTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
484 CMTime currentDuration = CMSampleBufferGetDuration(sampleBuffer);
485 if (currentDuration.value > 0)
486 currentTimestamp = CMTimeAdd(currentTimestamp, currentDuration);
490 m_lastVideoTimestamp = currentTimestamp;
491 dispatch_async(m_writerQueue, ^{
492 [self writeVideoSampleBuffer:sampleBuffer];
493 m_writeFirstAudioBuffer =
true;
495 }
else if (m_writeFirstAudioBuffer) {
496 m_lastAudioTimestamp = currentTimestamp;
497 dispatch_async(m_writerQueue, ^{
498 [self writeAudioSampleBuffer:sampleBuffer];
503- (
bool)addWriterInputs
505 Q_ASSERT(m_service && m_service->session());
506 Q_ASSERT(m_assetWriter.data());
508 AVFCameraSession *session = m_service->session();
510 m_cameraWriterInput.reset();
513 Q_ASSERT(session->videoCaptureDevice() && session->videoOutput() && session->videoOutput()->videoDataOutput());
515 m_cameraWriterInput.reset([[AVAssetWriterInput alloc]
516 initWithMediaType:AVMediaTypeVideo
517 outputSettings:m_videoSettings
518 sourceFormatHint:session->videoCaptureDevice()
519 .activeFormat.formatDescription]);
520 } @
catch (NSException *exception) {
521 qCWarning(qLcCamera) << Q_FUNC_INFO <<
"Failed to create video writer input:"
522 << QString::fromNSString(exception.reason);
523 m_cameraWriterInput.reset();
528 if (m_cameraWriterInput && [m_assetWriter canAddInput:m_cameraWriterInput]) {
529 [m_assetWriter addInput:m_cameraWriterInput];
531 qCDebug(qLcCamera) << Q_FUNC_INFO <<
"failed to add camera writer input";
532 m_cameraWriterInput.reset();
535 } @
catch (NSException *exception) {
536 qCWarning(qLcCamera) << Q_FUNC_INFO <<
"Failed to add video input:"
537 << QString::fromNSString(exception.reason);
538 m_cameraWriterInput.reset();
542 m_cameraWriterInput.data().expectsMediaDataInRealTime = YES;
545 m_audioWriterInput.reset();
548 m_audioWriterInput.reset([[AVAssetWriterInput alloc]
549 initWithMediaType:AVMediaTypeAudio
550 outputSettings:m_audioSettings]);
551 } @
catch (NSException *exception) {
552 qCWarning(qLcCamera) << Q_FUNC_INFO <<
"Failed to create audio writer input:"
553 << QString::fromNSString(exception.reason);
554 m_audioWriterInput.reset();
556 if (!m_cameraWriterInput)
559 if (!m_audioWriterInput) {
560 qWarning() << Q_FUNC_INFO <<
"failed to create audio writer input";
562 if (!m_cameraWriterInput)
566 if ([m_assetWriter canAddInput:m_audioWriterInput]) {
567 [m_assetWriter addInput:m_audioWriterInput];
568 m_audioWriterInput.data().expectsMediaDataInRealTime = YES;
570 qWarning() << Q_FUNC_INFO <<
"failed to add audio writer input";
571 m_audioWriterInput.reset();
572 if (!m_cameraWriterInput)
576 } @
catch (NSException *exception) {
579 <<
"Failed to add audio input:" << QString::fromNSString(exception.reason);
580 m_audioWriterInput.reset();
581 if (!m_cameraWriterInput)
593 Q_ASSERT(m_service && m_service->session());
594 AVFCameraSession *session = m_service->session();
597 Q_ASSERT(session->videoOutput() && session->videoOutput()->videoDataOutput());
598 [session->videoOutput()->videoDataOutput() setSampleBufferDelegate:self queue:m_videoQueue];
602 Q_ASSERT(session->audioOutput());
603 [session->audioOutput() setSampleBufferDelegate:self queue:m_audioQueue];
607- (
void)updateDuration:(CMTime)newTimeStamp
609 Q_ASSERT(CMTIME_IS_VALID(m_startTime));
610 Q_ASSERT(CMTIME_IS_VALID(m_lastTimeStamp));
611 if (CMTimeCompare(newTimeStamp, m_lastTimeStamp) > 0) {
613 const CMTime duration = CMTimeSubtract(newTimeStamp, m_startTime);
614 if (CMTIME_IS_INVALID(duration))
617 m_durationInMs.storeRelease(CMTimeGetSeconds(duration) * 1000);
618 m_lastTimeStamp = newTimeStamp;
620 m_delegate->updateDuration([self durationInMs]);
624- (qint64)durationInMs
626 return m_durationInMs.loadAcquire();
AVFCameraSession * session() const
bool qt_capture_session_isValid(AVFCameraService *service)