Added audio level calculation for the VU meter

preferencesAboutTextFull
Craig Watson 9 years ago
parent 518b7d26d4
commit 1e731be171
  1. 85
      src/podcast/ffmpeg/UBMicrophoneInput.cpp
  2. 3
      src/podcast/ffmpeg/UBMicrophoneInput.h

@ -50,7 +50,6 @@ int UBMicrophoneInput::sampleFormat()
int sampleSize = mAudioFormat.sampleSize(); int sampleSize = mAudioFormat.sampleSize();
QAudioFormat::SampleType sampleType = mAudioFormat.sampleType(); QAudioFormat::SampleType sampleType = mAudioFormat.sampleType();
// qDebug() << "Input sample format: " << sampleSize << "bits " << sampleType;
switch (sampleType) { switch (sampleType) {
case QAudioFormat::Unknown: case QAudioFormat::Unknown:
@ -61,10 +60,12 @@ int UBMicrophoneInput::sampleFormat()
return AV_SAMPLE_FMT_S16; return AV_SAMPLE_FMT_S16;
if (sampleSize == 32) if (sampleSize == 32)
return AV_SAMPLE_FMT_S32; return AV_SAMPLE_FMT_S32;
break;
case QAudioFormat::UnSignedInt: case QAudioFormat::UnSignedInt:
if (sampleSize == 8) if (sampleSize == 8)
return AV_SAMPLE_FMT_U8; return AV_SAMPLE_FMT_U8;
break;
case QAudioFormat::Float: case QAudioFormat::Float:
return AV_SAMPLE_FMT_FLT; return AV_SAMPLE_FMT_FLT;
@ -72,6 +73,8 @@ int UBMicrophoneInput::sampleFormat()
default: default:
return AV_SAMPLE_FMT_NONE; return AV_SAMPLE_FMT_NONE;
} }
return AV_SAMPLE_FMT_NONE;
} }
QString UBMicrophoneInput::codec() QString UBMicrophoneInput::codec()
@ -96,11 +99,14 @@ bool UBMicrophoneInput::init()
mAudioFormat = mAudioDeviceInfo.preferredFormat(); mAudioFormat = mAudioDeviceInfo.preferredFormat();
mAudioInput = new QAudioInput(mAudioDeviceInfo, mAudioFormat, NULL); mAudioInput = new QAudioInput(mAudioDeviceInfo, mAudioFormat, NULL);
//mAudioInput->setNotifyInterval(100);
connect(mAudioInput, SIGNAL(stateChanged(QAudio::State)), connect(mAudioInput, SIGNAL(stateChanged(QAudio::State)),
this, SLOT(onAudioInputStateChanged(QAudio::State))); this, SLOT(onAudioInputStateChanged(QAudio::State)));
qDebug() << "Input sample format: " << mAudioFormat.sampleSize() << "bit"
<< mAudioFormat.sampleType() << "at" << mAudioFormat.sampleRate() << "Hz"
<< "; codec: " << mAudioFormat.codec();
return true; return true;
} }
@ -159,12 +165,26 @@ void UBMicrophoneInput::setInputDevice(QString name)
} }
static qint64 uSecsElapsed = 0;
void UBMicrophoneInput::onDataReady() void UBMicrophoneInput::onDataReady()
{ {
int numBytes = mAudioInput->bytesReady(); int numBytes = mAudioInput->bytesReady();
if (numBytes > 0) uSecsElapsed += mAudioFormat.durationForBytes(numBytes);
emit dataAvailable(mIODevice->read(numBytes));
// Only emit data every 100ms
if (uSecsElapsed > 100000) {
uSecsElapsed = 0;
QByteArray data = mIODevice->read(numBytes);
quint8 level = audioLevel(data);
if (level != mLastAudioLevel) {
mLastAudioLevel = level;
emit audioLevelChanged(level);
}
emit dataAvailable(data);
}
} }
void UBMicrophoneInput::onAudioInputStateChanged(QAudio::State state) void UBMicrophoneInput::onAudioInputStateChanged(QAudio::State state)
@ -184,6 +204,63 @@ void UBMicrophoneInput::onAudioInputStateChanged(QAudio::State state)
} }
} }
/**
* @brief Calculate the current audio level of an array of samples and return it
* @param data An array of audio samples
* @return A value between 0 and 255
*
* Audio level is calculated as the RMS (root mean square) of the samples
* in the supplied array.
*/
quint8 UBMicrophoneInput::audioLevel(const QByteArray &data)
{
int bytesPerSample = mAudioFormat.bytesPerFrame() / mAudioFormat.channelCount();
const char * ptr = data.constData();
double sum = 0;
int n_samples = data.size() / bytesPerSample;
for (int i(0); i < (data.size() - bytesPerSample); i += bytesPerSample) {
sum += pow(sampleRelativeLevel(ptr + i), 2);
}
double rms = sqrt(sum/n_samples);
// The vu meter looks a bit better when the RMS isn't displayed linearly, as perceived sound
// level increases logarithmically. So here RMS can be substituted by something like
// rms^(1/e)
rms = pow(rms, 1./exp(1));
return UINT8_MAX * rms;
}
/**
* @brief Calculate one sample's level relative to its maximum value
* @param sample One sample, in the format specified by mAudioFormat
* @return A double between 0 and 1.0, where 1.0 is the maximum value the sample can take.
*/
double UBMicrophoneInput::sampleRelativeLevel(const char* sample)
{
QAudioFormat::SampleType type = mAudioFormat.sampleType();
int sampleSize = mAudioFormat.sampleSize();
if (sampleSize == 16 && type == QAudioFormat::SignedInt)
return double(*reinterpret_cast<const int16_t*>(sample))/INT16_MAX;
if (sampleSize == 8 && type == QAudioFormat::SignedInt)
return double(*reinterpret_cast<const int8_t*>(sample))/INT8_MAX;
if (sampleSize == 16 && type == QAudioFormat::UnSignedInt)
return double(*reinterpret_cast<const uint16_t*>(sample))/UINT16_MAX;
if (sampleSize == 8 && type == QAudioFormat::UnSignedInt)
return double(*reinterpret_cast<const uint8_t*>(sample))/UINT8_MAX;
if (type == QAudioFormat::Float)
return (*reinterpret_cast<const float*>(sample) + 1.0)/2.;
return -1;
}
/** /**
* @brief Return a meaningful error string based on QAudio error codes * @brief Return a meaningful error string based on QAudio error codes

@ -44,6 +44,8 @@ private slots:
void onDataReady(); void onDataReady();
private: private:
double sampleRelativeLevel(const char* sample);
quint8 audioLevel(const QByteArray& data);
QString getErrorString(QAudio::Error errorCode); QString getErrorString(QAudio::Error errorCode);
QAudioInput* mAudioInput; QAudioInput* mAudioInput;
@ -52,6 +54,7 @@ private:
QAudioFormat mAudioFormat; QAudioFormat mAudioFormat;
qint64 mSeekPos; qint64 mSeekPos;
quint8 mLastAudioLevel;
}; };
#endif // UBMICROPHONEINPUT_H #endif // UBMICROPHONEINPUT_H

Loading…
Cancel
Save