sources/webrtc-reviewed/src_front/src/audio/vad.ts

Текстовый файл из учебного комплекта, не свидетельство установленного runtime. Вернуться к учебнику · Исходный файл

   1 import type { AppConfig } from "../config/appConfig";
   2 
   3 export type VadInstance = { track: MediaStreamTrack; stop: () => void };
   4 
   5 export function createEnergyVad(
   6   sourceTrack: MediaStreamTrack,
   7   config: AppConfig,
   8   getThresholdDb: () => number,
   9   onThresholdText?: (value: number) => void,
  10   onSpeechActivityChange?: (active: boolean) => void,
  11 ): VadInstance {
  12   const ctx = new AudioContext();
  13   const src = ctx.createMediaStreamSource(new MediaStream([sourceTrack]));
  14   const analyser = ctx.createAnalyser();
  15   analyser.fftSize = config.audio.vad.fftSize;
  16 
  17   const gainNode = ctx.createGain();
  18   gainNode.gain.value = 1.0;
  19 
  20   src.connect(analyser);
  21   analyser.connect(gainNode);
  22 
  23   const dest = ctx.createMediaStreamDestination();
  24   gainNode.connect(dest);
  25 
  26   const data = new Float32Array(analyser.fftSize);
  27   let stopped = false;
  28   let lastActive: boolean | null = null;
  29 
  30   const tick = () => {
  31     if (stopped) return;
  32 
  33     analyser.getFloatTimeDomainData(data);
  34     let sum = 0;
  35     for (let i = 0; i < data.length; i += 1) sum += data[i] * data[i];
  36 
  37     const rms = Math.sqrt(sum / data.length);
  38     const db = 20 * Math.log10(rms + 1e-12);
  39 
  40     const gate = getThresholdDb();
  41     onThresholdText?.(Math.round(gate));
  42 
  43     const active = db > gate;
  44     if (lastActive === null) {
  45       lastActive = active;
  46       if (active) {
  47         onSpeechActivityChange?.(true);
  48       }
  49     } else if (lastActive !== active) {
  50       lastActive = active;
  51       onSpeechActivityChange?.(active);
  52     }
  53 
  54     const target = active ? 1.0 : 0.0;
  55     const T = active ? config.audio.vad.attackSeconds : config.audio.vad.releaseSeconds;
  56 
  57     if (gainNode.gain.value !== target) {
  58       gainNode.gain.setTargetAtTime(target, ctx.currentTime, T);
  59     }
  60 
  61     requestAnimationFrame(tick);
  62   };
  63 
  64   requestAnimationFrame(tick);
  65 
  66   const outputTrack = dest.stream.getAudioTracks()[0];
  67 
  68   return {
  69     track: outputTrack,
  70     stop: () => {
  71       stopped = true;
  72       src.disconnect();
  73       analyser.disconnect();
  74       gainNode.disconnect();
  75       void ctx.close();
  76     },
  77   };
  78 }