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