summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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
}