From 0d783b730372ffaeebc22e882a3c65d83445932f Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Wed, 28 Jan 2015 17:51:19 +0100 Subject: [PATCH] Viewfinder settings control (2) - version for iOS/OS X QCameraViewfinderSettingsControl2 - version for AV foundation plugin (the new settings control interface implemented). Change-Id: I3fbfb87925e57c914d43eb711fa5422e26981207 Reviewed-by: Yoann Lopes --- .../avfoundation/camera/avfcamerarenderercontrol.h | 2 + .../camera/avfcamerarenderercontrol.mm | 18 +- src/plugins/avfoundation/camera/avfcameraservice.h | 4 + .../avfoundation/camera/avfcameraservice.mm | 7 + src/plugins/avfoundation/camera/avfcamerasession.h | 1 + .../avfoundation/camera/avfcamerasession.mm | 11 + .../camera/avfcameraviewfindersettingscontrol.h | 91 ++++ .../camera/avfcameraviewfindersettingscontrol.mm | 579 +++++++++++++++++++++ src/plugins/avfoundation/camera/camera.pro | 6 +- 9 files changed, 716 insertions(+), 3 deletions(-) create mode 100644 src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h create mode 100644 src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm diff --git a/src/plugins/avfoundation/camera/avfcamerarenderercontrol.h b/src/plugins/avfoundation/camera/avfcamerarenderercontrol.h index 5fbbcb6..92ab75b 100644 --- a/src/plugins/avfoundation/camera/avfcamerarenderercontrol.h +++ b/src/plugins/avfoundation/camera/avfcamerarenderercontrol.h @@ -61,6 +61,8 @@ public: void configureAVCaptureSession(AVFCameraSession *cameraSession); void syncHandleViewfinderFrame(const QVideoFrame &frame); + AVCaptureVideoDataOutput *videoDataOutput() const; + Q_SIGNALS: void surfaceChanged(QAbstractVideoSurface *surface); diff --git a/src/plugins/avfoundation/camera/avfcamerarenderercontrol.mm b/src/plugins/avfoundation/camera/avfcamerarenderercontrol.mm index 05edd0a..87bfeb8 100644 --- a/src/plugins/avfoundation/camera/avfcamerarenderercontrol.mm +++ b/src/plugins/avfoundation/camera/avfcamerarenderercontrol.mm @@ -31,6 +31,7 @@ ** ****************************************************************************/ +#include "avfcameraviewfindersettingscontrol.h" #include "avfcamerarenderercontrol.h" #include "avfcamerasession.h" #include "avfcameraservice.h" @@ -129,7 +130,17 @@ private: int height = CVPixelBufferGetHeight(imageBuffer); QAbstractVideoBuffer *buffer = new CVPixelBufferVideoBuffer(imageBuffer); - QVideoFrame frame(buffer, QSize(width, height), QVideoFrame::Format_RGB32); + + QVideoFrame::PixelFormat format = QVideoFrame::Format_RGB32; + if ([captureOutput isKindOfClass:[AVCaptureVideoDataOutput class]]) { + NSDictionary *settings = ((AVCaptureVideoDataOutput *)captureOutput).videoSettings; + if (settings && [settings objectForKey:(id)kCVPixelBufferPixelFormatTypeKey]) { + NSNumber *avf = [settings objectForKey:(id)kCVPixelBufferPixelFormatTypeKey]; + format = AVFCameraViewfinderSettingsControl2::QtPixelFormatFromCVFormat([avf unsignedIntValue]); + } + } + + QVideoFrame frame(buffer, QSize(width, height), format); m_renderer->syncHandleViewfinderFrame(frame); } @end @@ -236,6 +247,11 @@ void AVFCameraRendererControl::syncHandleViewfinderFrame(const QVideoFrame &fram m_cameraSession->onCameraFrameFetched(m_lastViewfinderFrame); } +AVCaptureVideoDataOutput *AVFCameraRendererControl::videoDataOutput() const +{ + return m_videoDataOutput; +} + void AVFCameraRendererControl::handleViewfinderFrame() { QVideoFrame frame; diff --git a/src/plugins/avfoundation/camera/avfcameraservice.h b/src/plugins/avfoundation/camera/avfcameraservice.h index 752065b..fffa144 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.h +++ b/src/plugins/avfoundation/camera/avfcameraservice.h @@ -55,6 +55,7 @@ class AVFAudioInputSelectorControl; class AVFCameraFocusControl; class AVFCameraExposureControl; class AVFCameraZoomControl; +class AVFCameraViewfinderSettingsControl2; class AVFCameraService : public QMediaService { @@ -76,6 +77,8 @@ public: AVFCameraFocusControl *cameraFocusControl() const { return m_cameraFocusControl; } AVFCameraExposureControl *cameraExposureControl() const {return m_cameraExposureControl; } AVFCameraZoomControl *cameraZoomControl() const {return m_cameraZoomControl; } + AVFCameraRendererControl *videoOutput() const {return m_videoOutput; } + AVFCameraViewfinderSettingsControl2 *viewfinderSettingsControl2() const {return m_viewfinderSettingsControl2; } private: AVFCameraSession *m_session; @@ -90,6 +93,7 @@ private: AVFCameraFocusControl *m_cameraFocusControl; AVFCameraExposureControl *m_cameraExposureControl; AVFCameraZoomControl *m_cameraZoomControl; + AVFCameraViewfinderSettingsControl2 *m_viewfinderSettingsControl2; }; QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfcameraservice.mm b/src/plugins/avfoundation/camera/avfcameraservice.mm index 5b4a90b..188dc82 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.mm +++ b/src/plugins/avfoundation/camera/avfcameraservice.mm @@ -50,6 +50,7 @@ #include "avfmediavideoprobecontrol.h" #include "avfcamerafocuscontrol.h" #include "avfcameraexposurecontrol.h" +#include "avfcameraviewfindersettingscontrol.h" #ifdef Q_OS_IOS #include "avfcamerazoomcontrol.h" @@ -84,6 +85,7 @@ AVFCameraService::AVFCameraService(QObject *parent): #ifdef Q_OS_IOS m_cameraZoomControl = new AVFCameraZoomControl(this); #endif + m_viewfinderSettingsControl2 = new AVFCameraViewfinderSettingsControl2(this); } AVFCameraService::~AVFCameraService() @@ -107,6 +109,8 @@ AVFCameraService::~AVFCameraService() #ifdef Q_OS_IOS delete m_cameraZoomControl; #endif + delete m_viewfinderSettingsControl2; + delete m_session; } @@ -140,6 +144,9 @@ QMediaControl *AVFCameraService::requestControl(const char *name) if (qstrcmp(name, QCameraFocusControl_iid) == 0) return m_cameraFocusControl; + if (qstrcmp(name, QCameraViewfinderSettingsControl2_iid) == 0) + return m_viewfinderSettingsControl2; + if (qstrcmp(name,QMediaVideoProbeControl_iid) == 0) { AVFMediaVideoProbeControl *videoProbe = 0; videoProbe = new AVFMediaVideoProbeControl(this); diff --git a/src/plugins/avfoundation/camera/avfcamerasession.h b/src/plugins/avfoundation/camera/avfcamerasession.h index 8598d7c..c5bae66 100644 --- a/src/plugins/avfoundation/camera/avfcamerasession.h +++ b/src/plugins/avfoundation/camera/avfcamerasession.h @@ -98,6 +98,7 @@ Q_SIGNALS: private: static void updateCameraDevices(); void attachInputDevices(); + void applyViewfinderSettings(); static QByteArray m_defaultCameraDevice; static QList m_cameraDevices; diff --git a/src/plugins/avfoundation/camera/avfcamerasession.mm b/src/plugins/avfoundation/camera/avfcamerasession.mm index e7e8864..5570aa8 100644 --- a/src/plugins/avfoundation/camera/avfcamerasession.mm +++ b/src/plugins/avfoundation/camera/avfcamerasession.mm @@ -39,6 +39,7 @@ #include "avfcameradevicecontrol.h" #include "avfaudioinputselectorcontrol.h" #include "avfmediavideoprobecontrol.h" +#include "avfcameraviewfindersettingscontrol.h" #include #include @@ -275,6 +276,7 @@ void AVFCameraSession::setState(QCamera::State newState) Q_EMIT readyToConfigureConnections(); [m_captureSession commitConfiguration]; [m_captureSession startRunning]; + applyViewfinderSettings(); } if (oldState == QCamera::ActiveState) { @@ -364,6 +366,15 @@ void AVFCameraSession::attachInputDevices() } } +void AVFCameraSession::applyViewfinderSettings() +{ + if (AVFCameraViewfinderSettingsControl2 *control = m_service->viewfinderSettingsControl2()) { + QCameraViewfinderSettings settings(control->requestedSettings()); + // TODO: Adjust the resolution (from image encoder control), updating 'settings'. + control->setViewfinderSettings(settings); + } +} + void AVFCameraSession::addProbe(AVFMediaVideoProbeControl *probe) { m_videoProbesMutex.lock(); diff --git a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h new file mode 100644 index 0000000..d2864c6 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef AVFCAMERAVIEWFINDERSETTINGSCONTROL_H +#define AVFCAMERAVIEWFINDERSETTINGSCONTROL_H + +#include +#include +#include + +#include +#include + +@class AVCaptureDevice; +@class AVCaptureVideoDataOutput; +@class AVCaptureConnection; +@class AVCaptureDeviceFormat; + +QT_BEGIN_NAMESPACE + +class AVFCameraSession; +class AVFCameraService; + +class AVFCameraViewfinderSettingsControl2 : public QCameraViewfinderSettingsControl2 +{ + Q_OBJECT + + friend class AVFCameraSession; +public: + AVFCameraViewfinderSettingsControl2(AVFCameraService *service); + + QList supportedViewfinderSettings() const Q_DECL_OVERRIDE; + QCameraViewfinderSettings viewfinderSettings() const Q_DECL_OVERRIDE; + void setViewfinderSettings(const QCameraViewfinderSettings &settings) Q_DECL_OVERRIDE; + + // "Converters": + static QVideoFrame::PixelFormat QtPixelFormatFromCVFormat(unsigned avPixelFormat); + static bool CVPixelFormatFromQtFormat(QVideoFrame::PixelFormat qtFormat, unsigned &conv); + +private: + void setResolution(const QSize &resolution); + void setFramerate(qreal minFPS, qreal maxFPS, bool useActive); + void setPixelFormat(QVideoFrame::PixelFormat newFormat); + AVCaptureDeviceFormat *findBestFormatMatch(const QCameraViewfinderSettings &settings) const; + bool convertPixelFormatIfSupported(QVideoFrame::PixelFormat format, unsigned &avfFormat) const; + void applySettings(); + QCameraViewfinderSettings requestedSettings() const; + // Aux. function to extract things like captureDevice, videoOutput, etc. + bool updateAVFoundationObjects() const; + + AVFCameraService *m_service; + mutable AVFCameraSession *m_session; + QCameraViewfinderSettings m_settings; + mutable AVCaptureDevice *m_captureDevice; + mutable AVCaptureVideoDataOutput *m_videoOutput; + mutable AVCaptureConnection *m_videoConnection; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm new file mode 100644 index 0000000..c9d04f6 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm @@ -0,0 +1,579 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "avfcameraviewfindersettingscontrol.h" +#include "avfcamerarenderercontrol.h" +#include "avfcamerautility.h" +#include "avfcamerasession.h" +#include "avfcameraservice.h" +#include "avfcameradebug.h" + +#include +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace { + +QVector qt_viewfinder_pixel_formats(AVCaptureVideoDataOutput *videoOutput) +{ + Q_ASSERT(videoOutput); + + QVector qtFormats; + + NSArray *pixelFormats = [videoOutput availableVideoCVPixelFormatTypes]; + for (NSObject *obj in pixelFormats) { + if (![obj isKindOfClass:[NSNumber class]]) + continue; + + NSNumber *formatAsNSNumber = static_cast(obj); + // It's actually FourCharCode (== UInt32): + const QVideoFrame::PixelFormat qtFormat(AVFCameraViewfinderSettingsControl2:: + QtPixelFormatFromCVFormat([formatAsNSNumber unsignedIntValue])); + if (qtFormat != QVideoFrame::Format_Invalid) + qtFormats << qtFormat; + } + + return qtFormats; +} + +bool qt_framerates_sane(const QCameraViewfinderSettings &settings) +{ + const qreal minFPS = settings.minimumFrameRate(); + const qreal maxFPS = settings.maximumFrameRate(); + + if (minFPS < 0. || maxFPS < 0.) + return false; + + 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 minFPS = settings.minimumFrameRate(); + const qreal maxFPS = settings.maximumFrameRate(); + + CMTime minDuration = kCMTimeInvalid; + CMTime maxDuration = kCMTimeInvalid; + if (minFPS > 0. || maxFPS > 0.) { + if (maxFPS) { + if (!videoConnection.supportsVideoMinFrameDuration) + qDebugCamera() << Q_FUNC_INFO << "maximum framerate is not supported"; + else + minDuration = CMTimeMake(1, maxFPS); + } + + if (minFPS) { + if (!videoConnection.supportsVideoMaxFrameDuration) + qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; + else + maxDuration = CMTimeMake(1, minFPS); + } + } + + if (videoConnection.supportsVideoMinFrameDuration) + videoConnection.videoMinFrameDuration = minDuration; + if (videoConnection.supportsVideoMaxFrameDuration) + videoConnection.videoMaxFrameDuration = maxDuration; +} + +#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; + + const AVFRational timeAsRational(qt_float_to_rational(1. / fps, 1000)); + return CMTimeMake(timeAsRational.first, timeAsRational.second); +} + +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 + if (CMTimeCompare(minFrameDuration, kCMTimeInvalid)) + [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; + if (CMTimeCompare(maxFrameDuration, kCMTimeInvalid)) + [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; +#endif +} + +#endif // Platform SDK >= 10.9, >= 7.0. + +// 'Dispatchers': + +AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection) +{ + Q_ASSERT(captureDevice); + Q_ASSERT(videoConnection); + + 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. + } + + const CMTime maxDuration = captureDevice.activeVideoMaxFrameDuration; + if (CMTimeCompare(maxDuration, kCMTimeInvalid)) { + if (const Float64 maxSeconds = CMTimeGetSeconds(maxDuration)) + fps.first = 1. / maxSeconds; // Min FPS = 1 / MaxDuration. + } + } else { +#else + { +#endif + fps = qt_connection_framerates(videoConnection); + } + + return fps; +} + +void qt_set_framerate_limits(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection, + const QCameraViewfinderSettings &settings) +{ + Q_ASSERT(captureDevice); + Q_ASSERT(videoConnection); +#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 + qt_set_framerate_limits(videoConnection, settings); +#else + qt_set_framerate_limits(videoConnection, settings); +#endif +} + +} // Unnamed namespace. + +AVFCameraViewfinderSettingsControl2::AVFCameraViewfinderSettingsControl2(AVFCameraService *service) + : m_service(service), + m_captureDevice(0), + m_videoOutput(0), + m_videoConnection(0) +{ + Q_ASSERT(service); +} + +QList AVFCameraViewfinderSettingsControl2::supportedViewfinderSettings() const +{ + QList supportedSettings; + + if (!updateAVFoundationObjects()) { + qDebugCamera() << Q_FUNC_INFO << "no capture device or video output found"; + return supportedSettings; + } + + QVector framerates; + + QVector pixelFormats(qt_viewfinder_pixel_formats(m_videoOutput)); + if (!pixelFormats.size()) + pixelFormats << QVideoFrame::Format_Invalid; // The default value. +#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)) { + if (!m_captureDevice.formats || !m_captureDevice.formats.count) { + qDebugCamera() << Q_FUNC_INFO << "no capture device formats found"; + return supportedSettings; + } + + for (AVCaptureDeviceFormat *format in m_captureDevice.formats) { + if (qt_is_video_range_subtype(format)) + continue; + const QSize res(qt_device_format_resolution(format)); + if (res.isNull() || !res.isValid()) + continue; + const QSize par(qt_device_format_pixel_aspect_ratio(format)); + if (par.isNull() || !par.isValid()) + continue; + + framerates = qt_device_format_framerates(format); + if (!framerates.size()) + framerates << AVFPSRange(); // The default value. + + for (int i = 0; i < pixelFormats.size(); ++i) { + for (int j = 0; j < framerates.size(); ++j) { + QCameraViewfinderSettings newSet; + newSet.setResolution(res); + newSet.setPixelAspectRatio(par); + newSet.setPixelFormat(pixelFormats[i]); + newSet.setMinimumFrameRate(framerates[j].first); + newSet.setMaximumFrameRate(framerates[j].second); + supportedSettings << newSet; + } + } + } + } else { +#else + { +#endif + // TODO: resolution and PAR. + framerates << qt_connection_framerates(m_videoConnection); + for (int i = 0; i < pixelFormats.size(); ++i) { + for (int j = 0; j < framerates.size(); ++j) { + QCameraViewfinderSettings newSet; + newSet.setPixelFormat(pixelFormats[i]); + newSet.setMinimumFrameRate(framerates[j].first); + newSet.setMaximumFrameRate(framerates[j].second); + supportedSettings << newSet; + } + } + } + + return supportedSettings; +} + +QCameraViewfinderSettings AVFCameraViewfinderSettingsControl2::viewfinderSettings() const +{ + QCameraViewfinderSettings settings; + + if (!updateAVFoundationObjects()) { + qDebugCamera() << Q_FUNC_INFO << "no capture device or video output found"; + return settings; + } + +#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)) { + if (!m_captureDevice.activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "no active capture device format"; + return settings; + } + + const QSize res(qt_device_format_resolution(m_captureDevice.activeFormat)); + const QSize par(qt_device_format_pixel_aspect_ratio(m_captureDevice.activeFormat)); + if (res.isNull() || !res.isValid() || par.isNull() || !par.isValid()) { + qDebugCamera() << Q_FUNC_INFO << "failed to obtain resolution/pixel aspect ratio"; + return settings; + } + + settings.setResolution(res); + settings.setPixelAspectRatio(par); + } +#endif + // TODO: resolution and PAR before 7.0. + const AVFPSRange fps = qt_current_framerates(m_captureDevice, m_videoConnection); + settings.setMinimumFrameRate(fps.first); + settings.setMaximumFrameRate(fps.second); + + if (NSObject *obj = [m_videoOutput.videoSettings objectForKey:(id)kCVPixelBufferPixelFormatTypeKey]) { + if ([obj isKindOfClass:[NSNumber class]]) { + NSNumber *nsNum = static_cast(obj); + settings.setPixelFormat(QtPixelFormatFromCVFormat([nsNum unsignedIntValue])); + } + } + + return settings; +} + +void AVFCameraViewfinderSettingsControl2::setViewfinderSettings(const QCameraViewfinderSettings &settings) +{ + if (settings.isNull()) { + qDebugCamera() << Q_FUNC_INFO << "empty viewfinder settings"; + return; + } + + if (m_settings == settings) + return; + + m_settings = settings; + applySettings(); +} + +QVideoFrame::PixelFormat AVFCameraViewfinderSettingsControl2::QtPixelFormatFromCVFormat(unsigned avPixelFormat) +{ + // BGRA <-> ARGB "swap" is intentional: + // to work correctly with GL_RGBA, color swap shaders + // (in QSG node renderer etc.). + switch (avPixelFormat) { + case kCVPixelFormatType_32ARGB: + return QVideoFrame::Format_BGRA32; + case kCVPixelFormatType_32BGRA: + return QVideoFrame::Format_ARGB32; + case kCVPixelFormatType_24RGB: + return QVideoFrame::Format_RGB24; + case kCVPixelFormatType_24BGR: + return QVideoFrame::Format_BGR24; + default: + return QVideoFrame::Format_Invalid; + } +} + +bool AVFCameraViewfinderSettingsControl2::CVPixelFormatFromQtFormat(QVideoFrame::PixelFormat qtFormat, unsigned &conv) +{ + // BGRA <-> ARGB "swap" is intentional: + // to work correctly with GL_RGBA, color swap shaders + // (in QSG node renderer etc.). + switch (qtFormat) { + case QVideoFrame::Format_ARGB32: + conv = kCVPixelFormatType_32BGRA; + break; + case QVideoFrame::Format_BGRA32: + conv = kCVPixelFormatType_32ARGB; + break; + // These two formats below are not supported + // by QSGVideoNodeFactory_RGB, so for now I have to + // disable them. + /* + case QVideoFrame::Format_RGB24: + conv = kCVPixelFormatType_24RGB; + break; + case QVideoFrame::Format_BGR24: + conv = kCVPixelFormatType_24BGR; + break; + */ + default: + return false; + } + + return true; +} + +AVCaptureDeviceFormat *AVFCameraViewfinderSettingsControl2::findBestFormatMatch(const QCameraViewfinderSettings &settings) const +{ +#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)) { + Q_ASSERT(m_captureDevice); + + const QSize &resolution = settings.resolution(); + if (!resolution.isNull() && resolution.isValid()) { + // Either the exact match (including high resolution for images on iOS) + // or a format with a resolution close to the requested one. + return qt_find_best_resolution_match(m_captureDevice, resolution); + } + + // No resolution requested, what about framerates? + if (!qt_framerates_sane(settings)) { + qDebugCamera() << Q_FUNC_INFO << "invalid framerate requested (min/max):" + << settings.minimumFrameRate() << settings.maximumFrameRate(); + return nil; + } + + const qreal minFPS(settings.minimumFrameRate()); + const qreal maxFPS(settings.maximumFrameRate()); + if (minFPS || maxFPS) + return qt_find_best_framerate_match(m_captureDevice, 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. + } +#endif + return nil; +} + +bool AVFCameraViewfinderSettingsControl2::convertPixelFormatIfSupported(QVideoFrame::PixelFormat qtFormat, unsigned &avfFormat)const +{ + Q_ASSERT(m_videoOutput); + + unsigned conv = 0; + if (!CVPixelFormatFromQtFormat(qtFormat, conv)) + return false; + + NSArray *formats = [m_videoOutput availableVideoCVPixelFormatTypes]; + if (!formats || !formats.count) + return false; + + for (NSObject *obj in formats) { + if (![obj isKindOfClass:[NSNumber class]]) + continue; + NSNumber *nsNum = static_cast(obj); + if ([nsNum unsignedIntValue] == conv) { + avfFormat = conv; + return true; + } + } + + return false; +} + +void AVFCameraViewfinderSettingsControl2::applySettings() +{ + if (m_settings.isNull()) + return; + + if (!updateAVFoundationObjects()) + return; + + if (m_session->state() != QCamera::LoadedState && + m_session->state() != QCamera::ActiveState) { + return; + } + + NSMutableDictionary *videoSettings = [NSMutableDictionary dictionaryWithCapacity:1]; +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + AVCaptureDeviceFormat *match = findBestFormatMatch(m_settings); + if (match) { + if (match != m_captureDevice.activeFormat) { + const AVFConfigurationLock lock(m_captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + + m_captureDevice.activeFormat = match; + } + } else { + qDebugCamera() << Q_FUNC_INFO << "matching device format not found"; + // We still can update the pixel format at least. + } +#endif + + unsigned avfPixelFormat = 0; + if (m_settings.pixelFormat() != QVideoFrame::Format_Invalid && + convertPixelFormatIfSupported(m_settings.pixelFormat(), avfPixelFormat)) { + [videoSettings setObject:[NSNumber numberWithUnsignedInt:avfPixelFormat] + forKey:(id)kCVPixelBufferPixelFormatTypeKey]; + } else { + // We have to set the pixel format, otherwise AVFoundation can change it to something we do not support. + if (NSObject *oldFormat = [m_videoOutput.videoSettings objectForKey:(id)kCVPixelBufferPixelFormatTypeKey]) { + [videoSettings setObject:oldFormat forKey:(id)kCVPixelBufferPixelFormatTypeKey]; + } else { + [videoSettings setObject:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA] + forKey:(id)kCVPixelBufferPixelFormatTypeKey]; + } + } + + if (videoSettings.count) + m_videoOutput.videoSettings = videoSettings; + + qt_set_framerate_limits(m_captureDevice, m_videoConnection, m_settings); +} + +QCameraViewfinderSettings AVFCameraViewfinderSettingsControl2::requestedSettings() const +{ + return m_settings; +} + +bool AVFCameraViewfinderSettingsControl2::updateAVFoundationObjects() const +{ + m_session = 0; + m_captureDevice = 0; + m_videoOutput = 0; + m_videoConnection = 0; + + if (!m_service->session()) + return false; + + if (!m_service->session()->videoCaptureDevice()) + return false; + + if (!m_service->videoOutput() || !m_service->videoOutput()->videoDataOutput()) + return false; + + AVCaptureVideoDataOutput *output = m_service->videoOutput()->videoDataOutput(); + AVCaptureConnection *connection = [output connectionWithMediaType:AVMediaTypeVideo]; + if (!connection) + return false; + + m_session = m_service->session(); + m_captureDevice = m_session->videoCaptureDevice(); + m_videoOutput = output; + m_videoConnection = connection; + + return true; +} + +QT_END_NAMESPACE + +#include "moc_avfcameraviewfindersettingscontrol.cpp" diff --git a/src/plugins/avfoundation/camera/camera.pro b/src/plugins/avfoundation/camera/camera.pro index 82084a1..19e3e63 100644 --- a/src/plugins/avfoundation/camera/camera.pro +++ b/src/plugins/avfoundation/camera/camera.pro @@ -39,7 +39,8 @@ HEADERS += \ avfcameradevicecontrol.h \ avfcamerafocuscontrol.h \ avfcameraexposurecontrol.h \ - avfcamerautility.h + avfcamerautility.h \ + avfcameraviewfindersettingscontrol.h OBJECTIVE_SOURCES += \ avfcameraserviceplugin.mm \ @@ -58,7 +59,8 @@ OBJECTIVE_SOURCES += \ avfcamerarenderercontrol.mm \ avfcamerafocuscontrol.mm \ avfcameraexposurecontrol.mm \ - avfcamerautility.mm + avfcamerautility.mm \ + avfcameraviewfindersettingscontrol.mm ios { -- 2.7.4