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:
parent
9de833ad39
commit
f72baa613f
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user