demos/spectrum/app/engine.cpp
changeset 25 e24348a560a6
child 29 b72c6db6890b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/spectrum/app/engine.cpp	Fri Jun 11 14:24:45 2010 +0300
@@ -0,0 +1,752 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "engine.h"
+#include "tonegenerator.h"
+#include "utils.h"
+
+#include <math.h>
+
+#include <QCoreApplication>
+#include <QMetaObject>
+#include <QSet>
+#include <QtMultimedia/QAudioInput>
+#include <QtMultimedia/QAudioOutput>
+#include <QDebug>
+#include <QThread>
+#include <QFile>
+
+//-----------------------------------------------------------------------------
+// Constants
+//-----------------------------------------------------------------------------
+
+const qint64 BufferDurationUs       = 10 * 1000000;
+const int    NotifyIntervalMs       = 100;
+
+// Size of the level calculation window in microseconds
+const int    LevelWindowUs          = 0.1 * 1000000;
+
+
+//-----------------------------------------------------------------------------
+// Helper functions
+//-----------------------------------------------------------------------------
+
+QDebug& operator<<(QDebug &debug, const QAudioFormat &format)
+{
+    debug << format.frequency() << "Hz"
+          << format.channels() << "channels";
+    return debug;
+}
+
+//-----------------------------------------------------------------------------
+// Constructor and destructor
+//-----------------------------------------------------------------------------
+
+Engine::Engine(QObject *parent)
+    :   QObject(parent)
+    ,   m_mode(QAudio::AudioInput)
+    ,   m_state(QAudio::StoppedState)
+    ,   m_generateTone(false)
+    ,   m_file(0)
+    ,   m_availableAudioInputDevices
+            (QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
+    ,   m_audioInputDevice(QAudioDeviceInfo::defaultInputDevice())
+    ,   m_audioInput(0)
+    ,   m_audioInputIODevice(0)
+    ,   m_recordPosition(0)
+    ,   m_availableAudioOutputDevices
+            (QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
+    ,   m_audioOutputDevice(QAudioDeviceInfo::defaultOutputDevice())
+    ,   m_audioOutput(0)
+    ,   m_playPosition(0)
+    ,   m_dataLength(0)
+    ,   m_rmsLevel(0.0)
+    ,   m_peakLevel(0.0)
+    ,   m_spectrumLengthBytes(0)
+    ,   m_spectrumAnalyser()
+    ,   m_spectrumPosition(0)
+    ,   m_count(0)
+{
+    qRegisterMetaType<FrequencySpectrum>("FrequencySpectrum");
+    CHECKED_CONNECT(&m_spectrumAnalyser,
+                    SIGNAL(spectrumChanged(FrequencySpectrum)),
+                    this,
+                    SLOT(spectrumChanged(FrequencySpectrum)));
+
+    initialize();
+
+#ifdef DUMP_DATA
+    createOutputDir();
+#endif
+
+#ifdef DUMP_SPECTRUM
+    m_spectrumAnalyser.setOutputPath(outputPath());
+#endif
+}
+
+Engine::~Engine()
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+//-----------------------------------------------------------------------------
+
+bool Engine::loadFile(const QString &fileName)
+{
+    bool result = false;
+    m_generateTone = false;
+
+    Q_ASSERT(!fileName.isEmpty());
+    Q_ASSERT(!m_file);
+    m_file = new QFile(fileName, this);
+    m_file->setFileName(fileName);
+    Q_ASSERT(m_file->exists());
+    if (m_file->open(QFile::ReadOnly)) {
+        m_wavFile.readHeader(*m_file);
+        if (isPCMS16LE(m_wavFile.format())) {
+            result = initialize();
+        } else {
+            emit errorMessage(tr("Audio format not supported"),
+                              formatToString(m_wavFile.format()));
+        }
+    } else {
+        emit errorMessage(tr("Could not open file"), fileName);
+    }
+
+    delete m_file;
+    m_file = 0;
+
+    return result;
+}
+
+bool Engine::generateTone(const Tone &tone)
+{
+    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_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()
+{
+    ENGINE_DEBUG << "Engine::initializeRecord";
+    Q_ASSERT(!m_file);
+    m_generateTone = false;
+    m_tone = SweptTone();
+    return initialize();
+}
+
+qint64 Engine::bufferDuration() const
+{
+    return BufferDurationUs;
+}
+
+qint64 Engine::dataDuration() const
+{
+    qint64 result = 0;
+    if (QAudioFormat() != m_format)
+        result = audioDuration(m_format, m_dataLength);
+    return result;
+}
+
+qint64 Engine::audioBufferLength() const
+{
+    qint64 length = 0;
+    if (QAudio::ActiveState == m_state || QAudio::IdleState == m_state) {
+        Q_ASSERT(QAudioFormat() != m_format);
+        switch (m_mode) {
+        case QAudio::AudioInput:
+            length = m_audioInput->bufferSize();
+            break;
+        case QAudio::AudioOutput:
+            length = m_audioOutput->bufferSize();
+            break;
+        }
+    }
+    return length;
+}
+
+void Engine::setWindowFunction(WindowFunction type)
+{
+    m_spectrumAnalyser.setWindowFunction(type);
+}
+
+
+//-----------------------------------------------------------------------------
+// Public slots
+//-----------------------------------------------------------------------------
+
+void Engine::startRecording()
+{
+    if (m_audioInput) {
+        if (QAudio::AudioInput == m_mode &&
+            QAudio::SuspendedState == m_state) {
+            m_audioInput->resume();
+        } else {
+            m_spectrumAnalyser.cancelCalculation();
+            spectrumChanged(0, 0, FrequencySpectrum());
+
+            m_buffer.fill(0);
+            setRecordPosition(0, true);
+            stopPlayback();
+            m_mode = QAudio::AudioInput;
+            CHECKED_CONNECT(m_audioInput, SIGNAL(stateChanged(QAudio::State)),
+                            this, SLOT(audioStateChanged(QAudio::State)));
+            CHECKED_CONNECT(m_audioInput, SIGNAL(notify()),
+                            this, SLOT(audioNotify()));
+            m_count = 0;
+            m_dataLength = 0;
+            emit dataDurationChanged(0);
+            m_audioInputIODevice = m_audioInput->start();
+            CHECKED_CONNECT(m_audioInputIODevice, SIGNAL(readyRead()),
+                            this, SLOT(audioDataReady()));
+        }
+    }
+}
+
+void Engine::startPlayback()
+{
+    if (m_audioOutput) {
+        if (QAudio::AudioOutput == 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();
+            spectrumChanged(0, 0, FrequencySpectrum());
+
+            setPlayPosition(0, true);
+            stopRecording();
+            m_mode = QAudio::AudioOutput;
+            CHECKED_CONNECT(m_audioOutput, SIGNAL(stateChanged(QAudio::State)),
+                            this, SLOT(audioStateChanged(QAudio::State)));
+            CHECKED_CONNECT(m_audioOutput, SIGNAL(notify()),
+                            this, SLOT(audioNotify()));
+            m_count = 0;
+            m_audioOutputIODevice.close();
+            m_audioOutputIODevice.setBuffer(&m_buffer);
+            m_audioOutputIODevice.open(QIODevice::ReadOnly);
+            m_audioOutput->start(&m_audioOutputIODevice);
+        }
+    }
+}
+
+void Engine::suspend()
+{
+    if (QAudio::ActiveState == m_state ||
+        QAudio::IdleState == m_state) {
+        switch (m_mode) {
+        case QAudio::AudioInput:
+            m_audioInput->suspend();
+            break;
+        case QAudio::AudioOutput:
+            m_audioOutput->suspend();
+            break;
+        }
+    }
+}
+
+void Engine::setAudioInputDevice(const QAudioDeviceInfo &device)
+{
+    if (device.deviceName() != m_audioInputDevice.deviceName()) {
+        m_audioInputDevice = device;
+        initialize();
+    }
+}
+
+void Engine::setAudioOutputDevice(const QAudioDeviceInfo &device)
+{
+    if (device.deviceName() != m_audioOutputDevice.deviceName()) {
+        m_audioOutputDevice = device;
+        initialize();
+    }
+}
+
+
+//-----------------------------------------------------------------------------
+// Private slots
+//-----------------------------------------------------------------------------
+
+void Engine::audioNotify()
+{
+    switch (m_mode) {
+    case QAudio::AudioInput: {
+            const qint64 recordPosition =
+                    qMin(BufferDurationUs, m_audioInput->processedUSecs());
+            setRecordPosition(recordPosition);
+
+            // Calculate level of most recently captured data
+            qint64 levelLength = audioLength(m_format, LevelWindowUs);
+            levelLength = qMin(m_dataLength, levelLength);
+            const qint64 levelPosition = m_dataLength - levelLength;
+            calculateLevel(levelPosition, levelLength);
+
+            // Calculate spectrum of most recently captured data
+            if (m_dataLength >= m_spectrumLengthBytes) {
+                const qint64 spectrumPosition = m_dataLength - m_spectrumLengthBytes;
+                calculateSpectrum(spectrumPosition);
+            }
+        }
+        break;
+    case QAudio::AudioOutput: {
+            const qint64 playPosition =
+                    qMin(dataDuration(), m_audioOutput->processedUSecs());
+            setPlayPosition(playPosition);
+
+            qint64 analysisPosition = audioLength(m_format, playPosition);
+
+            // Calculate level of data starting at current playback position
+            const qint64 levelLength = audioLength(m_format, LevelWindowUs);
+            if (analysisPosition + levelLength < m_dataLength)
+                calculateLevel(analysisPosition, levelLength);
+
+            if (analysisPosition + m_spectrumLengthBytes < m_dataLength)
+                calculateSpectrum(analysisPosition);
+
+            if (dataDuration() == playPosition)
+                stopPlayback();
+        }
+        break;
+    }
+}
+
+void Engine::audioStateChanged(QAudio::State state)
+{
+    ENGINE_DEBUG << "Engine::audioStateChanged from" << m_state
+                 << "to" << state;
+
+    if (QAudio::StoppedState == state) {
+        // Check error
+        QAudio::Error error = QAudio::NoError;
+        switch (m_mode) {
+        case QAudio::AudioInput:
+            error = m_audioInput->error();
+            break;
+        case QAudio::AudioOutput:
+            error = m_audioOutput->error();
+            break;
+        }
+        if (QAudio::NoError != error) {
+            reset();
+            return;
+        }
+    }
+    setState(state);
+}
+
+void Engine::audioDataReady()
+{
+    const qint64 bytesReady = m_audioInput->bytesReady();
+    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;
+
+        const qint64 duration = audioDuration(m_format, m_dataLength);
+        emit dataDurationChanged(duration);
+    }
+
+    if (m_buffer.size() == m_dataLength)
+        stopRecording();
+}
+
+void Engine::spectrumChanged(const FrequencySpectrum &spectrum)
+{
+    ENGINE_DEBUG << "Engine::spectrumChanged" << "pos" << m_spectrumPosition;
+    const qint64 positionUs = audioDuration(m_format, m_spectrumPosition);
+    const qint64 lengthUs = audioDuration(m_format, m_spectrumLengthBytes);
+    emit spectrumChanged(positionUs, lengthUs, spectrum);
+}
+
+
+//-----------------------------------------------------------------------------
+// Private functions
+//-----------------------------------------------------------------------------
+
+void Engine::reset()
+{
+    stopRecording();
+    stopPlayback();
+    setState(QAudio::AudioInput, QAudio::StoppedState);
+    setFormat(QAudioFormat());
+    delete m_audioInput;
+    m_audioInput = 0;
+    m_audioInputIODevice = 0;
+    setRecordPosition(0);
+    delete m_audioOutput;
+    m_audioOutput = 0;
+    setPlayPosition(0);
+    m_buffer.clear();
+    m_dataLength = 0;
+    m_spectrumPosition = 0;
+    emit dataDurationChanged(0);
+    setLevel(0.0, 0.0, 0);
+}
+
+bool Engine::initialize()
+{
+    bool result = false;
+
+    reset();
+
+    if (selectFormat()) {
+        const qint64 bufferLength = audioLength(m_format, BufferDurationUs);
+        m_buffer.resize(bufferLength);
+        m_buffer.fill(0);
+        emit bufferDurationChanged(BufferDurationUs);
+
+        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_buffer.size();
+            emit dataDurationChanged(bufferDuration());
+            setRecordPosition(bufferDuration());
+            result = true;
+        } else if (m_file) {
+            const qint64 length = m_wavFile.readData(*m_file, m_buffer, m_format);
+            if (length) {
+                m_dataLength = length;
+                emit dataDurationChanged(dataDuration());
+                setRecordPosition(dataDuration());
+                result = true;
+            }
+        } else {
+            m_audioInput = new QAudioInput(m_audioInputDevice, m_format, this);
+            m_audioInput->setNotifyInterval(NotifyIntervalMs);
+            result = true;
+        }
+
+        m_audioOutput = new QAudioOutput(m_audioOutputDevice, m_format, this);
+        m_audioOutput->setNotifyInterval(NotifyIntervalMs);
+        m_spectrumLengthBytes = SpectrumLengthSamples *
+                                (m_format.sampleSize() / 8) * m_format.channels();
+    } 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" << "format" << m_format;
+
+    return result;
+}
+
+bool Engine::selectFormat()
+{
+    bool foundSupportedFormat = false;
+
+    if (m_file) {
+        // Header is read from the WAV file; just need to check whether
+        // it is supported by the audio output device
+        QAudioFormat format = m_wavFile.format();
+        if (m_audioOutputDevice.isFormatSupported(m_wavFile.format())) {
+            setFormat(m_wavFile.format());
+            foundSupportedFormat = true;
+        } else {
+            // Try flipping mono <-> stereo
+            const int channels = (format.channels() == 1) ? 2 : 1;
+            format.setChannels(channels);
+            if (m_audioOutputDevice.isFormatSupported(format)) {
+                setFormat(format);
+                foundSupportedFormat = true;
+            }
+        }
+    } else {
+
+        QList<int> frequenciesList;
+    #ifdef Q_OS_WIN
+        // The Windows audio backend does not correctly report format support
+        // (see QTBUG-9100).  Furthermore, although the audio subsystem captures
+        // at 11025Hz, the resulting audio is corrupted.
+        frequenciesList += 8000;
+    #endif
+
+        if (!m_generateTone)
+            frequenciesList += m_audioInputDevice.supportedFrequencies();
+
+        frequenciesList += m_audioOutputDevice.supportedFrequencies();
+        frequenciesList = frequenciesList.toSet().toList(); // remove duplicates
+        qSort(frequenciesList);
+        ENGINE_DEBUG << "Engine::initialize frequenciesList" << frequenciesList;
+
+        QList<int> channelsList;
+        channelsList += m_audioInputDevice.supportedChannels();
+        channelsList += m_audioOutputDevice.supportedChannels();
+        channelsList = channelsList.toSet().toList();
+        qSort(channelsList);
+        ENGINE_DEBUG << "Engine::initialize channelsList" << channelsList;
+
+        QAudioFormat format;
+        format.setByteOrder(QAudioFormat::LittleEndian);
+        format.setCodec("audio/pcm");
+        format.setSampleSize(16);
+        format.setSampleType(QAudioFormat::SignedInt);
+        int frequency, channels;
+        foreach (frequency, frequenciesList) {
+            if (foundSupportedFormat)
+                break;
+            format.setFrequency(frequency);
+            foreach (channels, channelsList) {
+                format.setChannels(channels);
+                const bool inputSupport = m_generateTone ||
+                                          m_audioInputDevice.isFormatSupported(format);
+                const bool outputSupport = m_audioOutputDevice.isFormatSupported(format);
+                ENGINE_DEBUG << "Engine::initialize checking " << format
+                             << "input" << inputSupport
+                             << "output" << outputSupport;
+                if (inputSupport && outputSupport) {
+                    foundSupportedFormat = true;
+                    break;
+                }
+            }
+        }
+
+        if (!foundSupportedFormat)
+            format = QAudioFormat();
+
+        setFormat(format);
+    }
+
+    return foundSupportedFormat;
+}
+
+void Engine::stopRecording()
+{
+    if (m_audioInput) {
+        m_audioInput->stop();
+        QCoreApplication::instance()->processEvents();
+        m_audioInput->disconnect();
+    }
+    m_audioInputIODevice = 0;
+
+#ifdef DUMP_AUDIO
+    dumpData();
+#endif
+}
+
+void Engine::stopPlayback()
+{
+    if (m_audioOutput) {
+        m_audioOutput->stop();
+        QCoreApplication::instance()->processEvents();
+        m_audioOutput->disconnect();
+        setPlayPosition(0);
+    }
+}
+
+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(QAudio::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_dataLength);
+
+    qreal peakLevel = 0.0;
+
+    qreal sum = 0.0;
+    const char *ptr = m_buffer.constData() + position;
+    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_spectrumLengthBytes <= m_dataLength);
+    Q_ASSERT(0 == m_spectrumLengthBytes % 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_spectrumLengthBytes
+                 << "spectrumAnalyser.isReady" << m_spectrumAnalyser.isReady();
+
+    if(m_spectrumAnalyser.isReady()) {
+        m_spectrumBuffer = QByteArray::fromRawData(m_buffer.constData() + position,
+                                                   m_spectrumLengthBytes);
+        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;
+    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);
+}
+
+#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);
+        QString file;
+        foreach (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