summaryrefslogtreecommitdiffstats
path: root/examples/multimedia/spectrum/engine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'examples/multimedia/spectrum/engine.cpp')
-rw-r--r--examples/multimedia/spectrum/engine.cpp750
1 files changed, 750 insertions, 0 deletions
diff --git a/examples/multimedia/spectrum/engine.cpp b/examples/multimedia/spectrum/engine.cpp
new file mode 100644
index 000000000..cb7aeadcb
--- /dev/null
+++ b/examples/multimedia/spectrum/engine.cpp
@@ -0,0 +1,750 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "engine.h"
+#include "tonegenerator.h"
+#include "utils.h"
+
+#include <QAudioSink>
+#include <QAudioSource>
+#include <QCoreApplication>
+#include <QDebug>
+#include <QFile>
+#include <QMetaObject>
+#include <QSet>
+#include <QThread>
+
+#if QT_CONFIG(permissions)
+ #include <QPermission>
+#endif
+
+#include <math.h>
+
+//-----------------------------------------------------------------------------
+// Constants
+//-----------------------------------------------------------------------------
+
+const qint64 BufferDurationUs = 10 * 1000000;
+
+// Size of the level calculation window in microseconds
+const int LevelWindowUs = 0.1 * 1000000;
+
+//-----------------------------------------------------------------------------
+// Constructor and destructor
+//-----------------------------------------------------------------------------
+
+Engine::Engine(QObject *parent)
+ : QObject(parent),
+ m_mode(QAudioDevice::Input),
+ m_state(QAudio::StoppedState),
+ m_devices(new QMediaDevices(this)),
+ m_generateTone(false),
+ m_file(nullptr),
+ m_analysisFile(nullptr),
+ m_audioInput(nullptr),
+ m_audioInputIODevice(nullptr),
+ m_recordPosition(0),
+ m_audioOutput(nullptr),
+ m_playPosition(0),
+ m_bufferPosition(0),
+ m_bufferLength(0),
+ m_dataLength(0),
+ m_levelBufferLength(0),
+ m_rmsLevel(0.0),
+ m_peakLevel(0.0),
+ m_spectrumBufferLength(0),
+ m_spectrumPosition(0),
+ m_count(0)
+{
+ connect(&m_spectrumAnalyser,
+ QOverload<const FrequencySpectrum &>::of(&SpectrumAnalyser::spectrumChanged), this,
+ QOverload<const FrequencySpectrum &>::of(&Engine::spectrumChanged));
+
+ // This code might misinterpret things like "-something -category". But
+ // it's unlikely that that needs to be supported so we'll let it go.
+ QStringList arguments = QCoreApplication::instance()->arguments();
+ for (int i = 0; i < arguments.count(); ++i) {
+ if (arguments.at(i) == QStringLiteral("--"))
+ break;
+ }
+
+ initAudioDevices();
+
+ initialize();
+
+#ifdef DUMP_DATA
+ createOutputDir();
+#endif
+
+#ifdef DUMP_SPECTRUM
+ m_spectrumAnalyser.setOutputPath(outputPath());
+#endif
+
+ m_notifyTimer = new QTimer(this);
+ m_notifyTimer->setInterval(1000);
+ connect(m_notifyTimer, &QTimer::timeout, this, &Engine::audioNotify);
+}
+
+Engine::~Engine() = default;
+
+//-----------------------------------------------------------------------------
+// Public functions
+//-----------------------------------------------------------------------------
+
+bool Engine::loadFile(const QString &fileName)
+{
+ reset();
+ bool result = false;
+ Q_ASSERT(!m_generateTone);
+ Q_ASSERT(!m_file);
+ Q_ASSERT(!fileName.isEmpty());
+ QIODevice *file = new QFile(fileName);
+ if (file->open(QIODevice::ReadOnly)) {
+ m_file = new QWaveDecoder(file, this);
+ if (m_file->open(QIODevice::ReadOnly)) {
+ if (m_file->audioFormat().sampleFormat() == QAudioFormat::Int16) {
+ result = initialize();
+ } else {
+ emit errorMessage(tr("Audio format not supported"),
+ formatToString(m_file->audioFormat()));
+ }
+ } else
+ emit errorMessage(tr("Could not open WAV decoder for file"), fileName);
+ } else {
+ emit errorMessage(tr("Could not open file"), fileName);
+ }
+ if (result) {
+ file->close();
+ file->open(QIODevice::ReadOnly);
+ m_analysisFile = new QWaveDecoder(file, this);
+ m_analysisFile->open(QIODevice::ReadOnly);
+ }
+ return result;
+}
+
+bool Engine::generateTone(const Tone &tone)
+{
+ reset();
+ Q_ASSERT(!m_generateTone);
+ Q_ASSERT(!m_file);
+ m_generateTone = true;
+ m_tone = tone;
+ ENGINE_DEBUG << "Engine::generateTone"
+ << "startFreq" << m_tone.startFreq << "endFreq" << m_tone.endFreq << "amp"
+ << m_tone.amplitude;
+ return initialize();
+}
+
+bool Engine::generateSweptTone(qreal amplitude)
+{
+ Q_ASSERT(!m_generateTone);
+ Q_ASSERT(!m_file);
+ m_generateTone = true;
+ m_tone.startFreq = 1;
+ m_tone.endFreq = 0;
+ m_tone.amplitude = amplitude;
+ ENGINE_DEBUG << "Engine::generateSweptTone"
+ << "startFreq" << m_tone.startFreq << "amp" << m_tone.amplitude;
+ return initialize();
+}
+
+bool Engine::initializeRecord()
+{
+ reset();
+ ENGINE_DEBUG << "Engine::initializeRecord";
+ Q_ASSERT(!m_generateTone);
+ Q_ASSERT(!m_file);
+ m_generateTone = false;
+ m_tone = SweptTone();
+ return initialize();
+}
+
+qint64 Engine::bufferLength() const
+{
+ return m_file ? m_file->getDevice()->size() : m_bufferLength;
+}
+
+void Engine::setWindowFunction(WindowFunction type)
+{
+ m_spectrumAnalyser.setWindowFunction(type);
+}
+
+//-----------------------------------------------------------------------------
+// Public slots
+//-----------------------------------------------------------------------------
+
+void Engine::startRecording()
+{
+ if (m_audioInput) {
+ if (QAudioDevice::Input == m_mode && QAudio::SuspendedState == m_state) {
+ m_audioInput->resume();
+ } else {
+ m_spectrumAnalyser.cancelCalculation();
+ emit spectrumChanged(0, 0, FrequencySpectrum());
+
+ m_buffer.fill(0);
+ setRecordPosition(0, true);
+ stopPlayback();
+ m_mode = QAudioDevice::Input;
+ connect(m_audioInput, &QAudioSource::stateChanged, this, &Engine::audioStateChanged);
+
+ m_count = 0;
+ m_dataLength = 0;
+ emit dataLengthChanged(0);
+ m_audioInputIODevice = m_audioInput->start();
+ connect(m_audioInputIODevice, &QIODevice::readyRead, this, &Engine::audioDataReady);
+ }
+ m_notifyTimer->start();
+ }
+}
+
+void Engine::startPlayback()
+{
+ if (!m_audioOutput)
+ initialize();
+
+ if (m_audioOutput) {
+ if (QAudioDevice::Output == m_mode && QAudio::SuspendedState == m_state) {
+#ifdef Q_OS_WIN
+ // The Windows backend seems to internally go back into ActiveState
+ // while still returning SuspendedState, so to ensure that it doesn't
+ // ignore the resume() call, we first re-suspend
+ m_audioOutput->suspend();
+#endif
+ m_audioOutput->resume();
+ } else {
+ m_spectrumAnalyser.cancelCalculation();
+ emit spectrumChanged(0, 0, FrequencySpectrum());
+ setPlayPosition(0, true);
+ stopRecording();
+ m_mode = QAudioDevice::Output;
+ connect(m_audioOutput, &QAudioSink::stateChanged, this, &Engine::audioStateChanged);
+
+ m_count = 0;
+ if (m_file) {
+ m_file->seek(0);
+ m_bufferPosition = 0;
+ m_dataLength = 0;
+ m_audioOutput->start(m_file->getDevice());
+ } else {
+ m_audioOutputIODevice.close();
+ m_audioOutputIODevice.setBuffer(&m_buffer);
+ m_audioOutputIODevice.open(QIODevice::ReadOnly);
+ m_audioOutput->start(&m_audioOutputIODevice);
+ }
+ }
+ m_notifyTimer->start();
+ }
+}
+
+void Engine::suspend()
+{
+ if (QAudio::ActiveState == m_state || QAudio::IdleState == m_state) {
+ switch (m_mode) {
+ case QAudioDevice::Input:
+ m_audioInput->suspend();
+ break;
+ case QAudioDevice::Output:
+ m_audioOutput->suspend();
+ break;
+ default:
+ break;
+ }
+ m_notifyTimer->stop();
+ }
+}
+
+void Engine::setAudioInputDevice(const QAudioDevice &device)
+{
+ if (device.id() != m_audioInputDevice.id()) {
+ m_audioInputDevice = device;
+ initialize();
+ }
+}
+
+void Engine::setAudioOutputDevice(const QAudioDevice &device)
+{
+ if (device.id() != m_audioOutputDevice.id()) {
+ m_audioOutputDevice = device;
+ initialize();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Private slots
+//-----------------------------------------------------------------------------
+
+void Engine::initAudioDevices()
+{
+#if QT_CONFIG(permissions)
+ QMicrophonePermission microphonePermission;
+ switch (qApp->checkPermission(microphonePermission)) {
+ case Qt::PermissionStatus::Undetermined:
+ qApp->requestPermission(microphonePermission, this, &Engine::initAudioDevices);
+ return;
+ case Qt::PermissionStatus::Denied:
+ qWarning("Microphone permission is not granted!");
+ return;
+ case Qt::PermissionStatus::Granted:
+ break;
+ }
+#endif
+ m_availableAudioInputDevices = m_devices->audioInputs();
+ m_audioInputDevice = m_devices->defaultAudioInput();
+ m_availableAudioOutputDevices = m_devices->audioOutputs();
+ m_audioOutputDevice = m_devices->defaultAudioOutput();
+}
+
+void Engine::audioNotify()
+{
+ switch (m_mode) {
+ case QAudioDevice::Input: {
+ const qint64 recordPosition =
+ qMin(m_bufferLength, m_format.bytesForDuration(m_audioInput->processedUSecs()));
+ setRecordPosition(recordPosition);
+ const qint64 levelPosition = m_dataLength - m_levelBufferLength;
+ if (levelPosition >= 0)
+ calculateLevel(levelPosition, m_levelBufferLength);
+ if (m_dataLength >= m_spectrumBufferLength) {
+ const qint64 spectrumPosition = m_dataLength - m_spectrumBufferLength;
+ calculateSpectrum(spectrumPosition);
+ }
+ emit bufferChanged(0, m_dataLength, m_buffer);
+ } break;
+ case QAudioDevice::Output: {
+ const qint64 playPosition = m_format.bytesForDuration(m_audioOutput->processedUSecs());
+ setPlayPosition(qMin(bufferLength(), playPosition));
+ const qint64 levelPosition = playPosition - m_levelBufferLength;
+ const qint64 spectrumPosition = playPosition - m_spectrumBufferLength;
+ if (m_file) {
+ if (levelPosition > m_bufferPosition || spectrumPosition > m_bufferPosition
+ || qMax(m_levelBufferLength, m_spectrumBufferLength) > m_dataLength) {
+ m_bufferPosition = 0;
+ m_dataLength = 0;
+ // Data needs to be read into m_buffer in order to be analysed
+ const qint64 readPos = qMax(qint64(0), qMin(levelPosition, spectrumPosition));
+ const qint64 readEnd = qMin(m_analysisFile->getDevice()->size(),
+ qMax(levelPosition + m_levelBufferLength,
+ spectrumPosition + m_spectrumBufferLength));
+ const qint64 readLen =
+ readEnd - readPos + m_format.bytesForDuration(WaveformWindowDuration);
+ qDebug() << "Engine::audioNotify [1]"
+ << "analysisFileSize" << m_analysisFile->getDevice()->size() << "readPos"
+ << readPos << "readLen" << readLen;
+ if (m_analysisFile->seek(readPos + m_analysisFile->headerLength())) {
+ m_buffer.resize(readLen);
+ m_bufferPosition = readPos;
+ m_dataLength = m_analysisFile->read(m_buffer.data(), readLen);
+ qDebug() << "Engine::audioNotify [2]"
+ << "bufferPosition" << m_bufferPosition << "dataLength"
+ << m_dataLength;
+ } else {
+ qDebug() << "Engine::audioNotify [2]"
+ << "file seek error";
+ }
+ emit bufferChanged(m_bufferPosition, m_dataLength, m_buffer);
+ }
+ } else {
+ if (playPosition >= m_dataLength)
+ stopPlayback();
+ }
+ if (levelPosition >= 0
+ && levelPosition + m_levelBufferLength < m_bufferPosition + m_dataLength)
+ calculateLevel(levelPosition, m_levelBufferLength);
+ if (spectrumPosition >= 0
+ && spectrumPosition + m_spectrumBufferLength < m_bufferPosition + m_dataLength)
+ calculateSpectrum(spectrumPosition);
+ } break;
+ default:
+ break;
+ }
+}
+
+void Engine::audioStateChanged(QAudio::State state)
+{
+ ENGINE_DEBUG << "Engine::audioStateChanged from" << m_state << "to" << state;
+
+ if (QAudio::IdleState == state && m_file && m_file->pos() == m_file->getDevice()->size()) {
+ stopPlayback();
+ } else {
+ if (QAudio::StoppedState == state) {
+ // Check error
+ QAudio::Error error = QAudio::NoError;
+ switch (m_mode) {
+ case QAudioDevice::Input:
+ error = m_audioInput->error();
+ break;
+ case QAudioDevice::Output:
+ error = m_audioOutput->error();
+ break;
+ default:
+ break;
+ }
+ if (QAudio::NoError != error) {
+ emitError(error);
+ reset();
+ return;
+ }
+ }
+ setState(state);
+ }
+}
+
+void Engine::audioDataReady()
+{
+ Q_ASSERT(0 == m_bufferPosition);
+ const qint64 bytesReady = m_audioInput->bytesAvailable();
+ const qint64 bytesSpace = m_buffer.size() - m_dataLength;
+ const qint64 bytesToRead = qMin(bytesReady, bytesSpace);
+
+ const qint64 bytesRead =
+ m_audioInputIODevice->read(m_buffer.data() + m_dataLength, bytesToRead);
+
+ if (bytesRead) {
+ m_dataLength += bytesRead;
+ emit dataLengthChanged(dataLength());
+ }
+
+ if (m_buffer.size() == m_dataLength)
+ stopRecording();
+}
+
+void Engine::spectrumChanged(const FrequencySpectrum &spectrum)
+{
+ ENGINE_DEBUG << "Engine::spectrumChanged"
+ << "pos" << m_spectrumPosition;
+ emit spectrumChanged(m_spectrumPosition, m_spectrumBufferLength, spectrum);
+}
+
+//-----------------------------------------------------------------------------
+// Private functions
+//-----------------------------------------------------------------------------
+
+void Engine::resetAudioDevices()
+{
+ delete m_audioInput;
+ m_audioInput = nullptr;
+ m_audioInputIODevice = nullptr;
+ setRecordPosition(0);
+ delete m_audioOutput;
+ m_audioOutput = nullptr;
+ setPlayPosition(0);
+ m_spectrumPosition = 0;
+ setLevel(0.0, 0.0, 0);
+}
+
+void Engine::reset()
+{
+ stopRecording();
+ stopPlayback();
+ setState(QAudioDevice::Input, QAudio::StoppedState);
+ m_generateTone = false;
+ setFormat(QAudioFormat());
+ delete m_file;
+ m_file = nullptr;
+ delete m_analysisFile;
+ m_analysisFile = nullptr;
+ m_buffer.clear();
+ m_bufferPosition = 0;
+ m_bufferLength = 0;
+ m_dataLength = 0;
+ emit dataLengthChanged(0);
+ resetAudioDevices();
+}
+
+bool Engine::initialize()
+{
+ bool result = false;
+
+ QAudioFormat format = m_format;
+
+ if (selectFormat()) {
+ if (m_format != format) {
+ resetAudioDevices();
+ if (m_file) {
+ emit bufferLengthChanged(bufferLength());
+ emit dataLengthChanged(dataLength());
+ emit bufferChanged(0, 0, m_buffer);
+ setRecordPosition(bufferLength());
+ result = true;
+ } else {
+ m_bufferLength = m_format.bytesForDuration(BufferDurationUs);
+ m_buffer.resize(m_bufferLength);
+ m_buffer.fill(0);
+ emit bufferLengthChanged(bufferLength());
+ if (m_generateTone) {
+ if (0 == m_tone.endFreq) {
+ const qreal nyquist = nyquistFrequency(m_format);
+ m_tone.endFreq = qMin(qreal(SpectrumHighFreq), nyquist);
+ }
+ // Call function defined in utils.h, at global scope
+ ::generateTone(m_tone, m_format, m_buffer);
+ m_dataLength = m_bufferLength;
+ emit dataLengthChanged(dataLength());
+ emit bufferChanged(0, m_dataLength, m_buffer);
+ setRecordPosition(m_bufferLength);
+ result = true;
+ } else {
+ emit bufferChanged(0, 0, m_buffer);
+ m_audioInput = new QAudioSource(m_audioInputDevice, m_format, this);
+ result = true;
+ }
+ }
+ m_audioOutput = new QAudioSink(m_audioOutputDevice, m_format, this);
+ }
+ } else {
+ if (m_file)
+ emit errorMessage(tr("Audio format not supported"), formatToString(m_format));
+ else if (m_generateTone)
+ emit errorMessage(tr("No suitable format found"), "");
+ else
+ emit errorMessage(tr("No common input / output format found"), "");
+ }
+
+ ENGINE_DEBUG << "Engine::initialize"
+ << "m_bufferLength" << m_bufferLength;
+ ENGINE_DEBUG << "Engine::initialize"
+ << "m_dataLength" << m_dataLength;
+ ENGINE_DEBUG << "Engine::initialize"
+ << "format" << m_format;
+
+ return result;
+}
+
+bool Engine::selectFormat()
+{
+ bool foundSupportedFormat = false;
+
+ if (m_file || QAudioFormat() != m_format) {
+ QAudioFormat format = m_format;
+ if (m_file)
+ // Header is read from the WAV file; just need to check whether
+ // it is supported by the audio output device
+ format = m_file->audioFormat();
+ if (m_audioOutputDevice.isFormatSupported(format)) {
+ setFormat(format);
+ foundSupportedFormat = true;
+ }
+ } else {
+
+ int minSampleRate = qMin(m_audioInputDevice.minimumSampleRate(),
+ m_audioOutputDevice.minimumSampleRate());
+ int maxSampleRate = qMin(m_audioInputDevice.maximumSampleRate(),
+ m_audioOutputDevice.maximumSampleRate());
+ int minChannelCount = qMin(m_audioInputDevice.minimumChannelCount(),
+ m_audioOutputDevice.minimumChannelCount());
+ int maxChannelCount = qMin(m_audioInputDevice.maximumChannelCount(),
+ m_audioOutputDevice.maximumChannelCount());
+
+ QAudioFormat format;
+ format.setSampleFormat(QAudioFormat::Int16);
+ format.setSampleRate(qBound(minSampleRate, 48000, maxSampleRate));
+ format.setChannelCount(qBound(minChannelCount, 2, maxChannelCount));
+
+ const bool inputSupport = m_audioInputDevice.isFormatSupported(format);
+ const bool outputSupport = m_audioOutputDevice.isFormatSupported(format);
+ if (inputSupport && outputSupport)
+ foundSupportedFormat = true;
+
+ setFormat(format);
+ }
+
+ return foundSupportedFormat;
+}
+
+void Engine::stopRecording()
+{
+ if (m_audioInput) {
+ m_audioInput->stop();
+ QCoreApplication::instance()->processEvents();
+ m_audioInput->disconnect();
+ }
+ m_audioInputIODevice = nullptr;
+ m_notifyTimer->stop();
+
+#ifdef DUMP_AUDIO
+ dumpData();
+#endif
+}
+
+void Engine::stopPlayback()
+{
+ if (m_audioOutput) {
+ m_audioOutput->stop();
+ QCoreApplication::instance()->processEvents();
+ m_audioOutput->disconnect();
+ setPlayPosition(0);
+ }
+ m_notifyTimer->stop();
+}
+
+void Engine::setState(QAudio::State state)
+{
+ const bool changed = (m_state != state);
+ m_state = state;
+ if (changed)
+ emit stateChanged(m_mode, m_state);
+}
+
+void Engine::setState(QAudioDevice::Mode mode, QAudio::State state)
+{
+ const bool changed = (m_mode != mode || m_state != state);
+ m_mode = mode;
+ m_state = state;
+ if (changed)
+ emit stateChanged(m_mode, m_state);
+}
+
+void Engine::setRecordPosition(qint64 position, bool forceEmit)
+{
+ const bool changed = (m_recordPosition != position);
+ m_recordPosition = position;
+ if (changed || forceEmit)
+ emit recordPositionChanged(m_recordPosition);
+}
+
+void Engine::setPlayPosition(qint64 position, bool forceEmit)
+{
+ const bool changed = (m_playPosition != position);
+ m_playPosition = position;
+ if (changed || forceEmit)
+ emit playPositionChanged(m_playPosition);
+}
+
+void Engine::calculateLevel(qint64 position, qint64 length)
+{
+#ifdef DISABLE_LEVEL
+ Q_UNUSED(position);
+ Q_UNUSED(length);
+#else
+ Q_ASSERT(position + length <= m_bufferPosition + m_dataLength);
+
+ qreal peakLevel = 0.0;
+
+ qreal sum = 0.0;
+ const char *ptr = m_buffer.constData() + position - m_bufferPosition;
+ const char *const end = ptr + length;
+ while (ptr < end) {
+ const qint16 value = *reinterpret_cast<const qint16 *>(ptr);
+ const qreal fracValue = pcmToReal(value);
+ peakLevel = qMax(peakLevel, fracValue);
+ sum += fracValue * fracValue;
+ ptr += 2;
+ }
+ const int numSamples = length / 2;
+ qreal rmsLevel = sqrt(sum / numSamples);
+
+ rmsLevel = qMax(qreal(0.0), rmsLevel);
+ rmsLevel = qMin(qreal(1.0), rmsLevel);
+ setLevel(rmsLevel, peakLevel, numSamples);
+
+ ENGINE_DEBUG << "Engine::calculateLevel"
+ << "pos" << position << "len" << length << "rms" << rmsLevel << "peak"
+ << peakLevel;
+#endif
+}
+
+void Engine::calculateSpectrum(qint64 position)
+{
+#ifdef DISABLE_SPECTRUM
+ Q_UNUSED(position);
+#else
+ Q_ASSERT(position + m_spectrumBufferLength <= m_bufferPosition + m_dataLength);
+ Q_ASSERT(0 == m_spectrumBufferLength % 2); // constraint of FFT algorithm
+
+ // QThread::currentThread is marked 'for internal use only', but
+ // we're only using it for debug output here, so it's probably OK :)
+ ENGINE_DEBUG << "Engine::calculateSpectrum" << QThread::currentThread() << "count" << m_count
+ << "pos" << position << "len" << m_spectrumBufferLength
+ << "spectrumAnalyser.isReady" << m_spectrumAnalyser.isReady();
+
+ if (m_spectrumAnalyser.isReady()) {
+ m_spectrumBuffer = QByteArray::fromRawData(
+ m_buffer.constData() + position - m_bufferPosition, m_spectrumBufferLength);
+ m_spectrumPosition = position;
+ m_spectrumAnalyser.calculate(m_spectrumBuffer, m_format);
+ }
+#endif
+}
+
+void Engine::setFormat(const QAudioFormat &format)
+{
+ const bool changed = (format != m_format);
+ m_format = format;
+ m_levelBufferLength = m_format.bytesForDuration(LevelWindowUs);
+ m_spectrumBufferLength = SpectrumLengthSamples * format.bytesPerFrame();
+ if (changed)
+ emit formatChanged(m_format);
+}
+
+void Engine::setLevel(qreal rmsLevel, qreal peakLevel, int numSamples)
+{
+ m_rmsLevel = rmsLevel;
+ m_peakLevel = peakLevel;
+ emit levelChanged(m_rmsLevel, m_peakLevel, numSamples);
+}
+
+void Engine::emitError(QAudio::Error error)
+{
+ QString errorString;
+ switch (error) {
+ case QAudio::NoError:
+ errorString = tr("NoError");
+ break;
+ case QAudio::OpenError:
+ errorString = tr("OpenError: An error occurred opening the audio device.");
+ break;
+ case QAudio::IOError:
+ errorString = tr("IOError: An error occurred during read/write of audio device.");
+ break;
+ case QAudio::UnderrunError:
+ errorString = tr("UnderrunError: Audio data is not being fed"
+ "to the audio device at a fast enough rate.");
+ break;
+ case QAudio::FatalError:
+ errorString = tr("FatalError: A non-recoverable error has occurred,"
+ "the audio device is not usable at this time.");
+ break;
+ }
+
+ emit errorMessage(tr("Audio Device"), errorString);
+}
+
+#ifdef DUMP_DATA
+void Engine::createOutputDir()
+{
+ m_outputDir.setPath("output");
+
+ // Ensure output directory exists and is empty
+ if (m_outputDir.exists()) {
+ const QStringList files = m_outputDir.entryList(QDir::Files);
+ for (const QString &file : files)
+ m_outputDir.remove(file);
+ } else {
+ QDir::current().mkdir("output");
+ }
+}
+#endif // DUMP_DATA
+
+#ifdef DUMP_AUDIO
+void Engine::dumpData()
+{
+ const QString txtFileName = m_outputDir.filePath("data.txt");
+ QFile txtFile(txtFileName);
+ txtFile.open(QFile::WriteOnly | QFile::Text);
+ QTextStream stream(&txtFile);
+ const qint16 *ptr = reinterpret_cast<const qint16 *>(m_buffer.constData());
+ const int numSamples = m_dataLength / (2 * m_format.channels());
+ for (int i = 0; i < numSamples; ++i) {
+ stream << i << "\t" << *ptr << "\n";
+ ptr += m_format.channels();
+ }
+
+ const QString pcmFileName = m_outputDir.filePath("data.pcm");
+ QFile pcmFile(pcmFileName);
+ pcmFile.open(QFile::WriteOnly);
+ pcmFile.write(m_buffer.constData(), m_dataLength);
+}
+#endif // DUMP_AUDIO
+
+#include "moc_engine.cpp"