/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "avfvideoencodersettingscontrol.h" #include "avfcameraservice.h" #include "avfcamerautility.h" #include "avfcamerasession.h" #include "avfcamerarenderercontrol.h" #include QT_BEGIN_NAMESPACE Q_GLOBAL_STATIC_WITH_ARGS(QStringList, supportedCodecs, (QStringList() << QLatin1String("avc1") << QLatin1String("jpeg") #ifdef Q_OS_OSX << QLatin1String("ap4h") << QLatin1String("apcn") #endif )) static bool format_supports_framerate(AVCaptureDeviceFormat *format, qreal fps) { if (format && fps > qreal(0)) { const qreal epsilon = 0.1; for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) { if (range.maxFrameRate - range.minFrameRate < epsilon) { if (qAbs(fps - range.maxFrameRate) < epsilon) return true; } if (fps >= range.minFrameRate && fps <= range.maxFrameRate) return true; } } return false; } static bool real_list_contains(const QList &list, qreal value) { for (qreal r : list) { if (qFuzzyCompare(r, value)) return true; } return false; } AVFVideoEncoderSettingsControl::AVFVideoEncoderSettingsControl(AVFCameraService *service) : QVideoEncoderSettingsControl() , m_service(service) , m_restoreFormat(nil) { } QList 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 resolutions; resolutions.append(QSize(32, 32)); AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); if (device) { int maximumWidth = 0; const QVector formats(qt_unique_device_formats(device, m_service->session()->defaultCodec())); for (int i = 0; i < formats.size(); ++i) { const QSize res(qt_device_format_resolution(formats[i])); if (res.width() > maximumWidth) maximumWidth = res.width(); } if (maximumWidth > 0) resolutions.append(QSize(maximumWidth, maximumWidth)); } if (resolutions.count() == 1) resolutions.append(QSize(3840, 3840)); return resolutions; } QList AVFVideoEncoderSettingsControl::supportedFrameRates(const QVideoEncoderSettings &settings, bool *continuous) const { QList uniqueFrameRates; AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); if (!device) return uniqueFrameRates; if (continuous) *continuous = false; QVector allRates; if (!settings.resolution().isValid()) { const QVector formats(qt_unique_device_formats(device, 0)); for (int i = 0; i < formats.size(); ++i) { AVCaptureDeviceFormat *format = formats.at(i); allRates += qt_device_format_framerates(format); } } else { AVCaptureDeviceFormat *format = qt_find_best_resolution_match(device, settings.resolution(), m_service->session()->defaultCodec()); if (format) allRates = qt_device_format_framerates(format); } for (int j = 0; j < allRates.size(); ++j) { if (!real_list_contains(uniqueFrameRates, allRates[j].first)) uniqueFrameRates.append(allRates[j].first); if (!real_list_contains(uniqueFrameRates, allRates[j].second)) uniqueFrameRates.append(allRates[j].second); } return uniqueFrameRates; } QStringList AVFVideoEncoderSettingsControl::supportedVideoCodecs() const { return *supportedCodecs; } QString AVFVideoEncoderSettingsControl::videoCodecDescription(const QString &codecName) const { if (codecName == QLatin1String("avc1")) return QStringLiteral("H.264"); else if (codecName == QLatin1String("jpeg")) return QStringLiteral("M-JPEG"); #ifdef Q_OS_OSX else if (codecName == QLatin1String("ap4h")) return QStringLiteral("Apple ProRes 4444"); else if (codecName == QLatin1String("apcn")) return QStringLiteral("Apple ProRes 422 Standard Definition"); #endif return QString(); } QVideoEncoderSettings AVFVideoEncoderSettingsControl::videoSettings() const { return m_actualSettings; } void AVFVideoEncoderSettingsControl::setVideoSettings(const QVideoEncoderSettings &settings) { if (m_requestedSettings == settings) return; m_requestedSettings = m_actualSettings = settings; } NSDictionary *AVFVideoEncoderSettingsControl::applySettings(AVCaptureConnection *connection) { if (m_service->session()->state() != QCamera::LoadedState && m_service->session()->state() != QCamera::ActiveState) { return nil; } AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); if (!device) return nil; AVFPSRange currentFps = qt_current_framerates(device, connection); const bool needFpsChange = m_requestedSettings.frameRate() > 0 && m_requestedSettings.frameRate() != currentFps.second; NSMutableDictionary *videoSettings = [NSMutableDictionary dictionary]; // -- Codec // AVVideoCodecKey is the only mandatory key QString codec = m_requestedSettings.codec().isEmpty() ? supportedCodecs->first() : m_requestedSettings.codec(); if (!supportedCodecs->contains(codec)) { qWarning("Unsupported codec: '%s'", codec.toLocal8Bit().constData()); codec = supportedCodecs->first(); } [videoSettings setObject:codec.toNSString() forKey:AVVideoCodecKey]; m_actualSettings.setCodec(codec); // -- Resolution int w = m_requestedSettings.resolution().width(); int h = m_requestedSettings.resolution().height(); if (AVCaptureDeviceFormat *currentFormat = device.activeFormat) { CMFormatDescriptionRef formatDesc = currentFormat.formatDescription; CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc); // We have to change the device's activeFormat in 3 cases: // - the requested recording resolution is higher than the current device resolution // - the requested recording resolution has a different aspect ratio than the current device aspect ratio // - the requested frame rate is not available for the current device format AVCaptureDeviceFormat *newFormat = nil; if ((w <= 0 || h <= 0) && m_requestedSettings.frameRate() > 0 && !format_supports_framerate(currentFormat, m_requestedSettings.frameRate())) { newFormat = qt_find_best_framerate_match(device, m_service->session()->defaultCodec(), m_requestedSettings.frameRate()); } else if (w > 0 && h > 0) { AVCaptureDeviceFormat *f = qt_find_best_resolution_match(device, m_requestedSettings.resolution(), m_service->session()->defaultCodec()); if (f) { CMVideoDimensions d = CMVideoFormatDescriptionGetDimensions(f.formatDescription); qreal fAspectRatio = qreal(d.width) / d.height; if (w > dim.width || h > dim.height || qAbs((qreal(dim.width) / dim.height) - fAspectRatio) > 0.01) { newFormat = f; } } } if (qt_set_active_format(device, newFormat, !needFpsChange)) { m_restoreFormat = [currentFormat retain]; formatDesc = newFormat.formatDescription; dim = CMVideoFormatDescriptionGetDimensions(formatDesc); } if (w > 0 && h > 0) { // Make sure the recording resolution has the same aspect ratio as the device's // current resolution qreal deviceAspectRatio = qreal(dim.width) / dim.height; qreal recAspectRatio = qreal(w) / h; if (qAbs(deviceAspectRatio - recAspectRatio) > 0.01) { if (recAspectRatio > deviceAspectRatio) w = qRound(h * deviceAspectRatio); else h = qRound(w / deviceAspectRatio); } // recording resolution can't be higher than the device's active resolution w = qMin(w, dim.width); h = qMin(h, dim.height); } } if (w > 0 && h > 0) { // Width and height must be divisible by 2 w += w & 1; h += h & 1; [videoSettings setObject:[NSNumber numberWithInt:w] forKey:AVVideoWidthKey]; [videoSettings setObject:[NSNumber numberWithInt:h] forKey:AVVideoHeightKey]; m_actualSettings.setResolution(w, h); } else { m_actualSettings.setResolution(qt_device_format_resolution(device.activeFormat)); } // -- FPS if (needFpsChange) { m_restoreFps = currentFps; const qreal fps = m_requestedSettings.frameRate(); qt_set_framerate_limits(device, connection, fps, fps); } m_actualSettings.setFrameRate(qt_current_framerates(device, connection).second); // -- Codec Settings NSMutableDictionary *codecProperties = [NSMutableDictionary dictionary]; int bitrate = -1; float quality = -1.f; if (m_requestedSettings.encodingMode() == QMultimedia::ConstantQualityEncoding) { if (m_requestedSettings.quality() != QMultimedia::NormalQuality) { if (codec != QLatin1String("jpeg")) { qWarning("ConstantQualityEncoding is not supported for codec: '%s'", codec.toLocal8Bit().constData()); } else { switch (m_requestedSettings.quality()) { case QMultimedia::VeryLowQuality: quality = 0.f; break; case QMultimedia::LowQuality: quality = 0.25f; break; case QMultimedia::HighQuality: quality = 0.75f; break; case QMultimedia::VeryHighQuality: quality = 1.f; break; default: quality = -1.f; // NormalQuality, let the system decide break; } } } } else if (m_requestedSettings.encodingMode() == QMultimedia::AverageBitRateEncoding){ if (codec != QLatin1String("avc1")) qWarning("AverageBitRateEncoding is not supported for codec: '%s'", codec.toLocal8Bit().constData()); else bitrate = m_requestedSettings.bitRate(); } else { qWarning("Encoding mode is not supported"); } if (bitrate != -1) [codecProperties setObject:[NSNumber numberWithInt:bitrate] forKey:AVVideoAverageBitRateKey]; if (quality != -1.f) [codecProperties setObject:[NSNumber numberWithFloat:quality] forKey:AVVideoQualityKey]; [videoSettings setObject:codecProperties forKey:AVVideoCompressionPropertiesKey]; return videoSettings; } void AVFVideoEncoderSettingsControl::unapplySettings(AVCaptureConnection *connection) { m_actualSettings = m_requestedSettings; AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); if (!device) return; const bool needFpsChanged = m_restoreFps.first || m_restoreFps.second; if (m_restoreFormat) { qt_set_active_format(device, m_restoreFormat, !needFpsChanged); [m_restoreFormat release]; m_restoreFormat = nil; } if (needFpsChanged) { qt_set_framerate_limits(device, connection, m_restoreFps.first, m_restoreFps.second); m_restoreFps = AVFPSRange(); } } QT_END_NAMESPACE #include "moc_avfvideoencodersettingscontrol.cpp"