diff options
Diffstat (limited to 'src')
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 } |