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 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