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

refactor(avform): simplify and standardize sliders, use RMS for volume

Also improve usefulness of volume bar by including gain, clipping, and activation threshold. Remove magic numbers. Clear volumue display when mic is disabled.

Fix #4893
This commit is contained in:
Anthony Bilinski 2018-04-10 22:39:50 -07:00
parent f6622e4092
commit 1583991fb4
No known key found for this signature in database
GPG Key ID: 2AA8E0DA1B31FB3C
8 changed files with 116 additions and 100 deletions

View File

@ -62,6 +62,8 @@ public:
virtual qreal outputVolume() const = 0;
virtual void setOutputVolume(qreal volume) = 0;
virtual qreal maxOutputVolume() const = 0;
virtual qreal minOutputVolume() const = 0;
virtual qreal minInputGain() const = 0;
virtual void setMinInputGain(qreal dB) = 0;
@ -73,10 +75,7 @@ public:
virtual void setInputGain(qreal dB) = 0;
virtual qreal minInputThreshold() const = 0;
virtual void setMinInputThreshold(qreal dB) = 0;
virtual qreal maxInputThreshold() const = 0;
virtual void setMaxInputThreshold(qreal dB) = 0;
virtual qreal getInputThreshold() const = 0;
virtual void setInputThreshold(qreal percent) = 0;
@ -109,8 +108,9 @@ protected:
// Public default audio settings
static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000;
static constexpr uint32_t AUDIO_FRAME_DURATION = 20;
static constexpr uint32_t AUDIO_FRAME_SAMPLE_COUNT =
static constexpr uint32_t AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL =
AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE / 1000;
uint32_t AUDIO_FRAME_SAMPLE_COUNT_TOTAL = 0;
signals:
void frameAvailable(const int16_t* pcm, size_t sample_count, uint8_t channels,

View File

@ -32,6 +32,18 @@
#include <cassert>
namespace {
void applyGain(int16_t* buffer, uint32_t bufferSize, qreal gainFactor)
{
for (quint32 i = 0; i < bufferSize; ++i) {
// gain amplification with clipping to 16-bit boundaries
buffer[i] = qBound<int16_t>(std::numeric_limits<int16_t>::min(),
qRound(buffer[i] * gainFactor),
std::numeric_limits<int16_t>::max());
}
}
}
/**
* @class OpenAL
* @brief Provides the OpenAL audio backend
@ -181,7 +193,7 @@ void OpenAL::setMaxInputGain(qreal dB)
/**
* @brief The minimum threshold value for an input device.
*
* @return minimum threshold percentage
* @return minimum normalized threshold
*/
qreal OpenAL::minInputThreshold() const
{
@ -190,20 +202,9 @@ qreal OpenAL::minInputThreshold() const
}
/**
* @brief Set the minimum allowed threshold percentage
* @brief The maximum normalized threshold value for an input device.
*
* @note Default is 0%; usually you don't need to alter this value;
*/
void OpenAL::setMinInputThreshold(qreal percent)
{
QMutexLocker locker(&audioLock);
minInThreshold = percent;
}
/**
* @brief The maximum threshold value for an input device.
*
* @return maximum threshold percentage
* @return maximum normalized threshold
*/
qreal OpenAL::maxInputThreshold() const
{
@ -211,17 +212,6 @@ qreal OpenAL::maxInputThreshold() const
return maxInThreshold;
}
/**
* @brief Set the maximum allowed threshold percentage
*
* @note Default is 40%; usually you don't need to alter this value.
*/
void OpenAL::setMaxInputThreshold(qreal percent)
{
QMutexLocker locker(&audioLock);
maxInThreshold = percent;
}
void OpenAL::reinitInput(const QString& inDevDesc)
{
QMutexLocker locker(&audioLock);
@ -311,15 +301,15 @@ bool OpenAL::initInput(const QString& deviceName, uint32_t channels)
// TODO: Try to actually detect if our audio source is stereo
this->channels = channels;
int stereoFlag = channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
const uint32_t sampleRate = AUDIO_SAMPLE_RATE;
const uint16_t frameDuration = AUDIO_FRAME_DURATION;
const int bytesPerSample = 2;
const int safetyFactor = 2;
const ALCsizei bufSize = AUDIO_FRAME_SAMPLE_COUNT * channels * bytesPerSample * safetyFactor;
const int safetyFactor = 2; // internal OpenAL ring buffer. must be larger than our inputBuffer to avoid the ring
// from overwriting itself between captures.
AUDIO_FRAME_SAMPLE_COUNT_TOTAL = AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL * channels;
const ALCsizei ringBufSize = AUDIO_FRAME_SAMPLE_COUNT_TOTAL * bytesPerSample * safetyFactor;
const QByteArray qDevName = deviceName.toUtf8();
const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData();
alInDev = alcCaptureOpenDevice(tmpDevName, sampleRate, stereoFlag, bufSize);
alInDev = alcCaptureOpenDevice(tmpDevName, AUDIO_SAMPLE_RATE, stereoFlag, ringBufSize);
// Restart the capture if necessary
if (!alInDev) {
@ -327,7 +317,7 @@ bool OpenAL::initInput(const QString& deviceName, uint32_t channels)
return false;
}
inputBuffer = new int16_t[AUDIO_FRAME_SAMPLE_COUNT * channels];
inputBuffer = new int16_t[AUDIO_FRAME_SAMPLE_COUNT_TOTAL];
setInputGain(Settings::getInstance().getAudioInGainDecibel());
setInputThreshold(Settings::getInstance().getAudioThreshold());
@ -542,17 +532,22 @@ void OpenAL::playMono16SoundCleanup()
*
* @param[in] buf the current audio buffer
*
* @return volume in percent of max volume
* @return normalized volume between 0-1
*/
float OpenAL::getVolume()
{
quint32 samples = AUDIO_FRAME_SAMPLE_COUNT * channels;
float sum = 0.0;
const quint32 samples = AUDIO_FRAME_SAMPLE_COUNT_TOTAL;
const float rootTwo = 1.414213562; // sqrt(2), but sqrt is not constexpr
// calculate volume as the root mean squared of amplitudes in the sample
float sumOfSquares = 0;
for (quint32 i = 0; i < samples; i++) {
float sample = static_cast<float>(inputBuffer[i]) / std::numeric_limits<int16_t>::max();
sum += qAbs(sample);
sumOfSquares += std::pow(sample , 2);
}
return sum / samples;
const float rms = std::sqrt(sumOfSquares/samples);
// our calculated normalized volume could possibly be above 1 because our RMS assumes a sinusoidal wave
const float normalizedVolume = std::min(rms * rootTwo, 1.0f);
return normalizedVolume;
}
/**
@ -570,16 +565,20 @@ void OpenAL::doInput()
{
ALint curSamples = 0;
alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(curSamples), &curSamples);
if (curSamples < static_cast<ALint>(AUDIO_FRAME_SAMPLE_COUNT)) {
if (curSamples < static_cast<ALint>(AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL)) {
return;
}
captureSamples(alInDev, inputBuffer, AUDIO_FRAME_SAMPLE_COUNT);
captureSamples(alInDev, inputBuffer, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL);
applyGain(inputBuffer, AUDIO_FRAME_SAMPLE_COUNT_TOTAL, gainFactor);
float volume = getVolume();
if (volume >= inputThreshold) {
isActive = true;
emit startActive(voiceHold);
} else if (!isActive) {
volume = 0;
}
emit Audio::volumeAvailable(volume);
@ -587,14 +586,7 @@ void OpenAL::doInput()
return;
}
for (quint32 i = 0; i < AUDIO_FRAME_SAMPLE_COUNT * channels; ++i) {
// gain amplification with clipping to 16-bit boundaries
inputBuffer[i] = qBound<int16_t>(std::numeric_limits<int16_t>::min(),
qRound(inputBuffer[i] * OpenAL::inputGainFactor()),
std::numeric_limits<int16_t>::max());
}
emit Audio::frameAvailable(inputBuffer, AUDIO_FRAME_SAMPLE_COUNT, channels, AUDIO_SAMPLE_RATE);
emit Audio::frameAvailable(inputBuffer, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL, channels, AUDIO_SAMPLE_RATE);
}
void OpenAL::doOutput()
@ -757,8 +749,7 @@ void OpenAL::setInputGain(qreal dB)
gainFactor = qPow(10.0, (gain / 20.0));
}
void OpenAL::setInputThreshold(qreal percent)
void OpenAL::setInputThreshold(qreal normalizedThreshold)
{
inputThreshold = percent;
inputThreshold = normalizedThreshold;
}

View File

@ -48,6 +48,8 @@ public:
OpenAL();
virtual ~OpenAL();
qreal maxOutputVolume() const { return 1; }
qreal minOutputVolume() const { return 0; }
qreal outputVolume() const;
void setOutputVolume(qreal volume);
@ -61,13 +63,10 @@ public:
void setInputGain(qreal dB);
qreal minInputThreshold() const;
void setMinInputThreshold(qreal percent);
qreal maxInputThreshold() const;
void setMaxInputThreshold(qreal percent);
qreal getInputThreshold() const;
void setInputThreshold(qreal percent);
void setInputThreshold(qreal normalizedThreshold);
void reinitInput(const QString& inDevDesc);
bool reinitOutput(const QString& outDevDesc);
@ -137,13 +136,13 @@ protected:
qreal gainFactor = 1;
qreal minInGain = -30;
qreal maxInGain = 30;
qreal inputThreshold;
qreal inputThreshold = 0;
qreal voiceHold = 250;
bool isActive = false;
QTimer voiceTimer;
qreal minInThreshold = 0.0;
qreal maxInThreshold = 0.4;
int16_t* inputBuffer;
const qreal minInThreshold = 0.0;
const qreal maxInThreshold = 0.4;
int16_t* inputBuffer = nullptr;
};
#endif // OPENAL_H

View File

@ -321,13 +321,13 @@ void OpenAL2::doOutput()
alGetSourcedvSOFT(alProxySource, AL_SEC_OFFSET_LATENCY_SOFT, latency);
checkAlError();
ALshort outBuf[AUDIO_FRAME_SAMPLE_COUNT] = {0};
ALshort outBuf[AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL] = {0};
alcMakeContextCurrent(alProxyContext);
alcRenderSamplesSOFT(alProxyDev, outBuf, AUDIO_FRAME_SAMPLE_COUNT);
alcRenderSamplesSOFT(alProxyDev, outBuf, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL);
checkAlcError(alProxyDev);
alcMakeContextCurrent(alOutContext);
alBufferData(bufids[0], AL_FORMAT_MONO16, outBuf, AUDIO_FRAME_SAMPLE_COUNT * 2, AUDIO_SAMPLE_RATE);
alBufferData(bufids[0], AL_FORMAT_MONO16, outBuf, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL * 2, AUDIO_SAMPLE_RATE);
alSourceQueueBuffers(alProxySource, 1, bufids);
// initialize echo canceler if supported
@ -340,7 +340,7 @@ void OpenAL2::doOutput()
}
// do echo cancel
pass_audio_output(filterer, outBuf, AUDIO_FRAME_SAMPLE_COUNT);
pass_audio_output(filterer, outBuf, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL);
ALint state;
alGetSourcei(alProxySource, AL_SOURCE_STATE, &state);

View File

@ -26,6 +26,8 @@ public:
virtual void setAudioThreshold(qreal percent) = 0;
virtual int getOutVolume() const = 0;
virtual int getOutVolumeMin() const = 0;
virtual int getOutVolumeMax() const = 0;
virtual void setOutVolume(int volume) = 0;
virtual int getAudioBitrate() const = 0;

View File

@ -353,6 +353,8 @@ public:
void setAudioThreshold(qreal percent) override;
int getOutVolume() const override;
int getOutVolumeMin() const override { return 0; }
int getOutVolumeMax() const override { return 100; }
void setOutVolume(int volume) override;
int getAudioBitrate() const override;

View File

@ -70,32 +70,33 @@ AVForm::AVForm(Audio* audio, CoreAV* coreAV, CameraSource& camera,
connect(rescanButton, &QPushButton::clicked, this, &AVForm::rescanDevices);
playbackSlider->setTracking(false);
playbackSlider->setValue(audioSettings->getOutVolume());
playbackSlider->setMaximum(totalSliderSteps);
playbackSlider->setValue(getStepsFromValue(audioSettings->getOutVolume(),
audioSettings->getOutVolumeMin(), audioSettings->getOutVolumeMax()));
playbackSlider->installEventFilter(this);
microphoneSlider->setToolTip(tr("Use slider to set the gain of your input device ranging"
" from %1dB to %2dB.")
.arg(audio->minInputGain())
.arg(audio->maxInputGain()));
microphoneSlider->setMinimum(qRound(audio->minInputGain()) * 10);
microphoneSlider->setMaximum(qRound(audio->maxInputGain()) * 10);
microphoneSlider->setMaximum(totalSliderSteps);
microphoneSlider->setTickPosition(QSlider::TicksBothSides);
microphoneSlider->setTickInterval(
(qAbs(microphoneSlider->minimum()) + microphoneSlider->maximum()) / 4);
static const int numTicks = 4;
microphoneSlider->setTickInterval(totalSliderSteps / numTicks);
microphoneSlider->setTracking(false);
microphoneSlider->installEventFilter(this);
microphoneSlider->setValue(getStepsFromValue(audio->inputGain(), audio->minInputGain(), audio->maxInputGain()));
audioThresholdSlider->setToolTip(tr("Use slider to set the activation volume for your"
" input device."));
audioThresholdSlider->setMinimum(audio->minInputThreshold() * 1000);
audioThresholdSlider->setMaximum(audio->maxInputThreshold() * 1000);
audioThresholdSlider->setValue(audioSettings->getAudioThreshold() * 1000);
audioThresholdSlider->setMaximum(totalSliderSteps);
audioThresholdSlider->setValue(getStepsFromValue(audioSettings->getAudioThreshold(),
audio->minInputThreshold(), audio->maxInputThreshold()));
audioThresholdSlider->setTracking(false);
audioThresholdSlider->installEventFilter(this);
connect(audio, &Audio::volumeAvailable, this, &AVForm::setVolume);
volumeDisplay->setMinimum(audio->minInputThreshold() * 1000);
volumeDisplay->setMaximum(audio->maxInputThreshold() * 1000);
volumeDisplay->setMaximum(totalSliderSteps);
fillAudioQualityComboBox();
@ -166,7 +167,7 @@ void AVForm::rescanDevices()
void AVForm::setVolume(float value)
{
volumeDisplay->setValue(value * 1000);
volumeDisplay->setValue(getStepsFromValue(value, audio->minOutputVolume(), audio->maxOutputVolume()));
}
void AVForm::on_cbEnableBackend2_stateChanged()
@ -521,41 +522,51 @@ void AVForm::getAudioOutDevices()
void AVForm::on_inDevCombobox_currentIndexChanged(int deviceIndex)
{
audioSettings->setAudioInDevEnabled(deviceIndex != 0);
const bool inputEnabled = deviceIndex > 0;
audioSettings->setAudioInDevEnabled(inputEnabled);
QString deviceName;
if (deviceIndex > 0)
if (inputEnabled) {
deviceName = inDevCombobox->itemText(deviceIndex);
}
audioSettings->setInDev(deviceName);
audio->reinitInput(deviceName);
microphoneSlider->setEnabled(deviceIndex > 0);
microphoneSlider->setSliderPosition(qRound(audio->inputGain() * 10.0));
microphoneSlider->setEnabled(inputEnabled);
if (!inputEnabled) {
volumeDisplay->setValue(volumeDisplay->minimum());
}
}
void AVForm::on_outDevCombobox_currentIndexChanged(int deviceIndex)
{
audioSettings->setAudioOutDevEnabled(deviceIndex != 0);
const bool outputEnabled = deviceIndex > 0;
audioSettings->setAudioOutDevEnabled(outputEnabled);
QString deviceName;
if (deviceIndex > 0)
if (outputEnabled) {
deviceName = outDevCombobox->itemText(deviceIndex);
}
audioSettings->setOutDev(deviceName);
audio->reinitOutput(deviceName);
playbackSlider->setEnabled(deviceIndex > 0);
playbackSlider->setSliderPosition(qRound(audio->outputVolume() * 100.0));
playbackSlider->setEnabled(outputEnabled);
playbackSlider->setSliderPosition(getStepsFromValue(audio->outputVolume(),
audio->minOutputVolume(), audio->maxOutputVolume()));
}
void AVForm::on_playbackSlider_valueChanged(int value)
void AVForm::on_playbackSlider_valueChanged(int sliderSteps)
{
audioSettings->setOutVolume(value);
const int settingsVolume = getValueFromSteps(sliderSteps,
audioSettings->getOutVolumeMin(),audioSettings->getOutVolumeMax());
audioSettings->setOutVolume(settingsVolume);
if (audio->isOutputReady()) {
const qreal percentage = value / 100.0;
audio->setOutputVolume(percentage);
const qreal volume = getValueFromSteps(sliderSteps, audio->minOutputVolume(), audio->maxOutputVolume());
audio->setOutputVolume(volume);
if (cbEnableTestSound->isChecked())
audio->playMono16Sound(Audio::getSound(Audio::Sound::Test));
@ -570,22 +581,19 @@ void AVForm::on_cbEnableTestSound_stateChanged()
audio->playMono16Sound(Audio::getSound(Audio::Sound::Test));
}
void AVForm::on_microphoneSlider_valueChanged(int value)
void AVForm::on_microphoneSlider_valueChanged(int sliderSteps)
{
const qreal dB = value / 10.0;
const qreal dB = getValueFromSteps(sliderSteps, audio->minInputGain(), audio->maxInputGain());
audioSettings->setAudioInGainDecibel(dB);
audio->setInputGain(dB);
}
void AVForm::on_audioThresholdSlider_valueChanged(int value)
void AVForm::on_audioThresholdSlider_valueChanged(int sliderSteps)
{
const qreal percent = value / 1000.0;
audioSettings->setAudioThreshold(percent);
Audio::getInstance().setInputThreshold(percent);
const qreal normThreshold = getValueFromSteps(sliderSteps, audio->minInputThreshold(), audio->maxInputThreshold());
audioSettings->setAudioThreshold(normThreshold);
Audio::getInstance().setInputThreshold(normThreshold);
}
void AVForm::createVideoSurface()
{
if (camVideoSurface)
@ -616,3 +624,14 @@ void AVForm::retranslateUi()
{
Ui::AVForm::retranslateUi(this);
}
int AVForm::getStepsFromValue(qreal val, qreal valMin, qreal valMax)
{
const float norm = (val - valMin) / (valMax - valMin);
return norm * totalSliderSteps;
}
qreal AVForm::getValueFromSteps(int steps, qreal valMin, qreal valMax)
{
return (static_cast<float>(steps) / totalSliderSteps) * (valMax - valMin) + valMin;
}

View File

@ -68,10 +68,10 @@ private slots:
// audio
void on_inDevCombobox_currentIndexChanged(int deviceIndex);
void on_outDevCombobox_currentIndexChanged(int deviceIndex);
void on_playbackSlider_valueChanged(int value);
void on_playbackSlider_valueChanged(int sliderSteps);
void on_cbEnableTestSound_stateChanged();
void on_microphoneSlider_valueChanged(int value);
void on_audioThresholdSlider_valueChanged(int value);
void on_microphoneSlider_valueChanged(int sliderSteps);
void on_audioThresholdSlider_valueChanged(int sliderSteps);
void on_audioQualityComboBox_currentIndexChanged(int index);
// camera
@ -90,6 +90,8 @@ private:
void hideEvent(QHideEvent* event) final override;
void showEvent(QShowEvent* event) final override;
void open(const QString& devName, const VideoMode& mode);
int getStepsFromValue(qreal val, qreal valMin, qreal valMax);
qreal getValueFromSteps(int steps, qreal valMin, qreal valMax);
private:
Audio* audio;
@ -103,6 +105,7 @@ private:
QVector<QPair<QString, QString>> videoDeviceList;
QVector<VideoMode> videoModes;
uint alSource;
const uint totalSliderSteps = 100; // arbitrary number of steps to give slider a good "feel"
};
#endif