diff --git a/src/MediaRecorder.cpp b/src/MediaRecorder.cpp
index 53b26f1..03ca305 100644
--- a/src/MediaRecorder.cpp
+++ b/src/MediaRecorder.cpp
@@ -1,1241 +1,1243 @@
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2020 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kaidan. If not, see .
*/
#include "MediaRecorder.h"
#include "Kaidan.h"
#include
#include
/*
* NOTES: Codecs and containers supported list are available as soon as the object is created.
* Resolutions, frame rates etc are populated once the objects become *ready*.
*/
#define ENABLE_DEBUG false
#define SETTING_USER_DEFAULT QStringLiteral("User Default")
#define SETTING_DEFAULT_CAMERA_DEVICE_NAME QStringLiteral("Camera Device Name")
#define SETTING_DEFAULT_AUDIO_INPUT_DEVICE_NAME QStringLiteral("Audio Input Device Name")
static void connectCamera(QCamera *camera, MediaRecorder *receiver) {
QObject::connect(camera, &QCamera::statusChanged, receiver, &MediaRecorder::readyChanged);
}
static void connectImageCapturer(CameraImageCapture *capturer, MediaRecorder *receiver) {
QObject::connect(capturer, &CameraImageCapture::availabilityChanged, receiver, &MediaRecorder::availabilityStatusChanged);
QObject::connect(capturer, QOverload::of(&CameraImageCapture::error), receiver, &MediaRecorder::errorChanged);
QObject::connect(capturer, &CameraImageCapture::actualLocationChanged, receiver, &MediaRecorder::actualLocationChanged);
QObject::connect(capturer, &CameraImageCapture::readyForCaptureChanged, receiver, &MediaRecorder::readyChanged);
}
template
static void connectMediaRecorder(T *recorder, MediaRecorder *receiver) {
QObject::connect(recorder, QOverload::of(&T::availabilityChanged), receiver, &MediaRecorder::availabilityStatusChanged);
QObject::connect(recorder, &T::stateChanged, receiver, &MediaRecorder::stateChanged);
QObject::connect(recorder, &T::statusChanged, receiver, &MediaRecorder::statusChanged);
QObject::connect(recorder, QOverload::of(&T::error), receiver, &MediaRecorder::errorChanged);
QObject::connect(recorder, &T::actualLocationChanged, receiver, &MediaRecorder::actualLocationChanged);
QObject::connect(recorder, &T::durationChanged, receiver, &MediaRecorder::durationChanged);
QObject::connect(recorder, &T::mutedChanged, receiver, &MediaRecorder::mutedChanged);
QObject::connect(recorder, &T::volumeChanged, receiver, &MediaRecorder::volumeChanged);
QObject::connect(recorder, QOverload::of(&T::availabilityChanged), receiver, &MediaRecorder::readyChanged);
QObject::connect(recorder, &T::statusChanged, receiver, &MediaRecorder::readyChanged);
}
template <>
void connectMediaRecorder(QAudioRecorder *recorder, MediaRecorder *receiver) {
connectMediaRecorder(qobject_cast(recorder), receiver);
}
template
static QStringList buildCodecList(const QStringList &codecs, F toString,
MediaRecorder *recorder, const QString &startsWith = QString()) {
QStringList entries(codecs);
if (!startsWith.isEmpty()) {
entries.erase(std::remove_if(entries.begin(), entries.end(), [&startsWith](const QString &entry) {
return !entry.startsWith(startsWith, Qt::CaseInsensitive);
}), entries.end());
}
std::sort(entries.begin(), entries.end(), [&toString, recorder](const QString &left, const QString &right) {
return toString(left, recorder).compare(toString(right, recorder), Qt::CaseInsensitive) < 0;
});
entries.prepend(QString());
return entries;
}
static QList buildResolutionList(const QList &resolutions) {
QList entries(resolutions);
std::sort(entries.begin(), entries.end(), [](const QSize &left, const QSize &right) {
return left.width() == right.width()
? left.height() < right.height()
: left.width() < right.width();
});
entries.prepend(QSize());
return entries;
}
static QList buildQualityList() {
const QList entries {
CommonEncoderSettings::EncodingQuality::VeryLowQuality,
CommonEncoderSettings::EncodingQuality::LowQuality,
CommonEncoderSettings::EncodingQuality::NormalQuality,
CommonEncoderSettings::EncodingQuality::HighQuality,
CommonEncoderSettings::EncodingQuality::VeryHighQuality
};
return entries;
}
static QList buildSampleRateList(const QList &sampleRates) {
QList entries(sampleRates);
if (entries.isEmpty()) {
entries = {
8000,
16000,
22050,
32000,
37800,
44100,
48000,
96000,
192000
};
}
std::sort(entries.begin(), entries.end(), [](const int left, const int right) {
return left < right;
});
entries.prepend(-1);
return entries;
}
static QList buildFrameRateList(const QList &frameRates) {
QList entries(frameRates);
std::sort(entries.begin(), entries.end(), [](const qreal left, const qreal right) {
return left < right;
});
entries.prepend(0.0);
return entries;
}
MediaRecorder::MediaRecorder(QObject *parent)
: QObject(parent)
, m_cameraModel(new CameraModel(this))
, m_audioDeviceModel(new AudioDeviceModel(this))
, m_containerModel(new MediaSettingsContainerModel(this, this))
, m_imageCodecModel(new MediaSettingsImageCodecModel(this, this))
, m_imageResolutionModel(new MediaSettingsResolutionModel(this, this))
, m_imageQualityModel(new MediaSettingsQualityModel(this, this))
, m_audioCodecModel(new MediaSettingsAudioCodecModel(this, this))
, m_audioSampleRateModel(new MediaSettingsAudioSampleRateModel(this, this))
, m_audioQualityModel(new MediaSettingsQualityModel(this, this))
, m_videoCodecModel(new MediaSettingsVideoCodecModel(this, this))
, m_videoResolutionModel(new MediaSettingsResolutionModel(this, this))
, m_videoFrameRateModel(new MediaSettingsVideoFrameRateModel(this, this))
, m_videoQualityModel(new MediaSettingsQualityModel(this, this))
{
connect(this, &MediaRecorder::readyChanged, this, [this]() {
if (!isReady()) {
if (m_type == MediaRecorder::Type::Invalid) {
m_cameraModel->setCurrentIndex(-1);
m_audioDeviceModel->setCurrentIndex(-1);
m_containerModel->clear();
m_imageCodecModel->clear();
m_imageResolutionModel->clear();
m_imageQualityModel->clear();
m_audioCodecModel->clear();
m_audioSampleRateModel->clear();
m_audioQualityModel->clear();
m_videoCodecModel->clear();
m_videoResolutionModel->clear();
m_videoFrameRateModel->clear();
m_videoQualityModel->clear();
}
return;
}
#if ENABLE_DEBUG
qDebug("syncProperties Begin");
#endif
switch (m_type) {
case MediaRecorder::Type::Invalid: {
Q_UNREACHABLE();
break;
}
case MediaRecorder::Type::Image: {
m_cameraModel->setCurrentCamera(m_mediaSettings.camera);
m_imageCodecModel->setValuesAndCurrentValue(buildCodecList(m_imageCapturer->supportedImageCodecs(), imageEncoderCodec, this),
m_imageEncoderSettings.codec);
m_imageResolutionModel->setValuesAndCurrentValue(buildResolutionList(m_imageCapturer->supportedResolutions()),
m_imageEncoderSettings.resolution);
m_imageQualityModel->setValuesAndCurrentValue(buildQualityList(),
m_imageEncoderSettings.quality);
break;
}
case MediaRecorder::Type::Audio: {
m_audioDeviceModel->setCurrentAudioDevice(m_mediaSettings.audioInputDevice);
m_containerModel->setValuesAndCurrentValue(buildCodecList(m_audioRecorder->supportedContainers(), encoderContainer, this, QStringLiteral("audio")),
m_mediaSettings.container);
m_audioCodecModel->setValuesAndCurrentValue(buildCodecList(m_audioRecorder->supportedAudioCodecs(), audioEncoderCodec, this),
m_audioEncoderSettings.codec);
m_audioSampleRateModel->setValuesAndCurrentValue(buildSampleRateList(m_audioRecorder->supportedAudioSampleRates()),
m_audioEncoderSettings.sampleRate);
m_audioQualityModel->setValuesAndCurrentValue(buildQualityList(),
m_audioEncoderSettings.quality);
break;
}
case MediaRecorder::Type::Video: {
m_cameraModel->setCurrentCamera(m_mediaSettings.camera);
m_containerModel->setValuesAndCurrentValue(buildCodecList(m_videoRecorder->supportedContainers(), encoderContainer, this, QStringLiteral("video")),
m_mediaSettings.container);
m_audioCodecModel->setValuesAndCurrentValue(buildCodecList(m_videoRecorder->supportedAudioCodecs(), audioEncoderCodec, this),
m_audioEncoderSettings.codec);
m_audioSampleRateModel->setValuesAndCurrentValue(buildSampleRateList(m_videoRecorder->supportedAudioSampleRates()),
m_audioEncoderSettings.sampleRate);
m_audioQualityModel->setValuesAndCurrentValue(buildQualityList(),
m_audioEncoderSettings.quality);
m_videoCodecModel->setValuesAndCurrentValue(buildCodecList(m_videoRecorder->supportedVideoCodecs(), videoEncoderCodec, this),
m_videoEncoderSettings.codec);
m_videoResolutionModel->setValuesAndCurrentValue(buildResolutionList(m_videoRecorder->supportedResolutions()),
m_videoEncoderSettings.resolution);
m_videoFrameRateModel->setValuesAndCurrentValue(buildFrameRateList(m_videoRecorder->supportedFrameRates()),
m_videoEncoderSettings.frameRate);
m_videoQualityModel->setValuesAndCurrentValue(buildQualityList(),
m_videoEncoderSettings.quality);
break;
}
}
#if ENABLE_DEBUG
qDebug("syncProperties End");
#endif
});
}
MediaRecorder::~MediaRecorder()
{
if (isAvailable() && !isReady()) {
cancel();
}
}
MediaRecorder::Type MediaRecorder::type() const
{
return m_type;
}
void MediaRecorder::setType(MediaRecorder::Type type)
{
if (m_type == type) {
return;
}
setupRecorder(type);
}
QMediaObject *MediaRecorder::mediaObject() const
{
switch (m_type) {
case MediaRecorder::Type::Image:
case MediaRecorder::Type::Video:
return m_camera;
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Audio:
break;
}
return nullptr;
}
QString MediaRecorder::currentSettingsKey() const
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
break;
case MediaRecorder::Type::Image:
Q_ASSERT(!m_mediaSettings.camera.isNull());
return settingsKey(m_type, m_mediaSettings.camera.deviceName());
case MediaRecorder::Type::Audio:
Q_ASSERT(!m_mediaSettings.audioInputDevice.isNull());
return settingsKey(m_type, m_mediaSettings.audioInputDevice.deviceName());
case MediaRecorder::Type::Video:
Q_ASSERT(!m_mediaSettings.camera.isNull());
return settingsKey(m_type, m_mediaSettings.camera.deviceName());
}
return { };
}
MediaRecorder::AvailabilityStatus MediaRecorder::availabilityStatus() const
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
break;
case MediaRecorder::Type::Image:
return static_cast(m_imageCapturer->availability());
case MediaRecorder::Type::Audio:
return static_cast(m_audioRecorder->availability());
case MediaRecorder::Type::Video:
return static_cast(m_videoRecorder->availability());
}
return MediaRecorder::AvailabilityStatus::ServiceMissing;
}
MediaRecorder::State MediaRecorder::state() const
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Image:
break;
case MediaRecorder::Type::Audio:
return static_cast(m_audioRecorder->state());
case MediaRecorder::Type::Video:
return static_cast(m_videoRecorder->state());
}
return MediaRecorder::State::StoppedState;
}
MediaRecorder::Status MediaRecorder::status() const
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Image:
break;
case MediaRecorder::Type::Audio:
return static_cast(m_audioRecorder->status());
case MediaRecorder::Type::Video:
return static_cast(m_videoRecorder->status());
}
return MediaRecorder::Status::UnavailableStatus;
}
bool MediaRecorder::isAvailable() const
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
break;
case MediaRecorder::Type::Image:
return m_imageCapturer->isAvailable();
case MediaRecorder::Type::Audio:
return m_audioRecorder->isAvailable();
case MediaRecorder::Type::Video:
return m_videoRecorder->isAvailable();
}
return false;
}
bool MediaRecorder::isReady() const
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
break;
case MediaRecorder::Type::Image:
return m_imageCapturer->isReadyForCapture();
case MediaRecorder::Type::Audio:
return isAvailable()
&& status() >= MediaRecorder::Status::UnloadedStatus
&& status() <= MediaRecorder::Status::LoadedStatus;
case MediaRecorder::Type::Video:
return isAvailable()
&& m_camera->status() == QCamera::Status::ActiveStatus
&& status() >= MediaRecorder::Status::UnloadedStatus
&& status() <= MediaRecorder::Status::LoadedStatus;
}
return false;
}
MediaRecorder::Error MediaRecorder::error() const
{
static const QMap capturerMapping = {
{ QCameraImageCapture::Error::NoError, MediaRecorder::Error::NoError },
{ QCameraImageCapture::Error::NotReadyError, MediaRecorder::Error::NotReadyError },
{ QCameraImageCapture::Error::ResourceError, MediaRecorder::Error::ResourceError },
{ QCameraImageCapture::Error::OutOfSpaceError, MediaRecorder::Error::OutOfSpaceError },
{ QCameraImageCapture::Error::NotSupportedFeatureError, MediaRecorder::Error::NotSupportedFeatureError },
{ QCameraImageCapture::Error::FormatError, MediaRecorder::Error::FormatError }
};
static const QMap recorderMapping = {
{ QMediaRecorder::Error::NoError, MediaRecorder::Error::NoError },
{ QMediaRecorder::Error::ResourceError, MediaRecorder::Error::ResourceError },
{ QMediaRecorder::Error::FormatError, MediaRecorder::Error::FormatError },
{ QMediaRecorder::Error::OutOfSpaceError, MediaRecorder::Error::OutOfSpaceError }
};
switch (m_type) {
case MediaRecorder::Type::Invalid:
break;
case MediaRecorder::Type::Image: {
const auto it = capturerMapping.constFind(m_imageCapturer->error());
Q_ASSERT(it != capturerMapping.constEnd());
return it.value();
}
case MediaRecorder::Type::Audio: {
const auto it = recorderMapping.constFind(m_audioRecorder->error());
Q_ASSERT(it != recorderMapping.constEnd());
return it.value();
}
case MediaRecorder::Type::Video: {
const auto it = recorderMapping.constFind(m_videoRecorder->error());
Q_ASSERT(it != recorderMapping.constEnd());
return it.value();
}
}
return MediaRecorder::Error::NoError;
}
QString MediaRecorder::errorString() const
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
break;
case MediaRecorder::Type::Image:
return m_imageCapturer->errorString();
case MediaRecorder::Type::Audio:
return m_audioRecorder->errorString();
case MediaRecorder::Type::Video:
return m_videoRecorder->errorString();
}
return { };
}
QUrl MediaRecorder::actualLocation() const
{
static const auto urlExists = [](const QUrl &url) {
if (url.isEmpty() || (!url.scheme().isEmpty() && !url.isLocalFile())) {
return url;
}
const QUrl u(url.isLocalFile() ? url : QUrl::fromLocalFile(url.toString()));
return QFile::exists(u.toLocalFile()) ? u : QUrl();
};
switch (m_type) {
case MediaRecorder::Type::Invalid:
break;
case MediaRecorder::Type::Image:
return urlExists(m_imageCapturer->actualLocation());
case MediaRecorder::Type::Audio:
return urlExists(m_audioRecorder->actualLocation());
case MediaRecorder::Type::Video:
return urlExists(m_videoRecorder->actualLocation());
}
return { };
}
qint64 MediaRecorder::duration() const
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Image:
break;
case MediaRecorder::Type::Audio:
return m_audioRecorder->duration();
case MediaRecorder::Type::Video:
return m_videoRecorder->duration();
}
return 0;
}
bool MediaRecorder::isMuted() const
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Image:
break;
case MediaRecorder::Type::Audio:
return m_audioRecorder->isMuted();
case MediaRecorder::Type::Video:
return m_videoRecorder->isMuted();
}
return false;
}
void MediaRecorder::setMuted(bool muted)
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Image:
Q_UNREACHABLE();
break;
case MediaRecorder::Type::Audio:
m_audioRecorder->setMuted(muted);
break;
case MediaRecorder::Type::Video:
m_videoRecorder->setMuted(muted);
break;
}
}
qreal MediaRecorder::volume() const
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Image:
break;
case MediaRecorder::Type::Audio:
return m_audioRecorder->volume();
case MediaRecorder::Type::Video:
return m_videoRecorder->volume();
}
return false;
}
void MediaRecorder::setVolume(qreal volume)
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Image:
Q_UNREACHABLE();
break;
case MediaRecorder::Type::Audio:
m_audioRecorder->setVolume(volume);
break;
case MediaRecorder::Type::Video:
m_videoRecorder->setVolume(volume);
break;
}
}
MediaSettings MediaRecorder::mediaSettings() const
{
return m_mediaSettings;
}
void MediaRecorder::setMediaSettings(const MediaSettings &settings)
{
#if ENABLE_DEBUG
qDebug(Q_FUNC_INFO);
#endif
if (settings == m_mediaSettings) {
return;
}
const auto oldSettings = m_mediaSettings;
#if ENABLE_DEBUG
settings.dumpProperties(QStringLiteral("New"));
oldSettings.dumpProperties(QStringLiteral("Old"));
#endif
m_mediaSettings = settings;
if (!m_initializing) {
switch (m_type) {
case MediaRecorder::Type::Invalid:
Q_UNREACHABLE();
break;
case MediaRecorder::Type::Image:
if (oldSettings.camera != settings.camera) {
setupRecorder(m_type);
}
break;
case MediaRecorder::Type::Audio:
if (oldSettings.audioInputDevice != settings.audioInputDevice) {
setupRecorder(m_type);
} else {
m_audioRecorder->setContainerFormat(m_mediaSettings.container);
}
break;
case MediaRecorder::Type::Video:
if (oldSettings.camera != settings.camera) {
setupRecorder(m_type);
} else {
m_videoRecorder->setContainerFormat(m_mediaSettings.container);
}
break;
}
emit mediaSettingsChanged();
}
}
ImageEncoderSettings MediaRecorder::imageEncoderSettings() const
{
return m_imageEncoderSettings;
}
void MediaRecorder::setImageEncoderSettings(const ImageEncoderSettings &settings)
{
#if ENABLE_DEBUG
qDebug(Q_FUNC_INFO);
#endif
switch (m_type) {
case MediaRecorder::Type::Image: {
if (settings != m_imageEncoderSettings) {
#if ENABLE_DEBUG
settings.dumpProperties(QStringLiteral("New"));
m_imageEncoderSettings.dumpProperties(QStringLiteral("Old"));
#endif
m_imageEncoderSettings = settings;
if (!m_initializing) {
m_imageCapturer->setEncodingSettings(m_imageEncoderSettings.toQImageEncoderSettings());
emit imageEncoderSettingsChanged();
}
}
break;
}
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Audio:
case MediaRecorder::Type::Video:
Q_UNREACHABLE();
break;
}
}
AudioEncoderSettings MediaRecorder::audioEncoderSettings() const
{
return m_audioEncoderSettings;
}
void MediaRecorder::setAudioEncoderSettings(const AudioEncoderSettings &settings)
{
#if ENABLE_DEBUG
qDebug(Q_FUNC_INFO);
#endif
switch (m_type) {
case MediaRecorder::Type::Audio:
case MediaRecorder::Type::Video: {
if (settings != m_audioEncoderSettings) {
#if ENABLE_DEBUG
settings.dumpProperties(QStringLiteral("New"));
m_audioEncoderSettings.dumpProperties(QStringLiteral("Old"));
#endif
m_audioEncoderSettings = settings;
if (!m_initializing) {
if (m_audioRecorder) {
m_audioRecorder->setAudioSettings(m_audioEncoderSettings.toQAudioEncoderSettings());
} else if (m_videoRecorder) {
m_videoRecorder->setAudioSettings(m_audioEncoderSettings.toQAudioEncoderSettings());
}
emit audioEncoderSettingsChanged();
// Changing audio settings does not trigger ready changed.
if (m_type == MediaRecorder::Type::Audio) {
emit readyChanged();
}
}
}
break;
}
case MediaRecorder::Type::Image:
case MediaRecorder::Type::Invalid:
Q_UNREACHABLE();
break;
}
}
VideoEncoderSettings MediaRecorder::videoEncoderSettings() const
{
return m_videoEncoderSettings;
}
void MediaRecorder::setVideoEncoderSettings(const VideoEncoderSettings &settings)
{
#if ENABLE_DEBUG
qDebug(Q_FUNC_INFO);
#endif
switch (m_type) {
case MediaRecorder::Type::Video: {
if (settings != m_videoEncoderSettings) {
#if ENABLE_DEBUG
settings.dumpProperties(QStringLiteral("New"));
m_videoEncoderSettings.dumpProperties(QStringLiteral("Old"));
#endif
m_videoEncoderSettings = settings;
if (!m_initializing) {
m_videoRecorder->setVideoSettings(m_videoEncoderSettings.toQVideoEncoderSettings());
emit videoEncoderSettingsChanged();
}
}
break;
}
case MediaRecorder::Type::Image:
case MediaRecorder::Type::Audio:
case MediaRecorder::Type::Invalid:
Q_UNREACHABLE();
break;
}
}
void MediaRecorder::resetSettingsToDefaults()
{
#if ENABLE_DEBUG
qDebug(Q_FUNC_INFO);
#endif
switch (m_type) {
case MediaRecorder::Type::Invalid:
Q_UNREACHABLE();
break;
case MediaRecorder::Type::Image:
setMediaSettings(MediaSettings());
setImageEncoderSettings(ImageEncoderSettings());
break;
case MediaRecorder::Type::Audio:
setMediaSettings(MediaSettings());
setAudioEncoderSettings(AudioEncoderSettings());
break;
case MediaRecorder::Type::Video:
setMediaSettings(MediaSettings());
setAudioEncoderSettings(AudioEncoderSettings());
setVideoEncoderSettings(VideoEncoderSettings());
break;
}
}
void MediaRecorder::resetUserSettings()
{
resetSettings(userDefaultCamera(), userDefaultAudioInput());
}
void MediaRecorder::saveUserSettings()
{
#if ENABLE_DEBUG
qDebug(Q_FUNC_INFO);
#endif
switch (m_type) {
case MediaRecorder::Type::Invalid:
Q_UNREACHABLE();
break;
case MediaRecorder::Type::Image: {
QSettings &settings(*Kaidan::instance()->getSettings());
settings.beginGroup(settingsKey(m_type, SETTING_USER_DEFAULT));
settings.setValue(SETTING_DEFAULT_CAMERA_DEVICE_NAME, m_mediaSettings.camera.deviceName());
settings.endGroup();
- settings.beginGroup(currentSettingsKey());
- m_mediaSettings.saveSettings(&settings);
- m_imageEncoderSettings.saveSettings(&settings);
- settings.endGroup();
+ if (!m_mediaSettings.camera.isNull() || !m_mediaSettings.audioInputDevice.isNull()) {
+ settings.beginGroup(currentSettingsKey());
+ m_mediaSettings.saveSettings(&settings);
+ m_imageEncoderSettings.saveSettings(&settings);
+ settings.endGroup();
+ }
break;
}
case MediaRecorder::Type::Audio: {
QSettings &settings(*Kaidan::instance()->getSettings());
settings.beginGroup(settingsKey(m_type, SETTING_USER_DEFAULT));
settings.setValue(SETTING_DEFAULT_AUDIO_INPUT_DEVICE_NAME, m_mediaSettings.audioInputDevice.deviceName());
settings.endGroup();
settings.beginGroup(currentSettingsKey());
m_mediaSettings.saveSettings(&settings);
m_audioEncoderSettings.saveSettings(&settings);
settings.endGroup();
break;
}
case MediaRecorder::Type::Video: {
QSettings &settings(*Kaidan::instance()->getSettings());
settings.beginGroup(settingsKey(m_type, SETTING_USER_DEFAULT));
settings.setValue(SETTING_DEFAULT_CAMERA_DEVICE_NAME, m_mediaSettings.camera.deviceName());
settings.endGroup();
settings.beginGroup(currentSettingsKey());
m_mediaSettings.saveSettings(&settings);
m_audioEncoderSettings.saveSettings(&settings);
m_videoEncoderSettings.saveSettings(&settings);
settings.endGroup();
break;
}
}
}
void MediaRecorder::record()
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
Q_UNREACHABLE();
break;
case MediaRecorder::Type::Image:
#if ENABLE_DEBUG
m_mediaSettings.dumpProperties("Capture");
m_imageEncoderSettings.dumpProperties("Capture");
#endif
m_imageCapturer->capture();
break;
case MediaRecorder::Type::Audio:
#if ENABLE_DEBUG
m_mediaSettings.dumpProperties("Capture");
m_audioEncoderSettings.dumpProperties("Capture");
#endif
m_audioRecorder->record();
break;
case MediaRecorder::Type::Video:
#if ENABLE_DEBUG
m_mediaSettings.dumpProperties("Capture");
m_audioEncoderSettings.dumpProperties("Capture");
m_videoEncoderSettings.dumpProperties("Capture");
#endif
m_videoRecorder->record();
break;
}
}
void MediaRecorder::pause()
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Image:
Q_UNREACHABLE();
break;
case MediaRecorder::Type::Audio:
m_audioRecorder->pause();
break;
case MediaRecorder::Type::Video:
m_videoRecorder->pause();
break;
}
}
void MediaRecorder::stop()
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Image:
Q_UNREACHABLE();
break;
case MediaRecorder::Type::Audio:
m_audioRecorder->stop();
break;
case MediaRecorder::Type::Video:
m_videoRecorder->stop();
break;
}
}
void MediaRecorder::cancel()
{
switch (m_type) {
case MediaRecorder::Type::Invalid:
Q_UNREACHABLE();
break;
case MediaRecorder::Type::Image:
m_imageCapturer->cancelCapture();
deleteActualLocation();
break;
case MediaRecorder::Type::Audio:
case MediaRecorder::Type::Video: {
if (state() == MediaRecorder::State::RecordingState) {
stop();
}
deleteActualLocation();
break;
}
}
}
bool MediaRecorder::setMediaObject(QMediaObject *object)
{
Q_UNUSED(object);
return false;
}
QString MediaRecorder::settingsKey(MediaRecorder::Type type, const QString &deviceName) const
{
Q_ASSERT(type != MediaRecorder::Type::Invalid);
const QString deviceKey = QString(deviceName)
.replace(QLatin1Char('/'), QLatin1Char(' '))
.replace(QLatin1Char('\\'), QLatin1Char(' '))
.replace(QLatin1Char('('), QLatin1Char(' '))
.replace(QLatin1Char(')'), QLatin1Char(' '))
.replace(QLatin1Char('='), QLatin1Char(' '))
.replace(QLatin1Char(':'), QLatin1Char(' '))
.replace(QLatin1Char('\n'), QLatin1Char(' '))
.simplified();
return QString::fromLatin1("Multimedia/%1/%2").arg(Enums::toString(type), deviceKey);
}
CameraInfo MediaRecorder::userDefaultCamera() const
{
if (m_type == MediaRecorder::Type::Invalid) {
return CameraInfo();
}
QSettings &settings(*Kaidan::instance()->getSettings());
CameraInfo cameraInfo = m_cameraModel->defaultCamera();
settings.beginGroup(settingsKey(m_type, SETTING_USER_DEFAULT));
if (settings.contains(SETTING_DEFAULT_CAMERA_DEVICE_NAME)) {
const CameraInfo userCamera(settings.value(SETTING_DEFAULT_CAMERA_DEVICE_NAME).toString());
if (!userCamera.isNull()) {
cameraInfo = userCamera;
}
}
settings.endGroup();
return cameraInfo;
}
AudioDeviceInfo MediaRecorder::userDefaultAudioInput() const
{
if (m_type == MediaRecorder::Type::Invalid) {
return AudioDeviceInfo();
}
QSettings &settings(*Kaidan::instance()->getSettings());
AudioDeviceInfo audioInput = m_audioDeviceModel->defaultAudioInputDevice();
settings.beginGroup(settingsKey(m_type, SETTING_USER_DEFAULT));
if (settings.contains(SETTING_DEFAULT_AUDIO_INPUT_DEVICE_NAME)) {
const AudioDeviceInfo userAudioInput(AudioDeviceModel::audioInputDevice(settings.value(SETTING_DEFAULT_AUDIO_INPUT_DEVICE_NAME).toString()));
if (!userAudioInput.isNull()) {
audioInput = userAudioInput;
}
}
settings.endGroup();
return audioInput;
}
void MediaRecorder::resetSettings(const CameraInfo &camera, const AudioDeviceInfo &audioInput)
{
#if ENABLE_DEBUG
qDebug(Q_FUNC_INFO);
#endif
switch (m_type) {
case MediaRecorder::Type::Invalid:
m_mediaSettings = MediaSettings();
m_imageEncoderSettings = ImageEncoderSettings();
m_audioEncoderSettings = AudioEncoderSettings();
m_videoEncoderSettings = VideoEncoderSettings();
break;
case MediaRecorder::Type::Image: {
QSettings &settings(*Kaidan::instance()->getSettings());
MediaSettings mediaSettings(camera, AudioDeviceInfo());
ImageEncoderSettings imageSettings;
settings.beginGroup(settingsKey(m_type, mediaSettings.camera.deviceName()));
mediaSettings.loadSettings(&settings);
imageSettings.loadSettings(&settings);
settings.endGroup();
setMediaSettings(mediaSettings);
setImageEncoderSettings(imageSettings);
break;
}
case MediaRecorder::Type::Audio: {
QSettings &settings(*Kaidan::instance()->getSettings());
MediaSettings mediaSettings(CameraInfo(), audioInput);
AudioEncoderSettings audioSettings;
settings.beginGroup(settingsKey(m_type, mediaSettings.audioInputDevice.deviceName()));
mediaSettings.loadSettings(&settings);
audioSettings.loadSettings(&settings);
settings.endGroup();
setMediaSettings(mediaSettings);
setAudioEncoderSettings(audioSettings);
break;
}
case MediaRecorder::Type::Video: {
QSettings &settings(*Kaidan::instance()->getSettings());
MediaSettings mediaSettings(camera, AudioDeviceInfo());
AudioEncoderSettings audioSettings;
VideoEncoderSettings videoSettings;
settings.beginGroup(settingsKey(m_type, mediaSettings.camera.deviceName()));
mediaSettings.loadSettings(&settings);
audioSettings.loadSettings(&settings);
videoSettings.loadSettings(&settings);
settings.endGroup();
setMediaSettings(mediaSettings);
setAudioEncoderSettings(audioSettings);
setVideoEncoderSettings(videoSettings);
break;
}
}
}
void MediaRecorder::setupCamera()
{
// If there is no camera, there is no need to work any further
if (m_mediaSettings.camera.isNull())
return;
m_camera = new QCamera(m_mediaSettings.camera, this);
switch (m_type) {
case MediaRecorder::Type::Invalid:
case MediaRecorder::Type::Audio:
Q_UNREACHABLE();
break;
case MediaRecorder::Type::Image:
m_camera->setCaptureMode(QCamera::CaptureMode::CaptureStillImage);
m_camera->imageProcessing()->setWhiteBalanceMode(QCameraImageProcessing::WhiteBalanceMode::WhiteBalanceFlash);
m_camera->exposure()->setExposureCompensation(-1.0);
m_camera->exposure()->setExposureMode(QCameraExposure::ExposureMode::ExposurePortrait);
m_camera->exposure()->setFlashMode(QCameraExposure::FlashMode::FlashRedEyeReduction);
break;
case MediaRecorder::Type::Video:
m_camera->setCaptureMode(QCamera::CaptureMode::CaptureVideo);
m_camera->imageProcessing()->setWhiteBalanceMode(QCameraImageProcessing::WhiteBalanceMode::WhiteBalanceAuto);
m_camera->exposure()->setExposureCompensation(0);
m_camera->exposure()->setExposureMode(QCameraExposure::ExposureMode::ExposureAuto);
m_camera->exposure()->setFlashMode(QCameraExposure::FlashMode::FlashOff);
break;
}
connectCamera(m_camera, this);
}
void MediaRecorder::setupCapturer()
{
if (m_camera)
m_imageCapturer = new CameraImageCapture(m_camera, this);
else
m_imageCapturer = new CameraImageCapture({});
m_imageCapturer->setEncodingSettings(m_imageEncoderSettings.toQImageEncoderSettings());
connectImageCapturer(m_imageCapturer, this);
}
void MediaRecorder::setupAudioRecorder()
{
m_audioRecorder = new QAudioRecorder(this);
m_audioRecorder->setAudioInput(m_mediaSettings.audioInputDevice.deviceName());
m_audioRecorder->setContainerFormat(m_mediaSettings.container);
m_audioRecorder->setAudioSettings(m_audioEncoderSettings.toQAudioEncoderSettings());
connectMediaRecorder(m_audioRecorder, this);
}
void MediaRecorder::setupVideoRecorder()
{
if (m_camera)
m_videoRecorder = new QMediaRecorder(m_camera, this);
else
m_videoRecorder = new QMediaRecorder({}, this);
m_videoRecorder->setContainerFormat(m_mediaSettings.container);
m_videoRecorder->setAudioSettings(m_audioEncoderSettings.toQAudioEncoderSettings());
m_videoRecorder->setVideoSettings(m_videoEncoderSettings.toQVideoEncoderSettings());
connectMediaRecorder(m_videoRecorder, this);
}
void MediaRecorder::setupRecorder(MediaRecorder::Type type)
{
if (isAvailable() && !isReady()) {
cancel();
}
delete m_imageCapturer; m_imageCapturer = nullptr;
delete m_audioRecorder; m_audioRecorder = nullptr;
delete m_videoRecorder; m_videoRecorder = nullptr;
delete m_camera; m_camera = nullptr;
if (m_type != type) {
m_type = type;
m_initializing = true;
resetUserSettings();
m_initializing = false;
} else {
m_initializing = true;
resetSettings(m_mediaSettings.camera, m_mediaSettings.audioInputDevice);
m_initializing = false;
}
switch (m_type) {
case MediaRecorder::Type::Invalid:
emit imageEncoderSettingsChanged();
emit audioEncoderSettingsChanged();
emit videoEncoderSettingsChanged();
break;
case MediaRecorder::Type::Image:
setupCamera();
setupCapturer();
break;
case MediaRecorder::Type::Audio:
setupAudioRecorder();
break;
case MediaRecorder::Type::Video:
setupCamera();
setupVideoRecorder();
break;
}
emit typeChanged();
emit availabilityStatusChanged();
emit stateChanged();
emit statusChanged();
emit readyChanged();
emit errorChanged();
emit actualLocationChanged();
emit durationChanged();
emit mutedChanged();
emit volumeChanged();
if (m_camera && m_camera->state() != QCamera::State::ActiveState) {
m_camera->start();
}
}
void MediaRecorder::deleteActualLocation()
{
const QUrl url(actualLocation());
if (!url.isEmpty() && url.isLocalFile()) {
const QString filePath(url.toLocalFile());
QFile file(filePath);
if (file.exists()) {
if (file.remove()) {
emit actualLocationChanged();
} else {
qWarning("Can not delete record '%s'", qUtf8Printable(filePath));
}
}
}
}
QString MediaRecorder::encoderContainer(const QString &container, const void *userData)
{
const auto *recorder = static_cast(userData);
const auto *mediaRecorder = recorder->m_audioRecorder ? recorder->m_audioRecorder : recorder->m_videoRecorder;
return container.isEmpty()
? tr("Default")
: QString::fromLatin1("%1 (%2)").arg(mediaRecorder->containerDescription(container), container);
}
QString MediaRecorder::encoderResolution(const QSize &size, const void *userData)
{
Q_UNUSED(userData);
return size.isEmpty()
? tr("Default")
: QString::fromLatin1("%1x%2").arg(QString::number(size.width()), QString::number(size.height()));
}
QString MediaRecorder::encoderQuality(const CommonEncoderSettings::EncodingQuality quality, const void *userData)
{
Q_UNUSED(userData);
return CommonEncoderSettings::toString(quality);
}
QString MediaRecorder::imageEncoderCodec(const QString &codec, const void *userData)
{
const auto *recorder = static_cast(userData);
const auto *capturer = recorder->m_imageCapturer;
return codec.isEmpty()
? tr("Default")
: QString::fromLatin1("%1 (%2)").arg(capturer->imageCodecDescription(codec), codec);
}
QString MediaRecorder::audioEncoderCodec(const QString &codec, const void *userData)
{
const auto *recorder = static_cast(userData);
const auto *mediaRecorder = recorder->m_audioRecorder ? recorder->m_audioRecorder : recorder->m_videoRecorder;
return codec.isEmpty()
? tr("Default")
: QString::fromLatin1("%1 (%2)").arg(mediaRecorder->audioCodecDescription(codec), codec);
}
QString MediaRecorder::audioEncoderSampleRate(const int sampleRate, const void *userData)
{
Q_UNUSED(userData);
return sampleRate == -1
? tr("Default")
: QString::fromLatin1("%1 Hz").arg(sampleRate);
}
QString MediaRecorder::videoEncoderCodec(const QString &codec, const void *userData)
{
const auto *recorder = static_cast(userData);
const auto *videoRecorder = recorder->m_videoRecorder;
return codec.isEmpty()
? tr("Default")
: QString::fromLatin1("%1 (%2)").arg(videoRecorder->videoCodecDescription(codec), codec);
}
QString MediaRecorder::videoEncoderFrameRate(const qreal frameRate, const void *userData)
{
Q_UNUSED(userData);
return qIsNull(frameRate)
? tr("Default")
: QString::fromLatin1("%1 FPS").arg(frameRate);
}