blob: f8f4500c13b813ffe0f72be52992c75a3d738d72 [file] [log] [blame]
/****************************************************************************
**
** 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 <QtCore/qurl.h>
#include <QtCore/qfileinfo.h>
#include <QtMultimedia/qcameracontrol.h>
QT_USE_NAMESPACE
@interface AVFMediaRecorderDelegate : NSObject <AVCaptureFileOutputRecordingDelegate>
{
@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"