diff options
author | Miikka Heikkinen <miikka.heikkinen@digia.com> | 2013-10-09 11:25:00 +0300 |
---|---|---|
committer | Miikka Heikkinen <miikka.heikkinen@digia.com> | 2013-10-09 13:20:20 +0300 |
commit | 45bdd538baae5d41ab772a0430917080503ad89b (patch) | |
tree | ea07fd33fad7c98822a221385f6114b354971292 /examples | |
parent | 5f02a038891db0cfd6a9fa08d7576141485e5d71 (diff) |
Improve and document audiolevels example
Proper screenshot still missing
Task-number: QTRD-2394
Change-Id: I389655773f0095a32238d3850d40b88135092858
Reviewed-by: Tomi Korpipää <tomi.korpipaa@digia.com>
Diffstat (limited to 'examples')
-rw-r--r-- | examples/audiolevels/audiolevels.cpp | 17 | ||||
-rw-r--r-- | examples/audiolevels/audiolevels.h | 4 | ||||
-rw-r--r-- | examples/audiolevels/audiolevelsiodevice.cpp | 68 | ||||
-rw-r--r-- | examples/audiolevels/audiolevelsiodevice.h | 4 | ||||
-rw-r--r-- | examples/audiolevels/doc/src/audiolevels.qdoc | 61 | ||||
-rw-r--r-- | examples/audiolevels/main.cpp | 2 |
6 files changed, 126 insertions, 30 deletions
diff --git a/examples/audiolevels/audiolevels.cpp b/examples/audiolevels/audiolevels.cpp index 5171492f..d2038630 100644 --- a/examples/audiolevels/audiolevels.cpp +++ b/examples/audiolevels/audiolevels.cpp @@ -31,22 +31,22 @@ QT_DATAVISUALIZATION_USE_NAMESPACE AudioLevels::AudioLevels(Q3DBars *graph, QObject *parent) : QObject(parent), - m_device(0), m_graph(graph), + m_device(0), m_audioInput(0) { // Set up the graph - QBarDataRow *row = new QBarDataRow; - m_graph->activeDataProxy()->addRow(row); - m_graph->setBarThickness(0.04); - m_graph->setBarSpacing(QSizeF(0.0, 0.0)); + m_graph->setBarThickness(0.5); + m_graph->setBarSpacing(QSizeF(0.0, 1.0)); m_graph->setGridVisible(false); m_graph->setBackgroundVisible(false); - m_graph->valueAxis()->setRange(0.0, 1.0); + m_graph->valueAxis()->setRange(0.0, 2.0); m_graph->setShadowQuality(QDataVis::ShadowQualityNone); - m_graph->scene()->activeCamera()->setCameraPosition(-20.0, 10.0, 10); + m_graph->scene()->activeCamera()->setCameraPosition(-20.0, 10.0, 20); m_graph->setTheme(QDataVis::ThemeIsabelle); + m_graph->setBarType(QDataVis::MeshStyleBars); + //! [0] QAudioFormat formatAudio; formatAudio.setSampleRate(8000); formatAudio.setChannelCount(1); @@ -56,12 +56,13 @@ AudioLevels::AudioLevels(Q3DBars *graph, QObject *parent) formatAudio.setSampleType(QAudioFormat::UnSignedInt); QAudioDeviceInfo inputDevices = QAudioDeviceInfo::defaultInputDevice(); - m_audioInput = new QAudioInput(inputDevices,formatAudio, this); + m_audioInput = new QAudioInput(inputDevices, formatAudio, this); m_device = new AudioLevelsIODevice(m_graph->activeDataProxy(), this); m_device->open(QIODevice::WriteOnly); m_audioInput->start(m_device); + //! [0] } AudioLevels::~AudioLevels() diff --git a/examples/audiolevels/audiolevels.h b/examples/audiolevels/audiolevels.h index 90141850..db1d8936 100644 --- a/examples/audiolevels/audiolevels.h +++ b/examples/audiolevels/audiolevels.h @@ -35,9 +35,11 @@ public: ~AudioLevels(); private: - AudioLevelsIODevice *m_device; + //! [0] Q3DBars *m_graph; + AudioLevelsIODevice *m_device; QAudioInput *m_audioInput; + //! [0] }; #endif diff --git a/examples/audiolevels/audiolevelsiodevice.cpp b/examples/audiolevels/audiolevelsiodevice.cpp index c3a5cfd0..11cba5aa 100644 --- a/examples/audiolevels/audiolevelsiodevice.cpp +++ b/examples/audiolevels/audiolevelsiodevice.cpp @@ -21,10 +21,26 @@ QT_DATAVISUALIZATION_USE_NAMESPACE +//! [1] +static const int resolution = 8; +static const int rowSize = 800; +static const int rowCount = 7; // Must be odd number +static const int middleRow = rowCount / 2; +//! [1] + AudioLevelsIODevice::AudioLevelsIODevice(QBarDataProxy *proxy, QObject *parent) : QIODevice(parent), - m_proxy(proxy) + m_proxy(proxy), + m_array(new QBarDataArray) { + // We reuse the existing array for maximum performance, so construct the array here + //! [0] + m_array->reserve(rowCount); + for (int i = 0; i < rowCount; i++) + m_array->append(new QBarDataRow(rowSize)); + //! [0] + + qDebug() << "Total of" << (rowSize * rowCount) << "items in the array."; } // Implementation required for this pure virtual function @@ -35,32 +51,52 @@ qint64 AudioLevelsIODevice::readData(char *data, qint64 maxSize) return -1; } +//! [2] qint64 AudioLevelsIODevice::writeData(const char *data, qint64 maxSize) { - static const int resolution = 8; - static const int maxRowSize = 1000; - + // The amount of new data available. int newDataSize = maxSize / resolution; - const QBarDataRow *oldRow = m_proxy->rowAt(0); - - int rowSize = qMin((newDataSize + oldRow->size()), maxRowSize); + // If we get more data than array size, we need to adjust the start index for new data. + int newDataStartIndex = qMax(0, (newDataSize - rowSize)); - QBarDataRow *row = new QBarDataRow(rowSize); - int bottom = qMax(0, (newDataSize - rowSize)); + // Move the old data ahead in the rows (only do first half of rows + middle one now). + // If the amount of new data was larger than row size, skip copying. + if (!newDataStartIndex) { + for (int i = 0; i <= middleRow; i++) { + QBarDataItem *srcPos = m_array->at(i)->data(); + QBarDataItem *dstPos = srcPos + newDataSize; + memmove(dstPos, srcPos, (rowSize - newDataSize) * sizeof(QBarDataItem)); + } + } - // Insert data in reverse order, so that newest data is always at the front of the row + // Insert data in reverse order, so that newest data is always at the front of the row. int index = 0; - for (int i = newDataSize - 1; i >= bottom; i--) - (*row)[index++].setValue(((quint8)data[resolution * i] - 128) / 2.0); + for (int i = newDataSize - 1; i >= newDataStartIndex; i--) { + // Add 0.01 to the value to avoid gaps in the graph (i.e. zero height bars). + qreal value = qreal(quint8(data[resolution * i]) - 128) / 2.0 + 0.01; + (*m_array->at(middleRow))[index].setValue(value); + // Insert a fractional value into front half of the rows. + for (int j = 1; j <= middleRow; j++) { + qreal fractionalValue = value / qreal(j + 1); + (*m_array->at(middleRow - j))[index].setValue(fractionalValue); + } + index++; + } - // Append old data to new row - for (int i = newDataSize; i < rowSize; i++) - (*row)[i].setValue(oldRow->at(i - newDataSize).value()); + // Copy the front half of rows to the back half for symmetry. + index = 0; + for (int i = rowCount - 1; i > middleRow; i--) { + QBarDataItem *srcPos = m_array->at(index++)->data(); + QBarDataItem *dstPos = m_array->at(i)->data(); + memcpy(dstPos, srcPos, rowSize * sizeof(QBarDataItem)); + } - m_proxy->setRow(0, row); + // Reset the proxy array now that data has been updated to trigger a redraw. + m_proxy->resetArray(m_array); return maxSize; } +//! [2] diff --git a/examples/audiolevels/audiolevelsiodevice.h b/examples/audiolevels/audiolevelsiodevice.h index cc2929f8..8d665fe8 100644 --- a/examples/audiolevels/audiolevelsiodevice.h +++ b/examples/audiolevels/audiolevelsiodevice.h @@ -35,8 +35,10 @@ protected: qint64 writeData(const char *data, qint64 maxSize); private: + //! [0] QBarDataProxy *m_proxy; - int m_visibleCount; + QBarDataArray *m_array; + //! [0] }; #endif diff --git a/examples/audiolevels/doc/src/audiolevels.qdoc b/examples/audiolevels/doc/src/audiolevels.qdoc index 0d8144a6..8737c242 100644 --- a/examples/audiolevels/doc/src/audiolevels.qdoc +++ b/examples/audiolevels/doc/src/audiolevels.qdoc @@ -22,9 +22,64 @@ \ingroup qtdatavisualization_examples \brief Simple application showing real time audio data. - The audiolevels example shows how feed dynamic data to a graph using Q3DBars. + The audiolevels example shows how feed real-time dynamic data to a graph using Q3DBars. - \image audiolevels-example.png + This example reads the audio levels from a microphone and displays those levels + in a bar graph. To increase the load for demonstration purposes, and to make the + graph little fancier, slightly modified data is used to fill multiple rows. + + The interesting stuff happens in AudioLevels and AudioLevelsIODevice classes, so we + concentrate on those and skip explaining the basic Q3DBars functionality - for that see + \l{Bars Example}. + + AudioLevelsIODevice subclasses QIODevice and is given as input device for QAudioInput + class, so it receives microphone data. + + In the header file for QAudioInput class we declare necessary members: + + \snippet ../examples/audiolevels/audiolevels.h 0 + + And initialize the microphone listening in the source: + + \snippet ../examples/audiolevels/audiolevels.cpp 0 + + In the header file for AudioLevelsIODevice class we store pointers to the data proxy and + also the data array we give to the proxy, because we reuse the same array to keep memory + reallocations to the minimum: + + \snippet ../examples/audiolevels/audiolevelsiodevice.h 0 + + In the source file we define some static constants to define size of the data array and + the middle row index, as well as the resolution of the visualization. You may need to adjust + these values to get decent performance in low-end devices: - TODO + \snippet ../examples/audiolevels/audiolevelsiodevice.cpp 1 + + The \c resolution constant indicates the sample rate, e.g. value 8 means every eighth + byte from audio input is visualized. This is necessary to make the data readable, as it would + otherwise make the graph scroll too fast. + + In the AudioLevelsIODevice class constructor we initialize the data array: + + \snippet ../examples/audiolevels/audiolevelsiodevice.cpp 0 + + The AudioLevelsIODevice::writeData function is called whenever there is new audio data + available to be visualized. There we move the old data along the rows and insert new + data in the beginning of the rows: + + \snippet ../examples/audiolevels/audiolevelsiodevice.cpp 2 + + We use a couple of techniques here to improve performance. First off, we reuse + the existing data array, as this allows us to avoid any extra memory allocations in our + application code. This also means the data array dimensions do not change, which further + improves efficiency in the bar graph renderer. + Secondly, since the rows are QVectors of QBarDataItems, which do not allocate any data that needs + deletion, we can utilize memmove and memcpy functions to quickly move and copy data around. + + \note In the future versions of Qt Data Visualization, QBarDataItem might get extended so that + it does allocate some memory to store other optional bar properties besides the value. + In use cases where those optional properties are used, using memmove and memcpy would lead to + memory leaks, so use them with care. + + \image audiolevels-example.png */ diff --git a/examples/audiolevels/main.cpp b/examples/audiolevels/main.cpp index ea8acd6b..9d16610c 100644 --- a/examples/audiolevels/main.cpp +++ b/examples/audiolevels/main.cpp @@ -29,7 +29,7 @@ int main(int argc, char *argv[]) Q3DBars window; window.resize(1024, 768); - window.setTitle("QtDataVisualization microphone audio levels visualizer"); + window.setTitle("Qt Data Visualization - Microphone audio levels visualizer"); window.show(); AudioLevels audioLevels(&window); |