Skip to content

Commit

Permalink
[voicecall] Add VoiceCallAudioRecorder interface. Contributes to MER#…
Browse files Browse the repository at this point in the history
…1547
  • Loading branch information
matthewvogt committed Mar 25, 2016
1 parent 785be29 commit 554e7e6
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 1 deletion.
4 changes: 3 additions & 1 deletion plugins/declarative/src/src.pro
@@ -1,6 +1,6 @@
CONFIG += no_libvoicecall no_plugininstall
include(../../plugin.pri)
QT = core dbus qml
QT = core dbus qml multimedia

TARGET = voicecall
uri = org.nemomobile.voicecall
Expand All @@ -10,13 +10,15 @@ uri = org.nemomobile.voicecall
PKGCONFIG += ngf-qt5

HEADERS += \
voicecallaudiorecorder.h \
voicecallhandler.h \
voicecallmanager.h \
voicecallmodel.h \
voicecallprovidermodel.h \
voicecallplugin.h

SOURCES += \
voicecallaudiorecorder.cpp \
voicecallhandler.cpp \
voicecallmanager.cpp \
voicecallmodel.cpp \
Expand Down
207 changes: 207 additions & 0 deletions plugins/declarative/src/voicecallaudiorecorder.cpp
@@ -0,0 +1,207 @@

#include "voicecallaudiorecorder.h"

#include <QDateTime>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDir>
#include <QLocale>
#include <QtDebug>

namespace {

const QString recordingsDir("CallRecordings");

const quint16 ChannelCount = 1;
const quint16 SampleRate = 8000;
const quint16 SampleBits = 8;

QAudioFormat getRecordingFormat()
{
QAudioFormat format;

format.setChannelCount(ChannelCount);
format.setSampleRate(SampleRate);
format.setSampleSize(SampleBits);
format.setCodec(QStringLiteral("audio/pcm"));
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::UnSignedInt);

QAudioDeviceInfo info(QAudioDeviceInfo::defaultInputDevice());
if (!info.isFormatSupported(format)) {
format = info.nearestFormat(format);
}

return format;
}

const QAudioFormat recordingFormat(getRecordingFormat());

QDBusMessage createEnableVoicecallRecordingMessage(bool enable)
{
const QString routeManagerService("org.nemomobile.Route.Manager");
const QString routeManagerPath("/org/nemomobile/Route/Manager");
const QString routeManagerInterface("org.nemomobile.Route.Manager");

QDBusMessage msg = QDBusMessage::createMethodCall(routeManagerService,
routeManagerPath,
routeManagerInterface,
enable ? QString("Enable") : QString("Disable"));
msg.setArguments(QVariantList() << QVariant(QString("voicecallrec")));
return msg;
}

}

VoiceCallAudioRecorder::VoiceCallAudioRecorder(QObject *parent)
: QObject(parent)
, active(false)
{
}

VoiceCallAudioRecorder::~VoiceCallAudioRecorder()
{
terminateRecording();
}

void VoiceCallAudioRecorder::startRecording(const QString &name, const QString &uid)
{
if (name.isEmpty() || uid.isEmpty()) {
qWarning() << "Unable to create unidentified recording";
return;
}

if (active) {
qWarning() << "Recording already in progress";
return;
}

const QString timestamp(QLocale::c().toString(QDateTime::currentDateTime(), QStringLiteral("yyyyMMdd-HHmmsszzz")));
const QString fileName(QString("%1.%2.%3").arg(name).arg(uid).arg(timestamp));

if (initiateRecording(fileName)) {
label = name;
}
}

void VoiceCallAudioRecorder::stopRecording()
{
terminateRecording();
}

bool VoiceCallAudioRecorder::recording() const
{
return active;
}

QString VoiceCallAudioRecorder::decodeRecordingFileName(const QString &fileName)
{
return QFile::decodeName(fileName.toLocal8Bit());
}

bool VoiceCallAudioRecorder::deleteRecording(const QString &fileName)
{
const QString outputPath(QDir::home().path() + QDir::separator() + recordingsDir);
QDir outputDir(outputPath);
if (!outputDir.isReadable()) {
// We can't easily test writability
qWarning() << "Unreadable directory:" << outputDir;
}

if (outputDir.exists(fileName)) {
if (outputDir.remove(fileName)) {
return true;
} else {
qWarning() << "Unable to delete recording file:" << fileName;
}
} else {
qWarning() << "Unable to delete nonexistent recording file:" << fileName;
}
return false;
}

void VoiceCallAudioRecorder::inputStateChanged(QAudio::State state)
{
if (state == QAudio::StoppedState) {
if (input) {
if (input->error() != QAudio::NoError) {
qWarning() << "Recording stopped due to error:" << input->error();
}
}
terminateRecording();
}
}

