Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

VADAudioAnalyser.js 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import { getLogger } from '@jitsi/logger';
  2. import { EventEmitter } from 'events';
  3. import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
  4. import { DETECTOR_STATE_CHANGE, VAD_SCORE_PUBLISHED } from './DetectionEvents';
  5. import TrackVADEmitter from './TrackVADEmitter';
  6. const logger = getLogger(__filename);
  7. /**
  8. * Sample rate of TrackVADEmitter, it defines how many audio samples are processed at a time.
  9. * @type {number}
  10. */
  11. const VAD_EMITTER_SAMPLE_RATE = 4096;
  12. /**
  13. * Connects a TrackVADEmitter to the target conference local audio track and manages various services that use
  14. * the data to produce audio analytics (VADTalkMutedDetection and VADNoiseDetection).
  15. */
  16. export default class VADAudioAnalyser extends EventEmitter {
  17. /**
  18. * Creates <tt>VADAudioAnalyser</tt>
  19. * @param {JitsiConference} conference - JitsiConference instance that created us.
  20. * @param {Object} createVADProcessor - Function that creates a Voice activity detection processor. The processor
  21. * needs to implement the following functions:
  22. * - <tt>getSampleLength()</tt> - Returns the sample size accepted by getSampleLength.
  23. * - <tt>getRequiredPCMFrequency()</tt> - Returns the PCM frequency at which the processor operates.
  24. * - <tt>calculateAudioFrameVAD(pcmSample)</tt> - Process a 32 float pcm sample of getSampleLength size.
  25. * @constructor
  26. */
  27. constructor(conference, createVADProcessor) {
  28. super();
  29. /**
  30. * Member function that instantiates a VAD processor.
  31. */
  32. this._createVADProcessor = createVADProcessor;
  33. /**
  34. * Current {@link TrackVADEmitter}. VAD Emitter uses a {@link JitsiLocalTrack} and VAD processor to generate
  35. * period voice probability scores.
  36. */
  37. this._vadEmitter = null;
  38. /**
  39. * Current state of the _vadEmitter
  40. */
  41. this._isVADEmitterRunning = false;
  42. /**
  43. * Array of currently attached VAD processing services.
  44. */
  45. this._detectionServices = [];
  46. /**
  47. * Promise used to chain create and destroy operations associated with TRACK_ADDED and TRACK_REMOVED events
  48. * coming from the conference.
  49. * Because we have an async created component (VAD Processor) we need to make sure that it's initialized before
  50. * we destroy it ( when changing the device for instance), or when we use it from an external point of entry
  51. * i.e. (TRACK_MUTE_CHANGED event callback).
  52. */
  53. this._vadInitTracker = Promise.resolve();
  54. /**
  55. * Listens for {@link TrackVADEmitter} events and processes them.
  56. */
  57. this._processVADScore = this._processVADScore.bind(this);
  58. conference.on(JitsiConferenceEvents.TRACK_ADDED, this._trackAdded.bind(this));
  59. conference.on(JitsiConferenceEvents.TRACK_REMOVED, this._trackRemoved.bind(this));
  60. conference.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, this._trackMuteChanged.bind(this));
  61. }
  62. /**
  63. * Attach a VAD detector service to the analyser and handle it's state changes.
  64. *
  65. * @param {Object} vadTMDetector
  66. */
  67. addVADDetectionService(vadService) {
  68. this._detectionServices.push(vadService);
  69. vadService.on(DETECTOR_STATE_CHANGE, () => {
  70. // When the state of a detector changes check if there are any active detectors attached so that
  71. // the _vadEmitter doesn't run needlessly.
  72. const activeDetector = this._detectionServices.filter(detector => detector.isActive() === true);
  73. // If there are no active detectors running and the vadEmitter is running then stop the emitter as it is
  74. // uses a considerable amount of CPU. Otherwise start the service if it's stopped and there is a detector
  75. // that needs it.
  76. if (!activeDetector.length && this._isVADEmitterRunning) {
  77. this._stopVADEmitter();
  78. } else if (!this._isVADEmitterRunning) {
  79. this._startVADEmitter();
  80. }
  81. });
  82. }
  83. /**
  84. * Start the {@link TrackVADEmitter} and attach the event listener.
  85. * @returns {void}
  86. */
  87. _startVADEmitter() {
  88. if (this._vadEmitter) {
  89. this._vadEmitter.on(VAD_SCORE_PUBLISHED, this._processVADScore);
  90. this._vadEmitter.start();
  91. this._isVADEmitterRunning = true;
  92. }
  93. }
  94. /**
  95. * Stop the {@link TrackVADEmitter} and detach the event listener.
  96. * @returns {void}
  97. */
  98. _stopVADEmitter() {
  99. if (this._vadEmitter) {
  100. this._vadEmitter.removeListener(VAD_SCORE_PUBLISHED, this._processVADScore);
  101. this._vadEmitter.stop();
  102. }
  103. this._isVADEmitterRunning = false;
  104. }
  105. /**
  106. * Listens for {@link TrackVADEmitter} events and directs them to attached services as needed.
  107. *
  108. * @param {Object} vadScore -VAD score emitted by {@link TrackVADEmitter}
  109. * @param {Date} vadScore.timestamp - Exact time at which processed PCM sample was generated.
  110. * @param {number} vadScore.score - VAD score on a scale from 0 to 1 (i.e. 0.7)
  111. * @param {Float32Array} pcmData - Raw PCM data with which the VAD score was calculated.
  112. * @param {string} vadScore.deviceId - Device id of the associated track.
  113. * @listens VAD_SCORE_PUBLISHED
  114. */
  115. _processVADScore(vadScore) {
  116. for (const detector of this._detectionServices) {
  117. detector.processVADScore(vadScore);
  118. }
  119. }
  120. /**
  121. * Change the isMuted state of all attached detection services.
  122. *
  123. * @param {boolean} isMuted
  124. */
  125. _changeDetectorsMuteState(isMuted) {
  126. for (const detector of this._detectionServices) {
  127. detector.changeMuteState(isMuted);
  128. }
  129. }
  130. /**
  131. * Notifies the detector that a track was added to the associated {@link JitsiConference}.
  132. * Only take into account local audio tracks.
  133. * @param {JitsiTrack} track - The added track.
  134. * @returns {void}
  135. * @listens TRACK_ADDED
  136. */
  137. _trackAdded(track) {
  138. if (track.isLocalAudioTrack()) {
  139. // Keep a track promise so we take into account successive TRACK_ADD events being generated so that we
  140. // destroy/create the processing context in the proper order.
  141. this._vadInitTracker = this._vadInitTracker.then(() => this._createVADProcessor())
  142. .then(vadProcessor =>
  143. TrackVADEmitter.create(track.getDeviceId(), VAD_EMITTER_SAMPLE_RATE, vadProcessor)
  144. )
  145. .then(vadEmitter => {
  146. logger.debug('Created VAD emitter for track: ', track.getTrackLabel());
  147. this._vadEmitter = vadEmitter;
  148. // Iterate through the detection services and set their appropriate mute state, depending on
  149. // service this will trigger a DETECTOR_STATE_CHANGE which in turn might start the _vadEmitter.
  150. this._changeDetectorsMuteState(track.isMuted());
  151. })
  152. .catch(error => {
  153. logger.warn('Failed to start VADAudioAnalyser', error);
  154. });
  155. }
  156. }
  157. /**
  158. * Notifies the detector that the mute state of a {@link JitsiConference} track has changed. Only takes into account
  159. * local audio tracks.
  160. * @param {JitsiTrack} track - The track whose mute state has changed.
  161. * @returns {void}
  162. * @listens TRACK_MUTE_CHANGED
  163. */
  164. _trackMuteChanged(track) {
  165. if (track.isLocalAudioTrack()) {
  166. // On a mute toggle reset the state.
  167. this._vadInitTracker = this._vadInitTracker.then(() => {
  168. // Set mute status for the attached detection services.
  169. this._changeDetectorsMuteState(track.isMuted());
  170. });
  171. }
  172. }
  173. /**
  174. * Notifies the detector that a track associated with the {@link JitsiConference} was removed. Only takes into
  175. * account local audio tracks. Cleans up resources associated with the track and resets the processing context.
  176. *
  177. * @param {JitsiTrack} track - The removed track.
  178. * @returns {void}
  179. * @listens TRACK_REMOVED
  180. */
  181. _trackRemoved(track) {
  182. if (track.isLocalAudioTrack()) {
  183. // Use the promise to make sure operations are in sequence.
  184. this._vadInitTracker = this._vadInitTracker.then(() => {
  185. logger.debug('Removing track from VAD detection - ', track.getTrackLabel());
  186. // Track was removed, clean up and set appropriate states.
  187. if (this._vadEmitter) {
  188. this._stopVADEmitter();
  189. this._vadEmitter.destroy();
  190. this._vadEmitter = null;
  191. }
  192. // Reset state of detectors when active track is removed.
  193. for (const detector of this._detectionServices) {
  194. detector.reset();
  195. }
  196. });
  197. }
  198. }
  199. }