summaryrefslogtreecommitdiffstats
path: root/src/multimedia/platform/avfoundation
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia/platform/avfoundation')
-rw-r--r--src/multimedia/platform/avfoundation/avfoundation.pri18
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol.mm226
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol_p.h86
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol.mm119
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol_p.h94
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameracontrol.mm530
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameracontrol_p.h126
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameradebug_p.h68
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol.mm142
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol_p.h98
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol.mm831
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol_p.h112
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol.mm422
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol_p.h118
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol.mm83
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol_p.h81
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol.mm409
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol_p.h119
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameraservice.mm223
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameraservice_p.h126
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin.mm103
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin_p.h79
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerasession.mm491
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerasession_p.h155
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerautility.mm575
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerautility_p.h190
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol.mm262
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol_p.h129
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol.mm278
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol_p.h116
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfimageencodercontrol.mm239
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfimageencodercontrol_p.h96
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfmediaassetwriter.mm514
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfmediaassetwriter_p.h86
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol.mm113
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol_p.h80
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol.mm430
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios.mm414
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios_p.h126
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_p.h131
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol.mm61
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol_p.h71
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfstoragelocation.mm136
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfstoragelocation_p.h82
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol.mm385
-rw-r--r--src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol_p.h99
-rw-r--r--src/multimedia/platform/avfoundation/camera/camera.pri60
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink.mm241
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink_p.h101
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol.mm192
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol_p.h110
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol.mm161
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol_p.h86
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice.mm122
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice_p.h81
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin.mm99
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin_p.h84
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession.mm1067
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession_p.h172
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer.mm464
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios.mm307
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios_p.h131
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_p.h137
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput.mm42
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput_p.h71
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol.mm301
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol_p.h103
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol.mm255
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol_p.h129
-rw-r--r--src/multimedia/platform/avfoundation/mediaplayer/mediaplayer.pri48
70 files changed, 14036 insertions, 0 deletions
diff --git a/src/multimedia/platform/avfoundation/avfoundation.pri b/src/multimedia/platform/avfoundation/avfoundation.pri
new file mode 100644
index 000000000..29be7ec58
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/avfoundation.pri
@@ -0,0 +1,18 @@
+LIBS += -framework CoreFoundation \
+ -framework Foundation \
+ -framework AudioToolbox \
+ -framework CoreAudio \
+ -framework QuartzCore \
+ -framework CoreMedia \
+ -framework CoreVideo \
+ -framework QuartzCore \
+ -framework Metal
+osx:LIBS += -framework AppKit \
+ -framework AudioUnit
+ios:LIBS += -framework CoreGraphics \
+ -framework CoreVideo
+
+QMAKE_USE += avfoundation
+
+include(mediaplayer/mediaplayer.pri)
+!tvos:include(camera/camera.pri)
diff --git a/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol.mm b/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol.mm
new file mode 100644
index 000000000..b613ca32a
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol.mm
@@ -0,0 +1,226 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfaudioencodersettingscontrol_p.h"
+
+#include "avfcameraservice_p.h"
+#include "avfcamerasession_p.h"
+
+#include <AVFoundation/AVFoundation.h>
+#include <CoreAudio/CoreAudioTypes.h>
+
+QT_BEGIN_NAMESPACE
+
+struct AudioCodecInfo
+{
+ QString description;
+ int id;
+
+ AudioCodecInfo() : id(0) { }
+ AudioCodecInfo(const QString &desc, int i)
+ : description(desc), id(i)
+ { }
+};
+
+typedef QMap<QString, AudioCodecInfo> 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<int> AVFAudioEncoderSettingsControl::supportedSampleRates(const QAudioEncoderSettings &settings, bool *continuous) const
+{
+ Q_UNUSED(settings);
+
+ if (continuous)
+ *continuous = true;
+
+ return QList<int>() << 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/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol_p.h
new file mode 100644
index 000000000..b1851a5bf
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol_p.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFAUDIOENCODERSETTINGSCONTROL_H
+#define AVFAUDIOENCODERSETTINGSCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qaudioencodersettingscontrol.h>
+
+@class NSDictionary;
+@class AVCaptureAudioDataOutput;
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraService;
+
+class AVFAudioEncoderSettingsControl : public QAudioEncoderSettingsControl
+{
+public:
+ explicit AVFAudioEncoderSettingsControl(AVFCameraService *service);
+
+ QStringList supportedAudioCodecs() const override;
+ QString codecDescription(const QString &codecName) const override;
+ QList<int> supportedSampleRates(const QAudioEncoderSettings &settings, bool *continuous = nullptr) const override;
+ QAudioEncoderSettings audioSettings() const override;
+ void setAudioSettings(const QAudioEncoderSettings &settings) 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/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol.mm b/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol.mm
new file mode 100644
index 000000000..21ff70917
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol.mm
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcameradebug_p.h"
+#include "avfaudioinputselectorcontrol_p.h"
+#include "avfcameraservice_p.h"
+
+#import <AVFoundation/AVFoundation.h>
+
+QT_USE_NAMESPACE
+
+AVFAudioInputSelectorControl::AVFAudioInputSelectorControl(AVFCameraService *service, QObject *parent)
+ : QAudioInputSelectorControl(parent)
+ , m_dirty(true)
+{
+ Q_UNUSED(service);
+ NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
+ for (AVCaptureDevice *device in videoDevices) {
+ QString deviceId = QString::fromUtf8([[device uniqueID] UTF8String]);
+ m_devices << deviceId;
+ m_deviceDescriptions.insert(deviceId,
+ QString::fromUtf8([[device localizedName] UTF8String]));
+ }
+
+ AVCaptureDevice *defaultDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
+ if (defaultDevice) {
+ m_defaultDevice = QString::fromUtf8([defaultDevice.uniqueID UTF8String]);
+ m_activeInput = m_defaultDevice;
+ }
+}
+
+AVFAudioInputSelectorControl::~AVFAudioInputSelectorControl()
+{
+}
+
+QList<QString> AVFAudioInputSelectorControl::availableInputs() const
+{
+ return m_devices;
+}
+
+QString AVFAudioInputSelectorControl::inputDescription(const QString &name) const
+{
+ return m_deviceDescriptions.value(name);
+}
+
+QString AVFAudioInputSelectorControl::defaultInput() const
+{
+ return m_defaultDevice;
+}
+
+QString AVFAudioInputSelectorControl::activeInput() const
+{
+ return m_activeInput;
+}
+
+void AVFAudioInputSelectorControl::setActiveInput(const QString &name)
+{
+ if (name != m_activeInput) {
+ m_activeInput = name;
+ m_dirty = true;
+
+ Q_EMIT activeInputChanged(m_activeInput);
+ }
+}
+
+AVCaptureDevice *AVFAudioInputSelectorControl::createCaptureDevice()
+{
+ m_dirty = false;
+ AVCaptureDevice *device = nullptr;
+
+ if (!m_activeInput.isEmpty()) {
+ device = [AVCaptureDevice deviceWithUniqueID:
+ [NSString stringWithUTF8String:
+ m_activeInput.toUtf8().constData()]];
+ }
+
+ if (!device)
+ device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
+
+ return device;
+}
+
+#include "moc_avfaudioinputselectorcontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol_p.h
new file mode 100644
index 000000000..90a9bc3fc
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol_p.h
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFAUDIOINPUTSELECTORCONTROL_H
+#define AVFAUDIOINPUTSELECTORCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/qaudioinputselectorcontrol.h>
+#include <QtCore/qstringlist.h>
+
+#import <AVFoundation/AVFoundation.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraSession;
+class AVFCameraService;
+
+class AVFAudioInputSelectorControl : public QAudioInputSelectorControl
+{
+Q_OBJECT
+public:
+ AVFAudioInputSelectorControl(AVFCameraService *service, QObject *parent = nullptr);
+ ~AVFAudioInputSelectorControl();
+
+ QList<QString> availableInputs() const override;
+ QString inputDescription(const QString &name) const override;
+ QString defaultInput() const override;
+ QString activeInput() const override;
+
+public Q_SLOTS:
+ void setActiveInput(const QString &name) override;
+
+public:
+ //device changed since the last createCaptureDevice()
+ bool isDirty() const { return m_dirty; }
+ AVCaptureDevice *createCaptureDevice();
+
+private:
+ QString m_activeInput;
+ bool m_dirty;
+ QString m_defaultDevice;
+ QStringList m_devices;
+ QMap<QString, QString> m_deviceDescriptions;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameracontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcameracontrol.mm
new file mode 100644
index 000000000..27c28587f
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameracontrol.mm
@@ -0,0 +1,530 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcameradebug_p.h"
+#include "avfcameracontrol_p.h"
+#include "avfcamerasession_p.h"
+#include "avfcameraservice_p.h"
+#include "avfcamerautility_p.h"
+#include "avfcamerarenderercontrol_p.h"
+#include "qabstractvideosurface.h"
+
+QT_USE_NAMESPACE
+
+AVFCameraControl::AVFCameraControl(AVFCameraService *service, QObject *parent)
+ : QCameraControl(parent)
+ , m_session(service->session())
+ , m_service(service)
+ , m_state(QCamera::UnloadedState)
+ , m_lastStatus(QCamera::UnloadedStatus)
+ , m_captureMode(QCamera::CaptureStillImage)
+{
+ Q_UNUSED(service);
+ connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(updateStatus()));
+ connect(this, &AVFCameraControl::captureModeChanged, m_session, &AVFCameraSession::onCaptureModeChanged);
+}
+
+AVFCameraControl::~AVFCameraControl()
+{
+}
+
+QCamera::State AVFCameraControl::state() const
+{
+ return m_state;
+}
+
+void AVFCameraControl::setState(QCamera::State state)
+{
+ if (m_state == state)
+ return;
+ m_state = state;
+ m_session->setState(state);
+
+ Q_EMIT stateChanged(m_state);
+ updateStatus();
+}
+
+QCamera::Status AVFCameraControl::status() const
+{
+ static QCamera::Status statusTable[3][3] = {
+ { QCamera::UnloadedStatus, QCamera::UnloadingStatus, QCamera::StoppingStatus }, //Unloaded state
+ { QCamera::LoadingStatus, QCamera::LoadedStatus, QCamera::StoppingStatus }, //Loaded state
+ { QCamera::LoadingStatus, QCamera::StartingStatus, QCamera::ActiveStatus } //ActiveState
+ };
+
+ return statusTable[m_state][m_session->state()];
+}
+
+void AVFCameraControl::updateStatus()
+{
+ QCamera::Status newStatus = status();
+
+ if (m_lastStatus != newStatus) {
+ qDebugCamera() << "Camera status changed: " << m_lastStatus << " -> " << newStatus;
+ m_lastStatus = newStatus;
+ Q_EMIT statusChanged(m_lastStatus);
+ }
+}
+
+QCamera::CaptureModes AVFCameraControl::captureMode() const
+{
+ return m_captureMode;
+}
+
+void AVFCameraControl::setCaptureMode(QCamera::CaptureModes mode)
+{
+ if (m_captureMode == mode)
+ return;
+
+ m_captureMode = mode;
+ Q_EMIT captureModeChanged(mode);
+}
+
+bool AVFCameraControl::isCaptureModeSupported(QCamera::CaptureModes mode) const
+{
+ return true;
+}
+
+bool AVFCameraControl::canChangeProperty(QCameraControl::PropertyChangeType changeType, QCamera::Status status) const
+{
+ Q_UNUSED(changeType);
+ Q_UNUSED(status);
+
+ return true;
+}
+
+QCamera::LockTypes AVFCameraControl::supportedLocks() const
+{
+ return {};
+}
+
+QCamera::LockStatus AVFCameraControl::lockStatus(QCamera::LockType) const
+{
+ return QCamera::Unlocked;
+}
+
+void AVFCameraControl::searchAndLock(QCamera::LockTypes locks)
+{
+ Q_UNUSED(locks);
+}
+
+void AVFCameraControl::unlock(QCamera::LockTypes locks)
+{
+ Q_UNUSED(locks);
+}
+
+
+namespace {
+
+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;
+}
+
+}
+
+QList<QCameraViewfinderSettings> AVFCameraControl::supportedViewfinderSettings() const
+{
+ QList<QCameraViewfinderSettings> supportedSettings;
+
+ AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
+ if (!captureDevice) {
+ qDebugCamera() << Q_FUNC_INFO << "no capture device found";
+ return supportedSettings;
+ }
+
+ QVector<AVFPSRange> framerates;
+
+ QVector<QVideoFrame::PixelFormat> pixelFormats(viewfinderPixelFormats());
+
+ if (!pixelFormats.size())
+ pixelFormats << QVideoFrame::Format_Invalid; // The default value.
+
+ if (!captureDevice.formats || !captureDevice.formats.count) {
+ qDebugCamera() << Q_FUNC_INFO << "no capture device formats found";
+ return supportedSettings;
+ }
+
+ const QVector<AVCaptureDeviceFormat *> formats(qt_unique_device_formats(captureDevice,
+ m_service->session()->defaultCodec()));
+ for (int i = 0; i < formats.size(); ++i) {
+ AVCaptureDeviceFormat *format = formats[i];
+
+ 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;
+ }
+ }
+ }
+
+ return supportedSettings;
+}
+
+QCameraViewfinderSettings AVFCameraControl::viewfinderSettings() const
+{
+ QCameraViewfinderSettings settings = m_settings;
+
+ AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
+ if (!captureDevice) {
+ qDebugCamera() << Q_FUNC_INFO << "no capture device found";
+ return settings;
+ }
+
+ if (m_service->session()->state() != QCamera::LoadedState &&
+ m_service->session()->state() != QCamera::ActiveState) {
+ return settings;
+ }
+
+ if (!captureDevice.activeFormat) {
+ qDebugCamera() << Q_FUNC_INFO << "no active capture device format";
+ return settings;
+ }
+
+ const QSize res(qt_device_format_resolution(captureDevice.activeFormat));
+ const QSize par(qt_device_format_pixel_aspect_ratio(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);
+
+ const AVFPSRange fps = qt_current_framerates(captureDevice, videoConnection());
+ settings.setMinimumFrameRate(fps.first);
+ settings.setMaximumFrameRate(fps.second);
+
+ AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput() ? m_service->videoOutput()->videoDataOutput() : nullptr;
+ if (videoOutput) {
+ NSObject *obj = [videoOutput.videoSettings objectForKey:(id)kCVPixelBufferPixelFormatTypeKey];
+ if (obj && [obj isKindOfClass:[NSNumber class]]) {
+ NSNumber *nsNum = static_cast<NSNumber *>(obj);
+ settings.setPixelFormat(QtPixelFormatFromCVFormat([nsNum unsignedIntValue]));
+ }
+ }
+
+ return settings;
+}
+
+void AVFCameraControl::setViewfinderSettings(const QCameraViewfinderSettings &settings)
+{
+ if (m_settings == settings)
+ return;
+
+ m_settings = settings;
+#if defined(Q_OS_IOS)
+ bool active = m_service->session()->state() == QCamera::ActiveState;
+ if (active)
+ [m_service->session()->captureSession() beginConfiguration];
+ applySettings(m_settings);
+ if (active)
+ [m_service->session()->captureSession() commitConfiguration];
+#else
+ applySettings(m_settings);
+#endif
+}
+
+QVideoFrame::PixelFormat AVFCameraControl::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;
+ case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
+ case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
+ return QVideoFrame::Format_NV12;
+ case kCVPixelFormatType_422YpCbCr8:
+ return QVideoFrame::Format_UYVY;
+ case kCVPixelFormatType_422YpCbCr8_yuvs:
+ return QVideoFrame::Format_YUYV;
+ default:
+ return QVideoFrame::Format_Invalid;
+ }
+}
+
+bool AVFCameraControl::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;
+ case QVideoFrame::Format_NV12:
+ conv = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
+ break;
+ case QVideoFrame::Format_UYVY:
+ conv = kCVPixelFormatType_422YpCbCr8;
+ break;
+ case QVideoFrame::Format_YUYV:
+ conv = kCVPixelFormatType_422YpCbCr8_yuvs;
+ 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 *AVFCameraControl::findBestFormatMatch(const QCameraViewfinderSettings &settings) const
+{
+ AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
+ if (!captureDevice || settings.isNull())
+ return nil;
+
+ 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(captureDevice, resolution,
+ m_service->session()->defaultCodec(), false);
+ }
+
+ // 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(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.
+
+ return nil;
+}
+
+QVector<QVideoFrame::PixelFormat> AVFCameraControl::viewfinderPixelFormats() const
+{
+ QVector<QVideoFrame::PixelFormat> qtFormats;
+
+ AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput() ? m_service->videoOutput()->videoDataOutput() : nullptr;
+ if (!videoOutput) {
+ qDebugCamera() << Q_FUNC_INFO << "no video output found";
+ return qtFormats;
+ }
+
+ NSArray *pixelFormats = [videoOutput availableVideoCVPixelFormatTypes];
+
+ for (NSObject *obj in pixelFormats) {
+ if (![obj isKindOfClass:[NSNumber class]])
+ continue;
+
+ NSNumber *formatAsNSNumber = static_cast<NSNumber *>(obj);
+ // It's actually FourCharCode (== UInt32):
+ const QVideoFrame::PixelFormat qtFormat(QtPixelFormatFromCVFormat([formatAsNSNumber unsignedIntValue]));
+ if (qtFormat != QVideoFrame::Format_Invalid
+ && !qtFormats.contains(qtFormat)) { // Can happen, for example, with 8BiPlanar existing in video/full range.
+ qtFormats << qtFormat;
+ }
+ }
+
+ return qtFormats;
+}
+
+bool AVFCameraControl::convertPixelFormatIfSupported(QVideoFrame::PixelFormat qtFormat,
+ unsigned &avfFormat)const
+{
+ AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput() ? m_service->videoOutput()->videoDataOutput() : nullptr;
+ if (!videoOutput)
+ return false;
+
+ unsigned conv = 0;
+ if (!CVPixelFormatFromQtFormat(qtFormat, conv))
+ return false;
+
+ NSArray *formats = [videoOutput availableVideoCVPixelFormatTypes];
+ if (!formats || !formats.count)
+ return false;
+
+ if (m_service->videoOutput()->surface()) {
+ const QAbstractVideoSurface *surface = m_service->videoOutput()->surface();
+ QAbstractVideoBuffer::HandleType h = m_service->videoOutput()->supportsTextures()
+ ? QAbstractVideoBuffer::GLTextureHandle
+ : QAbstractVideoBuffer::NoHandle;
+ if (!surface->supportedPixelFormats(h).contains(qtFormat))
+ return false;
+ }
+
+ bool found = false;
+ for (NSObject *obj in formats) {
+ if (![obj isKindOfClass:[NSNumber class]])
+ continue;
+
+ NSNumber *nsNum = static_cast<NSNumber *>(obj);
+ if ([nsNum unsignedIntValue] == conv) {
+ avfFormat = conv;
+ found = true;
+ }
+ }
+
+ return found;
+}
+
+bool AVFCameraControl::applySettings(const QCameraViewfinderSettings &settings)
+{
+ if (m_service->session()->state() != QCamera::LoadedState &&
+ m_service->session()->state() != QCamera::ActiveState) {
+ return false;
+ }
+
+ AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
+ if (!captureDevice)
+ return false;
+
+ bool activeFormatChanged = false;
+
+ AVCaptureDeviceFormat *match = findBestFormatMatch(settings);
+ if (match) {
+ 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.
+ }
+
+ AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput() ? m_service->videoOutput()->videoDataOutput() : nullptr;
+ if (videoOutput) {
+ unsigned avfPixelFormat = 0;
+ 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
+
+ const QVector<QVideoFrame::PixelFormat> deviceFormats = viewfinderPixelFormats();
+ QAbstractVideoSurface *surface = m_service->videoOutput()->surface();
+ QVideoFrame::PixelFormat pickedFormat = deviceFormats.first();
+ if (surface) {
+ pickedFormat = QVideoFrame::Format_Invalid;
+ QAbstractVideoBuffer::HandleType h = m_service->videoOutput()->supportsTextures()
+ ? QAbstractVideoBuffer::GLTextureHandle
+ : QAbstractVideoBuffer::NoHandle;
+ QList<QVideoFrame::PixelFormat> surfaceFormats = surface->supportedPixelFormats(h);
+ for (int i = 0; i < surfaceFormats.count(); ++i) {
+ const QVideoFrame::PixelFormat surfaceFormat = surfaceFormats.at(i);
+ if (deviceFormats.contains(surfaceFormat)) {
+ pickedFormat = surfaceFormat;
+ break;
+ }
+ }
+ }
+
+ CVPixelFormatFromQtFormat(pickedFormat, avfPixelFormat);
+ }
+
+ NSMutableDictionary *videoSettings = [NSMutableDictionary dictionaryWithCapacity:1];
+ [videoSettings setObject:[NSNumber numberWithUnsignedInt:avfPixelFormat]
+ forKey:(id)kCVPixelBufferPixelFormatTypeKey];
+
+ const AVFConfigurationLock lock(captureDevice);
+ if (!lock)
+ qWarning("Failed to set active format (lock failed)");
+
+ videoOutput.videoSettings = videoSettings;
+ }
+
+ qt_set_framerate_limits(captureDevice, videoConnection(), settings.minimumFrameRate(), settings.maximumFrameRate());
+
+ return activeFormatChanged;
+}
+
+QCameraViewfinderSettings AVFCameraControl::requestedSettings() const
+{
+ return m_settings;
+}
+
+AVCaptureConnection *AVFCameraControl::videoConnection() const
+{
+ if (!m_service->videoOutput() || !m_service->videoOutput()->videoDataOutput())
+ return nil;
+
+ return [m_service->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo];
+}
+
+#include "moc_avfcameracontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameracontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcameracontrol_p.h
new file mode 100644
index 000000000..2cb464fe5
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameracontrol_p.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFCAMERACONTROL_H
+#define AVFCAMERACONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qobject.h>
+
+#include <QtMultimedia/qcameracontrol.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraSession;
+class AVFCameraService;
+@class AVCaptureDeviceFormat;
+@class AVCaptureConnection;
+
+class AVFCameraControl : public QCameraControl
+{
+Q_OBJECT
+public:
+ AVFCameraControl(AVFCameraService *service, QObject *parent = nullptr);
+ ~AVFCameraControl();
+
+ QCamera::State state() const override;
+ void setState(QCamera::State state) override;
+
+ QCamera::Status status() const override;
+
+ QCamera::CaptureModes captureMode() const override;
+ void setCaptureMode(QCamera::CaptureModes) override;
+ bool isCaptureModeSupported(QCamera::CaptureModes mode) const override;
+
+ bool canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const override;
+
+ QCamera::LockTypes supportedLocks() const override;
+
+ QCamera::LockStatus lockStatus(QCamera::LockType lock) const override;
+
+ void searchAndLock(QCamera::LockTypes locks) override;
+ void unlock(QCamera::LockTypes locks) override;
+
+ QList<QCameraViewfinderSettings> supportedViewfinderSettings() const override;
+ QCameraViewfinderSettings viewfinderSettings() const override;
+ void setViewfinderSettings(const QCameraViewfinderSettings &settings) 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;
+ QList<QVideoFrame::PixelFormat> viewfinderPixelFormats() const;
+ bool convertPixelFormatIfSupported(QVideoFrame::PixelFormat format, unsigned &avfFormat) const;
+ bool applySettings(const QCameraViewfinderSettings &settings);
+ QCameraViewfinderSettings requestedSettings() const;
+
+ AVCaptureConnection *videoConnection() const;
+
+private Q_SLOTS:
+ void updateStatus();
+
+private:
+ friend class AVFCameraSession;
+ AVFCameraSession *m_session;
+ AVFCameraService *m_service;
+ QCameraViewfinderSettings m_settings;
+
+ QCamera::State m_state;
+ QCamera::Status m_lastStatus;
+ QCamera::CaptureModes m_captureMode;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameradebug_p.h b/src/multimedia/platform/avfoundation/camera/avfcameradebug_p.h
new file mode 100644
index 000000000..616e53d99
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameradebug_p.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFDEBUG_H
+#define AVFDEBUG_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qtmultimediaglobal.h"
+
+#include <QtCore/qdebug.h>
+
+QT_USE_NAMESPACE
+
+//#define AVF_DEBUG_CAMERA
+
+#ifdef AVF_DEBUG_CAMERA
+#define qDebugCamera qDebug
+#else
+#define qDebugCamera QT_NO_QDEBUG_MACRO
+#endif
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol.mm
new file mode 100644
index 000000000..ac5711fb1
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol.mm
@@ -0,0 +1,142 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcameradebug_p.h"
+#include "avfcameradevicecontrol_p.h"
+#include "avfcameraservice_p.h"
+#include "avfcamerasession_p.h"
+
+QT_USE_NAMESPACE
+
+AVFCameraDeviceControl::AVFCameraDeviceControl(AVFCameraService *service, QObject *parent)
+ : QVideoDeviceSelectorControl(parent)
+ , m_service(service)
+ , m_selectedDevice(0)
+ , m_dirty(true)
+{
+ Q_UNUSED(m_service);
+}
+
+AVFCameraDeviceControl::~AVFCameraDeviceControl()
+{
+}
+
+int AVFCameraDeviceControl::deviceCount() const
+{
+ return AVFCameraSession::availableCameraDevices().count();
+}
+
+QString AVFCameraDeviceControl::deviceName(int index) const
+{
+ const QList<AVFCameraInfo> &devices = AVFCameraSession::availableCameraDevices();
+ if (index < 0 || index >= devices.count())
+ return QString();
+
+ return QString::fromUtf8(devices.at(index).deviceId);
+}
+
+QString AVFCameraDeviceControl::deviceDescription(int index) const
+{
+ const QList<AVFCameraInfo> &devices = AVFCameraSession::availableCameraDevices();
+ if (index < 0 || index >= devices.count())
+ return QString();
+
+ return devices.at(index).description;
+}
+
+QCamera::Position AVFCameraDeviceControl::cameraPosition(int index) const
+{
+ const QList<AVFCameraInfo> &devices = AVFCameraSession::availableCameraDevices();
+ if (index < 0 || index >= devices.count())
+ return QCamera::UnspecifiedPosition;
+
+ return devices.at(index).position;
+}
+
+int AVFCameraDeviceControl::cameraOrientation(int index) const
+{
+ const QList<AVFCameraInfo> &devices = AVFCameraSession::availableCameraDevices();
+ if (index < 0 || index >= devices.count())
+ return 0;
+
+ return devices.at(index).orientation;
+}
+
+
+int AVFCameraDeviceControl::defaultDevice() const
+{
+ return AVFCameraSession::defaultCameraIndex();
+}
+
+int AVFCameraDeviceControl::selectedDevice() const
+{
+ return m_selectedDevice;
+}
+
+void AVFCameraDeviceControl::setSelectedDevice(int index)
+{
+ if (index >= 0 &&
+ index < deviceCount() &&
+ index != m_selectedDevice) {
+ m_dirty = true;
+ m_selectedDevice = index;
+ Q_EMIT selectedDeviceChanged(index);
+ Q_EMIT selectedDeviceChanged(deviceName(index));
+ }
+}
+
+AVCaptureDevice *AVFCameraDeviceControl::createCaptureDevice()
+{
+ m_dirty = false;
+ AVCaptureDevice *device = nullptr;
+
+ QString deviceId = deviceName(m_selectedDevice);
+ if (!deviceId.isEmpty()) {
+ device = [AVCaptureDevice deviceWithUniqueID:
+ [NSString stringWithUTF8String:
+ deviceId.toUtf8().constData()]];
+ }
+
+ if (!device)
+ device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
+
+ return device;
+}
+
+#include "moc_avfcameradevicecontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol_p.h
new file mode 100644
index 000000000..0fb8628b2
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol_p.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFCAMERADEVICECONTROL_H
+#define AVFCAMERADEVICECONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/qvideodeviceselectorcontrol.h>
+#include <QtCore/qstringlist.h>
+
+#import <AVFoundation/AVFoundation.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraSession;
+class AVFCameraService;
+
+class AVFCameraDeviceControl : public QVideoDeviceSelectorControl
+{
+Q_OBJECT
+public:
+ AVFCameraDeviceControl(AVFCameraService *service, QObject *parent = nullptr);
+ ~AVFCameraDeviceControl();
+
+ int deviceCount() const override;
+
+ QString deviceName(int index) const override;
+ QString deviceDescription(int index) const override;
+ QCamera::Position cameraPosition(int index) const override;
+ int cameraOrientation(int index) const override;
+
+ int defaultDevice() const override;
+ int selectedDevice() const override;
+
+public Q_SLOTS:
+ void setSelectedDevice(int index) override;
+
+public:
+ //device changed since the last createCaptureDevice()
+ bool isDirty() const { return m_dirty; }
+ AVCaptureDevice *createCaptureDevice();
+
+private:
+ AVFCameraService *m_service;
+
+ int m_selectedDevice;
+ bool m_dirty;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol.mm
new file mode 100644
index 000000000..a0b2ae06d
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol.mm
@@ -0,0 +1,831 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcameraexposurecontrol_p.h"
+#include "avfcamerautility_p.h"
+#include "avfcamerasession_p.h"
+#include "avfcameraservice_p.h"
+#include "avfcameradebug_p.h"
+
+#include <QtCore/qvariant.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qpair.h>
+
+#include <AVFoundation/AVFoundation.h>
+
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+// All these methods to work with exposure/ISO/SS in custom mode do not support macOS.
+
+#ifdef Q_OS_IOS
+
+// Misc. helpers to check values/ranges:
+
+bool qt_check_ISO_conversion(float isoValue)
+{
+ if (isoValue >= std::numeric_limits<int>::max())
+ return false;
+ if (isoValue <= std::numeric_limits<int>::min())
+ return false;
+ return true;
+}
+
+bool qt_check_ISO_range(AVCaptureDeviceFormat *format)
+{
+ // Qt is using int for ISO, AVFoundation - float. It looks like the ISO range
+ // at the moment can be represented by int (it's max - min > 100, etc.).
+ Q_ASSERT(format);
+ if (format.maxISO - format.minISO < 1.) {
+ // ISO is in some strange units?
+ return false;
+ }
+
+ return qt_check_ISO_conversion(format.minISO)
+ && qt_check_ISO_conversion(format.maxISO);
+}
+
+bool qt_check_exposure_duration(AVCaptureDevice *captureDevice, CMTime duration)
+{
+ Q_ASSERT(captureDevice);
+
+ AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
+ if (!activeFormat) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to obtain capture device format";
+ return false;
+ }
+
+ return CMTimeCompare(duration, activeFormat.minExposureDuration) != -1
+ && CMTimeCompare(activeFormat.maxExposureDuration, duration) != -1;
+}
+
+bool qt_check_ISO_value(AVCaptureDevice *captureDevice, int newISO)
+{
+ Q_ASSERT(captureDevice);
+
+ AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
+ if (!activeFormat) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to obtain capture device format";
+ return false;
+ }
+
+ return !(newISO < activeFormat.minISO || newISO > activeFormat.maxISO);
+}
+
+bool qt_exposure_duration_equal(AVCaptureDevice *captureDevice, qreal qDuration)
+{
+ Q_ASSERT(captureDevice);
+ const CMTime avDuration = CMTimeMakeWithSeconds(qDuration, captureDevice.exposureDuration.timescale);
+ return !CMTimeCompare(avDuration, captureDevice.exposureDuration);
+}
+
+bool qt_iso_equal(AVCaptureDevice *captureDevice, int iso)
+{
+ Q_ASSERT(captureDevice);
+ return qFuzzyCompare(float(iso), captureDevice.ISO);
+}
+
+bool qt_exposure_bias_equal(AVCaptureDevice *captureDevice, qreal bias)
+{
+ Q_ASSERT(captureDevice);
+ return qFuzzyCompare(bias, qreal(captureDevice.exposureTargetBias));
+}
+
+// Converters:
+
+bool qt_convert_exposure_mode(AVCaptureDevice *captureDevice, QCameraExposure::ExposureMode mode,
+ AVCaptureExposureMode &avMode)
+{
+ // Test if mode supported and convert.
+ Q_ASSERT(captureDevice);
+
+ if (mode == QCameraExposure::ExposureAuto) {
+ if ([captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
+ avMode = AVCaptureExposureModeContinuousAutoExposure;
+ return true;
+ }
+ }
+
+ if (mode == QCameraExposure::ExposureManual) {
+ if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom]) {
+ avMode = AVCaptureExposureModeCustom;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// We set ISO/exposure duration with completion handlers, completion handlers try
+// to avoid dangling pointers (thus QPointer for QObjects) and not to create
+// a reference loop (in case we have ARC).
+
+void qt_set_exposure_bias(QPointer<AVFCameraService> service, QPointer<AVFCameraExposureControl> control,
+ AVCaptureDevice *captureDevice, float bias)
+{
+ Q_ASSERT(captureDevice);
+
+ __block AVCaptureDevice *device = captureDevice; //For ARC.
+
+ void (^completionHandler)(CMTime syncTime) = ^(CMTime) {
+ // Test that service control is still alive and that
+ // capture device is our device, if yes - emit actual value changed.
+ if (service) {
+ if (control) {
+ if (service->session() && service->session()->videoCaptureDevice() == device)
+ Q_EMIT control->actualValueChanged(int(QCameraExposureControl::ExposureCompensation));
+ }
+ }
+ device = nil;
+ };
+
+ [captureDevice setExposureTargetBias:bias completionHandler:completionHandler];
+}
+
+void qt_set_duration_iso(QPointer<AVFCameraService> service, QPointer<AVFCameraExposureControl> control,
+ AVCaptureDevice *captureDevice, CMTime duration, float iso)
+{
+ Q_ASSERT(captureDevice);
+
+ __block AVCaptureDevice *device = captureDevice; //For ARC.
+ const bool setDuration = CMTimeCompare(duration, AVCaptureExposureDurationCurrent);
+ const bool setISO = !qFuzzyCompare(iso, AVCaptureISOCurrent);
+
+ void (^completionHandler)(CMTime syncTime) = ^(CMTime) {
+ // Test that service control is still alive and that
+ // capture device is our device, if yes - emit actual value changed.
+ if (service) {
+ if (control) {
+ if (service->session() && service->session()->videoCaptureDevice() == device) {
+ if (setDuration)
+ Q_EMIT control->actualValueChanged(int(QCameraExposureControl::ShutterSpeed));
+ if (setISO)
+ Q_EMIT control->actualValueChanged(int(QCameraExposureControl::ISO));
+ }
+ }
+ }
+ device = nil;
+ };
+
+ [captureDevice setExposureModeCustomWithDuration:duration
+ ISO:iso
+ completionHandler:completionHandler];
+}
+
+#endif // defined(Q_OS_IOS)
+
+} // Unnamed namespace.
+
+AVFCameraExposureControl::AVFCameraExposureControl(AVFCameraService *service)
+ : m_service(service),
+ m_session(nullptr)
+{
+ Q_ASSERT(service);
+ m_session = m_service->session();
+ Q_ASSERT(m_session);
+
+ connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(cameraStateChanged(QCamera::State)));
+}
+
+bool AVFCameraExposureControl::isParameterSupported(ExposureParameter parameter) const
+{
+#ifdef Q_OS_IOS
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice)
+ return false;
+
+ // These are the parameters we have an API to support:
+ return parameter == QCameraExposureControl::ISO
+ || parameter == QCameraExposureControl::ShutterSpeed
+ || parameter == QCameraExposureControl::ExposureCompensation
+ || parameter == QCameraExposureControl::ExposureMode;
+#else
+ Q_UNUSED(parameter);
+ return false;
+#endif
+}
+
+QVariantList AVFCameraExposureControl::supportedParameterRange(ExposureParameter parameter,
+ bool *continuous) const
+{
+ QVariantList parameterRange;
+#ifdef Q_OS_IOS
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice || !isParameterSupported(parameter)) {
+ qDebugCamera() << Q_FUNC_INFO << "parameter not supported";
+ return parameterRange;
+ }
+
+ if (continuous)
+ *continuous = false;
+
+ AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
+
+ if (parameter == QCameraExposureControl::ISO) {
+ if (!activeFormat) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to obtain capture device format";
+ return parameterRange;
+ }
+
+ if (!qt_check_ISO_range(activeFormat)) {
+ qDebugCamera() << Q_FUNC_INFO << "ISO range can not be represented as int";
+ return parameterRange;
+ }
+
+ parameterRange << QVariant(int(activeFormat.minISO));
+ parameterRange << QVariant(int(activeFormat.maxISO));
+ if (continuous)
+ *continuous = true;
+ } else if (parameter == QCameraExposureControl::ExposureCompensation) {
+ parameterRange << captureDevice.minExposureTargetBias;
+ parameterRange << captureDevice.maxExposureTargetBias;
+ if (continuous)
+ *continuous = true;
+ } else if (parameter == QCameraExposureControl::ShutterSpeed) {
+ if (!activeFormat) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to obtain capture device format";
+ return parameterRange;
+ }
+
+ // CMTimeGetSeconds returns Float64, test the conversion below, if it's valid?
+ parameterRange << qreal(CMTimeGetSeconds(activeFormat.minExposureDuration));
+ parameterRange << qreal(CMTimeGetSeconds(activeFormat.maxExposureDuration));
+
+ if (continuous)
+ *continuous = true;
+ } else if (parameter == QCameraExposureControl::ExposureMode) {
+ if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom])
+ parameterRange << QVariant::fromValue(QCameraExposure::ExposureManual);
+
+ if ([captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure])
+ parameterRange << QVariant::fromValue(QCameraExposure::ExposureAuto);
+ }
+#else
+ Q_UNUSED(parameter);
+ Q_UNUSED(continuous);
+#endif
+ return parameterRange;
+}
+
+QVariant AVFCameraExposureControl::requestedValue(ExposureParameter parameter) const
+{
+ if (!isParameterSupported(parameter)) {
+ qDebugCamera() << Q_FUNC_INFO << "parameter not supported";
+ return QVariant();
+ }
+
+ if (parameter == QCameraExposureControl::ExposureMode)
+ return m_requestedMode;
+
+ if (parameter == QCameraExposureControl::ExposureCompensation)
+ return m_requestedCompensation;
+
+ if (parameter == QCameraExposureControl::ShutterSpeed)
+ return m_requestedShutterSpeed;
+
+ if (parameter == QCameraExposureControl::ISO)
+ return m_requestedISO;
+
+ return QVariant();
+}
+
+QVariant AVFCameraExposureControl::actualValue(ExposureParameter parameter) const
+{
+#ifdef Q_OS_IOS
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice || !isParameterSupported(parameter)) {
+ // Actually, at the moment !captiredevice => !isParameterSupported.
+ qDebugCamera() << Q_FUNC_INFO << "parameter not supported";
+ return QVariant();
+ }
+
+ if (parameter == QCameraExposureControl::ExposureMode) {
+ // This code expects exposureMode to be continuous by default ...
+ if (captureDevice.exposureMode == AVCaptureExposureModeContinuousAutoExposure)
+ return QVariant::fromValue(QCameraExposure::ExposureAuto);
+ return QVariant::fromValue(QCameraExposure::ExposureManual);
+ }
+
+ if (parameter == QCameraExposureControl::ExposureCompensation)
+ return captureDevice.exposureTargetBias;
+
+ if (parameter == QCameraExposureControl::ShutterSpeed)
+ return qreal(CMTimeGetSeconds(captureDevice.exposureDuration));
+
+ if (parameter == QCameraExposureControl::ISO) {
+ if (captureDevice.activeFormat && qt_check_ISO_range(captureDevice.activeFormat)
+ && qt_check_ISO_conversion(captureDevice.ISO)) {
+ // Can be represented as int ...
+ return int(captureDevice.ISO);
+ } else {
+ qDebugCamera() << Q_FUNC_INFO << "ISO can not be represented as int";
+ return QVariant();
+ }
+ }
+#else
+ Q_UNUSED(parameter);
+#endif
+ return QVariant();
+}
+
+bool AVFCameraExposureControl::setValue(ExposureParameter parameter, const QVariant &value)
+{
+ if (parameter == QCameraExposureControl::ExposureMode)
+ return setExposureMode(value);
+ else if (parameter == QCameraExposureControl::ExposureCompensation)
+ return setExposureCompensation(value);
+ else if (parameter == QCameraExposureControl::ShutterSpeed)
+ return setShutterSpeed(value);
+ else if (parameter == QCameraExposureControl::ISO)
+ return setISO(value);
+
+ return false;
+}
+
+bool AVFCameraExposureControl::setExposureMode(const QVariant &value)
+{
+#ifdef Q_OS_IOS
+ if (!value.canConvert<QCameraExposure::ExposureMode>()) {
+ qDebugCamera() << Q_FUNC_INFO << "invalid exposure mode value,"
+ << "QCameraExposure::ExposureMode expected";
+ return false;
+ }
+
+ const QCameraExposure::ExposureMode qtMode = value.value<QCameraExposure::ExposureMode>();
+ if (qtMode != QCameraExposure::ExposureAuto && qtMode != QCameraExposure::ExposureManual) {
+ qDebugCamera() << Q_FUNC_INFO << "exposure mode not supported";
+ return false;
+ }
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice) {
+ m_requestedMode = value;
+ Q_EMIT requestedValueChanged(int(QCameraExposureControl::ExposureMode));
+ return true;
+ }
+
+ AVCaptureExposureMode avMode = AVCaptureExposureModeAutoExpose;
+ if (!qt_convert_exposure_mode(captureDevice, qtMode, avMode)) {
+ qDebugCamera() << Q_FUNC_INFO << "exposure mode not supported";
+ return false;
+ }
+
+ const AVFConfigurationLock lock(captureDevice);
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock a capture device"
+ << "for configuration";
+ return false;
+ }
+
+ m_requestedMode = value;
+ [captureDevice setExposureMode:avMode];
+ Q_EMIT requestedValueChanged(int(QCameraExposureControl::ExposureMode));
+ Q_EMIT actualValueChanged(int(QCameraExposureControl::ExposureMode));
+
+ return true;
+#else
+ Q_UNUSED(value);
+ return false;
+#endif
+}
+
+bool AVFCameraExposureControl::setExposureCompensation(const QVariant &value)
+{
+#ifdef Q_OS_IOS
+ if (!value.canConvert<qreal>()) {
+ qDebugCamera() << Q_FUNC_INFO << "invalid exposure compensation"
+ <<"value, floating point number expected";
+ return false;
+ }
+
+ const qreal bias = value.toReal();
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice) {
+ m_requestedCompensation = value;
+ Q_EMIT requestedValueChanged(int(QCameraExposureControl::ExposureCompensation));
+ return true;
+ }
+
+ if (bias < captureDevice.minExposureTargetBias || bias > captureDevice.maxExposureTargetBias) {
+ // TODO: mixed fp types!
+ qDebugCamera() << Q_FUNC_INFO << "exposure compenstation value is"
+ << "out of range";
+ return false;
+ }
+
+ const AVFConfigurationLock lock(captureDevice);
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration";
+ return false;
+ }
+
+ qt_set_exposure_bias(m_service, this, captureDevice, bias);
+ m_requestedCompensation = value;
+ Q_EMIT requestedValueChanged(int(QCameraExposureControl::ExposureCompensation));
+
+ return true;
+#else
+ Q_UNUSED(value);
+ return false;
+#endif
+}
+
+bool AVFCameraExposureControl::setShutterSpeed(const QVariant &value)
+{
+#ifdef Q_OS_IOS
+ if (value.isNull())
+ return setExposureMode(QVariant::fromValue(QCameraExposure::ExposureAuto));
+
+ if (!value.canConvert<qreal>()) {
+ qDebugCamera() << Q_FUNC_INFO << "invalid shutter speed"
+ << "value, floating point number expected";
+ return false;
+ }
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice) {
+ m_requestedShutterSpeed = value;
+ Q_EMIT requestedValueChanged(int(QCameraExposureControl::ShutterSpeed));
+ return true;
+ }
+
+ const CMTime newDuration = CMTimeMakeWithSeconds(value.toReal(),
+ captureDevice.exposureDuration.timescale);
+ if (!qt_check_exposure_duration(captureDevice, newDuration)) {
+ qDebugCamera() << Q_FUNC_INFO << "shutter speed value is out of range";
+ return false;
+ }
+
+ const AVFConfigurationLock lock(captureDevice);
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration";
+ return false;
+ }
+
+ // Setting the shutter speed (exposure duration in Apple's terms,
+ // since there is no shutter actually) will also reset
+ // exposure mode into custom mode.
+ qt_set_duration_iso(m_service, this, captureDevice, newDuration, AVCaptureISOCurrent);
+
+ m_requestedShutterSpeed = value;
+ Q_EMIT requestedValueChanged(int(QCameraExposureControl::ShutterSpeed));
+
+ return true;
+#else
+ Q_UNUSED(value);
+ return false;
+#endif
+}
+
+bool AVFCameraExposureControl::setISO(const QVariant &value)
+{
+#ifdef Q_OS_IOS
+ if (value.isNull())
+ return setExposureMode(QVariant::fromValue(QCameraExposure::ExposureAuto));
+
+ if (!value.canConvert<int>()) {
+ qDebugCamera() << Q_FUNC_INFO << "invalid ISO value, int expected";
+ return false;
+ }
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice) {
+ m_requestedISO = value;
+ Q_EMIT requestedValueChanged(int(QCameraExposureControl::ISO));
+ return true;
+ }
+
+ if (!qt_check_ISO_value(captureDevice, value.toInt())) {
+ qDebugCamera() << Q_FUNC_INFO << "ISO value is out of range";
+ return false;
+ }
+
+ const AVFConfigurationLock lock(captureDevice);
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock a capture device"
+ << "for configuration";
+ return false;
+ }
+
+ // Setting the ISO will also reset
+ // exposure mode to the custom mode.
+ qt_set_duration_iso(m_service, this, captureDevice, AVCaptureExposureDurationCurrent, value.toInt());
+
+ m_requestedISO = value;
+ Q_EMIT requestedValueChanged(int(QCameraExposureControl::ISO));
+
+ return true;
+#else
+ Q_UNUSED(value);
+ return false;
+#endif
+}
+
+void AVFCameraExposureControl::cameraStateChanged(QCamera::State newState)
+{
+#ifdef Q_OS_IOS
+ if (m_session->state() != QCamera::ActiveState)
+ return;
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice) {
+ qDebugCamera() << Q_FUNC_INFO << "capture device is nil, but the session"
+ << "state is 'active'";
+ return;
+ }
+
+ Q_EMIT parameterRangeChanged(int(QCameraExposureControl::ExposureCompensation));
+ Q_EMIT parameterRangeChanged(int(QCameraExposureControl::ExposureMode));
+ Q_EMIT parameterRangeChanged(int(QCameraExposureControl::ShutterSpeed));
+ Q_EMIT parameterRangeChanged(int(QCameraExposureControl::ISO));
+
+ const AVFConfigurationLock lock(captureDevice);
+
+ CMTime newDuration = AVCaptureExposureDurationCurrent;
+ bool setCustomMode = false;
+
+ if (!m_requestedShutterSpeed.isNull()
+ && !qt_exposure_duration_equal(captureDevice, m_requestedShutterSpeed.toReal())) {
+ newDuration = CMTimeMakeWithSeconds(m_requestedShutterSpeed.toReal(),
+ captureDevice.exposureDuration.timescale);
+ if (!qt_check_exposure_duration(captureDevice, newDuration)) {
+ qDebugCamera() << Q_FUNC_INFO << "requested exposure duration is out of range";
+ return;
+ }
+ setCustomMode = true;
+ }
+
+ float newISO = AVCaptureISOCurrent;
+ if (!m_requestedISO.isNull() && !qt_iso_equal(captureDevice, m_requestedISO.toInt())) {
+ newISO = m_requestedISO.toInt();
+ if (!qt_check_ISO_value(captureDevice, newISO)) {
+ qDebugCamera() << Q_FUNC_INFO << "requested ISO value is out of range";
+ return;
+ }
+ setCustomMode = true;
+ }
+
+ if (!m_requestedCompensation.isNull()
+ && !qt_exposure_bias_equal(captureDevice, m_requestedCompensation.toReal())) {
+ // TODO: mixed fpns.
+ const qreal bias = m_requestedCompensation.toReal();
+ if (bias < captureDevice.minExposureTargetBias || bias > captureDevice.maxExposureTargetBias) {
+ qDebugCamera() << Q_FUNC_INFO << "exposure compenstation value is"
+ << "out of range";
+ return;
+ }
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration";
+ return;
+ }
+ qt_set_exposure_bias(m_service, this, captureDevice, bias);
+ }
+
+ // Setting shutter speed (exposure duration) or ISO values
+ // also reset exposure mode into Custom. With this settings
+ // we ignore any attempts to set exposure mode.
+
+ if (setCustomMode) {
+ if (!lock)
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration";
+ else
+ qt_set_duration_iso(m_service, this, captureDevice, newDuration, newISO);
+ return;
+ }
+
+ if (!m_requestedMode.isNull()) {
+ QCameraExposure::ExposureMode qtMode = m_requestedMode.value<QCameraExposure::ExposureMode>();
+ AVCaptureExposureMode avMode = AVCaptureExposureModeContinuousAutoExposure;
+ if (!qt_convert_exposure_mode(captureDevice, qtMode, avMode)) {
+ qDebugCamera() << Q_FUNC_INFO << "requested exposure mode is not supported";
+ return;
+ }
+
+ if (avMode == captureDevice.exposureMode)
+ return;
+
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration";
+ return;
+ }
+
+ [captureDevice setExposureMode:avMode];
+ Q_EMIT actualValueChanged(int(QCameraExposureControl::ExposureMode));
+ }
+#endif
+
+ if (newState == QCamera::UnloadedState) {
+ m_supportedModes = QCameraExposure::FlashOff;
+ Q_EMIT flashReady(false);
+ } else if (newState == QCamera::ActiveState) {
+ m_supportedModes = QCameraExposure::FlashOff;
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice) {
+ qDebugCamera() << Q_FUNC_INFO << "no capture device in 'Active' state";
+ Q_EMIT flashReady(false);
+ return;
+ }
+
+ if (captureDevice.hasFlash) {
+ if ([captureDevice isFlashModeSupported:AVCaptureFlashModeOn])
+ m_supportedModes |= QCameraExposure::FlashOn;
+ if ([captureDevice isFlashModeSupported:AVCaptureFlashModeAuto])
+ m_supportedModes |= QCameraExposure::FlashAuto;
+ }
+
+ if (captureDevice.hasTorch && [captureDevice isTorchModeSupported:AVCaptureTorchModeOn])
+ m_supportedModes |= QCameraExposure::FlashVideoLight;
+
+ Q_EMIT flashReady(applyFlashSettings());
+ }
+}
+
+
+
+QCameraExposure::FlashModes AVFCameraExposureControl::flashMode() const
+{
+ return m_flashMode;
+}
+
+void AVFCameraExposureControl::setFlashMode(QCameraExposure::FlashModes mode)
+{
+ if (m_flashMode == mode)
+ return;
+
+ if (m_session->state() == QCamera::ActiveState && !isFlashModeSupported(mode)) {
+ qDebugCamera() << Q_FUNC_INFO << "unsupported mode" << mode;
+ return;
+ }
+
+ m_flashMode = mode;
+
+ if (m_session->state() != QCamera::ActiveState)
+ return;
+
+ applyFlashSettings();
+}
+
+bool AVFCameraExposureControl::isFlashModeSupported(QCameraExposure::FlashModes mode) const
+{
+ // From what QCameraExposure has, we can support only these:
+ // FlashAuto = 0x1,
+ // FlashOff = 0x2,
+ // FlashOn = 0x4,
+ // AVCaptureDevice has these flash modes:
+ // AVCaptureFlashModeAuto
+ // AVCaptureFlashModeOff
+ // AVCaptureFlashModeOn
+ // QCameraExposure also has:
+ // FlashTorch = 0x20, --> "Constant light source."
+ // FlashVideoLight = 0x40. --> "Constant light source."
+ // AVCaptureDevice:
+ // AVCaptureTorchModeOff (no mapping)
+ // AVCaptureTorchModeOn --> FlashVideoLight
+ // AVCaptureTorchModeAuto (no mapping)
+
+ return m_supportedModes & mode;
+}
+
+bool AVFCameraExposureControl::isFlashReady() const
+{
+ if (m_session->state() != QCamera::ActiveState)
+ return false;
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice)
+ return false;
+
+ if (!captureDevice.hasFlash && !captureDevice.hasTorch)
+ return false;
+
+ if (!isFlashModeSupported(m_flashMode))
+ return false;
+
+#ifdef Q_OS_IOS
+ // AVCaptureDevice's docs:
+ // "The flash may become unavailable if, for example,
+ // the device overheats and needs to cool off."
+ if (m_flashMode != QCameraExposure::FlashVideoLight)
+ return [captureDevice isFlashAvailable];
+
+ return [captureDevice isTorchAvailable];
+#endif
+
+ return true;
+}
+
+bool AVFCameraExposureControl::applyFlashSettings()
+{
+ Q_ASSERT(m_session->requestedState() == QCamera::ActiveState);
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice) {
+ qDebugCamera() << Q_FUNC_INFO << "no capture device found";
+ return false;
+ }
+
+ if (!isFlashModeSupported(m_flashMode)) {
+ qDebugCamera() << Q_FUNC_INFO << "unsupported mode" << m_flashMode;
+ return false;
+ }
+
+ if (!captureDevice.hasFlash && !captureDevice.hasTorch) {
+ // FlashOff is the only mode we support.
+ // Return false - flash is not ready.
+ return false;
+ }
+
+ const AVFConfigurationLock lock(captureDevice);
+
+ if (m_flashMode != QCameraExposure::FlashVideoLight) {
+ if (captureDevice.torchMode != AVCaptureTorchModeOff) {
+#ifdef Q_OS_IOS
+ if (![captureDevice isTorchAvailable]) {
+ qDebugCamera() << Q_FUNC_INFO << "torch is not available at the moment";
+ return false;
+ }
+#endif
+ captureDevice.torchMode = AVCaptureTorchModeOff;
+ }
+#ifdef Q_OS_IOS
+ if (![captureDevice isFlashAvailable]) {
+ // We'd like to switch flash (into some mode), but it's not available:
+ qDebugCamera() << Q_FUNC_INFO << "flash is not available at the moment";
+ return false;
+ }
+#endif
+ } else {
+ if (captureDevice.flashMode != AVCaptureFlashModeOff) {
+#ifdef Q_OS_IOS
+ if (![captureDevice isFlashAvailable]) {
+ qDebugCamera() << Q_FUNC_INFO << "flash is not available at the moment";
+ return false;
+ }
+#endif
+ captureDevice.flashMode = AVCaptureFlashModeOff;
+ }
+
+#ifdef Q_OS_IOS
+ if (![captureDevice isTorchAvailable]) {
+ qDebugCamera() << Q_FUNC_INFO << "torch is not available at the moment";
+ return false;
+ }
+#endif
+ }
+
+ if (m_flashMode == QCameraExposure::FlashOff)
+ captureDevice.flashMode = AVCaptureFlashModeOff;
+ else if (m_flashMode == QCameraExposure::FlashOn)
+ captureDevice.flashMode = AVCaptureFlashModeOn;
+ else if (m_flashMode == QCameraExposure::FlashAuto)
+ captureDevice.flashMode = AVCaptureFlashModeAuto;
+ else if (m_flashMode == QCameraExposure::FlashVideoLight)
+ captureDevice.torchMode = AVCaptureTorchModeOn;
+
+ return true;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_avfcameraexposurecontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol_p.h
new file mode 100644
index 000000000..9cf60a7b8
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol_p.h
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFCAMERAEXPOSURECONTROL_H
+#define AVFCAMERAEXPOSURECONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/qcameraexposurecontrol.h>
+#include <QtMultimedia/qcameraexposure.h>
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraSession;
+class AVFCameraService;
+
+class AVFCameraExposureControl : public QCameraExposureControl
+{
+ Q_OBJECT
+
+public:
+ AVFCameraExposureControl(AVFCameraService *service);
+
+ bool isParameterSupported(ExposureParameter parameter) const override;
+ QVariantList supportedParameterRange(ExposureParameter parameter,
+ bool *continuous) const override;
+
+ QVariant requestedValue(ExposureParameter parameter) const override;
+ QVariant actualValue(ExposureParameter parameter) const override;
+ bool setValue(ExposureParameter parameter, const QVariant &value) override;
+
+ QCameraExposure::FlashModes flashMode() const override;
+ void setFlashMode(QCameraExposure::FlashModes mode) override;
+ bool isFlashModeSupported(QCameraExposure::FlashModes mode) const override;
+ bool isFlashReady() const override;
+
+private Q_SLOTS:
+ void cameraStateChanged(QCamera::State newState);
+
+private:
+ bool applyFlashSettings();
+
+ AVFCameraService *m_service;
+ AVFCameraSession *m_session;
+
+ QVariant m_requestedMode;
+ QVariant m_requestedCompensation;
+ QVariant m_requestedShutterSpeed;
+ QVariant m_requestedISO;
+
+ // Aux. setters:
+ bool setExposureMode(const QVariant &value);
+ bool setExposureCompensation(const QVariant &value);
+ bool setShutterSpeed(const QVariant &value);
+ bool setISO(const QVariant &value);
+
+ // Set of bits:
+ QCameraExposure::FlashModes m_supportedModes = QCameraExposure::FlashOff;
+ // Only one bit set actually:
+ QCameraExposure::FlashModes m_flashMode = QCameraExposure::FlashOff;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol.mm
new file mode 100644
index 000000000..62a7d55e2
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol.mm
@@ -0,0 +1,422 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcamerafocuscontrol_p.h"
+#include "avfcamerautility_p.h"
+#include "avfcameraservice_p.h"
+#include "avfcamerasession_p.h"
+#include "avfcameradebug_p.h"
+
+#include <QtCore/qdebug.h>
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+bool qt_focus_mode_supported(QCameraFocus::FocusModes mode)
+{
+ // Check if QCameraFocus::FocusMode has counterpart in AVFoundation.
+
+ // AVFoundation has 'Manual', 'Auto' and 'Continuous',
+ // where 'Manual' is actually 'Locked' + writable property 'lensPosition'.
+ // Since Qt does not provide an API to manipulate a lens position, 'Maual' mode
+ // (at the moment) is not supported.
+ return mode == QCameraFocus::AutoFocus
+ || mode == QCameraFocus::ContinuousFocus;
+}
+
+bool qt_focus_point_mode_supported(QCameraFocus::FocusPointMode mode)
+{
+ return mode == QCameraFocus::FocusPointAuto
+ || mode == QCameraFocus::FocusPointCustom
+ || mode == QCameraFocus::FocusPointCenter;
+}
+
+AVCaptureFocusMode avf_focus_mode(QCameraFocus::FocusModes requestedMode)
+{
+ if (requestedMode == QCameraFocus::AutoFocus)
+ return AVCaptureFocusModeAutoFocus;
+
+ return AVCaptureFocusModeContinuousAutoFocus;
+}
+
+}
+
+AVFCameraFocusControl::AVFCameraFocusControl(AVFCameraService *service)
+ : m_session(service->session()),
+ m_focusMode(QCameraFocus::ContinuousFocus),
+ m_focusPointMode(QCameraFocus::FocusPointAuto),
+ m_customFocusPoint(0.5f, 0.5f),
+ m_actualFocusPoint(m_customFocusPoint)
+{
+ Q_ASSERT(m_session);
+ connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(cameraStateChanged()));
+}
+
+QCameraFocus::FocusModes AVFCameraFocusControl::focusMode() const
+{
+ return m_focusMode;
+}
+
+void AVFCameraFocusControl::setFocusMode(QCameraFocus::FocusModes mode)
+{
+ if (m_focusMode == mode)
+ return;
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice) {
+ if (qt_focus_mode_supported(mode)) {
+ m_focusMode = mode;
+ Q_EMIT focusModeChanged(m_focusMode);
+ } else {
+ qDebugCamera() << Q_FUNC_INFO
+ << "focus mode not supported";
+ }
+ return;
+ }
+
+ if (isFocusModeSupported(mode)) {
+ const AVFConfigurationLock lock(captureDevice);
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO
+ << "failed to lock for configuration";
+ return;
+ }
+
+ captureDevice.focusMode = avf_focus_mode(mode);
+ m_focusMode = mode;
+ } else {
+ qDebugCamera() << Q_FUNC_INFO << "focus mode not supported";
+ return;
+ }
+
+ Q_EMIT focusModeChanged(m_focusMode);
+}
+
+bool AVFCameraFocusControl::isFocusModeSupported(QCameraFocus::FocusModes mode) const
+{
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice)
+ return false;
+
+ if (!qt_focus_mode_supported(mode))
+ return false;
+
+ return [captureDevice isFocusModeSupported:avf_focus_mode(mode)];
+}
+
+QCameraFocus::FocusPointMode AVFCameraFocusControl::focusPointMode() const
+{
+ return m_focusPointMode;
+}
+
+void AVFCameraFocusControl::setFocusPointMode(QCameraFocus::FocusPointMode mode)
+{
+ if (m_focusPointMode == mode)
+ return;
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice) {
+ if (qt_focus_point_mode_supported(mode)) {
+ m_focusPointMode = mode;
+ Q_EMIT focusPointModeChanged(mode);
+ }
+ return;
+ }
+
+ if (isFocusPointModeSupported(mode)) {
+ const AVFConfigurationLock lock(captureDevice);
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration";
+ return;
+ }
+
+ bool resetPOI = false;
+ if (mode == QCameraFocus::FocusPointCenter || mode == QCameraFocus::FocusPointAuto) {
+ if (m_actualFocusPoint != QPointF(0.5, 0.5)) {
+ m_actualFocusPoint = QPointF(0.5, 0.5);
+ resetPOI = true;
+ }
+ } else if (mode == QCameraFocus::FocusPointCustom) {
+ if (m_actualFocusPoint != m_customFocusPoint) {
+ m_actualFocusPoint = m_customFocusPoint;
+ resetPOI = true;
+ }
+ } // else for any other mode in future.
+
+ if (resetPOI) {
+ const CGPoint focusPOI = CGPointMake(m_actualFocusPoint.x(), m_actualFocusPoint.y());
+ [captureDevice setFocusPointOfInterest:focusPOI];
+ }
+ m_focusPointMode = mode;
+ } else {
+ qDebugCamera() << Q_FUNC_INFO << "focus point mode is not supported";
+ return;
+ }
+
+ Q_EMIT focusPointModeChanged(mode);
+}
+
+bool AVFCameraFocusControl::isFocusPointModeSupported(QCameraFocus::FocusPointMode mode) const
+{
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice)
+ return false;
+
+ if (!qt_focus_point_mode_supported(mode))
+ return false;
+
+ return [captureDevice isFocusPointOfInterestSupported];
+}
+
+QPointF AVFCameraFocusControl::customFocusPoint() const
+{
+ return m_customFocusPoint;
+}
+
+void AVFCameraFocusControl::setCustomFocusPoint(const QPointF &point)
+{
+ if (m_customFocusPoint == point)
+ return;
+
+ if (!QRectF(0.f, 0.f, 1.f, 1.f).contains(point)) {
+ qDebugCamera() << Q_FUNC_INFO << "invalid focus point (out of range)";
+ return;
+ }
+
+ m_customFocusPoint = point;
+ Q_EMIT customFocusPointChanged(m_customFocusPoint);
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice || m_focusPointMode != QCameraFocus::FocusPointCustom)
+ return;
+
+ if ([captureDevice isFocusPointOfInterestSupported]) {
+ const AVFConfigurationLock lock(captureDevice);
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration";
+ return;
+ }
+
+ m_actualFocusPoint = m_customFocusPoint;
+ const CGPoint focusPOI = CGPointMake(point.x(), point.y());
+ [captureDevice setFocusPointOfInterest:focusPOI];
+ if (m_focusMode != QCameraFocus::ContinuousFocus)
+ [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
+ } else {
+ qDebugCamera() << Q_FUNC_INFO << "focus point of interest not supported";
+ return;
+ }
+}
+
+QCameraFocusZoneList AVFCameraFocusControl::focusZones() const
+{
+ // Unsupported.
+ return QCameraFocusZoneList();
+}
+
+void AVFCameraFocusControl::cameraStateChanged()
+{
+ if (m_session->state() != QCamera::ActiveState)
+ return;
+
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice) {
+ qDebugCamera() << Q_FUNC_INFO << "capture device is nil in 'active' state";
+ return;
+ }
+
+ const AVFConfigurationLock lock(captureDevice);
+ if (m_customFocusPoint != m_actualFocusPoint
+ && m_focusPointMode == QCameraFocus::FocusPointCustom) {
+ if (![captureDevice isFocusPointOfInterestSupported]) {
+ qDebugCamera() << Q_FUNC_INFO
+ << "focus point of interest not supported";
+ return;
+ }
+
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration";
+ return;
+ }
+
+ m_actualFocusPoint = m_customFocusPoint;
+ const CGPoint focusPOI = CGPointMake(m_customFocusPoint.x(), m_customFocusPoint.y());
+ [captureDevice setFocusPointOfInterest:focusPOI];
+ }
+
+ if (m_focusMode != QCameraFocus::ContinuousFocus) {
+ const AVCaptureFocusMode avMode = avf_focus_mode(m_focusMode);
+ if (captureDevice.focusMode != avMode) {
+ if (![captureDevice isFocusModeSupported:avMode]) {
+ qDebugCamera() << Q_FUNC_INFO << "focus mode not supported";
+ return;
+ }
+
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration";
+ return;
+ }
+
+ [captureDevice setFocusMode:avMode];
+ }
+ }
+
+#ifdef Q_OS_IOS
+ const QCamera::State state = m_session->state();
+ if (state != QCamera::ActiveState) {
+ if (state == QCamera::UnloadedState && m_maxZoomFactor > 1.) {
+ m_maxZoomFactor = 1.;
+ Q_EMIT maximumDigitalZoomChanged(1.);
+ }
+ return;
+ }
+
+ if (!captureDevice || !captureDevice.activeFormat) {
+ qDebugCamera() << Q_FUNC_INFO << "camera state is active, but"
+ << "video capture device and/or active format is nil";
+ return;
+ }
+
+ if (captureDevice.activeFormat.videoMaxZoomFactor > 1.) {
+ if (!qFuzzyCompare(m_maxZoomFactor, captureDevice.activeFormat.videoMaxZoomFactor)) {
+ m_maxZoomFactor = captureDevice.activeFormat.videoMaxZoomFactor;
+ Q_EMIT maximumDigitalZoomChanged(m_maxZoomFactor);
+ }
+ } else if (!qFuzzyCompare(m_maxZoomFactor, CGFloat(1.))) {
+ m_maxZoomFactor = 1.;
+
+ Q_EMIT maximumDigitalZoomChanged(1.);
+ }
+
+ zoomToRequestedDigital();
+#endif
+}
+
+qreal AVFCameraFocusControl::maximumOpticalZoom() const
+{
+ // Not supported.
+ return 1.;
+}
+
+qreal AVFCameraFocusControl::maximumDigitalZoom() const
+{
+ return m_maxZoomFactor;
+}
+
+qreal AVFCameraFocusControl::requestedOpticalZoom() const
+{
+ // Not supported.
+ return 1;
+}
+
+qreal AVFCameraFocusControl::requestedDigitalZoom() const
+{
+ return m_requestedZoomFactor;
+}
+
+qreal AVFCameraFocusControl::currentOpticalZoom() const
+{
+ // Not supported.
+ return 1.;
+}
+
+qreal AVFCameraFocusControl::currentDigitalZoom() const
+{
+ return m_zoomFactor;
+}
+
+void AVFCameraFocusControl::zoomTo(qreal optical, qreal digital)
+{
+ Q_UNUSED(optical);
+ Q_UNUSED(digital);
+
+#ifdef QOS_IOS
+ if (qFuzzyCompare(CGFloat(digital), m_requestedZoomFactor))
+ return;
+
+ m_requestedZoomFactor = digital;
+ Q_EMIT requestedDigitalZoomChanged(digital);
+
+ zoomToRequestedDigital();
+#endif
+}
+
+#ifdef QOS_IOS
+void AVFCameraFocusControl::zoomToRequestedDigital()
+{
+ AVCaptureDevice *captureDevice = m_session->videoCaptureDevice();
+ if (!captureDevice || !captureDevice.activeFormat)
+ return;
+
+ if (qFuzzyCompare(captureDevice.activeFormat.videoMaxZoomFactor, CGFloat(1.)))
+ return;
+
+ const CGFloat clampedZoom = qBound(CGFloat(1.), m_requestedZoomFactor,
+ captureDevice.activeFormat.videoMaxZoomFactor);
+ const CGFloat deviceZoom = captureDevice.videoZoomFactor;
+ if (qFuzzyCompare(clampedZoom, deviceZoom)) {
+ // Nothing to set, but check if a signal must be emitted:
+ if (!qFuzzyCompare(m_zoomFactor, deviceZoom)) {
+ m_zoomFactor = deviceZoom;
+ Q_EMIT currentDigitalZoomChanged(deviceZoom);
+ }
+ return;
+ }
+
+ const AVFConfigurationLock lock(captureDevice);
+ if (!lock) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration";
+ return;
+ }
+
+ captureDevice.videoZoomFactor = clampedZoom;
+
+ if (!qFuzzyCompare(clampedZoom, m_zoomFactor)) {
+ m_zoomFactor = clampedZoom;
+ Q_EMIT currentDigitalZoomChanged(clampedZoom);
+ }
+}
+#endif
+
+QT_END_NAMESPACE
+
+#include "moc_avfcamerafocuscontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol_p.h
new file mode 100644
index 000000000..3527d48b6
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol_p.h
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFCAMERAFOCUSCONTROL_H
+#define AVFCAMERAFOCUSCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qglobal.h>
+
+#include <qcamerafocuscontrol.h>
+
+#include <AVFoundation/AVFoundation.h>
+
+@class AVCaptureDevice;
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraService;
+class AVFCameraSession;
+
+class AVFCameraFocusControl : public QCameraFocusControl
+{
+ Q_OBJECT
+public:
+ explicit AVFCameraFocusControl(AVFCameraService *service);
+
+ QCameraFocus::FocusModes focusMode() const override;
+ void setFocusMode(QCameraFocus::FocusModes mode) override;
+ bool isFocusModeSupported(QCameraFocus::FocusModes mode) const override;
+
+ QCameraFocus::FocusPointMode focusPointMode() const override;
+ void setFocusPointMode(QCameraFocus::FocusPointMode mode) override;
+ bool isFocusPointModeSupported(QCameraFocus::FocusPointMode mode) const override;
+ QPointF customFocusPoint() const override;
+ void setCustomFocusPoint(const QPointF &point) override;
+
+ QCameraFocusZoneList focusZones() const override;
+
+ qreal maximumOpticalZoom() const override;
+ qreal maximumDigitalZoom() const override;
+
+ qreal requestedOpticalZoom() const override;
+ qreal requestedDigitalZoom() const override;
+ qreal currentOpticalZoom() const override;
+ qreal currentDigitalZoom() const override;
+
+ void zoomTo(qreal optical, qreal digital) override;
+
+private Q_SLOTS:
+ void cameraStateChanged();
+
+private:
+#ifdef QOS_IOS
+ void zoomToRequestedDigital();
+#endif
+
+ AVFCameraSession *m_session;
+ QCameraFocus::FocusModes m_focusMode;
+ QCameraFocus::FocusPointMode m_focusPointMode;
+ QPointF m_customFocusPoint;
+ QPointF m_actualFocusPoint;
+
+ CGFloat m_maxZoomFactor;
+ CGFloat m_zoomFactor;
+ CGFloat m_requestedZoomFactor;
+};
+
+
+QT_END_NAMESPACE
+
+#endif // AVFCAMERAFOCUSCONTROL_H
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol.mm
new file mode 100644
index 000000000..4addfd938
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol.mm
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcamerametadatacontrol_p.h"
+#include "avfcamerasession_p.h"
+#include "avfcameraservice_p.h"
+
+QT_USE_NAMESPACE
+
+//metadata support is not implemented yet
+
+AVFCameraMetaDataControl::AVFCameraMetaDataControl(AVFCameraService *service, QObject *parent)
+ :QMetaDataWriterControl(parent)
+{
+ Q_UNUSED(service);
+}
+
+AVFCameraMetaDataControl::~AVFCameraMetaDataControl()
+{
+}
+
+bool AVFCameraMetaDataControl::isMetaDataAvailable() const
+{
+ return !m_tags.isEmpty();
+}
+
+bool AVFCameraMetaDataControl::isWritable() const
+{
+ return false;
+}
+
+QVariant AVFCameraMetaDataControl::metaData(const QString &key) const
+{
+ return m_tags.value(key);
+}
+
+void AVFCameraMetaDataControl::setMetaData(const QString &key, const QVariant &value)
+{
+ m_tags.insert(key, value);
+}
+
+QStringList AVFCameraMetaDataControl::availableMetaData() const
+{
+ return m_tags.keys();
+}
+
+#include "moc_avfcamerametadatacontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol_p.h
new file mode 100644
index 000000000..2f9138986
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol_p.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFCAMERAMETADATACONTROL_H
+#define AVFCAMERAMETADATACONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qmetadatawritercontrol.h>
+#include <QtCore/qvariant.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraService;
+
+class AVFCameraMetaDataControl : public QMetaDataWriterControl
+{
+ Q_OBJECT
+public:
+ AVFCameraMetaDataControl(AVFCameraService *service, QObject *parent = nullptr);
+ virtual ~AVFCameraMetaDataControl();
+
+ bool isMetaDataAvailable() const override;
+ bool isWritable() const override;
+
+ QVariant metaData(const QString &key) const override;
+ void setMetaData(const QString &key, const QVariant &value) override;
+ QStringList availableMetaData() const override;
+
+private:
+ QMap<QString, QVariant> m_tags;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol.mm
new file mode 100644
index 000000000..13131766c
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol.mm
@@ -0,0 +1,409 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "private/qabstractvideobuffer_p.h"
+#include "avfcamerarenderercontrol_p.h"
+#include "avfcamerasession_p.h"
+#include "avfcameraservice_p.h"
+#include "avfcameradebug_p.h"
+#include "avfcameracontrol_p.h"
+
+#ifdef Q_OS_IOS
+#include <QtGui/qopengl.h>
+#endif
+
+#include <QtMultimedia/qabstractvideosurface.h>
+#include <QtMultimedia/qabstractvideobuffer.h>
+
+#include <QtMultimedia/qvideosurfaceformat.h>
+
+QT_USE_NAMESPACE
+
+class CVImageVideoBuffer : public QAbstractVideoBuffer
+{
+public:
+ CVImageVideoBuffer(CVImageBufferRef buffer, AVFCameraRendererControl *renderer)
+#ifndef Q_OS_IOS
+ : QAbstractVideoBuffer(NoHandle)
+#else
+ : QAbstractVideoBuffer(renderer->supportsTextures()
+ && CVPixelBufferGetPixelFormatType(buffer) == kCVPixelFormatType_32BGRA
+ ? GLTextureHandle : NoHandle)
+ , m_texture(nullptr)
+ , m_renderer(renderer)
+#endif
+ , m_buffer(buffer)
+ , m_mode(NotMapped)
+ {
+#ifndef Q_OS_IOS
+ Q_UNUSED(renderer);
+#endif // Q_OS_IOS
+ CVPixelBufferRetain(m_buffer);
+ }
+
+ ~CVImageVideoBuffer()
+ {
+ CVImageVideoBuffer::unmap();
+#ifdef Q_OS_IOS
+ if (m_texture)
+ CFRelease(m_texture);
+#endif
+ CVPixelBufferRelease(m_buffer);
+ }
+
+ MapMode mapMode() const { return m_mode; }
+
+ MapData map(QAbstractVideoBuffer::MapMode mode)
+ {
+ MapData mapData;
+
+ // We only support RGBA or NV12 (or Apple's version of NV12),
+ // they are either 0 planes or 2.
+ mapData.nPlanes = CVPixelBufferGetPlaneCount(m_buffer);
+ Q_ASSERT(mapData.nPlanes <= 2);
+
+ if (!mapData.nPlanes) {
+ mapData.data[0] = map(mode, &mapData.nBytes, &mapData.bytesPerLine[0]);
+ mapData.nPlanes = mapData.data[0] ? 1 : 0;
+ return mapData;
+ }
+
+ // For a bi-planar format we have to set the parameters correctly:
+ if (mode != QAbstractVideoBuffer::NotMapped && m_mode == QAbstractVideoBuffer::NotMapped) {
+ CVPixelBufferLockBaseAddress(m_buffer, mode == QAbstractVideoBuffer::ReadOnly
+ ? kCVPixelBufferLock_ReadOnly
+ : 0);
+
+ mapData.nBytes = CVPixelBufferGetDataSize(m_buffer);
+
+ // At the moment we handle only bi-planar format.
+ mapData.bytesPerLine[0] = CVPixelBufferGetBytesPerRowOfPlane(m_buffer, 0);
+ mapData.bytesPerLine[1] = CVPixelBufferGetBytesPerRowOfPlane(m_buffer, 1);
+
+ mapData.data[0] = static_cast<uchar*>(CVPixelBufferGetBaseAddressOfPlane(m_buffer, 0));
+ mapData.data[1] = static_cast<uchar*>(CVPixelBufferGetBaseAddressOfPlane(m_buffer, 1));
+
+ m_mode = mode;
+ }
+
+ return mapData;
+ }
+
+ uchar *map(MapMode mode, qsizetype *numBytes, int *bytesPerLine)
+ {
+ if (mode != NotMapped && m_mode == NotMapped) {
+ CVPixelBufferLockBaseAddress(m_buffer, mode == QAbstractVideoBuffer::ReadOnly
+ ? kCVPixelBufferLock_ReadOnly
+ : 0);
+ if (numBytes)
+ *numBytes = CVPixelBufferGetDataSize(m_buffer);
+
+ if (bytesPerLine)
+ *bytesPerLine = CVPixelBufferGetBytesPerRow(m_buffer);
+
+ m_mode = mode;
+ return static_cast<uchar*>(CVPixelBufferGetBaseAddress(m_buffer));
+ } else {
+ return nullptr;
+ }
+ }
+
+ void unmap()
+ {
+ if (m_mode != NotMapped) {
+ CVPixelBufferUnlockBaseAddress(m_buffer, m_mode == QAbstractVideoBuffer::ReadOnly
+ ? kCVPixelBufferLock_ReadOnly
+ : 0);
+ m_mode = NotMapped;
+ }
+ }
+
+ QVariant handle() const
+ {
+#ifdef Q_OS_IOS
+ // Called from the render thread, so there is a current OpenGL context
+
+ if (!m_renderer->m_textureCache) {
+ CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault,
+ nullptr,
+ [EAGLContext currentContext],
+ nullptr,
+ &m_renderer->m_textureCache);
+
+ if (err != kCVReturnSuccess)
+ qWarning("Error creating texture cache");
+ }
+
+ if (m_renderer->m_textureCache && !m_texture) {
+ CVOpenGLESTextureCacheFlush(m_renderer->m_textureCache, 0);
+
+ CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
+ m_renderer->m_textureCache,
+ m_buffer,
+ nullptr,
+ GL_TEXTURE_2D,
+ GL_RGBA,
+ CVPixelBufferGetWidth(m_buffer),
+ CVPixelBufferGetHeight(m_buffer),
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ 0,
+ &m_texture);
+ if (err != kCVReturnSuccess)
+ qWarning("Error creating texture from buffer");
+ }
+
+ if (m_texture)
+ return CVOpenGLESTextureGetName(m_texture);
+ else
+ return 0;
+#else
+ return QVariant();
+#endif
+ }
+
+private:
+#ifdef Q_OS_IOS
+ mutable CVOpenGLESTextureRef m_texture;
+ AVFCameraRendererControl *m_renderer;
+#endif
+ CVImageBufferRef m_buffer;
+ MapMode m_mode;
+};
+
+
+@interface AVFCaptureFramesDelegate : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
+
+- (AVFCaptureFramesDelegate *) initWithRenderer:(AVFCameraRendererControl*)renderer;
+
+- (void) captureOutput:(AVCaptureOutput *)captureOutput
+ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
+ fromConnection:(AVCaptureConnection *)connection;
+
+@end
+
+@implementation AVFCaptureFramesDelegate
+{
+@private
+ AVFCameraRendererControl *m_renderer;
+}
+
+- (AVFCaptureFramesDelegate *) initWithRenderer:(AVFCameraRendererControl*)renderer
+{
+ if (!(self = [super init]))
+ return nil;
+
+ self->m_renderer = renderer;
+ return self;
+}
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput
+ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
+ fromConnection:(AVCaptureConnection *)connection
+{
+ Q_UNUSED(connection);
+ Q_UNUSED(captureOutput);
+
+ // NB: on iOS captureOutput/connection can be nil (when recording a video -
+ // avfmediaassetwriter).
+
+ CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
+
+ int width = CVPixelBufferGetWidth(imageBuffer);
+ int height = CVPixelBufferGetHeight(imageBuffer);
+ QVideoFrame::PixelFormat format =
+ AVFCameraControl::QtPixelFormatFromCVFormat(CVPixelBufferGetPixelFormatType(imageBuffer));
+ if (format == QVideoFrame::Format_Invalid)
+ return;
+
+ QVideoFrame frame(new CVImageVideoBuffer(imageBuffer, m_renderer),
+ QSize(width, height),
+ format);
+
+ m_renderer->syncHandleViewfinderFrame(frame);
+}
+
+@end
+
+
+AVFCameraRendererControl::AVFCameraRendererControl(QObject *parent)
+ : QVideoRendererControl(parent)
+ , m_surface(nullptr)
+ , m_supportsTextures(false)
+ , m_needsHorizontalMirroring(false)
+#ifdef Q_OS_IOS
+ , m_textureCache(nullptr)
+#endif
+{
+ m_viewfinderFramesDelegate = [[AVFCaptureFramesDelegate alloc] initWithRenderer:this];
+}
+
+AVFCameraRendererControl::~AVFCameraRendererControl()
+{
+ [m_cameraSession->captureSession() removeOutput:m_videoDataOutput];
+ [m_viewfinderFramesDelegate release];
+ if (m_delegateQueue)
+ dispatch_release(m_delegateQueue);
+#ifdef Q_OS_IOS
+ if (m_textureCache)
+ CFRelease(m_textureCache);
+#endif
+}
+
+QAbstractVideoSurface *AVFCameraRendererControl::surface() const
+{
+ return m_surface;
+}
+
+void AVFCameraRendererControl::setSurface(QAbstractVideoSurface *surface)
+{
+ if (m_surface != surface) {
+ m_surface = surface;
+ m_supportsTextures = m_surface
+ ? !m_surface->supportedPixelFormats(QAbstractVideoBuffer::GLTextureHandle).isEmpty()
+ : false;
+ Q_EMIT surfaceChanged(surface);
+ }
+}
+
+void AVFCameraRendererControl::configureAVCaptureSession(AVFCameraSession *cameraSession)
+{
+ m_cameraSession = cameraSession;
+ connect(m_cameraSession, SIGNAL(readyToConfigureConnections()),
+ this, SLOT(updateCaptureConnection()));
+
+ m_needsHorizontalMirroring = false;
+
+ m_videoDataOutput = [[[AVCaptureVideoDataOutput alloc] init] autorelease];
+
+ // Configure video output
+ m_delegateQueue = dispatch_queue_create("vf_queue", nullptr);
+ [m_videoDataOutput
+ setSampleBufferDelegate:m_viewfinderFramesDelegate
+ queue:m_delegateQueue];
+
+ [m_cameraSession->captureSession() addOutput:m_videoDataOutput];
+}
+
+void AVFCameraRendererControl::updateCaptureConnection()
+{
+ AVCaptureConnection *connection = [m_videoDataOutput connectionWithMediaType:AVMediaTypeVideo];
+ if (connection == nil || !m_cameraSession->videoCaptureDevice())
+ return;
+
+ // Frames of front-facing cameras should be mirrored horizontally (it's the default when using
+ // AVCaptureVideoPreviewLayer but not with AVCaptureVideoDataOutput)
+ if (connection.isVideoMirroringSupported)
+ connection.videoMirrored = m_cameraSession->videoCaptureDevice().position == AVCaptureDevicePositionFront;
+
+ // If the connection does't support mirroring, we'll have to do it ourselves
+ m_needsHorizontalMirroring = !connection.isVideoMirrored
+ && m_cameraSession->videoCaptureDevice().position == AVCaptureDevicePositionFront;
+}
+
+//can be called from non main thread
+void AVFCameraRendererControl::syncHandleViewfinderFrame(const QVideoFrame &frame)
+{
+ QMutexLocker lock(&m_vfMutex);
+ if (!m_lastViewfinderFrame.isValid()) {
+ static QMetaMethod handleViewfinderFrameSlot = metaObject()->method(
+ metaObject()->indexOfMethod("handleViewfinderFrame()"));
+
+ handleViewfinderFrameSlot.invoke(this, Qt::QueuedConnection);
+ }
+
+ m_lastViewfinderFrame = frame;
+
+ if (m_cameraSession && m_lastViewfinderFrame.isValid())
+ m_cameraSession->onCameraFrameFetched(m_lastViewfinderFrame);
+}
+
+AVCaptureVideoDataOutput *AVFCameraRendererControl::videoDataOutput() const
+{
+ return m_videoDataOutput;
+}
+
+#ifdef Q_OS_IOS
+
+AVFCaptureFramesDelegate *AVFCameraRendererControl::captureDelegate() const
+{
+ return m_viewfinderFramesDelegate;
+}
+
+void AVFCameraRendererControl::resetCaptureDelegate() const
+{
+ [m_videoDataOutput setSampleBufferDelegate:m_viewfinderFramesDelegate queue:m_delegateQueue];
+}
+
+#endif
+
+void AVFCameraRendererControl::handleViewfinderFrame()
+{
+ QVideoFrame frame;
+ {
+ QMutexLocker lock(&m_vfMutex);
+ frame = m_lastViewfinderFrame;
+ m_lastViewfinderFrame = QVideoFrame();
+ }
+
+ if (m_surface && frame.isValid()) {
+ if (m_surface->isActive() && (m_surface->surfaceFormat().pixelFormat() != frame.pixelFormat()
+ || m_surface->surfaceFormat().frameSize() != frame.size())) {
+ m_surface->stop();
+ }
+
+ if (!m_surface->isActive()) {
+ QVideoSurfaceFormat format(frame.size(), frame.pixelFormat(), frame.handleType());
+ if (m_needsHorizontalMirroring)
+ format.setProperty("mirrored", true);
+
+ if (!m_surface->start(format)) {
+ qWarning() << "Failed to start viewfinder m_surface, format:" << format;
+ } else {
+ qDebugCamera() << "Viewfinder started: " << format;
+ }
+ }
+
+ if (m_surface->isActive())
+ m_surface->present(frame);
+ }
+}
+
+
+#include "moc_avfcamerarenderercontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol_p.h
new file mode 100644
index 000000000..ade916ed6
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol_p.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFCAMERARENDERERCONTROL_H
+#define AVFCAMERARENDERERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/qvideorenderercontrol.h>
+#include <QtMultimedia/qvideoframe.h>
+#include <QtCore/qmutex.h>
+
+#import <AVFoundation/AVFoundation.h>
+
+@class AVFCaptureFramesDelegate;
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraSession;
+class AVFCameraService;
+class AVFCameraRendererControl;
+
+class AVFCameraRendererControl : public QVideoRendererControl
+{
+Q_OBJECT
+public:
+ AVFCameraRendererControl(QObject *parent = nullptr);
+ ~AVFCameraRendererControl();
+
+ QAbstractVideoSurface *surface() const override;
+ void setSurface(QAbstractVideoSurface *surface) override;
+
+ void configureAVCaptureSession(AVFCameraSession *cameraSession);
+ void syncHandleViewfinderFrame(const QVideoFrame &frame);
+
+ AVCaptureVideoDataOutput *videoDataOutput() const;
+
+ bool supportsTextures() const { return m_supportsTextures; }
+
+#ifdef Q_OS_IOS
+ AVFCaptureFramesDelegate *captureDelegate() const;
+ void resetCaptureDelegate() const;
+#endif
+
+Q_SIGNALS:
+ void surfaceChanged(QAbstractVideoSurface *surface);
+
+private Q_SLOTS:
+ void handleViewfinderFrame();
+ void updateCaptureConnection();
+
+private:
+ QAbstractVideoSurface *m_surface;
+ AVFCaptureFramesDelegate *m_viewfinderFramesDelegate;
+ AVFCameraSession *m_cameraSession;
+ AVCaptureVideoDataOutput *m_videoDataOutput;
+
+ bool m_supportsTextures;
+ bool m_needsHorizontalMirroring;
+
+#ifdef Q_OS_IOS
+ CVOpenGLESTextureCacheRef m_textureCache;
+#endif
+
+ QVideoFrame m_lastViewfinderFrame;
+ QMutex m_vfMutex;
+ dispatch_queue_t m_delegateQueue;
+
+ friend class CVImageVideoBuffer;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraservice.mm b/src/multimedia/platform/avfoundation/camera/avfcameraservice.mm
new file mode 100644
index 000000000..9fcab0ead
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameraservice.mm
@@ -0,0 +1,223 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qvariant.h>
+#include <QtCore/qdebug.h>
+
+#include "avfcameraservice_p.h"
+#include "avfcameracontrol_p.h"
+#include "avfcamerasession_p.h"
+#include "avfcameradevicecontrol_p.h"
+#include "avfaudioinputselectorcontrol_p.h"
+#include "avfcamerametadatacontrol_p.h"
+#include "avfmediarecordercontrol_p.h"
+#include "avfimagecapturecontrol_p.h"
+#include "avfcamerarenderercontrol_p.h"
+#include "avfmediarecordercontrol_p.h"
+#include "avfimagecapturecontrol_p.h"
+#include "avfmediavideoprobecontrol_p.h"
+#include "avfcamerafocuscontrol_p.h"
+#include "avfcameraexposurecontrol_p.h"
+#include "avfimageencodercontrol_p.h"
+#include "avfaudioencodersettingscontrol_p.h"
+#include "avfvideoencodersettingscontrol_p.h"
+#include "avfmediacontainercontrol_p.h"
+#include "avfcamerawindowcontrol_p.h"
+
+#ifdef Q_OS_IOS
+#include "avfmediarecordercontrol_ios_p.h"
+#endif
+
+QT_USE_NAMESPACE
+
+AVFCameraService::AVFCameraService(QObject *parent):
+ QMediaService(parent),
+ m_videoOutput(nullptr),
+ m_captureWindowControl(nullptr)
+{
+ m_session = new AVFCameraSession(this);
+ m_cameraControl = new AVFCameraControl(this);
+ m_videoDeviceControl = new AVFCameraDeviceControl(this);
+ m_audioInputSelectorControl = new AVFAudioInputSelectorControl(this);
+
+ m_metaDataControl = new AVFCameraMetaDataControl(this);
+#ifndef Q_OS_IOS
+ // This will connect a slot to 'captureModeChanged'
+ // and will break viewfinder by attaching AVCaptureMovieFileOutput
+ // in this slot.
+ m_recorderControl = new AVFMediaRecorderControl(this);
+#else
+ m_recorderControl = new AVFMediaRecorderControlIOS(this);
+#endif
+ m_imageCaptureControl = new AVFImageCaptureControl(this);
+ m_cameraFocusControl = new AVFCameraFocusControl(this);
+ m_cameraExposureControl = nullptr;
+#ifdef Q_OS_IOS
+ m_cameraExposureControl = new AVFCameraExposureControl(this);
+#endif
+
+ m_imageEncoderControl = new AVFImageEncoderControl(this);
+ m_audioEncoderSettingsControl = new AVFAudioEncoderSettingsControl(this);
+ m_videoEncoderSettingsControl = new AVFVideoEncoderSettingsControl(this);
+ m_mediaContainerControl = new AVFMediaContainerControl(this);
+}
+
+AVFCameraService::~AVFCameraService()
+{
+ m_cameraControl->setState(QCamera::UnloadedState);
+
+#ifdef Q_OS_IOS
+ delete m_recorderControl;
+#endif
+
+ if (m_captureWindowControl) {
+ m_session->setCapturePreviewOutput(nullptr);
+ delete m_captureWindowControl;
+ m_captureWindowControl = nullptr;
+ }
+
+ if (m_videoOutput) {
+ m_session->setVideoOutput(nullptr);
+ delete m_videoOutput;
+ m_videoOutput = nullptr;
+ }
+
+ //delete controls before session,
+ //so they have a chance to do deinitialization
+ delete m_imageCaptureControl;
+ //delete m_recorderControl;
+ delete m_metaDataControl;
+ delete m_cameraControl;
+ delete m_cameraFocusControl;
+ delete m_cameraExposureControl;
+ delete m_imageEncoderControl;
+ delete m_audioEncoderSettingsControl;
+ delete m_videoEncoderSettingsControl;
+ delete m_mediaContainerControl;
+
+ delete m_session;
+}
+
+QObject *AVFCameraService::requestControl(const char *name)
+{
+ if (qstrcmp(name, QCameraControl_iid) == 0)
+ return m_cameraControl;
+
+ if (qstrcmp(name, QVideoDeviceSelectorControl_iid) == 0)
+ return m_videoDeviceControl;
+
+ if (qstrcmp(name, QAudioInputSelectorControl_iid) == 0)
+ return m_audioInputSelectorControl;
+
+ //metadata support is not implemented yet
+ //if (qstrcmp(name, QMetaDataWriterControl_iid) == 0)
+ // return m_metaDataControl;
+
+ if (qstrcmp(name, QMediaRecorderControl_iid) == 0)
+ return m_recorderControl;
+
+ if (qstrcmp(name, QCameraImageCaptureControl_iid) == 0)
+ return m_imageCaptureControl;
+
+ if (qstrcmp(name, QCameraExposureControl_iid) == 0)
+ return m_cameraExposureControl;
+
+ if (qstrcmp(name, QCameraFocusControl_iid) == 0)
+ return m_cameraFocusControl;
+
+ if (qstrcmp(name, QImageEncoderControl_iid) == 0)
+ return m_imageEncoderControl;
+
+ if (qstrcmp(name, QAudioEncoderSettingsControl_iid) == 0)
+ return m_audioEncoderSettingsControl;
+
+ 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 = nullptr;
+ videoProbe = new AVFMediaVideoProbeControl(this);
+ m_session->addProbe(videoProbe);
+ return videoProbe;
+ }
+
+ if (!m_captureWindowControl) {
+ if (qstrcmp(name, QVideoWindowControl_iid) == 0) {
+ m_captureWindowControl = new AVFCameraWindowControl(this);
+ m_session->setCapturePreviewOutput(m_captureWindowControl);
+ return m_captureWindowControl;
+ }
+ }
+
+ if (!m_videoOutput) {
+ if (qstrcmp(name, QVideoRendererControl_iid) == 0)
+ m_videoOutput = new AVFCameraRendererControl(this);
+
+ if (m_videoOutput) {
+ m_session->setVideoOutput(m_videoOutput);
+ return m_videoOutput;
+ }
+ }
+
+ return nullptr;
+}
+
+void AVFCameraService::releaseControl(QObject *control)
+{
+ AVFMediaVideoProbeControl *videoProbe = qobject_cast<AVFMediaVideoProbeControl *>(control);
+ if (videoProbe) {
+ m_session->removeProbe(videoProbe);
+ delete videoProbe;
+ } else if (m_videoOutput == control) {
+ m_session->setVideoOutput(nullptr);
+ delete m_videoOutput;
+ m_videoOutput = nullptr;
+ }
+ else if (m_captureWindowControl == control) {
+ m_session->setCapturePreviewOutput(nullptr);
+ delete m_captureWindowControl;
+ m_captureWindowControl = nullptr;
+ }
+}
+
+
+#include "moc_avfcameraservice_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraservice_p.h b/src/multimedia/platform/avfoundation/camera/avfcameraservice_p.h
new file mode 100644
index 000000000..9efe4e9cb
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameraservice_p.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFCAMERASERVICE_H
+#define AVFCAMERASERVICE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qobject.h>
+#include <QtCore/qset.h>
+#include <qmediaservice.h>
+
+
+QT_BEGIN_NAMESPACE
+class QCameraControl;
+class QMediaRecorderControl;
+class AVFCameraControl;
+class AVFCameraMetaDataControl;
+class AVFVideoWindowControl;
+class AVFVideoWidgetControl;
+class AVFCameraRendererControl;
+class AVFImageCaptureControl;
+class AVFCameraSession;
+class AVFCameraDeviceControl;
+class AVFAudioInputSelectorControl;
+class AVFCameraFocusControl;
+class AVFCameraExposureControl;
+class AVFImageEncoderControl;
+class AVFMediaRecorderControl;
+class AVFMediaRecorderControlIOS;
+class AVFAudioEncoderSettingsControl;
+class AVFVideoEncoderSettingsControl;
+class AVFMediaContainerControl;
+class AVFCameraWindowControl;
+
+class AVFCameraService : public QMediaService
+{
+Q_OBJECT
+public:
+ AVFCameraService(QObject *parent = nullptr);
+ ~AVFCameraService();
+
+ QObject *requestControl(const char *name);
+ void releaseControl(QObject *control);
+
+ AVFCameraSession *session() const { return m_session; }
+ AVFCameraControl *cameraControl() const { return m_cameraControl; }
+ AVFCameraDeviceControl *videoDeviceControl() const { return m_videoDeviceControl; }
+ AVFAudioInputSelectorControl *audioInputSelectorControl() const { return m_audioInputSelectorControl; }
+ AVFCameraMetaDataControl *metaDataControl() const { return m_metaDataControl; }
+ QMediaRecorderControl *recorderControl() const { return m_recorderControl; }
+ AVFImageCaptureControl *imageCaptureControl() const { return m_imageCaptureControl; }
+ AVFCameraFocusControl *cameraFocusControl() const { return m_cameraFocusControl; }
+ AVFCameraExposureControl *cameraExposureControl() const {return m_cameraExposureControl; }
+ AVFCameraRendererControl *videoOutput() const {return m_videoOutput; }
+ AVFImageEncoderControl *imageEncoderControl() const {return m_imageEncoderControl; }
+ AVFAudioEncoderSettingsControl *audioEncoderSettingsControl() const { return m_audioEncoderSettingsControl; }
+ AVFVideoEncoderSettingsControl *videoEncoderSettingsControl() const {return m_videoEncoderSettingsControl; }
+ AVFMediaContainerControl *mediaContainerControl() const { return m_mediaContainerControl; }
+
+private:
+ AVFCameraSession *m_session;
+ AVFCameraControl *m_cameraControl;
+ AVFCameraDeviceControl *m_videoDeviceControl;
+ AVFAudioInputSelectorControl *m_audioInputSelectorControl;
+ AVFCameraRendererControl *m_videoOutput;
+ AVFCameraMetaDataControl *m_metaDataControl;
+ QMediaRecorderControl *m_recorderControl;
+ AVFImageCaptureControl *m_imageCaptureControl;
+ AVFCameraFocusControl *m_cameraFocusControl;
+ AVFCameraExposureControl *m_cameraExposureControl;
+ AVFImageEncoderControl *m_imageEncoderControl;
+ AVFAudioEncoderSettingsControl *m_audioEncoderSettingsControl;
+ AVFVideoEncoderSettingsControl *m_videoEncoderSettingsControl;
+ AVFMediaContainerControl *m_mediaContainerControl;
+ AVFCameraWindowControl *m_captureWindowControl;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin.mm b/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin.mm
new file mode 100644
index 000000000..2fd8d3fad
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin.mm
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qstring.h>
+#include <QtCore/qdebug.h>
+
+#include "avfcameraserviceplugin_p.h"
+#include "avfcameraservice_p.h"
+#include "avfcamerasession_p.h"
+
+#include <qmediaserviceproviderplugin.h>
+
+QT_BEGIN_NAMESPACE
+
+AVFServicePlugin::AVFServicePlugin()
+{
+}
+
+QMediaService* AVFServicePlugin::create(QString const& key)
+{
+ if (key == QLatin1String(Q_MEDIASERVICE_CAMERA))
+ return new AVFCameraService;
+ else
+ qWarning() << "unsupported key:" << key;
+
+ return nullptr;
+}
+
+void AVFServicePlugin::release(QMediaService *service)
+{
+ delete service;
+}
+
+QByteArray AVFServicePlugin::defaultDevice(const QByteArray &service) const
+{
+ if (service == Q_MEDIASERVICE_CAMERA) {
+ int i = AVFCameraSession::defaultCameraIndex();
+ if (i != -1)
+ return AVFCameraSession::availableCameraDevices().at(i).deviceId;
+ }
+
+ return QByteArray();
+}
+
+QList<QByteArray> AVFServicePlugin::devices(const QByteArray &service) const
+{
+ QList<QByteArray> devs;
+
+ if (service == Q_MEDIASERVICE_CAMERA) {
+ const QList<AVFCameraInfo> &cameras = AVFCameraSession::availableCameraDevices();
+ devs.reserve(cameras.size());
+ for (const AVFCameraInfo &info : cameras)
+ devs.append(info.deviceId);
+ }
+
+ return devs;
+}
+
+QString AVFServicePlugin::deviceDescription(const QByteArray &service, const QByteArray &device)
+{
+ if (service == Q_MEDIASERVICE_CAMERA)
+ return AVFCameraSession::cameraDeviceInfo(device).description;
+
+ return QString();
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin_p.h b/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin_p.h
new file mode 100644
index 000000000..e9028542c
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin_p.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef AVFSERVICEPLUGIN_H
+#define AVFSERVICEPLUGIN_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qmediaserviceproviderplugin.h>
+#include <QtCore/qmap.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFServicePlugin : public QMediaServiceProviderPlugin,
+ public QMediaServiceSupportedDevicesInterface
+{
+ Q_OBJECT
+ Q_INTERFACES(QMediaServiceSupportedDevicesInterface)
+
+public:
+ AVFServicePlugin();
+
+ QMediaService* create(QString const &key) override;
+ void release(QMediaService *service) override;
+
+ QByteArray defaultDevice(const QByteArray &service) const override;
+ QList<QByteArray> devices(const QByteArray &service) const override;
+ QString deviceDescription(const QByteArray &service, const QByteArray &device) override;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerasession.mm b/src/multimedia/platform/avfoundation/camera/avfcamerasession.mm
new file mode 100644
index 000000000..914ae6907
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerasession.mm
@@ -0,0 +1,491 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcameradebug_p.h"
+#include "avfcamerasession_p.h"
+#include "avfcameraservice_p.h"
+#include "avfcameracontrol_p.h"
+#include "avfcamerarenderercontrol_p.h"
+#include "avfcameradevicecontrol_p.h"
+#include "avfaudioinputselectorcontrol_p.h"
+#include "avfmediavideoprobecontrol_p.h"
+#include "avfimageencodercontrol_p.h"
+#include "avfcamerautility_p.h"
+#include "avfcamerawindowcontrol_p.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Foundation/Foundation.h>
+
+#include <QtCore/qdatetime.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qelapsedtimer.h>
+
+#include <QtCore/qdebug.h>
+
+QT_USE_NAMESPACE
+
+int AVFCameraSession::m_defaultCameraIndex;
+QList<AVFCameraInfo> AVFCameraSession::m_cameraDevices;
+
+@interface AVFCameraSessionObserver : NSObject
+
+- (AVFCameraSessionObserver *) initWithCameraSession:(AVFCameraSession*)session;
+- (void) processRuntimeError:(NSNotification *)notification;
+- (void) processSessionStarted:(NSNotification *)notification;
+- (void) processSessionStopped:(NSNotification *)notification;
+
+@end
+
+@implementation AVFCameraSessionObserver
+{
+@private
+ AVFCameraSession *m_session;
+ AVCaptureSession *m_captureSession;
+}
+
+- (AVFCameraSessionObserver *) initWithCameraSession:(AVFCameraSession*)session
+{
+ if (!(self = [super init]))
+ return nil;
+
+ self->m_session = session;
+ self->m_captureSession = session->captureSession();
+
+ [m_captureSession retain];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(processRuntimeError:)
+ name:AVCaptureSessionRuntimeErrorNotification
+ object:m_captureSession];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(processSessionStarted:)
+ name:AVCaptureSessionDidStartRunningNotification
+ object:m_captureSession];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(processSessionStopped:)
+ name:AVCaptureSessionDidStopRunningNotification
+ object:m_captureSession];
+
+ return self;
+}
+
+- (void) dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:AVCaptureSessionRuntimeErrorNotification
+ object:m_captureSession];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:AVCaptureSessionDidStartRunningNotification
+ object:m_captureSession];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:AVCaptureSessionDidStopRunningNotification
+ object:m_captureSession];
+ [m_captureSession release];
+ [super dealloc];
+}
+
+- (void) processRuntimeError:(NSNotification *)notification
+{
+ Q_UNUSED(notification);
+ QMetaObject::invokeMethod(m_session, "processRuntimeError", Qt::AutoConnection);
+}
+
+- (void) processSessionStarted:(NSNotification *)notification
+{
+ Q_UNUSED(notification);
+ QMetaObject::invokeMethod(m_session, "processSessionStarted", Qt::AutoConnection);
+}
+
+- (void) processSessionStopped:(NSNotification *)notification
+{
+ Q_UNUSED(notification);
+ QMetaObject::invokeMethod(m_session, "processSessionStopped", Qt::AutoConnection);
+}
+
+@end
+
+AVFCameraSession::AVFCameraSession(AVFCameraService *service, QObject *parent)
+ : QObject(parent)
+ , m_service(service)
+ , m_capturePreviewWindowOutput(nullptr)
+ , m_state(QCamera::UnloadedState)
+ , m_active(false)
+ , m_videoInput(nil)
+ , m_defaultCodec(0)
+{
+ m_captureSession = [[AVCaptureSession alloc] init];
+ m_observer = [[AVFCameraSessionObserver alloc] initWithCameraSession:this];
+
+ //configuration is commited during transition to Active state
+ [m_captureSession beginConfiguration];
+}
+
+AVFCameraSession::~AVFCameraSession()
+{
+ if (m_capturePreviewWindowOutput) {
+ m_capturePreviewWindowOutput->setLayer(nil);
+ }
+
+ if (m_videoInput) {
+ [m_captureSession removeInput:m_videoInput];
+ [m_videoInput release];
+ }
+
+ [m_observer release];
+ [m_captureSession release];
+}
+
+int AVFCameraSession::defaultCameraIndex()
+{
+ updateCameraDevices();
+ return m_defaultCameraIndex;
+}
+
+const QList<AVFCameraInfo> &AVFCameraSession::availableCameraDevices()
+{
+ updateCameraDevices();
+ return m_cameraDevices;
+}
+
+AVFCameraInfo AVFCameraSession::cameraDeviceInfo(const QByteArray &device)
+{
+ updateCameraDevices();
+
+ for (const AVFCameraInfo &info : qAsConst(m_cameraDevices)) {
+ if (info.deviceId == device)
+ return info;
+ }
+
+ return AVFCameraInfo();
+}
+
+void AVFCameraSession::updateCameraDevices()
+{
+#ifdef Q_OS_IOS
+ // Cameras can't change dynamically on iOS. Update only once.
+ if (!m_cameraDevices.isEmpty())
+ return;
+#else
+ // On OS X, cameras can be added or removed. Update the list every time, but not more than
+ // once every 500 ms
+ static QElapsedTimer timer;
+ if (timer.isValid() && timer.elapsed() < 500) // ms
+ return;
+#endif
+
+ m_defaultCameraIndex = -1;
+ m_cameraDevices.clear();
+
+ AVCaptureDevice *defaultDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
+ NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
+ for (AVCaptureDevice *device in videoDevices) {
+ if (defaultDevice && [defaultDevice.uniqueID isEqualToString:device.uniqueID])
+ m_defaultCameraIndex = m_cameraDevices.count();
+
+ AVFCameraInfo info;
+ info.deviceId = QByteArray([[device uniqueID] UTF8String]);
+ info.description = QString::fromNSString([device localizedName]);
+
+ // There is no API to get the camera sensor orientation, however, cameras are always
+ // mounted in landscape on iDevices.
+ // - Back-facing cameras have the top side of the sensor aligned with the right side of
+ // the screen when held in portrait ==> 270 degrees clockwise angle
+ // - Front-facing cameras have the top side of the sensor aligned with the left side of
+ // the screen when held in portrait ==> 270 degrees clockwise angle
+ // On OS X, the position will always be unspecified and the sensor orientation unknown.
+ switch (device.position) {
+ case AVCaptureDevicePositionBack:
+ info.position = QCamera::BackFace;
+ info.orientation = 270;
+ break;
+ case AVCaptureDevicePositionFront:
+ info.position = QCamera::FrontFace;
+ info.orientation = 270;
+ break;
+ default:
+ info.position = QCamera::UnspecifiedPosition;
+ info.orientation = 0;
+ break;
+ }
+
+ m_cameraDevices.append(info);
+ }
+
+#ifndef Q_OS_IOS
+ timer.restart();
+#endif
+}
+
+void AVFCameraSession::setVideoOutput(AVFCameraRendererControl *output)
+{
+ m_videoOutput = output;
+ if (output)
+ output->configureAVCaptureSession(this);
+}
+
+void AVFCameraSession::setCapturePreviewOutput(AVFCameraWindowControl *output)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << output;
+#endif
+
+ if (m_capturePreviewWindowOutput == output)
+ return;
+
+ if (m_capturePreviewWindowOutput)
+ m_capturePreviewWindowOutput->setLayer(nil);
+
+ m_capturePreviewWindowOutput = output;
+
+ if (m_capturePreviewWindowOutput) {
+ AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:m_captureSession];
+ m_capturePreviewWindowOutput->setLayer(previewLayer);
+ if (auto *camera = m_service->cameraControl()) {
+ m_capturePreviewWindowOutput->setNativeSize(camera->viewfinderSettings().resolution());
+ }
+ }
+}
+
+AVCaptureDevice *AVFCameraSession::videoCaptureDevice() const
+{
+ if (m_videoInput)
+ return m_videoInput.device;
+
+ return nullptr;
+}
+
+QCamera::State AVFCameraSession::state() const
+{
+ if (m_active)
+ return QCamera::ActiveState;
+
+ return m_state == QCamera::ActiveState ? QCamera::LoadedState : m_state;
+}
+
+void AVFCameraSession::setState(QCamera::State newState)
+{
+ if (m_state == newState)
+ return;
+
+ qDebugCamera() << Q_FUNC_INFO << m_state << " -> " << newState;
+
+ QCamera::State oldState = m_state;
+ m_state = newState;
+
+ //attach video input during Unloaded->Loaded transition
+ if (oldState == QCamera::UnloadedState)
+ attachVideoInputDevice();
+
+ if (m_state == QCamera::ActiveState) {
+ Q_EMIT readyToConfigureConnections();
+ m_defaultCodec = 0;
+ defaultCodec();
+
+ bool activeFormatSet = applyImageEncoderSettings()
+ | applyViewfinderSettings();
+
+ [m_captureSession commitConfiguration];
+
+ if (activeFormatSet) {
+ // According to the doc, the capture device must be locked before
+ // startRunning to prevent the format we set to be overriden by the
+ // session preset.
+ [videoCaptureDevice() lockForConfiguration:nil];
+ }
+
+ [m_captureSession startRunning];
+
+ if (activeFormatSet)
+ [videoCaptureDevice() unlockForConfiguration];
+ }
+
+ if (oldState == QCamera::ActiveState) {
+ [m_captureSession stopRunning];
+ [m_captureSession beginConfiguration];
+ }
+
+ Q_EMIT stateChanged(m_state);
+}
+
+void AVFCameraSession::processRuntimeError()
+{
+ qWarning() << tr("Runtime camera error");
+ Q_EMIT error(QCamera::CameraError, tr("Runtime camera error"));
+}
+
+void AVFCameraSession::processSessionStarted()
+{
+ qDebugCamera() << Q_FUNC_INFO;
+ if (!m_active) {
+ m_active = true;
+ Q_EMIT activeChanged(m_active);
+ Q_EMIT stateChanged(state());
+ }
+}
+
+void AVFCameraSession::processSessionStopped()
+{
+ qDebugCamera() << Q_FUNC_INFO;
+ if (m_active) {
+ m_active = false;
+ Q_EMIT activeChanged(m_active);
+ Q_EMIT stateChanged(state());
+ }
+}
+
+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:
+ if (m_service->videoDeviceControl()->isDirty()) {
+ if (m_videoInput) {
+ [m_captureSession removeInput:m_videoInput];
+ [m_videoInput release];
+ m_videoInput = nullptr;
+ m_activeCameraInfo = AVFCameraInfo();
+ }
+
+ AVCaptureDevice *videoDevice = m_service->videoDeviceControl()->createCaptureDevice();
+
+ NSError *error = nil;
+ m_videoInput = [AVCaptureDeviceInput
+ deviceInputWithDevice:videoDevice
+ error:&error];
+
+ if (!m_videoInput) {
+ qWarning() << "Failed to create video device input";
+ } else {
+ if ([m_captureSession canAddInput:m_videoInput]) {
+ m_activeCameraInfo = m_cameraDevices.at(m_service->videoDeviceControl()->selectedDevice());
+ [m_videoInput retain];
+ [m_captureSession addInput:m_videoInput];
+ } else {
+ qWarning() << "Failed to connect video device input";
+ }
+ }
+ }
+}
+
+bool AVFCameraSession::applyImageEncoderSettings()
+{
+ if (AVFImageEncoderControl *control = m_service->imageEncoderControl())
+ return control->applySettings();
+
+ return false;
+}
+
+bool AVFCameraSession::applyViewfinderSettings()
+{
+ if (auto *camera = m_service->cameraControl()) {
+ QCamera::CaptureModes currentMode = m_service->cameraControl()->captureMode();
+ QCameraViewfinderSettings vfSettings(camera->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 (currentMode.testFlag(QCamera::CaptureStillImage)) {
+ const QSize imageResolution(m_service->imageEncoderControl()->requestedSettings().resolution());
+ if (!imageResolution.isNull() && imageResolution.isValid())
+ vfSettings.setResolution(imageResolution);
+ }
+
+ camera->applySettings(vfSettings);
+
+ if (m_capturePreviewWindowOutput)
+ m_capturePreviewWindowOutput->setNativeSize(camera->viewfinderSettings().resolution());
+
+ return !vfSettings.isNull();
+ }
+
+ return false;
+}
+
+void AVFCameraSession::addProbe(AVFMediaVideoProbeControl *probe)
+{
+ m_videoProbesMutex.lock();
+ if (probe)
+ m_videoProbes << probe;
+ m_videoProbesMutex.unlock();
+}
+
+void AVFCameraSession::removeProbe(AVFMediaVideoProbeControl *probe)
+{
+ m_videoProbesMutex.lock();
+ m_videoProbes.remove(probe);
+ m_videoProbesMutex.unlock();
+}
+
+FourCharCode AVFCameraSession::defaultCodec()
+{
+ if (!m_defaultCodec) {
+ if (AVCaptureDevice *device = videoCaptureDevice()) {
+ AVCaptureDeviceFormat *format = device.activeFormat;
+ if (!format || !format.formatDescription)
+ return m_defaultCodec;
+ m_defaultCodec = CMVideoFormatDescriptionGetCodecType(format.formatDescription);
+ }
+ }
+ return m_defaultCodec;
+}
+
+void AVFCameraSession::onCameraFrameFetched(const QVideoFrame &frame)
+{
+ Q_EMIT newViewfinderFrame(frame);
+
+ m_videoProbesMutex.lock();
+ QSet<AVFMediaVideoProbeControl *>::const_iterator i = m_videoProbes.constBegin();
+ while (i != m_videoProbes.constEnd()) {
+ (*i)->newFrameProbed(frame);
+ ++i;
+ }
+ m_videoProbesMutex.unlock();
+}
+
+#include "moc_avfcamerasession_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerasession_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerasession_p.h
new file mode 100644
index 000000000..19f7945a3
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerasession_p.h
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFCAMERASESSION_H
+#define AVFCAMERASESSION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qmutex.h>
+#include <QtMultimedia/qcamera.h>
+#include <QVideoFrame>
+
+#import <AVFoundation/AVFoundation.h>
+
+@class AVFCameraSessionObserver;
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraControl;
+class AVFCameraService;
+class AVFCameraRendererControl;
+class AVFMediaVideoProbeControl;
+class AVFCameraWindowControl;
+
+struct AVFCameraInfo
+{
+ AVFCameraInfo() : position(QCamera::UnspecifiedPosition), orientation(0)
+ { }
+
+ QByteArray deviceId;
+ QString description;
+ QCamera::Position position;
+ int orientation;
+};
+
+class AVFCameraSession : public QObject
+{
+ Q_OBJECT
+public:
+ AVFCameraSession(AVFCameraService *service, QObject *parent = nullptr);
+ ~AVFCameraSession();
+
+ static int defaultCameraIndex();
+ static const QList<AVFCameraInfo> &availableCameraDevices();
+ static AVFCameraInfo cameraDeviceInfo(const QByteArray &device);
+ AVFCameraInfo activeCameraInfo() const { return m_activeCameraInfo; }
+
+ void setVideoOutput(AVFCameraRendererControl *output);
+ void setCapturePreviewOutput(AVFCameraWindowControl *output);
+ AVCaptureSession *captureSession() const { return m_captureSession; }
+ AVCaptureDevice *videoCaptureDevice() const;
+
+ QCamera::State state() const;
+ QCamera::State requestedState() const { return m_state; }
+ bool isActive() const { return m_active; }
+
+ void addProbe(AVFMediaVideoProbeControl *probe);
+ void removeProbe(AVFMediaVideoProbeControl *probe);
+ FourCharCode defaultCodec();
+
+ AVCaptureDeviceInput *videoInput() const {return m_videoInput;}
+
+public Q_SLOTS:
+ void setState(QCamera::State state);
+
+ void processRuntimeError();
+ void processSessionStarted();
+ void processSessionStopped();
+
+ void onCaptureModeChanged(QCamera::CaptureModes mode);
+
+ void onCameraFrameFetched(const QVideoFrame &frame);
+
+Q_SIGNALS:
+ void readyToConfigureConnections();
+ void stateChanged(QCamera::State newState);
+ void activeChanged(bool);
+ void newViewfinderFrame(const QVideoFrame &frame);
+ void error(int error, const QString &errorString);
+
+private:
+ static void updateCameraDevices();
+ void attachVideoInputDevice();
+ bool applyImageEncoderSettings();
+ bool applyViewfinderSettings();
+
+ static int m_defaultCameraIndex;
+ static QList<AVFCameraInfo> m_cameraDevices;
+ AVFCameraInfo m_activeCameraInfo;
+
+ AVFCameraService *m_service;
+ AVFCameraRendererControl *m_videoOutput;
+ AVFCameraWindowControl *m_capturePreviewWindowOutput;
+
+ QCamera::State m_state;
+ bool m_active;
+
+ AVCaptureSession *m_captureSession;
+ AVCaptureDeviceInput *m_videoInput;
+ AVFCameraSessionObserver *m_observer;
+
+ QSet<AVFMediaVideoProbeControl *> m_videoProbes;
+ QMutex m_videoProbesMutex;
+
+ FourCharCode m_defaultCodec;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerautility.mm b/src/multimedia/platform/avfoundation/camera/avfcamerautility.mm
new file mode 100644
index 000000000..fe56517df
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerautility.mm
@@ -0,0 +1,575 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcamerautility_p.h"
+#include "avfcameradebug_p.h"
+
+#include <QtCore/qvector.h>
+#include <QtCore/qpair.h>
+#include <private/qmultimediautils_p.h>
+
+#include <functional>
+#include <algorithm>
+#include <limits>
+#include <tuple>
+
+QT_BEGIN_NAMESPACE
+
+AVFPSRange qt_connection_framerates(AVCaptureConnection *videoConnection)
+{
+ Q_ASSERT(videoConnection);
+
+ AVFPSRange newRange;
+ // "The value in the videoMinFrameDuration is equivalent to the reciprocal
+ // of the maximum framerate, the value in the videoMaxFrameDuration is equivalent
+ // to the reciprocal of the minimum framerate."
+ if (videoConnection.supportsVideoMinFrameDuration) {
+ const CMTime cmMin = videoConnection.videoMinFrameDuration;
+ if (CMTimeCompare(cmMin, kCMTimeInvalid)) { // Has some non-default value:
+ if (const Float64 minSeconds = CMTimeGetSeconds(cmMin))
+ newRange.second = 1. / minSeconds;
+ }
+ }
+
+ if (videoConnection.supportsVideoMaxFrameDuration) {
+ const CMTime cmMax = videoConnection.videoMaxFrameDuration;
+ if (CMTimeCompare(cmMax, kCMTimeInvalid)) {
+ if (const Float64 maxSeconds = CMTimeGetSeconds(cmMax))
+ newRange.first = 1. / maxSeconds;
+ }
+ }
+
+ return newRange;
+}
+
+namespace {
+
+inline bool qt_area_sane(const QSize &size)
+{
+ return !size.isNull() && size.isValid()
+ && std::numeric_limits<int>::max() / size.width() >= size.height();
+}
+
+template <template <typename...> class Comp> // std::less or std::greater (or std::equal_to)
+struct ByResolution
+{
+ bool operator() (AVCaptureDeviceFormat *f1, AVCaptureDeviceFormat *f2)const
+ {
+ Q_ASSERT(f1 && f2);
+ const QSize r1(qt_device_format_resolution(f1));
+ const QSize r2(qt_device_format_resolution(f2));
+ // use std::tuple for lexicograpical sorting:
+ const Comp<std::tuple<int, int>> op = {};
+ return op(std::make_tuple(r1.width(), r1.height()),
+ std::make_tuple(r2.width(), r2.height()));
+ }
+};
+
+struct FormatHasNoFPSRange : std::unary_function<AVCaptureDeviceFormat *, bool>
+{
+ bool operator() (AVCaptureDeviceFormat *format)
+ {
+ Q_ASSERT(format);
+ return !format.videoSupportedFrameRateRanges || !format.videoSupportedFrameRateRanges.count;
+ }
+};
+
+Float64 qt_find_min_framerate_distance(AVCaptureDeviceFormat *format, Float64 fps)
+{
+ Q_ASSERT(format && format.videoSupportedFrameRateRanges
+ && format.videoSupportedFrameRateRanges.count);
+
+ AVFrameRateRange *range = [format.videoSupportedFrameRateRanges objectAtIndex:0];
+ Float64 distance = qAbs(range.maxFrameRate - fps);
+ for (NSUInteger i = 1, e = format.videoSupportedFrameRateRanges.count; i < e; ++i) {
+ range = [format.videoSupportedFrameRateRanges objectAtIndex:i];
+ distance = qMin(distance, qAbs(range.maxFrameRate - fps));
+ }
+
+ return distance;
+}
+
+} // Unnamed namespace.
+
+QVector<AVCaptureDeviceFormat *> qt_unique_device_formats(AVCaptureDevice *captureDevice, FourCharCode filter)
+{
+ // 'filter' is the format we prefer if we have duplicates.
+ Q_ASSERT(captureDevice);
+
+ QVector<AVCaptureDeviceFormat *> formats;
+
+ if (!captureDevice.formats || !captureDevice.formats.count)
+ return formats;
+
+ formats.reserve(captureDevice.formats.count);
+ for (AVCaptureDeviceFormat *format in captureDevice.formats) {
+ const QSize resolution(qt_device_format_resolution(format));
+ if (resolution.isNull() || !resolution.isValid())
+ continue;
+ formats << format;
+ }
+
+ if (!formats.size())
+ return formats;
+
+ std::sort(formats.begin(), formats.end(), ByResolution<std::less>());
+
+ QSize size(qt_device_format_resolution(formats[0]));
+ FourCharCode codec = CMVideoFormatDescriptionGetCodecType(formats[0].formatDescription);
+ int last = 0;
+ for (int i = 1; i < formats.size(); ++i) {
+ const QSize nextSize(qt_device_format_resolution(formats[i]));
+ if (nextSize == size) {
+ if (codec == filter)
+ continue;
+ formats[last] = formats[i];
+ } else {
+ ++last;
+ formats[last] = formats[i];
+ size = nextSize;
+ }
+ codec = CMVideoFormatDescriptionGetCodecType(formats[i].formatDescription);
+ }
+ formats.resize(last + 1);
+
+ return formats;
+}
+
+QSize qt_device_format_resolution(AVCaptureDeviceFormat *format)
+{
+ if (!format || !format.formatDescription)
+ return QSize();
+
+ const CMVideoDimensions res = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
+ return QSize(res.width, res.height);
+}
+
+QSize qt_device_format_high_resolution(AVCaptureDeviceFormat *format)
+{
+ Q_ASSERT(format);
+ QSize res;
+#if defined(Q_OS_IOS)
+ const CMVideoDimensions hrDim(format.highResolutionStillImageDimensions);
+ res.setWidth(hrDim.width);
+ res.setHeight(hrDim.height);
+#endif
+ return res;
+}
+
+QVector<AVFPSRange> qt_device_format_framerates(AVCaptureDeviceFormat *format)
+{
+ Q_ASSERT(format);
+
+ QVector<AVFPSRange> qtRanges;
+
+ if (!format.videoSupportedFrameRateRanges || !format.videoSupportedFrameRateRanges.count)
+ return qtRanges;
+
+ qtRanges.reserve(format.videoSupportedFrameRateRanges.count);
+ for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges)
+ qtRanges << AVFPSRange(range.minFrameRate, range.maxFrameRate);
+
+ return qtRanges;
+}
+
+QSize qt_device_format_pixel_aspect_ratio(AVCaptureDeviceFormat *format)
+{
+ Q_ASSERT(format);
+
+ if (!format.formatDescription) {
+ qDebugCamera() << Q_FUNC_INFO << "no format description found";
+ return QSize();
+ }
+
+ const CMVideoDimensions res = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
+ const CGSize resPAR = CMVideoFormatDescriptionGetPresentationDimensions(format.formatDescription, true, false);
+
+ if (qAbs(resPAR.width - res.width) < 1.) {
+ // "Pixel aspect ratio is used to adjust the width, leaving the height alone."
+ return QSize(1, 1);
+ }
+
+ if (!res.width || !resPAR.width)
+ return QSize();
+
+ int n, d;
+ qt_real_to_fraction(resPAR.width > res.width
+ ? res.width / qreal(resPAR.width)
+ : resPAR.width / qreal(res.width),
+ &n, &d);
+
+ return QSize(n, d);
+}
+
+AVCaptureDeviceFormat *qt_find_best_resolution_match(AVCaptureDevice *captureDevice,
+ const QSize &request,
+ FourCharCode filter,
+ bool stillImage)
+{
+ Q_ASSERT(captureDevice);
+ Q_ASSERT(!request.isNull() && request.isValid());
+
+ if (!captureDevice.formats || !captureDevice.formats.count)
+ return nullptr;
+
+ QVector<AVCaptureDeviceFormat *> formats(qt_unique_device_formats(captureDevice, filter));
+
+ for (int i = 0; i < formats.size(); ++i) {
+ AVCaptureDeviceFormat *format = formats[i];
+ if (qt_device_format_resolution(format) == request)
+ return format;
+ // iOS only (still images).
+ if (stillImage && qt_device_format_high_resolution(format) == request)
+ return format;
+ }
+
+ if (!qt_area_sane(request))
+ return nullptr;
+
+ typedef QPair<QSize, AVCaptureDeviceFormat *> FormatPair;
+
+ QVector<FormatPair> pairs; // default|HR sizes
+ pairs.reserve(formats.size());
+
+ for (int i = 0; i < formats.size(); ++i) {
+ AVCaptureDeviceFormat *format = formats[i];
+ const QSize res(qt_device_format_resolution(format));
+ if (!res.isNull() && res.isValid() && qt_area_sane(res))
+ pairs << FormatPair(res, format);
+ const QSize highRes(qt_device_format_high_resolution(format));
+ if (stillImage && !highRes.isNull() && highRes.isValid() && qt_area_sane(highRes))
+ pairs << FormatPair(highRes, format);
+ }
+
+ if (!pairs.size())
+ return nullptr;
+
+ AVCaptureDeviceFormat *best = pairs[0].second;
+ QSize next(pairs[0].first);
+ int wDiff = qAbs(request.width() - next.width());
+ int hDiff = qAbs(request.height() - next.height());
+ const int area = request.width() * request.height();
+ int areaDiff = qAbs(area - next.width() * next.height());
+ for (int i = 1; i < pairs.size(); ++i) {
+ next = pairs[i].first;
+ const int newWDiff = qAbs(next.width() - request.width());
+ const int newHDiff = qAbs(next.height() - request.height());
+ const int newAreaDiff = qAbs(area - next.width() * next.height());
+
+ if ((newWDiff < wDiff && newHDiff < hDiff)
+ || ((newWDiff <= wDiff || newHDiff <= hDiff) && newAreaDiff <= areaDiff)) {
+ wDiff = newWDiff;
+ hDiff = newHDiff;
+ best = pairs[i].second;
+ areaDiff = newAreaDiff;
+ }
+ }
+
+ return best;
+}
+
+AVCaptureDeviceFormat *qt_find_best_framerate_match(AVCaptureDevice *captureDevice,
+ FourCharCode filter,
+ Float64 fps)
+{
+ Q_ASSERT(captureDevice);
+ Q_ASSERT(fps > 0.);
+
+ const qreal epsilon = 0.1;
+
+ QVector<AVCaptureDeviceFormat *>sorted(qt_unique_device_formats(captureDevice, filter));
+ // Sort formats by their resolution in decreasing order:
+ std::sort(sorted.begin(), sorted.end(), ByResolution<std::greater>());
+ // We can use only formats with framerate ranges:
+ sorted.erase(std::remove_if(sorted.begin(), sorted.end(), FormatHasNoFPSRange()), sorted.end());
+
+ if (!sorted.size())
+ return nil;
+
+ for (int i = 0; i < sorted.size(); ++i) {
+ AVCaptureDeviceFormat *format = sorted[i];
+ for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {
+ if (range.maxFrameRate - range.minFrameRate < epsilon) {
+ // On OS X ranges are points (built-in camera).
+ if (qAbs(fps - range.maxFrameRate) < epsilon)
+ return format;
+ }
+
+ if (fps >= range.minFrameRate && fps <= range.maxFrameRate)
+ return format;
+ }
+ }
+
+ Float64 distance = qt_find_min_framerate_distance(sorted[0], fps);
+ AVCaptureDeviceFormat *match = sorted[0];
+ for (int i = 1; i < sorted.size(); ++i) {
+ const Float64 newDistance = qt_find_min_framerate_distance(sorted[i], fps);
+ if (newDistance < distance) {
+ distance = newDistance;
+ match = sorted[i];
+ }
+ }
+
+ return match;
+}
+
+AVFrameRateRange *qt_find_supported_framerate_range(AVCaptureDeviceFormat *format, Float64 fps)
+{
+ Q_ASSERT(format && format.videoSupportedFrameRateRanges
+ && format.videoSupportedFrameRateRanges.count);
+
+ const qreal epsilon = 0.1;
+
+ for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {
+ if (range.maxFrameRate - range.minFrameRate < epsilon) {
+ // On OS X ranges are points (built-in camera).
+ if (qAbs(fps - range.maxFrameRate) < epsilon)
+ return range;
+ }
+
+ if (fps >= range.minFrameRate && fps <= range.maxFrameRate)
+ return range;
+ }
+
+ AVFrameRateRange *match = [format.videoSupportedFrameRateRanges objectAtIndex:0];
+ Float64 distance = qAbs(match.maxFrameRate - fps);
+ for (NSUInteger i = 1, e = format.videoSupportedFrameRateRanges.count; i < e; ++i) {
+ AVFrameRateRange *range = [format.videoSupportedFrameRateRanges objectAtIndex:i];
+ const Float64 newDistance = qAbs(range.maxFrameRate - fps);
+ if (newDistance < distance) {
+ distance = newDistance;
+ match = range;
+ }
+ }
+
+ 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;
+}
+
+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;
+
+ 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;
+}
+
+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."
+ // Instead, use the first item in the supported frame rates.
+#ifdef Q_OS_IOS
+ [captureDevice setActiveVideoMinFrameDuration:minFrameDuration];
+ [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration];
+#elif defined(Q_OS_MACOS)
+ 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];
+
+ if (CMTimeCompare(maxFrameDuration, kCMTimeInvalid))
+ [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration];
+#endif // Q_OS_MACOS
+}
+
+void qt_set_framerate_limits(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection,
+ qreal minFPS, qreal maxFPS)
+{
+ Q_UNUSED(videoConnection);
+ Q_ASSERT(captureDevice);
+ qt_set_framerate_limits(captureDevice, minFPS, maxFPS);
+}
+
+AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection)
+{
+ Q_UNUSED(videoConnection);
+ Q_ASSERT(captureDevice);
+
+ AVFPSRange fps;
+ 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.
+ }
+
+ return fps;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerautility_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerautility_p.h
new file mode 100644
index 000000000..ffa9630a7
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerautility_p.h
@@ -0,0 +1,190 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFCAMERAUTILITY_H
+#define AVFCAMERAUTILITY_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qpair.h>
+#include <QtCore/qsize.h>
+
+#include <AVFoundation/AVFoundation.h>
+
+// In case we have SDK below 10.7/7.0:
+@class AVCaptureDeviceFormat;
+
+QT_BEGIN_NAMESPACE
+
+class AVFConfigurationLock
+{
+public:
+ explicit AVFConfigurationLock(AVCaptureDevice *captureDevice)
+ : m_captureDevice(captureDevice),
+ m_locked(false)
+ {
+ Q_ASSERT(m_captureDevice);
+ NSError *error = nil;
+ m_locked = [m_captureDevice lockForConfiguration:&error];
+ }
+
+ ~AVFConfigurationLock()
+ {
+ if (m_locked)
+ [m_captureDevice unlockForConfiguration];
+ }
+
+ operator bool() const
+ {
+ return m_locked;
+ }
+
+private:
+ Q_DISABLE_COPY(AVFConfigurationLock)
+
+ AVCaptureDevice *m_captureDevice;
+ bool m_locked;
+};
+
+struct AVFObjectDeleter {
+ static void cleanup(NSObject *obj)
+ {
+ if (obj)
+ [obj release];
+ }
+};
+
+template<class T>
+class AVFScopedPointer : public QScopedPointer<NSObject, AVFObjectDeleter>
+{
+public:
+ AVFScopedPointer() {}
+ explicit AVFScopedPointer(T *ptr) : QScopedPointer(ptr) {}
+ operator T*() const
+ {
+ // Quite handy operator to enable Obj-C messages: [ptr someMethod];
+ return data();
+ }
+
+ T *data() const
+ {
+ return static_cast<T *>(QScopedPointer::data());
+ }
+
+ T *take()
+ {
+ return static_cast<T *>(QScopedPointer::take());
+ }
+};
+
+template<>
+class AVFScopedPointer<dispatch_queue_t>
+{
+public:
+ AVFScopedPointer() : m_queue(nullptr) {}
+ explicit AVFScopedPointer(dispatch_queue_t q) : m_queue(q) {}
+
+ ~AVFScopedPointer()
+ {
+ if (m_queue)
+ dispatch_release(m_queue);
+ }
+
+ operator dispatch_queue_t() const
+ {
+ // Quite handy operator to enable Obj-C messages: [ptr someMethod];
+ return m_queue;
+ }
+
+ dispatch_queue_t data() const
+ {
+ return m_queue;
+ }
+
+ void reset(dispatch_queue_t q = nullptr)
+ {
+ if (m_queue)
+ dispatch_release(m_queue);
+ m_queue = q;
+ }
+
+private:
+ dispatch_queue_t m_queue;
+
+ Q_DISABLE_COPY(AVFScopedPointer)
+};
+
+typedef QPair<qreal, qreal> AVFPSRange;
+AVFPSRange qt_connection_framerates(AVCaptureConnection *videoConnection);
+
+QList<AVCaptureDeviceFormat *> qt_unique_device_formats(AVCaptureDevice *captureDevice,
+ FourCharCode preferredFormat);
+QSize qt_device_format_resolution(AVCaptureDeviceFormat *format);
+QSize qt_device_format_high_resolution(AVCaptureDeviceFormat *format);
+QSize qt_device_format_pixel_aspect_ratio(AVCaptureDeviceFormat *format);
+QList<AVFPSRange> qt_device_format_framerates(AVCaptureDeviceFormat *format);
+AVCaptureDeviceFormat *qt_find_best_resolution_match(AVCaptureDevice *captureDevice, const QSize &res,
+ FourCharCode preferredFormat, bool stillImage = true);
+AVCaptureDeviceFormat *qt_find_best_framerate_match(AVCaptureDevice *captureDevice,
+ FourCharCode preferredFormat,
+ 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);
+
+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/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol.mm
new file mode 100644
index 000000000..6c457318d
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol.mm
@@ -0,0 +1,262 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcamerawindowcontrol_p.h"
+
+#import <AVFoundation/AVFoundation.h>
+#import <QuartzCore/CATransaction.h>
+
+#if QT_HAS_INCLUDE(<AppKit/AppKit.h>)
+#import <AppKit/AppKit.h>
+#endif
+
+#if QT_HAS_INCLUDE(<UIKit/UIKit.h>)
+#import <UIKit/UIKit.h>
+#endif
+
+QT_USE_NAMESPACE
+
+AVFCameraWindowControl::AVFCameraWindowControl(QObject *parent)
+ : QVideoWindowControl(parent)
+{
+ setObjectName(QStringLiteral("AVFCameraWindowControl"));
+}
+
+AVFCameraWindowControl::~AVFCameraWindowControl()
+{
+ releaseNativeLayer();
+}
+
+WId AVFCameraWindowControl::winId() const
+{
+ return m_winId;
+}
+
+void AVFCameraWindowControl::setWinId(WId id)
+{
+ if (m_winId == id)
+ return;
+
+ m_winId = id;
+
+ detachNativeLayer();
+ m_nativeView = (NativeView*)m_winId;
+ attachNativeLayer();
+}
+
+QRect AVFCameraWindowControl::displayRect() const
+{
+ return m_displayRect;
+}
+
+void AVFCameraWindowControl::setDisplayRect(const QRect &rect)
+{
+ if (m_displayRect != rect) {
+ m_displayRect = rect;
+ updateCaptureLayerBounds();
+ }
+}
+
+bool AVFCameraWindowControl::isFullScreen() const
+{
+ return m_fullscreen;
+}
+
+void AVFCameraWindowControl::setFullScreen(bool fullscreen)
+{
+ if (m_fullscreen != fullscreen) {
+ m_fullscreen = fullscreen;
+ Q_EMIT fullScreenChanged(fullscreen);
+ }
+}
+
+void AVFCameraWindowControl::repaint()
+{
+ if (m_captureLayer)
+ [m_captureLayer setNeedsDisplay];
+}
+
+QSize AVFCameraWindowControl::nativeSize() const
+{
+ return m_nativeSize;
+}
+
+void AVFCameraWindowControl::setNativeSize(QSize size)
+{
+ if (m_nativeSize != size) {
+ m_nativeSize = size;
+ Q_EMIT nativeSizeChanged();
+ }
+}
+
+Qt::AspectRatioMode AVFCameraWindowControl::aspectRatioMode() const
+{
+ return m_aspectRatioMode;
+}
+
+void AVFCameraWindowControl::setAspectRatioMode(Qt::AspectRatioMode mode)
+{
+ if (m_aspectRatioMode != mode) {
+ m_aspectRatioMode = mode;
+ updateAspectRatio();
+ }
+}
+
+int AVFCameraWindowControl::brightness() const
+{
+ return 0;
+}
+
+void AVFCameraWindowControl::setBrightness(int brightness)
+{
+ if (0 != brightness)
+ qWarning("AVFCameraWindowControl doesn't support changing Brightness");
+}
+
+int AVFCameraWindowControl::contrast() const
+{
+ return 0;
+}
+
+void AVFCameraWindowControl::setContrast(int contrast)
+{
+ if (0 != contrast)
+ qWarning("AVFCameraWindowControl doesn't support changing Contrast");
+}
+
+int AVFCameraWindowControl::hue() const
+{
+ return 0;
+}
+
+void AVFCameraWindowControl::setHue(int hue)
+{
+ if (0 != hue)
+ qWarning("AVFCameraWindowControl doesn't support changing Hue");
+}
+
+int AVFCameraWindowControl::saturation() const
+{
+ return 0;
+}
+
+void AVFCameraWindowControl::setSaturation(int saturation)
+{
+ if (0 != saturation)
+ qWarning("AVFCameraWindowControl doesn't support changing Saturation");
+}
+
+void AVFCameraWindowControl::setLayer(AVCaptureVideoPreviewLayer *capturePreviewLayer)
+{
+ if (m_captureLayer == capturePreviewLayer)
+ return;
+
+ releaseNativeLayer();
+
+ m_captureLayer = capturePreviewLayer;
+
+ if (m_captureLayer)
+ retainNativeLayer();
+}
+
+void AVFCameraWindowControl::updateAspectRatio()
+{
+ if (m_captureLayer) {
+ switch (m_aspectRatioMode) {
+ case Qt::IgnoreAspectRatio:
+ [m_captureLayer setVideoGravity:AVLayerVideoGravityResize];
+ break;
+ case Qt::KeepAspectRatio:
+ [m_captureLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
+ break;
+ case Qt::KeepAspectRatioByExpanding:
+ [m_captureLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void AVFCameraWindowControl::updateCaptureLayerBounds()
+{
+ if (m_captureLayer && m_nativeView) {
+ [CATransaction begin];
+ [CATransaction setDisableActions: YES]; // disable animation/flicks
+ m_captureLayer.frame = m_displayRect.toCGRect();
+ [CATransaction commit];
+ }
+}
+
+void AVFCameraWindowControl::retainNativeLayer()
+{
+ [m_captureLayer retain];
+
+ updateAspectRatio();
+ attachNativeLayer();
+}
+
+void AVFCameraWindowControl::releaseNativeLayer()
+{
+ if (m_captureLayer) {
+ detachNativeLayer();
+ [m_captureLayer release];
+ m_captureLayer = nullptr;
+ }
+}
+
+void AVFCameraWindowControl::attachNativeLayer()
+{
+ if (m_captureLayer && m_nativeView) {
+#if defined(Q_OS_MACOS)
+ m_nativeView.wantsLayer = YES;
+#endif
+ CALayer *nativeLayer = m_nativeView.layer;
+ [nativeLayer addSublayer:m_captureLayer];
+ updateCaptureLayerBounds();
+ }
+}
+
+void AVFCameraWindowControl::detachNativeLayer()
+{
+ if (m_captureLayer && m_nativeView)
+ [m_captureLayer removeFromSuperlayer];
+}
+
+#include "moc_avfcamerawindowcontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol_p.h
new file mode 100644
index 000000000..d1a950e38
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol_p.h
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFCAMERAWINDOWCONTROL_H
+#define AVFCAMERAWINDOWCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QVideoWindowControl>
+
+@class AVCaptureVideoPreviewLayer;
+#if defined(Q_OS_MACOS)
+@class NSView;
+typedef NSView NativeView;
+#else
+@class UIView;
+typedef UIView NativeView;
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraWindowControl : public QVideoWindowControl
+{
+ Q_OBJECT
+public:
+ AVFCameraWindowControl(QObject *parent = nullptr);
+ virtual ~AVFCameraWindowControl() override;
+
+ // QVideoWindowControl interface
+public:
+ WId winId() const override;
+ void setWinId(WId id) override;
+
+ QRect displayRect() const override;
+ void setDisplayRect(const QRect &rect) override;
+
+ bool isFullScreen() const override;
+ void setFullScreen(bool fullScreen) override;
+
+ void repaint() override;
+
+ QSize nativeSize() const override;
+
+ Qt::AspectRatioMode aspectRatioMode() const override;
+ void setAspectRatioMode(Qt::AspectRatioMode mode) override;
+
+ int brightness() const override;
+ void setBrightness(int brightness) override;
+
+ int contrast() const override;
+ void setContrast(int contrast) override;
+
+ int hue() const override;
+ void setHue(int hue) override;
+
+ int saturation() const override;
+ void setSaturation(int saturation) override;
+
+ // AVF Camera implementation details
+ void setNativeSize(QSize size);
+ void setLayer(AVCaptureVideoPreviewLayer *capturePreviewLayer);
+
+private:
+ void updateAspectRatio();
+ void updateCaptureLayerBounds();
+
+ void retainNativeLayer();
+ void releaseNativeLayer();
+
+ void attachNativeLayer();
+ void detachNativeLayer();
+
+ WId m_winId{0};
+ QRect m_displayRect;
+ bool m_fullscreen{false};
+ Qt::AspectRatioMode m_aspectRatioMode{Qt::IgnoreAspectRatio};
+ QSize m_nativeSize;
+ AVCaptureVideoPreviewLayer *m_captureLayer{nullptr};
+ NativeView *m_nativeView{nullptr};
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFCAMERAWINDOWCONTROL_H
diff --git a/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol.mm b/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol.mm
new file mode 100644
index 000000000..b019cf2ef
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol.mm
@@ -0,0 +1,278 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcameradebug_p.h"
+#include "avfimagecapturecontrol_p.h"
+#include "avfcameraservice_p.h"
+#include "avfcamerautility_p.h"
+#include "avfcameracontrol_p.h"
+#include <private/qmemoryvideobuffer_p.h>
+
+#include <QtCore/qurl.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qbuffer.h>
+#include <QtConcurrent/qtconcurrentrun.h>
+#include <QtGui/qimagereader.h>
+
+QT_USE_NAMESPACE
+
+AVFImageCaptureControl::AVFImageCaptureControl(AVFCameraService *service, QObject *parent)
+ : QCameraImageCaptureControl(parent)
+ , m_service(service)
+ , m_session(service->session())
+ , m_cameraControl(service->cameraControl())
+ , m_ready(false)
+ , m_lastCaptureId(0)
+ , m_videoConnection(nil)
+{
+ Q_UNUSED(service);
+ m_stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
+
+ NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
+ AVVideoCodecJPEG, AVVideoCodecKey, nil];
+
+ [m_stillImageOutput setOutputSettings:outputSettings];
+ [outputSettings release];
+ connect(m_cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(updateReadyStatus()));
+ 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,
+ Qt::DirectConnection);
+}
+
+AVFImageCaptureControl::~AVFImageCaptureControl()
+{
+}
+
+bool AVFImageCaptureControl::isReadyForCapture() const
+{
+ return m_videoConnection &&
+ m_cameraControl->captureMode().testFlag(QCamera::CaptureStillImage) &&
+ m_cameraControl->status() == QCamera::ActiveStatus;
+}
+
+void AVFImageCaptureControl::updateReadyStatus()
+{
+ if (m_ready != isReadyForCapture()) {
+ m_ready = !m_ready;
+ qDebugCamera() << "ReadyToCapture status changed:" << m_ready;
+ Q_EMIT readyForCaptureChanged(m_ready);
+ }
+}
+
+int AVFImageCaptureControl::capture(const QString &fileName)
+{
+ m_lastCaptureId++;
+
+ if (!isReadyForCapture()) {
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(int, m_lastCaptureId),
+ Q_ARG(int, QCameraImageCapture::NotReadyError),
+ Q_ARG(QString, tr("Camera not ready")));
+ return m_lastCaptureId;
+ }
+
+ auto destination = m_service->imageCaptureControl()->captureDestination();
+ QString actualFileName;
+ if (destination & QCameraImageCapture::CaptureToFile) {
+ actualFileName = m_storageLocation.generateFileName(fileName,
+ QCamera::CaptureStillImage,
+ QLatin1String("img_"),
+ QLatin1String("jpg"));
+
+ qDebugCamera() << "Capture image to" << actualFileName;
+ }
+
+ CaptureRequest request = { m_lastCaptureId, QSharedPointer<QSemaphore>::create()};
+ m_requestsMutex.lock();
+ m_captureRequests.enqueue(request);
+ m_requestsMutex.unlock();
+
+ [m_stillImageOutput captureStillImageAsynchronouslyFromConnection:m_videoConnection
+ completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
+
+ if (error) {
+ QStringList messageParts;
+ messageParts << QString::fromUtf8([[error localizedDescription] UTF8String]);
+ messageParts << QString::fromUtf8([[error localizedFailureReason] UTF8String]);
+ messageParts << QString::fromUtf8([[error localizedRecoverySuggestion] UTF8String]);
+
+ QString errorMessage = messageParts.join(" ");
+ qDebugCamera() << "Image capture failed:" << errorMessage;
+
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(int, request.captureId),
+ Q_ARG(int, QCameraImageCapture::ResourceError),
+ Q_ARG(QString, errorMessage));
+ return;
+ }
+
+ // Wait for the preview to be generated before saving the JPEG (but only
+ // if we have AVFCameraRendererControl attached).
+ // It is possible to stop camera immediately after trying to capture an
+ // image; this can result in a blocked callback's thread, waiting for a
+ // new viewfinder's frame to arrive/semaphore to be released. It is also
+ // unspecified on which thread this callback gets executed, (probably it's
+ // not the same thread that initiated a capture and stopped the camera),
+ // so we cannot reliably check the camera's status. Instead, we wait
+ // with a timeout and treat a failure to acquire a semaphore as an error.
+ if (!m_service->videoOutput() || request.previewReady->tryAcquire(1, 1000)) {
+ qDebugCamera() << "Image capture completed";
+
+ NSData *nsJpgData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
+ QByteArray jpgData = QByteArray::fromRawData((const char *)[nsJpgData bytes], [nsJpgData length]);
+
+ if (destination & QCameraImageCapture::CaptureToBuffer) {
+ QBuffer data(&jpgData);
+ QImageReader reader(&data, "JPEG");
+ QSize size = reader.size();
+ QVideoFrame frame(new QMemoryVideoBuffer(QByteArray(jpgData.constData(), jpgData.size()), -1), size, QVideoFrame::Format_Jpeg);
+ QMetaObject::invokeMethod(this, "imageAvailable", Qt::QueuedConnection,
+ Q_ARG(int, request.captureId),
+ Q_ARG(QVideoFrame, frame));
+ }
+
+ if (!(destination & QCameraImageCapture::CaptureToFile))
+ return;
+
+ QFile f(actualFileName);
+ if (f.open(QFile::WriteOnly)) {
+ if (f.write(jpgData) != -1) {
+ QMetaObject::invokeMethod(this, "imageSaved", Qt::QueuedConnection,
+ Q_ARG(int, request.captureId),
+ Q_ARG(QString, actualFileName));
+ } else {
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(int, request.captureId),
+ Q_ARG(int, QCameraImageCapture::OutOfSpaceError),
+ Q_ARG(QString, f.errorString()));
+ }
+ } else {
+ QString errorMessage = tr("Could not open destination file:\n%1").arg(actualFileName);
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(int, request.captureId),
+ Q_ARG(int, QCameraImageCapture::ResourceError),
+ Q_ARG(QString, errorMessage));
+ }
+ } else {
+ const QLatin1String errorMessage("Image capture failed: timed out waiting"
+ " for a preview frame.");
+ qDebugCamera() << errorMessage;
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(int, request.captureId),
+ Q_ARG(int, QCameraImageCapture::ResourceError),
+ Q_ARG(QString, errorMessage));
+ }
+ }];
+
+ return request.captureId;
+}
+
+void AVFImageCaptureControl::onNewViewfinderFrame(const QVideoFrame &frame)
+{
+ QMutexLocker locker(&m_requestsMutex);
+
+ if (m_captureRequests.isEmpty())
+ return;
+
+ CaptureRequest request = m_captureRequests.dequeue();
+ Q_EMIT imageExposed(request.captureId);
+
+ QtConcurrent::run(&AVFImageCaptureControl::makeCapturePreview, this,
+ request,
+ frame,
+ 0 /* rotation */);
+}
+
+void AVFImageCaptureControl::makeCapturePreview(CaptureRequest request,
+ const QVideoFrame &frame,
+ int rotation)
+{
+ QTransform transform;
+ transform.rotate(rotation);
+
+ Q_EMIT imageCaptured(request.captureId, frame.image().transformed(transform));
+
+ request.previewReady->release();
+}
+
+void AVFImageCaptureControl::cancelCapture()
+{
+ //not supported
+}
+
+QCameraImageCapture::CaptureDestinations AVFImageCaptureControl::captureDestination() const
+{
+ return m_destination;
+}
+
+void AVFImageCaptureControl::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination)
+{
+ if (m_destination != destination) {
+ m_destination = destination;
+ updateCaptureConnection();
+ }
+}
+
+void AVFImageCaptureControl::updateCaptureConnection()
+{
+ if (m_session->videoCaptureDevice()
+ && m_cameraControl->captureMode().testFlag(QCamera::CaptureStillImage)) {
+ qDebugCamera() << Q_FUNC_INFO;
+ AVCaptureSession *captureSession = m_session->captureSession();
+
+ if (![captureSession.outputs containsObject:m_stillImageOutput]) {
+ if ([captureSession canAddOutput:m_stillImageOutput]) {
+ // Lock the video capture device to make sure the active format is not reset
+ const AVFConfigurationLock lock(m_session->videoCaptureDevice());
+ [captureSession addOutput:m_stillImageOutput];
+ m_videoConnection = [m_stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
+ updateReadyStatus();
+ }
+ } else {
+ m_videoConnection = [m_stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
+ }
+ }
+}
+
+#include "moc_avfimagecapturecontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol_p.h
new file mode 100644
index 000000000..3c781a475
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol_p.h
@@ -0,0 +1,116 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFIMAGECAPTURECONTROL_H
+#define AVFIMAGECAPTURECONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#import <AVFoundation/AVFoundation.h>
+
+#include <QtCore/qqueue.h>
+#include <QtCore/qsemaphore.h>
+#include <QtCore/qsharedpointer.h>
+#include <QtMultimedia/qcameraimagecapturecontrol.h>
+#include "avfcamerasession_p.h"
+#include "avfstoragelocation_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class AVFImageCaptureControl : public QCameraImageCaptureControl
+{
+Q_OBJECT
+public:
+ struct CaptureRequest {
+ int captureId;
+ QSharedPointer<QSemaphore> previewReady;
+ };
+
+ AVFImageCaptureControl(AVFCameraService *service, QObject *parent = nullptr);
+ ~AVFImageCaptureControl();
+
+ bool isReadyForCapture() const override;
+
+ QCameraImageCapture::DriveMode driveMode() const override { return QCameraImageCapture::SingleImageCapture; }
+ void setDriveMode(QCameraImageCapture::DriveMode ) override {}
+ AVCaptureStillImageOutput *stillImageOutput() const {return m_stillImageOutput;}
+
+ int capture(const QString &fileName) override;
+ void cancelCapture() override;
+
+ QCameraImageCapture::CaptureDestinations captureDestination() const override;
+ void setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) override;
+
+private Q_SLOTS:
+ void updateCaptureConnection();
+ void updateReadyStatus();
+ void onNewViewfinderFrame(const QVideoFrame &frame);
+
+private:
+ void makeCapturePreview(CaptureRequest request, const QVideoFrame &frame, int rotation);
+
+ AVFCameraService *m_service;
+ AVFCameraSession *m_session;
+ AVFCameraControl *m_cameraControl;
+ bool m_ready;
+ int m_lastCaptureId;
+ AVCaptureStillImageOutput *m_stillImageOutput;
+ AVCaptureConnection *m_videoConnection;
+ AVFStorageLocation m_storageLocation;
+
+ QMutex m_requestsMutex;
+ QQueue<CaptureRequest> m_captureRequests;
+
+ QCameraImageCapture::CaptureDestinations m_destination = QCameraImageCapture::CaptureToFile;
+};
+
+Q_DECLARE_TYPEINFO(AVFImageCaptureControl::CaptureRequest, Q_PRIMITIVE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol.mm b/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol.mm
new file mode 100644
index 000000000..113fdb62b
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol.mm
@@ -0,0 +1,239 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfimageencodercontrol_p.h"
+#include "avfimagecapturecontrol_p.h"
+#include "avfcamerautility_p.h"
+#include "avfcamerasession_p.h"
+#include "avfcameraservice_p.h"
+#include "avfcameradebug_p.h"
+#include "avfcameracontrol_p.h"
+
+#include <QtMultimedia/qmediaencodersettings.h>
+
+#include <QtCore/qdebug.h>
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_BEGIN_NAMESPACE
+
+AVFImageEncoderControl::AVFImageEncoderControl(AVFCameraService *service)
+ : m_service(service)
+{
+ Q_ASSERT(service);
+}
+
+QStringList AVFImageEncoderControl::supportedImageCodecs() const
+{
+ return QStringList() << QLatin1String("jpeg");
+}
+
+QString AVFImageEncoderControl::imageCodecDescription(const QString &codecName) const
+{
+ if (codecName == QLatin1String("jpeg"))
+ return tr("JPEG image");
+
+ return QString();
+}
+
+QList<QSize> AVFImageEncoderControl::supportedResolutions(const QImageEncoderSettings &settings,
+ bool *continuous) const
+{
+ Q_UNUSED(settings);
+
+ QList<QSize> resolutions;
+
+ if (!videoCaptureDeviceIsValid())
+ return resolutions;
+
+ AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
+ const QVector<AVCaptureDeviceFormat *> formats(qt_unique_device_formats(captureDevice,
+ m_service->session()->defaultCodec()));
+
+ for (int i = 0; i < formats.size(); ++i) {
+ AVCaptureDeviceFormat *format = formats[i];
+
+ const QSize res(qt_device_format_resolution(format));
+ if (!res.isNull() && res.isValid())
+ resolutions << res;
+#ifdef Q_OS_IOS
+ // From Apple's docs (iOS):
+ // By default, AVCaptureStillImageOutput emits images with the same dimensions as
+ // its source AVCaptureDevice instance’s activeFormat.formatDescription. However,
+ // if you set this property to YES, the receiver emits still images at the capture
+ // device’s highResolutionStillImageDimensions value.
+ const QSize hrRes(qt_device_format_high_resolution(format));
+ if (!hrRes.isNull() && hrRes.isValid())
+ resolutions << res;
+#endif
+ }
+
+ if (continuous)
+ *continuous = false;
+
+ return resolutions;
+}
+
+QImageEncoderSettings AVFImageEncoderControl::requestedSettings() const
+{
+ return m_settings;
+}
+
+QImageEncoderSettings AVFImageEncoderControl::imageSettings() const
+{
+ QImageEncoderSettings settings;
+
+ if (!videoCaptureDeviceIsValid())
+ return settings;
+
+ AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
+ if (!captureDevice.activeFormat) {
+ qDebugCamera() << Q_FUNC_INFO << "no active format";
+ return settings;
+ }
+
+ QSize res(qt_device_format_resolution(captureDevice.activeFormat));
+#ifdef Q_OS_IOS
+ if (!m_service->imageCaptureControl() || !m_service->imageCaptureControl()->stillImageOutput()) {
+ qDebugCamera() << Q_FUNC_INFO << "no still image output";
+ return settings;
+ }
+
+ AVCaptureStillImageOutput *stillImageOutput = m_service->imageCaptureControl()->stillImageOutput();
+ if (stillImageOutput.highResolutionStillImageOutputEnabled)
+ res = qt_device_format_high_resolution(captureDevice.activeFormat);
+#endif
+ if (res.isNull() || !res.isValid()) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to exctract the image resolution";
+ return settings;
+ }
+
+ settings.setResolution(res);
+
+ settings.setCodec(QLatin1String("jpeg"));
+
+ return settings;
+}
+
+void AVFImageEncoderControl::setImageSettings(const QImageEncoderSettings &settings)
+{
+ if (m_settings == settings)
+ return;
+
+ m_settings = settings;
+ applySettings();
+}
+
+bool AVFImageEncoderControl::applySettings()
+{
+ if (!videoCaptureDeviceIsValid())
+ return false;
+
+ AVFCameraSession *session = m_service->session();
+ if (!session || (session->state() != QCamera::ActiveState
+ && session->state() != QCamera::LoadedState)
+ || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureStillImage)) {
+ return false;
+ }
+
+ if (!m_service->imageCaptureControl()
+ || !m_service->imageCaptureControl()->stillImageOutput()) {
+ qDebugCamera() << Q_FUNC_INFO << "no still image output";
+ return false;
+ }
+
+ if (m_settings.codec().size()
+ && m_settings.codec() != QLatin1String("jpeg")) {
+ qDebugCamera() << Q_FUNC_INFO << "unsupported codec:" << m_settings.codec();
+ return false;
+ }
+
+ QSize res(m_settings.resolution());
+ if (res.isNull()) {
+ qDebugCamera() << Q_FUNC_INFO << "invalid resolution:" << res;
+ return false;
+ }
+
+ if (!res.isValid()) {
+ // Invalid == default value.
+ // Here we could choose the best format available, but
+ // activeFormat is already equal to 'preset high' by default,
+ // which is good enough, otherwise we can end in some format with low framerates.
+ return false;
+ }
+
+ bool activeFormatChanged = false;
+
+ AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
+ AVCaptureDeviceFormat *match = qt_find_best_resolution_match(captureDevice, res,
+ m_service->session()->defaultCodec());
+
+ if (!match) {
+ qDebugCamera() << Q_FUNC_INFO << "unsupported resolution:" << res;
+ return false;
+ }
+
+ activeFormatChanged = qt_set_active_format(captureDevice, match, true);
+
+#ifdef Q_OS_IOS
+ AVCaptureStillImageOutput *imageOutput = m_service->imageCaptureControl()->stillImageOutput();
+ if (res == qt_device_format_high_resolution(captureDevice.activeFormat))
+ imageOutput.highResolutionStillImageOutputEnabled = YES;
+ else
+ imageOutput.highResolutionStillImageOutputEnabled = NO;
+#endif
+
+ return activeFormatChanged;
+}
+
+bool AVFImageEncoderControl::videoCaptureDeviceIsValid() const
+{
+ if (!m_service->session() || !m_service->session()->videoCaptureDevice())
+ return false;
+
+ AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice();
+ if (!captureDevice.formats || !captureDevice.formats.count)
+ return false;
+
+ return true;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_avfimageencodercontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol_p.h
new file mode 100644
index 000000000..8c2742d35
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol_p.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFIMAGEENCODERCONTROL_H
+#define AVFIMAGEENCODERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/qmediaencodersettings.h>
+#include <QtMultimedia/qimageencodercontrol.h>
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qlist.h>
+
+@class AVCaptureDeviceFormat;
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraService;
+
+class AVFImageEncoderControl : public QImageEncoderControl
+{
+ Q_OBJECT
+
+ friend class AVFCameraSession;
+public:
+ AVFImageEncoderControl(AVFCameraService *service);
+
+ QStringList supportedImageCodecs() const override;
+ QString imageCodecDescription(const QString &codecName) const override;
+ QList<QSize> supportedResolutions(const QImageEncoderSettings &settings,
+ bool *continuous) const override;
+ QImageEncoderSettings imageSettings() const override;
+ void setImageSettings(const QImageEncoderSettings &settings) override;
+
+ QImageEncoderSettings requestedSettings() const;
+
+private:
+ AVFCameraService *m_service;
+ QImageEncoderSettings m_settings;
+
+ bool applySettings();
+ bool videoCaptureDeviceIsValid() const;
+};
+
+QSize qt_image_high_resolution(AVCaptureDeviceFormat *fomat);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter.mm b/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter.mm
new file mode 100644
index 000000000..57c5cb8c5
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter.mm
@@ -0,0 +1,514 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfaudioinputselectorcontrol.h"
+#include "avfmediarecordercontrol_ios.h"
+#include "avfcamerarenderercontrol.h"
+#include "avfmediaassetwriter.h"
+#include "avfcameraservice.h"
+#include "avfcamerasession.h"
+#include "avfcameradebug.h"
+#include "avfmediacontainercontrol.h"
+
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qatomic.h>
+
+QT_USE_NAMESPACE
+
+namespace {
+
+bool qt_camera_service_isValid(AVFCameraService *service)
+{
+ if (!service || !service->session())
+ return false;
+
+ AVFCameraSession *session = service->session();
+ if (!session->captureSession())
+ return false;
+
+ if (!session->videoInput())
+ return false;
+
+ if (!service->videoOutput()
+ || !service->videoOutput()->videoDataOutput()) {
+ return false;
+ }
+
+ return true;
+}
+
+enum WriterState
+{
+ WriterStateIdle,
+ WriterStateActive,
+ WriterStateAborted
+};
+
+using AVFAtomicInt64 = QAtomicInteger<qint64>;
+
+} // unnamed namespace
+
+@interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) (PrivateAPI)
+- (bool)addAudioCapture;
+- (bool)addWriterInputs;
+- (void)setQueues;
+- (void)updateDuration:(CMTime)newTimeStamp;
+@end
+
+@implementation QT_MANGLE_NAMESPACE(AVFMediaAssetWriter)
+{
+@private
+ AVFCameraService *m_service;
+
+ AVFScopedPointer<AVAssetWriterInput> m_cameraWriterInput;
+ AVFScopedPointer<AVCaptureDeviceInput> m_audioInput;
+ AVFScopedPointer<AVCaptureAudioDataOutput> m_audioOutput;
+ AVFScopedPointer<AVAssetWriterInput> m_audioWriterInput;
+
+ AVCaptureDevice *m_audioCaptureDevice;
+
+ // Queue to write sample buffers:
+ AVFScopedPointer<dispatch_queue_t> m_writerQueue;
+ // High priority serial queue for video output:
+ AVFScopedPointer<dispatch_queue_t> m_videoQueue;
+ // Serial queue for audio output:
+ AVFScopedPointer<dispatch_queue_t> m_audioQueue;
+
+ AVFScopedPointer<AVAssetWriter> m_assetWriter;
+
+ AVFMediaRecorderControlIOS *m_delegate;
+
+ bool m_setStartTime;
+
+ QAtomicInt m_state;
+
+ CMTime m_startTime;
+ CMTime m_lastTimeStamp;
+
+ NSDictionary *m_audioSettings;
+ NSDictionary *m_videoSettings;
+
+ AVFAtomicInt64 m_durationInMs;
+}
+
+- (id)initWithDelegate:(AVFMediaRecorderControlIOS *)delegate
+{
+ Q_ASSERT(delegate);
+
+ if (self = [super init]) {
+ m_delegate = delegate;
+ m_setStartTime = true;
+ m_state.storeRelaxed(WriterStateIdle);
+ m_startTime = kCMTimeInvalid;
+ m_lastTimeStamp = kCMTimeInvalid;
+ m_durationInMs.storeRelaxed(0);
+ m_audioSettings = nil;
+ m_videoSettings = nil;
+ }
+
+ return self;
+}
+
+- (bool)setupWithFileURL:(NSURL *)fileURL
+ cameraService:(AVFCameraService *)service
+ audioSettings:(NSDictionary *)audioSettings
+ videoSettings:(NSDictionary *)videoSettings
+ transform:(CGAffineTransform)transform
+{
+ Q_ASSERT(fileURL);
+
+ if (!qt_camera_service_isValid(service)) {
+ qDebugCamera() << Q_FUNC_INFO << "invalid camera service";
+ return false;
+ }
+
+ m_service = service;
+ m_audioSettings = audioSettings;
+ m_videoSettings = videoSettings;
+
+ m_writerQueue.reset(dispatch_queue_create("asset-writer-queue", DISPATCH_QUEUE_SERIAL));
+ if (!m_writerQueue) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create an asset writer's queue";
+ return false;
+ }
+
+ m_videoQueue.reset(dispatch_queue_create("video-output-queue", DISPATCH_QUEUE_SERIAL));
+ if (!m_videoQueue) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create video queue";
+ return false;
+ }
+ dispatch_set_target_queue(m_videoQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
+ m_audioQueue.reset(dispatch_queue_create("audio-output-queue", DISPATCH_QUEUE_SERIAL));
+ if (!m_audioQueue) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create audio queue";
+ // But we still can write video!
+ }
+
+ m_assetWriter.reset([[AVAssetWriter alloc] initWithURL:fileURL
+ fileType:m_service->mediaContainerControl()->fileType()
+ error:nil]);
+ if (!m_assetWriter) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create asset writer";
+ return false;
+ }
+
+ bool audioCaptureOn = false;
+
+ if (m_audioQueue)
+ audioCaptureOn = [self addAudioCapture];
+
+ if (![self addWriterInputs]) {
+ if (audioCaptureOn) {
+ AVCaptureSession *session = m_service->session()->captureSession();
+ [session removeOutput:m_audioOutput];
+ [session removeInput:m_audioInput];
+ m_audioOutput.reset();
+ m_audioInput.reset();
+ m_audioCaptureDevice = 0;
+ }
+ m_assetWriter.reset();
+ return false;
+ }
+
+ m_cameraWriterInput.data().transform = transform;
+
+ // Ready to start ...
+ return true;
+}
+
+- (void)start
+{
+ [self setQueues];
+
+ m_setStartTime = true;
+
+ m_state.storeRelease(WriterStateActive);
+
+ [m_assetWriter startWriting];
+ AVCaptureSession *session = m_service->session()->captureSession();
+ if (!session.running)
+ [session startRunning];
+}
+
+- (void)stop
+{
+ if (m_state.loadAcquire() != WriterStateActive)
+ return;
+
+ if ([m_assetWriter status] != AVAssetWriterStatusWriting)
+ return;
+
+ // Do this here so that -
+ // 1. '-abort' should not try calling finishWriting again and
+ // 2. async block (see below) will know if recorder control was deleted
+ // before the block's execution:
+ m_state.storeRelease(WriterStateIdle);
+ // Now, since we have to ensure no sample buffers are
+ // appended after a call to finishWriting, we must
+ // ensure writer's queue sees this change in m_state
+ // _before_ we call finishWriting:
+ dispatch_sync(m_writerQueue, ^{});
+ // Done, but now we also want to prevent video queue
+ // from updating our viewfinder:
+ dispatch_sync(m_videoQueue, ^{});
+
+ // Now we're safe to stop:
+ [m_assetWriter finishWritingWithCompletionHandler:^{
+ // This block is async, so by the time it's executed,
+ // it's possible that render control was deleted already ...
+ if (m_state.loadAcquire() == WriterStateAborted)
+ return;
+
+ AVCaptureSession *session = m_service->session()->captureSession();
+ if (session.running)
+ [session stopRunning];
+ [session removeOutput:m_audioOutput];
+ [session removeInput:m_audioInput];
+ QMetaObject::invokeMethod(m_delegate, "assetWriterFinished", Qt::QueuedConnection);
+ }];
+}
+
+- (void)abort
+{
+ // -abort is to be called from recorder control's dtor.
+
+ if (m_state.fetchAndStoreRelease(WriterStateAborted) != WriterStateActive) {
+ // Not recording, nothing to stop.
+ return;
+ }
+
+ // From Apple's docs:
+ // "To guarantee that all sample buffers are successfully written,
+ // you must ensure that all calls to appendSampleBuffer: and
+ // appendPixelBuffer:withPresentationTime: have returned before
+ // invoking this method."
+ //
+ // The only way we can ensure this is:
+ dispatch_sync(m_writerQueue, ^{});
+ // At this point next block (if any) on the writer's queue
+ // will see m_state preventing it from any further processing.
+ dispatch_sync(m_videoQueue, ^{});
+ // After this point video queue will not try to modify our
+ // viewfider, so we're safe to delete now.
+
+ [m_assetWriter finishWritingWithCompletionHandler:^{
+ }];
+}
+
+- (void)setStartTimeFrom:(CMSampleBufferRef)sampleBuffer
+{
+ // Writer's queue only.
+ Q_ASSERT(m_setStartTime);
+ Q_ASSERT(sampleBuffer);
+
+ if (m_state.loadAcquire() != WriterStateActive)
+ return;
+
+ QMetaObject::invokeMethod(m_delegate, "assetWriterStarted", Qt::QueuedConnection);
+
+ m_durationInMs.storeRelease(0);
+ m_startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
+ m_lastTimeStamp = m_startTime;
+ [m_assetWriter startSessionAtSourceTime:m_startTime];
+ m_setStartTime = false;
+}
+
+- (void)writeVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
+{
+ // This code is executed only on a writer's queue.
+ Q_ASSERT(sampleBuffer);
+
+ if (m_state.loadAcquire() == WriterStateActive) {
+ if (m_setStartTime)
+ [self setStartTimeFrom:sampleBuffer];
+
+ if (m_cameraWriterInput.data().readyForMoreMediaData) {
+ [self updateDuration:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
+ [m_cameraWriterInput appendSampleBuffer:sampleBuffer];
+ }
+ }
+
+ CFRelease(sampleBuffer);
+}
+
+- (void)writeAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer
+{
+ Q_ASSERT(sampleBuffer);
+
+ // This code is executed only on a writer's queue.
+ if (m_state.loadAcquire() == WriterStateActive) {
+ if (m_setStartTime)
+ [self setStartTimeFrom:sampleBuffer];
+
+ if (m_audioWriterInput.data().readyForMoreMediaData) {
+ [self updateDuration:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
+ [m_audioWriterInput appendSampleBuffer:sampleBuffer];
+ }
+ }
+
+ CFRelease(sampleBuffer);
+}
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput
+ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
+ fromConnection:(AVCaptureConnection *)connection
+{
+ Q_UNUSED(connection);
+
+ if (m_state.loadAcquire() != WriterStateActive)
+ return;
+
+ if (!CMSampleBufferDataIsReady(sampleBuffer)) {
+ qDebugCamera() << Q_FUNC_INFO << "sample buffer is not ready, skipping.";
+ return;
+ }
+
+ CFRetain(sampleBuffer);
+
+ if (captureOutput != m_audioOutput.data()) {
+ if (m_state.loadRelaxed() != WriterStateActive) {
+ CFRelease(sampleBuffer);
+ return;
+ }
+ // Find renderercontrol's delegate and invoke its method to
+ // show updated viewfinder's frame.
+ if (m_service && m_service->videoOutput()) {
+ NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> *vfDelegate =
+ (NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> *)m_service->videoOutput()->captureDelegate();
+ if (vfDelegate)
+ [vfDelegate captureOutput:nil didOutputSampleBuffer:sampleBuffer fromConnection:nil];
+ }
+
+ dispatch_async(m_writerQueue, ^{
+ [self writeVideoSampleBuffer:sampleBuffer];
+ });
+ } else {
+ dispatch_async(m_writerQueue, ^{
+ [self writeAudioSampleBuffer:sampleBuffer];
+ });
+ }
+}
+
+- (bool)addAudioCapture
+{
+ Q_ASSERT(m_service && m_service->session() && m_service->session()->captureSession());
+
+ if (!m_service->audioInputSelectorControl())
+ return false;
+
+ AVCaptureSession *captureSession = m_service->session()->captureSession();
+
+ 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: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 {
+ [captureSession addInput:m_audioInput];
+ }
+ }
+
+
+ m_audioOutput.reset([[AVCaptureAudioDataOutput alloc] init]);
+ if (m_audioOutput.data() && [captureSession canAddOutput:m_audioOutput]) {
+ [captureSession addOutput:m_audioOutput];
+ } 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;
+ }
+
+ return true;
+}
+
+- (bool)addWriterInputs
+{
+ Q_ASSERT(m_service && m_service->videoOutput()
+ && m_service->videoOutput()->videoDataOutput());
+ Q_ASSERT(m_assetWriter.data());
+
+ 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;
+ }
+
+ if ([m_assetWriter canAddInput:m_cameraWriterInput]) {
+ [m_assetWriter addInput:m_cameraWriterInput];
+ } else {
+ qDebugCamera() << Q_FUNC_INFO << "failed to add camera writer input";
+ m_cameraWriterInput.reset();
+ return false;
+ }
+
+ m_cameraWriterInput.data().expectsMediaDataInRealTime = YES;
+
+ if (m_audioOutput.data()) {
+ 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.
+ } else if ([m_assetWriter canAddInput:m_audioWriterInput]) {
+ [m_assetWriter addInput:m_audioWriterInput];
+ m_audioWriterInput.data().expectsMediaDataInRealTime = YES;
+ } else {
+ qDebugCamera() << Q_FUNC_INFO << "failed to add audio writer input";
+ m_audioWriterInput.reset();
+ // We can (still) write video though ...
+ }
+ }
+
+ return true;
+}
+
+- (void)setQueues
+{
+ Q_ASSERT(m_service && m_service->videoOutput() && m_service->videoOutput()->videoDataOutput());
+ Q_ASSERT(m_videoQueue);
+
+ [m_service->videoOutput()->videoDataOutput() setSampleBufferDelegate:self queue:m_videoQueue];
+
+ if (m_audioOutput.data()) {
+ Q_ASSERT(m_audioQueue);
+ [m_audioOutput setSampleBufferDelegate:self queue:m_audioQueue];
+ }
+}
+
+- (void)updateDuration:(CMTime)newTimeStamp
+{
+ Q_ASSERT(CMTimeCompare(m_startTime, kCMTimeInvalid));
+ Q_ASSERT(CMTimeCompare(m_lastTimeStamp, kCMTimeInvalid));
+ if (CMTimeCompare(newTimeStamp, m_lastTimeStamp) > 0) {
+
+ const CMTime duration = CMTimeSubtract(newTimeStamp, m_startTime);
+ if (!CMTimeCompare(duration, kCMTimeInvalid))
+ return;
+
+ m_durationInMs.storeRelease(CMTimeGetSeconds(duration) * 1000);
+ m_lastTimeStamp = newTimeStamp;
+ }
+}
+
+- (qint64)durationInMs
+{
+ return m_durationInMs.loadAcquire();
+}
+
+@end
diff --git a/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter_p.h b/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter_p.h
new file mode 100644
index 000000000..61d7d46bc
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter_p.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIAASSETWRITER_H
+#define AVFMEDIAASSETWRITER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "avfcamerautility.h"
+
+#include <QtCore/qglobal.h>
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFMediaRecorderControlIOS;
+class AVFCameraService;
+
+QT_END_NAMESPACE
+
+@interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate,
+ AVCaptureAudioDataOutputSampleBufferDelegate>
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(AVFMediaRecorderControlIOS) *)delegate;
+
+- (bool)setupWithFileURL:(NSURL *)fileURL
+ cameraService:(QT_PREPEND_NAMESPACE(AVFCameraService) *)service
+ audioSettings:(NSDictionary *)audioSettings
+ videoSettings:(NSDictionary *)videoSettings
+ transform:(CGAffineTransform)transform;
+
+// This to be called from the recorder control's thread:
+- (void)start;
+- (void)stop;
+// This to be called from the recorder control's dtor:
+- (void)abort;
+- (qint64)durationInMs;
+
+@end
+
+#endif // AVFMEDIAASSETWRITER_H
diff --git a/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol.mm b/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol.mm
new file mode 100644
index 000000000..09049de0b
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol.mm
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfmediacontainercontrol_p.h"
+
+#include <AVFoundation/AVMediaFormat.h>
+#include <QtCore/qmap.h>
+
+QT_BEGIN_NAMESPACE
+
+struct ContainerInfo
+{
+ QString description;
+ NSString *fileType;
+
+ ContainerInfo() : fileType(nil) { }
+ ContainerInfo(const QString &desc, NSString *type)
+ : description(desc), fileType(type)
+ { }
+};
+
+typedef QMap<QString, ContainerInfo> 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/multimedia/platform/avfoundation/camera/avfmediacontainercontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol_p.h
new file mode 100644
index 000000000..9450dc16a
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol_p.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIACONTAINERCONTROL_H
+#define AVFMEDIACONTAINERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qmediacontainercontrol.h>
+
+@class NSString;
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraService;
+
+class AVFMediaContainerControl : public QMediaContainerControl
+{
+public:
+ explicit AVFMediaContainerControl(AVFCameraService *service);
+
+ QStringList supportedContainers() const override;
+ QString containerFormat() const override;
+ void setContainerFormat(const QString &format) override;
+ QString containerDescription(const QString &formatMimeType) const override;
+
+ NSString *fileType() const;
+
+private:
+ QString m_format;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFMEDIACONTAINERCONTROL_H
diff --git a/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol.mm b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol.mm
new file mode 100644
index 000000000..27f78fea4
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol.mm
@@ -0,0 +1,430 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfcameradebug_p.h"
+#include "avfmediarecordercontrol_p.h"
+#include "avfcamerasession_p.h"
+#include "avfcameraservice_p.h"
+#include "avfcameracontrol_p.h"
+#include "avfaudioinputselectorcontrol_p.h"
+#include "avfaudioencodersettingscontrol_p.h"
+#include "avfvideoencodersettingscontrol_p.h"
+#include "avfmediacontainercontrol_p.h"
+
+#include <QtCore/qurl.h>
+#include <QtCore/qfileinfo.h>
+#include <QtMultimedia/qcameracontrol.h>
+
+
+QT_USE_NAMESPACE
+
+@interface AVFMediaRecorderDelegate : NSObject <AVCaptureFileOutputRecordingDelegate>
+{
+@private
+ AVFMediaRecorderControl *m_recorder;
+}
+
+- (AVFMediaRecorderDelegate *) initWithRecorder:(AVFMediaRecorderControl*)recorder;
+
+- (void) captureOutput:(AVCaptureFileOutput *)captureOutput
+ didStartRecordingToOutputFileAtURL:(NSURL *)fileURL
+ fromConnections:(NSArray *)connections;
+
+- (void) captureOutput:(AVCaptureFileOutput *)captureOutput
+ didFinishRecordingToOutputFileAtURL:(NSURL *)fileURL
+ fromConnections:(NSArray *)connections
+ error:(NSError *)error;
+@end
+
+@implementation AVFMediaRecorderDelegate
+
+- (AVFMediaRecorderDelegate *) initWithRecorder:(AVFMediaRecorderControl*)recorder
+{
+ if (!(self = [super init]))
+ return nil;
+
+ self->m_recorder = recorder;
+ return self;
+}
+
+- (void) captureOutput:(AVCaptureFileOutput *)captureOutput
+ didStartRecordingToOutputFileAtURL:(NSURL *)fileURL
+ fromConnections:(NSArray *)connections
+{
+ Q_UNUSED(captureOutput);
+ Q_UNUSED(fileURL);
+ Q_UNUSED(connections);
+
+ QMetaObject::invokeMethod(m_recorder, "handleRecordingStarted", Qt::QueuedConnection);
+}
+
+- (void) captureOutput:(AVCaptureFileOutput *)captureOutput
+ didFinishRecordingToOutputFileAtURL:(NSURL *)fileURL
+ fromConnections:(NSArray *)connections
+ error:(NSError *)error
+{
+ Q_UNUSED(captureOutput);
+ Q_UNUSED(fileURL);
+ Q_UNUSED(connections);
+
+ if (error) {
+ QStringList messageParts;
+ messageParts << QString::fromUtf8([[error localizedDescription] UTF8String]);
+ messageParts << QString::fromUtf8([[error localizedFailureReason] UTF8String]);
+ messageParts << QString::fromUtf8([[error localizedRecoverySuggestion] UTF8String]);
+
+ QString errorMessage = messageParts.join(" ");
+
+ QMetaObject::invokeMethod(m_recorder, "handleRecordingFailed", Qt::QueuedConnection,
+ Q_ARG(QString, errorMessage));
+ } else {
+ QMetaObject::invokeMethod(m_recorder, "handleRecordingFinished", Qt::QueuedConnection);
+ }
+}
+
+@end
+
+
+AVFMediaRecorderControl::AVFMediaRecorderControl(AVFCameraService *service, QObject *parent)
+ : QMediaRecorderControl(parent)
+ , m_service(service)
+ , m_cameraControl(service->cameraControl())
+ , m_audioInputControl(service->audioInputSelectorControl())
+ , m_session(service->session())
+ , m_connected(false)
+ , m_state(QMediaRecorder::StoppedState)
+ , m_lastStatus(QMediaRecorder::UnloadedStatus)
+ , m_recordingStarted(false)
+ , m_recordingFinished(false)
+ , 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];
+
+ connect(m_cameraControl, SIGNAL(stateChanged(QCamera::State)), SLOT(updateStatus()));
+ connect(m_cameraControl, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateStatus()));
+ connect(m_cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(setupSessionForCapture()));
+ connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(setupSessionForCapture()));
+ connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(setupSessionForCapture()));
+}
+
+AVFMediaRecorderControl::~AVFMediaRecorderControl()
+{
+ if (m_movieOutput) {
+ [m_session->captureSession() removeOutput:m_movieOutput];
+ [m_movieOutput release];
+ }
+
+ if (m_audioInput) {
+ [m_session->captureSession() removeInput:m_audioInput];
+ [m_audioInput release];
+ }
+
+ [m_recorderDelagate release];
+}
+
+QUrl AVFMediaRecorderControl::outputLocation() const
+{
+ return m_outputLocation;
+}
+
+bool AVFMediaRecorderControl::setOutputLocation(const QUrl &location)
+{
+ m_outputLocation = location;
+ return location.scheme() == QLatin1String("file") || location.scheme().isEmpty();
+}
+
+QMediaRecorder::State AVFMediaRecorderControl::state() const
+{
+ return m_state;
+}
+
+QMediaRecorder::Status AVFMediaRecorderControl::status() const
+{
+ QMediaRecorder::Status status = m_lastStatus;
+ //bool videoEnabled = m_cameraControl->captureMode().testFlag(QCamera::CaptureVideo);
+
+ if (m_cameraControl->status() == QCamera::ActiveStatus && m_connected) {
+ if (m_state == QMediaRecorder::StoppedState) {
+ if (m_recordingStarted && !m_recordingFinished)
+ status = QMediaRecorder::FinalizingStatus;
+ else
+ status = QMediaRecorder::LoadedStatus;
+ } else {
+ status = m_recordingStarted ? QMediaRecorder::RecordingStatus :
+ QMediaRecorder::StartingStatus;
+ }
+ } else {
+ //camera not started yet
+ status = m_cameraControl->state() == QCamera::ActiveState && m_connected ?
+ QMediaRecorder::LoadingStatus:
+ QMediaRecorder::UnloadedStatus;
+ }
+
+ return status;
+}
+
+void AVFMediaRecorderControl::updateStatus()
+{
+ QMediaRecorder::Status newStatus = status();
+
+ if (m_lastStatus != newStatus) {
+ qDebugCamera() << "Camera recorder status changed: " << m_lastStatus << " -> " << newStatus;
+ m_lastStatus = newStatus;
+ Q_EMIT statusChanged(m_lastStatus);
+ }
+}
+
+
+qint64 AVFMediaRecorderControl::duration() const
+{
+ if (!m_movieOutput)
+ return 0;
+
+ return qint64(CMTimeGetSeconds(m_movieOutput.recordedDuration) * 1000);
+}
+
+bool AVFMediaRecorderControl::isMuted() const
+{
+ return m_muted;
+}
+
+qreal AVFMediaRecorderControl::volume() const
+{
+ return m_volume;
+}
+
+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;
+ }
+
+ // 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);
+
+ const AVFConfigurationLock lock(m_session->videoCaptureDevice()); // prevents activeFormat from being overridden
+
+ [m_movieOutput setOutputSettings:videoSettings forConnection:videoConnection];
+}
+
+void AVFMediaRecorderControl::unapplySettings()
+{
+ m_service->audioEncoderSettingsControl()->unapplySettings();
+ m_service->videoEncoderSettingsControl()->unapplySettings([m_movieOutput connectionWithMediaType:AVMediaTypeVideo]);
+}
+
+void AVFMediaRecorderControl::setState(QMediaRecorder::State state)
+{
+ if (m_state == state)
+ return;
+
+ qDebugCamera() << Q_FUNC_INFO << m_state << " -> " << state;
+
+ switch (state) {
+ case QMediaRecorder::RecordingState:
+ {
+ if (m_connected) {
+ 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_"),
+ extension));
+
+ 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);
+ updateStatus();
+ Q_EMIT stateChanged(m_state);
+ } else {
+ Q_EMIT error(QMediaRecorder::FormatError, tr("Recorder not configured"));
+ }
+
+ } break;
+ case QMediaRecorder::PausedState:
+ {
+ Q_EMIT error(QMediaRecorder::FormatError, tr("Recording pause not supported"));
+ return;
+ } break;
+ case QMediaRecorder::StoppedState:
+ {
+ m_lastStatus = QMediaRecorder::FinalizingStatus;
+ Q_EMIT statusChanged(m_lastStatus);
+ [m_movieOutput stopRecording];
+ unapplySettings();
+ }
+ }
+}
+
+void AVFMediaRecorderControl::setMuted(bool muted)
+{
+ if (m_muted != muted) {
+ m_muted = muted;
+ Q_EMIT mutedChanged(muted);
+ }
+}
+
+void AVFMediaRecorderControl::setVolume(qreal volume)
+{
+ if (m_volume != volume) {
+ m_volume = volume;
+ Q_EMIT volumeChanged(volume);
+ }
+}
+
+void AVFMediaRecorderControl::handleRecordingStarted()
+{
+ m_recordingStarted = true;
+ updateStatus();
+}
+
+void AVFMediaRecorderControl::handleRecordingFinished()
+{
+ m_recordingFinished = true;
+ if (m_state != QMediaRecorder::StoppedState) {
+ m_state = QMediaRecorder::StoppedState;
+ Q_EMIT stateChanged(m_state);
+ }
+ updateStatus();
+}
+
+void AVFMediaRecorderControl::handleRecordingFailed(const QString &message)
+{
+ m_recordingFinished = true;
+ if (m_state != QMediaRecorder::StoppedState) {
+ m_state = QMediaRecorder::StoppedState;
+ Q_EMIT stateChanged(m_state);
+ }
+ updateStatus();
+
+ Q_EMIT error(QMediaRecorder::ResourceError, message);
+}
+
+void AVFMediaRecorderControl::setupSessionForCapture()
+{
+ if (!m_session->videoCaptureDevice())
+ return;
+
+ //adding movie output causes high CPU usage even when while recording is not active,
+ //connect it only while video capture mode is enabled.
+ // Similarly, connect the Audio input only in that mode, since it's only necessary
+ // when recording anyway. Adding an Audio input will trigger the microphone permission
+ // request on iOS, but it shoudn't do so until we actually try to record.
+ AVCaptureSession *captureSession = m_session->captureSession();
+
+ if (!m_connected
+ && m_cameraControl->captureMode().testFlag(QCamera::CaptureVideo)
+ && m_session->state() != QCamera::UnloadedState) {
+
+ // Lock the video capture device to make sure the active format is not reset
+ const AVFConfigurationLock lock(m_session->videoCaptureDevice());
+
+ // Add audio input
+ // Allow recording even if something wrong happens with the audio input initialization
+ AVCaptureDevice *audioDevice = m_audioInputControl->createCaptureDevice();
+ if (!audioDevice) {
+ qWarning("No audio input device available");
+ } else {
+ NSError *error = nil;
+ m_audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
+
+ if (!m_audioInput) {
+ qWarning() << "Failed to create audio device input";
+ } else if (![captureSession canAddInput:m_audioInput]) {
+ qWarning() << "Could not connect the audio input";
+ m_audioInput = nullptr;
+ } else {
+ [m_audioInput retain];
+ [captureSession addInput:m_audioInput];
+ }
+ }
+
+ if ([captureSession canAddOutput:m_movieOutput]) {
+ [captureSession addOutput:m_movieOutput];
+ m_connected = true;
+ } else {
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("Could not connect the video recorder"));
+ qWarning() << "Could not connect the video recorder";
+ }
+ } else if (m_connected
+ && (!m_cameraControl->captureMode().testFlag(QCamera::CaptureVideo)
+ || m_session->state() == QCamera::UnloadedState)) {
+
+ // Lock the video capture device to make sure the active format is not reset
+ const AVFConfigurationLock lock(m_session->videoCaptureDevice());
+
+ [captureSession removeOutput:m_movieOutput];
+
+ if (m_audioInput) {
+ [captureSession removeInput:m_audioInput];
+ [m_audioInput release];
+ m_audioInput = nil;
+ }
+
+ m_connected = false;
+ }
+
+ updateStatus();
+}
+
+#include "moc_avfmediarecordercontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios.mm b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios.mm
new file mode 100644
index 000000000..33064827d
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios.mm
@@ -0,0 +1,414 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include "avfmediarecordercontrol_ios.h"
+#include "avfcamerarenderercontrol.h"
+#include "avfcamerasession.h"
+#include "avfcameracontrol.h"
+#include "avfcameraservice.h"
+#include "avfcameradebug.h"
+#include "avfaudioencodersettingscontrol.h"
+#include "avfvideoencodersettingscontrol.h"
+#include "avfmediacontainercontrol.h"
+#include "avfcamerautility.h"
+
+#include <QtCore/qmath.h>
+#include <QtCore/qdebug.h>
+
+QT_USE_NAMESPACE
+
+namespace {
+
+bool qt_is_writable_file_URL(NSURL *fileURL)
+{
+ Q_ASSERT(fileURL);
+
+ if (![fileURL isFileURL])
+ return false;
+
+ if (NSString *path = [[fileURL path] stringByExpandingTildeInPath]) {
+ return [[NSFileManager defaultManager]
+ isWritableFileAtPath:[path stringByDeletingLastPathComponent]];
+ }
+
+ return false;
+}
+
+bool qt_file_exists(NSURL *fileURL)
+{
+ Q_ASSERT(fileURL);
+
+ if (NSString *path = [[fileURL path] stringByExpandingTildeInPath])
+ return [[NSFileManager defaultManager] fileExistsAtPath:path];
+
+ return false;
+}
+
+}
+
+AVFMediaRecorderControlIOS::AVFMediaRecorderControlIOS(AVFCameraService *service, QObject *parent)
+ : QMediaRecorderControl(parent)
+ , m_service(service)
+ , m_state(QMediaRecorder::StoppedState)
+ , m_lastStatus(QMediaRecorder::UnloadedStatus)
+ , m_audioSettings(nil)
+ , m_videoSettings(nil)
+{
+ Q_ASSERT(service);
+
+ m_writer.reset([[QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) alloc] initWithDelegate:this]);
+ if (!m_writer) {
+ qDebugCamera() << Q_FUNC_INFO << "failed to create an asset writer";
+ return;
+ }
+
+ AVFCameraControl *cameraControl = m_service->cameraControl();
+ if (!cameraControl) {
+ qDebugCamera() << Q_FUNC_INFO << "camera control is nil";
+ return;
+ }
+
+ connect(cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)),
+ SLOT(captureModeChanged(QCamera::CaptureModes)));
+ connect(cameraControl, SIGNAL(statusChanged(QCamera::Status)),
+ SLOT(cameraStatusChanged(QCamera::Status)));
+}
+
+AVFMediaRecorderControlIOS::~AVFMediaRecorderControlIOS()
+{
+ [m_writer abort];
+
+ if (m_audioSettings)
+ [m_audioSettings release];
+ if (m_videoSettings)
+ [m_videoSettings release];
+}
+
+QUrl AVFMediaRecorderControlIOS::outputLocation() const
+{
+ return m_outputLocation;
+}
+
+bool AVFMediaRecorderControlIOS::setOutputLocation(const QUrl &location)
+{
+ m_outputLocation = location;
+ return location.scheme() == QLatin1String("file") || location.scheme().isEmpty();
+}
+
+QMediaRecorder::State AVFMediaRecorderControlIOS::state() const
+{
+ return m_state;
+}
+
+QMediaRecorder::Status AVFMediaRecorderControlIOS::status() const
+{
+ return m_lastStatus;
+}
+
+qint64 AVFMediaRecorderControlIOS::duration() const
+{
+ return m_writer.data().durationInMs;
+}
+
+bool AVFMediaRecorderControlIOS::isMuted() const
+{
+ return false;
+}
+
+qreal AVFMediaRecorderControlIOS::volume() const
+{
+ return 1.;
+}
+
+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;
+ }
+
+ // 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];
+}
+
+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;
+ }
+}
+
+void AVFMediaRecorderControlIOS::setState(QMediaRecorder::State state)
+{
+ Q_ASSERT(m_service->session()
+ && m_service->session()->captureSession());
+
+ if (!m_writer) {
+ qDebugCamera() << Q_FUNC_INFO << "Invalid recorder";
+ return;
+ }
+
+ if (state == m_state)
+ return;
+
+ switch (state) {
+ case QMediaRecorder::RecordingState:
+ {
+ AVFCameraControl *cameraControl = m_service->cameraControl();
+ Q_ASSERT(cameraControl);
+
+ if (!(cameraControl->captureMode() & QCamera::CaptureVideo)) {
+ qDebugCamera() << Q_FUNC_INFO << "wrong capture mode, CaptureVideo expected";
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("Failed to start recording"));
+ return;
+ }
+
+ if (cameraControl->status() != QCamera::ActiveStatus) {
+ qDebugCamera() << Q_FUNC_INFO << "can not start record while camera is not active";
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("Failed to start recording"));
+ return;
+ }
+
+ const QString path(m_outputLocation.scheme() == QLatin1String("file") ?
+ m_outputLocation.path() : m_outputLocation.toString());
+ const QUrl fileURL(QUrl::fromLocalFile(m_storageLocation.generateFileName(path, QCamera::CaptureVideo,
+ QLatin1String("clip_"),
+ m_service->mediaContainerControl()->containerFormat())));
+
+ NSURL *nsFileURL = fileURL.toNSURL();
+ if (!nsFileURL) {
+ qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL;
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("Invalid output file URL"));
+ return;
+ }
+ if (!qt_is_writable_file_URL(nsFileURL)) {
+ qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL
+ << "(the location is not writable)";
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("Non-writeable file location"));
+ return;
+ }
+ if (qt_file_exists(nsFileURL)) {
+ // We test for/handle this error here since AWAssetWriter will raise an
+ // Objective-C exception, which is not good at all.
+ qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL
+ << "(file already exists)";
+ Q_EMIT error(QMediaRecorder::ResourceError, tr("File already exists"));
+ return;
+ }
+
+ AVCaptureSession *session = m_service->session()->captureSession();
+ // We stop session now so that no more frames for renderer's queue
+ // generated, will restart in assetWriterStarted.
+ [session stopRunning];
+
+ applySettings();
+
+ // 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;
+
+ if ([m_writer setupWithFileURL:nsFileURL
+ cameraService:m_service
+ audioSettings:m_audioSettings
+ videoSettings:m_videoSettings
+ transform:CGAffineTransformMakeRotation(qDegreesToRadians(rotation))]) {
+
+ m_state = QMediaRecorder::RecordingState;
+ m_lastStatus = QMediaRecorder::StartingStatus;
+
+ Q_EMIT actualLocationChanged(fileURL);
+ Q_EMIT stateChanged(m_state);
+ Q_EMIT statusChanged(m_lastStatus);
+
+ // Apple recommends to call startRunning and do all
+ // setup on a special queue, and that's what we had
+ // initially (dispatch_async to writerQueue). Unfortunately,
+ // writer's queue is not the only queue/thread that can
+ // access/modify the session, and as a result we have
+ // all possible data/race-conditions with Obj-C exceptions
+ // at best and something worse in general.
+ // Now we try to only modify session on the same thread.
+ [m_writer start];
+ } else {
+ [session startRunning];
+ Q_EMIT error(QMediaRecorder::FormatError, tr("Failed to start recording"));
+ }
+ } break;
+ case QMediaRecorder::PausedState:
+ {
+ Q_EMIT error(QMediaRecorder::FormatError, tr("Recording pause not supported"));
+ return;
+ } break;
+ case QMediaRecorder::StoppedState:
+ {
+ // Do not check the camera status, we can stop if we started.
+ stopWriter();
+ }
+ }
+}
+
+void AVFMediaRecorderControlIOS::setMuted(bool muted)
+{
+ Q_UNUSED(muted);
+ qDebugCamera() << Q_FUNC_INFO << "not implemented";
+}
+
+void AVFMediaRecorderControlIOS::setVolume(qreal volume)
+{
+ Q_UNUSED(volume);
+ qDebugCamera() << Q_FUNC_INFO << "not implemented";
+}
+
+void AVFMediaRecorderControlIOS::assetWriterStarted()
+{
+ m_lastStatus = QMediaRecorder::RecordingStatus;
+ Q_EMIT statusChanged(QMediaRecorder::RecordingStatus);
+}
+
+void AVFMediaRecorderControlIOS::assetWriterFinished()
+{
+ AVFCameraControl *cameraControl = m_service->cameraControl();
+ Q_ASSERT(cameraControl);
+
+ const QMediaRecorder::Status lastStatus = m_lastStatus;
+ const QMediaRecorder::State lastState = m_state;
+ if (cameraControl->captureMode() & QCamera::CaptureVideo)
+ m_lastStatus = QMediaRecorder::LoadedStatus;
+ else
+ m_lastStatus = QMediaRecorder::UnloadedStatus;
+
+ unapplySettings();
+
+ m_service->videoOutput()->resetCaptureDelegate();
+ [m_service->session()->captureSession() startRunning];
+ m_state = QMediaRecorder::StoppedState;
+ if (m_lastStatus != lastStatus)
+ Q_EMIT statusChanged(m_lastStatus);
+ if (m_state != lastState)
+ Q_EMIT stateChanged(m_state);
+}
+
+void AVFMediaRecorderControlIOS::captureModeChanged(QCamera::CaptureModes newMode)
+{
+ AVFCameraControl *cameraControl = m_service->cameraControl();
+ Q_ASSERT(cameraControl);
+
+ const QMediaRecorder::Status lastStatus = m_lastStatus;
+
+ if (newMode & QCamera::CaptureVideo) {
+ if (cameraControl->status() == QCamera::ActiveStatus)
+ m_lastStatus = QMediaRecorder::LoadedStatus;
+ } else {
+ if (m_lastStatus == QMediaRecorder::RecordingStatus)
+ return stopWriter();
+ else
+ m_lastStatus = QMediaRecorder::UnloadedStatus;
+ }
+
+ if (m_lastStatus != lastStatus)
+ Q_EMIT statusChanged(m_lastStatus);
+}
+
+void AVFMediaRecorderControlIOS::cameraStatusChanged(QCamera::Status newStatus)
+{
+ AVFCameraControl *cameraControl = m_service->cameraControl();
+ Q_ASSERT(cameraControl);
+
+ const QMediaRecorder::Status lastStatus = m_lastStatus;
+ const bool isCapture = cameraControl->captureMode() & QCamera::CaptureVideo;
+ if (newStatus == QCamera::StartingStatus) {
+ if (isCapture && m_lastStatus == QMediaRecorder::UnloadedStatus)
+ m_lastStatus = QMediaRecorder::LoadingStatus;
+ } else if (newStatus == QCamera::ActiveStatus) {
+ if (isCapture && m_lastStatus == QMediaRecorder::LoadingStatus)
+ m_lastStatus = QMediaRecorder::LoadedStatus;
+ } else {
+ if (m_lastStatus == QMediaRecorder::RecordingStatus)
+ return stopWriter();
+ if (newStatus == QCamera::UnloadedStatus)
+ m_lastStatus = QMediaRecorder::UnloadedStatus;
+ }
+
+ if (lastStatus != m_lastStatus)
+ Q_EMIT statusChanged(m_lastStatus);
+}
+
+void AVFMediaRecorderControlIOS::stopWriter()
+{
+ if (m_lastStatus == QMediaRecorder::RecordingStatus) {
+ m_lastStatus = QMediaRecorder::FinalizingStatus;
+
+ Q_EMIT statusChanged(m_lastStatus);
+
+ [m_writer stop];
+ }
+}
+
+#include "moc_avfmediarecordercontrol_ios.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios_p.h b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios_p.h
new file mode 100644
index 000000000..178c75cad
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios_p.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIARECORDERCONTROL_IOS_H
+#define AVFMEDIARECORDERCONTROL_IOS_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "avfmediaassetwriter.h"
+#include "avfstoragelocation.h"
+#include "avfcamerautility.h"
+
+#include <QtMultimedia/qmediarecordercontrol.h>
+#include <private/qvideooutputorientationhandler_p.h>
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qurl.h>
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraService;
+class QString;
+class QUrl;
+
+class AVFMediaRecorderControlIOS : public QMediaRecorderControl
+{
+ Q_OBJECT
+public:
+ AVFMediaRecorderControlIOS(AVFCameraService *service, QObject *parent = nullptr);
+ ~AVFMediaRecorderControlIOS() override;
+
+ QUrl outputLocation() const override;
+ bool setOutputLocation(const QUrl &location) override;
+
+ QMediaRecorder::State state() const override;
+ QMediaRecorder::Status status() const override;
+
+ qint64 duration() const override;
+
+ bool isMuted() const override;
+ qreal volume() const override;
+
+ void applySettings() override;
+ void unapplySettings();
+
+public Q_SLOTS:
+ void setState(QMediaRecorder::State state) override;
+ void setMuted(bool muted) override;
+ void setVolume(qreal volume) override;
+
+private:
+
+ Q_INVOKABLE void assetWriterStarted();
+ Q_INVOKABLE void assetWriterFinished();
+
+private Q_SLOTS:
+ void captureModeChanged(QCamera::CaptureModes);
+ void cameraStatusChanged(QCamera::Status newStatus);
+
+private:
+ void stopWriter();
+
+ AVFCameraService *m_service;
+ AVFScopedPointer<QT_MANGLE_NAMESPACE(AVFMediaAssetWriter)> m_writer;
+
+ QUrl m_outputLocation;
+ AVFStorageLocation m_storageLocation;
+
+ QMediaRecorder::State m_state;
+ QMediaRecorder::Status m_lastStatus;
+
+ NSDictionary *m_audioSettings;
+ NSDictionary *m_videoSettings;
+ QVideoOutputOrientationHandler m_orientationHandler;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFMEDIARECORDERCONTROL_IOS_H
diff --git a/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_p.h
new file mode 100644
index 000000000..9d8f6566b
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_p.h
@@ -0,0 +1,131 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIARECORDERCONTROL_H
+#define AVFMEDIARECORDERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qurl.h>
+#include <QtMultimedia/qmediarecordercontrol.h>
+
+#import <AVFoundation/AVFoundation.h>
+#include "avfstoragelocation_p.h"
+#include "avfcamerautility_p.h"
+
+@class AVFMediaRecorderDelegate;
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraSession;
+class AVFCameraControl;
+class AVFAudioInputSelectorControl;
+class AVFCameraService;
+
+class AVFMediaRecorderControl : public QMediaRecorderControl
+{
+Q_OBJECT
+public:
+ AVFMediaRecorderControl(AVFCameraService *service, QObject *parent = nullptr);
+ ~AVFMediaRecorderControl();
+
+ QUrl outputLocation() const override;
+ bool setOutputLocation(const QUrl &location) override;
+
+ QMediaRecorder::State state() const override;
+ QMediaRecorder::Status status() const override;
+
+ qint64 duration() const override;
+
+ bool isMuted() const override;
+ qreal volume() const override;
+
+ void applySettings() override;
+ void unapplySettings();
+
+public Q_SLOTS:
+ void setState(QMediaRecorder::State state) override;
+ void setMuted(bool muted) override;
+ void setVolume(qreal volume) override;
+
+ void handleRecordingStarted();
+ void handleRecordingFinished();
+ void handleRecordingFailed(const QString &message);
+
+private Q_SLOTS:
+ void setupSessionForCapture();
+ void updateStatus();
+
+private:
+ AVFCameraService *m_service;
+ AVFCameraControl *m_cameraControl;
+ AVFAudioInputSelectorControl *m_audioInputControl;
+ AVFCameraSession *m_session;
+
+ bool m_connected;
+ QUrl m_outputLocation;
+ QMediaRecorder::State m_state;
+ QMediaRecorder::Status m_lastStatus;
+
+ bool m_recordingStarted;
+ bool m_recordingFinished;
+
+ bool m_muted;
+ qreal m_volume;
+
+ AVCaptureDeviceInput *m_audioInput;
+ AVCaptureMovieFileOutput *m_movieOutput;
+ AVFMediaRecorderDelegate *m_recorderDelagate;
+ AVFStorageLocation m_storageLocation;
+
+ AVFPSRange m_restoreFPS;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol.mm b/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol.mm
new file mode 100644
index 000000000..c97ab1919
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol.mm
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Copyright (C) 2016 Integrated Computer Solutions, Inc
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfmediavideoprobecontrol_p.h"
+#include <qvideoframe.h>
+
+QT_BEGIN_NAMESPACE
+
+AVFMediaVideoProbeControl::AVFMediaVideoProbeControl(QObject *parent) :
+ QMediaVideoProbeControl(parent)
+{
+}
+
+AVFMediaVideoProbeControl::~AVFMediaVideoProbeControl()
+{
+
+}
+
+void AVFMediaVideoProbeControl::newFrameProbed(const QVideoFrame &frame)
+{
+ Q_EMIT videoFrameProbed(frame);
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol_p.h
new file mode 100644
index 000000000..c18bc4181
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol_p.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIAVIDEOPROBECONTROL_H
+#define AVFMEDIAVIDEOPROBECONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qmediavideoprobecontrol.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFMediaVideoProbeControl : public QMediaVideoProbeControl
+{
+ Q_OBJECT
+public:
+ explicit AVFMediaVideoProbeControl(QObject *parent = nullptr);
+ ~AVFMediaVideoProbeControl();
+
+ void newFrameProbed(const QVideoFrame& frame);
+
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFMEDIAVIDEOPROBECONTROL_H
diff --git a/src/multimedia/platform/avfoundation/camera/avfstoragelocation.mm b/src/multimedia/platform/avfoundation/camera/avfstoragelocation.mm
new file mode 100644
index 000000000..1855f8ec7
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfstoragelocation.mm
@@ -0,0 +1,136 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfstoragelocation_p.h"
+#include "QtCore/qstandardpaths.h"
+
+
+QT_BEGIN_NAMESPACE
+
+
+AVFStorageLocation::AVFStorageLocation()
+{
+}
+
+AVFStorageLocation::~AVFStorageLocation()
+{
+}
+
+/*!
+ * Generate the actual file name from user requested one.
+ * requestedName may be either empty (the default dir and naming theme is used),
+ * points to existing dir (the default name used)
+ * or specify the full actual path.
+ */
+QString AVFStorageLocation::generateFileName(const QString &requestedName,
+ QCamera::CaptureMode mode,
+ const QString &prefix,
+ const QString &ext) const
+{
+ if (requestedName.isEmpty())
+ return generateFileName(prefix, defaultDir(mode), ext);
+
+ if (QFileInfo(requestedName).isDir())
+ return generateFileName(prefix, QDir(requestedName), ext);
+
+ return requestedName;
+}
+
+QDir AVFStorageLocation::defaultDir(QCamera::CaptureMode mode) const
+{
+ QStringList dirCandidates;
+
+ if (mode == QCamera::CaptureVideo) {
+ dirCandidates << QStandardPaths::writableLocation(QStandardPaths::MoviesLocation);
+ } else {
+ dirCandidates << QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
+ }
+
+ dirCandidates << QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
+ dirCandidates << QDir::homePath();
+ dirCandidates << QDir::currentPath();
+ dirCandidates << QDir::tempPath();
+
+ for (const QString &path : qAsConst(dirCandidates)) {
+ if (QFileInfo(path).isWritable())
+ return QDir(path);
+ }
+
+ return QDir();
+}
+
+QString AVFStorageLocation::generateFileName(const QString &prefix, const QDir &dir, const QString &ext) const
+{
+ QString lastClipKey = dir.absolutePath()+QLatin1Char(' ')+prefix+QLatin1Char(' ')+ext;
+ int lastClip = m_lastUsedIndex.value(lastClipKey, 0);
+
+ if (lastClip == 0) {
+ //first run, find the maximum clip number during the fist capture
+ const auto list = dir.entryList(QStringList() << QString("%1*.%2").arg(prefix).arg(ext));
+ for (const QString &fileName : list) {
+ int imgNumber = QStringView{fileName}.mid(prefix.length(), fileName.size()-prefix.length()-ext.length()-1).toInt();
+ lastClip = qMax(lastClip, imgNumber);
+ }
+ }
+
+
+ //don't just rely on cached lastClip value,
+ //someone else may create a file after camera started
+ while (true) {
+ QString name = QString("%1%2.%3").arg(prefix)
+ .arg(lastClip+1,
+ 4, //fieldWidth
+ 10,
+ QLatin1Char('0'))
+ .arg(ext);
+
+ QString path = dir.absoluteFilePath(name);
+ if (!QFileInfo(path).exists()) {
+ m_lastUsedIndex[lastClipKey] = lastClip+1;
+ return path;
+ }
+
+ lastClip++;
+ }
+
+ return QString();
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/platform/avfoundation/camera/avfstoragelocation_p.h b/src/multimedia/platform/avfoundation/camera/avfstoragelocation_p.h
new file mode 100644
index 000000000..8794f0fae
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfstoragelocation_p.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFSTORAGE_H
+#define AVFSTORAGE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qtmultimediaglobal.h"
+
+#include <QtCore/qdir.h>
+#include <QtMultimedia/qcamera.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFStorageLocation
+{
+public:
+ AVFStorageLocation();
+ ~AVFStorageLocation();
+
+ QString generateFileName(const QString &requestedName,
+ QCamera::CaptureMode mode,
+ const QString &prefix,
+ const QString &ext) const;
+
+
+ QDir defaultDir(QCamera::CaptureMode mode) const;
+ QString generateFileName(const QString &prefix, const QDir &dir, const QString &ext) const;
+
+private:
+ mutable QMap<QString, int> m_lastUsedIndex;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol.mm b/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol.mm
new file mode 100644
index 000000000..0800e2021
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol.mm
@@ -0,0 +1,385 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfvideoencodersettingscontrol_p.h"
+
+#include "avfcameraservice_p.h"
+#include "avfcamerautility_p.h"
+#include "avfcamerasession_p.h"
+#include "avfcamerarenderercontrol_p.h"
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_GLOBAL_STATIC_WITH_ARGS(QStringList, supportedCodecs, (QStringList() << QLatin1String("avc1")
+ << QLatin1String("jpeg")
+ #ifdef Q_OS_OSX
+ << QLatin1String("ap4h")
+ << QLatin1String("apcn")
+ #endif
+ ))
+
+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;
+}
+
+static bool real_list_contains(const QList<qreal> &list, qreal value)
+{
+ for (qreal r : list) {
+ if (qFuzzyCompare(r, value))
+ return true;
+ }
+ return false;
+}
+
+AVFVideoEncoderSettingsControl::AVFVideoEncoderSettingsControl(AVFCameraService *service)
+ : QVideoEncoderSettingsControl()
+ , m_service(service)
+ , m_restoreFormat(nil)
+{
+}
+
+QList<QSize> 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<QSize> resolutions;
+ resolutions.append(QSize(32, 32));
+
+ AVCaptureDevice *device = m_service->session()->videoCaptureDevice();
+ if (device) {
+ int maximumWidth = 0;
+ const QVector<AVCaptureDeviceFormat *> 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));
+ }
+
+ if (resolutions.count() == 1)
+ resolutions.append(QSize(3840, 3840));
+
+ return resolutions;
+}
+
+QList<qreal> AVFVideoEncoderSettingsControl::supportedFrameRates(const QVideoEncoderSettings &settings,
+ bool *continuous) const
+{
+ QList<qreal> uniqueFrameRates;
+
+ AVCaptureDevice *device = m_service->session()->videoCaptureDevice();
+ if (!device)
+ return uniqueFrameRates;
+
+ if (continuous)
+ *continuous = false;
+
+ QVector<AVFPSRange> allRates;
+
+ if (!settings.resolution().isValid()) {
+ const QVector<AVCaptureDeviceFormat *> 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;
+}
+
+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;
+
+ AVFPSRange currentFps = qt_current_framerates(device, connection);
+ const bool needFpsChange = m_requestedSettings.frameRate() > 0
+ && m_requestedSettings.frameRate() != currentFps.second;
+
+ 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 (AVCaptureDeviceFormat *currentFormat = device.activeFormat) {
+ CMFormatDescriptionRef formatDesc = currentFormat.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(currentFormat, 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 (qt_set_active_format(device, newFormat, !needFpsChange)) {
+ m_restoreFormat = [currentFormat retain];
+ 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);
+ }
+ }
+
+ 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
+
+ if (needFpsChange) {
+ m_restoreFps = currentFps;
+ const qreal fps = m_requestedSettings.frameRate();
+ qt_set_framerate_limits(device, connection, fps, fps);
+ }
+ m_actualSettings.setFrameRate(qt_current_framerates(device, connection).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;
+
+ const bool needFpsChanged = m_restoreFps.first || m_restoreFps.second;
+
+ if (m_restoreFormat) {
+ qt_set_active_format(device, m_restoreFormat, !needFpsChanged);
+ [m_restoreFormat release];
+ m_restoreFormat = nil;
+ }
+
+ if (needFpsChanged) {
+ qt_set_framerate_limits(device, connection, m_restoreFps.first, m_restoreFps.second);
+ m_restoreFps = AVFPSRange();
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_avfvideoencodersettingscontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol_p.h
new file mode 100644
index 000000000..6d44d4a33
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol_p.h
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFVIDEOENCODERSETTINGSCONTROL_H
+#define AVFVIDEOENCODERSETTINGSCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qvideoencodersettingscontrol.h>
+
+#include "avfcamerautility_p.h"
+#import <AVFoundation/AVFoundation.h>
+
+@class NSDictionary;
+
+QT_BEGIN_NAMESPACE
+
+class AVFCameraService;
+
+class AVFVideoEncoderSettingsControl : public QVideoEncoderSettingsControl
+{
+ Q_OBJECT
+
+public:
+ explicit AVFVideoEncoderSettingsControl(AVFCameraService *service);
+
+ QList<QSize> supportedResolutions(const QVideoEncoderSettings &requestedVideoSettings,
+ bool *continuous = nullptr) const override;
+
+ QList<qreal> supportedFrameRates(const QVideoEncoderSettings &requestedVideoSettings,
+ bool *continuous = nullptr) const override;
+
+ QStringList supportedVideoCodecs() const override;
+ QString videoCodecDescription(const QString &codecName) const override;
+
+ QVideoEncoderSettings videoSettings() const override;
+ void setVideoSettings(const QVideoEncoderSettings &requestedVideoSettings) 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/multimedia/platform/avfoundation/camera/camera.pri b/src/multimedia/platform/avfoundation/camera/camera.pri
new file mode 100644
index 000000000..430094d14
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/camera/camera.pri
@@ -0,0 +1,60 @@
+HEADERS += \
+ $$PWD/avfcameradebug_p.h \
+ $$PWD/avfcameraserviceplugin_p.h \
+ $$PWD/avfcameracontrol_p.h \
+ $$PWD/avfcamerametadatacontrol_p.h \
+ $$PWD/avfimagecapturecontrol_p.h \
+ $$PWD/avfcameraservice_p.h \
+ $$PWD/avfcamerasession_p.h \
+ $$PWD/avfstoragelocation_p.h \
+ $$PWD/avfaudioinputselectorcontrol_p.h \
+ $$PWD/avfmediavideoprobecontrol_p.h \
+ $$PWD/avfcamerarenderercontrol_p.h \
+ $$PWD/avfcameradevicecontrol_p.h \
+ $$PWD/avfcamerafocuscontrol_p.h \
+ $$PWD/avfcameraexposurecontrol_p.h \
+ $$PWD/avfcamerautility_p.h \
+ $$PWD/avfimageencodercontrol_p.h \
+ $$PWD/avfvideoencodersettingscontrol_p.h \
+ $$PWD/avfmediacontainercontrol_p.h \
+ $$PWD/avfaudioencodersettingscontrol_p.h \
+ $$PWD/avfcamerawindowcontrol_p.h \
+
+SOURCES += \
+ $$PWD/avfcameraserviceplugin.mm \
+ $$PWD/avfcameracontrol.mm \
+ $$PWD/avfcamerametadatacontrol.mm \
+ $$PWD/avfimagecapturecontrol.mm \
+ $$PWD/avfcameraservice.mm \
+ $$PWD/avfcamerasession.mm \
+ $$PWD/avfstoragelocation.mm \
+ $$PWD/avfaudioinputselectorcontrol.mm \
+ $$PWD/avfmediavideoprobecontrol.mm \
+ $$PWD/avfcameradevicecontrol.mm \
+ $$PWD/avfcamerarenderercontrol.mm \
+ $$PWD/avfcamerafocuscontrol.mm \
+ $$PWD/avfcameraexposurecontrol.mm \
+ $$PWD/avfcamerautility.mm \
+ $$PWD/avfimageencodercontrol.mm \
+ $$PWD/avfvideoencodersettingscontrol.mm \
+ $$PWD/avfmediacontainercontrol.mm \
+ $$PWD/avfaudioencodersettingscontrol.mm \
+ $$PWD/avfcamerawindowcontrol.mm \
+
+osx {
+
+HEADERS += $$PWD/avfmediarecordercontrol_p.h
+SOURCES += $$PWD/avfmediarecordercontrol.mm
+
+}
+
+ios {
+
+HEADERS += \
+ $$PWD/avfmediaassetwriter_p.h \
+ $$PWD/avfmediarecordercontrol_ios_p.h
+SOURCES += \
+ $$PWD/avfmediaassetwriter.mm \
+ $$PWD/avfmediarecordercontrol_ios.mm
+
+}
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink.mm
new file mode 100644
index 000000000..64b625f0e
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink.mm
@@ -0,0 +1,241 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfdisplaylink_p.h"
+#include <QtCore/qcoreapplication.h>
+
+#ifdef QT_DEBUG_AVF
+#include <QtCore/qdebug.h>
+#endif
+
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+#import <QuartzCore/CADisplayLink.h>
+#import <Foundation/NSRunLoop.h>
+#define _m_displayLink static_cast<DisplayLinkObserver*>(m_displayLink)
+#else
+#endif
+
+QT_USE_NAMESPACE
+
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+@interface DisplayLinkObserver : NSObject
+
+- (void)start;
+- (void)stop;
+- (void)displayLinkNotification:(CADisplayLink *)sender;
+
+@end
+
+@implementation DisplayLinkObserver
+{
+ AVFDisplayLink *m_avfDisplayLink;
+ CADisplayLink *m_displayLink;
+}
+
+- (id)initWithAVFDisplayLink:(AVFDisplayLink *)link
+{
+ self = [super init];
+
+ if (self) {
+ m_avfDisplayLink = link;
+ m_displayLink = [[CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkNotification:)] retain];
+ }
+
+ return self;
+}
+
+- (void) dealloc
+{
+ if (m_displayLink) {
+ [m_displayLink release];
+ m_displayLink = nullptr;
+ }
+
+ [super dealloc];
+}
+
+- (void)start
+{
+ [m_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+}
+
+- (void)stop
+{
+ [m_displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+}
+
+- (void)displayLinkNotification:(CADisplayLink *)sender
+{
+ Q_UNUSED(sender);
+ m_avfDisplayLink->displayLinkEvent(nullptr);
+}
+
+@end
+#else
+static CVReturn CVDisplayLinkCallback(CVDisplayLinkRef displayLink,
+ const CVTimeStamp *inNow,
+ const CVTimeStamp *inOutputTime,
+ CVOptionFlags flagsIn,
+ CVOptionFlags *flagsOut,
+ void *displayLinkContext)
+{
+ Q_UNUSED(displayLink);
+ Q_UNUSED(inNow);
+ Q_UNUSED(flagsIn);
+ Q_UNUSED(flagsOut);
+
+ AVFDisplayLink *link = (AVFDisplayLink *)displayLinkContext;
+
+ link->displayLinkEvent(inOutputTime);
+ return kCVReturnSuccess;
+}
+#endif
+
+AVFDisplayLink::AVFDisplayLink(QObject *parent)
+ : QObject(parent)
+ , m_displayLink(nullptr)
+ , m_pendingDisplayLinkEvent(false)
+ , m_isActive(false)
+{
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ m_displayLink = [[DisplayLinkObserver alloc] initWithAVFDisplayLink:this];
+#else
+ // create display link for the main display
+ CVDisplayLinkCreateWithCGDisplay(kCGDirectMainDisplay, &m_displayLink);
+ if (m_displayLink) {
+ // set the current display of a display link.
+ CVDisplayLinkSetCurrentCGDisplay(m_displayLink, kCGDirectMainDisplay);
+
+ // set the renderer output callback function
+ CVDisplayLinkSetOutputCallback(m_displayLink, &CVDisplayLinkCallback, this);
+ }
+#endif
+}
+
+AVFDisplayLink::~AVFDisplayLink()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+
+ if (m_displayLink) {
+ stop();
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ [_m_displayLink release];
+#else
+ CVDisplayLinkRelease(m_displayLink);
+#endif
+ m_displayLink = nullptr;
+ }
+}
+
+bool AVFDisplayLink::isValid() const
+{
+ return m_displayLink != nullptr;
+}
+
+bool AVFDisplayLink::isActive() const
+{
+ return m_isActive;
+}
+
+void AVFDisplayLink::start()
+{
+ if (m_displayLink && !m_isActive) {
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ [_m_displayLink start];
+#else
+ CVDisplayLinkStart(m_displayLink);
+#endif
+ m_isActive = true;
+ }
+}
+
+void AVFDisplayLink::stop()
+{
+ if (m_displayLink && m_isActive) {
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ [_m_displayLink stop];
+#else
+ CVDisplayLinkStop(m_displayLink);
+#endif
+ m_isActive = false;
+ }
+}
+
+void AVFDisplayLink::displayLinkEvent(const CVTimeStamp *ts)
+{
+ // This function is called from a
+ // thread != gui thread. So we post the event.
+ // But we need to make sure that we don't post faster
+ // than the event loop can eat:
+ m_displayLinkMutex.lock();
+ bool pending = m_pendingDisplayLinkEvent;
+ m_pendingDisplayLinkEvent = true;
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ Q_UNUSED(ts);
+ memset(&m_frameTimeStamp, 0, sizeof(CVTimeStamp));
+#else
+ m_frameTimeStamp = *ts;
+#endif
+ m_displayLinkMutex.unlock();
+
+ if (!pending)
+ qApp->postEvent(this, new QEvent(QEvent::User), Qt::HighEventPriority);
+}
+
+bool AVFDisplayLink::event(QEvent *event)
+{
+ switch (event->type()){
+ case QEvent::User: {
+ m_displayLinkMutex.lock();
+ m_pendingDisplayLinkEvent = false;
+ CVTimeStamp ts = m_frameTimeStamp;
+ m_displayLinkMutex.unlock();
+
+ Q_EMIT tick(ts);
+
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+ return QObject::event(event);
+}
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink_p.h
new file mode 100644
index 000000000..6b95e1e07
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink_p.h
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFDISPLAYLINK_H
+#define AVFDISPLAYLINK_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qobject.h>
+#include <QtCore/qmutex.h>
+
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+#include <CoreVideo/CVBase.h>
+#else
+#include <QuartzCore/CVDisplayLink.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class AVFDisplayLink : public QObject
+{
+ Q_OBJECT
+public:
+ explicit AVFDisplayLink(QObject *parent = nullptr);
+ virtual ~AVFDisplayLink();
+ bool isValid() const;
+ bool isActive() const;
+
+public Q_SLOTS:
+ void start();
+ void stop();
+
+Q_SIGNALS:
+ void tick(const CVTimeStamp &ts);
+
+public:
+ void displayLinkEvent(const CVTimeStamp *);
+
+protected:
+ virtual bool event(QEvent *) override;
+
+private:
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ void *m_displayLink;
+#else
+ CVDisplayLinkRef m_displayLink;
+#endif
+ QMutex m_displayLinkMutex;
+ bool m_pendingDisplayLinkEvent;
+ bool m_isActive;
+ CVTimeStamp m_frameTimeStamp;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFDISPLAYLINK_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol.mm
new file mode 100644
index 000000000..764c1edf8
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol.mm
@@ -0,0 +1,192 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfmediaplayercontrol_p.h"
+#include "avfmediaplayersession_p.h"
+
+QT_USE_NAMESPACE
+
+AVFMediaPlayerControl::AVFMediaPlayerControl(QObject *parent) :
+ QMediaPlayerControl(parent)
+{
+}
+
+AVFMediaPlayerControl::~AVFMediaPlayerControl()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+}
+
+void AVFMediaPlayerControl::setSession(AVFMediaPlayerSession *session)
+{
+ m_session = session;
+
+ connect(m_session, SIGNAL(positionChanged(qint64)), this, SIGNAL(positionChanged(qint64)));
+ connect(m_session, SIGNAL(durationChanged(qint64)), this, SIGNAL(durationChanged(qint64)));
+ connect(m_session, SIGNAL(bufferStatusChanged(int)), this, SIGNAL(bufferStatusChanged(int)));
+ connect(m_session, SIGNAL(stateChanged(QMediaPlayer::State)),
+ this, SIGNAL(stateChanged(QMediaPlayer::State)));
+ connect(m_session, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)),
+ this, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
+ connect(m_session, SIGNAL(volumeChanged(int)), this, SIGNAL(volumeChanged(int)));
+ connect(m_session, SIGNAL(mutedChanged(bool)), this, SIGNAL(mutedChanged(bool)));
+ connect(m_session, SIGNAL(audioAvailableChanged(bool)), this, SIGNAL(audioAvailableChanged(bool)));
+ connect(m_session, SIGNAL(videoAvailableChanged(bool)), this, SIGNAL(videoAvailableChanged(bool)));
+ connect(m_session, SIGNAL(error(int,QString)), this, SIGNAL(error(int,QString)));
+ connect(m_session, &AVFMediaPlayerSession::playbackRateChanged,
+ this, &AVFMediaPlayerControl::playbackRateChanged);
+ connect(m_session, &AVFMediaPlayerSession::seekableChanged,
+ this, &AVFMediaPlayerControl::seekableChanged);
+}
+
+QMediaPlayer::State AVFMediaPlayerControl::state() const
+{
+ return m_session->state();
+}
+
+QMediaPlayer::MediaStatus AVFMediaPlayerControl::mediaStatus() const
+{
+ return m_session->mediaStatus();
+}
+
+QUrl AVFMediaPlayerControl::media() const
+{
+ return m_session->media();
+}
+
+const QIODevice *AVFMediaPlayerControl::mediaStream() const
+{
+ return m_session->mediaStream();
+}
+
+void AVFMediaPlayerControl::setMedia(const QUrl &content, QIODevice *stream)
+{
+ const QUrl oldContent = m_session->media();
+
+ m_session->setMedia(content, stream);
+
+ if (content != oldContent)
+ Q_EMIT mediaChanged(content);
+}
+
+qint64 AVFMediaPlayerControl::position() const
+{
+ return m_session->position();
+}
+
+qint64 AVFMediaPlayerControl::duration() const
+{
+ return m_session->duration();
+}
+
+int AVFMediaPlayerControl::bufferStatus() const
+{
+ return m_session->bufferStatus();
+}
+
+int AVFMediaPlayerControl::volume() const
+{
+ return m_session->volume();
+}
+
+bool AVFMediaPlayerControl::isMuted() const
+{
+ return m_session->isMuted();
+}
+
+bool AVFMediaPlayerControl::isAudioAvailable() const
+{
+ return m_session->isAudioAvailable();
+}
+
+bool AVFMediaPlayerControl::isVideoAvailable() const
+{
+ return m_session->isVideoAvailable();
+}
+
+bool AVFMediaPlayerControl::isSeekable() const
+{
+ return m_session->isSeekable();
+}
+
+QMediaTimeRange AVFMediaPlayerControl::availablePlaybackRanges() const
+{
+ return m_session->availablePlaybackRanges();
+}
+
+qreal AVFMediaPlayerControl::playbackRate() const
+{
+ return m_session->playbackRate();
+}
+
+void AVFMediaPlayerControl::setPlaybackRate(qreal rate)
+{
+ m_session->setPlaybackRate(rate);
+}
+
+void AVFMediaPlayerControl::setPosition(qint64 pos)
+{
+ m_session->setPosition(pos);
+}
+
+void AVFMediaPlayerControl::play()
+{
+ m_session->play();
+}
+
+void AVFMediaPlayerControl::pause()
+{
+ m_session->pause();
+}
+
+void AVFMediaPlayerControl::stop()
+{
+ m_session->stop();
+}
+
+void AVFMediaPlayerControl::setVolume(int volume)
+{
+ m_session->setVolume(volume);
+}
+
+void AVFMediaPlayerControl::setMuted(bool muted)
+{
+ m_session->setMuted(muted);
+}
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol_p.h
new file mode 100644
index 000000000..50a75427c
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol_p.h
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIAPLAYERCONTROL_H
+#define AVFMEDIAPLAYERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/QMediaPlayerControl>
+#include <QtCore/QObject>
+
+QT_BEGIN_NAMESPACE
+
+class AVFMediaPlayerSession;
+
+class AVFMediaPlayerControl : public QMediaPlayerControl
+{
+ Q_OBJECT
+public:
+ explicit AVFMediaPlayerControl(QObject *parent = nullptr);
+ ~AVFMediaPlayerControl();
+
+ void setSession(AVFMediaPlayerSession *session);
+
+ QMediaPlayer::State state() const override;
+ QMediaPlayer::MediaStatus mediaStatus() const override;
+
+ QUrl media() const override;
+ const QIODevice *mediaStream() const override;
+ void setMedia(const QUrl &content, QIODevice *stream) override;
+
+ qint64 position() const override;
+ qint64 duration() const override;
+
+ int bufferStatus() const override;
+
+ int volume() const override;
+ bool isMuted() const override;
+
+ bool isAudioAvailable() const override;
+ bool isVideoAvailable() const override;
+
+ bool isSeekable() const override;
+ QMediaTimeRange availablePlaybackRanges() const override;
+
+ qreal playbackRate() const override;
+ void setPlaybackRate(qreal rate) override;
+
+public Q_SLOTS:
+ void setPosition(qint64 pos) override;
+
+ void play() override;
+ void pause() override;
+ void stop() override;
+
+ void setVolume(int volume) override;
+ void setMuted(bool muted) override;
+
+private:
+ AVFMediaPlayerSession *m_session;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFMEDIAPLAYERCONTROL_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol.mm
new file mode 100644
index 000000000..5a5780e52
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol.mm
@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfmediaplayermetadatacontrol_p.h"
+#include "avfmediaplayersession_p.h"
+
+#include <QtMultimedia/qmediametadata.h>
+
+#import <AVFoundation/AVFoundation.h>
+
+QT_USE_NAMESPACE
+
+AVFMediaPlayerMetaDataControl::AVFMediaPlayerMetaDataControl(AVFMediaPlayerSession *session, QObject *parent)
+ : QMetaDataReaderControl(parent)
+ , m_session(session)
+ , m_asset(nullptr)
+{
+ QObject::connect(m_session, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(updateTags()));
+}
+
+AVFMediaPlayerMetaDataControl::~AVFMediaPlayerMetaDataControl()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+}
+
+bool AVFMediaPlayerMetaDataControl::isMetaDataAvailable() const
+{
+ return !m_tags.isEmpty();
+}
+
+bool AVFMediaPlayerMetaDataControl::isWritable() const
+{
+ return false;
+}
+
+QVariant AVFMediaPlayerMetaDataControl::metaData(const QString &key) const
+{
+ return m_tags.value(key);
+}
+
+QStringList AVFMediaPlayerMetaDataControl::availableMetaData() const
+{
+ return m_tags.keys();
+}
+
+static QString itemKey(AVMetadataItem *item)
+{
+ NSString *keyString = [item commonKey];
+
+ if (keyString.length != 0) {
+ if ([keyString isEqualToString:AVMetadataCommonKeyTitle]) {
+ return QMediaMetaData::Title;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeySubject]) {
+ return QMediaMetaData::SubTitle;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeyDescription]) {
+ return QMediaMetaData::Description;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeyPublisher]) {
+ return QMediaMetaData::Publisher;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeyCreationDate]) {
+ return QMediaMetaData::Date;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeyType]) {
+ return QMediaMetaData::MediaType;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeyLanguage]) {
+ return QMediaMetaData::Language;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeyCopyrights]) {
+ return QMediaMetaData::Copyright;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeyAlbumName]) {
+ return QMediaMetaData::AlbumTitle;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeyAuthor]) {
+ return QMediaMetaData::Author;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeyArtist]) {
+ return QMediaMetaData::ContributingArtist;
+ } else if ([keyString isEqualToString: AVMetadataCommonKeyArtwork]) {
+ return QMediaMetaData::PosterUrl;
+ }
+ }
+
+ return QString();
+}
+
+void AVFMediaPlayerMetaDataControl::updateTags()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+ AVAsset *currentAsset = static_cast<AVAsset*>(m_session->currentAssetHandle());
+
+ //Don't read the tags from the same asset more than once
+ if (currentAsset == m_asset)
+ return;
+
+ m_asset = currentAsset;
+
+ QVariantMap oldTags = m_tags;
+ //Since we've changed assets, clear old tags
+ m_tags.clear();
+ bool changed = false;
+
+ // TODO: also process ID3, iTunes and QuickTime metadata
+
+ NSArray *metadataItems = [currentAsset commonMetadata];
+ for (AVMetadataItem* item in metadataItems) {
+ const QString key = itemKey(item);
+ if (!key.isEmpty()) {
+ const QString value = QString::fromNSString([item stringValue]);
+ if (!value.isNull()) {
+ m_tags.insert(key, value);
+ if (value != oldTags.value(key)) {
+ changed = true;
+ Q_EMIT metaDataChanged(key, value);
+ }
+ }
+ }
+ }
+
+ if (oldTags.isEmpty() != m_tags.isEmpty()) {
+ Q_EMIT metaDataAvailableChanged(!m_tags.isEmpty());
+ changed = true;
+ }
+
+ if (changed)
+ Q_EMIT metaDataChanged();
+}
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol_p.h
new file mode 100644
index 000000000..1f8ecf26f
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol_p.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIAPLAYERMETADATACONTROL_H
+#define AVFMEDIAPLAYERMETADATACONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/QMetaDataReaderControl>
+#include <QtCore/qvariant.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFMediaPlayerSession;
+
+class AVFMediaPlayerMetaDataControl : public QMetaDataReaderControl
+{
+ Q_OBJECT
+public:
+ explicit AVFMediaPlayerMetaDataControl(AVFMediaPlayerSession *session, QObject *parent = nullptr);
+ virtual ~AVFMediaPlayerMetaDataControl();
+
+ bool isMetaDataAvailable() const override;
+ bool isWritable() const;
+
+ QVariant metaData(const QString &key) const override;
+ QStringList availableMetaData() const override;
+
+private Q_SLOTS:
+ void updateTags();
+
+private:
+ AVFMediaPlayerSession *m_session;
+ QVariantMap m_tags;
+ void *m_asset;
+
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFMEDIAPLAYERMETADATACONTROL_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice.mm
new file mode 100644
index 000000000..b8d3d4e82
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice.mm
@@ -0,0 +1,122 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfmediaplayerservice_p.h"
+#include "avfmediaplayersession_p.h"
+#include "avfmediaplayercontrol_p.h"
+#include "avfmediaplayermetadatacontrol_p.h"
+#include "avfvideooutput_p.h"
+#if QT_CONFIG(opengl)
+#include "avfvideorenderercontrol_p.h"
+#endif
+#include "avfvideowindowcontrol_p.h"
+
+#import <AVFoundation/AVFoundation.h>
+
+QT_USE_NAMESPACE
+
+AVFMediaPlayerService::AVFMediaPlayerService(QObject *parent)
+ : QMediaService(parent)
+ , m_videoOutput(nullptr)
+{
+ m_session = new AVFMediaPlayerSession(this);
+ m_control = new AVFMediaPlayerControl(this);
+ m_control->setSession(m_session);
+ m_playerMetaDataControl = new AVFMediaPlayerMetaDataControl(m_session, this);
+
+ connect(m_control, SIGNAL(mediaChanged(QMediaContent)), m_playerMetaDataControl, SLOT(updateTags()));
+}
+
+AVFMediaPlayerService::~AVFMediaPlayerService()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+ delete m_session;
+}
+
+QObject *AVFMediaPlayerService::requestControl(const char *name)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << name;
+#endif
+
+ if (qstrcmp(name, QMediaPlayerControl_iid) == 0)
+ return m_control;
+
+ if (qstrcmp(name, QMetaDataReaderControl_iid) == 0)
+ return m_playerMetaDataControl;
+
+#if QT_CONFIG(opengl)
+ if (qstrcmp(name, QVideoRendererControl_iid) == 0) {
+ if (!m_videoOutput)
+ m_videoOutput = new AVFVideoRendererControl(this);
+
+ m_session->setVideoOutput(qobject_cast<AVFVideoOutput*>(m_videoOutput));
+ return m_videoOutput;
+ }
+#endif
+ if (qstrcmp(name, QVideoWindowControl_iid) == 0) {
+ if (!m_videoOutput)
+ m_videoOutput = new AVFVideoWindowControl(this);
+
+ m_session->setVideoOutput(qobject_cast<AVFVideoOutput*>(m_videoOutput));
+ return m_videoOutput;
+ }
+ return nullptr;
+}
+
+void AVFMediaPlayerService::releaseControl(QObject *control)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << control;
+#endif
+ if (m_videoOutput == control) {
+#if QT_CONFIG(opengl)
+ AVFVideoRendererControl *renderControl = qobject_cast<AVFVideoRendererControl*>(m_videoOutput);
+
+ if (renderControl)
+ renderControl->setSurface(nullptr);
+#endif
+ m_videoOutput = nullptr;
+ m_session->setVideoOutput(nullptr);
+
+ delete control;
+ }
+}
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice_p.h
new file mode 100644
index 000000000..8d5f63da2
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice_p.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIAPLAYERSERVICE_H
+#define AVFMEDIAPLAYERSERVICE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/QMediaService>
+
+QT_BEGIN_NAMESPACE
+
+class AVFMediaPlayerSession;
+class AVFMediaPlayerControl;
+class AVFMediaPlayerMetaDataControl;
+class AVFVideoOutput;
+
+class AVFMediaPlayerService : public QMediaService
+{
+public:
+ explicit AVFMediaPlayerService(QObject *parent = nullptr);
+ ~AVFMediaPlayerService();
+
+ QObject *requestControl(const char *name) override;
+ void releaseControl(QObject *control) override;
+
+private:
+ AVFMediaPlayerSession *m_session;
+ AVFMediaPlayerControl *m_control;
+ QObject *m_videoOutput;
+ AVFMediaPlayerMetaDataControl *m_playerMetaDataControl;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFMEDIAPLAYERSERVICE_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin.mm
new file mode 100644
index 000000000..504909598
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin.mm
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfmediaplayerserviceplugin_p.h"
+#include <QtCore/QDebug>
+
+#include "avfmediaplayerservice_p.h"
+
+#import <AVFoundation/AVFoundation.h>
+
+QT_USE_NAMESPACE
+
+AVFMediaPlayerServicePlugin::AVFMediaPlayerServicePlugin()
+{
+ buildSupportedTypes();
+}
+
+QMediaService *AVFMediaPlayerServicePlugin::create(const QString &key)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << "AVFMediaPlayerServicePlugin::create" << key;
+#endif
+ if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER))
+ return new AVFMediaPlayerService;
+
+ qWarning() << "unsupported key: " << key;
+ return nullptr;
+}
+
+void AVFMediaPlayerServicePlugin::release(QMediaService *service)
+{
+ delete service;
+}
+
+QMultimedia::SupportEstimate AVFMediaPlayerServicePlugin::hasSupport(const QString &mimeType, const QStringList &codecs) const
+{
+ Q_UNUSED(codecs);
+
+ if (m_supportedMimeTypes.contains(mimeType))
+ return QMultimedia::ProbablySupported;
+
+ return QMultimedia::MaybeSupported;
+}
+
+QStringList AVFMediaPlayerServicePlugin::supportedMimeTypes() const
+{
+ return m_supportedMimeTypes;
+}
+
+void AVFMediaPlayerServicePlugin::buildSupportedTypes()
+{
+ //Populate m_supportedMimeTypes with mimetypes AVAsset supports
+ NSArray *mimeTypes = [AVURLAsset audiovisualMIMETypes];
+ for (NSString *mimeType in mimeTypes)
+ {
+ m_supportedMimeTypes.append(QString::fromUtf8([mimeType UTF8String]));
+ }
+#ifdef QT_DEBUG_AVF
+ qDebug() << "AVFMediaPlayerServicePlugin::buildSupportedTypes";
+ qDebug() << "Supported Types: " << m_supportedMimeTypes;
+#endif
+
+}
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin_p.h
new file mode 100644
index 000000000..d6e17c446
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef AVFMEDIAPLAYERSERVICEPLUGIN_H
+#define AVFMEDIAPLAYERSERVICEPLUGIN_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+#include <QtMultimedia/qmediaserviceproviderplugin.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFMediaPlayerServicePlugin
+ : public QMediaServiceProviderPlugin
+ , public QMediaServiceSupportedFormatsInterface
+{
+ Q_OBJECT
+ Q_INTERFACES(QMediaServiceSupportedFormatsInterface)
+
+public:
+ explicit AVFMediaPlayerServicePlugin();
+
+ QMediaService* create(QString const& key) override;
+ void release(QMediaService *service) override;
+
+ QMultimedia::SupportEstimate hasSupport(const QString &mimeType, const QStringList& codecs) const override;
+ QStringList supportedMimeTypes() const override;
+
+private:
+ void buildSupportedTypes();
+
+ QStringList m_supportedMimeTypes;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFMEDIAPLAYERSERVICEPLUGIN_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession.mm
new file mode 100644
index 000000000..d6824a3dc
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession.mm
@@ -0,0 +1,1067 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfmediaplayersession_p.h"
+#include "avfmediaplayerservice_p.h"
+#include "avfvideooutput_p.h"
+
+#include <qpointer.h>
+#include <QFileInfo>
+
+#import <AVFoundation/AVFoundation.h>
+
+QT_USE_NAMESPACE
+
+//AVAsset Keys
+static NSString* const AVF_TRACKS_KEY = @"tracks";
+static NSString* const AVF_PLAYABLE_KEY = @"playable";
+
+//AVPlayerItem keys
+static NSString* const AVF_STATUS_KEY = @"status";
+static NSString* const AVF_BUFFER_LIKELY_KEEP_UP_KEY = @"playbackLikelyToKeepUp";
+
+//AVPlayer keys
+static NSString* const AVF_RATE_KEY = @"rate";
+static NSString* const AVF_CURRENT_ITEM_KEY = @"currentItem";
+static NSString* const AVF_CURRENT_ITEM_DURATION_KEY = @"currentItem.duration";
+
+static void *AVFMediaPlayerSessionObserverRateObservationContext = &AVFMediaPlayerSessionObserverRateObservationContext;
+static void *AVFMediaPlayerSessionObserverStatusObservationContext = &AVFMediaPlayerSessionObserverStatusObservationContext;
+static void *AVFMediaPlayerSessionObserverBufferLikelyToKeepUpContext = &AVFMediaPlayerSessionObserverBufferLikelyToKeepUpContext;
+static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMediaPlayerSessionObserverCurrentItemObservationContext;
+static void *AVFMediaPlayerSessionObserverCurrentItemDurationObservationContext = &AVFMediaPlayerSessionObserverCurrentItemDurationObservationContext;
+
+@interface AVFMediaPlayerSessionObserver : NSObject<AVAssetResourceLoaderDelegate>
+
+@property (readonly, getter=player) AVPlayer* m_player;
+@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem;
+@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer;
+@property (readonly, getter=session) AVFMediaPlayerSession* m_session;
+
+- (AVFMediaPlayerSessionObserver *) initWithMediaPlayerSession:(AVFMediaPlayerSession *)session;
+- (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType;
+- (void) unloadMedia;
+- (void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys;
+- (void) assetFailedToPrepareForPlayback:(NSError *)error;
+- (void) playerItemDidReachEnd:(NSNotification *)notification;
+- (void) playerItemTimeJumped:(NSNotification *)notification;
+- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
+ change:(NSDictionary *)change context:(void *)context;
+- (void) detatchSession;
+- (void) dealloc;
+- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;
+@end
+
+@implementation AVFMediaPlayerSessionObserver
+{
+@private
+ AVFMediaPlayerSession *m_session;
+ AVPlayer *m_player;
+ AVPlayerItem *m_playerItem;
+ AVPlayerLayer *m_playerLayer;
+ NSURL *m_URL;
+ BOOL m_bufferIsLikelyToKeepUp;
+ NSData *m_data;
+ NSString *m_mimeType;
+}
+
+@synthesize m_player, m_playerItem, m_playerLayer, m_session;
+
+- (AVFMediaPlayerSessionObserver *) initWithMediaPlayerSession:(AVFMediaPlayerSession *)session
+{
+ if (!(self = [super init]))
+ return nil;
+
+ self->m_session = session;
+ self->m_bufferIsLikelyToKeepUp = FALSE;
+ return self;
+}
+
+- (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType
+{
+ [m_mimeType release];
+ m_mimeType = [mimeType retain];
+
+ if (m_URL != url)
+ {
+ [m_URL release];
+ m_URL = [url copy];
+
+ //Create an asset for inspection of a resource referenced by a given URL.
+ //Load the values for the asset keys "tracks", "playable".
+
+ // use __block to avoid maintaining strong references on variables captured by the
+ // following block callback
+ __block AVURLAsset *asset = [[AVURLAsset URLAssetWithURL:m_URL options:nil] retain];
+ [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
+
+ __block NSArray *requestedKeys = [[NSArray arrayWithObjects:AVF_TRACKS_KEY, AVF_PLAYABLE_KEY, nil] retain];
+
+ __block AVFMediaPlayerSessionObserver *blockSelf = self;
+ QPointer<AVFMediaPlayerSession> session(m_session);
+
+ // Tells the asset to load the values of any of the specified keys that are not already loaded.
+ [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
+ ^{
+ dispatch_async( dispatch_get_main_queue(),
+ ^{
+ if (session)
+ [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys];
+ [asset release];
+ [requestedKeys release];
+ });
+ }];
+ }
+}
+
+- (void) unloadMedia
+{
+ if (m_playerItem) {
+ [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY];
+ [m_playerItem removeObserver:self forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:AVPlayerItemDidPlayToEndTimeNotification
+ object:m_playerItem];
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:AVPlayerItemTimeJumpedNotification
+ object:m_playerItem];
+ m_playerItem = 0;
+ }
+ if (m_player) {
+ [m_player setRate:0.0];
+ [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY];
+ [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY];
+ [m_player removeObserver:self forKeyPath:AVF_RATE_KEY];
+ [m_player release];
+ m_player = 0;
+ }
+ if (m_playerLayer) {
+ [m_playerLayer release];
+ m_playerLayer = 0;
+ }
+#if defined(Q_OS_IOS)
+ [[AVAudioSession sharedInstance] setActive:NO error:nil];
+#endif
+}
+
+- (void) prepareToPlayAsset:(AVURLAsset *)asset
+ withKeys:(NSArray *)requestedKeys
+{
+ //Make sure that the value of each key has loaded successfully.
+ for (NSString *thisKey in requestedKeys)
+ {
+ NSError *error = nil;
+ AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << [thisKey UTF8String] << " status: " << keyStatus;
+#endif
+ if (keyStatus == AVKeyValueStatusFailed)
+ {
+ [self assetFailedToPrepareForPlayback:error];
+ return;
+ }
+ }
+
+ //Use the AVAsset playable property to detect whether the asset can be played.
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << "isPlayable: " << [asset isPlayable];
+#endif
+ if (!asset.playable)
+ {
+ //Generate an error describing the failure.
+ NSString *localizedDescription = NSLocalizedString(@"Item cannot be played", @"Item cannot be played description");
+ NSString *localizedFailureReason = NSLocalizedString(@"The assets tracks were loaded, but could not be made playable.", @"Item cannot be played failure reason");
+ NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ localizedDescription, NSLocalizedDescriptionKey,
+ localizedFailureReason, NSLocalizedFailureReasonErrorKey,
+ nil];
+ NSError *assetCannotBePlayedError = [NSError errorWithDomain:@"StitchedStreamPlayer" code:0 userInfo:errorDict];
+
+ [self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
+
+ return;
+ }
+
+ //At this point we're ready to set up for playback of the asset.
+ //Stop observing our prior AVPlayerItem, if we have one.
+ if (m_playerItem)
+ {
+ //Remove existing player item key value observers and notifications.
+ [self unloadMedia];
+ }
+
+ //Create a new instance of AVPlayerItem from the now successfully loaded AVAsset.
+ m_playerItem = [AVPlayerItem playerItemWithAsset:asset];
+
+ //Observe the player item "status" key to determine when it is ready to play.
+ [m_playerItem addObserver:self
+ forKeyPath:AVF_STATUS_KEY
+ options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
+ context:AVFMediaPlayerSessionObserverStatusObservationContext];
+
+ [m_playerItem addObserver:self
+ forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY
+ options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
+ context:AVFMediaPlayerSessionObserverBufferLikelyToKeepUpContext];
+
+ //When the player item has played to its end time we'll toggle
+ //the movie controller Pause button to be the Play button
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(playerItemDidReachEnd:)
+ name:AVPlayerItemDidPlayToEndTimeNotification
+ object:m_playerItem];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(playerItemTimeJumped:)
+ name:AVPlayerItemTimeJumpedNotification
+ object:m_playerItem];
+
+ //Get a new AVPlayer initialized to play the specified player item.
+ m_player = [AVPlayer playerWithPlayerItem:m_playerItem];
+ [m_player retain];
+
+ //Set the initial volume on new player object
+ if (self.session) {
+ [m_player setVolume:m_session->volume() / 100.0f];
+ [m_player setMuted:m_session->isMuted()];
+ }
+
+ //Create a new player layer if we don't have one already
+ if (!m_playerLayer)
+ {
+ m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:m_player];
+ [m_playerLayer retain];
+ m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
+ m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f);
+ }
+
+ //Observe the AVPlayer "currentItem" property to find out when any
+ //AVPlayer replaceCurrentItemWithPlayerItem: replacement will/did
+ //occur.
+ [m_player addObserver:self
+ forKeyPath:AVF_CURRENT_ITEM_KEY
+ options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
+ context:AVFMediaPlayerSessionObserverCurrentItemObservationContext];
+
+ //Observe the AVPlayer "rate" property to update the scrubber control.
+ [m_player addObserver:self
+ forKeyPath:AVF_RATE_KEY
+ options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
+ context:AVFMediaPlayerSessionObserverRateObservationContext];
+
+ //Observe the duration for getting the buffer state
+ [m_player addObserver:self
+ forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY
+ options:0
+ context:AVFMediaPlayerSessionObserverCurrentItemDurationObservationContext];
+#if defined(Q_OS_IOS)
+ [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
+ [[AVAudioSession sharedInstance] setActive:YES error:nil];
+#endif
+}
+
+-(void) assetFailedToPrepareForPlayback:(NSError *)error
+{
+ Q_UNUSED(error);
+ QMetaObject::invokeMethod(m_session, "processMediaLoadError", Qt::AutoConnection);
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+ qDebug() << [[error localizedDescription] UTF8String];
+ qDebug() << [[error localizedFailureReason] UTF8String];
+ qDebug() << [[error localizedRecoverySuggestion] UTF8String];
+#endif
+}
+
+- (void) playerItemDidReachEnd:(NSNotification *)notification
+{
+ Q_UNUSED(notification);
+ if (self.session)
+ QMetaObject::invokeMethod(m_session, "processEOS", Qt::AutoConnection);
+}
+
+- (void) playerItemTimeJumped:(NSNotification *)notification
+{
+ Q_UNUSED(notification);
+ if (self.session)
+ QMetaObject::invokeMethod(m_session, "processPositionChange", Qt::AutoConnection);
+}
+
+- (void) observeValueForKeyPath:(NSString*) path
+ ofObject:(id)object
+ change:(NSDictionary*)change
+ context:(void*)context
+{
+ //AVPlayerItem "status" property value observer.
+ if (context == AVFMediaPlayerSessionObserverStatusObservationContext)
+ {
+ AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue];
+ switch (status)
+ {
+ //Indicates that the status of the player is not yet known because
+ //it has not tried to load new media resources for playback
+ case AVPlayerStatusUnknown:
+ {
+ //QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection);
+ }
+ break;
+
+ case AVPlayerStatusReadyToPlay:
+ {
+ //Once the AVPlayerItem becomes ready to play, i.e.
+ //[playerItem status] == AVPlayerItemStatusReadyToPlay,
+ //its duration can be fetched from the item.
+ if (self.session)
+ QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection);
+ }
+ break;
+
+ case AVPlayerStatusFailed:
+ {
+ AVPlayerItem *playerItem = static_cast<AVPlayerItem*>(object);
+ [self assetFailedToPrepareForPlayback:playerItem.error];
+
+ if (self.session)
+ QMetaObject::invokeMethod(m_session, "processLoadStateFailure", Qt::AutoConnection);
+ }
+ break;
+ }
+ }
+ else if (context == AVFMediaPlayerSessionObserverBufferLikelyToKeepUpContext)
+ {
+ const bool isPlaybackLikelyToKeepUp = [m_playerItem isPlaybackLikelyToKeepUp];
+ if (isPlaybackLikelyToKeepUp != m_bufferIsLikelyToKeepUp) {
+ m_bufferIsLikelyToKeepUp = isPlaybackLikelyToKeepUp;
+ QMetaObject::invokeMethod(m_session, "processBufferStateChange", Qt::AutoConnection,
+ Q_ARG(int, isPlaybackLikelyToKeepUp ? 100 : 0));
+ }
+ }
+ //AVPlayer "rate" property value observer.
+ else if (context == AVFMediaPlayerSessionObserverRateObservationContext)
+ {
+ //QMetaObject::invokeMethod(m_session, "setPlaybackRate", Qt::AutoConnection, Q_ARG(qreal, [m_player rate]));
+ }
+ //AVPlayer "currentItem" property observer.
+ //Called when the AVPlayer replaceCurrentItemWithPlayerItem:
+ //replacement will/did occur.
+ else if (context == AVFMediaPlayerSessionObserverCurrentItemObservationContext)
+ {
+ AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
+ if (m_playerItem != newPlayerItem)
+ m_playerItem = newPlayerItem;
+ }
+ else if (context == AVFMediaPlayerSessionObserverCurrentItemDurationObservationContext)
+ {
+ const CMTime time = [m_playerItem duration];
+ const qint64 duration = static_cast<qint64>(float(time.value) / float(time.timescale) * 1000.0f);
+ if (self.session)
+ QMetaObject::invokeMethod(m_session, "processDurationChange", Qt::AutoConnection, Q_ARG(qint64, duration));
+ }
+ else
+ {
+ [super observeValueForKeyPath:path ofObject:object change:change context:context];
+ }
+}
+
+- (void) detatchSession
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+ m_session = 0;
+}
+
+- (void) dealloc
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+ [self unloadMedia];
+
+ if (m_URL) {
+ [m_URL release];
+ }
+
+ [m_mimeType release];
+ [super dealloc];
+}
+
+- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
+{
+ Q_UNUSED(resourceLoader);
+
+ if (![loadingRequest.request.URL.scheme isEqualToString:@"iodevice"])
+ return NO;
+
+ QIODevice *device = m_session->mediaStream();
+ if (!device)
+ return NO;
+
+ device->seek(loadingRequest.dataRequest.requestedOffset);
+ if (loadingRequest.contentInformationRequest) {
+ loadingRequest.contentInformationRequest.contentType = m_mimeType;
+ loadingRequest.contentInformationRequest.contentLength = device->size();
+ loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
+ }
+
+ if (loadingRequest.dataRequest) {
+ NSInteger requestedLength = loadingRequest.dataRequest.requestedLength;
+ int maxBytes = qMin(32 * 1064, int(requestedLength));
+ char buffer[maxBytes];
+ NSInteger submitted = 0;
+ while (submitted < requestedLength) {
+ qint64 len = device->read(buffer, maxBytes);
+ if (len < 1)
+ break;
+
+ [loadingRequest.dataRequest respondWithData:[NSData dataWithBytes:buffer length:len]];
+ submitted += len;
+ }
+
+ // Finish loading even if not all bytes submitted.
+ [loadingRequest finishLoading];
+ }
+
+ return YES;
+}
+@end
+
+AVFMediaPlayerSession::AVFMediaPlayerSession(AVFMediaPlayerService *service, QObject *parent)
+ : QObject(parent)
+ , m_service(service)
+ , m_videoOutput(nullptr)
+ , m_state(QMediaPlayer::StoppedState)
+ , m_mediaStatus(QMediaPlayer::NoMedia)
+ , m_mediaStream(nullptr)
+ , m_muted(false)
+ , m_tryingAsync(false)
+ , m_volume(100)
+ , m_rate(1.0)
+ , m_requestedPosition(-1)
+ , m_duration(0)
+ , m_bufferStatus(0)
+ , m_videoAvailable(false)
+ , m_audioAvailable(false)
+ , m_seekable(false)
+{
+ m_observer = [[AVFMediaPlayerSessionObserver alloc] initWithMediaPlayerSession:this];
+}
+
+AVFMediaPlayerSession::~AVFMediaPlayerSession()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+ //Detatch the session from the sessionObserver (which could still be alive trying to communicate with this session).
+ [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) detatchSession];
+ [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) release];
+}
+
+void AVFMediaPlayerSession::setVideoOutput(AVFVideoOutput *output)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << output;
+#endif
+
+ if (m_videoOutput == output)
+ return;
+
+ //Set the current output layer to null to stop rendering
+ if (m_videoOutput) {
+ m_videoOutput->setLayer(nullptr);
+ }
+
+ m_videoOutput = output;
+
+ if (m_videoOutput && m_state != QMediaPlayer::StoppedState)
+ m_videoOutput->setLayer([static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerLayer]);
+}
+
+void *AVFMediaPlayerSession::currentAssetHandle()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+ AVAsset *currentAsset = [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerItem] asset];
+ return currentAsset;
+}
+
+QMediaPlayer::State AVFMediaPlayerSession::state() const
+{
+ return m_state;
+}
+
+QMediaPlayer::MediaStatus AVFMediaPlayerSession::mediaStatus() const
+{
+ return m_mediaStatus;
+}
+
+QUrl AVFMediaPlayerSession::media() const
+{
+ return m_resources;
+}
+
+QIODevice *AVFMediaPlayerSession::mediaStream() const
+{
+ return m_mediaStream;
+}
+
+static void setURL(void *observer, const QByteArray &url, const QString &mimeType = QString())
+{
+ NSString *urlString = [NSString stringWithUTF8String:url.constData()];
+ NSURL *nsurl = [NSURL URLWithString:urlString];
+ [static_cast<AVFMediaPlayerSessionObserver*>(observer) setURL:nsurl mimeType:[NSString stringWithUTF8String:mimeType.toLatin1().constData()]];
+}
+
+static void setStreamURL(void *observer, const QByteArray &url)
+{
+ setURL(observer, QByteArrayLiteral("iodevice://") + url, QFileInfo(url).suffix());
+}
+
+void AVFMediaPlayerSession::setMedia(const QUrl &content, QIODevice *stream)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << content.request().url();
+#endif
+
+ [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) unloadMedia];
+
+ m_resources = content;
+ resetStream(stream);
+
+ setAudioAvailable(false);
+ setVideoAvailable(false);
+ setSeekable(false);
+ m_requestedPosition = -1;
+ Q_EMIT positionChanged(position());
+
+ const QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatus;
+ const QMediaPlayer::State oldState = m_state;
+
+ if (!m_mediaStream && content.isEmpty()) {
+ m_mediaStatus = QMediaPlayer::NoMedia;
+ if (m_mediaStatus != oldMediaStatus)
+ Q_EMIT mediaStatusChanged(m_mediaStatus);
+
+ m_state = QMediaPlayer::StoppedState;
+ if (m_state != oldState)
+ Q_EMIT stateChanged(m_state);
+
+ return;
+ }
+
+ m_mediaStatus = QMediaPlayer::LoadingMedia;
+ if (m_mediaStatus != oldMediaStatus)
+ Q_EMIT mediaStatusChanged(m_mediaStatus);
+
+ if (m_mediaStream) {
+ // If there is a data, try to load it,
+ // otherwise wait for readyRead.
+ if (m_mediaStream->size())
+ setStreamURL(m_observer, m_resources.toEncoded());
+ } else {
+ //Load AVURLAsset
+ //initialize asset using content's URL
+ setURL(m_observer, m_resources.toEncoded());
+ }
+
+ m_state = QMediaPlayer::StoppedState;
+ if (m_state != oldState)
+ Q_EMIT stateChanged(m_state);
+}
+
+qint64 AVFMediaPlayerSession::position() const
+{
+ AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerItem];
+
+ if (!playerItem)
+ return m_requestedPosition != -1 ? m_requestedPosition : 0;
+
+ CMTime time = [playerItem currentTime];
+ return static_cast<quint64>(float(time.value) / float(time.timescale) * 1000.0f);
+}
+
+qint64 AVFMediaPlayerSession::duration() const
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+ return m_duration;
+}
+
+int AVFMediaPlayerSession::bufferStatus() const
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+ return m_bufferStatus;
+}
+
+int AVFMediaPlayerSession::volume() const
+{
+ return m_volume;
+}
+
+bool AVFMediaPlayerSession::isMuted() const
+{
+ return m_muted;
+}
+
+void AVFMediaPlayerSession::setAudioAvailable(bool available)
+{
+ if (m_audioAvailable == available)
+ return;
+
+ m_audioAvailable = available;
+ Q_EMIT audioAvailableChanged(available);
+}
+
+bool AVFMediaPlayerSession::isAudioAvailable() const
+{
+ return m_audioAvailable;
+}
+
+void AVFMediaPlayerSession::setVideoAvailable(bool available)
+{
+ if (m_videoAvailable == available)
+ return;
+
+ m_videoAvailable = available;
+ Q_EMIT videoAvailableChanged(available);
+}
+
+bool AVFMediaPlayerSession::isVideoAvailable() const
+{
+ return m_videoAvailable;
+}
+
+bool AVFMediaPlayerSession::isSeekable() const
+{
+ return m_seekable;
+}
+
+void AVFMediaPlayerSession::setSeekable(bool seekable)
+{
+ if (m_seekable == seekable)
+ return;
+
+ m_seekable = seekable;
+ Q_EMIT seekableChanged(seekable);
+}
+
+QMediaTimeRange AVFMediaPlayerSession::availablePlaybackRanges() const
+{
+ AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerItem];
+
+ if (playerItem) {
+ QMediaTimeRange timeRanges;
+
+ NSArray *ranges = [playerItem loadedTimeRanges];
+ for (NSValue *timeRange in ranges) {
+ CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue];
+ qint64 startTime = qint64(float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0);
+ timeRanges.addInterval(startTime, startTime + qint64(float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0));
+ }
+ if (!timeRanges.isEmpty())
+ return timeRanges;
+ }
+ return QMediaTimeRange(0, duration());
+}
+
+qreal AVFMediaPlayerSession::playbackRate() const
+{
+ return m_rate;
+}
+
+void AVFMediaPlayerSession::setPlaybackRate(qreal rate)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << rate;
+#endif
+
+ if (qFuzzyCompare(m_rate, rate))
+ return;
+
+ m_rate = rate;
+
+ AVPlayer *player = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player];
+ if (player && m_state == QMediaPlayer::PlayingState)
+ [player setRate:m_rate];
+
+ Q_EMIT playbackRateChanged(m_rate);
+}
+
+void AVFMediaPlayerSession::setPosition(qint64 pos)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << pos;
+#endif
+
+ if (pos == position())
+ return;
+
+ AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerItem];
+ if (!playerItem) {
+ m_requestedPosition = pos;
+ Q_EMIT positionChanged(m_requestedPosition);
+ return;
+ }
+
+ if (!isSeekable()) {
+ if (m_requestedPosition != -1) {
+ m_requestedPosition = -1;
+ Q_EMIT positionChanged(position());
+ }
+ return;
+ }
+
+ pos = qMax(qint64(0), pos);
+ if (duration() > 0)
+ pos = qMin(pos, duration());
+
+ CMTime newTime = [playerItem currentTime];
+ newTime.value = (pos / 1000.0f) * newTime.timescale;
+ [playerItem seekToTime:newTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:nil];
+
+ Q_EMIT positionChanged(pos);
+
+ // Reset media status if the current status is EndOfMedia
+ if (m_mediaStatus == QMediaPlayer::EndOfMedia) {
+ QMediaPlayer::MediaStatus newMediaStatus = (m_state == QMediaPlayer::PausedState) ? QMediaPlayer::BufferedMedia
+ : QMediaPlayer::LoadedMedia;
+ Q_EMIT mediaStatusChanged((m_mediaStatus = newMediaStatus));
+ }
+}
+
+void AVFMediaPlayerSession::play()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << "currently: " << m_state;
+#endif
+
+ if (m_mediaStatus == QMediaPlayer::NoMedia || m_mediaStatus == QMediaPlayer::InvalidMedia)
+ return;
+
+ if (m_state == QMediaPlayer::PlayingState)
+ return;
+
+ if (m_videoOutput) {
+ m_videoOutput->setLayer([static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerLayer]);
+ }
+
+ // Reset media status if the current status is EndOfMedia
+ if (m_mediaStatus == QMediaPlayer::EndOfMedia)
+ setPosition(0);
+
+ if (m_mediaStatus == QMediaPlayer::LoadedMedia || m_mediaStatus == QMediaPlayer::BufferedMedia) {
+ // Setting the rate starts playback
+ [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] setRate:m_rate];
+ }
+
+ m_state = QMediaPlayer::PlayingState;
+ processLoadStateChange();
+
+ Q_EMIT stateChanged(m_state);
+}
+
+void AVFMediaPlayerSession::pause()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << "currently: " << m_state;
+#endif
+
+ if (m_mediaStatus == QMediaPlayer::NoMedia)
+ return;
+
+ if (m_state == QMediaPlayer::PausedState)
+ return;
+
+ m_state = QMediaPlayer::PausedState;
+
+ if (m_videoOutput) {
+ m_videoOutput->setLayer([static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerLayer]);
+ }
+
+ [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] pause];
+
+ // Reset media status if the current status is EndOfMedia
+ if (m_mediaStatus == QMediaPlayer::EndOfMedia)
+ setPosition(0);
+
+
+ Q_EMIT stateChanged(m_state);
+}
+
+void AVFMediaPlayerSession::stop()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << "currently: " << m_state;
+#endif
+
+ if (m_state == QMediaPlayer::StoppedState)
+ return;
+
+ // AVPlayer doesn't have stop(), only pause() and play().
+ [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] pause];
+ setPosition(0);
+
+ if (m_videoOutput) {
+ m_videoOutput->setLayer(nullptr);
+ }
+
+ if (m_mediaStatus == QMediaPlayer::BufferedMedia)
+ Q_EMIT mediaStatusChanged((m_mediaStatus = QMediaPlayer::LoadedMedia));
+
+ Q_EMIT positionChanged(position());
+ Q_EMIT stateChanged((m_state = QMediaPlayer::StoppedState));
+}
+
+void AVFMediaPlayerSession::setVolume(int volume)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << volume;
+#endif
+
+ if (m_volume == volume)
+ return;
+
+ m_volume = volume;
+
+ AVPlayer *player = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player];
+ if (player)
+ [player setVolume:volume / 100.0f];
+
+ Q_EMIT volumeChanged(m_volume);
+}
+
+void AVFMediaPlayerSession::setMuted(bool muted)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << muted;
+#endif
+
+ if (m_muted == muted)
+ return;
+
+ m_muted = muted;
+
+ AVPlayer *player = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player];
+ if (player)
+ [player setMuted:muted];
+
+ Q_EMIT mutedChanged(muted);
+}
+
+void AVFMediaPlayerSession::processEOS()
+{
+ //AVPlayerItem has reached end of track/stream
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+ Q_EMIT positionChanged(position());
+ m_mediaStatus = QMediaPlayer::EndOfMedia;
+ m_state = QMediaPlayer::StoppedState;
+
+ // At this point, frames should not be rendered anymore.
+ // Clear the output layer to make sure of that.
+ if (m_videoOutput)
+ m_videoOutput->setLayer(nullptr);
+
+ Q_EMIT mediaStatusChanged(m_mediaStatus);
+ Q_EMIT stateChanged(m_state);
+}
+
+void AVFMediaPlayerSession::processLoadStateChange(QMediaPlayer::State newState)
+{
+ AVPlayerStatus currentStatus = [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] status];
+
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO << currentStatus << ", " << m_mediaStatus << ", " << newState;
+#endif
+
+ if (m_mediaStatus == QMediaPlayer::NoMedia)
+ return;
+
+ if (currentStatus == AVPlayerStatusReadyToPlay) {
+
+ QMediaPlayer::MediaStatus newStatus = m_mediaStatus;
+
+ AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerItem];
+
+ if (playerItem) {
+ // Check each track for audio and video content
+ AVAssetTrack *videoTrack = nil;
+ NSArray *tracks = playerItem.tracks;
+ for (AVPlayerItemTrack *track in tracks) {
+ AVAssetTrack *assetTrack = track.assetTrack;
+ if (assetTrack) {
+ if ([assetTrack.mediaType isEqualToString:AVMediaTypeAudio])
+ setAudioAvailable(true);
+ if ([assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
+ setVideoAvailable(true);
+ if (!videoTrack)
+ videoTrack = assetTrack;
+ }
+ }
+ }
+
+ setSeekable([[playerItem seekableTimeRanges] count] > 0);
+
+ // Get the native size of the video, and reset the bounds of the player layer
+ AVPlayerLayer *playerLayer = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerLayer];
+ if (videoTrack && playerLayer) {
+ if (!playerLayer.bounds.size.width || !playerLayer.bounds.size.height) {
+ playerLayer.bounds = CGRectMake(0.0f, 0.0f,
+ videoTrack.naturalSize.width,
+ videoTrack.naturalSize.height);
+ }
+
+ if (m_videoOutput && newState != QMediaPlayer::StoppedState) {
+ m_videoOutput->setLayer(playerLayer);
+ }
+ }
+
+ if (m_requestedPosition != -1) {
+ setPosition(m_requestedPosition);
+ m_requestedPosition = -1;
+ }
+ }
+
+ newStatus = (newState != QMediaPlayer::StoppedState) ? QMediaPlayer::BufferedMedia
+ : QMediaPlayer::LoadedMedia;
+
+ if (newStatus != m_mediaStatus)
+ Q_EMIT mediaStatusChanged((m_mediaStatus = newStatus));
+
+ }
+
+ if (newState == QMediaPlayer::PlayingState && [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player]) {
+ // Setting the rate is enough to start playback, no need to call play()
+ [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] setRate:m_rate];
+ }
+}
+
+
+void AVFMediaPlayerSession::processLoadStateChange()
+{
+ processLoadStateChange(m_state);
+}
+
+
+void AVFMediaPlayerSession::processLoadStateFailure()
+{
+ Q_EMIT stateChanged((m_state = QMediaPlayer::StoppedState));
+}
+
+void AVFMediaPlayerSession::processBufferStateChange(int bufferStatus)
+{
+ if (bufferStatus == m_bufferStatus)
+ return;
+
+ auto status = m_mediaStatus;
+ // Buffered -> unbuffered.
+ if (!bufferStatus) {
+ status = QMediaPlayer::StalledMedia;
+ } else if (status == QMediaPlayer::StalledMedia) {
+ status = QMediaPlayer::BufferedMedia;
+ // Resume playback.
+ if (m_state == QMediaPlayer::PlayingState)
+ [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] setRate:m_rate];
+ }
+
+ if (m_mediaStatus != status)
+ Q_EMIT mediaStatusChanged(m_mediaStatus = status);
+
+ m_bufferStatus = bufferStatus;
+ Q_EMIT bufferStatusChanged(bufferStatus);
+}
+
+void AVFMediaPlayerSession::processDurationChange(qint64 duration)
+{
+ if (duration == m_duration)
+ return;
+
+ m_duration = duration;
+ Q_EMIT durationChanged(duration);
+}
+
+void AVFMediaPlayerSession::processPositionChange()
+{
+ if (m_state == QMediaPlayer::StoppedState)
+ return;
+
+ Q_EMIT positionChanged(position());
+}
+
+void AVFMediaPlayerSession::processMediaLoadError()
+{
+ if (m_requestedPosition != -1) {
+ m_requestedPosition = -1;
+ Q_EMIT positionChanged(position());
+ }
+
+ Q_EMIT mediaStatusChanged((m_mediaStatus = QMediaPlayer::InvalidMedia));
+
+ Q_EMIT error(QMediaPlayer::FormatError, tr("Failed to load media"));
+}
+
+void AVFMediaPlayerSession::streamReady()
+{
+ setStreamURL(m_observer, m_resources.toEncoded());
+}
+
+void AVFMediaPlayerSession::streamDestroyed()
+{
+ resetStream(nullptr);
+}
+
+void AVFMediaPlayerSession::resetStream(QIODevice *stream)
+{
+ if (m_mediaStream) {
+ disconnect(m_mediaStream, &QIODevice::readyRead, this, &AVFMediaPlayerSession::streamReady);
+ disconnect(m_mediaStream, &QIODevice::destroyed, this, &AVFMediaPlayerSession::streamDestroyed);
+ }
+
+ m_mediaStream = stream;
+
+ if (m_mediaStream) {
+ connect(m_mediaStream, &QIODevice::readyRead, this, &AVFMediaPlayerSession::streamReady);
+ connect(m_mediaStream, &QIODevice::destroyed, this, &AVFMediaPlayerSession::streamDestroyed);
+ }
+}
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession_p.h
new file mode 100644
index 000000000..c3728c524
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession_p.h
@@ -0,0 +1,172 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFMEDIAPLAYERSESSION_H
+#define AVFMEDIAPLAYERSESSION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QObject>
+#include <QtCore/QByteArray>
+#include <QtCore/QSet>
+#include <QtCore/QResource>
+
+#include <QtMultimedia/QMediaPlayerControl>
+#include <QtMultimedia/QMediaPlayer>
+
+QT_BEGIN_NAMESPACE
+
+class AVFMediaPlayerService;
+class AVFVideoOutput;
+
+class AVFMediaPlayerSession : public QObject
+{
+ Q_OBJECT
+public:
+ AVFMediaPlayerSession(AVFMediaPlayerService *service, QObject *parent = nullptr);
+ virtual ~AVFMediaPlayerSession();
+
+ void setVideoOutput(AVFVideoOutput *output);
+ void *currentAssetHandle();
+
+ QMediaPlayer::State state() const;
+ QMediaPlayer::MediaStatus mediaStatus() const;
+
+ QUrl media() const;
+ QIODevice *mediaStream() const;
+ void setMedia(const QUrl &content, QIODevice *stream);
+
+ qint64 position() const;
+ qint64 duration() const;
+
+ int bufferStatus() const;
+
+ int volume() const;
+ bool isMuted() const;
+
+ bool isAudioAvailable() const;
+ bool isVideoAvailable() const;
+
+ bool isSeekable() const;
+ QMediaTimeRange availablePlaybackRanges() const;
+
+ qreal playbackRate() const;
+
+public Q_SLOTS:
+ void setPlaybackRate(qreal rate);
+
+ void setPosition(qint64 pos);
+
+ void play();
+ void pause();
+ void stop();
+
+ void setVolume(int volume);
+ void setMuted(bool muted);
+
+ void processEOS();
+ void processLoadStateChange(QMediaPlayer::State newState);
+ void processPositionChange();
+ void processMediaLoadError();
+
+ void processLoadStateChange();
+ void processLoadStateFailure();
+
+ void processBufferStateChange(int bufferStatus);
+
+ void processDurationChange(qint64 duration);
+
+ void streamReady();
+ void streamDestroyed();
+
+Q_SIGNALS:
+ void positionChanged(qint64 position);
+ void durationChanged(qint64 duration);
+ void stateChanged(QMediaPlayer::State newState);
+ void bufferStatusChanged(int bufferStatus);
+ void mediaStatusChanged(QMediaPlayer::MediaStatus status);
+ void volumeChanged(int volume);
+ void mutedChanged(bool muted);
+ void audioAvailableChanged(bool audioAvailable);
+ void videoAvailableChanged(bool videoAvailable);
+ void playbackRateChanged(qreal rate);
+ void seekableChanged(bool seekable);
+ void error(int error, const QString &errorString);
+
+private:
+ void setAudioAvailable(bool available);
+ void setVideoAvailable(bool available);
+ void setSeekable(bool seekable);
+ void resetStream(QIODevice *stream = nullptr);
+
+ AVFMediaPlayerService *m_service;
+ AVFVideoOutput *m_videoOutput;
+
+ QMediaPlayer::State m_state;
+ QMediaPlayer::MediaStatus m_mediaStatus;
+ QIODevice *m_mediaStream;
+ QUrl m_resources;
+
+ bool m_muted;
+ bool m_tryingAsync;
+ int m_volume;
+ qreal m_rate;
+ qint64 m_requestedPosition;
+
+ qint64 m_duration;
+ int m_bufferStatus;
+ bool m_videoAvailable;
+ bool m_audioAvailable;
+ bool m_seekable;
+
+ void *m_observer;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFMEDIAPLAYERSESSION_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer.mm
new file mode 100644
index 000000000..88c83f1a8
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer.mm
@@ -0,0 +1,464 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfvideoframerenderer_p.h"
+
+#include <QtMultimedia/qabstractvideosurface.h>
+#include <QtOpenGL/QOpenGLFramebufferObject>
+#include <QtGui/QWindow>
+#include <QOpenGLShaderProgram>
+
+#ifdef QT_DEBUG_AVF
+#include <QtCore/qdebug.h>
+#endif
+
+#import <CoreVideo/CVBase.h>
+#import <AVFoundation/AVFoundation.h>
+
+QT_USE_NAMESPACE
+
+AVFVideoFrameRenderer::AVFVideoFrameRenderer(QAbstractVideoSurface *surface, QObject *parent)
+ : QObject(parent)
+ , m_videoLayerRenderer(nullptr)
+ , m_surface(surface)
+ , m_offscreenSurface(nullptr)
+ , m_glContext(nullptr)
+ , m_currentBuffer(1)
+ , m_isContextShared(true)
+{
+ m_fbo[0] = nullptr;
+ m_fbo[1] = nullptr;
+}
+
+AVFVideoFrameRenderer::~AVFVideoFrameRenderer()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+
+ [m_videoLayerRenderer release];
+ [m_metalDevice release];
+ delete m_fbo[0];
+ delete m_fbo[1];
+ delete m_offscreenSurface;
+ delete m_glContext;
+
+ if (m_useCoreProfile) {
+ glDeleteVertexArrays(1, &m_quadVao);
+ glDeleteBuffers(2, m_quadVbos);
+ delete m_shader;
+ }
+}
+
+quint64 AVFVideoFrameRenderer::renderLayerToTexture(AVPlayerLayer *layer)
+{
+ //Is layer valid
+ if (!layer)
+ return 0;
+
+ //If the glContext isn't shared, it doesn't make sense to return a texture for us
+ if (m_offscreenSurface && !m_isContextShared)
+ return 0;
+
+ QOpenGLFramebufferObject *fbo = initRenderer(layer);
+
+ if (!fbo)
+ return 0;
+
+ renderLayerToFBO(layer, fbo);
+ if (m_glContext)
+ m_glContext->doneCurrent();
+
+ return fbo->texture();
+}
+
+quint64 AVFVideoFrameRenderer::renderLayerToMTLTexture(AVPlayerLayer *layer)
+{
+ m_targetSize = QSize(layer.bounds.size.width, layer.bounds.size.height);
+
+ if (!m_metalDevice)
+ m_metalDevice = MTLCreateSystemDefaultDevice();
+
+ if (!m_metalTexture) {
+ auto desc = [[MTLTextureDescriptor alloc] init];
+ desc.textureType = MTLTextureType2D;
+ desc.width = NSUInteger(m_targetSize.width());
+ desc.height = NSUInteger(m_targetSize.height());
+ desc.resourceOptions = MTLResourceStorageModePrivate;
+ desc.usage = MTLTextureUsageRenderTarget;
+ desc.pixelFormat = MTLPixelFormatRGBA8Unorm;
+
+ m_metalTexture = [m_metalDevice newTextureWithDescriptor: desc];
+ [desc release];
+ }
+
+ if (!m_videoLayerRenderer) {
+ m_videoLayerRenderer = [CARenderer rendererWithMTLTexture:m_metalTexture options:nil];
+ [m_videoLayerRenderer retain];
+ }
+
+ if (m_videoLayerRenderer.layer != layer) {
+ m_videoLayerRenderer.layer = layer;
+ m_videoLayerRenderer.bounds = layer.bounds;
+ }
+
+ [m_videoLayerRenderer beginFrameAtTime:CACurrentMediaTime() timeStamp:NULL];
+ [m_videoLayerRenderer addUpdateRect:layer.bounds];
+ [m_videoLayerRenderer render];
+ [m_videoLayerRenderer endFrame];
+
+ return quint64(m_metalTexture);
+}
+
+QImage AVFVideoFrameRenderer::renderLayerToImage(AVPlayerLayer *layer)
+{
+ //Is layer valid
+ if (!layer) {
+ return QImage();
+ }
+
+ QOpenGLFramebufferObject *fbo = initRenderer(layer);
+
+ if (!fbo)
+ return QImage();
+
+ renderLayerToFBO(layer, fbo);
+ QImage fboImage = fbo->toImage();
+ if (m_glContext)
+ m_glContext->doneCurrent();
+
+ return fboImage;
+}
+
+QOpenGLFramebufferObject *AVFVideoFrameRenderer::initRenderer(AVPlayerLayer *layer)
+{
+
+ //Get size from AVPlayerLayer
+ m_targetSize = QSize(layer.bounds.size.width, layer.bounds.size.height);
+
+ QOpenGLContext *shareContext = !m_glContext && m_surface
+ ? qobject_cast<QOpenGLContext*>(m_surface->property("GLContext").value<QObject*>())
+ : nullptr;
+
+ //Make sure we have an OpenGL context to make current
+ if (shareContext || (!QOpenGLContext::currentContext() && !m_glContext)) {
+
+ //Create Hidden QWindow surface to create context in this thread
+ delete m_offscreenSurface;
+ m_offscreenSurface = new QWindow();
+ m_offscreenSurface->setSurfaceType(QWindow::OpenGLSurface);
+ //Needs geometry to be a valid surface, but size is not important
+ m_offscreenSurface->setGeometry(0, 0, 1, 1);
+ m_offscreenSurface->create();
+
+ delete m_glContext;
+ m_glContext = new QOpenGLContext();
+ m_glContext->setFormat(m_offscreenSurface->requestedFormat());
+
+ if (shareContext) {
+ m_glContext->setShareContext(shareContext);
+ m_isContextShared = true;
+ } else {
+#ifdef QT_DEBUG_AVF
+ qWarning("failed to get Render Thread context");
+#endif
+ m_isContextShared = false;
+ }
+ if (!m_glContext->create()) {
+ qWarning("failed to create QOpenGLContext");
+ return nullptr;
+ }
+
+ // CARenderer must be re-created with different current context, so release it now.
+ // See lines below where m_videoLayerRenderer is constructed.
+ if (m_videoLayerRenderer) {
+ [m_videoLayerRenderer release];
+ m_videoLayerRenderer = nullptr;
+ }
+
+ if (m_useCoreProfile) {
+ glDeleteVertexArrays(1, &m_quadVao);
+ glDeleteBuffers(2, m_quadVbos);
+ delete m_shader;
+ m_shader = nullptr;
+ }
+ }
+
+ //Need current context
+ if (m_glContext)
+ m_glContext->makeCurrent(m_offscreenSurface);
+
+ if (!m_metalDevice)
+ m_metalDevice = MTLCreateSystemDefaultDevice();
+
+ if (@available(macOS 10.13, *)) {
+ m_useCoreProfile = m_metalDevice && (QOpenGLContext::currentContext()->format().profile() ==
+ QSurfaceFormat::CoreProfile);
+ } else {
+ m_useCoreProfile = false;
+ }
+
+ // Create the CARenderer if needed for no Core OpenGL
+ if (!m_videoLayerRenderer) {
+ if (!m_useCoreProfile) {
+ m_videoLayerRenderer = [CARenderer rendererWithCGLContext: CGLGetCurrentContext()
+ options: nil];
+ [m_videoLayerRenderer retain];
+ } else if (@available(macOS 10.13, *)) {
+ // This is always true when m_useCoreProfile is true, but the compiler wants the check
+ // anyway
+ // Setup Core OpenGL shader, VAO, VBOs and metal renderer
+ m_shader = new QOpenGLShaderProgram();
+ m_shader->create();
+ if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, R"(#version 150 core
+ in vec2 qt_VertexPosition;
+ in vec2 qt_VertexTexCoord;
+ out vec2 qt_TexCoord;
+ void main()
+ {
+ qt_TexCoord = qt_VertexTexCoord;
+ gl_Position = vec4(qt_VertexPosition, 0.0f, 1.0f);
+ })")) {
+ qCritical() << "Vertex shader compilation failed" << m_shader->log();
+ }
+ if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, R"(#version 150 core
+ in vec2 qt_TexCoord;
+ out vec4 fragColor;
+ uniform sampler2DRect videoFrame;
+ void main(void)
+ {
+ ivec2 textureDim = textureSize(videoFrame);
+ fragColor = texture(videoFrame, qt_TexCoord * textureDim);
+ })")) {
+ qCritical() << "Fragment shader compilation failed" << m_shader->log();
+ }
+
+ // Setup quad where the video frame will be attached
+ GLfloat vertices[] = {
+ -1.0f, -1.0f,
+ 1.0f, -1.0f,
+ -1.0f, 1.0f,
+ 1.0f, 1.0f,
+ };
+
+ GLfloat uvs[] = {
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ };
+
+ glGenVertexArrays(1, &m_quadVao);
+ glBindVertexArray(m_quadVao);
+
+ // Create vertex buffer objects for vertices
+ glGenBuffers(2, m_quadVbos);
+
+ // Setup vertices
+ glBindBuffer(GL_ARRAY_BUFFER, m_quadVbos[0]);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr);
+ glEnableVertexAttribArray(0);
+
+ // Setup uvs
+ glBindBuffer(GL_ARRAY_BUFFER, m_quadVbos[1]);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(uvs), uvs, GL_STATIC_DRAW);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr);
+ glEnableVertexAttribArray(1);
+
+ glBindVertexArray(0);
+
+ // Setup shared Metal/OpenGL pixel buffer and textures
+ m_NSGLContext = QOpenGLContext::currentContext()->nativeInterface<QNativeInterface::QCocoaGLContext>()->nativeContext();
+ m_CGLPixelFormat = m_NSGLContext.pixelFormat.CGLPixelFormatObj;
+
+ NSDictionary* cvBufferProperties = @{
+ static_cast<NSString*>(kCVPixelBufferOpenGLCompatibilityKey) : @YES,
+ static_cast<NSString*>(kCVPixelBufferMetalCompatibilityKey): @YES,
+ };
+
+ CVPixelBufferCreate(kCFAllocatorDefault, static_cast<size_t>(m_targetSize.width()),
+ static_cast<size_t>(m_targetSize.height()), kCVPixelFormatType_32BGRA,
+ static_cast<CFDictionaryRef>(cvBufferProperties), &m_CVPixelBuffer);
+
+ m_textureName = createGLTexture(reinterpret_cast<CGLContextObj>(m_NSGLContext.CGLContextObj),
+ m_CGLPixelFormat, m_CVGLTextureCache, m_CVPixelBuffer,
+ m_CVGLTexture);
+ m_metalTexture = createMetalTexture(m_metalDevice, m_CVMTLTextureCache, m_CVPixelBuffer,
+ MTLPixelFormatBGRA8Unorm,
+ static_cast<size_t>(m_targetSize.width()),
+ static_cast<size_t>(m_targetSize.height()),
+ m_CVMTLTexture);
+
+ m_videoLayerRenderer = [CARenderer rendererWithMTLTexture:m_metalTexture options:nil];
+ [m_videoLayerRenderer retain];
+ }
+ }
+
+ //Set/Change render source if needed
+ if (m_videoLayerRenderer.layer != layer) {
+ m_videoLayerRenderer.layer = layer;
+ m_videoLayerRenderer.bounds = layer.bounds;
+ }
+
+ //Do we have FBO's already?
+ if ((!m_fbo[0] && !m_fbo[0]) || (m_fbo[0]->size() != m_targetSize)) {
+ delete m_fbo[0];
+ delete m_fbo[1];
+ m_fbo[0] = new QOpenGLFramebufferObject(m_targetSize);
+ m_fbo[1] = new QOpenGLFramebufferObject(m_targetSize);
+ }
+
+ //Switch buffer target
+ m_currentBuffer = !m_currentBuffer;
+ return m_fbo[m_currentBuffer];
+}
+
+void AVFVideoFrameRenderer::renderLayerToFBO(AVPlayerLayer *layer, QOpenGLFramebufferObject *fbo)
+{
+ //Start Rendering
+ //NOTE: This rendering method will NOT work on iOS as there is no CARenderer in iOS
+ if (!fbo->bind()) {
+ qWarning("AVFVideoRender FBO failed to bind");
+ return;
+ }
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, m_targetSize.width(), m_targetSize.height());
+
+ if (m_useCoreProfile) {
+ CGLLockContext(m_NSGLContext.CGLContextObj);
+ m_shader->bind();
+ glBindVertexArray(m_quadVao);
+ } else {
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+
+ // Render to FBO with inverted Y
+ glOrtho(0.0, m_targetSize.width(), 0.0, m_targetSize.height(), 0.0, 1.0);
+
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glLoadIdentity();
+ }
+
+ [m_videoLayerRenderer beginFrameAtTime:CACurrentMediaTime() timeStamp:nullptr];
+ [m_videoLayerRenderer addUpdateRect:layer.bounds];
+ [m_videoLayerRenderer render];
+ [m_videoLayerRenderer endFrame];
+
+ if (m_useCoreProfile) {
+ glActiveTexture(0);
+ glBindTexture(GL_TEXTURE_RECTANGLE, m_textureName);
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ glBindTexture(GL_TEXTURE_RECTANGLE, 0);
+
+ glBindVertexArray(0);
+
+ m_shader->release();
+
+ CGLFlushDrawable(m_NSGLContext.CGLContextObj);
+ CGLUnlockContext(m_NSGLContext.CGLContextObj);
+ } else {
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ }
+
+ glFinish(); //Rendering needs to be done before passing texture to video frame
+
+ fbo->release();
+}
+
+GLuint AVFVideoFrameRenderer::createGLTexture(CGLContextObj cglContextObj, CGLPixelFormatObj cglPixelFormtObj, CVOpenGLTextureCacheRef cvglTextureCache,
+ CVPixelBufferRef cvPixelBufferRef, CVOpenGLTextureRef cvOpenGLTextureRef)
+{
+ CVReturn cvret;
+ // Create an OpenGL CoreVideo texture cache from the pixel buffer.
+ cvret = CVOpenGLTextureCacheCreate(
+ kCFAllocatorDefault,
+ nil,
+ cglContextObj,
+ cglPixelFormtObj,
+ nil,
+ &cvglTextureCache);
+
+ // Create a CVPixelBuffer-backed OpenGL texture image from the texture cache.
+ cvret = CVOpenGLTextureCacheCreateTextureFromImage(
+ kCFAllocatorDefault,
+ cvglTextureCache,
+ cvPixelBufferRef,
+ nil,
+ &cvOpenGLTextureRef);
+
+ // Get an OpenGL texture name from the CVPixelBuffer-backed OpenGL texture image.
+ return CVOpenGLTextureGetName(cvOpenGLTextureRef);
+}
+
+id<MTLTexture> AVFVideoFrameRenderer::createMetalTexture(id<MTLDevice> mtlDevice, CVMetalTextureCacheRef cvMetalTextureCacheRef, CVPixelBufferRef cvPixelBufferRef,
+ MTLPixelFormat pixelFormat, size_t width, size_t height, CVMetalTextureRef cvMetalTextureRef)
+{
+ CVReturn cvret;
+ // Create a Metal Core Video texture cache from the pixel buffer.
+ cvret = CVMetalTextureCacheCreate(
+ kCFAllocatorDefault,
+ nil,
+ mtlDevice,
+ nil,
+ &cvMetalTextureCacheRef);
+
+ // Create a CoreVideo pixel buffer backed Metal texture image from the texture cache.
+ cvret = CVMetalTextureCacheCreateTextureFromImage(
+ kCFAllocatorDefault,
+ cvMetalTextureCacheRef,
+ cvPixelBufferRef, nil,
+ pixelFormat,
+ width, height,
+ 0,
+ &cvMetalTextureRef);
+
+ // Get a Metal texture using the CoreVideo Metal texture reference.
+ return CVMetalTextureGetTexture(cvMetalTextureRef);
+}
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios.mm
new file mode 100644
index 000000000..cbaa1aa11
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios.mm
@@ -0,0 +1,307 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfvideoframerenderer_ios.h"
+
+#include <QtMultimedia/qabstractvideosurface.h>
+#include <QtOpenGL/QOpenGLFramebufferObject>
+#include <QtOpenGL/QOpenGLShaderProgram>
+#include <QtGui/QOffscreenSurface>
+
+#ifdef QT_DEBUG_AVF
+#include <QtCore/qdebug.h>
+#endif
+
+#import <CoreVideo/CVBase.h>
+#import <AVFoundation/AVFoundation.h>
+QT_USE_NAMESPACE
+
+AVFVideoFrameRenderer::AVFVideoFrameRenderer(QAbstractVideoSurface *surface, QObject *parent)
+ : QObject(parent)
+ , m_glContext(nullptr)
+ , m_offscreenSurface(nullptr)
+ , m_surface(surface)
+ , m_textureCache(nullptr)
+ , m_videoOutput(nullptr)
+ , m_isContextShared(true)
+{
+}
+
+AVFVideoFrameRenderer::~AVFVideoFrameRenderer()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+
+ [m_videoOutput release]; // sending to nil is fine
+ if (m_textureCache)
+ CFRelease(m_textureCache);
+ if (m_metalTextureCache)
+ CFRelease(m_metalTextureCache);
+ [m_metalDevice release];
+ delete m_offscreenSurface;
+ delete m_glContext;
+}
+
+void AVFVideoFrameRenderer::setPlayerLayer(AVPlayerLayer *layer)
+{
+ Q_UNUSED(layer);
+ if (m_videoOutput) {
+ [m_videoOutput release];
+ m_videoOutput = nullptr;
+ // will be re-created in first call to copyPixelBufferFromLayer
+ }
+}
+
+quint64 AVFVideoFrameRenderer::renderLayerToMTLTexture(AVPlayerLayer *layer)
+{
+ if (!m_metalDevice)
+ m_metalDevice = MTLCreateSystemDefaultDevice();
+
+ if (!m_metalTextureCache) {
+ CVReturn err = CVMetalTextureCacheCreate(kCFAllocatorDefault, nullptr,
+ m_metalDevice, nullptr, &m_metalTextureCache);
+ if (err) {
+ qWarning() << "Error at CVMetalTextureCacheCreate" << err;
+ return 0;
+ }
+ }
+
+ size_t width = 0, height = 0;
+ CVPixelBufferRef pixelBuffer = copyPixelBufferFromLayer(layer, width, height);
+
+ if (!pixelBuffer)
+ return 0;
+
+ CVMetalTextureCacheFlush(m_metalTextureCache, 0);
+
+ CVMetalTextureRef texture = nil;
+ CVReturn err = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, m_metalTextureCache, pixelBuffer, NULL,
+ MTLPixelFormatBGRA8Unorm_sRGB, width, height, 0, &texture);
+
+ if (!texture || err)
+ qWarning("CVMetalTextureCacheCreateTextureFromImage failed (error: %d)", err);
+
+ CVPixelBufferRelease(pixelBuffer);
+ quint64 tex = 0;
+ if (texture) {
+ tex = quint64(CVMetalTextureGetTexture(texture));
+ CFRelease(texture);
+ }
+
+ return tex;
+}
+
+quint64 AVFVideoFrameRenderer::renderLayerToTexture(AVPlayerLayer *layer)
+{
+ initRenderer();
+
+ // If the glContext isn't shared, it doesn't make sense to return a texture for us
+ if (!m_isContextShared)
+ return 0;
+
+ size_t dummyWidth = 0, dummyHeight = 0;
+ auto texture = createCacheTextureFromLayer(layer, dummyWidth, dummyHeight);
+ auto tex = quint64(CVOGLTextureGetName(texture));
+ CFRelease(texture);
+
+ return tex;
+}
+
+static NSString* const AVF_PIXEL_FORMAT_KEY = (NSString*)kCVPixelBufferPixelFormatTypeKey;
+static NSNumber* const AVF_PIXEL_FORMAT_VALUE = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA];
+static NSDictionary* const AVF_OUTPUT_SETTINGS = [NSDictionary dictionaryWithObject:AVF_PIXEL_FORMAT_VALUE forKey:AVF_PIXEL_FORMAT_KEY];
+
+
+CVPixelBufferRef AVFVideoFrameRenderer::copyPixelBufferFromLayer(AVPlayerLayer *layer,
+ size_t& width, size_t& height)
+{
+ //Is layer valid
+ if (!layer) {
+#ifdef QT_DEBUG_AVF
+ qWarning("copyPixelBufferFromLayer: invalid layer");
+#endif
+ return nullptr;
+ }
+
+ if (!m_videoOutput) {
+ m_videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:AVF_OUTPUT_SETTINGS];
+ [m_videoOutput setDelegate:nil queue:nil];
+ AVPlayerItem * item = [[layer player] currentItem];
+ [item addOutput:m_videoOutput];
+ }
+
+ CFTimeInterval currentCAFrameTime = CACurrentMediaTime();
+ CMTime currentCMFrameTime = [m_videoOutput itemTimeForHostTime:currentCAFrameTime];
+ // happens when buffering / loading
+ if (CMTimeCompare(currentCMFrameTime, kCMTimeZero) < 0) {
+ return nullptr;
+ }
+
+ CVPixelBufferRef pixelBuffer = [m_videoOutput copyPixelBufferForItemTime:currentCMFrameTime
+ itemTimeForDisplay:nil];
+ if (!pixelBuffer) {
+#ifdef QT_DEBUG_AVF
+ qWarning("copyPixelBufferForItemTime returned nil");
+ CMTimeShow(currentCMFrameTime);
+#endif
+ return nullptr;
+ }
+
+ width = CVPixelBufferGetWidth(pixelBuffer);
+ height = CVPixelBufferGetHeight(pixelBuffer);
+ return pixelBuffer;
+}
+
+CVOGLTextureRef AVFVideoFrameRenderer::createCacheTextureFromLayer(AVPlayerLayer *layer,
+ size_t& width, size_t& height)
+{
+ CVPixelBufferRef pixelBuffer = copyPixelBufferFromLayer(layer, width, height);
+
+ if (!pixelBuffer)
+ return nullptr;
+
+ CVOGLTextureCacheFlush(m_textureCache, 0);
+
+ CVOGLTextureRef texture = nullptr;
+ CVReturn err = CVOGLTextureCacheCreateTextureFromImage(kCFAllocatorDefault, m_textureCache, pixelBuffer, nullptr,
+ GL_TEXTURE_2D, GL_RGBA,
+ (GLsizei) width, (GLsizei) height,
+ GL_BGRA, GL_UNSIGNED_BYTE, 0,
+ &texture);
+
+ if (!texture || err) {
+#ifdef QT_DEBUG_AVF
+ qWarning("CVOGLTextureCacheCreateTextureFromImage failed (error: %d)", err);
+#endif
+ }
+
+ CVPixelBufferRelease(pixelBuffer);
+
+ return texture;
+}
+
+QImage AVFVideoFrameRenderer::renderLayerToImage(AVPlayerLayer *layer)
+{
+ size_t width = 0;
+ size_t height = 0;
+ CVPixelBufferRef pixelBuffer = copyPixelBufferFromLayer(layer, width, height);
+
+ if (!pixelBuffer)
+ return QImage();
+
+ OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
+ if (pixelFormat != kCVPixelFormatType_32BGRA) {
+#ifdef QT_DEBUG_AVF
+ qWarning("CVPixelBuffer format is not BGRA32 (got: %d)", static_cast<quint32>(pixelFormat));
+#endif
+ return QImage();
+ }
+
+ CVPixelBufferLockBaseAddress(pixelBuffer, 0);
+ char *data = (char *)CVPixelBufferGetBaseAddress(pixelBuffer);
+ size_t stride = CVPixelBufferGetBytesPerRow(pixelBuffer);
+
+ // format here is not relevant, only using for storage
+ QImage img = QImage(width, height, QImage::Format_ARGB32);
+ for (size_t j = 0; j < height; j++) {
+ memcpy(img.scanLine(j), data, width * 4);
+ data += stride;
+ }
+
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
+ CVPixelBufferRelease(pixelBuffer);
+ return img;
+}
+
+void AVFVideoFrameRenderer::initRenderer()
+{
+ // even for using a texture directly, we need to be able to make a context current,
+ // so we need an offscreen, and we shouldn't assume we can make the surface context
+ // current on that offscreen, so use our own (sharing with it). Slightly
+ // excessive but no performance penalty and makes the QImage path easier to maintain
+
+ //Make sure we have an OpenGL context to make current
+ if (!m_glContext) {
+ //Create OpenGL context and set share context from surface
+ QOpenGLContext *shareContext = nullptr;
+ if (m_surface) {
+ shareContext = qobject_cast<QOpenGLContext*>(m_surface->property("GLContext").value<QObject*>());
+ }
+
+ m_glContext = new QOpenGLContext();
+ if (shareContext) {
+ m_glContext->setShareContext(shareContext);
+ m_isContextShared = true;
+ } else {
+#ifdef QT_DEBUG_AVF
+ qWarning("failed to get Render Thread context");
+#endif
+ m_isContextShared = false;
+ }
+ if (!m_glContext->create()) {
+#ifdef QT_DEBUG_AVF
+ qWarning("failed to create QOpenGLContext");
+#endif
+ return;
+ }
+ }
+
+ if (!m_offscreenSurface) {
+ m_offscreenSurface = new QOffscreenSurface();
+ m_offscreenSurface->setFormat(m_glContext->format());
+ m_offscreenSurface->create();
+ }
+
+ //Need current context
+ m_glContext->makeCurrent(m_offscreenSurface);
+
+ if (!m_textureCache) {
+ // Create a new open gl texture cache
+ CVReturn err = CVOGLTextureCacheCreate(kCFAllocatorDefault, nullptr,
+ [EAGLContext currentContext],
+ nullptr, &m_textureCache);
+ if (err) {
+ #ifdef QT_DEBUG_AVF
+ qWarning("Error at CVOGLTextureCacheCreate %d", err);
+ #endif
+ }
+ }
+
+}
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios_p.h
new file mode 100644
index 000000000..b69e33ceb
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios_p.h
@@ -0,0 +1,131 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFVIDEOFRAMERENDERER_H
+#define AVFVIDEOFRAMERENDERER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QObject>
+#include <QtGui/QImage>
+#include <QtGui/QOpenGLContext>
+#include <QtCore/QSize>
+
+#import "Metal/Metal.h"
+#import "MetalKit/MetalKit.h"
+
+@class AVPlayerLayer;
+@class AVPlayerItemVideoOutput;
+
+QT_BEGIN_NAMESPACE
+
+class QOpenGLContext;
+class QOpenGLFramebufferObject;
+class QOpenGLShaderProgram;
+class QOffscreenSurface;
+class QAbstractVideoSurface;
+
+typedef struct __CVBuffer *CVBufferRef;
+typedef CVBufferRef CVImageBufferRef;
+typedef CVImageBufferRef CVPixelBufferRef;
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+typedef struct __CVOpenGLESTextureCache *CVOpenGLESTextureCacheRef;
+typedef CVImageBufferRef CVOpenGLESTextureRef;
+// helpers to avoid boring if def
+typedef CVOpenGLESTextureCacheRef CVOGLTextureCacheRef;
+typedef CVOpenGLESTextureRef CVOGLTextureRef;
+#define CVOGLTextureGetTarget CVOpenGLESTextureGetTarget
+#define CVOGLTextureGetName CVOpenGLESTextureGetName
+#define CVOGLTextureCacheCreate CVOpenGLESTextureCacheCreate
+#define CVOGLTextureCacheCreateTextureFromImage CVOpenGLESTextureCacheCreateTextureFromImage
+#define CVOGLTextureCacheFlush CVOpenGLESTextureCacheFlush
+#else
+typedef struct __CVOpenGLTextureCache *CVOpenGLTextureCacheRef;
+typedef CVImageBufferRef CVOpenGLTextureRef;
+// helpers to avoid boring if def
+typedef CVOpenGLTextureCacheRef CVOGLTextureCacheRef;
+typedef CVOpenGLTextureRef CVOGLTextureRef;
+#define CVOGLTextureGetTarget CVOpenGLTextureGetTarget
+#define CVOGLTextureGetName CVOpenGLTextureGetName
+#define CVOGLTextureCacheCreate CVOpenGLTextureCacheCreate
+#define CVOGLTextureCacheCreateTextureFromImage CVOpenGLTextureCacheCreateTextureFromImage
+#define CVOGLTextureCacheFlush CVOpenGLTextureCacheFlush
+#endif
+
+class AVFVideoFrameRenderer : public QObject
+{
+public:
+ AVFVideoFrameRenderer(QAbstractVideoSurface *surface, QObject *parent = nullptr);
+
+ virtual ~AVFVideoFrameRenderer();
+
+ void setPlayerLayer(AVPlayerLayer *layer);
+
+ quint64 renderLayerToTexture(AVPlayerLayer *layer);
+ quint64 renderLayerToMTLTexture(AVPlayerLayer *layer);
+ QImage renderLayerToImage(AVPlayerLayer *layer);
+
+private:
+ void initRenderer();
+ CVPixelBufferRef copyPixelBufferFromLayer(AVPlayerLayer *layer, size_t& width, size_t& height);
+ CVOGLTextureRef createCacheTextureFromLayer(AVPlayerLayer *layer, size_t& width, size_t& height);
+
+ QOpenGLContext *m_glContext;
+ QOffscreenSurface *m_offscreenSurface;
+ QAbstractVideoSurface *m_surface;
+ CVOGLTextureCacheRef m_textureCache;
+ AVPlayerItemVideoOutput* m_videoOutput;
+ bool m_isContextShared;
+
+ id<MTLDevice> m_metalDevice = nil;
+ CVMetalTextureCacheRef m_metalTextureCache = nil;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFVIDEOFRAMERENDERER_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_p.h
new file mode 100644
index 000000000..465c1e563
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_p.h
@@ -0,0 +1,137 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFVIDEOFRAMERENDERER_H
+#define AVFVIDEOFRAMERENDERER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QObject>
+#include <QtGui/QImage>
+#include <QtGui/QOpenGLContext>
+#include <QtCore/QSize>
+
+#import "Metal/Metal.h"
+#import "MetalKit/MetalKit.h"
+
+@class CARenderer;
+@class AVPlayerLayer;
+
+QT_BEGIN_NAMESPACE
+
+class QOpenGLFramebufferObject;
+class QOpenGLShaderProgram;
+class QWindow;
+class QOpenGLContext;
+class QAbstractVideoSurface;
+
+class AVFVideoFrameRenderer : public QObject
+{
+public:
+ AVFVideoFrameRenderer(QAbstractVideoSurface *surface, QObject *parent = nullptr);
+
+ virtual ~AVFVideoFrameRenderer();
+
+ quint64 renderLayerToTexture(AVPlayerLayer *layer);
+ quint64 renderLayerToMTLTexture(AVPlayerLayer *layer);
+ QImage renderLayerToImage(AVPlayerLayer *layer);
+
+ static GLuint createGLTexture(CGLContextObj cglContextObj, CGLPixelFormatObj cglPixelFormtObj,
+ CVOpenGLTextureCacheRef cvglTextureCache,
+ CVPixelBufferRef cvPixelBufferRef,
+ CVOpenGLTextureRef cvOpenGLTextureRef);
+
+ static id<MTLTexture> createMetalTexture(id<MTLDevice> mtlDevice,
+ CVMetalTextureCacheRef cvMetalTextureCacheRef,
+ CVPixelBufferRef cvPixelBufferRef,
+ MTLPixelFormat pixelFormat, size_t width, size_t height,
+ CVMetalTextureRef cvMetalTextureRef);
+
+private:
+ QOpenGLFramebufferObject* initRenderer(AVPlayerLayer *layer);
+ void renderLayerToFBO(AVPlayerLayer *layer, QOpenGLFramebufferObject *fbo);
+ void renderLayerToFBOCoreOpenGL(AVPlayerLayer *layer, QOpenGLFramebufferObject *fbo);
+
+ CARenderer *m_videoLayerRenderer;
+ QAbstractVideoSurface *m_surface;
+ QOpenGLFramebufferObject *m_fbo[2];
+ QOpenGLShaderProgram *m_shader = nullptr;
+ QWindow *m_offscreenSurface;
+ QOpenGLContext *m_glContext;
+ QSize m_targetSize;
+
+ bool m_useCoreProfile = false;
+
+ // Shared pixel buffer
+ CVPixelBufferRef m_CVPixelBuffer;
+
+ // OpenGL Texture
+ CVOpenGLTextureCacheRef m_CVGLTextureCache;
+ CVOpenGLTextureRef m_CVGLTexture;
+ CGLPixelFormatObj m_CGLPixelFormat;
+ GLuint m_textureName = 0;
+
+ // Metal Texture
+ CVMetalTextureRef m_CVMTLTexture;
+ CVMetalTextureCacheRef m_CVMTLTextureCache;
+
+ NSOpenGLContext *m_NSGLContext = nullptr;
+
+ GLuint m_quadVao = 0;
+ GLuint m_quadVbos[2];
+
+ uint m_currentBuffer;
+ bool m_isContextShared;
+
+ id<MTLDevice> m_metalDevice = nil;
+ id<MTLTexture> m_metalTexture = nil;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFVIDEOFRAMERENDERER_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput.mm
new file mode 100644
index 000000000..ef12f07c1
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput.mm
@@ -0,0 +1,42 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfvideooutput_p.h"
+
+QT_USE_NAMESPACE
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput_p.h
new file mode 100644
index 000000000..d1dd94460
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput_p.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFVIDEOOUTPUT_H
+#define AVFVIDEOOUTPUT_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qobject.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFVideoOutput
+{
+public:
+ virtual ~AVFVideoOutput() {}
+ virtual void setLayer(void *playerLayer) = 0;
+};
+
+#define AVFVideoOutput_iid \
+ "org.qt-project.qt.AVFVideoOutput/5.0"
+Q_DECLARE_INTERFACE(AVFVideoOutput, AVFVideoOutput_iid)
+
+QT_END_NAMESPACE
+
+#endif // AVFVIDEOOUTPUT_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol.mm
new file mode 100644
index 000000000..4cb467296
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol.mm
@@ -0,0 +1,301 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfvideorenderercontrol_p.h"
+#include "avfdisplaylink_p.h"
+
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+#include "avfvideoframerenderer_ios_p.h"
+#else
+#include "avfvideoframerenderer_p.h"
+#endif
+
+#include <QtMultimedia/qabstractvideobuffer.h>
+#include <QtMultimedia/qabstractvideosurface.h>
+#include <QtMultimedia/qvideosurfaceformat.h>
+
+#include <private/qimagevideobuffer_p.h>
+
+#include <QtCore/qdebug.h>
+
+#import <AVFoundation/AVFoundation.h>
+
+QT_USE_NAMESPACE
+
+class TextureVideoBuffer : public QAbstractVideoBuffer
+{
+public:
+ TextureVideoBuffer(HandleType type, quint64 tex)
+ : QAbstractVideoBuffer(type)
+ , m_texture(tex)
+ {}
+
+ virtual ~TextureVideoBuffer()
+ {
+ }
+
+ MapMode mapMode() const { return NotMapped; }
+ MapData map(MapMode mode) override { return {}; }
+ void unmap() {}
+
+ QVariant handle() const
+ {
+ return QVariant::fromValue<unsigned long long>(m_texture);
+ }
+
+private:
+ quint64 m_texture;
+};
+
+AVFVideoRendererControl::AVFVideoRendererControl(QObject *parent)
+ : QVideoRendererControl(parent)
+ , m_surface(nullptr)
+ , m_playerLayer(nullptr)
+ , m_frameRenderer(nullptr)
+ , m_enableOpenGL(false)
+ , m_enableMetal(false)
+
+{
+ m_displayLink = new AVFDisplayLink(this);
+ connect(m_displayLink, SIGNAL(tick(CVTimeStamp)), SLOT(updateVideoFrame(CVTimeStamp)));
+}
+
+AVFVideoRendererControl::~AVFVideoRendererControl()
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << Q_FUNC_INFO;
+#endif
+ m_displayLink->stop();
+ [static_cast<AVPlayerLayer*>(m_playerLayer) release];
+}
+
+QAbstractVideoSurface *AVFVideoRendererControl::surface() const
+{
+ return m_surface;
+}
+
+void AVFVideoRendererControl::setSurface(QAbstractVideoSurface *surface)
+{
+#ifdef QT_DEBUG_AVF
+ qDebug() << "Set video surface" << surface;
+#endif
+
+ //When we have a valid surface, we can setup a frame renderer
+ //and schedule surface updates with the display link.
+ if (surface == m_surface)
+ return;
+
+ QMutexLocker locker(&m_mutex);
+
+ if (m_surface && m_surface->isActive())
+ m_surface->stop();
+
+ m_surface = surface;
+
+ //If the surface changed, then the current frame renderer is no longer valid
+ delete m_frameRenderer;
+ m_frameRenderer = nullptr;
+
+ //If there is now no surface to render too
+ if (m_surface == nullptr) {
+ m_displayLink->stop();
+ return;
+ }
+
+ //Surface changed, so we need a new frame renderer
+ m_frameRenderer = new AVFVideoFrameRenderer(m_surface, this);
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ if (m_playerLayer) {
+ m_frameRenderer->setPlayerLayer(static_cast<AVPlayerLayer*>(m_playerLayer));
+ }
+#endif
+
+ auto checkHandleType = [this] {
+ m_enableOpenGL = m_surface->supportedPixelFormats(QAbstractVideoBuffer::GLTextureHandle).contains(QVideoFrame::Format_BGR32);
+ m_enableMetal = m_surface->supportedPixelFormats(QAbstractVideoBuffer::MTLTextureHandle).contains(QVideoFrame::Format_BGR32);
+ };
+ checkHandleType();
+ connect(m_surface, &QAbstractVideoSurface::supportedFormatsChanged, this, checkHandleType);
+
+ //If we already have a layer, but changed surfaces start rendering again
+ if (m_playerLayer && !m_displayLink->isActive()) {
+ m_displayLink->start();
+ }
+
+}
+
+void AVFVideoRendererControl::setLayer(void *playerLayer)
+{
+ if (m_playerLayer == playerLayer)
+ return;
+
+ [static_cast<AVPlayerLayer*>(m_playerLayer) release];
+
+ m_playerLayer = [static_cast<AVPlayerLayer*>(playerLayer) retain];
+
+ //If there is an active surface, make sure it has been stopped so that
+ //we can update it's state with the new content.
+ if (m_surface && m_surface->isActive())
+ m_surface->stop();
+
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ if (m_frameRenderer) {
+ m_frameRenderer->setPlayerLayer(static_cast<AVPlayerLayer*>(playerLayer));
+ }
+#endif
+
+ //If there is no layer to render, stop scheduling updates
+ if (m_playerLayer == nullptr) {
+ m_displayLink->stop();
+ return;
+ }
+
+ setupVideoOutput();
+
+ //If we now have both a valid surface and layer, start scheduling updates
+ if (m_surface && !m_displayLink->isActive()) {
+ m_displayLink->start();
+ }
+}
+
+void AVFVideoRendererControl::updateVideoFrame(const CVTimeStamp &ts)
+{
+ Q_UNUSED(ts);
+
+ AVPlayerLayer *playerLayer = static_cast<AVPlayerLayer*>(m_playerLayer);
+
+ if (!playerLayer) {
+ qWarning("updateVideoFrame called without AVPlayerLayer (which shouldn't happen");
+ return;
+ }
+
+ if (!playerLayer.readyForDisplay || !m_surface)
+ return;
+
+ if (m_enableMetal) {
+ quint64 tex = m_frameRenderer->renderLayerToMTLTexture(playerLayer);
+ if (tex == 0)
+ return;
+
+ auto buffer = new TextureVideoBuffer(QAbstractVideoBuffer::MTLTextureHandle, tex);
+ QVideoFrame frame(buffer, m_nativeSize, QVideoFrame::Format_BGR32);
+ if (m_surface->isActive() && m_surface->surfaceFormat().pixelFormat() != frame.pixelFormat())
+ m_surface->stop();
+
+ if (!m_surface->isActive()) {
+ QVideoSurfaceFormat format(frame.size(), frame.pixelFormat(), QAbstractVideoBuffer::MTLTextureHandle);
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ format.setScanLineDirection(QVideoSurfaceFormat::TopToBottom);
+#else
+ format.setScanLineDirection(QVideoSurfaceFormat::BottomToTop);
+#endif
+ if (!m_surface->start(format))
+ qWarning("Failed to activate video surface");
+ }
+
+ if (m_surface->isActive())
+ m_surface->present(frame);
+
+ return;
+ }
+
+ if (m_enableOpenGL) {
+ quint64 tex = m_frameRenderer->renderLayerToTexture(playerLayer);
+ //Make sure we got a valid texture
+ if (tex == 0)
+ return;
+
+ QAbstractVideoBuffer *buffer = new TextureVideoBuffer(QAbstractVideoBuffer::GLTextureHandle, tex);
+ QVideoFrame frame = QVideoFrame(buffer, m_nativeSize, QVideoFrame::Format_BGR32);
+
+ if (m_surface && frame.isValid()) {
+ if (m_surface->isActive() && m_surface->surfaceFormat().pixelFormat() != frame.pixelFormat())
+ m_surface->stop();
+
+ if (!m_surface->isActive()) {
+ QVideoSurfaceFormat format(frame.size(), frame.pixelFormat(), QAbstractVideoBuffer::GLTextureHandle);
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ format.setScanLineDirection(QVideoSurfaceFormat::TopToBottom);
+#else
+ format.setScanLineDirection(QVideoSurfaceFormat::BottomToTop);
+#endif
+ if (!m_surface->start(format)) {
+ //Surface doesn't support GLTextureHandle
+ qWarning("Failed to activate video surface");
+ }
+ }
+
+ if (m_surface->isActive())
+ m_surface->present(frame);
+ }
+ } else {
+ //fallback to rendering frames to QImages
+ QImage frameData = m_frameRenderer->renderLayerToImage(playerLayer);
+
+ if (frameData.isNull()) {
+ return;
+ }
+
+ QAbstractVideoBuffer *buffer = new QImageVideoBuffer(frameData);
+ QVideoFrame frame = QVideoFrame(buffer, m_nativeSize, QVideoFrame::Format_ARGB32);
+ if (m_surface && frame.isValid()) {
+ if (m_surface->isActive() && m_surface->surfaceFormat().pixelFormat() != frame.pixelFormat())
+ m_surface->stop();
+
+ if (!m_surface->isActive()) {
+ QVideoSurfaceFormat format(frame.size(), frame.pixelFormat(), QAbstractVideoBuffer::NoHandle);
+
+ if (!m_surface->start(format)) {
+ qWarning("Failed to activate video surface");
+ }
+ }
+
+ if (m_surface->isActive())
+ m_surface->present(frame);
+ }
+
+ }
+}
+
+void AVFVideoRendererControl::setupVideoOutput()
+{
+ AVPlayerLayer *playerLayer = static_cast<AVPlayerLayer*>(m_playerLayer);
+ if (playerLayer)
+ m_nativeSize = QSize(playerLayer.bounds.size.width, playerLayer.bounds.size.height);
+}
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol_p.h
new file mode 100644
index 000000000..e46444745
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol_p.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFVIDEORENDERERCONTROL_H
+#define AVFVIDEORENDERERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/QVideoRendererControl>
+#include <QtCore/QMutex>
+#include <QtCore/QSize>
+
+#include "avfvideooutput_p.h"
+
+#import <CoreVideo/CVBase.h>
+
+QT_BEGIN_NAMESPACE
+
+class AVFDisplayLink;
+class AVFVideoFrameRenderer;
+
+class AVFVideoRendererControl : public QVideoRendererControl, public AVFVideoOutput
+{
+ Q_OBJECT
+ Q_INTERFACES(AVFVideoOutput)
+public:
+ explicit AVFVideoRendererControl(QObject *parent = nullptr);
+ virtual ~AVFVideoRendererControl();
+
+ QAbstractVideoSurface *surface() const override;
+ void setSurface(QAbstractVideoSurface *surface) override;
+
+ void setLayer(void *playerLayer) override;
+
+private Q_SLOTS:
+ void updateVideoFrame(const CVTimeStamp &ts);
+
+Q_SIGNALS:
+ void surfaceChanged(QAbstractVideoSurface *surface);
+
+private:
+ void setupVideoOutput();
+
+ QMutex m_mutex;
+ QAbstractVideoSurface *m_surface;
+
+ void *m_playerLayer;
+
+ AVFVideoFrameRenderer *m_frameRenderer;
+ AVFDisplayLink *m_displayLink;
+ QSize m_nativeSize;
+ bool m_enableOpenGL;
+ bool m_enableMetal;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFVIDEORENDERERCONTROL_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol.mm
new file mode 100644
index 000000000..85eb82dcb
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol.mm
@@ -0,0 +1,255 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "avfvideowindowcontrol_p.h"
+
+#include <AVFoundation/AVFoundation.h>
+#import <QuartzCore/CATransaction.h>
+
+#if QT_HAS_INCLUDE(<AppKit/AppKit.h>)
+#include <AppKit/AppKit.h>
+#endif
+
+#if QT_HAS_INCLUDE(<UIKit/UIKit.h>)
+#include <UIKit/UIKit.h>
+#endif
+
+QT_USE_NAMESPACE
+
+AVFVideoWindowControl::AVFVideoWindowControl(QObject *parent)
+ : QVideoWindowControl(parent)
+ , m_winId(0)
+ , m_fullscreen(false)
+ , m_brightness(0)
+ , m_contrast(0)
+ , m_hue(0)
+ , m_saturation(0)
+ , m_aspectRatioMode(Qt::IgnoreAspectRatio)
+ , m_playerLayer(nullptr)
+ , m_nativeView(nullptr)
+{
+}
+
+AVFVideoWindowControl::~AVFVideoWindowControl()
+{
+ if (m_playerLayer) {
+ [m_playerLayer removeFromSuperlayer];
+ [m_playerLayer release];
+ }
+}
+
+WId AVFVideoWindowControl::winId() const
+{
+ return m_winId;
+}
+
+void AVFVideoWindowControl::setWinId(WId id)
+{
+ m_winId = id;
+ m_nativeView = (NativeView*)m_winId;
+}
+
+QRect AVFVideoWindowControl::displayRect() const
+{
+ return m_displayRect;
+}
+
+void AVFVideoWindowControl::setDisplayRect(const QRect &rect)
+{
+ if (m_displayRect != rect) {
+ m_displayRect = rect;
+ updatePlayerLayerBounds();
+ }
+}
+
+bool AVFVideoWindowControl::isFullScreen() const
+{
+ return m_fullscreen;
+}
+
+void AVFVideoWindowControl::setFullScreen(bool fullScreen)
+{
+ if (m_fullscreen != fullScreen) {
+ m_fullscreen = fullScreen;
+ Q_EMIT QVideoWindowControl::fullScreenChanged(fullScreen);
+ }
+}
+
+void AVFVideoWindowControl::repaint()
+{
+ if (m_playerLayer)
+ [m_playerLayer setNeedsDisplay];
+}
+
+QSize AVFVideoWindowControl::nativeSize() const
+{
+ return m_nativeSize;
+}
+
+Qt::AspectRatioMode AVFVideoWindowControl::aspectRatioMode() const
+{
+ return m_aspectRatioMode;
+}
+
+void AVFVideoWindowControl::setAspectRatioMode(Qt::AspectRatioMode mode)
+{
+ if (m_aspectRatioMode != mode) {
+ m_aspectRatioMode = mode;
+ updateAspectRatio();
+ }
+}
+
+int AVFVideoWindowControl::brightness() const
+{
+ return m_brightness;
+}
+
+void AVFVideoWindowControl::setBrightness(int brightness)
+{
+ if (m_brightness != brightness) {
+ m_brightness = brightness;
+ Q_EMIT QVideoWindowControl::brightnessChanged(brightness);
+ }
+}
+
+int AVFVideoWindowControl::contrast() const
+{
+ return m_contrast;
+}
+
+void AVFVideoWindowControl::setContrast(int contrast)
+{
+ if (m_contrast != contrast) {
+ m_contrast = contrast;
+ Q_EMIT QVideoWindowControl::contrastChanged(contrast);
+ }
+}
+
+int AVFVideoWindowControl::hue() const
+{
+ return m_hue;
+}
+
+void AVFVideoWindowControl::setHue(int hue)
+{
+ if (m_hue != hue) {
+ m_hue = hue;
+ Q_EMIT QVideoWindowControl::hueChanged(hue);
+ }
+}
+
+int AVFVideoWindowControl::saturation() const
+{
+ return m_saturation;
+}
+
+void AVFVideoWindowControl::setSaturation(int saturation)
+{
+ if (m_saturation != saturation) {
+ m_saturation = saturation;
+ Q_EMIT QVideoWindowControl::saturationChanged(saturation);
+ }
+}
+
+void AVFVideoWindowControl::setLayer(void *playerLayer)
+{
+ AVPlayerLayer *layer = static_cast<AVPlayerLayer*>(playerLayer);
+ if (m_playerLayer == layer)
+ return;
+
+ if (!m_winId) {
+ qDebug("AVFVideoWindowControl: No video window");
+ return;
+ }
+
+#if defined(Q_OS_OSX)
+ [m_nativeView setWantsLayer:YES];
+#endif
+
+ if (m_playerLayer) {
+ [m_playerLayer removeFromSuperlayer];
+ [m_playerLayer release];
+ }
+
+ m_playerLayer = layer;
+
+ CALayer *nativeLayer = [m_nativeView layer];
+
+ if (layer) {
+ [layer retain];
+
+ m_nativeSize = QSize(m_playerLayer.bounds.size.width,
+ m_playerLayer.bounds.size.height);
+
+ updateAspectRatio();
+ [nativeLayer addSublayer:m_playerLayer];
+ updatePlayerLayerBounds();
+ }
+}
+
+void AVFVideoWindowControl::updateAspectRatio()
+{
+ if (m_playerLayer) {
+ switch (m_aspectRatioMode) {
+ case Qt::IgnoreAspectRatio:
+ [m_playerLayer setVideoGravity:AVLayerVideoGravityResize];
+ break;
+ case Qt::KeepAspectRatio:
+ [m_playerLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
+ break;
+ case Qt::KeepAspectRatioByExpanding:
+ [m_playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void AVFVideoWindowControl::updatePlayerLayerBounds()
+{
+ if (m_playerLayer) {
+ [CATransaction begin];
+ [CATransaction setDisableActions: YES]; // disable animation/flicks
+ m_playerLayer.frame = m_displayRect.toCGRect();
+ [CATransaction commit];
+ }
+}
+
+#include "moc_avfvideowindowcontrol_p.cpp"
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol_p.h
new file mode 100644
index 000000000..f4f2fc580
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol_p.h
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef AVFVIDEOWINDOWCONTROL_H
+#define AVFVIDEOWINDOWCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QVideoWindowControl>
+
+@class AVPlayerLayer;
+#if defined(Q_OS_OSX)
+@class NSView;
+typedef NSView NativeView;
+#else
+@class UIView;
+typedef UIView NativeView;
+#endif
+
+#include "avfvideooutput_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class AVFVideoWindowControl : public QVideoWindowControl, public AVFVideoOutput
+{
+ Q_OBJECT
+ Q_INTERFACES(AVFVideoOutput)
+
+public:
+ AVFVideoWindowControl(QObject *parent = nullptr);
+ virtual ~AVFVideoWindowControl();
+
+ // QVideoWindowControl interface
+public:
+ WId winId() const override;
+ void setWinId(WId id) override;
+
+ QRect displayRect() const override;
+ void setDisplayRect(const QRect &rect) override;
+
+ bool isFullScreen() const override;
+ void setFullScreen(bool fullScreen) override;
+
+ void repaint() override;
+ QSize nativeSize() const override;
+
+ Qt::AspectRatioMode aspectRatioMode() const override;
+ void setAspectRatioMode(Qt::AspectRatioMode mode) override;
+
+ int brightness() const override;
+ void setBrightness(int brightness) override;
+
+ int contrast() const override;
+ void setContrast(int contrast) override;
+
+ int hue() const override;
+ void setHue(int hue) override;
+
+ int saturation() const override;
+ void setSaturation(int saturation) override;
+
+ // AVFVideoOutput interface
+ void setLayer(void *playerLayer) override;
+
+private:
+ void updateAspectRatio();
+ void updatePlayerLayerBounds();
+
+ WId m_winId;
+ QRect m_displayRect;
+ bool m_fullscreen;
+ int m_brightness;
+ int m_contrast;
+ int m_hue;
+ int m_saturation;
+ Qt::AspectRatioMode m_aspectRatioMode;
+ QSize m_nativeSize;
+ AVPlayerLayer *m_playerLayer;
+ NativeView *m_nativeView;
+};
+
+QT_END_NAMESPACE
+
+#endif // AVFVIDEOWINDOWCONTROL_H
diff --git a/src/multimedia/platform/avfoundation/mediaplayer/mediaplayer.pri b/src/multimedia/platform/avfoundation/mediaplayer/mediaplayer.pri
new file mode 100644
index 000000000..2929ffe50
--- /dev/null
+++ b/src/multimedia/platform/avfoundation/mediaplayer/mediaplayer.pri
@@ -0,0 +1,48 @@
+QT += opengl network
+
+HEADERS += \
+ $$PWD/avfmediaplayercontrol_p.h \
+ $$PWD/avfmediaplayermetadatacontrol_p.h \
+ $$PWD/avfmediaplayerservice_p.h \
+ $$PWD/avfmediaplayersession_p.h \
+ $$PWD/avfmediaplayerserviceplugin_p.h \
+ $$PWD/avfvideooutput_p.h \
+ $$PWD/avfvideowindowcontrol_p.h
+
+SOURCES += \
+ $$PWD/avfmediaplayercontrol.mm \
+ $$PWD/avfmediaplayermetadatacontrol.mm \
+ $$PWD/avfmediaplayerservice.mm \
+ $$PWD/avfmediaplayerserviceplugin.mm \
+ $$PWD/avfmediaplayersession.mm \
+ $$PWD/avfvideooutput.mm \
+ $$PWD/avfvideowindowcontrol.mm
+
+ios|tvos {
+ qtConfig(opengl) {
+ HEADERS += \
+ $$PWD/avfvideoframerenderer_ios_p.h \
+ $$PWD/avfvideorenderercontrol_p.h \
+ $$PWD/avfdisplaylink_p.h
+
+ SOURCES += \
+ $$PWD/avfvideoframerenderer_ios.mm \
+ $$PWD/avfvideorenderercontrol.mm \
+ $$PWD/avfdisplaylink.mm
+ }
+ LIBS += -framework Foundation
+} else {
+ LIBS += -framework AppKit
+
+ qtConfig(opengl) {
+ HEADERS += \
+ $$PWD/avfvideoframerenderer_p.h \
+ $$PWD/avfvideorenderercontrol_p.h \
+ $$PWD/avfdisplaylink_p.h
+
+ SOURCES += \
+ $$PWD/avfvideoframerenderer.mm \
+ $$PWD/avfvideorenderercontrol.mm \
+ $$PWD/avfdisplaylink.mm
+ }
+}