1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "histogramwidget.h"
52#include <QPainter>
53#include <QHBoxLayout>
54
55template <class T>
56static QVector<qreal> getBufferLevels(const T *buffer, int frames, int channels);
57
58class QAudioLevel : public QWidget
59{
60 Q_OBJECT
61public:
62 explicit QAudioLevel(QWidget *parent = nullptr);
63
64 // Using [0; 1.0] range
65 void setLevel(qreal level);
66
67protected:
68 void paintEvent(QPaintEvent *event);
69
70private:
71 qreal m_level = 0;
72};
73
74QAudioLevel::QAudioLevel(QWidget *parent)
75 : QWidget(parent)
76{
77 setMinimumHeight(15);
78 setMaximumHeight(50);
79}
80
81void QAudioLevel::setLevel(qreal level)
82{
83 if (m_level != level) {
84 m_level = level;
85 update();
86 }
87}
88
89void QAudioLevel::paintEvent(QPaintEvent *event)
90{
91 Q_UNUSED(event);
92
93 QPainter painter(this);
94 // draw level
95 qreal widthLevel = m_level * width();
96 painter.fillRect(x: 0, y: 0, w: widthLevel, h: height(), c: Qt::red);
97 // clear the rest of the control
98 painter.fillRect(x: widthLevel, y: 0, w: width(), h: height(), c: Qt::black);
99}
100
101HistogramWidget::HistogramWidget(QWidget *parent)
102 : QWidget(parent)
103{
104 m_processor.moveToThread(thread: &m_processorThread);
105 qRegisterMetaType<QVector<qreal>>(typeName: "QVector<qreal>");
106 connect(sender: &m_processor, signal: &FrameProcessor::histogramReady, receiver: this, slot: &HistogramWidget::setHistogram);
107 m_processorThread.start(QThread::LowestPriority);
108 setLayout(new QHBoxLayout);
109}
110
111HistogramWidget::~HistogramWidget()
112{
113 m_processorThread.quit();
114 m_processorThread.wait(time: 10000);
115}
116
117void HistogramWidget::processFrame(const QVideoFrame &frame)
118{
119 if (m_isBusy && frame.isValid())
120 return; //drop frame
121
122 m_isBusy = true;
123 QMetaObject::invokeMethod(obj: &m_processor, member: "processFrame",
124 type: Qt::QueuedConnection, Q_ARG(QVideoFrame, frame), Q_ARG(int, m_levels));
125}
126
127// This function returns the maximum possible sample value for a given audio format
128qreal getPeakValue(const QAudioFormat& format)
129{
130 // Note: Only the most common sample formats are supported
131 if (!format.isValid())
132 return qreal(0);
133
134 if (format.codec() != "audio/pcm")
135 return qreal(0);
136
137 switch (format.sampleType()) {
138 case QAudioFormat::Unknown:
139 break;
140 case QAudioFormat::Float:
141 if (format.sampleSize() != 32) // other sample formats are not supported
142 return qreal(0);
143 return qreal(1.00003);
144 case QAudioFormat::SignedInt:
145 if (format.sampleSize() == 32)
146 return qreal(INT_MAX);
147 if (format.sampleSize() == 16)
148 return qreal(SHRT_MAX);
149 if (format.sampleSize() == 8)
150 return qreal(CHAR_MAX);
151 break;
152 case QAudioFormat::UnSignedInt:
153 if (format.sampleSize() == 32)
154 return qreal(UINT_MAX);
155 if (format.sampleSize() == 16)
156 return qreal(USHRT_MAX);
157 if (format.sampleSize() == 8)
158 return qreal(UCHAR_MAX);
159 break;
160 }
161
162 return qreal(0);
163}
164
165// returns the audio level for each channel
166QVector<qreal> getBufferLevels(const QAudioBuffer& buffer)
167{
168 QVector<qreal> values;
169
170 if (!buffer.isValid())
171 return values;
172
173 if (!buffer.format().isValid() || buffer.format().byteOrder() != QAudioFormat::LittleEndian)
174 return values;
175
176 if (buffer.format().codec() != "audio/pcm")
177 return values;
178
179 int channelCount = buffer.format().channelCount();
180 values.fill(from: 0, asize: channelCount);
181 qreal peak_value = getPeakValue(format: buffer.format());
182 if (qFuzzyCompare(p1: peak_value, p2: qreal(0)))
183 return values;
184
185 switch (buffer.format().sampleType()) {
186 case QAudioFormat::Unknown:
187 case QAudioFormat::UnSignedInt:
188 if (buffer.format().sampleSize() == 32)
189 values = getBufferLevels(buffer: buffer.constData<quint32>(), frames: buffer.frameCount(), channels: channelCount);
190 if (buffer.format().sampleSize() == 16)
191 values = getBufferLevels(buffer: buffer.constData<quint16>(), frames: buffer.frameCount(), channels: channelCount);
192 if (buffer.format().sampleSize() == 8)
193 values = getBufferLevels(buffer: buffer.constData<quint8>(), frames: buffer.frameCount(), channels: channelCount);
194 for (int i = 0; i < values.size(); ++i)
195 values[i] = qAbs(t: values.at(i) - peak_value / 2) / (peak_value / 2);
196 break;
197 case QAudioFormat::Float:
198 if (buffer.format().sampleSize() == 32) {
199 values = getBufferLevels(buffer: buffer.constData<float>(), frames: buffer.frameCount(), channels: channelCount);
200 for (int i = 0; i < values.size(); ++i)
201 values[i] /= peak_value;
202 }
203 break;
204 case QAudioFormat::SignedInt:
205 if (buffer.format().sampleSize() == 32)
206 values = getBufferLevels(buffer: buffer.constData<qint32>(), frames: buffer.frameCount(), channels: channelCount);
207 if (buffer.format().sampleSize() == 16)
208 values = getBufferLevels(buffer: buffer.constData<qint16>(), frames: buffer.frameCount(), channels: channelCount);
209 if (buffer.format().sampleSize() == 8)
210 values = getBufferLevels(buffer: buffer.constData<qint8>(), frames: buffer.frameCount(), channels: channelCount);
211 for (int i = 0; i < values.size(); ++i)
212 values[i] /= peak_value;
213 break;
214 }
215
216 return values;
217}
218
219template <class T>
220QVector<qreal> getBufferLevels(const T *buffer, int frames, int channels)
221{
222 QVector<qreal> max_values;
223 max_values.fill(from: 0, asize: channels);
224
225 for (int i = 0; i < frames; ++i) {
226 for (int j = 0; j < channels; ++j) {
227 qreal value = qAbs(t: qreal(buffer[i * channels + j]));
228 if (value > max_values.at(i: j))
229 max_values.replace(i: j, t: value);
230 }
231 }
232
233 return max_values;
234}
235
236void HistogramWidget::processBuffer(const QAudioBuffer &buffer)
237{
238 if (m_audioLevels.count() != buffer.format().channelCount()) {
239 qDeleteAll(c: m_audioLevels);
240 m_audioLevels.clear();
241 for (int i = 0; i < buffer.format().channelCount(); ++i) {
242 QAudioLevel *level = new QAudioLevel(this);
243 m_audioLevels.append(t: level);
244 layout()->addWidget(w: level);
245 }
246 }
247
248 QVector<qreal> levels = getBufferLevels(buffer);
249 for (int i = 0; i < levels.count(); ++i)
250 m_audioLevels.at(i)->setLevel(levels.at(i));
251}
252
253void HistogramWidget::setHistogram(const QVector<qreal> &histogram)
254{
255 m_isBusy = false;
256 m_histogram = histogram;
257 update();
258}
259
260void HistogramWidget::paintEvent(QPaintEvent *event)
261{
262 Q_UNUSED(event);
263
264 if (!m_audioLevels.isEmpty())
265 return;
266
267 QPainter painter(this);
268
269 if (m_histogram.isEmpty()) {
270 painter.fillRect(x: 0, y: 0, w: width(), h: height(), b: QColor::fromRgb(r: 0, g: 0, b: 0));
271 return;
272 }
273
274 qreal barWidth = width() / (qreal)m_histogram.size();
275
276 for (int i = 0; i < m_histogram.size(); ++i) {
277 qreal h = m_histogram[i] * height();
278 // draw level
279 painter.fillRect(x: barWidth * i, y: height() - h, w: barWidth * (i + 1), h: height(), c: Qt::red);
280 // clear the rest of the control
281 painter.fillRect(x: barWidth * i, y: 0, w: barWidth * (i + 1), h: height() - h, c: Qt::black);
282 }
283}
284
285void FrameProcessor::processFrame(QVideoFrame frame, int levels)
286{
287 QVector<qreal> histogram(levels);
288
289 do {
290 if (!levels)
291 break;
292
293 if (!frame.map(mode: QAbstractVideoBuffer::ReadOnly))
294 break;
295
296 if (frame.pixelFormat() == QVideoFrame::Format_YUV420P ||
297 frame.pixelFormat() == QVideoFrame::Format_NV12) {
298 // Process YUV data
299 uchar *b = frame.bits();
300 for (int y = 0; y < frame.height(); ++y) {
301 uchar *lastPixel = b + frame.width();
302 for (uchar *curPixel = b; curPixel < lastPixel; curPixel++)
303 histogram[(*curPixel * levels) >> 8] += 1.0;
304 b += frame.bytesPerLine();
305 }
306 } else {
307 QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format: frame.pixelFormat());
308 if (imageFormat != QImage::Format_Invalid) {
309 // Process RGB data
310 QImage image(frame.bits(), frame.width(), frame.height(), imageFormat);
311 image = image.convertToFormat(f: QImage::Format_RGB32);
312
313 const QRgb* b = (const QRgb*)image.bits();
314 for (int y = 0; y < image.height(); ++y) {
315 const QRgb *lastPixel = b + frame.width();
316 for (const QRgb *curPixel = b; curPixel < lastPixel; curPixel++)
317 histogram[(qGray(rgb: *curPixel) * levels) >> 8] += 1.0;
318 b = (const QRgb*)((uchar*)b + image.bytesPerLine());
319 }
320 }
321 }
322
323 // find maximum value
324 qreal maxValue = 0.0;
325 for (int i = 0; i < histogram.size(); i++) {
326 if (histogram[i] > maxValue)
327 maxValue = histogram[i];
328 }
329
330 if (maxValue > 0.0) {
331 for (int i = 0; i < histogram.size(); i++)
332 histogram[i] /= maxValue;
333 }
334
335 frame.unmap();
336 } while (false);
337
338 emit histogramReady(histogram);
339}
340
341#include "histogramwidget.moc"
342

source code of qtmultimedia/examples/multimediawidgets/player/histogramwidget.cpp