Przeglądaj źródła

refactor / enable VAD talk while muted

master
Andrei Gavrilescu 5 lat temu
rodzic
commit
b125bff7c7

+ 1
- 1
conference.js Wyświetl plik

@@ -1268,7 +1268,7 @@ export default {
1268 1268
         options.applicationName = interfaceConfig.APP_NAME;
1269 1269
         options.getWiFiStatsMethod = this._getWiFiStatsMethod;
1270 1270
         options.confID = `${locationURL.host}${locationURL.pathname}`;
1271
-        options.vadProcessor = createRnnoiseProcessorPromise;
1271
+        options.createVADProcessor = createRnnoiseProcessorPromise;
1272 1272
 
1273 1273
         return options;
1274 1274
     },

+ 0
- 1
react/features/base/devices/services/index.js Wyświetl plik

@@ -1,5 +1,4 @@
1 1
 export * from './device-detect/ActiveDeviceDetector';
2 2
 export * from './device-detect/Events';
3 3
 export * from './vad-reporter/Events';
4
-export * from './vad-reporter/TrackVADEmitter';
5 4
 export * from './vad-reporter/VADReportingService';

+ 9
- 6
react/features/base/devices/services/vad-reporter/Events.js Wyświetl plik

@@ -1,7 +1,10 @@
1
-// Event generated by a TrackVADEmitter when it emits a VAD score from rnnoise.
2
-// The generated objects are of type VADScore
3
-export const VAD_SCORE_PUBLISHED = 'vad-score-published';
4
-
5
-// Event generated by VADReportingService when if finishes creating a VAD report for the monitored devices.
6
-// The generated objects are of type Array<VADReportScore>, one score for each monitored device.
1
+/**
2
+ * Event generated by VADReportingService when if finishes creating a VAD report for the monitored devices.
3
+ * The generated objects are of type Array<Object>, one score for each monitored device.
4
+ * @event VAD_REPORT_PUBLISHED
5
+ * @type Array<Object> with the following structure:
6
+ * @property {Date} timestamp - Timestamp at which the compute took place.
7
+ * @property {number} avgVAD - Average VAD score over monitored period of time.
8
+ * @property {string} deviceId - Associate local audio device ID.
9
+ */
7 10
 export const VAD_REPORT_PUBLISHED = 'vad-report-published';

+ 0
- 258
react/features/base/devices/services/vad-reporter/TrackVADEmitter.js Wyświetl plik

@@ -1,258 +0,0 @@
1
-// @flow
2
-
3
-import { createRnnoiseProcessorPromise, getSampleLength } from '../../../../rnnoise';
4
-import EventEmitter from 'events';
5
-import JitsiMeetJS from '../../../lib-jitsi-meet';
6
-import logger from '../../logger';
7
-import { VAD_SCORE_PUBLISHED } from './Events';
8
-
9
-/**
10
- * The structure used by TrackVADEmitter to relay a score
11
- */
12
-export type VADScore = {
13
-
14
-    /**
15
-     * Device ID associated with the VAD score
16
-     */
17
-    deviceId: string,
18
-
19
-    /**
20
-     * The PCM score from 0 - 1 i.e. 0.60
21
-     */
22
-    score: number,
23
-
24
-    /**
25
-     * Epoch time at which PCM was recorded
26
-     */
27
-    timestamp: number
28
-
29
-};
30
-
31
-/**
32
- * Connects an audio JitsiLocalTrack to a RnnoiseProcessor using WebAudio ScriptProcessorNode.
33
- * Once an object is created audio from the local track flows through the ScriptProcessorNode as raw PCM.
34
- * The PCM is processed by the rnnoise module and a VAD (voice activity detection) score is obtained, the
35
- * score is published to consumers via an EventEmitter.
36
- * After work is done with this service the destroy method needs to be called for a proper cleanup.
37
- */
38
-export default class TrackVADEmitter extends EventEmitter {
39
-    /**
40
-     * The AudioContext instance.
41
-     */
42
-    _audioContext: AudioContext;
43
-
44
-    /**
45
-     * The MediaStreamAudioSourceNode instance.
46
-     */
47
-    _audioSource: MediaStreamAudioSourceNode;
48
-
49
-    /**
50
-     * The ScriptProcessorNode instance.
51
-     */
52
-    _audioProcessingNode: ScriptProcessorNode;
53
-
54
-    /**
55
-     * Buffer to hold residue PCM resulting after a ScriptProcessorNode callback
56
-     */
57
-    _bufferResidue: Float32Array;
58
-
59
-    /**
60
-     * State flag, check if the instance was destroyed
61
-     */
62
-    _destroyed: boolean = false;
63
-
64
-    /**
65
-     * The JitsiLocalTrack instance.
66
-     */
67
-    _localTrack: Object;
68
-
69
-    /**
70
-     * Device ID of the target microphone.
71
-     */
72
-    _micDeviceId: string;
73
-
74
-    /**
75
-     * Callback function that will be called by the ScriptProcessNode with raw PCM data, depending on the set sample
76
-     * rate.
77
-     */
78
-    _onAudioProcess: (audioEvent: Object) => void;
79
-
80
-    /**
81
-     * Sample rate of the ScriptProcessorNode.
82
-     */
83
-    _procNodeSampleRate: number;
84
-
85
-    /**
86
-     * Rnnoise adapter that allows us to calculate VAD score for PCM samples
87
-     */
88
-    _rnnoiseProcessor: Object;
89
-
90
-    /**
91
-     * PCM Sample size expected by the RnnoiseProcessor instance.
92
-     */
93
-    _rnnoiseSampleSize: number;
94
-
95
-    /**
96
-     * Constructor.
97
-     *
98
-     * @param {number} procNodeSampleRate - Sample rate of the ScriptProcessorNode. Possible values  256, 512, 1024,
99
-     *  2048, 4096, 8192, 16384. Passing other values will default to closes neighbor.
100
-     * @param {Object} rnnoiseProcessor - Rnnoise adapter that allows us to calculate VAD score
101
-     * for PCM samples.
102
-     * @param {Object} jitsiLocalTrack - JitsiLocalTrack corresponding to micDeviceId.
103
-     */
104
-    constructor(procNodeSampleRate: number, rnnoiseProcessor: Object, jitsiLocalTrack: Object) {
105
-        super();
106
-        this._procNodeSampleRate = procNodeSampleRate;
107
-        this._rnnoiseProcessor = rnnoiseProcessor;
108
-        this._localTrack = jitsiLocalTrack;
109
-        this._micDeviceId = jitsiLocalTrack.getDeviceId();
110
-        this._bufferResidue = new Float32Array([]);
111
-        this._audioContext = new AudioContext();
112
-        this._rnnoiseSampleSize = getSampleLength();
113
-        this._onAudioProcess = this._onAudioProcess.bind(this);
114
-
115
-        this._initializeAudioContext();
116
-        this._connectAudioGraph();
117
-
118
-        logger.log(`Constructed VAD emitter for device: ${this._micDeviceId}`);
119
-    }
120
-
121
-    /**
122
-     * Factory method that sets up all the necessary components for the creation of the TrackVADEmitter.
123
-     *
124
-     * @param {string} micDeviceId - Target microphone device id.
125
-     * @param {number} procNodeSampleRate - Sample rate of the proc node.
126
-     * @returns {Promise<TrackVADEmitter>} - Promise resolving in a new instance of TrackVADEmitter.
127
-     */
128
-    static async create(micDeviceId: string, procNodeSampleRate: number) {
129
-        let rnnoiseProcessor = null;
130
-        let localTrack = null;
131
-
132
-        try {
133
-            logger.log(`Initializing TrackVADEmitter for device: ${micDeviceId}`);
134
-
135
-            rnnoiseProcessor = await createRnnoiseProcessorPromise();
136
-            localTrack = await JitsiMeetJS.createLocalTracks({
137
-                devices: [ 'audio' ],
138
-                micDeviceId
139
-            });
140
-
141
-            // We only expect one audio track when specifying a device id.
142
-            if (!localTrack[0]) {
143
-                throw new Error(`Failed to create jitsi local track for device id: ${micDeviceId}`);
144
-            }
145
-
146
-            return new TrackVADEmitter(procNodeSampleRate, rnnoiseProcessor, localTrack[0]);
147
-        } catch (error) {
148
-            logger.error(`Failed to create TrackVADEmitter for ${micDeviceId} with error: ${error}`);
149
-
150
-            if (rnnoiseProcessor) {
151
-                rnnoiseProcessor.destroy();
152
-            }
153
-
154
-            if (localTrack) {
155
-                localTrack.stopStream();
156
-            }
157
-
158
-            throw error;
159
-        }
160
-    }
161
-
162
-    /**
163
-     * Sets up the audio graph in the AudioContext.
164
-     *
165
-     * @returns {Promise<void>}
166
-     */
167
-    _initializeAudioContext() {
168
-        this._audioSource = this._audioContext.createMediaStreamSource(this._localTrack.stream);
169
-
170
-        // TODO AudioProcessingNode is deprecated check and replace with alternative.
171
-        // We don't need stereo for determining the VAD score so we create a single chanel processing node.
172
-        this._audioProcessingNode = this._audioContext.createScriptProcessor(this._procNodeSampleRate, 1, 1);
173
-        this._audioProcessingNode.onaudioprocess = this._onAudioProcess;
174
-    }
175
-
176
-    /**
177
-     * ScriptProcessorNode callback, the input parameters contains the PCM audio that is then sent to rnnoise.
178
-     * Rnnoise only accepts PCM samples of 480 bytes whereas the webaudio processor node can't sample at a multiple
179
-     * of 480 thus after each _onAudioProcess callback there will remain and PCM buffer residue equal
180
-     * to _procNodeSampleRate / 480 which will be added to the next sample buffer and so on.
181
-     *
182
-     * @param {AudioProcessingEvent} audioEvent - Audio event.
183
-     * @returns {void}
184
-     */
185
-    _onAudioProcess(audioEvent: Object) {
186
-        // Prepend the residue PCM buffer from the previous process callback.
187
-        const inData = audioEvent.inputBuffer.getChannelData(0);
188
-        const completeInData = [ ...this._bufferResidue, ...inData ];
189
-        const sampleTimestamp = Date.now();
190
-
191
-        let i = 0;
192
-
193
-        for (; i + this._rnnoiseSampleSize < completeInData.length; i += this._rnnoiseSampleSize) {
194
-            const pcmSample = completeInData.slice(i, i + this._rnnoiseSampleSize);
195
-            const vadScore = this._rnnoiseProcessor.calculateAudioFrameVAD(pcmSample);
196
-
197
-            this.emit(VAD_SCORE_PUBLISHED, {
198
-                timestamp: sampleTimestamp,
199
-                score: vadScore,
200
-                deviceId: this._micDeviceId
201
-            });
202
-        }
203
-
204
-        this._bufferResidue = completeInData.slice(i, completeInData.length);
205
-    }
206
-
207
-    /**
208
-     * Connects the nodes in the AudioContext to start the flow of audio data.
209
-     *
210
-     * @returns {void}
211
-     */
212
-    _connectAudioGraph() {
213
-        this._audioSource.connect(this._audioProcessingNode);
214
-        this._audioProcessingNode.connect(this._audioContext.destination);
215
-    }
216
-
217
-    /**
218
-     * Disconnects the nodes in the AudioContext.
219
-     *
220
-     * @returns {void}
221
-     */
222
-    _disconnectAudioGraph() {
223
-        // Even thought we disconnect the processing node it seems that some callbacks remain queued,
224
-        // resulting in calls with and uninitialized context.
225
-        // eslint-disable-next-line no-empty-function
226
-        this._audioProcessingNode.onaudioprocess = () => {};
227
-        this._audioProcessingNode.disconnect();
228
-        this._audioSource.disconnect();
229
-    }
230
-
231
-    /**
232
-     * Cleanup potentially acquired resources.
233
-     *
234
-     * @returns {void}
235
-     */
236
-    _cleanupResources() {
237
-        logger.debug(`Cleaning up resources for device ${this._micDeviceId}!`);
238
-
239
-        this._disconnectAudioGraph();
240
-        this._localTrack.stopStream();
241
-        this._rnnoiseProcessor.destroy();
242
-    }
243
-
244
-    /**
245
-     * Destroy TrackVADEmitter instance (release resources and stop callbacks).
246
-     *
247
-     * @returns {void}
248
-     */
249
-    destroy() {
250
-        if (this._destroyed) {
251
-            return;
252
-        }
253
-
254
-        logger.log(`Destroying TrackVADEmitter for mic: ${this._micDeviceId}`);
255
-        this._cleanupResources();
256
-        this._destroyed = true;
257
-    }
258
-}

