You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VADTalkMutedDetection.ts 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import EventEmitter from '../util/EventEmitter';
  2. import { calculateAverage } from '../util/MathUtil';
  3. import { DETECTOR_STATE_CHANGE, VAD_TALK_WHILE_MUTED } from './DetectionEvents';
  4. /**
  5. * The threshold which the average VAD values for a span of time needs to exceed to trigger an event.
  6. * @type {number}
  7. */
  8. const VAD_AVG_THRESHOLD = 0.6;
  9. /**
  10. * The VAD score needed to trigger the processing algorithm, i.e. if a sample has the VAD score >= VAD_VOICE_LEVEL
  11. * we start processing all scores for a time span defined by const PROCESS_TIME_FRAME_SPAN_MS.
  12. * @type {number}
  13. */
  14. const VAD_VOICE_LEVEL = 0.9;
  15. /**
  16. * Sample rate of TrackVADEmitter, it defines how many audio samples are processed at a time.
  17. * @type {number}
  18. */
  19. /**
  20. * Time span over which we calculate an average score used to determine if we trigger the event.
  21. * @type {number}
  22. */
  23. const PROCESS_TIME_FRAME_SPAN_MS = 700;
  24. export interface IVADScore {
  25. deviceId: string;
  26. score: number;
  27. timestamp: Date;
  28. }
  29. /**
  30. * Detect if provided VAD score which is generated on a muted device is voice and fires an event.
  31. */
  32. export default class VADTalkMutedDetection extends EventEmitter {
  33. private _processing: boolean;
  34. private _scoreArray: number[];
  35. private _active: boolean;
  36. private _processTimeout?: ReturnType<typeof setTimeout>;
  37. /**
  38. * Creates <tt>VADTalkMutedDetection</tt>
  39. * @constructor
  40. */
  41. constructor() {
  42. super();
  43. /**
  44. * Flag which denotes the current state of the detection service i.e.if there is already a processing operation
  45. * ongoing.
  46. */
  47. this._processing = false;
  48. /**
  49. * Buffer that keeps the VAD scores for a period of time.
  50. */
  51. this._scoreArray = [];
  52. /**
  53. * Current mute state of the audio track being monitored.
  54. */
  55. this._active = false;
  56. this._calculateVADScore = this._calculateVADScore.bind(this);
  57. }
  58. /**
  59. * Compute cumulative VAD score function called once the PROCESS_TIME_FRAME_SPAN_MS timeout has elapsed.
  60. * @returns {void}
  61. * @fires VAD_TALK_WHILE_MUTED
  62. */
  63. private _calculateVADScore(): void {
  64. const score = calculateAverage(new Float32Array(this._scoreArray));
  65. if (score > VAD_AVG_THRESHOLD) {
  66. this.emit(VAD_TALK_WHILE_MUTED);
  67. // Event was fired. Stop event emitter and remove listeners so no residue events kick off after this point
  68. // and a single VAD_TALK_WHILE_MUTED is generated per mic muted state.
  69. this._setActiveState(false);
  70. }
  71. // We reset the context in case a new process phase needs to be triggered.
  72. this.reset();
  73. }
  74. /**
  75. * Set the active state of the detection service and notify any listeners.
  76. *
  77. * @param {boolean} active
  78. * @fires DETECTOR_STATE_CHANGE
  79. */
  80. private _setActiveState(active: boolean): void {
  81. this._active = active;
  82. this.emit(DETECTOR_STATE_CHANGE, this._active);
  83. }
  84. /**
  85. * Change the state according to the muted status of the tracked device.
  86. *
  87. * @param {boolean} isMuted - Is the device muted or not.
  88. */
  89. public changeMuteState(isMuted: boolean): void {
  90. // This service only needs to run when the microphone is muted.
  91. this._setActiveState(isMuted);
  92. this.reset();
  93. }
  94. /**
  95. * Check whether or not the service is active or not.
  96. *
  97. * @returns {boolean}
  98. */
  99. public isActive(): boolean {
  100. return this._active;
  101. }
  102. /**
  103. * Listens for {@link TrackVADEmitter} events and processes them.
  104. *
  105. * @param {Object} vadScore -VAD score emitted by {@link TrackVADEmitter}
  106. * @param {Date} vadScore.timestamp - Exact time at which processed PCM sample was generated.
  107. * @param {number} vadScore.score - VAD score on a scale from 0 to 1 (i.e. 0.7)
  108. * @param {string} vadScore.deviceId - Device id of the associated track.
  109. * @listens VAD_SCORE_PUBLISHED
  110. */
  111. public processVADScore(vadScore: IVADScore): void {
  112. if (!this._active) {
  113. return;
  114. }
  115. // There is a processing phase on going, add score to buffer array.
  116. if (this._processing) {
  117. this._scoreArray.push(vadScore.score);
  118. return;
  119. }
  120. // Because we remove all listeners on the vadEmitter once the main event is triggered,
  121. // there is no need to check for rogue events.
  122. if (vadScore.score > VAD_VOICE_LEVEL) {
  123. this._processing = true;
  124. this._scoreArray.push(vadScore.score);
  125. // Start gathering VAD scores for the configured period of time.
  126. this._processTimeout = setTimeout(this._calculateVADScore, PROCESS_TIME_FRAME_SPAN_MS);
  127. }
  128. }
  129. /**
  130. * Reset the processing context, clear buffer, cancel the timeout trigger.
  131. *
  132. * @returns {void}
  133. */
  134. public reset(): void {
  135. this._processing = false;
  136. this._scoreArray = [];
  137. clearTimeout(this._processTimeout);
  138. }
  139. }