bool VoiceCallAudioRecorder::initiateRecording(const QString &fileName)
{
terminateRecording();

if (!QDir::home().exists(recordingsDir)) {
// Create the directory for recordings
QDir::home().mkdir(recordingsDir);
if (!QDir::home().exists(recordingsDir)) {
qWarning() << "Unable to create:" << recordingsDir;
emit recordingError(FileCreation);
return false;
}
}
const QString outputPath(QDir::home().path() + QDir::separator() + recordingsDir);
QDir outputDir(outputPath);
if (!outputDir.isReadable()) {
// We can't easily test writability
qWarning() << "Unreadable directory:" << outputDir;
}

const QString filePath(outputDir.filePath(QString("%1.pcm").arg(QString::fromLocal8Bit(QFile::encodeName(fileName)))));

QScopedPointer<QFile> file(new QFile(filePath));
if (!file->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qWarning() << "Unable to open file for write:" << filePath;
emit recordingError(FileCreation);
return false;
}

QDBusMessage enableRecording(createEnableVoicecallRecordingMessage(true));
if (!QDBusConnection::systemBus().send(enableRecording)) {
qWarning() << "Unable to request recording activation" << QDBusConnection::systemBus().lastError();
file->remove();
emit recordingError(AudioRouting);
return false;
}

output.swap(file);

input.reset(new QAudioInput(recordingFormat));
connect(input.data(), &QAudioInput::stateChanged, this, &VoiceCallAudioRecorder::inputStateChanged);

input->start(output.data());
active = true;
emit recordingChanged();

return true;
}

void VoiceCallAudioRecorder::terminateRecording()
{
if (input) {
input->stop();
input.reset();

QDBusMessage disableRecording(createEnableVoicecallRecordingMessage(false));
if (!QDBusConnection::systemBus().send(disableRecording)) {
qWarning() << "Unable to request recording deactivation" << QDBusConnection::systemBus().lastError();
}
}
if (output) {
const QString fileName(output->fileName());
output->close();
output.reset();

emit callRecorded(fileName, label);
}
if (active) {
active = false;
emit recordingChanged();
}
}

53 changes: 53 additions & 0 deletions plugins/declarative/src/voicecallaudiorecorder.h
@@ -0,0 +1,53 @@
#ifndef VOICECALLAUDIORECORDER_H
#define VOICECALLAUDIORECORDER_H

#include <QAudioInput>
#include <QFile>
#include <QScopedPointer>

class VoiceCallAudioRecorder : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(VoiceCallAudioRecorder)

Q_ENUMS(ErrorCondition)

Q_PROPERTY(bool recording READ recording NOTIFY recordingChanged)

public:
enum ErrorCondition {
FileCreation,
FileStorage,
AudioRouting,
};

explicit VoiceCallAudioRecorder(QObject *parent);
~VoiceCallAudioRecorder();

Q_INVOKABLE void startRecording(const QString &name, const QString &uid);
Q_INVOKABLE void stopRecording();

bool recording() const;

Q_INVOKABLE QString decodeRecordingFileName(const QString &fileName);
Q_INVOKABLE bool deleteRecording(const QString &fileName);

signals:
void recordingChanged();
void recordingError(ErrorCondition error);
void callRecorded(const QString &fileName, const QString &label);

private slots:
void inputStateChanged(QAudio::State state);

private:
bool initiateRecording(const QString &fileName);
void terminateRecording();

QScopedPointer<QAudioInput> input;
QScopedPointer<QFile> output;
QString label;
bool active;
};

#endif
12 changes: 12 additions & 0 deletions plugins/declarative/src/voicecallplugin.cpp
Expand Up @@ -3,17 +3,29 @@
#include "voicecallhandler.h"
#include "voicecallmanager.h"

#include "voicecallaudiorecorder.h"
#include "voicecallmodel.h"
#include "voicecallprovidermodel.h"

#include <QtQml>

namespace {

QObject *voice_call_audio_recorder_api_factory(QQmlEngine *qmlEngine, QJSEngine *)
{
return new VoiceCallAudioRecorder(qmlEngine);
}

}

void VoiceCallPlugin::registerTypes(const char *uri)
{
qmlRegisterUncreatableType<VoiceCallHandler>(uri, 1, 0, "VoiceCall", "uncreatable type");
qmlRegisterUncreatableType<VoiceCallModel>(uri, 1, 0, "VoiceCallModel", "uncreatable type");
qmlRegisterUncreatableType<VoiceCallProviderModel>(uri, 1, 0, "VoiceCallProviderModel", "uncreatable type");

qmlRegisterSingletonType<VoiceCallAudioRecorder>(uri, 1, 0, "VoiceCallAudioRecorder", voice_call_audio_recorder_api_factory);

qmlRegisterType<VoiceCallManager>(uri, 1, 0, "VoiceCallManager");
}

2 changes: 2 additions & 0 deletions plugins/plugin.pri
Expand Up @@ -2,6 +2,8 @@ TEMPLATE = lib
QT = core
CONFIG += plugin link_pkgconfig

QMAKE_CXXFLAGS += -std=c++0x

# includes are ok all the time, yes, really.
# it's only used for some macros.
INCLUDEPATH += $$PWD/../lib/src
Expand Down

0 comments on commit 554e7e6

Please sign in to comment.