Initial implementation of Mac camera backend

Based on AVFoundation framework

Change-Id: If4cfd105a592f50b42606624548b9ffc870e3e47
Reviewed-by: Gunnar Sletta <gunnar.sletta@nokia.com>
This commit is contained in:
Dmytro Poplavskiy
2012-08-17 13:44:14 +10:00
committed by Qt by Nokia
parent 09a7fda971
commit 37b872da9e
27 changed files with 3009 additions and 0 deletions

View File

@@ -0,0 +1,342 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "avfcameradebug.h"
#include "avfmediarecordercontrol.h"
#include "avfcamerasession.h"
#include "avfcameraservice.h"
#include "avfcameracontrol.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_cameraControl(service->cameraControl())
, 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_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(reconnectMovieOutput()));
reconnectMovieOutput();
}
AVFMediaRecorderControl::~AVFMediaRecorderControl()
{
if (m_movieOutput)
[m_session->captureSession() removeOutput:m_movieOutput];
[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()
{
}
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) {
m_state = QMediaRecorder::RecordingState;
m_recordingStarted = false;
m_recordingFinished = false;
QString outputLocationPath = m_outputLocation.scheme() == QLatin1String("file") ?
m_outputLocation.path() : m_outputLocation.toString();
QUrl actualLocation = QUrl::fromLocalFile(
m_storageLocation.generateFileName(outputLocationPath,
QCamera::CaptureVideo,
QLatin1String("clip_"),
QLatin1String("mp4")));
qDebugCamera() << "Video capture location:" << actualLocation.toString();
NSString *urlString = [NSString stringWithUTF8String:actualLocation.toString().toUtf8().constData()];
NSURL *fileURL = [NSURL URLWithString:urlString];
[m_movieOutput startRecordingToOutputFileURL:fileURL recordingDelegate:m_recorderDelagate];
Q_EMIT actualLocationChanged(actualLocation);
} 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_state = QMediaRecorder::StoppedState;
[m_movieOutput stopRecording];
}
}
updateStatus();
Q_EMIT stateChanged(m_state);
}
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;
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::reconnectMovieOutput()
{
//adding movie output causes high CPU usage even when while recording is not active,
//connect it only while video capture mode is enabled
AVCaptureSession *captureSession = m_session->captureSession();
if (!m_connected && m_cameraControl->captureMode().testFlag(QCamera::CaptureVideo)) {
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)) {
[captureSession removeOutput:m_movieOutput];
m_connected = false;
}
updateStatus();
}
#include "moc_avfmediarecordercontrol.cpp"