diff options
Diffstat (limited to 'src/multimedia/platform/avfoundation')
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 + } +} |