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

feat(audio): add real gain control of the input device

The gain can be set now in dB values, providing acceptable results for a wide range of setups.

note: also introduces const-correctness and some minor cleanup.
This commit is contained in:
Nils Fenner 2016-05-29 21:40:05 +02:00
parent 9de833ad39
commit f72baa613f
No known key found for this signature in database
GPG Key ID: 9591A163FF9BE04C
6 changed files with 190 additions and 62 deletions

View File

@ -27,6 +27,7 @@
#include <QMutexLocker> #include <QMutexLocker>
#include <QPointer> #include <QPointer>
#include <QThread> #include <QThread>
#include <QtMath>
#include <QWaitCondition> #include <QWaitCondition>
#include <cassert> #include <cassert>
@ -35,6 +36,49 @@
#include "audiofilterer.h" #include "audiofilterer.h"
#endif #endif
/**
@internal
@class Audio::Private
@brief Encapsulates private audio framework from public qTox Audio API.
*/
class Audio::Private
{
public:
Private()
: minInputGain{-30.0}
, maxInputGain{30.0}
, gain{0.0}
, gainFactor{0.0}
{
}
qreal inputGain() const
{
return gain;
}
qreal inputGainFactor() const
{
return gainFactor;
}
void setInputGain(qreal dB)
{
gain = qBound(minInputGain, dB, maxInputGain);
gainFactor = qPow(10.0, (gain / 20.0));
}
public:
qreal minInputGain;
qreal maxInputGain;
private:
qreal gain;
qreal gainFactor;
};
/** /**
Returns the singleton instance. Returns the singleton instance.
*/ */
@ -45,15 +89,15 @@ Audio& Audio::getInstance()
} }
Audio::Audio() Audio::Audio()
: audioThread(new QThread) : d{new Private}
, alInDev(nullptr) , audioThread{new QThread}
, inGain{1.f} , alInDev{nullptr}
, inSubscriptions(0) , inSubscriptions{0}
, alOutDev(nullptr) , alOutDev{nullptr}
, alOutContext(nullptr) , alOutContext{nullptr}
, alMainSource(0) , alMainSource{0}
, alMainBuffer(0) , alMainBuffer{0}
, outputInitialized(false) , outputInitialized{false}
{ {
// initialize OpenAL error stack // initialize OpenAL error stack
alGetError(); alGetError();
@ -87,6 +131,7 @@ Audio::~Audio()
#ifdef QTOX_FILTER_AUDIO #ifdef QTOX_FILTER_AUDIO
filterer.closeFilter(); filterer.closeFilter();
#endif #endif
delete d;
} }
void Audio::checkAlError() noexcept void Audio::checkAlError() noexcept
@ -106,13 +151,14 @@ void Audio::checkAlcError(ALCdevice *device) noexcept
/** /**
Returns the current output volume (between 0 and 1) Returns the current output volume (between 0 and 1)
*/ */
ALfloat Audio::outputVolume() qreal Audio::outputVolume() const
{ {
QMutexLocker locker(&audioLock); QMutexLocker locker(&audioLock);
ALfloat volume = 0.0; ALfloat volume = 0.0;
if (alOutDev) { if (alOutDev)
{
alGetListenerf(AL_GAIN, &volume); alGetListenerf(AL_GAIN, &volume);
checkAlError(); checkAlError();
} }
@ -125,28 +171,78 @@ Set the master output volume.
@param[in] volume the master volume (between 0 and 1) @param[in] volume the master volume (between 0 and 1)
*/ */
void Audio::setOutputVolume(ALfloat volume) void Audio::setOutputVolume(qreal volume)
{ {
QMutexLocker locker(&audioLock); QMutexLocker locker(&audioLock);
volume = std::max(0.f, std::min(volume, 1.f)); volume = std::max(0.0, std::min(volume, 1.0));
alListenerf(AL_GAIN, volume); alListenerf(AL_GAIN, static_cast<ALfloat>(volume));
checkAlError(); checkAlError();
} }
ALfloat Audio::inputVolume() /**
@brief The minimum gain value for an input device.
@return minimum gain value in dB
*/
qreal Audio::minInputGain() const
{ {
QMutexLocker locker(&audioLock); QMutexLocker locker(&audioLock);
return d->minInputGain;
return inGain;
} }
void Audio::setInputVolume(ALfloat volume) /**
@brief Set the minimum allowed gain value in dB.
@note Default is -30dB; usually you don't need to alter this value;
*/
void Audio::setMinInputGain(qreal dB)
{ {
QMutexLocker locker(&audioLock); QMutexLocker locker(&audioLock);
d->minInputGain = dB;
}
inGain = std::max(0.f, std::min(volume, 1.f)); /**
@brief The maximum gain value for an input device.
@return maximum gain value in dB
*/
qreal Audio::maxInputGain() const
{
QMutexLocker locker(&audioLock);
return d->maxInputGain;
}
/**
@brief Set the maximum allowed gain value in dB.
@note Default is 30dB; usually you don't need to alter this value.
*/
void Audio::setMaxInputGain(qreal dB)
{
QMutexLocker locker(&audioLock);
d->maxInputGain = dB;
}
/**
@brief The dB gain value.
@return the gain value in dB
*/
qreal Audio::inputGain() const
{
QMutexLocker locker(&audioLock);
return d->inputGain();
}
/**
Set the input gain dB level.
*/
void Audio::setInputGain(qreal dB)
{
QMutexLocker locker(&audioLock);
d->setInputGain(dB);
} }
void Audio::reinitInput(const QString& inDevDesc) void Audio::reinitInput(const QString& inDevDesc)
@ -172,7 +268,8 @@ void Audio::subscribeInput()
{ {
QMutexLocker locker(&audioLock); QMutexLocker locker(&audioLock);
if (!autoInitInput()) { if (!autoInitInput())
{
qWarning("Failed to subscribe to audio input device."); qWarning("Failed to subscribe to audio input device.");
return; return;
} }
@ -235,7 +332,8 @@ bool Audio::initInput(QString inDevDescr)
const uint16_t frameDuration = AUDIO_FRAME_DURATION; const uint16_t frameDuration = AUDIO_FRAME_DURATION;
const uint32_t chnls = AUDIO_CHANNELS; const uint32_t chnls = AUDIO_CHANNELS;
const ALCsizei bufSize = (frameDuration * sampleRate * 4) / 1000 * chnls; const ALCsizei bufSize = (frameDuration * sampleRate * 4) / 1000 * chnls;
if (inDevDescr.isEmpty()) { if (inDevDescr.isEmpty())
{
const ALchar *pDeviceList = Audio::inDeviceNames(); const ALchar *pDeviceList = Audio::inDeviceNames();
if (pDeviceList) if (pDeviceList)
inDevDescr = QString::fromUtf8(pDeviceList, strlen(pDeviceList)); inDevDescr = QString::fromUtf8(pDeviceList, strlen(pDeviceList));
@ -246,11 +344,14 @@ bool Audio::initInput(QString inDevDescr)
sampleRate, stereoFlag, bufSize); sampleRate, stereoFlag, bufSize);
// Restart the capture if necessary // Restart the capture if necessary
if (!alInDev) { if (!alInDev)
{
qWarning() << "Failed to initialize audio input device:" << inDevDescr; qWarning() << "Failed to initialize audio input device:" << inDevDescr;
return false; return false;
} }
d->setInputGain(Settings::getInstance().getAudioInGain());
qDebug() << "Opened audio input" << inDevDescr; qDebug() << "Opened audio input" << inDevDescr;
alcCaptureStart(alInDev); alcCaptureStart(alInDev);
@ -273,7 +374,8 @@ bool Audio::initOutput(QString outDevDescr)
assert(!alOutDev); assert(!alOutDev);
if (outDevDescr.isEmpty()) { if (outDevDescr.isEmpty())
{
// default to the first available audio device. // default to the first available audio device.
const ALchar *pDeviceList = Audio::outDeviceNames(); const ALchar *pDeviceList = Audio::outDeviceNames();
if (pDeviceList) if (pDeviceList)
@ -293,7 +395,8 @@ bool Audio::initOutput(QString outDevDescr)
alOutContext = alcCreateContext(alOutDev, nullptr); alOutContext = alcCreateContext(alOutDev, nullptr);
checkAlcError(alOutDev); checkAlcError(alOutDev);
if (!alcMakeContextCurrent(alOutContext)) { if (!alcMakeContextCurrent(alOutContext))
{
qWarning() << "Cannot create output audio context"; qWarning() << "Cannot create output audio context";
return false; return false;
} }
@ -306,7 +409,8 @@ bool Audio::initOutput(QString outDevDescr)
checkAlError(); checkAlError();
Core* core = Core::getInstance(); Core* core = Core::getInstance();
if (core) { if (core)
{
// reset each call's audio source // reset each call's audio source
core->getAv()->invalidateCallSources(); core->getAv()->invalidateCallSources();
} }
@ -421,7 +525,8 @@ void Audio::cleanupOutput()
{ {
outputInitialized = false; outputInitialized = false;
if (alOutDev) { if (alOutDev)
{
alSourcei(alMainSource, AL_LOOPING, AL_FALSE); alSourcei(alMainSource, AL_LOOPING, AL_FALSE);
alSourceStop(alMainSource); alSourceStop(alMainSource);
alDeleteSources(1, &alMainSource); alDeleteSources(1, &alMainSource);
@ -486,8 +591,15 @@ void Audio::doCapture()
} }
#endif #endif
for (size_t i = 0; i < AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS; ++i) for (quint32 i = 0; i < AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS; ++i)
buf[i] *= inGain; {
// gain amplification with clipping to 16-bit boundaries
int ampPCM = qBound<int>(std::numeric_limits<int16_t>::min(),
qRound(buf[i] * d->inputGainFactor()),
std::numeric_limits<int16_t>::max());
buf[i] = static_cast<int16_t>(ampPCM);
}
emit frameAvailable(buf, AUDIO_FRAME_SAMPLE_COUNT, AUDIO_CHANNELS, AUDIO_SAMPLE_RATE); emit frameAvailable(buf, AUDIO_FRAME_SAMPLE_COUNT, AUDIO_CHANNELS, AUDIO_SAMPLE_RATE);
} }
@ -495,7 +607,7 @@ void Audio::doCapture()
/** /**
Returns true if the input device is open and suscribed to Returns true if the input device is open and suscribed to
*/ */
bool Audio::isInputReady() bool Audio::isInputReady() const
{ {
QMutexLocker locker(&audioLock); QMutexLocker locker(&audioLock);
return alInDev && inSubscriptions; return alInDev && inSubscriptions;
@ -504,7 +616,7 @@ bool Audio::isInputReady()
/** /**
Returns true if the output device is open Returns true if the output device is open
*/ */
bool Audio::isOutputReady() bool Audio::isOutputReady() const
{ {
QMutexLocker locker(&audioLock); QMutexLocker locker(&audioLock);
return alOutDev && outputInitialized; return alOutDev && outputInitialized;
@ -530,12 +642,14 @@ void Audio::subscribeOutput(ALuint& sid)
{ {
QMutexLocker locker(&audioLock); QMutexLocker locker(&audioLock);
if (!autoInitOutput()) { if (!autoInitOutput())
{
qWarning("Failed to subscribe to audio output device."); qWarning("Failed to subscribe to audio output device.");
return; return;
} }
if (!alcMakeContextCurrent(alOutContext)) { if (!alcMakeContextCurrent(alOutContext))
{
qWarning("Failed to activate output context."); qWarning("Failed to activate output context.");
return; return;
} }
@ -554,8 +668,10 @@ void Audio::unsubscribeOutput(ALuint &sid)
outSources.removeAll(sid); outSources.removeAll(sid);
if (sid) { if (sid)
if (alIsSource(sid)) { {
if (alIsSource(sid))
{
alDeleteSources(1, &sid); alDeleteSources(1, &sid);
qDebug() << "Audio source" << sid << "deleted. Sources active:" qDebug() << "Audio source" << sid << "deleted. Sources active:"
<< outSources.size(); << outSources.size();

View File

@ -56,20 +56,26 @@ class Audio : public QObject
{ {
Q_OBJECT Q_OBJECT
class Private;
public: public:
static Audio& getInstance(); static Audio& getInstance();
ALfloat outputVolume(); qreal outputVolume() const;
void setOutputVolume(ALfloat volume); void setOutputVolume(qreal volume);
ALfloat inputVolume(); qreal minInputGain() const;
void setInputVolume(ALfloat volume); void setMinInputGain(qreal dB);
qreal maxInputGain() const;
void setMaxInputGain(qreal dB);
qreal inputGain() const;
void setInputGain(qreal dB);
void reinitInput(const QString& inDevDesc); void reinitInput(const QString& inDevDesc);
bool reinitOutput(const QString& outDevDesc); bool reinitOutput(const QString& outDevDesc);
bool isInputReady(); bool isInputReady() const;
bool isOutputReady(); bool isOutputReady() const;
static const char* outDeviceNames(); static const char* outDeviceNames();
static const char* inDeviceNames(); static const char* inDeviceNames();
@ -113,12 +119,14 @@ private:
void getEchoesToFilter(AudioFilterer* filter, int samples); void getEchoesToFilter(AudioFilterer* filter, int samples);
#endif #endif
private:
Private* d;
private: private:
QThread* audioThread; QThread* audioThread;
QMutex audioLock; mutable QMutex audioLock;
ALCdevice* alInDev; ALCdevice* alInDev;
ALfloat inGain;
quint32 inSubscriptions; quint32 inSubscriptions;
QTimer captureTimer, playMono16Timer; QTimer captureTimer, playMono16Timer;

View File

@ -236,7 +236,7 @@ void Settings::loadGlobal()
s.beginGroup("Audio"); s.beginGroup("Audio");
inDev = s.value("inDev", "").toString(); inDev = s.value("inDev", "").toString();
outDev = s.value("outDev", "").toString(); outDev = s.value("outDev", "").toString();
inVolume = s.value("inVolume", 100).toInt(); audioInGainDecibel = s.value("inGain", 0).toReal();
outVolume = s.value("outVolume", 100).toInt(); outVolume = s.value("outVolume", 100).toInt();
filterAudio = s.value("filterAudio", false).toBool(); filterAudio = s.value("filterAudio", false).toBool();
s.endGroup(); s.endGroup();
@ -467,7 +467,7 @@ void Settings::saveGlobal()
s.beginGroup("Audio"); s.beginGroup("Audio");
s.setValue("inDev", inDev); s.setValue("inDev", inDev);
s.setValue("outDev", outDev); s.setValue("outDev", outDev);
s.setValue("inVolume", inVolume); s.setValue("inGain", audioInGainDecibel);
s.setValue("outVolume", outVolume); s.setValue("outVolume", outVolume);
s.setValue("filterAudio", filterAudio); s.setValue("filterAudio", filterAudio);
s.endGroup(); s.endGroup();
@ -1366,16 +1366,16 @@ void Settings::setInDev(const QString& deviceSpecifier)
inDev = deviceSpecifier; inDev = deviceSpecifier;
} }
int Settings::getInVolume() const qreal Settings::getAudioInGain() const
{ {
QMutexLocker locker{&bigLock}; QMutexLocker locker{&bigLock};
return inVolume; return audioInGainDecibel;
} }
void Settings::setInVolume(int volume) void Settings::setAudioInGain(qreal dB)
{ {
QMutexLocker locker{&bigLock}; QMutexLocker locker{&bigLock};
inVolume = volume; audioInGainDecibel = dB;
} }
QString Settings::getVideoDev() const QString Settings::getVideoDev() const

View File

@ -181,8 +181,8 @@ public:
QString getOutDev() const; QString getOutDev() const;
void setOutDev(const QString& deviceSpecifier); void setOutDev(const QString& deviceSpecifier);
int getInVolume() const; qreal getAudioInGain() const;
void setInVolume(int volume); void setAudioInGain(qreal dB);
int getOutVolume() const; int getOutVolume() const;
void setOutVolume(int volume); void setOutVolume(int volume);
@ -432,7 +432,7 @@ private:
// Audio // Audio
QString inDev; QString inDev;
QString outDev; QString outDev;
int inVolume; qreal audioInGainDecibel;
int outVolume; int outVolume;
bool filterAudio; bool filterAudio;

View File

@ -45,6 +45,8 @@ AVForm::AVForm() :
bodyUI = new Ui::AVSettings; bodyUI = new Ui::AVSettings;
bodyUI->setupUi(this); bodyUI->setupUi(this);
const Audio& audio = Audio::getInstance();
bodyUI->btnPlayTestSound->setToolTip( bodyUI->btnPlayTestSound->setToolTip(
tr("Play a test sound while changing the output volume.")); tr("Play a test sound while changing the output volume."));
@ -65,6 +67,14 @@ AVForm::AVForm() :
bodyUI->playbackSlider->installEventFilter(this); bodyUI->playbackSlider->installEventFilter(this);
connect(bodyUI->playbackSlider, &QSlider::valueChanged, connect(bodyUI->playbackSlider, &QSlider::valueChanged,
this, &AVForm::onPlaybackValueChanged); this, &AVForm::onPlaybackValueChanged);
bodyUI->microphoneSlider->setToolTip(
tr("Use slider to set the gain of your input device ranging"
" from %1dB to %2dB.")
.arg(audio.minInputGain())
.arg(audio.maxInputGain()));
bodyUI->microphoneSlider->setMinimum(qRound(audio.minInputGain()) * 10);
bodyUI->microphoneSlider->setMaximum(qRound(audio.maxInputGain()) * 10);
bodyUI->microphoneSlider->setTracking(false); bodyUI->microphoneSlider->setTracking(false);
bodyUI->microphoneSlider->installEventFilter(this); bodyUI->microphoneSlider->installEventFilter(this);
connect(bodyUI->microphoneSlider, &QSlider::valueChanged, connect(bodyUI->microphoneSlider, &QSlider::valueChanged,
@ -378,7 +388,7 @@ void AVForm::onInDevChanged(QString deviceDescriptor)
Audio& audio = Audio::getInstance(); Audio& audio = Audio::getInstance();
audio.reinitInput(deviceDescriptor); audio.reinitInput(deviceDescriptor);
bodyUI->microphoneSlider->setEnabled(bodyUI->inDevCombobox->currentIndex() != 0); bodyUI->microphoneSlider->setEnabled(bodyUI->inDevCombobox->currentIndex() != 0);
bodyUI->microphoneSlider->setSliderPosition(audio.inputVolume() * 100.f); bodyUI->microphoneSlider->setSliderPosition(qRound(audio.inputGain() * 10.0));
} }
void AVForm::onOutDevChanged(QString deviceDescriptor) void AVForm::onOutDevChanged(QString deviceDescriptor)
@ -390,7 +400,7 @@ void AVForm::onOutDevChanged(QString deviceDescriptor)
Audio& audio = Audio::getInstance(); Audio& audio = Audio::getInstance();
audio.reinitOutput(deviceDescriptor); audio.reinitOutput(deviceDescriptor);
bodyUI->playbackSlider->setEnabled(audio.isOutputReady()); bodyUI->playbackSlider->setEnabled(audio.isOutputReady());
bodyUI->playbackSlider->setSliderPosition(audio.outputVolume() * 100.f); bodyUI->playbackSlider->setSliderPosition(qRound(audio.outputVolume() * 100.0));
} }
void AVForm::onFilterAudioToggled(bool filterAudio) void AVForm::onFilterAudioToggled(bool filterAudio)
@ -414,10 +424,10 @@ void AVForm::onPlaybackValueChanged(int value)
void AVForm::onMicrophoneValueChanged(int value) void AVForm::onMicrophoneValueChanged(int value)
{ {
Settings::getInstance().setInVolume(value); const qreal dB = value / 10.0;
const qreal percentage = value / 100.0; Settings::getInstance().setAudioInGain(dB);
Audio::getInstance().setInputVolume(percentage); Audio::getInstance().setInputGain(dB);
} }
void AVForm::createVideoSurface() void AVForm::createVideoSurface()

View File

@ -46,12 +46,6 @@
</item> </item>
<item row="3" column="1" colspan="2"> <item row="3" column="1" colspan="2">
<widget class="QSlider" name="microphoneSlider"> <widget class="QSlider" name="microphoneSlider">
<property name="toolTip">
<string>Use slider to set volume of your microphone.</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>