diff options
author | Liang Qi <liang.qi@qt.io> | 2016-06-29 16:11:26 +0200 |
---|---|---|
committer | Liang Qi <liang.qi@qt.io> | 2016-06-30 07:33:04 +0200 |
commit | 27681cba4695355f2a0a6b01b85c429186d11a34 (patch) | |
tree | f11df2ec52d983b552f2e1b673e0845bc7e3ef05 /src/plugins | |
parent | f7a93757c709e8b2902bc4707752edb8649d009c (diff) | |
parent | bc53bb7913bbf68519508a0ab76c513335b3e5bb (diff) | |
download | qtmultimedia-27681cba4695355f2a0a6b01b85c429186d11a34.tar.gz |
Merge remote-tracking branch 'origin/5.6' into 5.7
Blacklisted a few functions in tst_QAudioInput.
Conflicts:
.qmake.conf
src/plugins/avfoundation/camera/avfcameracontrol.mm
src/plugins/avfoundation/camera/avfcameraservice.h
src/plugins/avfoundation/camera/avfcameraservice.mm
src/plugins/avfoundation/camera/avfcamerasession.h
src/plugins/avfoundation/camera/avfcamerasession.mm
src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.h
src/plugins/avfoundation/camera/avfcameraviewfindersettingscontrol.mm
src/plugins/avfoundation/camera/avfimagecapturecontrol.mm
src/plugins/avfoundation/camera/avfimageencodercontrol.mm
src/plugins/avfoundation/camera/avfmediarecordercontrol.h
src/plugins/avfoundation/camera/avfmediarecordercontrol.mm
tests/auto/integration/qaudioinput/BLACKLIST
Task-number: QTBUG-54459
Task-number: QTBUG-49736
Change-Id: I3a1fe8cef50b44d5c2785aaf4cf69fe3f16728e6
Diffstat (limited to 'src/plugins')
36 files changed, 1692 insertions, 607 deletions
diff --git a/src/plugins/alsa/qalsaaudiodeviceinfo.cpp b/src/plugins/alsa/qalsaaudiodeviceinfo.cpp index 0342ca546..869e1897e 100644 --- a/src/plugins/alsa/qalsaaudiodeviceinfo.cpp +++ b/src/plugins/alsa/qalsaaudiodeviceinfo.cpp @@ -143,35 +143,18 @@ QList<QAudioFormat::SampleType> QAlsaAudioDeviceInfo::supportedSampleTypes() bool QAlsaAudioDeviceInfo::open() { int err = 0; - QString dev = device; - QList<QByteArray> devices = availableDevices(mode); + QString dev; - if(dev.compare(QLatin1String("default")) == 0) { -#if SND_LIB_VERSION >= 0x1000e // 1.0.14 - if (devices.size() > 0) - dev = QLatin1String(devices.first().constData()); - else - return false; -#else - dev = QLatin1String("hw:0,0"); + if (!availableDevices(mode).contains(device.toLocal8Bit())) + return false; + +#if SND_LIB_VERSION < 0x1000e // 1.0.14 + if (device.compare(QLatin1String("default")) != 0) + dev = deviceFromCardName(device); + else #endif - } else { -#if SND_LIB_VERSION >= 0x1000e // 1.0.14 dev = device; -#else - int idx = 0; - char *name; - - QString shortName = device.mid(device.indexOf(QLatin1String("="),0)+1); - while (snd_card_get_name(idx,&name) == 0) { - if(dev.contains(QLatin1String(name))) - break; - idx++; - } - dev = QString(QLatin1String("hw:%1,0")).arg(idx); -#endif - } if(mode == QAudio::AudioOutput) { err=snd_pcm_open( &handle,dev.toLocal8Bit().constData(),SND_PCM_STREAM_PLAYBACK,0); } else { @@ -200,30 +183,12 @@ bool QAlsaAudioDeviceInfo::testSettings(const QAudioFormat& format) const snd_pcm_hw_params_t *params; QString dev; -#if SND_LIB_VERSION >= 0x1000e // 1.0.14 - dev = device; - if (dev.compare(QLatin1String("default")) == 0) { - QList<QByteArray> devices = availableDevices(QAudio::AudioOutput); - if (!devices.isEmpty()) - dev = QLatin1String(devices.first().constData()); - } -#else - if (dev.compare(QLatin1String("default")) == 0) { - dev = QLatin1String("hw:0,0"); - } else { - int idx = 0; - char *name; - - QString shortName = device.mid(device.indexOf(QLatin1String("="),0)+1); - - while(snd_card_get_name(idx,&name) == 0) { - if(shortName.compare(QLatin1String(name)) == 0) - break; - idx++; - } - dev = QString(QLatin1String("hw:%1,0")).arg(idx); - } +#if SND_LIB_VERSION < 0x1000e // 1.0.14 + if (device.compare(QLatin1String("default")) != 0) + dev = deviceFromCardName(device); + else #endif + dev = device; snd_pcm_stream_t stream = mode == QAudio::AudioOutput ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE; @@ -339,9 +304,11 @@ void QAlsaAudioDeviceInfo::updateLists() QList<QByteArray> QAlsaAudioDeviceInfo::availableDevices(QAudio::Mode mode) { QList<QByteArray> devices; - QByteArray filter; + bool hasDefault = false; #if SND_LIB_VERSION >= 0x1000e // 1.0.14 + QByteArray filter; + // Create a list of all current audio devices that support mode void **hints, **n; char *name, *descr, *io; @@ -365,12 +332,9 @@ QList<QByteArray> QAlsaAudioDeviceInfo::availableDevices(QAudio::Mode mode) io = snd_device_name_get_hint(*n, "IOID"); if ((descr != NULL) && ((io == NULL) || (io == filter))) { - QString deviceName = QLatin1String(name); - QString deviceDescription = QLatin1String(descr); - if (deviceDescription.contains(QLatin1String("Default Audio Device"))) - devices.prepend(deviceName.toLocal8Bit().constData()); - else - devices.append(deviceName.toLocal8Bit().constData()); + devices.append(name); + if (strcmp(name, "default") == 0) + hasDefault = true; } free(descr); @@ -386,12 +350,14 @@ QList<QByteArray> QAlsaAudioDeviceInfo::availableDevices(QAudio::Mode mode) while(snd_card_get_name(idx,&name) == 0) { devices.append(name); + if (strcmp(name, "default") == 0) + hasDefault = true; idx++; } #endif - if (devices.size() > 0) - devices.append("default"); + if (!hasDefault && devices.size() > 0) + devices.prepend("default"); return devices; } @@ -454,4 +420,20 @@ void QAlsaAudioDeviceInfo::checkSurround() snd_device_name_free_hint(hints); } +QString QAlsaAudioDeviceInfo::deviceFromCardName(const QString &card) +{ + int idx = 0; + char *name; + + QStringRef shortName = card.midRef(card.indexOf(QLatin1String("="), 0) + 1); + + while (snd_card_get_name(idx, &name) == 0) { + if (shortName.compare(QLatin1String(name)) == 0) + break; + idx++; + } + + return QString(QLatin1String("hw:%1,0")).arg(idx); +} + QT_END_NAMESPACE diff --git a/src/plugins/alsa/qalsaaudiodeviceinfo.h b/src/plugins/alsa/qalsaaudiodeviceinfo.h index 0147a2cf9..97b59ebf3 100644 --- a/src/plugins/alsa/qalsaaudiodeviceinfo.h +++ b/src/plugins/alsa/qalsaaudiodeviceinfo.h @@ -91,6 +91,7 @@ public: static QByteArray defaultInputDevice(); static QByteArray defaultOutputDevice(); static QList<QByteArray> availableDevices(QAudio::Mode); + static QString deviceFromCardName(const QString &card); private: bool open(); diff --git a/src/plugins/alsa/qalsaaudioinput.cpp b/src/plugins/alsa/qalsaaudioinput.cpp index 6ad9a6c5b..8109e6932 100644 --- a/src/plugins/alsa/qalsaaudioinput.cpp +++ b/src/plugins/alsa/qalsaaudioinput.cpp @@ -127,6 +127,12 @@ int QAlsaAudioInput::xrun_recovery(int err) int count = 0; bool reset = false; + // ESTRPIPE is not available in all OSes where ALSA is available + int estrpipe = EIO; +#ifdef ESTRPIPE + estrpipe = ESTRPIPE; +#endif + if(err == -EPIPE) { errorState = QAudio::UnderrunError; err = snd_pcm_prepare(handle); @@ -137,8 +143,7 @@ int QAlsaAudioInput::xrun_recovery(int err) if (bytesAvailable <= 0) reset = true; } - - } else if((err == -ESTRPIPE)||(err == -EIO)) { + } else if ((err == -estrpipe)||(err == -EIO)) { errorState = QAudio::IOError; while((err = snd_pcm_resume(handle)) == -EAGAIN){ usleep(100); @@ -306,34 +311,16 @@ bool QAlsaAudioInput::open() } - QString dev = QString(QLatin1String(m_device.constData())); - QList<QByteArray> devices = QAlsaAudioDeviceInfo::availableDevices(QAudio::AudioInput); - if(dev.compare(QLatin1String("default")) == 0) { -#if SND_LIB_VERSION >= 0x1000e // 1.0.14 - if (devices.size() > 0) - dev = QLatin1String(devices.first()); - else - return false; -#else - dev = QLatin1String("hw:0,0"); -#endif - } else { -#if SND_LIB_VERSION >= 0x1000e // 1.0.14 - dev = QLatin1String(m_device); -#else - int idx = 0; - char *name; - - QString shortName = QLatin1String(m_device.mid(m_device.indexOf('=',0)+1).constData()); + if (!QAlsaAudioDeviceInfo::availableDevices(QAudio::AudioOutput).contains(m_device)) + return false; - while(snd_card_get_name(idx,&name) == 0) { - if(qstrncmp(shortName.toLocal8Bit().constData(),name,shortName.length()) == 0) - break; - idx++; - } - dev = QString(QLatin1String("hw:%1,0")).arg(idx); + QString dev; +#if SND_LIB_VERSION < 0x1000e // 1.0.14 + if (m_device != "default") + dev = QAlsaAudioDeviceInfo::deviceFromCardName(m_device); + else #endif - } + dev = m_device; // Step 1: try and open the device while((count < 5) && (err < 0)) { @@ -565,8 +552,10 @@ qint64 QAlsaAudioInput::read(char* data, qint64 len) if(readFrames == -EPIPE) { errorState = QAudio::UnderrunError; err = snd_pcm_prepare(handle); +#ifdef ESTRPIPE } else if(readFrames == -ESTRPIPE) { err = snd_pcm_prepare(handle); +#endif } if(err != 0) break; } diff --git a/src/plugins/alsa/qalsaaudiooutput.cpp b/src/plugins/alsa/qalsaaudiooutput.cpp index d59e2b740..5e444a0a9 100644 --- a/src/plugins/alsa/qalsaaudiooutput.cpp +++ b/src/plugins/alsa/qalsaaudiooutput.cpp @@ -120,6 +120,12 @@ int QAlsaAudioOutput::xrun_recovery(int err) int count = 0; bool reset = false; + // ESTRPIPE is not available in all OSes where ALSA is available + int estrpipe = EIO; +#ifdef ESTRPIPE + estrpipe = ESTRPIPE; +#endif + if(err == -EPIPE) { errorState = QAudio::UnderrunError; emit errorChanged(errorState); @@ -127,7 +133,7 @@ int QAlsaAudioOutput::xrun_recovery(int err) if(err < 0) reset = true; - } else if((err == -ESTRPIPE)||(err == -EIO)) { + } else if ((err == -estrpipe)||(err == -EIO)) { errorState = QAudio::IOError; emit errorChanged(errorState); while((err = snd_pcm_resume(handle)) == -EAGAIN){ @@ -309,34 +315,16 @@ bool QAlsaAudioOutput::open() return false; } - QString dev = QString(QLatin1String(m_device.constData())); - QList<QByteArray> devices = QAlsaAudioDeviceInfo::availableDevices(QAudio::AudioOutput); - if(dev.compare(QLatin1String("default")) == 0) { -#if SND_LIB_VERSION >= 0x1000e // 1.0.14 - if (devices.size() > 0) - dev = QLatin1String(devices.first()); - else - return false; -#else - dev = QLatin1String("hw:0,0"); -#endif - } else { -#if SND_LIB_VERSION >= 0x1000e // 1.0.14 - dev = QLatin1String(m_device); -#else - int idx = 0; - char *name; - - QString shortName = QLatin1String(m_device.mid(m_device.indexOf('=',0)+1).constData()); + if (!QAlsaAudioDeviceInfo::availableDevices(QAudio::AudioOutput).contains(m_device)) + return false; - while (snd_card_get_name(idx,&name) == 0) { - if(qstrncmp(shortName.toLocal8Bit().constData(),name,shortName.length()) == 0) - break; - idx++; - } - dev = QString(QLatin1String("hw:%1,0")).arg(idx); + QString dev; +#if SND_LIB_VERSION < 0x1000e // 1.0.14 + if (m_device != "default") + dev = QAlsaAudioDeviceInfo::deviceFromCardName(m_device); + else #endif - } + dev = m_device; // Step 1: try and open the device while((count < 5) && (err < 0)) { diff --git a/src/plugins/android/src/common/qandroidvideooutput.cpp b/src/plugins/android/src/common/qandroidvideooutput.cpp index 4e96377d8..5c804ccc4 100644 --- a/src/plugins/android/src/common/qandroidvideooutput.cpp +++ b/src/plugins/android/src/common/qandroidvideooutput.cpp @@ -290,6 +290,10 @@ void QAndroidTextureVideoOutput::stop() void QAndroidTextureVideoOutput::reset() { + // flush pending frame + if (m_surface) + m_surface->present(QVideoFrame()); + clearSurfaceTexture(); } diff --git a/src/plugins/android/src/wrappers/jni/androidcamera.cpp b/src/plugins/android/src/wrappers/jni/androidcamera.cpp index fd5522e10..3295e4d33 100644 --- a/src/plugins/android/src/wrappers/jni/androidcamera.cpp +++ b/src/plugins/android/src/wrappers/jni/androidcamera.cpp @@ -315,7 +315,6 @@ AndroidCamera *AndroidCamera::open(int cameraId) if (!ok) { worker->quit(); worker->wait(5000); - delete d; delete worker; return 0; } 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 924b0d76a..6ac6325b4 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) @@ -484,8 +296,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. @@ -559,7 +372,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) { @@ -573,17 +386,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. @@ -593,7 +398,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 @@ -629,7 +434,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 { diff --git a/src/plugins/directshow/player/directshowiosource.cpp b/src/plugins/directshow/player/directshowiosource.cpp index 6dee14fb0..bb4d0f00d 100644 --- a/src/plugins/directshow/player/directshowiosource.cpp +++ b/src/plugins/directshow/player/directshowiosource.cpp @@ -46,13 +46,6 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/qurl.h> -static const GUID directshow_subtypes[] = -{ - MEDIASUBTYPE_Avi, - MEDIASUBTYPE_WAVE, - MEDIASUBTYPE_NULL -}; - DirectShowIOSource::DirectShowIOSource(DirectShowEventLoop *loop) : m_ref(1) , m_state(State_Stopped) @@ -63,30 +56,21 @@ DirectShowIOSource::DirectShowIOSource(DirectShowEventLoop *loop) , m_allocator(0) , m_peerPin(0) , m_pinId(QLatin1String("Data")) + , m_queriedForAsyncReader(false) { - QVector<AM_MEDIA_TYPE> mediaTypes; - - AM_MEDIA_TYPE type = - { - MEDIATYPE_Stream, // majortype - MEDIASUBTYPE_NULL, // subtype - TRUE, // bFixedSizeSamples - FALSE, // bTemporalCompression - 1, // lSampleSize - GUID_NULL, // formattype - 0, // pUnk - 0, // cbFormat - 0, // pbFormat - }; - - static const int count = sizeof(directshow_subtypes) / sizeof(GUID); - - for (int i = 0; i < count; ++i) { - type.subtype = directshow_subtypes[i]; - mediaTypes.append(type); - } + // This filter has only one possible output type, that is, a stream of data + // with no particular subtype. The graph builder will try every demux/decode filters + // to find one able to decode the stream. + // + // The filter works in pull mode, the downstream filter is responsible for requesting + // samples from this one. + + m_outputType.majortype = MEDIATYPE_Stream; + m_outputType.subtype = MEDIASUBTYPE_NULL; // Wildcard + m_outputType.bFixedSizeSamples = TRUE; + m_outputType.lSampleSize = 1; - setMediaTypes(mediaTypes); + setMediaTypes(QVector<AM_MEDIA_TYPE>() << m_outputType); } DirectShowIOSource::~DirectShowIOSource() @@ -136,6 +120,7 @@ HRESULT DirectShowIOSource::QueryInterface(REFIID riid, void **ppvObject) } else if (riid == IID_IPin) { *ppvObject = static_cast<IPin *>(this); } else if (riid == IID_IAsyncReader) { + m_queriedForAsyncReader = true; *ppvObject = static_cast<IAsyncReader *>(m_reader); } else { *ppvObject = 0; @@ -330,116 +315,40 @@ ULONG DirectShowIOSource::GetMiscFlags() // IPin HRESULT DirectShowIOSource::Connect(IPin *pReceivePin, const AM_MEDIA_TYPE *pmt) { - QMutexLocker locker(&m_mutex); - if (!pReceivePin) { + if (!pReceivePin) return E_POINTER; - } else if (m_state != State_Stopped) { - return VFW_E_NOT_STOPPED; - } else if (m_peerPin) { - return VFW_E_ALREADY_CONNECTED; - } else { - HRESULT hr = VFW_E_TYPE_NOT_ACCEPTED; - - m_peerPin = pReceivePin; - m_peerPin->AddRef(); - if (!pmt) { - IEnumMediaTypes *mediaTypes = 0; - if (pReceivePin->EnumMediaTypes(&mediaTypes) == S_OK) { - for (AM_MEDIA_TYPE *type = 0; - mediaTypes->Next(1, &type, 0) == S_OK; - DirectShowMediaType::deleteType(type)) { - switch (tryConnect(pReceivePin, type)) { - case S_OK: - DirectShowMediaType::freeData(type); - mediaTypes->Release(); - return S_OK; - case VFW_E_NO_TRANSPORT: - hr = VFW_E_NO_TRANSPORT; - break; - default: - break; - } - } - mediaTypes->Release(); - } - AM_MEDIA_TYPE type = - { - MEDIATYPE_Stream, // majortype - MEDIASUBTYPE_NULL, // subtype - TRUE, // bFixedSizeSamples - FALSE, // bTemporalCompression - 1, // lSampleSize - GUID_NULL, // formattype - 0, // pUnk - 0, // cbFormat - 0, // pbFormat - }; - - static const int count = sizeof(directshow_subtypes) / sizeof(GUID); - - for (int i = 0; i < count; ++i) { - type.subtype = directshow_subtypes[i]; - - switch (tryConnect(pReceivePin, &type)) { - case S_OK: - return S_OK; - case VFW_E_NO_TRANSPORT: - hr = VFW_E_NO_TRANSPORT; - break; - default: - break; - } - } - } else if (pmt->majortype == MEDIATYPE_Stream && (hr = tryConnect(pReceivePin, pmt))) { - return S_OK; - } - - m_peerPin->Release(); - m_peerPin = 0; + QMutexLocker locker(&m_mutex); - m_mediaType.clear(); + if (m_state != State_Stopped) + return VFW_E_NOT_STOPPED; - return hr; - } -} + if (m_peerPin) + return VFW_E_ALREADY_CONNECTED; -HRESULT DirectShowIOSource::tryConnect(IPin *pin, const AM_MEDIA_TYPE *type) -{ - m_mediaType = *type; + // If we get a type from the graph manager, check that we support that + if (pmt && (pmt->majortype != MEDIATYPE_Stream || pmt->subtype != MEDIASUBTYPE_NULL)) + return VFW_E_TYPE_NOT_ACCEPTED; - HRESULT hr = pin->ReceiveConnection(this, type); + // This filter only works in pull mode, the downstream filter must query for the + // AsyncReader interface during ReceiveConnection(). + // If it doesn't, we can't connect to it. + m_queriedForAsyncReader = false; + HRESULT hr = pReceivePin->ReceiveConnection(this, pmt ? pmt : &m_outputType); - if (!SUCCEEDED(hr)) { + if (SUCCEEDED(hr) && m_queriedForAsyncReader) { + m_peerPin = pReceivePin; + m_peerPin->AddRef(); + } else { + pReceivePin->Disconnect(); if (m_allocator) { m_allocator->Release(); m_allocator = 0; } - } else if (!m_allocator) { - hr = VFW_E_NO_TRANSPORT; - - if (IMemInputPin *memPin = com_cast<IMemInputPin>(pin, IID_IMemInputPin)) { - if ((m_allocator = com_new<IMemAllocator>(CLSID_MemoryAllocator))) { - ALLOCATOR_PROPERTIES properties; - if (memPin->GetAllocatorRequirements(&properties) == S_OK - || m_allocator->GetProperties(&properties) == S_OK) { - if (properties.cbAlign == 0) - properties.cbAlign = 1; - - ALLOCATOR_PROPERTIES actualProperties; - if (SUCCEEDED(hr = m_allocator->SetProperties(&properties, &actualProperties))) - hr = memPin->NotifyAllocator(m_allocator, TRUE); - } - if (!SUCCEEDED(hr)) { - m_allocator->Release(); - m_allocator = 0; - } - } - memPin->Release(); - } - if (!SUCCEEDED(hr)) - pin->Disconnect(); + if (!m_queriedForAsyncReader) + hr = VFW_E_NO_TRANSPORT; } + return hr; } @@ -453,6 +362,8 @@ HRESULT DirectShowIOSource::ReceiveConnection(IPin *pConnector, const AM_MEDIA_T HRESULT DirectShowIOSource::Disconnect() { + QMutexLocker locker(&m_mutex); + if (!m_peerPin) { return S_FALSE; } else if (m_state != State_Stopped) { @@ -471,8 +382,6 @@ HRESULT DirectShowIOSource::Disconnect() m_peerPin->Release(); m_peerPin = 0; - m_mediaType.clear(); - return S_OK; } } @@ -510,7 +419,7 @@ HRESULT DirectShowIOSource::ConnectionMediaType(AM_MEDIA_TYPE *pmt) return VFW_E_NOT_CONNECTED; } else { - DirectShowMediaType::copy(pmt, m_mediaType); + DirectShowMediaType::copy(pmt, m_outputType); return S_OK; } diff --git a/src/plugins/directshow/player/directshowiosource.h b/src/plugins/directshow/player/directshowiosource.h index bb90ba398..225cfc1ab 100644 --- a/src/plugins/directshow/player/directshowiosource.h +++ b/src/plugins/directshow/player/directshowiosource.h @@ -117,8 +117,6 @@ public: HRESULT STDMETHODCALLTYPE QueryDirection(PIN_DIRECTION *pPinDir); private: - HRESULT tryConnect(IPin *pin, const AM_MEDIA_TYPE *type); - volatile LONG m_ref; FILTER_STATE m_state; DirectShowIOReader *m_reader; @@ -127,9 +125,10 @@ private: IReferenceClock *m_clock; IMemAllocator *m_allocator; IPin *m_peerPin; - DirectShowMediaType m_mediaType; + DirectShowMediaType m_outputType; QString m_filterName; const QString m_pinId; + bool m_queriedForAsyncReader; QMutex m_mutex; }; diff --git a/src/plugins/pulseaudio/qpulseaudioengine.cpp b/src/plugins/pulseaudio/qpulseaudioengine.cpp index 19ba7472f..286f310bc 100644 --- a/src/plugins/pulseaudio/qpulseaudioengine.cpp +++ b/src/plugins/pulseaudio/qpulseaudioengine.cpp @@ -81,8 +81,10 @@ static void serverInfoCallback(pa_context *context, const pa_server_info *info, #endif QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata); + pulseEngine->m_serverLock.lockForWrite(); pulseEngine->m_defaultSink = info->default_sink_name; pulseEngine->m_defaultSource = info->default_source_name; + pulseEngine->m_serverLock.unlock(); pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0); } @@ -90,11 +92,6 @@ static void serverInfoCallback(pa_context *context, const pa_server_info *info, static void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int isLast, void *userdata) { QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata); - QMap<pa_sink_state, QString> stateMap; - stateMap[PA_SINK_INVALID_STATE] = "n/a"; - stateMap[PA_SINK_RUNNING] = "RUNNING"; - stateMap[PA_SINK_IDLE] = "IDLE"; - stateMap[PA_SINK_SUSPENDED] = "SUSPENDED"; if (isLast < 0) { qWarning() << QString("Failed to get sink information: %s").arg(pa_strerror(pa_context_errno(context))); @@ -109,6 +106,12 @@ static void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int Q_ASSERT(info); #ifdef DEBUG_PULSE + QMap<pa_sink_state, QString> stateMap; + stateMap[PA_SINK_INVALID_STATE] = "n/a"; + stateMap[PA_SINK_RUNNING] = "RUNNING"; + stateMap[PA_SINK_IDLE] = "IDLE"; + stateMap[PA_SINK_SUSPENDED] = "SUSPENDED"; + qDebug() << QString("Sink #%1\n" "\tState: %2\n" "\tName: %3\n" @@ -120,8 +123,10 @@ static void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int #endif QAudioFormat format = QPulseAudioInternal::sampleSpecToAudioFormat(info->sample_spec); + + QWriteLocker locker(&pulseEngine->m_sinkLock); pulseEngine->m_preferredFormats.insert(info->name, format); - pulseEngine->m_sinks.append(info->name); + pulseEngine->m_sinks.insert(info->index, info->name); } static void sourceInfoCallback(pa_context *context, const pa_source_info *info, int isLast, void *userdata) @@ -129,12 +134,6 @@ static void sourceInfoCallback(pa_context *context, const pa_source_info *info, Q_UNUSED(context) QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata); - QMap<pa_source_state, QString> stateMap; - stateMap[PA_SOURCE_INVALID_STATE] = "n/a"; - stateMap[PA_SOURCE_RUNNING] = "RUNNING"; - stateMap[PA_SOURCE_IDLE] = "IDLE"; - stateMap[PA_SOURCE_SUSPENDED] = "SUSPENDED"; - if (isLast) { pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0); return; @@ -143,6 +142,12 @@ static void sourceInfoCallback(pa_context *context, const pa_source_info *info, Q_ASSERT(info); #ifdef DEBUG_PULSE + QMap<pa_source_state, QString> stateMap; + stateMap[PA_SOURCE_INVALID_STATE] = "n/a"; + stateMap[PA_SOURCE_RUNNING] = "RUNNING"; + stateMap[PA_SOURCE_IDLE] = "IDLE"; + stateMap[PA_SOURCE_SUSPENDED] = "SUSPENDED"; + qDebug() << QString("Source #%1\n" "\tState: %2\n" "\tName: %3\n" @@ -154,8 +159,57 @@ static void sourceInfoCallback(pa_context *context, const pa_source_info *info, #endif QAudioFormat format = QPulseAudioInternal::sampleSpecToAudioFormat(info->sample_spec); + + QWriteLocker locker(&pulseEngine->m_sourceLock); pulseEngine->m_preferredFormats.insert(info->name, format); - pulseEngine->m_sources.append(info->name); + pulseEngine->m_sources.insert(info->index, info->name); +} + +static void event_cb(pa_context* context, pa_subscription_event_type_t t, uint32_t index, void* userdata) +{ + QPulseAudioEngine *pulseEngine = static_cast<QPulseAudioEngine*>(userdata); + + int type = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK; + int facility = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; + + switch (type) { + case PA_SUBSCRIPTION_EVENT_NEW: + case PA_SUBSCRIPTION_EVENT_CHANGE: + switch (facility) { + case PA_SUBSCRIPTION_EVENT_SERVER: + pa_operation_unref(pa_context_get_server_info(context, serverInfoCallback, userdata)); + break; + case PA_SUBSCRIPTION_EVENT_SINK: + pa_operation_unref(pa_context_get_sink_info_by_index(context, index, sinkInfoCallback, userdata)); + break; + case PA_SUBSCRIPTION_EVENT_SOURCE: + pa_operation_unref(pa_context_get_source_info_by_index(context, index, sourceInfoCallback, userdata)); + break; + default: + break; + } + break; + case PA_SUBSCRIPTION_EVENT_REMOVE: + switch (facility) { + case PA_SUBSCRIPTION_EVENT_SINK: + pulseEngine->m_sinkLock.lockForWrite(); + pulseEngine->m_preferredFormats.remove(pulseEngine->m_sinks.value(index)); + pulseEngine->m_sinks.remove(index); + pulseEngine->m_sinkLock.unlock(); + break; + case PA_SUBSCRIPTION_EVENT_SOURCE: + pulseEngine->m_sourceLock.lockForWrite(); + pulseEngine->m_preferredFormats.remove(pulseEngine->m_sources.value(index)); + pulseEngine->m_sources.remove(index); + pulseEngine->m_sourceLock.unlock(); + break; + default: + break; + } + break; + default: + break; + } } static void contextStateCallbackInit(pa_context *context, void *userdata) @@ -278,6 +332,13 @@ void QPulseAudioEngine::prepare() if (ok) { pa_context_set_state_callback(m_context, contextStateCallback, this); + + pa_context_set_subscribe_callback(m_context, event_cb, this); + pa_operation_unref(pa_context_subscribe(m_context, + pa_subscription_mask_t(PA_SUBSCRIPTION_MASK_SINK | + PA_SUBSCRIPTION_MASK_SOURCE | + PA_SUBSCRIPTION_MASK_SERVER), + NULL, NULL)); } else { pa_context_unref(m_context); m_context = 0; @@ -338,14 +399,6 @@ void QPulseAudioEngine::updateDevices() pa_operation_unref(operation); unlock(); - - // Swap the default output to index 0 - m_sinks.removeOne(m_defaultSink); - m_sinks.prepend(m_defaultSink); - - // Swap the default input to index 0 - m_sources.removeOne(m_defaultSource); - m_sources.prepend(m_defaultSource); } void QPulseAudioEngine::onContextFailed() @@ -366,7 +419,28 @@ QPulseAudioEngine *QPulseAudioEngine::instance() QList<QByteArray> QPulseAudioEngine::availableDevices(QAudio::Mode mode) const { - return mode == QAudio::AudioOutput ? m_sinks : m_sources; + QList<QByteArray> devices; + QByteArray defaultDevice; + + m_serverLock.lockForRead(); + + if (mode == QAudio::AudioOutput) { + QReadLocker locker(&m_sinkLock); + devices = m_sinks.values(); + defaultDevice = m_defaultSink; + } else { + QReadLocker locker(&m_sourceLock); + devices = m_sources.values(); + defaultDevice = m_defaultSource; + } + + m_serverLock.unlock(); + + // Swap the default device to index 0 + devices.removeOne(defaultDevice); + devices.prepend(defaultDevice); + + return devices; } QT_END_NAMESPACE diff --git a/src/plugins/pulseaudio/qpulseaudioengine.h b/src/plugins/pulseaudio/qpulseaudioengine.h index 5eb96bf00..f03dbfd16 100644 --- a/src/plugins/pulseaudio/qpulseaudioengine.h +++ b/src/plugins/pulseaudio/qpulseaudioengine.h @@ -53,6 +53,7 @@ #include <QtCore/qmap.h> #include <QtCore/qbytearray.h> +#include <QtCore/qreadwritelock.h> #include <qaudiosystemplugin.h> #include <pulse/pulseaudio.h> #include "qpulsehelpers.h" @@ -104,13 +105,17 @@ private: void release(); public: - QList<QByteArray> m_sinks; - QList<QByteArray> m_sources; + QMap<int, QByteArray> m_sinks; + QMap<int, QByteArray> m_sources; QMap<QByteArray, QAudioFormat> m_preferredFormats; QByteArray m_defaultSink; QByteArray m_defaultSource; + mutable QReadWriteLock m_sinkLock; + mutable QReadWriteLock m_sourceLock; + mutable QReadWriteLock m_serverLock; + private: pa_mainloop_api *m_mainLoopApi; pa_threaded_mainloop *m_mainLoop; diff --git a/src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp b/src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp index 7dd2e6557..f109dc4cb 100644 --- a/src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp +++ b/src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp @@ -38,6 +38,7 @@ #include <QtCore/qfunctions_winrt.h> #include <QtCore/QGlobalStatic> +#include <QtCore/QLoggingCategory> #include <QtCore/QMetaMethod> #include <QtCore/QPointer> #include <QtGui/QOpenGLContext> @@ -58,6 +59,8 @@ using namespace Microsoft::WRL; QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcMMVideoRender, "qt.mm.videorender") + #define BREAK_IF_FAILED(msg) RETURN_IF_FAILED(msg, break) #define CONTINUE_IF_FAILED(msg) RETURN_IF_FAILED(msg, continue) @@ -66,6 +69,7 @@ struct QWinRTVideoRendererControlGlobal { QWinRTVideoRendererControlGlobal() { + qCDebug(lcMMVideoRender) << __FUNCTION__; HRESULT hr; D3D_FEATURE_LEVEL featureLevels[] = @@ -202,6 +206,7 @@ ID3D11Device *QWinRTAbstractVideoRendererControl::d3dDevice() // This is required so that subclasses can stop the render thread before deletion void QWinRTAbstractVideoRendererControl::shutdown() { + qCDebug(lcMMVideoRender) << __FUNCTION__; Q_D(QWinRTAbstractVideoRendererControl); if (d->renderThread.isRunning()) { d->renderThread.requestInterruption(); @@ -212,6 +217,7 @@ void QWinRTAbstractVideoRendererControl::shutdown() QWinRTAbstractVideoRendererControl::QWinRTAbstractVideoRendererControl(const QSize &size, QObject *parent) : QVideoRendererControl(parent), d_ptr(new QWinRTAbstractVideoRendererControlPrivate) { + qCDebug(lcMMVideoRender) << __FUNCTION__; Q_D(QWinRTAbstractVideoRendererControl); d->format = QVideoSurfaceFormat(size, QVideoFrame::Format_BGRA32, @@ -232,6 +238,7 @@ QWinRTAbstractVideoRendererControl::QWinRTAbstractVideoRendererControl(const QSi QWinRTAbstractVideoRendererControl::~QWinRTAbstractVideoRendererControl() { + qCDebug(lcMMVideoRender) << __FUNCTION__; Q_D(QWinRTAbstractVideoRendererControl); CriticalSectionLocker locker(&d->mutex); shutdown(); @@ -253,6 +260,7 @@ void QWinRTAbstractVideoRendererControl::setSurface(QAbstractVideoSurface *surfa void QWinRTAbstractVideoRendererControl::syncAndRender() { + qCDebug(lcMMVideoRender) << __FUNCTION__; Q_D(QWinRTAbstractVideoRendererControl); QThread *currentThread = QThread::currentThread(); @@ -334,6 +342,7 @@ void QWinRTAbstractVideoRendererControl::setScanLineDirection(QVideoSurfaceForma void QWinRTAbstractVideoRendererControl::setActive(bool active) { + qCDebug(lcMMVideoRender) << __FUNCTION__ << active; Q_D(QWinRTAbstractVideoRendererControl); if (d->active == active) @@ -351,7 +360,7 @@ void QWinRTAbstractVideoRendererControl::setActive(bool active) return; } - d->renderThread.requestInterruption(); + shutdown(); if (d->surface && d->surface->isActive()) d->surface->stop(); } diff --git a/src/plugins/winrt/qwinrtcameracontrol.cpp b/src/plugins/winrt/qwinrtcameracontrol.cpp index 856549438..390364eb8 100644 --- a/src/plugins/winrt/qwinrtcameracontrol.cpp +++ b/src/plugins/winrt/qwinrtcameracontrol.cpp @@ -1124,46 +1124,54 @@ bool QWinRTCameraControl::setFocus(QCameraFocus::FocusModes modes) if (d->status == QCamera::UnloadedStatus) return false; - ComPtr<IFocusSettings> focusSettings; - ComPtr<IInspectable> focusSettingsObject; - HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Media_Devices_FocusSettings).Get(), &focusSettingsObject); - Q_ASSERT_SUCCEEDED(hr); - hr = focusSettingsObject.As(&focusSettings); - Q_ASSERT_SUCCEEDED(hr); - FocusMode mode; - if (modes.testFlag(QCameraFocus::ContinuousFocus)) { - mode = FocusMode_Continuous; - } else if (modes.testFlag(QCameraFocus::AutoFocus) - || modes.testFlag(QCameraFocus::MacroFocus) - || modes.testFlag(QCameraFocus::InfinityFocus)) { - // The Macro and infinity focus modes are only supported in auto focus mode on WinRT. - // QML camera focus doesn't support combined focus flags settings. In the case of macro - // and infinity Focus modes, the auto focus setting is applied. - mode = FocusMode_Single; - } else { - emit error(QCamera::NotSupportedFeatureError, QStringLiteral("Unsupported camera focus modes.")); - return false; - } - hr = focusSettings->put_Mode(mode); - Q_ASSERT_SUCCEEDED(hr); - AutoFocusRange range = AutoFocusRange_Normal; - if (modes.testFlag(QCameraFocus::MacroFocus)) - range = AutoFocusRange_Macro; - else if (modes.testFlag(QCameraFocus::InfinityFocus)) - range = AutoFocusRange_FullRange; - hr = focusSettings->put_AutoFocusRange(range); - Q_ASSERT_SUCCEEDED(hr); - hr = focusSettings->put_WaitForFocus(true); - Q_ASSERT_SUCCEEDED(hr); - hr = focusSettings->put_DisableDriverFallback(false); - Q_ASSERT_SUCCEEDED(hr); + bool result = false; + HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([modes, &result, d, this]() { + ComPtr<IFocusSettings> focusSettings; + ComPtr<IInspectable> focusSettingsObject; + HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Media_Devices_FocusSettings).Get(), &focusSettingsObject); + Q_ASSERT_SUCCEEDED(hr); + hr = focusSettingsObject.As(&focusSettings); + Q_ASSERT_SUCCEEDED(hr); + FocusMode mode; + if (modes.testFlag(QCameraFocus::ContinuousFocus)) { + mode = FocusMode_Continuous; + } else if (modes.testFlag(QCameraFocus::AutoFocus) + || modes.testFlag(QCameraFocus::MacroFocus) + || modes.testFlag(QCameraFocus::InfinityFocus)) { + // The Macro and infinity focus modes are only supported in auto focus mode on WinRT. + // QML camera focus doesn't support combined focus flags settings. In the case of macro + // and infinity Focus modes, the auto focus setting is applied. + mode = FocusMode_Single; + } else { + emit error(QCamera::NotSupportedFeatureError, QStringLiteral("Unsupported camera focus modes.")); + result = false; + return S_OK; + } + hr = focusSettings->put_Mode(mode); + Q_ASSERT_SUCCEEDED(hr); + AutoFocusRange range = AutoFocusRange_Normal; + if (modes.testFlag(QCameraFocus::MacroFocus)) + range = AutoFocusRange_Macro; + else if (modes.testFlag(QCameraFocus::InfinityFocus)) + range = AutoFocusRange_FullRange; + hr = focusSettings->put_AutoFocusRange(range); + Q_ASSERT_SUCCEEDED(hr); + hr = focusSettings->put_WaitForFocus(true); + Q_ASSERT_SUCCEEDED(hr); + hr = focusSettings->put_DisableDriverFallback(false); + Q_ASSERT_SUCCEEDED(hr); - ComPtr<IFocusControl2> focusControl2; - hr = d->focusControl.As(&focusControl2); + ComPtr<IFocusControl2> focusControl2; + hr = d->focusControl.As(&focusControl2); + Q_ASSERT_SUCCEEDED(hr); + hr = focusControl2->Configure(focusSettings.Get()); + result = SUCCEEDED(hr); + RETURN_OK_IF_FAILED("Failed to configure camera focus control"); + return S_OK; + }); Q_ASSERT_SUCCEEDED(hr); - hr = focusControl2->Configure(focusSettings.Get()); - RETURN_FALSE_IF_FAILED("Failed to configure camera focus control"); - return true; + Q_UNUSED(hr); // Silence release build + return result; } bool QWinRTCameraControl::setFocusPoint(const QPointF &focusPoint) @@ -1227,7 +1235,11 @@ bool QWinRTCameraControl::focus() if (!d->focusControl || status == AsyncStatus::Started) return false; - hr = d->focusControl->FocusAsync(&d->focusOperation); + QEventDispatcherWinRT::runOnXamlThread([&d, &hr]() { + hr = d->focusControl->FocusAsync(&d->focusOperation); + Q_ASSERT_SUCCEEDED(hr); + return S_OK; + }); const long errorCode = HRESULT_CODE(hr); if (errorCode == ERROR_OPERATION_IN_PROGRESS || errorCode == ERROR_WRITE_PROTECT) { |