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:
parent
cbf0f2e7e0
commit
44d1c6fe74
|
@ -34,6 +34,7 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QMutexLocker>
|
#include <QMutexLocker>
|
||||||
|
#include <QPointer>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QWaitCondition>
|
#include <QWaitCondition>
|
||||||
|
|
||||||
|
@ -47,6 +48,8 @@
|
||||||
#include <AL/alc.h>
|
#include <AL/alc.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Audio* Audio::instance{nullptr};
|
||||||
|
|
||||||
class AudioPrivate
|
class AudioPrivate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -86,6 +89,8 @@ public:
|
||||||
Audio::PtrList outputSubscriptions;
|
Audio::PtrList outputSubscriptions;
|
||||||
bool inputInitialized;
|
bool inputInitialized;
|
||||||
bool outputInitialized;
|
bool outputInitialized;
|
||||||
|
|
||||||
|
QPointer<AudioMeter> mAudioMeter;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -134,7 +139,115 @@ private:
|
||||||
ALuint mSource;
|
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.
|
Returns the singleton's instance. Will construct on first call.
|
||||||
|
@ -149,6 +262,14 @@ Audio& Audio::getInstance()
|
||||||
return *instance;
|
return *instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioMeterListener* Audio::createAudioMeterListener() const
|
||||||
|
{
|
||||||
|
if (!d->mAudioMeter)
|
||||||
|
d->mAudioMeter = new AudioMeter;
|
||||||
|
|
||||||
|
return new AudioMeterListener(d->mAudioMeter);
|
||||||
|
}
|
||||||
|
|
||||||
Audio::Audio()
|
Audio::Audio()
|
||||||
: d(new AudioPrivate)
|
: d(new AudioPrivate)
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
|
|
||||||
struct Tox;
|
struct Tox;
|
||||||
class AudioFilterer;
|
class AudioFilterer;
|
||||||
|
class AudioMeter;
|
||||||
|
class AudioMeterListener;
|
||||||
class AudioPrivate;
|
class AudioPrivate;
|
||||||
|
|
||||||
// Public default audio settings
|
// Public default audio settings
|
||||||
|
@ -49,6 +51,8 @@ public:
|
||||||
public:
|
public:
|
||||||
void startAudioThread();
|
void startAudioThread();
|
||||||
|
|
||||||
|
AudioMeterListener* createAudioMeterListener() const;
|
||||||
|
|
||||||
qreal outputVolume();
|
qreal outputVolume();
|
||||||
void setOutputVolume(qreal volume);
|
void setOutputVolume(qreal volume);
|
||||||
|
|
||||||
|
@ -105,4 +109,25 @@ private:
|
||||||
AudioPrivate* d;
|
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
|
#endif // AUDIO_H
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
|
|
||||||
MicFeedbackWidget::MicFeedbackWidget(QWidget *parent)
|
MicFeedbackWidget::MicFeedbackWidget(QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, timerId(0)
|
|
||||||
{
|
{
|
||||||
setFixedHeight(20);
|
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*)
|
void MicFeedbackWidget::showEvent(QShowEvent*)
|
||||||
{
|
{
|
||||||
timerId = startTimer(60);
|
mMeterListener = Audio::getInstance().createAudioMeterListener();
|
||||||
|
connect(mMeterListener, &AudioMeterListener::gainChanged, this, &MicFeedbackWidget::onGainMetered);
|
||||||
|
mMeterListener->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicFeedbackWidget::hideEvent(QHideEvent*)
|
void MicFeedbackWidget::hideEvent(QHideEvent*)
|
||||||
{
|
{
|
||||||
if (timerId != 0)
|
mMeterListener->stop();
|
||||||
{
|
}
|
||||||
killTimer(timerId);
|
|
||||||
timerId = 0;
|
void MicFeedbackWidget::onGainMetered(qreal value)
|
||||||
}
|
{
|
||||||
|
current = value;
|
||||||
|
//qDebug("Gain metered at %.3f", current);
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
class AudioMeterListener;
|
||||||
|
|
||||||
class MicFeedbackWidget : public QWidget
|
class MicFeedbackWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -30,13 +32,15 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent* event) override;
|
void paintEvent(QPaintEvent* event) override;
|
||||||
void timerEvent(QTimerEvent* event) override;
|
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
void hideEvent(QHideEvent* event) override;
|
void hideEvent(QHideEvent* event) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onGainMetered(qreal value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double current;
|
qreal current;
|
||||||
int timerId;
|
AudioMeterListener* mMeterListener;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MICFEEDBACKWIDGET_H
|
#endif // MICFEEDBACKWIDGET_H
|
||||||
|
|
Loading…
Reference in New Issue
Block a user