/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "avfcameradebug.h" #include "avfmediarecordercontrol.h" #include "avfcamerasession.h" #include "avfcameraservice.h" #include "avfcameracontrol.h" #include "avfaudioinputselectorcontrol.h" #include "avfaudioencodersettingscontrol.h" #include "avfvideoencodersettingscontrol.h" #include "avfmediacontainercontrol.h" #include #include #include QT_USE_NAMESPACE @interface AVFMediaRecorderDelegate : NSObject { @private AVFMediaRecorderControl *m_recorder; } - (AVFMediaRecorderDelegate *) initWithRecorder:(AVFMediaRecorderControl*)recorder; - (void) captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections; - (void) captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections error:(NSError *)error; @end @implementation AVFMediaRecorderDelegate - (AVFMediaRecorderDelegate *) initWithRecorder:(AVFMediaRecorderControl*)recorder { if (!(self = [super init])) return nil; self->m_recorder = recorder; return self; } - (void) captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections { Q_UNUSED(captureOutput); Q_UNUSED(fileURL); Q_UNUSED(connections) QMetaObject::invokeMethod(m_recorder, "handleRecordingStarted", Qt::QueuedConnection); } - (void) captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections error:(NSError *)error { Q_UNUSED(captureOutput); Q_UNUSED(fileURL); Q_UNUSED(connections) if (error) { QStringList messageParts; messageParts << QString::fromUtf8([[error localizedDescription] UTF8String]); messageParts << QString::fromUtf8([[error localizedFailureReason] UTF8String]); messageParts << QString::fromUtf8([[error localizedRecoverySuggestion] UTF8String]); QString errorMessage = messageParts.join(" "); QMetaObject::invokeMethod(m_recorder, "handleRecordingFailed", Qt::QueuedConnection, Q_ARG(QString, errorMessage)); } else { QMetaObject::invokeMethod(m_recorder, "handleRecordingFinished", Qt::QueuedConnection); } } @end AVFMediaRecorderControl::AVFMediaRecorderControl(AVFCameraService *service, QObject *parent) : QMediaRecorderControl(parent) , m_service(service) , m_cameraControl(service->cameraControl()) , m_audioInputControl(service->audioInputSelectorControl()) , m_session(service->session()) , m_connected(false) , m_state(QMediaRecorder::StoppedState) , m_lastStatus(QMediaRecorder::UnloadedStatus) , m_recordingStarted(false) , m_recordingFinished(false) , m_muted(false) , m_volume(1.0) , m_audioInput(nil) , m_restoreFPS(-1, -1) { m_movieOutput = [[AVCaptureMovieFileOutput alloc] init]; m_recorderDelagate = [[AVFMediaRecorderDelegate alloc] initWithRecorder:this]; connect(m_cameraControl, SIGNAL(stateChanged(QCamera::State)), SLOT(updateStatus())); connect(m_cameraControl, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateStatus())); connect(m_cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(setupSessionForCapture())); connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(setupSessionForCapture())); connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(setupSessionForCapture())); } AVFMediaRecorderControl::~AVFMediaRecorderControl() { if (m_movieOutput) { [m_session->captureSession() removeOutput:m_movieOutput]; [m_movieOutput release]; } if (m_audioInput) { [m_session->captureSession() removeInput:m_audioInput]; [m_audioInput release]; } [m_recorderDelagate release]; } QUrl AVFMediaRecorderControl::outputLocation() const { return m_outputLocation; } bool AVFMediaRecorderControl::setOutputLocation(const QUrl &location) { m_outputLocation = location; return location.scheme() == QLatin1String("file") || location.scheme().isEmpty(); } QMediaRecorder::State AVFMediaRecorderControl::state() const { return m_state; } QMediaRecorder::Status AVFMediaRecorderControl::status() const { QMediaRecorder::Status status = m_lastStatus; //bool videoEnabled = m_cameraControl->captureMode().testFlag(QCamera::CaptureVideo); if (m_cameraControl->status() == QCamera::ActiveStatus && m_connected) { if (m_state == QMediaRecorder::StoppedState) { if (m_recordingStarted && !m_recordingFinished) status = QMediaRecorder::FinalizingStatus; else status = QMediaRecorder::LoadedStatus; } else { status = m_recordingStarted ? QMediaRecorder::RecordingStatus : QMediaRecorder::StartingStatus; } } else { //camera not started yet status = m_cameraControl->state() == QCamera::ActiveState && m_connected ? QMediaRecorder::LoadingStatus: QMediaRecorder::UnloadedStatus; } return status; } void AVFMediaRecorderControl::updateStatus() { QMediaRecorder::Status newStatus = status(); if (m_lastStatus != newStatus) { qDebugCamera() << "Camera recorder status changed: " << m_lastStatus << " -> " << newStatus; m_lastStatus = newStatus; Q_EMIT statusChanged(m_lastStatus); } } qint64 AVFMediaRecorderControl::duration() const { if (!m_movieOutput) return 0; return qint64(CMTimeGetSeconds(m_movieOutput.recordedDuration) * 1000); } bool AVFMediaRecorderControl::isMuted() const { return m_muted; } qreal AVFMediaRecorderControl::volume() const { return m_volume; } void AVFMediaRecorderControl::applySettings() { if (m_state != QMediaRecorder::StoppedState || (m_session->state() != QCamera::ActiveState && m_session->state() != QCamera::LoadedState) || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureVideo)) { return; } // Configure audio settings [m_movieOutput setOutputSettings:m_service->audioEncoderSettingsControl()->applySettings() forConnection:[m_movieOutput connectionWithMediaType:AVMediaTypeAudio]]; // Configure video settings AVCaptureConnection *videoConnection = [m_movieOutput connectionWithMediaType:AVMediaTypeVideo]; NSDictionary *videoSettings = m_service->videoEncoderSettingsControl()->applySettings(videoConnection); const AVFConfigurationLock lock(m_session->videoCaptureDevice()); // prevents activeFormat from being overridden [m_movieOutput setOutputSettings:videoSettings forConnection:videoConnection]; } void AVFMediaRecorderControl::unapplySettings() { m_service->audioEncoderSettingsControl()->unapplySettings(); m_service->videoEncoderSettingsControl()->unapplySettings([m_movieOutput connectionWithMediaType:AVMediaTypeVideo]); } void AVFMediaRecorderControl::setState(QMediaRecorder::State state) { if (m_state == state) return; qDebugCamera() << Q_FUNC_INFO << m_state << " -> " << state; switch (state) { case QMediaRecorder::RecordingState: { if (m_connected) { QString outputLocationPath = m_outputLocation.scheme() == QLatin1String("file") ? m_outputLocation.path() : m_outputLocation.toString(); QString extension = m_service->mediaContainerControl()->containerFormat(); QUrl actualLocation = QUrl::fromLocalFile( m_storageLocation.generateFileName(outputLocationPath, QCamera::CaptureVideo, QLatin1String("clip_"), extension)); qDebugCamera() << "Video capture location:" << actualLocation.toString(); applySettings(); [m_movieOutput startRecordingToOutputFileURL:actualLocation.toNSURL() recordingDelegate:m_recorderDelagate]; m_state = QMediaRecorder::RecordingState; m_recordingStarted = false; m_recordingFinished = false; Q_EMIT actualLocationChanged(actualLocation); updateStatus(); Q_EMIT stateChanged(m_state); } else { Q_EMIT error(QMediaRecorder::FormatError, tr("Recorder not configured")); } } break; case QMediaRecorder::PausedState: { Q_EMIT error(QMediaRecorder::FormatError, tr("Recording pause not supported")); return; } break; case QMediaRecorder::StoppedState: { m_lastStatus = QMediaRecorder::FinalizingStatus; Q_EMIT statusChanged(m_lastStatus); [m_movieOutput stopRecording]; unapplySettings(); } } } void AVFMediaRecorderControl::setMuted(bool muted) { if (m_muted != muted) { m_muted = muted; Q_EMIT mutedChanged(muted); } } void AVFMediaRecorderControl::setVolume(qreal volume) { if (m_volume != volume) { m_volume = volume; Q_EMIT volumeChanged(volume); } } void AVFMediaRecorderControl::handleRecordingStarted() { m_recordingStarted = true; updateStatus(); } void AVFMediaRecorderControl::handleRecordingFinished() { m_recordingFinished = true; if (m_state != QMediaRecorder::StoppedState) { m_state = QMediaRecorder::StoppedState; Q_EMIT stateChanged(m_state); } updateStatus(); } void AVFMediaRecorderControl::handleRecordingFailed(const QString &message) { m_recordingFinished = true; if (m_state != QMediaRecorder::StoppedState) { m_state = QMediaRecorder::StoppedState; Q_EMIT stateChanged(m_state); } updateStatus(); Q_EMIT error(QMediaRecorder::ResourceError, message); } void AVFMediaRecorderControl::setupSessionForCapture() { if (!m_session->videoCaptureDevice()) return; //adding movie output causes high CPU usage even when while recording is not active, //connect it only while video capture mode is enabled. // Similarly, connect the Audio input only in that mode, since it's only necessary // when recording anyway. Adding an Audio input will trigger the microphone permission // request on iOS, but it shoudn't do so until we actually try to record. AVCaptureSession *captureSession = m_session->captureSession(); if (!m_connected && m_cameraControl->captureMode().testFlag(QCamera::CaptureVideo) && m_session->state() != QCamera::UnloadedState) { // Lock the video capture device to make sure the active format is not reset const AVFConfigurationLock lock(m_session->videoCaptureDevice()); // Add audio input // Allow recording even if something wrong happens with the audio input initialization AVCaptureDevice *audioDevice = m_audioInputControl->createCaptureDevice(); if (!audioDevice) { qWarning("No audio input device available"); } else { NSError *error = nil; m_audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error]; if (!m_audioInput) { qWarning() << "Failed to create audio device input"; } else if (![captureSession canAddInput:m_audioInput]) { qWarning() << "Could not connect the audio input"; m_audioInput = nullptr; } else { [m_audioInput retain]; [captureSession addInput:m_audioInput]; } } if ([captureSession canAddOutput:m_movieOutput]) { [captureSession addOutput:m_movieOutput]; m_connected = true; } else { Q_EMIT error(QMediaRecorder::ResourceError, tr("Could not connect the video recorder")); qWarning() << "Could not connect the video recorder"; } } else if (m_connected && (!m_cameraControl->captureMode().testFlag(QCamera::CaptureVideo) || m_session->state() == QCamera::UnloadedState)) { // Lock the video capture device to make sure the active format is not reset const AVFConfigurationLock lock(m_session->videoCaptureDevice()); [captureSession removeOutput:m_movieOutput]; if (m_audioInput) { [captureSession removeInput:m_audioInput]; [m_audioInput release]; m_audioInput = nil; } m_connected = false; } updateStatus(); } #include "moc_avfmediarecordercontrol.cpp"