diff options
-rw-r--r-- | src/plugins/audiocapture/audiocapture.pro | 2 | ||||
-rw-r--r-- | src/plugins/audiocapture/audiocaptureservice.cpp | 13 | ||||
-rw-r--r-- | src/plugins/audiocapture/audiocaptureservice.h | 3 | ||||
-rw-r--r-- | src/plugins/audiocapture/audiocapturesession.cpp | 458 | ||||
-rw-r--r-- | src/plugins/audiocapture/audiocapturesession.h | 194 | ||||
-rw-r--r-- | src/plugins/audiocapture/audiocontainercontrol.cpp | 2 | ||||
-rw-r--r-- | src/plugins/audiocapture/audioencodercontrol.cpp | 2 | ||||
-rw-r--r-- | src/plugins/audiocapture/audioinputselector.cpp | 2 | ||||
-rw-r--r-- | src/plugins/audiocapture/audiomediarecordercontrol.cpp | 415 | ||||
-rw-r--r-- | src/plugins/audiocapture/audiomediarecordercontrol.h | 148 | ||||
-rw-r--r-- | src/plugins/plugins.pro | 6 |
11 files changed, 513 insertions, 732 deletions
diff --git a/src/plugins/audiocapture/audiocapture.pro b/src/plugins/audiocapture/audiocapture.pro index ba2e5c802..9a5785c1e 100644 --- a/src/plugins/audiocapture/audiocapture.pro +++ b/src/plugins/audiocapture/audiocapture.pro @@ -7,7 +7,6 @@ HEADERS += audioencodercontrol.h \ audioinputselector.h \ audiocaptureservice.h \ audiocaptureserviceplugin.h \ - audiocapturesession.h \ audiocaptureprobecontrol.h SOURCES += audioencodercontrol.cpp \ @@ -16,7 +15,6 @@ SOURCES += audioencodercontrol.cpp \ audioinputselector.cpp \ audiocaptureservice.cpp \ audiocaptureserviceplugin.cpp \ - audiocapturesession.cpp \ audiocaptureprobecontrol.cpp OTHER_FILES += \ diff --git a/src/plugins/audiocapture/audiocaptureservice.cpp b/src/plugins/audiocapture/audiocaptureservice.cpp index f06871519..b9220fb41 100644 --- a/src/plugins/audiocapture/audiocaptureservice.cpp +++ b/src/plugins/audiocapture/audiocaptureservice.cpp @@ -38,7 +38,6 @@ ****************************************************************************/ #include "audiocaptureservice.h" -#include "audiocapturesession.h" #include "audioinputselector.h" #include "audioencodercontrol.h" #include "audiocontainercontrol.h" @@ -50,11 +49,10 @@ QT_BEGIN_NAMESPACE AudioCaptureService::AudioCaptureService(QObject *parent): QMediaService(parent) { - m_session = new AudioCaptureSession(this); - m_encoderControl = new AudioEncoderControl(m_session); - m_containerControl = new AudioContainerControl(m_session); - m_mediaControl = new AudioMediaRecorderControl(m_session); - m_inputSelector = new AudioInputSelector(m_session); + m_mediaControl = new AudioCaptureSession(this); + m_encoderControl = new AudioEncoderControl(m_mediaControl); + m_containerControl = new AudioContainerControl(m_mediaControl); + m_inputSelector = new AudioInputSelector(m_mediaControl); } AudioCaptureService::~AudioCaptureService() @@ -63,7 +61,6 @@ AudioCaptureService::~AudioCaptureService() delete m_containerControl; delete m_inputSelector; delete m_mediaControl; - delete m_session; } QMediaControl *AudioCaptureService::requestControl(const char *name) @@ -82,7 +79,7 @@ QMediaControl *AudioCaptureService::requestControl(const char *name) if (qstrcmp(name,QMediaAudioProbeControl_iid) == 0) { AudioCaptureProbeControl *probe = new AudioCaptureProbeControl(this); - m_session->addProbe(probe); + m_mediaControl->addProbe(probe); return probe; } diff --git a/src/plugins/audiocapture/audiocaptureservice.h b/src/plugins/audiocapture/audiocaptureservice.h index fa2a35feb..6cafd9528 100644 --- a/src/plugins/audiocapture/audiocaptureservice.h +++ b/src/plugins/audiocapture/audiocaptureservice.h @@ -62,11 +62,10 @@ public: QMediaControl *requestControl(const char *interface); void releaseControl(QMediaControl *control); private: - AudioCaptureSession *m_session; AudioEncoderControl *m_encoderControl; AudioContainerControl *m_containerControl; AudioInputSelector *m_inputSelector; - AudioMediaRecorderControl *m_mediaControl; + AudioCaptureSession *m_mediaControl; }; QT_END_NAMESPACE diff --git a/src/plugins/audiocapture/audiocapturesession.cpp b/src/plugins/audiocapture/audiocapturesession.cpp deleted file mode 100644 index 9bdb5a92d..000000000 --- a/src/plugins/audiocapture/audiocapturesession.cpp +++ /dev/null @@ -1,458 +0,0 @@ -/**************************************************************************** -** -** 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 <QtCore/qdebug.h> -#include <QtCore/qurl.h> -#include <QtCore/qdir.h> -#include <qaudiodeviceinfo.h> - -#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<QIODevice*>(&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<QAudioDeviceInfo> 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 diff --git a/src/plugins/audiocapture/audiocapturesession.h b/src/plugins/audiocapture/audiocapturesession.h deleted file mode 100644 index 3db7a7595..000000000 --- a/src/plugins/audiocapture/audiocapturesession.h +++ /dev/null @@ -1,194 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef AUDIOCAPTURESESSION_H -#define AUDIOCAPTURESESSION_H - -#include <QFile> -#include <QUrl> -#include <QDir> -#include <QMutex> - -#include "audioencodercontrol.h" -#include "audioinputselector.h" -#include "audiomediarecordercontrol.h" - -#include <qaudioformat.h> -#include <qaudioinput.h> -#include <qaudiodeviceinfo.h> - -QT_BEGIN_NAMESPACE - -class AudioCaptureProbeControl; - -class FileProbeProxy: public QFile { -public: - void startProbes(const QAudioFormat& format); - void stopProbes(); - void addProbe(AudioCaptureProbeControl *probe); - void removeProbe(AudioCaptureProbeControl *probe); - -protected: - virtual qint64 writeData(const char *data, qint64 len); - -private: - QAudioFormat m_format; - QList<AudioCaptureProbeControl*> m_probes; - QMutex m_probeMutex; -}; - - -class AudioCaptureSession : public QObject -{ - Q_OBJECT - -public: - AudioCaptureSession(QObject *parent = 0); - ~AudioCaptureSession(); - - QAudioFormat format() const; - void setFormat(const QAudioFormat &format); - - QString containerFormat() const; - void setContainerFormat(const QString &formatMimeType); - - QUrl outputLocation() const; - bool setOutputLocation(const QUrl& location); - - qint64 position() const; - - void setState(QMediaRecorder::State state); - QMediaRecorder::State state() const; - QMediaRecorder::Status status() const; - - void addProbe(AudioCaptureProbeControl *probe); - void removeProbe(AudioCaptureProbeControl *probe); - - void setCaptureDevice(const QString &deviceName); - - void setVolume(qreal v); - qreal volume() const; - - void setMuted(bool muted); - bool isMuted() const; - -signals: - void stateChanged(QMediaRecorder::State state); - void statusChanged(QMediaRecorder::Status status); - void positionChanged(qint64 position); - void actualLocationChanged(const QUrl &location); - void volumeChanged(qreal volume); - void mutedChanged(bool muted); - void error(int error, const QString &errorString); - -private slots: - void audioInputStateChanged(QAudio::State state); - void notify(); - -private: - void record(); - void pause(); - void stop(); - - void setStatus(QMediaRecorder::Status status); - - void setVolumeHelper(qreal volume); - - QDir defaultDir() const; - QString generateFileName(const QString &requestedName, - const QString &extension) const; - QString generateFileName(const QDir &dir, const QString &extension) const; - - FileProbeProxy file; - QString m_captureDevice; - QUrl m_requestedOutputLocation; - QUrl m_actualOutputLocation; - QMediaRecorder::State m_state; - QMediaRecorder::Status m_status; - QAudioInput *m_audioInput; - QAudioDeviceInfo m_deviceInfo; - QAudioFormat m_format; - bool m_wavFile; - qreal m_volume; - bool m_muted; - - // WAV header stuff - - struct chunk - { - char id[4]; - quint32 size; - }; - - struct RIFFHeader - { - chunk descriptor; - char type[4]; - }; - - struct WAVEHeader - { - chunk descriptor; - quint16 audioFormat; // PCM = 1 - quint16 numChannels; - quint32 sampleRate; - quint32 byteRate; - quint16 blockAlign; - quint16 bitsPerSample; - }; - - struct DATAHeader - { - chunk descriptor; -// quint8 data[]; - }; - - struct CombinedHeader - { - RIFFHeader riff; - WAVEHeader wave; - DATAHeader data; - }; - - CombinedHeader header; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/audiocapture/audiocontainercontrol.cpp b/src/plugins/audiocapture/audiocontainercontrol.cpp index fd7d12e65..2f8b382d2 100644 --- a/src/plugins/audiocapture/audiocontainercontrol.cpp +++ b/src/plugins/audiocapture/audiocontainercontrol.cpp @@ -38,7 +38,7 @@ ****************************************************************************/ #include "audiocontainercontrol.h" -#include "audiocapturesession.h" +#include "audiomediarecordercontrol.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/audiocapture/audioencodercontrol.cpp b/src/plugins/audiocapture/audioencodercontrol.cpp index 6fc519cef..cd3febddc 100644 --- a/src/plugins/audiocapture/audioencodercontrol.cpp +++ b/src/plugins/audiocapture/audioencodercontrol.cpp @@ -38,7 +38,7 @@ ****************************************************************************/ #include "audioencodercontrol.h" -#include "audiocapturesession.h" +#include "audiomediarecordercontrol.h" #include <qaudioformat.h> diff --git a/src/plugins/audiocapture/audioinputselector.cpp b/src/plugins/audiocapture/audioinputselector.cpp index 3e7e854c6..9ea3e2d5c 100644 --- a/src/plugins/audiocapture/audioinputselector.cpp +++ b/src/plugins/audiocapture/audioinputselector.cpp @@ -37,7 +37,7 @@ ** ****************************************************************************/ -#include "audiocapturesession.h" +#include "audiomediarecordercontrol.h" #include "audioinputselector.h" #include <qaudiodeviceinfo.h> diff --git a/src/plugins/audiocapture/audiomediarecordercontrol.cpp b/src/plugins/audiocapture/audiomediarecordercontrol.cpp index 767026180..72f51c2b0 100644 --- a/src/plugins/audiocapture/audiomediarecordercontrol.cpp +++ b/src/plugins/audiocapture/audiomediarecordercontrol.cpp @@ -37,85 +37,420 @@ ** ****************************************************************************/ -#include "audiocapturesession.h" -#include "audiomediarecordercontrol.h" - #include <QtCore/qdebug.h> +#include <QtCore/qurl.h> +#include <QtCore/qdir.h> +#include <qaudiodeviceinfo.h> + +#include "qmediarecorder.h" + +#include "audiomediarecordercontrol.h" +#include "audiocaptureprobecontrol.h" QT_BEGIN_NAMESPACE -AudioMediaRecorderControl::AudioMediaRecorderControl(QObject *parent) +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) : QMediaRecorderControl(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::duration() const +{ + if (m_audioInput) + return m_audioInput->processedUSecs() / 1000; + return 0; +} + +void AudioCaptureSession::setState(QMediaRecorder::State state) { - m_session = qobject_cast<AudioCaptureSession*>(parent); - connect(m_session, SIGNAL(positionChanged(qint64)), - this, SIGNAL(durationChanged(qint64))); - connect(m_session, SIGNAL(stateChanged(QMediaRecorder::State)), - this, SIGNAL(stateChanged(QMediaRecorder::State))); - connect(m_session, SIGNAL(statusChanged(QMediaRecorder::Status)), - this, SIGNAL(statusChanged(QMediaRecorder::Status))); - connect(m_session, SIGNAL(actualLocationChanged(QUrl)), - this, SIGNAL(actualLocationChanged(QUrl))); - connect(m_session, &AudioCaptureSession::volumeChanged, - this, &AudioMediaRecorderControl::volumeChanged); - connect(m_session, &AudioCaptureSession::mutedChanged, - this, &AudioMediaRecorderControl::mutedChanged); - connect(m_session, SIGNAL(error(int,QString)), - this, SIGNAL(error(int,QString))); + 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<QIODevice*>(&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); + } + } } -AudioMediaRecorderControl::~AudioMediaRecorderControl() +void AudioCaptureSession::pause() { + m_audioInput->suspend(); } -QUrl AudioMediaRecorderControl::outputLocation() const +void AudioCaptureSession::stop() { - return m_session->outputLocation(); + 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); + } } -bool AudioMediaRecorderControl::setOutputLocation(const QUrl& sink) +void AudioCaptureSession::addProbe(AudioCaptureProbeControl *probe) { - return m_session->setOutputLocation(sink); + file.addProbe(probe); } -QMediaRecorder::State AudioMediaRecorderControl::state() const +void AudioCaptureSession::removeProbe(AudioCaptureProbeControl *probe) { - return m_session->state(); + file.removeProbe(probe); } -QMediaRecorder::Status AudioMediaRecorderControl::status() const +void AudioCaptureSession::audioInputStateChanged(QAudio::State state) { - return m_session->status(); + 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; + } } -qint64 AudioMediaRecorderControl::duration() const +void AudioCaptureSession::notify() { - return m_session->position(); + emit durationChanged(duration()); } -bool AudioMediaRecorderControl::isMuted() const +void AudioCaptureSession::setCaptureDevice(const QString &deviceName) { - return m_session->isMuted(); + m_captureDevice = deviceName; + + QList<QAudioDeviceInfo> 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 AudioMediaRecorderControl::volume() const +qreal AudioCaptureSession::volume() const { - return m_session->volume(); + return m_volume; } -void AudioMediaRecorderControl::setState(QMediaRecorder::State state) +bool AudioCaptureSession::isMuted() const { - m_session->setState(state); + 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 AudioMediaRecorderControl::setMuted(bool muted) +void AudioCaptureSession::setMuted(bool muted) { - m_session->setMuted(muted); + if (m_muted == muted) + return; + + m_muted = muted; + + setVolumeHelper(m_muted ? 0 : m_volume); + + emit mutedChanged(m_muted); } -void AudioMediaRecorderControl::setVolume(qreal volume) +void AudioCaptureSession::setVolumeHelper(qreal volume) { - m_session->setVolume(volume); + if (!m_audioInput) + return; + + m_audioInput->setVolume(volume); } QT_END_NAMESPACE diff --git a/src/plugins/audiocapture/audiomediarecordercontrol.h b/src/plugins/audiocapture/audiomediarecordercontrol.h index cb6e79cb4..d2e0a99fc 100644 --- a/src/plugins/audiocapture/audiomediarecordercontrol.h +++ b/src/plugins/audiocapture/audiomediarecordercontrol.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -37,44 +37,150 @@ ** ****************************************************************************/ -#ifndef AUDIOMEDIARECORDERCONTROL_H -#define AUDIOMEDIARECORDERCONTROL_H +#ifndef AUDIOCAPTURESESSION_H +#define AUDIOCAPTURESESSION_H -#include <QtCore/qobject.h> +#include <QFile> +#include <QUrl> +#include <QDir> +#include <QMutex> -#include "qmediarecorder.h" +#include "audioencodercontrol.h" +#include "audioinputselector.h" #include "qmediarecordercontrol.h" +#include <qaudioformat.h> +#include <qaudioinput.h> +#include <qaudiodeviceinfo.h> + QT_BEGIN_NAMESPACE -class AudioCaptureSession; +class AudioCaptureProbeControl; + +class FileProbeProxy: public QFile { +public: + void startProbes(const QAudioFormat& format); + void stopProbes(); + void addProbe(AudioCaptureProbeControl *probe); + void removeProbe(AudioCaptureProbeControl *probe); + +protected: + virtual qint64 writeData(const char *data, qint64 len); + +private: + QAudioFormat m_format; + QList<AudioCaptureProbeControl*> m_probes; + QMutex m_probeMutex; +}; -class AudioMediaRecorderControl : public QMediaRecorderControl + +class AudioCaptureSession : public QMediaRecorderControl { Q_OBJECT + public: - AudioMediaRecorderControl(QObject *parent = 0); - ~AudioMediaRecorderControl(); + AudioCaptureSession(QObject *parent = 0); + ~AudioCaptureSession(); + + // QMediaRecorderControl interface + QUrl outputLocation() const override; + bool setOutputLocation(const QUrl& location) override; + + void setState(QMediaRecorder::State state) override; + QMediaRecorder::State state() const override; + QMediaRecorder::Status status() const override; + + void setVolume(qreal v) override; + qreal volume() const override; + + void setMuted(bool muted) override; + bool isMuted() const override; - QUrl outputLocation() const; - bool setOutputLocation(const QUrl &location); + qint64 duration() const override; - QMediaRecorder::State state() const; - QMediaRecorder::Status status() const; + void applySettings() override {} - qint64 duration() const; + QAudioFormat format() const; + void setFormat(const QAudioFormat &format); - bool isMuted() const; - qreal volume() const; + QString containerFormat() const; + void setContainerFormat(const QString &formatMimeType); - void applySettings() {} + void addProbe(AudioCaptureProbeControl *probe); + void removeProbe(AudioCaptureProbeControl *probe); - void setState(QMediaRecorder::State state); - void setMuted(bool); - void setVolume(qreal volume); + void setCaptureDevice(const QString &deviceName); + +private slots: + void audioInputStateChanged(QAudio::State state); + void notify(); private: - AudioCaptureSession* m_session; + void record(); + void pause(); + void stop(); + + void setStatus(QMediaRecorder::Status status); + + void setVolumeHelper(qreal volume); + + QDir defaultDir() const; + QString generateFileName(const QString &requestedName, + const QString &extension) const; + QString generateFileName(const QDir &dir, const QString &extension) const; + + FileProbeProxy file; + QString m_captureDevice; + QUrl m_requestedOutputLocation; + QUrl m_actualOutputLocation; + QMediaRecorder::State m_state; + QMediaRecorder::Status m_status; + QAudioInput *m_audioInput; + QAudioDeviceInfo m_deviceInfo; + QAudioFormat m_format; + bool m_wavFile; + qreal m_volume; + bool m_muted; + + // WAV header stuff + + struct chunk + { + char id[4]; + quint32 size; + }; + + struct RIFFHeader + { + chunk descriptor; + char type[4]; + }; + + struct WAVEHeader + { + chunk descriptor; + quint16 audioFormat; // PCM = 1 + quint16 numChannels; + quint32 sampleRate; + quint32 byteRate; + quint16 blockAlign; + quint16 bitsPerSample; + }; + + struct DATAHeader + { + chunk descriptor; +// quint8 data[]; + }; + + struct CombinedHeader + { + RIFFHeader riff; + WAVEHeader wave; + DATAHeader data; + }; + + CombinedHeader header; }; QT_END_NAMESPACE diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index f9cbfc8ec..96ec31ddf 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -28,13 +28,11 @@ win32: { qtConfig(gstreamer): SUBDIRS += gstreamer -unix:!mac:!android { - !qtConfig(gstreamer): SUBDIRS += audiocapture -} +unix:!mac:!android:!qtConfig(gstreamer): SUBDIRS += audiocapture darwin:!watchos { - SUBDIRS += audiocapture qtConfig(avfoundation): SUBDIRS += avfoundation + SUBDIRS += audiocapture } |