modified lib-jitsi-meet dev repo
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.

TrackVADEmitter.js 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import EventEmitter from 'events';
  2. import RTC from '../RTC/RTC';
  3. import { VAD_SCORE_PUBLISHED } from './DetectionEvents';
  4. /**
  5. * Connects an audio JitsiLocalTrack to a vadProcessor using WebAudio ScriptProcessorNode.
  6. * Once an object is created audio from the local track flows through the ScriptProcessorNode as raw PCM.
  7. * The PCM is processed by the injected vad module and a voice activity detection score is obtained, the
  8. * score is published to consumers via an EventEmitter.
  9. * After work is done with this service the destroy method needs to be called for a proper cleanup.
  10. */
  11. export default class TrackVADEmitter extends EventEmitter {
  12. /**
  13. * Constructor.
  14. *
  15. * @param {number} procNodeSampleRate - Sample rate of the ScriptProcessorNode. Possible values 256, 512, 1024,
  16. * 2048, 4096, 8192, 16384. Passing other values will default to closes neighbor.
  17. * @param {Object} vadProcessor - adapter that allows us to calculate VAD score
  18. * for PCM samples.
  19. * @param {Object} jitsiLocalTrack - JitsiLocalTrack corresponding to micDeviceId.
  20. */
  21. constructor(procNodeSampleRate, vadProcessor, jitsiLocalTrack) {
  22. super();
  23. this._procNodeSampleRate = procNodeSampleRate;
  24. this._vadProcessor = vadProcessor;
  25. this._localTrack = jitsiLocalTrack;
  26. this._micDeviceId = jitsiLocalTrack.getDeviceId();
  27. this._bufferResidue = new Float32Array([]);
  28. this._audioContext = new AudioContext({ sampleRate: 44100 });
  29. this._vadSampleSize = vadProcessor.getSampleLength();
  30. this._onAudioProcess = this._onAudioProcess.bind(this);
  31. this._initializeAudioContext();
  32. this._connectAudioGraph();
  33. }
  34. /**
  35. * Factory method that sets up all the necessary components for the creation of the TrackVADEmitter.
  36. *
  37. * @param {string} micDeviceId - Target microphone device id.
  38. * @param {number} procNodeSampleRate - Sample rate of the proc node.
  39. * @returns {Promise<TrackVADEmitter>} - Promise resolving in a new instance of TrackVADEmitter.
  40. */
  41. static create(micDeviceId, procNodeSampleRate, vadProcessor) {
  42. return RTC.obtainAudioAndVideoPermissions({
  43. devices: [ 'audio' ],
  44. micDeviceId
  45. }).then(localTrack => {
  46. // We only expect one audio track when specifying a device id.
  47. if (!localTrack[0]) {
  48. throw new Error(`Failed to create jitsi local track for device id: ${micDeviceId}`);
  49. }
  50. return new TrackVADEmitter(procNodeSampleRate, vadProcessor, localTrack[0]);
  51. // We have no exception handling at this point as there is nothing to clean up, the vadProcessor
  52. // life cycle is handled by whoever created this instance.
  53. });
  54. }
  55. /**
  56. * Sets up the audio graph in the AudioContext.
  57. *
  58. * @returns {Promise<void>}
  59. */
  60. _initializeAudioContext() {
  61. this._audioSource = this._audioContext.createMediaStreamSource(this._localTrack.stream);
  62. // TODO AudioProcessingNode is deprecated check and replace with alternative.
  63. // We don't need stereo for determining the VAD score so we create a single channel processing node.
  64. this._audioProcessingNode = this._audioContext.createScriptProcessor(this._procNodeSampleRate, 1, 1);
  65. this._audioProcessingNode.onaudioprocess = this._onAudioProcess;
  66. }
  67. /**
  68. * TODO maybe move this logic to the VAD Processor.
  69. * ScriptProcessorNode callback, the input parameters contains the PCM audio that is then sent to rnnoise.
  70. * Rnnoise only accepts PCM samples of 480 bytes whereas the webaudio processor node can't sample at a multiple
  71. * of 480 thus after each _onAudioProcess callback there will remain and PCM buffer residue equal
  72. * to _procNodeSampleRate / 480 which will be added to the next sample buffer and so on.
  73. *
  74. * @param {AudioProcessingEvent} audioEvent - Audio event.
  75. * @returns {void}
  76. */
  77. _onAudioProcess(audioEvent) {
  78. // Prepend the residue PCM buffer from the previous process callback.
  79. const inData = audioEvent.inputBuffer.getChannelData(0);
  80. const completeInData = [ ...this._bufferResidue, ...inData ];
  81. const sampleTimestamp = Date.now();
  82. let i = 0;
  83. for (; i + this._vadSampleSize < completeInData.length; i += this._vadSampleSize) {
  84. const pcmSample = completeInData.slice(i, i + this._vadSampleSize);
  85. const vadScore = this._vadProcessor.calculateAudioFrameVAD(pcmSample);
  86. this.emit(VAD_SCORE_PUBLISHED, {
  87. timestamp: sampleTimestamp,
  88. score: vadScore,
  89. deviceId: this._micDeviceId
  90. });
  91. }
  92. this._bufferResidue = completeInData.slice(i, completeInData.length);
  93. }
  94. /**
  95. * Connects the nodes in the AudioContext to start the flow of audio data.
  96. *
  97. * @returns {void}
  98. */
  99. _connectAudioGraph() {
  100. this._audioSource.connect(this._audioProcessingNode);
  101. this._audioProcessingNode.connect(this._audioContext.destination);
  102. }
  103. /**
  104. * Disconnects the nodes in the AudioContext.
  105. *
  106. * @returns {void}
  107. */
  108. _disconnectAudioGraph() {
  109. // Even thought we disconnect the processing node it seems that some callbacks remain queued,
  110. // resulting in calls with and uninitialized context.
  111. // eslint-disable-next-line no-empty-function
  112. this._audioProcessingNode.onaudioprocess = () => {};
  113. this._audioProcessingNode.disconnect();
  114. this._audioSource.disconnect();
  115. }
  116. /**
  117. * Cleanup potentially acquired resources.
  118. *
  119. * @returns {void}
  120. */
  121. _cleanupResources() {
  122. this._disconnectAudioGraph();
  123. this._localTrack.stopStream();
  124. }
  125. /**
  126. * Destroy TrackVADEmitter instance (release resources and stop callbacks).
  127. *
  128. * @returns {void}
  129. */
  130. destroy() {
  131. if (this._destroyed) {
  132. return;
  133. }
  134. this._cleanupResources();
  135. this._destroyed = true;
  136. }
  137. }