From 1583991fb494ee3dfcdf435b78260ab665c4e66b Mon Sep 17 00:00:00 2001 From: Anthony Bilinski Date: Tue, 10 Apr 2018 22:39:50 -0700 Subject: [PATCH] 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 --- src/audio/audio.h | 8 +-- src/audio/backend/openal.cpp | 89 +++++++++++++---------------- src/audio/backend/openal.h | 15 +++-- src/audio/backend/openal2.cpp | 8 +-- src/audio/iaudiosettings.h | 2 + src/persistence/settings.h | 2 + src/widget/form/settings/avform.cpp | 83 ++++++++++++++++----------- src/widget/form/settings/avform.h | 9 ++- 8 files changed, 116 insertions(+), 100 deletions(-) diff --git a/src/audio/audio.h b/src/audio/audio.h index 8aa5c3e2b..0b601b6eb 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -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, diff --git a/src/audio/backend/openal.cpp b/src/audio/backend/openal.cpp index 4d84b51a3..84895d6ed 100644 --- a/src/audio/backend/openal.cpp +++ b/src/audio/backend/openal.cpp @@ -32,6 +32,18 @@ #include +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(std::numeric_limits::min(), + qRound(buffer[i] * gainFactor), + std::numeric_limits::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(inputBuffer[i]) / std::numeric_limits::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(AUDIO_FRAME_SAMPLE_COUNT)) { + if (curSamples < static_cast(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(std::numeric_limits::min(), - qRound(inputBuffer[i] * OpenAL::inputGainFactor()), - std::numeric_limits::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; } - diff --git a/src/audio/backend/openal.h b/src/audio/backend/openal.h index a6a787b53..be6474dd4 100644 --- a/src/audio/backend/openal.h +++ b/src/audio/backend/openal.h @@ -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 diff --git a/src/audio/backend/openal2.cpp b/src/audio/backend/openal2.cpp index 1042be1a1..d4baea8cf 100644 --- a/src/audio/backend/openal2.cpp +++ b/src/audio/backend/openal2.cpp @@ -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); diff --git a/src/audio/iaudiosettings.h b/src/audio/iaudiosettings.h index ef944b4fd..8fad8530c 100644 --- a/src/audio/iaudiosettings.h +++ b/src/audio/iaudiosettings.h @@ -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; diff --git a/src/persistence/settings.h b/src/persistence/settings.h index 14cdc6143..0c46676c4 100644 --- a/src/persistence/settings.h +++ b/src/persistence/settings.h @@ -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; diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index 38574067d..5f8fede34 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -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(steps) / totalSliderSteps) * (valMax - valMin) + valMin; +} diff --git a/src/widget/form/settings/avform.h b/src/widget/form/settings/avform.h index 5bf9b3fdd..1c9cdb19f 100644 --- a/src/widget/form/settings/avform.h +++ b/src/widget/form/settings/avform.h @@ -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> videoDeviceList; QVector videoModes; uint alSource; + const uint totalSliderSteps = 100; // arbitrary number of steps to give slider a good "feel" }; #endif