source: trunk/demos/spectrum/app/engine.cpp@ 858

Last change on this file since 858 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 25.7 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the examples of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:BSD$
10** You may use this file under the terms of the BSD license as follows:
11**
12** "Redistribution and use in source and binary forms, with or without
13** modification, are permitted provided that the following conditions are
14** met:
15** * Redistributions of source code must retain the above copyright
16** notice, this list of conditions and the following disclaimer.
17** * Redistributions in binary form must reproduce the above copyright
18** notice, this list of conditions and the following disclaimer in
19** the documentation and/or other materials provided with the
20** distribution.
21** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
22** the names of its contributors may be used to endorse or promote
23** products derived from this software without specific prior written
24** permission.
25**
26** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include "engine.h"
42#include "tonegenerator.h"
43#include "utils.h"
44
45#include <math.h>
46
47#include <QCoreApplication>
48#include <QMetaObject>
49#include <QSet>
50#include <QtMultimedia/QAudioInput>
51#include <QtMultimedia/QAudioOutput>
52#include <QDebug>
53#include <QThread>
54#include <QFile>
55
56//-----------------------------------------------------------------------------
57// Constants
58//-----------------------------------------------------------------------------
59
60const qint64 BufferDurationUs = 10 * 1000000;
61const int NotifyIntervalMs = 100;
62
63// Size of the level calculation window in microseconds
64const int LevelWindowUs = 0.1 * 1000000;
65
66
67//-----------------------------------------------------------------------------
68// Helper functions
69//-----------------------------------------------------------------------------
70
71QDebug& operator<<(QDebug &debug, const QAudioFormat &format)
72{
73 debug << format.frequency() << "Hz"
74 << format.channels() << "channels";
75 return debug;
76}
77
78//-----------------------------------------------------------------------------
79// Constructor and destructor
80//-----------------------------------------------------------------------------
81
82Engine::Engine(QObject *parent)
83 : QObject(parent)
84 , m_mode(QAudio::AudioInput)
85 , m_state(QAudio::StoppedState)
86 , m_generateTone(false)
87 , m_file(0)
88 , m_analysisFile(0)
89 , m_availableAudioInputDevices
90 (QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
91 , m_audioInputDevice(QAudioDeviceInfo::defaultInputDevice())
92 , m_audioInput(0)
93 , m_audioInputIODevice(0)
94 , m_recordPosition(0)
95 , m_availableAudioOutputDevices
96 (QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
97 , m_audioOutputDevice(QAudioDeviceInfo::defaultOutputDevice())
98 , m_audioOutput(0)
99 , m_playPosition(0)
100 , m_bufferPosition(0)
101 , m_bufferLength(0)
102 , m_dataLength(0)
103 , m_levelBufferLength(0)
104 , m_rmsLevel(0.0)
105 , m_peakLevel(0.0)
106 , m_spectrumBufferLength(0)
107 , m_spectrumAnalyser()
108 , m_spectrumPosition(0)
109 , m_count(0)
110{
111 qRegisterMetaType<FrequencySpectrum>("FrequencySpectrum");
112 qRegisterMetaType<WindowFunction>("WindowFunction");
113 CHECKED_CONNECT(&m_spectrumAnalyser,
114 SIGNAL(spectrumChanged(FrequencySpectrum)),
115 this,
116 SLOT(spectrumChanged(FrequencySpectrum)));
117
118 initialize();
119
120#ifdef DUMP_DATA
121 createOutputDir();
122#endif
123
124#ifdef DUMP_SPECTRUM
125 m_spectrumAnalyser.setOutputPath(outputPath());
126#endif
127}
128
129Engine::~Engine()
130{
131
132}
133
134//-----------------------------------------------------------------------------
135// Public functions
136//-----------------------------------------------------------------------------
137
138bool Engine::loadFile(const QString &fileName)
139{
140 reset();
141 bool result = false;
142 Q_ASSERT(!m_generateTone);
143 Q_ASSERT(!m_file);
144 Q_ASSERT(!fileName.isEmpty());
145 m_file = new WavFile(this);
146 if (m_file->open(fileName)) {
147 if (isPCMS16LE(m_file->fileFormat())) {
148 result = initialize();
149 } else {
150 emit errorMessage(tr("Audio format not supported"),
151 formatToString(m_file->fileFormat()));
152 }
153 } else {
154 emit errorMessage(tr("Could not open file"), fileName);
155 }
156 if (result) {
157 m_analysisFile = new WavFile(this);
158 m_analysisFile->open(fileName);
159 }
160 return result;
161}
162
163bool Engine::generateTone(const Tone &tone)
164{
165 reset();
166 Q_ASSERT(!m_generateTone);
167 Q_ASSERT(!m_file);
168 m_generateTone = true;
169 m_tone = tone;
170 ENGINE_DEBUG << "Engine::generateTone"
171 << "startFreq" << m_tone.startFreq
172 << "endFreq" << m_tone.endFreq
173 << "amp" << m_tone.amplitude;
174 return initialize();
175}
176
177bool Engine::generateSweptTone(qreal amplitude)
178{
179 Q_ASSERT(!m_generateTone);
180 Q_ASSERT(!m_file);
181 m_generateTone = true;
182 m_tone.startFreq = 1;
183 m_tone.endFreq = 0;
184 m_tone.amplitude = amplitude;
185 ENGINE_DEBUG << "Engine::generateSweptTone"
186 << "startFreq" << m_tone.startFreq
187 << "amp" << m_tone.amplitude;
188 return initialize();
189}
190
191bool Engine::initializeRecord()
192{
193 reset();
194 ENGINE_DEBUG << "Engine::initializeRecord";
195 Q_ASSERT(!m_generateTone);
196 Q_ASSERT(!m_file);
197 m_generateTone = false;
198 m_tone = SweptTone();
199 return initialize();
200}
201
202qint64 Engine::bufferLength() const
203{
204 return m_file ? m_file->size() : m_bufferLength;
205}
206
207void Engine::setWindowFunction(WindowFunction type)
208{
209 m_spectrumAnalyser.setWindowFunction(type);
210}
211
212
213//-----------------------------------------------------------------------------
214// Public slots
215//-----------------------------------------------------------------------------
216
217void Engine::startRecording()
218{
219 if (m_audioInput) {
220 if (QAudio::AudioInput == m_mode &&
221 QAudio::SuspendedState == m_state) {
222 m_audioInput->resume();
223 } else {
224 m_spectrumAnalyser.cancelCalculation();
225 spectrumChanged(0, 0, FrequencySpectrum());
226
227 m_buffer.fill(0);
228 setRecordPosition(0, true);
229 stopPlayback();
230 m_mode = QAudio::AudioInput;
231 CHECKED_CONNECT(m_audioInput, SIGNAL(stateChanged(QAudio::State)),
232 this, SLOT(audioStateChanged(QAudio::State)));
233 CHECKED_CONNECT(m_audioInput, SIGNAL(notify()),
234 this, SLOT(audioNotify()));
235 m_count = 0;
236 m_dataLength = 0;
237 emit dataLengthChanged(0);
238 m_audioInputIODevice = m_audioInput->start();
239 CHECKED_CONNECT(m_audioInputIODevice, SIGNAL(readyRead()),
240 this, SLOT(audioDataReady()));
241 }
242 }
243}
244
245void Engine::startPlayback()
246{
247 if (m_audioOutput) {
248 if (QAudio::AudioOutput == m_mode &&
249 QAudio::SuspendedState == m_state) {
250#ifdef Q_OS_WIN
251 // The Windows backend seems to internally go back into ActiveState
252 // while still returning SuspendedState, so to ensure that it doesn't
253 // ignore the resume() call, we first re-suspend
254 m_audioOutput->suspend();
255#endif
256 m_audioOutput->resume();
257 } else {
258 m_spectrumAnalyser.cancelCalculation();
259 spectrumChanged(0, 0, FrequencySpectrum());
260 setPlayPosition(0, true);
261 stopRecording();
262 m_mode = QAudio::AudioOutput;
263 CHECKED_CONNECT(m_audioOutput, SIGNAL(stateChanged(QAudio::State)),
264 this, SLOT(audioStateChanged(QAudio::State)));
265 CHECKED_CONNECT(m_audioOutput, SIGNAL(notify()),
266 this, SLOT(audioNotify()));
267 m_count = 0;
268 if (m_file) {
269 m_file->seek(0);
270 m_bufferPosition = 0;
271 m_dataLength = 0;
272 m_audioOutput->start(m_file);
273 } else {
274 m_audioOutputIODevice.close();
275 m_audioOutputIODevice.setBuffer(&m_buffer);
276 m_audioOutputIODevice.open(QIODevice::ReadOnly);
277 m_audioOutput->start(&m_audioOutputIODevice);
278 }
279 }
280 }
281}
282
283void Engine::suspend()
284{
285 if (QAudio::ActiveState == m_state ||
286 QAudio::IdleState == m_state) {
287 switch (m_mode) {
288 case QAudio::AudioInput:
289 m_audioInput->suspend();
290 break;
291 case QAudio::AudioOutput:
292 m_audioOutput->suspend();
293 break;
294 }
295 }
296}
297
298void Engine::setAudioInputDevice(const QAudioDeviceInfo &device)
299{
300 if (device.deviceName() != m_audioInputDevice.deviceName()) {
301 m_audioInputDevice = device;
302 initialize();
303 }
304}
305
306void Engine::setAudioOutputDevice(const QAudioDeviceInfo &device)
307{
308 if (device.deviceName() != m_audioOutputDevice.deviceName()) {
309 m_audioOutputDevice = device;
310 initialize();
311 }
312}
313
314
315//-----------------------------------------------------------------------------
316// Private slots
317//-----------------------------------------------------------------------------
318
319void Engine::audioNotify()
320{
321 switch (m_mode) {
322 case QAudio::AudioInput: {
323 const qint64 recordPosition = qMin(m_bufferLength, audioLength(m_format, m_audioInput->processedUSecs()));
324 setRecordPosition(recordPosition);
325 const qint64 levelPosition = m_dataLength - m_levelBufferLength;
326 if (levelPosition >= 0)
327 calculateLevel(levelPosition, m_levelBufferLength);
328 if (m_dataLength >= m_spectrumBufferLength) {
329 const qint64 spectrumPosition = m_dataLength - m_spectrumBufferLength;
330 calculateSpectrum(spectrumPosition);
331 }
332 emit bufferChanged(0, m_dataLength, m_buffer);
333 }
334 break;
335 case QAudio::AudioOutput: {
336 const qint64 playPosition = audioLength(m_format, m_audioOutput->processedUSecs());
337 setPlayPosition(qMin(bufferLength(), playPosition));
338 const qint64 levelPosition = playPosition - m_levelBufferLength;
339 const qint64 spectrumPosition = playPosition - m_spectrumBufferLength;
340 if (m_file) {
341 if (levelPosition > m_bufferPosition ||
342 spectrumPosition > m_bufferPosition ||
343 qMax(m_levelBufferLength, m_spectrumBufferLength) > m_dataLength) {
344 m_bufferPosition = 0;
345 m_dataLength = 0;
346 // Data needs to be read into m_buffer in order to be analysed
347 const qint64 readPos = qMax(qint64(0), qMin(levelPosition, spectrumPosition));
348 const qint64 readEnd = qMin(m_analysisFile->size(), qMax(levelPosition + m_levelBufferLength, spectrumPosition + m_spectrumBufferLength));
349 const qint64 readLen = readEnd - readPos + audioLength(m_format, WaveformWindowDuration);
350 qDebug() << "Engine::audioNotify [1]"
351 << "analysisFileSize" << m_analysisFile->size()
352 << "readPos" << readPos
353 << "readLen" << readLen;
354 if (m_analysisFile->seek(readPos + m_analysisFile->headerLength())) {
355 m_buffer.resize(readLen);
356 m_bufferPosition = readPos;
357 m_dataLength = m_analysisFile->read(m_buffer.data(), readLen);
358 qDebug() << "Engine::audioNotify [2]" << "bufferPosition" << m_bufferPosition << "dataLength" << m_dataLength;
359 } else {
360 qDebug() << "Engine::audioNotify [2]" << "file seek error";
361 }
362 emit bufferChanged(m_bufferPosition, m_dataLength, m_buffer);
363 }
364 } else {
365 if (playPosition >= m_dataLength)
366 stopPlayback();
367 }
368 if (levelPosition >= 0 && levelPosition + m_levelBufferLength < m_bufferPosition + m_dataLength)
369 calculateLevel(levelPosition, m_levelBufferLength);
370 if (spectrumPosition >= 0 && spectrumPosition + m_spectrumBufferLength < m_bufferPosition + m_dataLength)
371 calculateSpectrum(spectrumPosition);
372 }
373 break;
374 }
375}
376
377void Engine::audioStateChanged(QAudio::State state)
378{
379 ENGINE_DEBUG << "Engine::audioStateChanged from" << m_state
380 << "to" << state;
381
382 if (QAudio::IdleState == state && m_file && m_file->pos() == m_file->size()) {
383 stopPlayback();
384 } else {
385 if (QAudio::StoppedState == state) {
386 // Check error
387 QAudio::Error error = QAudio::NoError;
388 switch (m_mode) {
389 case QAudio::AudioInput:
390 error = m_audioInput->error();
391 break;
392 case QAudio::AudioOutput:
393 error = m_audioOutput->error();
394 break;
395 }
396 if (QAudio::NoError != error) {
397 reset();
398 return;
399 }
400 }
401 setState(state);
402 }
403}
404
405void Engine::audioDataReady()
406{
407 Q_ASSERT(0 == m_bufferPosition);
408 const qint64 bytesReady = m_audioInput->bytesReady();
409 const qint64 bytesSpace = m_buffer.size() - m_dataLength;
410 const qint64 bytesToRead = qMin(bytesReady, bytesSpace);
411
412 const qint64 bytesRead = m_audioInputIODevice->read(
413 m_buffer.data() + m_dataLength,
414 bytesToRead);
415
416 if (bytesRead) {
417 m_dataLength += bytesRead;
418 emit dataLengthChanged(dataLength());
419 }
420
421 if (m_buffer.size() == m_dataLength)
422 stopRecording();
423}
424
425void Engine::spectrumChanged(const FrequencySpectrum &spectrum)
426{
427 ENGINE_DEBUG << "Engine::spectrumChanged" << "pos" << m_spectrumPosition;
428 emit spectrumChanged(m_spectrumPosition, m_spectrumBufferLength, spectrum);
429}
430
431
432//-----------------------------------------------------------------------------
433// Private functions
434//-----------------------------------------------------------------------------
435
436void Engine::resetAudioDevices()
437{
438 delete m_audioInput;
439 m_audioInput = 0;
440 m_audioInputIODevice = 0;
441 setRecordPosition(0);
442 delete m_audioOutput;
443 m_audioOutput = 0;
444 setPlayPosition(0);
445 m_spectrumPosition = 0;
446 setLevel(0.0, 0.0, 0);
447}
448
449void Engine::reset()
450{
451 stopRecording();
452 stopPlayback();
453 setState(QAudio::AudioInput, QAudio::StoppedState);
454 setFormat(QAudioFormat());
455 m_generateTone = false;
456 delete m_file;
457 m_file = 0;
458 delete m_analysisFile;
459 m_analysisFile = 0;
460 m_buffer.clear();
461 m_bufferPosition = 0;
462 m_bufferLength = 0;
463 m_dataLength = 0;
464 emit dataLengthChanged(0);
465 resetAudioDevices();
466}
467
468bool Engine::initialize()
469{
470 bool result = false;
471
472 QAudioFormat format = m_format;
473
474 if (selectFormat()) {
475 if (m_format != format) {
476 resetAudioDevices();
477 if (m_file) {
478 emit bufferLengthChanged(bufferLength());
479 emit dataLengthChanged(dataLength());
480 emit bufferChanged(0, 0, m_buffer);
481 setRecordPosition(bufferLength());
482 result = true;
483 } else {
484 m_bufferLength = audioLength(m_format, BufferDurationUs);
485 m_buffer.resize(m_bufferLength);
486 m_buffer.fill(0);
487 emit bufferLengthChanged(bufferLength());
488 if (m_generateTone) {
489 if (0 == m_tone.endFreq) {
490 const qreal nyquist = nyquistFrequency(m_format);
491 m_tone.endFreq = qMin(qreal(SpectrumHighFreq), nyquist);
492 }
493 // Call function defined in utils.h, at global scope
494 ::generateTone(m_tone, m_format, m_buffer);
495 m_dataLength = m_bufferLength;
496 emit dataLengthChanged(dataLength());
497 emit bufferChanged(0, m_dataLength, m_buffer);
498 setRecordPosition(m_bufferLength);
499 result = true;
500 } else {
501 emit bufferChanged(0, 0, m_buffer);
502 m_audioInput = new QAudioInput(m_audioInputDevice, m_format, this);
503 m_audioInput->setNotifyInterval(NotifyIntervalMs);
504 result = true;
505 }
506 }
507 m_audioOutput = new QAudioOutput(m_audioOutputDevice, m_format, this);
508 m_audioOutput->setNotifyInterval(NotifyIntervalMs);
509 }
510 } else {
511 if (m_file)
512 emit errorMessage(tr("Audio format not supported"),
513 formatToString(m_format));
514 else if (m_generateTone)
515 emit errorMessage(tr("No suitable format found"), "");
516 else
517 emit errorMessage(tr("No common input / output format found"), "");
518 }
519
520 ENGINE_DEBUG << "Engine::initialize" << "m_bufferLength" << m_bufferLength;
521 ENGINE_DEBUG << "Engine::initialize" << "m_dataLength" << m_dataLength;
522 ENGINE_DEBUG << "Engine::initialize" << "format" << m_format;
523
524 return result;
525}
526
527bool Engine::selectFormat()
528{
529 bool foundSupportedFormat = false;
530
531 if (m_file || QAudioFormat() != m_format) {
532 QAudioFormat format = m_format;
533 if (m_file)
534 // Header is read from the WAV file; just need to check whether
535 // it is supported by the audio output device
536 format = m_file->fileFormat();
537 if (m_audioOutputDevice.isFormatSupported(format)) {
538 setFormat(format);
539 foundSupportedFormat = true;
540 }
541 } else {
542
543 QList<int> frequenciesList;
544 #ifdef Q_OS_WIN
545 // The Windows audio backend does not correctly report format support
546 // (see QTBUG-9100). Furthermore, although the audio subsystem captures
547 // at 11025Hz, the resulting audio is corrupted.
548 frequenciesList += 8000;
549 #endif
550
551 if (!m_generateTone)
552 frequenciesList += m_audioInputDevice.supportedFrequencies();
553
554 frequenciesList += m_audioOutputDevice.supportedFrequencies();
555 frequenciesList = frequenciesList.toSet().toList(); // remove duplicates
556 qSort(frequenciesList);
557 ENGINE_DEBUG << "Engine::initialize frequenciesList" << frequenciesList;
558
559 QList<int> channelsList;
560 channelsList += m_audioInputDevice.supportedChannels();
561 channelsList += m_audioOutputDevice.supportedChannels();
562 channelsList = channelsList.toSet().toList();
563 qSort(channelsList);
564 ENGINE_DEBUG << "Engine::initialize channelsList" << channelsList;
565
566 QAudioFormat format;
567 format.setByteOrder(QAudioFormat::LittleEndian);
568 format.setCodec("audio/pcm");
569 format.setSampleSize(16);
570 format.setSampleType(QAudioFormat::SignedInt);
571 int frequency, channels;
572 foreach (frequency, frequenciesList) {
573 if (foundSupportedFormat)
574 break;
575 format.setFrequency(frequency);
576 foreach (channels, channelsList) {
577 format.setChannels(channels);
578 const bool inputSupport = m_generateTone ||
579 m_audioInputDevice.isFormatSupported(format);
580 const bool outputSupport = m_audioOutputDevice.isFormatSupported(format);
581 ENGINE_DEBUG << "Engine::initialize checking " << format
582 << "input" << inputSupport
583 << "output" << outputSupport;
584 if (inputSupport && outputSupport) {
585 foundSupportedFormat = true;
586 break;
587 }
588 }
589 }
590
591 if (!foundSupportedFormat)
592 format = QAudioFormat();
593
594 setFormat(format);
595 }
596
597 return foundSupportedFormat;
598}
599
600void Engine::stopRecording()
601{
602 if (m_audioInput) {
603 m_audioInput->stop();
604 QCoreApplication::instance()->processEvents();
605 m_audioInput->disconnect();
606 }
607 m_audioInputIODevice = 0;
608
609#ifdef DUMP_AUDIO
610 dumpData();
611#endif
612}
613
614void Engine::stopPlayback()
615{
616 if (m_audioOutput) {
617 m_audioOutput->stop();
618 QCoreApplication::instance()->processEvents();
619 m_audioOutput->disconnect();
620 setPlayPosition(0);
621 }
622}
623
624void Engine::setState(QAudio::State state)
625{
626 const bool changed = (m_state != state);
627 m_state = state;
628 if (changed)
629 emit stateChanged(m_mode, m_state);
630}
631
632void Engine::setState(QAudio::Mode mode, QAudio::State state)
633{
634 const bool changed = (m_mode != mode || m_state != state);
635 m_mode = mode;
636 m_state = state;
637 if (changed)
638 emit stateChanged(m_mode, m_state);
639}
640
641void Engine::setRecordPosition(qint64 position, bool forceEmit)
642{
643 const bool changed = (m_recordPosition != position);
644 m_recordPosition = position;
645 if (changed || forceEmit)
646 emit recordPositionChanged(m_recordPosition);
647}
648
649void Engine::setPlayPosition(qint64 position, bool forceEmit)
650{
651 const bool changed = (m_playPosition != position);
652 m_playPosition = position;
653 if (changed || forceEmit)
654 emit playPositionChanged(m_playPosition);
655}
656
657void Engine::calculateLevel(qint64 position, qint64 length)
658{
659#ifdef DISABLE_LEVEL
660 Q_UNUSED(position)
661 Q_UNUSED(length)
662#else
663 Q_ASSERT(position + length <= m_bufferPosition + m_dataLength);
664
665 qreal peakLevel = 0.0;
666
667 qreal sum = 0.0;
668 const char *ptr = m_buffer.constData() + position - m_bufferPosition;
669 const char *const end = ptr + length;
670 while (ptr < end) {
671 const qint16 value = *reinterpret_cast<const qint16*>(ptr);
672 const qreal fracValue = pcmToReal(value);
673 peakLevel = qMax(peakLevel, fracValue);
674 sum += fracValue * fracValue;
675 ptr += 2;
676 }
677 const int numSamples = length / 2;
678 qreal rmsLevel = sqrt(sum / numSamples);
679
680 rmsLevel = qMax(qreal(0.0), rmsLevel);
681 rmsLevel = qMin(qreal(1.0), rmsLevel);
682 setLevel(rmsLevel, peakLevel, numSamples);
683
684 ENGINE_DEBUG << "Engine::calculateLevel" << "pos" << position << "len" << length
685 << "rms" << rmsLevel << "peak" << peakLevel;
686#endif
687}
688
689void Engine::calculateSpectrum(qint64 position)
690{
691#ifdef DISABLE_SPECTRUM
692 Q_UNUSED(position)
693#else
694 Q_ASSERT(position + m_spectrumBufferLength <= m_bufferPosition + m_dataLength);
695 Q_ASSERT(0 == m_spectrumBufferLength % 2); // constraint of FFT algorithm
696
697 // QThread::currentThread is marked 'for internal use only', but
698 // we're only using it for debug output here, so it's probably OK :)
699 ENGINE_DEBUG << "Engine::calculateSpectrum" << QThread::currentThread()
700 << "count" << m_count << "pos" << position << "len" << m_spectrumBufferLength
701 << "spectrumAnalyser.isReady" << m_spectrumAnalyser.isReady();
702
703 if(m_spectrumAnalyser.isReady()) {
704 m_spectrumBuffer = QByteArray::fromRawData(m_buffer.constData() + position - m_bufferPosition,
705 m_spectrumBufferLength);
706 m_spectrumPosition = position;
707 m_spectrumAnalyser.calculate(m_spectrumBuffer, m_format);
708 }
709#endif
710}
711
712void Engine::setFormat(const QAudioFormat &format)
713{
714 const bool changed = (format != m_format);
715 m_format = format;
716 m_levelBufferLength = audioLength(m_format, LevelWindowUs);
717 m_spectrumBufferLength = SpectrumLengthSamples *
718 (m_format.sampleSize() / 8) * m_format.channels();
719 if (changed)
720 emit formatChanged(m_format);
721}
722
723void Engine::setLevel(qreal rmsLevel, qreal peakLevel, int numSamples)
724{
725 m_rmsLevel = rmsLevel;
726 m_peakLevel = peakLevel;
727 emit levelChanged(m_rmsLevel, m_peakLevel, numSamples);
728}
729
730#ifdef DUMP_DATA
731void Engine::createOutputDir()
732{
733 m_outputDir.setPath("output");
734
735 // Ensure output directory exists and is empty
736 if (m_outputDir.exists()) {
737 const QStringList files = m_outputDir.entryList(QDir::Files);
738 QString file;
739 foreach (file, files)
740 m_outputDir.remove(file);
741 } else {
742 QDir::current().mkdir("output");
743 }
744}
745#endif // DUMP_DATA
746
747#ifdef DUMP_AUDIO
748void Engine::dumpData()
749{
750 const QString txtFileName = m_outputDir.filePath("data.txt");
751 QFile txtFile(txtFileName);
752 txtFile.open(QFile::WriteOnly | QFile::Text);
753 QTextStream stream(&txtFile);
754 const qint16 *ptr = reinterpret_cast<const qint16*>(m_buffer.constData());
755 const int numSamples = m_dataLength / (2 * m_format.channels());
756 for (int i=0; i<numSamples; ++i) {
757 stream << i << "\t" << *ptr << "\n";
758 ptr += m_format.channels();
759 }
760
761 const QString pcmFileName = m_outputDir.filePath("data.pcm");
762 QFile pcmFile(pcmFileName);
763 pcmFile.open(QFile::WriteOnly);
764 pcmFile.write(m_buffer.constData(), m_dataLength);
765}
766#endif // DUMP_AUDIO
Note: See TracBrowser for help on using the repository browser.