diff options
-rw-r--r-- | demos/spectrum/app/engine.cpp | 315 | ||||
-rw-r--r-- | demos/spectrum/app/engine.h | 57 | ||||
-rw-r--r-- | demos/spectrum/app/mainwidget.cpp | 46 | ||||
-rw-r--r-- | demos/spectrum/app/mainwidget.h | 9 | ||||
-rw-r--r-- | demos/spectrum/app/progressbar.cpp | 26 | ||||
-rw-r--r-- | demos/spectrum/app/progressbar.h | 4 | ||||
-rw-r--r-- | demos/spectrum/app/utils.cpp | 4 | ||||
-rw-r--r-- | demos/spectrum/app/waveform.cpp | 120 | ||||
-rw-r--r-- | demos/spectrum/app/waveform.h | 14 | ||||
-rw-r--r-- | demos/spectrum/app/wavfile.cpp | 205 | ||||
-rw-r--r-- | demos/spectrum/app/wavfile.h | 36 |
11 files changed, 369 insertions, 467 deletions
diff --git a/demos/spectrum/app/engine.cpp b/demos/spectrum/app/engine.cpp index 0495fe3..cd847fe 100644 --- a/demos/spectrum/app/engine.cpp +++ b/demos/spectrum/app/engine.cpp @@ -85,6 +85,7 @@ Engine::Engine(QObject *parent) , m_state(QAudio::StoppedState) , m_generateTone(false) , m_file(0) + , m_analysisFile(0) , m_availableAudioInputDevices (QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) , m_audioInputDevice(QAudioDeviceInfo::defaultInputDevice()) @@ -96,15 +97,19 @@ Engine::Engine(QObject *parent) , m_audioOutputDevice(QAudioDeviceInfo::defaultOutputDevice()) , m_audioOutput(0) , m_playPosition(0) + , m_bufferPosition(0) + , m_bufferLength(0) , m_dataLength(0) + , m_levelBufferLength(0) , m_rmsLevel(0.0) , m_peakLevel(0.0) - , m_spectrumLengthBytes(0) + , m_spectrumBufferLength(0) , m_spectrumAnalyser() , m_spectrumPosition(0) , m_count(0) { qRegisterMetaType<FrequencySpectrum>("FrequencySpectrum"); + qRegisterMetaType<WindowFunction>("WindowFunction"); CHECKED_CONNECT(&m_spectrumAnalyser, SIGNAL(spectrumChanged(FrequencySpectrum)), this, @@ -132,34 +137,33 @@ Engine::~Engine() bool Engine::loadFile(const QString &fileName) { + reset(); bool result = false; - m_generateTone = false; - - Q_ASSERT(!fileName.isEmpty()); + Q_ASSERT(!m_generateTone); 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())) { + Q_ASSERT(!fileName.isEmpty()); + m_file = new WavFile(this); + if (m_file->open(fileName)) { + if (isPCMS16LE(m_file->fileFormat())) { result = initialize(); } else { emit errorMessage(tr("Audio format not supported"), - formatToString(m_wavFile.format())); + formatToString(m_file->fileFormat())); } } else { emit errorMessage(tr("Could not open file"), fileName); } - - delete m_file; - m_file = 0; - + if (result) { + m_analysisFile = new WavFile(this); + m_analysisFile->open(fileName); + } return result; } bool Engine::generateTone(const Tone &tone) { + reset(); + Q_ASSERT(!m_generateTone); Q_ASSERT(!m_file); m_generateTone = true; m_tone = tone; @@ -172,6 +176,7 @@ bool Engine::generateTone(const Tone &tone) bool Engine::generateSweptTone(qreal amplitude) { + Q_ASSERT(!m_generateTone); Q_ASSERT(!m_file); m_generateTone = true; m_tone.startFreq = 1; @@ -185,41 +190,18 @@ bool Engine::generateSweptTone(qreal amplitude) 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::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 Engine::bufferLength() 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; + return m_file ? m_file->size() : m_bufferLength; } void Engine::setWindowFunction(WindowFunction type) @@ -252,7 +234,7 @@ void Engine::startRecording() this, SLOT(audioNotify())); m_count = 0; m_dataLength = 0; - emit dataDurationChanged(0); + emit dataLengthChanged(0); m_audioInputIODevice = m_audioInput->start(); CHECKED_CONNECT(m_audioInputIODevice, SIGNAL(readyRead()), this, SLOT(audioDataReady())); @@ -275,7 +257,6 @@ void Engine::startPlayback() } else { m_spectrumAnalyser.cancelCalculation(); spectrumChanged(0, 0, FrequencySpectrum()); - setPlayPosition(0, true); stopRecording(); m_mode = QAudio::AudioOutput; @@ -284,10 +265,17 @@ void Engine::startPlayback() 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); + if (m_file) { + m_file->seek(0); + m_bufferPosition = 0; + m_dataLength = 0; + m_audioOutput->start(m_file); + } else { + m_audioOutputIODevice.close(); + m_audioOutputIODevice.setBuffer(&m_buffer); + m_audioOutputIODevice.open(QIODevice::ReadOnly); + m_audioOutput->start(&m_audioOutputIODevice); + } } } } @@ -332,40 +320,55 @@ void Engine::audioNotify() { switch (m_mode) { case QAudio::AudioInput: { - const qint64 recordPosition = - qMin(BufferDurationUs, m_audioInput->processedUSecs()); + const qint64 recordPosition = qMin(m_bufferLength, audioLength(m_format, 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; + 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 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(); + const qint64 playPosition = audioLength(m_format, 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->size(), qMax(levelPosition + m_levelBufferLength, spectrumPosition + m_spectrumBufferLength)); + const qint64 readLen = readEnd - readPos + audioLength(m_format, WaveformWindowDuration); + qDebug() << "Engine::audioNotify [1]" + << "analysisFileSize" << m_analysisFile->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; } @@ -376,27 +379,32 @@ 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; + if (QAudio::IdleState == state && m_file && m_file->pos() == m_file->size()) { + stopPlayback(); + } else { + 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); } - setState(state); } void Engine::audioDataReady() { + Q_ASSERT(0 == m_bufferPosition); const qint64 bytesReady = m_audioInput->bytesReady(); const qint64 bytesSpace = m_buffer.size() - m_dataLength; const qint64 bytesToRead = qMin(bytesReady, bytesSpace); @@ -407,9 +415,7 @@ void Engine::audioDataReady() if (bytesRead) { m_dataLength += bytesRead; - - const qint64 duration = audioDuration(m_format, m_dataLength); - emit dataDurationChanged(duration); + emit dataLengthChanged(dataLength()); } if (m_buffer.size() == m_dataLength) @@ -419,9 +425,7 @@ void Engine::audioDataReady() 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); + emit spectrumChanged(m_spectrumPosition, m_spectrumBufferLength, spectrum); } @@ -429,12 +433,8 @@ void Engine::spectrumChanged(const FrequencySpectrum &spectrum) // Private functions //----------------------------------------------------------------------------- -void Engine::reset() +void Engine::resetAudioDevices() { - stopRecording(); - stopPlayback(); - setState(QAudio::AudioInput, QAudio::StoppedState); - setFormat(QAudioFormat()); delete m_audioInput; m_audioInput = 0; m_audioInputIODevice = 0; @@ -442,13 +442,29 @@ void Engine::reset() 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); } +void Engine::reset() +{ + stopRecording(); + stopPlayback(); + setState(QAudio::AudioInput, QAudio::StoppedState); + setFormat(QAudioFormat()); + m_generateTone = false; + delete m_file; + m_file = 0; + delete m_analysisFile; + m_analysisFile = 0; + m_buffer.clear(); + m_bufferPosition = 0; + m_bufferLength = 0; + m_dataLength = 0; + emit dataLengthChanged(0); + resetAudioDevices(); +} + bool Engine::initialize() { bool result = false; @@ -457,45 +473,39 @@ bool Engine::initialize() if (selectFormat()) { if (m_format != format) { - format = m_format; - reset(); - m_format = format; - - 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()); + resetAudioDevices(); + if (m_file) { + emit bufferLengthChanged(bufferLength()); + emit dataLengthChanged(dataLength()); + emit bufferChanged(0, 0, m_buffer); + setRecordPosition(bufferLength()); 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()); + } else { + m_bufferLength = audioLength(m_format, 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 QAudioInput(m_audioInputDevice, m_format, this); + m_audioInput->setNotifyInterval(NotifyIntervalMs); 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) @@ -507,6 +517,8 @@ bool Engine::initialize() 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; @@ -521,18 +533,10 @@ bool Engine::selectFormat() 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_wavFile.format(); + format = m_file->fileFormat(); if (m_audioOutputDevice.isFormatSupported(format)) { setFormat(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 { @@ -656,12 +660,12 @@ void Engine::calculateLevel(qint64 position, qint64 length) Q_UNUSED(position) Q_UNUSED(length) #else - Q_ASSERT(position + length <= m_dataLength); + Q_ASSERT(position + length <= m_bufferPosition + m_dataLength); qreal peakLevel = 0.0; qreal sum = 0.0; - const char *ptr = m_buffer.constData() + position; + 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); @@ -687,18 +691,18 @@ 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 + 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_spectrumLengthBytes + << "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_spectrumLengthBytes); + m_spectrumBuffer = QByteArray::fromRawData(m_buffer.constData() + position - m_bufferPosition, + m_spectrumBufferLength); m_spectrumPosition = position; m_spectrumAnalyser.calculate(m_spectrumBuffer, m_format); } @@ -709,6 +713,9 @@ void Engine::setFormat(const QAudioFormat &format) { const bool changed = (format != m_format); m_format = format; + m_levelBufferLength = audioLength(m_format, LevelWindowUs); + m_spectrumBufferLength = SpectrumLengthSamples * + (m_format.sampleSize() / 8) * m_format.channels(); if (changed) emit formatChanged(m_format); } diff --git a/demos/spectrum/app/engine.h b/demos/spectrum/app/engine.h index e14ac83..c97083e 100644 --- a/demos/spectrum/app/engine.h +++ b/demos/spectrum/app/engine.h @@ -91,12 +91,6 @@ public: QAudio::State state() const { return m_state; } /** - * \return Reference to internal audio buffer - * \note This reference is valid for the lifetime of the Engine - */ - const QByteArray& buffer() const { return m_buffer; } - - /** * \return Current audio format * \note May be QAudioFormat() if engine is not initialized */ @@ -129,7 +123,7 @@ public: /** * Position of the audio input device. - * \return Position in microseconds. + * \return Position in bytes. */ qint64 recordPosition() const { return m_recordPosition; } @@ -147,27 +141,21 @@ public: /** * Position of the audio output device. - * \return Position in microseconds. + * \return Position in bytes. */ qint64 playPosition() const { return m_playPosition; } /** * Length of the internal engine buffer. - * \return Buffer length in microseconds. + * \return Buffer length in bytes. */ - qint64 bufferDuration() const; + qint64 bufferLength() const; /** * Amount of data held in the buffer. - * \return Data duration in microseconds. + * \return Data length in bytes. */ - qint64 dataDuration() const; - - /** - * Returns the size of the underlying audio buffer in bytes. - * This should be an approximation of the capture latency. - */ - qint64 audioBufferLength() const; + qint64 dataLength() const { return m_dataLength; } /** * Set window function applied to audio data before spectral analysis. @@ -203,23 +191,23 @@ signals: * Length of buffer has changed. * \param duration Duration in microseconds */ - void bufferDurationChanged(qint64 duration); + void bufferLengthChanged(qint64 duration); /** * Amount of data in buffer has changed. - * \param duration Duration of data in microseconds + * \param Length of data in bytes */ - void dataDurationChanged(qint64 duration); + void dataLengthChanged(qint64 duration); /** * Position of the audio input device has changed. - * \param position Position in microseconds + * \param position Position in bytes */ void recordPositionChanged(qint64 position); /** * Position of the audio output device has changed. - * \param position Position in microseconds + * \param position Position in bytes */ void playPositionChanged(qint64 position); @@ -233,12 +221,19 @@ signals: /** * Spectrum has changed. - * \param position Position of start of window in microseconds - * \param length Length of window in microseconds + * \param position Position of start of window in bytes + * \param length Length of window in bytes * \param spectrum Resulting frequency spectrum */ void spectrumChanged(qint64 position, qint64 length, const FrequencySpectrum &spectrum); + /** + * Buffer containing audio data has changed. + * \param position Position of start of buffer in bytes + * \param buffer Buffer + */ + void bufferChanged(qint64 position, qint64 length, const QByteArray &buffer); + private slots: void audioNotify(); void audioStateChanged(QAudio::State state); @@ -246,6 +241,7 @@ private slots: void spectrumChanged(const FrequencySpectrum &spectrum); private: + void resetAudioDevices(); bool initialize(); bool selectFormat(); void stopRecording(); @@ -275,8 +271,10 @@ private: bool m_generateTone; SweptTone m_tone; - QFile* m_file; - WavFile m_wavFile; + WavFile* m_file; + // We need a second file handle via which to read data into m_buffer + // for analysis + WavFile* m_analysisFile; QAudioFormat m_format; @@ -293,12 +291,15 @@ private: QBuffer m_audioOutputIODevice; QByteArray m_buffer; + qint64 m_bufferPosition; + qint64 m_bufferLength; qint64 m_dataLength; + int m_levelBufferLength; qreal m_rmsLevel; qreal m_peakLevel; - int m_spectrumLengthBytes; + int m_spectrumBufferLength; QByteArray m_spectrumBuffer; SpectrumAnalyser m_spectrumAnalyser; qint64 m_spectrumPosition; diff --git a/demos/spectrum/app/mainwidget.cpp b/demos/spectrum/app/mainwidget.cpp index fba28c6..4b53bbe 100644 --- a/demos/spectrum/app/mainwidget.cpp +++ b/demos/spectrum/app/mainwidget.cpp @@ -65,7 +65,7 @@ MainWidget::MainWidget(QWidget *parent) , m_mode(NoMode) , m_engine(new Engine(this)) #ifndef DISABLE_WAVEFORM - , m_waveform(new Waveform(m_engine->buffer(), this)) + , m_waveform(new Waveform(this)) #endif , m_progressBar(new ProgressBar(this)) , m_spectrograph(new Spectrograph(this)) @@ -166,19 +166,18 @@ void MainWidget::timerEvent(QTimerEvent *event) m_infoMessage->setText(""); } -void MainWidget::positionChanged(qint64 positionUs) +void MainWidget::audioPositionChanged(qint64 position) { #ifndef DISABLE_WAVEFORM - qint64 positionBytes = audioLength(m_engine->format(), positionUs); - m_waveform->positionChanged(positionBytes); + m_waveform->audioPositionChanged(position); #else - Q_UNUSED(positionUs) + Q_UNUSED(position) #endif } -void MainWidget::bufferDurationChanged(qint64 durationUs) +void MainWidget::bufferLengthChanged(qint64 length) { - m_progressBar->bufferDurationChanged(durationUs); + m_progressBar->bufferLengthChanged(length); } @@ -186,18 +185,6 @@ void MainWidget::bufferDurationChanged(qint64 durationUs) // Private slots //----------------------------------------------------------------------------- -void MainWidget::dataDurationChanged(qint64 duration) -{ -#ifndef DISABLE_WAVEFORM - const qint64 dataLength = audioLength(m_engine->format(), duration); - m_waveform->dataLengthChanged(dataLength); -#else - Q_UNUSED(duration) -#endif - - updateButtonStates(); -} - void MainWidget::showFileDialog() { const QString dir; @@ -363,13 +350,13 @@ void MainWidget::connectUi() CHECKED_CONNECT(m_engine, SIGNAL(formatChanged(const QAudioFormat &)), this, SLOT(formatChanged(const QAudioFormat &))); - m_progressBar->bufferDurationChanged(m_engine->bufferDuration()); + m_progressBar->bufferLengthChanged(m_engine->bufferLength()); - CHECKED_CONNECT(m_engine, SIGNAL(bufferDurationChanged(qint64)), - this, SLOT(bufferDurationChanged(qint64))); + CHECKED_CONNECT(m_engine, SIGNAL(bufferLengthChanged(qint64)), + this, SLOT(bufferLengthChanged(qint64))); - CHECKED_CONNECT(m_engine, SIGNAL(dataDurationChanged(qint64)), - this, SLOT(dataDurationChanged(qint64))); + CHECKED_CONNECT(m_engine, SIGNAL(dataLengthChanged(qint64)), + this, SLOT(updateButtonStates())); CHECKED_CONNECT(m_engine, SIGNAL(recordPositionChanged(qint64)), m_progressBar, SLOT(recordPositionChanged(qint64))); @@ -378,10 +365,10 @@ void MainWidget::connectUi() m_progressBar, SLOT(playPositionChanged(qint64))); CHECKED_CONNECT(m_engine, SIGNAL(recordPositionChanged(qint64)), - this, SLOT(positionChanged(qint64))); + this, SLOT(audioPositionChanged(qint64))); CHECKED_CONNECT(m_engine, SIGNAL(playPositionChanged(qint64)), - this, SLOT(positionChanged(qint64))); + this, SLOT(audioPositionChanged(qint64))); CHECKED_CONNECT(m_engine, SIGNAL(levelChanged(qreal, qreal, int)), m_levelMeter, SLOT(levelChanged(qreal, qreal, int))); @@ -397,6 +384,11 @@ void MainWidget::connectUi() CHECKED_CONNECT(m_spectrograph, SIGNAL(infoMessage(QString, int)), this, SLOT(infoMessage(QString, int))); + +#ifndef DISABLE_WAVEFORM + CHECKED_CONNECT(m_engine, SIGNAL(bufferChanged(qint64, qint64, const QByteArray &)), + m_waveform, SLOT(bufferChanged(qint64, qint64, const QByteArray &))); +#endif } void MainWidget::createMenus() @@ -428,7 +420,7 @@ void MainWidget::updateButtonStates() QAudio::IdleState == m_engine->state()); m_pauseButton->setEnabled(pauseEnabled); - const bool playEnabled = (m_engine->dataDuration() && + const bool playEnabled = (/*m_engine->dataLength() &&*/ (QAudio::AudioOutput != m_engine->mode() || (QAudio::ActiveState != m_engine->state() && QAudio::IdleState != m_engine->state()))); diff --git a/demos/spectrum/app/mainwidget.h b/demos/spectrum/app/mainwidget.h index cc1f9ed..13131c0 100644 --- a/demos/spectrum/app/mainwidget.h +++ b/demos/spectrum/app/mainwidget.h @@ -80,22 +80,21 @@ public slots: const FrequencySpectrum &spectrum); void infoMessage(const QString &message, int timeoutMs); void errorMessage(const QString &heading, const QString &detail); - void positionChanged(qint64 position); - void bufferDurationChanged(qint64 duration); + void audioPositionChanged(qint64 position); + void bufferLengthChanged(qint64 length); private slots: void showFileDialog(); void showSettingsDialog(); void showToneGeneratorDialog(); void initializeRecord(); - void dataDurationChanged(qint64 duration); void updateModeMenu(); + void updateButtonStates(); private: void createUi(); void createMenus(); void connectUi(); - void updateButtonStates(); void reset(); enum Mode { @@ -112,7 +111,9 @@ private: Engine* m_engine; +#ifndef DISABLE_WAVEFORM Waveform* m_waveform; +#endif ProgressBar* m_progressBar; Spectrograph* m_spectrograph; LevelMeter* m_levelMeter; diff --git a/demos/spectrum/app/progressbar.cpp b/demos/spectrum/app/progressbar.cpp index 6bfc690..0ac76f1 100644 --- a/demos/spectrum/app/progressbar.cpp +++ b/demos/spectrum/app/progressbar.cpp @@ -44,7 +44,7 @@ ProgressBar::ProgressBar(QWidget *parent) : QWidget(parent) - , m_bufferDuration(0) + , m_bufferLength(0) , m_recordPosition(0) , m_playPosition(0) , m_windowPosition(0) @@ -64,7 +64,7 @@ ProgressBar::~ProgressBar() void ProgressBar::reset() { - m_bufferDuration = 0; + m_bufferLength = 0; m_recordPosition = 0; m_playPosition = 0; m_windowPosition = 0; @@ -86,26 +86,26 @@ void ProgressBar::paintEvent(QPaintEvent * /*event*/) painter.fillRect(rect(), Qt::black); #endif - if (m_bufferDuration) { + if (m_bufferLength) { QRect bar = rect(); - const qreal play = qreal(m_playPosition) / m_bufferDuration; + const qreal play = qreal(m_playPosition) / m_bufferLength; bar.setLeft(rect().left() + play * rect().width()); - const qreal record = qreal(m_recordPosition) / m_bufferDuration; + const qreal record = qreal(m_recordPosition) / m_bufferLength; bar.setRight(rect().left() + record * rect().width()); painter.fillRect(bar, bufferColor); QRect window = rect(); - const qreal windowLeft = qreal(m_windowPosition) / m_bufferDuration; + const qreal windowLeft = qreal(m_windowPosition) / m_bufferLength; window.setLeft(rect().left() + windowLeft * rect().width()); - const qreal windowWidth = qreal(m_windowLength) / m_bufferDuration; + const qreal windowWidth = qreal(m_windowLength) / m_bufferLength; window.setWidth(windowWidth * rect().width()); painter.fillRect(window, windowColor); } } -void ProgressBar::bufferDurationChanged(qint64 bufferSize) +void ProgressBar::bufferLengthChanged(qint64 bufferSize) { - m_bufferDuration = bufferSize; + m_bufferLength = bufferSize; m_recordPosition = 0; m_playPosition = 0; m_windowPosition = 0; @@ -116,7 +116,7 @@ void ProgressBar::bufferDurationChanged(qint64 bufferSize) void ProgressBar::recordPositionChanged(qint64 recordPosition) { Q_ASSERT(recordPosition >= 0); - Q_ASSERT(recordPosition <= m_bufferDuration); + Q_ASSERT(recordPosition <= m_bufferLength); m_recordPosition = recordPosition; repaint(); } @@ -124,7 +124,7 @@ void ProgressBar::recordPositionChanged(qint64 recordPosition) void ProgressBar::playPositionChanged(qint64 playPosition) { Q_ASSERT(playPosition >= 0); - Q_ASSERT(playPosition <= m_bufferDuration); + Q_ASSERT(playPosition <= m_bufferLength); m_playPosition = playPosition; repaint(); } @@ -132,8 +132,8 @@ void ProgressBar::playPositionChanged(qint64 playPosition) void ProgressBar::windowChanged(qint64 position, qint64 length) { Q_ASSERT(position >= 0); - Q_ASSERT(position <= m_bufferDuration); - Q_ASSERT(position + length <= m_bufferDuration); + Q_ASSERT(position <= m_bufferLength); + Q_ASSERT(position + length <= m_bufferLength); m_windowPosition = position; m_windowLength = length; repaint(); diff --git a/demos/spectrum/app/progressbar.h b/demos/spectrum/app/progressbar.h index 8514adb..e715cf5 100644 --- a/demos/spectrum/app/progressbar.h +++ b/demos/spectrum/app/progressbar.h @@ -57,13 +57,13 @@ public: void paintEvent(QPaintEvent *event); public slots: - void bufferDurationChanged(qint64 bufferSize); + void bufferLengthChanged(qint64 length); void recordPositionChanged(qint64 recordPosition); void playPositionChanged(qint64 playPosition); void windowChanged(qint64 position, qint64 length); private: - qint64 m_bufferDuration; + qint64 m_bufferLength; qint64 m_recordPosition; qint64 m_playPosition; qint64 m_windowPosition; diff --git a/demos/spectrum/app/utils.cpp b/demos/spectrum/app/utils.cpp index 4ead6c2..49a7626 100644 --- a/demos/spectrum/app/utils.cpp +++ b/demos/spectrum/app/utils.cpp @@ -49,8 +49,10 @@ qint64 audioDuration(const QAudioFormat &format, qint64 bytes) qint64 audioLength(const QAudioFormat &format, qint64 microSeconds) { - return (format.frequency() * format.channels() * (format.sampleSize() / 8)) + qint64 result = (format.frequency() * format.channels() * (format.sampleSize() / 8)) * microSeconds / 1000000; + result -= result % (format.channelCount() * format.sampleSize()); + return result; } qreal nyquistFrequency(const QAudioFormat &format) diff --git a/demos/spectrum/app/waveform.cpp b/demos/spectrum/app/waveform.cpp index 1f7d315..bd854c0 100644 --- a/demos/spectrum/app/waveform.cpp +++ b/demos/spectrum/app/waveform.cpp @@ -44,12 +44,18 @@ #include <QResizeEvent> #include <QDebug> - -Waveform::Waveform(const QByteArray &buffer, QWidget *parent) +//#define PAINT_EVENT_TRACE +#ifdef PAINT_EVENT_TRACE +# define WAVEFORM_PAINT_DEBUG qDebug() +#else +# define WAVEFORM_PAINT_DEBUG nullDebug() +#endif + +Waveform::Waveform(QWidget *parent) : QWidget(parent) - , m_buffer(buffer) - , m_dataLength(0) - , m_position(0) + , m_bufferPosition(0) + , m_bufferLength(0) + , m_audioPosition(0) , m_active(false) , m_tileLength(0) , m_tileArrayStart(0) @@ -72,19 +78,19 @@ void Waveform::paintEvent(QPaintEvent * /*event*/) painter.fillRect(rect(), Qt::black); if (m_active) { - WAVEFORM_DEBUG << "Waveform::paintEvent" - << "windowPosition" << m_windowPosition - << "windowLength" << m_windowLength; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" + << "windowPosition" << m_windowPosition + << "windowLength" << m_windowLength; qint64 pos = m_windowPosition; const qint64 windowEnd = m_windowPosition + m_windowLength; int destLeft = 0; int destRight = 0; while (pos < windowEnd) { const TilePoint point = tilePoint(pos); - WAVEFORM_DEBUG << "Waveform::paintEvent" << "pos" << pos - << "tileIndex" << point.index - << "positionOffset" << point.positionOffset - << "pixelOffset" << point.pixelOffset; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos" << pos + << "tileIndex" << point.index + << "positionOffset" << point.positionOffset + << "pixelOffset" << point.pixelOffset; if (point.index != NullIndex) { const Tile &tile = m_tiles[point.index]; @@ -104,9 +110,9 @@ void Waveform::paintEvent(QPaintEvent * /*event*/) sourceRect.setLeft(point.pixelOffset); sourceRect.setRight(sourceRight); - WAVEFORM_DEBUG << "Waveform::paintEvent" << "tileIndex" << point.index - << "source" << point.pixelOffset << sourceRight - << "dest" << destLeft << destRight; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "tileIndex" << point.index + << "source" << point.pixelOffset << sourceRight + << "dest" << destLeft << destRight; painter.drawPixmap(destRect, *tile.pixmap, sourceRect); @@ -114,25 +120,25 @@ void Waveform::paintEvent(QPaintEvent * /*event*/) if (point.index < m_tiles.count()) { pos = tilePosition(point.index + 1); - WAVEFORM_DEBUG << "Waveform::paintEvent" << "pos ->" << pos; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos ->" << pos; } else { // Reached end of tile array - WAVEFORM_DEBUG << "Waveform::paintEvent" << "reached end of tile array"; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "reached end of tile array"; break; } } else { // Passed last tile which is painted - WAVEFORM_DEBUG << "Waveform::paintEvent" << "tile" << point.index << "not painted"; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "tile" << point.index << "not painted"; break; } } else { // pos is past end of tile array - WAVEFORM_DEBUG << "Waveform::paintEvent" << "pos" << pos << "past end of tile array"; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos" << pos << "past end of tile array"; break; } } - WAVEFORM_DEBUG << "Waveform::paintEvent" << "final pos" << pos << "final x" << destRight; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "final pos" << pos << "final x" << destRight; } } @@ -146,7 +152,6 @@ void Waveform::initialize(const QAudioFormat &format, qint64 audioBufferSize, qi { WAVEFORM_DEBUG << "Waveform::initialize" << "audioBufferSize" << audioBufferSize - << "m_buffer.size()" << m_buffer.size() << "windowDurationUs" << windowDurationUs; reset(); @@ -186,8 +191,9 @@ void Waveform::reset() { WAVEFORM_DEBUG << "Waveform::reset"; - m_dataLength = 0; - m_position = 0; + m_bufferPosition = 0; + m_buffer = QByteArray(); + m_audioPosition = 0; m_format = QAudioFormat(); m_active = false; deletePixmaps(); @@ -198,30 +204,31 @@ void Waveform::reset() m_windowLength = 0; } -void Waveform::dataLengthChanged(qint64 length) +void Waveform::bufferChanged(qint64 position, qint64 length, const QByteArray &buffer) { - WAVEFORM_DEBUG << "Waveform::dataLengthChanged" << length; - const qint64 oldLength = m_dataLength; - m_dataLength = length; - - if (m_active) { - if (m_dataLength < oldLength) - positionChanged(m_dataLength); - else - paintTiles(); - } + WAVEFORM_DEBUG << "Waveform::bufferChanged" + << "audioPosition" << m_audioPosition + << "bufferPosition" << position + << "bufferLength" << length; + m_bufferPosition = position; + m_bufferLength = length; + m_buffer = buffer; + paintTiles(); } -void Waveform::positionChanged(qint64 position) +void Waveform::audioPositionChanged(qint64 position) { - WAVEFORM_DEBUG << "Waveform::positionChanged" << position; - - if (position + m_windowLength > m_dataLength) - position = m_dataLength - m_windowLength; - - m_position = position; - - setWindowPosition(position); + WAVEFORM_DEBUG << "Waveform::audioPositionChanged" + << "audioPosition" << position + << "bufferPosition" << m_bufferPosition + << "bufferLength" << m_bufferLength; + + if (position >= m_bufferPosition) { + if (position + m_windowLength > m_bufferPosition + m_bufferLength) + position = qMax(qint64(0), m_bufferPosition + m_bufferLength - m_windowLength); + m_audioPosition = position; + setWindowPosition(position); + } } void Waveform::deletePixmaps() @@ -255,8 +262,6 @@ void Waveform::createPixmaps(const QSize &widgetSize) m_tiles[i].pixmap = m_pixmaps[i]; m_tiles[i].painted = false; } - - paintTiles(); } void Waveform::setWindowPosition(qint64 position) @@ -327,8 +332,9 @@ bool Waveform::paintTiles() for (int i=0; i<m_tiles.count(); ++i) { const Tile &tile = m_tiles[i]; if (!tile.painted) { - const qint64 tileEnd = m_tileArrayStart + (i + 1) * m_tileLength; - if (m_dataLength >= tileEnd) { + const qint64 tileStart = m_tileArrayStart + i * m_tileLength; + const qint64 tileEnd = tileStart + m_tileLength; + if (m_bufferPosition <= tileStart && m_bufferPosition + m_bufferLength >= tileEnd) { paintTile(i); updateRequired = true; } @@ -343,16 +349,23 @@ bool Waveform::paintTiles() void Waveform::paintTile(int index) { - WAVEFORM_DEBUG << "Waveform::paintTile" << "index" << index; - const qint64 tileStart = m_tileArrayStart + index * m_tileLength; - Q_ASSERT(m_dataLength >= tileStart + m_tileLength); + + WAVEFORM_DEBUG << "Waveform::paintTile" + << "index" << index + << "bufferPosition" << m_bufferPosition + << "bufferLength" << m_bufferLength + << "start" << tileStart + << "end" << tileStart + m_tileLength; + + Q_ASSERT(m_bufferPosition <= tileStart); + Q_ASSERT(m_bufferPosition + m_bufferLength >= tileStart + m_tileLength); Tile &tile = m_tiles[index]; Q_ASSERT(!tile.painted); const qint16* base = reinterpret_cast<const qint16*>(m_buffer.constData()); - const qint16* buffer = base + (tileStart / 2); + const qint16* buffer = base + ((tileStart - m_bufferPosition) / 2); const int numSamples = m_tileLength / (2 * m_format.channels()); QPainter painter(tile.pixmap); @@ -376,6 +389,11 @@ void Waveform::paintTile(int index) for (int i=0; i<numSamples; ++i) { const qint16* ptr = buffer + i * m_format.channels(); + + const int offset = reinterpret_cast<const char*>(ptr) - m_buffer.constData(); + Q_ASSERT(offset >= 0); + Q_ASSERT(offset < m_bufferLength); + const qint16 pcmValue = *ptr; const qreal realValue = pcmToReal(pcmValue); diff --git a/demos/spectrum/app/waveform.h b/demos/spectrum/app/waveform.h index 57c9eec..1c54c86 100644 --- a/demos/spectrum/app/waveform.h +++ b/demos/spectrum/app/waveform.h @@ -60,7 +60,7 @@ QT_FORWARD_DECLARE_CLASS(QByteArray) class Waveform : public QWidget { Q_OBJECT public: - Waveform(const QByteArray &buffer, QWidget *parent = 0); + Waveform(QWidget *parent = 0); ~Waveform(); // QWidget @@ -73,8 +73,8 @@ public: void setAutoUpdatePosition(bool enabled); public slots: - void dataLengthChanged(qint64 length); - void positionChanged(qint64 position); + void bufferChanged(qint64 position, qint64 length, const QByteArray &buffer); + void audioPositionChanged(qint64 position); private: static const int NullIndex = -1; @@ -167,9 +167,11 @@ private: void resetTiles(qint64 newStartPos); private: - const QByteArray& m_buffer; - qint64 m_dataLength; - qint64 m_position; + qint64 m_bufferPosition; + qint64 m_bufferLength; + QByteArray m_buffer; + + qint64 m_audioPosition; QAudioFormat m_format; bool m_active; diff --git a/demos/spectrum/app/wavfile.cpp b/demos/spectrum/app/wavfile.cpp index 74d5918..44c3ac5 100644 --- a/demos/spectrum/app/wavfile.cpp +++ b/demos/spectrum/app/wavfile.cpp @@ -78,179 +78,74 @@ struct CombinedHeader WAVEHeader wave; }; - - -WavFile::WavFile(const QAudioFormat &format, qint64 dataLength) - : m_format(format) - , m_dataLength(dataLength) - , m_dataPosition(0) -{ -} - -bool WavFile::readHeader(QIODevice &device) -{ - if (!device.isSequential()) { - if (!device.seek(0)) - return false; - // XXX: else, assume that current position is the start of the header - } - - CombinedHeader header; - if (device.read(reinterpret_cast<char *>(&header), sizeof(CombinedHeader)) != sizeof(CombinedHeader)) - return false; - - if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0 - || memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0) - && memcmp(&header.riff.type, "WAVE", 4) == 0 - && memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0 - && (header.wave.audioFormat == 1 || header.wave.audioFormat == 0)) { - - // Read off remaining header information - DATAHeader dataHeader; - - if (qFromLittleEndian<quint32>(header.wave.descriptor.size) > sizeof(WAVEHeader)) { - // Extended data available - quint16 extraFormatBytes; - if (device.peek((char*)&extraFormatBytes, sizeof(quint16)) != sizeof(quint16)) - return false; - const qint64 throwAwayBytes = sizeof(quint16) + qFromLittleEndian<quint16>(extraFormatBytes); - if (device.read(throwAwayBytes).size() != throwAwayBytes) - return false; - } - - if (device.read((char*)&dataHeader, sizeof(DATAHeader)) != sizeof(DATAHeader)) - return false; - - // Establish format - if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0) - m_format.setByteOrder(QAudioFormat::LittleEndian); - else - m_format.setByteOrder(QAudioFormat::BigEndian); - - int bps = qFromLittleEndian<quint16>(header.wave.bitsPerSample); - m_format.setChannels(qFromLittleEndian<quint16>(header.wave.numChannels)); - m_format.setCodec("audio/pcm"); - m_format.setFrequency(qFromLittleEndian<quint32>(header.wave.sampleRate)); - m_format.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample)); - m_format.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt); - - m_dataLength = qFromLittleEndian<quint32>(dataHeader.descriptor.size); - m_dataPosition = 0; - } - - return true; -} - -bool WavFile::writeHeader(QIODevice &device) +WavFile::WavFile(QObject *parent) + : QFile(parent) + , m_headerLength(0) { - CombinedHeader header; - DATAHeader dataHeader; - - memset(&header, 0, sizeof(CombinedHeader)); - - // RIFF header - if (m_format.byteOrder() == QAudioFormat::LittleEndian) - strncpy(&header.riff.descriptor.id[0], "RIFF", 4); - else - strncpy(&header.riff.descriptor.id[0], "RIFX", 4); - qToLittleEndian<quint32>(quint32(m_dataLength + sizeof(CombinedHeader) + sizeof(DATAHeader) - sizeof(chunk)), - reinterpret_cast<unsigned char*>(&header.riff.descriptor.size)); - strncpy(&header.riff.type[0], "WAVE", 4); - - // WAVE header - strncpy(&header.wave.descriptor.id[0], "fmt ", 4); - qToLittleEndian<quint32>(quint32(16), - reinterpret_cast<unsigned char*>(&header.wave.descriptor.size)); - qToLittleEndian<quint16>(quint16(1), - reinterpret_cast<unsigned char*>(&header.wave.audioFormat)); - qToLittleEndian<quint16>(quint16(m_format.channels()), - reinterpret_cast<unsigned char*>(&header.wave.numChannels)); - qToLittleEndian<quint32>(quint32(m_format.frequency()), - reinterpret_cast<unsigned char*>(&header.wave.sampleRate)); - qToLittleEndian<quint32>(quint32(m_format.frequency() * m_format.channels() * m_format.sampleSize() / 8), - reinterpret_cast<unsigned char*>(&header.wave.byteRate)); - qToLittleEndian<quint16>(quint16(m_format.channels() * m_format.sampleSize() / 8), - reinterpret_cast<unsigned char*>(&header.wave.blockAlign)); - qToLittleEndian<quint16>(quint16(m_format.sampleSize()), - reinterpret_cast<unsigned char*>(&header.wave.bitsPerSample)); - - // DATA header - strncpy(dataHeader.descriptor.id, "data", 4); - qToLittleEndian<quint32>(quint32(m_dataLength), - reinterpret_cast<unsigned char*>(&dataHeader.descriptor.size)); - return device.write(reinterpret_cast<const char *>(&header), sizeof(CombinedHeader)) == sizeof(CombinedHeader) - && device.write(reinterpret_cast<const char*>(&dataHeader), sizeof(DATAHeader)) == sizeof(DATAHeader); } -const QAudioFormat& WavFile::format() const +bool WavFile::open(const QString &fileName) { - return m_format; + close(); + setFileName(fileName); + return QFile::open(QIODevice::ReadOnly) && readHeader(); } -qint64 WavFile::dataLength() const +const QAudioFormat &WavFile::fileFormat() const { - return m_dataLength; + return m_fileFormat; } -qint64 WavFile::headerLength() +qint64 WavFile::headerLength() const { - return sizeof(CombinedHeader); -} - -bool WavFile::writeDataLength(QIODevice &device, qint64 dataLength) -{ - bool result = false; - if (!device.isSequential()) { - device.seek(40); - unsigned char dataLengthLE[4]; - qToLittleEndian<quint32>(quint32(dataLength), dataLengthLE); - result = (device.write(reinterpret_cast<const char *>(dataLengthLE), 4) == 4); - } - return result; +return m_headerLength; } -qint64 WavFile::readData(QIODevice &device, QByteArray &buffer, - QAudioFormat outputFormat) +bool WavFile::readHeader() { - // Sanity checks - if (!outputFormat.isValid()) - outputFormat = m_format; - - if (!isPCMS16LE(outputFormat) || !isPCMS16LE(m_format)) - return 0; - - if (m_dataPosition == m_dataLength) - return 0; - - // Process - qint64 result = 0; - - const int frameSize = 2 * m_format.channels(); // 16 bit samples - QVector<char> inputSample(frameSize); - - qint16 *output = reinterpret_cast<qint16*>(buffer.data()); + seek(0); + CombinedHeader header; + bool result = read(reinterpret_cast<char *>(&header), sizeof(CombinedHeader)) == sizeof(CombinedHeader); + if (result) { + if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0 + || memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0) + && memcmp(&header.riff.type, "WAVE", 4) == 0 + && memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0 + && (header.wave.audioFormat == 1 || header.wave.audioFormat == 0)) { + + // Read off remaining header information + DATAHeader dataHeader; + + if (qFromLittleEndian<quint32>(header.wave.descriptor.size) > sizeof(WAVEHeader)) { + // Extended data available + quint16 extraFormatBytes; + if (peek((char*)&extraFormatBytes, sizeof(quint16)) != sizeof(quint16)) + return false; + const qint64 throwAwayBytes = sizeof(quint16) + qFromLittleEndian<quint16>(extraFormatBytes); + if (read(throwAwayBytes).size() != throwAwayBytes) + return false; + } - while (result < buffer.size()) { - if (m_dataPosition == m_dataLength) - break; + if (read((char*)&dataHeader, sizeof(DATAHeader)) != sizeof(DATAHeader)) + return false; - // XXX only working with particular alignments - if (device.read(inputSample.data(), inputSample.count())) { - int inputIdx = 0; - for (int outputIdx = 0; outputIdx < outputFormat.channels(); ++outputIdx) { - const qint16* input = reinterpret_cast<const qint16*>(inputSample.data() + 2 * inputIdx); - *output++ = qFromLittleEndian<qint16>(*input); - result += 2; - if (inputIdx < m_format.channels()) - ++inputIdx; - } - m_dataPosition += frameSize; + // Establish format + if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0) + m_fileFormat.setByteOrder(QAudioFormat::LittleEndian); + else + m_fileFormat.setByteOrder(QAudioFormat::BigEndian); + + int bps = qFromLittleEndian<quint16>(header.wave.bitsPerSample); + m_fileFormat.setChannels(qFromLittleEndian<quint16>(header.wave.numChannels)); + m_fileFormat.setCodec("audio/pcm"); + m_fileFormat.setFrequency(qFromLittleEndian<quint32>(header.wave.sampleRate)); + m_fileFormat.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample)); + m_fileFormat.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt); } else { - break; + result = false; } } - + m_headerLength = pos(); return result; } - diff --git a/demos/spectrum/app/wavfile.h b/demos/spectrum/app/wavfile.h index fc14b08..935e935 100644 --- a/demos/spectrum/app/wavfile.h +++ b/demos/spectrum/app/wavfile.h @@ -46,38 +46,22 @@ #include <QtCore/qfile.h> #include <QtMultimedia/qaudioformat.h> -/** - * Helper class for reading WAV files - * - * See https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ - */ -class WavFile +class WavFile : public QFile { public: - WavFile(const QAudioFormat &format = QAudioFormat(), - qint64 dataLength = 0); + WavFile(QObject *parent = 0); - // Reads WAV header and seeks to start of data - bool readHeader(QIODevice &device); + bool open(const QString &fileName); + const QAudioFormat &fileFormat() const; + qint64 headerLength() const; - // Writes WAV header - bool writeHeader(QIODevice &device); - - // Read PCM data - qint64 readData(QIODevice &device, QByteArray &buffer, - QAudioFormat outputFormat = QAudioFormat()); - - const QAudioFormat& format() const; - qint64 dataLength() const; - - static qint64 headerLength(); - - static bool writeDataLength(QIODevice &device, qint64 dataLength); +private: + bool readHeader(); private: - QAudioFormat m_format; - qint64 m_dataLength; - qint64 m_dataPosition; + QAudioFormat m_fileFormat; + qint64 m_headerLength; + }; #endif |