From 4fa23e08a116b4d9bb67e25771c511838104999f Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Wed, 10 Feb 2016 14:52:44 +0100 Subject: AVFoundation: implement QVideoEncoderSettingsControl. Task-number: QTBUG-40338 Change-Id: Ic23dabaad94d7b293019460710ae1a097002e227 Reviewed-by: Christian Stromme --- src/plugins/avfoundation/camera/avfcameraservice.h | 8 +- .../avfoundation/camera/avfcameraservice.mm | 26 +- .../avfoundation/camera/avfcamerasession.mm | 6 +- src/plugins/avfoundation/camera/avfcamerautility.h | 4 + .../avfoundation/camera/avfcamerautility.mm | 183 +++++++++- .../camera/avfcameraviewfindersettingscontrol.h | 4 +- .../camera/avfcameraviewfindersettingscontrol.mm | 205 +---------- .../avfoundation/camera/avfmediaassetwriter.h | 5 +- .../avfoundation/camera/avfmediaassetwriter.mm | 27 +- .../avfoundation/camera/avfmediarecordercontrol.h | 7 +- .../avfoundation/camera/avfmediarecordercontrol.mm | 34 +- .../camera/avfmediarecordercontrol_ios.h | 3 + .../camera/avfmediarecordercontrol_ios.mm | 38 +- .../camera/avfvideoencodersettingscontrol.h | 82 +++++ .../camera/avfvideoencodersettingscontrol.mm | 401 +++++++++++++++++++++ src/plugins/avfoundation/camera/camera.pro | 6 +- 16 files changed, 781 insertions(+), 258 deletions(-) create mode 100644 src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.h create mode 100644 src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm (limited to 'src/plugins/avfoundation/camera') diff --git a/src/plugins/avfoundation/camera/avfcameraservice.h b/src/plugins/avfoundation/camera/avfcameraservice.h index 08b0ad26d..76241d7b2 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.h +++ b/src/plugins/avfoundation/camera/avfcameraservice.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2016 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -61,6 +61,7 @@ class AVFImageEncoderControl; class AVFCameraFlashControl; class AVFMediaRecorderControl; class AVFMediaRecorderControlIOS; +class AVFVideoEncoderSettingsControl; class AVFCameraService : public QMediaService { @@ -77,8 +78,7 @@ public: AVFCameraDeviceControl *videoDeviceControl() const { return m_videoDeviceControl; } AVFAudioInputSelectorControl *audioInputSelectorControl() const { return m_audioInputSelectorControl; } AVFCameraMetaDataControl *metaDataControl() const { return m_metaDataControl; } - AVFMediaRecorderControl *recorderControl() const; - AVFMediaRecorderControlIOS *recorderControlIOS() const; + QMediaRecorderControl *recorderControl() const { return m_recorderControl; } AVFImageCaptureControl *imageCaptureControl() const { return m_imageCaptureControl; } AVFCameraFocusControl *cameraFocusControl() const { return m_cameraFocusControl; } AVFCameraExposureControl *cameraExposureControl() const {return m_cameraExposureControl; } @@ -88,6 +88,7 @@ public: AVFCameraViewfinderSettingsControl *viewfinderSettingsControl() const {return m_viewfinderSettingsControl; } AVFImageEncoderControl *imageEncoderControl() const {return m_imageEncoderControl; } AVFCameraFlashControl *flashControl() const {return m_flashControl; } + AVFVideoEncoderSettingsControl *videoEncoderSettingsControl() const {return m_videoEncoderSettingsControl; } private: AVFCameraSession *m_session; @@ -106,6 +107,7 @@ private: AVFCameraViewfinderSettingsControl *m_viewfinderSettingsControl; AVFImageEncoderControl *m_imageEncoderControl; AVFCameraFlashControl *m_flashControl; + AVFVideoEncoderSettingsControl *m_videoEncoderSettingsControl; }; QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfcameraservice.mm b/src/plugins/avfoundation/camera/avfcameraservice.mm index 20156f06f..dc50319d0 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.mm +++ b/src/plugins/avfoundation/camera/avfcameraservice.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies). +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -53,6 +53,7 @@ #include "avfcameraviewfindersettingscontrol.h" #include "avfimageencodercontrol.h" #include "avfcameraflashcontrol.h" +#include "avfvideoencodersettingscontrol.h" #ifdef Q_OS_IOS #include "avfcamerazoomcontrol.h" @@ -99,6 +100,7 @@ AVFCameraService::AVFCameraService(QObject *parent): m_viewfinderSettingsControl = new AVFCameraViewfinderSettingsControl(this); m_imageEncoderControl = new AVFImageEncoderControl(this); m_flashControl = new AVFCameraFlashControl(this); + m_videoEncoderSettingsControl = new AVFVideoEncoderSettingsControl(this); } AVFCameraService::~AVFCameraService() @@ -130,6 +132,7 @@ AVFCameraService::~AVFCameraService() delete m_viewfinderSettingsControl; delete m_imageEncoderControl; delete m_flashControl; + delete m_videoEncoderSettingsControl; delete m_session; } @@ -176,6 +179,9 @@ QMediaControl *AVFCameraService::requestControl(const char *name) if (qstrcmp(name, QCameraFlashControl_iid) == 0) return m_flashControl; + if (qstrcmp(name, QVideoEncoderSettingsControl_iid) == 0) + return m_videoEncoderSettingsControl; + if (qstrcmp(name,QMediaVideoProbeControl_iid) == 0) { AVFMediaVideoProbeControl *videoProbe = 0; videoProbe = new AVFMediaVideoProbeControl(this); @@ -214,23 +220,5 @@ void AVFCameraService::releaseControl(QMediaControl *control) } } -AVFMediaRecorderControl *AVFCameraService::recorderControl() const -{ -#ifdef Q_OS_IOS - return 0; -#else - return static_cast(m_recorderControl); -#endif -} - -AVFMediaRecorderControlIOS *AVFCameraService::recorderControlIOS() const -{ -#ifdef Q_OS_OSX - return 0; -#else - return static_cast(m_recorderControl); -#endif -} - #include "moc_avfcameraservice.cpp" diff --git a/src/plugins/avfoundation/camera/avfcamerasession.mm b/src/plugins/avfoundation/camera/avfcamerasession.mm index 993e28319..eac1b44ec 100644 --- a/src/plugins/avfoundation/camera/avfcamerasession.mm +++ b/src/plugins/avfoundation/camera/avfcamerasession.mm @@ -386,13 +386,11 @@ bool AVFCameraSession::applyViewfinderSettings() // resolution is set, it takes precedence over the viewfinder resolution. if (AVFImageEncoderControl *imControl = m_service->imageEncoderControl()) { const QSize imageResolution(imControl->requestedSettings().resolution()); - if (!imageResolution.isNull() && imageResolution.isValid()) { + if (!imageResolution.isNull() && imageResolution.isValid()) vfSettings.setResolution(imageResolution); - vfControl->setViewfinderSettings(vfSettings); - } } - return vfControl->applySettings(); + return vfControl->applySettings(vfSettings); } return false; diff --git a/src/plugins/avfoundation/camera/avfcamerautility.h b/src/plugins/avfoundation/camera/avfcamerautility.h index 64093645f..7a0de4a66 100644 --- a/src/plugins/avfoundation/camera/avfcamerautility.h +++ b/src/plugins/avfoundation/camera/avfcamerautility.h @@ -178,6 +178,10 @@ AVFrameRateRange *qt_find_supported_framerate_range(AVCaptureDeviceFormat *forma #endif +AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection); +void qt_set_framerate_limits(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection, + qreal minFPS, qreal maxFPS); + QT_END_NAMESPACE #endif diff --git a/src/plugins/avfoundation/camera/avfcamerautility.mm b/src/plugins/avfoundation/camera/avfcamerautility.mm index 3668bb124..0caf07f07 100644 --- a/src/plugins/avfoundation/camera/avfcamerautility.mm +++ b/src/plugins/avfoundation/camera/avfcamerautility.mm @@ -171,8 +171,7 @@ QVector qt_unique_device_formats(AVCaptureDevice *captu QSize qt_device_format_resolution(AVCaptureDeviceFormat *format) { - Q_ASSERT(format); - if (!format.formatDescription) + if (!format || !format.formatDescription) return QSize(); const CMVideoDimensions res = CMVideoFormatDescriptionGetDimensions(format.formatDescription); @@ -383,4 +382,184 @@ AVFrameRateRange *qt_find_supported_framerate_range(AVCaptureDeviceFormat *forma #endif // SDK +void qt_set_framerate_limits(AVCaptureConnection *videoConnection, qreal minFPS, qreal maxFPS) +{ + Q_ASSERT(videoConnection); + + if (minFPS < 0. || maxFPS < 0. || (maxFPS && maxFPS < minFPS)) { + qDebugCamera() << Q_FUNC_INFO << "invalid framerates (min, max):" + << minFPS << maxFPS; + return; + } + + CMTime minDuration = kCMTimeInvalid; + if (maxFPS > 0.) { + if (!videoConnection.supportsVideoMinFrameDuration) + qDebugCamera() << Q_FUNC_INFO << "maximum framerate is not supported"; + else + minDuration = CMTimeMake(1, maxFPS); + } + if (videoConnection.supportsVideoMinFrameDuration) + videoConnection.videoMinFrameDuration = minDuration; + +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_5_0) +#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) + if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_9) { + if (minFPS > 0.) + qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; + } else +#endif + { + CMTime maxDuration = kCMTimeInvalid; + if (minFPS > 0.) { + if (!videoConnection.supportsVideoMaxFrameDuration) + qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; + else + maxDuration = CMTimeMake(1, minFPS); + } + if (videoConnection.supportsVideoMaxFrameDuration) + videoConnection.videoMaxFrameDuration = maxDuration; + } +#else + if (minFPS > 0.) + qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; +#endif +} + +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + +CMTime qt_adjusted_frame_duration(AVFrameRateRange *range, qreal fps) +{ + Q_ASSERT(range); + Q_ASSERT(fps > 0.); + + if (range.maxFrameRate - range.minFrameRate < 0.1) { + // Can happen on OS X. + return range.minFrameDuration; + } + + if (fps <= range.minFrameRate) + return range.maxFrameDuration; + if (fps >= range.maxFrameRate) + return range.minFrameDuration; + + int n, d; + qt_real_to_fraction(1. / fps, &n, &d); + return CMTimeMake(n, d); +} + +void qt_set_framerate_limits(AVCaptureDevice *captureDevice, qreal minFPS, qreal maxFPS) +{ + Q_ASSERT(captureDevice); + if (!captureDevice.activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "no active capture device format"; + return; + } + + if (minFPS < 0. || maxFPS < 0. || (maxFPS && maxFPS < minFPS)) { + qDebugCamera() << Q_FUNC_INFO << "invalid framerates (min, max):" + << minFPS << maxFPS; + return; + } + + CMTime minFrameDuration = kCMTimeInvalid; + CMTime maxFrameDuration = kCMTimeInvalid; + if (maxFPS || minFPS) { + AVFrameRateRange *range = qt_find_supported_framerate_range(captureDevice.activeFormat, + maxFPS ? maxFPS : minFPS); + if (!range) { + qDebugCamera() << Q_FUNC_INFO << "no framerate range found, (min, max):" + << minFPS << maxFPS; + return; + } + + if (maxFPS) + minFrameDuration = qt_adjusted_frame_duration(range, maxFPS); + if (minFPS) + maxFrameDuration = qt_adjusted_frame_duration(range, minFPS); + } + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + + // While Apple's docs say kCMTimeInvalid will end in default + // settings for this format, kCMTimeInvalid on OS X ends with a runtime + // exception: + // "The activeVideoMinFrameDuration passed is not supported by the device." +#ifdef Q_OS_IOS + [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; + [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; +#else // Q_OS_OSX + + if (CMTimeCompare(minFrameDuration, kCMTimeInvalid)) + [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; + +#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) +#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) +#endif + { + if (CMTimeCompare(maxFrameDuration, kCMTimeInvalid)) + [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; + } +#endif // QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) +#endif // Q_OS_OSX +} + +#endif // Platform SDK >= 10.9, >= 7.0. + +void qt_set_framerate_limits(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection, + qreal minFPS, qreal maxFPS) +{ + Q_ASSERT(captureDevice); +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) + qt_set_framerate_limits(captureDevice, minFPS, maxFPS); + else +#endif + if (videoConnection) + qt_set_framerate_limits(videoConnection, minFPS, maxFPS); + +} + +AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection) +{ + Q_ASSERT(captureDevice); + + AVFPSRange fps; +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_7, QSysInfo::MV_IOS_7_0)) { + const CMTime minDuration = captureDevice.activeVideoMinFrameDuration; + if (CMTimeCompare(minDuration, kCMTimeInvalid)) { + if (const Float64 minSeconds = CMTimeGetSeconds(minDuration)) + fps.second = 1. / minSeconds; // Max FPS = 1 / MinDuration. + } + +#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) +#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) +#endif + { + const CMTime maxDuration = captureDevice.activeVideoMaxFrameDuration; + if (CMTimeCompare(maxDuration, kCMTimeInvalid)) { + if (const Float64 maxSeconds = CMTimeGetSeconds(maxDuration)) + fps.first = 1. / maxSeconds; // Min FPS = 1 / MaxDuration. + } + } +#endif // QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) + + } else { +#else // OSX < 10.7 or iOS < 7.0 + { +#endif // QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (videoConnection) + fps = qt_connection_framerates(videoConnection); + } + + return fps; +} + QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h index 9a5bbd5de..85a63cb8e 100644 --- a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h +++ b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2016 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -76,7 +76,7 @@ private: AVCaptureDeviceFormat *findBestFormatMatch(const QCameraViewfinderSettings &settings) const; QVector viewfinderPixelFormats() const; bool convertPixelFormatIfSupported(QVideoFrame::PixelFormat format, unsigned &avfFormat) const; - bool applySettings(); + bool applySettings(const QCameraViewfinderSettings &settings); QCameraViewfinderSettings requestedSettings() const; AVCaptureConnection *videoConnection() const; diff --git a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm index 472e84ed1..1d2539893 100644 --- a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm +++ b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2016 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -66,194 +66,6 @@ bool qt_framerates_sane(const QCameraViewfinderSettings &settings) return !maxFPS || maxFPS >= minFPS; } -void qt_set_framerate_limits(AVCaptureConnection *videoConnection, - const QCameraViewfinderSettings &settings) -{ - Q_ASSERT(videoConnection); - - if (!qt_framerates_sane(settings)) { - qDebugCamera() << Q_FUNC_INFO << "invalid framerate (min, max):" - << settings.minimumFrameRate() << settings.maximumFrameRate(); - return; - } - - const qreal maxFPS = settings.maximumFrameRate(); - CMTime minDuration = kCMTimeInvalid; - if (maxFPS > 0.) { - if (!videoConnection.supportsVideoMinFrameDuration) - qDebugCamera() << Q_FUNC_INFO << "maximum framerate is not supported"; - else - minDuration = CMTimeMake(1, maxFPS); - } - if (videoConnection.supportsVideoMinFrameDuration) - videoConnection.videoMinFrameDuration = minDuration; - - const qreal minFPS = settings.minimumFrameRate(); -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_5_0) -#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) - if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_9) { - if (minFPS > 0.) - qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; - } else -#endif - { - CMTime maxDuration = kCMTimeInvalid; - if (minFPS > 0.) { - if (!videoConnection.supportsVideoMaxFrameDuration) - qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; - else - maxDuration = CMTimeMake(1, minFPS); - } - if (videoConnection.supportsVideoMaxFrameDuration) - videoConnection.videoMaxFrameDuration = maxDuration; - } -#else - if (minFPS > 0.) - qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; -#endif -} - -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - -CMTime qt_adjusted_frame_duration(AVFrameRateRange *range, qreal fps) -{ - Q_ASSERT(range); - Q_ASSERT(fps > 0.); - - if (range.maxFrameRate - range.minFrameRate < 0.1) { - // Can happen on OS X. - return range.minFrameDuration; - } - - if (fps <= range.minFrameRate) - return range.maxFrameDuration; - if (fps >= range.maxFrameRate) - return range.minFrameDuration; - - int n, d; - qt_real_to_fraction(1. / fps, &n, &d); - return CMTimeMake(n, d); -} - -void qt_set_framerate_limits(AVCaptureDevice *captureDevice, - const QCameraViewfinderSettings &settings) -{ - Q_ASSERT(captureDevice); - if (!captureDevice.activeFormat) { - qDebugCamera() << Q_FUNC_INFO << "no active capture device format"; - return; - } - - const qreal minFPS = settings.minimumFrameRate(); - const qreal maxFPS = settings.maximumFrameRate(); - if (!qt_framerates_sane(settings)) { - qDebugCamera() << Q_FUNC_INFO << "invalid framerates (min, max):" - << minFPS << maxFPS; - return; - } - - CMTime minFrameDuration = kCMTimeInvalid; - CMTime maxFrameDuration = kCMTimeInvalid; - if (maxFPS || minFPS) { - AVFrameRateRange *range = qt_find_supported_framerate_range(captureDevice.activeFormat, - maxFPS ? maxFPS : minFPS); - if (!range) { - qDebugCamera() << Q_FUNC_INFO << "no framerate range found, (min, max):" - << minFPS << maxFPS; - return; - } - - if (maxFPS) - minFrameDuration = qt_adjusted_frame_duration(range, maxFPS); - if (minFPS) - maxFrameDuration = qt_adjusted_frame_duration(range, minFPS); - } - - const AVFConfigurationLock lock(captureDevice); - if (!lock) { - qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; - return; - } - - // While Apple's docs say kCMTimeInvalid will end in default - // settings for this format, kCMTimeInvalid on OS X ends with a runtime - // exception: - // "The activeVideoMinFrameDuration passed is not supported by the device." -#ifdef Q_OS_IOS - [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; - [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; -#else // Q_OS_OSX - - if (CMTimeCompare(minFrameDuration, kCMTimeInvalid)) - [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; - -#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) -#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) -#endif - { - if (CMTimeCompare(maxFrameDuration, kCMTimeInvalid)) - [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; - } -#endif // QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) -#endif // Q_OS_OSX -} - -#endif // Platform SDK >= 10.9, >= 7.0. - -// 'Dispatchers': - -AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection) -{ - Q_ASSERT(captureDevice); - - AVFPSRange fps; -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_7, QSysInfo::MV_IOS_7_0)) { - const CMTime minDuration = captureDevice.activeVideoMinFrameDuration; - if (CMTimeCompare(minDuration, kCMTimeInvalid)) { - if (const Float64 minSeconds = CMTimeGetSeconds(minDuration)) - fps.second = 1. / minSeconds; // Max FPS = 1 / MinDuration. - } - -#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) -#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) -#endif - { - const CMTime maxDuration = captureDevice.activeVideoMaxFrameDuration; - if (CMTimeCompare(maxDuration, kCMTimeInvalid)) { - if (const Float64 maxSeconds = CMTimeGetSeconds(maxDuration)) - fps.first = 1. / maxSeconds; // Min FPS = 1 / MaxDuration. - } - } -#endif // QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) - - } else { -#else // OSX < 10.7 or iOS < 7.0 - { -#endif // QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - if (videoConnection) - fps = qt_connection_framerates(videoConnection); - } - - return fps; -} - -void qt_set_framerate_limits(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection, - const QCameraViewfinderSettings &settings) -{ - Q_ASSERT(captureDevice); -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) - qt_set_framerate_limits(captureDevice, settings); - else -#endif - if (videoConnection) - qt_set_framerate_limits(videoConnection, settings); - -} - } // Unnamed namespace. AVFCameraViewfinderSettingsControl2::AVFCameraViewfinderSettingsControl2(AVFCameraService *service) @@ -393,7 +205,7 @@ void AVFCameraViewfinderSettingsControl2::setViewfinderSettings(const QCameraVie return; m_settings = settings; - applySettings(); + applySettings(m_settings); } QVideoFrame::PixelFormat AVFCameraViewfinderSettingsControl2::QtPixelFormatFromCVFormat(unsigned avPixelFormat) @@ -478,8 +290,9 @@ AVCaptureDeviceFormat *AVFCameraViewfinderSettingsControl2::findBestFormatMatch( const qreal minFPS(settings.minimumFrameRate()); const qreal maxFPS(settings.maximumFrameRate()); if (minFPS || maxFPS) - return qt_find_best_framerate_match(captureDevice, maxFPS ? maxFPS : minFPS, - m_service->session()->defaultCodec()); + return qt_find_best_framerate_match(captureDevice, + m_service->session()->defaultCodec(), + maxFPS ? maxFPS : minFPS); // Ignore PAR for the moment (PAR without resolution can // pick a format with really bad resolution). // No need to test pixel format, just return settings. @@ -553,7 +366,7 @@ bool AVFCameraViewfinderSettingsControl2::convertPixelFormatIfSupported(QVideoFr return found; } -bool AVFCameraViewfinderSettingsControl2::applySettings() +bool AVFCameraViewfinderSettingsControl2::applySettings(const QCameraViewfinderSettings &settings) { if (m_service->session()->state() != QCamera::LoadedState && m_service->session()->state() != QCamera::ActiveState) { @@ -567,7 +380,7 @@ bool AVFCameraViewfinderSettingsControl2::applySettings() bool activeFormatChanged = false; #if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - AVCaptureDeviceFormat *match = findBestFormatMatch(m_settings); + AVCaptureDeviceFormat *match = findBestFormatMatch(settings); if (match) { if (match != captureDevice.activeFormat) { const AVFConfigurationLock lock(captureDevice); @@ -587,7 +400,7 @@ bool AVFCameraViewfinderSettingsControl2::applySettings() AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput() ? m_service->videoOutput()->videoDataOutput() : 0; if (videoOutput) { unsigned avfPixelFormat = 0; - if (!convertPixelFormatIfSupported(m_settings.pixelFormat(), avfPixelFormat)) { + if (!convertPixelFormatIfSupported(settings.pixelFormat(), avfPixelFormat)) { // If the the pixel format is not specified or invalid, pick the preferred video surface // format, or if no surface is set, the preferred capture device format @@ -623,7 +436,7 @@ bool AVFCameraViewfinderSettingsControl2::applySettings() } } - qt_set_framerate_limits(captureDevice, videoConnection(), m_settings); + qt_set_framerate_limits(captureDevice, videoConnection(), settings.minimumFrameRate(), settings.maximumFrameRate()); return activeFormatChanged; } diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.h b/src/plugins/avfoundation/camera/avfmediaassetwriter.h index 21915e9ee..dd5b60bf7 100644 --- a/src/plugins/avfoundation/camera/avfmediaassetwriter.h +++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.h @@ -86,13 +86,16 @@ QT_END_NAMESPACE @private CMTime m_startTime; CMTime m_lastTimeStamp; + + NSDictionary *m_videoSettings; } - (id)initWithQueue:(dispatch_queue_t)writerQueue delegate:(QT_PREPEND_NAMESPACE(AVFMediaRecorderControlIOS) *)delegate; - (bool)setupWithFileURL:(NSURL *)fileURL - cameraService:(QT_PREPEND_NAMESPACE(AVFCameraService) *)service; + cameraService:(QT_PREPEND_NAMESPACE(AVFCameraService) *)service + videoSettings:(NSDictionary *)videoSettings; - (void)start; - (void)stop; diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm index a541956a8..b5e2bbc94 100644 --- a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm +++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm @@ -73,7 +73,6 @@ bool qt_camera_service_isValid(AVFCameraService *service) - (bool)addAudioCapture; - (bool)addWriterInputs; - (void)setQueues; -- (NSDictionary *)videoSettings; - (NSDictionary *)audioSettings; - (void)updateDuration:(CMTime)newTimeStamp; @end @@ -98,6 +97,7 @@ bool qt_camera_service_isValid(AVFCameraService *service) m_startTime = kCMTimeInvalid; m_lastTimeStamp = kCMTimeInvalid; m_durationInMs.store(0); + m_videoSettings = nil; } return self; @@ -105,6 +105,7 @@ bool qt_camera_service_isValid(AVFCameraService *service) - (bool)setupWithFileURL:(NSURL *)fileURL cameraService:(AVFCameraService *)service + videoSettings:(NSDictionary *)videoSettings { Q_ASSERT(fileURL); @@ -114,6 +115,7 @@ bool qt_camera_service_isValid(AVFCameraService *service) } m_service = service; + m_videoSettings = videoSettings; m_videoQueue.reset(dispatch_queue_create("video-output-queue", DISPATCH_QUEUE_SERIAL)); if (!m_videoQueue) { @@ -364,7 +366,9 @@ bool qt_camera_service_isValid(AVFCameraService *service) && m_service->videoOutput()->videoDataOutput()); Q_ASSERT(m_assetWriter); - m_cameraWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:[self videoSettings]]); + m_cameraWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo + outputSettings:m_videoSettings + sourceFormatHint:m_service->session()->videoCaptureDevice().activeFormat.formatDescription]); if (!m_cameraWriterInput) { qDebugCamera() << Q_FUNC_INFO << "failed to create camera writer input"; return false; @@ -411,25 +415,6 @@ bool qt_camera_service_isValid(AVFCameraService *service) } } - -- (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 diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol.h b/src/plugins/avfoundation/camera/avfmediarecordercontrol.h index 4ea25ae2b..8617f4f4c 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol.h +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2016 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -39,6 +39,7 @@ #import #include "avfstoragelocation.h" +#include "avfcamerautility.h" @class AVFMediaRecorderDelegate; @@ -68,6 +69,7 @@ public: qreal volume() const; void applySettings(); + void unapplySettings(); public Q_SLOTS: void setState(QMediaRecorder::State state); @@ -83,6 +85,7 @@ private Q_SLOTS: void updateStatus(); private: + AVFCameraService *m_service; AVFCameraControl *m_cameraControl; AVFAudioInputSelectorControl *m_audioInputControl; AVFCameraSession *m_session; @@ -102,6 +105,8 @@ private: AVCaptureMovieFileOutput *m_movieOutput; AVFMediaRecorderDelegate *m_recorderDelagate; AVFStorageLocation m_storageLocation; + + AVFPSRange m_restoreFPS; }; QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm b/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm index 1b6e23ee5..4a3091f24 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies). +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -37,7 +37,7 @@ #include "avfcameraservice.h" #include "avfcameracontrol.h" #include "avfaudioinputselectorcontrol.h" -#include "avfcamerautility.h" +#include "avfvideoencodersettingscontrol.h" #include #include @@ -115,6 +115,7 @@ QT_USE_NAMESPACE AVFMediaRecorderControl::AVFMediaRecorderControl(AVFCameraService *service, QObject *parent) : QMediaRecorderControl(parent) + , m_service(service) , m_cameraControl(service->cameraControl()) , m_audioInputControl(service->audioInputSelectorControl()) , m_session(service->session()) @@ -126,6 +127,7 @@ AVFMediaRecorderControl::AVFMediaRecorderControl(AVFCameraService *service, QObj , m_muted(false) , m_volume(1.0) , m_audioInput(nil) + , m_restoreFPS(-1, -1) { m_movieOutput = [[AVCaptureMovieFileOutput alloc] init]; m_recorderDelagate = [[AVFMediaRecorderDelegate alloc] initWithRecorder:this]; @@ -225,6 +227,23 @@ qreal AVFMediaRecorderControl::volume() const void AVFMediaRecorderControl::applySettings() { + if (m_state != QMediaRecorder::StoppedState + || (m_session->state() != QCamera::ActiveState && m_session->state() != QCamera::LoadedState) + || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureVideo)) { + return; + } + + AVCaptureConnection *videoConnection = [m_movieOutput connectionWithMediaType:AVMediaTypeVideo]; + NSDictionary *videoSettings = m_service->videoEncoderSettingsControl()->applySettings(videoConnection); + + const AVFConfigurationLock lock(m_session->videoCaptureDevice()); // prevents activeFormat from being overridden + + [m_movieOutput setOutputSettings:videoSettings forConnection:videoConnection]; +} + +void AVFMediaRecorderControl::unapplySettings() +{ + m_service->videoEncoderSettingsControl()->unapplySettings([m_movieOutput connectionWithMediaType:AVMediaTypeVideo]); } void AVFMediaRecorderControl::setState(QMediaRecorder::State state) @@ -238,10 +257,6 @@ void AVFMediaRecorderControl::setState(QMediaRecorder::State state) case QMediaRecorder::RecordingState: { if (m_connected) { - m_state = QMediaRecorder::RecordingState; - m_recordingStarted = false; - m_recordingFinished = false; - QString outputLocationPath = m_outputLocation.scheme() == QLatin1String("file") ? m_outputLocation.path() : m_outputLocation.toString(); @@ -253,9 +268,15 @@ void AVFMediaRecorderControl::setState(QMediaRecorder::State state) qDebugCamera() << "Video capture location:" << actualLocation.toString(); + applySettings(); + [m_movieOutput startRecordingToOutputFileURL:actualLocation.toNSURL() recordingDelegate:m_recorderDelagate]; + m_state = QMediaRecorder::RecordingState; + m_recordingStarted = false; + m_recordingFinished = false; + Q_EMIT actualLocationChanged(actualLocation); } else { Q_EMIT error(QMediaRecorder::FormatError, tr("Recorder not configured")); @@ -271,6 +292,7 @@ void AVFMediaRecorderControl::setState(QMediaRecorder::State state) { m_state = QMediaRecorder::StoppedState; [m_movieOutput stopRecording]; + unapplySettings(); } } diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h index a055e54f6..5ee873ff7 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h @@ -70,6 +70,7 @@ public: qreal volume() const Q_DECL_OVERRIDE; void applySettings() Q_DECL_OVERRIDE; + void unapplySettings(); public Q_SLOTS: void setState(QMediaRecorder::State state) Q_DECL_OVERRIDE; @@ -98,6 +99,8 @@ private: QMediaRecorder::State m_state; QMediaRecorder::Status m_lastStatus; + + NSDictionary *m_videoSettings; }; QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm index 73e19e683..3cb33ab58 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm @@ -38,6 +38,8 @@ #include "avfcameracontrol.h" #include "avfcameraservice.h" #include "avfcameradebug.h" +#include "avfvideoencodersettingscontrol.h" +#include "avfcamerautility.h" #include @@ -77,6 +79,7 @@ AVFMediaRecorderControlIOS::AVFMediaRecorderControlIOS(AVFCameraService *service , m_service(service) , m_state(QMediaRecorder::StoppedState) , m_lastStatus(QMediaRecorder::UnloadedStatus) + , m_videoSettings(nil) { Q_ASSERT(service); @@ -107,6 +110,9 @@ AVFMediaRecorderControlIOS::AVFMediaRecorderControlIOS(AVFCameraService *service AVFMediaRecorderControlIOS::~AVFMediaRecorderControlIOS() { [m_writer abort]; + + if (m_videoSettings) + [m_videoSettings release]; } QUrl AVFMediaRecorderControlIOS::outputLocation() const @@ -147,6 +153,32 @@ qreal AVFMediaRecorderControlIOS::volume() const void AVFMediaRecorderControlIOS::applySettings() { + AVFCameraSession *session = m_service->session(); + if (!session) + return; + + if (m_state != QMediaRecorder::StoppedState + || (session->state() != QCamera::ActiveState && session->state() != QCamera::LoadedState) + || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureVideo)) { + return; + } + + AVCaptureConnection *conn = [m_service->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo]; + + m_videoSettings = m_service->videoEncoderSettingsControl()->applySettings(conn); + if (m_videoSettings) + [m_videoSettings retain]; +} + +void AVFMediaRecorderControlIOS::unapplySettings() +{ + AVCaptureConnection *conn = [m_service->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo]; + m_service->videoEncoderSettingsControl()->unapplySettings(conn); + + if (m_videoSettings) { + [m_videoSettings release]; + m_videoSettings = nil; + } } void AVFMediaRecorderControlIOS::setState(QMediaRecorder::State state) @@ -211,7 +243,9 @@ void AVFMediaRecorderControlIOS::setState(QMediaRecorder::State state) // generated, will restart in assetWriterStarted. [session stopRunning]; - if ([m_writer setupWithFileURL:nsFileURL cameraService:m_service]) { + applySettings(); + + if ([m_writer setupWithFileURL:nsFileURL cameraService:m_service videoSettings:m_videoSettings]) { m_state = QMediaRecorder::RecordingState; m_lastStatus = QMediaRecorder::StartingStatus; @@ -270,6 +304,8 @@ void AVFMediaRecorderControlIOS::assetWriterFinished() else m_lastStatus = QMediaRecorder::UnloadedStatus; + unapplySettings(); + m_service->videoOutput()->resetCaptureDelegate(); [m_service->session()->captureSession() startRunning]; diff --git a/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.h b/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.h new file mode 100644 index 000000000..200da90bd --- /dev/null +++ b/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 AVFVIDEOENCODERSETTINGSCONTROL_H +#define AVFVIDEOENCODERSETTINGSCONTROL_H + +#include + +#include "avfcamerautility.h" +#import + +@class NSDictionary; + +QT_BEGIN_NAMESPACE + +class AVFCameraService; + +class AVFVideoEncoderSettingsControl : public QVideoEncoderSettingsControl +{ + Q_OBJECT + +public: + explicit AVFVideoEncoderSettingsControl(AVFCameraService *service); + + QList supportedResolutions(const QVideoEncoderSettings &requestedVideoSettings, + bool *continuous = 0) const Q_DECL_OVERRIDE; + + QList supportedFrameRates(const QVideoEncoderSettings &requestedVideoSettings, + bool *continuous = 0) const Q_DECL_OVERRIDE; + + QStringList supportedVideoCodecs() const Q_DECL_OVERRIDE; + QString videoCodecDescription(const QString &codecName) const Q_DECL_OVERRIDE; + + QVideoEncoderSettings videoSettings() const Q_DECL_OVERRIDE; + void setVideoSettings(const QVideoEncoderSettings &requestedVideoSettings) Q_DECL_OVERRIDE; + + NSDictionary *applySettings(AVCaptureConnection *connection); + void unapplySettings(AVCaptureConnection *connection); + +private: + AVFCameraService *m_service; + + QVideoEncoderSettings m_requestedSettings; + QVideoEncoderSettings m_actualSettings; + + AVCaptureDeviceFormat *m_restoreFormat; + AVFPSRange m_restoreFps; +}; + +QT_END_NAMESPACE + +#endif // AVFVIDEOENCODERSETTINGSCONTROL_H diff --git a/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm b/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm new file mode 100644 index 000000000..87bc91129 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "avfvideoencodersettingscontrol.h" + +#include "avfcameraservice.h" +#include "avfcamerautility.h" +#include "avfcamerasession.h" +#include "avfcamerarenderercontrol.h" + +#include + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC_WITH_ARGS(QStringList, supportedCodecs, (QStringList() << QLatin1String("avc1") + << QLatin1String("jpeg") + #ifdef Q_OS_OSX + << QLatin1String("ap4h") + << QLatin1String("apcn") + #endif + )) + +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) +static bool format_supports_framerate(AVCaptureDeviceFormat *format, qreal fps) +{ + if (format && fps > qreal(0)) { + const qreal epsilon = 0.1; + for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) { + if (range.maxFrameRate - range.minFrameRate < epsilon) { + if (qAbs(fps - range.maxFrameRate) < epsilon) + return true; + } + + if (fps >= range.minFrameRate && fps <= range.maxFrameRate) + return true; + } + } + + return false; +} +#endif + +static bool real_list_contains(const QList &list, qreal value) +{ + Q_FOREACH (qreal r, list) { + if (qFuzzyCompare(r, value)) + return true; + } + return false; +} + +AVFVideoEncoderSettingsControl::AVFVideoEncoderSettingsControl(AVFCameraService *service) + : QVideoEncoderSettingsControl() + , m_service(service) + , m_restoreFormat(nil) +{ +} + +QList AVFVideoEncoderSettingsControl::supportedResolutions(const QVideoEncoderSettings &settings, + bool *continuous) const +{ + Q_UNUSED(settings) + + if (continuous) + *continuous = true; + + // AVFoundation seems to support any resolution for recording, with the following limitations: + // - The recording resolution can't be higher than the camera's active resolution + // - On OS X, the recording resolution is automatically adjusted to have the same aspect ratio as + // the camera's active resolution + QList resolutions; + resolutions.append(QSize(32, 32)); + +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_7, QSysInfo::MV_IOS_7_0)) { + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (device) { + int maximumWidth = 0; + const QVector formats(qt_unique_device_formats(device, + m_service->session()->defaultCodec())); + for (int i = 0; i < formats.size(); ++i) { + const QSize res(qt_device_format_resolution(formats[i])); + if (res.width() > maximumWidth) + maximumWidth = res.width(); + } + + if (maximumWidth > 0) + resolutions.append(QSize(maximumWidth, maximumWidth)); + } + } +#endif + + if (resolutions.count() == 1) + resolutions.append(QSize(3840, 3840)); + + return resolutions; +} + +QList AVFVideoEncoderSettingsControl::supportedFrameRates(const QVideoEncoderSettings &settings, + bool *continuous) const +{ +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + QList uniqueFrameRates; + + if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_7, QSysInfo::MV_IOS_7_0)) { + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (!device) + return uniqueFrameRates; + + if (continuous) + *continuous = false; + + QVector allRates; + + if (!settings.resolution().isValid()) { + const QVector formats(qt_unique_device_formats(device, 0)); + for (int i = 0; i < formats.size(); ++i) { + AVCaptureDeviceFormat *format = formats.at(i); + allRates += qt_device_format_framerates(format); + } + } else { + AVCaptureDeviceFormat *format = qt_find_best_resolution_match(device, + settings.resolution(), + m_service->session()->defaultCodec()); + if (format) + allRates = qt_device_format_framerates(format); + } + + for (int j = 0; j < allRates.size(); ++j) { + if (!real_list_contains(uniqueFrameRates, allRates[j].first)) + uniqueFrameRates.append(allRates[j].first); + if (!real_list_contains(uniqueFrameRates, allRates[j].second)) + uniqueFrameRates.append(allRates[j].second); + } + } + + return uniqueFrameRates; +#else + return QList(); +#endif +} + +QStringList AVFVideoEncoderSettingsControl::supportedVideoCodecs() const +{ + return *supportedCodecs; +} + +QString AVFVideoEncoderSettingsControl::videoCodecDescription(const QString &codecName) const +{ + if (codecName == QLatin1String("avc1")) + return QStringLiteral("H.264"); + else if (codecName == QLatin1String("jpeg")) + return QStringLiteral("M-JPEG"); +#ifdef Q_OS_OSX + else if (codecName == QLatin1String("ap4h")) + return QStringLiteral("Apple ProRes 4444"); + else if (codecName == QLatin1String("apcn")) + return QStringLiteral("Apple ProRes 422 Standard Definition"); +#endif + + return QString(); +} + +QVideoEncoderSettings AVFVideoEncoderSettingsControl::videoSettings() const +{ + return m_actualSettings; +} + +void AVFVideoEncoderSettingsControl::setVideoSettings(const QVideoEncoderSettings &settings) +{ + if (m_requestedSettings == settings) + return; + + m_requestedSettings = m_actualSettings = settings; +} + +NSDictionary *AVFVideoEncoderSettingsControl::applySettings(AVCaptureConnection *connection) +{ + if (m_service->session()->state() != QCamera::LoadedState && + m_service->session()->state() != QCamera::ActiveState) { + return nil; + } + + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (!device) + return nil; + + NSMutableDictionary *videoSettings = [NSMutableDictionary dictionary]; + + // -- Codec + + // AVVideoCodecKey is the only mandatory key + QString codec = m_requestedSettings.codec().isEmpty() ? supportedCodecs->first() : m_requestedSettings.codec(); + if (!supportedCodecs->contains(codec)) { + qWarning("Unsupported codec: '%s'", codec.toLocal8Bit().constData()); + codec = supportedCodecs->first(); + } + [videoSettings setObject:codec.toNSString() forKey:AVVideoCodecKey]; + m_actualSettings.setCodec(codec); + + // -- Resolution + + int w = m_requestedSettings.resolution().width(); + int h = m_requestedSettings.resolution().height(); + +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (device.activeFormat) { + CMFormatDescriptionRef formatDesc = device.activeFormat.formatDescription; + CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc); + + // We have to change the device's activeFormat in 3 cases: + // - the requested recording resolution is higher than the current device resolution + // - the requested recording resolution has a different aspect ratio than the current device aspect ratio + // - the requested frame rate is not available for the current device format + AVCaptureDeviceFormat *newFormat = nil; + if ((w <= 0 || h <= 0) + && m_requestedSettings.frameRate() > 0 + && !format_supports_framerate(device.activeFormat, m_requestedSettings.frameRate())) { + + newFormat = qt_find_best_framerate_match(device, + m_service->session()->defaultCodec(), + m_requestedSettings.frameRate()); + + } else if (w > 0 && h > 0) { + AVCaptureDeviceFormat *f = qt_find_best_resolution_match(device, + m_requestedSettings.resolution(), + m_service->session()->defaultCodec()); + + if (f) { + CMVideoDimensions d = CMVideoFormatDescriptionGetDimensions(f.formatDescription); + qreal fAspectRatio = qreal(d.width) / d.height; + + if (w > dim.width || h > dim.height + || qAbs((qreal(dim.width) / dim.height) - fAspectRatio) > 0.01) { + newFormat = f; + } + } + } + + if (newFormat && newFormat != device.activeFormat) { + const AVFConfigurationLock lock(device); + if (lock) { + m_restoreFormat = [device.activeFormat retain]; + m_restoreFps = qt_current_framerates(device, connection); + + device.activeFormat = newFormat; + + formatDesc = newFormat.formatDescription; + dim = CMVideoFormatDescriptionGetDimensions(formatDesc); + } + } + + if (w > 0 && h > 0) { + // Make sure the recording resolution has the same aspect ratio as the device's + // current resolution + qreal deviceAspectRatio = qreal(dim.width) / dim.height; + qreal recAspectRatio = qreal(w) / h; + if (qAbs(deviceAspectRatio - recAspectRatio) > 0.01) { + if (recAspectRatio > deviceAspectRatio) + w = qRound(h * deviceAspectRatio); + else + h = qRound(w / deviceAspectRatio); + } + + // recording resolution can't be higher than the device's active resolution + w = qMin(w, dim.width); + h = qMin(h, dim.height); + } + } +#endif + + if (w > 0 && h > 0) { + // Width and height must be divisible by 2 + w += w & 1; + h += h & 1; + + [videoSettings setObject:[NSNumber numberWithInt:w] forKey:AVVideoWidthKey]; + [videoSettings setObject:[NSNumber numberWithInt:h] forKey:AVVideoHeightKey]; + m_actualSettings.setResolution(w, h); + } else { + m_actualSettings.setResolution(qt_device_format_resolution(device.activeFormat)); + } + + // -- FPS + + const qreal fps = m_requestedSettings.frameRate(); + if (fps > qreal(0)) { + if (!m_restoreFps.first && !m_restoreFps.second) + m_restoreFps = qt_current_framerates(device, connection); + qt_set_framerate_limits(device, connection, fps, fps); + } + AVFPSRange currentFps = qt_current_framerates(device, connection); + m_actualSettings.setFrameRate(currentFps.second); + + // -- Codec Settings + + NSMutableDictionary *codecProperties = [NSMutableDictionary dictionary]; + int bitrate = -1; + float quality = -1.f; + + if (m_requestedSettings.encodingMode() == QMultimedia::ConstantQualityEncoding) { + if (m_requestedSettings.quality() != QMultimedia::NormalQuality) { + if (codec != QLatin1String("jpeg")) { + qWarning("ConstantQualityEncoding is not supported for codec: '%s'", codec.toLocal8Bit().constData()); + } else { + switch (m_requestedSettings.quality()) { + case QMultimedia::VeryLowQuality: + quality = 0.f; + break; + case QMultimedia::LowQuality: + quality = 0.25f; + break; + case QMultimedia::HighQuality: + quality = 0.75f; + break; + case QMultimedia::VeryHighQuality: + quality = 1.f; + break; + default: + quality = -1.f; // NormalQuality, let the system decide + break; + } + } + } + } else if (m_requestedSettings.encodingMode() == QMultimedia::AverageBitRateEncoding){ + if (codec != QLatin1String("avc1")) + qWarning("AverageBitRateEncoding is not supported for codec: '%s'", codec.toLocal8Bit().constData()); + else + bitrate = m_requestedSettings.bitRate(); + } else { + qWarning("Encoding mode is not supported"); + } + + if (bitrate != -1) + [codecProperties setObject:[NSNumber numberWithInt:bitrate] forKey:AVVideoAverageBitRateKey]; + if (quality != -1.f) + [codecProperties setObject:[NSNumber numberWithFloat:quality] forKey:AVVideoQualityKey]; + + [videoSettings setObject:codecProperties forKey:AVVideoCompressionPropertiesKey]; + + return videoSettings; +} + +void AVFVideoEncoderSettingsControl::unapplySettings(AVCaptureConnection *connection) +{ + m_actualSettings = m_requestedSettings; + + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (!device) + return; + +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (m_restoreFormat) { + const AVFConfigurationLock lock(device); + if (lock) + device.activeFormat = m_restoreFormat; + + [m_restoreFormat release]; + m_restoreFormat = nil; + } +#endif + + if (m_restoreFps.first || m_restoreFps.second) { + qt_set_framerate_limits(device, connection, m_restoreFps.first, m_restoreFps.second); + m_restoreFps = AVFPSRange(); + } +} + +QT_END_NAMESPACE + +#include "moc_avfvideoencodersettingscontrol.cpp" diff --git a/src/plugins/avfoundation/camera/camera.pro b/src/plugins/avfoundation/camera/camera.pro index 8563eb655..bf3f82934 100644 --- a/src/plugins/avfoundation/camera/camera.pro +++ b/src/plugins/avfoundation/camera/camera.pro @@ -37,7 +37,8 @@ HEADERS += \ avfcamerautility.h \ avfcameraviewfindersettingscontrol.h \ avfimageencodercontrol.h \ - avfcameraflashcontrol.h + avfcameraflashcontrol.h \ + avfvideoencodersettingscontrol.h OBJECTIVE_SOURCES += \ avfcameraserviceplugin.mm \ @@ -57,7 +58,8 @@ OBJECTIVE_SOURCES += \ avfcamerautility.mm \ avfcameraviewfindersettingscontrol.mm \ avfimageencodercontrol.mm \ - avfcameraflashcontrol.mm + avfcameraflashcontrol.mm \ + avfvideoencodersettingscontrol.mm osx { -- cgit v1.2.3 From a4656b06ebaa031666ccd2f29c2426647db534e2 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Fri, 11 Mar 2016 15:26:05 +0100 Subject: AVFoundation: correctly set default frame rate on OSX. On OSX, it's not possible to reset the frame rate to the default value by passing kCMTimeInvalid, so just set the first value from the supported frame rates. Change-Id: I984101c2a95e13053228e56f19b353e716eb2b67 Reviewed-by: Christian Stromme --- src/plugins/avfoundation/camera/avfcamerautility.mm | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/plugins/avfoundation/camera') diff --git a/src/plugins/avfoundation/camera/avfcamerautility.mm b/src/plugins/avfoundation/camera/avfcamerautility.mm index 0caf07f07..712868d22 100644 --- a/src/plugins/avfoundation/camera/avfcamerautility.mm +++ b/src/plugins/avfoundation/camera/avfcamerautility.mm @@ -489,10 +489,17 @@ void qt_set_framerate_limits(AVCaptureDevice *captureDevice, qreal minFPS, qreal // settings for this format, kCMTimeInvalid on OS X ends with a runtime // exception: // "The activeVideoMinFrameDuration passed is not supported by the device." + // Instead, use the first item in the supported frame rates. #ifdef Q_OS_IOS [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; #else // Q_OS_OSX + if (CMTimeCompare(minFrameDuration, kCMTimeInvalid) == 0 + && CMTimeCompare(maxFrameDuration, kCMTimeInvalid) == 0) { + AVFrameRateRange *range = captureDevice.activeFormat.videoSupportedFrameRateRanges.firstObject; + minFrameDuration = range.minFrameDuration; + maxFrameDuration = range.maxFrameDuration; + } if (CMTimeCompare(minFrameDuration, kCMTimeInvalid)) [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; -- cgit v1.2.3 From 2303d2fef8987f32b1615710ef8930f8d2854373 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 11 Feb 2016 01:47:12 +0100 Subject: AVFoundation: implement QMediaContainerControl. Change-Id: I5097a0450f5836e48a697249619a1822775f3ad8 Reviewed-by: Timur Pocheptsov --- src/plugins/avfoundation/camera/avfcameraservice.h | 3 + .../avfoundation/camera/avfcameraservice.mm | 6 ++ .../avfoundation/camera/avfmediaassetwriter.mm | 5 +- .../avfoundation/camera/avfmediacontainercontrol.h | 63 ++++++++++++ .../camera/avfmediacontainercontrol.mm | 106 +++++++++++++++++++++ .../avfoundation/camera/avfmediarecordercontrol.mm | 5 +- .../camera/avfmediarecordercontrol_ios.mm | 4 +- src/plugins/avfoundation/camera/camera.pro | 6 +- 8 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 src/plugins/avfoundation/camera/avfmediacontainercontrol.h create mode 100644 src/plugins/avfoundation/camera/avfmediacontainercontrol.mm (limited to 'src/plugins/avfoundation/camera') diff --git a/src/plugins/avfoundation/camera/avfcameraservice.h b/src/plugins/avfoundation/camera/avfcameraservice.h index 76241d7b2..ae46e49f1 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.h +++ b/src/plugins/avfoundation/camera/avfcameraservice.h @@ -62,6 +62,7 @@ class AVFCameraFlashControl; class AVFMediaRecorderControl; class AVFMediaRecorderControlIOS; class AVFVideoEncoderSettingsControl; +class AVFMediaContainerControl; class AVFCameraService : public QMediaService { @@ -89,6 +90,7 @@ public: AVFImageEncoderControl *imageEncoderControl() const {return m_imageEncoderControl; } AVFCameraFlashControl *flashControl() const {return m_flashControl; } AVFVideoEncoderSettingsControl *videoEncoderSettingsControl() const {return m_videoEncoderSettingsControl; } + AVFMediaContainerControl *mediaContainerControl() const { return m_mediaContainerControl; } private: AVFCameraSession *m_session; @@ -108,6 +110,7 @@ private: AVFImageEncoderControl *m_imageEncoderControl; AVFCameraFlashControl *m_flashControl; AVFVideoEncoderSettingsControl *m_videoEncoderSettingsControl; + AVFMediaContainerControl *m_mediaContainerControl; }; QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfcameraservice.mm b/src/plugins/avfoundation/camera/avfcameraservice.mm index dc50319d0..5599dfe4a 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.mm +++ b/src/plugins/avfoundation/camera/avfcameraservice.mm @@ -54,6 +54,7 @@ #include "avfimageencodercontrol.h" #include "avfcameraflashcontrol.h" #include "avfvideoencodersettingscontrol.h" +#include "avfmediacontainercontrol.h" #ifdef Q_OS_IOS #include "avfcamerazoomcontrol.h" @@ -101,6 +102,7 @@ AVFCameraService::AVFCameraService(QObject *parent): m_imageEncoderControl = new AVFImageEncoderControl(this); m_flashControl = new AVFCameraFlashControl(this); m_videoEncoderSettingsControl = new AVFVideoEncoderSettingsControl(this); + m_mediaContainerControl = new AVFMediaContainerControl(this); } AVFCameraService::~AVFCameraService() @@ -133,6 +135,7 @@ AVFCameraService::~AVFCameraService() delete m_imageEncoderControl; delete m_flashControl; delete m_videoEncoderSettingsControl; + delete m_mediaContainerControl; delete m_session; } @@ -182,6 +185,9 @@ QMediaControl *AVFCameraService::requestControl(const char *name) if (qstrcmp(name, QVideoEncoderSettingsControl_iid) == 0) return m_videoEncoderSettingsControl; + if (qstrcmp(name, QMediaContainerControl_iid) == 0) + return m_mediaContainerControl; + if (qstrcmp(name,QMediaVideoProbeControl_iid) == 0) { AVFMediaVideoProbeControl *videoProbe = 0; videoProbe = new AVFMediaVideoProbeControl(this); diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm index b5e2bbc94..8e49c7c8f 100644 --- a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm +++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm @@ -38,6 +38,7 @@ #include "avfcameraservice.h" #include "avfcamerasession.h" #include "avfcameradebug.h" +#include "avfmediacontainercontrol.h" //#include #include @@ -129,7 +130,9 @@ bool qt_camera_service_isValid(AVFCameraService *service) // But we still can write video! } - m_assetWriter.reset([[AVAssetWriter alloc] initWithURL:fileURL fileType:AVFileTypeQuickTimeMovie error:nil]); + m_assetWriter.reset([[AVAssetWriter alloc] initWithURL:fileURL + fileType:m_service->mediaContainerControl()->fileType() + error:nil]); if (!m_assetWriter) { qDebugCamera() << Q_FUNC_INFO << "failed to create asset writer"; return false; diff --git a/src/plugins/avfoundation/camera/avfmediacontainercontrol.h b/src/plugins/avfoundation/camera/avfmediacontainercontrol.h new file mode 100644 index 000000000..da31d2d13 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfmediacontainercontrol.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 AVFMEDIACONTAINERCONTROL_H +#define AVFMEDIACONTAINERCONTROL_H + +#include + +@class NSString; + +QT_BEGIN_NAMESPACE + +class AVFCameraService; + +class AVFMediaContainerControl : public QMediaContainerControl +{ +public: + explicit AVFMediaContainerControl(AVFCameraService *service); + + QStringList supportedContainers() const Q_DECL_OVERRIDE; + QString containerFormat() const Q_DECL_OVERRIDE; + void setContainerFormat(const QString &format) Q_DECL_OVERRIDE; + QString containerDescription(const QString &formatMimeType) const Q_DECL_OVERRIDE; + + NSString *fileType() const; + +private: + QString m_format; +}; + +QT_END_NAMESPACE + +#endif // AVFMEDIACONTAINERCONTROL_H diff --git a/src/plugins/avfoundation/camera/avfmediacontainercontrol.mm b/src/plugins/avfoundation/camera/avfmediacontainercontrol.mm new file mode 100644 index 000000000..a8dc3c844 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfmediacontainercontrol.mm @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "avfmediacontainercontrol.h" + +#include + +QT_BEGIN_NAMESPACE + +struct ContainerInfo +{ + QString description; + NSString *fileType; + + ContainerInfo() : fileType(nil) { } + ContainerInfo(const QString &desc, NSString *type) + : description(desc), fileType(type) + { } +}; + +typedef QMap SupportedContainers; +Q_GLOBAL_STATIC(SupportedContainers, containers); + +AVFMediaContainerControl::AVFMediaContainerControl(AVFCameraService *) + : QMediaContainerControl() + , m_format(QStringLiteral("mov")) // .mov is the default container format on Apple platforms +{ + if (containers->isEmpty()) { + containers->insert(QStringLiteral("mov"), + ContainerInfo(QStringLiteral("QuickTime movie file format"), + AVFileTypeQuickTimeMovie)); + containers->insert(QStringLiteral("mp4"), + ContainerInfo(QStringLiteral("MPEG-4 file format"), + AVFileTypeMPEG4)); + containers->insert(QStringLiteral("m4v"), + ContainerInfo(QStringLiteral("iTunes video file format"), + AVFileTypeAppleM4V)); +#ifdef Q_OS_IOS + containers->insert(QStringLiteral("3gp"), + ContainerInfo(QStringLiteral("3GPP file format"), + AVFileType3GPP)); +#endif + } +} + +QStringList AVFMediaContainerControl::supportedContainers() const +{ + return containers->keys(); +} + +QString AVFMediaContainerControl::containerFormat() const +{ + return m_format; +} + +void AVFMediaContainerControl::setContainerFormat(const QString &format) +{ + if (!containers->contains(format)) { + qWarning("Unsupported container format: '%s'", format.toLocal8Bit().constData()); + return; + } + + m_format = format; +} + +QString AVFMediaContainerControl::containerDescription(const QString &formatMimeType) const +{ + return containers->value(formatMimeType).description; +} + +NSString *AVFMediaContainerControl::fileType() const +{ + return containers->value(m_format).fileType; +} + +QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm b/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm index 4a3091f24..de2b14ef4 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm @@ -38,6 +38,7 @@ #include "avfcameracontrol.h" #include "avfaudioinputselectorcontrol.h" #include "avfvideoencodersettingscontrol.h" +#include "avfmediacontainercontrol.h" #include #include @@ -260,11 +261,13 @@ void AVFMediaRecorderControl::setState(QMediaRecorder::State state) QString outputLocationPath = m_outputLocation.scheme() == QLatin1String("file") ? m_outputLocation.path() : m_outputLocation.toString(); + QString extension = m_service->mediaContainerControl()->containerFormat(); + QUrl actualLocation = QUrl::fromLocalFile( m_storageLocation.generateFileName(outputLocationPath, QCamera::CaptureVideo, QLatin1String("clip_"), - QLatin1String("mp4"))); + extension)); qDebugCamera() << "Video capture location:" << actualLocation.toString(); diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm index 3cb33ab58..2052afb50 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm @@ -39,6 +39,7 @@ #include "avfcameraservice.h" #include "avfcameradebug.h" #include "avfvideoencodersettingscontrol.h" +#include "avfmediacontainercontrol.h" #include "avfcamerautility.h" #include @@ -215,7 +216,8 @@ void AVFMediaRecorderControlIOS::setState(QMediaRecorder::State state) 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")))); + QLatin1String("clip_"), + m_service->mediaContainerControl()->containerFormat()))); NSURL *nsFileURL = fileURL.toNSURL(); if (!nsFileURL) { diff --git a/src/plugins/avfoundation/camera/camera.pro b/src/plugins/avfoundation/camera/camera.pro index bf3f82934..f9782ef8f 100644 --- a/src/plugins/avfoundation/camera/camera.pro +++ b/src/plugins/avfoundation/camera/camera.pro @@ -38,7 +38,8 @@ HEADERS += \ avfcameraviewfindersettingscontrol.h \ avfimageencodercontrol.h \ avfcameraflashcontrol.h \ - avfvideoencodersettingscontrol.h + avfvideoencodersettingscontrol.h \ + avfmediacontainercontrol.h OBJECTIVE_SOURCES += \ avfcameraserviceplugin.mm \ @@ -59,7 +60,8 @@ OBJECTIVE_SOURCES += \ avfcameraviewfindersettingscontrol.mm \ avfimageencodercontrol.mm \ avfcameraflashcontrol.mm \ - avfvideoencodersettingscontrol.mm + avfvideoencodersettingscontrol.mm \ + avfmediacontainercontrol.mm osx { -- cgit v1.2.3 From f5de14107094080f15fad3beffb64ad95ff6c5e4 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 11 Feb 2016 04:34:57 +0100 Subject: AVFoundation: implement QAudioEncoderSettings. Change-Id: I24d3da1417142bc80bc6b6c1c8124c246afe03db Reviewed-by: Timur Pocheptsov Reviewed-by: Christian Stromme --- .../camera/avfaudioencodersettingscontrol.h | 69 +++++++ .../camera/avfaudioencodersettingscontrol.mm | 220 +++++++++++++++++++++ src/plugins/avfoundation/camera/avfcameraservice.h | 3 + .../avfoundation/camera/avfcameraservice.mm | 6 + .../avfoundation/camera/avfmediaassetwriter.h | 3 + .../avfoundation/camera/avfmediaassetwriter.mm | 33 ++-- .../avfoundation/camera/avfmediarecordercontrol.mm | 7 + .../camera/avfmediarecordercontrol_ios.h | 1 + .../camera/avfmediarecordercontrol_ios.mm | 21 +- src/plugins/avfoundation/camera/camera.pro | 6 +- 10 files changed, 346 insertions(+), 23 deletions(-) create mode 100644 src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.h create mode 100644 src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.mm (limited to 'src/plugins/avfoundation/camera') diff --git a/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.h b/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.h new file mode 100644 index 000000000..213065443 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 AVFAUDIOENCODERSETTINGSCONTROL_H +#define AVFAUDIOENCODERSETTINGSCONTROL_H + +#include + +@class NSDictionary; +@class AVCaptureAudioDataOutput; + +QT_BEGIN_NAMESPACE + +class AVFCameraService; + +class AVFAudioEncoderSettingsControl : public QAudioEncoderSettingsControl +{ +public: + explicit AVFAudioEncoderSettingsControl(AVFCameraService *service); + + QStringList supportedAudioCodecs() const Q_DECL_OVERRIDE; + QString codecDescription(const QString &codecName) const Q_DECL_OVERRIDE; + QList supportedSampleRates(const QAudioEncoderSettings &settings, bool *continuous = 0) const Q_DECL_OVERRIDE; + QAudioEncoderSettings audioSettings() const Q_DECL_OVERRIDE; + void setAudioSettings(const QAudioEncoderSettings &settings) Q_DECL_OVERRIDE; + + NSDictionary *applySettings(); + void unapplySettings(); + +private: + AVFCameraService *m_service; + + QAudioEncoderSettings m_requestedSettings; + QAudioEncoderSettings m_actualSettings; +}; + +QT_END_NAMESPACE + +#endif // AVFAUDIOENCODERSETTINGSCONTROL_H diff --git a/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.mm b/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.mm new file mode 100644 index 000000000..143a444a6 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.mm @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "avfaudioencodersettingscontrol.h" + +#include "avfcameraservice.h" +#include "avfcamerasession.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +struct AudioCodecInfo +{ + QString description; + int id; + + AudioCodecInfo() : id(0) { } + AudioCodecInfo(const QString &desc, int i) + : description(desc), id(i) + { } +}; + +typedef QMap SupportedAudioCodecs; +Q_GLOBAL_STATIC_WITH_ARGS(QString , defaultCodec, (QLatin1String("aac"))) +Q_GLOBAL_STATIC(SupportedAudioCodecs, supportedCodecs) + +AVFAudioEncoderSettingsControl::AVFAudioEncoderSettingsControl(AVFCameraService *service) + : QAudioEncoderSettingsControl() + , m_service(service) +{ + if (supportedCodecs->isEmpty()) { + supportedCodecs->insert(QStringLiteral("lpcm"), + AudioCodecInfo(QStringLiteral("Linear PCM"), + kAudioFormatLinearPCM)); + supportedCodecs->insert(QStringLiteral("ulaw"), + AudioCodecInfo(QStringLiteral("PCM Mu-Law 2:1"), + kAudioFormatULaw)); + supportedCodecs->insert(QStringLiteral("alaw"), + AudioCodecInfo(QStringLiteral("PCM A-Law 2:1"), + kAudioFormatALaw)); + supportedCodecs->insert(QStringLiteral("ima4"), + AudioCodecInfo(QStringLiteral("IMA 4:1 ADPCM"), + kAudioFormatAppleIMA4)); + supportedCodecs->insert(QStringLiteral("alac"), + AudioCodecInfo(QStringLiteral("Apple Lossless Audio Codec"), + kAudioFormatAppleLossless)); + supportedCodecs->insert(QStringLiteral("aac"), + AudioCodecInfo(QStringLiteral("MPEG-4 Low Complexity AAC"), + kAudioFormatMPEG4AAC)); + supportedCodecs->insert(QStringLiteral("aach"), + AudioCodecInfo(QStringLiteral("MPEG-4 High Efficiency AAC"), + kAudioFormatMPEG4AAC_HE)); + supportedCodecs->insert(QStringLiteral("aacl"), + AudioCodecInfo(QStringLiteral("MPEG-4 AAC Low Delay"), + kAudioFormatMPEG4AAC_LD)); + supportedCodecs->insert(QStringLiteral("aace"), + AudioCodecInfo(QStringLiteral("MPEG-4 AAC Enhanced Low Delay"), + kAudioFormatMPEG4AAC_ELD)); + supportedCodecs->insert(QStringLiteral("aacf"), + AudioCodecInfo(QStringLiteral("MPEG-4 AAC Enhanced Low Delay with SBR"), + kAudioFormatMPEG4AAC_ELD_SBR)); + supportedCodecs->insert(QStringLiteral("aacp"), + AudioCodecInfo(QStringLiteral("MPEG-4 HE AAC V2"), + kAudioFormatMPEG4AAC_HE_V2)); + supportedCodecs->insert(QStringLiteral("ilbc"), + AudioCodecInfo(QStringLiteral("iLBC"), + kAudioFormatiLBC)); + } +} + +QStringList AVFAudioEncoderSettingsControl::supportedAudioCodecs() const +{ + return supportedCodecs->keys(); +} + +QString AVFAudioEncoderSettingsControl::codecDescription(const QString &codecName) const +{ + return supportedCodecs->value(codecName).description; +} + +QList AVFAudioEncoderSettingsControl::supportedSampleRates(const QAudioEncoderSettings &settings, bool *continuous) const +{ + Q_UNUSED(settings) + + if (continuous) + *continuous = true; + + return QList() << 8000 << 96000; +} + +QAudioEncoderSettings AVFAudioEncoderSettingsControl::audioSettings() const +{ + return m_actualSettings; +} + +void AVFAudioEncoderSettingsControl::setAudioSettings(const QAudioEncoderSettings &settings) +{ + if (m_requestedSettings == settings) + return; + + m_requestedSettings = m_actualSettings = settings; +} + +NSDictionary *AVFAudioEncoderSettingsControl::applySettings() +{ + if (m_service->session()->state() != QCamera::LoadedState && + m_service->session()->state() != QCamera::ActiveState) { + return nil; + } + + NSMutableDictionary *settings = [NSMutableDictionary dictionary]; + + QString codec = m_requestedSettings.codec().isEmpty() ? *defaultCodec : m_requestedSettings.codec(); + if (!supportedCodecs->contains(codec)) { + qWarning("Unsupported codec: '%s'", codec.toLocal8Bit().constData()); + codec = *defaultCodec; + } + [settings setObject:[NSNumber numberWithInt:supportedCodecs->value(codec).id] forKey:AVFormatIDKey]; + m_actualSettings.setCodec(codec); + +#ifdef Q_OS_OSX + if (m_requestedSettings.encodingMode() == QMultimedia::ConstantQualityEncoding) { + int quality; + switch (m_requestedSettings.quality()) { + case QMultimedia::VeryLowQuality: + quality = AVAudioQualityMin; + break; + case QMultimedia::LowQuality: + quality = AVAudioQualityLow; + break; + case QMultimedia::HighQuality: + quality = AVAudioQualityHigh; + break; + case QMultimedia::VeryHighQuality: + quality = AVAudioQualityMax; + break; + case QMultimedia::NormalQuality: + default: + quality = AVAudioQualityMedium; + break; + } + [settings setObject:[NSNumber numberWithInt:quality] forKey:AVEncoderAudioQualityKey]; + + } else +#endif + if (m_requestedSettings.bitRate() > 0){ + [settings setObject:[NSNumber numberWithInt:m_requestedSettings.bitRate()] forKey:AVEncoderBitRateKey]; + } + + int sampleRate = m_requestedSettings.sampleRate(); + int channelCount = m_requestedSettings.channelCount(); + +#ifdef Q_OS_IOS + // Some keys are mandatory only on iOS + if (codec == QLatin1String("lpcm")) { + [settings setObject:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey]; + [settings setObject:[NSNumber numberWithInt:NO] forKey:AVLinearPCMIsBigEndianKey]; + [settings setObject:[NSNumber numberWithInt:NO] forKey:AVLinearPCMIsFloatKey]; + [settings setObject:[NSNumber numberWithInt:NO] forKey:AVLinearPCMIsNonInterleaved]; + } + + if (codec == QLatin1String("alac")) + [settings setObject:[NSNumber numberWithInt:24] forKey:AVEncoderBitDepthHintKey]; + + if (sampleRate <= 0) + sampleRate = codec == QLatin1String("ilbc") ? 8000 : 44100; + if (channelCount <= 0) + channelCount = codec == QLatin1String("ilbc") ? 1 : 2; +#endif + + if (sampleRate > 0) { + [settings setObject:[NSNumber numberWithInt:sampleRate] forKey:AVSampleRateKey]; + m_actualSettings.setSampleRate(sampleRate); + } + if (channelCount > 0) { + [settings setObject:[NSNumber numberWithInt:channelCount] forKey:AVNumberOfChannelsKey]; + m_actualSettings.setChannelCount(channelCount); + } + + return settings; +} + +void AVFAudioEncoderSettingsControl::unapplySettings() +{ + m_actualSettings = m_requestedSettings; +} + +QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfcameraservice.h b/src/plugins/avfoundation/camera/avfcameraservice.h index ae46e49f1..3075c7a52 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.h +++ b/src/plugins/avfoundation/camera/avfcameraservice.h @@ -61,6 +61,7 @@ class AVFImageEncoderControl; class AVFCameraFlashControl; class AVFMediaRecorderControl; class AVFMediaRecorderControlIOS; +class AVFAudioEncoderSettingsControl; class AVFVideoEncoderSettingsControl; class AVFMediaContainerControl; @@ -89,6 +90,7 @@ public: AVFCameraViewfinderSettingsControl *viewfinderSettingsControl() const {return m_viewfinderSettingsControl; } AVFImageEncoderControl *imageEncoderControl() const {return m_imageEncoderControl; } AVFCameraFlashControl *flashControl() const {return m_flashControl; } + AVFAudioEncoderSettingsControl *audioEncoderSettingsControl() const { return m_audioEncoderSettingsControl; } AVFVideoEncoderSettingsControl *videoEncoderSettingsControl() const {return m_videoEncoderSettingsControl; } AVFMediaContainerControl *mediaContainerControl() const { return m_mediaContainerControl; } @@ -109,6 +111,7 @@ private: AVFCameraViewfinderSettingsControl *m_viewfinderSettingsControl; AVFImageEncoderControl *m_imageEncoderControl; AVFCameraFlashControl *m_flashControl; + AVFAudioEncoderSettingsControl *m_audioEncoderSettingsControl; AVFVideoEncoderSettingsControl *m_videoEncoderSettingsControl; AVFMediaContainerControl *m_mediaContainerControl; }; diff --git a/src/plugins/avfoundation/camera/avfcameraservice.mm b/src/plugins/avfoundation/camera/avfcameraservice.mm index 5599dfe4a..8e726dd3b 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.mm +++ b/src/plugins/avfoundation/camera/avfcameraservice.mm @@ -53,6 +53,7 @@ #include "avfcameraviewfindersettingscontrol.h" #include "avfimageencodercontrol.h" #include "avfcameraflashcontrol.h" +#include "avfaudioencodersettingscontrol.h" #include "avfvideoencodersettingscontrol.h" #include "avfmediacontainercontrol.h" @@ -101,6 +102,7 @@ AVFCameraService::AVFCameraService(QObject *parent): m_viewfinderSettingsControl = new AVFCameraViewfinderSettingsControl(this); m_imageEncoderControl = new AVFImageEncoderControl(this); m_flashControl = new AVFCameraFlashControl(this); + m_audioEncoderSettingsControl = new AVFAudioEncoderSettingsControl(this); m_videoEncoderSettingsControl = new AVFVideoEncoderSettingsControl(this); m_mediaContainerControl = new AVFMediaContainerControl(this); } @@ -134,6 +136,7 @@ AVFCameraService::~AVFCameraService() delete m_viewfinderSettingsControl; delete m_imageEncoderControl; delete m_flashControl; + delete m_audioEncoderSettingsControl; delete m_videoEncoderSettingsControl; delete m_mediaContainerControl; @@ -182,6 +185,9 @@ QMediaControl *AVFCameraService::requestControl(const char *name) if (qstrcmp(name, QCameraFlashControl_iid) == 0) return m_flashControl; + if (qstrcmp(name, QAudioEncoderSettingsControl_iid) == 0) + return m_audioEncoderSettingsControl; + if (qstrcmp(name, QVideoEncoderSettingsControl_iid) == 0) return m_videoEncoderSettingsControl; diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.h b/src/plugins/avfoundation/camera/avfmediaassetwriter.h index dd5b60bf7..ff24ad49a 100644 --- a/src/plugins/avfoundation/camera/avfmediaassetwriter.h +++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.h @@ -64,6 +64,7 @@ QT_END_NAMESPACE QT_PREPEND_NAMESPACE(AVFScopedPointer) m_audioInput; QT_PREPEND_NAMESPACE(AVFScopedPointer) m_audioOutput; QT_PREPEND_NAMESPACE(AVFScopedPointer) m_audioWriterInput; + AVCaptureDevice *m_audioCaptureDevice; // High priority serial queue for video output: QT_PREPEND_NAMESPACE(AVFScopedPointer) m_videoQueue; @@ -87,6 +88,7 @@ QT_END_NAMESPACE CMTime m_startTime; CMTime m_lastTimeStamp; + NSDictionary *m_audioSettings; NSDictionary *m_videoSettings; } @@ -95,6 +97,7 @@ QT_END_NAMESPACE - (bool)setupWithFileURL:(NSURL *)fileURL cameraService:(QT_PREPEND_NAMESPACE(AVFCameraService) *)service + audioSettings:(NSDictionary *)audioSettings videoSettings:(NSDictionary *)videoSettings; - (void)start; diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm index 8e49c7c8f..396de8c35 100644 --- a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm +++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm @@ -74,7 +74,6 @@ bool qt_camera_service_isValid(AVFCameraService *service) - (bool)addAudioCapture; - (bool)addWriterInputs; - (void)setQueues; -- (NSDictionary *)audioSettings; - (void)updateDuration:(CMTime)newTimeStamp; @end @@ -98,6 +97,7 @@ bool qt_camera_service_isValid(AVFCameraService *service) m_startTime = kCMTimeInvalid; m_lastTimeStamp = kCMTimeInvalid; m_durationInMs.store(0); + m_audioSettings = nil; m_videoSettings = nil; } @@ -106,6 +106,7 @@ bool qt_camera_service_isValid(AVFCameraService *service) - (bool)setupWithFileURL:(NSURL *)fileURL cameraService:(AVFCameraService *)service + audioSettings:(NSDictionary *)audioSettings videoSettings:(NSDictionary *)videoSettings { Q_ASSERT(fileURL); @@ -116,6 +117,7 @@ bool qt_camera_service_isValid(AVFCameraService *service) } m_service = service; + m_audioSettings = audioSettings; m_videoSettings = videoSettings; m_videoQueue.reset(dispatch_queue_create("video-output-queue", DISPATCH_QUEUE_SERIAL)); @@ -150,6 +152,7 @@ bool qt_camera_service_isValid(AVFCameraService *service) [session removeInput:m_audioInput]; m_audioOutput.reset(); m_audioInput.reset(); + m_audioCaptureDevice = 0; } m_assetWriter.reset(); return false; @@ -327,20 +330,22 @@ bool qt_camera_service_isValid(AVFCameraService *service) AVCaptureSession *captureSession = m_service->session()->captureSession(); - AVCaptureDevice *audioDevice = m_service->audioInputSelectorControl()->createCaptureDevice(); - if (!audioDevice) { + m_audioCaptureDevice = m_service->audioInputSelectorControl()->createCaptureDevice(); + if (!m_audioCaptureDevice) { qWarning() << Q_FUNC_INFO << "no audio input device available"; return false; } else { NSError *error = nil; - m_audioInput.reset([[AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error] retain]); + m_audioInput.reset([[AVCaptureDeviceInput deviceInputWithDevice:m_audioCaptureDevice error:&error] retain]); if (!m_audioInput || error) { qWarning() << Q_FUNC_INFO << "failed to create audio device input"; + m_audioCaptureDevice = 0; m_audioInput.reset(); return false; } else if (![captureSession canAddInput:m_audioInput]) { qWarning() << Q_FUNC_INFO << "could not connect the audio input"; + m_audioCaptureDevice = 0; m_audioInput.reset(); return false; } else { @@ -355,6 +360,7 @@ bool qt_camera_service_isValid(AVFCameraService *service) } else { qDebugCamera() << Q_FUNC_INFO << "failed to add audio output"; [captureSession removeInput:m_audioInput]; + m_audioCaptureDevice = 0; m_audioInput.reset(); m_audioOutput.reset(); return false; @@ -388,7 +394,10 @@ bool qt_camera_service_isValid(AVFCameraService *service) m_cameraWriterInput.data().expectsMediaDataInRealTime = YES; if (m_audioOutput) { - m_audioWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:[self audioSettings]]); + CMFormatDescriptionRef sourceFormat = m_audioCaptureDevice ? m_audioCaptureDevice.activeFormat.formatDescription : 0; + m_audioWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio + outputSettings:m_audioSettings + sourceFormatHint:sourceFormat]); if (!m_audioWriterInput) { qDebugCamera() << Q_FUNC_INFO << "failed to create audio writer input"; // But we still can record video. @@ -418,20 +427,6 @@ bool qt_camera_service_isValid(AVFCameraService *service) } } -- (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)); diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm b/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm index de2b14ef4..734d9d693 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm @@ -37,6 +37,7 @@ #include "avfcameraservice.h" #include "avfcameracontrol.h" #include "avfaudioinputselectorcontrol.h" +#include "avfaudioencodersettingscontrol.h" #include "avfvideoencodersettingscontrol.h" #include "avfmediacontainercontrol.h" @@ -234,6 +235,11 @@ void AVFMediaRecorderControl::applySettings() return; } + // Configure audio settings + [m_movieOutput setOutputSettings:m_service->audioEncoderSettingsControl()->applySettings() + forConnection:[m_movieOutput connectionWithMediaType:AVMediaTypeAudio]]; + + // Configure video settings AVCaptureConnection *videoConnection = [m_movieOutput connectionWithMediaType:AVMediaTypeVideo]; NSDictionary *videoSettings = m_service->videoEncoderSettingsControl()->applySettings(videoConnection); @@ -244,6 +250,7 @@ void AVFMediaRecorderControl::applySettings() void AVFMediaRecorderControl::unapplySettings() { + m_service->audioEncoderSettingsControl()->unapplySettings(); m_service->videoEncoderSettingsControl()->unapplySettings([m_movieOutput connectionWithMediaType:AVMediaTypeVideo]); } diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h index 5ee873ff7..66e27004f 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h @@ -100,6 +100,7 @@ private: QMediaRecorder::State m_state; QMediaRecorder::Status m_lastStatus; + NSDictionary *m_audioSettings; NSDictionary *m_videoSettings; }; diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm index 2052afb50..f2c16f675 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm @@ -38,6 +38,7 @@ #include "avfcameracontrol.h" #include "avfcameraservice.h" #include "avfcameradebug.h" +#include "avfaudioencodersettingscontrol.h" #include "avfvideoencodersettingscontrol.h" #include "avfmediacontainercontrol.h" #include "avfcamerautility.h" @@ -80,6 +81,7 @@ AVFMediaRecorderControlIOS::AVFMediaRecorderControlIOS(AVFCameraService *service , m_service(service) , m_state(QMediaRecorder::StoppedState) , m_lastStatus(QMediaRecorder::UnloadedStatus) + , m_audioSettings(nil) , m_videoSettings(nil) { Q_ASSERT(service); @@ -112,6 +114,8 @@ AVFMediaRecorderControlIOS::~AVFMediaRecorderControlIOS() { [m_writer abort]; + if (m_audioSettings) + [m_audioSettings release]; if (m_videoSettings) [m_videoSettings release]; } @@ -164,8 +168,13 @@ void AVFMediaRecorderControlIOS::applySettings() return; } - AVCaptureConnection *conn = [m_service->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo]; + // audio settings + m_audioSettings = m_service->audioEncoderSettingsControl()->applySettings(); + if (m_audioSettings) + [m_audioSettings retain]; + // video settings + AVCaptureConnection *conn = [m_service->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo]; m_videoSettings = m_service->videoEncoderSettingsControl()->applySettings(conn); if (m_videoSettings) [m_videoSettings retain]; @@ -173,9 +182,15 @@ void AVFMediaRecorderControlIOS::applySettings() void AVFMediaRecorderControlIOS::unapplySettings() { + m_service->audioEncoderSettingsControl()->unapplySettings(); + AVCaptureConnection *conn = [m_service->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo]; m_service->videoEncoderSettingsControl()->unapplySettings(conn); + if (m_audioSettings) { + [m_audioSettings release]; + m_audioSettings = nil; + } if (m_videoSettings) { [m_videoSettings release]; m_videoSettings = nil; @@ -247,7 +262,9 @@ void AVFMediaRecorderControlIOS::setState(QMediaRecorder::State state) applySettings(); - if ([m_writer setupWithFileURL:nsFileURL cameraService:m_service videoSettings:m_videoSettings]) { + if ([m_writer setupWithFileURL:nsFileURL cameraService:m_service + audioSettings:m_audioSettings + videoSettings:m_videoSettings]) { m_state = QMediaRecorder::RecordingState; m_lastStatus = QMediaRecorder::StartingStatus; diff --git a/src/plugins/avfoundation/camera/camera.pro b/src/plugins/avfoundation/camera/camera.pro index f9782ef8f..a17ff5a73 100644 --- a/src/plugins/avfoundation/camera/camera.pro +++ b/src/plugins/avfoundation/camera/camera.pro @@ -39,7 +39,8 @@ HEADERS += \ avfimageencodercontrol.h \ avfcameraflashcontrol.h \ avfvideoencodersettingscontrol.h \ - avfmediacontainercontrol.h + avfmediacontainercontrol.h \ + avfaudioencodersettingscontrol.h OBJECTIVE_SOURCES += \ avfcameraserviceplugin.mm \ @@ -61,7 +62,8 @@ OBJECTIVE_SOURCES += \ avfimageencodercontrol.mm \ avfcameraflashcontrol.mm \ avfvideoencodersettingscontrol.mm \ - avfmediacontainercontrol.mm + avfmediacontainercontrol.mm \ + avfaudioencodersettingscontrol.mm osx { -- cgit v1.2.3 From c6a8c2c846e8f4305aca05be662172e55d5fb674 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Tue, 9 Feb 2016 23:44:33 +0100 Subject: AVFoundation: fix orientation of recorded videos. Make sure the video is recorded in the same orientation as the device. That means the top of the video matches the physical side of the device which is on top when starting recording. This patch makes sure the behavior is the same as on Android. In a future version, we should have an API to pick the desired orientation of the recorded video. Change-Id: Ia8bbfe621a0e54de3cb6bfe0a520f37e8a932539 Reviewed-by: Christian Stromme --- .../avfoundation/camera/avfmediaassetwriter.h | 3 ++- .../avfoundation/camera/avfmediaassetwriter.mm | 4 ++++ .../camera/avfmediarecordercontrol_ios.h | 2 ++ .../camera/avfmediarecordercontrol_ios.mm | 23 +++++++++++++++++++--- 4 files changed, 28 insertions(+), 4 deletions(-) (limited to 'src/plugins/avfoundation/camera') diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.h b/src/plugins/avfoundation/camera/avfmediaassetwriter.h index ff24ad49a..9b2479909 100644 --- a/src/plugins/avfoundation/camera/avfmediaassetwriter.h +++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.h @@ -98,7 +98,8 @@ QT_END_NAMESPACE - (bool)setupWithFileURL:(NSURL *)fileURL cameraService:(QT_PREPEND_NAMESPACE(AVFCameraService) *)service audioSettings:(NSDictionary *)audioSettings - videoSettings:(NSDictionary *)videoSettings; + videoSettings:(NSDictionary *)videoSettings + transform:(CGAffineTransform)transform; - (void)start; - (void)stop; diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm index 396de8c35..59c1d1aa2 100644 --- a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm +++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm @@ -108,6 +108,7 @@ bool qt_camera_service_isValid(AVFCameraService *service) cameraService:(AVFCameraService *)service audioSettings:(NSDictionary *)audioSettings videoSettings:(NSDictionary *)videoSettings + transform:(CGAffineTransform)transform { Q_ASSERT(fileURL); @@ -157,6 +158,9 @@ bool qt_camera_service_isValid(AVFCameraService *service) m_assetWriter.reset(); return false; } + + m_cameraWriterInput.data().transform = transform; + // Ready to start ... return true; } diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h index 66e27004f..33cb08804 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h @@ -39,6 +39,7 @@ #include "avfcamerautility.h" #include +#include #include #include @@ -102,6 +103,7 @@ private: NSDictionary *m_audioSettings; NSDictionary *m_videoSettings; + QVideoOutputOrientationHandler m_orientationHandler; }; QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm index f2c16f675..e65c98257 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm @@ -262,9 +262,26 @@ void AVFMediaRecorderControlIOS::setState(QMediaRecorder::State state) applySettings(); - if ([m_writer setupWithFileURL:nsFileURL cameraService:m_service - audioSettings:m_audioSettings - videoSettings:m_videoSettings]) { + // Make sure the video is recorded in device orientation. + // The top of the video will match the side of the device which is on top + // when recording starts (regardless of the UI orientation). + AVFCameraInfo cameraInfo = m_service->session()->activeCameraInfo(); + int screenOrientation = 360 - m_orientationHandler.currentOrientation(); + float rotation = 0; + if (cameraInfo.position == QCamera::FrontFace) + rotation = (screenOrientation + cameraInfo.orientation) % 360; + else + rotation = (screenOrientation + (360 - cameraInfo.orientation)) % 360; + + // convert to radians + rotation *= M_PI / 180.f; + + if ([m_writer setupWithFileURL:nsFileURL + cameraService:m_service + audioSettings:m_audioSettings + videoSettings:m_videoSettings + transform:CGAffineTransformMakeRotation(rotation)]) { + m_state = QMediaRecorder::RecordingState; m_lastStatus = QMediaRecorder::StartingStatus; -- cgit v1.2.3 From af5e0d04852e5efc1ebd9d099f3906bc66a62338 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 10 Mar 2016 11:47:24 +0100 Subject: AVFoundation: re-apply viewfinder settings on mode changes. Since the active viewfinder resolution can be overridden by the image and video capture resolutions, we need to re-evaluate the viewfinder settings whenever the capture mode changes. Change-Id: Ibdb7a070585cf67ebb2fcfb95ccbdd105f5f41cf Reviewed-by: Christian Stromme --- .../avfoundation/camera/avfcameracontrol.mm | 3 ++- src/plugins/avfoundation/camera/avfcamerasession.h | 4 +++- .../avfoundation/camera/avfcamerasession.mm | 22 +++++++++++++++++----- .../avfoundation/camera/avfimagecapturecontrol.mm | 3 ++- .../avfoundation/camera/avfimageencodercontrol.mm | 6 ++++-- 5 files changed, 28 insertions(+), 10 deletions(-) (limited to 'src/plugins/avfoundation/camera') diff --git a/src/plugins/avfoundation/camera/avfcameracontrol.mm b/src/plugins/avfoundation/camera/avfcameracontrol.mm index bef952e6f..8950c4f95 100644 --- a/src/plugins/avfoundation/camera/avfcameracontrol.mm +++ b/src/plugins/avfoundation/camera/avfcameracontrol.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies). +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -47,6 +47,7 @@ AVFCameraControl::AVFCameraControl(AVFCameraService *service, QObject *parent) { Q_UNUSED(service); connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(updateStatus())); + connect(this, &AVFCameraControl::captureModeChanged, m_session, &AVFCameraSession::onCaptureModeChanged); } AVFCameraControl::~AVFCameraControl() diff --git a/src/plugins/avfoundation/camera/avfcamerasession.h b/src/plugins/avfoundation/camera/avfcamerasession.h index 13a8a35c5..f42f84582 100644 --- a/src/plugins/avfoundation/camera/avfcamerasession.h +++ b/src/plugins/avfoundation/camera/avfcamerasession.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2016 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -93,6 +93,8 @@ public Q_SLOTS: void processSessionStarted(); void processSessionStopped(); + void onCaptureModeChanged(QCamera::CaptureModes mode); + void onCameraFrameFetched(const QVideoFrame &frame); Q_SIGNALS: diff --git a/src/plugins/avfoundation/camera/avfcamerasession.mm b/src/plugins/avfoundation/camera/avfcamerasession.mm index eac1b44ec..9e7c28755 100644 --- a/src/plugins/avfoundation/camera/avfcamerasession.mm +++ b/src/plugins/avfoundation/camera/avfcamerasession.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies). +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -286,8 +286,8 @@ void AVFCameraSession::setState(QCamera::State newState) m_defaultCodec = 0; defaultCodec(); - bool activeFormatSet = applyImageEncoderSettings(); - activeFormatSet |= applyViewfinderSettings(); + bool activeFormatSet = applyImageEncoderSettings() + | applyViewfinderSettings(); [m_captureSession commitConfiguration]; @@ -338,6 +338,17 @@ void AVFCameraSession::processSessionStopped() } } +void AVFCameraSession::onCaptureModeChanged(QCamera::CaptureModes mode) +{ + Q_UNUSED(mode) + + const QCamera::State s = state(); + if (s == QCamera::LoadedState || s == QCamera::ActiveState) { + applyImageEncoderSettings(); + applyViewfinderSettings(); + } +} + void AVFCameraSession::attachVideoInputDevice() { //Attach video input device: @@ -381,11 +392,12 @@ bool AVFCameraSession::applyImageEncoderSettings() bool AVFCameraSession::applyViewfinderSettings() { if (AVFCameraViewfinderSettingsControl2 *vfControl = m_service->viewfinderSettingsControl2()) { + QCamera::CaptureModes currentMode = m_service->cameraControl()->captureMode(); QCameraViewfinderSettings vfSettings(vfControl->requestedSettings()); // Viewfinder and image capture solutions must be the same, if an image capture // resolution is set, it takes precedence over the viewfinder resolution. - if (AVFImageEncoderControl *imControl = m_service->imageEncoderControl()) { - const QSize imageResolution(imControl->requestedSettings().resolution()); + if (currentMode.testFlag(QCamera::CaptureStillImage)) { + const QSize imageResolution(m_service->imageEncoderControl()->requestedSettings().resolution()); if (!imageResolution.isNull() && imageResolution.isValid()) vfSettings.setResolution(imageResolution); } diff --git a/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm b/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm index 3e6b6c778..28c77a533 100644 --- a/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm +++ b/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies). +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -67,6 +67,7 @@ AVFImageCaptureControl::AVFImageCaptureControl(AVFCameraService *service, QObjec connect(m_cameraControl, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateReadyStatus())); connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(updateCaptureConnection())); + connect(m_cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(updateCaptureConnection())); connect(m_session, &AVFCameraSession::newViewfinderFrame, this, &AVFImageCaptureControl::onNewViewfinderFrame, diff --git a/src/plugins/avfoundation/camera/avfimageencodercontrol.mm b/src/plugins/avfoundation/camera/avfimageencodercontrol.mm index e2eb0bd01..b35008030 100644 --- a/src/plugins/avfoundation/camera/avfimageencodercontrol.mm +++ b/src/plugins/avfoundation/camera/avfimageencodercontrol.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2016 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -38,6 +38,7 @@ #include "avfcamerasession.h" #include "avfcameraservice.h" #include "avfcameradebug.h" +#include "avfcameracontrol.h" #include @@ -182,7 +183,8 @@ bool AVFImageEncoderControl::applySettings() AVFCameraSession *session = m_service->session(); if (!session || (session->state() != QCamera::ActiveState - && session->state() != QCamera::LoadedState)) { + && session->state() != QCamera::LoadedState) + || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureStillImage)) { return false; } -- cgit v1.2.3 From 32b8310e34890b83929f8b143dc9947f370994e3 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Wed, 9 Mar 2016 16:26:33 +0100 Subject: AVFoundation: improve changing the capture device format. Make sure the device format is always set in the same way. We don't actually set the format anymore when it's the same as the current one. We also make sure the frame rate is preserved. Change-Id: I1c68239bc99d9c3cef920effcf47fc253220c26f Reviewed-by: Timur Pocheptsov --- src/plugins/avfoundation/camera/avfcamerautility.h | 3 ++ .../avfoundation/camera/avfcamerautility.mm | 50 ++++++++++++++++++++++ .../camera/avfcameraviewfindersettingscontrol.mm | 10 +---- .../avfoundation/camera/avfimageencodercontrol.mm | 10 +---- .../camera/avfvideoencodersettingscontrol.mm | 44 ++++++++----------- 5 files changed, 74 insertions(+), 43 deletions(-) (limited to 'src/plugins/avfoundation/camera') diff --git a/src/plugins/avfoundation/camera/avfcamerautility.h b/src/plugins/avfoundation/camera/avfcamerautility.h index 7a0de4a66..79e9359ec 100644 --- a/src/plugins/avfoundation/camera/avfcamerautility.h +++ b/src/plugins/avfoundation/camera/avfcamerautility.h @@ -176,6 +176,9 @@ AVCaptureDeviceFormat *qt_find_best_framerate_match(AVCaptureDevice *captureDevi Float64 fps); AVFrameRateRange *qt_find_supported_framerate_range(AVCaptureDeviceFormat *format, Float64 fps); +bool qt_formats_are_equal(AVCaptureDeviceFormat *f1, AVCaptureDeviceFormat *f2); +bool qt_set_active_format(AVCaptureDevice *captureDevice, AVCaptureDeviceFormat *format, bool preserveFps); + #endif AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection); diff --git a/src/plugins/avfoundation/camera/avfcamerautility.mm b/src/plugins/avfoundation/camera/avfcamerautility.mm index 712868d22..4bec1dbe9 100644 --- a/src/plugins/avfoundation/camera/avfcamerautility.mm +++ b/src/plugins/avfoundation/camera/avfcamerautility.mm @@ -380,6 +380,56 @@ AVFrameRateRange *qt_find_supported_framerate_range(AVCaptureDeviceFormat *forma return match; } +bool qt_formats_are_equal(AVCaptureDeviceFormat *f1, AVCaptureDeviceFormat *f2) +{ + if (f1 == f2) + return true; + + if (![f1.mediaType isEqualToString:f2.mediaType]) + return false; + + return CMFormatDescriptionEqual(f1.formatDescription, f2.formatDescription); +} + +bool qt_set_active_format(AVCaptureDevice *captureDevice, AVCaptureDeviceFormat *format, bool preserveFps) +{ + static bool firstSet = true; + + if (!captureDevice || !format) + return false; + + if (qt_formats_are_equal(captureDevice.activeFormat, format)) { + if (firstSet) { + // The capture device format is persistent. The first time we set a format, report that + // it changed even if the formats are the same. + // This prevents the session from resetting the format to the default value. + firstSet = false; + return true; + } + return false; + } + + firstSet = false; + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qWarning("Failed to set active format (lock failed)"); + return false; + } + + // Changing the activeFormat resets the frame rate. + AVFPSRange fps; + if (preserveFps) + fps = qt_current_framerates(captureDevice, nil); + + captureDevice.activeFormat = format; + + if (preserveFps) + qt_set_framerate_limits(captureDevice, nil, fps.first, fps.second); + + return true; +} + #endif // SDK void qt_set_framerate_limits(AVCaptureConnection *videoConnection, qreal minFPS, qreal maxFPS) diff --git a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm index 1d2539893..14f146d25 100644 --- a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm +++ b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm @@ -382,15 +382,7 @@ bool AVFCameraViewfinderSettingsControl2::applySettings(const QCameraViewfinderS #if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) AVCaptureDeviceFormat *match = findBestFormatMatch(settings); if (match) { - if (match != captureDevice.activeFormat) { - const AVFConfigurationLock lock(captureDevice); - if (lock) { - captureDevice.activeFormat = match; - activeFormatChanged = true; - } else { - qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; - } - } + activeFormatChanged = qt_set_active_format(captureDevice, match, false); } else { qDebugCamera() << Q_FUNC_INFO << "matching device format not found"; // We still can update the pixel format at least. diff --git a/src/plugins/avfoundation/camera/avfimageencodercontrol.mm b/src/plugins/avfoundation/camera/avfimageencodercontrol.mm index b35008030..20de4b5e8 100644 --- a/src/plugins/avfoundation/camera/avfimageencodercontrol.mm +++ b/src/plugins/avfoundation/camera/avfimageencodercontrol.mm @@ -227,15 +227,7 @@ bool AVFImageEncoderControl::applySettings() return false; } - if (match != captureDevice.activeFormat) { - const AVFConfigurationLock lock(captureDevice); - if (!lock) { - qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; - return false; - } - captureDevice.activeFormat = match; - activeFormatChanged = true; - } + activeFormatChanged = qt_set_active_format(captureDevice, match, true); #if defined(Q_OS_IOS) && QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_8_0) if (QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_8_0) { diff --git a/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm b/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm index 87bc91129..248997d57 100644 --- a/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm +++ b/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm @@ -215,6 +215,10 @@ NSDictionary *AVFVideoEncoderSettingsControl::applySettings(AVCaptureConnection if (!device) return nil; + AVFPSRange currentFps = qt_current_framerates(device, connection); + const bool needFpsChange = m_requestedSettings.frameRate() > 0 + && m_requestedSettings.frameRate() != currentFps.second; + NSMutableDictionary *videoSettings = [NSMutableDictionary dictionary]; // -- Codec @@ -234,8 +238,8 @@ NSDictionary *AVFVideoEncoderSettingsControl::applySettings(AVCaptureConnection int h = m_requestedSettings.resolution().height(); #if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - if (device.activeFormat) { - CMFormatDescriptionRef formatDesc = device.activeFormat.formatDescription; + if (AVCaptureDeviceFormat *currentFormat = device.activeFormat) { + CMFormatDescriptionRef formatDesc = currentFormat.formatDescription; CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc); // We have to change the device's activeFormat in 3 cases: @@ -245,7 +249,7 @@ NSDictionary *AVFVideoEncoderSettingsControl::applySettings(AVCaptureConnection AVCaptureDeviceFormat *newFormat = nil; if ((w <= 0 || h <= 0) && m_requestedSettings.frameRate() > 0 - && !format_supports_framerate(device.activeFormat, m_requestedSettings.frameRate())) { + && !format_supports_framerate(currentFormat, m_requestedSettings.frameRate())) { newFormat = qt_find_best_framerate_match(device, m_service->session()->defaultCodec(), @@ -267,17 +271,10 @@ NSDictionary *AVFVideoEncoderSettingsControl::applySettings(AVCaptureConnection } } - if (newFormat && newFormat != device.activeFormat) { - const AVFConfigurationLock lock(device); - if (lock) { - m_restoreFormat = [device.activeFormat retain]; - m_restoreFps = qt_current_framerates(device, connection); - - device.activeFormat = newFormat; - - formatDesc = newFormat.formatDescription; - dim = CMVideoFormatDescriptionGetDimensions(formatDesc); - } + if (qt_set_active_format(device, newFormat, !needFpsChange)) { + m_restoreFormat = [currentFormat retain]; + formatDesc = newFormat.formatDescription; + dim = CMVideoFormatDescriptionGetDimensions(formatDesc); } if (w > 0 && h > 0) { @@ -313,14 +310,12 @@ NSDictionary *AVFVideoEncoderSettingsControl::applySettings(AVCaptureConnection // -- FPS - const qreal fps = m_requestedSettings.frameRate(); - if (fps > qreal(0)) { - if (!m_restoreFps.first && !m_restoreFps.second) - m_restoreFps = qt_current_framerates(device, connection); + if (needFpsChange) { + m_restoreFps = currentFps; + const qreal fps = m_requestedSettings.frameRate(); qt_set_framerate_limits(device, connection, fps, fps); } - AVFPSRange currentFps = qt_current_framerates(device, connection); - m_actualSettings.setFrameRate(currentFps.second); + m_actualSettings.setFrameRate(qt_current_framerates(device, connection).second); // -- Codec Settings @@ -379,18 +374,17 @@ void AVFVideoEncoderSettingsControl::unapplySettings(AVCaptureConnection *connec if (!device) return; + const bool needFpsChanged = m_restoreFps.first || m_restoreFps.second; + #if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) if (m_restoreFormat) { - const AVFConfigurationLock lock(device); - if (lock) - device.activeFormat = m_restoreFormat; - + qt_set_active_format(device, m_restoreFormat, !needFpsChanged); [m_restoreFormat release]; m_restoreFormat = nil; } #endif - if (m_restoreFps.first || m_restoreFps.second) { + if (needFpsChanged) { qt_set_framerate_limits(device, connection, m_restoreFps.first, m_restoreFps.second); m_restoreFps = AVFPSRange(); } -- cgit v1.2.3