Created
January 15, 2017 06:35
-
-
Save sgpinkus/bf66e84105cc3e23e7113cca5e3b1772 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdlib.h> | |
#include <math.h> | |
#include <iostream> | |
#include <QDebug> | |
#include <QAudioDeviceInfo> | |
#include <qendian.h> | |
#include "audiolevelmeter.h" | |
AudioLevelMeter::AudioLevelMeter(QObject *parent) | |
: QObject(parent) | |
{ | |
setFormat(QAudioFormat()); | |
} | |
AudioLevelMeter::~AudioLevelMeter() { | |
} | |
/** | |
* Check the format is supported and calculate m_maxAmplitude. | |
* maxAmplitude is only used for noramlizing samples. | |
*/ | |
bool AudioLevelMeter::setFormat(const QAudioFormat &format) | |
{ | |
m_audioFormat = format; | |
m_channelCount = format.channelCount(); | |
m_validAudioFormat = true; | |
if(m_audioFormat.channelCount() <= 0) { | |
m_validAudioFormat = false; | |
} | |
else { | |
switch (m_audioFormat.sampleSize()) { | |
case 8: | |
switch (m_audioFormat.sampleType()) { | |
case QAudioFormat::UnSignedInt: | |
m_maxAmplitude = 255; | |
break; | |
case QAudioFormat::SignedInt: | |
m_maxAmplitude = 128; | |
break; | |
default: | |
break; | |
} | |
break; | |
case 16: | |
switch (m_audioFormat.sampleType()) { | |
case QAudioFormat::UnSignedInt: | |
m_maxAmplitude = 65535; | |
break; | |
case QAudioFormat::SignedInt: | |
m_maxAmplitude = 32768; | |
break; | |
default: | |
break; | |
} | |
break; | |
case 32: | |
switch (m_audioFormat.sampleType()) { | |
case QAudioFormat::UnSignedInt: | |
m_maxAmplitude = 0xffffffff; | |
break; | |
case QAudioFormat::SignedInt: | |
m_maxAmplitude = 0x80000000; | |
break; | |
case QAudioFormat::Float: | |
m_maxAmplitude = 0x80000000; // Kind of | |
default: | |
break; | |
} | |
break; | |
default: | |
// qWarning() << "Unsupported audio format:" << m_audioFormat; | |
m_validAudioFormat = false; | |
} | |
} | |
if(m_validAudioFormat) { | |
init(); | |
} | |
return m_validAudioFormat; | |
} | |
/** | |
* Ensure lists have correct size and initial values. Bit shit. | |
*/ | |
void AudioLevelMeter::init() { | |
QList<qreal> l; | |
for(quint32 i = 0; i < m_channelCount; i++) { | |
l.insert(i, 0.0); | |
} | |
m_avgAmplitude = m_expAmplitude = m_peakAmplitude = l; | |
} | |
/** | |
* Process next audio buff. Data is expected to be formatted according to our QAudioFormat. | |
* All levels are normalized to maxAmplitud for convenience - between [0,1]. | |
*/ | |
int AudioLevelMeter::read(const QAudioBuffer &buffer) | |
{ | |
if(!isValid()) { | |
return 0; | |
} | |
char * data = (char*)buffer.data(); | |
quint32 len = buffer.byteCount(); | |
const quint32 channelBytes = m_audioFormat.sampleSize() / 8; | |
const quint32 sampleBytes = m_channelCount * channelBytes; | |
const quint32 numSamples = len / sampleBytes; | |
Q_ASSERT(len % sampleBytes == 0); | |
for (quint32 j = 0; j < m_channelCount; ++j) { | |
const unsigned char *ptr = reinterpret_cast<const unsigned char *>(data); | |
quint32 maxAmpValue = 0; // Max amplitude value in seen in working channel. | |
quint32 sumAmpValue = 0; // Sum amplitude values in working channel. | |
ptr += j*channelBytes; | |
for(quint32 i = 0; i < numSamples; ++i) { | |
qint64 value = 0; | |
if(m_audioFormat.sampleSize() == 8 && m_audioFormat.sampleType() == QAudioFormat::UnSignedInt) { | |
value = *reinterpret_cast<const quint8*>(ptr); | |
} | |
else if(m_audioFormat.sampleSize() == 8 && m_audioFormat.sampleType() == QAudioFormat::SignedInt) { | |
value = *reinterpret_cast<const qint8*>(ptr); | |
} | |
else if(m_audioFormat.sampleSize() == 16 && m_audioFormat.sampleType() == QAudioFormat::UnSignedInt) { | |
if(m_audioFormat.byteOrder() == QAudioFormat::LittleEndian) | |
value = qFromLittleEndian<quint16>(ptr); | |
else | |
value = qFromBigEndian<quint16>(ptr); | |
} | |
else if(m_audioFormat.sampleSize() == 16 && m_audioFormat.sampleType() == QAudioFormat::SignedInt) { | |
if(m_audioFormat.byteOrder() == QAudioFormat::LittleEndian) { | |
value = qFromLittleEndian<qint16>(ptr); | |
} | |
else | |
value = qFromBigEndian<qint16>(ptr); | |
} | |
else if(m_audioFormat.sampleSize() == 32 && m_audioFormat.sampleType() == QAudioFormat::UnSignedInt) { | |
if(m_audioFormat.byteOrder() == QAudioFormat::LittleEndian) | |
value = qFromLittleEndian<quint32>(ptr); | |
else | |
value = qFromBigEndian<quint32>(ptr); | |
} | |
else if(m_audioFormat.sampleSize() == 32 && m_audioFormat.sampleType() == QAudioFormat::SignedInt) { | |
if(m_audioFormat.byteOrder() == QAudioFormat::LittleEndian) | |
value = qFromLittleEndian<qint32>(ptr); | |
else | |
value = qFromBigEndian<qint32>(ptr); | |
} | |
else if(m_audioFormat.sampleSize() == 32 && m_audioFormat.sampleType() == QAudioFormat::Float) { | |
value = *reinterpret_cast<const float*>(ptr) * 0x80000000; // assumes 0-1.0 | |
} | |
sumAmpValue += qAbs(value); | |
maxAmpValue = qMax(quint32(qAbs(value)), maxAmpValue); | |
ptr += sampleBytes; | |
} | |
m_avgAmplitude[j] = qreal(sumAmpValue / (numSamples * m_channelCount)) / m_maxAmplitude; | |
m_expAmplitude[j] = qreal(m_expAmplitude[j]*m_alphaExp + m_avgAmplitude[j]*(1.0-m_alphaExp)); | |
m_peakAmplitude[j] = qreal(maxAmpValue) / m_maxAmplitude; | |
} | |
updateThresholLevel(); | |
emit update(len); | |
return len; | |
} | |
void AudioLevelMeter::setLevelThresholds(qreal l, qreal h) | |
{ | |
m_lowLevelThreshold = qMax(0.0, qMin(1.0, l)); | |
m_highLevelThreshold = qMax(0.0, qMin(1.0, h)); | |
if(m_lowLevelThreshold > m_highLevelThreshold) { | |
m_lowLevelThreshold = m_highLevelThreshold; | |
} | |
} | |
quint32 AudioLevelMeter::maxAmplitude() const | |
{ | |
return m_maxAmplitude; | |
} | |
QPair<qreal,qreal> AudioLevelMeter::levelThresholds() const | |
{ | |
return QPair<qreal,qreal>(m_lowLevelThreshold, m_highLevelThreshold); | |
} | |
QList<qreal> AudioLevelMeter::avgAmplitude() const | |
{ | |
return m_avgAmplitude; | |
} | |
QList<qreal> AudioLevelMeter::expAmplitude() const | |
{ | |
return m_expAmplitude; | |
} | |
QList<qreal> AudioLevelMeter::peakAmplitude() const | |
{ | |
return m_peakAmplitude; | |
} | |
qreal AudioLevelMeter::channelAvgAmplitude() const | |
{ | |
qreal v = 0; | |
foreach(qreal s, m_avgAmplitude) { | |
v += s; | |
} | |
return v/m_channelCount; | |
} | |
qreal AudioLevelMeter::channelExpAmplitude() const | |
{ | |
qreal v = 0; | |
foreach(qreal s, m_expAmplitude) { | |
v += s; | |
} | |
return v/m_channelCount; | |
} | |
qreal AudioLevelMeter::channelPeakAmplitude() const | |
{ | |
qreal v = 0; | |
foreach(qreal s, m_peakAmplitude) { | |
v += s; | |
} | |
return v/m_channelCount; | |
} | |
void AudioLevelMeter::updateThresholLevel() | |
{ | |
if(m_thresholdState == HIGH) { | |
if(channelExpAmplitude() < m_lowLevelThreshold) { | |
m_thresholdState = LOW; | |
emit levelChange(LOW); | |
} | |
} | |
else { | |
if(channelExpAmplitude() > m_highLevelThreshold) { | |
m_thresholdState = HIGH; | |
emit levelChange(HIGH); | |
} | |
} | |
} | |
QDebug operator<<(QDebug dbg, AudioLevelMeter &s) | |
{ | |
QPair<qreal,qreal> tholds = s.levelThresholds(); | |
QDebugStateSaver saver(dbg); | |
dbg.nospace() << "FORMAT:" << s.format() << "\n" | |
<< "STATE:" << ((s.thresholdState() == AudioLevelMeter::LOW) ? "LOW" : "HIGH") << "; " | |
<< "THRESHOLDS:" << tholds << "; " | |
<< "TRIGGERLEVEL:" << s.channelExpAmplitude() << "; " | |
<< "MAX_AMP:" << s.maxAmplitude() << "; " | |
<< "PEAK_AMP:" << s.peakAmplitude() << "; " | |
<< "AVG_AMP:" << s.avgAmplitude() << "; " | |
<< "EXP_AMP:" << s.expAmplitude() << "; "; | |
return dbg; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef AUDIOLEVELMONITOR_H | |
#define AUDIOLEVELMONITOR_H | |
#include <QObject> | |
#include <QAudioBuffer> | |
/** | |
* An audio level meter. | |
* Reads data from a QAudioBuffer and updates audio level readings. | |
* Also implements audio a heuristic trigger signal based on the average level of the audio. | |
*/ | |
class AudioLevelMeter : public QObject | |
{ | |
Q_OBJECT | |
public: | |
typedef enum { LOW, HIGH } ThresholdState; | |
AudioLevelMeter(QObject *parent = NULL); | |
~AudioLevelMeter(); | |
bool setFormat(const QAudioFormat &format); | |
void init(); | |
void setSensitivity(qreal sensitivity) { m_alphaExp = qMax(0.0, qMin(1.0, sensitivity)); } | |
void setLevelThresholds(qreal l, qreal h); | |
bool isValid() { return m_validAudioFormat; } | |
QAudioFormat format() const { return m_audioFormat; } | |
quint32 maxAmplitude() const; | |
QPair<qreal,qreal> levelThresholds() const; | |
QList<qreal> avgAmplitude() const; | |
QList<qreal> expAmplitude() const; | |
QList<qreal> peakAmplitude() const; | |
qreal channelAvgAmplitude() const; | |
qreal channelExpAmplitude() const; | |
qreal channelPeakAmplitude() const; | |
ThresholdState thresholdState() const { return m_thresholdState; } | |
private slots: | |
int read(const QAudioBuffer &buffer); | |
private: | |
void updateThresholLevel(); | |
QAudioFormat m_audioFormat; | |
bool m_validAudioFormat = false; | |
quint32 m_channelCount = 0; | |
qreal m_alphaExp = 0.990; | |
quint32 m_maxAmplitude = 0; | |
QList<qreal> m_peakAmplitude; | |
QList<qreal> m_avgAmplitude; | |
QList<qreal> m_expAmplitude; | |
ThresholdState m_thresholdState = LOW; | |
qreal m_lowLevelThreshold = 0.2; | |
qreal m_highLevelThreshold = 0.4; | |
signals: | |
void levelChange(AudioLevelMeter::ThresholdState level); | |
void update(int read); | |
}; | |
QDebug operator<<(QDebug dbg, AudioLevelMeter &s); | |
#endif // AUDIOLEVELMONITOR_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment