Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "webrtc.h"
19 : #include "logger.h"
20 :
21 : #if __has_include(<common_audio/vad/include/webrtc_vad.h>)
22 : #include <common_audio/vad/include/webrtc_vad.h>
23 : #define HAVE_WEBRTC_VAD 1
24 : #else
25 : // Only installed by the Jami contrib build (see contrib/src/webrtc-audio-processing)
26 : #define HAVE_WEBRTC_VAD 0
27 : #endif
28 :
29 : #include <algorithm>
30 : #include <cmath>
31 :
32 : namespace jami {
33 :
34 : inline size_t
35 0 : webrtcFrameSize(AudioFormat format)
36 : {
37 0 : return (size_t) (webrtc::AudioProcessing::kChunkSizeMs * format.sample_rate / 1000);
38 : }
39 :
40 : constexpr int webrtcNoError = webrtc::AudioProcessing::kNoError;
41 :
42 : void
43 0 : WebRTCAudioProcessor::VadDeleter::operator()(VadInst* vad) const
44 : {
45 : #if HAVE_WEBRTC_VAD
46 0 : WebRtcVad_Free(vad);
47 : #else
48 : (void) vad;
49 : #endif
50 0 : }
51 :
52 0 : WebRTCAudioProcessor::WebRTCAudioProcessor(AudioFormat format, unsigned /* frameSize */)
53 0 : : AudioProcessor(format.withSampleFormat(AV_SAMPLE_FMT_FLTP), webrtcFrameSize(format))
54 : {
55 0 : JAMI_LOG("[webrtc-ap] WebRTCAudioProcessor, frame size = {:d} (={:d} ms), channels = {:d}",
56 : frameSize_,
57 : frameDurationMs_,
58 : format_.nb_channels);
59 :
60 0 : apm = webrtc::AudioProcessingBuilder().Create();
61 :
62 0 : webrtc::StreamConfig streamConfig((int) format_.sample_rate, (int) format_.nb_channels);
63 : webrtc::ProcessingConfig pconfig = {
64 : streamConfig, /* input stream */
65 : streamConfig, /* output stream */
66 : streamConfig, /* reverse input stream */
67 : streamConfig, /* reverse output stream */
68 0 : };
69 :
70 0 : if (apm->Initialize(pconfig) != webrtcNoError) {
71 0 : JAMI_ERROR("[webrtc-ap] Error initialising audio processing module");
72 : }
73 :
74 : // The APM dropped its voice detector in 1.0; use the standalone WebRTC VAD
75 : // (what the old VoiceDetection component wrapped) on the first channel.
76 : #if HAVE_WEBRTC_VAD
77 0 : if (WebRtcVad_ValidRateAndFrameLength((int) format_.sample_rate, frameSize_) == 0) {
78 0 : vad_.reset(WebRtcVad_Create());
79 0 : if (WebRtcVad_Init(vad_.get()) != 0) {
80 0 : JAMI_ERROR("[webrtc-ap] Error initialising voice activity detection");
81 0 : vad_.reset();
82 : }
83 0 : vadBuffer_.resize(frameSize_);
84 : } else {
85 0 : JAMI_WARNING("[webrtc-ap] {:d} Hz unsupported by the WebRTC VAD, voice activity disabled", format_.sample_rate);
86 : }
87 : #else
88 : JAMI_WARNING("[webrtc-ap] Built without the WebRTC VAD, voice activity disabled");
89 : #endif
90 0 : }
91 :
92 : void
93 0 : WebRTCAudioProcessor::applyConfig()
94 : {
95 0 : apm->ApplyConfig(config_);
96 0 : }
97 :
98 : void
99 0 : WebRTCAudioProcessor::enableNoiseSuppression(bool enabled)
100 : {
101 0 : JAMI_LOG("[webrtc-ap] enableNoiseSuppression {}", enabled);
102 0 : config_.noise_suppression.enabled = enabled;
103 0 : config_.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
104 0 : config_.high_pass_filter.enabled = enabled;
105 0 : applyConfig();
106 0 : }
107 :
108 : void
109 0 : WebRTCAudioProcessor::enableAutomaticGainControl(bool enabled)
110 : {
111 0 : JAMI_LOG("[webrtc-ap] enableAutomaticGainControl {}", enabled);
112 0 : config_.gain_controller1.enabled = enabled;
113 0 : config_.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::kAdaptiveAnalog;
114 0 : applyConfig();
115 0 : }
116 :
117 : void
118 0 : WebRTCAudioProcessor::enableEchoCancel(bool enabled)
119 : {
120 0 : JAMI_LOG("[webrtc-ap] enableEchoCancel {}", enabled);
121 0 : config_.echo_canceller.enabled = enabled;
122 0 : config_.echo_canceller.mobile_mode = false;
123 0 : applyConfig();
124 0 : }
125 :
126 : void
127 0 : WebRTCAudioProcessor::enableVoiceActivityDetection(bool enabled)
128 : {
129 0 : JAMI_LOG("[webrtc-ap] enableVoiceActivityDetection {}", enabled);
130 0 : if (!vad_) {
131 0 : if (enabled)
132 0 : JAMI_ERROR("[webrtc-ap] Voice activity detection unavailable");
133 0 : return;
134 : }
135 : #if HAVE_WEBRTC_VAD
136 : // Mode 3 is what VoiceDetection::kVeryLowLikelihood mapped to.
137 0 : if (WebRtcVad_set_mode(vad_.get(), enabled ? 3 : 0) != 0) {
138 0 : JAMI_ERROR("[webrtc-ap] Error setting voice detection mode");
139 : }
140 : #endif
141 0 : detectVoice_ = enabled;
142 : }
143 :
144 : std::shared_ptr<AudioFrame>
145 0 : WebRTCAudioProcessor::getProcessed()
146 : {
147 0 : if (tidyQueues()) {
148 0 : return {};
149 : }
150 :
151 0 : auto playback = playbackQueue_.dequeue();
152 0 : auto record = recordQueue_.dequeue();
153 0 : if (!playback || !record) {
154 0 : return {};
155 : }
156 0 : webrtc::StreamConfig sc((int) format_.sample_rate, (int) format_.nb_channels);
157 :
158 : // process reverse in place
159 0 : float** playData = (float**) playback->pointer()->extended_data;
160 0 : if (apm->ProcessReverseStream(playData, sc, sc, playData) != webrtcNoError) {
161 0 : JAMI_ERROR("[webrtc-ap] ProcessReverseStream failed");
162 : }
163 :
164 : // AEC3 estimates the delay itself; 0 keeps the API contract satisfied.
165 0 : if (apm->set_stream_delay_ms(0) != webrtcNoError) {
166 0 : JAMI_ERROR("[webrtc-ap] set_stream_delay_ms failed");
167 : }
168 0 : if (config_.gain_controller1.enabled) {
169 0 : apm->set_stream_analog_level(analogLevel_);
170 : }
171 :
172 : // process deinterleaved float recorded data in place
173 0 : float** recData = (float**) record->pointer()->extended_data;
174 0 : if (apm->ProcessStream(recData, sc, sc, recData) != webrtcNoError) {
175 0 : JAMI_ERROR("[webrtc-ap] ProcessStream failed");
176 : }
177 :
178 0 : if (config_.gain_controller1.enabled) {
179 0 : analogLevel_ = apm->recommended_stream_analog_level();
180 : }
181 :
182 0 : bool hasVoice = false;
183 : #if HAVE_WEBRTC_VAD
184 0 : if (detectVoice_ && vad_) {
185 0 : std::transform(recData[0], recData[0] + frameSize_, vadBuffer_.begin(), [](float s) {
186 0 : return (int16_t) std::lround(std::clamp(s, -1.f, 1.f) * 32767.f);
187 : });
188 0 : hasVoice = WebRtcVad_Process(vad_.get(), (int) format_.sample_rate, vadBuffer_.data(), vadBuffer_.size()) == 1;
189 : }
190 : #endif
191 0 : record->has_voice = detectVoice_ && getStabilizedVoiceActivity(hasVoice);
192 0 : return record;
193 0 : }
194 :
195 : } // namespace jami
|