File indexing completed on 2024-05-12 17:21:22

0001 /*
0002  * SPDX-FileCopyrightText: 2020 Jonah BrĂ¼chert <jbb@kaidan.im>
0003  * SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
0004  *
0005  * SPDX-License-Identifier: GPL-3.0-or-later
0006  */
0007 
0008 #include "audiorecorder.h"
0009 
0010 #include <QAudioDevice>
0011 #include <QCoreApplication>
0012 #include <QFileInfo>
0013 #include <QMediaDevices>
0014 #include <QMediaFormat>
0015 #include <QStandardPaths>
0016 #include <QQmlEngine>
0017 
0018 #include <KLocalizedString>
0019 
0020 #if QT_CONFIG(permissions)
0021 #include <QPermissions>
0022 #endif
0023 
0024 #include "recordingmodel.h"
0025 #include "settingsmodel.h"
0026 
0027 #include <QDebug>
0028 
0029 AudioRecorder *AudioRecorder::instance()
0030 {
0031     static AudioRecorder *s_audioRecorder = new AudioRecorder(qApp);
0032     return s_audioRecorder;
0033 }
0034 
0035 AudioRecorder::AudioRecorder(QObject *parent) : QMediaRecorder(parent)
0036 {
0037 #if QT_CONFIG(permissions)
0038     QMicrophonePermission microphonePermission;
0039     switch (qApp->checkPermission(microphonePermission)) {
0040     case Qt::PermissionStatus::Undetermined:
0041         qApp->requestPermission(microphonePermission, this, &AudioRecorder::instance);
0042         return;
0043     case Qt::PermissionStatus::Denied:
0044         qWarning("Microphone permission is not granted!");
0045         return;
0046     case Qt::PermissionStatus::Granted:
0047         break;
0048     }
0049 #endif
0050 
0051     m_mediaFormat = new QMediaFormat();
0052 
0053     updateFormats();
0054 
0055     setQuality(QMediaRecorder::HighQuality);
0056     setEncodingMode(QMediaRecorder::ConstantQualityEncoding);
0057 
0058     m_audioInput = new QAudioInput(QMediaDevices::defaultAudioInput(), this);
0059     m_mediaCaptureSession = new QMediaCaptureSession(this);
0060     m_mediaCaptureSession->setAudioInput(m_audioInput);
0061     m_mediaCaptureSession->setRecorder(this);
0062 
0063     m_audioProbe = new AudioProber(parent, this);
0064     //m_audioProbe->setSource(actualLocation());
0065 
0066     QQmlEngine::setObjectOwnership(m_audioProbe, QQmlEngine::CppOwnership);
0067 
0068     // once the file is done writing, save recording to model
0069     connect(this, &QMediaRecorder::recorderStateChanged, this, &AudioRecorder::handleStateChange);
0070 
0071     setAudioCodec(SettingsModel::instance()->audioCodec());
0072     setContainerFormat(SettingsModel::instance()->containerFormat());
0073     setAudioQuality(SettingsModel::instance()->audioQuality());
0074     // setAudioBitRate(0);
0075     // setAudioChannelCount(-1);
0076 }
0077 
0078 AudioProber *AudioRecorder::prober()
0079 {
0080     return m_audioProbe;
0081 }
0082 
0083 QString AudioRecorder::audioInput()
0084 {
0085     if (!m_audioInput) {
0086         return {};
0087     }
0088     return QString::fromUtf8(m_audioInput->device().id());
0089 }
0090 
0091 void AudioRecorder::setAudioInput(QAudioDevice device)
0092 {
0093     if (m_audioInput) {
0094         m_audioInput->deleteLater();
0095     }
0096     m_audioInput = new QAudioInput(device, this);
0097     if (m_mediaCaptureSession) {
0098         m_mediaCaptureSession->setAudioInput(m_audioInput);
0099     }
0100 
0101     Q_EMIT audioInputChanged();
0102 }
0103 
0104 QString AudioRecorder::audioCodec()
0105 {
0106     return QVariant::fromValue(m_mediaFormat->audioCodec()).toString();
0107 }
0108 
0109 void AudioRecorder::setAudioCodec(const QString &codec)
0110 {
0111     auto audioCodec = QVariant(codec).value<QMediaFormat::AudioCodec>();
0112     qDebug() << Q_FUNC_INFO << audioCodec;
0113     m_mediaFormat->setAudioCodec(audioCodec);
0114     setMediaFormat(*m_mediaFormat);
0115     Q_EMIT audioCodecChanged();
0116 }
0117 
0118 int AudioRecorder::audioQuality()
0119 {
0120     return quality();
0121 }
0122 
0123 void AudioRecorder::setAudioQuality(int quality)
0124 {
0125     setQuality(QVariant(quality).value<QMediaRecorder::Quality>());
0126     Q_EMIT audioQualityChanged();
0127 }
0128 
0129 QString AudioRecorder::storageFolder() const
0130 {
0131     return QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
0132 }
0133 
0134 void AudioRecorder::reset()
0135 {
0136     m_resetRequest = true;
0137     stop();
0138 }
0139 
0140 void AudioRecorder::handleStateChange(RecorderState state)
0141 {
0142     if (state == QMediaRecorder::StoppedState) {
0143         if (m_resetRequest) {
0144             // reset
0145             m_resetRequest = false;
0146             QFile(actualLocation().toString()).remove();
0147             qDebug() << "Discarded recording " << actualLocation().toString();
0148             m_recordingName = QString();
0149             
0150         } else {
0151             // rename file to desired file name
0152             renameCurrentRecording();
0153             // create recording
0154             saveRecording();
0155         }
0156         
0157     } else if (state == QMediaRecorder::PausedState) {
0158         m_cachedDuration = duration();
0159     }
0160 }
0161 
0162 void AudioRecorder::updateFormats(QMediaFormat::FileFormat fileFormat, QMediaFormat::AudioCodec audioCodec) {
0163     if (m_updatingFormats)
0164         return;
0165     m_updatingFormats = true;
0166 
0167     m_mediaFormat->setFileFormat(fileFormat);
0168     m_mediaFormat->setAudioCodec(audioCodec);
0169 
0170     m_supportedContainers.append(i18n("Default file format"));
0171     for (auto container : m_mediaFormat->supportedFileFormats(QMediaFormat::Encode)) {
0172         if (container < QMediaFormat::Mpeg4Audio) // Skip video formats
0173             continue;
0174         m_supportedContainers.append(QMediaFormat::fileFormatDescription(container));
0175     }
0176 
0177     setMediaFormat(*m_mediaFormat);
0178 
0179     m_supportedAudioCodecs.append(i18n("Default audio codec"));
0180     for (auto codec : m_mediaFormat->supportedAudioCodecs(QMediaFormat::Encode)) {
0181         m_supportedAudioCodecs.append(QMediaFormat::audioCodecDescription(codec));
0182     }
0183 
0184     m_updatingFormats = false;
0185 }
0186 
0187 void AudioRecorder::renameCurrentRecording()
0188 {
0189     if (!m_recordingName.isEmpty()) {
0190         
0191         // determine new file name
0192         QStringList spl = actualLocation().fileName().split(QStringLiteral("."));
0193         QString suffix = spl.size() > 0 ? QStringLiteral(".") + spl[spl.size()-1] : QString();
0194         QString path = storageFolder() + QStringLiteral("/") + m_recordingName;
0195         QString updatedPath = path + suffix;
0196         
0197         // ignore if the file destination is the same as the one currently being written to
0198         if (actualLocation().path() != updatedPath) {
0199             // if the file already exists, add a number to the end
0200             int cur = 1;
0201             QFileInfo check(updatedPath);
0202             while (check.exists()) {
0203                 updatedPath = QStringLiteral("%1_%2%3").arg(path, QString::number(cur), suffix);
0204                 check = QFileInfo(updatedPath);
0205                 cur++;
0206             }
0207             
0208             QFile(actualLocation().path()).rename(updatedPath);
0209         }
0210 
0211         m_savedPath = updatedPath;
0212         m_recordingName = QString();
0213     } else {
0214         m_savedPath = actualLocation().path();
0215     }
0216 }
0217 
0218 void AudioRecorder::setRecordingName(const QString &rName) {
0219     m_recordingName = rName;
0220 }
0221 
0222 void AudioRecorder::saveRecording() 
0223 {
0224     // get file name from path
0225     QStringList spl = m_savedPath.split(QStringLiteral("/"));
0226     QString fileName = spl.at(spl.size()-1).split(QStringLiteral("."))[0];
0227     
0228     RecordingModel::instance()->insertRecording(m_savedPath, fileName, QDateTime::currentDateTime(), m_cachedDuration / 1000);
0229 }
0230 
0231 QString AudioRecorder::containerFormat() const
0232 {
0233     return m_containerFormat;
0234 }
0235 
0236 void AudioRecorder::setContainerFormat(const QString &newContainerFormat)
0237 {
0238     if (m_containerFormat == newContainerFormat) {
0239         return;
0240     }
0241     m_containerFormat = newContainerFormat;
0242 
0243     m_mediaFormat->setFileFormat(QVariant(m_containerFormat).value<QMediaFormat::FileFormat>());
0244     setMediaFormat(*m_mediaFormat);
0245 }
0246 
0247 QStringList AudioRecorder::supportedAudioCodecs() const
0248 {
0249     return m_supportedAudioCodecs;
0250 }
0251 
0252 QStringList AudioRecorder::supportedContainers() const
0253 {
0254     return m_supportedContainers;
0255 }