/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include "qmediarecorder.h" #include "audiocapturesession.h" #include "audiocaptureprobecontrol.h" QT_BEGIN_NAMESPACE void FileProbeProxy::startProbes(const QAudioFormat &format) { m_format = format; } void FileProbeProxy::stopProbes() { m_format = QAudioFormat(); } void FileProbeProxy::addProbe(AudioCaptureProbeControl *probe) { QMutexLocker locker(&m_probeMutex); if (m_probes.contains(probe)) return; m_probes.append(probe); } void FileProbeProxy::removeProbe(AudioCaptureProbeControl *probe) { QMutexLocker locker(&m_probeMutex); m_probes.removeOne(probe); } qint64 FileProbeProxy::writeData(const char *data, qint64 len) { if (m_format.isValid()) { QMutexLocker locker(&m_probeMutex); for (AudioCaptureProbeControl* probe : qAsConst(m_probes)) probe->bufferProbed(data, len, m_format); } return QFile::writeData(data, len); } AudioCaptureSession::AudioCaptureSession(QObject *parent) : QObject(parent) , m_state(QMediaRecorder::StoppedState) , m_status(QMediaRecorder::UnloadedStatus) , m_audioInput(0) , m_deviceInfo(QAudioDeviceInfo::defaultInputDevice()) , m_wavFile(true) , m_volume(1.0) , m_muted(false) { m_format = m_deviceInfo.preferredFormat(); } AudioCaptureSession::~AudioCaptureSession() { setState(QMediaRecorder::StoppedState); } QAudioFormat AudioCaptureSession::format() const { return m_format; } void AudioCaptureSession::setFormat(const QAudioFormat &format) { m_format = format; } void AudioCaptureSession::setContainerFormat(const QString &formatMimeType) { m_wavFile = (formatMimeType.isEmpty() || QString::compare(formatMimeType, QLatin1String("audio/x-wav")) == 0); } QString AudioCaptureSession::containerFormat() const { if (m_wavFile) return QStringLiteral("audio/x-wav"); return QStringLiteral("audio/x-raw"); } QUrl AudioCaptureSession::outputLocation() const { return m_actualOutputLocation; } bool AudioCaptureSession::setOutputLocation(const QUrl& location) { if (m_requestedOutputLocation == location) return false; m_actualOutputLocation = QUrl(); m_requestedOutputLocation = location; if (m_requestedOutputLocation.isEmpty()) return true; if (m_requestedOutputLocation.isValid() && (m_requestedOutputLocation.isLocalFile() || m_requestedOutputLocation.isRelative())) { emit actualLocationChanged(m_requestedOutputLocation); return true; } m_requestedOutputLocation = QUrl(); return false; } qint64 AudioCaptureSession::position() const { if (m_audioInput) return m_audioInput->processedUSecs() / 1000; return 0; } void AudioCaptureSession::setState(QMediaRecorder::State state) { if (m_state == state) return; m_state = state; emit stateChanged(m_state); switch (m_state) { case QMediaRecorder::StoppedState: stop(); break; case QMediaRecorder::PausedState: pause(); break; case QMediaRecorder::RecordingState: record(); break; } } QMediaRecorder::State AudioCaptureSession::state() const { return m_state; } void AudioCaptureSession::setStatus(QMediaRecorder::Status status) { if (m_status == status) return; m_status = status; emit statusChanged(m_status); } QMediaRecorder::Status AudioCaptureSession::status() const { return m_status; } QDir AudioCaptureSession::defaultDir() const { QStringList dirCandidates; dirCandidates << QDir::home().filePath("Documents"); dirCandidates << QDir::home().filePath("My Documents"); dirCandidates << QDir::homePath(); dirCandidates << QDir::currentPath(); dirCandidates << QDir::tempPath(); for (const QString &path : qAsConst(dirCandidates)) { QDir dir(path); if (dir.exists() && QFileInfo(path).isWritable()) return dir; } return QDir(); } QString AudioCaptureSession::generateFileName(const QString &requestedName, const QString &extension) const { if (requestedName.isEmpty()) return generateFileName(defaultDir(), extension); QString path = requestedName; if (QFileInfo(path).isRelative()) path = defaultDir().absoluteFilePath(path); if (QFileInfo(path).isDir()) return generateFileName(QDir(path), extension); if (!path.endsWith(extension)) path.append(QString(".%1").arg(extension)); return path; } QString AudioCaptureSession::generateFileName(const QDir &dir, const QString &ext) const { int lastClip = 0; const auto list = dir.entryList(QStringList() << QString("clip_*.%1").arg(ext)); for (const QString &fileName : list) { int imgNumber = QStringView{fileName}.mid(5, fileName.size()-6-ext.length()).toInt(); lastClip = qMax(lastClip, imgNumber); } QString name = QString("clip_%1.%2").arg(lastClip+1, 4, //fieldWidth 10, QLatin1Char('0')).arg(ext); return dir.absoluteFilePath(name); } void AudioCaptureSession::record() { if (m_status == QMediaRecorder::PausedStatus) { m_audioInput->resume(); } else { if (m_deviceInfo.isNull()) { emit error(QMediaRecorder::ResourceError, QStringLiteral("No input device available.")); m_state = QMediaRecorder::StoppedState; emit stateChanged(m_state); setStatus(QMediaRecorder::UnavailableStatus); return; } setStatus(QMediaRecorder::LoadingStatus); m_format = m_deviceInfo.nearestFormat(m_format); m_audioInput = new QAudioInput(m_deviceInfo, m_format); connect(m_audioInput, SIGNAL(stateChanged(QAudio::State)), this, SLOT(audioInputStateChanged(QAudio::State))); connect(m_audioInput, SIGNAL(notify()), this, SLOT(notify())); QString filePath = generateFileName( m_requestedOutputLocation.isLocalFile() ? m_requestedOutputLocation.toLocalFile() : m_requestedOutputLocation.toString(), m_wavFile ? QLatin1String("wav") : QLatin1String("raw")); m_actualOutputLocation = QUrl::fromLocalFile(filePath); if (m_actualOutputLocation != m_requestedOutputLocation) emit actualLocationChanged(m_actualOutputLocation); file.setFileName(filePath); setStatus(QMediaRecorder::LoadedStatus); setStatus(QMediaRecorder::StartingStatus); if (file.open(QIODevice::WriteOnly)) { if (m_wavFile) { memset(&header,0,sizeof(CombinedHeader)); memcpy(header.riff.descriptor.id,"RIFF",4); header.riff.descriptor.size = 0xFFFFFFFF; // This should be updated on stop(), filesize-8 memcpy(header.riff.type,"WAVE",4); memcpy(header.wave.descriptor.id,"fmt ",4); header.wave.descriptor.size = 16; header.wave.audioFormat = 1; // for PCM data header.wave.numChannels = m_format.channelCount(); header.wave.sampleRate = m_format.sampleRate(); header.wave.byteRate = m_format.sampleRate()*m_format.channelCount()*m_format.sampleSize()/8; header.wave.blockAlign = m_format.channelCount()*m_format.sampleSize()/8; header.wave.bitsPerSample = m_format.sampleSize(); memcpy(header.data.descriptor.id,"data",4); header.data.descriptor.size = 0xFFFFFFFF; // This should be updated on stop(),samples*channels*sampleSize/8 file.write((char*)&header,sizeof(CombinedHeader)); } setVolumeHelper(m_muted ? 0 : m_volume); file.startProbes(m_format); m_audioInput->start(qobject_cast(&file)); } else { delete m_audioInput; m_audioInput = 0; emit error(QMediaRecorder::ResourceError, QStringLiteral("Can't open output location")); m_state = QMediaRecorder::StoppedState; emit stateChanged(m_state); setStatus(QMediaRecorder::UnloadedStatus); } } } void AudioCaptureSession::pause() { m_audioInput->suspend(); } void AudioCaptureSession::stop() { if(m_audioInput) { m_audioInput->stop(); file.stopProbes(); file.close(); if (m_wavFile) { qint32 fileSize = file.size(); file.open(QIODevice::ReadWrite | QIODevice::Unbuffered); file.read((char*)&header,sizeof(CombinedHeader)); header.riff.descriptor.size = fileSize - 8; // The RIFF chunk size is the file size minus // the first two RIFF fields (8 bytes) header.data.descriptor.size = fileSize - 44; // dataSize = fileSize - headerSize (44 bytes) file.seek(0); file.write((char*)&header,sizeof(CombinedHeader)); file.close(); } delete m_audioInput; m_audioInput = 0; setStatus(QMediaRecorder::UnloadedStatus); } } void AudioCaptureSession::addProbe(AudioCaptureProbeControl *probe) { file.addProbe(probe); } void AudioCaptureSession::removeProbe(AudioCaptureProbeControl *probe) { file.removeProbe(probe); } void AudioCaptureSession::audioInputStateChanged(QAudio::State state) { switch(state) { case QAudio::ActiveState: setStatus(QMediaRecorder::RecordingStatus); break; case QAudio::SuspendedState: setStatus(QMediaRecorder::PausedStatus); break; case QAudio::StoppedState: setStatus(QMediaRecorder::FinalizingStatus); break; default: break; } } void AudioCaptureSession::notify() { emit positionChanged(position()); } void AudioCaptureSession::setCaptureDevice(const QString &deviceName) { m_captureDevice = deviceName; QList devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); for (int i = 0; i < devices.size(); ++i) { QAudioDeviceInfo info = devices.at(i); if (m_captureDevice == info.deviceName()){ m_deviceInfo = info; return; } } m_deviceInfo = QAudioDeviceInfo::defaultInputDevice(); } qreal AudioCaptureSession::volume() const { return m_volume; } bool AudioCaptureSession::isMuted() const { return m_muted; } void AudioCaptureSession::setVolume(qreal v) { qreal boundedVolume = qBound(qreal(0), v, qreal(1)); if (m_volume == boundedVolume) return; m_volume = boundedVolume; if (!m_muted) setVolumeHelper(m_volume); emit volumeChanged(m_volume); } void AudioCaptureSession::setMuted(bool muted) { if (m_muted == muted) return; m_muted = muted; setVolumeHelper(m_muted ? 0 : m_volume); emit mutedChanged(m_muted); } void AudioCaptureSession::setVolumeHelper(qreal volume) { if (!m_audioInput) return; m_audioInput->setVolume(volume); } QT_END_NAMESPACE