2022-09-06 17:32:20 +00:00
|
|
|
import { getBaseUrl } from '../../base/util/helpers';
|
2022-07-20 12:31:17 +00:00
|
|
|
|
|
|
|
import logger from './logger';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class Implementing the effect interface expected by a JitsiLocalTrack.
|
|
|
|
* Effect applies rnnoise denoising on a audio JitsiLocalTrack.
|
|
|
|
*/
|
|
|
|
export class NoiseSuppressionEffect {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Web audio context.
|
|
|
|
*/
|
|
|
|
private _audioContext: AudioContext;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Source that will be attached to the track affected by the effect.
|
|
|
|
*/
|
|
|
|
private _audioSource: MediaStreamAudioSourceNode;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destination that will contain denoised audio from the audio worklet.
|
|
|
|
*/
|
|
|
|
private _audioDestination: MediaStreamAudioDestinationNode;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* `AudioWorkletProcessor` associated node.
|
|
|
|
*/
|
|
|
|
private _noiseSuppressorNode: AudioWorkletNode;
|
|
|
|
|
2022-08-01 08:58:37 +00:00
|
|
|
/**
|
|
|
|
* Audio track extracted from the original MediaStream to which the effect is applied.
|
|
|
|
*/
|
|
|
|
private _originalMediaTrack: MediaStreamTrack;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Noise suppressed audio track extracted from the media destination node.
|
|
|
|
*/
|
|
|
|
private _outputMediaTrack: MediaStreamTrack;
|
|
|
|
|
2022-07-20 12:31:17 +00:00
|
|
|
/**
|
|
|
|
* Effect interface called by source JitsiLocalTrack.
|
|
|
|
* Applies effect that uses a {@code NoiseSuppressor} service initialized with {@code RnnoiseProcessor}
|
|
|
|
* for denoising.
|
|
|
|
*
|
|
|
|
* @param {MediaStream} audioStream - Audio stream which will be mixed with _mixAudio.
|
|
|
|
* @returns {MediaStream} - MediaStream containing both audio tracks mixed together.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
startEffect(audioStream: MediaStream): MediaStream {
|
2022-08-01 08:58:37 +00:00
|
|
|
this._originalMediaTrack = audioStream.getAudioTracks()[0];
|
2022-07-20 12:31:17 +00:00
|
|
|
this._audioContext = new AudioContext();
|
|
|
|
this._audioSource = this._audioContext.createMediaStreamSource(audioStream);
|
|
|
|
this._audioDestination = this._audioContext.createMediaStreamDestination();
|
2022-08-01 08:58:37 +00:00
|
|
|
this._outputMediaTrack = this._audioDestination.stream.getAudioTracks()[0];
|
2022-07-20 12:31:17 +00:00
|
|
|
|
|
|
|
const baseUrl = `${getBaseUrl()}libs/`;
|
|
|
|
const workletUrl = `${baseUrl}noise-suppressor-worklet.min.js`;
|
|
|
|
|
|
|
|
// Connect the audio processing graph MediaStream -> AudioWorkletNode -> MediaStreamAudioDestinationNode
|
|
|
|
this._audioContext.audioWorklet.addModule(workletUrl)
|
|
|
|
.then(() => {
|
|
|
|
// After the resolution of module loading, an AudioWorkletNode can be constructed.
|
|
|
|
this._noiseSuppressorNode = new AudioWorkletNode(this._audioContext, 'NoiseSuppressorWorklet');
|
|
|
|
this._audioSource.connect(this._noiseSuppressorNode).connect(this._audioDestination);
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
logger.error('Error while adding audio worklet module: ', error);
|
|
|
|
});
|
|
|
|
|
2022-08-01 08:58:37 +00:00
|
|
|
// Sync the effect track muted state with the original track state.
|
|
|
|
this._outputMediaTrack.enabled = this._originalMediaTrack.enabled;
|
|
|
|
|
|
|
|
// We enable the audio on the original track because mute/unmute action will only affect the audio destination
|
|
|
|
// output track from this point on.
|
|
|
|
this._originalMediaTrack.enabled = true;
|
|
|
|
|
2022-07-20 12:31:17 +00:00
|
|
|
return this._audioDestination.stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the JitsiLocalTrack supports this effect.
|
|
|
|
*
|
|
|
|
* @param {JitsiLocalTrack} sourceLocalTrack - Track to which the effect will be applied.
|
|
|
|
* @returns {boolean} - Returns true if this effect can run on the specified track, false otherwise.
|
|
|
|
*/
|
|
|
|
isEnabled(sourceLocalTrack: any): boolean {
|
|
|
|
// JitsiLocalTracks needs to be an audio track.
|
|
|
|
return sourceLocalTrack.isAudioTrack();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clean up resources acquired by noise suppressor and rnnoise processor.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
stopEffect(): void {
|
2022-08-01 08:58:37 +00:00
|
|
|
// Sync original track muted state with effect state before removing the effect.
|
|
|
|
this._originalMediaTrack.enabled = this._outputMediaTrack.enabled;
|
|
|
|
|
2022-07-20 12:31:17 +00:00
|
|
|
// Technically after this process the Audio Worklet along with it's resources should be garbage collected,
|
|
|
|
// however on chrome there seems to be a problem as described here:
|
|
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1298955
|
|
|
|
this._noiseSuppressorNode?.port?.close();
|
|
|
|
this._audioDestination?.disconnect();
|
|
|
|
this._noiseSuppressorNode?.disconnect();
|
|
|
|
this._audioSource?.disconnect();
|
|
|
|
this._audioContext?.close();
|
|
|
|
}
|
|
|
|
}
|