summaryrefslogtreecommitdiffstats
path: root/src/multimedia/platform/darwin/camera/avfmediaencoder.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia/platform/darwin/camera/avfmediaencoder.mm')
-rw-r--r--src/multimedia/platform/darwin/camera/avfmediaencoder.mm649
1 files changed, 0 insertions, 649 deletions
diff --git a/src/multimedia/platform/darwin/camera/avfmediaencoder.mm b/src/multimedia/platform/darwin/camera/avfmediaencoder.mm
deleted file mode 100644
index 83e9c6d3d..000000000
--- a/src/multimedia/platform/darwin/camera/avfmediaencoder.mm
+++ /dev/null
@@ -1,649 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-
-#include "avfmediaencoder_p.h"
-#include "avfcamerarenderer_p.h"
-#include "avfcamerasession_p.h"
-#include "avfcamera_p.h"
-#include "avfcameraservice_p.h"
-#include "avfcameradebug_p.h"
-#include "avfcamerautility_p.h"
-#include "qaudiodevice.h"
-
-#include "qmediadevices.h"
-#include "qmediastoragelocation_p.h"
-#include "private/qmediarecorder_p.h"
-#include "private/qdarwinformatsinfo_p.h"
-#include "private/qplatformaudiooutput_p.h"
-
-#include <QtCore/qmath.h>
-#include <QtCore/qdebug.h>
-#include <QtCore/qmimetype.h>
-
-QT_USE_NAMESPACE
-
-namespace {
-
-bool qt_is_writable_file_URL(NSURL *fileURL)
-{
- Q_ASSERT(fileURL);
-
- if (![fileURL isFileURL])
- return false;
-
- if (NSString *path = [[fileURL path] stringByExpandingTildeInPath]) {
- return [[NSFileManager defaultManager]
- isWritableFileAtPath:[path stringByDeletingLastPathComponent]];
- }
-
- return false;
-}
-
-bool qt_file_exists(NSURL *fileURL)
-{
- Q_ASSERT(fileURL);
-
- if (NSString *path = [[fileURL path] stringByExpandingTildeInPath])
- return [[NSFileManager defaultManager] fileExistsAtPath:path];
-
- return false;
-}
-
-}
-
-AVFMediaEncoder::AVFMediaEncoder(QMediaRecorder *parent)
- : QObject(parent)
- , QPlatformMediaEncoder(parent)
- , m_state(QMediaRecorder::StoppedState)
- , m_duration(0)
- , m_audioSettings(nil)
- , m_videoSettings(nil)
- //, m_restoreFPS(-1, -1)
-{
- m_writer.reset([[QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) alloc] initWithDelegate:this]);
- if (!m_writer) {
- qDebugCamera() << Q_FUNC_INFO << "failed to create an asset writer";
- return;
- }
-}
-
-AVFMediaEncoder::~AVFMediaEncoder()
-{
- [m_writer abort];
-
- if (m_audioSettings)
- [m_audioSettings release];
- if (m_videoSettings)
- [m_videoSettings release];
-}
-
-bool AVFMediaEncoder::isLocationWritable(const QUrl &location) const
-{
- return location.scheme() == QLatin1String("file") || location.scheme().isEmpty();
-}
-
-QMediaRecorder::RecorderState AVFMediaEncoder::state() const
-{
- return m_state;
-}
-
-qint64 AVFMediaEncoder::duration() const
-{
- return m_duration;
-}
-
-void AVFMediaEncoder::updateDuration(qint64 duration)
-{
- m_duration = duration;
- durationChanged(m_duration);
-}
-
-static NSDictionary *avfAudioSettings(const QMediaEncoderSettings &encoderSettings)
-{
- NSMutableDictionary *settings = [NSMutableDictionary dictionary];
-
- int codecId = QDarwinFormatInfo::audioFormatForCodec(encoderSettings.mediaFormat().audioCodec());
- [settings setObject:[NSNumber numberWithInt:codecId] forKey:AVFormatIDKey];
-
- // Setting AVEncoderQualityKey is not allowed when format ID is alac or lpcm
- if (codecId != kAudioFormatAppleLossless && codecId != kAudioFormatLinearPCM
- && encoderSettings.encodingMode() == QMediaRecorder::ConstantQualityEncoding) {
- int quality;
- switch (encoderSettings.quality()) {
- case QMediaRecorder::VeryLowQuality:
- quality = AVAudioQualityMin;
- break;
- case QMediaRecorder::LowQuality:
- quality = AVAudioQualityLow;
- break;
- case QMediaRecorder::HighQuality:
- quality = AVAudioQualityHigh;
- break;
- case QMediaRecorder::VeryHighQuality:
- quality = AVAudioQualityMax;
- break;
- case QMediaRecorder::NormalQuality:
- default:
- quality = AVAudioQualityMedium;
- break;
- }
- [settings setObject:[NSNumber numberWithInt:quality] forKey:AVEncoderAudioQualityKey];
- } else {
- bool isBitRateSupported = false;
- int bitRate = encoderSettings.audioBitRate();
- if (bitRate > 0) {
- QList<AudioValueRange> bitRates = qt_supported_bit_rates_for_format(codecId);
- for (int i = 0; i < bitRates.count(); i++) {
- if (bitRate >= bitRates[i].mMinimum &&
- bitRate <= bitRates[i].mMaximum) {
- isBitRateSupported = true;
- break;
- }
- }
- if (isBitRateSupported)
- [settings setObject:[NSNumber numberWithInt:encoderSettings.audioBitRate()]
- forKey:AVEncoderBitRateKey];
- }
- }
-
- int sampleRate = encoderSettings.audioSampleRate();
- bool isSampleRateSupported = false;
- if (sampleRate >= 8000 && sampleRate <= 192000) {
- QList<AudioValueRange> sampleRates = qt_supported_sample_rates_for_format(codecId);
- for (int i = 0; i < sampleRates.count(); i++) {
- if (sampleRate >= sampleRates[i].mMinimum && sampleRate <= sampleRates[i].mMaximum) {
- isSampleRateSupported = true;
- break;
- }
- }
- }
-
- int channelCount = encoderSettings.audioChannelCount();
- bool isChannelCountSupported = false;
- if (channelCount > 0) {
- std::optional<QList<UInt32>> channelCounts = qt_supported_channel_counts_for_format(codecId);
- // An std::nullopt result indicates that
- // any number of channels can be encoded.
- if (channelCounts == std::nullopt) {
- isChannelCountSupported = true;
- } else {
- for (int i = 0; i < channelCounts.value().count(); i++) {
- if ((UInt32)channelCount == channelCounts.value()[i]) {
- isChannelCountSupported = true;
- break;
- }
- }
- }
- }
-
-#ifdef Q_OS_IOS
- // Some keys are mandatory only on iOS
- if (!isSampleRateSupported) {
- sampleRate = 44100;
- isSampleRateSupported = true;
- }
- if (!isChannelCountSupported) {
- channelCount = 2;
- isChannelCountSupported = true;
- }
-
- if (codecId == kAudioFormatAppleLossless)
- [settings setObject:[NSNumber numberWithInt:24] forKey:AVEncoderBitDepthHintKey];
-
- if (codecId == kAudioFormatLinearPCM) {
- [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];
- }
-#endif
- if (isSampleRateSupported)
- [settings setObject:[NSNumber numberWithInt:sampleRate] forKey:AVSampleRateKey];
- if (isChannelCountSupported)
- [settings setObject:[NSNumber numberWithInt:channelCount] forKey:AVNumberOfChannelsKey];
-
- return settings;
-}
-
-NSDictionary *avfVideoSettings(QMediaEncoderSettings &encoderSettings, AVCaptureDevice *device, AVCaptureConnection *connection, QSize nativeSize)
-{
- if (!device)
- return nil;
-
-
- // ### re-add needFpsChange
-// AVFPSRange currentFps = qt_current_framerates(device, connection);
-
- NSMutableDictionary *videoSettings = [NSMutableDictionary dictionary];
-
- // -- Codec
-
- // AVVideoCodecKey is the only mandatory key
- auto codec = encoderSettings.mediaFormat().videoCodec();
- NSString *c = QDarwinFormatInfo::videoFormatForCodec(codec);
- [videoSettings setObject:c forKey:AVVideoCodecKey];
- [c release];
-
- // -- Resolution
-
- int w = encoderSettings.videoResolution().width();
- int h = encoderSettings.videoResolution().height();
-
- if (AVCaptureDeviceFormat *currentFormat = device.activeFormat) {
- CMFormatDescriptionRef formatDesc = currentFormat.formatDescription;
- CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc);
- FourCharCode formatCodec = CMVideoFormatDescriptionGetCodecType(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)
- && encoderSettings.videoFrameRate() > 0
- && !qt_format_supports_framerate(currentFormat, encoderSettings.videoFrameRate())) {
-
- newFormat = qt_find_best_framerate_match(device,
- formatCodec,
- encoderSettings.videoFrameRate());
-
- } else if (w > 0 && h > 0) {
- AVCaptureDeviceFormat *f = qt_find_best_resolution_match(device,
- encoderSettings.videoResolution(),
- formatCodec);
-
- 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, false /*### !needFpsChange*/)) {
- 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;
-
- bool isPortrait = nativeSize.width() < nativeSize.height();
- // Make sure the video has the right aspect ratio
- if (isPortrait && h < w)
- qSwap(w, h);
- else if (!isPortrait && w < h)
- qSwap(w, h);
-
- encoderSettings.setVideoResolution(QSize(w, h));
- } else {
- w = nativeSize.width();
- h = nativeSize.height();
- encoderSettings.setVideoResolution(nativeSize);
- }
- [videoSettings setObject:[NSNumber numberWithInt:w] forKey:AVVideoWidthKey];
- [videoSettings setObject:[NSNumber numberWithInt:h] forKey:AVVideoHeightKey];
-
- // -- FPS
-
- if (true /*needFpsChange*/) {
- const qreal fps = encoderSettings.videoFrameRate();
- qt_set_framerate_limits(device, connection, fps, fps);
- }
- encoderSettings.setVideoFrameRate(qt_current_framerates(device, connection).second);
-
- // -- Codec Settings
-
- NSMutableDictionary *codecProperties = [NSMutableDictionary dictionary];
- int bitrate = -1;
- float quality = -1.f;
-
- if (encoderSettings.encodingMode() == QMediaRecorder::ConstantQualityEncoding) {
- if (encoderSettings.quality() != QMediaRecorder::NormalQuality) {
- if (codec != QMediaFormat::VideoCodec::MotionJPEG) {
- qWarning("ConstantQualityEncoding is not supported for MotionJPEG");
- } else {
- switch (encoderSettings.quality()) {
- case QMediaRecorder::VeryLowQuality:
- quality = 0.f;
- break;
- case QMediaRecorder::LowQuality:
- quality = 0.25f;
- break;
- case QMediaRecorder::HighQuality:
- quality = 0.75f;
- break;
- case QMediaRecorder::VeryHighQuality:
- quality = 1.f;
- break;
- default:
- quality = -1.f; // NormalQuality, let the system decide
- break;
- }
- }
- }
- } else if (encoderSettings.encodingMode() == QMediaRecorder::AverageBitRateEncoding){
- if (codec != QMediaFormat::VideoCodec::H264 && codec != QMediaFormat::VideoCodec::H265)
- qWarning() << "AverageBitRateEncoding is not supported for codec" << QMediaFormat::videoCodecName(codec);
- else
- bitrate = encoderSettings.videoBitRate();
- } 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 AVFMediaEncoder::applySettings(QMediaEncoderSettings &settings)
-{
- AVFCameraSession *session = m_service->session();
-
- // audio settings
- m_audioSettings = avfAudioSettings(settings);
- if (m_audioSettings)
- [m_audioSettings retain];
-
- // video settings
- AVCaptureDevice *device = session->videoCaptureDevice();
- if (!device)
- return;
- const AVFConfigurationLock lock(device); // prevents activeFormat from being overridden
- AVCaptureConnection *conn = [session->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo];
- auto nativeSize = session->videoOutput()->nativeSize();
- m_videoSettings = avfVideoSettings(settings, device, conn, nativeSize);
- if (m_videoSettings)
- [m_videoSettings retain];
-}
-
-void AVFMediaEncoder::unapplySettings()
-{
- if (m_audioSettings) {
- [m_audioSettings release];
- m_audioSettings = nil;
- }
- if (m_videoSettings) {
- [m_videoSettings release];
- m_videoSettings = nil;
- }
-}
-
-void AVFMediaEncoder::setMetaData(const QMediaMetaData &metaData)
-{
- m_metaData = metaData;
-}
-
-QMediaMetaData AVFMediaEncoder::metaData() const
-{
- return m_metaData;
-}
-
-void AVFMediaEncoder::setCaptureSession(QPlatformMediaCaptureSession *session)
-{
- AVFCameraService *captureSession = static_cast<AVFCameraService *>(session);
- if (m_service == captureSession)
- return;
-
- if (m_service)
- stop();
-
- m_service = captureSession;
- if (!m_service)
- return;
-
- connect(m_service, &AVFCameraService::cameraChanged, this, &AVFMediaEncoder::onCameraChanged);
- onCameraChanged();
-}
-
-void AVFMediaEncoder::record(QMediaEncoderSettings &settings)
-{
- if (!m_service || !m_service->session()) {
- qWarning() << Q_FUNC_INFO << "Encoder is not set to a capture session";
- return;
- }
-
- if (!m_writer) {
- qDebugCamera() << Q_FUNC_INFO << "Invalid recorder";
- return;
- }
-
- if (QMediaRecorder::RecordingState == m_state)
- return;
-
- m_service->session()->setActive(true);
- const bool audioOnly = settings.videoCodec() == QMediaFormat::VideoCodec::Unspecified;
- AVCaptureSession *session = m_service->session()->captureSession();
- float rotation = 0;
-
- if (!audioOnly) {
- AVFCamera *cameraControl = m_service->avfCameraControl();
- if (!cameraControl || !cameraControl->isActive()) {
- qDebugCamera() << Q_FUNC_INFO << "can not start record while camera is not active";
- Q_EMIT error(QMediaRecorder::ResourceError,
- QMediaRecorderPrivate::msgFailedStartRecording());
- return;
- }
- }
-
- const QString path(outputLocation().scheme() == QLatin1String("file") ?
- outputLocation().path() : outputLocation().toString());
- const QUrl fileURL(QUrl::fromLocalFile(QMediaStorageLocation::generateFileName(path,
- audioOnly ? QStandardPaths::MusicLocation : QStandardPaths::MoviesLocation,
- settings.mimeType().preferredSuffix())));
-
- NSURL *nsFileURL = fileURL.toNSURL();
- if (!nsFileURL) {
- qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL;
- Q_EMIT error(QMediaRecorder::ResourceError, tr("Invalid output file URL"));
- return;
- }
- if (!qt_is_writable_file_URL(nsFileURL)) {
- qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL
- << "(the location is not writable)";
- Q_EMIT error(QMediaRecorder::ResourceError, tr("Non-writeable file location"));
- return;
- }
- if (qt_file_exists(nsFileURL)) {
- // We test for/handle this error here since AWAssetWriter will raise an
- // Objective-C exception, which is not good at all.
- qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL
- << "(file already exists)";
- Q_EMIT error(QMediaRecorder::ResourceError, tr("File already exists"));
- return;
- }
-
- applySettings(settings);
-
- QVideoOutputOrientationHandler::setIsRecording(true);
-
- // We stop session now so that no more frames for renderer's queue
- // generated, will restart in assetWriterStarted.
- [session stopRunning];
-
- if ([m_writer setupWithFileURL:nsFileURL
- cameraService:m_service
- audioSettings:m_audioSettings
- videoSettings:m_videoSettings
- fileFormat:settings.fileFormat()
- transform:CGAffineTransformMakeRotation(qDegreesToRadians(rotation))]) {
-
- m_state = QMediaRecorder::RecordingState;
-
- Q_EMIT actualLocationChanged(fileURL);
- Q_EMIT stateChanged(m_state);
-
- // Apple recommends to call startRunning and do all
- // setup on a special queue, and that's what we had
- // initially (dispatch_async to writerQueue). Unfortunately,
- // writer's queue is not the only queue/thread that can
- // access/modify the session, and as a result we have
- // all possible data/race-conditions with Obj-C exceptions
- // at best and something worse in general.
- // Now we try to only modify session on the same thread.
- [m_writer start];
- } else {
- [session startRunning];
- Q_EMIT error(QMediaRecorder::FormatError,
- QMediaRecorderPrivate::msgFailedStartRecording());
- }
-}
-
-void AVFMediaEncoder::pause()
-{
- if (!m_service || !m_service->session() || state() != QMediaRecorder::RecordingState)
- return;
-
- toggleRecord(false);
- m_state = QMediaRecorder::PausedState;
- stateChanged(m_state);
-}
-
-void AVFMediaEncoder::resume()
-{
- if (!m_service || !m_service->session() || state() != QMediaRecorder::PausedState)
- return;
-
- toggleRecord(true);
- m_state = QMediaRecorder::RecordingState;
- stateChanged(m_state);
-}
-
-void AVFMediaEncoder::stop()
-{
- if (m_state != QMediaRecorder::StoppedState) {
- // Do not check the camera status, we can stop if we started.
- stopWriter();
- }
- QVideoOutputOrientationHandler::setIsRecording(false);
-}
-
-
-void AVFMediaEncoder::toggleRecord(bool enable)
-{
- if (!m_service || !m_service->session())
- return;
-
- if (!enable)
- [m_writer pause];
- else
- [m_writer resume];
-}
-
-void AVFMediaEncoder::assetWriterStarted()
-{
-}
-
-void AVFMediaEncoder::assetWriterFinished()
-{
- Q_ASSERT(m_service && m_service->session());
- AVFCameraSession *session = m_service->session();
-
- const QMediaRecorder::RecorderState lastState = m_state;
-
- unapplySettings();
-
- if (session->videoOutput()) {
- session->videoOutput()->resetCaptureDelegate();
- }
- if (session->audioPreviewDelegate()) {
- [session->audioPreviewDelegate() resetAudioPreviewDelegate];
- }
- [session->captureSession() startRunning];
-
- m_state = QMediaRecorder::StoppedState;
- if (m_state != lastState)
- Q_EMIT stateChanged(m_state);
-}
-
-void AVFMediaEncoder::onCameraChanged()
-{
- if (m_service && m_service->avfCameraControl()) {
- AVFCamera *cameraControl = m_service->avfCameraControl();
- connect(cameraControl, SIGNAL(activeChanged(bool)),
- SLOT(cameraActiveChanged(bool)));
- }
-}
-
-void AVFMediaEncoder::cameraActiveChanged(bool active)
-{
- Q_ASSERT(m_service);
- AVFCamera *cameraControl = m_service->avfCameraControl();
- Q_ASSERT(cameraControl);
-
- if (!active) {
- return stopWriter();
- }
-}
-
-void AVFMediaEncoder::stopWriter()
-{
- [m_writer stop];
-}
-
-#include "moc_avfmediaencoder_p.cpp"