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 <QPointer>
#include <QThread>
#include <QtMath>
#include <QWaitCondition>
#include <cassert>
@ -35,6 +36,49 @@
#include "audiofilterer.h"
#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.
*/
@ -45,15 +89,15 @@ Audio& Audio::getInstance()
}
Audio::Audio()
: audioThread(new QThread)
, alInDev(nullptr)
, inGain{1.f}
, inSubscriptions(0)
, alOutDev(nullptr)
, alOutContext(nullptr)
, alMainSource(0)
, alMainBuffer(0)
, outputInitialized(false)
: d{new Private}
, audioThread{new QThread}
, alInDev{nullptr}
, inSubscriptions{0}
, alOutDev{nullptr}
, alOutContext{nullptr}
, alMainSource{0}
, alMainBuffer{0}
, outputInitialized{false}
{
// initialize OpenAL error stack
alGetError();
@ -87,6 +131,7 @@ Audio::~Audio()
#ifdef QTOX_FILTER_AUDIO
filterer.closeFilter();
#endif
delete d;
}
void Audio::checkAlError() noexcept
@ -106,13 +151,14 @@ void Audio::checkAlcError(ALCdevice *device) noexcept
/**
Returns the current output volume (between 0 and 1)
*/
ALfloat Audio::outputVolume()
qreal Audio::outputVolume() const
{
QMutexLocker locker(&audioLock);
ALfloat volume = 0.0;
if (alOutDev) {
if (alOutDev)
{
alGetListenerf(AL_GAIN, &volume);
checkAlError();
}
@ -125,28 +171,78 @@ Set the master output volume.
@param[in] volume the master volume (between 0 and 1)
*/
void Audio::setOutputVolume(ALfloat volume)
void Audio::setOutputVolume(qreal volume)
{
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();
}
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);
return inGain;
return d->minInputGain;
}
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);
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)
@ -172,7 +268,8 @@ void Audio::subscribeInput()
{
QMutexLocker locker(&audioLock);
if (!autoInitInput()) {
if (!autoInitInput())
{
qWarning("Failed to subscribe to audio input device.");
return;
}
@ -235,7 +332,8 @@ bool Audio::initInput(QString inDevDescr)
const uint16_t frameDuration = AUDIO_FRAME_DURATION;
const uint32_t chnls = AUDIO_CHANNELS;
const ALCsizei bufSize = (frameDuration * sampleRate * 4) / 1000 * chnls;
if (inDevDescr.isEmpty()) {
if (inDevDescr.isEmpty())
{
const ALchar *pDeviceList = Audio::inDeviceNames();
if (pDeviceList)
inDevDescr = QString::fromUtf8(pDeviceList, strlen(pDeviceList));
@ -246,11 +344,14 @@ bool Audio::initInput(QString inDevDescr)
sampleRate, stereoFlag, bufSize);
// Restart the capture if necessary
if (!alInDev) {
if (!alInDev)
{
qWarning() << "Failed to initialize audio input device:" << inDevDescr;
return false;
}
d->setInputGain(Settings::getInstance().getAudioInGain());
qDebug() << "Opened audio input" << inDevDescr;
alcCaptureStart(alInDev);
@ -273,7 +374,8 @@ bool Audio::initOutput(QString outDevDescr)
assert(!alOutDev);
if (outDevDescr.isEmpty()) {
if (outDevDescr.isEmpty())
{
// default to the first available audio device.
const ALchar *pDeviceList = Audio::outDeviceNames();
if (pDeviceList)
@ -293,7 +395,8 @@ bool Audio::initOutput(QString outDevDescr)
alOutContext = alcCreateContext(alOutDev, nullptr);
checkAlcError(alOutDev);
if (!alcMakeContextCurrent(alOutContext)) {
if (!alcMakeContextCurrent(alOutContext))
{
qWarning() << "Cannot create output audio context";
return false;
}
@ -306,7 +409,8 @@ bool Audio::initOutput(QString outDevDescr)
checkAlError();
Core* core = Core::getInstance();
if (core) {
if (core)
{
// reset each call's audio source
core->getAv()->invalidateCallSources();
}
@ -421,7 +525,8 @@ void Audio::cleanupOutput()
{
outputInitialized = false;
if (alOutDev) {
if (alOutDev)
{
alSourcei(alMainSource, AL_LOOPING, AL_FALSE);
alSourceStop(alMainSource);
alDeleteSources(1, &alMainSource);
@ -486,8 +591,15 @@ void Audio::doCapture()
}
#endif
for (size_t i = 0; i < AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS; ++i)
buf[i] *= inGain;
for (quint32 i = 0; i < AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS; ++i)
{
// 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);
}
@ -495,7 +607,7 @@ void Audio::doCapture()
/**
Returns true if the input device is open and suscribed to
*/
bool Audio::isInputReady()
bool Audio::isInputReady() const
{
QMutexLocker locker(&audioLock);
return alInDev && inSubscriptions;
@ -504,7 +616,7 @@ bool Audio::isInputReady()
/**
Returns true if the output device is open
*/
bool Audio::isOutputReady()
bool Audio::isOutputReady() const
{
QMutexLocker locker(&audioLock);
return alOutDev && outputInitialized;
@ -530,12 +642,14 @@ void Audio::subscribeOutput(ALuint& sid)
{
QMutexLocker locker(&audioLock);
if (!autoInitOutput()) {
if (!autoInitOutput())
{
qWarning("Failed to subscribe to audio output device.");
return;
}
if (!alcMakeContextCurrent(alOutContext)) {
if (!alcMakeContextCurrent(alOutContext))
{
qWarning("Failed to activate output context.");
return;
}
@ -554,8 +668,10 @@ void Audio::unsubscribeOutput(ALuint &sid)
outSources.removeAll(sid);
if (sid) {
if (alIsSource(sid)) {
if (sid)
{
if (alIsSource(sid))
{
alDeleteSources(1, &sid);
qDebug() << "Audio source" << sid << "deleted. Sources active:"
<< outSources.size();

View File

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

View File

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

View File

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

View File

@ -45,6 +45,8 @@ AVForm::AVForm() :
bodyUI = new Ui::AVSettings;
bodyUI->setupUi(this);
const Audio& audio = Audio::getInstance();
bodyUI->btnPlayTestSound->setToolTip(
tr("Play a test sound while changing the output volume."));
@ -65,6 +67,14 @@ AVForm::AVForm() :
bodyUI->playbackSlider->installEventFilter(this);
connect(bodyUI->playbackSlider, &QSlider::valueChanged,
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->installEventFilter(this);
connect(bodyUI->microphoneSlider, &QSlider::valueChanged,
@ -378,7 +388,7 @@ void AVForm::onInDevChanged(QString deviceDescriptor)
Audio& audio = Audio::getInstance();
audio.reinitInput(deviceDescriptor);
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)
@ -390,7 +400,7 @@ void AVForm::onOutDevChanged(QString deviceDescriptor)
Audio& audio = Audio::getInstance();
audio.reinitOutput(deviceDescriptor);
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)
@ -414,10 +424,10 @@ void AVForm::onPlaybackValueChanged(int value)
void AVForm::onMicrophoneValueChanged(int value)
{
Settings::getInstance().setInVolume(value);
const qreal dB = value / 10.0;
const qreal percentage = value / 100.0;
Audio::getInstance().setInputVolume(percentage);
Settings::getInstance().setAudioInGain(dB);
Audio::getInstance().setInputGain(dB);
}
void AVForm::createVideoSurface()

View File

@ -46,12 +46,6 @@
</item>
<item row="3" column="1" colspan="2">
<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">
<enum>Qt::Horizontal</enum>
</property>