diff options
author | Edward Welbourne <edward.welbourne@qt.io> | 2016-07-20 15:53:34 +0200 |
---|---|---|
committer | Edward Welbourne <edward.welbourne@qt.io> | 2016-07-21 14:03:11 +0200 |
commit | 7a5e3145550015c9c6c3e8232c8257099aa2480c (patch) | |
tree | 0b5623c19c6d2ac39a9d95befcadc957029a2446 /src/plugins/avfoundation/camera | |
parent | 631b89ddde44dfc8b72b904d8c41368b2c02d037 (diff) | |
parent | 17d54a2eb57816dbc531feee80dbd25f835e733a (diff) | |
download | qtmultimedia-7a5e3145550015c9c6c3e8232c8257099aa2480c.tar.gz |
Merge remote-tracking branch 'origin/5.7' into dev
Conflicts:
src/plugins/directshow/player/directshowiosource.cpp
One side disintermediated filling a vector; the other reduced it to one entry.
src/plugins/directshow/player/directshowiosource.h
One side renamed a member, the other added another adjacent to it.
src/plugins/pulseaudio/qpulseaudioengine.h
One side added a header, the other replaced the next with a different header.
Change-Id: I3a031975f5af43ca39cca571f215c612f640b7d6
Diffstat (limited to 'src/plugins/avfoundation/camera')
24 files changed, 1411 insertions, 297 deletions
diff --git a/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.h b/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.h new file mode 100644 index 000000000..213065443 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef AVFAUDIOENCODERSETTINGSCONTROL_H +#define AVFAUDIOENCODERSETTINGSCONTROL_H + +#include <qaudioencodersettingscontrol.h> + +@class NSDictionary; +@class AVCaptureAudioDataOutput; + +QT_BEGIN_NAMESPACE + +class AVFCameraService; + +class AVFAudioEncoderSettingsControl : public QAudioEncoderSettingsControl +{ +public: + explicit AVFAudioEncoderSettingsControl(AVFCameraService *service); + + QStringList supportedAudioCodecs() const Q_DECL_OVERRIDE; + QString codecDescription(const QString &codecName) const Q_DECL_OVERRIDE; + QList<int> supportedSampleRates(const QAudioEncoderSettings &settings, bool *continuous = 0) const Q_DECL_OVERRIDE; + QAudioEncoderSettings audioSettings() const Q_DECL_OVERRIDE; + void setAudioSettings(const QAudioEncoderSettings &settings) Q_DECL_OVERRIDE; + + NSDictionary *applySettings(); + void unapplySettings(); + +private: + AVFCameraService *m_service; + + QAudioEncoderSettings m_requestedSettings; + QAudioEncoderSettings m_actualSettings; +}; + +QT_END_NAMESPACE + +#endif // AVFAUDIOENCODERSETTINGSCONTROL_H diff --git a/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.mm b/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.mm new file mode 100644 index 000000000..143a444a6 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfaudioencodersettingscontrol.mm @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "avfaudioencodersettingscontrol.h" + +#include "avfcameraservice.h" +#include "avfcamerasession.h" + +#include <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/plugins/avfoundation/camera/avfcameracontrol.mm b/src/plugins/avfoundation/camera/avfcameracontrol.mm index 038e3b326..c47eecfdf 100644 --- a/src/plugins/avfoundation/camera/avfcameracontrol.mm +++ b/src/plugins/avfoundation/camera/avfcameracontrol.mm @@ -53,6 +53,7 @@ AVFCameraControl::AVFCameraControl(AVFCameraService *service, QObject *parent) { Q_UNUSED(service); connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(updateStatus())); + connect(this, &AVFCameraControl::captureModeChanged, m_session, &AVFCameraSession::onCaptureModeChanged); } AVFCameraControl::~AVFCameraControl() diff --git a/src/plugins/avfoundation/camera/avfcameraservice.h b/src/plugins/avfoundation/camera/avfcameraservice.h index c65536b64..9ce637ee3 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.h +++ b/src/plugins/avfoundation/camera/avfcameraservice.h @@ -67,6 +67,9 @@ class AVFImageEncoderControl; class AVFCameraFlashControl; class AVFMediaRecorderControl; class AVFMediaRecorderControlIOS; +class AVFAudioEncoderSettingsControl; +class AVFVideoEncoderSettingsControl; +class AVFMediaContainerControl; class AVFCameraService : public QMediaService { @@ -83,8 +86,7 @@ public: AVFCameraDeviceControl *videoDeviceControl() const { return m_videoDeviceControl; } AVFAudioInputSelectorControl *audioInputSelectorControl() const { return m_audioInputSelectorControl; } AVFCameraMetaDataControl *metaDataControl() const { return m_metaDataControl; } - AVFMediaRecorderControl *recorderControl() const; - AVFMediaRecorderControlIOS *recorderControlIOS() const; + QMediaRecorderControl *recorderControl() const { return m_recorderControl; } AVFImageCaptureControl *imageCaptureControl() const { return m_imageCaptureControl; } AVFCameraFocusControl *cameraFocusControl() const { return m_cameraFocusControl; } AVFCameraExposureControl *cameraExposureControl() const {return m_cameraExposureControl; } @@ -94,6 +96,9 @@ public: AVFCameraViewfinderSettingsControl *viewfinderSettingsControl() const {return m_viewfinderSettingsControl; } AVFImageEncoderControl *imageEncoderControl() const {return m_imageEncoderControl; } AVFCameraFlashControl *flashControl() const {return m_flashControl; } + AVFAudioEncoderSettingsControl *audioEncoderSettingsControl() const { return m_audioEncoderSettingsControl; } + AVFVideoEncoderSettingsControl *videoEncoderSettingsControl() const {return m_videoEncoderSettingsControl; } + AVFMediaContainerControl *mediaContainerControl() const { return m_mediaContainerControl; } private: AVFCameraSession *m_session; @@ -112,6 +117,9 @@ private: AVFCameraViewfinderSettingsControl *m_viewfinderSettingsControl; AVFImageEncoderControl *m_imageEncoderControl; AVFCameraFlashControl *m_flashControl; + AVFAudioEncoderSettingsControl *m_audioEncoderSettingsControl; + AVFVideoEncoderSettingsControl *m_videoEncoderSettingsControl; + AVFMediaContainerControl *m_mediaContainerControl; }; QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfcameraservice.mm b/src/plugins/avfoundation/camera/avfcameraservice.mm index d3f254489..a1213be3b 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.mm +++ b/src/plugins/avfoundation/camera/avfcameraservice.mm @@ -59,6 +59,9 @@ #include "avfcameraviewfindersettingscontrol.h" #include "avfimageencodercontrol.h" #include "avfcameraflashcontrol.h" +#include "avfaudioencodersettingscontrol.h" +#include "avfvideoencodersettingscontrol.h" +#include "avfmediacontainercontrol.h" #ifdef Q_OS_IOS #include "avfcamerazoomcontrol.h" @@ -105,6 +108,9 @@ AVFCameraService::AVFCameraService(QObject *parent): m_viewfinderSettingsControl = new AVFCameraViewfinderSettingsControl(this); m_imageEncoderControl = new AVFImageEncoderControl(this); m_flashControl = new AVFCameraFlashControl(this); + m_audioEncoderSettingsControl = new AVFAudioEncoderSettingsControl(this); + m_videoEncoderSettingsControl = new AVFVideoEncoderSettingsControl(this); + m_mediaContainerControl = new AVFMediaContainerControl(this); } AVFCameraService::~AVFCameraService() @@ -136,6 +142,9 @@ AVFCameraService::~AVFCameraService() delete m_viewfinderSettingsControl; delete m_imageEncoderControl; delete m_flashControl; + delete m_audioEncoderSettingsControl; + delete m_videoEncoderSettingsControl; + delete m_mediaContainerControl; delete m_session; } @@ -182,6 +191,15 @@ QMediaControl *AVFCameraService::requestControl(const char *name) if (qstrcmp(name, QCameraFlashControl_iid) == 0) return m_flashControl; + if (qstrcmp(name, QAudioEncoderSettingsControl_iid) == 0) + return m_audioEncoderSettingsControl; + + if (qstrcmp(name, QVideoEncoderSettingsControl_iid) == 0) + return m_videoEncoderSettingsControl; + + if (qstrcmp(name, QMediaContainerControl_iid) == 0) + return m_mediaContainerControl; + if (qstrcmp(name,QMediaVideoProbeControl_iid) == 0) { AVFMediaVideoProbeControl *videoProbe = 0; videoProbe = new AVFMediaVideoProbeControl(this); @@ -220,23 +238,5 @@ void AVFCameraService::releaseControl(QMediaControl *control) } } -AVFMediaRecorderControl *AVFCameraService::recorderControl() const -{ -#ifdef Q_OS_IOS - return 0; -#else - return static_cast<AVFMediaRecorderControl *>(m_recorderControl); -#endif -} - -AVFMediaRecorderControlIOS *AVFCameraService::recorderControlIOS() const -{ -#ifdef Q_OS_OSX - return 0; -#else - return static_cast<AVFMediaRecorderControlIOS *>(m_recorderControl); -#endif -} - #include "moc_avfcameraservice.cpp" diff --git a/src/plugins/avfoundation/camera/avfcamerasession.h b/src/plugins/avfoundation/camera/avfcamerasession.h index 1c7aa40ab..3f90f1f7f 100644 --- a/src/plugins/avfoundation/camera/avfcamerasession.h +++ b/src/plugins/avfoundation/camera/avfcamerasession.h @@ -99,6 +99,8 @@ public Q_SLOTS: void processSessionStarted(); void processSessionStopped(); + void onCaptureModeChanged(QCamera::CaptureModes mode); + void onCameraFrameFetched(const QVideoFrame &frame); Q_SIGNALS: diff --git a/src/plugins/avfoundation/camera/avfcamerasession.mm b/src/plugins/avfoundation/camera/avfcamerasession.mm index 5adc30296..c32b072ef 100644 --- a/src/plugins/avfoundation/camera/avfcamerasession.mm +++ b/src/plugins/avfoundation/camera/avfcamerasession.mm @@ -292,8 +292,8 @@ void AVFCameraSession::setState(QCamera::State newState) m_defaultCodec = 0; defaultCodec(); - bool activeFormatSet = applyImageEncoderSettings(); - activeFormatSet |= applyViewfinderSettings(); + bool activeFormatSet = applyImageEncoderSettings() + | applyViewfinderSettings(); [m_captureSession commitConfiguration]; @@ -344,6 +344,17 @@ void AVFCameraSession::processSessionStopped() } } +void AVFCameraSession::onCaptureModeChanged(QCamera::CaptureModes mode) +{ + Q_UNUSED(mode) + + const QCamera::State s = state(); + if (s == QCamera::LoadedState || s == QCamera::ActiveState) { + applyImageEncoderSettings(); + applyViewfinderSettings(); + } +} + void AVFCameraSession::attachVideoInputDevice() { //Attach video input device: @@ -387,18 +398,17 @@ bool AVFCameraSession::applyImageEncoderSettings() bool AVFCameraSession::applyViewfinderSettings() { if (AVFCameraViewfinderSettingsControl2 *vfControl = m_service->viewfinderSettingsControl2()) { + QCamera::CaptureModes currentMode = m_service->cameraControl()->captureMode(); QCameraViewfinderSettings vfSettings(vfControl->requestedSettings()); // Viewfinder and image capture solutions must be the same, if an image capture // resolution is set, it takes precedence over the viewfinder resolution. - if (AVFImageEncoderControl *imControl = m_service->imageEncoderControl()) { - const QSize imageResolution(imControl->requestedSettings().resolution()); - if (!imageResolution.isNull() && imageResolution.isValid()) { + if (currentMode.testFlag(QCamera::CaptureStillImage)) { + const QSize imageResolution(m_service->imageEncoderControl()->requestedSettings().resolution()); + if (!imageResolution.isNull() && imageResolution.isValid()) vfSettings.setResolution(imageResolution); - vfControl->setViewfinderSettings(vfSettings); - } } - return vfControl->applySettings(); + return vfControl->applySettings(vfSettings); } return false; diff --git a/src/plugins/avfoundation/camera/avfcamerautility.h b/src/plugins/avfoundation/camera/avfcamerautility.h index ae36dae73..4da5f751e 100644 --- a/src/plugins/avfoundation/camera/avfcamerautility.h +++ b/src/plugins/avfoundation/camera/avfcamerautility.h @@ -182,8 +182,15 @@ AVCaptureDeviceFormat *qt_find_best_framerate_match(AVCaptureDevice *captureDevi Float64 fps); AVFrameRateRange *qt_find_supported_framerate_range(AVCaptureDeviceFormat *format, Float64 fps); +bool qt_formats_are_equal(AVCaptureDeviceFormat *f1, AVCaptureDeviceFormat *f2); +bool qt_set_active_format(AVCaptureDevice *captureDevice, AVCaptureDeviceFormat *format, bool preserveFps); + #endif +AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection); +void qt_set_framerate_limits(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection, + qreal minFPS, qreal maxFPS); + QT_END_NAMESPACE #endif diff --git a/src/plugins/avfoundation/camera/avfcamerautility.mm b/src/plugins/avfoundation/camera/avfcamerautility.mm index bdf88e24f..279642e4f 100644 --- a/src/plugins/avfoundation/camera/avfcamerautility.mm +++ b/src/plugins/avfoundation/camera/avfcamerautility.mm @@ -177,8 +177,7 @@ QVector<AVCaptureDeviceFormat *> qt_unique_device_formats(AVCaptureDevice *captu QSize qt_device_format_resolution(AVCaptureDeviceFormat *format) { - Q_ASSERT(format); - if (!format.formatDescription) + if (!format || !format.formatDescription) return QSize(); const CMVideoDimensions res = CMVideoFormatDescriptionGetDimensions(format.formatDescription); @@ -387,6 +386,243 @@ AVFrameRateRange *qt_find_supported_framerate_range(AVCaptureDeviceFormat *forma return match; } +bool qt_formats_are_equal(AVCaptureDeviceFormat *f1, AVCaptureDeviceFormat *f2) +{ + if (f1 == f2) + return true; + + if (![f1.mediaType isEqualToString:f2.mediaType]) + return false; + + return CMFormatDescriptionEqual(f1.formatDescription, f2.formatDescription); +} + +bool qt_set_active_format(AVCaptureDevice *captureDevice, AVCaptureDeviceFormat *format, bool preserveFps) +{ + static bool firstSet = true; + + if (!captureDevice || !format) + return false; + + if (qt_formats_are_equal(captureDevice.activeFormat, format)) { + if (firstSet) { + // The capture device format is persistent. The first time we set a format, report that + // it changed even if the formats are the same. + // This prevents the session from resetting the format to the default value. + firstSet = false; + return true; + } + return false; + } + + firstSet = false; + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qWarning("Failed to set active format (lock failed)"); + return false; + } + + // Changing the activeFormat resets the frame rate. + AVFPSRange fps; + if (preserveFps) + fps = qt_current_framerates(captureDevice, nil); + + captureDevice.activeFormat = format; + + if (preserveFps) + qt_set_framerate_limits(captureDevice, nil, fps.first, fps.second); + + return true; +} + #endif // SDK +void qt_set_framerate_limits(AVCaptureConnection *videoConnection, qreal minFPS, qreal maxFPS) +{ + Q_ASSERT(videoConnection); + + if (minFPS < 0. || maxFPS < 0. || (maxFPS && maxFPS < minFPS)) { + qDebugCamera() << Q_FUNC_INFO << "invalid framerates (min, max):" + << minFPS << maxFPS; + return; + } + + CMTime minDuration = kCMTimeInvalid; + if (maxFPS > 0.) { + if (!videoConnection.supportsVideoMinFrameDuration) + qDebugCamera() << Q_FUNC_INFO << "maximum framerate is not supported"; + else + minDuration = CMTimeMake(1, maxFPS); + } + if (videoConnection.supportsVideoMinFrameDuration) + videoConnection.videoMinFrameDuration = minDuration; + +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_5_0) +#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) + if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_9) { + if (minFPS > 0.) + qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; + } else +#endif + { + CMTime maxDuration = kCMTimeInvalid; + if (minFPS > 0.) { + if (!videoConnection.supportsVideoMaxFrameDuration) + qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; + else + maxDuration = CMTimeMake(1, minFPS); + } + if (videoConnection.supportsVideoMaxFrameDuration) + videoConnection.videoMaxFrameDuration = maxDuration; + } +#else + if (minFPS > 0.) + qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; +#endif +} + +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + +CMTime qt_adjusted_frame_duration(AVFrameRateRange *range, qreal fps) +{ + Q_ASSERT(range); + Q_ASSERT(fps > 0.); + + if (range.maxFrameRate - range.minFrameRate < 0.1) { + // Can happen on OS X. + return range.minFrameDuration; + } + + if (fps <= range.minFrameRate) + return range.maxFrameDuration; + if (fps >= range.maxFrameRate) + return range.minFrameDuration; + + int n, d; + qt_real_to_fraction(1. / fps, &n, &d); + return CMTimeMake(n, d); +} + +void qt_set_framerate_limits(AVCaptureDevice *captureDevice, qreal minFPS, qreal maxFPS) +{ + Q_ASSERT(captureDevice); + if (!captureDevice.activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "no active capture device format"; + return; + } + + if (minFPS < 0. || maxFPS < 0. || (maxFPS && maxFPS < minFPS)) { + qDebugCamera() << Q_FUNC_INFO << "invalid framerates (min, max):" + << minFPS << maxFPS; + return; + } + + CMTime minFrameDuration = kCMTimeInvalid; + CMTime maxFrameDuration = kCMTimeInvalid; + if (maxFPS || minFPS) { + AVFrameRateRange *range = qt_find_supported_framerate_range(captureDevice.activeFormat, + maxFPS ? maxFPS : minFPS); + if (!range) { + qDebugCamera() << Q_FUNC_INFO << "no framerate range found, (min, max):" + << minFPS << maxFPS; + return; + } + + if (maxFPS) + minFrameDuration = qt_adjusted_frame_duration(range, maxFPS); + if (minFPS) + maxFrameDuration = qt_adjusted_frame_duration(range, minFPS); + } + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + + // While Apple's docs say kCMTimeInvalid will end in default + // settings for this format, kCMTimeInvalid on OS X ends with a runtime + // exception: + // "The activeVideoMinFrameDuration passed is not supported by the device." + // Instead, use the first item in the supported frame rates. +#ifdef Q_OS_IOS + [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; + [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; +#else // Q_OS_OSX + if (CMTimeCompare(minFrameDuration, kCMTimeInvalid) == 0 + && CMTimeCompare(maxFrameDuration, kCMTimeInvalid) == 0) { + AVFrameRateRange *range = captureDevice.activeFormat.videoSupportedFrameRateRanges.firstObject; + minFrameDuration = range.minFrameDuration; + maxFrameDuration = range.maxFrameDuration; + } + + if (CMTimeCompare(minFrameDuration, kCMTimeInvalid)) + [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; + +#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) +#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) +#endif + { + if (CMTimeCompare(maxFrameDuration, kCMTimeInvalid)) + [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; + } +#endif // QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) +#endif // Q_OS_OSX +} + +#endif // Platform SDK >= 10.9, >= 7.0. + +void qt_set_framerate_limits(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection, + qreal minFPS, qreal maxFPS) +{ + Q_ASSERT(captureDevice); +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) + qt_set_framerate_limits(captureDevice, minFPS, maxFPS); + else +#endif + if (videoConnection) + qt_set_framerate_limits(videoConnection, minFPS, maxFPS); + +} + +AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection) +{ + Q_ASSERT(captureDevice); + + AVFPSRange fps; +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_7, QSysInfo::MV_IOS_7_0)) { + const CMTime minDuration = captureDevice.activeVideoMinFrameDuration; + if (CMTimeCompare(minDuration, kCMTimeInvalid)) { + if (const Float64 minSeconds = CMTimeGetSeconds(minDuration)) + fps.second = 1. / minSeconds; // Max FPS = 1 / MinDuration. + } + +#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) +#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) +#endif + { + const CMTime maxDuration = captureDevice.activeVideoMaxFrameDuration; + if (CMTimeCompare(maxDuration, kCMTimeInvalid)) { + if (const Float64 maxSeconds = CMTimeGetSeconds(maxDuration)) + fps.first = 1. / maxSeconds; // Min FPS = 1 / MaxDuration. + } + } +#endif // QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) + + } else { +#else // OSX < 10.7 or iOS < 7.0 + { +#endif // QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (videoConnection) + fps = qt_connection_framerates(videoConnection); + } + + return fps; +} + QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h index 138ecc332..bed755339 100644 --- a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h +++ b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h @@ -82,7 +82,7 @@ private: AVCaptureDeviceFormat *findBestFormatMatch(const QCameraViewfinderSettings &settings) const; QVector<QVideoFrame::PixelFormat> viewfinderPixelFormats() const; bool convertPixelFormatIfSupported(QVideoFrame::PixelFormat format, unsigned &avfFormat) const; - bool applySettings(); + bool applySettings(const QCameraViewfinderSettings &settings); QCameraViewfinderSettings requestedSettings() const; AVCaptureConnection *videoConnection() const; diff --git a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm index 19bc8815c..23dd2a4aa 100644 --- a/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm +++ b/src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm @@ -72,194 +72,6 @@ bool qt_framerates_sane(const QCameraViewfinderSettings &settings) return !maxFPS || maxFPS >= minFPS; } -void qt_set_framerate_limits(AVCaptureConnection *videoConnection, - const QCameraViewfinderSettings &settings) -{ - Q_ASSERT(videoConnection); - - if (!qt_framerates_sane(settings)) { - qDebugCamera() << Q_FUNC_INFO << "invalid framerate (min, max):" - << settings.minimumFrameRate() << settings.maximumFrameRate(); - return; - } - - const qreal maxFPS = settings.maximumFrameRate(); - CMTime minDuration = kCMTimeInvalid; - if (maxFPS > 0.) { - if (!videoConnection.supportsVideoMinFrameDuration) - qDebugCamera() << Q_FUNC_INFO << "maximum framerate is not supported"; - else - minDuration = CMTimeMake(1, maxFPS); - } - if (videoConnection.supportsVideoMinFrameDuration) - videoConnection.videoMinFrameDuration = minDuration; - - const qreal minFPS = settings.minimumFrameRate(); -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_5_0) -#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) - if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_9) { - if (minFPS > 0.) - qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; - } else -#endif - { - CMTime maxDuration = kCMTimeInvalid; - if (minFPS > 0.) { - if (!videoConnection.supportsVideoMaxFrameDuration) - qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; - else - maxDuration = CMTimeMake(1, minFPS); - } - if (videoConnection.supportsVideoMaxFrameDuration) - videoConnection.videoMaxFrameDuration = maxDuration; - } -#else - if (minFPS > 0.) - qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; -#endif -} - -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - -CMTime qt_adjusted_frame_duration(AVFrameRateRange *range, qreal fps) -{ - Q_ASSERT(range); - Q_ASSERT(fps > 0.); - - if (range.maxFrameRate - range.minFrameRate < 0.1) { - // Can happen on OS X. - return range.minFrameDuration; - } - - if (fps <= range.minFrameRate) - return range.maxFrameDuration; - if (fps >= range.maxFrameRate) - return range.minFrameDuration; - - int n, d; - qt_real_to_fraction(1. / fps, &n, &d); - return CMTimeMake(n, d); -} - -void qt_set_framerate_limits(AVCaptureDevice *captureDevice, - const QCameraViewfinderSettings &settings) -{ - Q_ASSERT(captureDevice); - if (!captureDevice.activeFormat) { - qDebugCamera() << Q_FUNC_INFO << "no active capture device format"; - return; - } - - const qreal minFPS = settings.minimumFrameRate(); - const qreal maxFPS = settings.maximumFrameRate(); - if (!qt_framerates_sane(settings)) { - qDebugCamera() << Q_FUNC_INFO << "invalid framerates (min, max):" - << minFPS << maxFPS; - return; - } - - CMTime minFrameDuration = kCMTimeInvalid; - CMTime maxFrameDuration = kCMTimeInvalid; - if (maxFPS || minFPS) { - AVFrameRateRange *range = qt_find_supported_framerate_range(captureDevice.activeFormat, - maxFPS ? maxFPS : minFPS); - if (!range) { - qDebugCamera() << Q_FUNC_INFO << "no framerate range found, (min, max):" - << minFPS << maxFPS; - return; - } - - if (maxFPS) - minFrameDuration = qt_adjusted_frame_duration(range, maxFPS); - if (minFPS) - maxFrameDuration = qt_adjusted_frame_duration(range, minFPS); - } - - const AVFConfigurationLock lock(captureDevice); - if (!lock) { - qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; - return; - } - - // While Apple's docs say kCMTimeInvalid will end in default - // settings for this format, kCMTimeInvalid on OS X ends with a runtime - // exception: - // "The activeVideoMinFrameDuration passed is not supported by the device." -#ifdef Q_OS_IOS - [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; - [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; -#else // Q_OS_OSX - - if (CMTimeCompare(minFrameDuration, kCMTimeInvalid)) - [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; - -#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) -#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) -#endif - { - if (CMTimeCompare(maxFrameDuration, kCMTimeInvalid)) - [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; - } -#endif // QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) -#endif // Q_OS_OSX -} - -#endif // Platform SDK >= 10.9, >= 7.0. - -// 'Dispatchers': - -AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection) -{ - Q_ASSERT(captureDevice); - - AVFPSRange fps; -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_7, QSysInfo::MV_IOS_7_0)) { - const CMTime minDuration = captureDevice.activeVideoMinFrameDuration; - if (CMTimeCompare(minDuration, kCMTimeInvalid)) { - if (const Float64 minSeconds = CMTimeGetSeconds(minDuration)) - fps.second = 1. / minSeconds; // Max FPS = 1 / MinDuration. - } - -#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) -#if QT_OSX_DEPLOYMENT_TARGET_BELOW(__MAC_10_9) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) -#endif - { - const CMTime maxDuration = captureDevice.activeVideoMaxFrameDuration; - if (CMTimeCompare(maxDuration, kCMTimeInvalid)) { - if (const Float64 maxSeconds = CMTimeGetSeconds(maxDuration)) - fps.first = 1. / maxSeconds; // Min FPS = 1 / MaxDuration. - } - } -#endif // QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9) - - } else { -#else // OSX < 10.7 or iOS < 7.0 - { -#endif // QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - if (videoConnection) - fps = qt_connection_framerates(videoConnection); - } - - return fps; -} - -void qt_set_framerate_limits(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection, - const QCameraViewfinderSettings &settings) -{ - Q_ASSERT(captureDevice); -#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) - qt_set_framerate_limits(captureDevice, settings); - else -#endif - if (videoConnection) - qt_set_framerate_limits(videoConnection, settings); - -} - } // Unnamed namespace. AVFCameraViewfinderSettingsControl2::AVFCameraViewfinderSettingsControl2(AVFCameraService *service) @@ -399,7 +211,7 @@ void AVFCameraViewfinderSettingsControl2::setViewfinderSettings(const QCameraVie return; m_settings = settings; - applySettings(); + applySettings(m_settings); } QVideoFrame::PixelFormat AVFCameraViewfinderSettingsControl2::QtPixelFormatFromCVFormat(unsigned avPixelFormat) @@ -494,8 +306,9 @@ AVCaptureDeviceFormat *AVFCameraViewfinderSettingsControl2::findBestFormatMatch( const qreal minFPS(settings.minimumFrameRate()); const qreal maxFPS(settings.maximumFrameRate()); if (minFPS || maxFPS) - return qt_find_best_framerate_match(captureDevice, maxFPS ? maxFPS : minFPS, - m_service->session()->defaultCodec()); + return qt_find_best_framerate_match(captureDevice, + m_service->session()->defaultCodec(), + maxFPS ? maxFPS : minFPS); // Ignore PAR for the moment (PAR without resolution can // pick a format with really bad resolution). // No need to test pixel format, just return settings. @@ -569,7 +382,7 @@ bool AVFCameraViewfinderSettingsControl2::convertPixelFormatIfSupported(QVideoFr return found; } -bool AVFCameraViewfinderSettingsControl2::applySettings() +bool AVFCameraViewfinderSettingsControl2::applySettings(const QCameraViewfinderSettings &settings) { if (m_service->session()->state() != QCamera::LoadedState && m_service->session()->state() != QCamera::ActiveState) { @@ -583,17 +396,9 @@ bool AVFCameraViewfinderSettingsControl2::applySettings() bool activeFormatChanged = false; #if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) - AVCaptureDeviceFormat *match = findBestFormatMatch(m_settings); + AVCaptureDeviceFormat *match = findBestFormatMatch(settings); if (match) { - if (match != captureDevice.activeFormat) { - const AVFConfigurationLock lock(captureDevice); - if (lock) { - captureDevice.activeFormat = match; - activeFormatChanged = true; - } else { - qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; - } - } + activeFormatChanged = qt_set_active_format(captureDevice, match, false); } else { qDebugCamera() << Q_FUNC_INFO << "matching device format not found"; // We still can update the pixel format at least. @@ -603,7 +408,7 @@ bool AVFCameraViewfinderSettingsControl2::applySettings() AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput() ? m_service->videoOutput()->videoDataOutput() : 0; if (videoOutput) { unsigned avfPixelFormat = 0; - if (!convertPixelFormatIfSupported(m_settings.pixelFormat(), avfPixelFormat)) { + if (!convertPixelFormatIfSupported(settings.pixelFormat(), avfPixelFormat)) { // If the the pixel format is not specified or invalid, pick the preferred video surface // format, or if no surface is set, the preferred capture device format @@ -639,7 +444,7 @@ bool AVFCameraViewfinderSettingsControl2::applySettings() } } - qt_set_framerate_limits(captureDevice, videoConnection(), m_settings); + qt_set_framerate_limits(captureDevice, videoConnection(), settings.minimumFrameRate(), settings.maximumFrameRate()); return activeFormatChanged; } diff --git a/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm b/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm index 6465e69e3..b59aa7bfd 100644 --- a/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm +++ b/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm @@ -73,6 +73,7 @@ AVFImageCaptureControl::AVFImageCaptureControl(AVFCameraService *service, QObjec connect(m_cameraControl, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateReadyStatus())); connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(updateCaptureConnection())); + connect(m_cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(updateCaptureConnection())); connect(m_session, &AVFCameraSession::newViewfinderFrame, this, &AVFImageCaptureControl::onNewViewfinderFrame, diff --git a/src/plugins/avfoundation/camera/avfimageencodercontrol.mm b/src/plugins/avfoundation/camera/avfimageencodercontrol.mm index 30e36262a..e5aa8a4c2 100644 --- a/src/plugins/avfoundation/camera/avfimageencodercontrol.mm +++ b/src/plugins/avfoundation/camera/avfimageencodercontrol.mm @@ -44,6 +44,7 @@ #include "avfcamerasession.h" #include "avfcameraservice.h" #include "avfcameradebug.h" +#include "avfcameracontrol.h" #include <QtMultimedia/qmediaencodersettings.h> @@ -188,7 +189,8 @@ bool AVFImageEncoderControl::applySettings() AVFCameraSession *session = m_service->session(); if (!session || (session->state() != QCamera::ActiveState - && session->state() != QCamera::LoadedState)) { + && session->state() != QCamera::LoadedState) + || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureStillImage)) { return false; } @@ -231,15 +233,7 @@ bool AVFImageEncoderControl::applySettings() return false; } - if (match != captureDevice.activeFormat) { - const AVFConfigurationLock lock(captureDevice); - if (!lock) { - qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; - return false; - } - captureDevice.activeFormat = match; - activeFormatChanged = true; - } + activeFormatChanged = qt_set_active_format(captureDevice, match, true); #if defined(Q_OS_IOS) && QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_8_0) if (QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_8_0) { diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.h b/src/plugins/avfoundation/camera/avfmediaassetwriter.h index c70deea10..fa1ec46a2 100644 --- a/src/plugins/avfoundation/camera/avfmediaassetwriter.h +++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.h @@ -70,6 +70,7 @@ QT_END_NAMESPACE QT_PREPEND_NAMESPACE(AVFScopedPointer)<AVCaptureDeviceInput> m_audioInput; QT_PREPEND_NAMESPACE(AVFScopedPointer)<AVCaptureAudioDataOutput> m_audioOutput; QT_PREPEND_NAMESPACE(AVFScopedPointer)<AVAssetWriterInput> m_audioWriterInput; + AVCaptureDevice *m_audioCaptureDevice; // High priority serial queue for video output: QT_PREPEND_NAMESPACE(AVFScopedPointer)<dispatch_queue_t> m_videoQueue; @@ -92,13 +93,19 @@ QT_END_NAMESPACE @private CMTime m_startTime; CMTime m_lastTimeStamp; + + NSDictionary *m_audioSettings; + NSDictionary *m_videoSettings; } - (id)initWithQueue:(dispatch_queue_t)writerQueue delegate:(QT_PREPEND_NAMESPACE(AVFMediaRecorderControlIOS) *)delegate; - (bool)setupWithFileURL:(NSURL *)fileURL - cameraService:(QT_PREPEND_NAMESPACE(AVFCameraService) *)service; + cameraService:(QT_PREPEND_NAMESPACE(AVFCameraService) *)service + audioSettings:(NSDictionary *)audioSettings + videoSettings:(NSDictionary *)videoSettings + transform:(CGAffineTransform)transform; - (void)start; - (void)stop; diff --git a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm index 1b8c253e2..98c8f99ff 100644 --- a/src/plugins/avfoundation/camera/avfmediaassetwriter.mm +++ b/src/plugins/avfoundation/camera/avfmediaassetwriter.mm @@ -44,6 +44,7 @@ #include "avfcameraservice.h" #include "avfcamerasession.h" #include "avfcameradebug.h" +#include "avfmediacontainercontrol.h" //#include <QtCore/qmutexlocker.h> #include <QtCore/qmetaobject.h> @@ -79,8 +80,6 @@ bool qt_camera_service_isValid(AVFCameraService *service) - (bool)addAudioCapture; - (bool)addWriterInputs; - (void)setQueues; -- (NSDictionary *)videoSettings; -- (NSDictionary *)audioSettings; - (void)updateDuration:(CMTime)newTimeStamp; @end @@ -104,6 +103,8 @@ bool qt_camera_service_isValid(AVFCameraService *service) m_startTime = kCMTimeInvalid; m_lastTimeStamp = kCMTimeInvalid; m_durationInMs.store(0); + m_audioSettings = nil; + m_videoSettings = nil; } return self; @@ -111,6 +112,9 @@ bool qt_camera_service_isValid(AVFCameraService *service) - (bool)setupWithFileURL:(NSURL *)fileURL cameraService:(AVFCameraService *)service + audioSettings:(NSDictionary *)audioSettings + videoSettings:(NSDictionary *)videoSettings + transform:(CGAffineTransform)transform { Q_ASSERT(fileURL); @@ -120,6 +124,8 @@ bool qt_camera_service_isValid(AVFCameraService *service) } m_service = service; + m_audioSettings = audioSettings; + m_videoSettings = videoSettings; m_videoQueue.reset(dispatch_queue_create("video-output-queue", DISPATCH_QUEUE_SERIAL)); if (!m_videoQueue) { @@ -133,7 +139,9 @@ bool qt_camera_service_isValid(AVFCameraService *service) // But we still can write video! } - m_assetWriter.reset([[AVAssetWriter alloc] initWithURL:fileURL fileType:AVFileTypeQuickTimeMovie error:nil]); + m_assetWriter.reset([[AVAssetWriter alloc] initWithURL:fileURL + fileType:m_service->mediaContainerControl()->fileType() + error:nil]); if (!m_assetWriter) { qDebugCamera() << Q_FUNC_INFO << "failed to create asset writer"; return false; @@ -151,10 +159,14 @@ bool qt_camera_service_isValid(AVFCameraService *service) [session removeInput:m_audioInput]; m_audioOutput.reset(); m_audioInput.reset(); + m_audioCaptureDevice = 0; } m_assetWriter.reset(); return false; } + + m_cameraWriterInput.data().transform = transform; + // Ready to start ... return true; } @@ -328,20 +340,22 @@ bool qt_camera_service_isValid(AVFCameraService *service) AVCaptureSession *captureSession = m_service->session()->captureSession(); - AVCaptureDevice *audioDevice = m_service->audioInputSelectorControl()->createCaptureDevice(); - if (!audioDevice) { + m_audioCaptureDevice = m_service->audioInputSelectorControl()->createCaptureDevice(); + if (!m_audioCaptureDevice) { qWarning() << Q_FUNC_INFO << "no audio input device available"; return false; } else { NSError *error = nil; - m_audioInput.reset([[AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error] retain]); + m_audioInput.reset([[AVCaptureDeviceInput deviceInputWithDevice:m_audioCaptureDevice error:&error] retain]); if (!m_audioInput || error) { qWarning() << Q_FUNC_INFO << "failed to create audio device input"; + m_audioCaptureDevice = 0; m_audioInput.reset(); return false; } else if (![captureSession canAddInput:m_audioInput]) { qWarning() << Q_FUNC_INFO << "could not connect the audio input"; + m_audioCaptureDevice = 0; m_audioInput.reset(); return false; } else { @@ -356,6 +370,7 @@ bool qt_camera_service_isValid(AVFCameraService *service) } else { qDebugCamera() << Q_FUNC_INFO << "failed to add audio output"; [captureSession removeInput:m_audioInput]; + m_audioCaptureDevice = 0; m_audioInput.reset(); m_audioOutput.reset(); return false; @@ -370,7 +385,9 @@ bool qt_camera_service_isValid(AVFCameraService *service) && m_service->videoOutput()->videoDataOutput()); Q_ASSERT(m_assetWriter); - m_cameraWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:[self videoSettings]]); + m_cameraWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo + outputSettings:m_videoSettings + sourceFormatHint:m_service->session()->videoCaptureDevice().activeFormat.formatDescription]); if (!m_cameraWriterInput) { qDebugCamera() << Q_FUNC_INFO << "failed to create camera writer input"; return false; @@ -387,7 +404,10 @@ bool qt_camera_service_isValid(AVFCameraService *service) m_cameraWriterInput.data().expectsMediaDataInRealTime = YES; if (m_audioOutput) { - m_audioWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:[self audioSettings]]); + CMFormatDescriptionRef sourceFormat = m_audioCaptureDevice ? m_audioCaptureDevice.activeFormat.formatDescription : 0; + m_audioWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio + outputSettings:m_audioSettings + sourceFormatHint:sourceFormat]); if (!m_audioWriterInput) { qDebugCamera() << Q_FUNC_INFO << "failed to create audio writer input"; // But we still can record video. @@ -417,39 +437,6 @@ bool qt_camera_service_isValid(AVFCameraService *service) } } - -- (NSDictionary *)videoSettings -{ - // TODO: these settings should be taken from - // the video encoding settings control. - // For now we either take recommended (iOS >= 7.0) - // or some hardcoded values - they are still better than nothing (nil). -#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_7_0) - AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput()->videoDataOutput(); - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_7_0 && videoOutput) - return [videoOutput recommendedVideoSettingsForAssetWriterWithOutputFileType:AVFileTypeQuickTimeMovie]; -#endif - NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, - [NSNumber numberWithInt:1280], AVVideoWidthKey, - [NSNumber numberWithInt:720], AVVideoHeightKey, nil]; - - return videoSettings; -} - -- (NSDictionary *)audioSettings -{ - // TODO: these settings should be taken from - // the video/audio encoder settings control. - // For now we either take recommended (iOS >= 7.0) - // or nil - this seems to be good enough. -#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_7_0) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_7_0 && m_audioOutput) - return [m_audioOutput recommendedAudioSettingsForAssetWriterWithOutputFileType:AVFileTypeQuickTimeMovie]; -#endif - - return nil; -} - - (void)updateDuration:(CMTime)newTimeStamp { Q_ASSERT(CMTimeCompare(m_startTime, kCMTimeInvalid)); diff --git a/src/plugins/avfoundation/camera/avfmediacontainercontrol.h b/src/plugins/avfoundation/camera/avfmediacontainercontrol.h new file mode 100644 index 000000000..da31d2d13 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfmediacontainercontrol.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef AVFMEDIACONTAINERCONTROL_H +#define AVFMEDIACONTAINERCONTROL_H + +#include <qmediacontainercontrol.h> + +@class NSString; + +QT_BEGIN_NAMESPACE + +class AVFCameraService; + +class AVFMediaContainerControl : public QMediaContainerControl +{ +public: + explicit AVFMediaContainerControl(AVFCameraService *service); + + QStringList supportedContainers() const Q_DECL_OVERRIDE; + QString containerFormat() const Q_DECL_OVERRIDE; + void setContainerFormat(const QString &format) Q_DECL_OVERRIDE; + QString containerDescription(const QString &formatMimeType) const Q_DECL_OVERRIDE; + + NSString *fileType() const; + +private: + QString m_format; +}; + +QT_END_NAMESPACE + +#endif // AVFMEDIACONTAINERCONTROL_H diff --git a/src/plugins/avfoundation/camera/avfmediacontainercontrol.mm b/src/plugins/avfoundation/camera/avfmediacontainercontrol.mm new file mode 100644 index 000000000..a8dc3c844 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfmediacontainercontrol.mm @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "avfmediacontainercontrol.h" + +#include <AVFoundation/AVMediaFormat.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/plugins/avfoundation/camera/avfmediarecordercontrol.h b/src/plugins/avfoundation/camera/avfmediarecordercontrol.h index b6d2e1b2a..a4894b3da 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol.h +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol.h @@ -45,6 +45,7 @@ #import <AVFoundation/AVFoundation.h> #include "avfstoragelocation.h" +#include "avfcamerautility.h" @class AVFMediaRecorderDelegate; @@ -74,6 +75,7 @@ public: qreal volume() const; void applySettings(); + void unapplySettings(); public Q_SLOTS: void setState(QMediaRecorder::State state); @@ -89,6 +91,7 @@ private Q_SLOTS: void updateStatus(); private: + AVFCameraService *m_service; AVFCameraControl *m_cameraControl; AVFAudioInputSelectorControl *m_audioInputControl; AVFCameraSession *m_session; @@ -108,6 +111,8 @@ private: AVCaptureMovieFileOutput *m_movieOutput; AVFMediaRecorderDelegate *m_recorderDelagate; AVFStorageLocation m_storageLocation; + + AVFPSRange m_restoreFPS; }; QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm b/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm index 8460abe14..79bf2e932 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol.mm @@ -43,7 +43,9 @@ #include "avfcameraservice.h" #include "avfcameracontrol.h" #include "avfaudioinputselectorcontrol.h" -#include "avfcamerautility.h" +#include "avfaudioencodersettingscontrol.h" +#include "avfvideoencodersettingscontrol.h" +#include "avfmediacontainercontrol.h" #include <QtCore/qurl.h> #include <QtCore/qfileinfo.h> @@ -121,6 +123,7 @@ QT_USE_NAMESPACE AVFMediaRecorderControl::AVFMediaRecorderControl(AVFCameraService *service, QObject *parent) : QMediaRecorderControl(parent) + , m_service(service) , m_cameraControl(service->cameraControl()) , m_audioInputControl(service->audioInputSelectorControl()) , m_session(service->session()) @@ -132,6 +135,7 @@ AVFMediaRecorderControl::AVFMediaRecorderControl(AVFCameraService *service, QObj , m_muted(false) , m_volume(1.0) , m_audioInput(nil) + , m_restoreFPS(-1, -1) { m_movieOutput = [[AVCaptureMovieFileOutput alloc] init]; m_recorderDelagate = [[AVFMediaRecorderDelegate alloc] initWithRecorder:this]; @@ -231,6 +235,29 @@ qreal AVFMediaRecorderControl::volume() const void AVFMediaRecorderControl::applySettings() { + if (m_state != QMediaRecorder::StoppedState + || (m_session->state() != QCamera::ActiveState && m_session->state() != QCamera::LoadedState) + || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureVideo)) { + return; + } + + // 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) @@ -244,24 +271,28 @@ void AVFMediaRecorderControl::setState(QMediaRecorder::State state) case QMediaRecorder::RecordingState: { if (m_connected) { - m_state = QMediaRecorder::RecordingState; - m_recordingStarted = false; - m_recordingFinished = false; - QString outputLocationPath = m_outputLocation.scheme() == QLatin1String("file") ? m_outputLocation.path() : m_outputLocation.toString(); + QString extension = m_service->mediaContainerControl()->containerFormat(); + QUrl actualLocation = QUrl::fromLocalFile( m_storageLocation.generateFileName(outputLocationPath, QCamera::CaptureVideo, QLatin1String("clip_"), - QLatin1String("mp4"))); + extension)); qDebugCamera() << "Video capture location:" << actualLocation.toString(); + applySettings(); + [m_movieOutput startRecordingToOutputFileURL:actualLocation.toNSURL() recordingDelegate:m_recorderDelagate]; + m_state = QMediaRecorder::RecordingState; + m_recordingStarted = false; + m_recordingFinished = false; + Q_EMIT actualLocationChanged(actualLocation); } else { Q_EMIT error(QMediaRecorder::FormatError, tr("Recorder not configured")); @@ -277,6 +308,7 @@ void AVFMediaRecorderControl::setState(QMediaRecorder::State state) { m_state = QMediaRecorder::StoppedState; [m_movieOutput stopRecording]; + unapplySettings(); } } diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h index c3fe02c44..6736f4639 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.h @@ -45,6 +45,7 @@ #include "avfcamerautility.h" #include <QtMultimedia/qmediarecordercontrol.h> +#include <private/qvideooutputorientationhandler_p.h> #include <QtCore/qglobal.h> #include <QtCore/qurl.h> @@ -76,6 +77,7 @@ public: qreal volume() const Q_DECL_OVERRIDE; void applySettings() Q_DECL_OVERRIDE; + void unapplySettings(); public Q_SLOTS: void setState(QMediaRecorder::State state) Q_DECL_OVERRIDE; @@ -104,6 +106,10 @@ private: QMediaRecorder::State m_state; QMediaRecorder::Status m_lastStatus; + + NSDictionary *m_audioSettings; + NSDictionary *m_videoSettings; + QVideoOutputOrientationHandler m_orientationHandler; }; QT_END_NAMESPACE diff --git a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm index 72386eeda..7c8725260 100644 --- a/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm +++ b/src/plugins/avfoundation/camera/avfmediarecordercontrol_ios.mm @@ -44,6 +44,10 @@ #include "avfcameracontrol.h" #include "avfcameraservice.h" #include "avfcameradebug.h" +#include "avfaudioencodersettingscontrol.h" +#include "avfvideoencodersettingscontrol.h" +#include "avfmediacontainercontrol.h" +#include "avfcamerautility.h" #include <QtCore/qdebug.h> @@ -83,6 +87,8 @@ AVFMediaRecorderControlIOS::AVFMediaRecorderControlIOS(AVFCameraService *service , m_service(service) , m_state(QMediaRecorder::StoppedState) , m_lastStatus(QMediaRecorder::UnloadedStatus) + , m_audioSettings(nil) + , m_videoSettings(nil) { Q_ASSERT(service); @@ -113,6 +119,11 @@ AVFMediaRecorderControlIOS::AVFMediaRecorderControlIOS(AVFCameraService *service AVFMediaRecorderControlIOS::~AVFMediaRecorderControlIOS() { [m_writer abort]; + + if (m_audioSettings) + [m_audioSettings release]; + if (m_videoSettings) + [m_videoSettings release]; } QUrl AVFMediaRecorderControlIOS::outputLocation() const @@ -153,6 +164,43 @@ qreal AVFMediaRecorderControlIOS::volume() const void AVFMediaRecorderControlIOS::applySettings() { + AVFCameraSession *session = m_service->session(); + if (!session) + return; + + if (m_state != QMediaRecorder::StoppedState + || (session->state() != QCamera::ActiveState && session->state() != QCamera::LoadedState) + || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureVideo)) { + return; + } + + // 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) @@ -189,7 +237,8 @@ void AVFMediaRecorderControlIOS::setState(QMediaRecorder::State state) const QString path(m_outputLocation.scheme() == QLatin1String("file") ? m_outputLocation.path() : m_outputLocation.toString()); const QUrl fileURL(QUrl::fromLocalFile(m_storageLocation.generateFileName(path, QCamera::CaptureVideo, - QLatin1String("clip_"), QLatin1String("mp4")))); + QLatin1String("clip_"), + m_service->mediaContainerControl()->containerFormat()))); NSURL *nsFileURL = fileURL.toNSURL(); if (!nsFileURL) { @@ -217,7 +266,28 @@ void AVFMediaRecorderControlIOS::setState(QMediaRecorder::State state) // generated, will restart in assetWriterStarted. [session stopRunning]; - if ([m_writer setupWithFileURL:nsFileURL cameraService:m_service]) { + 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; + + // convert to radians + rotation *= M_PI / 180.f; + + if ([m_writer setupWithFileURL:nsFileURL + cameraService:m_service + audioSettings:m_audioSettings + videoSettings:m_videoSettings + transform:CGAffineTransformMakeRotation(rotation)]) { + m_state = QMediaRecorder::RecordingState; m_lastStatus = QMediaRecorder::StartingStatus; @@ -276,6 +346,8 @@ void AVFMediaRecorderControlIOS::assetWriterFinished() else m_lastStatus = QMediaRecorder::UnloadedStatus; + unapplySettings(); + m_service->videoOutput()->resetCaptureDelegate(); [m_service->session()->captureSession() startRunning]; diff --git a/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.h b/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.h new file mode 100644 index 000000000..200da90bd --- /dev/null +++ b/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef AVFVIDEOENCODERSETTINGSCONTROL_H +#define AVFVIDEOENCODERSETTINGSCONTROL_H + +#include <qvideoencodersettingscontrol.h> + +#include "avfcamerautility.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 = 0) const Q_DECL_OVERRIDE; + + QList<qreal> supportedFrameRates(const QVideoEncoderSettings &requestedVideoSettings, + bool *continuous = 0) const Q_DECL_OVERRIDE; + + QStringList supportedVideoCodecs() const Q_DECL_OVERRIDE; + QString videoCodecDescription(const QString &codecName) const Q_DECL_OVERRIDE; + + QVideoEncoderSettings videoSettings() const Q_DECL_OVERRIDE; + void setVideoSettings(const QVideoEncoderSettings &requestedVideoSettings) Q_DECL_OVERRIDE; + + NSDictionary *applySettings(AVCaptureConnection *connection); + void unapplySettings(AVCaptureConnection *connection); + +private: + AVFCameraService *m_service; + + QVideoEncoderSettings m_requestedSettings; + QVideoEncoderSettings m_actualSettings; + + AVCaptureDeviceFormat *m_restoreFormat; + AVFPSRange m_restoreFps; +}; + +QT_END_NAMESPACE + +#endif // AVFVIDEOENCODERSETTINGSCONTROL_H diff --git a/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm b/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm new file mode 100644 index 000000000..248997d57 --- /dev/null +++ b/src/plugins/avfoundation/camera/avfvideoencodersettingscontrol.mm @@ -0,0 +1,395 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "avfvideoencodersettingscontrol.h" + +#include "avfcameraservice.h" +#include "avfcamerautility.h" +#include "avfcamerasession.h" +#include "avfcamerarenderercontrol.h" + +#include <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 + )) + +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) +static bool format_supports_framerate(AVCaptureDeviceFormat *format, qreal fps) +{ + if (format && fps > qreal(0)) { + const qreal epsilon = 0.1; + for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) { + if (range.maxFrameRate - range.minFrameRate < epsilon) { + if (qAbs(fps - range.maxFrameRate) < epsilon) + return true; + } + + if (fps >= range.minFrameRate && fps <= range.maxFrameRate) + return true; + } + } + + return false; +} +#endif + +static bool real_list_contains(const QList<qreal> &list, qreal value) +{ + Q_FOREACH (qreal r, list) { + if (qFuzzyCompare(r, value)) + return true; + } + return false; +} + +AVFVideoEncoderSettingsControl::AVFVideoEncoderSettingsControl(AVFCameraService *service) + : QVideoEncoderSettingsControl() + , m_service(service) + , m_restoreFormat(nil) +{ +} + +QList<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)); + +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_7, QSysInfo::MV_IOS_7_0)) { + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (device) { + int maximumWidth = 0; + const QVector<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)); + } + } +#endif + + if (resolutions.count() == 1) + resolutions.append(QSize(3840, 3840)); + + return resolutions; +} + +QList<qreal> AVFVideoEncoderSettingsControl::supportedFrameRates(const QVideoEncoderSettings &settings, + bool *continuous) const +{ +#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + QList<qreal> uniqueFrameRates; + + if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_7, QSysInfo::MV_IOS_7_0)) { + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (!device) + return uniqueFrameRates; + + if (continuous) + *continuous = false; + + QVector<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; +#else + return QList<qreal>(); +#endif +} + +QStringList AVFVideoEncoderSettingsControl::supportedVideoCodecs() const +{ + return *supportedCodecs; +} + +QString AVFVideoEncoderSettingsControl::videoCodecDescription(const QString &codecName) const +{ + if (codecName == QLatin1String("avc1")) + return QStringLiteral("H.264"); + else if (codecName == QLatin1String("jpeg")) + return QStringLiteral("M-JPEG"); +#ifdef Q_OS_OSX + else if (codecName == QLatin1String("ap4h")) + return QStringLiteral("Apple ProRes 4444"); + else if (codecName == QLatin1String("apcn")) + return QStringLiteral("Apple ProRes 422 Standard Definition"); +#endif + + return QString(); +} + +QVideoEncoderSettings AVFVideoEncoderSettingsControl::videoSettings() const +{ + return m_actualSettings; +} + +void AVFVideoEncoderSettingsControl::setVideoSettings(const QVideoEncoderSettings &settings) +{ + if (m_requestedSettings == settings) + return; + + m_requestedSettings = m_actualSettings = settings; +} + +NSDictionary *AVFVideoEncoderSettingsControl::applySettings(AVCaptureConnection *connection) +{ + if (m_service->session()->state() != QCamera::LoadedState && + m_service->session()->state() != QCamera::ActiveState) { + return nil; + } + + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (!device) + return nil; + + 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 QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + 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); + } + } +#endif + + if (w > 0 && h > 0) { + // Width and height must be divisible by 2 + w += w & 1; + h += h & 1; + + [videoSettings setObject:[NSNumber numberWithInt:w] forKey:AVVideoWidthKey]; + [videoSettings setObject:[NSNumber numberWithInt:h] forKey:AVVideoHeightKey]; + m_actualSettings.setResolution(w, h); + } else { + m_actualSettings.setResolution(qt_device_format_resolution(device.activeFormat)); + } + + // -- FPS + + 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 QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_7_0) + if (m_restoreFormat) { + qt_set_active_format(device, m_restoreFormat, !needFpsChanged); + [m_restoreFormat release]; + m_restoreFormat = nil; + } +#endif + + if (needFpsChanged) { + qt_set_framerate_limits(device, connection, m_restoreFps.first, m_restoreFps.second); + m_restoreFps = AVFPSRange(); + } +} + +QT_END_NAMESPACE + +#include "moc_avfvideoencodersettingscontrol.cpp" diff --git a/src/plugins/avfoundation/camera/camera.pro b/src/plugins/avfoundation/camera/camera.pro index 8563eb655..a17ff5a73 100644 --- a/src/plugins/avfoundation/camera/camera.pro +++ b/src/plugins/avfoundation/camera/camera.pro @@ -37,7 +37,10 @@ HEADERS += \ avfcamerautility.h \ avfcameraviewfindersettingscontrol.h \ avfimageencodercontrol.h \ - avfcameraflashcontrol.h + avfcameraflashcontrol.h \ + avfvideoencodersettingscontrol.h \ + avfmediacontainercontrol.h \ + avfaudioencodersettingscontrol.h OBJECTIVE_SOURCES += \ avfcameraserviceplugin.mm \ @@ -57,7 +60,10 @@ OBJECTIVE_SOURCES += \ avfcamerautility.mm \ avfcameraviewfindersettingscontrol.mm \ avfimageencodercontrol.mm \ - avfcameraflashcontrol.mm + avfcameraflashcontrol.mm \ + avfvideoencodersettingscontrol.mm \ + avfmediacontainercontrol.mm \ + avfaudioencodersettingscontrol.mm osx { |