+ 34
- 34
react/features/base/devices/services/vad-reporter/VADReportingService.js Wyświetl plik

@@ -1,11 +1,10 @@
1 1
 // @flow
2 2
 
3
+import { createRnnoiseProcessorPromise } from '../../../../rnnoise';
3 4
 import EventEmitter from 'events';
4 5
 import logger from '../../logger';
5
-import TrackVADEmitter from './TrackVADEmitter';
6
-import { VAD_SCORE_PUBLISHED, VAD_REPORT_PUBLISHED } from './Events';
7
-import type { VADScore } from './TrackVADEmitter';
8
-export type { VADScore };
6
+import JitsiMeetJS, { JitsiDetectionEvents } from '../../../lib-jitsi-meet';
7
+import { VAD_REPORT_PUBLISHED } from './Events';
9 8
 
10 9
 /**
11 10
  * Sample rate used by TrackVADEmitter, this value determines how often the ScriptProcessorNode is going to call the
@@ -30,36 +29,14 @@ type VADDeviceContext = {
30 29
     /**
31 30
      * Array with VAD scores publish from the emitter.
32 31
      */
33
-    scoreArray: Array<VADScore>,
32
+    scoreArray: Array<Object>,
34 33
 
35 34
     /**
36 35
      * TrackVADEmitter associated with media device
37 36
      */
