summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimur Pocheptsov <Timur.Pocheptsov@digia.com>2015-03-20 18:33:28 +0100
committerYoann Lopes <yoann.lopes@theqtcompany.com>2015-05-29 16:29:39 +0000
commit1508f775acfd7aad18e71dde35c3ff0c9b073fc1 (patch)
tree81e6ab4c4762e28a2a035db358456afc006f9435
parentaeb79d4a8bcb291822d74d923f7e68fb02ce96fe (diff)
downloadqtmultimedia-1508f775acfd7aad18e71dde35c3ff0c9b073fc1.tar.gz
Video asset writer for iOS
AVFoundation on iOS lacks the ability to use AVCaptureVideoDataOutput and AVCaptureMovieFileOutput simultaneously. Right now viewfinder stops working as soon as we add movie file output. The only workaround we have now is to write video/audio 'maually' - creating asset writer and feeding it with audio/video samples. Change-Id: I33a63546783279c545f0433b5051287269825d3f Task-number: QTBUG-37655 Reviewed-by: Yoann Lopes <yoann.lopes@theqtcompany.com>
-rw-r--r--src/plugins/avfoundation/camera/avfcamerarenderercontrol.h6
-rw-r--r--src/plugins/avfoundation/camera/avfcamerarenderercontrol.mm26
-rw-r--r--src/plugins/avfoundation/camera/avfcameraservice.h9
-rw-r--r--src/plugins/avfoundation/camera/avfcameraservice.mm31
-rw-r--r--src/plugins/avfoundation/camera/avfcamerasession.h2
-rw-r--r--src/plugins/avfoundation/camera/avfcamerautility.h68
-rw-r--r--src/plugins/avfoundation/camera/avfmediaassetwriter.h119
-rw-r--r--src/plugins/avfoundation/camera/avfmediaassetwriter.mm474
-rw-r--r--src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h108
-rw-r--r--src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm349
-rw-r--r--src/plugins/avfoundation/camera/camera.pro17
11 files changed, 1199 insertions, 10 deletions
diff --git a/src/plugins/avfoundation/camera/avfcamerarenderercontrol.h b/src/plugins/avfoundation/camera/avfcamerarenderercontrol.h
index 92ab75bd0..b8f92d9ca 100644
--- a/src/plugins/avfoundation/camera/avfcamerarenderercontrol.h
+++ b/src/plugins/avfoundation/camera/avfcamerarenderercontrol.h
@@ -63,6 +63,11 @@ public:
AVCaptureVideoDataOutput *videoDataOutput() const;
+#ifdef Q_OS_IOS
+ AVFCaptureFramesDelegate *captureDelegate() const;
+ void resetCaptureDelegate() const;
+#endif
+
Q_SIGNALS:
void surfaceChanged(QAbstractVideoSurface *surface);
@@ -80,6 +85,7 @@ private:
QVideoFrame m_lastViewfinderFrame;
QMutex m_vfMutex;
+ dispatch_queue_t m_delegateQueue;
};
QT_END_NAMESPACE
diff --git a/src/plugins/avfoundation/camera/avfcamerarenderercontrol.mm b/src/plugins/avfoundation/camera/avfcamerarenderercontrol.mm
index 1fa1df99e..dd838d9b5 100644
--- a/src/plugins/avfoundation/camera/avfcamerarenderercontrol.mm
+++ b/src/plugins/avfoundation/camera/avfcamerarenderercontrol.mm
@@ -143,6 +143,7 @@ private:
- (void) captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection;
+
@end
@implementation AVFCaptureFramesDelegate
@@ -163,6 +164,9 @@ private:
Q_UNUSED(connection);
Q_UNUSED(captureOutput);
+ // NB: on iOS captureOutput/connection can be nil (when recording a video -
+ // avfmediaassetwriter).
+
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
int width = CVPixelBufferGetWidth(imageBuffer);
@@ -176,6 +180,7 @@ private:
QVideoFrame frame(new CVPixelBufferVideoBuffer(imageBuffer), QSize(width, height), format);
m_renderer->syncHandleViewfinderFrame(frame);
}
+
@end
@@ -191,6 +196,8 @@ AVFCameraRendererControl::~AVFCameraRendererControl()
{
[m_cameraSession->captureSession() removeOutput:m_videoDataOutput];
[m_viewfinderFramesDelegate release];
+ if (m_delegateQueue)
+ dispatch_release(m_delegateQueue);
}
QAbstractVideoSurface *AVFCameraRendererControl::surface() const
@@ -217,11 +224,10 @@ void AVFCameraRendererControl::configureAVCaptureSession(AVFCameraSession *camer
m_videoDataOutput = [[[AVCaptureVideoDataOutput alloc] init] autorelease];
// Configure video output
- dispatch_queue_t queue = dispatch_queue_create("vf_queue", NULL);
+ m_delegateQueue = dispatch_queue_create("vf_queue", NULL);
[m_videoDataOutput
setSampleBufferDelegate:m_viewfinderFramesDelegate
- queue:queue];
- dispatch_release(queue);
+ queue:m_delegateQueue];
[m_cameraSession->captureSession() addOutput:m_videoDataOutput];
}
@@ -279,6 +285,20 @@ AVCaptureVideoDataOutput *AVFCameraRendererControl::videoDataOutput() const
return m_videoDataOutput;
}
+#ifdef Q_OS_IOS
+
+AVFCaptureFramesDelegate *AVFCameraRendererControl::captureDelegate() const
+{
+ return m_viewfinderFramesDelegate;
+}
+
+void AVFCameraRendererControl::resetCaptureDelegate() const
+{
+ [m_videoDataOutput setSampleBufferDelegate:m_viewfinderFramesDelegate queue:m_delegateQueue];
+}
+
+#endif
+
void AVFCameraRendererControl::handleViewfinderFrame()
{
QVideoFrame frame;
diff --git a/src/plugins/avfoundation/camera/avfcameraservice.h b/src/plugins/avfoundation/camera/avfcameraservice.h
index d557872a9..08b0ad26d 100644
--- a/src/plugins/avfoundation/camera/avfcameraservice.h
+++ b/src/plugins/avfoundation/camera/avfcameraservice.h
@@ -41,13 +41,13 @@
QT_BEGIN_NAMESPACE
class QCameraControl;
+class QMediaRecorderControl;
class AVFCameraControl;
class AVFCameraInfoControl;
class AVFCameraMetaDataControl;
class AVFVideoWindowControl;
class AVFVideoWidgetControl;
class AVFCameraRendererControl;
-class AVFMediaRecorderControl;
class AVFImageCaptureControl;
class AVFCameraSession;
class AVFCameraDeviceControl;
@@ -59,6 +59,8 @@ class AVFCameraViewfinderSettingsControl2;
class AVFCameraViewfinderSettingsControl;
class AVFImageEncoderControl;
class AVFCameraFlashControl;
+class AVFMediaRecorderControl;
+class AVFMediaRecorderControlIOS;
class AVFCameraService : public QMediaService
{
@@ -75,7 +77,8 @@ public:
AVFCameraDeviceControl *videoDeviceControl() const { return m_videoDeviceControl; }
AVFAudioInputSelectorControl *audioInputSelectorControl() const { return m_audioInputSelectorControl; }
AVFCameraMetaDataControl *metaDataControl() const { return m_metaDataControl; }
- AVFMediaRecorderControl *recorderControl() const { return m_recorderControl; }
+ AVFMediaRecorderControl *recorderControl() const;
+ AVFMediaRecorderControlIOS *recorderControlIOS() const;
AVFImageCaptureControl *imageCaptureControl() const { return m_imageCaptureControl; }
AVFCameraFocusControl *cameraFocusControl() const { return m_cameraFocusControl; }
AVFCameraExposureControl *cameraExposureControl() const {return m_cameraExposureControl; }
@@ -94,7 +97,7 @@ private:
AVFAudioInputSelectorControl *m_audioInputSelectorControl;
AVFCameraRendererControl *m_videoOutput;
AVFCameraMetaDataControl *m_metaDataControl;
- AVFMediaRecorderControl *m_recorderControl;
+ QMediaRecorderControl *m_recorderControl;
AVFImageCaptureControl *m_imageCaptureControl;
AVFCameraFocusControl *m_cameraFocusControl;
AVFCameraExposureControl *m_cameraExposureControl;
diff --git a/src/plugins/avfoundation/camera/avfcameraservice.mm b/src/plugins/avfoundation/camera/avfcameraservice.mm
index f163e1299..fd473b37b 100644
--- a/src/plugins/avfoundation/camera/avfcameraservice.mm
+++ b/src/plugins/avfoundation/camera/avfcameraservice.mm
@@ -56,6 +56,7 @@
#ifdef Q_OS_IOS
#include "avfcamerazoomcontrol.h"
+#include "avfmediarecordercontrol_ios.h"
#endif
#include <private/qmediaplaylistnavigator_p.h>
@@ -74,7 +75,14 @@ AVFCameraService::AVFCameraService(QObject *parent):
m_audioInputSelectorControl = new AVFAudioInputSelectorControl(this);
m_metaDataControl = new AVFCameraMetaDataControl(this);
+#ifndef Q_OS_IOS
+ // This will connect a slot to 'captureModeChanged'
+ // and will break viewfinder by attaching AVCaptureMovieFileOutput
+ // in this slot.
m_recorderControl = new AVFMediaRecorderControl(this);
+#else
+ m_recorderControl = new AVFMediaRecorderControlIOS(this);
+#endif
m_imageCaptureControl = new AVFImageCaptureControl(this);
m_cameraFocusControl = new AVFCameraFocusControl(this);
m_cameraExposureControl = 0;
@@ -97,6 +105,10 @@ AVFCameraService::~AVFCameraService()
{
m_cameraControl->setState(QCamera::UnloadedState);
+#ifdef Q_OS_IOS
+ delete m_recorderControl;
+#endif
+
if (m_videoOutput) {
m_session->setVideoOutput(0);
delete m_videoOutput;
@@ -205,4 +217,23 @@ void AVFCameraService::releaseControl(QMediaControl *control)
}
+AVFMediaRecorderControl *AVFCameraService::recorderControl() const
+{
+#ifdef Q_OS_IOS
+ return 0;
+#else
+ return static_cast<AVFMediaRecorderControl *>(m_recorderControl);
+#endif
+}
+
+AVFMediaRecorderControlIOS *AVFCameraService::recorderControlIOS() const
+{
+#ifdef Q_OS_OSX
+ return 0;
+#else
+ return static_cast<AVFMediaRecorderControlIOS *>(m_recorderControl);
+#endif
+}
+
+
#include "moc_avfcameraservice.cpp"
diff --git a/src/plugins/avfoundation/camera/avfcamerasession.h b/src/plugins/avfoundation/camera/avfcamerasession.h
index 7b25a99c3..2b322cfaf 100644
--- a/src/plugins/avfoundation/camera/avfcamerasession.h
+++ b/src/plugins/avfoundation/camera/avfcamerasession.h
@@ -83,6 +83,8 @@ public:
void removeProbe(AVFMediaVideoProbeControl *probe);
FourCharCode defaultCodec();
+ AVCaptureDeviceInput *videoInput() const {return m_videoInput;}
+
public Q_SLOTS:
void setState(QCamera::State state);
diff --git a/src/plugins/avfoundation/camera/avfcamerautility.h b/src/plugins/avfoundation/camera/avfcamerautility.h
index 03a61460f..42005a502 100644
--- a/src/plugins/avfoundation/camera/avfcamerautility.h
+++ b/src/plugins/avfoundation/camera/avfcamerautility.h
@@ -78,6 +78,74 @@ private:
bool m_locked;
};
+struct AVFObjectDeleter {
+ static void cleanup(NSObject *obj)
+ {
+ if (obj)
+ [obj release];
+ }
+};
+
+template<class T>
+class AVFScopedPointer : public QScopedPointer<NSObject, AVFObjectDeleter>
+{
+public:
+ AVFScopedPointer() {}
+ explicit AVFScopedPointer(T *ptr) : QScopedPointer(ptr) {}
+ operator T*() const
+ {
+ // Quite handy operator to enable Obj-C messages: [ptr someMethod];
+ return data();
+ }
+
+ T *data() const
+ {
+ return static_cast<T *>(QScopedPointer::data());
+ }
+
+ T *take()
+ {
+ return static_cast<T *>(QScopedPointer::take());
+ }
+};
+
+template<>
+class AVFScopedPointer<dispatch_queue_t>
+{
+public:
+ AVFScopedPointer() : m_queue(0) {}
+ explicit AVFScopedPointer(dispatch_queue_t q) : m_queue(q) {}
+
+ ~AVFScopedPointer()
+ {
+ if (m_queue)
+ dispatch_release(m_queue);
+ }
+
+ operator dispatch_queue_t() const
+ {
+ // Quite handy operator to enable Obj-C messages: [ptr someMethod];
+ return m_queue;
+ }
+
+ dispatch_queue_t data() const
+ {
+ return m_queue;
+ }
+
+ void reset(dispatch_queue_t q = 0)
+ {
+ if (m_queue)
+ dispatch_release(m_queue);
+ m_queue = q;
+ }
+
+private:
+ dispatch_queue_t m_queue;
+
+ Q_DISABLE_COPY(AVFScopedPointer);
+};
+
inline QSysInfo::MacVersion qt_OS_limit(QSysInfo::MacVersion osxVersion,
QSysInfo::MacVersion iosVersion)
{
diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.h b/src/plugins/avfoundation/camera/avfmediaassetwriter.h
new file mode 100644
index 000000000..eae700751
--- /dev/null
+++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIAASSETWRITER_H
+#define AVFMEDIAASSETWRITER_H
+
+#include "avfcamerautility.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qatomic.h>
+#include <QtCore/qmutex.h>
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraService;
+
+class AVFMediaAssetWriterDelegate
+{
+public:
+ virtual ~AVFMediaAssetWriterDelegate();
+
+ virtual void assetWriterStarted() = 0;
+ virtual void assetWriterFailedToStart() = 0;
+ virtual void assetWriterFailedToStop() = 0;
+ virtual void assetWriterFinished() = 0;
+};
+
+typedef QAtomicInteger<bool> AVFAtomicBool;
+typedef QAtomicInteger<qint64> AVFAtomicInt64;
+
+QT_END_NAMESPACE
+
+// TODO: any reasonable error handling requires smart pointers, otherwise it's getting crappy immediately.
+
+@interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate,
+ AVCaptureAudioDataOutputSampleBufferDelegate>
+{
+@private
+ AVFCameraService *m_service;
+
+ QT_MANGLE_NAMESPACE(AVFScopedPointer)<AVAssetWriterInput> m_cameraWriterInput;
+ QT_MANGLE_NAMESPACE(AVFScopedPointer)<AVCaptureDeviceInput> m_audioInput;
+ QT_MANGLE_NAMESPACE(AVFScopedPointer)<AVCaptureAudioDataOutput> m_audioOutput;
+ QT_MANGLE_NAMESPACE(AVFScopedPointer)<AVAssetWriterInput> m_audioWriterInput;
+
+ // High priority serial queue for video output:
+ QT_MANGLE_NAMESPACE(AVFScopedPointer)<dispatch_queue_t> m_videoQueue;
+ // Serial queue for audio output:
+ QT_MANGLE_NAMESPACE(AVFScopedPointer)<dispatch_queue_t> m_audioQueue;
+ // Queue to write sample buffers:
+ __weak dispatch_queue_t m_writerQueue;
+
+ QT_MANGLE_NAMESPACE(AVFScopedPointer)<AVAssetWriter> m_assetWriter;
+ // Delegate's queue.
+ __weak dispatch_queue_t m_delegateQueue;
+ // TODO: QPointer??
+ QT_PREPEND_NAMESPACE(AVFMediaAssetWriterDelegate) *m_delegate;
+
+ bool m_setStartTime;
+ QT_MANGLE_NAMESPACE(AVFAtomicBool) m_stopped;
+ bool m_stoppedInternal;
+ bool m_aborted;
+
+ QT_MANGLE_NAMESPACE(QMutex) m_writerMutex;
+@public
+ QT_MANGLE_NAMESPACE(AVFAtomicInt64) m_durationInMs;
+@private
+ CMTime m_startTime;
+ CMTime m_lastTimeStamp;
+}
+
+- (id)initWithQueue:(dispatch_queue_t)writerQueue
+ delegate:(QT_PREPEND_NAMESPACE(AVFMediaAssetWriterDelegate) *)delegate
+ delegateQueue:(dispatch_queue_t)delegateQueue;
+
+- (bool)setupWithFileURL:(NSURL *)fileURL
+ cameraService:(QT_PREPEND_NAMESPACE(AVFCameraService) *)service;
+
+- (void)start;
+- (void)stop;
+// This to be called if control's dtor gets called,
+// on the control's thread.
+- (void)abort;
+
+@end
+
+#endif // AVFMEDIAASSETWRITER_H
diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm
new file mode 100644
index 000000000..37004c1db
--- /dev/null
+++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm
@@ -0,0 +1,474 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfaudioinputselectorcontrol.h"
+#include "avfcamerarenderercontrol.h"
+#include "avfmediaassetwriter.h"
+#include "avfcameraservice.h"
+#include "avfcamerasession.h"
+#include "avfcameradebug.h"
+
+//#include <QtCore/qmutexlocker.h>
+#include <QtCore/qsysinfo.h>
+
+QT_USE_NAMESPACE
+
+namespace {
+
+bool qt_camera_service_isValid(AVFCameraService *service)
+{
+ if (!service || !service->session())
+ return false;
+
+ AVFCameraSession *session = service->session();
+ if (!session->captureSession())
+ return false;
+
+ if (!session->videoInput())
+ return false;
+
+ if (!service->videoOutput()
+ || !service->videoOutput()->videoDataOutput()) {
+ return false;
+ }
+
+ return true;
+}
+
+}
+
+AVFMediaAssetWriterDelegate::~AVFMediaAssetWriterDelegate()
+{
+}
+
+@interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) (PrivateAPI)
+- (bool)addAudioCapture;
+- (bool)addWriterInputs;
+- (void)setQueues;
+- (NSDictionary *)videoSettings;
+- (NSDictionary *)audioSettings;
+- (void)updateDuration:(CMTime)newTimeStamp;
+@end
+
+@implementation QT_MANGLE_NAMESPACE(AVFMediaAssetWriter)
+
+- (id)initWithQueue:(dispatch_queue_t)writerQueue
+ delegate:(AVFMediaAssetWriterDelegate *)delegate
+ delegateQueue:(dispatch_queue_t)delegateQueue
+{
+ Q_ASSERT(writerQueue);
+ Q_ASSERT(delegate);
+ Q_ASSERT(delegateQueue);
+
+ if (self = [super init]) {
+ m_writerQueue = writerQueue;
+ m_delegate = delegate;
+ m_delegateQueue = delegateQueue;
+ m_setStartTime = true;
+ m_stopped.store(true);
+ m_stoppedInternal = false;
+ m_aborted = false;
+ m_startTime = kCMTimeInvalid;
+ m_lastTimeStamp = kCMTimeInvalid;
+ m_durationInMs.store(0);
+ }
+
+ return self;
+}
+
+- (bool)setupWithFileURL:(NSURL *)fileURL
+ cameraService:(AVFCameraService *)service
+{
+ Q_ASSERT(fileURL);
+
+ if (!qt_camera_service_isValid(service)) {
+ qDebugCamera() << Q_FUNC_INFO << "invalid camera service";
+ return false;
+ }
+
+ m_service = service;
+
+ m_videoQueue.reset(dispatch_queue_create("video-output-queue", DISPATCH_QUEUE_SERIAL));
+ if (!m_videoQueue) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create video queue";
+ return false;
+ }
+ dispatch_set_target_queue(m_videoQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
+ m_audioQueue.reset(dispatch_queue_create("audio-output-queue", DISPATCH_QUEUE_SERIAL));
+ if (!m_audioQueue) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create audio queue";
+ // But we still can write video!
+ }
+
+ m_assetWriter.reset([[AVAssetWriter alloc] initWithURL:fileURL fileType:AVFileTypeQuickTimeMovie error:nil]);
+ if (!m_assetWriter) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create asset writer";
+ return false;
+ }
+
+ bool audioCaptureOn = false;
+
+ if (m_audioQueue)
+ audioCaptureOn = [self addAudioCapture];
+
+ if (![self addWriterInputs]) {
+ if (audioCaptureOn) {
+ AVCaptureSession *session = m_service->session()->captureSession();
+ [session removeOutput:m_audioOutput];
+ [session removeInput:m_audioInput];
+ m_audioOutput.reset();
+ m_audioInput.reset();
+ }
+ m_assetWriter.reset();
+ return false;
+ }
+ // Ready to start ...
+ return true;
+}
+
+- (void)start
+{
+ // To be executed on a writer's queue.
+ const QMutexLocker lock(&m_writerMutex);
+ if (m_aborted)
+ return;
+
+ [self setQueues];
+
+ m_setStartTime = true;
+ m_stopped.store(false);
+ m_stoppedInternal = false;
+ [m_assetWriter startWriting];
+ AVCaptureSession *session = m_service->session()->captureSession();
+ if (!session.running)
+ [session startRunning];
+}
+
+- (void)stop
+{
+ // To be executed on a writer's queue.
+ const QMutexLocker lock(&m_writerMutex);
+ if (m_aborted)
+ return;
+
+ if (m_stopped.load()) {
+ // Should never happen, but ...
+ // if something went wrong in a recorder control
+ // and we set state stopped without starting first ...
+ // m_stoppedIntenal will be false, but m_stopped - true.
+ return;
+ }
+
+ m_stopped.store(true);
+ m_stoppedInternal = true;
+ [m_assetWriter finishWritingWithCompletionHandler:^{
+ // TODO: make sure the session exist and we can call stop/remove on it.
+ AVCaptureSession *session = m_service->session()->captureSession();
+ [session stopRunning];
+ [session removeOutput:m_audioOutput];
+ [session removeInput:m_audioInput];
+ dispatch_async(m_delegateQueue, ^{
+ m_delegate->assetWriterFinished();
+ });
+ }];
+}
+
+- (void)abort
+{
+ // To be executed on any thread, prevents writer from
+ // accessing any external object (probably deleted by this time)
+ const QMutexLocker lock(&m_writerMutex);
+ m_aborted = true;
+ if (m_stopped.load())
+ return;
+ [m_assetWriter finishWritingWithCompletionHandler:^{
+ }];
+}
+
+- (void)setStartTimeFrom:(CMSampleBufferRef)sampleBuffer
+{
+ // Writer's queue only.
+ Q_ASSERT(m_setStartTime);
+ Q_ASSERT(sampleBuffer);
+
+ dispatch_async(m_delegateQueue, ^{
+ m_delegate->assetWriterStarted();
+ });
+
+ m_durationInMs.store(0);
+ m_startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
+ m_lastTimeStamp = m_startTime;
+ [m_assetWriter startSessionAtSourceTime:m_startTime];
+ m_setStartTime = false;
+}
+
+- (void)writeVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
+{
+ Q_ASSERT(sampleBuffer);
+
+ // This code is executed only on a writer's queue, but
+ // it can access potentially deleted objects, so we
+ // need a lock and m_aborted flag test.
+ {
+ const QMutexLocker lock(&m_writerMutex);
+ if (!m_aborted && !m_stoppedInternal) {
+ if (m_setStartTime)
+ [self setStartTimeFrom:sampleBuffer];
+
+ if (m_cameraWriterInput.data().readyForMoreMediaData) {
+ [self updateDuration:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
+ [m_cameraWriterInput appendSampleBuffer:sampleBuffer];
+ }
+ }
+ }
+
+ CFRelease(sampleBuffer);
+}
+
+- (void)writeAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer
+{
+ // This code is executed only on a writer's queue.
+ // it does not touch any shared/external data.
+ Q_ASSERT(sampleBuffer);
+
+ {
+ const QMutexLocker lock(&m_writerMutex);
+ if (!m_aborted && !m_stoppedInternal) {
+ if (m_setStartTime)
+ [self setStartTimeFrom:sampleBuffer];
+
+ if (m_audioWriterInput.data().readyForMoreMediaData) {
+ [self updateDuration:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
+ [m_audioWriterInput appendSampleBuffer:sampleBuffer];
+ }
+ }
+ }
+
+ CFRelease(sampleBuffer);
+}
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput
+ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
+ fromConnection:(AVCaptureConnection *)connection
+{
+ Q_UNUSED(connection)
+
+ // This method can be called on either video or audio queue, never on a writer's
+ // queue - it does not access any shared data except this atomic flag below.
+ if (m_stopped.load())
+ return;
+
+ // Even if we are stopped now, we still do not access any data.
+
+ if (!CMSampleBufferDataIsReady(sampleBuffer)) {
+ qDebugCamera() << Q_FUNC_INFO << "sample buffer is not ready, skipping.";
+ return;
+ }
+
+ CFRetain(sampleBuffer);
+
+ if (captureOutput != m_audioOutput.data()) {
+ {
+ const QMutexLocker lock(&m_writerMutex);
+ if (m_aborted || m_stoppedInternal) {
+ CFRelease(sampleBuffer);
+ return;
+ }
+
+ // Find renderercontrol's delegate and invoke its method to
+ // show updated viewfinder's frame.
+ if (m_service && m_service->videoOutput()) {
+ NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> *vfDelegate =
+ (NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> *)m_service->videoOutput()->captureDelegate();
+ if (vfDelegate)
+ [vfDelegate captureOutput:nil didOutputSampleBuffer:sampleBuffer fromConnection:nil];
+ }
+ }
+
+ dispatch_async(m_writerQueue, ^{
+ [self writeVideoSampleBuffer:sampleBuffer];
+ });
+ } else {
+ dispatch_async(m_writerQueue, ^{
+ [self writeAudioSampleBuffer:sampleBuffer];
+ });
+ }
+}
+
+- (bool)addAudioCapture
+{
+ Q_ASSERT(m_service && m_service->session() && m_service->session()->captureSession());
+
+ if (!m_service->audioInputSelectorControl())
+ return false;
+
+ AVCaptureSession *captureSession = m_service->session()->captureSession();
+
+ AVCaptureDevice *audioDevice = m_service->audioInputSelectorControl()->createCaptureDevice();
+ if (!audioDevice) {
+ qWarning() << Q_FUNC_INFO << "no audio input device available";
+ return false;
+ } else {
+ NSError *error = nil;
+ m_audioInput.reset([[AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error] retain]);
+
+ if (!m_audioInput || error) {
+ qWarning() << Q_FUNC_INFO << "failed to create audio device input";
+ m_audioInput.reset();
+ return false;
+ } else if (![captureSession canAddInput:m_audioInput]) {
+ qWarning() << Q_FUNC_INFO << "could not connect the audio input";
+ m_audioInput.reset();
+ return false;
+ } else {
+ [captureSession addInput:m_audioInput];
+ }
+ }
+
+
+ m_audioOutput.reset([[AVCaptureAudioDataOutput alloc] init]);
+ if (m_audioOutput && [captureSession canAddOutput:m_audioOutput]) {
+ [captureSession addOutput:m_audioOutput];
+ } else {
+ qDebugCamera() << Q_FUNC_INFO << "failed to add audio output";
+ [captureSession removeInput:m_audioInput];
+ m_audioInput.reset();
+ m_audioOutput.reset();
+ return false;
+ }
+
+ return true;
+}
+
+- (bool)addWriterInputs
+{
+ Q_ASSERT(m_service && m_service->videoOutput()
+ && m_service->videoOutput()->videoDataOutput());
+ Q_ASSERT(m_assetWriter);
+
+ m_cameraWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:[self videoSettings]]);
+ if (!m_cameraWriterInput) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create camera writer input";
+ return false;
+ }
+
+ if ([m_assetWriter canAddInput:m_cameraWriterInput]) {
+ [m_assetWriter addInput:m_cameraWriterInput];
+ } else {
+ qDebugCamera() << Q_FUNC_INFO << "failed to add camera writer input";
+ m_cameraWriterInput.reset();
+ return false;
+ }
+
+ m_cameraWriterInput.data().expectsMediaDataInRealTime = YES;
+
+ if (m_audioOutput) {
+ m_audioWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:[self audioSettings]]);
+ if (!m_audioWriterInput) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create audio writer input";
+ // But we still can record video.
+ } else if ([m_assetWriter canAddInput:m_audioWriterInput]) {
+ [m_assetWriter addInput:m_audioWriterInput];
+ m_audioWriterInput.data().expectsMediaDataInRealTime = YES;
+ } else {
+ qDebugCamera() << Q_FUNC_INFO << "failed to add audio writer input";
+ m_audioWriterInput.reset();
+ // We can (still) write video though ...
+ }
+ }
+
+ return true;
+}
+
+- (void)setQueues
+{
+ Q_ASSERT(m_service && m_service->videoOutput() && m_service->videoOutput()->videoDataOutput());
+ Q_ASSERT(m_videoQueue);
+
+ [m_service->videoOutput()->videoDataOutput() setSampleBufferDelegate:self queue:m_videoQueue];
+
+ if (m_audioOutput) {
+ Q_ASSERT(m_audioQueue);
+ [m_audioOutput setSampleBufferDelegate:self queue:m_audioQueue];
+ }
+}
+
+
+- (NSDictionary *)videoSettings
+{
+ // TODO: these settings should be taken from
+ // the video encoding settings control.
+ // For now we either take recommended (iOS >= 7.0)
+ // or some hardcoded values - they are still better than nothing (nil).
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_7_0)
+ AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput()->videoDataOutput();
+ if (QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_7_0 && videoOutput)
+ return [videoOutput recommendedVideoSettingsForAssetWriterWithOutputFileType:AVFileTypeQuickTimeMovie];
+#endif
+ NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey,
+ [NSNumber numberWithInt:1280], AVVideoWidthKey,
+ [NSNumber numberWithInt:720], AVVideoHeightKey, nil];
+
+ return videoSettings;
+}
+
+- (NSDictionary *)audioSettings
+{
+ // TODO: these settings should be taken from
+ // the video/audio encoder settings control.
+ // For now we either take recommended (iOS >= 7.0)
+ // or nil - this seems to be good enough.
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_7_0)
+ if (QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_7_0 && m_audioOutput)
+ return [m_audioOutput recommendedAudioSettingsForAssetWriterWithOutputFileType:AVFileTypeQuickTimeMovie];
+#endif
+
+ return nil;
+}
+
+- (void)updateDuration:(CMTime)newTimeStamp
+{
+ Q_ASSERT(CMTimeCompare(m_startTime, kCMTimeInvalid));
+ Q_ASSERT(CMTimeCompare(m_lastTimeStamp, kCMTimeInvalid));
+ if (CMTimeCompare(newTimeStamp, m_lastTimeStamp) > 0) {
+
+ const CMTime duration = CMTimeSubtract(newTimeStamp, m_startTime);
+ if (!CMTimeCompare(duration, kCMTimeInvalid))
+ return;
+
+ m_durationInMs.store(CMTimeGetSeconds(duration) * 1000);
+ m_lastTimeStamp = newTimeStamp;
+ }
+}
+
+@end
diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h
new file mode 100644
index 000000000..785769486
--- /dev/null
+++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIARECORDERCONTROL_IOS_H
+#define AVFMEDIARECORDERCONTROL_IOS_H
+
+#include "avfmediaassetwriter.h"
+#include "avfstoragelocation.h"
+#include "avfcamerautility.h"
+
+#include <QtMultimedia/qmediarecordercontrol.h>
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qurl.h>
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraService;
+class QString;
+class QUrl;
+
+class AVFMediaRecorderControlIOS : public QMediaRecorderControl, public AVFMediaAssetWriterDelegate
+{
+ Q_OBJECT
+public:
+ AVFMediaRecorderControlIOS(AVFCameraService *service, QObject *parent = 0);
+ ~AVFMediaRecorderControlIOS();
+
+ QUrl outputLocation() const Q_DECL_OVERRIDE;
+ bool setOutputLocation(const QUrl &location) Q_DECL_OVERRIDE;
+
+ QMediaRecorder::State state() const Q_DECL_OVERRIDE;
+ QMediaRecorder::Status status() const Q_DECL_OVERRIDE;
+
+ qint64 duration() const Q_DECL_OVERRIDE;
+
+ bool isMuted() const Q_DECL_OVERRIDE;
+ qreal volume() const Q_DECL_OVERRIDE;
+
+ void applySettings() Q_DECL_OVERRIDE;
+
+public Q_SLOTS:
+ void setState(QMediaRecorder::State state) Q_DECL_OVERRIDE;
+ void setMuted(bool muted) Q_DECL_OVERRIDE;
+ void setVolume(qreal volume) Q_DECL_OVERRIDE;
+
+ // Writer delegate:
+private:
+
+ void assetWriterStarted() Q_DECL_OVERRIDE;
+ void assetWriterFailedToStart() Q_DECL_OVERRIDE;
+ void assetWriterFailedToStop() Q_DECL_OVERRIDE;
+ void assetWriterFinished() Q_DECL_OVERRIDE;
+
+private Q_SLOTS:
+ void captureModeChanged(QCamera::CaptureModes);
+ void cameraStatusChanged(QCamera::Status newStatus);
+
+private:
+ void stopWriter();
+
+ AVFCameraService *m_service;
+
+ AVFScopedPointer<dispatch_queue_t> m_writerQueue;
+ AVFScopedPointer<QT_MANGLE_NAMESPACE(AVFMediaAssetWriter)> m_writer;
+
+ QUrl m_outputLocation;
+ AVFStorageLocation m_storageLocation;
+
+ QMediaRecorder::State m_state;
+ QMediaRecorder::Status m_lastStatus;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFMEDIARECORDERCONTROL_IOS_H
diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm
new file mode 100644
index 000000000..b763dbcce
--- /dev/null
+++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm
@@ -0,0 +1,349 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include "avfmediarecordercontrol_ios.h"
+#include "avfcamerarenderercontrol.h"
+#include "avfcamerasession.h"
+#include "avfcameracontrol.h"
+#include "avfcameraservice.h"
+#include "avfcameradebug.h"
+
+#include <QtCore/qdebug.h>
+
+QT_USE_NAMESPACE
+
+namespace {
+
+bool qt_is_writable_file_URL(NSURL *fileURL)
+{
+ Q_ASSERT(fileURL);
+
+ if (![fileURL isFileURL])
+ return false;
+
+ if (NSString *path = [[fileURL path] stringByExpandingTildeInPath]) {
+ return [[NSFileManager defaultManager]
+ isWritableFileAtPath:[path stringByDeletingLastPathComponent]];
+ }
+
+ return false;
+}
+
+bool qt_file_exists(NSURL *fileURL)
+{
+ Q_ASSERT(fileURL);
+
+ if (NSString *path = [[fileURL path] stringByExpandingTildeInPath])
+ return [[NSFileManager defaultManager] fileExistsAtPath:path];
+
+ return false;
+}
+
+}
+
+AVFMediaRecorderControlIOS::AVFMediaRecorderControlIOS(AVFCameraService *service, QObject *parent)
+ : QMediaRecorderControl(parent)
+ , m_service(service)
+ , m_state(QMediaRecorder::StoppedState)
+ , m_lastStatus(QMediaRecorder::UnloadedStatus)
+{
+ Q_ASSERT(service);
+
+ m_writerQueue.reset(dispatch_queue_create("asset-writer-queue", DISPATCH_QUEUE_SERIAL));
+ if (!m_writerQueue) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create an asset writer's queue";
+ return;
+ }
+
+ m_writer.reset([[QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) alloc] initWithQueue:m_writerQueue
+ delegate:this delegateQueue:dispatch_get_main_queue()]);
+ if (!m_writer) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create an asset writer";
+ return;
+ }
+
+ AVFCameraControl *cameraControl = m_service->cameraControl();
+ if (!cameraControl) {
+ qDebugCamera() << Q_FUNC_INFO << "camera control is nil";
+ return;
+ }
+
+ connect(cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)),
+ SLOT(captureModeChanged(QCamera::CaptureModes)));
+ connect(cameraControl, SIGNAL(statusChanged(QCamera::Status)),
+ SLOT(cameraStatusChanged(QCamera::Status)));
+}
+
+AVFMediaRecorderControlIOS::~AVFMediaRecorderControlIOS()
+{
+ [m_writer abort];
+}
+
+QUrl AVFMediaRecorderControlIOS::outputLocation() const
+{
+ return m_outputLocation;
+}
+
+bool AVFMediaRecorderControlIOS::setOutputLocation(const QUrl &location)
+{
+ m_outputLocation = location;
+ return location.scheme() == QLatin1String("file") || location.scheme().isEmpty();
+}
+
+QMediaRecorder::State AVFMediaRecorderControlIOS::state() const
+{
+ return m_state;
+}
+
+QMediaRecorder::Status AVFMediaRecorderControlIOS::status() const
+{
+ return m_lastStatus;
+}
+
+qint64 AVFMediaRecorderControlIOS::duration() const
+{
+ return m_writer.data()->m_durationInMs.load();
+}
+
+bool AVFMediaRecorderControlIOS::isMuted() const
+{
+ return false;
+}
+
+qreal AVFMediaRecorderControlIOS::volume() const
+{
+ return 1.;
+}
+
+void AVFMediaRecorderControlIOS::applySettings()
+{
+}
+
+void AVFMediaRecorderControlIOS::setState(QMediaRecorder::State state)
+{
+ Q_ASSERT(m_service->session()
+ && m_service->session()->captureSession());
+
+ if (!m_writer) {
+ qDebugCamera() << Q_FUNC_INFO << "Invalid recorder";
+ return;
+ }
+
+ if (state == m_state)
+ return;
+
+ switch (state) {
+ case QMediaRecorder::RecordingState:
+ {
+ AVFCameraControl *cameraControl = m_service->cameraControl();
+ Q_ASSERT(cameraControl);
+
+ if (!(cameraControl->captureMode() & QCamera::CaptureVideo)) {
+ qDebugCamera() << Q_FUNC_INFO << "wrong capture mode, CaptureVideo expected";
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("Failed to start recording"));
+ return;
+ }
+
+ if (cameraControl->status() != QCamera::ActiveStatus) {
+ qDebugCamera() << Q_FUNC_INFO << "can not start record while camera is not active";
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("Failed to start recording"));
+ return;
+ }
+
+ const QString path(m_outputLocation.scheme() == QLatin1String("file") ?
+ m_outputLocation.path() : m_outputLocation.toString());
+ const QUrl fileURL(QUrl::fromLocalFile(m_storageLocation.generateFileName(path, QCamera::CaptureVideo,
+ QLatin1String("clip_"), QLatin1String("mp4"))));
+
+ NSURL *nsFileURL = fileURL.toNSURL();
+ if (!nsFileURL) {
+ qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL;
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("Invalid output file URL"));
+ return;
+ }
+ if (!qt_is_writable_file_URL(nsFileURL)) {
+ qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL
+ << "(the location is not writable)";
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("Non-writeable file location"));
+ return;
+ }
+ if (qt_file_exists(nsFileURL)) {
+ // We test for/handle this error here since AWAssetWriter will raise an
+ // Objective-C exception, which is not good at all.
+ qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL
+ << "(file already exists)";
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("File already exists"));
+ return;
+ }
+
+ AVCaptureSession *session = m_service->session()->captureSession();
+ // We stop session now so that no more frames for renderer's queue
+ // generated, will restart in assetWriterStarted.
+ [session stopRunning];
+
+ if ([m_writer setupWithFileURL:nsFileURL cameraService:m_service]) {
+ m_state = QMediaRecorder::RecordingState;
+ m_lastStatus = QMediaRecorder::StartingStatus;
+
+ Q_EMIT actualLocationChanged(fileURL);
+ Q_EMIT stateChanged(m_state);
+ Q_EMIT statusChanged(m_lastStatus);
+
+ dispatch_async(m_writerQueue, ^{
+ [m_writer start];
+ });
+ } else {
+ [session startRunning];
+ Q_EMIT error(QMediaRecorder::FormatError, tr("Failed to start recording"));
+ }
+ } break;
+ case QMediaRecorder::PausedState:
+ {
+ Q_EMIT error(QMediaRecorder::FormatError, tr("Recording pause not supported"));
+ return;
+ } break;
+ case QMediaRecorder::StoppedState:
+ {
+ // Do not check the camera status, we can stop if we started.
+ stopWriter();
+ }
+ }
+}
+
+void AVFMediaRecorderControlIOS::setMuted(bool muted)
+{
+ Q_UNUSED(muted)
+ qDebugCamera() << Q_FUNC_INFO << "not implemented";
+}
+
+void AVFMediaRecorderControlIOS::setVolume(qreal volume)
+{
+ Q_UNUSED(volume);
+ qDebugCamera() << Q_FUNC_INFO << "not implemented";
+}
+
+void AVFMediaRecorderControlIOS::assetWriterStarted()
+{
+ m_lastStatus = QMediaRecorder::RecordingStatus;
+ Q_EMIT statusChanged(QMediaRecorder::RecordingStatus);
+}
+
+void AVFMediaRecorderControlIOS::assetWriterFailedToStart()
+{
+}
+
+void AVFMediaRecorderControlIOS::assetWriterFailedToStop()
+{
+}
+
+void AVFMediaRecorderControlIOS::assetWriterFinished()
+{
+ AVFCameraControl *cameraControl = m_service->cameraControl();
+ Q_ASSERT(cameraControl);
+
+ const QMediaRecorder::Status lastStatus = m_lastStatus;
+
+ if (cameraControl->captureMode() & QCamera::CaptureVideo)
+ m_lastStatus = QMediaRecorder::LoadedStatus;
+ else
+ m_lastStatus = QMediaRecorder::UnloadedStatus;
+
+ m_service->videoOutput()->resetCaptureDelegate();
+ [m_service->session()->captureSession() startRunning];
+
+ if (m_lastStatus != lastStatus)
+ Q_EMIT statusChanged(m_lastStatus);
+}
+
+void AVFMediaRecorderControlIOS::captureModeChanged(QCamera::CaptureModes newMode)
+{
+ AVFCameraControl *cameraControl = m_service->cameraControl();
+ Q_ASSERT(cameraControl);
+
+ const QMediaRecorder::Status lastStatus = m_lastStatus;
+
+ if (newMode & QCamera::CaptureVideo) {
+ if (cameraControl->status() == QCamera::ActiveStatus)
+ m_lastStatus = QMediaRecorder::LoadedStatus;
+ } else {
+ if (m_lastStatus == QMediaRecorder::RecordingStatus)
+ return stopWriter();
+ else
+ m_lastStatus = QMediaRecorder::UnloadedStatus;
+ }
+
+ if (m_lastStatus != lastStatus)
+ Q_EMIT statusChanged(m_lastStatus);
+}
+
+void AVFMediaRecorderControlIOS::cameraStatusChanged(QCamera::Status newStatus)
+{
+ AVFCameraControl *cameraControl = m_service->cameraControl();
+ Q_ASSERT(cameraControl);
+
+ const QMediaRecorder::Status lastStatus = m_lastStatus;
+ const bool isCapture = cameraControl->captureMode() & QCamera::CaptureVideo;
+ if (newStatus == QCamera::StartingStatus) {
+ if (isCapture && m_lastStatus == QMediaRecorder::UnloadedStatus)
+ m_lastStatus = QMediaRecorder::LoadingStatus;
+ } else if (newStatus == QCamera::ActiveStatus) {
+ if (isCapture && m_lastStatus == QMediaRecorder::LoadingStatus)
+ m_lastStatus = QMediaRecorder::LoadedStatus;
+ } else {
+ if (m_lastStatus == QMediaRecorder::RecordingStatus)
+ return stopWriter();
+ if (newStatus == QCamera::UnloadedStatus)
+ m_lastStatus = QMediaRecorder::UnloadedStatus;
+ }
+
+ if (lastStatus != m_lastStatus)
+ Q_EMIT statusChanged(m_lastStatus);
+}
+
+void AVFMediaRecorderControlIOS::stopWriter()
+{
+ if (m_lastStatus == QMediaRecorder::RecordingStatus) {
+ m_state = QMediaRecorder::StoppedState;
+ m_lastStatus = QMediaRecorder::FinalizingStatus;
+
+ Q_EMIT stateChanged(m_state);
+ Q_EMIT statusChanged(m_lastStatus);
+
+ dispatch_async(m_writerQueue, ^{
+ [m_writer stop];
+ });
+ }
+}
+
+#include "moc_avfmediarecordercontrol_ios.cpp"
diff --git a/src/plugins/avfoundation/camera/camera.pro b/src/plugins/avfoundation/camera/camera.pro
index ac389df77..62afdcd82 100644
--- a/src/plugins/avfoundation/camera/camera.pro
+++ b/src/plugins/avfoundation/camera/camera.pro
@@ -27,7 +27,6 @@ HEADERS += \
avfcameracontrol.h \
avfcamerametadatacontrol.h \
avfimagecapturecontrol.h \
- avfmediarecordercontrol.h \
avfcameraservice.h \
avfcamerasession.h \
avfstoragelocation.h \
@@ -49,7 +48,6 @@ OBJECTIVE_SOURCES += \
avfcameracontrol.mm \
avfcamerametadatacontrol.mm \
avfimagecapturecontrol.mm \
- avfmediarecordercontrol.mm \
avfcameraservice.mm \
avfcamerasession.mm \
avfstoragelocation.mm \
@@ -66,9 +64,20 @@ OBJECTIVE_SOURCES += \
avfimageencodercontrol.mm \
avfcameraflashcontrol.mm
+osx {
+
+HEADERS += avfmediarecordercontrol.h
+OBJECTIVE_SOURCES += avfmediarecordercontrol.mm
+
+}
+
ios {
-HEADERS += avfcamerazoomcontrol.h
-OBJECTIVE_SOURCES += avfcamerazoomcontrol.mm
+HEADERS += avfcamerazoomcontrol.h \
+ avfmediaassetwriter.h \
+ avfmediarecordercontrol_ios.h
+OBJECTIVE_SOURCES += avfcamerazoomcontrol.mm \
+ avfmediaassetwriter.mm \
+ avfmediarecordercontrol_ios.mm
}