"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 }