1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include <QtCore/qdebug.h>
41 #include <QtCore/qurl.h>
42 #include <QtCore/qdir.h>
43 #include <qaudiodeviceinfo.h>
44
45 #include "qmediarecorder.h"
46
47 #include "audiocapturesession.h"
48 #include "audiocaptureprobecontrol.h"
49
50 QT_BEGIN_NAMESPACE
51
startProbes(const QAudioFormat & format)52 void FileProbeProxy::startProbes(const QAudioFormat &format)
53 {
54 m_format = format;
55 }
56
stopProbes()57 void FileProbeProxy::stopProbes()
58 {
59 m_format = QAudioFormat();
60 }
61
addProbe(AudioCaptureProbeControl * probe)62 void FileProbeProxy::addProbe(AudioCaptureProbeControl *probe)
63 {
64 QMutexLocker locker(&m_probeMutex);
65
66 if (m_probes.contains(probe))
67 return;
68
69 m_probes.append(probe);
70 }
71
removeProbe(AudioCaptureProbeControl * probe)72 void FileProbeProxy::removeProbe(AudioCaptureProbeControl *probe)
73 {
74 QMutexLocker locker(&m_probeMutex);
75 m_probes.removeOne(probe);
76 }
77
writeData(const char * data,qint64 len)78 qint64 FileProbeProxy::writeData(const char *data, qint64 len)
79 {
80 if (m_format.isValid()) {
81 QMutexLocker locker(&m_probeMutex);
82
83 for (AudioCaptureProbeControl* probe : qAsConst(m_probes))
84 probe->bufferProbed(data, len, m_format);
85 }
86
87 return QFile::writeData(data, len);
88 }
89
AudioCaptureSession(QObject * parent)90 AudioCaptureSession::AudioCaptureSession(QObject *parent)
91 : QObject(parent)
92 , m_state(QMediaRecorder::StoppedState)
93 , m_status(QMediaRecorder::UnloadedStatus)
94 , m_audioInput(0)
95 , m_deviceInfo(QAudioDeviceInfo::defaultInputDevice())
96 , m_wavFile(true)
97 , m_volume(1.0)
98 , m_muted(false)
99 {
100 m_format = m_deviceInfo.preferredFormat();
101 }
102
~AudioCaptureSession()103 AudioCaptureSession::~AudioCaptureSession()
104 {
105 setState(QMediaRecorder::StoppedState);
106 }
107
format() const108 QAudioFormat AudioCaptureSession::format() const
109 {
110 return m_format;
111 }
112
setFormat(const QAudioFormat & format)113 void AudioCaptureSession::setFormat(const QAudioFormat &format)
114 {
115 m_format = format;
116 }
117
setContainerFormat(const QString & formatMimeType)118 void AudioCaptureSession::setContainerFormat(const QString &formatMimeType)
119 {
120 m_wavFile = (formatMimeType.isEmpty()
121 || QString::compare(formatMimeType, QLatin1String("audio/x-wav")) == 0);
122 }
123
containerFormat() const124 QString AudioCaptureSession::containerFormat() const
125 {
126 if (m_wavFile)
127 return QStringLiteral("audio/x-wav");
128
129 return QStringLiteral("audio/x-raw");
130 }
131
outputLocation() const132 QUrl AudioCaptureSession::outputLocation() const
133 {
134 return m_actualOutputLocation;
135 }
136
setOutputLocation(const QUrl & location)137 bool AudioCaptureSession::setOutputLocation(const QUrl& location)
138 {
139 if (m_requestedOutputLocation == location)
140 return false;
141
142 m_actualOutputLocation = QUrl();
143 m_requestedOutputLocation = location;
144
145 if (m_requestedOutputLocation.isEmpty())
146 return true;
147
148 if (m_requestedOutputLocation.isValid() && (m_requestedOutputLocation.isLocalFile()
149 || m_requestedOutputLocation.isRelative())) {
150 emit actualLocationChanged(m_requestedOutputLocation);
151 return true;
152 }
153
154 m_requestedOutputLocation = QUrl();
155 return false;
156 }
157
position() const158 qint64 AudioCaptureSession::position() const
159 {
160 if (m_audioInput)
161 return m_audioInput->processedUSecs() / 1000;
162 return 0;
163 }
164
setState(QMediaRecorder::State state)165 void AudioCaptureSession::setState(QMediaRecorder::State state)
166 {
167 if (m_state == state)
168 return;
169
170 m_state = state;
171 emit stateChanged(m_state);
172
173 switch (m_state) {
174 case QMediaRecorder::StoppedState:
175 stop();
176 break;
177 case QMediaRecorder::PausedState:
178 pause();
179 break;
180 case QMediaRecorder::RecordingState:
181 record();
182 break;
183 }
184 }
185
state() const186 QMediaRecorder::State AudioCaptureSession::state() const
187 {
188 return m_state;
189 }
190
setStatus(QMediaRecorder::Status status)191 void AudioCaptureSession::setStatus(QMediaRecorder::Status status)
192 {
193 if (m_status == status)
194 return;
195
196 m_status = status;
197 emit statusChanged(m_status);
198 }
199
status() const200 QMediaRecorder::Status AudioCaptureSession::status() const
201 {
202 return m_status;
203 }
204
defaultDir() const205 QDir AudioCaptureSession::defaultDir() const
206 {
207 QStringList dirCandidates;
208
209 dirCandidates << QDir::home().filePath("Documents");
210 dirCandidates << QDir::home().filePath("My Documents");
211 dirCandidates << QDir::homePath();
212 dirCandidates << QDir::currentPath();
213 dirCandidates << QDir::tempPath();
214
215 for (const QString &path : qAsConst(dirCandidates)) {
216 QDir dir(path);
217 if (dir.exists() && QFileInfo(path).isWritable())
218 return dir;
219 }
220
221 return QDir();
222 }
223
generateFileName(const QString & requestedName,const QString & extension) const224 QString AudioCaptureSession::generateFileName(const QString &requestedName,
225 const QString &extension) const
226 {
227 if (requestedName.isEmpty())
228 return generateFileName(defaultDir(), extension);
229
230 QString path = requestedName;
231
232 if (QFileInfo(path).isRelative())
233 path = defaultDir().absoluteFilePath(path);
234
235 if (QFileInfo(path).isDir())
236 return generateFileName(QDir(path), extension);
237
238 if (!path.endsWith(extension))
239 path.append(QString(".%1").arg(extension));
240
241 return path;
242 }
243
generateFileName(const QDir & dir,const QString & ext) const244 QString AudioCaptureSession::generateFileName(const QDir &dir,
245 const QString &ext) const
246 {
247 int lastClip = 0;
248 const auto list = dir.entryList(QStringList() << QString("clip_*.%1").arg(ext));
249 for (const QString &fileName : list) {
250 int imgNumber = fileName.midRef(5, fileName.size()-6-ext.length()).toInt();
251 lastClip = qMax(lastClip, imgNumber);
252 }
253
254 QString name = QString("clip_%1.%2").arg(lastClip+1,
255 4, //fieldWidth
256 10,
257 QLatin1Char('0')).arg(ext);
258
259 return dir.absoluteFilePath(name);
260 }
261
record()262 void AudioCaptureSession::record()
263 {
264 if (m_status == QMediaRecorder::PausedStatus) {
265 m_audioInput->resume();
266 } else {
267 if (m_deviceInfo.isNull()) {
268 emit error(QMediaRecorder::ResourceError,
269 QStringLiteral("No input device available."));
270 m_state = QMediaRecorder::StoppedState;
271 emit stateChanged(m_state);
272 setStatus(QMediaRecorder::UnavailableStatus);
273 return;
274 }
275
276 setStatus(QMediaRecorder::LoadingStatus);
277
278 m_format = m_deviceInfo.nearestFormat(m_format);
279 m_audioInput = new QAudioInput(m_deviceInfo, m_format);
280 connect(m_audioInput, SIGNAL(stateChanged(QAudio::State)),
281 this, SLOT(audioInputStateChanged(QAudio::State)));
282 connect(m_audioInput, SIGNAL(notify()),
283 this, SLOT(notify()));
284
285
286 QString filePath = generateFileName(
287 m_requestedOutputLocation.isLocalFile() ? m_requestedOutputLocation.toLocalFile()
288 : m_requestedOutputLocation.toString(),
289 m_wavFile ? QLatin1String("wav")
290 : QLatin1String("raw"));
291
292 m_actualOutputLocation = QUrl::fromLocalFile(filePath);
293 if (m_actualOutputLocation != m_requestedOutputLocation)
294 emit actualLocationChanged(m_actualOutputLocation);
295
296 file.setFileName(filePath);
297
298 setStatus(QMediaRecorder::LoadedStatus);
299 setStatus(QMediaRecorder::StartingStatus);
300
301 if (file.open(QIODevice::WriteOnly)) {
302 if (m_wavFile) {
303 memset(&header,0,sizeof(CombinedHeader));
304 memcpy(header.riff.descriptor.id,"RIFF",4);
305 header.riff.descriptor.size = 0xFFFFFFFF; // This should be updated on stop(), filesize-8
306 memcpy(header.riff.type,"WAVE",4);
307 memcpy(header.wave.descriptor.id,"fmt ",4);
308 header.wave.descriptor.size = 16;
309 header.wave.audioFormat = 1; // for PCM data
310 header.wave.numChannels = m_format.channelCount();
311 header.wave.sampleRate = m_format.sampleRate();
312 header.wave.byteRate = m_format.sampleRate()*m_format.channelCount()*m_format.sampleSize()/8;
313 header.wave.blockAlign = m_format.channelCount()*m_format.sampleSize()/8;
314 header.wave.bitsPerSample = m_format.sampleSize();
315 memcpy(header.data.descriptor.id,"data",4);
316 header.data.descriptor.size = 0xFFFFFFFF; // This should be updated on stop(),samples*channels*sampleSize/8
317 file.write((char*)&header,sizeof(CombinedHeader));
318 }
319
320 setVolumeHelper(m_muted ? 0 : m_volume);
321
322 file.startProbes(m_format);
323 m_audioInput->start(qobject_cast<QIODevice*>(&file));
324 } else {
325 delete m_audioInput;
326 m_audioInput = 0;
327 emit error(QMediaRecorder::ResourceError,
328 QStringLiteral("Can't open output location"));
329 m_state = QMediaRecorder::StoppedState;
330 emit stateChanged(m_state);
331 setStatus(QMediaRecorder::UnloadedStatus);
332 }
333 }
334 }
335
pause()336 void AudioCaptureSession::pause()
337 {
338 m_audioInput->suspend();
339 }
340
stop()341 void AudioCaptureSession::stop()
342 {
343 if(m_audioInput) {
344 m_audioInput->stop();
345 file.stopProbes();
346 file.close();
347 if (m_wavFile) {
348 qint32 fileSize = file.size();
349 file.open(QIODevice::ReadWrite | QIODevice::Unbuffered);
350 file.read((char*)&header,sizeof(CombinedHeader));
351 header.riff.descriptor.size = fileSize - 8; // The RIFF chunk size is the file size minus
352 // the first two RIFF fields (8 bytes)
353 header.data.descriptor.size = fileSize - 44; // dataSize = fileSize - headerSize (44 bytes)
354 file.seek(0);
355 file.write((char*)&header,sizeof(CombinedHeader));
356 file.close();
357 }
358 delete m_audioInput;
359 m_audioInput = 0;
360 setStatus(QMediaRecorder::UnloadedStatus);
361 }
362 }
363
addProbe(AudioCaptureProbeControl * probe)364 void AudioCaptureSession::addProbe(AudioCaptureProbeControl *probe)
365 {
366 file.addProbe(probe);
367 }
368
removeProbe(AudioCaptureProbeControl * probe)369 void AudioCaptureSession::removeProbe(AudioCaptureProbeControl *probe)
370 {
371 file.removeProbe(probe);
372 }
373
audioInputStateChanged(QAudio::State state)374 void AudioCaptureSession::audioInputStateChanged(QAudio::State state)
375 {
376 switch(state) {
377 case QAudio::ActiveState:
378 setStatus(QMediaRecorder::RecordingStatus);
379 break;
380 case QAudio::SuspendedState:
381 setStatus(QMediaRecorder::PausedStatus);
382 break;
383 case QAudio::StoppedState:
384 setStatus(QMediaRecorder::FinalizingStatus);
385 break;
386 default:
387 break;
388 }
389 }
390
notify()391 void AudioCaptureSession::notify()
392 {
393 emit positionChanged(position());
394 }
395
setCaptureDevice(const QString & deviceName)396 void AudioCaptureSession::setCaptureDevice(const QString &deviceName)
397 {
398 m_captureDevice = deviceName;
399
400 QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
401 for (int i = 0; i < devices.size(); ++i) {
402 QAudioDeviceInfo info = devices.at(i);
403 if (m_captureDevice == info.deviceName()){
404 m_deviceInfo = info;
405 return;
406 }
407 }
408 m_deviceInfo = QAudioDeviceInfo::defaultInputDevice();
409 }
410
volume() const411 qreal AudioCaptureSession::volume() const
412 {
413 return m_volume;
414 }
415
isMuted() const416 bool AudioCaptureSession::isMuted() const
417 {
418 return m_muted;
419 }
420
setVolume(qreal v)421 void AudioCaptureSession::setVolume(qreal v)
422 {
423 qreal boundedVolume = qBound(qreal(0), v, qreal(1));
424
425 if (m_volume == boundedVolume)
426 return;
427
428 m_volume = boundedVolume;
429
430 if (!m_muted)
431 setVolumeHelper(m_volume);
432
433 emit volumeChanged(m_volume);
434 }
435
setMuted(bool muted)436 void AudioCaptureSession::setMuted(bool muted)
437 {
438 if (m_muted == muted)
439 return;
440
441 m_muted = muted;
442
443 setVolumeHelper(m_muted ? 0 : m_volume);
444
445 emit mutedChanged(m_muted);
446 }
447
setVolumeHelper(qreal volume)448 void AudioCaptureSession::setVolumeHelper(qreal volume)
449 {
450 if (!m_audioInput)
451 return;
452
453 m_audioInput->setVolume(volume);
454 }
455
456
457
458 QT_END_NAMESPACE
459