File indexing completed on 2025-02-09 07:08:06
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 }