38
-    vadEmitter: TrackVADEmitter
37
+    vadEmitter: Object
39 38
 };
40 39
 
41
-/**
42
- * The structure used by VADReportingService to relay a score report
43
- */
44
-export type VADReportScore = {
45
-
46
-    /**
47
-     * Device ID associated with the VAD score
48
-     */
49
-    deviceId: string,
50
-
51
-    /**
52
-     * The PCM score from 0 - 1 i.e. 0.60
53
-     */
54
-    score: number,
55
-
56
-    /**
57
-     * Epoch time at which PCM was recorded
58
-     */
59
-    timestamp: number
60
-};
61
-
62
-
63 40
 /**
64 41
  * Voice activity detection reporting service. The service create TrackVADEmitters for the provided devices and
65 42
  * publishes an average of their VAD score over the specified interval via EventEmitter.
@@ -111,7 +88,7 @@ export default class VADReportingService extends EventEmitter {
111 88
      *
112 89
      * @returns {Promise<VADReportingService>}
113 90
      */
114
-    static create(micDeviceList: Array<MediaDeviceInfo>, intervalDelay: number) {
91
+    static async create(micDeviceList: Array<MediaDeviceInfo>, intervalDelay: number) {
115 92
         const vadReportingService = new VADReportingService(intervalDelay);
116 93
         const emitterPromiseArray = [];
117 94
 
@@ -125,8 +102,17 @@ export default class VADReportingService extends EventEmitter {
125 102
 
126 103
             logger.log(`Initializing VAD context for mic: ${micDevice.label} -> ${micDevice.deviceId}`);
127 104
 
128
-            const emitterPromise = TrackVADEmitter.create(micDevice.deviceId, SCRIPT_NODE_SAMPLE_RATE).then(emitter => {
129
-                emitter.on(VAD_SCORE_PUBLISHED, vadReportingService._devicePublishVADScore.bind(vadReportingService));
105
+            const rnnoiseProcessor = await createRnnoiseProcessorPromise();
106
+
107
+            const emitterPromise = JitsiMeetJS.createTrackVADEmitter(
108
+                micDevice.deviceId,
109
+                SCRIPT_NODE_SAMPLE_RATE,
110
+                rnnoiseProcessor
111
+            ).then(emitter => {
112
+                emitter.on(
113
+                    JitsiDetectionEvents.VAD_SCORE_PUBLISHED,
114
+                    vadReportingService._devicePublishVADScore.bind(vadReportingService)
115
+                );
130 116
 
131 117
                 return {
132 118
                     vadEmitter: emitter,
@@ -216,6 +202,7 @@ export default class VADReportingService extends EventEmitter {
216 202
      * Function called at set interval with selected compute. The result will be published on the set callback.
217 203
      *
218 204
      * @returns {void}
205
+     * @fires VAD_REPORT_PUBLISHED
219 206
      */
220 207
     _reportVadScore() {
221 208
         const vadComputeScoreArray = [];
@@ -243,16 +230,30 @@ export default class VADReportingService extends EventEmitter {
243 230
             });
244 231
         }
245 232
 
233
+        /**
234
+         * Once the computation for all the tracked devices is done, fire an event containing all the necessary
235
+         * information.
236
+         *
237
+         * @event VAD_REPORT_PUBLISHED
238
+         * @type Array<Object> with the following structure:
239
+         * @property {Date} timestamp - Timestamo at which the compute took place.
240
+         * @property {number} avgVAD - Average VAD score over monitored period of time.
241
+         * @property {string} deviceId - Associate local audio device ID.
242
+         */
246 243
         this.emit(VAD_REPORT_PUBLISHED, vadComputeScoreArray);
247 244
     }
248 245
 
249 246
     /**
250 247
      * Callback method passed to vad emitters in order to publish their score.
251 248
      *
252
-     * @param {VADScore} vadScore - Mic publishing the score.
249
+     * @param {Object} vadScore -VAD score emitted by.
250
+     * @param {Date}   vadScore.timestamp - Exact time at which processed PCM sample was generated.
251
+     * @param {number} vadScore.score - VAD score on a scale from 0 to 1 (i.e. 0.7).
252
+     * @param {string} vadScore.deviceId - Device id of the associated track.
253 253
      * @returns {void}
254
+     * @listens VAD_SCORE_PUBLISHED
254 255
      */
255
-    _devicePublishVADScore(vadScore: VADScore) {
256
+    _devicePublishVADScore(vadScore: Object) {
256 257
         const context = this._contextMap.get(vadScore.deviceId);
257 258
 
258 259
         if (context) {
@@ -280,5 +281,4 @@ export default class VADReportingService extends EventEmitter {
280 281
         this._clearContextMap();
281 282
         this._destroyed = true;
282 283
     }
283
-
284 284
 }

+ 1
- 0
react/features/base/lib-jitsi-meet/index.js Wyświetl plik

@@ -14,6 +14,7 @@ export const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
14 14
 export const JitsiConnectionEvents = JitsiMeetJS.events.connection;
15 15
 export const JitsiConnectionQualityEvents
16 16
     = JitsiMeetJS.events.connectionQuality;
17
+export const JitsiDetectionEvents = JitsiMeetJS.events.detection;
17 18
 export const JitsiE2ePingEvents = JitsiMeetJS.events.e2eping;
18 19
 export const JitsiMediaDevicesEvents = JitsiMeetJS.events.mediaDevices;
19 20
 export const JitsiParticipantConnectionStatus

+ 0
- 17
react/features/rnnoise/functions.js Wyświetl plik

@@ -25,20 +25,3 @@ export function createRnnoiseProcessorPromise() {
25 25
         throw new Error('Rnnoise module binding createRnnoiseProcessor not found!');
26 26
     });
27 27
 }
28
-
29
-/**
30
- * Get the accepted sample length for the rnnoise library. We might want to expose it with flow libdefs.
31
- *
32
- * @returns {number}
33
- */
34
-export function getSampleLength() {
35
-    const ns = getJitsiMeetGlobalNS();
36
-
37
-    const rnnoiseSample = ns?.effects?.rnnoise?.RNNOISE_SAMPLE_LENGTH;
38
-
39
-    if (!rnnoiseSample) {
40
-        throw new Error('Please call createRnnoiseProcessorPromise first or wait for promise to resolve!');
41
-    }
42
-
43
-    return rnnoiseSample;
44
-}

+ 16
- 2
react/features/stream-effects/rnnoise/RnnoiseProcessor.js Wyświetl plik

@@ -10,6 +10,11 @@ export const RNNOISE_SAMPLE_LENGTH: number = 480;
10 10
  */
11 11
 const RNNOISE_BUFFER_SIZE: number = RNNOISE_SAMPLE_LENGTH * 4;
12 12
 
13
+/**
14
+ *  Constant. Rnnoise only takes operates on 44.1Khz float 32 little endian PCM.
15
+ */
16
+const PCM_FREQUENCY: number = 44100;
17
+
13 18
 /**
14 19
  * Represents an adaptor for the rnnoise library compiled to webassembly. The class takes care of webassembly
15 20
  * memory management and exposes rnnoise functionality such as PCM audio denoising and VAD (voice activity
@@ -132,14 +137,23 @@ export default class RnnoiseProcessor {
132 137
     }
133 138
 
134 139
     /**
135
-     * Such comment very wow.
140
+     * Rnnoise can only operate on a certain PCM array size.
136 141
      *
137
-     * @returns {number}
142
+     * @returns {number} - The PCM sample array size as required by rnnoise.
138 143
      */
139 144
     getSampleLength() {
140 145
         return RNNOISE_SAMPLE_LENGTH;
141 146
     }
142 147
 
148
+    /**
149
+     * Rnnoise can only operate on a certain format of PCM sample namely float 32 44.1Kz.
150
+     *
151
+     * @returns {number} - PCM sample frequency as required by rnnoise.
152
+     */
153
+    getRequiredPCMFrequency() {
154
+        return PCM_FREQUENCY;
155
+    }
156
+
143 157
     /**
144 158
      * Release any resources required by the rnnoise context this needs to be called
145 159
      * before destroying any context that uses the processor.

+ 2
- 6
webpack.config.js Wyświetl plik

@@ -33,14 +33,12 @@ function getPerformanceHints(size) {
33 33
 const config = {
34 34
     devServer: {
35 35
         https: true,
36
-        logLevel: 'debug',
37 36
         inline: true,
38 37
         proxy: {
39 38
             '/': {
40 39
                 bypass: devServerProxyBypass,
41 40
                 secure: false,
42
-                target: devServerProxyTarget,
43
-                logLevel: 'debug'
41
+                target: devServerProxyTarget
44 42
             }
45 43
         }
46 44
     },
@@ -278,13 +276,11 @@ module.exports = [
278 276
  * target, undefined; otherwise, the path to the local file to be served.
279 277
  */
280 278
 function devServerProxyBypass({ path }) {
281
-    console.log('Fetching path: ', path);
282 279
     if (path.startsWith('/css/') || path.startsWith('/doc/')
283 280
             || path.startsWith('/fonts/') || path.startsWith('/images/')
284 281
             || path.startsWith('/sounds/')
285 282
             || path.startsWith('/static/')
286
-            || path.endsWith('.wasm')
287
-            || path.startsWith('/libs/lib-jitsi-meet')) {
283
+            || path.endsWith('.wasm')) {
288 284
         return path;
289 285
     }
290 286
 

Ładowanie…
Anuluj
Zapisz