1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

[WIP] implement threaded level meter for input level

Issues:
1. Using 100% processor.
2. Temporary silences an active call -> reactivate after widget hide.

Still greatly improves metering audio data, removing "read buffer" errors.
This commit is contained in:
Nils Fenner 2015-11-17 00:14:50 +01:00
parent cbf0f2e7e0
commit 44d1c6fe74
No known key found for this signature in database
GPG Key ID: 9591A163FF9BE04C
4 changed files with 165 additions and 36 deletions

View File

@ -34,6 +34,7 @@
#include <QDebug>
#include <QFile>
#include <QMutexLocker>
#include <QPointer>
#include <QThread>
#include <QWaitCondition>
@ -47,6 +48,8 @@
#include <AL/alc.h>
#endif
Audio* Audio::instance{nullptr};
class AudioPrivate
{
public:
@ -86,6 +89,8 @@ public:
Audio::PtrList outputSubscriptions;
bool inputInitialized;
bool outputInitialized;
QPointer<AudioMeter> mAudioMeter;
};
/**
@ -134,7 +139,115 @@ private:
ALuint mSource;
};
Audio* Audio::instance{nullptr};
class AudioMeter : public QThread
{
public:
AudioMeter()
: mActive(false)
{
connect(this, &AudioMeter::finished, this, &AudioMeter::deleteLater);
}
void waitForData(QMutex* condition)
{
mCheckGainChanged.wait(condition);
}
void monitorFrame()
{
mDoMonitoring.wakeAll();
}
private:
void run() override final
{
static const int framesize = AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS;
Audio& audio = Audio::getInstance();
mNewMaxGain = 0.f;
mActive = true;
while (mActive) {
int16_t buff[framesize] = {0};
if (audio.tryCaptureSamples(buff, AUDIO_FRAME_SAMPLE_COUNT)) {
mMeterLock.lock();
mNewMaxGain = 0.f;
for (int i = 0; i < framesize; ++i) {
mNewMaxGain = qMax(mNewMaxGain, qAbs(buff[i]) / 32767.0);
}
mMeterLock.unlock();
} else if (mNewMaxGain > 0.f) {
mNewMaxGain -= 0.01f;
}
mMeterLock.lock();
mCheckGainChanged.wakeAll();
mDoMonitoring.wait(&mMeterLock);
mMeterLock.unlock();
}
}
public:
QMutex mMeterLock;
QWaitCondition mDoMonitoring;
QWaitCondition mCheckGainChanged;
bool mActive;
qreal mNewMaxGain;
};
AudioMeterListener::AudioMeterListener(AudioMeter* measureThread)
: mActive(false)
, mAudioMeter(measureThread)
{
assert(mAudioMeter);
}
void AudioMeterListener::start()
{
QThread* listener = new QThread;
connect(listener, &QThread::started, this, &AudioMeterListener::doListen);
connect(listener, &QThread::finished, listener, &QThread::deleteLater);
moveToThread(listener);
listener->start();
mAudioMeter->start();
}
void AudioMeterListener::stop()
{
mActive = false;
}
void AudioMeterListener::doListen()
{
mMaxGain = 0.f;
mActive = true;
QMutexLocker locker(&mAudioMeter->mMeterLock);
while (mActive) {
mAudioMeter->waitForData(locker.mutex());
locker.unlock();
//qDebug() << "GAIN:" << mAudioMeter->mNewMaxGain << "/" << mMaxGain;
if (mAudioMeter->mNewMaxGain != mMaxGain) {
if (mAudioMeter->mNewMaxGain > mMaxGain)
{
mMaxGain = mAudioMeter->mNewMaxGain;
emit gainChanged(mMaxGain);
} else if (mMaxGain > 0.02f) {
mMaxGain -= 0.0005f;
emit gainChanged(mMaxGain);
}
}
locker.relock();
mAudioMeter->monitorFrame();
}
mAudioMeter->mActive = false;
}
/**
Returns the singleton's instance. Will construct on first call.
@ -149,6 +262,14 @@ Audio& Audio::getInstance()
return *instance;
}
AudioMeterListener* Audio::createAudioMeterListener() const
{
if (!d->mAudioMeter)
d->mAudioMeter = new AudioMeter;
return new AudioMeterListener(d->mAudioMeter);
}
Audio::Audio()
: d(new AudioPrivate)
{

View File

@ -28,6 +28,8 @@
struct Tox;
class AudioFilterer;
class AudioMeter;
class AudioMeterListener;
class AudioPrivate;
// Public default audio settings
@ -49,6 +51,8 @@ public:
public:
void startAudioThread();
AudioMeterListener* createAudioMeterListener() const;
qreal outputVolume();
void setOutputVolume(qreal volume);
@ -105,4 +109,25 @@ private:
AudioPrivate* d;
};
class AudioMeterListener : public QObject
{
Q_OBJECT
public:
explicit AudioMeterListener(AudioMeter* measureThread);
void start();
void stop();
signals:
void gainChanged(qreal newMaxGain);
private slots:
void doListen();
private:
bool mActive;
AudioMeter* mAudioMeter;
qreal mMaxGain;
};
#endif // AUDIO_H

View File

@ -24,7 +24,6 @@
MicFeedbackWidget::MicFeedbackWidget(QWidget *parent)
: QWidget(parent)
, timerId(0)
{
setFixedHeight(20);
}
@ -59,41 +58,21 @@ void MicFeedbackWidget::paintEvent(QPaintEvent*)
}
}
void MicFeedbackWidget::timerEvent(QTimerEvent*)
{
const int framesize = AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS;
int16_t buff[framesize] = {0};
if (Audio::getInstance().tryCaptureSamples(buff, AUDIO_FRAME_SAMPLE_COUNT))
{
double max = 0;
for (int i = 0; i < framesize; ++i)
max = std::max(max, fabs(buff[i] / 32767.0));
if (max > current)
current = max;
else
current -= 0.05;
update();
}
else if (current > 0)
{
current -= 0.01;
}
}
void MicFeedbackWidget::showEvent(QShowEvent*)
{
timerId = startTimer(60);
mMeterListener = Audio::getInstance().createAudioMeterListener();
connect(mMeterListener, &AudioMeterListener::gainChanged, this, &MicFeedbackWidget::onGainMetered);
mMeterListener->start();
}
void MicFeedbackWidget::hideEvent(QHideEvent*)
{
if (timerId != 0)
{
killTimer(timerId);
timerId = 0;
}
mMeterListener->stop();
}
void MicFeedbackWidget::onGainMetered(qreal value)
{
current = value;
//qDebug("Gain metered at %.3f", current);
update();
}

View File

@ -22,6 +22,8 @@
#include <QWidget>
class AudioMeterListener;
class MicFeedbackWidget : public QWidget
{
Q_OBJECT
@ -30,13 +32,15 @@ public:
protected:
void paintEvent(QPaintEvent* event) override;
void timerEvent(QTimerEvent* event) override;
void showEvent(QShowEvent* event) override;
void hideEvent(QHideEvent* event) override;
private slots:
void onGainMetered(qreal value);
private:
double current;
int timerId;
qreal current;
AudioMeterListener* mMeterListener;
};
#endif // MICFEEDBACKWIDGET_H