"Fossies" - the Fresh Open Source Software Archive 
Member "jitsi-meet-7329/react/features/stream-effects/noise-suppression/NoiseSuppressionEffect.ts" (9 Jun 2023, 8053 Bytes) of package /linux/misc/jitsi-meet-7329.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
See also the last
Fossies "Diffs" side-by-side code changes report for "NoiseSuppressionEffect.ts":
jitsi-meet_8319_vs_jitsi-meet_8615.
1 import { INoiseSuppressionConfig } from '../../base/config/configType';
2 import { getBaseUrl } from '../../base/util/helpers';
3
4 import logger from './logger';
5
6 interface IKrispState {
7 filterNode?: AudioWorkletNode;
8 filterNodeReady: boolean;
9 sdk: any;
10 sdkInitialized: boolean;
11 }
12
13 const krispState: IKrispState = {
14 filterNode: undefined,
15 filterNodeReady: false,
16 sdk: undefined,
17 sdkInitialized: false
18 };
19
20 let audioContext: AudioContext;
21
22 /**
23 * Class Implementing the effect interface expected by a JitsiLocalTrack.
24 * Effect applies rnnoise denoising on a audio JitsiLocalTrack.
25 */
26 export class NoiseSuppressionEffect {
27
28 /**
29 * Source that will be attached to the track affected by the effect.
30 */
31 private _audioSource: MediaStreamAudioSourceNode;
32
33 /**
34 * Destination that will contain denoised audio from the audio worklet.
35 */
36 private _audioDestination: MediaStreamAudioDestinationNode;
37
38 /**
39 * `AudioWorkletProcessor` associated node.
40 */
41 private _noiseSuppressorNode?: AudioWorkletNode;
42
43 /**
44 * Audio track extracted from the original MediaStream to which the effect is applied.
45 */
46 private _originalMediaTrack: MediaStreamTrack;
47
48 /**
49 * Noise suppressed audio track extracted from the media destination node.
50 */
51 private _outputMediaTrack: MediaStreamTrack;
52
53 /**
54 * Configured options for noise suppression.
55 */
56 private _options?: INoiseSuppressionConfig;
57
58 /**
59 * Instantiates a noise suppressor audio effect which will use either rnnoise or krisp.
60 *
61 * @param {INoiseSuppressionConfig} options - Configured options.
62 */
63 constructor(options?: INoiseSuppressionConfig) {
64 this._options = options;
65
66 const useKrisp = options?.krisp?.enabled;
67
68 logger.info(`NoiseSuppressionEffect created with ${useKrisp ? 'Krisp' : 'RNNoise'}`);
69 }
70
71 /**
72 * Effect interface called by source JitsiLocalTrack.
73 * Applies effect that uses a {@code NoiseSuppressor} service initialized with {@code RnnoiseProcessor}
74 * for denoising.
75 *
76 * @param {MediaStream} audioStream - Audio stream which will be mixed with _mixAudio.
77 * @returns {MediaStream} - MediaStream containing both audio tracks mixed together.
78 */
79 startEffect(audioStream: MediaStream): MediaStream {
80 this._originalMediaTrack = audioStream.getAudioTracks()[0];
81
82 if (!audioContext) {
83 audioContext = new AudioContext();
84 }
85
86 this._audioSource = audioContext.createMediaStreamSource(audioStream);
87 this._audioDestination = audioContext.createMediaStreamDestination();
88 this._outputMediaTrack = this._audioDestination.stream.getAudioTracks()[0];
89
90 let init;
91
92 if (this._options?.krisp?.enabled) {
93 init = _initializeKrisp(this._options).then(filterNode => {
94 this._noiseSuppressorNode = filterNode;
95
96 if (krispState.filterNodeReady) {
97 // @ts-ignore
98 krispState.filterNode?.enable();
99 }
100 });
101 } else {
102 init = _initializeKRnnoise().then(filterNode => {
103 this._noiseSuppressorNode = filterNode;
104 });
105 }
106
107 // Connect the audio processing graph MediaStream -> AudioWorkletNode -> MediaStreamAudioDestinationNode
108
109 init.then(() => {
110 if (this._noiseSuppressorNode) {
111 this._audioSource.connect(this._noiseSuppressorNode);
112 this._noiseSuppressorNode.connect(this._audioDestination);
113 }
114 });
115
116 // Sync the effect track muted state with the original track state.
117 this._outputMediaTrack.enabled = this._originalMediaTrack.enabled;
118
119 // We enable the audio on the original track because mute/unmute action will only affect the audio destination
120 // output track from this point on.
121 this._originalMediaTrack.enabled = true;
122
123 return this._audioDestination.stream;
124 }
125
126 /**
127 * Checks if the JitsiLocalTrack supports this effect.
128 *
129 * @param {JitsiLocalTrack} sourceLocalTrack - Track to which the effect will be applied.
130 * @returns {boolean} - Returns true if this effect can run on the specified track, false otherwise.
131 */
132 isEnabled(sourceLocalTrack: any): boolean {
133 // JitsiLocalTracks needs to be an audio track.
134 return sourceLocalTrack.isAudioTrack();
135 }
136
137 /**
138 * Clean up resources acquired by noise suppressor and rnnoise processor.
139 *
140 * @returns {void}
141 */
142 stopEffect(): void {
143 // Sync original track muted state with effect state before removing the effect.
144 this._originalMediaTrack.enabled = this._outputMediaTrack.enabled;
145
146 if (this._options?.krisp?.enabled) {
147 // When using Krisp we'll just disable the filter which we'll keep reusing.
148
149 // @ts-ignore
150 this._noiseSuppressorNode?.disable();
151 } else {
152 // Technically after this process the Audio Worklet along with it's resources should be garbage collected,
153 // however on chrome there seems to be a problem as described here:
154 // https://bugs.chromium.org/p/chromium/issues/detail?id=1298955
155 this._noiseSuppressorNode?.port?.close();
156 }
157
158 this._audioDestination?.disconnect();
159 this._noiseSuppressorNode?.disconnect();
160 this._audioSource?.disconnect();
161
162 audioContext.suspend();
163 }
164 }
165
166 /**
167 * Initializes the Krisp SDK and creates the filter node.
168 *
169 * @param {INoiseSuppressionConfig} options - Krisp options.
170 *
171 * @returns {Promise<AudioWorkletNode | undefined>}
172 */
173 async function _initializeKrisp(options: INoiseSuppressionConfig): Promise<AudioWorkletNode | undefined> {
174 await audioContext.resume();
175
176 if (!krispState.sdk) {
177 const baseUrl = `${getBaseUrl()}libs/krisp`;
178 const { default: KrispSDK } = await import(/* webpackIgnore: true */ `${baseUrl}/krispsdk.mjs`);
179
180 krispState.sdk = new KrispSDK({
181 params: {
182 models: {
183 model8: `${baseUrl}/models/model_8.kw`,
184 model16: `${baseUrl}/models/model_16.kw`,
185 model32: `${baseUrl}/models/model_32.kw`
186 },
187 logProcessStats: options?.krisp?.logProcessStats,
188 debugLogs: options?.krisp?.debugLogs
189 },
190 callbacks: {}
191 });
192 }
193
194 if (!krispState.sdkInitialized) {
195 // @ts-ignore
196 await krispState.sdk?.init();
197
198 krispState.sdkInitialized = true;
199 }
200
201 if (!krispState.filterNode) {
202 try {
203 // @ts-ignore
204 krispState.filterNode = await krispState.sdk?.createNoiseFilter(audioContext, () => {
205 logger.info('Krisp audio filter ready');
206
207 // Enable audio filtering.
208 // @ts-ignore
209 krispState.filterNode?.enable();
210 krispState.filterNodeReady = true;
211 });
212 } catch (e) {
213 logger.error('Failed to create Krisp noise filter', e);
214
215 krispState.filterNode = undefined;
216 krispState.filterNodeReady = false;
217 }
218 }
219
220 return krispState.filterNode;
221 }
222
223 /**
224 * Initializes the RNNoise audio worklet and creates the filter node.
225 *
226 * @returns {Promise<AudioWorkletNode | undefined>}
227 */
228 async function _initializeKRnnoise(): Promise<AudioWorkletNode | undefined> {
229 await audioContext.resume();
230
231 const baseUrl = `${getBaseUrl()}libs/`;
232 const workletUrl = `${baseUrl}noise-suppressor-worklet.min.js`;
233
234 try {
235 await audioContext.audioWorklet.addModule(workletUrl);
236 } catch (e) {
237 logger.error('Error while adding audio worklet module: ', e);
238
239 return;
240 }
241
242 // After the resolution of module loading, an AudioWorkletNode can be constructed.
243
244 return new AudioWorkletNode(audioContext, 'NoiseSuppressorWorklet');
245 }